Contents

Git的使用

Beautiful landscape

Nature's Beauty

Explore the wonders of the world

参考资料

Git 下载安装

  • 下载地址:https://git-scm.com/downloads

  • 详细安装教程:https://blog.csdn.net/mukes/article/details/115693833

初识 Git

分布式的版本控制系统
方便 管理代码版本,也可以帮助多人协作

Git 本质上是存储键值对(key-value)
它允许插入 任意类型的内容,并会返回一个 键值
通过该键值可以在任何时候再取出该内容

SHA-1 哈希值即 存储的数据头信息 的校验和

https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/Git%E5%8E%9F%E7%90%86%E5%9B%BE.png

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

.git 文件目录

执行 git init 后,当前所在目录下会创建一个 .git 目录
如果要备份或者复制一个库,一般只需要拷贝它即可

.git 目录结构:

内容 解释
hooks/ 钩子脚本
info/
objects/ 存储数据内容
refs/ 存储指向数据的提交对象的指针
config 项目特有的配置选项
HEAD 指向当前分支
index 保存暂存区信息

HEAD 是一个“指针”,指向当前分支的最新提交,默认指向最新的 main
暂存区实际上只是一个文件(位于.git/index)

gitconfig 配置文件

配置文件 文件所在路径 查看命令
系统全局配置 C:\Program Files\Git\etc\gitconfig git config --system --list
用户全局配置 C:\Users\[系统用户名]\.gitconfig git config --global --list
仓库/项目配置 项目目录下的 .git/config git config --local --list

上述配置的优先级从上到下逐渐变大

  • 配置用户信息
    commit 文件后,查看历史记录时方便了解提交人的情况

    1
    2
    
    git config --global user.name "yourname"
    git config --global user.email "??????@??.com"
    

    如果只想针对当前仓库,而不是所有仓库,可以将 --global 参数替换成 --local

  • 查看当前所有配置信息

    1
    
    git config --list
    
  • 设置彩色的输出

    1
    
    git config color.ui true
    

忽略文件

可以通过配置 .gitignore 文件来防止一些自动生成的日志文件、编译过程中的临时文件提交上去
一般放在代码库的根目录下,也可以在其他目录下
支持多个 .gitignore 文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 井号为注释,将被 Git 忽略

# `\` 为转义字符,可以转义空格  

# 忽略对 .a 或者 .o 为后缀的文件的跟踪
*.[oa]
# 但不忽略 lib.a 文件
!lib.a

# 忽略对 ~ 结尾的文件的跟踪
*~

# 仅忽略项目根目录下的 temp 文件
# 不包括其它目录下的 temp,例如不包括 src/temp
/temp

# 忽略 build/ 目录下的所有文件
# 例如忽略 build 目录以及 a/build 目录,等价于 **/build
build/

# 忽略 doc/notes.txt 但不包括 doc/server/arch.txt
doc/*.txt

* 只能匹配文件和当前目录下的一级目录
例如 foo/* 能匹配到 foo/test.jsonfoo/bar,但是不能匹配到 foo/bar/hello.c
因为 * 匹配不到含有 / 的部分

abc/** 则可以匹配目录 abc(相对于 .gitignore 的路径)下的所有文件
例如 a/**/b 可以匹配 a/ba/x/ba/x/y/b 等等

注意
所有路径都是 相对 .gitignore 文件所在的路径

常用命令

https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4.png

获取仓库

  • 本地初始化仓库

    步骤 命令
    切换到 D 盘 cd d:
    创建一个新的文件夹 learngit mkdir learngit
    进入新建的文件夹 cd learngit
    查看当前路径 pwd
    切换到新的文件夹下,初始化一个 git 本地仓库,即 Repository git init
    查看当前目录下的所有文件,会存在一个 .git 文件夹 ls -ah
  • 克隆远程仓库
    git clone git://github.com/greenhand/ubuk.git 自定义可选的项目目录名
    还支持 http(s):// 和 user@server:// 的数据传输协议

    git clone 会自动创建本地的 main 分支用于跟踪远程仓库中的 main 分支

    git clone 很慢可以先试试重置 git 代理 git config --global --unset http.https://github.com.proxy

