这篇是 Git 学习总帖的第二部分。上一篇主要整理的是本地仓库:.git、工作区、暂存区、版本库、回退、撤销、分支、合并、冲突、stash 等内容。

这一篇接着往后走,开始把代码放到远程仓库里。也就是:GitHub 仓库页面怎么看、怎么 clone 到本地、怎么 push、怎么 pull、怎么建立本地分支和远程分支的连接、.gitignore 怎么用、标签怎么管理,以及多人协作时到底应该怎么合并。

🌈 个人主页: 小小、码农的 CSDN 博客
🔥 系列方向: git学习
💪 学习宣言: 命令可以慢慢背,但逻辑一定要先想通。

目录

  • 一、Git 是分布式版本控制器
  • 二、创建远程仓库,先认识 GitHub 页面
  • 三、把远程仓库克隆到本地:HTTPS 和 SSH
  • 四、向远程仓库推送:git push
  • 五、从远程仓库拉取:git pull
  • 六、本地分支和远程分支怎么建立连接?
  • 七、.gitignore:哪些文件不应该提交
  • 八、配置命令别名:让长命令短一点
  • 九、标签管理:给重要版本起名字
  • 十、多人协作一:远程先有分支,本地再连接
  • 十一、多人协作二:本地先有分支,再推送到远程
  • 十二、远程分支删除了,本地为什么还能看到?
  • 十三、企业级开发模型:Git Flow
  • 十四、总结

一、Git 是分布式版本控制器

上一篇文章里,我们一直在本地使用 Git。比如:

git init
git add .
git commit -m "第一次提交"

这时候代码的版本记录都在自己电脑上,也就是本地仓库里。

但是 Git 不只是能在本地用,它是一个分布式版本控制器。分布式的意思是:每个人电脑上都可以有一份完整的仓库,大家再通过远程仓库进行同步。

可以先这样理解:

本地仓库:自己电脑上的 Git 仓库
远程仓库:GitHub / Gitee / GitLab 上的 Git 仓库

它们之间最常见的关系如下:

在这里插入图片描述

常见命令主要有三个:

命令 作用
git clone 把远程仓库复制到本地
git push 把本地提交推送到远程仓库
git pull 把远程仓库的更新拉取到本地,并尝试合并

这里要先记住一句话:

git commit 只是提交到本地仓库,不是提交到 GitHub。想让 GitHub 上也有这次提交,还需要 git push

也就是说,平时写代码的基本路线是:

修改文件 -> git add -> git commit -> git push

前两个阶段上一篇已经学过,这一篇重点就是从 push 开始,真正和远程仓库打交道。

二、创建远程仓库,先认识 GitHub 页面

远程仓库可以放在很多平台上,比如 GitHub、Gitee、GitLab。这里我用 GitHub 举例。

创建 GitHub 仓库的过程本身不复杂:登录 GitHub,点击创建仓库,填写仓库名,选择公开或私有,然后创建即可。

创建完之后,会进入类似这样的仓库页面:

在这里插入图片描述

作为新手,不需要一上来把 GitHub 所有功能都学完,先认识几个最常用的地方就行。

区域 作用
Code 查看代码、切换分支、复制 clone 地址
Issues 提问题、记录 bug、记录需求、讨论任务
Pull requests 提交合并请求,团队协作里经常用
Actions 自动化构建、测试、部署
Branches 查看仓库里的分支
Tags 查看版本标签

1. Code:代码入口

Code 是最常用的入口。

点绿色的 Code 按钮,可以看到仓库的克隆地址。这个地址后面会用于:

git clone 仓库地址

它一般有两种形式:

HTTPS 地址:https://github.com/用户名/仓库名.git
SSH 地址:git@github.com:用户名/仓库名.git

后面会单独讲 HTTPS 和 SSH 的区别。

2. Issues:问题和任务记录

Issue 可以理解为项目里的“问题单”或“任务单”。

它不只用来记录 bug,也可以记录:

  • 新需求;
  • 文档改进;
  • 待讨论的问题;
  • 某个功能的开发任务;
  • 用户反馈。

在这里插入图片描述

比如团队里有人发现登录功能有问题,就可以提一个 Issue。后面修复这个问题时,也可以把对应分支、提交、PR 关联起来。

3. Pull Request:请求合并代码

Pull Request 简称 PR,可以先理解成一句话:

我这个分支写完了,请求把它合并到目标分支,麻烦大家看一下有没有问题。

在这里插入图片描述

PR 里一般能看到:

  • 从哪个分支合并到哪个分支;
  • 改了哪些文件;
  • 每一行代码的差异;
  • 代码审查评论;
  • 自动测试有没有通过;
  • 最后能不能合并。

自己练习时,可以先在本地 merge。但以后真正工作里,更推荐通过 PR 来合并,因为这样方便代码评审,也方便留下协作记录。

三、把远程仓库克隆到本地:HTTPS 和 SSH

远程仓库创建好之后,我们要把它拿到本地开发,就需要 git clone

命令格式:

git clone 仓库地址

1. HTTPS 克隆

HTTPS 地址一般长这样:

https://github.com/用户名/仓库名.git

在你想存放项目的文件夹里打开终端,执行:

git clone https://github.com/用户名/仓库名.git

克隆完成后,会多出一个项目文件夹。进入这个文件夹:

cd 仓库名

查看当前仓库关联的远程地址:

git remote -v

可能看到类似结果:

origin  https://github.com/用户名/仓库名.git (fetch)
origin  https://github.com/用户名/仓库名.git (push)

这里的 origin 是 Git 默认给远程仓库起的名字。

fetch 表示从这个地址拉取,push 表示往这个地址推送。也就是说,这个本地仓库已经知道自己要和哪个远程仓库同步了。

2. SSH 克隆

SSH 地址一般长这样:

git@github.com:用户名/仓库名.git

使用方式:

git clone git@github.com:用户名/仓库名.git

但是 SSH 不是复制地址就一定能用。它需要先配置 SSH key。

如果没有配置,可能会出现:

Permission denied (publickey).
fatal: Could not read from remote repository.

这句话的意思是:GitHub 还不认识你这台电脑,所以不让你通过 SSH 操作仓库。

3. SSH key 的基本配置思路

生成 SSH key:

ssh-keygen -t rsa -C "你的邮箱"

一路回车后,一般会在用户目录下生成:

~/.ssh/id_rsa
~/.ssh/id_rsa.pub

注意:

  • id_rsa 是私钥,不能随便发给别人;
  • id_rsa.pub 是公钥,可以添加到 GitHub。

查看公钥:

cat ~/.ssh/id_rsa.pub

把输出内容复制到 GitHub 的 SSH keys 里。配置好之后,可以测试:

ssh -T git@github.com

如果提示认证成功,之后就可以用 SSH 地址进行 clone、push、pull 了。

4. name 和 email 要注意

本地提交时,Git 会记录提交者信息:

git config user.name
git config user.email

如果是全局配置:

git config --global user.name "你的名字"
git config --global user.email "你的邮箱"

建议 user.email 使用 GitHub 账号里已经验证过的邮箱。这样提交推到 GitHub 后,贡献记录更容易正常关联到你的账号。

四、向远程仓库推送:git push

本地提交之后,想把提交同步到远程仓库,就用 git push

比如本地主分支叫 master

git push origin master

它的意思是:

把本地 master 分支推送到 origin 远程仓库的 master 分支

更完整的写法是:

git push origin master:master

冒号左边是本地分支,冒号右边是远程分支:

git push origin 本地分支名:远程分支名

如果本地分支名和远程分支名一样,就可以省略冒号和冒号后面的内容:

git push origin master

这里一定要分清:

origin 是远程仓库名
master / main 是分支名

它俩不是一个东西。

五、从远程仓库拉取:git pull

如果远程仓库有新提交,本地想同步下来,就用 git pull

常见写法:

git pull origin master

意思是:

从 origin 远程仓库拉取 master 分支的更新,并合并到当前分支

这里和 push 有一点容易混:

  • git push origin master:master 这种冒号写法用来表达“本地分支推到远程哪个分支”;
  • 新手阶段使用 pull 时,先记 git pull origin master 这种写法即可,不要硬套 master:master

git pull 其实做了两步

git pull 不是单纯下载,它大概等价于:

git fetch
git merge

也就是:

  1. 先把远程的新内容拉下来;
  2. 再尝试合并到当前本地分支。

所以多人协作时,如果你和别人改了同一块内容,git pull 也可能产生冲突。

六、本地分支和远程分支怎么建立连接?

我们有时候可以写:

git push origin master

有时候又可以直接写:

git push

这是因为本地分支和远程分支之间可以建立“跟踪关系”。

在这里插入图片描述

查看当前分支和远程分支的连接关系:

git branch -vv

如果看到类似:

* master  abc1234 [origin/master] 提交说明

这里的 [origin/master] 就表示:

本地 master 分支正在跟踪远程 origin/master 分支

建立连接之后,在当前分支上就可以直接:

git pull
git push

1. 查看远程分支

查看远程分支:

git branch -r

查看本地和远程所有分支:

git branch -a

2. 远程有 dev,本地也想创建 dev 并连接

假设远程仓库里已经有:

origin/dev

现在本地想创建一个 dev 分支,并且让它跟踪远程的 origin/dev,可以写:

git checkout -b dev origin/dev

新版本 Git 也可以写:

git switch -c dev --track origin/dev

这条命令做了两件事:

  1. 在本地创建 dev 分支;
  2. 让本地 dev 和远程 origin/dev 建立连接。

建立好之后,后面在 dev 分支上就可以直接:

git pull
git push

七、.gitignore:哪些文件不应该提交

项目里不是所有文件都应该提交到 Git。

比如:

  • 编译生成的文件;
  • 日志文件;
  • 临时文件;
  • 本地 IDE 配置;
  • .env 这种环境配置;
  • 密码、密钥、token。

这些文件可以写进 .gitignore

示例:

# 编译产物
dist/
build/

# 日志文件
*.log

# 环境配置
.env

# 临时文件
*.tmp

1. 强制提交被忽略的文件

如果一个文件被 .gitignore 忽略了,但这次你就是想提交它,可以使用:

git add -f 文件名

-f 可以理解为 force,强制添加。

2. 在 .gitignore 里取消忽略

如果想忽略一类文件,但保留其中某个文件,可以用 !

*.log
!important.log

意思是:

忽略所有 .log 文件
但是不要忽略 important.log,这样比强制提交更好,因为他更尊重.gitignore规则

3. 查看某个文件为什么被忽略

开发的文件多了,有时候会忘记某个文件到底被哪条规则忽略了。

可以用:

git check-ignore -v 文件名

它会告诉你具体是哪一个 .gitignore 规则导致这个文件被忽略。

八、配置命令别名:让长命令短一点

有些 Git 命令经常用,但名字比较长,比如 status

我们可以给它起一个别名:

git config --global alias.st status

前面的 st 是你想起的别名,后面的 status 是原命令。

配置之后:

git st

就等价于:

git status

当然原来的 git status 依然可以使用。

再比如,查提交历史时,可能会用到比较长的命令:

git log --pretty=oneline --abbrev-commit

也可以给它起别名,比如:

git config --global alias.lg "log --pretty=oneline --abbrev-commit"

之后直接:

git lg

就能看到简短的提交记录。

九、标签管理:给重要版本起名字

有时候某次提交很重要,比如:

  • 第一次正式发布;
  • v1.0 版本;
  • 某个稳定版本;
  • 某次上线版本。

这时就可以给这个提交打一个标签。

在这里插入图片描述

标签可以理解为:

给某个 commit ID 起一个更好记的名字。

比如 v1.0 肯定比一长串 commit ID 好记。

1. 给最新一次提交打标签

git tag v1.0

查看标签:

git tag

2. 从 .git 里看看 tag 本质是什么

上一篇文章里提过 .git 是 Git 的版本库,里面保存着 Git 的很多核心信息。

打完标签后,可以看一下:

tree .git

或者直接看标签文件:

cat .git/refs/tags/v1.0

里面通常会看到一个 commit ID。

这就说明,轻量标签的本质很简单:

标签名 -> 某个 commit ID

所以我们说标签是给某次重要提交起名字。

3. 给之前的版本打标签

如果想给之前某次提交打标签,先查看提交历史:

git log --pretty=oneline --abbrev-commit

找到你想打标签的 commit ID,然后:

git tag v1.0 commitID

4. 创建带说明的标签

如果怕以后忘记这个标签是什么意思,可以加说明:

git tag -a v1.0 -m "important tag" commitID

查看标签详情:

git show v1.0

这样就可以看到标签对应的提交,以及你写的说明。

5. 推送标签到远程

标签默认也是在本地创建的。如果想让远程仓库也有这个标签,需要推送。

推送某一个标签:

git push origin v1.0

一次性推送所有标签:

git push origin --tags

这里常用写法是 --tags

6. 删除标签

删除本地标签:

git tag -d v1.0

删除远程标签:

git push origin :refs/tags/v1.0

也经常能看到简写:

git push origin :v1.0

但新手阶段,我更建议先记完整写法,因为它能看出来删除的是远程的 tag。

十、多人协作一:远程先有分支,本地再连接

接下来进入最重要的内容:多人协作开发。

为了模拟多人协作,可以把自己的一台电脑当作一个开发者,再用另一台电脑、虚拟机、云服务器,或者另一个目录模拟另一个开发者。

多人协作时,最容易混乱的不是命令本身,而是这几个问题:

远程有没有这个分支?
本地有没有这个分支?
本地分支和远程分支有没有建立连接?
最后要合并到哪个分支?

先看第一种情况:

远程仓库里已经有一个分支,本地现在要把它拉下来开发。

在这里插入图片描述

假设远程仓库已经有 dev 分支。

先更新远程信息:

git fetch origin

查看所有分支:

git branch -a

如果看到:

remotes/origin/dev

说明远程确实有 dev 分支。

然后本地创建 dev 分支,并连接远程 origin/dev

git checkout -b dev origin/dev

之后正常开发:

git add .
git commit -m "完成 dev 分支功能"
git push

因为本地 dev 已经和远程 origin/dev 建立了连接,所以这里可以直接 git push

把 dev 合并到 master/main

开发完成后,最终要把 dev 合并到主分支。

有两种方式:

方式一:本地 merge 后 push
方式二:提交 Pull Request,在 GitHub 上合并

自己初学时,可以先练本地 merge,这样能更熟悉命令。

比如主分支叫 master

第一步,先把本地 master 更新到最新:

git checkout master
git pull

第二步,切回 dev,让 dev 先合并最新的 master

git checkout dev
git merge master

如果这里发生冲突,就在 dev 分支上解决冲突,防止污染主分支:

git add .
git commit -m "解决 dev 与 master 的冲突"
git push origin dev

这一步的目的很重要:先在开发分支上把冲突处理干净,不要一上来就把冲突现场带到主分支。

第三步,确认 dev 已经能正常合并后,再切回 master 合并 dev

git checkout master
git merge dev
git add .
git commit -m "解决 dev 合并冲突"
git push origin master

这样做的好处是:master 已经是最新的,dev 也提前处理过和 master 的差异,最后 master 合并 dev 时通常会更干净。

工作中更推荐 PR:

dev -> master/main

PR 的好处是:合并前可以看差异、做代码评审、跑自动测试,也能留下讨论记录。

十一、多人协作二:本地先有分支,再推送到远程

第二种情况是:

远程还没有这个功能分支,我先在本地创建,写完后再推送到远程。

比如现在要开发 feature-1

第一步,创建并切换分支:

git checkout -b feature-1

第二步,编辑文件。比如新建或修改 function1

第三步,提交:

git add .
git commit -m "完成 feature-1"

第四步,推送到远程:

git push origin feature-1

默认建立了追踪关系

这样以后在 feature-1 分支上就可以直接:

git push
git pull

这种方式为什么方便?

因为每个人开发自己的功能分支,不会一直挤在同一个分支上。

比如:

feature-1:你开发的功能
feature-2:同事开发的功能
master:稳定主分支

这样平时大家互不影响,只有最后合并到主分支时,才可能出现冲突。

同事生病了,你要接着他的分支开发怎么办?

这个场景很真实。

