如何将过去添加到git存储库?

时间:2010-06-30 07:08:39

标签: git

我收到了一些源代码并决定使用git,因为我的同事使用了mkdir $VERSION等方法。虽然代码的过去目前似乎并不重要,但我仍然希望将其置于git控制之下以更好地理解开发过程。所以:

将过去的版本放入我现有的git仓库有什么方便的方法?目前没有远程仓库,所以我不介意重写历史记录,但考虑到远程存储库的解决方案当然是首选,除非它更复杂。基于目录或基于档案文件的历史记录,不需要任何更多交互的脚本的加分点。

4 个答案:

答案 0 :(得分:23)

要导入旧快照,您会发现Git's contrib/fast-import directory中的一些工具很有用。或者,如果您已在目录中拥有每个旧快照,则可以执行以下操作:

# 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的历史记录后,您可以使用前置操作的几个选项:移植和替换。

Grafts是一种每个存储库机制,可以(可能暂时)编辑各种现有提交的父项。移植由$GIT_DIR/info/grafts文件控制(在gitrepository-layout manpage的“信息/移植”下描述)。

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,树,提交或带注释的标记),因此该机制也更通用。替换机制记录在git replace manpage中。由于一般性,“prepending”设置稍微复杂一些(我们必须创建一个新的提交,而不是仅仅命名新的提交):

# 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

答案 1 :(得分:3)

如果您不想更改存储库中的提交,可以使用grafts覆盖提交的父信息。这就是Linux Kernel repo在开始使用Git之前从中获取历史记录的方法。

此消息:http://marc.info/?l=git&m=119636089519572似乎有我能找到的最佳文档。

您将创建一系列与您的pre-git历史记录相关的提交,然后使用.git/info/grafts文件使Git使用该序列中的最后一次提交作为您使用Git生成的第一个提交的父级。

答案 2 :(得分:2)

最简单的方法当然是创建一个新的git repo,首先提交历史记录,然后重新应用旧repo的补丁。但我更喜欢一种自动化耗时较少的解决方案。

答案 3 :(得分:0)

如果您只想永久合并2个存储库,最好的解决方案是从第二个存储库中导出所有提交(初始提交除外,该初始提交将存储库创建为另一个存储库的延续)。

我认为这是最好的,因为按照Chris Johnsen的说明进行操作时,它将把您在第二个存储库中的初始提交转换为删除多个文件的删除提交。而且,如果您尝试跳过初始提交,它将把您的第二次提交转换为删除所有文件的提交(当然,我必须尝试一下)。我不确定它如何影响git在命令中以git log --follow -- file/name.txt

跟踪文件历史记录的能力

您可以导出第二个存储库的整个历史记录(期望第一个提交,它已经存在于第一个存储库中),然后将其导入运行以下命令的第一个存储库中:

  1. 在第二个存储库上打开Linux命令行(以导出最新的提交)
  2. commit_count=$(git rev-list HEAD --count)
  3. git format-patch --full-index -$(($commit_count - 1))
  4. 将在第二个存储库的根目录上创建的所有git补丁.patch文件移动到第一个存储库根目录侧的名为patches的新目录
  5. 现在,在第一个存储库中打开Linux命令行(以导入最新的提交)
  6. git am ../patches/*.patch
  7. 如果在应用git补丁时遇到问题,请运行git am --abort,然后查看git: patch does not apply并尝试git am ../patches/*.patch --ignore-space-change --ignore-whitespace之类的操作,如链接的StackOverflow问题上建议的那样。

或者从命令行使用git,您也可以使用git接口,例如SmartGitGitExtensions

参考文献:

  1. https://www.ivankristianto.com/create-patch-files-from-multiple-commits-in-git/
  2. Git: How to create patches for a merge?
  3. https://www.ivankristianto.com/create-patch-files-from-multiple-commits-in-git/
  4. how to apply multiple git patches in one shot
  5. https://davidwalsh.name/git-export-patch

为完整起见,我在这里展示一个自动化的shell脚本,该脚本遵循Chris Johnsen步骤永久合并2个存储库。您需要在第一个存储库上运行它,在这里您想集成第二个存储库的历史记录,第二个存储库的历史记录将继续第一个存储库的历史记录。经过几个小时的试验,我发现这是最好的方法。如果您知道自己如何改进,请修复/共享/评论。

请先将您的第一个和第二个存储库都完全备份到.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

参考文献:

  1. https://feeding.cloud.geek.nz/posts/combining-multiple-commits-into-one/
  2. https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-replace.html
  3. Remove the last line from a file in Bash
  4. Force "git push" to overwrite remote files
  5. Git force push tag when the tag already exists on remote