在同一目录中存储几个不同的 .git 分支?

Store several different .git branches in the same directory?

在“..Downloads/Training”文件夹中 Windows 上没有本地 .git 存储库,使用每个安装的 git bash,我先输入

git init

touch .gitignore 根据 youtube 教程(参见下面列出的 link)复制。git忽略先前由 visual studio 存储库创建的文件内容,还添加了 . git忽略 git add .gitignore ,还通过键入在“..Downloads/Training”中添加了唯一的子文件夹 M01 git add .,提交更改 git commit -am "First commit,通过键入添加遥控器 git remote add origin https:name_of_remote.com/my_repository_folder,创建了一个分支 git branch M01, 切换到分支 git switch M01,还通过键入推送了回购 git push origin HEAD:M01。 并且存储库已成功推送到远程但现在有一个问题: 我需要将“..Downloads/Training”中每个文件夹的内容存储在远程的单独分支中。

因此,如果我通过键入 git branch M02 创建一个新的本地文件夹 M02 和一个分支,然后通过 git switch M02 切换到它,它会显示我之前添加到 M01 中的所有内容M02 分支中的分支,但是如果我通过键入 git rm . -r 从 M02 中删除文件(它会删除本地文件),它还会从 M01 分支和 M02 分支中删除文件。

有没有办法在 M01 分支中只存储 M01 本地文件夹,在 M02 分支中存储 M02 本地文件夹?

