title | date | order | categories | tags | permalink | |||||
---|---|---|---|---|---|---|---|---|---|---|
如何优雅的玩转 Git |
2019-03-08 16:25:13 -0800 |
1 |
|
|
/pages/2fc8b1/ |
Git 是一个开源的分布式版本控制系统。
Git 和其它版本控制系统(包括 Subversion 和近似工具)的主要差别在于 Git 对待数据的方式。 从概念上来说,其它大部分系统以文件变更列表的方式存储信息,而 Git 是把数据看作是对小型文件系统的一系列快照。
版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
介绍分布式版本控制系统前,有必要先了解一下传统的集中式版本控制系统。
集中化的版本控制系统,诸如 CVS,Subversion 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
这么做最显而易见的缺点是中央服务器的单点故障。如果宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。要是中央服务器的磁盘发生故障,碰巧没做备份,或者备份不够及时,就会有丢失数据的风险。最坏的情况是彻底丢失整个项目的所有历史更改记录。
分布式版本控制系统的客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的提取操作,实际上都是一次对代码仓库的完整备份。
Git 是分布式的。这是 Git 和其它非分布式的版本控制系统(例如 svn,cvs 等),最核心的区别。分布式带来以下好处:
- 工作时不需要联网 - 首先,分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件 A,你的同事也在他的电脑上改了文件 A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
- 更加安全
- 集中式版本控制系统,一旦中央服务器出了问题,所有人都无法工作。
- 分布式版本控制系统,每个人电脑中都有完整的版本库,所以某人的机器挂了,并不影响其它人。
个人认为,对于 Git 这个版本工具,再不了解原理的情况下,直接去学习命令行,可能会一头雾水。所以,本文特意将原理放在命令使用章节之前讲解。
当你一个项目到本地或创建一个 git 项目,项目目录下会有一个隐藏的 .git
子目录。这个目录是 git 用来跟踪管理版本库的,如果不熟悉其工作机制,千万不要手动修改。
hooks
目录:包含客户端或服务端的钩子脚本(hook scripts)info
目录:包含一个全局性排除(global exclude)文件, 用以放置那些不希望被记录在.gitignore
文件中的忽略模式(ignored patterns)。objects
目录:存储所有数据内容。refs
目录:存储指向数据(分支、远程仓库和标签等)的提交对象的指针HEAD
文件:指向目前被检出的分支。index
文件保存暂存区信息。config
文件:包含项目特有的配置选项。description
文件:description
文件仅供 GitWeb 程序使用,我们无需关心。
Git 中所有数据在存储前都计算校验和,然后以校验和来引用。 这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。 这个功能构筑在 Git 底层,是 Git 的关键组件。 若你在传送过程中丢失信息或损坏文件,Git 就能发现。
Git 计算校验和的使用 SHA-1 哈希算法。 这是一个由 40 个十六进制字符(0-9 和 a-f)组成字符串,基于 Git 中文件的内容或目录结构计算出来。 SHA-1 哈希值看起来是这样:
24b9da6552252987aa493b52f8696cd6d3b00373
Git 中使用这种哈希值的情况很多,你将经常看到这种哈希值。 实际上,Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。
在 GIt 中,你的文件可能会处于三种状态之一:
- 已修改(modified) - 已修改表示修改了文件,但还没保存到数据库中。
- 已暂存(staged) - 已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
- 已提交(committed) - 已提交表示数据已经安全的保存在本地数据库中。
与文件状态对应的,不同状态的文件在 Git 中处于不同的工作区域。
- 工作区(working) - 当你
git clone
一个项目到本地,相当于在本地克隆了项目的一个副本。工作区是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。 - 暂存区(staging) - 暂存区是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中。 有时候也被称作`‘索引’',不过一般说法还是叫暂存区。
- 本地仓库(local) - 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 本地仓库。
- 远程仓库(remote) - 以上几个工作区都是在本地。为了让别人可以看到你的修改,你需要将你的更新推送到远程仓库。同理,如果你想同步别人的修改,你需要从远程仓库拉取更新。
Git Flow 应该是目前流传最广的 Git 分支管理策略。Git Flow 围绕的核心点是版本发布(release),它适用于迭代版本较长的项目。
详细内容,可以参考这篇文章:Git 在团队中的最佳实践--如何正确使用 Git Flow
Git Flow 常用分支:
master
- 主线分支develop
- 开发分支feature
- 特性分支release
- 发布分支hotfix
- 问题修复分支
Git Flow 工作流程
主干分支有两个,它们是伴随着项目生命周期长期存在的分支。
master
- 这个分支对应发布到生产环境的代码。这个分支只允许从其他分支合入代码,不能在这个分支直接修改。所有在 master 分支上的 Commit 都应该打 Tag。develop
- 这个分支包含所有要发布到下一个 release 的代码,这个分支主要是从其他分支合入代码,比如 feature 分支。
这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回 develop 分支进入下一个 release。feature 分支开发结束后,必须合并回 develop 分支, 合并完分支后一般会删点这个 feature 分支,但是我们也可以保留。
release 分支基于 develop 分支创建,创建后,我们可以在这个 release 分支上进行测试,修复 Bug 等工作。同时,其它开发人员可以基于它开发新的 feature (记住:一旦创建了 release 分支之后不要从 develop 分支上合并新的改动到 release 分支)。
发布 release 分支时,合并 release 到 master 和 develop, 同时在 master 分支上打个 Tag 记住 release 版本号,然后可以删除 release 分支了。
当出现线上 bug 时,也意味着 master 存在 Bug。这时,我们需要基于 master 创建一个 hotfix 分支,在此分支上完成 bug 修复。修复后,我们应该将此分支合并回 master 和 develop 分支,同时在 master 上打一个 tag。所以,hotfix 的改动会进入下一个 release。
在实际开发中,如何具体落地 Git Flow 流程呢?
git 提供了 git flow
命令来手动管理,但是比较麻烦,所以还是建议使用 Git Flow 的 GUI 工具。比如:SourceTree、VScode 的 GitFlow 插件、Intellij 的 GitFlow 插件等。
想了解更详细的 Git Flow 介绍,可以参考:
对于简单且迭代频繁的项目来说,Git Flow 可能有些太复杂了。这时,可以考虑 Github Flow。
在 Github Flow 策略中,所有分支都是基于 master 创建。在 Feature 或 Bugfix 分支中完成工作后,将其合入 master,然后继续迭代。
想了解更详细的 Github Flow 介绍,可以参考:GitHub Flow
Git 每次提交代码,都要写 Commit message(提交说明),否则就不允许提交。
好的 Commit message 可以让人一眼就明白提交者修改了什么内容,有什么影响;而不好的 Commit message 写了和没写一样,甚至还可能误导别人。
先来看下图中不好的 Commit message 范例,从提交信息完全看不出来修改了什么。
再来一张较好的 Commit message 范例,每次提交的是什么内容,做了什么一目了然。
从前面,我们不难看出,完善的 Commit message 非常有利于项目维护。即时是个人维护的项目,时间久了,可能也会忘记当初自己改了什么。
Commit message 的作用还不仅仅是理解历史信息,它的主要作用如下:
(1)提供易于理解的历史信息,方便检索
(2)可以过滤某些 commit(比如文档改动),便于快速查找信息。
(3)可以直接从 commit 生成 Change log。
开源社区有很多 Commit message 的规范,个人推荐使用 Angular Git Commit 规范,这是目前使用最广的写法,比较合理和系统化,并且有配套的工具。
它主要有以下组成部分:
- 标题行:必填, 描述主要修改类型和内容
- 主题内容:描述为什么修改, 做了什么样的修改, 以及开发的思路等等
- 页脚注释:放 Breaking Changes 或 Closed Issues
常用的修改项
type
:commit 的类型feat
:新特性fix
:修改问题refactor
:代码重构docs
:文档修改style
:代码格式修改, 注意不是 css 修改test
:测试用例修改chore
:其他修改, 比如构建流程, 依赖管理.scope
:commit 影响的范围, 比如:route, component, utils, build...subject
:commit 的概述body
:commit 具体修改内容, 可以分为多行footer
:一些备注, 通常是 BREAKING CHANGE 或修复的 bug 的链接
Intellij 中有集成 Angular Git Commit 规范 的插件,可以帮助我们快速创建符合 Angular Git Commit 规范 的 Git Commit Message。
其使用步骤如下:
第一步,安装插件
第二步,提交代码时,按照模板填写 commit message
如果你的所有 Commit 都符合 Angular Git Commit 规范,那么发布新版本时,就可以用脚本自动生成 Change log。
生成的文档包括以下三个部分。
- New features
- Bug fixes
- Breaking changes.
每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。
conventional-changelog 就是生成 Change log 的工具,运行下面的命令即可。
$ npm install -g conventional-changelog
$ cd my-project
$ conventional-changelog -p angular -i CHANGELOG.md -w
上面命令不会覆盖以前的 Change log,只会在CHANGELOG.md
的头部加上自从上次发布以来的变动。
如果你想生成所有发布的 Change log,要改为运行下面的命令。
$ conventional-changelog -p angular -i CHANGELOG.md -w -r 0
为了方便使用,可以将其写入package.json
的scripts
字段。
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0"
}
}
以后,直接运行下面的命令即可。
$ npm run changelog
许多 Git 服务器都使用 SSH 公钥进行认证。 为了向 Git 服务器提供 SSH 公钥,如果某系统用户尚未拥有密钥,必须事先为其生成一份。 这个过程在所有操作系统上都是相似的。 首先,你需要确认自己是否已经拥有密钥。 默认情况下,用户的 SSH 密钥存储在其 ~/.ssh
目录下。 进入该目录并列出其中内容,你便可以快速确认自己是否已拥有密钥:
$ cd ~/.ssh
$ ls
authorized_keys2 id_dsa known_hosts
config id_dsa.pub
我们需要寻找一对以 id_dsa
或 id_rsa
命名的文件,其中一个带有 .pub
扩展名。 .pub
文件是你的公钥,另一个则是私钥。 如果找不到这样的文件(或者根本没有 .ssh
目录),你可以通过运行 ssh-keygen
程序来创建它们。在 Linux/Mac 系统中,ssh-keygen
随 SSH 软件包提供;在 Windows 上,该程序包含于 MSysGit 软件包中。
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/schacon/.ssh/id_rsa):
Created directory '/home/schacon/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/schacon/.ssh/id_rsa.
Your public key has been saved in /home/schacon/.ssh/id_rsa.pub.
The key fingerprint is:
d0:82:24:8e:d7:f1:bb:9b:33:53:96:93:49:da:9b:e3 [email protected]
首先 ssh-keygen
会确认密钥的存储位置(默认是 .ssh/id_rsa
),然后它会要求你输入两次密钥口令。如果你不想在使用密钥时输入口令,将其留空即可。
现在,进行了上述操作的用户需要将各自的公钥发送给任意一个 Git 服务器管理员(假设服务器正在使用基于公钥的 SSH 验证设置)。 他们所要做的就是复制各自的 .pub
文件内容,并将其通过邮件发送。 公钥看起来是这样的:
$ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA
t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En
mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx
NrRFi9wrf+M7Q== [email protected]
在你的 Github 账户中,依次点击 Settings > SSH and GPG keys > New SSH key
然后,将上面生成的公钥内容粘贴到 Key
编辑框并保存。至此大功告成。
后面,你在克隆你的 Github 项目时使用 SSH 方式即可。
如果觉得我的讲解还不够细致,可以参考:adding-a-new-ssh-key-to-your-github-account
.gitignore
文件可能从字面含义也不难猜出:这个文件里配置的文件或目录,会自动被 git 所忽略,不纳入版本控制。
在日常开发中,我们的项目经常会产生一些临时文件,如编译 Java 产生的 *.class
文件,又或是 IDE 自动生成的隐藏目录(Intellij 的 .idea
目录、Eclipse 的 .settings
目录等)等等。这些文件或目录实在没必要纳入版本管理。在这种场景下,你就需要用到 .gitignore
配置来过滤这些文件或目录。
.gitignore
配置的规则很简单,也没什么可说的,看几个例子,自然就明白了。
【示例】一份 Java 的 .gitignore
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
【推荐】这里推荐一个 Github 的开源项目:gitignore,在这里,你可以找到很多常用的 .gitignore 模板,如:Java、Nodejs、C++ 的
.gitignore
模板等等。
你有没有在和多人协同开发时遇到过以下烦恼?
开发者们分别使用不同的操作系统进行开发,有的人用 Windows,有的人用 Linux/MacOS。众所周知,不同操作系统默认的文件结尾行是不同的:在 Windows 上默认的是回车换行(Carriage Return Line Feed, CRLF),然而,在 Linux/MacOS 上则是换行(Line Feed, LF)。这就可能导致这种情况:明明文件内容一模一样,但是版本比对时仍然存在版本差异。
那么如何解决这个问题呢?Git 提供了 .gitattributes
配置文件,它允许使用者指定由 git 使用的文件和路径的属性。
在 Git 库中,一个普通文本文件的行尾默认是 LF
。对于工作目录,除了 text
属性之外,还可以设置 eol
属性或 core.eol
配置变量。
.gitattributes
文件中,可以用 text 属性指定某类文件或目录下的文件,控制它的行结束标准化。当一个文本文件被标准化时,它的行尾将在存储库中转换为 LF
。要控制工作目录中使用的行结束风格,请使用单个文件的eol
属性和所有文本文件的 core.eol
配置变量。
【示例】一份 .gitattributes 示例
* text=auto eol=lf
*.txt text
*.java text
*.scala text
*.groovy text
*.gradle text
*.properties text
# unix style
*.sh text eol=lf
# win style
*.bat text eol=crlf
# binary
*.jar binary
*.war binary
*.zip binary
*.tar binary
*.tar.gz binary
*.gz binary
*.apk binary
*.bin binary
*.exe binary
【推荐】这里推荐一个 Github 的开源项目:gitignore,在这里,你可以找到很多常用的 .gitignore 模板,如:Java、Nodejs、C++ 的
.gitignore
模板等等。
如果,你在不同的 Git 远程仓库中维护同一个项目,你可能会有这样的需求:能不能一次提交,同时 push 到多个远程仓库中呢?
这个可以有,解决方案如下:
比如,我有一个 blog 项目,同时维护在 Github 和 Gitee 上。
(1)首先,在 Github 和 Gitee 上配置本地的 ssh 公钥(如果是 Gitlab,也同样如此),这样中央仓库就能识别本地。
生成 SSH 公钥的方法,请参考上文的 “生成 SSH 公钥” 章节。
(2)进入 git 项目的隐藏目录 .git
,打开 config 文件,参考下面配置进行编辑:
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[remote "origin"]
url = [email protected]:dunwu/blog.git
url = [email protected]:turnon/blog.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[user]
name = dunwu
email = [email protected]
重点在于 remote "origin"
,同时配置了两个 url。配置后,一旦触发 push 远程仓库的动作,就会同时推送提交记录到配置的远程仓库。
开发软件,Bug 再所难免,出现问题不可怕,可怕的是放任不管;所以,优秀的软件项目,都应该管理好问题追踪。软件的使用者,在使用中,可能会遇到形形色色的问题,难以解决,需要向维护者寻求帮助。
问题追踪如此重要,所以各种代码托管平台都会提供 Issue 维护机制,如 Github Issue 和 Gitlab Issue。
如果一个项目的开源社区很活跃,在没有任何约束的前提下,提问肯定是五花八门的,让维护者难以招架。
其实,提问也是一门艺术,如果提问者的问题长篇大幅,言不达意,让别人难以理解,就很难得到有效帮助。关于如何高效的提问,推荐参考 提问的智慧 这篇文章,作者整理的非常好。
作为开发者,你不能期望所有提问者都是训练有素的提问者。所以,使用规范化的 Issue 模板来引导提问者提问,可以大大减轻开发者的负担。
如何在 Github Issue 平台上创建 Issue 模板呢?方法如下:
(1)在仓库根目录创建新目录 .github
(2)在 .github
目录中添加 ISSUE_TEMPLATE
目录,在其中添加的 md 文件都会被 Github 自动识,并将其作为 issue 的默认模板。
示例,下面是携程 apollo 的一个 Issue 模板,要求提问者填充 bug 描述、复现步骤、期望、截图、日志等细节。
如何在 Gitlab Issue 平台上创建 Issue 模板呢?方法如下:
(1)在仓库根目录创建新目录 .gitlab
(2)在 .gitlab
目录中添加 issue_templates
目录,在其中添加的 md 文件都会被 Gitlab 自动识,并将其作为 issue 的默认模板。
在执行提交代码(git commit),推送代码(git push)等行为时,我们可能希望做一些代码检查性工作,例如:代码 lint 检查、代码格式化等。当检查发现代码存在问题时,就拒绝代码提交,从而保证项目质量。
Git 提供了 Git Hook 机制,允许使用者在特定的重要动作发生时触发自定义脚本。有两类钩子:客户端钩子和服务器端钩子。客户端钩子由诸如提交和合并等操作所触发调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。钩子都被存储在 Git 项目目录下的 .git/hooks
子目录中。Git 在这个目录下放置了一些示例,这些示例的名字都是以 .sample
结尾,如果想启用它们,得先移除这个后缀。
常用的客户端钩子:
pre-commit
钩子:在提交信息前运行。 它用于检查即将提交的快照,例如,检查是否有所遗漏,确保测试运行,以及核查代码。 如果该钩子以非零值退出,Git 将放弃此次提交,不过你可以用git commit --no-verify
来绕过这个环节。 你可以利用该钩子,来检查代码风格是否一致(运行类似lint
的程序)、尾随空白字符是否存在(自带的钩子就是这么做的),或新方法的文档是否适当。prepare-commit-msg
钩子:在启动提交信息编辑器之前,默认信息被创建之后运行。 它允许你编辑提交者所看到的默认信息。 该钩子接收一些选项:存有当前提交信息的文件的路径、提交类型和修补提交的提交的 SHA-1 校验。 它对一般的提交来说并没有什么用;然而对那些会自动产生默认信息的提交,如提交信息模板、合并提交、压缩提交和修订提交等非常实用。 你可以结合提交模板来使用它,动态地插入信息。commit-msg
钩子:接收一个参数,此参数即上文提到的,存有当前提交信息的临时文件的路径。 如果该钩子脚本以非零值退出,Git 将放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息。 在本章的最后一节,我们将展示如何使用该钩子来核对提交信息是否遵循指定的模板。post-commit
钩子:在整个提交过程完成后运行。它不接收任何参数,但你可以很容易地通过运行git log -1 HEAD
来获得最后一次的提交信息。 该钩子一般用于通知之类的事情。pre-push
钩子:会在git push
运行期间, 更新了远程引用但尚未传送对象时被调用。 它接受远程分支的名字和位置作为参数,同时从标准输入中读取一系列待更新的引用。 你可以在推送开始之前,用它验证对引用的更新操作(一个非零的退出码将终止推送过程)。
想在 JavaScript 应用中使用 Git Hook,推荐使用 husky ,可以很方便的编写钩子处理命令。
使用方法很简单,先安装 husky
npm i -D husky
然后,在 package.json 中添加配置:
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
以上配置的作用是,当提交代码前( pre-commit
),先执行 lint-staged
;
lint-staged
中执行的动作是,对 src 目录的所有 js、vue 文件进行 eslint 检查,并尝试修复。如果修复后没有问题,就 git add 添加修改后的文件;如果修复失败,则拒绝提交代码。
- 官方资源
- 模板
- gitignore 模板 - .gitignore 文件模板
- gitattributes 模板 - .gitattributes 文件模板
- github-cheat-sheet - git 命令简略图表
- Git 教程
- Learn Git branching - 交互式教程
- Git 官方推荐教程 - Scott Chacon 的 Git 书。
- git-tips
- Git 中文教程
- 廖雪峰的 Git 教程
- 有关 git 的学习资源
- 文章
- Git 工具
- guis - Git 官网展示的客户端工具列表。
- gogs - 极易搭建的自助 Git 服务。
- gitflow - 应用 fit-flow 模型的工具。
- firstaidgit.io 一个可搜索的最常被问到的 Git 的问题
- git-extra-commands - 一堆有用的额外的 Git 脚本
- git-extras - GIT 工具集 -- repo summary, repl, changelog population, author commit percentages and more
- git-fire - git-fire 是一个 Git 插件,用于帮助在紧急情况下添加所有当前文件, 做提交(committing), 和推(push)到一个新分支(阻止合并冲突)。
- git-tips - Git 小提示
- git-town - 通用,高级 Git 工作流支持!
- GUI 客户端
- git cheat sheet