使用原始创建/修改的时间戳签出旧文件


Answers:


45

我相信,Git数据库中记录的唯一时间戳是作者和提交时间戳。我看不到Git修改文件时间戳以匹配最新提交的选项,并且这不是默认行为是有道理的(因为如果这样,Makefile将无法正常工作)。

您可以编写脚本来将文件的修改日期设置为最近一次提交的时间。它可能看起来像这样:

IFS="
"
for FILE in $(git ls-files)
do
    TIME=$(git log --pretty=format:%cd -n 1 --date=iso -- "$FILE")
    TIME=$(date -j -f '%Y-%m-%d %H:%M:%S %z' "$TIME" +%Y%m%d%H%M.%S)
    touch -m -t "$TIME" "$FILE"
done

10
此代码段存在几个问题:1-如果文件名中有空格,则失败;2-对于包含数千个文件的项目可能会失败;3-对于具有数千个提交(即使文件很少)的中型项目,性能绝对是很惨的
MestreLion 2012年

10
+1可能不适用于所有情况,但这是一个很好的简单答案。
qwerty9967 2013年

5
OP的问题不是如何保留原始文件修改的时间戳,而不是附加到文件的提交时间戳吗?
英国电信

15
围绕Make设计VCS是短视的。我认为这是Git的错。因此,这不是默认行为真的没有意义。使文件运行在文件内容上,而不是时间戳上。散列文件并查看散列是否与您构建的内容匹配会更强大。
英国电信

4
我同意英国电信和您的部分评论Dietrich。BT对OP的意义在于,您的回答实际上并不允许保留文件的原始时间。而是将其替换为原始结帐时间。不一样的事情...所以,我想他清楚地说您的帖子包含事实错误。正如您所指出的,我可以看到不存储时间戳的决定是从哪里来的。我也认为BT有点在意这种说法。我再次同意BT,这完全不是完全不能做到的充分理由。其他所有VCS都可以做到。
cregox

56

是的,metastore或git-cache-meta可以存储此类(元)信息!没有第三方工具,Git本身无法做到。Metastoregit-cache-meta可以存储文件的任何文件元数据。

这是设计使然,因为metastore或git-cache-meta正是用于该目的,并支持备份实用程序和同步工具。

(对不起,Jakub的回答有点有趣)


8
您甚至模仿了他的全能!如果您也使用粗体,我敢肯定您会获得更多的赞誉。;-)
Michael Scheper 2014年

1
所以我感到有点沮丧,主要是因为这两个工具(在研究了一些之后)在macOS上以惊人的方式表现出了优势。它们在Linux之外是完全不可移植的。git-cache-meta依赖于GNUfind-printf扩展,而且我几乎可以肯定,使metastore(作为C项目)在使可移植性方面需要做更多的工作。相当不幸。如果发现这种情况发生了变化,我将在此发布。
史蒂文·卢

40

,除非您使用诸如metastore之类的第三方工具,否则Git根本不会存储此类(元)信息或git-cache-meta之。保存的唯一时间戳是创建补丁/更改的时间(作者时间)和创建提交的时间(提交者时间)。

这是设计使然,因为Git是版本控制系统,而不是备份实用程序或同步工具。


是否有用于win32的metastore构建?还是应该为Windows重新创建脚本/钩子?Franklt,我不需要其他属性,只需mtime

7
我认为您的答案实际上是“是的!Metastore或git-cache-meta可以为您做到这一点!” 我猜这是失败派和乐观派服装之间的区别。
英国电信

2
另外,我听说,集市善变也是“版本控制系统”,它办店的元信息。这样没有什么错
cregox

说明性:Git为每个文件保留两个时间戳:作者日期(我认为这是Jakub所说的“时间补丁”)和提交者日期。前者是文件首次提交的时间,后者是文件最近提交的时间。
Michael Scheper

4
“这是设计使然,因为Git是版本控制系统,而不是备份实用程序或同步工具。” 不是必然的:忽略元数据(特别是日期,与版本密切相关的日期)与作为VCS或备份工具无关。此外,每个VCS与备份工具在功能上都有很大的内在重叠:它们都在努力保留重要的过去状态。最后,即使是Git,即使是GCS,它也不会忽略所有元数据(例如,它跟踪执行位)。但是,它仍然设计使然的,原因有所不同:Git专门关注内容。
Sz。

13

更新:TL; DR:git本身不能保存原始时间,但是某些解决方案可以通过各种方法来避免这种情况。git-restore-mtime是其中之一:

https://github.com/MestreLion/git-tools/

Ubuntu / Debian:sudo apt install git-restore-mtime
Fedora / RHEL / CentOS:sudo yum install git-tools

查看我的其他答案以获取更多详细信息

完全免责声明:我是《 git-tools


