Git撤销与合并

2020-03-22  本文已影响0人  overflowedstack

1. git init
创建一个空的git repo,也就是创建一个.git的子目录,这个目录包含了几乎所有git存储和操作的东西。新初始化的.git目录的典型结构如下:

.git $ ls -l
total 24
-rw-r--r--   1 a123  staff   23  3 22 20:57 HEAD
-rw-r--r--   1 a123  staff  137  3 22 20:57 config
-rw-r--r--   1 a123  staff   73  3 22 20:57 description
drwxr-xr-x  13 a123  staff  416  3 22 20:57 hooks
drwxr-xr-x   3 a123  staff   96  3 22 20:57 info
drwxr-xr-x   4 a123  staff  128  3 22 20:57 objects
drwxr-xr-x   4 a123  staff  128  3 22 20:57 refs

description文件仅供git web程序使用,平常无需关心。
config文件包含项目特有的配置选项。
info目录包含一个全局性排除文件,用以放置那些不希望被记录在.gitignore文件中的忽略模式。
hooks目录包含客户端或服务端的钩子脚本。
HEAD文件指向目前被检出的分支。
index文件(尚待创建)保存暂存区信息。
objects目录存储所有数据内容。
refs目录存储指向数据的提交对象的指针。

git的默认分支名字是master,git init时默认创建它。

myproject $ cat .git/HEAD
ref: refs/heads/master
myproject $ git status
位于分支 master

尚无提交

