咖啡图片
正在将巧克力泡入咖啡
ntainer" style="display: none">
文章

git和github相关记录

持续记录 git 和 github 使用过程中,之前没有注意过的点

git和github相关记录

初始化配置

初始化配置包括,下载 git、注册 github、配置 user 信息和代理,配置之后的信息会保存在.gitconfig 文件中。

配置命令:

1
2
3
4
git config --global user.name "your_name"
git config --global user.email "your_email"
git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy http://127.0.0.1:7890

127.0.0.1:7890 是 Clash 在本机开放的代理入口,不是 GitHub 的地址,也不是远程代理节点的地址。这个代理配置的意思是,告诉 Git 访问 HTTP/HTTPS 远程仓库时,把网络请求先交给本机 Clash。如果设置代理后关闭 Clash,Git 仍会尝试连接 127.0.0.1:7890,这时由于 Git 已经被固定配置为必须经过这个本地代理,往往直接失败。SSH 使用另一套网络程序和协议,和这个配置无关。

git 工作流示意

这张图是 Git 最经典的一张工作流示意图,它描述了代码从你修改到提交到远程仓库的整个生命周期,以及几个最常用命令分别作用在哪一步。

协作开发的一种工作流如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 首先将协作仓库 fork 一份到自己名下

git clone <my_repo_url> # 克隆自己名下的仓库到本地
git remote add upstream <cooperate_repo_url> # 添加协作的远程仓库
git fetch upstream # 获取协作仓库的最新修改

# 当前在 main 分支上维护,协作仓库更新后,需要同步到自己仓库
git fetch upstream
git switch main
git merge upstream/main
git push origin main

# 当前在 featire 分支上开发新功能,协作仓库更新后,需要同步到自己仓库
git fetch upstream
git switch feature
git rebase upstream/main
git push --force-with-lease origin feature

另外,建议 main 分支尽量保持和协作仓库一致,自己的提交都放在 feature 分支,这样同步最干净,提 PR 时冲突也少。

拓展阅读:团队开发中的 GitFlow 工作流

常用 git 命令

  • git init
  • git add
  • git commit
  • git remote add
  • git clone
  • git branch
  • git switch
  • git log
  • git reflog
  • git status
  • git diff
  • git blame
  • git restore
  • git reset
  • git revert
  • git fetch
  • git merge
  • git rebase
  • git cherry-pick
  • git stash
  • git pull
  • git push
  • git worktree
  • git bisect

branch

分支本质上是对提交的一种会自动移动的标签。

1
2
3
git branch -v # 查看所有本地分支
git branch -r # 查看所有本地保存的远程跟踪分支
git branch -a # 查看所有本地和本地保存的远程跟踪分支

创建分支的命令如下,默认从 HEAD 创建

1
2
3
4
5
git branch <新分支名> # 只创建不切换
git switch -c <新分支名> # 创建并切换,现代 Git 推荐的方式
git checkout -c <新分支名> # 创建并切换,旧写法
git switch -c <新分支名> master # 从指定分支创建
git switch -c <新分支名> abc123 # 从指定提交创建

新分支默认只存在于本地,第一次推送时会在远程创建同名分支。

删除分支的命令如下

1
2
git push origin --delete feature # 删除远程分支
git branch -d feature # 删除本地分支

tag

tag 是 Git 中的固定标签,不会随着新提交而自动向前移动,通常用来给某个重要提交起一个容易记住的名字。

1
2
3
git tag v1.0.0 # 给当前提交创建标签
git tag v1.0.0 abc123 # 给指定提交创建标签
git tag -d v1.0.0 # 删除本地标签

创建标签后,默认只存在于本地,如果需要让远程仓库上也存在,同样需要推送。

1
2
3
git push origin v1.0.0 # 推送指定标签
git push origin --tags # 推送所有本地标签
git push origin --delete v1.0.0 # 删除远程标签

commit amend

git commit 可以加–amend 参数,表示用当前暂存区和新的提交信息,重新生成最近一次提交,替换原来的提交。用于已经 git commit 之后

