Git使用技巧

Git使用技巧

  Git是最常用的版本管理工具,利于协同开发

​ 原来的标题是Github使用技巧,但是后来开发之后发现github和gitlab都是基于Git,因此改为Git

多人协同开发流程

​ 一般在开发产品适合,通常挑选一个分支作为可以上线的正式版本分支,比如master或者release,develop是用来开发的,可能带有bug。 当很多人参与同一个项目的时候,如果给每个人都有Commit到master和release分支的权限是非常不合理的。这个时候,就可以使用Fork + PR/MR的方式来实现多人协作开发。 每个开发者先Fork一份代码到自己的账号下,功能完成后发PR给项目管理者,项目管理者Code Review后确认无误后即可进行Merge操作,这样协作开发效率高,问题少。

安装Git

windows端

下载git工具,[这里是链接](https://git-scm.com/downloads),选择适合自己的版本进行安装。

mac端

苹果电脑自带Git。

linux

以centos为例

下载git源代码压缩文件

1
wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.23.0.tar.xz

解压缩和解归档

1
2
xz -d git-2.23.0.tar.xz
tar -xvf git-2.23.0.tar

安装底层依赖库

1
yum -y install libcurl-devel

构建和安装

1
make && make install

Github设置  

Windows新安装Git需要设置github账户。Mac默认没有修改的情况mac使用icloud账户登录系统,提交时会提示Your name and email address were configured automatically based on your username and hostname. Please check that they are accurate.也需要将提交用户改为github账户。

windows在cmd窗口输入命令,mac在终端输入。

方式一:直接设置自己的用户名和邮箱

1
2
$ git config --global user.name "coliyin@163.com"
$ git config --global user.email "coliyin@163.com"

设置SSHkey

方式二:修改配置文件

在终端输入

1
git config --global --edit

然后会进入vi修改配置文件,将name=和email=之后的内容修改为自己的用户名和邮箱。记得将首列的#号去掉。

修改完后渐入命令使配置生效

1
git commit --amend --reset-author

也可以按照windows设置,最后使其

通常来说,本地的Git只能建议只有一个版本,否则提交代码或者pr时会显示多个账号,会造成混乱

邮箱

邮箱是GitHub验证账户的重要标识,包括SSH key的生成。所以一般入职公司之后,如果使用GitHub都会要求改成公司的,所以更换邮箱之后都要重新生成一次public key,直接使用原来的会报错,像这样子

1
fatal: unable to access 'https://github.com/Kun8018/Kun8018.github.io.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443

在本地环境执行

1
ssh-keygen -t rsa -b 4096 -C "your-github@email.com"

然后找到对应的ssh key

1
2
cd .ssh
cd ~/.ssh

查看key中的内容

1
cat id_rsa.pub

复制输出并且在GitHub常见ssh key

在GitHub。account -> settings -> create a SSH and GPG keys,把本地的key粘贴进去就好

现在已经可以运行了,可以在本地验证一下

1
ssh -vT git@github.com

输出是这样就代表可以

1
2
3
4
debug1: channel 0: free: client-session, nchannels 1
Transferred: sent 3848, received 2040 bytes, in 0.2 seconds
Bytes per second: sent 16032.4, received 8499.5
debug1: Exit status 1

从远程仓库拉取项目

GitHub可以使用http和ssh两种方式获取代码

https比较简单,但是每次fetch和push都需要账号密码

sshfetch和push不需要在输入账号密码:

1
ssh-keygen -t rsa -b 4096 -C "1027690173@qq.com"

会请求你输入文件名和设置密码,可以不设置直接enter跳过,文件名为默认id_rsa,密码默认为空

在.ssh下查看文件,有id_rsa或id_dsa命名的文件即是,后缀为.pub的是公钥,没有的是私钥

1
2
cd ~/.ssh
ls

运行ssh-agent

1
eval "$(ssh-agent -s)"
1
Host * IdentityFile ~/.ssh/id_rsa

添加ssh key到github或gitlab

复制公钥

1
pbcopy < ~/.ssh/id_rsa.pub

粘贴到github ssh-key或者gitlab

首次下载项目

1
git clone

获取远程修改到本地

1
git  git@github.com:anyangxaut/LearnGit.git

基本操作

将文件夹变成git仓库

1
git init

当你完成了上述操作后,本地目录就变成了工作区(正在操作的工作目录)、仓库和工作区和本地仓库之间的暂存区(也称为缓存区)。

通过git add可以将指定的文件或所有文件添加到暂存区。

1
2
git add <file>
git add .

如果不希望将文件添加到暂存区,可以按照提示,使用git rm --cached <file>命令将文件从暂存区放回到工作区。

如果这个时候对工作区的文件又进行了修改使得工作区和暂存区的内容并不相同了,再次执行git status可以看到哪个或哪些文件被修改了,如果希望用暂存区的内容恢复工作区,可以使用下面的命令。

1
2
git restore <file>
git restore .

通过下面的命令可以将暂存区的内容纳入本地仓库,

1
git commit -m '本次提交的说明'

提交commit可以直接关联issue,在issue下面可以直接显示关联的commit代码

1
git commit -m '说明 #issue链接'

在pr的comment中添加issue的链接可以关联pr与issue,当pr被合并时issue会被自动关闭

可以通过git log查看每次提交对应的日志。

1
2
git log
git log --graph --oneline --abbrev-commit

gitlog不能显示已经删除的commit记录,需要查看时使用git reflog命令

1
git reflog

reflog可以显示所有分支的操作记录,包括已经删除的commit,要回复已经删除的commit使用cherry-pick

1
git cherru-pick 4c97ff3

远程操作

添加远程仓库(Git服务器)

1
git remote add origin git@gitee.com:jackfrued/python.git

从远程仓库取回代码。

1
git pull origin master

将本地代码(工作成果)推送到远程仓库。

1
git push -u origin master:master

删除远程分支

执行此命令时慎重操作

1
2
3
4
git branch -r -d origin/develop
git push origin :develop

git push origin --delete develop

分支

创建分支

1
git branch <branch-name>

切换分支

1
git switch <branch-name>

分支合并

dev分支上完成开发任务之后,如果希望将dev分支上的成果合并到master,可以先切回到master分支然后使用git merge来做分支合并,合并的结果如下图右上方所示。

1
2
git switch master
git merge --no-ff dev

在合并分支时,没有冲突的部分Git会做自动合并。如果发生了冲突(如devmaster分支上都修改了同一个文件),会看到CONFLICT (content): Merge conflict in <filename>. Automatic merge failed; fix conflicts and then commit the result(自动合并失败,修复冲突之后再次提交)的提示,这个时候我们可以用git diff来查看产生冲突的内容。解决冲突通常需要当事人当面沟通之后才能决定保留谁的版本,冲突解决后需要重新提交代码。

删除分支

如果分支上的工作成果还没有合并,那么在删除分支时会看到error: The branch '<branch-name>' is not fully merged.这样的错误提示。如果希望强行删除分支,可以使用-D参数。

1
2
3
4
git branch -d <branch-name>
error: The branch '<branch-name>' is not fully merged.
If you are sure you want to delete it, run 'git branch -D <branch-name>'.
git branch -D <branch-name>

分支变基

分支合并操作可以将多个分支上的工作成果最终合并到一个分支上,但是再多次合并操作之后,分支可能会变得非常的混乱和复杂,为了解决这个问题,可以使用git rebase操作来实现分支变基。

1
2
3
git rebase master
git switch master
git merge dev

关联远程分支

如果当前所在的分支还没有关联到远程分支,可以使用下面的命令为它们建立关联。

1
git branch --set-upstream-to origin/develop

也可以指定别的分支关联到远程分支

1
git branch --set-upstream-to origin/develop <branch-name>

也创建分支时使用了--track参数,直接指定与本地分支关联的远程分支

1
git branch --track <branch-name> origin/develop

解除关联远程分支

1
git branch --track <branch-name> origin/develop

rebase、squash与merge的区别

rebase可以尽可能保持master分支干净,并且易于识别author

squash也可以保持master分支干净,但是master中author都是maintainer,而不是原owner

merge不能保持master分支干净,但是保持了所有的commit history,大多数情况下都是不好的,个别情况好

子模块submodule

当你在一个git项目上工作时,你需要在其中使用另一个Git项目。也许它是一个第三方开发的库或者是你独立开发合并在多个父项目中使用。

在git中可以用子模块submodule来管理这些项目,submodule允许你将一个git仓库当作另外一个git仓库的子目录,这允许你克隆另外一个仓库到你的项目中并且保持你的提交相对独立

克隆含有子模块的项目

克隆含有子模块的项目可以先克隆父项目,再更新子模块,另一种是直接递归克隆整个项目

先克隆父项目,再更新子模块

1
git clone https://.../.git assets

此时子模块子模块还未初始化

初始化子模块

1
git submodule init

更新子模块

1
git submodule update

直接递归克隆整个项目

1
git clone https://.../.git assets --recursive

添加子模块

1
git submodule add https://.../.git assets

查看子模块

1
2
git status
git submodule

更新子模块

1
2
3
4
5
6
## 更新项目内子模块到最新版本
git submodule update
## 更新子模块为远程项目的最新版本
git submodule update --remote
## 更新所有子模块
git submodule foreach git pull

修改子模块

在子模块中修改文件后,直接提交到远程项目分支

1
2
3
git add .
git ci -m "commit"
git push origin HEAD:master

删除子模块

删除子模块比较麻烦,需要手动删除相关的文件,否则在添加子模块时有可能出现错误。

首先删除子模块文件夹

1
2
git rm --cached assets
rm -rf assets

删除相关子模块信息

1
2
3
[submodule "assets"]
path = assets
url = https://github.com/../.git

删除相关子模块信息

1
2
[submodule "assets"]
url = https://github.com/../.git

删除相关子模块文件

1
rm -rf ./git/modules/assets

子仓库subtree

与submodule的异同

git submodule:

允许其他仓库指定以一个commit嵌入仓库的子目录

仓库clone下来要init和update

会产生文件记录和submodule版本信息

git submodule删除起来比较费劲

git subtree:

避免以上问题

管理和更新流程比较方便

git subtree合并子仓库到项目中的子目录,不用像submodule一样每次子目录修改之后都要init和update,万一每次没update就直接add,将

git 1.5之后建议使用git submodule

使用方法

如果p1项目和p2项目共用S项目

添加subtree

1
git subtree add --prefix=<s project path>  <s project url> <branch> --squash

修改代码,可以改subtree里面的代码,添加相关commit

pull&push

1
2
git subtree pull --prefix=<s project path>  <s project url> <branch> --squash
git subtree push --prefix=<s project path> <s project url> <branch> --squash

拆分已有项目,比如P项目拆分出s项目

1
git subtree split -P <S project path> -b <tmp branch>

git会遍历所有commit,分离与S项目有关的commit,并存入临时分支branch中

创建子repo

1
2
3
4
5
6
mkdir 
cd s new path
git init
git pull <S project path> <tmp branch>
git remote add origin <S github>
git push origin -u master

清理原项目中的子项目数据

1
2
3
4
cd P project
git rm -rf
git commit -m
git branch -D

在新项目中添加subtree

1
2
git subtree add --prefix=<s project path>  <s project url> <branch> --squash
git push origin master

其他操作

git fetch:下载远程仓库的所有变动,可以将远程仓库下载到一个临时分支,然后再根据需要进行合并操作,git fetch命令和git merge命令可以看作是之前讲的git pull命令的分解动作。

1
2
git fetch origin master:temp
git merge temp

git push -f:强制提交,完全以自己的提交为准,之前其他人的提交都会被覆盖,适用于pr被block之后重新提交,提交后不需要重新提pr.

git rebase dev:解决合并冲突。rebase之后如果有冲突,会进入临时变基分支,手动消除冲突之后在rebase

git checkou branch: 切换分支

git checkou -b|B branch: 创建新分支并切换到该分支

git checkout -- a.txt : 将文件迁出修改到上一次提交的内容

git checkout commit_id -- a.txt : 将文件迁出修改到指定的提交历史中某次提交的内容

git checkout branch -- a.txt:将文件迁出修改到指定分支的该文件的内容

git checkout -- *.txt将根目录下所有指定后缀的文件都迁出

git checkout -- *.txt将根目录下所有指定目录的文件都迁出

git diff:常用于比较工作区和仓库、暂存区与仓库、两个分支之间有什么差别。

git diff --cached:查看有add但没有commit的改动

git diff HEAD:是上面两条命令的合并

git stash:将当前工作区和暂存区发生的变动放到一个临时的区域,让工作区变干净。这个命令适用于手头工作还没有提交,但是突然有一个更为紧急的任务(如线上bug需要修正)需要去处理的场景。

1
2
3
4
5
6
7
8
9
git stash ## 保存当前的工作进度,会把暂存区和工作区的改动保存起来,使用git stash sava ‘message’ 添加一些注释
git stash list ## 显示保存进度的列表,git stash可以执行多次
## 通过git stash pop命令恢复进度后,会删除当前进度。
git stash pop ## 恢复最新的进度到工作区,git默认会把工作区和暂存区的改动都恢复到工作区
git stash pop --index ## 恢复最新的进度到工作区和暂存区
git stash pop stash@{1} ## 恢复指定的进度到工作区,stash_id为通过git stash list命令得到的
git stash apply ##恢复最新的进度到工作区,除了不删除恢复的进度外,其他和git stash pop命令一样
git stash drop [stash_id] ## 删除一个存储的进度,如果不执行stash_id则默认保存最新的存储进度
git stash clear ## 删除所有存储进度

git reset:回退到指定的版本。

git revert:撤回提交信息。

git cherry-pick:挑选某个分支的提交并作为一个新的提交引入到你当前分支上。

默认cherry-pick只挑选单次的commit,如果想转移多个commit,使用命令git cherry-pick commitid1…commitid100

…命令默认不包含第一个commit id,如果你想包含第一个commit,也就是闭区间,使用git cherry-pick A^…B

Cherry-pick的过程中如果有冲突,需要先修改冲突文件,再git add .,然后继续执行git cherry-pick –continue

在任何阶段都可以执行git cherry-pick --abort放弃本次cherry-pick

git tag:经常用于查看或新增一个标签。

git rebase:分支变基,多用于合并commit和重新合并master分支的代码

如果一次开发提交过多commit,会有很多弊端:

1.不利于代码review:如果做一个很小的功能有很多commit,会很多。

2.会造成分支污染:如果项目充满了无用的commit,有一天项目出现紧急问题需要回滚代码,却发现海量commit,会很崩溃

合并最近四次commit

1
git rebase -i HEAD~4

rebase之后进入vim模式,把不需要的commit前面的pick改为squash就可以

合并其他分支

每次开发完都要先在master分支下拉取别的同事的远程代码,然后当前分支对master分支进行合并才不会冲突

具体操作

1
git:(feature1):git rebase master

执行命令后:

首先git会把feature1分支里面的每个commit取消掉

然后把上面的操作临时保存成一个patch文件,存在.git/rebase目录下

然后把feature1分支更新到最新的master分支下

最后把上面保存的patch文件应用到feature1分支上

出现冲突时需要先解决冲突,然后执行命令

1
2
git add .
git rebase --continue

在任何时候都可以随时取消rebase操作

1
git rebase --abort

git alias可以配置命令的别名,简化命令

1
2
3
4
5
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch

git ci -m "commit message"

查看文件修改历史

1
git log --follow -p 想要查看的文件

worktree

在大型软件开发过程中可能经常需要维护一个古老的分支,比如三年前的分支,当然 git 允许你每个分支维护一个版本,但是切换 branch 的成本太高,尤其是当代码变动很大的时候,有可能改变了项目结构,甚至可能变更了 build system,如果切换 branch,IDE 可能需要花费大量的时间来重新索引和设置。

但是通过 worktree, 可以避免频繁的切换分支,将老的分支 checkout 到单独的文件夹中作为 worktree,每一个分支都可以有一个独立的 IDE 工程。当然像过去一样你也可以在磁盘上 clone 这个 repo 很多次,但这意味着很多硬盘空间的浪费,甚至需要在不同的仓库中拉取相同的变更很多次。

回到原来的问题,使用 git worktree 确实能够解决最上面提及的问题。

git worktree 的命令只有几行非常容易记住

1
2
git worktree add ../new-dir some-existing-branch
git worktree add [path] [branch]

这行命令将在 new-dir 目录中将 some-existing-branch 中的内容 check out 出来,就像在该目录中 clone 了一份新代码一样。新的文件地址可以在文件系统中的任何位置,但是注意千万不要将目录放到主仓库中。在此之后新目录中的内容就可以和主仓库中的内容一样,新建分支,push 到远端。

当工作结束后可以直接删除该目录,然后运行 git worktree prune.

git worktree 非常适合大型项目又需要维护多个分支,想要避免来回切换的情况,这里总结一些优点:

  • git worktree 可以快速进行并行开发,同一个项目多个分支同时并行演进
  • git worktree 的提交可以在同一个项目中共享
  • git worktree 和单独 clone 项目相比,节省了硬盘空间,又因为 git worktree 使用 hard link 实现,要远远快于 clone

pr与mr

合并代码的操作在github中叫pr,在gitlab中成为mr,本质上都是合并代码

GitHub pr

强制push之后pr不能重开

Git Alias

开启zsh git plugin之后,会获得一群好用的git alias

Gitflow

进入本地文件夹,打开Git bash,

执行指令进行初始化,会在原始文件夹中生成一个隐藏的文件夹.git

1
2
rm -rf .git//删掉原来的.git目录
$ git init

将文件添加到本地仓库,运行命令:

1
$ git add .

输入本次提交说明

1
$ git commit -m "layout"

将本地仓库与远程仓库相关联,

1
$ git remote add origin https://github.com/CongliYin/CSS.git

如果出现错误:fatal: remote origin already exists,则执行以下语句:

1
$ git remote rm origin

执行上传命令

1
git push origin master

新建远程仓库需要添加-u参数

1
git push -u origin master

如果出现错误failed to push som refs to…….,则执行以下语句,先把远程服务器github上面的文件拉先来,再push 上去。:

1
$ git pull origin master

如果出现错误fatal: refusing to merge unrelated histories,后面加上–allow-unrelated-histories

1
git pull origin master --allow-unrelated-histories

特别注意:执行命令后,git会弹出一个GitHub登陆的小界面,你登录成功后要求你输入用户名和密码。这里的密码并不是你的GitHub的密码或者本地git的密码。而是GitHub的Personal access tokens

https://github.com/settings/tokens

错误

GitHub pull之后有冲突

尚未完成合并(MERGE_HEAD存在)?

1
rm -rf .git/MERGE*

或者

1
git merge --quit

git action

持续集成由很多操作组成,比如抓取代码、运行测试、登录远程服务器,发布到第三方服务等等。GitHub 把这些操作就称为 actions。

很多操作在不同项目里都是类似的,完全可以共享,因此github允许开发者把每个操作写成独立的脚本文件,存放到代码仓库里,使得其他开发者可以引用

如果你需要某个action,不必自己写复杂的脚本,直接引用别人写好的action即可,整个持续集成过程就变成了一个actions 的组合

基本概念:

workflow:持续集成一次运行的过程,就是一个workflow

job:一个workflow由一个或者多个job组成,含义是一次持续集成的运行可以完成多个任务

step:每个job由多个step组成,一步步完成

action:每个step可以依次执行一个或者多个命令(action)

github actions的配置文件叫做workflow文件,存放在代码仓库的.github/workflow目录

workflow采用yaml文件,文件名可以任取,后缀名统一为.yml,一个库可以有多个workflow文件,github只要发现.github/workflows目录里面有.yml文件就会自动运行该文件

workflow中常用的配置字段

name:workflow的名称,如果省略该字段,则默认是workflow的文件名

on:指定触发workflow的条件,通常是某些事件,可以是事件或事件的数组

例:on:[push,pull_request]表示push或者pull_request事件都可以触发workflow

on也可以限定某些分支的事件或标签,

1
2
3
4
on:
push:
branches:
- master

上面的代码表示是有master分支push时才触发

jobs:jobs是workflow文件的主体,表示要执行的一项或者多项任务

jobs中:

首先写出每一项任务的joh_id,名称自定义就可以,添加name字段是任务的说明

needs字段指定当前任务的依赖关系,即运行顺序

runs-on字段指定运行所需要的虚拟机环境,这是必填字段

runs-on可以选择github提供的虚拟机或者自己的服务器,使用自己的机器需要github能进行访问并给其所需的权限

有时候需要对多个操作系统、多个编程语言版本、多个平台进行测试,此时可以在runs-on字段下面配置一个构建矩阵

1
2
3
4
5
6
runs-on: ${{matrix.os}}
strategy:
matrix:
os:[ubuntu-16.04 ubuntu-18.04]
node:[6,8,10]
## 上面的代码配置了两种os操作系统和三种node版本共六种情况的构建矩阵,`{{matrix.os}}`是一个上下文参数

strategy策略包括:

matrix:构建矩阵

fail-fast:默认为true,即一旦某个矩阵任务失败则立即取消所有还在进行中的任务

max-paraller:可同时执行的最大并发数,默认情况下github会动态调整

此外还可以使用include为一个特定的os版本声明,用exclude删除特定的配置项

1
2
3
4
5
6
7
8
9
10
11
12
runs-on: ${{matrix.os}}
strategy:
matrix:
os:[macos-latest windows-latest ubuntu-18.04]
node:[4,6,8,10]
include:
- os: windows-latest
node: 4
npm: 2
exclude:
- os: macos-latest
node: 4

上面的代码声明了当os为windows-latest时增加一个node和npm的特定版本,当os为Macos-latest时移出node为4的版本

jobs.steps:steps字段指定每个job的运行步骤,可以包含一个或者多个步骤,每个steps可以指定三个字段

Steps.name:步骤名称

steps.run:该步骤的shell指令或者action

steps.env:该步骤所需的环境变量

steps.uses:使用哪个action

checkout action是一个标准动作,当有以下情况时必须率先使用checkout action:

1.workflow需要项目库当代码副本,如构建、测试、或持续集成这些操作

2.workflow中至少有一个action是在同一个项目库下定义的

此外,如果只是想浅克隆库或者只复制最新的版本,使用with:fetch-depth声明

1
2
3
- uses: actions/checkout@v1
with:
fetch-depth: 1

也可以引用现有库、自己的库或者docker的container

1
2
3
4
5
6
7
8
9
jobs: 
my_first_job:
step:
- name: My first step
uses: docker://alpine:3.8
uses: ./.github/actions/hello-word-action
uses: actions/setup-node@v1
with:
node-version: 10.x

if语句

在jobs和step中可以使用if条件语句,只有满足条件时才执行具体的job或者step

if语句中的任务检查语句

always():总是返回true

success():当上一步执行成功时才会返回true

failure():当上一步执行失败时才会返回ture

cancelled():当workflow被取消时返回true

1
2
3
4
5
6
7
8
9
steps:
- name: step1
if:always()

- name: step2
if:success()

- name: step3
if:failure()

上下文和表达式(expression)

有时候我们需要和第三方平台交互,这时通常需要配置一个token,但是这个token不可能明文使用的,通过${ { } }

的表达式就能传入

具体做法:

1.在具体repo库Settings中添加一个密钥,如SOMEONE_TOKEN

2.在workflow中通过表达式将 token安全地传入环境变量

1
2
3
4
steps:
- name: My first action
env:
SOMEONE_TOKEN:${{ secrets.SOMEONE_TOKEN}}

这里的secrets就是一个上下文,除此之外还有:

github.event_name:触发workflow的事件名称

job.status:当前job的状态,如success、failure等

Steps.output:某个action的输出

runner.os:runner的操作系统,如windows、linux或者macOS

github还做了一个官方市场,可以搜索到其他人提交的actions,另外还有一个awesome actions的仓库可以找到其他action

回滚

在github action下找到要回滚的版本,点击re-run就可以回到指定的版本

触发其他repo的workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name: Dispatch Event

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1

- name: dispatch event to another_repository
env:
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
EVENT: YOUR_EVENT_TYPE
ORG: YOUR_ORG_NAME
REPO: YOUR_TARGET_REPO_NAME
run: |
curl -d "{\"event_type\": \"${EVENT}\"}" -H "Content-Type: application/json" -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.everest-preview+json" "https://api.github.com/repos/${ORG}/${REPO}/dispatches"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: hugo publish

on:
push:
branches:
-master
repository_dispatch:
types: sub_commit

jobs:
build-deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
with:
submodule: recursive

好用的git action

action-js-inline

Https://github.com/marketplace/actions/execute-javascript-inline

可以在git action里执行js代码,而不只是shell代码

本地跑git action

https://www.github.com/nektos/act

Git Hooks

更新不了代码

代码加入本地仓库后,上传后显示everything -up-date,但是远程仓库没有更新

先创建新分支

1
git branch newbranch

检查分支创建是否成功

1
git branch

此时输出

1
2
* master
newbranch

切换到新创建的分支

1
git checkout newbranch

将改动提交到新分支

1
2
git add .
git commit -a

回到主分支

1
git checkout master

将新分支与原分支合并

1
git merge newbranch

正常合并没有冲突,如果产生冲突,查看冲突文件修改后再一次提交

1
git diff

解决后就正常提交

1
git push -u origin master

删除分支

1
git branch -d newbranch

检查版本信息

查看远程仓库信息

1
git remote -v
1
git status

检查文件或者文件夹在工作区或暂存区的状态,有三种

文件已经从工作区add到暂存区,git restore –staged filename

文件在工作区、暂存区都有,并且在工作区进行了修改或删除,没有add到暂存区

git add file

文件只在工作区

1
git checkout -- <file>

拉取暂存区文件为工作区文件

1
git log

git log 会按提交时间列出所有的更新,最近的更新排在最上面

1
git open

在git目录输入git open就能打开github对于的页面

1
npm install -g git-open

将本地仓库文件撤回至工作区

1
2
git reset --hard
git reser --mixed
1
git revert HEAD
1
git fetch origin

创建并更新远程分支,并拉取代码到origin,一般默认是master

git pull可以认为是git fetch和git merge的组合体

1
git rebase origin/master
1
git diff

git-diff能在命令行显示当前代码与上次提交时代码的修改,可以逐行见检查代码

代码检查

js

使用husky

安装

1
npm install husky -D

编辑package。json 》 prepare 脚本并且运行

1
2
npm set-script prepare "husky install" 
npm run prepare

添加钩子函数

1
2
npx husky add .husky/pre-commit "npm test"
git add ./husky/pre-commit

然后提交commit就会检查

如果不想检查使用no-verify

1
git commit -m '' --no-verify

在Github上工作

向开源项目贡献代码

一般开源库不会给其他人开放push权限,如果有很好的想法或者发现开源库有bug,可以向作者提pr(pull request)/mr(merge request)

首先Fork(关联复制)一份开源库A的代码到自己的github账号下( A1)

自己对于A1有完全的权限,此时在A1上加入自己的代码,commitA

发送Merge Request到原A库作者

原A库作者审核同意后,将commitA merge到A库代码中

GitHub activity

一般来说,只有对GitHub上repo的master分支操作时,比如push或者合并到master时GitHub activity会有记录

GitHub api

api.github.com/repos/{repo_name}/releases/tags/

1
2
curl -o index.json https:api.github.com/repos/vesoft-inc/nebula-graph/releases/tags/v2.5.0
https:api.github.com/repos/{repo_name}/releases/latest

pr/issue template

GitHub release/tag

Git tag常用于发布版本的标注,可以理解为tag是对某一次commit hash的别名设置。需要注意的是,git tag的同步与删除需要显式地指定名称

1
2
3
4
5
6
7
8
# 列出本地所有tag
git tag

# 过滤出v2.开头的tag
git tag -l 'v2.*'

# 使用管道输出tag列表,避免进入visual模式
git tag | sort -V # gtv

新增tag

1
2
3
4
5
6
7
8
# 创建一个名为 v0.0.1的轻量tag
git tag v0.0.1

# 创建一个名为 v0.0.1的附注tag
git tag -a v0.0.1 -m 'tag description'

# 将tag推送至远程
git push origin v0.0.1 #ggp v0.0.1

删除tag

1
2
3
4
5
# 删除本地tag
git tag -d v0.0.1

# 删除远程tag
git push origin -d v0.0.1 # gp origin: v0.0.1

语义化版本

1
git describe master --tags

git-open

git-open是一个npm包,可以在git提交后在命令行输入,快速打开gitlab

1
npm install git-open

使用时直接输入

1
git open

就可以在默认浏览器打开gitlab的提交页面

如果提交的分支不是master,需要在gitlab页面创建合并请求,选择审核人进行审核合并

在审核人确定合并之前,下次提交时不需要再次创建合并请求

确定合并之后下次提交到分支时则需要再次创建

Gitlab

gitlab自带nginx、redis等软件,所以运行起来较大,在RAM4GB及以上的服务器才可以跑起来

gitlab ci

过在项目根目录下配置.gitlab-ci.yml文件,可以控制ci流程的不同阶段,例如install/检查/编译/部署服务器。gitlab平台会扫描.gitlab-ci.yml文件,并据此处理ci流程

ci流程在每次团队成员push/merge后之后触发。每当你push/merge一次,gitlab-ci都会检查项目下有没有.gitlab-ci.yml文件,如果有,它会执行你在里面编写的脚本,并完整地走一遍从intall => eslint检查=>编译 =>部署服务器的流程

gitlab-ci提供了指定ci运行平台的机制,它提供了一个叫gitlab-runner的软件,只要在对应的平台(机器或docker)上下载并运行这个命令行软件,并输入从gitlab交互界面获取的token,就可以把当前机器和对应的gitlab-ci流程绑定,也即:每次跑ci都在这个平台上进行。

gitlab-ci的所有流程都是可视化的,每个流程节点的状态可以在gitlab的交互界面上看到,包括执行成功或失败。如下图所示,因为它的执行看上去就和多节管道一样,所以我们通常用“pipeLine”来称呼它

不同push/merge所触发的CI流程不会互相影响,也就是说,你的一次push引发的CI流程并不会因为接下来另一位同事的push而阻断,它们是互不影响的。这一个特点方便让测试同学根据不同版本进行测试。

pipeline不仅能被动触发,也是可以手动触发的。

通过gitlab-ci,前端开发在提交代码之后就不用管了,ci流程会自动部署到测试或集成环境的服务器。很大程度上节约了开发的时间。

同时,因为开发和测试人员可以共用gitlab里的pipeline界面, 测试同学能够随时把握代码部署的情况,同时还可以通过交互界面手动启动pipeline,自己去部署测试,从而节约和开发之间的沟通时间。

我们可以把eslint或其他的代码检查加到pipeline流程中,每当团队成员提交和合并一次,pipeline都会触发一次并对代码做一次全面检测,这样就从一个更细的粒度上控制代码质量了。

gitlab ci中的概念

Pipeline是Gitlab根据项目的.gitlab-ci.yml文件执行的流程,它由许多个任务节点组成, 而这些Pipeline上的每一个任务节点,都是一个独立的Job

Job在YML中的配置我们将会在下面介绍,现在需要知道的是:每个Job都会配置一个stage属性,来表示这个Job所处的阶段。

一个Pipleline有若干个stage,每个stage上有至少一个Job

Runner可以理解为:在特定机器上根据项目的.gitlab-ci.yml文件,对项目执行pipeline的程序。Runner可以分为两种: Specific RunnerShared Runner

  • Shared Runner是Gitlab平台提供的免费使用的runner程序,它由Google云平台提供支持,每个开发团队有十几个。对于公共开源项目是免费使用的,如果是私人项目则有每月2000分钟的CI时间上限。
  • Specific Runner是我们自定义的,在自己选择的机器上运行的runner程序,gitlab给我们提供了一个叫gitlab-runner的命令行软件,只要在对应机器上下载安装这个软件,并且运行gitlab-runner register命令,然后输入从gitlab-ci交互界面获取的token进行注册, 就可以在自己的机器上远程运行pipeline程序了。
  1. Shared Runner是所有项目都可以使用的,而Specific Runner只能针对特定项目运行
  2. Shared Runner默认基于docker运行,没有提前装配的执行pipeline的环境,例如node等。而Specific Runner你可以自由选择平台,可以是各种类型的机器,如Linux/Windows等,并在上面装配必需的运行环境,当然也可以选择Docker/K8s等
  3. 私人项目使用Shared Runner受运行时间的限制,而Specific Runner的使用则是完全自由的。

有时候你可能会发现:你的Job并没有被你新建的Runner执行,而是被Share Runner抢先执行了。你如果不想要Share Runner,你可以在Gitlab面板上关掉

CI流程的运行控制,决定于项目根目录下编写的配置文件—— .gitlab-ci.yml,正因如此,我们需要掌握YML的基本语法规则。

YML是一种编写配置文件的语言,比JSON更为简洁和方便,因此,我们首先要掌握的就是YML文件的编写语法。

Gitlab yaml

模块化

yaml

  • 使用 &符号可以定义一个片段的别名
  • 使用 <<符号和 ***** 符号可以将别名对应的YML片段导入
1
2
3
4
5
6
7
8
9
10
11
12
.common-config: &commonConfig
only: # 表示仅在develop/release分支上执行
refs:
- develop
- release

install-job:
# 其他配置 ....
<<: *commonConfig
build-job:
# 其他配置 ....
<<: *commonConfig

gitlab-ci提供的include关键字便可实现这个功能, 它可以用来导入外部的YML文件。

1
2
3
4
include:
- '/.gitlab-ci.wx.yml'
- '/.gitlab-ci.bd.yml'
- '/.gitlab-ci.h5.yml'

gitlab-ci还提供了extend关键字,它的功能和前面提到的YML的片段导入的功能是一样的,

1
2
3
4
5
6
7
8
9
10
11
12
13
.common-config: 
only: # 表示仅在develop/release分支上执行
refs:
- develop
- release

install-job:
# 其他配置 ....
extends: .common-config

build-job:
# 其他配置 ....
extends: .common-config

cache

为了重复运行pipeline的时候不会重复安装全部node_modules的包,从而减少pipeline的时间,提高pipeline的性能。

但是,这并不是cache关键字唯一的功能!

在介绍cache的另外一个功能之前,我要先说一下gitlab-ci的一个优点“恶心人”的特点:

它在运行下一个Job的时候,会默认把前一个Job新增的资源删除得干干静静

也就是说,我们上面bulid阶段编译生成的包,会在deploy阶段运行前被默认删除!(我生产包都没了我怎么部署emmmmmmm)

而cache的作用就在这里体现出来了:如果我们把bulid生产的包的路径添加到cache里面,虽然gitlab还是会删除bulid目录,但是因为在删除前我们已经重新上传了cache,并且在下个Job运行时又把cache给pull下来,那么这个时候就可以实现在下一个Job里面使用前一个Job的资源了

总而言之,cache的功能体现在两点:

  • 在不同pipeline之间重用资源
  • 在同一pipeline的不同Job之间重用资源

虽然cache会缓存旧的包,但我们并不用担心使用到旧的资源,因为npm install还是会如期运行,并检查package.json是否有更新,npm build的时候,生成的build资源包也会覆盖cache,并且在当前Job运行结束时,作为“新的cache”上传

artifacts

将生成的资源作为pipeline运行成功的附件上传,并在gitlab交互界面上提供下载

1
2
3
4
5
6
7
8
Build-job:
stage: build
script:
- 'npm run build'
artifacts:
name: 'bundle'
paths:
- build/

Image/Services

这两个关键字可使用Docker的镜像和服务运行Job,具体可参考Docker的相关资料,这里暂不多加叙述

Only/except

这两个关键字后面跟的值是tag或者分支名的列表。

故名思义

  • only的作用是指定当前Job仅仅只在某些tag或者branch上触发
  • 而except的作用是当前Job不在某些tag或者branch上触发
1
2
3
4
5
6
job:
# use regexp
only:
- /^issue-.*$/
- develop
- release

allow_failure

值为true/false, 表示当前Job是否允许允许失败。

  • 默认是false,也就是如果当前Job因为报错而失败,则当前pipeline停止
  • 如果是true,则即使当前Job失败,pipeline也会继续运行下去。
1
2
3
4
5
job1:
stage: test
script:
- execute_script_that_will_fail
allow_failure: true

retry

指明的是当前Job的失败重试次数的上限。

但是这个值只能在0 ~2之间,也就是重试次数最多为2次,包括第一次运行在内,Job最多自动运行3次

timeout

配置超时时间,超过时间判定为失败

1
2
3
Job:
script: rspec
timeout: 3h 30m

Job在何种状态下运行,它可设置为3个值

  • on_success: 仅当先前pipeline中的所有Job都成功(或因为已标记,被视为成功allow_failure)时才执行当前Job 。这是默认值。
  • on_failure: 仅当至少一个先前阶段的Job失败时才执行当前Job。
  • always: 执行当前Job,而不管先前pipeline的Job状态如何。

Gitlab Runner

下载gitlab runner 的rpm包,安装

1
rpm -i gitlab-runner_<arch>.rpm

通常作为一个 trigger 代理,任务开销很小,我们可以把 /etc/gitlab-runner/config.toml 配置里的 concurrent 可以改得大一些,以支持更高的并发量。

搭建 gitlab runner 每台 runner 只需要执行一次

注册 gitlab runner 每个 git 仓库每台 runner 都需要单独注册

先登入 gitlab,进入对应的 git 仓库(project)

- 展开左边侧边栏最下面 Settings -> CI/CD。点击页面上 Runners 栏右边的 Expand,页面往下滚动一点可看到

注册

1
sudo gitlab-runner register

2. Enter your GitLab instance URL.

上图红框中 Register the runner with this URL 下面的内容。

3. Enter the token you obtained to register the runner.

上图红框中 And this registration token 下面内容。

4. Enter a description for the runner. You can change this value later in the GitLab user interface.

在 gitlab 中显示的 runner 描述,该实践中我们把他当名字用,叫 scapegoat-01。

5. Enter the tags associated with the runner, separated by commas. You can change this value later in the GitLab user interface.

tag 相当于Jenkins中的 label, 用于 runner 分类。该实践中输入 scapegoat。

6. Provide the runner executor. For most use cases, enter docker.

这里我们选 shell, window 可选 powershell。

https://zhuanlan.zhihu.com/p/184936276

gitlab ci触发jenkins

理论上,gitlab的runner可以直接替代jenkins,但是实际项目中会有各种历史包袱。所以利用gitlab触发jenkins也可以

有两种方式,比较简单的方式是使用jenkins+webhook的插件方式实现

在jenkins的dashboard面板安装Generic Webhook Trigger插件

在构建触发器中选择Generic Webhook Trigger选项,将Jenkins和gitlab配合起来

image-20210816143402614.png

登录gitlab进入你要部署的项目>settings>Integrations>add webhook

测试webhook是否成功

https://juejin.cn/post/6997560212924137485#heading-24

也可以通过python-jenkins触发jenkins job

安装python和pip

1
2
sudo apt install python或sudo apt install python3
sudo apt install python-pip

安装python-jenkins

1
python -m pip install python-jenkinsor python3 -m pip install python-jenkins

python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/python# file name: Jenkins-Compile-trigger.py
import jenkins
import requests
import time
import sys
import os

jenkins_url = "http://yunxin-jenkins.netease.im:8080"
# gitlab的登录账号
jenkins_user = "zouliyong"
#获取gitlab access token章节获取到的token
jenkins_token = "xxxxxxxxxxxxxxxxx"
#Jenkins job name
job_name = "Lava-CI"
#从gitlab-ci中获取Jenkins job的参数,按需修改
job_parameters = {
"_GitlabSourceBranch" : os.getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"),
"_GitlabTargetBranch" : os.getenv("CI_MERGE_REQUEST_TARGET_BRANCH_NAME"),
"_GitlabMergeRequestLastCommit" : os.getenv("CI_COMMIT_SHA"),
"_GitlabSourceRepoHomepage" : os.getenv("CI_PROJECT_URL"),
"_GitlabMergeRequestIid" : os.getenv("CI_MERGE_REQUEST_IID"),
"_GitlabPipelineId" : os.getenv("CI_PIPELINE_ID"),
"_GitlabPipelineUrl" : os.getenv("CI_PIPELINE_URL"),
"_GitlabJobId" : os.getenv("CI_JOB_ID"),
"_GitlabUserName" : os.getenv("GITLAB_USER_NAME"),
"_GitlabUserEmail" : os.getenv("GITLAB_USER_EMAIL")
}

build_number = 0build_info = {"building" : False}
print("jenkins job name: ", job_name)
print("jenkins job parameters: ", job_parameters)
#连接Jenkins服务
server = jenkins.Jenkins(jenkins_url, username= jenkins_user, password= jenkins_token)
#获取Jenkins job最后一次build的build number
def last_build_number(server, job):
last_build = server.get_job_info(job)['lastBuild']
return 1
if None == last_build
else
last_build['number']

last_build = server.get_job_info(job_name)['lastBuild']
next_build_number = last_build_number(server, job_name) + 1
#触发Jenkins
jobqueue_id = server.build_job(job_name, parameters=job_parameters)
print("Jenkins build is waiting for running [queue id = %d] ..." % queue_id)sys.stdout.flush()
#到这里,Jenkins job已经被放到执行队列里了,
#只是触发不用等待Jenkins job结束的话,python脚本可以到此为止
#等待Jenkins job开始执行
while True:
if next_build_number <= last_build_number(server, job_name):
try:
build_info = server.get_build_info(job_name, next_build_number)
except requests.exceptions.RequestException as e:
print(e)
server = jenkins.Jenkins(jenkins_url, username= jenkins_user, password= jenkins_token)
build_info = server.get_build_info(job_name, next_build_number)
if queue_id == build_info["queueId"]:
build_number = next_build_number
print("build number: %d" % build_number)
break
next_build_number = next_build_number + 1
time.sleep(0.1)
print("Jenkins build is running [build number = %d] ..." % build_number)
print("Jenkins job URL: %s/job/%s/%d/display/redirect" % (jenkins_url, job_name, build_number))
sys.stdout.flush()
#到这里Jenkins job已经被正式调度,并开始执行
#等待Jenkins job执行结束
while build_info["building"]:
try:
build_info = server.get_build_info(job_name, build_number)
except requests.exceptions.RequestException as e:
print(e)
server = jenkins.Jenkins(jenkins_url, username= jenkins_user, password= jenkins_token)
build_info = server.get_build_info(job_name, build_number)
time.sleep(1)
# 获取执行结果
result = server.get_build_info(job_name, build_number)["result"]
print("jenkins build result: %s" % result)
assert("SUCCESS" == result)

gitlab ci yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#file name: .gitlab-ci.yml
stages:
- build
# 在代码push的时候触发
Compilation:
stage:
build
tags:
- scapegoat
# runner tag 参照上面注册gitlab runner章节
script:
- python Jenkins-Compile-trigger.py
# 只在merge request的时候触发CI:
stage:
build
tags:
- scapegoat
script:
# 这个脚本可以根据情况参照Jenkins-Compile-trigger.py自行修改
- python Jenkins-CI-trigger.py
only:
- merge_requests

https://juejin.cn/post/7073731514386612255

Gitbook

Gitbook是一个提供Markdown书籍托管的网络平台。支持通过git及github进行文档管理,使用它可以很简单地生成、发布电子图书。Gitbook也是一个Nodejs命令行工具,可以使用它搭建自己的gitbook站点。GitBook甚至提供Github hook,在每次push前自动更新书籍内容。

安装GitBook 控制台

1
npm install -g gitbook-cli

如果安装过gitbook旧版本需要卸载。

gitbook常用命令

1
gitbook serve -p 8080 .

Gitbook首先把你的Markdown文件编译为HTML文件,并根据SUMMARY.md生成书的目录。所有生存的文件都保存在当前目录下的一个名为_book的子目录中。完成这些工作后,Gitbook会作为一个HTTP Server运行,并在8080端口监听HTTP请求。

运行以上命令后,打开浏览器,在地址栏输入:http://localhost:8080即可看到你的书页了。

其中位于左侧书目顶部的Introduction一节就编译自README.md,而书目本身自编译自SUMMARY.md。你要在自己的网站上发布新书,只需把_book目录复制到服务器相应目录即可。至此Gitbook的基本用法就介绍完毕。

Gitbook的插件支持

在页面中嵌入Disqus评论

1
npm install gitbook-plugin-disqus

然后建立一个book.json文件,其格式如下:

1
2
3
4
5
6
7
8
9
10
{
"plugins":
["disqus"],
"pluginsConfig":
{ "disqus":
{ "shortName":
"NAME-FROM-DISQUS"
}
}
}

把上面的NAME-FROM-DISQUS修改为你在Disqus上的项目名即可。

再次运行命令:

$ gitbook serve -p 8080 .

并刷新浏览器,即可看到附加了Disqus评论的页面。

Gitbook电子书封面

可以为电子书添加封面。只需添加2个名为cover.jpgcover_small.jpg的两个图片即可。官方建议cover.jpg尺寸18002360,cover_small.jpg尺寸200262。花2元即可在淘宝上找个做封面的人为你制造一个简单的封面,做得好就要花更多一些了 :)

总体而言,GitBook还是很好玩,比起其他写作平台而言,要自由、简单,并舒服得多,可以用Vim编辑,支持Markdown语法,用git管理,关联GitHub后每次push后还能自动编译,生成多种电子书格式。如果你的书极为畅销的话,还能获取到捐赠或购买,没有理由不尝试的呀。

删除电子书

同样是在Book Setting中,可以删除电子书。在电子书列表中没有删除接口。

SVN

一般来说公司版本管理工具使用git的比较多,也有使用svn。SVN是sub vision的缩写,windows中svn客户端一般使用TortoiseSVN,mac中比较好用的当属CornerStone。TortoiseSVN是可视化svn界面,Cornerstone是收费的,因此你可以去网上下载破解版,直接安装即可。

TortoiseSVN

TortoiseSVN 常年管理文件和目录。文件存储于一个中央版本库中。版本库就像一个常见的文件服 务器,除了它保存你对文件和目录所有的改变。这一特性使得你可以恢复文件的旧版本并查看历史-谁 在什么时间如何进行的修改。

创建版本库

1
svnadmin create --fs-type bdb MyNewRepository

图标重载

使用svn-checkout检查文件状态。对号表示状态正常,红色感叹号表示文件被修改未提交,黄色感叹号表示产生冲突。

拉取项目

1
2


提交项目

1
svn commit

更新文件

1
svn update

Cornerstone

评论

You forgot to set the app_id or app_key for Valine. Please set it in _config.yml.

 

本文章阅读量:

  0

IT学徒、技术民工、斜杠青年

机器人爱好者、摄影爱好者

PS、PR、LR、达芬奇潜在学习者

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×