git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 13/13] Build in merge

From
Miklos Vajna <vmiklos@frugalware.org>
Date
Jul 1, 2008, 02:13 UTC
Message-ID
<20080701021317.GS4729@genesis.frugalware.org>
In-Reply-To
<7vd4lz4gtw.fsf@gitster.siamese.dyndns.org>
On Sun, Jun 29, 2008 at 10:44:43PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
Show 20 quoted lines
> > +static void save_state(void)
> > +{
> > +	int fd;
> > +	struct child_process stash;
> > +	const char *argv[] = {"stash", "create", NULL};
> > +
> > +	fd = open(git_path("MERGE_STASH"), O_WRONLY | O_CREAT, 0666);
> > +	if (fd < 0)
> > +		die("Could not write to %s", git_path("MERGE_STASH"));
> > +	memset(&stash, 0, sizeof(stash));
> > +	stash.argv = argv;
> > +	stash.out = fd;
> > +	stash.git_cmd = 1;
> > +	run_command(&stash);
> > +}
> 
> I first thought "heh, that's clever" until I noticed that we use "stash
> create" with "stash apply" these days instead of cpio for this.  I suspect
> that we can do away without leaving the stash in this temporary file, but
> that comment applies to the scripted version as well.
We can. I just did it. ;-)
> By the way, it would be consistent to name counterpart to dropsave in the
> scripted version as "drop_save" if you use "save_state" and "restore_state".
OK, renamed.
Show 33 quoted lines
> 
> > +static void reset_hard(unsigned const char *sha1, int verbose)
> > +{
> > +	struct tree *tree;
> > +	struct unpack_trees_options opts;
> > +	struct tree_desc t;
> > +
> > +	memset(&opts, 0, sizeof(opts));
> > +	opts.head_idx = -1;
> > +	opts.src_index = &the_index;
> > +	opts.dst_index = &the_index;
> > +	opts.update = 1;
> > +	opts.reset = 1;
> > +	if (verbose)
> > +		opts.verbose_update = 1;
> > +
> > +	tree = parse_tree_indirect(sha1);
> > +	if (!tree)
> > +		die("failed to unpack %s tree object", sha1_to_hex(sha1));
> > +	parse_tree(tree);
> > +	init_tree_desc(&t, tree->buffer, tree->size);
> > +	if (unpack_trees(1, &t, &opts))
> > +		exit(128); /* We've already reported the error, finish dying */
> > +}
> 
> Isn't this trashing all the cached stat info from the index?  If this is
> emulating "reset --hard", it also should set opts.merge and do
> oneway_merge, after reading the current index in, I think.  Resetting the
> index and the working tree is not particularly performance critical part,
> but trashing the cached stat info would hurt the performance of everything
> that reads the index after this function returns quite badly.  I suspect
> that you might be better off forking the real thing (reset --hard) if you
> cannot get it right here.

I just realized that builtin-reset forks read-tree as well, so I did almost the same.

