Bo2SS

Bo2SS

git commitの-mオプションの原理実装

機能要件#

[git commit -m "msg" に似た機能]

  • -m オプションを使用する場合はメッセージを直接印刷し、-m オプションを使用しない場合は自動的に vim を開いてメッセージを入力
  • 詳細説明
    • ① オプションとオプションパラメータ - m "first commit" を含む場合
      • 画像
      • 関連メッセージを直接印刷
    • ② -m オプションがない場合
      • 画像
      • 自動的に vim を開き、ユーザーが "second commit" と入力し、保存して終了した後に関連メッセージを印刷
      • 画像
      • vim で有効なメッセージを入力しなかった場合 [コメントは有効なメッセージには含まれない]
      • 画像
      • この操作は失敗し、ユーザーに親切に通知
  • [PS]
    • 3 つのプロセスが必要な場合があります:親プロセス、vim プロセス、vim によって生成されたファイルを削除するプロセス
    • vim で入力されたメッセージが有効かどうかを判断する必要があります
    • 子プロセスが実行され、親プロセスが待機し、データを読み取り、印刷します [cat]、rm

最終的な効果#

  • 入力./git_commit -m "first commit"
    • 画像
    • メッセージを直接出力
  • 入力./git_commit
    • まず vim が開き、任意のメッセージを入力できます
    • 画像
    • ① コメントを含む複数行のメッセージを入力した場合
      • image
      • 最終的な印刷結果は以下の通り
      • image
      • 複数行が 1 行に統合されます [git の設計に従う]、コメントは無視されます
    • ② 直接で vim を終了した場合
      • 画像
      • メッセージファイルを開けないという通知
    • ③ 全てがコメントの場合
      • 画像
      • メッセージが空であるという通知

[PS]

  • vim がファイルを開くときに、その TYPE=GITCOMMIT を指定できますか?現在 TYPE は空です
    • 👉 vim が開くファイル名を COMMIT_EDITMSG に変更すれば、メッセージコードに少し色付けができます

実装プロセス#

思考フローチャート#

  • 画像

【プロセス分析】

  • 後でrmプロセスを作成する必要があるため、親プロセスを直接vimプロセスに置き換えることはできません
    • そうでなければ、vim を終了するとプログラムが終了してしまいます
  • 親プロセスは子プロセスを fork し、exec ファミリーでそのプロセスをvimプロセスに置き換えます
  • 親プロセスはさらに子プロセスを fork し、rmプロセスに置き換えます
    • 親プロセスを直接rmプロセスに置き換えることも可能です
    • しかし、新しいプロセスを fork することで、親プロセスが削除状態を監視し、さらに多くの操作を行う可能性があります

コマンドライン引数の取得#

-m オプションをキャッチし、このオプションを使用するには必ずパラメータが必要です

#include "head.h"
int main(int argc, char **argv) {
    if (argc == 1) {
        // 1.オプションなし:vimでmsgを入力する必要があります
        printf("no msg, need vim.\n");
    }
    int opt;
    while ((opt = getopt(argc, argv, "m:")) != -1) {
        switch (opt) {
            case 'm': {
                // 2.-mおよびオプションパラメータあり:パラメータを印刷
                printf("msg: %s\n", optarg);
            } break;
            default: {
                // 3.オプションが不正:親切に通知
                fprintf(stderr, "Usage: %s [-m msg]\n", argv[0]);
                exit(1);
            }
        }
    }
    return 0;
}
  • 3 つのケースのみ:① オプションなし;② -m オプションとオプションパラメータあり;③ オプションが不正
  • ヘッダーファイル head.h は末尾にあります
  • テストスクリプト test.sh を使用してテストし、結果は以下の通りです:
    • 画像
    • 3 つのケースでの出力を示しました

メッセージの入力#

vim を使用してメッセージを入力します

  • 親プロセスは fork を使用して子プロセスを作成し、execlp を使用してそれを vim プロセスに置き換えます
  • 親プロセスは wait を使用して子プロセスの状態を監視し、ゾンビプロセスの発生を避けます
