将子目录分离(移动)到单独的 Git 存储库中

发布于 2024-07-10 07:47:40 字数 470 浏览 10 评论 0 原文

我有一个 Git 存储库,其中包含许多子目录。 现在我发现其中一个子目录与另一个子目录无关,应该分离到一个单独的存储库。

如何在保留子目录中文件的历史记录的同时执行此操作?

我想我可以制作一个克隆并删除每个克隆中不需要的部分,但我想这会在检查旧版本等时为我提供完整的树。这可能是可以接受的,但我更愿意能够假装两个存储库没有共享历史记录。

只是为了清楚起见,我有以下结构:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想要这样:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

I have a Git repository which contains a number of subdirectories. Now I have found that one of the subdirectories is unrelated to the other and should be detached to a separate repository.

How can I do this while keeping the history of the files within the subdirectory?

I guess I could make a clone and remove the unwanted parts of each clone, but I suppose this would give me the complete tree when checking out an older revision etc. This might be acceptable, but I would prefer to be able to pretend that the two repositories doesn't have a shared history.

Just to make it clear, I have the following structure:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

But I would like this instead:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(26

天荒地未老 2024-07-17 07:47:40

The Easy Way™

事实证明,这是一种常见且有用的做法,Git 的统治者使它变得非常简单(在版本 1.7.11 - 2012 年 5 月中添加)。 此外,下面的演练中有一个真实示例

  1. 准备旧仓库

     cd ; 
       git subtree split -P <文件夹名称>;   -b <新分支名称> 
      

    注意: 不得包含前导或尾随字符。 例如,名为 subproject 的文件夹必须作为 subproject 传递,而不是 ./subproject/

    注意: 是您将在现有/旧存储库中创建的分支,不是 新的[稍后出现]。

    Windows 用户注意事项:当您的文件夹深度 > 时 1、 必须有 *nix 风格的文件夹分隔符 (/)。 例如,名为 path1\path2\subproject 的文件夹必须作为 path1/path2/subproject

    传递

  2. 创建新的存储库

     mkdir ~/;   &&   cd ~/; 
       git初始化 
       git pull ;   <新分支名称> 
      
  3. 将新的存储库链接到 GitHub 或其他位置

     git Remote add origin <[电子邮件受保护] :user/new-repo.git> 
       git Push -u 原始主机 
      
  4. Cleanup inside 如果需要

     git rm -rf <文件夹名称>; 
      

    注意:这会将所有历史引用保留在存储库中。 如果您确实担心提交密码或者需要减小 .git 文件夹的文件大小,请参阅下面的附录


演练

这些步骤与上面相同,但是按照我的存储库的具体步骤进行操作,而不是使用

这是我在节点中实现 JavaScript 浏览器模块的项目:

tree ~/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

我想将单个文件夹 btoa 拆分到一个单独的 Git 存储库中

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

我现在有一个新分支 btoa-only< /code>,只有 btoa 的提交,我想创建一个新的存储库。

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

接下来,我在 GitHub 或 Bitbucket 等上创建一个新的存储库,并将其添加为 origin

git remote add origin [email protected]:node-browser-compat/btoa.git
git push -u origin master

快乐的一天!

注意:如果您使用 README.md.gitignoreLICENSE 创建存储库,则需要首先拉:

git pull origin master
git push origin master

最后,我想从更大的存储库中删除该文件夹

git rm -rf btoa

清除历史记录

默认情况下,从 Git 中删除文件实际上并不会删除它们;而是将它们删除。 它只是承诺他们不再存在了。 如果你想真正删除历史引用(即你提交了密码),你需要这样做:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

之后,你可以检查你的文件或文件夹是否不再显示在 Git 历史记录中:

git log -- <name-of-folder> # should show nothing

但是,你无法将删除“推送”到 GitHub 等。 如果你尝试,你会收到一个错误,并且你必须先git pull,然后才能git push - 然后你又回到历史记录中的所有内容。

因此,如果您想从“源”删除历史记录(即从 GitHub、Bitbucket 等删除历史记录),您需要删除存储库并重新推送存储库的修剪副本。 但是等等 - 还有更多! - 如果您真的担心删除密码或类似的东西,您需要修剪备份(见下文)。

使 .git 更小

前面提到的删除历史命令仍然会留下一堆备份文件 - 因为 Git 非常友善地帮助您避免意外破坏您的存储库。 它最终会在数天和数月内删除孤立的文件,但会将它们保留一段时间,以防您意识到不小心删除了您不想删除的内容。

因此,如果您真的想立即清空垃圾箱以减少存储库的克隆大小,您必须立即执行所有这些非常奇怪的操作:

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

也就是说,我建议除非您知道需要,否则不要执行这些步骤 - 以防万一您确实修剪了错误的子目录,您知道吗? 当您推送存储库时,备份文件不应被克隆,它们只会位于您的本地副本中。

信用

The Easy Way™

It turns out that this is such a common and useful practice that the overlords of Git made it really easy (added in version 1.7.11 - May 2012). Also, there's a real-world example in the walkthrough below.

  1. 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/

    Note: <name-of-new-branch> is a branch you will be creating in the existing/old repo, NOT the new one [that comes later].

    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

  2. Create the new repo

     mkdir ~/<new-repo> && cd ~/<new-repo>
     git init
     git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Link the new repo to GitHub or wherever

     git remote add origin <[email protected]:user/new-repo.git>
     git push -u origin master
    
  4. 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.


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:

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

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.

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

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:

git pull origin master
git push origin master

Lastly, I'll want to remove the folder from the bigger repo

git rm -rf btoa

Clearing your history

By default, removing files from Git doesn't actually remove them; it just commits that they aren't there any more. If you want to actually remove the historical references (i.e. you committed a password), you need to do this:

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 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.

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).

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.

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

