54点亮Git 技能树

from:
原文阅读

闯过这 54 关,点亮你的 Git 技能树

开篇

如今,Git 大行其道,颇有一统天下之势。

如果你的技能树上 Git 和 Github 的图标还没有点亮的话,你都不好意思说你是程序员。

别说互联网企业,我接触到的许多传统企业都在从 SVN,Clear Case 等迁移到 Git 上,甚至大厂还会有一个团队去定制适合自己企业的 Git 服务器。

很多人简历上写的「精通 Git 与 Github」,但如果你问他熟悉到什么程度的话,回答通常是「就是会用常用的 addcommitpush 操作」。

但工作中我们会遇到一些更加复杂的场景:
    * 忽略某些文件
    * 创建,删除分支
    * 找到最后修改某行代码的人
    * commit 后发现里边包含一个不应该提交的文件
    * commit 后发现少提交了一些文件
    * 一个文件中的多次有改动,怎么做到只提交其中的一部分?
    * 怎样整理提交记录使其更加整洁?
    * 快速定位是哪一次提交引入了 bug
    * ...

作为一线程序员,我们要如何快速学习 Git 以发挥其最大威力呢?

今天我就要向大家介绍一个工具,准确说应该是「游戏」,名字叫「githug」,它把平常可能遇到的一些场景都实例化,变成一个一个的关卡,一共有 55 个关卡:
    #1: init
    #2: config
    #3: add
    #4: commit
    #5: clone
    #6: clone_to_folder
    #7: ignore
    #8: include
    #9: status
    #10: number_of_files_committed
    #11: rm
    #12: rm_cached
    #13: stash
    #14: rename
    #15: restructure
    #16: log
    #17: tag
    #18: push_tags
    #19: commit_amend
    #20: commit_in_future
    #21: reset
    #22: reset_soft
    #23: checkout_file
    #24: remote
    #25: remote_url
    #26: pull
    #27: remote_add
    #28: push
    #29: diff
    #30: blame
    #31: branch
    #32: checkout
    #33: checkout_tag
    #34: checkout_tag_over_branch
    #35: branch_at
    #36: delete_branch
    #37: push_branch
    #38: merge
    #39: fetch
    #40: rebase
    #41: repack
    #42: cherry-pick
    #43: grep
    #44: rename_commit
    #45: squash
    #46: merge_squash
    #47: reorder
    #48: bisect
    #49: stage_lines
    #50: find_old_branch
    #51: revert
    #52: restore
    #53: conflict
    #54: submodule
    #55: contribute

安装


首先我们需要来安装这个游戏,githug 是用 Ruby 编写的,可通过如下命令安装:
gem install githug

如果遇到权限问题,请加上sudo
sudo gem install githug

安装成功后,在 Terminal 里进入你常用的目录,输入githug,会提示游戏目录不存在,是否要创建一个,输入y然后回车:



根据提示cd git_hug 进入游戏目录,准备开始游戏。

基本命令

在开始前我们还需要了解游戏的一些基本操作:
  • play - 默认命令,检查是否过关

  • hint - 显示过关提示

  • reset - 重启本关,或者重启到指定的某关

  • levels - 显示关卡列表

来试一下,githug reset


示例


我以第一关为例子给大家演示一下玩法。

第一关的名称是:init,提示是:「一个新目录 git_hug 被创建了,请把它初始化为一个空仓库」。
假设现在我不知道该怎么过关,我可以查看过关提示:



指示是:「你可以输入 git 命令来查看 git 命令列表」。



看最后一行,原来用 git init 就可以初始化一个空仓库,接着输入 githug 进行过关检测:



太棒了!顺利进入第二关!

怎么样?明白了吗?后面的 54 关就靠你自己了哦!

友情提示


在闯关过程中,尽量使用 githug hintgit help subcommand 获取帮助,也可以根据下面的两个教程来系统学习。

猴子都能懂的 git 入门

廖雪峰的 git 教程

为保大家都能顺利通关,学到所有的知识点,接下来我会写过关攻略,详细介绍每一关的玩法。

并且我不会直接给答案,而是演示整个过关的过程。

祝你好运!我们明天见。

