在此之前,推荐一个前端学习路线:https://objtube.github.io/front-end-roadmap
其中对于git部分给了许多宝贵的资源。
是什么
一个由Linus使用C语言开发的,免费开源的,分布式
的,代码版本控制系统,帮助开发团队维护代码。
可以用来记录代码内容,切换代码版本,多人开发时高效合并代码内容
分布式vs集中式
集中式版本控制系统
,版本库是集中存放在中央服务器
的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器
取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器
。中央服务器
就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
集中式版本控制系统
最大的毛病就是必须联网
才能工作,如果在局域网内还好,带宽够大,速度够快,可如果在互联网上,遇到网速慢的话,可能提交一个10M的文件就需要5分钟,这还不得把人给憋死啊。
那分布式版本控制系统
与集中式版本控制系统
有何不同呢?首先,分布式版本控制系统
根本没有中央服务器
,每个人的电脑上都是一个完整的版本库
,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上,那分布式
的意思就是,版本库分布在每一台电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送
给对方,就可以互相看到对方的修改了。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多
,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当中央服务器的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
安装
最早Git是在Linux上开发的,很长一段时间内,Git也只能在Linux和Unix系统上跑。不过,慢慢地有人把它移植到了Windows上。现在,Git可以在Linux、Unix、Mac和Windows这几大平台上正常运行了。
到官网下载安装包运行,不需要手动配置环境变量
安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功!
配置身份
1
2git config --global user.name "sanye"
git config --global user.email "xxxxx@qq.com"每次提交代码时需要表明自己的身份,因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。注意
git config
命令的--global
参数,用了这个参数,表示你这台机器上所有的Git仓库
都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
git仓库
记录文件状态,内容,和历史记录,就是一个.git隐藏文件
存储的内容
- 添加的
远程仓库
的地址 - 本地分支与远程分支的连接(追踪关系)
- git hooks
- 暂存区内容和版本库
创建仓库
把本地文件夹转换成git仓库:
git init
,如果使用的是Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。然后当前文件夹下就会出现一个.git
目录。这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。从其他服务器克隆git仓库,不需要
git init
git的三个区域
- 工作区:实际开发中操作的文件夹,就是你在电脑里能看到的目录
- 暂存区:提交代码之前的准备区域(暂存改动的文件),可以临时恢复代码内容,与版本库解耦合
- 版本库:提交并保存暂存库区的内容,产生一个版本快照,存储在版本库

常用命令
暂存文件
git add <文件名>
:暂存指定文件,实际上就是把文件修改添加到暂存区
git add .
:暂存所有文件,把工作区所有文件添加到暂存区
提交文件
git commit -m <注释说明>
:实际上就是把暂存区
的所有内容提交到当前分支;- 可以简单理解为,需要提交的文件修改通通放到
暂存区
,然后,一次性提交暂存区的所有修改。 - 一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的,即
工作区文件
和版本库文件
保持一致。

回退命令
git reset --soft <版本号>
:暂存区,工作区
的所有文件都不变,只改变head
指向,回退到指定版本git reset --mixed <版本号>
:工作区
文件不变,暂存区文件被覆盖为指定版本
的文件。git reset --hard <版本号>
:暂存区文件
和工作区文件
都被覆盖为指定版本的文件,但是若暂存区文件
本来就和回退版本的文件相同,则不会改变工作区文件。都会改变
head指针
的指向,指向为恢复的提交版本,回退命令可以用来撤销某次错误的提交,也可以用来回退到先前的版本其实除了指定版本号(哈希值),还可以使用
相对引用
的方式来实现版本回退。
虽然在你的本地分支中使用 git reset
很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦!
为了撤销更改并分享给别人,我们需要使用 git revert

