git的use-commit-times等效于什么?


97

我需要本地和服务器上文件的时间戳同步。这是通过Subversion实现的,方法是在配置中设置use-commit-times = true,以便每个文件的最后修改时间是在提交时。

每次克隆存储库时,我都希望文件的时间戳反映远程存储库中上次更改的时间,而不是克隆存储库的时间。

有什么办法可以用git吗?


在部署过程中,我将资产(图像,javascript文件和css文件)上传到CDN。每个文件名都附加有最后修改的时间戳。重要的是,我每次部署时都不要耗尽所有资产。(use-commit-times的另一个副作用是,我可以在本地执行此过程,并且知道我的服务器将引用相同的文件,但这并不重要。)如果不是进行git clone,我做了一个git fetch,然后从我的远程仓库中进行git reset --hard,这将对单个服务器有效,但不适用于多个服务器,因为每个服务器上的时间戳都不同。
本W

@BenW:git annex跟踪图像可能有用
jfs

您可以通过检查ID来检查更改的内容。您正在尝试使文件系统时间戳与vcs时间戳具有相同的含义。他们不是同一件事。
jthill

Answers:


25

我不确定这是否适用于DVCS(如“分布式” VCS中所述)

巨大的讨论已经在2007年进行(请参阅此主题)

Linus的一些答案并不十分喜欢这个想法。这是一个示例:

对不起。如果你看不出这是错误设定日期戳回东西,这将使一个简单的“化妆” miscompile源代码树,我不知道“错了”你说的是什么defintiion。
这是不对的。
这很傻。
而且这是完全不可行的。


(注意:小改进:签出后,不再修改最新文件的时间戳(Git 2.2.2 +,2015年1月):“ git checkout-切换分支时如何维护时间戳?”。)


长答案是:

我认为,如果这很常见,那么只使用多个存储库会更好。

一般情况下,打乱时间戳是行不通的。这只是要确保您“ make”以一种非常糟糕的方式感到困惑,并且不会进行足够的重新编译而不是重新编译太多

Git确实可以通过许多不同方式非常轻松地完成“检查另一个分支”任务。

您可以创建一些琐碎的脚本来执行以下任一操作(从琐碎的代码到更奇特的代码):

  • 只需创建一个新的仓库:
    git clone old new
    cd new
    git checkout origin/<branch>

你在那里。旧的时间戳在您的旧存储库中很好,并且您可以在新的仓库中工作(和编译),而完全不会影响旧的时间戳。

使用标志“ -n -l -s”到“ git clone”基本上可以使此瞬间。对于很多文件(例如像内核这样的大仓库),它的速度不会像切换分支那样快,但是导航工作树的第二个副本可能会非常强大。

  • 如果您想只用tar球做同样的事情
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

如果您只想要快照,那确实非常快。

  • 习惯了“ git show”,只需查看单个文件即可。
    实际上,有时这确实很有用。你只是做
    git show otherbranch:filename

在一个xterm窗口中,在另一个窗口中查看当前分支中的相同文件。特别是,这对于使用脚本编写的编辑器(即GNU emacs)来说应该是微不足道的,在这种情况下,使用该脚本编辑器中的其他分支应该可以基本上具有完整的“ dirted模式”。就我所知,emacs git模式已经提供了这样的功能(我不是emacs用户)

  • 在那个“虚拟目录”的极端例子中,至少有人在为FUSE开发git插件,即,您可以从字面上看就是显示所有分支的虚拟目录。

而且我敢肯定,上述任何一种方法都比带文件时间戳的游戏更好。

莱纳斯


5
同意 您不应将DVCS与分发系统混淆。git是DVCS,用于处理最终产品中内置的源代码。如果您要使用分配系统,则知道在哪里可以找到rsync
兰达·施瓦兹

14
嗯,我不得不相信他的论点是不可行的。是错的还是愚蠢的是另一回事。我使用时间戳对文件进行版本控制,然后将其上传到CDN,这就是为什么时间戳要反映文件的实际修改时间,而不是最后一次从存储库中提取文件的时间,这一点很重要。
本W

3
@Ben W:“ Linus的答案”并不是要在您的特定情况下说错。仅在此提醒您,DVCS不适用于这种功能(保留时间戳)。
VonC