幸福不弃 2024-07-17 07:47:40

更新:这个过程非常常见,以至于 git 团队使用新工具 git subtree 使其变得更加简单。 请参阅此处:将子目录分离(移动)到单独的 Git 存储库


您想要克隆存储库,然后使用 git filter-branch 来标记新存储库中除您希望进行垃圾收集的子目录之外的所有内容。

  1. 克隆本地存储库:

    git 克隆 /XYZ /ABC 
      

    (注意:将使用硬链接克隆存储库,但这不是问题,因为硬链接文件本身不会被修改 - 将创建新文件。)

  2. 现在,让我们保留我们也想重写有趣的分支,然后删除源以避免推送到那里并确保旧的提交不会被源引用:

    <前><代码>cd /ABC
    对于 i 在分支 1 br2 br3 中; 执行 gitbranch -t $i origin/$i; 完毕
    git 远程 rm 原点

    或对于所有远程分支:

    <前><代码>cd /ABC
    for i in $(git Branch -r | sed "s/.*origin\///"); 执行 gitbranch -t $i origin/$i; 完毕
    git 远程 rm 原点

  3. 现在您可能还想删除与子项目无关的标签; 您也可以稍后执行此操作,但您可能需要再次修剪您的存储库。 我没有这样做,并收到了警告:所有标签的 Ref 'refs/tags/v0.1' 未更改(因为它们都与子项目无关); 此外,删除此类标签后,将回收更多空间。 显然 git filter-branch 应该能够重写其他标签,但我无法验证这一点。 如果要删除所有标签,请使用 git tag -l | xargs git tag -d.

  4. 然后使用filter-branch和reset来排除其他文件,这样它们就可以被修剪。 我们还添加 --tag-name-filter cat --prune-empty 来删除空提交并重写标签(请注意,这将必须去除它们的签名):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all 
      

    或者,仅重写 HEAD 分支并忽略标签和其他分支:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD 
      
  5. 然后删除备份引用日志,以便真正回收空间(尽管现在该操作具有破坏性)

    git reset --hard 
      git for-each-ref --format="%(refname)" refs/original/ | 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 
      

    现在您有了 ABC 子目录的本地 git 存储库,并保留了其所有历史记录。

