모든 자식 커밋을 하나로 스쿼시하는 방법은 무엇입니까?


480

전체 저장소를 첫 번째 커밋까지 어떻게 스쿼시합니까?

첫 번째 커밋에 리베이스 할 수는 있지만 2 개의 커밋이 남습니다. 첫 번째 커밋 전에 커밋을 참조하는 방법이 있습니까?


8
"첫 번째 커밋 전에 커밋"?
innaM 2009

31
@innaM- 자식을 낳은 것은 원시 커밋입니다 . (유머 유머는 웹간을 통해 충분히 전달됩니다).
ripper234

11
이 질문의 후반에 오는 사람들에게는 더 현대적인 대답 을 사용 하십시오 .
Droogans

1
관련이 있지만 복제본 --root은 아닙니다 ( 실제로 스쿼시에 많은 커밋이있는 경우 모든 커밋 을 스쿼시하는 가장 좋은 솔루션은 아닙니다 ) : Git 저장소의 처음 두 커밋을 결합 하시겠습니까? .

2
Imo : 이것은 @MrTux에서 최고입니다 : stackoverflow.com/questions/30236694/… .
J0hnG4lt

답변:


130

아마도 가장 쉬운 방법은 현재 작업 복사본의 상태로 새 저장소를 만드는 것입니다. 모든 커밋 메시지를 유지하려면 먼저 git log > original.log새 리포지토리에서 초기 커밋 메시지에 대한 커밋 메시지를 편집하고 편집하십시오.

rm -rf .git
git init
git add .
git commit

또는

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log

62
그러나 당신은이 방법으로 가지를 잃어
버렸습니다

150
이 답변이 제공된 이후 Git은 진화했습니다. 아니요, 더 간단하고 좋은 방법이 git rebase -i --root있습니다. 참조 : stackoverflow.com/a/9254257/109618
David J.

5
이것은 어떤 경우에는 효과가 있지만 본질적으로 질문에 대한 대답은 아닙니다. 이 레시피를 사용하면 모든 구성과 다른 모든 브랜치를 잃게됩니다.
iwein

3
이것은 불필요하게 파괴적인 끔찍한 해결책입니다. 사용하지 마십시오.
Daniel Kamil Kozar

5
또한 서브 모듈을 깰 것입니다. -1
Krum

694

현재 자식 1.6.2 , 당신은 사용할 수 있습니다 git rebase --root -i.

첫 번째 커밋을 제외한 각 커밋에 대해로 변경 pick하십시오 squash.


49
원래 질문에 대답하는 완전하고 작동하는 명령 예제를 추가하십시오.
Jake

29
내가 허용 대답처럼 떨어져 내 전체 저장소를 불고 전에 반드시 숙지 좋겠 말한다 : /
마이크의 전관

38
이 답변은 괜찮지 만 20 커밋보다 대화식으로 rebasing하는 경우 대화 형 rebase가 너무 느리고 다루기 어려울 것입니다. 아마도 수백 또는 수천 개의 커밋을 스쿼시하려고하는데 어려움을 겪을 것입니다. 루트 커밋으로 소프트 또는 혼합 재설정을 한 다음 다시 커밋합니다.

20
@Pred squash모든 커밋에 사용하지 마십시오 . 첫 번째는이어야 pick합니다.
Geert

14
커밋이 많으면 'pick'을 'squash'로 수동으로 변경하기가 어렵습니다. 사용 : % S / 선택 / 스쿼시 / g VIM 명령 줄이 더 빠르게 수행합니다.
eilas

314

최신 정보

별칭을 만들었습니다 git squash-all.
사용법 예 : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

주의 사항 : 주석을 제공해야합니다. 그렇지 않으면 기본 커밋 메시지 "A new start"가 사용됩니다.

또는 다음 명령으로 별명을 작성할 수 있습니다.

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

짧막 한 농담

git reset $(git commit-tree HEAD^{tree} -m "A new start")

참고 : 여기 " A new start"는 예일 뿐이며, 자신의 언어를 자유롭게 사용하십시오.

TL; DR

스쿼시 할 필요가 없으며 git commit-tree고아 커밋을 만들고 함께 사용하십시오.

