“git checkout - 。”有什么区别?和“git reset HEAD --hard”? [英] What is the difference between "git checkout -- ." and "git reset HEAD --hard"?

查看:209
本文介绍了“git checkout - 。”有什么区别?和“git reset HEAD --hard”?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这不是一个关于 - 所做的一般性问题,就像在标记的副本中一样。这是一个特定于git的问题,要求澄清上述命令之间的操作差异。



如果我想清除当前目录而不存储或提交,我通常使用这些命令:

  git reset HEAD --hard 
git clean -fd
code>

同事也提到了使用这个命令:

  git checkout  - 。 

这对谷歌来说是一个困难的命令,而且从git文档中我不清楚这个命令实际上是什么确实。它似乎是后面提到的使用手册之一。



猜测它复制 git reset HEAD --hard ,但与我已经使用的命令相比,它究竟做了什么?

是否复制其中一个或两个命令,还是类似但略微不同?

解决方案

首先,让我们只讨论双连字符或双重短划线,以避开它(尤其是因为此问题不再具有标记为重复)。



Git主要在 POSIX批准的方式(见准则10),以表示选项参数与非选项参数之间的分界线。因为 git checkout 接受分支名称,如 git checkout master ,以及文件(路径)名称,如 git checkout README.txt ,你可以使用 - 强制Git解释 - 作为文件名,即使它本来是一个有效的分支名称。也就是说,如果您同时拥有名为 master 分支文件

  git checkout master 

会检查分支,但:

  git checkout  -  master 
$ b

会检出文件(从当前的索引)令人困惑。 / p>

分支机构,索引和文件,哦,我的



接下来,我们需要解决一个 git的怪癖结帐。从文档可以看到,有许多模式的 git checkout (文档列出了synposis中的六个单独的调用!)。有各种各样的rants(不同的质量:史蒂夫班纳特的实际上对于Git糟糕的用户体验模型非常有用,在我看来,尽管自然我并不认同它100%:-)),其中包括 git checkout 有太多的操作模式。



特别是,您可以 git checkout a 分支(转换分支)或 git checkout 一个或多个文件。后者从特定的提交或索引中提取文件。当Git从提交中提取文件时,它首先将它们复制到索引中,然后将它们从索引复制到到工作树上 。



这个序列有一个潜在的实现原因,但事实表明它是一个关键因素。我们需要了解很多关于Git的索引,因为它们都是 git reset 方式。



我认为,绘制一个三向图或表来说明当前或 HEAD -commit,索引和工作树。假设:


  • 有两个普通的提交文件 README.md file.txt ;

  • 有一个新的, git add -ed但uncommitted new.txt ;

  • 有一个名为 rmd.txt git rm -ed但未提交;
  • ,并且有一个未跟踪的文件,名为 untr。 txt



每个实体 - HEAD 提交,索引和工作树 - 现在拥有三个文件,但每个文件都包含一个不同的集合。整个州的表格如下所示:

  HEAD索引工作树
------ -------------------------
README.md README.md README.md
file.txt file.txt文件。 txt
new.txt new.txt
rmd.txt
untr.txt

除了这些之外,还有更多可能的状态:事实上,对于每个文件名,in / in-in-inHEAD,index和work-树(第八个组合不是全部三个,在这种情况下,我们甚至在第一个地方谈论什么文件?!)。

checkout reset 命令



关于 git checkout git reset ,都可以做很多事情。然而,每一个具体的调用都会将完成的事情减少到两个中的一个,我将在其中添加几个:


  • git checkout - 。:从索引,复制工作树,

  • HEAD,索引,然后
    工作树
  • git reset --mixed :从HEAD重置索引(然后单独保留工作树)

  • git reset --hard :从HEAD重置索引,然后重置索引中的工作树



这些重叠很多,但有几个截然不同的部分。



