git提交阶段性更改的子集(按文件名)

git提交阶段性更改的子集(按文件名),git,Git,我有以下情况: $ git status --porcelain M A M B A C D D AM E MM F MM G 我只想提交F和G的阶段性更改。我不想提交F和G的未老化更改,也不想提交A->E的任何更改 不起作用的事情: # commits all staged and F, G unstaged git commit F G # same as above git commit -o F G # commits all staged + unstaged F, G

我有以下情况:

$ git status --porcelain
 M A
M  B
A  C
D  D
AM E
MM F
MM G
我只想提交
F
G
的阶段性更改。我不想提交
F
G
的未老化更改,也不想提交
A
->
E
的任何更改

不起作用的事情:

# commits all staged and F, G unstaged
git commit F G

# same as above
git commit -o F G

# commits all staged + unstaged F, G (basically same as above)
git commit -i F G

# interactively add more to the index, keeps all already staged
git commit -p

# as above but limits interactive choices to F, G unstaged changes
git commit -p F G

# almost, but stash pop results in merge conflicts for F, G
git stash -k
git commit F G
git stash pop

更新:我知道这可以通过额外的直接文件副本来完成。但是我想要的是一些不太需要人工的东西。

您的情况可以通过简单的命令手动处理:

# if you care to distinguish between "content of E when it was added as a new file"
# and "new modified content of E" :
cp E E.new
git checkout E

# then :
git reset B C D E

# only F and G are still staged at this point :
git commit

# re-stage B C D E :
git add B C D E

# again, if you care about diff between E.new and E :
mv E.new E

如果您使用的是git版本2.23或更高版本:“还原索引或工作树”可以使用
git restore


如果您没有git restore,以下是一种系统化的方法:

# 1. use a working branch
$ git checkout -b wip

# 2. create a commit with your currently staged changes
$ git commit
# say created commit id is aacf32b

# 3. create a commit with the non staged changes
$ git add -u
$ git commit
# say created commit id is bb3498d

# 4. go back to the branch you actually wanted to modify :
$ git checkout master

# 5. use commit created at 2. to fetch the staged content you wanted to commit :
$ git checkout aacf32b -- F G
$ git commit

# 6. restore the remaining staged content on top of this commit :
$ git checkout aacf32b -- .
# as you noted : you need to apply one special treatment for deleted files :
$ git diff --diff-filter=D --name-only HEAD aacf32b | xargs git rm
$ git commit -m "temp commit - index"

# 7. restore the modified content you saved at step 3. :
$ git checkout bb3498d -- .
# likewise : manual step to also apply "delete" actions :
$ git diff --diff-filter=D --name-only HEAD bb3498d | xargs rm
$ git reset HEAD

# 8. return to the commit you wanted (the one created at step 5.),
#    while keeping the staged content in the index :
$ git reset --soft HEAD^

# check that everything is fine, then drop the wip branch
$ git branch -D wip

最简单的方法如下:

$git stash#(1)
$git checkout stash@{0}^2 F G#(2)
#索引现在只包含F&G的缓存更改。。。按要求提交
$git stash pop--索引#(3)
在上面的
(1)
中进行隐藏之后,如果执行
git log--oneline--graph--all--decoration
,您可以看到stash命令创建了两个提交。最上面的提交是隐藏前工作副本的状态。这个孩子有两个父母。第一个是隐藏所基于的前一个头部,第二个(我们的
^2
位于上面的
(2)
上)具有索引中的状态

(2)
中执行
签出
后,您仅对指定的文件
F
G
进行索引修改

最后一个命令,
(3)
,重新应用存储,保持索引中缓存状态与工作副本状态的优先分离。这是您以前状态的可选恢复。

注意:首先备份您的工作树,以防出现错误,因为可能会出现错误并丢失数据

你应该考虑使用<代码> Git Stase<代码>这里:

# Stash all changes, but keeps the staged changes.
git stash --keep-index

# Unstage and discard the changes in the unwanted files.
git reset B C D E
git checkout B C D E

# Now you are left with the changes in F and G.
git commit
完成后,您可以执行
git stash pop
来重新应用以前所做的所有更改。以前阶段化的更改应该再次阶段化,但这次F和G中先前阶段化的更改没有ops(您不应该在这里看到任何差异)


这有点复杂,但现在最好使用Git,因为Git commit没有选择权选择性地提交特定文件的阶段性更改。

