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

Re: [PATCH 08/18] git notes merge: Initial implementation handling trivial merges only

From
Johan Herland <johan@herland.net>
Date
Oct 8, 2010, 23:55 UTC
Message-ID
<201010090155.20858.johan@herland.net>
In-Reply-To
<20101007062433.GF2285@burratino>
On Thursday 7. October 2010 08.24.33 Jonathan Nieder wrote:
Show 13 quoted lines
> Johan Herland wrote:
> > This initial implementation of 'git notes merge' only handles the trivial
> > merge cases (i.e. where the merge is either a no-op, or a fast-forward).
> 
> [...]
> 
> This reminds me: it would be nice if non-builtin scripts could use
> 
> 	git notes --get-notes-ref $refarg
> 
> to learn the configured notes ref.  In other words, shouldn't the
> default_notes_ref() exposed in patch 2 also be exposed to scripted
> callers?

I agree, but I'd like to name it 'git notes get-ref' instead, to stay consistent with the current subcommand (instead of option) design.

> If someone else doesn't get around to it, I can mock
> something up in the next few days.

There is a patch implementing 'git notes get-ref' in the next iteration of this series. :)

Show 19 quoted lines
> > +++ b/notes-merge.h
> > @@ -0,0 +1,30 @@
> > +#ifndef NOTES_MERGE_H
> > +#define NOTES_MERGE_H
> > +
> > +struct notes_merge_options {
> > +	const char *local_ref;
> > +	const char *remote_ref;
> > +	int verbosity;
> > +};
> > +
> > +void init_notes_merge_options(struct notes_merge_options *o);
> 
> [...]
> 
> > +int notes_merge(struct notes_merge_options *o,
> > +		unsigned char *result_sha1);
> 
> So the command is usable as-is from other builtins.  Nice.

Yep, that's the idea. notes-merge.h is really only an extension of notes.h for providing the merge-related functionality.

Show 23 quoted lines
> > +++ b/notes-merge.c
> > @@ -0,0 +1,103 @@
> 
> [...]
> 
> > +void init_notes_merge_options(struct notes_merge_options *o)
> > +{
> > +	memset(o, 0, sizeof(struct notes_merge_options));
> > +	o->verbosity = 2;
> > +}
> > +
> > +static int show(struct notes_merge_options *o, int v)
> > +{
> > +	return (o->verbosity >= v) || o->verbosity >= 5;
> > +}
> 
> Should the verbosities be of enum type?
> 
> 	enum notes_merge_verbosity {
> 		DEFAULT_VERBOSITY = 2,
> 		MAX_VERBOSITY = 5,
> 		etc
> 	};

Maybe. I've added these constants and used them where it made sense (to me) in the next iteration.

Show 10 quoted lines
> > +
> > +#define OUTPUT(o, v, ...) \
> > +	do { if (show((o), (v))) { printf(__VA_ARGS__); puts(""); } } while (0)
> 
> I would find it easier to read
> 
> 	if (o->verbosity >= DEFAULT_VERBOSITY)
> 		fprintf(stderr, ...)
> 
> unless there are going to be a huge number of messages.

The current version is modeled on the show() and output() functions in merge-recursive.c. I think that works better in this situation. Or maybe you have a better solution for merge-recursive.c as well?

Show 20 quoted lines
> > +
> > +int notes_merge(struct notes_merge_options *o,
> > +		unsigned char *result_sha1)
> > +{
> > +	unsigned char local_sha1[20], remote_sha1[20];
> > +	struct commit *local, *remote;
> > +	struct commit_list *bases = NULL;
> > +	const unsigned char *base_sha1;
> > +	int result = 0;
> > +
> > +	hashclr(result_sha1);
> > +
> > +	OUTPUT(o, 5, "notes_merge(o->local_ref = %s, o->remote_ref = %s)",
> > +	       o->local_ref, o->remote_ref);
> 
> Would trace_printf be a good fit for messages like this one?  If not,
> any idea about how it could be made to fit some day?
> 
> (It is especially nice to be able to direct the trace somewhere other
> than stdout and stderr when running tests.)

Agreed. The OUTPUT(o, 5, ...) instances should use trace_printf() instead. Fixed in the next iteration.

Show 6 quoted lines
> > +
> > +	if (!o->local_ref || get_sha1(o->local_ref, local_sha1)) {
> > +		/* empty notes ref => assume empty notes tree */
> 
> Can an empty ref be distinguished from a missing ref?  It would be
> nice to error out when breakage is detected.

I'm not sure when you think we should (or shouldn't) error out. FWIW, I've modeled the current code on what 'git merge' does. See [1] for more details.

Show 16 quoted lines
> > +	/* Find merge bases */
> > +	bases = get_merge_bases(local, remote, 1);
> > +	if (!bases) {
> > +		base_sha1 = null_sha1;
> > +		OUTPUT(o, 4, "No merge base found; doing history-less merge");
> > +	} else if (!bases->next) {
> > +		base_sha1 = bases->item->object.sha1;
> > +		OUTPUT(o, 4, "One merge base found (%.7s)",
> > +		       sha1_to_hex(base_sha1));
> > +	} else {
> > +		/* TODO: How to handle multiple merge-bases? */
> 
> With a recursive merge of the ancestors, of course. :)
> 
> The difficult part is what to do when a merge of ancestors results in
> conflicts.

Exactly. I feel notes merge conflicts are confusing enough to the average user, and I'd rather not expose them to resolving conflicts in _recursive_ notes merges...

I guess this becomes a discussion of whether we should model notes merges on the 'resolve' merge strategy or the 'recursive' merge strategy. Without having studied each strategy in-depth, I don't know how much "better" 'recursive' is than 'resolve', especially not from the POV of notes merges. Are there features of 'recursive' that are useful to notes merges?

(All I know of the effectual differences between 'resolve' and
'recursive' is the hand waving on the 'git merge' man page:
  "...has been reported to result in fewer merge conflicts without
   causing mis-merges by tests done on actual merge commits taken from
   Linux 2.6 kernel development history.")

In conclusion, if someone can show that's it's worth implementing recursive notes merging, then I'll gladly do it, but until then I'll stick with the simplicity of using the first merge base.

Show 16 quoted lines
> > +++ b/builtin/notes.c
> > @@ -772,6 +779,50 @@ static int show(int argc, const char **argv, const
> > +static int merge(int argc, const char **argv, const char *prefix)
> > +{
> > [...]
> > +	o.verbosity = verbosity + 2; // default verbosity level is 2
> 
> Maybe
> 
> 	o.verbosity += verbosity;
> 
> or
> 
> 	o.verbosity = DEFAULT_NOTES_MERGE_VERBOSITY + verbosity;
> 
> to allow the default verbosity to be set in one place?
Agreed. Fixed in the next iteration.
Show 13 quoted lines
> > +	result = notes_merge(&o, result_sha1);
> > +
> > +	strbuf_addf(&msg, "notes: Merged notes from %s into %s",
> > +		    remote_ref.buf, default_notes_ref());
> > +	if (result == 0) { /* Merge resulted (trivially) in result_sha1 */
> > +		/* Update default notes ref with new commit */
> > +		update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
> > +			   0, DIE_ON_ERR);
> > +	} else {
> > +		/* TODO: */
> > +		die("'git notes merge' cannot yet handle non-trivial merges!");
> 
> Mm.  In the long run, will (result != 0) mean "merge conflict"?

Almost. If you look at the notes_merge() docs in notes-merge.h by the end of this series, you'll see the following return values:

0: Merge trivially succeeded in an existing commit (e.g. fast-forward).
1: Merge successfully completes a merge commit (i.e. no conflicts).
-1: Merge results is conflicts.
> Thanks for a pleasant read.
Thanks for reading! :)
...Johan
[1]: Let's analyze the different cases here:
1. local_ref ("our" side of the merge) doesn't exist
(i.e. an unborn notes ref):

In this situation 'git merge' simply creates local_ref to point at remote_ref. 'git notes merge' does the same.

2. local_ref is empty (.git/refs/notes/$local_ref is an empty file):
'git merge' fails with
  error: unable to resolve reference HEAD: No such file or directory
  fatal: Cannot lock the ref 'HEAD'.
'git notes merge' fails with
  error: unable to resolve reference refs/notes/$local_ref: No such file or directory
  fatal: Cannot lock the ref 'refs/notes/$local_ref'.
3. local_ref refers to a non-existing commit:
'git merge' fails with
  error: Could not read $non_existing_sha1
  error: Trying to write ref ORIG_HEAD with nonexistant object $non_existing_sha1
  fatal: Cannot update the ref 'ORIG_HEAD'.
  fatal: bad object HEAD
'git notes merge' fails with
  fatal: Failed to read notes tree referenced by refs/notes/$local_ref ($non_existing_sha1)
4. local_ref refers to a non-commit (tree or blob):
'git merge' fails with
  error: Object $tree_sha1 is a tree, not a commit
  error: Object $tree_sha1 is a tree, not a commit
  Segmentation fault (someone might want to look into this...)
'git notes merge' fails with
  error: Object $tree_sha1 is a tree, not a commit
  fatal: Could not parse commit 'refs/notes/$local_ref.
Looking at remote_ref instead:
5. remote_ref ("their" side of the merge) doesn't exist
(i.e. an unborn notes ref):
'git merge' fails with:
  fatal: '$remote_ref' does not point to a commit
'git notes merge' fails with:
  fatal: Could not parse commit 'refs/notes/$remote_ref'.
6. remote_ref is empty (.git/refs/notes/$remote_ref is an empty file):
same as #5
7. remote_ref refers to a non-existing commit:
same as #5
8. remote_ref refers to a non-commit (tree or blob):
'git merge' fails with
  error: $remote_ref: expected commit type, but the object dereferences to tree type
  fatal: '$remote_ref' does not point to a commit
'git notes merge' fails with
  error: Object $tree_sha1 is a tree, not a commit
  fatal: Could not parse commit '$remote_ref'.
I believe all these outcomes make sense.
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Previous: Jonathan NiederNext: Jonathan Nieder
Message 22 of 46 in “git notes merge”
  1. 00/18 git notes mergeJohan Herland, Sep 29, 2010
  2. 01/18 (trivial) notes.h: Minor documentation fixes to copy_notes()Johan Herland, Sep 29, 2010
  3. Jonathan NiederOct 5, 2010
  4. Johan HerlandOct 5, 2010
  5. Jonathan NiederOct 5, 2010
  6. 02/18 notes.h: Make default_notes_ref() available in notes APIJohan Herland, Sep 29, 2010
  7. 03/18 notes.h/c: Clarify the handling of notes objects that are == null_sha1Johan Herland, Sep 29, 2010
  8. Jonathan NiederOct 5, 2010
  9. Johan HerlandOct 5, 2010
  10. 04/18 notes.h/c: Propagate combine_notes_fn return value to add_note() and beyondJohan Herland, Sep 29, 2010
  11. Jonathan NiederOct 5, 2010
  12. Johan HerlandOct 6, 2010
  13. 05/18 (trivial) t3303: Indent with tabs instead of spaces for consistencyJohan Herland, Sep 29, 2010
  14. 06/18 notes.c: Use two newlines (instead of one) when concatenating notesJohan Herland, Sep 29, 2010
  15. 07/18 builtin/notes.c: Split notes ref DWIMmery into a separate functionJohan Herland, Sep 29, 2010
  16. Jonathan NiederOct 5, 2010
  17. Johan HerlandOct 7, 2010
  18. 08/18 git notes merge: Initial implementation handling trivial merges onlyJohan Herland, Sep 29, 2010
  19. Test script style (Re: [PATCH 08/18] git notes merge: Initial implementation handling trivial merges only)Jonathan Nieder, Oct 7, 2010
  20. Junio C HamanoOct 7, 2010
  21. Jonathan NiederOct 7, 2010
  22. Johan HerlandOct 8, 2010
  23. Jonathan NiederOct 9, 2010
  24. Johan HerlandOct 21, 2010
  25. 09/18 builtin/notes.c: Refactor creation of notes commits.Johan Herland, Sep 29, 2010
  26. 10/18 git notes merge: Handle real, non-conflicting notes mergesJohan Herland, Sep 29, 2010
  27. Ævar Arnfjörð BjarmasonSep 29, 2010
  28. Johan HerlandSep 29, 2010
  29. 11/18 git notes merge: Add automatic conflict resolvers (ours, theirs, union)Johan Herland, Sep 29, 2010
  30. Stephen BoydOct 2, 2010
  31. Johan HerlandOct 4, 2010
  32. 12/18 Documentation: Preliminary docs on 'git notes merge'Johan Herland, Sep 29, 2010
  33. Stephen BoydOct 2, 2010
  34. Johan HerlandOct 4, 2010
  35. 13/18 git notes merge: Manual conflict resolution, part 1/2Johan Herland, Sep 29, 2010
  36. 14/18 git notes merge: Manual conflict resolution, part 2/2Johan Herland, Sep 29, 2010
  37. Ævar Arnfjörð BjarmasonSep 29, 2010
  38. Johan HerlandSep 29, 2010
  39. 15/18 git notes merge: List conflicting notes in notes merge commit messageJohan Herland, Sep 29, 2010
  40. 16/18 git notes merge: --commit should fail if underlying notes ref has movedJohan Herland, Sep 29, 2010
  41. 17/18 git notes merge: Add another auto-resolving strategy: "cat_sort_uniq"Johan Herland, Sep 29, 2010
  42. 18/18 git notes merge: Add testcases for merging notes trees at different fanoutsJohan Herland, Sep 29, 2010
  43. Sverre RabbelierSep 29, 2010
  44. Johan HerlandSep 29, 2010
  45. Sverre RabbelierSep 29, 2010
  46. Johan HerlandSep 29, 2010

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.