0

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

git init

touch .gitignore根据 youtube 教程(请参阅下面列出的链接),从以前由 Visual Studio 存储库创建的 .gitignore 文件内容复制,还添加了 .gitignore 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,还通过键入推送 repo git push origin HEAD:M01。并且存储库已成功推送到该远程,但现在有一个问题:我需要将每个文件夹的内容存储在远程的单独分支中的“..Downloads/Training”中。

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

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

附加源教程链接:(https://www.youtube.com/watch?v=g4BJXfmAevA

4

2 回答 2

2

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

首先,让我们来看看 Git 存储库是什么,不是什么:

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

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

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

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

  • 存储库(.git文件夹)存储提交和其他内部 Git 对象。它们采用对象数据库的形式,Git 在其中通过哈希 ID 查找对象:大的、随机的数字,以十六进制表示。所有这些对象实际上都是只读的:一旦存储在数据库中,就无法更改任何对象。这意味着不能更改任何提交或文件。

  • 存储库还存储一个单独的名称数据库:分支名称、标签名称、远程跟踪名称和其他名称。这些存储名称中的每一个都只包含一个哈希 ID。对于分支名称,存储的哈希 ID 始终是提交的哈希 ID。(允许标签名称存储其他内部 Git 对象哈希 ID,通常不会直接与之交互。)这是查找特定提交的好(且快速)方法。

查找、提取、使用和创建新提交是我们对 Git 存储库所做的大部分工作。由于存储库是如此以提交为中心——而我们对存储库所做事情绝对是以提交为中心——重要的是要知道提交是什么和做什么,以及Git 如何提取一个:

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

  • 每个提交还存储一些元数据:关于提交本身的信息,例如提交者和提交时间。在此元数据中,Git 存储先前提交的哈希 ID。更准确地说,大多数提交都存储了前一次提交的哈希 ID ,但这条规则也有一些例外。

因为每个提交都是只读的,但您通常需要读取和写入文件,您将告诉 Git提取一些提交。当你这样做时,Git 会将文件提交中复制出来。为此,Git 将从内部存储的文件(及其内部存储的名称)中读取内部只读数据,并将其转换为您的计算机使用的普通文件夹中的文件设置。这些文件将进入您的工作树

换句话说,当您运行git checkoutor时git switch,Git 会执行以下两步序列的优化(和安全检查)变体:

  • 首先,Git从工作树中删除所有(跟踪的)文件
  • Then, Git replaces all the files in your working tree with those from the commit you just checked out.

This is why each commit stores every file: because switching from one commit to another will remove all the files from the commit you're leaving behind, and extract, instead, all the files from the commit you're moving to. The commits themselves are completely read-only, so no commit changes in this process, and no files are lost. Only your working tree is emptied and re-filled.

请注意,这是关于从一个提交切换到另一个提交。从一个分支切换到另一个分支不一定会切换提交。为了正确理解这一点,我们应该画出提交的图片。


1它们以一种特殊的、只读的、仅限 Git 的形式存储,在其中它们被压缩并且——对于 Git 的内部操作很重要——<em>去重。此重复数据删除步骤允许每个提交存储每个文件,而不会占用任何额外空间。事实上,许多不同的文件——以及其他内部 Git 对象——可能存储在单个计算机文件中(以一些大而丑陋的哈希 ID 命名,后跟.pack),尽管有时文件内容存储为 Git 所谓的松散对象。但是,无论哪种方式,这些文件也没有普通的文件名。


在分支中绘制提交的图片

每个提交都有一些大的、丑陋的、看起来随机的哈希 ID。我们不使用这些,而是​​使用单个大写字母来代替散列 ID。而且,大多数提交都包含一些先前提交的哈希 ID。让我们画一个箭头,而不是使用它,从后面的提交指向一个提交。我们将从一些哈希 ID 为的提交开始H

            <-H

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

        <-G <-H

G当然指向另一个更早的提交,所以我们必须继续:

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

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

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

在这里,commitH是链中的最后一个提交。H我们——或 Git——可以一直工作到链中的第一个提交, commit A;在那里,一切都停止了,因为没有更早的提交。

H 为了快速找到提交,我们给它一个分支名称,比如mainor master2 为了表示这一点,让我们输入名称,其中有一个箭头指向 commit H

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

如果您现在创建一个 名称,例如M01,该名称默认选择相同的提交。我们现在有两个 commit 名称H

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

你自己的 Git 软件,在你自己的存储库中工作——简称“你的 Git”——只能在这两个分支之一上。为了表示你在哪个分支上,让我们将特殊名称附加HEAD到这两个分支名称之一:

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

如果我们现在运行:

git switch M01

Git 将移至HEAD名称M01

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

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

现在假设我们从工作树中删除一堆文件——可能是整个文件夹中的文件——并创建一些新文件,然后运行git add .添加删除和创建,然后运行git commit​​. 当我们进行这个新的提交时,Git 将保存当时在 Git暂存区中的所有文件。更新了暂存区以匹配我们的工作树。3 这将创建一个新的 commit,它将获得一些看起来随机的哈希 ID,但我们将称之为“commit ”。让我们把它画进去:git add .I

...--G--H
         \
          I

CommitI作为其父级,具有较早的 commit H。那是因为我们使用commitH来提交;我们曾经是,现在仍然是,就像这里所说的那样,就在刚才——在我们跑之前——这个名字指向了 commit 。但是现在提交存在,该命令将's hash ID 写入当前分支 name,所以我们拥有的是:Ion branch M01git statusgit commitM01HIgit commitI

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

提交I包含我们告诉 Git 它应该有的文件;分支名称M01选择提交I;并HEAD告诉我们我们在分支上M01。如果我们现在运行:

git switch main

Git 将删除与 commit 一起使用的文件I提取与 commit一起使用的文件H。我们的工作树现在将匹配提交H,我们将拥有:

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

作为我们的提交图片。


2您自己的 Git 可能默认为,master而 GitHub 现在默认为main,这会在以后产生一些问题。

3我在这里跳过了一些关于.gitignore文件、跟踪文件、未跟踪文件以及索引如何工作的重要细节,以便专注于提交和分支。


你做错了什么

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

新分支和旧分支当前共享相同的最终提交

当您查看您的工作树时,您看到的只是您的工作树文件。它们与提交的文件不同,尽管如果git status说一切都匹配,它们确实匹配提交的文件。提交的文件永远不会——<em>不能——改变。

请注意,Git 只存储文件。分支末尾的提交中的文件M01可能具有类似M01/somefile. 这是文件的实际名称:M01/somefile. 它不是一个名为的文件M01,其中包含一个名为. 你的工作树有这样的设置——里面有文件的文件夹——在你的工作树中,斜线甚至可能是另一种方式,. 但是,在commit中,它只是一个名为. 这一点通常并不重要,但这意味着 Git 根本无法存储空文件夹(因为空文件夹不包含文件,而 Git 只能存储文件)。4somefileM01\somefileM01/somefile

但是如果通过键入从 M02 中git rm . -r删除文件(它会删除本地文件),它也会从 M01 分支和 M02 分支中删除文件。

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

Git 的 index / staging-area 因此充当您提议的未来提交。您在工作树中创建一个文件并运行git add以将其复制到 Git 的暂存区。你git checkoutgit switch某个现有的提交告诉 Git:删除所有当前提交的文件并从另一个提交中交换文件。如果您已经提交了所有内容,这是完全安全的;如果你有未提交的工作是不安全的,或者通常git checkoutgit switch检测到这些不安全的情况并避免破坏未提交的工作。5


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

5请注意,git checkout具有“危险”操作模式会破坏未保存的工作。新git switch命令实现了 的安全部分git checkout,而新git restore命令实现了 的不安全部分git checkout。因此,学习新的git switchand可能会更好git restore,这样您就始终知道自己是否正在运行安全检查命令。

由于各种原因,这两个“安全”命令有时都会让您切换分支,即使未保存的工作也是如此。当他们您切换,但您忘记先保存(添加和/或提交)时,您可以切换回来。当一个人进入完整的细节时,这变得相当复杂。如果您真的想知道,请参阅在当前分支上有未提交的更改时签出另一个分支。


一些进一步的细节

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

这里的 checkout 和 switch 之间有一个很小但很关键的区别:

git checkout --orphan newbranch

让 Git 的索引/暂存区域充满文件(并且根本不会触及您的工作树),但是:

git switch --orphan newbranch

清空 Git 的 index / staging-area(因此,从工作树中删除任何相应的跟踪文件)。如果您打算删除所有跟踪的文件,请使用git switch --orphan此步骤。

于 2021-08-24T05:32:01.253 回答
1

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

# 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))
于 2021-08-24T03:40:43.283 回答