如果之前成功地进行了Git stash,才可以使用"Git stash pop"。

14

我的工作流程中涉及很多这样的操作:

  • 暂存 git 变更
  • 拉取 git 最新代码
  • 弹出暂存的变更内容
  • 启动合并工具以解决冲突

我正在尝试编写一个脚本,一次性完成所有这些操作,这样我就可以从终端直接调用它。

#!/bin/bash

# First stash our local changes
git stash

# Then git pull to update our repo
git pull

# Pop the stash
git stash pop

# Launch mergetool if necessary
git mergetool

我遇到的问题是,如果我不小心运行了这个命令,在暂存区没有更改,git stash pop 会应用某些(通常是非常旧的)存储。我想要做的是只有在实际进行了存储后才运行 git stash pop。有没有方法可以做到这一点?


为什么你需要使用stash步骤呢? - CodeWizard
如果我有与拉取冲突的本地更改,它会显示以下错误信息:“错误:合并时以下文件的本地更改将被覆盖: file.ext 请在合并之前提交您的更改或将它们存储起来。 中止。” 因此,使用存储、拉取、弹出的方式可以让我拉取更改,然后通过合并工具将它们合并进去。 - bobroxsox
那为什么不直接提交呢? - CodeWizard
如果我提交并拉取,合并将向git添加另一个提交(即使只是快进),这会使历史记录比存储,拉取,弹出的方式更加混乱,后者仅保留拉取后我的自己的提交。 - bobroxsox
我还没有使用 Git 很长时间,仍在逐渐掌握它。同时也欢迎更好的工作流程建议 :) - bobroxsox
显示剩余2条评论
7个回答

15

2022年7月修订: 时间(和 Git)已经过去,取决于您的 Git 版本,以下大部分内容可能已不再准确。其中最重要的变化之一是现在有了 git stash pushgit stash create 命令。请参见脚注和评论。


正如Xavier Álvarez指出codeWizard写道的那样,在这里完全避免使用git stash可能是明智的选择。例如,我会考虑使用单独的git fetchgit rebase步骤(参见Xavier的答案),并注意到rebase现在具有--autostash选项,它就是你想要的,只是不能直接通过git pull便捷脚本使用。1

话虽如此,还是有方法能够做到你所说的。这有点棘手。如果git stash save有一个类似于git commit --allow-empty的“force”选项,那么这将更容易,但它没有这样的选项。2相反,你可以检测git stash save是否推送了一个新的 stash。如果git stash save有一个指示它是否推送了 stash 的退出状态,那么这也将更容易,但不幸的是它没有。这意味着我们必须完全依靠另一种技巧。我们从两个事实开始:git rev-parse 从“引用”中找到 SHA-1,并且git stash使用一个特定的引用。

git rev-parse命令会将任何引用转换为SHA-1:

$ git rev-parse refs/remotes/origin/master
2635c2b8bfc9aec07b7f023d8e3b3d02df715344

引用(Reference)通常以refs开头,是指向某个SHA-1 ID的名称。最常见的引用是分支:refs/heads/branch,你可能也使用过标签:refs/tags/tag,以及类似origin/master这样的远程跟踪分支,它的完整名称是refs/remotes/origin/master

stash脚本使用refs/stash,因此我们可以简单地运行git rev-parse refs/stash3 我们想在运行git stash save之前运行它,然后再次运行git stash save。如果输出发生变化,则git stash save步骤必须已经将一个新的存储推入了存储堆栈。

我们必须小心一点,因为如果存储堆栈为空(因为上一个存储被弹出或删除,或者尚未创建任何存储),git rev-parse将会给出错误消息并且不会产生SHA-1:

$ git rev-parse refs/stash
fatal: ambiguous argument 'refs/stash': unknown revision or path not in
the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
因此,我们实际上需要使用git rev-parse -q --verify refs/stash,如果引用不存在,则会静默地不产生任何内容,然后我们只需要在使用结果的任何Shell脚本中小心处理即可。
oldsha=$(git rev-parse -q --verify refs/stash)
git stash -q save  # add options as desired here
newsha=$(git rev-parse -q --verify refs/stash)
if [ "$oldsha" = "$newsha" ]; then
    made_stash_entry=false
else
    made_stash_entry=true
fi
... all of your other code goes here ...
if $made_stash_entry; then git stash pop; fi

1git pull命令基本上是git fetch后跟git merge的简写,或者如果你告诉它,可以运行git fetch后跟通常更合适的git rebase。不过,如果将它分成两个单独的步骤,你就能获得更多控制权,同时还能在合并或变基之前检查传入的更改。

编辑,2022年7月git pull现在不再是脚本,并且自动暂存与其一起工作。其中有过渡状态。

2你可以使用相对较新的createstore子命令有效地强制创建存储:创建一个存储,然后存储结果SHA-1,即使没有要存储的内容,你也已经强制进行了存储。但并非每个人都更新了最近的git,因此对于脚本来说,依赖旧方式(或如前所述,根本不使用存储,特别是因为它在各种版本的Git中存在各种次要但令人讨厌的错误)可能更明智。

编辑,2022年7月git stash现在不再是脚本,并且有了新的选项和动词。请参阅注释。

