如何在保留历史记录的同时将 git repo 重新植根到父文件夹?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3212485/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I Re-root a git repo to a parent folder while preserving history?
提问by masonk
I have a git repo in /foo/bar/baz
with a large commit history and multiple branches.
我有一个/foo/bar/baz
带有大量提交历史和多个分支的 git repo 。
I now want /foo/qux
to be in the same repo as /foo/bar/baz
, which means that I need them both to be in a repo rooted at /foo
. However, I want to preserve history of changes that I've made to /foo/bar/baz
.
现在我想/foo/qux
是在同一回购的/foo/bar/baz
,这意味着我需要他们既要在根的回购/foo
。但是,我想保留我对/foo/bar/baz
.
I first thought of git format-patch followed by apply, but commit messages aren't preserved.
我首先想到了 git format-patch,然后是 apply,但不保留提交消息。
So,
所以,
I need to reroot the repo
我需要重新root repo
(1) to an arbitrarily higher ancestor directory
(2) while preserving my commit history by making it look like I've been comitting to /foo/bar/baz
all along
(1)到任意较高祖先目录(2)同时使它保持我的提交历史看起来像我一直comitting到/foo/bar/baz
带齐
采纳答案by Walter Mundt
What you want is git filter-branch
, which can move a whole repository into a subtree, preserving history by making it look as if it's always been that way. Back up your repository before using this!
您想要的是git filter-branch
,它可以将整个存储库移动到子树中,通过使其看起来好像一直都是这样来保留历史记录。在使用之前备份您的存储库!
Here's the magic. In /foo/bar
, run:
这就是魔法。在/foo/bar
,运行:
git filter-branch --commit-filter '
TREE="";
shift;
SUBTREE=`echo -e 040000 tree $TREE"\tbar" | git mktree`
git commit-tree $SUBTREE "$@"' -- --all
That will make the /foo/bar
repository have another 'bar' subdirectory with all its contents throughout its whole history. Then you can move the entire repo up to the foo
level and add baz
code to it.
这将使/foo/bar
存储库具有另一个“bar”子目录,其中包含其整个历史记录中的所有内容。然后,您可以将整个 repo 上移到该foo
级别并向其添加baz
代码。
Update:
更新:
Okay, here's what's going on. A commit is a link to a "tree" (think of it as a SHA representing a whole filesystem subdirectory's contents) plus some "parent" SHA's and some metadata link author/message/etc. The git commit-tree
command is the low-level bit that wraps all this together. The parameter to --commit-filter
gets treated as a shell function and run in place of git commit-tree
during the filter process, and has to act like it.
好的,这是怎么回事。提交是指向“树”的链接(将其视为代表整个文件系统子目录内容的 SHA)加上一些“父”SHA 和一些元数据链接作者/消息/等。该git commit-tree
命令是低级位用于包装所有这一起。参数 to--commit-filter
被视为外壳函数并在git commit-tree
过滤过程中代替运行,并且必须像它一样运行。
What I'm doing is taking the first parameter, the original tree to commit, and building a new "tree object" that says it's in a subfolder via git mktree
, another low-level git command. To do that, I have to pipe into it something that looks like a git tree i.e. a set of (mode SP type SP SHA TAB filename) lines; thus the echo command. The output of mktree
is then substituted for the first parameter when I chain to the real commit-tree
; "$@"
is a way to pass all the other parameters intact, having stripped the first off with shift
. See git help mktree
and git help commit-tree
for info.
我正在做的是获取第一个参数,要提交的原始树,并构建一个新的“树对象”,通过git mktree
另一个低级 git 命令表示它位于子文件夹中。要做到这一点,我必须向它输入一些看起来像 git 树的东西,即一组(模式 SP 类型 SP SHA TAB 文件名)行;因此是 echo 命令。mktree
当我链接到 real 时,的输出将替换第一个参数commit-tree
;"$@"
是一种完整地传递所有其他参数的方法,用shift
. 查看git help mktree
和git help commit-tree
了解信息。
So, if you need multiple levels, you have to nest a few extra levels of tree objects (this isn't tested but is the general idea):
因此,如果您需要多个级别,则必须嵌套一些额外级别的树对象(这未经过测试,但这是总体思路):
git filter-branch --commit-filter '
TREE=""
shift
SUBTREE1=`echo -e 040000 tree $TREE"\tbar" | git mktree`
SUBTREE2=`echo -e 040000 tree $SUBTREE1"\tb" | git mktree`
SUBTREE3=`echo -e 040000 tree $SUBTREE2"\ta" | git mktree`
git commit-tree $SUBTREE3 "$@"' -- --all
That should shift the real contents down into a/b/bar
(note the reversed order).
这应该将实际内容向下移动a/b/bar
(注意相反的顺序)。
Update: Integrated improvements From Matthew Alpert's answer below. Without -- --all
this only works on the currently-checked out branch, but since the question is asking about a whole repo, it makes more sense to do it this way than branch-by-branch.
更新:综合改进来自下面 Matthew Alpert 的回答。如果没有-- --all
这仅适用于当前签出的分支,但由于问题是询问整个存储库,因此这样做比逐个分支更有意义。
回答by Andrew Aylett
Rather than create a new repository, move what's in your current repository into the right place: create a new directory bar
in your current directory and move the current content in (so your code is in /foo/bar/bar
). Then create a baz
directory next to your new bar
directory (/foo/bar/baz
). mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2
and you're done :).
与其创建新的存储库,不如将当前存储库中的内容移动到正确的位置:bar
在当前目录中创建一个新目录并将当前内容移动到其中(因此您的代码在 中/foo/bar/bar
)。然后baz
在新bar
目录 ( /foo/bar/baz
)旁边创建一个目录。 mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2
你就完成了:)。
Git's rename tracking means that your history will still work and Git's hashing of content means that even though you've moved things around, you're still referencing the same objects in the repository.
Git 的重命名跟踪意味着您的历史记录仍然有效,而 Git 的内容散列意味着即使您移动了内容,您仍然引用存储库中的相同对象。
回答by Jon Carter
I had a solution no one seems to have said yet:
我有一个似乎没有人说过的解决方案:
What I specifically needed was to include files from the parent directory in my repository (effectively moving the repo up one directory).
我特别需要的是将父目录中的文件包含在我的存储库中(有效地将 repo 向上移动一个目录)。
I achieved this via:
我通过以下方式实现了这一目标:
- move all files (except for .git) into a new subdirectory with the same name. And tell git about it(with
git mv
) - move all files from the parent directory into the now empty (except for .git/) current directory and tell git about it(with
git add
) - commit the whole thing into the repo, which hasn'tmoved (
git commit
). - move the current directory up one level in the directory hierarchy. (with command line jiggery-pokery)
- 将所有文件(.git 除外)移动到具有相同名称的新子目录中。并告诉 git(使用
git mv
) - 将所有文件从父目录移动到现在为空的(除了 .git/)当前目录 并告诉 git(使用
git add
) - 将整个内容提交到未移动 (
git commit
)的 repo 中。 - 将当前目录在目录层次结构中上移一级。(使用命令行 jiggery-pokery)
I hope this helps the next guy to come along -- I'm probably just having a brainless day, but I found the answers above over-elaborate and scary (for what Ineeded.) I know this is similar to Andrew Aylett's answer above, but my situation seemed a little different and I wanted a more general view.
我希望这可以帮助下一个人 - 我可能只是度过了无脑的一天,但我发现上面的答案过于详尽和可怕(对于我需要的东西。)我知道这类似于上面 Andrew Aylett 的答案,但我的情况似乎有点不同,我想要一个更一般的看法。
回答by Vic Seedoubleyew
Most common solution
最常见的解决方案
In most normal situations, git looks at all files relatively to its location (meaning the .git directory), as opposed to using absolute file paths.
在大多数正常情况下,git 会根据其位置(即 .git 目录)查看所有文件,而不是使用绝对文件路径。
Thus, if you don't mind having a commit in your history which shows that you have moved everything up, there is a very simple solution, which consists in moving the git directory. The only slightly tricky thing is to make sure git understands that the files are the same and that they only moved relatively to him :
因此,如果您不介意历史记录中的提交表明您已将所有内容向上移动,那么有一个非常简单的解决方案,即移动 git 目录。唯一有点棘手的是确保 git 理解文件是相同的,并且它们只是相对于他移动:
# Create sub-directory with the same name in /foo/bar
mkdir bar
# Move everything down, notifying git :
git mv file1 file2 file3 bar/
# Then move everything up one level :
mv .git ../.git
mv bar/* .
mv .gitignore ../
# Here, take care to move untracked files
# Then delete unused directory
rmdir bar
# and commit
cd ../
git commit
The only thing to be careful, is to correctly update .gitignore when moving to the new directory, to avoid staging unwanted files, or forgetting some.
唯一需要注意的是,在移动到新目录时正确更新 .gitignore,以避免暂存不需要的文件或忘记一些文件。
Bonus solution
奖金解决方案
In some settings, git manages to figure out by itself that files have been moved when it sees new files that are exactly the same as deleted files. In that case, the solution is even simpler :
在某些设置中,当 git 看到与已删除文件完全相同的新文件时,它会设法自行确定文件已被移动。在这种情况下,解决方案更简单:
mv .git ../.git
mv .gitignore ../.gitignore
cd ../
git commit
Again, be careful with your .gitignore
再次,小心你的 .gitignore
回答by Matthew Alpert
This adds to Walter Mundt's accepted answer. I would have rather commented on his answer, but I don't have the reputation.
这增加了 Walter Mundt 接受的答案。我宁愿评论他的回答,但我没有声誉。
So Walter Mundt's method works great, but it works only for one branch at a time. And after the first branch, there may be warnings that require -f to force the action through. So to do this for all branches at once, simply add "-- --all" to the end:
所以 Walter Mundt 的方法效果很好,但它一次只对一个分支有效。在第一个分支之后,可能会出现需要 -f 来强制执行操作的警告。因此,要一次对所有分支执行此操作,只需在末尾添加“---all”:
git filter-branch --commit-filter '
tree="";
shift;
subtree=`echo -e 040000 tree $tree"\tsrc" | git mktree`
git commit-tree $subtree "$@"' -- --all
And to do this for specific branches, add their names to the end instead, although I can't imagine why you would change the directory structure of only some of the branches.
并且要对特定分支执行此操作,请将它们的名称添加到末尾,尽管我无法想象您为什么只更改某些分支的目录结构。
Read more about this in the man page for git filter-branch. However, please notice the warning about possible difficulties pushing after using such a command. Just make sure you know what you're doing.
在 git filter-branch 的手册页中阅读有关此内容的更多信息。但是,请注意使用此类命令后可能会出现推送困难的警告。只要确保你知道你在做什么。
I'd appreciate more input on any potential problems with this method.
我希望能就这种方法的任何潜在问题提供更多意见。
回答by cueedee
This specifically answers "how do I move my git repo up one or more directories and make it look like it was always that way?"
这特别回答了“我如何将我的 git repo 向上移动一个或多个目录并使它看起来总是那样?”
With the advent of git >= 2.22.0
git filter-repo
can be leveraged to rewrite history to appear as if that parent directory had always been part of it.
随着git >= 2.22.0
git filter-repo
可以被利用来重写历史记录,就像父目录一直是它的一部分一样。
This is the same thing that @Walter-Mundt's answeraccomplishes using git filter-branch
, but is simpler and not as fragile to execute.
这与@Walter-Mundt 的答案使用 完成的事情相同git filter-branch
,但更简单,执行起来也不那么脆弱。
Note that these days git filter-repo
is advertised by git filter-branch
itself as the safer alternative.
请注意,这些天本身就git filter-repo
被宣传git filter-branch
为更安全的选择。
So, given that your repo lives in /foo/bar/baz
and you want to move it up to /foo
因此,鉴于您的存储库位于其中/foo/bar/baz
并且您希望将其移动到/foo
First, to prevent any changes to the files in the workspace while history is being rewritten, temporarily turn the repository into a so-called "bare"one like this:
首先,为了防止在重写历史记录时对工作区中的文件进行任何更改,请暂时将存储库变成所谓的“裸”存储库,如下所示:
cd /foo/bar/baz
git config --local --bool core.bare true
The actual history rewriting can now be done directly in the .git
directory itself:
实际的历史重写现在可以直接在.git
目录中完成:
cd ./.git
git filter-repo --path-rename :bar/baz/
This will rewrite the repo's complete history as if every path has always had bar/baz/
prepended to it. The actual files are untouched by this operation because this is a bare repository now.
这将重写 repo 的完整历史记录,就好像每条路径总是bar/baz/
预先存在一样。实际文件不受此操作影响,因为它现在是一个裸存储库。
To wrap up, turn it un-bare again, move the .git
directory up to its designated position, and reset:
要结束,再次将其打开,将.git
目录向上移动到指定位置,然后重置:
git config --local --bool core.bare false
cd ..
mv ./.git ../..
cd ../..
git reset
I think, the git reset
cancels the after-effects of the repository having been turned bare and back again. Try a git status
before doing git reset
to see what I mean.
我认为,这git reset
取消了存储库被关闭并再次返回的后果。git status
在做之前先试试看git reset
我的意思。
A final git status
should now prove that all is well, modulo some new untracked files in /foo/qux
to deal with.
git status
现在最终应该证明一切都很好,对一些新的未跟踪文件进行模数/foo/qux
处理。
CAVEAT- if you try the above on an un-cloned repository, git filter-repo
will refuse to do its magic unlessyou --force
it to... Have a backup at the ready and consider yourself warned.
CAVEAT- 如果您在未克隆的存储库上尝试上述操作,git filter-repo
将拒绝发挥其魔力,除非您--force
...准备好备份并考虑自己警告。
回答by Joeblade
1 addition to the accepted answer, which helped me get it to work: when I put the listed text in a shell script, for some reason the -e was kept. (quite likely because I am too thick to work with shell scripts)
1 除了已接受的答案,这帮助我让它工作:当我将列出的文本放入 shell 脚本时,出于某种原因保留了 -e。(很可能是因为我太厚,无法使用 shell 脚本)
when I removed the -e , and moved the quotes to encompass everything, it worked.
SUBTREE2=echo "040000 tree $SUBTREE1 modules" | git mktree
当我删除 -e 并移动引号以包含所有内容时,它起作用了。子树2=echo "040000 tree $SUBTREE1 modules" | git mktree
note that there is a tab between $SUBTREE1 and modules , which is the same \t that -e should interpret.
请注意, $SUBTREE1 和 modules 之间有一个制表符,它与 -e 应该解释的 \t 相同。
回答by VonC
You could create a git repo in foo
and reference both baz
and bar
through git submodules
.
你可以创建一个混帐回购协议foo
,并同时引用baz
和bar
通过git submodules
。
Then both bar
and baz
coexist with their full history preserved.
然后两者并存,bar
并baz
保留了它们的完整历史。
If you really want only one repo (foo), with both bar and baz history in it, then some grafts technique or subtree merge strategyare in order.
如果你真的只想要一个 repo (foo),其中包含 bar 和 baz 历史,那么一些移植技术或子树合并策略是合适的。