《Pro Git 第二版 中文版》笔记

直接记录快照

git直接记录快照,而非差异比较。

每次你提交更新,或在git中保存项目状态时,它对当时的全部文件制作一个快照并保存这个快照的索引。为了高效,如果文件没有修改,git不再重新存储该文件,而是只保留一个链接指向之前存储的文件。

优点:切换版本时,速度快。

缺点:对大文件进行修改时,git会增加同样大小的新的大文件。

配置文件

配置文件作用范围git命令
/etc/gitconfig所有用户git config --system
~/.gitconfig~/.config/git/config当前用户git config --global
.git/config当前仓库git config

git status

状态简览
$ git status -s
 M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt

?? :新添加的未跟踪文件

A:新添加到暂存区中的文件

右边的M:修改了,没放入暂存区

左边的M:修改了,放入了暂存区

git diff

# 显示 工作目录 对于 暂存区 的变化
git diff

# 显示 暂存区 对于 最后一次提交 的变化(你即将提交哪些内容)
git diff --cached

# 显示 工作目录 对于 最后一次提交 的变化
git diff HEAD

# 显示 工作目录 对于 commit1 的变化(只比较 file1 文件)
git diff commit1 -- file1

# 显示 commit2 对于 commit1 的变化
git diff commit1 commit2

git add

# 暂存修改、新增,不暂存删除(需要指定文件路径)
git add .

# 暂存修改、删除,不暂存新增(文件路径默认为.)
git add -u

# 暂存修改、删除、新增(文件路径默认为.)
git add -A

git commit

# 普通方式
git commit -m "msg"

# 自动暂存已跟踪的文件,并提交
git commit -a -m "msg"

# 修改上一次提交
git commit -a --amend -m "msg"
git commit -a --amend --no-edit

撤销操作

git revert

提交层面(撤销一个提交的同时会创建一个新的提交)

文件层面(然而并没有)

git reset

提交层面(修改HEAD指针)

# 只修改HEAD指针,暂存区和工作目录都不会改变
git reset --soft master
# 默认选项,修改暂存区
git reset --mixed master
# 暂存区和工作目录都被修改
git reset --hard master

文件层面(放弃暂存区文件的修改)

# 恢复文件:本地存库 -> 暂存区
git reset HEAD^ f1
# 等同于 git reset HEAD f1(默认分支为HEAD)
git reset f1
# 当文件名与分支名冲突时,要加--
git reset HEAD -- HEAD
git checkout

提交层面(切换分支)

git checkout master

文件层面(放弃工作目录的修改,恢复源可以是本地存库或暂存区)

# 恢复文件:历史 -> 暂存区 -> 工作目录
git checkout HEAD^ f1
# 恢复文件:暂存区 -> 工作目录(默认从暂存区恢复)
git checkout f1
# 当文件名与分支名冲突时,要加--
git checkout HEAD -- HEAD

git show

查看某个提交的文件列表:

git show --raw HEAD

git push

# 详细
git push origin refs/heads/localbranch:refs/heads/remotebranch
# 正规
git push origin localbranch:remotebranch
# 方便,等同于git push origin localbranch:localbranch
git push origin localbranch

跟踪分支

# 详细
git branch --set-upstream-to localbranch origin/remotebranch
# 快捷
git branch -u origin/remotebranch

删除分支

# 删除本地分支(已经被合并)
git branch -d dev
# 强制删除本地分支(即使没被合并)
git branch -D dev
# 删除远程分支(不知道是否需要被合并)
git push origin --delete dev

git rebase

# 对HEAD进行变基,修改续到master后面
git rebase master
# 对dev进行变基,找出处于dev与master的共同祖先之后的修改,然后把它们在master分支上重演一遍
git rebase master dev
# 找出【client】比【server】多出的提交,在【master】上重演一遍
git rebase --onto master server client

--onto的例子如下

在这里插入图片描述

在这里插入图片描述

本地协议

# 仅是指定路径
$ git clone p1/ p1_copy
Cloning into 'p1_copy'...
done.

# 指定file://
$ git clone file:///c/Users/10257773/Desktop/a/p1/ p1_copy
Cloning into 'p1_copy'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
Checking connectivity... done.

如果仅是指定路径,Git会尝试使用硬链接或直接复制所需的文件。

如果指定file://,Git会触发平时用于网络传输资料的进程,传输效率较低。它的主要目的是取得一个没有外部参考或对象的干净版本库副本。

裸仓库

# 把现有仓库导出为裸仓库
git clone --bare my_project my_project.git

# 初始化一个裸仓库,大家都能写入
git init --bare --shared my_project.git

2种祖先引用

HEAD 当前提交
HEAD^ 第一父提交(合并时所在分支)
HEAD^2 第二父提交(你所合并的分支,需要HEAD有多个父提交,否则报错)
HEAD~ 父提交(等于HEAD^)
HEAD~2 父提交的父提交
HEAD^^^ 第一父提交的第一父提交的第一父提交
HEAD~3^2 父提交的父提交的父提交 的 第二父提交

提交区间

# 在experiment分支中,不在master分支中的提交
git log master..experiment
# 等同git log master..HEAD
git log master..
# 被refA或refB包含,但是不被refC包含的提交
git log refA refB ^refC
git log refA refB --not refC
# 两个分支中的差异提交
git log master...experiment
F
E
D
C

# 显示每个提交到底处于哪一侧的分支
git log --left-right master..experiment
< F
< E
> D
> C

清理工作目录

# 移除文件(删除文件:未跟踪 且 没有被忽略)
git clean
# 移除文件 + 空目录
git clean -d
# 不实际执行,看看它会做什么
git clean -n
# 移除文件(删除文件:未跟踪 或 被忽略)
git clean -x

重写历史

# 修改最后一次提交
git commit -a --amend -m "xxx"
# 修改最后一次提交(使用原来的commit msg)
git commit -a --amend --no-edit

# 修改多个提交信息(从HEAD~2开始)
git rebase -i HEAD~3

# 从每一个提交移除一个文件
# --tree-filter 选项在检出项目的每一个提交后运行指定的命令然后重新提交结果
git filter-branch --tree-filter 'rm -f passwords.txt' HEAD

内部原理

.git目录
config:项目的配置文件
description:仅供GitWeb程序使用
hooks/:放钩子脚本的
info/:包含一个全局性排除文件,用以放置那些不希望被记录在.gitignore文件中的忽略模式
logs/:

HEAD:指示当前被检出的分支
index:保存暂存区信息
objects/:存储所有数据内容
refs/:存储指向数据(分支)的提交对象的指针
数据对象+树对象
# 刚开始,objects下没有文件
$ find .git/objects/ -type f
# 往git数据库存入一些文本
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
# 数据存入了objects目录,一个文件对应一条内容
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
# 从git那里取回数据
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
# 查看内部存储的对象的类型
$ git cat-file -t d670460b4b4aece5915caf5c68d12f560a9fe3e4
blob
# 创建一个新文件并将其内容存入数据库
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
# 向文件里写入新内容,并再次将其存入数据库
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
# 数据库记录下了该文件的两个不同版本,当然之前我们存入的第一条内容也还在
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
# 现在可以把文件内容恢复到第一个版本
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
# 或者第二个版本
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2
# 创建暂存区(暂存test.txt的第一版)
$ git update-index --add --cacheinfo 100644 \
  83baae61804e65cc73a7201a7252750c76066a30 test.txt
# 将暂存区内容写入一个树对象
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
# 验证它确实是一个树对象
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
# 接着我们来创建一个新的树对象,它包括 test.txt 文件的第二个版本,以及一个新的文件
$ echo 'new file' > new.txt
$ git update-index test.txt
$ git update-index --add new.txt
# 将当前暂存区的状态记录为一个树对象
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
# 将第一个树对象加入第二个树对象,使其成为新的树对象的一个子目录
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
提交对象
# 创建第一个提交对象
$ echo 'first commit' | git commit-tree d8329f
757bf0a1fc081e6feb89e63b6e602eee464af6f6
# 查看提交对象
$ git cat-file -p 757bf0a
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author 张灿 <zhang.can2@zte.com.cn> 1591259804 +0800
committer 张灿 <zhang.can2@zte.com.cn> 1591259804 +0800

first commit
# 创建第二个提交对象,父提交对象为第一个
$ echo 'second commit' | git commit-tree 0155eb -p 757bf0a
5a8dbae4634be4b1bd76f4da32ea7e131bdbd9a6
# 创建第三个提交对象,父提交对象为第二个
$ echo 'third commit' | git commit-tree 3c4e9c -p 5a8dbae
01341f0633b8817988a6f0b48e37e0fdead5dd4b
# 使用git log查看提交
$ git log 01341f0633b8817988a6f0b48e37e0fdead5dd4b
commit 01341f0633b8817988a6f0b48e37e0fdead5dd4b
Author: 张灿 <zhang.can2@zte.com.cn>
Date:   Thu Jun 4 16:46:28 2020 +0800

    third commit

commit 5a8dbae4634be4b1bd76f4da32ea7e131bdbd9a6
Author: 张灿 <zhang.can2@zte.com.cn>
Date:   Thu Jun 4 16:44:49 2020 +0800

    second commit

commit 757bf0a1fc081e6feb89e63b6e602eee464af6f6
Author: 张灿 <zhang.can2@zte.com.cn>
Date:   Thu Jun 4 16:36:44 2020 +0800

    first commit
对象存储的过程

(1)假设我们执行如下命令

$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

(2)git这样构造头部信息

# 格式
<对象类型> <数据内容的长度><空字节>
# 我们的例子
blob 12\u0000

(3)将头部信息和原始数据拼接起来

blob 12\u0000test content

(4)计算出新内容的SHA-1校验和

d670460b4b4aece5915caf5c68d12f560a9fe3e4

(5)git会通过zlib压缩这条新内容

x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D

(6)将压缩的内容写入磁盘

# 写入文件的路径
.git/objects/<sha1前2位>/<sha1后38位>

备注:

上面的例子是数据对象提交对象类型是commit树对象tree

数据对象的内容可以是任何东西,但提交对象和树对象的内容却有固定的格式。

分支引用

分支,就是引用,是一个指针(某个提交的SHA-1值)。

# 直接编辑引用文件(不提倡)
$ echo "01341f0633b8817988a6f0b48e37e0fdead5dd4b" > .git/refs/heads/master
$ git log --pretty=oneline master
01341f0633b8817988a6f0b48e37e0fdead5dd4b (HEAD -> master) third commit
5a8dbae4634be4b1bd76f4da32ea7e131bdbd9a6 second commit
757bf0a1fc081e6feb89e63b6e602eee464af6f6 first commit
# git提供的更加安全的命令(指定分支和提交)
$ git update-ref refs/heads/master 01341f0633b8817988a6f0b48e37e0fdead5dd4b
HEAD引用

HEAD文件是一个符号引用(一个指向其他引用的指针)

$ cat .git/HEAD
ref: refs/heads/master
# 查看HEAD引用
$ git symbolic-ref HEAD
refs/heads/master
# 设置HEAD引用
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test
标签引用(标签对象)
# 底层命令创建轻量标签
git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d
# 文件中存储的就是提交对象的SHA-1值
$ cat .git/refs/tags/v1.0
cac0cab538b970a37ea1e769cbbde608743bc96d
# 正常方式创建附注标签
git tag -a v1.1 HEAD^  -m 'test tag'
# 文件中的SHA-1值是自己对象的,而不是指向的提交对象
$ cat .git/refs/tags/v1.1
ce4614db12ac572e9614b03b98805925f45b1e27
# 查看标签对象
$ git cat-file -p ce4614db12ac572e9614b03b98805925f45b1e27
object c3103d4f98d4cafea4fb7a55273b15b4f04956a1
type commit
tag v1.1
tagger 张灿 <zhang.can2@zte.com.cn> 1591348127 +0800

test tag
远程引用
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
git文件模式
# 用于文件
100644:普通文件
100755:可执行文件
120000:符号链接
# 用于目录
# 用于子模块
引用规格
# 添加一个远程版本库
$ git remote add origin https://github.com/schacon/simplegit-progit

# .git/config文件中会添加一个小节
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*

fetch的值就是引用规格。

# 引用规格的格式
[+]<src>:<dst>
+号可选,告诉git即使在不能快进的情况下也要强制更新引用
src代表远程版本库中的引用
dst是那些远程引用在本地对应的位置
# 让git每次只拉取远程的master分支,而不是所有分支
fetch = +refs/heads/master:refs/remote/origin/master
# 也可以在命令行指定引用规格
$ git fetch origin master:refs/remote/origin/mymaster
# 在配置文件中指定多个用于获取操作的引用规格
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/experiment:refs/remotes/origin/experiment
# 不能在模式中使用部分通配符
fetch = +refs/heads/qa*:refs/remotes/origin/qa*
# 但我们可以使用命名空间(或目录)来达到类似目的
[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/*
# 把本地master分支推送到远程服务器的qa/master分支上
$ git push origin master:refs/heads/qa/master
# 或者写在配置文件中
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*
push = refs/heads/master:refs/heads/qa/master
# 删除远程服务器上的topic分支
$ git push :topic

技巧

统计代码变更行数

查看变化总数:

$ git diff --stat
 .../l3dci/action/AddDciIngressRouteAction.java     | 29 +++++++++++-----------
 .../l3dci/action/AddDciIngressRouteDataAction.java | 23 ++++++++---------
 .../l3dci/action/AddDciSubnetAclRuleAction.java    | 26 +++++++++----------
 .../impl/l3dci/action/AddL3DciStaticHostRoute.java | 10 +++-----
 .../zenic/dci/impl/l3dci/task/L3DciAddTask.java    |  6 ++---
 5 files changed, 44 insertions(+), 50 deletions(-)

查看各文件增/删行数:

$ git diff --numstat
14      15      dciapp/impl/src/main/java/com/zte/zenic/dci/impl/l3dci/action/AddDciIngressRouteAction.java
11      12      dciapp/impl/src/main/java/com/zte/zenic/dci/impl/l3dci/action/AddDciIngressRouteDataAction.java
13      13      dciapp/impl/src/main/java/com/zte/zenic/dci/impl/l3dci/action/AddDciSubnetAclRuleAction.java
4       6       dciapp/impl/src/main/java/com/zte/zenic/dci/impl/l3dci/action/AddL3DciStaticHostRoute.java
2       4       dciapp/impl/src/main/java/com/zte/zenic/dci/impl/l3dci/task/L3DciAddTask.java
最简单的git服务器

在服务器上:

# 创建裸仓库(会创建/root/example.git目录)
$ git init --bare example.git

在本地机器:

# 进入本地仓库目录
$ cd example
# 初始化为git仓库
$ git init
# 关联远程仓库
$ git remote add myServer root@192.168.1.25:example.git
# 解决每次推代码需要输入密码的问题
$ ssh-copy-id root@192.168.1.25
# 推送代码
$ git push myServer master

如果本地已经有代码了,那就不用在服务器上创建裸仓库了,只要把本地目录放到服务器上(但是会把本地.git复制过去,总归不太好):

$ scp -r example/.git root@192.168.1.25:/root/f1/example.git

在本地机器上clone仓库:

$ git clone root@192.168.1.25:f1/example.git
清理孤儿提交
git reflog expire --expire=all --all
git gc --aggressive --prune=now
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值