资料来源:https://www.runoob.com/git/git-tutorial.html、http://git-scm.com/docs
查看Git命令的帮助信息,git
1.Git 工作区、暂存区和版本库(以本地举例)、远程仓库
- 工作区:就是你在电脑里能看到的目录。
- 暂存区:英文叫
stage
或index
。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。 - 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。
- Git 工作区、暂存区和版本库
- 图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage/index),标记为 "master" 的是 master 分支所代表的目录树。
- 图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
- 图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下,里面包含了创建的各种对象及内容。
- 当对工作区修改(或新增)的文件执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
- 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
- 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
- 当执行 git rm --cached 命令时,会直接从暂存区删除文件,工作区则不做出改变。
- 当执行 git checkout . 或者 git checkout -- 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
- 当执行 git checkout HEAD . 或者 git checkout HEAD 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
2.Git文件状态
在Git中文件大概分为四种状态:已修改(modified)、已暂存(staged)、已提交(committed)、未追踪(Untrack)
- .gitignore内的文件,不会拥有任何一种状态,被git彻底无视。
- 处于ignore列表的文件,无法被add添加;但是可以强制添加
- 空目录、以及子目录全部是空目录的目录不会有Untrack状态,也无法通过add改变状态(无效)
- 工作目录新增文件时,只要不处于ignore目录,都会变成Untrack状态;
- 没有add过的文件或者被restore(不带--staged)的文件,处于Untrack状态;
- 初次add和被add后产生修改的文件,会处于modifed状态。
- 处于modified状态的文件,最开始可以进行add和restore两种操作,此时的add操作叫做
更新要提交的内容
,add后变为staged状态,restore(不加staged标记)后变为Untrack; - add后变为staged状态的文件,可用restore --staged 变回modified状态;这个staged状态的内容可以用来恢复内容。没有被add的modified状态文件内容没有被记录(虽然有撤回,但是本质不一样);
- 处于staged状态的文件,在没有commit之前再次产生修改时,会同时具有staged和modified两个状态(可以把statged状态的内容拉回来,覆盖。);但是commit时会使用内容最新的那个状态;
- commit会提交所有staged状态的文件,所以commit可以理解有一个modified到staged状态的过程(实际可能不存在,因为暂存区本来就有变动的记录);所以暂存状态不能理解为处于暂存区,应当指的是被纳入下一次提交的文件;任何被追踪的产生修改的文件都会在暂存区被记录;成为下一次提交的一部分;
- 未被追踪的文件被删除时,不会产生git状态。处于modofy未add时,会变成deleted状态;处于staged状态会保持暂存状态;
- 已经被删除的(deleted状态)被追踪的文件,恢复后会变成modified状态;
- 提示
- add的作用是将文件添加到暂存区,只有被add的文件才会被追踪
- 暂存区
- (1)所谓的暂存区只是一个简单的索引文件而已。
(2)暂存区这个索引文件里面包含的是文件的目录树,像一个虚拟的工作区,在这个虚拟工作区的目录树中,记录了文件名、文件的时间戳、文件长度、文件类型以及最重要的SHA-1值,文件的内容并没有存储在其中,所以说 它像一个虚拟的工作区。
(3)索引指向的是.Git/objects下的文件。
(4)暂存区的作用:除非是绕过暂存区直接提交,否则Git想把修改提交上去,就必须将修改存入暂存区最后才能commit。每次提交的是暂存区所对应的文件快照。
拓展:status提示信息
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
- 既然是Changes not staged for commit,就说明出现这个提示下的所有文件改动,都是存在于工作区的。stage是暂存区的意思,not stage说明都不在暂存区,那么说明在工作区。
- (use “git add …” to update what will be committed)。执行这条命令就可以工作区里面的改变加入到暂存区。可以执行git add .把当前目录下所有改动加入暂存区。
- (use “git checkout – …” to discard changes in working directory)。执行这条命令将丢弃在工作区的改动。可以执行git checkout *把当前目录下所有工作区的改动丢弃掉
Untracked files:
(use "git add ..." to include in what will be committed)
- Untracked files,就说明出现这个提示下的所有文件都是当前HEAD没有被加入过的文件。这种改动也属于工作区。
- (use “git add …” to include in what will be committed)。把Untracked files加入暂存区。
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
- 当前分支比远程分支多了一次commit
Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively
pull报错了,查看状态显示这个,先留着待解决吧
3.HEAD是什么
HEAD是Git中非常重要的一个概念,你可以称它为指针或者引用,它可以指向任意一个节点,并且指向的节点始终为当前工作目录,换句话说就是当前工作目录(也就是你所看到的代码)就是HEAD指向的节点。
4.git重命名检测
Git 采用了不同的方法:它没有选择去存储与文件移动操作相关的信息,而是采用了重命名检测算法。在该算法中,如果一个文件在某一次提交中消失了,它依然会存在于其前次提交,而如果某个拥有相同名字或相似内容的文件出现在了另一个位置,Git 就会自动检测到。如果是这种情况,Git 就会假定该文件被移动过了。
Git项目文件说明
Git init后主要有两个重要的文件和目录:.git目录和.gitignore
1. .gitignore
.gitignore文件存在于根目录(与.git同级的目录)用于在将文件提交到git暂存区时,指定将哪些文件排除;
有时候你想添加(git add)一个文件到Git,但发现添加不了,多半原因是这个文件被.gitignore
忽略了
git add .不会添加被.gitignore忽视的文件,而git add -f . 强制添加所有文件,即使是.gitignore忽视的文件也添加。
当.gitignore文件不是你编写的,但是它编写的不符合实际需求,你可以使用git check-ignore命令进行检查,看是哪一个规则有问题了
#检测
git check-ignore -v App.class
#结果
.gitignore:3:*.class App.class
.gitignore只能忽略那些原来没有被track的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。解决方法就是先把本地缓存删除(改变成未track状态),然后再提交。
git rm -r --cached .
git add .
git commit -m ‘update .gitignore’
也可以手动指定一个文件作为git忽略文件
git config core.excludesfile ***
对于全局Git配置,可以使用如下命令对全部仓库进行配置。
git config --global core.excludesfile **/.gitignore(文件相对或绝对位置)
忽略规则如下:
- 空格不匹配任意文件,可作为分隔符,可用反斜杠转义
- #开头的文件标识注释,可以使用反斜杠进行转义
- ! 开头的模式标识否定,该文件将会再次被包含,如果排除了该文件的父级目录,则使用 ! 也不会再次被包含。可以使用反斜杠进行转义
- / 结束的模式只匹配文件夹以及在该文件夹路径下的内容,但是不匹配该文件
- / 开始的模式匹配项目跟目录
- 如果一个模式不包含斜杠,则它匹配相对于当前 .gitignore 文件路径的内容,如果该模式不在 .gitignore 文件中,则相对于项目根目录
- ** 匹配多级目录,可在开始,中间,结束
- ? 通用匹配单个字符
- [] 通用匹配单个字符列表
- 各种项目的gitignore
- 参考地址:https://github.com/github/gitignore
2. .git目录
任意文件夹中,用 git init 命令初始化仓库,即可在此文件夹下创建 .git 文件夹(.打头为隐藏文件夹,所以平时可能看不到)。这个文件夹之外的部分叫做工作区(Working Directory),.git 文件夹我们称做 Git仓库 (Git Repository)。 通常会有7个文件5个目录,常见目录如下:
COMMIT_EDITMSG
HEAD
ORIG_HEAD
FETCH_HEAD
config
description
index
hooks/
info/
logs/
objects/
refs/
1. 文件 COMMIT_EDITMSG
此文件是一个临时文件,存储最后一次提交的信息内容,git commit 命令之后打开的编辑器就是在编辑此文件,而你退出编辑器后,git 会把此文件内容写入 commit 记录。
实际应用: git pull 远程仓库后,新增了很多提交,淹没了本地提交记录,直接 cat .git/COMMIT_EDITMSG 就可以弄清楚最后工作的位置了。
2. HEAD
此文件永远存储当前位置指针,就像 linux 中的 $PWD 变量和命令提示符的箭头一样,永远指向当前位置,表明当前的工作位置。在 git 中 HEAD 永远指向当前正在工作的那个 commit。(孤立HEAD?????)
HEAD 存储一个分支的 ref,Linux中运行:cat .git/HEAD 通常会显示:
ref: refs/heads/master
这说明你目前正在 master 分支工作。此时你的任何 commit,默认自动附加到 master 分支之上
git cat-file -p HEAD
, 显示详细的提交信息:
tree 4cbb261560348e1727b5137f3ab6eceae8e1f34d
parent 22c457fe24f737505356edfb8696c7e50fd9d971
author Evan You 1654857613 +0800
committer Evan You 1654857613 +0800
chore: test pass
孤立head,不指向任何commit
3. ORIG_HEAD
正因为 HEAD 比较重要,此文件会在你进行危险操作时备份 HEAD,如以下操作时会触发备份
git reset
git merge
git rebase
git pull
此文件应用示例
# 回滚到上一次的状态(慎用!!!)
git reset --hard ORIG_HEAD
4. FETCH_HEAD
这个文件作用在于追踪远程分支的拉取与合并,与其相关的命令有 git pull/fetch/merge
,而git pull 命令相当于执行以下两条命令:
$ git fetch
$ git merge FETCH_HEAD
# 显示如下>>>
From https://github.com/xxx/xxxx
* branch master -> FETCH_HEAD
Updating f785638..59db1b2
此时会默默备份 HEAD 到 ORIG_HEAD
5. config
此文件存储项目本地的 git 设置,典型内容如下:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
[remote "origin"]
url = git@gitlab.xxxx.com/xxx.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[branch "v2.6.0"]
remote = origin
merge = refs/heads/v2.6.0
[branch "v2.8.0"]
remote = origin
merge = refs/heads/v2.8.0
[core]
段的内容跟 git config 命令对应
执行以下命令:
git config user.name abc
git config user.email abc@abc.com
会在 config 文件中追加以下内容:
... ...
[user]
name = abc
email = abc@abc.com
git config --global 影响的则是全局配置文件 ~/.gitconfig。
[remote] 段表示远程仓库配置
[branch] 段表示分支同步设置
假设当前在 master 分支,执行 git pull 若出现以下提示:
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull 就说明 .git/config 文件缺少对应的 [branch "master"] 字段。
解决方案为:
git branch -u origin/master master
# 或者执行一次 push
git push -u origin master
会出现提示:
Branch 'master' set up to track remote branch 'master' from 'origin'.
其实就是生成以下内容在 .git/config中:
[branch "master"]
remote = origin
merge = refs/heads/master
手动编辑 .git/config,效果一样。这就是 upstream 的真正含义,即生成 config 中的这段配置。
6. description
说明这个文件主要用于 GitWeb 的描述,如果要启动 GitWeb 可用如下命令:
# 确保lighttpd已安装: brew install lighttpd
$ git instaweb --start
默认会启动 lighttpd 服务并打开浏览器 http://127.0.0.1:1234 (试着改成对外IP并分享给别人?)
以下显示当前的 git 仓库名称以及描述,默认的描述如下:
默认描述
上面这段话就是默认的 description
文件的内容,编辑这个文件来让你 GitWeb
描述更友好。
7. hooks/目录
存放 git hooks,用于在 git 命令前后做检查或做些自定义动作。运行 ls -F1 .git/hooks
prepare-commit-msg.sample # git commit 之前,编辑器启动之前触发,传入 COMMIT_FILE,COMMIT_SOURCE,SHA1
commit-msg.sample # git commit 之前,编辑器退出后触发,传入 COMMIT_EDITMSG 文件名
pre-commit.sample # git commit 之前,commit-msg 通过后触发,譬如校验文件名是否含中文
pre-push.sample # git push 之前触发
pre-receive.sample # git push 之后,服务端更新 ref 前触发
update.sample # git push 之后,服务端更新每一个 ref 时触发,用于针对每个 ref 作校验等
post-update.sample # git push 之后,服务端更新 ref 后触发
pre-rebase.sample # git rebase 之前触发,传入 rebase 分支作参数
applypatch-msg.sample # 用于 git am 命令提交信息校验
pre-applypatch.sample # 用于 git am 命令执行前动作
fsmonitor-watchman.sample # 配合 core.fsmonitor 设置来更好监测文件变化
参考
https://git-scm.com/docs/githooks
如果要启用某个 hook,只需把 .sample 删除即可,然后编辑其内容来实现相应的逻辑。
比如要校验每个 commit message 至少要包含两个单词,否则就提示并拒绝提交,将 commit-msg.sample 改为 commit-msg 后,编辑如下:
#!/bin/sh
grep -q 'Ss+S' $1 || { echo '提交信息至少为两个单词' && exit 1; }
这样当提交一个 commit 时,会执行 bash 命令: .git/hooks/commit-msg .git/COMMIT_EDITMSG,退出值不为 0,就拒绝提交。
8. info/目录
此文件夹基本就有两个文件:
- 文件 info/exclude 用于排除规则,与 .gitignore 功能类似。
- 可能会包含文件 info/refs ,用于跟踪各分支的信息。此文件一般通过命令 git update-server-info 生成,内容通常如下:
9. logs/目录
记录了操作信息,git reflog 命令以及像 HEAD@{1} 形式的路径会用到。如果删除此文件夹(危险!),那么依赖于 reflog 的命令就会报错。
文件夹 objects/
此文件夹简单说,就是 git的数据库,运行 tree .git/objects,可以看到目录结构:
.git/objects/
|-- 0c
| `-- d370696b581c38ee01e62b148a759f80facc2d
|-- 59
| `-- 3d5b490556791212acd5a516a37bbfa05d44dd
|-- 61
| `-- be44eedde61d723e5761577a2b420ba0fc2794
|-- 64
| `-- c0aed8ddcbb546bdcec2848938fc82348db227
|-- d4
| `-- 9904676ce8ddde276bdbfa9bbec313e90e0f50
|-- info
`-- pack
|-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.idx
`-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.pack
这些文件分两种形式:pack压缩包 形式放在 pack/ 目录下,除此之外都是 hash文件 形式,被叫做 loost objects。
这个文件夹以及相应的算法,我没找到独立的名称,就叫它 hash-object 体系吧。因为确实有个 git hash-object 命令存在,是一个底层的负责生成这些 loost objects 文件,如果要看到这些文件各自的含义,执行以下命令:
git cat-file --batch-check --batch-all-objects
可以看到
04c87c65f142f33945f2f5951cf7801a32dfa240 commit 194
098217953a6ca169bed33d2be8a07d584fcdaf30 tree 31
0cd370696b581c38ee01e62b148a759f80facc2d commit 245
2a810017bfc85d7db2627f4aabdaa1583212bda3 blob 19
3920a07c1d5694df6b8658592b0939241d70e9e5 tree 93
593d5b490556791212acd5a516a37bbfa05d44dd tag 148
61be44eedde61d723e5761577a2b420ba0fc2794 tree 154
... ...
但你会发现这个列表里有些值在文件夹中并不存在,因为除了 loost objects 它还汇总了 pack 文件中的内容。hash文件
又称为 loose object
,文件名称共由40字符的 SHA-1 hash 值组成,其中前两个字符为文件夹分桶,后38个字符为文件名称。
按文件内容可分为四种类型:commit, tree, blob, tag,若执行以下命令会生成所有四种类型:
echo -en 'xxn' > xx # 共 3 个字符
git add .
git commit -m 'update xx'
git tag -a 'v1.0' -m 'release: 1.0.0'
经过以上操作后,对比一下文件树,发现多了四个 hash文件:
|-- 0c
| `-- d370696b581c38ee01e62b148a759f80facc2d
|-- 18
| `-- 143661f96845f11e0b4ab7312bdc0f356834ce
|-- 30
| `-- 20feea86d222d83218eb3eb5aa9f58f73df04d
|-- 59
| `-- 3d5b490556791212acd5a516a37bbfa05d44dd
|-- 61
| `-- be44eedde61d723e5761577a2b420ba0fc2794
|-- 64
| `-- c0aed8ddcbb546bdcec2848938fc82348db227
|-- ad
| `-- f4c9afac7afae3ff3e95e6c4eefe009d547f00
|-- cc
| `-- c9bd67dc5c467859102d53d54c5ce851273bdd
|-- d4
| `-- 9904676ce8ddde276bdbfa9bbec313e90e0f50
|-- info
`-- pack
|-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.idx
`-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.pack
这四个 hash文件 分别是:
cc/c9bd67dc5c467859102d53d54c5ce851273bdd # blob
30/20feea86d222d83218eb3eb5aa9f58f73df04d # commit
ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00 # tree
18/143661f96845f11e0b4ab7312bdc0f356834ce # tag
其实这些文件都经过了压缩,压缩形式为 zlib。先安装一下解压工具 macOS 版 brew install pigz 或 windows 版 pigz,后执行:
$ pigz -d < .git/objects/cc/c9bd67dc5c467859102d53d54c5ce851273bdd
# BLOB类型,显示结果为>>>>(注意xx后有个n)
blob 3xx
$pigz -d < .git/objects/30/20feea86d222d83218eb3eb5aa9f58f73df04d
# COMMIT类型,显示结果为>>>>
commit 248tree adf4c9afac7afae3ff3e95e6c4eefe009d547f00
parent 0cd370696b581c38ee01e62b148a759f80facc2d
author jamesyang.yjm 1562044880 +0800
committer jamesyang.yjm 1562044880 +0800
update xx
$ pigz -d < .git/objects/ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00
# TREE类型,显示结果为>>>>
tree 154100644 abc*???]}?bJ?ڡX2??100644 asdf???CK?)?wZ???S?100644 iou???CK?)?wZ???S?100644 xx?ɽg?FxY-S?L?Q';?100644 yy???CK?)?wZ???S?
$ pigz -d < .git/objects/18/143661f96845f11e0b4ab7312bdc0f356834ce
# TAG类型,显示结果为>>>>
tag 155object 3020feea86d222d83218eb3eb5aa9f58f73df04d
type commit
tag v1.0
tagger jamesyang.yjm 1562045942 +0800
release: 1.0.0
会发现,显示结果都是 type size+内容 形式,这就是 object 文件的存储格式:
[type] [size][NULL][content]
type
可选值:commit, tree, blob, tag,NULL 就是C语言里的字符结束符:�,size 就是 NULL后内容的字节长度。
type
的几种类型可以使用 git cat-file -t hash 看到,内容可以用 git cat-file -p hash 看到。
git cat-file -t ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 显示结果为>>>>
blob
git cat-file -p ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 显示结果为>>>>
xx
所以 blob 文件就是对原文件内容的全量拷贝,同时前面加了 blob size�,而文件名称的 hash 值计算是计算整体字符的 SHA-1 值:
echo -en 'blob 3�xxn' | shasum
# 显示结果为>>>>
ccc9bd67dc5c467859102d53d54c5ce851273bdd -
知道原理后,其它类型格式请自行参考 斯坦福 Ben Lynn 所著的 GitMagic。
所以,当我们 git show 3020feea86d222d83218eb3eb5aa9f58f73df04d
时,会发生些什么?
找到 3020feea86d222d83218eb3eb5aa9f58f73df04d 这个 commit
,显示出来
找到此 commit 关联的 tree object: adf4c9afac7afae3ff3e95e6c4eefe009d547f00
,拉取相应的 blob 文件,并与当前工作区内的文件做 diff,然后显示出来
这就是 objects/ 文件夹作为 git数据库 被使用的真实例子。pack文件
为什么会有 .pack 文件?
由于每次 commit 都会生成许多 hash文件,并且由于 blob 文件都是全量存储的,导致 git 效率下降,于是有了 pack-format,优势:
对于大仓库存储效率高
利于网络传输,便于备份
增量存储,优化磁盘空间
将 .git/objects 下的部分文件打包成 pack格式
$ tree .git/objects/ | wc -l
311
$ git gc
Enumerating objects: 288, done.
Counting objects: 100% (288/288), done.
Delta compression using up to 4 threads
Compressing objects: 100% (287/287), done.
Writing objects: 100% (288/288), done.
Total 288 (delta 131), reused 90 (delta 0)
$ tree .git/objects/ | wc -l
12
可以看到文件数量减小了不少,其中大部分文件被打到一个 .pack 包中,并且是增量存储,有部分变更的文件只存储 基础hash + 变更内容,磁盘空间优化很明显。
git gc 其实运行了两条命令:git repack 用来打包 和 git prune-packed 用来移除已打包的 hash文件;
11.文件夹refs
refs 可以理解成文件系统中的 symbol link,看下结构:
$ tree .git/refs/
.git/refs
|-- heads
| `-- master
`-- tags
`-- v1.0
$ cat .git/refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
$ cat .git/refs/tags/v1.0
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
$ git cat-file -t 5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
commit
可以看到 master 和 v1.0 都指向 5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 这个 commit。
refs/heads/ 文件夹内的 ref 一般通过 git branch
生成。git show-ref --heads
可以查看。
refs/tags/ 文件夹内的 ref 一般通过 git tag 生成。git show-ref --tags
可以查看。
如下:
$ git branch abc
$ tree .git/refs/
.git/refs/
|-- heads
| |-- abc
| `-- master
`-- tags
`-- v1.0
$ cat .git/refs/heads/abc
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
说明新建分支其实就是生成了一个指向某个 commit 的 symbol link,当然在这里叫做 ref。
而 git tag
命令本质与 git branch
相同,只生成一个 ref 放在 tags 目录下,所以被称为 lightweight tag。
而 git tag -a xx
命令会首先生成一个类型为 tag 的 hash文件 放到 objects/ 目录,然后生成 ref 放到 tags 目录下指向那个文件。这就叫做 annotated tag,好处是可包含一些元信息如 tagger 和 message,被 git 的 hash-object 算法管理,可被 GPG 签名等,所以更稳定,更安全。
使用以下命令来拿到 refs 文件夹存储的信息:
$ git show-ref --head --dereference
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/abc
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/v1.0
5e84371048faa20412f5492e6af264a7e1edfec1 refs/tags/xx
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/xx^{}
我们来看这些信息如何变化的:
$ touch new_file && git add . && git commit -m 'add new_file'
[master 44b0d05] add new_file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 new_file
$ git show-ref --head --dereference
44b0d05ddadaaa8d2cc40d6647cc474b26f5d8d3 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/abc
44b0d05ddadaaa8d2cc40d6647cc474b26f5d8d3 refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/v1.0
5e84371048faa20412f5492e6af264a7e1edfec1 refs/tags/xx
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/xx^{}
diff 一下可以看到:
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/master
这两行发生了变化。也就是每次 commit 时,HEAD 与 heads 都会自动更新。
12. index文件
文件保存成二进制对象以后,还需要通知 Git 哪些文件发生了变动。所有变动的文件,Git 都记录在一个区域,叫做"暂存区"(英文叫做 index 或者 stage)。等到变动告一段落,再统一把暂存区里面的文件写入正式的版本历史。
git update-index命令用于在暂存区记录一个发生变动的文件。
$ git update-index --add --cacheinfo 100644
3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt
上面命令向暂存区写入文件名test.txt、二进制对象名(哈希值)和文件权限。
git ls-files命令可以显示暂存区当前的内容。
$ git ls-files --stage
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 test.txt
上面代码表示,暂存区现在只有一个文件test.txt,以及它的二进制对象名和权限。知道了二进制对象名,就可以在.git/objects子目录里面读出这个文件的内容。
git status命令会产生更可读的结果。
$ git status
要提交的变更:
新文件: test.txt
上面代码表示,暂存区里面只有一个新文件test.txt,等待写入历史。
资料来源
参考:https://developer.aliyun.com/article/716483
Git远程仓库
Git 并不像 SVN 那样有个中心服务器。目前我们使用到的 Git 命令都是在本地执行,如果你想通过 Git 分享你的代码或者与其他开发人员合作。 你就需要将数据放到一台其他开发人员能够连接的服务器上。
1.添加远程仓库
git remote add [shortname] [url] #添加远程仓库
git remote rm name # 删除远程仓库
git remote rename old_name new_name # 修改仓库名
2.查看远端仓库
$ git remote
origin
$ git remote -v
origin git@github.com:tianqixin/runoob-git-test.git (fetch)
origin git@github.com:tianqixin/runoob-git-test.git (push)
3.获取远端仓库代码 git fetch
# 只能fetch到一个空白的分支,然后可以手动merge
$ git fetch <远程主机名> <远程分支名>:<本地分支名>
不填的话都是默认
4.拉取 git pull
git pull <远程主机名> <远程分支名>:<本地分支名>
# 允许合并不相关的分支
$ git pull --allow-unrelated-histories
git pull操作其实是git fetch 与 git merge 两个命令的集合。 git fetch 和 git merge FETCH_HEAD 的简写。
相关文档:https://www.runoob.com/git/git-remote-repo.html
5.推送 git push
# 基本
$ git push <远程主机名> <本地分支名>:<远程分支名>
# 强制推送
$ git push --force origin master
# 删除远程分支
$ git push origin --delete master
# 允许合并不相关的分支
$ git push --allow-unrelated-histories
提示
如果另一个开发者在我们之前已经做过一次 push 操作,此次 push 命令就会被拒绝传送提交。这时候,我们必须要先做一次 pull 操作,将其他人新上载的更新取回,并本地合并。
如果本地分支名与远程分支名相同,则可以省略冒号,带上-u 参数相当于记录了push到远端分支的默认值,这样当下次我们还想要继续push的这个远端分支的时候推送命令就可以简写成git push即可。
Git 分支
1.创建分支命令 git branch
# 创建分支
$ git branch
# 创建分支并跟踪远程分支
$ git branch -u o/master foo
2.切换分支命令 git checkout
第一作用是切换分支,第二个是撤销修改。
# 切换指定分支
$ git checkout ||
# 创建一个的分支,它跟踪远程分支
$ git checkout -b 本地分支名x origin/远程分支名x
# 从暂存区恢复到工作区
$ git checkout .
提示
当你切换分支的时候,Git 会用该分支的最后提交的快照替换你的工作目录的内容, 所以多个分支不需要多个目录。
实际测试:
假设分支1 a文件未提交,分支2 a文件已提交。切换到到分支2时会替换 分支1的a文件。1切换到2时也会替换a文件;
两个分支都已经提交的 切换时会互相替换。一个提交一个没提交时,从a到b,b会保持a的暂存区和工作区
3.合并分支命令 git merge
# 合并指定分支到当前分支
$ git merge
4.删除分支 git branch -d
# 删除指定分支
$ git branch -d
5.分支列表 git branch
# 列出所有分支
$ git branch
# 查看远程所有分支
$ git branch -r
# 查看本地和远程所有分支
$ git branch -a
列出分支时,带*号的分支为当前活动分支
5.重命名分支 git branch -M
# 重命名指定分支
# 不填old默认重命名当前分支
$ git branch -m old new
# 强制重命名指定分支
$ git branch -M old new
git rebase 变基
1.介绍
Git rebase,通常被称作变基或衍合, 可以理解为另外一种合并的方式,与merge 会保留分支结构和原始提交记录不同,rebase 是在公共祖先的基础上,把新的提交链截取下来,在目标分支上进行重放,逐个应用选中的提交来完成合并。
不同公司,不同情况有不同使用场景,不过大部分情况推荐如下:
自己单机的时候,拉公共分支最新代码的时候使用rebase,也就是git pull -r或git pull --rebase。这样的好处很明显,提交记录会比较简洁。但有个缺点就是rebase以后我就不知道我的当前分支最早是从哪个分支拉出来的了,因为基底变了嘛,所以看个人需求了。
往公共分支上合代码的时候,使用merge。如果使用rebase,那么其他开发人员想看主分支的历史,就不是原来的历史了,历史已经被你篡改了。举个例子解释下,比如张三和李四从共同的节点拉出来开发,张三先开发完提交了两次然后merge上去了,李四后来开发完如果rebase上去(注意李四需要切换到自己本地的主分支,假设先pull了张三的最新改动下来,然后执行,然后再git push到远端),则李四的新提交变成了张三的新提交的新基底,本来李四的提交是最新的,结果最新的提交显示反而是张三的,就乱套了。
正因如此,大部分公司其实会禁用rebase,不管是拉代码还是push代码统一都使用merge,虽然会多出无意义的一条提交记录“Merge … to …”,但至少能清楚地知道主线上谁合了的代码以及他们合代码的时间先后顺序
2.原理
变基操作的工作原理很简单:Git 会让我们想要移动的提交序列在目标分支上按照相同的顺序重新再现一遍。这就相当于我们为各个原提交做了个副本,它们拥有相同的修改集、同一作者、日期以及注释信息。
3.命令
# 可以是commit 版本号、分支名称,合并多个提交到一个版本
$ git rebase -i [startpoint] [endpoint]
# 变基发生冲突时,解决后继续变基
$ git rebase --continue
# 无视冲突,继续变基操作
$ git rebase --skip
# 发生冲突时中断变基
$ git rebase --abort"
-i的意思是--interactive,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint]则指定了一个编辑区间,如果不指定[endpoint],则该区间的终点默认是当前分支HEAD所指向的commit(注:该区间指定的是一个前开后闭的区间)。
Git 标签
Git 中的tag指向一次commit的id,通常用来给开发分支做一个标记,如标记一个版本号。
1.添加标签
git tag -a version -m "note"
注解:git tag 是打标签的命令,-a 是添加标签,其后要跟新标签号,-m 及后面的字符串是对该标签的注释。
2.提交标签到远程仓库
git push origin -tags
注解:就像git push origin master 把本地修改提交到远程仓库一样,-tags可以把本地的打的标签全部提交到远程仓库。
3.删除标签
git tag -d version
注解:-d 表示删除,后面跟要删除的tag名字
4.删除远程标签
git push origin :refs/tags/version
注解:就像git push origin :branch_1
可以删除远程仓库的分支branch_1一样, 冒号前为空表示删除远程仓库的tag。
5.查看标签
git tag或者git tag -l
Git补丁
1.git fromat-patch
git format-patch 命令用于将 Git 仓库中的提交记录转换为补丁文件。补丁文件可以用于将更改应用到其他 Git 仓库或版本控制系统中。
# 生成单个补丁文件
git format-patch
# 和 是要生成补丁文件的提交范围
git format-patch ..
# -n 选项可以指定要生成的补丁文件数量。
git format-patch -n ..
# --stdout 选项可以将补丁文件输出到标准输出中,包括提交信息和更改内容。
git format-patch --stdout
# --no-signature 选项可以生成不包含提交信息的补丁文件,只包含更改内容
git format-patch --no-signature
2.git apply
# 应用单个补丁文件,将补丁文件中的更改应用到工作目录中。
git apply
# 应用多个补丁文件
git apply ...
# --ignore-space-change 选项可以忽略补丁文件中的空格更改。
git apply --ignore-space-change
# 应用补丁文件并忽略空格和换行符:
git apply --ignore-whitespace --ignore-whitespace
# --index 选项可以将更改添加到暂存区中,而不是仅仅应用更改到工作目录中。
git apply --index
3.git am
- git am 和 git apply 命令都用于将补丁文件应用到 Git 仓库中,但它们之间有一些区别。
- git am 命令可以自动创建一个新的提交,而 git apply 命令只会将更改应用到工作目录中,需要手动创建一个新的提交。
- git am 命令可以应用交互式补丁,以便手动选择要应用的更改,而 git apply 命令只能应用非交互式补丁。
- git am 命令可以应用多个补丁文件,并自动创建多个新的提交,而 git apply 命令只能应用一个补丁文件。
- git am 命令通常用于应用由 git format-patch 命令生成的补丁文件,而 git apply 命令可以应用由其他方式生成的补丁文件。
Git存储
git stash将本地未提交代码作为一个本地缓存。作用范围为本地工作区以及本地暂存区。
1.使用场景
- 当正在dev分支上开发某个项目,这时项目中出现一个bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用git stash命令将修改的内容保存至堆栈区,然后顺利切换到hotfix分支进行bug修复,修复完成后,再次切回到dev分支,从堆栈中恢复刚刚保存的内容。
- 由于疏忽,本应该在dev分支开发的内容,却在master上进行了开发,需要重新切回到dev分支上进行开发,可以用git stash将内容保存至堆栈中,切回到dev分支后,再次恢复内容即可。
2.git stash
# 保存当前工作区、暂存区的所有未提交代码
# 执行存储时,添加备注,方便查找
# git stash save “save message”
只有git stash 也是可以的,但查找时不方便识别。
3.查看stash列表
# 查看stash了哪些存储
$ git stash list
# 显示缓存的详细信息,n为第几个,默认数第一个,也就是0
$ git stash show stash@{0}
4.恢复缓存
git stash pop
命令恢复之前缓存的工作目录,将缓存堆栈中的对应stash删除,并将对应修改应用到当前的工作目录下,默认为第一个stash,即stash@{0},如果要应用并删除其他stash,命令:
git stash pop stash@{$num}
比如应用并删除第二个:
git stash pop stash@{1}
5.git stash apply
使用apply命令恢复,stash列表中的信息是会继续保留的,而使用pop命令进行恢复,会将stash列表中的信息进行删除。参数同pop
6.删除缓存
git stash drop stash@{num}
删除某个保存,num是可选项,通过git stash list可查看具体值
7.删除所有缓存
# 删除所有缓存的stash
$ git stash clear
8.导出到文件
使用 git stash list 命令查看当前的 stash 栈,并确定要导出的 stash 的索引号。
使用 git stash show -p > stash.diff 命令将 stash 中的修改导出到一个文件中。其中, 是要导出的 stash 的索引号,-p 参数表示显示修改的差异,stash.diff 是导出的文件名。
# 导出stash到文件
git stash show 1 -p > stash.diff
Git常用命令,基于使用步骤
使用步骤
提示:正常步骤应该是:先commit 然后pull 再 push ;
1.git config,配置Git
用于查看和修改Git配置信息,当安装Git后首先要做的事情是设置用户名称和email地址。这是非常重要的,因为每次Git提交都会使用该用户信息
#设置用户信息
git config --global user.name “未进化的程序猿”
git config --global user.email “486566947@qq.com”
#读取配置信息
git config --list
git config user.name
- /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
- ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
- 当前项目的 Git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。
- 提示
- PHPstorm中使用Git时的账号密码同样是Git配置中使用的账号密码
2.初始化仓库,git init
Git 使用 git init 命令来初始化一个 Git 仓库,Git 的很多命令都需要在 Git 的仓库中运行,所以 git init 是使用 Git 的第一个命令。
在执行完成 git init 命令后,Git 仓库会生成一个 .git 目录,该目录包含了资源的所有元数据,其他的项目目录保持不变。
使用当前目录作为 Git 仓库,我们只需使它初始化。
git init
该命令执行完后会在当前目录生成一个 .git 目录。
使用我们指定目录作为Git仓库。
git init newrepo
初始化后,会在 newrepo 目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。
3.将文件纳入版本控制 ,git add
如果当前目录下有几个文件想要纳入版本控制,需要先用 git add 命令告诉 Git 开始对这些文件进行跟踪,然后提交:
# 当前目录下.c结尾的所有文件(通配符)
$ git add *.c
# 指定README等多个文件文件
$ git add README README.md
# .git add all无论在哪个目录执行都会提交相应文件。
$ git add --all
# .git add .只能够提交当前目录或者它后代目录下相应文件。
$ git add .
4. 提交文件 ,git commit
注意
注: 在 Linux 系统中,commit 信息使用单引号 ',Windows 系统,commit 信息使用双引号 "。
所以在 git bash 中 git commit -m '提交说明' 这样是可以的,在 Windows 命令行中就要使用双引号 git commit -m "提交说明"。
暂存区保留本次变动的文件信息,等到修改了差不多了,就要把这些信息写入历史,这就相当于生成了当前项目的一个快照(snapshot)。
项目的历史就是由不同时点的快照构成。Git 可以将项目恢复到任意一个快照。快照在 Git 里面有一个专门名词,叫做 commit,生成快照又称为完成一次提交。
提示
每一次commit都会产生一个新的版本。产生一个代表版本号的散列值
git commit -m '初始化项目版本,记录提交信息'
git commit -am "新增提交说明"
设置了用户名和 Email,保存快照的时候,会记录是谁提交的。
Git文件生命周期
5. 克隆仓库,git clone
我们使用 git clone 从现有 Git 仓库中拷贝项目,Git仓库一般是一个远程连接
git clone
- repo:Git 仓库。
- directory:本地目录。
6. 当前提交状态,git status
git status 命令用于查看在你上次提交之后是否有对文件进行再次修改。
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached ..." to unstage)
new file: README
new file: hello.php
通常我们使用 -s
参数来获得简短的输出结果:
$ git status -s
AM README
A hello.php
AM 状态的意思是这个文件在我们将它添加到缓存之后又有改动。
7. git diff,比较差异
git diff 命令比较文件的不同,即比较文件在暂存区和工作区的差异。(-c 上下文格式的diff、-u 合并格式diff)
git diff 命令显示已写入暂存区和已经被修改但尚未写入暂存区文件的区别。
git diff 有两个主要的应用场景。
- 尚未缓存的改动:git diff
- 查看已缓存的改动: git diff --cached
- 查看已缓存的与未缓存的所有改动:git diff HEAD
- 显示摘要而非整个 diff:git diff --stat
显示暂存区和工作区的差异:
$ git diff [file]
显示两次提交之间的差异:
$ git diff --cached [file]
或
$ git diff --staged [file]
显示暂存区和上一次提交(commit)的差异:
$ git diff [first-branch]...[second-branch]
分支比较
# 直接将两个分支上最新的提交做diff
$ git diff topic master [file]
$ #或
$ git diff topic..master [file]
# 输出自topic和master分别开发以来,master分支上的变更。
$ git diff topic...master
# 查看简单的diff结果,可以加上--stat参数
# --name-only输出时只显示文件名
提示
git diff命令,在终端高度小于输出的文字高度时,会自动生成一个滚动区域,通过上、下来滚动查看文件差异。当窗口高度不够时,输出的内容较少,容易误认为diff差异只有这么点
8. 回退版本,git reset
git reset 命令用于回退版本,可以指定退回某一次提交的版本。只对本地分支有效,对远程分支无效
git reset [--soft | --mixed | --hard] [HEAD]
--mixed 为默认,可以不用带该参数,用于重置暂存区的文件与上一次的提交(commit)保持一致,工作区文件内容保持不变。
git reset [HEAD]
git reset HEAD
执行 git reset HEAD 以取消之前 git add 添加,但不希望包含在下一提交快照中的缓存。
实例:
$ git reset HEAD^ # 回退所有内容到上一个版本
$ git reset HEAD^ hello.php # 回退 hello.php 文件的版本到上一个版本
$ git reset 052e # 回退到指定版本
--soft
参数用于回退到某个版本:
$ git reset --soft HEAD~3 # 回退上上上一个版本
--hard
参数撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除之前的所有信息提交:
$ git reset –hard HEAD~3 # 回退上上上一个版本
$ git reset –hard bae128 # 回退到某个版本回退点之前的所有信息。
$ git reset --hard origin/master # 将本地的状态回退到和远程的一样
- HEAD 表示当前版本
- HEAD^ 上一个版本
- HEAD^^ 上上一个版本
- HEAD^^^ 上上上一个版本
HEAD~0 表示当前版本 - HEAD~1 上一个版本
- HEAD^2 上上一个版本
- HEAD^3 上上上一个版本
9.删除文件,git rm
将文件从暂存区和工作区中删除:
git rm
如果想把文件从暂存区域移除,但仍然希望保留在当前工作目录中,换句话说,仅是从跟踪清单中删除,使用 --cached 选项即可:
git rm --cached
可以递归删除,即如果后面跟的是一个目录做为参数,则会递归删除整个目录中的所有子目录和文件:
git rm –r *
10. 移动目录、文件,git mv
git mv 命令用于移动或重命名一个文件、目录或软连接。
git mv [file] [newfile]
如果新文件名已经存在,但还是要重命名它,可以使用 -f 参数:
git mv -f [file] [newfile]
11.查看提交历史git log、git blame
# 所有历史记录
$ git log
# 简洁版
$ git log --oneline
# 拓扑图版
$ git log --graph
# 反向显示
$ git log --reverse
# 指定提交者
$ git log --author=lius
# 时间范围
$ git log --before={3.weeks.ago} --after={2010-04-18}
# 反向显示
$ git log --reverse
# 指定文件
# git blame
以上多种参数可以组合使用
12. git show
# 查看分支最后一次提交或版本在代码层面的改动
$ git show |
13. git reflog
reflog是reference log的缩写,含义是引用日志,它会记录下HEAD节点和分支引用所指向的历史。可以使用git reflog命令来查看引用日志
14. git gc
gc 命令(gc 指的是垃圾回收)可用于清理版本库,移除所有不属于当前分支的提交对象。
15. git ls-files
查看一下git跟踪了哪些文件,此命令就可以列出所有git正在跟踪的文件
12. 撤销文件 git restore
# 将在 工作区 但是 不在暂存区 的文件撤销更改
$ git restore
# 作用是将 暂存区的文件从暂存区撤出,但不会更改文件
$ git restore --staged
问题记录
1.push和commit的区别
git commit操作的是本地库,git push操作的是远程库。
git commit是将本地修改过的文件提交到本地库中。
git push是将本地库中的最新信息发送给远程库。
如果本地不commit的话,修改的纪录可能会丢失,而有些修改当前是不需要同步至服务器的,所以什么时候同步过去由用户自己选择。什么时候需要同步再push到服务器
2. pull requests 和 merge requests
github可以对不同的用户赋予不同的分支权限,例如Gitlab中的:
- Guest:可以创建issue、发表评论,不能读写版本库
- Reporter:可以克隆代码,不能提交,QA、PM可以赋予这个权限
- Developer:可以克隆代码、开发、提交、push,RD可以赋予这个权限
- Master:可以创建项目、添加tag、保护分支、添加项目成员、编辑项目,核心RD负责人可以赋予这个权限
- Owner:可以设置项目访问权限 - Visibility Level、删除项目、迁移项目、管理组成员,开发组leader可以赋予这个权限
github基于fork的模式下,PR用于请求分支管理员 合并自己提交的代码(理解为请求拉取自己的代码),merge requests同理;
3. fetch和pull的区别
git在本地会保存两个版本的仓库,分为本地仓库和远程仓库。
fetch 只能更新远程仓库的代码为最新的,本地仓库的代码还未被更新,我们需要通过 git merge origin/master 来合并这两个版本,你可以把它理解为合并分支一样的。
pull 操作是将本地仓库和远程仓库(本地的)更新到远程的最新版本。fetch+merge,自动进行合并
4.checkout和reset时的变化
签出切换分支、版本、标签时文件的变化
- 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。加上--hard时会强制替换工作区、暂存区的内容;
- git restore会清除暂存区的修改内容,例如修改了test.vue ,会变为未修改时的内容;
- 当执行 git rm --cached 命令时,会直接从暂存区删除文件,工作区则不做出改变。
5. 保存远程时输入的账号密码
在 git bash 里输入命令:
git config --global credential.helper store
然后执行 git 操作,输入一遍密码后就会记录密码,以后就不用输入了。
要更改记录的用户名和密码,只需要更改用户目录下的 .git-credentials 文件即可。
6.移除文件的版本控制
- 还没有git add,在 .gitignore中添加
- 已经git add,先 git rm -r --cached 文件,然后在 .gitignore中添加
- 已经加到版本控制中,先 git rm -r --cached 文件,然后在 .gitignore中添加,最后 gti commit -m '提交.gitignore'
7.Git问题解决记录
- git config --global credential.helper store,解决SSl连接错误
- 克隆一个指定目录:https://blog.csdn.net/constant_rain/article/details/124913407
8.Github常规目录说明:
- dist 是指编译后的文件,可以理解为压缩发布版
- src 源码文件,适合动手能力强的童鞋
- docs 文档
- examples 示例文件
- test 测试脚本
- .gitignore 告诉git哪些文件不要上传到 GitHub,比如数据库配置文件等
- LICENSE.txt 授权协议
- README.md 自述文件,整个项目的简介、使用方法等
- bower.json Bower 包管理器配置文件
- package.json npm 包管理器配置文件
- 设置某个分支的权限(保护分支)
- 1. 管理员身份登录GitHub,找到项目
2. Settings-->Branches-->Protected branches--->Choose a branch... ,选择需要保护的分支,然后点击edit按钮,
3. Branch protection for 所选的分支名 --> 勾选Restrict who can push to this branch People and teams with push access
若不选择任何人,则任何人都没有push代码到该分支的权限。
9. git做不到一个文件一个分支有一个分支没有
例如一个数据库配置文件,本地和线索不一样,把它从暂存区拉出来,取消追踪变成deleted状态,本地文件实际还存在,同步到分支,远程分支当前版本已经没有这个文件了。
10. Git拉取指定分支
git clone 仓库地址 //默认master分支
git clone -b 分支名 仓库地址 //指定分支
11. git 常见的输出内容
# 已经提交的改变
$ Changes to be committed:
# 暂未提交的改变
$ Changes not staged for commit:
# 常见状态
deleted、modified
12.报错non-fast-forward
non-fast-forward
Dealing with “non-fast-forward” errors:(From time to time you may encounter this error while pushing)
To prevent you from losing history, non-fast-forward updates were rejected. Merge the remote changes before pushing again. See the 'non-fast forward' section of 'git push --help' for details.
This error can be a bit overwhelming at first, do not fear. Simply put, git cannot make the change on the remote without losing commits, so it refuses the push. Usually this is caused by another user pushing to the same branch. You can remedy this by fetching and merging the remote branch, or using pull to perform both at once.
In other cases this error is a result of destructive changes made locally by using commands like git commit --amend or git rebase. While you can override the remote by adding --force to the push command, you should only do so if you are absolutely certain this is what you want to do. Force-pushes can cause issues for other users that have fetched the remote branch, and is considered bad practice. When in doubt, don’t force-push.
以上时较为官方的解释,简单说就是push之前需要先同步远程版本。pull会自动合并,所以要改为fetch手动合并;
问题分析
可以这样理解这个问题就是:别人上传到远程仓库后,你没有及时的同步(、拉取)到本地,但是你同时又添加了一些内容(提交),以致于你在提交时,它会检测到你之前从远程仓库拉取的时候的仓库状态和现在的不一样。于是,它为了安全起见拒绝了你的提交(然后就报了这个错误)。
“不能快速前进”的原因是因为路不一样了,变得不好走了;体现在git里面就是提交历史出现分叉,主线不再是一条直线,而是在前端出现了分叉,git不知道该如何前进,所以报错了,让你来觉得走哪条路!说的简单点,就是远程仓库和本地仓库不同步了
解决问题
1.先合并之前的历史,再进行提交——提倡使用
先把git的东西fetch到你本地然后merge后再push,(如果有冲突就要解决冲突后再合并,冲突问题比较复杂,这里就不详细说了),这样就可以使远程仓库和你本地仓库一致了,然后就可以提交修改了。
$ git fetch origin master
$ git merge origin FETCH_HEAD
# 提示:这2句命令等价于,但是使用git fetch + git merge 更加安全。
$ git pull origin master
# 然后执行,重新定基,可以使得历史更加统一,即提交历史趋向于一条直线。
$ git pull --rebase origin master
2.丢弃之前的历史,强推(谨慎使用)
利用强覆盖方式用你本地的代码替代git仓库内的内容,远程仓库的东西会被本地仓库覆盖!!!
$ git push -f 或者 $ git push --force
官方文档提示:This flag disables these checks, and can cause the remote repository to lose commits; use it with care.
(即:此标志禁用这些检查,并可能导致远程存储库丢失提交;小心使用。)
不仅在此处,在平时使用时,也要非常注意,除非你真的是想覆盖远程仓库,不然最好不要强制执行。
13.彻底清理历史版本
先创建一个分支,添加所有文件,删除其他所有分支。
14.回退远程仓库的版本
先把远程仓库指定分支拉下来,手动回退,然后再强制推送上去(拉回来的远程版本库同时带了这个分支的所有历史版本);
Gitee和Github
1.查看fork的子仓
- github点击 Insights -> fork
- gitee点击fork边上的数字
2.保护分支
- github默认只有分支创建者和仓库管理员有push的权限,其他人可以提交PR
- gitee需要自己设置分支为保护分支,并在保护分支的设置内设置相应的保护级别和权限。
- github可以邀请别人成为协作者,个人仓库的协作者可以拉取(读取)仓库的内容并向仓库推送(写入)更改。
Markdown语法
- 使用 = 和 - 标记一级和二级标题
- 使用 # 号可表示 1-6 级标题,一级标题对应一个 # 号,二级标题对应两个 # 号,以此类推。
- 可以在一行中用三个以上的星号、减号、底线来建立一个分隔线,行内不能有其他东西。
- 如果段落上的文字要添加删除线,只需要在文字的两端加上两个波浪线 ~~ 即可
- 带下划线文本,以下是列表
- 有序列表使用数字并加上 . 号来表示,如:
- Markdown 区块引用是在段落开头使用 > 符号 ,然后后面紧跟一个空格符号,如果要在列表项目内放进区块,那么就需要在 > 前添加四个空格的缩进。:
- 代码区块使用 4 个空格或者一个制表符(Tab 键)。如果是段落上的一个函数或片段的代码可以用反引号把它包起来(`)。
- 链接使用方法如下: [链接名称](链接地址),我们可以通过变量来设置一个链接,变量赋值在文档末尾进行:
- Markdown 图片语法格式如下:![alt 属性文本](图片地址 "可选标题"),表格语法如下:
- ``,表示单行代码,``` ```,用于表示多行代码。
资料来源:https://www.runoob.com/git/git-tutorial.html、http://git-scm.com/docs
查看Git命令的帮助信息,git
1.Git 工作区、暂存区和版本库(以本地举例)、远程仓库
- 工作区:就是你在电脑里能看到的目录。
- 暂存区:英文叫
stage
或index
。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。 - 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。
- Git 工作区、暂存区和版本库
- 图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage/index),标记为 "master" 的是 master 分支所代表的目录树。
- 图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
- 图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下,里面包含了创建的各种对象及内容。
- 当对工作区修改(或新增)的文件执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
- 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
- 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
- 当执行 git rm --cached 命令时,会直接从暂存区删除文件,工作区则不做出改变。
- 当执行 git checkout . 或者 git checkout -- 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
- 当执行 git checkout HEAD . 或者 git checkout HEAD 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
2.Git文件状态
在Git中文件大概分为四种状态:已修改(modified)、已暂存(staged)、已提交(committed)、未追踪(Untrack)
- .gitignore内的文件,不会拥有任何一种状态,被git彻底无视。
- 处于ignore列表的文件,无法被add添加;但是可以强制添加
- 空目录、以及子目录全部是空目录的目录不会有Untrack状态,也无法通过add改变状态(无效)
- 工作目录新增文件时,只要不处于ignore目录,都会变成Untrack状态;
- 没有add过的文件或者被restore(不带--staged)的文件,处于Untrack状态;
- 初次add和被add后产生修改的文件,会处于modifed状态。
- 处于modified状态的文件,最开始可以进行add和restore两种操作,此时的add操作叫做
更新要提交的内容
,add后变为staged状态,restore(不加staged标记)后变为Untrack; - add后变为staged状态的文件,可用restore --staged 变回modified状态;这个staged状态的内容可以用来恢复内容。没有被add的modified状态文件内容没有被记录(虽然有撤回,但是本质不一样);
- 处于staged状态的文件,在没有commit之前再次产生修改时,会同时具有staged和modified两个状态(可以把statged状态的内容拉回来,覆盖。);但是commit时会使用内容最新的那个状态;
- commit会提交所有staged状态的文件,所以commit可以理解有一个modified到staged状态的过程(实际可能不存在,因为暂存区本来就有变动的记录);所以暂存状态不能理解为处于暂存区,应当指的是被纳入下一次提交的文件;任何被追踪的产生修改的文件都会在暂存区被记录;成为下一次提交的一部分;
- 未被追踪的文件被删除时,不会产生git状态。处于modofy未add时,会变成deleted状态;处于staged状态会保持暂存状态;
- 已经被删除的(deleted状态)被追踪的文件,恢复后会变成modified状态;
- 提示
- add的作用是将文件添加到暂存区,只有被add的文件才会被追踪
- 暂存区
- (1)所谓的暂存区只是一个简单的索引文件而已。
(2)暂存区这个索引文件里面包含的是文件的目录树,像一个虚拟的工作区,在这个虚拟工作区的目录树中,记录了文件名、文件的时间戳、文件长度、文件类型以及最重要的SHA-1值,文件的内容并没有存储在其中,所以说 它像一个虚拟的工作区。
(3)索引指向的是.Git/objects下的文件。
(4)暂存区的作用:除非是绕过暂存区直接提交,否则Git想把修改提交上去,就必须将修改存入暂存区最后才能commit。每次提交的是暂存区所对应的文件快照。
拓展:status提示信息
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
- 既然是Changes not staged for commit,就说明出现这个提示下的所有文件改动,都是存在于工作区的。stage是暂存区的意思,not stage说明都不在暂存区,那么说明在工作区。
- (use “git add …” to update what will be committed)。执行这条命令就可以工作区里面的改变加入到暂存区。可以执行git add .把当前目录下所有改动加入暂存区。
- (use “git checkout – …” to discard changes in working directory)。执行这条命令将丢弃在工作区的改动。可以执行git checkout *把当前目录下所有工作区的改动丢弃掉
Untracked files:
(use "git add ..." to include in what will be committed)
- Untracked files,就说明出现这个提示下的所有文件都是当前HEAD没有被加入过的文件。这种改动也属于工作区。
- (use “git add …” to include in what will be committed)。把Untracked files加入暂存区。
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
- 当前分支比远程分支多了一次commit
Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively
pull报错了,查看状态显示这个,先留着待解决吧
3.HEAD是什么
HEAD是Git中非常重要的一个概念,你可以称它为指针或者引用,它可以指向任意一个节点,并且指向的节点始终为当前工作目录,换句话说就是当前工作目录(也就是你所看到的代码)就是HEAD指向的节点。
4.git重命名检测
Git 采用了不同的方法:它没有选择去存储与文件移动操作相关的信息,而是采用了重命名检测算法。在该算法中,如果一个文件在某一次提交中消失了,它依然会存在于其前次提交,而如果某个拥有相同名字或相似内容的文件出现在了另一个位置,Git 就会自动检测到。如果是这种情况,Git 就会假定该文件被移动过了。
Git项目文件说明
Git init后主要有两个重要的文件和目录:.git目录和.gitignore
1. .gitignore
.gitignore文件存在于根目录(与.git同级的目录)用于在将文件提交到git暂存区时,指定将哪些文件排除;
有时候你想添加(git add)一个文件到Git,但发现添加不了,多半原因是这个文件被.gitignore
忽略了
git add .不会添加被.gitignore忽视的文件,而git add -f . 强制添加所有文件,即使是.gitignore忽视的文件也添加。
当.gitignore文件不是你编写的,但是它编写的不符合实际需求,你可以使用git check-ignore命令进行检查,看是哪一个规则有问题了
#检测
git check-ignore -v App.class
#结果
.gitignore:3:*.class App.class
.gitignore只能忽略那些原来没有被track的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。解决方法就是先把本地缓存删除(改变成未track状态),然后再提交。
git rm -r --cached .
git add .
git commit -m ‘update .gitignore’
也可以手动指定一个文件作为git忽略文件
git config core.excludesfile ***
对于全局Git配置,可以使用如下命令对全部仓库进行配置。
git config --global core.excludesfile **/.gitignore(文件相对或绝对位置)
忽略规则如下:
- 空格不匹配任意文件,可作为分隔符,可用反斜杠转义
- #开头的文件标识注释,可以使用反斜杠进行转义
- ! 开头的模式标识否定,该文件将会再次被包含,如果排除了该文件的父级目录,则使用 ! 也不会再次被包含。可以使用反斜杠进行转义
- / 结束的模式只匹配文件夹以及在该文件夹路径下的内容,但是不匹配该文件
- / 开始的模式匹配项目跟目录
- 如果一个模式不包含斜杠,则它匹配相对于当前 .gitignore 文件路径的内容,如果该模式不在 .gitignore 文件中,则相对于项目根目录
- ** 匹配多级目录,可在开始,中间,结束
- ? 通用匹配单个字符
- [] 通用匹配单个字符列表
- 各种项目的gitignore
- 参考地址:https://github.com/github/gitignore
2. .git目录
任意文件夹中,用 git init 命令初始化仓库,即可在此文件夹下创建 .git 文件夹(.打头为隐藏文件夹,所以平时可能看不到)。这个文件夹之外的部分叫做工作区(Working Directory),.git 文件夹我们称做 Git仓库 (Git Repository)。 通常会有7个文件5个目录,常见目录如下:
COMMIT_EDITMSG
HEAD
ORIG_HEAD
FETCH_HEAD
config
description
index
hooks/
info/
logs/
objects/
refs/
1. 文件 COMMIT_EDITMSG
此文件是一个临时文件,存储最后一次提交的信息内容,git commit 命令之后打开的编辑器就是在编辑此文件,而你退出编辑器后,git 会把此文件内容写入 commit 记录。
实际应用: git pull 远程仓库后,新增了很多提交,淹没了本地提交记录,直接 cat .git/COMMIT_EDITMSG 就可以弄清楚最后工作的位置了。
2. HEAD
此文件永远存储当前位置指针,就像 linux 中的 $PWD 变量和命令提示符的箭头一样,永远指向当前位置,表明当前的工作位置。在 git 中 HEAD 永远指向当前正在工作的那个 commit。(孤立HEAD?????)
HEAD 存储一个分支的 ref,Linux中运行:cat .git/HEAD 通常会显示:
ref: refs/heads/master
这说明你目前正在 master 分支工作。此时你的任何 commit,默认自动附加到 master 分支之上
git cat-file -p HEAD
, 显示详细的提交信息:
tree 4cbb261560348e1727b5137f3ab6eceae8e1f34d
parent 22c457fe24f737505356edfb8696c7e50fd9d971
author Evan You 1654857613 +0800
committer Evan You 1654857613 +0800
chore: test pass
孤立head,不指向任何commit
3. ORIG_HEAD
正因为 HEAD 比较重要,此文件会在你进行危险操作时备份 HEAD,如以下操作时会触发备份
git reset
git merge
git rebase
git pull
此文件应用示例
# 回滚到上一次的状态(慎用!!!)
git reset --hard ORIG_HEAD
4. FETCH_HEAD
这个文件作用在于追踪远程分支的拉取与合并,与其相关的命令有 git pull/fetch/merge
,而git pull 命令相当于执行以下两条命令:
$ git fetch
$ git merge FETCH_HEAD
# 显示如下>>>
From https://github.com/xxx/xxxx
* branch master -> FETCH_HEAD
Updating f785638..59db1b2
此时会默默备份 HEAD 到 ORIG_HEAD
5. config
此文件存储项目本地的 git 设置,典型内容如下:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
[remote "origin"]
url = git@gitlab.xxxx.com/xxx.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[branch "v2.6.0"]
remote = origin
merge = refs/heads/v2.6.0
[branch "v2.8.0"]
remote = origin
merge = refs/heads/v2.8.0
[core]
段的内容跟 git config 命令对应
执行以下命令:
git config user.name abc
git config user.email abc@abc.com
会在 config 文件中追加以下内容:
... ...
[user]
name = abc
email = abc@abc.com
git config --global 影响的则是全局配置文件 ~/.gitconfig。
[remote] 段表示远程仓库配置
[branch] 段表示分支同步设置
假设当前在 master 分支,执行 git pull 若出现以下提示:
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull 就说明 .git/config 文件缺少对应的 [branch "master"] 字段。
解决方案为:
git branch -u origin/master master
# 或者执行一次 push
git push -u origin master
会出现提示:
Branch 'master' set up to track remote branch 'master' from 'origin'.
其实就是生成以下内容在 .git/config中:
[branch "master"]
remote = origin
merge = refs/heads/master
手动编辑 .git/config,效果一样。这就是 upstream 的真正含义,即生成 config 中的这段配置。
6. description
说明这个文件主要用于 GitWeb 的描述,如果要启动 GitWeb 可用如下命令:
# 确保lighttpd已安装: brew install lighttpd
$ git instaweb --start
默认会启动 lighttpd 服务并打开浏览器 http://127.0.0.1:1234 (试着改成对外IP并分享给别人?)
以下显示当前的 git 仓库名称以及描述,默认的描述如下:
默认描述
上面这段话就是默认的 description
文件的内容,编辑这个文件来让你 GitWeb
描述更友好。
7. hooks/目录
存放 git hooks,用于在 git 命令前后做检查或做些自定义动作。运行 ls -F1 .git/hooks
prepare-commit-msg.sample # git commit 之前,编辑器启动之前触发,传入 COMMIT_FILE,COMMIT_SOURCE,SHA1
commit-msg.sample # git commit 之前,编辑器退出后触发,传入 COMMIT_EDITMSG 文件名
pre-commit.sample # git commit 之前,commit-msg 通过后触发,譬如校验文件名是否含中文
pre-push.sample # git push 之前触发
pre-receive.sample # git push 之后,服务端更新 ref 前触发
update.sample # git push 之后,服务端更新每一个 ref 时触发,用于针对每个 ref 作校验等
post-update.sample # git push 之后,服务端更新 ref 后触发
pre-rebase.sample # git rebase 之前触发,传入 rebase 分支作参数
applypatch-msg.sample # 用于 git am 命令提交信息校验
pre-applypatch.sample # 用于 git am 命令执行前动作
fsmonitor-watchman.sample # 配合 core.fsmonitor 设置来更好监测文件变化
参考
https://git-scm.com/docs/githooks
如果要启用某个 hook,只需把 .sample 删除即可,然后编辑其内容来实现相应的逻辑。
比如要校验每个 commit message 至少要包含两个单词,否则就提示并拒绝提交,将 commit-msg.sample 改为 commit-msg 后,编辑如下:
#!/bin/sh
grep -q 'Ss+S' $1 || { echo '提交信息至少为两个单词' && exit 1; }
这样当提交一个 commit 时,会执行 bash 命令: .git/hooks/commit-msg .git/COMMIT_EDITMSG,退出值不为 0,就拒绝提交。
8. info/目录
此文件夹基本就有两个文件:
- 文件 info/exclude 用于排除规则,与 .gitignore 功能类似。
- 可能会包含文件 info/refs ,用于跟踪各分支的信息。此文件一般通过命令 git update-server-info 生成,内容通常如下:
9. logs/目录
记录了操作信息,git reflog 命令以及像 HEAD@{1} 形式的路径会用到。如果删除此文件夹(危险!),那么依赖于 reflog 的命令就会报错。
文件夹 objects/
此文件夹简单说,就是 git的数据库,运行 tree .git/objects,可以看到目录结构:
.git/objects/
|-- 0c
| `-- d370696b581c38ee01e62b148a759f80facc2d
|-- 59
| `-- 3d5b490556791212acd5a516a37bbfa05d44dd
|-- 61
| `-- be44eedde61d723e5761577a2b420ba0fc2794
|-- 64
| `-- c0aed8ddcbb546bdcec2848938fc82348db227
|-- d4
| `-- 9904676ce8ddde276bdbfa9bbec313e90e0f50
|-- info
`-- pack
|-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.idx
`-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.pack
这些文件分两种形式:pack压缩包 形式放在 pack/ 目录下,除此之外都是 hash文件 形式,被叫做 loost objects。
这个文件夹以及相应的算法,我没找到独立的名称,就叫它 hash-object 体系吧。因为确实有个 git hash-object 命令存在,是一个底层的负责生成这些 loost objects 文件,如果要看到这些文件各自的含义,执行以下命令:
git cat-file --batch-check --batch-all-objects
可以看到
04c87c65f142f33945f2f5951cf7801a32dfa240 commit 194
098217953a6ca169bed33d2be8a07d584fcdaf30 tree 31
0cd370696b581c38ee01e62b148a759f80facc2d commit 245
2a810017bfc85d7db2627f4aabdaa1583212bda3 blob 19
3920a07c1d5694df6b8658592b0939241d70e9e5 tree 93
593d5b490556791212acd5a516a37bbfa05d44dd tag 148
61be44eedde61d723e5761577a2b420ba0fc2794 tree 154
... ...
但你会发现这个列表里有些值在文件夹中并不存在,因为除了 loost objects 它还汇总了 pack 文件中的内容。hash文件
又称为 loose object
,文件名称共由40字符的 SHA-1 hash 值组成,其中前两个字符为文件夹分桶,后38个字符为文件名称。
按文件内容可分为四种类型:commit, tree, blob, tag,若执行以下命令会生成所有四种类型:
echo -en 'xxn' > xx # 共 3 个字符
git add .
git commit -m 'update xx'
git tag -a 'v1.0' -m 'release: 1.0.0'
经过以上操作后,对比一下文件树,发现多了四个 hash文件:
|-- 0c
| `-- d370696b581c38ee01e62b148a759f80facc2d
|-- 18
| `-- 143661f96845f11e0b4ab7312bdc0f356834ce
|-- 30
| `-- 20feea86d222d83218eb3eb5aa9f58f73df04d
|-- 59
| `-- 3d5b490556791212acd5a516a37bbfa05d44dd
|-- 61
| `-- be44eedde61d723e5761577a2b420ba0fc2794
|-- 64
| `-- c0aed8ddcbb546bdcec2848938fc82348db227
|-- ad
| `-- f4c9afac7afae3ff3e95e6c4eefe009d547f00
|-- cc
| `-- c9bd67dc5c467859102d53d54c5ce851273bdd
|-- d4
| `-- 9904676ce8ddde276bdbfa9bbec313e90e0f50
|-- info
`-- pack
|-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.idx
`-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.pack
这四个 hash文件 分别是:
cc/c9bd67dc5c467859102d53d54c5ce851273bdd # blob
30/20feea86d222d83218eb3eb5aa9f58f73df04d # commit
ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00 # tree
18/143661f96845f11e0b4ab7312bdc0f356834ce # tag
其实这些文件都经过了压缩,压缩形式为 zlib。先安装一下解压工具 macOS 版 brew install pigz 或 windows 版 pigz,后执行:
$ pigz -d < .git/objects/cc/c9bd67dc5c467859102d53d54c5ce851273bdd
# BLOB类型,显示结果为>>>>(注意xx后有个n)
blob 3xx
$pigz -d < .git/objects/30/20feea86d222d83218eb3eb5aa9f58f73df04d
# COMMIT类型,显示结果为>>>>
commit 248tree adf4c9afac7afae3ff3e95e6c4eefe009d547f00
parent 0cd370696b581c38ee01e62b148a759f80facc2d
author jamesyang.yjm 1562044880 +0800
committer jamesyang.yjm 1562044880 +0800
update xx
$ pigz -d < .git/objects/ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00
# TREE类型,显示结果为>>>>
tree 154100644 abc*???]}?bJ?ڡX2??100644 asdf???CK?)?wZ???S?100644 iou???CK?)?wZ???S?100644 xx?ɽg?FxY-S?L?Q';?100644 yy???CK?)?wZ???S?
$ pigz -d < .git/objects/18/143661f96845f11e0b4ab7312bdc0f356834ce
# TAG类型,显示结果为>>>>
tag 155object 3020feea86d222d83218eb3eb5aa9f58f73df04d
type commit
tag v1.0
tagger jamesyang.yjm 1562045942 +0800
release: 1.0.0
会发现,显示结果都是 type size+内容 形式,这就是 object 文件的存储格式:
[type] [size][NULL][content]
type
可选值:commit, tree, blob, tag,NULL 就是C语言里的字符结束符:�,size 就是 NULL后内容的字节长度。
type
的几种类型可以使用 git cat-file -t hash 看到,内容可以用 git cat-file -p hash 看到。
git cat-file -t ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 显示结果为>>>>
blob
git cat-file -p ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 显示结果为>>>>
xx
所以 blob 文件就是对原文件内容的全量拷贝,同时前面加了 blob size�,而文件名称的 hash 值计算是计算整体字符的 SHA-1 值:
echo -en 'blob 3�xxn' | shasum
# 显示结果为>>>>
ccc9bd67dc5c467859102d53d54c5ce851273bdd -
知道原理后,其它类型格式请自行参考 斯坦福 Ben Lynn 所著的 GitMagic。
所以,当我们 git show 3020feea86d222d83218eb3eb5aa9f58f73df04d
时,会发生些什么?
找到 3020feea86d222d83218eb3eb5aa9f58f73df04d 这个 commit
,显示出来
找到此 commit 关联的 tree object: adf4c9afac7afae3ff3e95e6c4eefe009d547f00
,拉取相应的 blob 文件,并与当前工作区内的文件做 diff,然后显示出来
这就是 objects/ 文件夹作为 git数据库 被使用的真实例子。pack文件
为什么会有 .pack 文件?
由于每次 commit 都会生成许多 hash文件,并且由于 blob 文件都是全量存储的,导致 git 效率下降,于是有了 pack-format,优势:
对于大仓库存储效率高
利于网络传输,便于备份
增量存储,优化磁盘空间
将 .git/objects 下的部分文件打包成 pack格式
$ tree .git/objects/ | wc -l
311
$ git gc
Enumerating objects: 288, done.
Counting objects: 100% (288/288), done.
Delta compression using up to 4 threads
Compressing objects: 100% (287/287), done.
Writing objects: 100% (288/288), done.
Total 288 (delta 131), reused 90 (delta 0)
$ tree .git/objects/ | wc -l
12
可以看到文件数量减小了不少,其中大部分文件被打到一个 .pack 包中,并且是增量存储,有部分变更的文件只存储 基础hash + 变更内容,磁盘空间优化很明显。
git gc 其实运行了两条命令:git repack 用来打包 和 git prune-packed 用来移除已打包的 hash文件;
11.文件夹refs
refs 可以理解成文件系统中的 symbol link,看下结构:
$ tree .git/refs/
.git/refs
|-- heads
| `-- master
`-- tags
`-- v1.0
$ cat .git/refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
$ cat .git/refs/tags/v1.0
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
$ git cat-file -t 5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
commit
可以看到 master 和 v1.0 都指向 5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 这个 commit。
refs/heads/ 文件夹内的 ref 一般通过 git branch
生成。git show-ref --heads
可以查看。
refs/tags/ 文件夹内的 ref 一般通过 git tag 生成。git show-ref --tags
可以查看。
如下:
$ git branch abc
$ tree .git/refs/
.git/refs/
|-- heads
| |-- abc
| `-- master
`-- tags
`-- v1.0
$ cat .git/refs/heads/abc
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
说明新建分支其实就是生成了一个指向某个 commit 的 symbol link,当然在这里叫做 ref。
而 git tag
命令本质与 git branch
相同,只生成一个 ref 放在 tags 目录下,所以被称为 lightweight tag。
而 git tag -a xx
命令会首先生成一个类型为 tag 的 hash文件 放到 objects/ 目录,然后生成 ref 放到 tags 目录下指向那个文件。这就叫做 annotated tag,好处是可包含一些元信息如 tagger 和 message,被 git 的 hash-object 算法管理,可被 GPG 签名等,所以更稳定,更安全。
使用以下命令来拿到 refs 文件夹存储的信息:
$ git show-ref --head --dereference
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/abc
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/v1.0
5e84371048faa20412f5492e6af264a7e1edfec1 refs/tags/xx
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/xx^{}
我们来看这些信息如何变化的:
$ touch new_file && git add . && git commit -m 'add new_file'
[master 44b0d05] add new_file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 new_file
$ git show-ref --head --dereference
44b0d05ddadaaa8d2cc40d6647cc474b26f5d8d3 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/abc
44b0d05ddadaaa8d2cc40d6647cc474b26f5d8d3 refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/v1.0
5e84371048faa20412f5492e6af264a7e1edfec1 refs/tags/xx
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/xx^{}
diff 一下可以看到:
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/master
这两行发生了变化。也就是每次 commit 时,HEAD 与 heads 都会自动更新。
12. index文件
文件保存成二进制对象以后,还需要通知 Git 哪些文件发生了变动。所有变动的文件,Git 都记录在一个区域,叫做"暂存区"(英文叫做 index 或者 stage)。等到变动告一段落,再统一把暂存区里面的文件写入正式的版本历史。
git update-index命令用于在暂存区记录一个发生变动的文件。
$ git update-index --add --cacheinfo 100644
3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt
上面命令向暂存区写入文件名test.txt、二进制对象名(哈希值)和文件权限。
git ls-files命令可以显示暂存区当前的内容。
$ git ls-files --stage
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 test.txt
上面代码表示,暂存区现在只有一个文件test.txt,以及它的二进制对象名和权限。知道了二进制对象名,就可以在.git/objects子目录里面读出这个文件的内容。
git status命令会产生更可读的结果。
$ git status
要提交的变更:
新文件: test.txt
上面代码表示,暂存区里面只有一个新文件test.txt,等待写入历史。
资料来源
参考:https://developer.aliyun.com/article/716483
Git远程仓库
Git 并不像 SVN 那样有个中心服务器。目前我们使用到的 Git 命令都是在本地执行,如果你想通过 Git 分享你的代码或者与其他开发人员合作。 你就需要将数据放到一台其他开发人员能够连接的服务器上。
1.添加远程仓库
git remote add [shortname] [url] #添加远程仓库
git remote rm name # 删除远程仓库
git remote rename old_name new_name # 修改仓库名
2.查看远端仓库
$ git remote
origin
$ git remote -v
origin git@github.com:tianqixin/runoob-git-test.git (fetch)
origin git@github.com:tianqixin/runoob-git-test.git (push)
3.获取远端仓库代码 git fetch
# 只能fetch到一个空白的分支,然后可以手动merge
$ git fetch <远程主机名> <远程分支名>:<本地分支名>
不填的话都是默认
4.拉取 git pull
git pull <远程主机名> <远程分支名>:<本地分支名>
# 允许合并不相关的分支
$ git pull --allow-unrelated-histories
git pull操作其实是git fetch 与 git merge 两个命令的集合。 git fetch 和 git merge FETCH_HEAD 的简写。
相关文档:https://www.runoob.com/git/git-remote-repo.html
5.推送 git push
# 基本
$ git push <远程主机名> <本地分支名>:<远程分支名>
# 强制推送
$ git push --force origin master
# 删除远程分支
$ git push origin --delete master
# 允许合并不相关的分支
$ git push --allow-unrelated-histories
提示
如果另一个开发者在我们之前已经做过一次 push 操作,此次 push 命令就会被拒绝传送提交。这时候,我们必须要先做一次 pull 操作,将其他人新上载的更新取回,并本地合并。
如果本地分支名与远程分支名相同,则可以省略冒号,带上-u 参数相当于记录了push到远端分支的默认值,这样当下次我们还想要继续push的这个远端分支的时候推送命令就可以简写成git push即可。
Git 分支
1.创建分支命令 git branch
# 创建分支
$ git branch
# 创建分支并跟踪远程分支
$ git branch -u o/master foo
2.切换分支命令 git checkout
第一作用是切换分支,第二个是撤销修改。
# 切换指定分支
$ git checkout ||
# 创建一个的分支,它跟踪远程分支
$ git checkout -b 本地分支名x origin/远程分支名x
# 从暂存区恢复到工作区
$ git checkout .
提示
当你切换分支的时候,Git 会用该分支的最后提交的快照替换你的工作目录的内容, 所以多个分支不需要多个目录。
实际测试:
假设分支1 a文件未提交,分支2 a文件已提交。切换到到分支2时会替换 分支1的a文件。1切换到2时也会替换a文件;
两个分支都已经提交的 切换时会互相替换。一个提交一个没提交时,从a到b,b会保持a的暂存区和工作区
3.合并分支命令 git merge
# 合并指定分支到当前分支
$ git merge
4.删除分支 git branch -d
# 删除指定分支
$ git branch -d
5.分支列表 git branch
# 列出所有分支
$ git branch
# 查看远程所有分支
$ git branch -r
# 查看本地和远程所有分支
$ git branch -a
列出分支时,带*号的分支为当前活动分支
5.重命名分支 git branch -M
# 重命名指定分支
# 不填old默认重命名当前分支
$ git branch -m old new
# 强制重命名指定分支
$ git branch -M old new
git rebase 变基
1.介绍
Git rebase,通常被称作变基或衍合, 可以理解为另外一种合并的方式,与merge 会保留分支结构和原始提交记录不同,rebase 是在公共祖先的基础上,把新的提交链截取下来,在目标分支上进行重放,逐个应用选中的提交来完成合并。
不同公司,不同情况有不同使用场景,不过大部分情况推荐如下:
自己单机的时候,拉公共分支最新代码的时候使用rebase,也就是git pull -r或git pull --rebase。这样的好处很明显,提交记录会比较简洁。但有个缺点就是rebase以后我就不知道我的当前分支最早是从哪个分支拉出来的了,因为基底变了嘛,所以看个人需求了。
往公共分支上合代码的时候,使用merge。如果使用rebase,那么其他开发人员想看主分支的历史,就不是原来的历史了,历史已经被你篡改了。举个例子解释下,比如张三和李四从共同的节点拉出来开发,张三先开发完提交了两次然后merge上去了,李四后来开发完如果rebase上去(注意李四需要切换到自己本地的主分支,假设先pull了张三的最新改动下来,然后执行,然后再git push到远端),则李四的新提交变成了张三的新提交的新基底,本来李四的提交是最新的,结果最新的提交显示反而是张三的,就乱套了。
正因如此,大部分公司其实会禁用rebase,不管是拉代码还是push代码统一都使用merge,虽然会多出无意义的一条提交记录“Merge … to …”,但至少能清楚地知道主线上谁合了的代码以及他们合代码的时间先后顺序
2.原理
变基操作的工作原理很简单:Git 会让我们想要移动的提交序列在目标分支上按照相同的顺序重新再现一遍。这就相当于我们为各个原提交做了个副本,它们拥有相同的修改集、同一作者、日期以及注释信息。
3.命令
# 可以是commit 版本号、分支名称,合并多个提交到一个版本
$ git rebase -i [startpoint] [endpoint]
# 变基发生冲突时,解决后继续变基
$ git rebase --continue
# 无视冲突,继续变基操作
$ git rebase --skip
# 发生冲突时中断变基
$ git rebase --abort"
-i的意思是--interactive,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint]则指定了一个编辑区间,如果不指定[endpoint],则该区间的终点默认是当前分支HEAD所指向的commit(注:该区间指定的是一个前开后闭的区间)。
Git 标签
Git 中的tag指向一次commit的id,通常用来给开发分支做一个标记,如标记一个版本号。
1.添加标签
git tag -a version -m "note"
注解:git tag 是打标签的命令,-a 是添加标签,其后要跟新标签号,-m 及后面的字符串是对该标签的注释。
2.提交标签到远程仓库
git push origin -tags
注解:就像git push origin master 把本地修改提交到远程仓库一样,-tags可以把本地的打的标签全部提交到远程仓库。
3.删除标签
git tag -d version
注解:-d 表示删除,后面跟要删除的tag名字
4.删除远程标签
git push origin :refs/tags/version
注解:就像git push origin :branch_1
可以删除远程仓库的分支branch_1一样, 冒号前为空表示删除远程仓库的tag。
5.查看标签
git tag或者git tag -l
Git补丁
1.git fromat-patch
git format-patch 命令用于将 Git 仓库中的提交记录转换为补丁文件。补丁文件可以用于将更改应用到其他 Git 仓库或版本控制系统中。
# 生成单个补丁文件
git format-patch
# 和 是要生成补丁文件的提交范围
git format-patch ..
# -n 选项可以指定要生成的补丁文件数量。
git format-patch -n ..
# --stdout 选项可以将补丁文件输出到标准输出中,包括提交信息和更改内容。
git format-patch --stdout
# --no-signature 选项可以生成不包含提交信息的补丁文件,只包含更改内容
git format-patch --no-signature
2.git apply
# 应用单个补丁文件,将补丁文件中的更改应用到工作目录中。
git apply
# 应用多个补丁文件
git apply ...
# --ignore-space-change 选项可以忽略补丁文件中的空格更改。
git apply --ignore-space-change
# 应用补丁文件并忽略空格和换行符:
git apply --ignore-whitespace --ignore-whitespace
# --index 选项可以将更改添加到暂存区中,而不是仅仅应用更改到工作目录中。
git apply --index
3.git am
- git am 和 git apply 命令都用于将补丁文件应用到 Git 仓库中,但它们之间有一些区别。
- git am 命令可以自动创建一个新的提交,而 git apply 命令只会将更改应用到工作目录中,需要手动创建一个新的提交。
- git am 命令可以应用交互式补丁,以便手动选择要应用的更改,而 git apply 命令只能应用非交互式补丁。
- git am 命令可以应用多个补丁文件,并自动创建多个新的提交,而 git apply 命令只能应用一个补丁文件。
- git am 命令通常用于应用由 git format-patch 命令生成的补丁文件,而 git apply 命令可以应用由其他方式生成的补丁文件。
Git存储
git stash将本地未提交代码作为一个本地缓存。作用范围为本地工作区以及本地暂存区。
1.使用场景
- 当正在dev分支上开发某个项目,这时项目中出现一个bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用git stash命令将修改的内容保存至堆栈区,然后顺利切换到hotfix分支进行bug修复,修复完成后,再次切回到dev分支,从堆栈中恢复刚刚保存的内容。
- 由于疏忽,本应该在dev分支开发的内容,却在master上进行了开发,需要重新切回到dev分支上进行开发,可以用git stash将内容保存至堆栈中,切回到dev分支后,再次恢复内容即可。
2.git stash
# 保存当前工作区、暂存区的所有未提交代码
# 执行存储时,添加备注,方便查找
# git stash save “save message”
只有git stash 也是可以的,但查找时不方便识别。
3.查看stash列表
# 查看stash了哪些存储
$ git stash list
# 显示缓存的详细信息,n为第几个,默认数第一个,也就是0
$ git stash show stash@{0}
4.恢复缓存
git stash pop
命令恢复之前缓存的工作目录,将缓存堆栈中的对应stash删除,并将对应修改应用到当前的工作目录下,默认为第一个stash,即stash@{0},如果要应用并删除其他stash,命令:
git stash pop stash@{$num}
比如应用并删除第二个:
git stash pop stash@{1}
5.git stash apply
使用apply命令恢复,stash列表中的信息是会继续保留的,而使用pop命令进行恢复,会将stash列表中的信息进行删除。参数同pop
6.删除缓存
git stash drop stash@{num}
删除某个保存,num是可选项,通过git stash list可查看具体值
7.删除所有缓存
# 删除所有缓存的stash
$ git stash clear
8.导出到文件
使用 git stash list 命令查看当前的 stash 栈,并确定要导出的 stash 的索引号。
使用 git stash show -p > stash.diff 命令将 stash 中的修改导出到一个文件中。其中, 是要导出的 stash 的索引号,-p 参数表示显示修改的差异,stash.diff 是导出的文件名。
# 导出stash到文件
git stash show 1 -p > stash.diff
Git常用命令,基于使用步骤
使用步骤
提示:正常步骤应该是:先commit 然后pull 再 push ;
1.git config,配置Git
用于查看和修改Git配置信息,当安装Git后首先要做的事情是设置用户名称和email地址。这是非常重要的,因为每次Git提交都会使用该用户信息
#设置用户信息
git config --global user.name “未进化的程序猿”
git config --global user.email “486566947@qq.com”
#读取配置信息
git config --list
git config user.name
- /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
- ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
- 当前项目的 Git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。
- 提示
- PHPstorm中使用Git时的账号密码同样是Git配置中使用的账号密码
2.初始化仓库,git init
Git 使用 git init 命令来初始化一个 Git 仓库,Git 的很多命令都需要在 Git 的仓库中运行,所以 git init 是使用 Git 的第一个命令。
在执行完成 git init 命令后,Git 仓库会生成一个 .git 目录,该目录包含了资源的所有元数据,其他的项目目录保持不变。
使用当前目录作为 Git 仓库,我们只需使它初始化。
git init
该命令执行完后会在当前目录生成一个 .git 目录。
使用我们指定目录作为Git仓库。
git init newrepo
初始化后,会在 newrepo 目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。
3.将文件纳入版本控制 ,git add
如果当前目录下有几个文件想要纳入版本控制,需要先用 git add 命令告诉 Git 开始对这些文件进行跟踪,然后提交:
# 当前目录下.c结尾的所有文件(通配符)
$ git add *.c
# 指定README等多个文件文件
$ git add README README.md
# .git add all无论在哪个目录执行都会提交相应文件。
$ git add --all
# .git add .只能够提交当前目录或者它后代目录下相应文件。
$ git add .
4. 提交文件 ,git commit
注意
注: 在 Linux 系统中,commit 信息使用单引号 ',Windows 系统,commit 信息使用双引号 "。
所以在 git bash 中 git commit -m '提交说明' 这样是可以的,在 Windows 命令行中就要使用双引号 git commit -m "提交说明"。
暂存区保留本次变动的文件信息,等到修改了差不多了,就要把这些信息写入历史,这就相当于生成了当前项目的一个快照(snapshot)。
项目的历史就是由不同时点的快照构成。Git 可以将项目恢复到任意一个快照。快照在 Git 里面有一个专门名词,叫做 commit,生成快照又称为完成一次提交。
提示
每一次commit都会产生一个新的版本。产生一个代表版本号的散列值
git commit -m '初始化项目版本,记录提交信息'
git commit -am "新增提交说明"
设置了用户名和 Email,保存快照的时候,会记录是谁提交的。
Git文件生命周期
5. 克隆仓库,git clone
我们使用 git clone 从现有 Git 仓库中拷贝项目,Git仓库一般是一个远程连接
git clone
- repo:Git 仓库。
- directory:本地目录。
6. 当前提交状态,git status
git status 命令用于查看在你上次提交之后是否有对文件进行再次修改。
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached ..." to unstage)
new file: README
new file: hello.php
通常我们使用 -s
参数来获得简短的输出结果:
$ git status -s
AM README
A hello.php
AM 状态的意思是这个文件在我们将它添加到缓存之后又有改动。
7. git diff,比较差异
git diff 命令比较文件的不同,即比较文件在暂存区和工作区的差异。(-c 上下文格式的diff、-u 合并格式diff)
git diff 命令显示已写入暂存区和已经被修改但尚未写入暂存区文件的区别。
git diff 有两个主要的应用场景。
- 尚未缓存的改动:git diff
- 查看已缓存的改动: git diff --cached
- 查看已缓存的与未缓存的所有改动:git diff HEAD
- 显示摘要而非整个 diff:git diff --stat
显示暂存区和工作区的差异:
$ git diff [file]
显示两次提交之间的差异:
$ git diff --cached [file]
或
$ git diff --staged [file]
显示暂存区和上一次提交(commit)的差异:
$ git diff [first-branch]...[second-branch]
分支比较
# 直接将两个分支上最新的提交做diff
$ git diff topic master [file]
$ #或
$ git diff topic..master [file]
# 输出自topic和master分别开发以来,master分支上的变更。
$ git diff topic...master
# 查看简单的diff结果,可以加上--stat参数
# --name-only输出时只显示文件名
提示
git diff命令,在终端高度小于输出的文字高度时,会自动生成一个滚动区域,通过上、下来滚动查看文件差异。当窗口高度不够时,输出的内容较少,容易误认为diff差异只有这么点
8. 回退版本,git reset
git reset 命令用于回退版本,可以指定退回某一次提交的版本。只对本地分支有效,对远程分支无效
git reset [--soft | --mixed | --hard] [HEAD]
--mixed 为默认,可以不用带该参数,用于重置暂存区的文件与上一次的提交(commit)保持一致,工作区文件内容保持不变。
git reset [HEAD]
git reset HEAD
执行 git reset HEAD 以取消之前 git add 添加,但不希望包含在下一提交快照中的缓存。
实例:
$ git reset HEAD^ # 回退所有内容到上一个版本
$ git reset HEAD^ hello.php # 回退 hello.php 文件的版本到上一个版本
$ git reset 052e # 回退到指定版本
--soft
参数用于回退到某个版本:
$ git reset --soft HEAD~3 # 回退上上上一个版本
--hard
参数撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除之前的所有信息提交:
$ git reset –hard HEAD~3 # 回退上上上一个版本
$ git reset –hard bae128 # 回退到某个版本回退点之前的所有信息。
$ git reset --hard origin/master # 将本地的状态回退到和远程的一样
- HEAD 表示当前版本
- HEAD^ 上一个版本
- HEAD^^ 上上一个版本
- HEAD^^^ 上上上一个版本
HEAD~0 表示当前版本 - HEAD~1 上一个版本
- HEAD^2 上上一个版本
- HEAD^3 上上上一个版本
9.删除文件,git rm
将文件从暂存区和工作区中删除:
git rm
如果想把文件从暂存区域移除,但仍然希望保留在当前工作目录中,换句话说,仅是从跟踪清单中删除,使用 --cached 选项即可:
git rm --cached
可以递归删除,即如果后面跟的是一个目录做为参数,则会递归删除整个目录中的所有子目录和文件:
git rm –r *
10. 移动目录、文件,git mv
git mv 命令用于移动或重命名一个文件、目录或软连接。
git mv [file] [newfile]
如果新文件名已经存在,但还是要重命名它,可以使用 -f 参数:
git mv -f [file] [newfile]
11.查看提交历史git log、git blame
# 所有历史记录
$ git log
# 简洁版
$ git log --oneline
# 拓扑图版
$ git log --graph
# 反向显示
$ git log --reverse
# 指定提交者
$ git log --author=lius
# 时间范围
$ git log --before={3.weeks.ago} --after={2010-04-18}
# 反向显示
$ git log --reverse
# 指定文件
# git blame
以上多种参数可以组合使用
12. git show
# 查看分支最后一次提交或版本在代码层面的改动
$ git show |
13. git reflog
reflog是reference log的缩写,含义是引用日志,它会记录下HEAD节点和分支引用所指向的历史。可以使用git reflog命令来查看引用日志
14. git gc
gc 命令(gc 指的是垃圾回收)可用于清理版本库,移除所有不属于当前分支的提交对象。
15. git ls-files
查看一下git跟踪了哪些文件,此命令就可以列出所有git正在跟踪的文件
12. 撤销文件 git restore
# 将在 工作区 但是 不在暂存区 的文件撤销更改
$ git restore
# 作用是将 暂存区的文件从暂存区撤出,但不会更改文件
$ git restore --staged
问题记录
1.push和commit的区别
git commit操作的是本地库,git push操作的是远程库。
git commit是将本地修改过的文件提交到本地库中。
git push是将本地库中的最新信息发送给远程库。
如果本地不commit的话,修改的纪录可能会丢失,而有些修改当前是不需要同步至服务器的,所以什么时候同步过去由用户自己选择。什么时候需要同步再push到服务器
2. pull requests 和 merge requests
github可以对不同的用户赋予不同的分支权限,例如Gitlab中的:
- Guest:可以创建issue、发表评论,不能读写版本库
- Reporter:可以克隆代码,不能提交,QA、PM可以赋予这个权限
- Developer:可以克隆代码、开发、提交、push,RD可以赋予这个权限
- Master:可以创建项目、添加tag、保护分支、添加项目成员、编辑项目,核心RD负责人可以赋予这个权限
- Owner:可以设置项目访问权限 - Visibility Level、删除项目、迁移项目、管理组成员,开发组leader可以赋予这个权限
github基于fork的模式下,PR用于请求分支管理员 合并自己提交的代码(理解为请求拉取自己的代码),merge requests同理;
3. fetch和pull的区别
git在本地会保存两个版本的仓库,分为本地仓库和远程仓库。
fetch 只能更新远程仓库的代码为最新的,本地仓库的代码还未被更新,我们需要通过 git merge origin/master 来合并这两个版本,你可以把它理解为合并分支一样的。
pull 操作是将本地仓库和远程仓库(本地的)更新到远程的最新版本。fetch+merge,自动进行合并
4.checkout和reset时的变化
签出切换分支、版本、标签时文件的变化
- 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。加上--hard时会强制替换工作区、暂存区的内容;
- git restore会清除暂存区的修改内容,例如修改了test.vue ,会变为未修改时的内容;
- 当执行 git rm --cached 命令时,会直接从暂存区删除文件,工作区则不做出改变。
5. 保存远程时输入的账号密码
在 git bash 里输入命令:
git config --global credential.helper store
然后执行 git 操作,输入一遍密码后就会记录密码,以后就不用输入了。
要更改记录的用户名和密码,只需要更改用户目录下的 .git-credentials 文件即可。
6.移除文件的版本控制
- 还没有git add,在 .gitignore中添加
- 已经git add,先 git rm -r --cached 文件,然后在 .gitignore中添加
- 已经加到版本控制中,先 git rm -r --cached 文件,然后在 .gitignore中添加,最后 gti commit -m '提交.gitignore'
7.Git问题解决记录
- git config --global credential.helper store,解决SSl连接错误
- 克隆一个指定目录:https://blog.csdn.net/constant_rain/article/details/124913407
8.Github常规目录说明:
- dist 是指编译后的文件,可以理解为压缩发布版
- src 源码文件,适合动手能力强的童鞋
- docs 文档
- examples 示例文件
- test 测试脚本
- .gitignore 告诉git哪些文件不要上传到 GitHub,比如数据库配置文件等
- LICENSE.txt 授权协议
- README.md 自述文件,整个项目的简介、使用方法等
- bower.json Bower 包管理器配置文件
- package.json npm 包管理器配置文件
- 设置某个分支的权限(保护分支)
- 1. 管理员身份登录GitHub,找到项目
2. Settings-->Branches-->Protected branches--->Choose a branch... ,选择需要保护的分支,然后点击edit按钮,
3. Branch protection for 所选的分支名 --> 勾选Restrict who can push to this branch People and teams with push access
若不选择任何人,则任何人都没有push代码到该分支的权限。
9. git做不到一个文件一个分支有一个分支没有
例如一个数据库配置文件,本地和线索不一样,把它从暂存区拉出来,取消追踪变成deleted状态,本地文件实际还存在,同步到分支,远程分支当前版本已经没有这个文件了。
10. Git拉取指定分支
git clone 仓库地址 //默认master分支
git clone -b 分支名 仓库地址 //指定分支
11. git 常见的输出内容
# 已经提交的改变
$ Changes to be committed:
# 暂未提交的改变
$ Changes not staged for commit:
# 常见状态
deleted、modified
12.报错non-fast-forward
non-fast-forward
Dealing with “non-fast-forward” errors:(From time to time you may encounter this error while pushing)
To prevent you from losing history, non-fast-forward updates were rejected. Merge the remote changes before pushing again. See the 'non-fast forward' section of 'git push --help' for details.
This error can be a bit overwhelming at first, do not fear. Simply put, git cannot make the change on the remote without losing commits, so it refuses the push. Usually this is caused by another user pushing to the same branch. You can remedy this by fetching and merging the remote branch, or using pull to perform both at once.
In other cases this error is a result of destructive changes made locally by using commands like git commit --amend or git rebase. While you can override the remote by adding --force to the push command, you should only do so if you are absolutely certain this is what you want to do. Force-pushes can cause issues for other users that have fetched the remote branch, and is considered bad practice. When in doubt, don’t force-push.
以上时较为官方的解释,简单说就是push之前需要先同步远程版本。pull会自动合并,所以要改为fetch手动合并;
问题分析
可以这样理解这个问题就是:别人上传到远程仓库后,你没有及时的同步(、拉取)到本地,但是你同时又添加了一些内容(提交),以致于你在提交时,它会检测到你之前从远程仓库拉取的时候的仓库状态和现在的不一样。于是,它为了安全起见拒绝了你的提交(然后就报了这个错误)。
“不能快速前进”的原因是因为路不一样了,变得不好走了;体现在git里面就是提交历史出现分叉,主线不再是一条直线,而是在前端出现了分叉,git不知道该如何前进,所以报错了,让你来觉得走哪条路!说的简单点,就是远程仓库和本地仓库不同步了
解决问题
1.先合并之前的历史,再进行提交——提倡使用
先把git的东西fetch到你本地然后merge后再push,(如果有冲突就要解决冲突后再合并,冲突问题比较复杂,这里就不详细说了),这样就可以使远程仓库和你本地仓库一致了,然后就可以提交修改了。
$ git fetch origin master
$ git merge origin FETCH_HEAD
# 提示:这2句命令等价于,但是使用git fetch + git merge 更加安全。
$ git pull origin master
# 然后执行,重新定基,可以使得历史更加统一,即提交历史趋向于一条直线。
$ git pull --rebase origin master
2.丢弃之前的历史,强推(谨慎使用)
利用强覆盖方式用你本地的代码替代git仓库内的内容,远程仓库的东西会被本地仓库覆盖!!!
$ git push -f 或者 $ git push --force
官方文档提示:This flag disables these checks, and can cause the remote repository to lose commits; use it with care.
(即:此标志禁用这些检查,并可能导致远程存储库丢失提交;小心使用。)
不仅在此处,在平时使用时,也要非常注意,除非你真的是想覆盖远程仓库,不然最好不要强制执行。
13.彻底清理历史版本
先创建一个分支,添加所有文件,删除其他所有分支。
14.回退远程仓库的版本
先把远程仓库指定分支拉下来,手动回退,然后再强制推送上去(拉回来的远程版本库同时带了这个分支的所有历史版本);
Gitee和Github
1.查看fork的子仓
- github点击 Insights -> fork
- gitee点击fork边上的数字
2.保护分支
- github默认只有分支创建者和仓库管理员有push的权限,其他人可以提交PR
- gitee需要自己设置分支为保护分支,并在保护分支的设置内设置相应的保护级别和权限。
- github可以邀请别人成为协作者,个人仓库的协作者可以拉取(读取)仓库的内容并向仓库推送(写入)更改。
Markdown语法
- 使用 = 和 - 标记一级和二级标题
- 使用 # 号可表示 1-6 级标题,一级标题对应一个 # 号,二级标题对应两个 # 号,以此类推。
- 可以在一行中用三个以上的星号、减号、底线来建立一个分隔线,行内不能有其他东西。
- 如果段落上的文字要添加删除线,只需要在文字的两端加上两个波浪线 ~~ 即可
- 带下划线文本,以下是列表
- 有序列表使用数字并加上 . 号来表示,如:
- Markdown 区块引用是在段落开头使用 > 符号 ,然后后面紧跟一个空格符号,如果要在列表项目内放进区块,那么就需要在 > 前添加四个空格的缩进。:
- 代码区块使用 4 个空格或者一个制表符(Tab 键)。如果是段落上的一个函数或片段的代码可以用反引号把它包起来(`)。
- 链接使用方法如下: [链接名称](链接地址),我们可以通过变量来设置一个链接,变量赋值在文档末尾进行:
- Markdown 图片语法格式如下:![alt 属性文本](图片地址 "可选标题"),表格语法如下:
- ``,表示单行代码,``` ```,用于表示多行代码。