欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

git reset soft,hard,mixed之区别深解

GIT reset命令,似乎让人很迷惑,以至于误解,误用。但是事实上不应该如此难以理解,只要你理解到这个命令究竟在干什么。

首先我们来看几个术语

  • HEAD

这是当前分支版本顶端的别名,也就是在当前分支你最近的一个提交

  • Index

index也被称为staging area,是指一整套即将被下一个提交的文件集合。他也是将成为HEAD的父亲的那个commit

  • Working Copy

working copy代表你正在工作的那个文件集

  • Flow

当你第一次checkout一个分支,HEAD就指向当前分支的最近一个commit。在HEAD中的文件集(实际上他们从技术上不是文件,他们是blobs(一团),但是为了讨论的方便我们就简化认为他们就是一些文件)和在index中的文件集是相同的,在working copy的文件集和HEAD,INDEX中的文件集是完全相同的。所有三者(HEAD,INDEX(STAGING),WORKING COPY)都是相同的状态,GIT很happy。

当你对一个文件执行一次修改,Git感知到了这个修改,并且说:“嘿,文件已经变更了!你的working copy不再和index,head相同!”,随后GIT标记这个文件是修改过的。

然后,当你执行一个git add,它就stages the file in the index,并且GIT说:“嘿,OK,现在你的working copy和index区是相同的,但是他们和HEAD区是不同的!”

当你执行一个git commit,GIT就创建一个新的commit,随后HEAD就指向这个新的commit,而index,working copy的状态和HEAD就又完全匹配相同了,GIT又一次HAPPY了。

下面这一段是另外一个牛人的解释:

总的来说,git reset命令是用来将当前branch重置到另外一个commit的,而这个动作可能会将index以及work tree同样影响。比如如果你的master branch(当前checked out)是下面这个样子:

- A - B - C (HEAD, master)

HEAD和master branch tip是在一起的,而你希望将master指向到B,而不是C,那么你执行

git reset B以便移动master branch到B那个commit:

- A - B (HEAD, master)      # - C is still here, but there's no branch pointing to it anymore

注意:git reset和checkout是不一样的。如果你运行git checkout B,那么你讲得到:

- A - B (HEAD) - C (master)

这时HEAD和master branch就不在一个点上了,你进入detached HEAD STATE. HEAD,work tree,index都指向了B,但是master branch却依然指向C。如果在这个点上,你执行一个新的commit D,那么你讲得到下面(当然这可能并不是你想要的,你可能想要的是创一个branch做bug fix):

- A - B - C (master)
       \
        D (HEAD)

记住git reset不会产生commits,它仅仅更新一个branch(branch本身就是一个指向一个commit的指针)指向另外一个commit(Head和branch Tip同时移动保持一致).其他的仅剩对于index和work tree(working directory)有什么影响。git checkout xxxCommit则只影响HEAD,如果xxxCommit和一个branch tip是一致的话,则HEAD和branch相匹配,如果xxxCommit并不和任何branch tip相一致,则git进入detached HEAD 状态

 

 

  • Reset

