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

Re: [PATCH] cg-history FILE [NTH_PARENT]

From
Linus Torvalds <torvalds@osdl.org>
Date
Oct 19, 2005, 04:01 UTC
Message-ID
<Pine.LNX.4.64.0510182029240.3369@g5.osdl.org>
In-Reply-To
<4355BA43.5030509@research.att.com>
[ Ok, time for some serious power-git usage ]
On Tue, 18 Oct 2005, John Ellson wrote:
> 
> produce the history of a file, or its state at its nth_parent
>
> +       git-whatchanged -p "${ARGS[0]}" |
Actually, you're much better of _not_ using "-p" to generate a patch.

In fact, you don't even want the pretty format that "git-whatchanged" does, you really want the raw output.

Try this instead:
	git-rev-list HEAD | git-diff-tree --stdin -s -r "$ARG"
which will give _just_ a list of the commits that change the file "$ARG".

Here, the "--stdin" to git-diff-tree means that it should take its revision input from stdin (ie obvously the list of commits generated by git-rev-list), and the "-s" stands for "silent", ie git-diff-tree won't actually output the diff itself.

And the "-r" means that it should check the trees "recursively", which is needed since we want the diff-tree to traverse down the tree rather than just look at the top-level ("-p" to generate patches enables recursive by default since patches don't make sense on raw trees, but without the -p you need to do it explicitly).

And since we didn't ask for the header, the only thing you get is the list of commits that changed the file describled by the argument.

So now, you can just pick the n'th such commit, and do something like this
	#
	# Get the "${ARGS[1]}"th commit that changes  file "${ARGS[0]}"
	#
	rev=$(git-rev-list HEAD |
		git-diff-tree --stdin -s -r "${ARGS[0]}" |
		head -n "${ARGS[1]}" |
		tail -1)
	#
	# Pick up the file from that tree
	#
	filerev=$(git-ls-tree -r "$rev" "${ARGS[0]}" |
		cut -f1 |
		cut -d' ' -f3)
	#
	# And show it
	#
	git-cat-file blob $filerev
and you're done (untested, but you should get the idea).

Now, the interesting part about is that you can feed the output from git-diff-tree _back_ to git-diff-tree, so you can do some really fancy footwork like:

	git-rev-list rev1..rev2 |
		git-diff-tree --stdin -s -r "$ARG" |
		git-diff-tree --stdin -M --pretty -p
and what this will do is:
 - generate a list of all commits between rev1 and rev2
 - filter out just the commits that change the file "$ARG", and pass those 
   on.
 - for those commits, show the _whole_ diff, with rename detection and 
   with pretty-printed commit comment headers

In other words, you can basically look at all the full commits that changed one file (or a set of files). Efficiently.

This is kind of like "git-whatchanged", but it shows the full context of what changed. Of course, the second git-diff-tree can be used to limit the context to something else, ie you could do a variation of the above, something like

	git-rev-list v2.6.12.. |
		git-diff-tree --stdin -s -r drivers/usb/ |
		git-diff-tree --stdin -M --pretty -p drivers/ include/

which will show any commit that changed the drivers/usb/ directory after v2.6.12, but then limit the output of those commits to the drivers/ and include/ subdirectories (so anything that was changed in that same commit in a filesystem would _not_ be shown, for example, but if there were changes to drivers/scsi/ at the same time, they _would_ show).

Or, if you want to go really wild, do something like this on the kernel git tree:

	git-rev-list v2.6.12.. |
		git-diff-tree --stdin -s -r drivers/usb/ |
		git-diff-tree --stdin -s -r drivers/scsi/ |
		git-diff-tree --stdin -M --pretty -p drivers/scsi/ drivers/usb/ |
		less -S

which says to print out only those commits that change something _both_ in drivers/usb/ _and_ in drivers/scsi/ at the same time, and then show only those parts of the changes.

Try it out. It really does work, and is extremely powerful. It's even pretty efficient (make sure your tree is packed first, though ;). I can do the above in about three seconds for the current kernel history on my machine. That's 3 _seconds_ to go through what right now is 8005 commits:

	git-rev-list v2.6.12.. | wc -l

and the reason is exactly that the filename-based parsing is very good at efficiently pruning out all the tree information that isn't needed.

Very cool.

However, the "normal" situation is just the standard "git-whatchanged", which is much easier to use than something more complex like the above.

The core git commands are really designed to be scriptable, but "real life" seldom wants the complexity of quite that much flexibility.

		Linus
Previous: John Ellson
Message 6 of 6 in “Wanted - a file browser interface to git”
  1. John EllsonOct 19, 2005
  2. Linus TorvaldsOct 19, 2005
  3. John EllsonOct 19, 2005
  4. Linus TorvaldsOct 19, 2005
  5. cg-history FILE [NTH_PARENT] - was: Re: Wanted - a file browser interface to gitJohn Ellson, Oct 19, 2005
  6. Linus TorvaldsOct 19, 2005

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

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