Git

Git

初始化

1
git init

生成如下的目录:

  • HEAD:指示目前被检出的分支;
  • config*:项目特有的配置选项。
  • description:文件仅供 GitWeb 程序使用,我们无需关心。
  • hooks/:客户端或服务端的钩子脚本(hook scripts)
  • info/包含一个全局性排除(global exclude)文件,用以放置那些不希望被记录在 .gitignore 文件中的忽略模式(ignored patterns)。
  • objects/存储所有数据内容;
  • refs/存储指向数据(分支)的提交对象的指针;

接下来一一学习。

Git对象

Git的本质是一个寻址文件系统,这意思是Git 的核心部分是一个简单的键值对数据库(key-value data store)。
数据的内容都会存储到objects中。

现在用 hash-object 命令来展示如何存储数据,这个命令会生成一个SHA-1的哈希值作为Key.
–stdin 选项则指示该命令从标准输入读取内容;若不指定此选项,则须在命令尾部给出待存储文件的路径。

1
2
3
4
5
6
$ git init test
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

然后会在objects目录下看到一个新的文件,一个文件对应着一个数据内容。
校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。

通过 cat-file 命令取出数据

1
2
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

这个存储数据的对象就是git对象。

由此可以做一个简单的版本控制方案,存储“版本一”生成一个git对象,存储“版本二”生成一个git对象。需要哪一个版本就取出git对象中的数据去覆盖本地文件的数据。

  • echo ‘test content’ | git hash-object -w –stdin:将前面的内容写成一个git对象
  • git hash-object -w test.txt:将一个文件写成git对象
  • git update-index –add –cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt:将一个git对象放到暂存区,–cacheinfo是指定一个git对象
  • git write-tree:将暂存区的所有对象写成一个树对象
  • git read-tree –prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579:将一个树对象存储到暂存区
  • echo ‘first commit’ | git commit-tree d8329f -p fdf4fc3344e67ab068f836878b6c4951e3b15f3d:创建一个提交对象
  • echo “1a410efbd13591db07496601ebc7a059dd55cfe9” > .git/refs/heads/master:修改引用指向的提交对象
  • git log –pretty=oneline master:查看引用指向的提交对象的历史
  • git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9:更新引用指向的提交对象

git add = git hash-object -w (+) git update-index –all –cacheinfo (git对象) = git update-index –add 文件
//git commit = git write-tree (+) git commit-tree 把当前HEAD指向的提交对象作为父对象

引用

  • cat .git/HEAD:查看HEAD指针指向的ref引用
  • git commit:会取出HEAD的SHA-1值作为新提交的父提交。
  • git symbolic-ref HEAD refs/heads/test:修改HEAD中的ref引用
  • git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d:创建一个轻量标签对象
  • git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m ‘test tag’:创建一个附注标签对象
  • git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2:查看标签对象内容
  • git remote add origin git@github.com:schacon/simplegit-progit.git:添加一个远程分支时会新建一个远程引用
  • cat .git/refs/remotes/origin/master:查看远程引用

包文件

  • git gc:打包,生成包文件和一个索引, 包文件包含了刚才从文件系统中移除的所有对象的内容。索引文件包含了包文件的偏移信息。
  • git verify-pack -v .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx:查看已打包的内容

Git 最初向磁盘中存储对象时所使用的格式被称为“松散(loose)”对象格式。 但是,Git 会时不时地将多个这些对象打包成一个称为“包文件(packfile)”的二进制文件,以节省空间和提高效率。

引用规格

  • 引用规格:+: 其中 是一个模式(pattern),代表远程版本库中的引用; 是那些远程引用在本地所对应的位置。 + 号告诉 Git 即使在不能快进的情况下也要(强制)更新引用。

  • git remote add origin https:// github.com/schacon/simplegit-progit:添加远程分支到本地引用的简单映射方式

上述命令会在你的 .git/config 文件中添加一个小节,并在其中指定远程版本库的名称(origin)、URL 和一个用于获取操作的引用规格(refspec):

1
2
3
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*

如果想让 Git 每次只拉取远程的 master 分支,而不是所有分支,可以把(引用规格的)获取那一行修改为:fetch = +refs/heads/master:refs/remotes/origin/master。

如果有某些只希望被执行一次的操作,我们也可以在命令行指定引用规格。 若要将远程的 master 分支拉到本地的 origin/mymaster 分支,可以运行:
git fetch origin master:refs/remotes/origin/mymaster

  • git log origin/master || git log remotes/origin/master || git log refs/remotes/origin/master:查看远程分支的提交记录

我们不能在模式中使用部分通配符,所以像下面这样的引用规格是不合法的:fetch = +refs/heads/qa:refs/remotes/origin/qa。但我们可以使用命名空间(或目录)来达到类似目的,可以使用如下配置:

1
2
3
4
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/qa/*:refs/remotes/origin/qa/*

  • git push origin master:refs/heads/qa/master:把本地 master 分支推送到远程服务器的 qa/master 分支上,同样修改config配置可以实现默认git push origin的实现。

  • git push origin :topic || git push origin –delete topic(自Git v1.7.0以后可用):删除远程分支

数据维护

Git 会不定时地自动运行一个叫做 “auto gc” 的命令。 大多数时候,这个命令并不会产生效果。 然而,如果有太多松散对象(不在包文件中的对象)或者太多包文件,Git 会运行一个完整的 git gc 命令。 这个命令会做以下事情:收集所有松散对象并将它们放置到包文件中,将多个包文件合并为一个大的包文件,移除与任何提交都不相关的陈旧对象。

  • git gc –auto:手动执行自动垃圾回收。大约需要7000个以上的松散对象或超过50个的包文件才能让Git启动一次真正的gc命令。

gc 将会做的另一件事是打包你的引用到一个单独的文件。 假设你的仓库包含以下分支与标签:

1
2
3
4
5
$ find .git/refs -type f
.git/refs/heads/experiment
.git/refs/heads/master
.git/refs/tags/v1.0
.git/refs/tags/v1.1

如果你执行了 git gc 命令,refs 目录中将不会再有这些文件。 为了保证效率 Git 会将它们移动到名为 .git/packed-refs 的文件中,如下:

1
2
3
4
5
6
7
$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
^1a410efbd13591db07496601ebc7a059dd55cfe9

如果你更新了引用,Git 并不会修改这个文件,而是向 refs/heads 创建一个新的文件。 为了获得指定引用的正确 SHA-1 值,Git 会首先在 refs 目录中查找指定的引用,然后再到 packed-refs 文件中查找。

注意这个文件的最后一行,它会以 ^ 开头。 这个符号表示它上一行的标签是附注标签,^ 所在的那一行是附注标签指向的那个提交。

数据恢复

在你使用 Git 的时候,你可能会意外丢失一次提交。 通常这是因为你强制删除了正在工作的分支,但是最后却发现你还需要这个分支;亦或者硬重置了一个分支,放弃了你想要的提交。 如果这些事情已经发生,该如何找回你的提交呢?

  • git reflog:当你正在工作时,Git 会默默地记录每一次你改变 HEAD 时它的值。 每一次你提交或改变分支,引用日志都会被更新。 引用日志(reflog)也可以通过 git update-ref 命令更新:

    1
    2
    3
    4
    $ git reflog
    1a410ef HEAD@{0}: reset: moving to 1a410ef
    ab1afef HEAD@{1}: commit: modified repo.rb a bit
    484a592 HEAD@{2}: commit: added repo.rb
  • git log -g;以标准日志的格式输出引用日志

引用日志数据存放在 .git/logs/ 目录中。

如何恢复呢?

可以通过创建一个新的分支指向这个提交来恢复它,如下:

1
2
3
4
5
6
7
$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

如果出于某种原因,丢失的提交不在引用日志中,如何处理?

  • git fsck:检查数据库的完整性。 如果使用一个 –full 选项运行它,它会向你显示出所有没有被其他对象指向的对象:
    1
    2
    3
    4
    5
    6
    7
    $ git fsck --full
    Checking object directories: 100% (256/256), done.
    Checking objects: 100% (18/18), done.
    dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
    dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
    dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
    dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293

git常用命令

1.配置

git config –global user.name “xxx”
git config –global user.emil “xxx”

2.初始化

git init

3.克隆

git clone xxx(ssh,http)

4.修改

放入暂存区:git add
存入本地仓库:git commit -m “xxx”
放入暂存区+存入本地仓库:git commit -am “xxx”

5.撤销修改

撤销暂存区修改:git reset HEAD xxx.xx
撤销工作区的修改:git checkout xxx.xx

6.查看

当前状态:git status
比较工作区和暂存区:git diff
查改历史版本:git log
查看修改过的版本:git reflog

7.版本控制

删除工作空间改动代码, 撤销commit, 撤销add: git reset –hard 版本号
不删除工作空间改动代码,撤销commit,不撤销add:git reset –soft 版本号
不删除工作空间改动代码,撤销commit,且撤销add:git reset –mixed 版本号
commit注释写错了,只是想改一下注释:git commit –amend

8.暂存处理

把当前工作区暂存起来做其他处理:git stash
读取最近一次保存的内容:git stash pop
-> 取出来的内存会和当前工作区内容合并

9.分支

创建分支:git branch xxx
切换分支:git checkout xxx
创建+切换:git checkout -b xxx
查看分支:git branch
分支合并:git merge xxx(把xxx合并到现在所在分支)
分支删除:git branch -d xxx

10.远程分支

取回远程分支的更新:git fetch origin(主机名) master(分支名)
合并远程分支:git merge origin/master
取回+合并:git pull <远程主机名> <远程分支名>:<本地分支名>
新建远程分支:git push xxx:xxx
删除远程分支:git push :xxx
关联远程仓库:git remote add origin git@server-name:path/repo-name.git;
把当前分支推送到远程:git push -u origin master
-> 加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

11.rebase

git rebase -i cid

reword:修改提交信息;
edit:修改此提交;
squash:将提交融合到前一个提交中;
fixup:将提交融合到前一个提交中,不保留该提交的日志消息;
exec:在每个提交上运行我们想要 rebase 的命令;
drop:移除该提交。

本文标题:Git

文章作者:Sun

发布时间:2019年04月27日 - 00:04

最后更新:2020年06月23日 - 15:06

原始链接:https://sunyi720.github.io/2019/04/27/Java/git/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。