如何使用其中一个遥控器恢复本地丢失的 git 裸仓库(使用 separate-git-dir)

How to restore a locally lost git bare repo (using separate-git-dir) with one of its remotes

很久以前,我使用 git --separate-git-dir 设置了一个回购协议,因为我确实想要工作目录中的回购协议。不幸的是,那个单独的(大概是 "bare"?)repo 因硬盘驱动器故障而丢失,我想使用我添加的远程(经常被推送到)的内容重建它。

我该怎么做?

我尝试重新创建一个新的空 git 存储库:

git init --separate-git-dir=/desired/path/to/bare/git/repo

这当然会在工作目录中创建一个 .git 文件,其内容为 gitdir: /desired/path/to/bare/git/repo

然后,我做了:

git remote add network ssh://host/path/to/repo
git fetch network

但是,如果我 运行 git status,我得到

On branch master

No commits yet

我想得到什么

我只使用过(并推送过)master 分支。我希望在不修改工作目录的情况下让这个 repo 达到我可以键入 git status 的状态,希望它只看到当前未修改的工作目录和推送到远程的最后一次提交之间的变化,现在应该在我当地裸露吗?

我真的只是想从我离开的地方继续。

谢谢。

我认为这是最好的方法:

您输入的命令是一个好的开始:

git init --separate-git-dir=/desired/path/to/bare/git/repo
git remote add network ssh://host/path/to/repo
git fetch network

现在,您有了一个新的存储库,并且您的工作树完全保持不变。 Git 自动创建一个名为 "master" 的分支。分支 network/master 包含所有提交历史。但是你当地的主人是个孤儿..没有承诺。解决方案是将您的本地主机指向网络主机:

git reset network/master

您的本地工作树保持不变...但是您的索引和 HEAD 现在与 network/master 相同。 Git status 和 git diff 应该显示您更改的文件。您现在可以 git commit -a 或 git commit add 和 git commit.

TL;DR:剩下的就是创建一个 master b运行ch,可能带有 git branch master network/master(可能带有各种标志;见下文),填写索引,大概使用git read-tree。因此:

git branch master network/master
git read-tree master

可能就足够了。


