最近对 git 比较感兴趣,主要是因为平时工作使用 svn,慢慢感觉到 svn 的不方便。例如一定要在能连接 svn 服务器时才能提交代码。而 git 天生就没有这个问题。
在看 git 相关资料时发现一个叫做 Githug 的小游戏(其实是一些 git 命令行的练习题),它将平时使用 git 常遇到的场景变成一关一关,从头到尾玩通关,git 常用的基本技能也就掌握得差不多了。这里记录下每一关的场景和解决方法。
Level 1, init
|
|
要求把一个目录初始化为 git 仓库,只要在该目录中执行以下命令即可。
|
|
Level 2, config
|
|
配置 Git 的用户名和邮箱,用来作为 git 操作时的用户ID。可以全局设置一个,也可以针对当前仓库设置。
全局设置
|
|
仓库级别设置
|
|
Level 3, add
|
|
添加文件到 staging area。可以使用 git add
命令,在此前后可以使用 git status -s
命令查看文件的状态。
|
|
Level 4, commit
|
|
把 staging area 中的文件提交,使用 commit
命令。 -m
参数后跟着的是提交时的说明信息。也可以选择不加 -m
参数,直接执行 git commit
后会自动打开编辑器供输入说明信息。
|
|
Level 5, clone
|
|
克隆一个仓库到本地,默认目录名为仓库名。
|
|
Level 6, clone to folder
|
|
克隆一个仓库到本地,并指定目录名。
|
|
Level 7, ignore
|
|
所有 .swp 后缀的文件,不希望加入 git 版本管理。git 目录下有一个 .gitignore
文件,文件中列出了要 git 忽略的文件,支持用 * 号表示通配符。
所以只要在 .gitignore 文件中添加一行 *.swp,即可忽略所有 .swp 文件.
|
|
Level 8, include
|
|
忽略所有 .a 后缀的文件,除了 lib.a。
在 .gitignore
文件中使用 ! 表示不要忽略
|
|
Level 9, status
|
|
用 git status
命令查看当前目录中文件的 git 状态。Untracked files 下的红色的文件就是题目中要找的文件。
|
|
Level 10, number of files committed
|
|
要求找出有多少个文件处于待 commit 状态。用 git status
命令查看当前目录中文件的 git 状态。Changes to be commited 下的文件即是处于 staging area,待 commit 的文件。
|
|
Level 11, rm
|
|
有个文件从本地删除了,但未从 git 仓库中删除,要求找到这个文件,并提交删除。
同样的,先使用 git status
命令查看文件状态。
|
|
然后提交删除,提交之前先将修改的内容 add 到 staging area。
|
|
Level 12, rm cached
|
|
有个文件已经加到 staging area 中,要把它从 staging area 移除,注意不要从文件系统中删除。git rm
命令可以将文件从 git 仓库中删除,而加上 --cached
参数可以只从 staging area 中删除。
|
|
扩展:
|
|
Level 13, stash
|
|
将本地所做的修改,临时保存下来,但不是提交。这里可以利用 git 提供的 stash 功能,stash 是一个栈,可以多次保存。要恢复时可以多次 pop 将现场恢复回来。
|
|
扩展:
|
|
Level 14, rename
|
|
使用 git mv
重命名文件,执行完之后修改自动处于 staging area 中。
|
|
Level 15, restructure
|
|
移动已加入 git 管理的文件。把所有 .html
文件移动到一个新目录 src
中。
|
|
Level 16, log
|
|
找出最近一次提交的 hash 值。可以使用 git log
命令查看 log。
|
|
以下是输出内容:
|
|
扩展:
|
|
Level 17, tag
|
|
为最后一次 commit 打上 tag,命名为 new_tag
。可以使用 git tag <tag_name>
来打 tag。
|
|
扩展:
|
|
Level 18, push tags
|
|
把本地所有 tags 都推送到远端仓库。可以使用 git push
命令来 push,--tags
命令指定要推送的是 tags。
|
|
Level 19, commit amend
|
|
某次 commit 时漏掉了 forgotten_file.rb
文件,现在要求把这个文件加入到那次 commit 中去。可以使用 git commit
的 --amend
参数来修改 commit 信息。提交时会进入编辑器供输入提交信息。
|
|
Level 20, commit in future
|
|
还是提交,但是要求提交时间在明天。可以使用 git commit
的 --date=<date>
参数来指定 commit 的时间。
|
|
Level 21, reset
|
|
有 2 个文件都被添加到了 staging area,现在把其中一个从 starging area 撤销下来,下次再提交。可以使用 git reset HEAD <file>
来执行撤销操作。
|
|
Level 22, reset soft
|
|
撤销最后一次 commit,但依然保持本地的索引。可以使用 git reset
命令的 --soft
参数,这样 reset 之后上一次 commit 就被撤销了,而该 commit 所做的修改还会保留在 staging area 中。
|
|
扩展:
|
|
Level 23, checkout file
|
|
放弃本地对于某个文件的修改,可以使用 git checkout
命令。该命令会使本地所做的修改被回退。
|
|
Level 24, remote
|
|
git 提供了 git remote
命令和相关参数来查看和操作远端仓库。现在查看该项目的远端仓库。
|
|
Level 25, remote url
|
|
查看远端仓库的 URL。可以使用 git remote
的 -v
参数(--verbose
的简写) 命令来查看所有远端仓库的名字和对应的 URL。
|
|
Level 26, pull
|
|
要把远端仓库有而本地没有的修改拉取到本地,可以使用 git pull <remote-name> <master-name>
命令。在此之前可以先用 git remote -v
查看有哪些远端仓库。
|
|
Level 27, remote add
|
|
添加一个远端仓库,指定名字和 url。可以使用 git remote add <remote-name> <url>
。
|
|
Level 28, push
|
|
本地和远端仓库已经分别做了一些修改,现在要将本地与远端先对齐,然后再把本地的修改 push 到远端。可以使用 git rebase
命令将远端的修改拉取到本地,然后再 git push
将本地的修改推送到远端。
|
|
Level 29, diff
|
|
查看本地文件和 staging area 的差异,可以用 git diff
命令。
|
|
以下是输出的内容:
|
|
扩展:
|
|
Level 30, blame
|
|
找出 repository 中某个文件的某一行是谁写的。可以使用 git blame
命令来查看某个文件的每一行分别对应的 commit,从而可以知道每一行最后的修改人是谁。
|
|
以下是输出内容:
|
|
Level 31, branch
|
|
创建一个分支,可以使用 git branch <branch_name>
命令来创建。
|
|
扩展:
|
|
Level 32, checkout
|
|
创建并切换到分支 my_branch,可以使用上面提到的命令 git checkout -b
。
|
|
Level 33, checkout tag
|
|
切换到名字为 v1.2
的 tag。git checkout
命令除了可以切换 branch 之外,还可以切换到某个 tag。在此之前可以使用 git tag
命令查看所有 tag。
|
|
扩展:
|
|
Level 34, checkout tag over branch
|
|
与上一题一样,要求是 checkout 到名字为 v1.2
的 tag,但与此同时存在一个同名的 branch。此时可以用 ·git checkout tags/
|
|
Level 35, branch at
|
|
针对某个特定的 commit 创建一条分支。我们知道 git branch <branch_name>
可以基于当前版本创建一条分支,而在后面跟上某个 commit 的 hash_code 即可针对某个特定的 commit 创建分支。
先查看 log 信息
|
|
以下是输出内容:
|
|
找到需要的 hash code,创建分支
|
|
Level 36, delete branch
|
|
删除某条分支,可以使用 git branch -d <branch_name>
命令。
|
|
Level 37, push branch
|
|
push 某个指定的分支到远程仓库。
|
|
Level 38, merge
|
|
将 feature 分支的代码合并到 master 来。可以使用 git merge
。
先查看有哪些分支
|
|
把 feature 分支的代码合并到 master
|
|
Level 39, fetch
|
|
获得远程仓库的修改,但不要合并到本地分支来。可以使用 git fetch
来拉取远程仓库的代码,但不合并。
|
|
扩展:
|
|
Level 40, rebase
|
|
有2条分支,feature 和 master,各自前进了一段时间。现在准备把 feature 合并到 master 来,但假如我们使用 git merge
的话,在 master 分支会看到一条 merge 的log。
现在我们不希望产生这条 merge 的 log,而希望合并后的 log 看上去就像 feature 分支从来都不存在、feature 的提交都是在 master 上进行的,可以使用 git rebase
命令。
|
|
Level 41, repack
|
|
将未打包的松散对象打包。(作用不是很懂)
|
|
Level 42, cherry pick
|
|
要把 new-feature 分支上关于 README
文件的修改提取到 master 来。注意不是 merge,因为 new-feature 上还有其他的修改不想被合并到 master。可以使用 git cherry-pick
- 首先查看在 new-feature 分支中对 README.md 所做修改的 log
|
|
找到我们要提取的那个 commit,对应的 hash-code 是 ca32a6dac7b6f97975edbe19a4296c2ee7682f68
使用 git cherry-pick 命令把该 commit 应用到 master 来
|
|
Level 43, grep
|
|
搜索项目中有多少个 TODO
字符串,可以使用 git grep <string>
命令。
|
|
扩展:
git grep
命令和 Linux 的 grep
命令的作用有什么区别呢?其实两者都是用来搜索文件内容的,区别在于,git grep
命令会只搜索被 git 管理的那些文件,而 grep
命令会搜索本地文件系统中的文件,不管是不是被 git 管理。
Level 44, rename commit
|
|
纠正 first commit 中的拼写错误。git 允许对提交历史做一些修改,比如修改 message、把某个 commit 应用到另一个分支、删除某个提交。做法是使用 git rebase -i <commit-hash>
命令,它将列出这个 commit 之后的 log,然后可以对这些 log 做一些修改。
- 首先用
git log
查看是哪个 commit 需要修改
|
|
记录要修改的 commit 的上一个 commit 的 hash code:a0ed52f82cc226218902ad72f766064b991a31f0
使用
git rebase -i
进入修改的状态
|
|
- 此时会打开预设的编辑器,编辑 commit log,其中第一列表示要对每个 commit 做的修改,可能的值见下方注释。在这里我们把拼写错误的这条记录前面的命令修改为
werord
(表示要应用这个 commit 并要修改 message),保存退出即可。
|
|
- git 会再次进入编辑器,此时修改拼写错误的地方,保存并退出,git 将重新应用这个 commit。
|
|
Level 45, squash
|
|
要求将多个 commit 合并成一个,与 Level 44
一样是修改提交历史。同样使用 git rebase -i
由于步骤和 Level 44
类似,这里仅列出修改 commit 前面的命令的那一步。
|
|
Level 46, merge squash
|
|
要求将某个分支的所有修改都应用到 master。与 Merge 和 rebase 不同的是,这些修改被要求合并成一个。可以在 merge 时加上 --squash
参数。这样分支上的所有修改都会到 staging area 来,然后再 commit 一次,就能把那些修改一次性提交了。
|
|
Level 47, reorder
|
|
修改某几个提交的顺序。提到修改历史,我们就应该想起 git rebase -i
命令。
- 类似于
Level 44
操作。
|
|
- 来到到修改 commit 的那一步。
|
|
- 把顺序修改对,即把
Second commit
和Third commit
整行顺序对调。然后保存退出,即可。
|
|
Level 48, bisect
|
|
现在项目中有个 bug 是某一次 commit 引入的。使用 make test
可以测试项目,返回值是 0 的话表示测试通过,无 bug。要求找出引入 bug 的那个 commit。
我们的第一反应是看 commit log,将代码一个一个回退到各个版本,运行 make test
检查测试是否通过,以此来找到引入 bug 的那一次 commit。而 git 提供了 bisect
二分搜索方法来为我们做这件事。
git birect
的基本用法如下:
- 执行
git birect start
,表示开始二分搜索; - 找到任意一个有问题的 commit,标记一下这个版本。一般这种情况下当前版本都是有问题的,所以
git birect bad
- 找到任意一个没问题的 commit,标记一下这个版本。如
git birect good f608824888b83bbedc1f658be7496ffea467a8fb
- 当你至少标记了一个 good 和一个 bad 之后,就会看到类似于
Bisecting: 675 revisions left to test after this
的提示,告诉你你标记的两个版本之间有这么多个待测试的版本。 - 然后两者中间的那个版本会自动被
checkout
,你要做的是测试一下这个版本有没有问题,并标记为git birect good 或 bad
- git 会自动重复第 4 步,然后你重复第 5 步。这样一直二分搜索下来,直到找到第一个有问题的版本。
当然,这样一个一个版本手工测试还是很麻烦,如果你的项目有没有 bug 可以通过脚本自动检测,然后通过返回 0 来表示测试通过,返回其他值表示不通过,那么就可以省下手工测试的环节了。直接在第 4 步标记完之后,执行 git run <你的测试脚本>
,git 将自动重复执行 第 4、5 步,直至找到第一个有问题的版本。
下面来看 githug 的这个例子:
- 开始二分搜索:
|
|
- 检查当前版本,测试一下,发现不通过,标记为 bad
|
|
- 查看 log,checkout 到第一个版本,测试一下,通过,标记为 good
|
|
- 已经标记了一个 good 和一个 bad,可以执行自动二分搜索。最终输出的结果就是第一个有问题的 commit。
|
|
Level 49, stage lines
|
|
工作区在同一个文件中做了一些修改,现在想要其中的某些修改被 add 到 staging area,剩余的修改保留在工作区中。
可以使用 git add
的 -p
参数,选择哪些修改是要 add 的,哪些修改是先不 add 的。执行该命令会输出类似于 git diff
的结果,然后可以选择 e
进行编辑。
|
|
选择 e
后会进入编辑器,此时只要将不想被 add 的 +
号行删除,保存并退出,就不会被 add 到 staging area。
|
|
Level 50, find old branch
|
|
你在当前分支 kill_the_batman
工作完了之后,忘记了之前是从哪里来的了。git 提供了 git reflog
命令来管理所有操作记录。
|
|
可以看到 moving from solve_world_hunger to kill_the_batman
,所以知道之前是从 solve_world_hunger
来到 kill_the_batman
的。按题目要求,checkout 回去 solve_world_hunger
即可。
Level 51, revert
|
|
回退之前的某一个 commit。注意这里已经 push 过,所以不应该使用 git rebase -i
命令去修改历史,以免版本库混乱。而可以使用 git revert
来回退某个提交。当然,这将产生一个新的 commit,在编辑器中写上 message,保存并退出即可。
|
|
Level 52, restore
|
|
你之前一冲动,git reset --hard HEAD^
使版本库丢掉了一个 commit,回到了倒数第二个 commit 的状态。现在想找回来。
此时 git log
已看不到最后一次 commit 了,但 git reflog
还能看到,因为它保存着你所做的修改。
|
|
看到 40a0079
就是你要找回的那次 commit。接下来 git checkout
它即可。
|
|
Level 53, conflict
|
|
将 mybranch
分支 merge
到 master
,期间会有冲突,用编辑器编辑冲突的文件,去掉带 <<<
、===
和 >>>
的行,并编辑好它们之间的行,即可。
|
|
Level 54, submodule
|
|
要求将一个仓库作为一个 submodule
引入到当前项目中。
|
|
Level 55, 结束
|
|
到这里所有关卡就结束了,这一关是鼓励大家为 Githug 项目做贡献。
结束
Git 的管理相当复杂,又十分强大,这里列出的很多命令都还没有详细去了解,以上这些题只是能列出常见的场景和相应的操作方法,至于要了解各个命令的详细用法,还是得系统地学习才行。