1
2
3
4
5
git commit --amend # 打开编辑器修改提交信息
git commit --amend -m "正确的提交信息" # 直接指定新信息

git add test.py
git commit --amend --no-edit # 不修改提交信息,用于补充忘记提交的文件

另外,amend 只能修订当前 HEAD,修改更早的提交需要交互式 rebase。

checkout 和 switch

这俩的效果都是,让 HEAD 指向选定的分支或提交,并把暂存区和工作目录切换到该分支对应的版本。checkout 和 switch 操作都是在本地已有的分支和提交之间切换,是纯本地操作;clone、fetch、pull 等操作才涉及从远程获取内容。

对于一个已经存在的 master 分支:git checkout mastergit switch master 的效果完全相同,都是让 HEAD 指向 master,并把暂存区和工作目录切换到该分支对应的版本。两者区别在于命令职责,git switch 是专用的分支切换命令,而 git checkout 除了切换分支外,还可以恢复文件,例如 git checkout -- file.txt 的效果是将 file.txt 恢复到暂存区的状态,git checkout abc123 -- file.txt 的效果是将 file.txt 恢复到 abc123 这次提交的状态。而现代 Git 专用的恢复文件命令是 git restore。另外,git checkout <提交哈希> 会直接进入 detached HEAD;而 git switch --detach <提交哈希> 需要明确写 –detach,降低误操作概率。

git log

git log 用于查看 Git 的提交历史,默认会从当前 HEAD 开始,沿着父提交向前查找,通常按从新到旧显示

1
2
3
4
完整提交哈希
作者姓名和邮箱
提交时间
提交信息

git log 命令可以查看当前本地和远程的各个分支分别处于哪个 commit 上,以及当前位于哪个分支,在哪个检出的版本上。注意 origin/master 只是最近一次 fetch/pull 后记录的远程状态,不一定代表服务器此刻的最新状态。常用简洁输出格式,每个提交显示一行:git log --oneline。另外,执行 git log 之后终端会进入 less 查看器,按 q 键可以退出。

git reflog

相较于只能看 commit 的 git loggit reflog 可以查看 HEAD 曾经去过哪里;这一功能可以用于修复 reset、rebase 等带来的 commit 遗失的情况。但 git reflog 本质上是记录本地引用的移动历史,不会记录远程仓库的历史,能恢复的也仅仅是本地仓库曾经 ref 过的 commit。

git status

git status 用于查看当前 Git 仓库处于什么状态,反映 HEAD(最近提交)、暂存区和工作目录之间的状态差异,并输出以下信息

1
2
3
4
5
6
7
当前在哪个分支,或者是否处于 detached HEAD
本地分支是否领先或落后 upstream
哪些修改已经暂存
哪些修改尚未暂存
哪些文件尚未被 Git 跟踪
是否存在合并冲突
是否正在执行 merge、rebase、cherry-pick 等操作

git diff

git diff 用于查看两个状态之间具体有哪些文件内容差异。

1
2
3
4
5
6
7
git diff # 默认查看暂存区与工作目录之间的差异,即已修改但还未暂存的部分
git diff --staged # 查看本地提交与暂存区的差异,即已暂存但还未提交的部分
git diff HEAD # 查看HEAD与工作目录之间的差异,即已修改未提交的所有改动
git diff -- file.txt # 查看某个文件的修改
git diff -- src/ # 查看某个目录的修改
git diff <提交1> <提交2> # 比较两个提交
git diff master feature # 比较两个分支

git blame

可以看某个提交中某一行是谁写的,具体的用法用到再查吧,希望不要有用到这个命令的时候。

git reset

git reset 的作用是:把当前分支、暂存区,必要时还有工作目录,重置到指定提交的状态。针对提交来说,作用是移动当前分支的位置;针对文件来说,作用是重置暂存区,常用于取消 git add

针对提交的 reset 命令有三种模式,假设当前提交历史为

1
A <- B <- C master、HEAD

以 reset 上一次提交(HEAD~1 表示当前提交的上一个提交,也就是 B)为例

1
2
3
git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1

