Checking out old files WITH original create/modified timestamps

Solution 1:

YES, metastore or git-cache-meta can store such (meta-)information! Git by itself, without third-party tools, can't. Metastore or git-cache-meta can store any file metadata for a file.

That is by design, as metastore or git-cache-meta are intended for that very purpose, as well as supporting backup utilities and synchronization tools.

Solution 2:

I believe that the only timestamps recorded in the Git database are the author and commit timestamps. I don't see an option for Git to modify the file's timestamp to match the most recent commit, and it makes sense that this wouldn't be the default behavior (because if it were, Makefiles wouldn't work correctly).

You could write a script to set the modification date of your files to the the time of the most recent commit. It might look something like this:

# No arguments? Recursively list all git-controlled files in $PWD and start over
if [ $# = 0 ]; then
  git ls-files -z |xargs -0 sh "$0"
  exit $?
fi

for file in "$@"; do
  time="$(git log --pretty=format:%cd -n 1 \
                  --date=format:%Y%m%d%H%M.%S --date-order -- "$file")"
  if [ -z "$time" ]; then
    echo "ERROR: skipping '$file' -- no git log found" >&2
    continue
  fi
  touch -m -t "$time" "$file"
done

This accepts specific files as arguments or else updates each git-controlled file in the current directory or its children. This is done in a manner that permits spaces and even line breaks in filenames since git ls-files -z outputs a null-terminated file list and xargs -0 parses null-terminated lists into arguments.

This will take a while if you have a lot of files.

Solution 3:

NO, Git simply does not store such (meta-)information, unless you use third-party tools like metastore or git-cache-meta. The only timestamp that get stored is the time a patch/change was created (author time), and the time the commit was created (committer time).

That is by design, as Git is a version control system, not a backup utility or synchronization tool.

Solution 4:

UPDATE: TL;DR: Git itself does not save original times, but some solutions circumvent this by various methods. git-restore-mtime is one of them.

Ubuntu and Debian: sudo apt install git-restore-mtime Fedora, Red Hat Enterprise Linux (RHEL), and CentOS: sudo yum install git-tools

See my other answer for more details.

Full disclaimer: I'm the author of git-tools


This Python script may help: for each file, it applies the timestamp of the most recent commit where the file was modified:

  • Core functionality, with --help, debug messages. Can be run anywhere within the work tree
  • Full-fledged beast, with lots of options. Supports any repository layout.

Below is a really bare-bones version of the script. For actual usage I strongly suggest one of the more robust versions above:

#!/usr/bin/env python
# Bare-bones version. The current directory must be top-level of work tree.
# Usage: git-restore-mtime-bare [pathspecs...]
# By default update all files
# Example: to only update only the README and files in ./doc:
# git-restore-mtime-bare README doc

import subprocess, shlex
import sys, os.path

filelist = set()
for path in (sys.argv[1:] or [os.path.curdir]):
    if os.path.isfile(path) or os.path.islink(path):
        filelist.add(os.path.relpath(path))
    elif os.path.isdir(path):
        for root, subdirs, files in os.walk(path):
            if '.git' in subdirs:
                subdirs.remove('.git')
            for file in files:
                filelist.add(os.path.relpath(os.path.join(root, file)))

mtime = 0
gitobj = subprocess.Popen(shlex.split('git whatchanged --pretty=%at'),
                          stdout=subprocess.PIPE)
for line in gitobj.stdout:
    line = line.strip()
    if not line: continue

    if line.startswith(':'):
        file = line.split('\t')[-1]
        if file in filelist:
            filelist.remove(file)
            #print mtime, file
            os.utime(file, (mtime, mtime))
    else:
        mtime = long(line)

    # All files done?
    if not filelist:
        break

All versions parse the full log generated by a single git whatchanged command, which is hundreds of times faster than lopping for each file. It is under four seconds for Git (24,000 commits, 2,500 files) and less than one minute for the Linux kernel (40,000 files and 300,000 commits).

Solution 5:

This did the trick for me on Ubuntu (which lacks OS X's "-j" flag on date(1)):

for FILE in $(git ls-files)
do
    TIME=$(git log --pretty=format:%cd -n 1 --date=iso $FILE)
    TIME2=`echo $TIME | sed 's/-//g;s/ //;s/://;s/:/\./;s/ .*//'`
    touch -m -t $TIME2 $FILE
done