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 54ec2336968986b96ad46ff3adc73d9677bc25c0
parent 32a1bea865af0764895473525f0a514d7eee640e
Author: brookjeynes <me@brookjeynes.dev>
Date:   Thu, 25 Jun 2026 07:19:20 +1000

refactor: move related commits to 'commits' subcommand

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

Diffstat:
Mtask.c | 237++++++++++++++++++++++++++++++++++++-------------------------------------------
Mtasks/add_subcommands.txt | 2+-
2 files changed, 110 insertions(+), 129 deletions(-)

diff --git a/task.c b/task.c @@ -23,16 +23,20 @@ struct options { }; static void print_help(const char *program) { - printf("usage: %s [--priority A|-p A] <title> [properties...]\n", program); + printf("usage: %s <command> [options]\n", + program); printf("\n"); printf("create a new task.\n"); printf("\n"); + printf("commands:\n"); + printf(" commits <task-id> list all commits tied <task-id>.\n"); + printf(" new [--priority A|-p A] <title> [properties...] 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(" --related-commits <task-id> list all commits tied <task-id>.\n"); } static int has_txt_extension(const char *name) { @@ -350,77 +354,6 @@ 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 new_command(int argc, char *argv[], struct options *cli_args) { if (argc == 1) { fprintf(stderr, "err: title required\n"); @@ -514,18 +447,19 @@ int new_command(int argc, char *argv[], struct options *cli_args) { int write_failed = 0; int write_errno = 0; + if (cli_args->priority != '\0') { if (fprintf(f, "(%c) ", cli_args->priority) < 0) { - write_failed = 1; - write_errno = errno; + fclose(f); + fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + return 1; } } if (fprintf(f, "%s", cli_args->title) < 0) { - if (!write_failed) { - write_failed = 1; - write_errno = errno; - } + fclose(f); + fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + return 1; } int seen_title = 0; @@ -541,64 +475,122 @@ int new_command(int argc, char *argv[], struct options *cli_args) { } if (fprintf(f, " %s", argv[idx]) < 0) { - if (!write_failed) { - write_failed = 1; - write_errno = errno; - } + fclose(f); + fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + return 1; } } if (fprintf(f, "\n") < 0) { - if (!write_failed) { - write_failed = 1; - write_errno = errno; - } + fclose(f); + fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + return 1; } if (fprintf(f, "id: %s\n", id) < 0) { - if (!write_failed) { - write_failed = 1; - write_errno = errno; - } + fclose(f); + fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + return 1; } - int close_failed = 0; - int close_errno = 0; - if (fclose(f) != 0) { - close_failed = 1; - close_errno = errno; + char *editor = getenv("VISUAL"); + if (editor != NULL && strcmp(editor, "") == 0) { + editor = NULL; + } + if (editor == NULL) { + editor = getenv("EDITOR"); } - if (write_failed) { - fprintf(stderr, "err: failed to write file: %s\n", strerror(write_errno)); + if (editor == NULL || strcmp(editor, "") == 0) { + return 0; + } + + execlp("sh", "sh", "-c", "exec $1 \"$2\"", "task", editor, title_to_file, + NULL); + fprintf(stderr, "err: failed to open editor: %s", strerror(errno)); + + return 1; +} + +int related_commits_command(int argc, char *argv[], struct options *cli_args) { + if (argc != 2) { + fprintf(stderr, "err: task id required\n"); return 1; } - if (close_failed) { - fprintf(stderr, "err: failed to close file: %s\n", strerror(close_errno)); + if (strcmp(argv[1], "") == 0) { + fprintf(stderr, "err: task id can not be empty\n"); return 1; } - char *editor = getenv("VISUAL"); - if (editor != NULL && strcmp(editor, "") == 0) { - editor = NULL; + char *task_id = argv[1]; + + 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); + return 1; } - if (editor == NULL) { - editor = getenv("EDITOR"); + 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); + return 1; } - if (editor != NULL && strcmp(editor, "") == 0) { - editor = NULL; + 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); + return 1; } - if (editor == NULL) { - return 0; + 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); + return 1; } - execlp("sh", "sh", "-c", "exec $1 \"$2\"", "task", editor, title_to_file, - NULL); - perror("err: failed to open editor"); + 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); + return 1; + } + + const char *summary = git_commit_summary(commit); + const char *message = git_commit_message(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); + return 1; + } + + 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(); return 0; } @@ -623,25 +615,14 @@ 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); + if (argc > 1 && strcmp(argv[1], "new") == 0) { + if (new_command(argc - 1, argv + 1, &cli_args) != 0) { + return 1; } - - char *task_id = argv[2]; - print_related_commits(task_id); - - return 0; } - if (argc > 1 && strcmp(argv[1], "new") == 0) { - if (new_command(argc - 1, argv + 1, &cli_args) != 0) { + if (argc > 1 && strcmp(argv[1], "commits") == 0) { + if (related_commits_command(argc - 1, argv + 1, &cli_args) != 0) { return 1; } } diff --git a/tasks/add_subcommands.txt b/tasks/add_subcommands.txt @@ -5,5 +5,5 @@ task doesnt have subcommands. this is a little painful when adding new features as they are forced behind flags. [x] new -[ ] related-commits +[x] related-commits [ ] contexts / projects