我们的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

但这似乎相当复杂。有没有更好的方法来做这类事情呢?还是我采取了正确的方法?

注意,这涉及到将历史合并到现有的存储库中,而不是简单地从另一个存储库的一部分创建一个新的独立存储库(如前面的问题中所述)。


当前回答

我经常用的是http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/。简单快捷。

为了符合stackoverflow标准,以下是步骤:

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

其他回答

保留目录名

子目录过滤器(或更短的命令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

是的,点击filter-branch的——subdirectory-filter是关键。您使用它的事实本质上证明了没有更简单的方法—您别无选择,只能重写历史,因为您希望最终只得到文件的一个(重命名的)子集,而这根据定义改变了哈希值。由于没有任何标准命令(例如pull)重写历史,因此您无法使用它们来完成此任务。

当然,您可以细化细节—您的一些克隆和分支并不是严格必要的—但是总体方法是好的!遗憾的是它很复杂,但是git的意义当然不是让重写历史变得容易。

我经常用的是http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/。简单快捷。

为了符合stackoverflow标准,以下是步骤:

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

我发现Ross Hendrickson的博客很有用。这是一种非常简单的方法,您可以创建应用于新回购的补丁。更多细节请参见链接页面。

它只包含三个步骤(复制自博客):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

唯一的问题是我不能一次性应用所有补丁

git am --3way <patch-directory>/*.patch

在Windows下,我得到了一个InvalidArgument错误。所以我不得不一个接一个地打补丁。

对于类似的问题(尽管只针对特定存储库中的某些文件),这个脚本被证明是非常有用的: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
}