今天我将带大家完成前十关。第一关在前文中已经完成,我们从第二关开始吧。
如对任何命令有疑问请看第一篇里的推荐教程。

第二关




根据提示,我们要配置「用户名」和「邮箱」,为什么呢?
你的代码提交后,特别是提交到开源社区,被别人看到,人家觉得这个代码写的真牛,想给你一个工作机会,怎么联系你?

通常,我们在 IDE 里创建一个新文件,IDE 都会自动加上日期,作者,邮箱等信息。其实我认为这都是过时的传统了,现在我们喜欢尽量保持源代码干净。
像创建日志,作者,邮箱这些信息都应该交给版本控制系统来记录。
当然如果你代码写的很烂的话就要小心了,别人 git blame 一下就可以问候你的家人了。

具体怎么过关过程如下:


第三关


第四关



第五关



第六关



第七关


这一关对于不了解 Vim 的同学可能有一点麻烦。这里也是一个很贴近实际的场景,用 Vim 编辑文件的时候,会产生一个.swp 文件,这个文件的作用是:在你不小心退出了 Vim 或 Vim 自己崩溃后,再次编辑这个文件,就会提示你存在一个.swp 文件,询问是否恢复。
它属于临时文件,显然不应该被提交到 VCS 里去。所以 Git 有一个机制让我们可以忽略某些文件。


第八关


这一关是上一关的延伸,忽略某类文件,但排除其中特定的一个。



根据提示来看一下帮助 git gitignore --help,输入 /negate 进行搜索,一下就定位到下面这一段:



根据帮助我们知道了,可以用 ! 来对模式取反。
所以我们编辑 .gitignore,追加如下两行:
    *.a
    !lib.a

提示:需要注意使用:git gitignore --help,发现“!”的模式取反

成功过关!


第九关


git status 是一个非常常用的命令,插一句题外话:我给它配一个别名 gst ,这样敲起来就非常方便。


第十关


这两关都是考察基本概念,一个文件的几种状态:

  • untracked - 新增的文件,Git 根本不知道它的存在

  • not staged - 被索引过又被修改了的文件

  • staged - 通过 git add 后被即将被提交的文件



这里一看状态就知道了,如果现在执行 git commit,只有最上面的两个文件会被 commit。



今天就先到这里,如有任何疑问欢迎到 「这里」与我讨论!


今天我将带大家完成第 11 - 20 关。如对任何命令有疑问请看第一篇里的推荐教程。

第十一关




在 git 里,创建一个新文件时,要用 git add 来告诉 git,同样,删除一个文件时候,也要告诉 git。


第十二关


有时候执行 add 操作的时候不小心把多余的文件 add 进去了,这时我们需要把它从 staging area 移除出来,但不能删除文件。
通过 git help rm,搜索 cached,可以看到有这个参数,刚好能满足我们的需求。
    --cached
        Use this option to unstage and remove paths only from the index. Working tree files, whether modified or
        not, will be left alone.


第十三关


这一关的场景也非常常见,回想一下以前我们用 CVS 或 SVN 的时候。
你写一个新需求写的正 High,突然你的主管找到你说有一个用户打电话来遇到一个问题,非常紧急。
我以前的做法是:把当前改过的文件「复制」一份先放在其它地方,把代码还原到没修改的状态,签出对应 production 环境的代码。
定位修复问题,提交代码,确认问题修复后再合并回开发分支,并把之前备份的代码复制进来进行合并。

stash 正是解决这个问题的,只需要 git stash,它就把当前未提交的改动「复制」到另一个地方暂存起来,待要恢复的时候执行 git stash pop 即可。


第十四关


这一关有两种解法,第一种是:
    mv oldfile.txt newfile.txt
    git rm oldfile.txt
    git add newfile.txt

显然这样做用户体验很糟,Linux 肯定不会这么设计软件的。

第二种做法:

git mv oldfile.txt newfile.txt


第十五关


这一关相当于上一关的增强版,可以用通配符一起批量移动文件。


第十六关


有时候我们要看代码的提交历史,比如 [Code Review][9] 的时候。
不过 git 自带的 git log 命令比较弱,建议使用 GUI 客户端或 tig。


第十七关



第十八关