注意:对于大多数用途,git filter-branch 确实应该添加参数 -- --all。 是的,这确实是--空间--全部。 这需要是命令的最后一个参数。 正如 Matli 发现的那样,这会将项目分支和标签保留在新存储库中。

编辑:合并了下面评论中的各种建议,以确保存储库实际上已缩小(以前情况并非总是如此)。

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


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.

  1. 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.)

  2. 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
    
  3. 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.

  4. 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
    
  5. 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.

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).

夜无邪 2024-07-17 07:47:40

Paul 的答案 创建一个包含 /ABC 的新存储库,但不会从 /XYZ 中删除 /ABC。 以下命令将从 /XYZ 中删除 /ABC:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

当然,首先在“clone --no-hardlinks”存储库中测试它,然后使用 Paul 列出的重置、gc 和修剪命令。

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:

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.

泛泛之交 2024-07-17 07:47:40

我发现,为了从新存储库中正确删除旧历史记录,您必须在 filter-branch 步骤之后做更多的工作。

  1. 进行克隆和过滤:

    git clone --no-hardlinks foo bar;   光盘吧 
      git filter-branch --subdirectory-filter subdir/你/想要 
      
  2. 删除对旧历史记录的所有引用。 “origin”是跟踪你的克隆,“original”是过滤器分支保存旧东西的地方:

    git 远程 rm 原点 
      git update-ref -d refs/original/refs/heads/master 
      git reflog expire --expire=now --all 
      
  3. 即使是现在,您的历史记录也可能被困在 fsck 不会触及的包文件中。 将其撕成碎片,创建一个新的包文件并删除未使用的对象:

    git repack -ad 
      

过滤器手册中对此的解释分支。

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.

  1. Do the clone and the filter:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. 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
    
  3. 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.

静若繁花 2024-07-17 07:47:40

当使用较新版本的 git2.22+ 也许?)运行 git filter-branch 时,它会提示使用这个新工具git-filter-repo。 这个工具确实为我简化了事情。

使用filter-repo

命令进行过滤,根据原始问题创建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

假设:
* 远程 XYZ 存储库在推送之前是新的且空的

过滤和移动

在我的情况下,我还想移动几个目录以获得更一致的结构。 最初,我运行了简单的 filter-repo 命令,然后运行 ​​git mv dir-to-rename,但我发现使用 可以获得稍微“更好”的历史记录>--path-rename 选项。 现在,我在新存储库中看到的移动文件上的上次修改时间不再是 5 小时前,而是看到了去年(在 GitHub UI 中),它与原始存储库中的修改时间相匹配。

而不是...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

我最终跑了...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3

Notes:

  • 我认为 Git Rev News 博客文章 很好地解释了创建另一个 repo 过滤工具背后的原因。
  • 我最初尝试了在原始存储库中创建与目标存储库名称匹配的子目录的路径,然后进行过滤(使用 git filter-repo --subdirectory-filter dir-matching-new-仓库名称)。 该命令正确地将子目录转换为复制的本地存储库的根目录,但它也导致只有创建子目录所需的三个提交的历史记录。 (我没有意识到 --path 可以指定多次;因此,无需在源存储库中创建子目录。)因为当我时有人已提交到源存储库注意到我未能继承历史记录,我只是在 clone 命令后使用了 git reset commit-before-subdir-move --hard ,并添加了 --forcefilter-repo 命令以使其在稍微修改的本地克隆上运行。
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • 由于我不知道 git 的扩展模式,我对安装感到困惑,但最终我克隆了 git-filter-repo 并将其符号链接到 $(git --exec-path)
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)

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.

Filtering with filter-repo

Commands to create the XYZ repo from the original question:

# 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

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.

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)
誰認得朕 2024-07-17 07:47:40

编辑:添加了 Bash 脚本。

这里给出的答案对我来说仅部分有效; 许多大文件保留在缓存中。 最终有效的方法(下班后在 freenode 上的 #git 中):

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

使用之前的解决方案,存储库大小约为 100 MB。 这个将其减少到 1.7 MB。 也许它对某人有帮助:)