文件更新

  • 查看文件状态
    git status
    可以用来确定是否有未跟踪的文件 untracked files

  • 告知 Git 对某些文件进行跟踪
    例如对 .c 后缀的文件及 README 文件进行跟踪

    1
    2
    
    git add *.c
    git add README
    

    执行后,Git 会把目标文件快照放入暂存区 Index / Stage
    之后 .c 后缀的文件及 README 文件就处于 暂存状态

    一次将全部修改过的文件(夹)添加到暂存区中:

    1
    
    git add .
    

查看文件修改

  • 比较 工作区暂存区 的文件内容差异
    git diff
    加上参数 --cached 或者 --staged 查看的是 暂存区 文件与 本地仓库 文件的差异

  • 查看当前 工作区 文件与 本地仓库 文件的不同之处

    1
    
    git diff HEAD -- <文件名>
    

提交更新

将文件提交到本地仓库,即 Repository
提交前 git status 检查一下是否都已暂存
再执行:git commit -m "本次提交的相关描述"

-m 参数用于对本次提交进行描述,便于了解此次提交内容
可以多次执行 add,最后再执行一次 commit

执行后会返回:
1、当前是在哪个分支提交的
2、SHA-1 校验和
3、修改的文件数、多少行添加或者删除

commit 是按照时间顺序保存在数据库中的
每次提交都会产生一条记录,由以下三部分组成:
1、SHA-1 校验和,是本次提交的唯一标识,根据修改的文件内容采用摘要算法,计算出的不重复 40 位哈希值。本地一般使用前 6 位即可
2、-m 参数所填写的说明
3、快照,即完整的版本文件,以 对象树 的结构存在仓库 \.git\objects 目录里

跳过暂存区
git commit -a -m "a commit without git add"
-a-m 参数一起使用,已经跟踪的文件 会进行 暂存并提交
无需先 git add 一下

移除文件

将文件从暂存区中移除(取消跟踪)

先自行手动删除文件 rm somefiles.txt
这时候 status 会显示 somefiles.txt 在未暂存清单中
然后我们再执行

1
2
git rm somefiles.txt
git commit -m "移除 somefiles"

此时 somefiles.txt 就不再纳入版本管理了

https://cloud.tencent.com/developer/article/1124235#
但是有时候希望仅移除文件跟踪,但是不删除文件(仍然保存在当前工作区中)
可以执行 git rm --cached somefiles.txt 取消跟踪
再将该文件写入 .gitignore 文件中
然后再次 addcommit 即可

修改文件名

git mv <原文件名> <新文件名>

等同于

1
2
3
mv <原文件名> <新文件名>
git rm <原文件名>
git add <新文件名>

先对文件进行改名
删除工作区的原文件名,并将新文件名添加到暂存区

查看历史

git log
Git 会根据提交时间列出,最上面的是最新的提交
每条记录包含:SHA-1 校验和、作者名字及电子邮件地址、提交时间、提交说明
也可以在相应仓库目录下,执行 gitk 使用图形化工具查看历史

其他参数:
-p 查看每次提交的内容差异
-2 仅显示最近的两次提交
--stat 显示简要的增改行数统计
--graph 简单的提交图形显示
--pretty=oneline=format:"%h - %an, %ar : %s" 使用格式占位符实现定制显示的记录格式,包含 hash 值、作者、时间、提交说明

  • 查看某个人的提交记录

    1
    
    git log --author=somebodyname
    

    author 和 committer
    作者是对项目做出实际修改的人
    提交者是将工作成果提交到仓库的人
    可以是不同的人

  • 筛选符合条件的提交

    1
    2
    3
    4
    5
    
    $ git log --pretty="%h - %s" \
        --author=gitster \
        --since="2019-01-01" \
        --before="2023-01-01" \
        --no-merges -- t/
    

撤销操作

注意撤销是不可逆的
配合 git log 查看需要回退到某个版本的版本号

修改最新的一次提交

假设第一次提交后,发现漏掉了对一些文件的暂存
可以使用 --amend 参数再次提交,合并上一次的提交

1
2
3
git commit -m 'initial commit'
git add forgotten_file
git commit --amend

对比

  • checkout(用于撤销工作区、暂存区的修改)

    命令 说明
    git checkout <文件名> 撤销工作区的修改,把暂存区的内容恢复到工作区
    注意 会删除工作区中没有添加到暂存区的内容
    git checkout HEAD <文件名> 撤销工作区、暂存区的修改
    用 HEAD 指向的分支的最新版本替换工作区、暂存区
  • reset(用于撤销暂存区的修改,回退版本)

    命令 说明
    git reset HEAD <文件名> 撤销暂存区的修改,但是 不影响工作区
    git reset <版本号> 回退到指定版本,清空暂存区,不影响工作区
    • git reset 的参数
      • --soft
        不影响工作区、暂存区
      • --mixed (默认情况)
        不影响工作区
      • --hard
        会重置工作区、暂存区
  • revert(用于撤销某一次提交)
    git revert <版本号> -m '撤销对***的修改'
    本质上是用一个新的提交来完成撤销操作
    该新的提交是想要撤销的提交的逆向操作

上述 <文件名> 换成 . 则表示一次性将工作区所有文件都撤销
HEAD^ HEAD指向的最新版本的上一个版本
HEAD~3 HEAD指向的最新版本的上三个版本

远程仓库

远程仓库是托管在网络上的项目仓库
公共 Git 服务器:如 Github、Gitlab、码云 Gitee、腾讯 Coding 等
也可以搭建私有 Git 服务器

  • 创建 SSH Key
    先去 C:\Users\自己的 PC 用户名 路径下查看是否存在 .ssh 文件夹
    里面是否有 id_rsaid_rsa.pub 这两个文件
    如果有就可以跳过该步骤,否则

    1
    
    ssh-keygen -t rsa -C "??????@qq.com"
    

    执行后一路回车,暂时不需要设置密码

  • 查看 id_rsa.pub 中的内容

    1
    2
    
    cd ~/.ssh
    cat id_rsa.pub
    

    再手动复制一下内容

  • 设置 Github
    登录 Github,在 Settings 中找到 SSH 设置项,添加新的 SSH Key
    设置任意 title,在 Key 文本框里粘贴 id_rsa.pub 文件的内容
    这是为了验证提交人的信息

  • 关联远程仓库
    先在 Github 上面创建一个新的仓库,命名为 learngit
    再回来 gitbash 上执行

    1
    
    git remote add origin git@github.com:用户名/仓库名.git
    

    origin 用来指代对应的仓库地址

    [!NOTE|style:flat]
    建议在新建仓库的时候,不要勾选 README.gitignore 选项
    推荐统一由本地生成,这样在建立关联关系的时候就不会有冲突了

  • 查看远程仓库信息

    1
    2
    
    git remote show origin
    git remote -v
    
  • 远程仓库的重命名
    git remote rename originName revisedName

    远程仓库重命名后,分支也跟着改变,例如
    originName/main 变成了 revisedName/main

  • 远程仓库的删除
    git remote rm <待删除的仓库名>

  • 拉取 远程分支 的最新内容到 本地仓库
    git fetch <仓库名>
    Git 会去远程仓库中拉取你的本地仓库中所没有的数据

  • 拉取 远程分支 的最新内容到 工作区

    1
    
    git pull
    

    注意
    git fetch 后不会自动合并到当前工作分支
    git pull 会拉取后自动合并
    所以 git pull = git fetch + git merge

  • 推送到远程仓库
    git push origin main
    将本地的 main 分支推送到 origin 服务器上
    当然需要有 写权限 并且没有其他人在推送,此命令才能正常完成
    否则会被驳回,遇到这种情况,我们就需要再拉取、合并、推送

    第一次推送需要有个 -u 参数,将 main 远程分支设置为本地当前分支的上游分支
    之后推送时不用额外参数,直接 git pull 或者 git push,不必指定远程仓库和分支名

标签

