현재 커밋을 Git 저장소에서 유일한 (초기) 커밋으로 만드시겠습니까?


664

현재 로컬 Git 저장소가 있는데 Github 저장소로 푸시합니다.

로컬 리포지토리에는 ~ 10 개의 커밋이 있으며 Github 리포지토리는 동기화 된 복제본입니다.

내가하고 싶은 일은 로컬 Git 리포지토리에서 모든 버전 기록을 제거하므로 리포지토리의 현재 내용이 유일한 커밋으로 표시되므로 리포지토리 내의 이전 버전의 파일은 저장되지 않습니다.

그런 다음 이러한 변경 사항을 Github에 푸시하고 싶습니다.

Git rebase를 조사했지만 특정 버전을 제거하는 데 더 적합한 것으로 보입니다. 또 다른 잠재적 해결책은 로컬 저장소를 삭제하고 새 저장소를 작성하는 것입니다.

ETA : 추적되지 않는 특정 디렉토리 / 파일이 있습니다-가능하면 이러한 파일의 추적을 유지하고 싶습니다.


6
참조 stackoverflow.com/questions/435646/... ( "나는 Git 저장소의 처음 두 커밋을 결합하려면 어떻게합니까?")
Anonymoose


답변:


981

다음은 무차별 대입 방식입니다. 또한 저장소 구성을 제거합니다.

참고 : 리포지토리에 하위 모듈이있는 경우 작동하지 않습니다! 하위 모듈을 사용하는 경우 대화식 rebase 를 사용해야합니다.

1 단계 : 모든 기록 삭제 ( 백업이 있는지 확인합니다. 되돌릴 수 없음 )

cat .git/config  # note <github-uri>
rm -rf .git

2 단계 : 현재 콘텐츠만으로 Git 리포지토리 재구성

git init
git add .
git commit -m "Initial commit"

3 단계 : GitHub로 푸시

git remote add origin <github-uri>
git push -u --force origin master

3
감사합니다 Larsmans-나는 이것을 내 솔루션으로 사용하기로 선택했습니다. Git 리포지토리를 초기화하면 이전 리포지토리에서 추적되지 않은 파일의 기록이 손실되지만 이것은 아마도 내 문제에 대한 간단한 해결책 일 것입니다.
kaese

5
@kaese : 나는 당신 .gitignore이 그것들을 처리해야 한다고 생각합니다 .
Fred Foo

48
.git / config를 저장 한 후 복원하십시오.
lalebarde

@lalebarde .git / config를 복원 한 후에 는 이미 구성에 있다고 가정하고 해당 부분을 git commit -m "Initial commit"건너 뛰고 git remote add ...바로 밀어 넣을 수 있습니다. 그것은 나를 위해 일했다.
Buttle Butkus

24
민감한 데이터를 제거하려는 경우이 점에주의하십시오. 새로 푸시 된 마스터 브랜치에 단일 커밋 만 존재하면 오해의 소지가 있습니다. 히스토리는 여전히 존재 하지만 해당 브랜치에서는 액세스 할 수 없습니다. 예를 들어, 이전 커밋을 가리키는 태그가 있으면 이러한 커밋에 액세스 할 수 있습니다. 사실, 약간의 git foo를 가진 사람에게는이 git push 후에도 여전히 GitHub 저장소에서 모든 히스토리를 복구 할 수 있다고 확신합니다. 다른 브랜치 또는 태그가있는 경우 많은 자식이 필요합니다.
Robert Muil

621

나를 위해 일하고 하위 모듈을 계속 작동시키는 유일한 솔루션은

git checkout --orphan newBranch
git add -A  # Add all files and commit them
git commit
git branch -D master  # Deletes the master branch
git branch -m master  # Rename the current branch to master
git push -f origin master  # Force push master branch to github
git gc --aggressive --prune=all     # remove the old files

.git/하위 모듈이 있으면 삭제 하면 항상 큰 문제가 발생합니다. 사용 git rebase --root하면 어떻게 든 갈등이 생길 수 있습니다 (그리고 많은 역사를 가지고 있기 때문에 오래 걸립니다).


55
이것이 정답이어야합니다! git push -f origin master마지막 op로 태양을 추가하면 신선한 repo에서 태양이 다시 빛납니다! :)
gru

2
이것이 오래된 커밋을 유지하지 않습니까?
Brad

4
@JonePolvora git 페치; git reset --hard origin / master stackoverflow.com/questions/4785107/…
echo 17

5
이 작업을 수행 한 후 repo 여유 공간이 있습니까?
Inuart

8
@JasonGoemaat의 제안을 답변의 마지막 줄로 추가해야한다고 생각합니다. git gc --aggressive --prune all역사를 잃어 버리는 요점이 없다면 놓칠 것이다.
Tuncay Göncüoğlu

93

이것은 내가 선호하는 접근법입니다.

git branch new_branch_name $(echo "commit message" | git commit-tree HEAD^{tree})

HEAD의 모든 것을 추가하는 하나의 커밋으로 새로운 브랜치를 생성합니다. 다른 것을 변경하지 않으므로 완전히 안전합니다.


3
최선의 방법! 분명하고 작업을 수행하십시오. 또한 "마스터"에서 "로컬 작업"으로 변경하고 "new_branch_name"에서 "마스터"로 변경하여 분기의 이름을 바꿉니다. 마스터에서 다음을 수행하십시오. git -m local-changes git branch -m local-changes git checkout new_branch_name git branch -m master <
Valtoni Boaventura

이것은 정말로 짧고 매끈 해 보입니다. 내가 이해하지 못하거나 아직 보지 못한 유일한 것은 HEAD ^ {tree}뿐입니다. 그 외에도 나는 이것을 "지정된 커밋으로부터 새로운 브랜치 생성, ___에서 주어진 커밋 메시지로 새로운 커밋 객체를 생성함으로써
생성됨

3
자식 참조 구문에 대한 질문에 대한 답변을 찾을 수있는 확실한 장소는 git-rev-parse문서에 있습니다. 여기서 일어나는 일은 git-commit-tree나무 (레포의 스냅 샷)에 대한 참조 가 필요하지만 HEAD개정입니다. 커밋과 관련된 트리를 찾기 위해 <rev>^{<type>}양식 을 사용합니다 .
dan_waterworth

좋은 대답입니다. 잘 작동합니다. 마지막으로 말git push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez

31

커밋이 많은 경우 많은 작업으로 판명 될 수있는 다른 옵션은 대화 형 rebase입니다 (git 버전이 = = 1.7.12라고 가정).git rebase --root -i

편집기에 커밋 목록이 표시되면 :

  • 첫 번째 커밋에 대해 "pick"을 "reword"로 변경
  • 다른 커밋마다 "pick"을 "fixup"으로 변경

저장하고 닫습니다. 힘내 rebasing 시작합니다.

결국 당신은 새로운 루트 커밋을 갖게 될 것입니다. 루트 커밋은 그 뒤에 오는 모든 것들의 조합입니다.

장점은 리포지토리를 삭제할 필요가 없으며 다시 생각할 경우 항상 폴 백이 있다는 것입니다.

당신이 정말로 당신의 역사를 핵화하고 싶다면, 마스터를이 커밋으로 재설정하고 다른 모든 지점을 삭제하십시오.


리베이스가 완료된 후, 나는 밀 수 없습니다 :error: failed to push some refs to
Begueradj

@Begueradj 당신이 이미 당신이 기반으로 한 브랜치를 푸시했다면 push를 강제해야합니다 git push --force-with-lease. Force-with-Lease는 --force보다 덜 파괴적이므로 사용됩니다.
Carl

19

Larsmans 가 제안한 방법의 변형 :

untrackfiles 목록을 저장하십시오 :

git ls-files --others --exclude-standard > /tmp/my_untracked_files

git 구성을 저장하십시오.

mv .git/config /tmp/

그런 다음 larsmans의 첫 단계를 수행하십시오.

rm -rf .git
git init
git add .

구성을 복원하십시오.

mv /tmp/config .git/

추적되지 않은 파일의 추적을 해제하십시오.

cat /tmp/my_untracked_files | xargs -0 git rm --cached

그런 다음 커밋하십시오.

git commit -m "Initial commit"

마지막으로 저장소로 푸시하십시오.

git push -u --force origin master

6

아래는 @Zeelot의 답변에서 수정 된 스크립트입니다. 마스터 브랜치뿐만 아니라 모든 브랜치에서 히스토리를 제거해야합니다.

for BR in $(git branch); do   
  git checkout $BR
  git checkout --orphan ${BR}_temp
  git commit -m "Initial commit"
  git branch -D $BR
  git branch -m $BR
done;
git gc --aggressive --prune=all

그것은 내 목적을 위해 일했습니다 (서브 모듈을 사용하지 않습니다).


4
푸시 마스터가 절차를 완료하도록 강요하는 것을 잊었다 고 생각합니다.
not2qubit