설명

  1. 통해 단일 커밋을 만들 git commit-tree

    무엇입니까 git commit-tree HEAD^{tree} -m "A new start":

    제공된 트리 오브젝트를 기반으로 새 커미트 오브젝트를 작성하고 stdout에서 새 커미트 오브젝트 ID를 생성합니다. -m 또는 -F 옵션이 제공되지 않으면 표준 입력에서 로그 메시지를 읽습니다.

    식은에 HEAD^{tree}해당하는 트리 개체 HEAD, 즉 현재 분기의 끝을 의미합니다. Tree-ObjectsCommit-Objects를 참조하십시오 .

  2. 현재 분기를 새로운 커밋으로 재설정

    그런 다음 git reset현재 분기를 새로 만든 커밋 개체로 재설정하십시오.

이런 식으로, 작업 공간의 어떤 것도 건드리지 않으며 리베이스 / 스쿼시가 필요하지 않아서 정말 빠릅니다. 그리고 필요한 시간은 저장소 크기 또는 기록 깊이와 관련이 없습니다.

변형 : 프로젝트 템플릿의 새로운 저장소

이것은 다른 저장소를 템플릿 / 아키 타입 / 시드 / 스켈레톤으로 사용하여 새 프로젝트에서 "초기 커밋"을 생성하는 데 유용합니다. 예를 들면 다음과 같습니다.

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

이렇게하면 템플릿 저장소를 원격 ( origin또는 기타) 으로 추가하지 않고 템플릿 저장소의 기록을 초기 커밋으로 축소합니다.


6
git 개정 구문 (HEAD ^ {tree}) 구문은 다른 사람이 궁금한 경우를 위해 여기에 설명되어 있습니다. jk.gs/gitrevisions.html
Colin Bowern

1
로컬 및 원격 저장소 또는 둘 중 하나만 재설정합니까?
aleclarson

4
@aleclarson, 로컬 리포지토리의 현재 분기 만 재설정하고 git push -f전파에 사용 합니다.
ryenus

2
관련되지 않은 프로젝트 템플릿 저장소에서 새 프로젝트를 시작하는 방법을 찾고있는 동안이 답변을 찾았습니다 git clone. 추가하는 경우 --hardgit reset및 스위치 HEADFETCH_HEADgit commit-tree당신은 초기 템플릿의 REPO를 가져 오는 한 후 커밋 만들 수 있습니다. 이것을 보여주는 마지막 부분에서 답을 편집했습니다.
toolbear 2009

4
"Caveat"를 제거 할 수는 있지만${1?Please enter a message}
Elliot Cameron의

172

당신이하고 싶은 모든 커밋을 루트 커밋으로 스쿼시하는 것입니다.

git rebase --interactive --root

리베이스 작업은 대화식 리베이스 편집기 커밋 목록을 생성하고 리베이스 자체를 실행하기 위해 매우 느리게 실행될 것이기 때문에 많은 수의 커밋 (예 : 수백 개의 커밋)에는 비실용적입니다.

많은 커밋을 스쿼시 할 때보다 빠르고 효율적인 두 가지 솔루션은 다음과 같습니다.

대체 솔루션 # 1 : 고아 지점

현재 브랜치의 끝 (예 : 가장 최근의 커밋)에서 새 고아 브랜치를 만들 수 있습니다. 이 고아 브랜치는 완전히 새로운 별도의 커밋 히스토리 트리의 초기 루트 커밋을 형성하며, 이는 모든 커밋을 스쿼시하는 것과 실질적으로 같습니다.

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

선적 서류 비치:

대체 솔루션 # 2 : 소프트 리셋

또 다른 효율적인 솔루션은 단순히 루트 커밋에 혼합 또는 소프트 재설정을 사용하는 것입니다 <root>.

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

선적 서류 비치:


22
대안 솔루션 # 1 : 고아 가지-바위!
토마스

8
대체 솔루션 # 1 FTW. 변경 사항을 리모컨으로 푸시하려면 추가하십시오 git push origin master --force.
Eddy Verbruggen

1
잊지 마세요git push --force
NecipAllef

