背景:其实接触Git也很长时间了,自打上学那会就用Git作为版本控制工具,感触颇深。写这篇文章也是记录一下自己的理解留作日后词典进行查询,另外也是想把这些内容分享给大家。本篇将以问题为导向来阐述相关的知识,面对的对象为具有一定经验的开发者。
假设当前项目有两个分支,为master和dev,所有开发工作都在dev上进行,而master分支为用于发布生产代码的分支
我整理了我在日常工作中的十个问题,重点是第4个问题和第8个问题,如下所示:
1.撤销本地工作目录中的更改
git checkout -- <file>
这样将撤销对本地工作目录的更改,此操作与修改<file>是相反的操作。
此操作不会移动HEAD指向,这里的 “ -- ”其实就是代表的是HEAD
checkout 操作也可以跟 路径或文件如下图所示:
2.撤销本地缓存的更改
Reset With a Path
当reset 指令后面是某个文件或路径,则不会改变分支的指向(skip),只会影响缓存区和工作区的内容,如下图所示:
注意: git reset --mixed HEAD file.txt 与 git reset file.txt 是等价的
因为 --mixed是默认的策略,HEAD也是默认的指向值。
1.
//从缓存区下台这个文件,但不改变工作目录的内容,与git add <file>是反义词
git reset --mixed HEAD <file>
或
git reset <file>
2.
//从缓存区移除所有已加入的文件的缓存,而不会覆盖任何修改,与git add .是反义词
git reset
或
git reset HEAD
3.
//从缓存区移除所有已加入的文件的缓存,并且恢复工作目录
git reset --hard
或
git reset --hard HEAD
有三种选择:
--soft 只改HEAD指针的指向,不修改任何 缓存区和工作目录的内容
--mixed 默认,将改HEAD指针的指向,也将缓存区重置,但不改变工作目录的内容
--hard 改变HEAD指针的指向,缓存区重置,工作目录重置
3.当在本地提交commit后如何修改这个commit message?
git commit命令有一个amend参数,用于修改之前的提交信息
git commit --amend
当点击回车后,会出现一个编辑框,重新编辑内容并保存退出即可。
其实还有一个场景是:你写的commit message是对的,但是你忘记了修改仓库中某个文件的内容,那怎么办?
这时候你有两个方法,第一就是重新修改然后add和commit,即生成一个新的节点,后续也可以rebase解决。
第二种就是在当前这个节点上修改然后add,最后amend,即在原来的节点进行修改。
vim test.txt
git add .
git commit --amend
这是一种将忘记的修改添加到当前这个节点的方法
总结:amend这个参数不仅仅能修改commit message,还能对仓库中的文件再次进行更新和修改
4.当周三升级要对某个版本进行打包
首先通过git log
命令查询当前的历史,例如:
commit ae4fa4a6d8f9b4f6095c822935e3e0f7ab20f7cd (HEAD -> master)
Author: lsqtongxin <lsqtongxin@qq.com>
Date: Sat Jun 19 11:09:06 2021 +0800
add third.java
commit 1cf533f4bca614a163810da46657efc9343075d5
Author: lsqtongxin <lsqtongxin@qq.com>
Date: Sat Jun 19 11:08:18 2021 +0800
add second.java
commit 9d69109d65ffd13291c3f72f304e36a20b99b32b
Author: lsqtongxin <lsqtongxin@qq.com>
Date: Sat Jun 19 11:06:48 2021 +0800
add main.java
当你想使用second.java(1cf533f)来进行周三生产升级,但是当前的最新的节点为third.java(ae4fa4a)它是你最近完成的代码内容,即你电脑硬盘里显示为third.java版本。那你如何操作呢?
使用git checkout 命令来针对某个版本进行检出代码:
git checkout 1cf533f //这样你的硬盘将会恢复为second.java文件等
然后进行代码的打包、编译、测试即可,上传并升级即可。
又有人问,我第二天上班时候,如何恢复到最新的状态呢?
通过git log是没法找到third.java的commitID的,那么如何找到third.java的commitID呢?
假设我们刚刚是在master上进行操作,所以最新的third.java节点位于master分支上。
git checkout master //这样将会使恢复如初。
总结: git checkout
本质上是通过改变HEAD的指向来进行移动节点,在多分支操作中,这个命令还用于切换分支
5. 开发某个代码时想看一下之前的版本中某个文件
此操作不会移动HEAD指向
接上面的第4步骤中的git log
,例如:
git checkout <commit> <file>
查看文件之前的版本。
注意: 它将工作区中的某个文件的内容 变成指定的commitID的某个文件内容,同时缓冲区也会被修改,也就是说 你在工作区 修改<file>,然后执行这个操作,你原来的修改将不会被保存而是被commitID的文件内容进行覆盖。
那么如何返回最新的版本的file呢?
git chekcout HEAD <file>
这个命令也就是说 只能在没有任意修改的时候,进行切换来查看文件内容,否则将被覆盖。
具体比较如下图所示:
WD safe 代表 是否 working-directory safe ?
6.合并你的多个提交(压缩提交)
假设你在dev分支上,新建一个feature分支开发一个功能点,计划5个工作日完成,你为了不丢失每天的工作内容,每天在本地commit一个提交来记录你当天的工作,那么你就有了5个commitID,导致你后续会因为开发很多功能点会有很多个commitID,有的功能点需要10个工作日即10个commitID,你会发现这些commit非常繁杂。
那么我要是按照功能点的颗粒度,是否可以将这5个commitID或者10个commitID合并为一个commitID来呢?
这样会将你的历史很简洁、干净和清爽,翻阅过往的历史也会很快速。并且会使你的log保持清晰,简化整体commit数量,非常的实用。
git rebase -i <base> //在feature分支上
其中为一个基准点,为某一个commitID,都是以这个基准点进行合并,但是合并的内容不包括这个基准点。
git rebase -i cf375ac //在feature分支上
假设我们的历史从古至今为:25ccabd->0134238->d46d81e,也就是合并这三个节点为一个节点。
25ccabd为feature的第一天工作
0134238为feature的第二天工作
d46d81e为feature的第三天工作
而这个基准点为cf375ac,这个commitID cf375ac内容等等均不会变化。这里一定注意参数这不是25ccabd,然后将0134238和d46d81e前面文字由pick改为squash或者s,保存退出,然后再重新写提交信息,最后保存退出即可。
7.拉取更新
先 fetch,然后 merge。git pull
命令是整合了这两个过程的方式。
git fetch //从远程拉去默认主机origin的所有分支
git fetch <remote> //从远程拉去remote这个主机的所有分支
git fetch <remote> <branch> //从远程remote拉取某个分支的更新
git remote //查看所有的远程主机
例如:
git fetch origin dev //拉取远程origin/dev分支的更新
git branch -r //查看远程分支
git checkout dev //切换到dev分支
git merge origin/dev //将远程origin/dev合并到本地dev分支上
于是我们得到了远程origin/dev的更新,将其他同事的commit更新到了我的本地。
git pull
是上面两个过程的整合,可以参考第8步。
8.分支合并
假设我们基于dev分支开发某一个feature功能点:
git branch feature dev //基于dev新建一个feature分支
git checkout feature //切换到feature分支进行开发工作
这时候你就可以专注于你的工作,vim sample.txt / git add . /git commit -m "revise sample file"等
最后你完成了你的feature开发。
当多人协作时候,有许多人都合并了他的某个分支到dev分支上,导致dev分支有很多更新。
那么当我们完成了我们的feature功能后,我们如何操作呢?
首先我们应该远程拉取dev分支的更新:
git checkout dev //切换到dev分支
git pull origin //拉取远程origin的dev分支的更新
如果本地dev已经提交了更新,那么需要合并本地dev和远程dev:
git pull --rebase origin
当从远处origin dev更新完毕,即为(startTime)
然后在feature分支上进行rebase操作:
git rebase -i dev //这一步类似第6步骤
这一步rebase包含了两个方面的功能,1.以最新的dev为基 2.根据最新的dev进行合并多个节点。
最后切换到dev分支进行合并:
git checkout dev
git merge feature
其实这一步git merge是快速前进(Fast forward
模式)
再进行将dev分支推送到远程主机(endTime)
git push origin dev
这一步有可能失败,是因为在startTime到endTime这一段时间又有人提交更新,即你更新的时间到你提交的这段时间又有人提交,导致你的节点落后。
git pull --rebase origin
git push origin dev
这个 --rebase,就是会使用git merge替换为git rebase,即实现了git fetch和git rebase两条指令。
最后删除分支:
git branch -d feature
总结:
整体步骤分为
第一创建分支:根据业务需求在dev上创建你自己的本地分支feature
第二编写代码:在feature分支上进行编写,并产生了多个提交节点。
第三pull:先拉取远程dev的更新,
第四rebase:再基于dev进行 rebase,
第五merge,最后将feature合并到dev分支。
第六是push
第七是删除feature分支
9.撤销某次commit的更改
git revert <commitID>
这个命令的重点会再生成一个新的节点,并且不会删除这个旧的节点,这样避免了丢失项目历史。
一般用于当某个历史提交引入了bug,要么是将那个引入bug的节点revert退回,要么就是直接建立fixbug分支来解决bug再进行合并等。
10.删除某次commitID到最新节点的更改
git reset 这个命令用于改变当前分支所指向的commitID,而不是改变HEAD的指向,因为HEAD一般指向分支,如下所示:
HEAD --->>> master --->>> commitID
详细可参考 Git HEAD及detached head和Git - Reset Demystified
假设:从古至今 V1->V2->V3
git reset 有三种选择:
--soft 只改分支的指向,不修改任何 缓存区和工作目录的内容,也就是说 缓存区和工作区都保还继续保持最新的节点的内容,一般不用这个选项。
例如:git reset --soft V2 //此时 HEAD指向分支,分支指向V2,缓存区和工作区都还是V3
--mixed 默认,将改分支的指向,也将缓存区重置,但不改变工作目录的内容,也就是说 缓存区被改为commitID节点的内容,而工作目录还是保持最新节点的内容。
例如:git reset --mixed V2 //此时 HEAD指向分支,分支指向V2,缓存区是V2,工作区都还是V3
--hard 改变分支的指向,缓存区重置,工作目录重置,也就是说缓存区和工作区都被改为commitID节点的内容。
例如:git reset --hard V2 //此时 HEAD指向分支,分支指向V2,缓存区是V2,工作区是V2
10.1 删除某次commitID到目前的更改,且保存从commitID到目前节点的工作目录中各文件的内容的修改:
git reset <commit> //也可以是 HEAD^ 默认是mixed
10.2 删除某次commitID到目前的更改,不保存从commitID到目前节点的工作目录中各文件的内容的修改:
git reset --hard <commit>