Git的基础知识

# Git的基础知识

# 什么是git?

Git 是一个用于管理源代码的分布式版本控制系统。版本控制系统会在您修改文件时记录并保存更改,使您可以随时恢复以前的工作版本。

使用 Git,可以轻松访问源代码的修改历史记录。可以看到版本如何更改以及更改的人。因为整个 Git 历史都存储在共享存储库中,所以 Git 可以防止旧版本的无意覆盖。

  • 跟踪代码历史记录
  • 以团队形式协作编写代码
  • 查看谁做了哪些更改

# Git 组件

Git 项目包含三个主要组件:

  • 存储库 : 存储库是跟踪项目文件所有更改的“容器”。它保存着团队所做的所有提交。可以使用 git 日志命令 (opens new window)访问提交历史记录。
  • 工作树 : 工作树或工作目录由您正在处理的文件组成。您可以将工作树视为一个文件系统,您可以在其中查看和修改文件。
  • 索引 : 索引或暂存区是准备提交的地方。暂存后,工作树中的文件将与存储库中的文件进行比较。对工作树中文件的更改在提交之前被标记为已修改。

Work tree and index

# Git 的基本工作流程

  1. 修改工作树中的文件。
  2. 暂存要包含在下一次提交中的更改。
  3. 提交您的更改。(提交将从索引中获取文件并将它们作为快照存储在存储库中。)

# Git 文件的三种状态

  • 已修改
  • 已暂存
  • 已提交

修改文件时,只会在工作树中看到这些更改。然后必须暂存更改以将它们包含在您的下一次提交中。完成暂存所有文件后,您可以提交它们并添加一条消息来描述您所做的更改。然后您的更改将安全地记录在存储库中的新快照中。

Git components

# 存储库

存储库 (即 repository) 是用于存储代码的位于中心的文件夹。一旦拥有包含文件和目录的 Git 存储库,就可以开始跟踪更改和版本。

# 远程存储库与本地存储库

Git 存储库有两种类型:远程和本地。

  • 远程存储库托管在远程服务器上 (这可以在互联网上或异地服务器上;它甚至可以是不同路径中的同一台机器) 并在多个团队成员之间共享。
  • 本地存储库为单个用户托管在本地机器上。

虽然可以在本地存储库中使用 Git 版本控制功能,但协作功能 (例如与队友拉取和推送代码更改) 仅可在远程存储库上完成。

# 创建存储库

有两种方法可以在您的机器上创建本地存储库:您可以使用计算机上的文件夹从头开始创建一个新的存储库,或者克隆一个现有的存储库。

  1. 新的存储库

    以使用 git 初始化命令 (opens new window)。它可以将 Git 引入到现有的、未版本控制的项目中,以便您可以开始跟踪更改。

  2. 复制的存储库

    可以使用 git clone 命令 (opens new window)将远程存储库复制到本地计算机上。默认情况下,git clone会自动建立一个本地主分支,来跟踪它所克隆的远程主分支。

    克隆的存储库与原始存储库具有相同的历史日志。可以引用和回溯到本地存储库中的任何提交。

# 记录变更

Git 不会自动记录您所做的每个更改。您必须通过在索引中暂存这些更改来告诉 Git 您想要记录哪些更改。暂存后,您可以提交这些更改,以便将它们记录在存储库中。

# 进行更改

在工作树中所做的修改会在索引中注明修改,索引位于存储库和工作树之间,但不会直接保存到存储库中。所有更改会先在索引中暂存,然后才会保存在存储库中。

只有索引中的更改才会提交到存储库。

# 提交更改

提交命令 (opens new window)允许您在存储库的 Git 历史记录中记录文件更改。

您提交的每个更改都可以在相应的文件或目录中按时间顺序查看。

Git committing changes

# Git 提交消息

提交更改时,您需要输入提交信息 (opens new window)。提交消息应该简洁准确地描述您所做的更改。

# 还原变更

Git 最有价值的特性之一是能够撤销错误的操作。当您进行新的提交时,Git 会存储项目的快照,以便您可以在需要时返回到较早的版本。

撤消更改的方法主要有两种:git revertgit reset

# 撤消提交