15
@VonC:因为像Bazaar和Mercurial这样的其他现代DVCS可以很好地处理时间戳,所以我想说“ git不太适合这种功能”。如果“一个” DVCS 应该具有该功能,则值得商((我强烈认为它们确实如此)。
MestreLion

10
这不是问题的答案,而是关于在版本控制系统中执行此操作的优点的哲学讨论。如果这个人愿意,他们会问:“ git不使用提交时间作为文件的修改时间是什么原因?”
thomasfuchs 2014年

85

但是,如果您真的想在结帐时使用提交时间作为时间戳,请尝试使用此脚本并将其(作为可执行文件)放置在文件$ GIT_DIR / .git / hooks / post-checkout中:

#!/bin/sh -e

OS=${OS:-`uname`}
old_rev="$1"
new_rev="$2"

get_file_rev() {
    git rev-list -n 1 "$new_rev" "$1"
}

if   [ "$OS" = 'Linux' ]
then
    update_file_timestamp() {
        file_time=`git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1`
        touch -d "$file_time" "$1"
    }
elif [ "$OS" = 'FreeBSD' ]
then
    update_file_timestamp() {
        file_time=`date -r "$(git show --pretty=format:%at --abbrev-commit "$(get_file_rev "$1")" | head -n 1)" '+%Y%m%d%H%M.%S'`
        touch -h -t "$file_time" "$1"
    }
else
    echo "timestamp changing not implemented" >&2
    exit 1
fi

IFS=`printf '\t\n\t'`

git ls-files | while read -r file
do
    update_file_timestamp "$file"
done

但是请注意,此脚本将导致检出大型存储库的较大延迟(其中大型表示大量文件,而不是大型文件)。


55
+1是一个实际答案,而不仅仅是说“不要这样做”
DanC 2010年

4
| head -n 1应避免使用,因为它会生成一个新的进程,-n 1git rev-listgit log可用于代替。
俄勒冈州2012年

3
最好不要用`...`和阅读行for。请参阅为什么不阅读带有“ for”的行。我会去git ls-files -zwhile IFS= read -r -d ''
musiphil

2
Windows版本可以吗?
Ehryk

2
代替git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1您可以做的是git show --pretty=format:%ai -s "$(get_file_rev "$1")",它使show命令生成的数据少得多,并应减少开销。
斯科特·张伯伦

79

更新:我的解决方案现已打包到Debian / Ubuntu / Mint,Fedora,Gentoo和其他发行版中:

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

sudo apt install git-restore-mtime  # Debian/Ubuntu/Mint
yum install git-tools               # Fedora/ RHEL / CentOS
emerge dev-vcs/git-tools            # Gentoo

恕我直言,不存储时间戳(以及其他诸如权限和所有权之类的元数据)是的一限制git

莱纳斯的时间戳之所以有害是因为“混淆make”,这是la脚的

  • make clean 足以解决任何问题。

  • 仅适用于使用make(主要是C / C ++)的项目。对于诸如Python,Perl或一般文档之类的脚本,这完全没有意义。

  • 如果应用时间戳,则只会造成伤害。将它们存储在回购中不会有任何危害。--with-timestamps对于用户git checkout和朋友(clonepull等等),根据用户的判断,应用它们可能是一个简单的选择。

集市和Mercurial都存储元数据。结帐时,用户可以应用或不应用它们。但是在git中,由于原始时间戳记在存储库中甚至不可用,因此没有这样的选项。

因此,对于特定于项目子集的很小的收益(不必重新编译所有内容),git由于一般的DVCS受到损害,因此有关文件的某些信息会丢失,并且正如Linus所说,这样做是不可行的现在。伤心

也就是说,我可以提供2种方法吗?

1- http: //repo.or.cz/w/metastore.git,作者 DavidHärdeman。尝试首先要做的是:在提交时(通过预提交钩子)将元数据(不仅时间戳)存储git 存储库中,在拉动时(也通过钩子)重新应用元数据

2-我以前用于生成发行版tarball的脚本的简陋版本。正如在其他的答案中提到,这种方法是有一点不同:申请每个文件的时间戳中的最近一次提交该文件已被修改。

  • git-restore-mtime,具有很多选项,支持任何存储库布局,并且可以在Python 3上运行。

作为概念证明,以下是该脚本的一个非常简陋的版本,用于Python 2.7。对于实际使用,我强烈建议您使用上述完整版本:

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

即使对于怪物项目winegit甚至是Linux内核,性能也非常出色:

bash
# 0.27 seconds
# 5,750 log lines processed
# 62 commits evaluated
# 1,155 updated files

git
# 3.71 seconds
# 96,702 log lines processed
# 24,217 commits evaluated
# 2,495 updated files

wine
# 13.53 seconds
# 443,979 log lines processed
# 91,703 commits evaluated
# 6,005 updated files

linux kernel
# 59.11 seconds
# 1,484,567 log lines processed
# 313,164 commits evaluated
# 40,902 updated files

2
但是git 存储时间戳等。默认情况下,它只是不设置时间戳。只要看看git ls-files --debug
Ross Smith II

9
@RossSmithII:git ls-files在工作目录和索引上运行,因此这并不意味着它实际上将该信息存储存储库中。如果确实存储了,那么检索(和应用)mtime将会是微不足道的。
MestreLion

13
“ Linus时间戳的合理性是有害的,仅因为它“使makes混淆”是la脚的”-同意100%,DCVS不应该知道或关心其中包含的代码!这再次表明了尝试将为特定用例编写的工具重新利用为一般用例的陷阱。Mercurial一直是并且永远将是一个上乘的选择,因为它是经过设计而不是经过改进的。
伊恩·肯普

6
@davec不客气,很高兴它很有用。完整版本在github.com/MestreLion/git-tools上已经可以处理Windows,Python 3,非ASCII路径名等。以上脚本只是概念的可行证明,应避免在生产中使用。
MestreLion,2015年

3
您的论点是有效的。我希望有一些影响力的人对git提出增强的要求,使其具有建议的--with-timestamps选项。
weberjn

12

我接受了Giel的回答,而不是使用提交后的钩子脚本,而是将其用于我的自定义部署脚本中。

更新:我也删除了| head -n以下@eregon的建议,并增加了对其中包含空格的文件的支持:

# Adapted to use HEAD rather than the new commit ref
get_file_rev() {
    git rev-list -n 1 HEAD "$1"
}

# Same as Giel's answer above
update_file_timestamp() {
    file_time=`git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1`
    sudo touch -d "$file_time" "$1"
}

# Loop through and fix timestamps on all files in our CDN directory
old_ifs=$IFS
IFS=$'\n' # Support files with spaces in them
for file in $(git ls-files | grep "$cdn_dir")
do
    update_file_timestamp "${file}"
done
IFS=$old_ifs

感谢Daniel,这对您有所帮助
Alex Dean 2012年

该命令在使用中--abbrev-commit是多余的(commit hash不属于输出的一部分),并可以使用以下标志替换它:git show--pretty=format:%ai| head -n 1-sgit show
ElanRuusamäe16'-

1
@ DanielS.Sterling: %ai是作者日期,ISO 8601之类的格式,对于严格的 iso8601使用%aIgit-scm.com/docs/git-show
ElanRuusamäe2016年

4

我们被迫发明另一种解决方案,因为我们需要专门的修改时间而不是提交时间,并且该解决方案还必须具有可移植性(即,让python在Windows的git安装中正常工作并不是一件容易的事)并且速度很快。它类似于David Hardeman的解决方案,由于缺少文档,我决定不使用它(从存储库中我无法知道他的代码到底做什么)。

此解决方案将mtimes存储在git存储库中的文件.mtimes中,在提交时进行相应更新(选择性地选择暂存文件的mtimes),并将其应用于结帐。它甚至适用于cygwin / mingw版本的git(但您可能需要将一些文件从标准cygwin复制到git的文件夹中)

该解决方案包含3个文件:

  1. mtimestore-核心脚本,提供3个选项-a(全部保存-用于在已存在的仓库中初始化(与git相关的文件一起使用)),-s(用于保存分阶段的更改)和-r来还原它们。实际上,这有2个版本-bash版本(便携式,美观,易于阅读/修改)和c版本(混乱的版本,但速度很快,因为mingw bash非常慢,这使得无法在大型项目上使用bash解决方案)。
  2. 预提交钩
  3. 结帐后挂钩

预先提交:

#!/bin/bash
mtimestore -s
git add .mtimes

结帐后

#!/bin/bash
mtimestore -r

mtimestore-bash:

#!/bin/bash

function usage 
{
  echo "Usage: mtimestore (-a|-s|-r)"
  echo "Option  Meaning"
  echo " -a save-all - saves state of all files in a git repository"
  echo " -s save - saves mtime of all staged files of git repository"
  echo " -r restore - touches all files saved in .mtimes file"
  exit 1
}

function echodate 
{
  echo "$(stat -c %Y "$1")|$1" >> .mtimes
}

IFS=$'\n'

while getopts ":sar" optname
do
  case "$optname" in
    "s")
      echo "saving changes of staged files to file .mtimes"
      if [ -f .mtimes ]
      then
        mv .mtimes .mtimes_tmp
        pattern=".mtimes"
        for str in $(git diff --name-only --staged)
        do
          pattern="$pattern\|$str"
        done
        cat .mtimes_tmp | grep -vh "|\($pattern\)\b" >> .mtimes
      else
        echo "warning: file .mtimes does not exist - creating new"
      fi

      for str in $(git diff --name-only --staged)
      do
        echodate "$str" 
      done
      rm .mtimes_tmp 2> /dev/null
      ;;
    "a")
      echo "saving mtimes of all files to file .mtimes"
      rm .mtimes 2> /dev/null
      for str in $(git ls-files)
      do
        echodate "$str"
      done
      ;;
    "r")
      echo "restorim dates from .mtimes"
      if [ -f .mtimes ]
      then
        cat .mtimes | while read line
        do
          timestamp=$(date -d "1970-01-01 ${line%|*} sec GMT" +%Y%m%d%H%M.%S)
          touch -t $timestamp "${line##*|}"
        done
      else
        echo "warning: .mtimes not found"
      fi
      ;;
    ":")
      usage
      ;;
    *)
      usage
      ;;
esac

mtimestore-C ++

#include <time.h>
#include <utime.h>
#include <sys/stat.h>
#include <iostream>
#include <cstdlib>
#include <fstream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <ctime>
#include <map>


void changedate(int time, const char* filename)
{
  try
  {
    struct utimbuf new_times;
    struct stat foo;
    stat(filename, &foo);

    new_times.actime = foo.st_atime;
    new_times.modtime = time;
    utime(filename, &new_times);
  }
  catch(...)
  {}
}

bool parsenum(int& num, char*& ptr)
{
  num = 0;
  if(!isdigit(*ptr))
    return false;
  while(isdigit(*ptr))
  {
    num = num*10 + (int)(*ptr) - 48;
    ptr++;
  }
  return true;
}

//splits line into numeral and text part - return numeral into time and set ptr to the position where filename starts
bool parseline(const char* line, int& time, char*& ptr)
{
  if(*line == '\n' || *line == '\r')
    return false;
  time = 0;
  ptr = (char*)line;
  if( parsenum(time, ptr))
  { 
    ptr++;
    return true;
  }
  else
    return false;
}

//replace \r and \n (otherwise is interpretted as part of filename)
void trim(char* string)
{
  char* ptr = string;
  while(*ptr != '\0')
  {
    if(*ptr == '\n' || *ptr == '\r')
      *ptr = '\0';
    ptr++;
  }
}