以下 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

Edit: Bash script added.

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 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 :)


The following bash script automates the task:

!/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
樱花坊 2024-07-17 07:47:40

这不再那么复杂,您只需使用 git filter-branch 命令即可您的存储库的克隆,以剔除您不需要的子目录,然后推送到新的远程目录。

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

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.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
无妨# 2024-07-17 07:47:40

更新:git-subtree 模块非常有用,以至于 git 团队将其纳入核心并使其成为 git subtree。 请参阅此处:将子目录分离(移动)到单独的 Git 存储库

git-subtree 可能对此很有用

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/

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 may be useful for this

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (deprecated)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/

情域 2024-07-17 07:47:40

这是对 CoolAJ86“The Easy Way™”答案,以便将多个子文件夹(假设sub1sub2)拆分为一个新的 git 存储库。

Easy Way™(多个子文件夹)

  1. 准备旧存储库

    pushd ; 
      git filter-branch --tree-filter "mkdir <文件夹名称>; mv  ; <文件夹名称>/" HEAD 
      git subtree split -P <文件夹名称>;   -b <新分支名称> 
      波普德 
      

    注意: 不得包含前导或尾随字符。 例如,名为 subproject 的文件夹必须作为 subproject 传递,而不是 ./subproject/

    Windows 用户注意事项:当您的文件夹深度 > 时 1、 必须有 *nix 风格的文件夹分隔符 (/)。 例如,名为 path1\path2\subproject 的文件夹必须作为 path1/path2/subproject 传递。 此外,不要使用mv命令,而是使用move

    最后一点:与基本答案的独特且巨大的区别是脚本的第二行“git filter-branch...

  2. 创建新的存储库< /p>

    mkdir ; 
      Pushd ; 
    
      git初始化 
      git pull ;   <新分支名称> 
      
  3. 将新的存储库链接到 Github 或任何地方

    git 远程添加源 [电子邮件受保护] :我的用户/new-repo.git> 
      git推送原点-u master 
      
  4. 清理,如果需要

    popd # 退出 ; 
      Pushd <大仓库> 
    
      git rm -rf <文件夹名称> 
      

    注意:这会将所有历史引用保留在存储库中。如果您确实担心提交密码或需要,请参阅原始答案中的附录减少 .git 文件夹的文件大小。

Here is a small modification to CoolAJ86's "The Easy Way™" answer in order to split multiple sub folders (let's say sub1and sub2) into a new git repository.

The Easy Way™ (multiple sub folders)

  1. 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 mvcommand but move.

    Final note: the unique and big difference with the base answer is the second line of the script "git filter-branch..."

  2. Create the new repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Link the new repo to Github or wherever

    git remote add origin <[email protected]:my-user/new-repo.git>
    git push origin -u master
    
  4. 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.

多彩岁月 2024-07-17 07:47:40

最初的问题希望 XYZ/ABC/(*files) 变为 ABC/ABC/(*files)。 在为我自己的代码实现接受的答案后,我注意到它实际上将 XYZ/ABC/(*files) 更改为 ABC/(*files)。 过滤器分支手册页甚至说,

结果将包含该目录(并且仅包含该目录)作为其项目根目录。”

换句话说,它将顶级文件夹“向上”提升了一级。这是一个重要的区别,因为,例如, 我重命名了一个顶级文件夹。通过将文件夹“向上”提升一级,git 在我进行重命名的提交处失去了连续性。

在我的历史记录中, png" alt="I Lost contiuity after filter-branch">

我对这个问题的回答是制作存储库的 2 个副本,并手动删除要保留在每个副本中的文件夹。手册页为我提供了支持这:

[...]如果简单的一次提交足以解决您的问题,请避免使用[此命令]

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,

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.

I lost contiuity after filter-branch

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:

[...] avoid using [this command] if a simple single commit would suffice to fix your problem

绾颜 2024-07-17 07:47:40

要添加到 Paul 的答案,我发现为了最终恢复空间,我必须将 HEAD 推送到一个干净的存储库并修剪减小 .git/objects/pack 目录的大小。

