blob: 423c8beaaf79a1b6e50149b49e251768010dd7e7 [file]
#define USE_THE_REPOSITORY_VARIABLE
#include "builtin.h"
#include "advice.h"
#include "cache-tree.h"
#include "commit.h"
#include "commit-reach.h"
#include "config.h"
#include "editor.h"
#include "environment.h"
#include "gettext.h"
#include "hex.h"
#include "lockfile.h"
#include "merge-ort.h"
#include "oidmap.h"
#include "parse-options.h"
#include "path.h"
#include "read-cache.h"
#include "refs.h"
#include "replay.h"
#include "reset.h"
#include "revision.h"
#include "sequencer.h"
#include "strvec.h"
#include "tree.h"
#include "tree-walk.h"
#include "unpack-trees.h"
#include "wt-status.h"
#define GIT_HISTORY_DROP_USAGE \
N_("git history drop <commit> [--dry-run] [--update-refs=(branches|head)] [--empty=(drop|keep|abort)]")
#define GIT_HISTORY_FIXUP_USAGE \
N_("git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)]")
#define GIT_HISTORY_REWORD_USAGE \
N_("git history reword <commit> [--dry-run] [--update-refs=(branches|head)]")
#define GIT_HISTORY_SPLIT_USAGE \
N_("git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]")
#define GIT_HISTORY_SQUASH_USAGE \
N_("git history squash [--dry-run] [--update-refs=(branches|head)] [--reedit-message] <revision-range>")
static void change_data_free(void *util, const char *str UNUSED)
{
struct wt_status_change_data *d = util;
free(d->rename_source);
free(d);
}
static int fill_commit_message(struct repository *repo,
const struct object_id *old_tree,
const struct object_id *new_tree,
const char *default_message,
const char *action,
struct strbuf *out)
{
const char *path = git_path_commit_editmsg();
const char *hint =
_("Please enter the commit message for the %s changes."
" Lines starting\nwith '%s' will be ignored, and an"
" empty message aborts the commit.\n");
struct wt_status s;
wt_status_prepare(repo, &s);
FREE_AND_NULL(s.branch);
s.ahead_behind_flags = AHEAD_BEHIND_QUICK;
s.commit_template = 1;
s.colopts = 0;
s.display_comment_prefix = 1;
s.hints = 0;
s.use_color = 0;
s.whence = FROM_COMMIT;
s.committable = 1;
s.fp = fopen(path, "w");
if (!s.fp)
return error_errno(_("could not open '%s'"), path);
strbuf_addstr(out, default_message);
strbuf_addch(out, '\n');
strbuf_commented_addf(out, comment_line_str, hint, action, comment_line_str);
if (fwrite(out->buf, 1, out->len, s.fp) != out->len)
die_errno(_("could not write to '%s'"), path);
wt_status_collect_changes_trees(&s, old_tree, new_tree);
wt_status_print(&s);
wt_status_collect_free_buffers(&s);
string_list_clear_func(&s.change, change_data_free);
if (fclose(s.fp))
die_errno(_("could not write to '%s'"), path);
strbuf_reset(out);
if (launch_editor(path, out, NULL)) {
fprintf(stderr, _("Aborting commit as launching the editor failed.\n"));
return -1;
}
strbuf_stripspace(out, comment_line_str);
cleanup_message(out, COMMIT_MSG_CLEANUP_ALL, 0);
if (!out->len) {
fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
return -1;
}
return 0;
}
enum commit_tree_flags {
COMMIT_TREE_EDIT_MESSAGE = (1 << 0),
};
static int commit_tree_ext(struct repository *repo,
const char *action,
struct commit *commit_with_message,
const char *message_template,
const struct commit_list *parents,
const struct object_id *old_tree,
const struct object_id *new_tree,
struct commit **out,
enum commit_tree_flags flags)
{
const char *exclude_gpgsig[] = {
/* We reencode the message, so the encoding needs to be stripped. */
"encoding",
/* We need to strip signatures as those will become invalid. */
"gpgsig",
"gpgsig-sha256",
NULL,
};
const char *original_message, *original_body, *ptr;
struct commit_extra_header *original_extra_headers = NULL;
struct strbuf commit_message = STRBUF_INIT;
struct object_id rewritten_commit_oid;
char *original_author = NULL;
size_t len;
int ret;
/* We retain authorship of the original commit. */
original_message = repo_logmsg_reencode(repo, commit_with_message, NULL, NULL);
ptr = find_commit_header(original_message, "author", &len);
if (ptr)
original_author = xmemdupz(ptr, len);
find_commit_subject(original_message, &original_body);
if (!message_template)
message_template = original_body;
if (flags & COMMIT_TREE_EDIT_MESSAGE) {
ret = fill_commit_message(repo, old_tree, new_tree,
message_template, action, &commit_message);
if (ret < 0)
goto out;
} else {
strbuf_addstr(&commit_message, message_template);
}
original_extra_headers = read_commit_extra_headers(commit_with_message,
exclude_gpgsig);
ret = commit_tree_extended(commit_message.buf, commit_message.len, new_tree,
parents, &rewritten_commit_oid, original_author,
NULL, NULL, original_extra_headers);
if (ret < 0)
goto out;
*out = lookup_commit_or_die(&rewritten_commit_oid, "rewritten commit");
out:
free_commit_extra_headers(original_extra_headers);
strbuf_release(&commit_message);
free(original_author);
return ret;
}
static int first_parent_tree_oid(struct repository *repo,
struct commit *commit,
struct object_id *out)
{
struct commit *parent = commit->parents ? commit->parents->item : NULL;
if (!parent) {
oidcpy(out, repo->hash_algo->empty_tree);
return 0;
}
if (repo_parse_commit(repo, parent))
return error(_("unable to parse parent commit %s"),
oid_to_hex(&parent->object.oid));
oidcpy(out, &repo_get_commit_tree(repo, parent)->object.oid);
return 0;
}
static int commit_tree_with_edited_message(struct repository *repo,
const char *action,
struct commit *original,
struct commit **out)
{
struct object_id parent_tree_oid;
const struct object_id *tree_oid;
tree_oid = &repo_get_commit_tree(repo, original)->object.oid;
if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0)
return -1;
return commit_tree_ext(repo, action, original, NULL, original->parents,
&parent_tree_oid, tree_oid, out, COMMIT_TREE_EDIT_MESSAGE);
}
enum ref_action {
REF_ACTION_DEFAULT,
REF_ACTION_BRANCHES,
REF_ACTION_HEAD,
};
static int parse_ref_action(const struct option *opt, const char *value, int unset)
{
enum ref_action *action = opt->value;
BUG_ON_OPT_NEG_NOARG(unset, value);
if (!strcmp(value, "branches")) {
*action = REF_ACTION_BRANCHES;
} else if (!strcmp(value, "head")) {
*action = REF_ACTION_HEAD;
} else {
return error(_("%s expects one of 'branches' or 'head'"),
opt->long_name);
}
return 0;
}
static int revwalk_contains_merges(struct repository *repo,
const struct strvec *revwalk_args)
{
struct strvec args = STRVEC_INIT;
struct rev_info revs;
int ret;
strvec_pushv(&args, revwalk_args->v);
strvec_push(&args, "--min-parents=2");
repo_init_revisions(repo, &revs, NULL);
setup_revisions_from_strvec(&args, &revs, NULL);
if (args.nr != 1)
BUG("revisions were set up with invalid argument");
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto out;
}
if (get_revision(&revs)) {
ret = error(_("replaying merge commits is not supported yet!"));
goto out;
}
reset_revision_walk();
ret = 0;
out:
release_revisions(&revs);
strvec_clear(&args);
return ret;
}
static int setup_revwalk(struct repository *repo,
enum ref_action action,
struct commit *original,
struct rev_info *revs)
{
struct strvec args = STRVEC_INIT;
int ret;
repo_init_revisions(repo, revs, NULL);
strvec_push(&args, "ignored");
strvec_push(&args, "--reverse");
strvec_push(&args, "--topo-order");
strvec_push(&args, "--full-history");
/* We only want to see commits that are descendants of the old commit. */
strvec_pushf(&args, "--ancestry-path=%s",
oid_to_hex(&original->object.oid));
/*
* Ancestry path may also show ancestors of the old commit, but we
* don't want to see those, either.
*/
strvec_pushf(&args, "^%s", oid_to_hex(&original->object.oid));
/*
* When we're asked to update HEAD we need to verify that the commit
* that we want to rewrite is actually an ancestor of it and, if so,
* update it. Otherwise we'll update (or print) all descendant
* branches.
*/
if (action == REF_ACTION_HEAD) {
struct commit_list *from_list = NULL;
struct commit *head;
head = lookup_commit_reference_by_name("HEAD");
if (!head) {
ret = error(_("cannot look up HEAD"));
goto out;
}
commit_list_insert(original, &from_list);
ret = repo_is_descendant_of(repo, head, from_list);
commit_list_free(from_list);
if (ret < 0) {
ret = error(_("cannot determine descendance"));
goto out;
} else if (!ret) {
ret = error(_("rewritten commit must be an ancestor "
"of HEAD when using --update-refs=head"));
goto out;
}
strvec_push(&args, "HEAD");
} else {
strvec_push(&args, "--branches");
strvec_push(&args, "HEAD");
}
ret = revwalk_contains_merges(repo, &args);
if (ret < 0)
goto out;
setup_revisions_from_strvec(&args, revs, NULL);
if (args.nr != 1)
BUG("revisions were set up with invalid argument");
ret = 0;
out:
strvec_clear(&args);
return ret;
}
static int handle_ref_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
const char *reflog_msg,
struct strbuf *err)
{
if (!transaction) {
printf("update %s %s %s\n",
refname, oid_to_hex(new_oid), oid_to_hex(old_oid));
return 0;
}
return ref_transaction_update(transaction, refname, new_oid, old_oid,
NULL, NULL, 0, reflog_msg, err);
}
static int compute_pending_ref_updates(struct rev_info *revs,
enum ref_action action,
struct commit *original,
struct commit *rewritten,
enum replay_empty_commit_action empty,
struct replay_result *result)
{
const struct name_decoration *decoration;
struct replay_revisions_options opts = {
.empty = empty,
};
char hex[GIT_MAX_HEXSZ + 1];
bool detached_head;
int head_flags = 0;
int ret;
refs_read_ref_full(get_main_ref_store(revs->repo), "HEAD",
RESOLVE_REF_NO_RECURSE, NULL, &head_flags);
detached_head = !(head_flags & REF_ISSYMREF);
opts.onto = oid_to_hex_r(hex, &rewritten->object.oid);
ret = replay_revisions(revs, &opts, result);
if (ret)
return ret;
if (action != REF_ACTION_BRANCHES && action != REF_ACTION_HEAD)
BUG("unsupported ref action %d", action);
/*
* `replay_revisions()` only updates references that are
* ancestors of `rewritten`, so we need to manually
* handle updating references that point to `original`.
*/
for (decoration = get_name_decoration(&original->object);
decoration;
decoration = decoration->next)
{
if (decoration->type != DECORATION_REF_LOCAL &&
decoration->type != DECORATION_REF_HEAD)
continue;
if (action == REF_ACTION_HEAD &&
decoration->type != DECORATION_REF_HEAD)
continue;
/*
* We only need to update HEAD separately in case it's
* detached. If it's not we'd already update the branch
* it is pointing to.
*/
if (action == REF_ACTION_BRANCHES &&
decoration->type == DECORATION_REF_HEAD &&
!detached_head)
continue;
replay_result_queue_update(result, decoration->name,
&original->object.oid,
&rewritten->object.oid);
}
return 0;
}
static int apply_pending_ref_updates(struct repository *repo,
const struct replay_result *result,
const char *reflog_msg,
int dry_run)
{
struct ref_transaction *transaction = NULL;
struct strbuf err = STRBUF_INIT;
int ret;
if (!dry_run) {
transaction = ref_store_transaction_begin(get_main_ref_store(repo),
0, &err);
if (!transaction) {
ret = error(_("failed to begin ref transaction: %s"), err.buf);
goto out;
}
}
for (size_t i = 0; i < result->updates_nr; i++) {
ret = handle_ref_update(transaction,
result->updates[i].refname,
&result->updates[i].new_oid,
&result->updates[i].old_oid,
reflog_msg, &err);
if (ret) {
ret = error(_("failed to update ref '%s': %s"),
result->updates[i].refname, err.buf);
goto out;
}
}
if (transaction && ref_transaction_commit(transaction, &err)) {
ret = error(_("failed to commit ref transaction: %s"), err.buf);
goto out;
}
ret = 0;
out:
ref_transaction_free(transaction);
strbuf_release(&err);
return ret;
}
static int handle_reference_updates(struct rev_info *revs,
enum ref_action action,
struct commit *original,
struct commit *rewritten,
const char *reflog_msg,
int dry_run,
enum replay_empty_commit_action empty)
{
struct replay_result result = { 0 };
int ret;
ret = compute_pending_ref_updates(revs, action, original, rewritten,
empty, &result);
if (ret)
goto out;
ret = apply_pending_ref_updates(revs->repo, &result, reflog_msg, dry_run);
out:
replay_result_release(&result);
return ret;
}
static int commit_became_empty(struct repository *repo,
struct commit *original,
struct tree *result)
{
struct object_id parent_tree_oid;
if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0)
return -1;
return oideq(&result->object.oid, &parent_tree_oid);
}
static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
{
enum replay_empty_commit_action *value = opt->value;
BUG_ON_OPT_NEG(unset);
if (!strcmp(arg, "drop"))
*value = REPLAY_EMPTY_COMMIT_DROP;
else if (!strcmp(arg, "keep"))
*value = REPLAY_EMPTY_COMMIT_KEEP;
else if (!strcmp(arg, "abort"))
*value = REPLAY_EMPTY_COMMIT_ABORT;
else
die(_("unrecognized '--empty=' action '%s'; "
"valid values are \"drop\", \"keep\", and \"abort\"."), arg);
return 0;
}
static int cmd_history_fixup(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
const char * const usage[] = {
GIT_HISTORY_FIXUP_USAGE,
NULL,
};
enum replay_empty_commit_action empty = REPLAY_EMPTY_COMMIT_DROP;
enum ref_action action = REF_ACTION_DEFAULT;
enum commit_tree_flags flags = 0;
int dry_run = 0;
struct option options[] = {
OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
N_("control which refs should be updated"),
PARSE_OPT_NONEG, parse_ref_action),
OPT_BOOL('n', "dry-run", &dry_run,
N_("perform a dry-run without updating any refs")),
OPT_BIT(0, "reedit-message", &flags,
N_("open an editor to modify the commit message"),
COMMIT_TREE_EDIT_MESSAGE),
OPT_CALLBACK_F(0, "empty", &empty, "(drop|keep|abort)",
N_("how to handle commits that become empty"),
PARSE_OPT_NONEG, parse_opt_empty),
OPT_END(),
};
struct merge_result merge_result = { 0 };
struct merge_options merge_opts = { 0 };
struct strbuf reflog_msg = STRBUF_INIT;
struct commit *head_commit, *original, *rewritten;
struct tree *head_tree, *original_tree, *index_tree;
struct rev_info revs = { 0 };
bool skip_commit = false;
int ret;
argc = parse_options(argc, argv, prefix, options, usage, 0);
if (argc != 1) {
ret = error(_("command expects a single revision"));
goto out;
}
repo_config(repo, git_default_config, NULL);
if (action == REF_ACTION_DEFAULT)
action = REF_ACTION_BRANCHES;
if (is_bare_repository(repo)) {
ret = error(_("cannot run fixup in a bare repository"));
goto out;
}
/* Resolve the original commit, which is the one we want to fix up. */
original = lookup_commit_reference_by_name(argv[0]);
if (!original) {
ret = error(_("commit cannot be found: %s"), argv[0]);
goto out;
}
/*
* Resolve HEAD so we can use its tree as the merge base: the staged
* changes are expressed as a diff from HEAD's tree to the index tree.
*/
head_commit = lookup_commit_reference_by_name("HEAD");
if (!head_commit) {
ret = error(_("cannot look up HEAD"));
goto out;
}
head_tree = repo_get_commit_tree(repo, head_commit);
if (!head_tree) {
ret = error(_("cannot get tree for HEAD"));
goto out;
}
if (repo_read_index(repo) < 0) {
ret = error(_("unable to read index"));
goto out;
}
if (!repo_index_has_changes(repo, head_tree, NULL)) {
ret = error(_("nothing to fixup: no staged changes"));
goto out;
}
/*
* Write the index as a tree object. This is the "theirs" side of the
* three-way merge: it is HEAD's tree with the staged changes applied.
*/
index_tree = write_in_core_index_as_tree(repo, repo->index);
if (!index_tree) {
ret = error(_("unable to write index as a tree"));
goto out;
}
original_tree = repo_get_commit_tree(repo, original);
if (!original_tree) {
ret = error(_("cannot get tree for commit %s"), argv[0]);
goto out;
}
/*
* Perform the three-way merge to reapply changes in the index onto the
* target commit. This is using basically the same logic as a
* cherry-pick, where the base commit is our HEAD, ours is the original
* tree and theirs is the index tree.
*/
init_basic_merge_options(&merge_opts, repo);
merge_opts.ancestor = "HEAD";
merge_opts.branch1 = argv[0];
merge_opts.branch2 = "staged";
merge_incore_nonrecursive(&merge_opts, head_tree,
original_tree, index_tree, &merge_result);
if (merge_result.clean < 0) {
ret = error(_("merge failed while applying fixup"));
goto out;
}
if (!merge_result.clean) {
ret = error(_("fixup would produce conflicts; aborting"));
goto out;
}
ret = commit_became_empty(repo, original, merge_result.tree);
if (ret < 0)
goto out;
if (ret > 0) {
switch (empty) {
case REPLAY_EMPTY_COMMIT_DROP:
/*
* Drop the target commit by replaying its descendants
* directly onto its parent.
*/
rewritten = original->parents ? original->parents->item : NULL;
/*
* TODO: we don't yet have the ability to drop root
* commits, but there's ultimately no good reason for
* this restriction to exist other than a technical
* limitation.
*/
if (!rewritten) {
ret = error(_("cannot drop root commit %s: "
"it has no parent to replay onto"),
argv[0]);
goto out;
}
skip_commit = true;
break;
case REPLAY_EMPTY_COMMIT_KEEP:
/* Proceed and record the empty commit. */
break;
case REPLAY_EMPTY_COMMIT_ABORT:
ret = error(_("fixup makes commit %s empty"), argv[0]);
goto out;
}
}
ret = setup_revwalk(repo, action, original, &revs);
if (ret)
goto out;
if (!skip_commit) {
ret = commit_tree_ext(repo, "fixup", original, NULL, original->parents,
&original_tree->object.oid, &merge_result.tree->object.oid,
&rewritten, flags);
if (ret < 0) {
ret = error(_("failed writing fixed-up commit"));
goto out;
}
}
strbuf_addf(&reflog_msg, "fixup: updating %s", argv[0]);
ret = handle_reference_updates(&revs, action, original, rewritten,
reflog_msg.buf, dry_run, empty);
if (ret < 0) {
ret = error(_("failed replaying descendants"));
goto out;
}
ret = 0;
out:
merge_finalize(&merge_opts, &merge_result);
strbuf_release(&reflog_msg);
release_revisions(&revs);
return ret;
}
static int cmd_history_reword(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
const char * const usage[] = {
GIT_HISTORY_REWORD_USAGE,
NULL,
};
enum ref_action action = REF_ACTION_DEFAULT;
int dry_run = 0;
struct option options[] = {
OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
N_("control which refs should be updated"),
PARSE_OPT_NONEG, parse_ref_action),
OPT_BOOL('n', "dry-run", &dry_run,
N_("perform a dry-run without updating any refs")),
OPT_END(),
};
struct strbuf reflog_msg = STRBUF_INIT;
struct commit *original, *rewritten;
struct rev_info revs = { 0 };
int ret;
argc = parse_options(argc, argv, prefix, options, usage, 0);
if (argc != 1) {
ret = error(_("command expects a single revision"));
goto out;
}
repo_config(repo, git_default_config, NULL);
if (action == REF_ACTION_DEFAULT)
action = REF_ACTION_BRANCHES;
original = lookup_commit_reference_by_name(argv[0]);
if (!original) {
ret = error(_("commit cannot be found: %s"), argv[0]);
goto out;
}
ret = setup_revwalk(repo, action, original, &revs);
if (ret)
goto out;
ret = commit_tree_with_edited_message(repo, "reworded", original, &rewritten);
if (ret < 0) {
ret = error(_("failed writing reworded commit"));
goto out;
}
strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]);
ret = handle_reference_updates(&revs, action, original, rewritten,
reflog_msg.buf, dry_run, REPLAY_EMPTY_COMMIT_ABORT);
if (ret < 0) {
ret = error(_("failed replaying descendants"));
goto out;
}
ret = 0;
out:
strbuf_release(&reflog_msg);
release_revisions(&revs);
return ret;
}
static int write_ondisk_index(struct repository *repo,
struct object_id *oid,
const char *path)
{
struct unpack_trees_options opts = { 0 };
struct lock_file lock = LOCK_INIT;
struct tree_desc tree_desc;
struct index_state index;
struct tree *tree;
int ret;
index_state_init(&index, repo);
opts.head_idx = -1;
opts.src_index = &index;
opts.dst_index = &index;
tree = repo_parse_tree_indirect(repo, oid);
init_tree_desc(&tree_desc, &tree->object.oid, tree->buffer, tree->size);
if (unpack_trees(1, &tree_desc, &opts)) {
ret = error(_("unable to populate index with tree"));
goto out;
}
prime_cache_tree(repo, &index, tree);
if (hold_lock_file_for_update(&lock, path, 0) < 0) {
ret = error_errno(_("unable to acquire index lock"));
goto out;
}
if (write_locked_index(&index, &lock, COMMIT_LOCK)) {
ret = error(_("unable to write new index file"));
goto out;
}
ret = 0;
out:
rollback_lock_file(&lock);
release_index(&index);
return ret;
}
static int split_commit(struct repository *repo,
struct commit *original,
struct pathspec *pathspec,
struct commit **out)
{
struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT;
struct strbuf index_file = STRBUF_INIT;
struct index_state index = INDEX_STATE_INIT(repo);
const struct object_id *original_commit_tree_oid;
const struct object_id *old_tree_oid, *new_tree_oid;
struct object_id parent_tree_oid;
char original_commit_oid[GIT_MAX_HEXSZ + 1];
struct commit *first_commit, *second_commit;
struct commit_list *parents = NULL;
struct tree *split_tree;
int ret;
if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0) {
ret = -1;
goto out;
}
original_commit_tree_oid = get_commit_tree_oid(original);
/*
* Construct the first commit. This is done by taking the original
* commit parent's tree and selectively patching changes from the diff
* between that parent and its child.
*/
repo_git_path_replace(repo, &index_file, "%s", "history-split.index");
ret = write_ondisk_index(repo, &parent_tree_oid, index_file.buf);
if (ret < 0)
goto out;
ret = read_index_from(&index, index_file.buf, repo->gitdir);
if (ret < 0) {
ret = error(_("failed reading temporary index"));
goto out;
}
oid_to_hex_r(original_commit_oid, &original->object.oid);
ret = run_add_p_index(repo, &index, index_file.buf, &interactive_opts,
original_commit_oid, pathspec, ADD_P_DISALLOW_EDIT);
if (ret < 0)
goto out;
split_tree = write_in_core_index_as_tree(repo, &index);
if (!split_tree) {
ret = error(_("failed split tree"));
goto out;
}
unlink(index_file.buf);
strbuf_release(&index_file);
/*
* We disallow the cases where either the split-out commit or the
* original commit would become empty. Consequently, if we see that the
* new tree ID matches either of those trees we abort.
*/
if (oideq(&split_tree->object.oid, &parent_tree_oid)) {
ret = error(_("split commit is empty"));
goto out;
} else if (oideq(&split_tree->object.oid, original_commit_tree_oid)) {
ret = error(_("split commit tree matches original commit"));
goto out;
}
/*
* The first commit is constructed from the split-out tree. The base
* that shall be diffed against is the parent of the original commit.
*/
ret = commit_tree_ext(repo, "split-out", original, NULL, original->parents, &parent_tree_oid,
&split_tree->object.oid, &first_commit, COMMIT_TREE_EDIT_MESSAGE);
if (ret < 0) {
ret = error(_("failed writing first commit"));
goto out;
}
/*
* The second commit is constructed from the original tree. The base to
* diff against and the parent in this case is the first split-out
* commit.
*/
commit_list_append(first_commit, &parents);
old_tree_oid = &repo_get_commit_tree(repo, first_commit)->object.oid;
new_tree_oid = &repo_get_commit_tree(repo, original)->object.oid;
ret = commit_tree_ext(repo, "split-out", original, NULL, parents, old_tree_oid,
new_tree_oid, &second_commit, COMMIT_TREE_EDIT_MESSAGE);
if (ret < 0) {
ret = error(_("failed writing second commit"));
goto out;
}
*out = second_commit;
ret = 0;
out:
if (index_file.len)
unlink(index_file.buf);
strbuf_release(&index_file);
commit_list_free(parents);
release_index(&index);
return ret;
}
static int cmd_history_split(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
const char * const usage[] = {
GIT_HISTORY_SPLIT_USAGE,
NULL,
};
enum ref_action action = REF_ACTION_DEFAULT;
int dry_run = 0;
struct option options[] = {
OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
N_("control ref update behavior"),
PARSE_OPT_NONEG, parse_ref_action),
OPT_BOOL('n', "dry-run", &dry_run,
N_("perform a dry-run without updating any refs")),
OPT_END(),
};
struct commit *original, *rewritten = NULL;
struct strbuf reflog_msg = STRBUF_INIT;
struct pathspec pathspec = { 0 };
struct rev_info revs = { 0 };
int ret;
argc = parse_options(argc, argv, prefix, options, usage, 0);
if (argc < 1) {
ret = error(_("command expects a committish"));
goto out;
}
repo_config(repo, git_default_config, NULL);
if (action == REF_ACTION_DEFAULT)
action = REF_ACTION_BRANCHES;
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_FULL |
PATHSPEC_SYMLINK_LEADING_PATH |
PATHSPEC_PREFIX_ORIGIN,
prefix, argv + 1);
original = lookup_commit_reference_by_name(argv[0]);
if (!original) {
ret = error(_("commit cannot be found: %s"), argv[0]);
goto out;
}
ret = setup_revwalk(repo, action, original, &revs);
if (ret < 0)
goto out;
if (original->parents && original->parents->next) {
ret = error(_("cannot split up merge commit"));
goto out;
}
ret = split_commit(repo, original, &pathspec, &rewritten);
if (ret < 0)
goto out;
strbuf_addf(&reflog_msg, "split: updating %s", argv[0]);
ret = handle_reference_updates(&revs, action, original, rewritten,
reflog_msg.buf, dry_run, REPLAY_EMPTY_COMMIT_ABORT);
if (ret < 0) {
ret = error(_("failed replaying descendants"));
goto out;
}
ret = 0;
out:
strbuf_release(&reflog_msg);
clear_pathspec(&pathspec);
release_revisions(&revs);
return ret;
}
/*
* Resolve a "<base>..<tip>" revision range into the base commit just outside
* the range (which becomes the parent of the squashed commit), the oldest
* commit contained in the range (whose message the squash reuses), and the
* range tip (whose tree becomes the result). A merge inside the range is fine,
* but the range must have a single base and must not reach a root commit.
*/
static int resolve_squash_range(struct repository *repo,
const char **argv,
struct commit **base_out,
struct commit **oldest_out,
struct commit **tip_out,
struct oidset *interior_out)
{
struct rev_info revs;
struct commit *commit, *base = NULL, *oldest = NULL, *tip = NULL;
struct commit_list *boundaries = NULL, *b;
struct strvec args = STRVEC_INIT;
size_t i;
int ret;
repo_init_revisions(repo, &revs, NULL);
revs.reverse = 1;
revs.topo_order = 1;
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
revs.simplify_history = 0;
revs.boundary = 1;
strvec_push(&args, "ignored");
strvec_push(&args, "--ancestry-path");
strvec_pushv(&args, argv);
setup_revisions_from_strvec(&args, &revs, NULL);
if (args.nr != 1) {
ret = error(_("unrecognized argument: %s"), args.v[1]);
goto out;
}
if (revs.reverse != 1 || revs.topo_order != 1 ||
revs.sort_order != REV_SORT_IN_GRAPH_ORDER ||
revs.simplify_history != 0 || revs.boundary != 1) {
warning(_("ignoring rev-list options that would change how the "
"range is walked"));
revs.reverse = 1;
revs.topo_order = 1;
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
revs.simplify_history = 0;
revs.boundary = 1;
}
/*
* A squash needs a base to reparent onto, so the range has to exclude
* something, as in "<base>..<tip>". A revision range with no such
* bottom commit cannot be squashed.
*/
for (i = 0; i < revs.cmdline.nr; i++)
if (revs.cmdline.rev[i].flags & UNINTERESTING)
break;
if (i == revs.cmdline.nr) {
ret = error(_("not a '<base>..<tip>' revision range"));
goto out;
}
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto out;
}
/*
* Set boundary commits aside for the base check below, and put every
* in-range commit but the tip into the interior set. A ref pointing
* at an interior commit would dangle once the range is folded away.
*/
while ((commit = get_revision(&revs))) {
if (commit->object.flags & BOUNDARY) {
commit_list_insert(commit, &boundaries);
continue;
}
if (!oldest)
oldest = commit;
if (tip)
oidset_insert(interior_out, &tip->object.oid);
tip = commit;
}
if (!oldest) {
ret = error(_("the revision range is empty"));
goto out;
} else if (oldest == tip) {
ret = error(_("the revision range holds a single commit; "
"nothing to squash"));
goto out;
} else if (!oldest->parents) {
BUG("an in-range commit must have a parent");
}
base = oldest->parents->item;
/*
* A boundary other than the base is an in-range commit reaching a
* commit outside the range, so the range has more than one base.
*/
for (b = boundaries; b; b = b->next) {
if (b->item != base) {
ret = error(_("the revision range has more than one base; "
"cannot squash"));
goto out;
}
}
*base_out = base;
*oldest_out = oldest;
*tip_out = tip;
ret = 0;
out:
commit_list_free(boundaries);
reset_revision_walk();
release_revisions(&revs);
strvec_clear(&args);
return ret;
}
static const char *autosquash_target(const char *subject)
{
const char *rest;
while (skip_prefix(subject, "fixup! ", &rest) ||
skip_prefix(subject, "squash! ", &rest) ||
skip_prefix(subject, "amend! ", &rest))
subject = rest;
return subject;
}
static int reject_dangling_fixups(struct repository *repo,
struct commit *base,
struct commit *tip,
struct commit *oldest,
struct commit **msg_source,
struct commit **amend_source)
{
struct todo_list todo = TODO_LIST_INIT;
struct replay_opts opts = REPLAY_OPTS_INIT;
struct rev_info revs;
struct commit *commit, *last_amend = NULL;
struct strvec args = STRVEC_INIT;
char *dangling_subject = NULL, *dangling_target = NULL;
bool mixed_target = false, all_fixups_one_target;
bool past_oldest_group = false;
int i, ret, nr_dangling = 0;
*msg_source = oldest;
*amend_source = NULL;
repo_init_revisions(repo, &revs, NULL);
strvec_push(&args, "ignored");
strvec_push(&args, "--reverse");
strvec_push(&args, "--topo-order");
strvec_pushf(&args, "%s..%s", oid_to_hex(&base->object.oid),
oid_to_hex(&tip->object.oid));
setup_revisions_from_strvec(&args, &revs, NULL);
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto out;
}
while ((commit = get_revision(&revs)))
strbuf_addf(&todo.buf, "pick %s\n",
oid_to_hex(&commit->object.oid));
if (todo_list_parse_insn_buffer(repo, &opts, todo.buf.buf, &todo) < 0 ||
todo_list_rearrange_squash(&todo) < 0) {
ret = error(_("could not check the range for fixups"));
goto out;
}
for (i = 0; i < todo.nr; i++) {
const char *message, *subject_start, *target;
char *subject;
size_t sublen;
message = repo_logmsg_reencode(repo, todo.items[i].commit,
NULL, NULL);
sublen = find_commit_subject(message, &subject_start);
if (todo.items[i].command != TODO_PICK) {
if (!past_oldest_group &&
starts_with(subject_start, "amend! "))
*amend_source = todo.items[i].commit;
repo_unuse_commit_buffer(repo, todo.items[i].commit, message);
continue;
}
if (i)
past_oldest_group = true;
subject = xmemdupz(subject_start, sublen);
target = autosquash_target(subject);
if (target != subject) {
nr_dangling++;
if (!dangling_target) {
dangling_target = xstrdup(target);
dangling_subject = xstrdup(subject);
} else if (strcmp(dangling_target, target)) {
mixed_target = true;
}
if (starts_with(subject, "amend! "))
last_amend = todo.items[i].commit;
}
free(subject);
repo_unuse_commit_buffer(repo, todo.items[i].commit, message);
}
all_fixups_one_target = nr_dangling == todo.nr && !mixed_target;
if (nr_dangling && !all_fixups_one_target) {
ret = error(_("cannot squash '%s': its target is not in the "
"range"), dangling_subject);
} else {
if (last_amend)
*msg_source = last_amend;
ret = 0;
}
out:
free(dangling_subject);
free(dangling_target);
todo_list_release(&todo);
replay_opts_release(&opts);
reset_revision_walk();
release_revisions(&revs);
strvec_clear(&args);
return ret;
}
struct interior_ref_cb {
const struct oidset *interior;
const char *name;
};
static int find_interior_ref(const struct reference *ref, void *cb_data)
{
struct interior_ref_cb *data = cb_data;
if (oidset_contains(data->interior, ref->oid)) {
data->name = xstrdup(ref->name);
return 1;
}
return 0;
}
static int cmd_history_squash(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
const char * const usage[] = {
GIT_HISTORY_SQUASH_USAGE,
NULL,
};
enum ref_action action = REF_ACTION_DEFAULT;
enum commit_tree_flags flags = 0;
int dry_run = 0;
struct option options[] = {
OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
N_("control which refs should be updated"),
PARSE_OPT_NONEG, parse_ref_action),
OPT_BOOL('n', "dry-run", &dry_run,
N_("perform a dry-run without updating any refs")),
OPT_BIT(0, "reedit-message", &flags,
N_("open an editor to modify the commit message"),
COMMIT_TREE_EDIT_MESSAGE),
OPT_END(),
};
struct strbuf reflog_msg = STRBUF_INIT;
struct strbuf message = STRBUF_INIT;
struct oidset interior = OIDSET_INIT;
struct commit *base, *oldest, *tip, *rewritten, *msg_source,
*amend_source;
const struct object_id *base_tree_oid, *tip_tree_oid;
const char *message_template = NULL;
struct commit_list *parents = NULL;
struct rev_info revs = { 0 };
int ret;
argc = parse_options(argc, argv, prefix, options, usage,
PARSE_OPT_KEEP_UNKNOWN_OPT);
if (!argc) {
ret = error(_("command expects a revision range"));
goto out;
}
repo_config(repo, git_default_config, NULL);
if (action == REF_ACTION_DEFAULT)
action = REF_ACTION_BRANCHES;
ret = resolve_squash_range(repo, argv, &base, &oldest, &tip,
&interior);
if (ret < 0)
goto out;
ret = reject_dangling_fixups(repo, base, tip, oldest, &msg_source,
&amend_source);
if (ret < 0)
goto out;
if (amend_source) {
const char *amend_message, *body;
amend_message = repo_logmsg_reencode(repo, amend_source,
NULL, NULL);
find_commit_subject(amend_message, &body);
body = skip_blank_lines(body + commit_subject_length(body));
strbuf_addstr(&message, body);
message_template = message.buf;
repo_unuse_commit_buffer(repo, amend_source, amend_message);
}
if (action == REF_ACTION_BRANCHES) {
struct interior_ref_cb cb = { .interior = &interior };
refs_for_each_ref(get_main_ref_store(repo),
find_interior_ref, &cb);
if (cb.name) {
ret = error(_("'%s' points into the squashed range"),
cb.name);
advise_if_enabled(ADVICE_HISTORY_UPDATE_REFS,
_("Use --update-refs=head to rewrite only "
"the current branch and leave such refs "
"untouched."));
free((char *)cb.name);
goto out;
}
}
ret = setup_revwalk(repo, action, tip, &revs);
if (ret < 0)
goto out;
base_tree_oid = &repo_get_commit_tree(repo, base)->object.oid;
tip_tree_oid = &repo_get_commit_tree(repo, tip)->object.oid;
commit_list_append(base, &parents);
ret = commit_tree_ext(repo, "squash", msg_source, message_template,
parents,
base_tree_oid, tip_tree_oid, &rewritten, flags);
if (ret < 0) {
ret = error(_("failed writing squashed commit"));
goto out;
}
strbuf_addstr(&reflog_msg, "squash: updating ");
strbuf_join_argv(&reflog_msg, argc, argv, ' ');
ret = handle_reference_updates(&revs, action, tip, rewritten,
reflog_msg.buf, dry_run,
REPLAY_EMPTY_COMMIT_ABORT);
if (ret < 0) {
ret = error(_("failed replaying descendants"));
goto out;
}
ret = 0;
out:
strbuf_release(&reflog_msg);
strbuf_release(&message);
oidset_clear(&interior);
commit_list_free(parents);
release_revisions(&revs);
return ret;
}
static int update_worktree(struct repository *repo,
const struct commit *old_head,
const struct commit *new_head,
bool dry_run)
{
struct reset_working_tree_options opts = {
.oid_from = &old_head->object.oid,
.oid = &new_head->object.oid,
};
if (dry_run)
opts.flags |= RESET_WORKING_TREE_DRY_RUN;
return reset_working_tree(repo, &opts);
}
static int find_head_tree_change(struct repository *repo,
const struct replay_result *result,
struct commit **old_head,
struct commit **new_head,
bool *changed)
{
const struct replay_ref_update *head_update = NULL;
struct commit *old_head_commit, *new_head_commit;
struct tree *old_head_tree, *new_head_tree;
const char *head_target;
int head_flags;
*changed = false;
head_target = refs_resolve_ref_unsafe(get_main_ref_store(repo), "HEAD",
RESOLVE_REF_NO_RECURSE | RESOLVE_REF_READING,
NULL, &head_flags);
if (!head_target)
return error(_("cannot look up HEAD"));
for (size_t i = 0; i < result->updates_nr; i++) {
if (!strcmp(result->updates[i].refname, head_target)) {
head_update = &result->updates[i];
break;
}
}
if (!head_update)
return 0;
old_head_commit = lookup_commit_reference(repo, &head_update->old_oid);
new_head_commit = lookup_commit_reference(repo, &head_update->new_oid);
if (!old_head_commit || !new_head_commit)
return error(_("cannot resolve HEAD commit"));
old_head_tree = repo_get_commit_tree(repo, old_head_commit);
new_head_tree = repo_get_commit_tree(repo, new_head_commit);
if (!old_head_tree || !new_head_tree)
return error(_("cannot resolve tree for HEAD"));
if (oideq(&old_head_tree->object.oid, &new_head_tree->object.oid))
return 0;
*old_head = old_head_commit;
*new_head = new_head_commit;
*changed = true;
return 0;
}
static int cmd_history_drop(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
const char * const usage[] = {
GIT_HISTORY_DROP_USAGE,
NULL,
};
enum replay_empty_commit_action empty = REPLAY_EMPTY_COMMIT_DROP;
enum ref_action action = REF_ACTION_DEFAULT;
int dry_run = 0;
struct option options[] = {
OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
N_("control which refs should be updated"),
PARSE_OPT_NONEG, parse_ref_action),
OPT_BOOL('n', "dry-run", &dry_run,
N_("perform a dry-run without updating any refs")),
OPT_CALLBACK_F(0, "empty", &empty, "(drop|keep|abort)",
N_("how to handle descendants that become empty"),
PARSE_OPT_NONEG, parse_opt_empty),
OPT_END(),
};
struct strbuf reflog_msg = STRBUF_INIT;
struct commit *original, *rewritten;
struct rev_info revs = { 0 };
struct replay_result result = { 0 };
struct commit *old_head, *new_head;
bool head_moves = false;
int ret;
argc = parse_options(argc, argv, prefix, options, usage, 0);
if (argc != 1) {
ret = error(_("command expects a single revision"));
goto out;
}
repo_config(repo, git_default_config, NULL);
if (action == REF_ACTION_DEFAULT)
action = REF_ACTION_BRANCHES;
original = lookup_commit_reference_by_name(argv[0]);
if (!original) {
ret = error(_("commit cannot be found: %s"), argv[0]);
goto out;
}
if (!original->parents) {
ret = error(_("cannot drop root commit %s: "
"it has no parent to replay onto"),
argv[0]);
goto out;
} else if (original->parents->next) {
ret = error(_("cannot drop merge commit: %s"), argv[0]);
goto out;
}
ret = setup_revwalk(repo, action, original, &revs);
if (ret)
goto out;
rewritten = original->parents->item;
ret = compute_pending_ref_updates(&revs, action, original, rewritten,
empty, &result);
if (ret) {
ret = error(_("failed replaying descendants"));
goto out;
}
/*
* If HEAD will move as a result of the rewrite then we'll have to
* merge in the changes into the worktree and index. This merge can of
* course conflict, which will cause the whole operation to abort.
*
* If we had already updated the refs at that point then we'd have an
* inconsistent repository state. So we first perform a dry-run merge
* here before updating refs.
*/
if (!is_bare_repository(repo)) {
ret = find_head_tree_change(repo, &result, &old_head,
&new_head, &head_moves);
if (ret < 0)
goto out;
if (head_moves && update_worktree(repo, old_head, new_head, true) < 0) {
ret = error(_("dropping this commit would "
"overwrite local changes; aborting"));
goto out;
}
}
strbuf_addf(&reflog_msg, "drop: dropping %s", argv[0]);
ret = apply_pending_ref_updates(repo, &result, reflog_msg.buf, dry_run);
if (ret < 0) {
ret = error(_("failed to update references"));
goto out;
}
if (!dry_run && head_moves && update_worktree(repo, old_head, new_head, false) < 0) {
ret = error(_("could not update working tree to new commit %s"),
oid_to_hex(&new_head->object.oid));
goto out;
}
ret = 0;
out:
replay_result_release(&result);
strbuf_release(&reflog_msg);
release_revisions(&revs);
return ret;
}
int cmd_history(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
const char * const usage[] = {
GIT_HISTORY_DROP_USAGE,
GIT_HISTORY_FIXUP_USAGE,
GIT_HISTORY_REWORD_USAGE,
GIT_HISTORY_SPLIT_USAGE,
GIT_HISTORY_SQUASH_USAGE,
NULL,
};
parse_opt_subcommand_fn *fn = NULL;
struct option options[] = {
OPT_SUBCOMMAND("drop", &fn, cmd_history_drop),
OPT_SUBCOMMAND("fixup", &fn, cmd_history_fixup),
OPT_SUBCOMMAND("reword", &fn, cmd_history_reword),
OPT_SUBCOMMAND("split", &fn, cmd_history_split),
OPT_SUBCOMMAND("squash", &fn, cmd_history_squash),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options, usage, 0);
return fn(argc, argv, prefix, repo);
}