git push 命令默认是不会 push Tags 的,需要加参数。


第十九关


有时候提交之后发现漏掉了某些文件,怎么办?
往往很多人就会选择再单独提交一次,这样做其实是不合理的,之前的 commit 就不完整了,有可能上了 CI 就会挂掉。
好的做法是 amend:



命令执行后会调用默认的编辑器编辑 commit message,它会自动带出之前的 message,如果不需要修改,直接保存退出即可。

第二十关


默认提交的时候记录的是当前系统时间,但这一关要求覆盖提交日期。
我想不到有什么场景需要这么做,邪恶一点,有可能你把周五的提交,日期写成周末,这样表示周末在加班。
纯属YY,千万不要学,学了也不要告诉别人是我教你的。
git help commit,搜索 date
    --date=
        Override the author date used in the commit.





今天我将带大家完成第 21 - 30 关。如对任何命令有疑问请看第一篇里的推荐教程。

第二十一关




本来只想提交一个文件,结果意外地把另一个文件也 add 了,怎么把它搞出来?

git reset 可以用来改变 HEAD 的位置或把文件从 staging area 移除出来,但并不会丢失任何的修改(我在刚开始接触 git 的时候觉得这个命令很危险,总感觉会把变更丢掉)。


第二十二关


提交太快,多提交了一个文件怎么办?

git reset 可以帮我们把当前的 HEAD 重置到指定的位置,这里是倒数第二个,所以是:HEAD~1
但我们希望变更还保持在 staging area,不然还得 add 一次挺麻烦,那可以加上 \--soft 参数。
    --soft
          Does not touch the index file or the working tree at all (but resets the head to , just like
          all modes do). This leaves all your changed files "Changes to be committed", as git status would put it.


第二十三关


有时候改了一些代码,结果发现这个需求取消了,怎么能快速扔掉所有的变更,回到没有修改之前的状态呢?


第二十四关


为了与别人合作,我们需要把 Git 仓库 push 到远端仓库上去,远端仓库对应就是 remote


第二十五关


远端仓库除了有名称,还得有一个 URL 地址。


第二十六关


别人提交了代码,我们就要拉到本地来。


第二十七关



第二十八关



第二十九关



第三十关


居然有人把明文密码写到了代码里,赶紧拉出来枪毙十分钟,怎么快速找到某行代码最后的修改者呢?



今天就到这里了,明天再见!



今天我将带大家完成第 31 - 40 关,如对任何命令使用有疑问请看第一篇里的推荐教程。

第三十一关




当准备做的事情有可能会破坏其它东西时,为了不影响其他同事的开发工作,我们通常会拉一个分支出来,在分支上去做修改。


第三十二关


上一条命令只是创建了一个新的分支,并没有 checkout 过去,习惯做法通常是直接 git checkout -b xxx,创建并checkout 到新的分支。

如果使用 oh-my-zsh 的 git 插件的话,可以用 gbc,意思是:git branch create


第三十三关


版本 1.2 存在 bug,这里我们需要切换到 1.2 的代码以定位问题。Checkout tag 和分支没有什么区别。


第三十四关


但当存在同名的 tag 和分支时,git 不知道我们究竟是要 checkout 到 tag 还是到分支,它认为分支的优先级更高。

这时就要显式地告诉 git 我们是要切换到 tag。


第三十五关


有时忘记开新的分支,就修改并提交了代码。开分支的时候默认是基于最新的一次提交的,但我们也可以指定参数使其基于任一次提交。


第三十六关


分支开太多就不好管理,不管使用哪种分支模型,只有很少的分支会长期存在,大部分分支都是临时的,在代码合并后就会删除掉。


第三十七关


有时候在特性分支上提交了代码,但还不能并入主干,却又希望和别的同事分享(比如需要他们帮做 Code Review),那就需要把分支 push 到远程仓库中去。


第三十八关


将另一个分支并入当前工作分支。


第三十九关


当远程仓库有更新,但我们并不想合并到本地仓库,只想把代码拿下来看看,我们会用到 fetch 命令。


第四十关


Rebase 这里如果不理解,请看第一篇里的推荐教程。



今天就到这里了,明天(下次)再见!