git 进阶命令

FlynnZhou 2020-05-29

我们知道在git提交环节,存在三大部分:working tree, index file, HEAD

这三大部分中:

working tree就是你所工作在的目录,每当你在代码中进行了修改,working tree的状态就改变了。

index file是索引文件,它是连接working tree和HEAD的桥梁,每当我们使用git add命令来登记后,index file的内

容就改变了,此时index file就和working tree同步了,每次add都在.git/objects/中产生文件。

commit是最后的阶段,只有commit了,我们的代码才真正进入了git仓库。我们使用git-commit就是将index file里

的内容提交到commit中。

总结一下:

git diff是查看working tree与index file的差别的。

git diff –cached是查看index file与HEAD的差别的。

git diff HEAD是查看working tree和HEAD的差别的。(你一定没有忘记,HEAD代表的是最近的一次commit的信息)

demo:

git init

ehco ‘1‘>main.c

git add . 之后,git会根据main.c生成一个名字7f00,把7f00写入index文件中,

bogon:test-git lakeslove$ git ls-files --stage 
100644 7f0040ef82be149f4b9be85e4c838417cdb523de 0    main.c

同时在.git/objects/中生成blob文件:

.git/objects//7f

.git/objects//7f/0040ef82be149f4b9be85e4c838417cdb523de

git cat-file blob 7f00 ,得到结果是 1

echo ‘2‘>>main.c

git add . 之后,git会根据main.c生成一个名字650a,把650a写入index文件中替换7f00,

bogon:test-git lakeslove$ git ls-files --stage 
100644 650a4b55331fc68f7716d8072fdaa2d5a7a9cf5e 0    main.c

同时在.git/objects/中生成blob文件:

.git/objects//65

.git/objects//65/0a4b55331fc68f7716d8072fdaa2d5a7a9cf5e

git cat-file blob 7f00 ,得到结果是 1 2

 此时,head中是refs/heads/master,但实际上heads文件夹下是空的,检查如下:

bogon:test-git lakeslove$ cat .git/head
ref: refs/heads/master
bogon:test-git lakeslove$ cat .git/refs/heads/master
cat: .git/refs/heads/master: No such file or directory

执行commit

bogon:test-git lakeslove$ git commit -m ‘first commit‘

此时master出现

bogon:test-git lakeslove$ cat .git/refs/heads/master
7f2c3e37a5e2e87dd3eaee210c7435a2edd6be1d

继续查看.git/objects/,发现多了2个文件,其中一个是head中指向的文件7f2c

bogon:test-git lakeslove$ find .git/objects/
.git/objects/
.git/objects//pack
.git/objects//info
.git/objects//65
.git/objects//65/0a4b55331fc68f7716d8072fdaa2d5a7a9cf5e
.git/objects//b9
.git/objects//b9/de56321b0abed0131e19cf8f0c15bd91663279
.git/objects//7f
.git/objects//7f/2c3e37a5e2e87dd3eaee210c7435a2edd6be1d
.git/objects//7f/0040ef82be149f4b9be85e4c838417cdb523de

查看一下7f2c,发现7f2c是一个commit文件,指向刚刚生成的另一个tree文件b9de

bogon:test-git lakeslove$ git cat-file -t 7f2c
commit
bogon:test-git lakeslove$ git cat-file commit 7f2c
tree b9de56321b0abed0131e19cf8f0c15bd91663279
author liuxin <> 1590685131 +0800
committer liuxin <> 1590685131 +0800

first commit

查一下tree文件b9de,发现它指向我们第二次add . 生成的blob文件650a,内容是“1 2”

bogon:test-git lakeslove$ git ls-tree b9de
100644 blob 650a4b55331fc68f7716d8072fdaa2d5a7a9cf5e    main.c

于是我们总结一下:

git add . 之后,git会根据main.c生成一个名字650a(实际上是有几个文件生成几个名字),把650a写入index文件,同时在.git/objects/中生成名字为650a的blob文件,blob文件内容就是main.c 的内容

