Alternatively, you can fix the symptoms directly by saving and restoring file timestamps. This is kinda ugly, but it was interesting to write.
Python Timestamp Save/Restore Script
#!/usr/bin/env python from optparse import OptionParser import os import subprocess import cPickle as pickle try: check_output = subprocess.check_output except AttributeError: # check_output was added in Python 2.7, so it's not always available def check_output(*args, **kwargs): kwargs['stdout'] = subprocess.PIPE proc = subprocess.Popen(*args, **kwargs) output = proc.stdout.read() retcode = proc.wait() if retcode != 0: cmd = kwargs.get('args') if cmd is None: cmd = args[0] err = subprocess.CalledProcessError(retcode, cmd) err.output = output raise err else: return output def git_cmd(*args): return check_output(['git'] + list(args), stderr=subprocess.STDOUT) def walk_git_tree(rev): """ Generates (sha1,path) pairs for all blobs (files) listed by git ls-tree. """ tree = git_cmd('ls-tree', '-r', '-z', rev).rstrip('\0') for entry in tree.split('\0'): print entry mode, type, sha1, path = entry.split() if type == 'blob': yield (sha1, path) else: print 'WARNING: Tree contains a non-blob.' def collect_timestamps(rev): timestamps = {} for sha1, path in walk_git_tree(rev): s = os.lstat(path) timestamps[path] = (sha1, s.st_mtime, s.st_atime) print sha1, s.st_mtime, s.st_atime, path return timestamps def restore_timestamps(timestamps): for path, v in timestamps.items(): if os.path.isfile(path): sha1, mtime, atime = v new_sha1 = git_cmd('hash-object', '--', path).strip() if sha1 == new_sha1: print 'Restoring', path os.utime(path, (atime, mtime)) else: print path, 'has changed (not restoring)' elif os.path.exists(path): print 'WARNING: File is no longer a file...' def main(): oparse = OptionParser() oparse.add_option('--save', action='store_const', const='save', dest='action', help='Save the timestamps of all git tracked files') oparse.add_option('--restore', action='store_const', const='restore', dest='action', help='Restore the timestamps of git tracked files whose sha1 hashes have not changed') oparse.add_option('--db', action='store', dest='database', help='Specify the path to the data file to restore/save from/to') opts, args = oparse.parse_args() if opts.action is None: oparse.error('an action (--save or --restore) must be specified') if opts.database is None: repo = git_cmd('rev-parse', '--git-dir').strip() dbpath = os.path.join(repo, 'TIMESTAMPS') print 'Using default database:', dbpath else: dbpath = opts.database rev = git_cmd('rev-parse', 'HEAD').strip() print 'Working against rev', rev if opts.action == 'save': timestamps = collect_timestamps(rev) data = (rev, timestamps) pickle.dump(data, open(dbpath, 'wb')) elif opts.action == 'restore': rev, timestamps = pickle.load(open(dbpath, 'rb')) restore_timestamps(timestamps) if __name__ == '__main__': main()
Bash Test Script
#!/bin/bash if [ -d working ]; then echo "Cowardly refusing to mangle an existing 'working' dir." exit 1 fi mkdir working cd working # create the repository/working copy git init # add a couple of files echo "File added in master:r1." > file-1 echo "File added in master:r1." > file-2 mkdir dir echo "File added in master:r1." > dir/file-3 git add file-1 file-2 dir/file-3 git commit -m "r1: add-1, add-2, add-3" git tag r1 # sleep to ensure new or changed files won't have the same timestamp echo "Listing at r1" ls --full-time sleep 5 # make a change echo "File changed in master:r2." > file-2 echo "File changed in master:r2." > dir/file-3 echo "File added in master:r2." > file-4 git add file-2 dir/file-3 file-4 git commit -m "r2: change-2, change-3, add-4" git tag r2 # sleep to ensure new or changed files won't have the same timestamp echo "Listing at r2" ls --full-time sleep 5 # create a topic branch from r1 and make some changes git checkout -b topic r1 echo "File changed in topic:r3." > file-2 echo "File changed in topic:r3." > dir/file-3 echo "File added in topic:r3." > file-5 git add file-2 dir/file-3 file-5 git commit -m "r3: change-2, change-3, add-5" git tag r3 # sleep to ensure new or changed files won't have the same timestamp echo "Listing at r3" ls --full-time sleep 5 echo "Saving timestamps" ../save-timestamps.py --save echo "Checking out master and merging" # merge branch 'topic' git checkout master git merge topic echo "File changed in topic:r3." > file-2 # restore file-2 echo "File merged in master:r4." > dir/file-3 git add file-2 dir/file-3 git commit -m "r4: Merge branch 'topic'" git tag r4 echo "Listing at r4" ls --full-time echo "Restoring timestamps" ../save-timestamps.py --restore ls --full-time
I'll leave it as an exercise for the reader to clean up the Python script to remove extraneous output and add better error checking.
(1): Git checkout-and-merge without touching working tree, and(2)Update/pull a local Git branch without checking it out?.