void help()
{
  std::cout << "version: 1.4" << std::endl;
  std::cout << "usage: mtimestore <switch>" << std::endl;
  std::cout << "options:" << std::endl;
  std::cout << "  -a  saves mtimes of all git-versed files into .mtimes file (meant to be done on intialization of mtime fixes)" << std::endl;
  std::cout << "  -s  saves mtimes of modified staged files into .mtimes file(meant to be put into pre-commit hook)" << std::endl;
  std::cout << "  -r  restores mtimes from .mtimes file (that is meant to be stored in repository server-side and to be called in post-checkout hook)" << std::endl;
  std::cout << "  -h  show this help" << std::endl;
}

void load_file(const char* file, std::map<std::string,int>& mapa)
{

  std::string line;
  std::ifstream myfile (file, std::ifstream::in);

  if(myfile.is_open())
  {
      while ( myfile.good() )
      {
        getline (myfile,line);
        int time;
        char* ptr;
        if( parseline(line.c_str(), time, ptr))
        {
          if(std::string(ptr) != std::string(".mtimes"))
            mapa[std::string(ptr)] = time;
        }
      }
    myfile.close();
  }

}

void update(std::map<std::string, int>& mapa, bool all)
{
  char path[2048];
  FILE *fp;
  if(all)
    fp = popen("git ls-files", "r");
  else
    fp = popen("git diff --name-only --staged", "r");

  while(fgets(path, 2048, fp) != NULL)
  {
    trim(path);
    struct stat foo;
    int err = stat(path, &foo);
    if(std::string(path) != std::string(".mtimes"))
      mapa[std::string(path)]=foo.st_mtime;
  }
}

