一文带你精通git
- 回顾
- git对象
- 树对象
- 提交对象
- 重新认识git 基本命令
- git 高层命令
- 分支(特别重要)
- 分支冲突&分支合并
- git 存储
- git 后悔药
- 远程分支和团队协作
- 远程仓库冲突
回顾
博主之前直接已经写过了git的相关基础博客了,老铁可以自行去查看。本篇文章的目录是为了深入的学习git,关于git的基础部分我们可以
git对象
我们之前在学习git的时候开始重点学习的是暂存区、版本库.下面我们来学习一下git对象是什么。Git 的核心部分是一个简单的键值对数据库。你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索该内容。下面我们新建一个目录Test,并打开git Bash输入这个git
init命令。
我们交给git管理的文件都放到了这个目录里面。下面我们来看一条底层的git命令
git hash-object -w --stdin(或者为文件名)
下面我们来解释一下这条命令,其中
- -w表示需要将生成的对象存储起来,如果不指定该命令只会返回对象的键值。
- -stdin(standard input)该选项表示的是从标准读取内容,如果不指定该选项那么必须在命令尾部给出文件的路径.
下面我们来演示一下。
但是我们进入git目录下里面的object 目录下我们没用任务内容。下面我们加上-w选项再次来查看
此时我们看到了版本库里面已经有东西了。我们发现这个文件以生成的那一串哈西的请两位作为目录名后面的作为文件名。下面我们使用cat命令查看一下这个文件里面的内容。
我们发现这个文件里面的内容是被压缩过的。此时如果我们想要查看这个文件里面的内容我们可以使用git给我们提高的命令
git cat-file -p 哈西值
其中-p:代表的是可以自动判断类型,并为我们显示出友好的内容返回。
而-t 则是查看类型。
我们发现里面的内容就是我们之前的那段内容。现在我们久指定git hash-object做了什么事情了。给我一段内容我们把这段内容做成一个键值对,键就是这段内容的生成的哈西值而值就是这段内容,并将其放到./git/object目录当中。这也就是我们说的git对象。
总结:
git 对象本质就是key:value组成的键值对(key是value对应的hash),键值
对在git内部是一个blob类型。
下面我们就可以使用git hash-object -w 加文件名路径我们就可以让git给我们进行管理了
这个警告是这个Linux和Windows当中的换行不一样导致的。下面我们通过find 命令和 git cat-file命令来看出git版本库里面的存了什么。
我们发现这个文件已经交给git管理了,说吧了无论我们存储一段内容还是文件git都会将其做成git对象存储起来。
下面我们往test.txt里面再次写入一段内容,我们在再次查看git仓库里面保存的test.txt的内容是否会发生改变。
我们git这个数据库存的并没有发现改变。我们需要在显示的存一遍。git hash-object
此时我们发现现在这个git对象存储的才是我们修改之后的内容。我们也发现了test这段内容被存了两次。也就是我们没修改一次文件需要交给git管理就需要生成一个git对象。但是这也就有一个问题
1. 记住文件的每一个版本所对应的 SHA-1 值并不现实
2. 在 Git 中,文件名并没有被保存——我们仅保存了文件的内容
注意:
上面我们的操作都是对本地数据库进行操作不涉及暂存区
树对象
树对象(tree object),它能解决文件名保存的问题,也允许我们将多个文件
组织到一起。Git 以一种类似于 UNIX 文件系统的方式存储内容。所有内容均以树对象和数据对象(git 对象)的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象(git 对象)则大致上对应文件内容。一个树对象包含了一条或多条记录(每条记录含有一个指向 git 对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息)。一个树对象也可以包含另一个树对象。
我们可以通过 update-index;write-tree;read-tree 等命令来构建,树对像并塞入到暂存区。假设我们做了一系列操作之后得到一个树对像。这也说明了树对象和暂存区有关系。下面我们来演示一下如何生成树对象。
首先我们使用update-index命令,下面解释一下这条命令
文件模式为
100644,表明这是一个普通文件
100755,表示一个可执行文件;
120000,表示一个符号链接。
–add 选项:
因为此前该文件并不在暂存区中 首次需要—add
–cacheinfo 选项:
因为将要添加的文件位于 Git 数据库中,而不是位于当前
目录下 所有需要—cacheinfo
注意上面那段命令并没有往版本库里面放东西有兴趣的老铁可以使用find 命令进行查看即可。下面我们介绍一个命令查看一下暂存区
git ls-files -s
我们发现git对象的哈西和文件名对应上了,但是版本库里面没有东西也就是并没有生成树对象,也就是给git对象取个文件名上面那条命令将文件名和哈西对应上并将其加入到暂存区
我们可以使用git write-tree命令生成树对象,此时我们使用git cat-file -t查看其类型我们发现他是树对象。git write-tree 命令可以延迟执行。我们一直生成git对象当我们觉得可以了作为一个版本了我们就可以使用git write-tree命令生成树对象。树对象就可以代表项目的一个版本了由此可见。
我们生成树对象之后暂存区里面也就有东西了。下面我们将test.txt文件修改一下然后再生成git对象,再使用git update-index命令
此时我们发现原理暂存区里面的那条记录被覆盖掉了,再这里需要注意的是暂存区的负载是根据文件名进行覆盖的并不是整体的覆盖。就类似于这样的图
总结:
git对象:git对象代表文件的一个个版本
树对象:树对象代表项目的一个个版本
提交对象
上面我们知道了这树对象代表的是项目的一次快照也就是项目的一个版本但是我们并不知道这次干了什么事情,为什么出现了这一次版本。我们是不知道的,为了解决这个问题git引入了提交对象。
我们可以通过调用 commit-tree 命令创建一个提交对象,为此需要指定一个树对象的 SHA-1 值,以及该提交的父提交对象(如果有的话 第一次将暂存区做快照就没有父对象)
再这里就不做演示了,老铁可以自行进行测试
提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照;然后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个时间戳);留空一行,最后是提交注释接着,我们将创建另两个提交对象,它们分别引用各自的上一个提交(作为其父提交对象)
差不多图就是上面这样。
我们平时使用git log 查看拿到的哈西值就是提交对象,以后的版本回退和穿梭都是通过提交对象实现的。项目的一个版本就是一个提交对象,而项目的一次快照是树对象.
重新认识git 基本命令
下面我们来重新认识一下我们之前使用过的高层命令
git add
git commit
git push
首先第一步我们创建一个本地仓库,git init初始化一个本地仓库。我们创建一个文件并往里面写入内容
使用git ls-files -s 查看暂存区。
此时我们发现暂存区并没有东西,现在我们使用git add ./将当前路径下的文件加入到暂存区
此时我们发现暂存区里面有东西了。我们打开./git这个文件
如果暂存区里面有东西那么再 ./git 目录下会有一个index文件。下面我们再查看一下他的类型
我们发现他是一个git对象。通过上面的内容我们就能够知道git add 并不是只改变暂存区。下面我们来说一下git add 做了那些事情
1.将工作目录里面的修改做出一个git对象放到版本库当中
2.再把git对象从版本库里面拿出来放到暂存区里面
而再底层git add对应的命令是这两个命令
git hash-object -w 文件名//将其生成git对象
git update-index -add --cachinfo//将git对象和文件名对应
再这里特别需要注意的是生成git对象是增量的而不是覆盖的。这也就是git安全的问题
下面我们再来看看git commit 这个命令做了什么
1.将暂存区当中的git对象做成一个树对象
2.将树对象包裹一层注释做出一个提交对象放到版本库
下面我们来验证一下依然是上面那个操作
我们发现了我们使用git commit 之后又出现了两个对象我们可以使用git cat-file -t 查看其类型我们会发现一个是树对象一个是提交对象。现在我们就能知道了一个项目的版本是由一个树对象,一个提交对象和多个git对象组成。
下面我们来总结一下git 最基本的流程
git add ./
//对应底层命令
git hash-object -w 文件名(修改了多少个工作目录的文件命令就要执行多少次)
git update-index -add --cacheinfo命令大概是
git commit -m “注释内容”
git write-tree //生成树对象
git commit-tree //生成提交对象
git 高层命令
1.git init
要对现有的某个项目开始用 Git 管理,只需到此项目所在的目录,执行:git init
作用:初始化后,在当前目录下会出现一个名为 .git 的目录,所有 Git 需要
的数据和资源都存放在这个目录中。不过目前,仅仅是按照既有的结构框架初始化
好了里边所有的文件和目录,但我们还没有开始跟踪管理项目中的任何一个文件。
工作目录下面的所有文件都不外乎这两种状态:已跟踪 或 未跟踪,已跟踪的文件是指本来就被纳入版本控制管理的文件,在上次快照中有它们的记录,工作一段时间后,它们的状态可能是已提交,已修改或者已暂存所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照,也不在当前的暂存区域。
在这里需要注意的是:
初次克隆某个仓库时,工作目录中的所有文件都属于已跟踪文件,且状态为已提交;在编辑过某些文件之后,Git 将这些文件标为已修改。我们逐步把这些修改过的文件放到暂存区域,直到最后一次性提交所有这些暂存起来的文件。使用 Git 时的文件状态变化周期如下图所示
2. git status
我们可以使用git status查看文件的状态。现在我们把做的实验文件全部删掉重新进行实验
我们发现我们创建的文件没有交给git管理,此时这个文件处于未跟踪状态。如果此时我们使用git add将其交给git管理
当前这个状态是绿色的说明是已经被暂存了。如果我们使用git commit -m将其提交了
现在这个文件就处于以提交状态。如果我们修改了这个文件我们在使用git status 看看
此时我们发现这个文件处于已修改状态。这个状态也是未跟踪,我们只需要使用git add跟踪一下就可以。如果我们此时使用git add 将其跟踪一下,我们在进行修改一下这个文件,在使用git status这个命令查看一下
我们会发现一个是未跟踪一个是已修改,有了两种状态。此时我们需要git add重新暂存一次不然提交过去的版本很有可能不是我们想要的。
实际上 git status 的显示比较简单,仅仅是列出了修改过的文件,如果要查看具体修改了什么地方,可以用 git diff 命令.这个命令它已经能解决我们两个问题了:当前做的哪些更新还没有暂存?有哪些更新已经暂存起来准备好了下次提交?
- 当前做的哪些更新还没有暂存?,
命令:git diff(不加参数直接输入 git diff) - 有哪些更新已经暂存起来准备好了下次提交?
命令: git diff --cached 或者 git diff --staged(1.6.1 以上)
此时我们就可以看到修改了什么比git status要详细我们能够知道修改了那里。
3.git commit
当暂存区域已经准备妥当可以提交时,在此之前,请一定要确认还有什么修改过的或新建的文件还没有 git add 过,否则提交的时候不会记录这些还没暂存起来的变化。所以,每次准备提交前,先用 git status 看下,是不是都已暂存起来了,然后再运行提交命令 git commit.
命令:git commit
注意:这种方式会启动文本编辑器以便输入本次提交的说明,默认的提交消息包含最后一次运行 git status 的输出,放在注释行里,另外开头还有一空行,供你输入提交说明。你完全可以去掉这些注释行,不过留着也没关系,多少能帮你回想起这次更新的内容有哪些。另外也可以用 -m 参数后跟提交说明的方式,在一行命令中提交更新:
命令:git commit –m “message xxx”
提交时记录的是放在暂存区域的快照,任何还未暂存的仍然保持已修改状态,
可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目作一次快照,
以后可以回到这个状态,或者进行比较.
2.跳过暂存区
尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁
琐。Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit 加上 -a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤。下面我们来看一下这个如何来使用
3.删除文件git rm
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中注册删除(确切地说,是在暂存区域注册删除),然后提交。可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
什么意思了?下面我们来演示一下。我们将上面这个文件给删除掉
此时我们需要使用git add 和git commit 提交到版本库当中。此时提交至少当前这个版本当中没有快照了。
此时我们发现git add 之后暂存区里面并没有东西。但是git commit之后依然会给我们生成一个树对象提交对象,只不过这个树对象没有包含任何git对象。并且之前保存到git当中的数据是不会被修改的
也就是说我们在工作区删除了一个文件的操作其实在版本库里面是新增操作,新增了树对象和提交对象。
上面的删除操作我们可以使用git rm来代替。
4.重命名git mv
git mv file.from file.to
git status
其实,运行 git mv 就相当于运行了下面三条命令:
$ mv README.txt README
$ git rm README.txt
$ git add README
由于比较简单在这里就不做过多演示,老铁可以下来自行进行实验
5.查看历史记录 git log
在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。完成这个任务最简单而又有效的工具是 git log 命令.下面我们来演示一下
此时我们发现这个历史记录也太多了,甚至一个屏幕还装不下这是因为如果git log默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面。 正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。但是这样看起来很不爽。我们可以使用其参数来进行优化
git log --pretty=oneline
git log --oneline
这样看起来就舒服多了。注意这一串哈希值代表的是提交对象。
总结:
git init //初始化仓库
git status //查看文件状态
git diff //查看那些提交还没有暂存
git diff --staged //查看那些修改并且已经暂存了还没提交的文件
git log --oneline //查看历史记录
git add ./ //将修改添加到暂存区
git rm 文件名 //删除工作目录
git mv 源文件名 新文件名//将工作目录当中的文件进行重命名
git commit //需要写大量注释时使用
git commit -a
git commit -a -m 注释
分支(特别重要)
几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。 在很多版本控制系统中,这是一个略微低效的过程——常常需要完全创建一个源代码目录的副本。对于大项目来说,这样的过程会耗费很多时间。而 Git 的分支模型极其的高效轻量的。是 Git 的必杀技特性,也正因为这一特性,使得 Git 从众多版本控制系统中脱颖而出。
一般我们git init之后都会有一个master分支。我们每一次提交master都会跟着一起走
我们看到默认有一个master分支。其实master就是根提交对象取别名,这个master分支一定指向了一个提交对象,一般默认指向了一个最新的一个提交对象。
所以我们可以重新认识一下分支,分支就是指向最新提交对象的一个指针而已
- 创建分支 git branch 分支名
作用:为你创建了一个可以移动的新的指针。 比如,创建一个 testing 分
支:git branch testing。这会在当前所在的提交对象上创建一个指针
下面我们来创建一个分支
在这里需要注意的是每次移动都是HEAD指针带着master一起动的或者是HEAD带着其它分支一起动的
2.切换分支 git checkout 分支名
此时我们发现HEAD指针指向了testing分支了。这样做的目的主要是master存储稳定的代表我们创建一个分支然后切分到新的分支里面去修改代码,就算代码写烂了也不会影响主分支。代码写坏了我们把这个分支删掉就是了。这也就是分支的好处
我们看此时HEAD带着的是testing往前走而master并没有发生移动。如果我们发现这个分支上的代码写废了我们可以使用先切换回主分支然后将这个分支删除即可
3.git branch -d/D
我们回到主分支之后我们可以使用git branch -d删除但是-d只能删除空分支。如果分支里面有内容了我们需要使用-D选项强制删除这个分支。
git branch还有很多选项在这里不一一演示了,在这里直接给出命令.
1.git branch
不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一个列表.
2.git branch -v
可以查看每一个分支的最后一次提交
3 git branch name commitHash(重要)可以实现版本回退和穿梭
新建一个分支并且使分支指向对应的提交对象.
4 git branch –merged
查看哪些分支已经合并到当前分支,在这个列表中分支名字前没有 * 号的分支通常可以使用
git branch -d 删除掉.
5 git branch --no-merged
查看所有包含未合并工作的分支
尝试使用 git branch -d 命令删除在这个列表中的分支时会失败。如果真的想要删除分支并丢掉那些工作,可以使用 -D 选项强制删除它
如果我们需要查看完整的分支图我们可以使用git
6.git checkout -b 分支名
创建分支并切换到这个分支上来,下面演示一下这个命令的演示
如果我们需要查看这个交叉分支历史,我们可以使用
git log --oneline --decorate --graph --all
但是这样命令很复杂,非常的难记 ,这样我们可以给它配一个别名
git config --global alias.别名 命令
在这里需要注意的是配别名的时候一定需要加双引号否则会配置失败
注意切分分支的时候一定需要使用git status 下查看一下状态。否则会出现严重的问题下面我们来演示一下这个严重的问题。切换分支的时候我们一定要保证这个工作目录是干净的。现在我们来演示一下这个问题
首先我们进入testing 文件我们创建一个文件然后我们直接切换分支。
我们发现在master分支上出现了这个文件。这是因为切分分支需要改工作目录,暂存区,HEAD指针
.当我切分支的时候git发现这个文件没有暂存也没有提交很有可能丢了所有给我们保存下来了,这是git
出于安全考虑的。但是这也污染了master分支这是一个大坑,这是因为主分支一般是稳定的代码
如果我们暂存了但是没有提交此时然后切换分支此时也会污染这个master分支
所以我们切分支时一定需要保证工作区是干净的
总结:
1.最佳实际:每次切换分支时当前分支一定得是干净的(已提交状态)
2.切换分支(坑):在切换分支时,如果当前分支上有未暂存的修改(第一次)或者有未提交的暂存(第一次)分支可以切换成功但是可能会污染其它分支。
分支冲突&分支合并
分支冲突和分支合并在我的基础博客当中已经写的很非常的详细了。老铁们可以看看那篇博客在这里不过多说明
在这里主要说一下这个快进合并和典型合并
1.快进合并
在合并的时候,有时候会出现"快进(fast-forward)"这个词。 由于当前 master 分支所指向的提交是你当前提交的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)。
比如master和hotfix的合并就是这个快进合并,快进合并是不会出现分支冲突的
2.典型合并
当前的合并和你之前合并 hotfix 分支的时候看起来有一点不一样。 在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master 分支所在提交并不是 iss53 分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的工作祖先(C2),做一个简单的三方合并。
和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在
于他有不止一个父提交
许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为 develop 或者 next 的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master 分支了。,等待下一次的发布。随着你的提交而不断右移的指针。稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前。
3.分支的本质
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 它会在每次的提交操作中自动向前移动。注意 Git 的 “master” 分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所
以几乎每一个仓库都有 master 分支,是因为 git init 命令默认创建它,并且大多数人都懒得去改动它。
HEAD 引用 当运行类似于 git branch (branchname) 这样的命令时,Git会取得当前所在分支最新提交对应的 SHA-1 值,并将其加入你想要创建的任何新分支中。当你执行 git branch (branchname) 时,Git 如何知道最新提交的 SHA-1 值呢? 答案是 HEAD 文件。HEAD 文件是一个符号引用(symbolic reference),指向目前所在的分支。 所谓符号引用,意味着它并不像普通引用那样包含一个 SHA-1 值。它是一个指向其他引用的指针。
git 存储
有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态,而这时你想要切换到另一个分支做一点别的事情。 问题是,你不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。 针对这个问题的答案是 git stash
git stash 命令会将未完成的修改保存到一个栈上,而你可以在任何时候重新应用这些改动(git stash apply)
下面我们看看这个git stash 几个常见的选项
git stash list//查看存储
git stash apply stash//如果不指定一个储藏,Git 认为指定的是最近的储藏注意这样是不会弹出的
git stash pop//来应用储藏然后立即从栈上扔掉它
git stash drop//加上将要移除的储藏的名字来移除它
但是我们一般使用的是git stash 命令。现在我们来演示一下如何使用
此时我们在git status 一下
此时我们发现工作区是干净的,本质就是它给我们提交了一次。我们使用git log命令是看不到的
需要使用git reflog 进行查看。我们再次进来的
所以我们每次进来最后看一下这个分支里面的栈里面有没有这个东西,注意使用应用并弹出需要使用pop
栈不要太复杂,不然会让自己很麻烦
git 后悔药
git 的后悔药主要有工作区,暂存区,版本库。首先这个工作区的撤回我们可以直接使用ctl +z就可以了,或者我们使用git restore – 文件名或者git checkout。下面我们来演示一下
撤回对工作目录的修改.
2.撤回暂存区的修改
git reset HEAD -- .//注意最后的一个".",这条命令帮助我们一次性撤销所有放入暂存区的文件
git reset HEAD -- filename//撤销指定目标文件
然后我们在使用 git reset HEAD – 文件名撤回对暂存区的修改
这样我们就把暂存区的修改撤回了
下面我们来演示一个常见我们在提交的时候注释写错了,此时我们要如何撤回了
git commit – amend
这个命令会将暂存区中的文件提交。如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息如果你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作
git commit -m ‘initial commit’
git add forgotten_file
git commit –amend
最终你只会有一个提交 - 第二次提交将代替第一次提交的结果。
上面那张图我们发现此时我们注释写错了,我们需要进行修改此时我们可以使用git commit – amend
来修改注释
其实这个命令也就是帮我们重新提交了一次,通过日志我们发现
3.git reset 三步曲
1.移动 HEAD
reset 做的第一件事是移动 HEAD 的指向。假设我们再次修改了 file.txt 文件并第三次提交它。 现在的历史看起来是这样
第一步
git reset – soft HEAD~('HEAD ~ 也可以用提交对象的哈西值)
这与改变 HEAD 自身不同(checkout 所做的);reset 移动 HEAD 指向的分支。什么意思了
我们使用checkout 只是移动HEAD 指针。而上面这条命令是分支和HEAD指针一起动(注意他们的区别)
看一眼上图,理解一下发生的事情:它本质上是撤销了上一次 git commit 命令。 当你在运行 git commit 时,Git 会创建一个新的提交,并移动 HEAD 所指向的分支来使其指向该提交。当你将它 reset 回 HEAD~(HEAD 的父结点)时,其实就是把该分支移动回原来的位置,而不会改变索引和工作目录。 现在你可以更新索引并再次运行 git commit 来完成 git commit --amend 所要做的事情了。
我们可以使用git reflog 查看,注意git log只能查看HEAD指针所在的那条线上,而git reflog 只要HEAD指针发生变化了就会记录。
下面我们来演示一下git reset --soft HEAD~
我们发现暂存区和工作目录都没有发生改变,现在我们使用git log 查看一下
我们再次来验证一下HEAD里面的内容是什么
此时我们发现果然是这样。更多选项在后面会补充说明。
在这里我们就能够明白这个git commit --amend 就是git reset --soft HEAD~ 加上git commit 的语法糖
下面画一个图来解释一下
在这里并没用画出提交对象的哈西值,此时我们使用git commit --amend将注释修改为test v3"修改"
由于git log只能看到 HEAD 所在的那条分支所以 test v3可我们的感觉好像就像被撤回了一样
2.第二步曲
git reset --mixed HEAD~ 当然HEAD 也可以省略
git reset --mxied HEAD~会动这个暂存区和HEAD指针,下面我们来演示一下
此时我们发现暂存区里面的东西变了但是工作区里面的内容没有发现改变。下面我们在来看一下HEAD指针有没有变
我们发现这个HEAD指针也发生改变了。也就是说 git reset --mixed HEAD~ 会改变暂存区和HEAD指针。下面我先使用git reflog 和git reset --mixed HEAD~还原成原来的样子 进行下一个实验
第三步曲 git reset --hard HEAD~
你撤销了最后的提交、git add 和 git commit 命令以及工作目录中的所有工作。必须注意,–hard 标记是 reset 命令唯一的危险用法,它也是 Git 会真正地销毁数据的仅有的几个操作之一。 其他任何形式的 reset 调用都可以轻松撤消,但是 --hard 选项不能,因为它强制覆盖了工作目录中的文件。在这种特殊情况下,我们的 Git 数据库中的一个提交内还留有该文件的 v3 版本,我们可以通过 reflog 来找回它。但是若该文件还未提交,Git 仍会覆盖它从而导致无法恢复。(这个命令请谨慎使用)
这个命令做了三步改了暂存区和工作目录,它和切换分支有点像。切换分支HEAD,工作区,暂存区也会发现改变只不过切换分支不会带着分支一起走。
下面说一下git reset --hard commithash 和git checout commithash的区别
- git checkout 只动HEAD 而reset --hard会带着分支一起动(那些区域他们两是一样的)
- checkout对工作目录是安全的 rest --hard 是强制覆盖工作目录都数据是不安全的
下面我们来看看reset 加文件名
前面讲述了 reset 基本形式的行为,不过你还可以给它提供一个作用路径。 若指定了一个路径,reset 将会跳过第 1 步,并且将它的作用范围限定为指定的文件或文件集合。 这样做自然有它的道理,因为 HEAD 只是一个指针,你无法让它同时指向两个提交中各自的一部分。 不过索引和工作目录 可以部分更新,所以重置会继续进行第 2、3 步。现在,假如我们运行 git reset file.txt (这其实是 git reset --mixed HEAD file.txt 的简写形式,),它会:移动 HEAD 分支的指向 (因为是文件这一步忽略)
让索引看起来像 HEAD所以它本质上只是将 file.txt 从 HEAD 复制到索引中。
三步曲当中只有git reset [–mixed] HEAD filename 三步曲当中只有git reset [–mixed] 可以加文件名。其实原理就是,此时HEAD 指向的提交对象就可以找到上一次暂存区的内容拿上一次暂存区的内容覆盖掉现在的就可以了原理很简单。看起来像是撤销了修改。如果想全量撤销暂存区可以加文件名就可以了
三步曲总结
- 第一步曲:git reset --soft HEAD~ 只动HEAD分支跟着一起动
- 第二步曲:git reset --mixed HEAD~ 动HEAD,暂存区,分支
- 第三步曲:git rest --hard HEAD~ 动HEAD,暂存区,分支,工作区
远程分支和团队协作
在这里我们使用gitee来进行演示,在这里我们不用githup来演示。老铁们可以先去gitee上或者githup上创建一个仓库。
为了能在任意 Git 项目上团队协作,你需要知道如何管理自己的远程仓库。 远程仓库是指托管在因特网或其他网络中的你的项目的版本库。你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以读写。 与他人协作涉及管理远程仓库以及根据需要推送或拉取数据。 管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义它们是否被跟踪等等.
首先我们需要使用在本地创建一个仓库,然后再给创建一个远程仓库
git remote add 远程仓库名 url(远程仓库的地址)//添加一个远程仓库
git remote rm 远程仓库名 //删除一个远程仓库
git remote rename 名字 新名字
git remote show [remote-name]//查看某一个远程仓库的更多信息
git remote –v//显示远程仓库使用的 Git 别名与其对应的 URL
下面我们来演示一下
然后我们这个需要给这个仓库提交一些原始代码这一步通常是项目经理做的,项目经理创建仓库推送文件
而其它成员只需要git clone就可以了
成员就可以对这些文件进行修改
我们发现我们git clone之后它自动的给我们添加了远程仓库。现在成员开始修改代码
注意git clone 下来的远程仓库的别名是origin默认但是此时项目经理这边的情况是这样的,我们一起来看看吧
此时项目经理需要将这个代码拉取到本地来。此时我们需要使用git fetch命令拉取到本地来
但是此时我们查看一下本地目录看看
我们发现并没有出现ksy.txt这个文件
此时我们发现这个多了一个分支,它叫做远程跟踪分支。是本地分支和沟通的媒介。我们需要合并这个远程跟踪分支我们才能拿到这个ksy.txt 使用git merge
说白了就是git fetch 是先将代码拉取到远程跟踪分支上,所以我们才说他是本地分支和远程分支沟通的媒介。本地分支要想和远程分支沟通必须通过远程跟踪分支,我们每一次git push时都会给我们生成一个远程跟踪分支。
但是我们成员这个修改代码一般都会创建一个分支来进行推送这个代码,下面我们来演示一下
然后我们再将这个文件提交上去
那么此时项目经理那边就需要将用户提交过来的代码拿上来
此时项目经理这边多了一个远程跟踪分支而本地没有这个本地分支与之对应所以了我们需要创建一个本地分支.
此时我们发现文件的内容就下来了
现在有个问题我们成员再git push 的时候能不能直接 git push了能不能不要加后面那一堆,现在我们来演示一下
我们发现此时git不让我们提交,这是因为我们的本地分支和远程跟踪分支没有绑定关系。只有当git clone的时候本地分支和远程跟踪分支才有同步关系,也叫做本地分支跟踪了远程跟踪分支。本地分支有改变直接git push 即可。
我们可以使用git branch -u 来进行跟踪
git branch -u 跟踪的分支名
此时我们发现就提交上去了,当本地分支和远程跟踪分支同步之后。那么项目经理这边需要将数据拉取。项目经理这边需要使用使用git fetch 将代码拉取到远程跟踪分支,然后需要使用git merge合并这个远程跟踪分支。这样感觉太麻烦了,我们可以使用git pull命令下面演示一下
此时我们发现git pull弹出了一堆报错提示,这是因为这个我们这个仓库不是git clone下来的。本地分支和远程跟踪分支没有同步关系。所以它才无法拉取,我们只需要跟踪一下就可以使用git pull命令了
此时我们看到这个数据就拉取下来了。
远程跟踪分支是远程分支状态的引用。它们是你不能移动的本地分支。当你做任何网络通信操作时,它们会自动移动。它们以 (remote)/(branch) 形式命名,例如,如果你想要看你最后一
次 与 远 程 仓 库 origin 通信时 master 分 支 的 状 态 , 你 可 以 查看 origin/master 分支
当克隆 一 个 仓 库 时 , 它 通 常 会 自 动 地 创 建 一 个 跟踪 origin/master 的 master 分支
假设你的网络里有一个在 git.ourcompany.com 的 Git 服务器。 如果你从这里克隆,Git 的 clone 命令会为你自动将其命名为 origin,拉取它的所有数据,创建一个指向它的 master 分支的
指针,并且在本地将其命名为 origin/master。 Git 也会给你一个与 origin/master 分支在指向同一个地方的本地 master 分支,这样你就有工作的基础。
如果你在本地的 master 分支做了一些工作,然而在同一时间,其他人推送提交到git.ourcompany.com 并更新了它的 master 分支,那么你们的提交历史将向不同的方向前进。 只要你不与 origin 服务器连接,你的 origin/master 指针就不会移动
如果要同步你的工作,运行 git fetch origin 命令。 这个命令查找 “origin” 是哪一个服务器(在本例中,它是 git.ourcompany.com),从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master 指针指向新的、更新后的位置。
下面解释一下这个跟踪远程跟踪分支的几条命令。还是比较常用的
git branch -u origin/serverfix//跟踪那个分支
git checkout -b serverfix origin/serverfix//创建分支的时候同时跟踪一个分支
git checkout --track origin/serverfix//以serverfix创建本地分支 并跟踪origin/serverfix
当然查看远程跟踪分支我们可以使用git branch -vv
git branch -vv
远程仓库冲突
前面我们学过了,git 再本地进行这个典型合并的时候会有这个冲突。那么git 再进行远程协作时会不会有分支冲突了?当然有git pus 和git pull的时候都有可能有这个分支冲突。
如果两个人同时git push 并且两个人修改了同一个文件里面的同一行内容,此时就会产生冲突。
此时我们发现一个用户这个更改了第一行,另外一个用户也修改了第一行。第一个用户push之后第二个用户也push了此时就会发生分支冲突。此时如果我们想要解决冲突,首先需要git pull一下再产生冲突的地方
此时我们就发现出现了冲突了。解决冲突很简单只需要打开这个文件手动的更改然后再git add、commit、push就可以了。
我们自行决定然后再提交就行了。
现在冲突就解决了。再这里需要注意的时这边把冲突解决了,那边再去拉取的时候就不会产生冲突了。
再这里补充一下这个平时遇到问题时用的一些命令,再这里奉上啦
git pull origin master --allow-unrelated-histories //允许不相关历史提交,并强制合并
git push -f origin master//强制提交