我意识到我的问题与How to list the parent commit of a stash in `git stash list`和Get git stash parent commit非常相似,但那些有太多令人困惑、不同的回答,我在问我自己的问题。
让我们假设如果我跑git stash list,我看到stash@{0}: On featureX: someMessageHere
当我进行存储时,我如何才能显示我正在工作的提交的哈希值(我想这可以被视为父提交)?
我看到了很多不同的答案,我很困惑这些答案是什么,它们有什么不同,以及哪个是我问题的答案:
git show stash@{1}^git log -1 commitish^git log -1 stash@{0}^git log -g --no-walk --parents refs/stashgit for-each-ref --format='%(refname:short)' --points-at $(git rev-parse refs/stash~1) refs/headsgit log -g --format="%gd %H" refs/stash |
while read name hash; do
printf "%s %s " $name $(git rev-parse --short $name^)
git log -1 --format=%s $hash
done
Run Code Online (Sandbox Code Playgroud)
对于额外的上下文,这就是我要问的原因。
LeGEC 的回答是正确的。不过,(我希望)可以帮助您理解这一部分:
我对它们各自做什么、它们有何不同以及哪一个是我的问题的答案感到困惑:[各种命令的列表]
...在这方面,让我们快速(好吧...也许不是那么快)了解 Git 的内部工作方式。
首先,Git 中最重要的事情是commit。Git 中有一个反复出现的主题:您进行提交 ( git commit),您找到一个提交(多种方式),您显示一个提交(git show或有时git log),您签出一个提交(git checkout或git switch),然后您查看或查看提交(git log再次)。甚至git stash通过提交来工作。
提交有三大特点:
每一个都有一个唯一的ID。这是它的哈希 ID,它看起来像,例如,4a0fcf9f760c9774be77f51e1e88a7499b53d2e2。很多 Git 命令都缩写了这些——4a0f例如,有时你可以缩短到前四个字符,只要这不是模棱两可的,但是在一个大的存储库中,你通常需要 7 个或更多的字符(以及 Linux 存储库)现在最多 12 个)。1
每个存储一个完整的文件快照。我们不会在这里详细介绍。
而且,每个都存储一些元数据:诸如谁提交、何时提交以及为什么提交(日志消息)等信息。这个元数据的一部分是针对 Git 本身的,它给出了提交的父提交的哈希 ID——在提交本身之前的提交。
大多数提交只有一个父级。有些有两个或更多,在这种情况下,第一个父级是这里有趣的父级。至少一个提交——任何人在存储库中所做的第一个提交——必然没有父提交,因为在第一次提交之前没有提交。通常只有这些根提交之一;所有其他人都有历史。
1这些东西看起来随机,但实际上一点也不随机。随着您将越来越多的对象添加到 Git 存储库,每个对象都获得这些对象唯一的 ID 之一,因此您越来越有可能需要使用更完整的名称来区分它们。这就像一个聚会:如果那里只有 10 个人,Bruce 的名字可能是独一无二的,但是一旦您达到 10,000,您可能至少还需要一个名字首字母。
存储库中有四种 Git 对象,但大多数情况下,我们处理提交对象,而忽略其他对象。
这个父级——或者第一个父级,对于合并提交——是 Git 的工作方式:向后。我们通常在最后一次提交时启动 Git ,或者更准确地说,是某个分支中的最后一次提交。然后 Git 对最后一次提交做一些事情,例如:向我们显示作者的日期、姓名和电子邮件以及日志消息。然后,Git 使用该提交的父项将一个提交移回。它向我们展示了之前的提交。然后 Git 继续访问父级的父级——原始提交的祖父级——并向我们展示该提交,然后它再次返回。
当没有任何合并时,这形成了一个很好的简单的向后提交链。如果我们让一个大写字母代表每个提交的哈希 ID,我们可以这样绘制:
... <-F <-G <-H
Run Code Online (Sandbox Code Playgroud)
这H是链中的最后一次提交。我们(不知何故)让 Git 找到这个提交,并展示它。然后 Git 找到G的哈希 ID,它存储在 的元数据中H。Git 使用它来查找 commit G,它向我们展示了它。然后 GitF在里面找到的哈希ID G,依此类推。
(请注意,我们说的提交指回至其较早父提交。这就是为什么我们制定这些向后指向箭头,实现了Git可以轻松地去它有时重要的倒退,但艰难时期向前。提交G点背to early F, but not forwards to later H。不过,大多数时候,我们真的不必关心,而且很难画好这些箭头,所以大多数时候,我不打扰。)
git log例如,这就是这样做的。但是它如何找到 commitH呢?好吧,简单的方法是我们告诉它git log master。在上图中,我们可以再添加一个指针:我们有name master,指向 commit H,如下所示:
...--F--G--H <-- master
Run Code Online (Sandbox Code Playgroud)
如果我们git checkout master进行新提交,Git 将添加新提交,使其父项为H:
...--F--G--H <-- master
\
I
Run Code Online (Sandbox Code Playgroud)
但随后立即更新名称master,使其指向I现在提交:
...--F--G--H--I <-- master
Run Code Online (Sandbox Code Playgroud)
最后一部分的意思是git log使用名称来查找最后一次提交。如果我们给它一个分支名称,那就是它使用的名称。如果我们不给它任何名称,则git log使用特殊名称HEAD。但是我们也可以给它一些不是分支名称的东西,stash就是这样。
当git stash save(进行存储的旧方法)或git stash push(进行存储的新方法)进行提交时,它会设置它们以便特殊名称stash指代这些提交中的一个,并且该提交作为其第一个父级 -稍后我们将更多地讨论第一个父母——在你运行时(现在仍然是)当前的提交git stash。
也就是说,如果我们绘制它们,我们会得到:
...--G--H <-- master
|\
i-w <-- stash
Run Code Online (Sandbox Code Playgroud)
我不会去到为什么我打电话给他们i,并w在这里,但git stash文件还要求他们I与W(大写小写字母代替,我喜欢让我的大写字母更正常提交,不为这些藏匿的)。
这里重要的是 commit的第一个父级w是 commit H,它是您运行时的提交git stash push或用于创建的任何内容w。
当 Git 需要提交——或者实际上是任何内部对象,但我们再次只对这里的提交感兴趣时——实际上有很多方法可以命名它。完整的列表在gitrevisions 文档中进行了介绍。但是,出于我们的目的,我们希望专门查看^和~后缀。稍后我们将讨论大括号和数字后缀。
如果我们采用一些有效名称,例如HEADormaster或stash,并在末尾添加一个插入符号/帽子^或波浪号~,这是对 Git 内部修订查找器的指令:从我们已经命名的提交开始,找到提交的父级)。然后^后缀选择提交的第一个父项,因此这stash^意味着名称找到的提交的第一个父项stash。
波浪号后缀也选择第一亲。起初这似乎是多余的:develop^并且develop~都选择了 name 选择的提交的第一个父级branch。不过,我们可以在它们之后添加数字,然后它们就会变得不同。理解这一点的关键在于我们上面绘制的图表。假设我们有:
I--J
/ \
...--G--H M <-- develop
\ /
K--L <-- feature
Run Code Online (Sandbox Code Playgroud)
在这里, commitM是一个合并提交,因此它有两个父项。假设第一个父节点M是J,第二个父节点M是L——如果我们在命名为 commit时执行提交,然后运行到 make ,我们会得到M这样的结果。git checkout developdevelopJgit merge featureM
语法develop^2意味着找到 commit 的第二个父级M,即 find commit L。这命名了我们使用 name 获得的相同提交 - 所以我们feature可以这样做,只要我们还没有删除name feature。但重点是,M^2或者develop^2找到 commit L,因为这意味着找到第二个 parent。
同时,语法develop~2意味着找到 commit 的第一个父级的第一个父级M,即 find commit I。那是因为2在这种情况下是后退的次数。所以我们退一步,沿着第一个父行 from Mto J,然后再次沿着第一个(也是唯一的)父行 from Jto I。
当^或之后的数字~是1或根本不存在时,两者都做完全相同的事情:^1表示找到第一个父节点(后退一个第一父链接),并~1表示后退一个第一父链接。
git show stash@{1}^
我们稍后会介绍@{1}。现在,想象一下刚才说的stash^。namestash会找到一些提交,而 the^会找到它的第一个父级。然后git show将显示该提交。该git show命令通过以下方式执行此操作:
--pretty=format:...选项进行更改)最后一部分使它看起来像是提交本身持有一个差异——但事实并非如此。差异是为你计算的,当git show你开始这样做时。
git log -1 commitish^
同样,^后缀使 Git 返回到提交的父级。然后git log -1显示一次提交的日志消息,但不显示差异 - 显示内容的第一部分git show- 但-1在显示一次提交后停止。
git log -1 stash@{0}^
这很相似,除了现在我们有stash@{0}^代替commitish^。该^后缀适用于stash@{0}符,我们会得到一个位,再一次。
git log -g --no-walk --parents refs/stash
这个有点不同。该--no-walk选项是多余的-g并且没有意义,因为-g接管。不过,这个--parents选项确实有意义。为了-g正确谈论,我们需要进入我们涵盖该部分的部分。让我们把最后两个命令留到以后,现在开始 reflogs。@{number}
在 Git 中,每个引用——每个名字都像masterordevelop或者,事实上,stash——也可以保留自己单独的“以前”值的日志。对于普通的分支名称,这些日志只记住分支名称曾经指向的位置。因此,每个日志条目都会记住一个哈希 ID:分支名称的旧值。
例如,当您进行新的提交时,Git 会自动将分支名称提前以指向新的提交。但是名称曾经指向提交的父级,因此日志现在包含父级哈希 ID。如果您使用git reset来重置分支,这使得预重置哈希ID写入日志了。因此,日志只会在您工作时累积每个哈希 ID。
有知道这里有另外一个重要的事情:后缀选择数“个日志条目。数字零表示name 的当前值。所以拼写起来还有很长的路要走,但是 的旧值,是旧值,但现在更旧了,在你做了一些更新之后。@{number}master@{0}mastermaster@{1}mastermaster@{2}master
Git 通常会在一段时间后清除旧的日志条目——大多数日志条目默认为 90 天后,某些日志条目默认为 30 天。但是stash很特别,它的日志条目通常不会根据年龄清除。由于stash不是分支名称,因此它不受分支命令的操作。它改为操作由git stash命令,与它的push,pop和drop操作。
在这里,git stash使用stashreflog 来跟踪先前保存的 stash。当您使用git stash push,Git的重编使什么先前的日志条目是 stash@{0}变stash@{1},什么是stash@{1}变stash@{2},等等。这实际上与任何普通的分支引用日志条目相同(除了永不过期的部分)。但是不同的是,当您使用git stash pop或git stash drop,Git会扔掉旧的stash@{0}条目,那么什么是stash@{1}现在stash@{0},什么是stash@{2}现在stash@{1},等等。
所以现在我们可以正确地stash@{1}^从第一个开始git show:
git show stash@{1}^
该stash@{1}操作意味着在存储堆栈中找到一层深的存储提交。然后^后缀选择它的第一个父级。
由于stash@{1}是w存储堆栈中一层深的存储stash@{1}^提交,因此是其父提交。这就是这个 stash 挂起的提交。
最后,我们还可以解决这个问题:
git log -g --parents refs/stash
(我已经去掉了无意义的--no-walk。)
该-g选项指示git log查看引用日志,而不是像往常一样查找提交然后在历史记录中倒退。它将调查的一个 reflog 是 for refs/stash— 的完整拼写stash。
该--parents选项告诉git log不仅要显示每个提交哈希 ID,还要显示其所有父提交哈希 ID。
因此,我们将w在 stash 堆栈中看到每个提交,以及它的两个父提交。第一个父项将是该i-w对挂起的提交,第二个父项将是i提交。
git for-each-ref --format='%(refname:short)' --points-at $(git rev-parse refs/stash~1) refs/heads
该git for-each-ref命令是一个内部主力命令——它并不是真正面向最终用户的——它同时实现git branch --list和git tag --list,以及其他几个。因为这个命令是为了编写面向用户的命令,而不是直接被用户使用,它有很多选项:
该--format选项告诉它如何产生输出。在这里,我们选择打印名称的缩写形式(由于稍后的选项,它将是一个分支名称)。
该--points-at选项告诉它不要打印名称,除非名称专门命名了特定的提交。我们在这里告诉它我们想要的提交是另一个 Git 命令的输出,git rev-parse.
该refs/heads选项告诉git for-each-ref要使用哪些引用。该refs/heads空间包含您所有的分支名称。所以这告诉它:只查看我的分支名称,找到那些命名一个特定提交的分支名称;然后,对于您找到的任何名称,打印该分支名称的简短版本。
我们选择让它搜索的提交是 commit 的哈希 ID refs/stash~1。这使用~后缀来获取由 name 标识的提交的第一个父级refs/stash。这是 的完整拼写形式stash,因此我们要求 Gitrefs/stash用于查找w提交,然后使用它~来查找其父项,例如 commit H。然后我们让 Git 搜索所有分支名称以查看这些名称中是否有任何一个 commit H。如果名称refs/heads/master—branch —master标识 commit H,则该命令将打印名称master。
最后:
Run Code Online (Sandbox Code Playgroud)git log -g --format="%gd %H" refs/stash | while read name hash; do printf "%s %s " $name $(git rev-parse --short $name^) git log -1 --format=%s $hash done
这再次使用git log -g并refs/stash查看refs/stash. 告诉 Git如何打印每个这样的条目的%gd %H指令:打印或或或任何合适的,并打印与该引用日志条目关联的哈希 ID。--format%gdstash@{0}stash@{1}stash@{2}%H
此命令的输出进入 shell 循环。此循环读取名称(部分)和哈希 ID。然后命令打印:stash@{number}printf
git rev-parse给定我们刚刚读取的哈希 ID 时找到的哈希的简短版本加上^后缀,即存储的父提交的哈希的简短版本;最后,运行git log -1 --format=%s $hash,打印存储本身的主题行。
因此,这也将打印您想要的信息——由于--short在git rev-parse命令中——使用每个存储的适当父提交哈希 ID 的缩写形式。
您正在寻找的提交是stash@{0}^:
git show stash@{0}^
git log -1 --oneline stash@{0}^
git rev-parse stash@{0}^
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
794 次 |
| 最近记录: |