#include "config.h"
#include "lockfile.h"
#include "dir.h"
+#include "object-store.h"
#include "object.h"
#include "commit.h"
#include "sequencer.h"
#include "worktree.h"
#include "oidmap.h"
#include "oidset.h"
+#include "commit-slab.h"
+#include "alias.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
static GIT_PATH_FUNC(rebase_path_rewritten_pending,
"rebase-merge/rewritten-pending")
+/*
+ * The path of the file containig the OID of the "squash onto" commit, i.e.
+ * the dummy commit used for `reset [new root]`.
+ */
+static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
+
/*
* The path of the file listing refs that need to be deleted after the rebase
* finishes. This is used by the `label` command to record the need for cleanup.
warning(_("invalid commit message cleanup mode '%s'"),
s);
+ free((char *)s);
return status;
}
* (typically rebase --interactive) wants to take care
* of the commit itself so remove CHERRY_PICK_HEAD
*/
- unlink(git_path_cherry_pick_head());
+ unlink(git_path_cherry_pick_head(the_repository));
return;
}
static struct tree *empty_tree(void)
{
- return lookup_tree(the_hash_algo->empty_tree);
+ return lookup_tree(the_repository, the_repository->hash_algo->empty_tree);
}
static int error_dirty_index(struct replay_opts *opts)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
- to, unborn ? &null_oid : from,
+ to, unborn && !is_rebase_i(opts) ?
+ &null_oid : from,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
return !clean;
}
+static struct object_id *get_cache_tree_oid(void)
+{
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+
+ if (!cache_tree_fully_valid(active_cache_tree))
+ if (cache_tree_update(&the_index, 0)) {
+ error(_("unable to update cache tree"));
+ return NULL;
+ }
+
+ return &active_cache_tree->oid;
+}
+
static int is_index_unchanged(void)
{
- struct object_id head_oid;
+ struct object_id head_oid, *cache_tree_oid;
struct commit *head_commit;
if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
return error(_("could not resolve HEAD commit"));
- head_commit = lookup_commit(&head_oid);
+ head_commit = lookup_commit(the_repository, &head_oid);
/*
* If head_commit is NULL, check_commit, called from
if (parse_commit(head_commit))
return -1;
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
-
- if (!cache_tree_fully_valid(active_cache_tree))
- if (cache_tree_update(&the_index, 0))
- return error(_("unable to update cache tree"));
+ if (!(cache_tree_oid = get_cache_tree_oid()))
+ return -1;
- return !oidcmp(&active_cache_tree->oid,
- get_commit_tree_oid(head_commit));
+ return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit));
}
static int write_author_script(const char *message)
return NULL;
}
+/* Read author-script and return an ident line (author <email> timestamp) */
+static const char *read_author_ident(struct strbuf *buf)
+{
+ const char *keys[] = {
+ "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
+ };
+ char *in, *out, *eol;
+ int i = 0, len;
+
+ if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
+ return NULL;
+
+ /* dequote values and construct ident line in-place */
+ for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
+ if (!skip_prefix(in, keys[i], (const char **)&in)) {
+ warning("could not parse '%s' (looking for '%s'",
+ rebase_path_author_script(), keys[i]);
+ return NULL;
+ }
+
+ eol = strchrnul(in, '\n');
+ *eol = '\0';
+ sq_dequote(in);
+ len = strlen(in);
+
+ if (i > 0) /* separate values by spaces */
+ *(out++) = ' ';
+ if (i == 1) /* email needs to be surrounded by <...> */
+ *(out++) = '<';
+ memmove(out, in, len);
+ out += len;
+ if (i == 1) /* email needs to be surrounded by <...> */
+ *(out++) = '>';
+ in = eol + 1;
+ }
+
+ if (i < 3) {
+ warning("could not parse '%s' (looking for '%s')",
+ rebase_path_author_script(), keys[i]);
+ return NULL;
+ }
+
+ buf->len = out - buf->buf;
+ return buf->buf;
+}
+
static const char staged_changes_advice[] =
N_("you have staged changes in your working tree\n"
"If these changes are meant to be squashed into the previous commit, run:\n"
#define AMEND_MSG (1<<2)
#define CLEANUP_MSG (1<<3)
#define VERIFY_MSG (1<<4)
+#define CREATE_ROOT_COMMIT (1<<5)
/*
* If we are cherry-pick, and if the merge did not result in
struct child_process cmd = CHILD_PROCESS_INIT;
const char *value;
+ if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
+ struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
+ const char *author = is_rebase_i(opts) ?
+ read_author_ident(&script) : NULL;
+ struct object_id root_commit, *cache_tree_oid;
+ int res = 0;
+
+ if (!defmsg)
+ BUG("root commit without message");
+
+ if (!(cache_tree_oid = get_cache_tree_oid()))
+ res = -1;
+
+ if (!res)
+ res = strbuf_read_file(&msg, defmsg, 0);
+
+ if (res <= 0)
+ res = error_errno(_("could not read '%s'"), defmsg);
+ else
+ res = commit_tree(msg.buf, msg.len, cache_tree_oid,
+ NULL, &root_commit, author,
+ opts->gpg_sign);
+
+ strbuf_release(&msg);
+ strbuf_release(&script);
+ if (!res) {
+ update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR);
+ res = update_ref(NULL, "HEAD", &root_commit, NULL, 0,
+ UPDATE_REFS_MSG_ON_ERR);
+ }
+ return res < 0 ? error(_("writing root commit")) : 0;
+ }
+
cmd.git_cmd = 1;
if (is_rebase_i(opts)) {
struct strbuf author_ident = STRBUF_INIT;
struct strbuf committer_ident = STRBUF_INIT;
- commit = lookup_commit(oid);
+ commit = lookup_commit(the_repository, oid);
if (!commit)
die(_("couldn't look up newly created commit"));
if (parse_commit(commit))
if (get_oid("HEAD", &oid)) {
current_head = NULL;
} else {
- current_head = lookup_commit_reference(&oid);
+ current_head = lookup_commit_reference(the_repository, &oid);
if (!current_head)
return error(_("could not parse HEAD"));
if (oidcmp(&oid, ¤t_head->object.oid)) {
if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
get_commit_tree_oid(current_head) :
- &empty_tree_oid, &tree)) {
+ the_hash_algo->empty_tree, &tree)) {
res = 1; /* run 'git commit' to display error message */
goto out;
}
{
int res = 1;
- if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
+ if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
+ !(flags & CREATE_ROOT_COMMIT)) {
struct object_id oid;
struct strbuf sb = STRBUF_INIT;
&oid);
strbuf_release(&sb);
if (!res) {
- unlink(git_path_cherry_pick_head());
- unlink(git_path_merge_msg());
+ unlink(git_path_cherry_pick_head(the_repository));
+ unlink(git_path_merge_msg(the_repository));
if (!is_rebase_i(opts))
print_commit_summary(NULL, &oid,
SUMMARY_SHOW_AUTHOR_DATE);
return command == TODO_FIXUP || command == TODO_SQUASH;
}
+/* Does this command create a (non-merge) commit? */
+static int is_pick_or_similar(enum todo_command command)
+{
+ switch (command) {
+ case TODO_PICK:
+ case TODO_REVERT:
+ case TODO_EDIT:
+ case TODO_REWORD:
+ case TODO_FIXUP:
+ case TODO_SQUASH:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
static int update_squash_messages(enum todo_command command,
struct commit *commit, struct replay_opts *opts)
{
if (get_oid("HEAD", &head))
return error(_("need a HEAD to fixup"));
- if (!(head_commit = lookup_commit_reference(&head)))
+ if (!(head_commit = lookup_commit_reference(the_repository, &head)))
return error(_("could not read HEAD"));
if (!(head_message = get_commit_buffer(head_commit, NULL)))
return error(_("could not read HEAD's commit message"));
struct replay_opts *opts, int final_fixup)
{
unsigned int flags = opts->edit ? EDIT_MSG : 0;
- const char *msg_file = opts->edit ? NULL : git_path_merge_msg();
+ const char *msg_file = opts->edit ? NULL : git_path_merge_msg(the_repository);
struct object_id head;
struct commit *base, *next, *parent;
const char *base_label, *next_label;
return error(_("your index file is unmerged."));
} else {
unborn = get_oid("HEAD", &head);
- if (unborn)
+ /* Do we want to generate a root commit? */
+ if (is_pick_or_similar(command) && opts->have_squash_onto &&
+ !oidcmp(&head, &opts->squash_onto)) {
+ if (is_fixup(command))
+ return error(_("cannot fixup root commit"));
+ flags |= CREATE_ROOT_COMMIT;
+ unborn = 1;
+ } else if (unborn)
oidcpy(&head, the_hash_algo->empty_tree);
- if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
+ if (index_differs_from(unborn ? empty_tree_oid_hex() : "HEAD",
NULL, 0))
return error_dirty_index(opts);
}
flags |= CLEANUP_MSG;
msg_file = rebase_path_fixup_msg();
} else {
- const char *dest = git_path_squash_msg();
+ const char *dest = git_path_squash_msg(the_repository);
unlink(dest);
if (copy_file(dest, rebase_path_squash_msg(), 0666))
return error(_("could not rename '%s' to '%s'"),
rebase_path_squash_msg(), dest);
- unlink(git_path_merge_msg());
+ unlink(git_path_merge_msg(the_repository));
msg_file = dest;
flags |= EDIT_MSG;
}
res = do_recursive_merge(base, next, base_label, next_label,
&head, &msgbuf, opts);
if (res < 0)
- return res;
+ goto leave;
+
res |= write_message(msgbuf.buf, msgbuf.len,
- git_path_merge_msg(), 0);
+ git_path_merge_msg(the_repository), 0);
} else {
struct commit_list *common = NULL;
struct commit_list *remotes = NULL;
res = write_message(msgbuf.buf, msgbuf.len,
- git_path_merge_msg(), 0);
+ git_path_merge_msg(the_repository), 0);
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
if (status < 0)
return -1;
- item->commit = lookup_commit_reference(&commit_oid);
+ item->commit = lookup_commit_reference(the_repository, &commit_oid);
return !item->commit;
}
}
}
+ if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
+ if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
+ return error(_("unusable squash-onto"));
+ opts->have_squash_onto = 1;
+ }
+
return 0;
}
{
struct object_id head_oid;
- if (!file_exists(git_path_cherry_pick_head()) &&
- !file_exists(git_path_revert_head()))
+ if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
+ !file_exists(git_path_revert_head(the_repository)))
return error(_("no cherry-pick or revert in progress"));
if (read_ref_full("HEAD", 0, &head_oid, NULL))
return error(_("cannot resolve HEAD"));
if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
return error(_("could not copy '%s' to '%s'"),
rebase_path_squash_msg(), rebase_path_message());
- unlink(git_path_merge_msg());
- if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
+ unlink(git_path_merge_msg(the_repository));
+ if (copy_file(git_path_merge_msg(the_repository), rebase_path_message(), 0666))
return error(_("could not copy '%s' to '%s'"),
- rebase_path_message(), git_path_merge_msg());
+ rebase_path_message(),
+ git_path_merge_msg(the_repository));
return error_with_patch(commit, subject, subject_len, opts, 1, 0);
}
if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
return -1;
- /* Determine the length of the label */
- for (i = 0; i < len; i++)
- if (isspace(name[i]))
- len = i;
-
- strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
- if (get_oid(ref_name.buf, &oid) &&
- get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
- error(_("could not read '%s'"), ref_name.buf);
- rollback_lock_file(&lock);
- strbuf_release(&ref_name);
- return -1;
+ if (len == 10 && !strncmp("[new root]", name, len)) {
+ if (!opts->have_squash_onto) {
+ const char *hex;
+ if (commit_tree("", 0, the_hash_algo->empty_tree,
+ NULL, &opts->squash_onto,
+ NULL, NULL))
+ return error(_("writing fake root commit"));
+ opts->have_squash_onto = 1;
+ hex = oid_to_hex(&opts->squash_onto);
+ if (write_message(hex, strlen(hex),
+ rebase_path_squash_onto(), 0))
+ return error(_("writing squash-onto"));
+ }
+ oidcpy(&oid, &opts->squash_onto);
+ } else {
+ /* Determine the length of the label */
+ for (i = 0; i < len; i++)
+ if (isspace(name[i]))
+ len = i;
+
+ strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
+ if (get_oid(ref_name.buf, &oid) &&
+ get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
+ error(_("could not read '%s'"), ref_name.buf);
+ rollback_lock_file(&lock);
+ strbuf_release(&ref_name);
+ return -1;
+ }
}
memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
goto leave_merge;
}
+ if (opts->have_squash_onto &&
+ !oidcmp(&head_commit->object.oid, &opts->squash_onto)) {
+ /*
+ * When the user tells us to "merge" something into a
+ * "[new root]", let's simply fast-forward to the merge head.
+ */
+ rollback_lock_file(&lock);
+ ret = fast_forward_to(&merge_commit->object.oid,
+ &head_commit->object.oid, 0, opts);
+ goto leave_merge;
+ }
+
if (commit) {
const char *message = get_commit_buffer(commit, NULL);
const char *body;
write_author_script(message);
find_commit_subject(message, &body);
len = strlen(body);
- ret = write_message(body, len, git_path_merge_msg(), 0);
+ ret = write_message(body, len, git_path_merge_msg(the_repository), 0);
unuse_commit_buffer(commit, message);
if (ret) {
error_errno(_("could not write '%s'"),
- git_path_merge_msg());
+ git_path_merge_msg(the_repository));
goto leave_merge;
}
} else {
len = buf.len;
}
- ret = write_message(p, len, git_path_merge_msg(), 0);
+ ret = write_message(p, len, git_path_merge_msg(the_repository), 0);
strbuf_release(&buf);
if (ret) {
error_errno(_("could not write '%s'"),
- git_path_merge_msg());
+ git_path_merge_msg(the_repository));
goto leave_merge;
}
}
}
write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
- git_path_merge_head(), 0);
- write_message("no-ff", 5, git_path_merge_mode(), 0);
+ git_path_merge_head(the_repository), 0);
+ write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
bases = get_merge_bases(head_commit, merge_commit);
if (bases && !oidcmp(&merge_commit->object.oid,
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
- ret = !!run_git_commit(git_path_merge_msg(), opts,
+ ret = !!run_git_commit(git_path_merge_msg(the_repository), opts,
run_commit_flags);
leave_merge:
{
const char *argv[] = { "commit", NULL };
- if (!file_exists(git_path_cherry_pick_head()) &&
- !file_exists(git_path_revert_head()))
+ if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
+ !file_exists(git_path_revert_head(the_repository)))
return error(_("no cherry-pick or revert in progress"));
return run_command_v_opt(argv, RUN_GIT_CMD);
}
}
if (is_clean) {
- const char *cherry_pick_head = git_path_cherry_pick_head();
+ const char *cherry_pick_head = git_path_cherry_pick_head(the_repository);
if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
return error(_("could not remove CHERRY_PICK_HEAD"));
if (!is_rebase_i(opts)) {
/* Verify that the conflict has been resolved */
- if (file_exists(git_path_cherry_pick_head()) ||
- file_exists(git_path_revert_head())) {
+ if (file_exists(git_path_cherry_pick_head(the_repository)) ||
+ file_exists(git_path_revert_head(the_repository))) {
res = continue_single_pick();
if (res)
goto release_todo_list;
continue;
if (!get_oid(name, &oid)) {
- if (!lookup_commit_reference_gently(&oid, 1)) {
+ if (!lookup_commit_reference_gently(the_repository, &oid, 1)) {
enum object_type type = oid_object_info(the_repository,
&oid,
NULL);
p[i] = save;
}
}
- } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&
+ } else if (((len = strlen(label)) == the_hash_algo->hexsz &&
!get_oid_hex(label, &dummy)) ||
(len == 1 && *label == '#') ||
hashmap_get_from_hash(&state->labels,
}
if (!commit)
- fprintf(out, "%s onto\n", cmd_reset);
+ fprintf(out, "%s %s\n", cmd_reset,
+ rebase_cousins ? "onto" : "[new root]");
else {
const char *to = NULL;
return CHECK_IGNORE;
}
+define_commit_slab(commit_seen, unsigned char);
/*
* Check if the user dropped some commits by mistake
* Behaviour determined by rebase.missingCommitsCheck.
struct todo_list todo_list = TODO_LIST_INIT;
struct strbuf missing = STRBUF_INIT;
int advise_to_edit_todo = 0, res = 0, i;
+ struct commit_seen commit_seen;
+
+ init_commit_seen(&commit_seen);
strbuf_addstr(&todo_file, rebase_path_todo());
if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
for (i = 0; i < todo_list.nr; i++) {
struct commit *commit = todo_list.items[i].commit;
if (commit)
- commit->util = (void *)1;
+ *commit_seen_at(&commit_seen, commit) = 1;
}
todo_list_release(&todo_list);
for (i = todo_list.nr - 1; i >= 0; i--) {
struct todo_item *item = todo_list.items + i;
struct commit *commit = item->commit;
- if (commit && !commit->util) {
+ if (commit && !*commit_seen_at(&commit_seen, commit)) {
strbuf_addf(&missing, " - %s %.*s\n",
short_commit_name(commit),
item->arg_len, item->arg);
- commit->util = (void *)1;
+ *commit_seen_at(&commit_seen, commit) = 1;
}
}
"The possible behaviours are: ignore, warn, error.\n\n"));
leave_check:
+ clear_commit_seen(&commit_seen);
strbuf_release(&todo_file);
todo_list_release(&todo_list);
return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
}
+define_commit_slab(commit_todo_item, struct todo_item *);
+
/*
* Rearrange the todo list that has both "pick commit-id msg" and "pick
* commit-id fixup!/squash! msg" in it so that the latter is put immediately
struct hashmap subject2item;
int res = 0, rearranged = 0, *next, *tail, i;
char **subjects;
+ struct commit_todo_item commit_todo;
if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
return -1;
return -1;
}
+ init_commit_todo_item(&commit_todo);
/*
* The hashmap maps onelines to the respective todo list index.
*
if (is_fixup(item->command)) {
todo_list_release(&todo_list);
+ clear_commit_todo_item(&commit_todo);
return error(_("the script was already rearranged."));
}
- item->commit->util = item;
+ *commit_todo_item_at(&commit_todo, item->commit) = item;
parse_commit(item->commit);
commit_buffer = get_commit_buffer(item->commit, NULL);
else if (!strchr(p, ' ') &&
(commit2 =
lookup_commit_reference_by_name(p)) &&
- commit2->util)
+ *commit_todo_item_at(&commit_todo, commit2))
/* found by commit name */
- i2 = (struct todo_item *)commit2->util
+ i2 = *commit_todo_item_at(&commit_todo, commit2)
- todo_list.items;
else {
/* copy can be a prefix of the commit subject */
hashmap_free(&subject2item, 1);
todo_list_release(&todo_list);
+ clear_commit_todo_item(&commit_todo);
return res;
}