문제

예전에 어떻게 하는지 물어봤는데 처음 두 커밋을 스쿼시합니다. git 저장소에 있습니다.

솔루션은 다소 흥미롭고 Git의 다른 것들만큼 놀랍지는 않지만 프로젝트 개발 과정에서 절차를 여러 번 반복해야 하는 경우 여전히 속담처럼 상처를 받습니다.

따라서 저는 고통을 한 번만 겪고 표준 대화형 리베이스를 영원히 사용할 수 있는 편을 선호합니다.

그렇다면 내가 원하는 것은 첫 번째 목적으로만 존재하는 빈 초기 커밋을 갖는 것입니다.코드도 없고 아무것도 없습니다.리베이스의 기반이 될 수 있도록 공간을 차지합니다.

그렇다면 내 질문은 기존 저장소가 있는 경우 첫 번째 커밋 앞에 비어 있는 새 커밋을 삽입하고 다른 모든 커밋을 앞으로 이동하려면 어떻게 해야 합니까?입니다.

도움이 되었습니까?

해결책

2017년 중반 답변

부작용 없이 완전히 빈 커밋을 새로 만드는 것은 Git의 배관을 직접 사용하는 것이 가장 좋습니다.그렇게 하면 부작용을 피할 수 있습니다.작업 복사본이나 인덱스를 건드리지 않고 정리할 임시 분기도 없습니다.그래서:

  1. 커밋을 생성하려면 디렉토리 트리가 필요하므로 먼저 빈 트리를 생성합니다.

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  2. 이제 우리는 그것에 대해 커밋을 감쌀 수 있습니다:

    commit=`git commit-tree -m 'root commit' $tree`
    
  3. 이제 이를 기반으로 리베이스할 수 있습니다.

    git rebase --onto $commit --root master
    

그리고 그게 다야.쉘을 충분히 잘 알고 있다면 모든 것을 한 줄로 재정렬할 수 있습니다.

(참고:실제로는 이제 filter-branch.나중에 수정하겠습니다.)


역사적 답변 (다른 답변에서 참조)

다음은 추가 저장소를 생성하고 리모컨을 조작하고 분리된 헤드를 수정할 필요 없이 작동한다는 점에서 동일한 솔루션을 더욱 깔끔하게 구현한 것입니다.

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .

# then you apply the same steps
git commit --allow-empty -m 'root commit'
git rebase --onto newroot --root master
git branch -d newroot

짜잔, 당신은 결국 master 빈 루트 커밋을 포함하도록 기록이 다시 작성되었습니다.


참고:이전 버전의 Git에서는 --orphan 로 전환 checkout, 빈 분기를 만들려면 배관이 필요합니다.

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

다른 팁

아리스토텔레스 Pagaltzis와 Uwe Kleine-König의 답변과 Richard Bronosky의 의견을 병합했습니다.

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
# touch .gitignore && git add .gitignore # if necessary
git commit --allow-empty -m 'initial'
git rebase --onto newroot --root master
git branch -d newroot

(모든 것을 한 곳에 넣기 위해)

나는 아리스토텔레스의 대답을 좋아합니다. 그러나 대규모 저장소 (> 5000 커밋) 필터 브랜치가 몇 가지 이유로 Rebase보다 더 잘 작동한다는 것을 발견했습니다. 1) 더 빨리 2) 병합 충돌이있을 때 인간의 개입이 필요하지 않습니다. 3) 태그를 다시 작성하여 보존 할 수 있습니다. 필터 브랜치는 각 커밋의 내용에 대한 의문이 없기 때문에 작동합니다.이 'Rebase'이전과 정확히 동일합니다.

내 단계는 다음과 같습니다.

# first you need a new empty branch; let's call it `newroot`
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

# then you apply the same steps
git commit --allow-empty -m 'root commit'

# then use filter-branch to rebase everything on newroot
git filter-branch --parent-filter 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat master

'-tag-name-filter cat'옵션은 새로 생성 된 커밋을 가리키기 위해 태그를 다시 작성한다는 것을 의미합니다.

나는 아리스토텔레스와 켄트의 답변을 성공적으로 사용했습니다.

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .
git commit --allow-empty -m 'root commit'
git filter-branch --parent-filter \
'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat -- --all
# clean up
git checkout master
git branch -D newroot
# make sure your branches are OK first before this...
git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d

이것은 또한 모든 지점을 다시 작성합니다 (단지가 아니라 master) 태그 외에.

git rebase --root --onto $emptyrootcommit

트릭을 쉽게 수행해야합니다

나는 흥분 하고이 멋진 스크립트의 'Idempotent'버전을 썼습니다 ... 그것은 항상 동일한 빈 커밋을 삽입 할 것이며, 두 번 실행하면 매번 커밋 해시를 변경하지 않습니다. 그래서 여기 내 테이크가 있습니다 git-insert-empty-Root:

#!/bin/sh -ev
# idempotence achieved!
tmp_branch=__tmp_empty_root
git symbolic-ref HEAD refs/heads/$tmp_branch
git rm --cached -r . || true
git clean -f -d
touch -d '1970-01-01 UTC' .
GIT_COMMITTER_DATE='1970-01-01T00:00:00 +0000' git commit \
  --date='1970-01-01T00:00:00 +0000' --allow-empty -m 'initial'
git rebase --committer-date-is-author-date --onto $tmp_branch --root master
git branch -d $tmp_branch

추가 복잡성 가치가 있습니까? 어쩌면 그렇지는 않지만 나는 이것을 사용할 것입니다.

이것은 또한 레포의 여러 복제 된 사본 에서이 작업을 수행 할 수 있고 동일한 결과로 끝나면 여전히 호환 가능합니다 ... 테스트 ... 예, 작동하지만 삭제하고 추가해야합니다. 다시 리모콘, 예를 들어 :

git remote rm origin
git remote add --track master user@host:path/to/repo

글쎄, 여기 내가 생각해 낸 것입니다.

# Just setting variables on top for clarity.
# Set this to the path to your original repository.
ORIGINAL_REPO=/path/to/original/repository

# Create a new repository…
mkdir fun
cd fun
git init
# …and add an initial empty commit to it
git commit --allow-empty -m "The first evil."

# Add the original repository as a remote
git remote add previous $ORIGINAL_REPO
git fetch previous

# Get the hash for the first commit in the original repository
FIRST=`git log previous/master --pretty=format:%H  --reverse | head -1`
# Cherry-pick it
git cherry-pick $FIRST
# Then rebase the remainder of the original branch on top of the newly 
# cherry-picked, previously first commit, which is happily the second 
# on this branch, right after the empty one.
git rebase --onto master master previous/master

# rebase --onto leaves your head detached, I don't really know why)
# So now you overwrite your master branch with the newly rebased tree.
# You're now kinda done.
git branch -f master
git checkout master
# But do clean up: remove the remote, you don't need it anymore
git remote rm previous

나는 그것이 사용한다고 생각합니다 git replace 그리고 git filter-branch a를 사용하는 것보다 더 나은 솔루션입니다 git rebase:

  • 더 나은 전이
  • 더 쉽고 덜 위험합니다 (각 단계에서 결과를 확인하고 한 일을 취소 할 수 있습니다 ...)
  • 보장 된 결과가있는 여러 지점에서 잘 작동합니다

그 뒤에있는 아이디어는 다음과 같습니다.

  • 과거에 멀리 새로운 빈 커밋을 만듭니다
  • 새 루트 커밋이 부모로 추가 된 것을 제외하고는 정확히 유사한 커밋으로 이전 루트 커밋을 교체합니다.
  • 모든 것이 예상대로 실행되는지 확인하십시오 git filter-branch
  • 다시 한번, 모든 것이 정상인지 확인하고 더 이상 필요한 git 파일을 청소하십시오.

다음은 두 가지 첫 단계를위한 스크립트입니다.

#!/bin/bash
root_commit_sha=$(git rev-list --max-parents=0 HEAD)
git checkout --force --orphan new-root
find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null
git add -A
GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit"
new_root_commit_sha=$(git rev-parse HEAD)

echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..."

parent="parent $new_root_commit_sha"
replacement_commit=$(
 git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" |
 git hash-object -t commit -w --stdin
) || return 3
git replace "$root_commit_sha" "$replacement_commit"

당신은 위험 없이이 스크립트를 실행할 수 있습니다 (이전에 한 적이없는 작업을 수행하기 전에 백업을 수행하더라도 좋은 생각입니다.)) 결과가 예상되지 않은 경우 폴더에서 생성 된 파일을 삭제하십시오. .git/refs/replace 그리고 다시 시도하십시오;)

저장소 상태가 기대하는임을 확인한 후에는 다음 명령을 실행하여 이력을 업데이트하십시오. 모든 지점:

git filter-branch -- --all

이제, 당신은 두 개의 역사, 오래된 역사와 새로운 역사를 봐야합니다 (도움말을 참조하십시오. filter-branch 자세한 내용은). 2를 비교하고 모든 것이 정상인지 다시 확인할 수 있습니다. 만족하면 더 이상 필요한 파일을 삭제하십시오.

rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace

당신은 당신의 것으로 돌아갈 수 있습니다 master 지점 및 임시 분기를 삭제하십시오.

git checkout master
git branch -D new-root

이제 모든 일이 끝나야합니다;)

"Git Init"직후에 빈 커밋을 만드는 것을 잊어 버린 경우 저장소 시작시 빈 커밋을 추가하는 데 사용할 수있는 간단한 원 라이너가 있습니다.

git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)

Here's my bash script based on Kent's answer with improvements:

  • it checks out the original branch, not just master, when done;
  • I tried to avoid the temporary branch, but git checkout --orphan only works with a branch, not detached-head state, so it's checked out long enough to make the new root commit and then deleted;
  • it uses the hash of the new root commit during the filter-branch (Kent left a placeholder in there for manual replacement);
  • the filter-branch operation rewrites only the local branches, not remotes too
  • the author and committer metadata is standardised so that the root commit is identical across repositories.