3最好拼出完整名称,因为git rev-parse stash将首先查找名为stash分支。总的来说,在编写别名或脚本时,对于所有引用都要拼出全名(必要时使用--语法),以确保Git不会在奇怪的边角情况下执行它认为你的意思。


谢谢。在我的脚本使用案例中,这是完美的。谢谢! - U007D
“git stash create” 目前不支持 -u 选项来处理未跟踪的文件。因此,使用 git stash save 命令回答更加灵活,以防存在未跟踪的文件。 - steampowered
@steampowered:有趣的是那里不支持“-u”。最近在Git中,存储代码引起了一些关注,但上次我测试它(很久以前),它仍然无法正常工作,“在索引中进行更改,将工作树设置为匹配HEAD”。(感谢您修复示例中真/假状态交换的问题!) - torek
在最近的git中,我收到了以下错误信息:“致命错误:未指定子命令;由于意外的标记“save”,无法假定为'push'。” - Joseph Garvin

4

使用git stash save <messsage>命令时,传递的消息将在成功保存后显示。

一个技巧是生成时间戳作为消息,并删除最近的stash,如果在结果消息中找到了该时间戳。

一行代码:

t=timestamp-$(date +%s); r=$(git stash save $t); v=$(echo $r|grep $t); if [ "$v" ]; then git stash list; echo "SAVED! NOW REMOVING..."; git stash drop stash@{0}; else echo "Nothing to Stash!"; fi; echo "Stashes: "; git stash list; echo "Done!"

扩展:

# unique timestamp
t=timestamp-$(date +%s) 

# stash with message
r=$(git stash save $t)

# check if the value exists
v=$(echo $r|grep $t)

# if the message is found...
if [ "$v" ] then 

  # DEBUG: Before
  git stash list
  echo "SAVED! NOW REMOVING..."

  # remove last stash
  git stash drop stash@{0}
else 
  echo "Nothing to Stash!"
fi

# DEBUG: after
echo "Stash List: "
git stash list
echo "Done!"

4

阅读您解释为什么要做您所做的事情,我可能会采用完全不同的方法。首先,我会 获取 您想使用的遥控器:

git fetch <remote> (e.g. git fetch origin)

然后,我会对那个远程分支执行变基(rebase)

git rebase <remote>/<branch> (e.g. git rebase origin/master)

这将合并您的更改,您仍然可以解决任何冲突。
如果您不喜欢这种方法,您可以使用git pull命令,并加上--no-commit标志:
git pull --no-commit

这样合并后就不会执行自动提交。

那听起来像是更自然的工作流程,看起来基本上就是我想要做的。谢谢。 - bobroxsox

2
先运行 git status 命令,如果有本地修改,则运行 stash 命令。如果没有,则跳过。将此结果保存在 bool 变量中,如果没有新的存储,则不要运行 pop 命令。

嗯,那可能行。我想知道是否有比尝试解析输出更好的检查本地更改的方法。 - bobroxsox
编写一个shell脚本来为您执行此操作,并仅在正确的条件下执行“stash pop”,bobroxsox。 - try-catch-finally
@bobroxsox needStash="$(git status -s)"; [[ -n "${needStash}" ]] && git stash; ! git pull; [[ -n "${needStash}" ]] && git stash pop - undefined

2

我正在寻找类似的东西来自动合并主分支。最终我只是创建了一个带有唯一名称的空文件。下一步是在存储时包括未跟踪的文件(stash -u)。现在我知道我可以随时弹出,因为我正在创建用于存储的内容。完成后,我删除我创建的新文件。

然后我创建了以下别名:

 up  - pull with rebase and sub-modules*
 mm  - merge master
 tm  - create file with novel name
 rtm - remove said file

...以及实际的别名:

[alias]
up           = !git pull --rebase --prune --recurse-submodules $@ && git submodule update --init --recursive && git submodule foreach git up && echo 'git on up'
mm           = "!f() { git tm; git stash -u; git co ${1-master}; git up; git co -; git merge ${1-master}; git stash pop; git rtm; }; f"
tm           = "!f() { touch __nothing_to_see_here__; }; f"
rtm          = "!f() { rm __nothing_to_see_here__; }; f"

*up stolen from haacked


0

我遇到了同样的问题,并使用以下简单脚本解决了Windows:

@rem The following is unsetting one environment variable if already set by (normally by previous executions of the script)
set STASH_SUCCEED=
@rem If push succeeds the variable is set
git stash push && set STASH_SUCCEED=1
@rem Do your stuff here...

if defined STASH_SUCCEED ( git stash pop && echo "STASH POPPED" )

-2

如果你在脚本中做了很多事情,应该尽量避免使用stash,而是直接提交你的代码,然后拉取你的更改。

你也可以编写别名来代替脚本:

git config --global alias.<your alias> "git add. && git commit &1 && git pull"

以上命令将接受提交信息作为参数,并执行 git add、commit 和 pull 操作。


1
提交并拉取会在历史记录中添加一个合并提交,这会让事情变得混乱。如果您先存储,然后拉取,再弹出,最后再提交,则不会出现此问题。感谢您的理解。 - bobroxsox

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接