问题
我想将一个文件夹(以及子文件夹中包含的文件)从一个存储库移动到另一个,以保留历史记录。
我在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
stat
和check
都告诉我"什么都不做"。补丁对象远不是空的。顺便说一句,我不知道这是否相关,但补丁的创建和应用都是在分支中完成的。
有一个像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是如何工作的,尽管它并不熟悉,但它非常简单。
作为提醒或回顾或介绍,视情况而定:
存储库本身就是一个对象存储:根据类型和唯一名称(又名SHA1)要求任何东西,存储库会强制地将其反汇编;要求repo记住任何内容,你给它输入类型和字节,它(a)存储它,(b)给你它的唯一名称。
索引只是一个列表,将路径名映射到那里的存储库内容。
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