#!/bin/bash

# Save the current branch so we can check it out again later
INITIAL_BRANCH=`git symbolic-ref --short HEAD`
TEMP_BRANCH='newroot'

# Create a new temporary branch at a new root, and remove everything from the tree
git checkout --orphan "$TEMP_BRANCH"
git rm -rf .

# Commit this empty state with generic metadata that will not change - this should result in the same commit hash every time
export GIT_AUTHOR_NAME='nobody'
export GIT_AUTHOR_EMAIL='nobody@example.org'
export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000'
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
git commit --allow-empty -m 'empty root'
NEWROOT=`git rev-parse HEAD`

# Check out the commit we just made and delete the temporary branch
git checkout --detach "$NEWROOT"
git branch -D "$TEMP_BRANCH"

# Rewrite all the local branches to insert the new root commit, delete the 
# original/* branches left behind, and check out the rewritten initial branch
git filter-branch --parent-filter "sed \"s/^\$/-p $NEWROOT/\"" --tag-name-filter cat -- --branches
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git checkout "$INITIAL_BRANCH"

루트 커밋을 전환하려면 :

먼저, 원하는 커밋을 첫 번째로 만드십시오.

둘째, 다음을 사용하여 커밋 순서를 전환하십시오.

git rebase -i -root

편집자는 루트 커밋이 될 때까지 커밋과 함께 나타납니다.

1234 오래된 루트 메시지를 선택하십시오

0294 중간에 커밋을 선택하십시오

뿌리에 넣고 싶은 커밋 5678 픽

그런 다음 첫 번째 줄에 배치하여 원하는 커밋을 먼저 배치 할 수 있습니다. 예에서 :

뿌리에 넣고 싶은 커밋 5678 픽

1234 오래된 루트 메시지를 선택하십시오

0294 중간에 커밋을 선택하십시오

편집자 종료 커밋 주문이 변경되었습니다.

추신 : 편집기 git 사용을 변경하려면 실행 :

git config -global core.editor name_of_the_editor_program_you_want_to_use

다음 답변 Aristotle Pagaltzis 및 기타이지만 더 간단한 명령을 사용합니다.

zsh% git checkout --orphan empty     
Switched to a new branch 'empty'
zsh% git rm --cached -r .
zsh% git clean -fdx
zsh% git commit --allow-empty -m 'initial empty commit'
[empty (root-commit) 64ea894] initial empty commit
zsh% git checkout master
Switched to branch 'master'
zsh% git rebase empty
First, rewinding head to replay your work on top of it...
zsh% git branch -d empty 
Deleted branch empty (was 64ea894).

리포지토리에는 커밋 대기 대기 대기중인 로컬 수정이 포함되어 있지 않아야합니다.
메모 git checkout --orphan 새로운 버전의 git에서 작동 할 것입니다.
대부분의 시간에 주목하십시오 git status 유용한 힌트를 제공합니다.

새 저장소를 시작하십시오.

날짜를 원하는 시작 날짜로 다시 설정하십시오.

당신이 원하는 방식으로 모든 것을하십시오. 그렇게하고 싶었을 때 반영 할 시스템 시간을 조정하십시오. 불필요한 타이핑을 피하기 위해 필요에 따라 기존 저장소에서 파일을 끌어 당깁니다.

오늘 가면 저장소를 교체하면 완료됩니다.

당신이 단지 미쳤지 만 합리적으로 지능적이라면 (아마도 이와 같은 미친 아이디어를 생각하기 위해 일정량의 똑똑한 사람이 있어야하기 때문에) 프로세스를 스크립트 할 것입니다.

그것은 또한 당신이 과거가 지금부터 일주일로 다른 방법으로 일어났다 고 결정할 때 더 좋을 것입니다.

이 게시물은 오래된 게시물이라는 것을 알고 있지만이 페이지는 "Commit Git 삽입"에 대해 인터넷 검색을 할 때 첫 번째 페이지입니다.

왜 간단한 것을 복잡하게 만드는가?

당신은 ABC가 있고 ABZC를 원합니다.

  1. git rebase -i trunk (또는 이전에 b)
  2. B 라인에서 편집하도록 선택을 변경하십시오
  3. 변경 : git add ..
  4. git commit (git commit --amend B를 편집하고 z를 생성하지 않음)

당신은 많은 것을 만들 수 있습니다 git commit 여기에 더 많은 커밋을 삽입하려고합니다. 물론 5 단계에 문제가있을 수 있지만 GIT와의 충돌을 해결하는 것은 당신이 가져야 할 기술입니다. 그렇지 않다면, 연습!

  1. git rebase --continue

단순하지 않습니까?

당신이 이해한다면 git rebase, '루트'커밋을 추가하는 것은 문제가되지 않아야합니다.

git과 함께 즐기십시오!

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top