git의 use-commit-times와 동등한 것은 무엇입니까?


97

동기화하려면 로컬 및 서버에있는 파일의 타임 스탬프가 필요합니다. 이는 구성에서 use-commit-times = true를 설정하여 Subversion을 사용하여 각 파일의 마지막 수정이 커밋 된 시점이되도록합니다.

저장소를 복제 할 때마다 파일의 타임 스탬프가 저장소를 복제 할 때가 아니라 원격 저장소에서 마지막으로 변경된 시간을 반영하기를 원합니다.

자식으로 이것을 수행하는 방법이 있습니까?


배포 프로세스의 일부로 자산 (이미지, 자바 스크립트 파일 및 CSS 파일)을 CDN에 업로드합니다. 각 파일 이름에는 마지막으로 수정 된 타임 스탬프가 추가됩니다. 배포 할 때마다 모든 자산을 만료시키지 않는 것이 중요합니다. (use-commit-times의 또 다른 부작용은 내 로컬에서이 프로세스를 수행 할 수 있고 내 서버가 동일한 파일을 참조 할 것이라는 것을 알 수 있다는 것입니다.하지만 그것은 그다지 중요하지 않습니다.) git 복제를 수행하는 대신에 git fetch 다음에 git reset --hard from my remote repo. 단일 서버에서는 작동하지만 각 서버의 타임 스탬프가 다르기 때문에 여러 서버에서는 작동하지 않습니다.
Ben W

@BenW : git annex이미지를 추적하는 데 유용 할 수 있습니다
jfs

ID를 확인하여 변경 사항을 확인할 수 있습니다. 파일 시스템 타임 스탬프를 vcs 타임 스탬프와 동일한 의미로 만들려고합니다. 같은 의미가 아닙니다.
jthill

답변:


25

이것이 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 플러그인을 작업하고있었습니다. 즉, 말 그대로 모든 브랜치를 보여주는 가상 디렉토리를 가질 수 있습니다.

위의 방법 중 하나가 파일 타임 스탬프가있는 게임보다 더 나은 대안이라고 확신합니다.

리누스


5
동의합니다. DVCS를 배포 시스템과 혼동해서는 안됩니다. 최종 제품에 빌드git 될 소스 코드를 조작하기위한 DVCS 입니다. 배포 시스템을 원하는 경우 . rsync
Randal Schwartz

14
흠, 나는 그것이 실현 불가능하다는 그의 주장을 믿어야 할 것입니다. 그것이 틀렸거나 어리석은 것인지는 또 다른 문제입니다. 타임 스탬프를 사용하여 파일을 버전 화하고 CDN에 업로드합니다. 따라서 타임 스탬프는 파일이 리포지토리에서 마지막으로 풀다운 된 시점이 아니라 실제로 수정 된 시점을 반영하는 것이 중요합니다.
Ben W

3
@Ben W : "리누스의 대답"은 당신의 특정한 상황 에서 틀렸다고 말하는 것이 아닙니다 . DVCS가 그러한 종류의 기능 (타임 스탬프 보존)에 적합하지 않다는 것을 상기시키기위한 것입니다.
VonC

15
@VonC : Bazaar 및 Mercurial과 같은 다른 최신 DVCS는 타임 스탬프를 잘 처리하기 때문에 " git 은 이러한 기능에 적합하지 않습니다. "라고 말하고 싶습니다 . 만약 "a"DVCS 가 그 기능을 가지고 있어야 한다면 논란의 여지가 있습니다.
MestreLion 2013

10
이것은 질문에 대한 답이 아니라 버전 제어 시스템에서이 작업을 수행 할 때의 장점에 대한 철학적 토론입니다. 만약 그 사람이 그것을 좋아한다면, 그들은 "git이 파일의 수정 된 시간에 대해 커밋 시간을 사용하지 않는 이유는 무엇입니까?"라고 물었을 것입니다.
thomasfuchs

85

그러나 당신이 경우 실제로 사용하려면이 스크립트를 사용하여 시도하고 파일 $ 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

그러나이 스크립트는 대용량 저장소 (큰 파일 크기가 아니라 대용량 파일을 의미 함)를 체크 아웃 할 때 상당한 지연을 유발합니다.


55
실제 대답보다는 말 +1 "그렇게하지 마십시오"
DanC

4
| head -n 1이 새로운 프로세스를 생성 피해야 -n 1을 위해 git rev-list하고 git log대신 사용할 수 있습니다.
eregon 2012 년

3
`...`및로 행을 읽지 않는 것이 좋습니다 for. "for"행을 읽지 않는 이유를 참조하십시오 . 내가 가고 싶어 git ls-files -z하고 while IFS= read -r -d ''.
musiphil 2013 년