Show 16 quoted lines
> > +/* Get the name for the merge commit's message. */
> > +static void merge_name(const char *remote, struct strbuf *msg)
> > ...
> > +	strbuf_init(&buf, 0);
> > +	strbuf_addstr(&buf, "refs/heads/");
> > +	strbuf_addstr(&buf, remote);
> > +	dwim_ref(buf.buf, buf.len, branch_head, &ref);
> > +	if (!hashcmp(remote_head->sha1, branch_head)) {
> > +		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
> > +			sha1_to_hex(branch_head), remote);
> > +		return;
> > +	}
> 
> Hmm, why not resolve_ref() so that it does not dwim at all?  The point of
> the code is so that you can be confident that 'blah' *is* a local branch
> when you say "branch 'blah'".
Yes. I'm now using resolve_ref().
Show 16 quoted lines
> > +	/* See if remote matches <name>~<number>, or <name>^ */
> > +	ptr = strrchr(remote, '^');
> > +	if (ptr && ptr[1] == '\0') {
> > +		len = strlen(remote);
> > +		while ((ptr = (char *)memrchr(remote, '^', len)))
> > +			if (ptr && ptr[1] == '\0')
> > +				len = ptr - remote - 1;
> > +			else
> > +				break;
> 
> That's a funny way to say:
> 
> 	for (len = 0, ptr = remote + strlen(remote);
>              remote < ptr && ptr[-1] == '^';
>              ptr--)
> 		len++;

Ah, and this way I don't need memrchr(), which was pointed out to be problemtic on Cygwin.

Show 14 quoted lines
> 
> > +	if (len) {
> > +		struct strbuf truname = STRBUF_INIT;
> > +		strbuf_addstr(&truname, remote);
> > +		strbuf_setlen(&truname, len);
> > +		if (dwim_ref(truname.buf, truname.len, buf_sha, &ref)) {
> > +			strbuf_addf(msg,
> > +				"%s\t\tbranch '%s' (early part) of .\n",
> > +				sha1_to_hex(remote_head->sha1), truname.buf);
> > +			return;
> 
> Isn't this wrong?  Giving "v1.5.6~20" to this code will strip ~20 and make
> remote = "v1.5.6", to which dwim_ref() will happily say Ok, and you end up
> saying "branch 'v1.5.6' (early part)", don't you?
Right. Now I do
        strbuf_addstr(&truname, "refs/heads/");

Before appending the remote name to truname, so that should exclude tags.

Show 13 quoted lines
> > +static int read_tree_trivial(unsigned char *common, unsigned char *head,
> > +	unsigned char *one)
> > +{
> > +	int i, nr_trees = 0;
> > +	struct tree *trees[MAX_UNPACK_TREES];
> > +	struct tree_desc t[MAX_UNPACK_TREES];
> > +	struct unpack_trees_options opts;
> > +
> > +	memset(&opts, 0, sizeof(opts));
> > +	opts.head_idx = -1;
> 
> Is this the correct head_idx value for this three-way merge?  I think it
> should be 2 but please double check.
Yes, you are right. I just checked builtin-read-tree and it's 2, not -1.
Show 8 quoted lines
> > +static int commit_tree_trivial(const char *msg, unsigned const char *tree,
> > +		struct commit_list *parents, unsigned char *ret)
> > +{
> >  ...
> > +}
> 
> We may want to have another patch before this one to abstract most of
> cmd_commit_tree() out, perhaps?
Done. And now builtin-merge uses commit_tree() as well.
Show 31 quoted lines
> > +int cmd_merge(int argc, const char **argv, const char *prefix)
> > ...
> > +	/*
> > +	 * This could be traditional "merge <msg> HEAD <commit>..."  and
> > +	 * the way we can tell it is to see if the second token is HEAD,
> > +	 * but some people might have misused the interface and used a
> > +	 * committish that is the same as HEAD there instead.
> > +	 * Traditional format never would have "-m" so it is an
> > +	 * additional safety measure to check for it.
> > +	 */
> > +	strbuf_init(&buf, 0);
> > +	if (argc > 1) {
> > +		unsigned char second_sha1[20];
> > +
> > +		if (get_sha1(argv[1], second_sha1))
> > +			die("Not a valid ref: %s", argv[1]);
> > +		second_token = lookup_commit_reference_gently(second_sha1, 0);
> > +		if (!second_token)
> > +			die("'%s' is not a commit", argv[1]);
> 
> Interesting.
> 
> This _superficially_ is quite wrong, because the purpose of this part of
> the code is to tell if we got old-style invocation, and we should not
> barfing merely because what we got is _not_ old-style.  If it is not
> old-style, then it would be new-style, and the logic to tell if it is
> old-style should ideally not have much knowledge about the new-style
> invocation to say "hey, that's an incocrrect new-style invocation".  By
> the way, this part should probably be in a separate function:
> 
> 	static int is_old_style_invocation(int ac, const char **gv);
OK, I broke out is_old_style_invocation() from cmd_merge().
Show 26 quoted lines
> Old-style invocation of "git merge" (primarily by "git pull") was
> to call it as:
> 
> 	git merge "message here" HEAD $commit1 $commit2...
> 
> and it checks the second token ("HEAD" in the above, but people can misuse
> the interface to name the current branch name).  If the second token is
> not a ref that resolves to a commit, all you know is that this is _not_ an
> old-style invocation, and calling the program with new-style is not a
> crime.
> 
> The only reason this is wrong only superficially is because new style
> invocation would always be:
> 
> 	git merge [options] $commit1 $commit2...
> 
> after stripping the options, and these seemingly wrong die() will complain
> when you try to create an Octopus with the new-style syntax and the
> parameter given as the second remote parent is not a commit.  So the logic
> is wrong, the fact that the user gets the same error message for incorrect
> old-style invocation (perhaps "git merge <msg> HAED $commit") and
> incorrect new-style invocation "git merge $commit1 $nonsense" is just an
> accident, and the end result does not hurt, but asks for a "Huh? why does
> it check and complain only the second parent here but not the first one?".
> 
> It is interesting, but feels quite dirty.

Now if the second token is a valid SHA1 then I die() if it's not a commit, but otherwise I just assume it's a new-style invocation.

Show 10 quoted lines
> > +	if (!have_message && second_token &&
> > +		!hashcmp(second_token->object.sha1, head)) {
> 
> You need to know that resolve_ref() cleared head[] when head_invalid is
> true when reading this code to notice that, unlike the previous round of
> this patch, it is Ok not to check head_invalid is fine here.  I somehow
> feel it is an unnecessary optimization/obfuscation.
> 
> But once you have "is_old_style_invocation" suggested earlier, this part
> would look much cleaner and the above comment would become unnecessary.
Yes, now it's just:
       if (!have_message && is_old_style_invocation(argc, argv)) {
Show 34 quoted lines
> 
> > +	for (i = 0; i < argc; i++) {
> > +		struct object *o;
> > +
> > +		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
> > +		if (!o)
> > +			die("%s - not something we can merge", argv[i]);
> > +		remotes = &commit_list_insert(lookup_commit(o->sha1),
> > +			remotes)->next;
> > +
> > +		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
> > +		setenv(buf.buf, argv[i], 1);
> > +		strbuf_reset(&buf);
> > +	}
> > +
> > +		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
> > +			0, NULL, OBJ_COMMIT);
> > +		if (!o)
> > +			return 0;
> > +
> > +		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
> > +			return 0;
> 
> When o does not peel well, or checkout_fast_forward() returns failure,
> that would be a failure case, wouldn't it?  Why return 0?
> 
> Maybe you misread "exit" in shell scripts?  It does not mean exit(0); it
> means "exit with the same exit status as the last command".  So
> 
> 	new_head=$(git rev-parse ...) &&
>         git read-tree -m -u ... &&
>         finish || exit
> 
> will exit non-zero if any of the commands chained by && fails.

Thanks, that was the case. I thought "false || exit" exits with status code 0.

Show 21 quoted lines
> > +	/*
> > +	 * At this point, we need a real merge.  No matter what strategy
> > +	 * we use, it would operate on the index, possibly affecting the
> > +	 * working tree, and when resolved cleanly, have the desired
> > +	 * tree in the index -- this means that the index must be in
> > +	 * sync with the head commit.  The strategies are responsible
> > +	 * to ensure this.
> > +	 */
> > +	if (use_strategies.nr != 1) {
> > +		/*
> > +		 * Stash away the local changes so that we can try more
> > +		 * than one.
> > +		 */
> > +		save_state();
> > +		single_strategy = 0;
> > +	} else {
> > +		unlink(git_path("MERGE_STASH"));
> > +		single_strategy = 1;
> 
> I think s/single_strategy/(use_strategies.nr == 1)/ in the remainder of the
> code would be taking advantage of working in C ;-)
I dropped single_strategy.
Show 16 quoted lines
> 
> > +		if (ret) {
> > +			/*
> > +			 * The backend exits with 1 when conflicts are
> > +			 * left to be resolved, with 2 when it does not
> > +			 * handle the given merge at all.
> > +			 */
> > +			if (ret == 1) {
> 
> Probably from here til ...
> 
> > +				int cnt = 0;
> > ...
> > +				cnt += count_unmerged_entries();
> 
> ... here should be a separate "evaluate_result()" function.
Done.
Previous: Alex RiesenNext: Olivier Marin
Message 27 of 50 in “Build in merge”
  1. 00/15 Build in mergeMiklos Vajna, Jun 27, 2008
  2. 01/15 Move split_cmdline() to alias.cMiklos Vajna, Jun 27, 2008
  3. 02/15 Move commit_list_count() to commit.cMiklos Vajna, Jun 27, 2008
  4. 03/15 Move parse-options's skip_prefix() to git-compat-util.hMiklos Vajna, Jun 27, 2008
  5. 04/15 Add new test to ensure git-merge handles pull.twohead and pull.octopusMiklos Vajna, Jun 27, 2008
  6. 05/15 Move read_cache_unmerged() to read-cache.cMiklos Vajna, Jun 27, 2008
  7. 06/15 git-fmt-merge-msg: make it usable from other builtinsMiklos Vajna, Jun 27, 2008
  8. 07/15 Introduce get_octopus_merge_bases() in commit.cMiklos Vajna, Jun 27, 2008
  9. 08/15 Add new test to ensure git-merge handles more than 25 refs.Miklos Vajna, Jun 27, 2008
  10. 09/15 Introduce get_merge_bases_many()Miklos Vajna, Jun 27, 2008
  11. 10/15 Introduce reduce_heads()Miklos Vajna, Jun 27, 2008
  12. 11/15 Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()Miklos Vajna, Jun 27, 2008
  13. 12/15 strbuf_vaddf(): support %*s, tooMiklos Vajna, Jun 27, 2008
  14. 13/15 Add new test case to ensure git-merge reduces octopus parents when possibleMiklos Vajna, Jun 27, 2008
  15. 14/15 Add new test case to ensure git-merge prepends the custom merge messageMiklos Vajna, Jun 27, 2008
  16. 15/15 Build in mergeMiklos Vajna, Jun 27, 2008
  17. 15/15 Build in mergeMiklos Vajna, Jun 27, 2008
  18. Junio C HamanoJun 28, 2008
  19. Miklos VajnaJun 28, 2008
  20. 13/13 Build in mergeMiklos Vajna, Jun 28, 2008
  21. Junio C HamanoJun 29, 2008
  22. Jakub NarebskiJun 29, 2008
  23. Miklos VajnaJun 30, 2008
  24. 13/13 Build in mergeMiklos Vajna, Jun 30, 2008
  25. Junio C HamanoJun 30, 2008
  26. Alex RiesenJun 30, 2008
  27. Miklos VajnaJul 1, 2008
  28. Olivier MarinJun 30, 2008
  29. Miklos VajnaJun 30, 2008
  30. Junio C HamanoJun 30, 2008
  31. Miklos VajnaJun 30, 2008
  32. Johannes SchindelinJun 28, 2008
  33. Junio C HamanoJun 29, 2008
  34. Johannes SchindelinJun 29, 2008
  35. Alex RiesenJun 29, 2008
  36. Junio C HamanoJun 29, 2008
  37. Sverre RabbelierJun 29, 2008
  38. 08/15 Add new test to ensure git-merge handles more than 25 refs.Miklos Vajna, Jun 27, 2008
  39. Olivier MarinJun 29, 2008
  40. Add new test to ensure git-merge handles pull.twohead and pull.octopusMiklos Vajna, Jun 29, 2008
  41. Miklos VajnaJun 29, 2008
  42. Mike RalphsonJul 4, 2008
  43. Miklos VajnaJul 5, 2008
  44. Fix t7601-merge-pull-config.sh on AIXMiklos Vajna, Jul 5, 2008
  45. Junio C HamanoJul 5, 2008
  46. Olivier MarinJun 29, 2008
  47. Johannes SchindelinJun 29, 2008
  48. Olivier MarinJun 29, 2008
  49. Johannes SchindelinJun 29, 2008
  50. Olivier MarinJun 30, 2008

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.