void write(const char * file, std::map<std::string, int>& mapa)
{
  std::ofstream outputfile;
  outputfile.open(".mtimes", std::ios::out);
  for(std::map<std::string, int>::iterator itr = mapa.begin(); itr != mapa.end(); ++itr)
  {
    if(*(itr->first.c_str()) != '\0')
    {
      outputfile << itr->second << "|" << itr->first << std::endl;   
    }
  }
  outputfile.close();
}

int main(int argc, char *argv[])
{
  if(argc >= 2 && argv[1][0] == '-')
  {
    switch(argv[1][1])
    {
      case 'r':
        {
          std::cout << "restoring modification dates" << std::endl;
          std::string line;
          std::ifstream myfile (".mtimes");
          if (myfile.is_open())
          {
            while ( myfile.good() )
            {
              getline (myfile,line);
              int time, time2;
              char* ptr;
              parseline(line.c_str(), time, ptr);
              changedate(time, ptr);
            }
            myfile.close();
          }
        }
        break;
      case 'a':
      case 's':
        {
          std::cout << "saving modification times" << std::endl;

          std::map<std::string, int> mapa;
          load_file(".mtimes", mapa);
          update(mapa, argv[1][1] == 'a');
          write(".mtimes", mapa);
        }
        break;
      default:
        help();
        return 0;
    }
  } else
  {
    help();
    return 0;
  }

  return 0;
}
  • 请注意,可以将钩子放置在template-directory中以自动放置它们

更多信息可以在这里找到 https://github.com/kareltucek/git-mtime-extension 一些过时的信息在 http://www.ktweb.cz/blog/index.php?page=page&id=116

// edit-c ++版本已更新:

  • 现在,c ++版本保持字母顺序->减少合并冲突。
  • 摆脱了丑陋的system()调用。
  • 从结帐后挂钩中删除了$ git update-index --refresh $。在乌龟git下恢复时会引起一些问题,无论如何似乎并不重要。
  • 我们的Windows软件包可以从http://ktweb.cz/blog/download/git-mtimestore-1.4.rar下载

//编辑请参阅github以获取最新版本


1
请注意,在签出后,不再修改最新文件的时间戳(Git 2.2.2 +,2015年1月):stackoverflow.com/a/28256177/6309
VonC

3

以下脚本结合了-n 1HEAD建议,可在大多数非Linux环境(例如Cygwin)中使用,并且可以在事实发生后在结帐时运行:

#!/bin/bash -e

OS=${OS:-`uname`}

get_file_rev() {
    git rev-list -n 1 HEAD "$1"
}    

if [ "$OS" = 'FreeBSD' ]
then
    update_file_timestamp() {
        file_time=`date -r "$(git show --pretty=format:%at --abbrev-commit "$(get_file_rev "$1")" | head -n 1)" '+%Y%m%d%H%M.%S'`
        touch -h -t "$file_time" "$1"
    }    
else    
    update_file_timestamp() {
        file_time=`git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1`
        touch -d "$file_time" "$1"
    }    
fi    

OLD_IFS=$IFS
IFS=$'\n'

for file in `git ls-files`
do
    update_file_timestamp "$file"
done

IFS=$OLD_IFS

git update-index --refresh

假设您为上述脚本/path/to/templates/hooks/post-checkout和/或命名/path/to/templates/hooks/post-update,则可以通过以下方式在现有存储库上运行它:

git clone git://path/to/repository.git
cd repository
/path/to/templates/hooks/post-checkout

它还需要最后一行: git update-index --refresh // GUI工具可能依赖于索引,并在执行此操作后对所有文件显示“脏”状态。即在Windows版TortoiseGit中发生这种情况code.google.com/p/tortoisegit/issues/detail?id=861
Arioch

1
并感谢您的脚本。我希望这样的脚本是Git标准安装程序的一部分。并不是我个人需要它,但是团队成员只是觉得时间戳在刷新,而在VCS采用中它是红色的“停止”标语。
Arioch

3

该解决方案应该很快运行。它设置提交者时间为atimes,作者时间为mtimes。它不使用任何模块,因此应合理移植。

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

2

这是上述外壳解决方案的优化版本,并进行了较小的修复:

#!/bin/sh

if [ "$(uname)" = 'Darwin' ] ||
   [ "$(uname)" = 'FreeBSD' ]; then
   gittouch() {
      touch -ch -t "$(date -r "$(git log -1 --format=%ct "$1")" '+%Y%m%d%H%M.%S')" "$1"
   }