对某个提交打上标签

  • 只列出所有版本号为 v1. 开头的版本
    git tag -l "v1.*"

  • 查看标签的信息
    git show v1.1
    会显示提交者、提交时间、标签说明等等

  • 新建标签

    • 含附注的
      git tag -a v1.1 -m "new version 1.1"
      -a 参数指定标签名字
      -m 参数用于添加说明

    • 轻量级的
      没有任何参数,直接 git tag v1.1

    若要对 先前的提交 进行加注标签,只需要在命令的最后加上对应的校验和(前几位)即可

  • 分享标签
    默认情况下,git push 不会把标签推送到远程仓库上

    如若需要,可以执行:git push origin v1.1
    便将 本地 v1.1 标签的提交 推送到远程的 origin 分支上

    若需要一次性将所有标签推送过去,直接使用 --tags 参数
    这样其他人克隆仓库后也可以看到这些标签

  • 删除本地的标签

    1
    
    git tag -d <标签名>
    

紧急切换

  • 将当前 工作区 的内容暂存起来且隐藏

    1
    
    git stash
    
  • 查看隐藏的内容

    1
    
    git stash list
    
  • 恢复隐藏的内容并删除 stash

    1
    
    git stash pop
    

分支

Git 中的分支本质上是一个 可变指针,它指向 提交对象
代码仓库可以有多个分支,一般 main 为默认的主分支
每次提交,分支都会 跟着自动向前移动
注意,只能有一个分支处在活动状态,当我们要操作其他分支时,需要切换到对应分支

创建一个新的分支即创建一个新的 指针
但是同一个提交对象上如果有多个分支指针,Git 是怎么区分的呢?
是通过一个名为 HEAD 的特别指针
它是一个指向你 正在活动的本地分支 的指针(可以想成 当前分支 的别名)
HEAD 同样会跟着提交向前移动

内部原理

git add 暂存操作会对每一个文件计算校验和(即 SHA-1 哈希字符串)
然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型 的对象存储这些快照)
并将校验和加入暂存区

在 Git 中提交时,会保存一个 提交(commit)对象,该对象包含
1、一个指向 暂存内容快照 的指针
2、本次提交的作者等相关附属信息
3、零个或多个指向该 提交对象的父对象 的指针

首次提交是没有直接祖先的
普通提交有一个祖先
由两个或多个分支合并产生的提交则有多个祖先

当使用 git commit 新建一个提交对象前,Git 会先计算每一个子目录的校验和
然后在 Git 仓库中将这些目录保存为 树(tree)对象
之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着 指向这个树对象(项目根目录)的指针
如此它就可以在将来需要的时候,重现此次快照的内容了

我们在当前分支上做一些修改后,进行几次提交
每个提交对象都会包含一个 指向上次提交对象的 parent 指针
因为每次提交时都记录了祖先信息,即 parent 对象
将来要合并分支时,寻找恰当的合并基础(即共同祖先)的工作其实已经自然而然地摆在那里了
所以 Git 鼓励开发者频繁使用分支

Git 创建分支之所以快,就是因为只需要新建一个文件
写入 40 个字符长度的 SHA-1 字符串外加一个换行符即可
而不是对所有项目文件进行备份

分支的新建与合并

假设现在在开发某个网站
为实现某些需求,创建了一系列分支并开展工作
突然有一个问题需要紧急修补
我们先返回到已经发布到生产服务器上的主分支
为这次修补建立一个新分支,在该新分支上修复问题
测试通过后,回到生产服务器所在的主分支,将修补分支合并进来,再推送到生产服务器上
然后便可以切换到其他分支上,继续正常工作

新建并切换分支

git checkout -b iss53 或者 git switch -c iss53

表示新建名为 iss53 的新分支,并切换到该分支上
等同于:

1
2
3
4
5
// 创建 iss53 新分支
git branch iss53

// 切换过去
git checkout iss53 或者 git switch iss53

checkout iss53 干了两件事
1、修改 HEAD 的“指针”引用,指向 iss53 分支
2、把 iss53 分支内容还原到工作区

注意,切换分支前,最好保证工作区的清洁,否则会产生冲突

