The git command has a useful command to follow a file after a rename, as in git log --follow path/to/some/file
. Unfortunately, it works only for an individual file. I'd like to be able to do the equivalent of git log --follow some/directory
.
One way of doing this would probably be to massage the output of git ls-tree
and do that in a loop, but the problem then becomes that commits affecting multiple files won't be "coalesced" into one commit.
Is there a better way to do it? Note: using git 2.7.4
Find what file changed in a commit To find out which files changed in a given commit, use the git log --raw command. It's the fastest and simplest way to get insight into which files a commit affects.
Renaming a file or directory in git preserves its history. There are two main ways to git rename file or directory. You can use the mv or git rm commands.
Git keeps track of changes to files in the working directory of a repository by their name. When you move or rename a file, Git doesn't see that a file was moved; it sees that there's a file with a new filename, and the file with the old filename was deleted (even if the contents remain the same).
By default, with no arguments, git log lists the commits made in that repository in reverse chronological order; that is, the most recent commits show up first.
No.
Git does not store directories, only files. When you use any path name to commands like git log
or git diff
that look at entire commits, Git essentially says "start with the whole commit, then shrink it down to file(s) matching that path". Directories here simply wind up selecting every file in the directory.
The --follow
option can only follow one file. So if you could somehow manage to get it to apply to a directory, Git would first turn the directory into a set of files, then pick one of those files and follow just that one.
(The actual --follow
code is terribly hacky. It leverages off the rename detection code, but only works when commits are being compared in the newer-to-older order: if you add --reverse
, --follow
never works at all. Probably the whole thing needs to be thrown out and re-coded. Perhaps by re-coding it, you could make it handle multiple file names, and even directories full of files.)
There does not seem to be a builtin way to do this.
One can do this using a script and following a simple algorithm:
Here's a hacky way to do it using python 3 and the sh
module, won't work on Windows for various reasons.
#!/usr/bin/env python3
import os
import shlex
import sys
import tempfile
import sh
def _get_commits(fname):
# Ask for the commit and the timestamp
# .. the timestamp doesn't guarantee ordering, but good enough
for c in sh.git('log', '--follow', '--pretty=%ct %h', fname,
_tty_out=False, _iter=True):
c = c.strip().split()
yield int(c[0]), c[1]
def git_log_follow_multi(filenames):
if len(filenames) == 0:
print("Specify at least one file to log")
elif len(filenames) <= 1:
os.system('git log --follow -p %s' % filenames[0])
else:
# Use git log to generate lists of commits for each file, sort
commits = []
for fname in filenames:
commits += _get_commits(fname)
# Sort the lists (python's sort is stable)
commits.sort(reverse=True)
# Uniquify (http://www.peterbe.com/plog/uniqifiers-benchmark)
seen = set()
seen_add = seen.add
commits = [c for c in commits if not (c in seen or seen_add(c))]
# Finally, display them
tname = None
try:
file_list = ' '.join(shlex.quote(fname) for fname in filenames)
with tempfile.NamedTemporaryFile(mode='w', delete=False) as fp:
tname = fp.name
for _, commit in commits:
fp.write('git log -p -1 --color %s %s\n' % (commit, file_list))
# Use os.system to make our lives easier
os.system('bash %s | less -FRX' % tname)
finally:
if tname:
os.unlink(tname)
if __name__ == '__main__':
git_log_follow_multi(sys.argv[1:])
Now, this script doesn't exactly meet your needs since it takes a list of files, but you can execute it using a glob and it'll do what you're looking for.
./script.py src/*
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With