版本控制:此处的“版本”和文章,软件等的版本是一个意思,也就是控制(增删改查存储)以往版本,以及最新版本。
分散式:Git可以将本地项目上传至某个远程服务器(仓库),并且可在许可的情况下让其他人完整复制整个项目。也就是你的项目可以位于——自己电脑,远程服务器,其他人的电脑。
协作:一个项目可以真实存在于3类地方,那么如果有一个地方发生更改呢?Git以远程服务器作为中心,接收“自己电脑”(新)——>远程服务器(旧)<——“别人电脑”(新)的新旧项目的合并请求,如果合并的过程中发生合并冲突(更改冲突,追加不冲突),计算机无法识别哪些代码需要被丢弃,只能通过人工来决定哪些被丢弃。人工解决合并冲突后,远程服务器成为新版本,与其他人共享。
小结:Git的逻辑不难理解,没有什么惊世骇俗的新技术。就是维护一个远端版本共享中心,这个中心,存储了最新版本,以及全部的过往版本,一个团队的小伙伴都可以获取项目全部,如果想要更改,提交请求,管理员通过后合并到最后一个版本,生成最新版本。
本体
官方手册:https://git-scm.com/docs
将文件从工作目录WorkTree add到 暂存区Index 的行为叫做 “staging”(暂存)。
从暂存区将文件 revert/reset 工作目录的行为叫做 “unstage”(撤消暂存)。
如果是已移动到index的文件,状态叫为做 “staged”(已暂存)。
小结:一共有8个基础命令【init,add,rm,mv,status,diff,reset,commit】
涵盖初始化本地repo,增删改查工作树,各类回退,生成commit(多个commit节点组成一条分支)
`实际问题:我怎么将一个项目进行git版本控制?`
①
$ git init
作用:[在某个项目根目录下,右键 → Git bash here 进行本地仓库初始化]
结果:.git目录
子目录及文件
hooks 目录 - 我们会在此处放置客户端或服务器端脚本,以便用来连接到 Git 的不同生命周期事件。
hooks 目录可以用来连接到 Git 工作流的不同部分或事件
info 目录 - 包含全局排除文件
logs 目录 - 包含完整的所有commit信息,并单独存储Head当前指针的commit
objects 目录 - 此目录将存储我们提交的所有 commit
refs 目录 - 此目录存储指向 commit 的指针(“分支branch”和“标签tag”)
config 文件 - 存储了所有与项目有关的配置设置,这些值仅适用于当前仓库。该文件会覆盖全局设置
description 文件 - 此文件仅用于 GitWeb 程序,因此可以忽略
HEAD 文件 - 当前指针 40位SHA-1码(软链接至 refs/heads/master)
index 文件 - 记录当前跟踪的项目文件
COMMIT_EDITMSG 文件 - 临时文件,这就是我们通常意义上的 commit-message 文件本体
`实际问题1:如果你是在一个已经存在文件的文件夹(而不是空文件夹)中初始化 Git 仓库来进行版本控制的话
怎么对已存在的文件进行追踪更新?`
可通过 git add 命令来实现对指定文件的跟踪,然后执行 git commit 提交至新初始化的空的暂存库
②
$ git add <filename> ... / 通配符".":当前目录下全部文件
作用:跟踪一个或多个文件,将未跟踪(未在remote仓库)文件提交至暂存区(changes to be committed)
结果:.git/index增加新记录
③
$ git commit [-m "msg"]
作用:提交更改的文件至版本仓库,还能用于合并时把有冲突的文件标记为已解决状态等。
-m:用于只提供message信息,而不书写 description,用于绕过启动编辑器,直接commit
结果:更改 .git/HEAD文件 log目录 object目录
`实际问题2:我知道我add了某个文件,但我想知道整个项目还有多少文件没有纳入Git版本控制?`
④
$ git status
作用:判断仓库状态,1.显示index文件和当前HEAD commit之间存在差异的文件路径(add但没commit)
2.以及工作空间和index文件存储的差异路径——未由Git跟踪的文件路径(没add)
`实际问题3:我知道我add了某个文件,但我想知道这个文件具体的发生了什么改变?`
⑤
$ git diff
作用:用来查看已被加入但是尚未提交(add跟踪没commit)的更改。【diff==详细的部分的status】
区别1:git status 将告诉我们哪些文件更改了,但是不会显示具体是怎样的更改。
区别2:git diff / git diff --cached:前者查看working tree与index的差别。
后者查看index与已commit的当前节点的项目快照的差别。
(与git log -p相近,-p调用的diff)
`实际问题4:当有人要求你从远端仓库更新本地项目pull,但本地已有两个文件被更改且add到了index文件?
亦或者,你想撤销当前的commit,你觉得这个commit有一些瑕疵或问题`
⑥
$ git reset
作用1:撤消上一个 git add 操作
$ git reset [--hard/soft/mixed] <SHA-of-commit-to-reset / HEAD^ / HEAD~3>
作用2:
--hard,撤销 commit add worktree 的改变,完全回退
--soft,只撤销 commit,完整保留index与worktree
--mixed,撤销 commit add,但不回退worktree(git status显示无更新),默认值
--merge,撤销 merge commit add 操作,并保留原来的worktree更新
如果只是想修改最新commit的msg:$ git commit --amend
`实际问题5:当你的工作树中已经有某些文件被删除了?那暂存区index怎么办?`
⑦
$ git rm [-f -cached -r] 目录\/文件名
作用:删除文件从工作树及index暂存区,-r如果路径为目录,则递归删除全部
--cached 不会破坏任何工作树中的文件,它只是从暂存区删掉了文件(unstage 取消跟踪)。
`实际问题6:如果我有一个文件想要更改名称?那index怎么办?`
⑧
$ git mv [-v] [-f] [-k] <source> <destination>
$ git mv [-v] [-f] [-k] <source> ... <destination directory>
作用:移动重命名文件、目录或符号链接并更新索引。-k:跳过会导致错误条件的移动或重命名操作。-f:强制执行
注:-v == verbose 冗长的——》详细的。directory必须是已存在的,source必须已存在
日志记录 == SHA ,作者 ,日期 ,短消息/描述
小结:常用的日志命令只有两个,展示commit 全部/部分 信息:git log / git show SHA(7位)
`实际问题1:我想知道 总共提交了多少个commit以及消息摘要?`
①
$ git log [--oneline --graph --all --stat -p] <sha>
作用:查询当前branch的版本仓库的全部commit详细信息(sha+author+date+msg+tag)
--oneline:更换为缩略显示(sha+msg)
--graph --all:同时查看所有分支的commit
--stat:查询commit对文件修改粗略内容(增减行数,文件修改数)
-p == --patch:查询commit对文件修改的全部内容
<sha>:后接sha码仍然是显示全部的log,但是从指定的sha处开始显示,当然可以向上翻
`提示1`
git bash采用less分页显示,当“:”变成“(End)”时表示日志结束。可用下列常用命令
j 或 ↓ 一次向下移动一行
d 按照一半的屏幕幅面移动
f 按照整个屏幕幅面移动
k 或 ↑ 一次向上移动一行
u 按照一半的屏幕幅面移动
b 按照整个屏幕幅面移动
按下 q 可以退出日志(返回普通的命令提示符)
`提示2`
--patch显示的信息 解读
diff --git a/test.txt b/test.txt
index ccc3e7b..be4e668 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
aaaaa
+bbbbb
[-]表示旧行数,旧文件行数-1 +1 新行数
[+]表示新行数,新文件行数 2 行
`实际问题2:我已知一个commit的sha码,我怎么知晓这条commit的作者是谁?`
②
$ git show [--stat/ -p] <sha>(7位即可)
作用:查询 单条 给定的commit信息(SHA唯一的ID)--stat显示更改行数(粗略的)
注意:-p == --patch 默认选项,显示全部包含stat,如果搭配使用了--stat则将不显示更改的行数
关于commit的讨论
一般来说,应该一个commit是1个关键层级修改,可附带多个下属层级,即树状修改,并不应在一个commit中修改2个相同层级的并列修改。为什么?
一个是容易混乱,一个是不应违背最小分割的定义,使用同一标准的最小定义,操作、沟通、交流会方便快捷,不容易造成问题(例如我们国际通用的长度单位m,时间单位s)。
commit命令流程
git commit时不使用-m可选项,则将打开配置中指定的代码编辑器,在代码编辑器中:
必须提供commit描述description,以 # 开头的行是注释,将不会被记录,添加提交说明后保存文件
关闭编辑器以进行完成这个commit命令
commit description
建议:消息篇幅简短(少于 60 个字符),解释commit的作用(不是如何更改 或 为何更改!)
请勿:请勿使用"and",如果你必须使用 “and”,则说明可能进行了太多的更改,请将这个commit拆分为更多独立的commit
如下图所示,分支概念由多个独立的commit个体组成,一个commit可视为一个节点,多个节点相连可视为是一条线,从一条线的commit节点分出的多条线——分支结构。
常见分支结构如下图:master分支——主分支(≈软件公开运行版本),hotfixs分支——热修复(≈bug快速修复),release分支——发布分支(≈软件公开发行版本),develop分支——开发分支,功用——增加的 feature功能 都是从develop分支复制出去进行增添功能。feature分支——可能会有多个分支,比如前端有不同的功能进行实现,是可以分模块=>开新分支进行分别开发,最后多个平行feature分支功能共同合并至develop分支。
图来自:httpsnvie.compostsa-successful-git-branching-model
语法:$ git branch b-name SHA
功能:以特定SHA的 commit 作为新分支 b-name 的起点,显示时,会通过特别颜色表明哪个是当前活跃分支。注意,此命令创建新分支是不会自动切换过去的。仍然在当前活跃分支。
语法:$ git checkout branch
,$ git checkout -b|-B
功能:检出命令。切换已存在分支,以存在的commit/branch分支创建新分支并切换
实例:
$ git checkout -b footer master
基于master当前状态创建新分支footer,等于下面命令的组合
$ git branch -f
+ $ git checkout
语法:$ git branch (-d | -D) [-r]
删除一个分支。分支必须与其upstream跟踪分支中完全合并,或者没有设置upstream(–track或–set-upstream-to),则必须在当前HEAD中完全合并(为了避免数据丢失)
注意:关于删除
如果你基于某个已存在的commit 创建了 sidebar 新分支,并向其添加了新 commit,然后尝试使用 git branch -d sidebar 删除该分支,git 不会让你删除该分支,因为你无法删除当前所在的分支。
如果你切换到 master 分支并尝试删除 sidebar 分支,git 也不会让你删除,因为 sidebar 分支上的新 commit 会丢失!如果要强制删除,你需要使用大写的 D 选项 - git branch -D xxx【-D = -df】
语法:$ git branch
功能:显示本地所有分支
分支存在的意义:在其他分支做出的任何更改,都不影响 master 分支。
如果觉得 side分支 上的更改无用,则可以删掉该分支。或者你觉得 side分支 有必要保留,则可以将该分支上的更改与其他分支进行合并。
合并前检查
当本地有未commit的add更改与 git pull/git merge可能更新的文件重叠时,git pull和git merge将停止。
git 中的两种合并:普通合并 和 快进合并
注意:普通合并分支会生成一个commit
FAST-FORWARD MERGE
使用 --no-ff 选项可以禁止快速合并
假设一种常见情况:您正在跟踪一个上游存储库master分支,本地master分支没有提交任何更改,现在您想要更新到最新的上游master分支状态,在这种情况下,不需要新建一个commit来存储合并的历史记录,pull获取最新新增的commit节点接续在本地分支尾部,再直接将本地的HEAD以及暂存区index都指向最新的commit节点。
注:为什么暂存区index也指向commit,因为commit命令是将暂存区中的更改数据提交至本地版本库生成新的commit对象/节点,如果不指向最新的commit节点,那么可能会出现非尾部增添——中间插入的commit节点的情况。
普通合并
要合并的分支必须通过 mergeCommit(commit节点) 绑定在一起,这个合并提交commit节点将合并的两分支都视为父分支。git commit mergeCommit 后,本地HEAD、index和working tree将更新到它
【合并,以某一分支为主分支,另一分支为辅分支,将辅分支内容合并到主分支中】
一般当前头指针HEAD指向的分支是主分支,被合并的分支的头指针叫做 MERGE_HEAD
简单理解HEAD指针的作用,HEAD指针指向的是分支branch的最新commit对象,也就是worktree的最新物理状态(静止的),而不同的commit对象代表着 worktree 的不同物理状态。
比如 刚出生的婴儿(worktree)(没有衣服全裸–状态1),出生几个月的婴儿(有小衣服穿不裸–状态2),婴儿长大成人(三点一线比基尼≈全裸–状态3)这3个状态 静止保存下来就是 3个commit
分支 branch 就是指代 这个人 连续的成长历程,但git是通过离散的commit对象来≈构成连续
而 HEAD 就是 代表(指向)当前我们选取这个人(worktree)的哪个状态(commit)?
注:一般最新状态的 commit 我们一般称为分支头
当普通合并发生冲突,git无法自行判断舍弃哪个分支的冲突数据,将会出现下列行为:
1.HEAD指针不会发生改变
2.MERGE_HEAD(被合并分支)指针也不发生改变,仍指向其分支头(commit)
3.两分支无冲突合并的文件将在index暂存区和工作树中更新
4.有冲突合并的文件,index暂存区将最多纪录 冲突合并文件的3个版本
①阶段一:存储来自共同 commit 的版本
②阶段二:存储当前分支 HEAD 独有的内容
③阶段三:存储 MERGE_HEAD 独有的内容,可通过git ls-files -u
查看
最终,worktree文件会保存下 合并冲突的结果,冲突内容会被特殊标记<<< === >>>
5.不自动提交commit生成mergeCommit,当前分支保持在进行merge合并命令之前的状态,只不过是worktree保留了合并冲突结果。index暂存区也没有新的改变。
上面是git merge 能做到的事情,再向后,就是程序猿的工作。如果后悔合并(可能合并冲突的结果过于复杂,程序猿都无法完全解析),可通过git merge --abort
撤销。如果解决冲突很简单,就修改被<<< === >>>
标记的worktree文件内容,修改完成后!!
提交一个 commit 来记录冲突合并操作,结束
冲突合并符号
<<<<<<< HEAD 此行下方的所有内容(直到下个指示符)显示了当前分支上的行
======= 表示HEAD内容的结束位置,从这之后的所有行(直到下个指示符)是 MERGE_HEAD 分支的行内容
>>>>>>> MERGE_HEAD 合并分支的行结束指示符
实例
<<<<<<< HEAD
i cant image what happen
=======
but i dont image
>>>>>>> cb7b2830d406984d2d6ac51d5637d3d2e46ace3b
注1:cb7b283是远程仓库master分支的最新commit,也是与本地master分支的最新commit 04ffb76 冲突提交
注2:此时的 git bash 也不同往昔,还可以使用 git status 查看冲突合并的文件
# 往昔
INBreeze (master) my-travel-plans
# 冲突合并
INBreeze (master *+|MERGING) my-travel-plans
$ git status
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add ..." to mark resolution)
both modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
解决冲突
1.将<<<<<<< >>>>>>>
区域进行处理,删除冲突标志符,决定冲突内容如何被合并保留。
2.使用 git add xxx(冲突文件)
3.使用 git commit ,记录这次冲突合并——冲突解决完毕
查看分支commit 日志
INBreeze (master) my-travel-plans
$ git log --oneline
e00dff2 (HEAD -> master) conlifct complete
04ffb76 localChange
cb7b283 (origin/master, origin/HEAD) Update README.md
小结
快进合并:无冲突合并【移花接木】,直接将远端分支最新commits增添到本地旧分支末端
普通合并:有冲突合并,需要将冲突解决后,手动 commit 记录冲突合并。
语法:$ git clone [url] [项目根目录名]
实例:$ git clone https://github.com/libgit2/libgit2 mylibgit
作用:克隆远程仓库到当前目录下,如果不写项目根目录名,则默认是远端仓库名
注:Git 支持多种数据传输协议,https协议(以https为标志)、SSH协议(以@为标志)
语法:$ git remote add [远端简名] url
实例:git remote add origin https://github.com/INBreezefall/my-travel-plans.git
作用:链接至远程仓库源,不写远端简名默认origin
注:简写名显示方便,用于取代长串url链接https/ssh
验证链接
语法:$ git remote [-v]
作用:将已连接的远端仓库简名输出,-v 为详细信息,可以用于验证是否正确添加了远程仓库
语法:$ git remote show <远端简名>
作用:输出完整的远端链接信息:Fetch URL,Push URL,HEAD branch,Remote branch,
Local branch configured for ‘git pull’,Local ref configured for ‘git push’
删除链接
语法:$ git remote rm <远端简名>
作用:删除此链接
重命名链接
语法:$ git remote rename <旧远端简名> <新远端简名>
作用:重命名已存在的远端主机名称
链接远端与克隆远端的区别
clone == remote add + pull(workTree + branch + null index本地暂存区)+ branch
remote 仅仅是链接到远程仓库,未进行任何操作。
下列组合命令可以 == clone
$ git init
$ git remote add origin url
$ git pull
$ git branch --set-upstream master origin/master
注1:pull命令为拉取完整的远端仓库至本地版本库
注2:branch命令建立远端/本地分支追踪关系
pull
语法:$ git pull
实例:$ git pull origin next:master
作用:取回远程主机最新分支到本地跟踪分支,本地跟踪分支分支再与本地指定分支合并
前提条件:存在远端,并已建立 remote 链接
如果已建立远程/本地分支追踪关系
$ git pull origin
,可省略远程分支名remote-branch
如果远程仓库只有一个分支
$ git pull
,可省略远程仓库简名remote-shortname及远程分支名remote-branch
注:$ git pull -p
,会自动删除(对应远端版本库中已被删除的分支)本地分支
本地跟踪分支
具体描述是:为存储具有追踪关系的已连接的远端仓库的某一分支而建立的本地跟踪分支
本地跟踪分支命名规则:remote-shortname/remote-branch
,例如origin/master
需要注意的是:与当前分支master
是完全不同的相互独立的,两个分支
远程/本地分支追踪关系手动建立
语法:$ git branch --set-upstream master origin/next
fetch【 pull 命令的一半】
语法:$ git fetch
作用:fetch拿到特定远端分支的最新commits记录并合并到本地跟踪分支。通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。
语法:$ git fetch
作用:fetch拿到远端全部分支的最新commits记录并合并到本地跟踪分支。
注1:查看本地跟踪分支,使用$ git branch -r
注2:对于本地跟踪分支,可以使用 checkout 创建副本,也可以直接 merge 合并到本地其他分支。
$ git checkout -b newBrach origin/master
,git merge origin/master
(合并到当前分支)
重解析pull命令
第一步:远程分支master被下载合并到本地仓库的本地跟踪分支(origin/master),
origin/master分支的head指针移动到更新后的最新commit节点
第二步:本地跟踪分支(origin/master)合并到本地分支(master)
# pull == fetch + merge
$ git fetch origin master:origin/master
$ git diff origin/master
$ git merge origin/master
# 当前分支为 master
注1:pull执行时显示的信息与 git push 非常相似 - 要对项目计数、解压缩等等
它显示类似于 git log --stat 的信息,其中显示更改的文件以及添加或删除了多少行
它有一个短语 “fast-forward”,这意味着 Git 进行了快进合并(我们一会儿深入研究)
注2:跟踪分支不是实时跟踪远程分支在远程仓库上的情况。如果其他人对远程仓库做了更改,我们本地仓库中的 origin/master 跟踪分支不会自动更新。我们必须手动检查更新它才可能会移动。
注3:常见 pull error:local changes
$ git pull
error: Your local changes to the following files would be overwritten by merge:
README.md
Please commit your changes or stash them before you merge.
Aborting
Updating c32a4af..5f2b351
`第一类情况:已add修改,未commit,pull error`
# 第一类第一种情况:本地修改量小,无所谓
解决方案1:重置 reset --hard(完全重置) ,然后直接拉取pull
# 第一类第二种情况:本地修改量大,有所谓
解决方案2:隐藏 stash,先pull,然后再 stash pop 放出本地修改,然后出现合并冲突,解决再 add commit
解决方案3:重置 reset --soft(保留index与worktree),先 commit 再 pull,然后出现合并冲突,同上
`第二类情况:已add修改,已commit,pull conflict`
# 解决方案1:解决冲突文件内容,然后 add 再 commit【类似3】
# 解决方案2:重置,reset【类似1,3】
# 解决方案3:恢复,revert
参考博文
revert与reset的区别
常见pull的错误解决方式
语法:$ git push
作用:推送本地当前分支 master 分支的 commits 到远程版本库origin的master分支
语法:$ git push --all
作用:推送本地所有分支至远程版本库,不论是否存在对应关系。如果不存在的在远端创建新分支
一般流程:
第一步:将 worktree 更改 git add
进入暂存区index
第二步:将 暂存区 内容 进行 git commit
创建本地分支最新状态
第三步:git pull
获取远端分支最新状态
第四步:发生 merge conflict 合并冲突,手动 解决 冲突,然后 git add
将冲突更改放入暂存区,再 git commit
记录此次冲突合并。
第五步:git push
将本地分支最新状态推送(更新)远端分支
注1:不同分支可以分别push推到远程仓库,也可以merge成一个master分支再push
注2:当我们不知道远程仓库被提交新的commit时,使用git pull拉取更新的commit会与本地master分支出现冲突,这个时候我们还可以使用git fetch,则只更新 origin/master,但不合并本地master分支与最新的origin/master,当我们想合并远端更新origin/master时,在本地master分支上使用 git merge origin/master 即可,然后再push推送到remote repository(merge命令自动建立 merge commit)
指定的Git应该忽略,故意不跟踪的文件
在决定是否忽略路径时,Git通常会检查来自多个源的模式,具有以下优先顺序,从最高到最低
并且在一个优先级内,最后一个匹配模式决定结果。
不同的文件决定不同影响范围
①自己+其他开发者都应忽略,影响所有人push,规则同步至远程仓库:.git/.gitignore
文件
②只影响自己push,规则不同步至远程仓库:.git/info/exclude
文件
③不同子文件的.gitignore文件拥有不同的忽略规则会覆盖父级.gitignore文件规则
④本地全局跨项目的忽略规则:C:\Users\<你的用户名>\.gitignore
注1:如果是已经被add过的文件,不受.gitignore
文件忽略模式的影响。
注2:.gitignore
文件每一行都指定一个模式
注3:.gitignore
文件允许使用通配符及特殊的字符来表示某些格式/字符。
* 与 0 个或多个字符匹配
? 与 1 个字符匹配
[abc] 与 a\b\c\ab\bc\ac\abc 或匹配
** 与多个嵌套目录匹配 - a/**/z 与以下项匹配
a/z
a/b/z
a/b/c/z
# 实例
*.[oa] 忽略 所有.a或.o的文件
!test.a 不忽略test.a文件
dir/ 忽略所有dir目录
dir 忽略所有dir文件及dir同名目录
/dir 只忽略根目录下的dir目录,不忽略别的dir子目录
注4:.gitignore
文件无法直接在Windows创建,使用git bash命令vim .gitignore
,或使用cmd命令mv oldName newName
,不加文件后缀则默认还是原文件后缀。
参考博文:
https://www.cnblogs.com/kevingrace/p/5690241.html
https://www.jianshu.com/p/267cd94f1d49