Githug 通关流程

最近对 git 比较感兴趣,主要是因为平时工作使用 svn,慢慢感觉到 svn 的不方便。例如一定要在能连接 svn 服务器时才能提交代码。而 git 天生就没有这个问题。

在看 git 相关资料时发现一个叫做 Githug 的小游戏(其实是一些 git 命令行的练习题),它将平时使用 git 常遇到的场景变成一关一关,从头到尾玩通关,git 常用的基本技能也就掌握得差不多了。这里记录下每一关的场景和解决方法。

Level 1, init

1
2
3
4
5
Name: init
Level: 1
Difficulty: *
A new directory, `git_hug`, has been created; initialize an empty repository in it.

要求把一个目录初始化为 git 仓库,只要在该目录中执行以下命令即可。

1
2
3
➜ cd git_hug
➜ git init
Initialized empty Git repository in /Users/chant/gittest/git_hug/.git/

Level 2, config

1
2
3
4
5
Name: config
Level: 2
Difficulty: *
Set up your git name and email, this is important so that your commits can be identified.

配置 Git 的用户名和邮箱,用来作为 git 操作时的用户ID。可以全局设置一个,也可以针对当前仓库设置。

全局设置

1
2
git config user.name chanthuang
git config user.email chant.chenhuang@gmail.com

仓库级别设置

1
2
git config --local user.name chanthuang
git config --local user.email chant.chenhuang@gmail.com

Level 3, add

1
2
3
4
5
6
Name: add
Level: 3
Difficulty: *
There is a file in your folder called `README`, you should add it to your staging area
Note: You start each level with a new repo. Don't look for files from the previous one.

添加文件到 staging area。可以使用 git add 命令,在此前后可以使用 git status -s 命令查看文件的状态。

1
2
3
4
5
6
7
➜ git status -s
?? README
➜ git add README
➜ git status -s
A README

Level 4, commit

1
2
3
4
5
Name: commit
Level: 4
Difficulty: *
The `README` file has been added to your staging area, now commit it.

把 staging area 中的文件提交,使用 commit 命令。 -m 参数后跟着的是提交时的说明信息。也可以选择不加 -m 参数,直接执行 git commit 后会自动打开编辑器供输入说明信息。

1
2
3
4
➜ git commit -m 'add README file'
[master (root-commit) f254dc2] add README file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README

Level 5, clone

1
2
3
4
5
Name: clone
Level: 5
Difficulty: *
Clone the repository at https://github.com/Gazler/cloneme.

克隆一个仓库到本地,默认目录名为仓库名。

1
2
3
4
5
6
➜ git clone https://github.com/Gazler/cloneme
Cloning into 'cloneme'...
remote: Counting objects: 7, done.
remote: Total 7 (delta 0), reused 0 (delta 0), pack-reused 7
Unpacking objects: 100% (7/7), done.
Checking connectivity... done.

Level 6, clone to folder

1
2
3
4
5
Name: clone_to_folder
Level: 6
Difficulty: *
Clone the repository at https://github.com/Gazler/cloneme to `my_cloned_repo`.

克隆一个仓库到本地,并指定目录名。

1
2
3
4
5
6
➜ git clone https://github.com/Gazler/cloneme my_cloned_repo
Cloning into 'my_cloned_repo'...
remote: Counting objects: 7, done.
remote: Total 7 (delta 0), reused 0 (delta 0), pack-reused 7
Unpacking objects: 100% (7/7), done.
Checking connectivity... done.

Level 7, ignore

1
2
3
4
5
Name: ignore
Level: 7
Difficulty: **
The text editor 'vim' creates files ending in `.swp` (swap files) for all files that are currently open. We don't want them creeping into the repository. Make this repository ignore `.swp` files.

所有 .swp 后缀的文件,不希望加入 git 版本管理。git 目录下有一个 .gitignore 文件,文件中列出了要 git 忽略的文件,支持用 * 号表示通配符。

所以只要在 .gitignore 文件中添加一行 *.swp,即可忽略所有 .swp 文件.

1
➜ echo "*.swp" >> .gitignore

Level 8, include

1
2
3
4
5
Name: include
Level: 8
Difficulty: **
Notice a few files with the '.a' extension. We want git to ignore all but the 'lib.a' file.

忽略所有 .a 后缀的文件,除了 lib.a。
.gitignore 文件中使用 ! 表示不要忽略

1
2
➜ echo "*.a" >> .gitignore
➜ echo "\!lib.a" >> .gitignore

Level 9, status

1
2
3
4
5
Name: status
Level: 9
Difficulty: *
There are some files in this repository, one of the files is untracked, which file is it?

git status 命令查看当前目录中文件的 git 状态。Untracked files 下的红色的文件就是题目中要找的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: Guardfile
new file: README
new file: config.rb
new file: deploy.rb
new file: setup.rb
Untracked files:
(use "git add <file>..." to include in what will be committed)
database.yml

Level 10, number of files committed

1
2
3
4
5
Name: number_of_files_committed
Level: 10
Difficulty: *
There are some files in this repository, how many of the files will be committed?

要求找出有多少个文件处于待 commit 状态。用 git status 命令查看当前目录中文件的 git 状态。Changes to be commited 下的文件即是处于 staging area,待 commit 的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
➜ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: rubyfile1.rb
modified: rubyfile4.rb
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: rubyfile5.rb
Untracked files:
(use "git add <file>..." to include in what will be committed)
rubyfile6.rb
rubyfile7.rb

Level 11, rm

1
2
3
4
5
Name: rm
Level: 11
Difficulty: **
A file has been removed from the working tree, however the file was not removed from the repository. Find out what this file was and remove it.

有个文件从本地删除了,但未从 git 仓库中删除,要求找到这个文件,并提交删除。

同样的,先使用 git status 命令查看文件状态。

1
2
3
4
5
6
7
8
9
➜ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: deleteme.rb
no changes added to commit (use "git add" and/or "git commit -a")

然后提交删除,提交之前先将修改的内容 add 到 staging area。

1
2
3
4
5
6
7
8
9
10
11
12
➜ git add deleteme.rb
➜ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: deleteme.rb
➜ git commit -m 'delete file: deletemr.rb'
[master ceb5e66] delete file: deletemr.rb
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 deleteme.rb

Level 12, rm cached

1
2
3
4
5
Name: rm_cached
Level: 12
Difficulty: **
A file has accidentally been added to your staging area, find out which file and remove it from the staging area. *NOTE* Do not remove the file from the file system, only from git.

有个文件已经加到 staging area 中,要把它从 staging area 移除,注意不要从文件系统中删除。git rm 命令可以将文件从 git 仓库中删除,而加上 --cached 参数可以只从 staging area 中删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: deleteme.rb
➜ git rm --cached deleteme.rb
rm 'deleteme.rb'
➜ git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
deleteme.rb
nothing added to commit but untracked files present (use "git add" to track)

扩展:

1
2
--cached 参数 // 只从 staging area 中删除。
-f 参数 // 强制从 staging area 和文件系统中一起删除。

Level 13, stash

1
2
3
4
5
Name: stash
Level: 13
Difficulty: **
You've made some changes and want to work on them later. You should save them, but don't commit them.

将本地所做的修改,临时保存下来,但不是提交。这里可以利用 git 提供的 stash 功能,stash 是一个栈,可以多次保存。要恢复时可以多次 pop 将现场恢复回来。

1
2
3
➜ git stash
Saved working directory and index state WIP on master: 0206059 Add some lyrics
HEAD is now at 0206059 Add some lyrics

扩展:

1
2
git stash list // 查看 stash 栈的所有内容
git stash pop // 将最近一次入栈的内容出栈

Level 14, rename

1
2
3
4
5
Name: rename
Level: 14
Difficulty: ***
We have a file called `oldfile.txt`. We want to rename it to `newfile.txt` and stage this change.

使用 git mv 重命名文件,执行完之后修改自动处于 staging area 中。

1
2
3
➜ git mv oldfile.txt newfile.txt
➜ git status -s
R oldfile.txt -> newfile.txt

Level 15, restructure

1
2
3
4
5
Name: restructure
Level: 15
Difficulty: ***
You added some files to your repository, but now realize that your project needs to be restructured. Make a new folder named `src` and using Git move all of the .html files into this folder.

移动已加入 git 管理的文件。把所有 .html 文件移动到一个新目录 src 中。

1
2
3
4
5
6
7
8
9
10
11
➜ ls
about.html contact.html index.html
➜ mkdir src
➜ git mv *.html src/
➜ git status -s
R about.html -> src/about.html
R contact.html -> src/contact.html
R index.html -> src/index.html

Level 16, log

1
2
3
4
5
Name: log
Level: 16
Difficulty: **
You will be asked for the hash of most recent commit. You will need to investigate the logs of the repository for this.

找出最近一次提交的 hash 值。可以使用 git log 命令查看 log。

1
➜ git log

以下是输出内容:

1
2
3
4
5
commit 497191d5cae663b4f4dcd19ea8ff803c5fc49098
Author: chanthuang <chant.chenhuang@gmail.com>
Date: Sun Apr 10 15:54:12 2016 +0800
THIS IS THE COMMIT YOU ARE LOOKING FOR!

扩展:

1
-<n> // 该参数可以指定输出 log 的条目数。例如 git log -1 是只输出最近的一条 log

Level 17, tag

1
2
3
4
5
Name: tag
Level: 17
Difficulty: **
We have a git repo and we want to tag the current commit with `new_tag`.

为最后一次 commit 打上 tag,命名为 new_tag。可以使用 git tag <tag_name> 来打 tag。

1
2
3
4
➜ git tag new_tag
➜ git tag --list
new_tag

扩展:

1
2
3
git tag --list // 查看所有 tag
git tag -d <tag_name> // 删除已有的 tag
git tag <tag_name> <commit_hash> // 针对某个特定的 commit 打 tag

Level 18, push tags

1
2
3
4
5
Name: push_tags
Level: 18
Difficulty: **
There are tags in the repository that aren't pushed into remote repository. Push them now.

把本地所有 tags 都推送到远端仓库。可以使用 git push 命令来 push,--tags 命令指定要推送的是 tags。

1
2
3
4
➜ git push --tags
Total 0 (delta 0), reused 0 (delta 0)
To /var/folders/1n/jdb4hzp53dl8vcv3kkm7hp7m0000gn/T/d20160410-63290-19t6zo1/.git
* [new tag] tag_to_be_pushed -> tag_to_be_pushed

Level 19, commit amend

1
2
3
4
5
Name: commit_amend
Level: 19
Difficulty: **
The `README` file has been committed, but it looks like the file `forgotten_file.rb` was missing from the commit. Add the file and amend your previous commit to include it.

某次 commit 时漏掉了 forgotten_file.rb 文件,现在要求把这个文件加入到那次 commit 中去。可以使用 git commit--amend 参数来修改 commit 信息。提交时会进入编辑器供输入提交信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜ git st
?? forgotten_file.rb
➜ git add forgotten_file.rb
➜ git st
A forgotten_file.rb
➜ git commit --amend
[master 82010bb] Initial commit
Date: Sun Apr 10 16:17:32 2016 +0800
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README
create mode 100644 forgotten_file.rb

Level 20, commit in future

1
2
3
4
5
Name: commit_in_future
Level: 20
Difficulty: **
Commit your changes with the future date (e.g. tomorrow).

还是提交,但是要求提交时间在明天。可以使用 git commit--date=<date> 参数来指定 commit 的时间。

1
2
3
4
5
➜ git commit --date=11.04.2016T08:00:00 -m 'commit tomorrow'
[master (root-commit) 3b103a0] commit tomorrow
Date: Mon Apr 11 08:00:00 2016 +0800
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README

Level 21, reset

1
2
3
4
5
Name: reset
Level: 21
Difficulty: **
There are two files to be committed. The goal was to add each file as a separate commit, however both were added by accident. Unstage the file `to_commit_second.rb` using the reset command (don't commit anything).

有 2 个文件都被添加到了 staging area,现在把其中一个从 starging area 撤销下来,下次再提交。可以使用 git reset HEAD <file> 来执行撤销操作。

1
2
3
4
5
6
7
8
9
➜ git st
A to_commit_first.rb
A to_commit_second.rb
➜ git reset HEAD to_commit_second.rb
➜ git st
A to_commit_first.rb
?? to_commit_second.rb

Level 22, reset soft

1
2
3
4
5
Name: reset_soft
Level: 22
Difficulty: **
You committed too soon. Now you want to undo the last commit, while keeping the index.

撤销最后一次 commit,但依然保持本地的索引。可以使用 git reset 命令的 --soft 参数,这样 reset 之后上一次 commit 就被撤销了,而该 commit 所做的修改还会保留在 staging area 中。

1
2
3
4
5
6
➜ git status -s
➜ git reset --soft HEAD^1
➜ git status -s
A newfile.rb

扩展:

1
2
3
git reset --soft // 回退后将上一次 commit 的内容保留在 staging area 中
git reset --hard // 回退后不保留上一次 commit 的内容
git reset --mixed // 回退后将上一次 commit 的内容保留在 working tree 中

Level 23, checkout file

1
2
3
4
5
Name: checkout_file
Level: 23
Difficulty: ***
A file has been modified, but you don't want to keep the modification. Checkout the `config.rb` file from the last commit.

放弃本地对于某个文件的修改,可以使用 git checkout 命令。该命令会使本地所做的修改被回退。

1
2
3
4
5
6
7
➜ git status -s
M config.rb
➜ git checkout config.rb
➜ git status -s

Level 24, remote

1
2
3
4
5
Name: remote
Level: 24
Difficulty: **
This project has a remote repository. Identify it.

git 提供了 git remote 命令和相关参数来查看和操作远端仓库。现在查看该项目的远端仓库。

1
2
➜ git remote
my_remote_repo

Level 25, remote url

1
2
3
4
5
Name: remote_url
Level: 25
Difficulty: **
The remote repositories have a url associated to them. Please enter the url of remote_location.

查看远端仓库的 URL。可以使用 git remote-v 参数(--verbose 的简写) 命令来查看所有远端仓库的名字和对应的 URL。

1
2
3
4
5
➜ git remote -v
my_remote_repo https://github.com/Gazler/githug (fetch)
my_remote_repo https://github.com/Gazler/githug (push)
remote_location https://github.com/githug/not_a_repo (fetch)
remote_location https://github.com/githug/not_a_repo (push)

Level 26, pull

1
2
3
4
5
Name: pull
Level: 26
Difficulty: **
You need to pull changes from your origin repository.

要把远端仓库有而本地没有的修改拉取到本地,可以使用 git pull <remote-name> <master-name> 命令。在此之前可以先用 git remote -v 查看有哪些远端仓库。

1
2
3
4
5
6
7
8
9
10
11
➜ git remote -v
origin https://github.com/pull-this/thing-to-pull (fetch)
origin https://github.com/pull-this/thing-to-pull (push)
➜ git pull origin master
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
Unpacking objects: 100% (3/3), done.
From https://github.com/pull-this/thing-to-pull
* branch master -> FETCH_HEAD
* [new branch] master -> origin/master

Level 27, remote add

1
2
3
4
5
Name: remote_add
Level: 27
Difficulty: **
Add a remote repository called `origin` with the url https://github.com/githug/githug

添加一个远端仓库,指定名字和 url。可以使用 git remote add <remote-name> <url>

1
2
3
4
5
6
➜ git remote
➜ git remote add origin https://github.com/githug/githug
➜ git remote
origin

Level 28, push

1
2
3
4
5
Name: push
Level: 28
Difficulty: ***
Your local master branch has diverged from the remote origin/master branch. Rebase your commit onto origin/master and push it to remote.

本地和远端仓库已经分别做了一些修改,现在要将本地与远端先对齐,然后再把本地的修改 push 到远端。可以使用 git rebase 命令将远端的修改拉取到本地,然后再 git push 将本地的修改推送到远端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜ git rebase
First, rewinding head to replay your work on top of it...
Applying: First commit
Applying: Second commit
Applying: Third commit
➜ git push origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 598 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
To /var/folders/1n/jdb4hzp53dl8vcv3kkm7hp7m0000gn/T/d20160410-64180-19cgbfk/.git
c12cc72..e7d902b master -> master

Level 29, diff

1
2
3
4
5
Name: diff
Level: 29
Difficulty: **
There have been modifications to the `app.rb` file since your last commit. Find out which line has changed.

查看本地文件和 staging area 的差异,可以用 git diff 命令。

1
➜ git diff

以下是输出的内容:

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/app.rb b/app.rb
index 4f703ca..3bfa839 100644
--- a/app.rb
+++ b/app.rb
@@ -23,7 +23,7 @@ get '/yet_another' do
erb :success
end
get '/another_page' do
- @message = get_response('data.json')
+ @message = get_response('server.json')
erb :another
end

扩展:

1
2
3
git diff // 查看 working directory 与 staging area 之间的差异
git diff --cached // 查看 repository 与 staging area 之间的差异
git diff HEAD // 查看 working directory 与 repository 之间的差异

Level 30, blame

1
2
3
4
5
Name: blame
Level: 30
Difficulty: **
Someone has put a password inside the file `config.rb` find out who it was.

找出 repository 中某个文件的某一行是谁写的。可以使用 git blame 命令来查看某个文件的每一行分别对应的 commit,从而可以知道每一行最后的修改人是谁。

1
➜ git blame config.rb

以下是输出内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
^5e8863d (Gary Rennie 2012-03-08 23:05:24 +0000 1) class Config
70d00535 (Bruce Banner 2012-03-08 23:07:41 +0000 2) attr_accessor :name, :password
97bdd0cc (Spider Man 2012-03-08 23:08:15 +0000 3) def initialize(name, password = nil, options = {})
^5e8863d (Gary Rennie 2012-03-08 23:05:24 +0000 4) @name = name
97bdd0cc (Spider Man 2012-03-08 23:08:15 +0000 5) @password = password || "i<3evil"
00000000 (Not Committed Yet 2016-04-10 16:38:43 +0800 6)
09409480 (Spider Man 2012-03-08 23:06:18 +0000 7) if options[:downcase]
09409480 (Spider Man 2012-03-08 23:06:18 +0000 8) @name.downcase!
09409480 (Spider Man 2012-03-08 23:06:18 +0000 9) end
70d00535 (Bruce Banner 2012-03-08 23:07:41 +0000 10)
ffd39c2d (Gary Rennie 2012-03-08 23:08:58 +0000 11) if options[:upcase]
ffd39c2d (Gary Rennie 2012-03-08 23:08:58 +0000 12) @name.upcase!
ffd39c2d (Gary Rennie 2012-03-08 23:08:58 +0000 13) end
ffd39c2d (Gary Rennie 2012-03-08 23:08:58 +0000 14)
^5e8863d (Gary Rennie 2012-03-08 23:05:24 +0000 15) end
^5e8863d (Gary Rennie 2012-03-08 23:05:24 +0000 16) end

Level 31, branch

1
2
3
4
5
Name: branch
Level: 31
Difficulty: *
You want to work on a piece of code that has the potential to break things, create the branch test_code.

创建一个分支,可以使用 git branch <branch_name> 命令来创建。

1
2
3
4
5
➜ git branch test_code
➜ git branch
* master
test_code

扩展:

1
2
3
4
git branch // 列出所有分支
git checkout <branch_name> // 切换到某个已存在的分支
git checkout -b <branch_name> // 创建分支并切换到该分支
git branch -d <branch_name> // 删除某个分支

Level 32, checkout

1
2
3
4
5
Name: checkout
Level: 32
Difficulty: **
Create and switch to a new branch called my_branch. You will need to create a branch like you did in the previous level.

创建并切换到分支 my_branch,可以使用上面提到的命令 git checkout -b

1
2
➜ git checkout -b my_branch
Switched to a new branch 'my_branch'

Level 33, checkout tag

1
2
3
4
5
Name: checkout_tag
Level: 33
Difficulty: **
You need to fix a bug in the version 1.2 of your app. Checkout the tag `v1.2`.

切换到名字为 v1.2 的 tag。git checkout 命令除了可以切换 branch 之外,还可以切换到某个 tag。在此之前可以使用 git tag 命令查看所有 tag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜ git tag
v1.0
v1.2
v1.5
➜ git checkout v1.2
Note: checking out 'v1.2'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at 41bc247... Some more changes

扩展:

1
2
git tag // 列出所有 tag
git chekcout <tag_name> // 切换到某个tag

Level 34, checkout tag over branch

1
2
3
4
5
Name: checkout_tag_over_branch
Level: 34
Difficulty: **
You need to fix a bug in the version 1.2 of your app. Checkout the tag `v1.2` (Note: There is also a branch named `v1.2`).

与上一题一样,要求是 checkout 到名字为 v1.2 的 tag,但与此同时存在一个同名的 branch。此时可以用 ·git checkout tags/· 来明确指定要切换到的是 tag。

1
2
3
4
5
6
7
8
9
10
11
12
13
➜ git checkout tags/v1.2
Note: checking out 'tags/v1.2'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at b3342b2... Some more changes

Level 35, branch at

1
2
3
4
5
Name: branch_at
Level: 35
Difficulty: ***
You forgot to branch at the previous commit and made a commit on top of it. Create branch test_branch at the commit before the last.

针对某个特定的 commit 创建一条分支。我们知道 git branch <branch_name> 可以基于当前版本创建一条分支,而在后面跟上某个 commit 的 hash_code 即可针对某个特定的 commit 创建分支。

先查看 log 信息

1
➜ git log

以下是输出内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
commit ac1a8d622ac967ea27c6198d82db3d17c5d63165
Author: chanthuang <chant.chenhuang@gmail.com>
Date: Sun Apr 10 16:45:37 2016 +0800
Updating file1 again
commit 0931890a2e08647e889f7533e853146941d4cd4d
Author: chanthuang <chant.chenhuang@gmail.com>
Date: Sun Apr 10 16:45:37 2016 +0800
Updating file1
commit 2ba8a95e0c118ba9229a79ed52a594a687adfc14
Author: chanthuang <chant.chenhuang@gmail.com>
Date: Sun Apr 10 16:45:37 2016 +0800
Adding file1

找到需要的 hash code,创建分支

1
2
3
4
5
➜ git branch test_branch 0931890a2e08647e889f7533e853146941d4cd4d
➜ git branch
* master
test_branch

Level 36, delete branch

1
2
3
4
5
Name: delete_branch
Level: 36
Difficulty: **
You have created too many branches for your project. There is an old branch in your repo called 'delete_me', you should delete it.

删除某条分支,可以使用 git branch -d <branch_name> 命令。

1
2
3
4
5
6
➜ git branch
delete_me
* master
➜ git branch -d delete_me
Deleted branch delete_me (was b60afe2).

Level 37, push branch

1
2
3
4
5
Name: push_branch
Level: 37
Difficulty: **
You've made some changes to a local branch and want to share it, but aren't yet ready to merge it with the 'master' branch. Push only 'test_branch' to the remote repository

push 某个指定的分支到远程仓库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 先查看有哪些分支
➜ git branch
* master
other_branch
test_branch
// push test_branch 分支
➜ git push origin test_branch
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 575 bytes | 0 bytes/s, done.
Total 6 (delta 3), reused 0 (delta 0)
To /var/folders/1n/jdb4hzp53dl8vcv3kkm7hp7m0000gn/T/d20160406-41109-1bue94u/.git
* [new branch] test_branch -> test_branch

Level 38, merge

1
2
3
4
5
Name: merge
Level: 38
Difficulty: **
We have a file in the branch 'feature'; Let's merge it to the master branch.

将 feature 分支的代码合并到 master 来。可以使用 git merge

先查看有哪些分支

1
2
3
➜ git branch
feature
* master

把 feature 分支的代码合并到 master

1
2
3
4
5
6
➜ git merge feature
Updating e12277f..cc8ea5a
Fast-forward
file2 | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file2

Level 39, fetch

1
2
3
4
5
Name: fetch
Level: 39
Difficulty: **
Looks like a new branch was pushed into our remote repository. Get the changes without merging them with the local repository

获得远程仓库的修改,但不要合并到本地分支来。可以使用 git fetch 来拉取远程仓库的代码,但不合并。

1
2
3
4
5
6
7
➜ git fetch
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From /var/folders/1n/jdb4hzp53dl8vcv3kkm7hp7m0000gn/T/d20160406-41343-1b65e3j/
* [new branch] new_branch -> origin/new_branch

扩展:

1
git pull // 相当于先执行 git fetch 然后再执行 git merge。

Level 40, rebase

1
2
3
4
5
Name: rebase
Level: 40
Difficulty: **
We are using a git rebase workflow and the feature branch is ready to go into master. Let's rebase the feature branch onto our master branch.

有2条分支,feature 和 master,各自前进了一段时间。现在准备把 feature 合并到 master 来,但假如我们使用 git merge 的话,在 master 分支会看到一条 merge 的log。

现在我们不希望产生这条 merge 的 log,而希望合并后的 log 看上去就像 feature 分支从来都不存在、feature 的提交都是在 master 上进行的,可以使用 git rebase 命令。

1
2
3
➜ git rebase master feature
First, rewinding head to replay your work on top of it...
Applying: add feature

Level 41, repack

1
2
3
4
5
Name: repack
Level: 41
Difficulty: **
Optimise how your repository is packaged ensuring that redundant packs are removed.

将未打包的松散对象打包。(作用不是很懂)

1
2
3
4
➜ git repack -d
Counting objects: 3, done.
Writing objects: 100% (3/3), done.
Total 3 (delta 0), reused 0 (delta 0)

Level 42, cherry pick

1
2
3
4
5
Name: cherry-pick
Level: 42
Difficulty: ***
Your new feature isn't worth the time and you're going to delete it. But it has one commit that fills in `README` file, and you want this commit to be on the master as well.

要把 new-feature 分支上关于 README 文件的修改提取到 master 来。注意不是 merge,因为 new-feature 上还有其他的修改不想被合并到 master。可以使用 git cherry-pick

  1. 首先查看在 new-feature 分支中对 README.md 所做修改的 log
1
2
3
4
5
6
7
8
9
10
11
12
➜ git log new-feature README.md | cat
commit ca32a6dac7b6f97975edbe19a4296c2ee7682f68
Author: Andrey <aslushnikov@gmail.com>
Date: Wed Mar 28 02:25:51 2012 +0400
Filled in README.md with proper input
commit ea3dbcc5e2d2359698c3606b0ec44af9f76def54
Author: Andrey <aslushnikov@gmail.com>
Date: Wed Mar 28 02:20:32 2012 +0400
Initial commit
  1. 找到我们要提取的那个 commit,对应的 hash-code 是 ca32a6dac7b6f97975edbe19a4296c2ee7682f68

  2. 使用 git cherry-pick 命令把该 commit 应用到 master 来

1
2
3
4
5
➜ git cherry-pick ca32a6dac7b6f97975edbe19a4296c2ee7682f68
[master 6bbb198] Filled in README.md with proper input
Author: Andrey <aslushnikov@gmail.com>
Date: Wed Mar 28 02:25:51 2012 +0400
1 file changed, 1 insertion(+), 2 deletions(-)

Level 43, grep

1
2
3
4
5
Name: grep
Level: 43
Difficulty: **
Your project's deadline approaches, you should evaluate how many TODOs are left in your code

搜索项目中有多少个 TODO 字符串,可以使用 git grep <string> 命令。

1
git grep TODO

扩展:

git grep 命令和 Linux 的 grep 命令的作用有什么区别呢?其实两者都是用来搜索文件内容的,区别在于,git grep 命令会只搜索被 git 管理的那些文件,而 grep 命令会搜索本地文件系统中的文件,不管是不是被 git 管理。

Level 44, rename commit

1
2
3
4
5
Name: rename_commit
Level: 44
Difficulty: ***
Correct the typo in the message of your first (non-root) commit.

纠正 first commit 中的拼写错误。git 允许对提交历史做一些修改,比如修改 message、把某个 commit 应用到另一个分支、删除某个提交。做法是使用 git rebase -i <commit-hash> 命令,它将列出这个 commit 之后的 log,然后可以对这些 log 做一些修改。

  1. 首先用 git log 查看是哪个 commit 需要修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜ git log | cat
commit 1920ad31920c881b01e689e404c10120fbbd08ec
Author: chanthuang <chant.chenhuang@gmail.com>
Date: Thu Apr 7 10:46:02 2016 +0800
Second commit
commit 69f37250a69f11eba0939717e1cd03c3a9f02342
Author: chanthuang <chant.chenhuang@gmail.com>
Date: Thu Apr 7 10:46:02 2016 +0800
First coommit
commit a0ed52f82cc226218902ad72f766064b991a31f0
Author: chanthuang <chant.chenhuang@gmail.com>
Date: Thu Apr 7 10:46:02 2016 +0800
Initial
  1. 记录要修改的 commit 的上一个 commit 的 hash code:a0ed52f82cc226218902ad72f766064b991a31f0

  2. 使用 git rebase -i 进入修改的状态

1
➜ git rebase -i a0ed52f82cc226218902ad72f766064b991a31f0
  1. 此时会打开预设的编辑器,编辑 commit log,其中第一列表示要对每个 commit 做的修改,可能的值见下方注释。在这里我们把拼写错误的这条记录前面的命令修改为 werord(表示要应用这个 commit 并要修改 message),保存退出即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
reword 69f3725 First coommit
pick 1920ad3 Second commit
# Rebase a0ed52f..1920ad3 onto a0ed52f (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
  1. git 会再次进入编辑器,此时修改拼写错误的地方,保存并退出,git 将重新应用这个 commit。
1
2
3
4
5
6
7
8
9
10
11
12
First commit
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Thu Apr 7 11:07:50 2016 +0800
#
# rebase in progress; onto 69f3725
# You are currently editing a commit while rebasing branch 'master' on '69f3725'.
#
# Changes to be committed:
# new file: file1

Level 45, squash

1
2
3
4
5
Name: squash
Level: 45
Difficulty: ****
You have committed several times but would like all those changes to be one commit.

要求将多个 commit 合并成一个,与 Level 44 一样是修改提交历史。同样使用 git rebase -i 命令来修改历史。修改时把要合并的 commit 前面的命令修改为 squash,那么这条 commit 将会与上一条合并。

由于步骤和 Level 44 类似,这里仅列出修改 commit 前面的命令的那一步。

1
2
3
4
pick 4db8fcb Adding README
squash 73f993b Updating README (squash this commit into Adding README)
squash f03ea95 Updating README (squash this commit into Adding README)
squash 7ef90cf Updating README (squash this commit into Adding README)

Level 46, merge squash

1
2
3
4
5
Name: merge_squash
Level: 46
Difficulty: ***
Merge all commits from the long-feature-branch as a single commit.

要求将某个分支的所有修改都应用到 master。与 Merge 和 rebase 不同的是,这些修改被要求合并成一个。可以在 merge 时加上 --squash 参数。这样分支上的所有修改都会到 staging area 来,然后再 commit 一次,就能把那些修改一次性提交了。

1
2
3
4
5
6
7
8
➜ git merge --squash long-feature-branch
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested
➜ git commit -m 'merge long-feature-branch in one commit'
[master d0684b5] merge long-feature-branch in one commit
1 file changed, 3 insertions(+)
create mode 100644 file3

Level 47, reorder

1
2
3
4
5
Name: reorder
Level: 47
Difficulty: ****
You have committed several times but in the wrong order. Please reorder your commits.

修改某几个提交的顺序。提到修改历史,我们就应该想起 git rebase -i 命令。

  1. 类似于 Level 44 操作。
1
2
➜ git log
➜ git rebase -i 439d539dd9165e7becf230d3506ce08c1d3146b0
  1. 来到到修改 commit 的那一步。
1
2
3
pick 071e2e7 First commit
pick 1089baf Third commit
pick ae13c74 Second commit
  1. 把顺序修改对,即把 Second commitThird commit 整行顺序对调。然后保存退出,即可。
1
2
3
pick 071e2e7 First commit
pick ae13c74 Second commit
pick 1089baf Third commit

Level 48, bisect

1
2
3
4
5
Name: bisect
Level: 48
Difficulty: ***
A bug was introduced somewhere along the way. You know that running `ruby prog.rb 5` should output 15. You can also run `make test`. What are the first 7 chars of the hash of the commit that introduced the bug.

现在项目中有个 bug 是某一次 commit 引入的。使用 make test 可以测试项目,返回值是 0 的话表示测试通过,无 bug。要求找出引入 bug 的那个 commit。

我们的第一反应是看 commit log,将代码一个一个回退到各个版本,运行 make test 检查测试是否通过,以此来找到引入 bug 的那一次 commit。而 git 提供了 bisect 二分搜索方法来为我们做这件事。

git birect 的基本用法如下:

  1. 执行 git birect start,表示开始二分搜索;
  2. 找到任意一个有问题的 commit,标记一下这个版本。一般这种情况下当前版本都是有问题的,所以 git birect bad
  3. 找到任意一个没问题的 commit,标记一下这个版本。如 git birect good f608824888b83bbedc1f658be7496ffea467a8fb
  4. 当你至少标记了一个 good 和一个 bad 之后,就会看到类似于 Bisecting: 675 revisions left to test after this 的提示,告诉你你标记的两个版本之间有这么多个待测试的版本。
  5. 然后两者中间的那个版本会自动被 checkout,你要做的是测试一下这个版本有没有问题,并标记为 git birect good 或 bad
  6. git 会自动重复第 4 步,然后你重复第 5 步。这样一直二分搜索下来,直到找到第一个有问题的版本。

当然,这样一个一个版本手工测试还是很麻烦,如果你的项目有没有 bug 可以通过脚本自动检测,然后通过返回 0 来表示测试通过,返回其他值表示不通过,那么就可以省下手工测试的环节了。直接在第 4 步标记完之后,执行 git run <你的测试脚本>,git 将自动重复执行 第 4、5 步,直至找到第一个有问题的版本。

下面来看 githug 的这个例子:

  1. 开始二分搜索:
1
➜ git bisect start
  1. 检查当前版本,测试一下,发现不通过,标记为 bad
1
2
3
4
5
➜ make test
ruby prog.rb 5 | ruby test.rb
make: *** [test] Error 1
➜ git bisect bad
  1. 查看 log,checkout 到第一个版本,测试一下,通过,标记为 good
1
2
3
4
5
6
7
8
9
10
11
12
➜ git:(f351ca6) git log
➜ git:(f351ca6) git checkout f608824888b83bbedc1f658be7496ffea467a8fb
Previous HEAD position was f351ca6... Another Commit
HEAD is now at f608824... First commit
➜ git:(f608824) make test
ruby prog.rb 5 | ruby test.rb
➜ git:(f608824) git bisect good
Bisecting: 9 revisions left to test after this (roughly 3 steps)
[fdbfc0d403e5ac0b2659cbfa2cbb061fcca0dc2a] Another Commit
  1. 已经标记了一个 good 和一个 bad,可以执行自动二分搜索。最终输出的结果就是第一个有问题的 commit。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
➜ git:(fdbfc0d) git bisect run make test
running make test
ruby prog.rb 5 | ruby test.rb
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[18ed2ac1522a014412d4303ce7c8db39becab076] Another Commit
running make test
ruby prog.rb 5 | ruby test.rb
make: *** [test] Error 1
Bisecting: 2 revisions left to test after this (roughly 1 step)
[9f54462abbb991b167532929b34118113aa6c52e] Another Commit
running make test
ruby prog.rb 5 | ruby test.rb
Bisecting: 0 revisions left to test after this (roughly 1 step)
[5db7a7cb90e745e2c9dbdd84810ccc7d91d92e72] Another Commit
running make test
ruby prog.rb 5 | ruby test.rb
18ed2ac1522a014412d4303ce7c8db39becab076 is the first bad commit
commit 18ed2ac1522a014412d4303ce7c8db39becab076
Author: Robert Bittle <guywithnose@gmail.com>
Date: Mon Apr 23 06:52:10 2012 -0400
Another Commit
:100644 100644 917e70054c8f4a4a79a8e805c0e1601b455ad236 7562257b8e6446686ffc43a2386c50c254365020 M prog.rb
bisect run success

Level 49, stage lines

1
2
3
4
5
Name: stage_lines
Level: 49
Difficulty: ****
You've made changes within a single file that belong to two different features, but neither of the changes are yet staged. Stage only the changes belonging to the first feature.

工作区在同一个文件中做了一些修改,现在想要其中的某些修改被 add 到 staging area,剩余的修改保留在工作区中。

可以使用 git add-p 参数,选择哪些修改是要 add 的,哪些修改是先不 add 的。执行该命令会输出类似于 git diff 的结果,然后可以选择 e 进行编辑。

1
2
3
4
5
6
7
8
9
10
➜ git add -p feature.rb
diff --git a/feature.rb b/feature.rb
index 1a271e9..4a80dda 100644
--- a/feature.rb
+++ b/feature.rb
@@ -1 +1,3 @@
this is the class of my feature
+This change belongs to the first feature
+This change belongs to the second feature
Stage this hunk [y,n,q,a,d,/,e,?]? e

选择 e 后会进入编辑器,此时只要将不想被 add 的 + 号行删除,保存并退出,就不会被 add 到 staging area。

1
2
3
4
5
6
7
8
9
10
11
12
13
# Manual hunk edit mode -- see bottom for a quick guide
@@ -1 +1,3 @@
this is the class of my feature
+This change belongs to the first feature
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
#
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging. If it does not apply cleanly, you will be given
# an opportunity to edit again. If all lines of the hunk are removed,
# then the edit is aborted and the hunk is left unchanged.

Level 50, find old branch

1
2
3
4
5
Name: find_old_branch
Level: 50
Difficulty: ****
You have been working on a branch but got distracted by a major issue and forgot the name of it. Switch back to that branch.

你在当前分支 kill_the_batman 工作完了之后,忘记了之前是从哪里来的了。git 提供了 git reflog 命令来管理所有操作记录。

1
2
3
4
5
6
7
8
➜ git reflog | cat
894a16d HEAD@{0}: commit: commit another todo
6876e5b HEAD@{1}: checkout: moving from solve_world_hunger to kill_the_batman
324336a HEAD@{2}: commit: commit todo
6876e5b HEAD@{3}: checkout: moving from blowup_sun_for_ransom to solve_world_hunger
6876e5b HEAD@{4}: checkout: moving from kill_the_batman to blowup_sun_for_ransom
6876e5b HEAD@{5}: checkout: moving from cure_common_cold to kill_the_batman
6876e5b HEAD@{6}: commit (initial): initial commit

可以看到 moving from solve_world_hunger to kill_the_batman,所以知道之前是从 solve_world_hunger 来到 kill_the_batman 的。按题目要求,checkout 回去 solve_world_hunger 即可。

Level 51, revert

1
2
3
4
5
6
Name: revert
Level: 51
Difficulty: ****
You have committed several times but want to undo the middle commit.
All commits have been pushed, so you can't change existing history.

回退之前的某一个 commit。注意这里已经 push 过,所以不应该使用 git rebase -i 命令去修改历史,以免版本库混乱。而可以使用 git revert 来回退某个提交。当然,这将产生一个新的 commit,在编辑器中写上 message,保存并退出即可。

1
2
3
4
5
6
7
8
9
10
➜ git log --oneline | cat
dfdace3 Revert "Bad commit"
884980a Second commit
ddd7b37 Bad commit
9512eea First commit
➜ git revert ddd7b37
[master dfdace3] Revert "Bad commit"
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file3

Level 52, restore

1
2
3
4
5
Name: restore
Level: 52
Difficulty: ****
You decided to delete your latest commit by running `git reset --hard HEAD^`. (Not a smart thing to do.) You then change your mind, and want that commit back. Restore the deleted commit.

你之前一冲动,git reset --hard HEAD^ 使版本库丢掉了一个 commit,回到了倒数第二个 commit 的状态。现在想找回来。

此时 git log 已看不到最后一次 commit 了,但 git reflog 还能看到,因为它保存着你所做的修改。

1
2
3
4
5
➜ git reflog | cat
550223b HEAD@{0}: reset: moving to HEAD^
b97ae0a HEAD@{1}: commit: Restore this commit
550223b HEAD@{2}: commit: First commit
3b0fd52 HEAD@{3}: commit (initial): Initial commit

看到 40a0079 就是你要找回的那次 commit。接下来 git checkout 它即可。

1
➜ git checkout b97ae0a

Level 53, conflict

1
2
3
4
5
Name: conflict
Level: 53
Difficulty: ****
You need to merge mybranch into the current branch (master). But there may be some incorrect changes in mybranch which may cause conflicts. Solve any merge-conflicts you come across and finish the merge.

mybranch 分支 mergemaster,期间会有冲突,用编辑器编辑冲突的文件,去掉带 <<<===>>> 的行,并编辑好它们之间的行,即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
➜ git merge master mybranch
Auto-merging poem.txt
CONFLICT (content): Merge conflict in poem.txt
Automatic merge failed; fix conflicts and then commit the result.
➜ cat poem.txt
Humpty dumpty
<<<<<<< HEAD
Categorized shoes by color
=======
Sat on a wall
>>>>>>> mybranch
Humpty dumpty
Had a great fall
➜ vi poem.txt
➜ cat poem.txt
Humpty dumpty
Sat on a wall
Humpty dumpty
Had a great fall
➜ git status -s
UU poem.txt
➜ git add poem.txt
➜ git commit -m 'Merge mybranch'
[master a08e925] Merge mybranch

Level 54, submodule

1
2
3
4
5
Name: submodule
Level: 54
Difficulty: **
You want to include the files from the following repo: `https://github.com/jackmaney/githug-include-me` into a the folder `./githug-include-me`. Do this without cloning the repo or copying the files from the repo into this repo.

要求将一个仓库作为一个 submodule 引入到当前项目中。

1
2
3
4
5
6
➜ git submodule add https://github.com/jackmaney/githug-include-me githug-include-me
Cloning into 'githug-include-me'...
remote: Counting objects: 9, done.
remote: Total 9 (delta 0), reused 0 (delta 0), pack-reused 9
Unpacking objects: 100% (9/9), done.
Checking connectivity... done.

Level 55, 结束

1
2
3
4
5
6
7
Congratulations, you have solved the level!
Name: contribute
Level: 55
Difficulty: ***
This is the final level, the goal is to contribute to this repository by making a pull request on GitHub. Please note that this level is designed to encourage you to add a valid contribution to Githug, not testing your ability to create a pull request. Contributions that are likely to be accepted are levels, bug fixes and improved documentation.

到这里所有关卡就结束了,这一关是鼓励大家为 Githug 项目做贡献。

结束

Git 的管理相当复杂,又十分强大,这里列出的很多命令都还没有详细去了解,以上这些题只是能列出常见的场景和相应的操作方法,至于要了解各个命令的详细用法,还是得系统地学习才行。