2
Windows 버전이 가능합니까?
Ehryk 2015

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줄어들고 오버 헤드가 줄어 듭니다.
Scott Chamberlain

80

업데이트 : 내 솔루션은 이제 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

2
그러나 git 않는 점 등 타임 스탬프, 그냥 기본적으로 타임 스탬프를 설정하지 않습니다. 출력을보세요git ls-files --debug
Ross Smith II

9
@RossSmithII : git ls-files작업 디렉토리 및 색인에서 작동하므로 실제로 해당 정보를 저장소에 저장 한다는 의미는 아닙니다 . 저장했다면 mtime을 검색하고 적용하는 것은 사소한 일입니다.
MestreLion 2013 년

13
"Linus의 타임 스탬프가"혼란 스럽기 "때문에 해롭다는 근거는 절름발이입니다"-100 % 동의했습니다. DCVS는 포함 된 코드를 알거나 신경 쓰지 않아야합니다! 다시 이것은 특정 사용 사례를 위해 작성된 도구를 일반적인 사용 사례로 변경하려는 시도의 함정을 보여줍니다. Mercurial은 진화 된 것이 아니라 설계 되었기 때문에 항상 탁월한 선택이 될 것입니다.
Ian Kemp

6
@davec 천만에요, 유용해서 다행입니다. github.com/MestreLion/git-tools 의 정식 버전은 이미 Windows, Python 3, 비 ASCII 경로 이름 등을 처리합니다. 위의 스크립트는 작동하는 개념 증명에 불과하므로 프로덕션 용도로 사용하지 마십시오.
MestreLion 2015 년

3
귀하의 주장이 유효합니다. 나는 어떤 영향력을 가진 누군가가 당신이 제안한 --with-timestamps 옵션을 갖도록 git에 대한 개선 요청을하기를 바랍니다.
weberjn 2017

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

알고 도움 감사합니다 다니엘,
알렉스 딘

--abbrev-commit있는 불필요 git show의한 명령 --pretty=format:%ai(해시 출력의 일부가 커밋) 사용 및 | head -n 1사용으로 대체 될 수있다 -s에 플래그git show
엘란 Ruusamäe

1
@ DanielS.Sterling : %ai저자 날짜, ISO 8601입니다 같은 형식에 대한 엄격한 ISO8601의 사용 %aI: git-scm.com/docs/git-show
엘란 Ruusamäe는

4

우리는 커밋 시간이 아닌 수정 시간이 필요했기 때문에 또 다른 솔루션을 발명해야했습니다. 솔루션도 이식 가능해야하고 (즉, Windows의 git 설치에서 파이썬을 작동시키는 것은 정말 간단한 작업이 아닙니다) 빠릅니다. 문서가 부족하여 사용하지 않기로 결정한 David Hardeman의 솔루션과 유사합니다 (저장소에서 그의 코드가 정확히 무엇을하는지 알 수 없었습니다).

이 솔루션은 mtimes를 git 저장소의 .mtimes 파일에 저장하고 커밋에 따라 업데이트하고 (jsut는 준비된 파일의 mtimes 선택) 체크 아웃시 적용합니다. cygwin / mingw 버전의 git에서도 작동합니다 (하지만 표준 cygwin에서 git의 폴더로 일부 파일을 복사해야 할 수도 있습니다).

솔루션은 3 개의 파일로 구성됩니다.

  1. mtimestore-3 개의 옵션 -a (모두 저장-이미 존재하는 저장소에서 초기화 (git-versed 파일과 함께 작동)), -s (단계적 변경 사항 저장) 및 -r을 제공하여 복원하는 핵심 스크립트. 이것은 실제로 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;
}
  • 후크는 자동 배치를 위해 템플릿 디렉토리에 배치 될 수 있습니다.

자세한 정보는 여기에서 찾을 수 있습니다. 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 참조


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 도구는 색인에 의존하고 이러한 작업 후 모든 파일에 "dirty"상태를 표시 할 수 있습니다. 즉, Windows 용 TortoiseGit에서 발생합니다. code.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'The

1
그리고 스크립트 감사합니다. 그런 스크립트가 Git 표준 설치 프로그램의 일부 였으면합니다. 개인적으로 필요하다는 것은 아니지만 팀원들은 VCS 채택에서 타임 스탬프가 빨간색 "중지"배너로 재충전되는 것을 느낍니다.
Arioch 'The

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/gitrepo를 사용한 결과 :

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을 호출하는 대신 한 번에 모든 파일을 실행합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.