commit - 113ee344094a73718b3461c8a36110c48107f67d
commit + 9587e6cc5169e6d39431fd1066097fd3f04e5d51
blob - ccdc977d9180a5385e27d7658840d68175702374
blob + a7f240fdbe0cd8fc14dc557ebfd851c1b0b51d58
--- got/got.1
+++ got/got.1
The maximum is 3.
.El
.Tg cy
-.It Cm cherrypick Ar commit
+.It Xo
+.Cm cherrypick
+.Op Fl lX
+.Ar commit
+.Xc
.Dl Pq alias: Cm cy
Merge changes from a single
.Ar commit
.Cm got cherrypick
commands,
committed with
-.Cm got commit ,
+.Cm got commit
+where the log message of the cherrypicked commit will appear in the editor,
or discarded again with
.Cm got revert .
.Pp
.Cm got update .
If any relevant files already contain merge conflicts, these
conflicts must be resolved first.
+.Pp
+The options for
+.Nm
+.Cm cherrypick
+are as follows:
+.Bl -tag -width Ds
+.It Fl l
+Display a list of commit log messages recorded by cherrypick operations,
+represented by references in the
+.Dq refs/got/worktree
+reference namespace.
+If a
+.Ar commit
+is specified, only show the log message of the specified commit.
+.Pp
+If invoked in a work tree, only log messages recorded by cherrypick operations
+in the current work tree will be displayed.
+Otherwise, all commit log messages will be displayed irrespective of the
+work tree in which they were created.
+This option cannot be used with
+.Cm X .
+.It Fl X
+Delete log messages created by previous cherrypick operations, represented by
+references in the
+.Dq refs/got/worktree
+reference namespace.
+If a
+.Ar commit
+is specified, only delete the log message of the specified commit.
+.Pp
+If invoked in a work tree, only log messages recorded by cherrypick operations
+in the current work tree will be deleted.
+Otherwise, all commit log messages will be deleted irrespective of the
+work tree in which they were created.
+This option cannot be used with
+.Cm l .
+.El
+.Pp
.Tg bo
-.It Cm backout Ar commit
+.It Xo
+.Cm backout
+.Op Fl lX
+.Ar commit
+.Xc
.Dl Pq alias: Cm bo
Reverse-merge changes from a single
.Ar commit
.Cm got backout
commands,
committed with
-.Cm got commit ,
+.Cm got commit
+where the log message of the backed-out commit will appear in the editor,
or discarded again with
.Cm got revert .
.Pp
.Cm got update .
If any relevant files already contain merge conflicts, these
conflicts must be resolved first.
+.Pp
+The options for
+.Nm
+.Cm backout
+are as follows:
+.Bl -tag -width Ds
+.It Fl l
+Display a list of commit log messages recorded by backout operations,
+represented by references in the
+.Dq refs/got/worktree
+reference namespace.
+If a
+.Ar commit
+is specified, only show the log message of the specified commit.
+.Pp
+If invoked in a work tree, only log messages recorded by backout operations
+in the current work tree will be displayed.
+Otherwise, all commit log messages will be displayed irrespective of the
+work tree in which they were created.
+This option cannot be used with
+.Cm X .
+.It Fl X
+Delete log messages created by previous backout operations, represented by
+references in the
+.Dq refs/got/worktree
+reference namespace.
+If a
+.Ar commit
+is specified, only delete the log message of the specified commit.
+.Pp
+If invoked in a work tree, only log messages recorded by backout operations
+in the current work tree will be deleted.
+Otherwise, all commit log messages will be deleted irrespective of the
+work tree in which they were created.
+This option cannot be used with
+.Cm l .
+.El
+.Pp
.Tg rb
.It Xo
.Cm rebase
blob - 50fb5ec76cf7c16aa5ecf3017aa935c3f4c6cffa
blob + 326ea521fa760c8c7507f7ebcb14de2cc202defc
--- got/got.c
+++ got/got.c
*choice = GOT_PATCH_CHOICE_QUIT;
return NULL;
+}
+
+/*
+ * Shortcut work tree status callback to determine if the set of
+ * paths scanned has at least one versioned path that is modified.
+ * Set arg and return GOT_ERR_FILE_MODIFIED as soon as a path is
+ * passed with a status that is neither unchanged nor unversioned.
+ */
+static const struct got_error *
+worktree_has_changed_path(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 *has_changes = arg;
+
+ if (status == staged_status && (status == GOT_STATUS_DELETE))
+ status = GOT_STATUS_NO_CHANGE;
+
+ if (!(status == GOT_STATUS_NO_CHANGE ||
+ status == GOT_STATUS_UNVERSIONED) ||
+ staged_status != GOT_STATUS_NO_CHANGE) {
+ *has_changes = 1;
+ return got_error(GOT_ERR_FILE_MODIFIED);
+ }
+
+ return NULL;
+}
+
+/*
+ * Check that the changeset of the commit identified by id is
+ * comprised of at least one path that is modified in the work tree.
+ */
+static const struct got_error *
+commit_path_changed_in_worktree(int *add_logmsg, struct got_object_id *id,
+ struct got_worktree *worktree, struct got_repository *repo)
+{
+ const struct got_error *err;
+ struct got_pathlist_head paths;
+ struct got_commit_object *commit = NULL, *pcommit = NULL;
+ struct got_tree_object *tree = NULL, *ptree = NULL;
+ struct got_object_qid *pid;
+
+ TAILQ_INIT(&paths);
+
+ err = got_object_open_as_commit(&commit, repo, id);
+ if (err)
+ goto done;
+
+ pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
+
+ err = got_object_open_as_commit(&pcommit, repo, &pid->id);
+ if (err)
+ goto done;
+
+ err = got_object_open_as_tree(&tree, repo,
+ got_object_commit_get_tree_id(commit));
+ if (err)
+ goto done;
+
+ err = got_object_open_as_tree(&ptree, repo,
+ got_object_commit_get_tree_id(pcommit));
+ if (err)
+ goto done;
+
+ err = got_diff_tree(ptree, tree, NULL, NULL, -1, -1, "", "", repo,
+ got_diff_tree_collect_changed_paths, &paths, 0);
+ if (err)
+ goto done;
+
+ err = got_worktree_status(worktree, &paths, repo, 0,
+ worktree_has_changed_path, add_logmsg, check_cancelled, NULL);
+ if (err && err->code == GOT_ERR_FILE_MODIFIED) {
+ /*
+ * At least one changed path in the referenced commit is
+ * modified in the work tree, that's all we need to know!
+ */
+ err = NULL;
+ }
+
+done:
+ got_pathlist_free(&paths, GOT_PATHLIST_FREE_ALL);
+ if (commit)
+ got_object_commit_close(commit);
+ if (pcommit)
+ got_object_commit_close(pcommit);
+ if (tree)
+ got_object_tree_close(tree);
+ if (ptree)
+ got_object_tree_close(ptree);
+ return err;
}
+/*
+ * Remove any "logmsg" reference comprised entirely of paths that have
+ * been reverted in this work tree. If any path in the logmsg ref changeset
+ * remains in a changed state in the worktree, do not remove the reference.
+ */
static const struct got_error *
+rm_logmsg_ref(struct got_worktree *worktree, struct got_repository *repo)
+{
+ const struct got_error *err;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re;
+ struct got_commit_object *commit = NULL;
+ struct got_object_id *commit_id = NULL;
+ char *uuidstr = NULL;
+
+ TAILQ_INIT(&refs);
+
+ err = got_worktree_get_uuid(&uuidstr, worktree);
+ if (err)
+ goto done;
+
+ err = got_ref_list(&refs, repo, "refs/got/worktree",
+ got_ref_cmp_by_name, repo);
+ if (err)
+ goto done;
+
+ TAILQ_FOREACH(re, &refs, entry) {
+ const char *refname;
+ int has_changes = 0;
+
+ refname = got_ref_get_name(re->ref);
+
+ if (!strncmp(refname, GOT_WORKTREE_CHERRYPICK_REF_PREFIX,
+ GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN))
+ refname += GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN + 1;
+ else if (!strncmp(refname, GOT_WORKTREE_BACKOUT_REF_PREFIX,
+ GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN))
+ refname += GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN + 1;
+ else
+ continue;
+
+ if (strncmp(refname, uuidstr, GOT_WORKTREE_UUID_STRLEN) == 0)
+ refname += GOT_WORKTREE_UUID_STRLEN + 1; /* skip '-' */
+ else
+ continue;
+
+ err = got_repo_match_object_id(&commit_id, NULL, refname,
+ GOT_OBJ_TYPE_COMMIT, NULL, repo);
+ if (err)
+ goto done;
+
+ err = got_object_open_as_commit(&commit, repo, commit_id);
+ if (err)
+ goto done;
+
+ err = commit_path_changed_in_worktree(&has_changes, commit_id,
+ worktree, repo);
+ if (err)
+ goto done;
+
+ if (!has_changes) {
+ err = got_ref_delete(re->ref, repo);
+ if (err)
+ goto done;
+ }
+
+ got_object_commit_close(commit);
+ commit = NULL;
+ free(commit_id);
+ commit_id = NULL;
+ }
+
+done:
+ free(uuidstr);
+ free(commit_id);
+ got_ref_list_free(&refs);
+ if (commit)
+ got_object_commit_close(commit);
+ return err;
+}
+
+static const struct got_error *
cmd_revert(int argc, char *argv[])
{
const struct got_error *error = NULL;
goto done;
}
}
- error = apply_unveil(got_repo_get_path(repo), 1,
+
+ /*
+ * XXX "c" perm needed on repo dir to delete merge references.
+ */
+ error = apply_unveil(got_repo_get_path(repo), 0,
got_worktree_get_root_path(worktree));
if (error)
goto done;
cpa.action = "revert";
error = got_worktree_revert(worktree, &paths, revert_progress, NULL,
pflag ? choose_patch : NULL, &cpa, repo);
+
+ error = rm_logmsg_ref(worktree, repo);
done:
if (patch_script_file && fclose(patch_script_file) == EOF &&
error == NULL)
}
static const struct got_error *
+cat_logmsg(FILE *f, struct got_commit_object *commit, const char *idstr,
+ const char *type, int has_content)
+{
+ const struct got_error *err = NULL;
+ char *logmsg = NULL;
+
+ err = got_object_commit_get_logmsg(&logmsg, commit);
+ if (err)
+ return err;
+
+ if (fprintf(f, "%s# log message of %s commit %s:%s",
+ has_content ? "\n" : "", type, idstr, logmsg) < 0)
+ err = got_ferror(f, GOT_ERR_IO);
+
+ free(logmsg);
+ return err;
+}
+
+/*
+ * Lookup "logmsg" references of backed-out and cherrypicked commits
+ * belonging to the current work tree. If found, and the worktree has
+ * at least one modified file that was changed in the referenced commit,
+ * add its log message to *logmsg. Add all refs found to matched_refs
+ * to be scheduled for removal on successful commit.
+ */
+static const struct got_error *
+lookup_logmsg_ref(char **logmsg, struct got_reflist_head *matched_refs,
+ struct got_worktree *worktree, struct got_repository *repo)
+{
+ const struct got_error *err;
+ struct got_commit_object *commit = NULL;
+ struct got_object_id *id = NULL;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re, *re_match;
+ FILE *f = NULL;
+ char *uuidstr = NULL;
+ int added_logmsg = 0;
+
+ TAILQ_INIT(&refs);
+
+ err = got_opentemp_named(logmsg, &f, "got-commit-logmsg", "");
+ if (err)
+ goto done;
+
+ err = got_worktree_get_uuid(&uuidstr, worktree);
+ if (err)
+ goto done;
+
+ err = got_ref_list(&refs, repo, "refs/got/worktree",
+ got_ref_cmp_by_name, repo);
+ if (err)
+ goto done;
+
+ TAILQ_FOREACH(re, &refs, entry) {
+ const char *refname, *type;
+ int add_logmsg = 0;
+
+ refname = got_ref_get_name(re->ref);
+
+ if (strncmp(refname, GOT_WORKTREE_CHERRYPICK_REF_PREFIX,
+ GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN) == 0) {
+ refname += GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN + 1;
+ type = "cherrypicked";
+ } else if (strncmp(refname, GOT_WORKTREE_BACKOUT_REF_PREFIX,
+ GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN) == 0) {
+ refname += GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN + 1;
+ type = "backed-out";
+ } else
+ continue;
+
+ if (strncmp(refname, uuidstr, GOT_WORKTREE_UUID_STRLEN) == 0)
+ refname += GOT_WORKTREE_UUID_STRLEN + 1; /* skip '-' */
+ else
+ continue;
+
+ err = got_repo_match_object_id(&id, NULL, refname,
+ GOT_OBJ_TYPE_COMMIT, NULL, repo);
+ if (err)
+ goto done;
+
+ err = got_object_open_as_commit(&commit, repo, id);
+ if (err)
+ goto done;
+
+ err = commit_path_changed_in_worktree(&add_logmsg, id,
+ worktree, repo);
+ if (err)
+ goto done;
+
+ if (add_logmsg) {
+ err = cat_logmsg(f, commit, refname, type,
+ added_logmsg);
+ if (err)
+ goto done;
+ if (!added_logmsg)
+ ++added_logmsg;
+
+ err = got_reflist_entry_dup(&re_match, re);
+ if (err)
+ goto done;
+ TAILQ_INSERT_HEAD(matched_refs, re_match, entry);
+ }
+
+ got_object_commit_close(commit);
+ commit = NULL;
+ free(id);
+ id = NULL;
+ }
+
+done:
+ free(id);
+ free(uuidstr);
+ got_ref_list_free(&refs);
+ if (commit)
+ got_object_commit_close(commit);
+ if (f && fclose(f) == EOF && err == NULL)
+ err = got_error_from_errno("fclose");
+ if (!added_logmsg) {
+ if (*logmsg && unlink(*logmsg) != 0 && err == NULL)
+ err = got_error_from_errno2("unlink", *logmsg);
+ *logmsg = NULL;
+ }
+ return err;
+}
+
+static const struct got_error *
cmd_commit(int argc, char *argv[])
{
const struct got_error *error = NULL;
int allow_bad_symlinks = 0, non_interactive = 0, merge_in_progress = 0;
int show_diff = 1;
struct got_pathlist_head paths;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re;
int *pack_fds = NULL;
+ TAILQ_INIT(&refs);
TAILQ_INIT(&paths);
cl_arg.logmsg_path = NULL;
if (error)
goto done;
+ if (prepared_logmsg == NULL) {
+ error = lookup_logmsg_ref(&prepared_logmsg, &refs,
+ worktree, repo);
+ if (error)
+ goto done;
+ }
+
cl_arg.editor = editor;
cl_arg.cmdline_log = logmsg;
cl_arg.prepared_log = prepared_logmsg;
if (error)
goto done;
printf("Created commit %s\n", id_str);
+
+ TAILQ_FOREACH(re, &refs, entry) {
+ error = got_ref_delete(re->ref, repo);
+ if (error)
+ goto done;
+ }
+
done:
if (preserve_logmsg) {
fprintf(stderr, "%s: log message preserved in %s\n",
if (error == NULL)
error = pack_err;
}
+ got_ref_list_free(&refs);
got_pathlist_free(&paths, GOT_PATHLIST_FREE_PATH);
free(cwd);
free(id_str);
free(server_path);
free(repo_name);
return error;
+}
+
+/*
+ * Print and if delete is set delete all ref_prefix references.
+ * If wanted_ref is not NULL, only print or delete this reference.
+ */
+static const struct got_error *
+process_logmsg_refs(const char *ref_prefix, size_t prefix_len,
+ const char *wanted_ref, int delete, struct got_worktree *worktree,
+ struct got_repository *repo)
+{
+ const struct got_error *err;
+ struct got_pathlist_head paths;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re;
+ struct got_reflist_object_id_map *refs_idmap = NULL;
+ struct got_commit_object *commit = NULL;
+ struct got_object_id *id = NULL;
+ char *uuidstr = NULL;
+ int found = 0;
+
+ TAILQ_INIT(&refs);
+ TAILQ_INIT(&paths);
+
+ err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, repo);
+ if (err)
+ goto done;
+
+ err = got_reflist_object_id_map_create(&refs_idmap, &refs, repo);
+ if (err)
+ goto done;
+
+ if (worktree != NULL) {
+ err = got_worktree_get_uuid(&uuidstr, worktree);
+ if (err)
+ goto done;
+ }
+
+ if (wanted_ref) {
+ if (strncmp(wanted_ref, "refs/heads/", 11) == 0)
+ wanted_ref += 11;
+ }
+
+ TAILQ_FOREACH(re, &refs, entry) {
+ const char *refname;
+
+ refname = got_ref_get_name(re->ref);
+
+ err = check_cancelled(NULL);
+ if (err)
+ goto done;
+
+ if (strncmp(refname, ref_prefix, prefix_len) == 0)
+ refname += prefix_len + 1; /* skip '-' delimiter */
+ else
+ continue;
+
+ if (worktree == NULL || strncmp(refname, uuidstr,
+ GOT_WORKTREE_UUID_STRLEN) == 0)
+ refname += GOT_WORKTREE_UUID_STRLEN + 1; /* skip '-' */
+ else
+ continue;
+
+ err = got_repo_match_object_id(&id, NULL, refname,
+ GOT_OBJ_TYPE_COMMIT, NULL, repo);
+ if (err)
+ goto done;
+
+ err = got_object_open_as_commit(&commit, repo, id);
+ if (err)
+ goto done;
+
+ if (wanted_ref)
+ found = strncmp(wanted_ref, refname,
+ strlen(wanted_ref)) == 0;
+ if (wanted_ref && !found) {
+ struct got_reflist_head *ci_refs;
+
+ ci_refs = got_reflist_object_id_map_lookup(refs_idmap,
+ id);
+
+ if (ci_refs) {
+ char *refs_str = NULL;
+ char const *r = NULL;
+
+ err = build_refs_str(&refs_str, ci_refs, id,
+ repo, 1);
+ if (err)
+ goto done;
+
+ r = refs_str;
+ while (r) {
+ if (strncmp(r, wanted_ref,
+ strlen(wanted_ref)) == 0) {
+ found = 1;
+ break;
+ }
+ r = strchr(r, ' ');
+ if (r)
+ ++r;
+ }
+ free(refs_str);
+ }
+ }
+
+ if (wanted_ref == NULL || found) {
+ if (delete) {
+ err = got_ref_delete(re->ref, repo);
+ if (err)
+ goto done;
+ printf("deleted: ");
+ err = print_commit_oneline(commit, id, repo,
+ refs_idmap);
+ } else {
+ /*
+ * Print paths modified by commit to help
+ * associate commits with worktree changes.
+ */
+ err = get_changed_paths(&paths, commit,
+ repo, NULL);
+ if (err)
+ goto done;
+
+ err = print_commit(commit, id, repo, NULL,
+ &paths, NULL, 0, 0, refs_idmap, NULL);
+ got_pathlist_free(&paths,
+ GOT_PATHLIST_FREE_ALL);
+ }
+ if (err || found)
+ goto done;
+ }
+
+ got_object_commit_close(commit);
+ commit = NULL;
+ free(id);
+ id = NULL;
+ }
+
+ if (wanted_ref != NULL && !found)
+ err = got_error_fmt(GOT_ERR_NOT_REF, "%s", wanted_ref);
+
+done:
+ free(id);
+ free(uuidstr);
+ got_ref_list_free(&refs);
+ got_pathlist_free(&paths, GOT_PATHLIST_FREE_ALL);
+ if (refs_idmap)
+ got_reflist_object_id_map_free(refs_idmap);
+ if (commit)
+ got_object_commit_close(commit);
+ return err;
}
+/*
+ * Create new temp "logmsg" ref of the backed-out or cherrypicked commit
+ * identified by id for log messages to prepopulate the editor on commit.
+ */
+static const struct got_error *
+logmsg_ref(struct got_object_id *id, const char *prefix,
+ struct got_worktree *worktree, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ char *idstr, *ref = NULL, *refname = NULL;
+ int histedit_in_progress;
+ int rebase_in_progress, merge_in_progress;
+
+ /*
+ * Silenty refuse to create merge reference if any histedit, merge,
+ * or rebase operation is in progress.
+ */
+ err = got_worktree_histedit_in_progress(&histedit_in_progress,
+ worktree);
+ if (err)
+ return err;
+ if (histedit_in_progress)
+ return NULL;
+
+ err = got_worktree_rebase_in_progress(&rebase_in_progress, worktree);
+ if (err)
+ return err;
+ if (rebase_in_progress)
+ return NULL;
+
+ err = got_worktree_merge_in_progress(&merge_in_progress, worktree,
+ repo);
+ if (err)
+ return err;
+ if (merge_in_progress)
+ return NULL;
+
+ err = got_object_id_str(&idstr, id);
+ if (err)
+ return err;
+
+ err = got_worktree_get_logmsg_ref_name(&refname, worktree, prefix);
+ if (err)
+ goto done;
+
+ if (asprintf(&ref, "%s-%s", refname, idstr) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ err = create_ref(ref, got_worktree_get_base_commit_id(worktree),
+ -1, repo);
+done:
+ free(ref);
+ free(idstr);
+ free(refname);
+ return err;
+}
+
__dead static void
usage_cherrypick(void)
{
- fprintf(stderr, "usage: %s cherrypick commit-id\n", getprogname());
+ fprintf(stderr, "usage: %s cherrypick [-lX] [commit-id]\n",
+ getprogname());
exit(1);
}
struct got_object_id *commit_id = NULL;
struct got_commit_object *commit = NULL;
struct got_object_qid *pid;
- int ch;
+ int ch, list_refs = 0, remove_refs = 0;
struct got_update_progress_arg upa;
int *pack_fds = NULL;
- while ((ch = getopt(argc, argv, "")) != -1) {
+ while ((ch = getopt(argc, argv, "lX")) != -1) {
switch (ch) {
+ case 'l':
+ list_refs = 1;
+ break;
+ case 'X':
+ remove_refs = 1;
+ break;
default:
usage_cherrypick();
/* NOTREACHED */
"unveil", NULL) == -1)
err(1, "pledge");
#endif
- if (argc != 1)
+ if (list_refs || remove_refs) {
+ if (argc != 0 && argc != 1)
+ usage_cherrypick();
+ } else if (argc != 1)
usage_cherrypick();
+ if (list_refs && remove_refs)
+ option_conflict('l', 'X');
cwd = getcwd(NULL, 0);
if (cwd == NULL) {
error = got_worktree_open(&worktree, cwd);
if (error) {
- if (error->code == GOT_ERR_NOT_WORKTREE)
- error = wrap_not_worktree_error(error, "cherrypick",
- cwd);
- goto done;
+ if (list_refs || remove_refs) {
+ if (error->code != GOT_ERR_NOT_WORKTREE)
+ goto done;
+ } else {
+ if (error->code == GOT_ERR_NOT_WORKTREE)
+ error = wrap_not_worktree_error(error,
+ "cherrypick", cwd);
+ goto done;
+ }
}
- error = got_repo_open(&repo, got_worktree_get_repo_path(worktree),
+ error = got_repo_open(&repo,
+ worktree ? got_worktree_get_repo_path(worktree) : cwd,
NULL, pack_fds);
if (error != NULL)
goto done;
error = apply_unveil(got_repo_get_path(repo), 0,
- got_worktree_get_root_path(worktree));
+ worktree ? got_worktree_get_root_path(worktree) : NULL);
if (error)
goto done;
+ if (list_refs || remove_refs) {
+ error = process_logmsg_refs(GOT_WORKTREE_CHERRYPICK_REF_PREFIX,
+ GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN,
+ argc == 1 ? argv[0] : NULL, remove_refs, worktree, repo);
+ goto done;
+ }
+
error = got_repo_match_object_id(&commit_id, NULL, argv[0],
GOT_OBJ_TYPE_COMMIT, NULL, repo);
if (error)
if (error != NULL)
goto done;
- if (upa.did_something)
+ if (upa.did_something) {
+ error = logmsg_ref(commit_id,
+ GOT_WORKTREE_CHERRYPICK_REF_PREFIX, worktree, repo);
+ if (error)
+ goto done;
printf("Merged commit %s\n", commit_id_str);
+ }
print_merge_progress_stats(&upa);
done:
if (commit)
__dead static void
usage_backout(void)
{
- fprintf(stderr, "usage: %s backout commit-id\n", getprogname());
+ fprintf(stderr, "usage: %s backout [-lX] [commit-id]\n", getprogname());
exit(1);
}
struct got_object_id *commit_id = NULL;
struct got_commit_object *commit = NULL;
struct got_object_qid *pid;
- int ch;
+ int ch, list_refs = 0, remove_refs = 0;
struct got_update_progress_arg upa;
int *pack_fds = NULL;
- while ((ch = getopt(argc, argv, "")) != -1) {
+ while ((ch = getopt(argc, argv, "lX")) != -1) {
switch (ch) {
+ case 'l':
+ list_refs = 1;
+ break;
+ case 'X':
+ remove_refs = 1;
+ break;
default:
usage_backout();
/* NOTREACHED */
"unveil", NULL) == -1)
err(1, "pledge");
#endif
- if (argc != 1)
+ if (list_refs || remove_refs) {
+ if (argc != 0 && argc != 1)
+ usage_backout();
+ } else if (argc != 1)
usage_backout();
+ if (list_refs && remove_refs)
+ option_conflict('l', 'X');
cwd = getcwd(NULL, 0);
if (cwd == NULL) {
error = got_worktree_open(&worktree, cwd);
if (error) {
- if (error->code == GOT_ERR_NOT_WORKTREE)
- error = wrap_not_worktree_error(error, "backout", cwd);
- goto done;
+ if (list_refs || remove_refs) {
+ if (error->code != GOT_ERR_NOT_WORKTREE)
+ goto done;
+ } else {
+ if (error->code == GOT_ERR_NOT_WORKTREE)
+ error = wrap_not_worktree_error(error,
+ "backout", cwd);
+ goto done;
+ }
}
- error = got_repo_open(&repo, got_worktree_get_repo_path(worktree),
+ error = got_repo_open(&repo,
+ worktree ? got_worktree_get_repo_path(worktree) : cwd,
NULL, pack_fds);
if (error != NULL)
goto done;
error = apply_unveil(got_repo_get_path(repo), 0,
- got_worktree_get_root_path(worktree));
+ worktree ? got_worktree_get_root_path(worktree) : NULL);
if (error)
goto done;
+ if (list_refs || remove_refs) {
+ error = process_logmsg_refs(GOT_WORKTREE_BACKOUT_REF_PREFIX,
+ GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN,
+ argc == 1 ? argv[0] : NULL, remove_refs, worktree, repo);
+ goto done;
+ }
+
error = got_repo_match_object_id(&commit_id, NULL, argv[0],
GOT_OBJ_TYPE_COMMIT, NULL, repo);
if (error)
if (error != NULL)
goto done;
- if (upa.did_something)
+ if (upa.did_something) {
+ error = logmsg_ref(commit_id, GOT_WORKTREE_BACKOUT_REF_PREFIX,
+ worktree, repo);
+ if (error)
+ goto done;
printf("Backed out commit %s\n", commit_id_str);
+ }
print_merge_progress_stats(&upa);
done:
if (commit)
}
static const struct got_error *
+worktree_has_logmsg_ref(const char *caller, struct got_worktree *worktree,
+ struct got_repository *repo)
+{
+ const struct got_error *err;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re;
+ char *uuidstr = NULL;
+ static char msg[160];
+
+ TAILQ_INIT(&refs);
+
+ err = got_worktree_get_uuid(&uuidstr, worktree);
+ if (err)
+ goto done;
+
+ err = got_ref_list(&refs, repo, "refs/got/worktree",
+ got_ref_cmp_by_name, repo);
+ if (err)
+ goto done;
+
+ TAILQ_FOREACH(re, &refs, entry) {
+ const char *cmd, *refname, *type;
+
+ refname = got_ref_get_name(re->ref);
+
+ if (strncmp(refname, GOT_WORKTREE_CHERRYPICK_REF_PREFIX,
+ GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN) == 0) {
+ refname += GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN + 1;
+ cmd = "cherrypick";
+ type = "cherrypicked";
+ } else if (strncmp(refname, GOT_WORKTREE_BACKOUT_REF_PREFIX,
+ GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN) == 0) {
+ refname += GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN + 1;
+ cmd = "backout";
+ type = "backed-out";
+ } else
+ continue;
+
+ if (strncmp(refname, uuidstr, GOT_WORKTREE_UUID_STRLEN) != 0)
+ continue;
+
+ snprintf(msg, sizeof(msg),
+ "work tree has references created by %s commits which "
+ "must be removed with 'got %s -X' before running the %s "
+ "command", type, cmd, caller);
+ err = got_error_msg(GOT_ERR_WORKTREE_META, msg);
+ goto done;
+ }
+
+done:
+ free(uuidstr);
+ got_ref_list_free(&refs);
+ return err;
+}
+
+static const struct got_error *
process_backup_refs(const char *backup_ref_prefix,
const char *wanted_branch_name,
int delete, struct got_repository *repo)
gitconfig_path, pack_fds);
if (error != NULL)
goto done;
+
+ if (worktree != NULL && !list_backups && !delete_backups) {
+ error = worktree_has_logmsg_ref("rebase", worktree, repo);
+ if (error)
+ goto done;
+ }
error = get_author(&committer, repo, worktree);
if (error && error->code != GOT_ERR_COMMIT_NO_AUTHOR)
gitconfig_path, pack_fds);
if (error != NULL)
goto done;
+
+ if (worktree != NULL && !list_backups && !delete_backups) {
+ error = worktree_has_logmsg_ref("histedit", worktree, repo);
+ if (error)
+ goto done;
+ }
error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree);
if (error)
if (error != NULL)
goto done;
+ if (worktree != NULL) {
+ error = worktree_has_logmsg_ref("merge", worktree, repo);
+ if (error)
+ goto done;
+ }
+
error = apply_unveil(got_repo_get_path(repo), 0,
worktree ? got_worktree_get_root_path(worktree) : NULL);
if (error)
blob - d3c26b9a044948f5b93e8f2f6adf673b8dd7e599
blob + 444d64fb9897a4e6ab7664069d364358ec7bfacb
--- include/got_worktree.h
+++ include/got_worktree.h
struct got_worktree *, const char *);
/*
+ * Prefix for references pointing at base commit of backout/cherrypick commits.
+ * Reference path takes the form: PREFIX-WORKTREE_UUID-COMMIT_ID
+ */
+#define GOT_WORKTREE_CHERRYPICK_REF_PREFIX "refs/got/worktree/cherrypick"
+#define GOT_WORKTREE_BACKOUT_REF_PREFIX "refs/got/worktree/backout"
+
+#define GOT_WORKTREE_CHERRYPICK_REF_PREFIX_LEN \
+ sizeof(GOT_WORKTREE_CHERRYPICK_REF_PREFIX) - 1
+#define GOT_WORKTREE_BACKOUT_REF_PREFIX_LEN \
+ sizeof(GOT_WORKTREE_BACKOUT_REF_PREFIX) - 1
+#define GOT_WORKTREE_UUID_STRLEN 36
+
+const struct got_error *got_worktree_get_logmsg_ref_name(char **,
+ struct got_worktree *, const char *);
+/*
* Get the name of a work tree's HEAD reference.
*/
const char *got_worktree_get_head_ref_name(struct got_worktree *);
blob - febf807fc9a1e5b9c2cdab208adc8217a492980e
blob + 68190506ee35314d0cd4d2e3db28b27330f1f887
--- lib/worktree.c
+++ lib/worktree.c
}
const struct got_error *
+got_worktree_get_logmsg_ref_name(char **refname, struct got_worktree *worktree,
+ const char *prefix)
+{
+ return get_ref_name(refname, worktree, prefix);
+}
+
+const struct got_error *
got_worktree_get_base_ref_name(char **refname, struct got_worktree *worktree)
{
return get_ref_name(refname, worktree, GOT_WORKTREE_BASE_REF_PREFIX);