无文件要提交(创建/拷贝文件并使用 "git add" 建立跟踪

2. git的三种状态,以及工作区(Working directory),暂存区(Index),HEAD
Git 有三种状态,你的文件可能处于其中之一:已修改(modified)、已暂存(staged)和已提交(committed)

基于刚才init的git project,做一些改动。

myproject $ touch file1.txt
myproject $ git status
位于分支 master

尚无提交

未跟踪的文件:
  (使用 "git add <文件>..." 以包含要提交的内容)

    file1.txt

提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
myproject $ ls .git/
HEAD        description info        refs
config      hooks       objects

会看到在git add之后,.git下面多了一个index文件。

myproject $ git add file1.txt 
myproject $ ls .git/
HEAD        description index       objects
config      hooks       info        refs

这时候,所做的改动就处于已暂存状态,体现在index文件中。
可以利用以下命令查看git缓存了的内容。

myproject $ git ls-files --stage
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0   file1.txt

同时,.git/objects下面多了一个子文件夹,并生成了一个新文件。这个新文件就对应了刚才所做的改动。这就是git存储内容的方式--一个文件对应一条内容,以该内容加上特定头部信息一起的SHA-1校验和作为文件名。校验和的前两个字符用于命名子目录,余下的38个字符则作为文件名。后面会详叙。

myproject $ ls .git/objects
e6  info    pack
myproject $ ls .git/objects/e6
9de29bb2d1d6434b8b29ae775ad8c2e48c5391

可以通过cat-file命令从git那里查看存储的内容。
git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

myproject $ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

由于file1.txt的内容为空,所以这里显示为空。

这时候可以往file1.txt里添加一些内容,并git add。可以看到.git/objects又多了一个object。

myproject $ vim file1.txt 
myproject $ git add file1.txt 
myproject $ find .git/objects
.git/objects
.git/objects/pack
.git/objects/info
.git/objects/5e
.git/objects/5e/1187c8883693e5657056087bb5f105337fa0d6
.git/objects/e6
.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391

查看这个新的对象的内容以及类型。会发现它是一个blob对象。

myproject $ git cat-file -p 5e1187c8883693e5657056087bb5f105337fa0d6
this is line1.
myproject $ git cat-file -t 5e1187c8883693e5657056087bb5f105337fa0d6
blob

接下来commit这个change。
myProject $ git commit -m "first commit"

myproject $ git commit -m "first commit"
myproject $ git log
commit ec1ddc2cd64f33c13bd376e695987e88a0d35957 (HEAD -> master)
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:11:57 2020 +0800

    first commit

查看这个commit 对象的类型以及内容,commit的tree对象所指向的内容, 我们会发现,这个tree指向的是一个blob,而这个blob的内容,就是我们刚刚做过改动的文件。

myproject $ git cat-file -t ec1ddc2cd64f33c13bd376e695987e88a0d35957
commit
myproject $ git cat-file -p ec1ddc2cd64f33c13bd376e695987e88a0d35957
tree 4a0475f1371a1df3a7c1ee5d772e782abd072271
author stack <a123@123deMacBook-Pro.local> 1584882717 +0800
committer stack <a123@123deMacBook-Pro.local> 1584882717 +0800

first commit
myproject $ git cat-file -p 4a0475f1371a1df3a7c1ee5d772e782abd072271
100644 blob 5e1187c8883693e5657056087bb5f105337fa0d6    file1.txt
myproject $ git cat-file -t 4a0475f1371a1df3a7c1ee5d772e782abd072271
tree
myproject $ git cat-file -p 5e1187c8883693e5657056087bb5f105337fa0d6
this is line1.

同时,我们查看一下暂存区的内容:

myproject $ git ls-files --stage
100644 5e1187c8883693e5657056087bb5f105337fa0d6 0   file1.txt

会发现,暂存区指向的也是同样的blob对象。
至此,一个commit就提交了,工作区,暂存区,以及head又指向了同样的内容。
它们更新内容的顺序为,工作区->暂存区->head

3. git reset
将做过的change撤销掉,就像没有发生过一样。
git reset 应用的顺序为 head->暂存区->工作区。

(1) git reset --soft
当前,git的状态如下。

myproject $ git log
commit 2e62d3728e289f0c7270ac728b00f036e1c1edff (HEAD -> master)
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:23:35 2020 +0800

    third commit

commit ee31138368e8994f702902e21edd7d3f46c1a815
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:23:13 2020 +0800

    second commit

commit ec1ddc2cd64f33c13bd376e695987e88a0d35957
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:11:57 2020 +0800

    first commit

head指向的内容为:
(head是当前分支引用的指针,总是指向该分支上的最后一次提交。)

myproject $ git cat-file -p HEAD
tree cc90f92f82e86fa02c6dbdf0fe7f6b1b85abdd6c
parent ee31138368e8994f702902e21edd7d3f46c1a815
author stack <a123@123deMacBook-Pro.local> 1584883415 +0800
committer stack <a123@123deMacBook-Pro.local> 1584883415 +0800

third commit
myproject $ git ls-tree -r HEAD
100644 blob 3234a5d219806f3c2b72e2ca4d84c75a636cfcd1    file1.txt

index指向的内容为:
(索引是你的预期的下一个提交)

myproject $ git ls-files --stage
100644 3234a5d219806f3c2b72e2ca4d84c75a636cfcd1 0   file1.txt

我们来进行一次reset。(移动HEAD, --soft)

myproject $ git reset --soft HEAD~
myproject $ git ls-tree -r HEAD
100644 blob 9381eaa3762a236ddfb7ace54dd45e508ff55943    file1.txt
myproject $ git ls-files --stage
100644 3234a5d219806f3c2b72e2ca4d84c75a636cfcd1 0   file1.txt
myproject $ git log
commit ee31138368e8994f702902e21edd7d3f46c1a815 (HEAD -> master)
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:23:13 2020 +0800

    second commit

commit ec1ddc2cd64f33c13bd376e695987e88a0d35957
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:11:57 2020 +0800

    first commit

--soft将仅仅移动HEAD的指向,而并不会移动index以及工作区。
HEAD指的是HEAD的父节点。HEAD是父节点的父节点,也可以写成HEAD2.
所以这个命令本质上是撤销了上一次git commit命令。

(2) git reset --mixed
接下来,再通过reset来更新索引。(--mixed,默认行为)

myproject $ git ls-tree -r HEAD
100644 blob 9381eaa3762a236ddfb7ace54dd45e508ff55943    file1.txt
myproject $ git ls-files --stage
100644 3234a5d219806f3c2b72e2ca4d84c75a636cfcd1 0   file1.txt
myproject $ git reset --mixed HEAD~
重置后取消暂存的变更:
M   file1.txt
myproject $ git ls-tree -r HEAD
100644 blob 5e1187c8883693e5657056087bb5f105337fa0d6    file1.txt
myproject $ git ls-files --stage
100644 5e1187c8883693e5657056087bb5f105337fa0d6 0   file1.txt

(3) git reset --hard
reset更新工作目录(--hard)
git reset --hard HEAD~
--hard标记是reset命令的危险用法,它也是git会真正销毁数据的几个操作之一。

如果这个commit已经被推送到远端,可以用这个命令使远端也回退到相应的版本。
git push origin <branch> --force

4. git revert
将做过的change撤销掉,通过“反做”某一个版本,用一个新的commit来消除做过的change。
当前git的状态:

myproject $ git log
commit c216d922fd1cc851502bd5a1ed7e92200cae75be (HEAD -> master)
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:38:59 2020 +0800

    third commit

commit 0bb69921280911ec4a704ce6efcfd3ab1b11425a
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:38:32 2020 +0800

    second commit

commit af58830f47a7c260dcef186250429e33dcd43715
Author: stack <a123@123deMacBook-Pro.local>
Date:   Sun Mar 22 21:37:56 2020 +0800

    first commit

revert其中一个commit:

myproject $ git revert -n 0bb69921280911ec4a704ce6efcfd3ab1b11425a
myproject $ git status
位于分支 master
您在执行反转提交 0bb6992 的操作。
  (所有冲突已解决:运行 "git revert --continue")
  (使用 "git revert --abort" 以取消反转提交操作)

要提交的变更:
  (使用 "git reset HEAD <文件>..." 以取消暂存)

    删除:     file2.txt

myproject $ git commit -m "revert second commit"

再来看,多了一个commit,也就是用来revert的commit:

yproject $ git log --oneline
0bd4d1c (HEAD -> master) revert second commit
c216d92 third commit
0bb6992 second commit
af58830 first commit

而若是想要revert某个版本,但是在这个版本后又做过change,则在revert的过程中可能出现冲突,则需要解决冲突之后再提交。

5. git merge 与git rebase
先来讲讲git merge。
当前master 和 dev branch:

myproject $ git log --graph --all --oneline
* 3c3801f (HEAD -> dev) change file2 on dev
| * 79b700e (master) change file1 on master
|/  
* 50b905c first commit

接下来打算将dev的工作并入master分支。

myproject $ git checkout master
切换到分支 'master'
myproject $ git merge dev
Merge made by the 'recursive' strategy.
 file2.txt | 1 +
 1 file changed, 1 insertion(+)
myproject $ git status
位于分支 master
无文件要提交,干净的工作区
myproject $ git log --graph --all --oneline
*   413dabe (HEAD -> master) Merge branch 'dev'
|\  
| * 3c3801f (dev) change file2 on dev
* | 79b700e change file1 on master
|/  
* 50b905c first commit

另外,还想将master的工作也并入dev。
git merge之后,会发现dev branch指向了与master相同的commit:

myproject $ git checkout dev
切换到分支 'dev'
myproject $ git merge master
更新 3c3801f..413dabe
Fast-forward
 file1.txt | 1 +
 1 file changed, 1 insertion(+)
myproject $ git log --graph --all --oneline
*   413dabe (HEAD -> dev, master) Merge branch 'dev'
|\  
| * 3c3801f change file2 on dev
* | 79b700e change file1 on master
|/  
* 50b905c first commit

所以,git merge是把两个分支的最新快照,以及两者最近的共同祖先进行三方合并,合并的结果是生成一个新的快照。

接下来,用git rebase来合并分支。
当前的git状态

myproject $ git log --graph --all --oneline -n 3
* 5441022 (dev) change file2 again on dev
| * b727be3 (HEAD -> master) change file1 again on master
|/  
*   413dabe Merge branch 'dev'
|\ 

此时,采用git rebase,将dev的工作并入到master。

myproject $ git branch
  dev
* master
myproject $ git rebase dev
首先,回退头指针以便在其上重放您的工作...
应用:change file1 again on master
myproject $ git log --graph --all --oneline -n 3
* 4dd73d6 (HEAD -> master) change file1 again on master
* 5441022 (dev) change file2 again on dev
*   413dabe Merge branch 'dev'
|\

当在master branch上执行git rebase dev的时候,实际发生的事情是,找到master和dev两个分支的最近共同祖先,对比当前分支(master分支)相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将master分支指向目标基底(dev的head指向的commit),最后以此将之前另存为临时文件的修改依序应用。

可以看到,rebase使得提交历史更加整洁。尽管实际的开发工作是并行在不同branch上进行的,但是它们看上去就像是串行的一样,提交历史是一条直线没有分叉。

因此,变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。这两种方式,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同。

上一篇下一篇

猜你喜欢

热点阅读