在的代码管理版本控制工具几乎都是 Git 了,虽然有很多好看好用的 GUI 客户端(推荐 Fork),但常用的命令还是得要了然于心,这里只是列出常用的 Git 命令行指令,以供速查参考。

配置

本地全局配置文件的路径(macOS)为:~/.gitconfig,本地仓库的配置路径为:[仓库路径]/.git/config

查看配置

1
2
3
4
5
# 显示全局 / 仓库配置,在仓库目录下执行该命令则显示当前仓库的配置,在非仓库目录下则显示为全局配置。
$ git config -l` # -l, --list

# 查看单项配置 e.g. '$ git config --get user.name'
$ git config --get <key>

使用文本编辑器来编辑配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 设置默认的文本编辑器,默认使用 `vim`
$ git config --global core.editor <editor>

# 使用 Visual Studio Code
$ git config --global core.editor "code --wait"
# 使用 CotEditor
$ git config --global core.editor "cot -w"
# 使用 Sublime Text
$ git config --global core.editor "subl -w"
# 使用 Xcode
$ git config --global core.editor 'xed -w'
# 使用 Zed
$ git config --global core.editor 'zed -w'

# 在文本编辑器中编辑配置
$ git config --global -e # -e, --edit

用户名和邮箱

添加 --global 参数,则为全局设置;不加则为当前仓库设置,需要在当前仓库目录下执行该命令。(下同)

1
2
3
4
# 全局设置用户名
$ git config --global user.name <name>
# 全局设置邮箱
$ git config --global user.email <email>

默认提交信息 / 模板

$ git config --global commit.template <template-file-path>

在提交时,编辑器中就会显示模板文件中的信息,这里推荐 Git Commit 日志风格指南 中的模板。

别名

1
2
3
4
5
6
# e.g.
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.cm commit
$ git config --global alias.st status
## 配置好后,再输入 git 命令的时候就可以用别名来简化输入了,例如我们要查看状态,只需:'$ git st'

代理

由于众所周知的原因,国内访问 GitHub 都巨慢,配置代理可以加快访问速度。

配置 Git 代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 查看代理
$ git config --global http.proxy
$ git config --global https.proxy
$ git config --global socks.proxy

# 设置代理
# 适用于 privoxy 将 socks 协议转为 http 协议的 http 端口
$ git config --global http.proxy http://127.0.0.1:8888
$ git config --global https.proxy http://127.0.0.1:8888
$ git config --global socks.proxy 127.0.0.1:8889

# 取消代理
$ git config --global --unset http.proxy
$ git config --global --unset https.proxy
$ git config --global --unset socks.proxy

# 只对 github.com 设置代理
$ git config --global http.https://github.com.proxy socks5://127.0.0.1:1080
$ git config --global https.https://github.com.proxy socks5://127.0.0.1:1080

# 取消 github.com 代理
$ git config --global --unset http.https://github.com.proxy
$ git config --global --unset https.https://github.com.proxy

配置 SSH 代理

1
2
3
# 修改 ~/.ssh/config 文件
Host github.com
ProxyCommand nc -X 5 -x 127.0.0.1:8889 %h %p

删除配置

1
$ git config --global --unset <key>

仓库

创建与克隆

1
2
3
4
5
6
7
8
9
10
11
# 创建一个新的本地仓库
$ git init <repo-name>

# 克隆仓库
$ git clone <git-url>
# 浅克隆
$ git clone --depth 1 <git-url>
# 将仓库克隆到指定目录
$ git clone <git-url> <path>
# 将仓库克隆到指定目录,并指定分支
$ git clone <git-url> -b <branch-name> <path> # -b, --branch

忽略文件

文件 .gitignore 指定了 Git 应该忽略的未跟踪的文件,这里推荐 github/gitignore 模板。

符号 规则
行首 # 全行注释,不支持行尾类注释 (转义 \#)
行首 ! 否定模式 (转义 \!)
** 匹配任意路径
* 匹配任意多个字符
? 匹配任意一个字符
doc/** 匹配 doc 文件夹下的全部内容
doc/**/a 匹配任意深度路径下的 a 文件或文件夹
/ 表示路径分隔符,不区分操作系统
/ 结尾 仅会匹配文件夹,否则会匹配文件和文件夹
空行 不匹配任何文件
行尾空格 默认被忽略,可使用 \ 进行转义
行首空格 被正常处理,不会被忽略

当前 .gitignore 文件定义规则的优先级高于上级路径 .gitignore 定义规则的优先级;后定义的规则优先级高于前面定义规则的优先级。

远程仓库

1
2
3
4
5
6
7
8
9
10
11
12
# 向本地仓库添加一个新的远程仓库
$ git remote add <name> <url> # <name> 远程仓库的名称,通常是 origin
# 显示您设置的远程存储库的名称
$ git remote
# 显示远程存储库的名称和 URL
$ git remote -v # -v, --verbose
# 删除远程存储库
$ git remote rm <name> # remove, rm
# 更改 git repo 的 URL
$ git remote set-url origin <git-url>
# 查看远程分支和本地分支的对应关系
$ git remote show origin

同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 获取远程仓库所有分支
$ git fetch <name>

# 将远程分支合并到当前分支以使其保持最新状态
$ git merge <name>/<branch>
# 没有快进
$ git merge --no-ff [alias]/[branch]
# 仅快进
$ git merge --ff-only [alias]/[branch]

# 将本地分支提交传输到远程存储库分支
$ git push <name> <branch>
# 从跟踪远程分支获取并合并任何提交
$ git pull
# 将另一个分支的一个特定提交合并到当前分支
$ git cherry-pick <hash-id>

# 抛弃本地所有的修改,回到远程仓库的状态
$ git fetch --all && git reset --hard origin/main

观察你的仓库

1
2
3
4
5
6
7
8
9
10
# 显示当前活动分支的提交历史
$ git log
# 显示 branchA 上不在 branchB 上的提交
$ git log branchB..branchA
# 显示更改文件的提交,即使跨重命名
$ git log --follow [file]
# 显示 branchA 中的内容与 branchB 中的内容的差异
$ git diff branchB...branchA
# 以人类可读的格式显示 Git 中的任何对象
$ git show <SHA>

分支

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
# 列出所有本地分支
$ git branch
# 列出所有远程分支
$ git branch -r # -r, --remotes
# 列出所有分支,本地和远程
$ git branch -av # -a, --all; -v, -vv, --verbose

# 快速切换到上一个分支
$ git checkout -
# 切换到 my-branch,并更新工作目录
$ git checkout <my-branch>
# 创建并切换到新分支 new_branch
$ git checkout -b <new-branch>
# 从远程分支中创建并切换到本地分支
$ git checkout -b <branch-name> origin/<branch-name>

# 将分支 branch_a 合并到分支 branch_b
$ git checkout <branch-b>
$ git merge <branch-a>

# 删除名为 my-branch 的分支
$ git branch -d <my-branch>

# 标记当前提交
$ git tag <tagname>

提交

做出改变

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
36
# 在工作目录中显示修改后的文件,为您的下一次提交暂存
$ git status
# 暂存文件,准备提交

$ git add <file>
# 暂存所有更改的文件,准备提交
$ git add .
# 将所有暂存文件提交到版本化历史记录

$ git commit -m "commit message" # -m <msg>, --message=<msg>
# 将所有跟踪的文件提交到版本化历史记录
# 注意,-a 选项不会暂存未跟踪的新文件(即那些没有被 Git 跟踪过的文件)
$ git commit -am "commit message" # -a, --all

# 取消暂存文件,保留文件更改
$ git reset <file>
# 将所有内容恢复到最后一次提交
$ git reset --hard
# 还原所有修改,不会删除新增的文件
$ git checkout .
# 删除新增的文件
$ git clean -xdf # -f, --force

# 已更改但未暂存内容的差异
$ git diff
# 已 commited 但尚未提交的内容的差异
$ git diff --staged

# 在指定分支之前应用当前分支的任何提交
$ git rebase <branch>

# 把 A 分支的某一个 commit,放到 B 分支上
# 切换到 B 分支
$ git checkout <B>
# 将 A 分支 <hash-id> 的内容 pick 到 B 分支
$ git cherry-pick <hash-id>

临时提交

1
2
3
4
5
6
7
8
9
10
11
12
# 保存已修改和分阶段的更改
$ git stash
# 列出隐藏文件更改的堆栈顺序
$ git stash list
# 从存储堆栈顶部编写工作
$ git stash pop
# 丢弃存储堆栈顶部的更改
$ git stash drop
# 回到某个 stash 的状态
$ git stash apply <stash@{n}>
# 删除所有的 stash
$ git stash clear

提交到远程仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
# 推送当前分支到默认远程仓库
$ git push
# 推送指定分支到远程仓库
$ git push origin <feature-branch>
# 推送标签到远程
$ git push origin <tagname>
# 推送所有标签
$ git push --tags

# 撤销一条远程记录
$ git reset --hard HEAD~1
# 强制推送到远程仓库
$ git push -f origin HEAD:main

其他常用操作

rebase、merge、reset、revert

git rebase

git rebase 将一个分支上的更改应用到另一个分支的基础之上。它可以保持提交历史的整洁和线性,而不像 git merge 那样生成一个额外的合并提交记录。

示例:

  1. 更新 feature 分支到主分支的最新状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 1. 切换到 feature 分支:
    $ git checkout feature-branch

    # 2. 更新主分支:
    $ git checkout main
    $ git pull origin main

    # 3. 返回到 feature 分支并执行 rebase:
    $ git checkout feature-branch
    $ git rebase main
  2. 处理冲突

    rebase 过程中,如果存在冲突,Git 会暂停并提示你解决冲突。解决冲突后,使用以下命令继续 rebase

    1
    2
    $ git add <conflicted-files>
    $ git rebase --continue

    如果要中止 rebase 操作,可以使用:

    1
    $ git rebase --abort
  3. 推送变基后的更改

    1
    $ git push --force origin feature-branch

git merge

git merge 用于将两个或多个分支的开发历史合并在一起的命令。与 git rebase 不同的是,merge 保留了整个分支的历史记录,并在目标分支中创建一个新的合并提交。而 rebase 是将一个分支的改动在另一个分支的基础上重新应用。

示例:

  1. feature-branch 分支合并到 main 分支

    1
    2
    $ git checkout main
    $ git merge feature-branch
  2. 处理合并冲突

  3. 继续合并

    1
    2
    $ git add <conflicted-file>
    $ git commit
  • 快进合并 (Fast-forward merge)

    如果正在合并的分支是直接从目标分支创建的,而且目标分支在此期间没有任何新的提交,Git 会自动执行快进合并,不会创建新的合并提交。

  • 禁用快进合并

    1
    git merge --no-ff feature-branch
  • 其他选项

    • --squash:将所有合并的提交压缩成一个提交。不会生成合并提交记录。

      1
      $ git merge --squash feature-branch

      使用 --squash 合并后,你需要手动提交:

      1
      $ git commit
    • --no-commit:合并内容但不自动提交。你可以检查更改后手动提交。

      1
      $ git merge --no-commit feature-branch
    • --abort:如果在合并中遇到问题或冲突而不想继续,可以使用该命令终止合并并恢复到合并前的状态。

      1
      $ git merge --abort

git reset

git reset 用于撤销更改、重置头指针位置以及修改暂存区和工作目录。它可以根据不同的模式(--soft--mixed--hard)来改变暂存区和工作目录的状态。

  1. --soft:重置 HEAD 到指定提交,暂存区和工作目录不变。常用于修改最后一次提交。

    1
    $ git reset --soft HEAD~1

    例如,上述命令将当前分支的 HEAD 移动到前一个提交,保留所有更改在暂存区中。你可以进行新的提交或调整提交信息。

    1
    $ git commit --amend
  2. --mixed(默认):重置 HEAD 到指定提交,同步更新暂存区,但工作目录不变。常用于撤销提交但保留更改。

    1
    $ git reset --mixed HEAD~1
  3. --hard:重置 HEAD 到指定提交,同时同步更新暂存区和工作目录。会丢失所有未提交的更改。

    1
    2
    3
    $ git reset --hard HEAD~1
    # 也可以指定具体的 commit hash
    $ git reset --hard e7a1e7a1

git revert

git revert 用于撤销一个或多个已提交的变更,但与 git reset 不同,它不会改变项目的提交历史。相反,它会创建一个新的提交,这个提交包含了撤销指定提交的更改。这样的操作是“向前”撤销,仍然保留所有的提交记录。

  • 单个提交的撤销

    1
    $ git revert <commit-hash>
  • 多个提交的撤销

    1
    2
    # 撤销最近的三个提交:
    $ git revert HEAD~3..HEAD

放弃更改

  • 丢弃工作目录中的更改

    • 使用 git checkout

      1
      2
      3
      4
      # 恢复单个文件
      $ git checkout -- <path/to/file>
      # 恢复全部文件
      $ git checkout -- .
    • 使用 git restore

      1
      2
      3
      4
      # 恢复单个文件
      $ git restore <file>
      # 恢复全部文件
      $ git restore .
  • 丢弃暂存区中的更改

    1
    2
    3
    4
    # 恢复单个文件
    $ git reset path/to/file
    # 恢复全部文件
    $ git reset
  • 丢弃未提交的文件

    1
    2
    3
    4
    5
    6
    # 查看将要被删除的文件
    $ git clean -n
    # 真正执行删除操作
    $ git clean -f
    # 如果目录中有未跟踪的子目录并想同时删除,可以使用 -d 选项
    $ git clean -fd

重命名分支

1
2
3
4
5
6
7
8
# 1. 重命名为 new
$ git branch -m <newbranch> # -m, --move, move/rename a branch
$ git branch -m <oldbranch> <newbranch> #重命名分支
# 2. 推送并重置
$ git push origin -u <newbranch> # -u, --set-upstream
# 3. 删除远程分支
$ git push origin -d <oldbranch> # 方法 1; -d, --delete
$ git push origin :<oldbranch> # 方法 2

修改远程 Commit 记录

  1. 使用 git rebase 修改本地 Commit 记录

    1
    $ git rebase -i <base-commit> # -i, --interactive

    <base-commit> 是想要开始修改历史的位置。例如,如果想修改最后的 3 个提交,可以这样:

    1
    $ git rebase -i HEAD~3

    这会打开一个文本编辑器,显示最近 3 次提交的列表。你可以在列表中选择编辑、压缩、删除等操作。

    • pick:按原样保留此提交
    • reword:保留此提交但编辑提交信息
    • edit:保留此提交但会暂停变基,使你可以修改此提交
    • squash:将此提交与前一个提交合并,并编辑提交信息
    • fixup:将此提交与前一个提交合并,但不保留此提交的信息
    • drop:完全删除此提交

    在编辑器中,可能会看到如下内容:

    1
    2
    3
    pick abc1234 First commit message
    pick def5678 Second commit message
    pick ghi9012 Third commit message

    可以修改成:

    1
    2
    3
    edit abc1234 First commit message
    pick def5678 Second commit message
    pick ghi9012 Third commit message

    然后保存并关闭编辑器。

  2. 编辑提交

    此时 Git 会停止在第一个需要修改的提交。你可以使用以下命令进行修改:

    • 修改文件,e.g. git add <modified-file>
    • 使用 git commit --amend 进行提交更改

    修改完每个提交后,可以继续下一个提交:

    1
    $ git rebase --continue

    重复这个过程直到完成所有提交的修改。

  3. 强制推送到远程仓库

    最后,确保没有人提交进行推送,强制推送这些更改到远程仓库。

    1
    $ git push -f origin <branch-name>

Worktree

git worktree 用于在同一个 Git 仓库中创建多个工作目录(工作树)。这对于在同一个仓库中的不同分支上进行并行开发特别有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看目前工作目录
$ git worktree list

# 新建工作目录
## 作为全新 branch
$ git worktree add -b <new_branch_name> <folder_path> <source_branch>
## 从已有的 branch
$ git worktree add <folder_path> <source_branch>

# 移除工作目录
$ git worktree remove <worktrees>

# 重新整理工作目录
# 当手动删除了工作目录时,可以使用此命令清理它们的记录
$ git worktree prune

Fork 仓库同步上游仓库

1
2
3
4
5
6
7
8
9
10
# 1. 设置上游仓库
$ git remote add upstream <git-url>

# 2. 本地项目操作
$ git fetch upstream # 获取上游仓库更新
$ git stach # 暂存本地修改(如果有)
$ git branch -a # 列出所有远程仓库地址(非必须)
$ git rebase remotes/upstream/main # 使用远程仓库的提交记录来重写本地提交记录
$ git push -f # 强制推送到远程(github)仓库
$ git stach pop # 恢复暂存的本地修改(如果有)

查看某段代码是谁写的

1
$ git blame <file-name>

提交次数统计

1
$ git log --oneline | wc -l

仓库大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Git 仓库的总大小
$ git count-objects -vH # -v, --verbose; -H, --human-readable

# 列出 Git 仓库中所有被跟踪的文件的大小
$ git ls-files -z | xargs -0 du -hs
# git ls-files:输出仓库中所有被跟踪的文件的相对路径
# -z:使 git ls-files 以空字节(null 字节)作为分隔符输出文件名
# xargs:接受输入并将其逐个传递给 du 命令
# -0:使 xargs 以空字节(null 字节)作为分隔符输入文件名
# dudu 命令用于计算文件或目录的磁盘使用情况
# -h 选项表示以人类可读的格式显示(如 KB、MB)
# -s 选项表示计算总和,不显示子目录的详细信息

# 查询历史体积大的 10 个文件
$ git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | awk '/^blob/ {print substr($0,6)}' | sort --numeric-sort --key=2 --reverse | head -n 10 | cut -c 13-

展示忽略的文件

1
$ git status --ignored

清除 .gitignore 文件中记录的文件

1
2
3
$ git clean -X -f
# -X: remove only files ignored by Git.
# -f, --force

孤立分支

1
2
# 创建一个新的孤立分支
$ git checkout --orphan <branch-name>

孤立分支(orphan branch)不包含任何历史提交记录,相当于保存修改,但是重写 commit 历史,从这条分支开始的提交历史是全新的。比如在现有仓库中创建一个全新的项目时特别有用,而不想保留先前项目的历史记录。

修改最后的提交

  1. 查看最后一次提交的信息

    1
    $ git log -1
  2. 修改最后一次提交

    1
    2
    3
    4
    5
    6
    # 修改最后一次提交的作者信息
    $ git commit --amend --author='Author Name <email@address.com>'
    # 重写最后的提交消息
    $ git commit --amend -m "new message"
    # 修改最新的提交而不更改提交消息
    $ git commit --amend --no-edit
  3. 修改提交后,将更改推送到远程仓库

    1
    $ git push --force origin <branch-name>

另见