// メッセージの入力
void input_msg() {
    pid_t pid;
    int status;
    // 親プロセスが子プロセスをコピー
    if ((pid = fork()) < 0) {
        perror("fork()");
        exit(1);
    }
    if (pid == 0) {
        // 子プロセスをvimプロセスに置き換え
        execlp("vim", "vim", "COMMIT_MSG", NULL);
    } else {
        wait(&status);  // 子プロセスの状態を監視
        // 親プロセスがvimエラーを通知
        if (WEXITSTATUS(status)) printf("vim error!\n");
        else printf("input msg completed!\n");
    }
    return ;
}
  • オプションなしのコマンドでは、vim プロセスが COMMIT_MSG ファイルを開きます
    • 画像
    • ユーザーはメッセージを入力できます
  • vim を終了すると、vim プロセスが終了し、親プロセスが感知できます
    • 画像
    • 親プロセスが "input msg completed!" と通知します

メッセージの読み取り#

親プロセスがメッセージファイルを読み取ろうとします

  • 【親切な通知】メッセージファイルが開けない可能性があります [ファイルが存在しない場合]、または有効なメッセージが含まれていない可能性があります [全てコメント]
  • 【行ごとの解析】行コメントの存在を判断します;複数行の有効メッセージが入力された場合、結果を印刷する際に 1 行に統合し、各行のメッセージを空白で区切ります [git の設計に従う]
#define MAX_LENGTH 512
// メッセージの読み取り
void read_msg() {
    int fd, flag = 0;                    // flag:有効なメッセージを読み取ったかどうか
    char buff[MAX_LENGTH] = {0};         // 最大MAX_LENGTHバイトのメッセージを読み取る
    ssize_t nread;
    // メッセージファイルを開けない場合、親切に通知
    if ((fd = open("COMMIT_MSG", O_RDONLY)) < 0) {
        printf("aborting commit due to msg file can't open.\n");
        exit(1);
    }
    // メッセージファイルを読み取る
    if ((nread = read(fd, buff, sizeof(buff) - 1)) > 0) {
        char *line;
        // 行ごとにメッセージが有効かどうかを判断
        line = strtok(buff, "\n");
        while (line != NULL) {
            // 無効なメッセージでない場合 [コメントでない]
            if (line[0] != '#') {
                flag || printf("msg:");  // 最初の有効なメッセージを印刷する際にメッセージプレフィックス:"msg:"を印刷
                flag = 1;
                printf(" %s", line);
            }
            line = strtok(NULL, "\n");   // buffの'\0'に達するまで繰り返し呼び出す
        }
        flag && printf("\n");
    }
    // 有効なメッセージが読み取れなかった場合、親切に通知
    if (!flag) {
        printf("aborting commit due to empty commit msg.\n");
    }
    close(fd);
    return ;
}
  • 特別な flag 出力メッセージプレフィックス:"msg:"
  • 最大 512 バイトのメッセージファイルを読み取ります
  • ⭐文字列分割関数 strtok——cplusplus
    • char* strtok(char* str, const char* delimiters)
    • 画像
    • 繰り返し呼び出す必要があり、2 回目以降は str を NULL に置き換え、毎回呼び出すと分割された文字列が返され、NULL は str の末尾に達したことを示します
  • 結果は以下の通りです:
    • オプションなしのコマンドで、vim に次のメッセージを入力します
    • 画像
    • 読み取り成功後、1 行に統合して印刷
    • 画像
    • コメントは無視されます

メッセージファイルの削除#

親プロセスがさらに多くのことを行う可能性があるため、ここで子プロセスを fork し、rm プロセスに置き換えます

  • 親プロセスは rm プロセスの状態を監視でき、rm プロセスも失敗情報を報告します
