trailer: avoid unnecessary splitting on lines

trailer.c currently splits lines while processing a buffer (and also rejoins lines when needing to invoke ignore_non_trailer). Avoid such line splitting, except when generating the strings corresponding to trailers (for ease of use by clients - a subsequent patch will allow other components to obtain the layout of a trailer block in a buffer, including the trailers themselves). The main purpose of this is to make it easy to return pointers into the original buffer (for a subsequent patch), but this also significantly reduces the number of memory allocations required. Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jonathan Tan committed Nov 2, 2016 at 10:29 UTC 022349c3b091f2aa047f1cd12b5409d564b25324
1 file changed +100 -94
trailer.c
+100 -94
@@ -102,12 +102,12 @@ static int same_trailer(struct trailer_item *a, struct arg_item *b)
102 return same_token(a, b) && same_value(a, b);
103 }
104
105 -static inline int contains_only_spaces(const char *str)
105 +static inline int is_blank_line(const char *str)
106 {
107 const char *s = str;
108 - while (*s && isspace(*s))
108 + while (*s && *s != '\n' && isspace(*s))
109 s++;
110 - return !*s;
110 + return !*s || *s == '\n';
111 }
112
113 static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b)
@@ -702,51 +702,71 @@ static void process_command_line_args(struct list_head *arg_head,
702 free(cl_separators);
703 }
704
705 -static struct strbuf **read_input_file(const char *file)
705 +static void read_input_file(struct strbuf *sb, const char *file)
706 {
707 - struct strbuf **lines;
708 - struct strbuf sb = STRBUF_INIT;
709 -
707 if (file) {
711 - if (strbuf_read_file(&sb, file, 0) < 0)
708 + if (strbuf_read_file(sb, file, 0) < 0)
709 die_errno(_("could not read input file '%s'"), file);
710 } else {
714 - if (strbuf_read(&sb, fileno(stdin), 0) < 0)
711 + if (strbuf_read(sb, fileno(stdin), 0) < 0)
712 die_errno(_("could not read from stdin"));
713 }
714 +}
715
718 - lines = strbuf_split(&sb, '\n');
716 +static const char *next_line(const char *str)
717 +{
718 + const char *nl = strchrnul(str, '\n');
719 + return nl + !!*nl;
720 +}
721
720 - strbuf_release(&sb);
722 +/*
723 + * Return the position of the start of the last line. If len is 0, return -1.
724 + */
725 +static int last_line(const char *buf, size_t len)
726 +{
727 + int i;
728 + if (len == 0)
729 + return -1;
730 + if (len == 1)
731 + return 0;
732 + /*
733 + * Skip the last character (in addition to the null terminator),
734 + * because if the last character is a newline, it is considered as part
735 + * of the last line anyway.
736 + */
737 + i = len - 2;
738
722 - return lines;
739 + for (; i >= 0; i--) {
740 + if (buf[i] == '\n')
741 + return i + 1;
742 + }
743 + return 0;
744 }
745
746 /*
726 - * Return the (0 based) index of the start of the patch or the line
727 - * count if there is no patch in the message.
747 + * Return the position of the start of the patch or the length of str if there
748 + * is no patch in the message.
749 */
729 -static int find_patch_start(struct strbuf **lines, int count)
750 +static int find_patch_start(const char *str)
751 {
731 - int i;
752 + const char *s;
753
733 - /* Get the start of the patch part if any */
734 - for (i = 0; i < count; i++) {
735 - if (starts_with(lines[i]->buf, "---"))
736 - return i;
754 + for (s = str; *s; s = next_line(s)) {
755 + if (starts_with(s, "---"))
756 + return s - str;
757 }
758
739 - return count;
759 + return s - str;
760 }
761
762 /*
743 - * Return the (0 based) index of the first trailer line or count if
744 - * there are no trailers. Trailers are searched only in the lines from
745 - * index (count - 1) down to index 0.
763 + * Return the position of the first trailer line or len if there are no
764 + * trailers.
765 */
747 -static int find_trailer_start(struct strbuf **lines, int count)
766 +static int find_trailer_start(const char *buf, size_t len)
767 {
749 - int start, end_of_title, only_spaces = 1;
768 + const char *s;
769 + int end_of_title, l, only_spaces = 1;
770 int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0;
771 /*
772 * Number of possible continuation lines encountered. This will be
@@ -758,13 +778,13 @@ static int find_trailer_start(struct strbuf **lines, int count)
778 int possible_continuation_lines = 0;
779
780 /* The first paragraph is the title and cannot be trailers */
761 - for (start = 0; start < count; start++) {
762 - if (lines[start]->buf[0] == comment_line_char)
781 + for (s = buf; s < buf + len; s = next_line(s)) {
782 + if (s[0] == comment_line_char)
783 continue;
764 - if (contains_only_spaces(lines[start]->buf))
784 + if (is_blank_line(s))
785 break;
786 }
767 - end_of_title = start;
787 + end_of_title = s - buf;
788
789 /*
790 * Get the start of the trailers by looking starting from the end for a
@@ -772,30 +792,33 @@ static int find_trailer_start(struct strbuf **lines, int count)
792 * trailers, or (ii) contains at least one Git-generated trailer and
793 * consists of at least 25% trailers.
794 */
775 - for (start = count - 1; start >= end_of_title; start--) {
795 + for (l = last_line(buf, len);
796 + l >= end_of_title;
797 + l = last_line(buf, l)) {
798 + const char *bol = buf + l;
799 const char **p;
800 int separator_pos;
801
779 - if (lines[start]->buf[0] == comment_line_char) {
802 + if (bol[0] == comment_line_char) {
803 non_trailer_lines += possible_continuation_lines;
804 possible_continuation_lines = 0;
805 continue;
806 }
784 - if (contains_only_spaces(lines[start]->buf)) {
807 + if (is_blank_line(bol)) {
808 if (only_spaces)
809 continue;
810 non_trailer_lines += possible_continuation_lines;
811 if (recognized_prefix &&
812 trailer_lines * 3 >= non_trailer_lines)
790 - return start + 1;
791 - if (trailer_lines && !non_trailer_lines)
792 - return start + 1;
793 - return count;
813 + return next_line(bol) - buf;
814 + else if (trailer_lines && !non_trailer_lines)
815 + return next_line(bol) - buf;
816 + return len;
817 }
818 only_spaces = 0;
819
820 for (p = git_generated_prefixes; *p; p++) {
798 - if (starts_with(lines[start]->buf, *p)) {
821 + if (starts_with(bol, *p)) {
822 trailer_lines++;
823 possible_continuation_lines = 0;
824 recognized_prefix = 1;
@@ -803,8 +826,8 @@ static int find_trailer_start(struct strbuf **lines, int count)
826 }
827 }
828
806 - separator_pos = find_separator(lines[start]->buf, separators);
807 - if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) {
829 + separator_pos = find_separator(bol, separators);
830 + if (separator_pos >= 1 && !isspace(bol[0])) {
831 struct list_head *pos;
832
833 trailer_lines++;
@@ -814,13 +837,13 @@ static int find_trailer_start(struct strbuf **lines, int count)
837 list_for_each(pos, &conf_head) {
838 struct arg_item *item;
839 item = list_entry(pos, struct arg_item, list);
817 - if (token_matches_item(lines[start]->buf, item,
840 + if (token_matches_item(bol, item,
841 separator_pos)) {
842 recognized_prefix = 1;
843 break;
844 }
845 }
823 - } else if (isspace(lines[start]->buf[0]))
846 + } else if (isspace(bol[0]))
847 possible_continuation_lines++;
848 else {
849 non_trailer_lines++;
@@ -831,88 +854,70 @@ continue_outer_loop:
854 ;
855 }
856
834 - return count;
835 -}
836 -
837 -/* Get the index of the end of the trailers */
838 -static int find_trailer_end(struct strbuf **lines, int patch_start)
839 -{
840 - struct strbuf sb = STRBUF_INIT;
841 - int i, ignore_bytes;
842 -
843 - for (i = 0; i < patch_start; i++)
844 - strbuf_addbuf(&sb, lines[i]);
845 - ignore_bytes = ignore_non_trailer(sb.buf, sb.len);
846 - strbuf_release(&sb);
847 - for (i = patch_start - 1; i >= 0 && ignore_bytes > 0; i--)
848 - ignore_bytes -= lines[i]->len;
849 -
850 - return i + 1;
857 + return len;
858 }
859
853 -static int has_blank_line_before(struct strbuf **lines, int start)
860 +/* Return the position of the end of the trailers. */
861 +static int find_trailer_end(const char *buf, size_t len)
862 {
855 - for (;start >= 0; start--) {
856 - if (lines[start]->buf[0] == comment_line_char)
857 - continue;
858 - return contains_only_spaces(lines[start]->buf);
859 - }
860 - return 0;
863 + return len - ignore_non_trailer(buf, len);
864 }
865
863 -static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end)
866 +static int ends_with_blank_line(const char *buf, size_t len)
867 {
865 - int i;
866 - for (i = start; lines[i] && i < end; i++)
867 - fprintf(outfile, "%s", lines[i]->buf);
868 + int ll = last_line(buf, len);
869 + if (ll < 0)
870 + return 0;
871 + return is_blank_line(buf + ll);
872 }
873
874 static int process_input_file(FILE *outfile,
871 - struct strbuf **lines,
875 + const char *str,
876 struct list_head *head)
877 {
874 - int count = 0;
875 - int patch_start, trailer_start, trailer_end, i;
878 + int patch_start, trailer_start, trailer_end;
879 struct strbuf tok = STRBUF_INIT;
880 struct strbuf val = STRBUF_INIT;
881 struct trailer_item *last = NULL;
882 + struct strbuf *trailer, **trailer_lines, **ptr;
883
880 - /* Get the line count */
881 - while (lines[count])
882 - count++;
883 -
884 - patch_start = find_patch_start(lines, count);
885 - trailer_end = find_trailer_end(lines, patch_start);
886 - trailer_start = find_trailer_start(lines, trailer_end);
884 + patch_start = find_patch_start(str);
885 + trailer_end = find_trailer_end(str, patch_start);
886 + trailer_start = find_trailer_start(str, trailer_end);
887
888 /* Print lines before the trailers as is */
889 - print_lines(outfile, lines, 0, trailer_start);
889 + fwrite(str, 1, trailer_start, outfile);
890
891 - if (!has_blank_line_before(lines, trailer_start - 1))
891 + if (!ends_with_blank_line(str, trailer_start))
892 fprintf(outfile, "\n");
893
894 /* Parse trailer lines */
895 - for (i = trailer_start; i < trailer_end; i++) {
895 + trailer_lines = strbuf_split_buf(str + trailer_start,
896 + trailer_end - trailer_start,
897 + '\n',
898 + 0);
899 + for (ptr = trailer_lines; *ptr; ptr++) {
900 int separator_pos;
897 - if (lines[i]->buf[0] == comment_line_char)
901 + trailer = *ptr;
902 + if (trailer->buf[0] == comment_line_char)
903 continue;
899 - if (last && isspace(lines[i]->buf[0])) {
904 + if (last && isspace(trailer->buf[0])) {
905 struct strbuf sb = STRBUF_INIT;
901 - strbuf_addf(&sb, "%s\n%s", last->value, lines[i]->buf);
906 + strbuf_addf(&sb, "%s\n%s", last->value, trailer->buf);
907 strbuf_strip_suffix(&sb, "\n");
908 free(last->value);
909 last->value = strbuf_detach(&sb, NULL);
910 continue;
911 }
907 - separator_pos = find_separator(lines[i]->buf, separators);
912 + separator_pos = find_separator(trailer->buf, separators);
913 if (separator_pos >= 1) {
909 - parse_trailer(&tok, &val, NULL, lines[i]->buf,
914 + parse_trailer(&tok, &val, NULL, trailer->buf,
915 separator_pos);
916 last = add_trailer_item(head,
917 strbuf_detach(&tok, NULL),
918 strbuf_detach(&val, NULL));
919 } else {
915 - strbuf_addbuf(&val, lines[i]);
920 + strbuf_addbuf(&val, trailer);
921 strbuf_strip_suffix(&val, "\n");
922 add_trailer_item(head,
923 NULL,
@@ -920,6 +925,7 @@ static int process_input_file(FILE *outfile,
925 last = NULL;
926 }
927 }
928 + strbuf_list_free(trailer_lines);
929
930 return trailer_end;
931 }
@@ -968,7 +974,7 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str
974 {
975 LIST_HEAD(head);
976 LIST_HEAD(arg_head);
971 - struct strbuf **lines;
977 + struct strbuf sb = STRBUF_INIT;
978 int trailer_end;
979 FILE *outfile = stdout;
980
@@ -976,13 +982,13 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str
982 git_config(git_trailer_default_config, NULL);
983 git_config(git_trailer_config, NULL);
984
979 - lines = read_input_file(file);
985 + read_input_file(&sb, file);
986
987 if (in_place)
988 outfile = create_in_place_tempfile(file);
989
990 /* Print the lines before the trailers */
985 - trailer_end = process_input_file(outfile, lines, &head);
991 + trailer_end = process_input_file(outfile, sb.buf, &head);
992
993 process_command_line_args(&arg_head, trailers);
994
@@ -993,11 +999,11 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str
999 free_all(&head);
1000
1001 /* Print the lines after the trailers as is */
996 - print_lines(outfile, lines, trailer_end, INT_MAX);
1002 + fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile);
1003
1004 if (in_place)
1005 if (rename_tempfile(&trailers_tempfile, file))
1006 die_errno(_("could not rename temporary file to %s"), file);
1007
1002 - strbuf_list_free(lines);
1008 + strbuf_release(&sb);
1009 }