您在这里做的大多数事情都是相当手动的,包括所有的
Git stash
方法,它实际上只是从当前索引和当前工作树中进行两次提交,然后使用
git reset--hard
或等效工具将所有内容恢复到它在
HEAD
中显示的方式。来自
git stash
的两个提交不在任何分支上,但可以稍后使用
git stash apply
进行恢复。但这不是我在这里要做的。首先考虑手动完成这个操作,然后,如果你这样做,通常会把手动过程转换成脚本(见下面的例子)。 一个关键是要注意只有一个可分辨索引(“索引”),但您可以有其他索引文件。您可以拥有任意数量的临时索引文件。将环境变量
GIT_INDEX_FILE
设置为不存在的文件名,例如:

export GIT_INDEX_FILE=$(realpath $(git rev-parse --git-dir))/index$$
(这取决于外壳的PID是否具有唯一的文件名)或:

您现在在这个临时索引中没有任何暂存内容。从当前提交中填充,使其与当前提交匹配:

git read-tree HEAD
所有文件现在都被重新设置为匹配
。现在是最困难的部分:

I want to commit only the staged changes for F and G
这些是存储库中的blob和/或(主)索引中的模式更改。我们必须取消GIT_INDEX_文件的设置才能读取它们。我们可以在创建临时索引之前执行此操作,但现在要执行此操作,我们可以使用一个子shell,在其中取消设置环境变量:

(unset GIT_INDEX_FILE && git ls-file --staged F G) | git update-index --index-info
现在,我们的临时索引中有文件
F
G
,用于提交,我们可以运行
git commit
,无需任何选项,也可以使用
-m
或类似工具从该临时索引提交

完成新提交后,只需取消设置
GIT\u INDEX\u文件
(尽管要进行清理,还应删除临时索引,
rm-f$GIT\u INDEX\u文件
)。如果在脚本中执行所有这些操作,则退出脚本(删除任何临时文件后)就足够了

未经测试但考虑到可能出现的拐角情况的基本脚本如下所示:

#! /bin/sh

case $# in
0) echo "usage: git onlycommit file1 ... fileN"; exit 1;;
esac

# exit (and clean up) on error
set -e
tf=$(mktemp)
rm -f "$tf"
trap "rm -f \"$tf\"" 0 1 2 3 15

# if GIT_INDEX_FILE is already set, make our reset use that value;
# otherwise, make our reset just unset it
if [ "${GIT_INDEX_FILE+true}" = true ]; then
    reset_index="export GIT_INDEX_FILE=\"${GIT_INDEX_FILE}"\"
else
    reset_index="unset GIT_INDEX_FILE"
fi
export GIT_INDEX_FILE="$tf"

git read-tree HEAD
(eval $reset_index; git ls-files --staged "$@") | git update-index --index-info
git commit
这应该进行测试,也可能是虚构的,例如,它可能应该首先测试所有参数文件是否标记为“staged for commit”,即当前索引项与
HEAD中的索引项不同。它也应该确保你不在一个冲突的合并中,或者至少,所选文件不是.


如果您调用它
git onlycommit
并将其放入
$PATH
中,您应该能够将其作为
git onlycommit F G
运行,我认为这不太可能:(如果有人能提供答案,我会很高兴。为什么不
git reset
将所有内容移出暂存区,然后只
git add F G
添加要提交的文件,提交它们,然后
git add
为下一次提交而添加其余文件?@R10t——因为在其中捕获了大量信息更改在索引中,我不想丢失它。我的实际情况是,我有几十个文件,其中包含各种阶段性和非阶段性更改。然后我意识到我只想提交我已阶段性更改的一部分。我知道如何通过复制文件手动执行。我有一大堆处于a-E状态的文件。我澄清了目前,我正在寻找一种不那么手工的方法。我在第2步和第3步描述的创建提交的一种可能更短的方法是使用“git”
#! /bin/sh

case $# in
0) echo "usage: git onlycommit file1 ... fileN"; exit 1;;
esac

# exit (and clean up) on error
set -e
tf=$(mktemp)
rm -f "$tf"
trap "rm -f \"$tf\"" 0 1 2 3 15

# if GIT_INDEX_FILE is already set, make our reset use that value;
# otherwise, make our reset just unset it
if [ "${GIT_INDEX_FILE+true}" = true ]; then
    reset_index="export GIT_INDEX_FILE=\"${GIT_INDEX_FILE}"\"
else
    reset_index="unset GIT_INDEX_FILE"
fi
export GIT_INDEX_FILE="$tf"

git read-tree HEAD
(eval $reset_index; git ls-files --staged "$@") | git update-index --index-info
git commit