git commit 之后,git会在.git/objects/生成一个commit文件和一个tree文件,

commit文件的内容是tree文件的名字,tree文件的内容是这次根据目录生成的其他tree文件,以及add . 生成的blob文件

在把commit文件写入head里标注的文件(.git/refs/heads/master(master是分支名))中。

关于git branch和git checkout branchname

git branch branchname 用于建立一个名字叫branchname的分支。但是你想过这个分支会根据什么来建么?是根据

working tree?还是根据index file?还是commit呢?(不卖关子,答案告诉你,是…commit!)

ps:如果你学有余力,我再告诉你一个信息。在你git branch一个新分支后,在目录.git/refs/heads目录下会多出一个

新的文件,对应于新分支的名称,用来记录新分支下对应的“最后一次commit的信息”。

ps:如果你学有余力,我还要告诉你一个信息。当你git branch一个新分支并checkout转移到这个新分支后,.git目录

下的HEAD文件会相应的改变,它的内容将对应着.git/refs/heads/新分支的名称。

关于git checkout命令

情况1:

如果我们在master新增了代码,执行了git add . ,那么可以直接git checkout -b test1,此时test1可以看到master的代码,

如果此时在test1把代码commit,再git checkout master时,发现刚刚在master上写的代码没有了。

我们分析一下这个过程:

master新增了代码,代码在工作目录下,也写入里index里,也在.git/objects/中生成了blob文件。

git checkout -b test1之后,复制了index中内容,head中的内容从ref: refs/heads/master变成了ref: refs/heads/test1,而工作目录内容没变。

此时如果git commit,在.git/objects/中生成了commit文件和tree文件,commit指向这个tree文件,而这个tree文件指向刚刚生成的blob文件,

把commit文件名写在refs/heads/test1中,那么刚刚写的代码就成了test1分支的代码了。

当切换回master时,会根据master的head重新恢复master的代码,index和工作目录里的代码自然就没有了。

此时再在test1分支进行修改刚刚那个文件,不add . ,并git checkout master ,就会出现冲突并报错,导致切换失败。

原因是此时test1的head中内容(最近一次提交)和master的head中的不一致,如果切换过去,那么git不知道把test1的工作目录里新增的内容放在哪里,所以无法合并。

clone和pull命令

git commit -a命令只可以用在已经建立了index file之后,也就是在第一次初始化时,必

须要使用git add命令来建立index file,否则会报错。

git是这样设计的:clone的话,是clone远端的当

前分支。通俗的说,远端当前处在哪个分支,你clone来的就是哪个分支。

查看远程分支:

bogon:test-git lakeslove$ git branch -a
* test1
  remotes/origin/HEAD -> origin/test1
  remotes/origin/master
  remotes/origin/test1
git pull origin master
(如果你想拉到本地的dev分支上,首先git checkout -b dev,然后使用git pull origin dev,这样就将本地dev分支与远程origin/dev相绑定了)

其他拉取远程分支的办法:

git fetch origin test1:test1_local

git pull origin test1:test1

 

比较分支间不同的命令

git diff master test1 //简单容易记住

git log -p --left-right master...test1  //可以看到每次提交的不同

git whatchanged -p master...test1//不建议使用

关于 git reset命令

1 讲解git reset –soft

2 讲解git reset –hard

3 讲解git reset –mixed

4 讲解git reset

5 讲解git reset –

你会发现

git reset –soft HEAD^  之后,git diff返回空,而git diff –cached和git

diff HEAD会返回有效信息。这说明使用–soft选项后,只回退了commit的信息,而不会回复到index file一级。哈哈,这

就明了了!你如果想撤销commit,并且只回退commit的信息,那么就用–soft吧!

而且你可以观察到git reset的意思是“撤销到哪个位置”,。也就是说代码管理者需要在后面的参数中指定一个之前

的commit位置。如上面提到的HEAD^。

2 讲解git reset –hard

有了–soft的试验思路,我想你也应该知道如何测试–hard了。有意思的工作留给你去自己完成吧。我只说结论:

–hard会完全撤销一个commit,彻底的回复到上一次commit的状态。连working tree的源代码也会完全倒退到上次

commit之时的状态。所以使用–hard后,git diff,git diff –cached和git diff HEAD都会返回空。

有了这个–hard好工具,你可以这样做:在当前的current working tree中修改了代码,你可以选择git add或者不

add,然后使用git reset –hard HEAD命令就可以恢复到修改之前的最初状态了。你修改的代码和git add的信息都会被丢弃。

这个用法记住它,早晚你会用到它。但往往你会武断的认为git reset只能恢复到之前的commit状态,但你往往想不到git

reset还可以恢复到当前的HEAD所指定的commit状态。

3 讲解git reset –mixed

–mixed选项会撤销最近的一次commit,只保留working tree的源代码级的修改,而index file和commit都会回复到上

一次commit的状态。所以使用–mixed后,git diff和git diff HEAD会有有效信息的输出,而git diff –cached会输出空。

4 讲解git reset

我只需要告诉你–mixed是git reset的默认选项。你应该知道了,git reset和git reset –mixed效果是完全一样的。

5 讲解git reset –

如果你想从index file中删除一个已登记的文件,那么就用这个命令。

git show-branch会显示分支名称和其开发日志的内容。分割线上下的列是垂直对应的,

比如第一行! [master] master-first的!垂直向下是+*++ [master] master-first 的+,

bogon:test-git lakeslove$ git show-branch
! [master] master-first
 * [master_2] m2_1
  ! [second] second1
   ! [second_2] second1
----
 *   [master_2] m2_1
  ++ [second] second1
+*++ [master] master-first
+(加号)表示所在分支包含此行所标识的commit
(空格)表示所在分支不包含此行所标识的commit
-(减号)表示所在分支是经过merge得到的,而所在行的内容即是merge的基本信息。
*(星号)表示如果需要在某列标识+(加号),且此列为当前分支所在列,那么则将+(加号)转变为*(星号)。

从上图可以看出,

从master-first 分支branch出了second分支,second执行commit一次,commit信息为‘second1’,之后second分支branch出了second_2分支,

之后master-first分支branch出了master_2分支,master_2执行commit一次,commit信息为‘m2_1

git 备份

其实备份及恢复过程非常简单,但是如果你不甚了解,可能git会给你一个小小的惊吓~~
举例来说:
我用于git管理的项目目录为yaoming,那么备份的话,我只需要将整个yaoming目录拷贝到其他存储设备上即可,你
使用cp、rsync或者rcp等,都随你。记得查看一下,其中的隐藏目录.git也一定要备份过去。
当恢复时,只需要将备份在其他设备上的yaoming目录拷贝回来,然后在yaoming目录下运行git-init即可,git很聪
明的,他知道你是想恢复一个仓库还是想新建一个空仓库。然后,就OK了,您可以继续项目开发了。
当然如果你不知道执行git-init这一步,信心满满的以为git仓库可以随便挪,那git的报错提示“fatal: Not a git
repository”,会让你以为你的log、branch等等都付之东流了呢!(其实只是虚惊一场~~)

经过测试发现,只要把cp -R xxx xxx2 这样拷贝过去,那么cd xxx2,就可以正常指向git命令,无需再次git init。

如果非要执行一下git init,结果如下:没啥影响。

bogon:test-git2 lakeslove$ git init
Reinitialized existing Git repository in /Users/lakeslove/workspace/learnspace/c/gitlearn/test-git1/test-git2/.git/

毕竟这本书是一个学生在2009年毕业季找工作时边学边写的,git很多命令已经优化了,凑合看就好。

相关推荐