Git克隆更改文件修改时间

66

当我使用“git clone ...”命令克隆Git存储库时,本地存储库中的所有克隆文件都具有相同的修改时间,日期和时间与发出git clone命令时相同。

是否有一种方法可以克隆远程Git存储库,并为每个文件使用实际修改时间?


4
你可以通过 git log -n1 -- file 命令获取文件最后修改的时间;这也是 git 的作用之一。 - Amadan
9
我不太理解“这就是Git的用途”的说法。为什么修改时间不像CVS那样被保存呢? - user3302761
4
“@turnt 这不是问题……程序可以更改它们创建的文件的修改时间,因此这是程序的选择。” - golimar
2
规范问题的候选项: Subversion的"use-commit-times"在Git中对应的是什么? (2009) 和 如何在Git中还原文件的原始创建时间和修改时间? (2010)。虽然Mecurial有“Timestamp扩展”,但也没有什么太大用处。 - Peter Mortensen
1
这个回答解决了你的问题吗?检出具有原始创建/修改时间戳的旧文件 - Ivan Baldo
显示剩余2条评论
10个回答

41

5
但它可以在远程端保存本地时间。当文件基于其修改时间编译时,我该如何解决构建问题? - user3302761
7
好的,感谢您的回复和建议。我看到了讨论,但是认为不保存修改时间的论点并不充分,因为Git是一个版本控制系统。我使用CVS已经多年了,它也有这个功能,并且并不会影响它的使用。实际上,简单的ls -ltr命令可以显示从CVS存储库检出的修改文件的顺序。 - user3302761
3
构建工具依赖于文件的修改时间实际上是不将此作为元数据存储的原因。如果将修改时间更新为提交时间,那么在检出旧提交后,必须开始一个干净的构建,因为文件会被认为比相应的派生文件更旧,并且不会导致重新构建它们。(假设构建系统依赖于修改时间。) - Magnus Bäck
9
它可以保存协调世界时,是吗? - user626528
4
@VonC,你永远不能保证你的时间是准确的。但这并不意味着放弃使用时间。请注意修改内容以使其更通俗易懂,但不要改变原意。 - user626528
显示剩余7条评论

37
您可以获取Git存储库中所有文件的最后修改日期(最后提交时间)。请参见如何检索Git存储库中所有文件的最后修改日期
然后使用touch命令更改修改日期:
git ls-tree -r --name-only HEAD | while read filename; do
  unixtime=$(git log -1 --format="%at" -- "${filename}")
  touchtime=$(date -d @$unixtime +'%Y%m%d%H%M.%S')
  touch -t ${touchtime} "${filename}"
done

同时请参阅我在此处的要点


1
太棒了!运行得非常好。对我们来说,这非常关键,因为它可以加速我们基于Makefile的构建过程。 - Erik Osterman
1
这就是答案。除了一个变化,在文件名包含空格的情况下,你应该在$filename周围添加引号。 - P. T.
1
这怎么能算是答案呢?您说:“您可以检索Git存储库中所有文件的上次修改日期(最后提交时间)” <-- 那是提交的日期和时间。那不是每个文件的“上次修改日期”。就我所知,那是涉及添加该文件的提交的日期/时间。因此,如果一堆文件都在同一次提交中添加,则即使它们相隔几天、几个月、几年或几个小时,您的脚本也会为它们提供相同的日期/时间。 - barlop
1
如果有人在一个目录中进行了第一次提交,该目录包含在不同时间编写的一些小型Ruby脚本,并且他们进行了提交,进行了一些更改,再次提交并将其推送到存储库,然后他们从另一台计算机上进行git克隆,那么所有文件都具有相同的日期。然后他们运行您的脚本,他们只会得到一堆带有一个日期/时间戳记的文件和一堆带有另一个日期/时间戳记的文件,仅针对提交日期,这不是文件的最后修改日期/时间。 - barlop
2
@barlop git历史记录并不总是实际编辑的历史记录,它可以通过修改和rebase -i进行修改。这是作者选择呈现的逻辑历史记录,在其中提交是对一组文件的逻辑上原子、同时的更改。如果您想记录“file1在file2之前更改”,那么这些之间必须存在一个点——这些必须是单独的提交。 - Beni Cherniavsky-Paskin
显示剩余2条评论

12

有另一种重置 mtime 的选项是git-restore-mtime

sudo apt install git-restore-mtime # Debian/Ubuntu example
git clone <myurl>
cd <mydir>
git restore-mtime

8
这个Linux一行命令可以解决所有文件(不包括文件夹,只有文件)的问题——它还可以解决文件名中带有空格的文件的问题:
git ls-files -z | xargs -0 -n1 -I{} -- git log -1 --format="%ai {}" {} | perl -ne 'chomp;next if(/'"'"'/);($d,$f)=(/(^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d(?: \+\d\d\d\d|)) (.*)/);print "d=$d f=$f\n"; `touch -d "$d" '"'"'$f'"'"'`;'

1
非常非常好。酷!!! 就我所看到的,一些文件可能尚未使用此解决方案进行转换。例如:1.包含字符'(39 0027' APOSTROPHE)的文件,2.存储库根目录中的文件,3.包含(也可以是)的文件。也许您可以找时间查看这些特定情况? - user7468395
1
这是从Git仓库检索,所以只会提供提交日期。而不是实际文件的最后修改日期/时间。 - barlop

6

以下是我认为更易于理解的user11882487的答案的简短版本:

git ls-files | xargs -I{} git log -1 --date=format:%Y%m%d%H%M.%S --format='touch -t %ad "{}"' "{}" | $SHELL

工作无误,谢谢! - undefined

2

在一行代码列表中添加...

for f in $(git ls-files) ; do touch -d $(git log -1 --format='%aI' "$f") "$f" ; done

1
运行log -1一次只能处理一个文件,这让我感到很烦。因此,我编写了这个程序可以一次性处理所有文件。
( # don't alter any modified-file stamps:
  git diff --name-status --no-find-copies --no-renames | awk '$1="D"' FS=$'\t' OFS=$'\t'
  git log --pretty=%cI --first-parent --name-status -m --no-find-copies --no-renames
) | awk ' NF==1 { date=$1 }
          NF<2 || seen[$2]++ { next }
          $1!="D" { print "touch -d",date,$2 }' FS=$'\t'

这个命令可以在十秒钟内完成 Linux 历史记录的操作(将所有的 touch 命令通过 shell 进行管道传输需要一分钟)。

这是破坏二分法等操作的好方法。我属于那些认为不要试图超负荷使用文件系统时间戳的阵营,坚持这样做的人显然必须经过艰苦的学习,但我可以看出,在某些工作流中,这可能真的不会对你造成伤害。

总之,千万不要盲目地这样做。


你能让你的回答更加自包含吗?例如,它是否遵循/操作git ls-files的输出(而不是使用xargs)?“每个文件运行一次log -1”这个答案指的是哪些答案(有四个答案都有“log -1”)?(使用链接到答案,因为用户名可能随时更改。) - Peter Mortensen
@PeterMortensen 它会按原样打印触摸命令,不需要添加任何内容。将它们通过 shell 管道传递,我认为“将所有触摸命令通过 shell 管道传递”这一提法已经明确说明了。任何运行 log -1 的答案必然会对每个文件运行一次,我的反对是针对这种方法的。 - jthill

1

这适用于以前多个答案中的解决方案:

使用%at格式,然后使用touch -d \@$epochdelta,以避免日期时间转换问题。


1

在Python中实现这一点比其他选项要简单,因为os.utime接受git log命令输出的Unix时间戳。这个示例使用了GitPython,但也可以使用subprocess.run调用git log来实现。

import git
from os import utime
from pathlib import Path

repo_path = "my_repo"
repo = git.Repo(repo_path)

for n in repo.tree().list_traverse():
    filepath = Path(repo.working_dir) / n.path
    unixtime = repo.git.log(
        "-1", "--format='%at'", "--", n.path
    ).strip("'")
    if not unixtime.isnumeric():
        raise ValueError(
            f"git log gave non-numeric timestamp {unixtime} for {n.path}"
        )
    utime(filepath, times=(int(unixtime), int(unixtime)))

这与此答案中的git restore-mtime命令和最高评分答案中的脚本结果相匹配。

如果您在克隆后立即执行此操作,则可以重用传递给git.Repo.clone_fromto_path参数,而不是访问Repo对象上的working_dir属性。


0
要在Windows上获取带有修改日期的文件列表,您可以使用以下命令(在PowerShell上有效)。
git ls-tree -r --name-only HEAD | ForEach-Object { "$(git log -1 --format="%ai" -- "$_")`t$_" } | sort

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接