如果你仔细研究reset命令本身就知道,它本身做的事情就是重置HEAD(当前分支的版本顶端)到另外一个commit。假设我们有一个分支(名称本身无所谓,所以我们就简单称为"super-duper-feature”分支吧),图形化表示如下:

如果我们执行:

git reset HEAD

任何事情都不会发生,这是因为我们告诉GIT重置这个分支到HEAD,而这个正是它现在所在的位置。

git reset HEAD~1

当我们再执行上面的命令时(HEAD~1是“the commit right before HEAD”的别名,或者说:put differently "HEAD's parent"),我们的分支将会如下所示

如果我们执行git reset HEAD~2,则意味着将HEAD从顶端的commit往下移动两个更早的commit。

  • Parameters
  1. soft

--soft参数告诉Git重置HEAD到另外一个commit,但也到此为止。如果你指定--soft参数,Git将停止在那里而什么也不会根本变化。这意味着index,working copy都不会做任何变化,所有的在original HEAD和你重置到的那个commit之间的所有变更集都放在stage(index)区域中。

 

  2.hard

--hard参数将会blow out everything.它将重置HEAD返回到另外一个commit(取决于~12的参数),重置index以便反映HEAD的变化,并且重置working copy也使得其完全匹配起来。这是一个比较危险的动作,具有破坏性,数据因此可能会丢失!如果真是发生了数据丢失又希望找回来,那么只有使用:git reflog命令了。makes everything match the commit you have reset to.你的所有本地修改将丢失。如果我们希望彻底丢掉本地修改但是又不希望更改branch所指向的commit,则执行git reset --hard = git reset --hard HEAD. i.e. don't change the branch but get rid of all local changes.另外一个场景是简单地移动branch从一个到另一个commit而保持index/work区域同步。这将确实令你丢失你的工作,因为它将修改你的work tree!

  3.mixed(default)

--mixed是reset的默认参数,也就是当你不指定任何参数时的参数。它将重置HEAD到另外一个commit,并且重置index以便和HEAD相匹配,但是也到此为止。working copy不会被更改。所有该branch上从original HEAD(commit)到你重置到的那个commit之间的所有变更将作为local modifications保存在working area中,(被标示为local modification or untracked via git status),但是并未staged的状态,你可以重新检视然后再做修改和commit




来自  http://www.cnblogs.com/kidsitcn/p/4513297.html

 

git reset hard/soft/mixed区别

 4176人阅读 评论(0) 收藏 举报
 

目录(?)[+]

 
根据–soft –mixed –hard,会对working tree和index和HEAD进行重置:
    git reset --mixed:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commit和index信息
    git reset --soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可

    Git reset  --hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容,此命令 慎用!

 

git reset 命令是git中最常用的命令,但也是最危险,最容易被误用的命令。

一、master 分支

 

    我们知道git在初始化时,会为我们默认创建一个master分支,那这个master到底是什么呢?其实它在.git目录下对应了一个引用文件-----.git/refs/heads/master文件,而该文件的内容便是该分支中最新的一次提交的ID:

1
2
3
4
5
6
7
8
9
10
11
12
cat .git/refs/heads/master
22f8aae534916e1174711f138573acfbb47e489c
 
$ git cat-file -t 22f8aae
commit
 
$ git log --oneline
22f8aae git ignore file added.
4e79a0b Rename third to third.txt
46ae7e3 remove branch_first.txt file from master branch
55d1e70 branch first file
545a382 Merge commit '35bbd32'

   当我们用以上查看master文件内容时,发现它果然记录了最新一次的提交。了解了这个前提,那我们来分析下git reset到底做了什么?

二、reset命令

    以上面的例子为基础我们来探讨reset命令的魔力,首先我们来执行一个reset命令,reset到上一次提交

1
2
3
4
5
6
7
8
9
$ git reset --hard HEAD^
HEAD is now at 4e79a0b Rename third to third.txt
 
Quadrangle@QUADRANGLE-PC /d/GitRepo/GitOne (master)
$ git log --oneline
4e79a0b Rename third to third.txt
46ae7e3 remove branch_first.txt file from master branch
55d1e70 branch first file
545a382 Merge commit '35bbd32'

   HEAD^的意思就是最新一次提交的父提交,reset后,我们看一下提交日志,发现最新一次提交没了。到底git做了什么呢?我们还能找回最新的一次提交吗?

    回答上面两个问题,就用到上面就到的master引用文件了,这是我们再看一下这个文件的内容有什么变化?

1
2
cat .git/refs/heads/master
4e79a0ba92f1ae63dc661e29343fa0c369ca480d

   发现了吧,里面记录了reset后的最后一次提交。这时或许我们有所觉察了。先不急着下结论,我们看怎么回到最新的提交呢?其实reset后,git没有删除最新提交的相关信息,包括目录树,因此只要我们记住提交ID,便可以重新reset回来:

1
2
3
4
5
6
7
8
9
$ git reset --hard 22f8aae
HEAD is now at 22f8aae git ignore file added.
 
$ git log --oneline
22f8aae git ignore file added.
4e79a0b Rename third to third.txt
46ae7e3 remove branch_first.txt file from master branch
55d1e70 branch first file
545a382 Merge commit '35bbd32'

   这样查看log,发现最新提交又回来了。但是如果我们忘了最新提交ID 怎么办呢?好办,reflog命令可以最总引用变更的记录:

1
2
3
4
5
6
Quadrangle@QUADRANGLE-PC /d/GitRepo/GitOne (master)
$ git reflog
22f8aae HEAD@{0}: reset: moving to 22f8aae
4e79a0b HEAD@{1}: reset: moving to HEAD^
22f8aae HEAD@{2}: reset: moving to 22f8aae
4e79a0b HEAD@{3}: reset: moving to HEAD^

   我们可以找到前面任意操作的记录,并且可以reset到任意提交。

三、揭秘

    之前我们看到了reset后master文件的内容发生了变化,其实这就是reset命令的本质,但结合不同的参数,会有额外的工作:如--hard --soft --mixed等,他们决定了是否重置暂存区或工作区。

我们来看下面图片

113832_xvvr_258230.png

其实reset命令有两种用法:

  1. git reset [-q] [commit] [--] <paths>

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

第一种用法是不会重置引用的,即不会修改master文件。只是用某一次提交的文件提交暂存区的文件

第二种用法不使用<paths> 则会重置引用,并且参数不同决定是否覆盖暂存区和工作区:

  • --hard参数会执行途中1,2,3 全部动作,即暂存区,工作区全部用指定提交版本的目录树替换掉

  • --soft 参数只执行1, 不进行暂存区和工作区的覆盖

  • --mixed或不使用参数,执行1,2覆盖暂存区,但不覆盖工作区

 

这样一来相信对reset的理解会用更深刻的理解了吧。


来自 http://blog.csdn.net/carolzhang8406/article/details/49761927


一、基本篇

 

在git的一般使用中,如果发现错误的将不想staging的文件add进入index之后,想回退取消,则可以使用命令:git reset HEAD <file>...,同时git add完毕之后,git也会做相应的提示,比如:

 

引用

 

# Changes to be committed: 
#   (use "git reset HEAD <file>..." to unstage) 
#new file:   Test.scala 

 

git reset [--hard|soft|mixed|merge|keep] [<commit>HEAD]:将当前的分支重设(reset)到指定的<commit>或者HEAD(默认,如果不显示指定commit,默认是HEAD,即最新的一次提交),并且根据[mode]有可能更新index和working directory。mode的取值可以是hard、soft、mixed、merged、keep。下面来详细说明每种模式的意义和效果。

 

A). --hard:重设(reset) index和working directory,自从<commit>以来在working directory中的任何改变都被丢弃,并把HEAD指向<commit>。 

 

