问题
Say I have a file in git called filex.code
, and I want to see the full code of the last x
versions of that file with each changed section highlighted -- all in one place. So an x
-paned commit history of filex.code
, almost as if I were doing an x
-paned diff, but viewing historical versions rather than merging from different branches.
The greater x
, the better. Crossplatform would be great, but any of the Big Three works. Being able to edit the latest version would also be great, but read-only visualization is plenty.
Note that this is different from a simple history of commits to a file, so the otherwise wonderful gitk path/to/file (or SourceTree or whatever visual git client you love) isn't what I'm looking for. git log -p also comes close, and its output tantalizingly includes all the information I'd want, just that it's all in a linear, almost "procedural" output format rather than a good, relatively non-hierarchical, visual one like your favorite three-paned GUI'd mergetool's.
(Edit: Another really cool option that ultimately still experiences the shortcomings of only showing each line's latest source & a linear output is git blame, but it's cool.)
So I'm not precisely looking for setting up difftool either, I don't think. Rather than diffing two known versions of a file, I want to visualize x
iterations of historical edits to a single file.
Asking too much? Is this a WTFA (Write The "Fantastic" App [yourself]) situation?
Lesser alternative: Is there a three-paned mergetool that I can trick into displaying the last three commits of a single file?
回答1:
This script opens last N revisions of the file side-by-side.
#!/usr/bin/env python
import os, sys, tempfile
from shutil import rmtree
from subprocess import call, Popen, PIPE
from optparse import OptionParser
from traceback import print_exc
COMMAND = 'vim -d'
def vcall(cmd, **kwargs):
if options.verbose:
print ' '.join(cmd)
return call(' '.join(cmd) if sys.platform == 'darwin' else cmd,
**kwargs)
parser = OptionParser('usage: %s [-n <number of revisions>] filename' %
sys.argv[0])
parser.add_option('-n', '--num', dest='N', type='int',
help='number of revisions', default=3)
parser.add_option('-v', '--verbose', dest='verbose',
help='be verbose', default=False, action='store_true')
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error('incorrect number of arguments')
filename = args[0]
if vcall('git rev-parse'.split()) != 0:
sys.exit(1)
try:
cmd = 'git rev-list HEAD --'.split() + [filename]
if options.verbose:
print ' '.join(cmd)
pipe = Popen(' '.join(cmd) if sys.platform == 'darwin' else cmd,
stdout=PIPE).stdout
revs = []
for i, line in enumerate(pipe):
if i == options.N:
break
revs.append(line.rstrip())
except:
print_exc()
N = len(revs)
if N == 0:
sys.exit('fatal: ambiguous argument %s: path not in the working tree' %
filename)
elif N < options.N:
sys.stderr.write('%s has only %d revision%s' %
(filename, N, 's' if N > 1 else ''))
tempdir = ''
try:
tempdir = tempfile.mkdtemp()
head, tail = os.path.split(filename)
tempfiles = []
for i in xrange(N):
tempfiles.append(tail + ('.%d' % i if i else ''))
for i, f in enumerate(tempfiles):
with open(os.sep.join((tempdir, f)), 'w') as fout:
vcall(['git', 'show', '%s:./%s' % (revs[i], filename)], stdout=fout)
vcall(COMMAND.split() + list(reversed(tempfiles)), shell=True, cwd=tempdir)
except:
print_exc()
finally:
try:
if tempdir and os.path.isdir(tempdir):
rmtree(tempdir)
except:
print_exc()
Notes:
Vimdiff has a limitation of highlighting diffs in only 4 (first) buffers, but as for showing side-by-side - all file revisions are shown (eg N=20 works great). To avoid the warning for N>4 use
COMMAND = 'vim -O'
to see versions side-by-side without any diffs at all.The script has grown to be too large for SO style, but it is quite bullet-proof now - yet simple enough for an experienced eye.
来源:https://stackoverflow.com/questions/11726144/view-full-file-diff-of-x-commits-of-a-single-files-history-thats-hosted-in