git 一个分布式版本控制系统,由Linus Torvalds(芬兰计算机科学家,linux之父呀)在2005创建,最初是为了帮助管理 Linux 内核的开发而设计

使用git可以协作并行工作,版本控制,可以创建分支轻松合并更改,代码保存在多个地方,防止数据丢失。

Git的核心概念

  • 仓库 (Repository): 你的项目代码库,包含所有文件和历史记录。

    • 本地仓库: 在你电脑上的仓库。
    • 远程仓库: 在线托管的仓库,例如GitHub、GitLab、Gitee。
  • 工作区 (Working Directory): 你在电脑上看到和编辑的实际文件。

  • 暂存区 (Staging Area / Index): 一个临时区域,用于存放你准备提交(commit)的更改。你可以选择性地将文件添加到暂存区。

  • 提交 (Commit): 将暂存区的更改永久保存到本地仓库中,并生成一个唯一的**提交ID (Commit ID)**。每次提交都像是一个项目状态的“快照”。

  • 分支 (Branch): 从主线(通常是mainmaster分支)分出来的独立开发路径。你可以在不影响主线代码的情况下进行新功能开发或修复bug。

  • 合并 (Merge): 将一个分支的更改合并到另一个分支。

  • 拉取 (Pull): 从远程仓库下载最新的更改到本地仓库,并自动合并。

  • 推送 (Push): 将本地仓库的更改上传到远程仓库。

Git安装与配置

