Skip to content

Git 速查手册

Git 基本流程

Git管理的文件有 5 种状态:

  • 未跟踪(untracked):新添加的文件,或被移除跟踪的文件,未建立跟踪,通过 git add 添加暂存并建立跟踪。
  • 未修改:从仓库签出的文件默认状态,修改后就是“已修改”状态了。
  • 已修改(modified):文件被修改后的状态。
  • 已暂存(staged):修改、新增的文件添加到暂存区后的状态。
  • 已提交(committed):从暂存区提交到版本库。

分支操作

创建并切换分支

bash
git checkout -b <new-branch-name>

切换分支

bash
git checkout <branch-name>

重命名分支

先切换到要重命名的分支,然后执行以下命令:

bash
git checkout <old-branch-name>
git branch -m <new-branch-name>

或者不需要切换分支,直接使用以下命令:

bash
git branch -m <old-branch-name> <new-branch-name>

删除分支

bash
# 在删除某个分支之前,确保你不在该分支上
git branch -d <branch-name>

Git Rebase

git rebase 是 Git 中一个用于合并变更的命令,可以将一个分支的提交“重新应用”到另一个分支上,同时保持提交历史的清晰。通常在下面的场景中使用:

  • 我们从 main 分支拉取了一条 feature 分支在本地进行功能开发。
  • 远程的 main 分支在之后又合并了一些新的提交。
  • 我们想在 feature 分支集成 main 的最新更改。(比如与 main 分支有冲突,需要在 feature 分支解决冲突)

基本操作

  1. 切换到要 rebase 的分支
bash
git checkout <branch>
  1. 执行 rebase:将 feature-branch 的提交应用到 main 分支上。
bash
git rebase main
  1. 处理冲突:如果 rebase 过程中遇到冲突,Git 会提示手动解决。解决冲突后执行:
bash
git add <file>
git rebase --continue
  1. 完成 rebase 后推送:由于 rebase 会重写提交历史,推送时需要使用 --force
bash
git push --force

在推送到自己的 feature 分支之后,可以提交 pull request 将修改合并到 main 分支。

交互式 Rebase

git rebase -i(interactive rebase)是 Git 提供的一个交互式 rebase 功能,允许你在 rebase 过程中精细控制提交历史。它特别适合整理和优化提交历史,修改多个提交的顺序、内容,甚至合并、删除提交。

  1. 执行交互式 rebase: 在分支上使用 git rebase -i,并指定基准提交(基准是当前分支的父分支或任意提交),例如:
bash
git rebase -i HEAD~3

这会打开一个文本编辑器,显示过去 3 次提交的列表:

text
a1b2c3d Fix a bug
b2c3d4e Add new feature
c3d4e5f Improve documentation

git rebase -i 不仅可以使用 HEAD~N 指定一个范围,还可以直接指定一个具体的提交哈希(commit hash)作为基准,这样可以更灵活地选择需要操作的提交。

bash
git rebase -i <commit-hash>

在这条命令中,<commit-hash> 是你想要作为基准的提交。所有位于该提交之后的提交都会进入交互式 rebase 编辑模式。值得注意的是,指定的 <commit-hash> 本身不会被修改,它只会作为 rebase 的起点。

假设你的提交历史如下:

text
a1b2c3d Fix a bug
b2c3d4e Add new feature
c3d4e5f Improve documentation
d4e5f6g Initial commit

你想对最近的 3 个提交进行交互式 rebase,但基准设为 d4e5f6g(即最早的 Initial commit)。可以执行以下命令:

bash
git rebase -i d4e5f6g

这将会打开编辑器,显示从 c3d4e5fa1b2c3d 的提交列表供你编辑:

text
a1b2c3d Fix a bug
b2c3d4e Add new feature
c3d4e5f Improve documentation
  1. 编辑提交操作: 每个提交前的 pick 表示 Git 将按顺序应用这些提交。可以将 pick 替换为以下命令:
  • pick:保持提交不变。
  • reword:修改提交信息。
  • edit:修改提交的内容或信息。
  • squash:将当前提交与前一个提交合并。
  • fixup:类似 squash,但不保留当前提交的信息。
  • drop:删除提交。
  1. 保存并退出: 编辑完提交列表后,保存文件并退出编辑器,Git 会根据你指定的命令逐步执行操作。

假设你有以下提交历史:

text
pick a1b2c3d Fix a bug
pick b2c3d4e Add new feature
pick c3d4e5f Improve documentation

你可以将这些操作修改为:

text
pick a1b2c3d Fix a bug
squash b2c3d4e Add new feature
fixup c3d4e5f Improve documentation

这会将后两个提交合并到第一个提交中,保持一个干净的历史记录。

处理冲突

如果在 rebase 过程中遇到冲突,Git 会暂停,并要求你解决冲突。解决冲突后,运行:

bash
git add <conflict-file>
git rebase --continue

如果你想中止 rebase,可以运行以下命令:

bash
git rebase --abort

参考资料

Git Merge

git merge 和 git rebase 命令类似,也是用于将一个分支的变更合并到当前分支。与 git rebase 保持历史记录清晰线性不同,git merge 会保留分支的历史记录并生成一个新的合并提交(merge commit)。

要将 main 分支合并到 feature 分支,以下是详细步骤和例子:

  1. 切换到 feature 分支
bash
git checkout feature
  1. 执行 git merge 命令将 main 合并到 feature
bash
git merge main

如果两者的代码没有冲突,Git 会直接进行快速合并。 如果合并过程中有冲突,Git 会标记冲突的文件,并要求你手动解决冲突:

使用编辑器打开冲突文件,解决冲突后,将其标记为已解决:

bash
git add <conflict-file>

继续合并过程:

bash
git commit

Git Rebase 和 Git Merge 的区别

git merge:

  • 方式:将两个分支的变更合并,并创建一个新的合并提交(merge commit)。它保留了每个分支的原始提交历史。
  • 结果:合并历史会显示出分叉点,并在合并时产生额外的 merge commit。

git rebase:

  • 方式:将一个分支的提交重新应用到目标分支的最新状态。它会将你当前分支的提交“移到”目标分支之上,使提交历史变得线性。
  • 结果:不产生新的 merge commit,提交历史更简洁、连续。

Cherry-pick 操作

git cherry-pick 是一个 Git 命令,用于从一个分支中选择特定的提交,并将该提交应用到当前所在的分支。它的作用是将某个提交(或者多个提交)从一个分支复制到另一个分支,而无需合并整个分支。这样可以方便地在不同分支之间应用特定的修改,而不需要进行完整的合并。

主要用途:

    1. 修复或功能迁移:当你只想从某个分支(如 master)提取特定的修改,应用到当前分支(如 hotfix),而不需要整个分支的改动时。
    1. 避免全量合并:有时你不希望将整个分支合并到当前分支,只想引入一个或多个具体的提交,cherry-pick 就能帮你做到这一点。
    1. 快速修复:如果你在 master 分支上做了一些修复,想要将这些修复快速应用到 hotfix 分支中,可以使用 cherry-pick

假设你在 master 分支上有一个提交(commit A),而你想把这个提交应用到 hotfix 分支上:

bash
git checkout hotfix
git cherry-pick <commit-hash-of-A>

这样,commit A 就会被应用到 hotfix 分支,而不会影响 master 分支上的其他提交。

撤销修改

撤销工作区中的修改

如果你在工作目录中修改了文件,但尚未将其添加到暂存区,可以使用以下命令恢复到最近的提交状态:

bash
# 建议使用 -- 参数用于明确区分文件名和其他选项
git checkout -- <file-name>
# 撤销工作区中所有文件的修改
git checkout .

修改上一次提交(未推送到远程)

如果你只是想修改上一次提交的内容或提交信息,可以使用 --amend 选项。这不会真正撤销提交,而是允许你修改它。

bash
git commit --amend

这会打开编辑器,你可以修改提交信息,也可以通过 git add 添加新的更改到该提交中。

撤销最后一次提交,但保留文件的更改和暂存

如果你已经提交了更改,但希望撤销提交并保留文件的更改在工作目录中(即不丢失工作内容),你可以使用 git reset --soft

bash
git reset --soft HEAD^
  • --soft:将提交撤销,但保留所有修改在暂存区中,你可以重新提交。
  • HEAD^:表示上一个提交(最后一次提交)。

这样提交会被撤销,但文件仍然保持已暂存状态,你可以根据需要修改或者重新提交。

撤销最后一次提交并保留修改但不暂存

如果你希望撤销提交,并且将所有文件的更改从暂存区移到工作目录中(即取消暂存),可以使用 git reset --mixed--mixed 是默认选项,可以省略。

bash
git reset --mixed HEAD^
# 或者
git restore --staged

撤销最后一次提交并取消所有更改

如果你不仅想撤销提交,还想丢弃所有已提交的更改(即回到提交之前的状态),你可以使用 git reset --hard。注意,这种方式会丢失更改

bash
git reset --hard HEAD^

撤销已经推送到远程仓库的提交

如果你已经将提交推送到远程仓库,那么你撤销的操作会更加复杂,特别是在团队协作中,你需要谨慎操作。以下是几种方法:

方法 1:使用 git revert

git revert 用来生成一个新的提交,它会反转某个提交的内容,不修改提交历史。适用于已经推送到远程的情况。

bash
git revert <commit-hash>
  • 这会生成一个新的提交,撤销指定的提交。

方法 2:使用 git reset + 强制推送

你也可以使用 git reset --hard 撤销本地提交,然后强制推送到远程仓库。注意:这会重写远程历史记录,可能会影响其他团队成员的工作,慎用

bash
git reset --hard HEAD^
git push origin <branch-name> --force

参考资料

保持本地 Fork 仓库与上游仓库同步

设置上游仓库(只需要首次设置)

首先在 Github 上 fork 上游仓库。

然后 clone fork 的仓库到本地。

bash
# 本例中是 git clone https://github.com/cr7258/higress.git
git clone https://github.com/<your-github-user>/<repo>.git
cd <repo>

添加上游仓库作为远程仓库。

bash
# 本例中是 git remote add upstream https://github.com/alibaba/higress.git
git remote add upstream https://github.com/<upstream>/<repo>.git

验证上游仓库是否添加成功。

bash
git remote -v

# 示例输出
origin	https://github.com/cr7258/higress.git (fetch)
origin	https://github.com/cr7258/higress.git (push)
upstream	https://github.com/alibaba/higress.git (fetch)
upstream	https://github.com/alibaba/higress.git (push)

同步上游仓库的更新

获取上游仓库的更新。

bash
git fetch upstream

将上游的更新 rebase 到本地分支。

bash
git checkout main  # 切换到你要更新的分支,通常是 main 或 master
git rebase upstream/main  # 将上游的 main 分支 rebase 到本地的 main 分支

解决可能的冲突,如果有冲突,Git 会提示你处理冲突,处理完冲突后继续 rebase。

bash
git rebase --continue

推送更新到你的 fork 仓库,如果使用了 rebase,需要强制推送。

bash
git push -f origin main

完全同步上游(丢弃本地改动)

bash
git fetch origin
git reset --hard origin/<branch-name>

使用 Git Worktree 管理多分支

git worktree 允许你在同一仓库中同时检出多个分支,从而方便并行开发和减少频繁的分支切换,提高开发效率。

创建一个新的 Worktree

bash
# 方式一:基于当前分支,新建一个 worktree 目录,新的分支名就是新建目录的名称
git worktree add <path>
# 示例:假设当前在 feature-1 分支,那么执行以下命令会在项目目录平级(假设在项目根目录执行命令)的 feature-2 目录下基于 feature-1 分支创建一个 feature-2 分支
git worktree add ../feature-2

# 方式二:基于当前分支,新建一个 worktree 目录,新的分支名是指定的名称
git worktree add <path> -b <branch>

# 方式三:基于指定分支,新建一个 worktree 目录,新的分支名是指定的名称
git worktree add <path> -b <branch> <commit|branch>
# 示例:假设当前在 feature-1 分支,那么执行以下命令会在项目目录平级(假设在项目根目录执行命令)的 feature-2 目录下基于 main 分支创建一个 feature-2 分支
git worktree add ../feature-2 -b feature-2 main
# 或者
git worktree add ../feature-2 main

列出 Worktree

bash
git worktree list

# 输出示例
/Users/I576375/tmp/git-demo/higress        6f5c0f4 [new-feature-1]
/Users/I576375/tmp/git-demo/hotfix-1       210b97b [hotfix-1]

删除 Worktree

bash
git worktree remove <worktree>

# 示例
git worktree remove hotfix-1

参考资料

Git Clone

使用 Git SSH 协议 Clone 仓库

当 Git 仓库太大时,或者网络环境不好时,使用 SSH 协议 clone 仓库可能会更稳定。请确保你已设置 SSH 密钥对并将公钥添加到你的 GitHub 帐户。

如果默认的 Github 的 22 端口不可用,可以使用 443 端口。配置 ~/.ssh/config 文件:

yaml
Host github.com
  Hostname ssh.github.com
  Port 443

然后就可以使用 SSH 协议 clone 仓库了:

bash
git clone git@github.com:<username>/<repo>.git
# 例如
git clone git@github.com:cr7258/pytorch-deep-learning.git

Git 配置

设置缓冲区

遇到 fatal: the remote end hung up unexpectedly 错误时,可以尝试增加缓冲区大小。

bash
git config http.postBuffer 524288000

Git LFS

Git LFS(Large File Storage)是一个 Git 扩展,专门用于管理大文件或二进制文件。它允许你将大文件托管在远程服务器,而不占用 Git 仓库的空间。使用 Git LFS 可以有效避免 Git 仓库膨胀问题。

以下是 Git LFS 的使用步骤:

1. 安装 Git LFS

在使用 Git LFS 之前,你需要先安装它。

macOS:

bash
brew install git-lfs

Ubuntu/Debian:

bash
sudo apt-get install git-lfs

Windows: 通过 Git for Windows 安装程序安装 Git LFS,或从官网 Git LFS Releases 下载并安装。

2. 初始化 Git LFS

安装完成后,在你的项目中运行以下命令来初始化 Git LFS:

bash
git lfs install

这会将 Git LFS 设置为你的 Git 仓库的一部分。

3. 跟踪大文件

Git LFS 可以通过文件扩展名或者特定的文件路径来跟踪大文件。比如要跟踪所有的 .psd 文件,可以使用以下命令:

bash
git lfs track "*.psd"

这会在仓库中创建一个 .gitattributes 文件,记录了哪些文件是用 Git LFS 跟踪的。

4. 提交大文件

当你添加和提交文件时,Git LFS 会自动处理大文件。可以像平常一样使用 Git 命令提交大文件:

bash
git add path/to/largefile.psd
git commit -m "Add large PSD file"
git push origin main

Git LFS 会将大文件推送到远程 Git LFS 服务器,而 Git 仓库只存储指向这些文件的指针文件。

5. 获取大文件

如果克隆了一个使用 Git LFS 的仓库,你需要运行以下命令来下载 LFS 文件:

bash
git lfs pull

可以通过 git config lfs.fetchinclude 或者 git config lfs.fetchexclude 设置在 git lfs pull 时处理的文件,例如:

bash
# 配置 LFS 下载对象时仅包含 images 文件夹
git config lfs.fetchinclude 'images/**'

# 也可以选择仅排除指定的文件夹
git config lfs.fetchexclude 'videos/**'

如果你想在克隆时自动获取大文件,可以在克隆时加上 --recursive 选项:

bash
git clone --recursive <repository-url>

6. 检查 LFS 状态

要检查哪些文件被 LFS 跟踪,可以使用以下命令:

bash
git lfs ls-files

7. 移除文件跟踪

如果想要停止 LFS 跟踪某个文件类型或文件路径,你可以手动编辑 .gitattributes 文件,移除相关条目,然后执行以下命令:

bash
git lfs untrack "*.psd"

参考资料

问题

Git 仓库太大或者网络问题 Clone 失败

有时候由于网络问题或者 Git 仓库太大,可能导致 clone 失败。

bash
git clone https://github.com/cr7258/pytorch-deep-learning.git
Cloning into 'pytorch-deep-learning'...
remote: Enumerating objects: 4252, done.
remote: Counting objects: 100% (274/274), done.
remote: Compressing objects: 100% (107/107), done.
error: RPC failed; curl 18 Transferred a partial file46 MiB/s
error: 8172 bytes of body are still expected
fetch-pack: unexpected disconnect while reading sideband packet
fatal: early EOF
fatal: fetch-pack: invalid index-pack output

可以尝试以下方法:

  • 增加缓冲区大小:可以尝试增加 Git 的缓冲区大小以允许更大的传输。
bash
git config --global http.postBuffer 524288000  # Set a larger buffer size
git config --global core.compression 0         # Disable compression
bash
git clone --depth=1 https://github.com/cr7258/pytorch-deep-learning.git

参考资料