概述
Git是分布式版本控制系统(Distributed Version Control System,简称DVCS)。为什么说是分布式呢?这是相对于集中式而言的。
集中式版本控制系统,典型代表是SVN,版本库是集中存放在中央服务器的。开发人员工作的时候首先要从中央服务器下载最新的版本,然后开发,开发完后,需要把自己开发的代码提交到中央服务器。
看起来没什么问题啊。
但,一旦中央服务器挂了,代码都提交不了,也就没有所谓的版本管理了。
相比之下,Git是分布式的,即使中央服务器挂了,影响也有限。
为什么?这就和分布式的含义有关了,这里的分布式并不是说Git被部署在多台服务器上。
而是说有两种类型的仓库:本地仓库和远程仓库。
- 本地仓库:开发人员自己机器上的Git仓库。
- 远程仓库:远程服务器上的Git仓库。
之间的关系如图所示:
解释:
- clone:克隆,就是将远程仓库复制到本地。
- push:推送,就是将本地仓库代码上传到远程仓库。
- pull:拉取,就是将远程仓库代码下载到本地仓库。
- commit:提交,在自有仓库内提交。
注意看,commit是在自己机器上操作的。这样即使中央服务器挂了,代码的版本管理不受影响,只是不能clone、push和pull。
工作流程
Git的工作流程如下:
- clone(克隆):从远程仓库中克隆代码到本地仓库。
- checkout(检出):从本地仓库中检出一个仓库分支然后进行修订。
- add(添加):在提交前先将代码提交到暂存区。
- commit(提交):提交到本地仓库。本地仓库中保存修改的各个历史版本。
- pull(拉取):从远程库拉到本地库,自动进行合并(merge),然后放到到工作区,相当于fetch+merge。
- push(推送):修改完成后,需要和团队成员共享代码时,将代码推送到远程仓库。
安装
客户端
Windows
下载Git并按安装。
地址:https://git-scm.com/download
macOS
brew install git
在macOS上,我们通过brew
安装git。
1 | brew install git |
在安装完成后,我们可能会发现通过git --version
发现没有生效。这时候只需要退出控制台,重新进入即可。
卸载之前的git
如果之前参考过https://www.runoob.com/git/git-install-setup.html
,现在需要升级的话,建议先卸载原来的git。
1 | sh /usr/local/git/uninstall.sh |
此外,在/usr/bin/
目录下,还有git。我们直接卸载会发现卸载失败,原因是macOS升级从10.11
版本后引入了Rootless机制,即时是使用root身份也无法删除/System
、/bin
、/sbin
、/usr
等目录,所以我们还需要关闭Rootless机制。
重启,按住Command+R,进入恢复模式,在顶部菜单栏找到Terminal,打开。输入命令csrutil disable
,关闭Rootless机制,然后便可以卸载之前的git。
卸载完成之后,安装git。
1 | brew install git |
如果brew已经安装成功过git,此时需要执行下面命令
1 | brew link --overwrite git |
升级完成后建议还是把刚刚关闭的Rootless开启,重启,按住Command+R,进入恢复模式,打开Terminal执行命令csrutil enable
。
更换brew的源
如果brew的操作特别的慢的话,可以考虑更换brew的源。
打印当前的源
1 | cd "$(brew --repo)" |
切换阿里源
1 | cd "$(brew --repo)" |
切换腾讯源
1 | cd "$(brew --repo)" |
切换清华源
1 | cd "$(brew --repo)" |
切换官方原始源
1 | cd "$(brew --repo)" |
brew代理
配置临时代理
一般建议配置临时代理,示例代码:
1 | export ALL_PROXY=socks5://【地址】:【端口】 |
部署代理
- 下载
TcpRoute2
,地址:
https://github.com/GameXG/TcpRoute2 - 修改
config.toml.example
文件名,去除.example
后缀。 - 修改
config.toml.example
文件内容,addr=":7070"
。
服务端
关于服务端,我们可以利用互联网上提供的一些代码托管服务。
最常见的就是Github:https://github.com
如果Github访问不顺畅的话,可以借助工具DevSidecar。
也可以考虑国内的替代品,Gitee(https://gitee.com),以及利用Gitlab,自行搭建Git服务端。
环境配置
此处的环境配置,指的是客户端的环境配置。
设置用户信息
示例代码:
1 | git config --global user.name "KakaWanYifan" |
查看配置信息
示例代码:
1 | git config --list |
运行结果:
1 | diff.astextplain.textconv=astextplain |
只查看某个配置信息
示例代码:
1 | git config user.name |
运行结果:
1 | KakaWanYifan |
配置文件
通过上面的命令设置的信息会保存在当前用户的.gitconfig
文件中。
例如:C:\Users\wanyf\.gitconfig
设置代理
设置命令
1 | git config --global http.proxy http://xxx:8080 |
需要用户名密码
1 | git config --global http.proxy http://【用户名】:【密码】@http://10.10.10.10:8080 |
查看是否生效
1 | git config --get --global http.proxy |
删除代理
1 | git config --global (或 --local) --unset http.proxy |
获取Git仓库
获取Git仓库有两种方式:
- 在本地初始化一个Git仓库
- 从远程仓库克隆
在本地初始化一个Git仓库
执行步骤如下:
- 创建一个空目录作为本地Git仓库
- 执行命令
git init
如果创建成功,会看到.git文件夹(此文件夹为隐藏文件夹),里面存储了日志信息和文件版本信息等内容。
从远程仓库克隆
此外,还可以从远程仓库克隆。
命令如下:
1 | git clone 远程Git仓库地址 |
本地仓库的操作
文件的状态
Git工作目录下对于文件的修改(增加、删除、更新)会存在几个状态,这些修改的状态会随着我们执行Git的命令而发生变化。
之间的关系如下:
解释说明:
- 新创建的文件会处于未跟踪状态
- 被修改的已有的文件会处于未暂存状态
- 通过
git add
进入暂存状态 - 通过
git commit
提交
查看文件状态
git status
,查看文件状态。
示例代码:
1 | git status |
运行结果:
1 | On branch master |
添加文件到暂存区
git add
例如,将未跟踪的文件加入暂存区。
示例代码:
1 | git add 1.txt |
运行结果:
1 | On branch master |
提交暂存区的修改到本地仓库
git commit
,将暂存区的文件修改提交到本地仓库。
示例代码:
1 | git commit |
运行结果:
1 | git status |
利用git commit
提交,会打开一个类似VIM编辑器,然后我们输入每次提交描述。也可以通过git commit -m '提交描述'
直接输入提交描述。
查看日志
通过git log
命令查看日志。
命令形式:git log [option]
options:
--all
:显示所有分支--pretty=oneline
:将提交信息显示为一行--abbrev-commit
:使得输出的commitId更简短--graph
:以图的形式显示
示例代码:
1 | git log |
运行结果:
1 | commit d46c6bf62dca1e24e0e12717647e67bc273a7ed6 (HEAD -> master, origin/master, origin/HEAD) |
回退
git reset
,回退。
例如,回退到某个commit之前的版本。
git reset --hard commitID
示例代码:
1 | git reset 6d977c9932da5931cfd1d910e87c9de29d8ba852 |
那么,怎么再回去呢?
如果知道commitID的话,再用git reset --hard commitID
即可。
如果不知道的话,可以通过git reflog
找回。
如果我们只是git add
,还没有提交,但是想取消暂存怎么办呢?直接git reset
。
git reset --hard HEAD^
回退到上个版本。git reset --hard HEAD~n
回退到前n次提交之前,若n=3,则可以回退到3次提交之前。
将文件添加至忽略列表
有时候我们会有些文件无需纳入Git的管理,也不希望它们总出现在未跟踪文件列表。
比如:日志文件,编译过程中的临时文件等。
在这种情况下,我们可以创建一个名为.gitignore的文件(文件名称固定),列出要忽略的文件模式。
例如:
1 | # no .a files |
1 | **/*.csv |
解释说明:**
表示任何子目录,*.csv
表示任何.csv
文件。这样,不论是当前目录下的.csv
文件还是子目录中的.csv
文件都会被忽略。
远程仓库的操作
查看远程仓库
git remote
,会看到每一个远程仓库的简写。
示例代码:
1 | git remote |
运行结果:
1 | origin |
解释说明:
origin,是Git克隆的仓库服务器的默认名字
git remote -v
,查看远程仓库的地址。
示例代码:
1 | git remote -v |
运行结果:
1 | origin git@github.com:KakaWanYifan/kakawanyifan.com.git (fetch) |
解释说明:fatch
是抓取,push
是推送,分别代表抓取和推送的地址。
(在上文,我们查看brew的源,用的也是该命令。)
可以通过git remote show origin
,查看更详细的信息。
示例代码:
1 | git remote show origin |
运行结果:
1 | * remote origin |
添加远程仓库
git remote add <shortname> <url>
,添加一个新的远程Git仓库,同时指定一个可以引用的简写。
示例代码:
1 | git remote add origin git@github.com:KakaWanYifan/d.git |
另外,一个本地仓库可以对应多个远程仓库。
示例代码:
1 | git remote add origin git@github.com:KakaWanYifan/d.git |
运行结果:
1 | my git@github.com:KakaWanYifan/e.git (fetch) |
从远程仓库克隆
git clone <URL> [本地目录]
其中[本地目录]
可以省略,能自动生成一个目录。
移除无效的远程仓库
git remote rm <shortname>
,移除远程仓库。
注意,该操作只在本地仓库生效,对远程仓库没有影响。
从远程仓库中抓取与拉取
git fetch
,从远程仓库获取最新版本到本地仓库,不会自动merge。
git pull
,从远程仓库获取最新版本并merge到本地仓库。
示例代码:
1 | git fetch git@github.com:KakaWanYifan/d.git |
解释说明:fetch之后,在目录中并没有相关的文件,需要在手动执行git merge
。
示例代码:
1 | git pull git@github.com:KakaWanYifan/d.git |
运行结果:
1 | 1.txt 2.txt 3.txt 4.txt 5.txt |
推送到远程仓库
git push [-f] [--set-upstream] [远端名称 [本地分支名][:远端分支名]]
、
- 如果远程分支名和本地分支名称相同,则可以只写本地分支。
git push origin master
-f
:强制覆盖--set-upstream
:推送到远端的同时并且建立起和远端分支的关联关系。
git push --set-upstream origin master
- 如果当前分支已经和远端分支关联,则可以省略分支名和远端名。
所以很多时候,我们只需要命令git push
。
分支的操作
查看分支
列出所有本地分支
示例代码:
1 | git branch |
运行结果:
1 | * master |
列出所有远程分支
示例代码:
1 | git branch -r |
运行结果:
1 | origin/HEAD -> origin/master |
列出所有本地分支和远程分支
示例代码:
1 | git branch -a |
运行结果:
1 | * master |
创建分支
git branch
,后面跟着新分支名。
示例代码:
1 | git branch dev |
运行结果:
1 | dev |
切换分支
示例代码:
1 | git checkout dev |
运行结果:
1 | Switched to branch 'dev' |
推送至远程仓库分支
示例代码:
1 | git push origin dev |
运行结果:
1 | Total 0 (delta 0), reused 0 (delta 0) |
合并分支
示例代码:
1 | git merge dev |
运行结果:
1 | Already up to date. |
注意,有时候合并操作不会如此顺利,如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git就没办法合并它们,同时会提示文件冲突。此时需要我们打开冲突的文件并修复冲突内容,最后执行git add
命令来标识冲突已解决。
删除分支
示例代码:
1 | git branch -d dev |
运行结果:
1 | Deleted branch dev (was 7d7666b). |
如果要删除的分支中进行了一些修改,但坚持要删除此分支,可以将命令中的-d
参数改为-D
。
如果要删除远程仓库中的分支,可以使用命令
1 | git push origin –d branchName |
rebase
还有一种操作,rebase,这种操作也可以起到合并分支的效果,但与merge的逻辑不一样。
merge的逻辑
merge的逻辑是找到这两个分支的祖先commit,在两个分支最新的commit进行三方对比合并。
如图,共同的祖先是commit2,master分支最新的是commit6,develop分支最新的是commit5,merge会基于2,5,6这三个commit进行对比,对每个文件进行一个哈希计算,这个值一样的话说明文件没有改动过。
commit6和commit2对比,如果文件的哈希值不一样,同时commit5和commit2对比,发现一样,说明只有commit6修改了这个文件,这种情况直接合并,不会提示。
commit6和commit2对比,如果文件的哈希值不一样,同时commit5和commit2对比,哈希值不一样,说明两个分支都对同一个文件修改了,则提示冲突,需要我们手动merge。
最后合并完后会生成一个新的commit7
rebase的逻辑
重新基于一个分支上进行commit,就是会把当前分支从祖先的commit后提交的commit都撤销掉,放到一个缓存里面去,然后基于一个分支的后面,把缓存的commit再按顺序一个个新增到这个分支后面。
例如上图,在develop分支进行rebase master分支,则会把develop分支上的基于和master共同的祖先分支commit2的后面的commit4、5撤销掉,在master的最新的commit6后面重新增加commit4、5上去,这时候develop相当于在master最新的commit上逐步提交了两个commit4、5。
标签的操作
Git可以在某一次提交上打上标签,以示重要。通过标签可以非常方便回到某个时间点。
列出所有标签
示例代码:
1 | git tag |
运行结果:
1 | 0.1 |
创建标签
git tag <标签名>
将标签推送至远程仓库
示例代码:
1 | git push origin 0.1 |
运行结果:
1 | Total 0 (delta 0), reused 0 (delta 0) |
检出标签
git checkout -b <新分支名> <标签名>
,新建分支,并指向指定的标签。
示例代码:
1 | git checkout -b xin 0.1 |
运行结果:
1 | Switched to a new branch 'xin' |
删除标签
删除本地标签
示例代码:
1 | git tag -d 0.1 |
运行结果:
1 | Deleted tag '0.1' (was 7d7666b) |
删除远程标签
示例代码:
1 | git push origin :refs/tag/0.1 |
运行结果:
1 | remote: warning: Deleting a non-existent ref. |
SSH协议传输数据
通过SSH协议传输数据。这样对于Github来说,在国内的话,会更快更顺畅。
步骤
- 设置git的user name和email
- 检查是否存在SSH Key
- 生成SSH Key
- GitHub添加SSH Key
- 验证
设置git的user name和email
该部分在上文已经讨论过,不再进行讨论,略。
检查是否存在SSH-Key
示例代码:
1 | cd ~/.ssh |
运行结果:
1 | id_rsa id_rsa.pub known_hosts |
- 该结果说明已经存在了SSH-Key
- Windows系统的方法相同,但是需要注意,要通过
Git Bash
。
生成SSH-Key
1 | ssh-keygen -t rsa -C "第一步配置的邮箱" |
期间会有几个确认,一般直接回车即可。
如果SSH Key已经存在的话,会直接覆盖。
在生成之后,通过命令cat id_rsa.pub
查看秘钥(ssh-rsa开头的),并拷贝。
GitHub添加SSH Key
GitHub点击用户头像,选择setting,新建一个SSH-Key,把上一步拷贝的秘钥复制进去。
验证
示例代码:
1 | ssh -T git@github.com |
运行结果:
1 | Hi KakaWanYifan! You've successfully authenticated, but GitHub does not provide shell access. |
修改
特别的,之前已经是https的链接,现在想要用SSH提交怎么办?
直接修改项目目录下 .git文件夹下的config文件,将地址修改一下就好了。
IDEA中的Git
配置
一般,IDEA会根据我们配置的环境变量,自动去找到Git。如果没有找到,我们可以自行设置。
我们可以点击Test
按钮测试是否正确配置。
操作
加入Git管理
如果有一个项目,之前没有加入到Git的管理中,现在想加入Git管理,方式如下:
忽略列表
一般在IDEA的Java项目中,需要忽略的有:
/.idea/
/target/
【工程名】.iml
添加到暂存区
提交到本地
在IDEA中,有三个地方可以提交。
在工具栏中,勾
。
在顶部菜单栏中,Commit...
。
右键工程,Git
、Commit Directory
。
推送到远程
与"提交到本地"类似,推送同样有三个地方,截图略。
因为我们是直接在本地创建的仓库,所以这时候还需要定义远程仓库的地址。
在本文,我们远程仓库的地址是:git@github.com:KakaWanYifan/git.git
(这种格式的地址,就是我们上文讨论的SSH。)
克隆到本地
在IDEA的启动页面选择"Git from VCS"。
或者,我们可以在顶部菜单栏找到New
、Project from Version Control
从远程拉取
关于从远程拉取,这个很简单,pull
。
需要注意的是,在IDEA中,除了pull
,还有一个Update projects
。
关于Merge
和Rebase
的区别,我们在上文已经讨论过。
版本对比
创建和切换分支
分支合并
规范
分支规范
- 最稳定的代码放在
master
分支上(相当于SVN的trunk分支),我们不要直接在master
分支上提交代码,只能在该分支上进行代码合并操作,例如将其它分支的代码合并到master
分支上。 - 我们日常开发中的代码需要从
master
分支拉一条develop
分支,该分支所有人都能访问,但一般情况下,我们也不会直接在该分支上提交代码,代码同样是从其它分支合并到develop
分支上去。 - 当我们需要开发某个特性时,需要从
develop
分支拉出一条feature
分支,例如feature-name1
与feature-name2
,在这些分支上并行地开发具体特性。 - 当特性开发完毕后,我们决定需要发布某个版本了,此时需要从
develop
分支上拉出一条qa
分支,例如qa-name1-name2
,并将需要发布的特性从相关feature
分支一同合并到qa
分支上,随后将针对qa
分支部署测试环境,测试工程师在该分支上做功能测试,开发工程师在该分支上修改bug。 - 待测试工程师无法找到任何bug时,我们可继续从
master
分支拉出一条release
分支,此时release
的版本号必须根据master
的tag
版本号来递增,例如release1.0.0
,并将qa-name1-name2
分支合并到release1.0.0
,并部署到预发环境,再次验证以后,均无任何bug
,此时可将release
分支部署到生产环境。 - 待上线完成后,将
release
分支上的代码同时合并到develop
分支与master
分支,并在master
分支上打一个tag
,例如v1.0.0
。 - 当生产环境发现bug时,我们需要从对应的
tag上
例如(v1.0.0
)拉出一条hotfix
分支(例如hotfix1.0.1
),并在该分支上做bug修复。待bug完全修复后,需将hotfix
分支上的代码同时合并到develop
分支与master
分支。最后,在master
分支打tag
(例如 v1.0.1)。
管理规范
- 上传内容:保证GIT上保存的是"干净"的代码,不得有编译后再次生成的代码,如Java字节码文件和JSP生成文件,也不能有IDE生成文件。
- 上传注释:必须加简要的注释,注释的内容应包含开发的模块名称以及功能描述。
- 功能提交:
[模块名称]功能描述
,如:[用户模块]用户列表增加手机号字段显示;
。 - BugFix:
[模块名称]Bug-编号:Bug-描述
,如:[用户模块]Bug-1203:用户创建保存失败已修复;
。
- 功能提交:
- 上传质量:提交和合并到分支上的代码尽量保证是自己测试通过的代码,以免影响别的项目/同事。