make it possible to merge vendor branches with 'got merge'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
diff --git a/got/got.1 b/got/got.1
index 06422c8..c408a29 100644
--- a/got/got.1
+++ b/got/got.1
@@ -2141,8 +2141,9 @@ If a linear project history is desired, then use of
should be preferred over
.Cm got merge .
However, even strictly linear projects may require merge commits in order
-to merge in new versions of code imported from third-party projects on
-vendor branches.
+to merge in new versions of third-party code stored on vendor branches
+created with
+.Cm got import .
.Pp
Merge commits are commits based on multiple parent commits.
The tip commit of the work tree's current branch, which must be set with
@@ -2154,14 +2155,14 @@ The tip commit of the specified
.Ar branch
will be used as the second parent.
.Pp
+No ancestral relationship between the two branches is required.
+If the two branches have already been merged previously, only new changes
+will be merged.
+.Pp
It is not possible to create merge commits with more than two parents.
If more than one branch needs to be merged, then multiple merge commits
with two parents each can be created in sequence.
.Pp
-The
-.Ar branch
-must share common ancestry with the work tree's current branch.
-.Pp
While merging changes found on the
.Ar branch
into the work tree, show the status of each affected file,
diff --git a/got/got.c b/got/got.c
index 0bf2f90..461ac8d 100644
--- a/got/got.c
+++ b/got/got.c
@@ -2701,7 +2701,7 @@ check_linear_ancestry(struct got_object_id *commit_id,
struct got_object_id *yca_id;
err = got_commit_graph_find_youngest_common_ancestor(&yca_id,
- commit_id, base_commit_id, repo, check_cancelled, NULL);
+ commit_id, base_commit_id, 1, repo, check_cancelled, NULL);
if (err)
return err;
@@ -8597,7 +8597,7 @@ print_backup_ref(const char *branch_name, const char *new_id_str,
goto done;
err = got_commit_graph_find_youngest_common_ancestor(&yca_id,
- old_commit_id, new_commit_id, repo, check_cancelled, NULL);
+ old_commit_id, new_commit_id, 1, repo, check_cancelled, NULL);
if (err)
goto done;
@@ -8954,7 +8954,7 @@ cmd_rebase(int argc, char *argv[])
base_commit_id = got_worktree_get_base_commit_id(worktree);
error = got_commit_graph_find_youngest_common_ancestor(&yca_id,
- base_commit_id, branch_head_commit_id, repo,
+ base_commit_id, branch_head_commit_id, 1, repo,
check_cancelled, NULL);
if (error)
goto done;
@@ -10729,16 +10729,10 @@ cmd_merge(int argc, char *argv[])
if (error)
goto done;
error = got_commit_graph_find_youngest_common_ancestor(&yca_id,
- wt_branch_tip, branch_tip, repo,
+ wt_branch_tip, branch_tip, 0, repo,
check_cancelled, NULL);
- if (error)
- goto done;
- if (yca_id == NULL) {
- error = got_error_msg(GOT_ERR_ANCESTRY,
- "specified branch shares no common ancestry "
- "with work tree's branch");
+ if (error && error->code != GOT_ERR_ANCESTRY)
goto done;
- }
if (!continue_merge) {
error = check_path_prefix(wt_branch_tip, branch_tip,
@@ -10746,22 +10740,24 @@ cmd_merge(int argc, char *argv[])
GOT_ERR_MERGE_PATH, repo);
if (error)
goto done;
- error = check_same_branch(wt_branch_tip, branch,
- yca_id, repo);
- if (error) {
- if (error->code != GOT_ERR_ANCESTRY)
+ if (yca_id) {
+ error = check_same_branch(wt_branch_tip, branch,
+ yca_id, repo);
+ if (error) {
+ if (error->code != GOT_ERR_ANCESTRY)
+ goto done;
+ error = NULL;
+ } else {
+ 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);
goto done;
- error = NULL;
- } else {
- 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);
- goto done;
+ }
}
error = got_worktree_merge_prepare(&fileindex, worktree,
branch, repo);
@@ -10774,6 +10770,14 @@ cmd_merge(int argc, char *argv[])
if (error)
goto done;
print_update_progress_stats(&upa);
+ if (!upa.did_something) {
+ error = got_worktree_merge_abort(worktree, fileindex,
+ repo, update_progress, &upa);
+ if (error)
+ goto done;
+ printf("Already up-to-date\n");
+ goto done;
+ }
}
if (upa.conflicts > 0 || upa.missing > 0) {
diff --git a/include/got_commit_graph.h b/include/got_commit_graph.h
index 1538d54..617697d 100644
--- a/include/got_commit_graph.h
+++ b/include/got_commit_graph.h
@@ -32,4 +32,4 @@ const struct got_error *got_commit_graph_intersect(struct got_object_id **,
/* Find the youngest common ancestor of two commits. */
const struct got_error *got_commit_graph_find_youngest_common_ancestor(
struct got_object_id **, struct got_object_id *, struct got_object_id *,
- struct got_repository *, got_cancel_cb, void *);
+ int, struct got_repository *, got_cancel_cb, void *);
diff --git a/lib/commit_graph.c b/lib/commit_graph.c
index 02b48fa..55b4da2 100644
--- a/lib/commit_graph.c
+++ b/lib/commit_graph.c
@@ -600,6 +600,7 @@ got_commit_graph_iter_next(struct got_object_id **id,
const struct got_error *
got_commit_graph_find_youngest_common_ancestor(struct got_object_id **yca_id,
struct got_object_id *commit_id, struct got_object_id *commit_id2,
+ int first_parent_traversal,
struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg)
{
const struct got_error *err = NULL;
@@ -613,11 +614,11 @@ got_commit_graph_find_youngest_common_ancestor(struct got_object_id **yca_id,
if (commit_ids == NULL)
return got_error_from_errno("got_object_idset_alloc");
- err = got_commit_graph_open(&graph, "/", 1);
+ err = got_commit_graph_open(&graph, "/", first_parent_traversal);
if (err)
goto done;
- err = got_commit_graph_open(&graph2, "/", 1);
+ err = got_commit_graph_open(&graph2, "/", first_parent_traversal);
if (err)
goto done;
diff --git a/lib/send.c b/lib/send.c
index 439105b..6a928ef 100644
--- a/lib/send.c
+++ b/lib/send.c
@@ -167,7 +167,7 @@ check_linear_ancestry(const char *refname, struct got_object_id *my_id,
"bad object type on server for %s", refname);
err = got_commit_graph_find_youngest_common_ancestor(&yca_id,
- my_id, their_id, repo, cancel_cb, cancel_arg);
+ my_id, their_id, 1, repo, cancel_cb, cancel_arg);
if (err)
return err;
if (yca_id == NULL)
diff --git a/regress/cmdline/merge.sh b/regress/cmdline/merge.sh
index 1c7ea84..1d534c5 100755
--- a/regress/cmdline/merge.sh
+++ b/regress/cmdline/merge.sh
@@ -1131,6 +1131,111 @@ test_merge_no_op() {
test_done "$testroot" "$ret"
}
+test_merge_imported_branch() {
+ local testroot=`test_init merge_import`
+ local commit0=`git_show_head $testroot/repo`
+ local commit0_author_time=`git_show_author_time $testroot/repo`
+
+ # import a new sub-tree to the 'files' branch such that
+ # none of the files added here collide with existing ones
+ mkdir -p $testroot/tree/there
+ mkdir -p $testroot/tree/be/lots
+ mkdir -p $testroot/tree/files
+ echo "there should" > $testroot/tree/there/should
+ echo "be lots of" > $testroot/tree/be/lots/of
+ echo "files here" > $testroot/tree/files/here
+ got import -r $testroot/repo -b files -m 'import files' \
+ $testroot/tree > /dev/null
+
+ 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
+
+ (cd $testroot/wt && got merge files > $testroot/stdout)
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ echo "got merge failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ local merge_commit0=`git_show_head $testroot/repo`
+ cat > $testroot/stdout.expected <<EOF
+A be/lots/of
+A files/here
+A there/should
+Merged refs/heads/files into refs/heads/master: $merge_commit0
+EOF
+ 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
+
+ # try to merge again while no new changes are available
+ (cd $testroot/wt && got merge files > $testroot/stdout)
+ ret="$?"
+ if [ "$ret" != "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" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # update the 'files' branch
+ (cd $testroot/repo && git reset -q --hard master)
+ (cd $testroot/repo && git checkout -q files)
+ echo "indeed" > $testroot/repo/indeed
+ (cd $testroot/repo && git add indeed)
+ git_commit $testroot/repo -m "adding another file indeed"
+ echo "be lots and lots of" > $testroot/repo/be/lots/of
+ git_commit $testroot/repo -m "lots of changes"
+
+ (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
+
+ # we should now be able to merge more changes from files branch
+ (cd $testroot/wt && got merge files > $testroot/stdout)
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ echo "got merge failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ local merge_commit1=`git_show_branch_head $testroot/repo master`
+ cat > $testroot/stdout.expected <<EOF
+G be/lots/of
+A indeed
+Merged refs/heads/files into refs/heads/master: $merge_commit1
+EOF
+
+ 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
@@ -1139,3 +1244,4 @@ run_test test_merge_in_progress
run_test test_merge_path_prefix
run_test test_merge_missing_file
run_test test_merge_no_op
+run_test test_merge_imported_branch