当前位置:  开发笔记 > 编程语言 > 正文

什么相当于git的use-commit-times?

如何解决《什么相当于git的use-commit-times?》经验,为你挑选了5个好方法。

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

每次我克隆我的存储库时,我都希望文件的时间戳能够反映最后一次更改它们在远程存储库中的时间,而不是在克隆存储库时.

有没有办法用git做到这一点?



1> Giel..:

但是,如果您真的想在签出时使用时间戳的提交时间,请尝试使用此脚本并将其(作为可执行文件)放在$ 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

但是请注意,此脚本会导致检出大型存储库时出现相当大的延迟(大型存储库意味着大量文件,而不是大文件大小).


给出实际答案的+1,而不只是说"不要那样做"
`| 应该避免使用head -n 1`,因为它会生成一个新进程,`-n 1`代表`git rev-list`和`git log`可以代替使用.
最好不要用`\`... \``和`for`读取行; 请参阅[为什么不用"for"读取行](http://mywiki.wooledge.org/DontReadLinesWithFor).我会选择`git ls-files -z`和`IFS = read -r -d''`.
Windows版本可以吗?
而不是`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`命令生成的数据少得多,应该减少高架。

2> MestreLion..:

更新:我的解决方案现在打包成Debian/Ubuntu/Mint,Fedora,Gentoo和其他可能的发行版:

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


恕我直言,不存储时间戳(和其他元数据,如权限和所有权)是一个很大的限制git.

Linus的时间戳因为它"混淆make" 而有害的理由是蹩脚的:

make clean 足以解决任何问题.

仅适用于使用makeC++ C++的项目.对于Python,Perl或一般文档这样的脚本来说,这完全没有实际意义.

如果您应用时间戳,则只会造成伤害.将它们存放在回购中是没有害处的.运用他们可以是一个简单--with-timestamps的选择git checkout和朋友(clone,pull等等),在用户的自由裁量权.

Bazaar和Mercurial都存储元数据.用户可以在结账时申请或不申请.但在git的,因为原来的时间戳,甚至没有可用的回购,有没有这样的选项.

因此,对于一个项目子集特定的非常小的收益(不必重新编译所有内容),git因为一般的DVCS被削弱,来自文件的一些信息丢失了,并且,正如Linus所说,它是不可能的现在.伤心.

那就是说,我可以提供两种方法吗?

1 - http://repo.or.cz/w/metastore.git,DavidHärdeman.尝试做git 首先应该做的事情:在提交时(通过预提交挂钩)在repo中存储元数据(不仅是时间戳),并在拉(也通过挂钩)时重新应用它们.

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

核心功能,带--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

性能相当令人印象深刻,即使对于怪物项目wine,git甚至是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


"Linus的时间戳有害的原因只是因为它"混淆了"是蹩脚的" - 同意100%,DCVS不应该知道或关心它包含的代码!这再次显示了尝试将针对特定用例编写的工具重新用于一般用例的缺陷.Mercurial始终是一个优越的选择,因为它是设计的,而不是进化的.
@RossSmithII:`git ls-files`对工作目录和索引进行操作,因此它并不意味着它实际上*存储了回购信息.如果它存储,检索(和应用)mtime将是微不足道的.
@davec你很受欢迎,很高兴这很有用.https://github.com/MestreLion/git-tools上的完整版本已经处理了Windows,Python 3,非ASCII路径名等.上述脚本只是一个可靠的概念证明,避免将其用于生产用途.
您的论点是有效的。我希望有一些影响力的人对git提出增强的要求,使其具有建议的--with-timestamps选项。

3> VonC..:

我不确定这适用于DVCS(如"分布式"VCS)

2007年已经进行了大量讨论(见本文)

Linus的一些答案对这个想法并不太热衷.这是一个样本:

对不起.如果你没有看到它是如何错误的刚毛邮戳回东西,这将使一个简单的"做" miscompile源代码树,我不知道"错了"你说的是什么defintiion.
这是不对的.
这很傻.
实施它是完全不可能的.


(注意:小改进:结账后,不再修改最新文件的时间戳(Git 2.2.2 +,2015年1月):"git checkout - 如何在切换分支时保持时间戳?".)


答案很长的答案是:

如果这是常见的话,我认为你只需要使用多个存储库就可以了.

弄乱时间戳一般不会起作用.它只是要你保证,"制造"获取一个非常糟糕的方式混淆,并且不重新编译足够的重新编译,而不是太多.

Git确实能够以很多不同的方式轻松地"检查其他分支".

你可以创建一些琐碎的脚本来执行以下任何操作(从琐碎到更具异国情调):

只需创建一个新的回购:

git clone old new
cd new
git checkout origin/

那就是你.旧的仓库中的旧时间戳很好,您可以在新的仓库中工作(并编译),而不会对旧的仓库产生影响.

使用标志"-n -l -s"来"git clone"基本上可以立即实现.对于许多文件(例如像内核这样的大型存储库),它不会像仅仅切换分支一样快,但是对工作树的第二个副本进行分配可能非常强大.

如果你愿意,只用一个tar-ball做同样的事情

git archive --format=tar --prefix=new-tree/  |
        (cd .. ; tar xvf -)

如果你只想要一个快照,这真的很快.

习惯于" git show",只看个别文件.
实际上这有时非常有用.你这样做

git show otherbranch:filename

在一个xterm窗口中,在另一个窗口中查看当前分支中的同一文件.特别是,这对于可编写脚本的编辑器(即GNU emacs)来说应该是微不足道的,在编辑器中,应该可以基本上为编辑器中的其他分支设置一个完整的"直接模式".据我所知,emacs git模式已经提供了这样的东西(我不是emacs用户)

在"虚拟目录"的极端例子中,至少有人为FUSE工作git插件,也就是说,你可以真正拥有显示所有分支的虚拟目录.

而且我确信上述任何一种都比使用文件时间戳玩游戏更好.

莱纳斯


@VonC:由于其他现代DVCS如Bazaar和Mercurial处理时间戳就好了,我宁愿说"*git*不适合这种功能".如果"a"DVCS*应该*具有该功能是有争议的(我强烈认为他们这样做).
嗯,我必须相信他的观点,认为这是不可行的.不管它是错还是愚蠢都是另一回事.我使用时间戳对我的文件进行版本化并将它们上传到CDN,这就是为什么时间戳反映文件实际修改时的重要性,而不是最后从回购中删除的时间.
这不是问题的答案,而是关于在版本控制系统中执行此操作的优点的哲学讨论.如果这个人会喜欢这个,他们就会问:"git没有使用提交时间来修改文件的时间是什么原因?"
同意.您不应该将DVCS与分发系统混淆.`git`是一个DVCS,用于操作将被构建*到您的最终产品中的源代码.如果你想要一个分发系统,你知道在哪里可以找到`rsync`.
@Ben W:"Linus的回答"并不是说你的*特殊情况是错误的.它仅作为提醒,DVCS不适合这种功能(时间戳保留).
有没有人知道如果他们在git repo中我怎么可能保留我的文件的最后修改时间?

4> Alex Dean..:

我接受了Giel的回答,而不是使用post-commit钩子脚本,将其用于我的自定义部署脚本.

更新:我还删除了一个关于| 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


供参考,%ai是"作者日期,ISO 8601格式"

5> Karel Tucek..:

我们被迫发明了另一个解决方案,因为我们需要特定的修改时间而不是提交时间,并且解决方案也必须是可移植的(即在Windows的git安装中使python工作真的不是一项简单的任务)而且速度快.它类似于David Hardeman的解决方案,我决定不使用它,因为缺少文档(从存储库我无法知道他的代码到底是什么).

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

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

    mtimestore - 提供3个选项的核心脚本-a(全部保存 - 用于已经存在的repo中的初始化(与git-versed文件一起工作)), - s(用于保存分阶段的更改)和-r以恢复它们.这实际上有两个版本 - 一个bash one(便携式,漂亮,易于阅读/修改)和c版本(凌乱但快速,因为mingw bash非常慢,这使得无法在大型项目上使用bash解决方案).

    预提交钩子

    结账后挂钩

预提交:

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


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 " << 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& 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& 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& mapa)
{
  std::ofstream outputfile;
  outputfile.open(".mtimes", std::ios::out);
  for(std::map::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 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;
}

请注意,可以将挂钩放入模板目录中以自动化其放置位置

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

//编辑 - 更新的c ++版本:

现在c ++版本维护字母顺序 - >减少合并冲突.

摆脱了丑陋的system()调用.

从post-checkout hook删除$ git update-index --refresh $.在乌龟git下恢复会产生一些问题,但无论如何似乎并不重要.

我们的Windows软件包可以在http://ktweb.cz/blog/download/git-mtimestore-1.4.rar下载

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

推荐阅读
Life一切安好
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有