2
약간 수정해야했습니다. git branch체크 아웃 된 분기 옆에 별표가 포함되어 붙은 후 분기 이름 인 것처럼 모든 파일이나 폴더로 해석됩니다. 대신, 나는 git branch --format="%(refname:lstrip=2)"단지 지명을 주었다.
벤 리차드

@ not2qubit : 이것에 감사드립니다. 정확한 명령은 무엇입니까? git push --force origin master또는 git push --force-with-lease? 분명히 후자는 더 안전합니다 ( stackoverflow.com/questions/5509543/… 참조 )
Shafique Jamal 22:19

@BenRichards. 흥미 롭군 지점 이름과 일치하는 폴더로 테스트하여 다시 시도한 다음 답변을 업데이트하겠습니다. 감사.
Shafique Jamal

5

얕은 클론을 사용할 수 있습니다 (git> 1.9) :

git clone --depth depth remote-url

추가 읽기 : http://blogs.atlassian.com/2014/05/handle-big-repositories-git/


4
이러한 복제는 새로운 저장소로 푸시 될 수 없습니다.
Seweryn Niemiec 2011

1
그러한 한계를 극복하는 방법을 아는 것이 도움이 될 것입니다. 누군가 이것이 왜 강제로 밀릴 수 없는지 설명 할 수 있습니까?
not2qubit

귀하의 질문에 대한 답변 : stackoverflow.com/questions/6900103/…
Matthias M

4

git filter-branch 주요 수술 도구입니다.

git filter-branch --parent-filter true -- @^!

--parent-filter부모를 stdin에 놓고 stdout에 다시 작성된 부모를 인쇄해야합니다. 유닉스는 true성공적으로 종료되고 아무것도 인쇄하지 않으므로 부모는 없습니다. @^!이다 힘내가 나타내는 표현이 "머리가 커밋하지만 모든 부모의". 그런 다음 다른 모든 심판을 삭제하고 여가를 누리십시오.


3

Github 저장소를 삭제하고 새로 만드십시오. 지금까지 가장 빠르고 쉽고 안전한 방법. 결국, 단일 커밋을 가진 마스터 브랜치가 필요할 때 허용되는 솔루션에서 모든 명령을 수행하려면 무엇이 필요합니까?


1
주요 요점 중 하나는 어디에서 포크되었는지 볼 수 있다는 것입니다.
not2qubit

방금이 작업을 수행했는데 괜찮습니다
thanos.a

2

아래의 방법은 정확하게 재현 할 수 있으므로 양쪽이 일관된 경우 복제를 다시 실행할 필요가 없으며 다른 쪽에서도 스크립트를 실행하면됩니다.

git log -n1 --format=%H >.git/info/grafts
git filter-branch -f
rm .git/info/grafts

그런 다음 정리하려면 다음 스크립트를 시도하십시오.

http://sam.nipl.net/b/git-gc-all-ferocious

리포지토리의 각 지점에 대해 "히스토리를 죽이는"스크립트를 작성했습니다.

http://sam.nipl.net/b/git-kill-history

또한보십시오: http://sam.nipl.net/b/confirm


1
고마워 그냥 참고 : 일부 업데이트를 사용할 수있는 각 지점에 대한 역사를 죽일 스크립트는 - 다음과 같은 오류를 제공합니다 : git-hash: not foundSupport for <GIT_DIR>/info/grafts is deprecated
Shafique 자말

1
@ShafiqueJamal, 감사, 작은 "자식 해시"스크립트입니다 git log HEAD~${1:-0} -n1 --format=%H, 여기 sam.aiki.info/b/git-hash 그것은 대중 소비를위한 하나의 스크립트에서 모든 것을 넣어 더 좋을 것이다. 다시 사용하면 "grafts"를 대체하는 새로운 기능으로 어떻게 사용하는지 알아낼 수 있습니다.
Sam Watkins

2

내가하고 싶은 일은 로컬 Git 리포지토리에서 모든 버전 기록을 제거하므로 리포지토리의 현재 내용이 유일한 커밋으로 표시되므로 리포지토리 내의 이전 버전의 파일은 저장되지 않습니다.

더 개념적 답변 :

git은 태그 / 브랜치 / 참조가 그들을 가리 키지 않으면 오래된 커밋을 자동으로 가비지 수집합니다. 따라서 모든 태그 / 분기를 제거하고 분기와 관련된 새 고아 커밋을 만들어야합니다. 일반적으로 분기 master는 해당 커밋을 가리 킵니다.

