Thread (6 messages) flat view 6 messages, 2 authors, 2016-06-15

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

From: Linus Torvalds <torvalds@osdl.org>
Date: 2016-06-15 22:42:09

[ 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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help