ChatGPT解决这个技术问题 Extra ChatGPT

如何将一些文件从一个 git repo 移动到另一个(不是克隆),保留历史

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

但这似乎很令人费解。一般来说,有没有更好的方法来做这种事情?还是我采用了正确的方法?

请注意,这涉及将历史合并到现有存储库中,而不是简单地从另一个存储库 (as in an earlier question) 的一部分创建新的独立存储库。

这听起来对我来说是一种合理的方法。我想不出任何明显的方法来显着改进您的方法。很高兴 Git 实际上确实使这变得容易(例如,我不想尝试在 Subversion 的不同存储库之间移动文件目录)。
@ebneter - 我已经使用shell脚本手动完成了这个(将历史从一个svn repo移动到另一个)。基本上,我将特定文件/目录中的历史记录(差异、提交日志消息)重播到第二个存储库中。
我想知道你为什么不做 git fetch p2 && git merge p2 而不是 git fetch p2 && git branch .. && git merge p2?编辑:好吧,看起来你想在一个名为 p2 的新分支中获取更改,而不是当前分支。
有没有办法防止 --filter-branch 破坏目录结构?那个“git mv”步骤会导致大量的文件删除和文件创建。
git filter-repo 是 2021 年执行此操作的正确工具,而不是 filter-branch

F
Fabio says Reinstate Monica

如果您的历史记录是健全的,您可以将提交作为补丁取出并将它们应用到新存储库中:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

或者在一行

git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

