git stash
1、应用场景:
(1)当正在dev分支上开发某个项目,这时项目中出现一个bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用git stash命令将修改的内容保存至堆栈区,然后顺利切换到hotfix分支进行bug修复,修复完成后,再次切回到dev分支,从堆栈中恢复刚刚保存的内容。
(2)由于疏忽,本应该在dev分支开发的内容,却在master上进行了开发,需要重新切回到dev分支上进行开发,可以用git stash将内容保存至堆栈中,切回到dev分支后,再次恢复内容即可。
总的来说,git stash命令的作用就是将目前还不想提交的但是已经修改的内容进行保存至堆栈中,后续可以在某个分支上恢复出堆栈中的内容。这也就是说,stash中的内容不仅仅可以恢复到原先开发的分支,也可以恢复到其他任意指定的分支上。
2、原理:
将本地没提交的内容(工作区和暂存区)进行缓存并从当前分支移除,缓存的数据结构为堆栈,先进后出。
3、注意点:
stash 只会操作被git追踪的文件,对于新增的文件,需要进行git add [文件名]让git追踪这文件,再进行stash就可以对新文件进行操作。
4、常用指令:
git stash:将所有未提交的修改(工作区和暂存区)保存至堆栈中,用于后续恢复当前工作目录。git默认按如下规则标识储藏记录(WIP意为work in progess, index用于后面取出所对应储藏的修改):
git stash save "xxx":作用等同于git stash,区别是可以加一些注释
git stash list: 返回缓存的列表
git stash pop:将堆栈中最新的内容pop出来应用到当前分支上,且会删除堆中的记录。如果从stash中恢复的内容和当前目录中的内容发生了冲突,那么会提示报错,需要解决冲突,可以通过创建新的分支来解决冲突。
git stash apply:与pop相似,但他不会在堆栈中删除这条缓存,适合在多个分支中进行缓存应用
git stash drop [名]:从堆栈中移除某个指定的stash,举例:git stash drop stash@{0}
git stash clear:清除堆栈中的所有 内容
git stash show:查看堆栈中最新保存的stash和当前目录的差异。git stash show stash@{1}查看指定的stash和当前目录差异。通过 git stash show -p 查看详细的不同。同样,通过git stash show stash@{1} -p查看指定的stash的差异内容。
git stash branch:从最新的stash创建分支。应用场景:stash中的内容和当前目录的内容发生冲突,这里通过创建新的分支来解决。发生冲突时,需手动解决冲突。
git merge / git rebase
应该明白这两个命令都用于把一个分支的变更整合进另一个分支——只不过他们达成同样目的的方式不同。
场景:当你在feature分支开发新功能时,另一位团队成员更新了main分支的内容。为了把main分支里新增的代码应用到feature分支,有两种方法:merge 和 rebase。
1、git merge main
会在feature分支中创建一个合并提交,这次提交会连结两个分支的提交历史。
问题:为什么gitLab中merge后,history里新增了多个main里的修改历史,而不是只有一次?答:merge之后,feature分支的history中应该会包含master的commit,同时新增一个合并提交。图片有点误导,history并非只看绿色球
优点:合并操作很友好,没有破坏性。现存的分支历史不会发生什么改变。这一特性避免了rebase操作的所有缺陷。
缺点:会在提交历史上增加一个无关的提交历史。如果main分支的更新非常活跃,会对feature分支的提交历史产生相当程度的污染。
2、git rebase main
把feature分支的起始历史放到main分支的最后一次提交之上。会通过为原始分支的每次提交创建全新的提交(提交内容一样,但commit id不同),从而重写原始分支的提交历史。
优点:可以让项目提交历史变得非常干净整洁:1、消除了git merge操作所需创建的没有必要的合并提交。2、rebase会造就一个线性的项目提交历史,不会碰到任何历史分叉,使得git log等命令更简单。
缺点:如果不能遵循rebase的黄金法则,重写项目提交历史会为协作工作流程带来潜在的灾难性后果;rebase操作丢失了合并提交能够提供的上下文信息——所以你就无法知道feature分支是什么时候应用了main分支的变更。
问题:会出现冲突吗 答:
3、可交互式rebase
可交互式rebase让你在把变更提交给其他分支之前有机会对提交记录进行修改。通常使用场景在于合并feature分支到main分支之前,对于feature分支杂乱的提交记录进行整理。这一点是git merge操作所无法提供的。
使用步骤:
- git checkout feature
- git rebase -i main
(1)执行以上命令会打开一个文本编辑器,其中内容为分支中需要移动的所有提交列表。
(2)通过修改pick命令或者对提交历史进行重新排序???,你可以让最终的提交历史变成任何你希望的样子。
(3)当你保存并关闭这个文件之后,Git会根据你的调改结果执行rebase操作。
其中,第(1)步的git rebase 用法:当使用git rebase命令时,有两种选项可以作为新的base:feature分支的父分支(比如 main 分支),或者是本分支内历史中的某一次提交。后者示例:git rebase -i HEAD~3、git rebase -i f1f92b
其中,在第(2)步,可以执行的6个操作:
- reword:修改提交信息
- edit:修改此提交
- squash:将提交融合到前一个提交中
- fixup:将提交融合到前一个提交中,不保留该提交的日志消息
- exec:在每个提交上运行我们想要 rebase 的命令
- drop:移除该提交。
4、rebase操作黄金法则
git rebase的黄金法则 =》永远不要在公共分支上使用它!
如果你通过同一分支与其他开发者进行协作,那么这个分支就是公共分支!
所以任何时候要执行git rebase命令之前,先确认“是否有其他人也正在使用此分支?”如果答案是确定的,那么你就应该停下来想想有没有其他非破坏性的操作(比如试试git revert命令)???。除了这样的情况之外,重写提交历史都是安全的。
5、实用场景
(1)清理本地自己分支的提交记录,让每一次提交都更加聚焦并有意义
(2)引入上游的修改
(3)整合审查通过的功能:被团队审查通过的功能代码,可以先使用rebase将新代码移动到main分支的顶端,然后在进行git merge合并新功能到main分支中。在merge到main分支之前进行一次rebase,可以保证这次merge操作是可以快速前进的,这样提交历史看上去就是完美的线性。这也给你机会可以在真正合并之前进行一次提交历史的清理。
无论是个人开发,还是公司协作开发,只要没有特殊需求,用merge准没错!!
git revert 还原
git revert 可以撤销指定的提交内容,做了一个反向操作,撤销后会生成一个新的commit。注意: 可能会出现冲突,那么需要手动修改冲突的文件。
1、两种commit:
讨论 revert 时,需要分两种情况:(1)常规的 commit,也就是使用 git commit 提交的 commit;(2) merge commit,在使用 git merge 合并两个分支之后,你将会得到一个新的 merge commit。
merge commit 和常规 commit 的不同之处在于: merge commit 包含两个 parent commit,代表该 merge commit 是从哪两个 commit 合并过来的。
2、revert 常规commit
使用 git revert <commit id> 即可,git 会生成一个新的 commit,将指定的 commit 内容从当前分支上撤除。
3、revert merge commit
如果直接使用 git revert ,git 也不知道到底要撤除哪一条分支上的内容,这时需要指定一个 parent number 标识出"主线",主线的内容将会保留,而另一条分支的内容将被 revert。
revert merge commit 需要添加 -m 选项以代表这次 revert 的是一个 merge commit。需要注意的是,-m 选项接收的参数是一个数字,数字取值为 1 和 2,也就是 git show 命令里 Merge 行里面列出来的第一个还是第二个,其含义用来保留某个分支。
操作示例如下:git revert -m 1 bd86846
4、场景:revert之后重新上线!
假设狗蛋在自己分支 goudan/a-cool-feature 上开发了一个功能,并合并到了 master 上,之后 master 上又提交了一个修改 h,这时提交历史如下:
突然,大家发现狗蛋的分支存在严重的 bug,需要 revert 掉,于是大家把 g 这个 merge commit revert 掉了,记为 G,如下:
然后狗蛋回到自己的分支进行 bugfix,修好之后想重新合并到 master,直觉上只需要再 merge 到 master 即可(或者使用 cherry-pick),像这样:
i 是新的 merge commit。但需要注意的是,这 不能 得到我们期望的结果。因为 d 和 e 两个提交曾经被丢弃过,如此合并到 master 的代码,并不会重新包含 d 和 e 两个提交的内容,相当于只有 goudan/a-cool-feature 上的新 commit 被合并了进来,而 goudan/a-cool-feature 分支之前的内容,依然是被 revert 掉了。
所以,如果想恢复整个 goudan/a-cool-feature 所做的修改,应该先把 G revert 掉:
其中 G' 是对 G 的 revert 操作生成的 commit,把之前撤销合并时丢弃的代码恢复了回来,然后再 merge 狗蛋的分支,把解决 bug 写的新代码合并到 master 分支。
git reset
常见的git reset命令包括:
–soft 重置 HEAD,保留index区和工作区,让仓库恢复到执行git commit之前的状态。
–mixed 重置HEAD和index,保留工作区。默认参数。
–hard 重置HEAD、index和工作区。
1、git reset --mixed
(1)修改了工作区的一个文件并通过git add命令添加到index 区,但是又想要恢复刚刚的git add操作
=》执行 git reset或者git reset --mixed, 这种情况下会重置index区的变更,保留工作区内容
(2)如果我们已经执行完git commit命令,但是想要进行恢复重置的话
=》执行 git reset HEAD^,就会重置index区域和repository区,但是会保留工作区内容
2、git reset --soft
(1)我们已经执行完git commit操作,这时我们发现需要commit内容存在错误,需要恢复
=》可以执行 git reset --soft HEAD^,执行后 repository区域的已经被重置,而index区域的依然被保留
3、git reset --hard
(1)在执行完git commit后
使用git reset --hard命令,执行后 head会指向上一次commit,之前修改的内容已经不在了。
4、git revert 和 git reset的区别
- git reset 是回滚到对应的commit-id,相当于是删除了commit-id以后的所有的提交,并且不会产生新的commit-id记录,如果要推送到远程服务器的话,需要强制推送-f
- git revert 是反做撤销其中的commit-id,然后重新生成一个commit-id。本身不会对其他的提交commit-id产生影响,如果要推送到远程服务器的话,就是普通的操作git push就好了
git fetch + git merge = git pull
远程仓库副本, 可以理解为存在于本地的远程仓库缓存。如需更新,可通过git fetch/pull命令获取远程仓库内容。使用fech获取时,并未合并到本地仓库,此时可使用git merge实现远程仓库副本与本地仓库的合并。
git fetch 指令并没有影响本地分支,即head指针位置未改变。
git log
git log是用来查看历史提交记录的。如果你不指定分支或者master,默认情况下git log显示的是目前你HEAD的位置的git提交日志。
默认不加参数,最近的排在最上方,显示提交对象的哈希值,作者、提交日期、和提交说明。按q退出历史记录列表。
git log 命令有非常多的参数选项,比如:
--oneline:只显示提交的 SHA1 值和提交信息,SHA1 还是缩短显示前几位,一般为前七位。
--graph:绘制一个 ASCII 图像来展示提交历史的分支结构,类似于一个树形结构
--stat:在git log 的基础上输出文件增删改的统计数据。
-p:与--stat类似,不过-p参数更为详细,可以看到每个文件更为详细的修改内容。
git reflog
- 使用git log命令只可以查看到HEAD指针及其之前的版本信息,如果版本发生过回退操作,则可能会出现,HEAD指针之后仍存在历史提交版本的情况,而这些提交版本信息通过git log命令是看不到的。即:git log命令是显示当前的HEAD和它的祖先,递归是沿着当前指针的父亲,父亲的父亲,……,这样的原则。
- 我们可以通过使用git reflog命令,就可查看到所有历史版本信息。由于查看所有历史版本信息的目的,大多是为了进行版本回退或恢复操作所使用,从中找到所需的commit索引,所以该命令被命名为reflog,即:引用日志。
git log命令与git reflog命令作用范围示意图:
HEAD是什么
git其实就是靠HEAD知道我们该处于哪个分支的,你可以把HEAD理解成一个指针,HEAD指针通常指向我们所在的分支(的分支指针)。
当然HEAD的指向是可以改变的,比如你提交了commit,切换了仓库,分支,或者回滚了版本,切换了tag等。
HEAD指的就是 .git/HEAD 文件,内容就是HEAD指针所指向的分支:
从上述返回信息可以看出,当前HEAD指针指向了另一个文件,这个文件就是.git/refs/heads/master,那么我们顺藤摸瓜,看看.git/refs/heads/master这个文件的文件内容
可以看到,这个文件的内容是一串哈希码,而这个哈希码正是master分支上最新的提交所对应的哈希码。
当我们在某个分支上创建新的提交时,分支指针总是会指向当前分支的最新提交。而刚才又说过,HEAD指针通常会指向当前所在分支的分支指针。
结合上述两点,我们可以得出如下结论:HEAD指针 ——–> 分支指针 ——–> 最新提交。
也就是说,通常情况下,HEAD指针总是通过分支指针,间接的指向了当前分支的最新提交。
参考文章:
Git使用Merge和Rebase blog.csdn.net/kevinxxw/ar…
Git 基本命令 merge 和 rebase,你真的了解吗-Michael翔 www.cnblogs.com/michael-xia…
动图展示 10 大 Git 命令 juejin.cn/post/684490…
git stash详解 blog.csdn.net/stone_yw/ar…
Git 之 revert blog.csdn.net/liuxiao7238…
git reset详解 blog.csdn.net/yaobao888/a…
Git之旅(8):HEAD是啥?www.zsythink.net/archives/34…
每天一Git之 Git log zhuanlan.zhihu.com/p/183131156
Git操作 — 51.git reflog命令 www.jianshu.com/p/7e4cef386…
\