Git에서 루트 커밋 앞에 커밋을 삽입 하시겠습니까?
git 저장소에서 처음 두 커밋 을 스쿼시하는 방법에 대해 이전에 요청했습니다 .
솔루션은 다소 흥미롭고 실제로 git의 다른 것들만큼 마음이 뒤 틀리지는 않지만 프로젝트 개발과 함께 절차를 여러 번 반복 해야하는 경우 여전히 약간의 상처가됩니다.
따라서 한 번만 고통을 겪고 표준 대화 형 rebase를 영원히 사용할 수 있습니다.
그러므로 내가하고 싶은 것은 첫 번째 목적을 위해서만 존재하는 빈 초기 커밋을 갖는 것입니다. 코드도없고 아무것도 없습니다. 공간을 확보하여 rebase의 기반이 될 수 있습니다.
내 질문은 기존 저장소를 가지고 첫 번째 빈 커밋 앞에 새 빈 커밋을 삽입하고 다른 모든 사람을 앞으로 옮기는 방법은 무엇입니까?
2017 년 중반 답변
부작용없이 완전히 비어있는 새로운 커밋을 만드는 것은 아마도 Git의 배관을 직접 사용하는 것이 가장 좋습니다. 이렇게하면 작업 복사본이나 색인을 건드리지 않고 정리할 임시 분기 등의 부작용을 피할 수 있습니다.
커밋을 만들려면 디렉토리 트리가 필요하므로 먼저 비어있는 트리를 만듭니다.
tree=`git hash-object -wt tree --stdin < /dev/null`
이제 커밋을 감쌀 수 있습니다.
commit=`git commit-tree -m 'root commit' $tree`
그리고 이제 우리는 그것에 기초를 둘 수 있습니다 :
git rebase --onto $commit --root master
그리고 그게 다야. 쉘을 충분히 알고 있다면 그 모든 것을 하나의 라이너로 재 배열 할 수 있습니다.
(NB : 실제로는 이제을 사용 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
Voila, master
빈 루트 커밋을 포함하도록 히스토리를 다시 작성했습니다.
NB .:로 --orphan
전환 할 수없는 구 버전의 Git checkout
에서는 빈 브랜치를 생성하기 위해 배관이 필요합니다.
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
Aristotle 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) 태그를 다시 작성하여 보존 할 수 있습니다. filter-branch는 각 커밋의 내용에 대해 의문의 여지가 없으므로 작동합니다.이 '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
내가 사용하는 것을 생각 git replace
하고하는 것은 git filter-branch
를 사용하는 것보다 더 나은 솔루션입니다 git rebase
:
- 더 나은 성능
- 쉽고 덜 위험합니다 (각 단계에서 결과를 확인하고 수행 한 작업을 취소 할 수 있습니다 ...)
- 보장 된 결과로 여러 가지와 잘 작동
그 뒤에 아이디어는 :
- 과거에 새로운 빈 커밋 만들기
- 새 루트 커밋이 부모로 추가된다는 점을 제외하고는 이전 루트 커밋을 정확히 비슷한 커밋으로 교체하십시오.
- 모든 것이 예상대로 실행되고 실행되는지 확인
git filter-branch
- 다시 한 번 모든 것이 정상인지 확인하고 더 이상 필요없는 자식 파일을 정리하십시오.
다음은 두 가지 첫 단계에 대한 스크립트입니다.
#!/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
Now, you must see 2 histories, the old one and the new one (see help on filter-branch
for more information). You could compare the 2 and check again if all is OK. If you are satisfied, delete the no more needed files:
rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace
You could return to your master
branch and delete the temporary branch:
git checkout master
git branch -D new-root
Now, all should be done ;)
Well, here's what I came up with:
# 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
Here's a simple one-liner which can be used to add an empty commit at the start of a repository, if you forgot to create an empty commit immediately after "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"
To switch the root commit:
First, create the commit you want as the first.
Second, switch the order of the commits using:
git rebase -i --root
An editor will appear with the commits until the root commit, like:
pick 1234 old root message
pick 0294 A commit in the middle
pick 5678 commit you want to put at the root
You can then put the commit you want first, by placing it in the first line. In the example:
pick 5678 commit you want to put at the root
pick 1234 old root message
pick 0294 A commit in the middle
Exit the editor the commit order will have changed.
PS: To change the editor git uses, run:
git config --global core.editor name_of_the_editor_program_you_want_to_use
Following answer Aristotle Pagaltzis and others but using more simple commands
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).
Note your repo shouldn't contain no local modifications waiting to be commited.
Note git checkout --orphan
will work at new versions of git, I guess.
Note most of the time git status
gives useful hints.
Combining the latest and greatest. No side effects, no conflicts, keeping tags.
git log --reverse
tree=`git hash-object -wt tree --stdin < /dev/null`
commit=`git commit-tree -m 'Initialize empty repository' $tree`
echo $commit # copy below, interpolation didn't work for me
git filter-branch --parent-filter 'sed "s/^\$/-p <commit>/"' --tag-name-filter cat master
git log --reverse
Note that on GitHub you will lose CI run data and PR might get messed up unless other branches are fixed as well.
Start a new repository.
Set your date back to the start date you want.
Do everything the way you wish you'd done it, adjusting the system time to reflect when you'd wished you'd done it that way. Pull files from the existing repository as needed to avoid a lot of needless typing.
When you get to today, swap the repositories and you're done.
If you're just crazy (established) but reasonably intelligent (likely, because you have to have a certain amount of smarts to think up crazy ideas like this) you will script the process.
That will also make it nicer when you decide you want the past to have happened some other way a week from now.
I know this post is old but this page is the first one when Googling "inserting commit git".
Why make simple things complicated?
You have A-B-C and you want A-B-Z-C.
git rebase -i trunk
(or anything before B)- change pick to edit on the B line
- make your changes:
git add ..
git commit
(git commit --amend
which will edit B and not create Z)
[You can make as many git commit
as you want here to insert more commits. Of course, you may have troubles with step 5, but resolving merging conflict with git is a skill you should have. If not, practice!]
git rebase --continue
Simple, isn't it?
If you understand git rebase
, adding a 'root' commit should not be a problem.
Have fun with git!
참고URL : https://stackoverflow.com/questions/645450/insert-a-commit-before-the-root-commit-in-git
'Programming' 카테고리의 다른 글
최신 C ++로 무료 성능을 얻을 수 있습니까? (0) | 2020.05.06 |
---|---|
Git의 저자와 커미터의 차이점은 무엇입니까? (0) | 2020.05.06 |
오류 만들기 : 구분 기호 누락 (0) | 2020.05.05 |
설치된 판다 버전을 찾는 방법 (0) | 2020.05.05 |
리소스를로드하지 못했습니다 : net :: ERR_INSECURE_RESPONSE (0) | 2020.05.05 |