git - 如何使用 `git format-patch` 和 `git am` 将文件从一个 git repo 移动到另一个保留历史记录

标签 git

问题

我想将一个文件夹(和子文件夹包含的文件)从一个存储库移动到另一个存储库,以保留历史记录。

我在 SE 上找到了一种方法:How to move files from one git repo to another (not a clone), preserving history . 关于 blog.neutrino.es 的不同想法.这是我想在这里讨论的最后一个。

尝试的解决方案

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

如果我没理解错的话,这个想法是假装我们要通过电子邮件提交提交,然后将它们重新导入到另一个存储库中。

错误

我在执行 git am/tmp/mergepatchs/*.patch 时收到此错误消息:

Applying: Initial commit
error: .gitignore: already exists in index
error: README.md: already exists in index
Patch failed at 0001 Initial commit
The copy of the patch that failed is found in:
   /Users/myuser/repo/org/.git/rebase-apply/patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

为了更好地理解这个过程,我首先尝试了一个文件(而不是整个目录)。然而在 git am 之后“什么都没有”发生(即没有新文件,git status 不报告任何变化)。这是为什么?

然后我尝试了:

INITCOMMIT=$(git rev-list --parents HEAD | egrep "^[a-f0-9]{40}$")
git format-patch -1 -o /tmp/mergepatchs ${INITCOMMIT}

但是又得到了和之前一样的错误信息。

为什么补丁会失败?


编辑 1

我尝试了一些相关的东西,灵感来自 How to create and apply a patch with Git .

~/repo/org 中:

$ git format-patch --root HEAD --stdout myfile.c > /tmp/mergepaths/01.patch

~/depo/dest 中:

$ git apply --stat /tmp/mergepaths/01.patch 
 0 files changed
$ git apply --check /tmp/mergepaths/01.patch 
$ git am < /tmp/mergepaths/01.patch 

statcheck 都告诉我“什么都不做”。补丁对象远非空。 顺便说一句,我不知道这是否相关,但是补丁的创建和应用都是在分支中完成的。

最佳答案

有这样的手术工具,git filter-branch .

对于这么简单的事情,您不需要太多的安全网。不过,作为引用,这就是我做任何可能弄脏历史、命名空间或工作树的事情的方式

# make a throwaway sandbox to play in:
git clone -s . /tmp/deleteme
cd !$
git checkout -b sliced

# do it
git filter-branch --subdirectory-filter your/subdir

# and if the result looks good:
git push origin sliced

filter-branch docs

你可以推送到任何你有 url 或路径的 repo,只需直接使用 url 而不是为 onesy-twosie 工作创建远程名称。推送并重命名:git push u://r/l sliced:branchnameinthatrepo


从您想要选择和重新定位子目录的评论中。这是一些相当简单的 git read-tree工作。 Read-tree 对索引进行操作,因此您需要一个索引过滤器。也就是说,这个:

git filter-branch --index-filter '
        git read-tree --prefix=des/ti/nation/    $GIT_COMMIT:source/subdir
        git read-tree -m                         $GIT_COMMIT    `git mktree </dev/null`
'

尽管您不熟悉它,但如果您只记得 git 的工作原理,它就会非常简单。

作为提醒、回顾或介绍,视情况而定:

  1. 适当的存储库是一个对象存储:通过类型和唯一名称(又名 SHA1)请求任何东西,存储库会主动返回它;要求 repo 记住任何内容,你给它类型和字节,它 (a) 存储它,(b) 给你它的唯一名称。

  2. 索引只是一个列表,将路径名映射到存储库内容。

  3. git read-tree是 checkout 、 merge 和重置的基础操作——它实际上并不对存储库进行操作,它使用的所有对象都已经存在。您向它提供现有的树,它会将它们与索引中的内容结合起来(并可选择更新工作树,尽管这在这里无关紧要)以生成您想要的索引,或者至少让您更接近它。

    <

上面的第一个read-tree是

    git read-tree --prefix=destination/subdir/ $GIT_COMMIT:source/subdir

你可以确定什么的基本性质git read-tree将通过计算你给了它多少棵树来完成。这是一个单树读取,在这里用于添加到索引(编辑: 顺便说一句,没有选项,1-tree git read-tree replaces the index)。此处添加的树位于 source/subdir在被过滤的提交中,read-tree 使用 destination/subdir/ 将其全部添加到索引中添加到路径名的前面。

下一个读取树是一个二叉树读取,它为 git checkout 做索引(和工作树,如果你想在这里做的话)。 -- 它将原始树和目标树之间的差异应用于索引。在这里,原始树是 $GIT_COMMIT和目标树,git mktree </dev/null , 是空树。所以操作是“在索引中找到原始树中的所有内容,并使所有这些条目看起来与目标树完全一样”,也就是这里“让它们全部消失”。上面添加的目标子目录不涉及,它不在原始树中,因此读取树不会使它消失。

过滤器已完成,filter-branch 提交新索引中的内容(记住,已经在 repo 中的内容),是时候进行下一次提交了。

read-tree docs .此链接跳过描述,这是故意的。不要读它。

关于git - 如何使用 `git format-patch` 和 `git am` 将文件从一个 git repo 移动到另一个保留历史记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28830916/

相关文章:

ios - 执行:git reset --hard HEAD后如何恢复

c++ - CMake + 查找包或 checkout 并安装

git - 将 "git git"别名为 "git"?

git - 自定义 Git 提交消息模板

git - Git-当两个 Remote 具有相同标签名称时 checkout 一个远程标签

git - 在 git show-ref -d 的上下文中取消引用是什么

git - 使用命令行 Git 检查分支

ios - 如何防止 Xcode 自动选择团队配置文件?

git - 在 bash 脚本中执行 git 命令

xcode - 创建项目后创建git仓库