git
所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。
不幸的是,Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,前面我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件。
使用统一UTF-8编码,所有语言使用同一编码,既没有冲突,又被所有平台支持。
使用Windows的童鞋要特别注意:千万不要使用Windows自带的记事本编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件,他们自作聪明地在每个文件开头添加了0xefbbbf(十六进制)的字符,你会遇到很多不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由记事本的弱智行为带来的。建议你下载[Visual Studio Code](https://code.visualstudio.com/)代替记事本,不但功能强大,而且免费!
版本管理的核心:Git 的版本控制管理的是一系列提交(commits),每个提交形成一个快照(snapshot),记录了项目在某个时间点的状态。这些提交通过父子关系构成了一个链条,形成了分支。
HEAD
的作用:HEAD
指向的是当前检出的分支的最新提交。也就是说,Git 知道你当前工作在哪个版本上是通过 HEAD
指针实现的。当你进行 commit
、checkout
、reset
等操作时,HEAD
的位置会改变,以反映你当前的工作状态。
举例:
- 提交:当你提交更改时,Git 会在当前分支的末尾生成一个新的提交,并将
HEAD
移动到该新的提交。 - 分支切换:当你切换到另一个分支时,
HEAD
也会移动到该分支的最新提交点。 - 回滚:当你使用
reset
或checkout
回滚到某个提交时,HEAD
会指向那个提交。
本地仓库
git init : 初始化仓库
git add */文件名 : 把文件添加到仓库
Unix的哲学是“没有消息就是好消息”,说明添加成功。
git commit -m "第一次提交" : 把文件提交到仓库
虽然Git告诉我们readme.txt
被修改了,但如果能看看具体修改了什么内容,自然是很好的。比如你休假两周从国外回来,第一天上班时,已经记不清上次怎么修改的readme.txt
git diff 文件名 : 查看文件修改了什么
用git diff HEAD -- readme.txt
命令可以查看工作区和版本库里面最新版本的区别
知道了对readme.txt
作了什么修改后,再把它提交到仓库就放心多了。
像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩RPG游戏时,每通过一关就会自动把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打Boss之前,你会手动存盘,以便万一打Boss失败了,可以从最近的地方重新开始。Git也是一样,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为commit
。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个commit
恢复,然后继续工作,而不是把几个月的工作成果全部丢失。
git log : 显示从最近到最远的提交日志
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline
参数
git log --pretty=oneline
如何进行版本回退?
在Git中,用HEAD
表示当前版本。上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
git reset --hard HEAD^ 回退到上一个版本
--hard参数有啥意义?
--hard会回退到上个版本的已提交状态,而
--soft会回退到上个版本的未提交状态,
--mixed会回退到上个版本已添加但未提交的状态。现在,先放心使用
--hard
当我们回退了版本以后,想再回到最新的版本,就需要最新版本的版本号了。怎么办呢?
git reflog : 用来记录你的每一条命令
版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。
工作区:就是电脑上能看到的本地目录
版本库:工作区的一个.git文件夹
版本库中有很多东西,其中最重要的就是被称为stage(或index)的暂存区,还有git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
当我们使用git add将文件添加进来的时候,实际上就是把文件修改添加到暂存区。
当我们进行git commit提交修改的时候,实际上就是把暂存区中的所有内容提交到当前分支。
可以简单的理解为,需要提交的文件修改都放到暂存区(git add),然后一次性提交暂存区的所有修改(git commit)。
如果你没有对工作区进行任何修改,那么工作区是干净的。
这里我们要提到一点,为什么git会比其他版本管理工具要优秀呢?
那是因为,git跟踪并管理的是修改,并非文件。
当我们进行以下操作时(实验:为了理解修改):第一次修改 -> git add
-> 第二次修改 -> git commit
第一次修改将被提交到分支,但是,第二次修改并不会被提交到分支。因为第二次修改并没有被放到暂存区。
那么如何提交第二次修改呢?
可以继续git add再git commit。也可以在将第一次修改提交前,将第一次修改和第二次修改一块放到暂存区,然后一块提交。
那么如何撤销修改呢?
git checkout -- file
可以丢弃工作区的修改。
放弃对工作区的修改有两种情况:第一种,修改后还没有被放到暂存区(没有add,只对文件进行了修改),这时候撤销修改就会回到和版本库一样的状态,也就是说,这个修改直接被忽略了。第二种,已经将文件添加到了暂存区(进行了add),又做出了修改,这时候撤销修改的话,文件会回到添加到暂存区的状态。
删除文件呢(这里指的是commit后的文件,因为commit之前删除不会导致工作区和版本库不同)?
删除文件有两种情况
第一种,自己主动删除。一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm
命令删了 rm test.txt .这个时候,git知道你删除了文件,因此工作区和版本库不一致。一般需要先从版本库中删除文件(git rm 文件),然后git commit -m "remove 文件"提交这个删除操作
第二种,误删。这时候使用(git checkout -- test.txt)恢复误删文件。其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
需要注意的是,从来没有被添加到版本库就被删除的文件(没有commit的),是无法恢复的!
远程仓库
添加远程仓库
第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
ssh-keygen -t rsa -C "youremail@example.com"
如果一切顺利的话,可以在用户主目录里找到.ssh
目录,里面有id_rsa
和id_rsa.pub
两个文件,这两个就是SSH Key的秘钥对,id_rsa
是私钥,不能泄露出去,id_rsa.pub
是公钥,可以放心地告诉任何人。
第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub
文件的内容
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
在github上创建一个仓库后。
先关联仓库
git remote add origin git@github.com:example/example.git
这里的origin是一个常用的默认习惯命名。当然,也可以起其他的名字。
将本地仓库推从到远程仓库
git push -u origin master
把本地库的内容推送到远程,用git push
命令,实际上是把当前分支master
推送到远程。
为什么第一次推送要加 -u
由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令
第一次提交以后,就可以用
git push origin master
来进行推送了
如果,远程仓库添加错了,怎么办?
使用git remote -v
查看远程库信息
根据具体的名字来删除远程仓库
git remote rm origin
此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。
从远程库克隆
直接使用
git clone git@github.com:example/example.git
将远程仓库克隆到本地。
值得注意的是,GitHub给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git
这样的地址。实际上,Git支持多种协议,默认的git://
使用ssh
,但也可以使用https
等其他协议。
使用https
除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http
端口的公司内部就无法使用ssh
协议而只能用https
。
分支管理
创建和合并分支
根本原理是什么样呢?
在版本回退里,我们已经知道,在每次进行提交之后,git都会把它们串成一条时间线,这条时间线就是一个分支。这条分支就是我们所说的主分支,也就是master分支。严格来说,HEAD并不是指向提交的,而是指向master的,master才指向提交。
当我们每进行一次提交,master就会向前移动一步,随着不断地提交,master分支的线越来越长。
当我们创建了一个新的分支dev后,git会新建一个指针,这个指针会指向master当前的提交,然后再把HEAD指向dev。
注意:在进行上述操作时,工作区没有任何变化
从现在开始,工作区的修改和提交就是针对dev分支的了。如果进行了新的提交。那么会出现HEAD指向的dev往后移动一步,而master不变。
当我们在dev上的工作完成以后,就可以把dev合并到master上去了。怎么合并呢?就是让master直接指向dev的当前提交。
git合并是非常快的。本质上就是修改指针。
合并完成后,dev分支的作用就没有了。可以删除dev分支。
具体操作
git checkout -b dev : 创建分支并切换
git switch -c dev : 创建分支并切换
git branch dev :创建分支
git checkout dev :切换分支
git switch dev :切换分支
git branch :查看当前分支
git switch master :切换回master分支
发现在dev分支上添加的内容不见了。不要着急,现在将dev分支合并到master分支上去。
git merge dev :将dev分支合并到当前的分支
注意:如果同一个文件来说,内容不一样,进行合并会覆盖掉master中的原有内容
解决冲突
当分支冲突时,也就是说,当我们修改过dev分支后,没有在master分支上提交。然后在master上进行了修改之后,进行了合并。这时候,两个分支的文件内容会冲突。
文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件。
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存。
再进行提交,就变成正常了。
分支管理策略(能看到分支合并历史)
当我们进行实际开发的时候,master一般作为发布版本的主分支。一般不在上面进行开发。在那干活呢?在副分支上干活,dev上。如果是多人共同合作的话,还会有很多很多分支。每个人都有自己的分支。
git merge --no-ff -m "merge with no-ff" dev
合并分支时,加上--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward
合并就看不出来曾经做过合并(git默认是fast forward
合并分支)
bug分支
现在有一个bug任务比较紧急。目前来看,需要创建一个分支来修复它,但是dev分支上的工作还没有提交。dev上的工作至少还需要两天才能完成,但是bug比较急,需要5分钟之内解决。这时候该怎么办?
git提供一个stash功能,可以把工作现场储藏起来,等以后恢复现场后继续工作。
git stash
用git status查看工作区,是干净的。
这时候就可以想办法修复bug了。首先要确认在哪个分支上修改bug,如果是master,就从master上创建分支。在新创建的分支上修复好bug以后,合并到master分支,删除新分支。
这时候要继续进行dev分支上的工作了,恢复工作内容一般有两个办法。
先查看工作现场在哪
git stash list
第一种,使用
git stash apply
恢复以后,stash内容不会删除,需要手动使用git stash drop删除。
第二种,使用
git stash pop
恢复的同时会把stash也删除了。
在master上修复bug以后,又出现了一个新问题,master上有的问题,dev分支上应该也有。这时候难道需要在dev上进行一遍相同的操作?
大可不必。git提供一个
git cherry-pick 版本号(提交的时候出现)
切换到dev分支,执行上面的命令。git会自动给dev分支做一次提交,相当于复制了修复bug的分支在dev提交。
开发一个新功能,最好新建一个分支;
如果要丢弃一个没有被合并过的分支,可以通过
git branch -D <name>`
强行删除。