githookとブランチ管理の組み合わせ実践

ファイル

この記事は最初に公開されました: https://github.com/bigo-frontend/blog/ フォローと再投稿を歓迎します。

導入

プロジェクト開発中には、開発ブランチ、テスト ブランチ、トランク ブランチなどが存在します。通常、テスト ブランチは他のブランチにマージできませんが、誤ってマージしたり (手が震えたり)、知らないうちに新しいものを追加したりする可能性があり、後でオンラインになったときに発見されます (または見つからなかった場合)。 、テストブランチを直接置くだけです。ブランチのコードはオンラインになります)、結果は大なり小なりあり、ロールバックは面倒です。

マージフェーズ中に違法なブランチのマージを直接禁止することはできますか? 答えは「はい」です。次の問題を解くだけです。

  • 合併されているのでしょうか?
  • 現在の支店の名前は何ですか?
  • マージ先のブランチの名前は何ですか?
  • 現在のブランチとマージ対象のブランチが条件を満たしているかどうか (たとえば、マージ対象のブランチをテスト ブランチにすることはできません)

予備知識

gitフック

githooks は、簡単に言えば、git コマンドの実行中にトリガーされるフック関数 (スクリプト プログラム) です。特定の git コマンドによってどのようなフックがトリガーされるかがわかっていれば、それに応じた処理を行うことができます。たとえば、送信情報が仕様 ( commitlint など) に準拠しているかどうかを確認したり、マージを防止したりするために使用できます。この記事で説明する特定のブランチについて説明します。

gitマージ

注文:git merge <branch>

合併の可能性は3通りある

  • fast-forward merge : マージするとき、現在のブランチとマージ先のブランチはブランチ履歴に分岐がありません [簡単に理解すると、マージされるブランチは現在のブランチに基づいており、現在のブランチはそれ以来一度も発生していません。新しいブランチが作成されました変更]。次の方法でgit merge --no-ff <branch>2 番目のマージ ケースに変更できます。

早送りマージ |  アトラシアン

  • no fast-forward merge : 直接の親がマージする 2 つのブランチを指す新しい履歴ノードを追加します。

早送りマージなし |  アトラシアン

  • マージ競合: マージ競合が発生しています。現時点では、競合を解決してから、再度追加してコミットする必要があります。

画像の出典: https://www.atlassian.com/git/tutorials/using-branches/git-merge

事前説明ここのプロジェクトは、 huskyを使用してgit フックを管理する
フロントエンド プロジェクトです。

よくある質問

合併されてるのかな

githooksを参照すると、マージ ステージで次のフックがトリガーされる可能性があることがわかります(この理由は、マージには多くの状況があり、それぞれの状況でトリガーされるフックが一貫していないためであると考えられます)。

  • pre-merge-commit
  • prepare-commit-msg
  • commit-msg
  • post-merge
ケースのマージ\トリガー フック pre-merge-commit prepare-commit-msg commit-msg post-merge
fast-forward merge
no fast-forward merge
merge conflict競合を解決した後に追加してコミットする

merge conflict初期状態から中間状態までは中間状態となり(当前分支 | MERGING)、マージ関連のフックはトリガーされません。競合が解決され、add&commit が開始されると、対応するフックがトリガーされます。

マージの状況に応じて、使用されるフックは次のとおりです。

  • fast-forward mergeおよびno fast-forward merge:post-mergeロジック処理にフックを使用する
  • merge conflict:prepare-commit-msg論理処理に使用 [commit-msgフックではマージされたブランチ名が取得できないため、のみ使用可能prepare-commit-msg]

現在のブランチ名を取得します

git rev-parse --abbrev-ref HEAD

参照: https://stackoverflow.com/questions/6245570/how-to-get-the-current-branch-name-in-git

マージされたブランチ名を取得する

マージ後

このフックはハンドルno fast-forward mergefast-forward mergeマージを行います。

post-mergeフックがトリガーされると、ブランチはマージされ、reflog が更新されるため、git reflogマージされたブランチ情報は次の方法で取得できます。

最初の 2 つのマージ ケースでは、git reflog -1返されるログ形式は次のとおりです。

  • no fast-forward merge:e7cb874 HEAD@{0}: merge feat/no-fast-forward: Merge made by the 'recursive' strategy.
  • fast-forward merge:724446f HEAD@{0}: merge feat/fast-forward: Fast-forward

通常のマッチングにより対応するブランチ名を抽出できます。コードは次のとおりです。

const {
    
     execSync } = require('child_process');

function getMergeBranch() {
    
    
  // 从 reflog 提取合并进来的分支名
  function getBranchNameFromReflog(reflogMessage) {
    
    
    const reg = /@\{\d+\}: merge (.*):/;
    return reg.exec(reflogMessage)[1];
  }

  const reflogMessage = execSync('git reflog -1', {
    
     encoding: 'utf8' });
  const mergedBranchName = getBranchNameFromReflog(reflogMessage);
  return mergedBranchName;
}

準備-コミット-メッセージ

このフックは、マージ競合のケースを処理します。

競合が解決されないため、reflog は更新されず、マージされたブランチを reflog から取得できません。

ただし、マージ競合フェーズ中は、.git/MERGE_HEADマージされたブランチのハッシュが保持されます。
トリガーされるとprepare-commit-msg、ファイルを読み取って対応するコンテンツを取得し、git name-rev [hash]コマンドを使用して対応するブランチ名を取得できます。

const {
    
     execSync } = require('child_process');
const path = require('path');
const fs = require('fs');

// 从 .git/MERGE_HEAD (sha) 提取合并进来的分支名
function getMergeBranch() {
    
    
  try {
    
    
    const mergeHeadPath = path.resolve(process.cwd(), '.git/MERGE_HEAD');
    const mergeHeadSha = fs.readFileSync(mergeHeadPath, {
    
     encoding: 'utf8' });
    const mergeBranchInfo = execSync(`git name-rev ${
      
      mergeHeadSha}`);
    return / (.*?)\n/.exec(mergeBranchInfo)[1];
  } catch (err) {
    
    
    return '';
  }
}

マージされたブランチは要件を満たしていますか

それぞれのシーンに合わせた対応が可能です。たとえば、間違ったブランチをマージした後、プロンプトが表示され、オペレーターはロールバックするかどうかなどを決定できます。

const {
    
     execSync } = require('child_process');
const readline = require('readline');

function showConfirm(currentBranch, mergeBranch, inConflict) {
    
    
  log(`检测到非法合并: ${
      
      mergeBranch} ==into==> ${
      
      currentBranch}`);

  const rl = readline.createInterface({
    
    
    input: process.stdin,
    output: process.stdout,
  });

  rl.question(`是否撤销本次合并?(y/n) `, (answer) => {
    
    
    if (answer === 'y') {
    
    
      log('撤销合并中...');
      if (inConflict) {
    
    
        log(`exec: git merge --abort`);
        execSync('git merge --abort');
        log('已撤销合并 done');
        rl.close();
        process.exit(-1);
      } else {
    
    
        log(`exec: git reset --merge HEAD@{1}`);
        execSync('git reset --merge HEAD@{1}');
        log('已撤销合并 done');
        rl.close();
        process.exit(0);
      }
    } else {
    
    
      rl.close();
      process.exit(0);
    }
  });
};

その他の問題

post-merge最初の質問「マージされているかどうか」では、最終的に 2 つのフックを使用してprepare-commit-msg対応するインターセプト処理を行っていますが、no fast-forward mergeの場合、この 2 つのフックがトリガーされる、つまりインターセプト処理が繰り返し実行されます。この時点で判断が必要となり、フック内のインターセプト ロジックは に
ある場合にのみmerge conflict実行されます。prepare-commit-msg各マージ状況でインターセプト ロジックが 1 回だけトリガーされることが保証されます。

.git/MERGE_MSGその目的は、ファイルが存在するかどうか、およびその内容が矛盾する情報であるかどうかを検出することです。

const {
    
     execSync } = require('child_process');
const path = require('path');
const fs = require('fs');

function isMergingConflict() {
    
    
  // 是否合并中
  const mergeMsgPath = path.resolve(process.cwd(), '.git/MERGE_MSG');
  const isMerging = fs.existsSync(mergeMsgPath);
  if (!isMerging) {
    
    
    return false;
  }

  try {
    
    
    const mergeMsg = fs.readFileSync(mergeMsgPath, {
    
     encoding: 'utf8' });
    return /\n# Conflicts:\n/.test(mergeMsg); // 如果是冲突则能匹配上
  } catch (err) {
    
    }
  return false;
}

要約する

git-hooks-prevent-merge-summary

参考

https://git-scm.com/docs/githooks
https://www.atlassian.com/git/tutorials/using-branches/git-merge

議論するメッセージを残してくださる皆様を歓迎します。スムーズな仕事と幸せな生活をお祈りしています。

私は bigo のフロントエンドです。次号でお会いしましょう。

おすすめ

転載: blog.csdn.net/yeyeye0525/article/details/124590479