Restore a file's modification time in Git

Git

Git Problem Overview


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 modification 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.)

Git Solutions


Solution 1 - Git

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 @0git 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.

Solution 2 - Git

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.

Solution 3 - Git

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

Solution 4 - Git

Git Tools:

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

Solution 5 - Git

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.

Solution 6 - Git

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 <[email protected]>

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;

Solution 7 - Git

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.

Solution 8 - Git

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

Solution 9 - Git

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrampionView Question on Stackoverflow
Solution 1 - GitTallOneView Answer on Stackoverflow
Solution 2 - GitChris JohnsenView Answer on Stackoverflow
Solution 3 - GitstefanctView Answer on Stackoverflow
Solution 4 - GitAndrew MurphyView Answer on Stackoverflow
Solution 5 - GitAlec the GeekView Answer on Stackoverflow
Solution 6 - GittchristView Answer on Stackoverflow
Solution 7 - GitgslView Answer on Stackoverflow
Solution 8 - GitPilouPiliView Answer on Stackoverflow
Solution 9 - GitGuy ChauliacView Answer on Stackoverflow