Re: [PATCH v2 4/4] implement automatic fast forward merge for submodules

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tuesday 06 July 2010, Heiko Voigt wrote:
> This implements a simple merge strategy for submodule hashes. We check
> whether one side of the merge candidates is already contained in the
> other and then merge automatically.
> 
> If both sides contain changes we search for a merge in the submodule.
> In case a single one exists we check that out and suggest it as the
> merge resolution. A list of candidates is returned when we find multiple
> merges that contain both sides of the changes.
> 
> This is useful for a workflow in which the developers can publish topic
> branches in submodules and a seperate maintainer merges them. In case
> the developers always wait until their branch gets merged before tracking
> them in the superproject all merges of branches that contain submodule
> changes will be resolved automatically. If developers choose to track
> their feature branch the maintainer might get a conflict but git will
> search the submodule for a merge and suggest it/them as a resolution.
> 
> Signed-off-by: Heiko Voigt <hvoigt@xxxxxxxxxx>

In the commit subject: s/fast forward/fast-forward/

> diff --git a/submodule.c b/submodule.c

> +static int find_first_merges(struct object_array *result, const char *path,
> +		struct commit *a, struct commit *b)

[...]

> +	/* get all revisions that merge commit a */
> +	snprintf(merged_revision, sizeof(merged_revision), "^%s",
> +			find_unique_abbrev(a->object.sha1, 40));

Why do you call find_unique_abbrev(..., 40) here?
Isn't sha1_to_hex(a->object.sha1) a better solution?

