GIT 最佳实践

GIT 本质是一个数据库,用来存代码的

  • 工作区:一个沙箱环境,GIT 不负责管理,你尽管在沙箱里面对文件进行操作
  • 暂存区:工作区文件变动先不急着提交,暂存到一定数量,在提交到版本库
  • 版本库:

!! Linus 永远的神!

配置用户

git config --global user.name "mozzie"
git config --global user.email himozzie@foxmail.com

alias 别名

解决参数太多,记不住的问题

!! HEAD -> master HEAD 相当于一个指针,指向当前所在分支

# 查看项目分支图,
git config --global alias.lo "log --oneline --decorate --graph --all"

.git 文件结构

  • hooks:提交代码前,检查代码格式……
  • info:包含一个排除性文件
  • logs:保存日志信息,不太需要
  • objects:相当于数据库,存储所有数据内容
  • refs:存放提交对象指针,管理分支的
  • config:配置文件
  • description:仓库描述信息
  • HEAD:指示目前被检出的分支
  • index:文件保存暂存区信息

修改远程仓库

# 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 ./,相当于如下操作:

# 有多少文件改动,就执行多少次
git hash-object -w 文件名
git update-index

!! git add ./ 先把工作区生成 git 对象,放到版本库,然后再放到暂存区

暂存区提交到版本库

git commit -m 'comment',相当于如下操作:

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

C1              master
|——C2——C3——C4   mozzie

分支列表

git branch

创建分支

git branch 分支名,并不会自动切换到分支

切换分支

!! 最佳实践:每次切换分之前,git status 查一下,当前分支一定要是干净的

git checkout 分支名

合并分支

!! 做任何事情,确保做完了,再合并到 master 分支

场景:需要增加功能feat:#53

# HEAD -> master,新开一个分支
git checkout -b 'feat53'

突然发现 bug,需要修复bug:#52

# 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,所以有可能存在冲突,需要手动解决

# 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如下:

* hasfh2asd 1.txt
* 1shfd2zsw 2.txt
* 67rf73has 3.txt
* 03uhr4rug 4.txt

输入git branch CCC 03uhr4rug,那么会创建一个名为CCC的分支,并且CCC分支有4.txt

通常想看原来的某个版本的代码,就可以这样操作,看完,把这个分支删了

远程分支

git clone下来的分支,默认就会建立一个远程跟踪分支(同步关系),例如 master 分支

  • 本地分支

场景一:如果想公开一个share分支 ,与他人共同写作:

# 过程中会生成生成一个远程跟踪分支 origin/share
git push origin share

场景二:创建一个本地分支b1,直接跟踪远程分支orgin/b1

git checkout -b 'b1' 'origin/b1'

场景三:已存在一个本地分支dev,改成远程跟踪分支

# HEAD -> dev,建立 本地分支 dev 与 远程分支 origin/dev 关系
git branch -u origin/dev
# 这样就可以直接
git push / git pull
  • 远程分支

查看远程分支:git remote -v

查看当前本地分支的远程跟踪分支:git branch -vv

远程分支删除, 本地更新 –prune

# 不加 --prune,和 fetch 等价, 远程被删除的分支不会同步删除本地origin的分支
git remote update origin --prune

提交规范

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内容,处理字符串

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配置

{
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint"
    }
  }
}

生成 ssh key

ssh-keygen -t rsa -C "himozzie@foxmail.com"

查看 ssh 公钥

cat ~/.ssh/id_rsa.pub

git 代理

前提条件是开了扶墙工具

git clone 拉取方式选择 http/https(默认会让你输入账号密码,比较蛋疼),不要选择 ssl 拉取

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

终端临时代理

# 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 钩子
#!/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 项目某分支下执行

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为提交者的名称

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/

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 文件复制一份

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

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,填入

#!/bin/sh
export GIT_WORK_TREE=/www/wwwroot/webB
export GIT_DIR=${GIT_WORK_TREE}/.git
cd ${GIT_WORK_TREE}
git pull

2、利用 ssh 工具登录服务器

ssh root@mozzie.cn

3、生成 ssh key

ssh-keygen -t rsa -C "himozzie@foxmail.com"

4、配置公钥到 gogs

cat ~/.ssh/id_rsa.pub

把打印出的公钥,配置到 ssh 密钥

5、最后一步

配置好服务端的公钥后,就可以无需用户名密码,cd /webB目录下,执行git pull,此后每次git push A项目,服务端都会触发GIT钩子,自动从REPO拉取最新的仓库文件