$ mkdir ...ABC.git
$ cd ...ABC.git
$ git init --bare

在 gc 修剪之后,还可以执行以下操作:

$ git push ...ABC.git HEAD

然后您可以执行

$ git clone ...ABC.git

此操作,并且 ABC/.git 的大小会减小

实际上,推送到清理存储库不需要一些耗时的步骤(例如 git gc),即:

$ git clone --no-hardlinks /XYZ /ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ...ABC.git HEAD

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.

i.e.

$ mkdir ...ABC.git
$ cd ...ABC.git
$ git init --bare

After the gc prune, also do:

$ git push ...ABC.git HEAD

Then you can do

$ git clone ...ABC.git

and the size of ABC/.git is reduced

Actually, some of the time consuming steps (e.g. git gc) aren't needed with the push to clean repository, i.e.:

$ git clone --no-hardlinks /XYZ /ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ...ABC.git HEAD
往昔成烟 2024-07-17 07:47:40

看来这里的大多数(全部?)答案都依赖于某种形式的 git filter-branch --subdirectory-filter 及其同类。 这可能“大多数时候”有效,但对于某些情况,例如,当您重命名文件夹时,例如:

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

如果您执行正常的 git 过滤器样式来提取“move_this_dir_renamed”,您将丢失它之后发生的文件更改历史记录最初是“move_this_dir”(ref)。

