我们的Git存储库最初是一个大型SVN存储库的一部分,其中每个项目都有自己的树,如下所示:
project1/branches
/tags
/trunk
project2/branches
/tags
/trunk
显然,使用svn mv将文件从一个文件移动到另一个文件非常容易。但是在Git中,每个项目都在它自己的存储库中,今天我被要求将一个子目录从project2移动到project1。我是这样做的:
$ git clone project2
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do
> git mv $f deeply/buried/different/java/source/directory/B
> done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push
但这似乎相当复杂。有没有更好的方法来做这类事情呢?还是我采取了正确的方法?
注意,这涉及到将历史合并到现有的存储库中,而不是简单地从另一个存储库的一部分创建一个新的独立存储库(如前面的问题中所述)。
对于类似的问题(尽管只针对特定存储库中的某些文件),这个脚本被证明是非常有用的:git-import
简短的版本是,它从现有的存储库中创建给定文件或目录($object)的补丁文件:
cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"
然后应用到一个新的存储库:
cd new_repo
git am "$temp"/*.patch
详情请查阅:
文档化的来源
git format-patch
git是
下面的bash函数可以使用这种有用的方法。下面是一个用法示例:
gitcp <Repo1_basedir> <path_inside_repo1> <Repo2_basedir> . gitcp <Repo1_basedir> <path_inside_repo1>
gitcp ()
{
fromdir="$1";
frompath="$2";
to="$3";
echo "Moving git files from "$fromdir" at "$frompath" to "$to" ..";
tmpdir=/tmp/gittmp;
cd "$fromdir";
git format-patch --thread -o $tmpdir --root -- "$frompath";
cd "$to";
git am $tmpdir/*.patch
}
使用来自http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/的灵感,我创建了这个Powershell函数来做同样的事情,到目前为止对我来说效果很好:
# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
# The file or directory within the current Git repo to migrate.
param([string] $fileOrDir)
# Path to the destination repo
param([string] $destRepoDir)
# A temp directory to use for storing the patch file (optional)
param([string] $tempDir = "\temp\migrateGit")
mkdir $tempDir
# git log $fileOrDir -- to list commits that will be migrated
Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
git format-patch -o $tempDir --root -- $fileOrDir
cd $destRepoDir
Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
ls $tempDir -Filter *.patch `
| foreach { git am $_.FullName }
}
这个例子的用法:
git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"
完成此操作后,可以在合并migrate-from-project2分支之前重新组织该分支上的文件。
如果有关文件的路径在两个repo中是相同的,并且您希望只带来一个文件或一小组相关文件,一个简单的方法是使用git进行选择。
第一步是使用git fetch <remote-url>将从另一个repo提交到您自己的本地repo。这将使FETCH_HEAD指向从另一个repo提交的头;如果你想在你完成了其他的取回之后保留对该提交的引用,你可以使用git tag other-head FETCH_HEAD来标记它。
You will then need to create an initial commit for that file (if it doesn't exist) or a commit to bring the file to a state that can be patched with the first commit from the other repo you want to bring in. You may be able to do this with a git cherry-pick <commit-0> if commit-0 introduced the files you want, or you may need to construct the commit 'by hand'. Add -n to the cherry-pick options if you need to modify the initial commit to, e.g., drop files from that commit you don't want to bring in.
在此之后,您可以继续精选后续提交,在必要时再次使用-n。在最简单的情况下(所有提交都是你想要的,并且干净地应用),你可以在cherry-pick命令行上给出完整的提交列表:git cherry-pick <commit-1> <commit-2> <commit-3> ....
通过使用git-filter-repo,这变得更简单。
为了移动project2/sub/dir到project1/sub/dir:
# Create a new repo containing only the subdirectory:
git clone project2 project2_clone --no-local
cd project2_clone
git filter-repo --path sub/dir
# Merge the new repo:
cd ../project1
git remote add tmp ../project2_clone/
git fetch tmp master
git merge remotes/tmp/master --allow-unrelated-histories
git remote remove tmp
简单地安装工具:pip3 install git-filter-repo
(更多细节和选项在README)
# Before: (root)
.
|-- project1
| `-- 3
`-- project2
|-- 1
`-- sub
`-- dir
`-- 2
# After: (project1)
.
├── 3
└── sub
└── dir
└── 2
保留目录名
子目录过滤器(或更短的命令git子树)工作得很好,但对我来说并不管用,因为他们从提交信息中删除了目录名。在我的场景中,我只想将一个存储库的部分合并到另一个存储库中,并保留历史记录WITH完整的路径名。
我的解决方案是使用树筛选器,简单地从源存储库的临时克隆中删除不需要的文件和目录,然后通过5个简单步骤将该克隆拉入目标存储库。
# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk
在我的例子中,我不需要保存我正在迁移的回购,也不需要保存任何以前的历史记录。我用不同的遥控器,截取了同一根树枝的补丁
#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory
在这两个步骤中,我能够让另一个回购的分支出现在同一个回购中。
最后,我设置这个分支(我从另一个回购导入)遵循目标回购的主线(所以我可以准确地区分它们)
git br --set-upstream-to=origin/mainline
现在它的行为就好像它只是我在同一次回购中推的另一个分支。