具体一个例子,假设有三个commit, git st:
commit3: add test3.c
commit2: add test2.c
commit1: add test1.c
执行git reset --hard HEAD~1后,
显示:HEAD is now at commit2,运行git log
commit2: add test2.c
commit1: add test1.c
运行git st, 没有任何变化

 

B)--soft:index 和working directory中的内容不作任何改变,仅仅把HEAD指向<commit>。这个模式的效果是,执行完毕后,自 从<commit>以来的所有改变都会显示在git status的"Changes to be committed"中。 

 

具体一个例子,假设有三个commit, git st:
commit3: add test3.c
commit2: add test2.c
commit1: add test1.c
执行git reset --soft(默认) HEAD~1后,运行git log
commit2: add test2.c
commit1: add test1.c
运行git status, 则test3.c处于暂存区,处于准备提交状态。即此时git commit就会提交它。

 

  在使用git进行协作开发时,我们经常需要将自己的修改生成patch发给被人,但是在修改代码的过程中我们进行了很多次的提交,如何生成从最初的代码状态到最终代码状态的patch呢?下面要介绍的功能是应对这中情况。
现假设我们git软件仓库中的分支情况如下:
a-->b-->c
也就是说我们的代码从状态a修改到状态b,进行 一次提交,然后再修改到状态c,进行一次提交。这时我们已经肯定由a到c的修改是正确的,不再需要状态b了,并且要把从a到c的变化生成一个patch发 送给别人。如果直接打包的话会生成两个path,那么如何生成一个patch呢,这时就需要git-reset命令。
首先给状态a创建一个tag,假设名称为A,然后执行
git-reset --soft A
这样我们的软件仓库就变为
a
状态b和状态c都已经被删除了,但是当前的代码并没有被改变,还是状态c的代码,这时我们做一次提交,软件仓库变成下面的样子:
a-->d
状态d和状态c所对应的代码是完全相同的,只是名字不同。现在就可以生成一个patch打包发给别人了

 

