开发者

Restore a file's modification time in Git

I understand the default Git behaviour of updating the modification time every time it changes a file, but there are times when I want to restore a file's original modificati开发者_JAVA技巧on time.

Is there a way I can tell Git to do this?

(As an example, when working on a large project, I made some changes to configure.ac, found out that autotools doesn't work on my system, and wanted to restore configure.ac's to its original contents and modification time so that make doesn't try to update configure with my broken autotools.)


Restore the modificaton time of a list of files to the author date of the their last commit with

gitmtim(){ local f;for f;do touch -d @0`git log --pretty=%at -n1 -- "$f"` "$f"; done;}; gitmtim configure.ac

It will not change directories recursively, though.

If you want to change a whole working tree, e.g. after a fresh clone or checkout, you may try

git log --pretty=%at --name-status --reverse | perl -ane '($x,$f)=@F;next if !$x;$t=$x,next if !defined($f)||$s{$f};$s{$f}=utime($t,$t,$f),next if $x=~/[AM]/;'

NB: I grepped for utime in builtin/clone.c and got no matches.


Git does not do this. Like your linked FAQ says, it would break using timestamp-based dependency analysis tools like make.

Think about what would happen if old timestamps were applied to files checked out from ‘old’ commits:

  • make from a clean directory works fine
  • checkout an older branch/tag/commit (the files would have timestamps older than the build products now!)
  • make now does nothing, because all the build products are newer than their dependencies

But, if you really want it, all the information is there. You could write your own tool to do it.

In your case, just use something like touch -r configure configure.ac to reset the modification time of only configure.ac, (or bring configure forward in time with touch configure).


Actually, this is an easy “exercise for the reader” if you want to practice reading C code. The function that changes timestamps is utime or utimes. Search the code for uses of those functions (hint: git grep utime in a git.git clone). If there are some uses, analyze the code paths to find out when it updates timestamps.


The following shell script should work on any POSIX-compatible system to set the modification and access timestamp of all tracked files (and directories). The only downside I could determine yet is that it is quite slow but that's fine for my use case (setting the right dates when producing release archives).

rev=HEAD
for f in $(git ls-tree -r -t --full-name --name-only "$rev") ; do
    touch -d $(git log --pretty=format:%cI -1 "$rev" -- "$f") "$f";
done


Git Tools:

sudo apt install git-restore-mtime
cd [repo]
git restore-mtime


I believe the 'proper' fix is to actually compare the SHA-1 hash value of each input file to see if it's changed from the last build.

This is a lot of work. However, I have started a project to try and create a proof of concept (still very early stage). As well as identifying the correct build steps, it's also designed to create an audit list of input files for later forensics.

See Git building -- it's based on something similar I did a few years ago with Subversion.


This tool should do the trick. It updates mtimes to the author time and the atimes to the committer time. It would work as a checkout hook.

Run with DEBUG=1 to get it to tell you exactly what it's doing.

Notice also that it uses no modules, just basic Perl, so should run anywhere.

#!/usr/bin/perl

# git-utimes: update file times to last commit on them
# Tom Christiansen <tchrist@perl.com>

use v5.10;      # for pipe open on a list
use strict;
use warnings;
use constant DEBUG => !!$ENV{DEBUG};

my @gitlog = (
    qw[git log --name-only],
    qq[--format=format:"%s" %ct %at],
    @ARGV,
);

open(GITLOG, "-|", @gitlog)             || die "$0: Cannot open pipe from `@gitlog`: $!\n";

our $Oops = 0;
our %Seen;
$/ = "";

while (<GITLOG>) {
    next if /^"Merge branch/;

    s/^"(.*)" //                        || die;
    my $msg = $1;

    s/^(\d+) (\d+)\n//gm                || die;
    my @times = ($1, $2);               # last one, others are merges

    for my $file (split /\R/) {         # I'll kill you if you put vertical whitespace in our paths
        next if $Seen{$file}++;
        next if !-f $file;              # no longer here

        printf "atime=%s mtime=%s %s -- %s\n",
                (map { scalar localtime $_ } @times),
                $file, $msg,
                                        if DEBUG;

        unless (utime @times, $file) {
            print STDERR "$0: Couldn't reset utimes on $file: $!\n";
            $Oops++;
        }
    }

}
exit $Oops;


We had the same issue at work and have successfully been using the git-store-meta Perl script by Danny Lin.

It definitely solved the problem indicated in your question.


This takes most of what stefanct proposed, but while implementing a similar script I just added a parallel feature.

In my case (1000 files) I went from 60 seconds to 15 seconds to do the operation doing it in parallel.

#!/bin/bash

change_date() {
      local dd=`git log -1 HEAD --pretty="%ci" -- $1`
      if [ -z "$dd" ];
      then echo "$1 is not versionned";
      else touch -d "$dd" $1;
      fi
}
#list_of_files = find .
list_of_files=`git ls-tree -r -t --full-name --name-only HEAD`

for f in $list_of_files;do
  if test "$(jobs | wc -l)" -ge 16; then
    wait
  fi
  {
    change_date  $f;
  } &
done
wait

You can adjust the number of parallel jobs allowed by changing this line:

test "$(jobs | wc -l)" -ge 16


The following sets the timestamps of the files to the last commit time for me:

git log --pretty=%at --name-status | 
perl -ane '($x,$f)=@F; $x or next; $t=$x, next if !$f; next if $s{$f} || $x!~/[AM]/; $s{$f}++; utime($t,$t,$f)'


I wrote a little tool that will allow you to restore the modification time of the files in a directory after doing a merge or checkout with Git.

https://bitbucket.org/chabernac/metadatarestore/wiki/Home

Use the tool as a hook in Git when doing a commit, checkout or merge. See 8.3 Customizing Git - Git Hooks for information about Git hooks. You can find examples of Git hooks in the .git/hooks directory of your project.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