commit - f6794adc6b56432ea2c960ccef0b199d2441d395
commit + 0ebf8283cd162a594e725ca2a1fd9f16e6ece6e4
blob - 99596d6ea0e30d3b6d1eeaa28f571e10d4768f9a
blob + 59507d1863a1ad983bf5f3e0811d54e3c511f8f5
--- got/got.1
+++ got/got.1
.It Cm rb
Short alias for
.Cm rebase .
+.It Cm histedit [ Fl a ] [ Fl c] [ Fl F Ar histedit-script ]
+Edit commit history between the work tree's current base commit and
+the tip commit of the work tree's current branch.
+.Pp
+Editing of commit history is controlled via a
+.Ar histedit script
+which can be edited interactively or passed on the command line.
+The format of the histedit script is line-based.
+Each line in the script begins with a command name, followed by
+whitespace and an argument.
+For most commands, the expected argument is a commit ID SHA1 hash.
+Any remaining text on the line is ignored.
+Lines which begin with the
+.Sq #
+character are ignored entirely.
+.Pp
+The available commands are as follows:
+.Bl -column YXZ pick-commit
+.It pick Ar commit Ta Use the specified commit as it is.
+.It edit Ar commit Ta Use the specfified commit but once changes have been
+merged into the work tree interrupt the histedit operation for amending.
+.It fold Ar commit Ta Combine the specified commit with the next commit
+listed further below that will be used.
+.It drop Ar commit Ta Remove this commit from the edited history.
+.It mesg Ar log-message Ta Use the specified single-line log message for
+the commit on the previous line.
+If the log message argument is left empty, open an editor where a new
+log message can be written.
+.El
+.Pp
+Every commit in the history being edited must be mentioned in histedit script.
+Lines may be re-ordered to change the order of commits in the edited history.
+.Pp
+Edited commits are accumulated on a temporary branch.
+Once history editing has completed successfully, the temporary branch becomes
+the new version of the work tree's base branch and the work tree is
+automatically switched to it.
+.Pp
+While merging commits, show the status of each affected file,
+using the following status codes:
+.Bl -column YXZ description
+.It G Ta file was merged
+.It C Ta file was merged and conflicts occurred during merge
+.It ! Ta changes destined for a missing file were not merged
+.It D Ta file was deleted
+.It d Ta file's deletion was obstructed by local modifications
+.It A Ta new file was added
+.It ~ Ta changes destined for a non-regular file were not merged
+.El
+.Pp
+If merge conflicts occur the histedit operation is interrupted and may
+be continued once conflicts have been resolved.
+Alternatively, the histedit operation may be aborted which will leave
+the work tree switched back to its original branch.
+.Pp
+If a merge conflict is resolved in a way which renders the merged
+change into a no-op change, the corresponding commit will be elided
+when the histedit operation continues.
+.Pp
+.Cm got histedit
+will refuse to run if certain preconditions are not met.
+If the work tree contains multiple base commits it must first be updated
+to a single base commit with
+.Cm got update .
+If the work tree contains local changes, these changes must first be
+committed with
+.Cm got commit
+or reverted with
+.Cm got revert .
+If the edited history contains changes to files outside of the work tree's
+path prefix, the work tree cannot be used to edit the history of this branch.
+.Pp
+The
+.Cm got update
+and
+.Cm got commit
+commands will refuse to run while a histedit operation is in progress.
+Other commands which manipulate the work tree may be used for
+conflict resolution purposes.
+.Pp
+The options for
+.Cm got histedit
+are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Abort an interrupted histedit operation.
+If this option is used, no further command-line arguments are allowed.
+.It Fl c
+Continue an interrupted histedit operation.
+If this option is used, no further command-line arguments are allowed.
.El
+.It Cm he
+Short alias for
+.Cm histedit .
+.El
.Sh ENVIRONMENT
.Bl -tag -width GOT_AUTHOR
.It Ev GOT_AUTHOR
.Pp
.Dl $ got diff master unified-buffer-cache > /tmp/ubc.diff
.Pp
+Edit the entire commit history of the
+.Dq unified-buffer-cache
+branch:
+.Pp
+.Dl $ got update -b unified-buffer-cache
+.Dl $ got update -c master
+.Dl $ got histedit
+.Pp
.Sh SEE ALSO
.Xr tog 1 ,
.Xr git-repository 5 ,
blob - 7d190467b8b7bb62264d404054183db585d304c5
blob + 3dd118c77e4b297b156ed437c7928b5f83cc302b
--- got/got.c
+++ got/got.c
__dead static void usage_cherrypick(void);
__dead static void usage_backout(void);
__dead static void usage_rebase(void);
+__dead static void usage_histedit(void);
static const struct got_error* cmd_init(int, char *[]);
static const struct got_error* cmd_import(int, char *[]);
static const struct got_error* cmd_cherrypick(int, char *[]);
static const struct got_error* cmd_backout(int, char *[]);
static const struct got_error* cmd_rebase(int, char *[]);
+static const struct got_error* cmd_histedit(int, char *[]);
static struct got_cmd got_commands[] = {
{ "init", cmd_init, usage_init, "" },
{ "cherrypick", cmd_cherrypick, usage_cherrypick, "cy" },
{ "backout", cmd_backout, usage_backout, "bo" },
{ "rebase", cmd_rebase, usage_rebase, "rb" },
+ { "histedit", cmd_histedit, usage_histedit, "he" },
};
static void
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;
}
char *commit_id_str = NULL;
const char *branch_name = NULL;
struct got_reference *head_ref = NULL;
- int ch, did_something = 0, rebase_in_progress;
+ int ch, did_something = 0;
while ((ch = getopt(argc, argv, "b:c:")) != -1) {
switch (ch) {
if (error)
goto done;
- error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree);
+ error = check_rebase_or_histedit_in_progress(worktree);
if (error)
- goto done;
- if (rebase_in_progress) {
- error = got_error(GOT_ERR_REBASING);
goto done;
- }
if (argc == 0) {
path = strdup("");
const char *got_author = getenv("GOT_AUTHOR");
struct collect_commit_logmsg_arg cl_arg;
char *editor = NULL;
- int ch, rebase_in_progress;
+ int ch;
while ((ch = getopt(argc, argv, "m:")) != -1) {
switch (ch) {
if (error)
goto done;
- error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree);
+ error = check_rebase_or_histedit_in_progress(worktree);
if (error)
- goto done;
- if (rebase_in_progress) {
- error = got_error(GOT_ERR_REBASING);
goto done;
- }
error = got_repo_open(&repo, got_worktree_get_repo_path(worktree));
if (error != NULL)
fprintf(stderr, "usage: %s rebase [-a] | [-c] | branch\n",
getprogname());
exit(1);
+}
+
+void
+trim_logmsg(char *logmsg, int limit)
+{
+ char *nl;
+ size_t len;
+
+ len = strlen(logmsg);
+ if (len > limit)
+ len = limit;
+ logmsg[len] = '\0';
+ nl = strchr(logmsg, '\n');
+ if (nl)
+ *nl = '\0';
+}
+
+static const struct got_error *
+get_short_logmsg(char **logmsg, int limit, struct got_commit_object *commit)
+{
+ const char *logmsg0 = NULL;
+
+ logmsg0 = got_object_commit_get_logmsg(commit);
+
+ while (isspace((unsigned char)logmsg0[0]))
+ logmsg0++;
+
+ *logmsg = strdup(logmsg0);
+ if (*logmsg == NULL)
+ return got_error_from_errno("strdup");
+
+ trim_logmsg(*logmsg, limit);
+ return NULL;
}
static const struct got_error *
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;
- char *logmsg0 = NULL, *logmsg, *nl;
- size_t len;
+ 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;
}
- logmsg0 = strdup(got_object_commit_get_logmsg(commit));
- if (logmsg0 == NULL) {
- err = got_error_from_errno("strdup");
- goto done;
- }
- logmsg = logmsg0;
-
- while (isspace((unsigned char)logmsg[0]))
- logmsg++;
-
old_id_str[12] = '\0';
if (new_id_str)
new_id_str[12] = '\0';
- len = strlen(logmsg);
- if (len > 42)
- len = 42;
- logmsg[len] = '\0';
- nl = strchr(logmsg, '\n');
- if (nl)
- *nl = '\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(logmsg0);
return err;
}
static const struct got_error *
rebase_commit(struct got_pathlist_head *merged_paths,
struct got_worktree *worktree, struct got_reference *tmp_branch,
- struct got_object_id *commit_id, struct got_repository *repo)
+ struct got_object_id *commit_id, struct got_repository *repo)
{
const struct got_error *error;
struct got_commit_object *commit;
if (repo)
got_repo_close(repo);
return error;
+}
+
+__dead static void
+usage_histedit(void)
+{
+ fprintf(stderr, "usage: %s histedit [-a] [-c] [-F path]\n",
+ getprogname());
+ exit(1);
+}
+
+#define GOT_HISTEDIT_PICK 'p'
+#define GOT_HISTEDIT_EDIT 'e'
+#define GOT_HISTEDIT_FOLD 'f'
+#define GOT_HISTEDIT_DROP 'd'
+#define GOT_HISTEDIT_MESG 'm'
+
+static struct got_histedit_cmd {
+ unsigned char code;
+ const char *name;
+ const char *desc;
+} got_histedit_cmds[] = {
+ { GOT_HISTEDIT_PICK, "pick", "use commit" },
+ { GOT_HISTEDIT_EDIT, "edit", "use commit but stop for amending" },
+ { GOT_HISTEDIT_FOLD, "fold", "combine with commit below" },
+ { GOT_HISTEDIT_DROP, "drop", "remove commit from history" },
+ { GOT_HISTEDIT_MESG, "mesg",
+ "single-line log message for commit above (open editor if empty)" },
+};
+
+struct got_histedit_list_entry {
+ TAILQ_ENTRY(got_histedit_list_entry) entry;
+ struct got_object_id *commit_id;
+ const struct got_histedit_cmd *cmd;
+ char *logmsg;
+};
+TAILQ_HEAD(got_histedit_list, got_histedit_list_entry);
+
+static const struct got_error *
+histedit_write_commit(struct got_object_id *commit_id, const char *cmdname,
+ FILE *f, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ char *logmsg = NULL, *id_str = NULL;
+ struct got_commit_object *commit = NULL;
+ size_t n;
+
+ err = got_object_open_as_commit(&commit, repo, commit_id);
+ if (err)
+ goto done;
+
+ err = get_short_logmsg(&logmsg, 34, commit);
+ if (err)
+ goto done;
+
+ err = got_object_id_str(&id_str, commit_id);
+ if (err)
+ goto done;
+
+ n = fprintf(f, "%s %s %s\n", cmdname, id_str, logmsg);
+ if (n < 0)
+ err = got_ferror(f, GOT_ERR_IO);
+done:
+ if (commit)
+ got_object_commit_close(commit);
+ free(id_str);
+ free(logmsg);
+ return err;
+}
+
+static const struct got_error *
+histedit_write_commit_list(struct got_object_id_queue *commits, FILE *f,
+ struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ struct got_object_qid *qid;
+
+ if (SIMPLEQ_EMPTY(commits))
+ return got_error(GOT_ERR_EMPTY_HISTEDIT);
+
+ SIMPLEQ_FOREACH(qid, commits, entry) {
+ err = histedit_write_commit(qid->id, got_histedit_cmds[0].name,
+ f, repo);
+ if (err)
+ break;
+ }
+
+ return err;
+}
+
+static const struct got_error *
+write_cmd_list(FILE *f)
+{
+ const struct got_error *err = NULL;
+ int n, i;
+
+ n = fprintf(f, "# Available histedit commands:\n");
+ if (n < 0)
+ return got_ferror(f, GOT_ERR_IO);
+
+ for (i = 0; i < nitems(got_histedit_cmds); i++) {
+ struct got_histedit_cmd *cmd = &got_histedit_cmds[i];
+ n = fprintf(f, "# %s (%c): %s\n", cmd->name, cmd->code,
+ cmd->desc);
+ if (n < 0) {
+ err = got_ferror(f, GOT_ERR_IO);
+ break;
+ }
+ }
+ n = fprintf(f, "# Commits will be processed in order from top to "
+ "bottom of this file.\n");
+ if (n < 0)
+ return got_ferror(f, GOT_ERR_IO);
+ return err;
+}
+
+static const struct got_error *
+histedit_syntax_error(int lineno)
+{
+ static char msg[42];
+ int ret;
+
+ ret = snprintf(msg, sizeof(msg), "histedit syntax error on line %d",
+ lineno);
+ if (ret == -1 || ret >= sizeof(msg))
+ return got_error(GOT_ERR_HISTEDIT_SYNTAX);
+
+ return got_error_msg(GOT_ERR_HISTEDIT_SYNTAX, msg);
+}
+
+static const struct got_error *
+append_folded_commit_msg(char **new_msg, struct got_histedit_list_entry *hle,
+ char *logmsg, struct got_repository *repo)
+{
+ const struct got_error *err;
+ struct got_commit_object *folded_commit = NULL;
+ char *id_str;
+
+ err = got_object_id_str(&id_str, hle->commit_id);
+ if (err)
+ return err;
+
+ err = got_object_open_as_commit(&folded_commit, repo, hle->commit_id);
+ if (err)
+ goto done;
+
+ if (asprintf(new_msg, "%s%s# log message of folded commit %s: %s",
+ logmsg ? logmsg : "", logmsg ? "\n" : "", id_str,
+ got_object_commit_get_logmsg(folded_commit)) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+done:
+ if (folded_commit)
+ got_object_commit_close(folded_commit);
+ free(id_str);
+ return err;
+}
+
+static struct got_histedit_list_entry *
+get_folded_commits(struct got_histedit_list_entry *hle)
+{
+ struct got_histedit_list_entry *prev, *folded = NULL;
+
+ prev = TAILQ_PREV(hle, got_histedit_list, entry);
+ while (prev && prev->cmd->code == GOT_HISTEDIT_FOLD) {
+ folded = prev;
+ prev = TAILQ_PREV(prev, got_histedit_list, entry);
+ }
+
+ return folded;
+}
+
+static const struct got_error *
+histedit_edit_logmsg(struct got_histedit_list_entry *hle,
+ struct got_repository *repo)
+{
+ char *logmsg_path = NULL, *id_str = NULL;
+ char *logmsg = NULL, *new_msg = NULL, *editor = NULL;
+ const struct got_error *err = NULL;
+ struct got_commit_object *commit = NULL;
+ int fd;
+ struct got_histedit_list_entry *folded = NULL;
+
+ err = got_object_open_as_commit(&commit, repo, hle->commit_id);
+ if (err)
+ return err;
+
+ folded = get_folded_commits(hle);
+ if (folded) {
+ while (folded != hle) {
+ err = append_folded_commit_msg(&new_msg, folded,
+ logmsg, repo);
+ if (err)
+ goto done;
+ free(logmsg);
+ logmsg = new_msg;
+ folded = TAILQ_NEXT(folded, entry);
+ }
+ }
+
+ err = got_object_id_str(&id_str, hle->commit_id);
+ if (err)
+ goto done;
+ if (asprintf(&new_msg,
+ "%s\n# original log message of commit %s: %s",
+ logmsg ? logmsg : "", id_str,
+ got_object_commit_get_logmsg(commit)) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+ free(logmsg);
+ logmsg = new_msg;
+
+ err = got_object_id_str(&id_str, hle->commit_id);
+ if (err)
+ goto done;
+
+ err = got_opentemp_named_fd(&logmsg_path, &fd, "/tmp/got-logmsg");
+ if (err)
+ goto done;
+
+ dprintf(fd, logmsg);
+ close(fd);
+
+ err = get_editor(&editor);
+ if (err)
+ goto done;
+
+ err = edit_logmsg(&hle->logmsg, editor, logmsg_path, logmsg);
+ if (err) {
+ if (err->code != GOT_ERR_COMMIT_MSG_EMPTY)
+ goto done;
+ err = NULL;
+ hle->logmsg = strdup(got_object_commit_get_logmsg(commit));
+ if (hle->logmsg == NULL)
+ err = got_error_from_errno("strdup");
+ }
+done:
+ if (logmsg_path && unlink(logmsg_path) != 0 && err == NULL)
+ err = got_error_from_errno2("unlink", logmsg_path);
+ free(logmsg_path);
+ free(logmsg);
+ free(editor);
+ if (commit)
+ got_object_commit_close(commit);
+ return err;
}
+
+static const struct got_error *
+histedit_parse_list(struct got_histedit_list *histedit_cmds,
+ FILE *f, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ char *line = NULL, *p, *end;
+ size_t size;
+ ssize_t len;
+ int lineno = 0, i;
+ const struct got_histedit_cmd *cmd;
+ struct got_object_id *commit_id = NULL;
+ struct got_histedit_list_entry *hle = NULL;
+
+ for (;;) {
+ len = getline(&line, &size, f);
+ if (len == -1) {
+ const struct got_error *getline_err;
+ if (feof(f))
+ break;
+ getline_err = got_error_from_errno("getline");
+ err = got_ferror(f, getline_err->code);
+ break;
+ }
+ lineno++;
+ p = line;
+ while (isspace((unsigned char)p[0]))
+ p++;
+ if (p[0] == '#' || p[0] == '\0') {
+ free(line);
+ line = NULL;
+ continue;
+ }
+ cmd = NULL;
+ for (i = 0; i < nitems(got_histedit_cmds); i++) {
+ cmd = &got_histedit_cmds[i];
+ if (strncmp(cmd->name, p, strlen(cmd->name)) == 0 &&
+ isspace((unsigned char)p[strlen(cmd->name)])) {
+ p += strlen(cmd->name);
+ break;
+ }
+ if (p[0] == cmd->code && isspace((unsigned char)p[1])) {
+ p++;
+ break;
+ }
+ }
+ if (cmd == NULL) {
+ 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_run_editor(struct got_histedit_list *histedit_cmds,
+ const char *path, 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);
+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 char *,
+ struct got_object_id_queue *, const char *, struct got_repository *);
+
+static const struct got_error *
+histedit_edit_script(struct got_histedit_list *histedit_cmds,
+ struct got_object_id_queue *commits, struct got_repository *repo)
+{
+ const struct got_error *err;
+ FILE *f = NULL;
+ char *path = NULL;
+
+ err = got_opentemp_named(&path, &f, "got-histedit");
+ if (err)
+ return err;
+
+ err = write_cmd_list(f);
+ if (err)
+ goto done;
+
+ err = histedit_write_commit_list(commits, f, repo);
+ if (err)
+ goto done;
+
+ if (fclose(f) != 0) {
+ err = got_error_from_errno("fclose");
+ goto done;
+ }
+ f = NULL;
+
+ err = histedit_run_editor(histedit_cmds, path, repo);
+ if (err) {
+ const char *errmsg = err->msg;
+ if (err->code != GOT_ERR_HISTEDIT_SYNTAX)
+ goto done;
+ err = histedit_edit_list_retry(histedit_cmds, errmsg,
+ commits, path, repo);
+ }
+done:
+ if (f && fclose(f) != 0 && err == NULL)
+ err = got_error_from_errno("fclose");
+ if (path && unlink(path) != 0 && err == NULL)
+ err = got_error_from_errno2("unlink", path);
+ free(path);
+ return err;
+}
+
+static const struct got_error *
+histedit_save_list(struct got_histedit_list *histedit_cmds,
+ struct got_worktree *worktree, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ char *path = NULL;
+ FILE *f = NULL;
+ struct got_histedit_list_entry *hle;
+ struct got_commit_object *commit = NULL;
+
+ err = got_worktree_get_histedit_list_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;
+}
+
+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 char *errmsg, struct got_object_id_queue *commits,
+ const char *path, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ int resp = ' ';
+
+ while (resp != 'c' && resp != 'r' && resp != 'q') {
+ printf("%s: %s\n(c)ontinue editing, (r)estart editing, "
+ "or (a)bort: ", getprogname(), errmsg);
+ resp = getchar();
+ switch (resp) {
+ case 'c':
+ err = histedit_run_editor(histedit_cmds, path, repo);
+ break;
+ case 'r':
+ err = histedit_edit_script(histedit_cmds,
+ commits, repo);
+ break;
+ case 'a':
+ err = got_error(GOT_ERR_HISTEDIT_CANCEL);
+ break;
+ default:
+ printf("invalid response '%c'\n", resp);
+ break;
+ }
+ }
+
+ return err;
+}
+
+static const struct got_error *
+histedit_complete(struct got_worktree *worktree,
+ 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, 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_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, 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 *
+histedit_check_script(struct got_histedit_list *histedit_cmds,
+ struct got_object_id_queue *commits, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ struct got_object_qid *qid;
+ struct got_histedit_list_entry *hle;
+ static char msg[80];
+ char *id_str;
+
+ if (TAILQ_EMPTY(histedit_cmds))
+ return got_error(GOT_ERR_EMPTY_HISTEDIT);
+
+ SIMPLEQ_FOREACH(qid, commits, entry) {
+ TAILQ_FOREACH(hle, histedit_cmds, entry) {
+ if (got_object_id_cmp(qid->id, hle->commit_id) == 0)
+ break;
+ }
+ if (hle == NULL) {
+ err = got_object_id_str(&id_str, qid->id);
+ if (err)
+ return err;
+ snprintf(msg, sizeof(msg),
+ "commit %s missing from histedit script", id_str);
+ free(id_str);
+ return got_error_msg(GOT_ERR_HISTEDIT_CMD, msg);
+ }
+ }
+
+ if (hle->cmd->code == GOT_HISTEDIT_FOLD)
+ return got_error_msg(GOT_ERR_HISTEDIT_CMD,
+ "last commit in histedit script cannot be folded");
+
+ return NULL;
+}
+
+static const struct got_error *
+cmd_histedit(int argc, char *argv[])
+{
+ const struct got_error *error = NULL;
+ struct got_worktree *worktree = NULL;
+ struct got_repository *repo = NULL;
+ char *cwd = NULL;
+ struct got_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;
+ int edit_in_progress = 0, abort_edit = 0, continue_edit = 0;
+ const char *edit_script_path = NULL;
+ unsigned char rebase_status = GOT_STATUS_NO_CHANGE;
+ struct got_object_id_queue commits;
+ struct got_pathlist_head merged_paths;
+ const struct got_object_id_queue *parent_ids;
+ struct got_object_qid *pid;
+ struct got_histedit_list histedit_cmds;
+ struct got_histedit_list_entry *hle;
+
+ SIMPLEQ_INIT(&commits);
+ TAILQ_INIT(&histedit_cmds);
+ TAILQ_INIT(&merged_paths);
+
+ while ((ch = getopt(argc, argv, "acF:")) != -1) {
+ switch (ch) {
+ case 'a':
+ abort_edit = 1;
+ break;
+ case 'c':
+ continue_edit = 1;
+ break;
+ case 'F':
+ edit_script_path = optarg;
+ break;
+ default:
+ usage_histedit();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+#ifndef PROFILE
+ if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd "
+ "unveil", NULL) == -1)
+ err(1, "pledge");
+#endif
+ if (abort_edit && continue_edit)
+ usage_histedit();
+ if (argc != 0)
+ usage_histedit();
+
+ /*
+ * This command cannot apply unveil(2) in all cases because the
+ * user may choose to run an editor to edit the histedit script
+ * and to edit individual commit log messages.
+ * unveil(2) traverses exec(2); if an editor is used we have to
+ * apply unveil after edit script and log messages have been written.
+ * XXX TODO: Make use of unveil(2) where possible.
+ */
+
+ cwd = getcwd(NULL, 0);
+ if (cwd == NULL) {
+ error = got_error_from_errno("getcwd");
+ goto done;
+ }
+ error = got_worktree_open(&worktree, cwd);
+ if (error)
+ goto done;
+
+ error = got_repo_open(&repo, got_worktree_get_repo_path(worktree));
+ if (error != NULL)
+ goto done;
+
+ error = got_worktree_rebase_in_progress(&rebase_in_progress, worktree);
+ if (error)
+ goto done;
+ if (rebase_in_progress) {
+ error = got_error(GOT_ERR_REBASING);
+ goto done;
+ }
+
+ error = got_worktree_histedit_in_progress(&edit_in_progress, worktree);
+ if (error)
+ goto done;
+
+ if (edit_in_progress && abort_edit) {
+ int did_something;
+ error = got_worktree_histedit_continue(&resume_commit_id,
+ &tmp_branch, &branch, &base_commit_id, 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, repo,
+ branch, base_commit_id, update_progress, &did_something);
+ if (error)
+ goto done;
+ printf("Histedit of %s aborted\n",
+ got_ref_get_symref_target(branch));
+ goto done; /* nothing else to do */
+ } else if (abort_edit) {
+ error = got_error(GOT_ERR_NOT_HISTEDIT);
+ goto done;
+ }
+
+ if (continue_edit) {
+ char *path;
+
+ if (!edit_in_progress) {
+ error = got_error(GOT_ERR_NOT_HISTEDIT);
+ goto done;
+ }
+
+ error = got_worktree_get_histedit_list_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, 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);
+ error = collect_commits_to_rebase(&commits, head_commit_id,
+ pid->id, base_commit_id,
+ got_worktree_get_path_prefix(worktree), repo);
+ got_object_commit_close(commit);
+ commit = NULL;
+ if (error)
+ goto done;
+ } else {
+ if (edit_in_progress) {
+ error = got_error(GOT_ERR_HISTEDIT_BUSY);
+ goto done;
+ }
+
+ error = got_ref_open(&branch, repo,
+ got_worktree_get_head_ref_name(worktree), 0);
+ if (error != NULL)
+ goto done;
+
+ error = got_ref_resolve(&head_commit_id, repo, branch);
+ 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);
+ error = collect_commits_to_rebase(&commits, head_commit_id,
+ pid->id, got_worktree_get_base_commit_id(worktree),
+ got_worktree_get_path_prefix(worktree), repo);
+ got_object_commit_close(commit);
+ commit = NULL;
+ if (error)
+ goto done;
+
+ if (edit_script_path) {
+ error = histedit_load_list(&histedit_cmds,
+ edit_script_path, repo);
+ if (error)
+ goto done;
+ } else {
+ error = histedit_edit_script(&histedit_cmds, &commits,
+ repo);
+ if (error)
+ goto done;
+
+ }
+
+ error = histedit_save_list(&histedit_cmds, worktree,
+ repo);
+ if (error)
+ goto done;
+
+ error = got_worktree_histedit_prepare(&tmp_branch, &branch,
+ &base_commit_id, worktree, repo);
+ if (error)
+ goto done;
+
+ }
+
+ error = histedit_check_script(&histedit_cmds, &commits, repo);
+ if (error)
+ goto done;
+
+ TAILQ_FOREACH(hle, &histedit_cmds, entry) {
+ if (resume_commit_id) {
+ if (got_object_id_cmp(hle->commit_id,
+ resume_commit_id) != 0)
+ continue;
+
+ resume_commit_id = NULL;
+ if (hle->cmd->code == GOT_HISTEDIT_DROP ||
+ hle->cmd->code == GOT_HISTEDIT_FOLD) {
+ error = histedit_skip_commit(hle, worktree,
+ repo);
+ } else {
+ error = histedit_commit(NULL, worktree,
+ tmp_branch, hle, repo);
+ }
+ if (error)
+ goto done;
+ continue;
+ }
+
+ if (hle->cmd->code == GOT_HISTEDIT_DROP) {
+ error = histedit_skip_commit(hle, worktree, repo);
+ if (error)
+ goto done;
+ continue;
+ }
+
+ error = got_object_open_as_commit(&commit, repo,
+ hle->commit_id);
+ if (error)
+ goto done;
+ parent_ids = got_object_commit_get_parent_ids(commit);
+ pid = SIMPLEQ_FIRST(parent_ids);
+
+ error = got_worktree_histedit_merge_files(&merged_paths,
+ worktree, pid->id, hle->commit_id, repo, rebase_progress,
+ &rebase_status, check_cancelled, NULL);
+ if (error)
+ goto done;
+ got_object_commit_close(commit);
+ commit = NULL;
+
+ if (rebase_status == GOT_STATUS_CONFLICT) {
+ got_worktree_rebase_pathlist_free(&merged_paths);
+ break;
+ }
+
+ if (hle->cmd->code == GOT_HISTEDIT_EDIT) {
+ char *id_str;
+ error = got_object_id_str(&id_str, hle->commit_id);
+ if (error)
+ goto done;
+ printf("Stopping histedit for amending commit %s\n",
+ id_str);
+ free(id_str);
+ got_worktree_rebase_pathlist_free(&merged_paths);
+ error = got_worktree_histedit_postpone(worktree);
+ 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, 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);
+ if (error)
+ goto done;
+ error = got_error_msg(GOT_ERR_CONFLICTS,
+ "conflicts must be resolved before rebasing can continue");
+ } else
+ error = histedit_complete(worktree, tmp_branch, branch, repo);
+done:
+ got_object_id_queue_free(&commits);
+ 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;
+}
blob - 48ede3e31fd5acad44260af32cff4957fb97fdad
blob + 01c340bddf058f3296064d6b7a860341ce2eeeb2
--- include/got_error.h
+++ include/got_error.h
#define GOT_ERR_REBASE_COMMITID 87
#define GOT_ERR_REBASING 88
#define GOT_ERR_REBASE_PATH 89
+#define GOT_ERR_NOT_HISTEDIT 90
+#define GOT_ERR_EMPTY_HISTEDIT 91
+#define GOT_ERR_NO_HISTEDIT_CMD 92
+#define GOT_ERR_HISTEDIT_SYNTAX 93
+#define GOT_ERR_HISTEDIT_CANCEL 94
+#define GOT_ERR_HISTEDIT_COMMITID 95
+#define GOT_ERR_HISTEDIT_BUSY 96
+#define GOT_ERR_HISTEDIT_CMD 97
static const struct got_error {
int code;
"work tree and must be continued or aborted first" },
{ GOT_ERR_REBASE_PATH, "cannot rebase branch which contains "
"changes outside of this work tree's path prefix" },
+ { GOT_ERR_NOT_HISTEDIT, "histedit operation not in progress" },
+ { GOT_ERR_EMPTY_HISTEDIT,"no commits to edit" },
+ { GOT_ERR_NO_HISTEDIT_CMD,"no histedit commands provided" },
+ { GOT_ERR_HISTEDIT_SYNTAX,"syntax error in histedit command list" },
+ { GOT_ERR_HISTEDIT_CANCEL,"histedit operation cancelled" },
+ { GOT_ERR_HISTEDIT_COMMITID,"histedit commit ID mismatch" },
+ { GOT_ERR_HISTEDIT_BUSY,"histedit operation is in progress in this "
+ "work tree and must be continued or aborted first" },
+ { GOT_ERR_HISTEDIT_CMD, "bad histedit command" },
};
/*
blob - 4581e987c69013fec31a525f414b1596bf5ce460
blob + 6e6d2f53d8d2d5dd4b9b32075806433ee2a4cb49
--- include/got_worktree.h
+++ include/got_worktree.h
*/
const struct got_error *got_worktree_rebase_abort(struct got_worktree *,
struct got_repository *, struct got_reference *,
+ got_worktree_checkout_cb, void *);
+
+/*
+ * Prepare for editing the history of the work tree's current branch.
+ * This function creates references to a temporary branch, and the
+ * work tree's current branch, under the "got/worktree/histedit/" namespace.
+ * These references are used to keep track of histedit operation state and
+ * are used as input and/or output arguments with other histedit-related
+ * functions.
+ */
+const struct got_error *got_worktree_histedit_prepare(struct got_reference **,
+ struct got_reference **, struct got_object_id **, struct got_worktree *,
+ struct got_repository *);
+
+/*
+ * Continue an interrupted histedit operation.
+ * This function returns existing references created when histedit was
+ * prepared and the ID of the commit currently being edited.
+ * It should be called before resuming or aborting a histedit operation.
+ */
+const struct got_error *got_worktree_histedit_continue(struct got_object_id **,
+ struct got_reference **, struct got_reference **, struct got_object_id **,
+ struct got_worktree *, struct got_repository *);
+
+/* Check whether a histedit operation is in progress. */
+const struct got_error *got_worktree_histedit_in_progress(int *,
+ struct got_worktree *);
+
+/*
+ * Merge changes from the commit currently being edited into the work tree.
+ * Report affected files, including merge conflicts, via the specified
+ * progress callback. Also populate a list of affected paths which should
+ * be passed to got_worktree_histedit_commit() after a conflict-free merge.
+ * This list must be initialized with TAILQ_INIT() and disposed of with
+ * got_worktree_rebase_pathlist_free().
+ */
+const struct got_error *got_worktree_histedit_merge_files(
+ struct got_pathlist_head *, struct got_worktree *,
+ struct got_object_id *, struct got_object_id *, struct got_repository *,
+ got_worktree_checkout_cb, void *, got_worktree_cancel_cb, void *);
+
+/*
+ * Commit changes merged by got_worktree_histedit_merge_files() to a temporary
+ * branch and return the ID of the newly created commit. An optional list of
+ * merged paths can be provided; otherwise this function will perform a status
+ * crawl across the entire work tree to find paths to commit.
+ * An optional log message can be provided which will be used instead of the
+ * commit's original message.
+ */
+const struct got_error *got_worktree_histedit_commit(struct got_object_id **,
+ struct got_pathlist_head *, struct got_worktree *,
+ struct got_reference *, struct got_commit_object *,
+ struct got_object_id *, const char *, struct got_repository *);
+
+/*
+ * Record the specified commit as skipped during histedit.
+ * This should be called for commits which get dropped or get folded into
+ * a subsequent commit.
+ */
+const struct got_error *got_worktree_histedit_skip_commit(struct got_worktree *,
+ struct got_object_id *, struct got_repository *);
+
+/* Postpone the histedit operation. */
+const struct got_error *got_worktree_histedit_postpone(struct got_worktree *);
+
+/*
+ * Complete the current histedit operation. This should be called once all
+ * commits have been edited successfully.
+ */
+const struct got_error *got_worktree_histedit_complete(struct got_worktree *,
+ struct got_reference *, struct got_reference *, struct got_repository *);
+
+/*
+ * Abort the current histedit operation.
+ * Report reverted files via the specified progress callback.
+ */
+const struct got_error *got_worktree_histedit_abort(struct got_worktree *,
+ struct got_repository *, struct got_reference *, struct got_object_id *,
got_worktree_checkout_cb, void *);
+
+/* Get the path to this work tree's histedit command list file. */
+const struct got_error *got_worktree_get_histedit_list_path(char **,
+ struct got_worktree *);
blob - 317d0fdf6a3b2bf43047f3743caed1b085199df0
blob + 2a0626057ea68d7928c4de3678836630f02cca24
--- lib/got_lib_worktree.h
+++ lib/got_lib_worktree.h
#define GOT_WORKTREE_LOCK "lock"
#define GOT_WORKTREE_FORMAT "format"
#define GOT_WORKTREE_UUID "uuid"
+#define GOT_WORKTREE_HISTEDIT_LIST "histedit-list"
#define GOT_WORKTREE_FORMAT_VERSION 1
#define GOT_WORKTREE_INVALID_COMMIT_ID GOT_SHA1_STRING_ZERO
/* Reference pointing at the ID of the current commit being rebased. */
#define GOT_WORKTREE_REBASE_COMMIT_REF_PREFIX "refs/got/worktree/rebase/commit"
+
+/* Temporary branch which accumulates commits during a histedit operation. */
+#define GOT_WORKTREE_HISTEDIT_TMP_REF_PREFIX "refs/got/worktree/histedit/tmp"
+
+/* Symbolic reference pointing at the name of the branch being edited. */
+#define GOT_WORKTREE_HISTEDIT_BRANCH_REF_PREFIX \
+ "refs/got/worktree/histedit/branch"
+
+/* Reference pointing at the ID of the work tree's pre-edit base commit. */
+#define GOT_WORKTREE_HISTEDIT_BASE_COMMIT_REF_PREFIX \
+ "refs/got/worktree/histedit/base-commit"
+
+/* Reference pointing at the ID of the current commit being edited. */
+#define GOT_WORKTREE_HISTEDIT_COMMIT_REF_PREFIX \
+ "refs/got/worktree/histedit/commit"
blob - 7a096570fcf9fc2d50fbd56c6a4a29ad6b170e54
blob + cdff663f385ece6d4196b6e18a30d7ddcb7ab947
--- lib/worktree.c
+++ lib/worktree.c
{
return get_ref_name(refname, worktree,
GOT_WORKTREE_REBASE_COMMIT_REF_PREFIX);
+}
+
+static const struct got_error *
+get_histedit_tmp_ref_name(char **refname, struct got_worktree *worktree)
+{
+ return get_ref_name(refname, worktree,
+ GOT_WORKTREE_HISTEDIT_TMP_REF_PREFIX);
+}
+
+static const struct got_error *
+get_histedit_branch_symref_name(char **refname, struct got_worktree *worktree)
+{
+ return get_ref_name(refname, worktree,
+ GOT_WORKTREE_HISTEDIT_BRANCH_REF_PREFIX);
+}
+
+static const struct got_error *
+get_histedit_base_commit_ref_name(char **refname, struct got_worktree *worktree)
+{
+ return get_ref_name(refname, worktree,
+ GOT_WORKTREE_HISTEDIT_BASE_COMMIT_REF_PREFIX);
}
+static const struct got_error *
+get_histedit_commit_ref_name(char **refname, struct got_worktree *worktree)
+{
+ return get_ref_name(refname, worktree,
+ GOT_WORKTREE_HISTEDIT_COMMIT_REF_PREFIX);
+}
+const struct got_error *
+got_worktree_get_histedit_list_path(char **path, struct got_worktree *worktree)
+{
+ if (asprintf(path, "%s/%s/%s", worktree->root_path,
+ GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_HISTEDIT_LIST) == -1) {
+ *path = NULL;
+ return got_error_from_errno("asprintf");
+ }
+ return NULL;
+}
+
/*
* Prevent Git's garbage collector from deleting our base commit by
* setting a reference to our base commit's ID.
collect_rebase_commit_msg(struct got_pathlist_head *commitable_paths,
char **logmsg, void *arg)
{
- struct got_commit_object *commit = arg;
-
- *logmsg = strdup(got_object_commit_get_logmsg(commit));
- if (*logmsg == NULL)
- return got_error_from_errno("strdup");
-
+ *logmsg = arg;
return NULL;
}
got_pathlist_free(merged_paths);
}
-const struct got_error *
-got_worktree_rebase_merge_files(struct got_pathlist_head *merged_paths,
- struct got_worktree *worktree, struct got_object_id *parent_commit_id,
- struct got_object_id *commit_id, struct got_repository *repo,
- got_worktree_checkout_cb progress_cb, void *progress_arg,
- got_worktree_cancel_cb cancel_cb, void *cancel_arg)
+static const struct got_error *
+store_commit_id(const char *commit_ref_name, struct got_object_id *commit_id,
+ struct got_repository *repo)
{
const struct got_error *err;
- struct got_fileindex *fileindex;
- char *fileindex_path, *commit_ref_name = NULL;
struct got_reference *commit_ref = NULL;
- struct collect_merged_paths_arg cmp_arg;
- /* Work tree is locked/unlocked during rebase preparation/teardown. */
-
- err = open_fileindex(&fileindex, &fileindex_path, worktree);
- if (err)
- return err;
-
- err = get_rebase_commit_ref_name(&commit_ref_name, worktree);
- if (err)
- goto done;
err = got_ref_open(&commit_ref, repo, commit_ref_name, 0);
if (err) {
if (err->code != GOT_ERR_NOT_REF)
goto done;
}
}
+done:
+ if (commit_ref)
+ got_ref_close(commit_ref);
+ return err;
+}
+
+static const struct got_error *
+rebase_merge_files(struct got_pathlist_head *merged_paths,
+ const char *commit_ref_name, struct got_worktree *worktree,
+ struct got_object_id *parent_commit_id, struct got_object_id *commit_id,
+ struct got_repository *repo, got_worktree_checkout_cb progress_cb,
+ void *progress_arg, got_worktree_cancel_cb cancel_cb, void *cancel_arg)
+{
+ const struct got_error *err;
+ struct got_fileindex *fileindex;
+ char *fileindex_path;
+ struct got_reference *commit_ref = NULL;
+ struct collect_merged_paths_arg cmp_arg;
+ /* Work tree is locked/unlocked during rebase preparation/teardown. */
+
+ err = open_fileindex(&fileindex, &fileindex_path, worktree);
+ if (err)
+ return err;
+
cmp_arg.progress_cb = progress_cb;
cmp_arg.progress_arg = progress_arg;
cmp_arg.merged_paths = merged_paths;
err = merge_files(worktree, fileindex, fileindex_path,
parent_commit_id, commit_id, repo, collect_merged_paths,
&cmp_arg, cancel_cb, cancel_arg);
-done:
got_fileindex_free(fileindex);
free(fileindex_path);
if (commit_ref)
}
const struct got_error *
-got_worktree_rebase_commit(struct got_object_id **new_commit_id,
- struct got_pathlist_head *merged_paths, struct got_worktree *worktree,
- struct got_reference *tmp_branch, struct got_commit_object *orig_commit,
- struct got_object_id *orig_commit_id, struct got_repository *repo)
+got_worktree_rebase_merge_files(struct got_pathlist_head *merged_paths,
+ struct got_worktree *worktree, struct got_object_id *parent_commit_id,
+ struct got_object_id *commit_id, struct got_repository *repo,
+ got_worktree_checkout_cb progress_cb, void *progress_arg,
+ got_worktree_cancel_cb cancel_cb, void *cancel_arg)
{
+ const struct got_error *err;
+ char *commit_ref_name;
+
+ err = get_rebase_commit_ref_name(&commit_ref_name, worktree);
+ if (err)
+ return err;
+
+ err = store_commit_id(commit_ref_name, commit_id, repo);
+ if (err)
+ goto done;
+
+ err = rebase_merge_files(merged_paths, commit_ref_name, worktree,
+ parent_commit_id, commit_id, repo, progress_cb, progress_arg,
+ cancel_cb, cancel_arg);
+done:
+ free(commit_ref_name);
+ return err;
+}
+
+const struct got_error *
+got_worktree_histedit_merge_files(struct got_pathlist_head *merged_paths,
+ struct got_worktree *worktree, struct got_object_id *parent_commit_id,
+ struct got_object_id *commit_id, struct got_repository *repo,
+ got_worktree_checkout_cb progress_cb, void *progress_arg,
+ got_worktree_cancel_cb cancel_cb, void *cancel_arg)
+{
+ const struct got_error *err;
+ char *commit_ref_name;
+
+ err = get_histedit_commit_ref_name(&commit_ref_name, worktree);
+ if (err)
+ return err;
+
+ err = store_commit_id(commit_ref_name, commit_id, repo);
+ if (err)
+ goto done;
+
+ err = rebase_merge_files(merged_paths, commit_ref_name, worktree,
+ parent_commit_id, commit_id, repo, progress_cb, progress_arg,
+ cancel_cb, cancel_arg);
+done:
+ free(commit_ref_name);
+ return err;
+}
+
+static const struct got_error *
+rebase_commit(struct got_object_id **new_commit_id,
+ struct got_pathlist_head *merged_paths, struct got_reference *commit_ref,
+ struct got_worktree *worktree, struct got_reference *tmp_branch,
+ struct got_commit_object *orig_commit, const char *new_logmsg,
+ struct got_repository *repo)
+{
const struct got_error *err, *sync_err;
struct got_pathlist_head commitable_paths;
struct collect_commitables_arg cc_arg;
struct got_fileindex *fileindex = NULL;
- char *fileindex_path = NULL, *commit_ref_name = NULL;
+ char *fileindex_path = NULL;
struct got_reference *head_ref = NULL;
struct got_object_id *head_commit_id = NULL;
- struct got_reference *commit_ref = NULL;
- struct got_object_id *commit_id = NULL;
+ char *logmsg = NULL;
TAILQ_INIT(&commitable_paths);
*new_commit_id = NULL;
/* Work tree is locked/unlocked during rebase preparation/teardown. */
-
- err = get_rebase_commit_ref_name(&commit_ref_name, worktree);
- if (err)
- return err;
- err = got_ref_open(&commit_ref, repo, commit_ref_name, 0);
- if (err)
- goto done;
- err = got_ref_resolve(&commit_id, repo, commit_ref);
- if (err)
- goto done;
- if (got_object_id_cmp(commit_id, orig_commit_id) != 0) {
- err = got_error(GOT_ERR_REBASE_COMMITID);
- goto done;
- }
err = open_fileindex(&fileindex, &fileindex_path, worktree);
if (err)
if (err)
goto done;
+ if (new_logmsg)
+ logmsg = strdup(new_logmsg);
+ else
+ logmsg = strdup(got_object_commit_get_logmsg(orig_commit));
+ if (logmsg == NULL)
+ return got_error_from_errno("strdup");
+
err = commit_worktree(new_commit_id, &commitable_paths, head_commit_id,
worktree, NULL, got_object_commit_get_author(orig_commit),
got_object_commit_get_committer(orig_commit),
- collect_rebase_commit_msg, orig_commit,
- rebase_status, NULL, repo);
+ collect_rebase_commit_msg, logmsg, rebase_status, NULL, repo);
if (err)
goto done;
if (fileindex)
got_fileindex_free(fileindex);
free(fileindex_path);
- free(commit_ref_name);
- if (commit_ref)
- got_ref_close(commit_ref);
free(head_commit_id);
if (head_ref)
got_ref_close(head_ref);
}
const struct got_error *
+got_worktree_rebase_commit(struct got_object_id **new_commit_id,
+ struct got_pathlist_head *merged_paths, struct got_worktree *worktree,
+ struct got_reference *tmp_branch, struct got_commit_object *orig_commit,
+ struct got_object_id *orig_commit_id, struct got_repository *repo)
+{
+ const struct got_error *err;
+ char *commit_ref_name;
+ struct got_reference *commit_ref = NULL;
+ struct got_object_id *commit_id = NULL;
+
+ err = get_rebase_commit_ref_name(&commit_ref_name, worktree);
+ if (err)
+ return err;
+
+ err = got_ref_open(&commit_ref, repo, commit_ref_name, 0);
+ if (err)
+ goto done;
+ err = got_ref_resolve(&commit_id, repo, commit_ref);
+ if (err)
+ goto done;
+ if (got_object_id_cmp(commit_id, orig_commit_id) != 0) {
+ err = got_error(GOT_ERR_REBASE_COMMITID);
+ goto done;
+ }
+
+ err = rebase_commit(new_commit_id, merged_paths, commit_ref,
+ worktree, tmp_branch, orig_commit, NULL, repo);
+done:
+ if (commit_ref)
+ got_ref_close(commit_ref);
+ free(commit_ref_name);
+ free(commit_id);
+ return err;
+}
+
+const struct got_error *
+got_worktree_histedit_commit(struct got_object_id **new_commit_id,
+ struct got_pathlist_head *merged_paths, struct got_worktree *worktree,
+ struct got_reference *tmp_branch, struct got_commit_object *orig_commit,
+ struct got_object_id *orig_commit_id, const char *new_logmsg,
+ struct got_repository *repo)
+{
+ const struct got_error *err;
+ char *commit_ref_name;
+ struct got_reference *commit_ref = NULL;
+ struct got_object_id *commit_id = NULL;
+
+ err = get_histedit_commit_ref_name(&commit_ref_name, worktree);
+ if (err)
+ return err;
+
+ err = got_ref_open(&commit_ref, repo, commit_ref_name, 0);
+ if (err)
+ goto done;
+ err = got_ref_resolve(&commit_id, repo, commit_ref);
+ if (err)
+ goto done;
+ if (got_object_id_cmp(commit_id, orig_commit_id) != 0) {
+ err = got_error(GOT_ERR_HISTEDIT_COMMITID);
+ goto done;
+ }
+
+ err = rebase_commit(new_commit_id, merged_paths, commit_ref,
+ worktree, tmp_branch, orig_commit, new_logmsg, repo);
+done:
+ if (commit_ref)
+ got_ref_close(commit_ref);
+ free(commit_ref_name);
+ free(commit_id);
+ return err;
+}
+
+const struct got_error *
got_worktree_rebase_postpone(struct got_worktree *worktree)
{
return lock_worktree(worktree, LOCK_SH);
got_ref_close(resolved);
free(tree_id);
free(commit_id);
+ if (fileindex)
+ got_fileindex_free(fileindex);
+ free(fileindex_path);
+ TAILQ_FOREACH(pe, &revertible_paths, entry)
+ free((char *)pe->path);
+ got_pathlist_free(&revertible_paths);
+
+ unlockerr = lock_worktree(worktree, LOCK_SH);
+ if (unlockerr && err == NULL)
+ err = unlockerr;
+ return err;
+}
+
+const struct got_error *
+got_worktree_histedit_prepare(struct got_reference **tmp_branch,
+ struct got_reference **branch_ref, struct got_object_id **base_commit_id,
+ struct got_worktree *worktree, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ char *tmp_branch_name = NULL;
+ char *branch_ref_name = NULL;
+ char *base_commit_ref_name = NULL;
+ struct got_fileindex *fileindex = NULL;
+ char *fileindex_path = NULL;
+ struct check_rebase_ok_arg ok_arg;
+ struct got_reference *wt_branch = NULL;
+ struct got_reference *base_commit_ref = NULL;
+
+ *tmp_branch = NULL;
+ *branch_ref = NULL;
+ *base_commit_id = NULL;
+
+ err = lock_worktree(worktree, LOCK_EX);
+ if (err)
+ return err;
+
+ err = open_fileindex(&fileindex, &fileindex_path, worktree);
+ if (err)
+ goto done;
+
+ ok_arg.worktree = worktree;
+ ok_arg.repo = repo;
+ ok_arg.rebase_in_progress = 0;
+ err = got_fileindex_for_each_entry_safe(fileindex, check_rebase_ok,
+ &ok_arg);
+ if (err)
+ goto done;
+
+ err = get_histedit_tmp_ref_name(&tmp_branch_name, worktree);
+ if (err)
+ goto done;
+
+ err = get_histedit_branch_symref_name(&branch_ref_name, worktree);
+ if (err)
+ goto done;
+
+ err = get_histedit_base_commit_ref_name(&base_commit_ref_name,
+ worktree);
+ if (err)
+ goto done;
+
+ err = got_ref_open(&wt_branch, repo, worktree->head_ref_name,
+ 0);
+ if (err)
+ goto done;
+
+ err = got_ref_alloc_symref(branch_ref, branch_ref_name, wt_branch);
+ if (err)
+ goto done;
+
+ err = got_ref_write(*branch_ref, repo);
+ if (err)
+ goto done;
+
+ err = got_ref_alloc(&base_commit_ref, base_commit_ref_name,
+ worktree->base_commit_id);
+ if (err)
+ goto done;
+ err = got_ref_write(base_commit_ref, repo);
+ if (err)
+ goto done;
+ *base_commit_id = got_object_id_dup(worktree->base_commit_id);
+ if (*base_commit_id == NULL) {
+ err = got_error_from_errno("got_object_id_dup");
+ goto done;
+ }
+
+ err = got_ref_alloc(tmp_branch, tmp_branch_name,
+ worktree->base_commit_id);
+ if (err)
+ goto done;
+ err = got_ref_write(*tmp_branch, repo);
+ if (err)
+ goto done;
+
+ err = got_worktree_set_head_ref(worktree, *tmp_branch);
+ if (err)
+ goto done;
+done:
+ free(fileindex_path);
if (fileindex)
got_fileindex_free(fileindex);
+ free(tmp_branch_name);
+ free(branch_ref_name);
+ free(base_commit_ref_name);
+ if (wt_branch)
+ got_ref_close(wt_branch);
+ if (err) {
+ if (*branch_ref) {
+ got_ref_close(*branch_ref);
+ *branch_ref = NULL;
+ }
+ if (*tmp_branch) {
+ got_ref_close(*tmp_branch);
+ *tmp_branch = NULL;
+ }
+ free(*base_commit_id);
+ lock_worktree(worktree, LOCK_SH);
+ }
+ return err;
+}
+
+const struct got_error *
+got_worktree_histedit_postpone(struct got_worktree *worktree)
+{
+ return lock_worktree(worktree, LOCK_SH);
+}
+
+const struct got_error *
+got_worktree_histedit_in_progress(int *in_progress,
+ struct got_worktree *worktree)
+{
+ const struct got_error *err;
+ char *tmp_branch_name = NULL;
+
+ err = get_histedit_tmp_ref_name(&tmp_branch_name, worktree);
+ if (err)
+ return err;
+
+ *in_progress = (strcmp(tmp_branch_name, worktree->head_ref_name) == 0);
+ free(tmp_branch_name);
+ return NULL;
+}
+
+const struct got_error *
+got_worktree_histedit_continue(struct got_object_id **commit_id,
+ struct got_reference **tmp_branch, struct got_reference **branch_ref,
+ struct got_object_id **base_commit_id,
+ struct got_worktree *worktree, struct got_repository *repo)
+{
+ const struct got_error *err;
+ char *commit_ref_name = NULL, *base_commit_ref_name = NULL;
+ char *tmp_branch_name = NULL, *branch_ref_name = NULL;
+ struct got_reference *commit_ref = NULL;
+ struct got_reference *base_commit_ref = NULL;
+
+ *commit_id = NULL;
+ *tmp_branch = NULL;
+ *base_commit_id = NULL;
+
+ err = get_histedit_tmp_ref_name(&tmp_branch_name, worktree);
+ if (err)
+ return err;
+
+ err = get_histedit_branch_symref_name(&branch_ref_name, worktree);
+ if (err)
+ goto done;
+
+ err = get_histedit_commit_ref_name(&commit_ref_name, worktree);
+ if (err)
+ goto done;
+
+ err = get_histedit_base_commit_ref_name(&base_commit_ref_name,
+ worktree);
+ if (err)
+ goto done;
+
+ err = got_ref_open(branch_ref, repo, branch_ref_name, 0);
+ if (err)
+ goto done;
+
+ err = got_ref_open(&commit_ref, repo, commit_ref_name, 0);
+ if (err)
+ goto done;
+ err = got_ref_resolve(commit_id, repo, commit_ref);
+ if (err)
+ goto done;
+
+ err = got_ref_open(&base_commit_ref, repo, base_commit_ref_name, 0);
+ if (err)
+ goto done;
+ err = got_ref_resolve(base_commit_id, repo, base_commit_ref);
+ if (err)
+ goto done;
+
+ err = got_ref_open(tmp_branch, repo, tmp_branch_name, 0);
+ if (err)
+ goto done;
+done:
+ free(commit_ref_name);
+ free(branch_ref_name);
+ if (commit_ref)
+ got_ref_close(commit_ref);
+ if (base_commit_ref)
+ got_ref_close(base_commit_ref);
+ if (err) {
+ free(*commit_id);
+ *commit_id = NULL;
+ free(*base_commit_id);
+ *base_commit_id = NULL;
+ if (*tmp_branch) {
+ got_ref_close(*tmp_branch);
+ *tmp_branch = NULL;
+ }
+ }
+ return err;
+}
+
+static const struct got_error *
+delete_histedit_refs(struct got_worktree *worktree, struct got_repository *repo)
+{
+ const struct got_error *err;
+ char *tmp_branch_name = NULL, *base_commit_ref_name = NULL;
+ char *branch_ref_name = NULL, *commit_ref_name = NULL;
+
+ err = get_histedit_tmp_ref_name(&tmp_branch_name, worktree);
+ if (err)
+ goto done;
+ err = delete_ref(tmp_branch_name, repo);
+ if (err)
+ goto done;
+
+ err = get_histedit_base_commit_ref_name(&base_commit_ref_name,
+ worktree);
+ if (err)
+ goto done;
+ err = delete_ref(base_commit_ref_name, repo);
+ if (err)
+ goto done;
+
+ err = get_histedit_branch_symref_name(&branch_ref_name, worktree);
+ if (err)
+ goto done;
+ err = delete_ref(branch_ref_name, repo);
+ if (err)
+ goto done;
+
+ err = get_histedit_commit_ref_name(&commit_ref_name, worktree);
+ if (err)
+ goto done;
+ err = delete_ref(commit_ref_name, repo);
+ if (err)
+ goto done;
+done:
+ free(tmp_branch_name);
+ free(base_commit_ref_name);
+ free(branch_ref_name);
+ free(commit_ref_name);
+ return err;
+}
+
+const struct got_error *
+got_worktree_histedit_abort(struct got_worktree *worktree,
+ struct got_repository *repo, struct got_reference *branch,
+ struct got_object_id *base_commit_id,
+ got_worktree_checkout_cb progress_cb, void *progress_arg)
+{
+ const struct got_error *err, *unlockerr, *sync_err;
+ struct got_reference *resolved = NULL;
+ struct got_fileindex *fileindex = NULL;
+ char *fileindex_path = NULL;
+ struct got_pathlist_head revertible_paths;
+ struct got_pathlist_entry *pe;
+ struct collect_revertible_paths_arg crp_arg;
+ struct got_object_id *tree_id = NULL;
+
+ TAILQ_INIT(&revertible_paths);
+
+ err = lock_worktree(worktree, LOCK_EX);
+ if (err)
+ return err;
+
+ err = got_ref_open(&resolved, repo,
+ got_ref_get_symref_target(branch), 0);
+ if (err)
+ goto done;
+
+ err = got_worktree_set_head_ref(worktree, resolved);
+ if (err)
+ goto done;
+
+ err = got_worktree_set_base_commit_id(worktree, repo, base_commit_id);
+ if (err)
+ goto done;
+
+ err = got_object_id_by_path(&tree_id, repo, base_commit_id,
+ worktree->path_prefix);
+ if (err)
+ goto done;
+
+ err = delete_histedit_refs(worktree, repo);
+ if (err)
+ goto done;
+
+ err = open_fileindex(&fileindex, &fileindex_path, worktree);
+ if (err)
+ goto done;
+
+ crp_arg.revertible_paths = &revertible_paths;
+ crp_arg.worktree = worktree;
+ err = worktree_status(worktree, "", fileindex, repo,
+ collect_revertible_paths, &crp_arg, NULL, NULL);
+ if (err)
+ goto done;
+
+ TAILQ_FOREACH(pe, &revertible_paths, entry) {
+ err = revert_file(worktree, fileindex, pe->path,
+ progress_cb, progress_arg, repo);
+ if (err)
+ goto sync;
+ }
+
+ err = checkout_files(worktree, fileindex, "", tree_id, NULL,
+ repo, progress_cb, progress_arg, NULL, NULL);
+sync:
+ sync_err = sync_fileindex(fileindex, fileindex_path);
+ if (sync_err && err == NULL)
+ err = sync_err;
+done:
+ got_ref_close(resolved);
+ free(tree_id);
+ if (fileindex)
+ got_fileindex_free(fileindex);
free(fileindex_path);
TAILQ_FOREACH(pe, &revertible_paths, entry)
free((char *)pe->path);
err = unlockerr;
return err;
}
+
+const struct got_error *
+got_worktree_histedit_complete(struct got_worktree *worktree,
+ struct got_reference *tmp_branch, struct got_reference *edited_branch,
+ struct got_repository *repo)
+{
+ const struct got_error *err, *unlockerr;
+ struct got_object_id *new_head_commit_id = NULL;
+ struct got_reference *resolved = NULL;
+
+ err = got_ref_resolve(&new_head_commit_id, repo, tmp_branch);
+ if (err)
+ return err;
+
+ err = got_ref_open(&resolved, repo,
+ got_ref_get_symref_target(edited_branch), 0);
+ if (err)
+ goto done;
+
+ err = got_ref_change_ref(resolved, new_head_commit_id);
+ if (err)
+ goto done;
+
+ err = got_ref_write(resolved, repo);
+ if (err)
+ goto done;
+
+ err = got_worktree_set_head_ref(worktree, resolved);
+ if (err)
+ goto done;
+
+ err = delete_histedit_refs(worktree, repo);
+done:
+ free(new_head_commit_id);
+ unlockerr = lock_worktree(worktree, LOCK_SH);
+ if (unlockerr && err == NULL)
+ err = unlockerr;
+ return err;
+}
+
+const struct got_error *
+got_worktree_histedit_skip_commit(struct got_worktree *worktree,
+ struct got_object_id *commit_id, struct got_repository *repo)
+{
+ const struct got_error *err;
+ char *commit_ref_name;
+
+ err = get_histedit_commit_ref_name(&commit_ref_name, worktree);
+ if (err)
+ return err;
+
+ err = store_commit_id(commit_ref_name, commit_id, repo);
+ if (err)
+ goto done;
+
+ err = delete_ref(commit_ref_name, repo);
+done:
+ free(commit_ref_name);
+ return err;
+}
blob - 682c9e1542649798fd47bc71d7532329397d40f9
blob + 191b50efa64f9e598f4f8865387623806d8453c5
--- regress/cmdline/Makefile
+++ regress/cmdline/Makefile
REGRESS_TARGETS=checkout update status log add rm diff commit \
- cherrypick backout rebase import
+ cherrypick backout rebase import histedit
NOOBJ=Yes
checkout:
import:
./import.sh
+histedit:
+ ./histedit.sh
+
.include <bsd.regress.mk>
blob - /dev/null
blob + 98604e6814628faa295d9b0a155046626d878a18 (mode 755)
--- /dev/null
+++ regress/cmdline/histedit.sh
+#!/bin/sh
+#
+# Copyright (c) 2019 Stefan Sperling <stsp@openbsd.org>
+#
+# 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.
+
+. ./common.sh
+
+function test_histedit_no_op {
+ local testroot=`test_init histedit_no_op`
+
+ local orig_commit=`git_show_head $testroot/repo`
+
+ echo "modified alpha on master" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on master" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+ git_commit $testroot/repo -m "committing changes"
+ local old_commit1=`git_show_head $testroot/repo`
+
+ echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+ git_commit $testroot/repo -m "committing to zeta on master"
+ local old_commit2=`git_show_head $testroot/repo`
+
+ got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "pick $old_commit1" > $testroot/histedit-script
+ echo "pick $old_commit2" >> $testroot/histedit-script
+
+ (cd $testroot/wt && got histedit -F $testroot/histedit-script \
+ > $testroot/stdout)
+
+ local new_commit1=`git_show_parent_commit $testroot/repo`
+ local new_commit2=`git_show_head $testroot/repo`
+
+ local short_old_commit1=`trim_obj_id 28 $old_commit1`
+ local short_old_commit2=`trim_obj_id 28 $old_commit2`
+ local short_new_commit1=`trim_obj_id 28 $new_commit1`
+ local short_new_commit2=`trim_obj_id 28 $new_commit2`
+
+ echo "G alpha" > $testroot/stdout.expected
+ echo "D beta" >> $testroot/stdout.expected
+ echo "A epsilon/new" >> $testroot/stdout.expected
+ echo "$short_old_commit1 -> $short_new_commit1: committing changes" \
+ >> $testroot/stdout.expected
+ echo "G epsilon/zeta" >> $testroot/stdout.expected
+ echo -n "$short_old_commit2 -> $short_new_commit2: " \
+ >> $testroot/stdout.expected
+ echo "committing to zeta on master" >> $testroot/stdout.expected
+ echo "Switching work tree to refs/heads/master" \
+ >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "modified alpha on master" > $testroot/content.expected
+ cat $testroot/wt/alpha > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ if [ -e $testroot/wt/beta ]; then
+ echo "removed file beta still exists on disk" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo "new file on master" > $testroot/content.expected
+ cat $testroot/wt/epsilon/new > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got status > $testroot/stdout)
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout)
+ echo "commit $new_commit2 (master)" > $testroot/stdout.expected
+ echo "commit $new_commit1" >> $testroot/stdout.expected
+ echo "commit $orig_commit" >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_histedit_swap {
+ local testroot=`test_init histedit_swap`
+
+ local orig_commit=`git_show_head $testroot/repo`
+
+ echo "modified alpha on master" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on master" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+ git_commit $testroot/repo -m "committing changes"
+ local old_commit1=`git_show_head $testroot/repo`
+
+ echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+ git_commit $testroot/repo -m "committing to zeta on master"
+ local old_commit2=`git_show_head $testroot/repo`
+
+ got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "pick $old_commit2" > $testroot/histedit-script
+ echo "pick $old_commit1" >> $testroot/histedit-script
+
+ (cd $testroot/wt && got histedit -F $testroot/histedit-script \
+ > $testroot/stdout)
+
+ local new_commit2=`git_show_parent_commit $testroot/repo`
+ local new_commit1=`git_show_head $testroot/repo`
+
+ local short_old_commit1=`trim_obj_id 28 $old_commit1`
+ local short_old_commit2=`trim_obj_id 28 $old_commit2`
+ local short_new_commit1=`trim_obj_id 28 $new_commit1`
+ local short_new_commit2=`trim_obj_id 28 $new_commit2`
+
+ echo "G epsilon/zeta" > $testroot/stdout.expected
+ echo -n "$short_old_commit2 -> $short_new_commit2: " \
+ >> $testroot/stdout.expected
+ echo "committing to zeta on master" >> $testroot/stdout.expected
+ echo "G alpha" >> $testroot/stdout.expected
+ echo "D beta" >> $testroot/stdout.expected
+ echo "A epsilon/new" >> $testroot/stdout.expected
+ echo "$short_old_commit1 -> $short_new_commit1: committing changes" \
+ >> $testroot/stdout.expected
+ echo "Switching work tree to refs/heads/master" \
+ >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "modified alpha on master" > $testroot/content.expected
+ cat $testroot/wt/alpha > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ if [ -e $testroot/wt/beta ]; then
+ echo "removed file beta still exists on disk" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo "new file on master" > $testroot/content.expected
+ cat $testroot/wt/epsilon/new > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got status > $testroot/stdout)
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout)
+ echo "commit $new_commit1 (master)" > $testroot/stdout.expected
+ echo "commit $new_commit2" >> $testroot/stdout.expected
+ echo "commit $orig_commit" >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_histedit_drop {
+ local testroot=`test_init histedit_drop`
+
+ local orig_commit=`git_show_head $testroot/repo`
+
+ echo "modified alpha on master" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on master" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+ git_commit $testroot/repo -m "committing changes"
+ local old_commit1=`git_show_head $testroot/repo`
+
+ echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+ git_commit $testroot/repo -m "committing to zeta on master"
+ local old_commit2=`git_show_head $testroot/repo`
+
+ got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "drop $old_commit1" > $testroot/histedit-script
+ echo "pick $old_commit2" >> $testroot/histedit-script
+
+ (cd $testroot/wt && got histedit -F $testroot/histedit-script \
+ > $testroot/stdout)
+
+ local new_commit1=`git_show_parent_commit $testroot/repo`
+ local new_commit2=`git_show_head $testroot/repo`
+
+ local short_old_commit1=`trim_obj_id 28 $old_commit1`
+ local short_old_commit2=`trim_obj_id 28 $old_commit2`
+ local short_new_commit1=`trim_obj_id 28 $new_commit1`
+ local short_new_commit2=`trim_obj_id 28 $new_commit2`
+
+ echo "$short_old_commit1 -> drop commit: committing changes" \
+ > $testroot/stdout.expected
+ echo "G epsilon/zeta" >> $testroot/stdout.expected
+ echo -n "$short_old_commit2 -> $short_new_commit2: " \
+ >> $testroot/stdout.expected
+ echo "committing to zeta on master" >> $testroot/stdout.expected
+ echo "Switching work tree to refs/heads/master" \
+ >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ for f in alpha beta; do
+ echo "$f" > $testroot/content.expected
+ cat $testroot/wt/$f > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+ done
+
+ if [ -e $testroot/wt/new ]; then
+ echo "file new exists on disk but should not" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ (cd $testroot/wt && got status > $testroot/stdout)
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout)
+ echo "commit $new_commit2 (master)" > $testroot/stdout.expected
+ echo "commit $orig_commit" >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_histedit_fold {
+ local testroot=`test_init histedit_fold`
+
+ local orig_commit=`git_show_head $testroot/repo`
+
+ echo "modified alpha on master" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on master" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+ git_commit $testroot/repo -m "committing changes"
+ local old_commit1=`git_show_head $testroot/repo`
+
+ echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+ git_commit $testroot/repo -m "committing to zeta on master"
+ local old_commit2=`git_show_head $testroot/repo`
+
+ got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "fold $old_commit1" > $testroot/histedit-script
+ echo "pick $old_commit2" >> $testroot/histedit-script
+ echo "mesg committing folded changes" >> $testroot/histedit-script
+
+ (cd $testroot/wt && got histedit -F $testroot/histedit-script \
+ > $testroot/stdout)
+
+ local new_commit1=`git_show_parent_commit $testroot/repo`
+ local new_commit2=`git_show_head $testroot/repo`
+
+ local short_old_commit1=`trim_obj_id 28 $old_commit1`
+ local short_old_commit2=`trim_obj_id 28 $old_commit2`
+ local short_new_commit1=`trim_obj_id 28 $new_commit1`
+ local short_new_commit2=`trim_obj_id 28 $new_commit2`
+
+ echo "G alpha" > $testroot/stdout.expected
+ echo "D beta" >> $testroot/stdout.expected
+ echo "A epsilon/new" >> $testroot/stdout.expected
+ echo "$short_old_commit1 -> fold commit: committing changes" \
+ >> $testroot/stdout.expected
+ echo "G epsilon/zeta" >> $testroot/stdout.expected
+ echo -n "$short_old_commit2 -> $short_new_commit2: " \
+ >> $testroot/stdout.expected
+ echo "committing folded changes" >> $testroot/stdout.expected
+ echo "Switching work tree to refs/heads/master" \
+ >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "modified alpha on master" > $testroot/content.expected
+ cat $testroot/wt/alpha > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ if [ -e $testroot/wt/beta ]; then
+ echo "removed file beta still exists on disk" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo "new file on master" > $testroot/content.expected
+ cat $testroot/wt/epsilon/new > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got status > $testroot/stdout)
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout)
+ echo "commit $new_commit2 (master)" > $testroot/stdout.expected
+ echo "commit $orig_commit" >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_histedit_edit {
+ local testroot=`test_init histedit_edit`
+
+ local orig_commit=`git_show_head $testroot/repo`
+
+ echo "modified alpha on master" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on master" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+ git_commit $testroot/repo -m "committing changes"
+ local old_commit1=`git_show_head $testroot/repo`
+
+ echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+ git_commit $testroot/repo -m "committing to zeta on master"
+ local old_commit2=`git_show_head $testroot/repo`
+
+ got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "edit $old_commit1" > $testroot/histedit-script
+ echo "mesg committing changes" >> $testroot/histedit-script
+ echo "pick $old_commit2" >> $testroot/histedit-script
+
+ (cd $testroot/wt && got histedit -F $testroot/histedit-script \
+ > $testroot/stdout)
+
+ local short_old_commit1=`trim_obj_id 28 $old_commit1`
+ local short_old_commit2=`trim_obj_id 28 $old_commit2`
+
+ echo "G alpha" > $testroot/stdout.expected
+ echo "D beta" >> $testroot/stdout.expected
+ echo "A epsilon/new" >> $testroot/stdout.expected
+ echo "Stopping histedit for amending commit $old_commit1" \
+ >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "edited modified alpha on master" > $testroot/wt/alpha
+
+ (cd $testroot/wt && got histedit -c > $testroot/stdout)
+
+ local new_commit1=`git_show_parent_commit $testroot/repo`
+ local new_commit2=`git_show_head $testroot/repo`
+
+ local short_new_commit1=`trim_obj_id 28 $new_commit1`
+ local short_new_commit2=`trim_obj_id 28 $new_commit2`
+
+ echo "$short_old_commit1 -> $short_new_commit1: committing changes" \
+ > $testroot/stdout.expected
+ echo "G epsilon/zeta" >> $testroot/stdout.expected
+ echo -n "$short_old_commit2 -> $short_new_commit2: " \
+ >> $testroot/stdout.expected
+ echo "committing to zeta on master" >> $testroot/stdout.expected
+ echo "Switching work tree to refs/heads/master" \
+ >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "edited modified alpha on master" > $testroot/content.expected
+ cat $testroot/wt/alpha > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ if [ -e $testroot/wt/beta ]; then
+ echo "removed file beta still exists on disk" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo "new file on master" > $testroot/content.expected
+ cat $testroot/wt/epsilon/new > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got status > $testroot/stdout)
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout)
+ echo "commit $new_commit2 (master)" > $testroot/stdout.expected
+ echo "commit $new_commit1" >> $testroot/stdout.expected
+ echo "commit $orig_commit" >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_histedit_fold_last_commit {
+ local testroot=`test_init histedit_fold_last_commit`
+
+ local orig_commit=`git_show_head $testroot/repo`
+
+ echo "modified alpha on master" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on master" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+ git_commit $testroot/repo -m "committing changes"
+ local old_commit1=`git_show_head $testroot/repo`
+
+ echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+ git_commit $testroot/repo -m "committing to zeta on master"
+ local old_commit2=`git_show_head $testroot/repo`
+
+ got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "pick $old_commit1" > $testroot/histedit-script
+ echo "fold $old_commit2" >> $testroot/histedit-script
+ echo "mesg committing folded changes" >> $testroot/histedit-script
+
+ (cd $testroot/wt && got histedit -F $testroot/histedit-script \
+ > $testroot/stdout 2> $testroot/stderr)
+
+ ret="$?"
+ if [ "$ret" == "0" ]; then
+ echo "histedit succeeded unexpectedly" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo "got: last commit in histedit script cannot be folded" \
+ > $testroot/stderr.expected
+
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_histedit_missing_commit {
+ local testroot=`test_init histedit_missing_commit`
+
+ local orig_commit=`git_show_head $testroot/repo`
+
+ echo "modified alpha on master" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on master" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+ git_commit $testroot/repo -m "committing changes"
+ local old_commit1=`git_show_head $testroot/repo`
+
+ echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+ git_commit $testroot/repo -m "committing to zeta on master"
+ local old_commit2=`git_show_head $testroot/repo`
+
+ got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "pick $old_commit1" > $testroot/histedit-script
+ echo "mesg committing changes" >> $testroot/histedit-script
+
+ (cd $testroot/wt && got histedit -F $testroot/histedit-script \
+ > $testroot/stdout 2> $testroot/stderr)
+
+ ret="$?"
+ if [ "$ret" == "0" ]; then
+ echo "histedit succeeded unexpectedly" >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo "got: commit $old_commit2 missing from histedit script" \
+ > $testroot/stderr.expected
+
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_histedit_abort {
+ local testroot=`test_init histedit_abort`
+
+ local orig_commit=`git_show_head $testroot/repo`
+
+ echo "modified alpha on master" > $testroot/repo/alpha
+ (cd $testroot/repo && git rm -q beta)
+ echo "new file on master" > $testroot/repo/epsilon/new
+ (cd $testroot/repo && git add epsilon/new)
+ git_commit $testroot/repo -m "committing changes"
+ local old_commit1=`git_show_head $testroot/repo`
+
+ echo "modified zeta on master" > $testroot/repo/epsilon/zeta
+ git_commit $testroot/repo -m "committing to zeta on master"
+ local old_commit2=`git_show_head $testroot/repo`
+
+ got checkout -c $orig_commit $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "edit $old_commit1" > $testroot/histedit-script
+ echo "mesg committing changes" >> $testroot/histedit-script
+ echo "pick $old_commit2" >> $testroot/histedit-script
+
+ (cd $testroot/wt && got histedit -F $testroot/histedit-script \
+ > $testroot/stdout)
+
+ local short_old_commit1=`trim_obj_id 28 $old_commit1`
+ local short_old_commit2=`trim_obj_id 28 $old_commit2`
+
+ echo "G alpha" > $testroot/stdout.expected
+ echo "D beta" >> $testroot/stdout.expected
+ echo "A epsilon/new" >> $testroot/stdout.expected
+ echo "Stopping histedit for amending commit $old_commit1" \
+ >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "edited modified alpha on master" > $testroot/wt/alpha
+
+ (cd $testroot/wt && got histedit -a > $testroot/stdout)
+
+ local new_commit1=`git_show_parent_commit $testroot/repo`
+ local new_commit2=`git_show_head $testroot/repo`
+
+ local short_new_commit1=`trim_obj_id 28 $new_commit1`
+ local short_new_commit2=`trim_obj_id 28 $new_commit2`
+
+ echo "Switching work tree to refs/heads/master" \
+ > $testroot/stdout.expected
+ echo "R alpha" >> $testroot/stdout.expected
+ echo "R beta" >> $testroot/stdout.expected
+ echo "R epsilon/new" >> $testroot/stdout.expected
+ echo "Histedit of refs/heads/master aborted" \
+ >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ for f in alpha beta; do
+ echo "$f" > $testroot/content.expected
+ cat $testroot/wt/$f > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+ done
+
+ echo "new file on master" > $testroot/content.expected
+ cat $testroot/wt/epsilon/new > $testroot/content
+ cmp -s $testroot/content.expected $testroot/content
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/content.expected $testroot/content
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got status > $testroot/stdout)
+
+ echo "? epsilon/new" > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/wt && got log -l3 | grep ^commit > $testroot/stdout)
+ echo "commit $new_commit2 (master)" > $testroot/stdout.expected
+ echo "commit $new_commit1" >> $testroot/stdout.expected
+ echo "commit $orig_commit" >> $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+run_test test_histedit_no_op
+run_test test_histedit_swap
+run_test test_histedit_drop
+run_test test_histedit_fold
+run_test test_histedit_edit
+run_test test_histedit_fold_last_commit
+run_test test_histedit_missing_commit
+run_test test_histedit_abort