合并

1
2
3
4
5
6
7
         分支 a
           |
-c1-c2-c3-c4
           \
            c5
             |
           分支 b
  • 示例一

假设分支 a 是分支 b 的 直接上游
现在切换到分支 a 上,执行 git merge -m "合并信息" b
将分支 b 合并到当前分支 a 进来
合并时出现 “fast forward” 快进,是因为没有需要解决的分歧,Git 只需要把 指针右移

添加参数 --no-ff 可以禁用 fast forward 方法

这时候 a 和 b 两个分支指针指向同一提交对象
分支 b 便已经完成使命,我们可以将其删除 git branch -d b
若使用 -D 参数则表示进行强制删除

1
2
3
4
5
-c1-c2-c3-c4
           \
            c5
             |
           分支 a
  • 示例二

假设上述是分支 b 要并入分支 a 中
注意到 分支 a 不是分支 b 的直接祖先
不同于 fast forward,Git 会对两个 分支的末端 以及 共同祖先,进行一次 三方合并计算
之后创建一个新的快照,并自动创建指向它的提交对象
这个提交对象有 两个祖先(即上面两个分支末端的提交对象)

1
2
3
4
5
             分支 a
              |
-c1-c2-c3-c4-c6
           \ /
            c5
  • 冲突合并
    在不同分支中,若修改了同一文件的同一部分
    这时要进行合并,只能由人来决定

    git status 查看哪些文件合并时存在冲突,会以 unmerged 状态列出
    冲突部分由 ======= 隔开
    手动修改冲突内容,注意删除掉 =======<<<<<<<>>>>>>>
    再执行 git add 进行暂存,冲突便解决了
    也可以使用可视化合并工具 git mergetool

分支管理

  • 列出所有分支(包括远程分支)
    git branch -a
    前面有 * 号的即当前所在分支

    -v 参数用于查看各个分支的 末端提交对象 的信息
    --merged 参数查看 已经合并到当前分支的分支
    --no-merged 参数查看 还未合并进来的分支

利用分支开发的工作流程

想象成不同的流水线
下面是四个不同的分支(纵向看)

1
2
3
4
5
6
7
8
C11       C7
C10       C6
 \    C5 /
  C9  C4
  C8  C2
  C3 /
  C1
  C0
main 主分支 开发分支 功能 A 分支
存放稳定的代码,开发后测试通过 的代码
不允许随便修改和合并
用于团队 日常开发 用,比如团队计划 10 月份开发 10 个功能并发版,则在此分支上进行
不影响主分支的稳定
开发人员根据自己的需要,可以创建一些 临时分支用于特定功能
开发完毕后再合并到开发分支,并删除该分支
  • 长期分支
    仅在 main 分支中保留完全稳定的代码,即已经发布或即将发布的代码
    创建 develop 或 next 的平行分支,专门用于后续的开发,或仅用于稳定性测试
    不一定要绝对稳定,一旦进入某种稳定状态,便可以把它合并到 main 里,等待下一次的发布

  • 特性(Topic)分支
    短期的,用于实现单一特性或者与其相关工作的分支
    上述分支均为本地分支

远程分支 remote branch

远程仓库名/分支名
例如:origin/main
表示 上一次 与 origin 仓库通讯时,main 分支的样子

假设团队的 Git 服务器地址为 git.ourcompany.com
现在将仓库克隆下来,Git 会自动将此远程仓库命名为 origin,并下载其中所有的数据
建立一个指向它的 main 分支的指针,在本地命名为 origin/main但无法在本地更改 origin/main 的内容
接着,Git 建立一个属于你自己的本地 main 分支,与 origin/main 分支位置相同,然后可以就此开始工作

假设有多个远程分支,在不同远程服务器上
我们需要将它们都加为远程仓库

1
2
3
git remote add teamone git://git.team1.com
git remote add teamtwo git://git.team2.com
git remote add teamthree git://git.team3.com

git fetch teamone 用来获取 teamone 小组服务器上,我们自己本地还没有的数据
执行后,我们本地仓库就有了一个指向 teamone/main 分支的索引,但是注意它是无法移动的
如需要合并到当前分支上,还要执行 git merge teamone/main