하위 레벨 git 명령으로 파고 들지 않으면 아무도 도달 할 수없는 오래된 커밋을 다시 볼 수 없습니다. 그것이 당신에게 충분하다면, 나는 거기서 멈추고 자동 GC가 원할 때마다 일을하도록 할 것입니다. 즉시 제거하려면 git gc( 을 사용 하여 --aggressive --prune=all) 사용할 수 있습니다 . 원격 git 리포지토리의 경우 파일 시스템에 대한 셸 액세스 권한이없는 한 강제로 할 수있는 방법이 없습니다.


@Zeelot의 답변에서 볼 때 멋진 추가 기능.
Mogens TrasherDK

예, Zeelot 's는 기본적 으로이 작업을 수행하는 명령을 가지고 있습니다 (완전히 다시 시작하면 OP에 적합 할 수 있음). @MogensTrasherDK
AnoE

0

여기 있습니다 :

#!/bin/bash
#
# By Zibri (2019)
#
# Usage: gitclean username password giturl
#
gitclean () 
{ 
    odir=$PWD;
    if [ "$#" -ne 3 ]; then
        echo "Usage: gitclean username password giturl";
        return 1;
    fi;
    temp=$(mktemp -d 2>/dev/null /dev/shm/git.XXX || mktemp -d 2>/dev/null /tmp/git.XXX);
    cd "$temp";
    url=$(echo "$3" |sed -e "s/[^/]*\/\/\([^@]*@\)\?\.*/\1/");
    git clone "https://$1:$2@$url" && { 
        cd *;
        for BR in "$(git branch|tr " " "\n"|grep -v '*')";
        do
            echo working on branch $BR;
            git checkout $BR;
            git checkout --orphan $(basename "$temp"|tr -d .);
            git add -A;
            git commit -m "Initial Commit" && { 
                git branch -D $BR;
                git branch -m $BR;
                git push -f origin $BR;
                git gc --aggressive --prune=all
            };
        done
    };
    cd $odir;
    rm -rf "$temp"
}

또한 여기에서 호스팅됩니다 : https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743


가! 명령 줄에서 숨겨지지 않은 보호되지 않은 암호를 제공하지 마십시오! 또한 git branch의 출력은 일반적으로 스크립팅에 적합하지 않습니다. 배관 도구를보고 싶을 수도 있습니다.
D. Ben Knoble

-1

.git프로젝트 에서 폴더를 삭제하고 IntelliJ를 통한 버전 제어와 다시 통합 하여 비슷한 문제를 해결했습니다 . 참고 : .git폴더가 숨겨져 있습니다. 를 사용하여 터미널에서 확인한 ls -a다음을 사용하여 제거 할 수 rm -rf .git있습니다.


1 단계에서 수행 한 작업은 다음과 같습니다. rm -rf .git?

-1

이를 위해 Shallow Clone 명령 git clone --depth 1 URL-저장소의 현재 HEAD 만 복제합니다.


-2

자식에서 마지막 커밋을 제거하려면 간단히 실행하면됩니다.

git reset --hard HEAD^ 

상단에서 여러 커밋을 제거하는 경우 다음을 실행할 수 있습니다.

git reset --hard HEAD~2 

마지막 두 커밋을 제거합니다. 더 많은 커밋을 제거하기 위해 숫자를 늘릴 수 있습니다.

자세한 정보는 여기에 있습니다.

Git tututurial 은 저장소를 제거하는 방법에 대한 도움말을 제공합니다.

파일을 기록에서 제거하고 실수로 다시 커밋되지 않도록 .gitignore에 추가하려고합니다. 이 예에서는 GitHub gem 리포지토리에서 Rakefile을 제거합니다.

git clone https://github.com/defunkt/github-gem.git

cd github-gem

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch Rakefile' \
  --prune-empty --tag-name-filter cat -- --all

히스토리에서 파일을 지웠으므로 실수로 다시 커밋하지 않도록하십시오.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

저장소 상태에 만족하면 변경 사항을 강제로 눌러 원격 저장소를 겹쳐 써야합니다.

git push origin master --force

6
저장소에서 파일을 제거하거나 커밋하면 질문과 전혀 관련이 없습니다 (이력을 완전히 제거하라는 요청은 완전히 다릅니다). OP는 클린 히스토리를 원하지만 저장소의 현재 상태를 유지하려고합니다.
Victor Schröder

이것은 질문에서 요구 된 결과를 생성하지 않습니다. 커밋 후 마지막으로 모든 변경 사항을 버리고 그 이후 모든 변경 사항을 잃어 버리지 만 질문은 현재 파일을 유지하고 기록을 삭제하도록 요청합니다.
Tuncay Göncüoğlu
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.