[英]How to split a git repository into recent and older commits at a specific commit point, preserving branches?
如何在特定的提交SHA上将git存储库分为两个存储库(最近的存储库和历史记录),同时又保留每个存储库中的分支,并正确链接到它们在master上的提交?
尽管有许多SO问题询问并回答如何拆分子目录(例如The Easy Way ),但这不是我需要做的。 相反,我需要将存储库中的提交划分为在某个提交之前的所有提交以及随后的所有提交。 尽管我的存储库很大,在过去十年的历史中有数千个提交和数百个分支,但问题可以归结为一个简单的存储库,其中包含8个提交(1-8)和三个分支(主,A,B):
1 - 2 - 3 - 4 - 5 - 6 - master
\ \
7 8
\ \
A B
转换后,我需要两个存储库。 第一个(项目历史记录)应包含历史提交1、2、3和4,以及分支A上的关联提交7。第二个(项目近期)应包含分支4、5、6和关联的提交8在分支B上这些看起来像:
project-history project-recent
1 - 2 - 3 - 4 -master 4 - 5 - 6 - master
\ \
7 8
\ \
A B
在将Git存储库一分为二中描述了一个类似的问题,但是没有一个答案被接受,也没有产生我需要的结果,下面将与测试脚本一起进行描述。
Pro Git书第7.13章Git-Tools-Replace提供了一种非常接近的方法。 在这种方法中,您首先创建历史记录,然后将最近的提交重新建立到新的孤立提交上。
history
分支 这一切都很好。
但是下一部分不能完全正常工作:
aaf5c36
git commit-tree 8e3dbc5^{tree}
aaf5c36
开始将主aaf5c36
到aaf5c36
git rebase --preserve-merges --onto aaf5c36 8e3dbc5
问题:分支B与新的最近项目存储库中的master断开连接。 生成的存储库如下所示:
project-history project-recent
1 - 2 - 3 - 4 -master 4 - 5 - 6 - master
\
7 1 - 2 - 3 - 4 - 5 - 8- B
\
A
repo-split-example.sh脚本创建一个示例存储库( repo-split-example
),然后使用此技术将其拆分为repo-split-history
和repo-split-recent
存储库,但是分支B在后者中未附加。 另外,通过将分支B推送到最近的存储库中,历史提交也被推送到存储库中(提交1,2,3),并且有提交4和5的副本(原始的,以及从提交中重写的)。变基)。 这是最近的项目回购的最终状态:
$ git log --graph --all --oneline --decorate
* c29649c (HEAD -> master) sixth
* e8545fd fifth
* 8e3dbc5 fourth
* aaf5c36 Get history from historical repository at file:///Users/jones/development/git-svn-migrate/repo-split-history
* 7a98d11 (B) branchB
* 1f620ac fifth
* 1853778 fourth
* 14ab901 third
* 8dd0189 second
* bb1fc8d first
而我想要的是:
$ git log --graph --all --oneline --decorate
* c29649c (HEAD -> master) sixth
| * 7a98d11 (B) branchB
|/
* e8545fd fifth
* 8e3dbc5 fourth
* aaf5c36 Get history from historical repository at file:///Users/jones/development/git-svn-migrate/repo-split-history
repo-split-example.sh脚本是重现此问题的简便方法。 如何获得最近的项目存储库,以包含master的最近提交以及分支B的提交,并正确地链接到基于基础的提交5( fifth
)?
谢谢你的建议!
环顾四周之后,我确定可以手动将最近的分支重新建立到新重写的树中。 为此,对于最近的树中的每个分支,我都会这样做:
# Rebase branch B onto the newly rewritten fifth commit
git branch temp e8545fd # the SHA of the rewritten fifth commit
git checkout B
git rebase temp # This works, but will (always?) have conflicts because it starts
# from the beginning because there is no common merge base for the commit
git branch -d temp
因此,这可行,并产生期望的结果。 git rebase temp
会产生大量合并冲突(自历史记录开始以来,每次提交都会发生一次合并冲突),因为重写的第五次提交不会与原始分支B共享任何历史记录。因此,在在这里,对于我的真实存储库来说,这将花费太长时间。 因此,仍在寻找一种可行的解决方案,在该解决方案中,可以在没有合并冲突的情况下进行变基。
我终于弄清楚了,因此在此处记录该过程,希望它有用。 除了重新部署基础之外,还可以使用移植来拆分存储库,然后使用filter-branch重写树以使移植永久化。 因此,假设TRUNCPOINT
是要在其上拆分存储库的提交的SHA, TRUNCPARENT
是其父级的SHA,并且project-history
和project-recent
都是新初始化的存储库,准备接收历史提交和最近的提交,即最终过程将存储库分为两部分,如下所示:
只需在$ TRUNCPOINT创建一个分支,然后将该分支和所有源自该分支的分支推入project-history
。
git branch history $TRUNCPOINT
git push project-history history:master
git push project-history A
这对推动当地的历史提交hisotry
跳转到master
的分支project-history
式回购,然后推分支A到project-history
资料库,以及。 结果如下:
git log --graph --oneline --decorate --all
* fdc8f84 (A) branchA a1
| * 7237a3e (HEAD -> master) fourth
| * 55be55d third
|/
* 26555d8 second
* 5a68ca2 first
到目前为止还不错,因为历史上最近的提交是第四次提交。
现在我们需要拆分存储库,以将最近的提交从TRUNCPOINT提交到master的HEAD。
接下来的这些命令将创建一个空的提交,该提交将成为最近提交树的新根。
MESSAGE="Get history from historical repository"
BASECOMMIT=`echo $MESSAGE | git commit-tree ${TRUNCPARENT}^{tree}`
最后,我们嫁接了存储库,告诉它$ TRUNCPOINT的父级现在是$ BASECOMMIT,而不是其原始父级。 这有效地截断了$ TRUNCPOINT处的历史记录。 然后,我们使用filter-branch
重写历史记录以使嫁接永久化,然后将master及其关联的分支B推送到project-recent
的project-recent
存储库。
echo "${TRUNCPOINT} ${BASECOMMIT}" > .git/info/grafts
git filter-branch -- --all
git push project-recent master
git push project-recent B
这是project-recent
回购的结果拆分内容。
git log --graph --oneline --decorate --all
* 2335aeb (B) branchB b2
* 2bb7ea3 branchB b1
| * 83c3ae9 (HEAD -> master) sixth
|/
* 25931c5 fifth
* 1e1e201 fourth
* a7f3373 Get history from historical repository
请注意,根提交a7f3373
是我们人工创建的BASECOMMIT,并且其提交日志可以包含一条消息,该消息将用户指向具有项目历史记录的存储库位置,从而允许将来的用户使用git replace
重新加入两个存储库如果需要的话。 可以下载完整的过程作为可复制的脚本 ,但下面也将其包括在内以供参考。
在我们的实际案例中,我们唯一的其他主要问题是试图确定哪些分支应该推入历史存储库,哪些分支应该推入最近的存储库。 但是,此答案显示了拆分本身是如何完成的,以创建两个存储库。
#!/bin/bash
WORKDIR=${PWD}
create_repos () {
rm -rf repo-split-example repo-split-recent repo-split-history
# Create the repo to be split
example_repo
# Create the repo to contain the historical commits
HISTREPO="file://${WORKDIR}/repo-split-history"
mkdir ../repo-split-history
cd ../repo-split-history/
git init --bare
cd ../repo-split-example
git remote add project-history $HISTREPO
# Create the repo to contain the recent commits
RECEREPO="file://${WORKDIR}/repo-split-recent"
mkdir ../repo-split-recent
cd ../repo-split-recent/
git init --bare
cd ../repo-split-example
git remote add project-recent $RECEREPO
}
example_repo () {
# Part I: set up a test repo with our example commits
mkdir repo-split-example
cd repo-split-example/
git init
echo "We want to split the repository into project-recent and project-history portions, following the instructions at https://git-scm.com/book/en/v2/Git-Tools-Replace., but also including branches." > README.md
echo " "
echo "First commit." >> README.md
git add README.md
git commit -m "first"
echo "Second commit." >> README.md
git add README.md
git commit -m "second"
git checkout -b A HEAD
echo "Add Branch A change." >> README.md
git add README.md
git commit -m "branchA a1"
git checkout master
echo "Third commit." >> README.md
git add README.md
git commit -m "third"
TRUNCPARENT=`git rev-parse HEAD`
echo "Fourth commit." >> README.md
git add README.md
git commit -m "fourth"
TRUNCPOINT=`git rev-parse HEAD`
echo "Fifth commit." >> README.md
git add README.md
git commit -m "fifth"
FIFTH=`git rev-parse HEAD`
git checkout -b B HEAD
echo "Add Branch B change. b1" >> README.md
git add README.md
git commit -m "branchB b1"
B1=`git rev-parse HEAD`
echo "Add Branch B change. b2" >> README.md
git add README.md
git commit -m "branchB b2"
B2=`git rev-parse HEAD`
git checkout master
echo "Sixth commit." >> README.md
git add README.md
git commit -m "sixth"
# Now we have a repo with the requisite structure, ready to be split
git log --graph --all --oneline --decorate
}
split_repo () {
# Part II: Split the git repo into historical and current halves at $TRUNCPOINT
# Following guidelines at https://git-scm.com/book/en/v2/Git-Tools-Replace
# First create a branch for the historical commits
echo "Branching history at $TRUNCPOINT"
git branch history $TRUNCPOINT
git log --graph --oneline --decorate history A
# Now copy the history repo to the remote HISTREPO repository
git push project-history history:master
git push project-history A
# Now to split the repo to get the recent history from TRUNCPOINT to HEAD of master
# Create a base commit for the new new recent history
MESSAGE="Get history from historical repository at $HISTREPO"
BASECOMMIT=`echo $MESSAGE | git commit-tree ${TRUNCPARENT}^{tree}`
# Split the repository by grafting the TRUNCPARENT onto BASECOMMIT
echo "${TRUNCPOINT} ${BASECOMMIT}" > .git/info/grafts
git filter-branch -- --all
# Finally, push the current rewritten master and associated branches to a new repository
git push project-recent master
git push project-recent B
}
create_repos
split_repo
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.