commit 417a6e492637a0a223ba0d9830101a574ee1ed50 from: Stefan Sperling date: Sat Sep 26 19:16:51 2020 UTC add a test case to trigger a bug where diff chunks grow way too much context commit - 9ecc16c7829c00559730041b0b12a9ce566c0d27 commit + 417a6e492637a0a223ba0d9830101a574ee1ed50 blob - 10fc21a065ea7662ada86afa15db95344d7e98e4 blob + 7dd803c57dde72920901af09368460ef3b26bd48 --- test/expect001.diff +++ test/expect001.diff @@ -4,9 +4,9 @@ -A -B C --A ++B + A B -+A - B +-B A +C blob - 1ce7fb3bddfe8db6c469d14ef1dbe056aa2eb0c2 blob + d6c64921df76e942bca9dbaaa24e90a5ccbfcb52 --- test/expect002.diff +++ test/expect002.diff @@ -4,10 +4,10 @@ -A -B C --A ++B + A B -+A - B +-B A +C X blob - /dev/null blob + 9b0cebedc097d96f20f592688813f696cd2873bf (mode 644) --- /dev/null +++ test/expect016.diff @@ -0,0 +1,30 @@ +--- test016.left.txt ++++ test016.right.txt +@@ -1063,7 +1063,7 @@ + const char *uri, *dirname; + char *proto, *host, *port, *repo_name, *server_path; + char *default_destdir = NULL, *id_str = NULL; +- const char *repo_path; ++ const char *repo_path, *remote_repo_path; + struct got_repository *repo = NULL; + struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs; + struct got_pathlist_entry *pe; +@@ -1387,6 +1387,9 @@ + goto done; + } + got_path_strip_trailing_slashes(server_path); ++ remote_repo_path = server_path; ++ while (remote_repo_path[0] == '/') ++ remote_repo_path++; + if (asprintf(&gotconfig, + "remote \"%s\" {\n" + "\tserver %s\n" +@@ -1397,7 +1400,7 @@ + "}\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, host, proto, + port ? "\tport " : "", port ? port : "", port ? "\n" : "", +- server_path, ++ remote_repo_path, + mirror_references ? "\tmirror-references yes\n" : "") == -1) { + error = got_error_from_errno("asprintf"); + goto done; blob - f2b8d42de7348bdf55fa8fa3e22691c387e77fd2 blob + e3f7d395587ff642f388ff35f4d434f538f543aa --- test/verify_all.sh +++ test/verify_all.sh @@ -8,18 +8,20 @@ verify_diff_script() { orig_left="$1" orig_right="$2" the_diff="$3" - diff_opts="$4" - expected_diff="$5" + expected_diff="$4" + diff_opts="$5" verify_left="verify.$orig_left" verify_right="verify.$orig_right" - if [ -n "$diff_opts" ]; then + if [ -e "$expected_diff" ]; then + echo cmp "$got_diff" "$expected_diff" if ! cmp "$got_diff" "$expected_diff" ; then echo "FAIL: $got_diff != $expected_diff" return 1 fi - elif [ "x$diff_type" = "xunidiff" ]; then + fi + if [ -z "$diff_opts" -a "x$diff_type" = "xunidiff" ]; then cp "$orig_left" "$verify_right" patch --quiet -u "$verify_right" "$the_diff" if ! cmp "$orig_right" "$verify_right" ; then @@ -33,7 +35,7 @@ verify_diff_script() { echo "FAIL: $orig_left != $verify_left" return 1 fi - else + elif [ -z "$diff_opts" ]; then tail -n +3 "$the_diff" | grep -v "^+" | sed 's/^.//' > "$verify_left" tail -n +3 "$the_diff" | grep -v "^-" | sed 's/^.//' > "$verify_right" @@ -59,6 +61,6 @@ for left in test*.left* ; do "$diff_prog" $diff_opts "$left" "$right" > "$got_diff" set -e - verify_diff_script "$left" "$right" "$got_diff" "$diff_opts" "$expected_diff" + verify_diff_script "$left" "$right" "$got_diff" "$expected_diff" "$diff_opts" set +e done blob - /dev/null blob + 2abb63153d40d88e1af8f211eb545027c1061b0a (mode 644) --- /dev/null +++ test/test016.left.txt @@ -0,0 +1,9686 @@ +/* + * Copyright (c) 2017 Martin Pieuchot + * Copyright (c) 2018, 2019, 2020 Stefan Sperling + * Copyright (c) 2020 Ori Bernstein + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_version.h" +#include "got_error.h" +#include "got_object.h" +#include "got_reference.h" +#include "got_repository.h" +#include "got_path.h" +#include "got_cancel.h" +#include "got_worktree.h" +#include "got_diff.h" +#include "got_commit_graph.h" +#include "got_fetch.h" +#include "got_blame.h" +#include "got_privsep.h" +#include "got_opentemp.h" +#include "got_gotconfig.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +static volatile sig_atomic_t sigint_received; +static volatile sig_atomic_t sigpipe_received; + +static void +catch_sigint(int signo) +{ + sigint_received = 1; +} + +static void +catch_sigpipe(int signo) +{ + sigpipe_received = 1; +} + + +struct got_cmd { + const char *cmd_name; + const struct got_error *(*cmd_main)(int, char *[]); + void (*cmd_usage)(void); + const char *cmd_alias; +}; + +__dead static void usage(int); +__dead static void usage_init(void); +__dead static void usage_import(void); +__dead static void usage_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); +__dead static void usage_diff(void); +__dead static void usage_blame(void); +__dead static void usage_tree(void); +__dead static void usage_status(void); +__dead static void usage_ref(void); +__dead static void usage_branch(void); +__dead static void usage_tag(void); +__dead static void usage_add(void); +__dead static void usage_remove(void); +__dead static void usage_revert(void); +__dead static void usage_commit(void); +__dead static void usage_cherrypick(void); +__dead static void usage_backout(void); +__dead static void usage_rebase(void); +__dead static void usage_histedit(void); +__dead static void usage_integrate(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_init(int, char *[]); +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 *[]); +static const struct got_error* cmd_diff(int, char *[]); +static const struct got_error* cmd_blame(int, char *[]); +static const struct got_error* cmd_tree(int, char *[]); +static const struct got_error* cmd_status(int, char *[]); +static const struct got_error* cmd_ref(int, char *[]); +static const struct got_error* cmd_branch(int, char *[]); +static const struct got_error* cmd_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_revert(int, char *[]); +static const struct got_error* cmd_commit(int, char *[]); +static const struct got_error* cmd_cherrypick(int, char *[]); +static const struct got_error* cmd_backout(int, char *[]); +static const struct got_error* cmd_rebase(int, char *[]); +static const struct got_error* cmd_histedit(int, char *[]); +static const struct got_error* cmd_integrate(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 struct got_cmd got_commands[] = { + { "init", cmd_init, usage_init, "" }, + { "import", cmd_import, usage_import, "im" }, + { "clone", cmd_clone, usage_clone, "cl" }, + { "fetch", cmd_fetch, usage_fetch, "fe" }, + { "checkout", cmd_checkout, usage_checkout, "co" }, + { "update", cmd_update, usage_update, "up" }, + { "log", cmd_log, usage_log, "" }, + { "diff", cmd_diff, usage_diff, "di" }, + { "blame", cmd_blame, usage_blame, "bl" }, + { "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" }, + { "revert", cmd_revert, usage_revert, "rv" }, + { "commit", cmd_commit, usage_commit, "ci" }, + { "cherrypick", cmd_cherrypick, usage_cherrypick, "cy" }, + { "backout", cmd_backout, usage_backout, "bo" }, + { "rebase", cmd_rebase, usage_rebase, "rb" }, + { "histedit", cmd_histedit, usage_histedit, "he" }, + { "integrate", cmd_integrate, usage_integrate,"ig" }, + { "stage", cmd_stage, usage_stage, "sg" }, + { "unstage", cmd_unstage, usage_unstage, "ug" }, + { "cat", cmd_cat, usage_cat, "" }, + { "info", cmd_info, usage_info, "" }, +}; + +static void +list_commands(void) +{ + int i; + + fprintf(stderr, "commands:"); + for (i = 0; i < nitems(got_commands); i++) { + struct got_cmd *cmd = &got_commands[i]; + fprintf(stderr, " %s", cmd->cmd_name); + } + fputc('\n', stderr); +} + +int +main(int argc, char *argv[]) +{ + struct got_cmd *cmd; + unsigned int i; + int ch; + int hflag = 0, Vflag = 0; + static struct option longopts[] = { + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0} + }; + + setlocale(LC_CTYPE, ""); + + while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) { + switch (ch) { + case 'h': + hflag = 1; + break; + case 'V': + Vflag = 1; + break; + default: + usage(hflag); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (Vflag) { + got_version_print_str(); + return 1; + } + + if (argc <= 0) + usage(hflag); + + signal(SIGINT, catch_sigint); + signal(SIGPIPE, catch_sigpipe); + + for (i = 0; i < nitems(got_commands); i++) { + const struct got_error *error; + + cmd = &got_commands[i]; + + if (strcmp(cmd->cmd_name, argv[0]) != 0 && + strcmp(cmd->cmd_alias, argv[0]) != 0) + continue; + + if (hflag) + got_commands[i].cmd_usage(); + + error = got_commands[i].cmd_main(argc, argv); + if (error && error->code != GOT_ERR_CANCELLED && + error->code != GOT_ERR_PRIVSEP_EXIT && + !(sigpipe_received && + error->code == GOT_ERR_ERRNO && errno == EPIPE) && + !(sigint_received && + error->code == GOT_ERR_ERRNO && errno == EINTR)) { + fprintf(stderr, "%s: %s\n", getprogname(), error->msg); + return 1; + } + + return 0; + } + + fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]); + list_commands(); + return 1; +} + +__dead static void +usage(int hflag) +{ + fprintf(stderr, "usage: %s [-h] [-V | --version] command [arg ...]\n", + getprogname()); + if (hflag) + list_commands(); + exit(1); +} + +static const struct got_error * +get_editor(char **abspath) +{ + const struct got_error *err = NULL; + const char *editor; + + *abspath = NULL; + + editor = getenv("VISUAL"); + if (editor == NULL) + editor = getenv("EDITOR"); + + if (editor) { + err = got_path_find_prog(abspath, editor); + if (err) + return err; + } + + if (*abspath == NULL) { + *abspath = strdup("/bin/ed"); + if (*abspath == NULL) + return got_error_from_errno("strdup"); + } + + return NULL; +} + +static const struct got_error * +apply_unveil(const char *repo_path, int repo_read_only, + const char *worktree_path) +{ + const struct got_error *err; + +#ifdef PROFILE + if (unveil("gmon.out", "rwc") != 0) + return got_error_from_errno2("unveil", "gmon.out"); +#endif + if (repo_path && unveil(repo_path, repo_read_only ? "r" : "rwc") != 0) + return got_error_from_errno2("unveil", repo_path); + + if (worktree_path && unveil(worktree_path, "rwc") != 0) + return got_error_from_errno2("unveil", worktree_path); + + if (unveil(GOT_TMPDIR_STR, "rwc") != 0) + return got_error_from_errno2("unveil", GOT_TMPDIR_STR); + + err = got_privsep_unveil_exec_helpers(); + if (err != NULL) + return err; + + if (unveil(NULL, NULL) != 0) + return got_error_from_errno("unveil"); + + return NULL; +} + +__dead static void +usage_init(void) +{ + fprintf(stderr, "usage: %s init repository-path\n", getprogname()); + exit(1); +} + +static const struct got_error * +cmd_init(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + char *repo_path = NULL; + int ch; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_init(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_init(); + + repo_path = strdup(argv[0]); + if (repo_path == NULL) + return got_error_from_errno("strdup"); + + got_path_strip_trailing_slashes(repo_path); + + error = got_path_mkdir(repo_path); + if (error && + !(error->code == GOT_ERR_ERRNO && errno == EEXIST)) + goto done; + + error = apply_unveil(repo_path, 0, NULL); + if (error) + goto done; + + error = got_repo_init(repo_path); +done: + free(repo_path); + return error; +} + +__dead static void +usage_import(void) +{ + fprintf(stderr, "usage: %s import [-b branch] [-m message] " + "[-r repository-path] [-I pattern] path\n", getprogname()); + exit(1); +} + +int +spawn_editor(const char *editor, const char *file) +{ + pid_t pid; + sig_t sighup, sigint, sigquit; + int st = -1; + + sighup = signal(SIGHUP, SIG_IGN); + sigint = signal(SIGINT, SIG_IGN); + sigquit = signal(SIGQUIT, SIG_IGN); + + switch (pid = fork()) { + case -1: + goto doneediting; + case 0: + execl(editor, editor, file, (char *)NULL); + _exit(127); + } + + while (waitpid(pid, &st, 0) == -1) + if (errno != EINTR) + break; + +doneediting: + (void)signal(SIGHUP, sighup); + (void)signal(SIGINT, sigint); + (void)signal(SIGQUIT, sigquit); + + if (!WIFEXITED(st)) { + errno = EINTR; + return -1; + } + + return WEXITSTATUS(st); +} + +static const struct got_error * +edit_logmsg(char **logmsg, const char *editor, const char *logmsg_path, + const char *initial_content) +{ + const struct got_error *err = NULL; + char buf[1024]; + struct stat st, st2; + FILE *fp; + int content_changed = 0; + size_t len; + + *logmsg = NULL; + + if (stat(logmsg_path, &st) == -1) + return got_error_from_errno2("stat", logmsg_path); + + if (spawn_editor(editor, logmsg_path) == -1) + return got_error_from_errno("failed spawning editor"); + + if (stat(logmsg_path, &st2) == -1) + return got_error_from_errno("stat"); + + if (st.st_mtime == st2.st_mtime && st.st_size == st2.st_size) + return got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, + "no changes made to commit message, aborting"); + + *logmsg = malloc(st2.st_size + 1); + if (*logmsg == NULL) + return got_error_from_errno("malloc"); + (*logmsg)[0] = '\0'; + len = 0; + + fp = fopen(logmsg_path, "r"); + if (fp == NULL) { + err = got_error_from_errno("fopen"); + goto done; + } + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (!content_changed && strcmp(buf, initial_content) != 0) + content_changed = 1; + if (buf[0] == '#' || (len == 0 && buf[0] == '\n')) + continue; /* remove comments and leading empty lines */ + len = strlcat(*logmsg, buf, st2.st_size); + } + fclose(fp); + + while (len > 0 && (*logmsg)[len - 1] == '\n') { + (*logmsg)[len - 1] = '\0'; + len--; + } + + if (len == 0 || !content_changed) + err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, + "commit message cannot be empty, aborting"); +done: + if (err) { + free(*logmsg); + *logmsg = NULL; + } + return err; +} + +static const struct got_error * +collect_import_msg(char **logmsg, char **logmsg_path, const char *editor, + const char *path_dir, const char *branch_name) +{ + char *initial_content = NULL; + const struct got_error *err = NULL; + int initial_content_len; + int fd = -1; + + initial_content_len = asprintf(&initial_content, + "\n# %s to be imported to branch %s\n", path_dir, + branch_name); + if (initial_content_len == -1) + return got_error_from_errno("asprintf"); + + err = got_opentemp_named_fd(logmsg_path, &fd, + GOT_TMPDIR_STR "/got-importmsg"); + if (err) + goto done; + + if (write(fd, initial_content, initial_content_len) == -1) { + err = got_error_from_errno2("write", *logmsg_path); + goto done; + } + + err = edit_logmsg(logmsg, editor, *logmsg_path, initial_content); +done: + if (fd != -1 && close(fd) == -1 && err == NULL) + err = got_error_from_errno2("close", *logmsg_path); + free(initial_content); + if (err) { + free(*logmsg_path); + *logmsg_path = NULL; + } + return err; +} + +static const struct got_error * +import_progress(void *arg, const char *path) +{ + printf("A %s\n", path); + return NULL; +} + +static const struct got_error * +get_author(char **author, struct got_repository *repo, + struct got_worktree *worktree) +{ + const struct got_error *err = NULL; + const char *got_author = NULL, *name, *email; + const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL; + + *author = NULL; + + if (worktree) + worktree_conf = got_worktree_get_gotconfig(worktree); + repo_conf = got_repo_get_gotconfig(repo); + + /* + * Priority of potential author information sources, from most + * significant to least significant: + * 1) work tree's .got/got.conf file + * 2) repository's got.conf file + * 3) repository's git config file + * 4) environment variables + * 5) global git config files (in user's home directory or /etc) + */ + + if (worktree_conf) + got_author = got_gotconfig_get_author(worktree_conf); + if (got_author == NULL) + got_author = got_gotconfig_get_author(repo_conf); + if (got_author == NULL) { + name = got_repo_get_gitconfig_author_name(repo); + email = got_repo_get_gitconfig_author_email(repo); + if (name && email) { + if (asprintf(author, "%s <%s>", name, email) == -1) + return got_error_from_errno("asprintf"); + return NULL; + } + + got_author = getenv("GOT_AUTHOR"); + if (got_author == NULL) { + name = got_repo_get_global_gitconfig_author_name(repo); + email = got_repo_get_global_gitconfig_author_email( + repo); + if (name && email) { + if (asprintf(author, "%s <%s>", name, email) + == -1) + return got_error_from_errno("asprintf"); + return NULL; + } + /* TODO: Look up user in password database? */ + return got_error(GOT_ERR_COMMIT_NO_AUTHOR); + } + } + + *author = strdup(got_author); + if (*author == NULL) + return got_error_from_errno("strdup"); + + /* + * Really dumb email address check; we're only doing this to + * avoid git's object parser breaking on commits we create. + */ + while (*got_author && *got_author != '<') + got_author++; + if (*got_author != '<') { + err = got_error(GOT_ERR_COMMIT_NO_EMAIL); + goto done; + } + while (*got_author && *got_author != '@') + got_author++; + if (*got_author != '@') { + err = got_error(GOT_ERR_COMMIT_NO_EMAIL); + goto done; + } + while (*got_author && *got_author != '>') + got_author++; + if (*got_author != '>') + err = got_error(GOT_ERR_COMMIT_NO_EMAIL); +done: + if (err) { + free(*author); + *author = NULL; + } + return err; +} + +static const struct got_error * +get_gitconfig_path(char **gitconfig_path) +{ + const char *homedir = getenv("HOME"); + + *gitconfig_path = NULL; + if (homedir) { + if (asprintf(gitconfig_path, "%s/.gitconfig", homedir) == -1) + return got_error_from_errno("asprintf"); + + } + return NULL; +} + +static const struct got_error * +cmd_import(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + char *path_dir = NULL, *repo_path = NULL, *logmsg = NULL; + char *gitconfig_path = NULL, *editor = NULL, *author = NULL; + const char *branch_name = "main"; + char *refname = NULL, *id_str = NULL, *logmsg_path = NULL; + struct got_repository *repo = NULL; + struct got_reference *branch_ref = NULL, *head_ref = NULL; + struct got_object_id *new_commit_id = NULL; + int ch; + struct got_pathlist_head ignores; + struct got_pathlist_entry *pe; + int preserve_logmsg = 0; + + TAILQ_INIT(&ignores); + + while ((ch = getopt(argc, argv, "b:m:r:I:")) != -1) { + switch (ch) { + case 'b': + branch_name = optarg; + break; + case 'm': + logmsg = strdup(optarg); + if (logmsg == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + break; + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) { + error = got_error_from_errno2("realpath", + optarg); + goto done; + } + break; + case 'I': + if (optarg[0] == '\0') + break; + error = got_pathlist_insert(&pe, &ignores, optarg, + NULL); + if (error) + goto done; + break; + default: + usage_import(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", + NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_import(); + + if (repo_path == NULL) { + repo_path = getcwd(NULL, 0); + if (repo_path == NULL) + return got_error_from_errno("getcwd"); + } + got_path_strip_trailing_slashes(repo_path); + error = get_gitconfig_path(&gitconfig_path); + if (error) + goto done; + error = got_repo_open(&repo, repo_path, gitconfig_path); + if (error) + goto done; + + error = get_author(&author, repo, NULL); + if (error) + return error; + + /* + * 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 (asprintf(&refname, "refs/heads/%s", branch_name) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + + error = got_ref_open(&branch_ref, repo, refname, 0); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + } else { + error = got_error_msg(GOT_ERR_BRANCH_EXISTS, + "import target branch already exists"); + goto done; + } + + path_dir = realpath(argv[0], NULL); + if (path_dir == NULL) { + error = got_error_from_errno2("realpath", argv[0]); + goto done; + } + got_path_strip_trailing_slashes(path_dir); + + /* + * unveil(2) traverses exec(2); if an editor is used we have + * to apply unveil after the log message has been written. + */ + if (logmsg == NULL || strlen(logmsg) == 0) { + error = get_editor(&editor); + if (error) + goto done; + free(logmsg); + error = collect_import_msg(&logmsg, &logmsg_path, editor, + path_dir, refname); + if (error) { + if (error->code != GOT_ERR_COMMIT_MSG_EMPTY && + logmsg_path != NULL) + preserve_logmsg = 1; + goto done; + } + } + + if (unveil(path_dir, "r") != 0) { + error = got_error_from_errno2("unveil", path_dir); + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = apply_unveil(got_repo_get_path(repo), 0, NULL); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_repo_import(&new_commit_id, path_dir, logmsg, + author, &ignores, repo, import_progress, NULL); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_alloc(&branch_ref, refname, new_commit_id); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_write(branch_ref, repo); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_object_id_str(&id_str, new_commit_id); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); + if (error) { + if (error->code != GOT_ERR_NOT_REF) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_alloc_symref(&head_ref, GOT_REF_HEAD, + branch_ref); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_write(head_ref, repo); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + } + + printf("Created branch %s with commit %s\n", + got_ref_get_name(branch_ref), id_str); +done: + if (preserve_logmsg) { + fprintf(stderr, "%s: log message preserved in %s\n", + getprogname(), logmsg_path); + } else if (logmsg_path && unlink(logmsg_path) == -1 && error == NULL) + error = got_error_from_errno2("unlink", logmsg_path); + free(logmsg); + free(logmsg_path); + free(repo_path); + free(editor); + free(refname); + free(new_commit_id); + free(id_str); + free(author); + free(gitconfig_path); + if (branch_ref) + got_ref_close(branch_ref); + if (head_ref) + got_ref_close(head_ref); + return error; +} + +__dead static void +usage_clone(void) +{ + fprintf(stderr, "usage: %s clone [-a] [-b branch] [-l] [-m] [-q] [-v] " + "[-R reference] repository-url [directory]\n", getprogname()); + exit(1); +} + +struct got_fetch_progress_arg { + char last_scaled_size[FMT_SCALED_STRSIZE]; + int last_p_indexed; + int last_p_resolved; + int verbosity; +}; + +static const struct got_error * +fetch_progress(void *arg, const char *message, off_t packfile_size, + int nobj_total, int nobj_indexed, int nobj_loose, int nobj_resolved) +{ + struct got_fetch_progress_arg *a = arg; + char scaled_size[FMT_SCALED_STRSIZE]; + int p_indexed, p_resolved; + int print_size = 0, print_indexed = 0, print_resolved = 0; + + if (a->verbosity < 0) + return NULL; + + if (message && message[0] != '\0') { + printf("\rserver: %s", message); + fflush(stdout); + return NULL; + } + + if (packfile_size > 0 || nobj_indexed > 0) { + if (fmt_scaled(packfile_size, scaled_size) == 0 && + (a->last_scaled_size[0] == '\0' || + strcmp(scaled_size, a->last_scaled_size)) != 0) { + print_size = 1; + if (strlcpy(a->last_scaled_size, scaled_size, + FMT_SCALED_STRSIZE) >= FMT_SCALED_STRSIZE) + return got_error(GOT_ERR_NO_SPACE); + } + if (nobj_indexed > 0) { + p_indexed = (nobj_indexed * 100) / nobj_total; + if (p_indexed != a->last_p_indexed) { + a->last_p_indexed = p_indexed; + print_indexed = 1; + print_size = 1; + } + } + if (nobj_resolved > 0) { + p_resolved = (nobj_resolved * 100) / + (nobj_total - nobj_loose); + if (p_resolved != a->last_p_resolved) { + a->last_p_resolved = p_resolved; + print_resolved = 1; + print_indexed = 1; + print_size = 1; + } + } + + } + if (print_size || print_indexed || print_resolved) + printf("\r"); + if (print_size) + printf("%*s fetched", FMT_SCALED_STRSIZE, scaled_size); + if (print_indexed) + printf("; indexing %d%%", p_indexed); + if (print_resolved) + printf("; resolving deltas %d%%", p_resolved); + if (print_size || print_indexed || print_resolved) + fflush(stdout); + + return NULL; +} + +static const struct got_error * +create_symref(const char *refname, struct got_reference *target_ref, + int verbosity, struct got_repository *repo) +{ + const struct got_error *err; + struct got_reference *head_symref; + + err = got_ref_alloc_symref(&head_symref, refname, target_ref); + if (err) + return err; + + err = got_ref_write(head_symref, repo); + got_ref_close(head_symref); + if (err == NULL && verbosity > 0) { + printf("Created reference %s: %s\n", GOT_REF_HEAD, + got_ref_get_name(target_ref)); + } + return err; +} + +static const struct got_error * +list_remote_refs(struct got_pathlist_head *symrefs, + struct got_pathlist_head *refs) +{ + const struct got_error *err; + struct got_pathlist_entry *pe; + + TAILQ_FOREACH(pe, symrefs, entry) { + const char *refname = pe->path; + const char *targetref = pe->data; + + printf("%s: %s\n", refname, targetref); + } + + TAILQ_FOREACH(pe, refs, entry) { + const char *refname = pe->path; + struct got_object_id *id = pe->data; + char *id_str; + + err = got_object_id_str(&id_str, id); + if (err) + return err; + printf("%s: %s\n", refname, id_str); + free(id_str); + } + + return NULL; +} + +static const struct got_error * +create_ref(const char *refname, struct got_object_id *id, + int verbosity, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_reference *ref; + char *id_str; + + err = got_object_id_str(&id_str, id); + if (err) + return err; + + err = got_ref_alloc(&ref, refname, id); + if (err) + goto done; + + err = got_ref_write(ref, repo); + got_ref_close(ref); + + if (err == NULL && verbosity >= 0) + printf("Created reference %s: %s\n", refname, id_str); +done: + free(id_str); + return err; +} + +static int +match_wanted_ref(const char *refname, const char *wanted_ref) +{ + if (strncmp(refname, "refs/", 5) != 0) + return 0; + refname += 5; + + /* + * Prevent fetching of references that won't make any + * sense outside of the remote repository's context. + */ + if (strncmp(refname, "got/", 4) == 0) + return 0; + if (strncmp(refname, "remotes/", 8) == 0) + return 0; + + if (strncmp(wanted_ref, "refs/", 5) == 0) + wanted_ref += 5; + + /* Allow prefix match. */ + if (got_path_is_child(refname, wanted_ref, strlen(wanted_ref))) + return 1; + + /* Allow exact match. */ + return (strcmp(refname, wanted_ref) == 0); +} + +static int +is_wanted_ref(struct got_pathlist_head *wanted_refs, const char *refname) +{ + struct got_pathlist_entry *pe; + + TAILQ_FOREACH(pe, wanted_refs, entry) { + if (match_wanted_ref(refname, pe->path)) + return 1; + } + + return 0; +} + +static const struct got_error * +create_wanted_ref(const char *refname, struct got_object_id *id, + const char *remote_repo_name, int verbosity, struct got_repository *repo) +{ + const struct got_error *err; + char *remote_refname; + + if (strncmp("refs/", refname, 5) == 0) + refname += 5; + + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote_repo_name, refname) == -1) + return got_error_from_errno("asprintf"); + + err = create_ref(remote_refname, id, verbosity, repo); + free(remote_refname); + return err; +} + +static const struct got_error * +cmd_clone(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + const char *uri, *dirname; + char *proto, *host, *port, *repo_name, *server_path; + char *default_destdir = NULL, *id_str = NULL; + const char *repo_path; + struct got_repository *repo = NULL; + struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs; + struct got_pathlist_entry *pe; + struct got_object_id *pack_hash = NULL; + int ch, fetchfd = -1, fetchstatus; + pid_t fetchpid = -1; + struct got_fetch_progress_arg fpa; + char *git_url = NULL; + char *gitconfig_path = NULL, *gotconfig_path = NULL; + char *gitconfig = NULL, *gotconfig = NULL; + FILE *gitconfig_file = NULL, *gotconfig_file = NULL; + ssize_t n; + int verbosity = 0, fetch_all_branches = 0, mirror_references = 0; + int list_refs_only = 0; + struct got_reference *head_symref = NULL; + + TAILQ_INIT(&refs); + TAILQ_INIT(&symrefs); + TAILQ_INIT(&wanted_branches); + TAILQ_INIT(&wanted_refs); + + while ((ch = getopt(argc, argv, "ab:lmvqR:")) != -1) { + switch (ch) { + case 'a': + fetch_all_branches = 1; + break; + case 'b': + error = got_pathlist_append(&wanted_branches, + optarg, NULL); + if (error) + return error; + break; + case 'l': + list_refs_only = 1; + break; + case 'm': + mirror_references = 1; + break; + case 'v': + if (verbosity < 0) + verbosity = 0; + else if (verbosity < 3) + verbosity++; + break; + case 'q': + verbosity = -1; + break; + case 'R': + error = got_pathlist_append(&wanted_refs, + optarg, NULL); + if (error) + return error; + break; + default: + usage_clone(); + break; + } + } + argc -= optind; + argv += optind; + + if (fetch_all_branches && !TAILQ_EMPTY(&wanted_branches)) + errx(1, "-a and -b options are mutually exclusive"); + if (list_refs_only) { + if (!TAILQ_EMPTY(&wanted_branches)) + errx(1, "-l and -b options are mutually exclusive"); + if (fetch_all_branches) + errx(1, "-l and -a options are mutually exclusive"); + if (mirror_references) + errx(1, "-l and -m options are mutually exclusive"); + if (verbosity == -1) + errx(1, "-l and -q options are mutually exclusive"); + if (!TAILQ_EMPTY(&wanted_refs)) + errx(1, "-l and -R options are mutually exclusive"); + } + + uri = argv[0]; + + if (argc == 1) + dirname = NULL; + else if (argc == 2) + dirname = argv[1]; + else + usage_clone(); + + error = got_fetch_parse_uri(&proto, &host, &port, &server_path, + &repo_name, uri); + if (error) + goto done; + + if (asprintf(&git_url, "%s://%s%s%s%s%s", proto, + host, port ? ":" : "", port ? port : "", + server_path[0] != '/' ? "/" : "", server_path) == -1) { + error = got_error_from_errno("asprintf"); + 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; + } + if (dirname == NULL) { + if (asprintf(&default_destdir, "%s.git", repo_name) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + repo_path = default_destdir; + } else + repo_path = dirname; + + if (!list_refs_only) { + error = got_path_mkdir(repo_path); + if (error) + goto done; + + error = got_repo_init(repo_path); + if (error) + goto done; + error = got_repo_open(&repo, repo_path, NULL); + if (error) + goto done; + } + + if (strcmp(proto, "git+ssh") == 0 || strcmp(proto, "ssh") == 0) { + if (unveil(GOT_FETCH_PATH_SSH, "x") != 0) { + error = got_error_from_errno2("unveil", + GOT_FETCH_PATH_SSH); + goto done; + } + } + error = apply_unveil(repo ? got_repo_get_path(repo) : NULL, 0, NULL); + if (error) + goto done; + + if (verbosity >= 0) + printf("Connecting to %s%s%s\n", host, + port ? ":" : "", port ? port : ""); + + error = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, + server_path, verbosity); + if (error) + goto done; + + fpa.last_scaled_size[0] = '\0'; + fpa.last_p_indexed = -1; + fpa.last_p_resolved = -1; + fpa.verbosity = verbosity; + error = got_fetch_pack(&pack_hash, &refs, &symrefs, + GOT_FETCH_DEFAULT_REMOTE_NAME, mirror_references, + fetch_all_branches, &wanted_branches, &wanted_refs, + list_refs_only, verbosity, fetchfd, repo, + fetch_progress, &fpa); + if (error) + goto done; + + if (list_refs_only) { + error = list_remote_refs(&symrefs, &refs); + goto done; + } + + error = got_object_id_str(&id_str, pack_hash); + if (error) + goto done; + if (verbosity >= 0) + printf("\nFetched %s.pack\n", id_str); + free(id_str); + + /* Set up references provided with the pack file. */ + TAILQ_FOREACH(pe, &refs, entry) { + const char *refname = pe->path; + struct got_object_id *id = pe->data; + char *remote_refname; + + if (is_wanted_ref(&wanted_refs, refname) && + !mirror_references) { + error = create_wanted_ref(refname, id, + GOT_FETCH_DEFAULT_REMOTE_NAME, + verbosity - 1, repo); + if (error) + goto done; + continue; + } + + error = create_ref(refname, id, verbosity - 1, repo); + if (error) + goto done; + + if (mirror_references) + continue; + + if (strncmp("refs/heads/", refname, 11) != 0) + continue; + + if (asprintf(&remote_refname, + "refs/remotes/%s/%s", GOT_FETCH_DEFAULT_REMOTE_NAME, + refname + 11) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + error = create_ref(remote_refname, id, verbosity - 1, repo); + free(remote_refname); + if (error) + goto done; + } + + /* Set the HEAD reference if the server provided one. */ + TAILQ_FOREACH(pe, &symrefs, entry) { + struct got_reference *target_ref; + const char *refname = pe->path; + const char *target = pe->data; + char *remote_refname = NULL, *remote_target = NULL; + + if (strcmp(refname, GOT_REF_HEAD) != 0) + continue; + + error = got_ref_open(&target_ref, repo, target, 0); + if (error) { + if (error->code == GOT_ERR_NOT_REF) { + error = NULL; + continue; + } + goto done; + } + + error = create_symref(refname, target_ref, verbosity, repo); + got_ref_close(target_ref); + if (error) + goto done; + + if (mirror_references) + continue; + + if (strncmp("refs/heads/", target, 11) != 0) + continue; + + if (asprintf(&remote_refname, + "refs/remotes/%s/%s", GOT_FETCH_DEFAULT_REMOTE_NAME, + refname) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (asprintf(&remote_target, + "refs/remotes/%s/%s", GOT_FETCH_DEFAULT_REMOTE_NAME, + target + 11) == -1) { + error = got_error_from_errno("asprintf"); + free(remote_refname); + goto done; + } + error = got_ref_open(&target_ref, repo, remote_target, 0); + if (error) { + free(remote_refname); + free(remote_target); + if (error->code == GOT_ERR_NOT_REF) { + error = NULL; + continue; + } + goto done; + } + error = create_symref(remote_refname, target_ref, + verbosity - 1, repo); + free(remote_refname); + free(remote_target); + got_ref_close(target_ref); + if (error) + goto done; + } + if (pe == NULL) { + /* + * We failed to set the HEAD reference. If we asked for + * a set of wanted branches use the first of one of those + * which could be fetched instead. + */ + TAILQ_FOREACH(pe, &wanted_branches, entry) { + const char *target = pe->path; + struct got_reference *target_ref; + + error = got_ref_open(&target_ref, repo, target, 0); + if (error) { + if (error->code == GOT_ERR_NOT_REF) { + error = NULL; + continue; + } + goto done; + } + + error = create_symref(GOT_REF_HEAD, target_ref, + verbosity, repo); + got_ref_close(target_ref); + if (error) + goto done; + break; + } + } + + /* Create got.conf(5). */ + gotconfig_path = got_repo_get_path_gotconfig(repo); + if (gotconfig_path == NULL) { + error = got_error_from_errno("got_repo_get_path_gotconfig"); + goto done; + } + gotconfig_file = fopen(gotconfig_path, "a"); + if (gotconfig_file == NULL) { + error = got_error_from_errno2("fopen", gotconfig_path); + goto done; + } + got_path_strip_trailing_slashes(server_path); + if (asprintf(&gotconfig, + "remote \"%s\" {\n" + "\tserver %s\n" + "\tprotocol %s\n" + "%s%s%s" + "\trepository \"%s\"\n" + "%s" + "}\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, host, proto, + port ? "\tport " : "", port ? port : "", port ? "\n" : "", + server_path, + mirror_references ? "\tmirror-references yes\n" : "") == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + n = fwrite(gotconfig, 1, strlen(gotconfig), gotconfig_file); + if (n != strlen(gotconfig)) { + error = got_ferror(gotconfig_file, GOT_ERR_IO); + goto done; + } + + /* Create a config file Git can understand. */ + gitconfig_path = got_repo_get_path_gitconfig(repo); + if (gitconfig_path == NULL) { + error = got_error_from_errno("got_repo_get_path_gitconfig"); + goto done; + } + gitconfig_file = fopen(gitconfig_path, "a"); + if (gitconfig_file == NULL) { + error = got_error_from_errno2("fopen", gitconfig_path); + goto done; + } + if (mirror_references) { + if (asprintf(&gitconfig, + "[remote \"%s\"]\n" + "\turl = %s\n" + "\tfetch = +refs/*:refs/*\n" + "\tmirror = true\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, git_url) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + } else if (fetch_all_branches) { + if (asprintf(&gitconfig, + "[remote \"%s\"]\n" + "\turl = %s\n" + "\tfetch = +refs/heads/*:refs/remotes/%s/*\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, git_url, + GOT_FETCH_DEFAULT_REMOTE_NAME) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + } else { + const char *branchname; + + /* + * If the server specified a default branch, use just that one. + * Otherwise fall back to fetching all branches on next fetch. + */ + if (head_symref) { + branchname = got_ref_get_symref_target(head_symref); + if (strncmp(branchname, "refs/heads/", 11) == 0) + branchname += 11; + } else + branchname = "*"; /* fall back to all branches */ + if (asprintf(&gitconfig, + "[remote \"%s\"]\n" + "\turl = %s\n" + "\tfetch = +refs/heads/%s:refs/remotes/%s/%s\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, git_url, + branchname, GOT_FETCH_DEFAULT_REMOTE_NAME, + branchname) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + } + n = fwrite(gitconfig, 1, strlen(gitconfig), gitconfig_file); + if (n != strlen(gitconfig)) { + error = got_ferror(gitconfig_file, GOT_ERR_IO); + goto done; + } + + if (verbosity >= 0) + printf("Created %s repository '%s'\n", + mirror_references ? "mirrored" : "cloned", repo_path); +done: + if (fetchpid > 0) { + if (kill(fetchpid, SIGTERM) == -1) + error = got_error_from_errno("kill"); + if (waitpid(fetchpid, &fetchstatus, 0) == -1 && error == NULL) + error = got_error_from_errno("waitpid"); + } + if (fetchfd != -1 && close(fetchfd) == -1 && error == NULL) + error = got_error_from_errno("close"); + if (gotconfig_file && fclose(gotconfig_file) == EOF && error == NULL) + error = got_error_from_errno("fclose"); + if (gitconfig_file && fclose(gitconfig_file) == EOF && error == NULL) + error = got_error_from_errno("fclose"); + if (repo) + got_repo_close(repo); + if (head_symref) + got_ref_close(head_symref); + TAILQ_FOREACH(pe, &refs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&refs); + TAILQ_FOREACH(pe, &symrefs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&symrefs); + got_pathlist_free(&wanted_branches); + got_pathlist_free(&wanted_refs); + free(pack_hash); + free(proto); + free(host); + free(port); + free(server_path); + free(repo_name); + free(default_destdir); + free(gotconfig); + free(gitconfig); + free(gotconfig_path); + free(gitconfig_path); + free(git_url); + return error; +} + +static const struct got_error * +update_ref(struct got_reference *ref, struct got_object_id *new_id, + int replace_tags, int verbosity, struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *new_id_str = NULL; + struct got_object_id *old_id = NULL; + + err = got_object_id_str(&new_id_str, new_id); + if (err) + goto done; + + if (!replace_tags && + strncmp(got_ref_get_name(ref), "refs/tags/", 10) == 0) { + err = got_ref_resolve(&old_id, repo, ref); + if (err) + goto done; + if (got_object_id_cmp(old_id, new_id) == 0) + goto done; + if (verbosity >= 0) { + printf("Rejecting update of existing tag %s: %s\n", + got_ref_get_name(ref), new_id_str); + } + goto done; + } + + if (got_ref_is_symbolic(ref)) { + if (verbosity >= 0) { + printf("Replacing reference %s: %s\n", + got_ref_get_name(ref), + got_ref_get_symref_target(ref)); + } + err = got_ref_change_symref_to_ref(ref, new_id); + if (err) + goto done; + err = got_ref_write(ref, repo); + if (err) + goto done; + } else { + err = got_ref_resolve(&old_id, repo, ref); + if (err) + goto done; + if (got_object_id_cmp(old_id, new_id) == 0) + goto done; + + err = got_ref_change_ref(ref, new_id); + if (err) + goto done; + err = got_ref_write(ref, repo); + if (err) + goto done; + } + + if (verbosity >= 0) + printf("Updated %s: %s\n", got_ref_get_name(ref), + new_id_str); +done: + free(old_id); + free(new_id_str); + return err; +} + +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 [-a] [-b branch] [-d] [-l] " + "[-r repository-path] [-t] [-q] [-v] [-R reference] " + "[remote-repository-name]\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 reference %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 reference %s: %s\n", + got_ref_get_name(ref), id_str); + } + } +done: + free(id); + free(id_str); + return NULL; +} + +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; + + SIMPLEQ_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; + + SIMPLEQ_FOREACH(re, &my_refs, entry) { + const char *refname = got_ref_get_name(re->ref); + + if (!remote->mirror_references) { + 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; + } + + TAILQ_FOREACH(pe, their_refs, entry) { + if (strcmp(local_refname, pe->path) == 0) + break; + } + if (pe != NULL) + continue; + + TAILQ_FOREACH(pe, their_symrefs, entry) { + if (strcmp(local_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: + 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) +{ + const struct got_error *err, *unlock_err; + char *remote_refname; + struct got_reference *ref; + + if (strncmp("refs/", refname, 5) == 0) + refname += 5; + + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote_repo_name, refname) == -1) + return got_error_from_errno("asprintf"); + + err = got_ref_open(&ref, repo, remote_refname, 1); + if (err) { + if (err->code != GOT_ERR_NOT_REF) + goto done; + err = create_ref(remote_refname, id, verbosity, repo); + } else { + err = update_ref(ref, id, 0, verbosity, repo); + unlock_err = got_ref_unlock(ref); + if (unlock_err && err == NULL) + err = unlock_err; + got_ref_close(ref); + } +done: + free(remote_refname); + return err; +} + +static const struct got_error * +cmd_fetch(int argc, char *argv[]) +{ + const struct got_error *error = NULL, *unlock_err; + 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; + char *id_str = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + const struct got_gotconfig *repo_conf = NULL, *worktree_conf = NULL; + struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs; + struct got_pathlist_entry *pe; + struct got_object_id *pack_hash = NULL; + int i, ch, fetchfd = -1, fetchstatus; + pid_t fetchpid = -1; + struct got_fetch_progress_arg fpa; + int verbosity = 0, fetch_all_branches = 0, list_refs_only = 0; + int delete_refs = 0, replace_tags = 0; + + TAILQ_INIT(&refs); + TAILQ_INIT(&symrefs); + TAILQ_INIT(&wanted_branches); + TAILQ_INIT(&wanted_refs); + + while ((ch = getopt(argc, argv, "ab:dlr:tvqR:")) != -1) { + switch (ch) { + case 'a': + fetch_all_branches = 1; + break; + case 'b': + error = got_pathlist_append(&wanted_branches, + optarg, NULL); + if (error) + return error; + break; + case 'd': + delete_refs = 1; + break; + case 'l': + list_refs_only = 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': + replace_tags = 1; + break; + case 'v': + if (verbosity < 0) + verbosity = 0; + else if (verbosity < 3) + verbosity++; + break; + case 'q': + verbosity = -1; + break; + case 'R': + error = got_pathlist_append(&wanted_refs, + optarg, NULL); + if (error) + return error; + break; + default: + usage_fetch(); + break; + } + } + argc -= optind; + argv += optind; + + if (fetch_all_branches && !TAILQ_EMPTY(&wanted_branches)) + errx(1, "-a and -b options are mutually exclusive"); + if (list_refs_only) { + if (!TAILQ_EMPTY(&wanted_branches)) + errx(1, "-l and -b options are mutually exclusive"); + if (fetch_all_branches) + errx(1, "-l and -a options are mutually exclusive"); + if (delete_refs) + errx(1, "-l and -d options are mutually exclusive"); + if (verbosity == -1) + errx(1, "-l and -q options are mutually exclusive"); + } + + if (argc == 0) + remote_name = GOT_FETCH_DEFAULT_REMOTE_NAME; + else if (argc == 1) + remote_name = argv[0]; + else + usage_fetch(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + 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++) { + remote = &remotes[i]; + if (strcmp(remote->name, remote_name) == 0) + 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++) { + remote = &remotes[i]; + if (strcmp(remote->name, remote_name) == 0) + break; + } + } + } + if (remote == NULL) { + got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo); + for (i = 0; i < nremotes; i++) { + remote = &remotes[i]; + if (strcmp(remote->name, remote_name) == 0) + break; + } + } + if (remote == NULL) { + error = got_error_path(remote_name, GOT_ERR_NO_REMOTE); + goto done; + } + + error = got_fetch_parse_uri(&proto, &host, &port, &server_path, + &repo_name, remote->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; + } + + if (strcmp(proto, "git+ssh") == 0 || strcmp(proto, "ssh") == 0) { + if (unveil(GOT_FETCH_PATH_SSH, "x") != 0) { + error = got_error_from_errno2("unveil", + GOT_FETCH_PATH_SSH); + goto done; + } + } + error = apply_unveil(got_repo_get_path(repo), 0, NULL); + if (error) + goto done; + + if (verbosity >= 0) + printf("Connecting to \"%s\" %s%s%s\n", remote->name, host, + port ? ":" : "", port ? port : ""); + + error = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, + server_path, verbosity); + if (error) + goto done; + + fpa.last_scaled_size[0] = '\0'; + fpa.last_p_indexed = -1; + fpa.last_p_resolved = -1; + fpa.verbosity = verbosity; + error = got_fetch_pack(&pack_hash, &refs, &symrefs, remote->name, + remote->mirror_references, fetch_all_branches, &wanted_branches, + &wanted_refs, list_refs_only, verbosity, fetchfd, repo, + fetch_progress, &fpa); + if (error) + goto done; + + if (list_refs_only) { + error = list_remote_refs(&symrefs, &refs); + goto done; + } + + if (pack_hash == NULL) { + if (verbosity >= 0) + printf("Already up-to-date\n"); + } else if (verbosity >= 0) { + error = got_object_id_str(&id_str, pack_hash); + if (error) + goto done; + printf("\nFetched %s.pack\n", id_str); + free(id_str); + id_str = NULL; + } + + /* Update references provided with the pack file. */ + TAILQ_FOREACH(pe, &refs, entry) { + const char *refname = pe->path; + struct got_object_id *id = pe->data; + struct got_reference *ref; + char *remote_refname; + + if (is_wanted_ref(&wanted_refs, refname) && + !remote->mirror_references) { + error = update_wanted_ref(refname, id, + remote->name, verbosity, repo); + if (error) + goto done; + continue; + } + + if (remote->mirror_references || + strncmp("refs/tags/", refname, 10) == 0) { + error = got_ref_open(&ref, repo, refname, 1); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + error = create_ref(refname, id, verbosity, + repo); + if (error) + goto done; + } else { + error = update_ref(ref, id, replace_tags, + verbosity, repo); + unlock_err = got_ref_unlock(ref); + if (unlock_err && error == NULL) + error = unlock_err; + got_ref_close(ref); + if (error) + goto done; + } + } else if (strncmp("refs/heads/", refname, 11) == 0) { + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote_name, refname + 11) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + + error = got_ref_open(&ref, repo, remote_refname, 1); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + error = create_ref(remote_refname, id, + verbosity, repo); + if (error) + goto done; + } else { + error = update_ref(ref, id, replace_tags, + verbosity, repo); + unlock_err = got_ref_unlock(ref); + if (unlock_err && error == NULL) + error = unlock_err; + got_ref_close(ref); + if (error) + goto done; + } + + /* Also create a local branch if none exists yet. */ + error = got_ref_open(&ref, repo, refname, 1); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + error = create_ref(refname, id, verbosity, + repo); + if (error) + goto done; + } else { + unlock_err = got_ref_unlock(ref); + if (unlock_err && error == NULL) + error = unlock_err; + got_ref_close(ref); + } + } + } + if (delete_refs) { + error = delete_missing_refs(&refs, &symrefs, remote, + verbosity, repo); + if (error) + goto done; + } + + if (!remote->mirror_references) { + /* Update remote HEAD reference if the server provided one. */ + TAILQ_FOREACH(pe, &symrefs, entry) { + struct got_reference *target_ref; + const char *refname = pe->path; + const char *target = pe->data; + char *remote_refname = NULL, *remote_target = NULL; + + if (strcmp(refname, GOT_REF_HEAD) != 0) + continue; + + if (strncmp("refs/heads/", target, 11) != 0) + continue; + + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote->name, refname) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (asprintf(&remote_target, "refs/remotes/%s/%s", + remote->name, target + 11) == -1) { + error = got_error_from_errno("asprintf"); + free(remote_refname); + goto done; + } + + error = got_ref_open(&target_ref, repo, remote_target, + 0); + if (error) { + free(remote_refname); + free(remote_target); + if (error->code == GOT_ERR_NOT_REF) { + error = NULL; + continue; + } + goto done; + } + error = update_symref(remote_refname, target_ref, + verbosity, repo); + free(remote_refname); + free(remote_target); + got_ref_close(target_ref); + if (error) + goto done; + } + } +done: + if (fetchpid > 0) { + if (kill(fetchpid, SIGTERM) == -1) + error = got_error_from_errno("kill"); + if (waitpid(fetchpid, &fetchstatus, 0) == -1 && error == NULL) + error = got_error_from_errno("waitpid"); + } + if (fetchfd != -1 && close(fetchfd) == -1 && error == NULL) + error = got_error_from_errno("close"); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &refs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&refs); + TAILQ_FOREACH(pe, &symrefs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&symrefs); + got_pathlist_free(&wanted_branches); + got_pathlist_free(&wanted_refs); + free(id_str); + free(cwd); + free(repo_path); + free(pack_hash); + free(proto); + free(host); + free(port); + free(server_path); + free(repo_name); + return error; +} + + +__dead static void +usage_checkout(void) +{ + fprintf(stderr, "usage: %s checkout [-E] [-b branch] [-c commit] " + "[-p prefix] repository-path [worktree-path]\n", getprogname()); + exit(1); +} + +static void +show_worktree_base_ref_warning(void) +{ + fprintf(stderr, "%s: warning: could not create a reference " + "to the work tree's base commit; the commit could be " + "garbage-collected by Git; making the repository " + "writable and running 'got update' will prevent this\n", + getprogname()); +} + +struct got_checkout_progress_arg { + const char *worktree_path; + int had_base_commit_ref_error; +}; + +static const struct got_error * +checkout_progress(void *arg, unsigned char status, const char *path) +{ + struct got_checkout_progress_arg *a = arg; + + /* Base commit bump happens silently. */ + if (status == GOT_STATUS_BUMP_BASE) + return NULL; + + if (status == GOT_STATUS_BASE_REF_ERR) { + a->had_base_commit_ref_error = 1; + return NULL; + } + + while (path[0] == '/') + path++; + + printf("%c %s/%s\n", status, a->worktree_path, path); + return NULL; +} + +static const struct got_error * +check_cancelled(void *arg) +{ + if (sigint_received || sigpipe_received) + return got_error(GOT_ERR_CANCELLED); + return NULL; +} + +static const struct got_error * +check_linear_ancestry(struct got_object_id *commit_id, + struct got_object_id *base_commit_id, int allow_forwards_in_time_only, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_id *yca_id; + + err = got_commit_graph_find_youngest_common_ancestor(&yca_id, + commit_id, base_commit_id, repo, check_cancelled, NULL); + if (err) + return err; + + if (yca_id == NULL) + return got_error(GOT_ERR_ANCESTRY); + + /* + * Require a straight line of history between the target commit + * and the work tree's base commit. + * + * Non-linear situations such as this require a rebase: + * + * (commit) D F (base_commit) + * \ / + * C E + * \ / + * B (yca) + * | + * A + * + * 'got update' only handles linear cases: + * Update forwards in time: A (base/yca) - B - C - D (commit) + * Update backwards in time: D (base) - C - B - A (commit/yca) + */ + if (allow_forwards_in_time_only) { + if (got_object_id_cmp(base_commit_id, yca_id) != 0) + return got_error(GOT_ERR_ANCESTRY); + } else if (got_object_id_cmp(commit_id, yca_id) != 0 && + got_object_id_cmp(base_commit_id, yca_id) != 0) + return got_error(GOT_ERR_ANCESTRY); + + free(yca_id); + return NULL; +} + +static const struct got_error * +check_same_branch(struct got_object_id *commit_id, + struct got_reference *head_ref, struct got_object_id *yca_id, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_commit_graph *graph = NULL; + struct got_object_id *head_commit_id = NULL; + int is_same_branch = 0; + + err = got_ref_resolve(&head_commit_id, repo, head_ref); + if (err) + goto done; + + if (got_object_id_cmp(head_commit_id, commit_id) == 0) { + is_same_branch = 1; + goto done; + } + if (yca_id && got_object_id_cmp(commit_id, yca_id) == 0) { + is_same_branch = 1; + goto done; + } + + err = got_commit_graph_open(&graph, "/", 1); + if (err) + goto done; + + err = got_commit_graph_iter_start(graph, head_commit_id, repo, + check_cancelled, NULL); + if (err) + goto done; + + for (;;) { + struct got_object_id *id; + err = got_commit_graph_iter_next(&id, graph, repo, + check_cancelled, NULL); + if (err) { + if (err->code == GOT_ERR_ITER_COMPLETED) + err = NULL; + break; + } + + if (id) { + if (yca_id && got_object_id_cmp(id, yca_id) == 0) + break; + if (got_object_id_cmp(id, commit_id) == 0) { + is_same_branch = 1; + break; + } + } + } +done: + if (graph) + got_commit_graph_close(graph); + free(head_commit_id); + if (!err && !is_same_branch) + err = got_error(GOT_ERR_ANCESTRY); + return err; +} + +static const struct got_error * +checkout_ancestry_error(struct got_reference *ref, const char *commit_id_str) +{ + static char msg[512]; + const char *branch_name; + + if (got_ref_is_symbolic(ref)) + branch_name = got_ref_get_symref_target(ref); + else + branch_name = got_ref_get_name(ref); + + if (strncmp("refs/heads/", branch_name, 11) == 0) + branch_name += 11; + + snprintf(msg, sizeof(msg), + "target commit is not contained in branch '%s'; " + "the branch to use must be specified with -b; " + "if necessary a new branch can be created for " + "this commit with 'got branch -c %s BRANCH_NAME'", + branch_name, commit_id_str); + + return got_error_msg(GOT_ERR_ANCESTRY, msg); +} + +static const struct got_error * +cmd_checkout(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_reference *head_ref = NULL; + struct got_worktree *worktree = NULL; + char *repo_path = NULL; + char *worktree_path = NULL; + const char *path_prefix = ""; + const char *branch_name = GOT_REF_HEAD; + char *commit_id_str = NULL; + char *cwd = NULL, *path = NULL; + int ch, same_path_prefix, allow_nonempty = 0; + struct got_pathlist_head paths; + struct got_checkout_progress_arg cpa; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "b:c:Ep:")) != -1) { + switch (ch) { + case 'b': + branch_name = optarg; + break; + case 'c': + commit_id_str = strdup(optarg); + if (commit_id_str == NULL) + return got_error_from_errno("strdup"); + break; + case 'E': + allow_nonempty = 1; + break; + case 'p': + path_prefix = optarg; + break; + default: + usage_checkout(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc == 1) { + char *base, *dotgit; + repo_path = realpath(argv[0], NULL); + if (repo_path == NULL) + return got_error_from_errno2("realpath", argv[0]); + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + if (path_prefix[0]) + path = strdup(path_prefix); + else + path = strdup(repo_path); + if (path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + base = basename(path); + if (base == NULL) { + error = got_error_from_errno2("basename", path); + goto done; + } + dotgit = strstr(base, ".git"); + if (dotgit) + *dotgit = '\0'; + if (asprintf(&worktree_path, "%s/%s", cwd, base) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + } else if (argc == 2) { + repo_path = realpath(argv[0], NULL); + if (repo_path == NULL) { + error = got_error_from_errno2("realpath", argv[0]); + goto done; + } + worktree_path = realpath(argv[1], NULL); + if (worktree_path == NULL) { + if (errno != ENOENT) { + error = got_error_from_errno2("realpath", + argv[1]); + goto done; + } + worktree_path = strdup(argv[1]); + if (worktree_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } else + usage_checkout(); + + got_path_strip_trailing_slashes(repo_path); + got_path_strip_trailing_slashes(worktree_path); + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + /* Pre-create work tree path for unveil(2) */ + error = got_path_mkdir(worktree_path); + if (error) { + if (!(error->code == GOT_ERR_ERRNO && errno == EISDIR) && + !(error->code == GOT_ERR_ERRNO && errno == EEXIST)) + goto done; + if (!allow_nonempty && + !got_path_dir_is_empty(worktree_path)) { + error = got_error_path(worktree_path, + GOT_ERR_DIR_NOT_EMPTY); + goto done; + } + } + + error = apply_unveil(got_repo_get_path(repo), 0, worktree_path); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, branch_name, 0); + if (error != NULL) + goto done; + + error = got_worktree_init(worktree_path, head_ref, path_prefix, repo); + if (error != NULL && !(error->code == GOT_ERR_ERRNO && errno == EEXIST)) + goto done; + + error = got_worktree_open(&worktree, worktree_path); + if (error != NULL) + goto done; + + error = got_worktree_match_path_prefix(&same_path_prefix, worktree, + path_prefix); + if (error != NULL) + goto done; + if (!same_path_prefix) { + error = got_error(GOT_ERR_PATH_PREFIX); + goto done; + } + + if (commit_id_str) { + struct got_object_id *commit_id; + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + if (error) + goto done; + error = check_linear_ancestry(commit_id, + got_worktree_get_base_commit_id(worktree), 0, repo); + if (error != NULL) { + free(commit_id); + if (error->code == GOT_ERR_ANCESTRY) { + error = checkout_ancestry_error( + head_ref, commit_id_str); + } + goto done; + } + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) { + if (error->code == GOT_ERR_ANCESTRY) { + error = checkout_ancestry_error( + head_ref, commit_id_str); + } + goto done; + } + error = got_worktree_set_base_commit_id(worktree, repo, + commit_id); + free(commit_id); + if (error) + goto done; + } + + error = got_pathlist_append(&paths, "", NULL); + if (error) + goto done; + cpa.worktree_path = worktree_path; + cpa.had_base_commit_ref_error = 0; + error = got_worktree_checkout_files(worktree, &paths, repo, + checkout_progress, &cpa, check_cancelled, NULL); + if (error != NULL) + goto done; + + printf("Now shut up and hack\n"); + if (cpa.had_base_commit_ref_error) + show_worktree_base_ref_warning(); +done: + got_pathlist_free(&paths); + free(commit_id_str); + free(repo_path); + free(worktree_path); + free(cwd); + free(path); + return error; +} + +struct got_update_progress_arg { + int did_something; + int conflicts; + int obstructed; + int not_updated; +}; + +void +print_update_progress_stats(struct got_update_progress_arg *upa) +{ + if (!upa->did_something) + return; + + if (upa->conflicts > 0) + printf("Files with new merge conflicts: %d\n", upa->conflicts); + if (upa->obstructed > 0) + printf("File paths obstructed by a non-regular file: %d\n", + upa->obstructed); + if (upa->not_updated > 0) + printf("Files not updated because of existing merge " + "conflicts: %d\n", upa->not_updated); +} + +__dead static void +usage_update(void) +{ + fprintf(stderr, "usage: %s update [-b branch] [-c commit] [path ...]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +update_progress(void *arg, unsigned char status, const char *path) +{ + struct got_update_progress_arg *upa = arg; + + if (status == GOT_STATUS_EXISTS || + status == GOT_STATUS_BASE_REF_ERR) + return NULL; + + upa->did_something = 1; + + /* Base commit bump happens silently. */ + if (status == GOT_STATUS_BUMP_BASE) + return NULL; + + if (status == GOT_STATUS_CONFLICT) + upa->conflicts++; + if (status == GOT_STATUS_OBSTRUCTED) + upa->obstructed++; + if (status == GOT_STATUS_CANNOT_UPDATE) + upa->not_updated++; + + while (path[0] == '/') + path++; + printf("%c %s\n", status, path); + return NULL; +} + +static const struct got_error * +switch_head_ref(struct got_reference *head_ref, + struct got_object_id *commit_id, struct got_worktree *worktree, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *base_id_str; + int ref_has_moved = 0; + + /* Trivial case: switching between two different references. */ + if (strcmp(got_ref_get_name(head_ref), + got_worktree_get_head_ref_name(worktree)) != 0) { + printf("Switching work tree from %s to %s\n", + got_worktree_get_head_ref_name(worktree), + got_ref_get_name(head_ref)); + return got_worktree_set_head_ref(worktree, head_ref); + } + + err = check_linear_ancestry(commit_id, + got_worktree_get_base_commit_id(worktree), 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; + int in_progress; + + err = got_worktree_rebase_in_progress(&in_progress, worktree); + if (err) + return err; + if (in_progress) + return got_error(GOT_ERR_REBASING); + + err = got_worktree_histedit_in_progress(&in_progress, worktree); + if (err) + return err; + if (in_progress) + return got_error(GOT_ERR_HISTEDIT_BUSY); + + return NULL; +} + +static const struct got_error * +get_worktree_paths_from_argv(struct got_pathlist_head *paths, int argc, + char *argv[], struct got_worktree *worktree) +{ + const struct got_error *err = NULL; + char *path; + int i; + + if (argc == 0) { + path = strdup(""); + if (path == NULL) + return got_error_from_errno("strdup"); + return got_pathlist_append(paths, path, NULL); + } + + for (i = 0; i < argc; i++) { + err = got_worktree_resolve_path(&path, worktree, argv[i]); + if (err) + break; + err = got_pathlist_append(paths, path, NULL); + if (err) { + free(path); + break; + } + } + + return err; +} + +static const struct got_error * +wrap_not_worktree_error(const struct got_error *orig_err, + const char *cmdname, const char *path) +{ + const struct got_error *err; + struct got_repository *repo; + static char msg[512]; + + err = got_repo_open(&repo, path, NULL); + if (err) + return orig_err; + + snprintf(msg, sizeof(msg), + "'got %s' needs a work tree in addition to a git repository\n" + "Work trees can be checked out from this Git repository with " + "'got checkout'.\n" + "The got(1) manual page contains more information.", cmdname); + err = got_error_msg(GOT_ERR_NOT_WORKTREE, msg); + got_repo_close(repo); + return err; +} + +static const struct got_error * +cmd_update(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *worktree_path = NULL; + struct got_object_id *commit_id = NULL; + char *commit_id_str = NULL; + const char *branch_name = NULL; + struct got_reference *head_ref = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch; + struct got_update_progress_arg upa; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "b:c:")) != -1) { + switch (ch) { + case 'b': + branch_name = optarg; + break; + case 'c': + commit_id_str = strdup(optarg); + if (commit_id_str == NULL) + return got_error_from_errno("strdup"); + break; + default: + usage_update(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + worktree_path = getcwd(NULL, 0); + if (worktree_path == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, worktree_path); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "update", + worktree_path); + 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); + 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 = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, branch_name ? branch_name : + got_worktree_get_head_ref_name(worktree), 0); + if (error != NULL) + goto done; + if (commit_id_str == NULL) { + error = got_ref_resolve(&commit_id, repo, head_ref); + if (error != NULL) + goto done; + error = got_object_id_str(&commit_id_str, commit_id); + if (error != NULL) + goto done; + } else { + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + free(commit_id_str); + commit_id_str = NULL; + if (error) + goto done; + error = got_object_id_str(&commit_id_str, commit_id); + if (error) + goto done; + } + + if (branch_name) { + struct got_object_id *head_commit_id; + TAILQ_FOREACH(pe, &paths, entry) { + if (pe->path_len == 0) + continue; + error = got_error_msg(GOT_ERR_BAD_PATH, + "switching between branches requires that " + "the entire work tree gets updated"); + goto done; + } + error = got_ref_resolve(&head_commit_id, repo, head_ref); + if (error) + goto done; + error = check_linear_ancestry(commit_id, head_commit_id, 0, + repo); + free(head_commit_id); + if (error != NULL) + goto done; + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) + goto done; + error = switch_head_ref(head_ref, commit_id, worktree, repo); + if (error) + goto done; + } else { + error = check_linear_ancestry(commit_id, + got_worktree_get_base_commit_id(worktree), 0, repo); + if (error != NULL) { + if (error->code == GOT_ERR_ANCESTRY) + error = got_error(GOT_ERR_BRANCH_MOVED); + goto done; + } + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) + goto done; + } + + if (got_object_id_cmp(got_worktree_get_base_commit_id(worktree), + commit_id) != 0) { + error = got_worktree_set_base_commit_id(worktree, repo, + commit_id); + if (error) + goto done; + } + + memset(&upa, 0, sizeof(upa)); + error = got_worktree_checkout_files(worktree, &paths, repo, + update_progress, &upa, check_cancelled, NULL); + if (error != NULL) + goto done; + + if (upa.did_something) + printf("Updated to commit %s\n", commit_id_str); + else + printf("Already up-to-date\n"); + print_update_progress_stats(&upa); +done: + free(worktree_path); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(commit_id); + free(commit_id_str); + return error; +} + +static const struct got_error * +diff_blobs(struct got_object_id *blob_id1, struct got_object_id *blob_id2, + const char *path, int diff_context, int ignore_whitespace, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_blob_object *blob1 = NULL, *blob2 = NULL; + + if (blob_id1) { + err = got_object_open_as_blob(&blob1, repo, blob_id1, 8192); + if (err) + goto done; + } + + err = got_object_open_as_blob(&blob2, repo, blob_id2, 8192); + if (err) + goto done; + + while (path[0] == '/') + path++; + err = got_diff_blob(blob1, blob2, path, path, diff_context, + ignore_whitespace, stdout); +done: + if (blob1) + got_object_blob_close(blob1); + got_object_blob_close(blob2); + return err; +} + +static const struct got_error * +diff_trees(struct got_object_id *tree_id1, struct got_object_id *tree_id2, + const char *path, int diff_context, int ignore_whitespace, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_tree_object *tree1 = NULL, *tree2 = NULL; + struct got_diff_blob_output_unidiff_arg arg; + + if (tree_id1) { + err = got_object_open_as_tree(&tree1, repo, tree_id1); + if (err) + goto done; + } + + err = got_object_open_as_tree(&tree2, repo, tree_id2); + if (err) + goto done; + + arg.diff_context = diff_context; + arg.ignore_whitespace = ignore_whitespace; + arg.outfile = stdout; + while (path[0] == '/') + path++; + err = got_diff_tree(tree1, tree2, path, path, repo, + got_diff_blob_output_unidiff, &arg, 1); +done: + if (tree1) + got_object_tree_close(tree1); + if (tree2) + got_object_tree_close(tree2); + return err; +} + +static const struct got_error * +get_changed_paths(struct got_pathlist_head *paths, + struct got_commit_object *commit, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_id *tree_id1 = NULL, *tree_id2 = NULL; + struct got_tree_object *tree1 = NULL, *tree2 = NULL; + struct got_object_qid *qid; + + qid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); + if (qid != NULL) { + struct got_commit_object *pcommit; + err = got_object_open_as_commit(&pcommit, repo, + qid->id); + if (err) + return err; + + tree_id1 = got_object_commit_get_tree_id(pcommit); + got_object_commit_close(pcommit); + + } + + if (tree_id1) { + err = got_object_open_as_tree(&tree1, repo, tree_id1); + if (err) + goto done; + } + + tree_id2 = got_object_commit_get_tree_id(commit); + err = got_object_open_as_tree(&tree2, repo, tree_id2); + if (err) + goto done; + + err = got_diff_tree(tree1, tree2, "", "", repo, + got_diff_tree_collect_changed_paths, paths, 0); +done: + if (tree1) + got_object_tree_close(tree1); + if (tree2) + got_object_tree_close(tree2); + return err; +} + +static const struct got_error * +print_patch(struct got_commit_object *commit, struct got_object_id *id, + const char *path, int diff_context, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_commit_object *pcommit = NULL; + char *id_str1 = NULL, *id_str2 = NULL; + struct got_object_id *obj_id1 = NULL, *obj_id2 = NULL; + struct got_object_qid *qid; + + qid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); + if (qid != NULL) { + err = got_object_open_as_commit(&pcommit, repo, + qid->id); + if (err) + return err; + } + + if (path && path[0] != '\0') { + int obj_type; + err = got_object_id_by_path(&obj_id2, repo, id, path); + if (err) + goto done; + err = got_object_id_str(&id_str2, obj_id2); + if (err) { + free(obj_id2); + goto done; + } + if (pcommit) { + err = got_object_id_by_path(&obj_id1, repo, + qid->id, path); + if (err) { + if (err->code != GOT_ERR_NO_TREE_ENTRY) { + free(obj_id2); + goto done; + } + } else { + err = got_object_id_str(&id_str1, obj_id1); + if (err) { + free(obj_id2); + goto done; + } + } + } + err = got_object_get_type(&obj_type, repo, obj_id2); + if (err) { + free(obj_id2); + goto done; + } + printf("diff %s %s\n", id_str1 ? id_str1 : "/dev/null", id_str2); + switch (obj_type) { + case GOT_OBJ_TYPE_BLOB: + err = diff_blobs(obj_id1, obj_id2, path, diff_context, + 0, repo); + break; + case GOT_OBJ_TYPE_TREE: + err = diff_trees(obj_id1, obj_id2, path, diff_context, + 0, repo); + break; + default: + err = got_error(GOT_ERR_OBJ_TYPE); + break; + } + free(obj_id1); + free(obj_id2); + } else { + obj_id2 = got_object_commit_get_tree_id(commit); + err = got_object_id_str(&id_str2, obj_id2); + if (err) + goto done; + obj_id1 = got_object_commit_get_tree_id(pcommit); + err = got_object_id_str(&id_str1, obj_id1); + if (err) + goto done; + printf("diff %s %s\n", id_str1 ? id_str1 : "/dev/null", id_str2); + err = diff_trees(obj_id1, obj_id2, "", diff_context, 0, repo); + } +done: + free(id_str1); + free(id_str2); + if (pcommit) + got_object_commit_close(pcommit); + return err; +} + +static char * +get_datestr(time_t *time, char *datebuf) +{ + struct tm mytm, *tm; + char *p, *s; + + tm = gmtime_r(time, &mytm); + if (tm == NULL) + return NULL; + s = asctime_r(tm, datebuf); + if (s == NULL) + return NULL; + p = strchr(s, '\n'); + if (p) + *p = '\0'; + return s; +} + +static const struct got_error * +match_logmsg(int *have_match, struct got_object_id *id, + struct got_commit_object *commit, regex_t *regex) +{ + const struct got_error *err = NULL; + regmatch_t regmatch; + char *id_str = NULL, *logmsg = NULL; + + *have_match = 0; + + err = got_object_id_str(&id_str, id); + if (err) + return err; + + err = got_object_commit_get_logmsg(&logmsg, commit); + if (err) + goto done; + + if (regexec(regex, logmsg, 1, ®match, 0) == 0) + *have_match = 1; +done: + free(id_str); + free(logmsg); + return err; +} + +static void +match_changed_paths(int *have_match, struct got_pathlist_head *changed_paths, + regex_t *regex) +{ + regmatch_t regmatch; + struct got_pathlist_entry *pe; + + *have_match = 0; + + TAILQ_FOREACH(pe, changed_paths, entry) { + if (regexec(regex, pe->path, 1, ®match, 0) == 0) { + *have_match = 1; + break; + } + } +} + +#define GOT_COMMIT_SEP_STR "-----------------------------------------------\n" + +static const struct got_error * +print_commit(struct got_commit_object *commit, struct got_object_id *id, + struct got_repository *repo, const char *path, + struct got_pathlist_head *changed_paths, int show_patch, + int diff_context, struct got_reflist_head *refs) +{ + const struct got_error *err = NULL; + char *id_str, *datestr, *logmsg0, *logmsg, *line; + char datebuf[26]; + time_t committer_time; + const char *author, *committer; + char *refs_str = NULL; + struct got_reflist_entry *re; + + SIMPLEQ_FOREACH(re, refs, entry) { + char *s; + const char *name; + struct got_tag_object *tag = NULL; + struct got_object_id *ref_id; + int cmp; + + name = got_ref_get_name(re->ref); + if (strcmp(name, GOT_REF_HEAD) == 0) + continue; + if (strncmp(name, "refs/", 5) == 0) + name += 5; + if (strncmp(name, "got/", 4) == 0) + continue; + if (strncmp(name, "heads/", 6) == 0) + name += 6; + if (strncmp(name, "remotes/", 8) == 0) { + name += 8; + s = strstr(name, "/" GOT_REF_HEAD); + if (s != NULL && s[strlen(s)] == '\0') + continue; + } + err = got_ref_resolve(&ref_id, repo, re->ref); + if (err) + return err; + if (strncmp(name, "tags/", 5) == 0) { + err = got_object_open_as_tag(&tag, repo, ref_id); + if (err) { + if (err->code != GOT_ERR_OBJ_TYPE) { + free(ref_id); + return err; + } + /* Ref points at something other than a tag. */ + err = NULL; + tag = NULL; + } + } + cmp = got_object_id_cmp(tag ? + got_object_tag_get_object_id(tag) : ref_id, id); + free(ref_id); + if (tag) + got_object_tag_close(tag); + if (cmp != 0) + continue; + s = refs_str; + if (asprintf(&refs_str, "%s%s%s", s ? s : "", s ? ", " : "", + name) == -1) { + err = got_error_from_errno("asprintf"); + free(s); + return err; + } + free(s); + } + err = got_object_id_str(&id_str, id); + if (err) + return err; + + printf(GOT_COMMIT_SEP_STR); + printf("commit %s%s%s%s\n", id_str, refs_str ? " (" : "", + refs_str ? refs_str : "", refs_str ? ")" : ""); + free(id_str); + id_str = NULL; + free(refs_str); + refs_str = NULL; + printf("from: %s\n", got_object_commit_get_author(commit)); + committer_time = got_object_commit_get_committer_time(commit); + datestr = get_datestr(&committer_time, datebuf); + if (datestr) + printf("date: %s UTC\n", datestr); + author = got_object_commit_get_author(commit); + committer = got_object_commit_get_committer(commit); + if (strcmp(author, committer) != 0) + printf("via: %s\n", committer); + if (got_object_commit_get_nparents(commit) > 1) { + const struct got_object_id_queue *parent_ids; + struct got_object_qid *qid; + int n = 1; + parent_ids = got_object_commit_get_parent_ids(commit); + SIMPLEQ_FOREACH(qid, parent_ids, entry) { + err = got_object_id_str(&id_str, qid->id); + if (err) + return err; + printf("parent %d: %s\n", n++, id_str); + free(id_str); + } + } + + err = got_object_commit_get_logmsg(&logmsg0, commit); + if (err) + return err; + + logmsg = logmsg0; + do { + line = strsep(&logmsg, "\n"); + if (line) + printf(" %s\n", line); + } while (line); + free(logmsg0); + + if (changed_paths) { + struct got_pathlist_entry *pe; + TAILQ_FOREACH(pe, changed_paths, entry) { + struct got_diff_changed_path *cp = pe->data; + printf(" %c %s\n", cp->status, pe->path); + } + printf("\n"); + } + if (show_patch) { + err = print_patch(commit, id, path, diff_context, repo); + if (err == 0) + printf("\n"); + } + + if (fflush(stdout) != 0 && err == NULL) + err = got_error_from_errno("fflush"); + return err; +} + +static const struct got_error * +print_commits(struct got_object_id *root_id, struct got_object_id *end_id, + struct got_repository *repo, const char *path, int show_changed_paths, + int show_patch, const char *search_pattern, int diff_context, int limit, + int log_branches, int reverse_display_order, struct got_reflist_head *refs) +{ + const struct got_error *err; + struct got_commit_graph *graph; + regex_t regex; + int have_match; + struct got_object_id_queue reversed_commits; + struct got_object_qid *qid; + struct got_commit_object *commit; + struct got_pathlist_head changed_paths; + struct got_pathlist_entry *pe; + + SIMPLEQ_INIT(&reversed_commits); + TAILQ_INIT(&changed_paths); + + if (search_pattern && regcomp(®ex, search_pattern, + REG_EXTENDED | REG_NOSUB | REG_NEWLINE)) + return got_error_msg(GOT_ERR_REGEX, search_pattern); + + err = got_commit_graph_open(&graph, path, !log_branches); + if (err) + return err; + err = got_commit_graph_iter_start(graph, root_id, repo, + check_cancelled, NULL); + if (err) + goto done; + for (;;) { + struct got_object_id *id; + + if (sigint_received || sigpipe_received) + break; + + err = got_commit_graph_iter_next(&id, graph, repo, + check_cancelled, NULL); + if (err) { + if (err->code == GOT_ERR_ITER_COMPLETED) + err = NULL; + break; + } + if (id == NULL) + break; + + err = got_object_open_as_commit(&commit, repo, id); + if (err) + break; + + if (show_changed_paths && !reverse_display_order) { + err = get_changed_paths(&changed_paths, commit, repo); + if (err) + break; + } + + if (search_pattern) { + err = match_logmsg(&have_match, id, commit, ®ex); + if (err) { + got_object_commit_close(commit); + break; + } + if (have_match == 0 && show_changed_paths) + match_changed_paths(&have_match, + &changed_paths, ®ex); + if (have_match == 0) { + got_object_commit_close(commit); + TAILQ_FOREACH(pe, &changed_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&changed_paths); + continue; + } + } + + if (reverse_display_order) { + err = got_object_qid_alloc(&qid, id); + if (err) + break; + SIMPLEQ_INSERT_HEAD(&reversed_commits, qid, entry); + got_object_commit_close(commit); + } else { + err = print_commit(commit, id, repo, path, + show_changed_paths ? &changed_paths : NULL, + show_patch, diff_context, refs); + got_object_commit_close(commit); + if (err) + break; + } + if ((limit && --limit == 0) || + (end_id && got_object_id_cmp(id, end_id) == 0)) + break; + + TAILQ_FOREACH(pe, &changed_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&changed_paths); + } + if (reverse_display_order) { + SIMPLEQ_FOREACH(qid, &reversed_commits, entry) { + err = got_object_open_as_commit(&commit, repo, qid->id); + if (err) + break; + if (show_changed_paths) { + err = get_changed_paths(&changed_paths, + commit, repo); + if (err) + break; + } + err = print_commit(commit, qid->id, repo, path, + show_changed_paths ? &changed_paths : NULL, + show_patch, diff_context, refs); + got_object_commit_close(commit); + if (err) + break; + TAILQ_FOREACH(pe, &changed_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&changed_paths); + } + } +done: + while (!SIMPLEQ_EMPTY(&reversed_commits)) { + qid = SIMPLEQ_FIRST(&reversed_commits); + SIMPLEQ_REMOVE_HEAD(&reversed_commits, entry); + got_object_qid_free(qid); + } + TAILQ_FOREACH(pe, &changed_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&changed_paths); + if (search_pattern) + regfree(®ex); + got_commit_graph_close(graph); + return err; +} + +__dead static void +usage_log(void) +{ + fprintf(stderr, "usage: %s log [-b] [-c commit] [-C number] [ -l N ] " + "[-p] [-P] [-x commit] [-s search-pattern] [-r repository-path] " + "[-R] [path]\n", getprogname()); + exit(1); +} + +static int +get_default_log_limit(void) +{ + const char *got_default_log_limit; + long long n; + const char *errstr; + + got_default_log_limit = getenv("GOT_LOG_DEFAULT_LIMIT"); + if (got_default_log_limit == NULL) + return 0; + n = strtonum(got_default_log_limit, 0, INT_MAX, &errstr); + if (errstr != NULL) + return 0; + return n; +} + +static const struct got_error * +resolve_commit_arg(struct got_object_id **id, const char *commit_arg, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_reference *ref; + + *id = NULL; + + err = got_ref_open(&ref, repo, commit_arg, 0); + if (err == NULL) { + int obj_type; + err = got_ref_resolve(id, repo, ref); + got_ref_close(ref); + if (err) + return err; + err = got_object_get_type(&obj_type, repo, *id); + if (err) + return err; + if (obj_type == GOT_OBJ_TYPE_TAG) { + struct got_tag_object *tag; + err = got_object_open_as_tag(&tag, repo, *id); + if (err) + return err; + if (got_object_tag_get_object_type(tag) != + GOT_OBJ_TYPE_COMMIT) { + got_object_tag_close(tag); + return got_error(GOT_ERR_OBJ_TYPE); + } + free(*id); + *id = got_object_id_dup( + got_object_tag_get_object_id(tag)); + if (*id == NULL) + err = got_error_from_errno( + "got_object_id_dup"); + got_object_tag_close(tag); + if (err) + return err; + } else if (obj_type != GOT_OBJ_TYPE_COMMIT) + return got_error(GOT_ERR_OBJ_TYPE); + } else { + err = got_repo_match_object_id_prefix(id, commit_arg, + GOT_OBJ_TYPE_COMMIT, repo); + } + + return err; +} + +static const struct got_error * +cmd_log(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + struct got_object_id *start_id = NULL, *end_id = NULL; + char *repo_path = NULL, *path = NULL, *cwd = NULL, *in_repo_path = NULL; + const char *start_commit = NULL, *end_commit = NULL; + const char *search_pattern = NULL; + int diff_context = -1, ch; + int show_changed_paths = 0, show_patch = 0, limit = 0, log_branches = 0; + int reverse_display_order = 0; + const char *errstr; + struct got_reflist_head refs; + + SIMPLEQ_INIT(&refs); + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) + == -1) + err(1, "pledge"); +#endif + + limit = get_default_log_limit(); + + while ((ch = getopt(argc, argv, "bpPc:C:l:r:Rs:x:")) != -1) { + switch (ch) { + case 'p': + show_patch = 1; + break; + case 'P': + show_changed_paths = 1; + break; + case 'c': + start_commit = optarg; + break; + case 'C': + diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT, + &errstr); + if (errstr != NULL) + err(1, "-C option %s", errstr); + break; + case 'l': + limit = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + err(1, "-l option %s", errstr); + break; + case 'b': + log_branches = 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 'R': + reverse_display_order = 1; + break; + case 's': + search_pattern = optarg; + break; + case 'x': + end_commit = optarg; + break; + default: + usage_log(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (diff_context == -1) + diff_context = 3; + else if (!show_patch) + errx(1, "-C requires -p"); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + error = NULL; + } + + if (argc == 0) { + path = strdup(""); + if (path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } else if (argc == 1) { + if (worktree) { + error = got_worktree_resolve_path(&path, worktree, + argv[0]); + if (error) + goto done; + } else { + path = strdup(argv[0]); + if (path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } else + usage_log(); + + if (repo_path == NULL) { + repo_path = worktree ? + strdup(got_worktree_get_repo_path(worktree)) : strdup(cwd); + } + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (start_commit == NULL) { + struct got_reference *head_ref; + struct got_commit_object *commit = NULL; + error = got_ref_open(&head_ref, repo, + worktree ? got_worktree_get_head_ref_name(worktree) + : GOT_REF_HEAD, 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&start_id, repo, head_ref); + got_ref_close(head_ref); + if (error != NULL) + goto done; + error = got_object_open_as_commit(&commit, repo, + start_id); + if (error != NULL) + goto done; + got_object_commit_close(commit); + } else { + error = resolve_commit_arg(&start_id, start_commit, repo); + if (error != NULL) + goto done; + } + if (end_commit != NULL) { + error = resolve_commit_arg(&end_id, end_commit, repo); + if (error != NULL) + goto done; + } + + if (worktree) { + const char *prefix = got_worktree_get_path_prefix(worktree); + char *p; + if (asprintf(&p, "%s%s%s", prefix, + (strcmp(prefix, "/") != 0) ? "/" : "", path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + error = got_repo_map_path(&in_repo_path, repo, p, 0); + free(p); + } else + error = got_repo_map_path(&in_repo_path, repo, path, 1); + if (error != NULL) + goto done; + if (in_repo_path) { + free(path); + path = in_repo_path; + } + + error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL); + if (error) + goto done; + + error = print_commits(start_id, end_id, repo, path, show_changed_paths, + show_patch, search_pattern, diff_context, limit, log_branches, + reverse_display_order, &refs); +done: + free(path); + free(repo_path); + free(cwd); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + got_ref_list_free(&refs); + return error; +} + +__dead static void +usage_diff(void) +{ + fprintf(stderr, "usage: %s diff [-C number] [-r repository-path] [-s] " + "[-w] [object1 object2 | path]\n", getprogname()); + exit(1); +} + +struct print_diff_arg { + struct got_repository *repo; + struct got_worktree *worktree; + int diff_context; + const char *id_str; + int header_shown; + int diff_staged; + int ignore_whitespace; +}; + +/* + * Create a file which contains the target path of a symlink so we can feed + * it as content to the diff engine. + */ +static const struct got_error * +get_symlink_target_file(int *fd, int dirfd, const char *de_name, + const char *abspath) +{ + const struct got_error *err = NULL; + char target_path[PATH_MAX]; + ssize_t target_len, outlen; + + *fd = -1; + + if (dirfd != -1) { + target_len = readlinkat(dirfd, de_name, target_path, PATH_MAX); + if (target_len == -1) + return got_error_from_errno2("readlinkat", abspath); + } else { + target_len = readlink(abspath, target_path, PATH_MAX); + if (target_len == -1) + return got_error_from_errno2("readlink", abspath); + } + + *fd = got_opentempfd(); + if (*fd == -1) + return got_error_from_errno("got_opentempfd"); + + outlen = write(*fd, target_path, target_len); + if (outlen == -1) { + err = got_error_from_errno("got_opentempfd"); + goto done; + } + + if (lseek(*fd, 0, SEEK_SET) == -1) { + err = got_error_from_errno2("lseek", abspath); + goto done; + } +done: + if (err) { + close(*fd); + *fd = -1; + } + return err; +} + +static const struct got_error * +print_diff(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) +{ + struct print_diff_arg *a = arg; + const struct got_error *err = NULL; + struct got_blob_object *blob1 = NULL; + int fd = -1; + FILE *f2 = NULL; + char *abspath = NULL, *label1 = NULL; + struct stat sb; + + if (a->diff_staged) { + if (staged_status != GOT_STATUS_MODIFY && + staged_status != GOT_STATUS_ADD && + staged_status != GOT_STATUS_DELETE) + return NULL; + } else { + if (staged_status == GOT_STATUS_DELETE) + return NULL; + if (status == GOT_STATUS_NONEXISTENT) + return got_error_set_errno(ENOENT, path); + if (status != GOT_STATUS_MODIFY && + status != GOT_STATUS_ADD && + status != GOT_STATUS_DELETE && + status != GOT_STATUS_CONFLICT) + return NULL; + } + + if (!a->header_shown) { + printf("diff %s %s%s\n", a->id_str, + got_worktree_get_root_path(a->worktree), + a->diff_staged ? " (staged changes)" : ""); + a->header_shown = 1; + } + + if (a->diff_staged) { + const char *label1 = NULL, *label2 = NULL; + switch (staged_status) { + case GOT_STATUS_MODIFY: + label1 = path; + label2 = path; + break; + case GOT_STATUS_ADD: + label2 = path; + break; + case GOT_STATUS_DELETE: + label1 = path; + break; + default: + return got_error(GOT_ERR_FILE_STATUS); + } + return got_diff_objects_as_blobs(blob_id, staged_blob_id, + label1, label2, a->diff_context, a->ignore_whitespace, + a->repo, stdout); + } + + if (staged_status == GOT_STATUS_ADD || + staged_status == GOT_STATUS_MODIFY) { + char *id_str; + err = got_object_open_as_blob(&blob1, a->repo, staged_blob_id, + 8192); + if (err) + goto done; + err = got_object_id_str(&id_str, staged_blob_id); + if (err) + goto done; + if (asprintf(&label1, "%s (staged)", id_str) == -1) { + err = got_error_from_errno("asprintf"); + free(id_str); + goto done; + } + free(id_str); + } else if (status != GOT_STATUS_ADD) { + err = got_object_open_as_blob(&blob1, a->repo, blob_id, 8192); + if (err) + goto done; + } + + if (status != GOT_STATUS_DELETE) { + if (asprintf(&abspath, "%s/%s", + got_worktree_get_root_path(a->worktree), path) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + if (dirfd != -1) { + fd = openat(dirfd, de_name, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (errno != ELOOP) { + err = got_error_from_errno2("openat", + abspath); + goto done; + } + err = get_symlink_target_file(&fd, dirfd, + de_name, abspath); + if (err) + goto done; + } + } else { + fd = open(abspath, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (errno != ELOOP) { + err = got_error_from_errno2("open", + abspath); + goto done; + } + err = get_symlink_target_file(&fd, dirfd, + de_name, abspath); + if (err) + goto done; + } + } + if (fstat(fd, &sb) == -1) { + err = got_error_from_errno2("fstat", abspath); + goto done; + } + f2 = fdopen(fd, "r"); + if (f2 == NULL) { + err = got_error_from_errno2("fdopen", abspath); + goto done; + } + fd = -1; + } else + sb.st_size = 0; + + err = got_diff_blob_file(blob1, label1, f2, sb.st_size, path, + a->diff_context, a->ignore_whitespace, stdout); +done: + if (blob1) + got_object_blob_close(blob1); + if (f2 && fclose(f2) == EOF && err == NULL) + err = got_error_from_errno("fclose"); + if (fd != -1 && close(fd) == -1 && err == NULL) + err = got_error_from_errno("close"); + free(abspath); + return err; +} + +static const struct got_error * +cmd_diff(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *repo_path = NULL; + struct got_object_id *id1 = NULL, *id2 = NULL; + const char *id_str1 = NULL, *id_str2 = NULL; + char *label1 = NULL, *label2 = NULL; + int type1, type2; + int diff_context = 3, diff_staged = 0, ignore_whitespace = 0, ch; + const char *errstr; + char *path = NULL; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "C:r:sw")) != -1) { + switch (ch) { + case 'C': + diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT, + &errstr); + if (errstr != NULL) + err(1, "-C option %s", errstr); + 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 's': + diff_staged = 1; + break; + case 'w': + ignore_whitespace = 1; + break; + default: + usage_diff(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + if (argc <= 1) { + if (repo_path) + errx(1, + "-r option can't be used when diffing a work tree"); + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "diff", + cwd); + goto done; + } + repo_path = strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + if (argc == 1) { + error = got_worktree_resolve_path(&path, worktree, + argv[0]); + if (error) + goto done; + } else { + path = strdup(""); + if (path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } else if (argc == 2) { + if (diff_staged) + errx(1, "-s option can't be used when diffing " + "objects in repository"); + id_str1 = argv[0]; + id_str2 = argv[1]; + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + if (worktree) { + repo_path = strdup( + got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + } else + usage_diff(); + + error = got_repo_open(&repo, repo_path, NULL); + free(repo_path); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (argc <= 1) { + struct print_diff_arg arg; + struct got_pathlist_head paths; + char *id_str; + + TAILQ_INIT(&paths); + + error = got_object_id_str(&id_str, + got_worktree_get_base_commit_id(worktree)); + if (error) + goto done; + arg.repo = repo; + arg.worktree = worktree; + arg.diff_context = diff_context; + arg.id_str = id_str; + arg.header_shown = 0; + arg.diff_staged = diff_staged; + arg.ignore_whitespace = ignore_whitespace; + + error = got_pathlist_append(&paths, path, NULL); + if (error) + goto done; + + error = got_worktree_status(worktree, &paths, repo, print_diff, + &arg, check_cancelled, NULL); + free(id_str); + got_pathlist_free(&paths); + goto done; + } + + error = got_repo_match_object_id(&id1, &label1, id_str1, + GOT_OBJ_TYPE_ANY, 1, repo); + if (error) + goto done; + + error = got_repo_match_object_id(&id2, &label2, id_str2, + GOT_OBJ_TYPE_ANY, 1, repo); + if (error) + goto done; + + error = got_object_get_type(&type1, repo, id1); + if (error) + goto done; + + error = got_object_get_type(&type2, repo, id2); + if (error) + goto done; + + if (type1 != type2) { + error = got_error(GOT_ERR_OBJ_TYPE); + goto done; + } + + switch (type1) { + case GOT_OBJ_TYPE_BLOB: + error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, + diff_context, ignore_whitespace, repo, stdout); + break; + case GOT_OBJ_TYPE_TREE: + error = got_diff_objects_as_trees(id1, id2, "", "", + diff_context, ignore_whitespace, repo, stdout); + break; + case GOT_OBJ_TYPE_COMMIT: + printf("diff %s %s\n", label1, label2); + error = got_diff_objects_as_commits(id1, id2, diff_context, + ignore_whitespace, repo, stdout); + break; + default: + error = got_error(GOT_ERR_OBJ_TYPE); + } +done: + free(label1); + free(label2); + free(id1); + free(id2); + free(path); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + return error; +} + +__dead static void +usage_blame(void) +{ + fprintf(stderr, + "usage: %s blame [-c commit] [-r repository-path] path\n", + getprogname()); + exit(1); +} + +struct blame_line { + int annotated; + char *id_str; + char *committer; + char datebuf[11]; /* YYYY-MM-DD + NUL */ +}; + +struct blame_cb_args { + struct blame_line *lines; + int nlines; + int nlines_prec; + int lineno_cur; + off_t *line_offsets; + FILE *f; + struct got_repository *repo; +}; + +static const struct got_error * +blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id) +{ + const struct got_error *err = NULL; + struct blame_cb_args *a = arg; + struct blame_line *bline; + char *line = NULL; + size_t linesize = 0; + struct got_commit_object *commit = NULL; + off_t offset; + struct tm tm; + time_t committer_time; + + if (nlines != a->nlines || + (lineno != -1 && lineno < 1) || lineno > a->nlines) + return got_error(GOT_ERR_RANGE); + + if (sigint_received) + return got_error(GOT_ERR_ITER_COMPLETED); + + if (lineno == -1) + return NULL; /* no change in this commit */ + + /* Annotate this line. */ + bline = &a->lines[lineno - 1]; + if (bline->annotated) + return NULL; + err = got_object_id_str(&bline->id_str, id); + if (err) + return err; + + err = got_object_open_as_commit(&commit, a->repo, id); + if (err) + goto done; + + bline->committer = strdup(got_object_commit_get_committer(commit)); + if (bline->committer == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + + committer_time = got_object_commit_get_committer_time(commit); + if (localtime_r(&committer_time, &tm) == NULL) + return got_error_from_errno("localtime_r"); + if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d", + &tm) >= sizeof(bline->datebuf)) { + err = got_error(GOT_ERR_NO_SPACE); + goto done; + } + bline->annotated = 1; + + /* Print lines annotated so far. */ + bline = &a->lines[a->lineno_cur - 1]; + if (!bline->annotated) + goto done; + + offset = a->line_offsets[a->lineno_cur - 1]; + if (fseeko(a->f, offset, SEEK_SET) == -1) { + err = got_error_from_errno("fseeko"); + goto done; + } + + while (bline->annotated) { + char *smallerthan, *at, *nl, *committer; + size_t len; + + if (getline(&line, &linesize, a->f) == -1) { + if (ferror(a->f)) + err = got_error_from_errno("getline"); + break; + } + + committer = bline->committer; + smallerthan = strchr(committer, '<'); + if (smallerthan && smallerthan[1] != '\0') + committer = smallerthan + 1; + at = strchr(committer, '@'); + if (at) + *at = '\0'; + len = strlen(committer); + if (len >= 9) + committer[8] = '\0'; + + nl = strchr(line, '\n'); + if (nl) + *nl = '\0'; + printf("%.*d) %.8s %s %-8s %s\n", a->nlines_prec, a->lineno_cur, + bline->id_str, bline->datebuf, committer, line); + + a->lineno_cur++; + bline = &a->lines[a->lineno_cur - 1]; + } +done: + if (commit) + got_object_commit_close(commit); + free(line); + return err; +} + +static const struct got_error * +cmd_blame(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL; + char *link_target = NULL; + struct got_object_id *obj_id = NULL; + struct got_object_id *commit_id = NULL; + struct got_blob_object *blob = NULL; + char *commit_id_str = NULL; + struct blame_cb_args bca; + int ch, obj_type, i; + size_t filesize; + + memset(&bca, 0, sizeof(bca)); + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "c:r:")) != -1) { + switch (ch) { + case 'c': + commit_id_str = optarg; + break; + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) + return got_error_from_errno2("realpath", + optarg); + got_path_strip_trailing_slashes(repo_path); + break; + default: + usage_blame(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc == 1) + path = argv[0]; + else + usage_blame(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + if (error) + goto done; + } + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + + if (worktree) { + const char *prefix = got_worktree_get_path_prefix(worktree); + char *p, *worktree_subdir = cwd + + strlen(got_worktree_get_root_path(worktree)); + if (asprintf(&p, "%s%s%s%s%s", + prefix, (strcmp(prefix, "/") != 0) ? "/" : "", + worktree_subdir, worktree_subdir[0] ? "/" : "", + path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + error = got_repo_map_path(&in_repo_path, repo, p, 0); + free(p); + } else { + error = got_repo_map_path(&in_repo_path, repo, path, 1); + } + if (error) + goto done; + + if (commit_id_str == NULL) { + struct got_reference *head_ref; + error = got_ref_open(&head_ref, repo, worktree ? + got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD, 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, head_ref); + got_ref_close(head_ref); + if (error != NULL) + goto done; + } else { + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + if (error) + goto done; + } + + error = got_object_resolve_symlinks(&link_target, in_repo_path, + commit_id, repo); + if (error) + goto done; + + error = got_object_id_by_path(&obj_id, repo, commit_id, + link_target ? link_target : in_repo_path); + if (error) + goto done; + + error = got_object_get_type(&obj_type, repo, obj_id); + if (error) + goto done; + + if (obj_type != GOT_OBJ_TYPE_BLOB) { + error = got_error_path(link_target ? link_target : in_repo_path, + GOT_ERR_OBJ_TYPE); + goto done; + } + + error = got_object_open_as_blob(&blob, repo, obj_id, 8192); + if (error) + goto done; + bca.f = got_opentemp(); + if (bca.f == NULL) { + error = got_error_from_errno("got_opentemp"); + goto done; + } + error = got_object_blob_dump_to_file(&filesize, &bca.nlines, + &bca.line_offsets, bca.f, blob); + if (error || bca.nlines == 0) + goto done; + + /* Don't include \n at EOF in the blame line count. */ + if (bca.line_offsets[bca.nlines - 1] == filesize) + bca.nlines--; + + bca.lines = calloc(bca.nlines, sizeof(*bca.lines)); + if (bca.lines == NULL) { + error = got_error_from_errno("calloc"); + goto done; + } + bca.lineno_cur = 1; + bca.nlines_prec = 0; + i = bca.nlines; + while (i > 0) { + i /= 10; + bca.nlines_prec++; + } + bca.repo = repo; + + error = got_blame(link_target ? link_target : in_repo_path, commit_id, + repo, blame_cb, &bca, check_cancelled, NULL); +done: + free(in_repo_path); + free(link_target); + free(repo_path); + free(cwd); + free(commit_id); + free(obj_id); + if (blob) + got_object_blob_close(blob); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + if (bca.lines) { + for (i = 0; i < bca.nlines; i++) { + struct blame_line *bline = &bca.lines[i]; + free(bline->id_str); + free(bline->committer); + } + free(bca.lines); + } + free(bca.line_offsets); + if (bca.f && fclose(bca.f) == EOF && error == NULL) + error = got_error_from_errno("fclose"); + return error; +} + +__dead static void +usage_tree(void) +{ + fprintf(stderr, + "usage: %s tree [-c commit] [-r repository-path] [-iR] [path]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +print_entry(struct got_tree_entry *te, const char *id, const char *path, + const char *root_path, struct got_repository *repo) +{ + const struct got_error *err = NULL; + int is_root_path = (strcmp(path, root_path) == 0); + const char *modestr = ""; + mode_t mode = got_tree_entry_get_mode(te); + char *link_target = NULL; + + path += strlen(root_path); + while (path[0] == '/') + path++; + + if (got_object_tree_entry_is_submodule(te)) + modestr = "$"; + else if (S_ISLNK(mode)) { + int i; + + err = got_tree_entry_get_symlink_target(&link_target, te, repo); + if (err) + return err; + for (i = 0; i < strlen(link_target); i++) { + if (!isprint((unsigned char)link_target[i])) + link_target[i] = '?'; + } + + modestr = "@"; + } + else if (S_ISDIR(mode)) + modestr = "/"; + else if (mode & S_IXUSR) + modestr = "*"; + + printf("%s%s%s%s%s%s%s\n", id ? id : "", path, + is_root_path ? "" : "/", got_tree_entry_get_name(te), modestr, + link_target ? " -> ": "", link_target ? link_target : ""); + + free(link_target); + return NULL; +} + +static const struct got_error * +print_tree(const char *path, struct got_object_id *commit_id, + int show_ids, int recurse, const char *root_path, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_id *tree_id = NULL; + struct got_tree_object *tree = NULL; + int nentries, i; + + err = got_object_id_by_path(&tree_id, repo, commit_id, path); + if (err) + goto done; + + err = got_object_open_as_tree(&tree, repo, tree_id); + if (err) + goto done; + nentries = got_object_tree_get_nentries(tree); + for (i = 0; i < nentries; i++) { + struct got_tree_entry *te; + char *id = NULL; + + if (sigint_received || sigpipe_received) + break; + + te = got_object_tree_get_entry(tree, i); + if (show_ids) { + char *id_str; + err = got_object_id_str(&id_str, + got_tree_entry_get_id(te)); + if (err) + goto done; + if (asprintf(&id, "%s ", id_str) == -1) { + err = got_error_from_errno("asprintf"); + free(id_str); + goto done; + } + free(id_str); + } + err = print_entry(te, id, path, root_path, repo); + free(id); + if (err) + goto done; + + if (recurse && S_ISDIR(got_tree_entry_get_mode(te))) { + char *child_path; + if (asprintf(&child_path, "%s%s%s", path, + path[0] == '/' && path[1] == '\0' ? "" : "/", + got_tree_entry_get_name(te)) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + err = print_tree(child_path, commit_id, show_ids, 1, + root_path, repo); + free(child_path); + if (err) + goto done; + } + } +done: + if (tree) + got_object_tree_close(tree); + free(tree_id); + return err; +} + +static const struct got_error * +cmd_tree(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + const char *path, *refname = NULL; + char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL; + struct got_object_id *commit_id = NULL; + char *commit_id_str = NULL; + int show_ids = 0, recurse = 0; + int ch; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "c:r:iR")) != -1) { + switch (ch) { + case 'c': + commit_id_str = optarg; + break; + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) + return got_error_from_errno2("realpath", + optarg); + got_path_strip_trailing_slashes(repo_path); + break; + case 'i': + show_ids = 1; + break; + case 'R': + recurse = 1; + break; + default: + usage_tree(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc == 1) + path = argv[0]; + else if (argc > 1) + usage_tree(); + else + path = NULL; + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + + if (path == NULL) { + if (worktree) { + char *p, *worktree_subdir = cwd + + strlen(got_worktree_get_root_path(worktree)); + if (asprintf(&p, "%s/%s", + got_worktree_get_path_prefix(worktree), + worktree_subdir) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + error = got_repo_map_path(&in_repo_path, repo, p, 0); + free(p); + if (error) + goto done; + } else + path = "/"; + } + if (in_repo_path == NULL) { + error = got_repo_map_path(&in_repo_path, repo, path, 1); + if (error != NULL) + goto done; + } + + if (commit_id_str == NULL) { + struct got_reference *head_ref; + if (worktree) + refname = got_worktree_get_head_ref_name(worktree); + else + refname = GOT_REF_HEAD; + error = got_ref_open(&head_ref, repo, refname, 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, head_ref); + got_ref_close(head_ref); + if (error != NULL) + goto done; + } else { + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + if (error) + goto done; + } + + error = print_tree(in_repo_path, commit_id, show_ids, recurse, + in_repo_path, repo); +done: + free(in_repo_path); + free(repo_path); + free(cwd); + free(commit_id); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + return error; +} + +__dead static void +usage_status(void) +{ + fprintf(stderr, "usage: %s status [-s status-codes ] [path ...]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +print_status(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) +{ + if (status == staged_status && (status == GOT_STATUS_DELETE)) + status = GOT_STATUS_NO_CHANGE; + if (arg) { + char *status_codes = arg; + size_t ncodes = strlen(status_codes); + int i; + for (i = 0; i < ncodes ; i++) { + if (status == status_codes[i] || + staged_status == status_codes[i]) + break; + } + if (i == ncodes) + return NULL; + } + printf("%c%c %s\n", status, staged_status, path); + return NULL; +} + +static const struct got_error * +cmd_status(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *status_codes = NULL;; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch, i; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "s:")) != -1) { + switch (ch) { + case 's': + for (i = 0; i < strlen(optarg); i++) { + switch (optarg[i]) { + case GOT_STATUS_MODIFY: + case GOT_STATUS_ADD: + case GOT_STATUS_DELETE: + case GOT_STATUS_CONFLICT: + case GOT_STATUS_MISSING: + case GOT_STATUS_OBSTRUCTED: + case GOT_STATUS_UNVERSIONED: + case GOT_STATUS_MODE_CHANGE: + case GOT_STATUS_NONEXISTENT: + break; + default: + errx(1, "invalid status code '%c'", + optarg[i]); + } + } + status_codes = optarg; + break; + default: + usage_status(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "status", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + error = got_worktree_status(worktree, &paths, repo, print_status, + status_codes, check_cancelled, NULL); +done: + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_ref(void) +{ + fprintf(stderr, + "usage: %s ref [-r repository] [-l] [-c object] [-s reference] " + "[-d] [name]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +list_refs(struct got_repository *repo, const char *refname) +{ + static const struct got_error *err = NULL; + struct got_reflist_head refs; + struct got_reflist_entry *re; + + SIMPLEQ_INIT(&refs); + err = got_ref_list(&refs, repo, refname, got_ref_cmp_by_name, NULL); + if (err) + return err; + + SIMPLEQ_FOREACH(re, &refs, entry) { + char *refstr; + refstr = got_ref_to_str(re->ref); + if (refstr == NULL) + return got_error_from_errno("got_ref_to_str"); + printf("%s: %s\n", got_ref_get_name(re->ref), refstr); + free(refstr); + } + + got_ref_list_free(&refs); + return NULL; +} + +static const struct got_error * +delete_ref(struct got_repository *repo, const char *refname) +{ + const struct got_error *err = NULL; + struct got_reference *ref; + + err = got_ref_open(&ref, repo, refname, 0); + if (err) + return err; + + err = got_ref_delete(ref, repo); + got_ref_close(ref); + return err; +} + +static const struct got_error * +add_ref(struct got_repository *repo, const char *refname, const char *target) +{ + const struct got_error *err = NULL; + struct got_object_id *id; + struct got_reference *ref = NULL; + + /* + * Don't let the user create a reference name with a leading '-'. + * While technically a valid reference name, this case is usually + * an unintended typo. + */ + if (refname[0] == '-') + return got_error_path(refname, GOT_ERR_REF_NAME_MINUS); + + err = got_repo_match_object_id_prefix(&id, target, GOT_OBJ_TYPE_ANY, + repo); + if (err) { + struct got_reference *target_ref; + + if (err->code != GOT_ERR_BAD_OBJ_ID_STR) + return err; + err = got_ref_open(&target_ref, repo, target, 0); + if (err) + return err; + err = got_ref_resolve(&id, repo, target_ref); + got_ref_close(target_ref); + if (err) + return err; + } + + err = got_ref_alloc(&ref, refname, id); + if (err) + goto done; + + err = got_ref_write(ref, repo); +done: + if (ref) + got_ref_close(ref); + free(id); + return err; +} + +static const struct got_error * +add_symref(struct got_repository *repo, const char *refname, const char *target) +{ + const struct got_error *err = NULL; + struct got_reference *ref = NULL; + struct got_reference *target_ref = NULL; + + /* + * Don't let the user create a reference name with a leading '-'. + * While technically a valid reference name, this case is usually + * an unintended typo. + */ + if (refname[0] == '-') + return got_error_path(refname, GOT_ERR_REF_NAME_MINUS); + + err = got_ref_open(&target_ref, repo, target, 0); + if (err) + return err; + + err = got_ref_alloc_symref(&ref, refname, target_ref); + if (err) + goto done; + + err = got_ref_write(ref, repo); +done: + if (target_ref) + got_ref_close(target_ref); + if (ref) + got_ref_close(ref); + return err; +} + +static const struct got_error * +cmd_ref(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *repo_path = NULL; + int ch, do_list = 0, do_delete = 0; + const char *obj_arg = NULL, *symref_target= NULL; + char *refname = NULL; + + while ((ch = getopt(argc, argv, "c:dr:ls:")) != -1) { + switch (ch) { + case 'c': + obj_arg = optarg; + break; + case 'd': + do_delete = 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 'l': + do_list = 1; + break; + case 's': + symref_target = optarg; + break; + default: + usage_ref(); + /* NOTREACHED */ + } + } + + if (obj_arg && do_list) + errx(1, "-c and -l options are mutually exclusive"); + if (obj_arg && do_delete) + errx(1, "-c and -d options are mutually exclusive"); + if (obj_arg && symref_target) + errx(1, "-c and -s options are mutually exclusive"); + if (symref_target && do_delete) + errx(1, "-s and -d options are mutually exclusive"); + if (symref_target && do_list) + errx(1, "-s and -l options are mutually exclusive"); + if (do_delete && do_list) + errx(1, "-d and -l options are mutually exclusive"); + + argc -= optind; + argv += optind; + + if (do_list) { + if (argc != 0 && argc != 1) + usage_ref(); + if (argc == 1) { + refname = strdup(argv[0]); + if (refname == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } else { + if (argc != 1) + usage_ref(); + refname = strdup(argv[0]); + if (refname == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + + if (refname) + got_path_strip_trailing_slashes(refname); + +#ifndef PROFILE + if (do_list) { + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath fattr flock proc exec " + "sendfd unveil", NULL) == -1) + err(1, "pledge"); + } +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), do_list, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (do_list) + error = list_refs(repo, refname); + else if (do_delete) + error = delete_ref(repo, refname); + else if (symref_target) + error = add_symref(repo, refname, symref_target); + else { + if (obj_arg == NULL) + usage_ref(); + error = add_ref(repo, refname, obj_arg); + } +done: + free(refname); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(repo_path); + return error; +} + +__dead static void +usage_branch(void) +{ + fprintf(stderr, + "usage: %s branch [-c commit] [-d] [-r repository] [-l] [-n] " + "[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; + + 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) +{ + 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; + + SIMPLEQ_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", + got_ref_cmp_by_name, NULL); + if (err) + return err; + + SIMPLEQ_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; + + if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) + return got_error_from_errno("asprintf"); + + err = got_ref_open(&ref, repo, refname, 0); + if (err) + goto done; + + 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 = got_ref_delete(ref, repo); +done: + if (ref) + got_ref_close(ref); + free(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 *base_refname = NULL, *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 (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(base_refname); + free(refname); + return err; +} + +static const struct got_error * +cmd_branch(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *repo_path = NULL; + int ch, do_list = 0, do_show = 0, do_update = 1; + const char *delref = NULL, *commit_id_arg = NULL; + struct got_reference *ref = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + struct got_object_id *commit_id = NULL; + char *commit_id_str = NULL; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "c:d:r:ln")) != -1) { + switch (ch) { + case 'c': + commit_id_arg = optarg; + break; + case 'd': + delref = optarg; + 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 'l': + do_list = 1; + break; + case 'n': + do_update = 0; + break; + default: + usage_branch(); + /* NOTREACHED */ + } + } + + if (do_list && delref) + errx(1, "-l and -d options are mutually exclusive"); + + 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(); + +#ifndef PROFILE + if (do_list || do_show) { + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath fattr flock proc exec " + "sendfd unveil", NULL) == -1) + err(1, "pledge"); + } +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), do_list, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (do_show) + error = show_current_branch(repo, worktree); + else if (do_list) + error = list_branches(repo, worktree); + else if (delref) + error = delete_branch(repo, worktree, delref); + else { + 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, 1, repo); + 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 commit %s\n", commit_id_str); + print_update_progress_stats(&upa); + } + } +done: + if (ref) + got_ref_close(ref); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(repo_path); + free(commit_id); + free(commit_id_str); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + return error; +} + + +__dead static void +usage_tag(void) +{ + fprintf(stderr, + "usage: %s tag [-c commit] [-r repository] [-l] " + "[-m message] name\n", getprogname()); + exit(1); +} + +#if 0 +static const struct got_error * +sort_tags(struct got_reflist_head *sorted, struct got_reflist_head *tags) +{ + const struct got_error *err = NULL; + struct got_reflist_entry *re, *se, *new; + struct got_object_id *re_id, *se_id; + struct got_tag_object *re_tag, *se_tag; + time_t re_time, se_time; + + SIMPLEQ_FOREACH(re, tags, entry) { + se = SIMPLEQ_FIRST(sorted); + if (se == NULL) { + err = got_reflist_entry_dup(&new, re); + if (err) + return err; + SIMPLEQ_INSERT_HEAD(sorted, new, entry); + continue; + } else { + err = got_ref_resolve(&re_id, repo, re->ref); + if (err) + break; + err = got_object_open_as_tag(&re_tag, repo, re_id); + free(re_id); + if (err) + break; + re_time = got_object_tag_get_tagger_time(re_tag); + got_object_tag_close(re_tag); + } + + while (se) { + err = got_ref_resolve(&se_id, repo, re->ref); + if (err) + break; + err = got_object_open_as_tag(&se_tag, repo, se_id); + free(se_id); + if (err) + break; + se_time = got_object_tag_get_tagger_time(se_tag); + got_object_tag_close(se_tag); + + if (se_time > re_time) { + err = got_reflist_entry_dup(&new, re); + if (err) + return err; + SIMPLEQ_INSERT_AFTER(sorted, se, new, entry); + break; + } + se = SIMPLEQ_NEXT(se, entry); + continue; + } + } +done: + return err; +} +#endif + +static const struct got_error * +list_tags(struct got_repository *repo, struct got_worktree *worktree) +{ + static const struct got_error *err = NULL; + struct got_reflist_head refs; + struct got_reflist_entry *re; + + SIMPLEQ_INIT(&refs); + + err = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo); + if (err) + return err; + + SIMPLEQ_FOREACH(re, &refs, entry) { + const char *refname; + char *refstr, *tagmsg0, *tagmsg, *line, *id_str, *datestr; + char datebuf[26]; + const char *tagger; + time_t tagger_time; + struct got_object_id *id; + struct got_tag_object *tag; + struct got_commit_object *commit = NULL; + + refname = got_ref_get_name(re->ref); + if (strncmp(refname, "refs/tags/", 10) != 0) + continue; + refname += 10; + refstr = got_ref_to_str(re->ref); + if (refstr == NULL) { + err = got_error_from_errno("got_ref_to_str"); + break; + } + printf("%stag %s %s\n", GOT_COMMIT_SEP_STR, refname, refstr); + free(refstr); + + err = got_ref_resolve(&id, repo, re->ref); + if (err) + break; + err = got_object_open_as_tag(&tag, repo, id); + if (err) { + if (err->code != GOT_ERR_OBJ_TYPE) { + free(id); + break; + } + /* "lightweight" tag */ + err = got_object_open_as_commit(&commit, repo, id); + if (err) { + free(id); + break; + } + tagger = got_object_commit_get_committer(commit); + tagger_time = + got_object_commit_get_committer_time(commit); + err = got_object_id_str(&id_str, id); + free(id); + if (err) + break; + } else { + free(id); + tagger = got_object_tag_get_tagger(tag); + tagger_time = got_object_tag_get_tagger_time(tag); + err = got_object_id_str(&id_str, + got_object_tag_get_object_id(tag)); + if (err) + break; + } + printf("from: %s\n", tagger); + datestr = get_datestr(&tagger_time, datebuf); + if (datestr) + printf("date: %s UTC\n", datestr); + if (commit) + printf("object: %s %s\n", GOT_OBJ_LABEL_COMMIT, id_str); + else { + switch (got_object_tag_get_object_type(tag)) { + case GOT_OBJ_TYPE_BLOB: + printf("object: %s %s\n", GOT_OBJ_LABEL_BLOB, + id_str); + break; + case GOT_OBJ_TYPE_TREE: + printf("object: %s %s\n", GOT_OBJ_LABEL_TREE, + id_str); + break; + case GOT_OBJ_TYPE_COMMIT: + printf("object: %s %s\n", GOT_OBJ_LABEL_COMMIT, + id_str); + break; + case GOT_OBJ_TYPE_TAG: + printf("object: %s %s\n", GOT_OBJ_LABEL_TAG, + id_str); + break; + default: + break; + } + } + free(id_str); + if (commit) { + err = got_object_commit_get_logmsg(&tagmsg0, commit); + if (err) + break; + got_object_commit_close(commit); + } else { + tagmsg0 = strdup(got_object_tag_get_message(tag)); + got_object_tag_close(tag); + if (tagmsg0 == NULL) { + err = got_error_from_errno("strdup"); + break; + } + } + + tagmsg = tagmsg0; + do { + line = strsep(&tagmsg, "\n"); + if (line) + printf(" %s\n", line); + } while (line); + free(tagmsg0); + } + + got_ref_list_free(&refs); + return NULL; +} + +static const struct got_error * +get_tag_message(char **tagmsg, char **tagmsg_path, const char *commit_id_str, + const char *tag_name, const char *repo_path) +{ + const struct got_error *err = NULL; + char *template = NULL, *initial_content = NULL; + char *editor = NULL; + int initial_content_len; + int fd = -1; + + if (asprintf(&template, GOT_TMPDIR_STR "/got-tagmsg") == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + initial_content_len = asprintf(&initial_content, + "\n# tagging commit %s as %s\n", + commit_id_str, tag_name); + if (initial_content_len == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + err = got_opentemp_named_fd(tagmsg_path, &fd, template); + if (err) + goto done; + + if (write(fd, initial_content, initial_content_len) == -1) { + err = got_error_from_errno2("write", *tagmsg_path); + goto done; + } + + err = get_editor(&editor); + if (err) + goto done; + err = edit_logmsg(tagmsg, editor, *tagmsg_path, initial_content); +done: + free(initial_content); + free(template); + free(editor); + + if (fd != -1 && close(fd) == -1 && err == NULL) + err = got_error_from_errno2("close", *tagmsg_path); + + /* Editor is done; we can now apply unveil(2) */ + if (err == NULL) + err = apply_unveil(repo_path, 0, NULL); + if (err) { + free(*tagmsg); + *tagmsg = NULL; + } + return err; +} + +static const struct got_error * +add_tag(struct got_repository *repo, struct got_worktree *worktree, + const char *tag_name, const char *commit_arg, const char *tagmsg_arg) +{ + const struct got_error *err = NULL; + struct got_object_id *commit_id = NULL, *tag_id = NULL; + char *label = NULL, *commit_id_str = NULL; + struct got_reference *ref = NULL; + char *refname = NULL, *tagmsg = NULL, *tagger = NULL; + char *tagmsg_path = NULL, *tag_id_str = NULL; + int preserve_tagmsg = 0; + + /* + * Don't let the user create a tag name with a leading '-'. + * While technically a valid reference name, this case is usually + * an unintended typo. + */ + if (tag_name[0] == '-') + return got_error_path(tag_name, GOT_ERR_REF_NAME_MINUS); + + err = get_author(&tagger, repo, worktree); + if (err) + return err; + + err = got_repo_match_object_id(&commit_id, &label, commit_arg, + GOT_OBJ_TYPE_COMMIT, 1, repo); + if (err) + goto done; + + err = got_object_id_str(&commit_id_str, commit_id); + if (err) + goto done; + + if (strncmp("refs/tags/", tag_name, 10) == 0) { + refname = strdup(tag_name); + if (refname == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + tag_name += 10; + } else if (asprintf(&refname, "refs/tags/%s", tag_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_TAG_EXISTS); + goto done; + } else if (err->code != GOT_ERR_NOT_REF) + goto done; + + if (tagmsg_arg == NULL) { + err = get_tag_message(&tagmsg, &tagmsg_path, commit_id_str, + tag_name, got_repo_get_path(repo)); + if (err) { + if (err->code != GOT_ERR_COMMIT_MSG_EMPTY && + tagmsg_path != NULL) + preserve_tagmsg = 1; + goto done; + } + } + + err = got_object_tag_create(&tag_id, tag_name, commit_id, + tagger, time(NULL), tagmsg ? tagmsg : tagmsg_arg, repo); + if (err) { + if (tagmsg_path) + preserve_tagmsg = 1; + goto done; + } + + err = got_ref_alloc(&ref, refname, tag_id); + if (err) { + if (tagmsg_path) + preserve_tagmsg = 1; + goto done; + } + + err = got_ref_write(ref, repo); + if (err) { + if (tagmsg_path) + preserve_tagmsg = 1; + goto done; + } + + err = got_object_id_str(&tag_id_str, tag_id); + if (err) { + if (tagmsg_path) + preserve_tagmsg = 1; + goto done; + } + printf("Created tag %s\n", tag_id_str); +done: + if (preserve_tagmsg) { + fprintf(stderr, "%s: tag message preserved in %s\n", + getprogname(), tagmsg_path); + } else if (tagmsg_path && unlink(tagmsg_path) == -1 && err == NULL) + err = got_error_from_errno2("unlink", tagmsg_path); + free(tag_id_str); + if (ref) + got_ref_close(ref); + free(commit_id); + free(commit_id_str); + free(refname); + free(tagmsg); + free(tagmsg_path); + free(tagger); + return err; +} + +static const struct got_error * +cmd_tag(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, *commit_id_str = NULL; + char *gitconfig_path = NULL; + const char *tag_name, *commit_id_arg = NULL, *tagmsg = NULL; + int ch, do_list = 0; + + while ((ch = getopt(argc, argv, "c:m:r:l")) != -1) { + switch (ch) { + case 'c': + commit_id_arg = optarg; + break; + case 'm': + tagmsg = optarg; + 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 'l': + do_list = 1; + break; + default: + usage_tag(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (do_list) { + if (commit_id_arg != NULL) + errx(1, + "-c option can only be used when creating a tag"); + if (tagmsg) + errx(1, "-l and -m options are mutually exclusive"); + if (argc > 0) + usage_tag(); + } else if (argc != 1) + usage_tag(); + + tag_name = argv[0]; + +#ifndef PROFILE + if (do_list) { + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath fattr flock proc exec " + "sendfd unveil", NULL) == -1) + err(1, "pledge"); + } +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + if (do_list) { + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + error = list_tags(repo, worktree); + } else { + error = get_gitconfig_path(&gitconfig_path); + if (error) + goto done; + error = got_repo_open(&repo, repo_path, gitconfig_path); + if (error != NULL) + goto done; + + if (tagmsg) { + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + } + + if (commit_id_arg == NULL) { + struct got_reference *head_ref; + struct got_object_id *commit_id; + error = got_ref_open(&head_ref, repo, + worktree ? got_worktree_get_head_ref_name(worktree) + : GOT_REF_HEAD, 0); + if (error) + goto done; + error = got_ref_resolve(&commit_id, repo, head_ref); + got_ref_close(head_ref); + if (error) + goto done; + error = got_object_id_str(&commit_id_str, commit_id); + free(commit_id); + if (error) + goto done; + } + + error = add_tag(repo, worktree, tag_name, + commit_id_str ? commit_id_str : commit_id_arg, tagmsg); + } +done: + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(repo_path); + free(gitconfig_path); + free(commit_id_str); + return error; +} + +__dead static void +usage_add(void) +{ + fprintf(stderr, "usage: %s add [-R] [-I] path ...\n", + getprogname()); + exit(1); +} + +static const struct got_error * +add_progress(void *arg, unsigned char status, const char *path) +{ + while (path[0] == '/') + path++; + printf("%c %s\n", status, path); + return NULL; +} + +static const struct got_error * +cmd_add(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch, can_recurse = 0, no_ignores = 0; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "IR")) != -1) { + switch (ch) { + case 'I': + no_ignores = 1; + break; + case 'R': + can_recurse = 1; + break; + default: + usage_add(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + if (argc < 1) + usage_add(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "add", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + if (!can_recurse && no_ignores) { + error = got_error_msg(GOT_ERR_BAD_PATH, + "disregarding ignores requires -R option"); + goto done; + + } + + if (!can_recurse) { + char *ondisk_path; + struct stat sb; + TAILQ_FOREACH(pe, &paths, entry) { + if (asprintf(&ondisk_path, "%s/%s", + got_worktree_get_root_path(worktree), + pe->path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (lstat(ondisk_path, &sb) == -1) { + if (errno == ENOENT) { + free(ondisk_path); + continue; + } + error = got_error_from_errno2("lstat", + ondisk_path); + free(ondisk_path); + goto done; + } + free(ondisk_path); + if (S_ISDIR(sb.st_mode)) { + error = got_error_msg(GOT_ERR_BAD_PATH, + "adding directories requires -R option"); + goto done; + } + } + } + + error = got_worktree_schedule_add(worktree, &paths, add_progress, + NULL, repo, no_ignores); +done: + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_remove(void) +{ + fprintf(stderr, "usage: %s remove [-f] [-k] [-R] [-s status-codes] " + "path ...\n", getprogname()); + exit(1); +} + +static const struct got_error * +print_remove_status(void *arg, unsigned char status, + unsigned char staged_status, const char *path) +{ + while (path[0] == '/') + path++; + if (status == GOT_STATUS_NONEXISTENT) + return NULL; + if (status == staged_status && (status == GOT_STATUS_DELETE)) + status = GOT_STATUS_NO_CHANGE; + printf("%c%c %s\n", status, staged_status, path); + return NULL; +} + +static const struct got_error * +cmd_remove(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + const char *status_codes = NULL; + char *cwd = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch, delete_local_mods = 0, can_recurse = 0, keep_on_disk = 0, i; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "fkRs:")) != -1) { + switch (ch) { + case 'f': + delete_local_mods = 1; + break; + case 'k': + keep_on_disk = 1; + break; + case 'R': + can_recurse = 1; + break; + case 's': + for (i = 0; i < strlen(optarg); i++) { + switch (optarg[i]) { + case GOT_STATUS_MODIFY: + delete_local_mods = 1; + break; + case GOT_STATUS_MISSING: + break; + default: + errx(1, "invalid status code '%c'", + optarg[i]); + } + } + status_codes = optarg; + break; + default: + usage_remove(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + if (argc < 1) + usage_remove(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "remove", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + if (!can_recurse) { + char *ondisk_path; + struct stat sb; + TAILQ_FOREACH(pe, &paths, entry) { + if (asprintf(&ondisk_path, "%s/%s", + got_worktree_get_root_path(worktree), + pe->path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (lstat(ondisk_path, &sb) == -1) { + if (errno == ENOENT) { + free(ondisk_path); + continue; + } + error = got_error_from_errno2("lstat", + ondisk_path); + free(ondisk_path); + goto done; + } + free(ondisk_path); + if (S_ISDIR(sb.st_mode)) { + error = got_error_msg(GOT_ERR_BAD_PATH, + "removing directories requires -R option"); + goto done; + } + } + } + + error = got_worktree_schedule_delete(worktree, &paths, + delete_local_mods, status_codes, print_remove_status, NULL, + repo, keep_on_disk); +done: + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_revert(void) +{ + fprintf(stderr, "usage: %s revert [-p] [-F response-script] [-R] " + "path ...\n", getprogname()); + exit(1); +} + +static const struct got_error * +revert_progress(void *arg, unsigned char status, const char *path) +{ + if (status == GOT_STATUS_UNVERSIONED) + return NULL; + + while (path[0] == '/') + path++; + printf("%c %s\n", status, path); + return NULL; +} + +struct choose_patch_arg { + FILE *patch_script_file; + const char *action; +}; + +static const struct got_error * +show_change(unsigned char status, const char *path, FILE *patch_file, int n, + int nchanges, const char *action) +{ + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + switch (status) { + case GOT_STATUS_ADD: + printf("A %s\n%s this addition? [y/n] ", path, action); + break; + case GOT_STATUS_DELETE: + printf("D %s\n%s this deletion? [y/n] ", path, action); + break; + case GOT_STATUS_MODIFY: + if (fseek(patch_file, 0L, SEEK_SET) == -1) + return got_error_from_errno("fseek"); + printf(GOT_COMMIT_SEP_STR); + while ((linelen = getline(&line, &linesize, patch_file)) != -1) + printf("%s", line); + if (ferror(patch_file)) + return got_error_from_errno("getline"); + printf(GOT_COMMIT_SEP_STR); + printf("M %s (change %d of %d)\n%s this change? [y/n/q] ", + path, n, nchanges, action); + break; + default: + return got_error_path(path, GOT_ERR_FILE_STATUS); + } + + return NULL; +} + +static const struct got_error * +choose_patch(int *choice, void *arg, unsigned char status, const char *path, + FILE *patch_file, int n, int nchanges) +{ + const struct got_error *err = NULL; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + int resp = ' '; + struct choose_patch_arg *a = arg; + + *choice = GOT_PATCH_CHOICE_NONE; + + if (a->patch_script_file) { + char *nl; + err = show_change(status, path, patch_file, n, nchanges, + a->action); + if (err) + return err; + linelen = getline(&line, &linesize, a->patch_script_file); + if (linelen == -1) { + if (ferror(a->patch_script_file)) + return got_error_from_errno("getline"); + return NULL; + } + nl = strchr(line, '\n'); + if (nl) + *nl = '\0'; + if (strcmp(line, "y") == 0) { + *choice = GOT_PATCH_CHOICE_YES; + printf("y\n"); + } else if (strcmp(line, "n") == 0) { + *choice = GOT_PATCH_CHOICE_NO; + printf("n\n"); + } else if (strcmp(line, "q") == 0 && + status == GOT_STATUS_MODIFY) { + *choice = GOT_PATCH_CHOICE_QUIT; + printf("q\n"); + } else + printf("invalid response '%s'\n", line); + free(line); + return NULL; + } + + while (resp != 'y' && resp != 'n' && resp != 'q') { + err = show_change(status, path, patch_file, n, nchanges, + a->action); + if (err) + return err; + resp = getchar(); + if (resp == '\n') + resp = getchar(); + if (status == GOT_STATUS_MODIFY) { + if (resp != 'y' && resp != 'n' && resp != 'q') { + printf("invalid response '%c'\n", resp); + resp = ' '; + } + } else if (resp != 'y' && resp != 'n') { + printf("invalid response '%c'\n", resp); + resp = ' '; + } + } + + if (resp == 'y') + *choice = GOT_PATCH_CHOICE_YES; + else if (resp == 'n') + *choice = GOT_PATCH_CHOICE_NO; + else if (resp == 'q' && status == GOT_STATUS_MODIFY) + *choice = GOT_PATCH_CHOICE_QUIT; + + return NULL; +} + + +static const struct got_error * +cmd_revert(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + char *cwd = NULL, *path = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch, can_recurse = 0, pflag = 0; + FILE *patch_script_file = NULL; + const char *patch_script_path = NULL; + struct choose_patch_arg cpa; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "pF:R")) != -1) { + switch (ch) { + case 'p': + pflag = 1; + break; + case 'F': + patch_script_path = optarg; + break; + case 'R': + can_recurse = 1; + break; + default: + usage_revert(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc < 1) + usage_revert(); + 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_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "revert", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + if (patch_script_path) { + patch_script_file = fopen(patch_script_path, "r"); + if (patch_script_file == NULL) { + error = got_error_from_errno2("fopen", + patch_script_path); + goto done; + } + } + error = apply_unveil(got_repo_get_path(repo), 1, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + if (!can_recurse) { + char *ondisk_path; + struct stat sb; + TAILQ_FOREACH(pe, &paths, entry) { + if (asprintf(&ondisk_path, "%s/%s", + got_worktree_get_root_path(worktree), + pe->path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (lstat(ondisk_path, &sb) == -1) { + if (errno == ENOENT) { + free(ondisk_path); + continue; + } + error = got_error_from_errno2("lstat", + ondisk_path); + free(ondisk_path); + goto done; + } + free(ondisk_path); + if (S_ISDIR(sb.st_mode)) { + error = got_error_msg(GOT_ERR_BAD_PATH, + "reverting directories requires -R option"); + goto done; + } + } + } + + cpa.patch_script_file = patch_script_file; + cpa.action = "revert"; + error = got_worktree_revert(worktree, &paths, revert_progress, NULL, + pflag ? choose_patch : NULL, &cpa, repo); +done: + if (patch_script_file && fclose(patch_script_file) == EOF && + error == NULL) + error = got_error_from_errno2("fclose", patch_script_path); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(path); + free(cwd); + return error; +} + +__dead static void +usage_commit(void) +{ + fprintf(stderr, "usage: %s commit [-m msg] [-S] [path ...]\n", + getprogname()); + exit(1); +} + +struct collect_commit_logmsg_arg { + const char *cmdline_log; + const char *editor; + const char *worktree_path; + const char *branch_name; + const char *repo_path; + char *logmsg_path; + +}; + +static const struct got_error * +collect_commit_logmsg(struct got_pathlist_head *commitable_paths, char **logmsg, + void *arg) +{ + char *initial_content = NULL; + struct got_pathlist_entry *pe; + const struct got_error *err = NULL; + char *template = NULL; + struct collect_commit_logmsg_arg *a = arg; + int initial_content_len; + int fd = -1; + size_t len; + + /* if a message was specified on the command line, just use it */ + if (a->cmdline_log != NULL && strlen(a->cmdline_log) != 0) { + len = strlen(a->cmdline_log) + 1; + *logmsg = malloc(len + 1); + if (*logmsg == NULL) + return got_error_from_errno("malloc"); + strlcpy(*logmsg, a->cmdline_log, len); + return NULL; + } + + if (asprintf(&template, "%s/logmsg", a->worktree_path) == -1) + return got_error_from_errno("asprintf"); + + initial_content_len = asprintf(&initial_content, + "\n# changes to be committed on branch %s:\n", + a->branch_name); + if (initial_content_len == -1) + return got_error_from_errno("asprintf"); + + err = got_opentemp_named_fd(&a->logmsg_path, &fd, template); + if (err) + goto done; + + if (write(fd, initial_content, initial_content_len) == -1) { + err = got_error_from_errno2("write", a->logmsg_path); + goto done; + } + + TAILQ_FOREACH(pe, commitable_paths, entry) { + struct got_commitable *ct = pe->data; + dprintf(fd, "# %c %s\n", + got_commitable_get_status(ct), + got_commitable_get_path(ct)); + } + + err = edit_logmsg(logmsg, a->editor, a->logmsg_path, initial_content); +done: + free(initial_content); + free(template); + + if (fd != -1 && close(fd) == -1 && err == NULL) + err = got_error_from_errno2("close", a->logmsg_path); + + /* Editor is done; we can now apply unveil(2) */ + if (err == NULL) + err = apply_unveil(a->repo_path, 0, a->worktree_path); + if (err) { + free(*logmsg); + *logmsg = NULL; + } + return err; +} + +static const struct got_error * +cmd_commit(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + char *cwd = NULL, *id_str = NULL; + struct got_object_id *id = NULL; + const char *logmsg = NULL; + struct collect_commit_logmsg_arg cl_arg; + char *gitconfig_path = NULL, *editor = NULL, *author = NULL; + int ch, rebase_in_progress, histedit_in_progress, preserve_logmsg = 0; + int allow_bad_symlinks = 0; + struct got_pathlist_head paths; + + TAILQ_INIT(&paths); + cl_arg.logmsg_path = NULL; + + while ((ch = getopt(argc, argv, "m:S")) != -1) { + switch (ch) { + case 'm': + logmsg = optarg; + break; + case 'S': + allow_bad_symlinks = 1; + break; + default: + usage_commit(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "commit", cwd); + goto done; + } + + error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); + if (error) + goto done; + if (rebase_in_progress) { + error = got_error(GOT_ERR_REBASING); + goto done; + } + + error = got_worktree_histedit_in_progress(&histedit_in_progress, + worktree); + if (error) + goto done; + + error = get_gitconfig_path(&gitconfig_path); + if (error) + goto done; + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + gitconfig_path); + if (error != NULL) + goto done; + + error = get_author(&author, repo, worktree); + if (error) + return error; + + /* + * unveil(2) traverses exec(2); if an editor is used we have + * to apply unveil after the log message has been written. + */ + if (logmsg == NULL || strlen(logmsg) == 0) + error = get_editor(&editor); + else + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + cl_arg.editor = editor; + cl_arg.cmdline_log = logmsg; + cl_arg.worktree_path = got_worktree_get_root_path(worktree); + cl_arg.branch_name = got_worktree_get_head_ref_name(worktree); + if (!histedit_in_progress) { + if (strncmp(cl_arg.branch_name, "refs/heads/", 11) != 0) { + error = got_error(GOT_ERR_COMMIT_BRANCH); + goto done; + } + cl_arg.branch_name += 11; + } + cl_arg.repo_path = got_repo_get_path(repo); + error = got_worktree_commit(&id, worktree, &paths, author, NULL, + allow_bad_symlinks, collect_commit_logmsg, &cl_arg, + print_status, NULL, repo); + if (error) { + if (error->code != GOT_ERR_COMMIT_MSG_EMPTY && + cl_arg.logmsg_path != NULL) + preserve_logmsg = 1; + goto done; + } + + error = got_object_id_str(&id_str, id); + if (error) + goto done; + printf("Created commit %s\n", id_str); +done: + if (preserve_logmsg) { + fprintf(stderr, "%s: log message preserved in %s\n", + getprogname(), cl_arg.logmsg_path); + } else if (cl_arg.logmsg_path && unlink(cl_arg.logmsg_path) == -1 && + error == NULL) + error = got_error_from_errno2("unlink", cl_arg.logmsg_path); + free(cl_arg.logmsg_path); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(id_str); + free(gitconfig_path); + free(editor); + free(author); + return error; +} + +__dead static void +usage_cherrypick(void) +{ + fprintf(stderr, "usage: %s cherrypick commit-id\n", getprogname()); + exit(1); +} + +static const struct got_error * +cmd_cherrypick(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + char *cwd = NULL, *commit_id_str = NULL; + struct got_object_id *commit_id = NULL; + struct got_commit_object *commit = NULL; + struct got_object_qid *pid; + struct got_reference *head_ref = NULL; + int ch; + struct got_update_progress_arg upa; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_cherrypick(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_cherrypick(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "cherrypick", + cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = got_repo_match_object_id_prefix(&commit_id, argv[0], + GOT_OBJ_TYPE_COMMIT, repo); + if (error != NULL) { + struct got_reference *ref; + if (error->code != GOT_ERR_BAD_OBJ_ID_STR) + goto done; + error = got_ref_open(&ref, repo, argv[0], 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, ref); + got_ref_close(ref); + if (error != NULL) + goto done; + } + error = got_object_id_str(&commit_id_str, commit_id); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, + got_worktree_get_head_ref_name(worktree), 0); + if (error != NULL) + goto done; + + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) { + if (error->code != GOT_ERR_ANCESTRY) + goto done; + error = NULL; + } else { + error = got_error(GOT_ERR_SAME_BRANCH); + goto done; + } + + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + goto done; + pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); + 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) + printf("Merged commit %s\n", commit_id_str); + print_update_progress_stats(&upa); +done: + if (commit) + got_object_commit_close(commit); + free(commit_id_str); + if (head_ref) + got_ref_close(head_ref); + if (worktree) + got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__dead static void +usage_backout(void) +{ + fprintf(stderr, "usage: %s backout commit-id\n", getprogname()); + exit(1); +} + +static const struct got_error * +cmd_backout(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + char *cwd = NULL, *commit_id_str = NULL; + struct got_object_id *commit_id = NULL; + struct got_commit_object *commit = NULL; + struct got_object_qid *pid; + struct got_reference *head_ref = NULL; + int ch; + struct got_update_progress_arg upa; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_backout(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_backout(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "backout", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = got_repo_match_object_id_prefix(&commit_id, argv[0], + GOT_OBJ_TYPE_COMMIT, repo); + if (error != NULL) { + struct got_reference *ref; + if (error->code != GOT_ERR_BAD_OBJ_ID_STR) + goto done; + error = got_ref_open(&ref, repo, argv[0], 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, ref); + got_ref_close(ref); + if (error != NULL) + goto done; + } + error = got_object_id_str(&commit_id_str, commit_id); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, + got_worktree_get_head_ref_name(worktree), 0); + if (error != NULL) + goto done; + + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) + goto done; + + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + goto done; + pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); + if (pid == NULL) { + error = got_error(GOT_ERR_ROOT_COMMIT); + goto done; + } + + 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) + printf("Backed out commit %s\n", commit_id_str); + print_update_progress_stats(&upa); +done: + if (commit) + got_object_commit_close(commit); + free(commit_id_str); + if (head_ref) + got_ref_close(head_ref); + if (worktree) + got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__dead static void +usage_rebase(void) +{ + fprintf(stderr, "usage: %s rebase [-a] | [-c] | branch\n", + getprogname()); + exit(1); +} + +void +trim_logmsg(char *logmsg, int limit) +{ + char *nl; + size_t len; + + len = strlen(logmsg); + if (len > limit) + len = limit; + logmsg[len] = '\0'; + nl = strchr(logmsg, '\n'); + if (nl) + *nl = '\0'; +} + +static const struct got_error * +get_short_logmsg(char **logmsg, int limit, struct got_commit_object *commit) +{ + const 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 *new_base_branch, + struct got_reference *tmp_branch, struct got_repository *repo) +{ + printf("Switching work tree to %s\n", got_ref_get_name(branch)); + return got_worktree_rebase_complete(worktree, fileindex, + new_base_branch, tmp_branch, branch, repo); +} + +static const struct got_error * +rebase_commit(struct got_pathlist_head *merged_paths, + struct got_worktree *worktree, struct got_fileindex *fileindex, + struct got_reference *tmp_branch, + struct got_object_id *commit_id, struct got_repository *repo) +{ + const struct got_error *error; + struct got_commit_object *commit; + struct got_object_id *new_commit_id; + + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + return error; + + error = got_worktree_rebase_commit(&new_commit_id, merged_paths, + worktree, fileindex, tmp_branch, commit, commit_id, repo); + if (error) { + if (error->code != GOT_ERR_COMMIT_NO_CHANGES) + goto done; + error = show_rebase_progress(commit, commit_id, NULL); + } else { + error = show_rebase_progress(commit, commit_id, new_commit_id); + free(new_commit_id); + } +done: + got_object_commit_close(commit); + return error; +} + +struct check_path_prefix_arg { + const char *path_prefix; + size_t len; + int errcode; +}; + +static const struct got_error * +check_path_prefix_in_diff(void *arg, struct got_blob_object *blob1, + struct got_blob_object *blob2, struct got_object_id *id1, + struct got_object_id *id2, const char *path1, const char *path2, + 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, "", "", repo, + check_path_prefix_in_diff, &cpp_arg, 0); +done: + if (tree1) + got_object_tree_close(tree1); + if (tree2) + got_object_tree_close(tree2); + if (commit) + got_object_commit_close(commit); + if (parent_commit) + got_object_commit_close(parent_commit); + return err; +} + +static const struct got_error * +collect_commits(struct got_object_id_queue *commits, + struct got_object_id *initial_commit_id, + struct got_object_id *iter_start_id, struct got_object_id *iter_stop_id, + const char *path_prefix, int path_prefix_errcode, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_commit_graph *graph = NULL; + struct got_object_id *parent_id = NULL; + struct got_object_qid *qid; + struct got_object_id *commit_id = initial_commit_id; + + err = got_commit_graph_open(&graph, "/", 1); + if (err) + return err; + + err = got_commit_graph_iter_start(graph, iter_start_id, repo, + check_cancelled, NULL); + if (err) + goto done; + 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; + SIMPLEQ_INSERT_HEAD(commits, qid, entry); + commit_id = parent_id; + } + } +done: + got_commit_graph_close(graph); + return err; +} + +static const struct got_error * +cmd_rebase(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + struct got_fileindex *fileindex = NULL; + char *cwd = NULL; + struct got_reference *branch = NULL; + struct got_reference *new_base_branch = NULL, *tmp_branch = NULL; + struct got_object_id *commit_id = NULL, *parent_id = NULL; + struct got_object_id *resume_commit_id = NULL; + struct got_object_id *branch_head_commit_id = NULL, *yca_id = NULL; + struct got_commit_object *commit = NULL; + int ch, rebase_in_progress = 0, abort_rebase = 0, continue_rebase = 0; + int histedit_in_progress = 0; + unsigned char rebase_status = GOT_STATUS_NO_CHANGE; + struct got_object_id_queue commits; + struct got_pathlist_head merged_paths; + const struct got_object_id_queue *parent_ids; + struct got_object_qid *qid, *pid; + + SIMPLEQ_INIT(&commits); + TAILQ_INIT(&merged_paths); + + while ((ch = getopt(argc, argv, "ac")) != -1) { + switch (ch) { + case 'a': + abort_rebase = 1; + break; + case 'c': + continue_rebase = 1; + break; + default: + usage_rebase(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (abort_rebase && continue_rebase) + usage_rebase(); + else if (abort_rebase || continue_rebase) { + if (argc != 0) + usage_rebase(); + } else if (argc != 1) + usage_rebase(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "rebase", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = got_worktree_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_rebase_in_progress(&rebase_in_progress, worktree); + if (error) + goto done; + + if (abort_rebase) { + struct got_update_progress_arg upa; + 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)); + memset(&upa, 0, sizeof(upa)); + error = got_worktree_rebase_abort(worktree, fileindex, repo, + new_base_branch, update_progress, &upa); + if (error) + goto done; + printf("Rebase of %s aborted\n", got_ref_get_name(branch)); + print_update_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, + resume_commit_id, repo); + if (error) + goto done; + + yca_id = got_object_id_dup(resume_commit_id); + if (yca_id == NULL) { + error = got_error_from_errno("got_object_id_dup"); + goto done; + } + } else { + error = got_ref_open(&branch, repo, argv[0], 0); + if (error != NULL) + goto done; + } + + error = got_ref_resolve(&branch_head_commit_id, repo, branch); + if (error) + goto done; + + if (!continue_rebase) { + struct got_object_id *base_commit_id; + + base_commit_id = got_worktree_get_base_commit_id(worktree); + error = got_commit_graph_find_youngest_common_ancestor(&yca_id, + base_commit_id, branch_head_commit_id, repo, + check_cancelled, NULL); + if (error) + goto done; + if (yca_id == NULL) { + error = got_error_msg(GOT_ERR_ANCESTRY, + "specified branch shares no common ancestry " + "with work tree's branch"); + goto done; + } + + error = check_same_branch(base_commit_id, branch, yca_id, repo); + if (error) { + if (error->code != GOT_ERR_ANCESTRY) + goto done; + error = NULL; + } else { + error = got_error_msg(GOT_ERR_SAME_BRANCH, + "specified branch resolves to a commit which " + "is already contained in work tree's branch"); + goto done; + } + error = got_worktree_rebase_prepare(&new_base_branch, + &tmp_branch, &fileindex, worktree, branch, repo); + if (error) + goto done; + } + + commit_id = branch_head_commit_id; + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + goto done; + + parent_ids = got_object_commit_get_parent_ids(commit); + pid = SIMPLEQ_FIRST(parent_ids); + if (pid == NULL) { + if (!continue_rebase) { + struct got_update_progress_arg upa; + memset(&upa, 0, sizeof(upa)); + error = got_worktree_rebase_abort(worktree, fileindex, + repo, new_base_branch, update_progress, &upa); + if (error) + goto done; + printf("Rebase of %s aborted\n", + got_ref_get_name(branch)); + print_update_progress_stats(&upa); + + } + error = got_error(GOT_ERR_EMPTY_REBASE); + goto done; + } + error = collect_commits(&commits, commit_id, pid->id, + yca_id, got_worktree_get_path_prefix(worktree), + GOT_ERR_REBASE_PATH, repo); + got_object_commit_close(commit); + commit = NULL; + if (error) + goto done; + + if (SIMPLEQ_EMPTY(&commits)) { + if (continue_rebase) { + error = rebase_complete(worktree, fileindex, + branch, new_base_branch, tmp_branch, repo); + 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); + 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; + } + } + + pid = NULL; + SIMPLEQ_FOREACH(qid, &commits, entry) { + struct got_update_progress_arg upa; + + 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_update_progress_stats(&upa); + if (upa.conflicts > 0) + rebase_status = GOT_STATUS_CONFLICT; + + if (rebase_status == GOT_STATUS_CONFLICT) { + error = show_rebase_merge_conflict(qid->id, repo); + if (error) + goto done; + got_worktree_rebase_pathlist_free(&merged_paths); + break; + } + + error = rebase_commit(&merged_paths, worktree, fileindex, + tmp_branch, commit_id, repo); + got_worktree_rebase_pathlist_free(&merged_paths); + if (error) + goto done; + } + + if (rebase_status == GOT_STATUS_CONFLICT) { + error = got_worktree_rebase_postpone(worktree, fileindex); + if (error) + goto done; + error = got_error_msg(GOT_ERR_CONFLICTS, + "conflicts must be resolved before rebasing can continue"); + } else + error = rebase_complete(worktree, fileindex, branch, + new_base_branch, tmp_branch, repo); +done: + got_object_id_queue_free(&commits); + free(branch_head_commit_id); + free(resume_commit_id); + free(yca_id); + if (commit) + got_object_commit_close(commit); + if (branch) + got_ref_close(branch); + if (new_base_branch) + got_ref_close(new_base_branch); + if (tmp_branch) + got_ref_close(tmp_branch); + if (worktree) + got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__dead static void +usage_histedit(void) +{ + fprintf(stderr, "usage: %s histedit [-a] [-c] [-F histedit-script] [-m]\n", + getprogname()); + exit(1); +} + +#define GOT_HISTEDIT_PICK 'p' +#define GOT_HISTEDIT_EDIT 'e' +#define GOT_HISTEDIT_FOLD 'f' +#define GOT_HISTEDIT_DROP 'd' +#define GOT_HISTEDIT_MESG 'm' + +static struct got_histedit_cmd { + unsigned char code; + const char *name; + const char *desc; +} got_histedit_cmds[] = { + { GOT_HISTEDIT_PICK, "pick", "use commit" }, + { GOT_HISTEDIT_EDIT, "edit", "use commit but stop for amending" }, + { GOT_HISTEDIT_FOLD, "fold", "combine with 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, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_qid *qid; + + if (SIMPLEQ_EMPTY(commits)) + return got_error(GOT_ERR_EMPTY_HISTEDIT); + + SIMPLEQ_FOREACH(qid, commits, entry) { + err = histedit_write_commit(qid->id, got_histedit_cmds[0].name, + f, repo); + if (err) + break; + 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; + int n, i; + char *id_str; + struct got_object_qid *qid; + + qid = SIMPLEQ_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++) { + 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 == -1 || ret >= sizeof(msg)) + return got_error(GOT_ERR_HISTEDIT_SYNTAX); + + return got_error_msg(GOT_ERR_HISTEDIT_SYNTAX, msg); +} + +static const struct got_error * +append_folded_commit_msg(char **new_msg, struct got_histedit_list_entry *hle, + char *logmsg, struct got_repository *repo) +{ + const struct got_error *err; + struct got_commit_object *folded_commit = NULL; + char *id_str, *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; + 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; + + write(fd, logmsg, logmsg_len); + close(fd); + + err = get_editor(&editor); + if (err) + goto done; + + err = edit_logmsg(&hle->logmsg, editor, logmsg_path, logmsg); + if (err) { + if (err->code != GOT_ERR_COMMIT_MSG_EMPTY) + goto done; + err = got_object_commit_get_logmsg(&hle->logmsg, commit); + } +done: + 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 size; + ssize_t len; + int lineno = 0, i; + const struct got_histedit_cmd *cmd; + struct got_object_id *commit_id = NULL; + struct got_histedit_list_entry *hle = NULL; + + for (;;) { + len = getline(&line, &size, f); + if (len == -1) { + const struct got_error *getline_err; + if (feof(f)) + break; + getline_err = got_error_from_errno("getline"); + err = got_ferror(f, getline_err->code); + break; + } + lineno++; + p = line; + while (isspace((unsigned char)p[0])) + p++; + if (p[0] == '#' || p[0] == '\0') { + free(line); + line = NULL; + continue; + } + cmd = NULL; + for (i = 0; i < nitems(got_histedit_cmds); i++) { + cmd = &got_histedit_cmds[i]; + if (strncmp(cmd->name, p, strlen(cmd->name)) == 0 && + isspace((unsigned char)p[strlen(cmd->name)])) { + p += strlen(cmd->name); + break; + } + if (p[0] == cmd->code && isspace((unsigned char)p[1])) { + p++; + break; + } + } + if (i == nitems(got_histedit_cmds)) { + err = histedit_syntax_error(lineno); + break; + } + while (isspace((unsigned char)p[0])) + p++; + if (cmd->code == GOT_HISTEDIT_MESG) { + if (hle == NULL || hle->logmsg != NULL) { + err = got_error(GOT_ERR_HISTEDIT_CMD); + break; + } + if (p[0] == '\0') { + err = histedit_edit_logmsg(hle, repo); + if (err) + break; + } else { + hle->logmsg = strdup(p); + if (hle->logmsg == NULL) { + err = got_error_from_errno("strdup"); + break; + } + } + free(line); + line = NULL; + continue; + } else { + end = p; + while (end[0] && !isspace((unsigned char)end[0])) + end++; + *end = '\0'; + + err = got_object_resolve_id_str(&commit_id, repo, p); + if (err) { + /* override error code */ + err = histedit_syntax_error(lineno); + break; + } + } + hle = malloc(sizeof(*hle)); + if (hle == NULL) { + err = got_error_from_errno("malloc"); + break; + } + hle->cmd = cmd; + hle->commit_id = commit_id; + hle->logmsg = NULL; + commit_id = NULL; + free(line); + line = NULL; + TAILQ_INSERT_TAIL(histedit_cmds, hle, entry); + } + + free(line); + free(commit_id); + return err; +} + +static const struct got_error * +histedit_check_script(struct got_histedit_list *histedit_cmds, + struct got_object_id_queue *commits, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_qid *qid; + struct got_histedit_list_entry *hle; + static char msg[92]; + char *id_str; + + if (TAILQ_EMPTY(histedit_cmds)) + return got_error_msg(GOT_ERR_EMPTY_HISTEDIT, + "histedit script contains no commands"); + if (SIMPLEQ_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); + } + } + + SIMPLEQ_FOREACH(qid, commits, entry) { + TAILQ_FOREACH(hle, histedit_cmds, entry) { + if (got_object_id_cmp(qid->id, hle->commit_id) == 0) + break; + } + if (hle == NULL) { + err = got_object_id_str(&id_str, qid->id); + if (err) + return err; + snprintf(msg, sizeof(msg), + "commit %s missing from histedit script", id_str); + free(id_str); + return got_error_msg(GOT_ERR_HISTEDIT_CMD, msg); + } + } + + 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, "r"); + if (f == NULL) { + err = got_error_from_errno("fopen"); + goto done; + } + err = histedit_parse_list(histedit_cmds, f, repo); + if (err) + goto done; + + err = histedit_check_script(histedit_cmds, commits, repo); +done: + if (f && fclose(f) != 0 && err == NULL) + err = got_error_from_errno("fclose"); + free(editor); + return err; +} + +static const struct got_error * +histedit_edit_list_retry(struct got_histedit_list *, const struct got_error *, + struct got_object_id_queue *, const char *, 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, 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, repo); + if (err) + goto done; + + if (edit_logmsg_only) { + rewind(f); + err = histedit_parse_list(histedit_cmds, f, repo); + } else { + if (fclose(f) != 0) { + err = got_error_from_errno("fclose"); + goto done; + } + f = NULL; + err = histedit_run_editor(histedit_cmds, path, commits, repo); + if (err) { + if (err->code != GOT_ERR_HISTEDIT_SYNTAX && + err->code != GOT_ERR_HISTEDIT_CMD) + goto done; + err = histedit_edit_list_retry(histedit_cmds, err, + commits, path, branch_name, repo); + } + } +done: + if (f && fclose(f) != 0 && err == NULL) + err = got_error_from_errno("fclose"); + if (path && unlink(path) != 0 && err == NULL) + err = got_error_from_errno2("unlink", path); + free(path); + return err; +} + +static const struct got_error * +histedit_save_list(struct got_histedit_list *histedit_cmds, + struct got_worktree *worktree, struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *path = NULL; + FILE *f = NULL; + struct got_histedit_list_entry *hle; + struct got_commit_object *commit = NULL; + + err = got_worktree_get_histedit_script_path(&path, worktree); + if (err) + return err; + + f = fopen(path, "w"); + if (f == NULL) { + err = got_error_from_errno2("fopen", path); + goto done; + } + TAILQ_FOREACH(hle, histedit_cmds, entry) { + err = histedit_write_commit(hle->commit_id, hle->cmd->name, f, + repo); + if (err) + break; + + if (hle->logmsg) { + int n = fprintf(f, "%c %s\n", + GOT_HISTEDIT_MESG, hle->logmsg); + if (n < 0) { + err = got_ferror(f, GOT_ERR_IO); + break; + } + } + } +done: + if (f && fclose(f) != 0 && err == NULL) + err = got_error_from_errno("fclose"); + free(path); + if (commit) + got_object_commit_close(commit); + return err; +} + +void +histedit_free_list(struct got_histedit_list *histedit_cmds) +{ + struct got_histedit_list_entry *hle; + + while ((hle = TAILQ_FIRST(histedit_cmds))) { + TAILQ_REMOVE(histedit_cmds, hle, entry); + free(hle); + } +} + +static const struct got_error * +histedit_load_list(struct got_histedit_list *histedit_cmds, + const char *path, struct got_repository *repo) +{ + const struct got_error *err = NULL; + FILE *f = NULL; + + f = fopen(path, "r"); + if (f == NULL) { + err = got_error_from_errno2("fopen", path); + goto done; + } + + err = histedit_parse_list(histedit_cmds, f, repo); +done: + if (f && fclose(f) != 0 && err == NULL) + err = got_error_from_errno("fclose"); + return err; +} + +static const struct got_error * +histedit_edit_list_retry(struct got_histedit_list *histedit_cmds, + const struct got_error *edit_err, struct got_object_id_queue *commits, + const char *path, 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, repo); + if (err) { + if (err->code != GOT_ERR_HISTEDIT_SYNTAX && + err->code != GOT_ERR_HISTEDIT_CMD) + break; + prev_err = err; + resp = ' '; + continue; + } + break; + } else if (resp == 'a') { + err = got_error(GOT_ERR_HISTEDIT_CANCEL); + break; + } else + printf("invalid response '%c'\n", resp); + } + + return err; +} + +static const struct got_error * +histedit_complete(struct got_worktree *worktree, + struct got_fileindex *fileindex, struct got_reference *tmp_branch, + struct got_reference *branch, struct got_repository *repo) +{ + printf("Switching work tree to %s\n", + got_ref_get_symref_target(branch)); + return got_worktree_histedit_complete(worktree, fileindex, tmp_branch, + branch, repo); +} + +static const struct got_error * +show_histedit_progress(struct got_commit_object *commit, + struct got_histedit_list_entry *hle, struct got_object_id *new_id) +{ + const struct got_error *err; + char *old_id_str = NULL, *new_id_str = NULL, *logmsg = NULL; + + err = got_object_id_str(&old_id_str, hle->commit_id); + if (err) + goto done; + + if (new_id) { + err = got_object_id_str(&new_id_str, new_id); + if (err) + goto done; + } + + old_id_str[12] = '\0'; + if (new_id_str) + new_id_str[12] = '\0'; + + if (hle->logmsg) { + logmsg = strdup(hle->logmsg); + if (logmsg == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + trim_logmsg(logmsg, 42); + } else { + err = get_short_logmsg(&logmsg, 42, commit); + if (err) + goto done; + } + + switch (hle->cmd->code) { + case GOT_HISTEDIT_PICK: + case GOT_HISTEDIT_EDIT: + printf("%s -> %s: %s\n", old_id_str, + new_id_str ? new_id_str : "no-op change", logmsg); + break; + case GOT_HISTEDIT_DROP: + case GOT_HISTEDIT_FOLD: + printf("%s -> %s commit: %s\n", old_id_str, hle->cmd->name, + logmsg); + break; + default: + break; + } +done: + free(old_id_str); + free(new_id_str); + return err; +} + +static const struct got_error * +histedit_commit(struct got_pathlist_head *merged_paths, + struct got_worktree *worktree, struct got_fileindex *fileindex, + struct got_reference *tmp_branch, struct got_histedit_list_entry *hle, + struct got_repository *repo) +{ + const struct got_error *err; + struct got_commit_object *commit; + struct got_object_id *new_commit_id; + + if ((hle->cmd->code == GOT_HISTEDIT_EDIT || get_folded_commits(hle)) + && hle->logmsg == NULL) { + err = histedit_edit_logmsg(hle, repo); + if (err) + return err; + } + + err = got_object_open_as_commit(&commit, repo, hle->commit_id); + if (err) + return err; + + err = got_worktree_histedit_commit(&new_commit_id, merged_paths, + worktree, fileindex, tmp_branch, commit, hle->commit_id, + hle->logmsg, repo); + if (err) { + if (err->code != GOT_ERR_COMMIT_NO_CHANGES) + goto done; + err = show_histedit_progress(commit, hle, NULL); + } else { + err = show_histedit_progress(commit, hle, new_commit_id); + free(new_commit_id); + } +done: + got_object_commit_close(commit); + return err; +} + +static const struct got_error * +histedit_skip_commit(struct got_histedit_list_entry *hle, + struct got_worktree *worktree, struct got_repository *repo) +{ + const struct got_error *error; + struct got_commit_object *commit; + + error = got_worktree_histedit_skip_commit(worktree, hle->commit_id, + repo); + if (error) + return error; + + error = got_object_open_as_commit(&commit, repo, hle->commit_id); + if (error) + return error; + + error = show_histedit_progress(commit, hle, NULL); + got_object_commit_close(commit); + return error; +} + +static const struct got_error * +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; + 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; + struct got_update_progress_arg upa; + int edit_in_progress = 0, abort_edit = 0, continue_edit = 0; + int edit_logmsg_only = 0; + const char *edit_script_path = NULL; + unsigned char rebase_status = GOT_STATUS_NO_CHANGE; + struct got_object_id_queue commits; + struct got_pathlist_head merged_paths; + const struct got_object_id_queue *parent_ids; + struct got_object_qid *pid; + struct got_histedit_list histedit_cmds; + struct got_histedit_list_entry *hle; + + SIMPLEQ_INIT(&commits); + TAILQ_INIT(&histedit_cmds); + TAILQ_INIT(&merged_paths); + memset(&upa, 0, sizeof(upa)); + + while ((ch = getopt(argc, argv, "acF:m")) != -1) { + switch (ch) { + case 'a': + abort_edit = 1; + break; + case 'c': + continue_edit = 1; + break; + case 'F': + edit_script_path = optarg; + break; + case 'm': + edit_logmsg_only = 1; + break; + default: + usage_histedit(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (abort_edit && continue_edit) + errx(1, "histedit's -a and -c options are mutually exclusive"); + if (edit_script_path && edit_logmsg_only) + errx(1, "histedit's -F and -m options are mutually exclusive"); + if (abort_edit && edit_logmsg_only) + errx(1, "histedit's -a and -m options are mutually exclusive"); + if (continue_edit && edit_logmsg_only) + errx(1, "histedit's -c and -m options are mutually exclusive"); + if (argc != 0) + usage_histedit(); + + /* + * This command cannot apply unveil(2) in all cases because the + * user may choose to run an editor to edit the histedit script + * and to edit individual commit log messages. + * unveil(2) traverses exec(2); if an editor is used we have to + * apply unveil after edit script and log messages have been written. + * XXX TODO: Make use of unveil(2) where possible. + */ + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "histedit", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); + if (error) + goto done; + if (rebase_in_progress) { + error = got_error(GOT_ERR_REBASING); + goto done; + } + + error = got_worktree_histedit_in_progress(&edit_in_progress, worktree); + if (error) + goto done; + + if (edit_in_progress && 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 && abort_edit) { + error = got_worktree_histedit_continue(&resume_commit_id, + &tmp_branch, &branch, &base_commit_id, &fileindex, + worktree, repo); + if (error) + goto done; + printf("Switching work tree to %s\n", + got_ref_get_symref_target(branch)); + error = got_worktree_histedit_abort(worktree, fileindex, repo, + branch, base_commit_id, update_progress, &upa); + if (error) + goto done; + printf("Histedit of %s aborted\n", + got_ref_get_symref_target(branch)); + print_update_progress_stats(&upa); + goto done; /* nothing else to do */ + } else if (abort_edit) { + error = got_error(GOT_ERR_NOT_HISTEDIT); + goto done; + } + + if (continue_edit) { + char *path; + + if (!edit_in_progress) { + error = got_error(GOT_ERR_NOT_HISTEDIT); + goto done; + } + + error = got_worktree_get_histedit_script_path(&path, worktree); + if (error) + goto done; + + error = histedit_load_list(&histedit_cmds, path, repo); + free(path); + if (error) + goto done; + + error = got_worktree_histedit_continue(&resume_commit_id, + &tmp_branch, &branch, &base_commit_id, &fileindex, + worktree, repo); + if (error) + goto done; + + error = got_ref_resolve(&head_commit_id, repo, branch); + if (error) + goto done; + + error = got_object_open_as_commit(&commit, repo, + head_commit_id); + if (error) + goto done; + parent_ids = got_object_commit_get_parent_ids(commit); + pid = SIMPLEQ_FIRST(parent_ids); + if (pid == NULL) { + error = got_error(GOT_ERR_EMPTY_HISTEDIT); + goto done; + } + error = collect_commits(&commits, head_commit_id, pid->id, + base_commit_id, got_worktree_get_path_prefix(worktree), + GOT_ERR_HISTEDIT_PATH, repo); + got_object_commit_close(commit); + commit = NULL; + if (error) + goto done; + } else { + if (edit_in_progress) { + error = got_error(GOT_ERR_HISTEDIT_BUSY); + goto done; + } + + error = got_ref_open(&branch, repo, + got_worktree_get_head_ref_name(worktree), 0); + if (error != NULL) + goto done; + + 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 = SIMPLEQ_FIRST(parent_ids); + if (pid == NULL) { + error = got_error(GOT_ERR_EMPTY_HISTEDIT); + goto done; + } + error = collect_commits(&commits, head_commit_id, pid->id, + got_worktree_get_base_commit_id(worktree), + got_worktree_get_path_prefix(worktree), + GOT_ERR_HISTEDIT_PATH, repo); + got_object_commit_close(commit); + commit = NULL; + if (error) + goto done; + + if (SIMPLEQ_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, + update_progress, &upa); + print_update_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, repo); + if (error) { + got_worktree_histedit_abort(worktree, fileindex, + repo, branch, base_commit_id, + update_progress, &upa); + print_update_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, + update_progress, &upa); + print_update_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, check_local_changes, &have_changes, + check_cancelled, NULL); + got_pathlist_free(&paths); + 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, 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 = SIMPLEQ_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_update_progress_stats(&upa); + if (upa.conflicts > 0) + rebase_status = GOT_STATUS_CONFLICT; + + if (rebase_status == GOT_STATUS_CONFLICT) { + error = show_rebase_merge_conflict(hle->commit_id, + repo); + if (error) + goto done; + got_worktree_rebase_pathlist_free(&merged_paths); + break; + } + + if (hle->cmd->code == GOT_HISTEDIT_EDIT) { + char *id_str; + error = got_object_id_str(&id_str, hle->commit_id); + if (error) + goto done; + printf("Stopping histedit for amending commit %s\n", + id_str); + free(id_str); + got_worktree_rebase_pathlist_free(&merged_paths); + error = got_worktree_histedit_postpone(worktree, + fileindex); + goto done; + } + + if (hle->cmd->code == GOT_HISTEDIT_FOLD) { + error = histedit_skip_commit(hle, worktree, repo); + if (error) + goto done; + continue; + } + + error = histedit_commit(&merged_paths, worktree, fileindex, + tmp_branch, hle, repo); + got_worktree_rebase_pathlist_free(&merged_paths); + if (error) + goto done; + } + + if (rebase_status == GOT_STATUS_CONFLICT) { + error = got_worktree_histedit_postpone(worktree, fileindex); + if (error) + goto done; + error = got_error_msg(GOT_ERR_CONFLICTS, + "conflicts must be resolved before histedit can continue"); + } else + error = histedit_complete(worktree, fileindex, tmp_branch, + branch, repo); +done: + got_object_id_queue_free(&commits); + histedit_free_list(&histedit_cmds); + free(head_commit_id); + free(base_commit_id); + free(resume_commit_id); + if (commit) + got_object_commit_close(commit); + if (branch) + got_ref_close(branch); + if (tmp_branch) + got_ref_close(tmp_branch); + if (worktree) + got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__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; + + 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]; + + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + 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); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + 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; + } + + 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) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(base_commit_id); + free(commit_id); + free(refname); + free(base_refname); + return error; +} + +__dead static void +usage_stage(void) +{ + fprintf(stderr, "usage: %s stage [-l] | [-p] [-F response-script] " + "[-S] [file-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; + struct got_pathlist_entry *pe; + 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; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "lpF:S")) != -1) { + switch (ch) { + case 'l': + list_stage = 1; + break; + case 'p': + pflag = 1; + break; + case 'F': + patch_script_path = optarg; + break; + case 'S': + allow_bad_symlinks = 1; + break; + default: + usage_stage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (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_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); + if (error != NULL) + goto done; + + if (patch_script_path) { + patch_script_file = fopen(patch_script_path, "r"); + 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; + + if (list_stage) + error = got_worktree_status(worktree, &paths, repo, + 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) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_unstage(void) +{ + fprintf(stderr, "usage: %s unstage [-p] [-F response-script] " + "[file-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; + struct got_pathlist_entry *pe; + 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; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "pF:")) != -1) { + switch (ch) { + case 'p': + pflag = 1; + break; + case 'F': + patch_script_path = optarg; + break; + default: + usage_unstage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (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_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); + if (error != NULL) + goto done; + + if (patch_script_path) { + patch_script_file = fopen(patch_script_path, "r"); + 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_update_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) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_cat(void) +{ + fprintf(stderr, "usage: %s cat [-r repository ] [ -c commit ] [ -P ] " + "arg1 [arg2 ...]\n", getprogname()); + exit(1); +} + +static const struct got_error * +cat_blob(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_blob_object *blob; + + err = got_object_open_as_blob(&blob, repo, id, 8192); + if (err) + return err; + + err = got_object_blob_dump_to_file(NULL, NULL, NULL, outfile, blob); + got_object_blob_close(blob); + return err; +} + +static const struct got_error * +cat_tree(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_tree_object *tree; + int nentries, i; + + err = got_object_open_as_tree(&tree, repo, id); + if (err) + return err; + + nentries = got_object_tree_get_nentries(tree); + for (i = 0; i < nentries; i++) { + struct got_tree_entry *te; + char *id_str; + if (sigint_received || sigpipe_received) + break; + te = got_object_tree_get_entry(tree, i); + err = got_object_id_str(&id_str, got_tree_entry_get_id(te)); + if (err) + break; + fprintf(outfile, "%s %.7o %s\n", id_str, + got_tree_entry_get_mode(te), + got_tree_entry_get_name(te)); + free(id_str); + } + + got_object_tree_close(tree); + return err; +} + +static const struct got_error * +cat_commit(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_commit_object *commit; + const struct got_object_id_queue *parent_ids; + struct got_object_qid *pid; + char *id_str = NULL; + const char *logmsg = NULL; + + err = got_object_open_as_commit(&commit, repo, id); + if (err) + return err; + + err = got_object_id_str(&id_str, got_object_commit_get_tree_id(commit)); + if (err) + goto done; + + fprintf(outfile, "%s%s\n", GOT_COMMIT_LABEL_TREE, id_str); + parent_ids = got_object_commit_get_parent_ids(commit); + fprintf(outfile, "numparents %d\n", + got_object_commit_get_nparents(commit)); + SIMPLEQ_FOREACH(pid, parent_ids, entry) { + char *pid_str; + err = got_object_id_str(&pid_str, pid->id); + if (err) + goto done; + fprintf(outfile, "%s%s\n", GOT_COMMIT_LABEL_PARENT, pid_str); + free(pid_str); + } + fprintf(outfile, "%s%s %lld +0000\n", GOT_COMMIT_LABEL_AUTHOR, + got_object_commit_get_author(commit), + got_object_commit_get_author_time(commit)); + + fprintf(outfile, "%s%s %lld +0000\n", GOT_COMMIT_LABEL_COMMITTER, + got_object_commit_get_author(commit), + got_object_commit_get_committer_time(commit)); + + logmsg = got_object_commit_get_logmsg_raw(commit); + fprintf(outfile, "messagelen %zd\n", strlen(logmsg)); + fprintf(outfile, "%s", logmsg); +done: + free(id_str); + got_object_commit_close(commit); + return err; +} + +static const struct got_error * +cat_tag(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_tag_object *tag; + char *id_str = NULL; + const char *tagmsg = NULL; + + err = got_object_open_as_tag(&tag, repo, id); + if (err) + return err; + + err = got_object_id_str(&id_str, got_object_tag_get_object_id(tag)); + if (err) + goto done; + + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_OBJECT, id_str); + + switch (got_object_tag_get_object_type(tag)) { + case GOT_OBJ_TYPE_BLOB: + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TYPE, + GOT_OBJ_LABEL_BLOB); + break; + case GOT_OBJ_TYPE_TREE: + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TYPE, + GOT_OBJ_LABEL_TREE); + break; + case GOT_OBJ_TYPE_COMMIT: + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TYPE, + GOT_OBJ_LABEL_COMMIT); + break; + case GOT_OBJ_TYPE_TAG: + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TYPE, + GOT_OBJ_LABEL_TAG); + break; + default: + break; + } + + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TAG, + got_object_tag_get_name(tag)); + + fprintf(outfile, "%s%s %lld +0000\n", GOT_TAG_LABEL_TAGGER, + got_object_tag_get_tagger(tag), + got_object_tag_get_tagger_time(tag)); + + tagmsg = got_object_tag_get_message(tag); + fprintf(outfile, "messagelen %zd\n", strlen(tagmsg)); + fprintf(outfile, "%s", tagmsg); +done: + free(id_str); + got_object_tag_close(tag); + return err; +} + +static const struct got_error * +cmd_cat(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *repo_path = NULL, *label = NULL; + const char *commit_id_str = NULL; + struct got_object_id *id = NULL, *commit_id = NULL; + int ch, obj_type, i, force_path = 0; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "c:r:P")) != -1) { + switch (ch) { + case 'c': + commit_id_str = optarg; + 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 'P': + force_path = 1; + break; + default: + usage_cat(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + if (worktree) { + if (repo_path == NULL) { + repo_path = strdup( + got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + if (repo_path == NULL) { + repo_path = getcwd(NULL, 0); + if (repo_path == NULL) + return got_error_from_errno("getcwd"); + } + + error = got_repo_open(&repo, repo_path, NULL); + free(repo_path); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + + if (commit_id_str == NULL) + commit_id_str = GOT_REF_HEAD; + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + if (error) + goto done; + + for (i = 0; i < argc; i++) { + if (force_path) { + error = got_object_id_by_path(&id, repo, commit_id, + argv[i]); + if (error) + break; + } else { + error = got_repo_match_object_id(&id, &label, argv[i], + GOT_OBJ_TYPE_ANY, 0, repo); + if (error) { + if (error->code != GOT_ERR_BAD_OBJ_ID_STR && + error->code != GOT_ERR_NOT_REF) + break; + error = got_object_id_by_path(&id, repo, + commit_id, argv[i]); + if (error) + break; + } + } + + error = got_object_get_type(&obj_type, repo, id); + if (error) + break; + + switch (obj_type) { + case GOT_OBJ_TYPE_BLOB: + error = cat_blob(id, repo, stdout); + break; + case GOT_OBJ_TYPE_TREE: + error = cat_tree(id, repo, stdout); + break; + case GOT_OBJ_TYPE_COMMIT: + error = cat_commit(id, repo, stdout); + break; + case GOT_OBJ_TYPE_TAG: + error = cat_tag(id, repo, stdout); + break; + default: + error = got_error(GOT_ERR_OBJ_TYPE); + break; + } + if (error) + break; + free(label); + label = NULL; + free(id); + id = NULL; + } +done: + free(label); + free(id); + free(commit_id); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + return error; +} + +__dead static void +usage_info(void) +{ + fprintf(stderr, "usage: %s info [path ...]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +print_path_info(void *arg, const char *path, mode_t mode, time_t mtime, + struct got_object_id *blob_id, struct got_object_id *staged_blob_id, + struct got_object_id *commit_id) +{ + const struct got_error *err = NULL; + char *id_str = NULL; + char datebuf[128]; + struct tm mytm, *tm; + struct got_pathlist_head *paths = arg; + struct got_pathlist_entry *pe; + + /* + * Clear error indication from any of the path arguments which + * would cause this file index entry to be displayed. + */ + TAILQ_FOREACH(pe, paths, entry) { + if (got_path_cmp(path, pe->path, strlen(path), + pe->path_len) == 0 || + got_path_is_child(path, pe->path, pe->path_len)) + pe->data = NULL; /* no error */ + } + + printf(GOT_COMMIT_SEP_STR); + if (S_ISLNK(mode)) + printf("symlink: %s\n", path); + else if (S_ISREG(mode)) { + printf("file: %s\n", path); + printf("mode: %o\n", mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + } else if (S_ISDIR(mode)) + printf("directory: %s\n", path); + else + printf("something: %s\n", path); + + tm = localtime_r(&mtime, &mytm); + if (tm == NULL) + return NULL; + if (strftime(datebuf, sizeof(datebuf), "%c %Z", tm) >= sizeof(datebuf)) + return got_error(GOT_ERR_NO_SPACE); + printf("timestamp: %s\n", datebuf); + + if (blob_id) { + err = got_object_id_str(&id_str, blob_id); + if (err) + return err; + printf("based on blob: %s\n", id_str); + free(id_str); + } + + if (staged_blob_id) { + err = got_object_id_str(&id_str, staged_blob_id); + if (err) + return err; + printf("based on staged blob: %s\n", id_str); + free(id_str); + } + + if (commit_id) { + err = got_object_id_str(&id_str, commit_id); + if (err) + return err; + printf("based on commit: %s\n", id_str); + free(id_str); + } + + return NULL; +} + +static const struct got_error * +cmd_info(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *id_str = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + char *uuidstr = NULL; + int ch, show_files = 0; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_info(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "status", cwd); + goto done; + } + + error = apply_unveil(NULL, 0, got_worktree_get_root_path(worktree)); + if (error) + goto done; + + if (argc >= 1) { + error = get_worktree_paths_from_argv(&paths, argc, argv, + worktree); + if (error) + goto done; + show_files = 1; + } + + error = got_object_id_str(&id_str, + got_worktree_get_base_commit_id(worktree)); + if (error) + goto done; + + error = got_worktree_get_uuid(&uuidstr, worktree); + if (error) + goto done; + + printf("work tree: %s\n", got_worktree_get_root_path(worktree)); + printf("work tree base commit: %s\n", id_str); + printf("work tree path prefix: %s\n", + got_worktree_get_path_prefix(worktree)); + printf("work tree branch reference: %s\n", + got_worktree_get_head_ref_name(worktree)); + printf("work tree UUID: %s\n", uuidstr); + printf("repository: %s\n", got_worktree_get_repo_path(worktree)); + + if (show_files) { + struct got_pathlist_entry *pe; + TAILQ_FOREACH(pe, &paths, entry) { + if (pe->path_len == 0) + continue; + /* + * Assume this path will fail. This will be corrected + * in print_path_info() in case the path does suceeed. + */ + pe->data = (void *)got_error_path(pe->path, + GOT_ERR_BAD_PATH); + } + error = got_worktree_path_info(worktree, &paths, + print_path_info, &paths, check_cancelled, NULL); + if (error) + goto done; + TAILQ_FOREACH(pe, &paths, entry) { + if (pe->data != NULL) { + error = pe->data; /* bad path */ + break; + } + } + } +done: + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + free(id_str); + free(uuidstr); + return error; +} blob - /dev/null blob + 6d084dbeab1fa1eadc21ffbd54e530f79199acb5 (mode 644) --- /dev/null +++ test/test016.right.txt @@ -0,0 +1,9689 @@ +/* + * Copyright (c) 2017 Martin Pieuchot + * Copyright (c) 2018, 2019, 2020 Stefan Sperling + * Copyright (c) 2020 Ori Bernstein + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_version.h" +#include "got_error.h" +#include "got_object.h" +#include "got_reference.h" +#include "got_repository.h" +#include "got_path.h" +#include "got_cancel.h" +#include "got_worktree.h" +#include "got_diff.h" +#include "got_commit_graph.h" +#include "got_fetch.h" +#include "got_blame.h" +#include "got_privsep.h" +#include "got_opentemp.h" +#include "got_gotconfig.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +static volatile sig_atomic_t sigint_received; +static volatile sig_atomic_t sigpipe_received; + +static void +catch_sigint(int signo) +{ + sigint_received = 1; +} + +static void +catch_sigpipe(int signo) +{ + sigpipe_received = 1; +} + + +struct got_cmd { + const char *cmd_name; + const struct got_error *(*cmd_main)(int, char *[]); + void (*cmd_usage)(void); + const char *cmd_alias; +}; + +__dead static void usage(int); +__dead static void usage_init(void); +__dead static void usage_import(void); +__dead static void usage_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); +__dead static void usage_diff(void); +__dead static void usage_blame(void); +__dead static void usage_tree(void); +__dead static void usage_status(void); +__dead static void usage_ref(void); +__dead static void usage_branch(void); +__dead static void usage_tag(void); +__dead static void usage_add(void); +__dead static void usage_remove(void); +__dead static void usage_revert(void); +__dead static void usage_commit(void); +__dead static void usage_cherrypick(void); +__dead static void usage_backout(void); +__dead static void usage_rebase(void); +__dead static void usage_histedit(void); +__dead static void usage_integrate(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_init(int, char *[]); +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 *[]); +static const struct got_error* cmd_diff(int, char *[]); +static const struct got_error* cmd_blame(int, char *[]); +static const struct got_error* cmd_tree(int, char *[]); +static const struct got_error* cmd_status(int, char *[]); +static const struct got_error* cmd_ref(int, char *[]); +static const struct got_error* cmd_branch(int, char *[]); +static const struct got_error* cmd_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_revert(int, char *[]); +static const struct got_error* cmd_commit(int, char *[]); +static const struct got_error* cmd_cherrypick(int, char *[]); +static const struct got_error* cmd_backout(int, char *[]); +static const struct got_error* cmd_rebase(int, char *[]); +static const struct got_error* cmd_histedit(int, char *[]); +static const struct got_error* cmd_integrate(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 struct got_cmd got_commands[] = { + { "init", cmd_init, usage_init, "" }, + { "import", cmd_import, usage_import, "im" }, + { "clone", cmd_clone, usage_clone, "cl" }, + { "fetch", cmd_fetch, usage_fetch, "fe" }, + { "checkout", cmd_checkout, usage_checkout, "co" }, + { "update", cmd_update, usage_update, "up" }, + { "log", cmd_log, usage_log, "" }, + { "diff", cmd_diff, usage_diff, "di" }, + { "blame", cmd_blame, usage_blame, "bl" }, + { "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" }, + { "revert", cmd_revert, usage_revert, "rv" }, + { "commit", cmd_commit, usage_commit, "ci" }, + { "cherrypick", cmd_cherrypick, usage_cherrypick, "cy" }, + { "backout", cmd_backout, usage_backout, "bo" }, + { "rebase", cmd_rebase, usage_rebase, "rb" }, + { "histedit", cmd_histedit, usage_histedit, "he" }, + { "integrate", cmd_integrate, usage_integrate,"ig" }, + { "stage", cmd_stage, usage_stage, "sg" }, + { "unstage", cmd_unstage, usage_unstage, "ug" }, + { "cat", cmd_cat, usage_cat, "" }, + { "info", cmd_info, usage_info, "" }, +}; + +static void +list_commands(void) +{ + int i; + + fprintf(stderr, "commands:"); + for (i = 0; i < nitems(got_commands); i++) { + struct got_cmd *cmd = &got_commands[i]; + fprintf(stderr, " %s", cmd->cmd_name); + } + fputc('\n', stderr); +} + +int +main(int argc, char *argv[]) +{ + struct got_cmd *cmd; + unsigned int i; + int ch; + int hflag = 0, Vflag = 0; + static struct option longopts[] = { + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0} + }; + + setlocale(LC_CTYPE, ""); + + while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) { + switch (ch) { + case 'h': + hflag = 1; + break; + case 'V': + Vflag = 1; + break; + default: + usage(hflag); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (Vflag) { + got_version_print_str(); + return 1; + } + + if (argc <= 0) + usage(hflag); + + signal(SIGINT, catch_sigint); + signal(SIGPIPE, catch_sigpipe); + + for (i = 0; i < nitems(got_commands); i++) { + const struct got_error *error; + + cmd = &got_commands[i]; + + if (strcmp(cmd->cmd_name, argv[0]) != 0 && + strcmp(cmd->cmd_alias, argv[0]) != 0) + continue; + + if (hflag) + got_commands[i].cmd_usage(); + + error = got_commands[i].cmd_main(argc, argv); + if (error && error->code != GOT_ERR_CANCELLED && + error->code != GOT_ERR_PRIVSEP_EXIT && + !(sigpipe_received && + error->code == GOT_ERR_ERRNO && errno == EPIPE) && + !(sigint_received && + error->code == GOT_ERR_ERRNO && errno == EINTR)) { + fprintf(stderr, "%s: %s\n", getprogname(), error->msg); + return 1; + } + + return 0; + } + + fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]); + list_commands(); + return 1; +} + +__dead static void +usage(int hflag) +{ + fprintf(stderr, "usage: %s [-h] [-V | --version] command [arg ...]\n", + getprogname()); + if (hflag) + list_commands(); + exit(1); +} + +static const struct got_error * +get_editor(char **abspath) +{ + const struct got_error *err = NULL; + const char *editor; + + *abspath = NULL; + + editor = getenv("VISUAL"); + if (editor == NULL) + editor = getenv("EDITOR"); + + if (editor) { + err = got_path_find_prog(abspath, editor); + if (err) + return err; + } + + if (*abspath == NULL) { + *abspath = strdup("/bin/ed"); + if (*abspath == NULL) + return got_error_from_errno("strdup"); + } + + return NULL; +} + +static const struct got_error * +apply_unveil(const char *repo_path, int repo_read_only, + const char *worktree_path) +{ + const struct got_error *err; + +#ifdef PROFILE + if (unveil("gmon.out", "rwc") != 0) + return got_error_from_errno2("unveil", "gmon.out"); +#endif + if (repo_path && unveil(repo_path, repo_read_only ? "r" : "rwc") != 0) + return got_error_from_errno2("unveil", repo_path); + + if (worktree_path && unveil(worktree_path, "rwc") != 0) + return got_error_from_errno2("unveil", worktree_path); + + if (unveil(GOT_TMPDIR_STR, "rwc") != 0) + return got_error_from_errno2("unveil", GOT_TMPDIR_STR); + + err = got_privsep_unveil_exec_helpers(); + if (err != NULL) + return err; + + if (unveil(NULL, NULL) != 0) + return got_error_from_errno("unveil"); + + return NULL; +} + +__dead static void +usage_init(void) +{ + fprintf(stderr, "usage: %s init repository-path\n", getprogname()); + exit(1); +} + +static const struct got_error * +cmd_init(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + char *repo_path = NULL; + int ch; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_init(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_init(); + + repo_path = strdup(argv[0]); + if (repo_path == NULL) + return got_error_from_errno("strdup"); + + got_path_strip_trailing_slashes(repo_path); + + error = got_path_mkdir(repo_path); + if (error && + !(error->code == GOT_ERR_ERRNO && errno == EEXIST)) + goto done; + + error = apply_unveil(repo_path, 0, NULL); + if (error) + goto done; + + error = got_repo_init(repo_path); +done: + free(repo_path); + return error; +} + +__dead static void +usage_import(void) +{ + fprintf(stderr, "usage: %s import [-b branch] [-m message] " + "[-r repository-path] [-I pattern] path\n", getprogname()); + exit(1); +} + +int +spawn_editor(const char *editor, const char *file) +{ + pid_t pid; + sig_t sighup, sigint, sigquit; + int st = -1; + + sighup = signal(SIGHUP, SIG_IGN); + sigint = signal(SIGINT, SIG_IGN); + sigquit = signal(SIGQUIT, SIG_IGN); + + switch (pid = fork()) { + case -1: + goto doneediting; + case 0: + execl(editor, editor, file, (char *)NULL); + _exit(127); + } + + while (waitpid(pid, &st, 0) == -1) + if (errno != EINTR) + break; + +doneediting: + (void)signal(SIGHUP, sighup); + (void)signal(SIGINT, sigint); + (void)signal(SIGQUIT, sigquit); + + if (!WIFEXITED(st)) { + errno = EINTR; + return -1; + } + + return WEXITSTATUS(st); +} + +static const struct got_error * +edit_logmsg(char **logmsg, const char *editor, const char *logmsg_path, + const char *initial_content) +{ + const struct got_error *err = NULL; + char buf[1024]; + struct stat st, st2; + FILE *fp; + int content_changed = 0; + size_t len; + + *logmsg = NULL; + + if (stat(logmsg_path, &st) == -1) + return got_error_from_errno2("stat", logmsg_path); + + if (spawn_editor(editor, logmsg_path) == -1) + return got_error_from_errno("failed spawning editor"); + + if (stat(logmsg_path, &st2) == -1) + return got_error_from_errno("stat"); + + if (st.st_mtime == st2.st_mtime && st.st_size == st2.st_size) + return got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, + "no changes made to commit message, aborting"); + + *logmsg = malloc(st2.st_size + 1); + if (*logmsg == NULL) + return got_error_from_errno("malloc"); + (*logmsg)[0] = '\0'; + len = 0; + + fp = fopen(logmsg_path, "r"); + if (fp == NULL) { + err = got_error_from_errno("fopen"); + goto done; + } + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (!content_changed && strcmp(buf, initial_content) != 0) + content_changed = 1; + if (buf[0] == '#' || (len == 0 && buf[0] == '\n')) + continue; /* remove comments and leading empty lines */ + len = strlcat(*logmsg, buf, st2.st_size); + } + fclose(fp); + + while (len > 0 && (*logmsg)[len - 1] == '\n') { + (*logmsg)[len - 1] = '\0'; + len--; + } + + if (len == 0 || !content_changed) + err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, + "commit message cannot be empty, aborting"); +done: + if (err) { + free(*logmsg); + *logmsg = NULL; + } + return err; +} + +static const struct got_error * +collect_import_msg(char **logmsg, char **logmsg_path, const char *editor, + const char *path_dir, const char *branch_name) +{ + char *initial_content = NULL; + const struct got_error *err = NULL; + int initial_content_len; + int fd = -1; + + initial_content_len = asprintf(&initial_content, + "\n# %s to be imported to branch %s\n", path_dir, + branch_name); + if (initial_content_len == -1) + return got_error_from_errno("asprintf"); + + err = got_opentemp_named_fd(logmsg_path, &fd, + GOT_TMPDIR_STR "/got-importmsg"); + if (err) + goto done; + + if (write(fd, initial_content, initial_content_len) == -1) { + err = got_error_from_errno2("write", *logmsg_path); + goto done; + } + + err = edit_logmsg(logmsg, editor, *logmsg_path, initial_content); +done: + if (fd != -1 && close(fd) == -1 && err == NULL) + err = got_error_from_errno2("close", *logmsg_path); + free(initial_content); + if (err) { + free(*logmsg_path); + *logmsg_path = NULL; + } + return err; +} + +static const struct got_error * +import_progress(void *arg, const char *path) +{ + printf("A %s\n", path); + return NULL; +} + +static const struct got_error * +get_author(char **author, struct got_repository *repo, + struct got_worktree *worktree) +{ + const struct got_error *err = NULL; + const char *got_author = NULL, *name, *email; + const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL; + + *author = NULL; + + if (worktree) + worktree_conf = got_worktree_get_gotconfig(worktree); + repo_conf = got_repo_get_gotconfig(repo); + + /* + * Priority of potential author information sources, from most + * significant to least significant: + * 1) work tree's .got/got.conf file + * 2) repository's got.conf file + * 3) repository's git config file + * 4) environment variables + * 5) global git config files (in user's home directory or /etc) + */ + + if (worktree_conf) + got_author = got_gotconfig_get_author(worktree_conf); + if (got_author == NULL) + got_author = got_gotconfig_get_author(repo_conf); + if (got_author == NULL) { + name = got_repo_get_gitconfig_author_name(repo); + email = got_repo_get_gitconfig_author_email(repo); + if (name && email) { + if (asprintf(author, "%s <%s>", name, email) == -1) + return got_error_from_errno("asprintf"); + return NULL; + } + + got_author = getenv("GOT_AUTHOR"); + if (got_author == NULL) { + name = got_repo_get_global_gitconfig_author_name(repo); + email = got_repo_get_global_gitconfig_author_email( + repo); + if (name && email) { + if (asprintf(author, "%s <%s>", name, email) + == -1) + return got_error_from_errno("asprintf"); + return NULL; + } + /* TODO: Look up user in password database? */ + return got_error(GOT_ERR_COMMIT_NO_AUTHOR); + } + } + + *author = strdup(got_author); + if (*author == NULL) + return got_error_from_errno("strdup"); + + /* + * Really dumb email address check; we're only doing this to + * avoid git's object parser breaking on commits we create. + */ + while (*got_author && *got_author != '<') + got_author++; + if (*got_author != '<') { + err = got_error(GOT_ERR_COMMIT_NO_EMAIL); + goto done; + } + while (*got_author && *got_author != '@') + got_author++; + if (*got_author != '@') { + err = got_error(GOT_ERR_COMMIT_NO_EMAIL); + goto done; + } + while (*got_author && *got_author != '>') + got_author++; + if (*got_author != '>') + err = got_error(GOT_ERR_COMMIT_NO_EMAIL); +done: + if (err) { + free(*author); + *author = NULL; + } + return err; +} + +static const struct got_error * +get_gitconfig_path(char **gitconfig_path) +{ + const char *homedir = getenv("HOME"); + + *gitconfig_path = NULL; + if (homedir) { + if (asprintf(gitconfig_path, "%s/.gitconfig", homedir) == -1) + return got_error_from_errno("asprintf"); + + } + return NULL; +} + +static const struct got_error * +cmd_import(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + char *path_dir = NULL, *repo_path = NULL, *logmsg = NULL; + char *gitconfig_path = NULL, *editor = NULL, *author = NULL; + const char *branch_name = "main"; + char *refname = NULL, *id_str = NULL, *logmsg_path = NULL; + struct got_repository *repo = NULL; + struct got_reference *branch_ref = NULL, *head_ref = NULL; + struct got_object_id *new_commit_id = NULL; + int ch; + struct got_pathlist_head ignores; + struct got_pathlist_entry *pe; + int preserve_logmsg = 0; + + TAILQ_INIT(&ignores); + + while ((ch = getopt(argc, argv, "b:m:r:I:")) != -1) { + switch (ch) { + case 'b': + branch_name = optarg; + break; + case 'm': + logmsg = strdup(optarg); + if (logmsg == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + break; + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) { + error = got_error_from_errno2("realpath", + optarg); + goto done; + } + break; + case 'I': + if (optarg[0] == '\0') + break; + error = got_pathlist_insert(&pe, &ignores, optarg, + NULL); + if (error) + goto done; + break; + default: + usage_import(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", + NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_import(); + + if (repo_path == NULL) { + repo_path = getcwd(NULL, 0); + if (repo_path == NULL) + return got_error_from_errno("getcwd"); + } + got_path_strip_trailing_slashes(repo_path); + error = get_gitconfig_path(&gitconfig_path); + if (error) + goto done; + error = got_repo_open(&repo, repo_path, gitconfig_path); + if (error) + goto done; + + error = get_author(&author, repo, NULL); + if (error) + return error; + + /* + * 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 (asprintf(&refname, "refs/heads/%s", branch_name) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + + error = got_ref_open(&branch_ref, repo, refname, 0); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + } else { + error = got_error_msg(GOT_ERR_BRANCH_EXISTS, + "import target branch already exists"); + goto done; + } + + path_dir = realpath(argv[0], NULL); + if (path_dir == NULL) { + error = got_error_from_errno2("realpath", argv[0]); + goto done; + } + got_path_strip_trailing_slashes(path_dir); + + /* + * unveil(2) traverses exec(2); if an editor is used we have + * to apply unveil after the log message has been written. + */ + if (logmsg == NULL || strlen(logmsg) == 0) { + error = get_editor(&editor); + if (error) + goto done; + free(logmsg); + error = collect_import_msg(&logmsg, &logmsg_path, editor, + path_dir, refname); + if (error) { + if (error->code != GOT_ERR_COMMIT_MSG_EMPTY && + logmsg_path != NULL) + preserve_logmsg = 1; + goto done; + } + } + + if (unveil(path_dir, "r") != 0) { + error = got_error_from_errno2("unveil", path_dir); + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = apply_unveil(got_repo_get_path(repo), 0, NULL); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_repo_import(&new_commit_id, path_dir, logmsg, + author, &ignores, repo, import_progress, NULL); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_alloc(&branch_ref, refname, new_commit_id); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_write(branch_ref, repo); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_object_id_str(&id_str, new_commit_id); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); + if (error) { + if (error->code != GOT_ERR_NOT_REF) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_alloc_symref(&head_ref, GOT_REF_HEAD, + branch_ref); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + + error = got_ref_write(head_ref, repo); + if (error) { + if (logmsg_path) + preserve_logmsg = 1; + goto done; + } + } + + printf("Created branch %s with commit %s\n", + got_ref_get_name(branch_ref), id_str); +done: + if (preserve_logmsg) { + fprintf(stderr, "%s: log message preserved in %s\n", + getprogname(), logmsg_path); + } else if (logmsg_path && unlink(logmsg_path) == -1 && error == NULL) + error = got_error_from_errno2("unlink", logmsg_path); + free(logmsg); + free(logmsg_path); + free(repo_path); + free(editor); + free(refname); + free(new_commit_id); + free(id_str); + free(author); + free(gitconfig_path); + if (branch_ref) + got_ref_close(branch_ref); + if (head_ref) + got_ref_close(head_ref); + return error; +} + +__dead static void +usage_clone(void) +{ + fprintf(stderr, "usage: %s clone [-a] [-b branch] [-l] [-m] [-q] [-v] " + "[-R reference] repository-url [directory]\n", getprogname()); + exit(1); +} + +struct got_fetch_progress_arg { + char last_scaled_size[FMT_SCALED_STRSIZE]; + int last_p_indexed; + int last_p_resolved; + int verbosity; +}; + +static const struct got_error * +fetch_progress(void *arg, const char *message, off_t packfile_size, + int nobj_total, int nobj_indexed, int nobj_loose, int nobj_resolved) +{ + struct got_fetch_progress_arg *a = arg; + char scaled_size[FMT_SCALED_STRSIZE]; + int p_indexed, p_resolved; + int print_size = 0, print_indexed = 0, print_resolved = 0; + + if (a->verbosity < 0) + return NULL; + + if (message && message[0] != '\0') { + printf("\rserver: %s", message); + fflush(stdout); + return NULL; + } + + if (packfile_size > 0 || nobj_indexed > 0) { + if (fmt_scaled(packfile_size, scaled_size) == 0 && + (a->last_scaled_size[0] == '\0' || + strcmp(scaled_size, a->last_scaled_size)) != 0) { + print_size = 1; + if (strlcpy(a->last_scaled_size, scaled_size, + FMT_SCALED_STRSIZE) >= FMT_SCALED_STRSIZE) + return got_error(GOT_ERR_NO_SPACE); + } + if (nobj_indexed > 0) { + p_indexed = (nobj_indexed * 100) / nobj_total; + if (p_indexed != a->last_p_indexed) { + a->last_p_indexed = p_indexed; + print_indexed = 1; + print_size = 1; + } + } + if (nobj_resolved > 0) { + p_resolved = (nobj_resolved * 100) / + (nobj_total - nobj_loose); + if (p_resolved != a->last_p_resolved) { + a->last_p_resolved = p_resolved; + print_resolved = 1; + print_indexed = 1; + print_size = 1; + } + } + + } + if (print_size || print_indexed || print_resolved) + printf("\r"); + if (print_size) + printf("%*s fetched", FMT_SCALED_STRSIZE, scaled_size); + if (print_indexed) + printf("; indexing %d%%", p_indexed); + if (print_resolved) + printf("; resolving deltas %d%%", p_resolved); + if (print_size || print_indexed || print_resolved) + fflush(stdout); + + return NULL; +} + +static const struct got_error * +create_symref(const char *refname, struct got_reference *target_ref, + int verbosity, struct got_repository *repo) +{ + const struct got_error *err; + struct got_reference *head_symref; + + err = got_ref_alloc_symref(&head_symref, refname, target_ref); + if (err) + return err; + + err = got_ref_write(head_symref, repo); + got_ref_close(head_symref); + if (err == NULL && verbosity > 0) { + printf("Created reference %s: %s\n", GOT_REF_HEAD, + got_ref_get_name(target_ref)); + } + return err; +} + +static const struct got_error * +list_remote_refs(struct got_pathlist_head *symrefs, + struct got_pathlist_head *refs) +{ + const struct got_error *err; + struct got_pathlist_entry *pe; + + TAILQ_FOREACH(pe, symrefs, entry) { + const char *refname = pe->path; + const char *targetref = pe->data; + + printf("%s: %s\n", refname, targetref); + } + + TAILQ_FOREACH(pe, refs, entry) { + const char *refname = pe->path; + struct got_object_id *id = pe->data; + char *id_str; + + err = got_object_id_str(&id_str, id); + if (err) + return err; + printf("%s: %s\n", refname, id_str); + free(id_str); + } + + return NULL; +} + +static const struct got_error * +create_ref(const char *refname, struct got_object_id *id, + int verbosity, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_reference *ref; + char *id_str; + + err = got_object_id_str(&id_str, id); + if (err) + return err; + + err = got_ref_alloc(&ref, refname, id); + if (err) + goto done; + + err = got_ref_write(ref, repo); + got_ref_close(ref); + + if (err == NULL && verbosity >= 0) + printf("Created reference %s: %s\n", refname, id_str); +done: + free(id_str); + return err; +} + +static int +match_wanted_ref(const char *refname, const char *wanted_ref) +{ + if (strncmp(refname, "refs/", 5) != 0) + return 0; + refname += 5; + + /* + * Prevent fetching of references that won't make any + * sense outside of the remote repository's context. + */ + if (strncmp(refname, "got/", 4) == 0) + return 0; + if (strncmp(refname, "remotes/", 8) == 0) + return 0; + + if (strncmp(wanted_ref, "refs/", 5) == 0) + wanted_ref += 5; + + /* Allow prefix match. */ + if (got_path_is_child(refname, wanted_ref, strlen(wanted_ref))) + return 1; + + /* Allow exact match. */ + return (strcmp(refname, wanted_ref) == 0); +} + +static int +is_wanted_ref(struct got_pathlist_head *wanted_refs, const char *refname) +{ + struct got_pathlist_entry *pe; + + TAILQ_FOREACH(pe, wanted_refs, entry) { + if (match_wanted_ref(refname, pe->path)) + return 1; + } + + return 0; +} + +static const struct got_error * +create_wanted_ref(const char *refname, struct got_object_id *id, + const char *remote_repo_name, int verbosity, struct got_repository *repo) +{ + const struct got_error *err; + char *remote_refname; + + if (strncmp("refs/", refname, 5) == 0) + refname += 5; + + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote_repo_name, refname) == -1) + return got_error_from_errno("asprintf"); + + err = create_ref(remote_refname, id, verbosity, repo); + free(remote_refname); + return err; +} + +static const struct got_error * +cmd_clone(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + const char *uri, *dirname; + char *proto, *host, *port, *repo_name, *server_path; + char *default_destdir = NULL, *id_str = NULL; + const char *repo_path, *remote_repo_path; + struct got_repository *repo = NULL; + struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs; + struct got_pathlist_entry *pe; + struct got_object_id *pack_hash = NULL; + int ch, fetchfd = -1, fetchstatus; + pid_t fetchpid = -1; + struct got_fetch_progress_arg fpa; + char *git_url = NULL; + char *gitconfig_path = NULL, *gotconfig_path = NULL; + char *gitconfig = NULL, *gotconfig = NULL; + FILE *gitconfig_file = NULL, *gotconfig_file = NULL; + ssize_t n; + int verbosity = 0, fetch_all_branches = 0, mirror_references = 0; + int list_refs_only = 0; + struct got_reference *head_symref = NULL; + + TAILQ_INIT(&refs); + TAILQ_INIT(&symrefs); + TAILQ_INIT(&wanted_branches); + TAILQ_INIT(&wanted_refs); + + while ((ch = getopt(argc, argv, "ab:lmvqR:")) != -1) { + switch (ch) { + case 'a': + fetch_all_branches = 1; + break; + case 'b': + error = got_pathlist_append(&wanted_branches, + optarg, NULL); + if (error) + return error; + break; + case 'l': + list_refs_only = 1; + break; + case 'm': + mirror_references = 1; + break; + case 'v': + if (verbosity < 0) + verbosity = 0; + else if (verbosity < 3) + verbosity++; + break; + case 'q': + verbosity = -1; + break; + case 'R': + error = got_pathlist_append(&wanted_refs, + optarg, NULL); + if (error) + return error; + break; + default: + usage_clone(); + break; + } + } + argc -= optind; + argv += optind; + + if (fetch_all_branches && !TAILQ_EMPTY(&wanted_branches)) + errx(1, "-a and -b options are mutually exclusive"); + if (list_refs_only) { + if (!TAILQ_EMPTY(&wanted_branches)) + errx(1, "-l and -b options are mutually exclusive"); + if (fetch_all_branches) + errx(1, "-l and -a options are mutually exclusive"); + if (mirror_references) + errx(1, "-l and -m options are mutually exclusive"); + if (verbosity == -1) + errx(1, "-l and -q options are mutually exclusive"); + if (!TAILQ_EMPTY(&wanted_refs)) + errx(1, "-l and -R options are mutually exclusive"); + } + + uri = argv[0]; + + if (argc == 1) + dirname = NULL; + else if (argc == 2) + dirname = argv[1]; + else + usage_clone(); + + error = got_fetch_parse_uri(&proto, &host, &port, &server_path, + &repo_name, uri); + if (error) + goto done; + + if (asprintf(&git_url, "%s://%s%s%s%s%s", proto, + host, port ? ":" : "", port ? port : "", + server_path[0] != '/' ? "/" : "", server_path) == -1) { + error = got_error_from_errno("asprintf"); + 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; + } + if (dirname == NULL) { + if (asprintf(&default_destdir, "%s.git", repo_name) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + repo_path = default_destdir; + } else + repo_path = dirname; + + if (!list_refs_only) { + error = got_path_mkdir(repo_path); + if (error) + goto done; + + error = got_repo_init(repo_path); + if (error) + goto done; + error = got_repo_open(&repo, repo_path, NULL); + if (error) + goto done; + } + + if (strcmp(proto, "git+ssh") == 0 || strcmp(proto, "ssh") == 0) { + if (unveil(GOT_FETCH_PATH_SSH, "x") != 0) { + error = got_error_from_errno2("unveil", + GOT_FETCH_PATH_SSH); + goto done; + } + } + error = apply_unveil(repo ? got_repo_get_path(repo) : NULL, 0, NULL); + if (error) + goto done; + + if (verbosity >= 0) + printf("Connecting to %s%s%s\n", host, + port ? ":" : "", port ? port : ""); + + error = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, + server_path, verbosity); + if (error) + goto done; + + fpa.last_scaled_size[0] = '\0'; + fpa.last_p_indexed = -1; + fpa.last_p_resolved = -1; + fpa.verbosity = verbosity; + error = got_fetch_pack(&pack_hash, &refs, &symrefs, + GOT_FETCH_DEFAULT_REMOTE_NAME, mirror_references, + fetch_all_branches, &wanted_branches, &wanted_refs, + list_refs_only, verbosity, fetchfd, repo, + fetch_progress, &fpa); + if (error) + goto done; + + if (list_refs_only) { + error = list_remote_refs(&symrefs, &refs); + goto done; + } + + error = got_object_id_str(&id_str, pack_hash); + if (error) + goto done; + if (verbosity >= 0) + printf("\nFetched %s.pack\n", id_str); + free(id_str); + + /* Set up references provided with the pack file. */ + TAILQ_FOREACH(pe, &refs, entry) { + const char *refname = pe->path; + struct got_object_id *id = pe->data; + char *remote_refname; + + if (is_wanted_ref(&wanted_refs, refname) && + !mirror_references) { + error = create_wanted_ref(refname, id, + GOT_FETCH_DEFAULT_REMOTE_NAME, + verbosity - 1, repo); + if (error) + goto done; + continue; + } + + error = create_ref(refname, id, verbosity - 1, repo); + if (error) + goto done; + + if (mirror_references) + continue; + + if (strncmp("refs/heads/", refname, 11) != 0) + continue; + + if (asprintf(&remote_refname, + "refs/remotes/%s/%s", GOT_FETCH_DEFAULT_REMOTE_NAME, + refname + 11) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + error = create_ref(remote_refname, id, verbosity - 1, repo); + free(remote_refname); + if (error) + goto done; + } + + /* Set the HEAD reference if the server provided one. */ + TAILQ_FOREACH(pe, &symrefs, entry) { + struct got_reference *target_ref; + const char *refname = pe->path; + const char *target = pe->data; + char *remote_refname = NULL, *remote_target = NULL; + + if (strcmp(refname, GOT_REF_HEAD) != 0) + continue; + + error = got_ref_open(&target_ref, repo, target, 0); + if (error) { + if (error->code == GOT_ERR_NOT_REF) { + error = NULL; + continue; + } + goto done; + } + + error = create_symref(refname, target_ref, verbosity, repo); + got_ref_close(target_ref); + if (error) + goto done; + + if (mirror_references) + continue; + + if (strncmp("refs/heads/", target, 11) != 0) + continue; + + if (asprintf(&remote_refname, + "refs/remotes/%s/%s", GOT_FETCH_DEFAULT_REMOTE_NAME, + refname) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (asprintf(&remote_target, + "refs/remotes/%s/%s", GOT_FETCH_DEFAULT_REMOTE_NAME, + target + 11) == -1) { + error = got_error_from_errno("asprintf"); + free(remote_refname); + goto done; + } + error = got_ref_open(&target_ref, repo, remote_target, 0); + if (error) { + free(remote_refname); + free(remote_target); + if (error->code == GOT_ERR_NOT_REF) { + error = NULL; + continue; + } + goto done; + } + error = create_symref(remote_refname, target_ref, + verbosity - 1, repo); + free(remote_refname); + free(remote_target); + got_ref_close(target_ref); + if (error) + goto done; + } + if (pe == NULL) { + /* + * We failed to set the HEAD reference. If we asked for + * a set of wanted branches use the first of one of those + * which could be fetched instead. + */ + TAILQ_FOREACH(pe, &wanted_branches, entry) { + const char *target = pe->path; + struct got_reference *target_ref; + + error = got_ref_open(&target_ref, repo, target, 0); + if (error) { + if (error->code == GOT_ERR_NOT_REF) { + error = NULL; + continue; + } + goto done; + } + + error = create_symref(GOT_REF_HEAD, target_ref, + verbosity, repo); + got_ref_close(target_ref); + if (error) + goto done; + break; + } + } + + /* Create got.conf(5). */ + gotconfig_path = got_repo_get_path_gotconfig(repo); + if (gotconfig_path == NULL) { + error = got_error_from_errno("got_repo_get_path_gotconfig"); + goto done; + } + gotconfig_file = fopen(gotconfig_path, "a"); + if (gotconfig_file == NULL) { + error = got_error_from_errno2("fopen", gotconfig_path); + goto done; + } + got_path_strip_trailing_slashes(server_path); + remote_repo_path = server_path; + while (remote_repo_path[0] == '/') + remote_repo_path++; + if (asprintf(&gotconfig, + "remote \"%s\" {\n" + "\tserver %s\n" + "\tprotocol %s\n" + "%s%s%s" + "\trepository \"%s\"\n" + "%s" + "}\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, host, proto, + port ? "\tport " : "", port ? port : "", port ? "\n" : "", + remote_repo_path, + mirror_references ? "\tmirror-references yes\n" : "") == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + n = fwrite(gotconfig, 1, strlen(gotconfig), gotconfig_file); + if (n != strlen(gotconfig)) { + error = got_ferror(gotconfig_file, GOT_ERR_IO); + goto done; + } + + /* Create a config file Git can understand. */ + gitconfig_path = got_repo_get_path_gitconfig(repo); + if (gitconfig_path == NULL) { + error = got_error_from_errno("got_repo_get_path_gitconfig"); + goto done; + } + gitconfig_file = fopen(gitconfig_path, "a"); + if (gitconfig_file == NULL) { + error = got_error_from_errno2("fopen", gitconfig_path); + goto done; + } + if (mirror_references) { + if (asprintf(&gitconfig, + "[remote \"%s\"]\n" + "\turl = %s\n" + "\tfetch = +refs/*:refs/*\n" + "\tmirror = true\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, git_url) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + } else if (fetch_all_branches) { + if (asprintf(&gitconfig, + "[remote \"%s\"]\n" + "\turl = %s\n" + "\tfetch = +refs/heads/*:refs/remotes/%s/*\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, git_url, + GOT_FETCH_DEFAULT_REMOTE_NAME) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + } else { + const char *branchname; + + /* + * If the server specified a default branch, use just that one. + * Otherwise fall back to fetching all branches on next fetch. + */ + if (head_symref) { + branchname = got_ref_get_symref_target(head_symref); + if (strncmp(branchname, "refs/heads/", 11) == 0) + branchname += 11; + } else + branchname = "*"; /* fall back to all branches */ + if (asprintf(&gitconfig, + "[remote \"%s\"]\n" + "\turl = %s\n" + "\tfetch = +refs/heads/%s:refs/remotes/%s/%s\n", + GOT_FETCH_DEFAULT_REMOTE_NAME, git_url, + branchname, GOT_FETCH_DEFAULT_REMOTE_NAME, + branchname) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + } + n = fwrite(gitconfig, 1, strlen(gitconfig), gitconfig_file); + if (n != strlen(gitconfig)) { + error = got_ferror(gitconfig_file, GOT_ERR_IO); + goto done; + } + + if (verbosity >= 0) + printf("Created %s repository '%s'\n", + mirror_references ? "mirrored" : "cloned", repo_path); +done: + if (fetchpid > 0) { + if (kill(fetchpid, SIGTERM) == -1) + error = got_error_from_errno("kill"); + if (waitpid(fetchpid, &fetchstatus, 0) == -1 && error == NULL) + error = got_error_from_errno("waitpid"); + } + if (fetchfd != -1 && close(fetchfd) == -1 && error == NULL) + error = got_error_from_errno("close"); + if (gotconfig_file && fclose(gotconfig_file) == EOF && error == NULL) + error = got_error_from_errno("fclose"); + if (gitconfig_file && fclose(gitconfig_file) == EOF && error == NULL) + error = got_error_from_errno("fclose"); + if (repo) + got_repo_close(repo); + if (head_symref) + got_ref_close(head_symref); + TAILQ_FOREACH(pe, &refs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&refs); + TAILQ_FOREACH(pe, &symrefs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&symrefs); + got_pathlist_free(&wanted_branches); + got_pathlist_free(&wanted_refs); + free(pack_hash); + free(proto); + free(host); + free(port); + free(server_path); + free(repo_name); + free(default_destdir); + free(gotconfig); + free(gitconfig); + free(gotconfig_path); + free(gitconfig_path); + free(git_url); + return error; +} + +static const struct got_error * +update_ref(struct got_reference *ref, struct got_object_id *new_id, + int replace_tags, int verbosity, struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *new_id_str = NULL; + struct got_object_id *old_id = NULL; + + err = got_object_id_str(&new_id_str, new_id); + if (err) + goto done; + + if (!replace_tags && + strncmp(got_ref_get_name(ref), "refs/tags/", 10) == 0) { + err = got_ref_resolve(&old_id, repo, ref); + if (err) + goto done; + if (got_object_id_cmp(old_id, new_id) == 0) + goto done; + if (verbosity >= 0) { + printf("Rejecting update of existing tag %s: %s\n", + got_ref_get_name(ref), new_id_str); + } + goto done; + } + + if (got_ref_is_symbolic(ref)) { + if (verbosity >= 0) { + printf("Replacing reference %s: %s\n", + got_ref_get_name(ref), + got_ref_get_symref_target(ref)); + } + err = got_ref_change_symref_to_ref(ref, new_id); + if (err) + goto done; + err = got_ref_write(ref, repo); + if (err) + goto done; + } else { + err = got_ref_resolve(&old_id, repo, ref); + if (err) + goto done; + if (got_object_id_cmp(old_id, new_id) == 0) + goto done; + + err = got_ref_change_ref(ref, new_id); + if (err) + goto done; + err = got_ref_write(ref, repo); + if (err) + goto done; + } + + if (verbosity >= 0) + printf("Updated %s: %s\n", got_ref_get_name(ref), + new_id_str); +done: + free(old_id); + free(new_id_str); + return err; +} + +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 [-a] [-b branch] [-d] [-l] " + "[-r repository-path] [-t] [-q] [-v] [-R reference] " + "[remote-repository-name]\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 reference %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 reference %s: %s\n", + got_ref_get_name(ref), id_str); + } + } +done: + free(id); + free(id_str); + return NULL; +} + +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; + + SIMPLEQ_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; + + SIMPLEQ_FOREACH(re, &my_refs, entry) { + const char *refname = got_ref_get_name(re->ref); + + if (!remote->mirror_references) { + 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; + } + + TAILQ_FOREACH(pe, their_refs, entry) { + if (strcmp(local_refname, pe->path) == 0) + break; + } + if (pe != NULL) + continue; + + TAILQ_FOREACH(pe, their_symrefs, entry) { + if (strcmp(local_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: + 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) +{ + const struct got_error *err, *unlock_err; + char *remote_refname; + struct got_reference *ref; + + if (strncmp("refs/", refname, 5) == 0) + refname += 5; + + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote_repo_name, refname) == -1) + return got_error_from_errno("asprintf"); + + err = got_ref_open(&ref, repo, remote_refname, 1); + if (err) { + if (err->code != GOT_ERR_NOT_REF) + goto done; + err = create_ref(remote_refname, id, verbosity, repo); + } else { + err = update_ref(ref, id, 0, verbosity, repo); + unlock_err = got_ref_unlock(ref); + if (unlock_err && err == NULL) + err = unlock_err; + got_ref_close(ref); + } +done: + free(remote_refname); + return err; +} + +static const struct got_error * +cmd_fetch(int argc, char *argv[]) +{ + const struct got_error *error = NULL, *unlock_err; + 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; + char *id_str = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + const struct got_gotconfig *repo_conf = NULL, *worktree_conf = NULL; + struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs; + struct got_pathlist_entry *pe; + struct got_object_id *pack_hash = NULL; + int i, ch, fetchfd = -1, fetchstatus; + pid_t fetchpid = -1; + struct got_fetch_progress_arg fpa; + int verbosity = 0, fetch_all_branches = 0, list_refs_only = 0; + int delete_refs = 0, replace_tags = 0; + + TAILQ_INIT(&refs); + TAILQ_INIT(&symrefs); + TAILQ_INIT(&wanted_branches); + TAILQ_INIT(&wanted_refs); + + while ((ch = getopt(argc, argv, "ab:dlr:tvqR:")) != -1) { + switch (ch) { + case 'a': + fetch_all_branches = 1; + break; + case 'b': + error = got_pathlist_append(&wanted_branches, + optarg, NULL); + if (error) + return error; + break; + case 'd': + delete_refs = 1; + break; + case 'l': + list_refs_only = 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': + replace_tags = 1; + break; + case 'v': + if (verbosity < 0) + verbosity = 0; + else if (verbosity < 3) + verbosity++; + break; + case 'q': + verbosity = -1; + break; + case 'R': + error = got_pathlist_append(&wanted_refs, + optarg, NULL); + if (error) + return error; + break; + default: + usage_fetch(); + break; + } + } + argc -= optind; + argv += optind; + + if (fetch_all_branches && !TAILQ_EMPTY(&wanted_branches)) + errx(1, "-a and -b options are mutually exclusive"); + if (list_refs_only) { + if (!TAILQ_EMPTY(&wanted_branches)) + errx(1, "-l and -b options are mutually exclusive"); + if (fetch_all_branches) + errx(1, "-l and -a options are mutually exclusive"); + if (delete_refs) + errx(1, "-l and -d options are mutually exclusive"); + if (verbosity == -1) + errx(1, "-l and -q options are mutually exclusive"); + } + + if (argc == 0) + remote_name = GOT_FETCH_DEFAULT_REMOTE_NAME; + else if (argc == 1) + remote_name = argv[0]; + else + usage_fetch(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + 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++) { + remote = &remotes[i]; + if (strcmp(remote->name, remote_name) == 0) + 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++) { + remote = &remotes[i]; + if (strcmp(remote->name, remote_name) == 0) + break; + } + } + } + if (remote == NULL) { + got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo); + for (i = 0; i < nremotes; i++) { + remote = &remotes[i]; + if (strcmp(remote->name, remote_name) == 0) + break; + } + } + if (remote == NULL) { + error = got_error_path(remote_name, GOT_ERR_NO_REMOTE); + goto done; + } + + error = got_fetch_parse_uri(&proto, &host, &port, &server_path, + &repo_name, remote->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; + } + + if (strcmp(proto, "git+ssh") == 0 || strcmp(proto, "ssh") == 0) { + if (unveil(GOT_FETCH_PATH_SSH, "x") != 0) { + error = got_error_from_errno2("unveil", + GOT_FETCH_PATH_SSH); + goto done; + } + } + error = apply_unveil(got_repo_get_path(repo), 0, NULL); + if (error) + goto done; + + if (verbosity >= 0) + printf("Connecting to \"%s\" %s%s%s\n", remote->name, host, + port ? ":" : "", port ? port : ""); + + error = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, + server_path, verbosity); + if (error) + goto done; + + fpa.last_scaled_size[0] = '\0'; + fpa.last_p_indexed = -1; + fpa.last_p_resolved = -1; + fpa.verbosity = verbosity; + error = got_fetch_pack(&pack_hash, &refs, &symrefs, remote->name, + remote->mirror_references, fetch_all_branches, &wanted_branches, + &wanted_refs, list_refs_only, verbosity, fetchfd, repo, + fetch_progress, &fpa); + if (error) + goto done; + + if (list_refs_only) { + error = list_remote_refs(&symrefs, &refs); + goto done; + } + + if (pack_hash == NULL) { + if (verbosity >= 0) + printf("Already up-to-date\n"); + } else if (verbosity >= 0) { + error = got_object_id_str(&id_str, pack_hash); + if (error) + goto done; + printf("\nFetched %s.pack\n", id_str); + free(id_str); + id_str = NULL; + } + + /* Update references provided with the pack file. */ + TAILQ_FOREACH(pe, &refs, entry) { + const char *refname = pe->path; + struct got_object_id *id = pe->data; + struct got_reference *ref; + char *remote_refname; + + if (is_wanted_ref(&wanted_refs, refname) && + !remote->mirror_references) { + error = update_wanted_ref(refname, id, + remote->name, verbosity, repo); + if (error) + goto done; + continue; + } + + if (remote->mirror_references || + strncmp("refs/tags/", refname, 10) == 0) { + error = got_ref_open(&ref, repo, refname, 1); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + error = create_ref(refname, id, verbosity, + repo); + if (error) + goto done; + } else { + error = update_ref(ref, id, replace_tags, + verbosity, repo); + unlock_err = got_ref_unlock(ref); + if (unlock_err && error == NULL) + error = unlock_err; + got_ref_close(ref); + if (error) + goto done; + } + } else if (strncmp("refs/heads/", refname, 11) == 0) { + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote_name, refname + 11) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + + error = got_ref_open(&ref, repo, remote_refname, 1); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + error = create_ref(remote_refname, id, + verbosity, repo); + if (error) + goto done; + } else { + error = update_ref(ref, id, replace_tags, + verbosity, repo); + unlock_err = got_ref_unlock(ref); + if (unlock_err && error == NULL) + error = unlock_err; + got_ref_close(ref); + if (error) + goto done; + } + + /* Also create a local branch if none exists yet. */ + error = got_ref_open(&ref, repo, refname, 1); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + error = create_ref(refname, id, verbosity, + repo); + if (error) + goto done; + } else { + unlock_err = got_ref_unlock(ref); + if (unlock_err && error == NULL) + error = unlock_err; + got_ref_close(ref); + } + } + } + if (delete_refs) { + error = delete_missing_refs(&refs, &symrefs, remote, + verbosity, repo); + if (error) + goto done; + } + + if (!remote->mirror_references) { + /* Update remote HEAD reference if the server provided one. */ + TAILQ_FOREACH(pe, &symrefs, entry) { + struct got_reference *target_ref; + const char *refname = pe->path; + const char *target = pe->data; + char *remote_refname = NULL, *remote_target = NULL; + + if (strcmp(refname, GOT_REF_HEAD) != 0) + continue; + + if (strncmp("refs/heads/", target, 11) != 0) + continue; + + if (asprintf(&remote_refname, "refs/remotes/%s/%s", + remote->name, refname) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (asprintf(&remote_target, "refs/remotes/%s/%s", + remote->name, target + 11) == -1) { + error = got_error_from_errno("asprintf"); + free(remote_refname); + goto done; + } + + error = got_ref_open(&target_ref, repo, remote_target, + 0); + if (error) { + free(remote_refname); + free(remote_target); + if (error->code == GOT_ERR_NOT_REF) { + error = NULL; + continue; + } + goto done; + } + error = update_symref(remote_refname, target_ref, + verbosity, repo); + free(remote_refname); + free(remote_target); + got_ref_close(target_ref); + if (error) + goto done; + } + } +done: + if (fetchpid > 0) { + if (kill(fetchpid, SIGTERM) == -1) + error = got_error_from_errno("kill"); + if (waitpid(fetchpid, &fetchstatus, 0) == -1 && error == NULL) + error = got_error_from_errno("waitpid"); + } + if (fetchfd != -1 && close(fetchfd) == -1 && error == NULL) + error = got_error_from_errno("close"); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &refs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&refs); + TAILQ_FOREACH(pe, &symrefs, entry) { + free((void *)pe->path); + free(pe->data); + } + got_pathlist_free(&symrefs); + got_pathlist_free(&wanted_branches); + got_pathlist_free(&wanted_refs); + free(id_str); + free(cwd); + free(repo_path); + free(pack_hash); + free(proto); + free(host); + free(port); + free(server_path); + free(repo_name); + return error; +} + + +__dead static void +usage_checkout(void) +{ + fprintf(stderr, "usage: %s checkout [-E] [-b branch] [-c commit] " + "[-p prefix] repository-path [worktree-path]\n", getprogname()); + exit(1); +} + +static void +show_worktree_base_ref_warning(void) +{ + fprintf(stderr, "%s: warning: could not create a reference " + "to the work tree's base commit; the commit could be " + "garbage-collected by Git; making the repository " + "writable and running 'got update' will prevent this\n", + getprogname()); +} + +struct got_checkout_progress_arg { + const char *worktree_path; + int had_base_commit_ref_error; +}; + +static const struct got_error * +checkout_progress(void *arg, unsigned char status, const char *path) +{ + struct got_checkout_progress_arg *a = arg; + + /* Base commit bump happens silently. */ + if (status == GOT_STATUS_BUMP_BASE) + return NULL; + + if (status == GOT_STATUS_BASE_REF_ERR) { + a->had_base_commit_ref_error = 1; + return NULL; + } + + while (path[0] == '/') + path++; + + printf("%c %s/%s\n", status, a->worktree_path, path); + return NULL; +} + +static const struct got_error * +check_cancelled(void *arg) +{ + if (sigint_received || sigpipe_received) + return got_error(GOT_ERR_CANCELLED); + return NULL; +} + +static const struct got_error * +check_linear_ancestry(struct got_object_id *commit_id, + struct got_object_id *base_commit_id, int allow_forwards_in_time_only, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_id *yca_id; + + err = got_commit_graph_find_youngest_common_ancestor(&yca_id, + commit_id, base_commit_id, repo, check_cancelled, NULL); + if (err) + return err; + + if (yca_id == NULL) + return got_error(GOT_ERR_ANCESTRY); + + /* + * Require a straight line of history between the target commit + * and the work tree's base commit. + * + * Non-linear situations such as this require a rebase: + * + * (commit) D F (base_commit) + * \ / + * C E + * \ / + * B (yca) + * | + * A + * + * 'got update' only handles linear cases: + * Update forwards in time: A (base/yca) - B - C - D (commit) + * Update backwards in time: D (base) - C - B - A (commit/yca) + */ + if (allow_forwards_in_time_only) { + if (got_object_id_cmp(base_commit_id, yca_id) != 0) + return got_error(GOT_ERR_ANCESTRY); + } else if (got_object_id_cmp(commit_id, yca_id) != 0 && + got_object_id_cmp(base_commit_id, yca_id) != 0) + return got_error(GOT_ERR_ANCESTRY); + + free(yca_id); + return NULL; +} + +static const struct got_error * +check_same_branch(struct got_object_id *commit_id, + struct got_reference *head_ref, struct got_object_id *yca_id, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_commit_graph *graph = NULL; + struct got_object_id *head_commit_id = NULL; + int is_same_branch = 0; + + err = got_ref_resolve(&head_commit_id, repo, head_ref); + if (err) + goto done; + + if (got_object_id_cmp(head_commit_id, commit_id) == 0) { + is_same_branch = 1; + goto done; + } + if (yca_id && got_object_id_cmp(commit_id, yca_id) == 0) { + is_same_branch = 1; + goto done; + } + + err = got_commit_graph_open(&graph, "/", 1); + if (err) + goto done; + + err = got_commit_graph_iter_start(graph, head_commit_id, repo, + check_cancelled, NULL); + if (err) + goto done; + + for (;;) { + struct got_object_id *id; + err = got_commit_graph_iter_next(&id, graph, repo, + check_cancelled, NULL); + if (err) { + if (err->code == GOT_ERR_ITER_COMPLETED) + err = NULL; + break; + } + + if (id) { + if (yca_id && got_object_id_cmp(id, yca_id) == 0) + break; + if (got_object_id_cmp(id, commit_id) == 0) { + is_same_branch = 1; + break; + } + } + } +done: + if (graph) + got_commit_graph_close(graph); + free(head_commit_id); + if (!err && !is_same_branch) + err = got_error(GOT_ERR_ANCESTRY); + return err; +} + +static const struct got_error * +checkout_ancestry_error(struct got_reference *ref, const char *commit_id_str) +{ + static char msg[512]; + const char *branch_name; + + if (got_ref_is_symbolic(ref)) + branch_name = got_ref_get_symref_target(ref); + else + branch_name = got_ref_get_name(ref); + + if (strncmp("refs/heads/", branch_name, 11) == 0) + branch_name += 11; + + snprintf(msg, sizeof(msg), + "target commit is not contained in branch '%s'; " + "the branch to use must be specified with -b; " + "if necessary a new branch can be created for " + "this commit with 'got branch -c %s BRANCH_NAME'", + branch_name, commit_id_str); + + return got_error_msg(GOT_ERR_ANCESTRY, msg); +} + +static const struct got_error * +cmd_checkout(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_reference *head_ref = NULL; + struct got_worktree *worktree = NULL; + char *repo_path = NULL; + char *worktree_path = NULL; + const char *path_prefix = ""; + const char *branch_name = GOT_REF_HEAD; + char *commit_id_str = NULL; + char *cwd = NULL, *path = NULL; + int ch, same_path_prefix, allow_nonempty = 0; + struct got_pathlist_head paths; + struct got_checkout_progress_arg cpa; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "b:c:Ep:")) != -1) { + switch (ch) { + case 'b': + branch_name = optarg; + break; + case 'c': + commit_id_str = strdup(optarg); + if (commit_id_str == NULL) + return got_error_from_errno("strdup"); + break; + case 'E': + allow_nonempty = 1; + break; + case 'p': + path_prefix = optarg; + break; + default: + usage_checkout(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc == 1) { + char *base, *dotgit; + repo_path = realpath(argv[0], NULL); + if (repo_path == NULL) + return got_error_from_errno2("realpath", argv[0]); + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + if (path_prefix[0]) + path = strdup(path_prefix); + else + path = strdup(repo_path); + if (path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + base = basename(path); + if (base == NULL) { + error = got_error_from_errno2("basename", path); + goto done; + } + dotgit = strstr(base, ".git"); + if (dotgit) + *dotgit = '\0'; + if (asprintf(&worktree_path, "%s/%s", cwd, base) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + } else if (argc == 2) { + repo_path = realpath(argv[0], NULL); + if (repo_path == NULL) { + error = got_error_from_errno2("realpath", argv[0]); + goto done; + } + worktree_path = realpath(argv[1], NULL); + if (worktree_path == NULL) { + if (errno != ENOENT) { + error = got_error_from_errno2("realpath", + argv[1]); + goto done; + } + worktree_path = strdup(argv[1]); + if (worktree_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } else + usage_checkout(); + + got_path_strip_trailing_slashes(repo_path); + got_path_strip_trailing_slashes(worktree_path); + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + /* Pre-create work tree path for unveil(2) */ + error = got_path_mkdir(worktree_path); + if (error) { + if (!(error->code == GOT_ERR_ERRNO && errno == EISDIR) && + !(error->code == GOT_ERR_ERRNO && errno == EEXIST)) + goto done; + if (!allow_nonempty && + !got_path_dir_is_empty(worktree_path)) { + error = got_error_path(worktree_path, + GOT_ERR_DIR_NOT_EMPTY); + goto done; + } + } + + error = apply_unveil(got_repo_get_path(repo), 0, worktree_path); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, branch_name, 0); + if (error != NULL) + goto done; + + error = got_worktree_init(worktree_path, head_ref, path_prefix, repo); + if (error != NULL && !(error->code == GOT_ERR_ERRNO && errno == EEXIST)) + goto done; + + error = got_worktree_open(&worktree, worktree_path); + if (error != NULL) + goto done; + + error = got_worktree_match_path_prefix(&same_path_prefix, worktree, + path_prefix); + if (error != NULL) + goto done; + if (!same_path_prefix) { + error = got_error(GOT_ERR_PATH_PREFIX); + goto done; + } + + if (commit_id_str) { + struct got_object_id *commit_id; + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + if (error) + goto done; + error = check_linear_ancestry(commit_id, + got_worktree_get_base_commit_id(worktree), 0, repo); + if (error != NULL) { + free(commit_id); + if (error->code == GOT_ERR_ANCESTRY) { + error = checkout_ancestry_error( + head_ref, commit_id_str); + } + goto done; + } + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) { + if (error->code == GOT_ERR_ANCESTRY) { + error = checkout_ancestry_error( + head_ref, commit_id_str); + } + goto done; + } + error = got_worktree_set_base_commit_id(worktree, repo, + commit_id); + free(commit_id); + if (error) + goto done; + } + + error = got_pathlist_append(&paths, "", NULL); + if (error) + goto done; + cpa.worktree_path = worktree_path; + cpa.had_base_commit_ref_error = 0; + error = got_worktree_checkout_files(worktree, &paths, repo, + checkout_progress, &cpa, check_cancelled, NULL); + if (error != NULL) + goto done; + + printf("Now shut up and hack\n"); + if (cpa.had_base_commit_ref_error) + show_worktree_base_ref_warning(); +done: + got_pathlist_free(&paths); + free(commit_id_str); + free(repo_path); + free(worktree_path); + free(cwd); + free(path); + return error; +} + +struct got_update_progress_arg { + int did_something; + int conflicts; + int obstructed; + int not_updated; +}; + +void +print_update_progress_stats(struct got_update_progress_arg *upa) +{ + if (!upa->did_something) + return; + + if (upa->conflicts > 0) + printf("Files with new merge conflicts: %d\n", upa->conflicts); + if (upa->obstructed > 0) + printf("File paths obstructed by a non-regular file: %d\n", + upa->obstructed); + if (upa->not_updated > 0) + printf("Files not updated because of existing merge " + "conflicts: %d\n", upa->not_updated); +} + +__dead static void +usage_update(void) +{ + fprintf(stderr, "usage: %s update [-b branch] [-c commit] [path ...]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +update_progress(void *arg, unsigned char status, const char *path) +{ + struct got_update_progress_arg *upa = arg; + + if (status == GOT_STATUS_EXISTS || + status == GOT_STATUS_BASE_REF_ERR) + return NULL; + + upa->did_something = 1; + + /* Base commit bump happens silently. */ + if (status == GOT_STATUS_BUMP_BASE) + return NULL; + + if (status == GOT_STATUS_CONFLICT) + upa->conflicts++; + if (status == GOT_STATUS_OBSTRUCTED) + upa->obstructed++; + if (status == GOT_STATUS_CANNOT_UPDATE) + upa->not_updated++; + + while (path[0] == '/') + path++; + printf("%c %s\n", status, path); + return NULL; +} + +static const struct got_error * +switch_head_ref(struct got_reference *head_ref, + struct got_object_id *commit_id, struct got_worktree *worktree, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *base_id_str; + int ref_has_moved = 0; + + /* Trivial case: switching between two different references. */ + if (strcmp(got_ref_get_name(head_ref), + got_worktree_get_head_ref_name(worktree)) != 0) { + printf("Switching work tree from %s to %s\n", + got_worktree_get_head_ref_name(worktree), + got_ref_get_name(head_ref)); + return got_worktree_set_head_ref(worktree, head_ref); + } + + err = check_linear_ancestry(commit_id, + got_worktree_get_base_commit_id(worktree), 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; + int in_progress; + + err = got_worktree_rebase_in_progress(&in_progress, worktree); + if (err) + return err; + if (in_progress) + return got_error(GOT_ERR_REBASING); + + err = got_worktree_histedit_in_progress(&in_progress, worktree); + if (err) + return err; + if (in_progress) + return got_error(GOT_ERR_HISTEDIT_BUSY); + + return NULL; +} + +static const struct got_error * +get_worktree_paths_from_argv(struct got_pathlist_head *paths, int argc, + char *argv[], struct got_worktree *worktree) +{ + const struct got_error *err = NULL; + char *path; + int i; + + if (argc == 0) { + path = strdup(""); + if (path == NULL) + return got_error_from_errno("strdup"); + return got_pathlist_append(paths, path, NULL); + } + + for (i = 0; i < argc; i++) { + err = got_worktree_resolve_path(&path, worktree, argv[i]); + if (err) + break; + err = got_pathlist_append(paths, path, NULL); + if (err) { + free(path); + break; + } + } + + return err; +} + +static const struct got_error * +wrap_not_worktree_error(const struct got_error *orig_err, + const char *cmdname, const char *path) +{ + const struct got_error *err; + struct got_repository *repo; + static char msg[512]; + + err = got_repo_open(&repo, path, NULL); + if (err) + return orig_err; + + snprintf(msg, sizeof(msg), + "'got %s' needs a work tree in addition to a git repository\n" + "Work trees can be checked out from this Git repository with " + "'got checkout'.\n" + "The got(1) manual page contains more information.", cmdname); + err = got_error_msg(GOT_ERR_NOT_WORKTREE, msg); + got_repo_close(repo); + return err; +} + +static const struct got_error * +cmd_update(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *worktree_path = NULL; + struct got_object_id *commit_id = NULL; + char *commit_id_str = NULL; + const char *branch_name = NULL; + struct got_reference *head_ref = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch; + struct got_update_progress_arg upa; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "b:c:")) != -1) { + switch (ch) { + case 'b': + branch_name = optarg; + break; + case 'c': + commit_id_str = strdup(optarg); + if (commit_id_str == NULL) + return got_error_from_errno("strdup"); + break; + default: + usage_update(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + worktree_path = getcwd(NULL, 0); + if (worktree_path == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, worktree_path); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "update", + worktree_path); + 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); + 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 = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, branch_name ? branch_name : + got_worktree_get_head_ref_name(worktree), 0); + if (error != NULL) + goto done; + if (commit_id_str == NULL) { + error = got_ref_resolve(&commit_id, repo, head_ref); + if (error != NULL) + goto done; + error = got_object_id_str(&commit_id_str, commit_id); + if (error != NULL) + goto done; + } else { + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + free(commit_id_str); + commit_id_str = NULL; + if (error) + goto done; + error = got_object_id_str(&commit_id_str, commit_id); + if (error) + goto done; + } + + if (branch_name) { + struct got_object_id *head_commit_id; + TAILQ_FOREACH(pe, &paths, entry) { + if (pe->path_len == 0) + continue; + error = got_error_msg(GOT_ERR_BAD_PATH, + "switching between branches requires that " + "the entire work tree gets updated"); + goto done; + } + error = got_ref_resolve(&head_commit_id, repo, head_ref); + if (error) + goto done; + error = check_linear_ancestry(commit_id, head_commit_id, 0, + repo); + free(head_commit_id); + if (error != NULL) + goto done; + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) + goto done; + error = switch_head_ref(head_ref, commit_id, worktree, repo); + if (error) + goto done; + } else { + error = check_linear_ancestry(commit_id, + got_worktree_get_base_commit_id(worktree), 0, repo); + if (error != NULL) { + if (error->code == GOT_ERR_ANCESTRY) + error = got_error(GOT_ERR_BRANCH_MOVED); + goto done; + } + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) + goto done; + } + + if (got_object_id_cmp(got_worktree_get_base_commit_id(worktree), + commit_id) != 0) { + error = got_worktree_set_base_commit_id(worktree, repo, + commit_id); + if (error) + goto done; + } + + memset(&upa, 0, sizeof(upa)); + error = got_worktree_checkout_files(worktree, &paths, repo, + update_progress, &upa, check_cancelled, NULL); + if (error != NULL) + goto done; + + if (upa.did_something) + printf("Updated to commit %s\n", commit_id_str); + else + printf("Already up-to-date\n"); + print_update_progress_stats(&upa); +done: + free(worktree_path); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(commit_id); + free(commit_id_str); + return error; +} + +static const struct got_error * +diff_blobs(struct got_object_id *blob_id1, struct got_object_id *blob_id2, + const char *path, int diff_context, int ignore_whitespace, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_blob_object *blob1 = NULL, *blob2 = NULL; + + if (blob_id1) { + err = got_object_open_as_blob(&blob1, repo, blob_id1, 8192); + if (err) + goto done; + } + + err = got_object_open_as_blob(&blob2, repo, blob_id2, 8192); + if (err) + goto done; + + while (path[0] == '/') + path++; + err = got_diff_blob(blob1, blob2, path, path, diff_context, + ignore_whitespace, stdout); +done: + if (blob1) + got_object_blob_close(blob1); + got_object_blob_close(blob2); + return err; +} + +static const struct got_error * +diff_trees(struct got_object_id *tree_id1, struct got_object_id *tree_id2, + const char *path, int diff_context, int ignore_whitespace, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_tree_object *tree1 = NULL, *tree2 = NULL; + struct got_diff_blob_output_unidiff_arg arg; + + if (tree_id1) { + err = got_object_open_as_tree(&tree1, repo, tree_id1); + if (err) + goto done; + } + + err = got_object_open_as_tree(&tree2, repo, tree_id2); + if (err) + goto done; + + arg.diff_context = diff_context; + arg.ignore_whitespace = ignore_whitespace; + arg.outfile = stdout; + while (path[0] == '/') + path++; + err = got_diff_tree(tree1, tree2, path, path, repo, + got_diff_blob_output_unidiff, &arg, 1); +done: + if (tree1) + got_object_tree_close(tree1); + if (tree2) + got_object_tree_close(tree2); + return err; +} + +static const struct got_error * +get_changed_paths(struct got_pathlist_head *paths, + struct got_commit_object *commit, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_id *tree_id1 = NULL, *tree_id2 = NULL; + struct got_tree_object *tree1 = NULL, *tree2 = NULL; + struct got_object_qid *qid; + + qid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); + if (qid != NULL) { + struct got_commit_object *pcommit; + err = got_object_open_as_commit(&pcommit, repo, + qid->id); + if (err) + return err; + + tree_id1 = got_object_commit_get_tree_id(pcommit); + got_object_commit_close(pcommit); + + } + + if (tree_id1) { + err = got_object_open_as_tree(&tree1, repo, tree_id1); + if (err) + goto done; + } + + tree_id2 = got_object_commit_get_tree_id(commit); + err = got_object_open_as_tree(&tree2, repo, tree_id2); + if (err) + goto done; + + err = got_diff_tree(tree1, tree2, "", "", repo, + got_diff_tree_collect_changed_paths, paths, 0); +done: + if (tree1) + got_object_tree_close(tree1); + if (tree2) + got_object_tree_close(tree2); + return err; +} + +static const struct got_error * +print_patch(struct got_commit_object *commit, struct got_object_id *id, + const char *path, int diff_context, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_commit_object *pcommit = NULL; + char *id_str1 = NULL, *id_str2 = NULL; + struct got_object_id *obj_id1 = NULL, *obj_id2 = NULL; + struct got_object_qid *qid; + + qid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); + if (qid != NULL) { + err = got_object_open_as_commit(&pcommit, repo, + qid->id); + if (err) + return err; + } + + if (path && path[0] != '\0') { + int obj_type; + err = got_object_id_by_path(&obj_id2, repo, id, path); + if (err) + goto done; + err = got_object_id_str(&id_str2, obj_id2); + if (err) { + free(obj_id2); + goto done; + } + if (pcommit) { + err = got_object_id_by_path(&obj_id1, repo, + qid->id, path); + if (err) { + if (err->code != GOT_ERR_NO_TREE_ENTRY) { + free(obj_id2); + goto done; + } + } else { + err = got_object_id_str(&id_str1, obj_id1); + if (err) { + free(obj_id2); + goto done; + } + } + } + err = got_object_get_type(&obj_type, repo, obj_id2); + if (err) { + free(obj_id2); + goto done; + } + printf("diff %s %s\n", id_str1 ? id_str1 : "/dev/null", id_str2); + switch (obj_type) { + case GOT_OBJ_TYPE_BLOB: + err = diff_blobs(obj_id1, obj_id2, path, diff_context, + 0, repo); + break; + case GOT_OBJ_TYPE_TREE: + err = diff_trees(obj_id1, obj_id2, path, diff_context, + 0, repo); + break; + default: + err = got_error(GOT_ERR_OBJ_TYPE); + break; + } + free(obj_id1); + free(obj_id2); + } else { + obj_id2 = got_object_commit_get_tree_id(commit); + err = got_object_id_str(&id_str2, obj_id2); + if (err) + goto done; + obj_id1 = got_object_commit_get_tree_id(pcommit); + err = got_object_id_str(&id_str1, obj_id1); + if (err) + goto done; + printf("diff %s %s\n", id_str1 ? id_str1 : "/dev/null", id_str2); + err = diff_trees(obj_id1, obj_id2, "", diff_context, 0, repo); + } +done: + free(id_str1); + free(id_str2); + if (pcommit) + got_object_commit_close(pcommit); + return err; +} + +static char * +get_datestr(time_t *time, char *datebuf) +{ + struct tm mytm, *tm; + char *p, *s; + + tm = gmtime_r(time, &mytm); + if (tm == NULL) + return NULL; + s = asctime_r(tm, datebuf); + if (s == NULL) + return NULL; + p = strchr(s, '\n'); + if (p) + *p = '\0'; + return s; +} + +static const struct got_error * +match_logmsg(int *have_match, struct got_object_id *id, + struct got_commit_object *commit, regex_t *regex) +{ + const struct got_error *err = NULL; + regmatch_t regmatch; + char *id_str = NULL, *logmsg = NULL; + + *have_match = 0; + + err = got_object_id_str(&id_str, id); + if (err) + return err; + + err = got_object_commit_get_logmsg(&logmsg, commit); + if (err) + goto done; + + if (regexec(regex, logmsg, 1, ®match, 0) == 0) + *have_match = 1; +done: + free(id_str); + free(logmsg); + return err; +} + +static void +match_changed_paths(int *have_match, struct got_pathlist_head *changed_paths, + regex_t *regex) +{ + regmatch_t regmatch; + struct got_pathlist_entry *pe; + + *have_match = 0; + + TAILQ_FOREACH(pe, changed_paths, entry) { + if (regexec(regex, pe->path, 1, ®match, 0) == 0) { + *have_match = 1; + break; + } + } +} + +#define GOT_COMMIT_SEP_STR "-----------------------------------------------\n" + +static const struct got_error * +print_commit(struct got_commit_object *commit, struct got_object_id *id, + struct got_repository *repo, const char *path, + struct got_pathlist_head *changed_paths, int show_patch, + int diff_context, struct got_reflist_head *refs) +{ + const struct got_error *err = NULL; + char *id_str, *datestr, *logmsg0, *logmsg, *line; + char datebuf[26]; + time_t committer_time; + const char *author, *committer; + char *refs_str = NULL; + struct got_reflist_entry *re; + + SIMPLEQ_FOREACH(re, refs, entry) { + char *s; + const char *name; + struct got_tag_object *tag = NULL; + struct got_object_id *ref_id; + int cmp; + + name = got_ref_get_name(re->ref); + if (strcmp(name, GOT_REF_HEAD) == 0) + continue; + if (strncmp(name, "refs/", 5) == 0) + name += 5; + if (strncmp(name, "got/", 4) == 0) + continue; + if (strncmp(name, "heads/", 6) == 0) + name += 6; + if (strncmp(name, "remotes/", 8) == 0) { + name += 8; + s = strstr(name, "/" GOT_REF_HEAD); + if (s != NULL && s[strlen(s)] == '\0') + continue; + } + err = got_ref_resolve(&ref_id, repo, re->ref); + if (err) + return err; + if (strncmp(name, "tags/", 5) == 0) { + err = got_object_open_as_tag(&tag, repo, ref_id); + if (err) { + if (err->code != GOT_ERR_OBJ_TYPE) { + free(ref_id); + return err; + } + /* Ref points at something other than a tag. */ + err = NULL; + tag = NULL; + } + } + cmp = got_object_id_cmp(tag ? + got_object_tag_get_object_id(tag) : ref_id, id); + free(ref_id); + if (tag) + got_object_tag_close(tag); + if (cmp != 0) + continue; + s = refs_str; + if (asprintf(&refs_str, "%s%s%s", s ? s : "", s ? ", " : "", + name) == -1) { + err = got_error_from_errno("asprintf"); + free(s); + return err; + } + free(s); + } + err = got_object_id_str(&id_str, id); + if (err) + return err; + + printf(GOT_COMMIT_SEP_STR); + printf("commit %s%s%s%s\n", id_str, refs_str ? " (" : "", + refs_str ? refs_str : "", refs_str ? ")" : ""); + free(id_str); + id_str = NULL; + free(refs_str); + refs_str = NULL; + printf("from: %s\n", got_object_commit_get_author(commit)); + committer_time = got_object_commit_get_committer_time(commit); + datestr = get_datestr(&committer_time, datebuf); + if (datestr) + printf("date: %s UTC\n", datestr); + author = got_object_commit_get_author(commit); + committer = got_object_commit_get_committer(commit); + if (strcmp(author, committer) != 0) + printf("via: %s\n", committer); + if (got_object_commit_get_nparents(commit) > 1) { + const struct got_object_id_queue *parent_ids; + struct got_object_qid *qid; + int n = 1; + parent_ids = got_object_commit_get_parent_ids(commit); + SIMPLEQ_FOREACH(qid, parent_ids, entry) { + err = got_object_id_str(&id_str, qid->id); + if (err) + return err; + printf("parent %d: %s\n", n++, id_str); + free(id_str); + } + } + + err = got_object_commit_get_logmsg(&logmsg0, commit); + if (err) + return err; + + logmsg = logmsg0; + do { + line = strsep(&logmsg, "\n"); + if (line) + printf(" %s\n", line); + } while (line); + free(logmsg0); + + if (changed_paths) { + struct got_pathlist_entry *pe; + TAILQ_FOREACH(pe, changed_paths, entry) { + struct got_diff_changed_path *cp = pe->data; + printf(" %c %s\n", cp->status, pe->path); + } + printf("\n"); + } + if (show_patch) { + err = print_patch(commit, id, path, diff_context, repo); + if (err == 0) + printf("\n"); + } + + if (fflush(stdout) != 0 && err == NULL) + err = got_error_from_errno("fflush"); + return err; +} + +static const struct got_error * +print_commits(struct got_object_id *root_id, struct got_object_id *end_id, + struct got_repository *repo, const char *path, int show_changed_paths, + int show_patch, const char *search_pattern, int diff_context, int limit, + int log_branches, int reverse_display_order, struct got_reflist_head *refs) +{ + const struct got_error *err; + struct got_commit_graph *graph; + regex_t regex; + int have_match; + struct got_object_id_queue reversed_commits; + struct got_object_qid *qid; + struct got_commit_object *commit; + struct got_pathlist_head changed_paths; + struct got_pathlist_entry *pe; + + SIMPLEQ_INIT(&reversed_commits); + TAILQ_INIT(&changed_paths); + + if (search_pattern && regcomp(®ex, search_pattern, + REG_EXTENDED | REG_NOSUB | REG_NEWLINE)) + return got_error_msg(GOT_ERR_REGEX, search_pattern); + + err = got_commit_graph_open(&graph, path, !log_branches); + if (err) + return err; + err = got_commit_graph_iter_start(graph, root_id, repo, + check_cancelled, NULL); + if (err) + goto done; + for (;;) { + struct got_object_id *id; + + if (sigint_received || sigpipe_received) + break; + + err = got_commit_graph_iter_next(&id, graph, repo, + check_cancelled, NULL); + if (err) { + if (err->code == GOT_ERR_ITER_COMPLETED) + err = NULL; + break; + } + if (id == NULL) + break; + + err = got_object_open_as_commit(&commit, repo, id); + if (err) + break; + + if (show_changed_paths && !reverse_display_order) { + err = get_changed_paths(&changed_paths, commit, repo); + if (err) + break; + } + + if (search_pattern) { + err = match_logmsg(&have_match, id, commit, ®ex); + if (err) { + got_object_commit_close(commit); + break; + } + if (have_match == 0 && show_changed_paths) + match_changed_paths(&have_match, + &changed_paths, ®ex); + if (have_match == 0) { + got_object_commit_close(commit); + TAILQ_FOREACH(pe, &changed_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&changed_paths); + continue; + } + } + + if (reverse_display_order) { + err = got_object_qid_alloc(&qid, id); + if (err) + break; + SIMPLEQ_INSERT_HEAD(&reversed_commits, qid, entry); + got_object_commit_close(commit); + } else { + err = print_commit(commit, id, repo, path, + show_changed_paths ? &changed_paths : NULL, + show_patch, diff_context, refs); + got_object_commit_close(commit); + if (err) + break; + } + if ((limit && --limit == 0) || + (end_id && got_object_id_cmp(id, end_id) == 0)) + break; + + TAILQ_FOREACH(pe, &changed_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&changed_paths); + } + if (reverse_display_order) { + SIMPLEQ_FOREACH(qid, &reversed_commits, entry) { + err = got_object_open_as_commit(&commit, repo, qid->id); + if (err) + break; + if (show_changed_paths) { + err = get_changed_paths(&changed_paths, + commit, repo); + if (err) + break; + } + err = print_commit(commit, qid->id, repo, path, + show_changed_paths ? &changed_paths : NULL, + show_patch, diff_context, refs); + got_object_commit_close(commit); + if (err) + break; + TAILQ_FOREACH(pe, &changed_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&changed_paths); + } + } +done: + while (!SIMPLEQ_EMPTY(&reversed_commits)) { + qid = SIMPLEQ_FIRST(&reversed_commits); + SIMPLEQ_REMOVE_HEAD(&reversed_commits, entry); + got_object_qid_free(qid); + } + TAILQ_FOREACH(pe, &changed_paths, entry) { + free((char *)pe->path); + free(pe->data); + } + got_pathlist_free(&changed_paths); + if (search_pattern) + regfree(®ex); + got_commit_graph_close(graph); + return err; +} + +__dead static void +usage_log(void) +{ + fprintf(stderr, "usage: %s log [-b] [-c commit] [-C number] [ -l N ] " + "[-p] [-P] [-x commit] [-s search-pattern] [-r repository-path] " + "[-R] [path]\n", getprogname()); + exit(1); +} + +static int +get_default_log_limit(void) +{ + const char *got_default_log_limit; + long long n; + const char *errstr; + + got_default_log_limit = getenv("GOT_LOG_DEFAULT_LIMIT"); + if (got_default_log_limit == NULL) + return 0; + n = strtonum(got_default_log_limit, 0, INT_MAX, &errstr); + if (errstr != NULL) + return 0; + return n; +} + +static const struct got_error * +resolve_commit_arg(struct got_object_id **id, const char *commit_arg, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_reference *ref; + + *id = NULL; + + err = got_ref_open(&ref, repo, commit_arg, 0); + if (err == NULL) { + int obj_type; + err = got_ref_resolve(id, repo, ref); + got_ref_close(ref); + if (err) + return err; + err = got_object_get_type(&obj_type, repo, *id); + if (err) + return err; + if (obj_type == GOT_OBJ_TYPE_TAG) { + struct got_tag_object *tag; + err = got_object_open_as_tag(&tag, repo, *id); + if (err) + return err; + if (got_object_tag_get_object_type(tag) != + GOT_OBJ_TYPE_COMMIT) { + got_object_tag_close(tag); + return got_error(GOT_ERR_OBJ_TYPE); + } + free(*id); + *id = got_object_id_dup( + got_object_tag_get_object_id(tag)); + if (*id == NULL) + err = got_error_from_errno( + "got_object_id_dup"); + got_object_tag_close(tag); + if (err) + return err; + } else if (obj_type != GOT_OBJ_TYPE_COMMIT) + return got_error(GOT_ERR_OBJ_TYPE); + } else { + err = got_repo_match_object_id_prefix(id, commit_arg, + GOT_OBJ_TYPE_COMMIT, repo); + } + + return err; +} + +static const struct got_error * +cmd_log(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + struct got_object_id *start_id = NULL, *end_id = NULL; + char *repo_path = NULL, *path = NULL, *cwd = NULL, *in_repo_path = NULL; + const char *start_commit = NULL, *end_commit = NULL; + const char *search_pattern = NULL; + int diff_context = -1, ch; + int show_changed_paths = 0, show_patch = 0, limit = 0, log_branches = 0; + int reverse_display_order = 0; + const char *errstr; + struct got_reflist_head refs; + + SIMPLEQ_INIT(&refs); + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) + == -1) + err(1, "pledge"); +#endif + + limit = get_default_log_limit(); + + while ((ch = getopt(argc, argv, "bpPc:C:l:r:Rs:x:")) != -1) { + switch (ch) { + case 'p': + show_patch = 1; + break; + case 'P': + show_changed_paths = 1; + break; + case 'c': + start_commit = optarg; + break; + case 'C': + diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT, + &errstr); + if (errstr != NULL) + err(1, "-C option %s", errstr); + break; + case 'l': + limit = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + err(1, "-l option %s", errstr); + break; + case 'b': + log_branches = 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 'R': + reverse_display_order = 1; + break; + case 's': + search_pattern = optarg; + break; + case 'x': + end_commit = optarg; + break; + default: + usage_log(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (diff_context == -1) + diff_context = 3; + else if (!show_patch) + errx(1, "-C requires -p"); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + error = NULL; + } + + if (argc == 0) { + path = strdup(""); + if (path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } else if (argc == 1) { + if (worktree) { + error = got_worktree_resolve_path(&path, worktree, + argv[0]); + if (error) + goto done; + } else { + path = strdup(argv[0]); + if (path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } else + usage_log(); + + if (repo_path == NULL) { + repo_path = worktree ? + strdup(got_worktree_get_repo_path(worktree)) : strdup(cwd); + } + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (start_commit == NULL) { + struct got_reference *head_ref; + struct got_commit_object *commit = NULL; + error = got_ref_open(&head_ref, repo, + worktree ? got_worktree_get_head_ref_name(worktree) + : GOT_REF_HEAD, 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&start_id, repo, head_ref); + got_ref_close(head_ref); + if (error != NULL) + goto done; + error = got_object_open_as_commit(&commit, repo, + start_id); + if (error != NULL) + goto done; + got_object_commit_close(commit); + } else { + error = resolve_commit_arg(&start_id, start_commit, repo); + if (error != NULL) + goto done; + } + if (end_commit != NULL) { + error = resolve_commit_arg(&end_id, end_commit, repo); + if (error != NULL) + goto done; + } + + if (worktree) { + const char *prefix = got_worktree_get_path_prefix(worktree); + char *p; + if (asprintf(&p, "%s%s%s", prefix, + (strcmp(prefix, "/") != 0) ? "/" : "", path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + error = got_repo_map_path(&in_repo_path, repo, p, 0); + free(p); + } else + error = got_repo_map_path(&in_repo_path, repo, path, 1); + if (error != NULL) + goto done; + if (in_repo_path) { + free(path); + path = in_repo_path; + } + + error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL); + if (error) + goto done; + + error = print_commits(start_id, end_id, repo, path, show_changed_paths, + show_patch, search_pattern, diff_context, limit, log_branches, + reverse_display_order, &refs); +done: + free(path); + free(repo_path); + free(cwd); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + got_ref_list_free(&refs); + return error; +} + +__dead static void +usage_diff(void) +{ + fprintf(stderr, "usage: %s diff [-C number] [-r repository-path] [-s] " + "[-w] [object1 object2 | path]\n", getprogname()); + exit(1); +} + +struct print_diff_arg { + struct got_repository *repo; + struct got_worktree *worktree; + int diff_context; + const char *id_str; + int header_shown; + int diff_staged; + int ignore_whitespace; +}; + +/* + * Create a file which contains the target path of a symlink so we can feed + * it as content to the diff engine. + */ +static const struct got_error * +get_symlink_target_file(int *fd, int dirfd, const char *de_name, + const char *abspath) +{ + const struct got_error *err = NULL; + char target_path[PATH_MAX]; + ssize_t target_len, outlen; + + *fd = -1; + + if (dirfd != -1) { + target_len = readlinkat(dirfd, de_name, target_path, PATH_MAX); + if (target_len == -1) + return got_error_from_errno2("readlinkat", abspath); + } else { + target_len = readlink(abspath, target_path, PATH_MAX); + if (target_len == -1) + return got_error_from_errno2("readlink", abspath); + } + + *fd = got_opentempfd(); + if (*fd == -1) + return got_error_from_errno("got_opentempfd"); + + outlen = write(*fd, target_path, target_len); + if (outlen == -1) { + err = got_error_from_errno("got_opentempfd"); + goto done; + } + + if (lseek(*fd, 0, SEEK_SET) == -1) { + err = got_error_from_errno2("lseek", abspath); + goto done; + } +done: + if (err) { + close(*fd); + *fd = -1; + } + return err; +} + +static const struct got_error * +print_diff(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) +{ + struct print_diff_arg *a = arg; + const struct got_error *err = NULL; + struct got_blob_object *blob1 = NULL; + int fd = -1; + FILE *f2 = NULL; + char *abspath = NULL, *label1 = NULL; + struct stat sb; + + if (a->diff_staged) { + if (staged_status != GOT_STATUS_MODIFY && + staged_status != GOT_STATUS_ADD && + staged_status != GOT_STATUS_DELETE) + return NULL; + } else { + if (staged_status == GOT_STATUS_DELETE) + return NULL; + if (status == GOT_STATUS_NONEXISTENT) + return got_error_set_errno(ENOENT, path); + if (status != GOT_STATUS_MODIFY && + status != GOT_STATUS_ADD && + status != GOT_STATUS_DELETE && + status != GOT_STATUS_CONFLICT) + return NULL; + } + + if (!a->header_shown) { + printf("diff %s %s%s\n", a->id_str, + got_worktree_get_root_path(a->worktree), + a->diff_staged ? " (staged changes)" : ""); + a->header_shown = 1; + } + + if (a->diff_staged) { + const char *label1 = NULL, *label2 = NULL; + switch (staged_status) { + case GOT_STATUS_MODIFY: + label1 = path; + label2 = path; + break; + case GOT_STATUS_ADD: + label2 = path; + break; + case GOT_STATUS_DELETE: + label1 = path; + break; + default: + return got_error(GOT_ERR_FILE_STATUS); + } + return got_diff_objects_as_blobs(blob_id, staged_blob_id, + label1, label2, a->diff_context, a->ignore_whitespace, + a->repo, stdout); + } + + if (staged_status == GOT_STATUS_ADD || + staged_status == GOT_STATUS_MODIFY) { + char *id_str; + err = got_object_open_as_blob(&blob1, a->repo, staged_blob_id, + 8192); + if (err) + goto done; + err = got_object_id_str(&id_str, staged_blob_id); + if (err) + goto done; + if (asprintf(&label1, "%s (staged)", id_str) == -1) { + err = got_error_from_errno("asprintf"); + free(id_str); + goto done; + } + free(id_str); + } else if (status != GOT_STATUS_ADD) { + err = got_object_open_as_blob(&blob1, a->repo, blob_id, 8192); + if (err) + goto done; + } + + if (status != GOT_STATUS_DELETE) { + if (asprintf(&abspath, "%s/%s", + got_worktree_get_root_path(a->worktree), path) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + if (dirfd != -1) { + fd = openat(dirfd, de_name, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (errno != ELOOP) { + err = got_error_from_errno2("openat", + abspath); + goto done; + } + err = get_symlink_target_file(&fd, dirfd, + de_name, abspath); + if (err) + goto done; + } + } else { + fd = open(abspath, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (errno != ELOOP) { + err = got_error_from_errno2("open", + abspath); + goto done; + } + err = get_symlink_target_file(&fd, dirfd, + de_name, abspath); + if (err) + goto done; + } + } + if (fstat(fd, &sb) == -1) { + err = got_error_from_errno2("fstat", abspath); + goto done; + } + f2 = fdopen(fd, "r"); + if (f2 == NULL) { + err = got_error_from_errno2("fdopen", abspath); + goto done; + } + fd = -1; + } else + sb.st_size = 0; + + err = got_diff_blob_file(blob1, label1, f2, sb.st_size, path, + a->diff_context, a->ignore_whitespace, stdout); +done: + if (blob1) + got_object_blob_close(blob1); + if (f2 && fclose(f2) == EOF && err == NULL) + err = got_error_from_errno("fclose"); + if (fd != -1 && close(fd) == -1 && err == NULL) + err = got_error_from_errno("close"); + free(abspath); + return err; +} + +static const struct got_error * +cmd_diff(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *repo_path = NULL; + struct got_object_id *id1 = NULL, *id2 = NULL; + const char *id_str1 = NULL, *id_str2 = NULL; + char *label1 = NULL, *label2 = NULL; + int type1, type2; + int diff_context = 3, diff_staged = 0, ignore_whitespace = 0, ch; + const char *errstr; + char *path = NULL; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "C:r:sw")) != -1) { + switch (ch) { + case 'C': + diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT, + &errstr); + if (errstr != NULL) + err(1, "-C option %s", errstr); + 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 's': + diff_staged = 1; + break; + case 'w': + ignore_whitespace = 1; + break; + default: + usage_diff(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + if (argc <= 1) { + if (repo_path) + errx(1, + "-r option can't be used when diffing a work tree"); + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "diff", + cwd); + goto done; + } + repo_path = strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + if (argc == 1) { + error = got_worktree_resolve_path(&path, worktree, + argv[0]); + if (error) + goto done; + } else { + path = strdup(""); + if (path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } else if (argc == 2) { + if (diff_staged) + errx(1, "-s option can't be used when diffing " + "objects in repository"); + id_str1 = argv[0]; + id_str2 = argv[1]; + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + if (worktree) { + repo_path = strdup( + got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + } else + usage_diff(); + + error = got_repo_open(&repo, repo_path, NULL); + free(repo_path); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (argc <= 1) { + struct print_diff_arg arg; + struct got_pathlist_head paths; + char *id_str; + + TAILQ_INIT(&paths); + + error = got_object_id_str(&id_str, + got_worktree_get_base_commit_id(worktree)); + if (error) + goto done; + arg.repo = repo; + arg.worktree = worktree; + arg.diff_context = diff_context; + arg.id_str = id_str; + arg.header_shown = 0; + arg.diff_staged = diff_staged; + arg.ignore_whitespace = ignore_whitespace; + + error = got_pathlist_append(&paths, path, NULL); + if (error) + goto done; + + error = got_worktree_status(worktree, &paths, repo, print_diff, + &arg, check_cancelled, NULL); + free(id_str); + got_pathlist_free(&paths); + goto done; + } + + error = got_repo_match_object_id(&id1, &label1, id_str1, + GOT_OBJ_TYPE_ANY, 1, repo); + if (error) + goto done; + + error = got_repo_match_object_id(&id2, &label2, id_str2, + GOT_OBJ_TYPE_ANY, 1, repo); + if (error) + goto done; + + error = got_object_get_type(&type1, repo, id1); + if (error) + goto done; + + error = got_object_get_type(&type2, repo, id2); + if (error) + goto done; + + if (type1 != type2) { + error = got_error(GOT_ERR_OBJ_TYPE); + goto done; + } + + switch (type1) { + case GOT_OBJ_TYPE_BLOB: + error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, + diff_context, ignore_whitespace, repo, stdout); + break; + case GOT_OBJ_TYPE_TREE: + error = got_diff_objects_as_trees(id1, id2, "", "", + diff_context, ignore_whitespace, repo, stdout); + break; + case GOT_OBJ_TYPE_COMMIT: + printf("diff %s %s\n", label1, label2); + error = got_diff_objects_as_commits(id1, id2, diff_context, + ignore_whitespace, repo, stdout); + break; + default: + error = got_error(GOT_ERR_OBJ_TYPE); + } +done: + free(label1); + free(label2); + free(id1); + free(id2); + free(path); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + return error; +} + +__dead static void +usage_blame(void) +{ + fprintf(stderr, + "usage: %s blame [-c commit] [-r repository-path] path\n", + getprogname()); + exit(1); +} + +struct blame_line { + int annotated; + char *id_str; + char *committer; + char datebuf[11]; /* YYYY-MM-DD + NUL */ +}; + +struct blame_cb_args { + struct blame_line *lines; + int nlines; + int nlines_prec; + int lineno_cur; + off_t *line_offsets; + FILE *f; + struct got_repository *repo; +}; + +static const struct got_error * +blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id) +{ + const struct got_error *err = NULL; + struct blame_cb_args *a = arg; + struct blame_line *bline; + char *line = NULL; + size_t linesize = 0; + struct got_commit_object *commit = NULL; + off_t offset; + struct tm tm; + time_t committer_time; + + if (nlines != a->nlines || + (lineno != -1 && lineno < 1) || lineno > a->nlines) + return got_error(GOT_ERR_RANGE); + + if (sigint_received) + return got_error(GOT_ERR_ITER_COMPLETED); + + if (lineno == -1) + return NULL; /* no change in this commit */ + + /* Annotate this line. */ + bline = &a->lines[lineno - 1]; + if (bline->annotated) + return NULL; + err = got_object_id_str(&bline->id_str, id); + if (err) + return err; + + err = got_object_open_as_commit(&commit, a->repo, id); + if (err) + goto done; + + bline->committer = strdup(got_object_commit_get_committer(commit)); + if (bline->committer == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + + committer_time = got_object_commit_get_committer_time(commit); + if (localtime_r(&committer_time, &tm) == NULL) + return got_error_from_errno("localtime_r"); + if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d", + &tm) >= sizeof(bline->datebuf)) { + err = got_error(GOT_ERR_NO_SPACE); + goto done; + } + bline->annotated = 1; + + /* Print lines annotated so far. */ + bline = &a->lines[a->lineno_cur - 1]; + if (!bline->annotated) + goto done; + + offset = a->line_offsets[a->lineno_cur - 1]; + if (fseeko(a->f, offset, SEEK_SET) == -1) { + err = got_error_from_errno("fseeko"); + goto done; + } + + while (bline->annotated) { + char *smallerthan, *at, *nl, *committer; + size_t len; + + if (getline(&line, &linesize, a->f) == -1) { + if (ferror(a->f)) + err = got_error_from_errno("getline"); + break; + } + + committer = bline->committer; + smallerthan = strchr(committer, '<'); + if (smallerthan && smallerthan[1] != '\0') + committer = smallerthan + 1; + at = strchr(committer, '@'); + if (at) + *at = '\0'; + len = strlen(committer); + if (len >= 9) + committer[8] = '\0'; + + nl = strchr(line, '\n'); + if (nl) + *nl = '\0'; + printf("%.*d) %.8s %s %-8s %s\n", a->nlines_prec, a->lineno_cur, + bline->id_str, bline->datebuf, committer, line); + + a->lineno_cur++; + bline = &a->lines[a->lineno_cur - 1]; + } +done: + if (commit) + got_object_commit_close(commit); + free(line); + return err; +} + +static const struct got_error * +cmd_blame(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL; + char *link_target = NULL; + struct got_object_id *obj_id = NULL; + struct got_object_id *commit_id = NULL; + struct got_blob_object *blob = NULL; + char *commit_id_str = NULL; + struct blame_cb_args bca; + int ch, obj_type, i; + size_t filesize; + + memset(&bca, 0, sizeof(bca)); + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "c:r:")) != -1) { + switch (ch) { + case 'c': + commit_id_str = optarg; + break; + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) + return got_error_from_errno2("realpath", + optarg); + got_path_strip_trailing_slashes(repo_path); + break; + default: + usage_blame(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc == 1) + path = argv[0]; + else + usage_blame(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + if (error) + goto done; + } + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + + if (worktree) { + const char *prefix = got_worktree_get_path_prefix(worktree); + char *p, *worktree_subdir = cwd + + strlen(got_worktree_get_root_path(worktree)); + if (asprintf(&p, "%s%s%s%s%s", + prefix, (strcmp(prefix, "/") != 0) ? "/" : "", + worktree_subdir, worktree_subdir[0] ? "/" : "", + path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + error = got_repo_map_path(&in_repo_path, repo, p, 0); + free(p); + } else { + error = got_repo_map_path(&in_repo_path, repo, path, 1); + } + if (error) + goto done; + + if (commit_id_str == NULL) { + struct got_reference *head_ref; + error = got_ref_open(&head_ref, repo, worktree ? + got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD, 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, head_ref); + got_ref_close(head_ref); + if (error != NULL) + goto done; + } else { + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + if (error) + goto done; + } + + error = got_object_resolve_symlinks(&link_target, in_repo_path, + commit_id, repo); + if (error) + goto done; + + error = got_object_id_by_path(&obj_id, repo, commit_id, + link_target ? link_target : in_repo_path); + if (error) + goto done; + + error = got_object_get_type(&obj_type, repo, obj_id); + if (error) + goto done; + + if (obj_type != GOT_OBJ_TYPE_BLOB) { + error = got_error_path(link_target ? link_target : in_repo_path, + GOT_ERR_OBJ_TYPE); + goto done; + } + + error = got_object_open_as_blob(&blob, repo, obj_id, 8192); + if (error) + goto done; + bca.f = got_opentemp(); + if (bca.f == NULL) { + error = got_error_from_errno("got_opentemp"); + goto done; + } + error = got_object_blob_dump_to_file(&filesize, &bca.nlines, + &bca.line_offsets, bca.f, blob); + if (error || bca.nlines == 0) + goto done; + + /* Don't include \n at EOF in the blame line count. */ + if (bca.line_offsets[bca.nlines - 1] == filesize) + bca.nlines--; + + bca.lines = calloc(bca.nlines, sizeof(*bca.lines)); + if (bca.lines == NULL) { + error = got_error_from_errno("calloc"); + goto done; + } + bca.lineno_cur = 1; + bca.nlines_prec = 0; + i = bca.nlines; + while (i > 0) { + i /= 10; + bca.nlines_prec++; + } + bca.repo = repo; + + error = got_blame(link_target ? link_target : in_repo_path, commit_id, + repo, blame_cb, &bca, check_cancelled, NULL); +done: + free(in_repo_path); + free(link_target); + free(repo_path); + free(cwd); + free(commit_id); + free(obj_id); + if (blob) + got_object_blob_close(blob); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + if (bca.lines) { + for (i = 0; i < bca.nlines; i++) { + struct blame_line *bline = &bca.lines[i]; + free(bline->id_str); + free(bline->committer); + } + free(bca.lines); + } + free(bca.line_offsets); + if (bca.f && fclose(bca.f) == EOF && error == NULL) + error = got_error_from_errno("fclose"); + return error; +} + +__dead static void +usage_tree(void) +{ + fprintf(stderr, + "usage: %s tree [-c commit] [-r repository-path] [-iR] [path]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +print_entry(struct got_tree_entry *te, const char *id, const char *path, + const char *root_path, struct got_repository *repo) +{ + const struct got_error *err = NULL; + int is_root_path = (strcmp(path, root_path) == 0); + const char *modestr = ""; + mode_t mode = got_tree_entry_get_mode(te); + char *link_target = NULL; + + path += strlen(root_path); + while (path[0] == '/') + path++; + + if (got_object_tree_entry_is_submodule(te)) + modestr = "$"; + else if (S_ISLNK(mode)) { + int i; + + err = got_tree_entry_get_symlink_target(&link_target, te, repo); + if (err) + return err; + for (i = 0; i < strlen(link_target); i++) { + if (!isprint((unsigned char)link_target[i])) + link_target[i] = '?'; + } + + modestr = "@"; + } + else if (S_ISDIR(mode)) + modestr = "/"; + else if (mode & S_IXUSR) + modestr = "*"; + + printf("%s%s%s%s%s%s%s\n", id ? id : "", path, + is_root_path ? "" : "/", got_tree_entry_get_name(te), modestr, + link_target ? " -> ": "", link_target ? link_target : ""); + + free(link_target); + return NULL; +} + +static const struct got_error * +print_tree(const char *path, struct got_object_id *commit_id, + int show_ids, int recurse, const char *root_path, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_id *tree_id = NULL; + struct got_tree_object *tree = NULL; + int nentries, i; + + err = got_object_id_by_path(&tree_id, repo, commit_id, path); + if (err) + goto done; + + err = got_object_open_as_tree(&tree, repo, tree_id); + if (err) + goto done; + nentries = got_object_tree_get_nentries(tree); + for (i = 0; i < nentries; i++) { + struct got_tree_entry *te; + char *id = NULL; + + if (sigint_received || sigpipe_received) + break; + + te = got_object_tree_get_entry(tree, i); + if (show_ids) { + char *id_str; + err = got_object_id_str(&id_str, + got_tree_entry_get_id(te)); + if (err) + goto done; + if (asprintf(&id, "%s ", id_str) == -1) { + err = got_error_from_errno("asprintf"); + free(id_str); + goto done; + } + free(id_str); + } + err = print_entry(te, id, path, root_path, repo); + free(id); + if (err) + goto done; + + if (recurse && S_ISDIR(got_tree_entry_get_mode(te))) { + char *child_path; + if (asprintf(&child_path, "%s%s%s", path, + path[0] == '/' && path[1] == '\0' ? "" : "/", + got_tree_entry_get_name(te)) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + err = print_tree(child_path, commit_id, show_ids, 1, + root_path, repo); + free(child_path); + if (err) + goto done; + } + } +done: + if (tree) + got_object_tree_close(tree); + free(tree_id); + return err; +} + +static const struct got_error * +cmd_tree(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + const char *path, *refname = NULL; + char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL; + struct got_object_id *commit_id = NULL; + char *commit_id_str = NULL; + int show_ids = 0, recurse = 0; + int ch; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "c:r:iR")) != -1) { + switch (ch) { + case 'c': + commit_id_str = optarg; + break; + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) + return got_error_from_errno2("realpath", + optarg); + got_path_strip_trailing_slashes(repo_path); + break; + case 'i': + show_ids = 1; + break; + case 'R': + recurse = 1; + break; + default: + usage_tree(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc == 1) + path = argv[0]; + else if (argc > 1) + usage_tree(); + else + path = NULL; + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + + if (path == NULL) { + if (worktree) { + char *p, *worktree_subdir = cwd + + strlen(got_worktree_get_root_path(worktree)); + if (asprintf(&p, "%s/%s", + got_worktree_get_path_prefix(worktree), + worktree_subdir) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + error = got_repo_map_path(&in_repo_path, repo, p, 0); + free(p); + if (error) + goto done; + } else + path = "/"; + } + if (in_repo_path == NULL) { + error = got_repo_map_path(&in_repo_path, repo, path, 1); + if (error != NULL) + goto done; + } + + if (commit_id_str == NULL) { + struct got_reference *head_ref; + if (worktree) + refname = got_worktree_get_head_ref_name(worktree); + else + refname = GOT_REF_HEAD; + error = got_ref_open(&head_ref, repo, refname, 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, head_ref); + got_ref_close(head_ref); + if (error != NULL) + goto done; + } else { + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + if (error) + goto done; + } + + error = print_tree(in_repo_path, commit_id, show_ids, recurse, + in_repo_path, repo); +done: + free(in_repo_path); + free(repo_path); + free(cwd); + free(commit_id); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + return error; +} + +__dead static void +usage_status(void) +{ + fprintf(stderr, "usage: %s status [-s status-codes ] [path ...]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +print_status(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) +{ + if (status == staged_status && (status == GOT_STATUS_DELETE)) + status = GOT_STATUS_NO_CHANGE; + if (arg) { + char *status_codes = arg; + size_t ncodes = strlen(status_codes); + int i; + for (i = 0; i < ncodes ; i++) { + if (status == status_codes[i] || + staged_status == status_codes[i]) + break; + } + if (i == ncodes) + return NULL; + } + printf("%c%c %s\n", status, staged_status, path); + return NULL; +} + +static const struct got_error * +cmd_status(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *status_codes = NULL;; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch, i; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "s:")) != -1) { + switch (ch) { + case 's': + for (i = 0; i < strlen(optarg); i++) { + switch (optarg[i]) { + case GOT_STATUS_MODIFY: + case GOT_STATUS_ADD: + case GOT_STATUS_DELETE: + case GOT_STATUS_CONFLICT: + case GOT_STATUS_MISSING: + case GOT_STATUS_OBSTRUCTED: + case GOT_STATUS_UNVERSIONED: + case GOT_STATUS_MODE_CHANGE: + case GOT_STATUS_NONEXISTENT: + break; + default: + errx(1, "invalid status code '%c'", + optarg[i]); + } + } + status_codes = optarg; + break; + default: + usage_status(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "status", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + error = got_worktree_status(worktree, &paths, repo, print_status, + status_codes, check_cancelled, NULL); +done: + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_ref(void) +{ + fprintf(stderr, + "usage: %s ref [-r repository] [-l] [-c object] [-s reference] " + "[-d] [name]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +list_refs(struct got_repository *repo, const char *refname) +{ + static const struct got_error *err = NULL; + struct got_reflist_head refs; + struct got_reflist_entry *re; + + SIMPLEQ_INIT(&refs); + err = got_ref_list(&refs, repo, refname, got_ref_cmp_by_name, NULL); + if (err) + return err; + + SIMPLEQ_FOREACH(re, &refs, entry) { + char *refstr; + refstr = got_ref_to_str(re->ref); + if (refstr == NULL) + return got_error_from_errno("got_ref_to_str"); + printf("%s: %s\n", got_ref_get_name(re->ref), refstr); + free(refstr); + } + + got_ref_list_free(&refs); + return NULL; +} + +static const struct got_error * +delete_ref(struct got_repository *repo, const char *refname) +{ + const struct got_error *err = NULL; + struct got_reference *ref; + + err = got_ref_open(&ref, repo, refname, 0); + if (err) + return err; + + err = got_ref_delete(ref, repo); + got_ref_close(ref); + return err; +} + +static const struct got_error * +add_ref(struct got_repository *repo, const char *refname, const char *target) +{ + const struct got_error *err = NULL; + struct got_object_id *id; + struct got_reference *ref = NULL; + + /* + * Don't let the user create a reference name with a leading '-'. + * While technically a valid reference name, this case is usually + * an unintended typo. + */ + if (refname[0] == '-') + return got_error_path(refname, GOT_ERR_REF_NAME_MINUS); + + err = got_repo_match_object_id_prefix(&id, target, GOT_OBJ_TYPE_ANY, + repo); + if (err) { + struct got_reference *target_ref; + + if (err->code != GOT_ERR_BAD_OBJ_ID_STR) + return err; + err = got_ref_open(&target_ref, repo, target, 0); + if (err) + return err; + err = got_ref_resolve(&id, repo, target_ref); + got_ref_close(target_ref); + if (err) + return err; + } + + err = got_ref_alloc(&ref, refname, id); + if (err) + goto done; + + err = got_ref_write(ref, repo); +done: + if (ref) + got_ref_close(ref); + free(id); + return err; +} + +static const struct got_error * +add_symref(struct got_repository *repo, const char *refname, const char *target) +{ + const struct got_error *err = NULL; + struct got_reference *ref = NULL; + struct got_reference *target_ref = NULL; + + /* + * Don't let the user create a reference name with a leading '-'. + * While technically a valid reference name, this case is usually + * an unintended typo. + */ + if (refname[0] == '-') + return got_error_path(refname, GOT_ERR_REF_NAME_MINUS); + + err = got_ref_open(&target_ref, repo, target, 0); + if (err) + return err; + + err = got_ref_alloc_symref(&ref, refname, target_ref); + if (err) + goto done; + + err = got_ref_write(ref, repo); +done: + if (target_ref) + got_ref_close(target_ref); + if (ref) + got_ref_close(ref); + return err; +} + +static const struct got_error * +cmd_ref(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *repo_path = NULL; + int ch, do_list = 0, do_delete = 0; + const char *obj_arg = NULL, *symref_target= NULL; + char *refname = NULL; + + while ((ch = getopt(argc, argv, "c:dr:ls:")) != -1) { + switch (ch) { + case 'c': + obj_arg = optarg; + break; + case 'd': + do_delete = 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 'l': + do_list = 1; + break; + case 's': + symref_target = optarg; + break; + default: + usage_ref(); + /* NOTREACHED */ + } + } + + if (obj_arg && do_list) + errx(1, "-c and -l options are mutually exclusive"); + if (obj_arg && do_delete) + errx(1, "-c and -d options are mutually exclusive"); + if (obj_arg && symref_target) + errx(1, "-c and -s options are mutually exclusive"); + if (symref_target && do_delete) + errx(1, "-s and -d options are mutually exclusive"); + if (symref_target && do_list) + errx(1, "-s and -l options are mutually exclusive"); + if (do_delete && do_list) + errx(1, "-d and -l options are mutually exclusive"); + + argc -= optind; + argv += optind; + + if (do_list) { + if (argc != 0 && argc != 1) + usage_ref(); + if (argc == 1) { + refname = strdup(argv[0]); + if (refname == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } else { + if (argc != 1) + usage_ref(); + refname = strdup(argv[0]); + if (refname == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + + if (refname) + got_path_strip_trailing_slashes(refname); + +#ifndef PROFILE + if (do_list) { + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath fattr flock proc exec " + "sendfd unveil", NULL) == -1) + err(1, "pledge"); + } +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), do_list, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (do_list) + error = list_refs(repo, refname); + else if (do_delete) + error = delete_ref(repo, refname); + else if (symref_target) + error = add_symref(repo, refname, symref_target); + else { + if (obj_arg == NULL) + usage_ref(); + error = add_ref(repo, refname, obj_arg); + } +done: + free(refname); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(repo_path); + return error; +} + +__dead static void +usage_branch(void) +{ + fprintf(stderr, + "usage: %s branch [-c commit] [-d] [-r repository] [-l] [-n] " + "[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; + + 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) +{ + 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; + + SIMPLEQ_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", + got_ref_cmp_by_name, NULL); + if (err) + return err; + + SIMPLEQ_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; + + if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) + return got_error_from_errno("asprintf"); + + err = got_ref_open(&ref, repo, refname, 0); + if (err) + goto done; + + 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 = got_ref_delete(ref, repo); +done: + if (ref) + got_ref_close(ref); + free(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 *base_refname = NULL, *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 (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(base_refname); + free(refname); + return err; +} + +static const struct got_error * +cmd_branch(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *repo_path = NULL; + int ch, do_list = 0, do_show = 0, do_update = 1; + const char *delref = NULL, *commit_id_arg = NULL; + struct got_reference *ref = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + struct got_object_id *commit_id = NULL; + char *commit_id_str = NULL; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "c:d:r:ln")) != -1) { + switch (ch) { + case 'c': + commit_id_arg = optarg; + break; + case 'd': + delref = optarg; + 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 'l': + do_list = 1; + break; + case 'n': + do_update = 0; + break; + default: + usage_branch(); + /* NOTREACHED */ + } + } + + if (do_list && delref) + errx(1, "-l and -d options are mutually exclusive"); + + 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(); + +#ifndef PROFILE + if (do_list || do_show) { + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath fattr flock proc exec " + "sendfd unveil", NULL) == -1) + err(1, "pledge"); + } +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), do_list, + worktree ? got_worktree_get_root_path(worktree) : NULL); + if (error) + goto done; + + if (do_show) + error = show_current_branch(repo, worktree); + else if (do_list) + error = list_branches(repo, worktree); + else if (delref) + error = delete_branch(repo, worktree, delref); + else { + 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, 1, repo); + 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 commit %s\n", commit_id_str); + print_update_progress_stats(&upa); + } + } +done: + if (ref) + got_ref_close(ref); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(repo_path); + free(commit_id); + free(commit_id_str); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + return error; +} + + +__dead static void +usage_tag(void) +{ + fprintf(stderr, + "usage: %s tag [-c commit] [-r repository] [-l] " + "[-m message] name\n", getprogname()); + exit(1); +} + +#if 0 +static const struct got_error * +sort_tags(struct got_reflist_head *sorted, struct got_reflist_head *tags) +{ + const struct got_error *err = NULL; + struct got_reflist_entry *re, *se, *new; + struct got_object_id *re_id, *se_id; + struct got_tag_object *re_tag, *se_tag; + time_t re_time, se_time; + + SIMPLEQ_FOREACH(re, tags, entry) { + se = SIMPLEQ_FIRST(sorted); + if (se == NULL) { + err = got_reflist_entry_dup(&new, re); + if (err) + return err; + SIMPLEQ_INSERT_HEAD(sorted, new, entry); + continue; + } else { + err = got_ref_resolve(&re_id, repo, re->ref); + if (err) + break; + err = got_object_open_as_tag(&re_tag, repo, re_id); + free(re_id); + if (err) + break; + re_time = got_object_tag_get_tagger_time(re_tag); + got_object_tag_close(re_tag); + } + + while (se) { + err = got_ref_resolve(&se_id, repo, re->ref); + if (err) + break; + err = got_object_open_as_tag(&se_tag, repo, se_id); + free(se_id); + if (err) + break; + se_time = got_object_tag_get_tagger_time(se_tag); + got_object_tag_close(se_tag); + + if (se_time > re_time) { + err = got_reflist_entry_dup(&new, re); + if (err) + return err; + SIMPLEQ_INSERT_AFTER(sorted, se, new, entry); + break; + } + se = SIMPLEQ_NEXT(se, entry); + continue; + } + } +done: + return err; +} +#endif + +static const struct got_error * +list_tags(struct got_repository *repo, struct got_worktree *worktree) +{ + static const struct got_error *err = NULL; + struct got_reflist_head refs; + struct got_reflist_entry *re; + + SIMPLEQ_INIT(&refs); + + err = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo); + if (err) + return err; + + SIMPLEQ_FOREACH(re, &refs, entry) { + const char *refname; + char *refstr, *tagmsg0, *tagmsg, *line, *id_str, *datestr; + char datebuf[26]; + const char *tagger; + time_t tagger_time; + struct got_object_id *id; + struct got_tag_object *tag; + struct got_commit_object *commit = NULL; + + refname = got_ref_get_name(re->ref); + if (strncmp(refname, "refs/tags/", 10) != 0) + continue; + refname += 10; + refstr = got_ref_to_str(re->ref); + if (refstr == NULL) { + err = got_error_from_errno("got_ref_to_str"); + break; + } + printf("%stag %s %s\n", GOT_COMMIT_SEP_STR, refname, refstr); + free(refstr); + + err = got_ref_resolve(&id, repo, re->ref); + if (err) + break; + err = got_object_open_as_tag(&tag, repo, id); + if (err) { + if (err->code != GOT_ERR_OBJ_TYPE) { + free(id); + break; + } + /* "lightweight" tag */ + err = got_object_open_as_commit(&commit, repo, id); + if (err) { + free(id); + break; + } + tagger = got_object_commit_get_committer(commit); + tagger_time = + got_object_commit_get_committer_time(commit); + err = got_object_id_str(&id_str, id); + free(id); + if (err) + break; + } else { + free(id); + tagger = got_object_tag_get_tagger(tag); + tagger_time = got_object_tag_get_tagger_time(tag); + err = got_object_id_str(&id_str, + got_object_tag_get_object_id(tag)); + if (err) + break; + } + printf("from: %s\n", tagger); + datestr = get_datestr(&tagger_time, datebuf); + if (datestr) + printf("date: %s UTC\n", datestr); + if (commit) + printf("object: %s %s\n", GOT_OBJ_LABEL_COMMIT, id_str); + else { + switch (got_object_tag_get_object_type(tag)) { + case GOT_OBJ_TYPE_BLOB: + printf("object: %s %s\n", GOT_OBJ_LABEL_BLOB, + id_str); + break; + case GOT_OBJ_TYPE_TREE: + printf("object: %s %s\n", GOT_OBJ_LABEL_TREE, + id_str); + break; + case GOT_OBJ_TYPE_COMMIT: + printf("object: %s %s\n", GOT_OBJ_LABEL_COMMIT, + id_str); + break; + case GOT_OBJ_TYPE_TAG: + printf("object: %s %s\n", GOT_OBJ_LABEL_TAG, + id_str); + break; + default: + break; + } + } + free(id_str); + if (commit) { + err = got_object_commit_get_logmsg(&tagmsg0, commit); + if (err) + break; + got_object_commit_close(commit); + } else { + tagmsg0 = strdup(got_object_tag_get_message(tag)); + got_object_tag_close(tag); + if (tagmsg0 == NULL) { + err = got_error_from_errno("strdup"); + break; + } + } + + tagmsg = tagmsg0; + do { + line = strsep(&tagmsg, "\n"); + if (line) + printf(" %s\n", line); + } while (line); + free(tagmsg0); + } + + got_ref_list_free(&refs); + return NULL; +} + +static const struct got_error * +get_tag_message(char **tagmsg, char **tagmsg_path, const char *commit_id_str, + const char *tag_name, const char *repo_path) +{ + const struct got_error *err = NULL; + char *template = NULL, *initial_content = NULL; + char *editor = NULL; + int initial_content_len; + int fd = -1; + + if (asprintf(&template, GOT_TMPDIR_STR "/got-tagmsg") == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + initial_content_len = asprintf(&initial_content, + "\n# tagging commit %s as %s\n", + commit_id_str, tag_name); + if (initial_content_len == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + err = got_opentemp_named_fd(tagmsg_path, &fd, template); + if (err) + goto done; + + if (write(fd, initial_content, initial_content_len) == -1) { + err = got_error_from_errno2("write", *tagmsg_path); + goto done; + } + + err = get_editor(&editor); + if (err) + goto done; + err = edit_logmsg(tagmsg, editor, *tagmsg_path, initial_content); +done: + free(initial_content); + free(template); + free(editor); + + if (fd != -1 && close(fd) == -1 && err == NULL) + err = got_error_from_errno2("close", *tagmsg_path); + + /* Editor is done; we can now apply unveil(2) */ + if (err == NULL) + err = apply_unveil(repo_path, 0, NULL); + if (err) { + free(*tagmsg); + *tagmsg = NULL; + } + return err; +} + +static const struct got_error * +add_tag(struct got_repository *repo, struct got_worktree *worktree, + const char *tag_name, const char *commit_arg, const char *tagmsg_arg) +{ + const struct got_error *err = NULL; + struct got_object_id *commit_id = NULL, *tag_id = NULL; + char *label = NULL, *commit_id_str = NULL; + struct got_reference *ref = NULL; + char *refname = NULL, *tagmsg = NULL, *tagger = NULL; + char *tagmsg_path = NULL, *tag_id_str = NULL; + int preserve_tagmsg = 0; + + /* + * Don't let the user create a tag name with a leading '-'. + * While technically a valid reference name, this case is usually + * an unintended typo. + */ + if (tag_name[0] == '-') + return got_error_path(tag_name, GOT_ERR_REF_NAME_MINUS); + + err = get_author(&tagger, repo, worktree); + if (err) + return err; + + err = got_repo_match_object_id(&commit_id, &label, commit_arg, + GOT_OBJ_TYPE_COMMIT, 1, repo); + if (err) + goto done; + + err = got_object_id_str(&commit_id_str, commit_id); + if (err) + goto done; + + if (strncmp("refs/tags/", tag_name, 10) == 0) { + refname = strdup(tag_name); + if (refname == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + tag_name += 10; + } else if (asprintf(&refname, "refs/tags/%s", tag_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_TAG_EXISTS); + goto done; + } else if (err->code != GOT_ERR_NOT_REF) + goto done; + + if (tagmsg_arg == NULL) { + err = get_tag_message(&tagmsg, &tagmsg_path, commit_id_str, + tag_name, got_repo_get_path(repo)); + if (err) { + if (err->code != GOT_ERR_COMMIT_MSG_EMPTY && + tagmsg_path != NULL) + preserve_tagmsg = 1; + goto done; + } + } + + err = got_object_tag_create(&tag_id, tag_name, commit_id, + tagger, time(NULL), tagmsg ? tagmsg : tagmsg_arg, repo); + if (err) { + if (tagmsg_path) + preserve_tagmsg = 1; + goto done; + } + + err = got_ref_alloc(&ref, refname, tag_id); + if (err) { + if (tagmsg_path) + preserve_tagmsg = 1; + goto done; + } + + err = got_ref_write(ref, repo); + if (err) { + if (tagmsg_path) + preserve_tagmsg = 1; + goto done; + } + + err = got_object_id_str(&tag_id_str, tag_id); + if (err) { + if (tagmsg_path) + preserve_tagmsg = 1; + goto done; + } + printf("Created tag %s\n", tag_id_str); +done: + if (preserve_tagmsg) { + fprintf(stderr, "%s: tag message preserved in %s\n", + getprogname(), tagmsg_path); + } else if (tagmsg_path && unlink(tagmsg_path) == -1 && err == NULL) + err = got_error_from_errno2("unlink", tagmsg_path); + free(tag_id_str); + if (ref) + got_ref_close(ref); + free(commit_id); + free(commit_id_str); + free(refname); + free(tagmsg); + free(tagmsg_path); + free(tagger); + return err; +} + +static const struct got_error * +cmd_tag(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, *commit_id_str = NULL; + char *gitconfig_path = NULL; + const char *tag_name, *commit_id_arg = NULL, *tagmsg = NULL; + int ch, do_list = 0; + + while ((ch = getopt(argc, argv, "c:m:r:l")) != -1) { + switch (ch) { + case 'c': + commit_id_arg = optarg; + break; + case 'm': + tagmsg = optarg; + 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 'l': + do_list = 1; + break; + default: + usage_tag(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (do_list) { + if (commit_id_arg != NULL) + errx(1, + "-c option can only be used when creating a tag"); + if (tagmsg) + errx(1, "-l and -m options are mutually exclusive"); + if (argc > 0) + usage_tag(); + } else if (argc != 1) + usage_tag(); + + tag_name = argv[0]; + +#ifndef PROFILE + if (do_list) { + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath fattr flock proc exec " + "sendfd unveil", NULL) == -1) + err(1, "pledge"); + } +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + if (repo_path == NULL) { + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + else + error = NULL; + if (worktree) { + repo_path = + strdup(got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) + error = got_error_from_errno("strdup"); + if (error) + goto done; + } else { + repo_path = strdup(cwd); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + if (do_list) { + error = got_repo_open(&repo, repo_path, NULL); + if (error != NULL) + goto done; + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + error = list_tags(repo, worktree); + } else { + error = get_gitconfig_path(&gitconfig_path); + if (error) + goto done; + error = got_repo_open(&repo, repo_path, gitconfig_path); + if (error != NULL) + goto done; + + if (tagmsg) { + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + } + + if (commit_id_arg == NULL) { + struct got_reference *head_ref; + struct got_object_id *commit_id; + error = got_ref_open(&head_ref, repo, + worktree ? got_worktree_get_head_ref_name(worktree) + : GOT_REF_HEAD, 0); + if (error) + goto done; + error = got_ref_resolve(&commit_id, repo, head_ref); + got_ref_close(head_ref); + if (error) + goto done; + error = got_object_id_str(&commit_id_str, commit_id); + free(commit_id); + if (error) + goto done; + } + + error = add_tag(repo, worktree, tag_name, + commit_id_str ? commit_id_str : commit_id_arg, tagmsg); + } +done: + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(repo_path); + free(gitconfig_path); + free(commit_id_str); + return error; +} + +__dead static void +usage_add(void) +{ + fprintf(stderr, "usage: %s add [-R] [-I] path ...\n", + getprogname()); + exit(1); +} + +static const struct got_error * +add_progress(void *arg, unsigned char status, const char *path) +{ + while (path[0] == '/') + path++; + printf("%c %s\n", status, path); + return NULL; +} + +static const struct got_error * +cmd_add(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch, can_recurse = 0, no_ignores = 0; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "IR")) != -1) { + switch (ch) { + case 'I': + no_ignores = 1; + break; + case 'R': + can_recurse = 1; + break; + default: + usage_add(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + if (argc < 1) + usage_add(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "add", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + if (!can_recurse && no_ignores) { + error = got_error_msg(GOT_ERR_BAD_PATH, + "disregarding ignores requires -R option"); + goto done; + + } + + if (!can_recurse) { + char *ondisk_path; + struct stat sb; + TAILQ_FOREACH(pe, &paths, entry) { + if (asprintf(&ondisk_path, "%s/%s", + got_worktree_get_root_path(worktree), + pe->path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (lstat(ondisk_path, &sb) == -1) { + if (errno == ENOENT) { + free(ondisk_path); + continue; + } + error = got_error_from_errno2("lstat", + ondisk_path); + free(ondisk_path); + goto done; + } + free(ondisk_path); + if (S_ISDIR(sb.st_mode)) { + error = got_error_msg(GOT_ERR_BAD_PATH, + "adding directories requires -R option"); + goto done; + } + } + } + + error = got_worktree_schedule_add(worktree, &paths, add_progress, + NULL, repo, no_ignores); +done: + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_remove(void) +{ + fprintf(stderr, "usage: %s remove [-f] [-k] [-R] [-s status-codes] " + "path ...\n", getprogname()); + exit(1); +} + +static const struct got_error * +print_remove_status(void *arg, unsigned char status, + unsigned char staged_status, const char *path) +{ + while (path[0] == '/') + path++; + if (status == GOT_STATUS_NONEXISTENT) + return NULL; + if (status == staged_status && (status == GOT_STATUS_DELETE)) + status = GOT_STATUS_NO_CHANGE; + printf("%c%c %s\n", status, staged_status, path); + return NULL; +} + +static const struct got_error * +cmd_remove(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + const char *status_codes = NULL; + char *cwd = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch, delete_local_mods = 0, can_recurse = 0, keep_on_disk = 0, i; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "fkRs:")) != -1) { + switch (ch) { + case 'f': + delete_local_mods = 1; + break; + case 'k': + keep_on_disk = 1; + break; + case 'R': + can_recurse = 1; + break; + case 's': + for (i = 0; i < strlen(optarg); i++) { + switch (optarg[i]) { + case GOT_STATUS_MODIFY: + delete_local_mods = 1; + break; + case GOT_STATUS_MISSING: + break; + default: + errx(1, "invalid status code '%c'", + optarg[i]); + } + } + status_codes = optarg; + break; + default: + usage_remove(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + if (argc < 1) + usage_remove(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "remove", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + if (!can_recurse) { + char *ondisk_path; + struct stat sb; + TAILQ_FOREACH(pe, &paths, entry) { + if (asprintf(&ondisk_path, "%s/%s", + got_worktree_get_root_path(worktree), + pe->path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (lstat(ondisk_path, &sb) == -1) { + if (errno == ENOENT) { + free(ondisk_path); + continue; + } + error = got_error_from_errno2("lstat", + ondisk_path); + free(ondisk_path); + goto done; + } + free(ondisk_path); + if (S_ISDIR(sb.st_mode)) { + error = got_error_msg(GOT_ERR_BAD_PATH, + "removing directories requires -R option"); + goto done; + } + } + } + + error = got_worktree_schedule_delete(worktree, &paths, + delete_local_mods, status_codes, print_remove_status, NULL, + repo, keep_on_disk); +done: + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_revert(void) +{ + fprintf(stderr, "usage: %s revert [-p] [-F response-script] [-R] " + "path ...\n", getprogname()); + exit(1); +} + +static const struct got_error * +revert_progress(void *arg, unsigned char status, const char *path) +{ + if (status == GOT_STATUS_UNVERSIONED) + return NULL; + + while (path[0] == '/') + path++; + printf("%c %s\n", status, path); + return NULL; +} + +struct choose_patch_arg { + FILE *patch_script_file; + const char *action; +}; + +static const struct got_error * +show_change(unsigned char status, const char *path, FILE *patch_file, int n, + int nchanges, const char *action) +{ + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + switch (status) { + case GOT_STATUS_ADD: + printf("A %s\n%s this addition? [y/n] ", path, action); + break; + case GOT_STATUS_DELETE: + printf("D %s\n%s this deletion? [y/n] ", path, action); + break; + case GOT_STATUS_MODIFY: + if (fseek(patch_file, 0L, SEEK_SET) == -1) + return got_error_from_errno("fseek"); + printf(GOT_COMMIT_SEP_STR); + while ((linelen = getline(&line, &linesize, patch_file)) != -1) + printf("%s", line); + if (ferror(patch_file)) + return got_error_from_errno("getline"); + printf(GOT_COMMIT_SEP_STR); + printf("M %s (change %d of %d)\n%s this change? [y/n/q] ", + path, n, nchanges, action); + break; + default: + return got_error_path(path, GOT_ERR_FILE_STATUS); + } + + return NULL; +} + +static const struct got_error * +choose_patch(int *choice, void *arg, unsigned char status, const char *path, + FILE *patch_file, int n, int nchanges) +{ + const struct got_error *err = NULL; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + int resp = ' '; + struct choose_patch_arg *a = arg; + + *choice = GOT_PATCH_CHOICE_NONE; + + if (a->patch_script_file) { + char *nl; + err = show_change(status, path, patch_file, n, nchanges, + a->action); + if (err) + return err; + linelen = getline(&line, &linesize, a->patch_script_file); + if (linelen == -1) { + if (ferror(a->patch_script_file)) + return got_error_from_errno("getline"); + return NULL; + } + nl = strchr(line, '\n'); + if (nl) + *nl = '\0'; + if (strcmp(line, "y") == 0) { + *choice = GOT_PATCH_CHOICE_YES; + printf("y\n"); + } else if (strcmp(line, "n") == 0) { + *choice = GOT_PATCH_CHOICE_NO; + printf("n\n"); + } else if (strcmp(line, "q") == 0 && + status == GOT_STATUS_MODIFY) { + *choice = GOT_PATCH_CHOICE_QUIT; + printf("q\n"); + } else + printf("invalid response '%s'\n", line); + free(line); + return NULL; + } + + while (resp != 'y' && resp != 'n' && resp != 'q') { + err = show_change(status, path, patch_file, n, nchanges, + a->action); + if (err) + return err; + resp = getchar(); + if (resp == '\n') + resp = getchar(); + if (status == GOT_STATUS_MODIFY) { + if (resp != 'y' && resp != 'n' && resp != 'q') { + printf("invalid response '%c'\n", resp); + resp = ' '; + } + } else if (resp != 'y' && resp != 'n') { + printf("invalid response '%c'\n", resp); + resp = ' '; + } + } + + if (resp == 'y') + *choice = GOT_PATCH_CHOICE_YES; + else if (resp == 'n') + *choice = GOT_PATCH_CHOICE_NO; + else if (resp == 'q' && status == GOT_STATUS_MODIFY) + *choice = GOT_PATCH_CHOICE_QUIT; + + return NULL; +} + + +static const struct got_error * +cmd_revert(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + char *cwd = NULL, *path = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + int ch, can_recurse = 0, pflag = 0; + FILE *patch_script_file = NULL; + const char *patch_script_path = NULL; + struct choose_patch_arg cpa; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "pF:R")) != -1) { + switch (ch) { + case 'p': + pflag = 1; + break; + case 'F': + patch_script_path = optarg; + break; + case 'R': + can_recurse = 1; + break; + default: + usage_revert(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc < 1) + usage_revert(); + 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_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "revert", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + if (patch_script_path) { + patch_script_file = fopen(patch_script_path, "r"); + if (patch_script_file == NULL) { + error = got_error_from_errno2("fopen", + patch_script_path); + goto done; + } + } + error = apply_unveil(got_repo_get_path(repo), 1, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + if (!can_recurse) { + char *ondisk_path; + struct stat sb; + TAILQ_FOREACH(pe, &paths, entry) { + if (asprintf(&ondisk_path, "%s/%s", + got_worktree_get_root_path(worktree), + pe->path) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + if (lstat(ondisk_path, &sb) == -1) { + if (errno == ENOENT) { + free(ondisk_path); + continue; + } + error = got_error_from_errno2("lstat", + ondisk_path); + free(ondisk_path); + goto done; + } + free(ondisk_path); + if (S_ISDIR(sb.st_mode)) { + error = got_error_msg(GOT_ERR_BAD_PATH, + "reverting directories requires -R option"); + goto done; + } + } + } + + cpa.patch_script_file = patch_script_file; + cpa.action = "revert"; + error = got_worktree_revert(worktree, &paths, revert_progress, NULL, + pflag ? choose_patch : NULL, &cpa, repo); +done: + if (patch_script_file && fclose(patch_script_file) == EOF && + error == NULL) + error = got_error_from_errno2("fclose", patch_script_path); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(path); + free(cwd); + return error; +} + +__dead static void +usage_commit(void) +{ + fprintf(stderr, "usage: %s commit [-m msg] [-S] [path ...]\n", + getprogname()); + exit(1); +} + +struct collect_commit_logmsg_arg { + const char *cmdline_log; + const char *editor; + const char *worktree_path; + const char *branch_name; + const char *repo_path; + char *logmsg_path; + +}; + +static const struct got_error * +collect_commit_logmsg(struct got_pathlist_head *commitable_paths, char **logmsg, + void *arg) +{ + char *initial_content = NULL; + struct got_pathlist_entry *pe; + const struct got_error *err = NULL; + char *template = NULL; + struct collect_commit_logmsg_arg *a = arg; + int initial_content_len; + int fd = -1; + size_t len; + + /* if a message was specified on the command line, just use it */ + if (a->cmdline_log != NULL && strlen(a->cmdline_log) != 0) { + len = strlen(a->cmdline_log) + 1; + *logmsg = malloc(len + 1); + if (*logmsg == NULL) + return got_error_from_errno("malloc"); + strlcpy(*logmsg, a->cmdline_log, len); + return NULL; + } + + if (asprintf(&template, "%s/logmsg", a->worktree_path) == -1) + return got_error_from_errno("asprintf"); + + initial_content_len = asprintf(&initial_content, + "\n# changes to be committed on branch %s:\n", + a->branch_name); + if (initial_content_len == -1) + return got_error_from_errno("asprintf"); + + err = got_opentemp_named_fd(&a->logmsg_path, &fd, template); + if (err) + goto done; + + if (write(fd, initial_content, initial_content_len) == -1) { + err = got_error_from_errno2("write", a->logmsg_path); + goto done; + } + + TAILQ_FOREACH(pe, commitable_paths, entry) { + struct got_commitable *ct = pe->data; + dprintf(fd, "# %c %s\n", + got_commitable_get_status(ct), + got_commitable_get_path(ct)); + } + + err = edit_logmsg(logmsg, a->editor, a->logmsg_path, initial_content); +done: + free(initial_content); + free(template); + + if (fd != -1 && close(fd) == -1 && err == NULL) + err = got_error_from_errno2("close", a->logmsg_path); + + /* Editor is done; we can now apply unveil(2) */ + if (err == NULL) + err = apply_unveil(a->repo_path, 0, a->worktree_path); + if (err) { + free(*logmsg); + *logmsg = NULL; + } + return err; +} + +static const struct got_error * +cmd_commit(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + char *cwd = NULL, *id_str = NULL; + struct got_object_id *id = NULL; + const char *logmsg = NULL; + struct collect_commit_logmsg_arg cl_arg; + char *gitconfig_path = NULL, *editor = NULL, *author = NULL; + int ch, rebase_in_progress, histedit_in_progress, preserve_logmsg = 0; + int allow_bad_symlinks = 0; + struct got_pathlist_head paths; + + TAILQ_INIT(&paths); + cl_arg.logmsg_path = NULL; + + while ((ch = getopt(argc, argv, "m:S")) != -1) { + switch (ch) { + case 'm': + logmsg = optarg; + break; + case 'S': + allow_bad_symlinks = 1; + break; + default: + usage_commit(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "commit", cwd); + goto done; + } + + error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); + if (error) + goto done; + if (rebase_in_progress) { + error = got_error(GOT_ERR_REBASING); + goto done; + } + + error = got_worktree_histedit_in_progress(&histedit_in_progress, + worktree); + if (error) + goto done; + + error = get_gitconfig_path(&gitconfig_path); + if (error) + goto done; + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + gitconfig_path); + if (error != NULL) + goto done; + + error = get_author(&author, repo, worktree); + if (error) + return error; + + /* + * unveil(2) traverses exec(2); if an editor is used we have + * to apply unveil after the log message has been written. + */ + if (logmsg == NULL || strlen(logmsg) == 0) + error = get_editor(&editor); + else + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = get_worktree_paths_from_argv(&paths, argc, argv, worktree); + if (error) + goto done; + + cl_arg.editor = editor; + cl_arg.cmdline_log = logmsg; + cl_arg.worktree_path = got_worktree_get_root_path(worktree); + cl_arg.branch_name = got_worktree_get_head_ref_name(worktree); + if (!histedit_in_progress) { + if (strncmp(cl_arg.branch_name, "refs/heads/", 11) != 0) { + error = got_error(GOT_ERR_COMMIT_BRANCH); + goto done; + } + cl_arg.branch_name += 11; + } + cl_arg.repo_path = got_repo_get_path(repo); + error = got_worktree_commit(&id, worktree, &paths, author, NULL, + allow_bad_symlinks, collect_commit_logmsg, &cl_arg, + print_status, NULL, repo); + if (error) { + if (error->code != GOT_ERR_COMMIT_MSG_EMPTY && + cl_arg.logmsg_path != NULL) + preserve_logmsg = 1; + goto done; + } + + error = got_object_id_str(&id_str, id); + if (error) + goto done; + printf("Created commit %s\n", id_str); +done: + if (preserve_logmsg) { + fprintf(stderr, "%s: log message preserved in %s\n", + getprogname(), cl_arg.logmsg_path); + } else if (cl_arg.logmsg_path && unlink(cl_arg.logmsg_path) == -1 && + error == NULL) + error = got_error_from_errno2("unlink", cl_arg.logmsg_path); + free(cl_arg.logmsg_path); + if (repo) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(id_str); + free(gitconfig_path); + free(editor); + free(author); + return error; +} + +__dead static void +usage_cherrypick(void) +{ + fprintf(stderr, "usage: %s cherrypick commit-id\n", getprogname()); + exit(1); +} + +static const struct got_error * +cmd_cherrypick(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + char *cwd = NULL, *commit_id_str = NULL; + struct got_object_id *commit_id = NULL; + struct got_commit_object *commit = NULL; + struct got_object_qid *pid; + struct got_reference *head_ref = NULL; + int ch; + struct got_update_progress_arg upa; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_cherrypick(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_cherrypick(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "cherrypick", + cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = got_repo_match_object_id_prefix(&commit_id, argv[0], + GOT_OBJ_TYPE_COMMIT, repo); + if (error != NULL) { + struct got_reference *ref; + if (error->code != GOT_ERR_BAD_OBJ_ID_STR) + goto done; + error = got_ref_open(&ref, repo, argv[0], 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, ref); + got_ref_close(ref); + if (error != NULL) + goto done; + } + error = got_object_id_str(&commit_id_str, commit_id); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, + got_worktree_get_head_ref_name(worktree), 0); + if (error != NULL) + goto done; + + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) { + if (error->code != GOT_ERR_ANCESTRY) + goto done; + error = NULL; + } else { + error = got_error(GOT_ERR_SAME_BRANCH); + goto done; + } + + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + goto done; + pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); + 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) + printf("Merged commit %s\n", commit_id_str); + print_update_progress_stats(&upa); +done: + if (commit) + got_object_commit_close(commit); + free(commit_id_str); + if (head_ref) + got_ref_close(head_ref); + if (worktree) + got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__dead static void +usage_backout(void) +{ + fprintf(stderr, "usage: %s backout commit-id\n", getprogname()); + exit(1); +} + +static const struct got_error * +cmd_backout(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + char *cwd = NULL, *commit_id_str = NULL; + struct got_object_id *commit_id = NULL; + struct got_commit_object *commit = NULL; + struct got_object_qid *pid; + struct got_reference *head_ref = NULL; + int ch; + struct got_update_progress_arg upa; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_backout(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_backout(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "backout", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = got_repo_match_object_id_prefix(&commit_id, argv[0], + GOT_OBJ_TYPE_COMMIT, repo); + if (error != NULL) { + struct got_reference *ref; + if (error->code != GOT_ERR_BAD_OBJ_ID_STR) + goto done; + error = got_ref_open(&ref, repo, argv[0], 0); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, ref); + got_ref_close(ref); + if (error != NULL) + goto done; + } + error = got_object_id_str(&commit_id_str, commit_id); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, + got_worktree_get_head_ref_name(worktree), 0); + if (error != NULL) + goto done; + + error = check_same_branch(commit_id, head_ref, NULL, repo); + if (error) + goto done; + + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + goto done; + pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit)); + if (pid == NULL) { + error = got_error(GOT_ERR_ROOT_COMMIT); + goto done; + } + + 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) + printf("Backed out commit %s\n", commit_id_str); + print_update_progress_stats(&upa); +done: + if (commit) + got_object_commit_close(commit); + free(commit_id_str); + if (head_ref) + got_ref_close(head_ref); + if (worktree) + got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__dead static void +usage_rebase(void) +{ + fprintf(stderr, "usage: %s rebase [-a] | [-c] | branch\n", + getprogname()); + exit(1); +} + +void +trim_logmsg(char *logmsg, int limit) +{ + char *nl; + size_t len; + + len = strlen(logmsg); + if (len > limit) + len = limit; + logmsg[len] = '\0'; + nl = strchr(logmsg, '\n'); + if (nl) + *nl = '\0'; +} + +static const struct got_error * +get_short_logmsg(char **logmsg, int limit, struct got_commit_object *commit) +{ + const 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 *new_base_branch, + struct got_reference *tmp_branch, struct got_repository *repo) +{ + printf("Switching work tree to %s\n", got_ref_get_name(branch)); + return got_worktree_rebase_complete(worktree, fileindex, + new_base_branch, tmp_branch, branch, repo); +} + +static const struct got_error * +rebase_commit(struct got_pathlist_head *merged_paths, + struct got_worktree *worktree, struct got_fileindex *fileindex, + struct got_reference *tmp_branch, + struct got_object_id *commit_id, struct got_repository *repo) +{ + const struct got_error *error; + struct got_commit_object *commit; + struct got_object_id *new_commit_id; + + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + return error; + + error = got_worktree_rebase_commit(&new_commit_id, merged_paths, + worktree, fileindex, tmp_branch, commit, commit_id, repo); + if (error) { + if (error->code != GOT_ERR_COMMIT_NO_CHANGES) + goto done; + error = show_rebase_progress(commit, commit_id, NULL); + } else { + error = show_rebase_progress(commit, commit_id, new_commit_id); + free(new_commit_id); + } +done: + got_object_commit_close(commit); + return error; +} + +struct check_path_prefix_arg { + const char *path_prefix; + size_t len; + int errcode; +}; + +static const struct got_error * +check_path_prefix_in_diff(void *arg, struct got_blob_object *blob1, + struct got_blob_object *blob2, struct got_object_id *id1, + struct got_object_id *id2, const char *path1, const char *path2, + 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, "", "", repo, + check_path_prefix_in_diff, &cpp_arg, 0); +done: + if (tree1) + got_object_tree_close(tree1); + if (tree2) + got_object_tree_close(tree2); + if (commit) + got_object_commit_close(commit); + if (parent_commit) + got_object_commit_close(parent_commit); + return err; +} + +static const struct got_error * +collect_commits(struct got_object_id_queue *commits, + struct got_object_id *initial_commit_id, + struct got_object_id *iter_start_id, struct got_object_id *iter_stop_id, + const char *path_prefix, int path_prefix_errcode, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_commit_graph *graph = NULL; + struct got_object_id *parent_id = NULL; + struct got_object_qid *qid; + struct got_object_id *commit_id = initial_commit_id; + + err = got_commit_graph_open(&graph, "/", 1); + if (err) + return err; + + err = got_commit_graph_iter_start(graph, iter_start_id, repo, + check_cancelled, NULL); + if (err) + goto done; + 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; + SIMPLEQ_INSERT_HEAD(commits, qid, entry); + commit_id = parent_id; + } + } +done: + got_commit_graph_close(graph); + return err; +} + +static const struct got_error * +cmd_rebase(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + struct got_repository *repo = NULL; + struct got_fileindex *fileindex = NULL; + char *cwd = NULL; + struct got_reference *branch = NULL; + struct got_reference *new_base_branch = NULL, *tmp_branch = NULL; + struct got_object_id *commit_id = NULL, *parent_id = NULL; + struct got_object_id *resume_commit_id = NULL; + struct got_object_id *branch_head_commit_id = NULL, *yca_id = NULL; + struct got_commit_object *commit = NULL; + int ch, rebase_in_progress = 0, abort_rebase = 0, continue_rebase = 0; + int histedit_in_progress = 0; + unsigned char rebase_status = GOT_STATUS_NO_CHANGE; + struct got_object_id_queue commits; + struct got_pathlist_head merged_paths; + const struct got_object_id_queue *parent_ids; + struct got_object_qid *qid, *pid; + + SIMPLEQ_INIT(&commits); + TAILQ_INIT(&merged_paths); + + while ((ch = getopt(argc, argv, "ac")) != -1) { + switch (ch) { + case 'a': + abort_rebase = 1; + break; + case 'c': + continue_rebase = 1; + break; + default: + usage_rebase(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (abort_rebase && continue_rebase) + usage_rebase(); + else if (abort_rebase || continue_rebase) { + if (argc != 0) + usage_rebase(); + } else if (argc != 1) + usage_rebase(); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "rebase", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + error = got_worktree_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_rebase_in_progress(&rebase_in_progress, worktree); + if (error) + goto done; + + if (abort_rebase) { + struct got_update_progress_arg upa; + 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)); + memset(&upa, 0, sizeof(upa)); + error = got_worktree_rebase_abort(worktree, fileindex, repo, + new_base_branch, update_progress, &upa); + if (error) + goto done; + printf("Rebase of %s aborted\n", got_ref_get_name(branch)); + print_update_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, + resume_commit_id, repo); + if (error) + goto done; + + yca_id = got_object_id_dup(resume_commit_id); + if (yca_id == NULL) { + error = got_error_from_errno("got_object_id_dup"); + goto done; + } + } else { + error = got_ref_open(&branch, repo, argv[0], 0); + if (error != NULL) + goto done; + } + + error = got_ref_resolve(&branch_head_commit_id, repo, branch); + if (error) + goto done; + + if (!continue_rebase) { + struct got_object_id *base_commit_id; + + base_commit_id = got_worktree_get_base_commit_id(worktree); + error = got_commit_graph_find_youngest_common_ancestor(&yca_id, + base_commit_id, branch_head_commit_id, repo, + check_cancelled, NULL); + if (error) + goto done; + if (yca_id == NULL) { + error = got_error_msg(GOT_ERR_ANCESTRY, + "specified branch shares no common ancestry " + "with work tree's branch"); + goto done; + } + + error = check_same_branch(base_commit_id, branch, yca_id, repo); + if (error) { + if (error->code != GOT_ERR_ANCESTRY) + goto done; + error = NULL; + } else { + error = got_error_msg(GOT_ERR_SAME_BRANCH, + "specified branch resolves to a commit which " + "is already contained in work tree's branch"); + goto done; + } + error = got_worktree_rebase_prepare(&new_base_branch, + &tmp_branch, &fileindex, worktree, branch, repo); + if (error) + goto done; + } + + commit_id = branch_head_commit_id; + error = got_object_open_as_commit(&commit, repo, commit_id); + if (error) + goto done; + + parent_ids = got_object_commit_get_parent_ids(commit); + pid = SIMPLEQ_FIRST(parent_ids); + if (pid == NULL) { + if (!continue_rebase) { + struct got_update_progress_arg upa; + memset(&upa, 0, sizeof(upa)); + error = got_worktree_rebase_abort(worktree, fileindex, + repo, new_base_branch, update_progress, &upa); + if (error) + goto done; + printf("Rebase of %s aborted\n", + got_ref_get_name(branch)); + print_update_progress_stats(&upa); + + } + error = got_error(GOT_ERR_EMPTY_REBASE); + goto done; + } + error = collect_commits(&commits, commit_id, pid->id, + yca_id, got_worktree_get_path_prefix(worktree), + GOT_ERR_REBASE_PATH, repo); + got_object_commit_close(commit); + commit = NULL; + if (error) + goto done; + + if (SIMPLEQ_EMPTY(&commits)) { + if (continue_rebase) { + error = rebase_complete(worktree, fileindex, + branch, new_base_branch, tmp_branch, repo); + 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); + 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; + } + } + + pid = NULL; + SIMPLEQ_FOREACH(qid, &commits, entry) { + struct got_update_progress_arg upa; + + 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_update_progress_stats(&upa); + if (upa.conflicts > 0) + rebase_status = GOT_STATUS_CONFLICT; + + if (rebase_status == GOT_STATUS_CONFLICT) { + error = show_rebase_merge_conflict(qid->id, repo); + if (error) + goto done; + got_worktree_rebase_pathlist_free(&merged_paths); + break; + } + + error = rebase_commit(&merged_paths, worktree, fileindex, + tmp_branch, commit_id, repo); + got_worktree_rebase_pathlist_free(&merged_paths); + if (error) + goto done; + } + + if (rebase_status == GOT_STATUS_CONFLICT) { + error = got_worktree_rebase_postpone(worktree, fileindex); + if (error) + goto done; + error = got_error_msg(GOT_ERR_CONFLICTS, + "conflicts must be resolved before rebasing can continue"); + } else + error = rebase_complete(worktree, fileindex, branch, + new_base_branch, tmp_branch, repo); +done: + got_object_id_queue_free(&commits); + free(branch_head_commit_id); + free(resume_commit_id); + free(yca_id); + if (commit) + got_object_commit_close(commit); + if (branch) + got_ref_close(branch); + if (new_base_branch) + got_ref_close(new_base_branch); + if (tmp_branch) + got_ref_close(tmp_branch); + if (worktree) + got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__dead static void +usage_histedit(void) +{ + fprintf(stderr, "usage: %s histedit [-a] [-c] [-F histedit-script] [-m]\n", + getprogname()); + exit(1); +} + +#define GOT_HISTEDIT_PICK 'p' +#define GOT_HISTEDIT_EDIT 'e' +#define GOT_HISTEDIT_FOLD 'f' +#define GOT_HISTEDIT_DROP 'd' +#define GOT_HISTEDIT_MESG 'm' + +static struct got_histedit_cmd { + unsigned char code; + const char *name; + const char *desc; +} got_histedit_cmds[] = { + { GOT_HISTEDIT_PICK, "pick", "use commit" }, + { GOT_HISTEDIT_EDIT, "edit", "use commit but stop for amending" }, + { GOT_HISTEDIT_FOLD, "fold", "combine with 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, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_qid *qid; + + if (SIMPLEQ_EMPTY(commits)) + return got_error(GOT_ERR_EMPTY_HISTEDIT); + + SIMPLEQ_FOREACH(qid, commits, entry) { + err = histedit_write_commit(qid->id, got_histedit_cmds[0].name, + f, repo); + if (err) + break; + 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; + int n, i; + char *id_str; + struct got_object_qid *qid; + + qid = SIMPLEQ_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++) { + 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 == -1 || ret >= sizeof(msg)) + return got_error(GOT_ERR_HISTEDIT_SYNTAX); + + return got_error_msg(GOT_ERR_HISTEDIT_SYNTAX, msg); +} + +static const struct got_error * +append_folded_commit_msg(char **new_msg, struct got_histedit_list_entry *hle, + char *logmsg, struct got_repository *repo) +{ + const struct got_error *err; + struct got_commit_object *folded_commit = NULL; + char *id_str, *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; + 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; + + write(fd, logmsg, logmsg_len); + close(fd); + + err = get_editor(&editor); + if (err) + goto done; + + err = edit_logmsg(&hle->logmsg, editor, logmsg_path, logmsg); + if (err) { + if (err->code != GOT_ERR_COMMIT_MSG_EMPTY) + goto done; + err = got_object_commit_get_logmsg(&hle->logmsg, commit); + } +done: + 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 size; + ssize_t len; + int lineno = 0, i; + const struct got_histedit_cmd *cmd; + struct got_object_id *commit_id = NULL; + struct got_histedit_list_entry *hle = NULL; + + for (;;) { + len = getline(&line, &size, f); + if (len == -1) { + const struct got_error *getline_err; + if (feof(f)) + break; + getline_err = got_error_from_errno("getline"); + err = got_ferror(f, getline_err->code); + break; + } + lineno++; + p = line; + while (isspace((unsigned char)p[0])) + p++; + if (p[0] == '#' || p[0] == '\0') { + free(line); + line = NULL; + continue; + } + cmd = NULL; + for (i = 0; i < nitems(got_histedit_cmds); i++) { + cmd = &got_histedit_cmds[i]; + if (strncmp(cmd->name, p, strlen(cmd->name)) == 0 && + isspace((unsigned char)p[strlen(cmd->name)])) { + p += strlen(cmd->name); + break; + } + if (p[0] == cmd->code && isspace((unsigned char)p[1])) { + p++; + break; + } + } + if (i == nitems(got_histedit_cmds)) { + err = histedit_syntax_error(lineno); + break; + } + while (isspace((unsigned char)p[0])) + p++; + if (cmd->code == GOT_HISTEDIT_MESG) { + if (hle == NULL || hle->logmsg != NULL) { + err = got_error(GOT_ERR_HISTEDIT_CMD); + break; + } + if (p[0] == '\0') { + err = histedit_edit_logmsg(hle, repo); + if (err) + break; + } else { + hle->logmsg = strdup(p); + if (hle->logmsg == NULL) { + err = got_error_from_errno("strdup"); + break; + } + } + free(line); + line = NULL; + continue; + } else { + end = p; + while (end[0] && !isspace((unsigned char)end[0])) + end++; + *end = '\0'; + + err = got_object_resolve_id_str(&commit_id, repo, p); + if (err) { + /* override error code */ + err = histedit_syntax_error(lineno); + break; + } + } + hle = malloc(sizeof(*hle)); + if (hle == NULL) { + err = got_error_from_errno("malloc"); + break; + } + hle->cmd = cmd; + hle->commit_id = commit_id; + hle->logmsg = NULL; + commit_id = NULL; + free(line); + line = NULL; + TAILQ_INSERT_TAIL(histedit_cmds, hle, entry); + } + + free(line); + free(commit_id); + return err; +} + +static const struct got_error * +histedit_check_script(struct got_histedit_list *histedit_cmds, + struct got_object_id_queue *commits, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_qid *qid; + struct got_histedit_list_entry *hle; + static char msg[92]; + char *id_str; + + if (TAILQ_EMPTY(histedit_cmds)) + return got_error_msg(GOT_ERR_EMPTY_HISTEDIT, + "histedit script contains no commands"); + if (SIMPLEQ_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); + } + } + + SIMPLEQ_FOREACH(qid, commits, entry) { + TAILQ_FOREACH(hle, histedit_cmds, entry) { + if (got_object_id_cmp(qid->id, hle->commit_id) == 0) + break; + } + if (hle == NULL) { + err = got_object_id_str(&id_str, qid->id); + if (err) + return err; + snprintf(msg, sizeof(msg), + "commit %s missing from histedit script", id_str); + free(id_str); + return got_error_msg(GOT_ERR_HISTEDIT_CMD, msg); + } + } + + 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, "r"); + if (f == NULL) { + err = got_error_from_errno("fopen"); + goto done; + } + err = histedit_parse_list(histedit_cmds, f, repo); + if (err) + goto done; + + err = histedit_check_script(histedit_cmds, commits, repo); +done: + if (f && fclose(f) != 0 && err == NULL) + err = got_error_from_errno("fclose"); + free(editor); + return err; +} + +static const struct got_error * +histedit_edit_list_retry(struct got_histedit_list *, const struct got_error *, + struct got_object_id_queue *, const char *, 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, 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, repo); + if (err) + goto done; + + if (edit_logmsg_only) { + rewind(f); + err = histedit_parse_list(histedit_cmds, f, repo); + } else { + if (fclose(f) != 0) { + err = got_error_from_errno("fclose"); + goto done; + } + f = NULL; + err = histedit_run_editor(histedit_cmds, path, commits, repo); + if (err) { + if (err->code != GOT_ERR_HISTEDIT_SYNTAX && + err->code != GOT_ERR_HISTEDIT_CMD) + goto done; + err = histedit_edit_list_retry(histedit_cmds, err, + commits, path, branch_name, repo); + } + } +done: + if (f && fclose(f) != 0 && err == NULL) + err = got_error_from_errno("fclose"); + if (path && unlink(path) != 0 && err == NULL) + err = got_error_from_errno2("unlink", path); + free(path); + return err; +} + +static const struct got_error * +histedit_save_list(struct got_histedit_list *histedit_cmds, + struct got_worktree *worktree, struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *path = NULL; + FILE *f = NULL; + struct got_histedit_list_entry *hle; + struct got_commit_object *commit = NULL; + + err = got_worktree_get_histedit_script_path(&path, worktree); + if (err) + return err; + + f = fopen(path, "w"); + if (f == NULL) { + err = got_error_from_errno2("fopen", path); + goto done; + } + TAILQ_FOREACH(hle, histedit_cmds, entry) { + err = histedit_write_commit(hle->commit_id, hle->cmd->name, f, + repo); + if (err) + break; + + if (hle->logmsg) { + int n = fprintf(f, "%c %s\n", + GOT_HISTEDIT_MESG, hle->logmsg); + if (n < 0) { + err = got_ferror(f, GOT_ERR_IO); + break; + } + } + } +done: + if (f && fclose(f) != 0 && err == NULL) + err = got_error_from_errno("fclose"); + free(path); + if (commit) + got_object_commit_close(commit); + return err; +} + +void +histedit_free_list(struct got_histedit_list *histedit_cmds) +{ + struct got_histedit_list_entry *hle; + + while ((hle = TAILQ_FIRST(histedit_cmds))) { + TAILQ_REMOVE(histedit_cmds, hle, entry); + free(hle); + } +} + +static const struct got_error * +histedit_load_list(struct got_histedit_list *histedit_cmds, + const char *path, struct got_repository *repo) +{ + const struct got_error *err = NULL; + FILE *f = NULL; + + f = fopen(path, "r"); + if (f == NULL) { + err = got_error_from_errno2("fopen", path); + goto done; + } + + err = histedit_parse_list(histedit_cmds, f, repo); +done: + if (f && fclose(f) != 0 && err == NULL) + err = got_error_from_errno("fclose"); + return err; +} + +static const struct got_error * +histedit_edit_list_retry(struct got_histedit_list *histedit_cmds, + const struct got_error *edit_err, struct got_object_id_queue *commits, + const char *path, 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, repo); + if (err) { + if (err->code != GOT_ERR_HISTEDIT_SYNTAX && + err->code != GOT_ERR_HISTEDIT_CMD) + break; + prev_err = err; + resp = ' '; + continue; + } + break; + } else if (resp == 'a') { + err = got_error(GOT_ERR_HISTEDIT_CANCEL); + break; + } else + printf("invalid response '%c'\n", resp); + } + + return err; +} + +static const struct got_error * +histedit_complete(struct got_worktree *worktree, + struct got_fileindex *fileindex, struct got_reference *tmp_branch, + struct got_reference *branch, struct got_repository *repo) +{ + printf("Switching work tree to %s\n", + got_ref_get_symref_target(branch)); + return got_worktree_histedit_complete(worktree, fileindex, tmp_branch, + branch, repo); +} + +static const struct got_error * +show_histedit_progress(struct got_commit_object *commit, + struct got_histedit_list_entry *hle, struct got_object_id *new_id) +{ + const struct got_error *err; + char *old_id_str = NULL, *new_id_str = NULL, *logmsg = NULL; + + err = got_object_id_str(&old_id_str, hle->commit_id); + if (err) + goto done; + + if (new_id) { + err = got_object_id_str(&new_id_str, new_id); + if (err) + goto done; + } + + old_id_str[12] = '\0'; + if (new_id_str) + new_id_str[12] = '\0'; + + if (hle->logmsg) { + logmsg = strdup(hle->logmsg); + if (logmsg == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + trim_logmsg(logmsg, 42); + } else { + err = get_short_logmsg(&logmsg, 42, commit); + if (err) + goto done; + } + + switch (hle->cmd->code) { + case GOT_HISTEDIT_PICK: + case GOT_HISTEDIT_EDIT: + printf("%s -> %s: %s\n", old_id_str, + new_id_str ? new_id_str : "no-op change", logmsg); + break; + case GOT_HISTEDIT_DROP: + case GOT_HISTEDIT_FOLD: + printf("%s -> %s commit: %s\n", old_id_str, hle->cmd->name, + logmsg); + break; + default: + break; + } +done: + free(old_id_str); + free(new_id_str); + return err; +} + +static const struct got_error * +histedit_commit(struct got_pathlist_head *merged_paths, + struct got_worktree *worktree, struct got_fileindex *fileindex, + struct got_reference *tmp_branch, struct got_histedit_list_entry *hle, + struct got_repository *repo) +{ + const struct got_error *err; + struct got_commit_object *commit; + struct got_object_id *new_commit_id; + + if ((hle->cmd->code == GOT_HISTEDIT_EDIT || get_folded_commits(hle)) + && hle->logmsg == NULL) { + err = histedit_edit_logmsg(hle, repo); + if (err) + return err; + } + + err = got_object_open_as_commit(&commit, repo, hle->commit_id); + if (err) + return err; + + err = got_worktree_histedit_commit(&new_commit_id, merged_paths, + worktree, fileindex, tmp_branch, commit, hle->commit_id, + hle->logmsg, repo); + if (err) { + if (err->code != GOT_ERR_COMMIT_NO_CHANGES) + goto done; + err = show_histedit_progress(commit, hle, NULL); + } else { + err = show_histedit_progress(commit, hle, new_commit_id); + free(new_commit_id); + } +done: + got_object_commit_close(commit); + return err; +} + +static const struct got_error * +histedit_skip_commit(struct got_histedit_list_entry *hle, + struct got_worktree *worktree, struct got_repository *repo) +{ + const struct got_error *error; + struct got_commit_object *commit; + + error = got_worktree_histedit_skip_commit(worktree, hle->commit_id, + repo); + if (error) + return error; + + error = got_object_open_as_commit(&commit, repo, hle->commit_id); + if (error) + return error; + + error = show_histedit_progress(commit, hle, NULL); + got_object_commit_close(commit); + return error; +} + +static const struct got_error * +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; + 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; + struct got_update_progress_arg upa; + int edit_in_progress = 0, abort_edit = 0, continue_edit = 0; + int edit_logmsg_only = 0; + const char *edit_script_path = NULL; + unsigned char rebase_status = GOT_STATUS_NO_CHANGE; + struct got_object_id_queue commits; + struct got_pathlist_head merged_paths; + const struct got_object_id_queue *parent_ids; + struct got_object_qid *pid; + struct got_histedit_list histedit_cmds; + struct got_histedit_list_entry *hle; + + SIMPLEQ_INIT(&commits); + TAILQ_INIT(&histedit_cmds); + TAILQ_INIT(&merged_paths); + memset(&upa, 0, sizeof(upa)); + + while ((ch = getopt(argc, argv, "acF:m")) != -1) { + switch (ch) { + case 'a': + abort_edit = 1; + break; + case 'c': + continue_edit = 1; + break; + case 'F': + edit_script_path = optarg; + break; + case 'm': + edit_logmsg_only = 1; + break; + default: + usage_histedit(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (abort_edit && continue_edit) + errx(1, "histedit's -a and -c options are mutually exclusive"); + if (edit_script_path && edit_logmsg_only) + errx(1, "histedit's -F and -m options are mutually exclusive"); + if (abort_edit && edit_logmsg_only) + errx(1, "histedit's -a and -m options are mutually exclusive"); + if (continue_edit && edit_logmsg_only) + errx(1, "histedit's -c and -m options are mutually exclusive"); + if (argc != 0) + usage_histedit(); + + /* + * This command cannot apply unveil(2) in all cases because the + * user may choose to run an editor to edit the histedit script + * and to edit individual commit log messages. + * unveil(2) traverses exec(2); if an editor is used we have to + * apply unveil after edit script and log messages have been written. + * XXX TODO: Make use of unveil(2) where possible. + */ + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "histedit", cwd); + goto done; + } + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree); + if (error) + goto done; + if (rebase_in_progress) { + error = got_error(GOT_ERR_REBASING); + goto done; + } + + error = got_worktree_histedit_in_progress(&edit_in_progress, worktree); + if (error) + goto done; + + if (edit_in_progress && 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 && abort_edit) { + error = got_worktree_histedit_continue(&resume_commit_id, + &tmp_branch, &branch, &base_commit_id, &fileindex, + worktree, repo); + if (error) + goto done; + printf("Switching work tree to %s\n", + got_ref_get_symref_target(branch)); + error = got_worktree_histedit_abort(worktree, fileindex, repo, + branch, base_commit_id, update_progress, &upa); + if (error) + goto done; + printf("Histedit of %s aborted\n", + got_ref_get_symref_target(branch)); + print_update_progress_stats(&upa); + goto done; /* nothing else to do */ + } else if (abort_edit) { + error = got_error(GOT_ERR_NOT_HISTEDIT); + goto done; + } + + if (continue_edit) { + char *path; + + if (!edit_in_progress) { + error = got_error(GOT_ERR_NOT_HISTEDIT); + goto done; + } + + error = got_worktree_get_histedit_script_path(&path, worktree); + if (error) + goto done; + + error = histedit_load_list(&histedit_cmds, path, repo); + free(path); + if (error) + goto done; + + error = got_worktree_histedit_continue(&resume_commit_id, + &tmp_branch, &branch, &base_commit_id, &fileindex, + worktree, repo); + if (error) + goto done; + + error = got_ref_resolve(&head_commit_id, repo, branch); + if (error) + goto done; + + error = got_object_open_as_commit(&commit, repo, + head_commit_id); + if (error) + goto done; + parent_ids = got_object_commit_get_parent_ids(commit); + pid = SIMPLEQ_FIRST(parent_ids); + if (pid == NULL) { + error = got_error(GOT_ERR_EMPTY_HISTEDIT); + goto done; + } + error = collect_commits(&commits, head_commit_id, pid->id, + base_commit_id, got_worktree_get_path_prefix(worktree), + GOT_ERR_HISTEDIT_PATH, repo); + got_object_commit_close(commit); + commit = NULL; + if (error) + goto done; + } else { + if (edit_in_progress) { + error = got_error(GOT_ERR_HISTEDIT_BUSY); + goto done; + } + + error = got_ref_open(&branch, repo, + got_worktree_get_head_ref_name(worktree), 0); + if (error != NULL) + goto done; + + 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 = SIMPLEQ_FIRST(parent_ids); + if (pid == NULL) { + error = got_error(GOT_ERR_EMPTY_HISTEDIT); + goto done; + } + error = collect_commits(&commits, head_commit_id, pid->id, + got_worktree_get_base_commit_id(worktree), + got_worktree_get_path_prefix(worktree), + GOT_ERR_HISTEDIT_PATH, repo); + got_object_commit_close(commit); + commit = NULL; + if (error) + goto done; + + if (SIMPLEQ_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, + update_progress, &upa); + print_update_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, repo); + if (error) { + got_worktree_histedit_abort(worktree, fileindex, + repo, branch, base_commit_id, + update_progress, &upa); + print_update_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, + update_progress, &upa); + print_update_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, check_local_changes, &have_changes, + check_cancelled, NULL); + got_pathlist_free(&paths); + 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, 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 = SIMPLEQ_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_update_progress_stats(&upa); + if (upa.conflicts > 0) + rebase_status = GOT_STATUS_CONFLICT; + + if (rebase_status == GOT_STATUS_CONFLICT) { + error = show_rebase_merge_conflict(hle->commit_id, + repo); + if (error) + goto done; + got_worktree_rebase_pathlist_free(&merged_paths); + break; + } + + if (hle->cmd->code == GOT_HISTEDIT_EDIT) { + char *id_str; + error = got_object_id_str(&id_str, hle->commit_id); + if (error) + goto done; + printf("Stopping histedit for amending commit %s\n", + id_str); + free(id_str); + got_worktree_rebase_pathlist_free(&merged_paths); + error = got_worktree_histedit_postpone(worktree, + fileindex); + goto done; + } + + if (hle->cmd->code == GOT_HISTEDIT_FOLD) { + error = histedit_skip_commit(hle, worktree, repo); + if (error) + goto done; + continue; + } + + error = histedit_commit(&merged_paths, worktree, fileindex, + tmp_branch, hle, repo); + got_worktree_rebase_pathlist_free(&merged_paths); + if (error) + goto done; + } + + if (rebase_status == GOT_STATUS_CONFLICT) { + error = got_worktree_histedit_postpone(worktree, fileindex); + if (error) + goto done; + error = got_error_msg(GOT_ERR_CONFLICTS, + "conflicts must be resolved before histedit can continue"); + } else + error = histedit_complete(worktree, fileindex, tmp_branch, + branch, repo); +done: + got_object_id_queue_free(&commits); + histedit_free_list(&histedit_cmds); + free(head_commit_id); + free(base_commit_id); + free(resume_commit_id); + if (commit) + got_object_commit_close(commit); + if (branch) + got_ref_close(branch); + if (tmp_branch) + got_ref_close(tmp_branch); + if (worktree) + got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__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; + + 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]; + + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + 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); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + 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; + } + + 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) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(base_commit_id); + free(commit_id); + free(refname); + free(base_refname); + return error; +} + +__dead static void +usage_stage(void) +{ + fprintf(stderr, "usage: %s stage [-l] | [-p] [-F response-script] " + "[-S] [file-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; + struct got_pathlist_entry *pe; + 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; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "lpF:S")) != -1) { + switch (ch) { + case 'l': + list_stage = 1; + break; + case 'p': + pflag = 1; + break; + case 'F': + patch_script_path = optarg; + break; + case 'S': + allow_bad_symlinks = 1; + break; + default: + usage_stage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (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_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); + if (error != NULL) + goto done; + + if (patch_script_path) { + patch_script_file = fopen(patch_script_path, "r"); + 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; + + if (list_stage) + error = got_worktree_status(worktree, &paths, repo, + 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) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_unstage(void) +{ + fprintf(stderr, "usage: %s unstage [-p] [-F response-script] " + "[file-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; + struct got_pathlist_entry *pe; + 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; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "pF:")) != -1) { + switch (ch) { + case 'p': + pflag = 1; + break; + case 'F': + patch_script_path = optarg; + break; + default: + usage_unstage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); +#endif + if (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_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); + if (error != NULL) + goto done; + + if (patch_script_path) { + patch_script_file = fopen(patch_script_path, "r"); + 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_update_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) + got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + return error; +} + +__dead static void +usage_cat(void) +{ + fprintf(stderr, "usage: %s cat [-r repository ] [ -c commit ] [ -P ] " + "arg1 [arg2 ...]\n", getprogname()); + exit(1); +} + +static const struct got_error * +cat_blob(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_blob_object *blob; + + err = got_object_open_as_blob(&blob, repo, id, 8192); + if (err) + return err; + + err = got_object_blob_dump_to_file(NULL, NULL, NULL, outfile, blob); + got_object_blob_close(blob); + return err; +} + +static const struct got_error * +cat_tree(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_tree_object *tree; + int nentries, i; + + err = got_object_open_as_tree(&tree, repo, id); + if (err) + return err; + + nentries = got_object_tree_get_nentries(tree); + for (i = 0; i < nentries; i++) { + struct got_tree_entry *te; + char *id_str; + if (sigint_received || sigpipe_received) + break; + te = got_object_tree_get_entry(tree, i); + err = got_object_id_str(&id_str, got_tree_entry_get_id(te)); + if (err) + break; + fprintf(outfile, "%s %.7o %s\n", id_str, + got_tree_entry_get_mode(te), + got_tree_entry_get_name(te)); + free(id_str); + } + + got_object_tree_close(tree); + return err; +} + +static const struct got_error * +cat_commit(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_commit_object *commit; + const struct got_object_id_queue *parent_ids; + struct got_object_qid *pid; + char *id_str = NULL; + const char *logmsg = NULL; + + err = got_object_open_as_commit(&commit, repo, id); + if (err) + return err; + + err = got_object_id_str(&id_str, got_object_commit_get_tree_id(commit)); + if (err) + goto done; + + fprintf(outfile, "%s%s\n", GOT_COMMIT_LABEL_TREE, id_str); + parent_ids = got_object_commit_get_parent_ids(commit); + fprintf(outfile, "numparents %d\n", + got_object_commit_get_nparents(commit)); + SIMPLEQ_FOREACH(pid, parent_ids, entry) { + char *pid_str; + err = got_object_id_str(&pid_str, pid->id); + if (err) + goto done; + fprintf(outfile, "%s%s\n", GOT_COMMIT_LABEL_PARENT, pid_str); + free(pid_str); + } + fprintf(outfile, "%s%s %lld +0000\n", GOT_COMMIT_LABEL_AUTHOR, + got_object_commit_get_author(commit), + got_object_commit_get_author_time(commit)); + + fprintf(outfile, "%s%s %lld +0000\n", GOT_COMMIT_LABEL_COMMITTER, + got_object_commit_get_author(commit), + got_object_commit_get_committer_time(commit)); + + logmsg = got_object_commit_get_logmsg_raw(commit); + fprintf(outfile, "messagelen %zd\n", strlen(logmsg)); + fprintf(outfile, "%s", logmsg); +done: + free(id_str); + got_object_commit_close(commit); + return err; +} + +static const struct got_error * +cat_tag(struct got_object_id *id, struct got_repository *repo, FILE *outfile) +{ + const struct got_error *err; + struct got_tag_object *tag; + char *id_str = NULL; + const char *tagmsg = NULL; + + err = got_object_open_as_tag(&tag, repo, id); + if (err) + return err; + + err = got_object_id_str(&id_str, got_object_tag_get_object_id(tag)); + if (err) + goto done; + + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_OBJECT, id_str); + + switch (got_object_tag_get_object_type(tag)) { + case GOT_OBJ_TYPE_BLOB: + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TYPE, + GOT_OBJ_LABEL_BLOB); + break; + case GOT_OBJ_TYPE_TREE: + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TYPE, + GOT_OBJ_LABEL_TREE); + break; + case GOT_OBJ_TYPE_COMMIT: + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TYPE, + GOT_OBJ_LABEL_COMMIT); + break; + case GOT_OBJ_TYPE_TAG: + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TYPE, + GOT_OBJ_LABEL_TAG); + break; + default: + break; + } + + fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TAG, + got_object_tag_get_name(tag)); + + fprintf(outfile, "%s%s %lld +0000\n", GOT_TAG_LABEL_TAGGER, + got_object_tag_get_tagger(tag), + got_object_tag_get_tagger_time(tag)); + + tagmsg = got_object_tag_get_message(tag); + fprintf(outfile, "messagelen %zd\n", strlen(tagmsg)); + fprintf(outfile, "%s", tagmsg); +done: + free(id_str); + got_object_tag_close(tag); + return err; +} + +static const struct got_error * +cmd_cat(int argc, char *argv[]) +{ + const struct got_error *error; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *repo_path = NULL, *label = NULL; + const char *commit_id_str = NULL; + struct got_object_id *id = NULL, *commit_id = NULL; + int ch, obj_type, i, force_path = 0; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "c:r:P")) != -1) { + switch (ch) { + case 'c': + commit_id_str = optarg; + 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 'P': + force_path = 1; + break; + default: + usage_cat(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + error = got_worktree_open(&worktree, cwd); + if (error && error->code != GOT_ERR_NOT_WORKTREE) + goto done; + if (worktree) { + if (repo_path == NULL) { + repo_path = strdup( + got_worktree_get_repo_path(worktree)); + if (repo_path == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + } + } + + if (repo_path == NULL) { + repo_path = getcwd(NULL, 0); + if (repo_path == NULL) + return got_error_from_errno("getcwd"); + } + + error = got_repo_open(&repo, repo_path, NULL); + free(repo_path); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 1, NULL); + if (error) + goto done; + + if (commit_id_str == NULL) + commit_id_str = GOT_REF_HEAD; + error = got_repo_match_object_id(&commit_id, NULL, + commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo); + if (error) + goto done; + + for (i = 0; i < argc; i++) { + if (force_path) { + error = got_object_id_by_path(&id, repo, commit_id, + argv[i]); + if (error) + break; + } else { + error = got_repo_match_object_id(&id, &label, argv[i], + GOT_OBJ_TYPE_ANY, 0, repo); + if (error) { + if (error->code != GOT_ERR_BAD_OBJ_ID_STR && + error->code != GOT_ERR_NOT_REF) + break; + error = got_object_id_by_path(&id, repo, + commit_id, argv[i]); + if (error) + break; + } + } + + error = got_object_get_type(&obj_type, repo, id); + if (error) + break; + + switch (obj_type) { + case GOT_OBJ_TYPE_BLOB: + error = cat_blob(id, repo, stdout); + break; + case GOT_OBJ_TYPE_TREE: + error = cat_tree(id, repo, stdout); + break; + case GOT_OBJ_TYPE_COMMIT: + error = cat_commit(id, repo, stdout); + break; + case GOT_OBJ_TYPE_TAG: + error = cat_tag(id, repo, stdout); + break; + default: + error = got_error(GOT_ERR_OBJ_TYPE); + break; + } + if (error) + break; + free(label); + label = NULL; + free(id); + id = NULL; + } +done: + free(label); + free(id); + free(commit_id); + if (worktree) + got_worktree_close(worktree); + if (repo) { + const struct got_error *repo_error; + repo_error = got_repo_close(repo); + if (error == NULL) + error = repo_error; + } + return error; +} + +__dead static void +usage_info(void) +{ + fprintf(stderr, "usage: %s info [path ...]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +print_path_info(void *arg, const char *path, mode_t mode, time_t mtime, + struct got_object_id *blob_id, struct got_object_id *staged_blob_id, + struct got_object_id *commit_id) +{ + const struct got_error *err = NULL; + char *id_str = NULL; + char datebuf[128]; + struct tm mytm, *tm; + struct got_pathlist_head *paths = arg; + struct got_pathlist_entry *pe; + + /* + * Clear error indication from any of the path arguments which + * would cause this file index entry to be displayed. + */ + TAILQ_FOREACH(pe, paths, entry) { + if (got_path_cmp(path, pe->path, strlen(path), + pe->path_len) == 0 || + got_path_is_child(path, pe->path, pe->path_len)) + pe->data = NULL; /* no error */ + } + + printf(GOT_COMMIT_SEP_STR); + if (S_ISLNK(mode)) + printf("symlink: %s\n", path); + else if (S_ISREG(mode)) { + printf("file: %s\n", path); + printf("mode: %o\n", mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + } else if (S_ISDIR(mode)) + printf("directory: %s\n", path); + else + printf("something: %s\n", path); + + tm = localtime_r(&mtime, &mytm); + if (tm == NULL) + return NULL; + if (strftime(datebuf, sizeof(datebuf), "%c %Z", tm) >= sizeof(datebuf)) + return got_error(GOT_ERR_NO_SPACE); + printf("timestamp: %s\n", datebuf); + + if (blob_id) { + err = got_object_id_str(&id_str, blob_id); + if (err) + return err; + printf("based on blob: %s\n", id_str); + free(id_str); + } + + if (staged_blob_id) { + err = got_object_id_str(&id_str, staged_blob_id); + if (err) + return err; + printf("based on staged blob: %s\n", id_str); + free(id_str); + } + + if (commit_id) { + err = got_object_id_str(&id_str, commit_id); + if (err) + return err; + printf("based on commit: %s\n", id_str); + free(id_str); + } + + return NULL; +} + +static const struct got_error * +cmd_info(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *id_str = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + char *uuidstr = NULL; + int ch, show_files = 0; + + TAILQ_INIT(&paths); + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_info(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_worktree_open(&worktree, cwd); + if (error) { + if (error->code == GOT_ERR_NOT_WORKTREE) + error = wrap_not_worktree_error(error, "status", cwd); + goto done; + } + + error = apply_unveil(NULL, 0, got_worktree_get_root_path(worktree)); + if (error) + goto done; + + if (argc >= 1) { + error = get_worktree_paths_from_argv(&paths, argc, argv, + worktree); + if (error) + goto done; + show_files = 1; + } + + error = got_object_id_str(&id_str, + got_worktree_get_base_commit_id(worktree)); + if (error) + goto done; + + error = got_worktree_get_uuid(&uuidstr, worktree); + if (error) + goto done; + + printf("work tree: %s\n", got_worktree_get_root_path(worktree)); + printf("work tree base commit: %s\n", id_str); + printf("work tree path prefix: %s\n", + got_worktree_get_path_prefix(worktree)); + printf("work tree branch reference: %s\n", + got_worktree_get_head_ref_name(worktree)); + printf("work tree UUID: %s\n", uuidstr); + printf("repository: %s\n", got_worktree_get_repo_path(worktree)); + + if (show_files) { + struct got_pathlist_entry *pe; + TAILQ_FOREACH(pe, &paths, entry) { + if (pe->path_len == 0) + continue; + /* + * Assume this path will fail. This will be corrected + * in print_path_info() in case the path does suceeed. + */ + pe->data = (void *)got_error_path(pe->path, + GOT_ERR_BAD_PATH); + } + error = got_worktree_path_info(worktree, &paths, + print_path_info, &paths, check_cancelled, NULL); + if (error) + goto done; + TAILQ_FOREACH(pe, &paths, entry) { + if (pe->data != NULL) { + error = pe->data; /* bad path */ + break; + } + } + } +done: + TAILQ_FOREACH(pe, &paths, entry) + free((char *)pe->path); + got_pathlist_free(&paths); + free(cwd); + free(id_str); + free(uuidstr); + return error; +}