1. Git 初学者详细指南
今天我们要聊的是版本控制系统,也就是Git。
如果不了解和理解这个工具,您真的不能成为一名成熟的程序员。当然,您不必为了持续使用而将所有 Git 命令和功能都记在脑子里。您需要了解一组命令,以帮助您了解正在发生的一切。
Git 基础知识
Git 是我们代码的分布式版本控制系统。我们为什么需要它?团队需要某种系统来管理他们的工作。需要跟踪随时间发生的变化。
也就是说,我们需要能够逐步查看哪些文件已更改以及如何更改。当您调查在单个任务的上下文中发生了什么变化时,这一点尤其重要,从而可以恢复更改。
让我们想象以下情况:我们有工作代码,其中的一切都很好,但后来我们决定改进或调整一些东西。没什么大不了的,但我们的“改进”破坏了程序的一半功能,使其无法运行。那么现在怎么办?如果没有 Git,你将不得不坐下来思考几个小时,试图记住一切最初的样子。但使用 Git,我们只需回滚提交 — 仅此而已。
或者如果有两个开发人员同时更改自己的代码怎么办?没有Git,他们就复制原来的代码文件,单独修改。有时两者都想将他们的更改添加到主目录。在这种情况下你会怎么做?
如果你使用 Git 就不会出现这样的问题。
安装 Git
让我们在您的计算机上安装 Java 这个过程对于不同的操作系统略有不同。
在 Windows 上安装
像往常一样,您需要下载并运行一个 exe 文件。这里的一切都很简单:点击第一个谷歌链接,执行安装,就是这样。为此,我们将使用 Windows 提供的 bash 控制台。
在 Windows 上,您需要运行 Git Bash。这是它在“开始”菜单中的样子:
现在这是您可以使用的命令提示符。
为避免每次都必须转到项目所在的文件夹才能在那里打开 Git,您可以在项目文件夹中使用鼠标右键打开命令提示符,并输入我们需要的路径:
在 Linux 上安装
通常 Git 是 Linux 发行版的一部分并且已经安装,因为它是最初为 Linux 内核开发而编写的工具。但有些情况下它不是。要检查,您需要打开一个终端并输入:git --version。如果你得到一个可理解的答案,那么就不需要安装任何东西。
打开终端并安装。对于 Ubuntu,您需要编写:sudo apt-get install git。就是这样:现在您可以在任何终端中使用 Git。
在 macOS 上安装
在这里,同样,您首先需要检查 Git 是否已经存在(见上文,与 Linux 相同)。
如果您没有,那么最简单的获取方式就是下载最新版本。如果安装了Xcode,那么Git肯定会自动安装。
Git 设置
Git 为提交工作的用户设置了用户设置。这是有道理的,也是必要的,因为 Git 在创建提交时将此信息用于 Author 字段。
通过运行以下命令为所有项目设置用户名和密码:
如果您需要更改特定项目的作者(例如个人项目),您可以删除“--global”。这将为我们提供以下内容:
一点理论
进入正题,我们应该给大家介绍几个新的言行……否则,就没什么好聊的了。当然,这是来自英语的行话,所以我会在括号中添加翻译。
有什么言行?
- git 仓库
- 犯罪
- 分支
- 合并
- 冲突
- 拉
- 推
- 如何忽略一些文件(.gitignore)
等等。
Git 中的状态
Git有几个雕像需要理解和记住:
- 未追踪
- 修改的
- 上演
- 坚定的
你应该怎么理解这个?
这些是适用于包含我们代码的文件的状态。换句话说,它们的生命周期通常是这样的:
- 创建但尚未添加到存储库的文件具有“未跟踪”状态。
- 当我们对已经添加到 Git 存储库的文件进行更改时,它们的状态为“已修改”。
- 在我们更改的文件中,我们选择我们需要的(例如,我们不需要编译的类),这些类被更改为“暂存”状态。
- 提交是从处于暂存状态的准备文件创建的,并进入 Git 存储库。之后,没有文件处于“暂存”状态。但可能仍然存在状态为“已修改”的文件。
这是它的样子:
什么是提交?
提交是版本控制的主要事件。它包含自提交开始以来所做的所有更改。提交像单链表一样链接在一起。
具体来说,有一个第一次提交。创建第二个提交时,它(第二个)知道第一个之后会发生什么。并且以这种方式,可以跟踪信息。
提交也有自己的信息,即所谓的元数据:
- 提交的唯一标识符,可用于查找它
- 提交作者的名字,创建者
- 创建提交的日期
- 描述提交期间所做的事情的注释
这是它的样子:
什么是分支?
分支是指向某个提交的指针。因为提交知道哪个提交在它之前,所以当一个分支指向一个提交时,所有那些先前的提交也适用于它。
因此,我们可以说您可以拥有任意数量的分支指向同一个提交。
工作发生在分支中,因此当创建新提交时,分支会将其指针移至最近的提交。
开始使用 Git
您可以单独使用本地存储库,也可以使用远程存储库。
要练习所需的命令,您可以将自己限制在本地存储库中。它只将所有项目的信息存储在本地的 .git 文件夹中。
如果我们谈论的是远程存储库,那么所有信息都存储在远程服务器上的某个地方:只有项目的副本存储在本地。可以将对本地副本所做的更改推送 (git push) 到远程存储库。
在这里和下面的讨论中,我们讨论的是在控制台中使用 Git。当然,您可以使用某种基于 GUI 的解决方案(例如 IntelliJ IDEA),但首先您应该弄清楚正在执行的命令及其含义。
在本地存储库中使用 Git
要创建本地存储库,您需要编写:
这将在控制台的当前目录中创建一个隐藏的 .git 文件夹。
.git 文件夹存储有关 Git 存储库的所有信息。不要删除它;)
接下来,将文件添加到项目中,并为它们分配“未跟踪”状态。要检查您的工作的当前状态,请这样写:
我们在 master 分支,我们会一直呆在这里,直到我们切换到另一个分支。
这显示哪些文件已更改但尚未添加到“暂存”状态。要将它们添加到“暂存”状态,您需要编写“git add”。我们在这里有几个选项,例如:
- git add -A — 将所有文件添加到“暂存”状态
- 混帐添加。— 添加此文件夹和所有子文件夹中的所有文件。与上一个基本相同;
- git add <file name> — 添加特定文件。在这里您可以使用正则表达式根据某种模式添加文件。例如,git add *.java:这意味着您只想添加扩展名为 java 的文件。
前两个选项显然很简单。随着最新的添加,事情变得更加有趣,所以让我们写:
要检查状态,我们使用我们已知的命令:
在这里您可以看到正则表达式已正常工作:test_resource.txt 现在处于“暂存”状态。
最后,使用本地存储库的最后阶段(使用远程存储库时还有一个阶段;))——创建一个新的提交:
接下来是一个很棒的命令,用于查看分支上的提交历史记录。让我们利用它:
在这里您可以看到我们已经创建了第一个提交,它包括我们在命令行上提供的文本。理解这段文字应该尽可能准确地解释这次提交期间做了什么是非常重要的。这将在未来多次帮助我们。
尚未入睡的好奇读者可能想知道 GitTest.java 文件发生了什么。让我们现在就知道。为此,我们使用:
如您所见,它仍处于“未跟踪”状态,正在等待。但是,如果我们根本不想将它添加到项目中怎么办?有时会发生这种情况。
为了让事情变得更有趣,让我们现在尝试更改我们的 test_resource.txt 文件。让我们在那里添加一些文本并检查状态:
在这里您可以清楚地看到“未跟踪”和“已修改”状态之间的区别。
GitTest.java 是“未跟踪的”,而 test_resource.txt 是“已修改的”。
现在我们有处于修改状态的文件,我们可以检查对它们所做的更改。这可以使用以下命令完成:
也就是说,您可以在这里清楚地看到我添加到我们的文本文件中的内容:hello world!
让我们将更改添加到文本文件并创建一个提交:
要查看所有提交,请编写:
如您所见,我们现在有两个提交。
我们将以相同的方式添加 GitTest.java。这里没有评论,只有命令:
使用 .gitignore
显然,我们只想在存储库中单独保留源代码,而不是其他任何东西。那还能有什么呢?至少,由开发环境生成的编译类和/或文件。
要告诉 Git 忽略它们,我们需要创建一个特殊文件。这样做:在项目的根目录中创建一个名为 .gitignore 的文件。此文件中的每一行代表一个要忽略的模式。
在此示例中,.gitignore 文件将如下所示:
目标/
*.iml
.idea/
让我们来看看:
- 第一行是忽略所有扩展名为.class的文件
- 第二行是忽略“目标”文件夹及其包含的所有内容
- 第三行是忽略所有扩展名为.iml的文件
- 第四行忽略.idea文件夹
让我们尝试使用一个例子。为了看看它是如何工作的,让我们将编译后的 GitTest.class 添加到项目中并检查项目状态:
显然,我们不想以某种方式不小心将已编译的类添加到项目中(使用 git add -A)。为此,创建一个 .gitignore 文件并添加前面描述的所有内容:
现在让我们使用提交将 .gitignore 文件添加到项目中:
现在是关键时刻:我们有一个“未跟踪”的已编译类 GitTest.class,我们不想将其添加到 Git 存储库中。
现在我们应该看到 .gitignore 文件的效果:
完美的!.gitignore +1 :)
使用分支机构
自然地,只在一个分支工作对于孤独的开发人员来说是不方便的,而当团队中有多个人时,这是不可能的。这就是为什么我们有分支机构。
分支只是指向提交的可移动指针。
在这一部分中,我们将探索在不同的分支中工作:如何将一个分支的更改合并到另一个分支,可能会出现什么冲突,等等。
要查看存储库中所有分支的列表并了解您所在的分支,您需要编写:
你可以看到我们只有一个 master 分支。它前面的星号表示我们在其中。顺便说一句,你也可以使用“git status”命令来找出我们在哪个分支。
然后有几个创建分支的选项(可能有更多——这些是我使用的):
- 根据我们所在的分支创建一个新分支(99% 的情况)
- 根据特定提交创建分支(1% 的案例)
让我们根据特定的提交创建一个分支
我们将依赖提交的唯一标识符。为了找到它,我们写:
我们用注释“added hello world...”突出显示了提交,它的唯一标识符是 6c44e53d06228f888f2f454d3cb8c1c976dd73f8。我们想创建一个从此提交开始的“开发”分支。为此,我们写:
创建一个分支时,只有来自 master 分支的前两个提交。为了验证这一点,我们首先确保切换到不同的分支并查看那里的提交数量:
正如预期的那样,我们有两个提交。顺便说一下,这里有一个有趣的点:这个分支中还没有 .gitignore 文件,所以我们的编译文件 (GitTest.class) 现在以“未跟踪”状态突出显示。
现在我们可以通过编写以下代码再次查看我们的分支:
可以看到有两个分支:“master”和“development”。我们目前正在开发中。
让我们在当前的基础上创建一个分支
创建分支的第二种方法是从另一个分支创建它。我们想在master分支的基础上创建一个分支。首先,我们需要切换到它,下一步是创建一个新的。让我们来看看:
- git checkout master——切换到master分支
- git status — 验证我们实际上在 master 分支中
这里可以看到我们切换到了master分支,.gitignore文件生效了,编译后的类不再高亮为“untracked”。
现在我们在master分支的基础上新建一个分支:
如果您不确定这个分支是否与“master”相同,您可以通过执行“git log”并查看所有提交来轻松检查。应该有四个。
解决冲突
在我们探讨什么是冲突之前,我们需要讨论将一个分支合并到另一个分支。
这张图描绘了将一个分支合并到另一个分支的过程:
在这里,我们有一个主要分支。在某些时候,从主分支创建一个辅助分支,然后对其进行修改。工作完成后,我们需要将一个分支合并到另一个分支中。
在我们的示例中,我们创建了 feature/update-txt-files 分支。正如分支名称所示,我们正在更新文本。
现在我们需要为这项工作创建一个新的提交:
现在,如果我们想将 feature/update-txt-files 分支合并到 master 中,我们需要到 master 中编写“git merge feature/update-txt-files”:
因此,master 分支现在还包含添加到 feature/update-txt-files 的提交。
添加了此功能,因此您可以删除功能分支。为此,我们写:
让情况复杂化:现在假设您需要再次更改 txt 文件。但是现在这个文件也将在 master 分支中被更改。换句话说,它会平行变化。当我们想将我们的新代码合并到 master 分支时,Git 将无法弄清楚该怎么做。
我们将基于 master 创建一个新分支,对 text_resource.txt 进行更改,并为此工作创建一个提交:
...我们对文件进行更改
转到 master 分支并在与 feature 分支相同的行上更新此文本文件:
…我们更新了 test_resource.txt
现在最有趣的一点是:我们需要将更改从 feature/add-header 分支合并到 master。我们在master分支,所以只需要写:
但结果会在 test_resource.txt 文件中发生冲突:
在这里我们可以看到 Git 无法自行决定如何合并这段代码。它告诉我们需要先解决冲突,然后才执行提交。
好的。我们在文本编辑器中打开有冲突的文件并查看:
要了解 Git 在这里做了什么,我们需要记住我们做了哪些更改以及在哪里,然后进行比较:
- 主分支中这一行的更改位于“<<<<<<< HEAD”和“=======”之间。
- feature/add-header 分支中的更改位于“=======”和“>>>>>>> feature/add-header”之间。
这就是 Git 告诉我们它无法弄清楚如何在文件中的这个位置执行合并的方式。它把这个部分从不同的分支分成两部分,并邀请我们自己解决合并冲突。
很公平。我大胆地决定删除所有内容,只留下“header”一词:
让我们看一下更改的状态。描述会略有不同。我们没有“修改”状态,而是“未合并”。那么我们可以提到第五种身份吗?我不认为这是必要的。让我们来看看:
我们可以说服自己,这是一个特殊的、不寻常的案例。让我们继续:
您可能会注意到描述建议只写“git commit”。让我们尝试这样写:
就这样,我们做到了——我们在控制台中解决了冲突。
当然,这在集成开发环境中可以做得更容易一些。例如,在 IntelliJ IDEA 中,一切都设置得非常好,您可以在其中执行所有必要的操作。但是 IDE 在“幕后”做了很多事情,我们常常不了解那里到底发生了什么。当没有理解时,问题就会出现。
使用远程存储库
最后一步是找出使用远程存储库所需的更多命令。
正如我所说,远程存储库是存储存储库的地方,您可以从中克隆它。
有哪些远程存储库?例子:
- GitHub 是最大的存储库和协作开发存储平台。
- GitLab 是一个基于 Web 的开源 DevOps 生命周期工具。它是一个基于 Git 的系统,用于管理代码存储库,具有自己的 wiki、错误跟踪系统、CI/CD 管道和其他功能。
- BitBucket 是一个基于 Mercurial 和 Git 版本控制系统的项目托管和协作开发的 Web 服务。有一段时间,它比 GitHub 有一个很大的优势,因为它提供免费的私有存储库。去年,GitHub 也免费向大家推出了这个能力。
- 等等…
使用远程存储库时,首先要做的是将项目克隆到本地存储库。
为此,我们导出了我们在本地制作的项目现在每个人都可以通过编写自己克隆它:
现在有项目的完整本地副本。为了确保项目的本地副本是最新的,您需要通过编写来拉取项目:
在我们的例子中,远程存储库中目前没有任何变化,所以响应是:已经是最新的。
但是,如果我们对远程存储库进行任何更改,本地存储库会在我们拉取它们后更新。
最后,最后一个命令是将数据推送到远程存储库。当我们在本地做了一些事情,想要发送到远程仓库时,首先要在本地创建一个新的commit。为了证明这一点,让我们在文本文件中添加其他内容:
现在对我们来说很常见——我们为这项工作创建一个提交:
将其推送到远程存储库的命令是:
就这样吧!
有用的链接 |
---|
|
2. 如何在 IntelliJ IDEA 中使用 Git
在这一部分中,您将学习如何在 IntelliJ IDEA 中使用 Git。
所需输入:
- 阅读、跟进并理解前面的部分。这将有助于确保一切都已准备就绪。
- 安装 IntelliJ IDEA。这里的一切都应该井井有条:)
- 分配一个小时来完全掌握。
让我们使用我在有关 Git 的文章中使用的演示项目。
在本地克隆项目
这里有两个选项:
- 如果你已经有一个 GitHub 帐户并想稍后推送一些东西,最好 fork 项目并克隆你自己的副本。您可以在另一篇文章“分叉工作流示例”的标题下阅读有关如何创建分叉的信息。
- 克隆存储库并在本地执行所有操作,但无法将整个操作推送到服务器。
要从 GitHub 克隆项目,您需要复制项目链接并将其传递给 IntelliJ IDEA:
-
复制项目地址:
-
打开 IntelliJ IDEA 并选择“从版本控制获取”:
-
复制粘贴项目地址:
-
系统将提示您创建一个 IntelliJ IDEA 项目。接受提议:
-
由于没有构建系统,我们选择“Create project from existing sources”:
-
接下来你会看到这个漂亮的屏幕:
现在我们弄清楚了克隆,你可以四处看看。
作为 Git UI 的 IntelliJ IDEA 第一眼
仔细查看克隆的项目:您已经可以获得很多关于版本控制系统的信息。
首先,我们在左下角有版本控制面板。在这里您可以找到所有本地更改并获得提交列表(类似于“git log”)。
让我们继续讨论 Log。有一定的可视化可以帮助我们准确地了解开发是如何进行的。例如,您可以看到创建了一个新分支,并在 txt commit 中添加了 header,然后将其合并到 master 分支中。如果单击一个提交,您可以在右上角看到有关该提交的所有信息:所有更改和元数据。
此外,您可以看到实际的变化。我们还看到那里解决了冲突。IDEA 也很好地展示了这一点。
如果您双击在此提交期间更改的文件,我们将看到冲突是如何解决的:
我们注意到在左侧和右侧我们有需要合并为一个的同一文件的两个版本。在中间,我们有最终的合并结果。
当一个项目有很多分支、commit、用户时,需要按分支、用户、日期分别查找:
在开始之前,也值得解释一下如何理解我们处于哪个分支。
在右下角,有一个标有“Git: master”的按钮。“Git:”后面的内容是当前分支。如果单击该按钮,您可以做很多有用的事情:切换到另一个分支、创建一个新分支、重命名现有分支等等。
使用存储库
有用的热键
为了以后的工作,你需要记住几个非常有用的热键:
- CTRL+T — 从远程存储库 (git pull) 获取最新更改。
- CTRL+K — 创建提交/查看所有当前更改。这包括未跟踪和修改的文件 (git commit)。
- CTRL+SHIFT+K — 这是将更改推送到远程存储库的命令。所有在本地创建但尚未在远程存储库中创建的提交都将被推送 (git push)。
- ALT+CTRL+Z — 将特定文件中的更改回滚到在本地存储库中创建的最后一次提交的状态。如果在左上角选择整个项目,则可以回滚所有文件中的更改。
我们想要什么?
为了完成工作,我们需要掌握一个随处可见的基本场景。
目标是在单独的分支中实现新功能,然后将其推送到远程存储库(然后您还需要创建到主分支的拉取请求,但这超出了本课的范围)。
这样做需要什么?
-
获取主分支中的所有当前更改(例如,“master”)。
-
从这个主要分支,为您的工作创建一个单独的分支。
-
实施新功能。
-
去主分支检查我们工作时是否有任何新的变化。如果没有,那么一切都很好。但是,如果有变化,那么我们将执行以下操作:转到工作分支并将更改从主分支重新设置为我们的分支。如果一切顺利,那就太好了。但完全有可能会发生冲突。碰巧的是,它们可以提前解决,而不会在远程存储库中浪费时间。
你想知道为什么你应该这样做吗?这是一种礼貌,并且可以防止在将您的分支推送到本地存储库后发生冲突(当然,仍然有可能发生冲突,但会变得更小)。
-
将您的更改推送到远程存储库。
如何从远程服务器获取更改?
我们通过新的提交向 README 添加了描述,并希望获得这些更改。如果在本地存储库和远程存储库中都进行了更改,那么我们将被邀请在合并和变基之间进行选择。我们选择合并。
输入 CTRL+T:
您现在可以看到 README 是如何更改的,即从远程存储库中提取更改,并且在右下角您可以看到来自服务器的更改的所有详细信息。
基于master新建一个分支
这里的一切都很简单。
转到右下角并单击Git: master。选择+ 新分支。
选中Checkout branch复选框并输入新分支的名称。在我们的例子中:这将是readme-improver。Git: master然后将更改为Git: readme-improver。
让我们模拟并行工作
要出现冲突,就必须有人制造冲突。
我们将通过浏览器使用新的提交来编辑 README,从而模拟并行工作。就好像有人在我们处理同一个文件时对它进行了更改。结果会发生冲突。我们将从第 10 行中删除单词“fully”。
实现我们的功能
我们的任务是更改 README 并为新文章添加描述。也就是说,Git 中的工作是通过 IntelliJ IDEA 进行的。添加这个:
更改已完成。现在我们可以创建一个提交。按CTRL+K,这给了我们:
在创建提交之前,我们需要仔细查看此窗口提供的内容。
在Commit Message部分,我们编写与提交相关的文本。然后要创建它,我们需要单击Commit。
我们写 README 已经更改并创建提交。左下角会弹出一个警告,其中包含提交的名称:
检查主分支是否发生变化
我们完成了任务。有用。我们编写了测试。一切安好。但是在推送到服务器之前,我们仍然需要检查主分支是否有变化。怎么会这样?很容易:有人在您之后收到任务,并且有人完成任务的速度比您完成任务的速度快。
所以我们需要去master分支。为此,我们需要执行以下屏幕截图右下角所示的操作:
在 master 分支中,按CTRL+T从远程服务器获取其最新更改。查看发生了什么变化,您可以轻松地看到发生了什么:
“fully”这个词被删除了。也许营销人员认为它不应该这样写,并给开发人员一个任务来更新它。
我们现在有一个最新版本的 master 分支的本地副本。返回readme-improver。
现在我们需要将更改从 master 分支重新设置为我们的。我们这样做:
如果你做的一切都正确并跟着我,结果应该在 README 文件中显示冲突:
在这里,我们还有很多信息需要了解和吸收。此处显示的是存在冲突的文件列表(在我们的示例中为一个文件)。我们可以从三个选项中进行选择:
- 接受你的——只接受来自 readme-improver 的更改。
- 接受他们的——只接受主人的改变。
- 合并——自己选择要保留的和丢弃的。
目前尚不清楚发生了什么变化。如果 master 分支有变化,那肯定是那里需要的,所以我们不能简单地接受我们的变化。因此,我们选择merge:
这里我们可以看到分为三部分:
- 这些是自述文件改进程序的更改。
- 合并的结果。目前,它是更改之前存在的内容。
- 来自 master 分支的更改。
我们需要产生一个让每个人都满意的合并结果。查看我们更改之前更改的内容,我们意识到他们只是删除了“fully”这个词。好的没问题!这意味着我们还将在合并结果中删除它,然后添加我们的更改。更正合并结果后,我们可以单击Apply。
然后会弹出一个通知,告诉我们变基成功了:
那里!我们通过 IntelliJ IDEA 解决了第一个冲突。
将更改推送到远程服务器
下一步是将更改推送到远程服务器并创建拉取请求。为此,只需按CTRL+SHIFT+K即可。然后我们得到:
在左侧,将有一个尚未推送到远程存储库的提交列表。右侧将是所有已更改的文件。就是这样!按Push,你会体验到快乐 :)
如果推送成功,你会在右下角看到这样的通知:
奖励:创建拉取请求
让我们转到 GitHub 存储库,我们看到 GitHub 已经知道我们想要什么:
单击比较和拉取请求。然后单击创建拉取请求。因为我们提前解决了冲突,现在在创建一个pull request的时候,我们可以立即合并它:
就这样吧!
GO TO FULL VERSION