执行之后的提交历史都会变成

1
A <- B master、HEAD

三种模式的区别如下:

  • –soft:暂存区保持不变,工作目录保持不变。即提交 C 不再属于 master 的当前历史,但 C 中的修改仍保留在暂存区,可以马上重新提交, 适合提交信息写错、提交内容需要重新组织等情况。不过如果只是修改最近一次提交信息,通常直接使用 git commit --amend 更方便。
  • –mixed(默认模式):暂存区重置到目标提交,工作目录保持不变。C 中的修改仍留在工作目录,修改不再位于暂存区,需要重新 git add。
  • –hard:暂存区重置到目标提交,工作目录重置到目标提交。master 回到 B,暂存区恢复为 B,工作目录中的已跟踪文件恢复为 B,C 中的修改以及其他未提交的已跟踪文件修改都会被丢弃。

针对文件的 reset 命令为

1
git reset -- file.txt

这种写法不会移动分支,也不会修改工作目录文件,只会把 file.txt 从暂存区撤下来,相当于撤销了 git add file.txt 。不过现代 Git 推荐写成 git restore --staged file.txt,两者完全等价。

取消全部暂存可以执行

1
git reset

1
git reset --mixed HEAD

两者完全等价,即当前分支不移动,只把整个暂存区恢复到 HEAD,工作目录修改仍保留。

如果是尚未推送的本地提交或者纯个人开发,可以使用 git reset,否则如果是已推送到远程的提交,尤其是多人协作开发时,不要随便使用针对提交的 reset,这可能会破坏别人的提交历史,这种情况建议使用 git revert

git revert

git revert 的作用是:通过创建一个新的提交,抵消某个旧提交引入的修改。它不会删除旧提交,也不会把分支指针向后移动,适合撤销已经推送到远程的提交。注意 revert 不是把仓库恢复到某个提交之前,而是反向应用某个提交引入的变化。

1
2
3
4
git revert HEAD # 抵消最近一次提交
git revert --no-commit HEAD # 只生成反向修改,不创建提交
git revert <commit_hash> # 抵消指定提交,通常会打开编辑器让用户确认新提交的信息
git revert --no-edit <commit_hash> # 抵消指定提交,跳过编辑器确认并使用默认信息

如果旧提交涉及的代码后来又被修改,则会产生冲突,建议手动解决。

1
2
git add <冲突文件>
git revert --continue

不想继续也可以取消整个 revert

1
git revert --abort

git fetch

git fetch 的作用是:从远程仓库下载本地还没有的提交、分支和相关对象,并更新本地的远程跟踪分支,但不修改当前本地分支和工作目录,即只有本地记录的 origin/master 被更新;本地 master、HEAD 和工作目录都不会改变。

1
2
3
git fetch # 获取当前默认远程仓库
git fetch origin # 获取指定的远程仓库
git fetch --all # 获取所有已配置的远程仓库

git merge

git merge 的作用是:把另一个分支的提交和修改整合到当前分支中,方向由当前分支决定。一个具体的例子:把 feature 合并进 master

1
2
git switch master
git merge feature

合并时存在以下几种情况:

  • fast-forward 合并:master 没有产生新提交,即 feature 分支的改动已经是基于 master 分支最新的提交进行的,即

    1
    2
    3
    
    A -> B  master
          \
           C -> D  feature
    

    此时 Git 只需要把 master 向前移动即可,不会产生额外的合并提交。

    1
    2
    3
    
    A -> B -> C -> D
                   ↑
           master、feature
    

    使用 git merge --ff-only feature 命令,表示只允许这种合并,如果两个分支已经分叉则命令会停止。

  • 普通 merge 合并:如果 master 分支和 feature 分支都有自己的新提交,即

    1
    2
    3
    4
    5
    
           C  master
          /
    A -> B
          \
           D -> E  feature
    

    合并后 Git 会创建一个合并提交 M,M 有两个父提交,master 分支的提交更新为 M,feature 分支的提交保持不变。

    1
    2
    3
    4
    5
    6
    7
    
           C --------\
          /           M  master
    A -> B            /
          \          /
           D -> E --/
                    ↑
                  feature
    
  • squash 合并:把 feature 分支的所有修改压成一次普通提交,这会得到功能分支的最终修改,但不会创建具有两个父提交的真正 merge commit,也不会记录分支之的合并关系。

    1
    2
    3
    
    git switch master
    git merge --squash feature
    git commit -m "合并 feature 功能"
    

对于普通 merge,在 git add <已解决冲突的文件> 之后,也可以使用 git commit 命令完成合并提交,两者在普通 merge 时效果等价,唯一的区别在于 git merge --continue 会使用 Git 已准备好的默认合并提交信息;git commit -m "..." 可以指定合并提交信息。(所谓不普通的 merge,指的是 rebase 或 cherry-pick,这俩可能还有后续提交需要继续处理,不能简单用 git commit 代替整个继续流程)

如果要放弃本次 merge

1
git merge --abort

对于远程分支,git merge 本身不会连接远程服务器,应该先获取远程仓库的最新状态

1
2
3
git fetch origin
git switch master
git merge origin/master

git rebase

git rebase 的作用是:把当前分支上的一组提交,重新应用到另一个提交之后,即更换这些提交的基础。一个具体的例子,把 feature 分支 rebase 到 master 分支

1
2
git switch feature
git rebase master

假设分支历史是:

1
2
3
4
5
        C  master
      /
A -> B
      \
        D -> E feature

Git 会找出 feature 独有的提交 D、E -> 将 feature 移动到 main 的 C -> 依次重新应用 D、E 的修改。rebase 之后结果如下,其中 D’、E’ 是新提交,即使内容相同,由于父提交发生了变化,因此提交哈希也会变化。

1
2
3
A -> B -> C -> D' -> E'  feature
          ↑
        master

如果要放弃本次 rebase

1
git rebase --abort

rebase 会逐个重新应用提交,和 merge 一样可能遇到冲突。

另外,rebase 会改写提交历史,不要 rebase 已经推送且其他人可能基于它继续开发的提交,否则其他人的历史会与新历史不一致。因此,rebase 更适合整理尚未共享的本地提交,而 merge 适合共享历史。

git cherry-pick

git cherry-pick 的作用是:选择某一个或几个已有提交,把这些提交引入的修改重新应用到当前分支,并生成新的提交,可以理解为“只挑需要的提交”。一个具体的例子如下

假设提交历史是:

1
2
3
4
5
        C  master
      /
A -> B
      \
        D -> E feature

其中提交 E 修复了一个重要 bug。现在只想把 E 的修改带到 master,不想合并整个 feature。

1
2
git switch master
git cherry-pick <E的提交哈希>

执行结果:

1
2
3
4
5
       C -> E'  master
      /
A -> B
      \
       D -> E   feature

原提交 E 仍然在 feature,master 上产生了新提交 E’。cherry-pick 不是复制整个 E 的项目快照,而是计算 E 的父提交 -> E 之间的修改,然后把这些修改应用到当前 HEAD 上。

cherry-pick 也可以挑选多个提交,通常按照从旧到新的顺序排列

1
2
3
git cherry-pick abc123 def456 789abcd
git cherry-pick A..C # 挑选不包括 A 在内的从 A 到 C 的连续提交
git cherry-pick A^..C # 挑选包括 A 在内的从 A 到 C 的连续提交,A^ 表示 A 的父提交

只把修改放入暂存区和工作目录,但暂时不提交。可以继续挑选其它提交,最后统一提交

1
2
git cherry-pick --no-commit abc123
git cherry-pick -n abc123 # 简写

记录原提交来源,-x 会在新提交信息中增加类似内容:(cherry picked from commit abc123…)

1
git cherry-pick -x abc123

适合在不同发布分支之间移植修复,方便之后追踪来源。

发生冲突时需要手动解决冲突,然后继续 cherry-pick

1
2
3
4
git status
# 手动解决冲突
git add <已解决文件>
git cherry-pick --continue

如果要取消:

1
2
git cherry-pick --skip # 取消当前正在挑选的提交
git cherry-pick --abort # 取消整个cherry-pick

在一次 cherry-pick 了多个提交时,两种取消是有区别的。

git stash

git stash 的作用是:把尚未提交的修改临时收起来,让工作目录恢复到较干净的状态,之后再把修改取回来。假设当前状态是:HEAD 为已提交的版本 A,暂存区为已经 git add 的版本 B,工作目录为继续修改之后的版本 C。

1
2
3
git stash push # 默认不会保存未跟踪文件和被.gitignore忽略的文件
git stash push -u # 保存未跟踪文件,但不会保存被.gitignore忽略的文件
git stash push -a # 保存未跟踪文件和被.gitignore忽略的文件

Git 会将暂存区和工作目录中已跟踪文件的修改保存到储藏区(注意和暂存区区分开),然后将暂存区和工作目录都恢复到 HEAD 的版本 A 状态,当前分支和 HEAD 不会移动,也不会产生当前分支上的提交。

查看储藏区内容

1
2
git stash list # 查看储藏列表
git stash show -p stash@{0} # 查看某一次具体的储藏内容

应用和删除储藏区内容

1
2
3
4
5
git stash apply # 应用最新储藏,但保留储藏记录
git stash apply stash@{1} # 应用指定储藏
git stash pop # 将最新储藏的内容恢复到工作区,默认不恢复暂存区(所有恢复出来的文件都是未暂存状态),成功后删除储藏记录
git stash pop --index # 将最新储藏的内容恢复到工作区,并且恢复暂存区,成功后删除储藏记录
git stash drop stash@{0} # 删除指定储藏

如果 pop 发生冲突,Git 会保留该 stash,避免修改丢失。

git stash 常用于临时切换任务,假设本身在 feature 分支上,且存在未提交的更改

1
2
3
4
5
git stash push -u -m "某个功能开发到一半"
git switch master
# 处理其它事情
git switch feature
git stash pop

git pull

git pull 的作用是:从远程仓库获取最新提交,并把它们整合到当前所在的本地分支,相当于 git fetch + git merge

假设本地当前位于 master 分支,基本用法是

1
git pull origin master

如果之前执行过 git pull -u origin master,即指定过 upstream(默认跟踪和比较的远程分支),后续可以直接执行 git pullgit push

如果当前位于 feature,执行

1
git pull origin master

这会把远程 master 整合进当前 feature,并不会自动切换到本地 master。

若要更新本地 master,应执行

1
2
git switch master
git pull origin master

git pull 能保留不冲突的本地修改,发生冲突时需要手动解决。

git push

git push 的作用是:把本地已经创建的提交传输到远程仓库,并尝试更新远程分支或标签。git push 只会推送已提交的内容,暂存区和储存区的修改不会被推送。基本用法是

1
git push origin master

这一命令实际上同时指定了远程分支和本地分支,git push 的完整语法为

1
2
git push <远程仓库名> <本地分支>:<远程分支>
git push origin master:master # 这是完整写法

当本地分支和远程分支同名时,Git 允许省略冒号和远程目标,表示把本地 master 推送到 origin 的同名 master 分支。当只写一个分支名时,表示省略冒号和远程目标,即指定本地分支。所以即使当前位于 feature,该命令仍然推送本地 master。如果远程没有对应的同名分支,Git 会创建它。

git push 在远程含有本地不存在的提交历史时会发生冲突,需要手动解决。正常处理方式是先获取并整合

1
2
3
git fetch origin
git rebase origin/master
git push

或者使用 merge

1
2
3
git fetch origin
git merge origin/master
git push

如果因为 amend 或 rebase 有意改写了自己独占分支的历史,可以使用:

1
git push --force-with-lease

这是一种带安全检查的强制推送,它会先检查远程分支是否仍处于本地预期的位置,通常在 rebase、amend 或 reset 改写提交历史后使用。

假设原来:

1
2
远程:A -> B -> C
本地:A -> B -> C

执行 amend 或 rebase 后,本地提交被重新生成:

1
2
远程:A -> B -> C
本地:A -> B' -> C'

