동기화하려면 로컬 및 서버에있는 파일의 타임 스탬프가 필요합니다. 이는 구성에서 use-commit-times = true를 설정하여 Subversion을 사용하여 각 파일의 마지막 수정이 커밋 된 시점이되도록합니다.
저장소를 복제 할 때마다 파일의 타임 스탬프가 저장소를 복제 할 때가 아니라 원격 저장소에서 마지막으로 변경된 시간을 반영하기를 원합니다.
자식으로 이것을 수행하는 방법이 있습니까?
답변
이것이 DVCS ( “분산”VCS에서와 같이)에 적합한 지 잘 모르겠습니다.
거대한 토론은 이미 2007 년에 열렸습니다 (이 스레드 참조).
그리고 Linus의 답변 중 일부는 아이디어에 너무 치열하지 않았습니다. 다음은 한 가지 샘플입니다.
죄송 해요. 소스 트리를 간단하게 “만들기”로 잘못 컴파일 하도록 날짜 스탬프를 다시 설정하는 것이 어떻게 잘못된 것인지 알지 못한다면 “잘못된”정의가 무엇인지 알 수 없습니다.
잘못되었습니다.
바보입니다.
그리고 구현하기가 완전히 불가능합니다.
(참고 : 작은 개선 : 체크 아웃 후 최신 파일의 타임 스탬프가 더 이상 수정되지 않습니다 (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”에 사용하여 기본적으로이를 즉각적으로 만듭니다. 많은 파일 (예 : 커널과 같은 큰 저장소)의 경우 분기를 전환하는 것만 큼 빠르지는 않지만 작업 트리의 두 번째 복사본을 사용하는 것은 매우 강력 할 수 있습니다.
- 대신 타르볼로 똑같은 일을하세요.
git archive --format=tar --prefix=new-tree/ <branchname> | (cd .. ; tar xvf -)
스냅 샷 만 원하면 정말 빠릅니다.
- ”
git show
“에 익숙해 지고 개별 파일을 살펴보십시오.
이것은 실제로 때때로 정말 유용합니다. 당신은 그냥
git show otherbranch:filename
하나의 xterm 창에서 다른 창에서 현재 분기의 동일한 파일을 봅니다. 특히 이것은 스크립트 가능한 편집기 (예 : GNU emacs)와 관련하여 간단해야합니다. 기본적으로 이것을 사용하여 편집기 내의 다른 분기에 대해 전체 “dired mode”를 가질 수 있어야합니다. 내가 아는 한, emacs git 모드는 이미 이와 같은 것을 제공합니다 (저는 emacs 사용자가 아닙니다)
- 그리고 그 “가상 디렉토리”의 극단적 인 예에서, 적어도 누군가가 FUSE 용 git 플러그인을 작업하고있었습니다. 즉, 말 그대로 모든 브랜치를 보여주는 가상 디렉토리를 가질 수 있습니다.
위의 방법 중 하나가 파일 타임 스탬프가있는 게임보다 더 나은 대안이라고 확신합니다.
리누스
답변
그러나 당신이 경우 실제로 사용하려면이 스크립트를 사용하여 시도하고 파일 $ GIT_DIR / .git / 후크 / 후 체크 아웃에 (실행 파일)를 배치 한 후 체크 아웃시 타임 스탬프를 위해 시간을 커밋 :
#!/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
sudo apt install git-restore-mtime # Debian/Ubuntu/Mint
yum install git-tools # Fedora/ RHEL / CentOS
emerge dev-vcs/git-tools # Gentoo
이럴 아니라 저장 타임 스탬프 (소유권 및 권한과 같은 다른 메타 데이터)가 인 대형 의 제한 git
.
Linus의 타임 스탬프가 “혼동 make
” 하기 때문에 해롭다는 이유 는 절름발이입니다 .
-
make clean
문제를 해결하기에 충분합니다. -
make
주로 C / C ++ 를 사용하는 프로젝트에만 적용됩니다 . Python, Perl 또는 일반적인 문서와 같은 스크립트에 대해서는 완전히 논쟁의 여지가 있습니다. -
타임 스탬프 를 적용 하는 경우에만 해가됩니다 . repo 에 저장 해도 아무런 해가 없습니다 . 을 적용하는 것은 간단한의 수
--with-timestamps
에 대한 옵션git checkout
과 친구 (clone
,pull
상기 등), 사용자의 재량.
Bazaar와 Mercurial은 모두 메타 데이터를 저장합니다. 사용자는 체크 아웃시 적용 여부를 결정할 수 있습니다. 그러나 git에서는 원래 타임 스탬프가 저장소 에서도 사용할 수 없기 때문에 그러한 옵션이 없습니다.
따라서 프로젝트의 하위 집합에만 해당하는 매우 작은 이득 (모든 것을 다시 컴파일 할 필요가 없음)의 git
경우 일반 DVCS가 손상 되고 파일에 대한 일부 정보가 손실 되며 Linus가 말했듯이 수행하기가 불가능합니다. 지금. 슬픈 .
즉, 두 가지 접근 방식을 제공 할 수 있습니까?
1- http ://repo.or.cz/w/metastore.git, David Härdeman 작성. git
처음에해야 할 일을 시도합니다 : 커밋 할 때 (사전 커밋 후크를 통해) 리포지토리에 메타 데이터 (타임 스탬프뿐만 아니라) 를 저장 하고 끌어 올 때 (후크를 통해) 다시 적용합니다.
2-이전에 릴리스 타르볼을 생성하기 위해 사용한 스크립트의 겸손한 버전. 다른 답변에서 언급 한 바와 같이, 접근 방식은 조금 다른 각 파일에 대해 적용 : 타임 스탬프 의 가장 최근의 커밋 파일이 수정되었습니다.
- 많은 옵션이있는 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
성능도 몬스터 프로젝트를 위해 꽤 인상적이다 wine
, git
또는 리눅스 커널 :
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
답변
나는 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
답변
우리는 커밋 시간이 아닌 수정 시간이 필요했기 때문에 또 다른 솔루션을 발명해야했습니다. 솔루션도 이식 가능해야하고 (즉, Windows의 git 설치에서 파이썬을 작동시키는 것은 정말 간단한 작업이 아닙니다) 빠릅니다. 문서가 부족하여 사용하지 않기로 결정한 David Hardeman의 솔루션과 유사합니다 (저장소에서 그의 코드가 정확히 무엇을하는지 알 수 없었습니다).
이 솔루션은 mtimes를 git 저장소의 .mtimes 파일에 저장하고 커밋에 따라 업데이트하고 (jsut는 준비된 파일의 mtimes 선택) 체크 아웃시 적용합니다. cygwin / mingw 버전의 git에서도 작동합니다 (하지만 표준 cygwin에서 git의 폴더로 일부 파일을 복사해야 할 수도 있습니다).
솔루션은 3 개의 파일로 구성됩니다.
- mtimestore-3 개의 옵션 -a (모두 저장-이미 존재하는 저장소에서 초기화 (git-versed 파일과 함께 작동)), -s (단계적 변경 사항 저장) 및 -r을 제공하여 복원하는 핵심 스크립트. 이것은 실제로 bash 버전 (휴대용, 멋지고 읽기 / 수정하기 쉬운)과 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 <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;
}
- 후크는 자동 배치를 위해 템플릿 디렉토리에 배치 될 수 있습니다.
자세한 정보는 여기에서 찾을 수 있습니다.
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 $를 삭제했습니다. 거북이 자식에서 되돌리기에 몇 가지 문제를 일으키고 어쨌든별로 중요하지 않은 것 같습니다.
- Windows 패키지는 http://ktweb.cz/blog/download/git-mtimestore-1.4.rar 에서 다운로드 할 수 있습니다.
// 최신 버전은 github 참조
답변
다음 스크립트는 -n 1
및 HEAD
제안을 통합하고 대부분의 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
답변
이 솔루션은 매우 빠르게 실행됩니다. 커미터 시간에는 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;