Skip to content

Commit 04d3d3c

Browse files
artagnongitster
authored andcommitted
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 pick 4d68932 submodule add: allow relative repository path pick f22a17e submodule add: clean up duplicated code pick 59a5775 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 <[email protected]> Helped-by: Junio C Hamano <[email protected]> Helped-by: Jonathan Nieder <[email protected]> Signed-off-by: Ramkumar Ramachandra <[email protected]> Signed-off-by: Jonathan Nieder <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9044143 commit 04d3d3c

File tree

2 files changed

+178
-4
lines changed

2 files changed

+178
-4
lines changed

builtin/revert.c

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "rerere.h"
1414
#include "merge-recursive.h"
1515
#include "refs.h"
16+
#include "dir.h"
1617

1718
/*
1819
* This implements the builtins revert and cherry-pick.
@@ -60,6 +61,10 @@ struct replay_opts {
6061

6162
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
6263

64+
#define SEQ_DIR "sequencer"
65+
#define SEQ_HEAD_FILE "sequencer/head"
66+
#define SEQ_TODO_FILE "sequencer/todo"
67+
6368
static const char *action_name(const struct replay_opts *opts)
6469
{
6570
return opts->action == REVERT ? "revert" : "cherry-pick";
@@ -587,25 +592,146 @@ static void read_and_refresh_cache(struct replay_opts *opts)
587592
rollback_lock_file(&index_lock);
588593
}
589594

590-
static int pick_commits(struct replay_opts *opts)
595+
/*
596+
* Append a commit to the end of the commit_list.
597+
*
598+
* next starts by pointing to the variable that holds the head of an
599+
* empty commit_list, and is updated to point to the "next" field of
600+
* the last item on the list as new commits are appended.
601+
*
602+
* Usage example:
603+
*
604+
* struct commit_list *list;
605+
* struct commit_list **next = &list;
606+
*
607+
* next = commit_list_append(c1, next);
608+
* next = commit_list_append(c2, next);
609+
* assert(commit_list_count(list) == 2);
610+
* return list;
611+
*/
612+
struct commit_list **commit_list_append(struct commit *commit,
613+
struct commit_list **next)
614+
{
615+
struct commit_list *new = xmalloc(sizeof(struct commit_list));
616+
new->item = commit;
617+
*next = new;
618+
new->next = NULL;
619+
return &new->next;
620+
}
621+
622+
static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
623+
struct replay_opts *opts)
624+
{
625+
struct commit_list *cur = NULL;
626+
struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
627+
const char *sha1_abbrev = NULL;
628+
const char *action_str = opts->action == REVERT ? "revert" : "pick";
629+
630+
for (cur = todo_list; cur; cur = cur->next) {
631+
sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
632+
if (get_message(cur->item, &msg))
633+
return error(_("Cannot get commit message for %s"), sha1_abbrev);
634+
strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject);
635+
}
636+
return 0;
637+
}
638+
639+
static void walk_revs_populate_todo(struct commit_list **todo_list,
640+
struct replay_opts *opts)
591641
{
592642
struct rev_info revs;
593643
struct commit *commit;
644+
struct commit_list **next;
645+
646+
prepare_revs(&revs, opts);
647+
648+
next = todo_list;
649+
while ((commit = get_revision(&revs)))
650+
next = commit_list_append(commit, next);
651+
}
652+
653+
static void create_seq_dir(void)
654+
{
655+
const char *seq_dir = git_path(SEQ_DIR);
656+
657+
if (!(file_exists(seq_dir) && is_directory(seq_dir))
658+
&& mkdir(seq_dir, 0777) < 0)
659+
die_errno(_("Could not create sequencer directory '%s'."), seq_dir);
660+
}
661+
662+
static void save_head(const char *head)
663+
{
664+
const char *head_file = git_path(SEQ_HEAD_FILE);
665+
static struct lock_file head_lock;
666+
struct strbuf buf = STRBUF_INIT;
667+
int fd;
668+
669+
fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
670+
strbuf_addf(&buf, "%s\n", head);
671+
if (write_in_full(fd, buf.buf, buf.len) < 0)
672+
die_errno(_("Could not write to %s."), head_file);
673+
if (commit_lock_file(&head_lock) < 0)
674+
die(_("Error wrapping up %s."), head_file);
675+
}
676+
677+
static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
678+
{
679+
const char *todo_file = git_path(SEQ_TODO_FILE);
680+
static struct lock_file todo_lock;
681+
struct strbuf buf = STRBUF_INIT;
682+
int fd;
683+
684+
fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
685+
if (format_todo(&buf, todo_list, opts) < 0)
686+
die(_("Could not format %s."), todo_file);
687+
if (write_in_full(fd, buf.buf, buf.len) < 0) {
688+
strbuf_release(&buf);
689+
die_errno(_("Could not write to %s."), todo_file);
690+
}
691+
if (commit_lock_file(&todo_lock) < 0) {
692+
strbuf_release(&buf);
693+
die(_("Error wrapping up %s."), todo_file);
694+
}
695+
strbuf_release(&buf);
696+
}
697+
698+
static int pick_commits(struct replay_opts *opts)
699+
{
700+
struct commit_list *todo_list = NULL;
701+
struct strbuf buf = STRBUF_INIT;
702+
unsigned char sha1[20];
703+
struct commit_list *cur;
704+
int res;
594705

595706
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
596707
if (opts->allow_ff)
597708
assert(!(opts->signoff || opts->no_commit ||
598709
opts->record_origin || opts->edit));
599710
read_and_refresh_cache(opts);
600711

601-
prepare_revs(&revs, opts);
712+
walk_revs_populate_todo(&todo_list, opts);
713+
create_seq_dir();
714+
if (get_sha1("HEAD", sha1)) {
715+
if (opts->action == REVERT)
716+
die(_("Can't revert as initial commit"));
717+
die(_("Can't cherry-pick into empty head"));
718+
}
719+
save_head(sha1_to_hex(sha1));
602720

603-
while ((commit = get_revision(&revs))) {
604-
int res = do_pick_commit(commit, opts);
721+
for (cur = todo_list; cur; cur = cur->next) {
722+
save_todo(cur, opts);
723+
res = do_pick_commit(cur->item, opts);
605724
if (res)
606725
return res;
607726
}
608727

728+
/*
729+
* Sequence of picks finished successfully; cleanup by
730+
* removing the .git/sequencer directory
731+
*/
732+
strbuf_addf(&buf, "%s", git_path(SEQ_DIR));
733+
remove_dir_recursively(&buf, 0);
734+
strbuf_release(&buf);
609735
return 0;
610736
}
611737

t/t3510-cherry-pick-sequence.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/bin/sh
2+
3+
test_description='Test cherry-pick continuation features
4+
5+
+ anotherpick: rewrites foo to d
6+
+ picked: rewrites foo to c
7+
+ unrelatedpick: rewrites unrelated to reallyunrelated
8+
+ base: rewrites foo to b
9+
+ initial: writes foo as a, unrelated as unrelated
10+
11+
'
12+
13+
. ./test-lib.sh
14+
15+
pristine_detach () {
16+
rm -rf .git/sequencer &&
17+
git checkout -f "$1^0" &&
18+
git read-tree -u --reset HEAD &&
19+
git clean -d -f -f -q -x
20+
}
21+
22+
test_expect_success setup '
23+
echo unrelated >unrelated &&
24+
git add unrelated &&
25+
test_commit initial foo a &&
26+
test_commit base foo b &&
27+
test_commit unrelatedpick unrelated reallyunrelated &&
28+
test_commit picked foo c &&
29+
test_commit anotherpick foo d &&
30+
git config advice.detachedhead false
31+
32+
'
33+
34+
test_expect_success 'cherry-pick persists data on failure' '
35+
pristine_detach initial &&
36+
test_must_fail git cherry-pick base..anotherpick &&
37+
test_path_is_dir .git/sequencer &&
38+
test_path_is_file .git/sequencer/head &&
39+
test_path_is_file .git/sequencer/todo
40+
'
41+
42+
test_expect_success 'cherry-pick cleans up sequencer state upon success' '
43+
pristine_detach initial &&
44+
git cherry-pick initial..picked &&
45+
test_path_is_missing .git/sequencer
46+
'
47+
48+
test_done

0 commit comments

Comments
 (0)