让我们考虑名为<特别是上面的code> new.txt 。它现在在索引中,所以如果我们将从索引复制到工作树,我们用索引副本替换工作树副本。这就是 git checkout - new.txt 的例子。



如果我们从从 HEAD 复制到索引,索引中没有任何反应 new.txt new .txt HEAD 中不存在。因此,一个明确的 git checkout HEAD - new.txt 就会失败,而 git checkout HEAD - 。会复制 HEAD 中的文件,并保留两个现有的 new.txt 版本不受干扰。



文件 rmd.txt 不在索引中,所以如果我们 git checkout - - 。,Git没有看到它,也没有做任何事情。但是如果我们 git checkout HEAD - 。,Git会从 HEAD rmd.txt / code>到索引(现在回来了),然后从索引到工作树(现在它又回到了那里)。

git reset 命令与没有路径名参数一起使用时有一个关键区别。在这里,它从字面上重新设置索引以匹配提交。这意味着对于 new.txt ,它注意到该文件不在 HEAD 中,所以它删除索引条目。如果与 - hard 一起使用,它也会删除工作树条目。同时 rmd.txt HEAD ,所以它将它复制回索引,和 - hard ,以及工作树。



如果存在unstaged,即work只修改其他两个文件 README.md file.txt ,两种形式的 git checkout - hard 形式的 git reset

如果对这些文件进行了 staged 更改,那么这些更改已被复制到工作树中,然后 git reset 取消它们的阶段。那么你给它起名为 HEAD 的变体 git checkout 。但是,将索引文件复制回工作树的变种 git checkout 可以保持这些阶段性更改!



顶级vs当前目录



最后,值得注意的是,意思是当前目录,可能随时与Git仓库顶部不同:

  $ git rev-parse  - -show-toplevel 
/home/torek/src/kernel.org/git
$ pwd
/home/torek/src/kernel.org/git/Documentation
$ git rev-parse --show-cdup
../

在这里,我在文档顶级目录的子目录 git ,所以表示 c $ c> Documentation 及其子目录中的所有内容。使用 git checkout - 。将会检出(来自索引)所有文档 Documentation / RelNotes 文件,但不包含任何 ../ builtin 文件。但是,如果没有路径名称, git reset 会重置所有条目,包括 .. ../ builtin


This is not a general question about what '--' does, as in the marked duplicate. This is a git-specific question asking for clarity on what the operational differences are between the mentioned commands.

If I want to clean out my current directory without stashing or committing, I usually use these commands:

git reset HEAD --hard
git clean -fd

A co-worker also mentioned using this command:

git checkout -- .

It's a difficult command to google, and it's not clear to me from the git documentation what this command actually does. It seems to be one of the later-mentioned usages in the manual.

At a guess it replicates git reset HEAD --hard, but what exactly does it do as compared to the commands I'm already using?
Does it replicate one or both of the commands, or is it similar yet subtly different?

解决方案

First, let's just address the double hyphen or double dash, to get it out of the way (especially since this question no longer has a marked duplicate).

Git mostly uses this in the POSIX-approved fashion (see Guideline 10), to indicate a dividing line between option arguments and non-option arguments. Since git checkout accepts branch names, as in git checkout master, and also file (path) names, as in git checkout README.txt, you can use the -- to force Git to interpret whatever comes after the -- as a file name, even if it would otherwise be a valid branch name. That is, if you have both a branch and a file named master:

git checkout master

will check out the branch, but:

git checkout -- master

will check out the file (confusingly, from the current index).

Branches, index, and files, oh my

