【问题标题】:How to prepend the past to a git repository?如何将过去添加到 git 存储库?
【发布时间】:2010-06-30 07:08:38
【问题描述】:

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

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

【问题讨论】:

标签: git


【解决方案1】:

对于导入旧快照,您会发现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 的“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、树、提交或带注释的标签),因此该机制也更通用。替换机制记录在git replace manpage 中。由于通用性,“前置”设置涉及更多(我们必须创建一个新提交,而不是仅仅命名新父级):

# 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

【讨论】:

  • 谢谢,这对于一个小的测试子集非常有用,现在要完整的了:)(我使用了替换选项)
  • 目前这对我来说不是问题,但我还是会问:在git filter-branching 之前使用替换选项不会重写历史记录,因此更容易分享对吧?
  • 如果没有 git filter branch,移植和替换都不会真正重写历史(它们只会对提交 DAG 产生影响,就好像它们已经重写了历史一样)。替换的好处是 1)它们可以通过命令行参数或环境变量禁用,2)它们可以被推送/获取,3)它们可以在任何对象上工作,而不仅仅是提交的父“属性”。推送替换的能力使它们易于通过普通的 Git 协议共享(您可以共享移植条目,但您必须使用一些“带外”机制(即不推送/获取)来传播它们)。
  • 阅读您的“取消删除”问题,我看到有问题的文件在快照中,但不在您的 Git 历史记录中。如果你有一些 Git 历史的标签,我会这样做:从你的原始 Git 存储库开始(在任何过滤或重写之前;如果你没有简单的备份/克隆,请参阅 refs/original/),使用 @987654341 @(或--index-filter)将文件添加到您的历史记录,同时重写其标签,然后执行移植/替换和git filter-branch --tag-name-filter cat -- --all 永久建立移植/替换。
  • 警告:Git 2.18(2018 年第二季度)中已移除移植物。请参阅“What are .git/info/grafts for?”。
【解决方案2】:

如果您不想更改存储库中的提交,您可以使用移植来覆盖提交的父信息。这就是 Linux Kernel repo 在他们开始使用 Git 之前所做的事情。

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

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

【讨论】:

【解决方案3】:

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

【讨论】:

    【解决方案4】:

    如果您只想永久合并 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,您还可以使用SmartGitGitExtensions 等git 接口

    参考资料:

    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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-06
      • 1970-01-01
      • 2018-09-02
      • 2010-09-12
      相关资源
      最近更新 更多