threads / rfc / 8057

[RFC] Second parent for reverts

Subject: [RFC] Second parent for reverts

## tl;dr

53 messages between May 9, 2007 and May 29, 2007.

replies: 52people: 11as markdown or json

Daniel Barkalow· May 9, 2007, 19:20 UTC · lore

The discussion about having a header to specify, for a revert commit, what it reverts made me realize that this header *would* be useful, but that we don't need a *new* header for it. I think that the right method is to add the parent of the reverted commit as a second parent for the revert.

If you have:
a -> b -> c -> d
And you want to revert b, the most exact flow would be:
a -> b -> c -> d -> e
       \         /
        -> a' ---

I.e., you exactly remove the effects of b to generate a commit that has the same tree as a, and then you merge.

But a' doesn't actually take anything from b, since it's reverting all of b (unless it's only reverting part of b), and, if b isn't there, it doesn't need a commit message, either, so it's not different from a. So the flow should be:

a -> b -> c -> d -> e
  \              /
   --------------

And this means blame work correctly: lines that b changed will be blamed on a (or an ancestor), because e will match a there and be different from d. So I think git-revert should simply add in the reverted patch's parent. Does this analysis make sense to other people?

	-Daniel
*This .sig left intentionally blank*
Johannes Schindelin· May 9, 2007, 20:07 UTC · re: Daniel Barkalow · lore

Re: [RFC] Second parent for reverts

Hi,
On Wed, 9 May 2007, Daniel Barkalow wrote:
Show 5 quoted lines
> The discussion about having a header to specify, for a revert commit, 
> what it reverts made me realize that this header *would* be useful, but 
> that we don't need a *new* header for it. I think that the right method 
> is to add the parent of the reverted commit as a second parent for the 
> revert.

I am not so sure. In a sense, you are correct. But everybody who does "git log --no-merges" would no longer see reverts. Which is somewhat incorrect.

Ciao, Dscho

Shawn O. Pearce· May 9, 2007, 20:22 UTC · re: Johannes Schindelin · lore

Re: [RFC] Second parent for reverts

Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
Show 10 quoted lines
> On Wed, 9 May 2007, Daniel Barkalow wrote:
> 
> > The discussion about having a header to specify, for a revert commit, 
> > what it reverts made me realize that this header *would* be useful, but 
> > that we don't need a *new* header for it. I think that the right method 
> > is to add the parent of the reverted commit as a second parent for the 
> > revert.
> 
> I am not so sure. In a sense, you are correct. But everybody who does "git 
> log --no-merges" would no longer see reverts. Which is somewhat incorrect.
Right.

I've actually done what Daniel just talked about doing in one of my "production" repositories. I did it by hand as a developer had created a bad merge and accidentially reverted 800 files during that merge. 80 or so commits later along a public non-rewinding branch coworkers realized things weren't right, and asked me to fix the mess. As I wanted to save the blame data when I reverted-the-revert I did what Daniel suggests.

But since the revert-the-revert wasn't really an interesting point in history, and neither was the bad merge, I don't really care that neither shows up with --no-merges. The original bad merge was a simple honest mistake made by a developer who was new to Git, and was only caused because merge-recursive wasn't installed properly on that system.

As Dscho says, most reverts are interesting points in time. *Why* a particular revert was done is important.

And so I have to disagree quite a bit with Daniel's idea, for exactly that reason. If I'm looking at a block of code in a file I want to know why its there. If blame tells me its a revert of something, that tells me we tried another path and it didn't work out. I might be sitting here looking at this line because I'm thinking of redoing whatever it was that wasn't good!

So that revert commit message better say why that thing didn't work out.

If I really do care about the source of that line, I can always re-run blame on the parent of the reverted commit (hence why ^ is so nice as a suffix on a commit-ish!) and examine the line again.

Hmm. I should teach git-gui to parse out the revert message and let you click into its parent. Simple enough. Maybe it will be in 0.7.0. Maybe it won't be. ;-)

-- 
Shawn.
Johan Herland· May 9, 2007, 22:26 UTC · re: Shawn O. Pearce · lore

Re: [RFC] Second parent for reverts

On Wednesday 09 May 2007, Shawn O. Pearce wrote:
Show 39 quoted lines
> Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> > On Wed, 9 May 2007, Daniel Barkalow wrote:
> > > The discussion about having a header to specify, for a revert
> > > commit, what it reverts made me realize that this header *would*
> > > be useful, but that we don't need a *new* header for it. I think
> > > that the right method is to add the parent of the reverted commit
> > > as a second parent for the revert.
> >
> > I am not so sure. In a sense, you are correct. But everybody who
> > does "git log --no-merges" would no longer see reverts. Which is
> > somewhat incorrect.
>
> Right.
>
> I've actually done what Daniel just talked about doing in one of my
> "production" repositories.  I did it by hand as a developer had
> created a bad merge and accidentially reverted 800 files during
> that merge.  80 or so commits later along a public non-rewinding
> branch coworkers realized things weren't right, and asked me
> to fix the mess.  As I wanted to save the blame data when I
> reverted-the-revert I did what Daniel suggests.
>
> But since the revert-the-revert wasn't really an interesting point
> in history, and neither was the bad merge, I don't really care that
> neither shows up with --no-merges.  The original bad merge was a
> simple honest mistake made by a developer who was new to Git, and
> was only caused because merge-recursive wasn't installed properly
> on that system.
>
>
> As Dscho says, most reverts are interesting points in time.  *Why*
> a particular revert was done is important.
>
> And so I have to disagree quite a bit with Daniel's idea, for exactly
> that reason.  If I'm looking at a block of code in a file I want to
> know why its there.  If blame tells me its a revert of something,
> that tells me we tried another path and it didn't work out.  I might
> be sitting here looking at this line because I'm thinking of redoing
> whatever it was that wasn't good!

Sorry for butting in with pretty much the same arguments as I had in the previous thread on the "Reverts" header field, but...:

Isn't this exactly why we could use the "Reverts" header field?
1. It would enable Daniel (and me) to get the "correct" blame data (for 
some version of "correct" that at least seems to make sense to us).
2. It would make it trivial for git-log (and other porcelains) to detect 
reverts and color them bright red in Shawn's and Dscho's logs, so that 
they can easily see when and why things were reverted.
3. It wouldn't screw up "git log --no-merges" since it doesn't interfere 
with the "real" parent data.
What am I still missing here?
> Hmm.  I should teach git-gui to parse out the revert message and
> let you click into its parent.  Simple enough.  Maybe it will be
> in 0.7.0.  Maybe it won't be.  ;-)

Isn't it better/cleaner to get this info from a (strict-format) header field, rather than parsing the commit message? (or worse, detecting reverse diffs/patches?)

Have fun!
...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Junio C Hamano· May 9, 2007, 21:54 UTC · re: Daniel Barkalow · lore

Re: [RFC] Second parent for reverts

Daniel Barkalow <barkalow@iabervon.org> writes:
Show 31 quoted lines
> The discussion about having a header to specify, for a revert commit, what 
> it reverts made me realize that this header *would* be useful, but that we 
> don't need a *new* header for it. I think that the right method is to add 
> the parent of the reverted commit as a second parent for the revert.
>
> If you have:
>
> a -> b -> c -> d
>
> And you want to revert b, the most exact flow would be:
>
> a -> b -> c -> d -> e
>        \         /
>         -> a' ---
>
> I.e., you exactly remove the effects of b to generate a commit that has 
> the same tree as a, and then you merge.
>
> But a' doesn't actually take anything from b, since it's reverting all of 
> b (unless it's only reverting part of b), and, if b isn't there, it 
> doesn't need a commit message, either, so it's not different from a. So 
> the flow should be:
>
> a -> b -> c -> d -> e
>   \              /
>    --------------
>
> And this means blame work correctly: lines that b changed will be blamed 
> on a (or an ancestor), because e will match a there and be different from 
> d. So I think git-revert should simply add in the reverted patch's parent. 
> Does this analysis make sense to other people?

The revert operation at the tree level (not commit level) treats AS IF b is a common ancestor between a and d and computes a merge between a and d using that fake common ancestor to reach at e. So it is understandable that you are confused that the result somehow has something to do with a merge between a and d.

But other than that, the "analysis" does not make any sense to me.

When you have a merge commit somebody else made, you should be able to reproduce it yourself, with the help from the intent of the merge explained in the commit log of the merge commit (usually it says "I am merging this development line with that one"). With a->b->c->d history, what is the sane merge result between a and d? It won't be e. The only clue that you did a revert of b is contained in the message "Revert b", because b is not a common ancestor between a and d.

An interesting tangent is that you can revert both b and c with a single tree-level operation by pretending as if c is a common ancestor between a and d and run tree-level 3-way merge to apply difference between c and d on top of a to come up with e. It would not make sense to record neither one of a b c as the fake second parent.

I would suggest to leave a revert as a revert. It is not a merge.

In general, you should think of an act of making a commit (not limited to a merge but a single parent) to mean that you are making this statement:

	I have considered the development history that leads to
	the parent commits this commit records, and the tree
	recorded with this commit suits the purpose of my branch
	better than all of them.

Obviously, if you considered the history that leads to 'd', you have considered the hsitory that leads to 'a' as well, so recording both 'a' and 'd' as parents does not make much sense. You could have recorded 'e' as an Octopus of 'a', 'b', ... 'd', and the statement does not change.

Linus Torvalds· May 9, 2007, 22:16 UTC · re: Junio C Hamano · lore

Re: [RFC] Second parent for reverts

On Wed, 9 May 2007, Junio C Hamano wrote:
> 
> I would suggest to leave a revert as a revert.  It is not a
> merge.
Yes, please.

People can do fancy things if they want, but quite frankly, if you start playing games with the "parent" pointer, it starts losing its meaning, and becomes a random "pointer to another related commit".

Then, you might as well say "oh, this commit is related to that other commit", and decide to call the other commit "related" too, and now a random commit just looks like a strange merge.

That's simply not what "parenthood" is about. Parenthood is about nonlinear development, and parents should not be reachable from each other (which such a bogus revert or "related" parent would almost always be: it would be reachable from the *real* parent.

If you want a "related to that commit" field, it should be a separate field in the commit object. But since it doesn't really have any real *semantic* meaning to git itself, it shouldn't be in the header. We could, for example, make it be in the free-form section, and teach our graphical visualization tools to automatically turn it into a hyperlink.

.. which we already do.
		Linus
Linus Torvalds· May 10, 2007, 16:35 UTC · re: Linus Torvalds · lore

Re: [RFC] Second parent for reverts

On Wed, 9 May 2007, Linus Torvalds wrote:
Show 8 quoted lines
> 
> If you want a "related to that commit" field, it should be a separate 
> field in the commit object. But since it doesn't really have any real 
> *semantic* meaning to git itself, it shouldn't be in the header. We 
> could, for example, make it be in the free-form section, and teach our 
> graphical visualization tools to automatically turn it into a hyperlink.
> 
> .. which we already do.

Btw, sorry for harping on this issue, but one of the really *great* things about putting things in the free-form section is a somewhat unanticipated huge advantage:

 - we've had much better integration with non-git users than any other SCM 
   I've ever seen!

Now, a lot of that was by design (ie one of the primary design goals for git was to work well with patches), and it's one of the reasons that I totally dismiss the whole "track file ID's" idiocy: anything but "content" will pretty much by definition not be tracked by any other source control system.

But it turns out that the whole "you can point to commits in the free-form commit message" thing has worked out really well. It means, for example, that people can do "git revert" operations in their own local repository, AND THEY TRANSLATE BEAUTIFULLY EVEN AS EMAILED PATCHES!

I'm shouting, because it's easy to overlook these kinds of issues, but they are really really important. Designing your SCM around the notion that everybody will use a totally integrated system is a mistake! It's a *huge* mistake. Even a lot of git users end up sending patches back and forth, just because for many things it's actually more appropriate.

So I'm literally getting patches that refer to commits that I've integrated ("Commit xyz introduced a nasty bug", or "Revert commit abc") and they work very well even when I'm not merging with those people natively through git.

And yes, maybe the kernel is a bit unusual in this, but I really don't think it should be. In many ways, emailing patches around is a much better workflow for actual *development* than doing git merges. The git merges are wonderful, but they are kind of a "the development is done, let's merge it" operation - they are not good for sending stuff out for comments or discussion!

So in general, putting things into the headers and having git semantic meaning should be discouraged. The "parents" thing is special, because the whole "history" thing is very deeply integrated in git, and obviously has to be (any SCM that does _not_ have parenthood information is totally broken *cough*CVS/SVN*cough*), but other than that we should actually strive to _avoid_ anything with deep git semantics.

		Linus
Johan Herland· May 10, 2007, 18:06 UTC · re: Linus Torvalds · lore

Re: [RFC] Second parent for reverts

On Thursday 10 May 2007, Linus Torvalds wrote:
Show 26 quoted lines
> On Wed, 9 May 2007, Linus Torvalds wrote:
> > If you want a "related to that commit" field, it should be a
> > separate field in the commit object. But since it doesn't really
> > have any real *semantic* meaning to git itself, it shouldn't be in
> > the header. We could, for example, make it be in the free-form
> > section, and teach our graphical visualization tools to
> > automatically turn it into a hyperlink.
> >
> > .. which we already do.
>
> Btw, sorry for harping on this issue, but one of the really *great*
> things about putting things in the free-form section is a somewhat
> unanticipated huge advantage:
>
>  - we've had much better integration with non-git users than any
> other SCM I've ever seen!
>
> [...]
>
> So in general, putting things into the headers and having git
> semantic meaning should be discouraged. The "parents" thing is
> special, because the whole "history" thing is very deeply integrated
> in git, and obviously has to be (any SCM that does _not_ have
> parenthood information is totally broken *cough*CVS/SVN*cough*), but
> other than that we should actually strive to _avoid_ anything with
> deep git semantics.

Ok. I'm sold. I will take my header fields and go away before y'all replace me with a very small shell script. :)

BTW, I'm wondering whether anybody has ever thought about allowing after-the-fact annotations on commits. Kinda like free-form continuations on the commit message. It would allow people to make notes on previous commits that were either forgotten at commit-time, or only became apparent after the commit was done.

Furthermore, if we make git-blame pay attention to hints in the commit message (like Junio suggested somewhere else in this thread) - including the annotations - we can then add annotations to guide git-blame whenever it gets the blame wrong.

There's probably other things we could use this for.

Obviously we can't store the annotations in the commit object itself (because commit objects are immutable). I'm thinking annotations could be stored as simple (compressed) text files in .git/annotations/, under the same sha1/filename as the corresponding commit object is stored under .git/objects/. That would make them easy to retrieve from their corresponding commit.

Anyway, it's just an idea that struck me. Feel free to tell me why this is the worst idea since, oh, I don't know, say, my header fields idea...

Have fun! :)
...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Linus Torvalds· May 10, 2007, 18:22 UTC · re: Johan Herland · lore

Re: [RFC] Second parent for reverts

On Thu, 10 May 2007, Johan Herland wrote:
Show 6 quoted lines
> 
> BTW, I'm wondering whether anybody has ever thought about allowing 
> after-the-fact annotations on commits. Kinda like free-form 
> continuations on the commit message. It would allow people to make 
> notes on previous commits that were either forgotten at commit-time, or 
> only became apparent after the commit was done.
We kind of have some of that.

Tag objects can be used that way, and the "grafts" file is a very special case.

But if you want to do it on a larger scale, you'd need something that is really optimized for that. For example, git internally now has a notion of "decorating" arbitrary objects with arbitrary data, and if you just had an efficient file format to create such decorations (for blame or other special ops), the *code* is easy to write. It's how

	git log --decorate

works right now (the "data" is just the tag names, but you could make it read other decorations, and the git data structures are very efficient, and allow different types of decorations to be used independently of each other).

		Linus
Johan Herland· May 27, 2007, 14:08 UTC · re: Linus Torvalds · lore

[PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Thursday 10 May 2007, Linus Torvalds wrote:
Show 25 quoted lines
> On Thu, 10 May 2007, Johan Herland wrote:
> > 
> > BTW, I'm wondering whether anybody has ever thought about allowing 
> > after-the-fact annotations on commits. Kinda like free-form 
> > continuations on the commit message. It would allow people to make 
> > notes on previous commits that were either forgotten at commit-time, or 
> > only became apparent after the commit was done.
> 
> We kind of have some of that.
> 
> Tag objects can be used that way, and the "grafts" file is a very special 
> case. 
> 
> But if you want to do it on a larger scale, you'd need something that is 
> really optimized for that. For example, git internally now has a notion of 
> "decorating" arbitrary objects with arbitrary data, and if you just had an 
> efficient file format to create such decorations (for blame or other 
> special ops), the *code* is easy to write. It's how
> 
> 	git log --decorate
> 
> works right now (the "data" is just the tag names, but you could make it 
> read other decorations, and the git data structures are very efficient, 
> and allow different types of decorations to be used independently of 
> each other).

I've been working on combining tag objects and --decorate into a useful proof-of-concept that provides the after-the-fact commit annotations I requested above, and here's the result:

<quote src="Documentation/git-note.txt"> A git note provides an after-the-fact text annotation associated with an exiting object in the git object database. The note itself is also stored in the database (as a special case of a tag object). Note object are therefore -- as all other objects in the database -- immutable, i.e. once added they cannot be edited, although they can be deleted.

As with regular tag objects, notes are useless unless they have a corresponding reference stored in `.git/refs`. For notes, these references are stored in a hierarchy under `.git/refs/notes`. For each object that has one or more associated notes, there is a subdirectory under `.git/refs/notes` named after the object identifier (SHA1 sum). The object's subdirectory contains one file per note associated with that object. The end result is that notes are easily accessible from the name/identifier of the object they are associated with.

Note that deleting a note with `git-note -d` only deletes the reference to the note. The note itself is still present in the object database, but will be considered unreachable by gitlink:git-fsck[1], and may be removed with gitlink:git-prune[1]. </quote>

Here are some more details from the design notes I based my work on:
- Notes must not run counter to the existing design (and design goals) of git
- A note is itself stored as a git tag object:
	- "object" has SHA1 of this note's object (as with regular tag objects)
	- "type" has type of this note's object (as with regular tag objects)
	- "tag" is fundamentally unimportant to the note. However, in order
	  to provide a unique tag name (so as not to be confused with any
	  other tag object) this is automatically set to "note-{$sha1}",
	  where {$sha1} is the SHA1 sum of the "object" and "tagger" fields
	  and the note message. If uniqueness of tag names within tag objects
	  is not important, we may consider dropping this
	- "tagger" has the note author and date (same as regular tag objects)
	- The rest of the tag object contains the note message, similar in
	  form to a commit message
- External mapping of objects to associated notes is kept below .git/refs/notes:
	- One file per note object (as with tag refs in .git/refs/tags)
	- Located at .git/refs/notes/{$object-sha1}/{$note-sha1}
	- File contains SHA1 of the note object (same as refs in .git/refs/tags)
	- Yes, this means that the filename (minus directory part) is identical
	  to the contents of the file
Some of the properties that result from the above decisions:
- A note is uniquely identified by its SHA1 sum (as are all other git objects)
- Notes are trivially packable
- The entire set of note refs can be fully recreated by trawling the object db
  for tag objects with the tag name field matching the "note-{$sha1}" pattern
- Notes behave like regular tags under clone/push/fetch
- Notes are automatically checked by fsck (although some extra checking has
  has been added)
- Little impact to existing git architecture
However, there are still some remaining questions:
- Is the creation of a unique tag name (i.e. the 'tag' field _inside_ the
  object) for note objects really necessary? Which parts of the system rely
  on these names to be unique across tag objects?
- Should notes have their own object type instead of piggy-backing on "tag"?
- What about having a note object type with minimal header fields, and make
  tags just a special case of a note object with an extra tag name header?
- What about notes on notes? How are tags on tags treated? How should they be
  treated?
- Currently noted objects (notees?) are treated as reachable from their
  associated notes, i.e. like tags. This means that an otherwise dangling
  object will not be detected and removed if it has associated notes.
  This may not be what we want. If not, the following needs to be done:
	- Notes should probably have their own object type at this point
	- An object is no longer considered reachable from the associated notes
	  This mostly affects git-fsck and git-prune, which should now be able
	  to remove dangling objects with notes
Finally, here's the shortlog of the patches that will follow this email:
Johan Herland (15):
      git-note: Add git-note command for adding/listing/deleting git notes
      git-note: (Documentation) Add git-note manual page
      git-note: (Administrivia) Add git-note to Makefile, .gitignore, etc.
      git-note: (Plumbing) Add plumbing-level support for git notes
      git-note: (Plumbing) Add support for git notes to git-rev-parse and git-show-ref
      git-note: (Documentation) Explain the new '--notes' option to git-rev-parse and git-show-ref
      git-note: (Almost plumbing) Add support for git notes to git-pack-refs and git-fsck
      git-note: (Decorations) Add note decorations to "git-{log,show,whatchanged} --decorate"
      git-note: (Documentation) Explain new behaviour of --decorate in git-{log,show,whatchanged}
      git-note: (Transfer) Teach git-clone how to clone notes
      git-note: (Transfer) Teach git-fetch to auto-follow notes
      git-note: (Transfer) Teach git-push to push notes when --all or --notes is given
      git-note: (Documentation) Explain the new --notes option to git-push
      git-note: (Tests) Add tests for git-note and associated functionality
      git-note: Add display of notes to gitk
 .gitignore                        |    3 +-
 Documentation/cmd-list.perl       |    5 +-
 Documentation/git-log.txt         |    3 +-
 Documentation/git-note.txt        |   95 ++++++++++++
 Documentation/git-push.txt        |    7 +-
 Documentation/git-rev-parse.txt   |    5 +-
 Documentation/git-show-ref.txt    |   14 +-
 Documentation/git-show.txt        |    4 +
 Documentation/git-whatchanged.txt |    4 +
 Makefile                          |    2 +-
 builtin-fetch--tool.c             |   11 ++
 builtin-fsck.c                    |   61 ++++++++
 builtin-log.c                     |  108 +++++++++++++-
 builtin-pack-refs.c               |    5 +-
 builtin-push.c                    |   20 ++-
 builtin-rev-parse.c               |    5 +
 builtin-show-ref.c                |   16 ++-
 commit.h                          |    7 +
 generate-cmdlist.sh               |    1 +
 git-clone.sh                      |    5 +-
 git-fetch.sh                      |   27 ++++
 git-note.sh                       |  227 +++++++++++++++++++++++++++
 git-parse-remote.sh               |    4 +-
 gitk                              |   38 +++++-
 log-tree.c                        |   67 ++++++++-
 refs.c                            |   20 +++
 refs.h                            |    4 +
 t/t3850-note.sh                   |  303 +++++++++++++++++++++++++++++++++++++
 28 files changed, 1033 insertions(+), 38 deletions(-)
 create mode 100644 Documentation/git-note.txt
 create mode 100755 git-note.sh
 create mode 100755 t/t3850-note.sh
Have fun!
...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Johan Herland· May 27, 2007, 14:09 UTC · re: Johan Herland · lore

[PATCH 01/15] git-note: Add git-note command for adding/listing/deleting git notes

Synopsis: 'git-note' [-m <msg> | -F <file>] [<head>] 'git-note' -l [<name>...] 'git-note' -d <name>...

'git-note' without any parameters creates 'note' associated with HEAD. Specifying an object name/ref to 'git-note' will cause the note to be associated with that object instead.

`git-note -l [<name>...]` lists notes associated with the given <name>s. If no <name> is given, all notes are listed. The given <name> may itself be a note object, in which case it is listed along with its associated notes (if any).

`git-note -d <name>...` deletes the notes associated with the given <name>s. The given <name> may itself be a note object, in which case it is deleted along with its associated notes (if any).

git-note.sh is partly based on git-tag.sh
Signed-off-by: Johan Herland <johan@herland.net>
---
 git-note.sh |  227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 227 insertions(+), 0 deletions(-)
 create mode 100755 git-note.sh
diff --git a/git-note.sh b/git-note.sh
new file mode 100755
index 0000000..4f539e2
--- /dev/null
+++ b/git-note.sh
@@ -0,0 +1,227 @@
+#!/bin/sh
+
+USAGE='-l [<name>...] | -d <name>... | [-m <msg> | -F <file>] [<head>]'
+LONG_USAGE='Add, delete and show git object notes
+	-l	list/show notes associated with the given object.
+	-d	delete the notes associated with the given object.
+	-m	use the given note message (instead of prompting).
+	-F 	Take the note message from the given file (instead of prompting).
+For -l and -d, the given <name> may be a note object itself, in which case
+it will be included in the listing/deletion.
+When <head> is not given, it defaults to HEAD.'
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+
+# Show the note identified by $1
+show_note () {
+	authordate=$(git-cat-file -p $1 | grep -m1 '^tagger ' | cut -c8-)
+	echo
+	echo "--- by $authordate"
+	git-cat-file -p $1 | tail -n+6
+	return 0
+}
+
+# Show notes below the refs/notes directory given in $1
+show_notes_below_dir () {
+	prev_object=
+	for note_data in $(git-for-each-ref --shell \
+			--sort=-taggerdate --sort=*authordate \
+			--format='note=%(objectname);object=%(*objectname);object_type=%(*objecttype);object_tag=%(*tag);' \
+			$1)
+	do
+		eval "$note_data"
+
+		# Separate note objects from regular tags
+		if test "$object_type" = "tag" -a -n "$(echo "$object_tag" | grep '^note-')"; then
+			object_type="note"
+			object_tag=""
+		fi
+
+		description=
+		# Create description based on object type
+		case "$object_type" in
+		commit)
+			description="$(git-show --pretty='format:%h (%s) by %an <%ae> on %aD' $object | head -n1)"
+			;;
+		tag)
+			description="$object ($object_tag)"
+			;;
+		*)
+			description="$object"
+			;;
+		esac
+
+		if test "$object" != "$prev_object"; then
+			test -z "$prev_object" || echo
+			echo "=== Notes on $object_type $description"
+		fi
+		show_note $note
+		prev_object="$object"
+	done
+	return 0
+}
+
+# Show the notes associated with the objects in $@
+show_associated_notes () {
+	if test "$#" = "0"; then
+		show_notes_below_dir "refs/notes"
+		return 0
+	fi
+
+	while case "$#" in 0) break ;; esac
+	do
+		obj=$(git-rev-parse --verify "$1") || \
+				die "error: failed to find object named '$1'"
+
+		# If $obj is a note itself, handle it first
+		if test "$(git-cat-file -t $obj)" = "tag"; then
+			if test -n "$(git-cat-file -p $obj | grep '^tag note-')"; then
+				echo
+				echo "=== Note $obj"
+				show_note "$obj"
+			fi
+		fi
+
+		# List all notes associated with $obj.
+		show_notes_below_dir "refs/notes/$obj"
+
+		shift
+	done
+	return 0
+}
+
+# Delete the noteref in $1 pointing to the note identified by the SHA1 in $2
+delete_note () {
+	git-update-ref -m "note: delete" -d "$1" "$2"
+	return 0
+}
+
+# Delete all notes associated with object in $1
+delete_object_notes () {
+	for note_data in $(git-for-each-ref --shell \
+			--sort=taggerdate --sort=*authordate \
+			--format='note=%(objectname);object=%(*objectname);object_type=%(*objecttype);noteref=%(refname)' \
+			refs/notes/$1)
+	do
+		eval "$note_data"
+		echo "Deleting note $note associated with $object_type $object"
+		delete_note "$noteref" "$note"
+	done
+
+	# Try to get rid of $object's directory below refs/notes as well
+	if test -d "$GIT_DIR/refs/notes/$1"; then
+		rmdir "$GIT_DIR/refs/notes/$1"
+	fi
+	return 0
+}
+
+# Delete the notes associated with the objects in $@
+delete_associated_notes () {
+	while case "$#" in 0) break ;; esac
+	do
+		obj=$(git-rev-parse --verify "$1") || \
+				die "error: failed to find object named '$1'"
+
+		# If $obj is a note itself, handle it first
+		if test "$(git-cat-file -t $obj)" = "tag"; then
+			objref="$(git-cat-file -p $obj | grep -m1 '^object ' | cut -c8-)"
+			if test -n "$objref"; then
+				echo "Deleting note $obj"
+				delete_note "refs/notes/$objref/$obj" "$obj"
+			fi
+		fi
+
+		# Delete all notes associated with $obj
+		delete_object_notes "$obj"
+
+		shift
+	done
+	return 0
+}
+
+message_given=
+message=
+while case "$#" in 0) break ;; esac
+do
+	case "$1" in
+	-l)
+		shift
+		show_associated_notes "$@"
+		exit $?
+		;;
+	-d)
+		shift
+		if test "$#" = "0"; then
+			die "error: option -d needs one or more objects"
+		fi
+		delete_associated_notes "$@"
+		exit $?
+		;;
+	-m)
+		shift
+		message="$1"
+		if test "$#" = "0"; then
+			die "error: option -m needs an argument"
+		else
+			message_given=1
+		fi
+		;;
+	-F)
+		shift
+		if test "$#" = "0"; then
+			die "error: option -F needs an argument"
+		else
+			message="$(cat "$1")"
+			message_given=1
+		fi
+		;;
+	-*)
+		usage
+		;;
+	*)
+		break
+		;;
+	esac
+	shift
+done
+
+object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
+type=$(git-cat-file -t $object) || exit 1
+tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
+
+# Process note message
+trap 'rm -f "$GIT_DIR"/NOTE_TMP* "$GIT_DIR"/NOTE_FINALMSG "$GIT_DIR"/NOTE_EDITMSG' 0
+
+if test -z "$message_given"; then
+	( echo ""
+	  echo "#"
+	  echo "# Write a note on $type $object"
+	  echo "#" ) > "$GIT_DIR/NOTE_EDITMSG"
+	${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/NOTE_EDITMSG" || exit
+else
+	printf '%s\n' "$message" >"$GIT_DIR/NOTE_EDITMSG"
+fi
+
+grep -v '^#' <"$GIT_DIR/NOTE_EDITMSG" |
+git-stripspace >"$GIT_DIR/NOTE_FINALMSG"
+
+test -s "$GIT_DIR/NOTE_FINALMSG" -o -n "$message_given" || {
+	echo >&2 "No note?"
+	exit 1
+}
+
+name="note-`( printf 'object %s\ntagger %s\n\n' "$tagger"; \
+              cat "$GIT_DIR/NOTE_FINALMSG" ) \
+            | sha1sum | cut -d" " -f1`"
+
+git-check-ref-format "notes/$object/$name" ||
+	die "Failed to create a valid note name. (internal git-note error)"
+
+( printf 'object %s\ntype %s\ntag %s\ntagger %s\n\n' \
+		"$object"  "$type"  "$name" "$tagger";
+		cat "$GIT_DIR/NOTE_FINALMSG" ) >"$GIT_DIR/NOTE_TMP"
+rm -f "$GIT_DIR/NOTE_FINALMSG"
+note=$(git-mktag < "$GIT_DIR/NOTE_TMP")
+
+git-update-ref "refs/notes/$object/$note" "$note"
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:10 UTC · re: Johan Herland · lore

[PATCH 02/15] git-note: (Documentation) Add git-note manual page

The manual page describes the operation of the git-note command, as well as some discussion around the design and usage of git notes.

Signed-off-by: Johan Herland <johan@herland.net>
---
 Documentation/git-note.txt |   95 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 95 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-note.txt
diff --git a/Documentation/git-note.txt b/Documentation/git-note.txt
new file mode 100644
index 0000000..077d526
--- /dev/null
+++ b/Documentation/git-note.txt
@@ -0,0 +1,95 @@
+git-note(1)
+===========
+
+NAME
+----
+git-note - Create, list or delete git note objects
+
+
+SYNOPSIS
+--------
+[verse]
+'git-note' [-m <msg> | -F <file>] [<head>]
+'git-note' -l [<name>...]
+'git-note' -d <name>...
+
+
+DESCRIPTION
+-----------
+Adds a 'note' in `.git/refs/notes/` on an existing git object.
+
+A 'note' is an annotation associated with an existing git object.
+The note is similar in form to a commit message.
+
+Unless `-l` or `-d` is passed, the command creates a 'note' object,
+and requires the note message.  Unless `-m <msg>` or `-F <file>` is
+given, an editor is started for the user to type in the note message.
+
+`-l [<name>...]` lists notes associated with the given <name>s. If no
+<name> is given, all notes are listed. The given <name> may itself be
+a note object, in which case it is listed along with its associated
+notes (if any).
+
+`-d <name>...` deletes the notes associated with the given <name>s.
+The given <name> may itself be a note object, in which case it is
+deleted along with its associated notes (if any).
+
+
+OPTIONS
+-------
+-d <name>...::
+	Delete existing notes associated with the given name(s).
+
+-l [<name>...]::
+	List notes associated with the given name(s). List all notes id no
+	name is given.
+
+-m <msg>::
+	Use the given note message (instead of prompting)
+
+-F <file>::
+	Take the note message from the given file (instead of prompting).
+	Use '-' to read the message from the standard input.
+
+
+DISCUSSION
+----------
+A git note provides an after-the-fact text annotation associated with an
+exiting object in the git object database. The note itself is also stored
+in the database (as a special case of a tag object). Note object are therefore
+-- as all other objects in the database -- immutable, i.e. once added they
+cannot be edited, although they can be deleted.
+
+As with regular tag objects, notes are useless unless they have a
+corresponding reference stored in `.git/refs`. For notes, these references
+are stored in a hierarchy under `.git/refs/notes`. For each object that has
+one or more associated notes, there is a subdirectory under
+`.git/refs/notes` named after the object identifier (SHA1 sum). The
+object's subdirectory contains one file per note associated with that object.
+The end result is that notes are easily accessible from the name/identifier
+of the object they are associated with.
+
+Note that deleting a note with `git-note -d` only deletes the reference to
+the note. The note itself is still present in the object database, but will
+be considered unreachable by gitlink:git-fsck[1], and may be removed with
+gitlink:git-prune[1].
+
+
+USE CASE
+--------
+Notes may be used to add any kind of extra information to existing git
+objects that may not become apparent until 'after' the original object is
+created. A common use case is to add -- for example -- information on bugs
+to existing commit objects. E.g. making notes like "This commit closes
+bug #123", or "This commit opens bug #456". These messages may then be
+parsed by custom tools that interoperate with a bug tracker, etc.
+
+
+Author
+------
+Written by Johan Herland <johan@herland.net>.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:11 UTC · re: Johan Herland · lore

[PATCH 03/15] git-note: (Administrivia) Add git-note to Makefile, .gitignore, etc.

Teach Makefile, .gitignore, Documentation/cmd-list.perl and generate-cmdlist.sh on the existence of the git-note command.

The patch also does some unrelated whitespace cleanup in Documentation/cmd-list.perl and an alphabetizing fix in .gitignore, just to keep things neat and tidy.

Signed-off-by: Johan Herland <johan@herland.net>
---
 .gitignore                  |    3 ++-
 Documentation/cmd-list.perl |    5 +++--
 Makefile                    |    2 +-
 generate-cmdlist.sh         |    1 +
 4 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/.gitignore b/.gitignore
index 4dc0c39..0f34b94 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,8 +82,9 @@ git-merge-subtree
 git-mergetool
 git-mktag
 git-mktree
-git-name-rev
 git-mv
+git-name-rev
+git-note
 git-pack-redundant
 git-pack-objects
 git-pack-refs
diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
index 443802a..dc7e8b3 100755
--- a/Documentation/cmd-list.perl
+++ b/Documentation/cmd-list.perl
@@ -103,13 +103,13 @@ git-diff-files                          plumbinginterrogators
 git-diff-index                          plumbinginterrogators
 git-diff                                mainporcelain
 git-diff-tree                           plumbinginterrogators
-git-fast-import				ancillarymanipulators
+git-fast-import                         ancillarymanipulators
 git-fetch                               mainporcelain
 git-fetch-pack                          synchingrepositories
 git-fmt-merge-msg                       purehelpers
 git-for-each-ref                        plumbinginterrogators
 git-format-patch                        mainporcelain
-git-fsck	                        ancillaryinterrogators
+git-fsck                                ancillaryinterrogators
 git-gc                                  mainporcelain
 git-get-tar-commit-id                   ancillaryinterrogators
 git-grep                                mainporcelain
@@ -140,6 +140,7 @@ git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain
 git-name-rev                            plumbinginterrogators
+git-note                                mainporcelain
 git-pack-objects                        plumbingmanipulators
 git-pack-redundant                      plumbinginterrogators
 git-pack-refs                           ancillarymanipulators
diff --git a/Makefile b/Makefile
index 29243c6..c2f9f22 100644
--- a/Makefile
+++ b/Makefile
@@ -209,7 +209,7 @@ SCRIPT_SH = \
 	git-applymbox.sh git-applypatch.sh git-am.sh \
 	git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
 	git-merge-resolve.sh git-merge-ours.sh \
-	git-lost-found.sh git-quiltimport.sh
+	git-lost-found.sh git-quiltimport.sh git-note.sh
 
 SCRIPT_PERL = \
 	git-add--interactive.perl \
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index 975777f..0a37fd0 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -26,6 +26,7 @@ init
 log
 merge
 mv
+note
 prune
 pull
 push
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:11 UTC · re: Johan Herland · lore

[PATCH 04/15] git-note: (Plumbing) Add plumbing-level support for git notes

This patch declares and implements two functions:
1. for_each_note_ref() - for iterating over note references in refs/notes.
   This is analogous to for_each_tag_ref(), for_each_branch_ref() and
   for_each_remote_ref(), which is already present.
2. cleanup_notes_subdirs() - a convenience function for removing empty
   subdirectories from refs/notes. These subdirectories would otherwise be
   left behind after removing notes, or after packing note refs.
The above functions will be used by subsequent git-note patches.
Signed-off-by: Johan Herland <johan@herland.net>
---
 refs.c |   20 ++++++++++++++++++++
 refs.h |    4 ++++
 2 files changed, 24 insertions(+), 0 deletions(-)
diff --git a/refs.c b/refs.c
index 2ae3235..f0abfcd 100644
--- a/refs.c
+++ b/refs.c
@@ -569,6 +569,11 @@ int for_each_tag_ref(each_ref_fn fn, void *cb_data)
 	return do_for_each_ref("refs/tags/", fn, 10, cb_data);
 }
 
