在 Git 上,我目前在分支上有一些暂存但未提交的更改master
。
我不想提交到 master 分支,而是想
development
;然后reset
/清除 上的分阶段更改master
;然后development
到master
远程,并保留development
分支;然后master
从远程刷新本地master
,而不更改本地现有的未提交文件请问我应该怎么做呢?我仍然是 git 的初学者,所以请分步骤解释一下,以便我可以遵循。
add
注1:我的分阶段更改包含 100 多个文件,因此手动将它们逐一手动挑选到新分支会很痛苦。如果可能的话,我试图避免这种容易出错的方式。
注2:有超过30个文件我没有暂存更改。即使从远程刷新后,我也想在本地保留这些更改master
。
更改不是“在分支上”进行的。事实上,Git根本没有变化:Git 只有快照。
\n这是什么意思?嗯,这意味着你的问题的简短答案是:
\ngit branch
); 然后git switch
或git checkout
);然后git commit
)。您可以将前两个步骤与git switch -c development
或结合起来git checkout -b development
。这两个命令执行相同的操作:git switch
是 Git 2.23 中的新命令,作为将重载git checkout
命令拆分为两个单独命令的项目的一部分;旧的git checkout
仍然保留在 Git 中,并且可能会在接下来的 20 年中保留,但慢慢迁移到新的是个好主意。
重要的是要认识到这个过程\xe2\x80\x94git switch -c development
特别是\xe2\x80\x94 使用了快捷方式。它不适用于某些其他情况,但适用于这种情况。
不过,这确实值得更长的解释。 为什么上面的方法有效?您需要了解的内容从以下内容开始:
\nGit 的新手常常认为 Git 是关于文件的,这很自然:我们在 Git 中存储文件。或者,他们可能认为 Git 是关于分支的,这也很自然:我们总是“在”某个分支\xe2\x80\x94 上,如所说的git status
或on branch master
其他什么。从技术上讲,您也可以不位于任何分支上,即 Git 所谓的“分离 HEAD 模式”,但除了某些特殊情况外,您通常不希望以这种方式工作。
问题是,这些观点都不正确。归根结底,Git 的核心就是提交。确实,每个提交都存储文件,并且我们确实将提交形成分支,我们还发现有branchs\xe2\x80\x94或更准确地说,有分支名称,我们(相当草率地/)懒惰地)调用分支,即使我们也将其他事物称为分支。但最终,Git 是关于提交的。
\n(注意:如果当我们使用分支来查找分支时您感觉到有什么问题,那么您就在正确的轨道上:这个概念有问题。事实上,这个词分支定义错误。它(ab)用于多种用途。)
\n存储库的核心是提交的集合,以及我们稍后将讨论的其他一些内容。这些提交是四种 Git对象之一,Git 将所有这些对象存储在一个大型键值数据库(对象数据库)中,该数据库使用哈希 ID(或更正式的对象 ID或 OID)作为键。Git 迫切需要这些对象 ID 才能在数据库中查找提交。
\n这些哈希 ID 又大又丑,而且看起来很随机。例如,9bf691b78cf906751e65d65ba0c6ffdcd9a5a12c
是 Git 本身的 Git 存储库的任何克隆中的特定提交。 每个提交都有一个唯一的哈希 ID:宇宙中任何地方的所有 Git 软件都同意该事物9bf691...blahblah
意味着提交,即使这个特定的存储库从未有过该提交并且永远不会获得它。每次提交时,Git 都会生成一个新的唯一哈希 ID。1这意味着查找提交 所需的只是哈希 ID\xe2\x80\x94,但同样,Git 确实需要该哈希 ID,以便它可以在其对象数据库中查找。要么它有该对象,所以它有提交,要么没有。如果您的 Git 存储库缺少提交,您将需要从某个拥有该提交的存储库获取提交。我们将省略细节,但这就是重点。git fetch
不管怎样,考虑到提交如此重要,您需要确切地知道提交是什么以及它对您有什么作用。因此,除了看起来奇怪的随机“数字”(哈希 ID)之外,您还需要了解以下内容:
\n每次提交都是只读的。编号系统需要这样做。
\n每次提交都包含(间接)每个文件。更准确地说,提交具有每个文件(它所拥有的)的完整快照,作为一种存档:源文件的 tarball 或 zip 文件或 WinRAR 或其他文件。Git 非常巧妙地存储这些\xe2\x80\x94,包括对内容进行重复删除\xe2\x80\x94,以便内容在提交之间甚至在提交内共享,因此即使每个提交都有每个文件,但大多数提交确实很小。在新存储库中的第一个存储库不是,因为该存储库必须第一次存储所有文件,但此后,大多数提交大多会重复使用大多数文件,因此那些重复使用的文件不需要空间。
\n除了快照之外,每个提交还包含一些元数据或有关提交本身的信息。例如,这包括提交人的姓名和电子邮件地址。
\n除了您的姓名和电子邮件地址(Git 从您的设置中获取user.name
)之外user.email
,Git 主要自行构建所有元数据。你只需运行git commit
,Git 就会生成一个快照\xe2\x80\x94,我们很快就会看到“来自何处”\xe2\x80\x94,并添加元数据。对于 Git 来说,任何一次提交中最重要的元数据之一是先前提交哈希 ID 的列表。大多数提交在此列表中只有一个条目:我们将这些提交称为“普通”提交。
这个单个先前提交哈希 ID 存储在普通提交的元数据中,使提交“指向”其父级。也就是说,提交会记住哪个提交出现在该特定提交之前。如果我们喜欢\xe2\x80\x94并且我确实喜欢\xe2\x80\x94,我们可以画这个,将较新的提交放在右侧,将较旧的提交放在左侧,如下所示:
\n... <-F <-G <-H\n
Run Code Online (Sandbox Code Playgroud)\n这里H
代表链中最后一次提交的“h”ash ID。CommitH
有所有文件的完整快照,加上一些元数据,commit中的H
元数据包含早期 commit 的哈希 ID G
。SoH
指向 G
,如向后伸出 o 的小箭头所示H
。
这意味着如果我们能够获取 Git 提交的哈希 ID H
,Git 就可以使用 commitH
来查找更早的提交G
。当然,G
它也是一个普通的提交,因此它有一个伸出的箭头,向后指向其父级F
。Git 现在可以找到 commit F
。只要这也是一个普通的提交,它就向后指向另一个提交。
这样,Git就可以找到链中的每一次提交,只要Git能找到链中的最后一次提交即可。我们所要做的就是记住上次提交的哈希 ID。当然,记忆9bf691b78-ugh-glah-whatever
是非常痛苦的,所以 Git 为我们提供了一种避免这种情况的方法。
1我们可以用数学证明这个想法注定会失败。然而,哈希 ID 空间的巨大规模将故障日期置于足够遥远的未来,\xe2\x80\x94我们希望\xe2\x80\x94我们永远不必关心。
\n为了避免记住提交的哈希 ID H
,我们只需告诉 Git 我们想要一个分支名称,例如master
。Git 将最后一个哈希 ID 粘贴到名称中:
...--G--H <-- master\n
Run Code Online (Sandbox Code Playgroud)\n该名称现在指向最后一次提交,Git 可以从中找到每个较早的提交。这就是它的全部内容\xe2\x80\x94 好吧,几乎全部。
\n正如我之前提到的,Git 喜欢我们在分支“上”。位于某个分支上意味着特殊名称HEAD
附加到分支名称上:这就是 Git 如何知道我们实际使用的是众多分支名称中的哪一个。
现在让我们添加一个新的分支名称,并使其也指向 commit H
,如下所示:
...--G--H <-- development, master (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n这意味着我们已经“开启”了master
。两个名称都选择 commit H
,而 commit是我们现在正在使用的H
提交,但我们正在使用名称来执行此操作。master
如果我们git switch development
现在运行,我们会得到:
...--G--H <-- development (HEAD), master\n
Run Code Online (Sandbox Code Playgroud)\n我们仍在使用 commit ,但现在我们通过 name 来H
这样做。当我们创建新的提交时,这一点很重要。因为我们正在使用 commit ,所以我们的新提交将向后指向,但该新提交的哈希 ID将保存在当前分支名称中,如下所示:development
H
H
...--G--H <-- master\n \\\n I <-- development (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n如果我们现在进行另一个新提交,这个新提交J
将向后指向I
\xe2\x80\x94 因为我们现在正在提交I
,通过名称\xe2\x80\x94 并且 Git 将再次development
更新名称:development
...--G--H <-- master\n \\\n I--J <-- development (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n这就是 Git 中分支的生长方式。我们是否应该切换回master
现在:
...--G--H <-- master (HEAD)\n \\\n I--J <-- development\n
Run Code Online (Sandbox Code Playgroud)\nGit 将删除development
(即 commit- )文件J
,并放回所有 commit-H
文件。
我(简要地)提到 Git 的提交是只读的,文件以一种归档方式存储在提交中,并进行压缩和重复数据删除。这对我们来说意味着我们实际上无法读取这些文件\xe2\x80\x94,只有 Git 可以读取它们\xe2\x80\x94,并且实际上没有任何东西,甚至 Git 本身,可以覆盖它们。这对于归档\xe2\x80\x94 来说非常有用,这就是提交正在做的事情,至少在第一级\xe2\x80\x94 但对于完成任何新工作毫无用处。
\n为了完成工作,Git 必须在提交中取消归档文件。git switch
当我们使用或进行切换时,Git 将从我们要移动到的提交中git checkout
提取所有文件。当然,首先,Git 必须删除我们要删除的所有文件。然后 Git 将所有文件提取到您可以使用的地方。那是你的工作树或工作树。您现在可以完成工作了!
Git 的重复数据删除技巧在这里发挥了作用。删除和替换文件有点慢,因此 Git在从一个提交切换到另一个提交之前检查哪些文件是重复的。对于这些文件,Git 根本不需要执行任何操作\xe2\x80\x94,然后就不会了。而且,如果我们从 commit 切换H
到 commit H
,这意味着每个文件都是重复的,因此 Git 不必删除和替换任何文件。
这就是为什么创建一个新的分支名称,然后切换到它在这里是安全的。无需返工文件;根本不需要碰任何文件。所以 Git 不会触及任何文件,一切都很好。
\n不过,关于这一点还有更多要说的。通常,Git确实必须填写工作树。例如,考虑一下这样的情况:您刚刚克隆了一个存储库,Git 正在第一次填充您的工作树。您可能会想:啊,好吧,Git 只是提取所有文件。 例如,这就是其他版本控制系统所做的事情。这就足够了。但这不是 Git 所做的。
\n相反,Git 有一种文件跟踪系统,Git 通过三个名称来调用该系统:索引、暂存区域,有时(现在主要是像这样的标志git rm --cached
)缓存。这三个名字都指同一件事。
当 Git 提取提交时,Git 会使用该提交中的文件填充其索引和工作树。索引中的副本(或者可能是“副本”)是预先去重复的,以 Git\ 的内部只读格式存储,但与 commit\xe2\x80\x94 中的副本不同,它会被冻结提交本身继续存在\xe2\x80\x94索引副本可以批量替换。由于初始副本(或“副本”)是提交中任何内容的重复项\xe2\x80\x94,因此\xe2\x80\x94it\会自动删除重复项,几乎什么都没有。(索引条目本身仍然需要一些空间来保存文件名和一堆缓存数据。)
\n同样,该索引是“暂存区域”:这是同一事物的两个名称。当您修改文件的工作树副本时,索引副本 \xe2\x80\x94 不会发生任何变化!它只是坐在那里,仍然保留着提交中的去重副本。
\n但是,当您运行时git add
,Git 会读取文件的工作树副本,将其压缩为 Git 在提交中使用的内部格式,并检查重复项。如果压缩文件是重复的,Git 会丢弃它刚刚构建的压缩副本,并使用现有的压缩副本。否则,它会保存刚刚制作的压缩副本。无论哪种情况,Git 现在都会将新的或重复使用的副本/“副本”交换到索引中。2
这一切的结果很简单:
\ngit add
,索引保存每个文件的副本(预先去重),准备提交;git add
,索引仍然保存每个文件的副本(预先去重),准备提交。这意味着索引始终保存每个文件的副本,准备提交。 实际上,索引保存了您建议的下一次提交的快照。
\n当您运行时git commit
,Git 只是以当时的形式打包索引中的所有文件,以便在新快照中使用。这将是新提交的存档。Git 还会在此时收集或生成必要的元数据\xe2\x80\x94,使用当前提交哈希 ID作为父项\xe2\x80\x94,并将所有这些写出以获得新提交的随机哈希 ID,然后git commit
将新的提交存储到数据库中,并将其 ID 填充到当前分支名称中。
这一切确实相对简单。那么变化从哪里来呢?
\n2从技术上讲,索引条目保存文件名、一些缓存数据以及全对象数据库中对象的Blob 哈希 ID 。你真的不需要担心这个。如果您愿意,您可以将索引视为包含完整副本。
\ngit show
如果您在普通提交上运行,Git 将:
Git此时会计算这个差异!Git 使用该普通提交中的元数据来查找该提交的父级。然后,Git 提取两个快照(实际上是在内存中的临时区域)并比较删除重复的文件。由于现在很难发现重复项,因此 Git 实际上只需要比较不同的文件。对于每个这样的文件,Git 都会计算一组更改,如果将这些更改应用于该文件的父级副本,则会生成该文件的子级副本。
\n这就是您看到的差异:git diff
通过将父母与孩子进行比较而得出的差异。(该git show
命令调用与此相同的内部代码git diff
。它只是git show
自动为您找到父级。如果您想使用git diff
这种方式,则必须选择两个提交。必须选择两个提交的好处是你可以选择任何一对提交。)
当你运行时git status
,Git:
On branch master
例如);git diff
快照进行比较;和git diff
。第一次比较\xe2\x80\x94当前提交与索引/暂存区域\xe2\x80\x94中建议的下一个快照之间发生了什么变化,可以快速跳过已删除重复的相同文件,并仅比较不同的文件。由于它不会发出一组实际的更改,因此它会短路代码\xe2\x80\x94您可以使用git diff --name-status
\xe2\x80\x94自己执行此操作,并仅显示某些文件已更改。
此处显示为已更改的任何文件都列在为 commit 暂存的更改下。新文件或删除的文件在这里以相同的方式显示。(Git 也在这里进行重命名检测;我们不会正确介绍这一点。)
\n列出这些“暂存提交”文件后,git status
第一个差异就完成了。现在它继续执行第二次操作git diff --name-status
,这次将其索引中的内容与工作树中的内容进行比较。3 对于每个相同的文件,Git 再次什么也没说。但对于不同的文件,Git 现在会提及文件的名称,并在未暂存提交的更改下列出该文件。
不过这里有一点奇怪。假设您使用操作系统的“删除文件”命令(无论适用于您的操作系统)从工作树中删除了一个文件,而不是从 Git 的索引中删除它。Git 会说该文件的删除“未暂存以进行提交”。这是有道理的,并且与第一种 diff 匹配:如果您使用git rm
,它会删除索引和工作树副本,您会看到删除为“暂存以进行提交”(然后索引和工作树缺少副本)匹配,所以不再提及)。
但是假设您的工作树中有一个尚未编辑的全新文件git add
。Git 会在 diff 输出之后保存这些文件名。然后它继续抱怨这些文件未被跟踪。
3由于工作树中的文件没有经过压缩和去重\xe2\x80\x94,所以它们只是普通文件\xe2\x80\x94Git 必须在这里更加努力地工作。我们也将跳过所有这些细节。
\n.gitignore
未跟踪的文件是位于工作树中但不在 Git\ 索引中的文件。git add
这就是全部内容,除了索引是您通过和 (部分)控制的事实之外git rm
。理解这一点至关重要,因为.gitignore
.
该.gitignore
文件的命名相当错误。这不会忽略文件。Git 提交 Git 索引中的所有内容:您运行git commit
,Git 索引中的所有内容都会进入新提交。.gitignore
从这个开始是什么: 它让git status
闭嘴.
例如,当您运行时git status
,它会对您的所有构建文件发出很多抱怨声。但它们是故意不被追踪的,我们希望它们保持这种状态。抱怨他们只会适得其反。因此列出这些内容.gitignore
告诉 Git:关闭____。
该git add
命令还有一些集体“添加所有内容”模式:例如,git add .
或git add --all
。它们会查找 Git会抱怨的所有文件,并添加它们。由于列出文件.gitignore
使 Git 停止抱怨,因此它也使 Git 停止添加这些文件(如果它们当前未被跟踪)。
.gitignore
不做的是在跟踪文件时阻止 Git 提交文件。如果某个文件在 Git 的索引中,它将被提交。“添加全部”或git add -u
(更新)模式将从工作树副本更新索引副本。所以这应该被称为.git-do-not-complain-about-these-files-when-they-are-untracked-and-do-not-add-them-with-an-en-masse-git-add-operation-either
,或其他什么。但没有人愿意输入这样的文件名,所以.gitignore
就是这样。
一旦您了解了索引是什么以及它的作用以及当您更改提交时 Git 如何交换索引和工作树副本,就会清楚只要您不更改提交\xe2\x80\x94,例如,当您创建新分支仍然选择当前提交,然后切换到该分支\xe2\x80\x94,通过首先创建新分支来在新分支上进行新提交是非常安全的。
\n(稍后,一旦您了解了分支名称如何“指向”提交,就很容易了解如何进行提交,然后创建一个分支名称,然后将另一个分支名称向后移动一步,以实现相同的目的。但这是更多的工作,所以你不妨做更简单、更清晰的事情。)
\n 归档时间: |
|
查看次数: |
1873 次 |
最近记录: |