{"thread":{"id":"10953","subject":"Using Filemerge.app as a git-diff viewer","startedAt":"2007-11-21T10:31:46Z","lastAt":"2007-11-21T14:36:58Z","messageCount":9,"participants":["Toby White","Jonathan del Strother","Jeff King","Wincent Colaiuta","Jakub Narebski"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"60490","messageId":"47440912.8010800@cam.ac.uk","threadId":"10953","inReplyTo":null,"subject":"Using Filemerge.app as a git-diff viewer","fromName":"Toby White","fromEmail":"tow21@cam.ac.uk","sentAt":"2007-11-21T10:31:46Z","receivedAt":"2007-11-21T10:31:46Z","isPatch":false,"sender":{"key":"tow21@cam.ac.uk","avatar":null},"body":"Mac OS X bundles a rather nice graphical diff viewer (Filemerge.app)\nwith its developer tools.\n\nWhile git-merge knows how to use this as a merge tool, I couldn't\nfind any way to easily use Filemerge as a viewer for the output\nof git-diff.\n\n(http://thread.gmane.org/gmane.comp.version-control.git/58702\ndiscusses the problem, and recommends piping git-diff into kompare.\nFilemerge unfortunately won't accept diff output on stdin)\n\nSo I wrote a quick script (below) which does what I need. Of all\nthe available git-diff flags, it only understands \"--cached\", and\nup to two commit objects, and no paths, but that's enough for me.\nWithin those constraints, it has the same semantics as git-diff.\n\nIt's not very nice, but in case anyone else wants this:\n\n#!/bin/sh\n#\n# Filemerge.app must not already be open before running\n# this script, or opendiff below will return immediately,\n# and the TMPDIRs deleted before it gets the chance to read\n# them.\n\nif test $# = 0; then\n   OLD=`git-write-tree`\nelif test \"$1\" = --cached; then\n   OLD=HEAD\n   NEW=`git-write-tree`\n   shift\nfi\nif test $# -gt 0; then\n   OLD=\"$1\"; shift\nfi\ntest $# -gt 0 && test -z \"$CACHED\" && NEW=\"$1\"\n\nTMPDIR1=`mktemp -d`\ngit-archive --format=tar $OLD | (cd $TMPDIR1; tar xf -)\nif test -z \"$NEW\"; then\n   TMPDIR2=$(git rev-parse --show-cdup)\n   test -z \"$cdup\" && TMPDIR2=.\nelse\n   TMPDIR2=`mktemp -d`\n   git-archive --format=tar $NEW | (cd $TMPDIR2; tar xf -)\nfi\n\nopendiff $TMPDIR1 $TMPDIR2 | cat\nrm -rf $TMPDIR1\ntest ! -z \"$NEW\" && rm -rf $TMPDIR2\n\n-- \nDr. Toby O. H. White\nDept. Earth Sciences,\nDowning Street,\nCambridge CB2 3EQ\nUnited Kingdom\n\nTel: +44 1223 333464\nFax: +44 1223 333450\nWeb: http://uszla.me.uk\n"},{"id":"60492","messageId":"2736C1E3-6294-49B0-96A2-8BCCFA27EDCD@steelskies.com","threadId":"10953","inReplyTo":"47440912.8010800@cam.ac.uk","subject":"Re: Using Filemerge.app as a git-diff viewer","fromName":"Jonathan del Strother","fromEmail":"maillist@steelskies.com","sentAt":"2007-11-21T11:20:05Z","receivedAt":"2007-11-21T11:20:05Z","isPatch":false,"sender":{"key":"jon.delstrother@bestbefore.tv","avatar":"https://gravatar.com/avatar/754e21ab701c00e2d21fc261187254c34b2a1c0b959d9ee5be1a295990be3081?d=mp&s=160"},"body":"On 21 Nov 2007, at 10:31, Toby White wrote:\n\n> Mac OS X bundles a rather nice graphical diff viewer (Filemerge.app)\n> with its developer tools.\n>\n> While git-merge knows how to use this as a merge tool, I couldn't\n> find any way to easily use Filemerge as a viewer for the output\n> of git-diff.\n>\n> (http://thread.gmane.org/gmane.comp.version-control.git/58702\n> discusses the problem, and recommends piping git-diff into kompare.\n> Filemerge unfortunately won't accept diff output on stdin)\n>\n> So I wrote a quick script (below) which does what I need. Of all\n> the available git-diff flags, it only understands \"--cached\", and\n> up to two commit objects, and no paths, but that's enough for me.\n> Within those constraints, it has the same semantics as git-diff.\n\n> ...\n\nHandy, thanks.\nJust as a note to anyone who runs into the same problems as me - I  \ncouldn't persuade this script to work at first.  On my Leopard  \ninstall, mktemp needs a template argument to run.  After replacing  \nboth `mktemp -d`s in this script with `mktemp -d /tmp/diff.XXXXX`, it  \nworks nicely.\n\nJon\n"},{"id":"60494","messageId":"20071121112757.GA17231@sigill.intra.peff.net","threadId":"10953","inReplyTo":"47440912.8010800@cam.ac.uk","subject":"Re: Using Filemerge.app as a git-diff viewer","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2007-11-21T11:27:57Z","receivedAt":"2007-11-21T11:27:57Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Nov 21, 2007 at 10:31:46AM +0000, Toby White wrote:\n\n> So I wrote a quick script (below) which does what I need. Of all\n> the available git-diff flags, it only understands \"--cached\", and\n> up to two commit objects, and no paths, but that's enough for me.\n> Within those constraints, it has the same semantics as git-diff.\n\nHave you looked at the documentation for GIT_EXTERNAL_DIFF (try git(7))?\nI think it is a cleaner way of doing what you want (although I think you\nwill get each file diffed individually, which is perhaps not what you\nwant).\n\nSomething like:\n\n$ cat >merge.sh <<EOF\n#!/bin/sh\nopendiff \"$1\" \"$2\"\nEOF\n$ GIT_EXTERNAL_DIFF=./merge.sh git-diff ...\n\n> #!/bin/sh\n> #\n> # Filemerge.app must not already be open before running\n> # this script, or opendiff below will return immediately,\n> # and the TMPDIRs deleted before it gets the chance to read\n> # them.\n>\n> if test $# = 0; then\n>   OLD=`git-write-tree`\n> elif test \"$1\" = --cached; then\n>   OLD=HEAD\n>   NEW=`git-write-tree`\n>   shift\n> fi\n> if test $# -gt 0; then\n>   OLD=\"$1\"; shift\n> fi\n> test $# -gt 0 && test -z \"$CACHED\" && NEW=\"$1\"\n\nwrite-tree? Yikes. If you want to diff against the working tree, then do\nthat. If you want to diff against the index, then you probably want to\ngit-checkout-index to a tmpdir, and diff against that.\n\n> git-archive --format=tar $OLD | (cd $TMPDIR1; tar xf -)\n\nAgain, this could be simpler and faster by using git-checkout-index\n(preceded by git-read-tree into a temp index, if you are comparing\nagainst a tree).\n\n-Peff\n"},{"id":"60505","messageId":"1A9343EE-BB45-4CF8-9F17-E6A73C5F0B83@wincent.com","threadId":"10953","inReplyTo":"20071121112757.GA17231@sigill.intra.peff.net","subject":"Re: Using Filemerge.app as a git-diff viewer","fromName":"Wincent Colaiuta","fromEmail":"win@wincent.com","sentAt":"2007-11-21T12:59:37Z","receivedAt":"2007-11-21T12:59:37Z","isPatch":false,"sender":{"key":"greg@hurrell.net","avatar":"https://avatars.githubusercontent.com/u/7074?v=4"},"body":"El 21/11/2007, a las 12:27, Jeff King escribió:\n\n> On Wed, Nov 21, 2007 at 10:31:46AM +0000, Toby White wrote:\n>\n>> So I wrote a quick script (below) which does what I need. Of all\n>> the available git-diff flags, it only understands \"--cached\", and\n>> up to two commit objects, and no paths, but that's enough for me.\n>> Within those constraints, it has the same semantics as git-diff.\n>\n> Have you looked at the documentation for GIT_EXTERNAL_DIFF (try  \n> git(7))?\n> I think it is a cleaner way of doing what you want (although I think  \n> you\n> will get each file diffed individually, which is perhaps not what you\n> want).\n>\n> Something like:\n>\n> $ cat >merge.sh <<EOF\n> #!/bin/sh\n> opendiff \"$1\" \"$2\"\n> EOF\n> $ GIT_EXTERNAL_DIFF=./merge.sh git-diff ...\n\nA few problems with that:\n\n- FileMerge is broken, and crashes if you pass /dev/null as a param  \n(which happens for new/deleted files)\n\n- you need to escape those $-signs\n\n- the params you're interested in are actually $2 and $5, not $1 and  \n$2, according to git(7)\n\n- you need to handle the clean tree case (no params are passed)\n\n- and also the 1-param case, \"for unmerged paths\", whatever that means\n\n- will only work when run from the top of the tree (path parameters  \nare passed in relative to that)\n\nSo here's a less-broken version of your suggestion, but it's still  \nbroken; a relatively complex wrapper is required to do this right:\n\n$ cat >merge.sh <<EOF\n#!/bin/sh\n[ \\$# -eq 7 ] && opendiff \"\\$2\" \"\\$5\"\nEOF\nchmod +x merge.sh\nGIT_EXTERNAL_DIFF=./merge.sh git-diff ...\n\nCheers,\nWincent\n"},{"id":"60506","messageId":"20071121130446.GA18414@sigill.intra.peff.net","threadId":"10953","inReplyTo":"1A9343EE-BB45-4CF8-9F17-E6A73C5F0B83@wincent.com","subject":"Re: Using Filemerge.app as a git-diff viewer","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2007-11-21T13:04:46Z","receivedAt":"2007-11-21T13:04:46Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Nov 21, 2007 at 01:59:37PM +0100, Wincent Colaiuta wrote:\n\n> So here's a less-broken version of your suggestion, but it's still broken; a \n> relatively complex wrapper is required to do this right:\n\nThanks. My \"something like\" should more accurately have been \"I really\ndidn't think about this at all.\" But I do think a GIT_EXTERNAL_DIFF-type\napproach (and if GIT_EXTERNAL_DIFF isn't powerful enough, considering\nsome alternate interface) is nicer than trying to wrap git-diff.\n\n> $ cat >merge.sh <<EOF\n> #!/bin/sh\n> [ \\$# -eq 7 ] && opendiff \"\\$2\" \"\\$5\"\n> EOF\n\nThis is probably easier to read using <<'EOF' to quote (but I failed to\ndo even that in my example).\n\n-Peff\n"},{"id":"60507","messageId":"47442E57.8010004@cam.ac.uk","threadId":"10953","inReplyTo":"20071121112757.GA17231@sigill.intra.peff.net","subject":"Re: Using Filemerge.app as a git-diff viewer","fromName":"Toby White","fromEmail":"tow21@cam.ac.uk","sentAt":"2007-11-21T13:10:47Z","receivedAt":"2007-11-21T13:10:47Z","isPatch":false,"sender":{"key":"tow21@cam.ac.uk","avatar":null},"body":"Jeff King wrote:\n\n> Have you looked at the documentation for GIT_EXTERNAL_DIFF (try git(7))?\n> I think it is a cleaner way of doing what you want (although I think you\n> will get each file diffed individually, which is perhaps not what you\n> want).\n> \n> Something like:\n> \n> $ cat >merge.sh <<EOF\n> #!/bin/sh\n> opendiff \"$1\" \"$2\"\n> EOF\n> $ GIT_EXTERNAL_DIFF=./merge.sh git-diff ...\n\nI hadn't seen that, no, but that's not quite right.\n(Wincent pointed out its flaws better than me. Basically,\nopendiff is not really diff-like enough.)\n\nAnd in any case, that launches Filemerge repeatedly\non every file separately, which makes reviewing a large diff\ntime-consuming and not very helpful.\n\n> write-tree? Yikes. If you want to diff against the working tree, then do\n> that. If you want to diff against the index, then you probably want to\n> git-checkout-index to a tmpdir, and diff against that.\n\nAm I misunderstanding the documentation? From man git-write-tree\n\n\"Conceptually, git-write-tree sync()s the current index contents into a\n  set of tree files. In order to have that match what is actually in your\n  directory right now, you need to have done a git-update-index phase\n  before you did the git-write-tree.\"\n\nSo git-write-tree precisely does give you the index not the working tree,\nby my reading.\n\n>> git-archive --format=tar $OLD | (cd $TMPDIR1; tar xf -)\n> \n> Again, this could be simpler and faster by using git-checkout-index\n> (preceded by git-read-tree into a temp index, if you are comparing\n> against a tree).\n\nErm, ok, this is rapidly approaching the limit of my git knowledge,\nbut while I can see git-read-tree will write a tree-ish into a temp\nindex,\n\n(so presumably\ngit-read-tree --index-output=$TMPFILE <commit>\nought to work. Except it doesn't, I get the error message\nfatal: unable to write new index file\n),\n\nI can't see how to make git-checkout-index read from a temp index.\n\nAnd I'm assuming I don't want to go stomping all over the current\nindex just in order to do a diff, which shouldn't change the state\nof my repository.\n\nIs there a canonical way to checkout a given commit object into\na fresh directory?\n\n-- \nDr. Toby O. H. White\nDept. Earth Sciences,\nDowning Street,\nCambridge CB2 3EQ\nUnited Kingdom\n\nTel: +44 1223 333464\nFax: +44 1223 333450\nWeb: http://uszla.me.uk\n"},{"id":"60511","messageId":"4455F263-251E-4B04-9F61-A26A3548B316@wincent.com","threadId":"10953","inReplyTo":"47442E57.8010004@cam.ac.uk","subject":"Re: Using Filemerge.app as a git-diff viewer","fromName":"Wincent Colaiuta","fromEmail":"win@wincent.com","sentAt":"2007-11-21T13:28:03Z","receivedAt":"2007-11-21T13:28:03Z","isPatch":false,"sender":{"key":"greg@hurrell.net","avatar":"https://avatars.githubusercontent.com/u/7074?v=4"},"body":"El 21/11/2007, a las 14:10, Toby White escribió:\n\n> (Wincent pointed out its flaws better than me. Basically,\n> opendiff is not really diff-like enough.)\n>\n> And in any case, that launches Filemerge repeatedly\n> on every file separately, which makes reviewing a large diff\n> time-consuming and not very helpful.\n\nI know it's not much help to you right now, but when I first asked  \nabout a side-by-side diff viewer back in September I explored creating  \na wrapper using GIT_EXTERNAL_DIFF and was basically unsatisfied with  \nthe results.\n\nSo I decided to write a very simple native Cocoa app (I'm working on  \nMac OS X); it was to be a diff viewer and nothing more (not a  \nrepository browser). I was able to spend a few hours on it back in  \nSeptember but then other things came up so I haven't been able to  \nfinish it, but I do intend to come back to it when time permits. You  \ncan browse the work in progress at:\n\n<http://git.wincent.com/gdiff.git>\n\nBasically consists of a fast Ragel-generated state machine for parsing  \ngit-diff output, patches and email messages, and the beginnings of a  \nUI. But like I said, not much help to you right now as it can't yet be  \nused to do anything useful. My goal for it is to provide a nice  \nexperience, above all when reviewing patches on a mailing list (ie.  \nwhere your local repository doesn't have the proposed commits in it  \nyet). In fact, even when run from outside of any Git repo I want it to  \nat least show the known parts of the files (the context) and provide a  \nshaded background for the unknown bits.\n\nCheers,\nWincent\n"},{"id":"60513","messageId":"fi1dhg$eb5$1@ger.gmane.org","threadId":"10953","inReplyTo":"1A9343EE-BB45-4CF8-9F17-E6A73C5F0B83@wincent.com","subject":"Re: Using Filemerge.app as a git-diff viewer","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2007-11-21T13:58:09Z","receivedAt":"2007-11-21T13:58:09Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Wincent Colaiuta wrote:\n\n> - you need to escape those $-signs\n\n[...]\n\n> $ cat >merge.sh <<EOF\n\nOr use\n\n$ cat >merge.sh <<\\EOF\n\n-- \nJakub Narebski\nWarsaw, Poland\nShadeHawk on #git\n"},{"id":"60518","messageId":"20071121143658.GA24108@sigill.intra.peff.net","threadId":"10953","inReplyTo":"47442E57.8010004@cam.ac.uk","subject":"Re: Using Filemerge.app as a git-diff viewer","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2007-11-21T14:36:58Z","receivedAt":"2007-11-21T14:36:58Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Nov 21, 2007 at 01:10:47PM +0000, Toby White wrote:\n\n> And in any case, that launches Filemerge repeatedly\n> on every file separately, which makes reviewing a large diff\n> time-consuming and not very helpful.\n\nI think that particular complaint of GIT_EXTERNAL_DIFF has come up\nbefore. It probably wouldn't be too hard to support a separate\nenvironment variable to mean \"diff these two trees\" (or tree/cache, or\ncache/working tree, etc) rather than \"diff these two files\".\n\n> Am I misunderstanding the documentation? From man git-write-tree\n>\n> \"Conceptually, git-write-tree sync()s the current index contents into a\n>  set of tree files. In order to have that match what is actually in your\n>  directory right now, you need to have done a git-update-index phase\n>  before you did the git-write-tree.\"\n>\n> So git-write-tree precisely does give you the index not the working tree,\n> by my reading.\n\nWhat you are doing isn't _wrong_, but it is not the most efficient way\nof doing it, and it is a little bit awkward, I think.\n\nThere are basically three conceptual \"spots\" for files in git:\n\n  - in the object database (i.e., tree objects pointing to blobs)\n  - in the index (the blobs are actually in the object db)\n  - in the working tree (i.e., actual files)\n\ngit-write-tree moves data from the index into the object database\n(git-commit is more or less git-write-tree followed by git-commit-tree).\ngit-archive (piped to tar) moves data from the object database (because\nit takes a tree object name) into the working tree.\n\nSo you are basically moving data into the object db, and then out to the\nworking tree. But there is a command that moves data directly from the\nindex to the working tree: git-checkout-index.\n\nNow, what you are doing is not terribly inefficient in that sense, since\ncreating a tree object is usually pretty inexpensive. But it does\ninvolve writing to the object db for a diff, which is conceptually a\nread-only operation.\n\nHowever, git-checkout-index also avoids piping through tar, which is\nlikely to be faster for a large data set.\n\n> Erm, ok, this is rapidly approaching the limit of my git knowledge,\n\nBTW, I don't mean to nitpick your script, which obviously works for you.\nI'm just trying to increase your git knowledge. :)\n\n> but while I can see git-read-tree will write a tree-ish into a temp\n> index,\n>\n> (so presumably\n> git-read-tree --index-output=$TMPFILE <commit>\n> ought to work. Except it doesn't, I get the error message\n> fatal: unable to write new index file\n> ),\n\nAre you perhaps running against the rename(2)-ability boundaries\nmentioned in the man page? --index-output is relatively new, and I'm not\nsure of the reasons surrounding it. I think what you really want to work\nwith a temp index is to set GIT_INDEX_FILE.\n\n> I can't see how to make git-checkout-index read from a temp index.\n\nIt should read from GIT_INDEX_FILE. So the correct incantation for\nchecking out a tree should be:\n\nGIT_INDEX_FILE=$TMPFILE git-read-tree <commit>\nGIT_INDEX_FILE=$TMPFILE git-checkout-index -a --prefix=$TMPDIR/\n\nand of course, for the --cached case, you can just use the existing\nindex:\n\ngit-checkout-index -a --prefix=$TMPDIR/\n\n(btw, I have no idea if creating a temp index is actually faster than\npiping through tar. I suspect it is, but haven't tested. So it may be\nsane to use checkout-index for the --cached case, which is what I was\noriginally suggesting, and simply use git-archive for the tree case).\n\n> And I'm assuming I don't want to go stomping all over the current\n> index just in order to do a diff, which shouldn't change the state\n> of my repository.\n\nCorrect.\n\n> Is there a canonical way to checkout a given commit object into\n> a fresh directory?\n\ngit-archive piped to tar is the canonical porcelain way (again, I was\nsuggesting checkout-index mainly for the --cached case). The way I\nshowed above is the \"plumbing\" way (and is what underlies git-checkout,\nfor example, though of course a temp index is not needed in that case).\n\nHope that make sense.\n\n-Peff\n"}]}