Next, we need to address a quirk of git checkout. As one can see from the documentation, there are many "modes" of git checkout (the documentation lists six separate invocations in the synposis!). There are various rants (of varying quality: Steve Bennet's is actually useful, in my opinion, though naturally I do not agree with it 100% :-) ) about Git's poor "user experience" model, including the fact that git checkout has too many modes of operation.

In particular, you can git checkout a branch (to switch branches), or git checkout one or more files. The latter extracts the files from a particular commit, or from the index. When Git extracts files from a commit, it first copies them to the index, and then copies them from the index, to the work-tree.

There is an underlying implementation reason for this sequence, but the fact that it shows through at all is a key element. We need to know a lot about Git's index, because both git checkout and git reset use it, and sometimes in different ways.

It's a good idea, I think, to draw a three-way diagram or table illustrating the current—or HEAD—commit, the index, and the work-tree. Suppose that:

  • there are two ordinary, committed files README.md and file.txt;
  • there is a new, git add-ed but uncommitted new.txt;
  • there is a file named rmd.txt that has been git rm-ed but not committed;
  • and there is an untracked file named untr.txt.

Each entity—the HEAD commit, the index, and the work-tree—holds three files right now, but each holds a different set of files. The table of the entire state then looks like this:

  HEAD       index    work-tree
-------------------------------
README.md  README.md  README.md
file.txt   file.txt   file.txt
           new.txt    new.txt
rmd.txt
                      untr.txt

There are many more possible states than just these: in fact, for each file-name, there are seven possible combinations of "in/not-in" HEAD, index, and work-tree (the eighth combination is "not in all three", in which case, what file are we even talking about in the first place?!).

The checkout and reset commands

The two commands you're asking about, git checkout and git reset, are both able to do many things. The specific invocations of each, however, reduce the "things done" to one of two, to which I will add several more:

  • git checkout -- .: copies from index, to work-tree, only
  • git checkout HEAD -- .: copies from HEAD, to index, and then to work-tree
  • git reset --mixed: resets index from HEAD (and then leaves work-tree alone)
  • git reset --hard: resets index from HEAD, then resets work-tree from index

These overlap a lot, but there are several crucially-different parts.

Let's consider the file named new.txt above in particular. It's in the index right now, so if we copy from the index, to the work-tree, we replace the work-tree copy with the index copy. This is what git checkout -- new.txt does, for instance.

If, instead, we start by copying from HEAD to the index, nothing happens to new.txt in the index: new.txt doesn't exist in HEAD. Hence an explicit git checkout HEAD -- new.txt just fails, while a git checkout HEAD -- . copies the files that are in HEAD and leaves the two existing new.txt versions undisturbed.

The file rmd.txt is gone from the index, so if we git checkout -- ., Git does not see it and does nothing about it. But if we git checkout HEAD -- ., Git copies rmd.txt from HEAD into the index (now it's back) and then from the index to the work-tree (and now it's back there, too).

The git reset command has a key difference when used with no path name arguments. Here, it literally re-sets the index to match the commit. That means that for new.txt, it notices that the file is not in HEAD, so it removes the index entry. If used with --hard, it therefore also removes the work-tree entry. Meanwhile rmd.txt is in HEAD, so it copies that back to the index, and with --hard, to the work-tree as well.

If there are unstaged, i.e., work-tree only, changes to the other two files README.md and file.txt, both forms of git checkout and the --hard form of git reset wipe out those changes.

If there are staged changes to those files—changes that have been copied into the work-tree—then git reset un-stages them. So does the variant of git checkout where you give it the name HEAD. However, the variant of git checkout where you copy the index files back to the work-tree keeps those staged changes staged!

Top level vs current directory

Last, it's worth noting that ., meaning the current directory, may at any time be different from "top of Git repository":

$ git rev-parse --show-toplevel
/home/torek/src/kernel.org/git
$ pwd
/home/torek/src/kernel.org/git/Documentation
$ git rev-parse --show-cdup
../

Here, I am in the Documentation sub-directory of the top level directory git, so . means everything in Documentation and its subdirectories. Using git checkout -- . will check out (from the index) all the Documentation and Documentation/RelNotes files, but not any of the ../builtin files, for instance. But git reset, when used without path names, will reset all entries, including those for .. and ../builtin.

这篇关于“git checkout - 。”有什么区别?和“git reset HEAD --hard”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