合并两个 GitHub 仓库而不丢失提交历史

合并两个 GitHub 仓库而不丢失历史记录

我们正在将 MDN Web 文档项目中较小的示例代码仓库合并到较大的父仓库中。我们认为将文件从一个仓库复制到另一个仓库会导致提交历史丢失,但我们觉得这可能是一个可以接受的策略。毕竟,我们并没有删除旧的仓库,而是将其存档。

在移动了几个仓库之后,我们确实 从一位社区成员那里收到了一个问题,指出在移动这些仓库时丢失历史记录并不理想,并且存在 一个相对简单的方法 来避免这种情况。我尝试了几个不同的选项,最终确定了基于 Eric Lee 在其博客上 分享的策略

tl;dr 该方法是使用基本的 git 命令将我们旧仓库的所有历史记录应用到新仓库中,而无需使用特殊的工具。

入门

对于实验,我使用了 sw-test 仓库,该仓库要合并到 dom-examples 仓库中。

这是 Eric 如何描述第一步

# Assume the current directory is where we want the new repository to be created
# Create the new repository

git init

# Before we do a merge, we need to have an initial commit, so we’ll make a dummy commit

dir > deleteme.txt
git add .
git commit -m “Initial dummy commit”

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master

我可以跳过到 `git remote ...` 步骤的所有内容,因为我的目标仓库已经有一些历史记录,所以我从以下步骤开始

git clone https://github.com/mdn/dom-examples.git
cd dom-examples

在这个仓库上运行 `git log`,我看到以下提交历史记录

commit cdfd2aeb93cb4bd8456345881997fcec1057efbb (HEAD -> master, upstream/master)
Merge: 1c7ff6e dfe991b
Author:
Date:   Fri Aug 5 10:21:27 2022 +0200

    Merge pull request #143 from mdn/sideshowbarker/webgl-sample6-UNPACK_FLIP_Y_WEBGL

    “Using textures in WebGL”: Fix orientation of Firefox logo

commit dfe991b5d1b34a492ccd524131982e140cf1e555
Author:
Date:   Fri Aug 5 17:08:50 2022 +0900

    “Using textures in WebGL”: Fix orientation of Firefox logo

    Fixes <https://github.com/mdn/content/issues/10132>

commit 1c7ff6eec8bb0fff5630a66a32d1b9b6b9d5a6e5
Merge: be41273 5618100
Author:
Date:   Fri Aug 5 09:01:56 2022 +0200

    Merge pull request #142 from mdn/sideshowbarker/webgl-demo-add-playsInline-drop-autoplay

    WebGL sample8: Drop “autoplay”; add “playsInline”

commit 56181007b7a33907097d767dfe837bb5573dcd38
Author:
Date:   Fri Aug 5 13:41:45 2022 +0900

在当前设置下,我可以从 `git remote` 命令继续,但我想知道当前目录是否包含与服务工作者仓库中文件或文件夹冲突的文件或文件夹。我四处搜索,看看是否有人遇到过同样的情况,但没有找到答案。然后我突然想到!我需要准备服务工作者仓库以便移动。

我的意思是什么?我需要在 `sw-test` 仓库的根目录中创建一个名为 `service-worker/sw-test` 的新目录,并将所有相关文件移动到这个新的子目录中。这将允许我安全地将其合并到 `dom-examples` 中,因为所有内容都已包含在子文件夹中。

要开始,我需要克隆我们要合并到 `dom-` `examples` 中的仓库。

git clone https://github.com/mdn/sw-test.git
cd sw-test

好的,现在我们可以开始准备仓库了。第一步是创建我们的新子目录。

mkdir service-worker
mkdir service-worker/sw-test

有了这个,我只需要将根目录中的所有内容移动到子目录中。为此,我们将使用 移动(mv)命令

注意:在此阶段不要运行以下任何命令。


# enable extendedglob for ZSH
set -o extendedglob
mv ^sw-test(D) service-worker/swtest

上面的命令比你想象的要复杂一些。它使用否定语法。下一节将解释为什么我们需要它以及如何启用它。

使用 `mv` 时如何排除子目录

虽然最终目标看起来很简单,但我敢肯定我为了弄清楚如何让最后一个移动命令生效而长出了不少白头发。我阅读了许多 StackOverflow 线程、博客文章和不同命令的手册页,取得了不同程度的成功。但是,最初的选项都没有完全满足我的需求。我最终偶然发现两个 StackOverflow 线程,它们为我指明了答案。

