为什么 git 不能通过路径进行硬/软重置?

$ git reset -- <file_path>可以通过路径重置。

但是,$ git reset (--hard|--soft) <file_path>将报告如下错误:

Cannot do hard|soft reset with paths.
120450 次浏览

因为这没有意义(其他命令已经提供了这种功能) ,而且它减少了意外做错事情的可能性。

路径的“硬重置”是用 git checkout HEAD -- <path>完成的(检出文件的现有版本)。

对路径进行软重置是没有意义的。

路径的混合重置就是 git reset -- <path>所做的。

您可以使用 git checkout HEAD <path>完成您正在尝试做的事情。

也就是说,提供的错误消息对我来说没有意义(因为 git reset在子目录上工作得很好) ,我看不出为什么 git reset --hard不能完全按照您的要求做。

Git 复位——软 HEAD ~ 1文件名撤消提交,但更改保留在本地。文件名可以是——对于所有已提交的文件

问题 怎么做已经是 回答,我将解释 为什么的部分。

那么,基特复位做什么呢? 根据指定的参数,它可以做两件不同的事情:

  • 如果指定了路径,它将用提交文件(默认情况下是 HEAD)替换索引中匹配的文件。此操作根本不影响工作树,通常与 git add 相反。

  • 如果不指定路径,它会将当前分支头移动到指定的提交,并且 还有那个可选地将索引和工作树重置为该提交的状态。此 附加费行为由 mode 参数控制:
    - soft : 不要触摸索引和工作树。
    混合 (默认) : 重置索引但不重置工作树。
    - hard : 重置索引和工作树。
    还有其他选项,请参阅完整列表和一些用例的文档。

    当您没有指定提交时,它默认为 HEAD,所以 git reset --soft将什么也不做,因为它是一个命令,用于将标头移动到 HEAD (到其当前状态)。另一方面,由于 副作用git reset --hard是有意义的,它说移动头部到 HEAD还有重置索引和工作树到 HEAD。

    我认为现在应该很清楚为什么这个操作本质上不适用于特定的文件——它的初衷是移动一个分支头,重置工作树,而索引是次要功能。

解释

git reset手册列出了3种调用方式:

  • 2是文件明智的: 这些不影响工作树,但仅操作 <paths>指定的索引中的文件:

    • git reset [-q] [<tree-ish>] [--] <paths>..
    • git reset (--patch | -p) [<tree-ish>] [--] [<paths>...]
  • 1是提交明智的: 在引用的 <commit>中操作 所有文件,而 影响工作树:

    • git reset [<mode>] [<commit>]

没有仅对指定文件 还有操作的调用模式会影响工作树。

解决办法

如果你想两者兼得:

  • 重置文件的索引/缓存版本
  • 检查文件(例如,使工作树匹配索引和提交版本)

您可以在 git 配置文件中使用这个别名:

[alias]
reco   = !"cd \"${GIT_PREFIX:-.}\" && git reset \"$@\" && git checkout \"$@\" && git status --short #"  # Avoid: "fatal: Cannot do hard reset with paths."

然后,您可以执行下列操作之一:

$ git reco <paths>


$ git reco <branch/commit> <paths>


$ git reco -- <paths>

(Mnenonic for reco: reset & & ccheck-out)

这背后有一个非常重要的原因: ABC0和 reset的原理

在 Git 术语中,退房意味着“进入当前工作树”。使用 git checkout,我们可以用来自 任何区域的数据填充工作树,这些数据来自存储库中的提交,或者来自提交的单个文件,或者来自 集结地(这甚至是默认的)。

反过来,git重置没有这个角色。顾名思义,它将重置当前参考文献,但是 一直都是储存库作为源,独立于“到达”(——软、——混合或——硬)。

简介:

  • Checkout : From where (index/repo commit)-> working tree
  • 重置 : 回购提交-> 覆盖 HEAD (以及可选的索引和工作树)

