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

时间:2015-03-03 11:40:55

标签: 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都告诉我&#34;没有&#34;将会完成。补丁对象远非空。 顺便说一句,我不知道这是否相关,但补丁的创建和应用都是在分支机构中完成的。

2 个答案:

答案 0 :(得分:10)

这样的手术工具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

你可以推送到你有网址或路径的任何仓库,只需直接使用网址,而不是为偶数工作制作一个远程名称。推送和重命名: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进行操作,它所使用的所有对象都已存在。你给现有的树提供它,它将它们与索引中的内容结合起来(并且可选地更新工作树,虽然这里不相关)以产生你想要的索引,或者至少让你更接近一步它

  4. 上面的第一个阅读树是

        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是空树。因此,操作是&#34;在索引中找到原始树中的所有内容,并使所有条目看起来与目标树完全相同。又名在这里&#34;让它们全部消失&#34;。上面添加的目标子目录不涉及,它不在原始树中,因此read-tree不会使其消失。

    过滤器的完成,filter-branch提交新索引中的内容(已经在repo中的内容,请记住)以及下次提交的时间。

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

答案 1 :(得分:2)

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

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