git 学习笔记 3
start at 2023/01/03.

修改提交记录

程序员心情不好,不小心在Commit里骂了客户,要怎么消掉?

总共有四种方法:

  1. 把整个.git文件删了!(不建议)
  2. 使用git rebase来改动历史记录
  3. git reset命令把commit删除,整理之后在重新commit
  4. 使用--amend参数改动最后一次的commit

这里采用第四种方法,第二种和第三种方法会在后面陆续介绍,至于第一种方法,大多数情况都不会使用,不要轻易使用这种方法,造成的影响不可忽略

使用 --amend 参数进行Commit

我们来看一下原来的 Commit 记录

$ git log --oneline
431cb79 (HEAD -> master) WTF
af3323f first commit

我们发现最后一条 Commit 信息的 WTF 好像不是特别友好,以免客户困扰,我们来修改一下

$ git commit --amend -m "Win Till Fail"
[master e41a824] Win Till Fail
 Date: Wed Jan 4 00:36:12 2023 +0800
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 gogo.cpp
$ git log --oneline
e41a824 (HEAD -> master) Win Till Fail
af3323f first commit

这样我们就把 WTF 改成 Win Till Fail了!

你如果细心一点会发现前后两次 Commit 的SHA-1的值发生了变化:431cb79 -> e41a824,两次Commit 的时间和文件内容都是相同的,看起来 Commit 并没有变化,但是Commit的信息不同导致生成的确实是新生成的Commit,其实这一点我在上一篇笔记里的最后也提到了,但当时没有仔细看,以为Commit的时间会变化,事实上时间是不变的,所以甚至如果你的Commit信息相同,SHA-1 的值也会发生变化

可以改动更早的记录吗

可以,要用到 rebase 命令来处理,以为 --amend 参数只能处理最后的 Commit,rebase命令后面会讲

⚠ 虽然只是修改了信息,但也算是改动了一次历史记录,所以尽量不要在已经 Push 出去之后再改动,否则会给别人造成困扰

追加文件到最后一次提交

刚刚完成的 Commit,但是发现还有一个文件忘记 add 了,又不想为了这个文件再 commit 一次

如果你愿意的话当然可以再commit一次,这样就能少记一点东西了,一般有两种方法来完成这件事情

  1. 使用 git reset 命令把最后一次的Commit删除,加入新文件之后再重新Commit
  2. 使用 --amend 参数进行 Commit

按照这个书的节奏,我们先介绍第二种方法,第一种会在后面介绍

下面这个例子我们信息里填的是 add 1.c and 2.c 但是我们的 2.c 忘记 add

$ git commit -m "add 1.c and 2.c"
[master 26d2497] add 1.c and 2.c
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 1.c
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        2.c

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

接下来的流程其实和修改上一次 Commit 的信息很相似,甚至和一般的 commit 流程很相似

$ git add 2.c
$ git commit --amend --no-edit
[master c1bb84f] add 1.c and 2.c
 Date: Wed Jan 4 00:55:15 2023 +0800
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 1.c
 create mode 100644 2.c
$ git status
On branch master
nothing to commit, working tree clean

--no-edit 表示不修改提交的信息,当然如果你同时还需要修改提交的信息也是可以的,-m "xxx"就行了,我下面是不想再打前面的字,所以让git帮我打开Vim编辑

$ touch 3.c
$ git add 3.c
$ git commit --amend
[master a31b723] add 1.c and 2.c and 3.c
 Date: Wed Jan 4 00:55:15 2023 +0800
 3 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 1.c
 create mode 100644 2.c
 create mode 100644 3.c
$ git log --oneline
a31b723 (HEAD -> master) add 1.c and 2.c and 3.c
2578146 Win Till Fail
af3323f first commit

体会到了 --amend 的方便了吧,但是还是要commit 前谨慎一点,一旦Push 出去了就很难收回来了

⚠ 同样的,这是对历史记录的改动,所以尽量不要在已经 Push 出去之后再改动,否则会给别人造成困扰

提交空文件夹

平时使用的时候你可能会遇到新建了一个文件夹,git 不帮你提交的情况

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

这是因为 git 在计算、产生对象的时候是根据 文件的内容 来计算的,所以只是加入文件夹不加文件的话,git 认为工作区并没有发生变化,Git是无法提交空文件夹的

那如果我想要提交怎么办呢?很简单,只需要在那个文件夹里随便放个空的文件就可以了,建议用隐藏文件,这样 git 就知道这里有一个文件夹了

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

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

之后的流程就和一般的 commit 流程一样了

忽略部分文件

一般我们不希望一些文件跟着源文件一起交上去,比如密码配置文件之类的

你应该也知道这里要说的是什么知识了,git 提供 .gitignore 文件,你可以在这个文件里设置规则,只要匹配到的文件都会被 git 自动忽略掉,如果项目里没有这个文件,那么就需要我们手动添加它

$ touch .gitignore
# 或者直接写一条规则进去
$ echo "iAmADir/*" > .gitignore

这样 git 就会把 iAmADir 里的所有文件都忽略掉了

$ 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)

下面是一些规则参考,并不是正则表达式

# 忽略 secret.yml 文件
secret.yml

# 忽略 config 目录下的 datebase.yml 文件
config/datebase.yml

# 忽略掉 db 目录下的所有后缀是 .sqlite3 的文件
db/*.sqlite3

# 忽略掉所有后缀是 .tmp 的文件
*.tmp

# 你甚至可以忽略掉 .gitignore 文件 但一般不会怎么做
.gitignore

只要.gitignore文件存在,本地仓库就会按照这个规则来忽略文件,哪怕你忽略了.gitignore 或者没有上传到服务器上,但一般还是建议把这个文件 commit 进项目里上传,这样所有开发的人都可以共享到这个规则

如果你不知道自己用的工具或者语言通常会忽略那些文件,那么你可以看这里,上面整理了一份常见的 .gitignore 文件

可以忽略这个忽略添加文件吗?

是可以的,你只需要用 -f 强制 add 就行了

$ git add -f ./iAmADir/.keep
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   iAmADir/.keep

为什么忽略了还会显示?

那是因为 .gitignore 的作用只会对规则适用后的文件产生作用,如果你的那个文件在指定规则前就已经被 git 接手,那么之后制订的规则就对它无效,那么如何把之前逃过的文件都移除呢,只需要用git rm --cached 就可以他们都移除了

清除忽略的文件

只需要在 git clean 后面加上 -X 参数就可以了

$ git clean -X
# f是强制删除的意思
$ git clean -fX

查看特定文件的提交记录

git log 除了能看整个项目的提交记录,还能对特定文件的提交记录就行查看,只需要在 git log 后面跟上那个文件名就可以了

$ git log 1.c
commit c9e9a479b62176eb3832d23869a5c3b507841a9e (HEAD -> master)
Author: lol <233@qq.com>
Date:   Wed Jan 4 01:50:20 2023 +0800

    modfiy 1.c again

commit 3061d40cd908018feef6c43f0712fe28770f1238
Author: lol <233@qq.com>
Date:   Wed Jan 4 01:49:38 2023 +0800

    modfiy 1.c

commit a31b723100193e714dc809f0a8c95683b75daf03
Author: lol <233@qq.com>
Date:   Wed Jan 4 00:55:15 2023 +0800

    add 1.c and 2.c and 3.c

加上参数 -p 可以看到每次提交具体干了什么事情

$ git log -p 1.c
commit c9e9a479b62176eb3832d23869a5c3b507841a9e (HEAD -> master)
Author: lol <233@qq.com>
Date:   Wed Jan 4 01:50:20 2023 +0800

    modfiy 1.c again

diff --git a/1.c b/1.c
index 4b5fa63..7082e89 100644
--- a/1.c
+++ b/1.c
@@ -1 +1,3 @@
-hello, world
+print(233)
+hello,world!
+

commit 3061d40cd908018feef6c43f0712fe28770f1238
Author: lol <233@qq.com>
Date:   Wed Jan 4 01:49:38 2023 +0800

    modfiy 1.c

diff --git a/1.c b/1.c
index e69de29..4b5fa63 100644
--- a/1.c
+++ b/1.c
@@ -0,0 +1 @@
+hello, world

commit a31b723100193e714dc809f0a8c95683b75daf03
Author: lol <233@qq.com>
Date:   Wed Jan 4 00:55:15 2023 +0800

    add 1.c and 2.c and 3.c

diff --git a/1.c b/1.c
new file mode 100644
index 0000000..e69de29

格式可以有点复杂,+ 号表示新增,- 号表示删除

谁写的这行代码

网站怎么挂了?!这行代码是谁写的!

经常会出现这种情况,git 提供指令 git blame 帮你找出这个人

blame

v. 责怪,归咎于;怨,怪

n. 责备,过错,罪责

取名取得很对

$ git blame 1.c
c9e9a479 (lol 2023-01-04 01:50:20 +0800 1) print(233)
c9e9a479 (lol 2023-01-04 01:50:20 +0800 2) hello,world!
c9e9a479 (lol 2023-01-04 01:50:20 +0800 3)

可以看到某行代码是那次提交加上的,人名,时间,代码全都有,背锅的人想逃都逃不掉

如果要查的文件太大了,可以用参数 -L 只查指定几行

$ git blame -L 1,1 1.c
c9e9a479 (lol 2023-01-04 01:50:20 +0800 1) print(233)
$ git blame -L 2,3 1.c
c9e9a479 (lol 2023-01-04 01:50:20 +0800 2) hello,world!
c9e9a479 (lol 2023-01-04 01:50:20 +0800 3)

很多时候,git blame 抓到的 “凶手” 大多数都是自己

不小心把文件或者目录删除了

rm 是一个强大而恐怖的命令,使用的时候非常小心,不要在状态不好的时候使用,会造成不可挽回的损失,可以连 Git 都救不回来

人总有犯错的时候,不管什么原因,如果是在 git 中不小心把文件或者目录给删除了,都是可以挽救回来的,这也是使用版本管理系统最主要的原因之一,我们故意用rm 删点文件

$ rm *.c
$ ls -al
total 4
drwxr-xr-x 4 paradox paradox 160  1月 4日 02:18 .
drwxrwxrwt 9 root    root    200  1月 4日 00:35 ..
-rw-r--r-- 1 paradox paradox   0  1月 4日 00:35 a.cpp
-rw-r--r-- 1 paradox paradox   0  1月 4日 00:35 f.cpp
drwxr-xr-x 8 paradox paradox 280  1月 4日 02:18 .git
-rw-r--r-- 1 paradox paradox  21  1月 4日 01:32 .gitignore
-rw-r--r-- 1 paradox paradox   0  1月 4日 00:35 gogo.cpp
drwxr-xr-x 2 paradox paradox  60  1月 4日 01:18 iAmADir
$ 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:    1.c
        deleted:    2.c
        deleted:    3.c

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

我们发现三个 c 的文件已经被我们删除了,这时候如果我们想要把 1.c 给挽救回来,就可以用 git checkout 命令

$ git checkout 1.c
Updated 1 path from the index
$ ls
1.c  a.cpp  f.cpp  gogo.cpp  iAmADir

我们发现文件回来了!

如果我们想让所有文件都回来只需要用 . 来指定全部文件

$ git checkout .
Updated 2 paths from the index
$ ls
1.c  2.c  3.c  a.cpp  f.cpp  gogo.cpp  iAmADir

全都回来了!

这个技巧不仅可以将删除的文件挽救回来,当改动某个文件后悔的时候,也可以用来还原那个文件

echo "lol" > 2.c
$ cat 2.c
lol
$ git checkout 2.c
Updated 1 path from the index
$ cat 2.c
$

我们发现 2.c 已经变回原来的空文件了

⚠ 并不是所有情况下都能恢复被删除的文件,因为整个 git 的记录都是放在根目录下的 .git 文件夹里,如果这个文件夹被删除了,就意味着所有的历史记录都被删除了,那么删除的文件就无法恢复了,所以 .git 文件夹是不能乱删的

git是怎么挽救文件的

git checkout 在后面关于分支的内容还会出现,它可以用来切换分支,但如果后面跟的是文件名或者路径的时候,git 就不会切换分支,而是把文件从 .git 目录里面复制一份到当前的工作目录

更精确地说,这个命令会把暂存区中的内容拿来覆盖工作目录中的内容

$ echo "hello" > 2.c
$ git add 2.c
$ echo "45" > 2.c
$ cat 2.c
45
$ git checkout 2.c
Updated 1 path from the index
$ cat 2.c
hello

我们并没有提交 echo "hello" > 2.c 的这个改动,但是 checkout 之后的 2.c 的数据是 hello 而不是空值,所以 checkout默认把暂存区的文件拿过来覆盖的,而不是仓库

checkout 是可以加参数的,下面的命令能让两个版本以上的文件来覆盖

但是注意,这同时也会更新暂存区

$ git checkout HEAD~2 2.c
Updated 1 path from 840e90e
$ cat 2.c
$

现在只做到了这本书的第59页,总共有200多页,进度只有百分之二十五吧

但是后面的内容应该会讲的很快

学下来收获颇丰,学完之后做一个命令的整理,把常用的命令都提出来

到时候还可以再学习一下 lazygit ,做一个教程介绍,毕竟懒点好

2023/01/03
> CLICK TO back <