写在前面
有关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 工作原理
git跟踪并管理的是修改,而非文件。而且git只能文本信息的修改和恢复,对于二进制文件,比如word或者图片,只能监听到改动却无法对改动进行恢复。
git 分支概念
每次提交(git commit
),git都会把这些提交串成一个时间线(后文中管这个时间线叫做分支)
默认情况下,使用git init
初始化的项目只有一条分支,叫做master
分支。可以理解成有一个叫做master
的指针指向着当前最新一次提交。还有一个概念就是存在一个HEAD
指针,而且请记住HEAD
永远指向当前的分支。HEAD指向的是哪个分支,当前我们就向那分支提交代码。
下图展示了一个普通git项目的分支结构
可见,当前项目只有一个分支,就是master
分支,有三次提交。然后HEAD
指针,指向当前的master
分支。
因分支必须指向某一次commit,所以必须先有commit才有后来的分支。某个分支必须至少有一次提交
git commit
之后,才会在git branch
指令中显示出来
随着你的每次提交,master
分支都会向前移动,HEAD
指针也同样跟着向前移动。
当某个场景中,我们从当前分支master
创建了新的分支,比如叫做dev
。下面这条指令是创建并切换到dev分支。
git branch -b dev
背地里,Git会新建一个指针叫做dev
,指向master
指向的相同的提交,然后再把HEAD
指向dev
,就表示当前分支在dev
上。
可以看出,git创建一个分支很快,因为除了增加一个dev指针,改动HEAD指针指向以外,工作区的文件没有变化。
从现在开始,对工作区的修改和提交就是针对dev分支了。比如新提交一次后,dev指针就会往后移动一步,但是注意,master指针不变。
git分支合并
假如我们在dev分支上的开发工作完成了,就可以吧dev分支上的代码合并到master分支上。有两种情况
-
快进模式 Fast forward 简称FF模式
本模式下的前提是:master分支,在dev分支拉出后,没有过提交。这样的合并,Git直接就把master
指向dev
指向的当前分提交即可,完成了快速合并git checkout master git merge dev
合并完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分支时,会提示冲突。如下所示
这个时候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
提交,这时我们的分支图变成这个样子
最后查看一下分支的合并情况
git log --graph --pretty=oneline --abbrev-commit
上面的截图可以看出,解决冲突的部分被单独划归出来了
禁用快速合并模式(简称no-ff模式)保留分支commit信息
前面提到过快速合并,快速合并的场景下,快速合并没有冲突。但是有个缺点就是,在合并后的master的log日志中看不到本次合并的dev分支的commit的id和描述信息
即,合并完成后,一旦删除了dev分支,我们既无法知道分支存在过,也无法区分那些修改是在分支上进行的。下面看一个禁止掉快速合并的情况
git merge --no-ff -m "merge with no-ff" dev
上面可以看到 分支合并历史中记录了合并过来的分支的commitid
如果不适用no-ff模式,单纯使用git reflog查看一下日志,你能看出来的,那些分支合并过来了吗
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的操作