Skip to contentSkip to conversation

Wanted - a file browser interface to git

PATCH In review John Ellson 2 participants 6 messages Jun 15, 2016

lore
Share & tools
lore
#0 lore raw

I know that Linus thinks that files are less important than commits, but we are finding a real need to be able to browse though old versions of files and we have not yet found an efficient way to do it with git.

An example is: "I know that file xxx contained algorithm yyy at some point in the past and now I'd like to browse back through the history of xxx to find the exact details."

I think what I'd like is a file browser on git that:

- can navigate the directory tree, starting by default with the HEAD tree, but able to browse the state of the tree at any time in the history.

- can select any file from the tree, and then view the state of that file at any time in its history by stepping forward or back through commits that have affected that file.

- can view the difference between any pair of states of the file, with annotations as to the source of the changes.

- can search for a string across the complete history of a file.

- can invoke the users choice of editor on the file.

Neither gitk nor qgit provide tree browsing, so it can be hard to get at a specific file.

qgit has nice file browser that annotates all changes, but I think I'd prefer a two panel diff view.

Neither qgit not gitk provide links to an editor so that a file can be worked on once found.

Am I out in left field here, or does anyone else feel the need for something like this?

John

#1 lore raw
Quoted 4 lines · John Ellson

An example is: "I know that file xxx contained algorithm yyy at some point in the past and now I'd like to browse back through the history of xxx to find the exact details."

You are aware of "git whatchanged -p xxx", right?

Yeah, it's not graphical, and I agree that it might be very cool to have a graphical version of it. But I thought I'd mention it even so. A surprising number of people seem to have never realized, and at least for me personally, it's one of the most common things I do.

Linus

#2 lore raw
Quoted 7 lines · Linus Torvalds
Quoted 4 lines · John Ellson

An example is: "I know that file xxx contained algorithm yyy at some point in the past and now I'd like to browse back through the history of xxx to find the exact details."

Quoted 6 lines

You are aware of "git whatchanged -p xxx", right?

Yeah, it's not graphical, and I agree that it might be very cool to have a graphical version of it. But I thought I'd mention it even so. A surprising number of people seem to have never realized, and at least for me personally, it's one of the most common things I do.

Linus

Linus,

I wasn't aware of it, no. Looks very useful. Thanks.

I see that you can take the tree id from the diff-tree lines and then produce the state of the file at that time with "cg-admin-cat -r <id> xxx" Is that how you would do it?

Are there any plans for cogito to support it?

John

#3 lore raw
Quoted 10 lines · John Ellson
Quoted 2 lines · Linus Torvalds

You are aware of "git whatchanged -p xxx", right?

I wasn't aware of it, no. Looks very useful. Thanks.

I see that you can take the tree id from the diff-tree lines and then produce the state of the file at that time with "cg-admin-cat -r <id> xxx"
Is that how you would do it?

Well, I'd do it with the git commands: once you see the diff, you should know the SHA1's of the source and destination, and then you can just do

git-cat-file blob [sha1]

to get the before (or after) state.

The way I'd get the SHA1 is either (now with the extended diff format) in the short form from the diff itself (the "index" line), or by just separately doing a

git-diff-tree -r [sha-of-commit]

to see the "raw" diff format.

All the diff things can take a pathname limiter, the same way "git-whatchanged" does, so if you are only interested in one file, just name the file:

git-diff-tree -r [sha-of-commit] [filename]

And just to make clear how powerful this is: "filename" doesn't have to be a single file. It can be a set of files and/or directories, so if you want to track multiple things at the same time, just do multiple filenames.

What I do a lot is to check what has changed in some particular subsystem, ie somebody says that something broke in SCSI, and then I do

git-whatchanged -p drivers/scsi/ include/scsi/

and it will show any changes to anything under either of those directories.

"git-whatchanged" really is very powerful. The silly thing is that it really boils down to just a single line script (well, with various argument handling etc it's actually five lines, but the "core" is really just a single pipeline of "git-rev-list | git-diff-tree --stdin".

Quoted 1 line

Are there any plans for cogito to support it?

Well, cogito could certainly just do a "cg-whatchanged", but it's really the same thing. Since cogito depends on git anyway, cogito users could just use the git-whatchanged functionality.

Or to make it more seamless, just do

alias cg-whatchanged=git-whatchanged

or something like that ;^)

Linus

#4 lore raw

PATCH 1/1 cg-history FILE [NTH_PARENT] - was: Re: Wanted - a file browser interface to git

In reply to Linus Torvalds
Quoted 11 lines · Linus Torvalds
Quoted 3 lines · John Ellson

An example is: "I know that file xxx contained algorithm yyy at some point in the past and now I'd like to browse back through the history of xxx to find the exact details."

You are aware of "git whatchanged -p xxx", right?

Yeah, it's not graphical, and I agree that it might be very cool to have a graphical version of it. But I thought I'd mention it even so. A surprising number of people seem to have never realized, and at least for me personally, it's one of the most common things I do.

Linus

OK. Here is a not-very-smart cogito command to display the history of a file, or the state of the nth parent of the file in its history.

Feedback and or complete rewrites are requested ;-)

John

produce the history of a file, or its state at its nth_parent

---
commit 8478ad1164e37e9cca039a3f9552d2a98f7bead6
tree 40b39d5f9af573a7815f073cada03c7903bfc6fa
parent 5d74e4859afc81a4658133d5a83809ac814dbf34
author John Ellson <[email protected]> Tue, 18 Oct 2005 23:13:44 -0400 committer John Ellson <[email protected]> Tue, 18 Oct 2005 23:13:44 -0400

 cg-history |   36 ++++++++++++++++++++++++++++++++++++
 1 files changed, 36 insertions(+), 0 deletions(-)
cg-history new file +36 -0
new file mode 100755
index 0000000..ba86f71
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+#
+# Display the change history of a file.
+# Copyright (c) John Ellson, 2005
+#
+# The change history of a file is displayed on stdout, or
+# if an integer is provided for NTH_PARENT, then the complete
+# state of the file at that step in its history is sent to stdout.
+#
+
+USAGE="cg-history FILE [NTH_PARENT]"
+
+. ${COGITO_LIB}cg-Xlib || exit 1
+
+[ "$ARGS" ] || usage
+
+if [ "${ARGS[1]}" = "" ]; then
+       git-whatchanged -p "${ARGS[0]}"
+else
+       i=0
+       git-whatchanged -p "${ARGS[0]}" |
+       while read -r cmd sha rest
+       do
+               case "$cmd" in
+               diff-tree)
+                       i=`expr $i + 1`
+                       if [ $i = ${ARGS[1]} ] ; then
+                               cg-admin-cat -r "$sha" "${ARGS[0]}"
+                               exit 0
+                       fi
+                       ;;
+               *)
+                       ;;
+               esac
+       done
+fi
#5 lore raw

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

[ Ok, time for some serious power-git usage ]

Quoted 4 lines · John Ellson

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