과거에 Git 커밋을 어떻게합니까?


222

개인적으로 사용하기 위해 모든 것을 Git으로 변환하고 있으며 저장소에 이미 오래된 버전의 파일이 있습니다. 파일의 "수정 날짜"에 따라 올바른 순서로 기록에 커밋하려면 어떻게 파일의 정확한 기록을 갖습니까?

나는 이것이 효과가 있다고 들었다.

git filter-branch --env-filter="GIT_AUTHOR_DATE=... --index-filter "git commit path/to/file --date " --tag-name-filter cat -- --all  

짧고 간단한 답변 : stackoverflow.com/a/34639957/2708266
Yash

33
이에 대한 답변을 찾는 사람들이 GitHub "기여 줄무늬"를 계속 이런 식으로 유지하고 싶은지 궁금합니다.
ZitRo

1
@ZitRo 예. 그리고 간단하게 설정이 git commit --date="xxx day ago" -m "yyy"사람이 궁금하면 그 목적에 충분하다.
Vlas Sokolov

alexpeattie.com/blog/working-with-dates-in-git : 부드러운 설명을 찾고 있다면
thepurpleowl

답변:


198

당신이받은 조언에 결함이 있습니다. 무조건 GIT_AUTHOR_DATE를 설정하면 --env-filter모든 커밋 날짜가 다시 작성됩니다. 또한 git commit inside 를 사용하는 것은 드문 일 --index-filter입니다.

여기서 여러 개의 독립적 인 문제를 처리하고 있습니다.

"지금"이외의 날짜 지정

각 커밋에는 작성자 날짜와 커미터 날짜의 두 날짜가 있습니다. 새 커밋을 작성하는 모든 명령에 대해 환경 변수 GIT_AUTHOR_DATE 및 GIT_COMMITTER_DATE를 통해 값을 제공하여 각각을 대체 할 수 있습니다. git-commit (1) 또는 아래의 “날짜 형식”을 참조하십시오 .

Git internal format = <unix timestamp> <time zone offset>, e.g.  1112926393 +0200
RFC 2822            = e.g. Thu, 07 Apr 2005 22:13:13 +0200
ISO 8601            = e.g. 2005-04-07T22:13:13

