本文最后更新于 2024-07-14,文章内容可能已经过时。

Git😈是功能强大的版本控制工具,在git中有一些功能相似、容易混淆的命令,比如 rebase与mergereset与revertpull与fetch等。今天在这里来详细解析一下这几对容易混淆的命令,辨别两者之间的区别,明确各自的使用场景。

rebase与merge

在Git中,rebasemerge都用于合并不同分支的修改,但它们的实现方式和结果有所不同。

  1. merge:合并操作。它会取出一个公共的祖先节点,然后尝试将两个分支从该节点开始发生的所有变化都合并到一起,最终生成一个新的节点(合并提交)。这个新节点会包含两个分支的所有修改。
  2. rebase:变基操作。它会先将当前分支上的所有提交临时保存,然后将当前分支更新到目标分支的最新状态,接着将之前保存的提交逐个应用到目标分支的最新状态上,形成一个新的线性提交历史。

merge优缺点

merge的优点

  • 可以保留项目的历史分支结构和合并点时。
  • 可以保留完整的合并历史,方便追踪每个分支的修改来源。
  • 合并冲突时,可以清晰地看到冲突发生的具体位置,便于解决。

merge的缺点

  • 在多人协作时, 如果频繁使用 merge,容易导致项目提交历史变的复杂,形成“分叉历史”。
  • 解决合并冲突时,可能会引入不必要的合并提交,增加阅读和维护成本。

rebase的优缺点

rebase的优点

  • 可以保持 提交历史的线性,使得代码库更加清晰,易于阅读和维护。
  • 在解决合并冲突时,只需要解决一次,提高了效率。

rebase的缺点

  • 改变了原有的提交历史,可能导致一些基于旧提交历史的操作(cherry-pick等)出现问题。
  • 在多人开发的公共分支上使用 rebase操作可能导致其他开发者在拉去代码时遇到问题,因为他们的本地提交历史已经与远程分支不同步了。

rebase与merge的适用场景

  • merge的使用场景

当你希望保留完整的合并历史时,可以使用 merge

# 假设我们有两个分支:master 和 feature
# 在 feature 分支上开发新功能并提交
git checkout feature
# 修改文件...
git add .
git commit -m "Add feature X"

# 切换到 master 分支,并将 feature 分支的修改合并到 master
git checkout master
git merge feature

如果合并过程出现冲突,Git会提示你手动解决冲突,并提交合并后的结果。

  • rebase的使用场景

当你希望保持一个线性的、整洁的、没有交叉的commit历史时,可以使用 rebase

# 假设我们有两个分支:master 和 feature
# 在 feature 分支上开发新功能并提交
git checkout feature
# 修改文件...
git add .
git commit -m "Add feature X"

# 切换到 feature 分支,将 feature 分支上的提交变基到 master 分支的最新状态
git checkout feature
git rebase master

# 如果有冲突,解决冲突后继续 rebase
# git add .
# git rebase --continue

# 变基完成后,将 feature 分支的修改合并到 master(此时是快进合并)
git checkout master
git merge feature

注意事项

  • 在使用 git rebase 之前,确保你的分支不是与他人共享的,因为 git rebase 会改变提交的哈希值,这可能导致其他人需要解决不必要的冲突。
  • 现在主流的 代码仓库托管平台(比如gitlab、github、icoding)等,可以设置代码提交的 合并规则,有些合并规则会在当提交者的提交不是基于远程仓库的最新状态时,自动帮你产生一个合并提交; 有的合并则会要求提交者推送的提交必须基于远程仓库的最新状态,否则不允许合并,这个时候提交者需要先在本地 使用 git pull origin $BRANCH_NAME --rebase 把远程仓库的最新状态拉取到本地,然后再修改代码,提交和推送。

fetch 与 pull

在Git中,fetchpull都是用于从远程仓库获取数据的命令。但是,它们在处理方式和结果上有所不同。

fetch

fetch命令用于从远程仓库下载最新的数据到本地仓库,但它不会自动合并或修改当前的工作。fetch会将远程仓库中的最新数据拉取到本地仓库的远程跟踪分支上,但不会改变当前工作分支的内容。

示例代码:

# 切换到本地仓库
cd my-local-repo

# 从远程仓库(origin)获取最新数据,但不合并
git fetch origin

# 查看所有分支的最新状态(包括远程跟踪分支)
git branch -a

执行 git fetch origin后,你可以通过 git branch -a查看所有分支的最新状态,包括本地分支和远程跟踪分支(通常以 remotes/origin/开头)。

pull

pull命令不仅从远程仓库下载最新的数据,还会自动尝试将这些数据合并到当前工作分支中。

换句话说,pull命令是 fetchmerge两个命令的组合。

示例代码:

# 切换到本地仓库
cd my-local-repo

# 切换到要更新的分支(例如:master)
git checkout master

# 从远程仓库(origin)获取最新数据,并尝试合并到当前分支
git pull origin master

执行 git pull origin master后,Git会首先执行 fetch命令从远程仓库获取 master分支的最新数据,然后尝试将这些数据合并到本地的 master分支中。如果合并过程中出现冲突,Git会提示你手动解决这些冲突。

fecth 与 pull的区别

  1. 操作对象不同:
  • fetch操作的是远程跟踪分支。
  • pull操作的是当前工作分支。
  1. 合并方式不同:
  • fetch不会自动合并远程仓库的数据到当前工作分支中。
  • pull会尝试将远程仓库的数据合并到当前工作分支中。
  1. 提交历史不同:
  • 使用 fetch更新代码时,本地的库中对应远程跟踪分支的commit ID会更新,但当前分支的commit ID不会改变。
  • 使用 pull更新代码时,如果合并成功,本地的库中当前分支的commit ID会发生变化,因为 pull实际上会创建一个新的合并提交。

fetch与pull的实际应用

  1. 使用 fetch查看远程仓库的最新更改:

当你想要查看远程仓库的最新更改而不希望这些更改立即影响你的工作时,可以使用 fetch命令。通过查看远程跟踪分支的更改,你可以决定是否将这些更改合并到你的工作分支中。

  1. 使用 pull同步远程仓库的更改:

当你确定要将远程仓库的更改合并到你的工作分支中时,可以使用 pull命令。这将自动从远程仓库下载最新的数据并尝试将它们合并到你的工作分支中。如果合并过程中出现冲突,你需要手动解决这些冲突。


revert 与 reset

git revertgit reset 是Git中用于撤销更改的两种不同方法,它们的主要区别在于如何处理撤销的更改以及对历史的影响。下面分别解释这两种命令及其使用场景:

git revert

  • 功能git revert 创建一个新的提交,该提交“反转”或“撤销”先前提交的效果。这意味着它不会改变原有的提交历史,而是 添加一个新的提交来抵消旧提交的影响。
  • 历史影响:使用 revert后,原始提交仍然保留在历史中,只是其更改被新的提交所抵消。
  • 使用场景
    • 当你想要 撤销一个已经推送到远程仓库的提交,而又不希望删除这个提交的历史记录时。
    • 当你需要在保留历史的同时,解决某个提交引入的问题。
    • 当你在团队协作中,需要撤销一个影响多人工作的提交,而又不想破坏历史记录。

git reset

  • 功能git reset 可以将当前分支的HEAD指针移动到另一个提交点,并且可以有不同的模式(--soft, --mixed, --hard)来决定是否以及如何更新工作目录和暂存区的状态。
  • 历史影响
    • 使用 reset --soft不会改变工作目录和暂存区,只是移动HEAD指针。
    • 使用 reset --mixed(默认模式)会将暂存区重置到指定的提交状态,但工作目录保持不变。
    • 使用 reset --hard会重置暂存区和工作目录到指定的提交状态,丢弃未提交的更改。
  • 使用场景
    • 当你想要抛弃尚未提交的更改,以便重新开始时。
    • 当你在一个孤立的环境中工作,不需要考虑远程仓库或其他开发者的同步问题时。
    • 当你需要回退到某个特定的提交点,并且可能重写历史,比如在本地实验新功能或修复时。

总结

  • git revert 更适合于团队协作环境,因为它保留了历史记录,而只是在历史中添加了新的提交来纠正错误。
  • git reset 更适合于单人或独立的开发环境,特别是在你确定更改不会影响他人,或者你有权重写历史的情况下。

在使用 git reset特别是 --hard选项时要格外小心,因为它可能会永久丢失未提交的更改。而在使用 git revert时,由于它是在历史中添加新的提交,因此更加安全,但也可能导致更多的提交历史。