C)--mixed:仅reset index,但是不reset working directory。这个模式是默认模式,即当不显示告知git reset模式时,会使用mixed模式。这个模式的效果是,working directory中文件的修改都会被保留,不会丢弃,但是也不会被标记成"Changes to be committed",但是会打出什么还未被更新的报告。报告如下: 

 

引用

 

Unstaged changes after reset: 

 

M Test.Scala 

 

M test.txt

 

 

 

D)--merge--keep用的不多,在下面的例子中说明。 

 

二、常用示例

 

下面列出一些git reset的典型的应用场景: 

 

A) 回滚add操纵 

 

引用

 

$ edit                                     (1) 

 

$ git add frotz.c filfre.c 

 

$ mailx                                    (2) 

 

$ git reset                                (3) 

 

$ git pull git://info.example.com/ nitfol  (4) 

 

 

 

(1) 编辑文件frotz.c, filfre.c,做了些更改,并把更改添加到了index 

 

(2) 查看邮件,发现某人要你pull,有一些改变需要你merge下来 

 

(3) 然而,你已经把index搞乱了,因为index同HEAD commit不匹配了,但是你知道,即将pull的东西不会影响已经修改的frotz.c和filfre.c,因此你可以revert这两个文件的改变。 revert后,那些改变应该依旧在working directory中,因此执行git reset。 

 

(4) 然后,执行了pull之后,自动merge,frotz.c和filfre.c这些改变依然在working directory中。 

 

 

 

B) 回滚最近一次commit 

 

引用

 

$ git commit ... 

 

$ git reset --soft HEAD^      (1) 

 

$ edit                        (2) 

 

$ git commit -a -c ORIG_HEAD  (3) 

 

 

 

(1) 当提交了之后,你又发现代码没有提交完整,或者你想重新编辑一下提交的comment,执行git reset --soft HEAD^,让working tree还跟reset之前一样,不作任何改变。 

 

HEAD^指向HEAD之前最近的一次commit。 

 

(2) 对working tree下的文件做修改 

 

(3) 然后使用reset之前那次commit的注释、作者、日期等信息重新提交。注意,当执行git reset命令时,git会把老的HEAD拷贝到文件.git/ORIG_HEAD中,在命令中可以使用ORIG_HEAD引用这个commit。 commit 命令中 -a 参数的意思是告诉git,自动把所有修改的和删除的文件都放进stage area,未被git跟踪的新建的文件不受影响。commit命令中-c <commit> 或者 -C <commit>意思是拿已经提交的commit对象中的信息(作者,提交者,注释,时间戳等)提交,那么这条commit命令的意思就非常 清晰了,把所有更改的文件加入stage area,并使用上次的提交信息重新提交。 

 

 

 

C) 回滚最近几次commit,并把这几次commit放到叫做topic的branch上去。 

 

引用

 

$ git branch topic/wip     (1) 

 

$ git reset --hard HEAD~3  (2) 

 

$ git checkout topic/wip   (3)

 

 

 

(1) 你已经提交了一些commit,但是此时发现这些commit还不够成熟,不能进入master分支,但你希望在新的branch上润色这些commit改动。因此执行了git branch命令在当前的HEAD上建立了新的叫做 topic/wip的分支。 

 

(2) 然后回滚master branch上的最近三次提交。HEAD~3指向当前HEAD-3个commit的commit,git reset --hard HEAD~3即删除最近的三个commit(删除HEAD, HEAD^, HEAD~2),将HEAD指向HEAD~3。 

 

 

 

D) 永久删除最后几个commit 

 

引用

 

$ git commit ... 

 

$ git reset --hard HEAD~3   (1)

 

 

 

(1) 最后三个commit(即HEAD, HEAD^和HEAD~2)提交有问题,你想永久删除这三个commit。 

 

 

 

E) 回滚merge和pull操作 

 

引用

 

$ git pull                         (1) 

 

Auto-merging nitfol 

 

CONFLICT (content): Merge conflict in nitfol 

 

Automatic merge failed; fix conflicts and then commit the result. 

 

$ git reset --hard                 (2) 

 

$ git pull . topic/branch          (3) 

 

Updating from 41223... to 13134... 

 

Fast-forward 

 

$ git reset --hard ORIG_HEAD       (4)

 

 

 

(1) 从origin拉下来一些更新,但是产生了很多冲突,你暂时没有这么多时间去解决这些冲突,因此你决定稍候有空的时候再重新pull。 

 