정상적인 사용 중에 새 커밋을 작성하는 유일한 명령은 git commit 입니다. --date작성자 날짜를 직접 지정할 수 있는 옵션도 있습니다. 예상 사용량에는 git filter-branch --env-filter위에서 언급 한 환경 변수도 사용됩니다 (이 옵션은 옵션 이름을 지정한 후 "env"의 일부입니다. git-filter-branch (1)의 "옵션" 및 기본 "배관"명령 git-commit -tree (1) .

단일 참조 기록에 파일 삽입

리포지토리가 매우 간단한 경우 (즉, 태그가없는 단일 분기 만있는 경우) git rebase 를 사용 하여 작업을 수행 할 수 있습니다.

다음 명령에서“A”대신 커밋의 객체 이름 (SHA-1 해시)을 사용하십시오. git commit 을 실행할 때 "date override"메소드 중 하나를 사용하는 것을 잊지 마십시오 .

---A---B---C---o---o---o   master

git checkout master
git checkout A~0
git add path/to/file
git commit --date='whenever'
git tag ,new-commit -m'delete me later'
git checkout -
git rebase --onto ,new-commit A
git tag -d ,new-commit

---A---N                      (was ",new-commit", but we delete the tag)
        \
         B'---C'---o---o---o   master

추가 된 위치에 새 커밋을 작성하는 대신 새 파일을 포함하도록 A를 업데이트하려면 git commit --amend대신 을 사용하십시오 git commit. 결과는 다음과 같습니다.

---A'---B'---C'---o---o---o   master

위의 내용은 새 커밋의 부모가되어야하는 커밋의 이름을 지정할 수있는 한 작동합니다. 실제로 새로운 루트 커밋 (부모 없음)을 통해 새 파일을 추가하려면 약간 다른 것이 필요합니다.

B---C---o---o---o   master

git checkout master
git checkout --orphan new-root
git rm -rf .
git add path/to/file
GIT_AUTHOR_DATE='whenever' git commit
git checkout -
git rebase --root --onto new-root
git branch -d new-root

N                       (was new-root, but we deleted it)
 \
  B'---C'---o---o---o   master

git checkout --orphan상대적으로 새롭지 만 (Git 1.7.2), 이전 버전의 Git에서 작동 하는 것과 동일한 작업을 수행하는 다른 방법이 있습니다.

다중 참조 기록에 파일 삽입

저장소가 더 복잡한 경우 (즉, 하나 이상의 참조 (분기, 태그 등)가있는 경우) git filter-branch 를 사용해야 할 것입니다 . git filter-branch를 사용하기 전에 전체 저장소의 백업 사본을 작성해야합니다. 전체 작업 트리 의 간단한 tar 아카이브 (.git 디렉토리 포함)로 충분합니다. git filter-branch 는 백업 참조를 만들지 만 .git디렉토리를 삭제 하고 백업에서 복원 하여 바로 올바른 필터링에서 복구하는 것이 더 쉬운 경우가 많습니다 .

참고 : 아래 예는 git update-index --add대신 하위 수준 명령을 사용 git add합니다. git add를 사용할 수는 있지만 먼저 외부 위치에서 예상 경로로 파일을 복사해야합니다 ( --index-filter빈 임시 GIT_WORK_TREE에서 명령을 실행).

모든 기존 커밋에 새 파일을 추가하려면 다음을 수행하십시오.

new_file=$(git hash-object -w path/to/file)
git filter-branch \
  --index-filter \
    'git update-index --add --cacheinfo 100644 '"$new_file"' path/to/file' \
  --tag-name-filter cat \
  -- --all
git reset --hard

기존 커밋 날짜를 변경해야 할 이유가 없습니다 --env-filter 'GIT_AUTHOR_DATE=…'. 그것을 사용했다면, 모든 커밋에 대한 날짜를 다시 쓰도록 조건부로 만들 것입니다.

기존의 커밋 (“A”) 후에 커밋에만 새 파일을 표시하려면 다음을 수행하십시오.

file_path=path/to/file
before_commit=$(git rev-parse --verify A)
file_blob=$(git hash-object -w "$file_path")
git filter-branch \
  --index-filter '

    if x=$(git rev-list -1 "$GIT_COMMIT" --not '"$before_commit"') &&
       test -n "$x"; then
         git update-index --add --cacheinfo 100644 '"$file_blob $file_path"'
    fi

  ' \
  --tag-name-filter cat \
  -- --all
git reset --hard

히스토리 중간에 삽입 할 새 커밋을 통해 파일을 추가 하려면 git filter-branch 를 사용하기 전에 새 커밋을 생성하고 git filter-branch 에 추가 --parent-filter해야합니다 .

file_path=path/to/file
before_commit=$(git rev-parse --verify A)

git checkout master
git checkout "$before_commit"
git add "$file_path"
git commit --date='whenever'
new_commit=$(git rev-parse --verify HEAD)
file_blob=$(git rev-parse --verify HEAD:"$file_path")
git checkout -

git filter-branch \
  --parent-filter "sed -e s/$before_commit/$new_commit/g" \
  --index-filter '

    if x=$(git rev-list -1 "$GIT_COMMIT" --not '"$new_commit"') &&
       test -n "$x"; then
         git update-index --add --cacheinfo 100644 '"$file_blob $file_path"'
    fi

  ' \
  --tag-name-filter cat \
  -- --all
git reset --hard

로부터의 "고아"방법을 통해 커밋 새 루트를 만들 : 당신은 또한 파일이 처음 커밋 새로운 루트에서 추가로를 준비 할 수 자식 REBASE의 섹션 (캡처 거기에 new_commit무조건을 사용) --index-filter, 그리고 --parent-filter처럼 "sed -e \"s/^$/-p $new_commit/\"".


유스 케이스의 첫 번째 예인 "다중 참조 히스토리에 파일 삽입":에서 --index-filter리턴 한 커밋에 적용 할 수 있는 방법이 git rev-list있습니까? 현재 인덱스 필터가 rev-list의 하위 세트에 적용되는 것을 볼 수 있습니다. 통찰력을 인정하십시오.
고슴도치

@Hedgehog : 모든 "multi-ref"예제 -- --all는 모든 참조에서 도달 할 수있는 모든 커밋을 처리 하는 데 사용 합니다. 마지막 예는 특정 커밋 만 변경하는 방법을 보여줍니다 (원하는대로 GIT_COMMIT를 테스트하십시오). 특정 커밋 목록 만 변경하려면 필터링하기 전에 목록을 저장 git rev-list … >/tmp/commits_to_rewrite한 다음 (예 :) 필터 내부의 멤버 자격을 테스트하십시오 (예 :) if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index …. 정확히 무엇을 달성하려고합니까? 설명에 설명하기에 너무 많은 질문이 있으면 새로운 질문을 시작하고 싶을 수도 있습니다.
Chris Johnsen

필자의 경우 소스 제어하에 작은 프로젝트가 있습니다. (솔로 프로젝트이므로 사용할 시스템을 결정하지 않았기 때문에 그렇게하지 않았습니다). 내 "소스 제어"는 전체 프로젝트 트리를 새 디렉토리로 복제하고 이전 버전 디렉토리의 이름을 바꾸는 문제였습니다. 나는 Git (SCCS, RCS 및 CVS와 유사한 추악한 독점 RCS 파생물을 사용한 후)을 처음 사용하므로 나에게 이야기하는 것에 대해 걱정하지 않아도된다. 답변에 "파일을 단일 참조 기록에 삽입"섹션의 변형이 포함되어 있다는 인상을 받았습니다. 옳은?
Steve

1
@Steve : "단일 참조"시나리오는 기록이 단일 개발 라인에서 온 경우에 적용됩니다 (즉, 모든 스냅 샷이 단일 선형 브랜치의 연속 지점으로 간주되는 경우 의미가 있습니다). "다중 참조"시나리오는 기록에 여러 지점이 있거나있는 경우 적용됩니다. 당신의 역사가 복잡하지 않다면 (다른 클라이언트를위한“포크 된”버전,“개발”등에서 새로운 작업이 진행되는 동안“버그 픽스”브랜치를 유지하지 않았다면), 아마도“단일 참조”상황을보고있을 것입니다. 그러나 상황이 질문의 상황과 다른 것 같습니다…
Chris Johnsen

1
@ 스티브 : 당신은 역사의 일련 가지고 있기 때문에 디렉토리 스냅 샷을 - 당신은 이미이없는 힘내 저장소 - 당신은 아마 사용할 수 있습니다 import-directories.perl망할 놈의에서 contrib/(또는 import-tars.perl, 또는 import-zips.py심지어와 (당신의 스냅 샷에서 새 Git 저장소를 만들 수 ...) "오래된"타임 스탬프). 내 대답 의 rebase/ filter-branch기술은 기존 리포지토리 기록에서 "삭제 된"파일을 삽입하려는 경우에만 필요합니다.
Chris Johnsen

119

평소와 같이 커밋을 만들 수 있지만 커밋 할 때 환경 변수 GIT_AUTHOR_DATEGIT_COMMITTER_DATE적절한 날짜 시간을 설정하십시오 .

물론 이것은 분기의 끝에서 (즉, 현재 HEAD 커밋 앞에서) 커밋을 수행합니다. repo에서 더 멀리 밀어 넣으려면 약간 화려해야합니다. 이 역사가 있다고 가정 해 봅시다.

o--o--o--o--o

그리고 새로운 커밋 ( "X"로 표시)이 두 번째 로 나타나기를 원합니다 .

o--X--o--o--o--o

가장 쉬운 방법은 첫 번째 커밋에서 분기하고 새 커밋을 추가 한 다음 새 커밋 위에 다른 커밋을 리베이스하는 것입니다. 이렇게 :

$ git checkout -b new_commit $desired_parent_of_new_commit
$ git add new_file
$ GIT_AUTHOR_DATE='your date' GIT_COMMITTER_DATE='your date' git commit -m 'new (old) files'
$ git checkout master
$ git rebase new_commit
$ git branch -d new_commit

25
그래서 여기 다음 시간 동안 '19시 32분 10초 2013 -0400 (금) 년 7 월 26'이며, 항상 좋은 대답을 찾을 수 있지만, 다음 날짜 형식을 찾기 위해 떨어져 서둘러해야
MeBigFatGuy

94
GIT_AUTHOR_DATE='Fri Jul 26 19:32:10 2013 -0400' GIT_COMMITTER_DATE='Fri Jul 26 19:32:10 2013 -0400' git commit
Xeoncross


3
그건 그렇고, '-0400'부분은 시간대 오프셋을 지정합니다. 그렇지 않으면 시간에 따라 시간이 변경되므로 올바르게 선택해야합니다. 예를 들어 시카고에 사는 나 자신의 경우 '-0600'(북미 CST)을 선택해야했습니다. 여기에서 코드를 찾을 수 있습니다 : timeanddate.com/time/zones
evaldeslacasa

2
날짜를 반복해야하므로 다른 변수를 만드는 것이 더 쉽다는 것을 알았습니다.THE_TIME='2019-03-30T8:20:00 -0500' GIT_AUTHOR_DATE=$THE_TIME GIT_COMMITTER_DATE=$THE_TIME git commit -m 'commit message here'
Frank Henard

118

나는이 질문이 꽤 오래되었다는 것을 알고 있지만 실제로 나를 위해 일한 것입니다.

git commit --date="10 day ago" -m "Your commit message" 

16
이것은 완벽하게 작동했습니다. --date인간이 읽을 수있는 상대 날짜 형식도 지원 한다는 사실에 놀랐습니다 .
ZitRo

@ZitRo 왜 10 일이 아닌가?
Alex78191

10
경고 : git --date$ GIT_AUTHOR_DATE 만 수정하므로 상황에 따라 커밋에 첨부 된 현재 날짜가 표시됩니다 ($ GIT_COMMITTER_DATE)
Guido U. Draheim

3
@ GuidoU.Draheim이 말한 것에 태그를 달았습니다. 를 사용하여 커밋의 전체 세부 사항을 확인할 수 있습니다 git show <commit-hash> --format=fuller. 여기에 AuthorDate지정된 날짜가 표시 되지만 CommitDate실제 커밋 날짜는 표시됩니다.
d4nyll

CommitDate를 변경하거나 완전히 과거의 커밋을 수행 할 수있는 방법이 없습니까? @ d4nyll
Rahul

35

시간이 지남에 따라 myfile 의 많은 버전을 myfile_bak, myfile_old, myfile_2010, backups / myfile 등으로 저장했습니다. 수정 날짜를 사용하여 myfile의 기록을 git에 저장하고 싶었습니다. 그래서 myfile을하는 가장 오래된 이름을 변경 git add myfile한 후 git commit --date=(modification date from ls -l) myfile, myfile을 옆에 오래된 이름을 변경, 다른 자식은 --date, 반복과 함께 커밋 ...

이를 다소 자동화하기 위해 shell-foo를 사용하여 파일의 수정 시간을 얻을 수 있습니다. 내가 시작 ls -l하고 cut, 그러나 합계 (1) 더 직접적

git commit --date = "`stat -c % y myfile` " myfile


1
좋은. 필자는 git 시절 이전 에 파일 수정 시간을 사용하고 싶었던 파일을 가지고있었습니다 . 그러나 이것이 단지 커밋 날짜 (저자 날짜가 아님)를 설정하지는 않습니까?
Xeoncross

git-scm.com/docs/git-commit에서 : --date"커밋에 사용 된 작성자 날짜를 무시하십시오." git log님의 날짜는 AuthorDate 인 것 같습니다 git log --pretty=fuller. AuthorDate와 CommitDate를 모두 보여줍니다.
skierpage

16
git commit옵션 --date은을 수정 GIT_AUTHOR_DATE하지 않고을 수정합니다 GIT_COMMITTER_DATE. Pro Git Book은 다음과 같이 설명합니다. "저자는 저작물을 처음 쓴 사람이고 커미터는 마지막으로 작품을 적용한 사람입니다." 날짜와 관련하여 GIT_AUTHOR_DATE는 파일이 변경된 GIT_COMMITTER_DATE날짜 이고, 는 커밋 된 날짜입니다. 여기서는 기본적으로 git log작성자 날짜를 "날짜"로 표시하지만 --since옵션이 주어지면 필터링을 위해 커밋 날짜를 사용합니다 .
Christopher

OS X 10.11.1에는 stat에 대한 -c 옵션이 없습니다.
thinsoldier

stat -c %ymacOS (및 기타 BSD 변형)와 동일합니다 stat -f %m.
빅터

21

다음은 내가에 변경 사항을 적용하기 위해 무엇을 사용 foo하는 N=1과거의 일 :

git add foo
git commit -m "Update foo"
git commit --amend --date="$(date -v-1d)"

더 오래된 날짜로 커밋하려면 3 일 전에 다시 date인수를 변경하십시오 date -v-3d.

예를 들어 어제 무언가를 저지르는 것을 잊었을 때 정말 유용합니다.

UPDATE : 또는 --datelike와 같은 표현식도 허용합니다 . 따라서 한 줄 명령으로 줄일 수 있습니다.--date "3 days ago"--date "yesterday"

git add foo ; git commit --date "yesterday" -m "Update"

6
경고 : git --date$ GIT_AUTHOR_DATE 만 수정하므로 상황에 따라 커밋에 첨부 된 현재 날짜가 표시됩니다 ($ GIT_COMMITTER_DATE)
Guido U. Draheim

두 가지 접근 방식을 혼합하면 거의 완벽하게 맞습니다.git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej

대박! 사람이 읽을 수있는 날짜와 동일한 명령git commit --amend --date="$(date -R -d '2020-06-15 16:31')"
pixelbrackets

16

필자의 경우 --date 옵션을 사용하는 동안 git 프로세스가 중단되었습니다. 내가 끔찍한 짓을했을 수도 있습니다. 결과적으로 일부 index.lock 파일이 나타납니다. 그래서 .git 폴더에서 .lock 파일을 수동으로 삭제하고 실행하여 모든 수정 된 파일이 통과 된 날짜에 커밋되어 이번에는 효과가있었습니다. 모든 답변에 감사드립니다.

git commit --date="`date --date='2 day ago'`" -am "update"

5
대한 의견을git commit --date 참조하십시오 . 또한 @JstRoRR
Christopher

4
경고 : git --date$ GIT_AUTHOR_DATE 만 수정하므로 상황에 따라 커밋에 첨부 된 현재 날짜가 표시됩니다 ($ GIT_COMMITTER_DATE)
Guido U. Draheim

9

그것은 과거에 행해졌처럼 당신이 모두 설정해야 그 모습을 커밋 만들려면 GIT_AUTHOR_DATEGIT_COMMITTER_DATE:

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit -m '...'

여기서 date -d'...'정확히 같은 일이 될 수 2019-01-01 12:00:00또는 같은 상대 5 months ago 24 days ago.

git log에서 두 날짜를 모두 보려면

git log --pretty=fuller

병합 커밋에도 적용됩니다.

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git merge <branchname> --no-ff

2

언제든지 컴퓨터에서 날짜를 변경하고 커밋 한 다음 날짜를 변경하고 푸시 할 수 있습니다.


1
저는 이것이 올바른 습관이 아니라고 생각합니다. 알 수없는 문제가 생길 수 있습니다
Vino

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