使用git revert 命令 (opens new window)撤消以前的提交。这是撤消更改的最常用方法。

Revert 命令可创建一个新的提交,用于恢复先前提交所做的更改。它允许您撤消不需要的更改,而无需完全移除提交或修改存储库的历史记录。它是一个有用的工具,用于管理对 Git 存储库的更改,同时保留其历史记录。

Diagram of reverting commits.

# 移除提交

使用 git reset 命令 (opens new window)使 HEAD 指向先前的提交。您可以通过进入重置模式来指定重置命令的范围。

# 重置模式

共有三种主要的重置模式:

  • 混合的 (默认) 模式可恢复已更改索引的状态。
  • Soft 模式可撤消先前的提交。
  • Hard 模式可移除提交的所有痕迹。

以下是每种重置模式的快速细分。

Diagram showcasing reset modes.

# 同步存储库

远程存储库可以位于私人服务器、与您不同的计算机上,或者通过某些项目管理软件托管,例如 Backlog (opens new window)

无论您在哪里托管,都需要经常将本地存储库与远程存储库同步,以便与其他团队成员共享更改内容。

可以使用三个命令同步存储库:git push (opens new window), git pull (opens new window),和 git merge (opens new window).

# 推送更改

要与他人共享更改,您必须使用 git push (opens new window)命令。这将更新远程存储库并将其与本地存储库同步。

Diagram of pushing changes.

# 拉取更改

每当有人将他们的更改推送到共享的远程存储库时,您的本地存储库就会过时。要将本地存储库与新更新的远程存储库重新同步,只需运行 git pull 命令 (opens new window)

当运行拉取命令时,最新的修订历史记录会从远程存储库下载并导入到您的本地存储库。

Diagram of pulling changes.

# 合并更改

如果您的本地存储库已过时,您对远程存储库的推送将被拒绝。

Diagram of a ouddated repo.如果您的本地存储库已过时,则无法推送到远程存储库。

在这种情况下,您可以使用 git merge 命令 (opens new window)在推送前整合远程分支的本地副本的最新修改。Git 强制执行此操作以确保其他成员所做的更改可保留在版本历史记录中。

Diagram of a merging latest changes.如果您的本地存储库已过时,则必须在推送之前合并最新更改。

在合并期间,Git 将尝试自动应用历史更改并将它们与当前分支合并。但是如果有冲突,会报错提示你手动解决。

# 解决合并冲突

在正确完成合并之前,您可能会遇到需要解决的冲突。例如,如果两个或多个成员在两个不同的分支 (即远程和本地分支) 中对文件的同一部分进行更改,Git 无法自动合并它们。

发生这种情况时,Git 会在冲突文件中添加冲突解决标记。这些标记可帮助您确定文件的哪些部分需要手动处理。

Diagram of a merging change.发生冲突的示例。

在我们上面的例子中,=====上面的所有内容都是您的本地内容,下面的所有内容都来自远程分支。

在继续创建合并提交之前,您必须依照下列所示方式解决冲突部分。

Diagram of a merging change.

# 管理 Git 历史记录

有时您需要修改本地提交历史记录。示例场景包括需要更改您的提交信息 (opens new window),更新您的提交顺序,或将提交压缩在一起。

# 修改提交

您可以通过运行 git commit --amend 命令 (opens new window)修改同一分支中的最新提交。这个命令可以方便地将新的或更新的文件添加到上一次提交中。这也是一种编辑提交消息或将提交消息添加到上一次提交的简便方法。

# 将提交复制到新分支

变基是将在一个分支上提交的所有更改复制到新分支的过程。

运行 git rebase 命令 (opens new window)并加上-i选项以重写、替换、删除和合并历史记录中的个别提交。

您也可以使用变基命令来:

  • 重写过去的提交信息
  • 将一组提交压缩在一起
  • 添加尚未提交的文件

Diagram of reabsing.Diagram of identifying a commit to rewrite.

# 将提交复制到其他分支

您可以使用 git cherry-pick 命令 (opens new window)将现有提交从另一个分支复制到存储库中的当前分支。

Cherry-picking 可以让您:

  • 将提交从错误的分支移动到正确的分支。
  • 根据另一个分支的现有提交,为当前分支添加提交。

Diagram using the cherry-pick command.

# 合并提交

压缩是将多个提交合并为一个提交的过程。

如果你运行 git merge 命令 (opens new window)--squash选项,一个新的提交会将来自一个分支的所有提交组合在一起。然后该提交可以合并到当前分支中。

Diagram using the squash command.

# Git协作

# 分支

建立分支允许开发人员从原始代码库中分支出来并将他们的工作与他人隔离开来。它还有助于 Git 可在稍后轻松合并版本。

# 什么是 Git 分支?

Git 分支在本质上是一条独立的开发线。在处理新功能或 bug 修复时,您可以使用分支来将您的工作与其他团队成员的工作隔离开来。

单独的分支可以合并为一个分支。下图说明如何使用分支并行进行开发。

Diagram of multiple projects.

主分支或其他分支中的更改不会影响您的分支,除非您从这些分支中拉取最新更改。

为每个任务 (即 bug 修复、新功能等) 创建一个新分支是一种常见的做法。这种方法让其他人可以轻松识别预期的变更,并简化回溯。

# 创建分支

创建新分支不会更改存储库;它只是指出了提交。

例如,如果我们使用 git branch 命令 (opens new window)创建一个名为issue1的分支,存储库将保持不变,但我们为当前提交添加了一个新指针。

下图显示了创建此分支时会发生的情况。

Diagram of creating branches.

# 切换分支

git checkout 命令 (opens new window)可更新工作树中的文件,以匹配存储在您希望切换到的分支中的版本。

# 指向分支

HEAD 用于表示分支的当前快照。对于一个新的存储库,在默认情况下,Git 会将 HEAD 指向主分支。更改 HEAD 指向的位置将更新您的活动分支。

~ (代字号) 和 ^ (插入符号) 指向相对于特定提交的位置。这些符号与提交引用一起使用,通常是 HEAD 或提交哈希(hash)。

  • ~ 指的是祖先 (多少代取决于数量)。
  • HEAD~1 指的是提交的第一个父级。
  • HEAD~2 指的是提交的第一个祖父级。
  • ^ 指的是合并提交的父级。
  • HEAD^1 指的是 HEAD 的第一个父级,其中 head 是合并提交。
  • HEAD^2 指的是 HEAD 的第一个祖父级,其中 head 是合并提交。

合并提交中的提交可以有两个父项。

Diagram of git symbols pointing to specific positions.

# 暂存分支

如果您的工作树中有未提交的更改 (或添加的新文件),并且您想要切换到新分支,则这些未提交的更改也将转移到新分支。您提交的更改将提交到新分支。

但是,如果 Git 发现当前分支中未提交的更改与您要切换到的新分支中的文件之间存在冲突,则不会允许您切换。您必须在切换分支之前提交或暂存这些更改。

以将stash想象成一个抽屉,用于暂时存储未提交的更改。存储允许您搁置工作树中的任何”混乱的“更改,并以 clean slate 继续在新分支上工作。

# 远程分支

# 拉取远程分支

您可以使用 git pull 命令 (opens new window)将远程存储库中的最新更改应用到本地存储库。

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

Diagram displaying an updatream branch.

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

Diagram displaying a fast-forward merge.

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

Diagram displaying a merge and commit before a pull.

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

Diagram displaying no conflict auto merge.

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

# 获取远程分支

只要没有冲突,在执行拉取时,来自远程分支的更改会自动合并到您当前的本地分支。如果您想获取远程的修改但又不想将它们合并到您当前的本地分支中,您可以执行 git fetch 命令 (opens new window)

获取将从远程下载本地分支上尚不存在的更改。获取FETCH_HEAD ref 将跟踪从远程存储库中获取的更改。

# 推送分支到远程

在将本地分支推送到远程存储库之前,所有提交都可用。换句话说,您可以按照自己的节奏在本地分支工作,而不会影响其他团队成员。

当您将本地分支推送到远程时,Git 将快进合并到目标存储库。

但是,如果推送导致非快进合并,Git 将拒绝您的推送以防止您覆盖以前的提交。在这种情况下,您必须拉取最新的远程更改并再次推送。

Diagram displaying git push command.

# 分支的工作流程

该工作流由五种类型的分支组成,每种分支具有不同的作用:

  • 主要分支
  • 功能分支 (即主题分支)
  • 发布分支
  • 修补分支
  • 开发分支 (即集成分支)

# 主分支(main)

在存储库中进行第一次提交时,默认情况下 Git 会自动创建一个主分支。随后的提交将在主分支下进行,直到您决定创建并切换到另一个分支。

驻留在主分支的代码库是生产就绪的。当最新的提交准备好用于特定版本时,它将被赋予一个发布标签。

# 功能分支

当您开始处理新功能或 bug 修复时,您应该创建一个功能分支 (即主题分支)。功能分支通常是在开发分支之外创建的。在功能的整个开发生命周期中,该主题分支可以驻留在您的本地机器中。

每当您准备好将变更集与开发分支合并时,您将把这个分支推送到远程存储库。

# 发布分支

当您推出新版本时,您会创建一个发布分支。发布分支可帮助您确保新功能的正常运行。

按照惯例,在命名发布分支时以前缀release-开头。

通常当它接近生产就绪时,您会在开发分支之外创建发布分支。

# 修补分支

当您需要快速向生产代码库添加关键修复时,您可以在主分支之外创建一个修补分支。

按照惯例,在命名修补分支时以前缀hotfix-开头。

修补分支的优点是它允许您快速发布补丁,并将更改与主分支合并,而无需等待下一个版本。

修补分支也应该与开发分支合并。

# 开发分支

您的团队应该始终保持开发分支 (即集成分支) 的稳定。您的团队从这个分支创建新的分支,它可以在生产环境中运行。持续集成工具,例如 Jenkins (opens new window) 可以做到这一点。

# 整合分支

一旦您完成了一个功能分支的工作,您通常会将它与一个开发分支合并。您可以使用 git 合并或 git 变基命令来完成此操作,但结果会有不同:

  • 合并方法:保留合并分支的所有更改和历史记录。多次合并后,修订历史记录可能会变得复杂。
  • 变基方法:维护一个干净的修订历史记录,因为合并的提交会附加在目标分支的末尾。与合并的方法相比,冲突可能更频繁地发生。

# 合并分支

您可以使用 git merge 指令 (opens new window)来将多个分支集成。

考虑下面的情况。有两个分支:一个bugfix分支,其中有一些来自main分支的提交。

Branch

在这种情况下,将“bugfix“合并回“主要“分支并不是什么大问题。那是因为自从创建“bugfix”分支以来,“主要“分支没有改变。Git 将通过将“主要“分支位置移动到“bugfix“分支的最新位置来合并它。这种合并称为“快进“。

Fast-forward merge

然而,在下面的示例中,自从bugfix分支出来后,main分支已经更新了几次。在这两个分支上执行合并时,必须组合来自bugfixmain分支的更改。

It has advanced more than when a branch is divided

对于这种合并,创建一个“合并提交“并将“主要“位置更新为新创建的合并提交。

Merge commit incorporating both changes

即使快进合并是可能的,您仍然可以明确地强制它在没有快进合并的情况下进行合并。

Non fast-forward merge

# 将分支变基

要获得更清晰的修订历史记录,您可以使用 git rebase 命令 (opens new window)来整合您的分支。

假设我们有两个具有非快进合并场景的分支。

Branch

变基将导致分支历史记录看起来类似于下面的示例。

Unify branches by using rebase

当您将bugfix分支变基到主分支时,来自bugfix分支的提交将被重播并附加到主分支的末尾。结果是bugfix分支历史记录中的单个简单提交串流。

如果在附加提交时发生冲突,Git 会要求您解决冲突,然后再继续对其他提交进行变基。

Unify branches by using rebase

变基不会移动main的位置。在任何情况下,您都可以在变基后进行快进或从bugfixmain的干净合并。

Unify branches by using rebase

# 标签

Git 标签标记并标记历史记录中的特定提交。标签通常用于指示发布版本,发布名称 (即 v1.0) 是标签的名称。

Git 标签有两种类型:

  • 轻量标签
  • 附注标签

轻量标签类似于不会改变的分支。它只是直接指向历史记录中的特定提交。轻量标签主要在您的本地工作区中暂时使用。

附注标签是校验和的,通常在计划标记重要提交时使用。您可以添加消息、签名、日期以及标记者的姓名和电子邮件。

# 查看变更

在进行大的更改之前,比如合并到主分支或向公众发布更新,开发人员会进行代码审查,包括系统地检查所有的代码更改。这个协作过程包括一个或多个审查员仔细检查代码中的潜在问题,如错误、bug 和对编码标准的遵守。

# 代码审查的好处

  • 提高代码质量
  • 知识共享与协作
  • 错误和 Bug 检测
  • 一致性和遵守标准
  • 持续学习和成长

# 拉取请求

拉取请求使代码审查成为团队工作流程中不可或缺的一部分变得更加容易。

Development without/with pull request

拉取请求通知其他开发团队成员在您的本地存储库中所做的更改,并提供以下功能:

  • 在需要审查或合并工作时通知团队成员
  • 以易于理解的方式显示对源代码所做的更改
  • 提供一个交流源代码的平台

# 拉取请求的工作流程

这是一个简单的开发工作流程,其中包含您的团队可以遵循的拉取请求:

  1. [开发人员] 克隆或拉取工作目标的源代码。
  2. [开发人员] 为工作创建一个分支。
  3. [开发人员] 进行功能的添加、修改等开发工作。
  4. [开发者] 推送完成的任务。
  5. [开发人员] 创建拉取请求,指定哪个分支合并到哪个分支。
  6. [审查者] 检查通知的拉取请求和审查中的更改。
  7. [审查者] 判断工作并在必要时向开发人员发送反馈。
  8. [审查者] 审查没有问题就合并。
  9. [审查者] 如果由于审查而不需要拉取请求,则关闭。

根据需要经常重复步骤 3 到 7 以提高源代码的质量。

# 如何使用Git

# Git命令与设置

# 目录


#

# Git 的基本命令

创建 Git 存储库

$ git init

添加文件/目录到索引

$ git add <filepattern>

将更改提交到本地存储库

$ git commit

-a选项就像一个快捷方式,可以检测更改的文件 (新添加的文件除外),将它们添加到索引, 并提交它们。

-m选项允许您同时提交和指定提交消息。如果您不指定-m,将打开一个文本编辑器,提示您输入提交消息。

撤消上一次提交的更改

$ git revert HEAD

Git revert 命令将一个提交作为参数,并创建一个新的提交来撤消该提交所做的更改。

显示工作树状态

$ git status

添加-s选项将只显示已更改的文件名。

添加-s选项,后面再接-b选项,将在输出中包含分支名称。

显示对工作树和索引的更改

$ git diff

在默认情况下,diff 命令将会显示工作树和索引之间的差异。

如果添加--cached选项,将显示索引和 HEAD 之间的差异。

如果您指定提交哈希,将会显示工作树和当前的 HEAD/提交之间的差异

显示提交日志

$ git log

在默认情况下,日志将会显示当前分支的提交列表。

指定文件名将仅显示该给定文件的提交日志。

显示提交详细信息

$ git show <commit>

指定可以通过 git log 命令 (opens new window)或命令参数中的 HEAD

重命名文件

$ git mv <oldfilename> <newfilename>

从工作树和索引中移除文件

$ git rm <file>

从工作树中移除未跟踪文件

$ git clean

添加-n选项将只显示将要移除的文件。添加-f选项实际上会移除文件。

在默认的情况下,不会移除.gitignore配置文件下列出的文件。但是,如果您指定-x选项,.gitignore下列出的文件将从工作树中移除。

将文件恢复到工作树

$ git checkout -- <file>

从索引中移除文件

$ git reset HEAD -- <file>

仅将修改和删除的文件添加到索引

$ git add -u

此命令将仅注册已添加到索引的文件的更改。它不会暂存未跟踪的文件。

# Git 的远程命令

复制存储库

$ git clone <url>

克隆命令将在本地计算机上创建现有远程存储库的副本。它还将配置本地存储库以自动跟踪远程存储库。

添加远程存储库

$ git remote add <name>

显示远程存储库列表

$ git remote

如果添加-v选项,您就可以查看远程存储库的详细信息。

从远程存储库签出分支

$ git checkout <branch>

checkout 命令会基于您已经获取的远程存储库中的分支,在您的本地存储库中创建一个分支。

创建分支更改并将其推送到远程存储库

$ git push <repository> <refspec>

push 命令在远程存储库中创建一个分支,并从本地存储库中推送更改。您必须指定远程存储库和要推送到的分支。

推送命令的-u选项将允许 Git 在成功推送本地分支时添加对远程存储库的跟踪引用。下次执行推送/获取/拉取时,您将不必指定存储库参数

检查远程存储库中的分支更改

$ git fetch <repository> <refspec>

获取命令允许您从远程存储库检索最新数据,以检查更改的内容。但是,此命令不会自动将更改合并到您现有的任何工作中。

repositoryrefspec参数都是可选的。省略存储库名称将产生与推送命令相同的操作。省略refspec参数将确保获取可应用于该远程存储库中的所有分支。

从远程存储库获取并合并最新的分支更改

$ git pull <repository> <refspec>

pull 命令将从远程存储库中检索最新更改的内容,并将其直接合并到您的本地存储库中。基本上,pull = fetch + merge

repositoryrefspec参数都是可选的。省略存储库名称将产生与推送命令相同的操作。省略refspec参数将确保拉取仅应用于当前分支。

从远程存储库删除分支

$ git push --delete <repository> <branchname>

删除远程存储库中的分支。

在推送命令中加入--delete选项,从远程存储库中删除指定的分支

在远程存储库中创建标签

$ git push <repository> <tagname>

如果添加--tags选项,则本地存储库中存在的所有标签都将被推送到远程存储库中,并与您指定的任何符号引用一起在远程存储库中创<tagname>

从远程存储库删除标签

$ git push --delete <repository> <tagname>

使用推送命令的--delete选项可以从远程存储库中删除指定的标签。

修改远程存储库地址

$ git remote set-url <name> <newurl>

将现有远程存储库的地址改为<newurl>中指定的地址。

重命名远程存储库

$ git remote rename <old> <new>

将现有远程存储库的名称从<old>改为<new>

# Git 的分支命令

显示分支列表

$ git branch

当前分支将以绿色突出显示并标有星号。

添加-r选项还将列出远程跟踪分支。添加-a选项将同时显示远程和本地分支。

创建分支

$ git branch <branchname>

重命名分支

$ git branch -m <oldbranch> <newbranch>

删除分支

$ git branch -d <branchname>

如果该分支还没有完全与上游分支合并,或者在 HEAD 中如果没有上游,Git 将不允许您删除该分支。但是,您可以指定-D以强制删除它,而不管其合并状态如何。

切换分支

$ git checkout <branch>

这将允许您签出,并切换到您想要的分支。

添加-b选项将创建一个新分支,并切换到它

合并分支

$ git merge <branch>

添加--no-ff选项将导致 git merge 命令都会是创建合并提交,而不是快进。这很有用,因为它允许您保留合并前分支的历史记录信息。

当您添加--squash选项时,Git 会创建一个代表合并更改的单个提交,而不是创建合并提交。 此提交包含来自合并分支的更改,但不包含任何与合并分支或合并过程本身相关的信息

# Git 暂存命令

暂存当前的更改

$ git stash save

暂存命令会保存您的本地修改,并恢复工作目录以匹配 HEAD 提交。

您可以选择省略保存。如果您确实指定保存,您可以输入讯息来标记存储内容 (例如,git 暂存“making a big change”)。

显示暂存列表

$ git stash list

从暂存中恢复更改

$ git stash pop

最新的暂存将在您当前的工作中恢复。

添加暂存 ID 的参数 (例如 stash@{1}) 会在您当前的工作中将此特定暂存恢复。

删除暂存

$ git stash drop

最新的暂存将被删除。

在暂存 ID 上添加参数 (例如,stash@{1}) 将删除此特定暂存。

删除所有暂存

$ git stash clear

# 标签命令

显示标签列表

$ git tag

添加-n选项将显示每个标签上的注释。

创建标签

$ git tag <tagname>

创建带有信息的标签

$ git tag -a <tagname>

删除标签

$ git tag -d <tagname>