使用 git orphan 作为文件夹并保留历史记录

Sex*_*yMF 2 git orphan

我在不同的存储库中有一些项目,我想将它们与不同的孤立分支合并在同一个存储库下。为此,我创建了一个新的存储库并在其中启动它。

  1. 如何获取现有的存储库,将其作为孤立分支导入并保留历史记录?

  2. 是否可以使用作为不同文件夹打开的 2 个孤立分支?假设我有 2 个孤儿分支,我想同时处理这两个分支,可能吗?我有超过 2 个,我想使用打开的单个 git UI,这样开发起来会更有效率。今天我检查了每个存储库。将其合并到同一个存储库下后,我仍然需要并行处理所有分支。

orphan-branch-B
              \.gitignore
              \.README.md
              \ and more

orphan-branch-B
              \.gitignore
              \.README.md
              \ and more
Run Code Online (Sandbox Code Playgroud)

tor*_*rek 5

长话短说

\n\n
\n

我有超过 2 个 [分支],我想使用打开的单个 git UI ...

\n
\n\n

这是否可能,如果可能,如何实现,取决于用户界面。一般来说,询问 Git 不会给你答案。命令行答案是使用git worktree(非常需要 Git 2.15 或更高版本)。

\n\n

长的

\n\n
\n
    \n
  1. 如何获取现有的存储库,将其作为孤立分支导入并保留历史记录?
  2. \n
\n
\n\n

你不知道,真的。这个操作\xe2\x80\x94和问题\xe2\x80\x94可能没有意义,因为我怀疑你的意思是孤儿分支的意思是Git的意思。读到最后以确定它是否有意义。

\n\n

Git 存储库到底是什么?

\n\n

Git存储库本质上由两个数据库组成。一个数据库只保存 Git对象,其中最有趣的是称为提交的对象,每个提交代表所有文件的完整快照。1 另一个数据库保存名称\xe2\x80\x94 分支名称(例如\xe2\x80\x94 master)和标记名称(例如v2.1\xe2\x80\x94),这些名称是您(至少在最初的 Git)中找到有趣提交的方式。

\n\n

每个提交\xe2\x80\x94,再次代表所有文件的快照;提交包含更改\xe2\x80\x94 由其哈希 ID 唯一标识。哈希 ID 是一串丑陋的字母和数字,看起来是随机的,但实际上是提交的全部内容的加密校验和:快照,加上告诉您谁创建了快照的元数据(姓名和电子邮件地址),何时(时间戳)、为什么(日志消息)等等。因为每个提交都会存储其直接前一个提交或提交的实际哈希 ID,所以 Git 很容易从最后一个提交开始并向后工作:

\n\n
... <-F <-G <-H   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,分支名称只是保存分支中最后一次master提交的哈希 ID 。历史记录本身只是由该示例中的提交 \xe2\x80\x94\xe2\x80\x94 开始并向后工作(从提交到父提交,一次一个提交)形成的提交链。H

\n\n

这里有一个小问题,因为链条不一定是线性的。在进行了如上所述的一系列提交之后,我们可能还会有第二个提交序列:

\n\n
          G--H  <-- master\n         /\n...--E--F\n         \\\n          I--J   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,提交和更早的提交F都在两个分支上,而提交G-H仅在和仅上。如果我们然后合并到,我们会得到一个稍微特殊的提交:masterI-Jdevelop Jmaster

\n\n
          G--H\n         /    \\\n...--E--F      K  <-- master\n         \\    /\n          I--J   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

虽然 commitK像往常一样有一个简单的快照,但它现在有两个父级,而不是一个,使其成为合并提交。要查看 的历史记录,我们必须同时K返回到提交H J 两者。从那里我们回到G I;从那里我们回到F,在那里历史重新聚合,在合并时分叉。

\n\n

换句话说,Git 向后工作:历史在逻辑上在合并时收敛,并且由于 Git 向后工作,历史实际上在合并时发散。从逻辑上讲,历史在你衍生出第二个分支的那一点上是分歧的,但在 Git 中,它实际上在那一点上收敛了,因为 Git 是向后工作的。

\n\n

分支名称的特殊之处master在于它始终指向我们想要说的分支上的最后一次提交。这一点特别重要,因为您正在询问孤儿分支

\n\n
\n\n

1其他三种对象类型是(树保存文件名)、blob(每个 blob 是一个文件的内容)和带注释的标签,例如v2.1. Git 使用提交+树+blob 组合来构造每个提交所代表的快照。

\n\n
\n\n

Git 如何进行新的提交:索引和工作树

\n\n
\n
    \n
  1. 是否可以使用作为不同文件夹打开的 2 个孤立分支?
  2. \n
\n
\n\n

如果您有 Git 2.5 或更高版本\xe2\x80\x94,由于 Git 2.5\xe2\x80\x94 中初始实现中存在一些错误,2.15 或更高版本是一个好主意,您可以同时使用git worktree两个不同的分支,在两个不同的工作树中。现在是时候讨论 Git 的索引工作树概念了,之后我们将讨论孤立分支的定义。

\n\n

Git 提交快照中的所有内容都将永远冻结。任何提交\xe2\x80\x94 的任何部分(包括其日志消息、用户名、父哈希 ID 以及作为该提交\xe2\x80\x94 的一部分存储的任何已保存文件的任何部分)均无法更改。 由某个现有哈希 ID 标识的任何现有提交的任何内容都无法更改。 它的所有文件都被及时冻结。(它们也被压缩,有时非常压缩。如果您愿意,您可以将它们视为冻干的。)这对于存档非常有用:您可以随时返回到任何先前的提交。但这对于完成任何新工作来说毫无用处。

\n\n

为了让您完成工作,Git 使您能够检查提交。检查提交会做三件事:

\n\n
    \n
  1. 第一个也是最明显的一点是,它会“重新水合”冻干的提交,将其所有文件提取到某种工作区域,在那里它们具有正常的、非冻结的、非 Git 化的形式。该工作区域通常位于存储库本身旁边,是您的工作树(或工作树,有时是工作目录或此类拼写的某种变体。)

  2. \n
  3. 第二个,一旦您考虑一下,也很明显,如果您使用git checkout mastergit checkout develop或其他什么,它会记住您用来从该分支获取最新提交的分支名称。或者,如果您曾经git checkout <hash-id>回到过去,它会记住哈希 ID。无论哪种方式,\xe2\x80\x94 通过分支名称或哈希 ID\xe2\x80\x94,它也会记住您的提交

  4. \n
  5. 这里所做的第三件事(大部分是不可见的)git checkout是填写 Git\ 的索引

  6. \n
\n\n

把这个东西称为索引是一个无用的名称\xe2\x80\x94索引到底传达什么?\xe2\x80\x94所以它还有两个名字:它有时被称为暂存,有时被称为cache,取决于谁或 Git 的哪个部分正在执行此调用。不过,这三个名字都代表同一件事。索引的含义和作用在合并期间变得有点复杂,但它的主要作用是它以 Git 化的形式保存提交中的所有文件,准备冻结,但是 \xe2\x80\x94 与真正的 commit\xe2\x80\x94实际上并未冻结

\n\n

这意味着索引保存将进入下一次提交的所有文件。换句话说,这是提议的下一次提交。你从以下开始:

\n\n
git checkout master\n
Run Code Online (Sandbox Code Playgroud)\n\n

对于由 name 标识的提交中的每个文件,您现在拥有该文件的三个副本,master而不是两个:

\n\n
    \n
  • HEAD:file是存储在提交中的文件。它无法更改:它是 Git 化的、冻结的且只读的。用git show HEAD:file起来看看吧。

  • \n
  • :file是存储在索引中的文件。是可以改变的!它是 Git 化的,但您可以随时用新副本替换它。git show :file起来看看吧。

  • \n
  • file是存储在您的工作树中的文件。这是一个普通文件,您可以用它做任何您想做的事情。使用普通(非 Git)命令来查看或更改它或执行任何您想要的操作。

  • \n
\n\n

如果您更改了某些文件(例如 )file,并且希望 Git在下一次提交中存储版本,那么您现在必须更新建议的下一次提交:

\n\n
git add file\n
Run Code Online (Sandbox Code Playgroud)\n\n

这会将工作树文件复制到索引中,并用工作树中:file新的 Git 化文件副本覆盖。file

\n\n

因此,索引始终包含建议的下一次提交。您可以使用 更新此提案git add

\n\n

请注意,如果您使用git checkout其他分支,则可以将下一个提交提案替换为与您刚刚签出的提交相匹配的不同提案。(此规则有意有一些例外;请参阅当当前分支上存在未提交的更改时检出另一个分支。)这反过来意味着索引和工作树实际上是一对:索引索引工作树。当您通过更改周围的一些文件来更改工作树时,您需要通过git add这些文件来更新索引。

\n\n

当你运行 时git commit,Git 会执行以下操作:

\n\n
    \n
  • 保存您的姓名和电子邮件地址;
  • \n
  • 保存当前时间(新提交的时间戳);
  • \n
  • 从您那里收集日志消息,以进入新的提交;
  • \n
  • 使用当前提交的哈希 ID 作为父哈希 ID;
  • \n
  • 将所有这些内容以及索引中的 Git 化文件保存到新的提交中,该提交会自动获取新的哈希唯一哈希 ID(通过对所有这些数据计算加密校验和)
  • \n
  • 提交的哈希 ID 写入当前分支
  • \n
\n\n

也就是说,如果您有:

\n\n
...--F--G--H   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

你现在有:

\n\n
...--F--G--H--I   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,该名称 记录了您刚刚进行的新提交的master哈希 ID 。该新提交的父I提交是commit 的哈希 ID ,即您在进行此新提交之前签出的哈希 ID。H

\n\n

历史就是这样形成的!进行新的提交(当您运行时,Git刚刚根据索引中的任何内容进行了git commit新的提交)创建了我们的新提交I。新提交的父提交是您已通过 Git 签出的提交。因为 Git从索引、索引和新匹配中进行了提交,就像您第一次运行git checkout master获取 commit时所做的那样H。现在一切看起来都很适合您修改工作树中的内容,用于git add将其复制回索引中,然后运行git commit以创建一个新的,J其父级是I并且其保存的快照来自索引。

\n\n

建立一个新分支

\n\n

现在您已经了解了现有分支的工作原理,让我们看看创建分支的过程。I假设我们从您刚刚做出的提交开始master

\n\n
...--F--G--H--I   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

让我们创建一个名为的新分支feature/short

\n\n
git checkout -b feature/short\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们现在的样子是这样的:

\n\n
...--F--G--H--I   <-- master, feature/short (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

也就是说,名称\xe2\x80\x94bothmasterfeature/short\xe2\x80\x94 都标识现有提交I。特殊名称附加到 name上HEAD,Git 用它来记住我们所在的分支feature/short

\n\n

现在我们将像往常一样弄乱工作树,像git add往常一样运行,然后运行git commit​​. Git 将收集我们的姓名、电子邮件、时间、日志消息等,并J使用索引中的快照和父级进行新的提交I。然后它会将J\ 的实际哈希 ID(无论是什么)写入名称中feature/short

\n\n
...--F--G--H--I   <-- master\n               \\\n                J   <-- feature/short (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

历史可以追溯到,然后等等。新的提交位于新分支 的顶端。我们的索引现在与我们的提交和工作树相匹配,并且仍然附加到我们的分支。JIHfeature/shortJHEADfeature/short

\n\n

现在您已经了解了有关分支 \xe2\x80\x94 的所有信息,除了孤立分支(我们稍后会介绍)。

\n\n

添加工作树

\n\n

如果你仔细观察的话,你现在已经意识到,“索引”不仅索引了工作树,它和工作树还与这个特殊的名称有着密切的关系HEAD。我们用来git checkout将我们的名称附加到某个分支名称,在此过程中,我们用来自一个特定提交的HEAD所有内容填充我们的索引和工作树,该提交位于该分支的顶端\xe2\x80\x94,该提交是名点。所有这些实体\xe2\x80\x94 、索引、工作树和分支名称\xe2\x80\x94 同时更改。HEAD

\n\n

所做git worktree add的是创建一个新的三元组\xe2\x80\x94a新的<HEAD,索引,工作树>组\xe2\x80\x94并git checkout在该新组中运行。新的工作树必须驻留在计算机的不同区域:不同的文件夹(如果您喜欢术语“文件夹”)。新添加的工作树位于不同的分支上。所有工作树必须位于不同的分支上,即使这些分支名称标识相同的提交!每个工作树都有自己的索引 和HEAD,如果您从一个工作树切换到另一个工作树,则必须更改您的索引HEAD和索引的想法。

\n\n

每次提交中的文件都是冻干的:Git 化和压缩的,没有用处。提取到工作树中的文件经过重新水化并且有用。因此,添加更多工作树的能力意味着您可以同时进行不同的提交,只要它们位于不同的工作树中即可。

\n\n

(作为一种特殊情况,任何工作树都可以有一个分离的 HEAD,您可以在其中通过哈希 ID 提取特定的提交。因此,如果您需要查看 16 个不同的历史提交,则可以添加 16 个工作树,每个工作树位于不同的分离的例如,关注那个历史性的承诺。)

\n\n

孤儿分支

\n\n

现在我们已经解决了所有这些问题,我们终于可以\xe2\x80\x94了!\xe2\x80\x94看看什么是孤儿分支。它比你想象的要少!

\n\n

我们已经知道,它HEAD通常附加到某个现有分支名称,并且现有分支名称存储单个提交的哈希 ID,我们将其称为该分支的提示。当以这种方式设置时,进行新的提交会更新分支名称,以便现有的分支名称现在存储我们刚刚进行的新提交的新的、唯一的提交哈希 ID。

\n\n

我们还顺便提到,HEAD可以存储提交\xe2\x80\x94 的哈希 ID,Git 将其称为分离的 HEAD。这里HEAD没有附加分支名称,因此有“分离”一词。索引和工作树在这里以通常的方式工作:索引以冻干形式保存来自 detached-HEAD 提交哈希 ID 的所有文件,但实际上不再冻结,并且工作树保存所有文件来自该提交。您也可以通过这种方式进行新提交:如果您这样做,Git 只会将新提交的哈希 ID 存储到 name 中HEAD。没有分支名称会记住这个哈希 ID。仅HEAD保存该哈希 ID。这些提交很容易因错误而丢失!如果你用来git checkout移动你的HEAD,你已经丢失了你所做的新提交的哈希 ID\xe2\x80\x94,所以至少要小心分离的 HEAD,以免你失去理智。:-)

\n\n

不过,还有另一种模式HEAD。Git 允许您将您的分支名称附加HEAD不存在的分支名称。为此,您可以使用git checkout --orphan

\n\n
git checkout --orphan feature/tall\n
Run Code Online (Sandbox Code Playgroud)\n\n

这很像git checkout -b. 但-b首先创建分支名称然后附加HEAD到分支名称。这是分支名称的创建,在名称中存储了哈希 ID!当我们进行feature/short上述操作时,我们创建了指向现有提交的名称I,即已经记住的相同提交master

\n\n

当我们使用 时git checkout --orphan,Git不会创建分支名称。我们最终得到这样的图片:

\n\n
...--F--G--H--I   <-- master\n               \\\n                J   <-- feature/short\n\nfeature/tall (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

索引工作树的内容保持不变,与以前完全相同,但名称 根本feature/tall不作为分支名称存在。它只是HEAD附着在它上面。由于它不作为分支名称存在,因此它不指向任何现有的提交。

\n\n

如果我们现在进行提交,Git 会将索引的内容保存为新快照。如果我们没有更改任何内容,这些内容将与 commit 匹配J。所以我们会得到一个新的提交K。新提交的父K应该是我们现在签出的任何一个提交\xe2\x80\x94,即由我们所HEAD附加的分支名称标识的提交。但那个分支不存在!

\n\n

Git 在这里所做的就是在一个新的、完全空的、还没有提交的存储库中进行第一次提交时执行相同的操作。Git 只是简单地在没有父项的情况下进行提交。这样的提交称为根提交,我们可以这样画:

\n\n
K\n
Run Code Online (Sandbox Code Playgroud)\n\n

完成新的提交后,Git 现在会更新我们所附加的分支名称HEAD。这个名字是feature/tall,所以现在我们有:

\n\n
...--F--G--H--I   <-- master\n               \\\n                J   <-- feature/short\n\nK   <-- feature/tall (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

新分支feature/tall现已存在。它的出现是因为我们一如既往地从索引\xe2\x80\x94 进行了新的提交\xe2\x80\x94,并且该新提交没有历史记录

\n\n

毕竟,历史只是一连串的提交,从任何地方开始并向后推算。我们从K\xe2\x80\x94开始向后工作,好吧,没有其他地方可去。所以我们开始K并显示提交,我们就完成了。历史的终结!那里没有其他东西了。

\n\n

现在,当然,如果我们从Jor开始I并向后推算,就会有历史。但这与我们开始并向后追溯的历史无关Kfeature/tall孤儿分支也是如此。它只是一个与一切无关的分支。

\n\n

这个特殊的属性在一个新的、完全空的存储库中非常有用。这样的存储库没有提交,也没有分支,我们通过创建一些文件,将它们复制到我们最初为空的索引中来进行第一个提交\xe2\x80\x94,并且提交\xe2\x80\x94应该是第一个也是唯一的提交这个仍然新但现在不为空的存储库中。如果我们HEAD附加到分支名称master\xe2\x80\x94(当然是 \xe2\x80\x94),这将创建我们的第一个分支名称,master,指向第一个也是唯一的提交,我们可以调用它,A但它具有唯一的哈希 ID这是我们创建的文件内容的加密校验和,加上我们的姓名、电子邮件地址、我们输入的日志消息以及我们运行的时间git commit,所有这些加起来使这次提交在宇宙中独一无二。

\n\n

使用git checkout --orphan设置类似的条件,除了索引和工作树可能不为。对此孤儿分支进行第一次提交就是创建孤儿分支的原因。与往常一样,进入的快照是运行时索引中任何内容。日志消息是您输入的任何内容。新提交没有提交,这就是 Git 称其为孤儿提交的原因。git commit

\n\n

结论

\n\n

如果你想要一个孤儿提交,这就是你得到它的方式。但根据定义,它没有历史,因为历史父母的链条。如果你想要一个孤儿,你就得不到历史;如果你想要历史,你可能不会使用孤儿。

\n