+int for_each_note_ref(each_ref_fn fn, void *cb_data)
+{
+	return do_for_each_ref("refs/notes/", fn, 11, cb_data);
+}
+
 int for_each_branch_ref(each_ref_fn fn, void *cb_data)
 {
 	return do_for_each_ref("refs/heads/", fn, 11, cb_data);
@@ -1433,3 +1438,18 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)
 {
 	return do_for_each_reflog("", fn, cb_data);
 }
+
+void cleanup_notes_subdirs ()
+{
+	DIR *dir = opendir(git_path("refs/notes/"));
+
+	if (dir) {
+		struct dirent *de;
+		while ((de = readdir(dir)) != NULL) {
+			if (de->d_name[0] == '.')
+				continue;
+			rmdir(git_path("refs/notes/%s", de->d_name));
+		}
+		closedir(dir);
+	}
+}
diff --git a/refs.h b/refs.h
index f234eb7..6175bfb 100644
--- a/refs.h
+++ b/refs.h
@@ -21,6 +21,7 @@ typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flag
 extern int head_ref(each_ref_fn, void *);
 extern int for_each_ref(each_ref_fn, void *);
 extern int for_each_tag_ref(each_ref_fn, void *);
+extern int for_each_note_ref(each_ref_fn, void *);
 extern int for_each_branch_ref(each_ref_fn, void *);
 extern int for_each_remote_ref(each_ref_fn, void *);
 
@@ -64,4 +65,7 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg
 /** resolve ref in nested "gitlink" repository */
 extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result);
 
+/** clean up empty subdirs below "refs/notes/" */
+extern void cleanup_notes_subdirs();
+
 #endif /* REFS_H */
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:12 UTC · re: Johan Herland · lore

[PATCH 05/15] git-note: (Plumbing) Add support for git notes to git-rev-parse and git-show-ref

Teach git-rev-parse and git-show-ref to show notes by adding a '--notes' option. The '--notes' option is analogous to the '--tags' option for the respective commands.

Signed-off-by: Johan Herland <johan@herland.net>
---
 builtin-rev-parse.c |    5 +++++
 builtin-show-ref.c  |   16 +++++++++++-----
 2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 37addb2..4ab88af 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -43,6 +43,7 @@ static int is_rev_argument(const char *arg)
 		"--max-count=",
 		"--min-age=",
 		"--no-merges",
+		"--notes",
 		"--objects",
 		"--objects-edge",
 		"--parents",
@@ -310,6 +311,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 				for_each_tag_ref(show_reference, NULL);
 				continue;
 			}
+			if (!strcmp(arg, "--notes")) {
+				for_each_note_ref(show_reference, NULL);
+				continue;
+			}
 			if (!strcmp(arg, "--remotes")) {
 				for_each_remote_ref(show_reference, NULL);
 				continue;
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
index 9463ff0..8a7d0e7 100644
--- a/builtin-show-ref.c
+++ b/builtin-show-ref.c
@@ -4,10 +4,11 @@
 #include "tag.h"
 #include "path-list.h"
 
-static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*] < ref-list";
+static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--notes] [--] [pattern*] < ref-list";
 
 static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0,
-	found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0;
+	notes_only = 0, found_match = 0, verify = 0, quiet = 0, hash_only = 0,
+	abbrev = 0;
 static const char **pattern;
 
 static void show_one(const char *refname, const unsigned char *sha1)
@@ -25,11 +26,12 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
 	const char *hex;
 	unsigned char peeled[20];
 
-	if (tags_only || heads_only) {
+	if (tags_only || heads_only || notes_only) {
 		int match;
 
-		match = heads_only && !prefixcmp(refname, "refs/heads/");
-		match |= tags_only && !prefixcmp(refname, "refs/tags/");
+		match  = heads_only && !prefixcmp(refname, "refs/heads/");
+		match |= tags_only  && !prefixcmp(refname, "refs/tags/");
+		match |= notes_only && !prefixcmp(refname, "refs/notes/");
 		if (!match)
 			return 0;
 	}
@@ -213,6 +215,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 			heads_only = 1;
 			continue;
 		}
+		if (!strcmp(arg, "--notes")) {
+			notes_only = 1;
+			continue;
+		}
 		if (!strcmp(arg, "--exclude-existing"))
 			return exclude_existing(NULL);
 		if (!prefixcmp(arg, "--exclude-existing="))
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:13 UTC · re: Johan Herland · lore

[PATCH 06/15] git-note: (Documentation) Explain the new '--notes' option to git-rev-parse and git-show-ref

Signed-off-by: Johan Herland <johan@herland.net>
---
 Documentation/git-rev-parse.txt |    5 ++++-
 Documentation/git-show-ref.txt  |   14 +++++++-------
 2 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 7757abe..7d36011 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -73,8 +73,11 @@ OPTIONS
 --tags::
 	Show tag refs found in `$GIT_DIR/refs/tags`.
 
+--notes::
+	Show note refs found in `$GIT_DIR/refs/notes`.
+
 --remotes::
-	Show tag refs found in `$GIT_DIR/refs/remotes`.
+	Show refs found in `$GIT_DIR/refs/remotes`.
 
 --show-prefix::
 	When the command is invoked from a subdirectory, show the
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 2355aa5..7e204f0 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference]
-	     [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>...
+	     [-s|--hash] [--abbrev] [--tags] [--heads] [--notes] [--] <pattern>...
 'git-show-ref' --exclude-existing[=pattern]
 
 DESCRIPTION
@@ -33,16 +33,16 @@ OPTIONS
 
 	Show the HEAD reference.
 
---tags, --heads::
+--heads, --tags, --notes::
 
-	Limit to only "refs/heads" and "refs/tags", respectively.  These
-	options are not mutually exclusive; when given both, references stored
-	in "refs/heads" and "refs/tags" are displayed.
+	Limit to only "refs/heads", "refs/tags" and "refs/notes", respectively.
+	These options are not mutually exclusive; when given more than one,
+	references classified as any of them are displayed.
 
 -d, --dereference::
 
-	Dereference tags into object IDs as well. They will be shown with "^{}"
-	appended.
+	Dereference tags and notes into object IDs as well. They will be shown
+	with "^{}" appended.
 
 -s, --hash::
 
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:13 UTC · re: Johan Herland · lore

[PATCH 07/15] git-note: (Almost plumbing) Add support for git notes to git-pack-refs and git-fsck

Teach git-pack-refs to pack note refs in the same way that regular tag refs are packed. Also, make sure to clean up empty subdirs in refs/notes after packing note refs.

Teach git-fsck some extra checking of note refs.
Signed-off-by: Johan Herland <johan@herland.net>
---
 builtin-fsck.c      |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin-pack-refs.c |    5 +++-
 2 files changed, 65 insertions(+), 1 deletions(-)
diff --git a/builtin-fsck.c b/builtin-fsck.c
index cbbcaf0..edbb976 100644
--- a/builtin-fsck.c
+++ b/builtin-fsck.c
@@ -522,9 +522,70 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
 	return 0;
 }
 
+static int fsck_handle_note_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+	/*
+	 * - Verify that refname has format $object/$note
+	 * - Verify that contents of $object/$note == $note
+	 * - Verify that $object/$note references a tag object
+	 * - Verify that the tag object points to $object
+	 * - Verify that $object exists. (already done by fsck_tag())
+	 */
+	 /* Even on error, we still return 0 to keep for_each_ref() going. */
+
+	const char *object_name = refname;
+	const char *note_name = strchr(object_name, '/');
+	size_t object_name_len, note_name_len;
+	unsigned char object_sha1[20], note_sha1[20];
+	struct object *note_obj;
+	struct tag *note;
+
+	if (!note_name) {
+		error("%s: invalid note refname; missing note part", refname);
+		return 0;
+	}
+	object_name_len = note_name - object_name;
+	note_name_len = strlen(++note_name);
+	if (object_name_len != 40 || get_sha1_hex(object_name, object_sha1)) {
+		error("%s: invalid note refname; object part not valid SHA1 sum", refname);
+		return 0;
+	}
+	if (note_name_len != 40 || get_sha1_hex(note_name, note_sha1)) {
+		error("%s: invalid note refname; note part not valid SHA1 sum", refname);
+		return 0;
+	}
+	if (hashcmp(note_sha1, sha1)) {
+		error("%s: invalid note ref; note part not identical to note's SHA1 sum (%s)",
+			refname, sha1_to_hex(sha1));
+		return 0;
+	}
+	note_obj = lookup_object(sha1);
+	if (!note_obj) { /* Couldn't find note object... */
+		if (!has_sha1_file(sha1)) /* ...and it's not hidden in a pack */
+			error("%s: invalid note ref; must point at a valid object",
+				refname);
+		return 0; /* Return even if in a pack. */
+	}
+	if (note_obj->type != OBJ_TAG) {
+		error("%s: invalid note ref; must point at tag object (type == %s)",
+			refname, typename(note_obj->type));
+		return 0;
+	}
+	note = (struct tag *) parse_object(sha1);
+	if (hashcmp(note->tagged->sha1, object_sha1)) {
+		error("%s: invalid note ref; "
+			"object part not identical to tagged object (%s)",
+			refname, sha1_to_hex(note->tagged->sha1));
+		return 0;
+	}
+
+	return 0;
+}
+
 static void get_default_heads(void)
 {
 	for_each_ref(fsck_handle_ref, NULL);
+	for_each_note_ref(fsck_handle_note_ref, NULL);
 	if (include_reflogs)
 		for_each_reflog(fsck_handle_reflog, NULL);
 
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
index 1952950..57b9fee 100644
--- a/builtin-pack-refs.c
+++ b/builtin-pack-refs.c
@@ -39,6 +39,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
 	if ((flags & REF_ISSYMREF))
 		return 0;
 	is_tag_ref = !prefixcmp(path, "refs/tags/");
+	is_tag_ref |= !prefixcmp(path, "refs/notes/");
 
 	/* ALWAYS pack refs that were already packed or are tags */
 	if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref && !(flags & REF_ISPACKED))
@@ -109,8 +110,10 @@ static int pack_refs(unsigned int flags)
 		die("failed to write ref-pack file (%s)", strerror(errno));
 	if (commit_lock_file(&packed) < 0)
 		die("unable to overwrite old ref-pack file (%s)", strerror(errno));
-	if (cbdata.flags & PACK_REFS_PRUNE)
+	if (cbdata.flags & PACK_REFS_PRUNE) {
 		prune_refs(cbdata.ref_to_prune);
+		cleanup_notes_subdirs();
+	}
 	return 0;
 }
 
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:14 UTC · re: Johan Herland · lore

[PATCH 08/15] git-note: (Decorations) Add note decorations to "git-{log,show,whatchanged} --decorate"

Introduce the concept of note decorations (supplementing the existing name decorations), and teach the code handling the --decorate option how to display note decorations.

The result is that notes are displayed below the commit message for
these commands:
- git-log --decorate
- git-show --decorate
- git-whatchanged --decorate
Signed-off-by: Johan Herland <johan@herland.net>
---
 builtin-log.c |  108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 commit.h      |    7 ++++
 log-tree.c    |   67 +++++++++++++++++++++++++++++++++--
 3 files changed, 178 insertions(+), 4 deletions(-)
diff --git a/builtin-log.c b/builtin-log.c
index 3744712..ee10f2a 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -20,6 +20,108 @@ static int default_show_root = 1;
 /* this is in builtin-diff.c */
 void add_head(struct rev_info *revs);
 
