Download raw body.
add -M option: tell got merge not to fast-forward
The below patch adds an -M option to got merge which makes it create a merge commit even if fast-forwarding is possible. (The merge can still be interrupted by a conflict or the -n option.) Notes: - Is M the right letter for this option? I think the answer is "yes" but this is sort of the last chance to change our minds. Devil's advocate: if we decide to make -n the default, it's possible we'd want to make "-M" might the opposite of -n, i.e. create the merge commit without interrupting. (But there are plenty of other letters. It's too bad -C ("commit!") is already used. Maybe "-c" could serve two purposes, continuing and opposite of -n; not sure if that's a good idea.) - Option conflicts for "got merge" could use work. "c" + "n" isn't detected, and the man page is wrong that "no other command-line arguments are allowed" with "-c", since "-C" is permitted. -- James diff 13d9dc7e2e880cfe93cf23a39c48427cda7ff636 refs/heads/main commit - 13d9dc7e2e880cfe93cf23a39c48427cda7ff636 commit + f5547efe8089114ee0383d65d9b42ada004016e6 blob - 71c0950fc006ad8faab300bfdc70c29496c8493d blob + 234d94604d5296c784792c130bb88dd64b8393bb --- got/got.1 +++ got/got.1 @@ -2811,7 +2811,7 @@ or reverted with .Tg mg .It Xo .Cm merge -.Op Fl aCcn +.Op Fl aCcMn .Op Ar branch .Xc .Dl Pq alias: Cm mg @@ -2940,6 +2940,8 @@ If this option is used, no other command-line argument .It Fl c Continue an interrupted merge operation. If this option is used, no other command-line arguments are allowed. +.It Fl M +Create a merge commit even if the branches have not diverged. .It Fl n Merge changes into the work tree as usual but do not create a merge commit immediately. blob - 5348f3e02299f72518fb1fae98ac8eb68f013b85 blob + f0f73cb11cc2c22294fb3716a2eba2a4a2ba8be9 --- got/got.c +++ got/got.c @@ -13144,7 +13144,7 @@ cmd_merge(int argc, char *argv[]) struct got_object_id *branch_tip = NULL, *yca_id = NULL; struct got_object_id *wt_branch_tip = NULL; int ch, merge_in_progress = 0, abort_merge = 0, continue_merge = 0; - int allow_conflict = 0, interrupt_merge = 0; + int allow_conflict = 0, prefer_fast_forward = 1, interrupt_merge = 0; struct got_update_progress_arg upa; struct got_object_id *merge_commit_id = NULL; char *branch_name = NULL; @@ -13158,7 +13158,7 @@ cmd_merge(int argc, char *argv[]) err(1, "pledge"); #endif - while ((ch = getopt(argc, argv, "aCcn")) != -1) { + while ((ch = getopt(argc, argv, "aCcMn")) != -1) { switch (ch) { case 'a': abort_merge = 1; @@ -13168,6 +13168,9 @@ cmd_merge(int argc, char *argv[]) case 'c': continue_merge = 1; break; + case 'M': + prefer_fast_forward = 0; + break; case 'n': interrupt_merge = 1; break; @@ -13188,6 +13191,10 @@ cmd_merge(int argc, char *argv[]) } if (abort_merge && continue_merge) option_conflict('a', 'c'); + if (abort_merge && !prefer_fast_forward) + option_conflict('a', 'M'); + if (continue_merge && !prefer_fast_forward) + option_conflict('c', 'M'); if (abort_merge || continue_merge) { if (argc != 0) usage_merge(); @@ -13326,7 +13333,8 @@ cmd_merge(int argc, char *argv[]) error = got_worktree_merge_prepare(&fileindex, worktree, repo); if (error) goto done; - if (yca_id && got_object_id_cmp(wt_branch_tip, yca_id) == 0) { + if (prefer_fast_forward && yca_id && + got_object_id_cmp(wt_branch_tip, yca_id) == 0) { struct got_pathlist_head paths; if (interrupt_merge) { error = got_error_fmt(GOT_ERR_BAD_OPTION, blob - b1980ee2c0e36130e8066d6897401ba97642d4a4 blob + a7908e3851266c3fe538eb4321c3fab455fc0d8d --- regress/cmdline/merge.sh +++ regress/cmdline/merge.sh @@ -470,6 +470,134 @@ test_merge_backward() { test_done "$testroot" "$ret" } +test_merge_forward_commit() { + local testroot=`test_init merge_forward_commit` + local commit0=`git_show_head $testroot/repo` + + (cd $testroot/repo && git checkout -q -b newbranch) + echo "modified alpha on branch" > $testroot/repo/alpha + git_commit $testroot/repo -m "committing to alpha on newbranch" + local commit1=`git_show_head $testroot/repo` + + got checkout -b master $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got merge -M newbranch > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "got merge failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + local merge_commit=`git_show_branch_head $testroot/repo master` + + echo "G alpha" >> $testroot/stdout.expected + echo -n "Merged refs/heads/newbranch into refs/heads/master: " \ + >> $testroot/stdout.expected + echo $merge_commit >> $testroot/stdout.expected + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # We should have created a merge commit with two parents. + (cd $testroot/wt && got log -l1 | grep ^parent > $testroot/stdout) + echo "parent 1: $commit0" > $testroot/stdout.expected + echo "parent 2: $commit1" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + test_done "$testroot" "$ret" +} + +test_merge_forward_interrupt() { + # Test -M and -n options together. + local testroot=`test_init merge_forward_commit` + local commit0=`git_show_head $testroot/repo` + + (cd $testroot/repo && git checkout -q -b newbranch) + echo "modified alpha on branch" > $testroot/repo/alpha + git_commit $testroot/repo -m "committing to alpha on newbranch" + local commit1=`git_show_head $testroot/repo` + + got checkout -b master $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got merge -M -n newbranch > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "got merge failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + echo "G alpha" > $testroot/stdout.expected + echo "Merge of refs/heads/newbranch interrupted on request" \ + >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # Continue the merge. + (cd $testroot/wt && got merge -c > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + echo "got merge failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + local merge_commit=`git_show_branch_head $testroot/repo master` + + echo "M alpha" > $testroot/stdout.expected + echo -n "Merged refs/heads/newbranch into refs/heads/master: " \ + >> $testroot/stdout.expected + echo $merge_commit >> $testroot/stdout.expected + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # We should have created a merge commit with two parents. + (cd $testroot/wt && got log -l1 | grep ^parent > $testroot/stdout) + echo "parent 1: $commit0" > $testroot/stdout.expected + echo "parent 2: $commit1" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + test_merge_backward() { local testroot=`test_init merge_backward` local commit0=`git_show_head $testroot/repo` @@ -1867,6 +1995,8 @@ run_test test_merge_backward test_parseargs "$@" run_test test_merge_basic run_test test_merge_forward +run_test test_merge_forward_commit +run_test test_merge_forward_interrupt run_test test_merge_backward run_test test_merge_continue run_test test_merge_continue_new_commit
add -M option: tell got merge not to fast-forward