简体   繁体   中英

View full file diff of `x` commits of a single file's history (that's hosted in git)

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?

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:

  1. 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.

  2. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM