Download raw body.
got backout/cherrypick metadata (for commit log messages)
On 23-01-26 08:34PM, Mark Jamsek wrote:
> On 23-01-26 09:50AM, Stefan Sperling wrote:
> > On Thu, Jan 26, 2023 at 06:04:43PM +1100, Mark Jamsek wrote:
> > > As a quick recap, when the user runs 'got {backout,cherrypick} commit',
> > > we create a "logmsg" reference pointing to the tree's base commit. What
> > > it points at is largely irrelevant for now, but the reference path takes
> > > the form:
> > >
> > > refs/got/worktree/cherrypick-${WORKTREE_UUID}-$cherry-picked-commit-id}
> > > refs/got/worktree/backout-${WORKTREE_UUID}-${backed-out-commit-id}
> > >
> > > As such, when the user next runs 'got commit', we parse the log
> > > message(s) from the ${backed-out,cherry-picked}-commit-id refs with
> > > which we prepopulate the editor. This way, if the user invokes N 'got
> > > {bo,cy}' commands, all N log messages will appear in the editor. This is
> > > the primary reason I think this idea of stsp's is better than the first.
> > > The other being what op observed: no change is required from the user;
> > > just cherrypick and commit!
> >
> > I think the manual page should talk about this at a higher level
> > of abstraction.
> >
> > Users won't know/care whether cherrypick commands created any references.
> > What matters to users is that Got will try to remember log messages of
> > cherrypicked commits and append any relevant log messages to the commit
> > message template.
> >
> > We could say something like:
> > [[[
> > .It Fl l
> > Display a list of commit log messages recorded by cherrypick operations,
> > represented by references in the "refs/got/worktree/" reference namespace.
> > ]]]
> >
> > Similar to how documentation of -X options for rebase/histedit just talks
> > about "backups" rather than references, and how the corresponding -l option
> > mentions how this is implemented, but otherwise keeps the high-level concepts
> > in focus.
>
> Good idea! I'll make this change in the next revision. Thanks for the
> feedback and also for making this change very straightforward :)
New diff below with your improvements to the documentation plus regress.
Just in case you've already reviewed the first diff, the only changes--
besides the addition to regress/cmdline--are to the options in got.1 as
per your suggestion (I think I used your wording verbatim), and
a copypasta mistake in got.c:process_logmsg_refs(), specifically this
line:
err = got_ref_list(&refs, repo, "refs/got/worktree",
got_ref_cmp_by_name, repo);
was changed to:
err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, repo);
Also, to aid review, the backout regress is quite literally a copypasta
of the cherrypick followed by s/cherrypick/backout because the interface
coverage is identical with these tests. I want to add more, but the diff
is becoming a bit outrageous :)
Besides which, some wider testing would also be nice, and I think this
is a good enough start. But if not, please just let me know and I'll
resubmit once I've covered the actual commit side too. Thanks!
diffstat /home/mark/src/got
M got/got.1 | 90+ 4-
M got/got.c | 688+ 22-
M include/got_worktree.h | 15+ 0-
M lib/worktree.c | 7+ 0-
M regress/cmdline/backout.sh | 279+ 0-
M regress/cmdline/cherrypick.sh | 279+ 0-
6 files changed, 1358 insertions(+), 26 deletions(-)
diff /home/mark/src/got
commit - 711bec03cb52c156a80885f2ee8ad7fce27dd57e
path + /home/mark/src/got
blob - ccdc977d9180a5385e27d7658840d68175702374
file + got/got.1
--- got/got.1
+++ got/got.1
@@ -1970,7 +1970,11 @@ The maximum is 3.
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
@@ -2001,7 +2005,8 @@ committed with
.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
@@ -2012,8 +2017,50 @@ conflicts must be resolved first.
.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
@@ -2044,7 +2091,8 @@ committed with
.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
@@ -2055,6 +2103,44 @@ conflicts must be resolved first.
.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
file + got/got.c
--- got/got.c
+++ got/got.c
@@ -8370,7 +8370,179 @@ static const struct got_error *
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;
@@ -8447,7 +8619,11 @@ cmd_revert(int argc, char *argv[])
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;
@@ -8489,6 +8665,8 @@ 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)
@@ -8670,6 +8848,132 @@ cmd_commit(int argc, char *argv[])
}
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;
@@ -8686,8 +8990,11 @@ cmd_commit(int argc, char *argv[])
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;
@@ -8809,6 +9116,13 @@ cmd_commit(int argc, char *argv[])
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;
@@ -8837,6 +9151,13 @@ done:
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",
@@ -8858,6 +9179,7 @@ done:
if (error == NULL)
error = pack_err;
}
+ got_ref_list_free(&refs);
got_pathlist_free(&paths, GOT_PATHLIST_FREE_PATH);
free(cwd);
free(id_str);
@@ -9437,10 +9759,221 @@ __dead static void
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);
}
@@ -9454,12 +9987,18 @@ cmd_cherrypick(int argc, char *argv[])
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 */
@@ -9474,8 +10013,13 @@ cmd_cherrypick(int argc, char *argv[])
"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) {
@@ -9489,22 +10033,35 @@ cmd_cherrypick(int argc, char *argv[])
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)
@@ -9524,8 +10081,13 @@ cmd_cherrypick(int argc, char *argv[])
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)
@@ -9551,7 +10113,7 @@ usage_backout(void)
__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);
}
@@ -9565,12 +10127,18 @@ cmd_backout(int argc, char *argv[])
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 */
@@ -9585,8 +10153,13 @@ cmd_backout(int argc, char *argv[])
"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) {
@@ -9600,21 +10173,35 @@ cmd_backout(int argc, char *argv[])
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)
@@ -9638,8 +10225,13 @@ cmd_backout(int argc, char *argv[])
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)
@@ -10100,6 +10692,62 @@ process_backup_refs(const char *backup_ref_prefix,
}
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)
@@ -10340,6 +10988,12 @@ cmd_rebase(int argc, char *argv[])
if (error != NULL)
goto done;
+ if (worktree != NULL && !list_backups && !delete_backups) {
+ error = worktree_has_logmsg_ref("rebase", worktree, repo);
+ if (error)
+ goto done;
+ }
+
error = get_author(&committer, repo, worktree);
if (error && error->code != GOT_ERR_COMMIT_NO_AUTHOR)
goto done;
@@ -11677,6 +12331,12 @@ cmd_histedit(int argc, char *argv[])
if (error != NULL)
goto done;
+ if (worktree != NULL && !list_backups && !delete_backups) {
+ error = worktree_has_logmsg_ref("histedit", worktree, repo);
+ if (error)
+ goto done;
+ }
+
error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree);
if (error)
goto done;
@@ -12310,6 +12970,12 @@ cmd_merge(int argc, char *argv[])
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
file + include/got_worktree.h
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -87,6 +87,21 @@ const struct got_error *got_worktree_match_path_prefix
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
file + lib/worktree.c
--- lib/worktree.c
+++ lib/worktree.c
@@ -2242,6 +2242,13 @@ got_worktree_get_base_ref_name(char **refname, struct
}
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);
blob - 02acb5b14f9b116de16f3ff0fe380929aada9a65
file + regress/cmdline/backout.sh
--- regress/cmdline/backout.sh
+++ regress/cmdline/backout.sh
@@ -242,8 +242,287 @@ test_parseargs "$@"
test_done "$testroot" 0
}
+test_backout_logmsg_ref() {
+ local testroot=`test_init backout_logmsg_ref`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/repo && git checkout -q -b newbranch)
+
+ echo "modified delta on branch" > $testroot/repo/gamma/delta
+ echo "modified alpha on branch" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on branch" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+
+ git_commit $testroot/repo -m "commit changes on newbranch"
+ local commit_time=`git_show_author_time $testroot/repo`
+ local branch_rev=`git_show_head $testroot/repo`
+
+ echo "modified new file on branch" > $testroot/repo/epsilon/new
+
+ git_commit $testroot/repo -m "commit modified new file on newbranch"
+ local commit_time2=`git_show_author_time $testroot/repo`
+ local branch_rev2=`git_show_head $testroot/repo`
+
+ (cd $testroot/wt && got backout $branch_rev > /dev/null)
+ (cd $testroot/wt && got backout $branch_rev2 > /dev/null)
+
+ # show all backout log message refs in the work tree
+ local sep="-----------------------------------------------"
+ local logmsg="commit changes on newbranch"
+ local changeset=" M alpha\n D beta\n A epsilon/new\n M gamma/delta"
+ local logmsg2="commit modified new file on newbranch"
+ local changeset2=" M epsilon/new"
+ local date=`date -u -r $commit_time +"%a %b %e %X %Y UTC"`
+ local date2=`date -u -r $commit_time2 +"%a %b %e %X %Y UTC"`
+ local ymd=`date -u -r $commit_time +"%F"`
+ local short_id=$(printf '%.7s' $branch_rev)
+ local ymd2=`date -u -r $commit_time2 +"%F"`
+ local short_id2="newbranch"
+ local sorted=$(printf "$branch_rev\n$branch_rev2" | sort)
+
+ for r in $sorted; do
+ echo $sep >> $testroot/stdout.expected
+ if [ $r == $branch_rev ]; then
+ echo "commit $r" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date" >> $testroot/stdout.expected
+ printf " \n $logmsg\n \n" >> $testroot/stdout.expected
+ printf "$changeset\n\n" >> $testroot/stdout.expected
+
+ # for forthcoming wt 'backout -X' test
+ echo "deleted: $ymd $short_id $logmsg" >> \
+ $testroot/stdout.wt_deleted
+ else
+ echo "commit $r (newbranch)" \
+ >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date2" >> $testroot/stdout.expected
+ printf " \n $logmsg2\n \n" >> $testroot/stdout.expected
+ printf "$changeset2\n\n" >> $testroot/stdout.expected
+
+ # for forthcoming wt 'backout -X' test
+ echo "deleted: $ymd2 $short_id2 $logmsg2" >> \
+ $testroot/stdout.wt_deleted
+ fi
+ done
+
+ (cd $testroot/wt && got backout -l > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # only show log message ref of the specified commit id
+ echo $sep > $testroot/stdout.expected
+ echo "commit $branch_rev" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date" >> $testroot/stdout.expected
+ printf " \n $logmsg\n \n" >> $testroot/stdout.expected
+ printf "$changeset\n\n" >> $testroot/stdout.expected
+
+ (cd $testroot/wt && got backout -l $branch_rev > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # only show log message ref of the specified symref
+ echo $sep > $testroot/stdout.expected
+ echo "commit $branch_rev2 (newbranch)" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date2" >> $testroot/stdout.expected
+ printf " \n $logmsg2\n \n" >> $testroot/stdout.expected
+ printf "$changeset2\n\n" >> $testroot/stdout.expected
+
+ (cd $testroot/wt && got backout -l "newbranch" > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # create a second work tree with backed-out commits and ensure
+ # bo -l within the new work tree only shows the refs it created
+ got checkout $testroot/repo $testroot/wt2 > /dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/repo && git checkout -q -b newbranch2)
+
+ echo "modified delta on branch2" > $testroot/repo/gamma/delta
+ echo "modified alpha on branch2" > $testroot/repo/alpha
+ echo "new file on branch2" > $testroot/repo/epsilon/new2
+ (cd $testroot/repo && git add epsilon/new2)
+
+ git_commit $testroot/repo -m "commit changes on newbranch2"
+ local b2_commit_time=`git_show_author_time $testroot/repo`
+ local branch2_rev=`git_show_head $testroot/repo`
+
+ echo "modified file new2 on branch2" > $testroot/repo/epsilon/new2
+
+ git_commit $testroot/repo -m "commit modified file new2 on newbranch2"
+ local b2_commit_time2=`git_show_author_time $testroot/repo`
+ local branch2_rev2=`git_show_head $testroot/repo`
+
+ (cd $testroot/wt2 && got backout $branch2_rev > /dev/null)
+ (cd $testroot/wt2 && got backout $branch2_rev2 > /dev/null)
+
+ local b2_logmsg="commit changes on newbranch2"
+ local b2_changeset=" M alpha\n A epsilon/new2\n M gamma/delta"
+ local b2_logmsg2="commit modified file new2 on newbranch2"
+ local b2_changeset2=" M epsilon/new2"
+ date=`date -u -r $b2_commit_time +"%a %b %e %X %Y UTC"`
+ date2=`date -u -r $b2_commit_time2 +"%a %b %e %X %Y UTC"`
+ sorted=$(printf "$branch2_rev\n$branch2_rev2" | sort)
+
+ echo -n > $testroot/stdout.expected
+ for r in $sorted; do
+ echo $sep >> $testroot/stdout.expected
+ if [ $r == $branch2_rev ]; then
+ echo "commit $r" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date" >> $testroot/stdout.expected
+ printf " \n $b2_logmsg\n \n" >> \
+ $testroot/stdout.expected
+ printf "$b2_changeset\n\n" >> \
+ $testroot/stdout.expected
+ else
+ echo "commit $r (newbranch2)" \
+ >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date2" >> $testroot/stdout.expected
+ printf " \n $b2_logmsg2\n \n" >> \
+ $testroot/stdout.expected
+ printf "$b2_changeset2\n\n" >> \
+ $testroot/stdout.expected
+ fi
+ done
+
+ (cd $testroot/wt2 && got backout -l > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # ensure both wt and wt2 logmsg refs can be retrieved from the repo
+ sorted=`printf \
+ "$branch_rev\n$branch_rev2\n$branch2_rev\n$branch2_rev2" | sort`
+
+ echo -n > $testroot/stdout.expected
+ for r in $sorted; do
+ echo "commit $r" >> $testroot/stdout.expected
+ done
+
+ (cd $testroot/repo && got backout -l | grep ^commit | \
+ sort | cut -f1,2 -d' ' > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # delete logmsg ref of the specified commit in work tree 2
+ ymd=`date -u -r $b2_commit_time +"%F"`
+ short_id=$(printf '%.7s' $branch2_rev)
+
+ echo "deleted: $ymd $short_id $b2_logmsg" > $testroot/stdout.expected
+ (cd $testroot/wt2 && got backout -X $branch2_rev > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # delete all logmsg refs in work tree 1
+ (cd $testroot && mv stdout.wt_deleted stdout.expected)
+ (cd $testroot/wt && got backout -X > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # confirm all work tree 1 refs were deleted
+ echo -n > $testroot/stdout.expected
+ (cd $testroot/wt && got backout -l > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # make sure the remaining ref in work tree 2 was not also deleted
+ echo $sep > $testroot/stdout.expected
+ echo "commit $branch2_rev2 (newbranch2)" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date2" >> $testroot/stdout.expected
+ printf " \n $b2_logmsg2\n \n" >> $testroot/stdout.expected
+ printf "$b2_changeset2\n\n" >> $testroot/stdout.expected
+
+ (cd $testroot/wt2 && got backout -l > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # ensure we can delete work tree refs from the repository dir
+ ymd=`date -u -r $b2_commit_time2 +"%F"`
+ echo "deleted: $ymd newbranch2 $b2_logmsg2" > $testroot/stdout.expected
+ (cd $testroot/repo && got backout -X > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+
+ test_done "$testroot" "$ret"
+}
+
test_parseargs "$@"
run_test test_backout_basic
run_test test_backout_edits_for_file_since_deleted
run_test test_backout_next_commit
run_test test_backout_umask
+run_test test_backout_logmsg_ref
blob - 464cd2159b38557b4fa61c2ac402ba344ba16d43
file + regress/cmdline/cherrypick.sh
--- regress/cmdline/cherrypick.sh
+++ regress/cmdline/cherrypick.sh
@@ -1725,6 +1725,284 @@ test_parseargs "$@"
test_done "$testroot" 0
}
+test_cherrypick_logmsg_ref() {
+ local testroot=`test_init cherrypick_logmsg_ref`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/repo && git checkout -q -b newbranch)
+
+ echo "modified delta on branch" > $testroot/repo/gamma/delta
+ echo "modified alpha on branch" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on branch" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+
+ git_commit $testroot/repo -m "commit changes on newbranch"
+ local commit_time=`git_show_author_time $testroot/repo`
+ local branch_rev=`git_show_head $testroot/repo`
+
+ echo "modified new file on branch" > $testroot/repo/epsilon/new
+
+ git_commit $testroot/repo -m "commit modified new file on newbranch"
+ local commit_time2=`git_show_author_time $testroot/repo`
+ local branch_rev2=`git_show_head $testroot/repo`
+
+ (cd $testroot/wt && got cherrypick $branch_rev > /dev/null)
+ (cd $testroot/wt && got cherrypick $branch_rev2 > /dev/null)
+
+ # show all log message refs in the work tree
+ local sep="-----------------------------------------------"
+ local logmsg="commit changes on newbranch"
+ local changeset=" M alpha\n D beta\n A epsilon/new\n M gamma/delta"
+ local logmsg2="commit modified new file on newbranch"
+ local changeset2=" M epsilon/new"
+ local date=`date -u -r $commit_time +"%a %b %e %X %Y UTC"`
+ local date2=`date -u -r $commit_time2 +"%a %b %e %X %Y UTC"`
+ local ymd=`date -u -r $commit_time +"%F"`
+ local short_id=$(printf '%.7s' $branch_rev)
+ local ymd2=`date -u -r $commit_time2 +"%F"`
+ local short_id2="newbranch"
+ local sorted=$(printf "$branch_rev\n$branch_rev2" | sort)
+
+ for r in $sorted; do
+ echo $sep >> $testroot/stdout.expected
+ if [ $r == $branch_rev ]; then
+ echo "commit $r" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date" >> $testroot/stdout.expected
+ printf " \n $logmsg\n \n" >> $testroot/stdout.expected
+ printf "$changeset\n\n" >> $testroot/stdout.expected
+
+ # for forthcoming wt 'cherrypick -X' test
+ echo "deleted: $ymd $short_id $logmsg" >> \
+ $testroot/stdout.wt_deleted
+ else
+ echo "commit $r (newbranch)" \
+ >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date2" >> $testroot/stdout.expected
+ printf " \n $logmsg2\n \n" >> $testroot/stdout.expected
+ printf "$changeset2\n\n" >> $testroot/stdout.expected
+
+ # for forthcoming wt 'cherrypick -X' test
+ echo "deleted: $ymd2 $short_id2 $logmsg2" >> \
+ $testroot/stdout.wt_deleted
+ fi
+ done
+
+ (cd $testroot/wt && got cherrypick -l > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # only show log message ref of the specified commit id
+ echo $sep > $testroot/stdout.expected
+ echo "commit $branch_rev" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date" >> $testroot/stdout.expected
+ printf " \n $logmsg\n \n" >> $testroot/stdout.expected
+ printf "$changeset\n\n" >> $testroot/stdout.expected
+
+ (cd $testroot/wt && got cherrypick -l $branch_rev > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # only show log message ref of the specified symref
+ echo $sep > $testroot/stdout.expected
+ echo "commit $branch_rev2 (newbranch)" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date2" >> $testroot/stdout.expected
+ printf " \n $logmsg2\n \n" >> $testroot/stdout.expected
+ printf "$changeset2\n\n" >> $testroot/stdout.expected
+
+ (cd $testroot/wt && got cherrypick -l "newbranch" > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # create a second work tree with cherrypicked commits and ensure
+ # cy -l within the new work tree only shows the refs it created
+ got checkout $testroot/repo $testroot/wt2 > /dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/repo && git checkout -q -b newbranch2)
+
+ echo "modified delta on branch2" > $testroot/repo/gamma/delta
+ echo "modified alpha on branch2" > $testroot/repo/alpha
+ echo "new file on branch2" > $testroot/repo/epsilon/new2
+ (cd $testroot/repo && git add epsilon/new2)
+
+ git_commit $testroot/repo -m "commit changes on newbranch2"
+ local b2_commit_time=`git_show_author_time $testroot/repo`
+ local branch2_rev=`git_show_head $testroot/repo`
+
+ echo "modified file new2 on branch2" > $testroot/repo/epsilon/new2
+
+ git_commit $testroot/repo -m "commit modified file new2 on newbranch2"
+ local b2_commit_time2=`git_show_author_time $testroot/repo`
+ local branch2_rev2=`git_show_head $testroot/repo`
+
+ (cd $testroot/wt2 && got cherrypick $branch2_rev > /dev/null)
+ (cd $testroot/wt2 && got cherrypick $branch2_rev2 > /dev/null)
+
+ local b2_logmsg="commit changes on newbranch2"
+ local b2_changeset=" M alpha\n A epsilon/new2\n M gamma/delta"
+ local b2_logmsg2="commit modified file new2 on newbranch2"
+ local b2_changeset2=" M epsilon/new2"
+ date=`date -u -r $b2_commit_time +"%a %b %e %X %Y UTC"`
+ date2=`date -u -r $b2_commit_time2 +"%a %b %e %X %Y UTC"`
+ sorted=$(printf "$branch2_rev\n$branch2_rev2" | sort)
+
+ echo -n > $testroot/stdout.expected
+ for r in $sorted; do
+ echo $sep >> $testroot/stdout.expected
+ if [ $r == $branch2_rev ]; then
+ echo "commit $r" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date" >> $testroot/stdout.expected
+ printf " \n $b2_logmsg\n \n" >> \
+ $testroot/stdout.expected
+ printf "$b2_changeset\n\n" >> \
+ $testroot/stdout.expected
+ else
+ echo "commit $r (newbranch2)" \
+ >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date2" >> $testroot/stdout.expected
+ printf " \n $b2_logmsg2\n \n" >> \
+ $testroot/stdout.expected
+ printf "$b2_changeset2\n\n" >> \
+ $testroot/stdout.expected
+ fi
+ done
+
+ (cd $testroot/wt2 && got cherrypick -l > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # ensure both wt and wt2 logmsg refs can be retrieved from the repo
+ sorted=`printf \
+ "$branch_rev\n$branch_rev2\n$branch2_rev\n$branch2_rev2" | sort`
+
+ echo -n > $testroot/stdout.expected
+ for r in $sorted; do
+ echo "commit $r" >> $testroot/stdout.expected
+ done
+
+ (cd $testroot/repo && got cherrypick -l | grep ^commit | \
+ sort | cut -f1,2 -d' ' > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # delete logmsg ref of the specified commit in work tree 2
+ ymd=`date -u -r $b2_commit_time +"%F"`
+ short_id=$(printf '%.7s' $branch2_rev)
+
+ echo "deleted: $ymd $short_id $b2_logmsg" > $testroot/stdout.expected
+ (cd $testroot/wt2 && got cherrypick -X $branch2_rev > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # delete all logmsg refs in work tree 1
+ (cd $testroot && mv stdout.wt_deleted stdout.expected)
+ (cd $testroot/wt && got cherrypick -X > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # confirm all work tree 1 refs were deleted
+ echo -n > $testroot/stdout.expected
+ (cd $testroot/wt && got cherrypick -l > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # make sure the remaining ref in work tree 2 was not also deleted
+ echo $sep > $testroot/stdout.expected
+ echo "commit $branch2_rev2 (newbranch2)" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $date2" >> $testroot/stdout.expected
+ printf " \n $b2_logmsg2\n \n" >> $testroot/stdout.expected
+ printf "$b2_changeset2\n\n" >> $testroot/stdout.expected
+
+ (cd $testroot/wt2 && got cherrypick -l > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # ensure we can delete work tree refs from the repository dir
+ ymd=`date -u -r $b2_commit_time2 +"%F"`
+ echo "deleted: $ymd newbranch2 $b2_logmsg2" > $testroot/stdout.expected
+ (cd $testroot/repo && got cherrypick -X > $testroot/stdout)
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+
+ test_done "$testroot" "$ret"
+}
+
test_parseargs "$@"
run_test test_cherrypick_basic
run_test test_cherrypick_root_commit
@@ -1743,3 +2021,4 @@ run_test test_cherrypick_umask
run_test test_cherrypick_dot_on_a_line_by_itself
run_test test_cherrypick_binary_file
run_test test_cherrypick_umask
+run_test test_cherrypick_logmsg_ref
--
Mark Jamsek <fnc.bsdbox.org>
GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68
got backout/cherrypick metadata (for commit log messages)