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

yzckvree  于 2022-12-28  发布在  Git
关注(0)|答案(4)|浏览(163)

我有几个项目在不同的仓库,我想统一在同一个repo下不同的孤儿分支。为此,我创建了一个新的仓库,并开始在里面。
1.如何获取一个现有的回购协议,将其作为孤儿分支导入并保留历史记录?
1.有没有可能把两个孤立分支作为不同的文件夹打开?假设我有两个孤立分支,我想并行处理它们,有没有可能?我有两个以上的孤立分支,我想打开一个git UI,这样开发效率会更高。今天我 checkout 了每个仓库。在我把它们合并到同一个仓库下之后,我仍然需要并行处理所有分支。

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

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

41zrol4v1#

TL; DR

我有超过2个[分支],我想使用单个Git UI打开...
这是否可能,如果可能,如何实现,取决于用户界面。一般来说,询问Git不会得到答案。命令行答案是使用git worktree(Git 2.15或更高版本非常适合)。

1.如何获取一个现有的回购协议,将其作为孤儿分支导入并保留历史记录?
你不知道,真的。这个操作--和问题--可能没有意义,因为我怀疑你指的是 * Git * 所指的 * 孤儿分支 *。读到最后,看看它是否有意义。

到底什么是Git仓库?

一个Git * repository * 本质上由两个数据库组成。一个数据库只保存Git * objects *,其中最有趣的是一个名为 * commit * 的数据库,每个commit代表所有文件的一个完整快照。1另一个数据库保存 * names *-分支名称如master和标记名称如v2.1-这些名称是你,至少在Git最初,会 * 发现 * 有趣的提交。
每次提交--再次代表所有文件的"快照";提交 * 不 * 包含更改-由其哈希ID唯一标识。哈希ID是一个由字母和数字组成的庞大字符串,看起来很随机,但实际上是提交的整个内容的加密校验和:快照,加上元数据,告诉你是谁 * 制作了 * 快照(名字和电子邮件地址),什么时候(时间戳),为什么(日志消息),等等。因为每个提交都存储了它的前一个或 * 父 * 提交的实际哈希ID,所以Git很容易从 * 最后一个 * 提交开始向后处理:

... <-F <-G <-H   <-- master

因此,像master这样的 * 分支名称 * 只是保存分支中 * 最后一个 * 提交的哈希ID。历史本身就是一个提交链,从该提交开始(在本例中为H),然后从提交到父提交,一次一个提交。
这里有一个小问题,因为链不一定是线性的。在完成了一个类似上面的提交序列之后,我们可能还会有第二个提交序列:

G--H  <-- master
         /
...--E--F
         \
          I--J   <-- develop

这里,F和更早的提交都在 * 两个 * 分支上,而G-H * 只 * 在master上,I-J * 只 * 在develop上。如果我们把J * 合并 * 到master中,我们得到一个稍微特殊的提交:

G--H
         /    \
...--E--F      K  <-- master
         \    /
          I--J   <-- develop

虽然提交K像往常一样有一个简单的快照,但它现在有 * 两个 * 父提交,而不是一个,使它成为一个 * 合并提交 *.要查看来自K的历史记录,我们必须同时返回到提交H * 和 * J * 两者 *.从那里我们返回到G * 和 * I;从那里我们回到F,在那里历史重新收敛,在合并处已经发散。
换句话说,Git是逆向运行的:历史在合并时逻辑上是收敛的,因为Git是向后运行的,所以历史在合并时实际上是"发散"的。历史逻辑上是在你派生出第二个分支的地方发散的,但是在Git中它实际上是在那个地方"收敛"的,因为Git是向后运行的。
master这样的 * 分支名称 * 的特殊之处在于,它 * 总是指向我们希望指出的分支上的最后一次提交 *。这一点特别重要,因为您要询问的是 * 孤儿分支 *。
1其他三种对象类型是 * tree (树保存文件名)、 blob *(每个blob都是一个文件的内容)和 * annoted tag *(用于v2.1等标记)。Git使用commit + tree + blob组合来构造每个commit所代表的快照。

Git如何创建新提交:索引和工作树