+static int get_note_decoration_data(const char *data, unsigned long data_len, const char **author, unsigned long *author_len, const char **date, unsigned long *date_len, const char **msg, unsigned long *msg_len)
+{
+	/*
+	 * Set author, date and msg pointers to their appropriate positions
+	 * within the given data buffer (tag object data). Also set the
+	 * appropriate lengths, respectively.
+	 */
+	const char *p, *q, *end = data + data_len;
+	if (!data || !data_len)
+		return 1;
+	p = data;
+	end = data + data_len;
+	do {
+		if (end - p >= 7 && !memcmp("tagger ", p, 7)) {
+			*author = p + 7;
+			if ((q = memchr(*author, '>', end - *author))) {
+				*author_len = ++q - *author;
+				*date = ++q;
+				q = memchr(*date, '\n', end - *date);
+				*date_len = q ? q - *date : 0;
+			}
+			else if ((q = memchr(*author, '\n', end - *author)))
+				*author_len = q - *author;
+			else
+				*author_len = 0;
+		}
+		else if (end - p >= 1 && !memcmp("\n", p, 1)) {
+			*msg = ++p;
+			*msg_len = end - *msg;
+			break;
+		}
+		p = memchr(p, '\n', end - p);
+	} while (p++);
+	return 0;
+}
+
+static int add_note_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+	struct object *obj;
+	struct tag *note;
+	struct note_decoration *res;
+	enum object_type data_type;
+	char *data;
+	const char   *author = "",   *date = "",   *msg = "";
+	unsigned long author_len = 0, date_len = 0, msg_len = 0;
+	unsigned long note_len, data_len;
+	unsigned long time = 0;
+
+	/* Get object pointed to by note */
+	obj = parse_object(sha1);
+	if (!obj)
+		return 0;
+	if (obj->type != OBJ_TAG)
+		return 0;
+
+	note = (struct tag *)obj;
+	obj = note->tagged;
+	if (!obj)
+		return 0;
+
+	/* Generate note decoration from note data */
+	data = read_sha1_file(note->object.sha1, &data_type, &data_len);
+	if (!data || !data_len)
+		return 0;
+	if (data_type != OBJ_TAG) {
+		free(data);
+		return 0; /* should not happen */
+	}
+
+	if (get_note_decoration_data(data,  data_len,
+			&author, &author_len, &date, &date_len, &msg, &msg_len))
+	{
+		free(data);
+		return 0; /* should not happen */
+	}
+	if (date && *date && date_len) { /* Found date; prettify it */
+		char *tz_p, *end;
+		int tz;
+		time = strtoul(date, &tz_p, 10);
+		tz = strtol(tz_p, &end, 10);
+		if (end - date > date_len) {
+			free(data);
+			return 0; /* overflowed; should not happen */
+		}
+		date = show_date(time, tz, DATE_NORMAL);
+		date_len = strlen(date);
+	}
+	note_len = 10 + author_len + date_len + msg_len;
+	res = xmalloc(sizeof(struct note_decoration) + note_len);
+	snprintf(res->note, note_len + 1, "\n--- by %.*s %.*s\n%.*s",
+			(int) author_len, author,
+			(int)   date_len,   date,
+			(int)    msg_len,    msg);
+	res->sort_hint = time;
+
+	/* Add note decoration to object that note points to */
+	res->next = add_decoration(&note_decoration, obj, res);
+
+	free(data);
+	return 0;
+}
+
 static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
 {
 	int plen = strlen(prefix);
@@ -35,6 +137,8 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
 	struct object *obj = parse_object(sha1);
 	if (!obj)
 		return 0;
+	if (!prefixcmp(refname, "refs/notes/"))
+		return 0; /* notes shouldn't show up here */
 	add_name_decoration("", refname, obj);
 	while (obj->type == OBJ_TAG) {
 		obj = ((struct tag *)obj)->tagged;
@@ -67,8 +171,10 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 			else
 				git_log_output_encoding = "";
 		} else if (!strcmp(arg, "--decorate")) {
-			if (!decorate)
+			if (!decorate) {
 				for_each_ref(add_ref_decoration, NULL);
+				for_each_note_ref(add_note_decoration, NULL);
+			}
 			decorate = 1;
 		} else
 			die("unrecognized argument: %s", arg);
diff --git a/commit.h b/commit.h
index 86e8dca..93cac79 100644
--- a/commit.h
+++ b/commit.h
@@ -28,6 +28,13 @@ struct name_decoration {
 	struct name_decoration *next;
 	char name[1];
 };
+/* While we can decorate any object with a note, it's only used for commits.. */
+extern struct decoration note_decoration;
+struct note_decoration {
+	struct note_decoration *next;
+	unsigned long sort_hint;
+	char note[1];
+};
 
 struct commit *lookup_commit(const unsigned char *sha1);
 struct commit *lookup_commit_reference(const unsigned char *sha1);
diff --git a/log-tree.c b/log-tree.c
index 4bef909..dccaca2 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -5,6 +5,7 @@
 #include "reflog-walk.h"
 
 struct decoration name_decoration = { "object names" };
+struct decoration note_decoration = { "object notes" };
 
 static void show_parents(struct commit *commit, int abbrev)
 {
@@ -15,7 +16,7 @@ static void show_parents(struct commit *commit, int abbrev)
 	}
 }
 
-static void show_decorations(struct commit *commit)
+static void show_name_decorations(struct commit *commit)
 {
 	const char *prefix;
 	struct name_decoration *decoration;
@@ -32,6 +33,65 @@ static void show_decorations(struct commit *commit)
 	putchar(')');
 }
 
+static int compare_note_decorations(const void *a, const void *b)
+{
+	const struct note_decoration *na = *((const struct note_decoration **) a);
+	const struct note_decoration *nb = *((const struct note_decoration **) b);
+	if (na->sort_hint < nb->sort_hint)
+		return -1;
+	else if (na->sort_hint == nb->sort_hint)
+		return 0;
+	return 1;
+}
+
+static void show_note_decorations(struct commit *commit)
+{
+	struct note_decoration *decoration;
+	struct note_decoration **decoArray;
+	unsigned int numDecorations = 0, i;
+
+	decoration = lookup_decoration(&note_decoration, &commit->object);
+	if (!decoration)
+		return;
+
+	/* Sort notes */
+	while (decoration) {
+		++numDecorations;
+		decoration = decoration->next;
+	}
+	decoration = lookup_decoration(&note_decoration, &commit->object);
+	decoArray = xmalloc(sizeof(struct note_decoration *) * numDecorations);
+	i = 0;
+	while (decoration) {
+		decoArray[i++] = decoration;
+		decoration = decoration->next;
+	}
+	qsort(decoArray, numDecorations, sizeof(struct note_decoration *), compare_note_decorations);
+
+	/* Print notes */
+	puts("\nNotes:");
+	for (i = 0; i < numDecorations; ++i) {
+		const char *p, *q;
+		decoration = decoArray[i];
+		p = decoration->note;
+		while (1) {
+			/* print everything indented by 4 spaces */
+			q = strchr(p, '\n');
+			if (!q)
+				q = p + strlen(p);
+			if (q - p)
+				printf("    %.*s\n", q - p, p);
+			else
+				printf("\n");
+			if (!*q || !*(q + 1))
+				break;
+			p = q + 1;
+		}
+	}
+
+	free(decoArray);
+}
+
 /*
  * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
  * Signed-off-by: and Acked-by: lines.
@@ -155,7 +215,7 @@ void show_log(struct rev_info *opt, const char *sep)
 		fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
 		if (opt->parents)
 			show_parents(commit, abbrev_commit);
-		show_decorations(commit);
+		show_name_decorations(commit);
 		putchar(opt->diffopt.line_termination);
 		return;
 	}
@@ -260,7 +320,7 @@ void show_log(struct rev_info *opt, const char *sep)
 			printf(" (from %s)",
 			       diff_unique_abbrev(parent->object.sha1,
 						  abbrev_commit));
-		show_decorations(commit);
+		show_name_decorations(commit);
 		printf("%s",
 		       diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
 		putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
@@ -286,6 +346,7 @@ void show_log(struct rev_info *opt, const char *sep)
 		len = append_signoff(this_header, sizeof(this_header), len,
 				     opt->add_signoff);
 	printf("%s%s%s", this_header, extra, sep);
+	show_note_decorations(commit);
 }
 
 int log_tree_diff_flush(struct rev_info *opt)
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:14 UTC · re: Johan Herland · lore

[PATCH 09/15] git-note: (Documentation) Explain new behaviour of --decorate in git-{log,show,whatchanged}

The addition of git notes to --decorate output is noted on the git-log manual page. In addition, the --decorate documentation is copied to the git-show and git-whatchanged manual pages, where any mention of --decorate have been missing until now.

Signed-off-by: Johan Herland <johan@herland.net>
---
 Documentation/git-log.txt         |    3 ++-
 Documentation/git-show.txt        |    4 ++++
 Documentation/git-whatchanged.txt |    4 ++++
 3 files changed, 10 insertions(+), 1 deletions(-)
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 0f353f6..1168372 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -52,7 +52,8 @@ include::pretty-options.txt[]
 	See also gitlink:git-reflog[1].
 
 --decorate::
-    Print out the ref names of any commits that are shown.
+	Print out the ref names and notes associated with any commits that are
+	shown.
 
 <paths>...::
 	Show only commits that affect the specified paths.
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
index 34c5caf..a31ba6b 100644
--- a/Documentation/git-show.txt
+++ b/Documentation/git-show.txt
@@ -38,6 +38,10 @@ OPTIONS
 	For a more complete list of ways to spell object names, see
 	"SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
 
+--decorate::
+	Print out the ref names and notes associated with any commits that are
+	shown.
+
 include::pretty-options.txt[]
 
 
diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt
index 399bff3..3f77b3d 100644
--- a/Documentation/git-whatchanged.txt
+++ b/Documentation/git-whatchanged.txt
@@ -43,6 +43,10 @@ OPTIONS
 	<format> can be one of 'raw', 'medium', 'short', 'full',
 	and 'oneline'.
 
+--decorate::
+	Print out the ref names and notes associated with any commits that are
+	shown.
+
 -m::
 	By default, differences for merge commits are not shown.
 	With this flag, show differences to that commit from all
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:15 UTC · re: Johan Herland · lore

[PATCH 10/15] git-note: (Transfer) Teach git-clone how to clone notes

This patch causes git notes to be cloned in the same way that tags are cloned.

Signed-off-by: Johan Herland <johan@herland.net>
---
 git-clone.sh |    5 ++++-
 1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/git-clone.sh b/git-clone.sh
index fdd354f..6bb2fd5 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -57,7 +57,7 @@ Perhaps git-update-server-info needs to be run there?"
 		*^*)	continue;;
 		esac
 		case "$bare,$name" in
-		yes,* | ,heads/* | ,tags/*) ;;
+		yes,* | ,heads/* | ,tags/* | ,notes/*) ;;
 		*)	continue ;;
 		esac
 		if test -n "$use_separate_remote" &&
@@ -320,6 +320,7 @@ then
 		branch_top="heads"
 	fi
 	tag_top="tags"
+	note_top="notes"
 	while read sha1 name
 	do
 		case "$name" in
@@ -331,6 +332,8 @@ then
 			destname="refs/$branch_top/${name#refs/heads/}" ;;
 		refs/tags/*)
 			destname="refs/$tag_top/${name#refs/tags/}" ;;
+		refs/notes/*)
+			destname="refs/$note_top/${name#refs/notes/}" ;;
 		*)
 			continue ;;
 		esac
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:15 UTC · re: Johan Herland · lore

[PATCH 11/15] git-note: (Transfer) Teach git-fetch to auto-follow notes

This causes git-fetch to auto-follow notes in the same fashion that tags are auto-followed by default.

Pretty much all the code in this patch is about doing the same thing for notes as what is already done for tags.

Signed-off-by: Johan Herland <johan@herland.net>
---
 builtin-fetch--tool.c |   11 +++++++++++
 git-fetch.sh          |   27 +++++++++++++++++++++++++++
 git-parse-remote.sh   |    4 ++--
 3 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c
index ed4d5de..71ddc6d 100644
--- a/builtin-fetch--tool.c
+++ b/builtin-fetch--tool.c
@@ -82,6 +82,8 @@ static int update_local_ref(const char *name,
 		/* new ref */
 		if (!strncmp(name, "refs/tags/", 10))
 			msg = "storing tag";
+		else if (!strncmp(name, "refs/notes/", 11))
+			msg = "storing note";
 		else
 			msg = "storing head";
 		fprintf(stderr, "* %s: storing %s\n",
@@ -103,6 +105,11 @@ static int update_local_ref(const char *name,
 		show_new(type, sha1_new);
 		return update_ref("updating tag", name, sha1_new, NULL);
 	}
+	else if (!strncmp(name, "refs/notes/", 11)) {
+		fprintf(stderr, "* %s: updating with %s\n", name, note);
+		show_new(type, sha1_new);
+		return update_ref("updating note", name, sha1_new, NULL);
+	}
 
 	current = lookup_commit_reference(sha1_old);
 	updated = lookup_commit_reference(sha1_new);
@@ -163,6 +170,10 @@ static int append_fetch_head(FILE *fp,
 		kind = "tag";
 		what = remote_name + 10;
 	}
+	else if (!strncmp(remote_name, "refs/notes/", 11)) {
+		kind = "note";
+		what = remote_name + 11;
+	}
 	else if (!strncmp(remote_name, "refs/remotes/", 13)) {
 		kind = "remote branch";
 		what = remote_name + 13;
diff --git a/git-fetch.sh b/git-fetch.sh
index 0e05cf1..a2a7874 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -353,6 +353,33 @@ case "$no_tags$tags" in
 	esac
 esac
 
+# automated note following
+case "" in # --notes and --no-notes options?
+'')
+	case "$reflist" in
+	*:refs/*)
+		# effective only when we are following remote branch
+		# using local tracking branch.
+		notelist=$(IFS='	' &&
+		echo "$ls_remote_result" |
+		git-show-ref --exclude-existing=refs/notes/ |
+		while read sha1 name
+		do
+			git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
+			echo >&2 "Auto-following $name"
+			echo ".${name}:${name}"
+		done)
+	esac
+	case "$notelist" in
+	'') ;;
+	?*)
+		# do not deepen a shallow tree when following notes
+		shallow_depth=
+		fetch_main "$notelist" || exit ;;
+	esac
+esac
+
+
 # If the original head was empty (i.e. no "master" yet), or
 # if we were told not to worry, we do not have to check.
 case "$orig_head" in
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index 0506b12..95dcc10 100755
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -144,13 +144,13 @@ canon_refs_list_for_fetch () {
 		case "$remote" in
 		'' | HEAD ) remote=HEAD ;;
 		refs/*) ;;
-		heads/* | tags/* | remotes/* ) remote="refs/$remote" ;;
+		heads/* | tags/* | notes/* | remotes/* ) remote="refs/$remote" ;;
 		*) remote="refs/heads/$remote" ;;
 		esac
 		case "$local" in
 		'') local= ;;
 		refs/*) ;;
-		heads/* | tags/* | remotes/* ) local="refs/$local" ;;
+		heads/* | tags/* | notes/* | remotes/* ) local="refs/$local" ;;
 		*) local="refs/heads/$local" ;;
 		esac
 
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:15 UTC · re: Johan Herland · lore

[PATCH 12/15] git-note: (Transfer) Teach git-push to push notes when --all or --notes is given

As with tags, git-push will not push notes by default. However, if --all or --notes (new) is given, git-push will now push notes (similary to tags, if --all or --tags is given).

Signed-off-by: Johan Herland <johan@herland.net>
---
 builtin-push.c |   20 ++++++++++++--------
 1 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/builtin-push.c b/builtin-push.c
index cb78401..e3d6a21 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -8,9 +8,9 @@
 
 #define MAX_URI (16)
 
-static const char push_usage[] = "git-push [--all] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
+static const char push_usage[] = "git-push [--all] [--tags] [--notes] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
 
-static int all, tags, force, thin = 1, verbose;
+static int all, tags, notes, force, thin = 1, verbose;
 static const char *receivepack;
 
 #define BUF_SIZE (2084)
@@ -32,7 +32,8 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag,
 	/* Ignore the "refs/" at the beginning of the refname */
 	ref += 5;
 
-	if (!prefixcmp(ref, "tags/"))
+	if ((tags  && !prefixcmp(ref, "tags/")) ||
+	    (notes && !prefixcmp(ref, "notes/")))
 		add_refspec(xstrdup(ref));
 	return 0;
 }
@@ -49,9 +50,8 @@ static void expand_refspecs(void)
 		 */
 		return;
 	}
-	if (!tags)
-		return;
-	for_each_ref(expand_one_ref, NULL);
+	if (tags || notes)
+		for_each_ref(expand_one_ref, NULL);
 }
 
 struct wildcard_cb {
@@ -141,7 +141,7 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
 {
 	int n = 0;
 	FILE *f = fopen(git_path("remotes/%s", repo), "r");
-	int has_explicit_refspec = refspec_nr || all || tags;
+	int has_explicit_refspec = refspec_nr || all || tags || notes;
 
 	if (!f)
 		return -1;
@@ -227,7 +227,7 @@ static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI])
 	config_repo = repo;
 	config_current_uri = 0;
 	config_uri = uri;
-	config_get_refspecs = !(refspec_nr || all || tags);
+	config_get_refspecs = !(refspec_nr || all || tags || notes);
 	config_get_receivepack = (receivepack == NULL);
 
 	git_config(get_remote_config);
@@ -388,6 +388,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 			tags = 1;
 			continue;
 		}
+		if (!strcmp(arg, "--notes")) {
+			notes = 1;
+			continue;
+		}
 		if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) {
 			force = 1;
 			continue;
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:16 UTC · re: Johan Herland · lore

[PATCH 13/15] git-note: (Documentation) Explain the new --notes option to git-push

Signed-off-by: Johan Herland <johan@herland.net>
---
 Documentation/git-push.txt |    7 ++++++-
 1 files changed, 6 insertions(+), 1 deletions(-)
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index e9ad106..2e98844 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
 SYNOPSIS
 --------
 [verse]
-'git-push' [--all] [--tags] [--receive-pack=<git-receive-pack>]
+'git-push' [--all] [--tags] [--notes] [--receive-pack=<git-receive-pack>]
            [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]
 
 DESCRIPTION
@@ -69,6 +69,11 @@ the remote repository.
 	addition to refspecs explicitly listed on the command
 	line.
 
+\--notes::
+	All refs under `$GIT_DIR/refs/notes` are pushed, in
+	addition to refspecs explicitly listed on the command
+	line.
+
 \--receive-pack=<git-receive-pack>::
 	Path to the 'git-receive-pack' program on the remote
 	end.  Sometimes useful when pushing to a remote
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:16 UTC · re: Johan Herland · lore

[PATCH 14/15] git-note: (Tests) Add tests for git-note and associated functionality

This test script add tests for:
- git-note (including -l and -d options)
- State of note/tag objects
- git-show-ref --notes
- git-for-each-ref refs/notes
- git-{log,show,whatchanged} --decorate
Signed-off-by: Johan Herland <johan@herland.net>
---
 t/t3850-note.sh |  303 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 303 insertions(+), 0 deletions(-)
 create mode 100755 t/t3850-note.sh
diff --git a/t/t3850-note.sh b/t/t3850-note.sh
new file mode 100755
index 0000000..f737f7c
--- /dev/null
+++ b/t/t3850-note.sh
@@ -0,0 +1,303 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johan Herland
+#
+
+test_description='Test of git-note'
+
+. ./test-lib.sh
+
+
+# Prepare repo and create some notes
+
+test_expect_success 'Preparing for test' '
+	echo "foo" > foo &&
+	git-add foo &&
+	test_tick &&
+	git-commit -m "Initial commit" &&
+	echo "bar" >> foo
+	test_tick &&
+	git-commit -m "Second commit" foo
+'
+test_expect_success 'Testing git-note -m' '
+	test_tick &&
+	git-note -m "spam"
+'
+test_expect_success 'Testing git-note -F' '
+	echo "spam spam" > note.msg &&
+	test_tick &&
+	git-note -F note.msg
+'
+test_expect_success 'Testing git-note <commit>' '
+	test_tick &&
+	git-note -m "spam spam spam" 301711b66fe71164f646b798706a2c1f7024da8d
+'
+
+# At this point we should have:
+# - commit @ 301711b66fe71164f646b798706a2c1f7024da8d ("Initial commit")
+#   - note @ 93bbbc95a42852552494327537263d475aac1789 ("spam spam spam")
+# - commit @ 9671cbee7ad26528645b2665c8f74d39a6288864 ("Second commit")
+#   - note @ 1245491fece5910086bc1870a4a746256f8a7ecf ("spam")
+#   - note @ 466f861dde008d3905d320225eebef727665a3f0 ("spam spam")
+
+# Verify note objects
+
+test_expect_success 'Testing git-cat-file tag <note>' '
+	cat > note.expect << EOF &&
+object 9671cbee7ad26528645b2665c8f74d39a6288864
+type commit
+tag note-14669883eb004f12636307269f537d59eb143125
+tagger C O Mitter <committer@example.com> 1112912113 -0700
+
+spam
+EOF
+	git-cat-file tag 1245491fece5910086bc1870a4a746256f8a7ecf > note.output &&
+	cmp note.expect note.output &&
+	cat > note.expect << EOF &&
+object 9671cbee7ad26528645b2665c8f74d39a6288864
+type commit
+tag note-ad2099b641be660c4e616bf0b46dcaca391764af
+tagger C O Mitter <committer@example.com> 1112912173 -0700
+
+spam spam
+EOF
+	git-cat-file tag 466f861dde008d3905d320225eebef727665a3f0 > note.output &&
+	cmp note.expect note.output &&
+	cat > note.expect << EOF &&
+object 301711b66fe71164f646b798706a2c1f7024da8d
+type commit
+tag note-c1afd1d9a4e2df7efbb29ff88e50e866e88eb108
+tagger C O Mitter <committer@example.com> 1112912233 -0700
+
+spam spam spam
+EOF
+	git-cat-file tag 93bbbc95a42852552494327537263d475aac1789 > note.output &&
+	cmp note.expect note.output
+'
+
+# Verify output from git-note -l
+
+test_expect_success 'Testing git-note -l' '
+	cat > note.expect << EOF &&
+=== Notes on commit 9671cbe (Second commit) by A U Thor <author@example.com> on Thu, 7 Apr 2005 15:14:13 -0700
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:15:13 2005 -0700
+spam
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:16:13 2005 -0700
+spam spam
+
+=== Notes on commit 301711b (Initial commit) by A U Thor <author@example.com> on Thu, 7 Apr 2005 15:13:13 -0700
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:17:13 2005 -0700
+spam spam spam
+EOF
+	git-note -l > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Testing git-note -l <commit>' '
+	cat > note.expect << EOF &&
+=== Notes on commit 9671cbe (Second commit) by A U Thor <author@example.com> on Thu, 7 Apr 2005 15:14:13 -0700
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:15:13 2005 -0700
+spam
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:16:13 2005 -0700
+spam spam
+EOF
+	git-note -l 9671cbee7ad26528645b2665c8f74d39a6288864 > note.output &&
+	cmp note.expect note.output &&
+	cat > note.expect << EOF &&
+=== Notes on commit 301711b (Initial commit) by A U Thor <author@example.com> on Thu, 7 Apr 2005 15:13:13 -0700
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:17:13 2005 -0700
+spam spam spam
+EOF
+	git-note -l 301711b66fe71164f646b798706a2c1f7024da8d > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Testing git-note -l <note> <note> <note>' '
+	cat > note.expect << EOF &&
+
+=== Note 93bbbc95a42852552494327537263d475aac1789
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:17:13 2005 -0700
+spam spam spam
+
+=== Note 466f861dde008d3905d320225eebef727665a3f0
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:16:13 2005 -0700
+spam spam
+
+=== Note 1245491fece5910086bc1870a4a746256f8a7ecf
+
+--- by C O Mitter <committer@example.com> Thu Apr 7 15:15:13 2005 -0700
+spam
+EOF
+	git-note -l 93bbbc95a42852552494327537263d475aac1789 \
+	            466f861dde008d3905d320225eebef727665a3f0 \
+	            1245491fece5910086bc1870a4a746256f8a7ecf > note.output &&
+	cmp note.expect note.output
+'
+
+# Verify that notes show up in git-show-ref and git-for-each-ref
+
+test_expect_success 'Testing git-show-ref with notes' '
+	cat > note.expect << EOF &&
+9671cbee7ad26528645b2665c8f74d39a6288864 refs/heads/master
+93bbbc95a42852552494327537263d475aac1789 refs/notes/301711b66fe71164f646b798706a2c1f7024da8d/93bbbc95a42852552494327537263d475aac1789
+1245491fece5910086bc1870a4a746256f8a7ecf refs/notes/9671cbee7ad26528645b2665c8f74d39a6288864/1245491fece5910086bc1870a4a746256f8a7ecf
+466f861dde008d3905d320225eebef727665a3f0 refs/notes/9671cbee7ad26528645b2665c8f74d39a6288864/466f861dde008d3905d320225eebef727665a3f0
+EOF
+	git-show-ref > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Testing git-show-ref --notes' '
+	cat > note.expect << EOF &&
+93bbbc95a42852552494327537263d475aac1789 refs/notes/301711b66fe71164f646b798706a2c1f7024da8d/93bbbc95a42852552494327537263d475aac1789
+1245491fece5910086bc1870a4a746256f8a7ecf refs/notes/9671cbee7ad26528645b2665c8f74d39a6288864/1245491fece5910086bc1870a4a746256f8a7ecf
+466f861dde008d3905d320225eebef727665a3f0 refs/notes/9671cbee7ad26528645b2665c8f74d39a6288864/466f861dde008d3905d320225eebef727665a3f0
+EOF
+	git-show-ref --notes > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Testing git-for-each-ref refs/notes' '
+	cat > note.expect << EOF &&
+93bbbc95a42852552494327537263d475aac1789 tag	refs/notes/301711b66fe71164f646b798706a2c1f7024da8d/93bbbc95a42852552494327537263d475aac1789
+1245491fece5910086bc1870a4a746256f8a7ecf tag	refs/notes/9671cbee7ad26528645b2665c8f74d39a6288864/1245491fece5910086bc1870a4a746256f8a7ecf
+466f861dde008d3905d320225eebef727665a3f0 tag	refs/notes/9671cbee7ad26528645b2665c8f74d39a6288864/466f861dde008d3905d320225eebef727665a3f0
+EOF
+	git-for-each-ref refs/notes > note.output &&
+	cmp note.expect note.output
+'
+
+# Verify output from git-log/git-show/git-whatchanged --decorate
+
+test_expect_success 'Testing git-log --decorate' '
+	cat > note.expect << EOF &&
+commit 9671cbee7ad26528645b2665c8f74d39a6288864 (refs/heads/master)
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:14:13 2005 -0700
+
+    Second commit
+
+Notes:
+
+    --- by C O Mitter <committer@example.com> Thu Apr 7 15:15:13 2005 -0700
+    spam
+
+    --- by C O Mitter <committer@example.com> Thu Apr 7 15:16:13 2005 -0700
+    spam spam
+
+commit 301711b66fe71164f646b798706a2c1f7024da8d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    Initial commit
+
+Notes:
+
+    --- by C O Mitter <committer@example.com> Thu Apr 7 15:17:13 2005 -0700
+    spam spam spam
+EOF
+	git-log --decorate > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Testing git-show --decorate' '
+	cat > note.expect << EOF &&
+commit 9671cbee7ad26528645b2665c8f74d39a6288864 (refs/heads/master)
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:14:13 2005 -0700
+
+    Second commit
+
+Notes:
+
+    --- by C O Mitter <committer@example.com> Thu Apr 7 15:15:13 2005 -0700
+    spam
+
+    --- by C O Mitter <committer@example.com> Thu Apr 7 15:16:13 2005 -0700
+    spam spam
+
+diff --git a/foo b/foo
+index 257cc56..3bd1f0e 100644
+--- a/foo
++++ b/foo
+@@ -1 +1,2 @@
+ foo
++bar
+EOF
+	git-show --decorate > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Testing git-whatchanged --decorate' '
+	cat > note.expect << EOF &&
+commit 9671cbee7ad26528645b2665c8f74d39a6288864 (refs/heads/master)
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:14:13 2005 -0700
+
+    Second commit
+
+Notes:
+
+    --- by C O Mitter <committer@example.com> Thu Apr 7 15:15:13 2005 -0700
+    spam
+
+    --- by C O Mitter <committer@example.com> Thu Apr 7 15:16:13 2005 -0700
+    spam spam
+
+:100644 100644 257cc56... 3bd1f0e... M	foo
+
+commit 301711b66fe71164f646b798706a2c1f7024da8d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    Initial commit
+
+Notes:
+
+    --- by C O Mitter <committer@example.com> Thu Apr 7 15:17:13 2005 -0700
+    spam spam spam
+
+:000000 100644 0000000... 257cc56... A	foo
+EOF
+	git-whatchanged --decorate > note.output &&
+	cmp note.expect note.output
+'
+
+# Verify successful removal of notes
+
+test_expect_success 'Testing git-note -d <note>' '
+	cat > note.expect << EOF &&
+Deleting note 93bbbc95a42852552494327537263d475aac1789
+EOF
+	git-note -d 93bbbc95a42852552494327537263d475aac1789 > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Verify repo state after git-note -d <note>' '
+	cat > note.expect << EOF &&
+1245491fece5910086bc1870a4a746256f8a7ecf refs/notes/9671cbee7ad26528645b2665c8f74d39a6288864/1245491fece5910086bc1870a4a746256f8a7ecf
+466f861dde008d3905d320225eebef727665a3f0 refs/notes/9671cbee7ad26528645b2665c8f74d39a6288864/466f861dde008d3905d320225eebef727665a3f0
+EOF
+	git-show-ref --notes > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Testing git-note -d <commit>' '
+	cat > note.expect << EOF &&
+Deleting note 1245491fece5910086bc1870a4a746256f8a7ecf associated with commit 9671cbee7ad26528645b2665c8f74d39a6288864
+Deleting note 466f861dde008d3905d320225eebef727665a3f0 associated with commit 9671cbee7ad26528645b2665c8f74d39a6288864
+EOF
+	git-note -d 9671cbee7ad26528645b2665c8f74d39a6288864 > note.output &&
+	cmp note.expect note.output
+'
+test_expect_success 'Verify repo state after git-note -d <commit>' '
+	cat > note.expect << EOF &&
+EOF
+	git-show-ref --notes > note.output
+	test "$?" == "1" &&
+	cmp note.expect note.output
+'
+
+# Testing of clone/fetch/push?
+
+test_done
-- 
1.5.2.101.gee49f
Johan Herland· May 27, 2007, 14:17 UTC · re: Johan Herland · lore

[PATCH 15/15] git-note: Add display of notes to gitk

Teach gitk to display notes below the commit message similarly to how notes are displayed when using --decorate with git-{log,show,whatchanged}.

Signed-off-by: Johan Herland <johan@herland.net>
---
 gitk |   38 +++++++++++++++++++++++++++++++++++---
 1 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/gitk b/gitk
index a57e84c..7bed56b 100755
--- a/gitk
+++ b/gitk
@@ -303,7 +303,7 @@ proc getcommit {id} {
 }
 
 proc readrefs {} {
-    global tagids idtags headids idheads tagcontents
+    global tagids idtags idnotes headids idheads tagcontents
     global otherrefids idotherrefs mainhead
 
     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
@@ -318,7 +318,7 @@ proc readrefs {} {
 	if {[regexp {^remotes/.*/HEAD$} $path match]} {
 	    continue
 	}
-	if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
+	if {![regexp {^(tags|notes|heads)/(.*)$} $path match type name]} {
 	    set type others
 	    set name $path
 	}
@@ -341,6 +341,18 @@ proc readrefs {} {
 	    catch {
 	        set tagcontents($name) [exec git cat-file tag $id]
 	    }
+	} elseif { $type == "notes" } {
+	    puts stderr "note name = '$name', id = '$id'"
+	    lappend idnotes($id) $name
+	    set obj {}
+	    set type {}
+	    set tag {}
+	    catch {
+		set commit [exec git rev-parse "$id^0"]
+		if {$commit != $id} {
+		    lappend idnotes($commit) $id
+		}
+	    }
 	} elseif { $type == "heads" } {
 	    set headids($name) $id
 	    lappend idheads($id) $name
@@ -3864,7 +3876,7 @@ proc selectline {l isnew} {
     global displayorder linehtag linentag linedtag
     global canvy0 linespc parentlist childlist
     global currentid sha1entry
-    global commentend idtags linknum
+    global commentend idtags idnotes linknum
     global mergemax numcommits pending_select
     global cmitmode desc_tags anc_tags showneartags allcommits desc_heads
 
@@ -4009,6 +4021,26 @@ proc selectline {l isnew} {
     $ctext insert end "\n"
     appendwithlinks [lindex $info 5] {comment}
 
+    if {[info exists idnotes($id)]} {
+	$ctext insert end "\nNotes:\n"
+	set notes [exec git note -l "$id"]
+	# strip first line.
+	set firstLineEnd [string first "\n" $notes]
+	if {$firstLineEnd < 0} {
+	    # should never happen...
+	    set hdrend 0
+	}
+	set newnotes [string range $notes [expr {$firstLineEnd + 1}] end]
+	# Indent everything by 4 spaces
+	set notes {}
+	foreach line [split $newnotes "\n"] {
+	    append notes "    $line\n"
+	}
+	# Append to $ctext with auto-highlighting of SHA1 IDs.
+	appendwithlinks $notes {comment}
+	$ctext insert end "\n"
+    }
+
     $ctext tag delete Comments
     $ctext tag remove found 1.0 end
     $ctext conf -state disabled
-- 
1.5.2.101.gee49f
Junio C Hamano· May 27, 2007, 20:09 UTC · re: Johan Herland · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Johan Herland <johan@herland.net> writes:
> I've been working on combining tag objects and --decorate into a useful
> proof-of-concept that provides the after-the-fact commit annotations I
> requested above, and here's the result:
> ...

Very nicely presented. I appreciate [PATCH 0/N] summary for a series like this that talks about not just what and how it does, but focus more on why it does it in a particular way, on design issues, and decisions. It makes it very pleasant to comment on the series to have a well written summary like this, because in the early stage of a review cycle, we would want to talk about the design, ignoring implementation details.

Show 5 quoted lines
> However, there are still some remaining questions:
>
> - Is the creation of a unique tag name (i.e. the 'tag' field _inside_ the
>   object) for note objects really necessary? Which parts of the system rely
>   on these names to be unique across tag objects?

None. Although your use of note-X{40} would make "recovering" easier, I would imagine.

> - Should notes have their own object type instead of piggy-backing on "tag"?
>
> - What about having a note object type with minimal header fields, and make
>   tags just a special case of a note object with an extra tag name header?
Two things struck me:
 - The "reverse" mapping 'note' tag uses uses FS as a database.
   Clever but would not scale well.
 - *BUT* the reverse mapping 'note' tag uses is exactly what we
    would want to use for tags that are not notes, for tools
    like gitk to attach little flags to commits.

So maybe we _should_ not even need to call this a new 'notes' system. I am just saying your second point above in a different way, but I suspect all what is needed is to make the filtering and presentation of tag objects a bit more flexible, and making use of the 'peeled' (see .git/packed-refs, for example) mapping ultra cheap for ordinary tags.

What you would need are:
 - Ultra-cheap reverse lookup: "I have an object; which tags
   point at this?"
   You solved it with refs/notes/<sha1-of-tagged>/, but it would
   be useful to extend this to any tag in general.
 - Easy way to filter the above question: "I have an object;
   which tags of this particlar class point at this?"
   You are essentially prefiltering by only making notes tags
   available via refs/notes/ hierarchy, but you can also filter
   with your naming convention "tag notes-<sha1>".  We _could_
   (I am not seriously suggesting this yet as I haven't thought
   through the issues yet) allow "keyword" header to tag
   objects, and allow --decorate='kwd1,kwd2,...' to limit the
   tags we take object decorations from the ones that has one of
   the specified keywords.  If we do that, I suspect your
   'notes' system would naturally fall out as regular tags that
   happen to have a keyword header with 'note' on it.
> - What about notes on notes? How are tags on tags treated? How should they be
>   treated?

You can create a tag object that points at another tag. When we peel a tag using "^{}" notation, currently it is peeled all the way until we hit a non tag, so if you need to look at intermediate tags, you need to parse them yourself. IOW, there is no Porcelain feature to let you take advantage of this capability, easily. But we could use this to say "I attest that he tagged that object".

> - Currently noted objects (notees?) are treated as reachable from their
>   associated notes, i.e. like tags. This means that an otherwise dangling
>   object will not be detected and removed if it has associated notes.

I think it largely depends on how you intend to use 'notes' if this is a problem.

The reachability issue is not limited to cruft removal; it also affects the fetching.

I would imagine the intended use of a 'note' is to annotate objects (mostly commits, but not necessarily) after the fact, e.g. "this is reported to fix the issue 35833". You may or may not want to propagate this across repositories, and the way you implemented it is to have a ref pointing at the note -- then it would make it fetchable by other people. Otherwise it would not. With your implementation, unfortunately, not having a ref under refs/notes/ would also make the information unavailable to yourself.

I think the semantics you would want, when a 'note' object that points at another object (pointee) exists, is to allow creating a fake (reverse) reachability that says "pointee points at the note". When such a reachability exists:

 - A fetch from such a repository that transfers out the pointee
   will drag the note attached to it along with it (so you would
   teach rev-list/upload-pack about the extended reachability).
 - Incidentally, having a ref that points at the pointee is
   enough to protect the note that points at the pointee from
   getting pruned (so you would teach fsck/prune about the
   extended reachability).

Othewise, the 'note' is subject to garbage collection. This is pretty much an extension to the existing 'grafts' mechanism, which can only override commit and its parents; what is done in the above is to add (not override) to existing reachability to any object (not just commit, but anything that can get 'noted').

Johan Herland· May 28, 2007, 00:29 UTC · re: Junio C Hamano · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Sunday 27 May 2007, Junio C Hamano wrote:
Show 14 quoted lines
> Johan Herland <johan@herland.net> writes:
> 
> > I've been working on combining tag objects and --decorate into a useful
> > proof-of-concept that provides the after-the-fact commit annotations I
> > requested above, and here's the result:
> > ...
> 
> Very nicely presented. I appreciate [PATCH 0/N] summary for a
> series like this that talks about not just what and how it does,
> but focus more on why it does it in a particular way, on design
> issues, and decisions.  It makes it very pleasant to comment on
> the series to have a well written summary like this, because in
> the early stage of a review cycle, we would want to talk about
> the design, ignoring implementation details.

Thanks, no problem. You're doing a really good job keeping all this crazy development on track. The very least I can do is make your job easier. :)

Show 8 quoted lines
> > However, there are still some remaining questions:
> >
> > - Is the creation of a unique tag name (i.e. the 'tag' field _inside_ the
> >   object) for note objects really necessary? Which parts of the system rely
> >   on these names to be unique across tag objects?
> 
> None.  Although your use of note-X{40} would make "recovering"
> easier, I would imagine.

Yeah, I'd rather just use _one_ identifier for all notes, instead of generating yet another SHA1 sum, just to make each note (unecessarily) unique.

Show 9 quoted lines
> > - Should notes have their own object type instead of piggy-backing on "tag"?
> >
> > - What about having a note object type with minimal header fields, and make
> >   tags just a special case of a note object with an extra tag name header?
> 
> Two things struck me:
> 
>  - The "reverse" mapping 'note' tag uses uses FS as a database.
>    Clever but would not scale well.
Agreed.
>  - *BUT* the reverse mapping 'note' tag uses is exactly what we
>     would want to use for tags that are not notes, for tools
>     like gitk to attach little flags to commits.

For notes, it's obvious, as they aren't very useful unless we have a fast reverse mapping, but it makes sense that other types of tag objects would want this as well.

Show 6 quoted lines
> So maybe we _should_ not even need to call this a new 'notes'
> system.  I am just saying your second point above in a different
> way, but I suspect all what is needed is to make the filtering
> and presentation of tag objects a bit more flexible, and making
> use of the 'peeled' (see .git/packed-refs, for example) mapping
> ultra cheap for ordinary tags.

Agreed. The peeled mapping makes a lot of sense here. I didn't know about it when I first designed this, and I somehow managed to miss it when I made the patches to pack-refs and show-refs.

Actually, I thought about something similar to the peeled mapping when I designed the note refs. I basically had 3 reverse mapping options lined up:

1. Put _all_ note refs in a single two-column file, noted objects
   ("notees") in the first column, and note objects in the second.
   Keep the file sorted by object name (first column), and keep the
   notes per object sorted chronologically. This would provide one of
   the fastest possible lookups for "git-note -l". However, the file
   would probably grow extremely large, slowing down insertion (and
   lookup) considerably.
2. One file per object with notes. Notes sorted chronologically within
   that file. This is sort of an intermediate between (1) and (3).
3. Keep a two-level hierarchy with one subdir per noted object, and one
   file per note. This is what I ended up with, mostly because it fit
   perfectly into the existing layout of refs/, and required very little
   code to implement.

Of course, now that I know about the peeled mapping, it makes more sense to use a variation on that (maybe together with (1) or (2)).

Show 21 quoted lines
> What you would need are:
> 
>  - Ultra-cheap reverse lookup: "I have an object; which tags
>    point at this?"
> 
>    You solved it with refs/notes/<sha1-of-tagged>/, but it would
>    be useful to extend this to any tag in general.
> 
>  - Easy way to filter the above question: "I have an object;
>    which tags of this particlar class point at this?"
> 
>    You are essentially prefiltering by only making notes tags
>    available via refs/notes/ hierarchy, but you can also filter
>    with your naming convention "tag notes-<sha1>".  We _could_
>    (I am not seriously suggesting this yet as I haven't thought
>    through the issues yet) allow "keyword" header to tag
>    objects, and allow --decorate='kwd1,kwd2,...' to limit the
>    tags we take object decorations from the ones that has one of
>    the specified keywords.  If we do that, I suspect your
>    'notes' system would naturally fall out as regular tags that
>    happen to have a keyword header with 'note' on it.

Yeah, I was thinking somewhat similarly; basically making tag objects simpler and more versatile by replacing the "tag" header (which is fundamentally unimportant to my notes) with a "tagtype" header which could be set to "tag" for tag objects, "note" for note objects, etc. Each tagtype could then further define headers specific to that tagtype. For example could "tagtype == tag" objects reintroduce the "tag" header.

The tagtype field could then serve pretty much the same purpose as the keyword header you suggest above.

Show 10 quoted lines
> > - What about notes on notes? How are tags on tags treated? How should they be
> >   treated?
> 
> You can create a tag object that points at another tag.  When we
> peel a tag using "^{}" notation, currently it is peeled all the
> way until we hit a non tag, so if you need to look at
> intermediate tags, you need to parse them yourself.  IOW, there
> is no Porcelain feature to let you take advantage of this
> capability, easily.  But we could use this to say "I attest that
> he tagged that object".

Yeah, I'm still undecided on whether we want to allow notes on notes. Some might argue that a note on a note should really be just another note on the original noted object, and that nested notes doesn't make sense. But then, on the other hand, there might be a use for nested notes. Say, what about allowing threaded discussion on commits represented as nested note objects? ;-P

In my current implementation, "git-note <tag>" will create a note on the tag object, and not on the commit pointed to by the tag object. I'm not sure that's the default behaviour I want; at least I would probably want the note to show up when looking at the commit pointed to by that tag

Show 13 quoted lines
> > - Currently noted objects (notees?) are treated as reachable from their
> >   associated notes, i.e. like tags. This means that an otherwise dangling
> >   object will not be detected and removed if it has associated notes.
> 
> I think it largely depends on how you intend to use 'notes' if
> this is a problem.
> 
> The reachability issue is not limited to cruft removal; it also
> affects the fetching.
> 
> I would imagine the intended use of a 'note' is to annotate
> objects (mostly commits, but not necessarily) after the fact,
> e.g. "this is reported to fix the issue 35833".

Yep, sounds about right, although other people might have other, just as valid, ideas.

Show 23 quoted lines
> You may or may 
> not want to propagate this across repositories, and the way you
> implemented it is to have a ref pointing at the note -- then it
> would make it fetchable by other people.  Otherwise it would not.
> With your implementation, unfortunately, not having a ref under
> refs/notes/ would also make the information unavailable to
> yourself.
> 
> I think the semantics you would want, when a 'note' object that
> points at another object (pointee) exists, is to allow creating
> a fake (reverse) reachability that says "pointee points at the
> note".  When such a reachability exists:
> 
>  - A fetch from such a repository that transfers out the pointee
>    will drag the note attached to it along with it (so you would
>    teach rev-list/upload-pack about the extended reachability).
> 
>  - Incidentally, having a ref that points at the pointee is
>    enough to protect the note that points at the pointee from
>    getting pruned (so you would teach fsck/prune about the
>    extended reachability).
> 
> Othewise, the 'note' is subject to garbage collection.

Yeah, the current implementation does not allow for this reverse/weaker relationship at all.

I agree that fetchability and prunability of a note depends on the fetch-/prunability of its pointee. Furthermore, a note on an otherwise dangling object should not keep that object alive, but rather allow that object to be pruned (automatically causing the note to be pruned as well).

Show 5 quoted lines
> This is 
> pretty much an extension to the existing 'grafts' mechanism,
> which can only override commit and its parents; what is done in
> the above is to add (not override) to existing reachability to
> any object (not just commit, but anything that can get 'noted').

I'll have to read up on the 'grafts' mechanism, but it already sounds like a much better way to encode these relationships than my crude attempts in 'refs/notes'.

Thanks a lot for the feedback.
Have fun!
...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Jakub Narebski· May 28, 2007, 00:59 UTC · re: Junio C Hamano · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Junio C Hamano wrote:
Show 7 quoted lines
> You can create a tag object that points at another tag.  When we
> peel a tag using "^{}" notation, currently it is peeled all the
> way until we hit a non tag, so if you need to look at
> intermediate tags, you need to parse them yourself.  IOW, there
> is no Porcelain feature to let you take advantage of this
> capability, easily.  But we could use this to say "I attest that
> he tagged that object".

Hmmm... I wonder why "git show exaples/tag" (where 'examples/tag' is tag to tag) does show empty output, and why "git show examples/tag^{tag}" does not work...

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Linus Torvalds· May 28, 2007, 04:37 UTC · re: Johan Herland · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Sun, 27 May 2007, Johan Herland wrote:
> 
> I've been working on combining tag objects and --decorate into a useful
> proof-of-concept that provides the after-the-fact commit annotations I
> requested above, and here's the result:
Ok, looks fine to me. I do have a few questions:
 - why don't you just let people name their notes, the same way we name 
   tags (and then actually using it as the note name?)
   Putting them in the refs/notes/ filesystem by their SHA1 seems a bit 
   wasteful, and it would seem that it could be quite nice to name the 
   notes some way?
 - This will probably scale horribly badly if you have tens of thousands 
   of notes, even when they are packed. Do we care?
Other than that, it looks straightforward and sane.
		Linus
Johan Herland· May 28, 2007, 10:54 UTC · re: Linus Torvalds · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Monday 28 May 2007, Linus Torvalds wrote:
Show 14 quoted lines
> 
> On Sun, 27 May 2007, Johan Herland wrote:
> > 
> > I've been working on combining tag objects and --decorate into a useful
> > proof-of-concept that provides the after-the-fact commit annotations I
> > requested above, and here's the result:
> 
> Ok, looks fine to me. I do have a few questions:
>  - why don't you just let people name their notes, the same way we name 
>    tags (and then actually using it as the note name?)
> 
>    Putting them in the refs/notes/ filesystem by their SHA1 seems a bit 
>    wasteful, and it would seem that it could be quite nice to name the 
>    notes some way?

Well, when I first designed them, I thought of notes more as an extension to the commit message than as a special case of tags. Therefore naming them didn't seem natural at that point. But now that it all seems to blend together (cf. Junio's answer), I'm not opposed to putting names on notes at all. But I don't want to make naming mandatory. It should still be possible to add a note like "Fixes bug #12345" in a jiffy, without having to think up a note name as well (or having a badly thought-up name pollute some namespace).

So I guess we end up with two kinds of objects/entities:
1. tags, with mandatory naming and optional comment/message
2. notes, with optional naming and mandatory comment/message

To some degree we can say that the name is the ref, and the comment/message is the object. We therefore get:

1. tags, with mandatory ref and optional tag object (we have this today)
2. notes, with optional ref and mandatory object

Today, a note needs its ref in order to stay alive, but this is purely a (badly designed) technical measure. When we get the proper reverse mapping done (as discussed in Junio's answer), notes will stop polluting refs/ and we can instead allow naming of notes by adding simple (tag-type) refs to note objects.

Also, as mentioned by Junio, we want to do the reverse mapping for regular tags as well.

At this point, there's no real difference between tags and notes (some would say there never were a difference), and we can stop caring about whether a note is a note or a tag, etc, at least in the plumbing.

We may still want to enforce a difference in the procelain, though. When presenting tags/notes (e.g. with --decorate), users might be interested in classifying and filtering their tags/notes in different categories. Categories might be ("note", "tag"), or we can make it user-extensible, like Junio's "keyword" idea). Either way, we'll have to make room for this classification in the tag object by adding another header ("tagtype", "keyword", whatever...).

And while we're on the subject of changing the tag object, I'd like for the "tag" header (the one holding the tag name) to become optional. When doing my ref <-> name trick above, I conveniently forgot this little bugger. Basically the only reason for this one to exist is to include the name of the tag in the data passed to gpg for signing. This is of course necesssary in order to make renaming a signed tag impossible. (Allowing renaming would make it possible to replace it with a malicious tag with the original name.) Therefore the "tag" header must be mandatory for signed tags. But for all other tags (including notes) this header is pretty much useless.

>  - This will probably scale horribly badly if you have tens of thousands 
>    of notes, even when they are packed. Do we care?

I can't see why the current implementation would scale any worse than an equivalent number of (annotated/signed) tags. But then again, the tag system might not have been designed with tens of thousands of tag objects in mind. :)

> Other than that, it looks straightforward and sane.
Thanks for the feedback
Have fun!
...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Linus Torvalds· May 28, 2007, 16:28 UTC · re: Johan Herland · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Mon, 28 May 2007, Johan Herland wrote:
Show 8 quoted lines
> 
> >  - This will probably scale horribly badly if you have tens of thousands 
> >    of notes, even when they are packed. Do we care?
> 
> I can't see why the current implementation would scale any worse than an
> equivalent number of (annotated/signed) tags. But then again, the tag
> system might not have been designed with tens of thousands of tag objects
> in mind. :)

Right. I was more thinking that this "notes" thing could potentially be a very useful thing for some random workflow - using notes to indicate that some commit has been vetted by somebody, for example (ie adding things like "Acked-by:" after-the-fact, which happens for the kernel).

And once you start using notes for something like that, I think you're going to end up with a set of notes that grows with history, and potentially grows quite quickly.

So I can see people having thousands of tags, but usually you only tag releases. In contrast, I can see notes being used not as a "per release" thing, but closer to a "per commit" thing. And that kind of worries me, I can see workflows where you end up having tons and tons of notes.

But hey, maybe I just worry unnecessarily.
		Linus
Johan Herland· May 28, 2007, 16:40 UTC · re: Linus Torvalds · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Monday 28 May 2007, Linus Torvalds wrote:
Show 20 quoted lines
> > I can't see why the current implementation would scale any worse than an
> > equivalent number of (annotated/signed) tags. But then again, the tag
> > system might not have been designed with tens of thousands of tag objects
> > in mind. :)
> 
> Right. I was more thinking that this "notes" thing could potentially be a 
> very useful thing for some random workflow - using notes to indicate that 
> some commit has been vetted by somebody, for example (ie adding things 
> like "Acked-by:" after-the-fact, which happens for the kernel).
> 
> And once you start using notes for something like that, I think you're 
> going to end up with a set of notes that grows with history, and 
> potentially grows quite quickly.
> 
> So I can see people having thousands of tags, but usually you only tag 
> releases. In contrast, I can see notes being used not as a "per release" 
> thing, but closer to a "per commit" thing. And that kind of worries me, I 
> can see workflows where you end up having tons and tons of notes.
> 
> But hey, maybe I just worry unnecessarily.

I still don't see what makes note objects inherently more expensive than commit objects. Except for the refs, of course, but we're getting rid of those (at least replacing them with a more efficient reverse mapping).

And even if we _do_ end up with 10 notes per commit, we could always design some kind of "supernote" that lets "git-gc" pack all the notes related to a commit into _one_ object.

Have fun!
...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Linus Torvalds· May 28, 2007, 16:58 UTC · re: Johan Herland · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Mon, 28 May 2007, Johan Herland wrote:
> 
> I still don't see what makes note objects inherently more expensive than
> commit objects. Except for the refs, of course, but we're getting rid
> of those (at least replacing them with a more efficient reverse mapping).
It's exactly the refs that I worry about.
Anything that needs to read in all notes at startup is going to be _slow_.
In contrast, commits we read when (and only when) we need them.
		Linus
Johan Herland· May 28, 2007, 17:48 UTC · re: Linus Torvalds · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Monday 28 May 2007, Linus Torvalds wrote:
Show 12 quoted lines
> 
> On Mon, 28 May 2007, Johan Herland wrote:
> > 
> > I still don't see what makes note objects inherently more expensive than
> > commit objects. Except for the refs, of course, but we're getting rid
> > of those (at least replacing them with a more efficient reverse mapping).
> 
> It's exactly the refs that I worry about.
> 
> Anything that needs to read in all notes at startup is going to be _slow_.
> 
> In contrast, commits we read when (and only when) we need them.

Ok. But the reverse mapping will help with this, won't it? We'll look up the interesting commits and find their associated note objects directly.

BTW, if you use "git-note -l <commit>" it'll go directly to <commit>'s subdir in refs/notes and never look at any other notes.

Of course, this currently does not help the plumbing much, which uses for_each_note_ref() instead...

...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Junio C Hamano· May 28, 2007, 20:45 UTC · re: Johan Herland · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Johan Herland <johan@herland.net> writes:
Show 17 quoted lines
> On Monday 28 May 2007, Linus Torvalds wrote:
>> 
>> On Mon, 28 May 2007, Johan Herland wrote:
>> > 
>> > I still don't see what makes note objects inherently more expensive than
>> > commit objects. Except for the refs, of course, but we're getting rid
>> > of those (at least replacing them with a more efficient reverse mapping).
>> 
>> It's exactly the refs that I worry about.
>> 
>> Anything that needs to read in all notes at startup is going to be _slow_.
>> 
>> In contrast, commits we read when (and only when) we need them.
>
> Ok. But the reverse mapping will help with this, won't it?
> We'll look up the interesting commits and find their associated
> note objects directly.
The issue Linus brought up worries me, too.

The "efficient reverse mapping" is still handwaving at this stage. What it needs to do is an equivalent to your implementation with "refs/notes/<a dir per commit>/<note>". The "efficient" one might do a flat file that says "notee note" per line sorted by notee, or it might use BDB or sqlite, but the amount of the data and complexity of the look-up is really the same. A handful notes per each commit in the history (I think Linus's "Acked-by after the fact" example a very sensible thing to want from this subsystem).

I am not saying that it is impossible to make the set-up cost for the "efficient lookup" almost zero, and to make it lazy and on-demand. The concern above just adds one design constraints to that "efficient reverse mapping" code yet to come.

Shawn O. Pearce· May 28, 2007, 21:35 UTC · re: Junio C Hamano · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Junio C Hamano <junkio@cox.net> wrote:
Show 16 quoted lines
> Johan Herland <johan@herland.net> writes:
> > Ok. But the reverse mapping will help with this, won't it?
> > We'll look up the interesting commits and find their associated
> > note objects directly.
> 
> The issue Linus brought up worries me, too.
> 
> The "efficient reverse mapping" is still handwaving at this
> stage.  What it needs to do is an equivalent to your
> implementation with "refs/notes/<a dir per commit>/<note>".  The
> "efficient" one might do a flat file that says "notee note" per
> line sorted by notee, or it might use BDB or sqlite, but the
> amount of the data and complexity of the look-up is really the
> same.  A handful notes per each commit in the history (I think
> Linus's "Acked-by after the fact" example a very sensible thing
> to want from this subsystem).

Please, don't use BDB or sqllite. I really don't trust either. I've lost data to both. I've *never* lost data to a Git packfile. ;-)

I'm actually thinking pack v4. OK, I know its just a virtual hand waving thing still, but there's really no reason Nico and I cannot get the damn thing finished before we both wind up buying the farm.

What if we use a "slow" storage by "refs/notes/$objname/$notename", and we also allow them to appear in the packed-refs file. But during a repack we instead stick the annotations into the same packfile as $objname, and we also include a list of $notename after $objname's other data.

This way we have quick access to the $notename(s) of all notes of $objname through the pack, and we can lazily go get the notes raw data if we need them. This isn't too different from what we do with parent fields. We initialize the commit_list when we parse the commit but we don't parse the parents until we really need them.

Once packed we delete the note ref (if loose) and during a repack of the packed-refs file we delete the note if $notename exists in the packfile.

If someone wants notes we can check to see if refs/notes exists; if it does then we enumerate all refs and catalog the notes we found in memory. Note search then works off the in-memory list and off the packfiles. If refs/notes doesn't exist (and we should delete it when we prune away those ref files or prune them out of packed-refs) then we can skip the ref enumeration and just go straight to the packfile(s). Most notes will be in the packfiles. I think most people repack often enough that the handful of unpacked notes before the next repack won't be a major bottleneck. Especially since we can get the target $objname directly from a readdir() call, or by splitting the string in the packed-refs file.

>From an object enumeration standpoint during packfile generation

the notes for a given object are treated like the parent fields in a commit; they come after the object itself, but unlike the parent fields they are always output if the object itself was output. (Hence an --objects-edge enumeration would include the notes only if the commit itself had been included.)

Unfortunately that doesn't cover the case of a note being added months later and needing to distribute it to clients that already have the object the note is attached to.

I haven't been following this discussion very closely, but I'd also like to suggest that if annotated tags are being used for notes that the "tag <name>" field be left out of them. I don't see why a note should be given a specific name that sits in a (roughly) global namespace.

-- 
Shawn.
Johannes Schindelin· May 28, 2007, 23:37 UTC · re: Shawn O. Pearce · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Hi,
On Mon, 28 May 2007, Shawn O. Pearce wrote:
Show 21 quoted lines
> Junio C Hamano <junkio@cox.net> wrote:
> > Johan Herland <johan@herland.net> writes:
> > > Ok. But the reverse mapping will help with this, won't it?
> > > We'll look up the interesting commits and find their associated
> > > note objects directly.
> > 
> > The issue Linus brought up worries me, too.
> > 
> > The "efficient reverse mapping" is still handwaving at this
> > stage.  What it needs to do is an equivalent to your
> > implementation with "refs/notes/<a dir per commit>/<note>".  The
> > "efficient" one might do a flat file that says "notee note" per
> > line sorted by notee, or it might use BDB or sqlite, but the
> > amount of the data and complexity of the look-up is really the
> > same.  A handful notes per each commit in the history (I think
> > Linus's "Acked-by after the fact" example a very sensible thing
> > to want from this subsystem).
> 
> Please, don't use BDB or sqllite.  I really don't trust either.
> I've lost data to both.  I've *never* lost data to a Git packfile.
> ;-)

Also, there is the question of dependencies. We don't have to repeat SVN's errors.

As for sqllite: the only reason I was _not_ horrified when I realized that cvsserver needs sqllite was that it is not really important for the common user.

> I'm actually thinking pack v4.  OK, I know its just a virtual hand
> waving thing still, but there's really no reason Nico and I cannot
> get the damn thing finished before we both wind up buying the farm.

Maybe I am missing something important here, but I think we do not at all need a generic database here. Just a key/value store.

As it happens, we already have such a beast. It is called object store here.

Now, the only reason we cannot do something like "SHA-1 ^ 0xff[...]ff" is the SHA-1 for the note for that commit, is that you could possibly have more than one note for the commit.

Of course, without that restriction, we could reuse our object store logic for notes.

Side note: the more I think about this notes thing, the more I get disgusted by the deep changes we'd have to do just to accomodate for them. Are they really worth it? Or would something like a pseudo-branch suffice, being one strand of notes (commits), where the second commit parent is actually the commit the note is for?

Ciao, Dscho

Linus Torvalds· May 29, 2007, 03:12 UTC · re: Shawn O. Pearce · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Mon, 28 May 2007, Shawn O. Pearce wrote:
> 
> What if we use a "slow" storage by "refs/notes/$objname/$notename",

This _really_ won't scale. Even if the notes don't exist, just doing the lookup (which will fail for most commits) will be horribly slow, and will populate the dentry cache with negative entries.

To get good filesystem performance, you have to
 - have reasonable hit-rates (and looking it up for each commit is _not_ 
   going to do that)
 - not have millions of objects.

So you'd have to have a separate database. You could do it with a separate index file (or mixing it up with the "index v4" and doing it with a single index file that also contains normal objects), but the point is, it's going to be a real separate database.

The current "decorations" approach is actually much more efficient with packed refs (dense in the filesystem), and probably not very bad at all for a smallish set (ie a few hundred to a few thousand) objects, but just reading all the decorations ends up being an overhead at some point.

		Linus
Shawn O. Pearce· May 29, 2007, 03:22 UTC · re: Linus Torvalds · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Linus Torvalds <torvalds@linux-foundation.org> wrote:
Show 8 quoted lines
> 
> On Mon, 28 May 2007, Shawn O. Pearce wrote:
> > 
> > What if we use a "slow" storage by "refs/notes/$objname/$notename",
> 
> This _really_ won't scale. Even if the notes don't exist, just doing the 
> lookup (which will fail for most commits) will be horribly slow, and will 
> populate the dentry cache with negative entries.
Yes.

I think you missed what I was trying to say. We *definately* do not want to probe the OS and ask "do you have $objname1? $objname2?" for exactly the reason you just stated. (Though you being some sort of kernel guru means you know a hellva lot more about that dentry cache thing than I do!)

What I meant was something more like:
	DIR *d = opendir("refs/notes");
	if (d) {
		... notes can exist in both packfiles and "loose" ...
		only_packed_notes = 0;
		while (readdir(d)) {
			... the entry name here is the name of an object ...
			... stuff into a hash table, we can come back later ...
			... for the subdirectory contents if we need it ...
		}
		closedir(d);
		... also examine "packed-refs", in case any are there ...
	} else {
		only_packed_notes = 1;
		... we *only* have notes in packfiles, if we have any at all...
	}
Then looking up a note is just a probe into our in-memory hash
(if only_packed_notes is false) and a probe into the packfile(s) to
find the notes for the object.  Not very expensive if the packfiles
have the reverse obj->tag mappings indexed within them.
 
> To get good filesystem performance, you have to
>  - have reasonable hit-rates (and looking it up for each commit is _not_ 
>    going to do that)
>  - not have millions of objects.
Which is why we:
  a) allow these things to migrate into packed-refs, because
  getting into there is a hellva lot cheaper than getting into
  a packfile;
  b) move them into a packfile when we repack loose objects,
  because then we have really good access.
  c) take them out of packed-refs once they are into a packfile,
  and get them out of the loose refs/notes directory as early as
  possible.
-- 
Shawn.
Jakub Narebski· May 29, 2007, 07:04 UTC · re: Shawn O. Pearce · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Shawn O. Pearce wrote:
Show 12 quoted lines
> Which is why we:
> 
>   a) allow these things to migrate into packed-refs, because
>   getting into there is a hellva lot cheaper than getting into
>   a packfile;
> 
>   b) move them into a packfile when we repack loose objects,
>   because then we have really good access.
> 
>   c) take them out of packed-refs once they are into a packfile,
>   and get them out of the loose refs/notes directory as early as
>   possible.
Three-tier layout? Loose note refs -> packed refs -> packfile? 
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Andy Parkins· May 29, 2007, 11:04 UTC · re: Linus Torvalds · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Tuesday 2007 May 29, Linus Torvalds wrote:
> So you'd have to have a separate database. You could do it with a separate
> index file (or mixing it up with the "index v4" and doing it with a single
> index file that also contains normal objects), but the point is, it's
> going to be a real separate database.

This is an off-the-wall suggestion; but why not use the object database a bit more strongly than just for storing the notes? The original method was to use the filesystem to store refs/notes/$SHA1, which was essentially annotated tags. The object database has been designed for storing thousands of hierarchically organised files, so why not use that instead of the refs/ tree?

That is to say, make a "private" branch, independent of any real repository branches. I'll call it "notes". Then each commit in that branch represents a change to a note (or multiple notes). The tree attached to each commit provides the reverse lookup that's wanted, and also maps to the object itself. So where a normal tree has