因此,似乎真正保留所有更改历史记录的唯一方法(如果您的情况是这样),本质上是复制存储库(创建一个新的存储库,将其设置为origin),然后核对其他所有内容并将子目录重命名为父目录,如下所示:

  1. 在本地克隆多模块项目
  2. 分支 - 检查那里有什么:gitbranch-a
  3. 对要包含的每个分支进行签出在拆分中获取工作站上的本地副本:git checkout --track origin/branchABC
  4. 在新目录中创建副本:cp -r oldmultimod simple
  5. 进入新建项目副本: cd simple
  6. 删除本项目中不需要的其他模块:
  7. git rm otherModule1 other2 other3
  8. 现在只保留目标模块的子目录
  9. Get删除模块子目录,以便模块根目录成为新项目根目录
  10. git mv moduleSubdir1/* 。
  11. 删除遗留子目录: rmdir moduleSubdir1
  12. 随时检查更改:git status
  13. 创建新的 git 存储库并复制其 URL 以将该项目指向其中:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo< /code>
  15. 验证这是否正确: git remote -v
  16. 将更改推送到远程存储库: git push
  17. 转到远程存储库并检查是否全部存在
  18. 重复此操作需要任何其他分支:git checkoutbranch2

这遵循github 文档“将子文件夹拆分到新存储库中” 步骤 6-11 将模块推送到新存储库。

这不会为您节省 .git 文件夹中的任何空间,但它会保留这些文件的所有更改历史记录,即使是在重命名期间也是如此。 如果没有“大量”历史丢失等,这可能不值得。但至少保证您不会丢失较旧的提交!

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:

 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).

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:

  1. Clone the multi-module project locally
  2. Branches - check what's there: git branch -a
  3. 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
  4. Make a copy in a new directory: cp -r oldmultimod simple
  5. Go into the new project copy: cd simple
  6. Get rid of the other modules that aren't needed in this project:
  7. git rm otherModule1 other2 other3
  8. Now only the subdir of the target module remains
  9. Get rid of the module subdir so that the module root becomes the new project root
  10. git mv moduleSubdir1/* .
  11. Delete the relic subdir: rmdir moduleSubdir1
  12. Check changes at any point: git status
  13. Create the new git repo and copy its URL to point this project into it:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. Verify this is good: git remote -v
  16. Push the changes up to the remote repo: git push
  17. Go to the remote repo and check it's all there
  18. 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.

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!

王权女流氓 2024-07-17 07:47:40

现在正确的方法如下:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub 现在甚至有 关于此类情况的小文章

但请务必首先将原始存储库克隆到单独的目录(因为它会删除所有文件和其他目录,并且您可能需要使用它们)。

所以你的算法应该是:

  1. 使用 git filter-branch将远程存储库克隆到另一个目录,
  2. 仅在某个子目录下留下文件,推送到新的远程
  3. 创建提交以从原始远程存储库中删除该子目录

Proper way now is the following:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub now even have small article about such cases.

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).

So your algorithm should be:

  1. clone your remote repo to another directory
  2. using git filter-branch left only files under some subdirectory, push to new remote
  3. create commit to remove this subdirectory from your original remote repo
没︽人懂的悲伤 2024-07-17 07:47:40

我推荐 GitHub 将子文件夹拆分为新文件夹的指南存储库。 这些步骤与 Paul 的回答类似,但我发现他们的说明更容易理解。

我修改了说明,以便它们申请本地存储库,而不是托管在 GitHub 上的存储库。


将子文件夹拆分为新文件夹存储库

  1. 打开 Git Bash。

  2. 将当前工作目录更改为您要创建新存储库的位置。

  3. 克隆包含子文件夹的存储库。

git clone 旧存储库文件夹 新存储库文件夹 
  
  • 将当前工作目录更改为克隆的存储库。
  • cd 存储库名称 
      
  • 要从存储库中的其余文件中过滤掉子文件夹,请运行 git filter-branch 并提供以下信息:
    • FOLDER-NAME:项目中您想要从中创建单独存储库的文件夹。
      • 提示:Windows 用户应使用 / 来分隔文件夹。
    • BRANCH-NAME:当前项目的默认分支,例如 mastergh-pages
  • git filter-branch --prune-empty --subdirectory-filter 文件夹名称 分支名称  
      # 过滤目录中的指定分支并删除空提交 
      重写 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89) 
      Ref 'refs/heads/BRANCH-NAME' 被重写 
      

    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.

    I have modified the instructions so that they apply for a local repository, rather than one hosted on GitHub.


    Splitting a subfolder out into a new repository

    1. Open Git Bash.

    2. Change the current working directory to the location where you want to create your new repository.

    3. Clone the repository that contains the subfolder.

    git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
    
    1. Change the current working directory to your cloned repository.
    cd REPOSITORY-NAME
    
    1. 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
    
    时光与爱终年不遇 2024-07-17 07:47:40

    我确实遇到了这个问题,但所有基于 git filter-branch 的标准解决方案都非常慢。 如果您有一个小型存储库,那么这可能不是问题,这对我来说是。 我编写了另一个基于 libgit2 的 git 过滤程序,该程序第一步为主存储库的每个过滤创建分支,然后将它们推送到干净的存储库作为下一步。 在我的存储库(500Mb 100000 次提交)上,标准 git 过滤分支方法需要几天时间。 我的程序需要几分钟才能完成相同的过滤。

    它有一个美妙的名字 git_filter 并居住在这里:

    https://github.com/slobababy/git_filter

    on GitHub。

    我希望它对某人有用。

    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.

    It has the fabulous name of git_filter and lives here:

    https://github.com/slobobaby/git_filter

    on GitHub.

    I hope it is useful to someone.

    日久见人心 2024-07-17 07:47:40

    使用此过滤命令删除子目录,同时保留标签和分支:

    git filter-branch --index-filter \
    "git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
    --tag-name-filter cat -- --all
    

    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
    
    云裳 2024-07-17 07:47:40

    无论如何,以下是如何在 Windows 计算机上使用 GitHub。 假设您有一个克隆的存储库位于 C:\dir1 中。 目录结构如下所示:C:\dir1\dir2\dir3dir3 目录是我想要成为一个新的单独存储库的目录。

    Github:

    1. 创建新存储库:MyTeam/mynewrepo

    Bash 提示:

    1. $ cd c:/Dir1
    2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
      返回:引用'refs/heads/master'被重写(仅供参考:dir2/dir3区分大小写。)

    3. $ git remote add some_name [电子邮件受保护]:MyTeam/mynewrepo.git
      git 远程添加源等。 不起作用,返回“远程源已存在

    4. $ git push --progress some_name master

    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.

    Github:

    1. Create your new repository: MyTeam/mynewrepo

    Bash Prompt:

    1. $ cd c:/Dir1
    2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
      Returned: Ref 'refs/heads/master' was rewritten (fyi: dir2/dir3 is case sensitive.)

    3. $ git remote add some_name [email protected]:MyTeam/mynewrepo.git
      git remote add origin etc. did not work, returned "remote origin already exists"

    4. $ git push --progress some_name master

    伴我心暖 2024-07-17 07:47:40

    正如我上面提到的,我不得不使用相反的解决方案(删除所有提交都没有触及我的 dir/subdir/targetdir),这似乎工作得很好,删除了大约 95% 的提交(根据需要)。 然而,还存在两个小问题。

    首先filter-branch在删除引入或修改代码的提交方面做得非常出色,但显然,合并提交在吉蒂宇宙。

    这是一个外观问题,我可能可以忍受(他说......慢慢后退,眼睛别开)

    第二剩下的少数提交几乎全部都是重复的! 我似乎获得了第二条冗余的时间线,几乎涵盖了该项目的整个历史。 有趣的是(您可以从下图中看到),我的三个本地分支并不都在同一时间线上(这当然就是它存在的原因,而不仅仅是垃圾收集)。

    我唯一能想象的是,删除的提交之一也许是 filter-branch 实际上删除的单个合并提交,这创建了并行时间线,因为每个现在未合并的链都获取了自己的提交副本。 (耸耸肩我的 TARDiS 在哪里?)我很确定我可以解决这个问题,尽管我真的很想了解它是如何发生的。

    在疯狂的 mergefest-O-RAMA 的情况下,我可能会独自留下那个,因为它已经在我的提交历史中牢牢地根深蒂固——每当我靠近时都会对我构成威胁——它似乎并没有真正导致任何非美观的问题,因为它在 Tower.app 中非常漂亮。

    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.

    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.

    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.

    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.

    旧时模样 2024-07-17 07:47:40

    最简单的方法

    1. 安装 git splits。 我将其创建为 git 扩展,基于 jkeating 的解决方案
    2. 将目录拆分为本地分支
      <代码>
      #更改到您的存储库目录
      cd /路径/到/repo
      #检查分支
      git checkout XYZ
      #将多个目录拆分为新分支XYZ
      git split -b XYZ XY1 XY2

    3. 在某处创建一个空的存储库。 我们假设我们在 GitHub 上创建了一个名为 xyz 的空存储库,其路径为:[email protected]:simpliwp/xyz.git

    4. 推送到新存储库。
      <代码>
      #为空存储库添加一个新的远程源,以便我们可以推送到 GitHub 上的空存储库
      git Remote add origin_xyz [电子邮件受保护]:simpliwp/xyz.git
      #将分支推送到空仓库的主分支
      git push origin_xyz XYZ:master

    5. 将新创建的远程存储库克隆到新的本地目录
      <代码>
      #将当前目录从旧存储库中更改出来
      cd /path/to/where/you/want/the/new/local/repo
      #克隆您刚刚推送到的远程存储库
      git clone [电子邮件受保护]:simpliwp/xyz.git

    The Easier Way

    1. install git splits. I created it as a git extension, based on jkeating's solution.
    2. 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

    3. 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

    4. 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

    5. 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

    始终不够 2024-07-17 07:47:40

    在垃圾收集之前,您可能需要类似“git reflog expire --expire=now --all”之类的内容来实际清除文件。 git filter-branch 只是删除历史记录中的引用,但不会删除保存数据的引用日志条目。 当然,先测试一下。

    尽管我的初始条件有些不同,但这样做时我的磁盘使用率急剧下降。 也许 --subdirectory-filter 否定了这种需要,但我对此表示怀疑。

    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.

    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.

    原谅过去的我 2024-07-17 07:47:40

    查看 git_split 项目 https://github.com/vangorra/git_split

    将 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.
    

    Check out git_split project at https://github.com/vangorra/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_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.
    
    ┾廆蒐ゝ 2024-07-17 07:47:40

    将其放入您的 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'
    

    Put this into your 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'
    
    泪冰清 2024-07-17 07:47:40

    我确信 git 子树一切都很好,但我想要移动的 git 管理代码的子目录都在 eclipse 中。
    因此,如果您使用 egit,那就非常简单了。
    获取您想要移动的项目并进行分组 -> 断开连接,然后分组 -> 将其共享到新位置。 它将默认尝试使用旧的存储库位置,但您可以取消选中使用现有选择并选择新位置来移动它。
    万岁。

    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.

    七堇年 2024-07-17 07:47:40

    您可以轻松尝试 https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

    这对我有用。 我在上面给出的步骤中遇到的问题是

    1. 在此命令中git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME
      在此命令中,BRANCH-NAMEma​​ster

    2. 如果由于保护问题而提交时最后一步失败,请遵循 - https://docs.gitlab.com/ee/user/project/protected_branches.html

    You can easily try the 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

    1. in this command git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME
      The BRANCH-NAME is master

    2. if the last step fails when committing due to protection issue follow - https://docs.gitlab.com/ee/user/project/protected_branches.html

    ⊕婉儿 2024-07-17 07:47:40

    我找到了非常直接的解决方案,
    这个想法是复制存储库,然后删除不必要的部分。
    它的工作原理如下:

    1) 克隆您想要拆分的存储库

    git clone [email protected]:testrepo/test.git
    

    2) 移动到 git 文件夹

    cd test/
    

    2) 删除不必要的文件夹并提交

    rm -r ABC/
    git add .
    enter code here
    git commit -m 'Remove ABC'
    

    3) 使用 BFG

    cd ..
    java -jar bfg.jar --delete-folders "{ABC}" test
    cd test/
    git reflog expire --expire=now --all && git gc --prune=now --aggressive
    

    对于多个文件夹,您可以使用逗号

    java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git 
      

    4) 检查历史记录是否不包含您刚刚删除的文件/文件夹

    git log --diff-filter=D --summary | grep delete
    

    5) 现在您有了干净的存储库,没有 ABC,
    所以只需将其推入新的原点

    remote add origin [email protected]:username/new_repo
    git push -u origin master
    

    即可。 您可以重复这些步骤来获取另一个存储库,

    只需删除 XY1,XY2 并重命名 XYZ -> ABC 第 3 步

    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

    git clone [email protected]:testrepo/test.git
    

    2) Move to git folder

    cd test/
    

    2) Remove unnecessary folders and commit it

    rm -r ABC/
    git add .
    enter code here
    git commit -m 'Remove ABC'
    

    3) Remove unnecessary folder(s) form history with BFG

    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

    git log --diff-filter=D --summary | grep delete
    

    5) Now you have clean repository without ABC,
    so just push it into new origin

    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

    偏爱你一生 2024-07-17 07:47:40

    发现这篇精彩的文章原始参考易于理解。 在这里记录它,以防它无法访问。

    1. 准备当前存储库

    $ cd path/to/repository
    $ git subtree split -P my-folder -b my-folder
    Created branch 'my-folder'
    aecbdc3c8fe2932529658f5ed40d95c135352eff
    

    文件夹的名称必须是相对路径,从存储库的根目录开始。

    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
    

    这里我们只需要 cd 到新文件夹,初始化新的存储库,并提交任何内容。

    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'.
    

    我们在此处添加来自 GitHub 的远程新存储库,然后将我们的第一个提交推送到它。

    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
    

    最后,我们回到根目录,从主存储库中删除该文件夹,然后提交并推送更改。
    现在,我们的主存储库中有该文件夹,但链接到一个完全独立的存储库,可以在多个项目中重复使用。

    Found this wonderful article Original reference easy to follow. Documenting it here in case if it get's inaccessible.

    1. Preparing the current repository

    $ 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

    $ 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.

    3.Add new remote repository and push

    $ 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.

    4. Remove folder from main repository and push

    $ 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.

    ~没有更多了~
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文