512 lines
12 KiB
Markdown
512 lines
12 KiB
Markdown
|
---
|
|||
|
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`拉取最新的仓库文件
|