2 独自使用Git时的常见场景

本机测试环境:macOS 11

14 | 怎么删除不需要的分支?

git branch -d <BRANCH_NAME>

  • -d--delete
  • 有条件地删除一个本地分支
    • ❗️条件:当该分支被合并到上游分支(本地分支对应的远端分支,可设置)或者HEAD(当前分支)时,才能成功执行删除

git branch -D <BRANCH_NAME>

  • -D--delete --force
  • 强制删除一个本地分支
    • 当确定删除分支没有风险时,可以使用

PS:

  • 不能删除当前的分支,如需删除,可通过 git checkout <BRANCH_NAME> 先切换分支
  • 删除远程分支: git push <REMOTE> --delete <BRANCH_NAME>
    • 通过 git fetch -p / --prune 同步分支列表,此时就不会显示远程已被删除的分支了

15 | 怎么修改最新commit的message?

输入 git commit --amend ,弹出文本编辑器,修改新的commit message,如红框所示:

输入 :wq 保存并退出后,终端显示修改结果:

再次查看commit信息:

修改成功!

PS:git commit --amend

  • 本质上是替换上一次提交,不局限于修改message
  • 一般用于修改未push到远端之前的commit操作

16 | 怎么修改老旧commit的message?

git rebase -i 可以用来修改前几次的commit信息, -i 为使用交互模式

——具体步骤——

先使用 git log 查看当前的版本历史:

我们想修改顺数第3个commit的message,那么需要基于它的父commit来操作。

使用git rebase -i d8796c00719323800976e6c7fcfe6b02627ec6b2 命令,末尾添加父commit的hash值(如果要对最开始的commit进行修改,可使用 git rebase -i --root ) ,弹出编辑界面:

commit的顺序倒置了一下;从Commands部分可以看到有很多用法。

我们使用 reword / r 命令,保留某次commit,但修改其message,如下:

:wq 保存退出后,又弹出一个新的编辑界面:

修改commit的message,再:wq 保存退出。

修改成功,❗️可以看到这里使用了分离头指针,修改完成后,还更新了master分支的指向,说明commit的hash值很可能发生了改变。

那么验证一下,再次通过 git log 查看版本历史:

可以发现顺数第3个commit的message修改成功,前3个commit的hash值都发生了改变,除了第3个commit的message都没有改变,其他信息其实也都没有改变,可自行验证。

⚠️:

  • 一般基于自己的分支,用在还没有提交到集成分支(团队)之前,否则一定要慎用,其会对集成分支造成影响。
  • rebase 常用于变基,可关注其和 merge 的相同点和不同点,参考

17 | 怎样把连续的多个commit整理成1个?

同样使用 git rebase -i

——具体步骤——

先使用 git log 查看当前的版本历史:

现在想合并上面红框内的3个commit,减少分支里的commit数。

使用 git rebase -i --root 命令,末尾添加父commit的hash值(因为要对最开始的commit进行修改,所以使用--root ) ,弹出编辑界面:

要合并的3个commit见上面红框内,顺序与 git log 展示的顺序相反。

使用Commands中的 squash / s 命令,可以将某个commit融合到前一个commit中,修改如下:

:wq 保存退出后,又弹出一个新的编辑界面:

添加第2行,作为融合3个commit的message,保留下面每个commit的message,再:wq 保存退出。

整合成功,❗️可以看到这里使用了分离头指针,修改完成后,还更新了master分支的指向。

git log 也验证了整合的结果,可以发现2个commit的hash值都发生了改变,因为commit的message或者父commit的指向发生了改变。

PS:

  • 整合后会导致一些commit没有用了,git会进行清理。
  • 通过 gitk --all 观察此时的分支情况:
  1. 可以发现出现了两棵树,两个分支之间已经没有关联
  2. 这是因为master分支的根commit已经改变

18 | 怎样把间隔的几个commit整理成1个?

命令: git rebase -i

通过 git log 命令查看版本历史:

想合并如红框所示的间隔的几个commit,同样使用 git rebase -i 76e5c9ca 命令,后面添加的是父commit的hash值,弹出如下熟悉的窗口:

调整要合并的commit位置,并使用 s 命令,修改如下:

:wq 保存退出后,提示存在合并冲突,需要解决。

上面蓝框中提示了接下来可以进行的操作,这里我们尝试第一种方式:解决冲突。

PS:还可以借助 git status 查看当前建议的操作:

接下来查看readme文件,解决里面的冲突,删除红框位置的代码即可:

再运行 git add readme 添加刚才的修改记录。

