我需要本地和服务器上的文件时间戳同步.这是通过在配置中设置use-commit-times = true来完成Subversion,以便每个文件的最后修改时间是在提交时.
每次我克隆我的存储库时,我都希望文件的时间戳能够反映最后一次更改它们在远程存储库中的时间,而不是在克隆存储库时.
有没有办法用git做到这一点?
但是,如果您真的想在签出时使用时间戳的提交时间,请尝试使用此脚本并将其(作为可执行文件)放在$ 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
但是请注意,此脚本会导致检出大型存储库时出现相当大的延迟(大型存储库意味着大量文件,而不是大文件大小).
更新:我的解决方案现在打包成Debian/Ubuntu/Mint,Fedora,Gentoo和其他可能的发行版:
https://github.com/MestreLion/git-tools#install
恕我直言,不存储时间戳(和其他元数据,如权限和所有权)是一个很大的限制git
.
Linus的时间戳因为它"混淆make
" 而有害的理由是蹩脚的:
make clean
足以解决任何问题.
仅适用于使用make
C++ 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
我不确定这适用于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插件,也就是说,你可以真正拥有显示所有分支的虚拟目录.
而且我确信上述任何一种都比使用文件时间戳玩游戏更好.
莱纳斯
我接受了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
我们被迫发明了另一个解决方案,因为我们需要特定的修改时间而不是提交时间,并且解决方案也必须是可移植的(即在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
请注意,可以将挂钩放入模板目录中以自动化其放置位置
更多信息可以在这里找到 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获取最新版本