正确使用 GitHub 工作流的简明教程
概述
当我们在开发个人项目时,对于 Git/GitHub 的使用往往采用的是“一把梭”的形式,即“git pull
、git add .
、git commit
、git push
”的一套组合拳。虽然在个人项目中,这是非常方便且清晰的,但在任何具有一定成熟度的团队项目中,这样“一把梭”的工作流显然是不可取的——这会造成协作者们代码提交的混乱与冲突。因此,本文旨在分享一个正确使用 GitHub 工作流的方式,这适用于个人或中小型团队的项目。
由于本文面向已熟练掌握 Git/GitHub 相关概念与操作的读者,因此本文对于 Git 的安装与 GitHub 的基础使用方法不再赘述。若您尚未接触过 Git/GitHub,抑或是对 Git/GitHub 操作不熟悉,请访问 Git 教程 | 菜鸟教程 学习 Git/GitHub 的相关知识。请在阅读本文前,确保您的 Git/GitHub 已进行了正确的初始配置。此外,本文以 Ubuntu 22.04.4 LTS 为例,并以最简单基础的 Https 协议 工作流进行演示。若您的操作系统为 Windows 或 macOS,请注意一些平台相关的(命令)格式特性(如在 Windows 上使用\
表示文件路径,虽然现代版本的 Windows 已支持使用/
来表示文件路径)。
以下是有关 Git/GitHub 的资源:
基本流程
克隆仓库
首先,我们使用 git clone
命令克隆目标仓库。以下为克隆的目标仓库与对应的 Git 操作:
# [your-repository-address] 为您想要克隆的 GitHub 仓库地址
git clone [your-repository-address].git
在本文示例中,待克隆的仓库为:https://github.com/Anawaert/GitHub-Workflow-Demonstration:
因此克隆的具体命令为:
git clone https://github.com/Anawaert/GitHub-Workflow-Demonstration.git
不难从 GitHub 页面看到,这个远程仓库已经有了两个提交,且都是 master 分支上的提交。因此,Git 分支的图谱为:
建立新的本地分支
在本地仓库创建一个新的分支,而不是在 master 分支上直接进行操作有非常多的好处:比如您可以在这个新分支上随意地对代码进行改动,而无需担心 master 分支因为改动时发生了一些错误而导致无法工作;又或者说,您可以通过这两个不同分支进行进度区分,master 分支代表着现在公域的进度,而新分支代表着您自己的改动进度……总的来说,创建一个新分支好处良多,而创建 Git 新分支的命令为:
# [new-branch-name] 代表着您想创建的分支名称
git checkout -b [new-branch-name]
使用 git checkout -b
命令会创建一个新分支,并将当前所处的分支(master 分支)复制一份到此分支。我们创建一个名为 develop 的分支,其命令为:
git checkout -b branch develop
此时 Git 分支的图谱为:
在新分支上开始我们的工作
此时,我们就可以在我们的新分支上开始我们对代码的编辑更改了。本文示例中的项目是一个计算两个整数之间各种操作关系的 C#
程序,如加减乘除。为了添加上“取模”和“乘方”的功能,对这个项目进行“贡献”,我们在 Visual Studio Code 中对代码进行编辑:
using System;
namespace GitHub_Flow_Demo
{
class Program
{
static int Add(int x, int y) => x + y;
static int Sub(int x, int y) => x - y;
static int Mul(int x, int y) => x * y;
static int Div(int x, int y) => x / y;
static int Mod(int x, int y) => x % y; // 新增
static int Pow(int x, int y) => (int)Math.Pow(x, y); // 新增
static void Main(string[] args)
{
Console.WriteLine("Input two numbers split by a space to calculate:");
string input = Console.ReadLine() ?? string.Empty;
if (input.Trim().Length <= 1)
{
Console.WriteLine("Exit");
return;
}
string[] numbers = input.Split(' ');
Console.WriteLine("Sum: " + Add(int.Parse(numbers[0]), int.Parse(numbers[1])));
Console.WriteLine("Sub: " + Sub(int.Parse(numbers[0]), int.Parse(numbers[1])));
Console.WriteLine("Mul: " + Mul(int.Parse(numbers[0]), int.Parse(numbers[1])));
Console.WriteLine("Div: " + Div(int.Parse(numbers[0]), int.Parse(numbers[1])));
Console.WriteLine("Mod: " + Mod(int.Parse(numbers[0]), int.Parse(numbers[1]))); // 新增
Console.WriteLine("Pow: " + Pow(int.Parse(numbers[0]), int.Parse(numbers[1]))); // 新增
}
}
}
当然,这个段代码看起来一定是非常“多此一举”的,即所有的计算操作本都可以直接输出。但为了演示最普遍的“编写函数与函数调用”更改,此处使用了最啰嗦的方式编写代码。
经过我们对代码的更改,此时本地磁盘上的文件内容已发生改变,且内容变化已被 Git 捕捉。观察 Visual Studio Code 左边栏的资源管理器中可以发现,此时 Program.cs
已被标记了一个红褐色的“M”,这代表着这个文件的改动未暂存。
使用 git diff
命令可查看当前我们的代码改动和先前分支中保存的代码有何差异。虽然这不是必要的,但还是强烈建议使用该命令来检查差异:
git diff
接下来使用 git add
和 git commit
命令来暂存和提交我们的代码改动,它们的使用分别如下所示:
# [file-names] 为所有您需要追踪暂存的文件,多个文件之间使用空格分隔,“.”代表当前目录中所有(子)文件
git add [file-names]
# [messgae] 为您在提交该版本时的提交信息,即变更摘要,长度应适中,信息量合适
git commit -m [messgae]
对应本文具体的示例即为:
git add .
git commit -m "Third commit, adding two functions"
此时 Git 分支的图谱为:
将本地新分支及其更改同步至远端
我们已经在本地完成了代码更改,但还需要上传推送至远端才算完成了对该项目的“贡献”。我们使用 git remote
相关的命令来查看与配置远程仓库,首先,查看远程仓库的具体信息:
git remote -v
可以看到我们的远程仓库别名为 origin,拉取与推送地址都是 https://github.com/Anawaert/GitHub-Workflow-Demonstration.git。接下来,使用 git push
命令将我们的 develop 分支推送至远端:
# [remote-repo-alias] 为远程仓库的别名
# [local-branch] 为当前的本地分支名
# [remote-branch] 为远程仓库的分支名,若省略则默认与本地分支名相同
git push [remote-repo-alias] [local-branch]:[remote-branch]
在本文示例中即为:
git push origin develop:develop
若您从未配置过 GitHub 验证相关的凭证存储,那么在推送时需要输入您的 GitHub 用户名与对应的密码。请注意,从2021年起,GitHub 不再支持使用 GitHub 账户名与密码的方式进行验证,取而代之的是 GitHub 账户名+个人许可令牌的方式。若您从未配置过 GitHub Personal Access Token,请访问 此处 以了解更多,本文仅展示一个获取 Personal Access Token 的简单流程:
首先进入 GitHub 的个人设置页,在左边栏中最下端找到“开发者设置”:
在开发者设置中,在左边栏中最下端找到“个人许可令牌”,选择“令牌(经典)”:
选择“生成新的令牌(经典)”:
填写基本令牌信息与操作权限,若非个人独用,请不要赋予该令牌全部权限:
确认生成后,请务必将该令牌复制后保存至一个安全的地方,因为该令牌明文将只有这一次展示机会:
随后,使用令牌代替密码进行推送:
打开该我们所推送的远程仓库,可以发现该仓库多出来了一个 develop 分支,此时 GitHub 也非常贴心地告诉我们该分支超前于主分支(master 分支)。
做到了这一步,我们基本上就完成了对远程仓库的“贡献”。至于远程仓库上 develop 分支和 master 的合并操作,则由远程仓库的管理者进行操作,我们只需关注自己的分支是否被合并。
此时,Git 分支的图谱为:
再接再厉
假设远程仓库管理者将您与其他人分支上的改动合并至 master 分支,并删除了其它的远端分支后,此时远端的 master 主分支与我们本地的所有分支都有了差异:
若此时我们正好又要继续开发,我们首先需要将本地仓库的分支切换回 master 分支:
# [your-branch-name] 为您要切换的分支
git checkout [your-branch-name]
切换到 master 分支,即为:
git checkout master
切换至 master 分支后,我们硬盘上的文件将对应着先前 master 分支上的改动进度:
此时,我们可以将本地的 develop 分支删除:
# [your-branch-name] 为您需要删除的分支名
git branch --delete [your-branch-name]
# 强制删除
git branch --delete --force [your-branch-name]
在本文示例中,由于 develop 分支显然与 master 分支未合并,因此删除它需要强制删除,即需使用 --force
参数:
git branch --delete --force develop
或可简写为:
# -d 代表着非强制,而 -D 代表着强制删除
git branch -D develop
最后,使用 git pull
命令将本地 master 分支与远端 master 分支进行同步:
# [remote-repo-alias] 为您需要拉取合并的远端仓库别名
# [remote-branch] 为要拉取合并的远端仓库分支名
# [local-branch] 为您本地仓库的分支名
# 注意:git push 和 git pull 的远端/本地分支名参数位置不一样,git push 为“前本后远”,git pull 为“前远后本”
git pull [remote-repo-alias] [remote-branch]:[local-branch]
即:
git pull origin master:master
运行 Visual Studio Code 并打开该仓库,即可看到 35 行处多了一句 Console.WriteLine("Exit");
(这就是 fourth-commit 的改动):
这样,我们的本地仓库与远端仓库的进度又统一了。从 fourth-commit 这个位置开始,重复上述的操作,即可完成新一轮的代码提交与项目贡献。
总结
上述流程看起来确实是比“一把梭”的方式要复杂不少,但是它的确规避掉了“多个人同时在 master 上提交代码导致主分支一团糟”的问题。其实流程总结下来就是:克隆仓库 - checkout 到新分支 - 写代码并本地 commit - push 到远端新分支 - 删本地开发分支 - 拉取同步最新 master - checkout 到新分支……
但碍于篇幅限制,本文并未阐述当我们在推送前,若远程仓库的 master 分支又更新了的应对方法,以及合并、变基冲突的解决方案。在未来,关于冲突处理将专门再开一篇文章进行讲解介绍。若各位有需要补充或分享的内容,欢迎在下方评论区留言。