删除文件
我们通过
鼠标右键删除
的文件,删除是工作区的文件,这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status
命令会立刻告诉你哪些文件被删除了。现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令
git rm <文件名>
删掉,这个命令不仅会从你的工作目录中删除test.txt
文件,还会将其标记为已删除状态,并添加到暂存区(staging area),这意味着你准备在下一次提交时
记录这个删除操作。然后再
git commit
,产生一个新的提交。另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把
误删的文件
恢复到最新版本(其实直接ctrl+z也行):
撤销命令
git rm --cached <文件标识>
:文件标识可通过git ls-files(列出暂存区所有文件)
查看,这个指令用于移除暂存区的某个文件
,用于在commit之前撤销某个错误的暂存。git restore <目标文件>
:.
表示全部文件,如果你想撤销对某个文件的所有未提交更改,并将其恢复到与最近一次提交(HEAD)一致的状态,可以使用这个指令。**这个命令不会影响暂存区
的内容,只会影响你的工作目录
**。git restore --staged <目标文件>
:如果你已经将文件添加到了暂存区(即使用了git add
),但之后又想取消该文件的暂存
,同时保持工作目录中的修改不变,可以使用这个指令,这会将文件在暂存区的状态重置
为与最近一次提交相同,但保留工作目录中的更改。
其他指令
git ls-files
:显示暂存区文件git log --oneline
:显示历史提交记录git reflog --oneline
:查看完整历史的简略信息,记录了你的每一次命令(包括,提交,切换,回退等)git diff HEAD -- readme.txt
:可以查看工作区readme.txt
文件,和版本库里面最新提交中的readme.txt
文件的区别。
工作区文件状态
未跟踪(U)
就比如你新建了一个文件,它就是未跟踪的,你可以直接把它删了,就好像从未新建过一样。
已跟踪
新添加(A):第一次被git暂存,之前版本记录没有此文件,标志着开始被git跟踪
未修改(无符号):三个区域文件内容一致,通常在一次版本提交后
已修改(M):工作区内容or暂存区与版本库内容不同
已删除(D):工作区的一个
已经被跟踪
的文件被删除,但是版本库or暂存区中仍然存在git status
:查看文件详细状态git status -s
:查看文件简略状态
分支管理
概述
每次提交,Git都把它们串成一条时间线
,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支
,即master
分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。

每次提交,master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长。
当我们创建新的分支,例如dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上,就实现了分支切换
。

Git创建,切换一个分支很快,因为除了增加一个dev
指针,改改HEAD
的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交
就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变

假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并,我们管这种合并叫做fast-forward
。

所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支

创建分支
1 | git branch 分支名 |
创建一个新的指针,指向当前分支所指向的提交
,一般是最新的提交。
切换分支
1 | git checkout <branch-name> //切换分支 |
修改head指针指向
1 | git checkout -b <branch-name> //切换到创建的分支上去(执行了两个操作) |
切换分支后,以后提交的版本记录结点就会挂在当前的新分支
上去
在 Git 2.23 版本中,引入了一个名为 git switch
的新命令,最终会取代 git checkout
,因为 checkout
作为单个命令有点超载(它承载了很多独立的功能)。
除了切换分支,git checkout
指令还能让head指针指向具体的提交,而不是分支。只需要执行git checkout 提交的哈希值(输入前几位也行)
即可

除了指定哈希值
来确定提交结点,git还提供了相对引用
的方式,这种方式需要借助指针(分支指针或者head指针)
。
使用
^
向上移动 1 个提交记录使用
~<num>
向上移动多个提交记录,如~3
通过下面这个指令强制移动分支指针main到c4的第三级父节点。
查看所有分支
1 | $ git branch |
git branch
命令会列出所有分支,当前分支前面会标一个*
号。然后,我们就可以在dev
分支上正常提交
修改当前分支名
1 | git branch -M master //修改当前分支名为master |
合并分支与删除分支
1 | git merge <分支名> //把一条分支合并到当前分支,一般是合并到主分支master(要先切换到主分支) |
把一条分支合并到当前分支,一般是合并到主分支master(要先切换到主分支)
1 | git branch -d <分支名> //删除被合并的分支指针 |
要注意的是删除分支并不会删除分支上的任何提交,只是删除一个指针。
合并冲突
fast-forward
如果 dev分支基于 master分支创建,然后在 master 分支上没有新的提交,那么将 dev 分支合并回 master分支时,就可以进行 fast-forward 合并。直接让master指向dev指向的提交即可。

这个时候不会产生新的结点
,也不会有合并冲突,之后我们甚至可以删除dev分支
但是fast-forward有一个缺点,就是删除dev分支后,就看不出曾经有分支的痕迹了。如果要强制禁用Fast forward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息
1 | git merge --no-ff -m "merge with no-ff" dev |
因为本次合并要创建一个新的commit,所以加上-m
参数,把commit描述写进去。
不使用Fast forward
模式,merge后就像这样,此时即便删除了dev分支,也能看出分支存在的痕迹,因为删除分支不会删除提交结点。