1. Git 安装

  • Windows:下载 Git for Windows 安装包 (https://git-scm.com/download/win),按照提示安装。
  • macOS
    • 通过 Homebrew 安装:brew install git
    • 安装 Xcode Command Line Tools (会包含 Git):xcode-select --install
  • Linux:使用包管理器安装,例如 Debian/Ubuntu: sudo apt-get install git;Fedora: sudo dnf install git
  • 验证安装:安装完成后,在终端或命令行输入 git --version 检查 Git 是否成功安装并显示版本号。

安装完后验证安装:

1
git --version
  • 初始配置Git

安装完成后,需要配置你的用户名和邮箱,在每次提交时显示,这对于团队协作和追溯历史非常重要。

1
2
git config --global user.name "你的用户名"
git config --global user.email "你的邮箱@example.com"
  • --global 参数表示这些配置是全局的,对电脑上所有的Git仓库都有效。如果想为某个特定仓库设置不同的用户名和邮箱,可以进入该仓库目录后不加 --global 参数进行配置。
  • 配置默认编辑器(可选)git config --global core.edit "vim" # 或"code --wait"(VS Code)
  • 检查配置

可使用以下命令检查配置:

1
2
3
4
5
git config --list

# 查看特定配置
git config user.name
git config user.email

2..gitconfig 文件的常见配置项详解

.gitconfig 文件是 Git 的配置文件,它存储了 Git 的各种设置,包括你的用户信息、默认编辑器、别名等。Git 会在三个不同的级别查找这些配置:

  1. **系统级配置 (System-level)**:
    • 文件路径:通常位于 /etc/gitconfig (Linux/macOS) 或 Git 安装目录下的 etc/gitconfig (Windows)。
    • 作用范围:对系统上所有用户的所有 Git 仓库都有效。
    • 命令:git config --system
  2. **全局配置 (Global-level)**:
    • 文件路径:通常位于用户主目录下的 ~/.gitconfig (Linux/macOS) 或 C:\Users\<YourUsername>\.gitconfig (Windows)。
    • 作用范围:对当前用户的所有 Git 仓库都有效。这是最常用的配置级别。
    • 命令:git config --global
  3. **仓库级配置 (Repository-level)**:
    • 文件路径:位于每个 Git 仓库的 .git/config 目录下。
    • 作用范围:仅对当前 Git 仓库有效。
    • 命令:git config --local (默认,可以省略 --local)

优先级:仓库级配置 > 全局配置 > 系统级配置。这意味着如果同一个配置项在多个级别中都存在,Git 会优先使用最具体的(即仓库级)配置。

当你使用 git config 命令进行配置时,实际上就是修改这些 .gitconfig 文件。下面是一些常见的配置项及其作用:

1. [user] 用户信息

这是最基本也是最重要的配置,用于标识你的提交。

  • name: 你的用户名,会显示在 Git 提交记录中。

    1
    2
    [user]
    name = 你的名字

    对应命令:git config --global user.name "你的名字"

  • email: 你的邮箱地址,也会显示在 Git 提交记录中。

    1
    2
    [user]
    email = 你的邮箱@example.com

    对应命令:git config --global user.email "你的邮箱@example.com"

2. [core] 核心配置

包含了一些 Git 核心行为的设置。

  • editor: Git 在需要你输入提交信息或合并信息时使用的默认文本编辑器。

    1
    2
    [core]
    editor = vim

    对应命令:git config --global core.editor "vim" (也可以是 code --wait for VS Code, nano, subl -w, etc.)

  • autocrlf: 处理行结束符(CRLF vs. LF)。在跨平台协作时非常有用。

    • true (Windows 推荐):提交时将 CRLF 转换为 LF,检出时将 LF 转换为 CRLF。
    • input (macOS/Linux 推荐):提交时将 CRLF 转换为 LF,检出时不转换。
    • false:不做任何转换。
    1
    2
    [core]
    autocrlf = input

    对应命令:git config --global core.autocrlf input

  • excludesfile: 指定一个全局的 .gitignore 文件路径,用于忽略所有仓库中不需要跟踪的文件。

    1
    2
    [core]
    excludesfile = ~/.gitignore_global

    对应命令:git config --global core.excludesfile ~/.gitignore_global

3. [alias] 别名

你可以为常用的 Git 命令设置别名,以简化输入。

  • 示例

    1
    2
    3
    4
    5
    6
    7
    [alias]
    co = checkout
    br = branch
    ci = commit -m
    st = status
    unstage = restore --staged
    lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit

    对应命令:

    • git config --global alias.co checkout
    • git config --global alias.st status
    • git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

4. [color] 颜色设置

配置 Git 输出的颜色,提高可读性。

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [color]
    ui = auto
    [color "branch"]
    current = yellow reverse
    local = yellow
    remote = green
    [color "diff"]
    meta = yellow bold
    frag = magenta bold
    old = red bold
    new = green bold

    对应命令:git config --global color.ui auto

5. [init] 初始化配置

  • defaultBranch: 设置新仓库初始化时默认分支的名称(Git 2.28+ 推荐使用 main 而非 master)。

    1
    2
    [init]
    defaultBranch = main

    对应命令:git config --global init.defaultBranch main

6. [push] 推送配置

  • default: 设置 git push 命令的默认行为。

    • simple (Git 2.0+ 推荐):只推送当前分支到其对应的上游分支。
    • matching (旧版默认):推送所有名称相同的本地分支到远程。
    1
    2
    [push]
    default = simple

    对应命令:git config --global push.default simple

3. 查看 .gitconfig 文件内容

你可以直接用文本编辑器打开这些文件来查看和编辑,但更推荐使用 git config 命令:

  • 查看所有配置

    1
    git config --list
  • 查看某个级别的配置

    1
    2
    3
    git config --list --system
    git config --list --global
    git config --list --local
  • 查看特定配置项的值

    1
    2
    git config user.name
    git config alias.st

了解和配置 .gitconfig 文件能让你更好地定制 Git 的行为,提高工作效率。

4. 文件状态与暂存区

  • Git 文件状态

    • **Untracked (未跟踪)**:新创建的文件,Git 还没有开始跟踪它们。
    • **Unmodified (未修改)**:文件自上次提交以来没有发生变化。
    • **Modified (已修改)**:文件自上次提交以来已发生变化,但尚未添加到暂存区。
    • **Staged (已暂存)**:文件已修改并被添加到暂存区,准备好进行下一次提交。

5. Git 的数据结构

Git 并不是存储文件的差异,而是将每个文件作为一个快照(Snapshot)来存储。它使用一种叫 SHA-1 的算法来计算每个快照的唯一标识符。一个提交(Commit)包含了:

  • 一个唯一的 SHA-1 值。
  • 一个或多个指向父提交的指针。
  • 一个指向当前项目所有文件快照的指针。
  • 作者、提交者、提交信息、时间等元数据。

这种快照模式使得 Git 的操作速度非常快,尤其是在创建分支和合并时。

Git核心工作流程:工作区、暂存区、本地仓库

核心工作流程图示:

1
2
3
4
5
6
7
8
9
10
11
+----------------+       +-------------------+       +-------------------+
| 工作区 | ----> | 暂存区 | ----> | 本地仓库 |
| (Working Dir) | | (Staging Area) | | (Local Repo) |
| | | | | |
| 你编辑文件 | | 准备下次提交的 | | 已提交的历史 |
| 创建/修改/删除 | | 文件快照清单 | | 所有版本记录 |
+----------------+ +-------------------+ +-------------------+
^ | ^
| | |
| 修改文件 | git add | git commit
+------------------------+-------------------------+

实际操作流程:

  1. 你在工作区修改文件。
  2. 使用 git add <文件名>git add . 将你希望跟踪的更改从工作区添加到暂存区
  3. 使用 git commit -m "提交信息"暂存区的所有内容提交到本地仓库,形成一个新的版本。

Git基本操作

现在我们开始实践Git的基本操作。

1. 初始化一个新的Git仓库

如果有一个全新的项目,要被Git管理,需要初始化一个Git仓库。

1
2
3
4
# 进入项目根目录
cd /path/to/
# 初始化 Git 仓库
git init

这会在你的项目目录下创建一个名为 .git 的隐藏文件夹,这就是你的Git仓库,它包含了Git所需的所有信息。

2. 添加文件到暂存区

对文件进行更改后,需要告诉Git希望跟踪这些更改。

1
2
3
git add <文件名> # 添加单个文件
git add . # 添加当前目录下的所有更改(包括新建、修改、删除)
git add -u # 仅添加已跟踪的修改文件到暂存区 (不包括新文件)
1
2
3
# 取消暂存文件
git restore --staged <文件名> # Git 2.23+ 推荐
git reset HEAD <文件名> # 旧版本 Git

3. 提交更改

将暂存区的更改提交到本地仓库。每次提交都应该有一个清晰的**提交信息 (Commit Message)**,描述你这次提交做了什么。

1
git commit -m "你的提交信息"

一个好的提交信息能够帮助你和团队成员快速理解每次更改的目的。例如:

1
2
git commit -m "feat: 添加部署指南"
git commit -am "你的提交信息" # 仅适用于已跟踪的文件
  • 小技巧:

    提交信息建议使用动词开头,清晰明了。常见的提交信息前缀:

    • feat: 新功能 (feature)
    • fix: 修复bug (bug fix)
    • docs: 文档变更 (documentation)
    • style: 代码格式化 (whitespace, formatting)
    • refactor: 代码重构 (refactoring)
    • test: 增加测试 (tests)
    • chore: 构建过程或辅助工具的变动 (build process, auxiliary tools)

4. 查看仓库状态

git status 命令非常常用,它会显示你工作区和暂存区的状态,以及哪些文件被修改、哪些文件是未跟踪的。

1
git status
  • 红色文件名: 未跟踪的文件(untracked files)或已修改但未添加到暂存区的文件。
  • 绿色文件名: 已添加到暂存区的文件。

5. 查看提交历史

git log 命令可以查看提交历史,包括提交ID、作者、日期和提交信息。

1
git log

你也可以使用一些参数让输出更简洁:

1
2
3
4
5
6
7
git log --oneline # 每条提交显示一行
git log --graph --oneline --decorate # 以图形方式显示分支和提交
git log --graph --oneline --all # 以图形方式显示所有分支的提交历史
git log -p # 显示每次提交的详细内容差异 (patch)。
git log --author="你的名字" # 只显示特定作者的提交。
git log --since="2 weeks ago" # 显示最近两周的提交。
git log <文件名> # 只显示某个文件的修改历史。

通过状态与历史:git statusgit log可随时了解仓库的健康状况和发展历程。

当有多条分支和合并操作时 --graph会非常直观…

6. 撤销操作

Git 的四大撤销操作:

Git 的撤销操作主要作用于我们之前讲过的三个区域:工作区、暂存区、本地仓库

1. git restore:撤销工作区的修改

git restore 命令用于撤销工作区中尚未添加到暂存区的修改

  • 作用区域:工作区。
  • 工作方式:它会将工作区的文件恢复到最近一次提交暂存区时的状态。

你正在修改一个文件 file.txt,但突然发现改错了,想回到修改前的状态。

1
2
3
4
5
# 修改了文件
echo "I made a mistake" >> file.txt

# 撤销工作区的修改
git restore file.txt

执行后,file.txt 的内容会恢复到你上次 git commitgit add 时的状态。

2. git reset:撤销提交,并移动 HEAD 指针

git reset 是一个功能强大且危险的命令,它用于撤销提交,并根据不同的参数移动 HEAD 指针和文件。

  • 作用区域:本地仓库、暂存区、工作区。
  • 工作方式:它将 HEAD 指针(指向当前提交的指针)移动到指定的提交。

git reset 有三个主要的参数:

  • **git reset --soft <commit-hash>**:
    • 作用:只移动 HEAD 指针到指定的提交,保留你所有的修改在工作区和暂存区
    • 适用场景:你提交了几个 commit,但突然发现它们应该合并成一个,或者你提交信息写错了。你可以用 soft 模式回到上一个 commit,然后重新 commit
  • **git reset --mixed <commit-hash>**:
    • 作用:这是默认模式。它移动 HEAD 指针,并将修改放回到工作区清空暂存区
    • 适用场景:你已经提交了,但想撤销这次提交,并且把文件恢复到未暂存的状态。
  • **git reset --hard <commit-hash>**:
    • 作用:这是最危险的模式。它移动 HEAD 指针,并彻底删除所有工作区和暂存区中该提交之后的所有修改。
    • 适用场景:你提交了一个错误的修改,而且你确定不需要这些修改,想彻底回到一个干净的历史版本。
1
2
3
4
5
6
7
8
9
# 假设你提交了两次:C1 -> C2 -> C3 (HEAD)
# 1. 撤销到 C1,但保留所有修改
git reset --soft C1

# 2. 撤销到 C1,修改回到工作区
git reset --mixed C1

# 3. 彻底回到 C1,并删除 C2、C3 的所有修改
git reset --hard C1

3. git revert:反向提交

git revert 用于撤销一次提交,但它并不会删除提交历史,而是创建一个新的提交,来抵消之前提交的修改。

  • 作用区域:本地仓库。
  • 工作方式:它通过创建一个新的提交来“还原”指定的提交。

git reset 的区别

  • revert 不会改写历史,它会增加新的历史记录。这使得它在团队协作和共享分支上非常安全。
  • reset --hard 会改写历史,并删除提交。这在公共分支上非常危险,因为它会影响其他人的本地仓库。

你提交了一次 Bug 修复,但这个修复导致了更大的问题。你想撤销它。

1
2
3
4
5
# 找到导致问题的提交
git log --oneline
# 假设是这个提交:abcde123
# 创建一个新的提交,来撤销 abcde123 的修改
git revert abcde123

执行后,你的提交历史会增加一个新的提交,内容是撤销了 abcde123 的修改。

4. git clean:清除未追踪的文件

git clean 用于清理工作区中未被 Git 追踪的文件

  • 作用区域:工作区。
  • 工作方式:它会删除那些你从未执行过 git add 的文件。
  • **git clean -n**:
    • 作用预演,显示将被删除的文件,但不会实际删除。这是最安全的用法。
  • **git clean -f**:
    • 作用:强制删除未追踪的文件。
  • **git clean -fd**:
    • 作用:强制删除未追踪的文件和目录

你编译了一些临时文件,如 .o 文件,或者生成了一些日志文件,它们都没有被 git add。你想删除它们。

1
2
3
4
5
6
7
# 检查将要被删除的文件(安全模式)
git clean -n

# 确认无误后,强制删除
git clean -f
# 如果要删除目录,使用 -fd
git clean -fd

总结与对比:

命令作用区域撤销对象行为方式场景
git restore工作区未暂存的修改将文件恢复到暂存区或上次提交的状态。撤销工作区的改动。
git reset本地仓库提交和工作区/暂存区修改移动 HEAD 指针,可选择性地删除修改。撤销本地提交,但不推荐用于公共分支。
git revert本地仓库提交创建一个新的提交来抵消之前的修改。安全地撤销公共分支上的提交。
git clean工作区未追踪的文件彻底删除未被 Git 管理的文件。清理临时文件或编译产物。

在实际工作中,最常使用的可能是 git restoregit revert。而 git reset --hard 则像一把双刃剑,使用时务必小心。


功能对比:

git checkout

  • 切换分支:git checkout main
  • 查看提交(进入 detached HEAD):git checkout <commit>
  • 恢复文件:git checkout -- file.txt(在较新 Git 里推荐 git restore 替代)

git reset

  • 撤销暂存:git reset file.txt(相当于 git restore --staged file.txt
  • 移动 HEAD 到某个提交:git reset <commit>
  • 根据模式不同(--soft / --mixed / --hard),可以控制是否同时修改 暂存区工作区

7. 忽略文件:.gitignore

项目中,有些文件或目录不想被git跟踪,比如:临时文件,敏感信息,依赖包等,如果不忽略导致仓库臃肿且不必要。

在项目根目录下创建一个名为 .gitignore 的文件(注意文件名以点开头),然后将.gitignore文件本身添加到git并提交。

1
2
git add .gitignore
git commit -m "Add .gitignore file"

7.1 .gitignore 语法

每行定义一个忽略规则。

  • 空行 或以 # 开头的行都会被忽略。
  • *****:匹配零个或多个字符。
  • **?**:匹配单个字符。
  • **[]**:匹配括号内的任何一个字符(例如 [abc] 匹配 ‘a’, ‘b’, 或 ‘c’)。
  • ****:匹配任意目录层级。
  • / 结尾: 表示忽略目录,不包含其下的子文件。
  • ! 开头: 表示不忽略某个文件或目录(即使它被前面的规则忽略了)。

注意: 如果已经不小心把某个应该忽略的文件提交到了仓库,然后才添加到 .gitignore,Git不会自动从历史中删除它。需要手动从Git的跟踪中移除它:

1
2
git rm --cached <文件名>
git commit -m "Remove <文件名> from tracking"

--cached 参数表示只从Git的暂存区和仓库中移除,但保留你工作区的实际文件。

与远程仓库协作

远程仓库通常托管在 GitHub、GitLab 或 Bitbucket 等平台上,它为你提供了一个集中存放代码的地方,供团队成员共享和同步。

当你 git clonegit checkout 一个远程分支时,Git 会自动为你设置追踪关系。

  • git branch --set-upstream-to=<remote>/<branch>: 手动设置追踪关系。
1
2
3
4
5
# 在一个新分支上进行开发
git checkout -b dev-branch

# 将本地 dev-branch 分支与远程 origin/dev-branch 关联起来
git push --set-upstream origin dev-branch

https关联远程仓库

首先,你需要将你的本地仓库和远程仓库建立连接。

  • **git remote add <name> <url>**:添加一个新的远程仓库。name 通常是 originurl 是远程仓库的地址(可以是 HTTPS 或 SSH)。
  • **git remote -v**:查看当前本地仓库关联的所有远程仓库。
1
2
3
4
5
6
7
8
# 在 GitHub 上创建一个新的仓库,然后复制它的 HTTPS 地址
# 假设地址是 https://github.com/user/my-project.git

# 在本地项目目录下,关联远程仓库
git remote add origin https://github.com/user/my-project.git

# 查看关联情况
git remote -v

执行后,你会看到类似 origin https://github.com/user/my-project.git (fetch)(push) 的输出,表示成功关联。

如果用 SSH 方式连接远程 Git 仓库,而不是 HTTPS,这在私有仓库和频繁推送中很常用,因为免密码(通过 SSH Key)且安全。

ssh方式连接远程仓库

生成 SSH Key

如果你本机还没有 SSH Key,需要先生成:

1
2
# 默认在 ~/.ssh/id_ed25519
ssh-keygen -t ed25519 -C "your_email@example.com"
  • -t ed25519:推荐,安全性高,性能好
  • -C "your_email@example.com":给 key 加注释,一般用邮箱
  • 会提示输入文件路径和密码,可以直接回车使用默认路径,密码可以留空或设置

生成后,会得到两个文件:

1
2
~/.ssh/id_ed25519       # 私钥,不要泄露
~/.ssh/id_ed25519.pub # 公钥,用于上传到 Git 服务器

添加公钥到 Git 服务器

  1. 登录你的 Git 平台(GitHub / GitLab / Gitee / Bitbucket 等)
  2. 找到 SSH Keys 配置页面
  3. id_ed25519.pub 内容复制进去,保存
1
cat ~/.ssh/id_ed25519.pub

测试 SSH 连接

1
ssh -T git@github.com
  • 第一次会提示 Are you sure you want to continue connecting (yes/no)?,输入 yes
  • 成功后会看到类似:
1
Hi username! You've successfully authenticated, but GitHub does not provide shell access.

用 SSH 克隆仓库

假设仓库地址:

  • HTTPS: https://github.com/username/repo.git
  • SSH: git@github.com:username/repo.git
1
git clone git@github.com:username/repo.git

已有仓库改为 SSH

如果你已经用 HTTPS 克隆,可以切换为 SSH:

1
git remote set-url origin git@github.com:username/repo.git

检查是否修改成功:

1
git remote -v

输出示例:

1
2
origin  git@github.com:username/repo.git (fetch)
origin git@github.com:username/repo.git (push)

常用技巧

  1. 免输密码

    • 可以用 ssh-agent 添加私钥:

      1
      2
      eval "$(ssh-agent -s)"
      ssh-add ~/.ssh/id_ed25519
  2. 多账号管理

    • 可以在 ~/.ssh/config 配置:

      1
      2
      3
      4
      Host github-personal
      HostName github.com
      User git
      IdentityFile ~/.ssh/id_ed25519

    SSH 方式主要优点:免密码、安全、适合脚本/自动化部署 ,核心步骤:生成 SSH Key → 上传公钥 → 测试 → 克隆或切换远程地址

推送提交到远程仓库

当你完成了本地的开发并进行了提交后,你需要将这些提交推送到远程仓库,让团队成员能够看到你的工作。

  • **git push -u <remote> <branch>**:将本地分支的提交推送到远程仓库。
    • -u 选项(即 --set-upstream)是初次推送时常用的,它会建立本地分支和远程分支的关联,之后你就可以直接使用 git push 了。
1
2
# 将本地的 master 分支推送到远程仓库 origin
git push -u origin master

如果你正在使用 main 分支,则命令是 git push -u origin main

从远程仓库拉取更新

在团队协作中,其他成员可能已经推送了新的代码。你需要将这些最新的代码拉取到你的本地仓库,以确保你的代码是最新的。

  • git fetch <remote>:从远程仓库下载最新的提交,但不合并到你的本地分支。这些提交会保存在一个隐藏的远程分支中(如 origin/master)。
  • git pull <remote> <branch>:从远程仓库拉取更新,并自动合并到你当前所在的本地分支。git pull 实际上是 git fetchgit merge 的组合。
1
2
3
4
5
6
7
8
# 从远程仓库 origin 下载最新提交
git fetch origin

# 将远程 master 分支的更新合并到本地 master
git merge origin/master

# 简洁方式:直接拉取并合并
git pull origin master

克隆远程仓库

如果你是新加入一个团队,或者想在另一台电脑上获取项目的完整代码,你需要克隆远程仓库。

  • **git clone <url>**:克隆一个完整的远程仓库到本地。这个命令会自动完成 git initgit remote addgit pull 的所有步骤。
1
2
# 克隆一个远程仓库到当前目录
git clone https://github.com/user/my-project.git

克隆完成后,就拥有了一个完整的本地仓库,并且与远程仓库建立了关联。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
➜  ~ mkdir beizi_blog
➜ ~ cd beizi_blog/
➜ ~/beizi_blog git init
已初始化空的 Git 仓库于 /Users/bz/beizi_blog/.git/
# 这会在当前目录下创建一个隐藏的 .git 文件夹,用来存储 Git 的版本控制相关信息。

➜ ~/beizi_blog git remote -v
beizi_blog git@github.com:beiziya/beizi_blog.git (fetch)
beizi_blog git@github.com:beiziya/beizi_blog.git (push)
origin git@github.com:beiziya/beizi_blog.git (fetch)
origin git@github.com:beiziya/beizi_blog.git (push)
# 之前已经添加过远程仓库,又添加了一个

➜ ~/beizi_blog touch test.txt
➜ ~/beizi_blog echo "this is a test" >test.txt
➜ ~/beizi_blog cat test.txt
this is a test
➜ ~/beizi_blog git add . # 添加当前目录所有文件到暂存区

➜ ~/beizi_blog git commit -m "first file"
[main(根提交) c4b8e0c] first file
1 file changed, 1 insertion(+)
create mode 100644 test.txt
➜ ~/beizi_blog git push -u beizi_blog main # 推送到远程仓库
枚举对象中: 3, 完成.
对象计数中: 100% (3/3), 完成.
写入对象中: 100% (3/3), 218 字节 | 218.00 KiB/s, 完成.
总共 3(差异 0),复用 0(差异 0),包复用 0(来自 0 个包)
To github.com:beiziya/beizi_blog.git
* [new branch] main -> main
分支 'main' 设置为跟踪 'beizi_blog/main'

-u 参数只需在第一次推送分支时使用。之后,可以直接使用 git push

如果已经设置过关联,也可以手动设置本地分支与远程分支的关联,例如:

1
git branch --set-upstream-to=origin/main main

前提:远程有beizi_blog仓库,并设置好ssh key或 gpg key 我已经设置好了。以下设置步骤

设置ssh key

1
2
3
4
5
6
7
8
9
➜  ~/beizi_blog ls ~/.ssh
beiziya_id_rsa config id_rsa.pub known_hosts.old
beiziya_id_rsa.pub id_rsa known_hosts
# 可以看到有我的公私钥,可通过ssh-keygen生成
# -t dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa
# Specifies the type of key to create. The possible values are
# “dsa”, “ecdsa”, “ecdsa-sk”, “ed25519”, “ed25519-sk”, or “rsa”.
#可以指定不同加密算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
➜  ~/beizi_blog ssh-keygen -t rsa -b 4096 -C "test keys" #测试生成密钥对到当前目录
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/bz/.ssh/id_rsa): beizi_blog_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in beizi_blog_rsa
Your public key has been saved in beizi_blog_rsa.pub
The key fingerprint is:
SHA256:yWreqJG5jxl6c+cRcfiP9zMyP1s0haTP6htro26YyP4 test keys
The key's randomart image is:
+---[RSA 4096]----+
| . |
| . o . |
| o . . . .|
| . = o .|
| S . o..|
| o . . o . ..|
| =.o..o. = .|
| .oO+o+..o=++. |
| ..=*=+Eooo+*+= |
+----[SHA256]-----+
➜ ~/beizi_blog ls
beizi_blog_rsa beizi_blog_rsa.pub test.txt

# 注:千万不能把密钥放到项目目录中,这只是测试


# 将ssh key添加到ssh代理
eval "$(ssh-agent -s)"
ssh-add ./beizi_blog_rsa

# 将.pub中的内容复制到剪贴板
pbcopy < ./beizi_blog_rsa.pub # macos

#粘贴到git帐户ssh keys中

如果想让不同的密钥对访问不同主机,需要建立ssh config 文件

1
2
3
4
5
6
7
8
9
10
11
12
# GitHub
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/github_rsa

# GitLab
Host gitlab.com
HostName gitlab.com
User your_username
IdentityFile ~/.ssh/gitlab_rsa

分支管理——Git的灵魂

分支 (Branch) 就像是 Git 提供的一条独立的开发路径。它允许你在不影响主线开发(通常是 mainmaster 分支)的情况下,进行新功能的开发、bug 修复或实验性尝试。

在 Git 中,分支的本质只是一个指向某次提交 (commit) 的指针。 默认情况下,Git 会创建一个名为 main (或 master) 的主分支。当你进行提交时,这个分支指针就会向前移动。

  • 当你创建一个新的分支时,Git 只是在当前提交上新增了一个指向它的指针。这个过程几乎瞬间完成,这也是 Git 分支轻量化的原因。
  • 默认情况下,当你初始化一个 Git 仓库时,会有一个名为 master (或 main) 的主分支。

分支的常用命令

查看分支

  • git branch:查看本地分支,当前分支前带 *
  • git branch -a:显示所有分支(本地 + 远程)
  • git branch -r:仅显示远程分支
  • git branch -vv:显示分支详细信息(跟踪远程分支、最新提交)

创建分支

  • git branch new-feature:从当前 HEAD 创建分支 new-feature
  • git branch new-feature commit-hash:从指定 commit 创建分支

删除分支

  • git branch -d branch-name:删除本地分支(已合并时可用)
  • git branch -D branch-name:强制删除本地分支(未合并也可删)

重命名分支

  • git branch -m old-name new-name:将本地分支 old-name 改名为 new-name
  • git branch -m new-name:将当前分支改名

设置/查看远程跟踪

  • git branch -u origin/main:设置当前分支跟踪远程 origin/main
  • git branch -vv:查看分支与远程分支对应关系

检查分支合并情况

  • git branch --merged:列出已合并到当前分支的分支
  • git branch --no-merged:列出未合并到当前分支的分支

清理远程分支引用

  • git fetch -p:删除已经在远程不存在的分支引用

切换分支(补充)

  • 传统:git checkout branch-name
  • 推荐:git switch branch-namegit switch -c new-branch(创建并切换)

git switch 是 Git 2.23 之后推荐的新命令,更加直观安全,专门用来切换分支。
git checkout 功能更多(切换分支 + 恢复文件),所以有时候容易误操作。

  • 切换到现有分支
    • git checkout <branch-name>
    • git switch <branch-name>
  • 创建并切换分支
    • git checkout -b <branch-name>
    • git switch -c <branch-name>
  • 从旧版本创建分支
    • git checkout -b <branch-name> <commit-hash>
    • git switch -c <branch-name> <commit-hash>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 创建一个名为 "feature-a" 的新分支,并切换过去
git checkout -b feature-a

# 2. 在新分支上进行开发和提交
echo "add new feature" > feature-a.txt
git add .
git commit -m "feat: add feature A"

# 3. 切换回主分支
git checkout main

# 4. 删除 "feature-a" 分支(需要先切换回主分支)
# git branch -d feature-a
# 如果有未合并的提交,会报错。
1
2
3
4
5
6
7
8
9
10
11
git_test$ git branch 
--list
* main
git_test$ git show-ref --heads
ab20832781f1ad7f29bcad109322bd6d997b5cbf refs/heads/--list
ab20832781f1ad7f29bcad109322bd6d997b5cbf refs/heads/main
# 可能误操作创建了一个--list分支,git branch -d --list无法删除它,因为有一个--list参数,在前面加--
# 如果不添加-- git会误认为操作错误
/git_test$ git branch -d -- --list
Deleted branch --list (was ab20832).

合并分支

  • 基本合并
    • git merge <branch>:将指定分支合并到当前分支
  • 创建合并提交
    • git merge --no-ff <branch>:即使可以快进,也强制创建一个合并提交
  • 不提交,只合并到暂存区
    • git merge --no-commit <branch>:合并修改到暂存区,但不生成合并提交
  • 不修改工作区
    • git merge --no-ff --no-commit <branch>:将修改放到暂存区,手动检查后再提交
  • 快速前进合并
    • git merge --ff <branch>:允许快进合并(默认行为)
  • 指定合并策略
    • git merge -s recursive <branch>:默认策略,处理普通分支合并
    • git merge -s ours <branch>:保留当前分支的内容,忽略合并分支内容
    • git merge -s theirs <branch>:保留被合并分支内容(需配合 -X 使用)
  • 解决冲突策略
    • git merge -X theirs <branch>:在冲突时优先使用被合并分支的内容
    • git merge -X ours <branch>:在冲突时优先使用当前分支的内容
  • 其他常用
    • git merge --abort:放弃合并,回到合并前状态
    • git merge --edit:允许编辑合并提交信息
    • git merge --quiet:合并时只显示错误,不输出普通信息

💡 :

  • 平时日常开发最常用的命令就是:
    • git merge <branch>(普通合并)
    • git merge --no-ff <branch>(保持合并记录)
    • git merge --abort(遇到冲突想放弃合并)

当你在一个分支上完成了功能开发后,你需要将它合并回主分支,让你的修改成为项目的一部分。

  • **git merge <branch-name>**:将指定分支的提交合并到当前分支。
1
2
3
4
5
6
7
8
# 1. 确保你在主分支(main)上
git switch main # 或 git checkout main

# 2. 将 "feature-a" 分支的提交合并到 main 分支
git merge feature-a

# 3. 查看提交历史,你会发现 main 分支现在包含了 feature-a 的所有提交
git log --oneline --graph

Git 的合并有两种方式:

  • 快进式合并(Fast-Forward):如果主分支在你创建分支后没有任何新的提交,Git 会直接将主分支指针移动到新分支的最新提交上,合并过程非常快。
  • 三方合并(Three-Way Merge):如果主分支在你创建分支后有了新的提交,Git 会找到两个分支的共同祖先,然后将两个分支的修改进行合并。

解决冲突

在合并分支时,如果两个分支修改了同一个文件的同一部分,Git 无法自动合并,就会产生冲突(Conflict)

处理冲突的步骤

  1. Git 会在冲突文件中插入特殊标记,告诉你哪些部分存在冲突。

    1
    2
    3
    4
    5
    <<<<<<< HEAD
    这是主分支的代码
    =======
    这是你的分支的代码
    >>>>>>> feature-a
  2. 你需要手动编辑文件,选择你想要保留的代码,并删除所有 Git 标记。

  3. 编辑完成后,使用 git add <file> 将文件标记为已解决冲突。

  4. 最后,使用 git commit 完成合并。

撤消合并

  • 取消合并(回到合并前状态)
1
git merge --abort
  • 只接受 main 分支的版本
1
2
3
git checkout --ours a.txt
git add a.txt
git commit
  • 只接受 被合并的分支的版本
1
2
3
git checkout --theirs a.txt
git add a.txt
git commit

注意:和git merge -s ours/theirs的区别:全局合并策略

文件冲突解决git checkout --ours / --theirs

协作工作流:Forking 工作流

开源项目和许多大型团队中,最常见的协作模式是 Forking 工作流。它的核心思想是:不直接向主仓库推送代码,而是先复刻(Fork)主仓库到你自己的账户下,然后通过拉取请求(Pull Request)合并请求(Merge Request)来贡献代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git clone https://github.com/your-username/project.git

git checkout -b new-feature
...
git commit -m "feat: add new feature"

git push origin new-feature

# 添加主仓库为 upstream
git remote add upstream https://github.com/original-owner/project.git
# 从 upstream 拉取最新代码并合并
git pull upstream main
# 将更新推送到你自己的远程仓库
git push origin main

Git 的核心思维

Git 远不止是一堆命令。它代表着一种高效、安全的工作模式

  1. 提交是原子性的:每次提交只做一件事。
  2. 分支是廉价的:大胆创建分支,并行开发,隔离风险。
  3. 先拉后推:在推送代码前,先拉取最新代码,以减少冲突。
  4. Fork 和 Pull Request:这是现代开源协作的最佳实践。

Git 的高级知识点

git stash 临时存放改动

  • 作用:把当前工作区和暂存区的改动“存放”到一个栈(stash stack)里,并把工作区恢复干净(回到 HEAD)。
  • 典型场景
    • 你正在写代码,突然需要切换分支去修复一个 bug,但又不想提交未完成的代码。
    • stash 把改动临时存起来,等修完 bug 再回来 stash pop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
git stash            # 保存当前改动
git stash save "msg" # 带说明保存 或 git stash push -m "说明"
git stash push -u # 连未追踪文件(untracked files)也一起保存
git stash push -a # 包含忽略文件(ignored files)
git stash push -m "只存某些文件" file1 file2 # 指定文件 stash

git stash list # 查看保存的改动
git stash show stash@{0} # 查看某条 stash 的简略差异
git stash show -p stash@{0} # 查看详细 diff

git stash pop # 应用最近一次 stash,并删除
git stash apply # 应用但不删除

git stash drop # 删除某条 stash
git stash clear # 清空所有 stash

# 从 stash 创建新分支
git stash branch hotfix stash@{0} # Git 会基于 stash 所在的 commit 创建一个新分支 hotfix,并自动应用 stash 的改动,非常适合临时需求被打断的情况。
# 交互式选择(类似 git add -p)
git stash -p

git stash -p

👉 逐个 hunk(代码块)决定要不要放进 stash。

1
2
3
4
5
6
7
8
9
10
11
12
git_test$ git stash -p
diff --git a/test.txt b/test.txt
index 55d52a7..e6959c3 100644
--- a/test.txt
+++ b/test.txt
@@ -3,3 +3,5 @@ bbbbbb
dddd
eee
fff
+222222
+111111
(1/1) Stash this hunk [y,n,q,a,d,e,?]?

表示当前是 1 个 hunk(第 1/1 个),你需要选择如何处理。

🔹 参数含义:

-p 模式下,有这些操作选项:

  • y → yes,把这个 hunk 存到 stash
  • n → no,不 stash 这个 hunk,保留在工作区
  • q → quit,退出,不 stash 剩下的 hunks
  • a → all,stash 所有 hunks(后续都自动 yes)
  • d → do not stash this and remaining hunks(后续都自动 no)
  • e → edit,手动编辑 hunk 内容,只 stash 你想要的部分
  • ? → 显示帮助说明

多个 stash 的管理

1
2
3
git_test$ git stash list
stash@{0}: WIP on main: a98b738 add: ee
stash@{1}: WIP on main: 02f466d add: d
  • stash@{0}

    • 栈顶(最新存的 stash)
    • 内容:基于 main 分支a98b738 提交时,你执行了 git stash
    • add: eee 是当时的提交信息(说明 stash 是从哪个 commit 派生的)。
  • stash@{1}

    • 更早存的 stash
    • 内容:基于 main 分支02f466d 提交时保存的改动。
  • 查看某条 stash 的改动:

    1
    2
    git stash show stash@{0}       # 简略差异
    git stash show -p stash@{0} # 详细 diff
  • 应用(恢复)某条 stash:

    1
    2
    git stash apply stash@{0}   # 应用但不删除
    git stash pop stash@{0} # 应用并删除
  • 删除某条 stash:

    1
    git stash drop stash@{1}
  • 清空所有 stash:

    1
    git stash clear

🔹 使用小技巧

  1. 多个 stash 的管理
    你可以 stash 很多次,每次都会编号,stash@{0} 永远是最新的。

  2. 带名字的 stash
    默认 git stash 会生成 “WIP on : “,但你可以自己写名字:

    1
    git stash push -m "修复用户登录表单未提交完成"

    这样 git stash list 更好理解。


分支分叉(Divergence)

1. 什么是分叉(分支不一致)?

本地分支和远程分支各自有独立提交,形成了如下结构:

1
2
3
A---B---C (origin/main)
\
D---E (main)

即:

  • 远程分支 origin/main 有提交 C;
  • 本地分支 main 有提交 D 和 E;
  • 两者的共同祖先是 B。

2. 如何检测分叉状态?

1
git log --oneline --graph --all --decorate

或使用定向对比:

1
2
git log origin/main..HEAD --oneline     # 查看本地领先部分
git log HEAD..origin/main --oneline # 查看远程领先部分

二、变基(Rebase)

1. 什么是 rebase?

git rebase origin/main 会将你本地的提交 “摘下” 来,依次应用到远程分支的最新提交之后,保持提交历史线性。

2. 何时使用 git pull --rebase

当你 本地有提交,远程也有新提交,希望保持干净历史(避免 merge commit)时。

设置默认行为(推荐):

1
git config --global pull.rebase true

三、变基过程中的冲突处理

1. 检测当前是否正在变基

1
git status

输出类似:

1
交互式变基操作正在进行中...

说明你正在 rebase 流程中,遇到冲突被暂停。

2. 如何解决冲突?

手动处理冲突文件中标记的内容:

1
2
3
4
5
<<<<<<< HEAD
远程内容
=======
本地提交内容
>>>>>>> 提交号

如需保留本地版本:

1
git checkout --ours <file>

或保留远程版本:

1
git checkout --theirs <file>

然后:

1
2
git add <file>
git rebase --continue

四、冲突解决完成后的操作

1. 继续变基流程

1
git rebase --continue

如果你不想继续这个补丁:

1
git rebase --skip

中止整个 rebase:

1
git rebase --abort

五、变基后的推送

变基改变了提交历史,因此 必须使用强推

1
git push --force origin main

⚠️ 注意:强推有风险,确保你理解自己的操作,并协作团队知情。

六、常用可视化与对比命令

目的命令
查看所有分支图谱git log --oneline --graph --all --decorate
查看本地领先git log origin/main..HEAD --oneline
查看远程领先git log HEAD..origin/main --oneline
查看变基基础点git merge-base HEAD origin/main

七、实际工作中建议的工作流(最佳实践)

  1. 每次开发前:

    1
    2
    git fetch origin
    git rebase origin/main
  2. 每次提交前,确保你是最新状态,不要与远程冲突。

  3. 推送前检查差异,避免不必要的冲突和强推。

延伸学习点

知识点说明
git cherry查看哪些提交在某分支但不在另一个
git reflog查看 HEAD 的变更历史(特别适合误操作回退)
git rerere自动记住冲突解决历史,减少重复劳动
git rebase -i交互式变基:可以 squash、edit、drop 提交
git stash暂存未完成的工作,避免干扰变基