你如何告诉git仅存储索引?

99

我刚刚使用了git add -p将一堆更改添加到索引中,然后我意识到我错过了一个应该放在上一个提交中的更改。

现在我不能使用commit --amend,因为我已经将所有这些新更改添加到了索引中,而且我不想使用'git reset'将它们全部从索引中删除,因为重新添加它们需要很长时间。

我需要的是像git stash这样的东西,只会隐藏索引-它应该让工作文件保持原样。然后我可以隐藏索引,添加缺少的更改,提交它,然后弹出隐藏并使我的索引恢复到原来的状态。

看起来git stash似乎无法做到这一点,但我是否遗漏了什么?谢谢!


2
我认为你只需要使用git commit命令 - 它会将你的索引(index)创建成一个提交(commit)。在这之后,Kevin Ballard的回答解释了如何合理地重写历史记录... - Mark Longair
14
听起来你想要类似于“git stash --keep-index”的东西,但是针对工作目录,也就是“--working-tree”。我也是这样想的,但是这个功能并不存在。 - Leif Gruenwoldt
从版本2.35开始,git在git stash push子命令中提供了--staged选项。 (为了方便引用@user3840170的答案。) - tom
16个回答

2
以下是我以前编写的一个小脚本,可以实现这个功能:
(注意:我最初是在https://dev59.com/m3A75IYBdhLWcg3w6Nhj#17137669上发布的,但它似乎也适用于此处。虽然这些不完全是重复的问题,但我认为它在两种情况下都可以作为可能的答案)
#!/bin/sh

# first, go to the root of the git repo
cd `git rev-parse --show-toplevel`

# create a commit with only the stuff in staging
INDEXTREE=`git write-tree`
INDEXCOMMIT=`echo "" | git commit-tree $INDEXTREE -p HEAD`

# create a child commit with the changes in the working tree
git add -A
WORKINGTREE=`git write-tree`
WORKINGCOMMIT=`echo "" | git commit-tree $WORKINGTREE -p $INDEXCOMMIT`

# get back to a clean state with no changes, staged or otherwise
git reset -q --hard

# Cherry-pick the index changes back to the index, and stash.
# This cherry-pick is guaranteed to suceed
git cherry-pick -n $INDEXCOMMIT
git stash

# Now cherry-pick the working tree changes. This cherry-pick may fail
# due to conflicts
git cherry-pick -n $WORKINGCOMMIT

CONFLICTS=`git ls-files -u`
if test -z "$CONFLICTS"; then
    # If there are no conflicts, it's safe to reset, so that
    # any previously unstaged changes remain unstaged
    #
    # However, if there are conflicts, then we don't want to reset the files
    # and lose the merge/conflict info.
    git reset -q
fi

您可以将上述脚本保存为git-stash-index,并将其放在您的路径中,然后可以通过git stash-index命令进行调用。
# <hack hack hack>
git add <files that you want to stash>
git stash-index

现在stash中包含一个新条目,仅包含您暂存的更改,而您的工作树仍然包含任何未暂存的更改。

主要的问题是,如果工作树包含依赖于索引更改的更改,则您可能无法干净地删除索引的更改而不会导致冲突。

在这种情况下,任何此类冲突都将保留在通常的未合并冲突状态中,类似于cherry-pick / merge之后。

例如:

git init
echo blah >> "blah"
git add -A
git commit -m "blah"

echo "another blah" >> blah
git add -A
echo "yet another blah" >> blah

# now HEAD contains "blah", the index contains "blah\nanother blah"
# and the working tree contains "blah\nanother blah\nyetanother blah"

git stash-index

# A new stash is created containing "blah\nanother blah", and we are
# left with a merge conflict, which can be resolved to produce
# "blah\nyet another blah"

我在使用Windows操作系统,并成功将此内容与Windows快捷方式结合使用以运行bash脚本。个人建议在此脚本中添加类似于echo "INDEXTREE: $INDEXTREE"的命令,以便在出现问题时能够查看其位置(但您需要一种方法来保持窗口打开)。 - Qwertie

2

我曾经遇到过这种情况,最终使用了一些旨在脚本化的git stash选项。Git stash create可以让您创建一个(但不应用)存储对象(其中包括索引)。Git stash store将其添加到git stash堆栈中,而不会隐式执行git stash save所做的git reset --hard。这实际上允许您通过手动重置索引来存储索引:

# This will add an entry to your git stash stack, but *not* modify anything
git stash store -m "Stashed index" $(git stash create)

# This will reset the index, without touching the workspace.
git reset --mixed 

现在,您已经成功地隐藏了您的索引,并且在完成后可以使用 "git stash pop --index" 进行操作。

需要注意的是,所创建的储藏对象包括了工作区和索引中修改的文件,即使你只关心索引,所以当你尝试从栈中弹出(pop)时,可能会出现冲突,即使你使用了 --index 标志(这意味着 "也应用来自暂存区(index)的内容",而不是 "仅应用来自暂存区(index)的内容")。在这种情况下,您可以在弹出之前执行 "git reset --hard" (因为您正在重置的更改恰好也是您立即弹出或应用的更改,所以它并不像看起来那么危险。)


0

假设您的工作副本包含一些脏更改和索引中的更改。

将更改从索引移动到存储,我们使用选项-p,它允许忽略索引中的更改。因此,我们需要额外的步骤来“反转”操作:首先撤销脏更改,然后存储索引,最后返回脏更改:

$ git stash push -m dirty -p
$ git stash push -m index

$ git stash list
stash@{0}: On master: index
stash@{1}: On master: dirty

$ git stash pop stash@{1}

由于在交互式块选择模式下接受每个更改可能会很无聊,因此有一个交互式选项:

a-将此块和文件中所有后续块隐藏


0

如果您将准备提交的文件添加到索引中,但只想暂存它们,可以使用以下函数。

>> momomo.com.git.stash.added

或者

>> momomo.com.git.stash.added "name of the  stash"

Bash函数:

momomo.com.git.stash.added() {
    local name="$1";

    if [[ "${name}" == "" ]]; then
        name="$(date)"
    fi

    # This will stash everything, but let the added ones remain
    # stash@{1} 
    git stash --keep-index

    # This will stash only the remaining ones
    # @stash@{0}
    git stash save "${name}"

    # Restore everything by applying the first push stash which is now at index 1
    git stash apply stash@{1}

    # Then drop it
    git stash drop stash@{1}

    # At the top of the stash should now be only the originally indexed files
}

请注意,您现在需要重新将事物添加到索引中。

不要在没有测试前面的命令是否成功的情况下执行 git stash drop stash@{1} - Kaz
很容易修改,我猜。不确定存储是否会失败。 - mjs

0

对于大多数情况来说,没有必要提供如此复杂的答案。假设我没有弄错并且理解了情况。

正如所说,Magit 对于这种特定情况具有完美的功能。您可以使用类似的工作流程仅使用 git 命令来完成 Magit 的某些操作,例如:

git add -u  # Add all the changed files to stage
git restore --staged -p .  # Add what you don't want to the unstaged section where you can stash
# Then do the classic `-k`
git stash -k  # Stash all the unstaged hunks
git restore --staged .  # unstage everything and continue with your development

这个想法是做你想要的相反操作,然后将未暂存的内容与你的暂存内容交换,因为我所知道的没有逆转-k只暂存“索引”。


-1

这是一个(比较)简单的食谱(至少容易跟随):

git commit -m "Will come back and this will be removed, no worries"
git stash save "will drop it, no worries"
git reset --soft HEAD~1 # getting rid of previous revision
git stash save "this is what I really wanted to stash, set the right message"
git stash pop stash@{1} # get files that I didn't want to stash on the working tree

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