git入门(廖雪峰老师)
根据廖雪峰老师的git教程进行学习总结;
1、之前上班用的都是svn进行管理,那么svn和git有什么区别呢?
svn是集中式的版本控制系统,而git是分布式版本控制系统,那么集中式和分布式版本控制系统有什么区别呢?
集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。(集中式版本控制系统最大的毛病就是必须联网才能工作,如果在局域网内还好,宽带够大,速度够快,可如果在互联网上,遇到网速慢的话,可能提交一个10M的文件就需要5分钟)
分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作得时候,就不需要联网了,因为版本库就在你自己的电脑上,既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比如说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩只需把自己的修改推送个对方,就可以互相看到对方的修改了。(和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了)
2、在Windows上安装Git
在Windows上使用Git,可以从Git官网直接下载:https://git-scm.com/downloads,然后按默认选项安装即可。
安装完成后,在开始菜单里找到“Git”=》“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功!
安装完成后,还需要最后一步设置,在命令行输入:
$ git config --global user.name "Your Name" $ git config --global user.email "email@example.com"
注意git config命令的--global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
3、创建版本库
首先,选择一个合适的地方,创建一个空目录:
$ mkdir git
$ cd git
$ pwd
/e/git
pwd命令用于显示当前目录。我在e盘,仓库位于/e/git
注意:windows系统中,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文
第二步,通过git init命令把这个目录变成Git可以管理的仓库
$ git init Initialized empty Git repository in E:/git/.git/
这样Git仓库就创建好了,而且告诉你是一个空的(empty Git repository),当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。
如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看见
4、把文件添加到版本库
首先,在目录下编写一个readme.txt文件,然后用命令git add告诉Git,把文件添加到仓库
$ git add readme.txt
然后,用命令git commit告诉Git,把文件提交到仓库:
$ git commit -m "wrote a readme file"
显示如下:
简单解释一下git commit命令,-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便的找到改动记录。git commit命令行执行成功后会告诉你,1 file changed:一个文件被改动;2 insertions:插入了两行内容(为什么Git添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件)
5、查看仓库当前状态和查看修改内容
git status命令可以让我们时刻掌握仓库当前的状态;
如果不记得修改内容,可以使用git diff这个命令查看;
提交修改和提交新文件一样分两步,第一步是git add;第二步是git commit -m "explain";
要随时掌握工作区的状态,使用git status命令;
如果git status告诉你有文件被修改过,用git diff可以查看修改内容
6、版本回退
可以使用git log命令查看历史记录,如果嫌输出的信息太多,可以加上--pretty=oneline
前面一大串十六进制表示的是commit id(版本号)。
在git中,用HEAD表示当前版本,也就是最新的提交,上一个版本就是HEAD^,上上一个版本就是HEAD^^,一次类推100个,可以简写成HEAD~100,如果只是回退到上一个版本,可以使用git reset命令:
$ git reset --hard HEAD^
查看了一下内容,果然被回退了,那么,再想来到刚刚提交最新的那个版本怎么办?
可以找到那个版本的commit id,就可以指定回到未来的某个版本
$ git reset --hard 39263
版本号没必要写全,前几位就可以了,git会自动去找,
又回到刚刚最新版本了。
Git中,提供了一个命令git reflog用来记录你的每一次命令:
小结:HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id。
穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本(git log --pretty=oneline)
要重返未来,用git reflog 查看命令历史,以便确定要回到哪个版本
7、工作区和暂存区
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以,现在,git commit
就是往master
分支上提交更改。
修改readme.txt文件,添加LICENSE文件,没有git add,使用git status查看:
使用git add把两个都添加后:
现在,暂存区的状态就变成这样了:
git add
命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit
就可以一次性把暂存区的所有修改提交到分支
8、管理修改
当你修改文件之后,如果git add进去了,可是没有git commit,然后又修改了这个文件,然后再git commit进去,那么其实提交的内容是第一次修改的内容,因为第二次修改没有git add进去,也就是还停留在工作区,所以你git commit之后再git status还是会提示你有一个文件没有add,可以使用git diff HEAD -- readme.txt命令查看工作区和版本库里面最新版本的区别。
小结:每次修改,如果不用git add到暂存区,就不会加入到commit中。
9、撤销修改
当你修改了内容还没有git add的时候,你可以发现git会告诉你,git checkout -- file可以丢弃工作区的修改:
$ git checkout -- readme.txt
命令git checkout -- readme.txt
意思就是,把readme.txt
文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.txt
自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt
已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或git add
时的状态。
注意:git checkout -- file
命令中的--
很重要,没有--
,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout
命令
如果你进行了修改,并且git add进去了暂存区:
Git同样告诉我们,用命令git reset HEAD <file>
可以把暂存区的修改撤销掉(unstage),重新放回工作区:
然后再丢弃工作区的修改:
小结:
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file
。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD <file>
,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
10、删除文件
一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm
命令删了:
$ rm test.txt
这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status
命令会立刻告诉你哪些文件被删除了:
现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm
删掉,并且git commit
:
现在,文件就从版本库中被删除了。(提示:先手动删除文件,然后使用git rm <file>和git add <file>效果是一样的)
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
小结:命令git rm
用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
11、远程仓库
使用GitHub来做Git仓库托管服务。
第一步:需要创建一个GitHub账号,由于本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以需要一点设置:
①:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
$ssh-keygen -t rsa -C "youremail@163.com"
你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。
如果一切顺利的话,可以在用户主目录里找到.ssh
目录,里面有id_rsa
和id_rsa.pub
两个文件,这两个就是SSH Key的秘钥对,id_rsa
是私钥,不能泄露出去,id_rsa.pub
是公钥,可以放心地告诉任何人。
②:登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub
文件的内容:
点“Add Key”,你就应该看到已经添加的Key:
小结:可以把自己用到的每台电脑的key都添加到GitHub上,就可以在每台电脑上往GitHub推送了。GitHub上免费托管的Git仓库一般是任何人都可以看到的。(除非交钱,把仓库改为私有的)
12、添加远程库
现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。
首先,登陆GitHub,然后,在右上角找到“new repository”按钮,创建一个新的仓库:
在Repository name填入learngit
,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库:
目前,在GitHub上的这个learngit
仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。
现在,我们根据GitHub的提示,在本地的learngit
仓库下运行命令:
$ git remote add origina git@github.com:lsc202426/test.git
$ git push -u origina master
或者
$ git remote add origin https://github.com/lsc202426/learngit.git $ git push -u origin master
SSH警告:当你第一次使用Git的clone
或者push
命令连接GitHub时,会得到一个警告:这是因为Git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时,需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器,输入yes
回车即可。
Git会输出一个警告,告诉你已经把GitHub的Key添加到本机的一个信任列表里了:warning
由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
添加后,远程库的名字就是origin
,这是Git默认的叫法,也可以改成别的,但是origin
这个名字一看就知道是远程库。
看人家评论里面是这样解决的,后面再尝试一下。
从现在起,只要本地作了提交,就可以通过命令:
$ git push origin master
小结:要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git;
关联后,使用命令git push -u origin master
第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master
推送最新修改
13、从远程库克隆
首先,登陆GitHub,创建一个新的仓库,名字叫gitskills
:
我们勾选Initialize this repository with a README
,这样GitHub会自动为我们创建一个README.md
文件。创建完毕后,可以看到README.md
文件:
现在,远程库已经准备好了,下一步是用命令git clone
克隆一个本地库:
注意把Git库的地址换成你自己的,然后进入gitskills
目录看看,已经有README.md
文件了:
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。GitHub给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git
这样的地址。实际上,Git支持多种协议,默认的git://
使用ssh,但也可以使用https
等其他协议。使用https
除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh
协议而只能用https
。
小结:要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone
命令克隆。Git支持多种协议,包括https
,但通过ssh
支持的原生git
协议速度最快。
14、创建与合并分支
首先,我们创建dev分支,然后切换到dev分支:
git checkout命令加上-b参数表示创建并切换,相当于一下两条命令:
$ git branch dev $git checkout dev Switched to branch 'dev'
然后,用git branch命令查看当前分支:
git branch命令会列出所有分支,当前分支前面会标一个*号。
然后,我们就可以在dev分支上正常提交,比如对readme.txt做个修改,加上一行,然后提交:
现在,dev分支的工作完成,我们就可以切换回master分支:
切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了,因为那个提交是在dev分支上,而master分支此刻的提交点并没有变。
现在,我们把dev分支的工作成果合并到master分支上:
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
注意到上面的Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,我们后面会讲其他方式的合并。
合并完成后,就可以放心地删除dev
分支了:
删除后,查看branch
,就只剩下master
分支了:
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master
分支上工作效果是一样的,但过程更安全。
小结:Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git chcekout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
15、解决冲突
准备新的feature1
分支,继续我们的新分支开发:
修改readme.txt最后一行,改为:Creating a new branch is quick AND simple.
在featurel分支上提交:
切换到master分支:
Git还会自动提示我们当前master分支比远程的master分支要超前一个提交
在master分支上把readme.txt文件的最后一行改为:Creating a new branch is quick & simple.
现在,master分支和featurel分支个字都分别由新的提交变成了这样:
这种情况下,Git无法执行“快速合并”,只能视图把个字的修改合并起来,但这种合并就可能会有冲突,我们试试看:
果然冲突了!Git告诉我们,readme.txt
文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件:
我们可以直接查看readme.txt的内容:
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存:Creating a new branch is quick and simple.
用带参数的git log
也可以看到分支的合并情况:
最后,删除feature1
分支:
小结:当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
用git log --graph
命令可以看到分支合并图。
16、分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
下面我们实战一下--no-ff
方式的git merge
:
首先,创建并切换dev分支;修改readme.txt文件并提交一个新的commit;然后切换回master;准备合并dev
分支,请注意--no-ff
参数,表示禁用Fast forward
:
$ git merge --no-ff -m "merge with no-ff" dev Merge made by the 'recursive' strategy. readme.txt | 1 + 1 file changed, 1 insertion(+)
因为本次合并要创建一个新的commit,所以加上-m 参数,把commit描述写进去合并后,我们用git log看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit * e1e9c68 (HEAD -> master) merge with no-ff |\ | * f52c633 (dev) add merge |/ * cf810e4 conflict fixed ...
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本;
你和你的小伙伴们每个人都在dev
分支上干活,每个人都有自己的分支,时不时地往dev
分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
小结:合并分支时,加上--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward
合并就看不出来曾经做过合并。
17、Bug分支
软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101
来修复它,但是,等等,当前正在dev
上进行的工作还没有提交:
$ git status On branch dev Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: hello.py Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: readme.txt
并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
幸好,Git还提供了一个stash
功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
现在,用git status
查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。
首先确定要在哪个分支上修复bug,假定需要在master
分支上修复,就从master
创建临时分支:
$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. (use "git push" to publish your local commits) $ git checkout -b issue-101 Switched to a new branch 'issue-101'
现在修复bug,需要把“Git is free software ...”改为“Git is a free software ...”,然后提交:
$ git add readme.txt $ git commit -m "fix bug 101" [issue-101 4c805e2] fix bug 101 1 file changed, 1 insertion(+), 1 deletion(-)
修复完成后,切换到master
分支,并完成合并,最后删除issue-101
分支:
$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. (use "git push" to publish your local commits) $ git merge --no-ff -m "merged bug fix 101" issue-101 Merge made by the 'recursive' strategy. readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
太棒了,原计划两个小时的bug修复只花了5分钟!现在,是时候接着回到dev
分支干活了!
$ git checkout dev Switched to branch 'dev' $ git status On branch dev nothing to commit, working tree clean
工作区是干净的,刚才的工作现场存到哪去了?用git stash list
命令看看:
$ git stash list stash@{0}: WIP on dev: f52c633 add merge
工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
一是用git stash apply
恢复,但是恢复后,stash内容并不删除,你需要用git stash drop
来删除;
另一种方式是用git stash pop
,恢复的同时把stash内容也删了:
$ git stash pop On branch dev Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: hello.py Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: readme.txt Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list
查看,就看不到任何stash内容了:
$ git stash list
你可以多次stash,恢复的时候,先用git stash list
查看,然后恢复指定的stash,用命令:
$ git stash apply stash@{0}
小结:修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没有完成时,先把工作现场git stash
一下,然后去修复bug,修复后,再git stash pop
,回到工作现场。
18、Feature分支
添加一个新功能时,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
如果该feature分支已经add并且commit;但是还没有合并,如果想要删除:
$ git branch -d feature-vulcan error: The branch 'feature-vulcan' is not fully merged. If you are sure you want to delete it, run 'git branch -D feature-vulcan'.
销毁失败。Git友情提醒,feature-vulcan
分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D
参数。。
现在我们强行删除:
$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 287773e).
小结:开发一个新feature,最好新建一个分支;
如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>
强行删除。
19、多人协作
多人协作的工作模式通常是这样:
-
首先,可以试图用
git push origin <branch-name>
推送自己的修改; -
如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull
试图合并; -
如果合并有冲突,则解决冲突,并在本地提交;
-
没有冲突或者解决掉冲突后,再用
git push origin <branch-name>
推送就能成功!
如果git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
。
这就是多人协作的工作模式。
小结:
-
查看远程库信息,使用
git remote -v
; -
本地新建的分支如果不推送到远程,对其他人就是不可见的;
-
从本地推送分支,使用
git push origin branch-name
,如果推送失败,先用git pull
抓取远程的新提交; -
在本地创建和远程分支对应的分支,使用
git checkout -b branch-name origin/branch-name
,本地和远程分支的名称最好一致; -
建立本地分支和远程分支的关联,使用
git branch --set-upstream branch-name origin/branch-name
; -
从远程抓取分支,使用
git pull
,如果有冲突,要先处理冲突。
20、rebase(未完待续!)