在 Git 中恢复文件的修改时间

我理解 每次更改文件时更新修改时间的默认 Git 行为,但有时候我想恢复文件的原始修改时间。

有什么办法能让 Git 这么做吗?

(举个例子,当我在一个大型项目中工作时,我对 configure.ac做了一些修改,发现自动化工具在我的系统上无法工作,我想恢复 configure.ac的原始内容和修改时间,这样 make就不会试图用我坏掉的自动化工具更新 configure

34983 次浏览

Git 不会这样做,就像你的常见问题解答链接所说的那样,它会使用像 制造这样基于时间戳的依赖分析工具来中断。

想象一下,如果将旧时间戳应用于从“旧”提交中签出的文件,会发生什么情况:

  • Make 从一个干净的目录可以正常工作
  • 签出一个旧的分支/标记/提交(现在这些文件的时间戳比构建产品的时间戳还要早!)
  • Make 现在什么也不做,因为所有构建产品都比它们的依赖项更新

但是,如果您真的想要它,所有的信息都在那里。您可以编写自己的工具来完成它。

在您的情况下,只需使用类似 touch -r configure configure.ac的东西来重置仅 configure.ac的修改时间,(或使用 touch configure及时提前配置)。


实际上,如果你想练习阅读 C 代码,这是一个简单的“读者练习”。更改时间戳的函数是 utimeutimes。在代码中搜索这些函数的用法(提示: Git Git克隆中的 git grep utime)。如果有一些用途,请分析代码路径,找出它何时更新时间戳。

我相信“正确”的修复方法是实际比较每个输入文件的 SHA-1散列值,看看它是否与上次构建相比有所改变。

工作量很大。然而,我已经开始了一个项目,试图创建一个概念验证(仍处于非常早期的阶段)。除了确定正确的构建步骤之外,它还被设计为创建一个输入文件的审计列表,以便以后进行取证。

看看 废物建筑——它是基于几年前我用 颠覆做的类似的东西。

将文件列表的修改时间恢复到上次提交的作者日期

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

但是,它不会递归地更改目录。

如果您想更改整个工作树,例如在新的克隆或结帐之后,您可以尝试

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]/;'

注意: 我在内建/克隆中搜索了 utime,没有找到匹配的。

我编写了一个小工具,它允许您在使用 Git 进行合并或签出之后恢复目录中文件的修改时间。

Https://bitbucket.org/chabernac/metadatarestore/wiki/home

在 Git 中使用该工具作为提交、签出或合并的钩子。有关 Git 挂钩的信息,请参见 自定义 Git-Git-Hooks。您可以在项目的 .git/hooks目录中找到 Git 挂钩的示例。

下面的 shell 脚本应该适用于任何与 POSIX 兼容的系统,以设置所有跟踪文件(和目录)的修改和访问时间戳。我能确定的唯一缺点是它相当慢,但是对我的用例来说这很好(在生成发布存档时设置正确的日期)。

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

这个工具应该可以。它将时间更新为作者时间,将时间更新为提交者时间。它可以作为结账钩。

运行 DEBUG=1让它告诉你它到底在做什么。

还要注意,它没有使用模块,只使用基本的 Perl,因此应该在任何地方运行。

#!/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;

我们在工作中遇到了同样的问题,并且成功地使用了 Danny Lin 编写的 Git-store-meta Perl 脚本。

这绝对解决了你问题中提到的问题。

Git 工具 :

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

这占用了 Stifant 求婚了的大部分功能,但是在实现类似的脚本时,我只是添加了一个并行特性。

在我的例子(1000个文件)中,我从60秒到15秒并行执行这个操作。

#!/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

您可以通过更改以下行来调整允许的并行作业数:

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

下面将文件的时间戳设置为我的最后一次提交时间:

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)'