1.是否可以将2个孤立分支打开为不同的文件夹?
如果你有Git 2.5或更高版本(由于Git 2.5初始实现中的一些bug,2.15或更高版本是个好主意),你可以使用git worktree同时处理两个不同的分支,在两个不同的 * work-tree * 中。现在是时候讨论Git的 * index * 和 * work-tree * 概念了,之后我们将讨论 * orphan branch * 的定义。
Git提交快照中的所有内容都将被永久冻结。任何提交的任何部分--包括日志消息、用户名、父哈希ID,以及 * 任何 * 保存为 * 该提交 * 一部分的 * 文件的任何部分--都无法更改。**任何现有提交(由某个现有哈希ID标识)的任何内容都无法更改。(它们也被压缩了,有时候压缩得非常厉害。如果你愿意,你可以把它们想象成是冻干的。)这对于归档来说非常棒:你可以在任何时候回到之前的任何一次提交,但是这对于完成任何新的工作都是无用的。
为了让你完成工作,Git给你提供了 * checkout * 提交的能力。 checkout 一个提交有三个作用:

1.第一个也是最明显的一个是,它对一个被冻结的提交进行了"再水合",将它的所有文件提取到某种工作区,在那里它们具有正常的、非冻结的、非Git化的形式。这个工作区通常就在仓库本身旁边,是你的 * work-tree *(或working tree,有时候是working directory,或类似拼写的变体)。
1.第二个,你想想也很明显,如果你使用git checkout mastergit checkout develop或其他什么,它会记住 * 你使用哪个分支名称从那个分支获得最新的提交 *。或者,如果你使用git checkout <hash-id>回到过去,它会记住哈希ID。我也是。

  1. git checkout在这里做的第三件事是填充Git的 * index
    把这个东西叫做 * the index * 是一个无用的名字--
    index * 到底传达了什么?--所以它还有两个名字:它有时被称为 * staging area *,有时被称为 * cache ,这取决于是谁或者是Git的哪个部分在调用它。这三个名字都代表同一个东西。在合并过程中,索引的作用和作用会变得有点复杂,但它的主要作用是保存提交中的所有文件,以Git化的形式, 准备 * 冻结,但与真正的提交不同 * 实际上没有冻结 *。
    这意味着索引保存了所有将要进入 * next * 提交的文件,换句话说,它是一个提议的下一次提交。
git checkout master

并且对于 * 曾经 * 在由名称master标识的提交中的每个文件,现在你拥有该文件的不是两个而是 * 三个 * 副本:

  • HEAD:file是保存在提交中的文件。它 * 不能 * 被更改:它是Git化的,冻结的,只读的,用git show HEAD:file可以看到它。
  • :file是存储在 * index * 中的文件。它 * 可以 * 被更改!它是Git化的,但是你可以在任何时候用一个新的副本 * 替换 * 它。使用git show :file来查看它。
  • file是存储在工作树中的文件。它是一个普通的文件,你可以对它做任何你想做的事情。使用普通(非Git)命令来查看或更改它,或者做任何你想做的事情。

如果你已经 * 修改了 * 某个文件,比如file,并且你希望Git在 * next * commit中保存这个 * new * 版本,你现在必须更新你的下一次提交:

git add file

这会将工作树文件 * 复制 * 到索引中,用工作树中的文件file的新Git化副本覆盖:file
因此,* 索引总是包含建议的下一次提交 *。您可以使用git add更新此建议。
注意,如果你git checkout了其他分支,你就用一个与你刚刚 checkout 的提交相匹配的不同的提议来替换下一个提交提议。参见Checkout another branch when there are uncommitted changes on the current branch。)这又意味着索引和工作树实际上是一对:当你对工作树进行修改时,通过改变周围的一些文件,你需要通过git add这些文件来更新你的索引。
当你运行git commit的时候,Git会做的是:

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

也就是说,如果您:

...--F--G--H   <-- master

您现在拥有:

...--F--G--H--I   <-- master
  • name * master现在记录了你刚刚提交的新提交的哈希ID I,这个新提交的 * 父提交 * 哈希ID是提交H,也就是你在提交这个新提交之前 checkout 的提交。

历史就是这样形成的!创建一个新的提交,Git刚刚从你运行git commit时索引中的任何内容创建了一个新的提交I。新提交的父提交是你让Git checkout 的提交。因为Git是从索引、索引和新的匹配中创建提交的,就像你第一次运行git checkout master来获得提交H一样,现在一切看起来都很好,你可以修改工作树中的东西,使用git add将它复制回索引中,并运行git commit以生成一个新的J,其父级为I,其保存的快照来自索引。

创建新分支

