commit - 58e5e037d6e3e5d94bdf674e074fcdf411e80bfc
commit + 179f9db092046395efaa1dc9e767a1ec109e1174
blob - 51fec6b6e692d45a3c6ed44d2cd5a4a5649253e6
blob + 1f7297bf2a0f12c2fbcce5fbb38a533d88f94d4e
--- TODO
+++ TODO
instead of the root directory checked out at /usr/src.
The next LLVM release 12.1 would later be committed onto the llvm-12
branch and then merged into main at /usr/src/gnu/llvm in the same way.
-- Teach 'got merge' to forward a branch reference if possible, instead of
- creating a merge commit. Forwarding should only be done if linear
- history exists from the tip of the branch being merged to the tip of
- the work tree's branch, and if the tip of the work tree's branch is
- itself not a merge commit (this makes "stacked" merges possible
- by default, and prevents a 'main' branch reference from being forwarded
- to a vendor branch in case no new commits were added to 'main' since
- the previous vendor merge). Provide an option (-M) which forces creation
- of a merge commit, for cases where users deem forwarding undesirable.
+- Provide an option (-M) to 'got merge' which forces creation of a merge commit
+ even when forwarding is possible, for cases where users deem forwarding
+ undesirable.
- When a clone fails the HEAD symref will always point to "refs/heads/main"
(ie. the internal default HEAD symref of Got). Resuming a failed clone with
'got fetch' is supposed to work. To make this easier, if the HEAD symref
blob - cf38fa07face9d2a811a09f32c2b6e6bc5cff414
blob + ee5ecec15b1c9954ab672ace7b9165717723b7d2
--- got/got.1
+++ got/got.1
.Op Ar branch
.Xc
.Dl Pq alias: Cm mg
-Create a merge commit based on the current branch of the work tree and
-the specified
-.Ar branch .
+Merge the specified
+.Ar branch
+into the current branch of the work tree.
+If the branches have diverged, creates a merge commit.
+Otherwise, if
+.Ar branch
+already includes all commits from the work tree's branch, updates the work
+tree's branch to be the same as
+.Ar branch
+without creating a commit, and updates the work tree to the most recent commit
+on the branch.
+.Pp
If a linear project history is desired, then use of
.Cm got rebase
should be preferred over
.Pp
.Cm got merge
will refuse to run if certain preconditions are not met.
-If history of the
-.Ar branch
-is based on the work tree's current branch, then no merge commit can
-be created and
-.Cm got integrate
-may be used to integrate the
-.Ar branch
-instead.
If the work tree is not yet fully updated to the tip commit of its
branch, then the work tree must first be updated with
.Cm got update .
blob - a14b71c8099e53f924c43a9d3f6a90a70ffe1449
blob + ea8a08baed68affba73cafa577c3125e9756466a
--- got/got.c
+++ got/got.c
error = check_path_prefix(wt_branch_tip, branch_tip,
got_worktree_get_path_prefix(worktree),
GOT_ERR_MERGE_PATH, repo);
+ if (error)
+ goto done;
+ 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) {
- static char msg[512];
- snprintf(msg, sizeof(msg),
- "cannot create a merge commit because %s is based "
- "on %s; %s can be integrated with 'got integrate' "
- "instead", branch_name,
- got_worktree_get_head_ref_name(worktree),
- branch_name);
- error = got_error_msg(GOT_ERR_SAME_BRANCH, msg);
+ struct got_pathlist_head paths;
+ if (interrupt_merge) {
+ error = got_error_msg(GOT_ERR_SAME_BRANCH,
+ "merge is a fast-forward; this is "
+ "incompatible with got merge -n");
+ goto done;
+ }
+ printf("Forwarding %s to %s\n",
+ got_ref_get_name(wt_branch), branch_name);
+ error = got_ref_change_ref(wt_branch, branch_tip);
+ if (error)
+ goto done;
+ error = got_ref_write(wt_branch, repo);
+ if (error)
+ goto done;
+ error = got_worktree_set_base_commit_id(worktree, repo,
+ branch_tip);
+ if (error)
+ goto done;
+ TAILQ_INIT(&paths);
+ error = got_pathlist_append(&paths, "", NULL);
+ if (error)
+ goto done;
+ error = got_worktree_checkout_files(worktree,
+ &paths, repo, update_progress, &upa,
+ check_cancelled, NULL);
+ got_pathlist_free(&paths, GOT_PATHLIST_FREE_NONE);
+ if (error)
+ goto done;
+ if (upa.did_something) {
+ char *id_str;
+ error = got_object_id_str(&id_str, branch_tip);
+ if (error)
+ goto done;
+ printf("Updated to commit %s\n", id_str);
+ free(id_str);
+ } else
+ printf("Already up-to-date\n");
+ print_update_progress_stats(&upa);
goto done;
}
- error = got_worktree_merge_prepare(&fileindex, worktree,
- branch, repo);
+ error = got_worktree_merge_write_refs(worktree, branch, repo);
if (error)
goto done;
blob - f7de090bfb2bd10312bdd0c389b5eabe41a33fdd
blob + 5b0b7259e6ff5fc0edf8711ac6780dd49315fe54
--- include/got_worktree.h
+++ include/got_worktree.h
struct got_worktree *, struct got_repository *);
/*
- * Prepare for merging a branch into the work tree's current branch.
+ * Prepare for merging a branch into the work tree's current branch: lock the
+ * worktree and check that preconditions are satisfied. The function also
+ * returns a pointer to a fileindex which must be passed back to other
+ * merge-related functions.
+ */
+const struct got_error *got_worktree_merge_prepare(struct got_fileindex **,
+ struct got_worktree *, struct got_repository *);
+
+/*
* This function creates a reference to the branch being merged, and to
* this branch's current tip commit, in the "got/worktree/merge/" namespace.
* These references are used to keep track of merge operation state and are
* used as input and/or output arguments with other merge-related functions.
- * The function also returns a pointer to a fileindex which must be
- * passed back to other merge-related functions.
*/
-const struct got_error *got_worktree_merge_prepare(struct got_fileindex **,
- struct got_worktree *, struct got_reference *, struct got_repository *);
+const struct got_error *got_worktree_merge_write_refs(struct got_worktree *,
+ struct got_reference *, struct got_repository *);
/*
* Continue an interrupted merge operation.
blob - e97f10e73cd0df0521ce4e36b40897121837e8d5
blob + 95e2e6bfa46ac12fd077b083c122ade77e54106f
--- lib/worktree.c
+++ lib/worktree.c
const struct got_error *got_worktree_merge_prepare(
struct got_fileindex **fileindex, struct got_worktree *worktree,
- struct got_reference *branch, struct got_repository *repo)
+ struct got_repository *repo)
{
const struct got_error *err = NULL;
char *fileindex_path = NULL;
- char *branch_refname = NULL, *commit_refname = NULL;
- struct got_reference *wt_branch = NULL, *branch_ref = NULL;
- struct got_reference *commit_ref = NULL;
- struct got_object_id *branch_tip = NULL, *wt_branch_tip = NULL;
+ struct got_reference *wt_branch = NULL;
+ struct got_object_id *wt_branch_tip = NULL;
struct check_rebase_ok_arg ok_arg;
*fileindex = NULL;
&ok_arg);
if (err)
goto done;
-
- err = get_merge_branch_ref_name(&branch_refname, worktree);
- if (err)
- return err;
-
- err = get_merge_commit_ref_name(&commit_refname, worktree);
- if (err)
- return err;
err = got_ref_open(&wt_branch, repo, worktree->head_ref_name,
0);
err = got_error(GOT_ERR_MERGE_OUT_OF_DATE);
goto done;
}
+
+done:
+ free(fileindex_path);
+ if (wt_branch)
+ got_ref_close(wt_branch);
+ free(wt_branch_tip);
+ if (err) {
+ if (*fileindex) {
+ got_fileindex_free(*fileindex);
+ *fileindex = NULL;
+ }
+ lock_worktree(worktree, LOCK_SH);
+ }
+ return err;
+}
+const struct got_error *got_worktree_merge_write_refs(
+ struct got_worktree *worktree, struct got_reference *branch,
+ struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ char *branch_refname = NULL, *commit_refname = NULL;
+ struct got_reference *branch_ref = NULL, *commit_ref = NULL;
+ struct got_object_id *branch_tip = NULL;
+
+ err = get_merge_branch_ref_name(&branch_refname, worktree);
+ if (err)
+ return err;
+
+ err = get_merge_commit_ref_name(&commit_refname, worktree);
+ if (err)
+ return err;
+
err = got_ref_resolve(&branch_tip, repo, branch);
if (err)
goto done;
done:
free(branch_refname);
free(commit_refname);
- free(fileindex_path);
if (branch_ref)
got_ref_close(branch_ref);
if (commit_ref)
got_ref_close(commit_ref);
- if (wt_branch)
- got_ref_close(wt_branch);
- free(wt_branch_tip);
- if (err) {
- if (*fileindex) {
- got_fileindex_free(*fileindex);
- *fileindex = NULL;
- }
- lock_worktree(worktree, LOCK_SH);
- }
+ free(branch_tip);
return err;
}
blob - 71e5e49a5b7f5ebee4e83f1e29b7b2352977c697
blob + 92b590aa2ef2958738fb2d4e8419cb879fc9bf5c
--- regress/cmdline/merge.sh
+++ regress/cmdline/merge.sh
return 1
fi
- # need a divergant commit on the main branch for 'got merge'
- (cd $testroot/wt && got merge newbranch \
- > $testroot/stdout 2> $testroot/stderr)
- ret=$?
- if [ $ret -eq 0 ]; then
- echo "got merge succeeded unexpectedly" >&2
- test_done "$testroot" "1"
- return 1
- fi
- echo -n "got: cannot create a merge commit because " \
- > $testroot/stderr.expected
- echo -n "refs/heads/newbranch is based on refs/heads/master; " \
- >> $testroot/stderr.expected
- echo -n "refs/heads/newbranch can be integrated with " \
- >> $testroot/stderr.expected
- echo "'got integrate' instead" >> $testroot/stderr.expected
- cmp -s $testroot/stderr.expected $testroot/stderr
- ret=$?
- if [ $ret -ne 0 ]; then
- diff -u $testroot/stderr.expected $testroot/stderr
- test_done "$testroot" "$ret"
- return 1
- fi
-
- # create the required dirvergant commit
+ # create a divergent commit
(cd $testroot/repo && git checkout -q master)
echo "modified zeta on master" > $testroot/repo/epsilon/zeta
git_commit $testroot/repo -m "committing to zeta on master"
gamma/delta
symlink@ -> alpha
EOF
+ 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_forward() {
+ local testroot=`test_init merge_forward`
+ local commit0=`git_show_head $testroot/repo`
+
+ # Create a commit before branching, which will be used to help test
+ # preconditions for "got merge".
+ echo "modified alpha" > $testroot/repo/alpha
+ git_commit $testroot/repo -m "common commit"
+ local commit1=`git_show_head $testroot/repo`
+
+ (cd $testroot/repo && git checkout -q -b newbranch)
+ echo "modified beta on branch" > $testroot/repo/beta
+ git_commit $testroot/repo -m "committing to beta on newbranch"
+ local commit2=`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
+
+ # must not use a mixed-commit work tree with 'got merge'
+ (cd $testroot/wt && got update -c $commit0 alpha > /dev/null)
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got update failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+ (cd $testroot/wt && got merge newbranch \
+ > $testroot/stdout 2> $testroot/stderr)
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "got merge succeeded unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+ echo -n "got: work tree contains files from multiple base commits; " \
+ > $testroot/stderr.expected
+ echo "the entire work tree must be updated first" \
+ >> $testroot/stderr.expected
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got update > /dev/null)
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got update failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # 'got merge -n' refuses to fast-forward
+ (cd $testroot/wt && got merge -n newbranch \
+ > $testroot/stdout 2> $testroot/stderr)
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "got merge succeeded unexpectedly" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+ echo -n "got: merge is a fast-forward; " > $testroot/stderr.expected
+ echo "this is incompatible with got merge -n" \
+ >> $testroot/stderr.expected
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got merge newbranch \
+ > $testroot/stdout 2> $testroot/stderr)
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got merge failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "Forwarding refs/heads/master to refs/heads/newbranch" \
+ > $testroot/stdout.expected
+ echo "U beta" >> $testroot/stdout.expected
+ echo "Updated to commit $commit2" \
+ >> $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
+
+ (cd $testroot/wt && got log | grep ^commit > $testroot/stdout)
+ echo -n "commit $commit2 " > $testroot/stdout.expected
+ echo "(master, newbranch)" >> $testroot/stdout.expected
+ echo "commit $commit1" >> $testroot/stdout.expected
+ echo "commit $commit0" >> $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_backward() {
+ local testroot=`test_init merge_backward`
+ local commit0=`git_show_head $testroot/repo`
+
+ (cd $testroot/repo && git checkout -q -b newbranch)
+ (cd $testroot/repo && git checkout -q master)
+ echo "modified alpha on master" > $testroot/repo/alpha
+ git_commit $testroot/repo -m "committing to alpha on master"
+
+ 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 newbranch \
+ > $testroot/stdout 2> $testroot/stderr)
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got merge failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+ echo "Already up-to-date" > $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_parseargs "$@"
run_test test_merge_basic
+run_test test_merge_forward
+run_test test_merge_backward
run_test test_merge_continue
run_test test_merge_continue_new_commit
run_test test_merge_abort