The Easy Way™
It turns out that this is such a common and useful practice that the overlords of Git made it really easy, but you have to have a newer version of Git (>= 1.7.11 May 2012). See the appendix for how to install the latest Git. Also, there's a real-world example in the walkthrough below.
事实证明,这是一个如此普遍和有用的做法,以至于Git的霸主们让它变得非常容易,但你必须有一个更新版本的Git(>=1.7.11 2012年5月)。有关如何安装最新的Git,请参阅附录。此外,在下面的演练中还有一个真实世界的示例。
Prepare the old repo
cd <big-repo>
git subtree split -P <name-of-folder> -b <name-of-new-branch>
Note: <name-of-folder>
must NOT contain leading or trailing characters. For instance, the folder named subproject
MUST be passed as subproject
, NOT ./subproject/
注意:<文件夹名称>不得包含前导或尾随字符。例如,名为subproject的文件夹必须作为subproject传递,而不是./subproject/
Note for Windows users: When your folder depth is > 1, <name-of-folder>
must have *nix style folder separator (/). For instance, the folder named path1\path2\subproject
MUST be passed as path1/path2/subproject
Windows用户注意:当文件夹深度>1时,<文件夹名称>必须有*nix样式的文件夹分隔符(/)。例如,名为路径1\路径2\子项目的文件夹必须作为路径1/路径2/子项目传递
Create the new repo
mkdir ~/<new-repo> && cd ~/<new-repo>
git init
git pull </path/to/big-repo> <name-of-new-branch>
Link the new repo to GitHub or wherever
git remote add origin <[email protected]:user/new-repo.git>
git push -u origin master
Cleanup inside <big-repo>
, if desired
git rm -rf <name-of-folder>
Note: This leaves all the historical references in the repository. See the Appendix below if you're actually concerned about having committed a password or you need to decreasing the file size of your .git
folder.
注意:这会将所有历史引用保留在存储库中。如果您确实担心已提交密码或需要减小.git文件夹的文件大小,请参阅下面的附录。
Walkthrough
These are the same steps as above, but following my exact steps for my repository instead of using <meta-named-things>
.
这些步骤与上面的步骤相同,但是遵循我的存储库的确切步骤,而不是使用
。
Here's a project I have for implementing JavaScript browser modules in node:
下面是我的一个项目,用于在节点中实现Java浏览器模块:
tree ~/node-browser-compat
node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator
I want to split out a single folder, btoa
, into a separate Git repository
我想将单个文件夹btoa拆分到单独的Git存储库中
cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only
I now have a new branch, btoa-only
, that only has commits for btoa
and I want to create a new repository.
我现在有了一个新的分支,仅限btoa,它只提交了btoa,并且我想创建一个新的存储库。
mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only
Next, I create a new repo on GitHub or Bitbucket, or whatever and add it as the origin
接下来,我在GitHub或BitBucket上创建一个新的repo,并将其添加为原点
git remote add origin [email protected]:node-browser-compat/btoa.git
git push -u origin master
Happy day!
快乐的一天!
Note: If you created a repo with a README.md
, .gitignore
and LICENSE
, you will need to pull first:
注意:如果您创建了具有Readme.md、.gitignore和许可证的回购,则需要首先提取:
git pull origin master
git push origin master
Lastly, I'll want to remove the folder from the bigger repo
最后,我想从较大的存储库中删除该文件夹
git rm -rf btoa
Appendix
Latest Git on macOS
To get the latest version of Git using Homebrew:
要使用自制软件获取最新版本的Git,请执行以下操作:
brew install git
Latest Git on Ubuntu
sudo apt-get update
sudo apt-get install git
git --version
If that doesn't work (you have a very old version of Ubuntu), try
如果这不起作用(你有一个非常旧的Ubuntu版本),试试
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
If that still doesn't work, try
如果这仍然不起作用,试一试
sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree
Thanks to rui.araujo from the comments.
感谢来自评论的rui.araujo。
Clearing your history
By default removing files from Git doesn't actually remove them, it just commits that they aren't there anymore. If you want to actually remove the historical references (i.e. you committed a password), you need to do this:
默认情况下,从Git中删除文件实际上并不会删除它们,它只是承诺它们不再存在。如果您想要实际删除历史引用(即您提交了密码),则需要执行以下操作:
git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD
After that, you can check that your file or folder no longer shows up in the Git history at all
在此之后,您可以检查您的文件或文件夹是否不再出现在Git历史记录中
git log -- <name-of-folder> # should show nothing
However, you can't "push" deletes to GitHub and the like. If you try, you'll get an error and you'll have to git pull
before you can git push
- and then you're back to having everything in your history.
然而,你不能把删除内容“推”到GitHub之类的网站上。如果你尝试,你会得到一个错误,你将不得不Git拉之前你可以Git推送-然后你又回到了你的历史上的一切。
So if you want to delete history from the "origin" - meaning to delete it from GitHub, Bitbucket, etc - you'll need to delete the repo and re-push a pruned copy of the repo. But wait - there's more! - if you're really concerned about getting rid of a password or something like that you'll need to prune the backup (see below).
因此,如果你想从“源”中删除历史--意思是从GitHub、BitBucket等中删除它--你需要删除回购并重新推送该回购的修剪后的副本。但等等--还有更多!--如果你真的担心删除密码或类似的东西,你需要删除备份(见下文)。
Making .git
smaller
The aforementioned delete history command still leaves behind a bunch of backup files - because Git is all too kind in helping you to not ruin your repo by accident. It will eventually delete orphaned files over the days and months, but it leaves them there for a while in case you realize that you accidentally deleted something you didn't want to.
前面提到的删除历史记录命令仍然会留下一堆备份文件--因为Git在帮助您避免意外破坏repo方面做得太好了。它最终会在几天或几个月的时间里删除孤立的文件,但它会让它们保留一段时间,以防你意识到你不小心删除了你不想删除的东西。
So if you really want to empty the trash to reduce the clone size of a repo immediately you have to do all of this really weird stuff:
因此,如果您真的想要立即清空垃圾桶以减少回购的克隆大小,您必须执行所有这些非常奇怪的操作:
rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now
git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune
That said, I'd recommend not performing these steps unless you know that you need to - just in case you did prune the wrong subdirectory, y'know? The backup files shouldn't get cloned when you push the repo, they'll just be in your local copy.
也就是说,我建议不要执行这些步骤,除非您知道您需要这样做--以防您确实删除了错误的子目录,您知道吗?当您推送回购时,备份文件不应该被克隆,它们只会在您的本地副本中。
Credit
Update: This process is so common, that the git team made it much simpler with a new tool, git subtree
. See here: Detach (move) subdirectory into separate Git repository
更新:这一过程非常常见,Git团队使用新工具git subtree使其变得简单得多。查看此处:将子目录分离(移动)到单独的Git存储库中
You want to clone your repository and then use git filter-branch
to mark everything but the subdirectory you want in your new repo to be garbage-collected.
您想要克隆您的存储库,然后使用git Filter-BRANCH来标记除了您希望在新存储库中被垃圾收集的子目录之外的所有内容。
To clone your local repository:
git clone /XYZ /ABC
(Note: the repository will be cloned using hard-links, but that is not a problem since the hard-linked files will not be modified in themselves - new ones will be created.)
Now, let us preserve the interesting branches which we want to rewrite as well, and then remove the origin to avoid pushing there and to make sure that old commits will not be referenced by the origin:
cd /ABC
for i in branch1 br2 br3; do git branch -t $i origin/$i; done
git remote rm origin
or for all remote branches:
cd /ABC
for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
git remote rm origin
Now you might want to also remove tags which have no relation with the subproject; you can also do that later, but you might need to prune your repo again. I did not do so and got a WARNING: Ref 'refs/tags/v0.1' is unchanged
for all tags (since they were all unrelated to the subproject); additionally, after removing such tags more space will be reclaimed. Apparently git filter-branch
should be able to rewrite other tags, but I could not verify this. If you want to remove all tags, use git tag -l | xargs git tag -d
.
Then use filter-branch and reset to exclude the other files, so they can be pruned. Let's also add --tag-name-filter cat --prune-empty
to remove empty commits and to rewrite tags (note that this will have to strip their signature):
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
or alternatively, to only rewrite the HEAD branch and ignore tags and other branches:
git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
Then delete the backup reflogs so the space can be truly reclaimed (although now the operation is destructive)
git reset --hard
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git reflog expire --expire=now --all
git gc --aggressive --prune=now
and now you have a local git repository of the ABC sub-directory with all its history preserved.
Note: For most uses, git filter-branch
should indeed have the added parameter -- --all
. Yes that's really --space-- all
. This needs to be the last parameters for the command. As Matli discovered, this keeps the project branches and tags included in the new repo.
注意:对于大多数用途,GIT Filter-BRANCH确实应该添加参数-all。是的,那真的是--空间--全部。这需要是该命令的最后一个参数。正如Matli发现的那样,这使项目分支和标记包含在新的repo中。
Edit: various suggestions from comments below were incorporated to make sure, for instance, that the repository is actually shrunk (which was not always the case before).
编辑:下面的评论中的各种建议被纳入,例如,确保存储库实际上是缩小的(以前并不总是这样)。
Paul's answer creates a new repository containing /ABC, but does not remove /ABC from within /XYZ. The following command will remove /ABC from within /XYZ:
Paul的回答创建了一个包含/abc的新存储库,但没有从/XYZ中删除/abc。以下命令将从/XYZ中删除/abc:
git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD
Of course, test it in a 'clone --no-hardlinks' repository first, and follow it with the reset, gc and prune commands Paul lists.
当然,首先在“克隆--无硬链接”存储库中测试它,然后使用Paul列出的Reset、GC和prune命令。
I’ve found that in order to properly delete the old history from the new repository, you have to do a little more work after the filter-branch
step.
我发现,为了正确地从新存储库中删除旧历史记录,您必须在过滤器分支步骤之后做一些更多的工作。
Do the clone and the filter:
git clone --no-hardlinks foo bar; cd bar
git filter-branch --subdirectory-filter subdir/you/want
Remove every reference to the old history. “origin” was keeping track of your clone, and “original” is where filter-branch saves the old stuff:
git remote rm origin
git update-ref -d refs/original/refs/heads/master
git reflog expire --expire=now --all
Even now, your history might be stuck in a packfile that fsck won’t touch. Tear it to shreds, creating a new packfile and deleting the unused objects:
git repack -ad
There is an explanation of this in the manual for filter-branch.
在过滤器分支的手册中有对此的解释。
When running git filter-branch
using a newer version of git
(2.22+
maybe?), it says to use this new tool git-filter-repo. This tool certainly simplified things for me.
当使用较新版本的Git(2.22+可能?)运行git过滤器分支时,它会提示使用这个新工具git-Filter-repo。这个工具当然为我简化了事情。
Filtering with filter-repo
Commands to create the XYZ
repo from the original question:
根据原始问题创建XYZ回购的命令:
# create local clone of original repo in directory XYZ
tmp $ git clone [email protected]:user/original.git XYZ
# switch to working in XYZ
tmp $ cd XYZ
# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2
# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)
# XYZ $ ls -1
# XY1
# XY2
# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2
# point at new hosted, dedicated repo
XYZ $ git remote add origin [email protected]:user/XYZ.git
# push (and track) remote master
XYZ $ git push -u origin master
assumptions:
* remote XYZ repo was new and empty before the push
假设:*远程XYZ回购在推送之前是新的和空的
Filtering and moving
In my case, I also wanted to move a couple of directories for a more consistent structure. Initially, I ran that simple filter-repo
command followed by git mv dir-to-rename
, but I found I could get a slightly "better" history using the --path-rename
option. Instead of seeing last modified 5 hours ago
on moved files in the new repo I now see last year
(in the GitHub UI), which matches the modified times in the original repo.
在我的例子中,我还想移动几个目录以获得更一致的结构。最初,我先运行简单Filter-repo命令,然后运行git mv dir-to-rename,但我发现使用--path-rename选项可以获得稍微好一点的历史记录。我现在看到的不是5小时前在新的repo中移动的文件的最后一次修改,而是去年(在GitHub用户界面中),这与原始repo中的修改时间相匹配。
Instead of...
而不是。
git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3 # which updates last modification time
I ultimately ran...
我最终跑了..。
git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Notes:
- I thought the Git Rev News blog post explained well the reasoning behind creating yet another repo-filtering tool.
- I initially tried the path of creating a sub-directory matching the target repo name in the original repository and then filtering (using
git filter-repo --subdirectory-filter dir-matching-new-repo-name
). That command correctly converted that subdirectory to the root of the copied local repo, but it also resulted in a history of only the three commits it took to create the subdirectory. (I hadn't realized that --path
could be specified multiple times; thereby, obviating the need to create a subdirectory in the source repo.) Since someone had committed to the source repo by the time I noticed that I'd failed to carry forward the history, I just used git reset commit-before-subdir-move --hard
after the clone
command, and added --force
to the filter-repo
command to get it to operate on the slightly modified local clone.
git clone ...
git reset HEAD~7 --hard # roll back before mistake
git filter-repo ... --force # tell filter-repo the alterations are expected
- I was stumped on the install since I was unaware of the extension pattern with
git
, but ultimately I cloned git-filter-repo and symlinked it to $(git --exec-path)
:
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)
Edit: Bash script added.
编辑:添加了Bash脚本。
The answers given here worked just partially for me; Lots of big files remained in the cache. What finally worked (after hours in #git on freenode):
这里给出的答案对我来说只起到了部分作用;很多大文件仍然留在缓存中。最终奏效的(在#git on freenode的几个小时后):
git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now
With the previous solutions, the repository size was around 100 MB. This one brought it down to 1.7 MB. Maybe it helps somebody :)
在以前的解决方案中,存储库大小约为100 MB。这一次,它的内存降到了1.7MB。也许它对某人有帮助:)
The following bash script automates the task:
以下bash脚本自动执行该任务:
!/bin/bash
if (( $# < 3 ))
then
echo "Usage: $0 </path/to/repo/> <directory/to/extract/> <newName>"
echo
echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
exit 1
fi
clone=/tmp/${3}Clone
newN=/tmp/${3}
git clone --no-hardlinks file://$1 ${clone}
cd ${clone}
git filter-branch --subdirectory-filter $2 --prune-empty --tag-name-filter cat -- --all
git clone file://${clone} ${newN}
cd ${newN}
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now
This is no longer so complex you can just use the git filter-branch command on a clone of you repo to cull the subdirectories you don't want and then push to the new remote.
这不再是那么复杂,您只需在您的repo的克隆上使用git Filter-Branch命令来剔除您不需要的子目录,然后推送到新的远程目录。
git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
Update: The git-subtree module was so useful that the git team pulled it into core and made it git subtree
. See here: Detach (move) subdirectory into separate Git repository
更新:git-subtree模块非常有用,Git团队将其放入core,并将其创建为git子树。查看此处:将子目录分离(移动)到单独的Git存储库中
git-subtree may be useful for this
Git-subtree可能对此很有用
http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (deprecated)
Http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt(已弃用)
http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/
Http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/
Here is a small modification to CoolAJ86's "The Easy Way™" answer in order to split multiple sub folders (let's say sub1
and sub2
) into a new git repository.
这里是对CoolAJ86的S“The Easy Way™”答案的一个小修改,目的是将多个子文件夹(比方说sub1和sub2)拆分到一个新的git存储库中。
The Easy Way™ (multiple sub folders)
Prepare the old repo
pushd <big-repo>
git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
git subtree split -P <name-of-folder> -b <name-of-new-branch>
popd
Note: <name-of-folder>
must NOT contain leading or trailing characters. For instance, the folder named subproject
MUST be passed as subproject
, NOT ./subproject/
Note for windows users: when your folder depth is > 1, <name-of-folder>
must have *nix style folder separator (/). For instance, the folder named path1\path2\subproject
MUST be passed as path1/path2/subproject
. Moreover don't use mv
command but move
.
Final note: the unique and big difference with the base answer is the second line of the script "git filter-branch...
"
Create the new repo
mkdir <new-repo>
pushd <new-repo>
git init
git pull </path/to/big-repo> <name-of-new-branch>
Link the new repo to Github or wherever
git remote add origin <gi[email protected]:my-user/new-repo.git>
git push origin -u master
Cleanup, if desired
popd # get out of <new-repo>
pushd <big-repo>
git rm -rf <name-of-folder>
Note: This leaves all the historical references in the repository.See the Appendix in the original answer if you're actually concerned about having committed a password or you need to decreasing the file size of your .git
folder.
The original question wants XYZ/ABC/(*files) to become ABC/ABC/(*files). After implementing the accepted answer for my own code, I noticed that it actually changes XYZ/ABC/(*files) into ABC/(*files). The filter-branch man page even says,
原始问题希望XYZ/ABC/(*FILES)变成ABC/ABC/(*FILES)。在为我自己的代码实现了被接受的答案之后,我注意到它实际上将XYZ/ABC/(*FILES)更改为ABC/(*FILES)。筛选器分支手册页甚至说,
The result will contain that directory (and only that) as its project root."
In other words, it promotes the top-level folder "up" one level. That's an important distinction because, for example, in my history I had renamed a top-level folder. By promoting folders "up" one level, git loses continuity at the commit where I did the rename.
换句话说,它将顶级文件夹提升了一个级别。这是一个重要的区别,因为,例如,在我的历史中,我曾重新命名一个顶级文件夹。通过将文件夹“向上”提升一级,Git在提交时失去了连续性,我在提交时进行了重命名。
My answer to the question then is to make 2 copies of the repository and manually delete the folder(s) you want to keep in each. The man page backs me up with this:
我对这个问题的回答是复制两份存储库,然后手动删除您想要保留在每个副本中的文件夹(S)。手册页为我提供了这样的支持:
[...] avoid using [this command] if a simple single commit would suffice to fix your problem
To add to Paul's answer, I found that to ultimately recover space, I have to push HEAD to a clean repository and that trims down the size of the .git/objects/pack directory.
为了补充Paul的回答,我发现为了最终恢复空间,我必须将HEAD推送到一个干净的存储库,这会缩小.git/Objects/pack目录的大小。
i.e.
即
$ mkdir ...ABC.git
$ cd ...ABC.git
$ git init --bare
After the gc prune, also do:
在gc prune之后,还可以:
$ git push ...ABC.git HEAD
Then you can do
然后你就可以做
$ git clone ...ABC.git
and the size of ABC/.git is reduced
并缩小了abc/.git的大小
Actually, some of the time consuming steps (e.g. git gc) aren't needed with the push to clean repository, i.e.:
实际上,清理存储库的推送不需要一些耗时的步骤(例如git GC),即:
$ git clone --no-hardlinks /XYZ /ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ...ABC.git HEAD
It appears that most (all?) of the answers here rely on some form of git filter-branch --subdirectory-filter
and its ilk. This may work "most times" however for some cases, for instance the case of when you renamed the folder, ex:
看起来大多数(全部?)这里的答案依赖于某种形式的git筛选器-分支-子目录-筛选器及其类似工具。然而,这在“大多数情况下”可能适用于某些情况,例如,当您重命名文件夹时,例如:
ABC/
/move_this_dir # did some work here, then renamed it to
ABC/
/move_this_dir_renamed
If you do a normal git filter style to extract "move_this_dir_renamed" you will lose file change history that occurred from back when it was initially "move_this_dir" (ref).
如果你做一个普通的GIT过滤器样式来解压“move_this_dir_rename”,你将丢失最初是“move_this_dir”(Ref)时从后面发生的文件更改历史。
It thus appears that the only way to really keep all change history (if yours is a case like this), is, in essence, to copy the repository (create a new repo, set that to be the origin), then nuke everything else and rename the subdirectory to the parent like this:
因此,看起来真正保存所有更改历史的唯一方法(如果您的更改历史是这样的),本质上是复制存储库(创建一个新的repo,将其设置为源),然后删除其他所有内容,并将子目录重命名为父目录,如下所示:
- Clone the multi-module project locally
- Branches - check what's there:
git branch -a
- Do a checkout to each branch to be included in the split to get a local copy on your workstation:
git checkout --track origin/branchABC
- Make a copy in a new directory:
cp -r oldmultimod simple
- Go into the new project copy:
cd simple
- Get rid of the other modules that aren't needed in this project:
git rm otherModule1 other2 other3
- Now only the subdir of the target module remains
- Get rid of the module subdir so that the module root becomes the new project root
git mv moduleSubdir1/* .
- Delete the relic subdir:
rmdir moduleSubdir1
- Check changes at any point:
git status
- Create the new git repo and copy its URL to point this project into it:
git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
- Verify this is good:
git remote -v
- Push the changes up to the remote repo:
git push
- Go to the remote repo and check it's all there
- Repeat it for any other branch needed:
git checkout branch2
This follows the github doc "Splitting a subfolder out into a new repository" steps 6-11 to push the module to a new repo.
这遵循GitHub文档“将子文件夹拆分到新的存储库”的步骤6-11,以将模块推送到新的存储库。
This will not save you any space in your .git folder, but it will preserve all your change history for those files even across renames. And this may not be worth it if there isn't "a lot" of history lost, etc. But at least you are guaranteed not to lose older commits!
这不会在.git文件夹中为您节省任何空间,但它将保留这些文件的所有更改历史记录,即使重命名也是如此。如果历史没有丢失,这可能是不值得的,等等。但至少你可以保证不会丢失旧的提交!
Proper way now is the following:
现在正确的做法是:
git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]
Git筛选器-BRANCH--PRUNE-EMPTY--子目录-筛选器文件夹名称[First_BRANCH][Another_BRANCH]
GitHub now even have small article about such cases.
GitHub现在甚至有一篇关于这类案件的小文章。
But be sure to clone your original repo to separate directory first (as it would delete all the files and other directories and you probable need to work with them).
但请确保首先将原始repo克隆到单独的目录中(因为这会删除所有文件和其他目录,您可能需要处理它们)。
So your algorithm should be:
所以你的算法应该是:
- clone your remote repo to another directory
- using
git filter-branch
left only files under some subdirectory, push to new remote
- create commit to remove this subdirectory from your original remote repo
I recommend GitHub's guide to splitting subfolders into a new repository. The steps are similar to Paul's answer, but I found their instructions easier to understand.
我推荐GitHub的将子文件夹拆分到新存储库的指南。这些步骤与保罗的答案相似,但我发现他们的说明更容易理解。
I have modified the instructions so that they apply for a local repository, rather than one hosted on GitHub.
我已经修改了指令,使其适用于本地存储库,而不是托管在GitHub上的存储库。
Open Git Bash.
Change the current working directory to the location where you want to create your new repository.
Clone the repository that contains the subfolder.
git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
- Change the current working directory to your cloned repository.
cd REPOSITORY-NAME
- To filter out the subfolder from the rest of the files in the repository, run
git filter-branch
, supplying this information:
FOLDER-NAME
: The folder within your project that you'd like to create a separate repository from.
- Tip: Windows users should use
/
to delimit folders.
BRANCH-NAME
: The default branch for your current project, for example, master
or gh-pages
.
git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten
I had exactly this problem but all the standard solutions based on git filter-branch were extremely slow. If you have a small repository then this may not be a problem, it was for me. I wrote another git filtering program based on libgit2 which as a first step creates branches for each filtering of the primary repository and then pushes these to clean repositories as the next step. On my repository (500Mb 100000 commits) the standard git filter-branch methods took days. My program takes minutes to do the same filtering.
我确实遇到了这个问题,但是所有基于git过滤器分支的标准解决方案都非常慢。如果你有一个小的存储库,那么这可能不是问题,这对我来说是个问题。我编写了另一个基于libgit2的GIT过滤程序,作为第一步,它为主存储库的每个过滤创建分支,然后在下一步将这些分支推送到清除存储库。在我的存储库上(500MB 100000次提交),标准的GIT过滤器分支方法需要几天时间。我的程序只需要几分钟就可以完成同样的过滤。
It has the fabulous name of git_filter and lives here:
它有一个令人难以置信的名字git_Filter,生活在这里:
https://github.com/slobobaby/git_filter
Https://github.com/slobobaby/git_filter
on GitHub.
在GitHub上。
I hope it is useful to someone.
我希望它对某些人有用。
Use this filter command to remove a subdirectory, while preserving your tags and branches:
使用此筛选器命令删除子目录,同时保留您的标记和分支:
git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all
For what it's worth, here is how using GitHub on a Windows machine. Let's say you have a cloned repo in residing in C:\dir1
. The directory structure looks like this: C:\dir1\dir2\dir3
. The dir3
directory is the one I want to be a new separate repo.
以下是在Windows机器上使用GitHub的方法。假设您在C:\dir1中有一个克隆的repo。目录结构如下所示:C:\dir1\dir2\dir3。Di3目录是我想要成为一个新的单独回购的目录。
Github:
GitHub:
- Create your new repository:
MyTeam/mynewrepo
Bash Prompt:
Bash提示符:
$ cd c:/Dir1
$ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
Returned: Ref 'refs/heads/master' was rewritten
(fyi: dir2/dir3 is case sensitive.)
$ git remote add some_name [email protected]:MyTeam/mynewrepo.git
git remote add origin etc
. did not work, returned "remote origin already exists
"
$ git push --progress some_name master
As I mentioned above, I had to use the reverse solution (deleting all commits not touching my dir/subdir/targetdir
) which seemed to work pretty well removing about 95% of the commits (as desired). There are, however, two small issues remaining.
如上所述,我不得不使用相反的解决方案(删除所有未触及我的dir/subdir/Target dir的提交),这似乎很好地消除了大约95%的提交(根据需要)。然而,还有两个小问题仍然存在。
FIRST, filter-branch
did a bang up job of removing commits which introduce or modify code but apparently, merge commits are beneath its station in the Gitiverse.
首先,Filter-BRANCH做了一项出色的工作,删除了引入或修改代码的提交,但显然,合并提交位于它在Gitiverse中的位置之下。
This is a cosmetic issue which I can probably live with (he says...backing away slowly with eyes averted).
这是一个我可能可以忍受的表面问题(他说……慢慢地后退,眼睛避开)。
SECOND the few commits that remain are pretty much ALL duplicated! I seem to have acquired a second, redundant timeline that spans just about the entire history of the project. The interesting thing (which you can see from the picture below), is that my three local branches are not all on the same timeline (which is, certainly why it exists and isn't just garbage collected).
其次,剩下的几个提交几乎都是重复的!我似乎获得了第二个多余的时间表,它几乎跨越了项目的整个历史。有趣的是(您可以从下面的图片中看到),我的三个本地分支并不都在同一时间线上(这当然就是它存在的原因,而不仅仅是垃圾收集)。
The only thing I can imagine is that one of the deleted commits was, perhaps, the single merge commit that filter-branch
actually did delete, and that created the parallel timeline as each now-unmerged strand took its own copy of the commits. (shrug Where's my TARDiS?) I'm pretty sure I can fix this issue, though I'd really love to understand how it happened.
我能想象的唯一一件事是,其中一个被删除的提交可能是Filter-BRANCH实际删除的单个合并提交,它创建了平行的时间线,因为每个现在未合并的链都获得了它自己的提交副本。(耸耸肩,我的塔迪斯呢?)我非常肯定我可以解决这个问题,尽管我真的很想了解它是如何发生的。
In the case of crazy mergefest-O-RAMA, I'll likely be leaving that one alone since it has so firmly entrenched itself in my commit history—menacing at me whenever I come near—, it doesn't seem to be actually causing any non-cosmetic problems and because it is quite pretty in Tower.app.
就疯狂的Mergefest-O-Rama而言,我很可能不会去管它,因为它在我的承诺历史上根深蒂固--每当我接近它时,它就会威胁我--它似乎实际上并没有造成任何非美容的问题,因为它在Tower.app中相当漂亮。
The Easier Way
- install
git splits
. I created it as a git extension, based on jkeating's solution.
Split the directories into a local branch
#change into your repo's directory
cd /path/to/repo
#checkout the branch
git checkout XYZ
#split multiple directories into new branch XYZ
git splits -b XYZ XY1 XY2
Create an empty repo somewhere. We'll assume we've created an empty repo called xyz
on GitHub that has path : [email protected]:simpliwp/xyz.git
Push to the new repo.
#add a new remote origin for the empty repo so we can push to the empty repo on GitHub
git remote add origin_xyz [email protected]:simpliwp/xyz.git
#push the branch to the empty repo's master branch
git push origin_xyz XYZ:master
Clone the newly created remote repo into a new local directory
#change current directory out of the old repo
cd /path/to/where/you/want/the/new/local/repo
#clone the remote repo you just pushed to
git clone [email protected]:simpliwp/xyz.git
You might need something like "git reflog expire --expire=now --all" before the garbage collection to actually clean the files out. git filter-branch just removes references in the history, but doesn't remove the reflog entries that hold the data. Of course, test this first.
在垃圾收集之前,您可能需要类似于“git reflog expire--expire=now--all”这样的命令来实际清除文件。Git Filter-BRANCH只删除历史记录中的引用,但不删除保存数据的reflog条目。当然,首先要测试这一点。
My disk usage dropped dramatically in doing this, though my initial conditions were somewhat different. Perhaps --subdirectory-filter negates this need, but I doubt it.
在这样做的过程中,我的磁盘使用率大幅下降,尽管我的初始情况略有不同。也许--子目录过滤器否定了这一需求,但我对此表示怀疑。
Check out git_split project at https://github.com/vangorra/git_split
在https://github.com/vangorra/git_split上查看GIT_Split项目
Turn git directories into their very own repositories in their own location. No subtree funny business. This script will take an existing directory in your git repository and turn that directory into an independent repository of its own. Along the way, it will copy over the entire change history for the directory you provided.
在自己的位置将git目录转换为自己的存储库。没有子树有趣的事。该脚本将获取您的git存储库中的现有目录,并将该目录转换为自己的独立存储库。在此过程中,它将复制您提供的目录的整个更改历史。
./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
src_repo - The source repo to pull from.
src_branch - The branch of the source repo to pull from. (usually master)
relative_dir_path - Relative path of the directory in the source repo to split.
dest_repo - The repo to push to.
Put this into your gitconfig:
将以下内容放入您的gitconfig中:
reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'
I'm sure git subtree is all fine and wonderful, but my subdirectories of git managed code that I wanted to move was all in eclipse.
So if you're using egit, it's painfully easy.
Take the project you want to move and team->disconnect it, and then team->share it to the new location. It will default to trying to use the old repo location, but you can uncheck the use-existing selection and pick the new place to move it.
All hail egit.
我确信git子树是很好的,但是我想要移动的git托管代码的子目录都是不可移动的。因此,如果您使用的是egit,这是非常简单的。获取您想要移动的项目并将其团队->断开连接,然后团队->将其共享到新位置。它将默认尝试使用旧的回购位置,但您可以取消选中Use-Existing选项,并选择新的位置来移动它。万岁,万岁。
You can easily try the https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/
你可以很容易地试用https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/
This worked for me. The issues i faced in the steps given above are
这对我很管用。我在上面给出的步骤中面临的问题是
in this command git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME
The BRANCH-NAME
is master
if the last step fails when committing due to protection issue follow - https://docs.gitlab.com/ee/user/project/protected_branches.html
I've found quite straight forward solution,
The idea is to copy repository and then just remove unnecessary part.
This is how it works:
我已经找到了非常简单的解决方案,其想法是复制存储库,然后删除不必要的部分。它的工作原理如下:
1) Clone a repository you'd like to split
1)克隆要拆分的存储库
git clone [email protected]:testrepo/test.git
2) Move to git folder
2)移动到git文件夹
cd test/
2) Remove unnecessary folders and commit it
2)删除不必要的文件夹并提交
rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'
3) Remove unnecessary folder(s) form history with BFG
3)从BFG历史记录中删除不必要的文件夹(S)
cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive
for multiply folders you can use comma
java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git
4) Check that history doesn't contains the files/folders you just deleted
4)检查历史记录是否不包含您刚刚删除的文件/文件夹
git log --diff-filter=D --summary | grep delete
5) Now you have clean repository without ABC,
so just push it into new origin
5)现在你有了干净的仓库,没有ABC,所以只需将其推向新的来源
remote add origin [email protected]:username/new_repo
git push -u origin master
That's it. You can repeat the steps to get another repository,
就这样。您可以重复这些步骤以获取另一个存储库,
just remove XY1,XY2 and rename XYZ -> ABC on step 3
只需删除步骤3中的XY1、XY2并重命名为XYZ->ABC
Found this wonderful article Original reference easy to follow. Documenting it here in case if it get's inaccessible.
找到了这篇精彩的文章原创参考,简单易懂。在这里记录下来,以防无法访问。
1. Preparing the current repository
1.准备当前存储库
$ cd path/to/repository
$ git subtree split -P my-folder -b my-folder
Created branch 'my-folder'
aecbdc3c8fe2932529658f5ed40d95c135352eff
The name of the folder must be a relative path, starting from the root of the repository.
文件夹的名称必须是相对路径,从存储库的根目录开始。
2. Creating the new repository
2.创建新存储库
$ cd my-folder
$ git init
Initialized empty Git repository in /Users/adamwest/Projects/learngit/shop/my-folder/.git/
$ git add .
$ git commit -m "initial commit"
[master (root-commit) 192c10b] initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file
Here we just need to cd to the new folder, initialise the new repository, and commit any contents.
在这里,我们只需要用cd命令打开新文件夹,初始化新存储库,然后提交所有内容。
3.Add new remote repository and push
3.添加新的远程仓库并推送
$ git remote add origin [email protected]:robertlyall/my-folder.git
$ git push origin -u master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 199 bytes | 199.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:robertlyall/my-folder.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
We add the new repository remote from GitHub here, then push our first commit to it.
我们在这里添加远离GitHub的新存储库,然后将我们的第一个提交推送到它。
4. Remove folder from main repository and push
4.从主存储库中移除文件夹并推送
$ cd ../
$ git rm -rf my-folder
rm 'my-folder/file'
$ git commit -m "Remove old folder"
[master 56aedbe] remove old folder
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 my-folder/file
$ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 217 bytes | 217.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:robertlyall/shop.git
74dd8b3..56aedbe master -> master
Finally, we cd back to the rooot directory, remove the folder from our main repository, then commit and push the change.
Now, we have the folder in our main repository but linked to a completely separate repository that can be reused across multiple projects.
最后,我们用CD返回到根目录,从我们的主存储库中删除该文件夹,然后提交并推送更改。现在,我们的主存储库中有该文件夹,但它链接到一个完全独立的存储库,该存储库可以跨多个项目重复使用。
更多回答
@krlmlr sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s /usr/share/doc/git/contrib/subtree/git-subtree.sh /usr/lib/git-core/git-subtree To activate on Ubuntu 13.04
@krlmlr sudo chmod+x/usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln-S/usr/share/doc/git/contrib/subtree/git-subtree.sh/usr/lib/git-core/git-subtree在Ubuntu13.04上激活
If you have pushed a password to a public repository, you should change the password, not try to remove it from the public repo and hope nobody saw it.
如果您已经将密码推送到公共存储库,您应该更改密码,而不是尝试将其从公共资源库中删除,并希望没有人看到它。
This solution doesn't preserve history.
这种解决方案不能保存历史。
The popd
and pushd
command make this rather implicit and harder to grok what it intends to do ...
POPD和PUSH命令使得这一点相当隐含,并且更难理解它打算做什么……
Why do you need --no-hardlinks
? Removing one hardlink won't affect the other file. Git objects are immutable too. Only if you'd change owner/file permissions you need --no-hardlinks
.
你为什么需要--没有硬链接?删除一个硬链接不会影响另一个文件。Git对象也是不可变的。只有当您更改所有者/文件权限时才需要--无硬链接。
And if you want to rewrite your tags to not reference the old structure, add --tag-name-filter cat
如果您想重写标记以不引用旧结构,请添加--tag-name-Filter cat
Like Paul, I did not want project tags in my new repo, so I did not use -- --all
. I also ran git remote rm origin
, and git tag -l | xargs git tag -d
before the git filter-branch
command. This shrunk my .git
directory from 60M to ~300K. Note that I needed to run both of these commands to in order to get the size reduction.
像Paul一样,我不想在我的新repo中使用项目标签,所以我没有使用-all。在git Filter-BRANCH命令之前,我还运行了git emote rm Origin和git tag-L|xargs git tag-d。这会将我的.git目录从60M缩小到大约300K。请注意,我需要运行这两个命令才能减小大小。
The git man page recommends, instead of rm -rf .git/refs/original/
, git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
; I guess the latter is more robust if refs are not stored in the right place. Moreover, I believe that 'git remote rm origin' is also needed to shrink the repo, otherwise the refs from origin will keep objects referenced. @jonp, I think that was the problem for you. Finally, to also rewrite other branches, one must set them up manually with git branch
after cloninng, -- --all
and remove HEAD
(which stops rewriting of other branches).
Git手册页建议使用git for-each-ref--Format=“%(Refname)”refs/Origal/|xargs-n 1git update-ref-d,而不是rm-rf.git/refs/Original/;如果引用没有存储在正确的位置,我想后者会更可靠。此外,我认为‘git远程rm来源’也是需要的,以缩小repo,否则来自来源的引用将保持对象被引用。@jonp,我认为这就是你的问题。最后,要重写其他分支,必须在克隆之后使用git分支手动设置它们,-all并删除head(这将停止重写其他分支)。
Does this not create ABC/ instead of ABC/ABC/?
这不是创建abc/而不是abc/abc/吗?
make that git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEAD
and it will be much faster. index-filter works on the index while tree-filter has to checkout and stage everything for every commit.
将GIT筛选器-BRANCH--INDEX-FILTER“git rm-r-f--cached--Ignore-unMatch ABC”--prune-Empty Head设置为“Pune-Empty Head”,速度会快得多。索引筛选器在索引上工作,而树筛选器必须在每次提交时签出和暂存所有内容。
in some cases messing up the history of repository XYZ is overkill ... just a simple "rm -rf ABC; git rm -r ABC; git commit -m'extracted ABC into its own repo'" would work better for most people.
在某些情况下,扰乱存储库XYZ的历史是矫枉过正的……对于大多数人来说,只需简单的“rm-rf abc;git rm-r abc;git Commit-m‘将abc提取到自己的repo中’”会更好。
You probably wish to use -f (force) on this command if you do it more than once, e.g., to remove two directories after they have been separated. Otherwise you will get "Cannot create a new backup."
如果您多次使用-f(强制)命令,例如,要在分开后删除两个目录,则可能希望在此命令上使用-f(强制)。否则,您将得到“无法创建新备份”。
If you're doing the --index-filter
method, you may also want to make that git rm -q -r -f
, so that each invocation won't print a line for each file it deletes.
如果您正在使用--index-Filter方法,您可能还希望将git设置为rm-q-r-f,这样每次调用都不会为它删除的每个文件打印一行。
I would suggest editing Paul's answer, only because Paul's is so thorough.
我建议编辑保罗的答案,只是因为保罗的答案太彻底了。
I think somethink like git gc --aggressive --prune=now
is still missing, isn't it?
我认为像Git GC这样的人--咄咄逼人--Prune=Now仍然缺失,不是吗?
@Albert The repack command takes care of that, and there wouldn’t be any loose objects.
@Albert重新打包命令会处理这一点,并且不会有任何松散对象。
yeah, git gc --aggressive --prune=now
reduced much of new repo
是的,Git GC--积极的--修剪=现在减少了很多新的回购
Simple and elegant. Thanks!
简约典雅。谢谢!
And after all this I'm still getting the same error that I was getting before. fatal: packed object xxxxxx (stored in .git/objects/pack/pack-yyyyyyyy.pack) is corrupt
在经历了这一切之后,我仍然得到了和以前一样的错误。致命:打包对象xxxxxx(存储在.git/Objects/pack/pack-yyyyyyy.pack中)已损坏
Using git-filter-repo
should definitely be the preferred approach at this point. It’s much, much faster and safer than git-filter-branch
, and safeguards against a lot of the gotchas one can run into when rewriting one’s git history. Hopefully this answer gets some more attention, since it’s the one to address git-filter-repo
.
在这一点上,使用git-filter-repo肯定是首选的方法。它比git过滤器分支更快、更安全,而且可以防止人们在重写git历史时遇到的许多陷阱。希望这个答案能得到更多的关注,因为它是针对git-Filter-repo的答案。
acutally I am currently trying to get things to work with git filter-repo but unfortunately after running it, I am missing files, which were added in a commit, containing a path that was removed by filter-repo. For example: Foo/ Foo.cs Bar/ Bar.cs
All were added in the same commit. I want to move Foo and Bar in separate repos. So I cloned my repo in a folder matching the new repo name and did git filter-repo -path Foo
Foo get's removed too. I am talking about a much bigger repo and it is working for every other file but not if it's a constellation like this.
实际上,我目前正在尝试使用git Filter-repo,但不幸的是,在运行它之后,我丢失了在提交中添加的文件,其中包含一个被Filter-repo删除的路径。例如:Foo/Foo.cs Bar/Bar.cs都是在同一提交中添加的。我想把Foo和Bar分拆一下。因此,我将我的repo克隆到与新repo名称匹配的文件夹中,并删除了git Filter-repo-Path foo Foo‘s。我说的是更大的回购,它对所有其他文件都有效,但如果是这样的星座就不行。
If files were previously moved/renamed, this will not automatically retain the history before the move/rename. However, if you include the original paths/filenames in the command, that history will not be removed. For example, git filter-repo --path CurrentPathAfterRename --path OldPathBeforeRename
. git filter-repo --analyze
produces a file renames.txt that can be helpful in determining these. Alternatively, you may find a script like this helpful.
如果以前移动/重命名过文件,则不会自动保留移动/重命名之前的历史记录。但是,如果在命令中包含原始路径/文件名,则不会删除该历史记录。例如,Git Filter-repo--Path CurrentPath AfterRename--Path OldPath BepreRename。Git Filter-repo--Analyze生成一个文件renames.txt,该文件有助于确定这些参数。或者,您可能会发现这样的脚本很有帮助。
to move the subdir content to the root folder and keep only subdir files and corresponding history: git filter-repo --subdirectory-filter path/to/subdir
要将子目录内容移到根文件夹并仅保留子目录文件和相应的历史记录:Git Filter-repo--子目录-筛选器路径/to/subdir
This worked like a charm. YOUR_SUBDIR in the above example is the subdirectory that you want to KEEP, everything else will be removed
这就像是一种护身符。上例中的YOUR_SUBDIR是您要保留的子目录,其他所有内容都将被删除
Updates based on you comment.
根据您的评论进行更新。
This does not answer the question. From the docs it says The result will contain that directory (and only that) as its project root.
and indeed this is what you will get, i.e. the original project structure is not preserved.
这并没有回答这个问题。从文档中,它说结果将包含该目录(并且仅包含该目录)作为其项目根。事实上,这就是你将得到的,即原始的项目结构不会被保留。
@NicBright Can you illustrate your issue with XYZ and ABC as in the question, to show what's wrong?
@NicBright你能像问题中那样说明你对XYZ和ABC的问题,以表明哪里出了问题吗?
@jeremyjjbrown is it possible to reuse the cloned repo and not use a new repo, i.e. my question here stackoverflow.com/questions/49269602/…
@JeremyjjBrown是否可以重复使用克隆的repo而不使用新的repo,即我的问题在这里stackoverflow.com/Questions/49269602/…
git-subtree is now part of Git, although it's in the contrib tree, so not always installed by default. I know it is installed by the Homebrew git formula, but without its man page. apenwarr thus calls his version obsolete.
Git-subtree现在是Git的一部分,尽管它在conrib树中,所以并不总是默认安装。我知道它是按照Homebrew Git公式安装的,但没有手册页。因此,阿彭瓦尔称他的版本已经过时。
This worked for me with slight modification. Because my sub1
and sub2
folders didn't exist with the initial version, I had to modify my --tree-filter
script as follows: "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi"
. For the second filter-branch
command I replaced <sub1> with <sub2>, omitted creation of <name-of-folder>, and included -f
after filter-branch
to override the warning of an existing backup.
这对我来说很管用,只是稍微做了一些修改。因为我的sub1和sub2文件夹在初始版本中不存在,所以我必须修改我的--tree-filter脚本,如下所示:“mkdir<文件夹名称>;if[-d sub1];则mv<文件夹名称>/;fi”。对于第二个筛选器分支命令,我将替换为,省略了的创建,并在筛选器分支之后包含-f以覆盖现有备份的警告。
This does not work if any of the subdirs have changed during the history in git. How can this be solved?
如果Git中的任何子目录在历史过程中发生了更改,则此操作不起作用。如何才能解决这个问题?
@nietras see rogerdpack's answer. Took me a while to find it after reading and absorbing all the info in these other answers.
@nietras见rogerdpack的答案。在阅读并吸收了其他答案中的所有信息后,我花了一段时间才找到它。
I like the style of that graph. May I ask what tool you're using?
我喜欢那张图表的风格。请问您使用的是什么工具?
Tower for Mac. I really like it. It's almost worth switching to Mac for in itself.
为麦克而建的塔。我真喜欢它。转而使用Mac本身几乎是值得的。
Yep, though in my case, my subfoldered targetdir
had been renamed at some point and git filter-branch
simply called it a day, deleting all commits made prior to the rename! Shocking, considering how adept Git is at tracking such things and even migration of individual content chunks!
是的,虽然在我的例子中,我的子文件夹目标目录在某个时候被重命名了,而git过滤器分支只是简单地叫它一天,删除了在重命名之前所做的所有提交!令人震惊的是,考虑到Git是多么擅长跟踪这些东西,甚至是单个内容块的迁移!
Oh, also, if anyone finds themselves in the same boat, here's the command I used. Don't forget that git rm
takes multiple args, so there's no reason to run it for each file/folder: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
哦,还有,如果任何人发现自己处于同样的境地,这是我使用的命令。不要忘记,git rm使用多个参数,所以没有理由为每个文件/文件夹运行它:byebai=“dir/subdir2 dir2file1 dir/File2”;git Filter-Branch-f--index-Filter“git rm-q-r-f--cached--Ignore-unMatch$byebai”--prune-Empty-all
Found the needle in the git haystack! Now I can keep ALL my commit history.
大海捞针!现在我可以保留我所有的提交历史记录。
Nice post, but I notice the first paragraph of the doc you linked says If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.
Yet according to comments on all the answers here both filter-branch
and the subtree
script result in the loss of history wherever a subdirectory has been renamed. Is there anything that can be done to address this?
这篇文章很好,但我注意到你链接的文档的第一段说,如果你创建了一个新的存储库的克隆,当你把一个文件夹拆分到一个单独的存储库时,你不会丢失任何你的Git历史或更改。然而,根据对此处所有答案的评论,筛选器分支和子树脚本都会导致历史记录丢失,无论子目录在哪里被重命名。有什么办法可以解决这个问题吗?
Found the solution for preserving all commits, including those preceding directory renames/moves - it's rogerdpack's answer to this very question.
找到了保存所有提交的解决方案,包括前面的目录重命名/移动--这就是rogerdpack对这个问题的回答。
The only problem is that I can't use the cloned repo any more
唯一的问题是我不能再使用克隆的回购了
what is cat here?
猫在这里是什么?
An advantage of this method compared to "The Easy Way" is that the remote is already set up for the new repo, so you can immediately do a subtree add. In fact this way seems easier to me (even without git splits
)
与“简单的方法”相比,这种方法的一个优点是已经为新的repo设置了遥控器,因此您可以立即进行子树添加。事实上,这种方式对我来说似乎更容易(即使没有git分裂)
Props to AndrewD for posting this solution. I have forked his repo to make it work on OSX (github.com/ricardoespsanto/git-splits) if that's useful to anyone else
对AndrewD发布此解决方案的道具。我已经修改了他的回帖,使其在OSX(githeb.com/ricardopsanto/git-plits)上运行,如果这对其他人有用的话
The "fine and wonderful" part of subtree is that your subdirectory's history comes along for the ride. If you don't need the history, then your painfully easy method is the way to go.
子树的“精妙”之处在于,您的子目录的历史记录随处可见。如果你不需要历史,那么你的痛苦而简单的方法就是可行的。
Nearly perfect ... but you forgot "git filter-branch --prune-empty" to remove all old commits that are now empty. To do before push to origin master !
近乎完美。但是您忘记了使用“git过滤器-分支--修剪-空”来删除所有现在为空的旧提交。要做的事才推到原产地师傅!
If you made the mistake and still want to "repush" after having removed old empty commit, perform : "git push -u origin master --force-with-lease"
如果您犯了错误,并且在删除旧的空提交后仍想重新推送,请执行:“git Push-u Origin master--force-with-let”
我是一名优秀的程序员,十分优秀!