因为本地历史不再包含远程的 C,普通推送会拒绝,此时可以执行:

1
git push --force-with-lease origin feature

Git 会先检查:远程 feature 是否仍然位于我上次看到的位置。如果远程没有被其他人更新,允许强制改成:

1
A -> B' -> C'

如果其他人已经推送了 D:

1
2
远程实际状态: A -> B -> C -> D
本地记录 origin/feature:A -> B' -> C'

Git 发现远程实际位置和你预期的不一致,就会拒绝推送,以避免覆盖别人的 D。

注意,git push --force-with-lease 只适合于自己独占的功能分支,禁止在多人共同使用的 master/main 上随意强推。

绝对禁止使用 git push --force 进行强行推送,尤其是在多人共同使用的 master/main 分支上,一般 master/main 的远程分支也会设置保护来禁止强制推送。

git worktree

git worktree 可以同时把同一个 Git 仓库检出(checkout)到多个目录,每个目录可以处于不同的分支。比如当前在 main 分支,要同时开发一个新功能(feature 分支),修一个线上 bug(hotfix),又不想丢掉当前 main 分支中开发中的代码。可以执行

1
2
3
# 需要已有feature和hotfix分支
git worktree add ../project-feature feature
git worktree add ../project-hotfix hotfix

两个命令会分别在当前目录的上一级新建 project-feature 目录(处在 feature 分支)和新建 project-hotfix 目录(处在 hotfix 分支)。

执行之后,几个目录共用一个.git,即 Git 的对象数据库(.git/objects)、提交历史等是共享的,因此通常只多占工作目录中文件的空间,而不会复制整个仓库历史。

查看所有 worktree

1
git worktree list

新建不存在的分支

1
git worktree add -b feature/login ../login main # 从 main 创建 feature/login 分支,放在../login目录

删除 worktree,注意目录也会一起删除(要求工作区干净,否则 Git 会阻止删除)

1
git worktree remove ../login

另外,一个分支只能被一个 worktree 检出,Git 不允许同一个分支同时被多个 worktree 检出,以避免对同一分支在不同目录中进行互相不知情的修改。

git bisect

用于二分法查找 bug,找到第一个坏 commit。Git 负责按二分法进行 commit 跳转,由人进行测试并将结果告诉 Git。

1
2
3
git bisect start
git bisect bad
git bisect good 1

合并与冲突处理

开始合并前最好先检查工作区是否干净,如果有会导致冲突的未提交修改,Git 就会拒绝合并,稳妥做法是先提交或暂存

1
2
3
git stash push -u
git merge feature
git stash pop

Git 合并文件的方式是,找出两个分支的共同祖先,然后比较“共同祖先 -> 当前分支的修改”和“共同祖先 -> 被合并分支的修改”。如果修改位于不同文件或不同位置,Git 会自动合并;如果双方修改了同一部分内容,则会出现冲突。冲突文件中会出现如下标记:

1
2
3
4
5
  <<<<<<< HEAD
  当前分支的内容
  =======
  传入分支的内容
  >>>>>>> feature

在编辑器中还会提示选择保留”当前的更改“或“传入的更改”,两者具体所指的分支取决于当前所在的分支。

  • 当前的更改(Current Change):当前检出的分支,也就是 HEAD 所在分支的内容
  • 传入的更改(Incoming Change):正在合并进来的那个分支的内容

例如

1
2
git switch master
git merge feature

那么当前的更改指 HEAD 所在的 master 分支的更改,传入的更改指 merge 进来的 feature 分支的更改。

冲突的处理流程如下:

1
2
3
4
git status
# 手动修改冲突文件,删除冲突标记
git add <已解决冲突的文件>
git merge --continue

游离头问题

HEAD 可以理解为 Git 的“当前位置指针”,用于指示现在检出的版本在哪里,以及指示新的提交应该添加到哪里。正常情况下创建新提交:提交前:HEAD -> master -> A,提交后:HEAD -> master -> B -> A(Git 提交的箭头是从新提交指向旧提交)。

