branch: add a --copy (-c) option to go with --move (-m)

Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Sahil Dua committed Jun 18, 2017 at 23:19 UTC 52d59cc6452ed1aeec91f4c168a853ea8d9d5496
9 files changed +424 -46
Documentation/git-branch.txt
+13 -1
@@ -18,6 +18,7 @@ SYNOPSIS
18 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
19 'git branch' --unset-upstream [<branchname>]
20 'git branch' (-m | -M) [<oldbranch>] <newbranch>
21 +'git branch' (-c | -C) [<oldbranch>] <newbranch>
22 'git branch' (-d | -D) [-r] <branchname>...
23 'git branch' --edit-description [<branchname>]
24
@@ -64,6 +65,10 @@ If <oldbranch> had a corresponding reflog, it is renamed to match
65 renaming. If <newbranch> exists, -M must be used to force the rename
66 to happen.
67
68 +The `-c` and `-C` options have the exact same semantics as `-m` and
69 +`-M`, except instead of the branch being renamed it along with its
70 +config and reflog will be copied to a new name.
71 +
72 With a `-d` or `-D` option, `<branchname>` will be deleted. You may
73 specify more than one branch for deletion. If the branch currently
74 has a reflog then the reflog will also be deleted.
@@ -104,7 +109,7 @@ OPTIONS
109 In combination with `-d` (or `--delete`), allow deleting the
110 branch irrespective of its merged status. In combination with
111 `-m` (or `--move`), allow renaming the branch even if the new
107 - branch name already exists.
112 + branch name already exists, the same applies for `-c` (or `--copy`).
113
114 -m::
115 --move::
@@ -113,6 +118,13 @@ OPTIONS
118 -M::
119 Shortcut for `--move --force`.
120
121 +-c::
122 +--copy::
123 + Copy a branch and the corresponding reflog.
124 +
125 +-C::
126 + Shortcut for `--copy --force`.
127 +
128 --color[=<when>]::
129 Color branches to highlight current, local, and
130 remote-tracking branches.
builtin/branch.c
+51 -16
@@ -27,6 +27,7 @@ static const char * const builtin_branch_usage[] = {
27 N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
28 N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
29 N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
30 + N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
31 N_("git branch [<options>] [-r | -a] [--points-at]"),
32 N_("git branch [<options>] [-r | -a] [--format]"),
33 NULL
@@ -449,15 +450,19 @@ static void reject_rebase_or_bisect_branch(const char *target)
450 free_worktrees(worktrees);
451 }
452
452 -static void rename_branch(const char *oldname, const char *newname, int force)
453 +static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
454 {
455 struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
456 struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
457 int recovery = 0;
458 int clobber_head_ok;
459
459 - if (!oldname)
460 - die(_("cannot rename the current branch while not on any."));
460 + if (!oldname) {
461 + if (copy)
462 + die(_("cannot copy the current branch while not on any."));
463 + else
464 + die(_("cannot rename the current branch while not on any."));
465 + }
466
467 if (strbuf_check_branch_ref(&oldref, oldname)) {
468 /*
@@ -480,17 +485,33 @@ static void rename_branch(const char *oldname, const char *newname, int force)
485
486 reject_rebase_or_bisect_branch(oldref.buf);
487
483 - strbuf_addf(&logmsg, "Branch: renamed %s to %s",
484 - oldref.buf, newref.buf);
488 + if (copy)
489 + strbuf_addf(&logmsg, "Branch: copied %s to %s",
490 + oldref.buf, newref.buf);
491 + else
492 + strbuf_addf(&logmsg, "Branch: renamed %s to %s",
493 + oldref.buf, newref.buf);
494
486 - if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
495 + if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf))
496 die(_("Branch rename failed"));
497 + if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
498 + die(_("Branch copy failed"));
499
489 - if (recovery)
490 - warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
500 + if (recovery) {
501 + if (copy)
502 + warning(_("Copied a misnamed branch '%s' away"),
503 + oldref.buf + 11);
504 + else
505 + warning(_("Renamed a misnamed branch '%s' away"),
506 + oldref.buf + 11);
507 + }
508
492 - if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
493 - die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
509 + if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) {
510 + if (copy)
511 + die(_("Branch copied to %s, but HEAD is not updated!"), newname);
512 + else
513 + die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
514 + }
515
516 strbuf_release(&logmsg);
517
@@ -498,8 +519,10 @@ static void rename_branch(const char *oldname, const char *newname, int force)
519 strbuf_release(&oldref);
520 strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
521 strbuf_release(&newref);
501 - if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
522 + if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
523 die(_("Branch is renamed, but update of config-file failed"));
524 + if (copy && strcmp(oldname, newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0)
525 + die(_("Branch is copied, but update of config-file failed"));
526 strbuf_release(&oldsection);
527 strbuf_release(&newsection);
528 }
@@ -537,7 +560,7 @@ static int edit_branch_description(const char *branch_name)
560
561 int cmd_branch(int argc, const char **argv, const char *prefix)
562 {
540 - int delete = 0, rename = 0, force = 0, list = 0;
563 + int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
564 int reflog = 0, edit_description = 0;
565 int quiet = 0, unset_upstream = 0;
566 const char *new_upstream = NULL;
@@ -574,6 +597,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
597 OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
598 OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
599 OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
600 + OPT_BIT('c', "copy", &copy, N_("copy a branch and its reflog"), 1),
601 + OPT_BIT('C', NULL, &copy, N_("copy a branch, even if target exists"), 2),
602 OPT_BOOL(0, "list", &list, N_("list branch names")),
603 OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")),
604 OPT_BOOL(0, "edit-description", &edit_description,
@@ -617,14 +642,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
642 argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
643 0);
644
620 - if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
645 + if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0)
646 list = 1;
647
648 if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
649 filter.no_commit)
650 list = 1;
651
627 - if (!!delete + !!rename + !!new_upstream +
652 + if (!!delete + !!rename + !!copy + !!new_upstream +
653 list + unset_upstream > 1)
654 usage_with_options(builtin_branch_usage, options);
655
@@ -642,6 +667,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
667 if (force) {
668 delete *= 2;
669 rename *= 2;
670 + copy *= 2;
671 }
672
673 if (delete) {
@@ -696,13 +722,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
722
723 if (edit_branch_description(branch_name))
724 return 1;
725 + } else if (copy) {
726 + if (!argc)
727 + die(_("branch name required"));
728 + else if (argc == 1)
729 + copy_or_rename_branch(head, argv[0], 1, copy > 1);
730 + else if (argc == 2)
731 + copy_or_rename_branch(argv[0], argv[1], 1, copy > 1);
732 + else
733 + die(_("too many branches for a copy operation"));
734 } else if (rename) {
735 if (!argc)
736 die(_("branch name required"));
737 else if (argc == 1)
703 - rename_branch(head, argv[0], rename > 1);
738 + copy_or_rename_branch(head, argv[0], 0, rename > 1);
739 else if (argc == 2)
705 - rename_branch(argv[0], argv[1], rename > 1);
740 + copy_or_rename_branch(argv[0], argv[1], 0, rename > 1);
741 else
742 die(_("too many branches for a rename operation"));
743 } else if (new_upstream) {
cache.h
+2
@@ -1941,6 +1941,8 @@ extern int git_config_set_multivar_in_file_gently(const char *, const char *, co
1941 extern void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
1942 extern int git_config_rename_section(const char *, const char *);
1943 extern int git_config_rename_section_in_file(const char *, const char *, const char *);
1944 +extern int git_config_copy_section(const char *, const char *);
1945 +extern int git_config_copy_section_in_file(const char *, const char *, const char *);
1946 extern const char *git_etc_gitconfig(void);
1947 extern int git_env_bool(const char *, int);
1948 extern unsigned long git_env_ulong(const char *, unsigned long);
config.c
+82 -20
@@ -2638,8 +2638,8 @@ static int section_name_is_ok(const char *name)
2638 }
2639
2640 /* if new_name == NULL, the section is removed instead */
2641 -int git_config_rename_section_in_file(const char *config_filename,
2642 - const char *old_name, const char *new_name)
2641 +static int git_config_copy_or_rename_section_in_file(const char *config_filename,
2642 + const char *old_name, const char *new_name, int copy)
2643 {
2644 int ret = 0, remove = 0;
2645 char *filename_buf = NULL;
@@ -2648,6 +2648,7 @@ int git_config_rename_section_in_file(const char *config_filename,
2648 char buf[1024];
2649 FILE *config_file = NULL;
2650 struct stat st;
2651 + struct strbuf copystr = STRBUF_INIT;
2652
2653 if (new_name && !section_name_is_ok(new_name)) {
2654 ret = error("invalid section name: %s", new_name);
@@ -2683,12 +2684,30 @@ int git_config_rename_section_in_file(const char *config_filename,
2684 while (fgets(buf, sizeof(buf), config_file)) {
2685 int i;
2686 int length;
2687 + int is_section = 0;
2688 char *output = buf;
2689 for (i = 0; buf[i] && isspace(buf[i]); i++)
2690 ; /* do nothing */
2691 if (buf[i] == '[') {
2692 /* it's a section */
2691 - int offset = section_name_match(&buf[i], old_name);
2693 + int offset;
2694 + is_section = 1;
2695 +
2696 + /*
2697 + * When encountering a new section under -c we
2698 + * need to flush out any section we're already
2699 + * coping and begin anew. There might be
2700 + * multiple [branch "$name"] sections.
2701 + */
2702 + if (copystr.len > 0) {
2703 + if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
2704 + ret = write_error(get_lock_file_path(lock));
2705 + goto out;
2706 + }
2707 + strbuf_reset(&copystr);
2708 + }
2709 +
2710 + offset = section_name_match(&buf[i], old_name);
2711 if (offset > 0) {
2712 ret++;
2713 if (new_name == NULL) {
@@ -2696,25 +2715,30 @@ int git_config_rename_section_in_file(const char *config_filename,
2715 continue;
2716 }
2717 store.baselen = strlen(new_name);
2699 - if (!store_write_section(out_fd, new_name)) {
2700 - ret = write_error(get_lock_file_path(lock));
2701 - goto out;
2702 - }
2703 - /*
2704 - * We wrote out the new section, with
2705 - * a newline, now skip the old
2706 - * section's length
2707 - */
2708 - output += offset + i;
2709 - if (strlen(output) > 0) {
2718 + if (!copy) {
2719 + if (!store_write_section(out_fd, new_name)) {
2720 + ret = write_error(get_lock_file_path(lock));
2721 + goto out;
2722 + }
2723 +
2724 /*
2711 - * More content means there's
2712 - * a declaration to put on the
2713 - * next line; indent with a
2714 - * tab
2725 + * We wrote out the new section, with
2726 + * a newline, now skip the old
2727 + * section's length
2728 */
2716 - output -= 1;
2717 - output[0] = '\t';
2729 + output += offset + i;
2730 + if (strlen(output) > 0) {
2731 + /*
2732 + * More content means there's
2733 + * a declaration to put on the
2734 + * next line; indent with a
2735 + * tab
2736 + */
2737 + output -= 1;
2738 + output[0] = '\t';
2739 + }
2740 + } else {
2741 + copystr = store_create_section(new_name);
2742 }
2743 }
2744 remove = 0;
@@ -2722,11 +2746,30 @@ int git_config_rename_section_in_file(const char *config_filename,
2746 if (remove)
2747 continue;
2748 length = strlen(output);
2749 +
2750 + if (!is_section && copystr.len > 0) {
2751 + strbuf_add(&copystr, output, length);
2752 + }
2753 +
2754 if (write_in_full(out_fd, output, length) != length) {
2755 ret = write_error(get_lock_file_path(lock));
2756 goto out;
2757 }
2758 }
2759 +
2760 + /*
2761 + * Copy a trailing section at the end of the config, won't be
2762 + * flushed by the usual "flush because we have a new section
2763 + * logic in the loop above.
2764 + */
2765 + if (copystr.len > 0) {
2766 + if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
2767 + ret = write_error(get_lock_file_path(lock));
2768 + goto out;
2769 + }
2770 + strbuf_reset(&copystr);
2771 + }
2772 +
2773 fclose(config_file);
2774 config_file = NULL;
2775 commit_and_out:
@@ -2742,11 +2785,30 @@ out_no_rollback:
2785 return ret;
2786 }
2787
2788 +int git_config_rename_section_in_file(const char *config_filename,
2789 + const char *old_name, const char *new_name)
2790 +{
2791 + return git_config_copy_or_rename_section_in_file(config_filename,
2792 + old_name, new_name, 0);
2793 +}
2794 +
2795 int git_config_rename_section(const char *old_name, const char *new_name)
2796 {
2797 return git_config_rename_section_in_file(NULL, old_name, new_name);
2798 }
2799
2800 +int git_config_copy_section_in_file(const char *config_filename,
2801 + const char *old_name, const char *new_name)
2802 +{
2803 + return git_config_copy_or_rename_section_in_file(config_filename,
2804 + old_name, new_name, 1);
2805 +}
2806 +
2807 +int git_config_copy_section(const char *old_name, const char *new_name)
2808 +{
2809 + return git_config_copy_section_in_file(NULL, old_name, new_name);
2810 +}
2811 +
2812 /*
2813 * Call this to report error for your variable that should not
2814 * get a boolean value (i.e. "[my] var" means "true").
refs.c
+11
@@ -2032,3 +2032,14 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
2032 {
2033 return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg);
2034 }
2035 +
2036 +int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
2037 + const char *newref, const char *logmsg)
2038 +{
2039 + return refs->be->copy_ref(refs, oldref, newref, logmsg);
2040 +}
2041 +
2042 +int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
2043 +{
2044 + return refs_copy_existing_ref(get_main_ref_store(), oldref, newref, logmsg);
2045 +}
refs.h
+8 -1
@@ -440,7 +440,14 @@ char *shorten_unambiguous_ref(const char *refname, int strict);
440 /** rename ref, return 0 on success **/
441 int refs_rename_ref(struct ref_store *refs, const char *oldref,
442 const char *newref, const char *logmsg);
443 -int rename_ref(const char *oldref, const char *newref, const char *logmsg);
443 +int rename_ref(const char *oldref, const char *newref,
444 + const char *logmsg);
445 +
446 +/** copy ref, return 0 on success **/
447 +int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
448 + const char *newref, const char *logmsg);
449 +int copy_existing_ref(const char *oldref, const char *newref,
450 + const char *logmsg);
451
452 int refs_create_symref(struct ref_store *refs, const char *refname,
453 const char *target, const char *logmsg);
refs/files-backend.c
+38 -8
@@ -1733,9 +1733,9 @@ static int commit_ref_update(struct files_ref_store *refs,
1733 const struct object_id *oid, const char *logmsg,
1734 struct strbuf *err);
1735
1736 -static int files_rename_ref(struct ref_store *ref_store,
1736 +static int files_copy_or_rename_ref(struct ref_store *ref_store,
1737 const char *oldrefname, const char *newrefname,
1738 - const char *logmsg)
1738 + const char *logmsg, int copy)
1739 {
1740 struct files_ref_store *refs =
1741 files_downcast(ref_store, REF_STORE_WRITE, "rename_ref");
@@ -1767,8 +1767,12 @@ static int files_rename_ref(struct ref_store *ref_store,
1767 }
1768
1769 if (flag & REF_ISSYMREF) {
1770 - ret = error("refname %s is a symbolic ref, renaming it is not supported",
1771 - oldrefname);
1770 + if (copy)
1771 + ret = error("refname %s is a symbolic ref, copying it is not supported",
1772 + oldrefname);
1773 + else
1774 + ret = error("refname %s is a symbolic ref, renaming it is not supported",
1775 + oldrefname);
1776 goto out;
1777 }
1778 if (!refs_rename_ref_available(&refs->base, oldrefname, newrefname)) {
@@ -1776,13 +1780,19 @@ static int files_rename_ref(struct ref_store *ref_store,
1780 goto out;
1781 }
1782
1779 - if (log && rename(sb_oldref.buf, tmp_renamed_log.buf)) {
1783 + if (!copy && log && rename(sb_oldref.buf, tmp_renamed_log.buf)) {
1784 ret = error("unable to move logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
1785 oldrefname, strerror(errno));
1786 goto out;
1787 }
1788
1785 - if (refs_delete_ref(&refs->base, logmsg, oldrefname,
1789 + if (copy && log && copy_file(tmp_renamed_log.buf, sb_oldref.buf, 0644)) {
1790 + ret = error("unable to copy logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
1791 + oldrefname, strerror(errno));
1792 + goto out;
1793 + }
1794 +
1795 + if (!copy && refs_delete_ref(&refs->base, logmsg, oldrefname,
1796 orig_oid.hash, REF_NODEREF)) {
1797 error("unable to delete old %s", oldrefname);
1798 goto rollback;
@@ -1795,7 +1805,7 @@ static int files_rename_ref(struct ref_store *ref_store,
1805 * the safety anyway; we want to delete the reference whatever
1806 * its current value.
1807 */
1798 - if (!refs_read_ref_full(&refs->base, newrefname,
1808 + if (!copy && !refs_read_ref_full(&refs->base, newrefname,
1809 RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
1810 oid.hash, NULL) &&
1811 refs_delete_ref(&refs->base, NULL, newrefname,
@@ -1826,7 +1836,10 @@ static int files_rename_ref(struct ref_store *ref_store,
1836 lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL,
1837 REF_NODEREF, NULL, &err);
1838 if (!lock) {
1829 - error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
1839 + if (copy)
1840 + error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
1841 + else
1842 + error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
1843 strbuf_release(&err);
1844 goto rollback;
1845 }
@@ -1877,6 +1890,22 @@ static int files_rename_ref(struct ref_store *ref_store,
1890 return ret;
1891 }
1892
1893 +static int files_rename_ref(struct ref_store *ref_store,
1894 + const char *oldrefname, const char *newrefname,
1895 + const char *logmsg)
1896 +{
1897 + return files_copy_or_rename_ref(ref_store, oldrefname,
1898 + newrefname, logmsg, 0);
1899 +}
1900 +
1901 +static int files_copy_ref(struct ref_store *ref_store,
1902 + const char *oldrefname, const char *newrefname,
1903 + const char *logmsg)
1904 +{
1905 + return files_copy_or_rename_ref(ref_store, oldrefname,
1906 + newrefname, logmsg, 1);
1907 +}
1908 +
1909 static int close_ref(struct ref_lock *lock)
1910 {
1911 if (close_lock_file(lock->lk))
@@ -3383,6 +3412,7 @@ struct ref_storage_be refs_be_files = {
3412 files_create_symref,
3413 files_delete_refs,
3414 files_rename_ref,
3415 + files_copy_ref,
3416
3417 files_ref_iterator_begin,
3418 files_read_raw_ref,
refs/refs-internal.h
+4
@@ -543,6 +543,9 @@ typedef int delete_refs_fn(struct ref_store *ref_store, const char *msg,
543 typedef int rename_ref_fn(struct ref_store *ref_store,
544 const char *oldref, const char *newref,
545 const char *logmsg);
546 +typedef int copy_ref_fn(struct ref_store *ref_store,
547 + const char *oldref, const char *newref,
548 + const char *logmsg);
549
550 /*
551 * Iterate over the references in `ref_store` whose names start with
@@ -641,6 +644,7 @@ struct ref_storage_be {
644 create_symref_fn *create_symref;
645 delete_refs_fn *delete_refs;
646 rename_ref_fn *rename_ref;
647 + copy_ref_fn *copy_ref;
648
649 ref_iterator_begin_fn *iterator_begin;
650 read_raw_ref_fn *read_raw_ref;
t/t3200-branch.sh
+215
@@ -382,6 +382,221 @@ EOF
382 test_cmp expect actual
383 '
384
385 +test_expect_success 'git branch -c dumps usage' '
386 + test_expect_code 128 git branch -c 2>err &&
387 + test_i18ngrep "branch name required" err
388 +'
389 +
390 +test_expect_success 'git branch --copy dumps usage' '
391 + test_expect_code 128 git branch --copy 2>err &&
392 + test_i18ngrep "branch name required" err
393 +'
394 +
395 +test_expect_success 'git branch -c d e should work' '
396 + git branch -l d &&
397 + git reflog exists refs/heads/d &&
398 + git config branch.d.dummy Hello &&
399 + git branch -c d e &&
400 + git reflog exists refs/heads/d &&
401 + git reflog exists refs/heads/e &&
402 + echo Hello >expect &&
403 + git config branch.e.dummy >actual &&
404 + test_cmp expect actual &&
405 + echo Hello >expect &&
406 + git config branch.d.dummy >actual &&
407 + test_cmp expect actual
408 +'
409 +
410 +test_expect_success 'git branch --copy is a synonym for -c' '
411 + git branch -l copy &&
412 + git reflog exists refs/heads/copy &&
413 + git config branch.copy.dummy Hello &&
414 + git branch --copy copy copy-to &&
415 + git reflog exists refs/heads/copy &&
416 + git reflog exists refs/heads/copy-to &&
417 + echo Hello >expect &&
418 + git config branch.copy.dummy >actual &&
419 + test_cmp expect actual &&
420 + echo Hello >expect &&
421 + git config branch.copy-to.dummy >actual &&
422 + test_cmp expect actual
423 +'
424 +
425 +test_expect_success 'git branch -c ee ef should copy and checkout branch ef' '
426 + git checkout -b ee &&
427 + git reflog exists refs/heads/ee &&
428 + git config branch.ee.dummy Hello &&
429 + git branch -c ee ef &&
430 + git reflog exists refs/heads/ee &&
431 + git reflog exists refs/heads/ef &&
432 + test $(git config branch.ee.dummy) = Hello &&
433 + test $(git config branch.ef.dummy) = Hello &&
434 + test $(git rev-parse --abbrev-ref HEAD) = ef
435 +'
436 +
437 +test_expect_success 'git branch -c f/f g/g should work' '
438 + git branch -l f/f &&
439 + git reflog exists refs/heads/f/f &&
440 + git config branch.f/f.dummy Hello &&
441 + git branch -c f/f g/g &&
442 + git reflog exists refs/heads/f/f &&
443 + git reflog exists refs/heads/g/g &&
444 + test $(git config branch.f/f.dummy) = Hello &&
445 + test $(git config branch.g/g.dummy) = Hello
446 +'
447 +
448 +test_expect_success 'git branch -c m2 m2 should work' '
449 + git branch -l m2 &&
450 + git reflog exists refs/heads/m2 &&
451 + git config branch.m2.dummy Hello &&
452 + git branch -c m2 m2 &&
453 + git reflog exists refs/heads/m2 &&
454 + test $(git config branch.m2.dummy) = Hello
455 +'
456 +
457 +test_expect_success 'git branch -c zz zz/zz should fail' '
458 + git branch -l zz &&
459 + git reflog exists refs/heads/zz &&
460 + test_must_fail git branch -c zz zz/zz
461 +'
462 +
463 +test_expect_success 'git branch -c b/b b should fail' '
464 + git branch -l b/b &&
465 + test_must_fail git branch -c b/b b
466 +'
467 +
468 +test_expect_success 'git branch -C o/q o/p should work when o/p exists' '
469 + git branch -l o/q &&
470 + git reflog exists refs/heads/o/q &&
471 + git reflog exists refs/heads/o/p &&
472 + git branch -C o/q o/p
473 +'
474 +
475 +test_expect_success 'git branch -c -f o/q o/p should work when o/p exists' '
476 + git reflog exists refs/heads/o/q &&
477 + git reflog exists refs/heads/o/p &&
478 + git branch -c -f o/q o/p
479 +'
480 +
481 +test_expect_success 'git branch -c qq rr/qq should fail when r exists' '
482 + git branch qq &&
483 + git branch rr &&
484 + test_must_fail git branch -c qq rr/qq
485 +'
486 +
487 +test_expect_success 'git branch -C b1 b2 should fail when b2 is checked out' '
488 + git branch b1 &&
489 + git checkout -b b2 &&
490 + test_must_fail git branch -C b1 b2
491 +'
492 +
493 +test_expect_success 'git branch -C c1 c2 should succeed when c1 is checked out' '
494 + git checkout -b c1 &&
495 + git branch c2 &&
496 + git branch -C c1 c2 &&
497 + test $(git rev-parse --abbrev-ref HEAD) = c2
498 +'
499 +
500 +test_expect_success 'git branch -C c1 c2 should add entries to .git/logs/HEAD' '
501 + msg="Branch: copied refs/heads/c1 to refs/heads/c2" &&
502 + grep "$msg$" .git/logs/HEAD
503 +'
504 +
505 +test_expect_success 'git branch -C master should work when master is checked out' '
506 + git checkout master &&
507 + git branch -C master
508 +'
509 +
510 +test_expect_success 'git branch -C master master should work when master is checked out' '
511 + git checkout master &&
512 + git branch -C master master
513 +'
514 +
515 +test_expect_success 'git branch -C master5 master5 should work when master is checked out' '
516 + git checkout master &&
517 + git branch master5 &&
518 + git branch -C master5 master5
519 +'
520 +
521 +test_expect_success 'git branch -C ab cd should overwrite existing config for cd' '
522 + git branch -l cd &&
523 + git reflog exists refs/heads/cd &&
524 + git config branch.cd.dummy CD &&
525 + git branch -l ab &&
526 + git reflog exists refs/heads/ab &&
527 + git config branch.ab.dummy AB &&
528 + git branch -C ab cd &&
529 + git reflog exists refs/heads/ab &&
530 + git reflog exists refs/heads/cd &&
531 + test $(git config branch.ab.dummy) = AB &&
532 + test $(git config branch.cd.dummy) = AB
533 +'
534 +
535 +test_expect_success 'git branch -c correctly copies multiple config sections' '
536 + FOO=1 &&
537 + export FOO &&
538 + test_when_finished "git checkout master" &&
539 + git checkout -b source2 master &&
540 +
541 + # Assert that a config file with multiple config sections has
542 + # those sections preserved...
543 + cat >expect <<-\EOF &&
544 + branch.source2.key1=value1
545 + branch.dest2.key1=value1
546 + more.gar.b=age
547 + branch.source2.key2=value2
548 + branch.dest2.key2=value2
549 + EOF
550 + cat >config.branch <<\EOF &&
551 +;; Note the lack of -\EOF above & mixed indenting here. This is
552 +;; intentional, we are also testing that the formatting of copied
553 +;; sections is preserved.
554 +
555 +;; Comment for source2. Tabs
556 +[branch "source2"]
557 + ;; Comment for the source2 value
558 + key1 = value1
559 +;; Comment for more.gar. Spaces
560 +[more "gar"]
561 + ;; Comment for the more.gar value
562 + b = age
563 +;; Comment for source2, again. Mixed tabs/spaces.
564 +[branch "source2"]
565 + ;; Comment for the source2 value, again
566 + key2 = value2
567 +EOF
568 + cat config.branch >>.git/config &&
569 + git branch -c source2 dest2 &&
570 + git config -f .git/config -l | grep -F -e source2 -e dest2 -e more.gar >actual &&
571 + test_cmp expect actual &&
572 +
573 + # ...and that the comments and formatting for those sections
574 + # is also preserved.
575 + cat >expect <<\EOF &&
576 +;; Comment for source2. Tabs
577 +[branch "source2"]
578 + ;; Comment for the source2 value
579 + key1 = value1
580 +;; Comment for more.gar. Spaces
581 +[branch "dest2"]
582 + ;; Comment for the source2 value
583 + key1 = value1
584 +;; Comment for more.gar. Spaces
585 +[more "gar"]
586 + ;; Comment for the more.gar value
587 + b = age
588 +;; Comment for source2, again. Mixed tabs/spaces.
589 +[branch "source2"]
590 + ;; Comment for the source2 value, again
591 + key2 = value2
592 +[branch "dest2"]
593 + ;; Comment for the source2 value, again
594 + key2 = value2
595 +EOF
596 + sed -n -e "/Comment for source2/,\$p" .git/config >actual &&
597 + test_cmp expect actual
598 +'
599 +
600 test_expect_success 'deleting a symref' '
601 git branch target &&
602 git symbolic-ref refs/heads/symref refs/heads/target &&