열린 Github 풀 요청을 푸시하려는 경우 코드에서 고아 분기를 수행하지 마십시오 (즉, 위의 대안 솔루션 1을하지 마십시오)! 현재 헤드가 저장된 헤드 sha의 자손이 아니기 때문에 Github은 PR을 닫습니다 .
앤드류 맥키

대안 솔루션 # 1은 또한 커밋을 스쿼시 할 때 발생할 수있는 병합 충돌 을 방지합니다
FernAndr

52
echo "message" | git commit-tree HEAD^{tree}

HEAD 트리를 사용하여 고아 커밋을 만들고 stdout에 이름 (SHA-1)을 출력합니다. 그런 다음 지점을 재설정하십시오.

git reset SHA-1

27
git reset $(git commit-tree HEAD^{tree} -m "commit message")더 쉬울 것입니다.
Ryenus

4
^ 이것! -답이되어야합니다. 그것이 저자의 의도인지는 확실하지 않지만 내 것이 었습니다 (단일 커밋으로 깨끗한 레포가 필요하고 작업이 완료됩니다).
chesterbr

@ryenus, 귀하의 솔루션은 내가 찾고있는 것을 정확하게했습니다. 귀하의 의견을 답변으로 추가하면 이에 동의합니다.
tldr

2
하위 셸 변형을 제안하지 않은 이유는 Windows의 cmd.exe에서 작동하지 않기 때문입니다.
kusma

Windows 프롬프트에서 마지막 매개 변수를 인용해야 할 수도 있습니다. echo "message" | git commit-tree "HEAD^{tree}"
Bernard

41

다른 사람을 위해 작동하는 경우를 대비하여 내가 이렇게 한 방법은 다음과 같습니다.

이와 같은 일을 할 때는 항상 위험이 있으며 시작하기 전에 저장 브랜치를 만드는 것은 결코 나쁜 생각이 아닙니다.

로깅으로 시작

git log --oneline

첫 번째 커밋으로 스크롤하고 SHA를 복사하십시오.

git reset --soft <#sha#>

<#sha#>로그에서 복사 한 SHA로 교체

git status

모든 것이 녹색인지 확인하고 그렇지 않으면 실행 git add -A

git commit --amend

모든 현재 변경 사항을 현재 첫 커밋으로 수정

이제이 분기를 강제로 밀면 거기에있는 내용을 덮어 씁니다.


1
훌륭한 옵션! 정말 간단합니다.
twicejr

1
이것은 환상 이상의 것입니다! 감사!
Matt Komarnicki

1
이것은 실제로 역사를 떠난 것처럼 보입니다. 당신은 그것을 고아하지만 여전히 거기에 있습니다.
Brad

매우 유용한 답변 ...하지만 수정 명령 후에는 특수 구문을 사용하여 vim 편집기에서 자신을 찾을 수 있습니다. ESC, ENTER, : x는 친구입니다.
Erich Kuester

훌륭한 옵션!
danivicario

36

이식편 사용에 대해 읽었지만 많이 조사하지는 않았습니다.

어쨌든, 마지막 2 개의 커밋을 수동으로 다음과 같이 스쿼시 할 수 있습니다.

git reset HEAD~1
git add -A
git commit --amend

4
이것은 실제로 내가 찾고있는 대답입니다.
Jay

이것은 놀라운 답변입니다
마스터 요다

36

가장 쉬운 방법은 'plumbing'명령 update-ref을 사용하여 현재 분기를 삭제하는 것입니다.

git branch -D현재 분기 삭제를 중지하는 안전 밸브가 있으므로 사용할 수 없습니다 .

그러면 새로운 초기 커밋으로 시작할 수있는 '초기 커밋'상태로 돌아갑니다.

git update-ref -d refs/heads/master
git commit -m "New initial commit"


15

한 줄에 6 단어

git checkout --orphan new_root_branch  &&  git commit

@AlexanderMills, 당신은 읽어야 git help checkout에 대해--orphan
KYB

문서를 링크하여 읽을 수있는 모든 사람이 수동으로 검색 할 필요가 없습니까?
Alexander Mills

1
kyb

8

백업을 만들

git branch backup