else
   gittouch() {
      touch -ch -d "$(git log -1 --format=%ci "$1")" "$1"
   }
fi

git ls-files |
   while IFS= read -r file; do
      gittouch "$file"
   done

1

这是使用PHP的方法:

<?php
$r = popen('git ls-files', 'r');
$n_file = 0;

while (true) {
   $s_gets = fgets($r);
   if (feof($r)) {
      break;
   }
   $s_trim = rtrim($s_gets);
   $m_file[$s_trim] = false;
   $n_file++;
}

$r = popen('git log -m -z --name-only --relative --format=%ct .', 'r');

while ($n_file > 0) {
   $s_get = fgets($r);
   $s_trim = rtrim($s_get);
   $a_name = explode("\x0", $s_trim);
   $s_unix = array_pop($a_name);
   foreach ($a_name as $s_name) {
      if (! array_key_exists($s_name, $m_file)) {
         continue;
      }
      if ($m_file[$s_name]) {
         continue;
      }
      touch($s_name, $n_unix);
      $m_file[$s_name] = true;
      $n_file--;
   }
   $n_unix = (int)($s_unix);
}

它类似于此处的答案:

git的use-commit-times等效于什么?

它建立了一个像这样的答案的文件列表,但是它是从构建git ls-files 而不是仅仅在工作目录中查找的。这解决了排除问题,.git也解决了未跟踪文件的问题。另外,如果文件的最后一次提交是合并提交,则该答案将失败,我使用解决了这个问题git log -m。像其他答案一样,一旦找到所有文件,它将停止,因此它不必读取所有提交。例如:

https://github.com/git/git

截至本文发布时,它仅需读取292次提交。此外,它会根据需要忽略历史记录中的旧文件,并且不会触摸已被触摸的文件。最后,它似乎比其他解决方案要快一些。git/git回购结果:

PS C:\git> Measure-Command { git-touch.php }
TotalSeconds      : 3.4215134

0

我看到了对Windows版本的一些要求,所以就在这里。创建以下两个文件:

C:\ Program Files \ Git \ mingw64 \ share \ git-core \ templates \ hooks \ post-checkout

#!C:/Program\ Files/Git/usr/bin/sh.exe
exec powershell.exe -NoProfile -ExecutionPolicy Bypass -File "./$0.ps1"

C:\ Program Files \ Git \ mingw64 \ share \ git-core \ templates \ hooks \ post-checkout.ps1

[string[]]$changes = &git whatchanged --pretty=%at
$mtime = [DateTime]::Now;
[string]$change = $null;
foreach($change in $changes)
{
    if($change.Length -eq 0) { continue; }
    if($change[0] -eq ":")
    {
        $parts = $change.Split("`t");
        $file = $parts[$parts.Length - 1];
        if([System.IO.File]::Exists($file))
        {
            [System.IO.File]::SetLastWriteTimeUtc($file, $mtime);
        }
    }
    else
    {
        #get timestamp
        $mtime = [DateTimeOffset]::FromUnixTimeSeconds([Int64]::Parse($change)).DateTime;
    }
}

这利用了git whatchanged,因此它一次通过所有文件,而不是为每个文件调用git。


0

我正在做一个项目,其中保留我的存储库的副本以用于rsync基于基础的部署。我使用分支来定位不同的环境,git checkout导致文件修改发生更改。

了解到git无法提供检出文件和保留时间戳的方法后,我git log --format=format:%ai --name-only .在另一个SO问题中遇到了该命令:快速列出大量文件的最后提交日期

我现在在touch项目文件和目录中使用以下脚本,以便rsync更轻松地进行部署:

<?php
$lines = explode("\n", shell_exec('git log --format=format:%ai --name-only .'));
$times = array();
$time  = null;
$cwd   = isset($argv[1]) ? $argv[1] : getcwd();
$dirs  = array();

foreach ($lines as $line) {
    if ($line === '') {
        $time = null;
    } else if ($time === null) {
        $time = strtotime($line);
    } else {
        $path = $cwd . DIRECTORY_SEPARATOR . $line;
        if (file_exists($path)) {
            $parent = dirname($path);
            $dirs[$parent] = max(isset($parent) ? $parent : 0, $time);
            touch($path, $time);
        }
    }
}

foreach ($dirs as $dir => $time) {
    touch($dir, $time);
}
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.