- 原文地址:Git Concepts I Wish I Knew Years Ago
- 原文做者:Gabriel Abud
- 译文出自:DEV Community
- 译者:Arrackisarookie
我辈开发者使用最多的技术既不是 JavaScript, 也不是 Python 或者 HTML。 它甚至在面试中都不多被提到,也不多被列入工做的必备技术栈。html
没错,我说的正是 Git 和版本控制。node
长久以来,咱们大部分开发人员只学过一点点 Git 的概念。 这些知识仅仅能让咱们拥有在一个小团队内使用简单功能分支工做流的能力。 若是你也像我同样,这种状态将会伴随你的职业生涯好久。git
是时候再访 Git,从新审视一下掌握它对咱们职业生涯的提高有多么重要。 本指南能够做为一篇参考,它包含了一些我认为非常重要但可能不为人知的概念。 掌握 Git 以后,你管理代码的方式以及天天的工做流将会发生巨大的改变。 因为 Git 命令有些陈旧而且难以记忆,所以本文将会按照概念和预期表现进行分解。github
若是你对 Git 的基本概念掌握的不够牢固,好比工做目录、本地仓库和远程仓库之间的区别, 那么建议你能够先阅读这篇指南。 一样,若是没有掌握 Git 的基本命令,能够从官方文档开始学习。 本文并不意味着会带你从一个完全的新手变成专业人员, 而是默认你已经熟练掌握如何使用 Git。面试
$ git log
$ git log --oneline # 更为精炼的输出
$ git log --graph # 以分支的可视化图显示
复制代码
$ git reflag
复制代码
由于有时 git log
命令没法捕捉到撤销的命令, 特别是对于那些没法在 commit 历史里显示的命令。shell
在你运行了相似 git rebase
这样的“危害型”命令后, reflag
基本上能够算是一层安全网。 你不只能够看到以前所作的 commit, 并且还将看到致使 commit 的每个过程。 看这篇 Atlassian 上的文章 来了解更多关于 refs 运做方式。json
$ git status
复制代码
虽然 git status
是一个很是基础的命令,咱们很早以前就学过, 可是,因为它做为 Git 内部基本原理的学习工具,其重要程度仍然值得 咱们重复学习。 它还能够帮助你浏览复杂的 rebase 和 merge 过程。安全
$ git diff --staged # staged 的改变
$ git diff # unstaged 的改变
复制代码
$ git diff branch1..branch2
复制代码
$ git reset <commit-sha>
复制代码
这条命令将会撤销对应 commit,而且取消那次 commit 中的 stage 操做, 可是那些文件仍然保留在工做目录。bash
$ git switch branch-name # Git 2.23 中的新语法
$ git checkout branch-name # 经典语法
复制代码
git checkout
可能会有些让人难以理解,由于他既能够工做在文件层级, 也能够工做在分支层级。 从 Git 2.23 开始,咱们拥有了两个新的命令:app
git restore
用来 checkout 文件git switch
用来 checkout 分支(译者注:详细可访问 官方文档 和 Stack Overflow 相关提问)
若是你想避免 git checkout
形成的困扰,上面两个命令很是适合你。
$ git switch -
复制代码
(译者注: get 不到这个梗。。)
$ git reset --hard HEAD
复制代码
这条命令将重置本地目录到最近一次 commit 的状态,而且会放弃全部 unstage 的文件。
$ git restore <filename> # Git 2.23 新语法
$ git checkout -- <filename> # 经典语法
复制代码
$ git reset --hard HEAD~1
复制代码
$ git reset --hard HEAD~n # 回到倒数第 n 次 commit
$ git reset --hard <commit-sha> # 或者回到特定的某次提交
复制代码
soft
,mixed
和 hard
三种 reset 的不一样:
--soft
:撤销 commit 可是工做目录中会保留更改--mixed
(默认):撤销 commit,撤销当次 commit 的 stage,可是工做目录中会保留更改--hard
:撤销 commit,撤销当次 commit 的 stage,而且删除更改$ git push -f
复制代码
只要你的本地仓库和远程仓库有差别,这一步都是必要的。
WARNING:强制 push 须要格外当心。通常来讲, 在共享的分支你应该避免任何的强制 push。在开启一个 pull 请求以前, 你应将强制 push 限制在你本身的分支内,以避免在不经意间弄乱你队友的 git 历史。
$ git commit --amend
复制代码
$ git rebase -i <commit hash> # commit hash 是全部你想改变的 commit 以前的一个 commit 的 hash
复制代码
这条命令将开启一个互动提示,你能够经过它来选择保持、压缩、删除 哪个 commit。你也能够在这里改变 commit message。 好比在清理错字或者规范化 commit 时,它很是有用。
当深刻学习 Git 以后,我发现 rebasing 是很是使人困惑的主题之一。 查看这个 rebasing 文档了解更多。
$ git rebase --abort
复制代码
你能够在 rebase 过程当中使用这条命令。
我发现,rebase 带来的麻烦老是超过他的价值,特别是在 rebase 两个 有着大量相同更改的分支的时候。在完成整个 rebase 以前, 你均可以让这个 rebase 流产。
# 将 commit-sha 所指的 commit 带入当前分支
$ git cherry-pick <commit-sha>
复制代码
$ git checkout <branch-name> <filename>
复制代码
(译者注:这个仿佛不能用 git restore,git restore 更倾向于重置和恢复)
$ git rm --cached <file-name>
复制代码
$ git stash # 将已有更改保存在 stash 栈的栈顶
$ git stash save "对于更改的信息描述"
$ git stash -u # 同时也 stash 未被追踪 (untracked) 的文件
复制代码
$ git stash list
复制代码
$ git stash pop # 弹出最近添加到 stash 栈的项目
$ git stash apply stash@{stash_index} # 申请取出指定项目能够用 git stash list 查看
复制代码
$ git revert HEAD # 撤销最近一次 commit
$ git revert <commit-sha> # 撤销指定 commit
复制代码
这条命令将从新运行提交新的 commit 时的逆过程, 从而撤销你的更改而不会撤销历史记录。 在共享的分支中撤销 commit 时,重写历史记录会很是的复杂, 因此使用 git revert 是一种很安全的解决方式。
$ git branch --no-color --merged | command grep -vE "^(\+|\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d
复制代码
这条命令将删除本地除了 master、develop、dev 以外的全部已合并的分支, 若是你的主分支和 dev 分支有着另外的名字, 你能够改变相应的 grep
的正则。
这条命令很长,不太好记,但你能够为它设置一个别名,就像这样:
$ alias gbda='git branch --no-color --merged | command grep -vE "^(\+|\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d'
复制代码
若是你在使用 Oh My Zsh
,这一步它已经为你完成。 查看 aliases 了解更多。
$ git fetch --all --prune
复制代码
若是你已经为远程仓库设置了在 merge 时删除分支,这条命令也很是有用。
(译者注:git fetch --prune 将会在 fetch 前 移除在本地的全部远程仓库中再也不存在的远程跟踪引用,详见官方文档)
Git 命令有时会很长,不太容易记住。咱们不想每次都把它们敲一遍或者 花费几天将它们背下来,所以我强烈建议你为它们设置 Git 别名。
更方便的方式是,安装一个像 Z Shell
(Zsh) 中的 Oh My Zsh
同样的工具。 这样一来,你将拥有一大堆最经常使用的 Git 命令的别名。 你可使用 别名 + tab
来补全他们。我懒得按照个人喜爱设置 shell, 因此我喜欢用一些相似 Oh My Zsh 的开源工具,它们能够帮我配置好~ 更不用说它们还有这漂亮的外观了~
我天天用的最多的一些命令:
$ gst - git status
$ gc - git commit
$ gaa - git add --all
$ gco - git checkout
$ gp - git push
$ gl - git pull
$ gcb - git checkout -b
$ gm - git merge
$ grb - git rebase
$ gpsup - git push --set-upstream origin $(current_branch)
$ gbda - git branch --no-color --merged | command grep -vE "^(\+|\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d
$ gfa - git fetch --all --prune
复制代码
若是你忘记了这些或者其余你本身设置的别名,能够运行:
$ alias
复制代码
或者给出关键词进行搜索:
$ alias grep <alias-name>
复制代码
不少文件可能不须要存在于版本控制中,你能够设置全局 gitignore 文件 来忽视掉它们。须要忽视的文件多是一些 node_modules
文件夹, .vscode
或其余 IDE 的文件,以及一些 Python 的虚拟环境。
对于一些敏感信息,你可使用环境变量文件存放,而后将它们添加到 项目根目录下的 .gitignore
文件中。
你可能须要将一些文件标记为二进制文件,以便于 Git 能够将其忽视, 而且不用为它们产生冗长的差别性检测。Git 有一个 .gitattributes
文件 来实现这一操做。好比在一个 JavaScript 项目中, 你会在 .gitattributes
中添加一个 yarn-lock.json
或者 package-lock.json
,这样一来,在你每次更新时, Git 不用每次都尝试记录它们的差别变化。
# .gitattributes
package-lock.json binary
复制代码
你的团队可能会从 rebase 和 merge 两种工做流中二选其一, 两者都有利弊,我曾见过这两种方式均可以产生很高的效率。 对于大多数状况,除非你 真的 了解你正在作什么, 不然选择 merge 工做流就完事了。
当你主要使用 merge 来为产品更新迭代时, 仍然也能够高效的使用 rebase。 最多见的场景是,你正在一个 feature 上工做, 同时另一个开发者 pull 了一个别的 feature 到 master。 你确实可使用 git merge
将那些改变一块儿带上, 可是这样,你会对队友作的简单更改有一个额外的 commit。 你真正想作的是将你的提交 从新提交 到最新的 master 的最上面。
$ git rebase master
复制代码
这条命令将给你一个更干净的 commit 历史。
深度解释它们之间的不一样点可能须要一整篇论文来阐述, 所以,我建议你能够查阅 the Atlassian docs 中有关这些差别的文章。
我对于 GitHub 和 GitLab 最为熟悉,可是其余远程仓库管理器也 应该支持这些设置。
一旦有分支被 merge,你就不该该在关心这个分支, 由于它的历史将被映射到你的 master/dev 分支。 这一举措会显著的减小你所管理的分支数量。 也能够经过使用 git fetch -all --prune
更为高效的保持本地仓库的干净整洁。
若是没有这个设置,很容易在 git push
时,忘记本身正在 master, 这会潜在的破坏你的产品,一点也很差。
取决于团队的大小,你可能须要在 merge 前须要屡次的确认, 即便只是一个二人团队,最少也要确认一次。 你不用花费几个小时每行都看,但通常来讲, 你的代码应该至少有两我的看过。 反馈 是学习和我的提高的关键。
(译者注:CI 即 Continuous Integration,持续集成)
已损坏的改变不该该被 merge 到产品中。 测试人员没法 100% 的捕捉损坏的更改,所以须要自动执行这些检测。
保持 Pull 请求小而简洁,理想状况下不超过几百行。 小而频繁的 Pull 请求会使得审阅过程更快,从而产生更多的无 bug 代码, 也会让你的队友更轻松,从而提高团队的效率,也更容易分享学习经验。 团队内部达成一个共同的承诺,承诺天天都花一些时间来审阅公开 Pull 请求。
咱们都爱这样的审阅: ![reviewing]res.cloudinary.com/practicalde…)
若是你正在实现的特性在一段时间内都会处于损坏状态, 请使用特性标签来在产品中禁用它。 这将会防止你的特性分支和 dev/master 分支产生太大差别, 同时也容许你作更频繁,更小的 Pull 请求。不 merge 代码的时间越长, 之后 merge 的难度就越大。
最后,在你的 Pull 请求中放一个细节描述,若是必要的话, 能够放图片或者 GIF。若是你使用像 Jira 这样的工具管理票据 (tickets,译者注:没使过不太懂啥意思), 描述中也能够包括 Pull 请求地址的票据的编号。 Pull 请求的描述和可视化作的越详细,可能你的队友就越想审阅你的代码, 而不是拖延着下次必定。
你的团队可能会提出分支命名的规范,以便于导航。 我喜欢每一个分支以建立人的名字首字母开头,接着一个左斜杠, 而后是以短横线链接的分支描述。
这可能看起来微不足道,可是配合 tab 补全以及相似 grep 这样的工具一块儿使用, 这确实能够帮你找到并理解可能有的全部分支。
例如,我建立了一个新分支:
$ git checkout -b gabud/implement-important-feature
复制代码
一周后,当我忘记我以前给它起了什么名字, 我能够键入 git checkout gabud
,而后按 tab 键, 个人 Z shell 就会向我展现全部我本地的分支以供选择, 而不用看我队友们的分支。
语言很重要,通常来讲,我发现最好不要以破碎状态提交东西, 每一次提交都应该有一个简洁的信息,说明所作的更改。 按照官方 Git 建议,我发现最好使用当前的命令式意义来提交信息。 能够将每一个提交信息视为对 计算机/git 的命令, 以致于你能够将提交信息加到这句话后面:
若是这个 commit 被应用,将会...
在当前命令式意义上,良好的提交示例为:
$ git commit -m "Add name field to checkout form"
复制代码
如今能够这么读:“若是这个 commit 被应用,将会在表单中添加姓名域。”
这毫不是已经了解了 Git 的所有,建议查阅 官方文档 和 git help
了解更多。 不要惧怕向你的队友问一些 Git 的问题, 你会惊讶的发现大多数队友也有许多相同的问题。
那么你呢?哪一个 Git 命令或者概念在你的工做流中最有用呢~