commit acfd930bda7c65876653f0f2ad475436dc17ef9f from: Josh Rickmar date: Sat Jul 08 13:12:49 2023 UTC Remove unused functions from cvg commit - 3d1b16d126400e28a2cbab66b17dacd47a01763c commit + acfd930bda7c65876653f0f2ad475436dc17ef9f blob - 7a71eef0f01f4ba0d7203040a9f6ce7891dc108a blob + 495bc5a1d44efc6d760792f0976a4cbf808e0b8a --- Makefile.inc +++ Makefile.inc @@ -14,7 +14,6 @@ MANDIR ?= ${PREFIX}/man/man .else CFLAGS += -Werror -Wall -Wstrict-prototypes -Wmissing-prototypes CFLAGS += -Wwrite-strings -Wunused-variable -CFLAGS += -Wno-unused-function PREFIX ?= ${HOME} BINDIR ?= ${PREFIX}/bin LIBEXECDIR ?= ${BINDIR} blob - 12ab5c9682db9094d25525bc55e414088b2c75a8 blob + 1f51749ddf6911fb3d86837fa46e6565d991b44c --- cvg/got.c +++ cvg/got.c @@ -95,7 +95,6 @@ struct got_cmd { __dead static void usage(int, int); __dead static void usage_import(void); __dead static void usage_clone(void); -__dead static void usage_fetch(void); __dead static void usage_checkout(void); __dead static void usage_update(void); __dead static void usage_log(void); @@ -104,28 +103,19 @@ __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_tag(void); __dead static void usage_add(void); __dead static void usage_remove(void); __dead static void usage_patch(void); __dead static void usage_revert(void); __dead static void usage_commit(void); -__dead static void usage_send(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); -__dead static void usage_integrate(void); -__dead static void usage_merge(void); -__dead static void usage_stage(void); -__dead static void usage_unstage(void); __dead static void usage_cat(void); __dead static void usage_info(void); static const struct got_error* cmd_import(int, char *[]); static const struct got_error* cmd_clone(int, char *[]); -static const struct got_error* cmd_fetch(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 *[]); @@ -134,29 +124,20 @@ 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_tag(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_patch(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_send(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 const struct got_error* cmd_integrate(int, char *[]); -static const struct got_error* cmd_merge(int, char *[]); -static const struct got_error* cmd_stage(int, char *[]); -static const struct got_error* cmd_unstage(int, char *[]); static const struct got_error* cmd_cat(int, char *[]); static const struct got_error* cmd_info(int, char *[]); static const struct got_cmd got_commands[] = { { "import", cmd_import, usage_import, "im" }, { "clone", cmd_clone, usage_clone, "cl" }, - /*{ "fetch", cmd_fetch, usage_fetch, "fe" },*/ /* rolled into update */ { "checkout", cmd_checkout, usage_checkout, "co" }, { "update", cmd_update, usage_update, "up" }, { "log", cmd_log, usage_log, "" }, @@ -165,22 +146,14 @@ static const struct got_cmd got_commands[] = { { "tree", cmd_tree, usage_tree, "tr" }, { "status", cmd_status, usage_status, "st" }, { "ref", cmd_ref, usage_ref, "" }, - /*{ "branch", cmd_branch, usage_branch, "br" },*/ { "tag", cmd_tag, usage_tag, "" }, { "add", cmd_add, usage_add, "" }, { "remove", cmd_remove, usage_remove, "rm" }, { "patch", cmd_patch, usage_patch, "pa" }, { "revert", cmd_revert, usage_revert, "rv" }, { "commit", cmd_commit, usage_commit, "ci" }, - /*{ "send", cmd_send, usage_send, "se" },*/ /* part of commit */ { "cherrypick", cmd_cherrypick, usage_cherrypick, "cy" }, { "backout", cmd_backout, usage_backout, "bo" }, - /*{ "rebase", cmd_rebase, usage_rebase, "rb" },*/ - /*{ "histedit", cmd_histedit, usage_histedit, "he" },*/ - /*{ "integrate", cmd_integrate, usage_integrate,"ig" },*/ - /*{ "merge", cmd_merge, usage_merge, "mg" },*/ - /*{ "stage", cmd_stage, usage_stage, "sg" },*/ - /*{ "unstage", cmd_unstage, usage_unstage, "ug" },*/ { "cat", cmd_cat, usage_cat, "" }, { "info", cmd_info, usage_info, "" }, }; @@ -1996,204 +1969,6 @@ done: } static const struct got_error * -update_symref(const char *refname, struct got_reference *target_ref, - int verbosity, struct got_repository *repo) -{ - const struct got_error *err = NULL, *unlock_err; - struct got_reference *symref; - int symref_is_locked = 0; - - err = got_ref_open(&symref, repo, refname, 1); - if (err) { - if (err->code != GOT_ERR_NOT_REF) - return err; - err = got_ref_alloc_symref(&symref, refname, target_ref); - if (err) - goto done; - - err = got_ref_write(symref, repo); - if (err) - goto done; - - if (verbosity >= 0) - printf("Created reference %s: %s\n", - got_ref_get_name(symref), - got_ref_get_symref_target(symref)); - } else { - symref_is_locked = 1; - - if (strcmp(got_ref_get_symref_target(symref), - got_ref_get_name(target_ref)) == 0) - goto done; - - err = got_ref_change_symref(symref, - got_ref_get_name(target_ref)); - if (err) - goto done; - - err = got_ref_write(symref, repo); - if (err) - goto done; - - if (verbosity >= 0) - printf("Updated %s: %s\n", got_ref_get_name(symref), - got_ref_get_symref_target(symref)); - - } -done: - if (symref_is_locked) { - unlock_err = got_ref_unlock(symref); - if (unlock_err && err == NULL) - err = unlock_err; - } - got_ref_close(symref); - return err; -} - -__dead static void -usage_fetch(void) -{ - fprintf(stderr, "usage: %s fetch [-adlqtvX] [-b branch] " - "[-R reference] [-r repository-path] [remote-repository]\n", - getprogname()); - exit(1); -} - -static const struct got_error * -delete_missing_ref(struct got_reference *ref, - int verbosity, struct got_repository *repo) -{ - const struct got_error *err = NULL; - struct got_object_id *id = NULL; - char *id_str = NULL; - - if (got_ref_is_symbolic(ref)) { - err = got_ref_delete(ref, repo); - if (err) - return err; - if (verbosity >= 0) { - printf("Deleted %s: %s\n", - got_ref_get_name(ref), - got_ref_get_symref_target(ref)); - } - } else { - err = got_ref_resolve(&id, repo, ref); - if (err) - return err; - err = got_object_id_str(&id_str, id); - if (err) - goto done; - - err = got_ref_delete(ref, repo); - if (err) - goto done; - if (verbosity >= 0) { - printf("Deleted %s: %s\n", - got_ref_get_name(ref), id_str); - } - } -done: - free(id); - free(id_str); - return err; -} - -static const struct got_error * -delete_missing_refs(struct got_pathlist_head *their_refs, - struct got_pathlist_head *their_symrefs, - const struct got_remote_repo *remote, - int verbosity, struct got_repository *repo) -{ - const struct got_error *err = NULL, *unlock_err; - struct got_reflist_head my_refs; - struct got_reflist_entry *re; - struct got_pathlist_entry *pe; - char *remote_namespace = NULL; - char *local_refname = NULL; - - TAILQ_INIT(&my_refs); - - if (asprintf(&remote_namespace, "refs/remotes/%s/", remote->name) - == -1) - return got_error_from_errno("asprintf"); - - err = got_ref_list(&my_refs, repo, NULL, got_ref_cmp_by_name, NULL); - if (err) - goto done; - - TAILQ_FOREACH(re, &my_refs, entry) { - const char *refname = got_ref_get_name(re->ref); - const char *their_refname; - - if (remote->mirror_references) { - their_refname = refname; - } else { - if (strncmp(refname, remote_namespace, - strlen(remote_namespace)) == 0) { - if (strcmp(refname + strlen(remote_namespace), - GOT_REF_HEAD) == 0) - continue; - if (asprintf(&local_refname, "refs/heads/%s", - refname + strlen(remote_namespace)) == -1) { - err = got_error_from_errno("asprintf"); - goto done; - } - } else if (strncmp(refname, "refs/tags/", 10) != 0) - continue; - - their_refname = local_refname; - } - - TAILQ_FOREACH(pe, their_refs, entry) { - if (strcmp(their_refname, pe->path) == 0) - break; - } - if (pe != NULL) - continue; - - TAILQ_FOREACH(pe, their_symrefs, entry) { - if (strcmp(their_refname, pe->path) == 0) - break; - } - if (pe != NULL) - continue; - - err = delete_missing_ref(re->ref, verbosity, repo); - if (err) - break; - - if (local_refname) { - struct got_reference *ref; - err = got_ref_open(&ref, repo, local_refname, 1); - if (err) { - if (err->code != GOT_ERR_NOT_REF) - break; - free(local_refname); - local_refname = NULL; - continue; - } - err = delete_missing_ref(ref, verbosity, repo); - if (err) - break; - unlock_err = got_ref_unlock(ref); - got_ref_close(ref); - if (unlock_err && err == NULL) { - err = unlock_err; - break; - } - - free(local_refname); - local_refname = NULL; - } - } -done: - got_ref_list_free(&my_refs); - free(remote_namespace); - free(local_refname); - return err; -} - -static const struct got_error * update_wanted_ref(const char *refname, struct got_object_id *id, const char *remote_repo_name, int verbosity, struct got_repository *repo) { @@ -2256,33 +2031,6 @@ done: return err; } -static const struct got_error * -delete_refs_for_remote(struct got_repository *repo, const char *remote_name) -{ - const struct got_error *err = NULL; - struct got_reflist_head refs; - struct got_reflist_entry *re; - char *prefix; - - TAILQ_INIT(&refs); - - if (asprintf(&prefix, "refs/remotes/%s", remote_name) == -1) { - err = got_error_from_errno("asprintf"); - goto done; - } - err = got_ref_list(&refs, repo, prefix, got_ref_cmp_by_name, NULL); - if (err) - goto done; - - TAILQ_FOREACH(re, &refs, entry) - delete_ref(repo, re->ref); -done: - got_ref_list_free(&refs); - return err; -} - - - __dead static void usage_checkout(void) { @@ -2805,46 +2553,6 @@ update_progress(void *arg, unsigned char status, const } 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), 0, 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; @@ -6399,447 +6107,6 @@ done: } __dead static void -usage_branch(void) -{ - fprintf(stderr, "usage: %s branch [-lnt] [-c commit] [-d name] " - "[-r repository-path] [name]\n", getprogname()); - exit(1); -} - -static const struct got_error * -list_branch(struct got_repository *repo, struct got_worktree *worktree, - struct got_reference *ref) -{ - const struct got_error *err = NULL; - const char *refname, *marker = " "; - char *refstr; - - refname = got_ref_get_name(ref); - if (worktree && strcmp(refname, - got_worktree_get_head_ref_name(worktree)) == 0) { - struct got_object_id *id = NULL; - - err = got_ref_resolve(&id, repo, ref); - if (err) - return err; - if (got_object_id_cmp(id, - got_worktree_get_base_commit_id(worktree)) == 0) - marker = "* "; - else - marker = "~ "; - free(id); - } - - if (strncmp(refname, "refs/heads/", 11) == 0) - refname += 11; - if (strncmp(refname, "refs/got/worktree/", 18) == 0) - refname += 18; - if (strncmp(refname, "refs/remotes/", 13) == 0) - refname += 13; - - refstr = got_ref_to_str(ref); - if (refstr == NULL) - return got_error_from_errno("got_ref_to_str"); - - printf("%s%s: %s\n", marker, refname, refstr); - free(refstr); - return NULL; -} - -static const struct got_error * -show_current_branch(struct got_repository *repo, struct got_worktree *worktree) -{ - const char *refname; - - if (worktree == NULL) - return got_error(GOT_ERR_NOT_WORKTREE); - - refname = got_worktree_get_head_ref_name(worktree); - - if (strncmp(refname, "refs/heads/", 11) == 0) - refname += 11; - if (strncmp(refname, "refs/got/worktree/", 18) == 0) - refname += 18; - - printf("%s\n", refname); - - return NULL; -} - -static const struct got_error * -list_branches(struct got_repository *repo, struct got_worktree *worktree, - int sort_by_time) -{ - static const struct got_error *err = NULL; - struct got_reflist_head refs; - struct got_reflist_entry *re; - struct got_reference *temp_ref = NULL; - int rebase_in_progress, histedit_in_progress; - - TAILQ_INIT(&refs); - - if (worktree) { - err = got_worktree_rebase_in_progress(&rebase_in_progress, - worktree); - if (err) - return err; - - err = got_worktree_histedit_in_progress(&histedit_in_progress, - worktree); - if (err) - return err; - - if (rebase_in_progress || histedit_in_progress) { - err = got_ref_open(&temp_ref, repo, - got_worktree_get_head_ref_name(worktree), 0); - if (err) - return err; - list_branch(repo, worktree, temp_ref); - got_ref_close(temp_ref); - } - } - - err = got_ref_list(&refs, repo, "refs/heads", sort_by_time ? - got_ref_cmp_by_commit_timestamp_descending : got_ref_cmp_by_name, - repo); - if (err) - return err; - - TAILQ_FOREACH(re, &refs, entry) - list_branch(repo, worktree, re->ref); - - got_ref_list_free(&refs); - - err = got_ref_list(&refs, repo, "refs/remotes", sort_by_time ? - got_ref_cmp_by_commit_timestamp_descending : got_ref_cmp_by_name, - repo); - if (err) - return err; - - TAILQ_FOREACH(re, &refs, entry) - list_branch(repo, worktree, re->ref); - - got_ref_list_free(&refs); - - return NULL; -} - -static const struct got_error * -delete_branch(struct got_repository *repo, struct got_worktree *worktree, - const char *branch_name) -{ - const struct got_error *err = NULL; - struct got_reference *ref = NULL; - char *refname, *remote_refname = NULL; - - if (strncmp(branch_name, "refs/", 5) == 0) - branch_name += 5; - if (strncmp(branch_name, "heads/", 6) == 0) - branch_name += 6; - else if (strncmp(branch_name, "remotes/", 8) == 0) - branch_name += 8; - - if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) - return got_error_from_errno("asprintf"); - - if (asprintf(&remote_refname, "refs/remotes/%s", - branch_name) == -1) { - err = got_error_from_errno("asprintf"); - goto done; - } - - err = got_ref_open(&ref, repo, refname, 0); - if (err) { - const struct got_error *err2; - if (err->code != GOT_ERR_NOT_REF) - goto done; - /* - * Keep 'err' intact such that if neither branch exists - * we report "refs/heads" rather than "refs/remotes" in - * our error message. - */ - err2 = got_ref_open(&ref, repo, remote_refname, 0); - if (err2) - goto done; - err = NULL; - } - - if (worktree && - strcmp(got_worktree_get_head_ref_name(worktree), - got_ref_get_name(ref)) == 0) { - err = got_error_msg(GOT_ERR_SAME_BRANCH, - "will not delete this work tree's current branch"); - goto done; - } - - err = delete_ref(repo, ref); -done: - if (ref) - got_ref_close(ref); - free(refname); - free(remote_refname); - return err; -} - -static const struct got_error * -add_branch(struct got_repository *repo, const char *branch_name, - struct got_object_id *base_commit_id) -{ - const struct got_error *err = NULL; - struct got_reference *ref = NULL; - char *refname = NULL; - - /* - * Don't let the user create a branch name with a leading '-'. - * While technically a valid reference name, this case is usually - * an unintended typo. - */ - if (branch_name[0] == '-') - return got_error_path(branch_name, GOT_ERR_REF_NAME_MINUS); - - if (strncmp(branch_name, "refs/heads/", 11) == 0) - branch_name += 11; - - 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, base_commit_id); - if (err) - goto done; - - err = got_ref_write(ref, repo); -done: - if (ref) - got_ref_close(ref); - 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, do_show = 0, do_update = 1, sort_by_time = 0; - const char *delref = NULL, *commit_id_arg = NULL; - struct got_reference *ref = NULL; - struct got_pathlist_head paths; - struct got_object_id *commit_id = NULL; - char *commit_id_str = NULL; - int *pack_fds = NULL; - - TAILQ_INIT(&paths); - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec " - "sendfd unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "c:d:lnr:t")) != -1) { - switch (ch) { - case 'c': - commit_id_arg = optarg; - break; - case 'd': - delref = optarg; - break; - case 'l': - do_list = 1; - break; - case 'n': - do_update = 0; - break; - case 'r': - repo_path = realpath(optarg, NULL); - if (repo_path == NULL) - return got_error_from_errno2("realpath", - optarg); - got_path_strip_trailing_slashes(repo_path); - break; - case 't': - sort_by_time = 1; - break; - default: - usage_branch(); - /* NOTREACHED */ - } - } - - if (do_list && delref) - option_conflict('l', 'd'); - if (sort_by_time && !do_list) - errx(1, "-t option requires -l option"); - - argc -= optind; - argv += optind; - - if (!do_list && !delref && argc == 0) - do_show = 1; - - if ((do_list || delref || do_show) && commit_id_arg != NULL) - errx(1, "-c option can only be used when creating a branch"); - - if (do_list || delref) { - if (argc > 0) - usage_branch(); - } else if (!do_show && argc != 1) - usage_branch(); - - cwd = getcwd(NULL, 0); - if (cwd == NULL) { - error = got_error_from_errno("getcwd"); - goto done; - } - - error = got_repo_pack_fds_open(&pack_fds); - if (error != NULL) - 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, NULL, pack_fds); - if (error != NULL) - goto done; - -#ifndef PROFILE - if (do_list || do_show) { - /* Remove "cpath" promise. */ - if (pledge("stdio rpath wpath flock proc exec sendfd unveil", - NULL) == -1) - err(1, "pledge"); - } -#endif - - error = apply_unveil(got_repo_get_path(repo), do_list, - worktree ? got_worktree_get_root_path(worktree) : NULL); - if (error) - goto done; - - if (do_show) - error = show_current_branch(repo, worktree); - else if (do_list) - error = list_branches(repo, worktree, sort_by_time); - else if (delref) - error = delete_branch(repo, worktree, delref); - else { - struct got_reflist_head refs; - TAILQ_INIT(&refs); - error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, - NULL); - if (error) - goto done; - if (commit_id_arg == NULL) - commit_id_arg = worktree ? - got_worktree_get_head_ref_name(worktree) : - GOT_REF_HEAD; - error = got_repo_match_object_id(&commit_id, NULL, - commit_id_arg, GOT_OBJ_TYPE_COMMIT, &refs, repo); - got_ref_list_free(&refs); - if (error) - goto done; - error = add_branch(repo, argv[0], commit_id); - if (error) - goto done; - if (worktree && do_update) { - struct got_update_progress_arg upa; - char *branch_refname = NULL; - - error = got_object_id_str(&commit_id_str, commit_id); - if (error) - goto done; - error = get_worktree_paths_from_argv(&paths, 0, NULL, - worktree); - if (error) - goto done; - if (asprintf(&branch_refname, "refs/heads/%s", argv[0]) - == -1) { - error = got_error_from_errno("asprintf"); - goto done; - } - error = got_ref_open(&ref, repo, branch_refname, 0); - free(branch_refname); - if (error) - goto done; - error = switch_head_ref(ref, commit_id, worktree, - repo); - if (error) - goto done; - error = got_worktree_set_base_commit_id(worktree, repo, - commit_id); - if (error) - goto done; - memset(&upa, 0, sizeof(upa)); - error = got_worktree_checkout_files(worktree, &paths, - repo, update_progress, &upa, check_cancelled, - NULL); - if (error) - goto done; - if (upa.did_something) { - printf("Updated to %s: %s\n", - got_worktree_get_head_ref_name(worktree), - commit_id_str); - } - print_update_progress_stats(&upa); - } - } -done: - if (ref) - got_ref_close(ref); - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (worktree) - got_worktree_close(worktree); - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - free(cwd); - free(repo_path); - free(commit_id); - free(commit_id_str); - got_pathlist_free(&paths, GOT_PATHLIST_FREE_PATH); - return error; -} - - -__dead static void usage_tag(void) { fprintf(stderr, "usage: %s tag [-lVv] [-c commit] [-m message] " @@ -9132,4414 +8399,514 @@ done: return error; } -__dead static void -usage_send(void) +/* + * Print and if delete is set delete all ref_prefix references. + * If wanted_ref is not NULL, only print or delete this reference. + */ +static const struct got_error * +process_logmsg_refs(const char *ref_prefix, size_t prefix_len, + const char *wanted_ref, int delete, struct got_worktree *worktree, + struct got_repository *repo) { - fprintf(stderr, "usage: %s send [-afqTv] [-b branch] [-d branch] " - "[-r repository-path] [-t tag] [remote-repository]\n", - getprogname()); - exit(1); -} + const struct got_error *err; + struct got_pathlist_head paths; + struct got_reflist_head refs; + struct got_reflist_entry *re; + struct got_reflist_object_id_map *refs_idmap = NULL; + struct got_commit_object *commit = NULL; + struct got_object_id *id = NULL; + const char *header_prefix; + char *uuidstr = NULL; + int found = 0; -static void -print_load_info(int print_colored, int print_found, int print_trees, - int ncolored, int nfound, int ntrees) -{ - if (print_colored) { - printf("%d commit%s colored", ncolored, - ncolored == 1 ? "" : "s"); - } - if (print_found) { - printf("%s%d object%s found", - ncolored > 0 ? "; " : "", - nfound, nfound == 1 ? "" : "s"); - } - if (print_trees) { - printf("; %d tree%s scanned", ntrees, - ntrees == 1 ? "" : "s"); - } -} + TAILQ_INIT(&refs); + TAILQ_INIT(&paths); -struct got_send_progress_arg { - char last_scaled_packsize[FMT_SCALED_STRSIZE]; - int verbosity; - int last_ncolored; - int last_nfound; - int last_ntrees; - int loading_done; - int last_ncommits; - int last_nobj_total; - int last_p_deltify; - int last_p_written; - int last_p_sent; - int printed_something; - int sent_something; - struct got_pathlist_head *delete_branches; -}; + err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, repo); + if (err) + goto done; -static const struct got_error * -send_progress(void *arg, int ncolored, int nfound, int ntrees, - off_t packfile_size, int ncommits, int nobj_total, int nobj_deltify, - int nobj_written, off_t bytes_sent, const char *refname, - const char *errmsg, int success) -{ - struct got_send_progress_arg *a = arg; - char scaled_packsize[FMT_SCALED_STRSIZE]; - char scaled_sent[FMT_SCALED_STRSIZE]; - int p_deltify = 0, p_written = 0, p_sent = 0; - int print_colored = 0, print_found = 0, print_trees = 0; - int print_searching = 0, print_total = 0; - int print_deltify = 0, print_written = 0, print_sent = 0; + err = got_reflist_object_id_map_create(&refs_idmap, &refs, repo); + if (err) + goto done; - if (a->verbosity < 0) - return NULL; + if (worktree != NULL) { + err = got_worktree_get_uuid(&uuidstr, worktree); + if (err) + goto done; + } - if (refname) { - const char *status = success ? "accepted" : "rejected"; + if (wanted_ref) { + if (strncmp(wanted_ref, "refs/heads/", 11) == 0) + wanted_ref += 11; + } - if (success) { - struct got_pathlist_entry *pe; - TAILQ_FOREACH(pe, a->delete_branches, entry) { - const char *branchname = pe->path; - if (got_path_cmp(branchname, refname, - strlen(branchname), strlen(refname)) == 0) { - status = "deleted"; - a->sent_something = 1; - break; - } - } - } + if (strcmp(ref_prefix, GOT_WORKTREE_BACKOUT_REF_PREFIX) == 0) + header_prefix = "backout"; + else + header_prefix = "cherrypick"; - if (a->printed_something) - putchar('\n'); - printf("Server has %s %s", status, refname); - if (errmsg) - printf(": %s", errmsg); - a->printed_something = 1; - return NULL; - } + TAILQ_FOREACH(re, &refs, entry) { + const char *refname, *wt; - if (a->last_ncolored != ncolored) { - print_colored = 1; - a->last_ncolored = ncolored; - } + refname = got_ref_get_name(re->ref); - if (a->last_nfound != nfound) { - print_colored = 1; - print_found = 1; - a->last_nfound = nfound; - } + err = check_cancelled(NULL); + if (err) + goto done; - if (a->last_ntrees != ntrees) { - print_colored = 1; - print_found = 1; - print_trees = 1; - a->last_ntrees = ntrees; - } + if (strncmp(refname, ref_prefix, prefix_len) == 0) + refname += prefix_len + 1; /* skip '-' delimiter */ + else + continue; - if ((print_colored || print_found || print_trees) && - !a->loading_done) { - printf("\r"); - print_load_info(print_colored, print_found, print_trees, - ncolored, nfound, ntrees); - a->printed_something = 1; - fflush(stdout); - return NULL; - } else if (!a->loading_done) { - printf("\r"); - print_load_info(1, 1, 1, ncolored, nfound, ntrees); - printf("\n"); - a->loading_done = 1; - } + wt = refname; - if (fmt_scaled(packfile_size, scaled_packsize) == -1) - return got_error_from_errno("fmt_scaled"); - if (fmt_scaled(bytes_sent, scaled_sent) == -1) - return got_error_from_errno("fmt_scaled"); + if (worktree == NULL || strncmp(refname, uuidstr, + GOT_WORKTREE_UUID_STRLEN) == 0) + refname += GOT_WORKTREE_UUID_STRLEN + 1; /* skip '-' */ + else + continue; - if (a->last_ncommits != ncommits) { - print_searching = 1; - a->last_ncommits = ncommits; - } + err = got_repo_match_object_id(&id, NULL, refname, + GOT_OBJ_TYPE_COMMIT, NULL, repo); + if (err) + goto done; - if (a->last_nobj_total != nobj_total) { - print_searching = 1; - print_total = 1; - a->last_nobj_total = nobj_total; - } + err = got_object_open_as_commit(&commit, repo, id); + if (err) + goto done; - if (packfile_size > 0 && (a->last_scaled_packsize[0] == '\0' || - strcmp(scaled_packsize, a->last_scaled_packsize)) != 0) { - if (strlcpy(a->last_scaled_packsize, scaled_packsize, - FMT_SCALED_STRSIZE) >= FMT_SCALED_STRSIZE) - return got_error(GOT_ERR_NO_SPACE); - } + if (wanted_ref) + found = strncmp(wanted_ref, refname, + strlen(wanted_ref)) == 0; + if (wanted_ref && !found) { + struct got_reflist_head *ci_refs; - if (nobj_deltify > 0 || nobj_written > 0) { - if (nobj_deltify > 0) { - p_deltify = (nobj_deltify * 100) / nobj_total; - if (p_deltify != a->last_p_deltify) { - a->last_p_deltify = p_deltify; - print_searching = 1; - print_total = 1; - print_deltify = 1; + ci_refs = got_reflist_object_id_map_lookup(refs_idmap, + id); + + if (ci_refs) { + char *refs_str = NULL; + char const *r = NULL; + + err = build_refs_str(&refs_str, ci_refs, id, + repo, 1); + if (err) + goto done; + + r = refs_str; + while (r) { + if (strncmp(r, wanted_ref, + strlen(wanted_ref)) == 0) { + found = 1; + break; + } + r = strchr(r, ' '); + if (r) + ++r; + } + free(refs_str); } } - if (nobj_written > 0) { - p_written = (nobj_written * 100) / nobj_total; - if (p_written != a->last_p_written) { - a->last_p_written = p_written; - print_searching = 1; - print_total = 1; - print_deltify = 1; - print_written = 1; + + if (wanted_ref == NULL || found) { + if (delete) { + err = got_ref_delete(re->ref, repo); + if (err) + goto done; + printf("Deleted: "); + err = print_commit_oneline(commit, id, repo, + refs_idmap); + } else { + /* + * Print paths modified by commit to help + * associate commits with worktree changes. + */ + err = get_changed_paths(&paths, commit, + repo, NULL); + if (err) + goto done; + + err = print_commit(commit, id, repo, NULL, + &paths, NULL, 0, 0, refs_idmap, NULL, + header_prefix); + got_pathlist_free(&paths, + GOT_PATHLIST_FREE_ALL); + + if (worktree == NULL) + printf("work tree: %.*s\n\n", + GOT_WORKTREE_UUID_STRLEN, wt); } + if (err || found) + goto done; } - } - if (bytes_sent > 0) { - p_sent = (bytes_sent * 100) / packfile_size; - if (p_sent != a->last_p_sent) { - a->last_p_sent = p_sent; - print_searching = 1; - print_total = 1; - print_deltify = 1; - print_written = 1; - print_sent = 1; - } - a->sent_something = 1; + got_object_commit_close(commit); + commit = NULL; + free(id); + id = NULL; } - if (print_searching || print_total || print_deltify || print_written || - print_sent) - printf("\r"); - if (print_searching) - printf("packing %d reference%s", ncommits, - ncommits == 1 ? "" : "s"); - if (print_total) - printf("; %d object%s", nobj_total, - nobj_total == 1 ? "" : "s"); - if (print_deltify) - printf("; deltify: %d%%", p_deltify); - if (print_sent) - printf("; uploading pack: %*s %d%%", FMT_SCALED_STRSIZE - 2, - scaled_packsize, p_sent); - else if (print_written) - printf("; writing pack: %*s %d%%", FMT_SCALED_STRSIZE - 2, - scaled_packsize, p_written); - if (print_searching || print_total || print_deltify || - print_written || print_sent) { - a->printed_something = 1; - fflush(stdout); - } - return NULL; -} - -static const struct got_error * -cmd_send(int argc, char *argv[]) -{ - const struct got_error *error = NULL; - char *cwd = NULL, *repo_path = NULL; - const char *remote_name; - char *proto = NULL, *host = NULL, *port = NULL; - char *repo_name = NULL, *server_path = NULL; - const struct got_remote_repo *remotes, *remote = NULL; - int nremotes, nbranches = 0, ndelete_branches = 0; - struct got_repository *repo = NULL; - struct got_worktree *worktree = NULL; - const struct got_gotconfig *repo_conf = NULL, *worktree_conf = NULL; - struct got_pathlist_head branches; - struct got_pathlist_head tags; - struct got_reflist_head all_branches; - struct got_reflist_head all_tags; - struct got_pathlist_head delete_args; - struct got_pathlist_head delete_branches; - struct got_reflist_entry *re; - struct got_pathlist_entry *pe; - int i, ch, sendfd = -1, sendstatus; - pid_t sendpid = -1; - struct got_send_progress_arg spa; - int verbosity = 0, overwrite_refs = 0; - int send_all_branches = 0, send_all_tags = 0; - struct got_reference *ref = NULL; - int *pack_fds = NULL; - - TAILQ_INIT(&branches); - TAILQ_INIT(&tags); - TAILQ_INIT(&all_branches); - TAILQ_INIT(&all_tags); - TAILQ_INIT(&delete_args); - TAILQ_INIT(&delete_branches); - - while ((ch = getopt(argc, argv, "ab:d:fqr:Tt:v")) != -1) { - switch (ch) { - case 'a': - send_all_branches = 1; - break; - case 'b': - error = got_pathlist_append(&branches, optarg, NULL); - if (error) - return error; - nbranches++; - break; - case 'd': - error = got_pathlist_append(&delete_args, optarg, NULL); - if (error) - return error; - break; - case 'f': - overwrite_refs = 1; - break; - case 'q': - verbosity = -1; - break; - case 'r': - repo_path = realpath(optarg, NULL); - if (repo_path == NULL) - return got_error_from_errno2("realpath", - optarg); - got_path_strip_trailing_slashes(repo_path); - break; - case 'T': - send_all_tags = 1; - break; - case 't': - error = got_pathlist_append(&tags, optarg, NULL); - if (error) - return error; - break; - case 'v': - if (verbosity < 0) - verbosity = 0; - else if (verbosity < 3) - verbosity++; - break; - default: - usage_send(); - /* NOTREACHED */ - } - } - argc -= optind; - argv += optind; - - if (send_all_branches && !TAILQ_EMPTY(&branches)) - option_conflict('a', 'b'); - if (send_all_tags && !TAILQ_EMPTY(&tags)) - option_conflict('T', 't'); - - - if (argc == 0) - remote_name = GOT_SEND_DEFAULT_REMOTE_NAME; - else if (argc == 1) - remote_name = argv[0]; - else - usage_send(); - - cwd = getcwd(NULL, 0); - if (cwd == NULL) { - error = got_error_from_errno("getcwd"); - goto done; - } - - error = got_repo_pack_fds_open(&pack_fds); - if (error != NULL) - 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, NULL, pack_fds); - if (error) - goto done; - - if (worktree) { - worktree_conf = got_worktree_get_gotconfig(worktree); - if (worktree_conf) { - got_gotconfig_get_remotes(&nremotes, &remotes, - worktree_conf); - for (i = 0; i < nremotes; i++) { - if (strcmp(remotes[i].name, remote_name) == 0) { - remote = &remotes[i]; - break; - } - } - } - } - if (remote == NULL) { - repo_conf = got_repo_get_gotconfig(repo); - if (repo_conf) { - got_gotconfig_get_remotes(&nremotes, &remotes, - repo_conf); - for (i = 0; i < nremotes; i++) { - if (strcmp(remotes[i].name, remote_name) == 0) { - remote = &remotes[i]; - break; - } - } - } - } - if (remote == NULL) { - got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo); - for (i = 0; i < nremotes; i++) { - if (strcmp(remotes[i].name, remote_name) == 0) { - remote = &remotes[i]; - break; - } - } - } - if (remote == NULL) { - error = got_error_path(remote_name, GOT_ERR_NO_REMOTE); - goto done; - } - - error = got_dial_parse_uri(&proto, &host, &port, &server_path, - &repo_name, remote->send_url); - if (error) - goto done; - - if (strcmp(proto, "git") == 0) { -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec " - "sendfd dns inet unveil", NULL) == -1) - err(1, "pledge"); -#endif - } else if (strcmp(proto, "git+ssh") == 0 || - strcmp(proto, "ssh") == 0) { -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec " - "sendfd unveil", NULL) == -1) - err(1, "pledge"); -#endif - } else if (strcmp(proto, "http") == 0 || - strcmp(proto, "git+http") == 0) { - error = got_error_path(proto, GOT_ERR_NOT_IMPL); - goto done; - } else { - error = got_error_path(proto, GOT_ERR_BAD_PROTO); - goto done; - } - - error = got_dial_apply_unveil(proto); - if (error) - goto done; - - error = apply_unveil(got_repo_get_path(repo), 0, NULL); - if (error) - goto done; - - if (send_all_branches) { - error = got_ref_list(&all_branches, repo, "refs/heads", - got_ref_cmp_by_name, NULL); - if (error) - goto done; - TAILQ_FOREACH(re, &all_branches, entry) { - const char *branchname = got_ref_get_name(re->ref); - error = got_pathlist_append(&branches, - branchname, NULL); - if (error) - goto done; - nbranches++; - } - } else if (nbranches == 0) { - for (i = 0; i < remote->nsend_branches; i++) { - error = got_pathlist_append(&branches, - remote->send_branches[i], NULL); - if (error) - goto done; - } - } - - if (send_all_tags) { - error = got_ref_list(&all_tags, repo, "refs/tags", - got_ref_cmp_by_name, NULL); - if (error) - goto done; - TAILQ_FOREACH(re, &all_tags, entry) { - const char *tagname = got_ref_get_name(re->ref); - error = got_pathlist_append(&tags, - tagname, NULL); - if (error) - goto done; - } - } - - /* - * To prevent accidents only branches in refs/heads/ can be deleted - * with 'got send -d'. - * Deleting anything else requires local repository access or Git. - */ - TAILQ_FOREACH(pe, &delete_args, entry) { - const char *branchname = pe->path; - char *s; - struct got_pathlist_entry *new; - if (strncmp(branchname, "refs/heads/", 11) == 0) { - s = strdup(branchname); - if (s == NULL) { - error = got_error_from_errno("strdup"); - goto done; - } - } else { - if (asprintf(&s, "refs/heads/%s", branchname) == -1) { - error = got_error_from_errno("asprintf"); - goto done; - } - } - error = got_pathlist_insert(&new, &delete_branches, s, NULL); - if (error || new == NULL /* duplicate */) - free(s); - if (error) - goto done; - ndelete_branches++; - } - - if (nbranches == 0 && ndelete_branches == 0) { - struct got_reference *head_ref; - if (worktree) - error = got_ref_open(&head_ref, repo, - got_worktree_get_head_ref_name(worktree), 0); - else - error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); - if (error) - goto done; - if (got_ref_is_symbolic(head_ref)) { - error = got_ref_resolve_symbolic(&ref, repo, head_ref); - got_ref_close(head_ref); - if (error) - goto done; - } else - ref = head_ref; - error = got_pathlist_append(&branches, got_ref_get_name(ref), - NULL); - if (error) - goto done; - nbranches++; - } - - if (verbosity >= 0) { - printf("Connecting to \"%s\" %s://%s%s%s%s%s\n", - remote->name, proto, host, - port ? ":" : "", port ? port : "", - *server_path == '/' ? "" : "/", server_path); - } - - error = got_send_connect(&sendpid, &sendfd, proto, host, port, - server_path, verbosity); - if (error) - goto done; - - memset(&spa, 0, sizeof(spa)); - spa.last_scaled_packsize[0] = '\0'; - spa.last_p_deltify = -1; - spa.last_p_written = -1; - spa.verbosity = verbosity; - spa.delete_branches = &delete_branches; - error = got_send_pack(remote_name, &branches, &tags, &delete_branches, - verbosity, overwrite_refs, sendfd, repo, send_progress, &spa, - check_cancelled, NULL); - if (spa.printed_something) - putchar('\n'); - if (error) - goto done; - if (!spa.sent_something && verbosity >= 0) - printf("Already up-to-date\n"); -done: - if (sendpid > 0) { - if (kill(sendpid, SIGTERM) == -1) - error = got_error_from_errno("kill"); - if (waitpid(sendpid, &sendstatus, 0) == -1 && error == NULL) - error = got_error_from_errno("waitpid"); - } - if (sendfd != -1 && close(sendfd) == -1 && error == NULL) - error = got_error_from_errno("close"); - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (worktree) - got_worktree_close(worktree); - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - if (ref) - got_ref_close(ref); - got_pathlist_free(&branches, GOT_PATHLIST_FREE_NONE); - got_pathlist_free(&tags, GOT_PATHLIST_FREE_NONE); - got_ref_list_free(&all_branches); - got_ref_list_free(&all_tags); - got_pathlist_free(&delete_args, GOT_PATHLIST_FREE_NONE); - got_pathlist_free(&delete_branches, GOT_PATHLIST_FREE_PATH); - free(cwd); - free(repo_path); - free(proto); - free(host); - free(port); - free(server_path); - free(repo_name); - return error; -} - -/* - * Print and if delete is set delete all ref_prefix references. - * If wanted_ref is not NULL, only print or delete this reference. - */ -static const struct got_error * -process_logmsg_refs(const char *ref_prefix, size_t prefix_len, - const char *wanted_ref, int delete, struct got_worktree *worktree, - struct got_repository *repo) -{ - const struct got_error *err; - struct got_pathlist_head paths; - struct got_reflist_head refs; - struct got_reflist_entry *re; - struct got_reflist_object_id_map *refs_idmap = NULL; - struct got_commit_object *commit = NULL; - struct got_object_id *id = NULL; - const char *header_prefix; - char *uuidstr = NULL; - int found = 0; - - TAILQ_INIT(&refs); - TAILQ_INIT(&paths); - - err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, repo); - if (err) - goto done; - - err = got_reflist_object_id_map_create(&refs_idmap, &refs, repo); - if (err) - goto done; - - if (worktree != NULL) { - err = got_worktree_get_uuid(&uuidstr, worktree); - if (err) - goto done; - } - - if (wanted_ref) { - if (strncmp(wanted_ref, "refs/heads/", 11) == 0) - wanted_ref += 11; - } - - if (strcmp(ref_prefix, GOT_WORKTREE_BACKOUT_REF_PREFIX) == 0) - header_prefix = "backout"; - else - header_prefix = "cherrypick"; - - TAILQ_FOREACH(re, &refs, entry) { - const char *refname, *wt; - - refname = got_ref_get_name(re->ref); - - err = check_cancelled(NULL); - if (err) - goto done; - - if (strncmp(refname, ref_prefix, prefix_len) == 0) - refname += prefix_len + 1; /* skip '-' delimiter */ - else - continue; - - wt = refname; - - if (worktree == NULL || strncmp(refname, uuidstr, - GOT_WORKTREE_UUID_STRLEN) == 0) - refname += GOT_WORKTREE_UUID_STRLEN + 1; /* skip '-' */ - else - continue; - - err = got_repo_match_object_id(&id, NULL, refname, - GOT_OBJ_TYPE_COMMIT, NULL, repo); - if (err) - goto done; - - err = got_object_open_as_commit(&commit, repo, id); - if (err) - goto done; - - if (wanted_ref) - found = strncmp(wanted_ref, refname, - strlen(wanted_ref)) == 0; - if (wanted_ref && !found) { - struct got_reflist_head *ci_refs; - - ci_refs = got_reflist_object_id_map_lookup(refs_idmap, - id); - - if (ci_refs) { - char *refs_str = NULL; - char const *r = NULL; - - err = build_refs_str(&refs_str, ci_refs, id, - repo, 1); - if (err) - goto done; - - r = refs_str; - while (r) { - if (strncmp(r, wanted_ref, - strlen(wanted_ref)) == 0) { - found = 1; - break; - } - r = strchr(r, ' '); - if (r) - ++r; - } - free(refs_str); - } - } - - if (wanted_ref == NULL || found) { - if (delete) { - err = got_ref_delete(re->ref, repo); - if (err) - goto done; - printf("Deleted: "); - err = print_commit_oneline(commit, id, repo, - refs_idmap); - } else { - /* - * Print paths modified by commit to help - * associate commits with worktree changes. - */ - err = get_changed_paths(&paths, commit, - repo, NULL); - if (err) - goto done; - - err = print_commit(commit, id, repo, NULL, - &paths, NULL, 0, 0, refs_idmap, NULL, - header_prefix); - got_pathlist_free(&paths, - GOT_PATHLIST_FREE_ALL); - - if (worktree == NULL) - printf("work tree: %.*s\n\n", - GOT_WORKTREE_UUID_STRLEN, wt); - } - if (err || found) - goto done; - } - - got_object_commit_close(commit); - commit = NULL; - free(id); - id = NULL; - } - - if (wanted_ref != NULL && !found) - err = got_error_fmt(GOT_ERR_NOT_REF, "%s", wanted_ref); - -done: - free(id); - free(uuidstr); - got_ref_list_free(&refs); - got_pathlist_free(&paths, GOT_PATHLIST_FREE_ALL); - if (refs_idmap) - got_reflist_object_id_map_free(refs_idmap); - if (commit) - got_object_commit_close(commit); - return err; -} - -/* - * Create new temp "logmsg" ref of the backed-out or cherrypicked commit - * identified by id for log messages to prepopulate the editor on commit. - */ -static const struct got_error * -logmsg_ref(struct got_object_id *id, const char *prefix, - struct got_worktree *worktree, struct got_repository *repo) -{ - const struct got_error *err = NULL; - char *idstr, *ref = NULL, *refname = NULL; - int histedit_in_progress; - int rebase_in_progress, merge_in_progress; - - /* - * Silenty refuse to create merge reference if any histedit, merge, - * or rebase operation is in progress. - */ - err = got_worktree_histedit_in_progress(&histedit_in_progress, - worktree); - if (err) - return err; - if (histedit_in_progress) - return NULL; - - err = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); - if (err) - return err; - if (rebase_in_progress) - return NULL; - - err = got_worktree_merge_in_progress(&merge_in_progress, worktree, - repo); - if (err) - return err; - if (merge_in_progress) - return NULL; - - err = got_object_id_str(&idstr, id); - if (err) - return err; - - err = got_worktree_get_logmsg_ref_name(&refname, worktree, prefix); - if (err) - goto done; - - if (asprintf(&ref, "%s-%s", refname, idstr) == -1) { - err = got_error_from_errno("asprintf"); - goto done; - } - - err = create_ref(ref, got_worktree_get_base_commit_id(worktree), - -1, repo); -done: - free(ref); - free(idstr); - free(refname); - return err; -} - -__dead static void -usage_cherrypick(void) -{ - fprintf(stderr, "usage: %s cherrypick [-lX] [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; - int ch, list_refs = 0, remove_refs = 0; - struct got_update_progress_arg upa; - int *pack_fds = NULL; - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " - "unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "lX")) != -1) { - switch (ch) { - case 'l': - list_refs = 1; - break; - case 'X': - remove_refs = 1; - break; - default: - usage_cherrypick(); - /* NOTREACHED */ - } - } - - argc -= optind; - argv += optind; - - if (list_refs || remove_refs) { - if (argc != 0 && argc != 1) - usage_cherrypick(); - } else if (argc != 1) - usage_cherrypick(); - if (list_refs && remove_refs) - option_conflict('l', 'X'); - - cwd = getcwd(NULL, 0); - if (cwd == NULL) { - error = got_error_from_errno("getcwd"); - goto done; - } - - error = got_repo_pack_fds_open(&pack_fds); - if (error != NULL) - goto done; - - error = got_worktree_open(&worktree, cwd); - if (error) { - if (list_refs || remove_refs) { - if (error->code != GOT_ERR_NOT_WORKTREE) - goto done; - } else { - if (error->code == GOT_ERR_NOT_WORKTREE) - error = wrap_not_worktree_error(error, - "cherrypick", cwd); - goto done; - } - } - - error = got_repo_open(&repo, - worktree ? got_worktree_get_repo_path(worktree) : cwd, - NULL, pack_fds); - if (error != NULL) - goto done; - - error = apply_unveil(got_repo_get_path(repo), 0, - worktree ? got_worktree_get_root_path(worktree) : NULL); - if (error) - goto done; - - if (list_refs || remove_refs) { - error = process_logmsg_refs(GOT_WORKTREE_CHERRYPICK_REF_PREFIX, - GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN, - argc == 1 ? argv[0] : NULL, remove_refs, worktree, repo); - goto done; - } - - error = got_repo_match_object_id(&commit_id, NULL, argv[0], - GOT_OBJ_TYPE_COMMIT, NULL, repo); - if (error) - goto done; - error = got_object_id_str(&commit_id_str, commit_id); - if (error) - goto done; - - error = got_object_open_as_commit(&commit, repo, commit_id); - if (error) - goto done; - pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit)); - memset(&upa, 0, sizeof(upa)); - error = got_worktree_merge_files(worktree, pid ? &pid->id : NULL, - commit_id, repo, update_progress, &upa, check_cancelled, - NULL); - if (error != NULL) - goto done; - - if (upa.did_something) { - error = logmsg_ref(commit_id, - GOT_WORKTREE_CHERRYPICK_REF_PREFIX, worktree, repo); - if (error) - goto done; - printf("Merged commit %s\n", commit_id_str); - } - print_merge_progress_stats(&upa); -done: - free(cwd); - if (commit) - got_object_commit_close(commit); - free(commit_id_str); - if (worktree) - got_worktree_close(worktree); - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - - return error; -} - -__dead static void -usage_backout(void) -{ - fprintf(stderr, "usage: %s backout [-lX] [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; - int ch, list_refs = 0, remove_refs = 0; - struct got_update_progress_arg upa; - int *pack_fds = NULL; - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " - "unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "lX")) != -1) { - switch (ch) { - case 'l': - list_refs = 1; - break; - case 'X': - remove_refs = 1; - break; - default: - usage_backout(); - /* NOTREACHED */ - } - } - - argc -= optind; - argv += optind; - - if (list_refs || remove_refs) { - if (argc != 0 && argc != 1) - usage_backout(); - } else if (argc != 1) - usage_backout(); - if (list_refs && remove_refs) - option_conflict('l', 'X'); - - cwd = getcwd(NULL, 0); - if (cwd == NULL) { - error = got_error_from_errno("getcwd"); - goto done; - } - - error = got_repo_pack_fds_open(&pack_fds); - if (error != NULL) - goto done; - - error = got_worktree_open(&worktree, cwd); - if (error) { - if (list_refs || remove_refs) { - if (error->code != GOT_ERR_NOT_WORKTREE) - goto done; - } else { - if (error->code == GOT_ERR_NOT_WORKTREE) - error = wrap_not_worktree_error(error, - "backout", cwd); - goto done; - } - } - - error = got_repo_open(&repo, - worktree ? got_worktree_get_repo_path(worktree) : cwd, - NULL, pack_fds); - if (error != NULL) - goto done; - - error = apply_unveil(got_repo_get_path(repo), 0, - worktree ? got_worktree_get_root_path(worktree) : NULL); - if (error) - goto done; - - if (list_refs || remove_refs) { - error = process_logmsg_refs(GOT_WORKTREE_BACKOUT_REF_PREFIX, - GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN, - argc == 1 ? argv[0] : NULL, remove_refs, worktree, repo); - goto done; - } - - error = got_repo_match_object_id(&commit_id, NULL, argv[0], - GOT_OBJ_TYPE_COMMIT, NULL, repo); - if (error) - goto done; - error = got_object_id_str(&commit_id_str, commit_id); - if (error) - goto done; - - error = got_object_open_as_commit(&commit, repo, commit_id); - if (error) - goto done; - pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit)); - if (pid == NULL) { - error = got_error(GOT_ERR_ROOT_COMMIT); - goto done; - } - - memset(&upa, 0, sizeof(upa)); - error = got_worktree_merge_files(worktree, commit_id, &pid->id, - repo, update_progress, &upa, check_cancelled, NULL); - if (error != NULL) - goto done; - - if (upa.did_something) { - error = logmsg_ref(commit_id, GOT_WORKTREE_BACKOUT_REF_PREFIX, - worktree, repo); - if (error) - goto done; - printf("Backed out commit %s\n", commit_id_str); - } - print_merge_progress_stats(&upa); -done: - free(cwd); - if (commit) - got_object_commit_close(commit); - free(commit_id_str); - if (worktree) - got_worktree_close(worktree); - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - return error; -} - -__dead static void -usage_rebase(void) -{ - fprintf(stderr, "usage: %s rebase [-aCclX] [branch]\n", getprogname()); - exit(1); -} - -static 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 struct got_error *err; - char *logmsg0 = NULL; - const char *s; - - err = got_object_commit_get_logmsg(&logmsg0, commit); - if (err) - return err; - - s = logmsg0; - while (isspace((unsigned char)s[0])) - s++; - - *logmsg = strdup(s); - if (*logmsg == NULL) { - err = got_error_from_errno("strdup"); - goto done; - } - - trim_logmsg(*logmsg, limit); -done: - free(logmsg0); - return err; -} - -static const struct got_error * -show_rebase_merge_conflict(struct got_object_id *id, - struct got_repository *repo) -{ - const struct got_error *err; - struct got_commit_object *commit = NULL; - char *id_str = NULL, *logmsg = NULL; - - err = got_object_open_as_commit(&commit, repo, id); - if (err) - return err; - - err = got_object_id_str(&id_str, id); - if (err) - goto done; - - id_str[12] = '\0'; - - err = get_short_logmsg(&logmsg, 42, commit); - if (err) - goto done; - - printf("%s -> merge conflict: %s\n", id_str, logmsg); -done: - free(id_str); - got_object_commit_close(commit); - free(logmsg); - return err; -} - -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); - free(logmsg); - return err; -} - -static const struct got_error * -rebase_complete(struct got_worktree *worktree, struct got_fileindex *fileindex, - struct got_reference *branch, struct got_reference *tmp_branch, - struct got_repository *repo, int create_backup) -{ - printf("Switching work tree to %s\n", got_ref_get_name(branch)); - return got_worktree_rebase_complete(worktree, fileindex, - tmp_branch, branch, repo, create_backup); -} - -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, const char *committer, - struct got_object_id *commit_id, int allow_conflict, - 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, committer, commit, commit_id, - allow_conflict, 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, FILE *f1, FILE *f2, - struct got_object_id *id1, struct got_object_id *id2, - const char *path1, const char *path2, - mode_t mode1, mode_t mode2, 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, NULL, NULL, -1, -1, "", "", 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, commit_id; - struct got_object_qid *qid; - - err = got_commit_graph_open(&graph, "/", 1); - if (err) - return err; - - err = got_commit_graph_iter_start(graph, iter_start_id, repo, - check_cancelled, NULL); - if (err) - goto done; - - memcpy(&commit_id, initial_commit_id, sizeof(commit_id)); - while (got_object_id_cmp(&commit_id, iter_stop_id) != 0) { - err = got_commit_graph_iter_next(&parent_id, graph, repo, - check_cancelled, NULL); - 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 { - 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; - STAILQ_INSERT_HEAD(commits, qid, entry); - - memcpy(&commit_id, &parent_id, sizeof(commit_id)); - } - } -done: - got_commit_graph_close(graph); - return err; -} - -static const struct got_error * -get_commit_brief_str(char **brief_str, struct got_commit_object *commit) -{ - const struct got_error *err = NULL; - time_t committer_time; - struct tm tm; - char datebuf[11]; /* YYYY-MM-DD + NUL */ - char *author0 = NULL, *author, *smallerthan; - char *logmsg0 = NULL, *logmsg, *newline; - - committer_time = got_object_commit_get_committer_time(commit); - if (gmtime_r(&committer_time, &tm) == NULL) - return got_error_from_errno("gmtime_r"); - if (strftime(datebuf, sizeof(datebuf), "%G-%m-%d", &tm) == 0) - return got_error(GOT_ERR_NO_SPACE); - - author0 = strdup(got_object_commit_get_author(commit)); - if (author0 == NULL) - return got_error_from_errno("strdup"); - author = author0; - smallerthan = strchr(author, '<'); - if (smallerthan && smallerthan[1] != '\0') - author = smallerthan + 1; - author[strcspn(author, "@>")] = '\0'; - - err = got_object_commit_get_logmsg(&logmsg0, commit); - if (err) - goto done; - logmsg = logmsg0; - while (*logmsg == '\n') - logmsg++; - newline = strchr(logmsg, '\n'); - if (newline) - *newline = '\0'; - - if (asprintf(brief_str, "%s %s %s", - datebuf, author, logmsg) == -1) - err = got_error_from_errno("asprintf"); -done: - free(author0); - free(logmsg0); - return err; -} - -static const struct got_error * -delete_backup_ref(struct got_reference *ref, struct got_object_id *id, - struct got_repository *repo) -{ - const struct got_error *err; - char *id_str; - - err = got_object_id_str(&id_str, id); - if (err) - return err; - - err = got_ref_delete(ref, repo); - if (err) - goto done; - - printf("Deleted %s: %s\n", got_ref_get_name(ref), id_str); -done: - free(id_str); - return err; -} - -static const struct got_error * -print_backup_ref(const char *branch_name, const char *new_id_str, - struct got_object_id *old_commit_id, struct got_commit_object *old_commit, - struct got_reflist_object_id_map *refs_idmap, - struct got_repository *repo) -{ - const struct got_error *err = NULL; - struct got_reflist_head *refs; - char *refs_str = NULL; - struct got_object_id *new_commit_id = NULL; - struct got_commit_object *new_commit = NULL; - char *new_commit_brief_str = NULL; - struct got_object_id *yca_id = NULL; - struct got_commit_object *yca_commit = NULL; - char *yca_id_str = NULL, *yca_brief_str = NULL; - char *custom_refs_str; - - if (asprintf(&custom_refs_str, "formerly %s", branch_name) == -1) - return got_error_from_errno("asprintf"); - - err = print_commit(old_commit, old_commit_id, repo, NULL, NULL, NULL, - 0, 0, refs_idmap, custom_refs_str, NULL); - if (err) - goto done; - - err = got_object_resolve_id_str(&new_commit_id, repo, new_id_str); - if (err) - goto done; - - refs = got_reflist_object_id_map_lookup(refs_idmap, new_commit_id); - if (refs) { - err = build_refs_str(&refs_str, refs, new_commit_id, repo, 0); - if (err) - goto done; - } - - err = got_object_open_as_commit(&new_commit, repo, new_commit_id); - if (err) - goto done; - - err = get_commit_brief_str(&new_commit_brief_str, new_commit); - if (err) - goto done; - - err = got_commit_graph_find_youngest_common_ancestor(&yca_id, - old_commit_id, new_commit_id, 1, repo, check_cancelled, NULL); - if (err) - goto done; - - printf("has become commit %s%s%s%s\n %s\n", new_id_str, - refs_str ? " (" : "", refs_str ? refs_str : "", - refs_str ? ")" : "", new_commit_brief_str); - if (yca_id && got_object_id_cmp(yca_id, new_commit_id) != 0 && - got_object_id_cmp(yca_id, old_commit_id) != 0) { - free(refs_str); - refs_str = NULL; - - err = got_object_open_as_commit(&yca_commit, repo, yca_id); - if (err) - goto done; - - err = get_commit_brief_str(&yca_brief_str, yca_commit); - if (err) - goto done; - - err = got_object_id_str(&yca_id_str, yca_id); - if (err) - goto done; - - refs = got_reflist_object_id_map_lookup(refs_idmap, yca_id); - if (refs) { - err = build_refs_str(&refs_str, refs, yca_id, repo, 0); - if (err) - goto done; - } - printf("history forked at %s%s%s%s\n %s\n", - yca_id_str, - refs_str ? " (" : "", refs_str ? refs_str : "", - refs_str ? ")" : "", yca_brief_str); - } -done: - free(custom_refs_str); - free(new_commit_id); - free(refs_str); - free(yca_id); - free(yca_id_str); - free(yca_brief_str); - if (new_commit) - got_object_commit_close(new_commit); - if (yca_commit) - got_object_commit_close(yca_commit); - - return err; -} - -static const struct got_error * -worktree_has_logmsg_ref(const char *caller, struct got_worktree *worktree, - struct got_repository *repo) -{ - const struct got_error *err; - struct got_reflist_head refs; - struct got_reflist_entry *re; - char *uuidstr = NULL; - static char msg[160]; - - TAILQ_INIT(&refs); - - err = got_worktree_get_uuid(&uuidstr, worktree); - if (err) - goto done; - - err = got_ref_list(&refs, repo, "refs/got/worktree", - got_ref_cmp_by_name, repo); - if (err) - goto done; - - TAILQ_FOREACH(re, &refs, entry) { - const char *cmd, *refname, *type; - - refname = got_ref_get_name(re->ref); - - if (strncmp(refname, GOT_WORKTREE_CHERRYPICK_REF_PREFIX, - GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN) == 0) { - refname += GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN + 1; - cmd = "cherrypick"; - type = "cherrypicked"; - } else if (strncmp(refname, GOT_WORKTREE_BACKOUT_REF_PREFIX, - GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN) == 0) { - refname += GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN + 1; - cmd = "backout"; - type = "backed-out"; - } else - continue; - - if (strncmp(refname, uuidstr, GOT_WORKTREE_UUID_STRLEN) != 0) - continue; - - snprintf(msg, sizeof(msg), - "work tree has references created by %s commits which " - "must be removed with 'got %s -X' before running the %s " - "command", type, cmd, caller); - err = got_error_msg(GOT_ERR_WORKTREE_META, msg); - goto done; - } - -done: - free(uuidstr); - got_ref_list_free(&refs); - return err; -} - -static const struct got_error * -process_backup_refs(const char *backup_ref_prefix, - const char *wanted_branch_name, - int delete, struct got_repository *repo) -{ - const struct got_error *err; - struct got_reflist_head refs, backup_refs; - struct got_reflist_entry *re; - const size_t backup_ref_prefix_len = strlen(backup_ref_prefix); - struct got_object_id *old_commit_id = NULL; - char *branch_name = NULL; - struct got_commit_object *old_commit = NULL; - struct got_reflist_object_id_map *refs_idmap = NULL; - int wanted_branch_found = 0; - - TAILQ_INIT(&refs); - TAILQ_INIT(&backup_refs); - - err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL); - if (err) - return err; - - err = got_reflist_object_id_map_create(&refs_idmap, &refs, repo); - if (err) - goto done; - - if (wanted_branch_name) { - if (strncmp(wanted_branch_name, "refs/heads/", 11) == 0) - wanted_branch_name += 11; - } - - err = got_ref_list(&backup_refs, repo, backup_ref_prefix, - got_ref_cmp_by_commit_timestamp_descending, repo); - if (err) - goto done; - - TAILQ_FOREACH(re, &backup_refs, entry) { - const char *refname = got_ref_get_name(re->ref); - char *slash; - - err = check_cancelled(NULL); - if (err) - break; - - err = got_ref_resolve(&old_commit_id, repo, re->ref); - if (err) - break; - - err = got_object_open_as_commit(&old_commit, repo, - old_commit_id); - if (err) - break; - - if (strncmp(backup_ref_prefix, refname, - backup_ref_prefix_len) == 0) - refname += backup_ref_prefix_len; - - while (refname[0] == '/') - refname++; - - branch_name = strdup(refname); - if (branch_name == NULL) { - err = got_error_from_errno("strdup"); - break; - } - slash = strrchr(branch_name, '/'); - if (slash) { - *slash = '\0'; - refname += strlen(branch_name) + 1; - } - - if (wanted_branch_name == NULL || - strcmp(wanted_branch_name, branch_name) == 0) { - wanted_branch_found = 1; - if (delete) { - err = delete_backup_ref(re->ref, - old_commit_id, repo); - } else { - err = print_backup_ref(branch_name, refname, - old_commit_id, old_commit, refs_idmap, - repo); - } - if (err) - break; - } - - free(old_commit_id); - old_commit_id = NULL; - free(branch_name); - branch_name = NULL; - got_object_commit_close(old_commit); - old_commit = NULL; - } - - if (wanted_branch_name && !wanted_branch_found) { - err = got_error_fmt(GOT_ERR_NOT_REF, - "%s/%s/", backup_ref_prefix, wanted_branch_name); - } -done: - if (refs_idmap) - got_reflist_object_id_map_free(refs_idmap); - got_ref_list_free(&refs); - got_ref_list_free(&backup_refs); - free(old_commit_id); - free(branch_name); - if (old_commit) - got_object_commit_close(old_commit); - return err; -} - -static const struct got_error * -abort_progress(void *arg, unsigned char status, const char *path) -{ - /* - * Unversioned files should not clutter progress output when - * an operation is aborted. - */ - if (status == GOT_STATUS_UNVERSIONED) - return NULL; - - return update_progress(arg, status, path); -} - -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, *committer = NULL, *gitconfig_path = 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_object_id *head_commit_id = NULL; - struct got_reference *head_ref = NULL; - struct got_commit_object *commit = NULL; - int ch, rebase_in_progress = 0, abort_rebase = 0, continue_rebase = 0; - int histedit_in_progress = 0, merge_in_progress = 0; - int create_backup = 1, list_backups = 0, delete_backups = 0; - int allow_conflict = 0; - 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; - struct got_update_progress_arg upa; - int *pack_fds = NULL; - - STAILQ_INIT(&commits); - TAILQ_INIT(&merged_paths); - memset(&upa, 0, sizeof(upa)); - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " - "unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "aCclX")) != -1) { - switch (ch) { - case 'a': - abort_rebase = 1; - break; - case 'C': - allow_conflict = 1; - break; - case 'c': - continue_rebase = 1; - break; - case 'l': - list_backups = 1; - break; - case 'X': - delete_backups = 1; - break; - default: - usage_rebase(); - /* NOTREACHED */ - } - } - - argc -= optind; - argv += optind; - - if (list_backups) { - if (abort_rebase) - option_conflict('l', 'a'); - if (allow_conflict) - option_conflict('l', 'C'); - if (continue_rebase) - option_conflict('l', 'c'); - if (delete_backups) - option_conflict('l', 'X'); - if (argc != 0 && argc != 1) - usage_rebase(); - } else if (delete_backups) { - if (abort_rebase) - option_conflict('X', 'a'); - if (allow_conflict) - option_conflict('X', 'C'); - if (continue_rebase) - option_conflict('X', 'c'); - if (list_backups) - option_conflict('l', 'X'); - if (argc != 0 && argc != 1) - usage_rebase(); - } else if (allow_conflict) { - if (abort_rebase) - option_conflict('C', 'a'); - if (!continue_rebase) - errx(1, "-C option requires -c"); - } else { - 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_repo_pack_fds_open(&pack_fds); - if (error != NULL) - goto done; - - error = got_worktree_open(&worktree, cwd); - if (error) { - if (list_backups || delete_backups) { - if (error->code != GOT_ERR_NOT_WORKTREE) - goto done; - } else { - if (error->code == GOT_ERR_NOT_WORKTREE) - error = wrap_not_worktree_error(error, - "rebase", cwd); - goto done; - } - } - - error = get_gitconfig_path(&gitconfig_path); - if (error) - goto done; - error = got_repo_open(&repo, - worktree ? got_worktree_get_repo_path(worktree) : cwd, - gitconfig_path, pack_fds); - if (error != NULL) - goto done; - - if (worktree != NULL && !list_backups && !delete_backups) { - error = worktree_has_logmsg_ref("rebase", worktree, repo); - if (error) - goto done; - } - - error = get_author(&committer, repo, worktree); - if (error && error->code != GOT_ERR_COMMIT_NO_AUTHOR) - goto done; - - error = apply_unveil(got_repo_get_path(repo), 0, - worktree ? got_worktree_get_root_path(worktree) : NULL); - if (error) - goto done; - - if (list_backups || delete_backups) { - error = process_backup_refs( - GOT_WORKTREE_REBASE_BACKUP_REF_PREFIX, - argc == 1 ? argv[0] : NULL, delete_backups, repo); - goto done; /* nothing else to do */ - } - - error = got_worktree_histedit_in_progress(&histedit_in_progress, - worktree); - if (error) - goto done; - if (histedit_in_progress) { - error = got_error(GOT_ERR_HISTEDIT_BUSY); - goto done; - } - - error = got_worktree_merge_in_progress(&merge_in_progress, - worktree, repo); - if (error) - goto done; - if (merge_in_progress) { - error = got_error(GOT_ERR_MERGE_BUSY); - goto done; - } - - error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); - if (error) - goto done; - - if (abort_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; - 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, abort_progress, &upa); - if (error) - goto done; - printf("Rebase of %s aborted\n", got_ref_get_name(branch)); - print_merge_progress_stats(&upa); - 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, - committer, resume_commit_id, allow_conflict, 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; - if (strncmp(got_ref_get_name(branch), "refs/heads/", 11) != 0) { - error = got_error_msg(GOT_ERR_COMMIT_BRANCH, - "will not rebase a branch which lives outside " - "the \"refs/heads/\" reference namespace"); - 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; - - error = got_ref_open(&head_ref, repo, - got_worktree_get_head_ref_name(worktree), 0); - if (error) - goto done; - error = got_ref_resolve(&head_commit_id, repo, head_ref); - if (error) - goto done; - base_commit_id = got_worktree_get_base_commit_id(worktree); - if (got_object_id_cmp(base_commit_id, head_commit_id) != 0) { - error = got_error(GOT_ERR_REBASE_OUT_OF_DATE); - goto done; - } - - error = got_commit_graph_find_youngest_common_ancestor(&yca_id, - base_commit_id, branch_head_commit_id, 1, repo, - check_cancelled, NULL); - if (error) { - if (error->code == GOT_ERR_ANCESTRY) { - error = got_error_msg(GOT_ERR_ANCESTRY, - "specified branch shares no common " - "ancestry with work tree's branch"); - } - goto done; - } - - if (got_object_id_cmp(base_commit_id, yca_id) == 0) { - struct got_pathlist_head paths; - printf("%s is already based on %s\n", - got_ref_get_name(branch), - got_worktree_get_head_ref_name(worktree)); - error = switch_head_ref(branch, branch_head_commit_id, - worktree, repo); - if (error) - goto done; - error = got_worktree_set_base_commit_id(worktree, repo, - branch_head_commit_id); - if (error) - goto done; - TAILQ_INIT(&paths); - error = got_pathlist_append(&paths, "", NULL); - if (error) - goto done; - error = got_worktree_checkout_files(worktree, - &paths, repo, update_progress, &upa, - check_cancelled, NULL); - got_pathlist_free(&paths, GOT_PATHLIST_FREE_NONE); - if (error) - goto done; - if (upa.did_something) { - char *id_str; - error = got_object_id_str(&id_str, - branch_head_commit_id); - if (error) - goto done; - printf("Updated to %s: %s\n", - got_worktree_get_head_ref_name(worktree), - id_str); - free(id_str); - } else - printf("Already up-to-date\n"); - print_update_progress_stats(&upa); - 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 = STAILQ_FIRST(parent_ids); - if (pid) { - error = collect_commits(&commits, commit_id, &pid->id, - yca_id, got_worktree_get_path_prefix(worktree), - GOT_ERR_REBASE_PATH, repo); - if (error) - goto done; - } - - got_object_commit_close(commit); - commit = NULL; - - if (!continue_rebase) { - error = got_worktree_rebase_prepare(&new_base_branch, - &tmp_branch, &fileindex, worktree, branch, repo); - if (error) - goto done; - } - - if (STAILQ_EMPTY(&commits)) { - if (continue_rebase) { - error = rebase_complete(worktree, fileindex, - branch, tmp_branch, repo, create_backup); - goto done; - } else { - /* Fast-forward the reference of the branch. */ - struct got_object_id *new_head_commit_id; - char *id_str; - error = got_ref_resolve(&new_head_commit_id, repo, - new_base_branch); - if (error) - goto done; - error = got_object_id_str(&id_str, new_head_commit_id); - if (error) - goto done; - printf("Forwarding %s to commit %s\n", - got_ref_get_name(branch), id_str); - free(id_str); - error = got_ref_change_ref(branch, - new_head_commit_id); - if (error) - goto done; - /* No backup needed since objects did not change. */ - create_backup = 0; - } - } - - pid = NULL; - STAILQ_FOREACH(qid, &commits, entry) { - - commit_id = &qid->id; - parent_id = pid ? &pid->id : yca_id; - pid = qid; - - memset(&upa, 0, sizeof(upa)); - error = got_worktree_rebase_merge_files(&merged_paths, - worktree, fileindex, parent_id, commit_id, repo, - update_progress, &upa, check_cancelled, NULL); - if (error) - goto done; - - print_merge_progress_stats(&upa); - if (upa.conflicts > 0 || upa.missing > 0 || - upa.not_deleted > 0 || upa.unversioned > 0) { - if (upa.conflicts > 0) { - error = show_rebase_merge_conflict(&qid->id, - repo); - if (error) - goto done; - } - got_pathlist_free(&merged_paths, GOT_PATHLIST_FREE_PATH); - break; - } - - error = rebase_commit(&merged_paths, worktree, fileindex, - tmp_branch, committer, commit_id, 0, repo); - got_pathlist_free(&merged_paths, GOT_PATHLIST_FREE_PATH); - if (error) - goto done; - } - - if (upa.conflicts > 0 || upa.missing > 0 || - upa.not_deleted > 0 || upa.unversioned > 0) { - error = got_worktree_rebase_postpone(worktree, fileindex); - if (error) - goto done; - if (upa.conflicts > 0 && upa.missing == 0 && - upa.not_deleted == 0 && upa.unversioned == 0) { - error = got_error_msg(GOT_ERR_CONFLICTS, - "conflicts must be resolved before rebasing " - "can continue"); - } else if (upa.conflicts > 0) { - error = got_error_msg(GOT_ERR_CONFLICTS, - "conflicts must be resolved before rebasing " - "can continue; changes destined for some " - "files were not yet merged and should be " - "merged manually if required before the " - "rebase operation is continued"); - } else { - error = got_error_msg(GOT_ERR_CONFLICTS, - "changes destined for some files were not " - "yet merged and should be merged manually " - "if required before the rebase operation " - "is continued"); - } - } else - error = rebase_complete(worktree, fileindex, branch, - tmp_branch, repo, create_backup); -done: - free(cwd); - free(committer); - free(gitconfig_path); - got_object_id_queue_free(&commits); - free(branch_head_commit_id); - free(resume_commit_id); - free(head_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 (head_ref) - got_ref_close(head_ref); - if (worktree) - got_worktree_close(worktree); - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - return error; -} - -__dead static void -usage_histedit(void) -{ - fprintf(stderr, "usage: %s histedit [-aCcdeflmX] [-F histedit-script] " - "[branch]\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 const 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 next commit that will " - "be used" }, - { 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; - int 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, int edit_logmsg_only, int fold_only, int drop_only, - int edit_only, struct got_repository *repo) -{ - const struct got_error *err = NULL; - struct got_object_qid *qid; - const char *histedit_cmd = NULL; - - if (STAILQ_EMPTY(commits)) - return got_error(GOT_ERR_EMPTY_HISTEDIT); - - STAILQ_FOREACH(qid, commits, entry) { - histedit_cmd = got_histedit_cmds[0].name; - if (drop_only) - histedit_cmd = "drop"; - else if (edit_only) - histedit_cmd = "edit"; - else if (fold_only && STAILQ_NEXT(qid, entry) != NULL) - histedit_cmd = "fold"; - err = histedit_write_commit(&qid->id, histedit_cmd, f, repo); - if (err) - break; - if (edit_logmsg_only) { - int n = fprintf(f, "%c\n", GOT_HISTEDIT_MESG); - if (n < 0) { - err = got_ferror(f, GOT_ERR_IO); - break; - } - } - } - - return err; -} - -static const struct got_error * -write_cmd_list(FILE *f, const char *branch_name, - struct got_object_id_queue *commits) -{ - const struct got_error *err = NULL; - size_t i; - int n; - char *id_str; - struct got_object_qid *qid; - - qid = STAILQ_FIRST(commits); - err = got_object_id_str(&id_str, &qid->id); - if (err) - return err; - - n = fprintf(f, - "# Editing the history of branch '%s' starting at\n" - "# commit %s\n" - "# Commits will be processed in order from top to " - "bottom of this file.\n", branch_name, id_str); - if (n < 0) { - err = got_ferror(f, GOT_ERR_IO); - goto done; - } - - n = fprintf(f, "# Available histedit commands:\n"); - if (n < 0) { - err = got_ferror(f, GOT_ERR_IO); - goto done; - } - - for (i = 0; i < nitems(got_histedit_cmds); i++) { - const 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; - } - } -done: - free(id_str); - 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 < 0 || (size_t)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, *folded_logmsg = NULL; - - 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; - - err = got_object_commit_get_logmsg(&folded_logmsg, folded_commit); - if (err) - goto done; - if (asprintf(new_msg, "%s%s# log message of folded commit %s: %s", - logmsg ? logmsg : "", logmsg ? "\n" : "", id_str, - folded_logmsg) == -1) { - err = got_error_from_errno("asprintf"); - } -done: - if (folded_commit) - got_object_commit_close(folded_commit); - free(id_str); - free(folded_logmsg); - 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, *orig_logmsg = NULL; - char *logmsg = NULL, *new_msg = NULL, *editor = NULL; - const struct got_error *err = NULL; - struct got_commit_object *commit = NULL; - int logmsg_len; - int fd = -1; - 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; - err = got_object_commit_get_logmsg(&orig_logmsg, commit); - if (err) - goto done; - logmsg_len = asprintf(&new_msg, - "%s\n# original log message of commit %s: %s", - logmsg ? logmsg : "", id_str, orig_logmsg); - if (logmsg_len == -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, - GOT_TMPDIR_STR "/got-logmsg", ""); - if (err) - goto done; - - if (write(fd, logmsg, logmsg_len) == -1) { - err = got_error_from_errno2("write", logmsg_path); - goto done; - } - if (close(fd) == -1) { - err = got_error_from_errno2("close", logmsg_path); - goto done; - } - fd = -1; - - err = get_editor(&editor); - if (err) - goto done; - - err = edit_logmsg(&hle->logmsg, editor, logmsg_path, logmsg, - logmsg_len, 0); - if (err) { - if (err->code != GOT_ERR_COMMIT_MSG_EMPTY) - goto done; - err = NULL; - hle->logmsg = strdup(new_msg); - if (hle->logmsg == NULL) - err = got_error_from_errno("strdup"); - } -done: - if (fd != -1 && close(fd) == -1 && err == NULL) - err = got_error_from_errno2("close", logmsg_path); - if (logmsg_path && unlink(logmsg_path) != 0 && err == NULL) - err = got_error_from_errno2("unlink", logmsg_path); - free(logmsg_path); - free(logmsg); - free(orig_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 i, linesize = 0; - ssize_t linelen; - int lineno = 0, lastcmd = -1; - const struct got_histedit_cmd *cmd; - struct got_object_id *commit_id = NULL; - struct got_histedit_list_entry *hle = NULL; - - for (;;) { - linelen = getline(&line, &linesize, f); - if (linelen == -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') - 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 (lastcmd != GOT_HISTEDIT_PICK && - lastcmd != GOT_HISTEDIT_EDIT) { - 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; - } - } - lastcmd = cmd->code; - 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; - TAILQ_INSERT_TAIL(histedit_cmds, hle, entry); - lastcmd = cmd->code; - } - - 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[92]; - char *id_str; - - if (TAILQ_EMPTY(histedit_cmds)) - return got_error_msg(GOT_ERR_EMPTY_HISTEDIT, - "histedit script contains no commands"); - if (STAILQ_EMPTY(commits)) - return got_error(GOT_ERR_EMPTY_HISTEDIT); - - TAILQ_FOREACH(hle, histedit_cmds, entry) { - struct got_histedit_list_entry *hle2; - TAILQ_FOREACH(hle2, histedit_cmds, entry) { - if (hle == hle2) - continue; - if (got_object_id_cmp(hle->commit_id, - hle2->commit_id) != 0) - continue; - err = got_object_id_str(&id_str, hle->commit_id); - if (err) - return err; - snprintf(msg, sizeof(msg), "commit %s is listed " - "more than once in histedit script", id_str); - free(id_str); - return got_error_msg(GOT_ERR_HISTEDIT_CMD, msg); - } - } - - STAILQ_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); - } - } - - hle = TAILQ_LAST(histedit_cmds, got_histedit_list); - if (hle && 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, "re"); - 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) == EOF && 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 *, 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, const char *branch_name, - int edit_logmsg_only, int fold_only, int drop_only, int edit_only, - 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, branch_name, commits); - if (err) - goto done; - - err = histedit_write_commit_list(commits, f, edit_logmsg_only, - fold_only, drop_only, edit_only, repo); - if (err) - goto done; - - if (drop_only || edit_logmsg_only || fold_only || edit_only) { - rewind(f); - err = histedit_parse_list(histedit_cmds, f, repo); - } else { - if (fclose(f) == EOF) { - 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, branch_name, repo); - } - } -done: - if (f && fclose(f) == EOF && 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, "we"); - 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) == EOF && err == NULL) - err = got_error_from_errno("fclose"); - free(path); - if (commit) - got_object_commit_close(commit); - return err; -} - -static 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, "re"); - 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) == EOF && 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, const char *branch_name, 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 == '\n') - 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, branch_name, 0, 0, 0, 0, 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, - const char *committer, int allow_conflict, 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, committer, commit, hle->commit_id, - hle->logmsg, allow_conflict, 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 * -check_local_changes(void *arg, unsigned char status, - unsigned char staged_status, const char *path, - struct got_object_id *blob_id, struct got_object_id *staged_blob_id, - struct got_object_id *commit_id, int dirfd, const char *de_name) -{ - int *have_local_changes = arg; - - switch (status) { - case GOT_STATUS_ADD: - case GOT_STATUS_DELETE: - case GOT_STATUS_MODIFY: - case GOT_STATUS_CONFLICT: - *have_local_changes = 1; - return got_error(GOT_ERR_CANCELLED); - default: - break; - } - - switch (staged_status) { - case GOT_STATUS_ADD: - case GOT_STATUS_DELETE: - case GOT_STATUS_MODIFY: - *have_local_changes = 1; - return got_error(GOT_ERR_CANCELLED); - default: - break; - } - - return NULL; -} - -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, *committer = NULL, *gitconfig_path = 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, merge_in_progress = 0; - struct got_update_progress_arg upa; - int edit_in_progress = 0, abort_edit = 0, continue_edit = 0; - int drop_only = 0, edit_logmsg_only = 0, fold_only = 0, edit_only = 0; - int allow_conflict = 0, list_backups = 0, delete_backups = 0; - const char *edit_script_path = NULL; - 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; - int *pack_fds = NULL; - - STAILQ_INIT(&commits); - TAILQ_INIT(&histedit_cmds); - TAILQ_INIT(&merged_paths); - memset(&upa, 0, sizeof(upa)); - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " - "unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "aCcdeF:flmX")) != -1) { - switch (ch) { - case 'a': - abort_edit = 1; - break; - case 'C': - allow_conflict = 1; - break; - case 'c': - continue_edit = 1; - break; - case 'd': - drop_only = 1; - break; - case 'e': - edit_only = 1; - break; - case 'F': - edit_script_path = optarg; - break; - case 'f': - fold_only = 1; - break; - case 'l': - list_backups = 1; - break; - case 'm': - edit_logmsg_only = 1; - break; - case 'X': - delete_backups = 1; - break; - default: - usage_histedit(); - /* NOTREACHED */ - } - } - - argc -= optind; - argv += optind; - - if (abort_edit && allow_conflict) - option_conflict('a', 'C'); - if (abort_edit && continue_edit) - option_conflict('a', 'c'); - if (edit_script_path && allow_conflict) - option_conflict('F', 'C'); - if (edit_script_path && edit_logmsg_only) - option_conflict('F', 'm'); - if (abort_edit && edit_logmsg_only) - option_conflict('a', 'm'); - if (edit_logmsg_only && allow_conflict) - option_conflict('m', 'C'); - if (continue_edit && edit_logmsg_only) - option_conflict('c', 'm'); - if (abort_edit && fold_only) - option_conflict('a', 'f'); - if (fold_only && allow_conflict) - option_conflict('f', 'C'); - if (continue_edit && fold_only) - option_conflict('c', 'f'); - if (fold_only && edit_logmsg_only) - option_conflict('f', 'm'); - if (edit_script_path && fold_only) - option_conflict('F', 'f'); - if (abort_edit && edit_only) - option_conflict('a', 'e'); - if (continue_edit && edit_only) - option_conflict('c', 'e'); - if (edit_only && edit_logmsg_only) - option_conflict('e', 'm'); - if (edit_script_path && edit_only) - option_conflict('F', 'e'); - if (fold_only && edit_only) - option_conflict('f', 'e'); - if (drop_only && abort_edit) - option_conflict('d', 'a'); - if (drop_only && allow_conflict) - option_conflict('d', 'C'); - if (drop_only && continue_edit) - option_conflict('d', 'c'); - if (drop_only && edit_logmsg_only) - option_conflict('d', 'm'); - if (drop_only && edit_only) - option_conflict('d', 'e'); - if (drop_only && edit_script_path) - option_conflict('d', 'F'); - if (drop_only && fold_only) - option_conflict('d', 'f'); - if (list_backups) { - if (abort_edit) - option_conflict('l', 'a'); - if (allow_conflict) - option_conflict('l', 'C'); - if (continue_edit) - option_conflict('l', 'c'); - if (edit_script_path) - option_conflict('l', 'F'); - if (edit_logmsg_only) - option_conflict('l', 'm'); - if (drop_only) - option_conflict('l', 'd'); - if (fold_only) - option_conflict('l', 'f'); - if (edit_only) - option_conflict('l', 'e'); - if (delete_backups) - option_conflict('l', 'X'); - if (argc != 0 && argc != 1) - usage_histedit(); - } else if (delete_backups) { - if (abort_edit) - option_conflict('X', 'a'); - if (allow_conflict) - option_conflict('X', 'C'); - if (continue_edit) - option_conflict('X', 'c'); - if (drop_only) - option_conflict('X', 'd'); - if (edit_script_path) - option_conflict('X', 'F'); - if (edit_logmsg_only) - option_conflict('X', 'm'); - if (fold_only) - option_conflict('X', 'f'); - if (edit_only) - option_conflict('X', 'e'); - if (list_backups) - option_conflict('X', 'l'); - if (argc != 0 && argc != 1) - usage_histedit(); - } else if (allow_conflict && !continue_edit) - errx(1, "-C option requires -c"); - else 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_repo_pack_fds_open(&pack_fds); - if (error != NULL) - goto done; - - error = got_worktree_open(&worktree, cwd); - if (error) { - if (list_backups || delete_backups) { - if (error->code != GOT_ERR_NOT_WORKTREE) - goto done; - } else { - if (error->code == GOT_ERR_NOT_WORKTREE) - error = wrap_not_worktree_error(error, - "histedit", cwd); - goto done; - } - } - - if (list_backups || delete_backups) { - error = got_repo_open(&repo, - worktree ? got_worktree_get_repo_path(worktree) : cwd, - NULL, pack_fds); - if (error != NULL) - goto done; - error = apply_unveil(got_repo_get_path(repo), 0, - worktree ? got_worktree_get_root_path(worktree) : NULL); - if (error) - goto done; - error = process_backup_refs( - GOT_WORKTREE_HISTEDIT_BACKUP_REF_PREFIX, - argc == 1 ? argv[0] : NULL, delete_backups, repo); - goto done; /* nothing else to do */ - } - - error = get_gitconfig_path(&gitconfig_path); - if (error) - goto done; - error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), - gitconfig_path, pack_fds); - if (error != NULL) - goto done; - - if (worktree != NULL && !list_backups && !delete_backups) { - error = worktree_has_logmsg_ref("histedit", worktree, repo); - 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_merge_in_progress(&merge_in_progress, worktree, - repo); - if (error) - goto done; - if (merge_in_progress) { - error = got_error(GOT_ERR_MERGE_BUSY); - goto done; - } - - error = got_worktree_histedit_in_progress(&edit_in_progress, worktree); - if (error) - goto done; - - if (edit_in_progress && edit_logmsg_only) { - error = got_error_msg(GOT_ERR_HISTEDIT_BUSY, - "histedit operation is in progress in this " - "work tree and must be continued or aborted " - "before the -m option can be used"); - goto done; - } - if (edit_in_progress && drop_only) { - error = got_error_msg(GOT_ERR_HISTEDIT_BUSY, - "histedit operation is in progress in this " - "work tree and must be continued or aborted " - "before the -d option can be used"); - goto done; - } - if (edit_in_progress && fold_only) { - error = got_error_msg(GOT_ERR_HISTEDIT_BUSY, - "histedit operation is in progress in this " - "work tree and must be continued or aborted " - "before the -f option can be used"); - goto done; - } - if (edit_in_progress && edit_only) { - error = got_error_msg(GOT_ERR_HISTEDIT_BUSY, - "histedit operation is in progress in this " - "work tree and must be continued or aborted " - "before the -e option can be used"); - 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, abort_progress, &upa); - if (error) - goto done; - printf("Histedit of %s aborted\n", - got_ref_get_symref_target(branch)); - print_merge_progress_stats(&upa); - goto done; /* nothing else to do */ - } else if (abort_edit) { - error = got_error(GOT_ERR_NOT_HISTEDIT); - goto done; - } - - error = get_author(&committer, repo, worktree); - if (error) - 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 = STAILQ_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; - - if (strncmp(got_ref_get_name(branch), "refs/heads/", 11) != 0) { - error = got_error_msg(GOT_ERR_COMMIT_BRANCH, - "will not edit commit history of a branch outside " - "the \"refs/heads/\" reference namespace"); - 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 = STAILQ_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; - - if (STAILQ_EMPTY(&commits)) { - error = got_error(GOT_ERR_EMPTY_HISTEDIT); - 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, - abort_progress, &upa); - print_merge_progress_stats(&upa); - goto done; - } - } else { - const char *branch_name; - branch_name = got_ref_get_symref_target(branch); - if (strncmp(branch_name, "refs/heads/", 11) == 0) - branch_name += 11; - error = histedit_edit_script(&histedit_cmds, &commits, - branch_name, edit_logmsg_only, fold_only, - drop_only, edit_only, repo); - if (error) { - got_worktree_histedit_abort(worktree, fileindex, - repo, branch, base_commit_id, - abort_progress, &upa); - print_merge_progress_stats(&upa); - goto done; - } - - } - - error = histedit_save_list(&histedit_cmds, worktree, - repo); - if (error) { - got_worktree_histedit_abort(worktree, fileindex, - repo, branch, base_commit_id, - abort_progress, &upa); - print_merge_progress_stats(&upa); - 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); - if (error) - goto done; - } else { - struct got_pathlist_head paths; - int have_changes = 0; - - TAILQ_INIT(&paths); - error = got_pathlist_append(&paths, "", NULL); - if (error) - goto done; - error = got_worktree_status(worktree, &paths, - repo, 0, check_local_changes, &have_changes, - check_cancelled, NULL); - got_pathlist_free(&paths, - GOT_PATHLIST_FREE_NONE); - if (error) { - if (error->code != GOT_ERR_CANCELLED) - goto done; - if (sigint_received || sigpipe_received) - goto done; - } - if (have_changes) { - error = histedit_commit(NULL, worktree, - fileindex, tmp_branch, hle, - committer, allow_conflict, repo); - if (error) - goto done; - } else { - error = got_object_open_as_commit( - &commit, repo, hle->commit_id); - if (error) - goto done; - error = show_histedit_progress(commit, - hle, NULL); - got_object_commit_close(commit); - commit = NULL; - 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 = STAILQ_FIRST(parent_ids); - - error = got_worktree_histedit_merge_files(&merged_paths, - worktree, fileindex, &pid->id, hle->commit_id, repo, - update_progress, &upa, check_cancelled, NULL); - if (error) - goto done; - got_object_commit_close(commit); - commit = NULL; - - print_merge_progress_stats(&upa); - if (upa.conflicts > 0 || upa.missing > 0 || - upa.not_deleted > 0 || upa.unversioned > 0) { - if (upa.conflicts > 0) { - error = show_rebase_merge_conflict( - hle->commit_id, repo); - if (error) - goto done; - } - got_pathlist_free(&merged_paths, GOT_PATHLIST_FREE_PATH); - 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_pathlist_free(&merged_paths, GOT_PATHLIST_FREE_PATH); - 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, committer, allow_conflict, repo); - got_pathlist_free(&merged_paths, GOT_PATHLIST_FREE_PATH); - if (error) - goto done; - } - - if (upa.conflicts > 0 || upa.missing > 0 || - upa.not_deleted > 0 || upa.unversioned > 0) { - error = got_worktree_histedit_postpone(worktree, fileindex); - if (error) - goto done; - if (upa.conflicts > 0 && upa.missing == 0 && - upa.not_deleted == 0 && upa.unversioned == 0) { - error = got_error_msg(GOT_ERR_CONFLICTS, - "conflicts must be resolved before histedit " - "can continue"); - } else if (upa.conflicts > 0) { - error = got_error_msg(GOT_ERR_CONFLICTS, - "conflicts must be resolved before histedit " - "can continue; changes destined for some " - "files were not yet merged and should be " - "merged manually if required before the " - "histedit operation is continued"); - } else { - error = got_error_msg(GOT_ERR_CONFLICTS, - "changes destined for some files were not " - "yet merged and should be merged manually " - "if required before the histedit operation " - "is continued"); - } - } else - error = histedit_complete(worktree, fileindex, tmp_branch, - branch, repo); -done: - free(cwd); - free(committer); - free(gitconfig_path); - 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) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - return error; -} - -__dead static void -usage_integrate(void) -{ - fprintf(stderr, "usage: %s integrate branch\n", getprogname()); - exit(1); -} - -static const struct got_error * -cmd_integrate(int argc, char *argv[]) -{ - const struct got_error *error = NULL; - struct got_repository *repo = NULL; - struct got_worktree *worktree = NULL; - char *cwd = NULL, *refname = NULL, *base_refname = NULL; - const char *branch_arg = NULL; - struct got_reference *branch_ref = NULL, *base_branch_ref = NULL; - struct got_fileindex *fileindex = NULL; - struct got_object_id *commit_id = NULL, *base_commit_id = NULL; - int ch; - struct got_update_progress_arg upa; - int *pack_fds = NULL; - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " - "unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "")) != -1) { - switch (ch) { - default: - usage_integrate(); - /* NOTREACHED */ - } - } - - argc -= optind; - argv += optind; - - if (argc != 1) - usage_integrate(); - branch_arg = argv[0]; - - cwd = getcwd(NULL, 0); - if (cwd == NULL) { - error = got_error_from_errno("getcwd"); - goto done; - } - - error = got_repo_pack_fds_open(&pack_fds); - if (error != NULL) - goto done; - - error = got_worktree_open(&worktree, cwd); - if (error) { - if (error->code == GOT_ERR_NOT_WORKTREE) - error = wrap_not_worktree_error(error, "integrate", - cwd); - goto done; - } - - error = check_rebase_or_histedit_in_progress(worktree); - if (error) - goto done; - - error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), - NULL, pack_fds); - 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 = check_merge_in_progress(worktree, repo); - if (error) - goto done; - - if (asprintf(&refname, "refs/heads/%s", branch_arg) == -1) { - error = got_error_from_errno("asprintf"); - goto done; - } - - error = got_worktree_integrate_prepare(&fileindex, &branch_ref, - &base_branch_ref, worktree, refname, repo); - if (error) - goto done; - - refname = strdup(got_ref_get_name(branch_ref)); - if (refname == NULL) { - error = got_error_from_errno("strdup"); - got_worktree_integrate_abort(worktree, fileindex, repo, - branch_ref, base_branch_ref); - goto done; - } - base_refname = strdup(got_ref_get_name(base_branch_ref)); - if (base_refname == NULL) { - error = got_error_from_errno("strdup"); - got_worktree_integrate_abort(worktree, fileindex, repo, - branch_ref, base_branch_ref); - goto done; - } - if (strncmp(base_refname, "refs/heads/", 11) != 0) { - error = got_error(GOT_ERR_INTEGRATE_BRANCH); - got_worktree_integrate_abort(worktree, fileindex, repo, - branch_ref, base_branch_ref); - goto done; - } - - error = got_ref_resolve(&commit_id, repo, branch_ref); - if (error) - goto done; - - error = got_ref_resolve(&base_commit_id, repo, base_branch_ref); - if (error) - goto done; - - if (got_object_id_cmp(commit_id, base_commit_id) == 0) { - error = got_error_msg(GOT_ERR_SAME_BRANCH, - "specified branch has already been integrated"); - got_worktree_integrate_abort(worktree, fileindex, repo, - branch_ref, base_branch_ref); - goto done; - } - - error = check_linear_ancestry(commit_id, base_commit_id, 1, repo); - if (error) { - if (error->code == GOT_ERR_ANCESTRY) - error = got_error(GOT_ERR_REBASE_REQUIRED); - got_worktree_integrate_abort(worktree, fileindex, repo, - branch_ref, base_branch_ref); - goto done; - } - - memset(&upa, 0, sizeof(upa)); - error = got_worktree_integrate_continue(worktree, fileindex, repo, - branch_ref, base_branch_ref, update_progress, &upa, - check_cancelled, NULL); - if (error) - goto done; - - printf("Integrated %s into %s\n", refname, base_refname); - print_update_progress_stats(&upa); -done: - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (worktree) - got_worktree_close(worktree); - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - free(cwd); - free(base_commit_id); - free(commit_id); - free(refname); - free(base_refname); - return error; -} - -__dead static void -usage_merge(void) -{ - fprintf(stderr, "usage: %s merge [-aCcn] [branch]\n", getprogname()); - exit(1); -} - -static const struct got_error * -cmd_merge(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, *id_str = NULL, *author = NULL; - char *gitconfig_path = NULL; - struct got_reference *branch = NULL, *wt_branch = NULL; - struct got_object_id *branch_tip = NULL, *yca_id = NULL; - struct got_object_id *wt_branch_tip = NULL; - int ch, merge_in_progress = 0, abort_merge = 0, continue_merge = 0; - int allow_conflict = 0, prefer_fast_forward = 1, interrupt_merge = 0; - struct got_update_progress_arg upa; - struct got_object_id *merge_commit_id = NULL; - char *branch_name = NULL; - int *pack_fds = NULL; - - memset(&upa, 0, sizeof(upa)); - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " - "unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "aCcMn")) != -1) { - switch (ch) { - case 'a': - abort_merge = 1; - break; - case 'C': - allow_conflict = 1; - break; - case 'c': - continue_merge = 1; - break; - case 'M': - prefer_fast_forward = 0; - break; - case 'n': - interrupt_merge = 1; - break; - default: - usage_merge(); - /* NOTREACHED */ - } - } - - argc -= optind; - argv += optind; - - if (abort_merge) { - if (continue_merge) - option_conflict('a', 'c'); - if (!prefer_fast_forward) - option_conflict('a', 'M'); - if (interrupt_merge) - option_conflict('a', 'n'); - } else if (continue_merge) { - if (!prefer_fast_forward) - option_conflict('c', 'M'); - if (interrupt_merge) - option_conflict('c', 'n'); - } - if (allow_conflict) { - if (!continue_merge) - errx(1, "-C option requires -c"); - } - if (abort_merge || continue_merge) { - if (argc != 0) - usage_merge(); - } else if (argc != 1) - usage_merge(); - - cwd = getcwd(NULL, 0); - if (cwd == NULL) { - error = got_error_from_errno("getcwd"); - goto done; - } - - error = got_repo_pack_fds_open(&pack_fds); - if (error != NULL) - goto done; - - error = got_worktree_open(&worktree, cwd); - if (error) { - if (error->code == GOT_ERR_NOT_WORKTREE) - error = wrap_not_worktree_error(error, - "merge", cwd); - goto done; - } - - error = get_gitconfig_path(&gitconfig_path); - if (error) - goto done; - error = got_repo_open(&repo, - worktree ? got_worktree_get_repo_path(worktree) : cwd, - gitconfig_path, pack_fds); - if (error != NULL) - goto done; - - if (worktree != NULL) { - error = worktree_has_logmsg_ref("merge", worktree, repo); - if (error) - goto done; - } - - error = apply_unveil(got_repo_get_path(repo), 0, - worktree ? got_worktree_get_root_path(worktree) : NULL); - if (error) - goto done; - - error = check_rebase_or_histedit_in_progress(worktree); - if (error) - goto done; - - error = got_worktree_merge_in_progress(&merge_in_progress, worktree, - repo); - if (error) - goto done; - - if (merge_in_progress && !(abort_merge || continue_merge)) { - error = got_error(GOT_ERR_MERGE_BUSY); - goto done; - } - - if (!merge_in_progress && (abort_merge || continue_merge)) { - error = got_error(GOT_ERR_NOT_MERGING); - goto done; - } - - if (abort_merge) { - error = got_worktree_merge_continue(&branch_name, - &branch_tip, &fileindex, worktree, repo); - if (error) - goto done; - error = got_worktree_merge_abort(worktree, fileindex, repo, - abort_progress, &upa); - if (error) - goto done; - printf("Merge of %s aborted\n", branch_name); - goto done; /* nothing else to do */ - } - - if (strncmp(got_worktree_get_head_ref_name(worktree), - "refs/heads/", 11) != 0) { - error = got_error_fmt(GOT_ERR_COMMIT_BRANCH, - "work tree's current branch %s is outside the " - "\"refs/heads/\" reference namespace; " - "update -b required", - got_worktree_get_head_ref_name(worktree)); - goto done; - } - - error = get_author(&author, repo, worktree); - if (error) - goto done; - - error = got_ref_open(&wt_branch, repo, - got_worktree_get_head_ref_name(worktree), 0); - if (error) - goto done; - error = got_ref_resolve(&wt_branch_tip, repo, wt_branch); - if (error) - goto done; - - if (continue_merge) { - struct got_object_id *base_commit_id; - base_commit_id = got_worktree_get_base_commit_id(worktree); - if (got_object_id_cmp(wt_branch_tip, base_commit_id) != 0) { - error = got_error(GOT_ERR_MERGE_COMMIT_OUT_OF_DATE); - goto done; - } - error = got_worktree_merge_continue(&branch_name, - &branch_tip, &fileindex, worktree, repo); - if (error) - goto done; - } else { - error = got_ref_open(&branch, repo, argv[0], 0); - if (error != NULL) - goto done; - branch_name = strdup(got_ref_get_name(branch)); - if (branch_name == NULL) { - error = got_error_from_errno("strdup"); - goto done; - } - error = got_ref_resolve(&branch_tip, repo, branch); - if (error) - goto done; - } - - error = got_commit_graph_find_youngest_common_ancestor(&yca_id, - wt_branch_tip, branch_tip, 0, repo, - check_cancelled, NULL); - if (error && error->code != GOT_ERR_ANCESTRY) - goto done; - - if (!continue_merge) { - error = check_path_prefix(wt_branch_tip, branch_tip, - got_worktree_get_path_prefix(worktree), - GOT_ERR_MERGE_PATH, repo); - if (error) - goto done; - error = got_worktree_merge_prepare(&fileindex, worktree, repo); - if (error) - goto done; - if (prefer_fast_forward && yca_id && - got_object_id_cmp(wt_branch_tip, yca_id) == 0) { - struct got_pathlist_head paths; - if (interrupt_merge) { - error = got_error_fmt(GOT_ERR_BAD_OPTION, - "there are no changes to merge since %s " - "is already based on %s; merge cannot be " - "interrupted for amending; -n", - branch_name, got_ref_get_name(wt_branch)); - goto done; - } - printf("Forwarding %s to %s\n", - got_ref_get_name(wt_branch), branch_name); - error = got_ref_change_ref(wt_branch, branch_tip); - if (error) - goto done; - error = got_ref_write(wt_branch, repo); - if (error) - goto done; - error = got_worktree_set_base_commit_id(worktree, repo, - branch_tip); - if (error) - goto done; - TAILQ_INIT(&paths); - error = got_pathlist_append(&paths, "", NULL); - if (error) - goto done; - error = got_worktree_checkout_files(worktree, - &paths, repo, update_progress, &upa, - check_cancelled, NULL); - got_pathlist_free(&paths, GOT_PATHLIST_FREE_NONE); - if (error) - goto done; - if (upa.did_something) { - char *id_str; - error = got_object_id_str(&id_str, branch_tip); - if (error) - goto done; - printf("Updated to commit %s\n", id_str); - free(id_str); - } else - printf("Already up-to-date\n"); - print_update_progress_stats(&upa); - goto done; - } - error = got_worktree_merge_write_refs(worktree, branch, repo); - if (error) - goto done; - - error = got_worktree_merge_branch(worktree, fileindex, - yca_id, branch_tip, repo, update_progress, &upa, - check_cancelled, NULL); - if (error) - goto done; - print_merge_progress_stats(&upa); - if (!upa.did_something) { - error = got_worktree_merge_abort(worktree, fileindex, - repo, abort_progress, &upa); - if (error) - goto done; - printf("Already up-to-date\n"); - goto done; - } - } - - if (interrupt_merge) { - error = got_worktree_merge_postpone(worktree, fileindex); - if (error) - goto done; - printf("Merge of %s interrupted on request\n", branch_name); - } else if (upa.conflicts > 0 || upa.missing > 0 || - upa.not_deleted > 0 || upa.unversioned > 0) { - error = got_worktree_merge_postpone(worktree, fileindex); - if (error) - goto done; - if (upa.conflicts > 0 && upa.missing == 0 && - upa.not_deleted == 0 && upa.unversioned == 0) { - error = got_error_msg(GOT_ERR_CONFLICTS, - "conflicts must be resolved before merging " - "can continue"); - } else if (upa.conflicts > 0) { - error = got_error_msg(GOT_ERR_CONFLICTS, - "conflicts must be resolved before merging " - "can continue; changes destined for some " - "files were not yet merged and " - "should be merged manually if required before the " - "merge operation is continued"); - } else { - error = got_error_msg(GOT_ERR_CONFLICTS, - "changes destined for some " - "files were not yet merged and should be " - "merged manually if required before the " - "merge operation is continued"); - } - goto done; - } else { - error = got_worktree_merge_commit(&merge_commit_id, worktree, - fileindex, author, NULL, 1, branch_tip, branch_name, - allow_conflict, repo, continue_merge ? print_status : NULL, - NULL); - if (error) - goto done; - error = got_worktree_merge_complete(worktree, fileindex, repo); - if (error) - goto done; - error = got_object_id_str(&id_str, merge_commit_id); - if (error) - goto done; - printf("Merged %s into %s: %s\n", branch_name, - got_worktree_get_head_ref_name(worktree), - id_str); - - } -done: - free(gitconfig_path); - free(id_str); - free(merge_commit_id); - free(author); - free(branch_tip); - free(branch_name); - free(yca_id); - if (branch) - got_ref_close(branch); - if (wt_branch) - got_ref_close(wt_branch); - if (worktree) - got_worktree_close(worktree); - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - return error; -} - -__dead static void -usage_stage(void) -{ - fprintf(stderr, "usage: %s stage [-lpS] [-F response-script] " - "[path ...]\n", getprogname()); - exit(1); -} - -static const struct got_error * -print_stage(void *arg, unsigned char status, unsigned char staged_status, - const char *path, struct got_object_id *blob_id, - struct got_object_id *staged_blob_id, struct got_object_id *commit_id, - int dirfd, const char *de_name) -{ - const struct got_error *err = NULL; - char *id_str = NULL; - - if (staged_status != GOT_STATUS_ADD && - staged_status != GOT_STATUS_MODIFY && - staged_status != GOT_STATUS_DELETE) - return NULL; - - if (staged_status == GOT_STATUS_ADD || - staged_status == GOT_STATUS_MODIFY) - err = got_object_id_str(&id_str, staged_blob_id); - else - err = got_object_id_str(&id_str, blob_id); - if (err) - return err; - - printf("%s %c %s\n", id_str, staged_status, path); - free(id_str); - return NULL; -} - -static const struct got_error * -cmd_stage(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; - int ch, list_stage = 0, pflag = 0, allow_bad_symlinks = 0; - FILE *patch_script_file = NULL; - const char *patch_script_path = NULL; - struct choose_patch_arg cpa; - int *pack_fds = NULL; - - TAILQ_INIT(&paths); - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " - "unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "F:lpS")) != -1) { - switch (ch) { - case 'F': - patch_script_path = optarg; - break; - case 'l': - list_stage = 1; - break; - case 'p': - pflag = 1; - break; - case 'S': - allow_bad_symlinks = 1; - break; - default: - usage_stage(); - /* NOTREACHED */ - } - } - - argc -= optind; - argv += optind; - - if (list_stage && (pflag || patch_script_path)) - errx(1, "-l option cannot be used with other options"); - if (patch_script_path && !pflag) - errx(1, "-F option can only be used together with -p option"); - - cwd = getcwd(NULL, 0); - if (cwd == NULL) { - error = got_error_from_errno("getcwd"); - goto done; - } - - error = got_repo_pack_fds_open(&pack_fds); - if (error != NULL) - goto done; - - error = got_worktree_open(&worktree, cwd); - if (error) { - if (error->code == GOT_ERR_NOT_WORKTREE) - error = wrap_not_worktree_error(error, "stage", cwd); - goto done; - } - - error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), - NULL, pack_fds); - if (error != NULL) - goto done; - - if (patch_script_path) { - patch_script_file = fopen(patch_script_path, "re"); - if (patch_script_file == NULL) { - error = got_error_from_errno2("fopen", - patch_script_path); - goto done; - } - } - error = apply_unveil(got_repo_get_path(repo), 0, - got_worktree_get_root_path(worktree)); - if (error) - goto done; - - error = check_merge_in_progress(worktree, repo); - if (error) - goto done; - - error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); - if (error) - goto done; - - if (list_stage) - error = got_worktree_status(worktree, &paths, repo, 0, - print_stage, NULL, check_cancelled, NULL); - else { - cpa.patch_script_file = patch_script_file; - cpa.action = "stage"; - error = got_worktree_stage(worktree, &paths, - pflag ? NULL : print_status, NULL, - pflag ? choose_patch : NULL, &cpa, - allow_bad_symlinks, repo); - } -done: - if (patch_script_file && fclose(patch_script_file) == EOF && - error == NULL) - error = got_error_from_errno2("fclose", patch_script_path); - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (worktree) - got_worktree_close(worktree); - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - got_pathlist_free(&paths, GOT_PATHLIST_FREE_PATH); - free(cwd); - return error; -} - -__dead static void -usage_unstage(void) -{ - fprintf(stderr, "usage: %s unstage [-p] [-F response-script] " - "[path ...]\n", getprogname()); - exit(1); -} - - -static const struct got_error * -cmd_unstage(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; - int ch, pflag = 0; - struct got_update_progress_arg upa; - FILE *patch_script_file = NULL; - const char *patch_script_path = NULL; - struct choose_patch_arg cpa; - int *pack_fds = NULL; - - TAILQ_INIT(&paths); - -#ifndef PROFILE - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " - "unveil", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "F:p")) != -1) { - switch (ch) { - case 'F': - patch_script_path = optarg; - break; - case 'p': - pflag = 1; - break; - default: - usage_unstage(); - /* NOTREACHED */ - } - } - - argc -= optind; - argv += optind; - - if (patch_script_path && !pflag) - errx(1, "-F option can only be used together with -p option"); - - cwd = getcwd(NULL, 0); - if (cwd == NULL) { - error = got_error_from_errno("getcwd"); - goto done; - } - - error = got_repo_pack_fds_open(&pack_fds); - if (error != NULL) - goto done; - - error = got_worktree_open(&worktree, cwd); - if (error) { - if (error->code == GOT_ERR_NOT_WORKTREE) - error = wrap_not_worktree_error(error, "unstage", cwd); - goto done; - } - - error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), - NULL, pack_fds); - if (error != NULL) - goto done; - - if (patch_script_path) { - patch_script_file = fopen(patch_script_path, "re"); - if (patch_script_file == NULL) { - error = got_error_from_errno2("fopen", - patch_script_path); - goto done; - } - } - - error = apply_unveil(got_repo_get_path(repo), 0, - got_worktree_get_root_path(worktree)); - if (error) - goto done; - - error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); - if (error) - goto done; - - cpa.patch_script_file = patch_script_file; - cpa.action = "unstage"; - memset(&upa, 0, sizeof(upa)); - error = got_worktree_unstage(worktree, &paths, update_progress, - &upa, pflag ? choose_patch : NULL, &cpa, repo); - if (!error) - print_merge_progress_stats(&upa); -done: - if (patch_script_file && fclose(patch_script_file) == EOF && - error == NULL) - error = got_error_from_errno2("fclose", patch_script_path); - if (repo) { - const struct got_error *close_err = got_repo_close(repo); - if (error == NULL) - error = close_err; - } - if (worktree) - got_worktree_close(worktree); - if (pack_fds) { - const struct got_error *pack_err = - got_repo_pack_fds_close(pack_fds); - if (error == NULL) - error = pack_err; - } - got_pathlist_free(&paths, GOT_PATHLIST_FREE_PATH); - free(cwd); + if (wanted_ref != NULL && !found) + err = got_error_fmt(GOT_ERR_NOT_REF, "%s", wanted_ref); + +done: + free(id); + free(uuidstr); + got_ref_list_free(&refs); + got_pathlist_free(&paths, GOT_PATHLIST_FREE_ALL); + if (refs_idmap) + got_reflist_object_id_map_free(refs_idmap); + if (commit) + got_object_commit_close(commit); + return err; +} + +/* + * Create new temp "logmsg" ref of the backed-out or cherrypicked commit + * identified by id for log messages to prepopulate the editor on commit. + */ +static const struct got_error * +logmsg_ref(struct got_object_id *id, const char *prefix, + struct got_worktree *worktree, struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *idstr, *ref = NULL, *refname = NULL; + int histedit_in_progress; + int rebase_in_progress, merge_in_progress; + + /* + * Silenty refuse to create merge reference if any histedit, merge, + * or rebase operation is in progress. + */ + err = got_worktree_histedit_in_progress(&histedit_in_progress, + worktree); + if (err) + return err; + if (histedit_in_progress) + return NULL; + + err = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); + if (err) + return err; + if (rebase_in_progress) + return NULL; + + err = got_worktree_merge_in_progress(&merge_in_progress, worktree, + repo); + if (err) + return err; + if (merge_in_progress) + return NULL; + + err = got_object_id_str(&idstr, id); + if (err) + return err; + + err = got_worktree_get_logmsg_ref_name(&refname, worktree, prefix); + if (err) + goto done; + + if (asprintf(&ref, "%s-%s", refname, idstr) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + err = create_ref(ref, got_worktree_get_base_commit_id(worktree), + -1, repo); +done: + free(ref); + free(idstr); + free(refname); + return err; +} + +__dead static void +usage_cherrypick(void) +{ + fprintf(stderr, "usage: %s cherrypick [-lX] [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; + int ch, list_refs = 0, remove_refs = 0; + struct got_update_progress_arg upa; + int *pack_fds = NULL; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "lX")) != -1) { + switch (ch) { + case 'l': + list_refs = 1; + break; + case 'X': + remove_refs = 1; + break; + default: + usage_cherrypick(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (list_refs || remove_refs) { + if (argc != 0 && argc != 1) + usage_cherrypick(); + } else if (argc != 1) + usage_cherrypick(); + if (list_refs && remove_refs) + option_conflict('l', 'X'); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_repo_pack_fds_open(&pack_fds); + if (error != NULL) + goto done; + + error = got_worktree_open(&worktree, cwd); + if (error) { + if (list_refs || remove_refs) { + if (error->code != GOT_ERR_NOT_WORKTREE) + goto done; + } else { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, + "cherrypick", cwd); + goto done; + } + } + + error = got_repo_open(&repo, + worktree ? got_worktree_get_repo_path(worktree) : cwd, + NULL, pack_fds); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (list_refs || remove_refs) { + error = process_logmsg_refs(GOT_WORKTREE_CHERRYPICK_REF_PREFIX, + GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN, + argc == 1 ? argv[0] : NULL, remove_refs, worktree, repo); + goto done; + } + + error = got_repo_match_object_id(&commit_id, NULL, argv[0], + GOT_OBJ_TYPE_COMMIT, NULL, repo); + if (error) + goto done; + error = got_object_id_str(&commit_id_str, commit_id); + if (error) + goto done; + + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + goto done; + pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit)); + memset(&upa, 0, sizeof(upa)); + error = got_worktree_merge_files(worktree, pid ? &pid->id : NULL, + commit_id, repo, update_progress, &upa, check_cancelled, + NULL); + if (error != NULL) + goto done; + + if (upa.did_something) { + error = logmsg_ref(commit_id, + GOT_WORKTREE_CHERRYPICK_REF_PREFIX, worktree, repo); + if (error) + goto done; + printf("Merged commit %s\n", commit_id_str); + } + print_merge_progress_stats(&upa); +done: + free(cwd); + if (commit) + got_object_commit_close(commit); + free(commit_id_str); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *close_err = got_repo_close(repo); + if (error == NULL) + error = close_err; + } + if (pack_fds) { + const struct got_error *pack_err = + got_repo_pack_fds_close(pack_fds); + if (error == NULL) + error = pack_err; + } + + return error; +} + +__dead static void +usage_backout(void) +{ + fprintf(stderr, "usage: %s backout [-lX] [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; + int ch, list_refs = 0, remove_refs = 0; + struct got_update_progress_arg upa; + int *pack_fds = NULL; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "lX")) != -1) { + switch (ch) { + case 'l': + list_refs = 1; + break; + case 'X': + remove_refs = 1; + break; + default: + usage_backout(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (list_refs || remove_refs) { + if (argc != 0 && argc != 1) + usage_backout(); + } else if (argc != 1) + usage_backout(); + if (list_refs && remove_refs) + option_conflict('l', 'X'); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_repo_pack_fds_open(&pack_fds); + if (error != NULL) + goto done; + + error = got_worktree_open(&worktree, cwd); + if (error) { + if (list_refs || remove_refs) { + if (error->code != GOT_ERR_NOT_WORKTREE) + goto done; + } else { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, + "backout", cwd); + goto done; + } + } + + error = got_repo_open(&repo, + worktree ? got_worktree_get_repo_path(worktree) : cwd, + NULL, pack_fds); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (list_refs || remove_refs) { + error = process_logmsg_refs(GOT_WORKTREE_BACKOUT_REF_PREFIX, + GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN, + argc == 1 ? argv[0] : NULL, remove_refs, worktree, repo); + goto done; + } + + error = got_repo_match_object_id(&commit_id, NULL, argv[0], + GOT_OBJ_TYPE_COMMIT, NULL, repo); + if (error) + goto done; + error = got_object_id_str(&commit_id_str, commit_id); + if (error) + goto done; + + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + goto done; + pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit)); + if (pid == NULL) { + error = got_error(GOT_ERR_ROOT_COMMIT); + goto done; + } + + memset(&upa, 0, sizeof(upa)); + error = got_worktree_merge_files(worktree, commit_id, &pid->id, + repo, update_progress, &upa, check_cancelled, NULL); + if (error != NULL) + goto done; + + if (upa.did_something) { + error = logmsg_ref(commit_id, GOT_WORKTREE_BACKOUT_REF_PREFIX, + worktree, repo); + if (error) + goto done; + printf("Backed out commit %s\n", commit_id_str); + } + print_merge_progress_stats(&upa); +done: + free(cwd); + if (commit) + got_object_commit_close(commit); + free(commit_id_str); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *close_err = got_repo_close(repo); + if (error == NULL) + error = close_err; + } + if (pack_fds) { + const struct got_error *pack_err = + got_repo_pack_fds_close(pack_fds); + if (error == NULL) + error = pack_err; + } return error; }