现在你已经知道了现有分支是如何工作的,让我们来看看创建一个 * new * 分支的过程。假设我们从你刚刚在master上创建的提交I开始:

...--F--G--H--I   <-- master

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

git checkout -b feature/short

我们现在看到的是:

...--F--G--H--I   <-- master, feature/short (HEAD)

也就是说,* both names *-masterfeature/short-标识现有提交I。Git用来记住 * 我们在哪个分支 * 的特殊名称HEAD附加在名称feature/short上。

现在我们将像往常一样处理工作树,像往常一样运行git add,然后运行git commit. Git将收集我们的名字、电子邮件、时间、日志信息等等,并使用我们索引中的快照和父节点I进行新的提交J.然后它将写入J的实际哈希ID,不管它是什么.转换为名称feature/short

...--F--G--H--I   <-- master
               \
                J   <-- feature/short (HEAD)

J开始的 history 返回到I,然后是H,以此类推,新的 commit 位于新分支feature/short的顶端,我们的索引现在匹配我们的提交J和我们的工作树,并且HEAD仍然连接到我们的分支feature/short
现在你已经知道了关于分支的一切--当然,除了“孤儿”分支,我们稍后会讲到它。

添加工作树

如果您一直在密切关注,您现在就会意识到“index”不仅索引工作树,而且它和工作树都与特殊名称HEAD有着密切的关系,我们使用git checkoutHEAD附加到某个分支名称,在此过程中,我们用一个特定的 commit 来填充我们的索引和工作树,这个 * commit * 位于分支的 tip 处,也就是名字所指向的commit,所有这些实体-HEAD,索引,工作树和分支名字-同时改变。
git worktree add所做的是创建一个 new triple-一个新的〈HEAD,index,work-tree〉组-并在这个新组中运行git checkout。新的工作树必须位于 * 计算机中的不同区域:* 不同的文件夹,如果你喜欢术语 folder 的话。新添加的工作树位于 * 不同的分支 *。所有的工作树必须位于不同的分支,即使这些分支 * name * 标识相同的提交!每个工作树都有 * 自己的 * index和HEAD,如果你从一个工作树切换到另一个工作树,你必须改变你对HEAD和索引的想法。
每个 commit 中的文件都是冻干的:Git化和压缩,没有用。解压缩到 work-tree 中的文件是再水合的,是有用的。所以添加 more work-tree的能力意味着你可以同时提交不同的提交,只要它们在不同的work-tree中。
(As在特殊情况下,任何工作树都可以有一个 detached HEAD,你可以在这里通过哈希ID提取一个特定的提交。因此,如果你需要查看16个不同的历史提交,你可以添加16个工作树,每个工作树都位于该历史提交的不同detached HEAD上。

孤立分支

现在我们已经把所有这些都解决了,我们可以--终于!--看看什么是“孤儿分支”。它比你想象的要少!
我们已经知道,HEAD通常被附加到某个已有的分支名称上,而已有的分支名称存储了一个提交的哈希ID,我们称之为该分支的 tip。当事情以这种方式设置时,创建一个 new 提交会更新 branch name,这样,已有的分支名称现在存储了我们刚刚创建的新提交的新的、唯一的提交哈希ID。
顺便提一下,HEAD可以存储commit的哈希ID,Git称之为 detached HEAD。这里HEAD没有 * 附加到分支名称,因此使用了“detached”一词。索引和工作树的工作方式与通常一样:索引保存了detached-HEAD提交哈希ID中的所有文件,这些文件都是冻干的形式,但实际上并没有冻结,工作树保存了该提交中的所有文件。你也可以用这种方法创建一个新的提交:如果你这样做了,Git只会把新提交的哈希ID存储到HEAD中。没有任何 * 分支 * name会记住这个哈希ID。只有HEAD保存着这个哈希ID。这些提交很容易被错误地丢失!如果你使用git checkout来移动你的HEAD,你已经丢失了你所做的新提交的哈希ID--所以至少要对分离的HEAD小心一点,以免你失去理智。:-)
不过,对于HEAD还有一种模式,Git允许你将HEAD附加到 * 一个不存在的分支名称 * 上,为此,你可以使用git checkout --orphan

git checkout --orphan feature/tall

这和git checkout -b很像,但是-b首先 * 创建分支名 *,然后 * 将HEAD附加到分支名上,正是 * 创建 * 分支名的过程在名称中存储了一个哈希ID!当我们创建上面的feature/short时,我们 * 创建了 * 指向现有提交I的名称,master已经记住相同的提交。
当我们使用git checkout --orphan时,Git * 不会创建分支名称 *。我们最终得到了这样一幅图:

...--F--G--H--I   <-- master
               \
                J   <-- feature/short

feature/tall (HEAD)

index* 和work-tree* 的内容保持不变,和以前一样,但是 namefeature/tall根本不作为 * 分支name* 存在,只是HEAD附加在它上面,因为它不作为branch name存在,所以它不指向任何现有的提交。

如果我们现在提交,Git会把索引的内容保存为一个新的快照;如果我们没有做任何修改,这些内容与提交J匹配,所以我们会得到一个新的提交K,新提交K的 * parent * 应该是我们现在 checkout 的提交-由我们的HEAD所连接的分支名称标识的分支。但是该分支不存在!
Git在这里所做的就是对你在一个新的、完全空的仓库中所做的第一次提交做同样的事情。Git只是在没有父提交的情况下进行提交。这样的提交被称为 * 根提交 *,我们可以这样画:

K

完成新的提交后,Git现在更新HEAD所连接的分支名称,即feature/tall,所以现在我们有:

...--F--G--H--I   <-- master
               \
                J   <-- feature/short

K   <-- feature/tall (HEAD)

新的分支feature/tall现在存在了,它的出现 * 是因为 * 我们做了一个新的提交--和往常一样,从索引中--而这个新的提交没有历史记录
历史,毕竟,* 就是 * 一系列的提交,从任何地方开始,向后工作。我们从K开始,向后工作--好吧,没有别的地方可去。所以我们从K开始,显示提交,我们就完成了。历史结束了!那里没有别的东西了。
当然,如果我们从JI开始,然后往回走,就会有历史记录,但是它与我们从K开始往回走的历史记录没有联系,所以feature/tall是一个孤儿分支,它只是一个与所有内容都无关的分支。
这个特性在一个新的、完全空的仓库中非常有用,这样的仓库没有提交和分支,我们第一次提交时--创建一些文件,把它们复制到我们初始为空的索引中,并且committing-应该是这个新的但现在不为空的仓库中的第一个也是唯一的提交。如果我们的HEAD被附加到分支名称master-当然是这样的--这 * 创建了 * 我们的第一个分支名称master,指向第一个也是唯一的提交,我们可以将其称为A,但它具有唯一的哈希ID,该哈希ID是我们创建的文件内容的加密校验和加上我们的姓名加上我们的电子邮件地址加上我们输入的日志消息加上我们运行git commit的确切 * 时间 *,所有这些加在一起使得这个承诺在宇宙中独一无二。
使用git checkout --orphan设置类似的条件,只是索引和工作树可能不是 * 空 *。为这个孤立分支进行第一次提交会创建孤立分支。进入的 * 快照 * 总是在运行git commit时 * 索引 * 中的内容。日志消息是您输入的内容。新提交没有 * 父项。这就是为什么Git叫它孤儿。

结论

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

czfnxgou

czfnxgou2#

您正在寻找“git worktree”。
1.(可选)创建空存储库;例如:

mkdir .repo/
git clone --bare .../project .repo/project.git

1.从此存储库中创建工作树

git -C .repo/project.git worktree add `pwd`/project-A branch-A
git -C .repo/project.git worktree add `pwd`/project-B branch-B

您可以跳过第1步,从现有的非裸存储库创建工作树,但这会简化操作,因为分支项目是长期存在的。

dw1jzc5e

dw1jzc5e3#

你可以删除所有的东西,提交它,然后从顶端开始,或者在它上面挑选一个孤立的分支。

git rm -rf .
git commit -m 'remove everything'
zmeyuzjn

zmeyuzjn4#

git工作树:但是从现有的源代码中使用一个孤儿分支怎么样?你能不能也把一些更多的信息放在命令上?
目前尚不支持,但正在积极讨论/实施中

git-worktree add添加孤立分支功能(如git checkout中所示)

添加支持在添加新的工作树时创建孤立分支。这个功能等同于git checkout的--orphan标志。
实现此功能的最初原因是允许用户仅使用面向工作树的工作流来初始化新的存储库。
使用示例如下所示。

$ GIT_DIR=".git" git init --bare
$ git worktree add --orphan master master/

它被列在最新的“What 's cooking in git.git”中,但还没有正式合并到master中。

相关问题