Git 项目包含三个主要组件:存储库(repository)、工作树(working area)、暂存区(staging area)

  • 存储库是包含了项目完整历史记录的地方,每次提交后,存储库都会更新。您可以使用git 日志命令访问提交历史记录。
  • 工作树:假设你和你的团队在开发一个网站项目。你已经克隆了这个项目的 Git 存储库,并在本地计算机上创建了一个新文件夹来存放项目文件。这个文件夹就是你的“工作树”。
  • 暂存区(或索引)是一个中间区域,表示你已经选择了哪些文件或更改准备提交。可以使用git add命令将创建或修改的文件纳入暂存区,将文件添加到暂存区后,可以在提交时记录更改
# Git 日志命令
git status
# 添加-s选项将只显示已更改的文件名。
# 添加-s选项,后面再接-b选项,将在输出中包含分支名称。


Git分支

# 查看分支列表,当前分支将以绿色突出显示并标有星号。
git branch		# 这个命令显示的是本地分支
git branch -r   # 显示远程分支
git branch -a   # 显示本地分支和远程分支

对Git分支的理解

在 Git 中,创建新分支其实就是创建一个新的“指针”指向当前的提交,而不直接影响存储库中的内容。这可以通过一个简单的图示来理解,帮助你明白分支的概念。

假设你在项目中有如下提交记录:

A — B — C (main)

其中 ABC 是提交,每个字母代表一个提交的快照,main 是当前的分支指针,指向最新的提交 C

创建新分支

现在,假设你使用 git branch issue1 命令创建了一个新的分支 issue1,此时会发生什么?

  1. 不更改内容:存储库中文件的实际内容没有任何变化,也不会新增提交。
  2. 新增指针:Git 只是创建了一个名为 issue1 的新指针,这个指针也指向提交 C
A — B — C (main, issue1)

现在 mainissue1 都指向提交 C,两个分支是相同的。

切换到新分支并继续开发

如果你现在切换到 issue1 分支并继续提交新的更改,例如提交一个 D

  1. issue1 指向提交 D,而 main 仍然停留在提交 C
  2. 此时提交历史记录会变成:
A — B — C (main)
           \
            D (issue1)

这样,你的 main 分支没有受到 issue1 分支的更改影响。

HEAD 指针

1. 什么是 HEAD

  • HEAD 是 Git 中的一个特殊指针,用于指向你当前所在的分支的最新提交。
  • 在一个新的存储库中,HEAD 默认指向主分支(通常是 main)。
  • 如果切换分支或查看特定的提交,HEAD 的指向位置也会随之更改,代表当前的活动分支或提交。

例子

  • 如果当前在 main 分支,HEAD 会指向 main 的最新提交。
  • 当你切换到另一个分支 issue1HEAD 就会指向 issue1

2. ~ 符号的用法

  • ~ 表示“第 N 代祖先”,即从当前提交向上回溯 N 次的提交。
  • HEAD~1 表示当前提交的“第一个父提交”,也就是直接的上一个提交。
  • HEAD~2 表示当前提交的“第二个祖先”,相当于从当前提交再回溯两次。

图示例: 假设提交历史如下:

A — B — C — D (HEAD)
  • HEAD 当前指向提交 D
  • HEAD~1 指向 C(D 的直接父提交)。
  • HEAD~2 指向 B(再向上回溯一个父提交)。

3. ^ 符号的用法(针对合并提交)

  • ^ 通常用于合并提交,它用来指定不同的“父提交”。
  • 合并提交可能有两个父提交,分别用 ^1^2 来表示。
  • HEAD^1 表示合并提交的第一个父提交(主线)。
  • HEAD^2 表示合并提交的第二个父提交(分支线)。

例子: 假设存在合并提交 M,它是从 XY 合并而来的:

X — M (HEAD)
 \ 
  Y
  • HEAD^1 会指向 X(第一个父提交)。
  • HEAD^2 会指向 Y(第二个父提交)。

4. HEAD~^ 的结合使用

这些符号可以结合使用来指定更深层次的提交。例如:

  • HEAD~3 表示向上回溯三代提交。
  • HEAD^1~2 表示在合并提交 HEAD 的第一个父提交处,再向上回溯两代。

总结

  • HEAD 指向当前提交或活动分支的最新提交。
  • ~ 用于回溯到“第 N 代”祖先提交。
  • ^ 用于选择合并提交的第一个或第二个父提交,适用于合并提交的情况。

暂存分支(stashing)

简单来说,“stash” 就是 Git 提供的一个工具,用来暂时保存你工作树中的未提交的更改,这样你可以在没有干扰的情况下切换到其他分支工作。我们来理解一下:

1. 未提交的更改与切换分支

假设你的工作树中有一些未提交的更改,比如你正在编辑的代码文件。这些更改目前还没有被提交。

  • 如果直接切换到新分支,这些未提交的更改会“跟随”你一起切换到新分支上,并在新分支中继续保留。
  • 如果在新分支上提交,这些更改会被记录到新分支的历史中,而不是当前分支中。

2. 更改冲突

如果 Git 发现你当前分支中的未提交更改和你要切换到的新分支之间存在冲突(例如文件被同时修改),Git 会阻止你切换到新分支,除非你先处理这些更改。为了解决这个问题,你有两种选择:

  • 提交:你可以将当前的更改提交到当前分支,以便保存这些更改。
  • 暂存:如果不想提交这些更改,也不希望它们影响其他分支,可以使用 Git 的 stash 命令将它们暂时存起来。

3. stash (存储)如何帮助

git stash 命令可以把你当前的未提交更改保存到一个临时的“抽屉”里,以便你清理工作树来切换分支。使用 stash 可以让你在切换分支时不受未提交更改的干扰。

示例: 假设你在 main 分支上工作,做了一些未提交的修改,突然需要切换到 feature 分支:

  1. 你可以使用 git stash 将这些未提交的更改保存起来。
  2. 工作树现在恢复为干净的状态,你可以顺利切换到 feature 分支并在新分支上进行其他工作。
  3. 当你完成后,可以回到 main 分支,并使用 git stash apply 取出之前保存的更改,继续编辑或提交。

4. stash 的使用方式总结

  • git stash:把当前的未提交更改保存到一个临时存储区域,并清理工作树。
  • git stash apply:将存储的更改重新应用到工作树,可以在原始分支或者其他分支中使用。
  • git stash list:查看已存储的更改列表。
  • git stash drop:删除特定的 stash 存储记录。

总结

Git 的 stash 功能就像一个“临时抽屉”,用来存储你当前的未提交更改。这样,在切换分支时可以避免这些更改干扰你,并确保在需要时可以重新拿出这些更改继续工作。

git pull

可以使用git pull将远程存储库中的最新更改应用到本地存储库。

例如,假设远程分支位于本地分支的上游。远程分支将包含本地分支的所有更改,如下所示。

01

在这种情况下,如果我们要将远程分支 (origin/main) 的合并应用到我们的本地分支 (main),这将是一个快进合并。

02

但是如果本地 main 分支中的更改不存在于远程 origin/main 分支中,则拉取命令将执行合并,且将创建将这些更改绑定在一起的合并提交。

03

执行拉取时,会在本地存储库中自动创建合并提交。如果存在冲突,您将必须解决冲突并手动提交合并。

04

如果没有冲突,提交将自动合并。

git fetch

1. git fetchgit pull 的区别

  • git pull:会从远程分支下载最新的更改,并自动合并到你当前的本地分支。
  • git fetch:只从远程分支下载更改,但不会合并到你的本地分支。这意味着这些更改只是在你的本地仓库中被“获取”,但你的工作树和当前分支不会被更新。

2. FETCH_HEAD 是什么?

当你使用 git fetch 命令时,Git 会从远程分支下载新的提交,并保存这些更新的引用到一个叫做 FETCH_HEAD 的临时区域。这只是一个指针,表示你刚刚从远程仓库获取的最新状态。

比如,你在 main 分支上运行 git fetch origin,这不会改变你的 main 分支状态,但会在 FETCH_HEAD 指向远程分支的最新提交。你可以通过查看 FETCH_HEAD 来检查获取的更改。

3. 如何使用 FETCH_HEAD

获取完成后,你有两个选择:

  • 手动合并:你可以执行 git merge FETCH_HEAD 将刚刚获取的更改合并到你的当前分支。
  • 查看更新情况:在决定合并前,你可以先查看远程分支的变更,比如通过 git log FETCH_HEAD 来检查获取的提交内容。

举个例子

假设你的提交历史如下:

  • 远程仓库(origin)有新提交 EF

      A — B — C — D — E — F (origin/main)
    
  • 本地仓库仍然停留在 D

      A — B — C — D (main)
    

执行 git fetch

当你执行 git fetch origin 时,本地不会立即更新到 EF,但这些新提交会被下载到 FETCH_HEAD 中。你现在的状态是:

A — B — C — D (main)         <- 当前分支
               \
                E — F (FETCH_HEAD)

如果你执行 git merge FETCH_HEAD

此时,本地分支 main 将与 origin/main 合并,结果如下:

A — B — C — D — E — F (main)

使用 git pull

