简体   繁体   English

Git stash 只有在之前成功隐藏时才会弹出

[英]Git stash pop only if successfully stashed before

Part of my workflow involves doing a lot of this:我的工作流程的一部分涉及做很多这样的事情:

  • git stash changes git 存储更改
  • git pull git 拉
  • pop stashed changes弹出隐藏的更改
  • launch mergetool to resolve conflicts启动合并工具以解决冲突

I am trying to write a script to do all of these things at once, so I can just call it from the terminal.我正在尝试编写一个脚本来一次完成所有这些事情,所以我可以从终端调用它。

#!/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

The problem I'm running into is that if I run this accidentally, and there are no changes to stash, the git stash pop applies some (usually super old) stash.我遇到的问题是,如果我不小心运行了这个,并且没有对存储进行更改,那么git stash pop会应用一些(通常是超级旧的)存储。 What I want to do is run git stash pop only if I actually stashed something before.我想要做的是只有在我之前实际隐藏过某些东西时才运行git stash pop Is there a way to do this?有没有办法做到这一点?

Edit, July 2022 : Time (and Git) have moved on and depending on your Git version much of the below is not necessarily accurate any more.编辑,2022 年 7 月:时间(和 Git)已经过去,根据您的 Git 版本,以下大部分内容不再一定准确。 One of the most important changes is that there are now git stash push and git stash create commands.最重要的变化之一是现在有git stash pushgit stash create命令。 See the footnotes and comments.请参阅脚注和评论。


As Xavier Álvarez noted and codeWizard wrote , it's probably wiser to avoid git stash entirely here.正如Xavier Álvarez 所指出的和codeWizard 所写的那样,在这里完全避免git stash可能更明智。 For instance I'd look at using separate git fetch and git rebase steps (see Xavier's answer), and note that rebase now has --autostash which essentially does just what you want, it's just not directly available via the git pull convenience script.例如,我会考虑使用单独的git fetchgit rebase步骤(请参阅 Xavier 的答案),并注意 rebase 现在具有--autostash ,它基本上可以满足您的需求,只是不能通过git pull便利脚本直接使用。 1 1

That said, there is a way to do what you've asked.也就是说,一种方法可以做你所要求的。 It's a little bit tricky.这有点棘手。 It would be a lot easier if git stash save had a "force" option similar to git commit --allow-empty , but it doesn't have such an option.如果git stash save有一个类似于git commit --allow-empty的“强制”选项会容易得多,但它没有这样的选项。 2 Instead, what you can do is detect whether git stash save pushed a new stash. 2相反,您可以做的是检测git stash save是否推送了新的存储。 This too would be a lot easier if git stash save had an exit status indicating whether it pushed a stash, but again it doesn't.如果git stash save有一个退出状态指示它是否推送了一个存储,这也会容易得多,但它又没有。 That means we must rely on a different trick entirely.这意味着我们必须完全依赖一个不同的技巧。 We start with two facts: git rev-parse finds SHA-1s from "references", and git stash uses one particular reference.我们从两个事实开始: git rev-parse从“引用”中找到 SHA-1,而git stash使用一个特定的引用。

The git rev-parse command will translate any reference into an SHA-1: git rev-parse命令会将任何引用转换为 SHA-1:

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

A reference is just a name, usually starting with refs , that names some SHA-1 ID.引用只是一个名称,通常以refs开头,用于命名一些 SHA-1 ID。 The most common ones are branches: refs/heads/ branch .最常见的是分支: refs/heads/ branch You may have also used tags: refs/tags/ tag , and you have probably used remote-tracking branches like origin/master , which is short for the full name, refs/remotes/origin/master .您可能还使用过标签: refs/tags/ tag ,并且您可能使用过远程跟踪分支,例如origin/master ,它是全名refs/remotes/origin/master的缩写。

The stash script uses refs/stash , so we can simply run git rev-parse refs/stash . stash脚本使用refs/stash ,所以我们可以简单地运行git rev-parse refs/stash 3 We want to run it before git stash save , then again after git stash save . 3我们想在git stash save git stash save再次运行。 If the output changes, the git stash save step must have pushed a new stash onto the stash stack.如果输出更改,则git stash save步骤必须已将新的存储推入存储堆栈。

We do have to be a bit careful since if the stash stack is empty (because the last stash was popped or dropped earlier, or no stashes have ever been created yet), git rev-parse will give an error message and produce no SHA-1:我们必须要小心一点,因为如果存储堆栈为空(因为最后一个存储已被弹出或较早删除,或者尚未创建存储), 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>...]'

Hence we actually need git rev-parse -q --verify refs/stash , which silently produces nothing if the reference does not exist, and then we just need a little care in any shell script that uses the result:因此,我们实际上需要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

