hello云胜

技术与生活

0%

关键字

关键字是一门语言预定义的单词,作用是帮助编译器理解和解析源码。

如果查资料会说java有53个关键字,go只有25个关键字。go在标榜自己语法简单时,也经常拿这个说事。

但是,仔细看下会发现,java所说的53个关键字包括了11种基本类型和2个保留字。而go的25个关键字却是剔除了基本类型。所有具体来对比应该是40Vs25。

Java

关键字类别 关键字 关键字含义
访问控制 private 一种访问控制方式:私用模式,访问控制修饰符,可以应用于类、方法或字段(在类中声明的变量)
protected 一种访问控制方式:保护模式,可以应用于类、方法或字段(在类中声明的变量)的访问控制修饰符
public 一种访问控制方式:共用模式,可以应用于类、方法或字段(在类中声明的变量)的访问控制修饰符。
类、方法和变量修饰符 abstract 表明类或者成员方法具有抽象属性,用于修改类或方法
class 声明一个类,用来声明新的Java类
extends 表明一个类型是另一个类型的子类型。对于类,可以是另一个类或者抽象类;对于接口,可以是另一个接口
final 用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变,用来定义常量
implements 表明一个类实现了给定的接口
interface 接口
native 用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的
new 用来创建新实例对象
static 表明具有静态属性
strictfp 精确浮点数运算。基本没用过。
synchronized 表明一段代码需要同步执行
transient 声明不用序列化的成员域
volatile 表明两个或者多个变量必须同步地发生变化
程序控制 break 提前跳出一个块
continue 回到一次循环的开始处
return 从成员方法中返回数据
do 用在do-while循环结构中
while 用在循环结构中
if 条件语句的判断
else 用在条件语句中,表明当条件不成立时的分支
for 循环
instanceof 用来测试一个对象是否是指定类型的实例对象
switch 分支语句结构的引导词
case 用在switch语句之中,表示其中的一个分支
default 默认,例如:用在switch语句中,表明一个默认的分支。Java8 中也作用于声明接口函数的默认实现
错误处理 try 尝试一个可能抛出异常的程序块
catch 用在异常处理中,用来捕捉异常
throw 抛出一个异常
throws 声明在当前定义的成员方法中所有需要抛出的异常
包相关 import 表明要访问指定的类或包
package
变量引用 super 表明当前对象的父类型的引用或者父类型的构造方法
this 指向当前实例对象的引用,用于引用当前实例
void 声明当前成员方法没有返回值,void可以用作方法的返回类型,以指示该方法不返回值
保留字 goto 保留关键字,没有具体含义
const 保留关键字,没有具体含义,是一个类型修饰符,使用const声明的对象不能更新

Go

类别 关键字 含义
类、方法和变量修饰符 chan 定义一个channel
interface 定义接口
func 函数定义
map map结构类型
struct 定义结构体
type 定义类型
var 声明变量
const 声明常量
程序控制 break 提前跳出一个块
continue 回到一次循环的开始处
return 从方法中返回
if 条件语句的判断
else 用在条件语句中,表明当条件不成立时的分支
for 循环
switch 分支语句结构的引导词
case 用在switch语句之中,表示其中的一个分支
fallthrough 如果case带有fallthrough,程序会继续执行下一条case,不会再判断下一条case的值
default 默认选项,用在switch和select语句种
select go语言特有的channel选择结构
defer 延迟执行,函数的收尾工作。在函数结束前执行
go 并发
goto 跳转语句
range 从slice、map等结构中取元素
包相关 import 表明要访问指定的类或包
package

注意点

  1. go主要是去掉了的继承实现相关的关键字。

  2. 去掉了do-while结构,和for功能上重复。

  3. go语言中直接将将多线程的实现作为了关键字。也可见go的并发编程是他的杀手锏。

  4. go去掉了异常处理相关的。

几个陌生的关键字

go中有几个关键字还是比较陌生,先简单看下,以后也会专门写文章

go

go中的多线程编程称为goroutine,目前统一译为协程。

在go语言中进行多线程编程非常简单,只要用go关键字后面加上要执行的逻辑,那么这段代码就会以多线程的方式进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"time"
)

func main() {
for i := 0; i < 10; i++ {
// 启动协程
go func(i int) {
fmt.Println(i)
}(i)
}
time.Sleep(2 * time.Second)
}
1
2
3
4
5
6
7
8
9
10
11
go run .\main.go
9
3
1
2
4
7
8
5
0
6

相比于java的Thread run,go的协程写起来确实简单很多。以后再进行详细对比。

chan

通道。和go协程一样,chan是go进行并发编程的另一个招牌。主要的作用是实现并发同步。大大的降低了并发编程的难度。

go语言的一个设计哲学是

不要通过共享内存来进行多线程通信,而是要通过通信来实现共享内存。

通道机制就是这种设计哲学的一个设计结果。

可以先简单的将通道看作一个先进先出的阻塞队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

func Test_channel(t *testing.T) {
// 创建一个容量为1的只可以放string类型数据的通道
ch1 := make(chan string, 1)
ch1 <- "a"
// 通道已满,再放会阻塞
// ch1 <- "b"
t.Log("继续执行。。。")

e1 := <-ch1
t.Log("e1:", e1)

// 通道已空,再取会阻塞
e2 := <-ch1
t.Log("e2:", e2)
}

这是一个单元测试函数。

make

在创建chan时用到了make函数,make()函数是go语言的一个内置函数。只用于channel,map和slice的创建。

select

select一般和chan一起使用,目的是为了选择某个通道。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func Test_channel4(t *testing.T) {
// 创建三个通道
intChannels := [3]chan int{
make(chan int, 1),
make(chan int, 1),
make(chan int, 1),
}

// 随机选择一个发送
index := rand.Intn(3)
intChannels[index] <- index
t.Logf("向channe%d发送", index)

select {
case e := <-intChannels[0]:
t.Logf("0接收到:%d", e)
case e := <-intChannels[1]:
t.Logf("1接收到:%d", e)
case e := <-intChannels[2]:
t.Logf("2接收到:%d", e)
default:
t.Log("都没选中")
}
}

定义一个chan数组,随机选择一个通道发送,一旦某个通道有数据了,select会自动选中。

defer

defer相比于前面几个,就很好理解了。

延迟执行,也就是在函数结束前执行,用于执行程序出现运行时错误时的兜底处理。

就是java的try-catch-finally

举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
defer func() {
fmt.Println("正常退出")
}()
fmt.Println("=======hello=======")
defer func() {
// 使用go内置函数recover恢复panic
v := recover()
fmt.Println("恐慌被恢复了:", v)
}()
panic("使用panic函数产生一个恐慌!")
fmt.Println("执行不到这里")
}

执行结果

1
2
3
=======hello=======
恐慌被恢复了: 使用panic函数产生一个恐慌!
正常退出

panic()和recover()都是go语言的内建函数

panic()用于手动创建一个运行时错误。recover()专门用于恢复。

recover()必须用在defer延迟里。

trunk-based开发管理模型实践

目前业界主流的版本管理流程是Gitflow 和 trunk-based。

Gitflow流行的比较早。但是目前的流行度要低于 trunk-based模式工作流。trunk-based模式被誉为是现代化持续结成的最佳实践。

他俩的核心区别是,Gitflow是一个更严格的流程,只需要特性的管理员来批准代码可以合入主干。这样可以保证主干分支的代码质量。

而trunk-based流程相对而言更开放,所有开发者都有权限合入主干,以此来达到团队快速迭代功能的目标。

trunk-based流程

img

  1. 所有研发人员直接在trunk上提交代码

  2. 对外发布产品的时候需要从trunk上拉取release分支(比如1.1.x和1.2.x),并基于release分支来发布(比如1.1.0和1.1.1)

  3. release分支中出现的bug或者需要性能优化时,则需要在trunk上完成,并通过cherry-pick的方式在trunk中挑选对应的commits合并到release分支,此时的小版本号从1.1.0变成1.1.1

  4. 对外发布新功能时,需要基于trunk分支,重新拉取release分支,版本号从1.1.x变成1.2.x,同时1.1.x的release分支将被废弃

  5. trunk分支上,每个commit之间的提交间隔很短,通常在一天之内提交好几次,甚至更多次

trunk-based vs. Gitflow

Trunk-based流程的特点在于其名字,基于主干进行开发。

鼓励(或者说要求)开发者应频繁的将小的改动及早的合入主干分支。

这样开发者会创建短命的分支,而不是创建生命很长的特性分支。

Trunk-based是现代DevOps生命周期的常用实践。实际上Trunk-based是CI/CD的必备实践。

随着代码库复杂性和团队规模的增加,Trunk-based模式会比Gitflow流程显著降低代码冲突,而保证生产版本的顺利迭代。

Gitflow工作流基于master主干分支和develop开发分支,这俩个长期分支。

以及hotfixes, features, 和releases三个不同功能的临时性功能分支。

这三个分支根据其角色不同,又会有不同的合并策略。

Trunk-based流程相比于gitflow流程更加简单,没有那么多不同的分支需要管理。开发和发版都基于主干进行。

在Trunk-based流程下,主干分支应该始终保持稳定,没有问题,随时可以进行部署。

而这也是cicd的必备条件。

trunk-based流程的优点

  1. trunk-based流程是实现持续集成的必要条件。我们已经搭建好了自动化的部署和测试流程,但是我们的开发还是使用相互独立的分支,合并代码也是开发完一个大功能才进行合并,那么就很难发挥持续集成的优势。

    trunk-based流程中所有人基于main主干开发。我们在main主干上配置自动化的部署和测试和代码检查工具。

  2. trunk-based流程可以有助于降低代码合并时的冲突。因为trunk-based流程要求高频次小规模的提交,所以即使冲突,解决起来的难度也比较低。

  3. trunk-based流程要求高频次小规模的提交,使代码review更加高效和靠谱。

  4. 开发团队应该每天都提交合并代码。并且要始终保证主干代码的状态的绿的。也就是说主干始终都是可用的,随时可以进行部署交付的。

最佳实践

小规模高频次的开发任务

trunk-based流程注重快速的迭代节奏。保持较小的提交和分支可以更快地进行合并和部署。

只有几行代码的小改动更有利于减少代码审核人员的认知压力。

所以我们应该将任务划分成许多可以在1天以内完成的小模块

Feature flag功能标签

Feature flag的作用就是让开发者可以将开发的代码先提交,但是不启用。等以后再启用。

这样做的目的是可以避免创建新的特性分支,而是直接在main主干上开发新功能。

这样说有点抽象,举个例子

Feature flag是一个代码开发上的手段,说直白了就是代码中嵌入特性开关这种判断。

比如这种js代码

1
2
3
if(featureFlags[‘new-cool-feature’] == true){
renderNewCoolFeature();
}

通过这种开关,即使我们的功能没有完全开发完成,也可以先进行提交,只要能保证通过编译部署。

还可以通过Feature flag实现灰度等功能

![Feature Flagging Diagram - Desktop](D:\github\docs\git\trunk-based开发管理模型.assets\Feature Flagging Diagram - Desktop.png)

全面的自动化测试

自动化测试也是现代化CICD不可或缺的组成部分。

团队中的每一名开发都应该针对自己的功能模块来编写对应的单元测试,并在开发结束之后,在本地运行这些单元测试来验证代码的功能。

在开始开发新功能是,从主干拉取代码到本地后,首先要执行单元测试,已确认基础代码是ok的。

单元测试的原则是每一个单元测试能够独立运行并且能够在短时间内(5分钟)运行结束

及时的代码review

代码review放在自动化测试和自动化代码检查之后,因为已经通过了test和code coverage。reviewer只需要关注代码的功能和性能方向。所以同事进行review所需的时间不会很长。

这可以保证及时完成代码review,然后将代码尽早合入主干

每天至少合并主干一次

研发人员可以直接在trunkmaster分支上提交代码,当然也可以拉取feature分支,但是feature分支的生命周期应该在1天之内

分支数量不应该超过3个

分支数量不应该超过3个的说法可以讨论,底线是不应该超过这个代码仓库下开发人员的人数。

比如这个项目有5个开发,那么最大程度上,每人拉取一个feature分支。

进行了分支代码merge之后,最佳实践是删除无用的分支。

高效的CI服务

因为每一个提交到trunk上的改动,都会自动地触发CI服务。所以必须保证ci服务高效执行,能够快速完成构建和测试。

主干的可用性为第一要务

如果提交的代码不小心破坏了主干代码。必须立即停下手头的工作。优先修复主干问题。

如果这个问题无法快速解决,那么需要将此次提交撤销,并回退到上一次提交。这么做就是要确保trunk分支随时可用。

总之

Trunk-Based 流程更多的是依赖于人的行为,需要团队中的每一名成员都要掌握流程规范。

然后在一致的行为下应用自动化工具才能够从整体上提高企业的研发效率

代码同时提交到Github和码云

因为网络问题,有时Github会非常慢。可以在Github和码云同步同一个仓库,做一个备份。

以后push代码要提交到两个仓库里。

我的代码最初是在码云里。

不管是github还是码云都可以直接复制其他的仓库。创建仓库的时候选择import即可。

查看当前仓库信息:

1
2
3
4
$ git remote -v
origin https://gitee.com/helloyunsheng/docs.git (fetch)
origin https://gitee.com/helloyunsheng/docs.git (push)

现在时只有码云的地址

添加新的远程仓库地址

1
$ git remote add github https://github.com/jedyang/docs.git

再次查看

1
2
3
4
5
6
$ git remote -v
github https://github.com/jedyang/docs.git (fetch)
github https://github.com/jedyang/docs.git (push)
origin https://gitee.com/helloyunsheng/docs.git (fetch)
origin https://gitee.com/helloyunsheng/docs.git (push)

提交代码时

1
git push github master

可以指定提交哪个分支到哪个仓库

​ git config –global –edit

或者

git config –global user.name “Your Name”
git config –global user.email you@example.com

​ git commit –amend –reset-author

git解决换行符灾难

由于历史原因,不同的操作系统,在处理换行符时,使用了不同的方案。Windows 操作系统使用了 CRLF,而 Unix 阵营的操作系统则使用了 LF。Mac OS 最起初使用了 CR,后来到了 Mac OS X 后,改成了使用 LF,与 Unix 阵营保持了一致。虽然目前很多代码编辑器都支持自动识别和切换换行符风格,然而,总有那么一些不合群的编辑器,无法达到相应的兼容性。

我们的项目里有一些sh脚本,如果使用 CRLF 风格的换行符,bash 解释器可能无法正常工作;

8526589-441eb6dffbedf594

如图所示。

而且有一些同事的编辑器,一旦你打开文件。会自动帮助你默默的把换行符改成当前操作系统的风格。

这就在不知不觉中导致打的包部署失败。

解决

git提供了.gitattributes文件,可以配置禁止sh文件转换换行符,始终保持LF换行。

image-20200331103017245

对于sh文件,标记为文本文件,在文件入Git库时进行规范化,即行尾为LF。在检出到工作目录时,行尾也不会转换为CRLF(即保持LF)。

使用 *.bat text eol=crlf 就可以保证 Windows 的批处理文件在 checkout 至工作区时,始终被转换为 CRLF 风格的换行符;

gitattributes还有其他的配置,搜索即可。

GIT操作指南

[TOC]

作者 技术笔记与开源分享
版本 1.0
更新时间 2020-3-15

Git与SVN的主要区别

Svn是集中式的版本控制系统,而git是分布式的

集中式就存在单点故障风险,一旦SVN的远程仓库挂掉了,那我的本地项目再也不能做提交,也不能做分支的切换,也不能够干和版本管理相关的任何事情。更极端一点,如果svn的服务器磁盘故障,虽然我们可能利用某些员工本地的快照版本尽可能恢复工程,但是当我们把这个相对完整的版本重新部署到服务器的新仓库时,将会丢失所有的历史版本包括日志。

git是分布式的,表示我们每个人的本地项目都包含一个完整Git仓库。这一点一定要牢记,本地也是一个仓库

.svn只是简单的保存一些版本信息,但是如果你把.git目录的体积大小跟.svn比较,你会发现它们差距很大。因为.git目录是处于你的机器上的一个克隆版的版本库,并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。

基本工作原理的不同

这部分最关键,请耐心看完。理解git的思想,而不是单纯的会使用几个命令。知其然,更要知其所以然。

SVN是记录每个文件每个版本的差异变化,可以说是基于文件差异的版本控制

deltas

如图,svn是存储每个文件在版本的不断迭代中产生的差异

git不一样。如下图所示,git在你每次提交时,都会对所有文件产出一个快照。当然,如果文件没有修改,本次的快照只是生成一个链接,指向之前的文件,不会再copy一份文件。这和docker的分层镜像有点相似。

snapshots

对比两张图,git倾向于纵向切分,每一个版本是一个清晰独立的版本。而svn的每一个版本,需要在前一个版本的基础上追加差异才能得到。

代码提交方式

svn相对来说更容易上手,更新代码–>开发代码–>提交代码commit

git相对来说要复杂一点,更新代码–>开发代码–>add –>commit –>push

为什么步骤这么多,因为本地也是一个仓库

本地操作

因为git在本地是一个完整的仓库。所以绝大部分git命令都不需要访问远程仓库。

例如要浏览项目的历史,Git 不需外连到服务器去获取历史,只需直接从本地数据库中读取。

Git理论基础

首先明确git中的术语

仓库

2528995214-59b0b456ecf1b_articlex

workspace:工作区。当前你修改的代码所在的地方。

index:暂存区。执行 git add命令后变动会提交到这。进入暂存区的变动才开始被git管理。

repository:本地仓库。git commit命令

remote:远端仓库/中央仓库。git push命令。

下面这张图更详细一点:

669fea4aa86d4b97bed433bea954ec16

分支

有人把 Git 的分支模型称为它的“必杀技特性”,也正因为这一特性,使得 Git 从众多版本控制系统中脱颖而出。 为何 Git 的分支模型如此出众呢? Git 处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。理解和精通这一特性,你便会意识到 Git 是如此的强大而又独特

前面我们讲过,Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 快照 。

Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。

head-to-master

如图,最早的提交时98ca9,每次提交后,整个master分支不断变长。HEAD是一个特殊指针,告诉git,我们现在在哪个分支上。

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

然后,我们在f30ab这个提交上,使用git branch命令创建一个testing分支。(git branch命令只是创建分支,并不会切换分支)

使用git checkout testing进行分支切换

head-to-testing

现在head指针就指向了testing分支,我们所作的改动全部发生在testing分支上。

advance-testing

做一次新的改动提交。testing分支继续变长。但是master分支依然指向f30ab。

如果现在我们想回到master分支继续开发,只需要git checkout master切回master分支。

注意,这时你的工作目录恢复成 master 分支所指向的快照内容。 也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略 testing 分支所做的修改,以便于向另一个方向进行开发。

这与过去大多数版本控制系统形成了鲜明的对比,它们在创建分支时,将所有的项目文件都复制一遍,并保存到一个特定的目录。。所需时间的长短,完全取决于项目的规模。 而在 Git 中,任何规模的项目都能在瞬间创建新分支。 同时,由于每次提交都会记录父对象,所以寻找恰当的合并基础(即共同祖先)也是同样的简单和高效。

创建新分支的同时切换过去

通常我们会在创建一个新分支后立即切换过去,这可以用 git checkout -b 一条命令搞定。

Git基本操作

常用命令

4389199-ee631c43d3f89b99

分支操作

常用命令

git branch 列出所有本地分支
git branch -r 列出所有远程分支
git branch -a 列出所有本地分支和远程分支
git branch [branch-name] 新建一个分支,但依然停留在当前分支
git checkout -b [branch-name] 新建一个分支,并切换到该分支
git branch –track [branch] [remote-branch] 新建一个分支,与指定的远程分支建立追踪关系
git checkout [branch-name] 切换到指定分支,并更新工作区
git branch -d [branch-name] 删除分支
git push origin –delete [branch-name] 删除远程分支

merge操作

4389199-9f9069d6edd455fa

merge命令把不同的分支合并起来。如上图,在实际开放中,我们可能从master分支中切出一个分支,然后进行开发完成需求,中间经过R3,R4,R5的commit记录,最后开发完成需要合入master中,这便用到了merge。

git fetch [remote] merge之前先拉一下远程仓库最新代码
git merge [branch] 合并指定分支到当前分支

一般在merge之后,会出现conflict,需要针对冲突情况,手动解除冲突。主要是因为两个用户修改了同一文件的同一块区域。如下图所示,需要手动解除。

4389199-01f5ea82c147586a

rebase操作

4389199-69fa6b680835ecf5

rebase又称为衍合,是合并的另外一种选择。

在开始阶段,我们处于new分支上,执行git rebase dev,那么new分支上新的commit都在master分支上重演一遍,最后checkout切换回到new分支。这一点与merge是一样的,合并前后所处的分支并没有改变。head依然指向new分支

git rebase dev,通俗的解释就是new分支想站在dev的肩膀上继续下去。rebase也需要手动解决冲突。

rebase与merge的区别

现在我们有这样的两个分支,test和master,提交如下:

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

在master执行git merge test,然后会得到如下结果:

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

在master执行git rebase test,然后得到如下结果:

1
A---B---D---E---C'---F'   test, master

可以看到,merge操作会生成一个新的节点,之前的提交分开显示。而rebase操作不会生成新的节点,是将两个分支融合成一个线性的提交。

如果你想要一个干净的,没有merge commit的线性历史树,那么你应该选择git rebase
如果你想保留完整的历史记录,并且想要避免重写commit history的风险,你应该选择使用git merge

reset

4389199-dac56d87e51f61ca

reset命令把当前分支指向另一个位置,并且相应的变动工作区和暂存区。这个命令还是比较危险的。操作时请确保你知道你要做什么。

git reset —soft [commit] 只改变提交点,暂存区和工作目录的内容都不改变
git reset —mixed [commit] 改变提交点,同时改变暂存区的内容
git reset —hard [commit] 暂存区、工作区的内容都会被修改到与提交点完全一致的状态
git reset –hard HEAD 让工作区回到上次提交时的状态,本地的修改都不要了。

revert

4389199-dc3986ab2b51cab9

git revert用一个新提交来消除一个历史提交所做的任何修改。如上图,将提交回滚到15df9b6。

revert与reset的区别

4389199-98ff805e35213952

  • git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。

  • 在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现,减少冲突。但是git reset是之间把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入,产生很多冲突。

  • git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。

分支策略

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

push

上传本地仓库分支到远程仓库分支,实现同步。

git push [remote] [branch] 上传本地指定分支到远程仓库
git push [remote] –force 强行推送当前分支到远程仓库,即使有冲突不建议使用
git push [remote] –all 推送所有分支到远程仓库

打标签操作

Git 支持两种标签:轻量标签(lightweight)与附注标签(annotated)。

1
git tag v1.0   //轻量标签
1
git tag -a tagName -m "my tag"   //附注标签。建议使用

后期打标签

假如你忘记了对某个提交打标签,可以后期补上

1
git tag -a v1.2 9fceb02  -m "补打标签"

将tag同步到远端仓库

默认情况下,git push 命令并不会传送标签到远程仓库服务器上。 在创建完标签后你必须显式地推送标签
使用git push origin [tagName]推送单个分支。

1
git push origin v1.0

推送本地所有tag,使用git push origin --tags

切换到某个tag

跟分支一样,可以直接切换到某个tag去。

1
git checkout v1.0

但是注意:这个时候不位于任何分支,处于游离状态,可以考虑基于这个tag创建一个分支。

1
git checkout -b 新分支名 v1.0

其他命令

git status 显示有变更的文件
git log 显示当前分支的版本历史
git diff 显示暂存区和工作区的差异
git diff HEAD 显示工作区与当前分支最新commit之间的差异
git cherry-pick [commit] 选择一个commit,合并进当前分支

Git问题解决

员工密码定期修改后的问题

我们的git仓库bitbucket密码是和员工账号密码绑定的。我们的员工密码每两个月会强制修改一次,导致git密码校验失败。

错误显示:remote error: CAPTCHA required

bitbucket

解决:

1,打开控制面板;

2.点击打开用户账户;

83809ca8974148768c306ef15125a374

3.点击打开凭证管理(windows凭证管理栏)

4ec643c10d244dabb2756208d23bee08

4.普通凭证下拉打开修改你已存在的git账号密码

b88aae9c533449f5b8e3f3897fb1108d

修改或删除都可以

  1. 回到浏览器上把账户退出,重新登录下

    这一步经常会漏。

放弃本地修改

本地代码被改乱了。想要放弃重来。分三种情况。

1. 未使用git add 缓存代码

  • 放弃某个文件的修改

    注意中间有–

1
git checkout -- filename
  • 放弃所有文件修改 git checkout .
1
git checkout .
  • 此命令用来放弃掉所有还没有加入到缓存区(就是 git add 命令)的修改:内容修改与整个文件删除
  • 注意:此命令不会删除新建的文件,因为新建的文件还没加入git管理系统中,所以对git来说是未知,只需手动删除即可

2. 已使用git add 缓存代码,未使用git commit

  • 使用 git reset HEAD filename
1
git reset HEAD filename
  • 放弃所有文件修改 git reset HEAD
1
git reset HEAD

此命令用来清除 git 对于文件修改的缓存。相当于撤销 git add 命令所做的工作。在使用本命令后,本地的修改并不会消失,而是回到了第一步1. 未使用git add 缓存代码,继续使用用git checkout – filename,就可以放弃本地修改

3. 已经用 git commit 提交了代码

  • 使用 git reset –hard HEAD^ 来回退到上一次commit的状态
1
git reset --hard HEAD^
  • 或者回退到任意版本git reset –hard commit id ,使用git log命令查看git提交历史和commit id
1
git reset --hard commit id

linux下配置git保存用户名和密码

在用户根目录(~)下,使用 touch 命令创建文件 .git-credentials :

1
touch .git-credentials
  1. 然后用 vim 命令编辑此文件:
1
vim .git-credentials

按 i 键进入编辑模式,输入:

1
2
http://{你的用户名}:{你的密码}@你的服务器地址
https://{你的用户名}:{你的密码}@你的服务器地址

注意:去掉 {}

  1. 在终端下执行如下命令:
1
git config --global credential.helper store
  1. 可以看到 ~/.gitconfig 文件会多一项:
1
2
3
4
cat .gitconfig

[credential]
helper = store

说明已经配置好了,再次 push 或 pull 试试看吧,不需要输入密码了。

centos 安装git

源码安装

1
2
3
4
5
6
7
8
9
$ cd /tmp
$ wget --no-check-certificate https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.36.1.tar.gz
$ tar -xvzf git-2.36.1.tar.gz
$ cd git-2.36.1/
$ ./configure
$ make
$ sudo make install
$ git --version # 输出 git 版本号,说明安装成功
git version 2.36.1

把 Git 的二进制目录添加到 PATH 路径中,不然 Git 可能会因为找不到一些命令而报错

1
2
3
4
tee -a $HOME/.bashrc <<'EOF'
# Configure for git
export PATH=/usr/local/libexec/git-core:$PATH
EOF

配置git

1
2
3
4
$ git config --global user.name "Lingfei Kong"    # 用户名改成自己的
$ git config --global user.email "colin404@foxmail.com" # 邮箱改成自己的
$ git config --global credential.helper store # 设置 Git,保存用户名和密码
$ git config --global core.longpaths true # 解决 Git 中 'Filename too long' 的错误

其他

在 Git 中,我们会把非 ASCII 字符叫做 Unusual 字符。这类字符在 Git 输出到终端的时候默认是用 8 进制转义字符输出的(以防乱码),但现在的终端多数都支持直接显示非 ASCII 字符,所以我们可以关闭掉这个特性,具体的命令如下

1
$ git config --global core.quotepath off

这么说你可能不知道解决的什么问题,具体效果是这样的

GitHub 限制最大只能克隆 100M 的单个文件,为了能够克隆大于 100M 的文件,我们还需要安装 Git Large File Storage

1
$ git lfs install --skip-repo

git-flow实践

git-flow有两个涵义,一个是指软件开发领域的版本管理流程Gitflow。另一个是指git命令工具git flow。

目前业界主流的版本管理流程是Gitflow 和 trunk-based。

Gitflow流行的比较早。但是目前的流行度要低于 trunk-based模式工作流。trunk-based模式被誉为是现代化持续结成的最佳实践。

他俩的核心区别是,Gitflow是一个更严格的流程,只需要特性的管理员来批准代码可以合入主干。这样可以保证主干分支的代码质量。

而trunk-based流程相对而言更开放,所有开发者都有权限合入主干,以此来达到团队快速迭代功能的目标。

但是,在很多注重质量的大型公司,出于对主干代码的控制权考虑,还是会使用Gitflow 工作流(或其变种)。

我们通过结合使用git-flow工具来了解Gitflow工作流。

安装git-flow工具

git-flow工具是进行Gitflow实践的有力工具。

windows下安装很简单,其实就是安装git工具。安装好之后,git flow就已经可以使用了。

下载https://git-scm.com/download/win

image-20230419112959938

使用

git-flow工具实际上就是git命令的包装。执行一个git flow命令相当于标准的 Git 命令用脚本组合了起来进行执行。

所以git-flow工具并没有什么神奇之处。只是会让你的操作更加高效。

在一个新项目上进行测试。

init

git flow init进行git仓库的初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git flow init
Initialized empty Git repository in D:/temp/gitflow/.git/
No branches exist yet. Base branches must be created now.
Branch name for production releases: [master]
Branch name for "next release" development: [develop]

