本文完全来自廖雪峰老师的 Git教程
为方便回顾以及每日拜读,固作此笔记
廖雪峰老师的Git教程真的十分精彩,建议初学者直接点击上方链接到<廖雪峰的官方网站>进行学习。
1. Git简介
1.1 什么是Git
Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理项目。
Git是Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。
Git的诞生很有趣,康康
1.2 集中式vs分布式
集中式版本控制系统
:版本库集中存放在 中央服务器 ,工作时从中央服务器获取最新版,干完再推送到中央服务器。最大毛病在于必须 联网 工作,网速成为大问题。例:CVS、SVN
分布式版本控制系统
:没有“中央服务器”,每个人的电脑都是一个完整的版本库,修改后只需要把修改推送给对方,即可看到对方的修改。相比之下 安全 性更高,因为中央服务器出问题所有人就没法干活。例:Git、促使Git诞生的BitKeeper、Mercurial和Bazaar
分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改
2. Git安装
2.1 Linux
输入git
,查看是否安装git
若未安装
sudo apt-get install git
(Debian或Ubuntu系统)
2.2 Windows
安装完成后打开git bash
输入
git config --global user.name "Name"
git config --global user.email "Your Email"
--global
参数表示机器上所有Git仓库都使用这个配置
3. 创建版本库
3.1 版本库
什么是版本库?版本库
又名仓库
(repository)
可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
3.2 创建
1、创建一个空目录
注意:目录名不要含中文
2、git init
初始化仓库,使目录变成Git可管理的仓库
此时会多出一个.git目录,用来跟踪管理版本库的,不要乱改,否则会破坏仓库
注意:所有的版本控制系统只能跟踪文本文件
的改动,对于图片、视频具体改了什么是不知道的,而且也没法跟踪word文件的改动,因为word是二进制格式
3、编写一个readme.txt文件
4、git add
git add readme.txt
把文件添加到仓库
git add .
也可添加所有文件
5、git commit
git commit -m "a readme file"
把文件提交到仓库
-m
后输入的是本次提交的说明
用add
添加不同文件,commit
一次提交文件
4. 时光穿梭机
4.1 状态查看
要随时掌握工作区的状态,使用git status
命令。可以了解当前仓库是否有文件被修改或是否有将被提交的修改
如果git status
告诉你有文件被修改过,用git diff
可以查看修改内容。
4.2 版本回退
当修改文件到一定程度,就可以“保存一个快照”,这个快照Git称为commit
,一旦文件改乱了或误删了,就可以从最近的一个commit
恢复
git log
查看历史修改记录
git log --pretty=oneline
简略记录
关于输出,有一大串commit id(版本号),防止多人在同一版本库内工作起冲突
每提交一个新版本,实际上Git就会把它们自动串成一条时间线
启动时光穿梭机!
git reset --hard HEAD^
回退到上一个版本
HEAD
表示当前版本,多一个 ^
代表回退多一个版本,如HEAD^^
回退到上上个版本
HEAD~100
回退100个版本
但是回退后再git log
就看不到从此之后的版本了,好比回到过去却回不来了
别急,只要当前窗口没关,往上找到之前git log里往后的commit id就可以了
再次git reset --hard id号
版本号较长,没有必要写全,写前几位即可
当然,关了也是有后悔药吃的,git reflog
即可看到之前所有操作的版本号。在git内部其实并没有把版本删掉,只是用HEAD指针指向当前版本,然后更新工作区文件,所以看不到此后的版本
4.3 工作区和暂存区
工作区
:电脑的工作目录,不含.git目录
版本库
:.git目录
暂存库
:版本库下stage(或者叫index)的文件
版本库还包含Git为我们自动创建的第一个分支master
,和指向master的指针HEAD
add 和 commit 的作用如图所示
总之,git add 命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的
$ git status
On branch master
nothing to commit, working tree clean
4.4 管理修改
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
第一次修改 -> git add
-> 第二次修改 -> git commit
git status
,此时只会发现第一次修改的记录,而第二次修改并没提交
用git diff HEAD -- filename
命令可以查看工作区和版本库里面最新版本的区别,第二次修改并没被提交
因此,流程应如下:
第一次修改 -> git add
-> 第二次修改 -> git add
-> git commit
4.5 撤销修改
1、情况一
git checkout -- readme.txt
把readme.txt文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或git add
时的状态。
注意:git checkout -- file
命令中的--
很重要,没有--
,就变成了“切换到另一个分支”的命令
2、情况二
git reset HEAD <file>
把暂存区的修改撤销掉(unstage),重新放回工作区
git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本
3、情况三
从暂存区提交到版本库
需要参考 4.2 版本回退
4、情况四
推送到远程
没法在本地解救了
4.6 删除文件
删除工作区和版本库的同一文件
在工作区git rm
后git commit
若在工作区误删了,使用git checkout
来还原(有被添加到版本库过)
5. 远程仓库
以上的功能基本上和集中式版本控制SVN一样,但Git的杀手级功能在于:远程仓库
Github网站可以提供免费的Git仓库托管服务的远程仓库
由于本地GIt仓库和Github仓库之间的传输是通过SSH加密的,需要进行设置:
1、创建SSH Key
Git Bash打开,输入
ssh-keygen -t rsa -C "[email protected]"
接下来默认回车,在用户主目录下会生成.ssh目录,其中id_rsa
是私钥,id_rsa.pub
是公钥。
2、github 添加公钥
登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub
文件的内容
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
当然,GitHub允许你添加多个Key
5.1 添加远程仓库
1、登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库
在Repository name填入仓库名,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库
2、关联Github仓库
在本地仓库下运行
git remote add origin [email protected]:username/repositoryname.git
添加后,远程库的名字就是origin
,这是Git默认的叫法,也可以改成别的,但是origin
这个名字一看就知道是远程库
git push -u origin master
把本地库的内容推送到远程,用git push
命令,实际上是把当前分支master
推送到远程
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
3、删除远程库
git remote rm origin
此处的“删除”其实是解除了本地和远程的绑定关系
使用前,建议先用
git remote -v
查看远程库信息
5.2 从远程库克隆
git clone [email protected]:username/repositoryname.git
Git支持多种协议,默认的git://
使用ssh
,但也可以使用https
等其他协议。
使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https
6. 分支管理
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件
6.1 创建与合并分支
初始只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上
Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支
1、创建、切换分支
git checkout -b dev
创建dev
分支,然后切换到dev
分支
-b
表示创建并切换
相当于
git branch dev
git checkout dev
2、查看分支
git branch
命令查看所有分支,带*
号的为当前分支
git checkout master
切换回master
主分支
此时发现刚才添加的内容不见了,是因为提交的是在dev
分支上,而master
分支此刻的提交点并没有变
3、合并分支
git merge dev
合并指定分支到当前分支
Fast-forward
(快进模式):直接把master指向dev的当前提交,所以合并速度非常快
4、删除分支
git branch -d dev
新版本Git的另一种创建并切换分支方式
git switch -c dev
6.2 解决冲突
当master和feature1分支下都各有修改且不同并提交,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突。可以通过git status查看冲突文件
存在冲突,必须手动解决冲突后再提交
打开冲突文件,会有<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容
修改后再提交即可
用带参数的git log
也可以看到分支的合并情况
最后删除分支
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
用git log --graph命令可以看到分支合并图。
6.3 分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
git merge --no-ff -m "merge with no-ff" dev
--no-ff
参数,表示禁用Fast forward
-m
参数,把commit描述写进去
不使用Fast forward
模式,merge后就像这样
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
合并分支时,加上--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward
合并就看不出来曾经做过合并
6.4 Bug分支
开发中,bug若家常便饭,每一个bug都可以通过一个新的临时分支来修复,修复后再合并分支,然后删除临时分支
1、git stash
保存现场
但如果此时你正在进行的工作还没有提交,可以通过git stash
来把当前工作现场“储藏”起来,等以后恢复现场继续工作
保存现场后,git status
查看工作区,是干净的
2、创建临时分支
接下来,确定在哪个分支上修复bug,就切换到哪个分支上创建临时分支
3、修复完成后
切换回原分支,完成合并,之后删除临时分支
4、恢复工作现场
查看保存的工作现场git stash list
$ git stash list
stash@{0}: WIP on dev: f52c633 add merge
恢复方法有二
方法一:git stash apply
恢复,但恢复后stash内容并不删除,用git stash drop
来删除
方法二:git stash pop
,恢复同时删除
多次stash,恢复时指定
git stash apply stash@{0}
5、cherry-pick
假设在主分支上修复bug,但dev分支是早期从主分支上分出来的,所以也存在bug
可以通过cherry-pick
命令去复制某个特定的提交到当前分支,而不需要去重复操作修改一次
git cherry-pick <commit>
<commit>
是修复bug所做的提交的commit
另外也可以在dev分支上做修复而不去创建临时分支,然后再在主分支上“重放”
6.5 Feature分支
软件开发中,总有无穷无尽的新的功能要不断添加进来。
添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
当合并前突然要销毁所有所做的,git branch -d feature
是销毁不了的,必须使用
git branch -D feature
强行删除
6.6 多人协作
当你从远程仓库克隆时,实际上Git自动把本地的master
分支和远程的master
分支对应起来了,并且,远程仓库的默认名称是origin
。
查看远程库信息git remote
更详细信息git remote -v
(fetch)可抓取,(push)可推送
1、推送分支
将本地提交推送到远程库,推送时要指定本地分支
git push origin master
推送其他分支的话,修改master为其他分支名
master
分支是主分支,因此要时刻与远程同步;
dev
分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug
分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature
分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
2、抓取分支
当其他小伙伴clone远程库时,默认情况下只能看到master
分支,若要在dev
分支上开发,就要创建远程origin
的dev
分支到本地
git checkout -b dev origin/dev
在dev
上修改后,把dev
分支push
到远程
此时你也对同样的文件作了修改,试图推送但失败,因为小伙伴的最新提交和你试图推送的提交有冲突
把最新提交从origin/dev
抓下来,在本地合并,解决冲突再推送
git pull
会失败,因为本地dev
分支没有与远程origin/dev
分支链接
设置dev
和origin/dev
的链接
git branch --set-upstream-to=origin/dev dev
设置后在git pull
就可以了
然后手动解决冲突,提交,push
3、多人协作的工作模式通常是这样:
1)首先,可以试图用git push origin <branch-name>
推送自己的修改;
2)如果推送失败,则因为远程分支比你的本地更新,需要先用git pull
试图合并;
3)如果合并有冲突,则解决冲突,并在本地提交;
4)没有冲突或者解决掉冲突后,再用git push origin <branch-name>
推送就能成功!
如果git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
6.7 Rebase
多人协作下,Git的提交历史将不是条干净的直线,对我们查看历史提交的变化带来麻烦,因为分叉的提交需要多方对比
rebase
操作可以把本地未push的分叉提交历史整理成直线
git rebase
缺点是本地的分叉提交已经被修改过了
7. 标签管理
发布一个版本时,通常先在版本库打一个标签(tag)来唯一确定打标签时刻的版本。
将来需要取某个标签版本时可将打标签时刻的历史版本取出来。相当于版本库的一个快照。
标签是指向某个commit的指针(跟分支很像,但是分支可以移动,标签不能移动)
创建和删除标签都是瞬间完成的
7.1 创建标签
1、切换到打标签的分支
2、打标签
git tag v1.0
v1.0
为标签
git tag
查看所有标签
标签默认打在最新提交的commit上
若要打在历史commit上
先
git log --pretty=oneline --abbrev-commit
查看历史提交的commit id
然后
git tag v0.9 <commit id>
创建带有说明的标签,用-a
指定标签名,-m
指定说明文字
git tag -a v0.1 -m "version 0.1 released" <commit id>
3、查看标签信息
标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>
查看标签信息
7.2 操作标签
1、删除标签
git tag -d v0.1
2、推送标签到远程
创建的标签只存储在本地,不会自动推送到远程
git push origin <tagname>
一次性推送全部尚未推送到远程的本地标签
git push origin --tags
3、删除远程标签
先删除本地标签
然后
git push origin :refs/tags/v0.1
登陆GitHub查看是否删除
8. 使用Github
GitHub作为免费的远程仓库,如果是个人的开源项目,放到GitHub上是完全没有问题的。GitHub还是一个开源协作社区,通过GitHub,既可以让别人参与你的开源项目,也可以参与别人的开源项目。
参与一个开源项目,在项目主页点“Fork”就可以clone到自己账号,然后从自己的账号下clone到本地,这样才能推送修改。
如果直接从他人仓库克隆下来,由于没有权限,是推送不成功的。
修改项目后推送到自己仓库,然后发起pull request,看对方是否接受你的修改
9.使用Gitee
由于Github网速问题,国内的Gitee会好很多
和GitHub相比,Gitee也提供免费的Git仓库。此外,还集成了代码质量检测、项目演示等功能。对于团队协作开发,Gitee还提供了项目管理、代码托管、文档管理的服务,5人以下小团队免费。
1、注册添加SSH公钥
与github操作类似
2、创建远程仓库
3、关联本地和远程库
git remote add origin [email protected]:username/repositoryname.git
4、删除远程库
git remote rm origin
5、关联多个远程库
只要远程库名不一样就可以
10. 自定义Git
除了配置user.name
和user.email
,Git还有许多配置项
如让Git显示颜色
git config --global color.ui true
10.1 忽略特殊文件
有时需要把某些文件放到Git工作目录下,但又不能提交,比如数据库密码的配置等等。在每次git status
都会显示Untracked files ...
可以在Git工作区的根目录下创建一个特殊的.gitignore
文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件
不需要从头写.gitignore
文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore
忽略文件的原则是:
忽略操作系统自动生成的文件,比如缩略图等;
忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
1、创建.gitignore文件
2、添加隐藏的文件名
如
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
# My configurations:
db.ini
deploy_key_rsa
#
注释
被忽略的文件想添加到GIt
git add -f file
强制添加
3、例外规则
# 排除所有.开头的隐藏文件:
.*
# 排除所有.class文件:
*.class
# 不排除.gitignore和App.class:
!.gitignore
!App.class
4、添加.gitignore
到版本库
当有.*
规则时要添加!.gitignore
.gitignore
文件本身要放到版本库里,并且可以对.gitignore
做版本管理!
5、检查规则
git check-ignore
如
$ git check-ignore -v App.class
.gitignore:3:*.class App.class
.gitignore
的第3行规则忽略了该文件
10.2 配置别名
git config --global alias.st status
将status
用st
代替
co
checkout
ci
commit
br
branch
unstage
'reset HEAD'
last
'log -1'
lg
"log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
每个仓库的Git配置文件都放在.git/config文件中
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = [email protected]:michaelliao/learngit.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[alias]
last = log -1
别名就在[alias]
后面,要删除别名,直接把对应的行删掉即可
当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig
中
[alias]
co = checkout
ci = commit
br = branch
st = status
[user]
name = Your Name
email = [email protected]
10.3 搭建Git服务器
GitHub就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用。
搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简单的apt
命令就可以完成安装。
1、安装git
sudo apt-get install git
2、创建git用户,来运行git服务
sudo adduser git
3、创建证书登陆
收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub
文件,把所有公钥导入到/home/git/.ssh/authorized_keys
文件里,一行一个。
4、初始化Git仓库
先选定一个目录为Git仓库,假定是/srv/sample.git
,在/srv
目录下输入命令
sudo git init --bare sample.git
Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git
结尾。然后,把owner改为git
sudo chown -R git:git sample.git
5、禁用shell登陆
出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似下面的一行
git:x:1001:1001:,,,:/home/git:/bin/bash
改为
git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell
这样,git
用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell
每次一登录就自动退出
6、克隆远程仓库
$ git clone git@server:/srv/sample.git
Cloning into 'sample'...
warning: You appear to have cloned an empty repository.
管理公钥
如果团队很小,把每个人的公钥收集起来放到服务器的/home/git/.ssh/authorized_keys文件里就是可行的。如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis来管理公钥。
管理权限
有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人是否有读写权限会精确到每个分支甚至每个目录下。因为Git是为Linux源代码托管而开发的,所以Git也继承了开源社区的精神,不支持权限控制。不过,因为Git支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这个工具