既然我有一些空闲时间,让我把我上面的评论变成一个实际的答案。我们注意到 --separate-git-dir 不会使存储库成为 bare——裸存储库是 no 工作树——它只是将工作树和存储库(.git/* 文件)放在文件系统中的两个不同位置。要实现此功能,Git 在工作树中添加一个 .git 文件 ,如您所述,其中包含存储库本身的路径名。

因此,在这种情况下,该存储库本身已被损坏或破坏。您有一个没有存储库的工作树。您已经使用另一个 git init --separate-git-dir=... 和 运行 命令创建了一个新的空存储库:

git remote add network ssh://host/path/to/repo

(我可能会将遥控器命名为 origin 而不是 network,但是没有 错误 network,只是有点非常规。)

您接下来需要做的是用提交填充存储库本身,您做到了:

git fetch network

您的存储库本身(在任何目录中)现在有一些提交集,从您 运行 git fetch 的 URL 的 Git 存储库复制。

此时几乎一切正常。缺少两件事:

  • 没有本地 b运行ches,尽管 currentb运行ch 是名为 master。 b运行ch master 不存在。

  • 索引——将进入您将进行的下一个提交的文件集合——是空的。

如果您现在要进行提交,那将创建一个没有父提交且没有内容(empty tree)的提交,并创建 b运行ch master,但这很明显不是您想要的。相反,您可能想要:

  1. 创建本地名称 master 以匹配指向 b运行ch [=15 的最终提交的远程跟踪名称 origin/master =] 在您从中复制所有提交的 network 存储库中。为此,请使用:

    git branch master network/master
    

    你可以使用你喜欢的任何东西作为最后的参数,只要它命名任何实际的提交:原始哈希 ID 就足够了,或者类似 network/master^,或 network/master~3,或其他任何东西.如果您使用名称network/master,Git会识别出这是一个远程跟踪名称.1 因此,git branch 将此远程跟踪名称设置为新(本地)b运行ch 名称的 upstream master.

    更准确地说,自动上游设置是此处 git branch 默认 操作(以及某些形式的 git checkout)。您可以配置 Git 来更改默认值,或者您可以添加命令行标志来覆盖您已设置或未设置的任何默认值。

    为了防止这个上游设置,使用git branch --no-track master network/master。要强制 上游设置,请使用git branch --track master network/master,您可以将其缩写为git branch --track network/master

    是否以及何时设置上游设置由您决定。您可以随时使用 git branch --set-upstream 更改它,或稍后使用 git branch --unset-upstream.

  2. 删除它
  3. 既然 master 已经存在——或者您可以在创建 master 之前执行此操作,但我会在之后执行此操作,因为它感觉更简单且更容易正确——您将希望从您选择作为当前提交的提交中填充索引。通常 — 在这种 "repair broken Git repository" 情况以外的情况下 — 我们会使用 git checkout 同时执行第 1 步和第 2 步,但您想 不使用 ] 扰乱了当前的工作树,git checkout 扰乱了当前的工作树。所以我们将此作为单独的步骤 2,使用 Git 的较低级别 plumbing 命令之一:2

    git read-tree master
    

    (或者 git read-tree HEADgit read-tree @,如果你想输入更少:这三个都做同样的事情)。这只是将命名提交读入索引,根本不做任何其他事情:它将索引中的任何内容(什么都没有)替换为命名提交中的任何内容。

在完成 git branchgit read-tree 之后,git status 将能够比较当前提交——由 HEAD / master 命名的提交——与当前索引内容——它们当然会匹配——然后将当前索引内容与当前工作树内容进行比较。这些将根据您在第 1 步中设置自己的 master 时选择的提交来匹配或不同,并且无论如何您都准备好 git add 并根据需要进行新的提交。


1A remote-tracking name 是全拼以 refs/remotes/ 开头的任何名称。在这种情况下 network/master 实际上是 refs/remotes/network/master。 Git 文档大多称这些 remote-tracking b运行ch names 但我认为这里的 "branch" 这个词更具误导性,所以我省略了。

这些名称存在于您的 Git 存储库中,并根据您的Git 的b运行ch 名称自动创建和更新从另一个 Git 获取 - URL 中的那个存储在名称 network 下 - 每当你的 Git 从 Git 执行 git fetchnetwork。成功的 git push network 还会更新他们根据您 Git 的请求设置的任何 b运行 项。

2水暖瓷器在Git的区别真是就谁应该使用命令而言:瓷器命令是用户友好且面向目标的,3 管道命令是瓷器命令命令可能用作一系列此类命令中的一个(and/or 与其他系统实用程序结合使用)以实现某些实际目标。因此,管道命令往往是额外特定的和面向机制的(git read-tree、"fill index from commit";git rev-parse、"turn human-readable name into internal hash ID";git update-ref、"write raw hash ID into arbitrary reference" ).

3或者 "less actively user-hostile"。 :-) 另见 https://git-man-page-generator.lokaltog.net/

torek mentions in :

We've noted that --separate-git-dir doesn't make the repository bare—a bare repository is one with no work-tree — it just puts the work-tree and repository (.git/* files) in two different locations in the file system.

更清楚地说,你不能将 --bare--separate-git-dir 一起使用,考虑到你想要获得一个工作树,然后你需要填充(git switch -C master 应该是够了)

对于 Git 2.29(2020 年第 4 季度),“git init --separate-git-dir(man)”的目的是使用与工作树分开的存储库初始化一个新项目,或者,在现有项目的情况下,将存储库(.git/ 目录)移出工作树。
--separate-git-dir 与没有工作树的裸存储库一起使用是没有意义的,因此禁止将其与裸存储库一起使用。

参见 commit ccf236a (09 Aug 2020) by Eric Sunshine (sunshineco)
(由 Junio C Hamano -- gitster -- in commit a654836 合并,2020 年 8 月 24 日)

init: disallow --separate-git-dir with bare repository

Signed-off-by: Eric Sunshine

The purpose of "git init --separate-git-dir(man)" is to separate the repository from the worktree.
This is true even when --separate-git-dir is used on an existing worktree, in which case, it moves the .git/ subdirectory to a new location outside the worktree.

However, an outright bare repository (such as one created by "git init --bare"(man)), has no worktree, so using --separate-git-dir to separate it from its non-existent worktree is nonsensical. Therefore, make it an error to use --separate-git-dir on a bare repository.

Implementation note: "git init"(man) considers a repository bare if told so explicitly via --bare or if it guesses it to be so based upon heuristics.

  • In the explicit --bare case, a conflict with --separate-git-dir is easy to detect early.
  • In the guessed case, however, the conflict can only be detected once "bareness" is guessed, which happens after "git init"(man) has begun creating the repository.

Technically, we can get by with a single late check which would cover both cases, however, erroring out early, when possible, without leaving detritus provides a better user experience.


在 Git 2.29(2020 年第 4 季度)中,“git worktree"(man) 获得了一个“repair”子命令来帮助用户在手动移动工作树或存储库后进行恢复瞒着 Git.

此外,“git init --separate-work-dir=<path>"(man) 不再破坏与链接工作树相关的管理数据。

参见 commit 59d876c, commit 42264bc, commit b214ab5, commit bdd1f3e (31 Aug 2020), and commit e8e1ff2 (27 Aug 2020) by Eric Sunshine (sunshineco)
(由 Junio C Hamano -- gitster -- in commit eb7460f 合并,2020 年 9 月 9 日)

init: make --separate-git-dir work from within linked worktree

Reported-by: Henré Botha
Signed-off-by: Eric Sunshine

The intention of git init --separate-work-dir=<path>(man) is to move the .git/ directory to a location outside of the main worktree.

When used within a linked worktree, however, rather than moving the .git/ directory as intended, it instead incorrectly moves the worktree's .git/worktrees/<id> directory to <path>, thus disconnecting the linked worktree from its parent repository and breaking the worktree in the process since its local . git(man) file no longer points at a location at which it can find the object database.
Fix this broken behavior.

An intentional side-effect of this change is that it also closes a loophole not caught by ccf236a23a ("init: disallow --separate-git-dir with bare repository", 2020-08-09, Git v2.29.0 -- merge) in which the check to prevent --separate-git-dir being used in conjunction with a bare repository was unable to detect the invalid combination when invoked from within a linked worktree.
Therefore, add a test to verify that this loophole is closed, as well.


使用 Git 2.31(2021 年第一季度),“git worktree repair"(man) 学会了处理存储库和工作树都移动的情况。

参见 commit cf76bae (21 Dec 2020) by Eric Sunshine (sunshineco)
(由 Junio C Hamano -- gitster -- in commit 8664fcb 合并,2021 年 1 月 6 日)

worktree: teach repair to fix multi-directional breakage

Signed-off-by: Eric Sunshine

git worktree repair(man) knows how to repair the two-way links between the repository and a worktree as long as a link in one or the other direction is sound.

For instance, if a linked worktree is moved (without using git worktree move(man)), repair is possible because the worktree still knows the location of the repository even though the repository no longer knows where the worktree is.
Similarly, if the repository is moved, repair is possible since the repository still knows the locations of the worktrees even though the worktrees no longer know where the repository is.

However, if both the repository and the worktrees are moved, then links are severed in both directions, and no repair is possible.
This is the case even when the new worktree locations are specified as arguments to git worktree repair.

The reason for this limitation is twofold.

  • First, when repair consults the worktree's gitfile (/path/to/worktree/.git) to determine the corresponding <repo>/worktrees/<id>/gitdir file to fix, <repo> is the old path to the repository, thus it is unable to fix the gitdir file at its new location since it doesn't know where it is.
  • Second, when repair consults <repo>/worktrees/<id>/gitdir to find the location of the worktree's gitfile (/path/to/worktree/.git), the path recorded in gitdir is the old location of the worktree's gitfile, thus it is unable to repair the gitfile since it doesn't know where it is.

Fix these shortcomings by teaching repair to attempt to infer the new location of the <repo>/worktrees/<id>/gitdir file when the location recorded in the worktree's gitfile has become stale but the file is otherwise well-formed.

The inference is intentionally simple-minded.
For each worktree path specified as an argument, git worktree repair manually reads the ".git" gitfile at that location and, if it is well-formed, extracts the <id>.
It then searches for a corresponding <id> in <repo>/worktrees/ and, if found, concludes that there is a reasonable match and updates <repo>/worktrees/<id>/gitdir to point at the specified worktree path.
In order for <repo> to be known, git worktree repair must be run in the main worktree or bare repository.

git worktree repair first attempts to repair each incoming /path/to/worktree/.git gitfile to point at the repository, and then attempts to repair outgoing <repo>/worktrees/<id>/gitdir files to point at the worktrees.

This sequence was chosen arbitrarily when originally implemented since the order of fixes is immaterial as long as one side of the two-way link between the repository and a worktree is sound.

However, for this new repair technique to work, the order must be reversed. This is because the new inference mechanism, when it is successful, allows the outgoing <repo>/worktrees/<id>/gitdir file to be repaired, thus fixing one side of the two-way link.
Once that side is fixed, the other side can be fixed by the existing repair mechanism, hence the order of repairs is now significant.

Two safeguards are employed to avoid hijacking a worktree from a different repository if the user accidentally specifies a foreign worktree as an argument.

  • The first, as described above, is that it requires an <id> match between the repository and the worktree. That itself is not foolproof for preventing hijack, so:
  • the second safeguard is that the inference will only kick in if the worktree's /path/to/worktree/.git gitfile does not point at a repository.

git worktree 现在包含在其 man page 中:

If both the main working tree and linked working trees have been moved manually, then running repair in the main working tree and specifying the new <path> of each linked working tree will reestablish all connections in both directions.