0%

Git 拆分子仓库

使用 git 将一个仓库中的子目录移动到另一个仓库中,同时保留所有的上传历史记录

  • git filter-branch
  • git subtree split 只能独立一个目录

回滚仓库

git reset --hard <sha1-commit-id>
git push origin HEAD --force

使用 patch

$ git apply --check my_pcc_branch.patch
warning: src/main/java/.../AbstractedPanel.java has type 100644, expected 100755
error: patch failed: src/main/java/.../AbstractedPanel.java:13
error: src/main/java/.../AbstractedPanel.java: patch does not apply
git am --ignore-space-change --ignore-whitespace *.patch

目录结构

使用 ccat 为例

$ tree -L 2 -d
.
├── bin
├── completion
│   └── zsh
└── vendor
    ├── github.com
    └── golang.org
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/bump_isatty
  remotes/origin/bump_syntaxhighlight
  remotes/origin/master

New repository

拆分一个子目录为独立仓库

vendor 拆分为独立仓库,保留历史记录及 Branch [master, bump_syntaxhighlight]

git subtree

  1. Clone原有仓库到本地
     git clone https://github.com/jingweno/ccat
  2. 拆分指定分支 master 下项目
     $ git subtree split -P vendor -b master_tmp
     $ git co master_tmp
     $ tree -L 1 -d
     .
     ├── github.com
     └── golang.org
     $ git log . # 检查 history
  3. 创建 New Repo
     $ mkdir new-repo; cd new-repo
     $ git init # 本地repo
     $ git pull ../ccat master_tmp # ccat目录 master_tmp分支
  4. 清理 .git object
     $ git reset --hard
     $ git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d # 删除由git-filter-branch备份的原始参考
     $ git reflog expire --expire=now --all # 使用所有reflogs
     $ git gc --aggressive --prune=now # 垃圾收集所有未被引用的对象
  5. 关联远程仓库并上传
     $ git remote add origin https://github.com/breezetemple/split-test.git
     $ git push -u origin master
  6. Check
     $ git clone https://github.com/breezetemple/split-test.git
     $ git branch -a
     $ git log .

保留多个分支

$ git subtree split -P vendor -b master_tmp
$ git co origin/bump_syntaxhighlight -b bump_syntaxhighlight
$ git subtree split -P vendor -b bump_syntaxhighlight_tmp
$ git branch
  bump_syntaxhighlight
* bump_syntaxhighlight_tmp
  master
  master_tmp

$ mkdir new-repo; cd new-repo
$ git init # 本地repo
$ git pull ../ccat master_tmp # ccat目录 master_tmp分支
$ 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
$ git remote add origin https://github.com/breezetemple/split-test.git
$ git push -u origin master

$ rm new-repo -rf; mkdir new-repo; cd new-repo
$ git init # 本地repo
$ git co -b bump_syntaxhighlight
$ git pull ../subtree bump_syntaxhighlight_tmp
$ 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
$ git remote add origin https://github.com/breezetemple/split-test.git
$ git push -u origin bump_syntaxhighlight

git filter-branch

  1. Clone原有仓库到本地

     git clone https://github.com/jingweno/ccat filter-branch
  2. 在本地创建需要保留的分支
    保留 master 和 bump_syntaxhighlight

     $ git co origin/bump_syntaxhighlight -b bump_syntaxhighlight
     $ git branch
     * bump_syntaxhighlight
     master
  3. 取消远程库的关联

     $ git remote rm origin
  4. 删除所有tag

     $ git tag -l
     v0.0.1
     v0.0.2
     v0.0.3
     v0.1.0
     v1.0.0
     v1.1.0
     $ git tag -l | xargs git tag -d # 需要删除所有的tag,否则会出错
  5. 保留需要的目录和历史

     # 保留多个子文件夹
     $ git filter-branch -f --prune-empty --index-filter 'git rm --cached -r -q -- . ; git reset -q $GIT_COMMIT -- vendor' -- --all # 只保留 vendor
     $ git filter-branch -f --prune-empty --index-filter 'git rm --cached -r -q -- . ; git reset -q $GIT_COMMIT -- vendor completion' -- --all # 保留 vendor completion
     $ git branch -a
     bump_syntaxhighlight
     * master
     $ tree -L 2 -d
     .
     └── vendor
         ├── github.com
         └── golang.org
     $ git log .
    
     # 保留一个子文件夹,并且成为 root
     $ git filter-branch --prune-empty --subdirectory-filter vendor
     $ tree -L 1 -d
     .
     ├── bin
     ├── completion
     └── vendor
  6. 清理仓库

     $ 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
  7. 关联远程仓库并上传

     $ git remote add origin https://github.com/breezetemple/split-test.git
     $ git push -u origin master
     $ git push -u origin bump_syntaxhighlight

