зеркало из https://github.com/microsoft/git.git
revert: Save data for continuing after conflict resolution
Ever since v1.7.2-rc1~4^2~7 (revert: allow cherry-picking more than one commit, 2010-06-02), a single invocation of "git cherry-pick" or "git revert" can perform picks of several individual commits. To implement features like "--continue" to continue the whole operation, we will need to store some information about the state and the plan at the beginning. Introduce a ".git/sequencer/head" file to store this state, and ".git/sequencer/todo" file to store the plan. The head file contains the SHA-1 of the HEAD before the start of the operation, and the todo file contains an instruction sheet whose format is inspired by the format of the "rebase -i" instruction sheet. As a result, a typical todo file looks like: pick 8537f0e submodule add: test failure when url is not configured pick4d68932
submodule add: allow relative repository path pickf22a17e
submodule add: clean up duplicated code pick59a5775
make copy_ref globally available Since SHA-1 hex is abbreviated using an find_unique_abbrev(), it is unambiguous. This does not guarantee that there will be no ambiguity when more objects are added to the repository. These two files alone are not enough to implement a "--continue" that remembers the command-line options specified; later patches in the series save them too. These new files are unrelated to the existing .git/CHERRY_PICK_HEAD, which will still be useful while committing after a conflict resolution. Inspired-by: Christian Couder <chriscool@tuxfamily.org> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Родитель
9044143ff1
Коммит
04d3d3cfc4
134
builtin/revert.c
134
builtin/revert.c
|
@ -13,6 +13,7 @@
|
|||
#include "rerere.h"
|
||||
#include "merge-recursive.h"
|
||||
#include "refs.h"
|
||||
#include "dir.h"
|
||||
|
||||
/*
|
||||
* This implements the builtins revert and cherry-pick.
|
||||
|
@ -60,6 +61,10 @@ struct replay_opts {
|
|||
|
||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||
|
||||
#define SEQ_DIR "sequencer"
|
||||
#define SEQ_HEAD_FILE "sequencer/head"
|
||||
#define SEQ_TODO_FILE "sequencer/todo"
|
||||
|
||||
static const char *action_name(const struct replay_opts *opts)
|
||||
{
|
||||
return opts->action == REVERT ? "revert" : "cherry-pick";
|
||||
|
@ -587,10 +592,116 @@ static void read_and_refresh_cache(struct replay_opts *opts)
|
|||
rollback_lock_file(&index_lock);
|
||||
}
|
||||
|
||||
static int pick_commits(struct replay_opts *opts)
|
||||
/*
|
||||
* Append a commit to the end of the commit_list.
|
||||
*
|
||||
* next starts by pointing to the variable that holds the head of an
|
||||
* empty commit_list, and is updated to point to the "next" field of
|
||||
* the last item on the list as new commits are appended.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* struct commit_list *list;
|
||||
* struct commit_list **next = &list;
|
||||
*
|
||||
* next = commit_list_append(c1, next);
|
||||
* next = commit_list_append(c2, next);
|
||||
* assert(commit_list_count(list) == 2);
|
||||
* return list;
|
||||
*/
|
||||
struct commit_list **commit_list_append(struct commit *commit,
|
||||
struct commit_list **next)
|
||||
{
|
||||
struct commit_list *new = xmalloc(sizeof(struct commit_list));
|
||||
new->item = commit;
|
||||
*next = new;
|
||||
new->next = NULL;
|
||||
return &new->next;
|
||||
}
|
||||
|
||||
static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
|
||||
struct replay_opts *opts)
|
||||
{
|
||||
struct commit_list *cur = NULL;
|
||||
struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
|
||||
const char *sha1_abbrev = NULL;
|
||||
const char *action_str = opts->action == REVERT ? "revert" : "pick";
|
||||
|
||||
for (cur = todo_list; cur; cur = cur->next) {
|
||||
sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
|
||||
if (get_message(cur->item, &msg))
|
||||
return error(_("Cannot get commit message for %s"), sha1_abbrev);
|
||||
strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void walk_revs_populate_todo(struct commit_list **todo_list,
|
||||
struct replay_opts *opts)
|
||||
{
|
||||
struct rev_info revs;
|
||||
struct commit *commit;
|
||||
struct commit_list **next;
|
||||
|
||||
prepare_revs(&revs, opts);
|
||||
|
||||
next = todo_list;
|
||||
while ((commit = get_revision(&revs)))
|
||||
next = commit_list_append(commit, next);
|
||||
}
|
||||
|
||||
static void create_seq_dir(void)
|
||||
{
|
||||
const char *seq_dir = git_path(SEQ_DIR);
|
||||
|
||||
if (!(file_exists(seq_dir) && is_directory(seq_dir))
|
||||
&& mkdir(seq_dir, 0777) < 0)
|
||||
die_errno(_("Could not create sequencer directory '%s'."), seq_dir);
|
||||
}
|
||||
|
||||
static void save_head(const char *head)
|
||||
{
|
||||
const char *head_file = git_path(SEQ_HEAD_FILE);
|
||||
static struct lock_file head_lock;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int fd;
|
||||
|
||||
fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
|
||||
strbuf_addf(&buf, "%s\n", head);
|
||||
if (write_in_full(fd, buf.buf, buf.len) < 0)
|
||||
die_errno(_("Could not write to %s."), head_file);
|
||||
if (commit_lock_file(&head_lock) < 0)
|
||||
die(_("Error wrapping up %s."), head_file);
|
||||
}
|
||||
|
||||
static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
|
||||
{
|
||||
const char *todo_file = git_path(SEQ_TODO_FILE);
|
||||
static struct lock_file todo_lock;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int fd;
|
||||
|
||||
fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
|
||||
if (format_todo(&buf, todo_list, opts) < 0)
|
||||
die(_("Could not format %s."), todo_file);
|
||||
if (write_in_full(fd, buf.buf, buf.len) < 0) {
|
||||
strbuf_release(&buf);
|
||||
die_errno(_("Could not write to %s."), todo_file);
|
||||
}
|
||||
if (commit_lock_file(&todo_lock) < 0) {
|
||||
strbuf_release(&buf);
|
||||
die(_("Error wrapping up %s."), todo_file);
|
||||
}
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
static int pick_commits(struct replay_opts *opts)
|
||||
{
|
||||
struct commit_list *todo_list = NULL;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
unsigned char sha1[20];
|
||||
struct commit_list *cur;
|
||||
int res;
|
||||
|
||||
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
|
||||
if (opts->allow_ff)
|
||||
|
@ -598,14 +709,29 @@ static int pick_commits(struct replay_opts *opts)
|
|||
opts->record_origin || opts->edit));
|
||||
read_and_refresh_cache(opts);
|
||||
|
||||
prepare_revs(&revs, opts);
|
||||
walk_revs_populate_todo(&todo_list, opts);
|
||||
create_seq_dir();
|
||||
if (get_sha1("HEAD", sha1)) {
|
||||
if (opts->action == REVERT)
|
||||
die(_("Can't revert as initial commit"));
|
||||
die(_("Can't cherry-pick into empty head"));
|
||||
}
|
||||
save_head(sha1_to_hex(sha1));
|
||||
|
||||
while ((commit = get_revision(&revs))) {
|
||||
int res = do_pick_commit(commit, opts);
|
||||
for (cur = todo_list; cur; cur = cur->next) {
|
||||
save_todo(cur, opts);
|
||||
res = do_pick_commit(cur->item, opts);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sequence of picks finished successfully; cleanup by
|
||||
* removing the .git/sequencer directory
|
||||
*/
|
||||
strbuf_addf(&buf, "%s", git_path(SEQ_DIR));
|
||||
remove_dir_recursively(&buf, 0);
|
||||
strbuf_release(&buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Test cherry-pick continuation features
|
||||
|
||||
+ anotherpick: rewrites foo to d
|
||||
+ picked: rewrites foo to c
|
||||
+ unrelatedpick: rewrites unrelated to reallyunrelated
|
||||
+ base: rewrites foo to b
|
||||
+ initial: writes foo as a, unrelated as unrelated
|
||||
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
pristine_detach () {
|
||||
rm -rf .git/sequencer &&
|
||||
git checkout -f "$1^0" &&
|
||||
git read-tree -u --reset HEAD &&
|
||||
git clean -d -f -f -q -x
|
||||
}
|
||||
|
||||
test_expect_success setup '
|
||||
echo unrelated >unrelated &&
|
||||
git add unrelated &&
|
||||
test_commit initial foo a &&
|
||||
test_commit base foo b &&
|
||||
test_commit unrelatedpick unrelated reallyunrelated &&
|
||||
test_commit picked foo c &&
|
||||
test_commit anotherpick foo d &&
|
||||
git config advice.detachedhead false
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick persists data on failure' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..anotherpick &&
|
||||
test_path_is_dir .git/sequencer &&
|
||||
test_path_is_file .git/sequencer/head &&
|
||||
test_path_is_file .git/sequencer/todo
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick cleans up sequencer state upon success' '
|
||||
pristine_detach initial &&
|
||||
git cherry-pick initial..picked &&
|
||||
test_path_is_missing .git/sequencer
|
||||
'
|
||||
|
||||
test_done
|
Загрузка…
Ссылка в новой задаче