我们知道在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 <liuxin@i-counting.cn> 1590685131 +0800 committer liuxin <liuxin@i-counting.cn> 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很多命令已经优化了,凑合看就好。