ref-filter.c: filter & format refs in the same callback

Update 'filter_and_format_refs()' to try to perform ref filtering & formatting in a single ref iteration, without an intermediate 'struct ref_array'. This can only be done if no operations need to be performed on a pre-filtered array; specifically, if the refs are - filtered on reachability, - sorted, or - formatted with ahead-behind information they cannot be filtered & formatted in the same iteration. In that case, fall back on the current filter-then-sort-then-format flow. This optimization substantially improves memory usage due to no longer storing a ref array in memory. In some cases, it also dramatically reduces runtime (e.g. 'git for-each-ref --no-sort --count=1', which no longer loads all refs into a 'struct ref_array' to printing only the first ref). Signed-off-by: Victoria Dye <vdye@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Victoria Dye committed Nov 14, 2023 at 19:53 UTC bd98f9774e10808276c13f8438aa3c71093bc6a4
1 file changed +82 -6
ref-filter.c
+82 -6
@@ -2779,6 +2779,49 @@ static void free_array_item(struct ref_array_item *item)
2779 free(item);
2780 }
2781
2782 +struct ref_filter_and_format_cbdata {
2783 + struct ref_filter *filter;
2784 + struct ref_format *format;
2785 +
2786 + struct ref_filter_and_format_internal {
2787 + int count;
2788 + } internal;
2789 +};
2790 +
2791 +static int filter_and_format_one(const char *refname, const struct object_id *oid, int flag, void *cb_data)
2792 +{
2793 + struct ref_filter_and_format_cbdata *ref_cbdata = cb_data;
2794 + struct ref_array_item *ref;
2795 + struct strbuf output = STRBUF_INIT, err = STRBUF_INIT;
2796 +
2797 + ref = apply_ref_filter(refname, oid, flag, ref_cbdata->filter);
2798 + if (!ref)
2799 + return 0;
2800 +
2801 + if (format_ref_array_item(ref, ref_cbdata->format, &output, &err))
2802 + die("%s", err.buf);
2803 +
2804 + if (output.len || !ref_cbdata->format->array_opts.omit_empty) {
2805 + fwrite(output.buf, 1, output.len, stdout);
2806 + putchar('\n');
2807 + }
2808 +
2809 + strbuf_release(&output);
2810 + strbuf_release(&err);
2811 + free_array_item(ref);
2812 +
2813 + /*
2814 + * Increment the running count of refs that match the filter. If
2815 + * max_count is set and we've reached the max, stop the ref
2816 + * iteration by returning a nonzero value.
2817 + */
2818 + if (ref_cbdata->format->array_opts.max_count &&
2819 + ++ref_cbdata->internal.count >= ref_cbdata->format->array_opts.max_count)
2820 + return 1;
2821 +
2822 + return 0;
2823 +}
2824 +
2825 /* Free all memory allocated for ref_array */
2826 void ref_array_clear(struct ref_array *array)
2827 {
@@ -2962,16 +3005,49 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
3005 return ret;
3006 }
3007
3008 +static inline int can_do_iterative_format(struct ref_filter *filter,
3009 + struct ref_sorting *sorting,
3010 + struct ref_format *format)
3011 +{
3012 + /*
3013 + * Filtering & formatting results within a single ref iteration
3014 + * callback is not compatible with options that require
3015 + * post-processing a filtered ref_array. These include:
3016 + * - filtering on reachability
3017 + * - sorting the filtered results
3018 + * - including ahead-behind information in the formatted output
3019 + */
3020 + return !(filter->reachable_from ||
3021 + filter->unreachable_from ||
3022 + sorting ||
3023 + format->bases.nr);
3024 +}
3025 +
3026 void filter_and_format_refs(struct ref_filter *filter, unsigned int type,
3027 struct ref_sorting *sorting,
3028 struct ref_format *format)
3029 {
2969 - struct ref_array array = { 0 };
2970 - filter_refs(&array, filter, type);
2971 - filter_ahead_behind(the_repository, format, &array);
2972 - ref_array_sort(sorting, &array);
2973 - print_formatted_ref_array(&array, format);
2974 - ref_array_clear(&array);
3030 + if (can_do_iterative_format(filter, sorting, format)) {
3031 + int save_commit_buffer_orig;
3032 + struct ref_filter_and_format_cbdata ref_cbdata = {
3033 + .filter = filter,
3034 + .format = format,
3035 + };
3036 +
3037 + save_commit_buffer_orig = save_commit_buffer;
3038 + save_commit_buffer = 0;
3039 +
3040 + do_filter_refs(filter, type, filter_and_format_one, &ref_cbdata);
3041 +
3042 + save_commit_buffer = save_commit_buffer_orig;
3043 + } else {
3044 + struct ref_array array = { 0 };
3045 + filter_refs(&array, filter, type);
3046 + filter_ahead_behind(the_repository, format, &array);
3047 + ref_array_sort(sorting, &array);
3048 + print_formatted_ref_array(&array, format);
3049 + ref_array_clear(&array);
3050 + }
3051 }
3052
3053 static int compare_detached_head(struct ref_array_item *a, struct ref_array_item *b)