1 The git pull command is basically a short-hand for git fetch followed by git merge , or, if you tell it, to run git fetch followed by the usually-more-appropriate git rebase . 1 git pull命令基本上是git fetch后跟git merge的简写,或者,如果你告诉它,运行git fetch后跟通常更合适的git rebase If you break it up into its two separate steps, though, you get a lot more control, along with the ability to inspect the incoming changes before merging or rebasing.但是,如果您将其分成两个独立的步骤,您将获得更多的控制权,并且能够在合并或变基之前检查传入的更改。

Edit, July 2022 : git pull is no longer a script and autostash works with it now.编辑,2022 年 7 月git pull不再是脚本,autostash 现在可以使用它。 There were intermediate transition states along the way.沿途有中间过渡状态。

2 You can effectively force stash creation using the relatively new create and store subcommands: create a stash, then store the resulting SHA-1, and you've forced a stash-save even if there is nothing to stash. 2您可以使用相对较新的createstore子命令有效地强制创建 stash:创建一个 stash,然后存储生成的 SHA-1,即使没有要存储的内容,您也已强制进行 stash-save。 But not everyone is up to date with a recent git, so for scripts, it's probably wiser to rely on the old way (or as noted earlier, not use stash at all, especially since it has various minor but annoying bugs, in various versions of Git).但并不是每个人都使用最新的 git,因此对于脚本,依赖旧方法可能更明智(或者如前所述,根本不使用 stash,特别是因为它有各种小但烦人的错误,在各种版本中的 Git)。

Edit, July 2022 : git stash is no longer a script and has new options and verbs.编辑,2022 年 7 月git stash不再是脚本,并且具有新的选项和动词。 See comments.看评论。

3 It's wise to spell out the full name, because git rev-parse stash will first look for a branch named stash . 3拼出全名是明智的,因为git rev-parse stash将首先查找名为stash分支 This is true in general with all references when writing aliases or scripts: spell out full names (and use -- syntax as necessary) to make sure Git doesn't do what it thinks you meant, in odd corner cases.在编写别名或脚本时,所有引用通常都是如此:拼出全名(并根据需要使用--语法)以确保 Git 在奇怪的极端情况下不会按照您的意思行事

Reading your explanation of why do you do what you do I'd probably go for a completely different approach.阅读您对为什么要做您所做的事情的解释,我可能会采用完全不同的方法。 First, I'd fetch the remote you want to use:首先,我会获取您要使用的遥控器:

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

And then, I'd carry out a rebase against a specific branch of that remote:然后,我会对该遥控器的特定分支进行变基

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

This would merge your changes and you'd still be able to solve any conflicts.这将合并您的更改,您仍然可以解决任何冲突。

If you don't like this approach, you might want to use git pull with the --no-commit flag instead:如果您不喜欢这种方法,您可能希望使用带有--no-commit标志的git pull

git pull --no-commit

This way no autocommit would be performed after the merge.这样合并后不会执行自动提交。

When using git stash save <messsage> , the message you pass will show upon a successful save.使用git stash save <messsage>时,您传递的消息将在成功保存后显示。

So one trick is to generate a timestamp, which will be used as the message, and drop the most recent stash if the timestamp was found in the resulting message.所以一个技巧是生成一个时间戳,它将用作消息,如果在结果消息中找到时间戳,则删除最近的存储。

One line:一条线:

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!"

Expanded:扩展:

# 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!"

How about running a git status first?先运行一个 git status 怎么样? If there are local changes, run the stash command.如果有本地更改,请运行 stash 命令。 If not, skip it.如果没有,请跳过它。 Save this result in a bool and then don't run the pop if there was no fresh stash.将此结果保存在 bool 中,如果没有新的存储,则不要运行 pop。

I was looking for something similar for automating merging master.我正在寻找类似的东西来自动化合并大师。 I ended up just creating an empty file with a unique name.我最终只是创建了一个具有唯一名称的空文件。 The next step was to include untracked files when stashing ( stash -u ).下一步是在存储时包含未跟踪的文件( stash -u )。 Now I know I can always pop, since I am creating something to stash.现在我知道我总是可以弹出,因为我正在创造一些东西来藏匿。 To finish up I delete the new file I created once everything else is done.最后,我删除了其他所有操作完成后创建的新文件。

I then created the following aliases:然后我创建了以下别名:

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

...and the actual aliases: ...以及实际的别名:

[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 *从被黑客窃取

I got the same problem and resolved for Windows using the following simple script:我遇到了同样的问题,并使用以下简单脚本解决了 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" )

you should try to avoid stash if you are doing many things in as script, instead simply commit your code and then pull your changes.如果你在脚本中做很多事情,你应该尽量避免隐藏,而是简单地提交你的代码,然后拉你的更改。

You can also write an alias instead of a script:您还可以编写别名而不是脚本:

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

The above command will accept the commit message as parameter and will do git add , commit & pull上面的命令将接受提交消息作为参数并执行 git add , commit & pull

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM