Merge branch 'dl/merge-autostash'

"git merge" learns the "--autostash" option.

* dl/merge-autostash: (22 commits)
  pull: pass --autostash to merge
  t5520: make test_pull_autostash() accept expect_parent_num
  merge: teach --autostash option
  sequencer: implement apply_autostash_oid()
  sequencer: implement save_autostash()
  sequencer: unlink autostash in apply_autostash()
  sequencer: extract perform_autostash() from rebase
  rebase: generify create_autostash()
  rebase: extract create_autostash()
  reset: extract reset_head() from rebase
  rebase: generify reset_head()
  rebase: use apply_autostash() from sequencer.c
  sequencer: rename stash_sha1 to stash_oid
  sequencer: make apply_autostash() accept a path
  rebase: use read_oneliner()
  sequencer: make read_oneliner() extern
  sequencer: configurably warn on non-existent files
  sequencer: make read_oneliner() accept flags
  sequencer: make file exists check more efficient
  sequencer: stop leaking buf
  ...
This commit is contained in:
Junio C Hamano
2020-04-29 16:15:27 -07:00
20 changed files with 666 additions and 391 deletions

View File

@ -32,6 +32,7 @@
#include "alias.h"
#include "commit-reach.h"
#include "rebase-interactive.h"
#include "reset.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@ -419,25 +420,15 @@ static int write_message(const void *buf, size_t len, const char *filename,
return 0;
}
/*
* Reads a file that was presumably written by a shell script, i.e. with an
* end-of-line marker that needs to be stripped.
*
* Note that only the last end-of-line marker is stripped, consistent with the
* behavior of "$(cat path)" in a shell script.
*
* Returns 1 if the file was read, 0 if it could not be read or does not exist.
*/
static int read_oneliner(struct strbuf *buf,
const char *path, int skip_if_empty)
int read_oneliner(struct strbuf *buf,
const char *path, unsigned flags)
{
int orig_len = buf->len;
if (!file_exists(path))
return 0;
if (strbuf_read_file(buf, path, 0) < 0) {
warning_errno(_("could not read '%s'"), path);
if ((flags & READ_ONELINER_WARN_MISSING) ||
(errno != ENOENT && errno != ENOTDIR))
warning_errno(_("could not read '%s'"), path);
return 0;
}
@ -447,7 +438,7 @@ static int read_oneliner(struct strbuf *buf,
buf->buf[buf->len] = '\0';
}
if (skip_if_empty && buf->len == orig_len)
if ((flags & READ_ONELINER_SKIP_IF_EMPTY) && buf->len == orig_len)
return 0;
return 1;
@ -2504,8 +2495,10 @@ static int read_populate_opts(struct replay_opts *opts)
{
if (is_rebase_i(opts)) {
struct strbuf buf = STRBUF_INIT;
int ret = 0;
if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
if (read_oneliner(&buf, rebase_path_gpg_sign_opt(),
READ_ONELINER_SKIP_IF_EMPTY)) {
if (!starts_with(buf.buf, "-S"))
strbuf_reset(&buf);
else {
@ -2515,7 +2508,8 @@ static int read_populate_opts(struct replay_opts *opts)
strbuf_reset(&buf);
}
if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) {
if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(),
READ_ONELINER_SKIP_IF_EMPTY)) {
if (!strcmp(buf.buf, "--rerere-autoupdate"))
opts->allow_rerere_auto = RERERE_AUTOUPDATE;
else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
@ -2544,10 +2538,11 @@ static int read_populate_opts(struct replay_opts *opts)
opts->keep_redundant_commits = 1;
read_strategy_opts(opts, &buf);
strbuf_release(&buf);
strbuf_reset(&buf);
if (read_oneliner(&opts->current_fixups,
rebase_path_current_fixups(), 1)) {
rebase_path_current_fixups(),
READ_ONELINER_SKIP_IF_EMPTY)) {
const char *p = opts->current_fixups.buf;
opts->current_fixup_count = 1;
while ((p = strchr(p, '\n'))) {
@ -2557,12 +2552,16 @@ static int read_populate_opts(struct replay_opts *opts)
}
if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
return error(_("unusable squash-onto"));
if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) {
ret = error(_("unusable squash-onto"));
goto done_rebase_i;
}
opts->have_squash_onto = 1;
}
return 0;
done_rebase_i:
strbuf_release(&buf);
return ret;
}
if (!file_exists(git_path_opts_file()))
@ -3677,25 +3676,71 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
return -1;
}
static int apply_autostash(struct replay_opts *opts)
void create_autostash(struct repository *r, const char *path,
const char *default_reflog_action)
{
struct strbuf buf = STRBUF_INIT;
struct lock_file lock_file = LOCK_INIT;
int fd;
fd = repo_hold_locked_index(r, &lock_file, 0);
refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
if (0 <= fd)
repo_update_index_if_able(r, &lock_file);
rollback_lock_file(&lock_file);
if (has_unstaged_changes(r, 1) ||
has_uncommitted_changes(r, 1)) {
struct child_process stash = CHILD_PROCESS_INIT;
struct object_id oid;
argv_array_pushl(&stash.args,
"stash", "create", "autostash", NULL);
stash.git_cmd = 1;
stash.no_stdin = 1;
strbuf_reset(&buf);
if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
die(_("Cannot autostash"));
strbuf_trim_trailing_newline(&buf);
if (get_oid(buf.buf, &oid))
die(_("Unexpected stash response: '%s'"),
buf.buf);
strbuf_reset(&buf);
strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
if (safe_create_leading_directories_const(path))
die(_("Could not create directory for '%s'"),
path);
write_file(path, "%s", oid_to_hex(&oid));
printf(_("Created autostash: %s\n"), buf.buf);
if (reset_head(r, NULL, "reset --hard",
NULL, RESET_HEAD_HARD, NULL, NULL,
default_reflog_action) < 0)
die(_("could not reset --hard"));
if (discard_index(r->index) < 0 ||
repo_read_index(r) < 0)
die(_("could not read index"));
}
strbuf_release(&buf);
}
static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
{
struct strbuf stash_sha1 = STRBUF_INIT;
struct child_process child = CHILD_PROCESS_INIT;
int ret = 0;
if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) {
strbuf_release(&stash_sha1);
return 0;
if (attempt_apply) {
child.git_cmd = 1;
child.no_stdout = 1;
child.no_stderr = 1;
argv_array_push(&child.args, "stash");
argv_array_push(&child.args, "apply");
argv_array_push(&child.args, stash_oid);
ret = run_command(&child);
}
strbuf_trim(&stash_sha1);
child.git_cmd = 1;
child.no_stdout = 1;
child.no_stderr = 1;
argv_array_push(&child.args, "stash");
argv_array_push(&child.args, "apply");
argv_array_push(&child.args, stash_sha1.buf);
if (!run_command(&child))
if (attempt_apply && !ret)
fprintf(stderr, _("Applied autostash.\n"));
else {
struct child_process store = CHILD_PROCESS_INIT;
@ -3706,21 +3751,57 @@ static int apply_autostash(struct replay_opts *opts)
argv_array_push(&store.args, "-m");
argv_array_push(&store.args, "autostash");
argv_array_push(&store.args, "-q");
argv_array_push(&store.args, stash_sha1.buf);
argv_array_push(&store.args, stash_oid);
if (run_command(&store))
ret = error(_("cannot store %s"), stash_sha1.buf);
ret = error(_("cannot store %s"), stash_oid);
else
fprintf(stderr,
_("Applying autostash resulted in conflicts.\n"
_("%s\n"
"Your changes are safe in the stash.\n"
"You can run \"git stash pop\" or"
" \"git stash drop\" at any time.\n"));
" \"git stash drop\" at any time.\n"),
attempt_apply ?
_("Applying autostash resulted in conflicts.") :
_("Autostash exists; creating a new stash entry."));
}
strbuf_release(&stash_sha1);
return ret;
}
static int apply_save_autostash(const char *path, int attempt_apply)
{
struct strbuf stash_oid = STRBUF_INIT;
int ret = 0;
if (!read_oneliner(&stash_oid, path,
READ_ONELINER_SKIP_IF_EMPTY)) {
strbuf_release(&stash_oid);
return 0;
}
strbuf_trim(&stash_oid);
ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);
unlink(path);
strbuf_release(&stash_oid);
return ret;
}
int save_autostash(const char *path)
{
return apply_save_autostash(path, 0);
}
int apply_autostash(const char *path)
{
return apply_save_autostash(path, 1);
}
int apply_autostash_oid(const char *stash_oid)
{
return apply_save_autostash_oid(stash_oid, 1);
}
static const char *reflog_message(struct replay_opts *opts,
const char *sub_action, const char *fmt, ...)
{
@ -3776,7 +3857,7 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts,
return error(_("%s: not a valid OID"), orig_head);
if (run_git_checkout(r, opts, oid_to_hex(onto), action)) {
apply_autostash(opts);
apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return error(_("could not detach HEAD"));
}
@ -4095,7 +4176,7 @@ cleanup_head_ref:
run_command(&hook);
}
}
apply_autostash(opts);
apply_autostash(rebase_path_autostash());
if (!opts->quiet) {
if (!opts->verbose)
@ -4313,7 +4394,8 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
struct strbuf buf = STRBUF_INIT;
struct object_id oid;
if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
if (read_oneliner(&buf, rebase_path_stopped_sha(),
READ_ONELINER_SKIP_IF_EMPTY) &&
!get_oid_committish(buf.buf, &oid))
record_in_rewritten(&oid, peek_command(&todo_list, 0));
strbuf_release(&buf);
@ -5118,7 +5200,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
todo_list_add_exec_commands(todo_list, commands);
if (count_commands(todo_list) == 0) {
apply_autostash(opts);
apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return error(_("nothing to do"));
@ -5129,12 +5211,12 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
if (res == -1)
return -1;
else if (res == -2) {
apply_autostash(opts);
apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return -1;
} else if (res == -3) {
apply_autostash(opts);
apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
todo_list_release(&new_todo);