Skip to content

Commit 022349c

Browse files
jonathantanmygitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 710714a commit 022349c

File tree

1 file changed

+100
-94
lines changed

1 file changed

+100
-94
lines changed

trailer.c

Lines changed: 100 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ static int same_trailer(struct trailer_item *a, struct arg_item *b)
102102
return same_token(a, b) && same_value(a, b);
103103
}
104104

105-
static inline int contains_only_spaces(const char *str)
105+
static inline int is_blank_line(const char *str)
106106
{
107107
const char *s = str;
108-
while (*s && isspace(*s))
108+
while (*s && *s != '\n' && isspace(*s))
109109
s++;
110-
return !*s;
110+
return !*s || *s == '\n';
111111
}
112112

113113
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,
702702
free(cl_separators);
703703
}
704704

705-
static struct strbuf **read_input_file(const char *file)
705+
static void read_input_file(struct strbuf *sb, const char *file)
706706
{
707-
struct strbuf **lines;
708-
struct strbuf sb = STRBUF_INIT;
709-
710707
if (file) {
711-
if (strbuf_read_file(&sb, file, 0) < 0)
708+
if (strbuf_read_file(sb, file, 0) < 0)
712709
die_errno(_("could not read input file '%s'"), file);
713710
} else {
714-
if (strbuf_read(&sb, fileno(stdin), 0) < 0)
711+
if (strbuf_read(sb, fileno(stdin), 0) < 0)
715712
die_errno(_("could not read from stdin"));
716713
}
714+
}
717715

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+
}
719721

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;
721738

722-
return lines;
739+
for (; i >= 0; i--) {
740+
if (buf[i] == '\n')
741+
return i + 1;
742+
}
743+
return 0;
723744
}
724745

725746
/*
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.
728749
*/
729-
static int find_patch_start(struct strbuf **lines, int count)
750+
static int find_patch_start(const char *str)
730751
{
731-
int i;
752+
const char *s;
732753

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;
737757
}
738758

739-
return count;
759+
return s - str;
740760
}
741761

742762
/*
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.
746765
*/
747-
static int find_trailer_start(struct strbuf **lines, int count)
766+
static int find_trailer_start(const char *buf, size_t len)
748767
{
749-
int start, end_of_title, only_spaces = 1;
768+
const char *s;
769+
int end_of_title, l, only_spaces = 1;
750770
int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0;
751771
/*
752772
* Number of possible continuation lines encountered. This will be
@@ -758,53 +778,56 @@ static int find_trailer_start(struct strbuf **lines, int count)
758778
int possible_continuation_lines = 0;
759779

760780
/* 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)
763783
continue;
764-
if (contains_only_spaces(lines[start]->buf))
784+
if (is_blank_line(s))
765785
break;
766786
}
767-
end_of_title = start;
787+
end_of_title = s - buf;
768788

769789
/*
770790
* Get the start of the trailers by looking starting from the end for a
771791
* blank line before a set of non-blank lines that (i) are all
772792
* trailers, or (ii) contains at least one Git-generated trailer and
773793
* consists of at least 25% trailers.
774794
*/
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;
776799
const char **p;
777800
int separator_pos;
778801

779-
if (lines[start]->buf[0] == comment_line_char) {
802+
if (bol[0] == comment_line_char) {
780803
non_trailer_lines += possible_continuation_lines;
781804
possible_continuation_lines = 0;
782805
continue;
783806
}
784-
if (contains_only_spaces(lines[start]->buf)) {
807+
if (is_blank_line(bol)) {
785808
if (only_spaces)
786809
continue;
787810
non_trailer_lines += possible_continuation_lines;
788811
if (recognized_prefix &&
789812
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;
794817
}
795818
only_spaces = 0;
796819

797820
for (p = git_generated_prefixes; *p; p++) {
798-
if (starts_with(lines[start]->buf, *p)) {
821+
if (starts_with(bol, *p)) {
799822
trailer_lines++;
800823
possible_continuation_lines = 0;
801824
recognized_prefix = 1;
802825
goto continue_outer_loop;
803826
}
804827
}
805828

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])) {
808831
struct list_head *pos;
809832

810833
trailer_lines++;
@@ -814,13 +837,13 @@ static int find_trailer_start(struct strbuf **lines, int count)
814837
list_for_each(pos, &conf_head) {
815838
struct arg_item *item;
816839
item = list_entry(pos, struct arg_item, list);
817-
if (token_matches_item(lines[start]->buf, item,
840+
if (token_matches_item(bol, item,
818841
separator_pos)) {
819842
recognized_prefix = 1;
820843
break;
821844
}
822845
}
823-
} else if (isspace(lines[start]->buf[0]))
846+
} else if (isspace(bol[0]))
824847
possible_continuation_lines++;
825848
else {
826849
non_trailer_lines++;
@@ -831,95 +854,78 @@ static int find_trailer_start(struct strbuf **lines, int count)
831854
;
832855
}
833856

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;
851858
}
852859

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)
854862
{
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);
861864
}
862865

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)
864867
{
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);
868872
}
869873