Merge 合并

Git 会创建一个新的提交,称为
合并提交
。这个提交有两个父节点
:一个是目标分支
的最新提交,另一个是当前分支
的最新提交。在合并两个最新提交的时候可能存在冲突,比如两个提交对
同一个文件的相同部分
做了不同的修改
,这个时候需要手动解决冲突合并提交包含了解决冲突后的结果,或者在没有冲突的情况下,包含了源分支提交的全部内容
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本;
你和你的小伙伴们每个人都在dev
分支上干活,每个人都有自己的分支,时不时地往dev
分支上合并就可以了。
所以,团队合作的分支看起来就像这样:

git rebase
在上文我们看到了,使用git merge
合并两个分支出现冲突时,总会产生新的提交
,并导致最后的提交记录不是线性的
,而是分叉的

而git rebase
也能实现合并操作,并且能保证合并后的历史记录是线性的。
如果我们想把feature1分支合并到master分支,使用rebase,我们需要先切换到feature1分支,然后执行git rebase master
表明当前分支
要进行变基操作
,以master分支为新的根基
,然后feature1分支的所有独有提交,都会把master分支的最新提交,当作父节点,进行一次合并,并按原来的顺序挂载到master分支上。
如果在变基过程中出现冲突,Git 会暂停变基操作,并提示你解决冲突。你需要手动编辑文件以解决冲突,然后继续变基操作:git rebase --continue
,如果决定放弃当前的 rebase 操作,可以使用:git rebase --abort
假设原始的历史记录如下:
1 | A---B---C---D---E (master) |
执行 git checkout feature1
然后 git rebase master
后,历史记录变为:
1 | A---B---C---D---E (master) |
这里,F
, G
, H
被重新应用为 F'
, G'
, H'
。尽管它们代表相同的代码更改,但由于基点改变,它们成为了新的提交对象,拥有不同的哈希值。
注意
不能在刚创建一个仓库的时候就创建,查看分支。要先进行一次commit
操作,才会真正建立master
分支。这是因为分支的指针要指向提交结点的,只有进行了提交,才有指针指向该提交,才算是真正的建立了分支,成为一个有效的对象。
git远程仓库
保存版本库的历史记录,多人协作
创建远程仓库
注册账号
由于
本地Git仓库
和GitHub仓库
之间的传输是通过SSH
加密的,所以,需要一点设置:创建SSH Key。在用户主目录下(
比如:C:\Users\<用户名>\.ssh
),看看有没有.ssh
目录,如果有,再看看这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:1
ssh-keygen -t rsa -C "youremail@example.com"
你需要把邮件地址换成你自己的邮件地址,然后
一路回车
,使用默认值
即可,由于这个Key也不是用于军事目的,所以也无需设置密码。如果一切顺利的话,可以在用户主目录(比如:C:\Users\<用户名>\.ssh)
里找到.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。为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
当然,GitHub允许你添加
多个Key
。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
创建一个远程仓库,初始为空,获得
远程仓库地址
。本地Git仓库添加
远程仓库
地址git remote add <远程仓库别名> <远程仓库地址>
,例如:
git remote add origin https://gitee.com/lidongxu/work.git
远程地址添加一次就行,保存在
git仓库
推送本地Git仓库版本记录到远程仓库
git push -u <远程仓库别名> <本地分支名>:<远程分支名>
例如,git push -u origin master
,这里的master是master:master
的简写,指定把本地的哪个分支
提交到远程哪个分支名
,注意这是一个更新操作,不是合并操作- 在执行此命令前要确保当前本地分支是
master
分支 - 如果远程库是空的,我们第一次推送
master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送到远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取
时就可以省去-u
。 - 第一次使用gitee需要填写登录信息,如果需要修改gitee登录信息,可以在
控制面板->window凭据->普通凭据
处修改,删除。
当你第一次使用Git的
clone
或者push
命令连接GitHub时,会得到一个警告:1
2
3The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?这是因为Git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时,需要你确认GitHub的Key的指纹信息
是否真的来自GitHub的服务器
,输入yes
回车即可。Git会输出一个警告,告诉你已经把GitHub的Key添加到本机的一个信任列表里了。这个警告只会出现一次,后面的操作就不会有任何警告了。
移除远程仓库
git remote -v
:获取所有远程仓库git remote remove <远程仓库别名>
,移除远程仓库,不同远程仓库的别名不能相同。此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。
克隆远程仓库
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。
你也许还注意到,GitHub给出的地址不止一个,默认的git://
使用ssh
,速度快,但也可以使用https
等其他协议。
使用https
除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令
,但是在某些只开放http
端口的公司内部就无法使用ssh
协议而只能用https
。
新建一个文件并打开,右键打开
git bash
命令行,执行如下代码1
git clone <远程仓库地址>
这个操作建立了
本地仓库
和远程仓库
的连接,git 会自动为远程仓库创建一个默认的别名,这个别名叫做origin
,所以可以省略添加远程仓库的操作例如:
git clone https://gitee.com/lidongxu/work.git
git clone -b template url
,指定克隆远程仓库地址(url)
的template分支
多人协作
推送分支push
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支
,这样,Git就会把该分支,推送到远程库对应的远程分支上:
1 | git push -u origin master |
-u
的作用是建立远程分支origin/master
和本地当前分支的联系,后续提交可以省略。这里的master是master:master
的简写,指定把本地的哪个分支
提交到远程哪个分支名
要注意的是,push操作是更新,替换操作,而不是合并操作,但如果在远程同名分支有新的提交(在本地同名分支没有的提交),还会提示我们先进行pull操作,关于pull操作在后文有介绍。
1 | git push |
这个操作默认把本地当前分支
,推送到origin
仓库的同名远程分支。
但是,并不是一定要把本地所有分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master
分支是主分支,因此要时刻与远程同步;dev
分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
抓取分支pull
- 在本地分支上产生了几个版本记录后,想提交(push)该分支到远程仓库,执行
git push origin <branch-name>
- 如果推送失败,则因为远程分支比你的本地新,需要先用
git pull
拉取远程代码; git pull
是git fetch origin/当前分支名
和git merge origin/当前分支名
两个操作的简写,也就是说git pull
默认会从origin
远程仓库,拉取与当前分支同名的分支
,并将其与本地当前分支
合并。- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用
git push origin <branch-name>
推送就能成功! - 如果
git pull
提示no tracking information
,则说明本地分支
和远程分支
的链接关系
没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
。
使用github
我们一直用GitHub作为免费的远程仓库,如果是个人的开源项目,放到GitHub上是完全没有问题的。其实GitHub还是一个开源协作社区,通过GitHub,既可以让别人参与你的开源项目,也可以参与别人的开源项目。
在GitHub出现以前,开源项目开源容易,但让广大人民群众参与进来比较困难,因为要参与,就要提交代码,而给每个想提交代码的群众都开一个账号那是不现实的,因此,群众也仅限于报个bug,即使能改掉bug,也只能把diff文件用邮件发过去,很不方便。
但是在GitHub上,利用Git极其强大的克隆和分支功能,广大人民群众真正可以第一次自由参与各种开源项目了。
如何参与一个开源项目呢?比如人气极高的bootstrap项目,这是一个非常强大的CSS框架,你可以访问它的项目主页https://github.com/twbs/bootstrap
,点Fork
就在自己的账号下克隆了一个bootstrap仓库,然后,从自己的账号下clone
1 | git clone git@github.com:michaelliao/bootstrap.git |
一定要从自己的账号下clone自己fork的仓库,这样你才能推送修改。如果从bootstrap的作者的仓库地址git@github.com:twbs/bootstrap.git
克隆,因为没有权限,你将不能推送修改。
Bootstrap的官方仓库twbs/bootstrap
、你在GitHub上克隆的仓库my/bootstrap
,以及你自己克隆到本地电脑的仓库,他们的关系就像下图显示的那样:

如果你想修复bootstrap的一个bug,或者新增一个功能,立刻就可以开始干活,干完后,往自己的仓库推送。
如果你希望bootstrap的官方库能接受你的修改,你就可以在你的fork的仓库
上发起一个pull request
(注意不是在官方仓库上发起)到原始仓库。到当然,对方是否接受你的pull request就不一定了。