这个python脚本可能会有所帮助:对于每个文件,在文件被修改时应用最新提交的时间戳:

  • 核心功能,带有--help,调试消息。可以在工作树中的任何地方运行
  • 成熟的野兽,有很多选择。支持任何存储库布局。

下面是该脚本的一个非常简朴的版本。对于实际使用,我强烈建议您使用上述更强大的版本之一:

#!/usr/bin/env python
# Bare-bones version. Current dir 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

所有版本都解析由单个git whatchanged命令生成的完整日志,这比对每个文件进行记录要快数百倍。git不到4秒(24,000次提交,2,500个文件),Linux内核少于1分钟(40,000个文件,300,000次提交)


2
您的其他类似答案远胜于此!
cregox

$ python ./git-restore-mtime Traceback (most recent call last): File "./git-restore-mtime", line 122, in <module> 'git rev-parse --show-toplevel --git-dir')).split('\n')[:2] TypeError: Type str doesn't support the buffer API您介意告诉我们需要什么版本的Python吗?我正在使用3.3.3
Rolf

@Cawas:谢谢...我想。但是,两个答案中的代码都是相同的,所以我不确定为什么您认为另一个更好。唯一的区别是有关git的一些抱怨。这与该问题有些相关,但与这一问题无关。
MestreLion 2014年

1
@Rolf:我使用的是Python 2.7,似乎代码需要在Python 3中进行一些调整,谢谢指出。原因是:str在Python 2中等效bytestring于Python 3,而str在Python 3中等效unicode于Python2。您能否在github.com/MestreLion/git-tools/issues中报告此问题?
MestreLion 2014年

不只是“咆哮”。在这里,您还可以更详细地解释代码的作用,从而使代码更加清晰。
cregox

6

他在ubuntu上为我骗了(在日期(1)上缺少OSX的“ -j”标志)

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 

4

我已经使用git和file时间戳进行了一段时间的冲突。

测试了您的一些想法,并制作了我自己的巨大且前身/ ram繁重的脚本,直到我(在某些git wiki上)在perl中找到了一个脚本,该脚本几乎可以完成我想要的工作。 https://git.wiki.kernel.org/index.php/ExampleScripts

我想要的是能够根据提交日期保留对文件的最后修改。

因此,在进行一些调整后,脚本可以在2-3分钟内更改200k文件的创建和修改日期。

#!/usr/bin/perl
my %attributions;
my $remaining = 0;

open IN, "git ls-tree -r --full-name HEAD |" or die;
while (<IN>) {
    if (/^\S+\s+blob \S+\s+(\S+)$/) {
        $attributions{$1} = -1;
    }
}
close IN;

$remaining = (keys %attributions) + 1;
print "Number of files: $remaining\n";
open IN, "git log -r --root --raw --no-abbrev --date=raw --pretty=format:%h~%cd~ |" or die;
while (<IN>) {
    if (/^([^:~]+)~([^~]+)~$/) {
        ($commit, $date) = ($1, $2);
    } elsif (/^:\S+\s+1\S+\s+\S+\s+\S+\s+\S\s+(.*)$/) {
        if ($attributions{$1} == -1) {
            $attributions{$1} = "$date";
            $remaining--;

            utime $date, $date, $1;
            if ($remaining % 1000 == 0) {               
                print "$remaining\n";
            }
            if ($remaining <= 0) {
                break;
            }
        }
    }
}
close IN;

假设您的存储库中没有超过10k个文件,这将需要几秒钟的时间来执行,因此您可以将其挂钩到checkout,pull或其他git基本挂钩。


2

这是我的解决方案,考虑了包含空格的路径:

#! /bin/bash

IFS=$'\n'
list_of_files=($(git ls-files | sort))
unset IFS

for file in "${list_of_files[@]}"; do
  file_name=$(echo $file)

  ## When you collect the timestamps:
  TIME=$(date -r "$file_name" -Ins)

  ## When you want to recover back the timestamps:
  touch -m -d $TIME "$file_name"
done

请注意,这不会花费git log报告的时间,而是系统报告的时间。如果您需要自提交文件以来的时间,请使用git log解决方案,而不是date -r


2

本机git没有此功能,但可以通过挂钩脚本或第三方工具来实现。

我试过了metastore。它的速度非常快,但是我不喜欢安装的需求,并且元数据不是以纯文本格式存储的。git-cache-meta这是我尝试过的简单工具,但是对于大型存储库而言,它的运行速度非常慢(对于具有成千上万个文件的存储库,需要花费数分钟来更新元数据文件),并且可能存在跨平台兼容性问题。setgitperms其他方法也有我不喜欢的缺点。

最后,我为此工作制作了一个钩子脚本:git-store-meta。它具有很小的依赖性(* nix shellsortperlgit是git所必需的,并且可选地chownchgrpand touch),因此对于可以运行git的平台,不需要额外安装,性能令人满意(对于具有成千上万个回购协议的仓库的文件,更新元数据文件所需的时间不到10秒;尽管创建时间更长),以纯文本格式保存数据,并且可以自定义“保存”或“加载”的元数据。

