Git安装及使用

学习文件

git学习参考书: https://git-scm.com/book/zh/v2

git学习廖雪峰:https://www.liaoxuefeng.com/wiki/896043488029600

Git 使用规范流程: https://www.ruanyifeng.com/blog/2015/08/git-use-process.html

git commit规范: https://feflowjs.com/zh/guide/rule-git-commit.html

Git分支管理策略: https://www.ruanyifeng.com/blog/2012/07/git.html

常用命令

查看git配置:git config --list

查看关联远程仓库情况:git remote -v

修改主分支名字:git branch -M main

git reset HEAD~1 撤销改动, 使得当前的这个提交不存在, 回到上一个提交

git revert HEAD 撤消改动, 在这个提交以增加新提交的方式, 只不过下一个提交和上一个提交相同, 所以可以远程分享给其他人.

使用习惯

本地新建仓库,想同步到github

  1. 新建文件夹TestGit

  2. 在文件夹下写一些代码

  3. 添加仓库根目录.gitignore文件
    有时候我们不想让一些文件夹或者某类型的文件或者某文件进行版本管理,比如本地的图片库,比如生成的中间文件.class文件,vs2019项目的一些临时文件*.tmp_proj,*_wpftmp.csproj,我们此时可以在仓库根目录添加.gitignore文件进行忽略,git官网提供了一些.gitignore模板,这里也有一个可以根据语言和编辑器自动生成的.gitignore网站,用户可以随意组合已达到自己想要的效果,常用的语句有如下:

    *.a		忽略所有以a结尾的文件
    bin		忽略bin文件夹
    /mtk/*.txt 忽略根目录下mtk目录下的素有*.txt文件
    !/mtk/one.txt 不忽略这个one.txt文件
    

    另外,在github建立远程仓库的时候会让选择.gitignore文件的类型.

  4. 用git作管理:

    git init	//初始化git仓库管理
    git add .	//将本目录下所有文件存储暂存区,并根据.gitignore作文件筛选
    git add *	//不经过.gitignore作文件筛选,全部加入暂存区
    git add file//将file文件提交到暂存区
    git commit -m "xxxx"	//将暂存区的改动提交到版本库
    git branch -M main //将主分支改为main,一定要改,否则推送到远程仓库中为master分支,但是github远程仓库的主分支是main分支
    

    为了让版本信息更加易读,有三种commit的书写方式:

    1. commitizen:AngularJS 在 github上 的提交记录被业内许多人认可,逐渐被大家引用。
    2. 自己用 .git_template 文件设置模板
    3. 直接遵从”type:description”的书写方式,其中type借鉴commitizen有以下几种:
      • fix: 修复bug
      • add: 新功能
      • update: 更新
      • style : 代码格式改变
      • test: 增加测试代码
      • revert: 撤销上一次的commit
      • build: 构建工具或构建过程等的变动,如:gulp 换成了 webpack,webpack 升级等
  5. 关联远程仓库

    1. 在github上建立本地仓库同名的远程仓库TestGit,不要添加任何文件,因为我现在还不会处理这种情况…

    2. 关联远程仓库

      git remote add origin git@github.com:pengber/TestGit.git
      
    3. 第一次推送远程仓库

      git push -u origin main
      

      之后可以省略掉-u这个参数

查看别人的仓库代码

这种情况

  1. 直接克隆到本地查看
    git clone git@github.com:testerSunshine/12306.git

对别人的好代码进行修改

这种情况暂时不是很经常用

  1. 点击别人仓库Fork到自己账户下

  2. clone自己的同名仓库到本地

    git clone git@github.com:pengber/12306.git
    
  3. 自己修改,然后直接可以进行版本管理,不用再进行git remote这一步.

回滚到历史某个版本

参考Git恢复之前版本的两种方法reset、revert(图文详解)

回滚到某个版本, 并且该版本后的内容都不要了

git log			#查看版本号
git reset --hard 版本号
git push -f

切换某个版本看一下代码又切换回来

git checkout 到历史某个版本

git log
git checkout 版本号

创建新分支并切换

git checkout -b bugFix

合并分支

git commit 和 git commit -m区别

git commit -m 后面加一句简述, 是比较简便添加注释的方法

git commit 执行后会打开一个vim命令行, 而这里面会自带一些内容

image-20221112204310183

这时候编辑就可以用文本编辑器编辑比较长的段落了, 并且可以取消掉注释, 然后再这行后面加一些对于新增文件的说明之类的, 比如这样, 最后的结果就是这样的

image-20221112204603946

如果不添加注释, 则会commit fail, 因为git不允许空注释(除非加参数).

Git修改已提交的commit注释

git打标签并推送

git tag v1.0.0, 会对head所处的创建一个标签

git tag -a v1.0.0会创建一个标签, 并且会打开一个vim的注释页面

git push origin v1.0.0会把这个标签push上去, 默认不会push

git push origin --tags会把所有本地tags都push上去

git merge --no-ff dev

--no-ff是禁止快进式合并的意思, Git 合并两个分支时,如果顺着一个分支走下去可以到达另一个分支的话,那么Git 在合并两者时,只会简单地把指针右移, 也就是有这个参数可以很清楚的清楚分支合并的过程.

git merge feature

快进式合并

git merge --no-ff feature

非快进式合并

远程仓库相关操作

同一个本地仓库可以关联不通过的远程仓库,git remote -v 可以查看现在仓库关联了哪些远程仓库,如果同一个仓库关联码云和git两个仓库的话,则可以

git remote add gitee git@github.com:pengber/-git.gitgit remote add github git@gitee.com:pengber2/-git.git   

假设github账户为pengber,假设码云账户为pengber2,其中add后面那个名字就是push 时填写的远程仓库的代号,比如用git push github master 推送到github

如何快速关联/修改Git远程仓库地址
本地仓库关联远程仓库(通过仓库地址SSH关联)

如果新建远端仓库有文件, 和本地仓库其他文件不冲突, 解决办法

git pull 报错 ‘fatal: refusing to merge unrelated histories’ Git error

则可以

git pull origin main --allow-unrelated-histories

分支工作

首先主要版本代码在main分支上, 干活在salve分支上,

以下代码新初始化仓库并形成初始版本

git init .
git branch -M main
git add. 
git commit -m "init code

新建分支并转移到新分支

git checkout -b slave

现在有A.txt, B.txt, 在slave分支修改了B, 现在想在main修改A并push到远端, 并保持slave端与main端的一致性, 如何操作?

首先在slave端提交B的所有修改

git add .
git commit -m "update B"

然后切回main分支进行A的修改并提交push

git checkout main
//修改 A
git add A.txt
git commit -m "update A"

切回slave分支保持main的更新, 然后继续进行工作

git checkout slave
git merge main

更换远程分支

git remote remove origin
git remote add origin SSH地址

Git commit 规范

git commit的注释规范

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

type的取值:

  • feat: 新增feature
  • fix: 修复bug
  • docs: 修改文档
  • sytle: 修改了空格, 格式缩进等, 不改变代码逻辑
  • chore: 改变构建流程, 增加依赖库, 工具等
  • refactor: 代码重构, 没有加新功能或者修复bug
  • perf: 优化相关, 比如提升新能和体验
  • revert: 回滚到上一个版本

Git常用的三个临时分支

  • feat (功能分支)
  • fix (bug分支)
  • release(分支), 我还是新建成pre分支吧

git游戏

教学版本: https://learngitbranching.js.org/?locale=zh_CN

沙盒版本: https://learngitbranching.js.org/?locale=zh_CN&NODEMO=

这个游戏常用的命令

reset 恢复初始状态
undo 撤销
levels 显示所有关卡
show solution 显示本关解决方案

基础篇

1:Git Commit

2: Git Branch

git branch branchName创建一个新分支

git checkout <name>切换到分支

git checkout -b <your-branch-name> 创建并切换新分支

3: Git Merge

git merge <branch>现在head和main都处于C1中, 把branch分支合并到现在head上创建一个新节点, 并前进head指向的main到这个新节点上.

image-20221110223341382

4: Git Rebase

Git Rebase

第二种合并分支的方法是 git rebase。Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。

Rebase 的优势就是可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。

咱们还是实际操作一下吧……

image-20221110224234331

image-20221110224302556

image-20221110223708767

git checkout -b bugFix
git commit
git checkout master
git commit
git checkout bugFix
git rebase master

git rebase hash, 将现在head所拥有的从上一次分叉之后的分支移动到hash目标分支下面

git rebase hash1 hash2, 将hash2所拥有的从上一次分叉之后的分支移动到hash1的分支下面

高级篇

1: 分离 HEAD

HEAD的表示是用一个*来表示现在在哪个分支, HEAD可以移动到分支上, 比如main, bugFix上面, 也可以移动到某个Hash提交记录上, 所以要注意Head在哪里, 可以用git checkout 分支/hash来分离hash, Git官方建议用git switch来代替git checkout.

image-20221110153952310

$ git swich C4

2.相对引用(^)

image-20221110093142913

3.相对引用2(~)

image-20221110093313414

$ git branch -f main C6

$ git checkout HEAD^

$ git branch -f bugFix bugFix~4

$ git branch -f bugFix bugFix~3

4.撤销变更

在 Git 里撤销变更的方法很多。和提交一样,撤销变更由底层部分(暂存区的独立文件或者片段)和上层部分(变更到底是通过哪种方式被撤销的)组成。我们这个应用主要关注的是后者。

主要有两种方法用来撤销变更 —— 一是 git reset,还有就是 git revert。接下来咱们逐个进行讲解。

git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

虽然在你的本地分支中使用 git reset 很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦!

为了撤销更改并分享给别人,我们需要使用 git revert。使用git revert会在我们要撤销的提交记录后面居然多了一个新提交!这是因为新提交记录 C2' 引入了更改 —— 这些更改刚好是用来撤销 C2 这个提交的。也就是说 C2' 的状态与 C1 是相同的。

revert 之后就可以把你的更改推送到远程仓库与别人分享啦。

image-20221110095331310

注意 revert 和 reset 使用的参数不同。

$ git reset HEAD

$ git reset HEAD^

$ git checkout pushed

$ git revert HEAD

移动提交记录

1: Git Cherry-pick

我想要把这个提交放到这里, 那个提交放到刚才那个提交的后面

本系列的第一个命令是 git cherry-pick, 命令形式为:

  • git cherry-pick <提交号>...

如果你想将一些提交复制到当前所在的位置(HEAD)下面的话, Cherry-pick 是最直接的方式了。我个人非常喜欢 cherry-pick,因为它特别简单。

image-20221110103206731

$ git cherry-pick c3 c4 c7

2: 交互式 rebase

我们可以利用交互式的 rebase —— 如果你想从一系列的提交记录中找到想要的记录

交互式 rebase 指的是使用带参数 --interactive 的 rebase 命令, 简写为 -i

如果你在命令后增加了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。

在实际使用时,所谓的 UI 窗口一般会在文本编辑器 —— 如 Vim —— 中打开一个文件。 考虑到课程的初衷,我弄了一个对话框来模拟这些操作。

image-20221110103823125

$ git rebase -i HEAD~4

杂项

1: 只取一个提交记录

应用场景: 为了修BUG, commit了许多次, 但是前几次commit都有一些注释, 只有最后一次完成的真正是修复bug的功能, 所以只需要把这个提交记录接在上一次的主提交后就可以了

image-20221110110820504

$ git checkout main

$ git cherry-pick bugFix

2: 提交的技巧 #1

接下来这种情况也是很常见的:你之前在 newImage 分支上进行了一次提交,然后又基于它创建了 caption 分支,然后又提交了一次。

此时你想对某个以前的提交记录进行一些小小的调整。比如设计师想修改一下 newImage 中图片的分辨率,尽管那个提交记录并不是最新的了。

我们可以通过下面的方法来克服困难:

  • 先用 git rebase -i 将提交重新排序,然后把我们想要修改的提交记录挪到最前
  • 然后用 git commit --amend 来进行一些小修改
  • 接着再用 git rebase -i 来将他们调回原来的顺序
  • 最后我们把 main 移到修改的最前端(用你自己喜欢的方法),就大功告成啦!

当然完成这个任务的方法不止上面提到的一种(我知道你在看 cherry-pick 啦),之后我们会多点关注这些技巧啦,但现在暂时只专注上面这种方法。 最后有必要说明一下目标状态中的那几个' —— 我们把这个提交移动了两次,每移动一次会产生一个 ';而 C2 上多出来的那个是我们在使用了 amend 参数提交时产生的,所以最终结果就是这样了。

也就是说,我在对比结果的时候只会对比提交树的结构,对于 ' 的数量上的不同,并不纳入对比范围内。只要你的 main 分支结构与目标结构相同,我就算你通过。

image-20221110111826046

$ git rebase -i HEAD~2

#移动下分支

$ objective

$ git commit --amend

$ git rebase -i HEAD~2

#移动一下分支

$ git branch -f main c3''

3: 提交的技巧 #2

要在心里牢记 cherry-pick 可以将提交树上任何地方的提交记录取过来追加到 HEAD 上(只要不是 HEAD 上游的提交就没问题)。

image-20221110113904628

$ git checkout c1

$ git cherry-pick c2

$ git checkout main

$ git cherry-pick c2' c3

4: Git Tag

使用git tag v1 C1都可以对c1xxxxxxx加tag.

image-20221110115047798

$ git tag v0 C1

$ git tag v1 C2

$ git checkout v1

5: Git Describe

由于标签在代码库中起着“锚点”的作用,Git 还为此专门设计了一个命令用来描述离你最近的锚点(也就是标签),它就是 git describe

Git Describe 能帮你在提交历史中移动了多次以后找到方向;当你用 git bisect(一个查找产生 Bug 的提交记录的指令)找到某个提交记录时,或者是当你坐在你那刚刚度假回来的同事的电脑前时, 可能会用到这个命令

git describe 的语法是:

git describe <ref>

<ref> 可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会以你目前所检出的位置(HEAD)。

它输出的结果是这样的:

<tag>_<numCommits>_g<hash>

tag 表示的是离 ref 最近的标签, numCommits 是表示这个 reftag 相差有多少个提交记录, hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。

ref 提交记录上有某个标签时,则只输出标签名称

image-20221110115421110

$ git describe

v1_2_gC6

$ git describe main

v0_2_gC2

$ git describe side

v1_1_gC4

$ git describe

v1_2_gC6

$ git commit

高级话题

1: 多次 Rebase

哥们儿,我们准备了很多分支!咱们把这些分支 rebase 到 main 上吧。

但是你的领导给你提了点要求 —— 他们希望得到有序的提交历史,也就是我们最终的结果应该是 C6'C7' 上面, C5'C6' 上面,依此类推。

即使你搞砸了也没关系,用 reset 命令就可以重新开始了。记得看看我们提供的答案,看你能否使用更少的命令来完成任务!

image-20221110155049676

$ git rebase main bugFix

$ git rebase bugFix side

$ git rebase side another

$ git branch -f master another

命令解释

git rebase hash, 将现在head所拥有的从上一次分叉之后的分支移动到hash目标分支下面

git rebase hash1 hash2, 将hash2所拥有的从上一次分叉之后的分支移动到hash1的分支下面

2: 两个父节点

选择父提交记录

操作符 ^~ 符一样,后面也可以跟一个数字。

但是该操作符后面的数字与 ~ 后面的不同,并不是用来指定向上返回几代,而是指定合并提交记录的某个父提交。还记得前面提到过的一个合并提交有两个父提交吧,所以遇到这样的节点时该选择哪条路径就不是很清晰了。

Git 默认选择合并提交的“第一个”父提交,在操作符 ^ 后跟一个数字可以改变这一默认行为。

废话不多说,举个例子。

image-20221110161030958

并且, 这些操作符支持链式操作:git checkout HEAD~^2~2完成的是同样的操作.

image-20221110161146305

$git checkout HEAD~^2~
$git branch bugWork
$git checkout main

正确答案:

git branch bugWork HEAD~^2~

3: 纠缠不清的分支

现在我们的 main 分支是比 onetwothree 要多几个提交。出于某种原因,我们需要把 main 分支上最近的几次提交做不同的调整后,分别添加到各个的分支上。

one 需要重新排序并删除 C5two 仅需要重排排序,而 three 只需要提交一次。

image-20221110163513791


$ git rebase -i HEAD~4

#手动移动并删除C5

$ git branch -f one c2'

$ git branch -f main c5

$ git rebase -i HEAD~4

#手动移动

$ git branch -f main c5

$ git branch -f three c2

$ git branch -f two c2'

答案:

$ git checkout one

$ git cherry-pick C4 C3 C2

$ git checkout two

$ git cherry-pick C5 C4 C3 C2

$ git branch -f three C2

远程仓库

1.git clone

从技术上来讲,git clone 命令在真实的环境下的作用是在本地创建一个远程仓库的拷贝(比如从 github.com)。 但在我们的教程中使用这个命令会有一些不同 —— 它会在远程创建一个你本地仓库的副本。

$git clone

2.远程分支

既然你已经看过 git clone 命令了,咱们深入地看一下发生了什么。

你可能注意到的第一个事就是在我们的本地仓库多了一个名为 o/main 的分支, 这种类型的分支就叫远程分支。由于远程分支的特性导致其拥有一些特殊属性。

远程分支反映了远程仓库(在你上次和它通信时)的状态。这会有助于你理解本地的工作与公共工作的差别 —— 这是你与别人分享工作成果前至关重要的一步.

远程分支有一个特别的属性,在你检出时自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, (更新了远程分支之后)再用远程分享你的工作成果。

你可能想问这些远程分支的前面的 o/ 是什么意思呢?好吧, 远程分支有一个命名规范 —— 它们的格式是:

  • <remote name>/<branch name>

因此,如果你看到一个名为 o/main 的分支,那么这个分支就叫 main,远程仓库的名称就是 o

大多数的开发人员会将它们主要的远程仓库命名为 origin,并不是 o。这是因为当你用 git clone 某个仓库时,Git 已经帮你把远程仓库的名称设置为 origin

不过 origin 对于我们的 UI 来说太长了,因此不得不使用简写 o :) 但是要记住, 当你使用真正的 Git 时, 你的远程仓库默认为 origin!

image-20221110170747492

image-20221110170917825

$ git commit

$ git checkout o/main

$ git commit

3.git fetch

Git 远程仓库相当的操作实际可以归纳为两点:向远程仓库传输数据以及从远程仓库获取数据。既然我们能与远程仓库同步,那么就可以分享任何能被 Git 管理的更新(因此可以分享代码、文件、想法、情书等等)。

本节课我们将学习如何从远程仓库获取数据 —— 命令如其名,它就是 git fetch

你会看到当我们从远程仓库获取数据时, 远程分支也会更新以反映最新的远程仓库。在上一节课程中我们已经提及过这一点了。

git fetch 做了些什么

git fetch 完成了仅有的但是很重要的两步:

  • 从远程仓库下载本地仓库中缺失的提交记录
  • 更新远程分支指针(如 o/main)

git fetch 实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。

如果你还记得上一节课程中我们说过的,远程分支反映了远程仓库在你最后一次与它通信时的状态,git fetch 就是你与远程仓库通信的方式了!希望我说的够明白了,你已经了解 git fetch 与远程分支之间的关系了吧。

git fetch 通常通过互联网(使用 http://git:// 协议) 与远程仓库通信

git fetch 不会做的事

git fetch 并不会改变你本地仓库的状态。它不会更新你的 main 分支,也不会修改你磁盘上的文件。

理解这一点很重要,因为许多开发人员误以为执行了 git fetch 以后,他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来,但是并没有修改你本地的文件。我们在后面的课程中将会讲解能完成该操作的命令 :D

所以, 你可以将 git fetch 的理解为单纯的下载操作。

image-20221110171805866

$git fetch

4.git pull

既然我们已经知道了如何用 git fetch 获取远程的数据, 现在我们学习如何将这些变化更新到我们的工作当中。

其实有很多方法的 —— 当远程分支中有新的提交时,你可以像合并本地分支那样来合并远程分支。也就是说就是你可以执行以下命令:

  • git cherry-pick o/main
  • git rebase o/main
  • git merge o/main
  • 等等

实际上,由于先抓取更新再合并到本地分支这个流程很常用,因此 Git 提供了一个专门的命令来完成这两个操作。它就是我们要讲的 git pull

image-20221110172233280

git pull和上面的效果一模一样.

image-20221110172318539

$ git pull

5. 模拟团队合作

image-20221110172458211

git clone
git fakeTeamwork 2
git commit
git pull

6. Git Push

OK,我们已经学过了如何从远程仓库获取更新并合并到本地的分支当中。这非常棒……但是我如何与大家分享我的成果呢?

嗯,上传自己分享内容与下载他人的分享刚好相反,那与 git pull 相反的命令是什么呢?git push

git push 负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。一旦 git push 完成, 你的朋友们就可以从这个远程仓库下载你分享的成果了!

你可以将 git push 想象成发布你成果的命令。它有许多应用技巧,稍后我们会了解到,但是咱们还是先从基础的开始吧……

注意 —— git push 不带任何参数时的行为与 Git 的一个名为 push.default 的配置有关。它的默认值取决于你正使用的 Git 的版本,但是在教程中我们使用的是 upstream。 这没什么太大的影响,但是在你的项目中进行推送之前,最好检查一下这个配置。

image-20221110173517083

git commit
git commit 
git push

7.偏离的提交历史

假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是 —— 天啊!你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。

这种情况下, git push 就不知道该如何操作了。如果你执行 git push,Git 应该让远程仓库回到星期一那天的状态吗?还是直接在新代码的基础上添加你的代码,亦或由于你的提交已经过时而直接忽略你的提交?

因为这情况(历史偏离)有许多的不确定性,Git 是不会允许你 push 变更的。实际上它会强制你先合并远程最新的代码,然后才能分享你的工作。

image-20221110174002986

image-20221110174030245

很好!但是要敲那么多命令,有没有更简单一点的?

当然 —— 前面已经介绍过 git pull 就是 fetch 和 merge 的简写,类似的 git pull --rebase 就是 fetch 和 rebase 的简写!

让我们看看简写命令是如何工作的。

image-20221110174126607

image-20221110174224869

image-20221110174526882

git clone
git fakeTeamwork 1
git commit
git pull --rebase
git push

8.锁定的Master

远程服务器拒绝!(Remote Rejected)

如果你是在一个大的合作团队中工作, 很可能是main被锁定了, 需要一些Pull Request流程来合并修改。如果你直接提交(commit)到本地main, 然后试图推送(push)修改, 你将会收到这样类似的信息:

! [远程服务器拒绝] main -> main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.)

为什么会被拒绝?

远程服务器拒绝直接推送(push)提交到main, 因为策略配置要求 pull requests 来提交更新.

你应该按照流程,新建一个分支, 推送(push)这个分支并申请pull request,但是你忘记并直接提交给了main.现在你卡住并且无法推送你的更新.

解决办法

新建一个分支feature, 推送到远程服务器. 然后reset你的main分支和远程服务器保持一致, 否则下次你pull并且他人的提交和你冲突的时候就会有问题.

image-20221110174952615

git reset --hard o/master
git checkout -b feature C2
git push origin feature

Git 远程仓库高级操作

1.推送主分支

合并特性分支

既然你应该很熟悉 fetch、pull、push 了,现在我们要通过一个新的工作流来测试你的这些技能。

在大型项目中开发人员通常会在(从 main 上分出来的)特性分支上工作,工作完成后只做一次集成。这跟前面课程的描述很相像(把 side 分支推送到远程仓库),不过本节我们会深入一些.

但是有些开发人员只在 main 上做 push、pull —— 这样的话 main 总是最新的,始终与远程分支 (o/main) 保持一致。

对于接下来这个工作流,我们集成了两个步骤:

  • 将特性分支集成到 main
  • 推送并更新远程分支

image-20221110202704340

$ git fetch

$ git rebase o/main side1

$ git rebase side1 side2

$ git rebase side2 side3

$ git rebase side3 main

$ git push

2: 合并远程仓库

为了 push 新变更到远程仓库,你要做的就是包含远程仓库中最新变更。意思就是只要你的本地分支包含了远程分支(如 o/main)中的最新变更就可以了,至于具体是用 rebase 还是 merge,并没有限制。

那么既然没有规定限制,为何前面几节都在着重于 rebase 呢?为什么在操作远程分支时不喜欢用 merge 呢?

在开发社区里,有许多关于 merge 与 rebase 的讨论。以下是关于 rebase 的优缺点:

优点:

  • Rebase 使你的提交树变得很干净, 所有的提交都在一条线上

缺点:

  • Rebase 修改了提交树的历史

比如, 提交 C1 可以被 rebase 到 C3 之后。这看起来 C1 中的工作是在 C3 之后进行的,但实际上是在 C3 之前。

一些开发人员喜欢保留提交历史,因此更偏爱 merge。而其他人(比如我自己)可能更喜欢干净的提交树,于是偏爱 rebase。仁者见仁,智者见智。 :D

本关,我们还是解决上一关卡中的问题,但是要用 merge 替换 rebase。这显然有点画蛇添足,但这只是为了更好的说明上面的观点。

image-20221110205435768

$ git checkout main

$ git pull

$ git merge side1

$ git merge side2

$ git merge side3

$ git push

3.远程追踪

在前几节课程中有件事儿挺神奇的,Git 好像知道 maino/main 是相关的。当然这些分支的名字是相似的,可能会让你觉得是依此将远程分支 main 和本地的 main 分支进行了关联。这种关联在以下两种情况下可以清楚地得到展示:

  • pull 操作时, 提交记录会被先下载到 o/main 上,之后再合并到本地的 main 分支。隐含的合并目标由这个关联确定的。
  • push 操作时, 我们把工作从 main 推到远程仓库中的 main 分支(同时会更新远程分支 o/main) 。这个推送的目的地也是由这种关联确定的!

直接了当地讲,maino/main 的关联关系就是由分支的“remote tracking”属性决定的。main 被设定为跟踪 o/main —— 这意味着为 main 分支指定了推送的目的地以及拉取后合并的目标。

你可能想知道 main 分支上这个属性是怎么被设定的,你并没有用任何命令指定过这个属性呀!好吧, 当你克隆仓库的时候, Git 就自动帮你把这个属性设置好了。

当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/main)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 main

克隆完成后,你会得到一个本地分支(如果没有这个本地分支的话,你的目录就是“空白”的),但是可以查看远程仓库中所有的分支(如果你好奇心很强的话)。这样做对于本地仓库和远程仓库来说,都是最佳选择。

这也解释了为什么会在克隆的时候会看到下面的输出:

local branch "main" set to track remote branch "o/main"

我能自己指定这个属性吗?

当然可以啦!你可以让任意分支跟踪 o/main, 然后该分支会像 main 分支一样得到隐含的 push 目的地以及 merge 的目标。 这意味着你可以在分支 totallyNotMain 上执行 git push,将工作推送到远程仓库的 main 分支上。

有两种方法设置这个属性,第一种就是通过远程分支检出一个新的分支,执行:

git checkout -b totallyNotMain o/main

就可以创建一个名为 totallyNotMain 的分支,它跟踪远程分支 o/main

image-20221110211330099

image-20221110211408162

image-20221110211448427

image-20221110211546666

$ git checkout -b side o/main

$ git commit

$ git pull --rebase

$ git push

4.git push的参数

我们可以为 push 指定参数,语法是:

git push <remote> <place>

<place> 参数是什么意思呢?我们稍后会深入其中的细节, 先看看例子, 这个命令是:

git push origin main

把这个命令翻译过来就是:

切到本地仓库中的“main”分支,获取所有的提交,再到远程仓库“origin”中找到“main”分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。

我们通过“place”参数来告诉 Git 提交记录来自于 main, 要推送到远程仓库中的 main。它实际就是要同步的两个仓库的位置。

需要注意的是,因为我们通过指定参数告诉了 Git 所有它需要的信息, 所以它就忽略了我们所检出的分支的属性!

image-20221110215556387

image-20221110215620322

$ git push origin foo

$ git push origin main

5.Git Push的参数2

<place>参数详解

还记得之前课程说的吧,当为 git push 指定 place 参数为 main 时,我们同时指定了提交记录的来源和去向。

你可能想问 —— 如果来源和去向分支的名称不同呢?比如你想把本地的 foo 分支推送到远程仓库中的 bar 分支。

哎,很遗憾 Git 做不到…… 开个玩笑,别当真!当然是可以的啦 :) Git 拥有超强的灵活性(有点过于灵活了)

接下来咱们看看是怎么做的……

要同时为源和目的地指定 <place> 的话,只需要用冒号 : 将二者连起来就可以了:

git push origin <source>:<destination>

这个参数实际的值是个 refspec,“refspec” 是一个自造的词,意思是 Git 能识别的位置(比如分支 foo 或者 HEAD~1

一旦你指定了独立的来源和目的地,就可以组织出言简意赅的远程操作命令了,让我们看看演示!

image-20221110220055840

image-20221110220111855

image-20221110220138264

$ git push origin main^:foo
$ git push origin foo:main

6. Git Fetch的参数

Git fetch 的参数

我们刚学习了 git push 的参数,很酷的 <place> 参数,还有用冒号分隔的 refspecs(<source>:<destination>)。 这些参数可以用于 git fetch 吗?

你猜中了!git fetch 的参数和 git push 极其相似。他们的概念是相同的,只是方向相反罢了(因为现在你是下载,而非上传)

<place> 参数

如果你像如下命令这样为 git fetch 设置 的话:

git fetch origin foo

Git 会到远程仓库的 foo 分支上,然后获取所有本地不存在的提交,放到本地的 o/foo 上。

image-20221110220855057

image-20221110220922899

image-20221110220942225

$ git fetch origin foo:main

$ git fetch origin main^:foo

$ git checkout foo

$ git merge main

7.没有Source的Source

古怪的 <source>

Git 有两种关于 <source> 的用法是比较诡异的,即你可以在 git push 或 git fetch 时不指定任何 source,方法就是仅保留冒号和 destination 部分,source 部分留空。

  • git push origin :side
  • git fetch origin :bugFix

image-20221110221714411

image-20221110221733330

image-20221110221757974

$ git push origin :foo
$ git fetch origin :bar

8. Git Pull的参数

Git pull 参数

既然你已经掌握关于 git fetchgit push 参数的方方面面了,关于 git pull 几乎没有什么可以讲的了 :)

因为 git pull 到头来就是 fetch 后跟 merge 的缩写。你可以理解为用同样的参数执行 git fetch,然后再 merge 你所抓取到的提交记录。

以下命令在 Git 中是等效的:

git pull origin foo 相当于:

git fetch origin foo; git merge o/foo

还有…

git pull origin bar~1:bugFix 相当于:

git fetch origin bar~1:bugFix; git merge bugFix

看到了? git pull 实际上就是 fetch + merge 的缩写, git pull 唯一关注的是提交最终合并到哪里(也就是为 git fetch 所提供的 destination 参数)

一起来看个例子吧:

image-20221110222130136

image-20221110222143034

image-20221110222241861

image-20221110222301964

$ git pull origin bar:foo

$ git pull origin main:side

GitAction

简介

GitHub Actions 是一个自动执行任务的一种功能,github 官方提供了免费的 runnner 可供运行,Github-hosted runner就是服务器,有三种操作系统可供使用,分别是windows,UnbuntumacOS,也可以设置 runner 为自己的私有服务器.

Github Actions 结合GitHub仓库就可以实现脚本的自动运行,具体的应用场景有[^ 1]:

  • 自己写了一个pythn脚本,需要每天定时运行或者某个事件(push, pull)发生时运行.
  • 自己构建静态博客项目,将博客从本地 push 到仓库后,需要一些自动化的部署任务进行博客的更新与发布.
  • 一系列开发环境转化为生产环境的部署工作任务的自动化运行.

每个 action 的存在形式就是github仓库上方的 Action 按钮和仓库里配置 Action的文件.github/workflows,这个文件夹下的workflow是yaml格式的文件:

image-20211015142103825

GitHub提供了一些官方市场 ,如果需要某些action,比如配置操作系统运行某个程序的初始环境,直接引用其他人写好的 action 即可.

GitHub Actions组成

GitHub Action 完是自动执行任务,它有如下几个概念:

  1. Envents: 事件,就是处罚workflow工作的事件,常见的有push,pull,也可以是其它事件
  2. Workflows: workflow就是添加到仓库的受事件驱动或者定时的自动运行程序,由至少一个job组成,可以用于构建,测试,封装,发行或者部署任务在github.
  3. job: 一个 job 由多个在 runner上执行的 steps组成.
  4. steps: 一个 step 可以是一个 action 或者一个 shell 命令.每个 job 中的step在相同的runner上.
  5. actions: workflow执行的最小单位, 一个独立的命令.
  6. runners: 就是一个服务器,Github提供的runner有三种操作系统, 也可以配置自己的服务器.

Workflow文件样例

以我自己 fork 的bupt-nov-report-action仓库的 workflow 下的 mainl.yaml为例:

name: Automatically submit the 2019-nCoV report sheet of BUPT

on:
  schedule:
    - cron: "0/10 0 * * *"
  push:

jobs:
  checker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Use Node.js 12.x
        uses: actions/setup-node@v1
        with:
          node-version: '12.x'
      - name: Cache NPM dependencies
        uses: actions/cache@v1
        with:
          path: node_modules
          key: ${{ runner.OS }}-npm-cache
          restore-keys: |
            ${{ runner.OS }}-npm-cache
      - name: Install Dependencies
        run: |
          npm install -g yarn
          yarn --frozen-lockfile --non-interactive
      - name: Run Script
        run: |
          yarn run ts-node src/index.ts
        env:
          BUPT_USERNAME: ${{ secrets.BUPT_USERNAME }}
          BUPT_PASSWORD: ${{ secrets.BUPT_PASSWORD }}
          TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
          TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}

这个GitHub Action干了这么些事情:

  • 定时做这个action
  • 有一个名叫 checker 的 job
  • runner 是ubuntu
  • 这个job下有很多steps,每个steps由 name , uses, run 等关键字组成,做了以下事情:
    • 运行了github.com/action/checkout的 v1 版本的程序,检测环境.
    • 运行了actions/setup-nod@v1, 安装nodejs
    • 安装Npm依赖
    • 然后运行自己仓库的打卡脚本.
  • 这些统一组成了一个自动执行的程序.

参考

[^ 1]: 持续集成,持续交付,持续部署

GitHub Actions 入门教程

Learn GitHub Actions

bupt-ncov-report-action

Telegram bot

Telegram bot也是自动执行脚本,只不过提交方式是在telegram,就类似于在wechat给文件助手发送几个参数或者命令,文件传输助手自动执行部署的任务并且发送结果给终端,和github相似的地方都是自动执行脚本,只不过telegram引入了移动设备.


欢迎在评论区中进行批评指正,转载请注明来源,如涉及侵权,请联系作者删除。

×

喜欢就点赞,疼爱就打赏