870874
static int process_input_file(FILE *outfile,
871-
struct strbuf **lines,
875+
const char *str,
872876
struct list_head *head)
873877
{
874-
int count = 0;
875-
int patch_start, trailer_start, trailer_end, i;
878+
int patch_start, trailer_start, trailer_end;
876879
struct strbuf tok = STRBUF_INIT;
877880
struct strbuf val = STRBUF_INIT;
878881
struct trailer_item *last = NULL;
882+
struct strbuf *trailer, **trailer_lines, **ptr;
879883

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);
887887

888888
/* Print lines before the trailers as is */
889-
print_lines(outfile, lines, 0, trailer_start);
889+
fwrite(str, 1, trailer_start, outfile);
890890

891-
if (!has_blank_line_before(lines, trailer_start - 1))
891+
if (!ends_with_blank_line(str, trailer_start))
892892
fprintf(outfile, "\n");
893893

894894
/* 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++) {
896900
int separator_pos;
897-
if (lines[i]->buf[0] == comment_line_char)
901+
trailer = *ptr;
902+
if (trailer->buf[0] == comment_line_char)
898903
continue;
899-
if (last && isspace(lines[i]->buf[0])) {
904+
if (last && isspace(trailer->buf[0])) {
900905
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);
902907
strbuf_strip_suffix(&sb, "\n");
903908
free(last->value);
904909
last->value = strbuf_detach(&sb, NULL);
905910
continue;
906911
}
907-
separator_pos = find_separator(lines[i]->buf, separators);
912+
separator_pos = find_separator(trailer->buf, separators);
908913
if (separator_pos >= 1) {
909-
parse_trailer(&tok, &val, NULL, lines[i]->buf,
914+
parse_trailer(&tok, &val, NULL, trailer->buf,
910915
separator_pos);
911916
last = add_trailer_item(head,
912917
strbuf_detach(&tok, NULL),
913918
strbuf_detach(&val, NULL));
914919
} else {
915-
strbuf_addbuf(&val, lines[i]);
920+
strbuf_addbuf(&val, trailer);
916921
strbuf_strip_suffix(&val, "\n");
917922
add_trailer_item(head,
918923
NULL,
919924
strbuf_detach(&val, NULL));
920925
last = NULL;
921926
}
922927
}
928+
strbuf_list_free(trailer_lines);
923929

924930
return trailer_end;
925931
}
@@ -968,21 +974,21 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str
968974
{
969975
LIST_HEAD(head);
970976
LIST_HEAD(arg_head);
971-
struct strbuf **lines;
977+
struct strbuf sb = STRBUF_INIT;
972978
int trailer_end;
973979
FILE *outfile = stdout;
974980

975981
/* Default config must be setup first */
976982
git_config(git_trailer_default_config, NULL);
977983
git_config(git_trailer_config, NULL);
978984

979-
lines = read_input_file(file);
985+
read_input_file(&sb, file);
980986

981987
if (in_place)
982988
outfile = create_in_place_tempfile(file);
983989

984990
/* 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);
986992

987993
process_command_line_args(&arg_head, trailers);
988994

@@ -993,11 +999,11 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str
993999
free_all(&head);
9941000

9951001
/* 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);
9971003

9981004
if (in_place)
9991005
if (rename_tempfile(&trailers_tempfile, file))
10001006
die_errno(_("could not rename temporary file to %s"), file);
10011007

1002-
strbuf_list_free(lines);
1008+
strbuf_release(&sb);
10031009
}

0 commit comments

Comments
 (0)