list-objects-filter-options.con commit general UI improvements (05293f9)
   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 "promisor-remote.h"
  10#include "trace.h"
  11#include "url.h"
  12
  13static int parse_combine_filter(
  14        struct list_objects_filter_options *filter_options,
  15        const char *arg,
  16        struct strbuf *errbuf);
  17
  18/*
  19 * Parse value of the argument to the "filter" keyword.
  20 * On the command line this looks like:
  21 *       --filter=<arg>
  22 * and in the pack protocol as:
  23 *       "filter" SP <arg>
  24 *
  25 * The filter keyword will be used by many commands.
  26 * See Documentation/rev-list-options.txt for allowed values for <arg>.
  27 *
  28 * Capture the given arg as the "filter_spec".  This can be forwarded to
  29 * subordinate commands when necessary (although it's better to pass it through
  30 * expand_list_objects_filter_spec() first).  We also "intern" the arg for the
  31 * convenience of the current command.
  32 */
  33static int gently_parse_list_objects_filter(
  34        struct list_objects_filter_options *filter_options,
  35        const char *arg,
  36        struct strbuf *errbuf)
  37{
  38        const char *v0;
  39
  40        if (!arg)
  41                return 0;
  42
  43        if (filter_options->choice)
  44                BUG("filter_options already populated");
  45
  46        if (!strcmp(arg, "blob:none")) {
  47                filter_options->choice = LOFC_BLOB_NONE;
  48                return 0;
  49
  50        } else if (skip_prefix(arg, "blob:limit=", &v0)) {
  51                if (git_parse_ulong(v0, &filter_options->blob_limit_value)) {
  52                        filter_options->choice = LOFC_BLOB_LIMIT;
  53                        return 0;
  54                }
  55
  56        } else if (skip_prefix(arg, "tree:", &v0)) {
  57                if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) {
  58                        strbuf_addstr(errbuf, _("expected 'tree:<depth>'"));
  59                        return 1;
  60                }
  61                filter_options->choice = LOFC_TREE_DEPTH;
  62                return 0;
  63
  64        } else if (skip_prefix(arg, "sparse:oid=", &v0)) {
  65                struct object_context oc;
  66                struct object_id sparse_oid;
  67
  68                /*
  69                 * Try to parse <oid-expression> into an OID for the current
  70                 * command, but DO NOT complain if we don't have the blob or
  71                 * ref locally.
  72                 */
  73                if (!get_oid_with_context(the_repository, v0, GET_OID_BLOB,
  74                                          &sparse_oid, &oc))
  75                        filter_options->sparse_oid_value = oiddup(&sparse_oid);
  76                filter_options->choice = LOFC_SPARSE_OID;
  77                return 0;
  78
  79        } else if (skip_prefix(arg, "sparse:path=", &v0)) {
  80                if (errbuf) {
  81                        strbuf_addstr(
  82                                errbuf,
  83                                _("sparse:path filters support has been dropped"));
  84                }
  85                return 1;
  86
  87        } else if (skip_prefix(arg, "combine:", &v0)) {
  88                return parse_combine_filter(filter_options, v0, errbuf);
  89
  90        }
  91        /*
  92         * Please update _git_fetch() in git-completion.bash when you
  93         * add new filters
  94         */
  95
  96        strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg);
  97
  98        memset(filter_options, 0, sizeof(*filter_options));
  99        return 1;
 100}
 101
 102static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?";
 103
 104static int has_reserved_character(
 105        struct strbuf *sub_spec, struct strbuf *errbuf)
 106{
 107        const char *c = sub_spec->buf;
 108        while (*c) {
 109                if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) {
 110                        strbuf_addf(
 111                                errbuf,
 112                                _("must escape char in sub-filter-spec: '%c'"),
 113                                *c);
 114                        return 1;
 115                }
 116                c++;
 117        }
 118
 119        return 0;
 120}
 121
 122static int parse_combine_subfilter(
 123        struct list_objects_filter_options *filter_options,
 124        struct strbuf *subspec,
 125        struct strbuf *errbuf)
 126{
 127        size_t new_index = filter_options->sub_nr;
 128        char *decoded;
 129        int result;
 130
 131        ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1,
 132                      filter_options->sub_alloc);
 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
 184static int allow_unencoded(char ch)
 185{
 186        if (ch <= ' ' || ch == '%' || ch == '+')
 187                return 0;
 188        return !strchr(RESERVED_NON_WS, ch);
 189}
 190
 191static void filter_spec_append_urlencode(
 192        struct list_objects_filter_options *filter, const char *raw)
 193{
 194        struct strbuf buf = STRBUF_INIT;
 195        strbuf_addstr_urlencode(&buf, raw, allow_unencoded);
 196        trace_printf("Add to combine filter-spec: %s\n", buf.buf);
 197        string_list_append(&filter->filter_spec, strbuf_detach(&buf, NULL));
 198}
 199
 200/*
 201 * Changes filter_options into an equivalent LOFC_COMBINE filter options
 202 * instance. Does not do anything if filter_options is already LOFC_COMBINE.
 203 */
 204static void transform_to_combine_type(
 205        struct list_objects_filter_options *filter_options)
 206{
 207        assert(filter_options->choice);
 208        if (filter_options->choice == LOFC_COMBINE)
 209                return;
 210        {
 211                const int initial_sub_alloc = 2;
 212                struct list_objects_filter_options *sub_array =
 213                        xcalloc(initial_sub_alloc, sizeof(*sub_array));
 214                sub_array[0] = *filter_options;
 215                memset(filter_options, 0, sizeof(*filter_options));
 216                filter_options->sub = sub_array;
 217                filter_options->sub_alloc = initial_sub_alloc;
 218        }
 219        filter_options->sub_nr = 1;
 220        filter_options->choice = LOFC_COMBINE;
 221        string_list_append(&filter_options->filter_spec, xstrdup("combine:"));
 222        filter_spec_append_urlencode(
 223                filter_options,
 224                list_objects_filter_spec(&filter_options->sub[0]));
 225        /*
 226         * We don't need the filter_spec strings for subfilter specs, only the
 227         * top level.
 228         */
 229        string_list_clear(&filter_options->sub[0].filter_spec, /*free_util=*/0);
 230}
 231
 232void list_objects_filter_die_if_populated(
 233        struct list_objects_filter_options *filter_options)
 234{
 235        if (filter_options->choice)
 236                die(_("multiple filter-specs cannot be combined"));
 237}
 238
 239void parse_list_objects_filter(
 240        struct list_objects_filter_options *filter_options,
 241        const char *arg)
 242{
 243        struct strbuf errbuf = STRBUF_INIT;
 244        int parse_error;
 245
 246        if (!filter_options->choice) {
 247                string_list_append(&filter_options->filter_spec, xstrdup(arg));
 248
 249                parse_error = gently_parse_list_objects_filter(
 250                        filter_options, arg, &errbuf);
 251        } else {
 252                /*
 253                 * Make filter_options an LOFC_COMBINE spec so we can trivially
 254                 * add subspecs to it.
 255                 */
 256                transform_to_combine_type(filter_options);
 257
 258                string_list_append(&filter_options->filter_spec, xstrdup("+"));
 259                filter_spec_append_urlencode(filter_options, arg);
 260                ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1,
 261                              filter_options->sub_alloc);
 262
 263                parse_error = gently_parse_list_objects_filter(
 264                        &filter_options->sub[filter_options->sub_nr - 1], arg,
 265                        &errbuf);
 266        }
 267        if (parse_error)
 268                die("%s", errbuf.buf);
 269}
 270
 271int opt_parse_list_objects_filter(const struct option *opt,
 272                                  const char *arg, int unset)
 273{
 274        struct list_objects_filter_options *filter_options = opt->value;
 275
 276        if (unset || !arg)
 277                list_objects_filter_set_no_filter(filter_options);
 278        else
 279                parse_list_objects_filter(filter_options, arg);
 280        return 0;
 281}
 282
 283const char *list_objects_filter_spec(struct list_objects_filter_options *filter)
 284{
 285        if (!filter->filter_spec.nr)
 286                BUG("no filter_spec available for this filter");
 287        if (filter->filter_spec.nr != 1) {
 288                struct strbuf concatted = STRBUF_INIT;
 289                strbuf_add_separated_string_list(
 290                        &concatted, "", &filter->filter_spec);
 291                string_list_clear(&filter->filter_spec, /*free_util=*/0);
 292                string_list_append(
 293                        &filter->filter_spec, strbuf_detach(&concatted, NULL));
 294        }
 295
 296        return filter->filter_spec.items[0].string;
 297}
 298
 299const char *expand_list_objects_filter_spec(
 300        struct list_objects_filter_options *filter)
 301{
 302        if (filter->choice == LOFC_BLOB_LIMIT) {
 303                struct strbuf expanded_spec = STRBUF_INIT;
 304                strbuf_addf(&expanded_spec, "blob:limit=%lu",
 305                            filter->blob_limit_value);
 306                string_list_clear(&filter->filter_spec, /*free_util=*/0);
 307                string_list_append(
 308                        &filter->filter_spec,
 309                        strbuf_detach(&expanded_spec, NULL));
 310        }
 311
 312        return list_objects_filter_spec(filter);
 313}
 314
 315void list_objects_filter_release(
 316        struct list_objects_filter_options *filter_options)
 317{
 318        size_t sub;
 319
 320        if (!filter_options)
 321                return;
 322        string_list_clear(&filter_options->filter_spec, /*free_util=*/0);
 323        free(filter_options->sparse_oid_value);
 324        for (sub = 0; sub < filter_options->sub_nr; sub++)
 325                list_objects_filter_release(&filter_options->sub[sub]);
 326        free(filter_options->sub);
 327        memset(filter_options, 0, sizeof(*filter_options));
 328}
 329
 330void partial_clone_register(
 331        const char *remote,
 332        struct list_objects_filter_options *filter_options)
 333{
 334        char *cfg_name;
 335        char *filter_name;
 336
 337        /* Check if it is already registered */
 338        if (!promisor_remote_find(remote)) {
 339                git_config_set("core.repositoryformatversion", "1");
 340
 341                /* Add promisor config for the remote */
 342                cfg_name = xstrfmt("remote.%s.promisor", remote);
 343                git_config_set(cfg_name, "true");
 344                free(cfg_name);
 345        }
 346
 347        /*
 348         * Record the initial filter-spec in the config as
 349         * the default for subsequent fetches from this remote.
 350         */
 351        filter_name = xstrfmt("remote.%s.partialclonefilter", remote);
 352        /* NEEDSWORK: 'expand' result leaking??? */
 353        git_config_set(filter_name,
 354                       expand_list_objects_filter_spec(filter_options));
 355        free(filter_name);
 356
 357        /* Make sure the config info are reset */
 358        promisor_remote_reinit();
 359}
 360
 361void partial_clone_get_default_filter_spec(
 362        struct list_objects_filter_options *filter_options,
 363        const char *remote)
 364{
 365        struct promisor_remote *promisor = promisor_remote_find(remote);
 366        struct strbuf errbuf = STRBUF_INIT;
 367
 368        /*
 369         * Parse default value, but silently ignore it if it is invalid.
 370         */
 371        if (!promisor)
 372                return;
 373
 374        string_list_append(&filter_options->filter_spec,
 375                           promisor->partial_clone_filter);
 376        gently_parse_list_objects_filter(filter_options,
 377                                         promisor->partial_clone_filter,
 378                                         &errbuf);
 379        strbuf_release(&errbuf);
 380}