From: Stefan Sperling Subject: add got merge -n option To: gameoftrees@openbsd.org Date: Sun, 26 Sep 2021 16:22:58 +0200 Implement 'got merge -n' which always interrupts a merge before creating the merge commit. I believe this will useful if the changes being merged are non-trivial. For example, vendor branch merges may need to reconcile local changes with a potentially large amount of changes coming from an upstream project. Even when there are no merge conflicts or missing files, additional testing and verification may be required. This patch applies on top of my vendor-merge patch which I sent yesterday. ok? diff 400d752d446c28769c6aa522c3b8e8d699582ec6 ab784d002f85b2e06a56939ea5dfce2e61972532 blob - c408a296cf7c404394c1001c724ca9a0355c7e6a blob + 87a57e122c712ef21868c139097562f10ce37ee6 --- got/got.1 +++ got/got.1 @@ -2132,7 +2132,7 @@ or reverted with .It Cm ig Short alias for .Cm integrate . -.It Cm merge Oo Fl a Oc Oo Fl c Oc Op Ar branch +.It Cm merge Oo Fl a Oc Oo Fl c Oc Oo Fl n Oc Op Ar branch Create a merge commit based on the current branch of the work tree and the specified .Ar branch . @@ -2246,6 +2246,14 @@ 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 n +Merge changes into the work tree as usual but do not create a merge +commit immediately. +The merge result can be adjusted as desired before a merge commit is +created with +.Cm got merge -c . +Alternatively, the merge may be aborted with +.Cm got merge -a . .El .It Cm mg Short alias for blob - 39c5172e4d8e1b72ff03f0dbd81fc4ae8935b1b9 blob + 306de47eabea9518ce4c7e1562e583e3cb83ab49 --- got/got.c +++ got/got.c @@ -10590,7 +10590,7 @@ done: __dead static void usage_merge(void) { - fprintf(stderr, "usage: %s merge [-a] [-c] [branch]\n", + fprintf(stderr, "usage: %s merge [-a] [-c] [-n] [branch]\n", getprogname()); exit(1); } @@ -10607,13 +10607,14 @@ 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 interrupt_merge = 0; struct got_update_progress_arg upa; struct got_object_id *merge_commit_id = NULL; char *branch_name = NULL; memset(&upa, 0, sizeof(upa)); - while ((ch = getopt(argc, argv, "ac")) != -1) { + while ((ch = getopt(argc, argv, "acn")) != -1) { switch (ch) { case 'a': abort_merge = 1; @@ -10621,6 +10622,9 @@ cmd_merge(int argc, char *argv[]) case 'c': continue_merge = 1; break; + case 'n': + interrupt_merge = 1; + break; default: usage_rebase(); /* NOTREACHED */ @@ -10780,10 +10784,15 @@ cmd_merge(int argc, char *argv[]) } } - if (upa.conflicts > 0 || upa.missing > 0) { + if (interrupt_merge) { error = got_worktree_merge_postpone(worktree, fileindex); if (error) goto done; + printf("Merge of %s interrupted on request\n", branch_name); + } else if (upa.conflicts > 0 || upa.missing > 0) { + error = got_worktree_merge_postpone(worktree, fileindex); + if (error) + goto done; if (upa.conflicts > 0 && upa.missing == 0) { error = got_error_msg(GOT_ERR_CONFLICTS, "conflicts must be resolved before merging " blob - 1d534c5958a2e7da9413b01d8fcff42599c6f25b blob + 38220a83f2af8fc1c889f9271a2cd263cbed1dad --- regress/cmdline/merge.sh +++ regress/cmdline/merge.sh @@ -1236,6 +1236,152 @@ EOF test_done "$testroot" "$ret" } +test_merge_interrupt() { + local testroot=`test_init merge_interrupt` + local commit0=`git_show_head $testroot/repo` + local commit0_author_time=`git_show_author_time $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 branch_commit0=`git_show_branch_head $testroot/repo newbranch` + + got checkout -b master $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + echo "got checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + # create a non-conflicting commit + (cd $testroot/repo && git checkout -q master) + echo "modified beta on master" > $testroot/repo/beta + git_commit $testroot/repo -m "committing to beta on master" + local master_commit=`git_show_head $testroot/repo` + + # need an up-to-date work tree for 'got merge' + (cd $testroot/wt && got update > /dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got update failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got merge -n newbranch \ + > $testroot/stdout 2> $testroot/stderr) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got merge failed unexpectedly" >&2 + test_done "$testroot" "1" + 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" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got status > $testroot/stdout) + + echo "M alpha" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "modified alpha on branch" > $testroot/content.expected + cat $testroot/wt/alpha > $testroot/content + cmp -s $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + # adjust merge result + echo "adjusted merge result" > $testroot/wt/alpha + + # continue the merge + (cd $testroot/wt && got merge -c > $testroot/stdout) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got merge failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + local merge_commit=`git_show_head $testroot/repo` + + 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" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got status > $testroot/stdout) + + echo -n > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout) + echo "commit $merge_commit (master)" > $testroot/stdout.expected + echo "commit $master_commit" >> $testroot/stdout.expected + echo "commit $commit0" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got update > $testroot/stdout) + + echo 'Already up-to-date' > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "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: $master_commit" > $testroot/stdout.expected + echo "parent 2: $branch_commit0" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + test_parseargs "$@" run_test test_merge_basic run_test test_merge_continue @@ -1245,3 +1391,4 @@ run_test test_merge_path_prefix run_test test_merge_missing_file run_test test_merge_no_op run_test test_merge_imported_branch +run_test test_merge_interrupt