commit - 601aba22ab7ec8a86055e5fca4fcef11882c3ba4
commit + e600f1246e15fff13251ba9d299d74a24ae579c2
blob - a8b88374d28eb0bf77bcda9f79b0a80b31e5c227
blob + 6b06d43ab24d2750a66f3657513869ecb77d13f8
--- got/got.1
+++ got/got.1
.It Cm bo
Short alias for
.Cm backout .
-.It Cm rebase Oo Fl a Oc Oo Fl c Oc Op Ar branch
+.It Cm rebase Oo Fl a Oc Oo Fl c Oc Oo Fl l Oc Op Ar branch
Rebase commits on the specified
.Ar branch
onto the tip of the current branch of the work tree.
the new version of the specified
.Ar branch
and the work tree is automatically switched to it.
+.Pp
+Old commits in their pre-rebase state are automatically backed up in the
+.Dq refs/got/backup/rebase
+reference namespace.
+As long as these references are not removed older versions of rebased
+commits will remain in the repository and can be viewed with the
+.Cm got rebase -l
+command.
+Removal of these references makes objects which become unreachable via
+any reference subject to removal by Git's garbage collector.
.Pp
While rebasing commits, show the status of each affected file,
using the following status codes:
.It Fl c
Continue an interrupted rebase operation.
If this option is used, no other command-line arguments are allowed.
+.It Fl l
+Show a list of past rebase operations, represented by references in the
+.Dq refs/got/backup/rebase
+reference namespace.
+.Pp
+Display the author, date, and log message of each backed up commit,
+the object ID of the corresponding post-rebase commit, and
+the object ID of their common ancestor commit.
+Given these object IDs,
+the
+.Cm got log
+command with the
+.Fl c
+and
+.Fl x
+options can be used to examine the history of either version of the branch,
+and the
+.Cm got branch
+command with the
+.Fl c
+option can be used to create a new branch from a pre-rebase state if desired.
+.Pp
+If a
+.Ar branch
+is specified, only show commits which at some point in time represented this
+branch.
+Otherwise, list all backed up commits for any branches.
+.Pp
+If this option is used,
+.Cm got rebase
+does not require a work tree.
+None of the other options can be used together with
+.Fl l .
.El
.It Cm rb
Short alias for
.Cm rebase .
-.It Cm histedit Oo Fl a Oc Oo Fl c Oc Oo Fl f Oc Oo Fl F Ar histedit-script Oc Oo Fl m Oc
+.It Cm histedit Oo Fl a Oc Oo Fl c Oc Oo Fl f Oc Oo Fl F Ar histedit-script Oc Oo Fl m Oc Oo Fl l Oc Op Ar branch
Edit commit history between the work tree's current base commit and
the tip commit of the work tree's current branch.
.Pp
the new version of the work tree's branch and the work tree is automatically
switched to it.
.Pp
+Old commits in their pre-histedit state are automatically backed up in the
+.Dq refs/got/backup/histedit
+reference namespace.
+As long as these references are not removed older versions of edited
+commits will remain in the repository and can be viewed with the
+.Cm got histedit -l
+command.
+Removal of these references makes objects which become unreachable via
+any reference subject to removal by Git's garbage collector.
+.Pp
While merging commits, show the status of each affected file,
using the following status codes:
.Bl -column YXZ description
.Fl m
option can only be used when starting a new histedit operation.
If this option is used, no other command-line arguments are allowed.
+.It Fl l
+Show a list of past histedit operations, represented by references in the
+.Dq refs/got/backup/histedit
+reference namespace.
+.Pp
+Display the author, date, and log message of each backed up commit,
+the object ID of the corresponding post-histedit commit, and
+the object ID of their common ancestor commit.
+Given these object IDs,
+the
+.Cm got log
+command with the
+.Fl c
+and
+.Fl x
+options can be used to examine the history of either version of the branch,
+and the
+.Cm got branch
+command with the
+.Fl c
+option can be used to create a new branch from a pre-histedit state if desired.
+.Pp
+If a
+.Ar branch
+is specified, only show commits which at some point in time represented this
+branch.
+Otherwise, list all backed up commits for any branches.
+.Pp
+If this option is used,
+.Cm got histedit
+does not require a work tree.
+None of the other options can be used together with
+.Fl l .
.El
.It Cm he
Short alias for
blob - 5a81fddc99d43b6d16f047c9a6ade37e0058f4e6
blob + 72b046e72813d5b96fe84dcbe2363d5afb0fc7f9
--- got/got.c
+++ got/got.c
print_commit(struct got_commit_object *commit, struct got_object_id *id,
struct got_repository *repo, const char *path,
struct got_pathlist_head *changed_paths, int show_patch,
- int diff_context, struct got_reflist_object_id_map *refs_idmap)
+ int diff_context, struct got_reflist_object_id_map *refs_idmap,
+ const char *custom_refs_str)
{
const struct got_error *err = NULL;
char *id_str, *datestr, *logmsg0, *logmsg, *line;
time_t committer_time;
const char *author, *committer;
char *refs_str = NULL;
- struct got_reflist_head *refs;
err = got_object_id_str(&id_str, id);
if (err)
return err;
- refs = got_reflist_object_id_map_lookup(refs_idmap, id);
- if (refs) {
- err = build_refs_str(&refs_str, refs, id, repo);
- if (err)
- goto done;
+ if (custom_refs_str == NULL) {
+ struct got_reflist_head *refs;
+ refs = got_reflist_object_id_map_lookup(refs_idmap, id);
+ if (refs) {
+ err = build_refs_str(&refs_str, refs, id, repo);
+ if (err)
+ goto done;
+ }
}
printf(GOT_COMMIT_SEP_STR);
- printf("commit %s%s%s%s\n", id_str, refs_str ? " (" : "",
- refs_str ? refs_str : "", refs_str ? ")" : "");
+ if (custom_refs_str)
+ printf("commit %s (%s)\n", id_str, custom_refs_str);
+ else
+ printf("commit %s%s%s%s\n", id_str, refs_str ? " (" : "",
+ refs_str ? refs_str : "", refs_str ? ")" : "");
free(id_str);
id_str = NULL;
free(refs_str);
} else {
err = print_commit(commit, id, repo, path,
show_changed_paths ? &changed_paths : NULL,
- show_patch, diff_context, refs_idmap);
+ show_patch, diff_context, refs_idmap, NULL);
got_object_commit_close(commit);
if (err)
break;
}
err = print_commit(commit, qid->id, repo, path,
show_changed_paths ? &changed_paths : NULL,
- show_patch, diff_context, refs_idmap);
+ show_patch, diff_context, refs_idmap, NULL);
got_object_commit_close(commit);
if (err)
break;
__dead static void
usage_rebase(void)
{
- fprintf(stderr, "usage: %s rebase [-a] | [-c] | branch\n",
+ fprintf(stderr, "usage: %s rebase [-a] [-c] [-l] [branch]\n",
getprogname());
exit(1);
}
static const struct got_error *
rebase_complete(struct got_worktree *worktree, struct got_fileindex *fileindex,
struct got_reference *branch, struct got_reference *new_base_branch,
- struct got_reference *tmp_branch, struct got_repository *repo)
+ struct got_reference *tmp_branch, struct got_repository *repo,
+ int create_backup)
{
printf("Switching work tree to %s\n", got_ref_get_name(branch));
return got_worktree_rebase_complete(worktree, fileindex,
- new_base_branch, tmp_branch, branch, repo);
+ new_base_branch, tmp_branch, branch, repo, create_backup);
}
static const struct got_error *
done:
got_commit_graph_close(graph);
return err;
+}
+
+static const struct got_error *
+get_commit_brief_str(char **brief_str, struct got_commit_object *commit)
+{
+ const struct got_error *err = NULL;
+ time_t committer_time;
+ struct tm tm;
+ char datebuf[11]; /* YYYY-MM-DD + NUL */
+ char *author0 = NULL, *author, *smallerthan;
+ char *logmsg0 = NULL, *logmsg, *newline;
+
+ committer_time = got_object_commit_get_committer_time(commit);
+ if (localtime_r(&committer_time, &tm) == NULL)
+ return got_error_from_errno("localtime_r");
+ if (strftime(datebuf, sizeof(datebuf), "%G-%m-%d", &tm)
+ >= sizeof(datebuf))
+ return got_error(GOT_ERR_NO_SPACE);
+
+ author0 = strdup(got_object_commit_get_author(commit));
+ if (author0 == NULL)
+ return got_error_from_errno("strdup");
+ author = author0;
+ smallerthan = strchr(author, '<');
+ if (smallerthan && smallerthan[1] != '\0')
+ author = smallerthan + 1;
+ author[strcspn(author, "@>")] = '\0';
+
+ err = got_object_commit_get_logmsg(&logmsg0, commit);
+ if (err)
+ goto done;
+ logmsg = logmsg0;
+ while (*logmsg == '\n')
+ logmsg++;
+ newline = strchr(logmsg, '\n');
+ if (newline)
+ *newline = '\0';
+
+ if (asprintf(brief_str, "%s %s %s",
+ datebuf, author, logmsg) == -1)
+ err = got_error_from_errno("asprintf");
+done:
+ free(author0);
+ free(logmsg0);
+ return err;
+}
+
+static const struct got_error *
+print_backup_ref(const char *branch_name, const char *new_id_str,
+ struct got_object_id *old_commit_id, struct got_commit_object *old_commit,
+ struct got_reflist_object_id_map *refs_idmap,
+ struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ struct got_reflist_head *refs;
+ char *refs_str = NULL;
+ struct got_object_id *new_commit_id = NULL;
+ struct got_commit_object *new_commit = NULL;
+ char *new_commit_brief_str = NULL;
+ struct got_object_id *yca_id = NULL;
+ struct got_commit_object *yca_commit = NULL;
+ char *yca_id_str = NULL, *yca_brief_str = NULL;
+ char *custom_refs_str;
+
+ if (asprintf(&custom_refs_str, "formerly %s", branch_name) == -1)
+ return got_error_from_errno("asprintf");
+
+ err = print_commit(old_commit, old_commit_id, repo, NULL, NULL,
+ 0, 0, refs_idmap, custom_refs_str);
+ if (err)
+ goto done;
+
+ err = got_object_resolve_id_str(&new_commit_id, repo, new_id_str);
+ if (err)
+ goto done;
+
+ refs = got_reflist_object_id_map_lookup(refs_idmap, new_commit_id);
+ if (refs) {
+ err = build_refs_str(&refs_str, refs, new_commit_id, repo);
+ if (err)
+ goto done;
+ }
+
+ err = got_object_open_as_commit(&new_commit, repo, new_commit_id);
+ if (err)
+ goto done;
+
+ err = get_commit_brief_str(&new_commit_brief_str, new_commit);
+ if (err)
+ goto done;
+
+ err = got_commit_graph_find_youngest_common_ancestor(&yca_id,
+ old_commit_id, new_commit_id, repo, check_cancelled, NULL);
+ if (err)
+ goto done;
+
+ printf("has become commit %s%s%s%s\n %s\n", new_id_str,
+ refs_str ? " (" : "", refs_str ? refs_str : "",
+ refs_str ? ")" : "", new_commit_brief_str);
+ if (yca_id && got_object_id_cmp(yca_id, new_commit_id) != 0 &&
+ got_object_id_cmp(yca_id, old_commit_id) != 0) {
+ free(refs_str);
+ refs_str = NULL;
+
+ err = got_object_open_as_commit(&yca_commit, repo, yca_id);
+ if (err)
+ goto done;
+
+ err = get_commit_brief_str(&yca_brief_str, yca_commit);
+ if (err)
+ goto done;
+
+ err = got_object_id_str(&yca_id_str, yca_id);
+ if (err)
+ goto done;
+
+ refs = got_reflist_object_id_map_lookup(refs_idmap, yca_id);
+ if (refs) {
+ err = build_refs_str(&refs_str, refs, yca_id, repo);
+ if (err)
+ goto done;
+ }
+ printf("history forked at %s%s%s%s\n %s\n",
+ yca_id_str,
+ refs_str ? " (" : "", refs_str ? refs_str : "",
+ refs_str ? ")" : "", yca_brief_str);
+ }
+done:
+ free(custom_refs_str);
+ free(new_commit_id);
+ free(refs_str);
+ free(yca_id);
+ free(yca_id_str);
+ free(yca_brief_str);
+ if (new_commit)
+ got_object_commit_close(new_commit);
+ if (yca_commit)
+ got_object_commit_close(yca_commit);
+
+ return NULL;
}
static const struct got_error *
+list_backup_refs(const char *backup_ref_prefix, const char *wanted_branch_name,
+ struct got_repository *repo)
+{
+ const struct got_error *err;
+ struct got_reflist_head refs, backup_refs;
+ struct got_reflist_entry *re;
+ const size_t backup_ref_prefix_len = strlen(backup_ref_prefix);
+ struct got_object_id *old_commit_id = NULL;
+ char *branch_name = NULL;
+ struct got_commit_object *old_commit = NULL;
+ struct got_reflist_object_id_map *refs_idmap = NULL;
+
+ TAILQ_INIT(&refs);
+ TAILQ_INIT(&backup_refs);
+
+ err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
+ if (err)
+ return err;
+
+ err = got_reflist_object_id_map_create(&refs_idmap, &refs, repo);
+ if (err)
+ goto done;
+
+ if (wanted_branch_name) {
+ if (strncmp(wanted_branch_name, "refs/heads/", 11) == 0)
+ wanted_branch_name += 11;
+ }
+
+ err = got_ref_list(&backup_refs, repo, backup_ref_prefix,
+ got_ref_cmp_by_commit_timestamp_descending, repo);
+ if (err)
+ goto done;
+
+ TAILQ_FOREACH(re, &backup_refs, entry) {
+ const char *refname = got_ref_get_name(re->ref);
+ char *slash;
+
+ err = got_ref_resolve(&old_commit_id, repo, re->ref);
+ if (err)
+ break;
+
+ err = got_object_open_as_commit(&old_commit, repo,
+ old_commit_id);
+ if (err)
+ break;
+
+ if (strncmp(backup_ref_prefix, refname,
+ backup_ref_prefix_len) == 0)
+ refname += backup_ref_prefix_len;
+
+ while (refname[0] == '/')
+ refname++;
+
+ branch_name = strdup(refname);
+ if (branch_name == NULL) {
+ err = got_error_from_errno("strdup");
+ break;
+ }
+ slash = strrchr(branch_name, '/');
+ if (slash) {
+ *slash = '\0';
+ refname += strlen(branch_name) + 1;
+ }
+
+ if (wanted_branch_name == NULL ||
+ strcmp(wanted_branch_name, branch_name) == 0) {
+ err = print_backup_ref(branch_name, refname,
+ old_commit_id, old_commit, refs_idmap, repo);
+ if (err)
+ break;
+ }
+
+ free(old_commit_id);
+ old_commit_id = NULL;
+ free(branch_name);
+ branch_name = NULL;
+ got_object_commit_close(old_commit);
+ old_commit = NULL;
+ }
+done:
+ if (refs_idmap)
+ got_reflist_object_id_map_free(refs_idmap);
+ got_ref_list_free(&refs);
+ got_ref_list_free(&backup_refs);
+ free(old_commit_id);
+ free(branch_name);
+ if (old_commit)
+ got_object_commit_close(old_commit);
+ return err;
+}
+
+static const struct got_error *
cmd_rebase(int argc, char *argv[])
{
const struct got_error *error = NULL;
struct got_object_id *branch_head_commit_id = NULL, *yca_id = NULL;
struct got_commit_object *commit = NULL;
int ch, rebase_in_progress = 0, abort_rebase = 0, continue_rebase = 0;
- int histedit_in_progress = 0;
+ int histedit_in_progress = 0, create_backup = 1, list_backups = 0;
unsigned char rebase_status = GOT_STATUS_NO_CHANGE;
struct got_object_id_queue commits;
struct got_pathlist_head merged_paths;
SIMPLEQ_INIT(&commits);
TAILQ_INIT(&merged_paths);
- while ((ch = getopt(argc, argv, "ac")) != -1) {
+ while ((ch = getopt(argc, argv, "acl")) != -1) {
switch (ch) {
case 'a':
abort_rebase = 1;
case 'c':
continue_rebase = 1;
break;
+ case 'l':
+ list_backups = 1;
+ break;
default:
usage_rebase();
/* NOTREACHED */
"unveil", NULL) == -1)
err(1, "pledge");
#endif
- if (abort_rebase && continue_rebase)
- usage_rebase();
- else if (abort_rebase || continue_rebase) {
- if (argc != 0)
+ if (list_backups) {
+ if (abort_rebase)
+ option_conflict('l', 'a');
+ if (continue_rebase)
+ option_conflict('l', 'c');
+ if (argc != 0 && argc != 1)
usage_rebase();
- } else if (argc != 1)
- usage_rebase();
+ } else {
+ if (abort_rebase && continue_rebase)
+ usage_rebase();
+ else if (abort_rebase || continue_rebase) {
+ if (argc != 0)
+ usage_rebase();
+ } else if (argc != 1)
+ usage_rebase();
+ }
cwd = getcwd(NULL, 0);
if (cwd == NULL) {
}
error = got_worktree_open(&worktree, cwd);
if (error) {
- if (error->code == GOT_ERR_NOT_WORKTREE)
- error = wrap_not_worktree_error(error, "rebase", cwd);
- goto done;
+ if (list_backups) {
+ if (error->code != GOT_ERR_NOT_WORKTREE)
+ goto done;
+ } else {
+ if (error->code == GOT_ERR_NOT_WORKTREE)
+ error = wrap_not_worktree_error(error,
+ "rebase", cwd);
+ goto done;
+ }
}
- error = got_repo_open(&repo, got_worktree_get_repo_path(worktree),
- NULL);
+ error = got_repo_open(&repo,
+ worktree ? got_worktree_get_repo_path(worktree) : cwd, NULL);
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_backups) {
+ error = list_backup_refs(GOT_WORKTREE_REBASE_BACKUP_REF_PREFIX,
+ argc == 1 ? argv[0] : NULL, repo);
+ goto done; /* nothing else to do */
+ }
+
error = got_worktree_histedit_in_progress(&histedit_in_progress,
worktree);
if (error)
if (SIMPLEQ_EMPTY(&commits)) {
if (continue_rebase) {
error = rebase_complete(worktree, fileindex,
- branch, new_base_branch, tmp_branch, repo);
+ branch, new_base_branch, tmp_branch, repo,
+ create_backup);
goto done;
} else {
/* Fast-forward the reference of the branch. */
new_head_commit_id);
if (error)
goto done;
+ /* No backup needed since objects did not change. */
+ create_backup = 0;
}
}
"conflicts must be resolved before rebasing can continue");
} else
error = rebase_complete(worktree, fileindex, branch,
- new_base_branch, tmp_branch, repo);
+ new_base_branch, tmp_branch, repo, create_backup);
done:
got_object_id_queue_free(&commits);
free(branch_head_commit_id);
__dead static void
usage_histedit(void)
{
- fprintf(stderr, "usage: %s histedit [-a] [-c] [-f] [-F histedit-script] [-m]\n",
- getprogname());
+ fprintf(stderr, "usage: %s histedit [-a] [-c] [-f] "
+ "[-F histedit-script] [-m] [-l] [branch]\n", getprogname());
exit(1);
}
struct got_update_progress_arg upa;
int edit_in_progress = 0, abort_edit = 0, continue_edit = 0;
int edit_logmsg_only = 0, fold_only = 0;
+ int list_backups = 0;
const char *edit_script_path = NULL;
unsigned char rebase_status = GOT_STATUS_NO_CHANGE;
struct got_object_id_queue commits;
TAILQ_INIT(&merged_paths);
memset(&upa, 0, sizeof(upa));
- while ((ch = getopt(argc, argv, "acfF:m")) != -1) {
+ while ((ch = getopt(argc, argv, "acfF:ml")) != -1) {
switch (ch) {
case 'a':
abort_edit = 1;
case 'm':
edit_logmsg_only = 1;
break;
+ case 'l':
+ list_backups = 1;
+ break;
default:
usage_histedit();
/* NOTREACHED */
option_conflict('f', 'm');
if (edit_script_path && fold_only)
option_conflict('F', 'f');
- if (argc != 0)
+ if (list_backups) {
+ if (abort_edit)
+ option_conflict('l', 'a');
+ if (continue_edit)
+ option_conflict('l', 'c');
+ if (edit_script_path)
+ option_conflict('l', 'F');
+ if (edit_logmsg_only)
+ option_conflict('l', 'm');
+ if (fold_only)
+ option_conflict('l', 'f');
+ if (argc != 0 && argc != 1)
+ usage_histedit();
+ } else if (argc != 0)
usage_histedit();
/*
}
error = got_worktree_open(&worktree, cwd);
if (error) {
- if (error->code == GOT_ERR_NOT_WORKTREE)
- error = wrap_not_worktree_error(error, "histedit", cwd);
- goto done;
+ if (list_backups) {
+ if (error->code != GOT_ERR_NOT_WORKTREE)
+ goto done;
+ } else {
+ if (error->code == GOT_ERR_NOT_WORKTREE)
+ error = wrap_not_worktree_error(error,
+ "histedit", cwd);
+ goto done;
+ }
+ }
+
+ if (list_backups) {
+ error = got_repo_open(&repo,
+ worktree ? got_worktree_get_repo_path(worktree) : cwd,
+ NULL);
+ if (error != NULL)
+ goto done;
+ error = apply_unveil(got_repo_get_path(repo), 0,
+ worktree ? got_worktree_get_root_path(worktree) : NULL);
+ if (error)
+ goto done;
+ error = list_backup_refs(
+ GOT_WORKTREE_HISTEDIT_BACKUP_REF_PREFIX,
+ argc == 1 ? argv[0] : NULL, repo);
+ goto done; /* nothing else to do */
}
error = got_repo_open(&repo, got_worktree_get_repo_path(worktree),
blob - 1c93ac41afa54d36bb8a41c39bec6058ab4d6204
blob + e330768de849dc0fa39914378bac64b6f8a09c01
--- include/got_reference.h
+++ include/got_reference.h
struct got_reference *, struct got_reference *);
/*
+ * An implementation of got_ref_cmp_cb which compares commit timestamps.
+ * Requires a struct got_repository * as the void * argument.
+ */
+const struct got_error *got_ref_cmp_by_commit_timestamp_descending(void *,
+ int *, struct got_reference *, struct got_reference *);
+
+/*
* Append all known references to a caller-provided ref list head.
* Optionally limit references returned to those within a given
* reference namespace. Sort the list with the provided reference comparison
blob - 24fddd52e81d96ff7b1800a61de1f80319326a3e
blob + 9246dbdca16b2907e56bbebc9f597bb7e3e91b10
--- include/got_worktree.h
+++ include/got_worktree.h
/*
* Complete the current rebase operation. This should be called once all
* commits have been rebased successfully.
+ * The create_backup parameter controls whether the rebased branch will
+ * be backed up via a reference in refs/got/backup/rebase/.
*/
const struct got_error *got_worktree_rebase_complete(struct got_worktree *,
struct got_fileindex *, struct got_reference *, struct got_reference *,
- struct got_reference *, struct got_repository *);
+ struct got_reference *, struct got_repository *, int create_backup);
/*
* Abort the current rebase operation.
const struct got_error *
got_worktree_path_info(struct got_worktree *, struct got_pathlist_head *,
got_worktree_path_info_cb, void *, got_cancel_cb , void *);
+
+/* References pointing at pre-rebase commit backups. */
+#define GOT_WORKTREE_REBASE_BACKUP_REF_PREFIX "refs/got/backup/rebase"
+
+/* References pointing at pre-histedit commit backups. */
+#define GOT_WORKTREE_HISTEDIT_BACKUP_REF_PREFIX "refs/got/backup/histedit"
blob - 71840ebb38db39cca7f0629d64ca474b0077e335
blob + 4b530db4089bee13c3808f740ec124254aa41fca
--- lib/reference.c
+++ lib/reference.c
got_object_tag_close(tag1);
if (tag2)
got_object_tag_close(tag2);
+ if (commit1)
+ got_object_commit_close(commit1);
+ if (commit2)
+ got_object_commit_close(commit2);
+ return err;
+}
+
+const struct got_error *
+got_ref_cmp_by_commit_timestamp_descending(void *arg, int *cmp,
+ struct got_reference *ref1, struct got_reference *ref2)
+{
+ const struct got_error *err;
+ struct got_repository *repo = arg;
+ struct got_object_id *id1, *id2 = NULL;
+ struct got_commit_object *commit1 = NULL, *commit2 = NULL;
+ time_t time1, time2;
+
+ *cmp = 0;
+
+ err = got_ref_resolve(&id1, repo, ref1);
+ if (err)
+ return err;
+ err = got_ref_resolve(&id2, repo, ref2);
+ if (err)
+ goto done;
+
+ err = got_object_open_as_commit(&commit1, repo, id1);
+ if (err)
+ goto done;
+ err = got_object_open_as_commit(&commit2, repo, id2);
+ if (err)
+ goto done;
+
+ time1 = got_object_commit_get_committer_time(commit1);
+ time2 = got_object_commit_get_committer_time(commit2);
+ if (time1 < time2)
+ *cmp = 1;
+ else if (time2 < time1)
+ *cmp = -1;
+done:
+ free(id1);
+ free(id2);
if (commit1)
got_object_commit_close(commit1);
if (commit2)
blob - e81fcf0f255ee6dd35a283cf1c758066903935f0
blob + 3d7fd16f3bc469ada987b984a5a539da754992ca
--- lib/worktree.c
+++ lib/worktree.c
free(new_base_branch_ref_name);
free(branch_ref_name);
free(commit_ref_name);
+ return err;
+}
+
+const struct got_error *
+create_backup_ref(const char *backup_ref_prefix, struct got_reference *branch,
+ struct got_object_id *new_commit_id, struct got_repository *repo)
+{
+ const struct got_error *err;
+ struct got_reference *ref = NULL;
+ struct got_object_id *old_commit_id = NULL;
+ const char *branch_name = NULL;
+ char *new_id_str = NULL;
+ char *refname = NULL;
+
+ branch_name = got_ref_get_name(branch);
+ if (strncmp(branch_name, "refs/heads/", 11) != 0)
+ return got_error(GOT_ERR_BAD_REF_NAME); /* should not happen */
+ branch_name += 11;
+
+ err = got_object_id_str(&new_id_str, new_commit_id);
+ if (err)
+ return err;
+
+ if (asprintf(&refname, "%s/%s/%s", backup_ref_prefix, branch_name,
+ new_id_str) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ err = got_ref_resolve(&old_commit_id, repo, branch);
+ if (err)
+ goto done;
+
+ err = got_ref_alloc(&ref, refname, old_commit_id);
+ if (err)
+ goto done;
+
+ err = got_ref_write(ref, repo);
+done:
+ free(new_id_str);
+ free(refname);
+ free(old_commit_id);
+ if (ref)
+ got_ref_close(ref);
return err;
}
got_worktree_rebase_complete(struct got_worktree *worktree,
struct got_fileindex *fileindex, struct got_reference *new_base_branch,
struct got_reference *tmp_branch, struct got_reference *rebased_branch,
- struct got_repository *repo)
+ struct got_repository *repo, int create_backup)
{
const struct got_error *err, *unlockerr, *sync_err;
struct got_object_id *new_head_commit_id = NULL;
err = got_ref_resolve(&new_head_commit_id, repo, tmp_branch);
if (err)
return err;
+
+ if (create_backup) {
+ err = create_backup_ref(GOT_WORKTREE_REBASE_BACKUP_REF_PREFIX,
+ rebased_branch, new_head_commit_id, repo);
+ if (err)
+ goto done;
+ }
err = got_ref_change_ref(rebased_branch, new_head_commit_id);
if (err)
err = got_ref_open(&resolved, repo,
got_ref_get_symref_target(edited_branch), 0);
+ if (err)
+ goto done;
+
+ err = create_backup_ref(GOT_WORKTREE_HISTEDIT_BACKUP_REF_PREFIX,
+ resolved, new_head_commit_id, repo);
if (err)
goto done;
blob - 4eb63f995b4b84ea70ff445e9e93e6389910486d
blob + fbd40bcace0448588f375c648b957b069821ab5f
--- regress/cmdline/common.sh
+++ regress/cmdline/common.sh
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GOT_AUTHOR="$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>"
export GOT_AUTHOR_8="flan_hac"
+export GOT_AUTHOR_11="flan_hacker"
export GOT_LOG_DEFAULT_LIMIT=0
export GOT_TEST_ROOT="/tmp"
blob - 82d727d0ed29a44abebc77cbd4f0c415c5828142
blob + b6f5b85dd337c44468c9ff11b1cf6976705b46fb
--- regress/cmdline/histedit.sh
+++ regress/cmdline/histedit.sh
local testroot=`test_init histedit_no_op`
local orig_commit=`git_show_head $testroot/repo`
+ local orig_author_time=`git_show_author_time $testroot/repo`
echo "modified alpha on master" > $testroot/repo/alpha
(cd $testroot/repo && git rm -q beta)
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`
+ local old_author_time2=`git_show_author_time $testroot/repo`
got diff -r $testroot/repo $orig_commit $old_commit2 \
> $testroot/diff.expected
local new_commit1=`git_show_parent_commit $testroot/repo`
local new_commit2=`git_show_head $testroot/repo`
+ local new_author_time2=`git_show_author_time $testroot/repo`
local short_old_commit1=`trim_obj_id 28 $old_commit1`
local short_old_commit2=`trim_obj_id 28 $old_commit2`
(cd $testroot/wt && got update > $testroot/stdout)
echo 'Already up-to-date' > $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"
+ fi
+
+ # We should have a backup of old commits
+ (cd $testroot/repo && got histedit -l > $testroot/stdout)
+ d_orig2=`env TZ=UTC date -r $old_author_time2 +"%a %b %e %X %Y UTC"`
+ d_new2=`env TZ=UTC date -r $new_author_time2 +"%G-%m-%d"`
+ d_orig=`env TZ=UTC date -r $orig_author_time +"%G-%m-%d"`
+ cat > $testroot/stdout.expected <<EOF
+-----------------------------------------------
+commit $old_commit2 (formerly master)
+from: $GOT_AUTHOR
+date: $d_orig2
+
+ committing to zeta on master
+
+has become commit $new_commit2 (master)
+ $d_new2 $GOT_AUTHOR_11 committing to zeta on master
+history forked at $orig_commit
+ $d_orig $GOT_AUTHOR_11 adding the test tree
+EOF
cmp -s $testroot/stdout.expected $testroot/stdout
ret="$?"
if [ "$ret" != "0" ]; then
blob - 5bf2ae44883819420fe82ae946ad7f2f328b84b7
blob + f972bece510f4dafdcb5218eae89fbaa86daef0c
--- regress/cmdline/rebase.sh
+++ regress/cmdline/rebase.sh
test_rebase_basic() {
local testroot=`test_init rebase_basic`
+ local commit0=`git_show_head $testroot/repo`
+ local commit0_author_time=`git_show_author_time $testroot/repo`
(cd $testroot/repo && git checkout -q -b newbranch)
echo "modified delta on branch" > $testroot/repo/gamma/delta
local orig_commit1=`git_show_parent_commit $testroot/repo`
local orig_commit2=`git_show_head $testroot/repo`
+ local orig_author_time2=`git_show_author_time $testroot/repo`
(cd $testroot/repo && git checkout -q master)
echo "modified zeta on master" > $testroot/repo/epsilon/zeta
(cd $testroot/repo && git checkout -q newbranch)
local new_commit1=`git_show_parent_commit $testroot/repo`
local new_commit2=`git_show_head $testroot/repo`
+ local new_author_time2=`git_show_author_time $testroot/repo`
local short_orig_commit1=`trim_obj_id 28 $orig_commit1`
local short_orig_commit2=`trim_obj_id 28 $orig_commit2`
ret="$?"
if [ "$ret" != "0" ]; then
diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
fi
+
+ # We should have a backup of old commits
+ (cd $testroot/repo && got rebase -l > $testroot/stdout)
+ d_orig2=`env TZ=UTC date -r $orig_author_time2 +"%a %b %e %X %Y UTC"`
+ d_new2=`env TZ=UTC date -r $new_author_time2 +"%G-%m-%d"`
+ d_0=`env TZ=UTC date -r $commit0_author_time +"%G-%m-%d"`
+ cat > $testroot/stdout.expected <<EOF
+-----------------------------------------------
+commit $orig_commit2 (formerly newbranch)
+from: $GOT_AUTHOR
+date: $d_orig2
+
+ committing more changes on newbranch
+
+has become commit $new_commit2 (newbranch)
+ $d_new2 $GOT_AUTHOR_11 committing more changes on newbranch
+history forked at $commit0
+ $d_0 $GOT_AUTHOR_11 adding the test tree
+EOF
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
test_done "$testroot" "$ret"
}
ret="$?"
if [ "$ret" != "0" ]; then
diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
fi
+
+ # Forward-only rebase operations should not be backed up
+ (cd $testroot/repo && got rebase -l > $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
+ fi
test_done "$testroot" "$ret"
}