/* * Copyright (c) 2017 Martin Pieuchot * Copyright (c) 2018, 2019 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_version.h" #include "got_error.h" #include "got_object.h" #include "got_reference.h" #include "got_repository.h" #include "got_path.h" #include "got_worktree.h" #include "got_diff.h" #include "got_commit_graph.h" #include "got_blame.h" #include "got_privsep.h" #include "got_opentemp.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif static volatile sig_atomic_t sigint_received; static volatile sig_atomic_t sigpipe_received; static void catch_sigint(int signo) { sigint_received = 1; } static void catch_sigpipe(int signo) { sigpipe_received = 1; } struct got_cmd { const char *cmd_name; const struct got_error *(*cmd_main)(int, char *[]); void (*cmd_usage)(void); const char *cmd_alias; }; __dead static void usage(int); __dead static void usage_init(void); __dead static void usage_import(void); __dead static void usage_checkout(void); __dead static void usage_update(void); __dead static void usage_log(void); __dead static void usage_diff(void); __dead static void usage_blame(void); __dead static void usage_tree(void); __dead static void usage_status(void); __dead static void usage_ref(void); __dead static void usage_branch(void); __dead static void usage_add(void); __dead static void usage_remove(void); __dead static void usage_revert(void); __dead static void usage_commit(void); __dead static void usage_cherrypick(void); __dead static void usage_backout(void); __dead static void usage_rebase(void); __dead static void usage_histedit(void); static const struct got_error* cmd_init(int, char *[]); static const struct got_error* cmd_import(int, char *[]); static const struct got_error* cmd_checkout(int, char *[]); static const struct got_error* cmd_update(int, char *[]); static const struct got_error* cmd_log(int, char *[]); static const struct got_error* cmd_diff(int, char *[]); static const struct got_error* cmd_blame(int, char *[]); static const struct got_error* cmd_tree(int, char *[]); static const struct got_error* cmd_status(int, char *[]); static const struct got_error* cmd_ref(int, char *[]); static const struct got_error* cmd_branch(int, char *[]); static const struct got_error* cmd_add(int, char *[]); static const struct got_error* cmd_remove(int, char *[]); static const struct got_error* cmd_revert(int, char *[]); static const struct got_error* cmd_commit(int, char *[]); static const struct got_error* cmd_cherrypick(int, char *[]); static const struct got_error* cmd_backout(int, char *[]); static const struct got_error* cmd_rebase(int, char *[]); static const struct got_error* cmd_histedit(int, char *[]); static struct got_cmd got_commands[] = { { "init", cmd_init, usage_init, "" }, { "import", cmd_import, usage_import, "" }, { "checkout", cmd_checkout, usage_checkout, "co" }, { "update", cmd_update, usage_update, "up" }, { "log", cmd_log, usage_log, "" }, { "diff", cmd_diff, usage_diff, "" }, { "blame", cmd_blame, usage_blame, "" }, { "tree", cmd_tree, usage_tree, "" }, { "status", cmd_status, usage_status, "st" }, { "ref", cmd_ref, usage_ref, "" }, { "branch", cmd_branch, usage_branch, "br" }, { "add", cmd_add, usage_add, "" }, { "remove", cmd_remove, usage_remove, "rm" }, { "revert", cmd_revert, usage_revert, "rv" }, { "commit", cmd_commit, usage_commit, "ci" }, { "cherrypick", cmd_cherrypick, usage_cherrypick, "cy" }, { "backout", cmd_backout, usage_backout, "bo" }, { "rebase", cmd_rebase, usage_rebase, "rb" }, { "histedit", cmd_histedit, usage_histedit, "he" }, }; static void list_commands(void) { int i; fprintf(stderr, "commands:"); for (i = 0; i < nitems(got_commands); i++) { struct got_cmd *cmd = &got_commands[i]; fprintf(stderr, " %s", cmd->cmd_name); } fputc('\n', stderr); } int main(int argc, char *argv[]) { struct got_cmd *cmd; unsigned int i; int ch; int hflag = 0, Vflag = 0; setlocale(LC_CTYPE, ""); while ((ch = getopt(argc, argv, "hV")) != -1) { switch (ch) { case 'h': hflag = 1; break; case 'V': Vflag = 1; break; default: usage(hflag); /* NOTREACHED */ } } argc -= optind; argv += optind; optind = 0; if (Vflag) { got_version_print_str(); return 1; } if (argc <= 0) usage(hflag); signal(SIGINT, catch_sigint); signal(SIGPIPE, catch_sigpipe); for (i = 0; i < nitems(got_commands); i++) { const struct got_error *error; cmd = &got_commands[i]; if (strcmp(cmd->cmd_name, argv[0]) != 0 && strcmp(cmd->cmd_alias, argv[0]) != 0) continue; if (hflag) got_commands[i].cmd_usage(); error = got_commands[i].cmd_main(argc, argv); if (error && !(sigint_received || sigpipe_received)) { fprintf(stderr, "%s: %s\n", getprogname(), error->msg); return 1; } return 0; } fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]); list_commands(); return 1; } __dead static void usage(int hflag) { fprintf(stderr, "usage: %s [-h] [-V] command [arg ...]\n", getprogname()); if (hflag) list_commands(); exit(1); } static const struct got_error * get_editor(char **abspath) { const struct got_error *err = NULL; const char *editor; editor = getenv("VISUAL"); if (editor == NULL) editor = getenv("EDITOR"); if (editor) { err = got_path_find_prog(abspath, editor); if (err) return err; } if (*abspath == NULL) { *abspath = strdup("/bin/ed"); if (*abspath == NULL) return got_error_from_errno("strdup"); } return NULL; } static const struct got_error * apply_unveil(const char *repo_path, int repo_read_only, const char *worktree_path) { const struct got_error *err; #ifdef PROFILE if (unveil("gmon.out", "rwc") != 0) return got_error_from_errno2("unveil", "gmon.out"); #endif if (repo_path && unveil(repo_path, repo_read_only ? "r" : "rwc") != 0) return got_error_from_errno2("unveil", repo_path); if (worktree_path && unveil(worktree_path, "rwc") != 0) return got_error_from_errno2("unveil", worktree_path); if (unveil("/tmp", "rwc") != 0) return got_error_from_errno2("unveil", "/tmp"); err = got_privsep_unveil_exec_helpers(); if (err != NULL) return err; if (unveil(NULL, NULL) != 0) return got_error_from_errno("unveil"); return NULL; } __dead static void usage_init(void) { fprintf(stderr, "usage: %s init repository-path\n", getprogname()); exit(1); } static const struct got_error * cmd_init(int argc, char *argv[]) { const struct got_error *error = NULL; char *repo_path = NULL; int ch; while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: usage_init(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath unveil", NULL) == -1) err(1, "pledge"); #endif if (argc != 1) usage_init(); repo_path = strdup(argv[0]); if (repo_path == NULL) return got_error_from_errno("strdup"); got_path_strip_trailing_slashes(repo_path); error = got_path_mkdir(repo_path); if (error && !(error->code == GOT_ERR_ERRNO && errno == EEXIST)) goto done; error = apply_unveil(repo_path, 0, NULL); if (error) goto done; error = got_repo_init(repo_path); if (error != NULL) goto done; done: free(repo_path); return error; } __dead static void usage_import(void) { fprintf(stderr, "usage: %s import [-b branch] [-m message] " "[-r repository-path] [-I pattern] path\n", getprogname()); exit(1); } int spawn_editor(const char *editor, const char *file) { pid_t pid; sig_t sighup, sigint, sigquit; int st = -1; sighup = signal(SIGHUP, SIG_IGN); sigint = signal(SIGINT, SIG_IGN); sigquit = signal(SIGQUIT, SIG_IGN); switch (pid = fork()) { case -1: goto doneediting; case 0: execl(editor, editor, file, (char *)NULL); _exit(127); } while (waitpid(pid, &st, 0) == -1) if (errno != EINTR) break; doneediting: (void)signal(SIGHUP, sighup); (void)signal(SIGINT, sigint); (void)signal(SIGQUIT, sigquit); if (!WIFEXITED(st)) { errno = EINTR; return -1; } return WEXITSTATUS(st); } static const struct got_error * edit_logmsg(char **logmsg, const char *editor, const char *logmsg_path, const char *initial_content) { const struct got_error *err = NULL; char buf[1024]; struct stat st, st2; FILE *fp; int content_changed = 0; size_t len; *logmsg = NULL; if (stat(logmsg_path, &st) == -1) return got_error_from_errno2("stat", logmsg_path); if (spawn_editor(editor, logmsg_path) == -1) return got_error_from_errno("failed spawning editor"); if (stat(logmsg_path, &st2) == -1) return got_error_from_errno("stat"); if (st.st_mtime == st2.st_mtime && st.st_size == st2.st_size) return got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, "no changes made to commit message, aborting"); *logmsg = malloc(st2.st_size + 1); if (*logmsg == NULL) return got_error_from_errno("malloc"); (*logmsg)[0] = '\0'; len = 0; fp = fopen(logmsg_path, "r"); if (fp == NULL) { err = got_error_from_errno("fopen"); goto done; } while (fgets(buf, sizeof(buf), fp) != NULL) { if (!content_changed && strcmp(buf, initial_content) != 0) content_changed = 1; if (buf[0] == '#' || (len == 0 && buf[0] == '\n')) continue; /* remove comments and leading empty lines */ len = strlcat(*logmsg, buf, st2.st_size); } fclose(fp); while (len > 0 && (*logmsg)[len - 1] == '\n') { (*logmsg)[len - 1] = '\0'; len--; } if (len == 0 || !content_changed) err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, "commit message cannot be empty, aborting"); done: if (err) { free(*logmsg); *logmsg = NULL; } return err; } static const struct got_error * collect_import_msg(char **logmsg, const char *editor, const char *path_dir, const char *branch_name) { char *initial_content = NULL, *logmsg_path = NULL; const struct got_error *err = NULL; int fd; if (asprintf(&initial_content, "\n# %s to be imported to branch %s\n", path_dir, branch_name) == -1) return got_error_from_errno("asprintf"); err = got_opentemp_named_fd(&logmsg_path, &fd, "/tmp/got-importmsg"); if (err) goto done; dprintf(fd, initial_content); close(fd); err = edit_logmsg(logmsg, editor, logmsg_path, initial_content); done: free(initial_content); free(logmsg_path); return err; } static const struct got_error * import_progress(void *arg, const char *path) { printf("A %s\n", path); return NULL; } static const struct got_error * cmd_import(int argc, char *argv[]) { const struct got_error *error = NULL; char *path_dir = NULL, *repo_path = NULL, *logmsg = NULL; char *editor = NULL; const char *got_author = getenv("GOT_AUTHOR"); const char *branch_name = "master"; char *refname = NULL, *id_str = NULL; struct got_repository *repo = NULL; struct got_reference *branch_ref = NULL, *head_ref = NULL; struct got_object_id *new_commit_id = NULL; int ch; struct got_pathlist_head ignores; struct got_pathlist_entry *pe; TAILQ_INIT(&ignores); while ((ch = getopt(argc, argv, "b:m:r:I:")) != -1) { switch (ch) { case 'b': branch_name = optarg; break; case 'm': logmsg = strdup(optarg); if (logmsg == NULL) { error = got_error_from_errno("strdup"); goto done; } break; case 'r': repo_path = realpath(optarg, NULL); if (repo_path == NULL) { error = got_error_from_errno("realpath"); goto done; } break; case 'I': if (optarg[0] == '\0') break; error = got_pathlist_insert(&pe, &ignores, optarg, NULL); if (error) goto done; break; default: usage_init(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec unveil", NULL) == -1) err(1, "pledge"); #endif if (argc != 1) usage_import(); if (got_author == NULL) { /* TODO: Look current user up in password database */ error = got_error(GOT_ERR_COMMIT_NO_AUTHOR); goto done; } if (repo_path == NULL) { repo_path = getcwd(NULL, 0); if (repo_path == NULL) return got_error_from_errno("getcwd"); } got_path_strip_trailing_slashes(repo_path); error = got_repo_open(&repo, repo_path); if (error) goto done; if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) { error = got_error_from_errno("asprintf"); goto done; } error = got_ref_open(&branch_ref, repo, refname, 0); if (error) { if (error->code != GOT_ERR_NOT_REF) goto done; } else { error = got_error_msg(GOT_ERR_BRANCH_EXISTS, "import target branch already exists"); goto done; } path_dir = realpath(argv[0], NULL); if (path_dir == NULL) { error = got_error_from_errno("realpath"); goto done; } got_path_strip_trailing_slashes(path_dir); /* * unveil(2) traverses exec(2); if an editor is used we have * to apply unveil after the log message has been written. */ if (logmsg == NULL || strlen(logmsg) == 0) { error = get_editor(&editor); if (error) goto done; error = collect_import_msg(&logmsg, editor, path_dir, refname); if (error) goto done; } if (unveil(path_dir, "r") != 0) return got_error_from_errno2("unveil", path_dir); error = apply_unveil(got_repo_get_path(repo), 0, NULL); if (error) goto done; error = got_repo_import(&new_commit_id, path_dir, logmsg, got_author, &ignores, repo, import_progress, NULL); if (error) goto done; error = got_ref_alloc(&branch_ref, refname, new_commit_id); if (error) goto done; error = got_ref_write(branch_ref, repo); if (error) goto done; error = got_object_id_str(&id_str, new_commit_id); if (error) goto done; error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); if (error) { if (error->code != GOT_ERR_NOT_REF) goto done; error = got_ref_alloc_symref(&head_ref, GOT_REF_HEAD, branch_ref); if (error) goto done; error = got_ref_write(head_ref, repo); if (error) goto done; } printf("Created branch %s with commit %s\n", got_ref_get_name(branch_ref), id_str); done: free(repo_path); free(editor); free(refname); free(new_commit_id); free(id_str); if (branch_ref) got_ref_close(branch_ref); if (head_ref) got_ref_close(head_ref); return error; } __dead static void usage_checkout(void) { fprintf(stderr, "usage: %s checkout [-b branch] [-c commit] " "[-p prefix] repository-path [worktree-path]\n", getprogname()); exit(1); } static const struct got_error * checkout_progress(void *arg, unsigned char status, const char *path) { char *worktree_path = arg; /* Base commit bump happens silently. */ if (status == GOT_STATUS_BUMP_BASE) return NULL; while (path[0] == '/') path++; printf("%c %s/%s\n", status, worktree_path, path); return NULL; } static const struct got_error * check_cancelled(void *arg) { if (sigint_received || sigpipe_received) return got_error(GOT_ERR_CANCELLED); return NULL; } static const struct got_error * check_linear_ancestry(struct got_object_id *commit_id, struct got_object_id *base_commit_id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_id *yca_id; err = got_commit_graph_find_youngest_common_ancestor(&yca_id, commit_id, base_commit_id, repo); if (err) return err; if (yca_id == NULL) return got_error(GOT_ERR_ANCESTRY); /* * Require a straight line of history between the target commit * and the work tree's base commit. * * Non-linear situations such as this require a rebase: * * (commit) D F (base_commit) * \ / * C E * \ / * B (yca) * | * A * * 'got update' only handles linear cases: * Update forwards in time: A (base/yca) - B - C - D (commit) * Update backwards in time: D (base) - C - B - A (commit/yca) */ if (got_object_id_cmp(commit_id, yca_id) != 0 && got_object_id_cmp(base_commit_id, yca_id) != 0) return got_error(GOT_ERR_ANCESTRY); free(yca_id); return NULL; } static const struct got_error * check_same_branch(struct got_object_id *commit_id, struct got_reference *head_ref, struct got_object_id *yca_id, struct got_repository *repo) { const struct got_error *err = NULL; struct got_commit_graph *graph = NULL; struct got_object_id *head_commit_id = NULL; int is_same_branch = 0; err = got_ref_resolve(&head_commit_id, repo, head_ref); if (err) goto done; if (got_object_id_cmp(head_commit_id, commit_id) == 0) { is_same_branch = 1; goto done; } if (yca_id && got_object_id_cmp(commit_id, yca_id) == 0) { is_same_branch = 1; goto done; } err = got_commit_graph_open(&graph, head_commit_id, "/", 1, repo); if (err) goto done; err = got_commit_graph_iter_start(graph, head_commit_id, repo); if (err) goto done; for (;;) { struct got_object_id *id; err = got_commit_graph_iter_next(&id, graph); if (err) { if (err->code == GOT_ERR_ITER_COMPLETED) { err = NULL; break; } else if (err->code != GOT_ERR_ITER_NEED_MORE) break; err = got_commit_graph_fetch_commits(graph, 1, repo); if (err) break; } if (id) { if (yca_id && got_object_id_cmp(id, yca_id) == 0) break; if (got_object_id_cmp(id, commit_id) == 0) { is_same_branch = 1; break; } } } done: if (graph) got_commit_graph_close(graph); free(head_commit_id); if (!err && !is_same_branch) err = got_error(GOT_ERR_ANCESTRY); return err; } static const struct got_error * resolve_commit_arg(struct got_object_id **commit_id, const char *commit_id_arg, struct got_repository *repo) { const struct got_error *err; struct got_reference *ref; err = got_ref_open(&ref, repo, commit_id_arg, 0); if (err == NULL) { err = got_ref_resolve(commit_id, repo, ref); got_ref_close(ref); } else { if (err->code != GOT_ERR_NOT_REF) return err; err = got_repo_match_object_id_prefix(commit_id, commit_id_arg, GOT_OBJ_TYPE_COMMIT, repo); } return err; } static const struct got_error * cmd_checkout(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_repository *repo = NULL; struct got_reference *head_ref = NULL; struct got_worktree *worktree = NULL; char *repo_path = NULL; char *worktree_path = NULL; const char *path_prefix = ""; const char *branch_name = GOT_REF_HEAD; char *commit_id_str = NULL; int ch, same_path_prefix; struct got_pathlist_head paths; TAILQ_INIT(&paths); while ((ch = getopt(argc, argv, "b:c:p:")) != -1) { switch (ch) { case 'b': branch_name = optarg; break; case 'c': commit_id_str = strdup(optarg); if (commit_id_str == NULL) return got_error_from_errno("strdup"); break; case 'p': path_prefix = optarg; break; default: usage_checkout(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " "unveil", NULL) == -1) err(1, "pledge"); #endif if (argc == 1) { char *cwd, *base, *dotgit; repo_path = realpath(argv[0], NULL); if (repo_path == NULL) return got_error_from_errno2("realpath", argv[0]); cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } if (path_prefix[0]) { base = basename(path_prefix); if (base == NULL) { error = got_error_from_errno2("basename", path_prefix); goto done; } } else { base = basename(repo_path); if (base == NULL) { error = got_error_from_errno2("basename", repo_path); goto done; } } dotgit = strstr(base, ".git"); if (dotgit) *dotgit = '\0'; if (asprintf(&worktree_path, "%s/%s", cwd, base) == -1) { error = got_error_from_errno("asprintf"); free(cwd); goto done; } free(cwd); } else if (argc == 2) { repo_path = realpath(argv[0], NULL); if (repo_path == NULL) { error = got_error_from_errno2("realpath", argv[0]); goto done; } worktree_path = realpath(argv[1], NULL); if (worktree_path == NULL) { if (errno != ENOENT) { error = got_error_from_errno2("realpath", argv[1]); goto done; } worktree_path = strdup(argv[1]); if (worktree_path == NULL) { error = got_error_from_errno("strdup"); goto done; } } } else usage_checkout(); got_path_strip_trailing_slashes(repo_path); got_path_strip_trailing_slashes(worktree_path); error = got_repo_open(&repo, repo_path); if (error != NULL) goto done; /* Pre-create work tree path for unveil(2) */ error = got_path_mkdir(worktree_path); if (error) { if (!(error->code == GOT_ERR_ERRNO && errno == EISDIR)) goto done; if (!got_path_dir_is_empty(worktree_path)) { error = got_error_path(worktree_path, GOT_ERR_DIR_NOT_EMPTY); goto done; } } error = apply_unveil(got_repo_get_path(repo), 0, worktree_path); if (error) goto done; error = got_ref_open(&head_ref, repo, branch_name, 0); if (error != NULL) goto done; error = got_worktree_init(worktree_path, head_ref, path_prefix, repo); if (error != NULL && !(error->code == GOT_ERR_ERRNO && errno == EEXIST)) goto done; error = got_worktree_open(&worktree, worktree_path); if (error != NULL) goto done; error = got_worktree_match_path_prefix(&same_path_prefix, worktree, path_prefix); if (error != NULL) goto done; if (!same_path_prefix) { error = got_error(GOT_ERR_PATH_PREFIX); goto done; } if (commit_id_str) { struct got_object_id *commit_id; error = resolve_commit_arg(&commit_id, commit_id_str, repo); if (error) goto done; error = check_linear_ancestry(commit_id, got_worktree_get_base_commit_id(worktree), repo); if (error != NULL) { free(commit_id); goto done; } error = check_same_branch(commit_id, head_ref, NULL, repo); if (error) goto done; error = got_worktree_set_base_commit_id(worktree, repo, commit_id); free(commit_id); if (error) goto done; } error = got_pathlist_append(&paths, "", NULL); if (error) goto done; error = got_worktree_checkout_files(worktree, &paths, repo, checkout_progress, worktree_path, check_cancelled, NULL); if (error != NULL) goto done; printf("Now shut up and hack\n"); done: got_pathlist_free(&paths); free(commit_id_str); free(repo_path); free(worktree_path); return error; } __dead static void usage_update(void) { fprintf(stderr, "usage: %s update [-b branch] [-c commit] [path ...]\n", getprogname()); exit(1); } static const struct got_error * update_progress(void *arg, unsigned char status, const char *path) { int *did_something = arg; if (status == GOT_STATUS_EXISTS) return NULL; *did_something = 1; /* Base commit bump happens silently. */ if (status == GOT_STATUS_BUMP_BASE) return NULL; while (path[0] == '/') path++; printf("%c %s\n", status, path); return NULL; } static const struct got_error * switch_head_ref(struct got_reference *head_ref, struct got_object_id *commit_id, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err = NULL; char *base_id_str; int ref_has_moved = 0; /* Trivial case: switching between two different references. */ if (strcmp(got_ref_get_name(head_ref), got_worktree_get_head_ref_name(worktree)) != 0) { printf("Switching work tree from %s to %s\n", got_worktree_get_head_ref_name(worktree), got_ref_get_name(head_ref)); return got_worktree_set_head_ref(worktree, head_ref); } err = check_linear_ancestry(commit_id, got_worktree_get_base_commit_id(worktree), repo); if (err) { if (err->code != GOT_ERR_ANCESTRY) return err; ref_has_moved = 1; } if (!ref_has_moved) return NULL; /* Switching to a rebased branch with the same reference name. */ err = got_object_id_str(&base_id_str, got_worktree_get_base_commit_id(worktree)); if (err) return err; printf("Reference %s now points at a different branch\n", got_worktree_get_head_ref_name(worktree)); printf("Switching work tree from %s to %s\n", base_id_str, got_worktree_get_head_ref_name(worktree)); return NULL; } static const struct got_error * check_rebase_or_histedit_in_progress(struct got_worktree *worktree) { const struct got_error *err; int in_progress; err = got_worktree_rebase_in_progress(&in_progress, worktree); if (err) return err; if (in_progress) return got_error(GOT_ERR_REBASING); err = got_worktree_histedit_in_progress(&in_progress, worktree); if (err) return err; if (in_progress) return got_error(GOT_ERR_HISTEDIT_BUSY); return NULL; } static const struct got_error * get_worktree_paths_from_argv(struct got_pathlist_head *paths, int argc, char *argv[], struct got_worktree *worktree) { const struct got_error *err; char *path; int i; if (argc == 0) { path = strdup(""); if (path == NULL) return got_error_from_errno("strdup"); return got_pathlist_append(paths, path, NULL); } for (i = 0; i < argc; i++) { err = got_worktree_resolve_path(&path, worktree, argv[i]); if (err) break; err = got_pathlist_append(paths, path, NULL); if (err) { free(path); break; } } return err; } static const struct got_error * cmd_update(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; char *worktree_path = NULL; struct got_object_id *commit_id = NULL; char *commit_id_str = NULL; const char *branch_name = NULL; struct got_reference *head_ref = NULL; struct got_pathlist_head paths; struct got_pathlist_entry *pe; int ch, did_something = 0; TAILQ_INIT(&paths); while ((ch = getopt(argc, argv, "b:c:")) != -1) { switch (ch) { case 'b': branch_name = optarg; break; case 'c': commit_id_str = strdup(optarg); if (commit_id_str == NULL) return got_error_from_errno("strdup"); break; default: usage_update(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " "unveil", NULL) == -1) err(1, "pledge"); #endif worktree_path = getcwd(NULL, 0); if (worktree_path == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, worktree_path); if (error) goto done; error = check_rebase_or_histedit_in_progress(worktree); if (error) goto done; error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 0, got_worktree_get_root_path(worktree)); if (error) goto done; error = got_ref_open(&head_ref, repo, branch_name ? branch_name : got_worktree_get_head_ref_name(worktree), 0); if (error != NULL) goto done; if (commit_id_str == NULL) { error = got_ref_resolve(&commit_id, repo, head_ref); if (error != NULL) goto done; error = got_object_id_str(&commit_id_str, commit_id); if (error != NULL) goto done; } else { error = resolve_commit_arg(&commit_id, commit_id_str, repo); free(commit_id_str); commit_id_str = NULL; if (error) goto done; error = got_object_id_str(&commit_id_str, commit_id); if (error) goto done; } if (branch_name) { struct got_object_id *head_commit_id; TAILQ_FOREACH(pe, &paths, entry) { if (strlen(pe->path) == 0) continue; error = got_error_msg(GOT_ERR_BAD_PATH, "switching between branches requires that " "the entire work tree gets updated"); goto done; } error = got_ref_resolve(&head_commit_id, repo, head_ref); if (error) goto done; error = check_linear_ancestry(commit_id, head_commit_id, repo); free(head_commit_id); if (error != NULL) goto done; error = check_same_branch(commit_id, head_ref, NULL, repo); if (error) goto done; error = switch_head_ref(head_ref, commit_id, worktree, repo); if (error) goto done; } else { error = check_linear_ancestry(commit_id, got_worktree_get_base_commit_id(worktree), repo); if (error != NULL) { if (error->code == GOT_ERR_ANCESTRY) error = got_error(GOT_ERR_BRANCH_MOVED); goto done; } error = check_same_branch(commit_id, head_ref, NULL, repo); if (error) goto done; } if (got_object_id_cmp(got_worktree_get_base_commit_id(worktree), commit_id) != 0) { error = got_worktree_set_base_commit_id(worktree, repo, commit_id); if (error) goto done; } error = got_worktree_checkout_files(worktree, &paths, repo, update_progress, &did_something, check_cancelled, NULL); if (error != NULL) goto done; if (did_something) printf("Updated to commit %s\n", commit_id_str); else printf("Already up-to-date\n"); done: free(worktree_path); TAILQ_FOREACH(pe, &paths, entry) free((char *)pe->path); got_pathlist_free(&paths); free(commit_id); free(commit_id_str); return error; } static const struct got_error * print_patch(struct got_commit_object *commit, struct got_object_id *id, int diff_context, struct got_repository *repo) { const struct got_error *err = NULL; struct got_tree_object *tree1 = NULL, *tree2; struct got_object_qid *qid; char *id_str1 = NULL, *id_str2; struct got_diff_blob_output_unidiff_arg arg; err = got_object_open_as_tree(&tree2, repo, got_object_commit_get_tree_id(commit)); if (err) return err; qid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); if (qid != NULL) { struct got_commit_object *pcommit; err = got_object_open_as_commit(&pcommit, repo, qid->id); if (err) return err; err = got_object_open_as_tree(&tree1, repo, got_object_commit_get_tree_id(pcommit)); got_object_commit_close(pcommit); if (err) return err; err = got_object_id_str(&id_str1, qid->id); if (err) return err; } err = got_object_id_str(&id_str2, id); if (err) goto done; printf("diff %s %s\n", id_str1 ? id_str1 : "/dev/null", id_str2); arg.diff_context = diff_context; arg.outfile = stdout; err = got_diff_tree(tree1, tree2, "", "", repo, got_diff_blob_output_unidiff, &arg, 1); done: if (tree1) got_object_tree_close(tree1); got_object_tree_close(tree2); free(id_str1); free(id_str2); return err; } static char * get_datestr(time_t *time, char *datebuf) { char *p, *s = ctime_r(time, datebuf); p = strchr(s, '\n'); if (p) *p = '\0'; return s; } static const struct got_error * print_commit(struct got_commit_object *commit, struct got_object_id *id, struct got_repository *repo, int show_patch, int diff_context, struct got_reflist_head *refs) { const struct got_error *err = NULL; char *id_str, *datestr, *logmsg0, *logmsg, *line; char datebuf[26]; time_t committer_time; const char *author, *committer; char *refs_str = NULL; struct got_reflist_entry *re; SIMPLEQ_FOREACH(re, refs, entry) { char *s; const char *name; if (got_object_id_cmp(re->id, id) != 0) continue; name = got_ref_get_name(re->ref); if (strcmp(name, GOT_REF_HEAD) == 0) continue; if (strncmp(name, "refs/", 5) == 0) name += 5; if (strncmp(name, "got/", 4) == 0) continue; if (strncmp(name, "heads/", 6) == 0) name += 6; if (strncmp(name, "remotes/", 8) == 0) name += 8; s = refs_str; if (asprintf(&refs_str, "%s%s%s", s ? s : "", s ? ", " : "", name) == -1) { err = got_error_from_errno("asprintf"); free(s); break; } free(s); } err = got_object_id_str(&id_str, id); if (err) return err; printf("-----------------------------------------------\n"); printf("commit %s%s%s%s\n", id_str, refs_str ? " (" : "", refs_str ? refs_str : "", refs_str ? ")" : ""); free(id_str); id_str = NULL; free(refs_str); refs_str = NULL; printf("from: %s\n", got_object_commit_get_author(commit)); committer_time = got_object_commit_get_committer_time(commit); datestr = get_datestr(&committer_time, datebuf); printf("date: %s UTC\n", datestr); author = got_object_commit_get_author(commit); committer = got_object_commit_get_committer(commit); if (strcmp(author, committer) != 0) printf("via: %s\n", committer); if (got_object_commit_get_nparents(commit) > 1) { const struct got_object_id_queue *parent_ids; struct got_object_qid *qid; int n = 1; parent_ids = got_object_commit_get_parent_ids(commit); SIMPLEQ_FOREACH(qid, parent_ids, entry) { err = got_object_id_str(&id_str, qid->id); if (err) return err; printf("parent %d: %s\n", n++, id_str); free(id_str); } } logmsg0 = strdup(got_object_commit_get_logmsg(commit)); if (logmsg0 == NULL) return got_error_from_errno("strdup"); logmsg = logmsg0; do { line = strsep(&logmsg, "\n"); if (line) printf(" %s\n", line); } while (line); free(logmsg0); if (show_patch) { err = print_patch(commit, id, diff_context, repo); if (err == 0) printf("\n"); } if (fflush(stdout) != 0 && err == NULL) err = got_error_from_errno("fflush"); return err; } static const struct got_error * print_commits(struct got_object_id *root_id, struct got_repository *repo, char *path, int show_patch, int diff_context, int limit, int first_parent_traversal, struct got_reflist_head *refs) { const struct got_error *err; struct got_commit_graph *graph; err = got_commit_graph_open(&graph, root_id, path, first_parent_traversal, repo); if (err) return err; err = got_commit_graph_iter_start(graph, root_id, repo); if (err) goto done; for (;;) { struct got_commit_object *commit; struct got_object_id *id; if (sigint_received || sigpipe_received) break; err = got_commit_graph_iter_next(&id, graph); if (err) { if (err->code == GOT_ERR_ITER_COMPLETED) { err = NULL; break; } if (err->code != GOT_ERR_ITER_NEED_MORE) break; err = got_commit_graph_fetch_commits(graph, 1, repo); if (err) break; else continue; } if (id == NULL) break; err = got_object_open_as_commit(&commit, repo, id); if (err) break; err = print_commit(commit, id, repo, show_patch, diff_context, refs); got_object_commit_close(commit); if (err || (limit && --limit == 0)) break; } done: got_commit_graph_close(graph); return err; } __dead static void usage_log(void) { fprintf(stderr, "usage: %s log [-c commit] [-C number] [-f] [ -l N ] [-p] " "[-r repository-path] [path]\n", getprogname()); exit(1); } static const struct got_error * cmd_log(int argc, char *argv[]) { const struct got_error *error; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; struct got_commit_object *commit = NULL; struct got_object_id *id = NULL; char *repo_path = NULL, *path = NULL, *cwd = NULL, *in_repo_path = NULL; char *start_commit = NULL; int diff_context = 3, ch; int show_patch = 0, limit = 0, first_parent_traversal = 0; const char *errstr; struct got_reflist_head refs; SIMPLEQ_INIT(&refs); #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); #endif while ((ch = getopt(argc, argv, "b:pc:C:l:fr:")) != -1) { switch (ch) { case 'p': show_patch = 1; break; case 'c': start_commit = optarg; break; case 'C': diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT, &errstr); if (errstr != NULL) err(1, "-C option %s", errstr); break; case 'l': limit = strtonum(optarg, 1, INT_MAX, &errstr); if (errstr != NULL) err(1, "-l option %s", errstr); break; case 'f': first_parent_traversal = 1; break; case 'r': repo_path = realpath(optarg, NULL); if (repo_path == NULL) err(1, "-r option"); got_path_strip_trailing_slashes(repo_path); break; default: usage_log(); /* NOTREACHED */ } } argc -= optind; argv += optind; cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error && error->code != GOT_ERR_NOT_WORKTREE) goto done; error = NULL; if (argc == 0) { path = strdup(""); if (path == NULL) { error = got_error_from_errno("strdup"); goto done; } } else if (argc == 1) { if (worktree) { error = got_worktree_resolve_path(&path, worktree, argv[0]); if (error) goto done; } else { path = strdup(argv[0]); if (path == NULL) { error = got_error_from_errno("strdup"); goto done; } } } else usage_log(); if (repo_path == NULL) { repo_path = worktree ? strdup(got_worktree_get_repo_path(worktree)) : strdup(cwd); } if (repo_path == NULL) { error = got_error_from_errno("strdup"); goto done; } error = got_repo_open(&repo, repo_path); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 1, worktree ? got_worktree_get_root_path(worktree) : NULL); if (error) goto done; if (start_commit == NULL) { struct got_reference *head_ref; error = got_ref_open(&head_ref, repo, worktree ? got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD, 0); if (error != NULL) return error; error = got_ref_resolve(&id, repo, head_ref); got_ref_close(head_ref); if (error != NULL) return error; error = got_object_open_as_commit(&commit, repo, id); } else { struct got_reference *ref; error = got_ref_open(&ref, repo, start_commit, 0); if (error == NULL) { int obj_type; error = got_ref_resolve(&id, repo, ref); got_ref_close(ref); if (error != NULL) goto done; error = got_object_get_type(&obj_type, repo, id); if (error != NULL) goto done; if (obj_type == GOT_OBJ_TYPE_TAG) { struct got_tag_object *tag; error = got_object_open_as_tag(&tag, repo, id); if (error != NULL) goto done; if (got_object_tag_get_object_type(tag) != GOT_OBJ_TYPE_COMMIT) { got_object_tag_close(tag); error = got_error(GOT_ERR_OBJ_TYPE); goto done; } free(id); id = got_object_id_dup( got_object_tag_get_object_id(tag)); if (id == NULL) error = got_error_from_errno( "got_object_id_dup"); got_object_tag_close(tag); if (error) goto done; } else if (obj_type != GOT_OBJ_TYPE_COMMIT) { error = got_error(GOT_ERR_OBJ_TYPE); goto done; } error = got_object_open_as_commit(&commit, repo, id); if (error != NULL) goto done; } if (commit == NULL) { error = got_repo_match_object_id_prefix(&id, start_commit, GOT_OBJ_TYPE_COMMIT, repo); if (error != NULL) return error; } } if (error != NULL) goto done; error = got_repo_map_path(&in_repo_path, repo, path, 1); if (error != NULL) goto done; if (in_repo_path) { free(path); path = in_repo_path; } error = got_ref_list(&refs, repo); if (error) goto done; error = print_commits(id, repo, path, show_patch, diff_context, limit, first_parent_traversal, &refs); done: free(path); free(repo_path); free(cwd); free(id); if (worktree) got_worktree_close(worktree); if (repo) { const struct got_error *repo_error; repo_error = got_repo_close(repo); if (error == NULL) error = repo_error; } got_ref_list_free(&refs); return error; } __dead static void usage_diff(void) { fprintf(stderr, "usage: %s diff [-C number] [-r repository-path] " "[object1 object2 | path]\n", getprogname()); exit(1); } struct print_diff_arg { struct got_repository *repo; struct got_worktree *worktree; int diff_context; const char *id_str; int header_shown; }; static const struct got_error * print_diff(void *arg, unsigned char status, const char *path, struct got_object_id *blob_id, struct got_object_id *commit_id) { struct print_diff_arg *a = arg; const struct got_error *err = NULL; struct got_blob_object *blob1 = NULL; FILE *f2 = NULL; char *abspath = NULL; struct stat sb; if (status != GOT_STATUS_MODIFY && status != GOT_STATUS_ADD && status != GOT_STATUS_DELETE && status != GOT_STATUS_CONFLICT) return NULL; if (!a->header_shown) { printf("diff %s %s\n", a->id_str, got_worktree_get_root_path(a->worktree)); a->header_shown = 1; } if (status != GOT_STATUS_ADD) { err = got_object_open_as_blob(&blob1, a->repo, blob_id, 8192); if (err) goto done; } if (status != GOT_STATUS_DELETE) { if (asprintf(&abspath, "%s/%s", got_worktree_get_root_path(a->worktree), path) == -1) { err = got_error_from_errno("asprintf"); goto done; } f2 = fopen(abspath, "r"); if (f2 == NULL) { err = got_error_from_errno2("fopen", abspath); goto done; } if (lstat(abspath, &sb) == -1) { err = got_error_from_errno2("lstat", abspath); goto done; } } else sb.st_size = 0; err = got_diff_blob_file(blob1, f2, sb.st_size, path, a->diff_context, stdout); done: if (blob1) got_object_blob_close(blob1); if (f2 && fclose(f2) != 0 && err == NULL) err = got_error_from_errno("fclose"); free(abspath); return err; } static const struct got_error * cmd_diff(int argc, char *argv[]) { const struct got_error *error; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; char *cwd = NULL, *repo_path = NULL; struct got_object_id *id1 = NULL, *id2 = NULL; const char *id_str1 = NULL, *id_str2 = NULL; char *label1 = NULL, *label2 = NULL; int type1, type2; int diff_context = 3, ch; const char *errstr; char *path = NULL; #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); #endif while ((ch = getopt(argc, argv, "C:r:")) != -1) { switch (ch) { case 'C': diff_context = strtonum(optarg, 1, INT_MAX, &errstr); if (errstr != NULL) err(1, "-C option %s", errstr); break; case 'r': repo_path = realpath(optarg, NULL); if (repo_path == NULL) err(1, "-r option"); got_path_strip_trailing_slashes(repo_path); break; default: usage_diff(); /* NOTREACHED */ } } argc -= optind; argv += optind; cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error && error->code != GOT_ERR_NOT_WORKTREE) goto done; if (argc <= 1) { if (worktree == NULL) { error = got_error(GOT_ERR_NOT_WORKTREE); goto done; } if (repo_path) errx(1, "-r option can't be used when diffing a work tree"); repo_path = strdup(got_worktree_get_repo_path(worktree)); if (repo_path == NULL) { error = got_error_from_errno("strdup"); goto done; } if (argc == 1) { error = got_worktree_resolve_path(&path, worktree, argv[0]); if (error) goto done; } else { path = strdup(""); if (path == NULL) { error = got_error_from_errno("strdup"); goto done; } } } else if (argc == 2) { id_str1 = argv[0]; id_str2 = argv[1]; if (worktree && repo_path == NULL) { repo_path = strdup(got_worktree_get_repo_path(worktree)); if (repo_path == NULL) { error = got_error_from_errno("strdup"); goto done; } } } else usage_diff(); if (repo_path == NULL) { repo_path = getcwd(NULL, 0); if (repo_path == NULL) return got_error_from_errno("getcwd"); } error = got_repo_open(&repo, repo_path); free(repo_path); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 1, worktree ? got_worktree_get_root_path(worktree) : NULL); if (error) goto done; if (argc <= 1) { struct print_diff_arg arg; struct got_pathlist_head paths; char *id_str; TAILQ_INIT(&paths); error = got_object_id_str(&id_str, got_worktree_get_base_commit_id(worktree)); if (error) goto done; arg.repo = repo; arg.worktree = worktree; arg.diff_context = diff_context; arg.id_str = id_str; arg.header_shown = 0; error = got_pathlist_append(&paths, path, NULL); if (error) goto done; error = got_worktree_status(worktree, &paths, repo, print_diff, &arg, check_cancelled, NULL); free(id_str); got_pathlist_free(&paths); goto done; } error = got_repo_match_object_id_prefix(&id1, id_str1, GOT_OBJ_TYPE_ANY, repo); if (error) { struct got_reference *ref; if (error->code != GOT_ERR_BAD_OBJ_ID_STR) goto done; error = got_ref_open(&ref, repo, id_str1, 0); if (error != NULL) goto done; label1 = strdup(got_ref_get_name(ref)); if (label1 == NULL) { error = got_error_from_errno("strdup"); goto done; } error = got_ref_resolve(&id1, repo, ref); got_ref_close(ref); if (error != NULL) goto done; } else { error = got_object_id_str(&label1, id1); if (label1 == NULL) { error = got_error_from_errno("strdup"); goto done; } } error = got_repo_match_object_id_prefix(&id2, id_str2, GOT_OBJ_TYPE_ANY, repo); if (error) { struct got_reference *ref; if (error->code != GOT_ERR_BAD_OBJ_ID_STR) goto done; error = got_ref_open(&ref, repo, id_str2, 0); if (error != NULL) goto done; label2 = strdup(got_ref_get_name(ref)); if (label2 == NULL) { error = got_error_from_errno("strdup"); goto done; } error = got_ref_resolve(&id2, repo, ref); got_ref_close(ref); if (error != NULL) goto done; } else { error = got_object_id_str(&label2, id2); if (label2 == NULL) { error = got_error_from_errno("strdup"); goto done; } } error = got_object_get_type(&type1, repo, id1); if (error) goto done; error = got_object_get_type(&type2, repo, id2); if (error) goto done; if (type1 != type2) { error = got_error(GOT_ERR_OBJ_TYPE); goto done; } switch (type1) { case GOT_OBJ_TYPE_BLOB: error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, diff_context, repo, stdout); break; case GOT_OBJ_TYPE_TREE: error = got_diff_objects_as_trees(id1, id2, "", "", diff_context, repo, stdout); break; case GOT_OBJ_TYPE_COMMIT: printf("diff %s %s\n", label1, label2); error = got_diff_objects_as_commits(id1, id2, diff_context, repo, stdout); break; default: error = got_error(GOT_ERR_OBJ_TYPE); } done: free(label1); free(label2); free(id1); free(id2); free(path); if (worktree) got_worktree_close(worktree); if (repo) { const struct got_error *repo_error; repo_error = got_repo_close(repo); if (error == NULL) error = repo_error; } return error; } __dead static void usage_blame(void) { fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n", getprogname()); exit(1); } static const struct got_error * cmd_blame(int argc, char *argv[]) { const struct got_error *error; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL; struct got_object_id *commit_id = NULL; char *commit_id_str = NULL; int ch; #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); #endif while ((ch = getopt(argc, argv, "c:r:")) != -1) { switch (ch) { case 'c': commit_id_str = optarg; break; case 'r': repo_path = realpath(optarg, NULL); if (repo_path == NULL) err(1, "-r option"); got_path_strip_trailing_slashes(repo_path); break; default: usage_blame(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc == 1) path = argv[0]; else usage_blame(); cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } if (repo_path == NULL) { error = got_worktree_open(&worktree, cwd); if (error && error->code != GOT_ERR_NOT_WORKTREE) goto done; else error = NULL; if (worktree) { repo_path = strdup(got_worktree_get_repo_path(worktree)); if (repo_path == NULL) error = got_error_from_errno("strdup"); if (error) goto done; } else { repo_path = strdup(cwd); if (repo_path == NULL) { error = got_error_from_errno("strdup"); goto done; } } } error = got_repo_open(&repo, repo_path); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 1, NULL); if (error) goto done; if (worktree) { const char *prefix = got_worktree_get_path_prefix(worktree); char *p, *worktree_subdir = cwd + strlen(got_worktree_get_root_path(worktree)); if (asprintf(&p, "%s%s%s%s%s", prefix, (strcmp(prefix, "/") != 0) ? "/" : "", worktree_subdir, worktree_subdir[0] ? "/" : "", path) == -1) { error = got_error_from_errno("asprintf"); goto done; } error = got_repo_map_path(&in_repo_path, repo, p, 0); free(p); } else { error = got_repo_map_path(&in_repo_path, repo, path, 1); } if (error) goto done; if (commit_id_str == NULL) { struct got_reference *head_ref; error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); if (error != NULL) goto done; error = got_ref_resolve(&commit_id, repo, head_ref); got_ref_close(head_ref); if (error != NULL) goto done; } else { error = resolve_commit_arg(&commit_id, commit_id_str, repo); if (error) goto done; } error = got_blame(in_repo_path, commit_id, repo, stdout); done: free(in_repo_path); free(repo_path); free(cwd); free(commit_id); if (worktree) got_worktree_close(worktree); if (repo) { const struct got_error *repo_error; repo_error = got_repo_close(repo); if (error == NULL) error = repo_error; } return error; } __dead static void usage_tree(void) { fprintf(stderr, "usage: %s tree [-c commit] [-r repository-path] [-iR] path\n", getprogname()); exit(1); } static void print_entry(struct got_tree_entry *te, const char *id, const char *path, const char *root_path) { int is_root_path = (strcmp(path, root_path) == 0); path += strlen(root_path); while (path[0] == '/') path++; printf("%s%s%s%s%s\n", id ? id : "", path, is_root_path ? "" : "/", te->name, S_ISDIR(te->mode) ? "/" : ((te->mode & S_IXUSR) ? "*" : "")); } static const struct got_error * print_tree(const char *path, struct got_object_id *commit_id, int show_ids, int recurse, const char *root_path, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_id *tree_id = NULL; struct got_tree_object *tree = NULL; const struct got_tree_entries *entries; struct got_tree_entry *te; err = got_object_id_by_path(&tree_id, repo, commit_id, path); if (err) goto done; err = got_object_open_as_tree(&tree, repo, tree_id); if (err) goto done; entries = got_object_tree_get_entries(tree); te = SIMPLEQ_FIRST(&entries->head); while (te) { char *id = NULL; if (sigint_received || sigpipe_received) break; if (show_ids) { char *id_str; err = got_object_id_str(&id_str, te->id); if (err) goto done; if (asprintf(&id, "%s ", id_str) == -1) { err = got_error_from_errno("asprintf"); free(id_str); goto done; } free(id_str); } print_entry(te, id, path, root_path); free(id); if (recurse && S_ISDIR(te->mode)) { char *child_path; if (asprintf(&child_path, "%s%s%s", path, path[0] == '/' && path[1] == '\0' ? "" : "/", te->name) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = print_tree(child_path, commit_id, show_ids, 1, root_path, repo); free(child_path); if (err) goto done; } te = SIMPLEQ_NEXT(te, entry); } done: if (tree) got_object_tree_close(tree); free(tree_id); return err; } static const struct got_error * cmd_tree(int argc, char *argv[]) { const struct got_error *error; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; const char *path; char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL; struct got_object_id *commit_id = NULL; char *commit_id_str = NULL; int show_ids = 0, recurse = 0; int ch; #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); #endif while ((ch = getopt(argc, argv, "c:r:iR")) != -1) { switch (ch) { case 'c': commit_id_str = optarg; break; case 'r': repo_path = realpath(optarg, NULL); if (repo_path == NULL) err(1, "-r option"); got_path_strip_trailing_slashes(repo_path); break; case 'i': show_ids = 1; break; case 'R': recurse = 1; break; default: usage_tree(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc == 1) path = argv[0]; else if (argc > 1) usage_tree(); else path = NULL; cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } if (repo_path == NULL) { error = got_worktree_open(&worktree, cwd); if (error && error->code != GOT_ERR_NOT_WORKTREE) goto done; else error = NULL; if (worktree) { repo_path = strdup(got_worktree_get_repo_path(worktree)); if (repo_path == NULL) error = got_error_from_errno("strdup"); if (error) goto done; } else { repo_path = strdup(cwd); if (repo_path == NULL) { error = got_error_from_errno("strdup"); goto done; } } } error = got_repo_open(&repo, repo_path); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 1, NULL); if (error) goto done; if (path == NULL) { if (worktree) { char *p, *worktree_subdir = cwd + strlen(got_worktree_get_root_path(worktree)); if (asprintf(&p, "%s/%s", got_worktree_get_path_prefix(worktree), worktree_subdir) == -1) { error = got_error_from_errno("asprintf"); goto done; } error = got_repo_map_path(&in_repo_path, repo, p, 1); free(p); if (error) goto done; } else path = "/"; } if (in_repo_path == NULL) { error = got_repo_map_path(&in_repo_path, repo, path, 1); if (error != NULL) goto done; } if (commit_id_str == NULL) { struct got_reference *head_ref; error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); if (error != NULL) goto done; error = got_ref_resolve(&commit_id, repo, head_ref); got_ref_close(head_ref); if (error != NULL) goto done; } else { error = resolve_commit_arg(&commit_id, commit_id_str, repo); if (error) goto done; } error = print_tree(in_repo_path, commit_id, show_ids, recurse, in_repo_path, repo); done: free(in_repo_path); free(repo_path); free(cwd); free(commit_id); if (worktree) got_worktree_close(worktree); if (repo) { const struct got_error *repo_error; repo_error = got_repo_close(repo); if (error == NULL) error = repo_error; } return error; } __dead static void usage_status(void) { fprintf(stderr, "usage: %s status [path ...]\n", getprogname()); exit(1); } static const struct got_error * print_status(void *arg, unsigned char status, const char *path, struct got_object_id *blob_id, struct got_object_id *commit_id) { printf("%c %s\n", status, path); return NULL; } static const struct got_error * cmd_status(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; char *cwd = NULL; struct got_pathlist_head paths; struct got_pathlist_entry *pe; int ch; TAILQ_INIT(&paths); while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: usage_status(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); #endif cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error != NULL) goto done; error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 1, got_worktree_get_root_path(worktree)); if (error) goto done; error = got_worktree_status(worktree, &paths, repo, print_status, NULL, check_cancelled, NULL); done: TAILQ_FOREACH(pe, &paths, entry) free((char *)pe->path); got_pathlist_free(&paths); free(cwd); return error; } __dead static void usage_ref(void) { fprintf(stderr, "usage: %s ref [-r repository] -l | -d name | name target\n", getprogname()); exit(1); } static const struct got_error * list_refs(struct got_repository *repo) { static const struct got_error *err = NULL; struct got_reflist_head refs; struct got_reflist_entry *re; SIMPLEQ_INIT(&refs); err = got_ref_list(&refs, repo); if (err) return err; SIMPLEQ_FOREACH(re, &refs, entry) { char *refstr; refstr = got_ref_to_str(re->ref); if (refstr == NULL) return got_error_from_errno("got_ref_to_str"); printf("%s: %s\n", got_ref_get_name(re->ref), refstr); free(refstr); } got_ref_list_free(&refs); return NULL; } static const struct got_error * delete_ref(struct got_repository *repo, const char *refname) { const struct got_error *err = NULL; struct got_reference *ref; err = got_ref_open(&ref, repo, refname, 0); if (err) return err; err = got_ref_delete(ref, repo); got_ref_close(ref); return err; } static const struct got_error * add_ref(struct got_repository *repo, const char *refname, const char *target) { const struct got_error *err = NULL; struct got_object_id *id; struct got_reference *ref = NULL; /* * Don't let the user create a reference named '-'. * While technically a valid reference name, this case is usually * an unintended typo. */ if (refname[0] == '-' && refname[1] == '\0') return got_error(GOT_ERR_BAD_REF_NAME); err = got_repo_match_object_id_prefix(&id, target, GOT_OBJ_TYPE_ANY, repo); if (err) { struct got_reference *target_ref; if (err->code != GOT_ERR_BAD_OBJ_ID_STR) return err; err = got_ref_open(&target_ref, repo, target, 0); if (err) return err; err = got_ref_resolve(&id, repo, target_ref); got_ref_close(target_ref); if (err) return err; } err = got_ref_alloc(&ref, refname, id); if (err) goto done; err = got_ref_write(ref, repo); done: if (ref) got_ref_close(ref); free(id); return err; } static const struct got_error * cmd_ref(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; char *cwd = NULL, *repo_path = NULL; int ch, do_list = 0; const char *delref = NULL; /* TODO: Add -s option for adding symbolic references. */ while ((ch = getopt(argc, argv, "d:r:l")) != -1) { switch (ch) { case 'd': delref = optarg; break; case 'r': repo_path = realpath(optarg, NULL); if (repo_path == NULL) err(1, "-r option"); got_path_strip_trailing_slashes(repo_path); break; case 'l': do_list = 1; break; default: usage_ref(); /* NOTREACHED */ } } if (do_list && delref) errx(1, "-l and -d options are mutually exclusive\n"); argc -= optind; argv += optind; if (do_list || delref) { if (argc > 0) usage_ref(); } else if (argc != 2) usage_ref(); #ifndef PROFILE if (do_list) { if (pledge("stdio rpath wpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); } else { if (pledge("stdio rpath wpath cpath fattr flock proc exec " "sendfd unveil", NULL) == -1) err(1, "pledge"); } #endif cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } if (repo_path == NULL) { error = got_worktree_open(&worktree, cwd); if (error && error->code != GOT_ERR_NOT_WORKTREE) goto done; else error = NULL; if (worktree) { repo_path = strdup(got_worktree_get_repo_path(worktree)); if (repo_path == NULL) error = got_error_from_errno("strdup"); if (error) goto done; } else { repo_path = strdup(cwd); if (repo_path == NULL) { error = got_error_from_errno("strdup"); goto done; } } } error = got_repo_open(&repo, repo_path); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), do_list, worktree ? got_worktree_get_root_path(worktree) : NULL); if (error) goto done; if (do_list) error = list_refs(repo); else if (delref) error = delete_ref(repo, delref); else error = add_ref(repo, argv[0], argv[1]); done: if (repo) got_repo_close(repo); if (worktree) got_worktree_close(worktree); free(cwd); free(repo_path); return error; } __dead static void usage_branch(void) { fprintf(stderr, "usage: %s branch [-r repository] -l | -d name | " "name [base-branch]\n", getprogname()); exit(1); } static const struct got_error * list_branches(struct got_repository *repo, struct got_worktree *worktree) { static const struct got_error *err = NULL; struct got_reflist_head refs; struct got_reflist_entry *re; SIMPLEQ_INIT(&refs); err = got_ref_list(&refs, repo); if (err) return err; SIMPLEQ_FOREACH(re, &refs, entry) { const char *refname, *marker = " "; char *refstr; refname = got_ref_get_name(re->ref); if (strncmp(refname, "refs/heads/", 11) != 0) continue; if (worktree && strcmp(refname, got_worktree_get_head_ref_name(worktree)) == 0) { struct got_object_id *id = NULL; err = got_ref_resolve(&id, repo, re->ref); if (err) return err; if (got_object_id_cmp(id, got_worktree_get_base_commit_id(worktree)) == 0) marker = "* "; else marker = "~ "; free(id); } refname += 11; refstr = got_ref_to_str(re->ref); if (refstr == NULL) return got_error_from_errno("got_ref_to_str"); printf("%s%s: %s\n", marker, refname, refstr); free(refstr); } got_ref_list_free(&refs); return NULL; } static const struct got_error * delete_branch(struct got_repository *repo, const char *branch_name) { const struct got_error *err = NULL; struct got_reference *ref; char *refname; if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) return got_error_from_errno("asprintf"); err = got_ref_open(&ref, repo, refname, 0); if (err) goto done; err = got_ref_delete(ref, repo); got_ref_close(ref); done: free(refname); return err; } static const struct got_error * add_branch(struct got_repository *repo, const char *branch_name, const char *base_branch) { const struct got_error *err = NULL; struct got_object_id *id = NULL; struct got_reference *ref = NULL; char *base_refname = NULL, *refname = NULL; struct got_reference *base_ref; /* * Don't let the user create a branch named '-'. * While technically a valid reference name, this case is usually * an unintended typo. */ if (branch_name[0] == '-' && branch_name[1] == '\0') return got_error(GOT_ERR_BAD_REF_NAME); if (strcmp(GOT_REF_HEAD, base_branch) == 0) { base_refname = strdup(GOT_REF_HEAD); if (base_refname == NULL) return got_error_from_errno("strdup"); } else if (asprintf(&base_refname, "refs/heads/%s", base_branch) == -1) return got_error_from_errno("asprintf"); err = got_ref_open(&base_ref, repo, base_refname, 0); if (err) goto done; err = got_ref_resolve(&id, repo, base_ref); got_ref_close(base_ref); if (err) goto done; if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_ref_open(&ref, repo, refname, 0); if (err == NULL) { err = got_error(GOT_ERR_BRANCH_EXISTS); goto done; } else if (err->code != GOT_ERR_NOT_REF) goto done; err = got_ref_alloc(&ref, refname, id); if (err) goto done; err = got_ref_write(ref, repo); done: if (ref) got_ref_close(ref); free(id); free(base_refname); free(refname); return err; } static const struct got_error * cmd_branch(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; char *cwd = NULL, *repo_path = NULL; int ch, do_list = 0; const char *delref = NULL; while ((ch = getopt(argc, argv, "d:r:l")) != -1) { switch (ch) { case 'd': delref = optarg; break; case 'r': repo_path = realpath(optarg, NULL); if (repo_path == NULL) err(1, "-r option"); got_path_strip_trailing_slashes(repo_path); break; case 'l': do_list = 1; break; default: usage_branch(); /* NOTREACHED */ } } if (do_list && delref) errx(1, "-l and -d options are mutually exclusive\n"); argc -= optind; argv += optind; if (do_list || delref) { if (argc > 0) usage_branch(); } else if (argc < 1 || argc > 2) usage_branch(); #ifndef PROFILE if (do_list) { if (pledge("stdio rpath wpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); } else { if (pledge("stdio rpath wpath cpath fattr flock proc exec " "sendfd unveil", NULL) == -1) err(1, "pledge"); } #endif cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } if (repo_path == NULL) { error = got_worktree_open(&worktree, cwd); if (error && error->code != GOT_ERR_NOT_WORKTREE) goto done; else error = NULL; if (worktree) { repo_path = strdup(got_worktree_get_repo_path(worktree)); if (repo_path == NULL) error = got_error_from_errno("strdup"); if (error) goto done; } else { repo_path = strdup(cwd); if (repo_path == NULL) { error = got_error_from_errno("strdup"); goto done; } } } error = got_repo_open(&repo, repo_path); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), do_list, worktree ? got_worktree_get_root_path(worktree) : NULL); if (error) goto done; if (do_list) error = list_branches(repo, worktree); else if (delref) error = delete_branch(repo, delref); else { const char *base_branch; if (argc == 1) { base_branch = worktree ? got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD; } else base_branch = argv[1]; error = add_branch(repo, argv[0], base_branch); } done: if (repo) got_repo_close(repo); if (worktree) got_worktree_close(worktree); free(cwd); free(repo_path); return error; } __dead static void usage_add(void) { fprintf(stderr, "usage: %s add file-path ...\n", getprogname()); exit(1); } static const struct got_error * cmd_add(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_repository *repo = NULL; struct got_worktree *worktree = NULL; char *cwd = NULL; struct got_pathlist_head paths; struct got_pathlist_entry *pe; int ch, x; TAILQ_INIT(&paths); while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: usage_add(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); #endif if (argc < 1) usage_add(); cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 1, got_worktree_get_root_path(worktree)); if (error) goto done; for (x = 0; x < argc; x++) { char *path = realpath(argv[x], NULL); if (path == NULL) { error = got_error_from_errno2("realpath", argv[x]); goto done; } got_path_strip_trailing_slashes(path); error = got_pathlist_insert(&pe, &paths, path, NULL); if (error) { free(path); goto done; } } error = got_worktree_schedule_add(worktree, &paths, print_status, NULL, repo); done: if (repo) got_repo_close(repo); if (worktree) got_worktree_close(worktree); TAILQ_FOREACH(pe, &paths, entry) free((char *)pe->path); got_pathlist_free(&paths); free(cwd); return error; } __dead static void usage_remove(void) { fprintf(stderr, "usage: %s remove [-f] file-path ...\n", getprogname()); exit(1); } static const struct got_error * cmd_remove(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_worktree *worktree = NULL; struct got_repository *repo = NULL; char *cwd = NULL; struct got_pathlist_head paths; struct got_pathlist_entry *pe; int ch, i, delete_local_mods = 0; TAILQ_INIT(&paths); while ((ch = getopt(argc, argv, "f")) != -1) { switch (ch) { case 'f': delete_local_mods = 1; break; default: usage_add(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", NULL) == -1) err(1, "pledge"); #endif if (argc < 1) usage_remove(); cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error) goto done; error = apply_unveil(got_repo_get_path(repo), 1, got_worktree_get_root_path(worktree)); if (error) goto done; for (i = 0; i < argc; i++) { char *path = realpath(argv[i], NULL); if (path == NULL) { error = got_error_from_errno2("realpath", argv[i]); goto done; } got_path_strip_trailing_slashes(path); error = got_pathlist_insert(&pe, &paths, path, NULL); if (error) { free(path); goto done; } } error = got_worktree_schedule_delete(worktree, &paths, delete_local_mods, print_status, NULL, repo); if (error) goto done; done: if (repo) got_repo_close(repo); if (worktree) got_worktree_close(worktree); TAILQ_FOREACH(pe, &paths, entry) free((char *)pe->path); got_pathlist_free(&paths); free(cwd); return error; } __dead static void usage_revert(void) { fprintf(stderr, "usage: %s revert file-path ...\n", getprogname()); exit(1); } static const struct got_error * revert_progress(void *arg, unsigned char status, const char *path) { while (path[0] == '/') path++; printf("%c %s\n", status, path); return NULL; } static const struct got_error * cmd_revert(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_worktree *worktree = NULL; struct got_repository *repo = NULL; char *cwd = NULL, *path = NULL; struct got_pathlist_head paths; struct got_pathlist_entry *pe; int ch, i; TAILQ_INIT(&paths); while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: usage_revert(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " "unveil", NULL) == -1) err(1, "pledge"); #endif if (argc < 1) usage_revert(); for (i = 0; i < argc; i++) { char *path = realpath(argv[i], NULL); if (path == NULL) { error = got_error_from_errno2("realpath", argv[i]); goto done; } got_path_strip_trailing_slashes(path); error = got_pathlist_insert(&pe, &paths, path, NULL); if (error) { free(path); goto done; } } cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 1, got_worktree_get_root_path(worktree)); if (error) goto done; error = got_worktree_revert(worktree, &paths, revert_progress, NULL, repo); if (error) goto done; done: if (repo) got_repo_close(repo); if (worktree) got_worktree_close(worktree); free(path); free(cwd); return error; } __dead static void usage_commit(void) { fprintf(stderr, "usage: %s commit [-m msg] [path ...]\n", getprogname()); exit(1); } struct collect_commit_logmsg_arg { const char *cmdline_log; const char *editor; const char *worktree_path; const char *branch_name; const char *repo_path; char *logmsg_path; }; static const struct got_error * collect_commit_logmsg(struct got_pathlist_head *commitable_paths, char **logmsg, void *arg) { char *initial_content = NULL; struct got_pathlist_entry *pe; const struct got_error *err = NULL; char *template = NULL; struct collect_commit_logmsg_arg *a = arg; int fd; size_t len; /* if a message was specified on the command line, just use it */ if (a->cmdline_log != NULL && strlen(a->cmdline_log) != 0) { len = strlen(a->cmdline_log) + 1; *logmsg = malloc(len + 1); if (*logmsg == NULL) return got_error_from_errno("malloc"); strlcpy(*logmsg, a->cmdline_log, len); return NULL; } if (asprintf(&template, "%s/logmsg", a->worktree_path) == -1) return got_error_from_errno("asprintf"); if (asprintf(&initial_content, "\n# changes to be committed on branch %s:\n", a->branch_name) == -1) return got_error_from_errno("asprintf"); err = got_opentemp_named_fd(&a->logmsg_path, &fd, template); if (err) goto done; dprintf(fd, initial_content); TAILQ_FOREACH(pe, commitable_paths, entry) { struct got_commitable *ct = pe->data; dprintf(fd, "# %c %s\n", got_commitable_get_status(ct), got_commitable_get_path(ct)); } close(fd); err = edit_logmsg(logmsg, a->editor, a->logmsg_path, initial_content); done: unlink(a->logmsg_path); free(a->logmsg_path); free(initial_content); free(template); /* Editor is done; we can now apply unveil(2) */ if (err == NULL) { err = apply_unveil(a->repo_path, 0, a->worktree_path); if (err) { free(*logmsg); *logmsg = NULL; } } return err; } static const struct got_error * cmd_commit(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_worktree *worktree = NULL; struct got_repository *repo = NULL; char *cwd = NULL, *id_str = NULL; struct got_object_id *id = NULL; const char *logmsg = NULL; const char *got_author = getenv("GOT_AUTHOR"); struct collect_commit_logmsg_arg cl_arg; char *editor = NULL; int ch, rebase_in_progress, histedit_in_progress; struct got_pathlist_head paths; TAILQ_INIT(&paths); while ((ch = getopt(argc, argv, "m:")) != -1) { switch (ch) { case 'm': logmsg = optarg; break; default: usage_commit(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " "unveil", NULL) == -1) err(1, "pledge"); #endif if (got_author == NULL) { /* TODO: Look current user up in password database */ error = got_error(GOT_ERR_COMMIT_NO_AUTHOR); goto done; } cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error) goto done; error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); if (error) goto done; if (rebase_in_progress) { error = got_error(GOT_ERR_REBASING); goto done; } error = got_worktree_histedit_in_progress(&histedit_in_progress, worktree); if (error) goto done; error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; /* * unveil(2) traverses exec(2); if an editor is used we have * to apply unveil after the log message has been written. */ if (logmsg == NULL || strlen(logmsg) == 0) error = get_editor(&editor); else error = apply_unveil(got_repo_get_path(repo), 0, got_worktree_get_root_path(worktree)); if (error) goto done; cl_arg.editor = editor; cl_arg.cmdline_log = logmsg; cl_arg.worktree_path = got_worktree_get_root_path(worktree); cl_arg.branch_name = got_worktree_get_head_ref_name(worktree); if (!histedit_in_progress) { if (strncmp(cl_arg.branch_name, "refs/heads/", 11) != 0) { error = got_error(GOT_ERR_COMMIT_BRANCH); goto done; } cl_arg.branch_name += 11; } cl_arg.repo_path = got_repo_get_path(repo); cl_arg.logmsg_path = NULL; error = got_worktree_commit(&id, worktree, &paths, got_author, NULL, collect_commit_logmsg, &cl_arg, print_status, NULL, repo); if (error) { if (cl_arg.logmsg_path) fprintf(stderr, "%s: log message preserved in %s\n", getprogname(), cl_arg.logmsg_path); goto done; } if (cl_arg.logmsg_path) unlink(cl_arg.logmsg_path); error = got_object_id_str(&id_str, id); if (error) goto done; printf("Created commit %s\n", id_str); done: if (repo) got_repo_close(repo); if (worktree) got_worktree_close(worktree); free(cwd); free(id_str); free(editor); return error; } __dead static void usage_cherrypick(void) { fprintf(stderr, "usage: %s cherrypick commit-id\n", getprogname()); exit(1); } static const struct got_error * cmd_cherrypick(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_worktree *worktree = NULL; struct got_repository *repo = NULL; char *cwd = NULL, *commit_id_str = NULL; struct got_object_id *commit_id = NULL; struct got_commit_object *commit = NULL; struct got_object_qid *pid; struct got_reference *head_ref = NULL; int ch, did_something = 0; while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: usage_cherrypick(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " "unveil", NULL) == -1) err(1, "pledge"); #endif if (argc != 1) usage_cherrypick(); cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 0, got_worktree_get_root_path(worktree)); if (error) goto done; error = got_repo_match_object_id_prefix(&commit_id, argv[0], GOT_OBJ_TYPE_COMMIT, repo); if (error != NULL) { struct got_reference *ref; if (error->code != GOT_ERR_BAD_OBJ_ID_STR) goto done; error = got_ref_open(&ref, repo, argv[0], 0); if (error != NULL) goto done; error = got_ref_resolve(&commit_id, repo, ref); got_ref_close(ref); if (error != NULL) goto done; } error = got_object_id_str(&commit_id_str, commit_id); if (error) goto done; error = got_ref_open(&head_ref, repo, got_worktree_get_head_ref_name(worktree), 0); if (error != NULL) goto done; error = check_same_branch(commit_id, head_ref, NULL, repo); if (error) { if (error->code != GOT_ERR_ANCESTRY) goto done; error = NULL; } else { error = got_error(GOT_ERR_SAME_BRANCH); goto done; } error = got_object_open_as_commit(&commit, repo, commit_id); if (error) goto done; pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); error = got_worktree_merge_files(worktree, pid ? pid->id : NULL, commit_id, repo, update_progress, &did_something, check_cancelled, NULL); if (error != NULL) goto done; if (did_something) printf("Merged commit %s\n", commit_id_str); done: if (commit) got_object_commit_close(commit); free(commit_id_str); if (head_ref) got_ref_close(head_ref); if (worktree) got_worktree_close(worktree); if (repo) got_repo_close(repo); return error; } __dead static void usage_backout(void) { fprintf(stderr, "usage: %s backout commit-id\n", getprogname()); exit(1); } static const struct got_error * cmd_backout(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_worktree *worktree = NULL; struct got_repository *repo = NULL; char *cwd = NULL, *commit_id_str = NULL; struct got_object_id *commit_id = NULL; struct got_commit_object *commit = NULL; struct got_object_qid *pid; struct got_reference *head_ref = NULL; int ch, did_something = 0; while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: usage_backout(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " "unveil", NULL) == -1) err(1, "pledge"); #endif if (argc != 1) usage_backout(); cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 0, got_worktree_get_root_path(worktree)); if (error) goto done; error = got_repo_match_object_id_prefix(&commit_id, argv[0], GOT_OBJ_TYPE_COMMIT, repo); if (error != NULL) { struct got_reference *ref; if (error->code != GOT_ERR_BAD_OBJ_ID_STR) goto done; error = got_ref_open(&ref, repo, argv[0], 0); if (error != NULL) goto done; error = got_ref_resolve(&commit_id, repo, ref); got_ref_close(ref); if (error != NULL) goto done; } error = got_object_id_str(&commit_id_str, commit_id); if (error) goto done; error = got_ref_open(&head_ref, repo, got_worktree_get_head_ref_name(worktree), 0); if (error != NULL) goto done; error = check_same_branch(commit_id, head_ref, NULL, repo); if (error) goto done; error = got_object_open_as_commit(&commit, repo, commit_id); if (error) goto done; pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); if (pid == NULL) { error = got_error(GOT_ERR_ROOT_COMMIT); goto done; } error = got_worktree_merge_files(worktree, commit_id, pid->id, repo, update_progress, &did_something, check_cancelled, NULL); if (error != NULL) goto done; if (did_something) printf("Backed out commit %s\n", commit_id_str); done: if (commit) got_object_commit_close(commit); free(commit_id_str); if (head_ref) got_ref_close(head_ref); if (worktree) got_worktree_close(worktree); if (repo) got_repo_close(repo); return error; } __dead static void usage_rebase(void) { fprintf(stderr, "usage: %s rebase [-a] | [-c] | branch\n", getprogname()); exit(1); } void trim_logmsg(char *logmsg, int limit) { char *nl; size_t len; len = strlen(logmsg); if (len > limit) len = limit; logmsg[len] = '\0'; nl = strchr(logmsg, '\n'); if (nl) *nl = '\0'; } static const struct got_error * get_short_logmsg(char **logmsg, int limit, struct got_commit_object *commit) { const char *logmsg0 = NULL; logmsg0 = got_object_commit_get_logmsg(commit); while (isspace((unsigned char)logmsg0[0])) logmsg0++; *logmsg = strdup(logmsg0); if (*logmsg == NULL) return got_error_from_errno("strdup"); trim_logmsg(*logmsg, limit); return NULL; } static const struct got_error * show_rebase_progress(struct got_commit_object *commit, struct got_object_id *old_id, struct got_object_id *new_id) { const struct got_error *err; char *old_id_str = NULL, *new_id_str = NULL, *logmsg = NULL; err = got_object_id_str(&old_id_str, old_id); if (err) goto done; if (new_id) { err = got_object_id_str(&new_id_str, new_id); if (err) goto done; } old_id_str[12] = '\0'; if (new_id_str) new_id_str[12] = '\0'; err = get_short_logmsg(&logmsg, 42, commit); if (err) goto done; printf("%s -> %s: %s\n", old_id_str, new_id_str ? new_id_str : "no-op change", logmsg); done: free(old_id_str); free(new_id_str); return err; } static const struct got_error * rebase_progress(void *arg, unsigned char status, const char *path) { unsigned char *rebase_status = arg; while (path[0] == '/') path++; printf("%c %s\n", status, path); if (*rebase_status == GOT_STATUS_CONFLICT) return NULL; if (status == GOT_STATUS_CONFLICT || status == GOT_STATUS_MERGE) *rebase_status = status; return NULL; } static const struct got_error * rebase_complete(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *branch, struct got_reference *new_base_branch, struct got_reference *tmp_branch, struct got_repository *repo) { printf("Switching work tree to %s\n", got_ref_get_name(branch)); return got_worktree_rebase_complete(worktree, fileindex, new_base_branch, tmp_branch, branch, repo); } static const struct got_error * rebase_commit(struct got_pathlist_head *merged_paths, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *tmp_branch, struct got_object_id *commit_id, struct got_repository *repo) { const struct got_error *error; struct got_commit_object *commit; struct got_object_id *new_commit_id; error = got_object_open_as_commit(&commit, repo, commit_id); if (error) return error; error = got_worktree_rebase_commit(&new_commit_id, merged_paths, worktree, fileindex, tmp_branch, commit, commit_id, repo); if (error) { if (error->code != GOT_ERR_COMMIT_NO_CHANGES) goto done; error = show_rebase_progress(commit, commit_id, NULL); } else { error = show_rebase_progress(commit, commit_id, new_commit_id); free(new_commit_id); } done: got_object_commit_close(commit); return error; } struct check_path_prefix_arg { const char *path_prefix; size_t len; int errcode; }; static const struct got_error * check_path_prefix_in_diff(void *arg, struct got_blob_object *blob1, struct got_blob_object *blob2, struct got_object_id *id1, struct got_object_id *id2, const char *path1, const char *path2, struct got_repository *repo) { struct check_path_prefix_arg *a = arg; if ((path1 && !got_path_is_child(path1, a->path_prefix, a->len)) || (path2 && !got_path_is_child(path2, a->path_prefix, a->len))) return got_error(a->errcode); return NULL; } static const struct got_error * check_path_prefix(struct got_object_id *parent_id, struct got_object_id *commit_id, const char *path_prefix, int errcode, struct got_repository *repo) { const struct got_error *err; struct got_tree_object *tree1 = NULL, *tree2 = NULL; struct got_commit_object *commit = NULL, *parent_commit = NULL; struct check_path_prefix_arg cpp_arg; if (got_path_is_root_dir(path_prefix)) return NULL; err = got_object_open_as_commit(&commit, repo, commit_id); if (err) goto done; err = got_object_open_as_commit(&parent_commit, repo, parent_id); if (err) goto done; err = got_object_open_as_tree(&tree1, repo, got_object_commit_get_tree_id(parent_commit)); if (err) goto done; err = got_object_open_as_tree(&tree2, repo, got_object_commit_get_tree_id(commit)); if (err) goto done; cpp_arg.path_prefix = path_prefix; while (cpp_arg.path_prefix[0] == '/') cpp_arg.path_prefix++; cpp_arg.len = strlen(cpp_arg.path_prefix); cpp_arg.errcode = errcode; err = got_diff_tree(tree1, tree2, "", "", repo, check_path_prefix_in_diff, &cpp_arg, 0); done: if (tree1) got_object_tree_close(tree1); if (tree2) got_object_tree_close(tree2); if (commit) got_object_commit_close(commit); if (parent_commit) got_object_commit_close(parent_commit); return err; } static const struct got_error * collect_commits(struct got_object_id_queue *commits, struct got_object_id *initial_commit_id, struct got_object_id *iter_start_id, struct got_object_id *iter_stop_id, const char *path_prefix, int path_prefix_errcode, struct got_repository *repo) { const struct got_error *err = NULL; struct got_commit_graph *graph = NULL; struct got_object_id *parent_id = NULL; struct got_object_qid *qid; struct got_object_id *commit_id = initial_commit_id; err = got_commit_graph_open(&graph, initial_commit_id, "/", 1, repo); if (err) return err; err = got_commit_graph_iter_start(graph, iter_start_id, repo); if (err) goto done; while (got_object_id_cmp(commit_id, iter_stop_id) != 0) { err = got_commit_graph_iter_next(&parent_id, graph); if (err) { if (err->code == GOT_ERR_ITER_COMPLETED) { err = got_error_msg(GOT_ERR_ANCESTRY, "ran out of commits to rebase before " "youngest common ancestor commit has " "been reached?!?"); goto done; } else if (err->code != GOT_ERR_ITER_NEED_MORE) goto done; err = got_commit_graph_fetch_commits(graph, 1, repo); if (err) goto done; } else { err = check_path_prefix(parent_id, commit_id, path_prefix, path_prefix_errcode, repo); if (err) goto done; err = got_object_qid_alloc(&qid, commit_id); if (err) goto done; SIMPLEQ_INSERT_HEAD(commits, qid, entry); commit_id = parent_id; } } done: got_commit_graph_close(graph); return err; } static const struct got_error * cmd_rebase(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_worktree *worktree = NULL; struct got_repository *repo = NULL; struct got_fileindex *fileindex = NULL; char *cwd = NULL; struct got_reference *branch = NULL; struct got_reference *new_base_branch = NULL, *tmp_branch = NULL; struct got_object_id *commit_id = NULL, *parent_id = NULL; struct got_object_id *resume_commit_id = NULL; struct got_object_id *branch_head_commit_id = NULL, *yca_id = NULL; struct got_commit_object *commit = NULL; int ch, rebase_in_progress = 0, abort_rebase = 0, continue_rebase = 0; unsigned char rebase_status = GOT_STATUS_NO_CHANGE; struct got_object_id_queue commits; struct got_pathlist_head merged_paths; const struct got_object_id_queue *parent_ids; struct got_object_qid *qid, *pid; SIMPLEQ_INIT(&commits); TAILQ_INIT(&merged_paths); while ((ch = getopt(argc, argv, "ac")) != -1) { switch (ch) { case 'a': abort_rebase = 1; break; case 'c': continue_rebase = 1; break; default: usage_rebase(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " "unveil", NULL) == -1) err(1, "pledge"); #endif if (abort_rebase && continue_rebase) usage_rebase(); else if (abort_rebase || continue_rebase) { if (argc != 0) usage_rebase(); } else if (argc != 1) usage_rebase(); cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; error = apply_unveil(got_repo_get_path(repo), 0, got_worktree_get_root_path(worktree)); if (error) goto done; error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); if (error) goto done; if (abort_rebase) { int did_something; if (!rebase_in_progress) { error = got_error(GOT_ERR_NOT_REBASING); goto done; } error = got_worktree_rebase_continue(&resume_commit_id, &new_base_branch, &tmp_branch, &branch, &fileindex, worktree, repo); if (error) goto done; printf("Switching work tree to %s\n", got_ref_get_symref_target(new_base_branch)); error = got_worktree_rebase_abort(worktree, fileindex, repo, new_base_branch, update_progress, &did_something); if (error) goto done; printf("Rebase of %s aborted\n", got_ref_get_name(branch)); goto done; /* nothing else to do */ } if (continue_rebase) { if (!rebase_in_progress) { error = got_error(GOT_ERR_NOT_REBASING); goto done; } error = got_worktree_rebase_continue(&resume_commit_id, &new_base_branch, &tmp_branch, &branch, &fileindex, worktree, repo); if (error) goto done; error = rebase_commit(NULL, worktree, fileindex, tmp_branch, resume_commit_id, repo); if (error) goto done; yca_id = got_object_id_dup(resume_commit_id); if (yca_id == NULL) { error = got_error_from_errno("got_object_id_dup"); goto done; } } else { error = got_ref_open(&branch, repo, argv[0], 0); if (error != NULL) goto done; } error = got_ref_resolve(&branch_head_commit_id, repo, branch); if (error) goto done; if (!continue_rebase) { struct got_object_id *base_commit_id; base_commit_id = got_worktree_get_base_commit_id(worktree); error = got_commit_graph_find_youngest_common_ancestor(&yca_id, base_commit_id, branch_head_commit_id, repo); if (error) goto done; if (yca_id == NULL) { error = got_error_msg(GOT_ERR_ANCESTRY, "specified branch shares no common ancestry " "with work tree's branch"); goto done; } error = check_same_branch(base_commit_id, branch, yca_id, repo); if (error) { if (error->code != GOT_ERR_ANCESTRY) goto done; error = NULL; } else { error = got_error_msg(GOT_ERR_SAME_BRANCH, "specified branch resolves to a commit which " "is already contained in work tree's branch"); goto done; } error = got_worktree_rebase_prepare(&new_base_branch, &tmp_branch, &fileindex, worktree, branch, repo); if (error) goto done; } commit_id = branch_head_commit_id; error = got_object_open_as_commit(&commit, repo, commit_id); if (error) goto done; parent_ids = got_object_commit_get_parent_ids(commit); pid = SIMPLEQ_FIRST(parent_ids); error = collect_commits(&commits, commit_id, pid->id, yca_id, got_worktree_get_path_prefix(worktree), GOT_ERR_REBASE_PATH, repo); got_object_commit_close(commit); commit = NULL; if (error) goto done; if (SIMPLEQ_EMPTY(&commits)) { if (continue_rebase) error = rebase_complete(worktree, fileindex, branch, new_base_branch, tmp_branch, repo); else error = got_error(GOT_ERR_EMPTY_REBASE); goto done; } pid = NULL; SIMPLEQ_FOREACH(qid, &commits, entry) { commit_id = qid->id; parent_id = pid ? pid->id : yca_id; pid = qid; error = got_worktree_rebase_merge_files(&merged_paths, worktree, fileindex, parent_id, commit_id, repo, rebase_progress, &rebase_status, check_cancelled, NULL); if (error) goto done; if (rebase_status == GOT_STATUS_CONFLICT) { got_worktree_rebase_pathlist_free(&merged_paths); break; } error = rebase_commit(&merged_paths, worktree, fileindex, tmp_branch, commit_id, repo); got_worktree_rebase_pathlist_free(&merged_paths); if (error) goto done; } if (rebase_status == GOT_STATUS_CONFLICT) { error = got_worktree_rebase_postpone(worktree, fileindex); if (error) goto done; error = got_error_msg(GOT_ERR_CONFLICTS, "conflicts must be resolved before rebasing can continue"); } else error = rebase_complete(worktree, fileindex, branch, new_base_branch, tmp_branch, repo); done: got_object_id_queue_free(&commits); free(branch_head_commit_id); free(resume_commit_id); free(yca_id); if (commit) got_object_commit_close(commit); if (branch) got_ref_close(branch); if (new_base_branch) got_ref_close(new_base_branch); if (tmp_branch) got_ref_close(tmp_branch); if (worktree) got_worktree_close(worktree); if (repo) got_repo_close(repo); return error; } __dead static void usage_histedit(void) { fprintf(stderr, "usage: %s histedit [-a] [-c] [-F path]\n", getprogname()); exit(1); } #define GOT_HISTEDIT_PICK 'p' #define GOT_HISTEDIT_EDIT 'e' #define GOT_HISTEDIT_FOLD 'f' #define GOT_HISTEDIT_DROP 'd' #define GOT_HISTEDIT_MESG 'm' static struct got_histedit_cmd { unsigned char code; const char *name; const char *desc; } got_histedit_cmds[] = { { GOT_HISTEDIT_PICK, "pick", "use commit" }, { GOT_HISTEDIT_EDIT, "edit", "use commit but stop for amending" }, { GOT_HISTEDIT_FOLD, "fold", "combine with commit below" }, { GOT_HISTEDIT_DROP, "drop", "remove commit from history" }, { GOT_HISTEDIT_MESG, "mesg", "single-line log message for commit above (open editor if empty)" }, }; struct got_histedit_list_entry { TAILQ_ENTRY(got_histedit_list_entry) entry; struct got_object_id *commit_id; const struct got_histedit_cmd *cmd; char *logmsg; }; TAILQ_HEAD(got_histedit_list, got_histedit_list_entry); static const struct got_error * histedit_write_commit(struct got_object_id *commit_id, const char *cmdname, FILE *f, struct got_repository *repo) { const struct got_error *err = NULL; char *logmsg = NULL, *id_str = NULL; struct got_commit_object *commit = NULL; size_t n; err = got_object_open_as_commit(&commit, repo, commit_id); if (err) goto done; err = get_short_logmsg(&logmsg, 34, commit); if (err) goto done; err = got_object_id_str(&id_str, commit_id); if (err) goto done; n = fprintf(f, "%s %s %s\n", cmdname, id_str, logmsg); if (n < 0) err = got_ferror(f, GOT_ERR_IO); done: if (commit) got_object_commit_close(commit); free(id_str); free(logmsg); return err; } static const struct got_error * histedit_write_commit_list(struct got_object_id_queue *commits, FILE *f, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_qid *qid; if (SIMPLEQ_EMPTY(commits)) return got_error(GOT_ERR_EMPTY_HISTEDIT); SIMPLEQ_FOREACH(qid, commits, entry) { err = histedit_write_commit(qid->id, got_histedit_cmds[0].name, f, repo); if (err) break; } return err; } static const struct got_error * write_cmd_list(FILE *f) { const struct got_error *err = NULL; int n, i; n = fprintf(f, "# Available histedit commands:\n"); if (n < 0) return got_ferror(f, GOT_ERR_IO); for (i = 0; i < nitems(got_histedit_cmds); i++) { struct got_histedit_cmd *cmd = &got_histedit_cmds[i]; n = fprintf(f, "# %s (%c): %s\n", cmd->name, cmd->code, cmd->desc); if (n < 0) { err = got_ferror(f, GOT_ERR_IO); break; } } n = fprintf(f, "# Commits will be processed in order from top to " "bottom of this file.\n"); if (n < 0) return got_ferror(f, GOT_ERR_IO); return err; } static const struct got_error * histedit_syntax_error(int lineno) { static char msg[42]; int ret; ret = snprintf(msg, sizeof(msg), "histedit syntax error on line %d", lineno); if (ret == -1 || ret >= sizeof(msg)) return got_error(GOT_ERR_HISTEDIT_SYNTAX); return got_error_msg(GOT_ERR_HISTEDIT_SYNTAX, msg); } static const struct got_error * append_folded_commit_msg(char **new_msg, struct got_histedit_list_entry *hle, char *logmsg, struct got_repository *repo) { const struct got_error *err; struct got_commit_object *folded_commit = NULL; char *id_str; err = got_object_id_str(&id_str, hle->commit_id); if (err) return err; err = got_object_open_as_commit(&folded_commit, repo, hle->commit_id); if (err) goto done; if (asprintf(new_msg, "%s%s# log message of folded commit %s: %s", logmsg ? logmsg : "", logmsg ? "\n" : "", id_str, got_object_commit_get_logmsg(folded_commit)) == -1) { err = got_error_from_errno("asprintf"); goto done; } done: if (folded_commit) got_object_commit_close(folded_commit); free(id_str); return err; } static struct got_histedit_list_entry * get_folded_commits(struct got_histedit_list_entry *hle) { struct got_histedit_list_entry *prev, *folded = NULL; prev = TAILQ_PREV(hle, got_histedit_list, entry); while (prev && (prev->cmd->code == GOT_HISTEDIT_FOLD || prev->cmd->code == GOT_HISTEDIT_DROP)) { if (prev->cmd->code == GOT_HISTEDIT_FOLD) folded = prev; prev = TAILQ_PREV(prev, got_histedit_list, entry); } return folded; } static const struct got_error * histedit_edit_logmsg(struct got_histedit_list_entry *hle, struct got_repository *repo) { char *logmsg_path = NULL, *id_str = NULL; char *logmsg = NULL, *new_msg = NULL, *editor = NULL; const struct got_error *err = NULL; struct got_commit_object *commit = NULL; int fd; struct got_histedit_list_entry *folded = NULL; err = got_object_open_as_commit(&commit, repo, hle->commit_id); if (err) return err; folded = get_folded_commits(hle); if (folded) { while (folded != hle) { if (folded->cmd->code == GOT_HISTEDIT_DROP) { folded = TAILQ_NEXT(folded, entry); continue; } err = append_folded_commit_msg(&new_msg, folded, logmsg, repo); if (err) goto done; free(logmsg); logmsg = new_msg; folded = TAILQ_NEXT(folded, entry); } } err = got_object_id_str(&id_str, hle->commit_id); if (err) goto done; if (asprintf(&new_msg, "%s\n# original log message of commit %s: %s", logmsg ? logmsg : "", id_str, got_object_commit_get_logmsg(commit)) == -1) { err = got_error_from_errno("asprintf"); goto done; } free(logmsg); logmsg = new_msg; err = got_object_id_str(&id_str, hle->commit_id); if (err) goto done; err = got_opentemp_named_fd(&logmsg_path, &fd, "/tmp/got-logmsg"); if (err) goto done; dprintf(fd, logmsg); close(fd); err = get_editor(&editor); if (err) goto done; err = edit_logmsg(&hle->logmsg, editor, logmsg_path, logmsg); if (err) { if (err->code != GOT_ERR_COMMIT_MSG_EMPTY) goto done; err = NULL; hle->logmsg = strdup(got_object_commit_get_logmsg(commit)); if (hle->logmsg == NULL) err = got_error_from_errno("strdup"); } done: if (logmsg_path && unlink(logmsg_path) != 0 && err == NULL) err = got_error_from_errno2("unlink", logmsg_path); free(logmsg_path); free(logmsg); free(editor); if (commit) got_object_commit_close(commit); return err; } static const struct got_error * histedit_parse_list(struct got_histedit_list *histedit_cmds, FILE *f, struct got_repository *repo) { const struct got_error *err = NULL; char *line = NULL, *p, *end; size_t size; ssize_t len; int lineno = 0, i; const struct got_histedit_cmd *cmd; struct got_object_id *commit_id = NULL; struct got_histedit_list_entry *hle = NULL; for (;;) { len = getline(&line, &size, f); if (len == -1) { const struct got_error *getline_err; if (feof(f)) break; getline_err = got_error_from_errno("getline"); err = got_ferror(f, getline_err->code); break; } lineno++; p = line; while (isspace((unsigned char)p[0])) p++; if (p[0] == '#' || p[0] == '\0') { free(line); line = NULL; continue; } cmd = NULL; for (i = 0; i < nitems(got_histedit_cmds); i++) { cmd = &got_histedit_cmds[i]; if (strncmp(cmd->name, p, strlen(cmd->name)) == 0 && isspace((unsigned char)p[strlen(cmd->name)])) { p += strlen(cmd->name); break; } if (p[0] == cmd->code && isspace((unsigned char)p[1])) { p++; break; } } if (i == nitems(got_histedit_cmds)) { err = histedit_syntax_error(lineno); break; } while (isspace((unsigned char)p[0])) p++; if (cmd->code == GOT_HISTEDIT_MESG) { if (hle == NULL || hle->logmsg != NULL) { err = got_error(GOT_ERR_HISTEDIT_CMD); break; } if (p[0] == '\0') { err = histedit_edit_logmsg(hle, repo); if (err) break; } else { hle->logmsg = strdup(p); if (hle->logmsg == NULL) { err = got_error_from_errno("strdup"); break; } } free(line); line = NULL; continue; } else { end = p; while (end[0] && !isspace((unsigned char)end[0])) end++; *end = '\0'; err = got_object_resolve_id_str(&commit_id, repo, p); if (err) { /* override error code */ err = histedit_syntax_error(lineno); break; } } hle = malloc(sizeof(*hle)); if (hle == NULL) { err = got_error_from_errno("malloc"); break; } hle->cmd = cmd; hle->commit_id = commit_id; hle->logmsg = NULL; commit_id = NULL; free(line); line = NULL; TAILQ_INSERT_TAIL(histedit_cmds, hle, entry); } free(line); free(commit_id); return err; } static const struct got_error * histedit_check_script(struct got_histedit_list *histedit_cmds, struct got_object_id_queue *commits, struct got_repository *repo) { const struct got_error *err = NULL; struct got_object_qid *qid; struct got_histedit_list_entry *hle; static char msg[80]; char *id_str; if (TAILQ_EMPTY(histedit_cmds)) return got_error_msg(GOT_ERR_EMPTY_HISTEDIT, "histedit script contains no commands"); SIMPLEQ_FOREACH(qid, commits, entry) { TAILQ_FOREACH(hle, histedit_cmds, entry) { if (got_object_id_cmp(qid->id, hle->commit_id) == 0) break; } if (hle == NULL) { err = got_object_id_str(&id_str, qid->id); if (err) return err; snprintf(msg, sizeof(msg), "commit %s missing from histedit script", id_str); free(id_str); return got_error_msg(GOT_ERR_HISTEDIT_CMD, msg); } } if (hle->cmd->code == GOT_HISTEDIT_FOLD) return got_error_msg(GOT_ERR_HISTEDIT_CMD, "last commit in histedit script cannot be folded"); return NULL; } static const struct got_error * histedit_run_editor(struct got_histedit_list *histedit_cmds, const char *path, struct got_object_id_queue *commits, struct got_repository *repo) { const struct got_error *err = NULL; char *editor; FILE *f = NULL; err = get_editor(&editor); if (err) return err; if (spawn_editor(editor, path) == -1) { err = got_error_from_errno("failed spawning editor"); goto done; } f = fopen(path, "r"); if (f == NULL) { err = got_error_from_errno("fopen"); goto done; } err = histedit_parse_list(histedit_cmds, f, repo); if (err) goto done; err = histedit_check_script(histedit_cmds, commits, repo); done: if (f && fclose(f) != 0 && err == NULL) err = got_error_from_errno("fclose"); free(editor); return err; } static const struct got_error * histedit_edit_list_retry(struct got_histedit_list *, const struct got_error *, struct got_object_id_queue *, const char *, struct got_repository *); static const struct got_error * histedit_edit_script(struct got_histedit_list *histedit_cmds, struct got_object_id_queue *commits, struct got_repository *repo) { const struct got_error *err; FILE *f = NULL; char *path = NULL; err = got_opentemp_named(&path, &f, "got-histedit"); if (err) return err; err = write_cmd_list(f); if (err) goto done; err = histedit_write_commit_list(commits, f, repo); if (err) goto done; if (fclose(f) != 0) { err = got_error_from_errno("fclose"); goto done; } f = NULL; err = histedit_run_editor(histedit_cmds, path, commits, repo); if (err) { if (err->code != GOT_ERR_HISTEDIT_SYNTAX && err->code != GOT_ERR_HISTEDIT_CMD) goto done; err = histedit_edit_list_retry(histedit_cmds, err, commits, path, repo); } done: if (f && fclose(f) != 0 && err == NULL) err = got_error_from_errno("fclose"); if (path && unlink(path) != 0 && err == NULL) err = got_error_from_errno2("unlink", path); free(path); return err; } static const struct got_error * histedit_save_list(struct got_histedit_list *histedit_cmds, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *err = NULL; char *path = NULL; FILE *f = NULL; struct got_histedit_list_entry *hle; struct got_commit_object *commit = NULL; err = got_worktree_get_histedit_script_path(&path, worktree); if (err) return err; f = fopen(path, "w"); if (f == NULL) { err = got_error_from_errno2("fopen", path); goto done; } TAILQ_FOREACH(hle, histedit_cmds, entry) { err = histedit_write_commit(hle->commit_id, hle->cmd->name, f, repo); if (err) break; if (hle->logmsg) { int n = fprintf(f, "%c %s\n", GOT_HISTEDIT_MESG, hle->logmsg); if (n < 0) { err = got_ferror(f, GOT_ERR_IO); break; } } } done: if (f && fclose(f) != 0 && err == NULL) err = got_error_from_errno("fclose"); free(path); if (commit) got_object_commit_close(commit); return err; } void histedit_free_list(struct got_histedit_list *histedit_cmds) { struct got_histedit_list_entry *hle; while ((hle = TAILQ_FIRST(histedit_cmds))) { TAILQ_REMOVE(histedit_cmds, hle, entry); free(hle); } } static const struct got_error * histedit_load_list(struct got_histedit_list *histedit_cmds, const char *path, struct got_repository *repo) { const struct got_error *err = NULL; FILE *f = NULL; f = fopen(path, "r"); if (f == NULL) { err = got_error_from_errno2("fopen", path); goto done; } err = histedit_parse_list(histedit_cmds, f, repo); done: if (f && fclose(f) != 0 && err == NULL) err = got_error_from_errno("fclose"); return err; } static const struct got_error * histedit_edit_list_retry(struct got_histedit_list *histedit_cmds, const struct got_error *edit_err, struct got_object_id_queue *commits, const char *path, struct got_repository *repo) { const struct got_error *err = NULL, *prev_err = edit_err; int resp = ' '; while (resp != 'c' && resp != 'r' && resp != 'a') { printf("%s: %s\n(c)ontinue editing, (r)estart editing, " "or (a)bort: ", getprogname(), prev_err->msg); resp = getchar(); if (resp == 'c') { histedit_free_list(histedit_cmds); err = histedit_run_editor(histedit_cmds, path, commits, repo); if (err) { if (err->code != GOT_ERR_HISTEDIT_SYNTAX && err->code != GOT_ERR_HISTEDIT_CMD) break; prev_err = err; resp = ' '; continue; } break; } else if (resp == 'r') { histedit_free_list(histedit_cmds); err = histedit_edit_script(histedit_cmds, commits, repo); if (err) { if (err->code != GOT_ERR_HISTEDIT_SYNTAX && err->code != GOT_ERR_HISTEDIT_CMD) break; prev_err = err; resp = ' '; continue; } break; } else if (resp == 'a') { err = got_error(GOT_ERR_HISTEDIT_CANCEL); break; } else printf("invalid response '%c'\n", resp); } return err; } static const struct got_error * histedit_complete(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *tmp_branch, struct got_reference *branch, struct got_repository *repo) { printf("Switching work tree to %s\n", got_ref_get_symref_target(branch)); return got_worktree_histedit_complete(worktree, fileindex, tmp_branch, branch, repo); } static const struct got_error * show_histedit_progress(struct got_commit_object *commit, struct got_histedit_list_entry *hle, struct got_object_id *new_id) { const struct got_error *err; char *old_id_str = NULL, *new_id_str = NULL, *logmsg = NULL; err = got_object_id_str(&old_id_str, hle->commit_id); if (err) goto done; if (new_id) { err = got_object_id_str(&new_id_str, new_id); if (err) goto done; } old_id_str[12] = '\0'; if (new_id_str) new_id_str[12] = '\0'; if (hle->logmsg) { logmsg = strdup(hle->logmsg); if (logmsg == NULL) { err = got_error_from_errno("strdup"); goto done; } trim_logmsg(logmsg, 42); } else { err = get_short_logmsg(&logmsg, 42, commit); if (err) goto done; } switch (hle->cmd->code) { case GOT_HISTEDIT_PICK: case GOT_HISTEDIT_EDIT: printf("%s -> %s: %s\n", old_id_str, new_id_str ? new_id_str : "no-op change", logmsg); break; case GOT_HISTEDIT_DROP: case GOT_HISTEDIT_FOLD: printf("%s -> %s commit: %s\n", old_id_str, hle->cmd->name, logmsg); break; default: break; } done: free(old_id_str); free(new_id_str); return err; } static const struct got_error * histedit_commit(struct got_pathlist_head *merged_paths, struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_reference *tmp_branch, struct got_histedit_list_entry *hle, struct got_repository *repo) { const struct got_error *err; struct got_commit_object *commit; struct got_object_id *new_commit_id; if ((hle->cmd->code == GOT_HISTEDIT_EDIT || get_folded_commits(hle)) && hle->logmsg == NULL) { err = histedit_edit_logmsg(hle, repo); if (err) return err; } err = got_object_open_as_commit(&commit, repo, hle->commit_id); if (err) return err; err = got_worktree_histedit_commit(&new_commit_id, merged_paths, worktree, fileindex, tmp_branch, commit, hle->commit_id, hle->logmsg, repo); if (err) { if (err->code != GOT_ERR_COMMIT_NO_CHANGES) goto done; err = show_histedit_progress(commit, hle, NULL); } else { err = show_histedit_progress(commit, hle, new_commit_id); free(new_commit_id); } done: got_object_commit_close(commit); return err; } static const struct got_error * histedit_skip_commit(struct got_histedit_list_entry *hle, struct got_worktree *worktree, struct got_repository *repo) { const struct got_error *error; struct got_commit_object *commit; error = got_worktree_histedit_skip_commit(worktree, hle->commit_id, repo); if (error) return error; error = got_object_open_as_commit(&commit, repo, hle->commit_id); if (error) return error; error = show_histedit_progress(commit, hle, NULL); got_object_commit_close(commit); return error; } static const struct got_error * cmd_histedit(int argc, char *argv[]) { const struct got_error *error = NULL; struct got_worktree *worktree = NULL; struct got_fileindex *fileindex = NULL; struct got_repository *repo = NULL; char *cwd = NULL; struct got_reference *branch = NULL; struct got_reference *tmp_branch = NULL; struct got_object_id *resume_commit_id = NULL; struct got_object_id *base_commit_id = NULL; struct got_object_id *head_commit_id = NULL; struct got_commit_object *commit = NULL; int ch, rebase_in_progress = 0, did_something; int edit_in_progress = 0, abort_edit = 0, continue_edit = 0; const char *edit_script_path = NULL; unsigned char rebase_status = GOT_STATUS_NO_CHANGE; struct got_object_id_queue commits; struct got_pathlist_head merged_paths; const struct got_object_id_queue *parent_ids; struct got_object_qid *pid; struct got_histedit_list histedit_cmds; struct got_histedit_list_entry *hle; SIMPLEQ_INIT(&commits); TAILQ_INIT(&histedit_cmds); TAILQ_INIT(&merged_paths); while ((ch = getopt(argc, argv, "acF:")) != -1) { switch (ch) { case 'a': abort_edit = 1; break; case 'c': continue_edit = 1; break; case 'F': edit_script_path = optarg; break; default: usage_histedit(); /* NOTREACHED */ } } argc -= optind; argv += optind; #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " "unveil", NULL) == -1) err(1, "pledge"); #endif if (abort_edit && continue_edit) usage_histedit(); if (argc != 0) usage_histedit(); /* * This command cannot apply unveil(2) in all cases because the * user may choose to run an editor to edit the histedit script * and to edit individual commit log messages. * unveil(2) traverses exec(2); if an editor is used we have to * apply unveil after edit script and log messages have been written. * XXX TODO: Make use of unveil(2) where possible. */ cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); goto done; } error = got_worktree_open(&worktree, cwd); if (error) goto done; error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); if (error != NULL) goto done; error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); if (error) goto done; if (rebase_in_progress) { error = got_error(GOT_ERR_REBASING); goto done; } error = got_worktree_histedit_in_progress(&edit_in_progress, worktree); if (error) goto done; if (edit_in_progress && abort_edit) { error = got_worktree_histedit_continue(&resume_commit_id, &tmp_branch, &branch, &base_commit_id, &fileindex, worktree, repo); if (error) goto done; printf("Switching work tree to %s\n", got_ref_get_symref_target(branch)); error = got_worktree_histedit_abort(worktree, fileindex, repo, branch, base_commit_id, update_progress, &did_something); if (error) goto done; printf("Histedit of %s aborted\n", got_ref_get_symref_target(branch)); goto done; /* nothing else to do */ } else if (abort_edit) { error = got_error(GOT_ERR_NOT_HISTEDIT); goto done; } if (continue_edit) { char *path; if (!edit_in_progress) { error = got_error(GOT_ERR_NOT_HISTEDIT); goto done; } error = got_worktree_get_histedit_script_path(&path, worktree); if (error) goto done; error = histedit_load_list(&histedit_cmds, path, repo); free(path); if (error) goto done; error = got_worktree_histedit_continue(&resume_commit_id, &tmp_branch, &branch, &base_commit_id, &fileindex, worktree, repo); if (error) goto done; error = got_ref_resolve(&head_commit_id, repo, branch); if (error) goto done; error = got_object_open_as_commit(&commit, repo, head_commit_id); if (error) goto done; parent_ids = got_object_commit_get_parent_ids(commit); pid = SIMPLEQ_FIRST(parent_ids); if (pid == NULL) { error = got_error(GOT_ERR_EMPTY_HISTEDIT); goto done; } error = collect_commits(&commits, head_commit_id, pid->id, base_commit_id, got_worktree_get_path_prefix(worktree), GOT_ERR_HISTEDIT_PATH, repo); got_object_commit_close(commit); commit = NULL; if (error) goto done; } else { if (edit_in_progress) { error = got_error(GOT_ERR_HISTEDIT_BUSY); goto done; } error = got_ref_open(&branch, repo, got_worktree_get_head_ref_name(worktree), 0); if (error != NULL) goto done; error = got_ref_resolve(&head_commit_id, repo, branch); got_ref_close(branch); branch = NULL; if (error) goto done; error = got_object_open_as_commit(&commit, repo, head_commit_id); if (error) goto done; parent_ids = got_object_commit_get_parent_ids(commit); pid = SIMPLEQ_FIRST(parent_ids); if (pid == NULL) { error = got_error(GOT_ERR_EMPTY_HISTEDIT); goto done; } error = collect_commits(&commits, head_commit_id, pid->id, got_worktree_get_base_commit_id(worktree), got_worktree_get_path_prefix(worktree), GOT_ERR_HISTEDIT_PATH, repo); got_object_commit_close(commit); commit = NULL; if (error) goto done; error = got_worktree_histedit_prepare(&tmp_branch, &branch, &base_commit_id, &fileindex, worktree, repo); if (error) goto done; if (edit_script_path) { error = histedit_load_list(&histedit_cmds, edit_script_path, repo); if (error) { got_worktree_histedit_abort(worktree, fileindex, repo, branch, base_commit_id, update_progress, &did_something); goto done; } } else { error = histedit_edit_script(&histedit_cmds, &commits, repo); if (error) { got_worktree_histedit_abort(worktree, fileindex, repo, branch, base_commit_id, update_progress, &did_something); goto done; } } error = histedit_save_list(&histedit_cmds, worktree, repo); if (error) { got_worktree_histedit_abort(worktree, fileindex, repo, branch, base_commit_id, update_progress, &did_something); goto done; } } error = histedit_check_script(&histedit_cmds, &commits, repo); if (error) goto done; TAILQ_FOREACH(hle, &histedit_cmds, entry) { if (resume_commit_id) { if (got_object_id_cmp(hle->commit_id, resume_commit_id) != 0) continue; resume_commit_id = NULL; if (hle->cmd->code == GOT_HISTEDIT_DROP || hle->cmd->code == GOT_HISTEDIT_FOLD) { error = histedit_skip_commit(hle, worktree, repo); } else { error = histedit_commit(NULL, worktree, fileindex, tmp_branch, hle, repo); } if (error) goto done; continue; } if (hle->cmd->code == GOT_HISTEDIT_DROP) { error = histedit_skip_commit(hle, worktree, repo); if (error) goto done; continue; } error = got_object_open_as_commit(&commit, repo, hle->commit_id); if (error) goto done; parent_ids = got_object_commit_get_parent_ids(commit); pid = SIMPLEQ_FIRST(parent_ids); error = got_worktree_histedit_merge_files(&merged_paths, worktree, fileindex, pid->id, hle->commit_id, repo, rebase_progress, &rebase_status, check_cancelled, NULL); if (error) goto done; got_object_commit_close(commit); commit = NULL; if (rebase_status == GOT_STATUS_CONFLICT) { got_worktree_rebase_pathlist_free(&merged_paths); break; } if (hle->cmd->code == GOT_HISTEDIT_EDIT) { char *id_str; error = got_object_id_str(&id_str, hle->commit_id); if (error) goto done; printf("Stopping histedit for amending commit %s\n", id_str); free(id_str); got_worktree_rebase_pathlist_free(&merged_paths); error = got_worktree_histedit_postpone(worktree, fileindex); goto done; } if (hle->cmd->code == GOT_HISTEDIT_FOLD) { error = histedit_skip_commit(hle, worktree, repo); if (error) goto done; continue; } error = histedit_commit(&merged_paths, worktree, fileindex, tmp_branch, hle, repo); got_worktree_rebase_pathlist_free(&merged_paths); if (error) goto done; } if (rebase_status == GOT_STATUS_CONFLICT) { error = got_worktree_histedit_postpone(worktree, fileindex); if (error) goto done; error = got_error_msg(GOT_ERR_CONFLICTS, "conflicts must be resolved before rebasing can continue"); } else error = histedit_complete(worktree, fileindex, tmp_branch, branch, repo); done: got_object_id_queue_free(&commits); histedit_free_list(&histedit_cmds); free(head_commit_id); free(base_commit_id); free(resume_commit_id); if (commit) got_object_commit_close(commit); if (branch) got_ref_close(branch); if (tmp_branch) got_ref_close(tmp_branch); if (worktree) got_worktree_close(worktree); if (repo) got_repo_close(repo); return error; }