> +	init_revisions(&revs, NULL);
> +	rev_opts.submodule = path;
> +	setup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts);
> +
> +	/* save all revisions from the above list that contain b */
> +	if (prepare_revision_walk(&revs))
> +		die("revision walk setup failed");
> +	while ((commit = get_revision(&revs)) != NULL) {
> +		struct object *o = &(commit->object);
> +		if (in_merge_bases(b, (struct commit **) &o, 1)) {

Why not s/(struct commit **) &o/&commit/ ?


> +static void print_commit(struct commit *commit)
> +{
> +	static const char *format = " %h: %m %s";

You don't need this 'format' variable; instead use the literal string below.

> +	struct strbuf sb = STRBUF_INIT;
> +	struct pretty_print_context ctx = {0};
> +	ctx.date_mode = DATE_NORMAL;
> +	format_commit_message(commit, format, &sb, &ctx);
> +	strbuf_addstr(&sb, "\n");
> +	fprintf(stderr, "%s", sb.buf);

Drop the strbuf_addstr() and add the newline directly in the fprintf()
format:

fprintf(stderr, "%s\n", sb.buf);


> +int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
> +		    const unsigned char a[20], const unsigned char b[20])

Watch your line wrapping. Max 80 chars.

> +		warning("Submodule rewound can not merge");

s/Submodule rewound can not merge/
  Failed to merge submodule %s (commits don't follow merge-base)/
	
> +	/* 1. case a is contained in b or vice versa */

s/1. case/Case #1:/

> +	/* 2. case there are one ore more merges that contain a and b in
> +	 * the submodule. If there is a single one present it as
> +	 * suggestion to the user but leave it marked unmerged so the
> +	 * user needs to confirm the resolution.
> +	 */

s/2. case/Case #2:/

s/one ore more/one or more/

s/If there is a single one present it as suggestion to the user but/
  If there is only one, then present it a suggestion to the user, but/


> +	/* find commit which merges them */
> +	parent_count = find_first_merges(&merges, path, commit_a, commit_b);
> +	if (!parent_count) {
> +		warning("Failed to merge submodule %s (merge not found)", path);

s/merge not found/merge following commits not found/

BTW, what about a

#define MERGE_WARNING(path, msg) \
	warning("Failed to merge submodule %s (%s)", path, msg);

towards the top of merge_submodule, so you don't have to repeat
the same string over and over?

> +		goto finish;
> +	}
> +
> +	if (parent_count != 1) {
> +		warning("Failed to merge submodule %s (multiple merges found):", path);
> +		for (i = 0; i < merges.nr; i++) {
> +			print_commit((struct commit *) merges.objects[i].item);
> +		}
> +		goto finish;
> +	}
> +
> +	warning("Failed to merge submodule %s (not fast-forward):\n", path);
> +	fprintf(stderr, "Found a possible merge resolution for the submodule:\n");
> +	print_commit((struct commit *) merges.objects[0].item);
> +	fprintf(stderr, "If this is correct simply add it to the index for example\n"
> +			"by using:\n\n"
> +			"  git update-index --cacheinfo 160000 %s \"%s\"\n\n"
> +			"which will accept this suggestion.\n",
> +		sha1_to_hex(merges.objects[0].item->sha1), path);
> +
> +finish:
> +	free(merges.objects);
> +	return 0;
> +}

Hmm, you can get rid of the 'goto finish' by restructuring the code like this:

parent_count = find_first_merges(&merges, path, commit_a, commit_b);
switch (parent_count) {
case 0:
	MERGE_WARNING(path, "merge following commits not found");
	break;
case 1:
	MERGE_WARNING(path, "not fast-forward");
	fprintf(...
	...
	break;
default:
	MERGE_WARNING(path, "multiple merges found");
	for (i = 0; i < merges.nr; i++)
		print_commit((struct commit *) merges.objects[i].item);
}

free(merges.objects);
return 0;


> diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh
> index 4a7b893..0f568ab 100755
> --- a/t/t7405-submodule-merge.sh
> +++ b/t/t7405-submodule-merge.sh
> @@ -54,13 +54,128 @@ test_expect_success setup '
>  	git merge -s ours a
>  '
> 
> -test_expect_success 'merging with modify/modify conflict' '
> +# History setup
> +#
> +#      b
> +#    /   \
> +#   a     d
> +#    \   /
> +#      c
> +#
> +# a in the main repository records to sub-a in the submodule and
> +# analogous b and c. d should be automatically found by merging c into
> +# b in the main repository.
> +test_expect_success 'setup for merge search' '
> +	mkdir merge-search &&
> +	cd merge-search &&
> +	git init &&
> +	mkdir sub &&
> +	(cd sub &&
> +	 git init &&
> +	 echo "file-a" > file-a &&
> +	 git add file-a &&
> +	 git commit -m "sub-a" &&
> +	 git checkout -b sub-a) &&

If you just need to create a new branch, but not necessarily check it out,
then use 'git branch <name>' instead of 'git checkout -b <name>'.

> +	git add sub &&
> +	git commit -m "a" &&
> +	git checkout -b a &&

Same as above

> +	git checkout -b b &&
> +	(cd sub &&
> +	 git checkout -b sub-b &&
> +	 echo "file-b" > file-b &&
> +	 git add file-b &&
> +	 git commit -m "sub-b") &&
> +	git commit -a -m "b" &&
> +
> +	git checkout -b c a &&
> +	(cd sub &&
> +	 git checkout -b sub-c sub-a &&
> +	 echo "file-c" > file-c &&
> +	 git add file-c &&
> +	 git commit -m "sub-c") &&
> +	git commit -a -m "c" &&
> +
> +	git checkout -b d a &&
> +	(cd sub &&
> +	 git checkout -b sub-d sub-b &&
> +	 git merge sub-c) &&
> +	git commit -a -m "d" &&
> +	git checkout -b test b &&

same as above

> +	cd ..
> +'
> +
> +test_expect_success 'merge with one side as a fast-forward of the other' '
> +	(cd merge-search &&
> +	 git checkout -b test-forward b &&
> +	 git merge d &&
> +	 git ls-tree test-forward | grep sub | cut -f1 | cut -f3 -d" " > actual &&

Use "git ls-tree test-forward sub" instead of "git ls-tree test-forward | grep sub"

> +	 (cd sub &&
> +	  git rev-parse sub-d > ../expect) &&
> +	 test_cmp actual expect)
> +'
> +
> +test_expect_success 'merging should conflict for non fast-forward' '
> +	(cd merge-search &&
> +	 git checkout -b test-nonforward b &&
> +	 (cd sub &&
> +	  git rev-parse sub-d > ../expect) &&
> +	 test_must_fail 	git merge c 2> actual  &&
> +	 grep $(<expect) actual > /dev/null &&

I cannot find the "$(<expect)" construct anywhere else in out test scripts.
Is it portable? Should we maybe use "$(cat expect)" instead?

> +	 git reset --hard)
> +'
> +
> +test_expect_success 'merging should fail for ambigous common parent' '

s/ambigous/ambiguous/

> +	cd merge-search &&
> +	git checkout -b test-ambigous b &&
> +	(cd sub &&
> +	 git checkout -b ambigous sub-b &&
> +	 git merge sub-c &&
> +	 git rev-parse sub-d > ../expect1 &&
> +	 git rev-parse ambigous > ../expect2) &&

s/ambigous/ambiguous/

> +	test_must_fail git merge c 2> actual &&
> +	grep $(<expect1) actual > /dev/null &&
> +	grep $(<expect2) actual > /dev/null &&

Same $(<foo) construct...

> +	git reset --hard &&
> +	cd ..
> +'
> +
> +# in a situation like this
> +#
> +# submodule tree:
> +#
> +#    sub-a --- sub-b --- sub-d
> +#
> +# main tree:
> +#
> +#    e (sub-a)
> +#   /
> +#  bb (sub-b)
> +#   \
> +#    f (sub-d)
> +#
> +# A merge should fail because one change points backwards.

Instead: A merge between e and f should fail because one of the submodule
commits (sub-a) don't descend from the submodule merge-base (sub-b).


Otherwise, it looks good to me. Thanks for the effort!


Have fun! :)

...Johan

-- 
Johan Herland, <johan@xxxxxxxxxxx>
www.herland.net
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]