--- title: Git categories: - CS 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`拉取最新的仓库文件