推送本地分支

我们可以将本地分支推送到一个拥有写权限的远程仓库,和他人分享成果
git push teamone somefix:awesomebranch
这样就把本地的 somefix 分支,推送到了 teamone 远程仓库上,并命名为 awesomebranch 分支

其他人就可以从 teamone 远程仓库拉取你的代码了
例如:git checkout -b newbranch teamone/awesomebranch
这样就在本地新建了一个 newbranch 分支,其内容与你的成果一致
等同于
git checkout --track teamone/awesomebranch
会在本地新建一个 awesomebranch 分支,但是注意这个写法不能对新建的分支进行重命名

跟踪远程分支

上一节通过 git checkout 出来的本地分支称为 跟踪分支
是一种与某个远程分支有直接联系的本地分支

若当前所在分支为跟踪分支,执行 git push 便会自动判断应该向远程仓库中的哪个分支推送数据
git pull 拉取也一样

克隆仓库时,Git 会自动在本地创建一个 main 分支来跟踪远程仓库的 origin/main
这也是为什么 git push 一开始就能使用的原因

删除远程分支

假设某个远程分支已经完成,且合并进了远程的 main 分支中
我们可以将其删除:
git push teamone :awesomebranch
这样就将服务器上的 awesomebranch 分支删除了
可以理解为将本地的空白推送到远程仓库的 awesomebranch 分支上,让它消失

分支衍合 rebase

衍合和三方合并的 快照结果是相同的,区别就是提交历史
rebase 消除了提交历史中的分叉,更为整洁

假设有分支 main 和分支 b
现在要把分支 b 衍合到分支 main 上

1
2
git checkout b
git rebase main

等同于

git rebase main b

  • 原理
    先切换到分支 b 上
    Git 会回到两个分支 最近的共同祖先,根据历次提交对象生成 一系列补丁文件
    然后根据 基底分支 main 分支的 末端提交对象 作为新的出发点,逐个应用这些补丁文件
    最后生成一个新的合并提交对象,并由分支 b 的指针指着

    然后可以切换到 main 分支上进行一次快进合并
    (因为已经是直接上下游关系了)

  • 使用场景
    比如某些项目我们不是维护者,但想帮点忙,那么就可以使用衍合
    先在自己的一个分支里进行开发,当准备向主项目提交补丁的时候
    根据最新的 origin/main 进行一次衍合操作然后再提交,这样项目维护者就不需要做任何整合工作
    (注:实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决) 维护者只需根据你提供的仓库地址作一次快进合并,或者直接采纳你提交的补丁

风险

准则:
一旦分支中的提交对象发布到公共仓库,就不要对该分支进行衍合操作
我们应该把衍合当成一种在 推送之前清理提交历史 的手段

例子:
假设你和同事 Alice 在同一个远程仓库协作
你们都有一个基于 main 分支的本地分支 feature
时间线如下:

1
2
3
4
时间点 0: 你们都从 main 分支创建了 feature 分支 (commit A)
时间点 1: 你提交了 commit B 并推送到远程
时间点 2: Alice 拉取了你的 commit B,并基于它提交了 commit C
时间点 3: 你本地 rebase 了 commit B,然后强制推送

rebase 前的情况

1
2
3
远程仓库: A ← B
Alice 本地: A ← B ← C
你的本地: A ← B

你执行 rebase 后
假设你把 B rebase 成了 B'

1
你的本地: A ← B' (内容与B相同但哈希值不同)

你强制推送到远程后

1
2
远程仓库: A ← B'
Alice 本地: A ← B ← C

产生的问题
历史分叉:现在 Alice 的 commit C 是基于 B 的,但远程的 B 已经被替换为 B’
同步困难

  • 当 Alice 尝试推送她的 commit C 时会被拒绝
  • 她必须执行复杂的操作来使她的 C 适应新的 B’
    重复工作:Alice 可能需要手动解决冲突或重新应用她的更改

