task

creates a plain-text task file in the current directory
git clone git://brookjeynes.dev/bjeynes/task.git
Log | Files | Refs | README | LICENSE

commit 131f2bc09460ed05dc105252e9282886275d459f
parent 36003d104ee826cfa33f228b2811f0aa8501fa3e
Author: brookjeynes <me@brookjeynes.dev>
Date:   Fri, 19 Jun 2026 10:21:55 +1000

feat: add --related-commits flag

Task-id: zztpsvlwzrupwqsv
Signed-off-by: brookjeynes <me@brookjeynes.dev>

Diffstat:
MMakefile | 7+++++--
MREADME | 15+++++++++------
Mtask.c | 110++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Atasks/done/add_related_commits_flag.txt | 6++++++
4 files changed, 121 insertions(+), 17 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,7 +1,10 @@ NAME = task -TASK_CFLAGS = ${CFLAGS} -TASK_LDFLAGS = ${LDFLAGS} +LIBGIT_INC = -I/usr/local/include +LIBGIT_LIB = -L/usr/local/lib -lgit2 + +TASK_CFLAGS = ${LIBGIT_INC} ${CFLAGS} +TASK_LDFLAGS = ${LIBGIT_LIB} ${LDFLAGS} SRC = \ task.c diff --git a/README b/README @@ -3,17 +3,20 @@ task Create a plain-text task file in the current directory. + +Usage +----- + + $ task "Call Mom" + $ task -p A "Call Mom" @phone +family + The task title becomes the filename. Spaces, slashes, backslashes, and leading dots are replaced with underscores. If VISUAL or EDITOR is set, task opens the new file in that editor. - -Usage ------ - - $task "Call Mom" - $task -p A "Call Mom" @phone +family +To link a commit to a task, reference the tasks id in the commits footer via +the key 'Task-id', e.g. Task-id: zztpsvlwzrupwqsv Dependencies diff --git a/task.c b/task.c @@ -1,6 +1,8 @@ #define _DEFAULT_SOURCE #include <dirent.h> #include <errno.h> +#include <git2.h> +#include <git2/commit.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -26,18 +28,19 @@ static void print_help(const char *program) { printf("create a new task.\n"); printf("\n"); printf("options:\n"); - printf(" -h, --help show this help\n"); - printf(" -p, --priority A set priority A-Z\n"); - printf(" --context list all known contexts\n"); - printf(" --projects list all known projects\n"); + printf(" -h, --help show this help\n"); + printf(" -p, --priority A set priority A-Z\n"); + printf(" --context list all known contexts\n"); + printf(" --projects list all known projects\n"); + printf(" --related-commits <task-id> list all commits tied <task-id>.\n"); } -int has_txt_extension(const char *name) { +static int has_txt_extension(const char *name) { size_t len = strlen(name); return len >= 4 && strcmp(name + len - 4, ".txt") == 0; } -int generate_task_id(char out[TASK_ID_CHARS + 1]) { +static int generate_task_id(char out[TASK_ID_CHARS + 1]) { uint8_t bytes[TASK_ID_BYTES]; size_t got = 0; @@ -58,7 +61,8 @@ int generate_task_id(char out[TASK_ID_CHARS + 1]) { return 0; } -int task_id_exists_in_dir(const char *dir, const char *id, int allow_missing) { +static int task_id_exists_in_dir(const char *dir, const char *id, + int allow_missing) { struct dirent *dp; FILE *fd = NULL; int rc = -1; @@ -177,7 +181,7 @@ cleanup: return rc; } -int task_id_exists(const char *id) { +static int task_id_exists(const char *id) { int exists = task_id_exists_in_dir(TASKS_DIR, id, 0); if (exists != 0) { return exists; @@ -186,7 +190,7 @@ int task_id_exists(const char *id) { return task_id_exists_in_dir(DONE_TASKS_DIR, id, 1); } -int generate_unique_task_id(char id[TASK_ID_CHARS + 1]) { +static int generate_unique_task_id(char id[TASK_ID_CHARS + 1]) { for (;;) { if (generate_task_id(id) != 0) { return -1; @@ -346,6 +350,77 @@ cleanup: } } +static void print_related_commits(char *task_id) { + int error; + + git_libgit2_init(); + + git_repository *repo = NULL; + if ((error = git_repository_open(&repo, ".")) < 0) { + const git_error *e = git_error_last(); + fprintf(stderr, "err: %d/%d: %s\n", error, e->klass, e->message); + exit(error); + } + + git_object *obj = NULL; + if ((error = git_revparse_single(&obj, repo, "HEAD")) < 0) { + const git_error *e = git_error_last(); + fprintf(stderr, "err: %d/%d: %s\n", error, e->klass, e->message); + exit(error); + } + + const git_oid *head = NULL; + head = git_object_id(obj); + git_object_free(obj); + + git_revwalk *walker = NULL; + if ((error = git_revwalk_new(&walker, repo)) < 0) { + const git_error *e = git_error_last(); + fprintf(stderr, "err: %d/%d: %s\n", error, e->klass, e->message); + exit(error); + } + + if ((error = git_revwalk_push(walker, head)) < 0) { + const git_error *e = git_error_last(); + fprintf(stderr, "err: %d/%d: %s\n", error, e->klass, e->message); + exit(error); + } + + git_oid id; + while (!git_revwalk_next(&id, walker)) { + git_commit *commit; + if ((error = git_commit_lookup(&commit, repo, &id)) < 0) { + const git_error *e = git_error_last(); + fprintf(stderr, "err: %d/%d: %s\n", error, e->klass, e->message); + exit(error); + } + + const char *summary = git_commit_summary(commit); + const char *message = git_commit_message(commit); + const char *git_header = git_commit_raw_header(commit); + + git_message_trailer_array *trailers = + malloc(sizeof(git_message_trailer_array)); + if ((error = git_message_trailers(trailers, message)) < 0) { + const git_error *e = git_error_last(); + fprintf(stderr, "err: %d/%d: %s\n", error, e->klass, e->message); + exit(error); + } + + for (size_t i = 0; i < trailers->count; i++) { + if (strcmp(trailers->trailers[i].key, "Task-id") == 0) { + if (strcmp(trailers->trailers[i].value, task_id) == 0) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), &id); + printf("%s: %s\n", oid_str, summary); + } + } + } + } + + git_libgit2_shutdown(); +} + int main(int argc, char *argv[]) { struct options cli_args = {0}; cli_args.priority = '\0'; @@ -366,6 +441,23 @@ int main(int argc, char *argv[]) { return 0; } + if (argc > 1 && (strcmp(argv[1], "--related-commits") == 0)) { + if (argc != 3) { + fprintf(stderr, "err: task id required\n"); + exit(1); + } + + if (strcmp(argv[2], "") == 0) { + fprintf(stderr, "err: task id can not be empty\n"); + exit(1); + } + + char *task_id = argv[2]; + print_related_commits(task_id); + + return 0; + } + if (argc == 1) { fprintf(stderr, "err: title required\n"); exit(1); diff --git a/tasks/done/add_related_commits_flag.txt b/tasks/done/add_related_commits_flag.txt @@ -0,0 +1,6 @@ +add --related-commits flag @feat +git +id: zztpsvlwzrupwqsv + +this flag will allow the user to see which commits are tied to a specific +issue. it should match against a git trailer, maybe something like `Task-id: +<id>`