|
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 |
|
@@ -2744,3 +2745,199 @@ int skip_unnecessary_picks(void)
|
2744 | 2745 |
|
2745 | 2746 | return 0;
|
2746 | 2747 | }
|
| 2748 | + |
| 2749 | +struct subject2item_entry { |
| 2750 | + struct hashmap_entry entry; |
| 2751 | + int i; |
| 2752 | + char subject[FLEX_ARRAY]; |
| 2753 | +}; |
| 2754 | + |
| 2755 | +static int subject2item_cmp(const struct subject2item_entry *a, |
| 2756 | + const struct subject2item_entry *b, const void *key) |
| 2757 | +{ |
| 2758 | + return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject); |
| 2759 | +} |
| 2760 | + |
| 2761 | +/* |
| 2762 | + * Rearrange the todo list that has both "pick sha1 msg" and "pick sha1 |
| 2763 | + * fixup!/squash! msg" in it so that the latter is put immediately after the |
| 2764 | + * former, and change "pick" to "fixup"/"squash". |
| 2765 | + * |
| 2766 | + * Note that if the config has specified a custom instruction format, each log |
| 2767 | + * message will have to be retrieved from the commit (as the oneline in the |
| 2768 | + * script cannot be trusted) in order to normalize the autosquash arrangement. |
| 2769 | + */ |
| 2770 | +int rearrange_squash(void) |
| 2771 | +{ |
| 2772 | + const char *todo_file = rebase_path_todo(); |
| 2773 | + struct todo_list todo_list = TODO_LIST_INIT; |
| 2774 | + struct hashmap subject2item; |
| 2775 | + int res = 0, rearranged = 0, *next, *tail, fd, i; |
| 2776 | + char **subjects; |
| 2777 | + |
| 2778 | + fd = open(todo_file, O_RDONLY); |
| 2779 | + if (fd < 0) |
| 2780 | + return error_errno(_("could not open '%s'"), todo_file); |
| 2781 | + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { |
| 2782 | + close(fd); |
| 2783 | + return error(_("could not read '%s'."), todo_file); |
| 2784 | + } |
| 2785 | + close(fd); |
| 2786 | + if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) { |
| 2787 | + todo_list_release(&todo_list); |
| 2788 | + return -1; |
| 2789 | + } |
| 2790 | + |
| 2791 | + /* |
| 2792 | + * The hashmap maps onelines to the respective todo list index. |
| 2793 | + * |
| 2794 | + * If any items need to be rearranged, the next[i] value will indicate |
| 2795 | + * which item was moved directly after the i'th. |
| 2796 | + * |
| 2797 | + * In that case, last[i] will indicate the index of the latest item to |
| 2798 | + * be moved to appear after the i'th. |
| 2799 | + */ |
| 2800 | + hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp, |
| 2801 | + todo_list.nr); |
| 2802 | + ALLOC_ARRAY(next, todo_list.nr); |
| 2803 | + ALLOC_ARRAY(tail, todo_list.nr); |
| 2804 | + ALLOC_ARRAY(subjects, todo_list.nr); |
| 2805 | + for (i = 0; i < todo_list.nr; i++) { |
| 2806 | + struct strbuf buf = STRBUF_INIT; |
| 2807 | + struct todo_item *item = todo_list.items + i; |
| 2808 | + const char *commit_buffer, *subject, *p; |
| 2809 | + int i2 = -1; |
| 2810 | + struct subject2item_entry *entry; |
| 2811 | + |
| 2812 | + next[i] = tail[i] = -1; |
| 2813 | + if (item->command >= TODO_EXEC) { |
| 2814 | + subjects[i] = NULL; |
| 2815 | + continue; |
| 2816 | + } |
| 2817 | + |
| 2818 | + if (is_fixup(item->command)) { |
| 2819 | + todo_list_release(&todo_list); |
| 2820 | + return error(_("the script was already rearranged.")); |
| 2821 | + } |
| 2822 | + |
| 2823 | + item->commit->util = item; |
| 2824 | + |
| 2825 | + parse_commit(item->commit); |
| 2826 | + commit_buffer = get_commit_buffer(item->commit, NULL); |
| 2827 | + find_commit_subject(commit_buffer, &subject); |
| 2828 | + format_subject(&buf, subject, " "); |
| 2829 | + subject = subjects[i] = buf.buf; |
| 2830 | + unuse_commit_buffer(item->commit, commit_buffer); |
| 2831 | + if ((skip_prefix(subject, "fixup! ", &p) || |
| 2832 | + skip_prefix(subject, "squash! ", &p))) { |
| 2833 | + struct commit *commit2; |
| 2834 | + |
| 2835 | + for (;;) { |
| 2836 | + while (isspace(*p)) |
| 2837 | + p++; |
| 2838 | + if (!skip_prefix(p, "fixup! ", &p) && |
| 2839 | + !skip_prefix(p, "squash! ", &p)) |
| 2840 | + break; |
| 2841 | + } |
| 2842 | + |
| 2843 | + if ((entry = hashmap_get_from_hash(&subject2item, |
| 2844 | + strhash(p), p))) |
| 2845 | + /* found by title */ |
| 2846 | + i2 = entry->i; |
| 2847 | + else if (!strchr(p, ' ') && |
| 2848 | + (commit2 = |
| 2849 | + lookup_commit_reference_by_name(p)) && |
| 2850 | + commit2->util) |
| 2851 | + /* found by commit name */ |
| 2852 | + i2 = (struct todo_item *)commit2->util |
| 2853 | + - todo_list.items; |
| 2854 | + else { |
| 2855 | + /* copy can be a prefix of the commit subject */ |
| 2856 | + for (i2 = 0; i2 < i; i2++) |
| 2857 | + if (subjects[i2] && |
| 2858 | + starts_with(subjects[i2], p)) |
| 2859 | + break; |
| 2860 | + if (i2 == i) |
| 2861 | + i2 = -1; |
| 2862 | + } |
| 2863 | + } |
| 2864 | + if (i2 >= 0) { |
| 2865 | + rearranged = 1; |
| 2866 | + todo_list.items[i].command = |
| 2867 | + starts_with(subject, "fixup!") ? |
| 2868 | + TODO_FIXUP : TODO_SQUASH; |
| 2869 | + if (next[i2] < 0) |
| 2870 | + next[i2] = i; |
| 2871 | + else |
| 2872 | + next[tail[i2]] = i; |
| 2873 | + tail[i2] = i; |
| 2874 | + } else if (!hashmap_get_from_hash(&subject2item, |
| 2875 | + strhash(subject), subject)) { |
| 2876 | + FLEX_ALLOC_MEM(entry, subject, buf.buf, buf.len); |
| 2877 | + entry->i = i; |
| 2878 | + hashmap_entry_init(entry, strhash(entry->subject)); |
| 2879 | + hashmap_put(&subject2item, entry); |
| 2880 | + } |
| 2881 | + strbuf_detach(&buf, NULL); |
| 2882 | + } |
| 2883 | + |
| 2884 | + if (rearranged) { |
| 2885 | + struct strbuf buf = STRBUF_INIT; |
| 2886 | + char *format = NULL; |
| 2887 | + |
| 2888 | + git_config_get_string("rebase.instructionFormat", &format); |
| 2889 | + for (i = 0; i < todo_list.nr; i++) { |
| 2890 | + enum todo_command command = todo_list.items[i].command; |
| 2891 | + int cur = i; |
| 2892 | + |
| 2893 | + /* |
| 2894 | + * Initially, all commands are 'pick's. If it is a |
| 2895 | + * fixup or a squash now, we have rearranged it. |
| 2896 | + */ |
| 2897 | + if (is_fixup(command)) |
| 2898 | + continue; |
| 2899 | + |
| 2900 | + while (cur >= 0) { |
| 2901 | + int offset = todo_list.items[cur].offset_in_buf; |
| 2902 | + int end_offset = cur + 1 < todo_list.nr ? |
| 2903 | + todo_list.items[cur + 1].offset_in_buf : |
| 2904 | + todo_list.buf.len; |
| 2905 | + char *bol = todo_list.buf.buf + offset; |
| 2906 | + char *eol = todo_list.buf.buf + end_offset; |
| 2907 | + |
| 2908 | + /* replace 'pick', by 'fixup' or 'squash' */ |
| 2909 | + command = todo_list.items[cur].command; |
| 2910 | + if (is_fixup(command)) { |
| 2911 | + strbuf_addstr(&buf, |
| 2912 | + todo_command_info[command].str); |
| 2913 | + bol += strcspn(bol, " \t"); |
| 2914 | + } |
| 2915 | + |
| 2916 | + strbuf_add(&buf, bol, eol - bol); |
| 2917 | + |
| 2918 | + cur = next[cur]; |
| 2919 | + } |
| 2920 | + } |
| 2921 | + |
| 2922 | + fd = open(todo_file, O_WRONLY); |
| 2923 | + if (fd < 0) |
| 2924 | + res = error_errno(_("could not open '%s'"), todo_file); |
| 2925 | + else if (write(fd, buf.buf, buf.len) < 0) |
| 2926 | + res = error_errno(_("could not read '%s'."), todo_file); |
| 2927 | + else if (ftruncate(fd, buf.len) < 0) |
| 2928 | + res = error_errno(_("could not finish '%s'"), |
| 2929 | + todo_file); |
| 2930 | + close(fd); |
| 2931 | + strbuf_release(&buf); |
| 2932 | + } |
| 2933 | + |
| 2934 | + free(next); |
| 2935 | + free(tail); |
| 2936 | + for (i = 0; i < todo_list.nr; i++) |
| 2937 | + free(subjects[i]); |
| 2938 | + free(subjects); |
| 2939 | + hashmap_free(&subject2item, 1); |
| 2940 | + todo_list_release(&todo_list); |
| 2941 | + |
| 2942 | + return res; |
| 2943 | +} |
0 commit comments