保留了层级结构,为 vendor/*

  • -- --all 表明 --all 为 filter-branch options

    Note the – that separates filter-branch options from revision options, and the –all to rewrite all branches and tags.

  • --all all branches and tags.
  • --subdirectory-filter <directory>

    Only look at the history which touches the given subdirectory. The result will contain that directory (and only that) as its project root.

git-stitch-repo

Detach many subdirectories into a new, separate Git repository

git-filter-repo

git-filter-repo

拆分子目录并上传到一个已存在仓库

  • src: ccat
    $ tree -L 2 -d
    .
    ├── bin
    ├── completion
    │   └── zsh
    └── vendor
      ├── github.com
      └── golang.org
  • dst: test
    $ tree -L 1
    .
    ├── src
    └── test

目标:将 ccat/vendor 移动到 split-test

git filter-branch

  1. 源文件处理
    $ git remote rm origin # current branch: master
    $ git tag -l | xargs git tag -d
    $ git filter-branch --prune-empty --subdirectory-filter ./vendor -- --all
    $ tree -L 1
    .
    ├── github.com
    ├── golang.org
    └── vendor.json
    $ git filter-branch -f --prune-empty --index-filter 'git rm --cached -r -q -- . ; git reset -q $GIT_COMMIT -- vendor' -- --all
    $ tree -L 2 -d
    .
    └── vendor
     ├── github.com
     └── golang.org
  2. 清理仓库
    $ 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
  3. 目标仓库操作
    $ git clone https://github.com/breezetemple/split-test.git
    $ cd split-test # current branch: master
    # 添加本地源
    $ git remote add master ../filter-branch # master: ccat branch
    # 更新代码
    $ git fetch master
    # 建立跟踪分支 vendor -> master/master
    # git remote add repo-A-branch <git repository A directory>
    $ git co -b vendor master/master
    $ git co master
    # 合并
    $ git merge vendor --allow-unrelated-histories
    $ git push

拆分子仓库并且更改目录层次

目标

.
├── bin
├── completion
│   └── zsh
└── vendor
    ├── github.com
    └── golang.org

拆分为

.
├── zsh
├── github.com
└── golang.org

git read-tree 脚本

git subtree 只能操作单个文件,当需要操作多个文件切需要更改目录层次时,需要使用更加强大的工具 git filter-branch

  • --index-filter
  • --tree-filter

编写处理脚本如下:

git read-tree --empty

git show ${GIT_COMMIT}:vendor/github.com >/dev/null 2>&1
if [ $? -eq 0 ]; then
    git read-tree --prefix=github.com/ ${GIT_COMMIT}:vendor/github.com
fi

git show ${GIT_COMMIT}:vendor/golang.org >/dev/null 2>&1
if [ $? -eq 0 ]; then
    git read-tree --prefix=golang.org/ ${GIT_COMMIT}:vendor/golang.org
fi

git show ${GIT_COMMIT}:completion/zsh >/dev/null 2>&1
if [ $? -eq 0 ]; then
    git read-tree --prefix=zsh/ ${GIT_COMMIT}:completion/zsh
fi

脚本说明:

  • git read-tree <commit-id>
    可读取指定提交到当前 index
  • git read-tree --empty
    清除当前的 index
  • ${GIT_COMMIT}
  • git show ${GIT_COMMIT}:vendor/github.com
    判断当前提交中是否包含文件/文件夹
  • git read-tree --prefix=zsh/ <commit-id>:completion/zsh
    将指定提交的指定文件读入当前 index, 并且文件路径添加前缀

git filter-branch

  1. Clone原有仓库到本地
     git clone https://github.com/jingweno/ccat
  2. 在本地创建需要保留的分支
    保留 master 和 bump_syntaxhighlight
     $ git co origin/bump_syntaxhighlight -b bump_syntaxhighlight
     $ git branch
     * bump_syntaxhighlight
     master
  3. 取消远程库的关联
     $ git remote rm origin
  4. 删除所有tag
     $ git tag -l | xargs git tag -d # 需要删除所有的tag,否则会出错
  5. 保留需要的目录和历史
     # 按照脚本保留多个子文件夹并调整层级
     $ git filter-branch -f --index-filter PATH/TO/index-filter.sh -- --all # 绝对路径
     $ git branch -a
     bump_syntaxhighlight
     * master
     $ tree -L 2 -d
     .
     ├── github.com
     ├── golang.org
     └── zsh
     $ git log .
  6. 清理仓库
     $ 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
  7. 关联远程仓库并上传
     $ git remote add origin https://github.com/breezetemple/split-test.git
     $ git push -u origin master
     $ git push -u origin bump_syntaxhighlight

Ref

  1. Detach (move) subdirectory into separate Git repository
  2. merge_git_repo_as_subdir
  3. Move directory from one repository to another, preserving history
  4. git filter-branch
  5. an alternative to git-filter-branch – BFG Repo-Cleaner
  6. Preserve –no-ff merge commits with git filter-branch –subdirectory-filter
  7. git拆分工程