为了省去你的麻烦,以下是我的操作步骤。

首先,请注意。我在使用 ZSH 的 Mac 上(从 macOS Catalina 开始,它现在是默认 shell)。根据你的 shell,下面的说明可能会有所不同。

对于新版本的 ZSH,你可以使用 `set -o` 和 `set +o` 命令来启用和禁用设置。要启用 `extendedglob`,我使用了以下命令


# Yes, this _enables_ it
set -o extendedglob

在旧版本的 ZSH 上,你可以使用 `setopt` 和 `unsetopt` 命令。

setopt extendedglob

使用 bash,你可以使用以下命令实现相同的效果

shopt -s extglob

你可能想知道为什么要这样做?如果没有它,你将无法在上面的移动命令中使用我使用的否定运算符,而这是整个过程的关键。例如,如果你执行以下操作

mkdir service-worker
mv * service-worker/sw-test

它会“生效”,但你会看到类似这样的错误消息

mv: rename service-worker to service-worker/sw-test/service-worker: Invalid argument

我们希望告诉操作系统将所有内容都移动到我们的新子文件夹中,但该子文件夹本身除外。因此,我们需要这个否定语法。它默认情况下未启用,因为它可能会在文件名包含某些 `extendedglob` 模式(例如 `^`)时导致问题。因此,我们需要显式地启用它。

注意:你可能还想在完成移动操作后禁用它。

现在我们知道了如何以及为什么想要启用 `extendedglob`,我们可以继续使用我们的新功能。

注意:在此阶段不要运行以下任何命令。

mv ^sw-test(D) service-worker/sw-test

上面的意思是

  • 将当前目录中的所有文件移动到 `service-worker/sw-test` 中。
  • 不要尝试移动 `service-worker` 目录本身。
  • 选项 (D) 告诉移动命令也移动所有隐藏文件(例如 `。gitignore`)和隐藏文件夹(例如 `。git`)。

注意:我发现,如果我输入 `mv ^sw-test` 并按下 Tab 键,我的终端会将命令扩展为 `mv CODE_OF_CONDUCT.md LICENSE README.md app.js gallery image-list.js index.html service-worker star-wars-logo.jpg style.css sw.js`。如果我输入 `mv ^sw-test(D)` 并按下 Tab 键,它会扩展为 `mv .git .prettierrc CODE_OF_CONDUCT.md LICENSE README.md app.js gallery image-list.js index.html service-worker star-wars-logo.jpg style.css sw.js`。这很有趣,因为它清楚地展示了幕后发生的事情。这使你能够清楚地看到使用 `(D)` 的效果。我不确定这是否是 ZSH 的原生功能还是我的某个终端插件(例如 Fig)。你的情况可能有所不同。

处理隐藏文件并创建拉取请求

虽然能够这样移动所有隐藏文件和文件夹很好,但它会导致一个问题。由于 `。git` 文件夹被转移到我们的新子文件夹中,我们的根目录不再被视为 Git 仓库。这是一个问题。

因此,我不会运行带有 `(D)` 的上述命令,而是将隐藏文件作为单独的步骤进行移动。我将改为运行以下命令

mv ^(sw-test|service-worker) service-worker/sw-test

在此阶段,如果你运行 `ls`,它看起来像移动了所有内容。但事实并非如此,因为 `ls` 命令不会列出隐藏文件。要做到这一点,你需要传递 `-A` 标志,如下所示

ls -A

现在你应该看到类似以下内容

❯ ls -A
.git           .prettierrc    service-worker

查看上面的输出,我意识到我不需要移动 `。git` 文件夹。现在我只需要运行以下命令即可

mv .prettierrc service-worker

运行上面的命令后,`ls -A` 现在会输出以下内容

❯ ls -A
.git simple-service-worker

是时候来一段庆祝舞蹈了😁

现在我们可以继续了,因为我们已经成功地将所有内容移动到新的子目录中。但是,在执行此操作时,我意识到我忘记为工作创建一个 功能分支

这不是问题。我只需要运行命令 `git switch -C prepare-repo-for-move`。在此阶段运行 `git status` 应该会输出类似以下内容