如果 B 已经发布并被 Alice 使用,正确的做法应该是使用 git merge 而不是 git rebase
又或者与 Alice 协调,让 Alice 也同步进行 rebase 操作

数据传输协议

本地协议

local protocol
常见于团队每一个成员都对一个 共享的文件系统 拥有访问权
该远程仓库的路径就是本地文件系统中的一个目录,可以进行克隆、推送、获取

1
2
3
4
git clone /opt/git/project.git
git clone file:///opt/git/project.git

git remote add my_localproj /opt/git/project.git

  • 如果已经有一个全体共享的文件系统,那么只需要将 共享的中央仓库的读写权限 分配给团队成员即可


  • 难以控制从不同位置来的访问权限
    如果原始仓库不在你的个人笔记本电脑上,则需要先挂载该目录(如通过 NFS/Samba)才能访问

SSH(Secure Shell)协议

SSH 是同时支持 读写操作 的网络协议
git clone ssh://user@server:project.git

若不指明某个协议,则默认使用 SSH 协议
git clone user@server:project.git


  • 拥有对网络仓库的写权限
    安全性高:协议本身加密了所有传输的数据,包括认证信息,有助于保护数据和代码免受中间人攻击
    速度较快:对于大型仓库或频繁的操作,SSH 可能因为 减少了握手和加密 的开销而提供更快的性能


  • 不便于匿名访问,SSH 访问通常需要验证用户的身份,因此不适合匿名访问公共仓库

扩展
SSL 通信协议:基于 公私钥 进行身份验证
SSH(Secure Shell,安全外壳):一种 网络安全协议,通过公钥、私钥 非对称加密数据和认证机制 实现安全的访问和文件传输等业务,多用来进行远程登录、数据传输

Git 协议

需要先创建 git-export-daemon-ok 文件


  • 传输速度快


  • 缺少授权机制,一般需要和其他方法配合来访问项目
    比较难架设

HTTP/S 协议


  • 易于架设,占用服务器资源少
    支持匿名访问,对于公开仓库,HTTPS 允许用户无需认证即可访问和克隆


  • 克隆仓库花费时间较长
    由于 HTTPS 每次请求都需要进行 TLS 握手和加密/解密
    对于大型仓库或频繁的操作,这可能会引入额外的性能开销

    安全性略低(相对 SSH),虽然 HTTPS 本身是安全的,但它主要保护 数据传输过程 中的安全
    而不是像 SSH 那样提供 端到端的加密和身份验证

实际问题

Please commit your changes or stash them before you merge

本地开发后,某个文件修改后没提交
然后远程有人推送了代码,其中对应的文件有修改
这时本地 git pull 后会提示提交错误

git pull 操作即 git fetch + git merge
在进行合并操作时,存在本地修改的文件尚未被提交到版本控制系统,这些修改会被合并覆盖掉
有两种方式处理这些未提交的修改

1、commit your changes
先进行提交,然后再合并
git add 出现错误提示的文件
git commit -m '提交信息'

2、stash them before you merge
先将文件藏起来,合并结束后再重新应用这些修改
git stash save "合并前,隐藏本地修改"
git pull
git stash list 查看所有隐藏的修改
git stash apply stash@{n} 恢复指定的隐藏的修改

上述提交完或者取消隐藏后可能会出现冲突
需要我们手动解决冲突

1
2
3
4
5
6
7
8
9
<<<<<<

  远端仓库的代码

======

  本地隐藏的代码

>>>>>>

有趣的 Git 闯关练习

展示答案 show solution
选择关卡 levels
重置 reset