如果你执行 git pull 而不是 git fetch,Git 会自动完成 fetchmerge 两个步骤,因此会直接将远程更改合并到你的本地分支,相当于一步完成获取和合并操作。

mergerebase的例子

假设你在本地的 main 分支上,并且 origin/main 上有新的提交 EF,而你的本地 main 分支有一个不同的提交 K。现在你的提交历史看起来像这样:

远程分支 (origin/main):
A — B — C — D — E — F 

本地分支 (main):
A — B — C — D — K

1. 执行 git fetch 会发生什么?

当你执行 git fetch 时,Git 会将远程的 EF 提交下载到本地,但不会更改你当前 main 分支的状态。Git 会将 FETCH_HEAD 指向 origin/main 的最新提交 F。现在的状态如下:

A — B — C — D — E — F (origin/main)
             \
              K (main)

需要注意:这里的origin/main并不是远程分支本身,而是 git fetch 从远程仓库拉取的最新状态在本地的“映射”,是远程分支origin/main的本地追踪分支。它反映了执行git fetch时远程分支origin/main的状态

origin/main:这是你本地存储的远程追踪分支,反映了远程仓库 main 分支的最新状态,但并不是一个实际的可操作分支。

(🔴彻底懵逼了😳,先将就看吧……)

2. 合并远程的更改

此时,你可以选择如何将远程的更改合并到本地分支 main。你有以下几种常用的方式:

方法 1:git merge origin/main

如果你想将 origin/main 的更改合并到本地的 main 分支,可以直接执行:

git merge origin/main

这会创建一个新的合并提交 M,包含 KF 的合并内容,合并后的历史如下:

              E — F (origin/main)
             /     \
A — B — C — D — K - M (main)

方法 2:git rebase origin/main

你也可以使用 git rebase,把本地的提交 K 移动到 origin/main 最新提交 F 的后面。执行以下命令:

git rebase origin/main

rebase重写提交历史,使 K 看起来像是基于 F 提交的。历史记录将变成:

A — B — C — D — E — F (origin/main)
                      \
                        K (main)

这样,提交记录会更加线性,看起来像 K 是在 EF 之后产生的。

多分支下的操作细节


冲突发生的几种情况

感觉这部分内容还挺多的,可以单独写一篇文章了

冲突不仅仅会在切换分支时发生,Git 中的冲突还可能在其他多种操作中出现,尤其是在团队协作或多分支开发的场景下。以下是一些常见的可能发生冲突的情况:

1. 合并分支时的冲突

当你尝试将一个分支合并到另一个分支时,如果两个分支中同一个文件的同一行被不同地修改过,就会发生冲突。例如:

  • main 分支中的 config.txt 修改了 port=3306
  • feature 分支中的 config.txt 修改了 database=remotehost
  • 当你尝试 git merge feature 时,Git 会发现这两个修改存在冲突,因为它们在相同文件上有不兼容的更改。

解决方法:手动编辑冲突的文件,选择合并内容,并使用 git addgit commit 解决冲突。

2. rebase时的冲突

git rebase 是将当前分支上的更改“重新播放”到另一个分支的顶部。它类似于合并操作,但会重写提交历史。

  • 如果你在 feature 分支上进行了几次提交,而 main 分支在相同的文件上也进行了修改,那么在 git rebase main 时可能会遇到冲突。

解决方法:像合并冲突一样解决,手动调整冲突文件内容,git add 并继续 rebase 操作。

3. 变基或交互式变基中的冲突

如果你使用交互式变基(git rebase -i)来修改提交的顺序、删除或编辑提交,可能会导致冲突,因为提交之间的更改顺序被重排,造成无法自动应用的情况。

解决方法:在遇到冲突时,可以手动编辑文件,使用 git add 并继续 rebase

4. 在拉取远程更改(git pull)时的冲突

当你从远程仓库拉取代码(git pull)时,如果你的本地分支和远程分支都修改了同一个文件的相同内容,Git 将无法自动合并,导致冲突。

解决方法:Git 会提示你有冲突,需要手动合并本地和远程的更改,然后使用 git addgit commit 来解决冲突。

5. Cherry-pick 操作中的冲突

git cherry-pick 是从另一个分支中复制一个特定提交到当前分支。如果要拣选的提交中的更改和当前分支的某个提交发生冲突,Git 会提示冲突。

解决方法:在冲突文件中解决冲突,手动合并变更后,git add 并继续 cherry-pick

总结

冲突的情况不仅仅出现在切换分支时,还会在合并、rebase、pull、cherry-pick 等多种 Git 操作中出现。每次发生冲突时,通常需要手动解决冲突内容,将更改提交,然后继续操作。