blog-hexo/source/_posts/front-end/git.md
2023-11-06 16:05:27 +08:00

512 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Git
categories:
- Front-End
status: done
---
# GIT 最佳实践
GIT 本质是一个数据库,用来存代码的
- 工作区一个沙箱环境GIT 不负责管理,你尽管在沙箱里面对文件进行操作
- 暂存区:`工作区`文件变动先不急着提交,暂存到一定数量,在提交到版本库
- 版本库:
> !! Linus 永远的神!
## 配置用户
```bash
git config --global user.name "mozzie"
git config --global user.email himozzie@foxmail.com
```
## alias 别名
解决参数太多,记不住的问题
> !! HEAD -> master HEAD 相当于一个指针,指向当前所在分支
```bash
# 查看项目分支图,
git config --global alias.lo "log --oneline --decorate --graph --all"
```
## .git 文件结构
- hooks提交代码前检查代码格式……
- info包含一个排除性文件
- logs保存日志信息不太需要
- `objects`:相当于数据库,存储所有数据内容
- `refs`:存放提交对象指针,管理分支的
- config配置文件
- description仓库描述信息
- `HEAD`:指示目前被检出的分支
- `index`:文件保存暂存区信息
## 修改远程仓库
```bash
# way 1
git remote set-url origin [url]
# way 2
git remote rm origin
git remote add origin [url]
# way3
修改 config 文件
```
## 高层命令
### 初始化仓库
`git init`
### 修改添加到暂存区
`git add ./`,相当于如下操作:
```bash
# 有多少文件改动,就执行多少次
git hash-object -w 文件名
git update-index
```
> !! git add ./ 先把工作区生成 git 对象,放到版本库,然后再放到暂存区
### 暂存区提交到版本库
`git commit -m 'comment'`,相当于如下操作:
```bash
git write-tree
git commit-tree
```
也可以跳过暂存区提交,`git commit -a -m `
### 查看哪些修改没有暂存
`git diff`:没有暂存
`git diff --staged`:查看哪些修改以及被暂存了,但没有提交
### 查看提交历史记录
`git log --oneline`,打印出`hash值`是提交对象
## 分支
本质是一个`提交对象`,每次`git branch name`中的`name`,指针`HEAD`,就会根据`name`指向提交对象
如果要开发新功能,就新建一个`分支A`,写完再合`master`分支,正常来说`master`分支不会轻易给修改权限。
如果另一个新功能,和`分支A`同级、并行的,那就`切到master`分支,在`master`分支基础上,开`分支B`,进行新功能开发
> 一般来说master 分支没有权限,需要自己重新写开一个分支,分支名用 nickname
```bash
C1 master
|——C2——C3——C4 mozzie
```
### 分支列表
`git branch`
### 创建分支
`git branch 分支名`,并不会自动切换到分支
### 切换分支
> !! 最佳实践每次切换分之前git status 查一下,当前分支一定要是干净的
`git checkout 分支名`
### 合并分支
> !! 做任何事情,确保做完了,再合并到 master 分支
场景:需要增加功能`feat:#53`
```bash
# HEAD -> master新开一个分支
git checkout -b 'feat53'
```
突然发现 bug需要修复`bug:#52`
```bash
# HEAD -> feat53先提交#53分支的工作
git commit -a -m 'feat53 完成50%'
# 切回 master 分支
git checkout master
# HEAD -> master创建 issue52 分支
git checkout -b 'issue52'
# 改完了issue52
git commit -a -m 'fix:issue52'
# HEAD -> master
git checkout master
# 合
git merge issue52
# 删除 issue52分支(hash还在)
git branch -d issue52
```
> 此时,由于`issue52`是在之前的 master 分支上生成的,故而`feat53`的分支仍然存在`issue52`的 bug所以有可能存在冲突需要手动解决
```bash
# HEAD -> master
git merge feat53
# 此处省略解决冲突
git add ./
git commit -m 'fix:merge conflict'
# 删除 feat53
git branch -d feat53
```
### 删除分支
查看哪些分支合并到当前分支,`git branch --merged`,这个列表中分支名字前没有\*号的分支通常可以使用`git branch -d 分支名`删掉
`git branch -D 分支名`,强制删除
### 新建分支并指向指定提交对象
`git branch name commitHash`,例`git log --oneline`如下:
```bash
* hasfh2asd 1.txt
* 1shfd2zsw 2.txt
* 67rf73has 3.txt
* 03uhr4rug 4.txt
```
输入`git branch CCC 03uhr4rug`,那么会创建一个名为`CCC`的分支,并且`CCC`分支有`4.txt`
> 通常想看原来的某个版本的代码,就可以这样操作,看完,把这个分支删了
### 远程分支
> `git clone`下来的分支,默认就会建立一个`远程跟踪分支`(同步关系),例如 master 分支
- 本地分支
场景一:如果想公开一个`share`分支 ,与他人共同写作:
```bash
# 过程中会生成生成一个远程跟踪分支 origin/share
git push origin share
```
场景二:创建一个本地分支`b1`,直接跟踪远程分支`orgin/b1`
```bash
git checkout -b 'b1' 'origin/b1'
```
场景三:已存在一个本地分支`dev`,改成远程跟踪分支
```bash
# HEAD -> dev建立 本地分支 dev 与 远程分支 origin/dev 关系
git branch -u origin/dev
# 这样就可以直接
git push / git pull
```
- 远程分支
查看远程分支:`git remote -v`
查看当前本地分支的远程跟踪分支:`git branch -vv`
## 远程分支删除, 本地更新 --prune
```bash
# 不加 --prune和 fetch 等价, 远程被删除的分支不会同步删除本地origin的分支
git remote update origin --prune
```
# 提交规范
```bash
type(scope): subject
# 例如
feat(miniprogram): 增加了小程序模板消息相关功能
```
通常`type`有如下:
- feat - 新功能 feature
- fix - 修复 bug
- docs - 文档注释
- style - 代码格式(不影响代码运行的变动)
- refactor - 重构、优化(既不增加新功能,也不是修复 bug)
- perf - 性能优化
- test - 增加测试
- chore - 构建过程或辅助工具的变动
- revert - 回退
- build - 打包
## 自动生成 Change log
原理:利用 `child_process`获取 `git log`内容,处理字符串
```js
const execSync = require("child_process").execSync; //同步子进程
const fs = require("fs");
const process = require("process");
const path = require("path");
const inquirer = require("inquirer");
const dayjs = require("dayjs");
const axios = require("axios");
// env
const isForCommanHuman = process.argv.includes("--common");
// changelog.md生成路径
const outputpath = path.resolve(process.cwd(), "./changelog.md");
// 非空检测
if (!fs.existsSync(outputpath)) fs.writeFile(outputpath, "", (err) => {});
// 华丽的gitlog日志
const perfectGitLog = (startTime, endTime) =>
isForCommanHuman
? `git log --since="${startTime}" --until="${endTime}" --no-merges --pretty=format:"%cr %C(cyan)%s"`
: `git log --since="${startTime}" --until="${endTime}" --no-merges --pretty=format:"%C(yellow)%h %C(green)%cn %C(redz)(%cr:%ci) %C(cyan)%s"`;
const wxrobotHook =
"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=be4d4473-c290-4ddd-a089-41df3ed1d601";
// 当前时间
const now = Date.now();
// 2周前
const weeks_2_ago = now - 14 * 24 * 60 * 60 * 1000;
inquirer
.prompt([
{
type: "input",
name: "startDate",
message: `起始时间,默认13天前 \n`,
default: dayjs(weeks_2_ago).format("YYYY.MM.DD"),
validate: (val) => /\d{4}.\d{2}.\d{2}/.test(val),
},
{
type: "input",
name: "endDate",
message: `结束时间,默认今天 \n`,
default: dayjs(now).format("YYYY.MM.DD"),
validate: (val) => /\d{4}.\d{2}.\d{2}/.test(val),
},
{
type: "rawlist",
message: "是否通知到微信机器人:",
name: "notifyWxrobot",
choices: ["Y", "N"],
},
])
.then((answers) => {
const { startDate, endDate, notifyWxrobot } = answers;
const rowTemplate = perfectGitLog(startDate, endDate);
let fmt = execSync(rowTemplate)
.toString()
.trim()
.replace(/feat: /gi, "✅: ")
.replace(/fix: /gi, "🐛: ")
.replace(/chore: /gi, "🎨: ")
.replace(/perf: /gi, "⚡: ")
.replace(/docs: /gi, "📝: ")
.replace(/refactor: /gi, "🔨: ")
.replace(/anno: /gi, "🔖: ")
.replace(/style: /gi, "👷: ");
fs.writeFileSync(outputpath, fmt, (err) => {});
// 通知微信群聊机器人
if (notifyWxrobot === "Y") {
axios.post(wxrobotHook, {
msgtype: "markdown",
markdown: {
content: fmt,
},
});
}
})
.catch((error) => {
if (error.isTtyError) {
// Prompt couldn't be rendered in the current environment
} else {
// Something else went wrong
}
});
```
## 使用 husky+eslint 规范提交
> 需要配合 eslint
`git init`后,`yarn add husky`,在`package.json`配置
```json
{
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
}
}
```
## 生成 ssh key
```bash
ssh-keygen -t rsa -C "himozzie@foxmail.com"
```
## 查看 ssh 公钥
```bash
cat ~/.ssh/id_rsa.pub
```
# git 代理
> 前提条件是开了扶墙工具
git clone 拉取方式选择 http/https(默认会让你输入账号密码,比较蛋疼),不要选择 ssl 拉取
```bash
git config --global http.proxy 'http://127.0.0.1:1081'
git config --global https.proxy 'https://127.0.0.1:1081'
# 清除
git config --global --unset http.proxy
git config --global --unset https.proxy
```
# 终端临时代理
```bash
# cmd临时代理方案cmd窗口关闭则代理失效
set http_proxy=http://127.0.0.1:50015
set https_proxy=http://127.0.0.1:50015
```
# git 钩子(hooks)
原理:项目 git push 到远程仓库,远程仓库的钩子 post 通知 www/wwwroot 下的站点 pull 远程仓库
> git 钩子需要 git 服务和 pull 在同一环境中
- 创建远程仓库,配置钩子,git post-receive 钩子
```bash
#!/bin/bash
unset $(git rev-parse --local-env-vars);
# post-receive接收到pull指令后,执行bash命令
cd /www/wwwroot/doc.mozzie.cn/ && git pull origin master
```
> web 目录下 doc.mozzie.cn 是网站目录,本地项目编译打包后,直接 git push 的目录
## web 钩子
待耍
# 项目
## 统计项目代码行数
统计所有人代码增删量,拷贝如下命令, git bash 终端git 项目某分支下执行
```bash
git log --format='%aN' | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -; done
```
## 统计制定提交者代码量
替换`username`为提交者的名称
```bash
git log --author="username" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -
```
# 搭建 gogs
gogs 官网下载,压缩包解压到`www/wwwroot/`下
```bash
cd /www/wwwroot/gogs
# 启服务,默认3000的端口被占用则 ./gogs web -port 3001
./gogs web
```
浏览器访问`http://yourip:3000/install`,注意服务器安全组放行 3000 端口
- 为 gogs 添加 mysql 数据库
- 配置 mysql
- 配置 gogs 相关信息
- 配置其他信息
停掉`./gogs web`
在刚安装的 gogs 路径下,找到`/gogs/scripts/systemd/gogs.service` 文件复制一份
```bash
User=root
Group=root
WorkingDirectory=/www/wwwroot/git.mozzie.cn
ExecStart=/www/wwwroot/git.mozzie.cn/gogs web
Restart=always
Environment=USER=root HOME=/www/wwwroot/git.mozzie.cn
```
将修改好的`gogs.service`文件上传到`/etc/systemd/system`下,并执行以下命令来激活 gogs
```bash
sudo systemctl enable gogs
# 启动gogs
sudo systemctl start gogs
```
`/gogs/custom/conf/app.ini`,修改该文件可以自定义配置,安装步骤填错了,可以这里修改,重启 gogs 服务即可
## 配置 gogs GIT 钩子
场景:以 Gogs 为例,先 git push `A项目` 到远程仓库 `REPO`,服务端 web 目录`webB`文件夹触发钩子执行 git pull
1、gogs 初始化一个仓库`REPO`,仓库设置=>管理 GIT 钩子=>post-receive填入
```bash
#!/bin/sh
export GIT_WORK_TREE=/www/wwwroot/webB
export GIT_DIR=${GIT_WORK_TREE}/.git
cd ${GIT_WORK_TREE}
git pull
```
2、利用 ssh 工具登录服务器
```bash
ssh root@mozzie.cn
```
3、生成 ssh key
```bash
ssh-keygen -t rsa -C "himozzie@foxmail.com"
```
4、配置公钥到 gogs
```bash
cat ~/.ssh/id_rsa.pub
```
把打印出的公钥,配置到 ssh 密钥
5、最后一步
配置好服务端的公钥后,就可以无需用户名密码,`cd /webB`目录下,执行`git pull`,此后每次`git push A项目`,服务端都会触发`GIT钩子`,自动从`REPO`拉取最新的仓库文件