一、远程仓库
1.1 分布式与主从之分
Git 是分布式版本控制系统,同一个 Git 仓库可以分布到不同机器上。最早只有一台机器上有原始版本库,此后别的机器可以"克隆"它,而且每台机器上的版本库其实都一样,并没有主次之分。
其实一台电脑也能克隆多个版本库(不同目录),但是实际情况是找一台电脑充当"服务器"角色,每天 24 小时开机:每个人从它克隆一份到本机,把自己的提交推送上去,也从它拉取别人的提交。
1.2 自建服务器与托管平台
可以选择:
- 自己搭一台跑 Git 的服务器托管代码;
- 或者使用线上托管平台:
GitHub是国外网站、速度较慢,国内课堂与企业更常用码云(Gitee)
二、新建与克隆远程仓库
2.1 新建仓库
新建远程项目仓库时填写基本信息(仓库名 / 路径 / 是否开源),创建成功后还可以再把仓库设置为开源或私有。从创建好的远程仓库里能看到,之前本地学过的分支同样存在于远程仓库并被管理起来------刚创建的仓库有且只有一个默认的 master 分支。
2.2 两种传输协议
克隆用 git clone,后面跟远端仓库链接,链接从仓库页的"克隆/下载"里获取。SSH 和 HTTPS 是 Git 最常用的两种数据传输协议。
使用 HTTPS 方式没有前置要求,直接就能克隆,代价是每次都要输账号密码:
bash
$ git clone https://gitee.com/hyb91/git_teaching.git
Cloning into 'git_teaching'...
Username for 'https://gitee.com': hyb91
Password for 'https://hyb91@gitee.com':
remote: Enumerating objects: 4, done.
Unpacking objects: 100% (4/4), 1.80 KiB | 1.80 MiB/s, done.
$ ls git_teaching/
README.en.md README.md
SSH 协议使用公钥加密和公钥登录机制,更安全,但需要先把公钥放到服务器上由 Git 服务器管理。没配公钥就直接克隆,服务器会拒绝:
bash
$ git clone git@gitee.com:hyb91/git_teaching.git
git@gitee.com: Permission denied (publickey).
fatal: Could not read from remote repository.
2.3 生成并添加公钥
- 第一步:创建 SSH Key。先看用户主目录下有没有
.ssh目录,里面有没有id_rsa和id_rsa.pub,有就直接跳到下一步;没有就生成:
bash
# 注意要输入自己的邮箱,然后一路回车使用默认值
$ ssh-keygen -t rsa -C "123123@qq.com"
Generating public/private rsa key pair.
Your identification has been saved in /home/hyb/.ssh/id_rsa
Your public key has been saved in /home/hyb/.ssh/id_rsa.pub
$ ls -a .ssh/
. .. id_rsa id_rsa.pub known_hosts
这两个就是 SSH Key 的密钥对:id_rsa 是私钥,不能泄露出去;id_rsa.pub 是公钥,可以放心告诉任何人。
- 第二步:点开平台的"SSH 公钥"选项,把
id_rsa.pub内容贴进去,确认后输入账号密码认证即可:

再次克隆就成功了:

多人协作时,GitHub/Gitee 允许添加多个公钥,把每个人电脑上的 Key 都加上,就能在每台电脑上往远端提交推送。
2.4 origin 与 git remote
克隆之后,Git 会自动把本地 master 分支和远程 master 分支对应起来,并且远程仓库的默认名称是 origin:
bash
$ git remote
origin
$ git remote -v
origin git@gitee.com:hyb91/git_teaching.git (fetch)
origin git@gitee.com:hyb91/git_teaching.git (push)
上面的输出显示了可以抓取和推送的 origin 地址;如果没有推送权限,就看不到 push 的地址。
三、推送与拉取
3.1 push
本地克隆成功后就可以往仓库里提交内容,内容先只进本地仓库,要推到远端就需要用 git push------它把本地分支版本上传到远程并合并,格式如下:
bash
git push <远程主机名> <本地分支名>:<远程分支名>
# 如果本地分支名与远程分支名相同,可以省略冒号:
git push <远程主机名> <本地分支名>
把本地 master 推到 origin 主机的 master:
bash
$ git push origin master
To gitee.com:hyb91/git_teaching.git
c6ce3f0..7ce3183 master -> master
推送成功。使用 SSH 协议时不必每次输密码,方便很多;用 HTTPS 则每次推送都必须输入口令。
3.2 身份要一致
如果之前设置过全局的 user.name 和 user.email,这两项配置需要和 Gitee 上配置的用户名、邮箱一致,否则会出错;从来没设置过的,第一次提交时会报错,需要重新配置。
3.3 pull:拉取并合并
在 Gitee 上直接在线修改 README.md 后,远程仓库就领先本地一个版本。想让本地保持最新,需要拉取远端代码并合并:
bash
git pull <远程主机名> <远程分支名>:<本地分支名>
# 如果远程分支是与当前分支合并,则冒号后面的部分可以省略
git pull <远程主机名> <远程分支名>
实际执行结果:
bash
$ git pull origin master
* branch master -> FETCH_HEAD
7ce3183..60e6b0a master -> origin/master
Updating 7ce3183..60e6b0a
Fast-forward
README.md | 2 ++
1 file changed, 2 insertions(+)
push 是"上传并合并",pull 是"取回并合并",本地与远端一致时 pull 的结果就是 Fast-forward 快进策略。
四、三个实用的仓库级配置
4.1 .gitignore:忽略不该提交的文件
日常开发中有些文件不想或不应该提交到远端,比如保存了数据库密码的配置文件。在 Git 工作区根目录下创建一个 .gitignore 文件,把要忽略的文件名填进去,Git 就会自动忽略。
gitee 在创建仓库时就可以帮我们生成,但需要主动勾选;当时没选,事后在工作区补建一个也行。例如想忽略所有以 .so 和 .ini 结尾的文件:

检验 .gitignore 是否生效的标准就是 git status 是否显示 working tree clean。可以看到相应后缀的文件被忽略:

