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