下面是关卡中常用的语句

  • git commit
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/1_1.png

    执行一次提交后,产生新的节点 c2

    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/1_1_commit.png

  • git branch newImage
    创建一个名为 newImage 分支
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/1_2_branch.png

  • git checkout newImage; git commit
    切换到 newImage 分支并进行提交
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/1_2_%E5%88%9B%E5%BB%BA%E5%88%86%E6%94%AF%E5%B9%B6%E6%8F%90%E4%BA%A4.png

  • git checkout -b bugFix
    创建并切换到 bugFix 分支
    等同于:git branch bugFix; git checkout bugFix

  • git branch -f main C1
    强制让 main 分支指向 C1
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/2_3_2.png

    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/2_3_2_%E5%BC%BA%E5%88%B6%E5%88%86%E6%94%AF%E7%A7%BB%E5%8A%A8.png

  • git checkout 哈希值
    绝对引用,切换到对应哈希值的提交上,分离出 HEAD

  • git checkout main^ 等同于 git checkout main~1
    相对引用,向上移动一个提交
    让 HEAD 指向 main 的上一次提交
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/5_2.png

    git checkout main~; git checkout main^2; git checkout main~2;

    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/5_2_checkout.png

  • git checkout o/main
    切换到远程分支 o/main,若进行提交,则会变成 HEAD 分离状态,但是实际上 o/main 不会变化

  • git checkout -b feature C2; git push origin feature
    创建一个新的分支 feature 指向哈希值为 C2 的提交,再将本地的 feature 分支推送到远程

  • git checkout -b foo o/main
    设置新的分支 foo 跟踪远程仓库 o/main(原本是本地的main去跟踪)

  • git merge bugFix
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/1_3.png

    将分支 bugFix 合并到当前分支上

    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/1_3_merge.png

  • git rebase main
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/1_4.png

    使得提交记录清晰,将当前分支复制到 main 分支

    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/1_4_rebase.png

  • git rebase -i HEAD~4
    参数-i表示打开交互式的 rebase,在UI界面中可以调整提交的顺序,删除不想要的提交等等

  • git pull
    拉取并合并
    git pull = git fetch + git merge

  • git pull --rebase
    也是拉取后合并,但是 rebase 的提交记录更加简洁

  • git push 推送更新到远程仓库,若失败则可能是因为最新提交的 C3 基于远程分支中的 C1,而远程仓库中该分支已经更新到 C2 了。可以先 git fetch; git rebase o/main; git push 拉取最新的进行合并,再上传。但是若需要使得远程仓库也包含修改的分支,则使用 merge 而不是 rebase

  • git push origin foo
    推送更新远程的 foo 分支

  • git push origin foo^:main
    将本地的 foo 分支的父提交推送到远程的 main 分支上(支持重命名);对应的 push 改成 fetch 也是同理的;冒号前后置空可以用来删除分支

  • cat .git/HEAD
    查看 HEAD 指向

  • git reset HEAD~1
    将当前分支回退到 HEAD~1,但是当前分支还在工作区中只是未 add 到暂存区中
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/2_4.png

    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/2_4_reset.png

  • git revert HEAD~1
    创建与 HEAD~1 相同的新提交,效果相当于撤回,远程分支也就能进行 “撤回” 操作

  • git cherry-pick 哈希值1 哈希值2
    将对应哈希值的提交复制到当前分支进行提交
    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/3_1.png

    git cherry-pick C2 C4

    https://gcore.jsdelivr.net/gh/yjjyjya/mypicbed/about_Other/pic/3_1_cherrypick.png

    • cherry-pick 假设现在已经在 dev 分支上修复完 bug
      我们不想把整个 dev 合并过去
      zhi’xiang需要把 dev 上的这个 bug 修复所做的提交 复制 到 main 分支下
    1
    2
    3
    4
    5
    6
    
    // 在 main 分支上执行:
    git cherry-pick <版本号>
    
    // main 分支下会产生一个新的提交
    // 即 dev 分支上的版本号对应的提交
    // 最后同时更新 main、HEAD,以及工作区
    
  • git commit -amend
    把上一次的 commit 记录去除,修改 commit 信息

  • git tag v1 C1
    建立一个标签 v1,指向提交记录 C1

  • git describe C1
    查看提交的信息

  • ^数字
    可以指定向上移动到某个父提交,git branch 分支名 后面也可以跟上 ^ 指定位置

  • git clone 远程仓库地址
    在本地创建一个远程仓库的拷贝

  • git fetch
    可以理解为单纯的下载远程仓库

  • git fakeTeamwork foo 3
    在远程仓库的 foo 分支上做三次提交