简介

记录一下Git的基础操作原理,还有Git信息泄漏在比赛中的一些问题。 


Git基本操作

git init

创建一个git_test文件夹,并使用git init命令初始化一个空的Git 仓库

Rain@Rai4over MINGW64 ~/Desktop
$ mkdir git_test
Rain@Rai4over MINGW64 ~/Desktop
$ cd git_test/
Rain@Rai4over MINGW64 ~/Desktop/git_test
$ git init
Initialized empty Git repository in C:/Users/Rain/Desktop/git_test/.git/
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ ls -a
./  ../  .git/



git add && git commit

创建一个test1.txt文件,通过git add跟踪这个文件,然后使用git commit提交更新到仓库。

Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ echo 'test1file haha' >test1.txt
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ cat test1.txt
test1file haha
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
No commits yet
Untracked files:
 (use "git add <file>..." to include in what will be committed)
       test1.txt
nothing added to commit but untracked files present (use "git add" to track)
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git add test1.txt
warning: LF will be replaced by CRLF in test1.txt.
The file will have its original line endings in your working directory.
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
No commits yet
Changes to be committed:
 (use "git rm --cached <file>..." to unstage)
       new file:   test1.txt
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git commit -m "test1file"
[master (root-commit) 4089fd9] test1file
1 file changed, 1 insertion(+)
create mode 100644 test1.txt
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git log
commit 4089fd99a9bd7ee0cec8f94ce1c66b650054080e (HEAD -> master)
Author: Rai4over <[email protected]>
Date:   Sun Sep 2 17:52:35 2018 +0800
   test1file



查看状态 && 暂存区

git status命令可以查看仓库状态,列出当前目录所有还没有被git管理追踪的文件和被git管理且被修改但还未提交更新的文件.。

文件状态 
仓库中的文件可能存在于这三种状态: 
- Untracked files → 文件未被跟踪; 
- Changes to be committed → 文件已暂存,这是下次提交的内容; 
- Changes not staged for commit → 已跟踪文件的内容发生了变化,但还没有放到暂存区。

通过git add的文件会加入暂存区,之后git commit会将暂存区的所有文件提交更新。 


Git对象和目录

Git对象

在Git系统中有四中类型的对象,所有的Git操作都是基于这四种类型的对象。

  • "blob",这种对象用来保存文件的内容。

  • "tree",可以理解成一个对象关系树,它管理一些"tree"和“blob”对象。

  • "commit",指向一个"tree",它用来标记项目某一个特定时间点的状态。它包括以下关于时间点的元数据,如时间戳、最近一次提交的作者、指向上次提交、

  • "tag",给某个提交增添一个标记。


SHA1哈希值

在Git系统中,每个Git对象都通过哈希值来代表这个对象。哈希值是通过SHA1算法计算出来的,长度为40个字符(40-digit)。


Commit 对象

commit对象一般包含以下信息:

  • 代表commit的哈希值 

  • 指向tree对象的哈希值 

  • 作者 

  • 提交者 

  • 注释


Tree 对象

Tree对象一般包含以下信息: 

  • 代表blog的哈希值 

  • 指向tree对象的哈希值


Blob 对象

Blob对象一般包含用来存储文件的内容。 


Tag 对象

标签对象一般包含以下信息: 
- 一个对象名(SHA1签名) 
- 对象类型


对象间的关系

commit、tree、blob三个对象的简单关系如下: 

若仓库的目录结构如下:

|-- README

`-- lib

   |-- inc

   |   `-- tricks.rb

   `-- mylib.rb


git对象构的关系图如下: 


.git目录

在我们使用git init初始化git仓库的时候,会生成一个.git的隐藏目录,git会将所有的文件,目录,提交等转化为git对象,压缩存储在这个文件夹当中。

  • COMMIT_EDITMSG:保存最新的commit message,Git系统不会用到这个文件,只是给用户一个参考

  • config:这个是GIt仓库的配置文件

  • description:仓库的描述信息,主要给gitweb等git托管系统使用

  • HEAD:这个文件包含了一个档期分支(branch)的引用,通过这个文件Git可以得到下一次commit的parent

  • hooks:这个目录存放一些shell脚本,可以设置特定的git命令后触发相应的脚本;在搭建gitweb系统或其他 
    git托管系统会经常用到hook script

  • index:这个文件就是我们前面提到的暂存区(stage),是一个二进制文件

  • info:包含仓库的一些信息

  • logs:保存所有更新的引用记录

  • objects:所有的Git对象都会存放在这个目录中,对象的SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名

  • refs:这个目录一般包括三个子文件夹,heads、remotes和tags,heads中的文件标识了项目中的各个分支指向的当前commit

  • ORIG_HEAD:HEAD指针的前一个状态


Git引用

Git中的引用是一个非常重要的概念,对于理解分支(branch)、HEAD指针以及reflog非常有帮助。 

Git系统中的分支名、远程分支名、tag等都是指向某个commit的引用。比如master分支,origin/master远程分支,命名为V1.0.0.0的tag等都是引用,它们通过该保存某个commit的SHA1哈希值指向某个commit


HEAD

HEAD也是一个引用,一般情况下间接指向你当前所在的分支的最新的commit上。HEAD跟Git中一般的引用不同,它并不包含某个commit的SHA1哈希值,而是包含当前所在的分支,所有HEAD直接执行当前所在的分支,然后间接指向当前所在分支的最新提交。

查看当前分支,当前分支为master,并通过git log可以发现这个commit就是master分支上最新的提交。

Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git status
On branch master
nothing to commit, working tree clean
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ cat .git/HEAD
ref: refs/heads/master
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ cat .git/refs/heads/master
29bb92d948b861d13c0912694a6000d33b62fea3
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git cat-file -p 29bb92d948b861d13c0912694a6000d33b62fea3
tree 57f5feedc4fa5a298d6d89aa93fd205bb588458e
parent 4089fd99a9bd7ee0cec8f94ce1c66b650054080e
author Rai4over <[email protected]> 1535977002 +0800
committer Rai4over <[email protected]> 1535977002 +0800
test2
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git log -n 1
commit 29bb92d948b861d13c0912694a6000d33b62fea3 (HEAD -> master, test)
Author: Rai4over <[email protected]>
Date:   Mon Sep 3 20:16:42 2018 +0800
   test2




所有的内容都是环环相扣的,我们通过HEAD找到一个当前分支,然后通过当前分支的引用找到最新的commit,然后通过commit可以找到整个对象关系模型: 


引用和分支

  • git branch test,创建名为test分支。

  • git checkout test,切换到test分支。

除了master分支,创建了一个test的分支,切换到新分支,查看HEAD,HEAD的指向发生了变化。

再次查看“.git/refs/heads/”目录,可以看到除了master文件之外,又多了一个test文件,查看该文件的内容也是一个哈希值。

Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ cat .git/HEAD
ref: refs/heads/master
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ cat .git/refs/heads/master
29bb92d948b861d13c0912694a6000d33b62fea3
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git checkout test
Switched to branch 'test'
Rain@Rai4over MINGW64 ~/Desktop/git_test (test)
$ cat .git/HEAD
ref: refs/heads/test
Rain@Rai4over MINGW64 ~/Desktop/git_test (test)
$  cat .git/refs/heads/test
29bb92d948b861d13c0912694a6000d33b62fea3




  • git show-ref --heads,命令就可以查看所有的头

Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git show-ref --heads
29bb92d948b861d13c0912694a6000d33b62fea3 refs/heads/master
29bb92d948b861d13c0912694a6000d33b62fea3 refs/heads/test



日志

进入.git/logs文件夹,可以看到这个文件夹也有一个HEAD文件和refs目录,这些就是记录仓库修改的地方。

目录下记录了包括git commit,git checkout,git stash等命令的操作历史。

  • git stash,保存当前工作进度,会把暂存区和工作区的改动保存起来。 
    执行完这个命令后,在运行git status命令,就会发现当前是一个干净的工作区,没有任何改动。 
    当然可以使用git stash pop来恢复之前的进度.

Rain@Rai4over MINGW64 ~/Desktop/git_test/.git/logs (GIT_DIR!)
$ ls
HEAD  refs/
Rain@Rai4over MINGW64 ~/Desktop/git_test/.git/logs (GIT_DIR!)
$ ls refs/
heads/  stash
Rain@Rai4over MINGW64 ~/Desktop/git_test/.git/logs (GIT_DIR!)
$ ls refs/heads/
master  test


索引index

index/stage,就是更新的暂存区,看看index文件。 

index(索引)是一个存放了已排序的路径的二进制文件,并且每个路径都对应一个SHA1哈希值。在Git系统中,可以通过git ls-files --stage来显示index文件的内容。 

创建新文件,添加到暂存区(SHA1哈希值已经改变)

$ git ls-files --stage
100644 1453b33002ce30e42459ca3ea867c2aeefe1a0fa 0       test1.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       test2.txt
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ echo 333 > test3.txt
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git ls-files --stage
100644 1453b33002ce30e42459ca3ea867c2aeefe1a0fa 0       test1.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       test2.txt
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git add test3.txt
warning: LF will be replaced by CRLF in test3.txt.
The file will have its original line endings in your working directory.
Rain@Rai4over MINGW64 ~/Desktop/git_test (master)
$ git ls-files --stage
100644 1453b33002ce30e42459ca3ea867c2aeefe1a0fa 0       test1.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       test2.txt
100644 55bd0ac4c42e46cd751eb7405e12a35e61425550 0       test3.txt


对象的存储

所有的Git对象都会存放在.git/objects目录中,对象SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名。


.git泄漏 | Example

我们常用的.git泄漏文件恢复工具有很多,但是如果不懂原理,可能会遇到恢复文件缺少的问题,因为多数工具只会恢复HEAD指针的commit对象。

这里拿某比赛的题目举个例子:

打开浏览器console发现提示,并且恢复HEAD指针的commit对象并不能得出正确源码。


获得commit

这里可以想到使用git stash,当然最稳妥的办法当然是遍历所有commit,不管是通过log,refs,HEAD等文件或目录均可,得到的commit指针都是一样的。 

  • commit hash ,e5b2a2443c2b6d395d06960123142bc91123148c

  • 访问commit对象,.git/objects/e5/b2a2443c2b6d395d06960123142bc91123148c


获得tree

  • 根据commit对象得到tree对象的hash 


  • 访问tree对象,.git/objects/76/9905f5a6f425ce62ed9a1cbf375a61fb56b406


获得Blob

  • 根据tree对象得到blob对象的hash 

  • 访问blob对象,.git/objects/8e/f569f235780f24c42b60f50d528a03f7238c80 


其实只要明白了原理,不管commit的hash藏在哪里都能应对自如。


源链接

Hacking more

...