Hi Mozzie 显示 / 隐藏 左侧导航 [
⌘ K
偏好 ⌘ p
显示 / 隐藏 文章目录 ]

# Git

上次更新: 2024-03-15 23:14:48

# 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`拉取最新的仓库文件

重拾纯粹的写作

目录

隐藏目录 ]