100644 blob 156e952df8603c72532bcda95ddcd3bcb16ec5fd somefile
A "notes" tree would have

100644 blob 156e952df8603c72532bcda95ddcd3bcb16ec5fd $SHA1_OF_TARGET_COMMIT1 100644 blob 97e08b0ab483146cb8fff31138eaa828c24ac84f $SHA1_OF_TARGET_COMMIT2

All the "notes" tools could now use the standard git tools to look up a note. The notes would be version controlled (which is impressive in itself), and reverse lookup is the same as path-limited git-rev-list.

The tree objects are (I guess) stored alphabetically, so the reverse lookup could be done with a binary search. If a user really wanted to, they could check out the "notes" branch and edit the files directly.

Madness?  I'm sure.
Andy
-- 
Dr Andy Parkins, M Eng (hons), MIET
andyparkins@gmail.com
Johannes Schindelin· May 29, 2007, 11:12 UTC · re: Andy Parkins · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Hi,
On Tue, 29 May 2007, Andy Parkins wrote:
Show 9 quoted lines
> On Tuesday 2007 May 29, Linus Torvalds wrote:
> 
> > So you'd have to have a separate database. You could do it with a 
> > separate index file (or mixing it up with the "index v4" and doing it 
> > with a single index file that also contains normal objects), but the 
> > point is, it's going to be a real separate database.
> 
> This is an off-the-wall suggestion; but why not use the object database 
> a bit more strongly than just for storing the notes?
I alluded to the same yesterday.

However, just before waking up, I had the same idea as you, using a branch.

Show 8 quoted lines
> So where a normal tree has
> 
> 100644 blob 156e952df8603c72532bcda95ddcd3bcb16ec5fd somefile
> 
> A "notes" tree would have
> 
> 100644 blob 156e952df8603c72532bcda95ddcd3bcb16ec5fd $SHA1_OF_TARGET_COMMIT1
> 100644 blob 97e08b0ab483146cb8fff31138eaa828c24ac84f $SHA1_OF_TARGET_COMMIT2

Actually, just throw in one fan-out stage, and you should be fine, performance-wise.

And what is best: you could merge notes from somebody else easily, _exactly_ because it is a proper branch now.

Ciao, Dscho

Johan Herland· May 29, 2007, 07:06 UTC · re: Junio C Hamano · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Monday 28 May 2007, Junio C Hamano wrote:
Show 33 quoted lines
> Johan Herland <johan@herland.net> writes:
> > On Monday 28 May 2007, Linus Torvalds wrote:
> >> On Mon, 28 May 2007, Johan Herland wrote:
> >> > I still don't see what makes note objects inherently more expensive than
> >> > commit objects. Except for the refs, of course, but we're getting rid
> >> > of those (at least replacing them with a more efficient reverse mapping).
> >> 
> >> It's exactly the refs that I worry about.
> >> 
> >> Anything that needs to read in all notes at startup is going to be _slow_.
> >> 
> >> In contrast, commits we read when (and only when) we need them.
> >
> > Ok. But the reverse mapping will help with this, won't it?
> > We'll look up the interesting commits and find their associated
> > note objects directly.
> 
> The issue Linus brought up worries me, too.
> 
> The "efficient reverse mapping" is still handwaving at this
> stage.  What it needs to do is an equivalent to your
> implementation with "refs/notes/<a dir per commit>/<note>".  The
> "efficient" one might do a flat file that says "notee note" per
> line sorted by notee, or it might use BDB or sqlite, but the
> amount of the data and complexity of the look-up is really the
> same.  A handful notes per each commit in the history (I think
> Linus's "Acked-by after the fact" example a very sensible thing
> to want from this subsystem).
> 
> I am not saying that it is impossible to make the set-up cost
> for the "efficient lookup" almost zero, and to make it lazy and
> on-demand.  The concern above just adds one design constraints
> to that "efficient reverse mapping" code yet to come.
Ok, here's what I'm thinking so far on that reverse mapping:
1. Keep a file, ".git/reverse_tagmap_sorted" with one entry of the form
"pointee pointer" per line. The file is sorted on "pointee", so we can
easily do the radix-256-fan-out-followed-by-binary-search trick that
Linus mentioned in another thread. This should hopefully make lookup
fairly cheap. BTW, if there is a similar "pointee pointer"-type format
already being used in git, I'd be happy to use that instead. I looked
at the "peeled" format being used by packed-refs, but using that
directly doesn't sound like a good idea, since the refname causes the
entries to be of variable length, and the refnames are not interesting
to me at all.
2. Keep another file, ".git/reverse_tagmap_unsorted" in front of (1).
This file has exactly the same format, minus the sorting. It exists just
to make insertion cheap. Once this file reaches a certain size (i.e.
when trawling it on lookup becomes slightly painful), we shuffle the
entries into the sorted file (this would happen automatically on
insertion of an entry, and should _not_ have to be triggered by 'git-gc'
etc.).