❯ git status
On branch prepare-repo-for-move
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    .prettierrc
	deleted:    CODE_OF_CONDUCT.md
	deleted:    LICENSE
	deleted:    README.md
	deleted:    app.js
	deleted:    gallery/bountyHunters.jpg
	deleted:    gallery/myLittleVader.jpg
	deleted:    gallery/snowTroopers.jpg
	deleted:    image-list.js
	deleted:    index.html
	deleted:    star-wars-logo.jpg
	deleted:    style.css
	deleted:    sw.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	service-worker/

no changes added to commit (use "git add" and/or "git commit -a")

太好了!让我们添加我们的更改并提交它们。

git add .
git commit -m 'Moved all source files into new subdirectory'

现在我们想要推送我们的更改并打开一个拉取请求。

好的!让我们推送

git push origin prepare-repo-for-move

转到 GitHub 上的仓库。你应该会看到一个横幅,上面写着“mv-files-into-subdir 有一分钟前有最新的推送”以及一个“比较和拉取请求”按钮。

点击该按钮并按照步骤打开 拉取请求。一旦拉取请求变为绿色并且可以合并,就可以继续进行合并!

注意:根据你的工作流程,此时你可以要求团队成员在合并之前审查你提出的更改。另外,最好查看“已更改的文件”选项卡中的更改,以确保拉取请求中没有包含你无意包含的内容。如果有任何 冲突阻止 拉取请求合并,GitHub 会警告你,你需要解决这些冲突。这可以在 GitHub.com 上直接完成,也可以在本地完成并推送到 GitHub 作为单独的提交。

当你回到 GitHub 上的代码视图时,你应该会看到我们的新子目录和 `。gitignore` 文件。

这样,我们的仓库就准备好了移动。

合并我们的仓库

回到终端中,你需要切换回 `main` 分支

git switch main

你现在可以安全地删除功能分支并从远程分支拉取更改。

git branch -D prepare-repo-for-move
git pull origin main

拉取最新版本后运行 `ls -A` 应该会显示以下内容

❯ ls -A
.git           README.md      service-worker

此外,在根目录中运行 `git log` 会输出以下内容

commit 8fdfe7379130b8d6ea13ea8bf14a0bb45ad725d0 (HEAD -> gh-pages, origin/gh-pages, origin/HEAD)
Author: Schalk Neethling
Date:   Thu Aug 11 22:56:48 2022 +0200

    Create README.md

commit 254a95749c4cc3d7d2c7ec8a5902bea225870176
Merge: f5c319b bc2cdd9
Author: Schalk Neethling
Date:   Thu Aug 11 22:55:26 2022 +0200

    Merge pull request #45 from mdn/prepare-repo-for-move

    chore: prepare repo for move to dom-examples

commit bc2cdd939f568380ce03d56f50f16f2dc98d750c (origin/prepare-repo-for-move)
Author: Schalk Neethling
Date:   Thu Aug 11 22:53:13 2022 +0200

    chore: prepare repo for move to dom-examples

    Prepping the repository for the move to dom-examples

commit f5c319be3b8d4f14a1505173910877ca3bb429e5
Merge: d587747 2ed0eff
Author: Ruth John
Date:   Fri Mar 18 12:24:09 2022 +0000

    Merge pull request #43 from SimonSiefke/add-navigation-preload

以下是从我们之前偏离的地方留下的命令。

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master

好的,让我们结束这个过程。首先,我们需要进入我们要将项目移动到的项目的根目录。这里指的是 `dom-examples` 目录。进入目录的根目录后,运行以下命令

git remote add -f swtest https://github.com/mdn/sw-test.git

注意:`-f` 告诉 Git 获取远程分支。`ssw` 是你为远程分支指定的名称,因此它实际上可以是任何东西。

运行该命令后,我得到了以下输出

❯ git remote add -f swtest https://github.com/mdn/sw-test.git
Updating swtest
remote: Enumerating objects: 500, done.
remote: Counting objects: 100% (75/75), done.
remote: Compressing objects: 100% (57/57), done.
remote: Total 500 (delta 35), reused 45 (delta 15), pack-reused 425
Receiving objects: 100% (500/500), 759.76 KiB | 981.00 KiB/s, done.
Resolving deltas: 100% (269/269), done.
From <https://github.com/mdn/sw-test>
 * [new branch]      gh-pages        -> swtest/gh-pages
 * [new branch]      master          -> swtest/master
 * [new branch]      move-prettierrc -> swtest/move-prettierrc
 * [new branch]      rename-sw-test  -> swtest/rename-sw-test

