list-objects-filter-options.con commit list-objects-filter: implement composite filters (e987df5)
   1#include "cache.h"
   2#include "commit.h"
   3#include "config.h"
   4#include "revision.h"
   5#include "argv-array.h"
   6#include "list-objects.h"
   7#include "list-objects-filter.h"
   8#include "list-objects-filter-options.h"
   9#include "url.h"
  10
  11static int parse_combine_filter(
  12        struct list_objects_filter_options *filter_options,
  13        const char *arg,
  14        struct strbuf *errbuf);
  15
  16/*
  17 * Parse value of the argument to the "filter" keyword.
  18 * On the command line this looks like:
  19 *       --filter=<arg>
  20 * and in the pack protocol as:
  21 *       "filter" SP <arg>
  22 *
  23 * The filter keyword will be used by many commands.
  24 * See Documentation/rev-list-options.txt for allowed values for <arg>.
  25 *
  26 * Capture the given arg as the "filter_spec".  This can be forwarded to
  27 * subordinate commands when necessary (although it's better to pass it through
  28 * expand_list_objects_filter_spec() first).  We also "intern" the arg for the
  29 * convenience of the current command.
  30 */
  31static int gently_parse_list_objects_filter(
  32        struct list_objects_filter_options *filter_options,
  33        const char *arg,
  34        struct strbuf *errbuf)
  35{
  36        const char *v0;
  37
  38        if (filter_options->choice) {
  39                strbuf_addstr(
  40                        errbuf, _("multiple filter-specs cannot be combined"));
  41                return 1;
  42        }
  43
  44        if (!strcmp(arg, "blob:none")) {
  45                filter_options->choice = LOFC_BLOB_NONE;
  46                return 0;
  47
  48        } else if (skip_prefix(arg, "blob:limit=", &v0)) {
  49                if (git_parse_ulong(v0, &filter_options->blob_limit_value)) {
  50                        filter_options->choice = LOFC_BLOB_LIMIT;
  51                        return 0;
  52                }
  53
  54        } else if (skip_prefix(arg, "tree:", &v0)) {
  55                if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) {
  56                        strbuf_addstr(errbuf, _("expected 'tree:<depth>'"));
  57                        return 1;
  58                }
  59                filter_options->choice = LOFC_TREE_DEPTH;
  60                return 0;
  61
  62        } else if (skip_prefix(arg, "sparse:oid=", &v0)) {
  63                struct object_context oc;
  64                struct object_id sparse_oid;
  65
  66                /*
  67                 * Try to parse <oid-expression> into an OID for the current
  68                 * command, but DO NOT complain if we don't have the blob or
  69                 * ref locally.
  70                 */
  71                if (!get_oid_with_context(the_repository, v0, GET_OID_BLOB,
  72                                          &sparse_oid, &oc))
  73                        filter_options->sparse_oid_value = oiddup(&sparse_oid);
  74                filter_options->choice = LOFC_SPARSE_OID;
  75                return 0;
  76
  77        } else if (skip_prefix(arg, "sparse:path=", &v0)) {
  78                if (errbuf) {
  79                        strbuf_addstr(
  80                                errbuf,
  81                                _("sparse:path filters support has been dropped"));
  82                }
  83                return 1;
  84
  85        } else if (skip_prefix(arg, "combine:", &v0)) {
  86                return parse_combine_filter(filter_options, v0, errbuf);
  87
  88        }
  89        /*
  90         * Please update _git_fetch() in git-completion.bash when you
  91         * add new filters
  92         */
  93
  94        strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg);
  95
  96        memset(filter_options, 0, sizeof(*filter_options));
  97        return 1;
  98}
  99
 100static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?";
 101
 102static int has_reserved_character(
 103        struct strbuf *sub_spec, struct strbuf *errbuf)
 104{
 105        const char *c = sub_spec->buf;
 106        while (*c) {
 107                if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) {
 108                        strbuf_addf(
 109                                errbuf,
 110                                _("must escape char in sub-filter-spec: '%c'"),
 111                                *c);
 112                        return 1;
 113                }
 114                c++;
 115        }
 116
 117        return 0;
 118}
 119
 120static int parse_combine_subfilter(
 121        struct list_objects_filter_options *filter_options,
 122        struct strbuf *subspec,
 123        struct strbuf *errbuf)
 124{
 125        size_t new_index = filter_options->sub_nr++;
 126        char *decoded;
 127        int result;
 128
 129        ALLOC_GROW(filter_options->sub, filter_options->sub_nr,
 130                   filter_options->sub_alloc);
 131        memset(&filter_options->sub[new_index], 0,
 132               sizeof(*filter_options->sub));
 133
 134        decoded = url_percent_decode(subspec->buf);
 135
 136        result = has_reserved_character(subspec, errbuf) ||
 137                gently_parse_list_objects_filter(
 138                        &filter_options->sub[new_index], decoded, errbuf);
 139
 140        free(decoded);
 141        return result;
 142}
 143
 144static int parse_combine_filter(
 145        struct list_objects_filter_options *filter_options,
 146        const char *arg,
 147        struct strbuf *errbuf)
 148{
 149        struct strbuf **subspecs = strbuf_split_str(arg, '+', 0);
 150        size_t sub;
 151        int result = 0;
 152
 153        if (!subspecs[0]) {
 154                strbuf_addstr(errbuf, _("expected something after combine:"));
 155                result = 1;
 156                goto cleanup;
 157        }
 158
 159        for (sub = 0; subspecs[sub] && !result; sub++) {
 160                if (subspecs[sub + 1]) {
 161                        /*
 162                         * This is not the last subspec. Remove trailing "+" so
 163                         * we can parse it.
 164                         */
 165                        size_t last = subspecs[sub]->len - 1;
 166                        assert(subspecs[sub]->buf[last] == '+');
 167                        strbuf_remove(subspecs[sub], last, 1);
 168                }
 169                result = parse_combine_subfilter(
 170                        filter_options, subspecs[sub], errbuf);
 171        }
 172
 173        filter_options->choice = LOFC_COMBINE;
 174
 175cleanup:
 176        strbuf_list_free(subspecs);
 177        if (result) {
 178                list_objects_filter_release(filter_options);
 179                memset(filter_options, 0, sizeof(*filter_options));
 180        }
 181        return result;
 182}
 183
 184int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
 185                              const char *arg)
 186{
 187        struct strbuf buf = STRBUF_INIT;
 188        filter_options->filter_spec = strdup(arg);
 189        if (gently_parse_list_objects_filter(filter_options, arg, &buf))
 190                die("%s", buf.buf);
 191        return 0;
 192}
 193
 194int opt_parse_list_objects_filter(const struct option *opt,
 195                                  const char *arg, int unset)
 196{
 197        struct list_objects_filter_options *filter_options = opt->value;
 198
 199        if (unset || !arg) {
 200                list_objects_filter_set_no_filter(filter_options);
 201                return 0;
 202        }
 203
 204        return parse_list_objects_filter(filter_options, arg);
 205}
 206
 207void expand_list_objects_filter_spec(
 208        const struct list_objects_filter_options *filter,
 209        struct strbuf *expanded_spec)
 210{
 211        strbuf_init(expanded_spec, strlen(filter->filter_spec));
 212        if (filter->choice == LOFC_BLOB_LIMIT)
 213                strbuf_addf(expanded_spec, "blob:limit=%lu",
 214                            filter->blob_limit_value);
 215        else if (filter->choice == LOFC_TREE_DEPTH)
 216                strbuf_addf(expanded_spec, "tree:%lu",
 217                            filter->tree_exclude_depth);
 218        else
 219                strbuf_addstr(expanded_spec, filter->filter_spec);
 220}
 221
 222void list_objects_filter_release(
 223        struct list_objects_filter_options *filter_options)
 224{
 225        size_t sub;
 226
 227        if (!filter_options)
 228                return;
 229        free(filter_options->filter_spec);
 230        free(filter_options->sparse_oid_value);
 231        for (sub = 0; sub < filter_options->sub_nr; sub++)
 232                list_objects_filter_release(&filter_options->sub[sub]);
 233        free(filter_options->sub);
 234        memset(filter_options, 0, sizeof(*filter_options));
 235}
 236
 237void partial_clone_register(
 238        const char *remote,
 239        const struct list_objects_filter_options *filter_options)
 240{
 241        /*
 242         * Record the name of the partial clone remote in the
 243         * config and in the global variable -- the latter is
 244         * used throughout to indicate that partial clone is
 245         * enabled and to expect missing objects.
 246         */
 247        if (repository_format_partial_clone &&
 248            *repository_format_partial_clone &&
 249            strcmp(remote, repository_format_partial_clone))
 250                die(_("cannot change partial clone promisor remote"));
 251
 252        git_config_set("core.repositoryformatversion", "1");
 253        git_config_set("extensions.partialclone", remote);
 254
 255        repository_format_partial_clone = xstrdup(remote);
 256
 257        /*
 258         * Record the initial filter-spec in the config as
 259         * the default for subsequent fetches from this remote.
 260         */
 261        core_partial_clone_filter_default =
 262                xstrdup(filter_options->filter_spec);
 263        git_config_set("core.partialclonefilter",
 264                       core_partial_clone_filter_default);
 265}
 266
 267void partial_clone_get_default_filter_spec(
 268        struct list_objects_filter_options *filter_options)
 269{
 270        struct strbuf errbuf = STRBUF_INIT;
 271
 272        /*
 273         * Parse default value, but silently ignore it if it is invalid.
 274         */
 275        if (!core_partial_clone_filter_default)
 276                return;
 277
 278        filter_options->filter_spec = strdup(core_partial_clone_filter_default);
 279        gently_parse_list_objects_filter(filter_options,
 280                                         core_partial_clone_filter_default,
 281                                         &errbuf);
 282        strbuf_release(&errbuf);
 283}