|
18 | 18 | #include "quote.h"
|
19 | 19 | #include "log-tree.h"
|
20 | 20 | #include "wt-status.h"
|
| 21 | +#include "hashmap.h" |
21 | 22 |
|
22 | 23 | #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
23 | 24 |
|
@@ -2695,3 +2696,199 @@ int skip_unnecessary_picks(void)
|
2695 | 2696 |
|
2696 | 2697 | return 0;
|
2697 | 2698 | }
|
| 2699 | + |
| 2700 | +struct subject2item_entry { |
| 2701 | + struct hashmap_entry entry; |
| 2702 | + int i; |
| 2703 | + char subject[FLEX_ARRAY]; |
| 2704 | +}; |
| 2705 | + |
| 2706 | +static int subject2item_cmp(const struct subject2item_entry *a, |
| 2707 | + const struct subject2item_entry *b, const void *key) |
| 2708 | +{ |
| 2709 | + return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject); |
| 2710 | +} |
| 2711 | + |
| 2712 | +/* |
| 2713 | + * Rearrange the todo list that has both "pick sha1 msg" and "pick sha1 |
| 2714 | + * fixup!/squash! msg" in it so that the latter is put immediately after the |
| 2715 | + * former, and change "pick" to "fixup"/"squash". |
| 2716 | + * |
| 2717 | + * Note that if the config has specified a custom instruction format, each log |
| 2718 | + * message will have to be retrieved from the commit (as the oneline in the |
| 2719 | + * script cannot be trusted) in order to normalize the autosquash arrangement. |
| 2720 | + */ |
| 2721 | +int rearrange_squash(void) |
| 2722 | +{ |
| 2723 | + const char *todo_file = rebase_path_todo(); |
| 2724 | + struct todo_list todo_list = TODO_LIST_INIT; |
| 2725 | + struct hashmap subject2item; |
| 2726 | + int res = 0, rearranged = 0, *next, *tail, fd, i; |
| 2727 | + char **subjects; |
| 2728 | + |
| 2729 | + fd = open(todo_file, O_RDONLY); |
| 2730 | + if (fd < 0) |
| 2731 | + return error_errno(_("could not open '%s'"), todo_file); |
| 2732 | + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { |
| 2733 | + close(fd); |
| 2734 | + return error(_("could not read '%s'."), todo_file); |
| 2735 | + } |
| 2736 | + close(fd); |
| 2737 | + if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) { |
| 2738 | + todo_list_release(&todo_list); |
| 2739 | + return -1; |
| 2740 | + } |
| 2741 | + |
| 2742 | + /* |
| 2743 | + * The hashmap maps onelines to the respective todo list index. |
| 2744 | + * |
| 2745 | + * If any items need to be rearranged, the next[i] value will indicate |
| 2746 | + * which item was moved directly after the i'th. |
| 2747 | + * |
| 2748 | + * In that case, last[i] will indicate the index of the latest item to |
| 2749 | + * be moved to appear after the i'th. |
| 2750 | + */ |
| 2751 | + hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp, |
| 2752 | + todo_list.nr); |
| 2753 | + ALLOC_ARRAY(next, todo_list.nr); |
| 2754 | + ALLOC_ARRAY(tail, todo_list.nr); |
| 2755 | + ALLOC_ARRAY(subjects, todo_list.nr); |
| 2756 | + for (i = 0; i < todo_list.nr; i++) { |
| 2757 | + struct strbuf buf = STRBUF_INIT; |
| 2758 | + struct todo_item *item = todo_list.items + i; |
| 2759 | + const char *commit_buffer, *subject, *p; |
| 2760 | + int i2 = -1; |
| 2761 | + struct subject2item_entry *entry; |
| 2762 | + |
| 2763 | + next[i] = tail[i] = -1; |
| 2764 | + if (item->command >= TODO_EXEC) { |
| 2765 | + subjects[i] = NULL; |
| 2766 | + continue; |
| 2767 | + } |
| 2768 | + |
| 2769 | + if (is_fixup(item->command)) { |
| 2770 | + todo_list_release(&todo_list); |
| 2771 | + return error(_("the script was already rearranged.")); |
| 2772 | + } |
| 2773 | + |
| 2774 | + item->commit->util = item; |
| 2775 | + |
| 2776 | + parse_commit(item->commit); |
| 2777 | + commit_buffer = get_commit_buffer(item->commit, NULL); |
| 2778 | + find_commit_subject(commit_buffer, &subject); |
| 2779 | + format_subject(&buf, subject, " "); |
| 2780 | + subject = subjects[i] = buf.buf; |
| 2781 | + unuse_commit_buffer(item->commit, commit_buffer); |
| 2782 | + if ((skip_prefix(subject, "fixup! ", &p) || |
| 2783 | + skip_prefix(subject, "squash! ", &p))) { |
| 2784 | + struct commit *commit2; |
| 2785 | + |
| 2786 | + for (;;) { |
| 2787 | + while (isspace(*p)) |
| 2788 | + p++; |
| 2789 | + if (!skip_prefix(p, "fixup! ", &p) && |
| 2790 | + !skip_prefix(p, "squash! ", &p)) |
| 2791 | + break; |
| 2792 | + } |
| 2793 | + |
| 2794 | + if ((entry = hashmap_get_from_hash(&subject2item, |
| 2795 | + strhash(p), p))) |
| 2796 | + /* found by title */ |
| 2797 | + i2 = entry->i; |
| 2798 | + else if (!strchr(p, ' ') && |
| 2799 | + (commit2 = |
| 2800 | + lookup_commit_reference_by_name(p)) && |
| 2801 | + commit2->util) |
| 2802 | + /* found by commit name */ |
| 2803 | + i2 = (struct todo_item *)commit2->util |
| 2804 | + - todo_list.items; |
| 2805 | + else { |
| 2806 | + /* copy can be a prefix of the commit subject */ |
| 2807 | + for (i2 = 0; i2 < i; i2++) |
| 2808 | + if (subjects[i2] && |
| 2809 | + starts_with(subjects[i2], p)) |
| 2810 | + break; |
| 2811 | + if (i2 == i) |
| 2812 | + i2 = -1; |
| 2813 | + } |
| 2814 | + } |
| 2815 | + if (i2 >= 0) { |
| 2816 | + rearranged = 1; |
| 2817 | + todo_list.items[i].command = |
| 2818 | + starts_with(subject, "fixup!") ? |
| 2819 | + TODO_FIXUP : TODO_SQUASH; |
| 2820 | + if (next[i2] < 0) |
| 2821 | + next[i2] = i; |
| 2822 | + else |
| 2823 | + next[tail[i2]] = i; |
| 2824 | + tail[i2] = i; |
| 2825 | + } else if (!hashmap_get_from_hash(&subject2item, |
| 2826 | + strhash(subject), subject)) { |
| 2827 | + FLEX_ALLOC_MEM(entry, subject, buf.buf, buf.len); |
| 2828 | + entry->i = i; |
| 2829 | + hashmap_entry_init(entry, strhash(entry->subject)); |
| 2830 | + hashmap_put(&subject2item, entry); |
| 2831 | + } |
| 2832 | + strbuf_detach(&buf, NULL); |
| 2833 | + } |
| 2834 | + |
| 2835 | + if (rearranged) { |
| 2836 | + struct strbuf buf = STRBUF_INIT; |
| 2837 | + char *format = NULL; |
| 2838 | + |
| 2839 | + git_config_get_string("rebase.instructionFormat", &format); |
| 2840 | + for (i = 0; i < todo_list.nr; i++) { |
| 2841 | + enum todo_command command = todo_list.items[i].command; |
| 2842 | + int cur = i; |
| 2843 | + |
| 2844 | + /* |
| 2845 | + * Initially, all commands are 'pick's. If it is a |
| 2846 | + * fixup or a squash now, we have rearranged it. |
| 2847 | + */ |
| 2848 | + if (is_fixup(command)) |
| 2849 | + continue; |
| 2850 | + |
| 2851 | + while (cur >= 0) { |
| 2852 | + int offset = todo_list.items[cur].offset_in_buf; |
| 2853 | + int end_offset = cur + 1 < todo_list.nr ? |
| 2854 | + todo_list.items[cur + 1].offset_in_buf : |
| 2855 | + todo_list.buf.len; |
| 2856 | + char *bol = todo_list.buf.buf + offset; |
| 2857 | + char *eol = todo_list.buf.buf + end_offset; |
| 2858 | + |
| 2859 | + /* replace 'pick', by 'fixup' or 'squash' */ |
| 2860 | + command = todo_list.items[cur].command; |
| 2861 | + if (is_fixup(command)) { |
| 2862 | + strbuf_addstr(&buf, |
| 2863 | + todo_command_info[command].str); |
| 2864 | + bol += strcspn(bol, " \t"); |
| 2865 | + } |
| 2866 | + |
| 2867 | + strbuf_add(&buf, bol, eol - bol); |
| 2868 | + |
| 2869 | + cur = next[cur]; |
| 2870 | + } |
| 2871 | + } |
| 2872 | + |
| 2873 | + fd = open(todo_file, O_WRONLY); |
| 2874 | + if (fd < 0) |
| 2875 | + res = error_errno(_("could not open '%s'"), todo_file); |
| 2876 | + else if (write(fd, buf.buf, buf.len) < 0) |
| 2877 | + res = error_errno(_("could not read '%s'."), todo_file); |
| 2878 | + else if (ftruncate(fd, buf.len) < 0) |
| 2879 | + res = error_errno(_("could not finish '%s'"), |
| 2880 | + todo_file); |
| 2881 | + close(fd); |
| 2882 | + strbuf_release(&buf); |
| 2883 | + } |
| 2884 | + |
| 2885 | + free(next); |
| 2886 | + free(tail); |
| 2887 | + for (i = 0; i < todo_list.nr; i++) |
| 2888 | + free(subjects[i]); |
| 2889 | + free(subjects); |
| 2890 | + hashmap_free(&subject2item, 1); |
| 2891 | + todo_list_release(&todo_list); |
| 2892 | + |
| 2893 | + return res; |
| 2894 | +} |
0 commit comments