注意:虽然我们在本地删除了分支,但它不会自动与远程同步,所以你仍然可以看到对rename-sw-test分支的引用。如果你想在远程删除它,你需要在仓库根目录运行以下命令:git push origin :rename-sw-test(如果你已经配置了你的仓库“自动删除头部分支”,那么它将自动为你删除)。

只剩下几个命令了。

注意:在此阶段不要运行以下任何命令。

git merge swtest/gh-pages

哎呀!当我运行上面的命令时,我得到了以下错误

❯ git merge swtest/gh-pages
fatal: refusing to merge unrelated histories

但这几乎就是我想要的,对吧?这是merge命令的默认行为,但你可以传递一个标志并允许这种行为。

git merge swtest/gh-pages --allow-unrelated-histories

注意:为什么是gh-pages?通常情况下,你在这里合并的分支是main,但在这个特定的仓库中,默认分支被命名为gh-pages。以前,在使用 GitHub Pages 时,你需要一个名为gh-pages的分支,它将由 GitHub 自动部署到一个 URL,这个 URL 类似于mdn.github.io/sw-test。

运行上面的命令后,我得到了以下结果

❯ git merge swtest/gh-pages --allow-unrelated-histories
Auto-merging README.md
CONFLICT (add/add): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

啊,是的,当然。我们当前的项目和我们正在合并的项目都包含一个README.md,所以 Git 要求我们决定该怎么做。如果你在编辑器中打开README.md文件,你会注意到类似于以下的内容

<<<<<<< HEAD

=======

文件中可能会有很多这样的内容。你还会看到一些类似于以下内容的条目:>>>>>>> swtest/gh-pages。这突出了 Git 不确定如何解决的冲突。你可以手动遍历并清除它们。在这种情况下,我只想要dom-examples仓库根目录中的README.md内容,所以我将清理冲突或从 GitHub 复制README.md中的内容。

正如 Git 所要求的,我们将添加并提交我们的更改。

git add .
git commit -m 'merging sw-test into dom-examples'

上面的命令产生了以下输出

❯ git commit
[146-chore-move-sw-test-into-dom-examples 4300221] Merge remote-tracking branch 'swtest/gh-pages' into 146-chore-move-sw-test-into-dom-examples

如果我现在在目录根目录运行git log,我将看到以下内容

commit 4300221fe76d324966826b528f4a901c5f17ae20 (HEAD -> 146-chore-move-sw-test-into-dom-examples)
Merge: cdfd2ae 70c0e1e
Author: Schalk Neethling
Date:   Sat Aug 13 14:02:48 2022 +0200

    Merge remote-tracking branch 'swtest/gh-pages' into 146-chore-move-sw-test-into-dom-examples

commit 70c0e1e53ddb7d7a26e746c4a3412ccef5a683d3 (swtest/gh-pages)
Merge: 4b7cfb2 d4a042d
Author: Schalk Neethling
Date:   Sat Aug 13 13:30:58 2022 +0200

    Merge pull request #47 from mdn/move-prettierrc

    chore: move prettierrc

commit d4a042df51ab65e60498e949ffb2092ac9bccffc (swtest/move-prettierrc)
Author: Schalk Neethling
Date:   Sat Aug 13 13:29:56 2022 +0200

    chore: move prettierrc

    Move `.prettierrc` into the siple-service-worker folder

commit 4b7cfb239a148095b770602d8f6d00c9f8b8cc15
Merge: 8fdfe73 c86d1a1
Author: Schalk Neethling
Date:   Sat Aug 13 13:22:31 2022 +0200

    Merge pull request #46 from mdn/rename-sw-test

耶!现在我们当前的仓库中有sw-test的历史记录了!现在运行ls -A显示

❯ ls -A
.git                           indexeddb-examples             screen-wake-lock-api
.gitignore                     insert-adjacent                screenleft-screentop
CODE_OF_CONDUCT.md             matchmedia                     scrolltooptions
LICENSE                        media                          server-sent-events
README.md                      media-session                  service-worker
abort-api                      mediaquerylist                 streams
auxclick                       payment-request                touchevents
canvas                         performance-apis               web-animations-api
channel-messaging-basic        picture-in-picture             web-crypto
channel-messaging-multimessage pointer-lock                   web-share
drag-and-drop                  pointerevents                  web-speech-api
fullscreen-api                 reporting-api                  web-storage
htmldialogelement-basic        resize-event                   web-workers
indexeddb-api                  resize-observer                webgl-examples