detached HEAD 即“分离头指针”,表示 HEAD 没有指向某个本地分支,而是直接指向某次提交,这在查看旧版本、运行测试时很常见。detached HEAD 的风险在于,如果在 detached HEAD 状态下创建提交,新提交不属于任何分支;切换走后,再想找到会比较麻烦。因此如果已经在 detached HEAD 状态下做了有用的提交,应先创建分支保存它:

1
git switch -c my-new-branch

这行命令的意思是:以当前提交为起点,创建名为 my-new-branch 的本地分支,并立即切换到该分支。

远程仓库名

执行 git clone 时,Git 通常自动把源仓库命名为 origin。远程仓库名只是当前本地仓库里的别名,不会改变服务器上的仓库名称。同一个本地仓库中的远程名称不能重复。除非有多个远程仓库,否则沿用 origin 最省事,因为部分脚本和文档默认使用这个名字。

一个本地 Git 仓库可以配置多个远程仓库,origin 只是克隆时默认创建的第一个远程名称,并不是唯一允许的名字。使用命令 git remote -v 可以查看本地仓库配置的所有远程仓库。

master 和 origin/master

origin 是远程仓库地址的本地别名,本地 master 是实际开发和提交的本地分支,本地 origin/master 是本地保存的远程 master 状态记录,远程 master 是 GitHub 服务器上真正的 master。origin/master 通常被称为“远程分支”,但更准确地说,它是保存在本地的远程跟踪分支,记录你上次与远程通信时,远程 master 所在的位置,不是服务器上的实时分支。

HTTPS 和 SSH

这俩是访问远程仓库时的两种网络通讯协议,不影响 Git 功能,区别只是连接及认证方式。 个人长期使用通常可以选择 SSH;临时环境、服务器受限网络或希望少配置时,HTTPS 更方便。已有仓库也可以用命令 git remote set-url origin <your_address> 随时切换,仓库的连接方式(HTTPS 或 SSH)保存在每个本地仓库的远程地址配置中。

对比项HTTPSSSH
地址格式https://github.com/user/repo.gitgit@github.com: user/repo.git
加密方式HTTPS/TLS 加密SSH 加密
克隆公开仓库通常无需登录本机需配置 SSH key
访问私有仓库Token、凭据管理器或网页登录授权SSH key
日常推送需要凭据管理器保存认证配好 key 后无需重复认证

执行 git push 命令时,大致会经过:根据远程地址选择 SSH 或 HTTPS→ 自动取得 SSH 私钥或 HTTPS Token→ 远程平台根据凭据识别账户 → 检查该账户是否有仓库写权限 → 有权限的情况下完成项远程仓库的推送。

HTTPS 在第一次推送的网页登录完成后,通常会由 Git Credential Manager、系统钥匙串或其它凭据助手保存 Token。SSH 需要在本机生成 SSH 密钥对,私钥留在本机,公钥添加到 GitHub、GitLab 或公司的 Git 服务器。SSH key 的管理,通常是一台机器一个,添加到 GitHub/GitLab 账户后,本台机器即可访问这个账户有权限的所有仓库。

一台机器可以有多个账户以及多个 key,因此在访问远程仓库遇到权限问题时,需要确认远程仓库识别到了本地的哪个账户;HTTPS 的凭据同理,也需要确认凭据管理器当前缓存的是哪一个账户。仓库“有权限”只代表某个远程账户有权限;推送时远程服务器还要确认:你当前到底以哪个账户、哪把 SSH key 或哪份 HTTPS 凭据登录。

SSH 认证主要有两个方向,SSH 私钥用于向服务器证明“我是谁”,known_hosts 保存的是本机曾经连接过的 SSH 服务器的身份信息,用来确认“对面的服务器是谁”,里面会写入 Github 的主机公钥。以后再次连接时,SSH 会对比服务器发来的公钥是否与 known_hosts 中保存的一致,不一致时会发出安全警告,这是为了防止中间人攻击,比如有人伪装成 GitHub 截获你的连接。

本文由作者按照 CC BY 4.0 进行授权
/body>