git 使用小结
写在前面
有关Git的诞生故事以及Git的强大,这里无须赘述。写这篇文章的原因是因为,习惯了用Git桌面工具向Github提交代码的我,换了一台笔记本后突然发现我连最基本的Git命令行都不会了。。。羞愧至极,所以就开始我的学习之路。
我的学习方法是,先大概了解Git的相关指令,然后简单试验之后,开始搜集各类博客资料,汇总之后,再次对试验过程进行整理,我整理了一些高质量的博文在附录中或者在文中。
本文是一篇阶段性的记录文章,只记录我容易遗忘的知识点,引用的文章图片都标注了出处,好了下面开始本文
为什么很多人推荐从svn转向git
从使用者角度分析:
- svn下载源代码慢。在git中一个几个G的版本库,一般一二十分钟就能下载完毕,但是在svn中要一个小时左右;
- svn随时都得要与服务器交互,无论是查看log,还是查看以往的版本你必须跟服务器相连,并且速度奇慢无比,而git做这些几乎是瞬间的事;
- 各个分支之间的补丁迁移麻烦,在git上只要两三个命令就可以完事的(其实一个命令,因为需要查找与分支切换),但是在svn上你必须要下载每个分支的代码,然后比较修改,再上传;
从服务器角度说为什么要用git:
- git版本库占用空间小(几乎是svn的分支数之一也就是说如果有四个分支,svn的版本库的体积将接近git的四倍),SVN每个分支都是一份代码的copy,而git每个分支只是各个提交点的hash值的集合。分支几乎不占用什么空间;
- git是分布式管理系统,完全可以不对代码进行备份,但SVN不行,一旦服务器的硬盘挂掉整个代码库就完了;
- git不用时时联网查询,并且对文件进行压缩,使得文件体积大大减小,并且传输速度快,svn是单个文件,git是压缩后的,在使用svn时我已经碰到过好几次服务器无响应了。由于git很多都可以在本地操作的,所以大大降低了客户端对服务器的连接,出现这种情况的概率会大大减小;
- 如果客户端离服务器端非常远,在网速糟糕的情况下,用svn下载代码速度远不上git.
推荐文章 http://www.cnblogs.com/common1140/p/3952948.html
git 工作原理
![](https://img.haomeiwen.com/i1433342/da9c6379a64ca9df.png)
git跟踪并管理的是修改,而非文件。而且git只能文本信息的修改和恢复,对于二进制文件,比如word或者图片,只能监听到改动却无法对改动进行恢复。
git 分支概念
每次提交(git commit
),git都会把这些提交串成一个时间线(后文中管这个时间线叫做分支)
默认情况下,使用git init
初始化的项目只有一条分支,叫做master
分支。可以理解成有一个叫做master
的指针指向着当前最新一次提交。还有一个概念就是存在一个HEAD
指针,而且请记住HEAD
永远指向当前的分支。HEAD指向的是哪个分支,当前我们就向那分支提交代码。
下图展示了一个普通git项目的分支结构
![](https://img.haomeiwen.com/i1433342/66750628b7e6bc90.jpg)
可见,当前项目只有一个分支,就是master
分支,有三次提交。然后HEAD
指针,指向当前的master
分支。
因分支必须指向某一次commit,所以必须先有commit才有后来的分支。某个分支必须至少有一次提交
git commit
之后,才会在git branch
指令中显示出来
随着你的每次提交,master
分支都会向前移动,HEAD
指针也同样跟着向前移动。
当某个场景中,我们从当前分支master
创建了新的分支,比如叫做dev
。下面这条指令是创建并切换到dev分支。
git branch -b dev
背地里,Git会新建一个指针叫做dev
,指向master
指向的相同的提交,然后再把HEAD
指向dev
,就表示当前分支在dev
上。
![](https://img.haomeiwen.com/i1433342/187fa5befd4f56f6.jpg)
可以看出,git创建一个分支很快,因为除了增加一个dev指针,改动HEAD指针指向以外,工作区的文件没有变化。
从现在开始,对工作区的修改和提交就是针对dev分支了。比如新提交一次后,dev指针就会往后移动一步,但是注意,master指针不变。
![](https://img.haomeiwen.com/i1433342/cbefaba55f001e9e.jpg)
git分支合并
假如我们在dev分支上的开发工作完成了,就可以吧dev分支上的代码合并到master分支上。有两种情况
-
快进模式 Fast forward 简称FF模式
本模式下的前提是:master分支,在dev分支拉出后,没有过提交。这样的合并,Git直接就把master
指向dev
指向的当前分提交即可,完成了快速合并git checkout master git merge dev
![](https://img.haomeiwen.com/i1433342/446721ba81fb0007.jpg)
合并完dev分支后,dev分支可以删掉了,删掉dev分支的操作,其实就是把dev指针给删掉就好了。现在就只剩下一个master
指针指向当前提交了
git branch -d dev
如果没有提交,就删除分支,git会提示你,你需要先提交在删除。但是可以强制删除,使用
git branch -D dev
- 非快进模式
本模式发生的情况就是,分支dev对某个文件,比如readme.txt文件修改后并提交,此时切换回master,发现master分支也修改了readme.txt并提交。 这样master分支和dev分支都对同一个文件进行了修改并提交,在master分支合并dev分支时,会提示冲突。如下所示
![](https://img.haomeiwen.com/i1433342/d6d23afa44a5d4d2.jpg)
这个时候Git无法实现快速合并,只能先尝试着将各自的修改合并后提交,但是这个时候经常伴随着冲突,比如readme.txt文件出现了冲突,文件被修改成了如下内容
<<<<<<< HEAD
Creating a new branch is quick
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> dev
=== 分割冲突代码,上面HEAD标记的是当前分支的内容,下面dev标记的是dev分支合并过来的内容,自己进行取舍
解决完冲突后,将冲突文件 git add
git commit
提交,这时我们的分支图变成这个样子
![](https://img.haomeiwen.com/i1433342/5506ffb7e537eba1.jpg)
最后查看一下分支的合并情况
git log --graph --pretty=oneline --abbrev-commit
![](https://img.haomeiwen.com/i1433342/99b6cc246fd7b264.jpg)
上面的截图可以看出,解决冲突的部分被单独划归出来了
禁用快速合并模式(简称no-ff模式)保留分支commit信息
前面提到过快速合并,快速合并的场景下,快速合并没有冲突。但是有个缺点就是,在合并后的master的log日志中看不到本次合并的dev分支的commit的id和描述信息
![](https://img.haomeiwen.com/i1433342/c6a3260091bd8e8e.jpg)
即,合并完成后,一旦删除了dev分支,我们既无法知道分支存在过,也无法区分那些修改是在分支上进行的。下面看一个禁止掉快速合并的情况
git merge --no-ff -m "merge with no-ff" dev
![](https://img.haomeiwen.com/i1433342/1c016a64fdba3abb.jpg)
上面可以看到 分支合并历史中记录了合并过来的分支的commitid
如果不适用no-ff模式,单纯使用git reflog查看一下日志,你能看出来的,那些分支合并过来了吗
![](https://img.haomeiwen.com/i1433342/0eb6a32705f96daa.jpg)
git stash
当前情况:此时有两个分支,master和dev,dev编辑到一半,并未成功,所以不能提交。但此时master有一个bug需要马上去修复,但因为dev无法提交,所以用stash保存现场。
git stash
转去master去把bug修复完后,checkout到dev开发分支,应该先merge master 然后再
git stash pop
恢复dev开发现场
多人协作
当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支关联起来,并且,远程仓库的默认名称是origin。
-
查看远程仓库的名称
git remote
-
推送分支
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:
git push origin master
-
抓取分支
git clone git@github.com:yourname/learngit.git
从远程库中获取分支时,默认只能获取到master分支,使用下面指令创建并关联远程的dev分支
git checkout -b dev origin/dev
然后提交dev分支
git push origin dev
-
解决远程冲突
如果你提交之前,恰好有小伙伴对当前的dev的分支也做了某些修改,则可能提示你提交失败,这个时候,你需要使用
git pull
来把最新的提交从origin/dev上抓取下来,然后本地合并,解决冲突中,再次提交
-
删除远程库
git push origin :<branch name>
因此,多人协作的工作模式通常是这样:
-
可以试图用
git push origin branch-name
推送自己的修改; -
如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull
试图合并; -
如果合并有冲突,则解决冲突,并在本地提交;
-
没有冲突或者解决掉冲突后,再用
git push origin branch-name
推送就能成功!
注:如果
git pull
提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to=origin/dev dev
更多内容参考阮一峰老师的这篇讨论git工作流程的文章
经典文章
git 后悔药系列
git diff 分析代码差异
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
git diff #是工作区(work dict)和暂存区(stage)的比较
git diff --cached #是暂存区(stage)和分支(master)的比较
git 错误回退
-
将还在工作区内未添加到缓存区的文件恢复到上次add之前的状态
git checkout -- filename
-
将提交到缓存区的文件回退到工作区,将缓存区的该文件恢复到上次commit之前的状态
git reset HEAD filename
-
已经commit了,想回退到之前的某次提交
-
回退至某次快照(commit提交)
git reset --hard 3628164(此数字为某次commit的log日志前七位)
-
commitid可以通过如下命令查找,翻看历史操作记录
git reflog
-
-
已经提交到远程库了
无法回退
git 删除文件
使用 git rm filename
删除指定文件
执行完上面这条指令,相当于先删掉了文件,然后执行了git add,将删除的结果添加到了暂存区,所以如果想恢复文件的话,需要进行如下操作
git reset HEAD filename #恢复暂存区
git checkout -- filename #恢复工作区
删除历史文件(它会从HEAD所在的分支历史中查询并删除<FILE>文件)
git filter-branch --tree-filter 'rm -f <FILE>' HEAD
关联github
先建立本地库,关联远程库
-
生成秘钥
ssh-keygen -t rsa -C "youremail@example.com"
以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
-
在github填写公钥
登陆GitHub,打开“Account settings”,“SSH Keys”页面:然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容:
-
将本地git项目关联远程仓储
$ git remote add origin https://github.com/yourname/learngit.git
-
将本地改动推送到远程仓库
git push -u origin master #将当前分支推送到远端,第二次开始不用使用-u
先建立远程库,用本地库关联
-
克隆远程库
git clone https://github.com/yourname/learngit.git
GitHub给出的地址不止一个,还可以用https://github.com/yourname/gitskills.git这样的地址。实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。
使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。
git 标签处理
发布版本时,我们通常会给版本库打一个标签(tag)。标签是以一个更让人理解的方式来标记commit,
比如说请给我找出4.5.6版本的提交,而不是请给我找出commit id为5f54f5sd的提交。“找到了:git show v4.5.6”
类似IP和域名的关系。
git中标签虽然是版本库的快照,也是一个指向当前commit的指针。(和分支很像,但是分支可以移动,标签不能移动),所以创建和删除标签都是瞬间完成的。
-
在当前分支直接使用如下指令创建tag
git tag v1.0#可以是任意有意义的字符串
注:然后使用 git tag查询所有标签。tag不是按时间顺序排布的,而是按照名字排序的
-
标签是默认打在最新一次提交上的,添加commitid来对任意提交打tag
git tag v1.0 commitid
-
使用下面的指令可以用来创建带有说明的tag
git tag -a v0.1 -m "version 0.1 released" 3628164
-
使用 git show <tagname>可以查看tag的说明
-
git push origin <tagname>
可以推送一个本地标签(标签默认存储在本地) -
git push origin --tags
可以推送全部未推送过的本地标签 -
git tag -d <tagname>
可以删除一个本地标签; -
git push origin :refs/tags/<tagname>
可以删除一个远程标签。
提高效率的小指令
-
彩色的git输出
git config color.ui true
-
让快照的log信息紧凑输出
git log --pretty=oneline
-
以图表的形式查看提交历史
git log --graph
-
只查看commit id的精确位数(一般前7位就能识别)
git log --abbrev-commit
-
给常常的指令设置别名
git config --global alias.st status git st git config --global alias.unstage 'reset HEAD' git unstage test.py
-
只查看最近一次的log
git log -1
-
获取本地公钥的快捷方式(文本软件打开有可能出错!)
mac
pbcopy < ~/.ssh/id_rsa.pubwindows
clip < ~/.ssh/id_rsa.publinux
sudo apt-get install xclip
xclip -sel clip < ~/.ssh/id_rsa.pub
精彩理解
我觉得这svn和git两个工具主要的区别在于历史版本维护的位置
Git本地仓库包含代码库还有历史库,在本地的环境开发就可以记录历史
而SVN的历史库存在于中央仓库,每次对比与提交代码都必须连接到中央仓库才能进行
这样的好处在于:
1、自己可以在脱机环境查看开发的版本历史
2、多人开发时如果充当中央仓库的Git仓库挂了,任何一个开发者的仓库都可以作为中央仓库进行服务
svn中央服务器挂了,那我一样可以将本地的项目重新搭建一个服务器呢?
答:不行,你的本地没有历史版本
答!! 断网了,看看能不能查看历史版本?看看能不能提交代码?
关于git commit
git commit
命令确认的是最近的一次git add
,如果文件最近的内容修改没有被git add
,那么在git commit
时,最近的文件修改内容不会被提交。
-
指定文件可以提交未保存到暂存区(unstaged)
vi readme.txt
git commit readme.txt -m "commit with unstaged modify"
vi readme.txt
git commit -a -m "commit all file will commit unstaged modify" -
不使用其他参数不会提交unstaged modify
git commit -m "commit without param will not commit unstaged modify"
是否可以把公钥私钥一起给别人呢
只需要给公钥。
原始数据经过私钥加密后只能用公钥解密,换句话说,别人收到经过加密的数据后,如果用你的公钥能够解密,那么他就可以确认这些数据是你发送的
如果把私钥给别人的话,别人就可以冒充你给别人发东西了
关于回退操作
对于没提交到stage的修改;
删除后,重新恢复,修改的内容是会直接消失的;比如你在文件中添加一个字符:‘1’;不用git add file
添加到stage;直接用rm删除后,再用git checkout -- file
恢复;恢复过来后,去看文件是没有这个字符:‘1’的。
印证了
git checkout -- file
恢复的是已经添加到stage的内容;
而使用git rm
删除的就是stage的内。git reset HEAD -- file
会从master中将被删的stage的内容拷贝过去。如果你使用了git rm
之后接着使用git commit -m “remove file”
则会删除master里的内容;
所以,关于一次回退流程是这样的
-
git reset --hard HEAD^
可以将删除的master从回收站恢复过来; - 然后利用
git reset HEAD -- file
从master中拷贝到stage中; - 最后再用
git checkout -- file
从stage中拷贝到工作目录中。
关于未提交的修改
现象:
在分支修并提交后,切到主干,主干的工作区是干净的;在分支修改不提交,切回主干,主干工作区是被修改过未提交的状态
解释:
这样做的好处可能是你本来想对DEV分支进行想改,但是你忘了切换到dev你还在master就已经改了工作区,如果这时切换到dev修改的工作区内容没了,岂不是很操蛋。只有commit之后才确定修改的内容属于哪个分支。
未commit的工作区文件和stage文件是可以灵活地在且仅在任一branch存在的。这是前提。
-
在工作区做了修改,提交到DEV的分支,再切换回master
这时候,对master来说,工作区没有任何未提交的修正(因为所有修正都已经commit)。则工作区内容应该是与master分支最后一次提交的内容一致。(处于任何其他时间点,都意味着工作区可能存在修正,这就出现了矛盾)
-
在工作区做了修改,没有提交到DEV分支,即切换回master
这个时候,对master来说,工作区有了修正,那么就保持工作区的现有状态即可。
pull&& push
pull:本地 <-- 远程
push:本地 --> 远程
本质上都是同步commit
如果你本地落后远程,必然要pull
如果你本地超前远程,必然要push
课后查看
- 为什么会产生冲突?什么时候产生冲突
- 上面的截图可以看出,解决冲突的部分被单独划归出来了
- 图形界面操作
- 断网情况下查看一下svn的操作