假设同事开发的是 feature-2,他已经把分支 push 到远程了,但你本地看不到。

先拉取远程信息:

git pull

再查看所有分支:

git branch -a

如果看到:

remotes/origin/feature-2

说明远程有这个分支。

接下来创建本地分支,并连接远程分支:

git checkout -b feature-2 origin/feature-2

之后你就可以继续开发:

git add .
git commit -m "继续完成 feature-2"
git push

因为创建时已经建立连接,所以可以直接 git push

为什么 git branch -a 能看到远程分支?

这里有个容易误解的点。

本地没有建立 feature-2 分支,不代表本地完全不知道远程有这个分支。

当你执行:

git pull

Git 会把远程仓库的分支信息更新到本地,所以你能通过:

git branch -a

看到:

remotes/origin/feature-2

但是,看到远程分支不等于你已经在本地创建了对应分支。

如果你要真正切过去开发,还需要:

git checkout -b feature-2 origin/feature-2

也就是说:

拉取远程分支信息,不一定需要建立跟踪关系;
但想在本地开发这个分支,就要创建本地分支并连接它。

feature-1 和 feature-2 最后怎么合并?

可以用 PR,也可以本地合并。

初学时为了练命令,可以本地 merge。工作中更建议 PR。

假设现在 feature-1 已经合进了 master,此时 master 变新了。再合并 feature-2 时,可能会冲突。

比较稳的做法是:先让 feature-2 合并最新的 master,在 feature-2 上解决冲突。

git checkout master
git pull

git checkout feature-2
git merge master

如果有冲突,就在 feature-2 上解决:

git add .
git commit -m "解决 feature-2 与 master 的冲突"
git push origin feature-2

如果你是通过 PR 合并,接下来就可以提 PR:

feature-2 -> master/main

如果你是本地练习 merge,那还要继续回到 master,把已经处理好冲突的 feature-2 合并进来:

git checkout master
git merge feature-2
git push origin master

也就是说,完整流程是:

master 先 pull 到最新
-> feature-2 合并 master
-> 在 feature-2 上解决冲突
-> 回到 master 合并 feature-2
-> push master

这样比直接在主分支上解决冲突更安全,因为主分支不会被半成品冲突现场污染。

十二、远程分支删除了,本地为什么还能看到?

多人协作中还有一个问题:

远程分支明明已经删了,但本地执行:

git branch -a

还是能看到:

remotes/origin/old-feature

这是因为本地保存的远程分支记录还没清理。

可以先查看远程仓库状态:

git remote show origin

如果看到 stale,意思就是陈旧的、远程已经不存在的引用。

Git 一般也会提示你可以用什么命令清理。

直接清理:

git remote prune origin

注意,这里清理的是:

本地保存的远程分支记录

不是删除远程仓库上真实存在的分支。

十三、企业级开发模型:Git Flow

前面讲的是个人和多人协作时最常见的 Git 操作。

但到了企业项目,事情会更复杂一点。一个软件从零开始到上线,不只是写代码,通常会经历:

规划 -> 编码 -> 构建 -> 测试 -> 发布 -> 部署 -> 维护

这也是为什么课程里会提到 DevOps。

DevOps 可以先粗略理解为:让开发人员和运维人员更好地协作,通过工具和流程自动化,让软件构建、测试、发布、部署更加稳定和高效。

对我们现在学 Git 来说,重点不是立刻把 DevOps 全学完,而是明白:

公司项目通常会按照不同环境和不同阶段设计分支。

1. 常见开发环境

企业项目里经常会听到这些环境:

环境 作用
开发环境 程序员日常开发、调试使用
测试环境 测试人员验证功能和 bug
预发布环境 尽量接近生产环境,上线前最后确认
生产环境 正式提供给用户使用的线上环境

简单理解:

开发环境:写代码
测试环境:测功能
预发布环境:上线前演练
生产环境:用户真正访问

不同环境需要相对稳定的代码来源,所以就会有分支设计。

2. Git Flow 常见分支

Git Flow 是一种企业里比较常见的分支模型。

在这里插入图片描述

常见分支如下:

分支 作用
master / main 主分支,通常对应生产环境,保存稳定版本
develop 开发分支,保存已经完成但还没正式发布的功能
feature/... 功能分支,从 develop 切出来开发某个需求
release/... 预发布分支,用来测试和准备上线
hotfix/... 紧急修复分支,通常从 master/main 切出来

3. master/main 分支

mastermain 一般是最稳定的分支。

它通常对应生产环境,也就是用户真正使用的版本。

一般不建议直接在这个分支上写代码,更常见的是通过合并得到:

release -> master/main
hotfix -> master/main

正式发布后,通常还会打标签:

git tag -a v1.0.0 -m "发布 v1.0.0"
git push origin v1.0.0

这样以后线上出了问题,就能快速知道当前生产环境对应的是哪个版本。

4. develop 分支

develop 是开发主分支。

新功能一般不会直接写在 master/main 上,而是先从 develop 切出功能分支。

功能完成后,再合并回 develop

develop -> feature/login -> develop

5. feature 分支

开发新功能时,从 develop 创建 feature 分支:

git checkout develop
git pull
git checkout -b feature/login

开发完成后:

git add .
git commit -m "完成登录功能"
git push -u origin feature/login

然后通过 PR 或 merge 合并回 develop

feature 分支的好处是:一个功能一个分支,没写完不会影响别人。

6. release 分支

develop 上的一批功能准备上线时,会从 develop 切出 release 分支:

git checkout develop
git pull
git checkout -b release/v1.0.0
git push -u origin release/v1.0.0

release 分支通常用来:

  • 交给测试人员测试;
  • 修复上线前发现的问题;
  • 部署到测试环境或预发布环境;
  • 确认这一版到底包含哪些功能。

测试通过后,再合并到 master/main

7. hotfix 分支

如果线上突然出现严重 bug,需要马上修复,一般会从 master/main 切 hotfix 分支:

git checkout main
git pull
git checkout -b hotfix/fix-login-error

修复完成后:

git add .
git commit -m "修复登录异常"
git push -u origin hotfix/fix-login-error

hotfix 修完后,通常要合并到两个地方:

hotfix -> master/main
hotfix -> develop

为什么还要合回 develop

因为如果只修了线上分支,而开发分支没修,下一次从 develop 发版时,老 bug 可能又被带回线上。

8. Git Flow 不是死规则

最后要注意:Git Flow 很常见,但不是所有团队都必须这样用。

有些小项目可能只需要 main + feature;有些团队发布很频繁,可能更喜欢主干开发;有些公司会有自己的分支命名规范。

所以学 Git Flow,不是为了死背模板,而是理解它想解决什么问题:

  • 新功能开发不要影响稳定版本;
  • 测试和上线要有明确代码来源;
  • 线上 bug 要能快速修;
  • 每次发布要能追溯;
  • 多人协作时分支关系不能乱。

十四、总结

这一篇从我的笔记顺序出发,把远程仓库和多人协作整理了一遍。

最核心的线是:

GitHub 创建仓库
-> clone 到本地
-> 本地 commit
-> push 到远程
-> pull 拉取远程更新
-> branch -vv 查看连接关系
-> 多人通过分支和 PR 协作

几个容易混的点再总结一下:

  • commit 是提交到本地;
  • push 才是推送到远程;
  • pull 会拉取并尝试合并;
  • origin 是远程仓库名,不是分支名;
  • master/main/dev/feature-1 才是分支名;
  • git branch -vv 可以看本地分支跟踪哪个远程分支;
  • 远程有分支,本地可以 git checkout -b dev origin/dev 建立连接;
  • 本地新建分支,第一次推送推荐 git push -u origin 分支名
  • .gitignore 是为了避免提交不该提交的文件;
  • tag 可以理解为给重要 commit 起名字;
  • git remote prune origin 可以清理本地陈旧的远程分支记录;
  • 企业里常见 Git Flow,但具体分支模型要看团队规范。

我现在对远程仓库的理解是:Git 命令看起来很多,但只要一直问自己三个问题,思路就不会乱:

我现在在哪个本地分支?
这个分支连接的是哪个远程分支?
我最终要把代码合并到哪里?

把这三个问题想明白,多人协作就不再只是背命令了。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