因此,有点令人困惑的是 git reset COMMIT -- files的存在,因为只用一些文件“覆盖 HEAD”是没有意义的!

在没有官方解释的情况下,我只能推测 git 开发人员发现 reset仍然是放弃对暂存区域所做更改的命令的最佳名称,并且,鉴于唯一的数据源是存储库,那么“ 我们来扩展一下功能”代替了创建新命令。

因此,不知何故,git reset -- <files>已经有点例外: 它不会覆盖 HEAD。恕我直言,所有这些变化都是例外。即使我们可以构想一个 --hard版本,其他版本(例如 --soft)也没有意义。

确保在原点或上游(源)和实际分支之间加上斜线:

git reset --hard origin/branch

或者

git reset --hard upstream/branch`

这个答案现在被我在这里的更广泛的答案所引用: 所有关于检出 git 中的文件或目录的内容

为什么 git 不能通过路径进行硬/软重置?

它可以。它只需要几个命令,而不仅仅是一个。下面是如何:

摘要

警告: 在开始这个过程之前,git status应该是完全干净的!否则,您将面临永久丢失 git status所显示的任何未提交更改的风险,因为 git clean -fd‘ f‘ force 删除了当前工作树(文件系统)中的所有文件和‘ d’目录,但这些文件和目录在提交或分支 commit_hash中指定的路径中是 没有。因此,任何没有提交获得 永远失去了就好像你已经使用了 rm对它!

1. 如何通过路径重置 --soft:

# How to "soft reset" "path/to/some/file_or_dir" to its state exactly as it was
# at commit or branch `commit_hash`.
#
# SEE WARNING ABOVE!


git reset commit_hash -- path/to/some/file_or_dir
git checkout-index -fa
git clean -fd  # SEE WARNING ABOVE!

2. 如何通过路径重置 --hard:

# How to "hard reset" "path/to/some/file_or_dir" to its state exactly as it was
# at commit or branch `commit_hash`.
#
# SEE WARNING ABOVE!


git reset commit_hash -- path/to/some/file_or_dir
git checkout-index -fa
git clean -fd  # SEE WARNING ABOVE!
git commit -m "hard reset path/to/some/file_or_dir to its state \
as it was at commit_hash"

完整答案:

在 git 版本2.17.1中进行了测试(请使用 git --version检查您的版本)。

为什么 git 不能通过路径进行硬/软重置?

我不知道确切的 为什么,但我猜想,因为 git要么作出了一个开发决定,你和我都不同意,或者因为 git只是不完整,仍然需要实施这一点。另请参阅下面“通过路径重置 --hard”一节中提供的有关此问题的附加见解。一个真正的 --hard复位在一个单一的路径不能像一个 --hard复位整个分支一样。

但是,我们可以通过几个命令手动完成所需的行为。请注意,单独的 git checkout commit_hash -- path/to/some/file_or_dir不是其中之一,由于原因解释如下。

在继续之前,您应该了解 git reset做什么,什么是 工作树索引,以及 --soft--hard通常对 git reset做什么。如果您对这些主题有任何疑问,请先阅读下面的“ 背景知识”部分。

如何做一个 --soft--hard的 git 路径重置

又名: 如何完成这些无效命令中的任何一个的等效命令 手动操作:

# hypothetical commands not allowed in git, since `git` does NOT
# allow `--soft` or `--hard` resets on paths


git reset --soft commit_hash -- path/to/some/file_or_dir
git reset --hard commit_hash -- path/to/some/file_or_dir

因为上面的命令是不允许的,而且因为这个 checkout命令不能执行 可以之上的那些假设的命令,因为这个 checkout命令也不能执行 删除文件或者本地存在的文件夹,而这些文件或者文件夹不在 commit_hash中:

git checkout commit_hash -- path/to/some/file_or_dir

然后,你就可以完成上面假设的命令和下面这些命令一起做的事情。

1. 通过路径重置 --soft

描述: 使本地 path/to/some/file_or_dircommit_hash中的 file_or_dir相同,同时删除本地路径目录(如果 path/to/some/file_or_dir是一个目录)中不存在于 commit_hash目录中的文件。最后,将所有更改保留为“暂存”(添加但未提交)。

git reset commit_hash -- path/to/some/file_or_dir
git checkout-index -fa
git clean -fd

如果允许这样的命令,那么上面的结果正是我所期望的在路径上重置 --soft的结果。

有关 git checkout-index -fagit clean -fd部分的更多信息,请参见我的另一个答案: 使用 git,如何将工作树(本地文件系统状态)重置为索引(“暂存”文件)的状态?

请注意 您应该在每个单独的命令之后运行 git status,以查看每个命令在执行时的操作。下面是对各个命令的解释:

# Stage some changes in path/to/some/file_or_dir, by adding them to the index,
# to show how your local path/to/some/file_or_dir SHOULD look in order to
# match what it looks like at `commit_hash`, but do NOT actually _make_
# those changes in yourlocal file system. Rather, simply "untrack" files
# which should be deleted, and do NOT stage for commit changes which should
# NOT have been made in order to match what's in `commit_hash`.
git reset commit_hash -- path/to/some/file_or_dir
git status


# Now, delete and discard all your unstaged changes.


# First, copy your index (staged/added changes) to your working file
# tree (local file system). See this answer for these details:
# https://stackoverflow.com/a/66589020/4561887
# and https://stackoverflow.com/a/12184274/4561887
git checkout-index -fa
git status


# 'f'orce clean, including 'd'irectories. This means to **delete**
# untracked local files and folders. The `git reset` command above
# is what made these untracked. `git clean -fd` is what actually
# removes them.
git clean -fd
git status

2. 通过路径重置 --hard

描述: 执行上面的 --soft重置步骤,然后提交更改:

git reset commit_hash -- path/to/some/file_or_dir
git checkout-index -fa
git clean -fd
git commit -m "hard reset path/to/some/file_or_dir to its state \
as it was at commit_hash"

现在,为了更好的测量和最后的检查,您可以运行 git reset commit_hash -- path/to/some/file_or_dir,然后是 git status。您将看到,git status显示没有任何变化,因为 --hard通过路径重置在上面是成功的,所以这个对 git reset commit_hash -- path/to/some/file_or_dir的调用什么也没有做。太好了,成功了!

这些结果与真正的 --hard重置不同,因为真正的 --hard重置不会向 git commit添加新的提交。相反,它只是强制当前签出的分支指向另一个 commit_hash。但是,当“硬重置”只有几个文件或路径像这样,你 不行只是移动你的分支指针指向另一个 commit_hash,所以真的没有其他方法来实现一个合理的行为为这个命令,除了添加一个新的提交与这些“未添加”,或“重置”,改变,如上所述。

这也可能是 git本身不支持通过路径重置 --hard的原因; 也许是因为通过路径重置 --hard需要添加一个新的提交,这与通常的 --hard不添加新提交的行为略有偏差,而只是“重置”到(移动一个分支指针到)一个给定的提交。

但是,这并不能解释为什么 git至少不允许通过路径进行 --soft git 重置,因为这对我来说似乎更加标准。

背景知识

1. git基本术语

在阅读 man git reset页面时,您需要了解一些 git 术语:

  1. 工作树 = 本地文件系统; 这指的是在终端或 GUI 文件管理器(如 nemonautilusthunar)中导航文件系统时所看到的文件和文件夹的状态。
  2. <tree-ish> = 提交散列或分支名称
  3. Index = 运行 git status时看到的 绿色的。这些都是所有的变化是 git added (“阶段”) ,但尚未提交。当您运行 git add some_file时,通过将其更改移动到 索引,您正在“分段”some_file。现在可以说 some_file是“添加的”、“阶段的”或“在索引中”(都是一样的)。

2. man git reset

当你阅读这些解决方案时,注意到 man git reset的状态(强调后加)是很有见地和有帮助的:

git reset <paths>git add <paths>相反。

换句话说,git reset commit_hash -- some_file_or_dir可以“取消添加”,或者添加与 some_file_or_dir相反的更改(从而取消这些更改) ,就像在提交或分支 commit_hash中所包含的那样,同时也可以将 HEAD设置为指向 commit_hash,或者就像它指向指定文件或目录的 commit_hash一样(同样,通过添加必要的更改,使 工作树中的 some_file_or_dir看起来像 commit_hash中的 some_file_or_dir

此外,在 git行话中,“工作树”意味着“你的本地文件系统”(因为你的计算机通常会在文件夹管理器中或在终端导航时看到文件和文件夹) ,而 “索引”“索引文件”意味着“当你进行 git add时文件所在的位置,或者‘暂存’它们。”当您运行 git status时,所有显示为绿色的文件都是“暂存的”,或者在“索引”或“索引文件”(同样的东西)中。(资料来源: 在 Git 中,HEAD、工作树和索引之间的区别是什么?)。

现在,考虑到这一点,以下是 man git reset的一些重要部分:

git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]

在第三张表格[上表所示的表格]中, 将当前分支头(HEAD)设置为 <commit>,可选地修改索引和工作树 <tree-ish>/<commit>默认为所有形式的 HEAD

以及:

git reset [-q] [<tree-ish>] [--] <paths>...
This form resets the index entries for all <paths> to
their state at <tree-ish>. (It does not affect the working
tree or the current branch.)
    

**This means that `git reset <paths>` is the opposite of `git
add <paths>`.**
      

After running git reset <paths> to update the index entry,
you can use git-checkout(1) to check the contents out of
the index to the working tree. Alternatively, using git-
checkout(1) and specifying a commit, you can copy the
contents of a path out of a commit to the index and to the
working tree in one go.

以及:

git reset [<mode>] [<commit>]
This form resets the current branch head to <commit> and
possibly updates the index (resetting it to the tree of
<commit>) and the working tree depending on <mode>. If
<mode> is omitted, defaults to "--mixed". The <mode> must
be one of the following:
     

--soft
Does not touch the index file or the working tree at
all (but resets the head to <commit>, just like all
modes do). This leaves all your changed files "Changes
to be committed", as git status would put it.
     

--mixed
Resets the index but not the working tree (i.e., the
changed files are preserved but not marked for commit)
and reports what has not been updated. This is the
default action.
     

If -N is specified, removed paths are marked as
intent-to-add (see git-add(1)).
     

--hard
Resets the index and working tree. Any changes to
tracked files in the working tree since <commit> are
discarded.

3. 你也应该熟悉 man git checkout-index页面。

请记住,“索引”包含所有添加的或“暂存”的文件(在运行 git status时显示为 绿色的) ,而“工作树”指的是实际的本地文件系统(在运行 git status时也包含显示为 红色的的更改)。

在最基本的层面上,它是这样做的:

来自 man git checkout-index:

NAME
git-checkout-index - Copy files from the index to the working tree

以及:

-f, --force
forces overwrite of existing files


-a, --all
checks out all files in the index. Cannot be used together with
explicit filenames.

参考文献:

  1. [我的答案——直接适用,我需要能够在上面写出整个答案的前身答案! ] 使用 git,如何将工作树(本地文件系统状态)重置为索引(“分段”文件)的状态?
  2. 如何从当前 Git 工作树中删除本地(未跟踪的)文件
  3. 如何在 Git 中丢弃未暂存的更改?
  4. 在 Git 中,HEAD、工作树和索引之间的区别是什么?

相关阅读:

  1. [我的回答] 如何从另一个分支获取一个文件?