目的
了解Git, 以及工作原理, 能更好让自己解决在仓库中遇到的各种问题。
git
什么是git
分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。很多版本管理工具如:sourcetree, gitLab, tortoise等等, 针对这个目前只是说说git。
四个术语
Workspace : 工作区
Index / Stage : 暂存区
Repository : 本地仓库
Remote : 远程仓库
这四个是对应git管理过程中所处的4个阶段。
如图
图解git工作流程
从修改时间先后来讲,工作目录的内容是你当前看到的,也是最新的;index区标记了你当前工作目录中,哪些内容是被git管理的;而本地仓库保存了对象被提交过的各个版本,比起工作目录和暂存区的内容,它要更旧一些;远程仓库是本地仓库的异地备份,远程仓库的内容可能被分布在多个地点的处于协作关系的本地仓库 修改,因此它可能与本地仓库同步,也可能不同步,但是它的内容是最旧的。任何对象都是在工作目录中诞生和被修改;任何修改都是从进入index区才开始被 版本控制;只有把修改提交到本地仓库,该修改才能在仓库中留下痕迹;而要与协作者分享本地的修改,可以把它们push到远程仓库来共享。图最上方的 add、commit、push等,展示了git仓库的产生过程。反过来,我们可以从远程历史仓库中获得本地仓库的最后一个版本,clone到本地,从本 地检出对象的各个版本到index暂存区或工作目录中,从而实现任何对象或整个仓库的任意阶段状态的”回滚”。
开始接触这些概念可能比较绕,其实在git入门阶段,可以先抛开远程仓库不看,只了解管理本地仓库的”3棵树”就够了
二、Git命令
在开始之前,我们需要把下面的图看懂:
HEAD,头,它始终指向当前所处分支的最新的提交点。你所处的分支变化了,或者产生了新的提交点,HEAD就会跟着改变。
working directory,它是你的工作目录,也是当前你看到的东西。你的工作目录是与版本、分支相关的。
stage的东西虽然看不见,但是执行git status就会看到哪些对象的修改将在下一次commit的时候被放进本地仓库。这些东西称为stage。
commit
commit把暂存区的内容存入到本地仓库,并使得当前分支的HEAD向后移动一个提交点。如果对最后一次commit不满意,可以使用git commit --amend来进行撤销,修改之后再提交。如图所示的,ed489被4ca87取代,但是git log里看不到ed489的影子,这也正是amend的本意:原地修改,让上一次提交不露痕迹。
checkout
checkout用来检出并切换分支。checkout成功后,HEAD会指向被检出分支的最后一次提交点。对应的,工作目录、暂存区也都会与当前的分支进行匹配。下图是执行git checkout maint后的结果:
reset
reset命令把当前分支指向另一个位置,并且相应的变动工作目录和索引。如下图,执行git reset HEAD~3后,当前分支相当于回滚了3个提交点,由ed489回到了b325c:
reset有3种常用的模式:
soft,只改变提交点,暂存区和工作目录的内容都不改变
mixed,改变提交点,同时改变暂存区的内容。这是默认的回滚方式
hard,暂存区、工作目录的内容都会被修改到与提交点完全一致的状态
diff
我们在commit、merge、rebase、打patch之前,通常都需要看看这次提交都干了些什么,于是diff命令就派上用场了:
来比较下上图中5种不同的diff方式:
比较不同的提交点之间的异同,用git diff 提交点1 提交点2
比较当前分支与其他分支的异同,用git diff 其他分支名称
在当前分支内部进行比较,比较最新提交点与当前工作目录,用git diff HEAD
在当前分支内部进行比较,比较最新提交点与暂存区的内容,用git diff --cached
在当前分支内部进行比较,比较暂存区与当前工作目录,用git diff
merge
merge命令把不同的分支合并起来。如下图,HEAD处于master分支的ed489提交点上,other分支处于33104提交点上,项目负责人看了下觉得other分支的代码写的不错,于是想把代码合并到master分支,因此直接执行git merge other,如果没有发生冲突,other就成功合并到master分支了。
rebase
rebase又称为衍合,是合并的另外一种选择。merge把两个分支合并到一起进行提交,无论从它们公共的父节点开始(如上图,other分支与 master分支公共的父节点b325c),被合并的分支(other分支)发生过多少次提交,合并都只会在当前的分支上产生一次提交日志,如上图的 f8bc5。所以merge产生的提交日志不是线性的,万一某天需要回滚,就只能把merge整体回滚。而rebase可以理解为verbosely merge,完全重演下图分支topic的演化过程到master分支上。如下图:
在开始阶段,我们处于topic分支上,执行git rebase master,那么169a6和2c33a上发生的事情都在master分支上重演一遍,分别对应于master分支上的e57cf和f7e63,最后checkout切换回到topic分支。这一点与merge是一样的,合并前后所处的分支并没有改变。git rebase master,通俗的解释就是topic分支想站在master的肩膀上继续下去。
cherry-pick
cherry-pick命令复制一个提交点所做的工作,把它完整的应用到当前分支的某个提交点上。rebase可以认为是自动化的线性的cherry-pick。
例如执行git cherry-pick 2c33a:
正反过程对比
理解了上面最晦涩的几个命令,我们来从正反两个方向对比下版本在本地的3个阶段之间是如何转化的。如下图(history就是本地仓库):
如果觉得从本地工作目录到本地历史库每次都要经过index暂存区过渡不方便,可以采用图形右边的方式,把两步合并为一步。
三、命令与具体情况介绍使用
-
新建代码仓库
git init 创建本地仓库
git remote add origin gitAddress 本地库“关联”远程库
git remote remove origin 取消本地目录下关联的远程库
git clone gitAddress 从远程仓库克隆项目代码
git clone -b C gitAddress 从远程仓库克隆“指定分支C”上的代码 -
创建,分支,删除分支
git branch A 创建分支A
git checkout -b A 创建并且切换到A分支上
git chekcout B 切换到分支B
git branch -a 查看所有本地分支和远程分支
git merge B 合并指定分支B到当前分支
git branch -d(-D) B 删除分支B
git pull origin --delete B 删除远程分支B
git push origin 本地分支A:远程分支A 将本地分支push到远程分支 ,并且命名远程分支的名字为A
git fetch origin 同步远程仓库
git push -u origin master 关联后,使用该命令第一次推送 master 分支的所有内容,后续再推送的时候就可以省略后面三个参数了,其中参数 u 代表上游(upstream)的意思。 -
文件增、删、修改
git add 【xxx.cpp】 将指定文件添加到暂存区
git add . 添加当前目录下所有文件
git add -p 添加每个变化前,都会要求确认;对于同一个文件的多处变化,可以实现分次提交
git add -u 暂存修改的和删除的文件,不包括新增加的文件。
git add -A 暂存所有的文件,包括新增加的、修改的和删除的文件。
git rm [file1] [file2] … 删除工作区文件,并且将这次删除放入暂存区, 从工作区和索引中删除文件
git mv [file-original] [file-renamed] 改名文件,兵器将这个改名放入暂存区
git rm --cached [file1 [file2] 从索引中删除文件。但是本地文件还存在, 只是不希望这个文件被版本控制。 -
代码提交
git commit -m “description” 提交暂存区到本地仓库区
git commit [file1] [file2]… -m [message] 提交暂存区的指定文件到本地仓库区
git commit -a 提交工作区自上次commit之后的变化,直接到本地仓库区
git commit -v 提交时显示所有diff信息
git commit --amend [file1] [file2] … 重做上一次的commit,并包括指定文件的新变化
git commit --amend -m [message] 使用一次新的commit,代替上一次提交;如果代码没有任何新变化,则用来改写上一次commit的提交信息
git commit -am “description” 快速将工作区修改的文件暂存区,本地仓库区(不包含新增的)需要add -
回退, 撤销
git checkout [file] 恢复暂存区的指定文件到工作区
在还没有add的时候 git checkout 那个路径下的文件夹或者文件名
如果已经add过的文件: git reset HEAD 那个路径下的文件夹或者文件名。(就能回到未添加的状态)然后在用 1 中的方法撤销修改。
注意:如果你把某个文件删除了,或者是新添加的文件,都不会再恢复或者去掉,但是会把文件中的添加的代码去掉。
如果已经commit过的文件:
git reset --hard HEAD^ 那么如果要回退到上上个版本只需把HEAD^ 改成 HEAD^^ 以此类推。 那如果要回退到前100个版本的话,使用上面的方法肯定不方便,我们可以使用下面的简便命令 操作:git reset --hard HEAD~100 即可。
不过还有一种比较实用的方法:
git reset --hard [你要回退的版本号] 这样就能直接回退到你指定的那一次提交。
例如:git reset --hard 852782f
可获取到每次提交的版本号 git reflog
对于已经push的, 粗暴的方法,
git reset --hard 1243ad3(commit唯一标识) 或者 git checkout (commit唯一标识 前7位)
git push origin HEAD --force (强行push)
- 配置
git config – list 显示当前的git配置
git config -e [–global]编辑git配置文件
设置提交代码时的用户信息
git config --global user.name “username”
git config --global user.email “email”
git config -l
- stash 操作
(1)git stash save “save message” : 执行存储时,添加备注,方便查找,只有git stash 也要可以的,但查找时不方便识别。
(2)git stash list :查看stash了哪些存储
(3)git stash show :显示做了哪些改动,默认show第一个存储,如果要显示其他存贮,后面加stash@{$num},比如第二个 git stash show stash@{1}
(4)git stash show -p : 显示第一个存储的改动,如果想显示其他存存储,命令:git stash show stash@{$num} -p ,比如第二个:git stash show stash@{1} -p
(5)git stash apply :应用某个存储,但不会把存储从存储列表中删除,默认使用第一个存储,即stash@{0},如果要使用其他个,git stash apply stash@{$num} , 比如第二个:git stash apply stash@{1}
(6)git stash pop :命令恢复之前缓存的工作目录,将缓存堆栈中的对应stash删除,并将对应修改应用到当前的工作目录下,默认为第一个stash,即stash@{0},如果要应用并删除其他stash,命令:git stash pop stash@{$num} ,比如应用并删除第二个:git stash pop stash@{1}
(7)git stash drop stash@{KaTeX parse error: Expected 'EOF', got '}' at position 4: num}̲ :丢弃stash@{num}存储,从列表中删除这个存储
(8)git stash clear :删除所有缓存的stash
四、扩展介绍
HEAD
origin master , origin/master master 区别
eeee
基本操作图解
我们先来理解下 Git 工作区、暂存区和版本库概念 图解:
工作区: 就是你在电脑里能看到的目录。
暂存区: 英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
版本库: 工作区有一个隐藏目录 .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 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。