一、Git起步
(1) 与其他版本控制系统的差别
(a) 存储信息的方式
其他版本控制系统:以文件变更列表的方式存储信息;
Git:对小型文件系统的一组快照,每次提交制作快照时,若文件没有 修改则不重新存储,而只保留指向之前存储文件的链接;
(b) 本地执行
其他版本控制系统:集中式,有网络延时,无网络时无法提交;
Git:本地执行,无网络时也可本地提交;
(c) 完整性
其他版本控制系统:(待确定);
Git:SHA-1校验和;
(2) 三种状态
已提交(committed):数据已经安全的保存在本地数据库中;
已修改(modified):修改了文件,但还没保存到数据库中;
已暂存(staged):表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中;
(3) 三个工作区域
Git仓库:保存项目元数据和对象数据库的地方;
工作目录:项目的某个版本独立提取出来的内容;
暂存区域:一个文件,保存了下次将提交的文件列表信息(修改过的文件信息),也被称为“索引”;
(4) 配置
(a) 配置存放位置
/etc/gitconfig文件,使用–system选项,系统上每个用户通用配置;
~/.gitconfig或~/.config/git/config文件,使用–global选项,针对当前用户;
当前仓库.git/config,针对当前仓库;
(b) git config:
修改:
git config [--global|--system] KEY VALUE
查看所有:
git config —list
查看某一项:
git config KEY
别名:
git config [--global]alias.SHORT_COMMOND COMPLETE_COMMOND
如git config --global alias.last 'log -1 HEAD'
二、Git初始化
(1) 获取Git仓库
现有所有文件导入git中:
git init
;克隆现有的仓库:
git clone URL [NEW_NAME]
;
三、Git基本提交
(1) 文件状态
状态变化图(见右)
未跟踪;
已跟踪:
未修改(已提交);
已修改;
已暂存;
(2) 检查当前文件状态
git status [--short(-s)]
状态简览
?? — 未跟踪;
A — 新添加到暂存区;
D — 删除的文件;
左边M — 被修改并添加到暂存区(修改过,未add的无法提交);
右边M — 在工作区被修改了还没放入暂存区;
(2) 跟踪文件
(a) add
理解为“添加内容到下一次提交中”而不是“将一个文件添加到项目中”
git add FILE_NAME | DICTIONARY_NAME |.
参数:
-i:交互方式,可选择git基本功能;
-u:更新已经加入到暂存区的文件修改;
(b) 忽略文件:
.gitignore文件
规则:
注释:“#”开头;
防止递归:以(/)开头;
指定目录:以(/)结尾;
忽略:模式前加上惊叹号(!)取反;
glob 模式匹配:
匹配零个或多个任意字符:*;
匹配任意中间目录:**;
匹配任何一个方括号中的字符:[xxx];
匹配任何一个范围的字符:[x-x’];
匹配任意一个字符:?;
实用工具:http://gitignore.io
(3) 提交更新
git commit [-m | -a ] COMMIT_MESSAGE
COMMIT_MESSAGE多行信息,可用一个单引号实现
参数:
-m:可以简要填写提交信息;
a:自动暂存已跟踪过的文件,并提交;
(a) 补充提交
git commit --amend [-m NEW_MESSAGE]
提交之后,想重新提交刚才提交过的更新,用此命令;
用参数“-m”可以更新提交的信息;
(b) 提交的过程
git会保存一个提交对象,保持暂存内容快照的指针,以及提交时的信息;提交对象的父对象,是上一次提交的对象(可能多个或没有);
暂存操作为每个文件计算校验和,保存当前版本的文件快照,等待提交;
提交时,计算每个子目录的校验和,并将其保存为树对象;创建提交对象,包含指向树对象和各个文件快照的指针;非首次提交有指向父对象的指针;
(4) 移除文件
git rm [-f | --cached] FILE_NAME | DICTIONARY_NAME
参数:
-f:删除已修改并放入暂存区的文件;
-cached:将文件从暂存区(git仓库)中移除,但仍保留在当前工作目录中;
(5) 移动文件
git mv FILE_FROM FILE_TO
(6) 储藏与清理
(a) 储藏 stash
解决:要切换分支,但又不想为做了一半的工作创建一次提交的情况;
储藏:
git stash [save MESSAGE|-u(--include-untracked)|-k(--keep-index)|--all(-a)]
;默认储藏已暂存的修改;
u,则在此基础上加上未暂存的修改;
k,–keep-index则只储藏未暂存的修改;
–all,-a所有文件储藏,不仅仅本次修改;
查看:
git stash list
;恢复第n次储藏(与list中的数字一样),默认最近一次即stash@{0}:
git stash apply stash@{n}
恢复最近一次也可以用(但会删除储藏的记录):git stash pop
;移除储藏:
git stash drop stash@{n}
;以储藏新建分支:
git stash branch
;
(b) 清理 clean
移除不在暂存区的修改
git clean [-f|-d|-n]
-f 强制执行,-n 列出将会做的处理
四、还原历史
(1) 重置
(a) “三棵树”
HEAD:当前分支引用的指针,上一次提交;
Index:暂存区域引用指针,预期的下一次提交;
Working Directory:沙盒;
(b) reset
git reset --soft|mixed|hard
soft:移动HEAD到目标提交上,之间修改保留在暂存区;
mixed(默认):除了以上操作,将Index(暂存区)上的还原成目标提交上的,之间修改保留在工作区;
hard:除了以上两项操作,将工作区的修改还原成目标提交上的;
与checkout一样,区别在于:checkout更安全有检查,且移动HEAD(分支)而非其指向;
(2) 撤销提交
(a) 取消暂存的文件
将已加到暂存区的文件取消暂存
git reset HEAD FILE_NAME
git reset -- FILE_NAME
(b) 撤销对文件的修改
撤销所有文件修改,回到上一次提交状态:
git reset --hard HEAD
;撤销不在暂存区中的文件的修改:
git checkout — FILE_NAME
;将文件还原到BRANCH_NAME分支上的最新进度:
git checkout BRANCH_NAME — FILE_NAME
;
(c) 合并还原提交
git revert -m N BRANCH_NAME | COMMIT_HASH
撤销该次提交的操作,该次提交之后的修改仍然存在
若要撤销“还原提交”操作,继续用revert命令;
git revert -n
撤销而不产生提交信息,可查看做了哪些更改,再决定是否提交
(d) 重写提交历史
利用变基操作:
git rebase -i BRANCH_NAME | HEAD~n | ...
之后,有从当前分支到BRANCH_NAME分支之间的提交历史列表,选择pick(提交)或squash(合并)等操作;
利用reset:
git reset --soft COMMIT_HASH
,再进行提交git commit
;
五、分支与合并
(1) 分支
实际上是指向提交对象的可变指针,默认是master;
HEAD指针指向当前所在的本地分支;
(2) 新建分支
(a) 根据branch新建
git checkout -b [--track][NEW_BRANCH_NAME] BRANCH_NAME
# 等同于
git branch BRANCH_NAME
git checkout BRANCH_NAME
参数–track:将新分支与当前分支建立跟踪关系
(b) 根据commit新建:
git checkout COMMIT_HASH
git checkout -b NEW_BRANCH_NAME
# 或者
git branch NEW_BRANCH_NAMECOMMIT_HASH
(3) 查看分支
git branch [-v | --merged | --no-merged]
参数:
-v:查看每个分支的最后一次提交;
–merged,–no-merged:查看已合并、未合并到当前分支的分支;
(4) 删除分支
git branch -d | -D BRANCH_NAME
-D是丢掉未合并工作,强制删除
(5) 分支合并
git merge BRANCH_NAME
将BRANCH_NAME合并到当前分支上
若有冲突,git status
, git diff
查看冲突并解决,可用git mergetool
中止合并:git merge —abort
“fast-forward”:即如果当前HEAD是待合并commit的祖先,则会将当前分支指针指向待合并commit,不会产生新的merge commit,这是git merge操作的默认操作;
若加上参数git merge --no-ff
,则在此场景下,会产生新的merge commit;
(6) 变基
(a) 方法
使用rebase命令,可以将提交到某一分支上的所有修改都移至另一分支;
git checkout BRANCH_C4
git rebase BARNCH_C3
git checkout BRANCH_C3
git merge BRANCH_C4
# 等同于
git rebase BRANCH_C3 BRANCH_4
git checkout BRANCH_C3
git merge BRANCH_C4
变基过程中,若存在冲突则解决冲突,后用git rebase —continue
,而不是git commit
;
(b) 原理
首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用;
(c) 注意
不要对在你的仓库外有副本的分支执行变基;
(7) 打标签
(a) 列出标签
git tag [-l GLOB_STRING | -n]
参数:
-l:列出符合模式的所有标签;
-n:列出所有标签即对应的标签信息(若无标签信息则为commit信息)
(b) 查看标签
git show TAG_NAME
(c) 创建标签
附注标签:
git tag -a TAG_NAME -m TAG_MASSAGE
存储了标签的作者、时间等信息;
轻量标签:
git tag TAG_NAME
将提交校验和存储到一个文件中,不存其他信息;
后期打标签:
git tag -a TAG_NAME HASH
给某一次提交打标签
(d) 删除标签
git tag -d TAG_NAME
(e) 删除远程标签
git push origin :TAG_NAME
(f) 推送标签
git push origin TAG_NAME | --tags
将特定标签推送到服务器,推送所有标签
(g) 检出标签
git checkout -b BRANCH_NAME TAG_NAME
在特定的标签上创建一个分支
六、远程协作
(1) 远程仓库
(a) 查看远程仓库
git remote [-v | show REMOTE_NAME]
参数
无参数:列出远程服务器的简写;
-v:需要读写远程仓库的简写与其对应的URL;
show:列出远程仓库的URL与跟踪分支的信息,以及push和pull的情况;
(b) 添加远程仓库
git remote add [-u] SHORT_NAME URL
可使用SHORT_NAME来代替整个URL,参数-u设置为默认远程仓库与–set-upstream一样
(c) 拉取远程仓库
仅拉取:
git fetch REMOTE_NAME
访问远程仓库,从中拉取所有本地还没有的数据,但不会合并,需手动merge;
如果使用clone,会自动将其添加为远程仓库并简称为origin;
拉取并尝试合并:
git pull REMOTE_NAME BRANCH_NAME
拉取后自动尝试合并到当前分支;
(d) 推送到远程仓库
git push REMOTE_NAME LOCAL_BRANCH_NAME:REMOTE_BRANCH_NAME
git pull --ff-only
:类似的,可以在pull操作时加上参数–ff-only,只合并那些“fast-forward”的情况;git pull --rebase COMMIT
:不会产生新的merge提交,而是和rebase类似,将两个提交之间相差的提交部分提交到COMMIT之后;git push REMOTE_NAME +LOCAL_BRANCH:REMOTE_BRANCH
:强制更新远程分支,如果本地reset之后,可以采用这个命名更新远程分支;
(e) 重命名远程仓库
git remote rename OLD_REMOTE_NAME NEW_REMOTE_NAME
(f) 删除远程仓库
git remote rm REMOTE_NAME
(2) 远程分支
(a) 查看远程分支
git ls-remote REMOTE_NAME
git remote show REMOTE_NAME
git branch -r
# 所有跟踪分支
git branch -vv
(b) 增加远程仓库
git remote add REMOTE_NAME REMOTE_URL
(c) 跟踪分支
# 从远程拉取分支并跟踪
git checkout --track ORITIN_NAME/BRANCH_NAME
# 分支A跟踪分支B,分支B若有跟踪远程分支,则分支A也跟踪了
git checkout --track -b BRANCH_NAME_ABRANCH_NAME_B
(d) 删除分支
git push ORIGIN_NAME --delete BRANCH_NAME
七、检查与比较
(1) 比较文件
git diff [--staged(--cached) | HEAD]
参数
无参数:比较工作区与暂存区快照之间的差异;
-staged或–cached:已暂存的与HEAD的差异;
HEAD:比较工作区与HEAD的差异;
(2) 查看冲突
git diff [--ours | --theirs | --base]
以该分支为基础查看,合并了什么:–ours;
以对方分支为基础查看,合并了什么:–theirs;
查看冲突的改动:–base;
(3) 查看提交历史
git log [-p | -RECENT_N_TIMES | --stat |--pretty | --graph | --since | --before | --author | --committer | --grep |--decorate]
参数
-p:显示每次提交的内容差异;
RECENT_N_TIMES: 最近N次提交;
-stat:每次提交的简略统计信息(统计文件哪些行被修改了);
-pretty=oneline|format:”…”:
oneline:单行显示;
format:可用格式占位符 %H, %h 长短Hash字串,%an 作者名字,% cn 提交者名字,%cd 提交日期,%s提交说明;
-since(–after),–before(–until):限定日期,如“2008-10-01”;
-grep:仅显示指定关键字的提交;
-decorate:提交历史中列出分支信息;
git log –oneline –graph –decorate
一行查看日志,有branch合并的信息
git log -p FILE_NAME
显示文件FILE_NAME,每次提交实际修改的内容
git log -L BEGIN_LINE,END_LINE:FILE_NAME
指定文件中的某些行,查看这些行的变化日志
(4) 提交日志查看工具
(a) 引用日志
git reflog
与log的不同在于:最新更改放在列表前面显示,包括reset重置操作;(重置后也能查看到跳过的commit信息)
每当HEAD所指向的位置发生了变化,Git就会将这个信息存储到引用日志这个历史记录里;
(b) 查看具体修改 git show
查看分支在n次前所指向的提交:
git show BRANCH_NAME@{n}
父提交:
git show HASH^ git show HASH~
祖父提交:
git show HASH^^ git show HASH~2
(合并的)第二父提交:
git show HASH^2
(c) 提交区间
# 在分支A中,但不在分支B中的
git log BRANCH_B..BRANCH_A
git log ^BRANCH_B BRANCH_A
git log BRANCH_A --not BRANCH_B
# 只被两者之一包含
git log BRANCH_A...BRANCH_B
(d) 搜索
搜索目录文件:从提交历史或工作目录中查找一个字符串正则表达式;
git grep [-n | --count] FIND_GREP
参数
n:输出说找到匹配行行号;
-count:输出概述,仅包括哪些文件包含匹配及匹配数量;
搜索日志:
git log -S FIND_GREP --oneline
搜索文件的每一行来源:
git blame FILENAME