[译] 相见恨晚的 Git 命令

我辈开发者使用最多的技术既不是 JavaScript, 也不是 Python 或者 HTML。 它甚至在面试中都不多被提到,也不多被列入工做的必备技术栈。html

没错,我说的正是 Git 和版本控制。node

长久以来,咱们大部分开发人员只学过一点点 Git 的概念。 这些知识仅仅能让咱们拥有在一个小团队内使用简单功能分支工做流的能力。 若是你也像我同样,这种状态将会伴随你的职业生涯好久。git

是时候再访 Git,从新审视一下掌握它对咱们职业生涯的提高有多么重要。 本指南能够做为一篇参考,它包含了一些我认为非常重要但可能不为人知的概念。 掌握 Git 以后,你管理代码的方式以及天天的工做流将会发生巨大的改变。 因为 Git 命令有些陈旧而且难以记忆,所以本文将会按照概念和预期表现进行分解。github

若是你对 Git 的基本概念掌握的不够牢固,好比工做目录、本地仓库和远程仓库之间的区别, 那么建议你能够先阅读这篇指南。 一样,若是没有掌握 Git 的基本命令,能够从官方文档开始学习。 本文并不意味着会带你从一个完全的新手变成专业人员, 而是默认你已经熟练掌握如何使用 Git。面试

Git 命令

日志 (Logging)

我刚干哈了

$ 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 过程。安全

对比 staged (或者 unstaged) 中的异同

$ git diff --staged # staged 的改变
$ git diff # unstaged 的改变
复制代码

对比两个分支之间的异同

$ git diff branch1..branch2
复制代码

导航 (Navigation)

我想看看我以前干哈了

$ 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 -
复制代码

修改 (Modifications)

我把本身挖进了兔子洞,让咱们从新开始

(译者注: get 不到这个梗。。)

$ git reset --hard HEAD
复制代码

这条命令将重置本地目录到最近一次 commit 的状态,而且会放弃全部 unstage 的文件。

我想把一个文件重置到以前的样子

$ git restore <filename> # Git 2.23 新语法
$ git checkout -- <filename> # 经典语法
复制代码

我想撤销上一次 commit 而且重写历史

$ git reset --hard HEAD~1
复制代码

我想回到 n 次 commit 以前

$ git reset --hard HEAD~n # 回到倒数第 n 次 commit
$ git reset --hard <commit-sha> # 或者回到特定的某次提交
复制代码

softmixedhard 三种 reset 的不一样:

  • --soft:撤销 commit 可是工做目录中会保留更改
  • --mixed (默认):撤销 commit,撤销当次 commit 的 stage,可是工做目录中会保留更改
  • --hard:撤销 commit,撤销当次 commit 的 stage,而且删除更改

我已经重写了历史记录,如今想把这些改变 push 到远程仓库

$ git push -f
复制代码

只要你的本地仓库和远程仓库有差别,这一步都是必要的。

WARNING:强制 push 须要格外当心。通常来讲, 在共享的分支你应该避免任何的强制 push。在开启一个 pull 请求以前, 你应将强制 push 限制在你本身的分支内,以避免在不经意间弄乱你队友的 git 历史。

我想为上一次 commit 多加一些改变

$ git commit --amend
复制代码

我想重写本地的一堆 commit

$ git rebase -i <commit hash> # commit hash 是全部你想改变的 commit 以前的一个 commit 的 hash 
复制代码

这条命令将开启一个互动提示,你能够经过它来选择保持、压缩、删除 哪个 commit。你也能够在这里改变 commit message。 好比在清理错字或者规范化 commit 时,它很是有用。

当深刻学习 Git 以后,我发现 rebasing 是很是使人困惑的主题之一。 查看这个 rebasing 文档了解更多。

这个 rebase 垮了,报废掉它吧

$ git rebase --abort
复制代码

你能够在 rebase 过程当中使用这条命令。

我发现,rebase 带来的麻烦老是超过他的价值,特别是在 rebase 两个 有着大量相同更改的分支的时候。在完成整个 rebase 以前, 你均可以让这个 rebase 流产。

我想从另外一个分支把一个 commit 带到当前分支

# 将 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) 的文件
复制代码

我想看看个人 stash 里有啥

$ git stash list
复制代码

我想把 stash 里的东西取出来

$ git stash pop # 弹出最近添加到 stash 栈的项目
$ git stash apply stash@{stash_index} # 申请取出指定项目能够用 git stash list 查看
复制代码

我想撤销一次 commit 而不重写历史

$ git revert HEAD # 撤销最近一次 commit
$ git revert <commit-sha> # 撤销指定 commit
复制代码

这条命令将从新运行提交新的 commit 时的逆过程, 从而撤销你的更改而不会撤销历史记录。 在共享的分支中撤销 commit 时,重写历史记录会很是的复杂, 因此使用 git revert 是一种很安全的解决方式。

清理 (Cleanup)

我去,咋有这么多分支

$ 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 了解更多。

来清理旧分支和无效 commit 吧~

$ git fetch --all --prune
复制代码

若是你已经为远程仓库设置了在 merge 时删除分支,这条命令也很是有用。

(译者注:git fetch --prune 将会在 fetch 前 移除在本地的全部远程仓库中再也不存在的远程跟踪引用,详见官方文档)

Aliases

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>
复制代码

其余 Git 技巧

忽视文件 (Ignoring Files)

不少文件可能不须要存在于版本控制中,你能够设置全局 gitignore 文件 来忽视掉它们。须要忽视的文件多是一些 node_modules 文件夹, .vscode 或其余 IDE 的文件,以及一些 Python 的虚拟环境。

