问题:是否有一种方法可以“分叉”Git存储库,使得分叉(位于GitHub上)上没有保留任何历史记录,但我的本地存储库仍具有完整的历史记录,并且我可以向GitHub推送/拉取?
我没有任何大型存储库管理方面的经验,所以详细信息将非常感激。
您可以在Git中轻松创建一个新的、全新的历史记录。假设您想要将您的 master
分支作为要推送到GitHub的分支,而您希望完整的历史记录存储在 old-master
中。您只需要将您的 master
分支移动到 old-master
,然后使用git checkout --orphan
开始一个没有历史记录的全新分支即可:
git branch -m master old-master
git checkout --orphan master
git commit -m "Import clean version of my code"
现在你有一个没有历史记录的新的master
分支,你可以将其推送到GitHub。但是,正如你所说,你希望能够在本地仓库中看到所有的旧历史记录;并且可能希望不要断开连接。
你可以使用git replace
来实现这一点。替换引用是一种指定任何时候Git查看给定提交的替代提交的方法。因此,当查看历史记录时,你可以告诉Git查看旧分支的最后一次提交,而不是新分支的第一次提交。为了做到这一点,你需要将旧版本库中的断开的历史记录引入。
git replace master old-master
现在您有了新的分支,其中可以看到您所有的历史记录,但实际的提交对象与旧历史记录断开连接,因此您可以将新提交推送到GitHub而不会连带旧提交一起推送。将master
分支推送到GitHub,只有新的提交会被推送到GitHub。但是在gitk
或git log
中查看历史记录,您将看到完整的历史记录。
git push github master:master
gitk --all
需要注意的地方
如果你基于旧的提交记录创建新分支,你需要小心保持这些历史记录的独立性。否则,在这些分支上进行新的提交将会包含旧的提交记录,并且如果你将其推送到GitHub,就会一起推送整个历史记录。只要确保所有的新提交都是基于你的新master
而产生的,你就不用担心这个问题。
如果你运行git push --tags github
,那么它会推送所有标签,包括旧的标签,这将导致所有旧的历史记录被一起推送。你可以通过删除所有旧的标签(git tag -d $(git tag -l)
),或者从未使用git push --tags
,仅手动推送标签,或者像下面描述的那样使用两个存储库来解决这个问题。
这两个注意事项的基本问题是,如果你推送任何与旧历史记录相关联的引用(除了通过替换提交之外的引用),你将推送所有旧的历史记录。避免这种情况最好的方法可能是使用两个存储库:一个只包含新提交,另一个包含旧和新的完整历史记录,用于检查完整的历史记录。你在存储只有新提交的存储库中执行所有工作、提交、从GitHub推送和拉取;这样,你就不可能意外地将旧的提交推送出去。
然后,每当需要查看完整历史记录时,你可以将所有新提交拉到具有完整历史记录的存储库中。你可以从GitHub或本地存储库中拉取,哪个更方便就使用哪个。它将成为你的归档,但为了避免意外发布旧历史记录,你不会从中向GitHub推送任何东西。以下是设置方法:
~$ mkdir newrepo ~$ cd newrepo newrepo$ git init newrepo$ git pull ~/oldrepo master # 现在 newrepo 只有新的历史记录;我们可以设置 oldrepo 从中拉取 newrepo$ cd ~/oldrepo oldrepo$ git remote add newrepo ~/newrepo oldrepo$ git remote update oldrepo$ git branch --set-upstream master newrepo/master # ... 在 newrepo 中完成工作、提交、推送到GitHub等操作 # 现在如果我们想在 oldrepo 中查看完整历史记录: oldrepo$ git pull
如果你使用的 Git 版本早于 1.7.2
你没有 git checkout --orphan
命令,所以你需要通过从现有存储库的当前修订版本创建新存储库,然后拉取旧的断开连接的历史记录来手动完成此操作。你可以使用以下命令:
oldrepo$ mkdir ~/newrepo oldrepo$ cp $(git ls-files) ~/newrepo oldrepo$ cd ~/newrepo newrepo$ git init newrepo$ git add . newrepo$ git commit -m "Import clean version of my code" newrepo$ git fetch ~/oldrepo master:old-master
如果你使用的 Git 版本早于 1.6.5
git replace
和替换引用是在 1.6.5 中添加的,所以你需要使用一个较旧但有些不够灵活的机制,称为grafts,它允许你为
echo $(git rev-parse master) $(git rev-parse old-master) >> .git/info/grafts
这将使得在本地,master
提交看起来像是以 old-master
提交为其父提交,因此您将看到比使用 git replace
更多的提交。
git replace
而不是 grafts;我相信后者已经被弃用了。http://progit.org/2010/03/17/replace.html http://www.kernel.org/pub/software/scm/git/docs/git-replace.html - Emil Sitgit checkout --orphan
(Git 1.7.2及更高版本)省去了额外的存储库(以及复制文件等操作):git branch -m master old-master && git checkout --orphan master && git commit -m 'initial public release'
。 - Chris Johnsennewrepo
是在你设置old-master
和新孤立的master
分支于oldrepo
之后进行的步骤。这样,oldrepo
就拥有了完整的历史记录(可以从newrepo
拉取新的提交),而newrepo
则是你进行新工作并推向GitHub的地方,以避免意外推送一些旧的历史记录。 - Brian CampbellBrian的回答 看起来很全面且专业,但有点复杂。
更简单的解决方案是保持两个仓库。
一个私有的GitHub仓库,你在上面工作。你把所有完整历史记录推送到该仓库。
第二个仓库是一个公共的GitHub仓库,你只在想要“发布”新版本时才将其发布到该仓库。你可以使用简单的差异+补丁,然后提交+推送来发布它。
一个非常简单有趣的方法如下 -
假设您在REPO-A中有提交C1到C10,其中C1是初始提交,而C10是最新的HEAD。您想创建一个新的REPO-B,使其具有提交C4到C8(一个子集)。
注意:使用此方法将更改提交SHA(例如,在此情况下,将更改为C4'至C8'),但每个提交所包含的更改将保持不变,并且您的第一个提交现在将从所有更改开始 您之前的提交到该点组合起来。
我应该怎么做?
cp -R REPO-A REPO-B
可选择从REPO-B中删除所有远程存储库,因为您很可能希望将其用作独立的代码库。
cd REPO-B
git remote -v
git remote remove REMOTE_NAME
git checkout -b temp
git branch -f master C8
git checkout master
git branch -D temp
.git/info/grafts
目录中输入您的子集中较早一端的提交 SHA。在本例中,它是提交 C4 的 SHA。git rev-parse --verify C4 >> .git/info/grafts
不使用任何参数进行 Git 分支过滤:
git filter-branch
git filter-branch --all
git remote add origin NEWREMOTE
git push -u origin master
这个链接告诉你如何实际操作 - http://git.661346.n2.nabble.com/how-to-delete-the-entire-history-before-a-certain-commit-td5000540.html
你可以在 git-filter-branch(1) 手册、gitrepository-layout(5) Git 仓库布局说明和 gitglossary(7) Git 词汇表中了解有关嫁接的信息。
简而言之,.git/info/grafts 中的每一行都包含一个对象的 SHA-1 ID,后面是其有效(嫁接)父级的用空格分隔的列表。因此,要剪切历史记录,例如在提交 a3eb250f996bf5e 后,您需要在 .git/info/grafts 文件中放入一个只包含此 SHA-1 的行,例如:
$ git rev-parse --verify a3eb250f996bf5e >> .git/info/grafts