0%

Mosh的课程网址

使用VSCode:用ctrl+`调出terminal

1
print("*" * 10) # print ********** (10 times) on the terminal

Python Extension

在VSCode下载python扩展

👉 Linting: finding potential errors in our code

👉 Debugging

👉 Autocompletion: help us write code faster

👉 Code Formatting

👉 Unit Testing

👉 Code Snippets: reusable code blocks

Linting

下载pylint,显示错误提示

ctrl+shift+M:调出problem显示框,可以显示所有错误处

ctrl+shift+P:搜索

Formatting Code

Python Enhancement Proposals (PEPs)

PEP 8

1
2
3
#Example
x=1 #ugly
x = 1 #PEP 8 suggests.

ctrl+shift+P 搜索format,选择 format document,提示下载,就下载

然后在File > Preference > Setting 搜索 formatOnSave, 勾选。

001

这样在save python文件的时候,就自动reformat了。

Running Python Code

下载code runner extension, ctrl+alt+n 直接运行

Python Implementation

Python指一种编程语言,但是python的实现是一个program

1
2
3
4
CPython 		C 实现
Jython Java 实现
IronPython C# 实现
PyPy Subset of Python

比如你想在 python 中加一些 Java 代码,最好用 Jython 而不是 CPython

How Python Code Is Executed

C → C Compiler → Machine Code → Processor

Machine Code 对 Processor 特定,windows 机器无法识别 Mac的 machine code.

Java 则可以在不同的 platform 运行相同的code:

Java → Compiler → Java Bytecode → Java Virtual Machine → Machine Code

比如 Windows JVM → Windows Machine Code

Python 采用了相同的方法:

Python→ CPython → Python Bytecode → Python Virtual Machine → Machine Code

1
2
3
4
5
		CPython → Python Bytecode
/
Python
\
Jython → Java Bytecode

所以用 Jython 可以在 python code 中植入一些 Java code.

Quiz

👉 What is an expression?

👉 What is an syntax error?

👉 What does a linter do?

Mosh的课程网址

Why Rewrite History?

1
2
3
4
------ Bad History ------
👉 Poor commit messages
👉 Large commits
👉 Small commits

我们想要 clean history, 知道我们改了啥

1
2
3
4
5
6
------------- Tools -------------
👉 Squash small, related commits
👉 Split large commits
👉 Reword commit messages
👉 Drop unwanted commits
👉 Modify commits

The Golden Rule of Rewriting History

1
DON'T REWRITE PUBLIC HISTORY!

Example of a Bad History

1
2
3
4
5
6
7
8
9
10
(master)$ git log --oneline --all --graph
* 475ea30 (HEAD -> master) .
* 688a4d0 WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit
1
2
3
4
5
6
7
8
17de14d 应该是Render restaurants on the map.(少了一个on)
127cf78 Fix a typo是一个noise of history,应该合到17de14d去
24f3009 也应该合到17de14d去,形成一个单一逻辑的commit
175d394 应该在17de14d之前commit,没有reference怎么使用这个map?
db94b15 noise of history,扔掉或者更改提交信息
b0201fc 应该分成两个commit
688a4d0 同db94b15
475ea30 不知道干了啥,扔掉吧

Undoing Commits

soft:相当于回到git commit之前的状态→Removes the commit only

6_1

mixed:相当于回到git add之前的状态,即还没放进staging area→Unstages files

6_2

hard:最初的起点,啥都还没干→Discards local changes

6_3

1
2
3
4
5
6
7
8
9
10
11
12
(master)$ git reset --hard HEAD~1
HEAD is now at 688a4d0 WIP

(master)$ git log --oneline --all --graph
* 688a4d0 (HEAD -> master) WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

好的,最近的commit就扔掉了,仿佛没有存在过。

Reverting Commits

如果commit已经share with others,就不能直接reset了,而是用revert,保留log中的记录。

将最近的三条commit撤销(反做),其中 HEAD~3 指的是175d394,是不含在内的(是不会被反做的):

1
2
3
4
5
6
7
(master)$ git revert HEAD~3..HEAD
[master 12ad6f2] Revert "WIP"
1 file changed, 1 deletion(-)
[master 83527ab] Revert "Update terms of service and Google Map SDK version."
1 file changed, 1 deletion(-)
[master 667bde8] Revert "WIP"
1 file changed, 1 deletion(-)

但是这就有三条commit history了,一点都不clean:

1
2
3
4
5
6
7
8
9
10
11
12
(master)$ git log --oneline --all --graph
* 667bde8 (HEAD -> master) Revert "WIP"
* 83527ab Revert "Update terms of service and Google Map SDK version."
* 12ad6f2 Revert "WIP"
* 688a4d0 WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

所以可以不commit,反做的记录都在staging area里:

1
2
3
4
(master)$ git revert --no-commit HEAD~3..

(master|REVERTING)$ git status -s
M file1.txt

用—continue来继续,并手动将commit message改为”Revert bad code.“:

1
2
3
(master|REVERTING)$ git revert --continue
[master 5322dae] Revert bad code.
1 file changed, 3 deletions(-)

这样就只有一条history啦:

1
2
3
4
5
6
7
8
9
10
(master)$ git log --oneline --all --graph
* 5322dae (HEAD -> master) Revert bad code.
* 688a4d0 WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

Recovering Lost Commits

不小心reset了HEAD:

1
2
(master)$ git reset --hard HEAD~6
HEAD is now at 127cf78 Fix a typo

log都没了咋办:

1
2
3
4
(master)$ git log --oneline --all --graph
* 127cf78 (HEAD -> master) Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

其实人家Git给你存得好好的呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(master)$ git reflog
127cf78 (HEAD -> master) HEAD@{0}: reset: moving to HEAD~6
5322dae HEAD@{1}: commit: Revert bad code.
688a4d0 HEAD@{2}: reset: moving to HEAD~3
667bde8 HEAD@{3}: revert: Revert "WIP"
83527ab HEAD@{4}: revert: Revert "Update terms of service and Google Map SDK version."
12ad6f2 HEAD@{5}: revert: Revert "WIP"
688a4d0 HEAD@{6}: reset: moving to 688a4d0
fd52d6e HEAD@{7}: revert: Revert "Update terms of service and Google Map SDK version."
f2139a9 HEAD@{8}: revert: Revert "WIP"
688a4d0 HEAD@{9}: reset: moving to HEAD~1
475ea30 HEAD@{10}: commit: .
688a4d0 HEAD@{11}: commit: WIP
b0201fc HEAD@{12}: commit: Update terms of service and Google Map SDK version.
db94b15 HEAD@{13}: commit: WIP
175d394 HEAD@{14}: commit: Add a reference to Google Map SDK.
24f3009 HEAD@{15}: commit: Change the color of restaurant icons.
127cf78 (HEAD -> master) HEAD@{16}: commit: Fix a typo
17de14d HEAD@{17}: commit: Render restaurants the map.
00315a1 HEAD@{18}: commit (initial): Initial commit

用 commit ID 或者 entry ID 都可以恢复(此处用entry ID):

1
2
3
4
5
6
7
8
9
10
11
12
13
(master)$ git reset --hard HEAD@{1}
HEAD is now at 5322dae Revert bad code.

(master)$ git log --oneline --all --graph
* 5322dae (HEAD -> master) Revert bad code.
* 688a4d0 WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

假如有个Pointer是feature:

1
$ git reflog show feature

可以看到这个pointer的所有历史。

Amending Commit

Amending the Last Commit

假如现在有个 commit:

1
2
3
4
5
6
7
(master)$ echo cafes >> file1.txt

(master)$ git commit -am "Render cafes on the map"
warning: LF will be replaced by CRLF in file1.txt.
The file will have its original line endings in your working directory
[master b4c943f] Render cafes on the map
1 file changed, 1 insertion(+)

交了之后发现我们不是想写 cafes,而是想写 blue cafes 啊,但是这是个小错,能不能小修补?当然可以。

先打开 file1.txt 编辑,然后存入 staging area,最后使用 —amend 选项 commit,commit message 不变:

1
2
3
4
5
6
7
8
(master)$ code file1.txt

(master)$ git add .

(master)$ git commit --amend
[master a60e09a] Render cafes on the map
Date: Fri Apr 30 14:38:19 2021 +0800
1 file changed, 1 insertion(+)

可以看到还是这条commit,但是file1.txt里的内容已经改过来了:

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
(master)$ git log --oneline --all --graph
* a60e09a (HEAD -> master) Render cafes on the map
* 5322dae Revert bad code.
* 688a4d0 WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

(master)$ git show HEAD
commit a60e09a0bff95afbb3c83ca20591cec3c9afe638 (HEAD -> master)
Author: Stone <masaike@qq.com>
Date: Fri Apr 30 14:38:19 2021 +0800

Render cafes on the map

diff --git a/file1.txt b/file1.txt
index cb224a0..c44f4be 100644
--- a/file1.txt
+++ b/file1.txt
@@ -3,3 +3,4 @@ restaurants
fix a typo
color-red
ref-of-sdk
+blue cafes //-------看这里-----------

假如是想增加一个新文件,还是一样的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(master)$ echo hello > file2.txt

(master)$ git add .
warning: LF will be replaced by CRLF in file2.txt.
The file will have its original line endings in your working directory

(master)$ git commit --amend
[master 5bbe8a7] Render cafes on the map
Date: Fri Apr 30 14:38:19 2021 +0800
2 files changed, 2 insertions(+)
create mode 100644 file2.txt

(master)$ git log --oneline --all --graph
* 5bbe8a7 (HEAD -> master) Render cafes on the map
* 5322dae Revert bad code.
* 688a4d0 WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

看到在HEAD指向的commit里,不仅有file1.txt的内容修改,还能看到新增了一个file2.txt的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(master)$ git show HEAD
commit 5bbe8a7f4cabf9011ff28c32e97e0c4a79f10f8f (HEAD -> master)
Author: Stone <masaike@qq.com>
Date: Fri Apr 30 14:38:19 2021 +0800

Render cafes on the map

diff --git a/file1.txt b/file1.txt
index cb224a0..c44f4be 100644
--- a/file1.txt
+++ b/file1.txt
@@ -3,3 +3,4 @@ restaurants
fix a typo
color-red
ref-of-sdk
+blue cafes
diff --git a/file2.txt b/file2.txt
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file2.txt
@@ -0,0 +1 @@
+hello

如果这个file2.txt是不小心加上去的,其实不要,就先回到前一个状态,此时修改在本地目录,但不在staging area(用git status查看是红色的):

1
2
3
4
5
6
7
(master)$ git reset --mixed HEAD~1
Unstaged changes after reset:
M file1.txt

(master)$ git status -s
M file1.txt
?? file2.txt

删去untracked file,再 add 到 staging area,最后commit就行了。此时commit的就只有file1.txt的内容修改,而没有新增file2.txt:

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
31
32
33
34
35
36
37
(master)$ git clean -fd
Removing file2.txt

(master)$ git add .

(master)$ git commit -m "Render cafes on the map."
[master e47642a] Render cafes on the map.
1 file changed, 1 insertion(+)

(master)$ git log --oneline --all --graph
* e47642a (HEAD -> master) Render cafes on the map.
* 5322dae Revert bad code.
* 688a4d0 WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

(master)$ git show HEAD
commit e47642a4cd59cfec5697d82acbbfa7372547823e (HEAD -> master)
Author: Stone <masaike@qq.com>
Date: Fri Apr 30 14:42:11 2021 +0800

Render cafes on the map.

diff --git a/file1.txt b/file1.txt
index cb224a0..c44f4be 100644
--- a/file1.txt
+++ b/file1.txt
@@ -3,3 +3,4 @@ restaurants
fix a typo
color-red
ref-of-sdk
+blue cafes

Amending an Ealier Commit

需要rebase到想要修改的commit。


REBASING REWRITES HISTORY.

原始的history:

6_5

假如现在想修改commit B,就变成B*了。而history是不可变的,commit C不能以commit B*为parent commit,只能创建一个与C一摸一样的commit C*,然后以commit B*为parent commit,commit D同理。所以,虽然只有B要改,但实际上C和D都要重新创建。

6_6


我们现在想修改commit ID为 175d394 的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
26
27
28
(master)$ git log --oneline --all --graph
* e47642a (HEAD -> master) Render cafes on the map.
* 5322dae Revert bad code.
* 688a4d0 WIP
* b0201fc Update terms of service and Google Map SDK version.
* db94b15 WIP
* 175d394 Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

(master)$ git show 175d394
commit 175d3944118590daf26ba25c46da4061c0ad641b
Author: Stone <1140525895@qq.com>
Date: Fri Apr 30 10:01:56 2021 +0800

Add a reference to Google Map SDK.

diff --git a/file1.txt b/file1.txt
index 1cd7c63..cb224a0 100644
--- a/file1.txt
+++ b/file1.txt
@@ -2,3 +2,4 @@ hello
restaurants
fix a typo
color-red
+ref-of-sdk //----------看这里--------------

首先要rebase到它的上一个commit,即ID为24f3009的commit,在弹出的窗口改pick为edit:

6_4

1
2
3
4
5
6
7
8
9
(master)$ git rebase -i 24f3009
Stopped at 175d394... Add a reference to Google Map SDK.
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue

所以会需要你edit完这个commit用—amend修正了再continue。

修改内容是增加一个文件license.txt:

1
2
3
4
5
6
7
8
9
(master|REBASE 1/6)$ echo license > license.txt

(master|REBASE 1/6)$ git add .

(master|REBASE 1/6)$ git commit --amend
[detached HEAD 0eb205c] Add a reference to Google Map SDK.
Date: Fri Apr 30 10:01:56 2021 +0800
2 files changed, 2 insertions(+)
create mode 100644 license.txt

可以看到分了支了:

1
2
3
4
5
6
7
8
9
10
11
12
13
(master|REBASE 1/6)$ git log --oneline --all --graph
* 0eb205c (HEAD) Add a reference to Google Map SDK.
| * e47642a (master) Render cafes on the map.
| * 5322dae Revert bad code.
| * 688a4d0 WIP
| * b0201fc Update terms of service and Google Map SDK version.
| * db94b15 WIP
| * 175d394 Add a reference to Google Map SDK.
|/
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

继续rebase,把后面的commit调整完成(简单的pick),可以看到从修改的那条commit开始,ID全变了,尽管操作为pick的commit内容未变(其实是重新创建了这些commit):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(master|REBASE 1/6)$ git rebase --continue
Successfully rebased and updated refs/heads/master.

(master)$ git log --oneline --all --graph
* 0d35c4a (HEAD -> master) Render cafes on the map.
* 6f2b578 Revert bad code.
* fce1354 WIP
* a3f2e62 Update terms of service and Google Map SDK version.
* e67e73d WIP
* 0eb205c Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

检查一下,确实修改成功了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(master)$ git show 0eb205c
commit 0eb205cde3b4c6d852681d59d2821914f3a37a69
Author: Stone <1140525895@qq.com>
Date: Fri Apr 30 10:01:56 2021 +0800

Add a reference to Google Map SDK.

diff --git a/file1.txt b/file1.txt
index 1cd7c63..cb224a0 100644
--- a/file1.txt
+++ b/file1.txt
@@ -2,3 +2,4 @@ hello
restaurants
fix a typo
color-red
+ref-of-sdk
diff --git a/license.txt b/license.txt //----新增license.txt---
new file mode 100644
index 0000000..8da8489
--- /dev/null
+++ b/license.txt
@@ -0,0 +1 @@
+license

Dropping a Commit

比如我们想删掉 6f2b578,fce1354 和 e67e73d 的commit:

1
2
3
4
5
6
7
8
9
10
11
(master)$ git log --oneline --all --graph
* 0d35c4a (HEAD -> master) Render cafes on the map.
* 6f2b578 Revert bad code. //-------------
* fce1354 WIP //-------------
* a3f2e62 Update terms of service and Google Map SDK version.
* e67e73d WIP //-------------
* 0eb205c Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

rebase到0eb205c,在弹出的窗口直接删掉第1,3,4行就行了:

6_7

1
2
3
4
5
6
7
8
9
(master)$ git rebase -i 0eb205c
error: could not apply a3f2e62... Update terms of service and Google Map SDK version.
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply a3f2e62... Update terms of service and Google Map SDK version.
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt

有冲突就解决冲突:

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
(master|REBASE 1/2)$ git mergetool
Merging:
file1.txt

Normal merge conflict for 'file1.txt':
{local}: modified file
{remote}: modified file

(master|REBASE 1/2)$ git rebase --continue
[detached HEAD b153f53] Update terms of service and Google Map SDK version.
1 file changed, 1 insertion(+)
error: could not apply 0d35c4a... Render cafes on the map.
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 0d35c4a... Render cafes on the map.
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt

(master|REBASE 2/2)$ git mergetool
Merging:
file1.txt

Normal merge conflict for 'file1.txt':
{local}: modified file
{remote}: modified file

好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
(master|REBASE 2/2)$ git rebase --continue
[detached HEAD 3b55d73] Render cafes on the map.
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

(master)$ git log --oneline --all --graph
* 3b55d73 (HEAD -> master) Render cafes on the map.
* b153f53 Update terms of service and Google Map SDK version.
* 0eb205c Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

Rewording Commit Messages

比如要修改17de14d和b153f53的commit message:

1
2
3
4
5
6
7
8
(master)$ git log --oneline --all --graph
* 3b55d73 (HEAD -> master) Render cafes on the map.
* b153f53 Update terms of service and Google Map SDK version.
* 0eb205c Add a reference to Google Map SDK.
* 24f3009 Change the color of restaurant icons.
* 127cf78 Fix a typo
* 17de14d Render restaurants the map.
* 00315a1 Initial commit

首先rebase到17de14d的parent commit(用^表示),在弹出的窗口将对应的这两条的command由pick改为reword(此处不展示),关闭此窗口。然后又有两次弹窗,直接修改message即可。

1
2
3
4
5
6
7
8
9
(master)$ git rebase -i 17de14d^
[detached HEAD 38b49e0] Render restaurants on the map.
Date: Fri Apr 30 09:59:03 2021 +0800
1 file changed, 1 insertion(+)
[detached HEAD dd32e6e] Add a reference to Google Map SDK v1.0.0.
Date: Fri Apr 30 10:01:56 2021 +0800
2 files changed, 2 insertions(+)
create mode 100644 license.txt
Successfully rebased and updated refs/heads/master.

看看,改好了:

1
2
3
4
5
6
7
8
(master)$ git log --oneline --all --graph
* 526429c (HEAD -> master) Render cafes on the map.
* e3efdfd Update terms of service and Google Map SDK version.
* dd32e6e Add a reference to Google Map SDK v1.0.0. //-----v1.0.0------
* 1ca0331 Change the color of restaurant icons.
* cacde15 Fix a typo
* 38b49e0 Render restaurants on the map. //------on--------
* 00315a1 Initial commit

依然,00315a1之后的全部commit,ID都变了。

Re-ordering Commits

1
2
3
4
5
6
7
8
(master)$ git log --oneline --all --graph
* 2879b9f (HEAD -> master) Render cafes on the map.
* 95cd9e7 Update terms of service and Google Map SDK version.
* 398859b Add a reference to Google Map SDK v1.0.0.
* 1ca0331 Change the color of restaurant icons.
* cacde15 Fix a typo
* 38b49e0 Render restaurants on the map.
* 00315a1 Initial commit

要将398859b调到38b49e0前面去:

git rebase后弹出的窗口将398859b对应行移到最上边(选中该行,Atl+↑ 调整):

6_8

1
2
3
4
5
6
7
8
9
10
11
(master)$ git rebase -i 00315a1
Successfully rebased and updated refs/heads/master.

(master)$ git log --oneline --all --graph
* 3a8e00e (HEAD -> master) Render cafes on the map.
* 7a97000 Update terms of service and Google Map SDK version.
* 031bd2e Change the color of restaurant icons.
* f8bce71 Fix a typo
* 8eb9241 Render restaurants on the map. //---------------
* 2e926d2 Add a reference to Google Map SDK v1.0.0. //---------------
* 00315a1 Initial commit

Squashing Commits

1
2
3
4
5
6
7
8
(master)$ git log --oneline --all --graph
* 3a8e00e (HEAD -> master) Render cafes on the map.
* 7a97000 Update terms of service and Google Map SDK version.
* 031bd2e Change the color of restaurant icons.
* f8bce71 Fix a typo
* 8eb9241 Render restaurants on the map.
* 2e926d2 Add a reference to Google Map SDK v1.0.0.
* 00315a1 Initial commit

希望将8eb9241,f8bce71,031bd2e合并成一条commit。

Method 1: squash

git rebase之后弹出的窗口改pick为squash表示该条commit与前一条commit合并:

6_9

关闭该窗口,之后弹出的窗口,删除f8bce71,031bd2e的commit message,只留下8eb9241的commit message。

1
2
3
4
5
(master)$ git rebase -i 2e926d2
[detached HEAD dffab6e] Render restaurants on the map.
Date: Fri Apr 30 09:59:03 2021 +0800
1 file changed, 3 insertions(+)
Successfully rebased and updated refs/heads/master.
1
2
3
4
5
6
(master)$ git log --oneline --all --graph
* 42732b9 (HEAD -> master) Render cafes on the map.
* 56eb52d Update terms of service and Google Map SDK version.
* dffab6e Render restaurants on the map.
* 2e926d2 Add a reference to Google Map SDK v1.0.0.
* 00315a1 Initial commit

Nice.

Method 2: fixup

git rebase -i 弹出窗口,用fixup,就不需要手动删改commit message(Git不会pick用了fixup的那一条的message)。而是直接就用8eb9241的commit message:

6_10

log同上。

Splitting a Commit

将549d310拆成两个:

1
2
3
4
5
6
(master)$ git log --oneline --all --graph
* de27a35 (HEAD -> master) Render cafes on the map.
* 549d310 Update terms of service and Google Map SDK version.
* 8014c85 Render restaurants on the map.
* 2e926d2 Add a reference to Google Map SDK v1.0.0.
* 00315a1 Initial commit

pick改为edit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(master)$ git rebase -i 549d310^
Stopped at 549d310... Update terms of service and Google Map SDK version.
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue

(master|REBASE 1/2)$ git log --oneline --all --graph
* de27a35 (master) Render cafes on the map.
* 549d310 (HEAD) Update terms of service and Google Map SDK version.
* 8014c85 Render restaurants on the map.
* 2e926d2 Add a reference to Google Map SDK v1.0.0.
* 00315a1 Initial commit

(master|REBASE 1/2)$ git reset HEAD^ //---default:mixed-------

(master|REBASE 1/2)$ git status -s //------都是红色的状态-------
M file1.txt
?? sdkversion.txt

分两次提交两个不同的修改:

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
(master|REBASE 1/2)$ git add file1.txt

(master|REBASE 1/2)$ git commit -m "Add terms of service."
[detached HEAD 1d17606] Add terms of service.
1 file changed, 4 insertions(+)

(master|REBASE 1/2)$ git add .

(master|REBASE 1/2)$ git commit -m "Update Google Map SDK version."
[detached HEAD 55d127c] Update Google Map SDK version.
1 file changed, 1 insertion(+)
create mode 100644 sdkversion.txt

(master|REBASE 1/2)$ git log --oneline --all --graph
* ed8737e (HEAD) Update Google Map SDK version.
* 44220a0 Add terms of service.
| * de27a35 (master) Render cafes on the map.
| * 549d310 Update terms of service and Google Map SDK version.
|/
* 8014c85 Render restaurants on the map.
* 2e926d2 Add a reference to Google Map SDK v1.0.0.
* 00315a1 Initial commit

(master|REBASE 1/2)$ git rebase --continue
Successfully rebased and updated refs/heads/master.

整挺好:

1
2
3
4
5
6
7
(master)$ git log --oneline --all --graph
* a6ea6fd (HEAD -> master) Render cafes on the map.
* ed8737e Update Google Map SDK version. //---------------
* 44220a0 Add terms of service. //---------------
* 8014c85 Render restaurants on the map.
* 2e926d2 Add a reference to Google Map SDK v1.0.0.
* 00315a1 Initial commit

Mosh的课程网址

Workflows

👉 Centralized: a single repository
👉 Distributed:every developer has a repository √

Centralized Workflow

平时与中央服务器交互,万一中央服务器瘫痪,各repository之间可以交互。

5_01

5_02

中央服务器放在哪?

👉 private server

👉 cloud, 如 GitHub,GitLab

具体场景:

  1. John 和 Amy 都将 repository 拷贝到本地

5_03

  1. John commit了一些内容,想 share 给 Amy,就从本地 repository 拷贝到中央 repository

5_04

  1. Amy 将这些 commit 从中央 repository pull 到本地

5_05

  1. 如果和 Amy 自己的 commit 有 conflict,Amy 解决之后 push 到中央 repository

5_06

Integration-Manager

多用于Open-source project。通常有 maintainer 管理 project repository,其他的 contributors 不能直接 commit 到 project repository。

  1. Fork project repository 到自己的 repository,然后拷贝到本地

5_07

  1. Contributor 将自己的 commit push 到 repository

5_08

  1. Contributor 向 maintainer 发出 pull request,希望在 project repository commit 自己的贡献

5_09

  1. Maintainer 同意的话就从 contributor 的 repository pull 到自己的本地目录

5_10

  1. Maintainer review 过这些 commit 觉得没问题,就可以 push 到 project repository 了。

5_11

Cloning a Repository

拷贝远程仓库到本地,仓库目前只有一个commit:

1
2
3
4
5
6
7
8
9
10
11
$ git clone https://github.com/PurpleMStone/Mars.git MarsProject
Cloning into 'MarsProject'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

$ cd MarsProject/

(master)$ git log --oneline --all --graph
* 7216ded (HEAD -> master, origin/master, origin/HEAD) Initial commit

origin 指代的是远程仓库:

1
2
3
(master)$ git remote -v
origin https://github.com/PurpleMStone/Mars.git (fetch)
origin https://github.com/PurpleMStone/Mars.git (push)

Fetching

5_12

1
2
(master)$ git log --oneline --all --graph
* 7216ded (HEAD -> master, origin/master, origin/HEAD) Initial commit

在 remote repository 进行修改(update README.md,增加了第二行):

1
2
# Mars
A new line of code

用 git fetch 获取到本地,此时 origin/master 前进了,但是本地 master 还没动:

5_13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(master)$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 645 bytes | 1024 bytes/s, done.
From https://github.com/PurpleMStone/Mars
7216ded..e27337b master -> origin/master

(master)$ git log --oneline --all --graph
* e27337b (origin/master, origin/HEAD) Update README.md
* 7216ded (HEAD -> master) Initial commit

(master)$ git branch -vv
* master 7216ded [origin/master: behind 1] Initial commit

由于没有diverse branch, 直接让本地 master fast-forward merge origin/master 就行了:

5_14

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(master)$ git merge origin/master
Updating 7216ded..e27337b
Fast-forward
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

(master)$ git log --oneline --all --graph
* e27337b (HEAD -> master, origin/master, origin/HEAD) Update README.md
* 7216ded Initial commit

(master)$ git branch -vv
* master e27337b [origin/master] Update README.md

(master)$ cat README.md
# Mars
A new line of code

而如果有 diverse branch,可能 merge 的时候会有 conflict,解决了再 merge 就行了。

Pulling

1
pull = fetch + merge

比如在本地有个commit B, 在 remote repository 有个 commit C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(master)$ echo hello > file1.txt

(master)$ git add .
warning: LF will be replaced by CRLF in file1.txt.
The file will have its original line endings in your working directory

(master)$ git commit -m "Add file1.txt"
[master ce83c2f] Add file1.txt
1 file changed, 1 insertion(+)
create mode 100644 file1.txt

(master)$ git log --oneline --all --graph
* ce83c2f (HEAD -> master) Add file1.txt
* e27337b (origin/master, origin/HEAD) Update README.md
* 7216ded Initial commit

在 remote repository 将 README.md update 了两次(commit C )。

现在可以用 git pull 获取 commit C 并进行 3-way merge:

5_15

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(master)$ git pull
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), 1.28 KiB | 1024 bytes/s, done.
From https://github.com/PurpleMStone/Mars
e27337b..4085604 master -> origin/master
Merge made by the 'recursive' strategy.
README.md | 2 ++
1 file changed, 2 insertions(+)

(master)$ git log --oneline --all --graph
* b6e7d23 (HEAD -> master) Merge branch 'master' of https://github.com/PurpleMStone/Mars
|\
| * 4085604 (origin/master, origin/HEAD) Update README.md
| * 3e40ffc Update README.md
* | ce83c2f Add file1.txt
|/
* e27337b Update README.md
* 7216ded Initial commit

也可以用 git pull —rebase 将 B rebase,然后就是 linear history 了:

5_18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(master)$ git log --oneline --all --graph
* ce83c2f (HEAD -> master) Add file1.txt
| * 4085604 (origin/master, origin/HEAD) Update README.md
| * 3e40ffc Update README.md
|/
* e27337b Update README.md
* 7216ded Initial commit

(master)$ git pull --rebase
Successfully rebased and updated refs/heads/master.

(master)$ git log --oneline --all --graph
* ec68ae1 (HEAD -> master) Add file1.txt
* 4085604 (origin/master, origin/HEAD) Update README.md
* 3e40ffc Update README.md
* e27337b Update README.md
* 7216ded Initial commit

Pushing

将本地的 commit push 到 remote repository,然后 remote repository 的 master 会 move forward,然后 origin/master 会 move forward.

1
(master)$ git push

有些情况,push 会被拒绝:

如想将C push 到 remote,但是此时别人push了个D到remote,Git为了防止你overwrite别人的work,就不让push:

5_19

5_17

如果用

1
$ git push -f

remote就会丢掉D,然后让C push到remote, remote 的 master 指向C。(别用,谨慎)

好的做法是:用 git pull 在本地存下 D,然后用 3-way merge 或 rebase 的方法结合 local master 和 D,有冲突就解决冲突,然后再 git push 到 remote repository。这样,local 和remote 就一致了。

5_20

Storing Credentials

windows 在 https://github.com/Microsoft/Git-Credential-Manager-for-Windows 下载

不用每次 push 都登录一次。

Sharing Tags

给最近的一次 commit 加tag,并且share 到 github 上去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(master)$ git tag v1.0

(master)$ git log --oneline --all --graph
* 125409a (HEAD -> master, tag: v1.0, origin/master, origin/HEAD) add file2.txt
* ec68ae1 Add file1.txt
* 4085604 Update README.md
* 3e40ffc Update README.md
* e27337b Update README.md
* 7216ded Initial commit

(master)$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/PurpleMStone/Mars.git
* [new tag] v1.0 -> v1.0

更新 github 的页面会发现多了一个 tag。

删除tag:

1
2
3
$ git push origin --delete v1.0
To https://github.com/PurpleMStone/Mars.git
- [deleted] v1.0

更新 github 的页面会发现这个 tag被删了。但是在本地目录,tag还在:

1
2
3
4
5
6
7
8
9
10
$ git log --oneline --all --graph
* 125409a (HEAD -> master, tag: v1.0, origin/master, origin/HEAD) add file2.txt
* ec68ae1 Add file1.txt
* 4085604 Update README.md
* 3e40ffc Update README.md
* e27337b Update README.md
* 7216ded Initial commit

$ git tag -d v1.0
Deleted tag 'v1.0' (was 125409a)

Releases

在 github 上release, 会给当前 commit 版本打一个 tag。

加上Release Notes。

如果还不是stable的版本,勾上“Pre-release”。

5_21

Sharing branches

增加分支

在本地创建了一个新分支,git push 的报错信息意思是这个branch 没有 link to origin 的 branch:

1
2
3
4
5
6
7
8
(master)$ git switch -C feature/change-password
Switched to a new branch 'feature/change-password'

(feature/change-password)$ git push
fatal: The current branch feature/change-password has no upstream branch.
To push the current branch and set the remote as upstream, use

git push --set-upstream origin feature/change-password

-vv 查看当前的本地分支与远程分支的关联关系

-r 查看tracked branch (远程分支):

1
2
3
4
5
6
7
(feature/change-password)$ git branch -vv
* feature/change-password 125409a add file2.txt
master 125409a [origin/master] add file2.txt

(feature/change-password)$ git branch -r
origin/HEAD -> origin/master
origin/master

将新分支push上去(-u中的u是upstream的意思):

1
2
3
4
5
6
7
8
9
(feature/change-password)$ git push -u origin feature/change-password
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'feature/change-password' on GitHub by visiting:
remote: https://github.com/PurpleMStone/Mars/pull/new/feature/change-password
remote:
To https://github.com/PurpleMStone/Mars.git
* [new branch] feature/change-password -> feature/change-password
Branch 'feature/change-password' set up to track remote branch 'feature/change-password' from 'origin'.

再查看一下:

本地分支和远程分支关联起来了,github上也能看到新增的分支

1
2
3
4
5
6
7
8
(feature/change-password)$ git branch -vv
* feature/change-password 125409a [origin/feature/change-password] add file2.txt
master 125409a [origin/master] add file2.txt

(feature/change-password)$ git branch -r
origin/HEAD -> origin/master
origin/feature/change-password
origin/master

删除分支

删除远程分支:

1
2
3
4
5
6
7
(feature/change-password)$ git push -d origin feature/change-password
To https://github.com/PurpleMStone/Mars.git
- [deleted] feature/change-password

(feature/change-password)$ git branch -r
origin/HEAD -> origin/master
origin/master

但是本地分支还在,要切换回master分支删掉:

1
2
3
4
5
6
7
8
9
10
11
12
13
(feature/change-password)$ git branch -vv
* feature/change-password 125409a [origin/feature/change-password: gone] add file2.txt
master 125409a [origin/master] add file2.txt

(feature/change-password)$ git switch master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

(master)$ git branch -d feature/change-password
Deleted branch feature/change-password (was 125409a).

(master)$ git branch
* master

Collaboration Workflow

1
2
本Demo展示了两个contributor是如何通过GitHub来协作的
协作者:👉 我 👉 Amy

1. 我来创建新分支

在GitHub上创建一个新分支feature/change-password,然后在本地目录下:

1
2
3
4
5
6
7
8
9
10
11
12
(master)$ git fetch
From https://github.com/PurpleMStone/Mars
* [new branch] feature/change-passwork -> origin/feature/change-passwork
* [new tag] v1.0 -> v1.0

(master)$ git branch
* master

(master)$ git branch -r
origin/HEAD -> origin/master
origin/feature/change-passwork
origin/master

我们只得到一个remote tacked branch origin/feature/change-passwork,而本地没有一个branch是feature/change-passwork。

创建一个feature/change-passwork分支,相当于一个指针指向remote branch origin/feature/change-passwork:

1
2
3
(master)$ git switch -C feature/change-passwork origin/feature/change-passwork
Switched to a new branch 'feature/change-passwork'
Branch 'feature/change-passwork' set up to track remote branch 'feature/change-passwork' from 'origin'.

2. 另一个contributor

现在比如说有另一个协作者Amy,将这个库clone下来:

1
2
3
4
5
6
7
8
9
10
Amy$ git clone https://github.com/PurpleMStone/Mars.git
Cloning into 'Mars'...
remote: Enumerating objects: 17, done.
remote: Counting objects: 100% (17/17), done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 17 (delta 1), reused 4 (delta 0), pack-reused 0
Receiving objects: 100% (17/17), done.
Resolving deltas: 100% (1/1), done.

Amy$ cd Mars

查看本地分支,依然只有一个master,所以也要创建一个feature/change-passwork分支,相当于一个指针指向remote branch origin/feature/change-passwork:

1
2
3
4
5
6
7
Amy/Mars (master)$ git branch
* master

Amy/Mars (master)$
git switch -C feature/change-passwork origin/feature/change-passwork
Switched to a new branch 'feature/change-passwork'
Branch 'feature/change-passwork' set up to track remote branch 'feature/change-passwork' from 'origin'.

在这个分支下进行修改并commit,然后push到github,可以在 github 的该分支下看到这个修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Amy/Mars (feature/change-passwork)$ echo password > file1.txt

Amy/Mars (feature/change-passwork)
$ git commit -am "Update file1"
[feature/change-passwork 244f7a7] Update file1
1 file changed, 1 insertion(+), 1 deletion(-)

Amy/Mars (feature/change-passwork)$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 306 bytes | 306.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/PurpleMStone/Mars.git
125409a..244f7a7 feature/change-passwork -> feature/change-passwork

3. 我来Merge

这时本人将GitHub的变化pull到本地,并且发现HEAD指向feature/change-passwork分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(feature/change-passwork)$ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 286 bytes | 0 bytes/s, done.
From https://github.com/PurpleMStone/Mars
125409a..244f7a7 feature/change-passwork -> origin/feature/change-passwork
Updating 125409a..244f7a7
Fast-forward
file1.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

(feature/change-passwork)$ git log --oneline --all --graph
* 244f7a7 (HEAD -> feature/change-passwork, origin/feature/change-passwork) Update file1
* 125409a (tag: v1.0, origin/master, origin/HEAD, master) add file2.txt
* ec68ae1 Add file1.txt
* 4085604 Update README.md
* 3e40ffc Update README.md
* e27337b Update README.md
* 7216ded Initial commit

现在希望将分支merge到master,可以发现是一个Fast-forward merge,并且查看log,两个分支在本地确实是merge了,但是origin的远程master分支还是原样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(feature/change-passwork)$ git switch master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

(master)$ git merge feature/change-passwork
Updating 125409a..244f7a7
Fast-forward
file1.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

(master)$ git log --oneline --all --graph
* 244f7a7 (HEAD -> master, origin/feature/change-passwork, feature/change-passwork) Update file1
* 125409a (tag: v1.0, origin/master, origin/HEAD) add file2.txt
* ec68ae1 Add file1.txt
* 4085604 Update README.md
* 3e40ffc Update README.md
* e27337b Update README.md
* 7216ded Initial commit

这就需要用 git push 提交merge更改。再次查看log,远程分支也merge了。并且github上也可看到merge结果(feature/change-passwork的commit merge到master上了)。

1
2
3
4
5
6
7
8
9
10
11
12
13
(master)$ git push
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/PurpleMStone/Mars.git
125409a..244f7a7 master -> master

(master)$ git log --oneline --all --graph
* 244f7a7 (HEAD -> master, origin/master, origin/feature/change-passwork, origin/HEAD, feature/change-passwork) Update file1
* 125409a (tag: v1.0) add file2.txt
* ec68ae1 Add file1.txt
* 4085604 Update README.md
* 3e40ffc Update README.md
* e27337b Update README.md
* 7216ded Initial commit

4. 我来删除分支

既然 merge 了,分支留着也没用了,咱就把这分支删了吧,先删 remote branch,再删 local branch:

1
2
3
4
5
6
7
8
9
10
(master)$ git push -d origin feature/change-passwork
To https://github.com/PurpleMStone/Mars.git
- [deleted] feature/change-passwork

(master)$ git branch
feature/change-passwork
* master

(master)$ git branch -d feature/change-passwork
Deleted branch feature/change-passwork (was 244f7a7).

看看是不是真的删了:

1
2
3
4
5
6
(master)$ git branch
* master

(master)$ git branch -r
origin/HEAD -> origin/master
origin/master

NICE.

5. 另一个contributor的同步

git pull 获取GitHub变化:

1
2
3
4
5
6
7
Amy/Mars (master)$ git pull
From https://github.com/PurpleMStone/Mars
125409a..244f7a7 master -> origin/master
Updating 125409a..244f7a7
Fast-forward
file1.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

本地还有这个branch,删掉:

1
2
3
4
5
6
Amy/Mars (master)$ git branch
feature/change-passwork
* master

Amy/Mars (master)$ git branch -d feature/change-passwork
Deleted branch feature/change-passwork (was 244f7a7).

会发现origin还有这个branch:

1
2
3
4
Amy/Mars (master)$ git branch -r
origin/HEAD -> origin/master
origin/feature/change-passwork
origin/master

可以这样移除(可以刷新本地仓库与远程仓库,保持这些改动的同步):

1
2
3
4
5
6
7
8
Amy/Mars (master)$ git remote prune origin
Pruning origin
URL: https://github.com/PurpleMStone/Mars.git
* [pruned] origin/feature/change-passwork

Amy/Mars (master)$ git branch -r
origin/HEAD -> origin/master
origin/master

Mosh的课程网址

What are Branches?

  • master分支一般存着stable的commit版本,另一个分支如feature存着还不stable的版本,等到改到完善了,才将feature分支merge到master中。
  • MASTER指针指向master分支中最近commit的版本,其他分支同理。
  • HEAD指针可以指向master,也可以指向feature,用于方便地调整working branch。

4_01

Working with Branches

创建分支:

1
$ git branch bugfix

查看现有分支,其中标*的表示当前working branch:

1
2
3
$ git branch
bugfix
* master

也可以这样查看当前working branch:

1
2
3
$ git status
On branch master
nothing to commit, working tree clean

切换到bugfix branch:

1
2
$ git switch bugfix
Switched to branch 'bugfix'

Shortcut:创建并切换分支

1
$ git switch -C bugfix

-C表示creating


重命名分支:

1
2
3
(bugfix)$ git branch -m bugfix bugfix-signup-form

(bugfix-signup-form)$

假如在创建这个bugfix-signup-form分支之前,在master创建了一个audience.txt文件,内容如下:

1
2
3
4
AUDIENCE

This course is for anyone who wants to learn Git.
No prior experience is required.

创建并切换当前分支为bugfix-signup-form分支之后将该文件的内容改为:

1
2
3
WHO THIS COURSE IS FOR 
======================
This course is for anyone who wants to learn Git.

并提交更改:

1
2
3
4
5
6
7
(bugfix-signup-form)$ code audience.txt

(bugfix-signup-form)$ git add .

(bugfix-signup-form)$ git commit -m "fix the bug that prevented the users from signing up"
[bugfix-signup-form 7a5cdb3] fix the bug that prevented the users from signing up
1 file changed, 3 insertions(+), 4 deletions(-)

查看一下,HEAD指向当前分支:

1
2
3
(bugfix-signup-form)$ git log --oneline
7a5cdb3 (HEAD -> bugfix-signup-form) fix the bug that prevented the users from signing up
82ca774 (master) add audience.txt

切换回master分支并查看audience.txt内容:

1
2
3
4
(bugfix-signup-form)$ git switch master
Switched to branch 'master‘

(master)$ code audience.txt

发现audience.txt内容依然是旧版的:

1
2
3
4
AUDIENCE

This course is for anyone who wants to learn Git.
No prior experience is required.

查看修改log,会发现看不到bugfix-signup-form分支的更改log:

1
2
(master)$ git log --oneline
82ca774 (HEAD -> master) add audience.txt

除非用—all:

1
2
3
(master)$ git log --oneline --all
7a5cdb3 (bugfix-signup-form) fix the bug that prevented the users from signing up
82ca774 (HEAD -> master) add audience.txt

如果想删除bugfix-signup-form分支,由于还没merge,所以会报错。若确定真的要删,用-D:

1
2
3
(master)$ git branch -d bugfix-signup-form
error: The branch 'bugfix-signup-form' is not fully merged.
If you are sure you want to delete it, run 'git branch -D bugfix-signup-form'.

Comparing Branches

在将分支merge到master之前,先看看改了啥:

1
2
3
4
5
6
(master)$ git log master..bugfix-signup-form
commit 7a5cdb3ea29a72530f598fa7fee1358a6c4dcb23 (bugfix-signup-form)
Author: Stone <masaike@qq.com>
Date: Fri Apr 23 21:56:15 2021 +0800

fix the bug that prevented the users from signing up

查看具体改了啥内容(由于当前分支就是master, master..也可以省略不写):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(master)$ git diff master..bugfix-signup-form
diff --git a/audience.txt b/audience.txt
index 01fd2e1..ecbc229 100644
--- a/audience.txt
+++ b/audience.txt
@@ -1,4 +1,3 @@
-AUDIENCE
-
-This course is for anyone who wants to learn Git.
-No prior experience is required.
\ No newline at end of file
+WHO THIS COURSE IS FOR
+======================
+This course is for anyone who wants to learn Git.
\ No newline at end of file

看简略的修改状态信息:

1
2
(master)$ git diff --name-status bugfix-signup-form
M audience.txt

Stashing

bugfix-signup-form分支对audience.txt修改后,该分支尚未merge到master分支,此时master分支却又对audience.txt进行了修改,此时不允许切换到分支:

1
2
3
4
5
6
7
(master)$ git switch bugfix-signup-form
error: Your local changes to the following files would be overwritten by checkout:
audience.txt
Please commit your changes or stash them before you switch branches.
Aborting

(master)$

如果我们还不想commit在master分支对audience.txt的修改,可以先用stash保存工作现场:

1
2
(master)$ git stash push -m "New tax rules."
Saved working directory and index state On master: New tax rules.

By default, new untracked files are not included in the stash, 要用-a放入stash:

1
2
3
4
5
6
7
8
9
10
(master)$ echo hello > newfile.txt

(master)$ git status -s
?? newfile.txt

(master)$ git stash push -m "My new stash with newfile1.txt"
No local changes to save //------这样不行的--------------

(master)$ git stash push -am "My new stash"
Saved working directory and index state On master: My new stash

查看stash:

1
2
3
(master)$ git stash list
stash@{0}: On master: My new stash
stash@{1}: On master: New tax rules.

Stashing了就可以成功切换分支了:

1
2
(master)$ git switch bugfix-signup-form
Switched to branch 'bugfix-signup-form'

在分支的工作做完,换回master分支,想恢复工作现场,先看看改了啥(根据stash号):

1
2
3
(master)$ git stash show 1
audience.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

可以的话就apply到本地目录:

1
(master)$ git stash apply 1

用 git stash apply恢复后,保存的现场并不删除,需要用git stash drop 删除:

1
2
(master)$ git stash drop 1
Dropped refs/stash@{1} (92b61ede11aa9f3c076aed7c1409c7cc683e4efb)

清除全部stash:

1
(master)$ git stash clear

Merging

1
2
3
/******Merges*******/
👉 Fast-forward merges (if branches have not diverged)
👉 3-way merges (if branches have diverged)

Fast-forward merges

当前分支合并另一个分支的时候,如果合并的过程中没有冲突,则会通过直接移动两个分支的指针,来达到合并。

4_02

只有bugfix分支在改动,改到stable了,直接移动master指针到bugfix指向的位置,然后丢弃bugfix指针就行了。


3-way merges

4_03

两个分支都在改动,最后merge到一起。


Fast-forward Merges

1
2
3
(master)$ git log --oneline --all --graph
* 7a5cdb3 (bugfix-signup-form) fix the bug that prevented the users from signing up
* 82ca774 (HEAD -> master) add audience.txt
1
2
3
4
5
(master)$ git merge bugfix-signup-form
Updating 82ca774..7a5cdb3
Fast-forward
audience.txt | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
1
2
3
(master)$ git log --oneline --all --graph
* 7a5cdb3 (HEAD -> master, bugfix-signup-form) fix the bug that prevented the users from signing up
* 82ca774 add audience.txt

No fast-forward merges

merge 了 master 和 bugfix-signup-form 分支后,创建并切换到新分支bugfix-login-form。此时假如对已有文件 file1.js 进行 modify 并 commit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(master)$ git switch -C bugfix-login-form
Switched to and reset branch 'bugfix-login-form'

(bugfix-login-form)$ code file1.js

(bugfix-login-form)$ git add .

(bugfix-login-form)$ git commit -m "update file1.js"
[bugfix-login-form 1ad00a7] update file1.js
1 file changed, 1 insertion(+), 1 deletion(-)

(bugfix-login-form)$ git log --oneline --all --graph
* 1ad00a7 (HEAD -> bugfix-login-form) update file1.js
* 7a5cdb3 (master, bugfix-signup-form) fix the bug that prevented the users from signing up
* 82ca774 add audience.txt

此时master和bugfix-signup-form指向同一commit,bugfix-login-form 指向本 modify commit。然后用 No fast-forward 的方式来合并两分支,可以看到 git merge 命令的返回结果中不再有“Fast-forward”出现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(bugfix-login-form)$ git switch master
Switched to branch 'master'

(master)$ git merge --no-ff bugfix-login-form
Merge made by the 'recursive' strategy.
file1.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

(master)$ git log --oneline --all --graph
* 9612ab4 (HEAD -> master) Merge branch 'bugfix-login-form'
|\
| * 1ad00a7 (bugfix-login-form) update file1.js
|/
* 7a5cdb3 (bugfix-signup-form) fix the bug that prevented the users from signing up
* 82ca774 add audience.txt
1
2
3
4
/**使用No fast-forward merges的优缺点(两派观点)**/
👉 CONS:pollutes the history (those who prefer linear history)
👉 PROS:1) True reflection of history
2) Allow reverting a feature (easier for us to undo a feature)

如果使用No fast-forward merges:

4_04

红色标记的commit 是F1和F2的结合,如果红标的commit是个bad commit, 蓝标的commit 是和红标完全相反的commit,我们只需要revert 一个commit就行了。

而如果使用fast-forward merges:

4_05

要revert两个commit,会更加复杂。

3-way merges

创建新分支并在此分支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
26
27
28
(master)$ git switch -C feature-change-password
Switched to a new branch 'feature-change-password'

(feature-change-password)$ git log --oneline --all --graph
* 9612ab4 (HEAD -> feature-change-password, master) Merge branch 'bugfix-login-form'
|\
| * 1ad00a7 (bugfix-login-form) update file1.js
|/
* 7a5cdb3 (bugfix-signup-form) fix the bug that prevented the users from signing up
* 82ca774 add audience.txt

(feature-change-password)$ echo hello > change-password.txt

(feature-change-password)$ git add .

(feature-change-password)$ git commit -m "Build the change password form."
[feature-change-password 49f4f13] Build the change password form.
1 file changed, 1 insertion(+)
create mode 100644 change-password.txt

(feature-change-password)$ git log --oneline --all --graph
* 49f4f13 (HEAD -> feature-change-password) Build the change password form.
* 9612ab4 (master) Merge branch 'bugfix-login-form'
|\
| * 1ad00a7 (bugfix-login-form) update file1.js
|/
* 7a5cdb3 (bugfix-signup-form) fix the bug that prevented the users from signing up
* 82ca774 add audience.txt

返回master分支并在此分支commit新修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(feature-change-password)$ git switch master
Switched to branch 'master'

(master)$ code audience.txt

(master)$ git commit -am "Update audience.txt"
[master 4aa3534] Update audience.txt
1 file changed, 1 insertion(+), 1 deletion(-)

(master)$ git log --oneline --all --graph
* 4aa3534 (HEAD -> master) Update audience.txt
| * 49f4f13 (feature-change-password) Build the change password form.
|/
* 9612ab4 Merge branch 'bugfix-login-form'
|\
| * 1ad00a7 (bugfix-login-form) update file1.js
|/
* 7a5cdb3 (bugfix-signup-form) fix the bug that prevented the users from signing up
* 82ca774 add audience.txt

Merge这两个分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(master)$ git merge feature-change-password
Merge made by the 'recursive' strategy.
change-password.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 change-password.txt

(master)$ git log --oneline --all --graph
* 91dd8b9 (HEAD -> master) Merge branch 'feature-change-password'
|\
| * 49f4f13 (feature-change-password) Build the change password form.
* | 4aa3534 Update audience.txt
|/
* 9612ab4 Merge branch 'bugfix-login-form'
|\
| * 1ad00a7 (bugfix-login-form) update file1.js
|/
* 7a5cdb3 (bugfix-signup-form) fix the bug that prevented the users from signing up
* 82ca774 add audience.txt

Viewing the Merged Branches

查看merge到master的branch:

1
2
3
4
5
(master)$ git branch --merged
bugfix-login-form
bugfix-signup-form
feature-change-password
* master

这些分支已经merge了,就可以安全地删除了:

1
2
(master)$ git branch -d bugfix-login-form
Deleted branch bugfix-login-form (was 1ad00a7).

查看还没merge的分支:

1
(master)$ git branch --no-merged

Merge Conflicts

1
2
3
4
Conflicts (difference in two branches)
👉 Change1, Change2
👉 Change, Delete
👉 Add1, Add2

Demo:

  1. 创建新分支bugfix-change-password并提交对change-password.txt的修改
1
2
3
4
5
6
7
8
(master)$ git switch -C bugfix-change-password
Switched to a new branch 'bugfix-change-password'

(bugfix-change-password)$ code change-password.txt

(bugfix-change-password)$ git commit -am "Update change-password.txt"
[bugfix-change-password.txt 7e3da10] Update change-password.txt
1 file changed, 2 insertions(+)
  1. 切换到master分支,提交对change-password.txt的修改
1
2
3
4
5
6
7
8
(bugfix-change-password)$ git switch master
Switched to branch 'master'

(master)$ code change-password.txt

(master)$ git commit -am "Update change-password.txt"
[master adebbcf] Update change-password.txt
1 file changed, 2 insertions(+)
  1. Merge两个分支时出现conflict:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(master)$ git merge bugfix-change-password
merge: bugfix-change-password - not something we can merge

(master)$ git merge bugfix-change-password.txt
Auto-merging change-password.txt
CONFLICT (content): Merge conflict in change-password.txt
Automatic merge failed; fix conflicts and then commit the result.

(master|MERGING)$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)

Unmerged paths: //---------merge失败的原因所在------------------
(use "git add <file>..." to mark resolution)
both modified: change-password.txt

no changes added to commit (use "git add" and/or "git commit -a")

看看文件:

4_06

VSCode提供了几种(可以直接点击的):Accept Current Change等三种方法消除conflict

也可以手动改,但此时不能增加新的内容,只能选择两个分支的修改内容。最后改为:

1
2
3
4
hello

change in the master branch.
change in the bugfix branch.

这回就可以成功提交:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(master|MERGING)$ git add change-password.txt

(master|MERGING)$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)

Changes to be committed:
modified: change-password.txt

(master|MERGING)$ git commit
[master 74c5e1c] Merge branch 'bugfix-change-password.txt'

(master)$

Visual Merge Tools

1
2
3
👉 Kdiff 
👉 P4Merge
👉 WinMerge (Windows Only)

在官网下载P4Merge并安装,然后配置(安装路径):

1
2
3
$ git config --global merge.tool p4merge

$ git config --global mergetool.p4merge.path "D:/Program Files/P4Merge/p4merge.exe"

4_07

Aborting a Merge

想merge却发现有冲突,此时没有时间处理冲突,要回到merge之前的状态:

1
2
3
4
5
6
7
8
(master)$ git merge bugfix-change-password
Auto-merging change-password.txt
CONFLICT (content): Merge conflict in change-password.txt
Automatic merge failed; fix conflicts and then commit the result.

(master|MERGING)$ git merge --abort

(master)$

Undoing a Faulty Merge

merge了之后发现这个merge不合适,又不想merge了:

4_08

1
2
3
4
5
6
7
(master)$ git log --oneline --all --graph
* bbf6499 (HEAD -> master) Merge branch 'bugfix-change-password'
|\
| * fdfcee5 (bugfix-change-password) Update change-password.txt
* | 5975f40 Update change-password.txt
|/
* 3c6ba48 Update change-password.txt

4_09

用reset回到上一个master指向的commit,这之后的commit都会消失(看log):

1
2
3
4
5
6
7
8
(master)$ git reset --hard HEAD~1
HEAD is now at 5975f40 Update change-password.txt

(master)$ git log --oneline --all --graph
* 5975f40 (HEAD -> master) Update change-password.txt
| * fdfcee5 (bugfix-change-password) Update change-password.txt
|/
* 3c6ba48 Update change-password.txt

现在没有一个指针指向红标的commit,Git会将其视为垃圾,一段时间后自动将其删除:

4_10

但是在Git删除前它还在,还可以恢复出来:

1
2
3
4
5
6
7
8
9
10
(master)$ git reset --hard bbf6499
HEAD is now at bbf6499 Merge branch 'bugfix-change-password'

(master)$ git log --oneline --all --graph
* bbf6499 (HEAD -> master) Merge branch 'bugfix-change-password'
|\
| * fdfcee5 (bugfix-change-password) Update change-password.txt
* | 5975f40 Update change-password.txt
|/
* 3c6ba48 Update change-password.txt

4_11

4_12

4_13

git revert是用于“反做”某一个版本,以达到撤销该版本的修改的目的。比如,我们commit了三个版本(版本一、版本二、 版本三),突然发现版本二不行(如:有bug),想要撤销版本二,但又不想影响撤销版本三的提交,就可以用 git revert 命令来反做版本二,生成新的版本四,这个版本四里会保留版本三的东西,但撤销了版本二的东西。

适用场景: 如果我们想撤销之前的某一版本,但是又想保留该目标版本后面的版本,记录下这整个版本变动流程,就可以用这种方法

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(master)$ git log --oneline --all --graph
* bbf6499 (HEAD -> master) Merge branch 'bugfix-change-password'
|\
| * fdfcee5 (bugfix-change-password) Update change-password.txt
* | 5975f40 Update change-password.txt
|/
* 3c6ba48 Update change-password.txt

(master)$ git revert -m 1 HEAD
[master 5f99ab9] Revert "Merge branch 'bugfix-change-password'"
1 file changed, 1 deletion(-)

(master)$ git log --oneline --all --graph
* 5f99ab9 (HEAD -> master) Revert "Merge branch 'bugfix-change-password'"
* bbf6499 Merge branch 'bugfix-change-password'
|\
| * fdfcee5 (bugfix-change-password) Update change-password.txt
* | 5975f40 Update change-password.txt
|/
* 3c6ba48 Update change-password.txt

由于当前commit有两个parent commit (因为有两分支),所以用 -m 1 表示回到第一个parent commit (即上一个master指向的commit),可以看到所有的commit都保留了。

参考

https://blog.csdn.net/yxlshk/article/details/79944535

Squash Merging

4_14

可能B1和B2只是小改动,checkpoint之类的,我们不想让它们pollute了commit history, 所以用一个总的commit结合了这两个commit。而事实上并没有merge两个分支。

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
31
32
33
34
35
36
37
38
39
40
(master)$ git switch -C bugfix-photo-upload
Switched to a new branch 'bugfix-photo-upload'

(bugfix-photo-upload)$ echo bigfix >> audience.txt

(bugfix-photo-upload)$ git commit -am "Update audience.txt"
[bugfix-photo-upload c699541] Update audience.txt
1 file changed, 1 insertion(+), 1 deletion(-)

(bugfix-photo-upload)$ echo bigfix >> file1.js

(bugfix-photo-upload)$ git commit -am "Update file1.js"
[bugfix-photo-upload abb7001] Update file1.js
1 file changed, 1 insertion(+), 1 deletion(-)

(bugfix-photo-upload)$ git switch master
Switched to branch 'master'

(master)$ git merge --squash bugfix-photo-upload
Updating 5f99ab9..abb7001
Fast-forward
Squash commit -- not updating HEAD
audience.txt | 2 +-
file1.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)

(master)$ git status -s
M audience.txt
M file1.js

(master)$ git commit -m "Fix the bug on the photo upload page"
[master 3a99538] Fix the bug on the photo upload page
2 files changed, 2 insertions(+), 2 deletions(-)

(master)$ git log --oneline --all --graph
* 3a99538 (HEAD -> master) Fix the bug on the photo upload page
| * abb7001 (bugfix-photo-upload) Update file1.js
| * c699541 Update audience.txt
|/
* 5f99ab9 Revert "Merge branch 'bugfix-change-password'"

实际上并没有merge:

1
2
3
4
5
6
7
8
9
10
11
12
13
(master)$ git branch
bugfix-change-password
bugfix-photo-upload
bugfix-signup-form
* master

(master)$ git branch --merge
bugfix-change-password
bugfix-signup-form
* master

(master)$ git branch --no-merge
bugfix-photo-upload

删除bugfix-photo-upload后可以看到,只剩一个总的commit记录了:

1
2
3
4
5
6
(master)$ git branch -D bugfix-photo-upload
Deleted branch bugfix-photo-upload (was abb7001).

(master)$ git log --oneline --all --graph
* 3a99538 (HEAD -> master) Fix the bug on the photo upload page
* 5f99ab9 Revert "Merge branch 'bugfix-change-password'"

Rebasing

(图与 demo 不十分吻合,在 demo 中 feature 分支只有一个 commit)

  1. 初始状态:master 和 feature-shopping-cart 是 diverse 的,要么用 3-way merge,要么将feature-shopping-cart 分支 rebase,总之是不能 fast-forward merge 的

4_15

1
2
3
4
5
(master)$ git log --oneline --all --graph
* 4026973 (HEAD -> master) update toc.txt
| * 34112c9 (feature-shopping-cart) add cart.txt
|/
* 7370c4a update
  1. 将 feature-shopping-cart 分支 rebase 到 master 指向的 commit:

    4_17

1
2
3
4
5
6
7
8
9
10
(master)$ git switch feature-shopping-cart
Switched to branch 'feature-shopping-cart'

(feature-shopping-cart)$ git rebase master
Successfully rebased and updated refs/heads/feature-shopping-cart.

(feature-shopping-cart)$ git log --oneline --all --graph
* 21dc4d0 (HEAD -> feature-shopping-cart) add cart.txt
* 4026973 (master) update toc.txt
* 7370c4a update
  1. 现在可以 fast-forward merge 了:

    4_18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(feature-shopping-cart)
$ git switch master
Switched to branch 'master'

(master)$ git merge feature-shopping-cart
Updating 4026973..21dc4d0
Fast-forward
cart.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 cart.txt

(master)$ git log --oneline --all --graph
* 21dc4d0 (HEAD -> master, feature-shopping-cart) add cart.txt
* 4026973 update toc.txt
* 7370c4a update

但是,rebasing 很有可能会有 conflict,如下例中 master 和 feature-shopping-cart 各自更改toc.txt,就不能将 feature-shopping-cart rebase 到 master:

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
(master)$ echo ocean > toc.txt

(master)$ git commit -am "Update toc.txt"
[master f2e571a] Update toc.txt
1 file changed, 1 insertion(+), 2 deletions(-)

(master)$ git switch feature-shopping-cart
Switched to branch 'feature-shopping-cart'

(feature-shopping-cart)$ echo mountain > toc.txt

(feature-shopping-cart)$ git commit -am "add mountain to toc.txt"
[feature-shopping-cart 06ee042] add mountain to toc.txt
1 file changed, 1 insertion(+), 2 deletions(-)

(feature-shopping-cart)$ git log --oneline --all --graph
* 06ee042 (HEAD -> feature-shopping-cart) add mountain to toc.txt
| * f2e571a (master) Update toc.txt
|/
* 21dc4d0 add cart.txt

(feature-shopping-cart)$ git rebase master
error: could not apply 06ee042... add mountain to toc.txt
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 06ee042... add mountain to toc.txt
Auto-merging toc.txt
CONFLICT (content): Merge conflict in toc.txt

用p4merge解决一下conflict:

1
2
3
4
5
6
7
8
9
(feature-shopping-cart|REBASE 1/1)$ git mergetool
Merging:
toc.txt

Normal merge conflict for 'toc.txt':
{local}: modified file
{remote}: modified file

(feature-shopping-cart|REBASE 1/1)$ git rebase --abort

toc.txt.orig 是解决冲突的时候产生的额外文件,删掉就好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(feature-shopping-cart)$ git status
On branch feature-shopping-cart
Untracked files:
(use "git add <file>..." to include in what will be committed)
toc.txt.orig

nothing added to commit but untracked files present (use "git add" to track)

(feature-shopping-cart)$ cat toc.txt.orig
<<<<<<< HEAD
ocean
=======
mountain
>>>>>>> 06ee042 (add mountain to toc.txt)

(feature-shopping-cart)$ git clean -fd
Removing toc.txt.orig

为了不产生这种文件,可以设置一下:

1
(feature-shopping-cart)$ git config --global mergetool.keepBackup false

Cherry Picking

4_19

比如 F1 是有意思的commit,我们想要提交到master,但是我们还没有准备好将feature分支merge 到 master 分支,可以 cherry picking F1 到 master。

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
31
32
33
34
35
36
(master)$ git log --oneline --all --graph
* 06ee042 (feature-shopping-cart) add mountain to toc.txt
| * f2e571a (HEAD -> master) Update toc.txt
|/
* dc9b37f add toc.txt

(master)$ git cherry-pick 06ee042
Auto-merging toc.txt
CONFLICT (content): Merge conflict in toc.txt
error: could not apply 06ee042... add mountain to toc.txt
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

(master|CHERRY-PICKING)$ git mergetool
Merging:
toc.txt

Normal merge conflict for 'toc.txt':
{local}: modified file
{remote}: modified file

(master|CHERRY-PICKING)$ git status -s
M toc.txt

(master|CHERRY-PICKING)$ git commit
[master 3fcca9a] add mountain to toc.txt
Date: Mon Apr 26 21:54:11 2021 +0800
1 file changed, 1 insertion(+), 1 deletion(-)

(master)$ git log --oneline --all --graph
* 3fcca9a (HEAD -> master) add mountain to toc.txt
* f2e571a Update toc.txt
| * 06ee042 (feature-shopping-cart) add mountain to toc.txt
|/
* dc9b37f add toc.txt

最后可以看到master前进了一个commit,而且是pick了feature-shopping-cart分支的commit。

Picking Files from Another Branch

是 picking files, 而不是 picking commit 了。

1
2
3
4
5
6
7
8
9
10
11
(master)$ git switch -C feature-send-email
Switched to a new branch 'feature-send-email'

(feature-send-email)$ echo river > toc.txt

(feature-send-email)$ git commit -am "Update toc.txt"
[feature-send-email 4680781] Update toc.txt
1 file changed, 1 insertion(+), 1 deletion(-)

(feature-send-email)$ git switch master
Switched to branch 'master'

使用 git restore:

1
2
3
4
5
6
7
(master)$ git restore --source=feature-send-email -- toc.txt

(master)$ git status -s
M toc.txt

(master)$ cat toc.txt
river

然后就可以在 master 分支上提交了。

Mosh的课程网址

Viewing the History

基本命令:

1
$ git log

用 —oneline 查看简略信息,—stat 显示每个文件增删的行数:

1
$ git log --oneline --stat

—patch 查看每个文件具体的内容更改:

1
$ git log --oneline --patch

Filtering the History

用 -3 表示只查看前三条历史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 log --oneline
d7d9bfb (HEAD -> master) refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js
78daaab Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 add data
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 Initial commit
3fd1e3e Remove unused code
b184de1 Remove unused code
dd03e4e Fix the bug
3b634ca add Lines
a787fca add line
568c0fc Initial commit.

$ git log --oneline -3
d7d9bfb (HEAD -> master) refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js

用 author 名字过滤:

1
$ git log --oneline --author="Stone"

用时间过滤:

1
2
3
$ git log --oneline --after="2021-04-21"
$ git log --oneline --after="yesterday"
$ git log --oneline --after="one week ago"

用 commit 时的 message 过滤:

1
2
3
4
5
$ git log --oneline --grep="add"
fdb7fb6 add data
62416e2 add files
3b634ca add Lines
a787fca add line

用具体更改的内容过滤:

1
2
3
4
5
6
7
8
9
10
11
$ git log --oneline -S"world" --patch
0f62ff0 remove all js files
diff --git a/file1.js b/file1.js
deleted file mode 100644
index 29887f9..0000000
--- a/file1.js
+++ /dev/null
@@ -1,3 +0,0 @@
-hello world
-sky
-sun

根据 ID 选取某区间的 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
26
27
$ git log --oneline
d7d9bfb (HEAD -> master) refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js //----------到此为止--------------
78daaab Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 add data
62416e2 add files //---------从这上一行往上-----------
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 Initial commit
3fd1e3e Remove unused code
b184de1 Remove unused code
dd03e4e Fix the bug
3b634ca add Lines
a787fca add line
568c0fc Initial commit.

$ git log --oneline 62416e2..9b82d1b
9b82d1b Add file1.js
78daaab Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 add data

查看某个文件的修改 commit 历史:

1
2
3
4
5
6
7
8
9
10
11
$ git log --oneline file1.js
d7d9bfb (HEAD -> master) refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js
78daaab Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 add data
62416e2 add files
fde6777 modified
f264740 modified

如果报错说不清楚,就在文件名之前加—

1
$ git log --oneline -- file1.js

查看该文件的具体内容修改:

1
$ git log --oneline --patch -- file1.js

Formatting the Log Output

调整 log 格式

1
$ git log --pretty=format:"%Cgreen%an%Creset committed %h on %cd"

3_01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
%Cgreen
switch color to green

%an
author name

%Creset
reset color

%H
commit hash

%h
abbreviated commit hash

%cd
committer date (format respects --date= option)

Creating Aliases

给加格式的 log 命令起个别名:

1
$ git config --global alias.lg "log --pretty=format:'%an committed %h'"

就可以方便地使用啦:

1
2
3
4
5
$ git lg
Stone committed d7d9bfb
Stone committed d2acf0e
Stone committed 9b82d1b
Stone committed 78daaab

起别名的另一个例子:

1
2
3
$ git config --global alias.unstage "restore --staged ."

$ git unstage

Viewing a Commit

查看详细信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git show HEAD~2
commit 9b82d1b30e0ea17c2a63732ede1652f7ad0da29b
Author: Stone <masaike@qq.com>
Date: Thu Apr 22 19:04:57 2021 +0800

Add file1.js

diff --git a/file1.js b/file1.js
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file1.js
@@ -0,0 +1 @@
+hello

查看该commit版本的某文件的具体内容:

1
2
$ git show HEAD~2:file1.js
hello

查看简略信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ git show HEAD~2 --name-only
commit 9b82d1b30e0ea17c2a63732ede1652f7ad0da29b
Author: Stone <masaike@qq.com>
Date: Thu Apr 22 19:04:57 2021 +0800

Add file1.js

file1.js

$ git show HEAD~2 --name-status
commit 9b82d1b30e0ea17c2a63732ede1652f7ad0da29b
Author: Stone <masaike@qq.com>
Date: Thu Apr 22 19:04:57 2021 +0800

Add file1.js

A file1.js

最后一行的A 表示“added”

Viewing the Changes Across Commits

查看两个commit版本的区别:

1
2
3
4
5
6
7
8
9
$ git diff HEAD~2 HEAD
diff --git a/file1.js b/file1.js
index ce01362..24defb6 100644
--- a/file1.js
+++ b/file1.js
@@ -1 +1,2 @@
hello
+A new line of code
\ No newline at end of file

查看区别的简略信息:

1
2
3
4
5
$ git diff HEAD~2 HEAD --name-only
file1.js

$ git diff HEAD~2 HEAD --name-status
M file1.js

上面代码块的最后一行中的“M”表示“modified”。

查看两个commit版本中某个文件的区别:

1
2
3
4
5
6
7
8
9
$ git diff HEAD~2 HEAD file1.js
diff --git a/file1.js b/file1.js
index ce01362..24defb6 100644
--- a/file1.js
+++ b/file1.js
@@ -1 +1,2 @@
hello
+A new line of code
\ No newline at end of file

Checking Out a Commit

LAST表示最近commit,FIRST表示最初commit:

3_03 (7)

MASTER分支指针指向该分支最近的commit:

3_03 (1)

master只是其中的一个分支,事实上一个project可能会有许多分支,所以要用HEAD指向当前working的分支:

3_03 (2)

使用 git checkout 命令之后,HEAD指向指定的commit了:

3_03 (3)

具体例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(master)$ git checkout d6f4cc6
Note: switching to 'd6f4cc6'.

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 switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

git switch -c <new-branch-name>

Or undo this operation with:

git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at d6f4cc6 Initial commit

HEAD指向d6f4cc6而不是master了,所以是’detached HEAD’ state。

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
((d6f4cc6...))$ git log --oneline
d6f4cc6 (HEAD) Initial commit
3fd1e3e Remove unused code
b184de1 Remove unused code
dd03e4e Fix the bug
3b634ca add Lines
a787fca add line
568c0fc Initial commit.

((d6f4cc6...))$ git log --oneline --all
d7d9bfb (master) refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js
78daaab Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 add data
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 (HEAD) Initial commit
3fd1e3e Remove unused code
b184de1 Remove unused code
dd03e4e Fix the bug
3b634ca add Lines
a787fca add line
568c0fc Initial commit.

此时不能有new commit了,因为如果有new commit:

3_03 (5)

当HEAD指回master时:

3_03 (6)

这个new commit再也无法被访问到,就是一个dead commit了。

HEAD指回master:

1
2
3
4
5
((6f4cc6...))$ git checkout master
Previous HEAD position was d6f4cc6 Initial commit
Switched to branch 'master'

(master)$

Finding Bugs Using Bisect

可以用Bisect查找是哪次commit有bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(master)$ git log --oneline --all
d7d9bfb (HEAD -> master) refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js
78daaab Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 add data
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 Initial commit

开始。如果当前commit是bad commit,告诉它:

1
2
3
(master)$ git bisect start

(master|BISECTING)$ git bisect bad

检查发现最开始的commit是good commit,告诉它:

1
2
3
(master|BISECTING)$ git bisect good d6f4cc6
Bisecting: 5 revisions left to test after this (roughly 3 steps)
[fdb7fb6ef27ebfafaa2f7941201c2492c9f9d5ce] add data

说是需要检查5个修改,大概要3个步骤。

可以看到HEAD detach了,指向 good commit 和 bad commit 中间的 commit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
((fdb7fb6...)|BISECTING) $ git log --oneline --all
d7d9bfb (master, refs/bisect/bad) refactor file1 //---------bad--------
d2acf0e Refactor code
9b82d1b Add file1.js
78daaab Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 (HEAD) add data //---------HEAD--------
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 (refs/bisect/good-d6f4cc6574d699b11d91e52aa10543baf65c9af3) Initial commit //---------good--------

当前commit是good commit,告诉它:

1
2
3
((fdb7fb6...)|BISECTING)$ git bisect good
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[78daaab1d24ce993cbd82ab972cfeb9612369ed8] Delete file1.js

说是还需要检查2个修改,大概要2个步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
((78daaab...)|BISECTING)$ git log --oneline --all
d7d9bfb (master, refs/bisect/bad) refactor file1 //---------bad--------
d2acf0e Refactor code
9b82d1b Add file1.js
78daaab (HEAD) Delete file1.js //---------HEAD--------
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 (refs/bisect/good-fdb7fb6ef27ebfafaa2f7941201c2492c9f9d5ce) add data //---------new good--------
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 (refs/bisect/good-d6f4cc6574d699b11d91e52aa10543baf65c9af3) Initial commit //---------old good--------

Once again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
((78daaab...)|BISECTING)$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[d2acf0efae32ea3ffc555fbfc6e432a656dba66c] Refactor code

((d2acf0e...)|BISECTING)$ git log --oneline --all
d7d9bfb (master, refs/bisect/bad) refactor file1 //----------bad--------
d2acf0e (HEAD) Refactor code //---------HEAD--------
9b82d1b Add file1.js
78daaab (refs/bisect/good-78daaab1d24ce993cbd82ab972cfeb9612369ed8) Delete file1.js //---------new good--------
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 (refs/bisect/good-fdb7fb6ef27ebfafaa2f7941201c2492c9f9d5ce) add data
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 (refs/bisect/good-d6f4cc6574d699b11d91e52aa10543baf65c9af3) Initial commit

Once again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
((d2acf0e...)|BISECTING)$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[9b82d1b30e0ea17c2a63732ede1652f7ad0da29b] Add file1.js

((9b82d1b...)|BISECTING)$ git log --oneline --all
d7d9bfb (master) refactor file1
d2acf0e (refs/bisect/bad) Refactor code
9b82d1b (HEAD) Add file1.js
78daaab (refs/bisect/good-78daaab1d24ce993cbd82ab972cfeb9612369ed8) Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 (refs/bisect/good-fdb7fb6ef27ebfafaa2f7941201c2492c9f9d5ce) add data
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 (refs/bisect/good-d6f4cc6574d699b11d91e52aa10543baf65c9af3) Initial commit

也可以用HEAD:好的,终于找到了:

1
2
3
4
5
6
7
8
9
10
11
((9b82d1b...)|BISECTING)$ git bisect bad
9b82d1b30e0ea17c2a63732ede1652f7ad0da29b is the first bad commit
commit 9b82d1b30e0ea17c2a63732ede1652f7ad0da29b
Author: Stone <masaike@qq.com>
Date: Thu Apr 22 19:04:57 2021 +0800

Add file1.js

file1.js | 1 +
1 file changed, 1 insertion(+)
create mode 100644 file1.js

HEAD指回master:

1
2
3
4
5
((9b82d1b...)|BISECTING)$ git bisect reset
Previous HEAD position was 9b82d1b Add file1.js
Switched to branch 'master'

(master)$

Finding Contributors Using Shortlog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ git shortlog -n
Stone (19):
Initial commit.
add line
add Lines
Fix the bug
Remove unused code
Remove unused code
Initial commit
Refactor code
Add gitignore
modified
modified
add files
add data
remove all js files
commit file1.ls
Delete file1.js
Add file1.js
Refactor code
refactor file1
1
2
3
4
5
6
7
8
9
10
11
$ git shortlog -n -s
19 Stone

$ git shortlog -n -s -e
19 Stone <masaike@qq.com>

$ git shortlog -n -s -e --after="2020-04-21"
19 Stone <masaike@qq.com>

$ git shortlog -n -s -e --before="2021-04-22"
12 Stone <masaike@qq.com>

Viewing the History of a File

查看某文件的修改历史(简略信息):

1
2
3
4
$ git log --oneline file1.js
d7d9bfb (HEAD -> master) refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js

查看文件修改的状态:

1
2
3
4
5
6
7
8
9
10
$ git log --oneline --stat file1.js
d7d9bfb (HEAD -> master) refactor file1
file1.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
d2acf0e Refactor code
file1.js | 1 +
1 file changed, 1 insertion(+)
9b82d1b Add file1.js
file1.js | 1 +
1 file changed, 1 insertion(+)

具体信息:

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
$ git log --oneline --patch file1.js
d7d9bfb (HEAD -> master) refactor file1
diff --git a/file1.js b/file1.js
index 56f593b..24defb6 100644
--- a/file1.js
+++ b/file1.js
@@ -1,2 +1,2 @@
hello
-A new line of code
+A new line of code
\ No newline at end of file
d2acf0e Refactor code
diff --git a/file1.js b/file1.js
index ce01362..56f593b 100644
--- a/file1.js
+++ b/file1.js
@@ -1 +1,2 @@
hello
+A new line of code
9b82d1b Add file1.js
diff --git a/file1.js b/file1.js
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file1.js
@@ -0,0 +1 @@
+hello

Restoring a Deleted File

不小心删除了文件file1.js:

1
2
3
4
5
6
7
$ git rm file1.js
rm 'file1.js'

$ git commit -m "Remove file1.js"
[master e2ebefd] Remove file1.js
1 file changed, 2 deletions(-)
delete mode 100644 file1.js

想恢复它,就从d7d9bfb的commit恢复到本地目录,然后重新commit到仓库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git log --oneline -- file1.js
e2ebefd (HEAD -> master) Remove file1.js
d7d9bfb refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js

$ git checkout d7d9bfb file1.js
Updated 1 path from 8623496

$ git status -s
A file1.js

$ git commit -m "Restore file1.js"
[master 9328b9b] Restore file1.js
1 file changed, 2 insertions(+)
create mode 100644 file1.js

Blaming

Find the author of a perticular line (看看是谁写的烂代码):

1
2
3
4
5
$ git blame file1.js
9328b9b8 (Stone 2021-04-23 15:10:50 +0800 1) hello
49019215 (Stone 2021-04-23 15:19:09 +0800 2) A new line of code
49019215 (Stone 2021-04-23 15:19:09 +0800 3) Sky
49019215 (Stone 2021-04-23 15:19:09 +0800 4) Moon

加上-e看邮件:

1
2
3
4
5
$ git blame -e file1.js
9328b9b8 (<masaike@qq.com> 2021-04-23 15:10:50 +0800 1) hello
49019215 (<masaike@qq.com> 2021-04-23 15:19:09 +0800 2) A new line of code
49019215 (<masaike@qq.com> 2021-04-23 15:19:09 +0800 3) Sky
49019215 (<masaike@qq.com> 2021-04-23 15:19:09 +0800 4) Moon

加上-L看某几行:

1
2
3
$ git blame -e -L 1,2 file1.js
9328b9b8 (<masaike@qq.com> 2021-04-23 15:10:50 +0800 1) hello
49019215 (<masaike@qq.com> 2021-04-23 15:19:09 +0800 2) A new line of code

Tagging

给某次commit加个tag(实际上是个指向该commit的指针):

1
$ git tag v1.0 9328b9b
1
2
3
4
5
6
7
$ git log --oneline
4901921 (HEAD -> master) add 2 lines in file1.js
9328b9b (tag: v1.0) Restore file1.js //----------------------------
e2ebefd Remove file1.js
d7d9bfb refactor file1
d2acf0e Refactor code
9b82d1b Add file1.js

然后就可以用这个tag表示这个commit了,如:

1
$ git checkout v1.0

给当前commit(即MASTER指向的)起个详细的tag:

1
$ git tag -a v1.1 -m "My version 1.1"

查看目前的所有tags:

1
2
3
4
5
6
7
$ git tag
v1.0
v1.1

$ git tag -n
v1.0 Restore file1.js //-----和commit信息一样-----
v1.1 My version 1.1 //------tag信息------------

在此例中,v1.0是个lightweight tag,v1.1是个annotated tag。

annotated tag可以显示更详细的信息:加tag者的信息和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 show v1.1
tag v1.1
Tagger: Stone <masaike@qq.com>
Date: Fri Apr 23 15:27:43 2021 +0800

My version 1.1

commit 490192157cd80c704f4f85e2b2883685e3423f91 (HEAD -> master, tag: v1.1)
Author: Stone <masaike@qq.com>
Date: Fri Apr 23 15:19:09 2021 +0800

add 2 lines in file1.js

diff --git a/file1.js b/file1.js
index 24defb6..e27584b 100644
--- a/file1.js
+++ b/file1.js
@@ -1,2 +1,4 @@
hello
-A new line of code
\ No newline at end of file
+A new line of code
+Sky
+Moon
\ No newline at end of file

删除tag:

1
2
$ git tag -d v1.1
Deleted tag 'v1.1' (was c847e27)

Mosh的课程网址

初始化一个仓库

1
2
3
mkdir Moon
cd Moon
git init

在目录Moon下有一个.git隐藏文件夹,可以用以下命令看到:

1
ls -a

删除.git:

1
rm -rf .git

Git Workflow

本地directory→staging area (index)→repository

Staging Files

01

1
git status

查看,标红是因为新建的两个txt文件还没在staging area中。

使用下面的命令将两个文件的变化(新建/删除文件,文件内容更改)存入staging area中:

1
git add file1.txt file2.txt
1
git add *.txt
1
git add .

上面三种方式,(1)就add这两个文件;(2)add所有txt文件;(3)add所有文件

然后再查看,就绿了:

02

Committing Changes

提交到仓库

1
git commit -m "Initial commit."

Committing Best Practices

  • commit的时候文件的变化量应该适中(不要太多,也不要太少)
  • Commit often: 想要记录下某个状态时,就commit
  • Make it mean something

例如:对于两个改变 (1) Bug Fix, (2) Typo 最好不要一起commit,不然不知道改了啥,最好分开commit

Skipping the Staging Area

使用 -a 选项跳过git add

可以将两个选项 -a 和 -m 合并写成 -am

1
git commit -am "Fix the bug"

Remove Files

1
2
3
4
5
6
7
8
9
10
11
12
13
$ rm file1.txt
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: file1.txt

no changes added to commit (use "git add" and/or "git commit -a")

$ git ls-files
file1.txt
file2.txt

在本地目录下删除file1.txt,但是用git ls-files查看staging area中的文件会发现file1.txt还在其中。使用git add更新staging area然后commit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git add file1.txt

$ git ls-files
file2.txt

$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: file1.txt

$ git commit -m "Remove unused code"
[master b184de1] Remove unused code
1 file changed, 7 deletions(-)
delete mode 100644 file1.txt

除了上面的方法,也可以使用以下方法(git rm)一次性删除本地目录和staging area中的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git rm file2.txt
rm 'file2.txt'

$ git ls-files

$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: file2.txt

$ git commit -m "Remove unused code"
[master 3fd1e3e] Remove unused code
1 file changed, 1 deletion(-)
delete mode 100644 file2.txt

Renaming and Moving Files

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
$ mv file1.txt main.js

$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: file1.txt

Untracked files:
(use "git add <file>..." to include in what will be committed)
main.js

no changes added to commit (use "git add" and/or "git commit -a")

$ git add file1.txt

$ git add main.js
warning: LF will be replaced by CRLF in main.js.
The file will have its original line endings in your working directory

$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: file1.txt -> main.js

文件的更名涉及到两个变化,原文件file1.txt删除,以及新文件main.js的创建。

当然,也可以使用便捷的方式:

1
2
3
4
5
6
7
8
9
10
11
12
$ git mv file1.txt main.js

$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: file1.txt -> main.js

$ git commit -m "Refactor code"
[master 70800e8] Refactor code
1 file changed, 0 insertions(+), 0 deletions(-)
rename file1.txt => main.js (100%)

注意到0 insertions(+), 0 deletions(-),改名而已。

Ignoring Files

1
2
3
4
5
6
7
8
9
10
11
$ mkdir logs

$ echo hello > logs/dev.log

$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
logs/

nothing added to commit but untracked files present (use "git add" to track)

但是事实上我们并不想将log文件提交。方法:将 logs/ 写入.gitignore中

1
$ echo logs/ > .gitignore

用vscode打开文件.gitignore

1
$ code .gitignore

可以在 .gitignore 中的下一行写上*.log表示要忽略的文件类型。然后提交 .gitignore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore

nothing added to commit but untracked files present (use "git add" to track)

$ git add .gitignore
warning: LF will be replaced by CRLF in .gitignore.
The file will have its original line endings in your working directory

$ git commit -m "Add gitignore"
[master 9ed82f0] Add gitignore
1 file changed, 2 insertions(+)
create mode 100644 .gitignore

之后log文件的改变都会被staging area忽略了:

1
2
3
4
5
$ echo hello > logs/main.log

$ git status
On branch master
nothing to commit, working tree clean

可以看到nothing to commit。

​ 如果已经用 git add 不小心将 log 文件提交到 staging area 了(在写入.gitignore之前就交了),可以用命令删除 staging area 中的文件。先 help 一下看看用啥选项:

1
2
3
4
5
6
7
8
9
10
11
12
$ git rm -h
usage: git rm [<options>] [--] <file>...

-n, --dry-run dry run
-q, --quiet do not list removed files
--cached only remove from the index
-f, --force override the up-to-date check
-r allow recursive removal
--ignore-unmatch exit with a zero status even if nothing matched
--pathspec-from-file <file>
read pathspec from file
--pathspec-file-nul with --pathspec-from-file, pathspec elements are separated with NUL character
1
$ git rm --cached -r logs/

Short Status

git status 给出的信息易于理解,但是冗长:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ echo sky >> file1.js

$ echo sky > file2.js

$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: file1.js

Untracked files:
(use "git add <file>..." to include in what will be committed)
file2.js

no changes added to commit (use "git add" and/or "git commit -a")

加 -s 选项:

03

观察 M 的颜色变化,git add 之后 M 由红变绿,?? 变 A

1
2
M:表示modified
A:表示added

Viewing the Staged & Unstaged Changes

Motivation: 有时候在将changes上传到staging area或commit到repository之前,要先看看改了啥,改得对不对。

查看staged changes (在staging area中但是还没commit):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git diff --staged
diff --git a/file1.js b/file1.js
index ce01362..7633964 100644
--- a/file1.js
+++ b/file1.js
@@ -1 +1,3 @@
hello
+sky
+sun
diff --git a/file2.js b/file2.js
new file mode 100644
index 0000000..f5e95e7
--- /dev/null
+++ b/file2.js
@@ -0,0 +1 @@
+sky

查看unstaged changes(在本地目录下,不在staging area中):

1
$ git diff

比如在file1.js中将hello 改为 hello world:

04

a是在staging area中的copy,b是本地目录下的copy

@@ -1,3 +1,3 @@ 表示旧版本(staging area中的)从第一行开始,有3行(-1,3);新版本(本地目录下的)从第一行开始,有3行(+1,3)

标红的是旧版本内容,标绿的是新版本内容,白色的是未更改内容。

Visual Diff Tools

1
2
3
4
👉 KDiff3
👉 P4Merge
👉 WinMerge (Windows Only)
👉 VSCode

我们选用VSCode来可视化changes。配置:

1
2
3
4
5
$ git config --global diff.tool vscode

$ git config --global difftool.vscode.cmd "code --wait --diff $LOCAL $REMOTE"

$ git config --global -e

查看unstaged changes:

1
$ git difftool

06

查看staged changes(旧版本是前一次提交到仓库的版本,新版本是如今在staging area中的版本):

1
2
3
4
5
6
7
$ git difftool --staged

Viewing (1/2): 'file1.js'
Launch 'vscode' [Y/n]? y

Viewing (2/2): 'file2.js'
Launch 'vscode' [Y/n]? y

05

07

Viewing the History

用以下命令查看提交历史:

1
$ git log

查看简短描述 (最近更改显示在上面):

1
$ git log --oneline

换显示顺序 (最近更改显示在下面):

1
$ git log --oneline --reverse

Viewing a Commit

可以通过ID查看历史commit,如果ID前几位就唯一标识了某历史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
26
27
28
29
$ git log --oneline
fdb7fb6 (HEAD -> master) add data
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 Initial commit
3fd1e3e Remove unused code
b184de1 Remove unused code
dd03e4e Fix the bug
3b634ca add Lines
a787fca add line
568c0fc Initial commit.

$ git show 62416
commit 62416e293cdb5f7334ea6dc09a8fbb0314eedcc2
Author: Stone <masaike@qq.com>
Date: Thu Apr 22 11:32:51 2021 +0800

add files

diff --git a/file1.js b/file1.js
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file1.js
@@ -0,0 +1 @@
+hello

也可以用HEAD:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git show HEAD~1
commit 62416e293cdb5f7334ea6dc09a8fbb0314eedcc2
Author: Stone <masaike@qq.com>
Date: Thu Apr 22 11:32:51 2021 +0800

add files

diff --git a/file1.js b/file1.js
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file1.js
@@ -0,0 +1 @@
+hello

HEAD是当前commit的reference, HEAD~1是指从HEAD往后的一个commit,此例中就是ID为62416e2的commit。

也可以查看某历史 commit 中存于 repository 的内容(而不是changes):

1
2
3
4
5
$ git show HEAD~1:.gitignore
logs/
bin/
*.log
*.bin

也可以查看某历史 commit 中存于 repository 的所有文件:

1
2
3
4
5
6
$ git ls-tree HEAD
100644 blob 1cd4d4e7fe27c57dc89d6e68d1f535f00382e563 .gitignore
040000 tree 38051ae48accaf0025a257eaa7a3a328e1f0fe56 data
100644 blob 29887f938f21333a5eee9dfd0decb4b0a207d855 file1.js
100644 blob f5e95e70e524ec32d0200e10ba179ab4c5f13884 file2.js
100644 blob ce013625030ba8dba906f756967f9e9ca394464a main.js

格式:ID, 文件类型, 唯一标识符 (根据文件内容确定的), 文件名

data是个文件夹,所以类型为tree,里面有个文件main.txt。

根据文件唯一标识符查看文件内容:

1
2
3
4
5
6
7
$ git show 3805
tree 3805

main.txt

$ git show f5e95
sky

f5e95 是 file2.js 的唯一标识符的前几位,该文件的内容只有一行,就是 sky

1
2
3
4
5
总结:Git Objects
👉 Commits
👉 Blobs (Files)
👉 Trees (Directories)
👉 Tags

Unstaging Files

file1.js有两次changes,我们用 git add 将 file1.js 的第一个变化上传到 staging area中了,第二次 change 还在本地目录中。

我们不想同时 commit file1.js 和 file2.js 两个文件的变化。所以要 undo 这个 git add 操作:

1
$ git restore --staged file1.js

08

现在 file1.js 的两次变化都只在本地目录下而不在 staging area 中了。

原理:staging area 从目前的 repository 读取 file1.js 。因为两次变化都还没 commit ,repository 中的 file1.js 就是原始的 (两次变化之前的)。

如果要撤销 file2.js 的 git add 操作。repository 中还没有 file2.js, 所以撤销之后 file2.js 是一个untracked file (用??表示)。

09

Discarding Local Changes

1
$ git restore .

可以撤销本地目录下的改变。file2.js 依然是untracked file,因为本地目录要从staging area 读取上一次状态,而staging area 中没有 file2.js, 就不知道该做啥。所以要用

1
$ git clean

从你的工作目录中删除所有untracked,没有被管理过的文件。

参数说明:

1
2
3
4
5
6
7
8
9
10
11
12
$ git clean -h
usage: git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...

-q, --quiet do not print names of files removed
-n, --dry-run dry run
-f, --force force
-i, --interactive interactive cleaning
-d remove whole directories
-e, --exclude <pattern>
add <pattern> to ignore rules
-x remove ignored files, too
-X remove only ignored files

10

Restoring a File to an Earlier Version

下面的 demo 显示了删除了 file1.js 后用 git restore 从历史 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
26
27
28
29
30
31
32
33
$ git rm file1.js
rm 'file1.js'

$ git status -s
D file1.js

$ git commit -m "Delete file1.js"
[master 78daaab] Delete file1.js
1 file changed, 1 deletion(-)
delete mode 100644 file1.js

$ git log --oneline
78daaab (HEAD -> master) Delete file1.js
133904b commit file1.ls
0f62ff0 remove all js files
fdb7fb6 add data
62416e2 add files
fde6777 modified
f264740 modified
9ed82f0 Add gitignore
70800e8 Refactor code
d6f4cc6 Initial commit
3fd1e3e Remove unused code
b184de1 Remove unused code
dd03e4e Fix the bug
3b634ca add Lines
a787fca add line
568c0fc Initial commit.

$ git restore --source=HEAD~1 file1.js

$ git status -s
?? file1.js

数组与链表

数据结构的存储方式只有两种:数组(顺序存储)和链表(链式存储)

那些多样化的数据结构,究其源头,都是在链表或者数组上的特殊操作,API 不同而已

数组由于是紧凑连续存储,可以随机访问,通过索引快速找到对应元素,而且相对节约存储空间。但正因为连续存储,内存空间必须一次性分配够,所以说数组如果要扩容,需要重新分配一块更大的空间,再把数据全部复制过去,时间复杂度 O(N);而且你如果想在数组中间进行插入和删除,每次必须搬移后面的所有数据以保持连续,时间复杂度 O(N)。

链表因为元素不连续,而是靠指针指向下一个元素的位置,所以不存在数组的扩容问题;如果知道某一元素的前驱和后驱,操作指针即可删除该元素或者插入新元素,时间复杂度 O(1)。但是正因为存储空间不连续,你无法根据一个索引算出对应元素的地址,所以不能随机访问;而且由于每个元素必须存储指向前后元素位置的指针,会消耗相对更多的储存空间。

数据结构基本操作

遍历 + 访问:增删查改(在不同的应用场景,尽可能高效地增删查改

方式:线性-for/while;非线性-递归

任务:从输入的视频中直接预测raw声音信号

方法:编码-解码架构,即视频编码器+音频生成器(分层RNN)

建模:条件生成问题。训练一个有条件的生成模型以从输入的视频中合成raw声音波形。

估计条件概率:

$x_{1},x_{2},…,x_{m}$是输入视频帧的表示;$y_{1},y_{2},…,y_{n}$是输出声音波形的值(取值为0~255的整数序列)。原始波形样本是范围从-1到1的实数值,文中重新缩放和线性量化它们到256个bins。通常$m<<n$,因为音频的采样率远高于视频的采样率。

1

Sound Generator

采用的采样频率为:16kHz

问题:序列长度很长

解决方法:选用SampleRNN为声音生成器(原因:其从粗到细的结构使模型能够生成非常长的序列,每一层的循环结构捕捉到遥远样本之间的依赖关系。)

具体:1)细节层是多层感知机(MLP),获取下一个粗糙层(上层)的输出和前面的$k$个样本,以生成一个新样本。在训练过程中,波形样本(实数值,从-1到1)被线性量化到从0到255的整数范围,可以将最细的层的MLP看成是256-类分类器,在每个timestep预测得到一个样本(然后映射回实值,获得最终波形)。

2)粗糙层可以是GRU, LSTM或其他的RNN变体。包含多个波形样本(图中为2个)的节点意味着该层基于前一个timestep以及更粗层的预测,在每个timestep共同预测多个样本。

Video Encoder

Frame-to-frame method

$f_{i}$和$x_{i}$分别是视频的第$i$帧和该帧的表征。$V(.)$是在ImageNet上预先训练的VGG19网络的fc6特征提取操作,$x_{i}$是一个4096维的向量。在该模型中,将帧表征与声音生成器最粗层RNN的节点(样本)统一连接,对视觉信息进行编码,如图3(b)所示(内容以绿色虚线框表示)。

视频与音频采样率不同的问题解决:对于每个$x_{i}$,重复$s$次,其中

只将视觉特征提供给SampleRNN的最粗糙层,因为这一层很重要,因为它指导所有更细的层的生成以及提高计算效率。

Sequence-to-sequence method

提取VGG19网络的fc6特征作为每一帧的表征,然后用RNN处理作为视频编码器,使用视频编码器的最后一个隐藏状态初始化声音发生器的最粗层RNN的隐藏状态,然后开始声音生成。此时声音生成任务变为:

$H$表示视频编码RNN的最后一个隐藏状态或等效的声音发生器最粗糙层RNN的初始隐藏状态。

视频与音频采样率不同的问题解决:不像上面提到的基于帧的模型中明确地强制视频帧和波形样本之间的对齐。在这个序列到序列模型中,我们期望模型通过编码和解码来学习这两种模式之间的对齐。

Flow-based method

Motiv:视觉领域的运动信号,虽然有时很微小,但对于合成真实且同步良好的声音是至关重要的。

方案:增加一个基于光流的深度特征以明确捕获运动信号。与序列到序列方法不同之处为

$o_{i}$是第$i$帧的光流,$F(.)$是提取基于光流的深度特征的函数(非学习得到)。

Visual to Sound: Generating Natural Sound for Videos in the Wild

Sound Representation

计算声音特征的方法:将声波$w(t)$分解为子带包络$s_{n}(t)$(将声波滤波然后应用非线性)。1)在等效矩形带宽(ERB)尺度上应用40个带通滤波器$f_{n}$(加上一个低通和高通滤波器),并取响应的希尔伯特包络线;2)将包络下采到90Hz(约为3样本/帧)并压缩。

$H$是希尔伯特变换,$D$表示下采样,压缩常数$c=0.3$。由此产生的表示被称为耳蜗图(cochleagram)。

2

一般来说,撞击声如何捕捉材料的属性?为了从经验上评测这一点,文章使用子带包络作为特征向量,训练了一个线性支持向量机来预测数据库中的声音由哪个材料发出。文章对训练集重新采样,使每个类包含相同数量的撞击声(每类260个)。最终得到的材料分类器具有45.8% (chance = 5.9%)的分类平均精度(即每类精度值的平均值),其混淆矩阵如图3(b)所示。这些结果表明,撞击声传达了有关材料的重要信息,因此,如果一个算法能够学习从图像中准确预测这些声音,它就会有关于材料类别的隐含知识。

Predicting visually indicated sounds

将此任务建模为一个回归问题,目标是将一个视频帧序列映射为一个声频特征序列。使用的模型是RNN,以颜色和运动(motion)信息作为输入,预测出声频波形的子带包络。最后,从声音特征中产生波形。

1

Regressing sound features

输入图像序列:$I_{1}, I_{2}, …, I_{N}$

输出声音特征序列:$\mathop{s_{1}}\limits ^{\rightarrow}, \mathop{s_{2}}\limits ^{\rightarrow}, \mathop{s_{T}}\limits ^{\rightarrow}, where \mathop{s_{t}}\limits ^{\rightarrow}\in \mathbb{R}^{42}$

这些声音特征对应于图4中所示的耳蜗块。文章使用循环神经网络(RNN)来解决这个回归问题,它将卷积神经网络(CNN)计算的图像特征作为输入。

图像表示:如何表示运动(motion)信息?计算每一帧的spacetime图像,即三个通道是上一帧、当前帧和下一帧灰度版本的图像。对于每一帧$t$,通过拼接帧$t$的spacetime图像和第1帧的颜色图像的CNN特征来构建输入特征向量$x_{t}$,即

文章中训练的两种方式:1)从零开始初始化CNN,然后和RNN一起训练它;2)用一个为ImageNet分类训练的网络的权值初始化CNN。当使用预训练时,从卷积层中预计算特征,并仅对完全连接的层进行微调。

声音预测模型:使用基于LSTM的RNN模型。为了补偿视频和音频采样率之间的差异,文章中复制每个CNN特征向量$k$次(文章中使用$k=3$)

由此得到与声音特征序列等长的CNN特征序列$x_{1}, x_{2}, …, x_{N}$。在RNN的每个timestep,文章中使用当前图像特征向量$x_{t}$来更新隐藏变量$h_{t}$,然后通过一个仿射变换得到声音特征。为了使学习问题更简单,文中使用PCA在每个时间步长的42维特征向量投影到10维空间,然后预测这个低维向量。对网络进行评估时,反求PCA变换以获得声音特征。

文章使用随机梯度下降以Caffe共同训练RNN和CNN。它有助于收敛去除dropout和剪辑大的梯度。当从头开始训练时,文中通过对视频进行裁剪和镜像转换来增强数据。文中也使用多个LSTM层(数量取决于任务)。

Generating a waveform

问题:如何从声音特征中产生声波?

  1. 简单参数综合法:迭代地将子带包络加到白噪声样本上(文中只使用了一次迭代)。这种方法对于检查音频特征所捕获的信息非常有用,因为它代表了从特征到声音的直接转换。
  2. 对于产生对于人耳似是而非的声音的任务,在从特征到波形的转换过程中,先施加一个强的自然声音是更有效的。因此使用基于实例的合成方法,该方法将声音特征的窗口捕捉到训练集中最接近的样本上。文中通过拼接预测的声音特征$\mathop{s_{1}}\limits ^{\rightarrow}, \mathop{s_{2}}\limits ^{\rightarrow}, …, \mathop{s_{T}}\limits ^{\rightarrow}$(或它们的一个子序列)形成一个查询向量,在$L_{1}$距离测量的训练集中寻找其最近的邻居,并传递相应的波形。

项目网址

简介

现象:声音到达物体会在物体表面引起微小振动。

做法:使用物体的高速视频提取微小振动,部分恢复(造成振动的)声音。

具体:1)从一系列不同特性的物体的高速连续镜头中恢复声音;

​ 2)使用真实和仿真数据来评测那些影响了可视化地恢复声音的因素;

​ 3)声音恢复质量评价指标:可理解性(intelligibility)、SNR;直接对比输入和恢复出来的信号;

​ 4)探索如何利用普通用户相机的卷帘快门(rolling shutter)从而从标准帧率视频中恢复出声音;使用所提方法的空间分辨率来可视化声音引起的振动是如何随着物体表面变化的,以之恢复物体的振动模式。

Keywords:远程声音采集;视频中的声音;可视化声学。

Introduction

描述:声音到达某物品 → 1)物品表面跟随周围介质移动 OR 2)根据其振动模式发生形变。

应用场景:声音引起物体振动的现象被用于远程声音采集,并在监视和安防方面有重要应用,如在远处窃听谈话。远程声音采集的现有方法本质上是积极(active)的,需要将一个激光束或pattern投射到振动表面。

本文观察:只需要物体的高速视频,声音引起的物体振动通常能够产生足够的视觉信号来部分恢复出该声音。

本文贡献:提出一个消极的从视频中恢复声音信号的方法。视觉上检测微小物体振动→将振动转回音频信号(使得日常物品变成潜藏的麦克风)。做法:1)使用高速摄像机将物体视频;2)在一个Complex steerable pyramid (CSP)(建立在视频上)的维度上提取局部运动信号;3)这些局部信号被对齐并取平均,得到一个单一的一维运动信号,该信号捕捉对象随时间的全局运动;4)进一步滤波和去噪,得到恢复出来的声音。

对比积极方法:恢复效果不如积极方法;但是优点有 1)对于纹理物体和光照良好的场景不需要提供积极照明;2)除了高速摄像机外无需额外的传感器或检测模块;3)无需回射或反射振动表面(区别于激光麦克风);4)没有对相对于相机的表面方向施加明显的约束;5)产生了一个声音的空间测量,可用于分析物品中声音引起的形变。

讨论:虽然声音可以穿透大多数物质,但并不是所有的物体和材料都能很好地进行视觉声音恢复。声波在材料中的传播取决于多种因素,如材料的密度和可压缩性,以及物体的形状。文章进行对照实验,测量了不同物体和材料对已知和未知声音的反应,并评估文章所提技术对于从高速视频中恢复声音的能力。

传统麦克风的工作原理是将内部膜片的运动转化为电信号。膜片的设计使其在声压下容易移动,因此它的运动可以被记录下来并解释为音频。激光麦克风的工作原理与此类似,但它测量的是一个遥远物体的运动,本质上是将物体作为一个外部膜片。这是通过记录激光对物体表面的反射来实现的。最基本的激光麦克风记录反射激光的相位,以激光波长作模得到物体的距离。激光多普勒测振仪(LDV)通过测量反射激光的多普勒频移来确定反射面速度,从而解决相位包裹的模糊性。这两种类型的激光麦克风都可以从很远的距离恢复高质量的音频,但依赖于激光和接收器相对于具有适当反射比的地面的精确定位。

Zalevsky等人通过使用失焦高速相机来记录反射激光散斑模式的变化,解决了其中的一些局限性。他们的工作允许接收器的定位有更大的灵活性,但仍然依赖于记录反射激光。相比之下,本文的技术不依赖于主动照明。

本文方法依赖于从视频中提取极其细微运动的能力,因而也与对这些运动进行放大和可视化的工作有关。这些工作侧重于小运动的可视化,而本文侧重于测量这些运动并利用它们来恢复声音。本文工作中使用的局部运动信号来自Simoncelli等人提出的Complex steerable pyramid (CSP)中的相位变化,因为这些变化被证明非常适合于视频中微小动作的恢复。然而,也有可能使用其他技术来计算局部运动信号。例如,经典的光流和点相关方法在之前的视觉振动传感工作中被成功地使用。由于本文方法的输出是单个振动物体的一维运动信号,因而能够对输入视频中的所有像素进行平均,并在千分之一像素的数量级上处理极其微小的运动。

Recovering Sound from Video

1

输入:物体的高速视频(1kHz~20kHz)$V(x,y,t)$

假设:物体和相机的相对运动是由声音信号$s(t)$引起的振动主导的

目标:从$V$得到$s(t)$

步骤:1)根据不同的方向$θ$和尺度$r$将$V$分解成多个空间子带;

​ 2)计算每个像素、方向和尺度上的局部运动信号;通过一系列的平均和对齐操作将这些运动信号组合起来,为物体产生一个单一的全局运动信号;

​ 3)对物体的运动信号使用音频去噪和滤波技术,以获得恢复出来的声音。

Computing Local Motion Signals

使用$V$的CSP表示中的相位变化来计算局部运动。

CSP:将$V$中的每一帧根据不同的方向和尺度分解成复数子带的滤波器组。该变换的基础方程是带尺度和方向的兼具余弦和正弦相位的Gabor-like小波。每一对类余弦和类正弦滤波器都可以用来分离局部小波的振幅和它们的相位。具体地说,每个尺度$r$和方位$θ$是一个复数图像,可以用幅值$A$和相位$φ$表示为:

取这个等式中计算的局部相位$φ$,从参考帧$t_{0}$(通常是视频的第一帧)的局部相位中减去它们,计算相位变化

对于较小的运动,这些相位变化近似正比于图像结构在相应方向和尺度上的位移。

Computing the Global Motion Signal

对于CSP的每个尺度$r$和方位$θ$求局部运动信号的加权平均:

求加权平均的原因:局部相位在没有太多纹理的区域是模糊的,导致这些区域的运动信号是有噪声的。CSP的振幅A给出了纹理强度的度量,因此可以通过(平方)振幅来加权每个局部信号。

对齐:

得到全局运动信号:

归一化到[-1,1]的范围。

Denoising

目标:改善全局运动信号的SNR

观察到的现象:低频的高能量噪声通常与音频不一致

方法:应用高通Butterworth滤波器(截断频率为20-100Hz)

滤除加性噪声:目标是accuracy,用spectral subtraction;目标是可理解性,用感知驱动的语音增强算法(通过计算一个贝叶斯最优估计去噪信号的成本函数,考虑到人类对语音的感知)。本文的结果是自动使用二者之一的算法来去噪的。

恢复信号的不同频率可能会被记录对象不同地调制。第4.3节将展示如何使用已知测试信号来描述一个物体如何衰减不同频率,然后在新的视频中使用该信息来均衡从同一物体(或类似物体)中恢复的未知信号。

Experiments

第一组实验测试了可以从不同物体上恢复的频率范围。通过扬声器播放线性渐变频率的声音信号,然后观察哪些频率可以通过本文技术恢复。第二组实验集中在从视频中恢复人类语言。这些实验使用了来自TIMIT数据集的几个标准语音示例,以及通过扬声器播放的人类受试者的现场语音(扬声器被一个会说话的人替换)。

Sound Recovery from Different Objects/Materials

2

在几乎所有的结果中,恢复的信号在较高的频率中是较弱的。这是意料之中的,因为更高的频率产生更小的位移,并且被大多数材料严重衰减。然而,较高频率的功率下降不是单调的,可能是由于振动模式的刺激。毫不奇怪,较轻的物体更容易移动,比惰性的物体更容易支持更高频率的恢复。

Speech Recovery

评测指标:

(1) 评测accuracy: Segmental Signal-to-Noise Ratio (SSNR) 随时间的平均局部信噪比;

(2) 评测intelligibility: perceptually-based metric

(3) 评测恢复质量:Log Likelihood Ratio (LLR),评测恢复信号的谱形状与原始干净信号的谱形状有多接近

3

更高的帧率导致曝光时间减少,因此图像噪声更多,这就是为什么20,000FPS结果图比2200Hz时的结果噪声更大

4

VM和LDV结果相近,而LDV需要积极照明(必须在物体上粘上一条反反射胶带以便激光从物体上反射回来回到振动计上)

Transfer Functions and Equalization

可以使用第4.1节中的斜坡信号来表征物体的(可视的)频率响应,以提高从该物体的新观测中恢复的信号质量。理论上。如果认为物体是线性系统,可以使用维纳反卷积估计与该系统相关联的复数传递函数,并且传递函数可以用来以一种最优的方式(在均方误差意义上的)解卷积新的观测信号。然而,在实践中,这种方法很容易受到噪声和非线性artifacts的影响。因此,本文描述了一种更简单的方法,首先使用训练实例(线性斜坡)的短时间傅里叶变换在粗尺度上计算频率传递系数,然后使用这些传递系数使新的观测信号相等。

转移系数是从一对输入/输出的信号的短时功率谱中提取出来的。每个系数对应于观察到的训练信号的短时功率谱的一个频率,并作为随时间变化的频率幅值的加权平均被计算。每一时刻的权值由对准的输入训练信号的短时功率谱给出。由于输入信号一次只包含一个频率,这个加权方案忽略了图2(b)中所示的倍频等非线性artifacts。

一旦有了传输系数,我们就可以用它们来平衡新的信号。有很多方法可以做到这一点。将增益应用于新信号短时功率谱的频率上,然后在时域重新合成信号。应用于每个频率的增益与其相应的传递系数的倒数成正比,该系数上升到某个指数k。

表2显示了应用从薯片袋导出的均衡器到从同一物体恢复的语音序列的结果。在没有噪声的情况下,k设为1,但广谱噪声压缩了估计的传递系数的范围。使用更大的k可以弥补这一点。在其中一个女性语音示例上手动调整k值,然后将得到的均衡器应用于所有六个语音示例。由于这种均衡是为了提高恢复信号的可信度而不是语音的可理解性,因此使用谱减法来去除噪声。

5

注意,校准和均衡是可选的。特别是,本文中除表2之外的所有结果都假定不预先知道被记录物体的频率响应。

Analysis

Object response (A): 物体相应声音并移动,将空气压力转化为表面位移。

Processing (B): 将录制的视频转换成恢复的声音。

Object response (A)

6

图7(b):300Hz纯音测试,大多数物体的运动在声压(音量)上近似呈线性。结论:A可建模为LTI系统。

图7(c):斜坡信号(20Hz到2200Hz)测试,将A建模为LTI系统,用这个斜坡信号来恢复系统的脉冲响应。这是通过使用维纳反卷积用已知的输入对观察到的斜坡信号(这一次是由LDV记录的)进行反卷积来实现的。图7 (c)显示了从恢复的脉冲响应中得到的频率响应。从这张图中可以看到,大多数物体在低频率的响应比高频率的响应更强(正如预期的那样),但是这种趋势不是单调的。

$\boldsymbol{A}(\omega)$: 转移函数

$S(\omega)$: 声谱

$D_{mm}(\omega)$: 运动谱

项目网址