【问题标题】:Does git stash, stashes only staged files or even Unstaged and Untracked files?git stash、stash 是否只存储暂存文件,甚至是未暂存和未跟踪的文件?
【发布时间】:2020-07-30 06:12:52
【问题描述】:

我对错误分支上的代码做了一些更改 (dev)。我想将所有更改转移到 master 分支,我得到了一些 lead from here,但我不清楚 git stash 是否仅存储 staged 文件或甚至 Unstageduntracked 文件?

因为,在切换分支后,我需要在 @ 上提交 unstageduntracked 文件987654325@分行。

dev 分支上的状态:

❯ git status
On branch dev
Your branch is up to date with 'origin/dev'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   __init__.py
    modified:   src/App.py
    modified:   src/analysis/insis.py
    modified:   src/access/via/db.py
    modified:   src/process/DM.py
    modified:   start.sh
    modified:   utility/utils.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    docs/
    process.py
    src/flow.py
no changes added to commit (use "git add" and/or "git commit -a")

切换到master 分支时,出现以下错误:

❯ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
    start.sh
Please commit your changes or stash them before you switch branches.
Aborting

如何安全切换master,将上述所有未暂存和未跟踪更改从dev 转移到master

【问题讨论】:

    标签: git github gitlab gitlab-ci


    【解决方案1】:

    默认情况下:git stash 存储暂存文件(索引)和未暂存文件(已修改但未添加的跟踪文件)。未跟踪的文件不会被存储,而是留在磁盘上。

    你可以使用:

    • git stash -k 告诉 stash 让暂存文件保持原样,并且只存储未暂存的修改,
    • git stash -u 告诉 stash 包括未跟踪的文件,
    • git stash -a 告诉 stash包括未跟踪和忽略的文件。

    参考:git-stash options

    【讨论】:

    • 虽然第一个答案要详细得多。这是对git stash 对暂存、未暂存和未跟踪文件的行为的直接回答。
    • 但是,git statusgit diff --stagedgit stash push -m 之前的输出与git statusgit diff --stagedgit stash push -m 之后的输出不同。这是为什么呢?
    • 'git stash -k 告诉 stash 将暂存文件保持原样,并且只存储未暂存的修改,' 第二部分不正确:此命令隐藏 所有 修改。
    • @Maggyero :我认为您(正确地)指的是在存储中创建的提交包含所有修改的事实。不过,就您的工作树和索引而言:git stash -k 将保留您的索引,并且只会丢弃未暂存的更改。
    • 没错,我只是指你句子的第二部分,第一部分是正确的。您是否知道仅存储未暂存的修改的方法?
    【解决方案2】:

    git stash 所做的是进行一些提交。这意味着它不会做任何其他方式无法做的事情,只需提交即可。

    我不清楚 git stash 是否仅存储暂存文件,甚至是未暂存和未跟踪的文件?

    除了最后一部分之外,这不是一个格式正确的问题。默认情况下,git stash 不会存储未跟踪的文件,但 git stash 有一个选项可以做到这一点。 您可能不应该使用此选项。要真正了解 git stash 的工作原理以及您可以做什么,您需要了解很多事情。

    Git 就是提交

    Git 并不是关于分支*。相反,它是关于提交的。分支名称很有用:它们可以帮助您(和 Git)find 提交。但重要的是提交

    每个提交都包含您跟踪文件的所有的完整快照。 (当我们讨论 Git 的 indexstaging area 时,我们将在后面的部分中进入 tracked。)这些文件存储在提交中以一种特殊的、只读的、仅限 Git 的、冻结的、压缩的和去重复的形式。这种重复数据删除意味着当您重新使用之前提交的大部分文件的新提交时,new 提交实际上并不需要文件的另一个副本:它只是重新使用之前提交的冻结文件。

    每个提交都有编号,但编号不是简单的连续编号,如 #1、#2、#3 等。相反,每个提交都有一个大而丑陋的 hash ID,它是该特定提交所独有的。各地的每个 Git 都会同意 that 提交获得 that 哈希 ID,并且没有其他提交可以使用该哈希 ID。这在内部通过计算构成提交的所有位(一和零)的加密校验和来工作。由于所有 Git 都使用相同的算法,因此他们都会同意 that 提交的哈希 ID。

    (这反过来意味着实际上不可能更改一个提交。如果你从 Git 对象的大数据库中取出一个提交,修改一些位,然后放回去,新的提交有一个不同的哈希 ID。之前的提交继续存在。因此提交,就像 Git 的冻结存储文件一样,是不可能更改的。)

    除了每个文件的完整副本(但已删除重复)之外,每个提交还存储一些元数据:关于提交本身的一些信息。这些信息包括提交人的姓名和电子邮件地址、一些日期和时间戳,以及他们解释提交的原因为什么的日志消息;但它还包括一些仅适用于 Git 本身的内容:previous 提交的哈希 ID。这允许 Git 向后工作。

    分支名称只是保存分支上 last 提交的哈希 ID。由于我们对 stash 提交更感兴趣,因此我们不会在这里详细介绍,除了描述 Git 通常如何进行新提交。

    你的工作树

    每次提交中存储的文件都是只读的,而且,其格式是您计算机上的大多数程序实际上无法读取的。那么你怎么可能处理你的文件呢?或者,换句话说,提交是只读的,那么如何创建一个新的呢?

    Git 对此的回答是为您提供工作树工作树。你首先选择一些提交——例如使用git checkoutgit switch——然后Git 会从提交中获取所有冻结、压缩、去重的仅Git 文件,并将它们扩展为你可以使用的普通日常读/写文件可以读写。这些从提交中获取的日常使用的文件副本位于您的工作树中。

    这意味着您看到和使用的文件实际上并不是 Git 的 文件。 Git 的文件隐藏在 .git 目录中(而且大多数文件也没有正常的文件名:它们以哈希 ID 名称存储在 .git/objects 中和/或移动到特殊的 pack 文件中以更高的压缩率存储多个对象)。

    一定是这样,因为 Git 的文件是只读的和 Git-only 的,你需要所有东西都可以使用的读/写文件。所以 Git 会填充你的工作树,然后你就可以开始工作了。工作树文件是你的: Git 会在你要求时填写它们。

    这就足够了:Git 可以只拥有每个文件的两个副本,一个在当前提交中冻结,一个在您的工作树中解冻并有用。事实上,其他版本控制系统会这样做。但Git没有。 Git 为每个文件保留 第三个​​ 副本。1 这个额外的副本采用 Git 的冻结 格式,但与提交中的副本不同,它不是完全冻结:您可以替换它。


    1从技术上讲,这第三个副本是预先去重的,所以它实际上并不直接在索引中。相反,索引包含每个文件的模式、文件名、blob 哈希 ID、暂存编号以及其他使 Git 运行速度更快的信息。不过,您通常不需要知道这些:您可以假装索引包含每个文件的冻结格式副本,准备提交。如果您开始使用低级索引操作命令git ls-files --stagegit update-index,则只需了解 blob 哈希。


    索引或暂存区

    索引是 Git 中另一个非常重要的东西,但通常没有很好地解释。部分问题在于它有点复杂:例如,它在合并期间扮演了更大的角色。但总的来说,Git 的索引有一个很好且相对简单的描述:Git 的索引保存你提议的下一次提交。

    注意:索引要么非常重要,要么最初的命名很糟糕,以至于它现在有三个名称。它被称为索引(如此处),或 暂存区(代表其在进行新提交中的角色),或者——如今很少见——缓存。这三个名字都指同一个东西。2

    换句话说,在任何时候,每个文件都有三个活动副本:

    • 一个在当前或HEAD 提交中,这个副本被冻结:没有什么可以改变它。您可以创建具有不同副本的新 commits,但旧 commit 会继续保留旧副本。

    • 第二个已准备好冻结并在 Git 的索引中。它开始匹配冻结的副本。

    • 最后一个是日常格式,您可以使用它:它在您的工作树中。

    当您运行git commit 并进行新的提交时,Git 会立即打包 Git 索引中的所有文件,并将它们放入新的提交中。所以新的提交正好包含当时 Git 索引中的每个文件的版本。

    当您在文件上运行git add 时,您实际上是在告诉 Git:将文件的工作树版本复制到索引中。Git 将其压缩为冻结格式(并且 de - 如果已经有副本,则复制它)。如果这是一个全新的文件,现在它在索引中。如果它已经在索引中,则启动索引的旧副本out;现在索引副本与工作树副本匹配。

    你也可以告诉 Git 从索引中删除一个文件。如果它以前在那里(从提交或从您的工作树复制),现在它已经消失了。执行此操作的主要命令是git rmgit rm 不仅会删除索引副本,还会删除您的工作树副本。由于工作树副本根本不在 Git 中——除非你将它复制到索引中,也就是说,或者除非它来自提交——并且你刚刚还删除了索引副本,所以要小心这个操作.

    要删除文件的索引副本而不删除工作树副本,您可以使用git rm --cached。由于仍有工作树副本,因此危险性较小,但请记住工作树副本不在 Git 中,并且当您进行新提交时,新提交将没有文件副本。

    因此,当索引/暂存区域开始与提交匹配时,您通常会修改内容,然后让 Git 更新索引。您对索引所做的更新会导致“文件暂存以供提交”。如果您尚未更新索引,但已更新工作树中的文件,则会导致“文件未暂存以进行提交”。请注意,您可以同时执行这两种操作:

    1. 提取提交:现在文件 F 的所有三个副本都匹配。
    2. 修改 F 的工作树副本。现在HEAD 和索引 F 匹配,但工作树不匹配:文件“未暂存以进行提交”。
    3. 运行 git add <em>F</em> 现在 HEADF 的索引副本不匹配,但索引和工作树副本匹配:文件“暂存以待提交”。
    4. 再修改F的工作树副本。现在所有三个副本都不同,因此文件 F 既是“为提交而暂存”又是“未为提交暂存”!

    同样,这里要记住的是,每个文件有三个副本。通常,其中两个,甚至三个都匹配。当它们匹配时,Git 不会任何关于额外副本的信息。

    git addgit rm 命令是更新 Git 索引的两个主要命令。在进行新的提交之前,您必须更新 Git 的索引,因为 git commit 使用 Git 索引中的文件的副本。这就是它的全部内容。3


    2有时候,特别是在Git源码中,“缓存”这个词是指读取索引产生的内部数据结构。

    3请注意,git commit -agit commit <em>files</em> 通过将任何要提交的文件添加到索引来工作。这可能会变得相当复杂,尤其是如果您使用git commit --only;我们不会在这里详细介绍这些细节。


    跟踪文件与未跟踪文件

    这为我们带来了未跟踪文件的定义,这也非常简单:未跟踪文件是不在 Git 索引中的文件。 也就是说,如果你的工作树中有一些名为 U 的文件,但 U 不在 Git 的索引中现在,那么 U em> 未被跟踪。

    将文件 U 添加到 Git 的索引(同时将其保存在您的工作树中),现在 U 已被跟踪。从 Git 的索引中删除它(同时将它保存在你的工作树中),U 再次未被跟踪。由于git commit 只会在新提交中存储 Git 索引中的那些文件,因此不会提交未跟踪的文件。

    现在我们终于可以明智地谈论git stash

    一个普通的git commit 命令:

    • 从您那里收集一些元数据,例如您的姓名和电子邮件地址以及日志消息;
    • 将 Git 索引中已经处于冻结格式的所有文件写入新提交,并使用上述元数据(以及当前提交的哈希 ID)将所有这些文件放入新提交;和
    • 将新提交的哈希 ID 存储在当前分支名称中,因为新提交是新的 last 提交。

    git stash 命令开始时非常相似,但它不会从您那里获取日志消息,而是自己生成一条消息。然后它从 Git 的索引中的任何内容进行提交现在——但它根本不会将此提交放在 当前分支

    几乎以通常的方式进行了提交,git stash 现在运行相当于git add -u 来更新 Git 索引中的所有文件,基于您工作树中的相同文件。这会更新索引以匹配您的工作树文件——但只有 跟踪 文件在索引中(根据定义),因此只有这些文件得到更新。 stash 命令现在从 this 索引进行提交,保存所有跟踪的文件。

    Git 将这两个提交(第一个索引和一个工作树)绑定在一起,4 然后运行 ​​git reset --hard5 这会将所有跟踪的文件放入你的工作树恢复到它们在当前提交中的相同状态,并且还用当前提交中的冻结格式副本覆盖 Git 的索引。所以你现在有一对提交保存了之前的索引之前的工作树——两个完整的快照——但是未跟踪的文件根本没有保存在这里。

    因为这个 stash 提交对在 no 分支上,您可以稍后更改到新分支并使用 git stash applygit stash pop 或它们的任何变体将存储应用到不同的初始点。实际的应用过程有些复杂,如果你在应用时忘记使用--indexpop 命令会,如果成功,drop 两个提交,即使它实际上并没有使用索引提交。所以我总是建议任何使用git stash 的人避免使用git stash pop:首先使用git stash apply,然后检查以确保你得到了你想要的结果,然后再使用git stash drop 删除存储。


    4从技术上讲,Git 通过以合并提交的形式进行工作树提交来做到这一点。其他 Git 工具会认为这是一个正常的合并,并不能很好地工作;您需要在 stash 提交上使用 git stash 以使其表现良好。

    5不久前,git stash 是一个花哨的脚本,直接使用各种 Git 命令,包括git reset --hard。现在它是一个 C 程序,但它仍然执行相同的操作,只是无需运行额外的命令。


    git stash 有选项

    当你运行git stash 来创建一个新的存储时,你可以给它两个选项之一:

    • -u--include-untracked:这是第三次提交,我们稍后会描述。
    • -a--all:这也是第三次提交。

    这两个选项都告诉git stash 进行第三次存储提交,以及通常的两个(索引和工作树)。唯一的区别是第三次提交的内容:

    • -a所有文件在你的工作树中,但不在Git的索引中,进入第三次提交,包括.gitignore中列出的文件;
    • 对于-u.gitignore 列出的未跟踪文件进入第三次提交,但.gitignore 列出的未跟踪文件不要不。

    完成第三次提交后,6 stash 命令然后从您的工作树中删除进入此提交的每个文件。

    当你去恢复一个存储时,Git 会检查它是一个两次提交的存储还是一个三个提交的存储。如果是三提交存储,Git 将尝试从第三次提交中提取所有文件。如果这些文件中的任何一个现在在您的工作树中 - 请记住,这些文件在您进行存储时是未跟踪文件,所以它们在您的工作树中然后—Git 将完全拒绝提取此存储。您必须将所有此类文件移开,或完全删除它们。

    如果 Git 可以提取第三个提交,它会这样做,并且还会以通常的方式提取其他两个提交。因此,您可以使用这三个提交存储之一来存储未跟踪的文件。


    6出于技术原因,这个提交必须在工作树提交之前进行,所以在某种程度上它是第二次提交,但它被小心地转移到第三次,因为稍后提取。


    保持分阶段的变化分阶段进行

    如上所述,git stash pushgit stash save 将创建一个包含两个提交的新存储:

    • 一次提交按原样保存当前索引。
    • 另一个提交按原样保存当前工作树。

    暂存的更改实际上意味着“某个文件的索引副本与该文件的提交副本不同”。通常,这反过来意味着该文件的 work-tree 副本与索引副本匹配。

    稍后的git stash apply 没有--index 选项只是忽略索引提交。如果索引提交具有工作树提交所具有的每个文件的相同副本,并且您仅应用工作树提交,那么您将获得对工作树所做的相同更改:忽略索引提交没有任何损失,除了忘记您之前使用过哪些git add 命令。

    稍后的git stash apply with --index 告诉 Git:在应用工作树提交之前,尝试将索引提交应用到当前索引。不会总是有效,如果无效,git stash 只会建议您尝试不使用--index。如果使用--index 非常重要,这可能是个坏建议!

    如果一切正常,Git 的索引将会更新。所以现在一些更改将再次“暂存以供提交”——记住,这只是意味着“文件的索引副本与当前提交的副本不同”。

    无论如何,不​​管有没有--index,如果git stash apply 命令现在移动到工作树提交,它将使用Git 的合并机制来尝试应用这个提交。这可能会导致合并冲突。如果是这样,它们可能会非常混乱。

    我不喜欢大部分时间使用git stash

    大多数时候,我建议您直接提交,而不是 git stash

    提交很容易,即使是在“错误的分支”上,然后稍后再“删除”它。 (提交本身会保留一段时间,以防你决定要它回来:默认情况下,Git 至少需要 30 天才能决定这个提交不再有任何用处,之后 Git 将清除它。)只要您小心不要使用git push 或其他方式将其发送出去,在分支上进行的“错误提交”是无害的,并且提交后,您现在可以切换到正确的分支并使用git cherry-pick复制它。

    拥有 Git 的所有工具(包括 git diffgit showgit cherry-pick)在正常、日常、容易找到和容易看到的提交上工作比在提交上使用 git stash apply 更令人愉快很难检查,因为它们不是日常提交并且不在任何分支上

    尽管如此,git stash 有时会非常方便。如果出现问题,git stash branch 命令可以将现有存储转换为新分支,然后您可以访问 Git 的所有常规工具。

    【讨论】:

    • 难以置信的解释!你说的确实发生了。我使用了git add -ugit stashgit checkout mastergit stash apply,我得到了 CONFLICT (modify/delete): start-processing.sh 在更新的上游中删除并在隐藏的更改中进行了修改。版本 stashed 的 start-processing.sh 更改留在树中。 我错误地忘记了不要在 master 中添加这个文件,并且 stash 也没有添加到 master 中!但 git 不允许我在 master 上提交。
    • 在向 master 提交时,它会抱怨:❯ git commit -m " added documentation." U start-processing.sh 错误:无法提交,因为您有未合并的文件。提示:在工作树中修复它们,然后使用 'git add/rm ' 提示:酌情标记分辨率并进行提交。致命:由于未解决的冲突而退出。关于如何缓解它的任何建议?
    • 因为我在我的 dev 分支中有它的副本,所以我决定简单地做 git rm start-processing.sh 然后提交并推送到 master!,快速提问,有必要做 git stash pop 吗?如果相对于任何分支,此命令是否执行?我的意思是,我必须在特定的分支上才能触发此命令吗?
    • CONFLICT (modify/delete) 在您的索引中留下了合并冲突。上面没有提到这种情况,但是git stash apply 步骤失败了,因此 Git 没有删除存储。当您处于合并冲突状态时,许多 Git 操作被完全禁用。我不喜欢git stash 的原因之一是它会启动这个合并过程而不管它是否会成功,如果它确实失败了,存储库可能处于没有轻松出路的状态。
    • @KevinVictor: 值得一提的是git stash最近得到了Git核心团队的大量关注,以至于Git 2.35现在有了新的特性和一些更微妙的行为旧的存储脚本已更改。不过,原理都一样。
    猜你喜欢
    • 2021-07-23
    • 1970-01-01
    • 2016-10-23
    • 1970-01-01
    • 2019-05-06
    • 2016-12-19
    • 2013-11-30
    • 2018-09-08
    相关资源
    最近更新 更多