我收到了一些源代码并决定使用git进行管理,因为我的同事使用了 mkdir $VERSION
等方法。尽管过去的代码目前似乎不重要,但我仍然希望将它放在git控制下以更好地理解开发过程。因此:
有没有便捷的方法将那些过去的版本放入我已经存在的git仓库中?当前没有远程仓库,所以我不介意重写历史记录,但当然会优先考虑需要考虑到远程仓库的解决方案,除非它比较复杂。如果能提供一个脚本,可以基于目录或者存档文件的历史记录,不需要任何交互就可以进行操作,则额外加分。
我收到了一些源代码并决定使用git进行管理,因为我的同事使用了 mkdir $VERSION
等方法。尽管过去的代码目前似乎不重要,但我仍然希望将它放在git控制下以更好地理解开发过程。因此:
有没有便捷的方法将那些过去的版本放入我已经存在的git仓库中?当前没有远程仓库,所以我不介意重写历史记录,但当然会优先考虑需要考虑到远程仓库的解决方案,除非它比较复杂。如果能提供一个脚本,可以基于目录或者存档文件的历史记录,不需要任何交互就可以进行操作,则额外加分。
如果要导入旧的快照,你可以在Git的contrib/fast-import目录中找到一些有用的工具。或者,如果你已经将每个旧的快照放在一个目录中,你可以像这样做:
# Assumes the v* glob will sort in the right order
# (i.e. zero padded, fixed width numeric fields)
# For v1, v2, v10, v11, ... you might try:
# v{1..23} (1 through 23)
# v?{,?} (v+one character, then v+two characters)
# v?{,?{,?}} (v+{one,two,three} characters)
# $(ls -v v*) (GNU ls has "version sorting")
# Or, just list them directly: ``for d in foo bar baz quux; do''
(git init import)
for d in v*; do
if mv import/.git "$d/"; then
(cd "$d" && git add --all && git commit -m"pre-Git snapshot $d")
mv "$d/.git" import/
fi
done
(cd import && git checkout HEAD -- .)
cd work && git fetch ../import master:old-history
一旦你把旧的历史记录和基于Git的历史记录放在同一个仓库中,你就有几个选项可以进行前置操作:嫁接和替换。
嫁接是一种每个仓库都有的机制,用于(可能是暂时地)编辑各种现有提交的父级关系。嫁接由$GIT_DIR/info/grafts
文件控制(在gitrepository-layout manpage的“info/grafts”下有描述)。
INITIAL_SHA1=$(git rev-list --reverse master | head -1)
TIP_OF_OLD_HISTORY_SHA1=$(git rev-parse old-history)
echo $INITIAL_SHA1 $TIP_OF_OLD_HISTORY_SHA1 >> .git/info/grafts
将嫁接操作完成后(最初的初始提交没有任何父节点,嫁接操作为其提供了一个父节点),您可以使用所有正常的Git工具搜索和查看扩展历史记录(例如,git log
现在应该会在您提交后显示旧的历史记录)。
嫁接操作的主要问题是它们仅限于您的存储库。但是,如果您决定它们应成为历史记录的永久部分,您可以使用git filter-branch来实现这一点(首先备份您的.git
目录的tar / zip备份;git filter-branch将保存原始引用,但有时只使用普通备份更容易)。
git filter-branch --tag-name-filter cat -- --all
rm .git/info/grafts
替换机制是较新的特性(Git 1.6.5+),但可以在每个命令中基于需要禁用它们(git --no-replace-objects …
),并且它们可以被推送以便更易于共享。替换功能适用于单独的对象(blob、tree、commit或带注释的tag),因此这种机制也更为通用。替换机制在git replace手册页中有详细记录。由于其通用性,所谓的“前置”设置涉及到了一些额外的操作(我们需要创建一个新的提交而不只是指定新的父提交):
# the last commit of old history branch
oldhead=$(git rev-parse --verify old-history)
# the initial commit of current branch
newinit=$(git rev-list master | tail -n 1)
# create a fake commit based on $newinit, but with a parent
# (note: at this point, $oldhead must be a full commit ID)
newfake=$(git cat-file commit "$newinit" \
| sed "/^tree [0-9a-f]\+\$/aparent $oldhead" \
| git hash-object -t commit -w --stdin)
# replace the initial commit with the fake one
git replace -f "$newinit" "$newfake"
分享这个替换并不是自动的。您需要推送refs/replace
的一部分(或全部)来共享替换。
git push some-remote 'refs/replace/*'
如果您决定将替换操作永久化,使用git filter-branch(与嫁接一样;首先备份.git
目录的tar/zip文件):
git filter-branch --tag-name-filter cat -- --all
git replace -d $INITIAL_SHA1
refs/original/
),使用filter-branch --tag-name-filter cat --tree-filter … -- --all
(或--index-filter
)将文件添加到您的历史记录中,同时重写其标签,然后进行嫁接/替换和git filter-branch --tag-name-filter cat -- --all
以永久建立嫁接/替换。 - Chris Johnsen.git/info/grafts
文件使Git使用该序列中的最后一个提交作为第一个使用Git生成的提交的父提交。当然,最简单的方法是创建一个新的git repo,将历史记录首先提交,然后重新应用旧repo的补丁。但我更喜欢一种通过自动化来节省时间的解决方案。
如果您只想永久合并两个存储库,最好的解决方案是从第二个存储库中导出所有提交(除了初始提交,该提交将创建存储库作为另一个存储库的延续)。
我认为这是最好的方法,因为当您按照Chris Johnsen所解释的步骤执行时,它将将第二个存储库上的初始提交转换为删除提交,从而删除多个文件。如果您尝试跳过初始提交,它将将您的第二个提交转换为删除所有文件的提交(当然,我不得不尝试一下)。我不确定它如何影响git在命令行中跟踪文件历史的能力,例如git log --follow -- file/name.txt
您可以导出第二个存储库的整个历史记录(除了第一个提交,该提交已经存在于第一个存储库中),并运行以下命令将其导入到第一个存储库中:
在您的第二个存储库上打开Linux命令行(以导出最新提交)。 ``` commit_count=$(git rev-list HEAD --count) git format-patch --full-index -$(($commit_count - 1)) ``` 将所有在第二个存储库根目录创建的git补丁`.patch`文件移动到名为`patches`的新目录中,在第一个存储库根目录的旁边。 现在,在您的第一个存储库上打开Linux命令行(以导入最新提交)。 ``` git am ../patches/*.patch ``` 如果应用git补丁时遇到问题,请运行`git am --abort`,然后查看 git:patch does not apply,并尝试像链接的StackOverflow问题建议的那样运行`git am ../patches/*.patch --ignore-space-change --ignore-whitespace`。除了使用命令行中的 git
,您还可以使用像 SmartGit 或 GitExtensions 这样的 git 接口。
参考资料:
为了完整起见,这里提供一个自动化的shell脚本,遵循Chris Johnsen的步骤来永久合并两个仓库。您需要在第一个仓库上运行此脚本,其中您想要将第二个仓库的历史记录集成到第一个仓库中,第二个仓库继续第一个仓库的历史记录。经过几个小时的实验,我发现这是最好的方法。如果您知道如何改进某些内容,请修复/共享/评论。
在运行此操作之前,请完全备份您的第一个和第二个仓库到.zip
文件中。
old_history=master
new_history=master-temp
old_remote_name=deathaxe
old_remote_url=second_remote_url
git remote add $old_remote_name $old_remote_url
git fetch $old_remote_name
git branch --no-track $new_history refs/remotes/$old_remote_name/$old_history
git branch --set-upstream-to=origin/$old_history $new_history
# the last commit of old history branch
oldhead=$(git rev-parse --verify $old_history)
# the initial commit of current branch
# newinit=$(git rev-list $new_history | tail -n 2 | head -n -1)
newinit=$(git rev-list $new_history | tail -n 1)
# create a fake commit based on $newinit, but with a parent
# (note: at this point, $oldhead must be a full commit ID)
newfake=$(git cat-file commit "$newinit" \
| sed "/^tree [0-9a-f]\+\$/aparent $oldhead" \
| git hash-object -t commit -w --stdin)
# replace the initial commit with the fake one
# git replace <last commit> <first commit>
# git replace <object> <replacement>
git replace -f "$newinit" "$newfake"
# If you decide to make the replacement permanent, use git filter-branch
# (make a tar/zip backup of your .git directory first)
git filter-branch --tag-name-filter cat -- --all
git replace -d $newinit
git push -f --tags
git push -f origin $new_history
git checkout $old_history
git branch -d $new_history
git pull --rebase
参考资料: