部门内要做技术分享, 我选择了分享git相关的知识, 现在贴出来, 供大家学习参考.
目录:
文章目录
为什么要使用版本控制系统?
什么是git?
Git is a
free and open source
distributed version control system
designed to
handle everything from small to very large projects
with
speed and efficiency.
– https://git-scm.com/
“自由,开源,分布式的版本控制系统?”
– Pony
“快速, 高效?”
– Jack
“一千个读者心里有一千个哈姆雷特”, 1024个攻城狮就有1024个git.
关于什么是git, 当你用熟悉后心里自然会有一个答案, 别人再多的解释都不如自己的理解.
为什么要使用git?
强大的分支模型, 独特的暂存区设计, 分布式特性…
一些术语
- Workspace(工作区): 我们编辑代码的地方就是工作区
- Stage area/Staging/Index(暂存区): 工作区用来提交更改前可以暂存工作区的变化的地方。
- Local Repository(本地版本库)): 受git控制的所有文件修订历史的本地共享数据库.
- Remote Repository(远程版本库): 受git控制的所有文件修订历史的远程(服务端)共享数据库.
- Branch(分支): 从主线上分离开的副本,默认分支一般叫master
- Commit(提交): 通过
git commit
或者其他操作(如git merge
)会生成一个commit id, 此为一个提交. 实质为将暂存区的修改同步到版本库, 根据语境可分为动词或者名词. - Tag(标记, 标签): 特定提交的别名(如:
git tag v1.0 <commit-id>
). - more…
下述描述中, 如无特别说明, “版本库"特指"本地版本库”.
安装
略.
个人根据自己系统环境(Linux, MacOS, Win)自行安装.
我的环境:
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/tmp
git version
git version 2.21.0.windows.1
git子命令 (ref: https://git-scm.com/docs)
常用命令(应该熟练掌握)
配置相关
config
# git账户配置 git config --global user.email [email protected] git config --global user.name gl # 子命令别名配置 git config --global alias.st status git config --global alias.co checkout git config --global alias.br branch git config --global alias.cm commit -m git config --global alias.df diff git config --global alias.sh stash git config --global alias.ci commit git config --global alias.lg log --graph git config --global alias.lgo log --graph --oneline git config --global alias.logf log --pretty format:"%h%x09%an%x09%ad%x09%s" git config --global alias.cpk cherry-pick
初始化相关
- init (初始化一个版本库)
git init myrepo # 在当前目录下创建版本库myrepo(自动创建myrepo目录)
- clone (克隆版本库)
git clone [email protected]:my-test-group/git-share.git
基本操作
- add (工作区变动添加到暂存区)
- commit (暂存区修改提交到版本库)
- status (查看状态)
git status # 精简输出 git status -s
- diff
git diff # 查看工作区与暂存区的差异 git diff --cached # 查看暂存区和版本库的差异 git diff HEAD # 查看工作区和版本库的差异
- reset(撤销)
分支与合并
- branch(分支)
git branch -a git branch git branch -m new-branch # 创建并切换到新分支
- checkout
git checkout some-branch # 切换到 some-branch分支 git checkout -b new-branch # 基于当前分支 创建并切换到 new-branch分支 # 用暂存区的main.c覆盖工作区的main.c # 注意: 工作区中的main.c会被覆盖, 比较危险, 慎用! git checkout -- main.c # 用暂存区覆盖整个工作区, 非常危险, 慎用!!! git checkout -- .
- merge(合并分支)
- stash(本地修改和暂存区修改 临时 存储起来)
git stash list # 查看有哪些stash git stash save "为本次临时存储写一些说明" # 推荐此种用法 git stash pop # 取出最近一次stash git stash apply stash@{1} # 将倒数第二次stash应用到工作区.(stash从0开始编写) git stash drop stash@{1} # 删除倒数第二次stash, 慎用 git stash clear # 删除所有stash, 慎用
- tag(打标签)
共享与更新项目
-
fetch(下载但不合并)
# 下载所有分支 gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/project/git-share (feature-share) $ git fetch -v From my.gitlab.com:my-test-group/git-share = [up to date] feature-share -> origin/feature-share = [up to date] master -> origin/master # 下载指定分支 gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/project/git-share (feature-share) $ git fetch -v origin master From my.gitlab.com:my-test-group/git-share * branch master -> FETCH_HEAD = [up to date] master -> origin/master
-
remote (远程仓库管理)
# 为远程版本库[email protected]:my-test-group/git-share.git起个别名(绑定)为origin git remote add origin [email protected]:my-test-group/git-share.git # 查看有哪些远程版本库别名 $ git remote origin # 上述一条的详细输出 git remote -v origin [email protected]:my-test-group/git-share.git (fetch) origin [email protected]:my-test-group/git-share.git (push)
-
pull(下载并合并)
# 下载并合并master分支 git pull -v origin master # 下载远程分支dev到本地, 并重新命名为dev-local # dev-local仅为说明参数顺序, 实际应保持远程和本地分支名称一致. git pull -v origin dev:dev-local
-
push(推送)
# 推送本地feature-xxx分支到远程, 并在远程创建同名分支, 并作关联(-u) git push -u origin feature-xxx:feature-xxx # 强制推送, 慎用 git push -f
检查与对比
- log
git log git log --oneline # 简短输出 git log --oneline --graph # 带有"分支树"格式的输出 git log -2 # 查看最近两次提交日志
- diff
- show (查看某次提交具体的修改)
打补丁
- rebase
- cherry-pick(选择合并)
其他
- reflog (查看本地HEAD指针变更记录)
- blame (查看文件有哪些成员修改过, 如果你用过svn blame, 你应该很熟悉)
- flow (开发工作流的封装命令, 具体用法请自行学习, 本文主要讲思路, 实现都是大同小异)
开发工作流
ref: https://nvie.com/posts/a-successful-git-branching-model/
上面的文章和图建议仔细阅读, 会有不少收获
开发工作流总述:
环境
git工作流是基于git的分支功能而来. 也和开发环境有关系.
一般而言: 会有4个代码环境(根据实际情况, 可能会更多).
- 开发环境, 大家可以随时在这里调试(比如改了一行代码, 想看看效果)
- 公共测试环境(可能是测试人员用, 没有测试人员的, 就是开发人员公用)
- 预发布环境, 数据和代码与生产的几乎一样.
为什么说几乎呢?
因为数据可能是每天从生产环境同步一次, 在有新版本要发布时, 会先在预发布环境测试(此时功能已经确定, 可能会有小bug, api鲁棒性问题等). - 正式环境/生产环境
分支名称及生命周期
注意看上图:
共有5个(确切来说, 有2个长驻(长期存在)的分支: dev和master, 还有三类分支: feature, release, hotfix.
实际使用时, 可以根据需要增加一些其他分支, 如bugfix分支,
是一些测试环境的bug修改, 由于也不是新特性, 所以叫feature也不太好, 如"bugfix-get_user_list_no_response"
可以简单地这样认为:
- feature - 开发环境, 代表了一些新功能特性的开发
- dev - 公共测试环境, 代表了功能基本确定, 由feature合并进来
- release - 预发布环境, 代表了某个版本功能完全确定,只有一些小bug的情况
- master - 生产环境, 代表了代码经过测试后, 无bug的情况, 发布到生产环境.
如果环境没有那么多, 比如没有预发布环境, 需要根据情况取舍(比如预发布和公共测试环境为一套环境)
分支生成与删除
其中:
- feature/bug分支是基于dev分支产生,
- release分支基于dev分支产生,
- hotfix分支基于master分支产生.
- feature/bug开发完成后, 要合并到dev分支, 根据需要可即时删除或者后续删除.
- release测试通过后, 要合并到dev和master, 根据需要可即时删除或者后续删除.
- hotfix代表了正式环境的(紧急)bug, 测试通过后, 要合并到dev和master, 根据需要可即时删除或者后续删除.
开发工作流示例
准备: 以gitlab为例子, 建立群组(group)my-group, 然后再创建项目demo, 再基于分支master(gitlab自动创建的)建立分支dev. master为默认分支.
此时大概是这样的:
为了便于查看, 一些命令的输出会精简或者删除, 请注意!
clone
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace
$ cd /d/workspace/
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace
$ git clone [email protected]:my-group/demo.git
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace
$ cd demo/
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
分支准备
上面我们在 /d/worksapce 目录下, clone了demo项目. git status
查看了下状态.
假设现在我们接到一个需求, 功能有"添加到购物车"和"支付", 版本定为1.0.
基于上述的工作流策略及分支命名规则,
我们可以这样:
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 查看所有分支, 发现远端有个dev没clone到本地
$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 远端dev分支clone到本地, 并在本地建立同名分支
$ git pull origin dev:dev
From my.gitlab.com:my-group/demo
* [new branch] dev -> dev
Already up to date.
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 基于dev开一个分支, 代表"添加到购物车"这个功能在这里开发
$ git checkout -b feature-shopping-car dev
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
# 基于dev开一个分支, 代表"支付"这个功能在这里开发
$ git checkout -b feature-pay dev
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-pay)
# 切换分支到 feature-shopping-car, 准备开发 "添加到购物车"这个功能
$ git checkout feature-shopping-car
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
# 查看一下现有分支, 我们正处于feature-shopping-car这个分支上
$ git br -a
dev
feature-pay
* feature-shopping-car
master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master
开发"添加到购物车"这个功能
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
# 模拟完成需求
$ echo "添加商品1到购物车" >> shopping-car.txt
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
$ echo "添加商品N到购物车" >> shopping-car.txt
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
$ echo "添加完成!" >> shopping-car.txt
# 查看当前状态, st为status的别名, 注意参看本文开始的配置, 下此类情况不再赘述
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
$ git st
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
# 工作区变更添加到暂存区
$ git add shopping-car.txt
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
# 提交到版本库
$ git cm "添加到购物车功能完成"
开发"支付"这个功能
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-shopping-car)
# 切换到feature-pay分支准备开发"支付功能"
$ git co feature-pay
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-pay)
# 模拟开发支付功能(计算有错误为故意设置, 后面会用到)
$ cat >pay.txt <<EOF
> 计算总价格: 10+20=40
> 计算完成
> 支付
> 支付完成
> EOF
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-pay)
# 添加到暂存区
$ git add pay.txt
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-pay)
# 支付功能开发完成, 提交到版本库
$ git cm "支付功能完成"
插曲
上面我们有个需求, 版本定为1.0, 我们根据需求开了两个分支:
“feature-shopping-cart"和"feature-pay”, 实际可以根据需要起名为: “feature-1.0-shopping-car”,
因为可能两个并行的版本有同一个功能都在开发, 以示区分.
feature分支我们没有演示推送(push)操作, 这个看情况, 为了避免"磁盘损坏"导致代码丢失的情况发生,
建议feature分支也推送, 用完删除即可.
进入预发布阶段
上面我们已经开发完了所有功能, 在测试没啥问题后, 就可以进入预发布环境了:
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (feature-pay)
# 进入dev分支准备合并
$ git co dev
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (dev)
# 合并"支付"功能到dev分支, 注意--no-ff会保留分支信息, 下同
$ git merge --no-ff feature-pay
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (dev)
# 合并"添加到购物车"功能到dev分支
$ git merge --no-ff feature-shopping-car
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (dev)
# 查看dev的记录, 可以看到分支合并的信息(分叉), git log --oneline --graph
$ git lgo
* 8efcbc0 (HEAD -> dev) Merge branch 'feature-shopping-car' into dev
|\
| * cd6dde0 (feature-shopping-car) 添加到购物车功能完成
* | 0cde2ea Merge branch 'feature-pay' into dev
|\ \
| |/
|/|
| * 2da3542 (feature-pay) 支付功能完成
|/
* 2471ab4 (origin/master, origin/dev, origin/HEAD, master) Initial commit
# 推送dev到远程
$ git push origin dev
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (dev)
# feature合并完了, 再基于dev开一个release分支出来
# 表示此次需求我们功能已经开发完成, 测试也没啥问题
# 进入预发布阶段
$ git co -b release-shoppin$ git co -b release-shopping
# 合并完成后, 可以删除已经合并过的feature相关分支了
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 删除feature分支
$ git br -d feature-shopping-car
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 删除feature分支
$ git br -d feature-pay
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 看看release分支的文件, 是我们期待的
$ ls
pay.txt README.md shopping-car.txt
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 推送release分支, -u表示关联上游分支
$ git push -u origin release-shopping:release-shopping
......
* [new branch] release-shopping -> release-shopping
Branch 'release-shopping' set up to track remote branch 'release-shopping' from 'origin'.
插曲
"feature-*"分支很多时候由开发人员自己控制, 上面根据需求开了两个, 其实可以开更多, 比如
“购物车过期商品过滤”, "支付校验"等.
当众多分支合并到dev时, 导致日志输出可能非常混乱, 难以查看.
建议:
可以自己将feature的提交整理后再合并到dev分支, 可以参考以下几点:
- 无意义或者意义不明确的多个可以合并为一个
如两次提交: “添加一个按钮”, “为按钮增加点击事件”, 就可以合并为一个提交: “添加按钮, 点击执行xxx” - 如果功能分得太细, 导致分支很多, 可以另建立分支, 将分支合并后, 精简提交, 再合并到dev
- feature也不能起得太"大", 比如此次需求起为"feature-shopping", 所有功能放到一个分支里,
可能后续需求有变, 要减少功能时, 这时再想撤回就很麻烦了, 要人工摘出来代码. - 命名方面: feature-shopping-cart, feature-shopping_cart, feature/shopping-cart等都可以, 团队统一规范即可.
一般建议全小写, 用"-"连接.
预发布环境再测试
预发布环境只是与正式环境尽可能一致, 当然不可能完全一致, 所以在这里可以再做一些测试.
测试过程中我们发现, 支付时, 金额计算的有误(看上面的支付修改的"代码"):
计算总价格: 10+20=40
在release分支上发现bug是正常的, 因为还没有上线(发到正式)嘛, 我们改下, 再提交
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 为了演示, 用sed而不用vim改对
$ sed -i 's/10+20=40/10+20=30/g' pay.txt
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 确认下, 我们改对了
$ cat pay.txt
计算总价格: 10+20=30
计算完成
支付
支付完成
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 查看下状态, 工作区有发动
$ git st
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 添加到暂存区
$ git add pay.txt
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 提交到版本库
$ git cm "支付计算错误修改"
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 推送到远程版本库, 注意这里没有写成 git push origin release-shopping,
* 是由于我们之前推送到release分支时,使用了 -u 选项, 已经和远程的release-shopping作了关联
$ git push
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 查看下release分支现有的提交记录
$ git lgo
* 5d477fd (HEAD -> release-shopping, origin/release-shopping) 支付计算错误修改
* 8efcbc0 (dev) Merge branch 'feature-shopping-car' into dev
|\
| * cd6dde0 添加到购物车功能完成
* | 0cde2ea Merge branch 'feature-pay' into dev
|\ \
| |/
|/|
| * 2da3542 支付功能完成
|/
* 2471ab4 (origin/master, origin/dev, origin/HEAD, master) Initial commit
正式发布
经过测试, release分支没啥问题了, 我们来发布.
一般来说, 这一步需要管理员来处理, 毕竟是对master分支有改动的操作.
这里只是为了演示流程:
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (release-shopping)
# 切换到master
$ git co master
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 将release合并到master, 注意--squash选项是合并提交
# 这样merge后, 需要手动commit一下
$ git merge --squash release-shopping
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 查看master的日志还是老样子
$ git lgo
* 2471ab4 (HEAD -> master, origin/master, origin/dev, origin/HEAD) Initial commit
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 查看master的状态, 发现暂存区有变化
$ git st
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: pay.txt
new file: shopping-car.txt
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 我们手动提交下
$ git cm "v1.1: 添加到购物车, 支付功能"
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 再查看日志
$ git lgo
* 142bb0b (HEAD -> master) v1.1: 添加到购物车, 支付功能
* 2471ab4 (origin/master, origin/dev, origin/HEAD) Initial commit
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# master尽量做到一次提交(合并)打一个tag, 以后好回退
$ git tag v1.1 -m "添加到购物车, 支付功能"
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 查看tag
$ git tag -l -n
v1.1 添加到购物车, 支付功能
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# 删除已经合并了的release分支
$ git br -d release-shopping
# 同时删除远程的release分支
$ git push --delete origin release-shopping
To my.gitlab.com:my-group/demo.git
- [deleted] release-shopping
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# master推送到远程
$ git push origin master
gl@DESKTOP-U75AMEJ MINGW64 /d/workspace/demo (master)
# tag也推送上去
$ git push origin v1.1
v1.1开发完成
经过上述的操作, 我们已经看到了一个版本是如何用git工作流来完成的.
最后的结果展示:
dev分支提交图示
master分支提交图示
开发工作流注意事项
- 非长期分支, 分支名称和说明以 “-” 来分隔,
如feature-add_button, release-v1.2_add_privilege, hotfix-1.2.1)) - master仅允许管理员或授权人员推送, 合并等, 普通成员只有读权限.
- ** 合并到master时, 最好git rebase后合并, 或者合并时用
git merge --squash
, 来保证提交日志简洁. - ** 合并到dev时, 如将feature-add_button合并到dev,
先将 feature-add_button的提交精简(无意义的提交合并等)一些,
然后在dev上git merge --no-ff
来合并, 保证dev上保留完整的历史. - feature根据需要可以开多个, 如果某次开发有多个功能,
可按功能开多个feature分支, 避免只开一个分支, 合并时有的功能又不要了, 此时再"拆代码"就麻烦了
一些git服务端
一些git客户端
- TortoiseGit, 界面和TortoiseSVN很像, 新手不建议使用,
在熟悉命令之后再使用, 查看日志比较方便, 其他诸多特性请自行探索. - GitHub Desktop, Github官方出的一款git客户端, 新手不建议使用,
界面比较简洁, 查看历史变更比较方便, 其他诸多特性请自行探索. - Sourcetree, 据说是最好用的git客户端, 本人没有试过.
- more…
git学习资源
git help
- https://git-scm.com/docs
- https://www.liaoxuefeng.com/wiki/896043488029600
- https://git-scm.com/book/zh/v1/Git-%E5%9F%BA%E7%A1%80
- more…
参考
- https://git-scm.com/docs
- Git权威指南第二版.pdf
说明
图片来自互联网, 如有侵权, 请联系博主删除.