对我来说效果很好。如果您对metastore,git-cache-meta和其他方法不满意,请尝试此操作。


2

我希望您欣赏它的简单性:

# getcheckin - Retrieve the last committed checkin date and time for
#              each of the files in the git project.  After a "pull"
#              of the project, you can update the timestamp on the
#              pulled files to match that date/time.  There are many
#              that believe that this is not a good idea, but
#              I found it useful to get the right source file dates
#
#              NOTE: This script produces commands suitable for
#                    piping into BASH or other shell
# License: Creative Commons Attribution 3.0 United States
# (CC by 3.0 US)

##########
# walk back to the project parent or the relative pathnames don't make
# sense
##########
while [ ! -d ./.git ]
do
    cd ..
done
echo "cd $(pwd)"
##########
# Note that the date format is ISO so that touch will work
##########
git ls-tree -r --full-tree HEAD |\
    sed -e "s/.*\t//" | while read filename; do
    echo "touch --date=\"$(git log -1 --date=iso --format="%ad" -- "$filename")\" -m $filename" 
done

(仅供参考,标头注释中有一个意想不到的双重否定,您也可能希望在原始注释中加以修正:“许多人不相信这不是一个好主意。”)
Sz。


1

在我(和其他人)对OP的解释中,这是否意味着提交时间还是其他方面存在一些歧义,但是假设这意味着提交时间,那么这个简单的单行代码就可以在linux中工作(基于Dietrich Epp的答案片段) ):

git ls-files | xargs -I{} bash -c 'touch "{}" --date=@$(git log -n1 --pretty=format:%ct -- "{}")'

但是,有更多更复杂的答案(包括git hooks)从评论到由cregox提出的原始问题相关联。


大声笑这在我的结帐中转储了大量文件--date=@foo
mxcl

0

使用GNU工具。

s=$(git ls-files  | wc -l); 
git ls-files -z  |
 xargs -0 -I{} -n1 bash -c \
"git log --date=format:%Y%m%d%H%M.%S '--pretty=format:touch -m -t %cd \"{}\"%n' -n1 -- {}"|
 pv -l -s$s |
 parallel -n1 -j8

 967  0:00:05 [ 171 /s] [=====================================>  ] 16% 

$ git --version ; xargs --version | sed 1q ; ls --version | sed 1q;
  parallel --version  | sed 1q;  pv --version | sed 1q; sh --version | sed 1q 
git version 2.13.0
xargs (GNU findutils) 4.6.0
ls (GNU coreutils) 8.25
GNU parallel 20150522
pv 1.6.0 - Copyright 2015 Andrew Wood <andrew.wood@ivarch.com>
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

并行似乎没有做太多,可能是fs瓶颈。因人而异
ЯрославРахматуллин

0

在CentOS 7中,您/usr/share/doc/rsync-*/support/git-set-file-times与Debian(及其衍生版本)中的脚本相同/usr/share/doc/rsync/scripts/git-set-file-times.gz,原始脚本来自Eric Wong,位于https://yhbt.net/git-set-file-times

它的工作速度比这里提到的其他示例要快,您可能会发现将其安装在Linux发行版中更为方便。


0

这是我的。

比其他一些更快,因为我不会为找到的每个文件调用“获取日志”;而是一次调用“ git log”并将其输出转换为触摸命令。

在某些情况下,一次提交中列出的文件太多,无法容纳在单个shell命令缓冲区中。运行“ getconf ARG_MAX”以查看命令的最大长度(以字节为单位)-在我的debian安装中为2MB,足够了。

# set file last modification time to last commit of file
git log --reverse --date=iso --name-only | \
  grep -vE "^(commit |Merge:|Author:|    |^$)" | \
  grep -B 1 "^[^D][^a][^t][^e][^:][^ ]" | \
  grep -v "^\-\-" | \
  sed "s|^\(.*\)$|\"\1\"|;s|^\"Date: *\(.*\)\"$|~touch -c -m -d'\1'|" | \
  tr '~\n' '\n ' | \
  sh -

按行描述:

  • 最早提交的列表和文件名列表
  • 过滤掉不需要的提交/合并/作者行
  • 滤除以双破折号开头的行
  • sed(stream-edit)命令a)在行前加/双引号,并b)用〜touch -c -m -d替换“ Date :。”。 (touch命令选项为-c =如果不存在则不创建,-m =更改文件修改时间,-d =使用提供的日期/时间)
  • 将tilda(〜)和newline(\ n)字符分别转换为换行符和空格
  • 将生成的文本行流传输到外壳中。

就速度而言,它需要5秒1700提交700个目录中的6500个文件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.