如果我运行ls -A service-worker/,我得到

❯ ls -A service-worker/
simple-service-worker

最后,运行ls -A service-worker/simple-service-worker/显示

❯ ls -A service-worker/simple-service-worker/
.prettierrc        README.md          image-list.js      style.css
CODE_OF_CONDUCT.md app.js             index.html         sw.js
LICENSE            gallery            star-wars-logo.jpg

剩下的就是推送到远程仓库了。

git push origin 146-chore-mo…dom-examples

注意:不要将这个拉取请求压缩合并,否则所有提交将被压缩成一个提交。相反,你应该使用合并提交。你可以在 GitHub 的文档中阅读有关合并方法的详细信息。

合并拉取请求后,继续浏览仓库的提交历史记录。你会发现提交历史记录是完整的,并且已经合并。o/\o 现在你可以删除或归档旧的仓库了。

在这一点上,远程仓库已经没有必要了,所以我们可以安全地删除它。

git remote rm swtest

总结

完成此任务的步骤如下

# Clone the repository you want to merge
git clone https://github.com/mdn/sw-test.git
cd sw-test

# Create your feature branch
git switch -C prepare-repo-for-move
# NOTE: With older versions of Git you can run:
# git checkout -b prepare-repo-for-move

# Create directories as needed. You may only need one, not two as
# in the example below.
mkdir service-worker
mkdir service-worker/sw-test

# Enable extendedglob so we can use negation
# The command below is for modern versions of ZSH. See earlier
# in the post for examples for bash and older versions of ZSH
set -o extendedglob

# Move everything except hidden files into your subdirectory,
# also, exclude your target directories
mv ^(sw-test|service-worker) service-worker/sw-test

# Move any of the hidden files or folders you _do_ want
# to move into the subdirectory
mv .prettierrc service-worker

# Add and commit your changes
git add .
git commit -m 'Moved all source files into new subdirectory'

# Push your changes to GitHub
git push origin prepare-repo-for-move

# Head over to the repository on GitHub, open and merge your pull request
# Back in the terminal, switch to your `main` branch
git switch main

# Delete your feature branch
# This is not technically required, but I like to clean up after myself :)
git branch -D prepare-repo-for-move
# Pull the changes you just merged
git pull origin main

# Change to the root directory of your target repository
# If you have not yet cloned your target repository, change
# out of your current directory
cd ..

# Clone your target repository
git clone https://github.com/mdn/dom-examples.git
# Change directory
cd dom-examples

# Create a feature branch for the work
git switch -C 146-chore-move-sw-test-into-dom-examples

# Add your merge target as a remote
git remote add -f ssw https://github.com/mdn/sw-test.git

# Merge the merge target and allow unrelated history
git merge swtest/gh-pages --allow-unrelated-histories

# Add and commit your changes
git add .
git commit -m 'merging sw-test into dom-examples'

# Push your changes to GitHub
git push origin 146-chore-move-sw-test-into-dom-examples

# Open the pull request, have it reviewed by a team member, and merge.
# Do not squash merge this pull request, or else all commits will be
# squashed together as a single commit. Instead, you want to use a merge commit.

# Remove the remote for the merge target
git remote rm swtest

希望你现在知道如何使用 mv 命令排除子目录,设置和查看 shell 配置,以及使用基本 git 命令将 git 仓库的文件内容合并到一个新仓库中,同时保留整个提交历史记录。

关于 Schalk Neethling

我是一名 Mozilla 用户、布道者、作家和开发者,热衷于开源、Web 标准和可访问性。我如此投入其中,以至于我认为它们已经成为我的一部分,无法预见未来这些主题将不再是我日常生活的一部分。

更多由 Schalk Neethling 撰写的文章…


一条评论

  1. Jean-Sébastien Bour

    与其添加虚拟内容,不如使用 --allow-empty 标志创建一个空的初始提交。

    2022 年 8 月 29 日 下午 2:09

本文的评论已关闭。