Of course, if we think insertion directly into (1) will never be too expensive, we can drop (2) altogether.

I don't know enough about packing to have a good idea on how to pack these reverse tagmaps, but Shawn's thoughts about keeping associated tags/notes and objects close together makes a lot of sense. I'm just not sure yet where these reverse tagmaps fit into the whole picture.

Currently, AFAICS, the packed-refs file is never propagated into the packs, but stays separate for the lifetime of the repo, but then it seems we're designing these reverse tagmaps for managing a handful of notes per commit, i.e. to hold a couple of orders of magnitude more entries than the packed-refs file.

Maybe each pack should keep the reverse tagmap for all the object->note relationships internal to that pack? Everything else (unpacked notes, and object->note relationships spanning packs) would be kept in (1). Of course, when repacking, we'd try to keep objects and their notes together as much as possible, to maximize the in-pack reverse tagmap, and minimize the number of entries left behind in (1).

Have fun!
...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Jeff King· May 29, 2007, 08:22 UTC · re: Johan Herland · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Tue, May 29, 2007 at 09:06:29AM +0200, Johan Herland wrote:
Show 6 quoted lines
> 1. Keep a file, ".git/reverse_tagmap_sorted" with one entry of the form
> "pointee pointer" per line. The file is sorted on "pointee", so we can
> easily do the radix-256-fan-out-followed-by-binary-search trick that
> Linus mentioned in another thread. This should hopefully make lookup
> fairly cheap. BTW, if there is a similar "pointee pointer"-type format
> already being used in git, I'd be happy to use that instead. I looked

I did a similar thing (though rather than having "lines" at all, they were fixed-length pairs of binary sha1 hashes) to implement a negative delta cache (which turned out to be a stupid idea, but the implementation worked):

http://www.gelato.unsw.edu.au/archives/git/0606/23229.html
Show 7 quoted lines
> 2. Keep another file, ".git/reverse_tagmap_unsorted" in front of (1).
> This file has exactly the same format, minus the sorting. It exists just
> to make insertion cheap. Once this file reaches a certain size (i.e.
> when trawling it on lookup becomes slightly painful), we shuffle the
> entries into the sorted file (this would happen automatically on
> insertion of an entry, and should _not_ have to be triggered by 'git-gc'
> etc.).

The implementation I mentioned above collects several "to be inserted" entries (in memory) and then merge sorts the two lists into a new file. So it was fast in terms of comparisons, but it involved writing O(n) entries, which is probably bad for creating a single note.

> Of course, if we think insertion directly into (1) will never be too
> expensive, we can drop (2) altogether.
You can always find the right spot, memmove everything down one slot,
and insert. But that means:
  - the cost of insertion will be proportional to the number of items,
    whereas using an unsorted journal you get to amortize that cost over
    many insertions
  - if you update in place, you have to lock the db for reading while
    you are moving around. If you are always either appending to the
    journal or merging into a new file, you can avoid this.
-Peff
Johan Herland· May 29, 2007, 09:23 UTC · re: Jeff King · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Tuesday 29 May 2007, Jeff King wrote:
Show 15 quoted lines
> On Tue, May 29, 2007 at 09:06:29AM +0200, Johan Herland wrote:
> 
> > 1. Keep a file, ".git/reverse_tagmap_sorted" with one entry of the form
> > "pointee pointer" per line. The file is sorted on "pointee", so we can
> > easily do the radix-256-fan-out-followed-by-binary-search trick that
> > Linus mentioned in another thread. This should hopefully make lookup
> > fairly cheap. BTW, if there is a similar "pointee pointer"-type format
> > already being used in git, I'd be happy to use that instead. I looked
> 
> I did a similar thing (though rather than having "lines" at all, they
> were fixed-length pairs of binary sha1 hashes) to implement a negative
> delta cache (which turned out to be a stupid idea, but the
> implementation worked):
> 
> http://www.gelato.unsw.edu.au/archives/git/0606/23229.html

Cool. It won't give a big-Oh improvement, but it might give a worthwile boost anyway. I guess it's the usual tradeoff between speed and maintainability/transparency/readability.

Show 24 quoted lines
> > 2. Keep another file, ".git/reverse_tagmap_unsorted" in front of (1).
> > This file has exactly the same format, minus the sorting. It exists just
> > to make insertion cheap. Once this file reaches a certain size (i.e.
> > when trawling it on lookup becomes slightly painful), we shuffle the
> > entries into the sorted file (this would happen automatically on
> > insertion of an entry, and should _not_ have to be triggered by 'git-gc'
> > etc.).
> 
> The implementation I mentioned above collects several "to be inserted"
> entries (in memory) and then merge sorts the two lists into a new file.
> So it was fast in terms of comparisons, but it involved writing O(n)
> entries, which is probably bad for creating a single note.
> 
> > Of course, if we think insertion directly into (1) will never be too
> > expensive, we can drop (2) altogether.
> 
> You can always find the right spot, memmove everything down one slot,
> and insert. But that means:
>   - the cost of insertion will be proportional to the number of items,
>     whereas using an unsorted journal you get to amortize that cost over
>     many insertions
>   - if you update in place, you have to lock the db for reading while
>     you are moving around. If you are always either appending to the
>     journal or merging into a new file, you can avoid this.

Yep, I'm thinking (2) will be worth it in the end. O(#notes) per note insertion doesn't sound appealing at all, especially not if we're expecting a handful of notes per commit.

Have fun!
...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Junio C Hamano· May 28, 2007, 20:45 UTC · re: Johan Herland · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Johan Herland <johan@herland.net> writes:
Show 10 quoted lines
> And while we're on the subject of changing the tag object, I'd like for
> the "tag" header (the one holding the tag name) to become optional.
> When doing my ref <-> name trick above, I conveniently forgot this little
> bugger. Basically the only reason for this one to exist is to include the
> name of the tag in the data passed to gpg for signing. This is of course
> necesssary in order to make renaming a signed tag impossible. (Allowing
> renaming would make it possible to replace it with a malicious tag with
> the original name.) Therefore the "tag" header must be mandatory for
> signed tags. But for all other tags (including notes) this header is
> pretty much useless.
This brings up an interesting point, by the way.
Currently when you say
	$ git show v2.6.22

we "dwim_ref" v2.6.22. That is, we try to see if ".git/v2.6.22" is there (it isn't), check ".git/refs/v2.6.22" (no, again), then happily find that ".git/refs/tags/v2.6.22" is there and use it.

During this process we _never_ check if the tag object pointed at by the ref v2.6.22 has tagname v2.6.22 at all.

I do not think it is a problem at all that we do not use "tag" name during the look-up process. However it would be a good idea to add logic to fsck to warn upon inconsistencis (perhaps by mistake) between refname and tag's true name.

The check would say something like:
	If an annotated (signed or unsigned) tag has a "tag"
	line to give it the official $name, and if it is pointed
	at by a ref, the refname must end with "/$name".
	Otherwise we warn.

Trivially, the above rule says that having v2.6.22 tag under refs/tags/v2.6.20 is a mistake we would want to be warned upon.

The "/$name" part is to support merging a separate project into another project after the fact better. Shawn graciously names his tags in git-gui project "gitgui-0.7.1", to avoid tag name collisions with "v1.5.0" style tag names in git.git project, but we could have stored his v0.7.1 tag as refs/tags/gitgui/v0.7.1 to differenciate the tag namespace (this needs an update to the "tag following" code in git-fetch with a new configuration).

Shawn O. Pearce· May 28, 2007, 21:19 UTC · re: Junio C Hamano · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Junio C Hamano <junkio@cox.net> wrote:
Show 6 quoted lines
> Shawn graciously names
> his tags in git-gui project "gitgui-0.7.1", to avoid tag name
> collisions with "v1.5.0" style tag names in git.git project, but
> we could have stored his v0.7.1 tag as refs/tags/gitgui/v0.7.1
> to differenciate the tag namespace (this needs an update to the
> "tag following" code in git-fetch with a new configuration).

Actually I use that same naming convention everywhere else except in egit. I've found it easier to have the "major project name" prefixed in front of the version number as it sort of reminds me of what I'm looking at... yes, I get confused easily. There are far too many "v1.0.0", "v1.0.1" around... ;-)

-- 
Shawn.
Johan Herland· May 28, 2007, 23:46 UTC · re: Junio C Hamano · lore

[PATCH] Add fsck_verify_ref_to_tag_object() to verify that refname matches name stored in tag object

On Monday 28 May 2007, Junio C Hamano wrote:
Show 13 quoted lines
> However it would be a good
> idea to add logic to fsck to warn upon inconsistencis (perhaps
> by mistake) between refname and tag's true name.
>
> The check would say something like:
>
> 	If an annotated (signed or unsigned) tag has a "tag"
> 	line to give it the official $name, and if it is pointed
> 	at by a ref, the refname must end with "/$name".
> 	Otherwise we warn.
>
> Trivially, the above rule says that having v2.6.22 tag under
> refs/tags/v2.6.20 is a mistake we would want to be warned upon.
This patch adds the check described by Junio.

It is assumed that the "tag" header is mandatory for all tag objects. This might change in the future, at which point this patch should be revised.

Signed-off-by: Johan Herland <johan@herland.net>
---
I hope this is what you had in mind :)
Have fun!
...Johan
 builtin-fsck.c |   19 +++++++++++++++++++
 1 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/builtin-fsck.c b/builtin-fsck.c
index cbbcaf0..3594bd3 100644
--- a/builtin-fsck.c
+++ b/builtin-fsck.c
@@ -501,6 +501,23 @@ static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, in
 	return 0;
 }
 
+static void fsck_verify_ref_to_tag_object(const char *refname, struct object *obj)
+{
+	/* Verify that refname matches the name stored in obj's "tag" header */
+	struct tag *tagobj = (struct tag *) parse_object(obj->sha1);
+	size_t tagname_len = strlen(tagobj->tag);
+	size_t refname_len = strlen(refname);
+
+	if (tagname_len < refname_len &&
+	    !memcmp(tagobj->tag, refname + (refname_len - tagname_len), tagname_len) &&
+	    refname[(refname_len - tagname_len) - 1] == '/') {
+		/* OK: tag name is "$name", and refname ends with "/$name" */
+		return;
+	}
+	else
+		error("%s: Mismatch between tag ref and tag object's name %s", refname, tagobj->tag);
+}
+
 static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
 	struct object *obj;
@@ -515,6 +532,8 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
 		/* We'll continue with the rest despite the error.. */
 		return 0;
 	}
+	if (obj->type == OBJ_TAG) /* ref to tag object */
+		fsck_verify_ref_to_tag_object(refname, obj);
 	default_refs++;
 	obj->used = 1;
 	mark_reachable(obj, REACHABLE);
-- 
1.5.2.87.g875de-dirty
Michael S. Tsirkin· May 28, 2007, 17:29 UTC · re: Linus Torvalds · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Show 21 quoted lines
> Quoting Linus Torvalds <torvalds@linux-foundation.org>:
> Subject: Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits
> 
> 
> 
> On Sun, 27 May 2007, Johan Herland wrote:
> > 
> > I've been working on combining tag objects and --decorate into a useful
> > proof-of-concept that provides the after-the-fact commit annotations I
> > requested above, and here's the result:
> 
> Ok, looks fine to me. I do have a few questions:
>  - why don't you just let people name their notes, the same way we name 
>    tags (and then actually using it as the note name?)
> 
>    Putting them in the refs/notes/ filesystem by their SHA1 seems a bit 
>    wasteful, and it would seem that it could be quite nice to name the 
>    notes some way?
> 
>  - This will probably scale horribly badly if you have tens of thousands 
>    of notes, even when they are packed. Do we care?

Maybe note names could include the object they point to? Would this help with the scalability too (i.e. only get names for objects we want)?

-- 
MST
Michael S. Tsirkin· May 28, 2007, 17:42 UTC · re: Michael S. Tsirkin · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

Show 28 quoted lines
> Quoting Michael S. Tsirkin <mst@dev.mellanox.co.il>:
> Subject: Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits
> 
> > Quoting Linus Torvalds <torvalds@linux-foundation.org>:
> > Subject: Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits
> > 
> > 
> > 
> > On Sun, 27 May 2007, Johan Herland wrote:
> > > 
> > > I've been working on combining tag objects and --decorate into a useful
> > > proof-of-concept that provides the after-the-fact commit annotations I
> > > requested above, and here's the result:
> > 
> > Ok, looks fine to me. I do have a few questions:
> >  - why don't you just let people name their notes, the same way we name 
> >    tags (and then actually using it as the note name?)
> > 
> >    Putting them in the refs/notes/ filesystem by their SHA1 seems a bit 
> >    wasteful, and it would seem that it could be quite nice to name the 
> >    notes some way?
> > 
> >  - This will probably scale horribly badly if you have tens of thousands 
> >    of notes, even when they are packed. Do we care?
> 
> Maybe note names could include the object they point to?
> Would this help with the scalability too (i.e. only get
> names for objects we want)?

Hmm, I just noticed there's refs/notes/{object-sha}/{note-sha} that does exactly that. So - this seems to be clear to everyone else, but I seem to miss the reason why does this not scale well? We can just get the notes for objects we pull, right?

-- 
MST
Johan Herland· May 28, 2007, 17:58 UTC · re: Michael S. Tsirkin · lore

Re: [PATCH 00/15] git-note: A mechanisim for providing free-form after-the-fact annotations on commits

On Monday 28 May 2007, Michael S. Tsirkin wrote:
Show 9 quoted lines
> Quoting Michael S. Tsirkin <mst@dev.mellanox.co.il>:
> > Maybe note names could include the object they point to?
> > Would this help with the scalability too (i.e. only get
> > names for objects we want)?
> 
> Hmm, I just noticed there's refs/notes/{object-sha}/{note-sha}
> that does exactly that. So - this seems to be clear to everyone
> else, but I seem to miss the reason why does this not scale well?
> We can just get the notes for objects we pull, right?

Currently, only "git-note -l" (and by extension gitk) uses the shortcut through refs/notes/{object-sha}/...

The --decorate code and everybody else goes through for_each_note_ref() which looks up every note ref in the repo.

...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Martin Langhoff· May 10, 2007, 22:33 UTC · re: Linus Torvalds · lore

Re: [RFC] Second parent for reverts

On 5/11/07, Linus Torvalds <torvalds@linux-foundation.org> wrote:
> Designing your SCM around the notion
> that everybody will use a totally integrated system is a mistake! It's a
> *huge* mistake.

A huge mistake _everyone else_ makes. git is the only scm that works transparently with everything else -- witness the myriad of importers/exporters/gateways we have, all depending on the fact that git tracks content, and does not get distracted with fancy metadata.

cheers
martin
Junio C Hamano· May 10, 2007, 01:43 UTC · re: Junio C Hamano · lore

Re: [RFC] Second parent for reverts

Junio C Hamano <junkio@cox.net> writes:
Show 22 quoted lines
>> But a' doesn't actually take anything from b, since it's reverting all of 
>> b (unless it's only reverting part of b), and, if b isn't there, it 
>> doesn't need a commit message, either, so it's not different from a. So 
>> the flow should be:
>>
>> a -> b -> c -> d -> e
>>   \              /
>>    --------------
>>
>> And this means blame work correctly: lines that b changed will be blamed 
>> on a (or an ancestor), because e will match a there and be different from 
>> d. So I think git-revert should simply add in the reverted patch's parent. 
>> Does this analysis make sense to other people?
>
> The revert operation at the tree level (not commit level) treats
> AS IF b is a common ancestor between a and d and computes a
> merge between a and d using that fake common ancestor to reach
> at e.  So it is understandable that you are confused that the
> result somehow has something to do with a merge between a and d.
>
> But other than that, the "analysis" does not make any sense to
> me.
Side note.

In the same spirit as gitk and history browsers pay attention to the in-body SHA-1 of reverted commits, you could make git-blame pay attention to the revert message. I think the rough outline would go like this.

 (1) you run pass_blame_to_parent() on 'e' as usual and give as
     much blame as you can to 'd'; the remainder are attributed
     to 'e' but it is actually a revert of what 'b' did.
 (2) you notice that 'e' is a revert of 'b';
 (3) 'b' always has only one parent, as we do not revert a
     merge; so you can find 'a' easily.
 (4) instead of "take the responsibility for the remaining
     entries" as usual in assign_blame() while drilling down
     'e', you find the matching blob using find_origin and
     find_rename between 'a' and 'e'.  And pass blames that are
     attributed to 'e' down to 'a'.
 (5) then you keep going, digging 'd' and 'a'.
 

← back to recent threads