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

Hug*_*lle 2 git

问题

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

我在SE上找到了一种方法:如何将文件从一个git repo移动到另一个(不是克隆),保留历史记录.还有一个关于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
Run Code Online (Sandbox Code Playgroud)

如果我理解正确,我的想法是假装我们将通过电子邮件提交提交,并在另一个存储库中重新导入它们.

错误

我在执行以下操作时收到此错误消息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".
Run Code Online (Sandbox Code Playgroud)

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

然后我尝试了:

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

但后来得到了和以前一样的错误信息.

为什么补丁失败了?


编辑1

我尝试了一些相关的东西,灵感来自如何使用Git创建和应用补丁.

~/repo/org:

$ git format-patch --root HEAD --stdout myfile.c > /tmp/mergepaths/01.patch
Run Code Online (Sandbox Code Playgroud)

~/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 
Run Code Online (Sandbox Code Playgroud)

双方statcheck告诉我说:"没事"就搞定.补丁对象远非空.顺便说一句,我不知道这是否相关,但补丁的创建和应用都是在分支机构中完成的.

jth*_*ill 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
Run Code Online (Sandbox Code Playgroud)

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`
'
Run Code Online (Sandbox Code Playgroud)

如果你只记得git是如何工作的,那么尽管它不熟悉是很简单的.

作为提醒或回顾或介绍的情况可能是:

  1. 存储库本身就是一个对象存储:按类型和唯一名称(也就是SHA1)要求任何东西,回购服务器强制要求它回流; 要求repo记住任何内容,你输入它的类型和字节,它(a)存储它们和(b)为它提供唯一的名称.

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

  3. git read-tree是结帐和合并和重置的基础操作 - 它实际上不在repo上运行,它所使用的所有对象都已存在.你给现有的树提供它,它将它们与索引中的内容结合起来(并且可选地更新工作树,尽管这里不相关)来生成你想要的索引,或者至少让你更接近它.

上面的第一个读树是

    git read-tree --prefix=destination/subdir/ $GIT_COMMIT:source/subdir
Run Code Online (Sandbox Code Playgroud)

你可以git read-tree通过计算你给它的树数来确定将要做的事情的基本性质.这是一个单树读取,这里用来添加到索引(编辑:顺便说一句,没有选项,1树git read-tree替换索引).这里添加的树是在source/subdir被过滤的提交中,并且read-tree将它全部destination/subdir/添加到索引并添加到路径名的前面.

下一个读取树是一个双树读取,它执行索引(和worktree,如果有任何你想在这里做的)工作git checkout- 它将原始树和目标树之间的差异应用于索引.这里,原始树$GIT_COMMIT和目标树git mktree </dev/null是空树.因此,操作是"在索引中查找原始树中的所有内容,并使所有条目看起来与目标树完全相同",这里也称为"让它们全部消失".上面添加的目标子目录不涉及,它不在原始树中,因此read-tree不会使其消失.

过滤器完成后,filter-branch提交新索引中的内容(已经在repo中的内容,请记住),并且是下次提交的时间.

读树文档.此链接会跳过描述,这是有意的.不读它.