三个细节:
-
添加一个被忽略的文件,用
git add -f [filename]强制添加:
-
查询规则命中:
git check-ignore -v a.so,(eg输出:.gitignore第 3 行*.so忽略了该文件
-
规则
.*会把.gitignore自己也排除掉,加一条例外规则!.gitignore即可(把指定文件排除在规则外的写法:!+文件名
可以看到文件被追踪了:

4.2 alias:给命令起外号
Git 支持简化命令:
bash
$ git config --global alias.st status # git st 等价于 git status
$ git config --global alias.last 'log -1' # git last 显示最近一次提交
--global 是全局参数,对本机所有 Git 仓库生效;不加则只对当前仓库起作用。
配置别名:


4.3 tag标签
tag 可以简单理解为对某次 commit 的标识,相当于起了个别名。项目发布版本时,针对最后一次 commit 打一个 v1.0 这样的标签来标识里程碑,意义在于:commit id 是一串难以记住的十六进制数字,标签则一定起得容易记住且有含义,需要回退到某个重要版本时能快速定位。
bash
$ git tag v1.0 # 默认打在最新提交上
$ git tag # 查看所有标签
$ git tag v0.9 c6ce3f0 # 在指定 commit 上打标签
$ git tag -a v1.0 -m "说明" [commit_id] # 带说明的标签
$ git show v1.0 # 查看标签信息
注意标签不是按时间顺序排列,而是按字母排序。标签只存储在本地、不会自动推送到远程,所以打错了可以安全删除(git tag -d v0.9)。要推送到远端:
bash
$ git push origin v1.0 # 推送指定标签
$ git push origin --tags # 一次推送全部标签
已经推到远程的标签要删除就麻烦一点:先在本地 git tag -d v1.0,再从远程删除------删除命令也是 push,但格式特殊:
bash
$ git push origin :refs/tags/v1.0
- [deleted] v1.0
实际使用:
给当前分支打标签和查看:


给当前分支的commit打标签:

打附有说明的标签:

删除标签:

推送标签:

同时删除在本地和远端的标签:

五、多人协作模式一:同一分支上协作

准备工作:
- 把项目克隆到本机(可以在 Windows 上再克隆同一仓库模拟多人协作)
- 将用户添加进开发者列表
实际项目中绝对不允许直接在 master 分支上修改代码(为了保证主分支稳定),所以开发新功能时常常新建其他分支供迭代使用。
在 Gitee 上新建远程 dev 分支后,本地拉取就能看到:
bash
$ git pull
* [new branch] dev -> origin/dev
Already up to date.
$ git branch -r
origin/HEAD -> origin/master
origin/dev
origin/master
$ git checkout -b dev origin/dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
这里有个容易忽略的点:git branch 只能查看本地分支,要看远程分支得加 -r,而前提是先 pull 一次拉取最新远端内容。
两名开发者A和B在 dev 上各自提交并 push origin dev。但B在推送时可能失败------因为B的最新提交和A已推送的提交可能有冲突。解决办法:先 git pull 把最新提交从 origin/dev 抓下来,在本地合并、解决冲突,再推送。
bash
$ git pull
Updating 305f78a..72c5345
Fast-forward
file.txt | 1 + # 看到小伙伴的代码了

整个流程可以总结成五步:
- 先试着
git push origin branch-name推送自己的修改; - 推送失败说明远程分支比你本地新,先
git pull试图合并; - 合并有冲突就解决冲突,并在本地提交;
- 没有冲突或解决掉冲突后再
push就能成功; - 功能开发完毕,把分支
merge进master,最后删除分支。
六、多人协作模式二:一人一个 feature 分支

多需求并行时,不该让多人挤在一个分支上,而是一个需求或一个功能点就创建一个 feature 分支。

6.1 接手别人未完成的分支
如果开发者A需要临时接手B的工作,此时要做的操作是:
bash
# 必须先拉取远端仓库内容
$ git pull
* [new branch] feature-2 -> origin/feature-2
# 确认远程已有 feature-2
$ git branch -a
remotes/origin/feature-2
$ git checkout -b feature-2 origin/feature-2
Branch 'feature-2' set up to track remote branch 'feature-2' from 'origin'.
建立关联:checkout -b feature-2 origin/feature-2 把本地分支和远程分支关联起来,否则将来只用 git push 推送会失败
关联建立后本地代码与远端严格一致,接着开发并推送:
bash
$ git commit -m"modify function2" && git push origin feature-2
e1233f1..1079ae7 feature-2 -> feature-2
6.2 规范合并到 master
功能开发完毕后必须合并到 master ,步骤如下:
- 切回
master拉取,保证本地master是最新内容 - 再切回自己的分支
merge master,让冲突在自己的分支上解决 - 解决冲突后,切换回
master分支合并 - 推送本地
master到远程(pull request表单
bash
$ git checkout master && git pull # 保证本地 master 最新
$ git checkout feature-1 && git merge master # 在自己的分支吸收 master
$ git push origin feature-1 # 保证远程分支也是最新的
$ git checkout master && git merge feature-1 # 再合并回 master
$ git push origin master

Q:既然最终要合并到 master,为什么合并前还要 push origin feature-1?
A:两个原因------feature-1 已经 merge 进来新内容,为了保证远程分支最新;而实际开发中 master 的 merge 操作一般不是我们自己在本地做,而是由其他人员或平台在远程分支上操作,所以要保证远程分支是最新的。另外,如果 merge 出现冲突,不要忘记必须 commit 之后才能 push。
6.3 远程分支删除但本地分支还在的解决方法
远程分支删除后,git branch -a 依然能看到它们的残留。用 git remote show origin 可以查看 remote 地址、远程分支以及本地分支的对应关系,那些远端已不存在的分支会被标成 stale:
bash
Remote branches:
master tracked
refs/remotes/origin/dev stale (use 'git remote prune' to remove)
按提示执行 git remote prune origin 即可清理:
bash
$ git remote prune origin
* [pruned] origin/dev
七、企业级开发模型
7.1 DevOps 的由来
一个软件从零到交付,大致包括规划、编码、构建、测试、发布、部署和维护几个阶段。最初程序简单,一个人能包办全部;随着软件规模与复杂度攀升,开始出现精细化分工。但传统 IT 组织下开发团队(Dev)追求变化、运维团队(Ops)追求稳定,双方诉求冲突:敏捷团队把持续交付作为目标,运维团队则为了线上稳定强调变更控制,部门墙由此建立,不利于 IT 价值最大化。
为了弥合两者鸿沟,DevOps 登上舞台。它是 Development 和 Operations 的组合词,是一种重视开发人员与 IT 运维人员之间沟通合作的文化、运动或惯例;通过自动化"软件交付"和"架构变更"的流程,让构建、测试、发布软件更加快捷、频繁和可靠。而一个软件的迭代说到底是代码的迭代,代码要管理就离不开 Git------这就是 Git 与 DevOps 的关系。
7.2 四类环境
开发环境:程序员日常开发调试用的服务器,为方便调试一般打开全部错误报告和测试工具,是最基础的环境;测试环境:开发环境到生产环境的过渡环境,程序在它上面工作不正常,就肯定不能发布到生产机;预发布环境:为避免测试环境与线上环境差异导致缺陷漏测而设立,配置基本与生产环境一致,是产品质量最后一道防线;它不在线上集成服务器范围内,是单独的一些机器;生产环境:正式提供对外服务的线上环境,我们在移动端或 PC 端访问到的 App 都属于它。
主线可以概括成开发 → 测试 → 上线。规模稍大的公司环境不止这几个,正式上线前可能还有仿真/灰度环境,也可能有多套测试环境以满足不同版本的测试需要。一句话:一个项目的开始从设计开始,而一个项目的成功则从测试开始------测试体系的完善程度,是衡量软件企业整体水平的重要指标之一。
7.3 Git 分支设计规范
针对不同环境,一般会设计如下分支:
| 分支 | 名称 | 适用环境 |
|---|---|---|
master |
主分支 | 生产环境 |
release |
预发布分支 | 预发布 / 测试环境 |
develop |
开发分支 | 开发环境 |
feature |
需求开发分支 | 本地 |
hotfix |
紧急修复分支 | 本地 |
各分支的职责边界:
master是只读且唯一分支,用于部署到正式发布环境,一般由合并release得到;任何情况下不允许直接在上面改代码,所有推送都应该打tag做记录便于追溯,且该分支不可删除;release基于develop创建(前提是本次上线所有feature已合并进develop),可部署到测试或预发布集群,命名以release/开头,建议release/version_publishtime;它主要用于提交测试人员做功能测试,提测以它的代码为基准;若在它上面测出问题,需要回归验证develop;它属于临时分支,上线后可选删除;develop基于master创建,同样是只读且唯一分支,始终保持最新完成以及 bug 修复后的代码,可部署到开发环境对应集群;可根据需求大小决定由feature合并,还是直接在它上面开发(非常不建议);feature以develop为基础创建,命名以feature/开头,建议feature/user_createtime_feature;开发完成后需合回develop,需求一旦上线便删除;hotfix是线上 bug 修复分支,线上出现紧急问题时基于master创建,命名以hotfix/开头,建议hotfix/user_createtime_hotfix;修复完成后合并到master和develop并推送远程,一旦修复上线便删除。

7.4 Git Flow 不是万金油
上面这套就是企业级常用的 Git Flow 模型,但它并不适用于所有团队、所有环境和文化。如果你采用持续交付,就会想要尽可能简化交付过程的东西;有人喜欢基于主干的开发模式、喜欢用特性标志,而站在测试角度看,这些反倒会吓一跳。
关键在于站在自己团队或项目的角度思考:这种分支模型能解决哪些问题?会带来哪些问题?它为哪种开发提供更好的支持?分支模型最终都是为了让人更容易协作开发,所以要考虑使用者的需求,而不是盲目听信所谓"成功的分支模型"。各家规范会有细微差异,但万变不离其宗,都是为了效率与稳定。