|
13 | 13 | #include "rerere.h"
|
14 | 14 | #include "merge-recursive.h"
|
15 | 15 | #include "refs.h"
|
| 16 | +#include "dir.h" |
16 | 17 |
|
17 | 18 | /*
|
18 | 19 | * This implements the builtins revert and cherry-pick.
|
@@ -60,6 +61,10 @@ struct replay_opts {
|
60 | 61 |
|
61 | 62 | #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
62 | 63 |
|
| 64 | +#define SEQ_DIR "sequencer" |
| 65 | +#define SEQ_HEAD_FILE "sequencer/head" |
| 66 | +#define SEQ_TODO_FILE "sequencer/todo" |
| 67 | + |
63 | 68 | static const char *action_name(const struct replay_opts *opts)
|
64 | 69 | {
|
65 | 70 | return opts->action == REVERT ? "revert" : "cherry-pick";
|
@@ -587,25 +592,146 @@ static void read_and_refresh_cache(struct replay_opts *opts)
|
587 | 592 | rollback_lock_file(&index_lock);
|
588 | 593 | }
|
589 | 594 |
|
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) |
591 | 641 | {
|
592 | 642 | struct rev_info revs;
|
593 | 643 | 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; |
594 | 705 |
|
595 | 706 | setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
|
596 | 707 | if (opts->allow_ff)
|
597 | 708 | assert(!(opts->signoff || opts->no_commit ||
|
598 | 709 | opts->record_origin || opts->edit));
|
599 | 710 | read_and_refresh_cache(opts);
|
600 | 711 |
|
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)); |
602 | 720 |
|
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); |
605 | 724 | if (res)
|
606 | 725 | return res;
|
607 | 726 | }
|
608 | 727 |
|
| 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); |
609 | 735 | return 0;
|
610 | 736 | }
|
611 | 737 |
|
|
0 commit comments