(2) 由于pull操作产生了冲突,因此所有pull下来的改变尚未提交,仍然再stage area中,这种情况下git reset --hard 与 git reset --hard HEAD意思相同,即都是清除index和working tree中被搞乱的东西。 

 

(3) 将topic/branch合并到当前的branch,这次没有产生冲突,并且合并后的更改自动提交。 

 

(4) 但是此时你又发现将topic/branch合并过来为时尚早,因此决定退滚merge,执行git reset --hard ORIG_HEAD回滚刚才的pull/merge操作。说明:前面讲过,执行git reset时,git会把reset之前的HEAD放入.git/ORIG_HEAD文件中,命令行中使用ORIG_HEAD引用这个commit。同样 的,执行pull和merge操作时,git都会把执行操作前的HEAD放入ORIG_HEAD中,以防回滚操作。 

 

 

 

F) 在被污染的working tree中回滚merge或者pull 

 

引用

 

$ git pull                         (1) 

 

Auto-merging nitfol 

 

Merge made by recursive. 

 

nitfol                |   20 +++++---- 

 

... 

 

$ git reset --merge ORIG_HEAD      (2)

 

 

 

(1) 即便你已经在本地更改了一些你的working tree,你也可安全的git pull,前提是你知道将要pull的内容不会覆盖你的working tree中的内容。 

 

(2) git pull完后,你发现这次pull下来的修改不满意,想要回滚到pull之前的状态,从前面的介绍知道,我们可以执行git reset --hard ORIG_HEAD,但是这个命令有个副作用就是清空你的working tree,即丢弃你的本地未add的那些改变。为了避免丢弃working tree中的内容,可以使用git reset --merge ORIG_HEAD,注意其中的--hard 换成了 --merge,这样就可以避免在回滚时清除working tree。 

 

 

 

G) 被中断的工作流程 

 

在实际开发中经常出现这样的情形:你正在开发一个大的feature,此时来了一个紧急的bug需要修复,但是目前在working tree中的内容还没有成型,还不足以commit,但是你又必须切换的另外的branch去fix bug。请看下面的例子 

 

引用

 

$ git checkout feature ;# you were working in "feature" branch and 

 

$ work work work       ;# got interrupted 

 

$ git commit -a -m "snapshot WIP"                 (1) 

 

$ git checkout master 

 

$ fix fix fix 

 

$ git commit ;# commit with real log 

 

$ git checkout feature 

 

$ git reset --soft HEAD^ ;# go back to WIP state  (2) 

 

$ git reset                                       (3)

 

 

 

(1) 这次属于临时提交,因此随便添加一个临时注释即可。 

 

(2) 这次reset删除了WIP commit,并且把working tree设置成提交WIP快照之前的状态。 

 

(3) 此时,在index中依然遗留着“snapshot WIP”提交时所做的uncommit changes,git reset将会清理index成为尚未提交"snapshot WIP"时的状态便于接下来继续工作。 

 

 

 

(H) Reset单独的一个文件 

 

假设你已经添加了一个文件进入index,但是而后又不打算把这个文件提交,此时可以使用git reset把这个文件从index中去除。 

 

引用

 

$ git reset -- frotz.c                      (1) 

 

$ git commit -m "Commit files in index"     (2) 

 

$ git add frotz.c                           (3)

 

 

 

(1) 把文件frotz.c从index中去除, 

 

(2) 把index中的文件提交 

 

(3) 再次把frotz.c加入index 

 

 

 

(I) 保留working tree并丢弃一些之前的commit 

 

假设你正在编辑一些文件,并且已经提交,接着继续工作,但是现在你发现当前在working tree中的内容应该属于另一个branch,与这之前的commit没有什么关系。此时,你可以开启一个新的branch,并且保留着working tree中的内容。 

 

引用

 

$ git tag start 

 

$ git checkout -b branch1 

 

$ edit 

 

$ git commit ...                            (1) 

 

$ edit 

 

$ git checkout -b branch2                   (2) 

 

$ git reset --keep start                    (3)

 

 

 

(1) 这次是把在branch1中的改变提交了。 

 

(2) 此时发现,之前的提交不属于这个branch,此时你新建了branch2,并切换到了branch2上。 

(3) 此时你可以用reset --keep把在start之后的commit清除掉,但是保持working tree不变。

来自  http://www.iteye.com/topic/1134995

普通分类: