「笔记」Git 个人向笔记

写在前面

面向应用的 Git 个人向杂记。

TL;DR

$ tldr git

git

Distributed version control system.
Some subcommands such as commit, add, branch, checkout, push, etc. have their own usage documentation.
More information: https://git-scm.com/.

 - Execute a Git subcommand:
   git subcommand

 - Execute a Git subcommand on a custom repository root path:
   git -C path/to/repo subcommand

 - Execute a Git subcommand with a given configuration set:
   git -c 'config.key=value' subcommand

 - Display help:
   git --help

 - Display help for a specific subcommand (like clone, add, push, log, etc.):
   git help subcommand

 - Display version:
   git --version

原理

文件的状态变化周期

分支

Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 master 分支会在每次提交时自动向前移动。

Git 的 master 分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为 git init 命令默认创建它,并且大多数人都懒得去改动它。

有一个特殊指针 HEAD,指向当前所在的分支,代表当前工作目录的状态。HEAD 分支随着提交操作自动向前移动,检出时 HEAD 随之移动。

远程分支、远程跟踪分支、(本地)跟踪分支

特性维度 远程分支 (Remote Branch) 远程跟踪分支 (Remote-Tracking Branch) (本地)跟踪分支 (Tracking Branch)
存储位置 远程仓库服务器 (如 GitHub) 本地仓库 (位于 .git 目录) 本地仓库
本质 远程仓库中的实际分支 远程分支在本地只读快照书签 与特定远程跟踪分支关联的可写本地分支
命名格式 main, dev <remote>/<branch> (如 origin/main) 任意本地分支名 (如 my-feature)
可写性 可写 (通过推送) 只读 (Git自动更新) 可读可写
更新方式 团队成员通过 git push 更新 执行 git fetchgit pullgit pushGit自动更新 用户通过 git commit 等操作更新
主要作用 团队共享代码、集成工作的中心点 忠实记录上次与远程通信时远程分支的状态 本地开发的载体,与特定远程分支保持同步

简单来说:

  • 通过 git fetch将远程分支的最新状态“同步”到本地的远程跟踪分支;
  • 在本地跟踪分支上,通过 git merge或 git rebase将远程跟踪分支的更新“整合”到你的工作中;
  • 完成本地开发后,再通过 git push将你的工作“发布”回远程分支。

clone 时,对于远程仓库的每一个分支,Git 会在本地的 refs/remotes/origin/ 命名空间下创建一个对应的远程跟踪分支(如 origin/main, origin/dev)。

远程跟踪分支在本地仓库中默认是只读的,无法直接在其上进行提交。只要保持不与 origin 服务器连接(并拉取数据),你本地的 origin/master 指针就不会移动。可以添加多个远程仓库,则会有多个远程跟踪分支。

(本地)跟踪分支:

从一个远程跟踪分支 checkout 一个本地分支会自动创建所谓的“跟踪分支”(它跟踪的分支叫做“上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支,因为已知跟踪了哪个上游分支,在跟踪分支上可以简化 git pull、git push 的写法。

clone 时,Git 会读取远程仓库当前活跃的分支(通常是 main或 master),然后以此远程跟踪分支(origin/master)为基准,在本地创建一个同名的本地分支(master),并使其自动跟踪这个远程跟踪分支。最后,它将这个本地分支的文件状态检出到你的工作目录中,这样你看到的就是该项目最新版本的文件。

核心作用 具体说明 实际场景举例
简化推送/拉取命令 设置跟踪后,只需简单的 git pushgit pull,无需每次手动指定远程仓库和分支名。 未设置跟踪:git push origin main
设置跟踪后:git push
自动同步状态对比 Git 能清晰显示你的本地分支是“领先”还是“落后”于其跟踪的远程分支,方便你判断是否需要推送或拉取。 使用 git statusgit branch -vv 可看到类似提示:main [领先 origin/main 1 个提交]
规范团队协作流程 为团队协作提供了清晰的分支同步基准,确保大家都基于正确的远程分支进行开发,减少混乱。 新成员克隆仓库后,本地 main 分支会自动跟踪 origin/main,可以直接开始协作

Git 的树结构

Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 快照 。在进行提交操作时,Git 会保存一个提交对象(commit object)。除了包含提交信息外,还包含指向这个树对象(项目根目录)的指针。

例:三个 blob 对象(保存着文件快照)、一个 树 对象 (记录着目录结构和 blob 对象索引)以及一个 提交 对象(包含着指向前述树对象的指针和所有提交信息)。

做修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。

基础

忽略文件 .gitignore

修改 .gitignore

GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表, 你可以在 https://github.com/github/gitignore 找到它。

git status

查看已被暂存的修改或未被暂存的修改,仅查看哪些文件被修改

显示你的分支跟踪的是哪个远程分支,以及是领先、落后还是最新。

精简输出:git status -s,左栏指明了暂存区的状态,右栏指明了工作区的状态。

git diff

查看已被暂存的修改或未被暂存的修改的具体内容,显示文件中实际增加或删除的代码行。

git diff(不带参数)查看尚未暂存​的修改的具体内容。

git diff --staged(或 --cached

  • 查看​​所有已暂存文件​​的变更详情。

--check

  • 检查空白错误(行尾空格​、行首缩进问题​等)。

git add [文件]

开始跟踪一个文件,将该文件的当前状态放到暂存区。

可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。

将这个命令理解为“精确地将内容添加到下一次提交中”

git commit

提交放在暂存区域的快照,并提交记录到仓库中。

-m

  • 提交信息与命令放在同一行。
  • 例:$ git commit -m "Story 182: Fix benchmarks for speed"

-a

  • 把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤。

--amend

  • 修正上一次提交,新的提交的内容将覆盖原来的提交信息。
  • 最终只会有一个提交——第二次提交将代替第一次提交的结果,第一次提交不会出现在仓库的历史中。

git rm [文件]

从版本控制中移除文件。

无参数:

  • 从工作区和暂存区​​同时删除​​文件,并将此删除操作放入暂存区。
  • 若工作区相较暂存区有未提交的修改,则无法删除。

-r

  • 递归删除整个目录。

--cached

  • 仅从暂存区移除(停止追踪),但​​保留工作区中的本地文件。​​
  • 通常还需要将这个文件名添加到 .gitignore 文件中,以防止再次误提交。

-f

  • 强制删除文件,即使该文件有未提交的修改。

git mv [原路径] [新路径]

用于​​移动或重命名文件、目录​​,命令执行后,更改只存在于​​暂存区。

重命名:git mv README.md README

移动文件到目录:

  • git mv <源路径> ... <目标目录>
  • 最后一个参数必须是已存在的目录。
  • 例:移动多个文件:git mv file1.txt file2.txt target_directory/

一条 mv 命令等价于以下三条命令:

$ mv README.md README
$ git rm README.md
$ git add README

git log

无参:

  • 按时间先后顺序列出所有的提交,最近的更新排在最上面。

-[数字]

  • 显示最近的[数字]次提交。

-p--patch

  • 显示每次提交所引入的差异(按补丁的格式输出)。

--stat

  • 简略统计信息。

--pretty

  • 让输出更好看。
  • =format:格式化输出
$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 6 years ago : changed the version number
085bb3b - Scott Chacon, 6 years ago : removed unnecessary test
a11bef0 - Scott Chacon, 6 years ago : first commit

--graph

  • 形象地展示你的分支、合并历史。
$ git log --pretty=format:"%h %s" --graph
* 2d3acf9 ignore errors from SIGCHLD on trap
*  5e3ee11 Merge branch 'master' of git://github.com/dustin/grit
|\
| * 420eac9 Added a method for getting the current branch.
* | 30e367c timeout code and tests
* | 5a09431 add timeout protection to grit
* | e1193f8 support for heads with slashes in them
|/
* d6016bc require time for xmlschema
*  11d191e Merge branch 'defunkt' into local

--decorate

  • 查看各个分支当前所指的对象。

可以限制展示的提交:

  • --since --after--until--before:时间起止。
  • --author--committer:匹配展示的提交的作者、提交者。
  • --grep:匹配展示的提交的说明。
  • -S [字符串]:显示那些添加或删除了该字符串的提交。

git tag

无参:

  • 列出已有的标签。

-l--list

  • 按照通配符列出标签。

-a [标签名]

  • 添加标签,然后会打开 editor 输入 tag 的消息。
  • 同时指定 -m [消息字符串]:顺便加上一条消息。
  • 对过去的提交打标签:命令的末尾指定提交的校验和(或部分校验和)。

不加 -a-m,直接输入 [标签名]

  • 轻量标签,show 时不会看到额外的标签信息,只会显示出提交信息。

-d <tagname>

  • 删除标签。
  • 并不会从任何远程仓库中移除这个标签。
  • git push <remote> :refs/tags/<tagname>git push origin --delete <tagname> 更新远程仓库。

git show

查看所有标签信息和与之对应的提交信息。

参数 [标签]

  • 限定某个标签。

Git 别名

通过 git config 文件来为每一个命令设置一个别名。

如:$ git config --global alias.co checkout,则 git co 等价于 git checkout

执行外部命令:在命令前面加入 ! 符号,如将 git visual 定义为 gitk 的别名:$ git config --global alias.visual '!gitk'

Git 修订版本选择

参考:https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E9%80%89%E6%8B%A9%E4%BF%AE%E8%AE%A2%E7%89%88%E6%9C%AC

AI 总结如下:

语法类型 语法 说明 示例(假设在 git log 命令中)
单个提交
完整的 SHA-1 使用提交的 40 位完整哈希值。最精确、无歧义。 git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
简短的 SHA-1 使用提交哈希的前几位(至少4位,且不能产生歧义)。 git show 1c002d
分支名 指向该分支顶端的最新提交。 git show maingit show topic-branch
HEAD 指向当前工作目录所在的提交。 git show HEAD
引用日志 (仅限本地记录) 记录 HEAD 和分支的最近移动历史。
HEAD@{n} 查看 HEAD 在 n 次操作前指向的提交。 git show HEAD@{5} (查看 5 次操作前的提交)
<branchname>@{time} 查看某个分支在指定时间前指向的提交。 git show main@{yesterday} (查看昨天 main 分支的顶端)
祖先引用 基于提交的父级关系向上追溯历史。
^ (脱字符) 指向指定提交的第一父提交 git show HEAD^ (查看 HEAD 的父提交)
^n 在合并提交中,指向第 n 个父提交。合并提交的第一父提交是合并时所在分支,第二父提交是被合并的分支。 git show <merge-commit>^2 (查看合并提交的第二个父提交)
~ (波浪号) 指向指定提交的第一父提交,与 ^ 等价。 git show HEAD~ (与 HEAD^ 等价)
~n 向上追溯 n 代的第一父提交 git show HEAD~3 (查看祖父提交的父提交,即向上 3 代)
^~ 组合 可以灵活组合使用,精确定位复杂关系。 git show HEAD~3^2 (查看 HEAD 的第三父提交的第二父提交)
提交区间 用于比较两个或多个引用之间的提交差异。
双点 A..B 选择在引用 B 中但不在引用 A 中的提交。常用语:“B 上有哪些提交还没合并到 A?” git log main..experiment (查看 experiment 分支独有的提交)
git log origin/main..HEAD (查看本地有而远程仓库没有的提交,即即将推送的提交)
多点 ^A BB --not A A..B 功能相同,但可以排除多个引用。 git log ^main experiment (同 main..experiment)
选择被 B 包含,但不被 A 和 C 包含的提交。 git log main experiment ^hotfix (查看在 main 或 experiment 中,但不在 hotfix 中的提交)
三点 A...B 选择在 A 或 B 中,但不同时在两者中的提交。即两边的差异合集。 git log main...experiment (查看 main 和 experiment 分支的差异)
--left-right 参数 与三点语法结合使用,标记每个提交属于哪一侧。< 表示左侧引用,> 表示右侧引用。 git log --left-right main...experiment

Windows 终端转义:在 Windows 的 cmd.exe 中,^ 是特殊字符,需要双写或使用引号:
* git show HEAD^ (不工作)
* git show HEAD^^ (正确)
* git show "HEAD^" (正确)

留空默认为 HEAD:在双点语法中,如果留空一边,Git 默认使用 HEAD。例如 git log origin/main.. 等价于 git log origin/main..HEAD

远程仓库

git remote

查看已经配置的远程仓库服务器。

-v

  • 显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL。

git remote add <shortname> <url>

  • 添加一个新的远程 Git 仓库 <url>,同时指定一个方便使用的简写 <shortname>

git remote rename

  • 来修改一个远程仓库的简写名

git remote remove(或 rm):

  • 移除一个远程仓库。

git remote show <remote>

  • 列出远程仓库的 URL 与跟踪分支的信息:
    • 当你在特定的分支上执行 git push 会自动地推送到哪一个远程分支;
    • 哪些远程分支不在你的本地,哪些远程分支已经从服务器上移除了;
    • 当你执行 git pull 时,哪些本地分支可以与它跟踪的远程分支自动合并。

git clone

从现有的 Git 仓库 完整地复制其整个版本历史和数据,并在本地创建一个独立的、功能齐全的新仓库副本。

对于远程仓库的每一个分支,Git 会在本地的 refs/remotes/origin/ 命名空间下创建一个对应的远程跟踪分支(如 origin/main, origin/dev)。

然后,Git 会读取远程仓库当前活跃的分支(通常是 main 或 master),然后以此远程跟踪分支(origin/master)为基准,在本地创建一个同名的本地分支(master),并使其自动跟踪这个远程跟踪分支。

最后,它将这个本地分支的文件状态检出到你的工作目录中,这样你看到的就是该项目最新版本的文件。

--bare

  • 仅下载Git数据库(.git目录),不创建工作目录。

--depth={n}

  • 仅下载最近 \(n\) 次提交的历史。

git fetch

与远程仓库 同步数据,从中抓取本地没有的数据,并更新本地数据库,移动或创建本地的远程跟踪分支指针到更新之后的位置。

确保了本地仓库的数据库与远程仓库保持同步,但不改变当前工作目录文件、暂存区及本地分支的指针。

在之后可以手动将远程跟踪分支 merge 或 rebase 到任意本地分支。

git pull

相当于 fetch + merge 或 rebase(默认是 merge 模式,可通过参数 --rebase 切换)。

查找当前分支所跟踪的服务器与远程分支,从服务器上抓取数据,在完成 fetch 操作后,直接更新当前所在的本地分支

将直接改变工作目录文件、暂存区,并移动本地分支指针。安全性较低,自动合并可能引入未知问题或冲突。

默认是 merge 模式,即会产生一个合并提交(Merge Commit)。

--rebase

  • rebase 模式。
  • 重新应用本地提交到更新后的远程分支顶端,形成线性的提交历史。

git push

git push <remote> <branch>

  • 推送仓库当前中分支 的提交记录到远程服务器 的同名分支。
  • 想推送到不同名分支,如:git push origin serverfix:awesomebranch 来将本地的 serverfix 分支推送到远程仓库上的 awesomebranch 分支。

当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。

当与其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会被拒绝,必须先抓取他们的工作,并将其合并进你的工作后才能推送。

git push origin <tagname>

  • 推送标签。

--tags

  • 一次性推送很多标签。

--delete

  • 删除一个指定的远程分支
  • git push origin --delete serverfix,删除远程分支 serverfix

git push <remote> :refs/tags/<tagname>git push origin --delete <tagname>

  • 删除远程仓库的 tag。

分支

git branch <branch_name>

创建新分支,指向当前 HEAD 所在的提交对象,但是不切换过去。

Git 是怎么创建新分支本质上仅仅是:创建了一个可以移动的新的指针,所以很快。

git branch:

  • 无参数,输出当前所有分支的一个列表。
    • 字符:代表现在检出的那一个分支(也就是说,当前 HEAD 指针所指向的分支)。
  • 参数--merged--no-merged
    • 可以过滤这个列表中已经合并或尚未合并到当前分支的分支。
    • 可以提供一个附加的参数来查看其它分支的合并状态而不必先检出它们。 例如:git branch --no-merged master

-d:删除分支,包含了还未合并的工作时失败。

-D:强制删除分支。

-u--set-upstream-to

  • 设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支。
  • 设置好后,可以通过简写 @{upstream}@{u} 来引用它的上游分支。

-vv

  • 查看设置的所有跟踪分支。
  • 数据来源于于本地缓存的服务器数据,如果要查看最新的应该先 git fetch --all

git checkout <branch_name>

换到一个已存在的其他分支,即令 HEAD 指向其他分支。

工作目录将恢复成其他分支所指向的快照内容,即在该分支上最后一次提交的状态。

git checkout -b <newbranchname>

  • 创建一个新分支后立即切换过去。

--track

  • 创建跟踪分支并切换过去。
  • 如:git checkout --track <remote>/<branch>,创建远程分支 / 的同名跟踪分支。
  • 相当于 git checkout -b <branch> <remote>/<branch>
  • 想要不同名,就 git checkout -b <other_branch_name> <remote>/<branch>

git merge

将其他分支 指向的提交合并到当前分支中。

若想要合并的分支指向的提交是所在的提交的直接后继,则显然无冲突,只会简单的将指针向前推进(指针右移)——这叫做 “快进(fast-forward)”。

否则,将寻找其他分支与当前分支的最近公共祖先提交,然后基于最近公共祖先进行三方合并,结果是生成了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。

若产生合并冲突,即两个分支的修改都涉及到同一个文件的同一处,则需要解决合并产生的冲突。可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并。

$ git merge iss53 合并前

$ git merge iss53 合并后

因包含合并冲突而有待解决的文件,会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。 出现冲突的文件会包含一些特殊区段,看起来像下面这个样子:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

<<<<<<<=======>>>>>>> 这些行是文件有冲突的标记,若文件有这些标记 Git 就会认为该文件有冲突。

上半部分是 HEAD 指向的提交(即被合入的分支),下半部分是被合入的分支,你必须选择使用由 ======= 分割的两部分中的一个,仅保留了其中一个分支的修改,或者可以自行合并这些内容。

在你解决了所有文件里的冲突之后(删除了所有 <<<<<<<=======>>>>>>> 行),就可以再对每个文件使用 git add 命令来将其标记为冲突已解决,然后暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。然后就可以运行 git commit 来完成合并提交。此时会有默认情况下的提交信息,格式如下:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

git rebase

将提交到目标分支 上的所有修改都移至当前分支上。

原理是:首先找到当前分支与 的最近共同祖先提交,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标分支 指向的提交, 最后以此将之前另存为临时文件的修改依序应用。

下图为如下命令的图例:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

$ git rebase master

$ git rebase master

和 merge 同样要处理冲突,产生的结果提交与 merge 得到的合并提交是一样的。只是得到的提交历史结构不同。

一般使用 rebase 的目的是:确保在向远程分支推送时,保持提交历史的整洁。在这种情况下,一般首先在自己的分支里进行开发,开发完成时你需要先将你的代码变基到 origin/master 上,然后再向主项目提交修改。这样的话,该项目的维护者就不再需要进行整合工作,只需要在远程分支上快进合并便可。

简写写法:

  • git rebase <target_branch> <branch>
  • 变基到 <target_branch> 上;
  • 省得先 git checkout <target_branch> 了。

--onto

  • 对于三个分支,想将第一个分支变基到第二个分支,但是不想涉及第三个分支的修改。
  • 如:$ git rebase --onto master server client:中在 client 分支里但不在 server 分支里的修改(即 C8 和 C9),变基到 master 分支上。
  • 图例详见 https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA
  • 本质上是先找到三者的最近公共祖先,然后对比第一个分支相对于该祖先的历次提交,然后变基到第二个分支上。

变基时应注意:

如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。

如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。

reset, revert

用于撤销当前记录到祖先,或

git reset 通过把分支记录回退几个提交记录来实现撤销改动。git reset 将当前分支向上移动,原来指向的记录消失。

git reset 对共享远程分支无效,为了撤销更改并分享给别人,需要使用 git revert,revert 提交记录后,会在当前分支下创建该提交记录的副本。

cherry-pick

用于在当前分支下按序添加若干其他提交记录。

多人协作

Git - 向一个项目贡献:https://git-scm.com/book/zh/v2/%E5%88%86%E5%B8%83%E5%BC%8F-Git-%E5%90%91%E4%B8%80%E4%B8%AA%E9%A1%B9%E7%9B%AE%E8%B4%A1%E7%8C%AE

可供参考的工作流【十分钟学会正确的github工作流,和开源作者们使用同一套流程】 - bilibili:https://www.bilibili.com/video/BV19e4y1q7JJ

Github

工作流:6.2 GitHub - 对项目做出贡献 - gitbook

写在最后

参考:

命运多舛,造化弄人啊。

posted @ 2025-06-10 10:30  Luckyblock  阅读(82)  评论(0)    收藏  举报