機能要件#
[git commit -m "msg" に似た機能]
- -m オプションを使用する場合はメッセージを直接印刷し、-m オプションを使用しない場合は自動的に vim を開いてメッセージを入力
- 詳細説明
- ① オプションとオプションパラメータ - m "first commit" を含む場合
- 関連メッセージを直接印刷
- ② -m オプションがない場合
- 自動的に vim を開き、ユーザーが "second commit" と入力し、保存して終了した後に関連メッセージを印刷
- vim で有効なメッセージを入力しなかった場合 [コメントは有効なメッセージには含まれない]
- この操作は失敗し、ユーザーに親切に通知
- ① オプションとオプションパラメータ - m "first commit" を含む場合
- [PS]
- 3 つのプロセスが必要な場合があります:親プロセス、vim プロセス、vim によって生成されたファイルを削除するプロセス
- vim で入力されたメッセージが有効かどうかを判断する必要があります
- 子プロセスが実行され、親プロセスが待機し、データを読み取り、印刷します [cat]、rm
最終的な効果#
- 入力./git_commit -m "first commit"
- メッセージを直接出力
- 入力./git_commit
- まず vim が開き、任意のメッセージを入力できます
- ① コメントを含む複数行のメッセージを入力した場合
- 最終的な印刷結果は以下の通り
- 複数行が 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 で生成された実行可能ファイル名
参考#
- 主な知識点は「ネットワークとシステムプログラミング」を参考にしています
- 0 コース紹介とコマンドライン解析関数——getopt
- 1 ファイル、ディレクトリ操作と ls の実装の考え方——open、read
- 3 マルチプロセス——fork、wait、exec ファミリー⭐