How to name your supporting branch prefixes?
Feature branches? [feature/]
Bugfix branches? [bugfix/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? []
Hooks and filters directory? [D:/temp/gitflow/.git/hooks]

git flow的初始化,相比于git init的初始化,会自动创建分支。

gitflow工作流主要使用两个分支。

![01 How it works](D:\github\docs\git\Gitflow实践.assets\01 How it works-16818869602003.svg)

master分支是生产分支。不能直接工作在这个 master 分支上。不直接提交改动到 master 分支上是gitflow工作流程的一个基本规则。在master分支通过tag切出生产版本。

develop分支是开发分支。是我们进行任何新的开发的基础分支。比如新的特性分支从develop分支上拉出来。

一般最好就保持默认的分支名字。

这两个分支被称作为 长期分支。它们会存活在项目的整个生命周期中。

而其他的分支,例如针对功能的分支,针对release的分支,仅仅只是临时存在的。它们是根据需要来创建的,当它们完成了自己的任务之后就会被删除掉。

初始化时会设定这些功能分支的前缀名。

1
2
3
$ git branch -a
* develop
master

feature branch

进行新的功能开发时,我们应该切出一个新的特性分支。

feature branch是从develop分支切出。

功能开发完成后,feature 分支也是merge会develop分支,feature 分支不应该直接同main分支发生联系。

![02 Feature branches](D:\github\docs\git\Gitflow实践.assets\02 Feature branches.svg)

创建feature分支

使用git flow创建一个feature分支

1
2
3
4
5
6
7
8
9
10
11
$ git flow feature start add-user
Switched to a new branch 'feature/add-user'

Summary of actions:
- A new branch 'feature/add-user' was created, based on 'develop'
- You are now on branch 'feature/add-user'

Now, start committing on your feature. When done, use:

git flow feature finish add-user

我创建一个名为add-user的特性分支。gitflow会自动加上feature前缀。

这里执行的git操作其实就是

1
2
git checkout develop
git checkout -b add-user

提交分支

在我们开发完成这个功能后,需要把提交merge回develop分支。

如果直接使用git,应该这么操作

1
2
git checkout develop
git merge add-user

现在使用git flow,直接这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git flow feature finish add-user
Switched to branch 'develop'
Updating e38e2e6..744118e
Fast-forward
AddUser.java | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 AddUser.java
Deleted branch feature/add-user (was 744118e).

Summary of actions:
- The feature branch 'feature/add-user' was merged into 'develop'
- Feature branch 'feature/add-user' has been locally deleted
- You are now on branch 'develop'

并且会自动将feature/add-user删除

release branch

在一个开发周期内,我们在创建feature分支,开发完成,合并回develop。当所有的功能都开发完了,或者预定的发版日快到了时,我们就需要切出一个release分支,用来发预生产测试。

release分支也是从develop分支切出的。

release分支切出后,新的feature不应该合入release分支,只有紧急的bug修复,或者文档类工作可以合入。

如果这个release要进行交付,那么将个release分支合入main主干分支,并打上tag。

并且,这个release分支也要merge回develop分支,因为可能有了新的修改。

![03 Release branches](D:\github\docs\git\Gitflow实践.assets\03 Release branches.svg)

通过release分支,可以很好的控制开发节奏。也就是版本火车。

创建release分支

使用git flow创建一个release分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git flow release start 0.1.0
Switched to a new branch 'release/0.1.0'

Summary of actions:
- A new branch 'release/0.1.0' was created, based on 'develop'
- You are now on branch 'release/0.1.0'

Follow-up actions:
- Bump the version number now!
- Start committing last-minute fixes in preparing your release
- When done, run:

git flow release finish '0.1.0'

注意,release 分支是使用版本号命名的。这是一个最佳实践。当我们完成了release 后,git-flow 会自动去标记那些 release 提交。

然后我们会拿这个版本的release分支去测试。完成后就拿去发版了。

发布release

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ git flow release finish 0.1.0
Switched to branch 'master'
Merge made by the 'recursive' strategy.
AddUser.java | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 AddUser.java
Already on 'master'
Switched to branch 'develop'
Already up to date!
Merge made by the 'recursive' strategy.
Deleted branch release/0.1.0 (was 744118e).

Summary of actions:
- Release branch 'release/0.1.0' has been merged into 'master'
- The release was tagged '0.1.0'
- Release tag '0.1.0' has been back-merged into 'develop'
- Release branch 'release/0.1.0' has been locally deleted
- You are now on branch 'develop'

发布release做的事情就是

  • merge到master
  • merge回develop
  • 删除release 分支
  • 切到develop分支

hotfix branch

很多时候,我们会发现已经release出的版本有bug,hotfix分支就是进行已发布产品bug修复的分支。

![04 Hotfix branches](D:\github\docs\git\Gitflow实践.assets\04 Hotfix branches.svg)

创建hotfix分支

创建的命令和之前的很相似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git flow hotfix start userbug-fix
Switched to a new branch 'hotfix/userbug-fix'

Summary of actions:
- A new branch 'hotfix/userbug-fix' was created, based on 'master'
- You are now on branch 'hotfix/userbug-fix'

Follow-up actions:
- Start committing your hot fixes
- Bump the version number now!
- When done, run:

git flow hotfix finish 'userbug-fix'

hotfix分支特殊之处在于它是从main主干分支上切出来的。

这也是唯一会从main主干分支上切出来的一种分支。

完成修复

当hotfix分支完成修复后,要merge到main分支,并打上新的tag

同时也要merge回到develop分支

1
2
3
4
5
6
7
8
9
10
$ git flow hotfix finish userbug-fix
Switched to branch 'develop'
Deleted branch hotfix/userbug-fix (was cfbf979).

Summary of actions:
- Hotfix branch 'hotfix/userbug-fix' has been merged into 'master'
- The hotfix was tagged 'userbug-fix'
- Hotfix branch 'hotfix/userbug-fix' has been locally deleted
- You are now on branch 'develop'

总结

Gitflow工作流是众多基于git的工作流中的一种。

尤其适合于基于release版本的工作流程。

Gitflow工作流基于master主干分支和develop开发分支,这俩个长期分支。

以及hotfixes, features, 和releases三个不同功能的临时性功能分支。

这三个分支根据其角色不同,又会有不同的合并策略。

git flow工具是git命令的一个小扩展。主要作用是标准化了切分支,merge分支的流程。

使用git flow工具不是执行Gitflow工作流的必要条件。但是是执行Gitflow工作流的一个最佳实践。

gitflow的缺点:

开发者根据业务需求,创建自己的特性分支进行功能开发,这会是一个生命时间很长的分支,直到完成并测试通过了这个功能特性,这个分支的代码才会允许合并入主干。

这就可能导致大量的合并冲突。

并且gitflow会创建多个不同角色的分支,这进一步增加了分支管理的复杂性。

jenkins.JenkinsException: Could not parse JSON info for server

image-20230914153740342

原因时requests的版本太高了

image-20230914153849753

降到2.27.1就好了

1
pip install -U requests==2.27.1 -i https://pypi.tuna.tsinghua.edu.cn/simple

image-20230914153929045

stopBuild

1
2
3
4
5
6
7
8
9
10
def stop_build(self, name, number):
'''Stop a running Jenkins build.

:param name: Name of Jenkins job, ``str``
:param number: Jenkins build number for the job, ``int``
'''
folder_url, short_name = self._get_job_folder(name)
self.jenkins_open(requests.Request(
'POST', self._build_url(STOP_BUILD, locals())
))

停止一个构建,要传构建的名字,是下面这一串。

image-20230914170829458