对于一些敏感信息,你可使用环境变量文件存放,而后将它们添加到 项目根目录下的 .gitignore 文件中。

特殊文件 (Special Files)

你可能须要将一些文件标记为二进制文件,以便于 Git 能够将其忽视, 而且不用为它们产生冗长的差别性检测。Git 有一个 .gitattributes 文件 来实现这一操做。好比在一个 JavaScript 项目中, 你会在 .gitattributes 中添加一个 yarn-lock.json 或者 package-lock.json,这样一来,在你每次更新时, Git 不用每次都尝试记录它们的差别变化。

# .gitattributes
package-lock.json binary
复制代码

Git 工做流

Rebase vs. Merge

你的团队可能会从 rebase 和 merge 两种工做流中二选其一, 两者都有利弊,我曾见过这两种方式均可以产生很高的效率。 对于大多数状况,除非你 真的 了解你正在作什么, 不然选择 merge 工做流就完事了。

当你主要使用 merge 来为产品更新迭代时, 仍然也能够高效的使用 rebase。 最多见的场景是,你正在一个 feature 上工做, 同时另一个开发者 pull 了一个别的 feature 到 master。 你确实可使用 git merge 将那些改变一块儿带上, 可是这样,你会对队友作的简单更改有一个额外的 commit。 你真正想作的是将你的提交 从新提交 到最新的 master 的最上面。

$ git rebase master
复制代码

这条命令将给你一个更干净的 commit 历史。

深度解释它们之间的不一样点可能须要一整篇论文来阐述, 所以,我建议你能够查阅 the Atlassian docs 中有关这些差别的文章。

远程仓库设置 (Remote Repository Setting)

我对于 GitHub 和 GitLab 最为熟悉,可是其余远程仓库管理器也 应该支持这些设置。

1. 在 merge 时删除分支

一旦有分支被 merge,你就不该该在关心这个分支, 由于它的历史将被映射到你的 master/dev 分支。 这一举措会显著的减小你所管理的分支数量。 也能够经过使用 git fetch -all --prune 更为高效的保持本地仓库的干净整洁。

2. 防止直接 push 到 master

若是没有这个设置,很容易在 git push 时,忘记本身正在 master, 这会潜在的破坏你的产品,一点也很差。

3. merge 前至少须要一次确认

取决于团队的大小,你可能须要在 merge 前须要屡次的确认, 即便只是一个二人团队,最少也要确认一次。 你不用花费几个小时每行都看,但通常来讲, 你的代码应该至少有两我的看过。 反馈 是学习和我的提高的关键。

4. merge 前须要经过 CI 测试

(译者注:CI 即 Continuous Integration,持续集成)

已损坏的改变不该该被 merge 到产品中。 测试人员没法 100% 的捕捉损坏的更改,所以须要自动执行这些检测。

Pull 请求 (Pull Requests)

保持 Pull 请求小而简洁,理想状况下不超过几百行。 小而频繁的 Pull 请求会使得审阅过程更快,从而产生更多的无 bug 代码, 也会让你的队友更轻松,从而提高团队的效率,也更容易分享学习经验。 团队内部达成一个共同的承诺,承诺天天都花一些时间来审阅公开 Pull 请求。

咱们都爱这样的审阅: ![reviewing]res.cloudinary.com/practicalde…)

若是你正在实现的特性在一段时间内都会处于损坏状态, 请使用特性标签来在产品中禁用它。 这将会防止你的特性分支和 dev/master 分支产生太大差别, 同时也容许你作更频繁,更小的 Pull 请求。不 merge 代码的时间越长, 之后 merge 的难度就越大。

最后,在你的 Pull 请求中放一个细节描述,若是必要的话, 能够放图片或者 GIF。若是你使用像 Jira 这样的工具管理票据 (tickets,译者注:没使过不太懂啥意思), 描述中也能够包括 Pull 请求地址的票据的编号。 Pull 请求的描述和可视化作的越详细,可能你的队友就越想审阅你的代码, 而不是拖延着下次必定。

分支命名 (Branch Naming)

你的团队可能会提出分支命名的规范,以便于导航。 我喜欢每一个分支以建立人的名字首字母开头,接着一个左斜杠, 而后是以短横线链接的分支描述。

这可能看起来微不足道,可是配合 tab 补全以及相似 grep 这样的工具一块儿使用, 这确实能够帮你找到并理解可能有的全部分支。

例如,我建立了一个新分支:

$ git checkout -b gabud/implement-important-feature
复制代码

一周后,当我忘记我以前给它起了什么名字, 我能够键入 git checkout gabud,而后按 tab 键, 个人 Z shell 就会向我展现全部我本地的分支以供选择, 而不用看我队友们的分支。

提交信息 (Commit Messages)

语言很重要,通常来讲,我发现最好不要以破碎状态提交东西, 每一次提交都应该有一个简洁的信息,说明所作的更改。 按照官方 Git 建议,我发现最好使用当前的命令式意义来提交信息。 能够将每一个提交信息视为对 计算机/git 的命令, 以致于你能够将提交信息加到这句话后面:

若是这个 commit 被应用,将会...

在当前命令式意义上,良好的提交示例为:

$ git commit -m "Add name field to checkout form"
复制代码

如今能够这么读:“若是这个 commit 被应用,将会在表单中添加姓名域。”

最后的想法

这毫不是已经了解了 Git 的所有,建议查阅 官方文档git help 了解更多。 不要惧怕向你的队友问一些 Git 的问题, 你会惊讶的发现大多数队友也有许多相同的问题。

那么你呢?哪一个 Git 命令或者概念在你的工做流中最有用呢~

相关文章
相关标签/搜索