(取自 Exherbo’s docs


对于我需要移动的三个或四个文件,这是一个比接受的答案更简单的解决方案。我最终用 find-replace 修剪了补丁文件中的路径,以使其适合我的新 repo 的目录结构。
我添加了选项,以便二进制文件(如图像)也可以正确迁移:git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch。工作没有问题 AFAICT。
在应用步骤中,我使用 --committer-date-is-author-date 选项来保留原始提交日期,而不是文件移动的日期。
不适用于已移动/重命名的文件。我假设您需要为每个文件制作单独的补丁并将 --follow 选项添加到 git log(一次只能处理一个文件)。
历史记录中的合并提交会破坏“am”命令。您可以在上面的 git log 命令中添加“-m --first-parent”,然后它对我有用。
w
wass rubleff

尝试了各种方法将文件或文件夹从一个 Git 存储库移动到另一个存储库后,下面概述了唯一一种似乎可靠工作的方法。

它涉及克隆要从中移动文件或文件夹的存储库,将该文件或文件夹移动到根目录,重写 Git 历史记录,克隆目标存储库并将具有历史记录的文件或文件夹直接拉到此目标存储库中。

第一阶段

制作存储库 A 的副本,因为以下步骤对此副本进行了重大更改,您不应该推送! git clone --branch --origin origin --progress \ -v # 例如。 git clone --branch master --origin origin --progress \ # -v https://username@giturl/scm/projects/myprojects.git #(假设 myprojects 是您要从中复制的存储库) cd into it cd < git repository A 目录> # 例如。 cd /c/Working/GIT/myprojects 删除指向原始存储库的链接以避免意外进行任何远程更改(例如通过推送) git remote rm origin 浏览您的历史记录和文件,删除目录 1 中没有的任何内容。结果是目录 1 的内容涌入存储库 A 的基础。 git filter-branch --subdirectory-filter -- --all # 例如。 git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all 仅用于单个文件移动:检查剩下的内容并删除除所需文件之外的所有内容。 (您可能需要删除不想要的同名文件并提交。) git filter-branch -f --index-filter \ 'git ls-files -s | grep $'\t'FILE_TO_KEEP$ | GIT_INDEX_FILE=$GIT_INDEX_FILE.new \ git update-index --index-info && \ mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all # 例如。 FILE_TO_KEEP = pom.xml 仅保留来自 FOLDER_TO_KEEP 的 pom.xml 文件

第二阶段

清理步骤 git reset --hard 清理步骤 git gc --aggressive 清理步骤 git prune

您可能希望将这些文件导入到存储库 B 中的目录而不是根目录中:

使该目录 mkdir 例如。 mkdir FOLDER_TO_KEEP 将文件移动到该目录 git mv * 例如。 git mv * FOLDER_TO_KEEP 将文件添加到该目录 git add 。提交您的更改,我们已准备好将这些文件合并到新的存储库中 git commit

第三阶段

如果您还没有存储库 B,请复制存储库 B 的副本 git clone # 例如。 git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git(假设 FOLDER_TO_KEEP 是您要复制到的新存储库的名称) cd into it cd # 例如。 cd /c/Working/GIT/FOLDER_TO_KEEP 创建到存储库 A 的远程连接,作为存储库 B 中的一个分支 git remote add repo-A-branch # (repo-A-branch 可以是任何东西 - 它只是一个任意名称)#例如。 git remote add repo-A-branch /c/Working/GIT/myprojects 从这个分支(只包含你要移动的目录)拉到存储库 B 中。 git pull repo-A-branch master --allow-unrelated-histories 的pull 复制文件和历史。注意:您可以使用合并而不是拉取,但拉取效果更好。最后,您可能想通过删除与存储库 A git remote rm repo-A-branch Push 的远程连接来进行一些清理,一切就绪。 git 推送


我已经完成了这里列出的大多数步骤,但是它似乎只从主服务器(而不是从任何其他分支)复制文件或目录的提交历史。那正确吗?
我完成了这些步骤(感谢对细节的关注!),但我注意到在 GitHub 中它没有显示任何文件的历史记录,除了合并提交。但是,如果我责怪或 gitk,我会看到提交历史记录。知道为什么吗?
@mcarans 1. 我猜你的答案类似于 Greg Bayer's blog 2. 我没有在 第二阶段 中运行前三个命令我移动到将文件移动到新目录的步骤。 我是否还需要将文件夹.git移动到新目录中 3. 第二阶段中的修剪步骤我没有理解 有其他我不想碰的分支。
@mcarans 收到以下错误:致命:在第三阶段的第 4 步中应用时找不到远程参考 repo-B-branch,git pull repo-A-branch repo-B-branch --allow-unrelated-histories 但是 repo -B-branch 存在于 repo B
@mcarans 不幸的是,这不是可靠的方法,尽管它似乎是。它与所有其他解决方案存在相同的问题 - 它不保留重命名后的历史记录。就我而言,第一次提交是我重命名目录/文件时。除此之外的一切都丢失了。
S
StackzOfZtuff

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

当然,您可以改进细节 - 您的一些克隆和分支并不是绝对必要的 - 但整体方法很好!很遗憾它很复杂,但当然,git 的目的并不是让重写历史变得容易。


如果您的文件已通过多个目录移动,现在驻留在一个目录中怎么办——子目录过滤器仍然有效吗? (即我假设如果我只想移动一个文件,我可以将它移动到它自己的子目录中,这样可以吗?)
@rogerdpack:不,这不会通过重命名来跟随文件。我相信它似乎是在它被移动到选定的子目录时创建的。如果您只想选择一个文件,请查看 filter-branch 联机帮助页中的 --index-filter
有没有关于如何遵循重命名的秘诀?
我认为维护和管理历史是 git 的要点之一。
关于以下重命名:stackoverflow.com/questions/65220628/…(尚无答案,但希望将来会有)
c
codeMonkey

这通过使用 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 (more details and options in README)

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2

您需要在 git remote addgit merge 之间运行 git fetch 以使目标存储库了解源存储库中的更改。
我在 temp (project2) 克隆上一次性过滤并重命名:git filter-repo --path sub/dir --path-rename sub:newsub 以获得 /newsub/dir 的树。该工具使该过程非常简单。
如果文件先前被移动/重命名,这将不会自动保留移动/重命名之前的历史记录。但是,如果您在命令中包含原始路径/文件名,则不会删除该历史记录。例如,git filter-repo --path CurrentPathAfterRename --path OldPathBeforeRenamegit filter-repo --analyze 生成一个文件 renames.txt,有助于确定这些文件。或者,您可能会发现 script like this 很有帮助。
这也适用于移动单个文件。在 git filter-repo 命令参数中,只需为您要移动的每个单独文件或目录添加一个 --path 参数。
a
anhoppe

我发现 Ross Hendrickson's blog 非常有用。这是一种非常简单的方法,您可以在其中创建应用于新存储库的补丁。有关更多详细信息,请参阅链接页面。

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

# 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 错误。所以我不得不一个接一个地应用所有补丁。


对我没有用,因为在某些时候 sha-hashes 丢失了。这对我有帮助:stackoverflow.com/questions/17371150/…
与“git log”方法不同,这个选项对我来说非常有效!谢谢!
尝试了不同的方法将项目移动到新的仓库。这是唯一对我有用的。不敢相信这么普通的任务竟然这么复杂。
感谢分享Ross Hendrickson's blog。这种方法对我有用。
这是一个非常优雅的解决方案,但是,它再次遇到与所有其他解决方案相同的问题 - 它不会保留重命名后的历史记录。
J
Jocce Nilsson

保留目录名称

子目录过滤器(或更短的命令 git subtree)效果很好,但对我不起作用,因为它们从提交信息中删除了目录名称。在我的场景中,我只想将一个存储库的一部分合并到另一个存储库中,并保留带有完整路径名的历史记录。

我的解决方案是使用树过滤器并简单地从源存储库的临时克隆中删除不需要的文件和目录,然后通过 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

script 不会对您的原始存储库进行任何修改。如果映射文件中指定的 dest 存储库不存在,则此脚本将尝试创建它。
我还认为保持目录名称完整是非常重要的。否则,您将获得对目标存储库的额外重命名提交。
H
Hugh Perkins

我经常使用的是这里的 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 log 以彩色显示,则 grep ^commit 可能不起作用。如果是这样,请将 --no-color 添加到该 git log 命令。 (例如,git log --no-color $reposrc
W
WestCoastProjects

有类似的痒痒(尽管仅适用于给定存储库的某些文件),这个脚本被证明是非常有用的:git-import

简短的版本是它从现有存储库创建给定文件或目录 ($object) 的补丁文件:

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

然后将其应用于新的存储库:

cd new_repo
git am "$temp"/*.patch 

详情请查阅:

记录的来源

git 格式补丁

我是

更新(来自另一位作者)以下 bash 函数可以使用这种有用的方法。这是一个示例用法:

gitcp <Repo1_basedir> <path_inside_repo1> <Repo2_basedir>

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
}

o
oHo

此答案提供了基于 git am 的有趣命令,并通过示例逐步呈现。

客观的

您希望将部分或全部文件从一个存储库移动到另一个存储库。

你想保留他们的历史。

但是您并不关心保留标签和分支。

您接受重命名文件(以及重命名目录中的文件)的有限历史记录。

程序

使用 git log --pretty=email -p --reverse --full-index --binary 以电子邮件格式提取历史记录重新组织文件树并更新历史记录中的文件名更改 [可选] 使用 git am 应用新历史记录

1.以电子邮件格式提取历史记录

示例:提取 file3file4file5 的历史记录

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

清理临时目录目标

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

清理你的 repo 源

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

以电子邮件格式提取每个文件的历史记录

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

很遗憾,选项 --follow--find-copies-harder 不能与 --reverse 结合使用。这就是为什么当文件被重命名(或父目录被重命名)时历史被删除的原因。

之后:电子邮件格式的临时历史记录

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

2.重新组织文件树并更新历史中的文件名更改[可选]

假设您想将这三个文件移动到另一个 repo 中(可以是同一个 repo)。

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

因此重新组织您的文件:

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

您的临时历史现在是:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

更改历史记录中的文件名:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

注意:这会重写历史记录以反映路径和文件名的变化。 (即新仓库中新位置/名称的更改)

3.应用新的历史

您的另一个回购是:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

从临时历史文件应用提交:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

你的另一个仓库现在是:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

使用 git status 查看准备推送的提交数量 :-)

注意:由于历史记录已被重写以反映路径和文件名的更改:(即与上一个 repo 中的位置/名称相比)

无需 git mv 即可更改位置/文件名。

无需 git log --follow 即可访问完整历史记录。

额外技巧:在你的仓库中检测重命名/移动的文件

要列出已重命名的文件:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

更多自定义:您可以使用选项 --find-copies-harder--reverse 完成命令 git log。您还可以使用 cut -f3- 和 grepping 完整模式 '{.* => 删除前两列.*}'。

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

C
Chetan Basutkar

尝试这个

cd repo1

这将删除除提到的目录之外的所有目录,仅保留这些目录的历史记录

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

现在您可以在您的 git 遥控器中添加您的新仓库并将其推送到该仓库

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

添加 -f 以覆盖


警告: git-filter-branch 有大量的陷阱会产生错误的历史重写。在继续中止之前按 Ctrl-C,然后使用替代过滤工具,例如“git filter-repo”(github.com/newren/git-filter-repo)。有关详细信息,请参阅过滤器分支手册页;要消除此警告,请设置 FILTER_BRANCH_SQUELCH_WARNING=1。
c
crimbo

使用来自 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 分支上的文件。


j
jakub.g

我想要一些健壮且可重用的东西(一个命令并执行 + 撤消功能),所以我编写了以下 bash 脚本。多次为我工作,所以我想我会在这里分享。

它能够将任意文件夹 /path/to/foorepo1 移动到 /some/other/folder/barrepo2(文件夹路径可以相同或不同,与根文件夹的距离可能不同)。

由于它只检查涉及输入文件夹中文件的提交(而不是源存储库的所有提交),因此即使在大型源存储库中它也应该非常快,如果您只是提取一个在每个文件中都没有触及的深层嵌套子文件夹犯罪。

因为这样做是用所有旧仓库的历史创建一个孤立的分支,然后将它合并到 HEAD,它甚至可以在文件名冲突的情况下工作(然后你必须在课程结束时解决合并) .

如果没有文件名冲突,您只需要最后的 git commit 即可完成合并。

缺点是它可能不会跟随源 repo 中的文件重命名(在 REWRITE_FROM 文件夹之外) - 欢迎在 GitHub 上提出拉取请求以适应这一点。

GitHub 链接:git-move-folder-between-repos-keep-history

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

感谢这个脚本,它真的很有帮助。两个小修复: 1. 如果 REWRITE_TO 包含破折号,sed 表达式会失败。例如“我的文件夹”。因此,我将其修改为使用 @ 作为分隔符:SED_COMMAND='s@\t\"*@\t'${REWRITE_TO}'@' 2. 在现代 git 中,您必须提供 --allow-unrelated-histories 标志才能合并:git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit --allow-unrelated-histories && 我希望它对某人有所帮助,Ori。
T
Tapuzi

git subtree 直观地工作,甚至保留历史。

示例用法:将 git repo 添加为子目录:

git subtree add --prefix foo https://github.com/git/git.git master

解释:

#├── repo_bar
#│   ├── bar.txt
#└── repo_foo
#    └── foo.txt

cd repo_bar
git subtree add --prefix foo ../repo_foo master

#├── repo_bar
#│   ├── bar.txt
#│   └── foo
#│       └── foo.txt
#└── repo_foo
#    └── foo.txt

这是迄今为止最好和最新的答案。
J
Jason D

就我而言,我不需要保留要从中迁移的存储库或保留任何以前的历史记录。我有一个相同分支的补丁,来自不同的遥控器

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

在这两个步骤中,我能够让另一个 repo 的分支出现在同一个 repo 中。

最后,我设置了这个分支(我从另一个仓库导入的)跟随目标仓库的主线(这样我就可以准确地区分它们)

git br --set-upstream-to=origin/mainline

现在它表现得好像它只是我针对同一个 repo 推送的另一个分支。


c
cjs

如果有问题的文件在两个存储库中的路径相同,并且您只想带入一个文件或一小组相关文件,一个简单的方法是使用 git cherry-pick

第一步是使用 git fetch <remote-url> 将来自其他存储库的提交带入您自己的本地存储库。这将使 FETCH_HEAD 指向另一个仓库的头部提交;如果您希望在完成其他提取后保留对该提交的引用,您可能需要使用 git tag other-head FETCH_HEAD 对其进行标记。

然后,您将需要为该文件创建一个初始提交(如果它不存在)或一个提交以使该文件进入一个可以使用您想要引入的其他存储库的第一个提交进行修补的状态。您可以如果 commit-0 引入了您想要的文件,则可以使用 git cherry-pick <commit-0> 执行此操作,或者您可能需要“手动”构建提交。如果您需要修改初始提交,例如,从该提交中删除您不想引入的文件,请将 -n 添加到樱桃选择选项中。

之后,您可以继续 git cherry-pick 后续提交,必要时再次使用 -n。在最简单的情况下(所有提交都是您想要的并且干净地应用),您可以在cherry-pick 命令行上提供完整的提交列表:git cherry-pick <commit-1> <commit-2> <commit-3> ...


R
Roopkumar Akubathini

以下通过维护所有分支并保留历史记录将我的 GIT Stash 迁移到 GitLab 的方法。

将旧存储库克隆到本地。

git clone --bare <STASH-URL>

在 GitLab 中创建一个空存储库。

git push --mirror <GitLab-URL>

当我们将代码从 stash 迁移到 GitLab 时,我执行了上述操作,并且效果非常好。