然后按前面的提示运行 git rebase --continue 命令,弹出编辑框,输入message信息, :wq 保存退出即可(可能还需要解决冲突,重复上述步骤即可)

最后 git log 查看版本历史:

可以看出:如果被间隔的commit和要合并的commit有关联,很可能不符合预期,导致它们被合并成一个commit,所以自己要对这几个commit的内容非常清楚。

PS:个人认为不太实用,容易产生冲突。

19 | 怎么比较暂存区和HEAD所含文件的差异?

命令: git diff --cached

别忘了这张图:

  • HEAD指向的是当前分支,分支指向的则是一个commit。

演示:

修改readme.md文件后,输入 git add readme.md 将修改添加到暂存区;

再输入git diff --cached 比较暂存区和HEAD的差异:

可以看到添加了两行文本,确认符合预期后就可以commit了。

20 | 怎么比较工作区和暂存区所含文件的差异?

命令: git diff

还可以在命令后添加文件名,从而比较指定文件在工作区和暂存区的差异,如 git diff readme.md ,当然也可以添加多个文件名。

21 | 如何让暂存区恢复成和HEAD的一样?

场景:暂存区的内容不想要了,暂存区👉HEAD。

命令: git reset HEADHEAD 后面什么也不加,则将所有文件恢复成和 HEAD 一致

可以通过 git status 观察 reset 前后的变化,见下图:

reset 之后,修改的操作又放到了工作区,可以通过 git add 命令又将它们添加到暂存区。

PS:

git diff --cached 可以验证暂存区和HEAD内容的一致性。

git reset HEADgit restore --staged . 的效果是一样的,后者是git 2.23+后新增的。

22 | 如何让工作区的文件恢复为和暂存区一样?

场景:工作区做了修改,但是不想要了,想恢复为和暂存区一样。

命令: git checkout <FILENAME>

再提醒下这张图:

过程演示:

首先通过 git add ,将修改后的readme.md文件,从工作区添加到暂存区:

观察操作前后 git diff --cached 的结果,即暂存区与HEAD差异的变化,可知 git add 成功。

然后,再来修改readme.md文件,通过 git diff 观察工作区和暂存区的差异:

红框部分为此次的修改。

通过 git status 查看工作区和暂存区的情况:

图中上半部分是暂存区中待commit的内容;下半部分是工作区中待添加到暂存区的内容。

可以看到两个部分都有对readme.md的修改,分别对应刚才的两次修改。


❗️此时,如果想把readme.md文件在工作区的内容恢复回暂存区状态,使用 git checkout readme.md 即可。

可以看到工作区的readme.md文件已经不在待添加状态了。


PS:

  • 慎用git checkout ,可能导致文件找不回。
  • Git 2.23之后,用 git switchgit restore 来替代 git checkout 功能:git switch 替换 git checkout 切换分支的功能; git restore 替换对工作区文件进行恢复的功能。
  • git checkout <FILENAME>git restore <FILENAME> 的效果是一样的,后者是git 2.23+后新增的。

23 | 怎样取消暂存区部分文件的更改?

命令: git reset HEAD [FILENAME] ,类似第21节,在命令后面多添加了具体的文件名,也可以指定多个文件,用空格隔开。

下面示例展示了暂存区中的修改被一个一个退回到工作区中:

如果想把工作区中的修改再添加到暂存区,使用 git add 命令即可。

24 | 消除最近的几次提交

命令: git reset --hard <HASH_OF_COMMIT> ,可以改变HEAD的commit指向,并且暂存区和工作区的内容都回到该commit。

请看下面的例子:

观察 git reset --hard 前后的两个点:

1)通过 git log 可以看出HEAD的指向后退了两个commit;

2)通过 git diff 可以看出工作区和暂存区从存在差异到没有差异。

25 | 看看不同提交的指定文件的差异

命令: git diff <HASH_OF_COMMIT> <HASH_OF_COMMIT> [-- FILENAME]

最后添加指定的文件, 如果不添加,则会展示所有文件的差异;

<HASH_OF_COMMIT> 可以用直接换成分支名,分支名本质上也是指向一个commit。

PS:加--会使Git更明确你的意图。

26 | 正确删除文件的方法

命令: git rm <FILENAME>

效果如下:

本质上相当于删除了文件,并把这个删除操作同步到了工作区和暂存区里,等同于:

先通过 git reset --hard HEAD 让工作区和暂存区都恢复到HEAD的状态;

通过 git status ,分别观察 rm 文件以及 git rm 文件后,工作区和暂存区的状态:

1)前者改变了工作区,但没有暂存;

2)后者同步了暂存区,此时可以commit这次修改了。

27 | 开发中临时加塞了紧急任务怎么处理?

临时加塞了紧急任务,当前工作还只是半成品,该怎么办呢?

git stash :将当前工作区和暂存区的内容迁移到一个额外的栈上,只对被跟踪的文件有效。

完成了紧急任务,此时要恢复刚刚的半成品,先查看stash了哪些内容。

git stash list :显示stash的记录列表。

然后,将最近的一次stash内容恢复到工作区内。

git stash apply :恢复栈顶的保存记录,且记录列表里还保留该记录。

git stash pop :恢复栈顶的保存记录,并把记录列表里的该记录删除

  • PS:也可以指定某一个记录恢复。

过程展示:

来了紧急任务,先将暂存区里未完成的内容stash起来。

此时stash记录列表里添加了一个记录{0},并且当前工作区和暂存区里已经干净。

忙完了,恢复stash里最近的一个记录。

可以看到 apply 是不会清除stash记录的,并且被恢复的内容放到了工作区,而之前stash的是暂存区的内容。

再来另一种方式的恢复,使用 pop 并且添加 --index 参数。

可以看到 pop 会清除该stash记录,并且被恢复的内容放到了与stash时一样的暂存区, --index 其实就代表还会恢复暂存区。


PS:

WIP全称Work in progress,表示正在工作过程中,引申含义为“目前工作区中的代码正在编写中,这部分代码不能独立运行,是半成品”。

28 | 如何指定不需要Git管理的文件?

方法:在 .gitignore 文件中声明文件类型或文件名即可(必须是 .gitignore

声明doc/和doc的区别演示

1)doc/:忽略文件夹,但不会忽略同名的文件

创建doc文件夹,在里面添加readhim文件。

.gitignore 文件里添加 doc/ 后,Git将忽略对doc文件夹的跟踪。

但是,将doc文件夹换成doc文件后,Git将不会忽略跟踪doc文件。


2)doc:同时忽略文件夹和同名文件

而对于忽略规则doc,不管是文件夹还是同名文件,都会让Git忽略对其跟踪。


⚠️:

  • 常用通配符 * 忽略同类的文件
  • 文件已经被跟踪后, .gitignore 文件就不起作用了
    • 如果提交commit后,想再忽略一些已经提交的文件呢?
    • 可以把想忽略的文件先添加到 .gitignore 文件中,然后通过 git rm --cached FILENAME 的方式忽略掉无需跟踪的文件。
  • 在Github上新建仓库时,可以选择添加一份合适的.gitignore文件:(很贴心❤️)

PS:对于不同的语言或项目,常见的 .gitignore 文件可参考github/gitignore库。

29 | 如何将Git仓库备份到本地?

相关命令: git clonegit remotegit push


常用传输协议:本地协议、http/https协议、ssh协议,后两者在工作中最常用。

传输协议又可以分为两类:哑协议和智能协议,这里以本地协议为例:

1)/Path/to/.git;2)file:///Path/to/.git

前者为哑协议,后者为智能协议。


那么两者在Git备份时有什么区别呢?

智能协议相比哑协议,1)传输进度可见,2)并且传输速度更快。


智能协议和哑协议的效果对比:

通过 pwd 获取到了之前演示用的仓库地址为: /Users/double/Desktop/test

现在通过两种协议,将仓库备份到另外的地方:test_remote目录下。

哑协议

⚠️: --bare 代表创建裸仓库,即不带工作区的仓库,其方便作为服务端;备份的是.git文件夹,所以地址要添加 /.git ;最后的 ya.git 表示仓库名。

智能协议

回到test.remote目录下,使用智能协议,即添加 file:// 前缀。

对比可以看到直观的区别:哑协议不带进度条,而智能协议有进度条。


上面展示的是从远端克隆 (clone )仓库到本地的过程,那如何推送 (push ) 本地仓库到远端呢?

回到原仓库(这里原仓库的角色从远端转变成了本地),新建一个名为 new 的分支。

此时远端intel.git仓库还只有3个分支:

在本地添加远端服务器:

git remote add intel file:///Users/double/Desktop/test_remote/intel.git

PS: intel 为服务器别名,后面是服务器地址,使用智能协议。

通过 git remote -v 可以查看详细的服务器信息。

接下来进行推送: git push REMOTE_NAME 命令

⚠️:直接推送会报错,需要先设置上游。

再次查看下远端intel.git仓库的分支情况:

可以看到分支 new 已经被推送过来了~

学到这里,有哪些是你之前不知道的呢?欢迎交流~