// メッセージファイルを削除
void remove_file() {
    pid_t pid;
    int status;
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }
    if (pid == 0) {
        execlp("rm", "rm", "COMMIT_MSG", NULL);
    } else {
        wait(&status);
        // 親プロセスがrmの失敗を通知 [rmプロセスも通知があります]
        if (WEXITSTATUS(status)) printf("remove msg file error!\n");
    }
    return ;
}
  • メッセージファイルを使用した後、COMMIT_MSG ファイルは残りません

完全なコード#

git_commit.c#

#include "head.h"
#define MAX_LENGTH 512
// メッセージの入力
void input_msg() {
    pid_t pid;
    int status;
    // 親プロセスが子プロセスをコピー
    if ((pid = fork()) < 0) {
        perror("fork()");
        exit(1);
    }
    if (pid == 0) {
        // 子プロセスをvimプロセスに置き換え
        execlp("vim", "vim", "COMMIT_MSG", NULL);
    } else {
        wait(&status);  // 子プロセスの状態を監視
        // 親プロセスがvimエラーを通知
        if (WEXITSTATUS(status)) printf("vim error!\n");
    }
    return ;
}
// メッセージの読み取り
void read_msg() {
    int fd, flag = 0;                    // flag:有効なメッセージを読み取ったかどうか
    char buff[MAX_LENGTH] = {0};         // 最大MAX_LENGTHバイトのメッセージを読み取る
    ssize_t nread;
    // メッセージファイルを開けない場合、親切に通知
    if ((fd = open("COMMIT_MSG", O_RDONLY)) < 0) {
        printf("aborting commit due to msg file can't open.\n");
        exit(1);
    }
    // メッセージファイルを読み取る
    if ((nread = read(fd, buff, sizeof(buff) - 1)) > 0) {
        char *line;
        // 行ごとにメッセージが有効かどうかを判断
        line = strtok(buff, "\n");
        while (line != NULL) {
            // 無効なメッセージでない場合 [コメントでない]
            if (line[0] != '#') {
                flag || printf("msg:");  // 最初の有効なメッセージを印刷する際にメッセージプレフィックス:"msg:"を印刷
                flag = 1;
                printf(" %s", line);
            }
            line = strtok(NULL, "\n");   // buffの'\0'に達するまで繰り返し呼び出す
        }
        flag && printf("\n");
    }
    // 有効なメッセージが読み取れなかった場合、親切に通知
    if (!flag) {
        printf("aborting commit due to empty commit msg.\n");
    }
    close(fd);
    return ;
}
// メッセージファイルを削除
void remove_file() {
    pid_t pid;
    int status;
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }
    if (pid == 0) {
        execlp("rm", "rm", "COMMIT_MSG", NULL);
    } else {
        wait(&status);
        // 親プロセスがrmの失敗を通知 [rmプロセスも通知があります]
        if (WEXITSTATUS(status)) printf("remove msg file error!\n");
    }
    return ;
}
int main(int argc, char **argv) {
    if (argc == 1) {
        // 1.オプションなし:vimでmsgを入力する必要があります
        input_msg();
        read_msg();
        remove_file();  // ここに到達できれば、メッセージファイルは生成されています
    }
    int opt;
    while ((opt = getopt(argc, argv, "m:")) != -1) {
        switch (opt) {
            case 'm': {
                // 2.-mおよびオプションパラメータあり:パラメータを印刷
                printf("msg: %s\n", optarg);
            } break;
            default: {
                // 3.オプションが不正:親切に通知
                fprintf(stderr, "Usage: %s [-m msg]\n", argv[0]);
                exit(1);
            }
        }
    }
    return 0;
}

head.h#

#ifndef _HEAD_H
#define _HEAD_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#endif

test.sh#

#!/bin/bash
cmd="./git_commit"
msg="first commit"
echo "1)${cmd}:" | lolcat
${cmd}
echo "2)${cmd} -m \"${msg}\":" | lolcat
${cmd} -m "${msg}"
echo "3)${cmd} -m:" | lolcat
${cmd} -m
echo "3)${cmd} -b:" | lolcat
${cmd} -b
  • ./git_commit の git_commit は使用 gcc ... -o git_commit で生成された実行可能ファイル名

参考#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。