Git 全功能介绍
目前组内正在从 SVN 逐步迁移到 Git 上来,趁机介绍一下觉得挺好的,之前做过一次分享,但因为最近项目一直很忙,这篇总结才刚出来。
Git 历史和现状
Linux 的作者的另一个作品,2002年时他还在使用 Bitkeeper 作为 Linux 内核的版本管理,但因为它是 Copyright 有版权的软件备受质疑,然后 Andrew Tridgell 对 Bitkeeper 进行逆向工程,导致 BitMover 要回收 Linux 开发者的 Bitkeeper 的免费使用权,Linus 一怒之下花了10天写出了 Git。
名字的意思是:混蛋 - egotistical bastard
如今 Git 已经成为绝大多数开发者的选择, Tom Preston-Werner、Chris Wanstrath 和 PJ Hyett 在 2007 年 10 月推出的 Github 已经成为了全球最大的开发者网站,我们厂在上面也是贡献颇多。
更有甚者,一向自己造轮子的的微软,也打算把巨达 300G 的 Windows 源代码迁移到 Git 上进行管理,他们为 Git 提供了新的 GVFS 实现,有效地改善了 Git 对巨大代码仓库的性能。
另外说一句:Docker 的二进制 image 管理,也是基于 git 实现的。
集中式版本管理和分布式版本管理
Git 和 SVN 是从设计理念上就不一样的版本工具,SVN 将代码进行中心化管理,拥有更好的稳定性和安全性,但是去中心化的 Git 却是从 Linux 操作系统的开发需求而来,更加适合多人协作的开源项目,可以以任何一个点为 remote 将他的代码与本地代码合并,随着时间发展,还衍生出了更多强大功能和一整套操纵流程,让它也可以适应了商业软件的开发。
Git 和 SVN 代码历史的不同
SVN 的代码历史相对比较简单,因为它是中心化的,所有人的代码都直接提交到某个 repository 上,所以它的 Reversion ID 号是一个按顺序增加的数字类型,一般情况下不能在两个数字之间插入别的 reversion。
Git 的看起来就是杂乱多了,它的 Reversion ID 号是一个 40 位长度的 hash 值,通常也可以缩写为 7 位,这样做的原因是因为 Git 的最小单位是代码修改的历史,即为补丁 Patch,而分支、 Tag、Remote(一会儿会说到这些概念)等都只是分支的集合,互相之间可以随意拆分、合并。
我更愿意把分支、Tag、Remote 想象成不同的平行宇宙,因为某些机缘导致产生了分裂,走向了不同的历史,也可能因为某些机缘又合并到了一起,变得更加强大。
Git 基础命令
Git 按照场景可以分为以下场景(Scence):
- Workspace:当前工作区,修改的的最初状态。
- Staging:修改后,添加到准备提交的缓存状态。
- Local repository:本地的代码仓库,只对自己的代码生效。这也是和 svn 区别之一,svn commit 之后就直接提交到远程服务器了,git commit 之后只是到本地代码库。
- Remote repository:远程代码库,将自己的本地代码库同步到远程代码库上,这样可以供别的开发者分享自己的成果。
具体流程看图即可,下面对几个常用命令进行简单介绍
*PS: 图中没有提到 rebase 和 cherry-pick 命令,这两个命令也非常强大,后面有提到,有时间可以关注一下 *
补丁 diff
之前有提到过,补丁是 Git/SVN 代码版本管理的基础概念,它其实是以行为单位的文件修改历史,增加行以 + 号开头 ,删除行以 - 号开头,而修改一行,就是先 - 后 +。
在 Git 里可以通过 git diff
或者 Linux/Mac/Conemu 中,也可以通过 diff -Naur
来生成文件对比结果,有点类似下图。
这是整个代码管理的基础概念,所有的分支、Tag、Remote 都是在此基础上衍生的。
基本流程
1. 克隆代码到本地开发环境 - Clone
$ git clone [REPOSITORY_URL]
对应到了 svn checkout
命令,用于把远程代码克隆到本地,跟 svn 一样,REPOSITORY_URL 的协议非常灵活,之前流行用 ssh/scp 协议,但现在 https/http 协议渐渐流行起来了。
2. 更新代码 - Status/Commit/Log
刚有提到过 Git 的四种场景,其中前两种场景需要通过
$ git status
命令查看,代码刚刚新建可以看到是 Untracked files
(Workspace) 状态,执行 add
之后变成 Changes to be commited
(Stage) 状态,修改了 Stage 中的文件,又会变成 Changes not staged for commit
状态。
执行 commit
之后就从 Stage 中转移到了 Local repository 中,可以通过
$ git log
查看到代码提交。
3. Branch 和 Tag
如刚从所说,Branch 和 Tag 都可以看成是补丁的时序化集合,branch 可以互相合并,在 clone 完 repository 后有一个主线分支叫做 master。而 Tag 用于发布后标记版本,这两个只是从名字上不一样,功能(我感觉实现上)并没有太大区别。
和 SVN 不同, SVN 的 Branch 和 Tag 都是把 Trunk 整个代码库拷贝出来,Git 只是将补丁引用重新对当前代码应用一下,所以 Git 的 Branch/Tag 都非常轻量,切换起来非常轻松,使用 Git 要尽量多使用它的分支来提高开发效率,一会儿提到 Git flow 时会描述一下如果用分支进行代码功能开发管理。
3.1 新建分支的两种办法
$ git checkout -b [BRANCH_NAME] # 在当前版本切换并新建分支
$ git branch [BRANCH_NAME] # 直接新建分支
3.2 切换分支
$ git checkout [BRANCH_NAME]
3.3 合并分支的两种办法
$ git merge [BRANCH_NAME] # 将另外一个分支的代码,打到当前分支之后。
$ git rebase [BRANCH_NAME] # 不推荐,对代码进行比较,将本分支修改后的代码打到另外一个分支之后
rebase
通常情况下不推荐使用,因为 rebase 完下游分支,再从上游分支 merge 的时候会丢失分支合并的 commit,但是对于部分有 history mysophobia 的人来说,它是保持代码提交历史记录干净的神器,那个 Merge branch 'xxx' of http://github.com/xxx into yyy 的 commit 看起来也挺讨厌的。
对于已经推到 remote repository 的 commit,是不建议 rebase 的,因为一旦 rebase 了,别人再 pull 就会出一大堆的冲突 conflict,而且基本没法修,通常情况下还是建议用 merge 稳妥一些。
PS: rebase 还有一个强大的功能是配合 --interactive 参数修改之前的补丁,具体自己查一下,但是改完了 push --force 上去,别人再 pull 回来出 conflict 是必然的,但是这招是修改 Github 上的 Pull request(后面有提到)必备技能。
3.4 删除分支
$ git branch -d [BRANCH_NAME] # 已经合并到 master
$ git branch -D [BRANCH_NAME] # 该分支未合并到 master,强制删除
PS: 即使删除了分支等,也可以用 git reflogs 找回来喔
3.5 取消修改
git stash # 取消全部修改,很强大的是它可以恢复过来,具体自己查一下
git reset —soft [REV] # 保留修改内容,从 Local repository 中撤销,也可以用于回退历史记录
git reset —hard [REV] # 丢掉修改内容,从 Local repository 中撤销,也可以用于回退历史记录
推送本地代码到远程仓库
推送代码是为了跟别人一起合作,命令行非常简单
$ git push [REMOTE] [BRANCH]
remote 默认为 origin,如果不填的话就推送到它上面,branch 默认为当前分支,其实可以不加,加了就把指定的分支推送到远程了。
如果要将推送本地功能分支,建议 push 后面加上 —set-upstream 参数。
郑重警告:**永远不要对主线 master 分支执行 —force **
获取远程分支更新
$ git pull # 把代码更新到 workspace
$ git fetch # 把代码更新到 Local repository,可能需要通过 merge 再合并到 worksapce 一次。
远程仓库
Clone 之后会有一个默认的远程仓库为 origin,但如果还要增加别的远程仓库,就需要用到下面命令了:
$ git remote add [REMOTE_NAME] [URL] # 添加原创仓库
$ git fetch [REMOTE_NAME] # 获取远程仓库更新
$ git branch -a # 查看包括远程仓库以内的所有分支
$ git push [REMOTE_NAME] [BRANCH_NAME] # 推送到远程仓库
Github Pull Request & Gitlab Merge Request
Github 在 Git Remote 的基础上为了方便大家参与开源项目,衍生出的一套机制,目前常规开源项目的参与流程是,先注册一个 Github 账号,然后将感兴趣的开源项目 Fork 一份到自己的 namespace 下,然后拆分分支进行修改,然后提交到自己的 Github repository 下,再发起一个 Pull Request,让项目维护者来合并你的代码(Pull request 名副其实),在这个过程中,项目维护者会对你的代码进行 Review 和点评,你得按照维护者要求进行修改(这里 rebase 会用得很勤),修改通过,维护者同意后,就有他将代码合并进项目中。
具体流程自己走一圈就明白了,Gitlab 的 Merge Request 原理是一模一样的。
PS: 范例图片在 PPT 的第 22 页起
Git flow
Git flow 本来应该是本文的重点内容的,它是在 Git branch 的基础上实现了一套简单的功能模块化开发流程,主要思想是把分支分成了上下游几个层级,然后通过一套命令行工具进行维护。
- master 分支 - 与线上版本保持一致,当发生线上问题后可以很轻松地修复。
- develop 分支 - 功能开发基线分支,功能开发完之后合并到上面,所有功能开发完毕后经过测试上线,然后合并回 master。
- feature/* 功能开发分支 - 从 develop 上拆分,也需要随时把 develop 的更新 merge 回来,跟上游的分支保持一致,等功能开发完毕之后,即可合并到 develop。通过和上游分支保持一致,这样可以避免对误删别人的代码,所有代码冲突必须在下游分支修好,测试完毕后才可合并到上游分支。
- hotfix/* 热修复分支 - 主要用于线上 bug 修复,但是修复后应该同时合并到 master 和 develop 两个分支上。
然后 git flow 提供了套命令行工具来更加轻松地去做这些代码合并的事情。
$ git flow init # 初始化 git flow 分支模型
$ git flow feature start [NAME] # 开始一个功能分支
$ git flow feature finish [NAME] # 将功能分支合并进 develop
$ git flow hotfix start [NAME] # 开始一个热修复分支
$ git flow hotfix finish [NAME] # 将补丁合并进 develop 和 master
$ git flow release [NAME] # 发布一个新版本,打 tag
感觉 Git flow 得有个篇幅,下次有机会再来详述。
其它内容
有兴趣可以继续看一下别的相关内容,非常有意思:
- git svn - Git 可以以 svn 为代码后端,通过 Giit 来对 SVN 里的代码进行版本管理。
- git reflogs - 引用记录,在 git 中误删除某提交是不用害怕的,只要 commit 了,就可以通过 reflogs 找到,用 reset 或者 cherry pick 恢复。
- git cherry pick - 摘樱桃(commit),从另一个分支中单独将某个 patch 摘回来。
- git bare repository - 建立 Git 服务器
- git submodule - 子模块,一个大项目可以通过 submodule 进行拆分,可以随时进行子模块的版本更新和回溯。
- git hooks - 钩子,当 git 在 repository 上发生某种行为的时候,可以通过钩子触发一些行为,像目前 Github 上的一些第三方持续集成服务,就是在此基础上实现的。
- git signature - 签名,通过 gpg 在 commit 时对 patch 进行签名,证明那个补丁确实是自己提交的,可以参考一下 Github 的文档。
- Source Tree - Source Tree 是一套跨平台的 Git 图形界面,简单方便,我目前主要用它进行基本的 Patch 阅读,比命令行更加舒服。
多谢阅读,下次有机会再写。
版权所有丨转载请注明出处:https://kxq.io/archives/gitquan-gong-neng-jie-shao