git

0. 掌握git的关键点

0.1 3个区

工作区:.git 的父目录
暂存区:.git/index记录文件元信息 和 .git/objects使用blob记录文件内容
版本库: .git/objects使用commit tree组织blob

0.2 暂存区

它有两部分:
.git/index : 快照概述,包括文件的指纹,文件大小,文件名...
.git/objects : blob

0.2.1 git add

git add 完成对工作区做快照,如工作区有两个文件,两个目录 readme, src/main.c, obj ,
添加到暂存区后,查看 .git/index

$ git ls-files --stage --debug
100644 c200906efd24ec5e783bee7f23b5d7c941b0c12c 0       readme
  ctime: 1679136357:783754200
  mtime: 1679136373:750504700
  dev: 0        ino: 0
  uid: 0        gid: 0
  size: 4       flags: 0
100644(普通文件,文件权限0644) 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c(内容的SHA1值) 0       src/main.c
  ctime: 1679136363:909626900
  mtime: 1679136369:134322800
  dev: 0        ino: 0
  uid: 0        gid: 0
  size: 4       flags: 0

查看 .git/objects,已经创建了一个blob对象,内容为 readme,src/main.c 的内容(两个文件内容一样),

$ ls .git/objects/
58/  info/  pack/

$ git cat-file -t 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
blob

$ git cat-file -p 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
111

可见暂存区会存放文件内容,只不过blob没有关联的tree和commit,
并且没有子文件的目录obj并没有被记录。

0.2.2 git status

运行 git status 时,会对工作区的文件求SHA1值,与 index 中记录比较,如果SHA1值不同,或文件新增或删除,则报有改变
比如
readme文件内容为 111,将其修改成 222,运行git status后,会报readme被修改,
若改readme为 111,再次运行 git status后,不会报readme被修改。

结论: git status 是比较 文件内容的SHA1值,不在乎文件的元属性修改时间改变。

0.3 版本库

版本库由4个对象构成 blob tree tag commit
他们都是SHA1作为自己的名称,
运行 git commit -m "first commit",git 根据暂存库的描述,在objects下创建commit和 tree对象,并将其和 blob对象关联。

在.git/objects下可以发现多个无明确意义的目录,其下面是一个或多个名称很怪的文件

$ ls .git/objects/
24/  58/  75/  7f/  c2/  info/  pack/
$ ls .git/objects/24/
da758a92d93a001ad8425e2cfd97967b1f5782

这些文件就是对象,
使用 git cat-file -t [SHA1]可以查看对象的类型,
使用 git cat-file -p [SHA1]可以查看对象的内容
SHA1值由目录加文件名拼接得到

如下发现此对象是 commit 对象,且内容包括一个 tree对象和提交信息注释。

$ git cat-file -t 24da758a92d93a001ad8425e2cfd97967b1f5782
commit

$ git cat-file -p 24da758a92d93a001ad8425e2cfd97967b1f5782
tree 7f5e0374b464fab0ad73ee21a2c98934cce8c9bc
author yangxr <yangxr1995@gmail.com> 1679141339 +0800
committer yangxr <yangxr1995@gmail.com> 1679141339 +0800

first commit

使用这个tree对象的SHA1,继续追踪,发现tree的内容为blob对象和另一个tree对象

$ git cat-file -p 7f5e0374b464fab0ad73ee21a2c98934cce8c9bc
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c    readme
040000 tree 75be4436d22c3e0dfd4eed87629d1f5dbfd4f193    src

查看blob对象,发现blob对象存放内容,但是它自己不知道是谁的内容。

$ git cat-file -p 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
111

如果添加一个文件内容和 readme相同的文件到版本库会怎么样呢?
git是创建两个blob,还是共用一个blob?

添加一个 readme2,内容和readme一样,提交到版本库,用git log可以查看两次commit

$ git log --oneline
a2b4f1a (HEAD -> master) second commit
24da758 first commit

readme 的 blob是知道的为 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c,
追踪readme2,从 最近的提交追踪

$ git cat-file -p a2b4
tree 9a64fcf6b554d4a7a8e3012d80714e1ac5a3bf9b
parent 24da758a92d93a001ad8425e2cfd97967b1f5782
author yangxr <yangxr1995@gmail.com> 1679142505 +0800
committer yangxr <yangxr1995@gmail.com> 1679142505 +0800

second commit

$ git cat-file -p 9a64
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c    readme
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c    readme2
040000 tree 75be4436d22c3e0dfd4eed87629d1f5dbfd4f193    src

发现readme和 readme2共用一个blob

在追踪时发现 commit还有parent内容,其实这个内容就是上一个commit

$ git cat-file -p 24da
tree 7f5e0374b464fab0ad73ee21a2c98934cce8c9bc
author yangxr <yangxr1995@gmail.com> 1679141339 +0800
committer yangxr <yangxr1995@gmail.com> 1679141339 +0800

first commit

$ git cat-file -p 7f5e
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c    readme
040000 tree 75be4436d22c3e0dfd4eed87629d1f5dbfd4f193    src

总结

  • git commit 时,git会查看暂存区是否有更新,如果有,则按照暂存区的信息创建一个快照
  • 版本库中的快照由四种对象组成 blob tree commit tag,他们的关系如下

0.4 引用和分支

前面查看对象时,都是使用其名称也就是SHA1值,非常不方便,为了人更好的使用,git提供引用
相关文件如下

$ ls .git/HEAD
.git/HEAD

$ cat .git/HEAD
ref: refs/heads/master

$ ls .git/refs/
heads/  tags/

$ ls .git/refs/heads/
master

$ cat .git/refs/heads/master
a2b4f1ae30b763c4b30b32a47b2afa4b7ae49d13

说明,.git/refs下的文件记录某个commit对象,.git/HEAD记录某个 .git/refs某个文件
所以通过 .git/refs/ 或 .git/HEAD 都可以得到要找的commit对象,所以要使用该commit时不需要其SHA1,而可以这样做

$ git cat-file -p HEAD
tree 9a64fcf6b554d4a7a8e3012d80714e1ac5a3bf9b
parent 24da758a92d93a001ad8425e2cfd97967b1f5782
author yangxr <yangxr1995@gmail.com> 1679142505 +0800
committer yangxr <yangxr1995@gmail.com> 1679142505 +0800

second commit

$ git cat-file -p $(cat .git/refs/heads/master)
tree 9a64fcf6b554d4a7a8e3012d80714e1ac5a3bf9b
parent 24da758a92d93a001ad8425e2cfd97967b1f5782
author yangxr <yangxr1995@gmail.com> 1679142505 +0800
committer yangxr <yangxr1995@gmail.com> 1679142505 +0800

second commit

$ git cat-file -p master
tree 9a64fcf6b554d4a7a8e3012d80714e1ac5a3bf9b
parent 24da758a92d93a001ad8425e2cfd97967b1f5782
author yangxr <yangxr1995@gmail.com> 1679142505 +0800
committer yangxr <yangxr1995@gmail.com> 1679142505 +0800

second commit

那么HEAD到底记录哪个文件?
首先再创建三个分支(最早的master分支哪里来的?第一次commit时git默认创建的)

$ git branch new-branch1

$ git branch new-branch2

$ git branch new-branch3

当前分支为master时,HEAD指向 master

$ git branch
* master
  new-branch1
  new-branch2
  new-branch3

$ cat .git/HEAD
ref: refs/heads/master

切换分支到new-branch1,HEAD指向new-branch1

$ git checkout new-branch1
Switched to branch 'new-branch1'

$ cat .git/HEAD
ref: refs/heads/new-branch1

现在使用引用或HEAD能方便的查看末端commit,那么前面的commit呢?
首先需要知道自己前面有几个commit,使用git log获得

$ git log --oneline
a2b4f1a (HEAD -> new-branch1, new-branch3, new-branch2, master) second commit
24da758 first commit

使用分支名或HEAD加'^'符号,每加一个'^'向前移动一个commit

$ git cat-file -p master^
tree 7f5e0374b464fab0ad73ee21a2c98934cce8c9bc
author yangxr <yangxr1995@gmail.com> 1679141339 +0800
committer yangxr <yangxr1995@gmail.com> 1679141339 +0800

first commit

$ git cat-file -p HEAD^
tree 7f5e0374b464fab0ad73ee21a2c98934cce8c9bc
author yangxr <yangxr1995@gmail.com> 1679141339 +0800
committer yangxr <yangxr1995@gmail.com> 1679141339 +0800

first commit

结论

  • HEAD指向当前分支
  • 分支本质是引用,记录末端commit
  • 使用'^'得到前面的commit
  • tag和branch本质都是引用用于记录commit,不同的是,tag不会随着新增commit而移动,但branch会移动以保证自己指向末端commit
  • 还有种重量级tag,本质是对象,存放在objects中,其功能除了能引用commit,还能记录注释信息。

1. 工作区和暂存区

1.1 工作区 -> 暂存区

git add file
运行此命令后,会修改.git/index,添加文件的元信息,在.git/objects/创建blob对象,存储文件内容

1.2 工作区 <- 暂存区

由于.git/objects有blob对象(即使该对象未和commit关联),删除工作区的文件后,也可以恢复
rm file
git restore file
运行此命令后,git会根据文件名在.git/index中找到对应SHA1,再在.git/objects中找到对应的blob,恢复文件

1.3 删除工作区,保留暂存区

rm file

1.4 删除暂存区,保留工作区

git rm --cached file
运行此命令后,git会根据文件名在.git/index中找到对应SHA1,再在.git/objects中找到对应的blob,删除blob和index中元信息

1.5 删除工作区,删除暂存区

git rm -f file
注意工作区的文件也会被删除

1.6 忽略某些文件

如希望忽略所有.o .a文件和隐藏文件,则在工作目录创建 .gitignore文件

$ cat .gitignore
*.[o|a]
a.out
\.*

这时git会默认忽略这些文件,即使显示添加也会报错,提示需要强制操作

$ ls ./
main.c  main.o  readme

# git 没有略过了main.o
$ git status
On branch master

No commits yet

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

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

# 添加失败
$ git add ./main.o
The following paths are ignored by one of your .gitignore files:
main.o
hint: Use -f if you really want to add them.
hint: Turn this message off by running
hint: "git config advice.addIgnoredFile false"

1.7 撤销对暂存区的修改

撤销对暂存区的所有修改
git reset

撤销对暂存区中某个文件的修改
git restore --staged file

对暂存区的修改撤销,不会影响工作区

2. 暂存区和版本库

2.1 暂存区 -> 版本库

git commit -m "commit comment"
执行以上命令后,git会根据 .git/index 中文件元信息,创建 commit ,tree对象,并找到相关blob对象,将其关联。
commit对象还会指向上一个commit。

2.1.1 commit 错了,怎么改

比如 git commit -m "xFxx",这个 "xxx" 写错了,
或者提交后暂存区又被修改了,但不想建立新的commit
使用如下命令
git commit --amend

2.2 版本库 -> 暂存区

当提交内容错误或不完整,希望撤销提交,又想不修改工作区的文件。
git reset --soft SHA1

比如有文件 main.c
第一次提交时内容为 111 , commit 为 34dq
第二次提交的内容为 222 , commit 为 qwer
第三次提交的内容为 333 , commit 为 abdc

工作区内容为 444
使用 git reset --soft 34dq,退回到第一次提交后,
使用 git log --oneline,会发现只剩下一个 commit 34dq,
并且 工作区main.c 依旧是 444,
并且 git status 会提示 暂存区已经同步了工作区的修改,待提交
这时可以:

  1. 如果要提交新的修改
    git add main.c // 修改暂存区
    git commit -m "second commit"
  2. 如果想恢复main.c
    rm main.c
    git restore main.c // 因为此时 index中记录的main.c的blob值为第一个commit时的,所以能找到那时的blob

2.2.2 被撤销的commit和相关tree blob还存在吗?

2.3 删除暂存区,保留版本库

git rm --cached file

2.4 删除版本库,保留暂存区

2.5 删除版本库,删除暂存区

cp file file.bak
git rm -f file
git commit --amend
mv file.bak file
注意工作区的file文件也会被删除,所以需要备份

3. 工作区和版本库

3.1 工作区 -> 版本库

工作区无法直接到版本库

3.2 版本库 -> 工作区

运行下面命令,会报告工作区和版本库的差别
git checkout HEAD

如果发现工作区的file文件与版本库中不同,希望改成版本库中的file,则运行
git checkout file

如果希望使用版本库中以前的commit,恢复工作区
git checkout HEAD^

这时 git 会按照该commit修改工作区,并建立你新建一个分支

git chekout HEAD^

运行 git branch,建立了一个临时的引用
$ git branch
* (HEAD detached at 002ec8c)
  master

HEAD指向当前commit
$ cat .git/HEAD
002ec8cbec14232648a62032f0000632a7a1fb52

没有建立新分支
$ ls .git/refs/heads/
master

这时可以在此基础上建立新分支,就可以基于老的commit进行开发
$ git switch -c new-branch1
Switched to a new branch 'new-branch1'
$ git branch
  master
* new-branch1

如果想在同个分支上,基于老的commit开发,则可以使用 revert

$ git branch
* master
  new-branch1

Administrator@DESKTOP-AJ6AG7I MINGW64 ~/Desktop/tmp (master)
$ git log --oneline
10cbc27 (HEAD -> master) thrid commit
9496eaa Revert "second commit"
002ec8c (new-branch1) second commit
c2eef27 first commit

想基于第一个commit进行开发,并且保留后续的commit(即使已被废弃)
git revert HEAD

3.3.1 撤销版本库的commit,并撤销对暂存区的修改,工作区保持不变

git reset --mixed SHA1

仔细分析git reset

撤销暂存区修改

git reset不加参数,撤销对暂存区的修改

撤销commit

当要撤销commit时,使用 git reset --mixed/--soft/--hard SHA1

比如有提交
commit main.c:222 SHA1:eb23 "first commit"
commit main.c:111 SHA1:as1e "second commit"
当前main.c:333
git add main.c, 修改暂存区,保存main.c:333

git reset --mixed SHA1

使用 git reset --mixed as1e,
工作区:main.c:333 工作区保持不变
暂存区:main.c:111 暂存区被修改为第一次提交时的情况
版本库:as1e (HEAD->master) first commit,版本库HEAD和master引用指向第一次提交

git reset --soft SHA1

使用 git reset --soft as1e
工作区:main.c:333 工作区保持不变
暂存区:main.c:333 暂存区保持不变
版本库:as1e(HEAD->master) first commit,版本库HEAD和master引用指向第一次提交

git reset --hard SHA1

使用 git reset --hard as1e
工作区:main.c:111 工作区被修改为第一次提交的情况
暂存区:main.c:111 暂存区被修改为第一次提交的情况
版本库:as1e(HEAD->master) first commit,版本库HEAD和master引用指向第一次提交

版本库的各种操作

合并多个提交

比如有main.c
$ git log --oneline
a5334c7 (HEAD -> master) third commit main.c:333
78ba96f second commit main.c:222
9843b0a first commit main.c:111

我希望将第三次提交和第二次提交合并,
git rebase -i 9843b0a(第一次提交)
进入编辑环境

pick 78ba96f second commit
pick a5334c7 third commit //把pick改为s(squash压缩),保存退出,编辑注释信息

Rebase 9843b0a..a5334c7 onto 9843b0a (2 commands)

Commands:

p, pick = use commit

r, reword = use commit, but edit the commit message

e, edit = use commit, but stop for amending

s, squash = use commit, but meld into previous commit

f, fixup = like "squash", but discard this commit's log message

x, exec = run command (the rest of the line) using shell

b, break = stop here (continue rebase later with 'git rebase --continue')

d, drop = remove commit

l, label

t, reset

m, merge [-C | -c ]

. create a merge commit using the original merge commit's

. message (or the oneline, if no original merge commit was

. specified). Use -c to reword the commit message.

These lines can be re-ordered; they are executed from top to bottom.

If you remove a line here THAT COMMIT WILL BE LOST.

However, if you remove everything, the rebase will be aborted.

完成操作后
$ git log --oneline
133bb24 (HEAD -> master) second thrid commit
9843b0a first commit

查看版本库中main.c:333.
所以合并提交后,版本库使用最后的提交的内容

额外

修改git默认编辑器为vim
编辑 .git/config,在core加入 editor=vim

posted on 2023-03-16 08:41  开心种树  阅读(24)  评论(0编辑  收藏  举报