지정된 커밋으로 재설정

git reset --soft <#root>

그런 다음 모든 파일을 준비에 추가

git add .

메시지를 업데이트하지 않고 커밋

git commit --amend --no-edit

뭉친 커밋으로 새 지사를 밀다

git push -f

이전 커밋 메시지가 보존됩니까?
not2qubit

1
@ not2qubit 아니오 이것은 커밋 # 1, 커밋 # 2, 커밋 # 3 대신 이전 커밋 메시지를 보존하지 않습니다. 커밋의 모든 변경 사항을 단일 커밋 # 1로 묶습니다. 커밋 # 1은 <root>다시 커밋합니다. 커밋 메시지를 편집 할 필요없이 git commit --amend --no-edit현재 커밋에 대한 모든 변경 사항을 <root>커밋합니다.
David Morton

5

이식편을 사용하여 스쿼시하려면

파일을 추가하고 .git/info/grafts루트가되고 싶은 커밋 해시를 넣으십시오.

git log 이제 그 커밋에서 시작합니다

'실제'실행되도록 git filter-branch


1

이 답변 작성 외에 하나 (노 부모가 더-역사), 커밋 없다고 가정, (그들을 투표하십시오) 위의 몇 향상 또한 그의 커밋 커밋 모든 데이터를 유지하려면 없습니다 :

  • 저자 (이름과 이메일)
  • 작성일
  • 커미터 (이름과 이메일)
  • 확정 된 날짜
  • 커밋 로그 메시지

물론 새로운 / 단일 커밋의 commit-SHA는 새로운 (비) 역사를 나타내므로 부모없는 / 루트 커밋이되기 때문에 변경됩니다.

git log대한 일부 변수를 읽고 설정하면 git commit-tree됩니다. 위의 커밋 데이터를 유지하면서 master새 브랜치에서 단일 커밋을 생성한다고 가정합니다 one-commit.

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')

1

이를 위해 로컬 git 리포지토리를 첫 번째 커밋 해시 태그로 재설정 할 수 있으므로 해당 커밋 이후의 모든 변경 사항이 준비되지 않은 다음 --amend 옵션을 사용하여 커밋 할 수 있습니다.

git reset your-first-commit-hashtag
git add .
git commit --amend

그런 다음 필요한 경우 첫 번째 커밋 이름을 편집하고 파일을 저장하십시오.


1

나를 위해 다음과 같이 일했습니다 : 총 4 개의 커밋이 있었고 대화 형 rebase를 사용했습니다.

git rebase -i HEAD~3

첫 번째 커밋이 남아 있으며 3 개의 최신 커밋을했습니다.

다음에 나타나는 편집기에 갇혀 있으면 다음과 같이 표시됩니다.

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

먼저 커밋하고 다른 사람들을 스쿼시해야합니다. 당신이해야 할 것은 :

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

이를 위해 INSERT 키를 사용하여 '삽입'및 '편집'모드를 변경하십시오.

편집기를 저장하고 종료하려면을 사용하십시오 :wq. 커서가 커밋 라인 사이 또는 다른 곳에 있으면 ESC를 누르고 다시 시도하십시오.

결과적으로 두 가지 커밋이 발생했습니다. 첫 번째는 남았고 두 번째는 "이것은 3 개의 커밋의 조합입니다."라는 메시지가 표시됩니다.

자세한 내용은 여기를 참조하십시오 : https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit


0

나는 보통 다음과 같이합니다 :

  • 모든 것이 커밋되었는지 확인하고 문제가 발생할 경우 최신 커밋 ID를 기록하거나 백업으로 별도의 분기를 만듭니다.

  • git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`머리를 첫 번째 커밋으로 재설정하기 위해 실행 하지만 인덱스는 변경하지 마십시오. 첫 번째 커밋 이후의 모든 변경 사항은 커밋 준비가되었습니다.

  • 실행 git commit --amend -m "initial commit"하여 커밋을 수정하는 최초의 커밋과 메시지를 저지하거나 기존의 커밋 메시지를 유지하려는 경우, 당신은 실행할 수 변경git commit --amend --no-edit

  • git push -f변경 사항을 강제로 실행

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