Git的配置与使用

本文最后更新于 2024年9月9日 下午

本文主要分享了Git的配置与常用操作,方便日常进行版本管理。

安装与配置

Git官网

安装Git

  • Windows:参考安装教程(推荐查找此时最新的安装教程)

  • Linux:

    1
    2
    3
    sudo add-apt-repository ppa:git-core/ppa
    sudo apt update
    sudo apt install git
  • Mac:官方下载地址

配置Git

Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。

1
2
3
4
5
# 显示当前的Git配置
git config --list # 在Linux系统按`q`键退出

# 编辑Git配置文件。全局:~/.gitconfig,项目:/yourfolder/.git/config
git config -e [--global] # 在Linux系统按 Ctrl+X 退出

编辑器

默认情况下,Git 会调用你通过环境变量 $VISUAL$EDITOR 设置的文本编辑器, 如果没有设置,默认则会调用 vi 来创建和编辑你的提交以及标签信息。 你可以使用 core.editor 选项来修改默认的编辑器:

1
git config --global core.editor gedit  # nano vim gedit notepad++ Code emacs

现在,Git 会调用 gedit 编辑信息。

用户信息

1
2
3
4
# 设置提交代码时的用户信息
# 因为Git是分布式版本控制系统,所以需要填写用户名和邮箱作为一个标识
git config --global user.name "name" # --global表示设置为全局可用,如果想设置局部可用,对某个仓库指定的不同的用户名和邮箱,删除global即可
git config --global user.email "email address"

网络代理

  1. 配置了科学上网。首先确保浏览器能够访问谷歌,能访问GitHub

  2. 查看命令窗口的git配置。git是否设置了代理:

    1
    2
    git config --global --get http.proxy
    git config --global --get https.proxy

    如果什么都没有显示,说明没有配置代理。

  3. 为git设置代理。在linux的设置,网络中查看代理的地址,例如设置如下:

    1
    2
    git config --global http.proxy '127.0.0.1:1080'
    git config --global https.proxy '127.0.0.1:1080'

    这时候再次查看应该有代理内容输出,此时在git应该是可行的。

  4. (可选)git移除代理。

    1
    2
    git config --global --unset http.proxy
    git config --global --unset https.proxy
  5. 大功告成。

绑定GitHub账户

  1. 检验一下是否安装了SSH:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ssh
    # 输出如下:
    usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]
    [-D [bind_address:]port] [-E log_file] [-e escape_char]
    [-F configfile] [-I pkcs11] [-i identity_file]
    [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]
    [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]
    [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
    [user@]hostname [command]
  2. 指定 RSA 算法生成密钥:

    1
    ssh-keygen -t rsa  # 敲四次回车键,之后就就会生成两个文件,分别为秘钥 id_rsa 和公钥 id_rsa.pub

    默认生成目录:

    • Windows:C:/Users/ASUS/.ssh
    • Linux:~/.ssh
    • Mac:~/.ssh
  3. 把公钥id_rsa.pub的内容添加到GitHub。

    1. 复制id_rsa.pub文件内容:

      1
      sudo gedit ~/.ssh/id_rsa.pub  # 全选、复制
    2. 进入自己的GitHub主页,点击右上角头像,点击Settings,点击左栏Access-SSH and GPG keys,点击右上角New SSH key,将复制的公钥id_rsa.pub的内容粘贴到key内,Title内容自定义即可,点击Add SSH key

    3. 验证:

      1
      2
      3
      4
      ssh -T git@github.com
      # -> Are you sure you want to continue connecting (yes/no)?
      yes
      # -> Hi xxx! You've successfully authenticated, but GitHub does not provide shell access.
  4. 大功告成。

相关知识

Git流程

  • Workspace:工作区
  • Index / Stage:暂存区
  • Repository:仓库区(或本地仓库)
  • Remote:远程仓库

工作区与暂存区的区别

  • 工作区:就是你在电脑上看到的目录,比如目录里的文件(.git隐藏目录版本库除外)。或者以后需要再新建的目录文件等等都属于工作区范畴。
  • 版本库(Repository):工作区有一个隐藏目录.git,这个不属于工作区,这是版本库。其中版本库里面存了很多东西,其中最重要的就是stage(暂存区),还有Git为我们自动创建了第一个分支master,以及指向master的一个指针HEAD。

使用Git提交文件到版本库有两步:

  1. 使用git add把文件添加进去,实际上就是把文件添加到暂存区。
  2. 使用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支上。
  3. (可选)使用git push将本地仓库的内容push到远程仓库。

文件状态

Git库所在的文件夹中的文件状态:

  • untracked:未跟踪。这些是在工作目录中创建的,但还没有被暂存(或用git add命令添加)的任何新文件或目录。
  • unmodify:文件已经入库,未修改,即版本库中的文件快照内容与文件夹中完全一致。这种类型的文件有两种去处,如果它被修改,而变为modified。如果使用git rm移出版本库,则成为untracked文件。
  • modified:文件已修改,仅仅是修改,并没有进行其他的操作。这个文件也有两个去处,通过git add可进入暂存staged状态,使用git checkout则丢弃修改过,返回到unmodify状态,这个git checkout即从库中取出文件,覆盖当前修改。
  • staged:暂存状态。执行git commit则将修改同步到库中,这时库中的文件和本地文件又变为一致,文件为unmodify状态。执行git reset HEAD filename取消暂存,文件状态为modified
  • tracked:这些是Git所知道的所有文件或目录。这些是新添加(用git add添加)和提交(用git commit提交)到主仓库的文件和目录。
  • ignored:这些是Git知道的要全部排除、忽略或在Git仓库中不需要注意的所有文件或目录。本质上,这是一种告诉Git哪些未被追踪的文件应该保持不被追踪并且永远不会被提交的方法。

Git 状态untrackednot staged的区别:

  • untrack:表示是新文件,没有被git add过,是为跟踪的意思。
  • not staged:表示git add过的文件,即跟踪文件。再次修改还没有进行git add,就是没有暂存的意思。

.git文件夹的构成

  1. config 文件:这个文件包含了项目级别的配置选项。这里的配置仅适用于当前仓库。可以通过 git config 命令查看或修改这些配置。
  2. description 文件:这个文件仅用于 GitWeb 程序描述仓库,通常不需要修改。
  3. HEAD 文件:这个文件指向当前仓库中被检出的分支。通常此文件包含一个引用到 refs/heads 目录中的分支。
  4. hooks/ 目录:这个目录包含客户端或服务端的钩子脚本(hooks),这些脚本在特定的重要动作发生时触发。
  5. index 文件:该文件保存了暂存区的信息,即当前已经 git add 但还没有提交的变更。
  6. info/ 目录:包含一个 exclude 文件,用于定义不需要通过 .gitignore 文件公开排除的文件的局部模式(通常用于仅在当前仓库中忽略某些文件或目录)。
  7. logs/ 目录:存储了所有的引用(分支)的变更历史,每次提交或引用更新时都会记录在该目录下。
  8. objects/ 目录:这是 Git 存放所有数据(如提交对象、树对象、二进制大对象即 blob 等)的地方。
    1. objects/ 文件夹中,对象通过其 SHA-1 哈希的前两个字符来组织为子目录,剩下的 38 个字符作为子目录中的文件名。例如,一个 SHA-1 哈希值为 de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3 的对象会存储在 objects/de/9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3。当 Git 需要查找一个对象时,它首先会计算对象名称的 SHA-1 哈希值,然后先查找前两个字符对应的目录,再在该目录中查找剩余 38 字符命名的文件。这种方法有助于快速定位和访问存储对象。
    2. info 文件夹下通常包含一个名为 packs 的文件,它记录了所有的包文件(pack files)的索引,使得 Git 可以快速地定位并验证 pack 文件中的对象。
    3. pack 文件夹存放了 Git 的 pack 文件,这些文件是 Git 用来存储对象库中对象的压缩格式,尤其是在仓库较大或有大量历史提交时使用。
      1. .pack 文件:这些是实际的包文件,包含了多个压缩的 Git 对象。一个 pack 文件通常包含了一个项目的多个版本的文件和目录。
      2. .idx 文件:每个 .pack 文件都有一个对应的 .idx 索引文件。索引文件包含了所有包内对象的索引,使得 Git 可以快速找到任何特定对象所在的位置。.idx 文件通过二分查找等算法优化了访问速度。
  9. refs/ 目录:包含对本地分支(在 refs/heads/ 中)、远程仓库的引用(在 refs/remotes/ 中)和标签(在 refs/tags/ 中)的指针。每个文件都包含一个 SHA-1 哈希值,指向 Git 数据库中的对象。

拉取与推送

基本语法

初始化init

1
2
3
4
5
6
git init  # 建立一个标准的Git仓库。这样的仓库初始化后,其项目目录为工作空间,其下的.git目录是版本控制器。

git init --bare # 建立一个“裸”的Git仓库。一般用来初始化远程服务器仓库。
# 这样的仓库初始化后,其项目目录下就是标准仓库.git目录里的内容,没有工作空间。这个仓库只保存git历史提交的版本信息,而不允许用户在上面进行各种git操作(如:push、commit操作)。但是,你依旧可以使用git show命令查看提交内容。
# 不能进行checkout切换分支的操作,提示为`fatal: this operation must be run in a work tree`
# 可以通过pull或clone在其它位置获得原仓库的完整内容

拉取pull

git pull命令用于从远程获取代码并合并本地的版本。

1
2
3
4
git pull <远程主机名> <远程分支名>:<本地分支名>
git pull origin master:brantest # 将远程主机 origin 的 master 分支拉取过来,与本地的 brantest 分支合并
# 如果远程分支是与当前分支合并,则冒号后面的部分可以省略
git pull origin master

如果当前分支和要合并的分支存在冲突,Git会要求我们解决冲突并手动执行合并操作。

推送push

git push命令用于从将本地的分支版本上传到远程并合并。

1
2
3
4
5
6
7
8
git push <远程主机名> <本地分支名>:<远程分支名>
# 如果本地分支名与远程分支名相同,则可以省略冒号:
git push <远程主机名> <本地分支名>
git push origin master # 将本地的 master 分支推送到 origin 主机的 master 分支,相当于:
git push origin master:master
# 如果本地版本与远程版本有差异,但又要强制推送可以使用 --force 参数:
git push --force origin master
git push [remote] --all # 推送所有分支到远程仓库

克隆clone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
git clone [url]  # [url] 是你要拷贝的项目
# 执行完上述步骤后,Git 会克隆远程仓库到本地,并在当前目录下创建一个与远程仓库同名的文件夹
# 上述操作将复制该项目的全部记录

git clone <repository-url> <local-directory>
# <repository-url> 是你想克隆的远程 Git 仓库的 URL
# <local-directory> 是你想创建本地副本的目录路径

# 克隆特定分支
git clone --branch my-branch https://github.com/my-username/my-repository.git
# 克隆特定标签
git clone --branch v1.0.0 https://github.com/my-username/my-repository.git
# 克隆特定提交
git clone --branch 4c4ba1d https://github.com/my-username/my-repository.git
# 克隆子目录
git clone --depth 1 --filter=blob:none https://github.com/my-username/my-repository.git my-subdirectory # 这个命令将创建 my-repository 仓库的 my-subdirectory 子目录中的文件的本地副本
# depth 用于指定克隆深度,为1即表示只克隆最近一次commit

git clone --bare <url> [<directory>] # 它将克隆一个裸仓库(bare repository)。裸仓库是一个没有工作目录的Git仓库,只包含版本对象和引用,不保存源代码的修改历史,非常适合用作中央仓库或备份
git clone --bare https://github.com/username/repository.git

git clone --recursive <repository_url> # 我们在一个项目中使用了开源库或第三方库,而这些库通常会作为子模块嵌入到我们的项目中。如果我们使用普通的 Git Clone 命令只会将主项目克隆下来,而不会将子模块一同克隆。这时候,我们就可以使用 Git Clone –recursive 来克隆主项目和子模块。

增加add/删除rm/移动mv文件

1
2
3
4
5
6
7
8
9
10
11
git add [file1] [file2] ...  # 添加指定文件到暂存区
git add [dir] # 添加指定目录到暂存区,包括子目录
git add . # 添加当前目录的所有文件到暂存区

# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
git add -p

git rm [file1] [file2] ... # 删除工作区文件,并且将这次删除放入暂存区
git rm --cached [file] # 停止追踪指定文件,但该文件会保留在工作区
git mv [file-original] [file-renamed] # 改名文件,并且将这个改名放入暂存区

提交commit

1
2
3
4
5
6
7
8
9
10
11
12
git commit -m [message]  # 提交暂存区到仓库区
# 如果一行不够,可以只执行git commit,就会跳出文本编辑器,让你写多行
# 按Esc键,之后I表示插入模式。输入一个你想要的提交信息。在:wq之后按Esc键,保存并退出编辑器。这样,我们就成功地进行了一次提交。
git commit [file1] [file2] ... -m [message] # 提交暂存区的指定文件到仓库区
git commit -a # 提交工作区自上次commit之后的变化,直接到仓库区
git commit -v # 提交时显示所有diff信息

# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
git commit --amend -m [message]

git commit --amend [file1] [file2] ... # 重做上一次commit,并包括指定文件的新变化

git commit -m 提交的内容换行:

  • 方法1:先输入第一个双引号,按Enter即可换行,完成后再补齐后面的双引号。

    1
    2
    3
    git commit -m "This is the first line.
    [Enter]
    This is the second line."
  • 方法2:在双引号中使用转义字符\n来表示换行符。

    1
    2
    git commit -m "This is the first line.\nThis is the second line."  # 转义字符\n只能在双引号中使用,单引号中的\n会被直接作为普通文本处理。
    # 根据操作系统的不同,转义字符的使用可能会有所差异。在Windows系统下,您可能需要使用’^’符号来表示转义字符。
  • 方法三:使用多个-m参数来提交多行的信息,每个-m参数代表一行提交信息。

    1
    git commit -m "This is the first line." -m "This is the second line."
  • 方法4:使用文件。

    首先,创建一个名为message.txt的文本文件,文件内容如下:

    1
    2
    This is the first line.
    This is the second line.

    然后,可以使用以下命令来提交代码:

    1
    git commit -F message.txt  # 文件路径必须是相对于当前Git仓库根目录的路径

查看信息status/log/show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
git status  # 显示有变更的文件
git log # 显示当前分支的版本历史
git log --graph --pretty=format:'%Cred%h%Creset - %C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative # 更清晰的日志展示
git log --stat # 显示commit历史,以及每次commit发生变更的文件
git log -S [keyword] # 搜索提交历史,根据关键词
git log [tag] HEAD --pretty=format:%s # 显示某个commit之后的所有变动,每个commit占据一行
git log [tag] HEAD --grep feature # 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件

# 显示某个文件的版本历史,包括文件改名
git log --follow [file]
git whatchanged [file]

git log -p [file] # 显示指定文件相关的每一次diff
git log -5 --pretty --oneline # 显示过去5次提交

git shortlog -sn # 显示所有提交过的用户,按提交次数排序
git blame [file] # 显示指定文件是什么人在什么时间修改过

git show [commit] # 显示某次提交的元数据和内容变化
git show --name-only [commit] # 显示某次提交发生变化的文件
git show [commit]:[filename] # 显示某次提交时,某个文件的内容
git reflog # 显示当前分支的最近几次提交,还有分支切换历史

远程仓库remote

1
2
3
4
5
6
7
8
9
10
11
# 查看信息
git remote # 查看远程库的信息
git remote –v # 查看远程库的详细信息
git remote show [remote] # 显示某个远程仓库的信息
git fetch [remote] # 下载远程仓库的所有变动

# 创建
git remote add [shortname] [url] # 增加一个新的远程仓库,并命名。使用shortname指代url,方便输入和记忆

# 删除
git remote rm [远程主机名称] # 删除此远程连接

本地创建Git仓库

在操作Git仓库的时候多使用git status命令,这能帮助我们实时了解仓库的状态,非常有用。

在我们向远程仓库提交代码的时候,一定要先进行pull操作,再进行push操作,防止本地仓库与远程仓库不同步导致冲突的问题,尤其是本地创建Git仓库的情况,很容易就出现问题。

  1. 建立一个本地仓库进入,init初始化:

    1
    2
    3
    cd yourfolder  # 先进入到Git仓库的最顶层目录下
    git init # 初始化仓库
    # Initialized empty Git repository in ~/yourfolder/.git/

    这时候你当前的目录下会多了一个.git的目录,这个目录是Git来跟踪管理版本的。

  2. 关联远程仓库:

    1
    git remote add origin https://github.com/raulmur/ORB_SLAM2.git  # 关联远程仓库,同时将远程仓库命名为 origin

    将远程仓库设置为本地目录

  3. (略)同步远程仓库和本地仓库:

    1
    2
    3
    # 带上 -u 参数其实就相当于记录了push到远端分支的默认值,这样当下次我们还想要继续push的这个远端分支的时候推送命令就可以简写成git push即可
    # git pull origin master # 将远程仓库origin的master分支的内容拉取到本地,与本地的master分支进行合并
    # git push -u origin master # 将本地的 master 分支推送到 origin 主机的 master 分支
  4. 接下来的步骤与本地拉取Git仓库相同。先输入git addgit commit命令,将要提交的文件添加并提交到本地仓库;然后再输入git push origin master命令,将本地仓库修改(或者添加)的内容提交到远程仓库就完成啦。

本地拉取Git仓库

本地没有Git仓库,这时我们就可以直接将远程仓库clone到本地。通过clone命令创建的本地仓库,其本身就是一个 Git 仓库了,不用我们再进行init初始化操作啦,而且自动关联远程仓库。我们只需要在这个仓库进行修改或者添加等操作,然后commit即可。

  1. 直接将远程仓库 clone 到本地;

    1
    2
    cd yourfolder/
    git clone https://github.com/raulmur/ORB_SLAM2.git
  2. 新建或修改文件。

  3. 将文件添加并commit到本地仓库:

    提交时指定忽略的文件(夹)

    规范提交信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd yourfolder/
    git status # 查看仓库状态
    # nothing added to commit but untracked files present (use "git add"to track)

    git add . # 将文件添加到了临时缓冲区
    git commit -m "message" # 添加提交信息,可使用中文。见下文:规范提交信息

    # 如果你是第一次提交的话,会让你输入用户名和邮箱:
    git config --global user.email"you@example.com" # --global表示设置为全局可用,如果想设置局部可用,对某个仓库指定的不同的用户名和邮箱,删除global即可
    git config --global user.name"Your Name"
  4. 查看仓库提交日志:

    1
    2
    git log
    git log --graph --pretty=format:'%Cred%h%Creset - %C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative # 更清晰的日志展示
  5. 查看仓库状态:

    1
    2
    git status
    # nothing to commit,working tree clean
  6. 将本地仓库的内容push到远程仓库:

    1
    2
    3
    4
    5
    6
    git push origin master  # 将本地仓库的内容提交到远程仓库,origin是远程主机的名字,master仓库的分支名
    # 我们习惯性将远程仓库命名为origin,不过在需要关联多个远程仓库的时候,就需要我们再取别的名字啦!
    # git push origin main
    # git push -u origin main # 带上 -u 参数其实就相当于记录了push到远端分支的默认值,这样当下次我们还想要继续push的这个远端分支的时候推送命令就可以简写成git push即可

    # 第一次上传需要输入密码
  7. 大功告成。

规范提交信息

一般步骤和规范

提交的内容实现换行

以下是一些撰写规范化提交信息的指导原则和建议步骤:

  1. 使用清晰的语言。提交信息应该直接且简洁地描述所做的更改。使用简洁的语言有助于其他团队成员快速理解提交的目的。

  2. 遵循通用格式。一个广泛接受的Angular规范格式是这样的:

    1
    2
    3
    4
    5
    <type>(<scope>): <subject>
    <BLANK LINE>
    <body>
    <BLANK LINE>
    <footer>

    各部分说明如下:

    • type:提交类型:

      • feat:新功能(Feature)。feat用于表示引入新功能或特性的变动。这种变动通常是在代码库中新增的功能,而不仅仅是修复错误或进行代码重构。
      • fix/to:修复bug。这些bug可能由QA团队发现,或由开发人员在开发过程中识别。
        • fix关键字用于那些直接解决问题的提交。当创建一个包含必要更改的提交,并且这些更改能够直接修复已识别的bug时,应使用fix。这表明提交的代码引入了解决方案,并且问题已被立即解决。
        • to关键字则用于那些部分处理问题的提交。在一些复杂的修复过程中,可能需要多个步骤或多次提交来完全解决问题。在这种情况下,初始和中间的提交应使用to标记,表示它们为最终解决方案做出了贡献,但并未完全解决问题。最终解决问题的提交应使用fix标记,以表明问题已被彻底修复。
      • docs:文档(Documentation)。docs表示对文档的变动,这包括对代码库中的注释、README文件或其他文档的修改。这个前缀的提交通常用于更新文档以反映代码的变更,或者提供更好的代码理解和使用说明。
      • style: 格式(Format)。style用于表示对代码格式的变动,这些变动不影响代码的运行。通常包括空格、缩进、换行等风格调整。
      • refactor:重构(即不是新增功能,也不是修改bug的代码变动)。refactor表示对代码的重构,即修改代码的结构和实现方式,但不影响其外部行为。重构的目的是改进代码的可读性、可维护性和性能,而不是引入新功能或修复错误。
      • perf: 优化相关,比如提升性能、体验。perf表示与性能优化相关的变动。这可能包括对算法、数据结构或代码实现的修改,以提高代码的执行效率和用户体验。
      • test:增加测试。test表示增加测试,包括单元测试、集成测试或其他类型的测试。
      • chore:构建过程或辅助工具的变动。chore表示对构建过程或辅助工具的变动。这可能包括更新构建脚本、配置文件或其他与构建和工具相关的内容。
      • revert:回滚到上一个版本。revert用于回滚到以前的版本,撤销之前的提交。
      • merge:代码合并。merge表示进行代码合并,通常是在分支开发完成后将代码合并回主线。
      • sync:同步主线或分支的Bug。sync表示同步主线或分支的 Bug,通常用于解决因为合并而引入的问题。
    • scope:用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。可选。例如:

      1
      2
      feat(Controller): 添加用户登录功能
      # 这个提交消息中,Controller 是 scope,表示这次提交影响了控制层。

      如果你的修改影响了不止一个scope,你可以使用*代替。

    • subject:简短描述提交内容的标题,不超过50个字符。

    • body:详细描述更改的内容,说明原因和与之前行为的对比,可以分成多行。可选。

    • footer:Footer 部分只用于两种情况。可选。

      • 关联的 issue 或 pull request 号。

      • 不兼容变动。如果当前代码与上一个版本不兼容,则 Footer 部分以BREAKING CHANGE开头,后面是对变动的描述、以及变动理由和迁移方法。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        BREAKING CHANGE: isolate scope bindings definition has changed.

        To migrate the code follow the example below:

        Before:

        scope: {
        myAttr: 'attribute',
        }

        After:

        scope: {
        myAttr: '@',
        }

        The removed `inject` wasn't generaly useful for directives so there should be no code using it.
  3. 使用命令式语气。始终使用命令式的语气:

    1
    2
    3
    4
    git commit -m "fix bug causing system crash"
    # 而非
    git commit -m "fixed bug causing system crash"
    git commit -m "fixes bug causing system crash"
  4. 首字母小写。提交信息的标题建议首字母小写。

  5. 不要在标题行结束时加标点。标题应该简洁,在末尾没有标点结束。

  6. 分离标题与正文。标题行之后应该有一个空行,隔开标题和正文,有助于 Git 正确地格式化提交信息。

  7. 还有一种特殊情况,如果当前 commit 用于撤销以前的 commit,则必须以revert:开头,后面跟着被撤销 Commit 的 Header:

    1
    2
    3
    revert: feat(pencil): add 'graphiteWidth' option

    This reverts commit 667ecc1654a317a13331b17617d973392f415f02.

    Body部分的格式是固定的,必须写成This reverts commit <hash>.,其中的hash是被撤销 commit 的 SHA 标识符。

举例

  1. 添加新功能:

    1
    2
    3
    4
    5
    6
    git commit -m "feat: add user authentication system

    Implemented basic user authentication system using JWTs. This includes routes for registration, login, and token verification. The new system improves security by ensuring all actions require a valid token.

    Resolves #123
    "
  2. 修复一个错误:

    1
    2
    3
    4
    5
    6
    git commit -m "fix: correct typo in API documentation

    The JSON request body example in the user creation endpoint documentation had a typo in the 'email' field which has been corrected. This typo has potentially led to misunderstandings of API usage.

    Spotted by @username in code review.
    "
  3. 做出改进(重构):

    1
    2
    3
    4
    5
    6
    git commit -m "refactor: streamline user data handling

    Refactored the user data management to reduce redundancy and improve code clarity. Removed duplicated functions and replaced them with single setUser function that handles all scenarios.

    See merge request !456
    "
  4. 文档更新:

    1
    2
    3
    4
    git commit -m "docs: update README with API endpoint changes

    Updated the README document to reflect recent changes in API endpoints. Added documentation for the newly introduced payment processing API.
    "
  5. 添加用户配置文件编辑功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    feat(UserProfile): add user profile editing feature

    This commit introduces a new feature that allows users to edit their profiles
    directly from the user interface. The motivation behind this change is to
    enhance user interaction and provide a more seamless experience.

    Previously, users had to navigate to a separate editing page to update their
    profile information. With this new feature, users can now make changes
    efficiently from their profile page, eliminating unnecessary steps in the
    workflow.

    Changes included in this commit:
    - Added a new 'Edit Profile' button on the user profile page.
    - Implemented frontend components for profile editing.
    - Updated backend API to handle profile updates securely.

    By streamlining the profile editing process, we aim to improve overall user
    satisfaction and make our application more user-friendly. This enhancement is
    in response to user feedback, addressing the need for a more intuitive and
    accessible way to modify profile details.

    Closes #234

设置git commit模板(不推荐)

  1. 建立模板文件。在项目中建立.git_template文件,内容可以自定义:

    1
    2
    3
    type:
    scope:
    subject:
  2. 设置模板。运行如下命令:

    1
    git config [--global] commit.template .git_template # 当前项目。全局设置:可选参数 --global
  3. 提交代码。先使用git add .添加代码,然后使用git commit按照模板填写,最后git push推送到远端。

优点:规则可配置,更自由;配置方式简洁(只需添加配置文件)。

缺点:便利性差,每次都要使用编辑器填写模板;易出错,没有可靠的校验方式。

插件(推荐)

Commitizen

Commitizen是一个撰写合格 Commit message 的工具。安装命令如下:

1
npm install -g commitizen

然后,在项目目录里,运行下面的命令,使其支持 Angular 的 Commit message 格式:

1
commitizen init cz-conventional-changelog --save --save-exact

以后,凡是用到git commit命令,一律改为使用git cz。这时,就会出现选项,用来生成符合格式的 Commit message。

1
2
3
4
git add .
git cz

git log # 查看提交的 commit message

commitlint

commitlint是一个用于检查提交消息是否符合指定规范的工具。它可以帮助团队确保 Git 提交消息的一致性和规范性,尤其是当项目采用类似 Angular Commit Message Conventions 的规范时。

  1. 安装 Commitlint。首先,你需要安装 commitlint 及其相关的配置和规则。通常,@commitlint/config-conventional 是与 Angular 规范兼容的配置。

    1
    npm install --save-dev @commitlint/config-conventional @commitlint/cli
  2. 配置 Commitlint。在项目根目录下创建 commitlint.config.js 文件,并添加如下内容:

    1
    2
    3
    module.exports = {
    extends: ['@commitlint/config-conventional'],
    };

    这个配置文件使用了 @commitlint/config-conventional 中预定义的规则,确保符合常见的提交规范。

    检测规则有很多类,最常用的是上面采用的Conventional Commits specification。此规则是根据上文所说的Angular Team Commit Specification衍生出来的。commitlint更多规则可看:Shared configuration

    上面网址的规则中有两个比较类似的规则:

    @commitlint/config-angular是几乎满足了本文第二点中介绍的header、body、footer中所有的规则。然后添加了header中的type和scope必须为小写的规则。

    @commitlint/config-conventional是继承@commitlint/config-angular的全部规则上还有一些小的约束,例如

    • header最大长度为100个字符
    • body和footer每行的最大长度为100个字符,注意这里是每行,即可以换行。
  3. 配置Git Hooks。你可以使用 Husky 钩子工具来在提交前运行 commitlint。首先,安装 Husky:

    1
    npm install --save-dev husky

    然后,在 package.json 中添加以下配置:

    1
    2
    3
    4
    5
    "husky": {
    "hooks": {
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
    }

    上述代码目的是为了在git执行commit和merge之前,先执行commitlint -E HUSKY_GIT_PARAMS。从而使commitlint检测提交信息。

    git hooks有很多个周期函数,更多可点击查看githookspre-commitpre-commit-msg都是在因git commit触发的,而commit-msg是因git commitgit merge触发的,在多人合作项目中避免不了git merge指令,因此我们选择commit-msg周期函数。

  4. 添加了以上两个配置后,每次git commit前就会根据规则检查。如果不符合规则,则会中断。

validate-commit-msg

validate-commit-msg 用于检查 Node 项目的 Commit message 是否符合格式。

它的安装是手动的。首先,拷贝下面这个JS文件,放入你的代码库。文件名可以取为validate-commit-msg.js

接着,把这个脚本加入 Git 的 hook。下面是在package.json里面使用 ghooks,把这个脚本加为commit-msg时运行:

1
2
3
4
5
"config": {
"ghooks": {
"commit-msg": "./validate-commit-msg.js"
}
}

然后,每次git commit的时候,这个脚本就会自动检查 Commit message 是否合格。如果不合格,就会报错:

1
2
3
git add -A 
git commit -m "edit markdown"
# INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" ! was: edit markdown

VS Code

  • Commit Message Editor
  • git-commit-plugin

Clion

  • Git Commit Message Helper:标准化提交信息。设置-其它设置-GitCommitMessageHelper

提交时指定忽略的文件(夹)

基本方法

一般来说每个Git项目中都需要一个.gitignore文件,这个文件的作用就是告诉Git哪些文件不需要添加到版本管理中。实际项目中,很多文件都是不需要版本管理的,比如Python的.pyc文件和一些包含密码的配置文件等等。这个文件的内容是一些规则,Git会根据这些规则来判断是否将文件添加到版本控制中。

通常,一个.gitignore文件会被放在仓库的根目录下。根目录也被称为父目录和当前工作目录。根目录包含了组成项目的所有文件和其他文件夹。当然,你也可以把它放在版本库的任何文件夹中。你甚至可以有多个 .gitignore 文件。

被过滤掉的文件就不会出现在Git仓库中了。当然本地库中还有,只是push的时候不会上传。

方法一: 在Git项目中定义.gitignore文件。在项目的某个文件夹下定义名为.gitignore文件,在该文件中定义相应的忽略规则,来管理当前文件夹下的文件的Git提交行为。文件是可以提交到公有仓库中,这就为该项目下的所有开发者都共享一套定义好的忽略规则。在.gitingore文件中,遵循相应的语法,在每一行指定一个忽略规则。如:

1
2
3
4
# 忽略规则
*.log # 忽略所有的log后缀的文件
*.temp
/vendor

详细语法

方法二: 在Git项目的设置中指定排除文件。这种方式只是临时指定该项目的行为,需要编辑当前项目下的.git/info/exclude文件,然后将需要忽略提交的文件写入其中。需要注意的是,这种方式指定的忽略文件的根目录是项目根目录。这种方法就不提倡了,只能针对单一工程配置,而且还不能将过滤规则同步到其他开发者。

方法三: 定义Git全局的.gitignore文件。除了可以在项目中定义.gitignore文件外,还可以设置全局的git .gitignore文件来管理所有Git项目的行为。这种方式在不同的项目开发者之间是不共享的,是属于项目之上Git应用级别的行为。这种方式也需要创建相应的.gitignore文件,可以放在任意位置(比如:/home/wangshibo/hqsb_ios)。然后在当前目录下使用以下命令配置Git:

1
2
3
git config --global core.excludesfile ~/.gitignore
# 你会发现在~/.gitconfig文件中会出现excludesfile = /home/wangshibo/hqsb_ios/.gitignore
# 说明Git把文件过滤规则应用到了Global的规则中

.gitignore的优先级

在Git中,通配符规则可以应用于不同的层级。当Git查找要忽略的文件时,会按照以下顺序进行匹配:

  1. 匹配当前目录下的.gitignore文件中的规则
  2. 如果未匹配到规则,则查找父目录中的.gitignore文件,依次向上查找,直到根目录
  3. 递归忽略规则适用于子目录和子文件夹

如果存在相同的规则,Git会应用最接近要忽略的文件的规则。

.gitignore忽略规则的匹配语法

.gitignore文件中,每一行的忽略规则的语法如下:

  • 空格不匹配任意文件,可作为分隔符,可用反斜杠转义。
  • #开头的行都会被Git忽略。即#开头的文件标识注释,可以使用反斜杠进行转义。
  • 可以使用标准的glob模式匹配。所谓的glob模式是指shell所使用的简化了的正则表达式。
  • 以斜杠/开头表示当前.gitignore文件所在的目录(根目录),以斜杠/结尾表示要忽略整个目录及其所有内容。
  • 以星号*通配多个字符,即匹配多个任意字符;使用两个星号** 表示匹配任意中间目录,比如a/**/z可以匹配 a/z, a/b/za/b/c/z等。
  • 以问号?通配单个字符,即匹配一个任意字符。
  • 以方括号[]包含单个字符的匹配列表,即匹配任何一个列在方括号中的字符。比如[abc]表示要么匹配一个a,要么匹配一个b,要么匹配一个c;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配。比如[0-9]表示匹配所有09的数字,[a-z]表示匹配任意的小写字母)。
  • 以叹号!表示不忽略(跟踪)匹配到的文件或目录,即要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号!取反。需要特别注意的是:如果文件的父目录已经被前面的规则排除掉了,那么对这个文件用!规则是不起作用的。也就是说!开头的模式表示否定,该文件将会再次被包含,如果排除了该文件的父级目录,则使用!也不会再次被包含。可以使用反斜杠进行转义。

示例:

配置文件是按行从上到下进行规则匹配的,意味着如果前面的规则匹配的范围更大,则后面的规则将不会生效。

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
# comment  // 表示此行为注释,将被Git忽略

/text.txt // 忽略位于根目录下的 text.txt 文件
/test/text.txt // 忽略一个位于根目录下的 test 目录中的 text.txt 文件。同 test/text.txt
text.txt // 忽略任何 text.txt 文件

img* // 忽略所有名字以 img 开头的文件和目录
*.md // 忽略位于项目中任何地方的以 .md 为扩展名的任何文件
!README.md // 但不忽略 README.md 文件
!/bin/run.sh // 表示不忽略bin目录下的run.sh文件

debug/*.obj // 表示忽略debug/io.obj,不忽略 debug/common/io.obj和tools/debug/io.obj
doc/*.txt // 表示会忽略doc/notes.txt但不包括 doc/server/arch.txt

**/foo // 表示忽略/foo,a/foo,a/b/foo等
a/**/b // 表示忽略a/b, a/x/b,a/x/y/b等

test/ // 忽略位于你的项目中任何地方的名为 test 的目录(包括目录中的其他文件和其他子目录)
!test/example.md // 试图在一个被忽略的目录内排除一个文件是行不通的

test/* // 忽略位于你的项目中任何地方的名为 test 的目录中的文件,但不忽略该目录
!test/example.md // 这里可以在一个被忽略的目录内排除一个文件
!test/example/ // 在被忽略的目录内排除一个文件夹
!test/example // 在被忽略的目录内排除名为example文件和文件夹

/test/ // 只忽略当前目录(根目录)下的test文件夹
/test // 同时忽略当前目录(根目录)下的test文件和文件夹
test // 同时忽略任何带有这个名字的文件或目录

问题与解决

问题1:

情况:.gitignore中已经标明忽略的文件目录下的文件,但git push的时候还会出现在push的目录中,或者用git status查看状态,想要忽略的文件还是显示被追踪状态。原因:因为在git忽略目录中,新建的文件在git中会有缓存,如果某些文件之前已经被纳入了版本管理中(已经被跟踪),就算是在.gitignore中已经声明了忽略路径也是不起作用的。解决:这时候我们就应该先把本地缓存删除,然后再进行git的提交,这样就不会出现忽略的文件了。

1
2
3
4
5
git rm -r --cached .  # or
# git rm --cached directory/.env # 只删除特定文件的缓存
git add .
git commit -m 'update .gitignore'
git push -u origin master

问题2:

需求:在使用.gitignore文件后如何删除远程仓库中以前上传的此类文件而保留本地文件。在使用git和github的时候,之前没有写.gitignore文件,就上传了一些没有必要的文件,在添加了·文件后,就想删除远程仓库中的文件却想保存本地的文件。

解决:这时候不可以直接使用git rm directory,这样会删除本地仓库的文件。可以使用git rm -r –-cached directory来删除缓冲,然后进行git commitgit push。这样会发现远程仓库中的不必要文件就被删除了,以后可以直接使用git add -A来添加修改的内容,上传的文件就会受到.gitignore文件的内容约束。

将远程仓库设置为本地目录

背景知识

远端仓库实际上储存的是一个裸仓库。裸仓库命名上一般以仓库名+.git,比如repo.git,本地仓库就叫repo。如果你去GitHub上clone一个项目,你会发现地址是以.git结尾的:https://github.com/raulmur/ORB_SLAM2.git。

查看本地仓库配置./.git/config, 你会发现, 本地仓库就是用这个裸仓库的地址查询和更新代码的:

1
2
3
4
5
6
7
c4r:chromeExtPIP$ cat .git/config 
...

[remote "origin"]
url = https://github.com/raulmur/ORB_SLAM2.git
fetch = +refs/heads/*:refs/remotes/origin/*
...

这个裸仓库地址是可以你能访问的任意地方,不一定是github或其他仓库托管网站。所以说,你完全可以创建一个裸仓库在硬盘里,然后把本地仓库的地址指向硬盘就可以了:

git init初始化的远程仓库的话用户也可以在该目录下执行所有git方面的操作,但在本地库执行git push向其推送代码时是会报错的,因为此时远程仓库不知道本地仓库是否也在对工作副本进行了修改,直接push过去可能造成working copy的冲突。

解决办法就是使用git init –bare方法创建一个所谓的裸仓库,之所以叫裸仓库是因为这个仓库只保存git历史提交的版本信息,而不允许用户在上面进行各种git操作,如果你硬要操作的话,只会得到下面的错误(”This operation must be run in a work tree”)。

这个就是最好把远端仓库初始化成bare仓库的原因。

实际操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 在本地创建远程仓库
cd /path/to/repo_remote
git init --bare
# 关联本地仓库和远程仓库
cd /path/to/repo_local
git remote add remoteName ./path/to/repo_remote # 方便地用remoteName来代替完整的路径
# 之后的操作与上文的提交文件相同
# 就算repo_local文件夹被移除,也可以通过以下操作repo_remote的方法来获取原内容

# 在另一个文件夹clone原仓库
git clone /path/to/repo_local # or
git clone /path/to/repo_remote

# 另一个位置(电脑)想pull代码
cd /path/to/repo_other
git init
git remote add remoteName ./path/to/repo_remote
git pull <远程主机名> <远程分支名>:<本地分支名>

高阶操作

分支checkout

分支策略:

  • 首先master主分支应该是非常稳定的,也就是用来发布新版本,一般情况下不允许在上面干活,干活一般情况下在新建的dev分支上干活,干完后,比如上要发布,或者说dev分支代码稳定后可以合并到主分支master上来。

  • bug分支:在开发中,会经常碰到bug问题,那么有了bug就需要修复,在Git中,分支是很强大的,每个bug都可以通过一个临时分支来修复,修复完成后,合并分支,然后将临时的分支删除掉。

在各自分支的修改不会同步到其他分支,除非手动进行分支合并。使用git checkout切换分支后,在上一分支的修改不会同步到这一分支。

分支会继承被分支前的commit信息。

如果在一分支做了文件修改,但没有git add .git commit,而直接使用git checkout切换分支,会报错:

error: Your local changes to the following files would be overwritten by checkout:
filename
Please commit your changes or stash them before you switch branches.
Aborting

查看分支

1
2
3
4
# 查看分支
git branch # 列出所有本地分支,分支前的*号表示“当前所在的分支”
git branch -r # 列出所有远程分支
git branch -a # 列出所有本地分支和远程分支

创建/切换分支

GitFlow流程

GitFlow 是一种Git工作流,这个工作流程围绕着project的发布(release)定义了一个严格的如何建立分支的模型。它是团队成员遵守的一种代码管理方案。以下是基于Vincent Driessen提出的GitFlow流程图:

  • Production分支。
    • 也就是我们经常使用的Master分支,这个分支最近发布到生产环境的代码,最近发布的Release,这个分支只能从其他分支合并,不能在这个分支直接修改。
    • 所有在Master分支上的Commit应该打上Tag,一般情况下Master不存在CommitDevelop分支基于Master分支创建。
  • Develop 分支。
    • 这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支。
  • Feature分支。
    • 这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release
    • Feature分支做完后,必须合并回Develop分支, 合并完分支后一般会删点这个Feature分支,毕竟保留下来意义也不大。
  • Release分支。
    • 当你需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到MasterDevelop分支。
    • Release分支基于Develop分支创建,打完Release分支之后,我们可以在这个Release分支上测试,修改Bug等。同时,其它开发人员可以基于Develop分支新建Feature(记住:一旦打了Release分支之后不要从Develop分支上合并新的改动到Release分支)发布Release分支时,合并ReleaseMasterDevelop,同时在Master分支上打个Tag记住Release版本号,然后可以删除Release分支了。
  • Hotfix分支。
    • 当我们在Production发现新的Bug时候,我们需要创建一个Hotfix,完成Hotfix后,我们合并回MasterDevelop分支,所以Hotfix的改动会进入下一个Release
    • Hotfix分支基于Master分支创建,开发完后需要合并回MasterDevelop分支,同时在Master上打一个tag

分支命名规范

通用的Git分支命名规范示例:

推荐使用小写字母来命名分支,并使用连字符(-)来替代空格或其他符号,以保持URL的友好性。

除连字符外,应避免使用其他特殊字符如空格、波折号(_)等,因为这些字符可能在不同系统或脚本中引起歧义。

  • 主分支:

    • mainmaster:主分支,存放产品的正式发布版本。
    • developdev:开发分组的主分支,用于集成各种功能分支的更改。
  • 功能(Feature)分支:

    • feature/
    • feat/

    例如: feature/login-auth, feat/user-profile

  • 修复(Bugfix)分支:

    • bugfix/
    • fix/

    例如: bugfix/login-error, fix/api-crash

  • 发布(Release)分支:

    • release/

    例如: release/1.0.0

  • 紧急修复(Hotfix)分支:

    • hotfix/

    例如: hotmodefix/typo-in-homepage

  • 文档(Documentation)分支:

    • docs/

    例如: docs/installation-guide

  • 其他类型的分支也可能包括:

    • experiment/: 用于试验性的探索或研究。
    • optimize/: 用于性能优化相关的更改。
    • test/: 专门用来处理测试相关的任务。

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
git checkout -h  # 帮助

# 创建分支
git branch otherBranch # 创建了一个名为otherBranch的分支,但依然停留在当前分支
git checkout otherBranch # 切换到otherBranch分支,并更新工作区
git checkout -b otherBranch # 直接创建并切换到了一个名为otherBranch的分支
git checkout --orphan <new-branch> # 创建一个全新的分支,不包含原分支的提交历史

git checkout - # 快速切换回前一个分支,无需记住分支名称


git branch [branch] [commit] # 新建一个分支,指向指定commit
git checkout -b [branch] [tag] # 新建一个分支,指向某个tag

git branch --track [branch] [remote-branch] # 新建一个分支,与指定的远程分支建立追踪关系
git branch --set-upstream [branch] [remote-branch] # 建立追踪关系,在现有分支与指定的远程分支之间

git checkout -- <file> # 将指定文件 <file> 恢复到最新的提交状态,丢弃所有未提交的更改,这对于撤销不需要的更改非常有用
git checkout . # 放弃工作区中全部的修改

git checkout <commit-hash> # 切换到特定提交。你可以使用提交的哈希值 <commit-hash> 来切换到特定的提交状态。这将使你进入"分离头指针"状态,只能查看历史记录,而不能进行分支操作。通常情况下,不建议在分离头指针状态下工作,因为更改可能会丢失。
git checkout tags/<tag-name> # 切换到标签。如果你有一个标签 <tag-name>,你可以使用这个命令来切换到该标签所指向的提交状态。

合并分支

合并merge

基本语法
1
2
3
4
5
# 合并分支
git checkout master # 切换到master分支
git merge -m "message" otherBranch # 将otherBranch分支合并到master分支。默认提交信息为“Merge branch 'otherBranch'”
# 通常合并分支时,git一般使用”Fast forward”模式,在这种模式下,删除分支后,会丢掉分支信息,现在我们来使用带参数 –no-ff来禁用”Fast forward”模式。
git merge –no-ff -m "message" otherBranch

在合并分支的时候,要考虑到两个分支是否有冲突,如果有冲突,则不能直接合并,需要先自行解决冲突;反之,则可以直接合并。

从旧分支上继承的新分支,做出修改后,可以直接合并到旧分支(不会触发冲突)。如果旧分支在此期间也做出了修改,则会触发冲突。

Git合并时--no-ff的作用

在许多介绍 Git 工作流的文章里,都会推荐在合并分支时,加上--no-ff参数:

1
2
git checkout develop
git merge --no-ff feature

--no-ff在这的作用是禁止快进式合并。

Git 合并两个分支时,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,叫做“快进”(fast-forward),比如下图:

1
2
3
          A---B---C feature
/
D---E---F master

要把 feature 合并到 master 中,执行以下命令:

1
2
git checkout master
git merge feature

结果就会变成:

1
2
3
          A---B---C feature
/ master
D---E---F

因为 feature 就在 master 的下游,所以直接移动了 master 的指针,master 和 feature 都指向了 C。而如果执行了git merge --no-ff feature的话,是下面的结果:

1
2
3
          A---B---C feature
/ \
D---E---F-----------G master

由于--no-ff禁止了快进,所以会生成一个新的提交,master 指向 G。

从合并后的代码来看,结果其实是一样的,区别就在于 --no-ff 会让 Git 生成一个新的提交对象。为什么要这样?通常我们把 master 作为主分支,上面存放的都是比较稳定的代码,提交频率也很低,而 feature 是用来开发特性的,上面会存在许多零碎的提交,快进式合并会把 feature 的提交历史混入到 master 中,搅乱 master 的提交历史。所以如果你根本不在意提交历史,也不爱管 master 干不干净,那么 --no-ff 其实没什么用。不过,如果某一次 master 出现了问题,你需要回退到上个版本的时候,比如上例,你就会发现退一个版本到了 B,而不是想要的 F,因为 feature 的历史合并进了 master 里。

变基rebase

图解(与分支合并的不同)

当您 rebase 一个分支到另一个分支时,您将第一个分支的提交应用到第二个分支中的 HEAD 提交之上。

假设您创建了一个功能分支来处理特定任务并向该分支进行了多次提交:

当您在分支中开发时,您的队友会继续致力于掌握他们的工作:

当您执行 rebase 操作时,您可以通过将您的提交应用到 master 分支中>:

变基的主要好处是您可以获得清晰的项目历史记录,易于其他人阅读和理解。您的日志不包含 merge 操作生成的不必要的合并提交,并且您将获得易于导航和搜索的线性历史记录。

但是,当决定采用此工作流程时,您应该记住, rebase 会重写项目历史记录,因为它为原始功能分支中的每个提交创建新的提交,因此它们将具有不同的哈希值,这会妨碍可追溯性。

基本语法
1
2
3
# 检出 experiment 分支,然后将它变基到 master 分支上:
git checkout experiment
git rebase master

删除分支

1
2
3
4
5
# 删除分支
git branch -d otherBranch # 通过git branch -d命令可能出现删除不了现象,例如分支a的代码没有合并到主分支等
git branch -D otherBranch # 强制删除
git push origin --delete master # 删除 origin 主机的 master 分支
git branch -dr [origin/master] # 删除 origin 主机的 master 分支

本地仓库的删除分支操作不会影响到远程仓库的分支(即使git push origin -all),远程仓库的分支仍需手动删除。

标签tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看
git tag # 查看标签记录
git show [tag] # 查看tag信息

# 添加标签
git tag v1.0 # 为当前分支的commit添加标签
git tag [tag] [commit] # # 新建一个tag在指定commit

# 提交标签
git push [remote] [tag] # 提交指定tag
git push [remote] --tags # 提交所有tag

# 删除标签
git tag -d [tag] # 删除本地tag
git push origin :refs/tags/[tagName] # 删除远程tag

git checkout v1.0 # 切换到该标签下的代码状态

比较diff

1
2
3
4
5
git diff  # 显示暂存区和工作区的差异
git diff --cached [file] # 显示暂存区和上一个commit的差异
git diff HEAD # 显示工作区与当前分支最新commit之间的差异
git diff [first-branch]...[second-branch] # 显示两次提交之间的差异
git diff --shortstat "@{0 day ago}" # 显示今天你写了多少行代码

储藏stash

使用git的时候,我们往往使用分支(branch)解决任务切换问题,例如,我们往往会建一个自己的分支去修改和调试代码, 如果别人或者自己发现原有的分支上有个不得不修改的bug,我们往往会把完成一半的代码commit提交到本地仓库,然后切换分支去修改bug,改好之后再切换回来。这样的话往往log上会有大量不必要的记录。其实如果我们不想提交完成一半或者不完善的代码,但是却不得不去修改一个紧急Bug,那么使用git stash就可以将你当前未提交到本地(和服务器)的代码推入到Git的栈中,这时候你的工作区间和上一次提交的内容是完全一样的,所以你可以放心的修Bug,等到修完Bug,提交到服务器上后,再使用git stash apply将以前一半的工作应用回来。

1
2
3
4
5
6
7
# 暂时将未提交的变化移除,稍后再移入
git stash push -m "Work in progress" # 储藏(移除)
git stash pop # 移入

git stash # Stash changes
git stash apply # Reapply stashed changes
git stash list # List stashed changes

回滚reset

  1. 找到想要回退到某个版本的版本号:

    1
    2
    git log
    # -> commit auihcbuioahnaoij156651
  2. 找到想要回退的版本号之后,在本地 Git 仓库执行如下命令:

    1
    2
    git reset --hard <版本号>  # 抛弃当前工作区的修改
    git reset --soft <版本号> # 回退到之前的版本,但保留当前工作区的修改,可以重新提交
  3. 同步远端的内容:

    1
    2
    git push origin <分支名>
    git push origin <分支名> --force # 如果提示本地的版本落后于远端的版本
  4. 到这里,我们就可以把本地和远端的代码都回退到某一个指定的版本了。

  5. 如果想要回滚会最新的版本,使用git log命令只可以查看到HEAD指针及其之前的版本信息,如果版本发生过回退操作,则可能会出现,HEAD指针之后仍存在历史提交版本的情况,而这些提交版本信息通过git log命令是看不到的。我们可以通过使用git reflog命令,就可查看到所有历史版本信息。

    1
    git reflog
  6. 之后的操作与第2步开始相同。

撤销修改

方法一:如果知道要修改回的内容的话,可以直接手动更改那些需要的文件。然后git add添加到暂存区,最后git commit掉。

方法二:直接回滚到上一个版本。

方法三:git checkout -- filename命令可以丢弃工作区的修改,也可以用来恢复被删除的文件。

  • 文件修改后,还没有使用git add添加到暂存区:使用该命令后本地文件变回与版本库中一样的状态(内容)。
  • 文件已经使用git add添加到暂存区,之后又进行了修改:使用该命令后本地文件变回与添加暂存区后的状态(内容)。

删除rm

git rm命令从工作目录中删除文件并为下一次提交暂存删除。这对于从存储库中删除不需要的文件非常有用。

1
2
git rm file.txt  # Remove a file
git rm -r directory/ # Remove a directory

检查fsck

1
git fsck  # 检查Git仓库的完整性

追溯blame

  • 追溯一个指定文件的历史修改记录
  • 显示文件每一行的最后修改版本和作者

从远程获取代码库fetch

pull和fetch的区别

pull命令是从远程库获取最新代码并自动合并到本地分支的快捷方式。它实际上包含了两个操作:fetchmerge

如果当前分支和要合并的分支存在冲突,Git会要求我们解决冲突并手动执行合并操作。

fetch命令只是从远程库下载最新代码,但并不自动合并到本地分支。它只是将最新代码下载到本地仓库的一个特殊的分支,称为远程跟踪分支。这样我们可以查看最新代码的变动,然后决定是否需要合并到当前分支。

将指定的提交(commit)应用于其他分支cherry-pick

git cherry-pick命令将特定提交引入的更改应用于当前分支。这对于在分支之间移植错误修复或功能非常有用。

1
git cherry-pick abc1234  # Apply changes from commit abc1234

设置别名alias

简化命令输入:Git 进阶之「设置别名」

1
git config --global alias.psm 'push origin master'  -> git psm

发布

生成一个可供发布的压缩包

1
git archive

Change log

如果你的所有Commit都符合Angular格式,那么发布新版本时,Change log就可以用脚本自动生成(例1例2例3)。

生成的文档包括以下三个部分:

  • New features(新特性)
  • Bug fixes(bug修复)
  • Breaking changes(重大变更)

每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。

如果typefeatfix,则该 commit 将肯定出现在 Change log 之中。其他情况(docschorestylerefactortest)由你决定,要不要放入 Change log,建议是不要。

如果当前 commit 与被撤销的 commit,在同一个发布(release)里面,那么它们都不会出现在 Change log 里面。如果两者在不同的发布,那么当前 commit,会出现在 Change log 的Reverts小标题下面。

conventional-changelog 就是生成 Change log 的工具,运行下面的命令即可。

1
2
3
npm install -g conventional-changelog
cd my-project
conventional-changelog -p angular -i CHANGELOG.md -w

上面命令不会覆盖以前的 Change log,只会在CHANGELOG.md的头部加上自从上次发布以来的变动。

如果你想生成所有发布的 Change log,要改为运行下面的命令:

1
conventional-changelog -p angular -i CHANGELOG.md -w -r 0

为了方便使用,可以将其写入package.jsonscripts字段:

1
2
3
4
5
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0"
}
}

以后,直接运行下面的命令即可:

1
npm run changelog

指定开源许可证

详述 GitHub 中声明 LICENSE 的方法

将代码从原分支合并到自己的fork分支

详述 GitHub 如何将代码从原分支合并到 fork 分支

将本地Git仓库从一块硬盘移动到另一块硬盘

  1. 确保当前的工作目录没有未提交的更改:

    1
    git status
  2. 备份你的Git仓库。在进行移动之前,首先应该备份你的Git仓库,以防止意外数据丢失。可以通过以下命令将仓库备份到另一个位置:

    1
    cp -R /path/to/old/repository /path/to/backup/location
  3. 克隆一个临时仓库。在新硬盘上创建一个临时目录,并通过以下命令克隆原始仓库到临时目录中:

    1
    git clone /path/to/old/repository /path/to/new/temp/repository
  4. 检查新仓库是否正常工作。在新硬盘上创建一个临时目录,并通过以下命令克隆原始仓库到临时目录中:

    1
    2
    cd /path/to/new/temp/repository
    git status

    确保没有任何错误或冲突。如果有错误,需要解决它们,然后再继续下一步。

  5. 将本地分支推送到新仓库:

    1
    2
    3
    cd /path/to/new/temp/repository
    git remote add new-origin /path/to/new/repository
    git push new-origin --all

    这将把所有本地分支以及相关的提交推送到新仓库中。

  6. 更新远程仓库地址。切换到原始仓库中,并更新远程仓库的地址为新仓库的地址:

    1
    2
    cd /path/to/old/repository
    git remote set-url origin /path/to/new/repository
  7. 测试新仓库是否正常工作:

    1
    2
    cd /path/to/new/repository
    git status

    检查是否有错误或冲突。如果一切正常,那么新仓库就已经成功迁移了。

  8. 删除临时仓库。现在你可以删除临时目录,因为新仓库已经在新硬盘上建立并正常工作了:

    1
    rm -rf /path/to/new/temp/repository
  9. 大功告成。

在两台机器间同步代码

1
2
3
4
5
# 关键操作
# 关联远程仓库
git remote add origin //IP地址/共享文件夹名称
git remote add origin ssh://服务器名@服务器IP地址/服务器git仓库路径
git clone ssh://<username>@<ip>:<port>:/codes/project-bare/project-bare.git ./project

删除当前目录下的 Git 仓库

要删除当前目录下的 Git 仓库,你实际上需要删除这个目录中的 .git 文件夹。这个 .git 文件夹包含了所有 Git 相关的跟踪信息和对象数据库,一旦删除,这个目录就不再是一个 Git 仓库了。

这是一个不可逆操作,将会丧失所有版本控制信息。在执行此操作前,请确保确实希望删除所有与 Git 仓库相关的数据,或已经做好了相应的备份。

在 Linux 或 macOS 终端中,你可以使用以下命令来删除 .git 文件夹:

1
rm -rf .git

在 Windows 命令行中,你可以使用以下命令:

1
rmdir /s /q .git

这些命令将递归地删除 .git 目录及其所有内容,从而永久地从你的工作目录中移除 Git 版本控制。

执行完毕后,当前目录就不再是一个 Git 仓库,所有版本控制历史都会被删除。如果你的项目中还有其他重要的文件或数据,它们不会被这个操作影响。

问题与解决

只修改了一个文件,git却显示所有文件被修改了?

原因:不同操作系统使用的换行符是不一样的。Unix/Linux使用的是LF,Mac后期也采用了LF,但Windows一直使用CRLF【回车(CR, ASCII 13, r) 换行(LF, ASCII 10, n)】作为换行符。而git入库的代码采用的是LF格式,它考虑到了跨平台协作的场景,提供了“换行符自动转换”的功能。在Windows下使用git,在拉取文件时,会自动将LF换行符替换为CRLF;在提交时,又会将CRLF转回LF。就是这个转换有问题的。

解决:对git进行全局配置:

1
2
3
git config --global core.autocrlf false
git config --global core.filemode false
git config --global core.safecrlf true

git fsck命令检测到缺少的 blob 对象

git fsck命令检测到缺少的 blob 对象时,这通常说明你的 Git 仓库数据有损坏。Blob 对象是 Git 用来存储文件内容的数据结构。在这种情况下,一些文件的内容丢失了,这可能是由于磁盘错误、Git 操作错误或其他系统错误导致的。

要解决这个问题,你可以尝试以下几个步骤:

  1. 从其他克隆恢复缺失的对象。

    如果你有这个仓库的其他克隆(比如在其他机器上或者同事的机器上),尝试在那些仓库中找到丢失的 blobs。你可以在其他克隆的仓库上运行以下命令来检查是否存在缺失的 blob:

    1
    git cat-file -t <blob-sha1>

    如果命令有输出(通常是 blob),那么你可以从这个克隆中复制丢失的对象到你的仓库中:

    1
    2
    3
    4
    5
    # 在有完整对象的仓库中运行
    git cat-file -p <blob-sha1> > filename

    # 然后将这个文件移动到损坏仓库的相应目录中
    cp filename /path/to/broken-repository/.git/objects/b0/
  2. 检查备份。

    如果你的仓库定期备份,检查备份是否可以用来恢复丢失的数据。

  3. 克隆新的副本并重新应用更改。

    如果上述方法都无法恢复数据,最简单的方法可能是重新克隆仓库的一个新副本(如果远程仓库没有损坏的话):

    1
    git clone <repository-url>

    然后,如果你本地有些未推送的更改或者新的提交,尝试手动将这些更改重新应用到新克隆的仓库中。

  4. 使用 git-refloggit reset

    如果损坏是最近发生的,你可以尝试用 git reflog 查找到损坏前的一个好的状态,然后用 git reset --hard <good-commit-sha1> 来恢复到那个状态。

利用这些方式,你应该能恢复大部分数据,或者至少能找到足够的信息重建你的项目。处理完后不要忘记重新执行 git fsck 来确保所有问题都解决了。

CLion中的Git

相关操作

官方文档

在“文件-设置-版本控制-Git-Git可执行文件路径“中配置Git的安装路径(自动检测?)。

在“文件-设置-版本控制-文件状态颜色”中定义了多种目录中文件状态的颜色。

启用Git

  1. 在主菜单中,转至 VCS |启用版本控制集成。
  2. 在打开的对话框中,从可用版本控制系统列表中选择 Git,然后单击“确定”。或者,按 Alt+` 并选择“创建 Git 存储库”(或按 1 )。在打开的 Finder 窗口中,指定本地 Git 存储库的根文件夹。
  3. 您将收到一条通知,表明已为您的项目创建本地 Git 存储库。工具栏和状态栏上会出现Git相关的控件。
  4. 从左侧提交工具窗口 Alt+0 中,您可以查看本地更改并将其提交到本地 Git 存储库。在 下方 Git 工具窗口Alt+9中,您可以使用 Git 日志、管理来自 GitHub 的拉取请求等。

将文件添加到.gitignore

  1. 在“提交”工具窗口 Alt+0 的“本地更改”选项卡上,您会看到属于您的项目的文件列表。这些文件尚未添加到 Git 存储库 - 您需要选择要共享哪些文件以及 VCS 应忽略哪些文件。
  2. 按目录对文件进行分组:按 Ctrl+Alt+P或单击工具栏上的"四个小方块"并选择“目录”。
  3. 选择您不想共享的目录。例如,可以忽略以下目录,而不会破坏项目的完整性:
    • .idea:本地 CLion 安装的设置。除非您想在团队成员之间共享您的设置,否则请忽略此目录。
    • cmake-build-debug:为 CMake 构建工件自动创建的目录。
  4. 右键单击所选内容并选择添加到 .gitignore |添加到 .gitignore。
  5. 系统将提示您确认在项目的根目录中创建新的 .gitignore 文件。单击“创建”。
  6. 在打开的对话框中,您可以按“添加”立即将新创建的文件添加到 Git,也可以按“取消”推迟此操作。单击“添加”。您将看到 .gitignore 文件已添加到项目的根目录并放置到 Changes 区域。您选择忽略的目录不再显示在“无版本控制文件”列表中。
  7. 您可以随时编辑 .gitignore 文件以将新目录添加到列表中或删除现有目录。

提交并推送更改

现在,当所有不必要的目录都从未版本控制的文件列表中排除时,您只需将所有文件添加到存储库并提交它们以保存其当前状态。

基本用法

  1. 在“提交”工具窗口 Alt+0中,使用拖放操作将所有文件从“未版本化文件”列表移动到“更改”。通过单击根文件夹复选框选择(所有)文件(git add)。

    在提交工具窗口中,您还可以预览添加和修改的文件、使用高级提交选项、在提交中添加和排除文件等。

  2. 在“提交”工具窗口的下方“提交信息”处为您的提交输入提交信息(git commit)。

  3. 单击“提交”。执行提交后会出现相应的通知。

  4. 按 Ctrl+Shift+K或选择 Git |推送Push 可从当前分支将更改推送到远程存储库。 “推送提交”对话框打开。在这里您可以看到所有要推送的提交以及所有受影响的文件。在推送更改之前,您可以看到每个文件的差异。为此,请右键单击文件并选择“显示差异”或按 Ctrl+D。

  5. 单击“推送Push”。

    要恢复推送的提交,请在 Git 工具窗口Alt+9的“日志”选项卡上右键单击该提交,然后选择“恢复提交”。详情

    要撤消最新未推送的提交,请在 Git 工具窗口的日志选项卡Alt+9上右键单击它,然后选择“撤消提交”。详情

选择性提交

此操作允许您将选定提交的更改从一个分支应用到另一分支。

  1. 在 master 分支中,进行您要挑选的更改。提交 Ctrl+K 并推送 Ctrl+Shift+K 应用此更改。

  2. 切换到 new_feature 分支。

  3. 在底部 Git 工具窗口 Alt+9 中,打开“日志Log”选项卡。

  4. 在“分支”列表中,选择 master 。

  5. 选择您的最后一次提交并单击窗口顶部右侧的小樱桃图标“cherry-pick”。

  6. 通过在打开的对话框中按“Accept Theirs”来解决冲突。这意味着我们通过精心挑选的提交中的更改来覆盖 main.cpp 文件中的代码。

    如果没有出现冲突,cherry-pick选择将自动提交,您只需推送更改即可。

  7. 从打开的“提交更改Commit Changes”对话框中提交更改并推送它们 Ctrl+Shift+K。

关联远程仓库

为了使您的项目可供其他贡献者使用,您需要将其发布到远程存储库,例如 github.com。 CLion 提供与 GitHub 的集成,允许您管理 GitHub 上托管的项目fork外部存储库管理拉取pull请求以及从 IDE 执行其他 GitHub 操作。详细教程

  1. 在主菜单中,转至 VCS |在 GitHub 上分享项目。

  2. 在打开的对话框中,输入您的 GitHub 登录名和密码,然后单击登录。

    如果您尚未在 GitHub 上注册,请单击“注册 GitHub”以转至 github.com 并在那里创建一个新帐户。

    您可以更改 GitHub 帐户或在“首选项”|“添加新帐户”中添加新帐户。版本控制 | GitHub。请参阅管理多个 GitHub 帐户中的更多信息。

  3. 如果您为 GitHub 启用了双因素身份验证,则会出现对话框,输入代码并单击“确定”。

  4. 在打开的对话框中,您可以更改存储库名称(默认情况下,它与项目名称相同)、远程名称(默认情况下为 origin)、选择存储库类型(公共或私有),然后添加如果需要一些描述。

  5. 单击共享。项目成功发布到GitHub后,会出现通知。

  6. 单击通知中的链接以打开 GitHub 上的存储库。

您可以在“Git 远程”对话框中编辑远程列表。要打开该对话框,请选择 Git |管理远程。

创建一个新分支

例如,当您正在开发新功能并且不希望更改在测试之前进入主(主)分支时,您可能需要创建一个单独的分支。

  1. 按Ctrl+T拉取pull当前分支的最新版本。

  2. 在底部状态栏右侧,您可以看到当前分支 - master 。单击它打开 Git 分支菜单。

  3. 从 Git 分支菜单中,选择新建分支。

  4. 在打开的对话框中,指定分支名称,例如 new_feature ,然后选中“签出分支”复选框以立即切换到新分支。

    如果当前分支中有一些未版本化的更改,则需要在切换到另一个分支之前提交commit、还原revert或搁置shelve它们。教程

  5. 现在您已切换到新创建的分支。

  6. 将对应的分支添加到远程仓库。为此,请在 Git 分支菜单中选择本地分支Local Branches-当前分支名称,然后单击 推送Push。

  7. 在打开的对话框中,单击“推送Push”。新分支将被添加到远程存储库,并将出现在 Git 分支菜单的 远程分支Remote Branches 列表中。

如果对一个分支内容进行了修改,没有进行提交就签出,CLion会提示选择“强制签出”、“智能签出”和“不签出”。

查看更改

  1. 按Ctrl+T拉取pull当前分支的最新版本。

  2. 在工程中添加新文件,例如subfunc.cpp,并修改main.cpp文件。

    在项目工具窗口和编辑器选项卡中,CLion 对文件应用不同的颜色:蓝色表示已修改,绿色表示新添加。此外,在修改文件的装订线区域中,彩色更改标记出现在修改行旁边。

  3. 您可以单击装订线标记来查看详细信息。单击“显示行的差异”图标以在单独的窗口中查看差异。

  4. 转到提交工具窗口Alt+0,立即预览所有更改。双击文件以在编辑器中打开差异视图。详细教程

合并分支并解决冲突

有多种方法可以将更改从一个分支应用到另一个分支,例如合并和重新调整分支、挑选提交、应用单独的更改或文件。详情

如何合并两个分支和择优提交:

  1. 在“分支Branches”弹出窗口(主菜单 Git | 分支Branches)或 Git 工具窗口的“分支Branches”窗格中,选择要将要合并到的目标分支,然后从上下文菜单中选择“签出Checkout”以切换到该分支。

  2. 执行以下操作之一:

    • 如果不需要指定合并选项,请选择要合并到当前分支的分支,然后从子菜单中选择“合并到当前”。从底部右侧 Git Branches 菜单中,选择 new_feature 并单击 Merge into Current。

    • 如果需要指定合并选项,请从主菜单中选择 VCS Git |合并更改以打开“合并”对话框。选择要合并到当前分支的分支,单击“修改选项Modify options”并从以下选项中进行选择:

      • --no-ff(推荐) :在所有情况下都会创建合并提交,即使可以将合并解析为快进。Git合并时--no-ff的作用
      • --ff-only :仅当可以快进时才会解决合并。
      • --squash :将在当前分支之上创建包含所有拉取更改的单个提交。
      • -m :您将能够编辑合并提交的消息。
      • --no-commit :将执行合并,但不会创建合并提交,以便您可以在提交之前检查合并的结果。

      单击合并。

  3. 如果你的工作树是干净的(这意味着你没有未提交的更改),并且你的功能分支和目标分支之间没有发生冲突,Git 将合并这两个分支,并且合并提交将出现在 Git 工具的 Log 选项卡中窗口 Alt+9 :

  4. 由于我们在不同分支中对同一文件进行了更改,因此会出现“冲突”对话框。

  5. 在“冲突”对话框中,您有多个选项来解决冲突:

    • Accept Yours:保留在当前分支中所做的更改。
    • Accept Theirs:应用要合并到当前分支的更改。
    • Merge:在专用对话框中手动解决冲突。
  6. 单击合并Merge。 “合并修订Merge Revisions”对话框打开。

  7. 在此对话框中,您可以通过单击 the Apply changes from the left / the Apply changes from the right 接受更改,通过单击“拒绝更改”图标拒绝更改,然后在结果窗格中键入代码。详情

    语法错误在“合并修订”窗口中突出显示。

  8. 解决冲突,然后单击“应用”。

  9. 通过按 Ctrl+Shift+K或选择 Git | Push 将更改从当前分支推送到远程存储库。

  10. 您可以按Alt+9在底部 Git 工具窗口的 Log 选项卡中看到所有分支中的提交。从这里,您还可以恢复提交、从一个分支到另一个分支选择更改等等。详情

查看历史记录

详情

  • 在编辑器或项目工具窗口中右键单击该文件Alt+1并选择 Git |显示历史。 Git 工具窗口的历史记录选项卡打开。在此选项卡上,您可以查看影响该文件的所有提交,并找出您感兴趣的更改添加到哪个提交中
  • 在这里,您可以查看影响您感兴趣的代码选择的所有提交。在编辑器中,选择要查看其历史记录的代码片段,右键单击所选内容,然后选择 Git |显示选择历史记录。选择历史记录窗口将打开。

git插件

  • GitToolBox:通过附加功能扩展Git,比如行后显示提交历史。设置-版本控制-GitToolBox
  • Git Commit Message Helper:标准化提交信息。设置-其它设置-GitCommitMessageHelper

参考链接


Git的配置与使用
http://zeyulong.com/posts/5b8fd703/
作者
龙泽雨
发布于
2024年5月9日
许可协议