如何使用“git format patch”和“git am”将文件从一个git repo移动到另一个保存历史



问题

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

我在SE上找到了一种方法:如何将文件从一个git-reo移动到另一个git-reo(而不是克隆),保存历史。在blog.neutrin.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

我尝试了一些相关的东西,灵感来自如何使用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

过滤分支文档

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


从注释中,您既要选择子目录,也要重新定位子目录。这是一些相当简单的git 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是签出、合并和重置的底层操作——它实际上并没有对repo进行操作,它使用的所有对象都已经存在。你给它提供现有的树,它将它们与索引中的内容相结合(并有选择地更新工作树,尽管这在这里无关紧要),以产生你想要的索引,或者至少让你离它更近一步。

上面的第一个读取树是

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

你可以通过计算你给了git read-tree多少棵树来确定它的基本性质。这是一个单树读取,用于添加到索引中(edit:btw,没有选项,1-tree git read-tree代替索引)。这里添加的树位于正在筛选的提交中的source/subdir,读取树将其全部添加到索引中,destination/subdir/添加到路径名的前面。

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

过滤器完成后,过滤器分支提交新索引中的内容(记住,存储库中已经存在的内容),是时候进行下一次提交了。

读取树文档。这个链接跳过了描述,这是故意的。不要读。

看起来您遇到的问题是,您试图从补丁中创建的两个存储库中都存在某些文件。在您的示例中,这些是README.md和.gitignore

使用git-apply应用修补程序时,可以使用--exclude=<path-pattern>忽略这些文件。看见http://git-scm.com/docs/git-apply

最新更新