补充源码教程links: (https://www.youtube.com/watch?v=g4BJXfmAevA)

对于您的情况,一个好的解决方案是从一开始就创建一个空的根提交。每当您想从头开始一个新分支时,只需从根提交创建它即可。

# create the 1st empty root commit
git init
git commit --allow-empty -m"root of all"

# tag it so that you don't have to use its sha1 value which is hard to memorize
git tag root

# create a new branch that tracks no files
git branch newbranch root

如果您现在创建一个空的根提交还不算晚。由于您的存储库不为空,我们需要其他方法来创建根提交。

# create an orphan branch foo from the current branch
git checkout --orphan foo

# remove all cached files and directories
git rm --cached -rf .

# create the empty root commit
git commit --allow-empty -m "root of all"

# tag it
git tag root

# remove the orphan branch foo
git branch -D foo

这是创建空根提交并标记它的第三种方法,

git tag root $(git commit-tree -m"root of all" $(git mktree < /dev/null))

你学到了一些错误的东西。注意:我没有看过您链接的特定 youtube 教程,所以我不确定它是好是坏还是无关紧要。这一段完全基于您在问题中所写的内容。

首先,让我们了解什么是 Git 存储库,什么不是:

  • 一个Git存储库一个提交集合。这些提交可以使用 b运行ch 名称、标签名称和其他名称找到,但它不是 of b运行ches 的集合。它是 提交 的集合。一个松散的类比可能是昆虫的集合,您在其中标记昆虫:这使得集合 contain 标签,但它不是 of 标签的集合.也就是说,标签不是收集的目的。

  • 真正的存储库存储在 .git 目录(或文件夹,如果您喜欢该术语)。这个文件夹的存在,以及 Git 需要的特定文件和 sub-folders,告诉 Git 这个 一个存储库。如果 .git 目录本身不存在,或者缺少这些重要文件 and/or sub-folders,Git 会说这不是 Git 存储库。

  • A bare 存储库以 .git 文件夹开始和结束(然后通常将其命名为 repo.git 或类似的名称,而不是不仅仅是 .git)。从技术上讲,裸存储库仍然有一个索引 / staging-area,但这只是因为 Git 对 的实现 索引 / staging-area 主要是一个文件在 .git 目录中,名为 index。 (此文件不必存在:Git 将在需要时创建它。)

  • A non-bare 存储库,这是您通常使用的那种,也有一个 工作树。工作树是您查看和处理文件的地方。这些文件是您计算机上的普通日常文件,按照计算机喜欢的文件存储方式存储在文件夹中。重要的是要了解这些工作树文件 不在 Git 存储库 .

  • 存储库——.git 文件夹——存储提交和其他内部 Git 对象。它们采用 对象数据库 的形式,其中 Git 通过哈希 ID 查找对象:大的 random-looking 数字,以 hexadecimal 表示.所有这些对象实际上都是 read-only:一旦存储在数据库中,任何对象都不能更改。这意味着不能更改任何提交或文件。

  • 存储库还存储一个单独的名称数据库:b运行ch 名称、标签名称、remote-tracking 名称和其他名称。这些存储的名称中的每一个都恰好拥有一个哈希 ID。对于 b运行ch 名称,存储的哈希 ID 始终是提交的哈希 ID。 (允许标签名称存储其他 internal-Git-object 哈希 ID,您通常不会直接与之交互。)这是查找特定提交的好(且快)方法。

查找、提取、使用和创建新提交是我们对 Git 存储库所做的大部分工作。由于存储库是如此 commit-centric— 而我们使用 存储库是 definitely commit-centric— 了解这一点很重要提交是什么和做什么, Git 如何提取一个:

  • 每次提交都会存储所有文件的完整快照。提交中的文件 而不是 普通计算机文件格式。1 因为它们存储为 Git 对象,所以它们是所有 read-only。甚至 Git 也无法改变它们。而且,因为提交本身是一个 Git 对象,它有一个丑陋的大哈希 ID,因此 Git 可以在对象数据库中查找它。

  • 每个提交还存储一些 元数据: 关于提交本身的信息,例如提交人和时间。在此元数据中,Git 存储 先前 提交的哈希 ID。更准确地说,most 提交存储 one 先前提交的哈希 ID,但此规则有一些例外情况。

因为每次提交都是read-only,但是你通常需要读取和写入你的文件,你会告诉Git到extract 一些提交。当您这样做时,Git 将复制提交的文件 out。为此,Git 将从内部存储的文件(带有 internally-stored 名称)中读取 internal-only、read-only 数据并将其转换为普通的 file-in-folder 您的计算机使用的设置。这些文件将进入您的工作树

换句话说,当您 运行 git checkoutgit switch 时,Git 执行以下 two-step序列:

  • 首先,Git 从您的工作目录中删除所有(跟踪的)文件.
  • 然后,Git 将工作树中的所有文件替换为您刚刚签出的提交中的文件。

这就是为什么每次提交都会存储每个文件:因为从一个提交切换到另一个提交会删除您留下的提交中的所有文件,然后从您移动到的提交中提取所有文件。 提交本身是完整的read-only,所以在这个过程中没有提交更改,也没有文件丢失。只有你的工作树是清空 re-filled.

请注意,这里讨论的是从一个 commit 切换到另一个。从一个 b运行ch 切换到另一个并不一定会切换 commits。为了正确理解这一点,我们应该画出提交的图片。


1它们以特殊的形式存储,read-only,Git-only,它们被压缩并且对 Git 很重要' s 内部操作—de-duplicated。这个 de-duplication 步骤允许每个提交存储 每个 文件,而不需要任何额外的 space。事实上,许多不同的文件——加上其他内部 Git 对象——可能存储在单个计算机文件中(以一些丑陋的大哈希 ID 后跟 .pack 命名),尽管有时文件内容存储为Git 呼叫 松散物体 。不过,无论哪种方式,这些文件也没有普通文件名。


在 b运行ches

中绘制提交图片

每个提交都有一些又大又丑的 random-looking 哈希 ID。让我们使用单个大写字母代替哈希 ID,而不是使用它们。而且,大多数提交都包含一些 previous 提交的哈希 ID。与其使用它,不如画一个箭头,从后面的提交 指向前面的提交 。我们将从哈希 ID 为 H:

的一些提交开始
            <-H

提交 H 向后指向,我们将调用提交 G:

        <-G <-H

G 当然是指向另一个,still-earlier 提交,所以我们要继续:

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

最终我们会有一个提交链,一直指向有史以来的第一个提交。让我们在这里调用它 A,并绘制所有提交——但我现在会有点懒,使用线连接它们,即使箭头实际上只指向 向后. Git 无法进入并调整较早的提交以指向前方,因为较早的提交一旦完成就会一直冻结。所以我们有:

A--B--C--D--E--F--G--H

在这里,提交 H 是链中的 最后一个 提交。从 H 我们——或 Git——可以一路回到链中的 first 提交,提交 A;在那里,一切都停止了,因为没有更早的提交。

为了快速找到提交 H ,我们给它一个 b运行ch name,比如 mainmaster.2 为了表示这一点,让我们输入名称,其中有一个箭头指向提交 H:

...--G--H   <-- main

如果现在创建一个newname,比如M01,那么name[=默认情况下,324=] select 与 相同的提交 。我们现在有两个提交名称 H:

...--G--H   <-- main, M01

您自己的 Git 软件,在您自己的存储库中运行——简称“您的 Git”——只能在这两个 b 中的 一个 上运行切。为了表示您在哪一个,让我们将特殊名称 HEAD 附加到这两个 b运行ch 名称之一:

...--G--H   <-- main (HEAD), M01

如果我们现在运行:

git switch M01

Git 会将 HEAD 移动到名称 M01:

...--G--H   <-- main, M01 (HEAD)

我们仍在使用 commit H,因为两个名称 select 相同的 commit。您的工作树中的文件集将保持完全相同,因为我们没有更改 commits.

现在假设我们从工作树中删除一堆文件——可能是整个 folder-full 个文件,并创建一些新文件,然后 运行 git add . 添加removing-and-creating 然后 运行 git commit。当我们进行此 new 提交时,Git 将保存 所有 Git 中的文件 [=212] =]当时的集结区git add . 更新了暂存区以匹配我们的工作树。3 这将创建一个 新提交 ,这将得到一些 random-looking 哈希 ID,但我们将其称为“commit I”。让我们把它画进去:

...--G--H
         \
          I

提交 I 作为其父项具有较早的提交 H。那是因为我们使用 commit Hmake commit I;我们过去是,现在仍然是 on branch M01,正如 git status 会在这里说的那样,就在刚才——在我们 运行 git commit 之前——M01 指向的名字提交 H。但是现在提交 I 存在,git commit 命令将 I 的哈希 ID 写入current b运行ch name,所以我们有这个:

...--G--H   <-- main
         \
          I   <-- M01 (HEAD)

提交 I 包含我们告诉 Git 它应该有的文件; b运行ch name M01 selects commit I; HEAD 告诉我们我们在 b运行ch M01。如果我们现在 运行:

git switch main

Git 将 删除 提交 I 的文件, 提取 提交的文件提交 H。我们的工作树现在将匹配提交 H 并且我们将有:

...--G--H   <-- main (HEAD)
         \
          I   <-- M01

作为我们的提交图片。


2你自己的 Git 可能默认为 master 而 GitHub 现在默认为 main,这会产生一些稍后发布。

3我跳过了一些关于 .gitignore 文件、跟踪文件、未跟踪文件以及索引如何工作的重要细节,以便专注于commits-and-branches.


你弄错了什么

So if I create a new local folder M02 and a branch by typing git branch M02, switching to it by git switch M02, It shows me all of the contents that I have previously added into the M01 branch in the M02 branch ...

新的 b运行ch 和旧的 b运行ch 当前 共享相同的最终提交

当您查看 工作树 时,您看到的只是您的 工作树文件 。它们与提交的文件不同,尽管如果 git status 表示所有内容都匹配,它们会 匹配 提交的文件。提交的文件永远不会——不能——改变。

请注意 Git 仅存储 个文件M01 b运行ch 末尾的提交中的文件可能具有类似 M01/somefile 的名称。那是文件的实际名称:M01/somefile。它不是一个名为 M01 文件夹,其中包含一个名为 somefile 文件。您的 工作树 具有此设置 — 文件夹中包含文件 — 在您的工作树中,斜线甚至可能是相反的,M01\somefile。但是,在 commit 中,它只是一个名为 M01/somefile 的文件。这一点通常并不重要,但它意味着 Git 字面上不能存储空文件夹(因为空文件夹不包含文件,而 Git 只能存储 个文件).4

but If a remove the files from M02 by typing git rm . -r (it deletes local files), it also deletes the files from both M01 branch and M02 branch.

git rm -r . 操作会清除 Git 的索引/staging-area 和您的工作树。 现有的提交不会——也不能——改变。您现在所做的 future 提交将不包含任何文件(因为您删除了所有文件);当您放回一些文件并 git add 它们时,未来的提交将包含这些文件。

Git 的索引 / staging-area 因此充当您的 提议的未来提交 。您在工作树中创建一个文件,然后 运行 git add 将其复制到 Git 的暂存区。您 git checkoutgit switch 对某些现有提交告诉 Git: 删除所有当前提交的文件并交换来自其他提交的文件 。如果您已提交所有内容,这将是完全安全的;如果您有未提交的工作,安全,git checkoutgit switch通常会检测这些not-safe情况并避免破坏未提交的工作。5


4Git子模块有一些技巧可以在这里使用,但我们不深入讨论这些技巧。

5请注意 git checkout 有一种“危险”的操作模式,会破坏未保存的工作。新的 git switch 命令实现了 git checkout 的安全部分,而新的 git restore 命令实现了 git checkout 的不安全部分。所以学习新的 git switchgit restore 可能会更好,这样你就可以随时知道你是否正在 运行 执行 safety-checking 命令。

出于各种原因,这两个“安全”命令 有时 允许您切换 b运行 甚至是未保存的工作。当他们让你切换,但你忘记先保存(add-and/or-commit),你可以切换回去。当一个人进入完整的细节时,这会变得相当复杂。如果你真的想知道,请参阅Checkout another branch when there are uncommitted changes on the current branch


更多细节

向您展示了如何创建多个 root commit。根提交是类似于我们上面的提交 A 的提交:新的空存储库中的第一个提交,没有父提交。显然,第一个提交 不能 有一个先前的提交,所以 Git 必须能够创建一个根提交。使用 git checkout --orphangit switch --orphan 是创建根提交的另一种方法。

checkout 和 switch 之间有一个很小但很重要的区别:

git checkout --orphan newbranch

留下 Git 的索引 / staging-area 充满文件(并且根本不触及您的工作树),但是:

git switch --orphan newbranch

清空 Git 的索引/staging-area(结果,从您的工作树中删除任何相应的跟踪文件)。如果您打算删除所有跟踪的文件,使用 git switch --orphan 可以省去这一步。