美文网首页
【优化】记一次通过工具减少 Git 冲突

【优化】记一次通过工具减少 Git 冲突

作者: Gopal | 来源:发表于2020-11-18 12:54 被阅读0次

    起因

    当我们的项目越来越大的时候,Git 冲突是团队协作中令人非常苦恼的事情,不仅仅浪费了我们时间,而且很容易解决冲突的时候出现问题。

    我能想到的就是模块细分化,每个人都负责自己相关的模块,这样开发者之间的代码就不会相互影响,也就不会有代码的冲突。但多人协作项目中公共的代码必不可少,比如我们常见的公共变量文件 constant.js,并且大部分的冲突来源于此。

    基于此,Leader 提出了另外一个解决方案——控制书写代码的顺序

    为什么顺序那么重要

    对于这个问题,我们要先清楚,为什么会有代码冲突?

    因为我们改了同一个文件中同一行的代码

    举个例子,比如我们常量定义中有如下:

    export const Employees = {
      Andy: 'I can sing',
      Oliver: 'I can run',
      Ivan: 'I can rap'
    };
    

    同事 A 和我都要改到 Employees 这个常量,他添加 Brad: 'I am Brad' , 我添加了 Patrick: 'I am Patrick',我们都加到了该变量的后面,就会导致冲突,类似如下

    @@@ -1,4 -1,4 +1,8 @@@
      Andy: 'I can sing',
      Oliver: 'I can run',
      Ivan: 'I can rap',
      <<<<<<< HEAD
     +Brad: 'I am Brad',
      =======
    + Patrick: I am Patrick,
      >>>>>>>
    

    但假如我们两个都是有顺序的会变成怎样呢?

    首先调整之后文件的顺序如下:

    export const Employees = {
      Andy: 'I can sing',
      Ivan: 'I can rap',
      Oliver: 'I can run'
    };
    

    同事 A 添加代码如下:

    export const Employees = {
      Andy: 'I can sing',
    + Brad: 'I am Brad',
      Ivan: 'I can rap',
      Oliver: 'I can run'
    };
    

    我添加代码如下:

    export const Employees = {
      Andy: 'I can sing',
      Ivan: 'I can rap',
      Oliver: 'I can run',
    + Patrick: 'I am Patrick'
    };
    

    这个时候,当我们合代码的时候就没有冲突了

    通过工具去排序

    知道了顺序对于我们避免解决冲突的重要性,那么接下来就是要执行了,如果要求同事们都时刻遵循写代码的顺序,显然不太合理,我们打算用工具去执行。

    我用 vue-cli 演示下

    具体步骤如下:

    时机:在 git commit 之前,可以使用 git hooks 做到

    在 package.json 中加入如下代码,意思是在 git commit 之前会去执行 node ./auto-fix/index.js 以及 git add 命令

      "lint-staged": {
        "*.{js}": [
          "node ./auto-fix/index.js",
          "git add"
        ]
      },
      "gitHooks": {
        "pre-commit": "lint-staged"
      },
    

    如果发现没有触发的话,可以看下是不是没有这两个包 lint-stagedyorkie。其中 yorkie 是 尤大大 folk husky,它俩功能是一样的,都是生成一些 git hooks 文件,读取项目中package.json 的相关配置项去执行一些命令,区别是尤大做了一些逻辑和配置上的改动

    npm i lint-staged --save-dev
    npm i yorkie --save-dev  
    

    读取和排序:寻找指定目录下的文件(下面示例为 src/constant 目录下),匹配出文件中的对象,针对对象排序

    这里排序的策略是先针对 value 值进行排序,如果 value 值相同,再针对 key 值排序

    写入:将排好序的文件写回原文件

    提示:成功或者失败的提示

    新建文件 auto-fix/index.js, 代码如下:

    const fs = require('fs');
    const path = require('path');
    const chalk = require('chalk');
    
    function resolve(p) {
      return path.join(__dirname, '..', p);
    }
    
    const objRegex = /\{[^}\/\/]*\}/g;
    
    function fileDisplay(filePath) {
      let file = resolve(filePath);
      let fileList = fs.readdirSync(file, 'utf8');
    
      function compare() {
        return function ([key1, value1],[key2, value2]) {
          key1 = Number.isNaN(Number(key1)) ? key1.toLowerCase() : key1;
          key2 = Number.isNaN(Number(key2)) ? key2.toLowerCase() : key2;
          value1 = Number.isNaN(Number(value1)) ? value1.toLowerCase() : value1;
          value2 = Number.isNaN(Number(value2)) ? value2.toLowerCase() : value2;
          if (value1 === value2) {
            return key1 < key2 ? -1 : key1 > key2 ? 1 : 0; // 升序
          } else {
            return value1 < value2 ? -1 : value1 > value2 ? 1 : 0;
          }
        }
      }
    
      // 读取文件
      fileList.forEach(filename => {
        let fileDir = path.join(filePath, filename);
        console.log(chalk.cyan(`Auto fix ${fileDir}`))
        let fileContents = fs.readFileSync(fileDir, 'utf8');
    
        function sortObj(item) {
          item = item.replace(/(\S+):/g,"\"$1\":")
          .replace(/'/g, '"');
    
          let arr = [];
          item = JSON.parse(item);
          arr = Object.entries(item);
          // 排序
          arr = arr.sort(compare());
          let tempObj = {};
          // 将排序好的数组拼接成对象
          tempObj = arr.reduce((_sortedObj, [key, val]) => ({
            ..._sortedObj,
            [key]: val
          }), {});
          let tempStr = JSON.stringify(tempObj, null, 2);
          // 去掉 key 值的双引号
          tempStr = tempStr.replace(/"/g, "").replace(/\: /g,"\: \'").replace(/\,/g,"\'\,").replace(/\n\}/g,"\'\n\}");
          return tempStr;
        }
    
        // 匹配对象并排序并替换
        fileContents = fileContents.replace(objRegex, function(match) {
          return sortObj(match)
        });
    
        // 输出到文件中
        fs.writeFileSync(fileDir, fileContents, 'utf8');
      });
    
    }
    try {
      fileDisplay('src/constant');
      console.log(chalk.green(`Auto fix complete`))
    } catch (e) {
      console.log(e);
      console.log(chalk.red(`Auto fix Error, You can check the the problem in the auto-fix/index.js directory`))
      throw new Error(`Auto fix Error, You can check the the problem in the auto-fix/index.js directory`)
    }
    

    结果

    Gif 演示一下

    探索:对象属性遍历有顺序么?

    我们知道,数组遍历是有顺序的,也经常说对对象遍历是无序的。

    但实际上我理解这个“无序”指的只是不会按照属性排列前后的顺序而已,对象属性遍历本身是有自己的一套规则的。

    可以实验一下

    let obj = { [Symbol()]:0, b:0, 10:0, 2:0, a:0 };
    // for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
    // 输出 2 10 b a
    for (let key in obj) {
      console.log(key)
    }
    // Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
    // [ '2', '10', 'b', 'a' ]
    console.log(Object.keys(obj));
    
    // Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
    // [ '2', '10', 'b', 'a' ]
    console.log(Object.getOwnPropertyNames(obj));
    
    // Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
    // [ Symbol() ]
    console.log(Object.getOwnPropertySymbols(obj));
    
    // Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
    // [ '2', '10', 'b', 'a', Symbol() ]
    console.log(Reflect.ownKeys(obj));
    

    总结如下:

    • 首先遍历所有数值键,按照数值升序排列
    • 其次遍历所有字符串键,按照加入时间升序排列
    • 最后遍历所有 Symbol 键,按照加入时间升序排列

    回顾实现——也存在类似问题

    上面的实现中,我是先根据属性的 value 值排序,如果 value 值相同再根据 key 值排序。排序后得到一个有顺序的二维数组,类似如下:

    [["Andy","I am Andy"],["Gopal","I am Gopal"],["Ivan","I am Ivan"],["Oliver","I am Oliver"],["Patrick","I am Patrick"]]
    

    然后遍历数组依次写入对象中,这样看起来似乎是没有问题,结果却是也是!但假如 key 值是 number 类型呢?比如类似如下:

    export const Employees = {
      Andy: 'I am Andy',
      Gopal: 'I am Gopal',
      Ivan: 'I am Ivan',
      1: 'Z',
      2: 'A',
      3: 'J',
      Oliver: 'I am Oliver',
      Patrick: 'I am Patrick'
    };
    

    按照预期,按照 value 排序,应该得到

    export const Employees = {
      2: 'A',
      Andy: 'I am Andy',
      Gopal: 'I am Gopal',
      Ivan: 'I am Ivan',
      Oliver: 'I am Oliver',
      Patrick: 'I am Patrick'
      3: 'J',
      1: 'Z'
    };
    

    但事实上,我们得到了类似如下的结果:

    export const Employees = {
      1: 'Z',
      2: 'A',
      3: 'J',
      Andy: 'I am Andy',
      Gopal: 'I am Gopal',
      Ivan: 'I am Ivan',
      Oliver: 'I am Oliver',
      Patrick: 'I am Patrick'
    };
    

    这个实际上就是跟我们上面提到的属性遍历规则有关,因为首先遍历所有数值键,按照数值升序排列。其次遍历所有字符串键,按照加入时间升序排列。

    缺点

    除了上面所说的属性顺序问题,如果你细读上面的代码,其实是会发现一些问题,也算是一些 TODO 项,如果有朋友解决了,欢迎给我提个 pr

    • 目前只对简单的对象有用,嵌套的对象无效
    • 对象中如果书写注释,将导致对象无法匹配到
    • 对开发者的代码进行了更改,有可能开发者会有疑惑(这一点,我尽量使用提示去说明)
    • 能避免大部分冲突,但实际上不能 100%

    总结

    本文记录了一次通过利用 git hooks 在代码提交之前给相关的代码排序,从而减少合代码时候的冲突,也探讨了一下 Js 对象属性遍历属性的问题,希望对大家有所启发。

    涉及到的 demo 已放 Github,有更好的想法可以提 PR

    原创不易,欢迎点赞评论~

    往期优秀文章推荐

    参考

    相关文章

      网友评论

          本文标题:【优化】记一次通过工具减少 Git 冲突

          本文链接:https://www.haomeiwen.com/subject/cdymbktx.html