list-objects.con commit t1300: `--unset-all` can leave an empty section behind (bug) (b73bdc3)
   1#include "cache.h"
   2#include "tag.h"
   3#include "commit.h"
   4#include "tree.h"
   5#include "blob.h"
   6#include "diff.h"
   7#include "tree-walk.h"
   8#include "revision.h"
   9#include "list-objects.h"
  10#include "list-objects-filter.h"
  11#include "list-objects-filter-options.h"
  12
  13static void process_blob(struct rev_info *revs,
  14                         struct blob *blob,
  15                         show_object_fn show,
  16                         struct strbuf *path,
  17                         const char *name,
  18                         void *cb_data,
  19                         filter_object_fn filter_fn,
  20                         void *filter_data)
  21{
  22        struct object *obj = &blob->object;
  23        size_t pathlen;
  24        enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
  25
  26        if (!revs->blob_objects)
  27                return;
  28        if (!obj)
  29                die("bad blob object");
  30        if (obj->flags & (UNINTERESTING | SEEN))
  31                return;
  32
  33        pathlen = path->len;
  34        strbuf_addstr(path, name);
  35        if (filter_fn)
  36                r = filter_fn(LOFS_BLOB, obj,
  37                              path->buf, &path->buf[pathlen],
  38                              filter_data);
  39        if (r & LOFR_MARK_SEEN)
  40                obj->flags |= SEEN;
  41        if (r & LOFR_DO_SHOW)
  42                show(obj, path->buf, cb_data);
  43        strbuf_setlen(path, pathlen);
  44}
  45
  46/*
  47 * Processing a gitlink entry currently does nothing, since
  48 * we do not recurse into the subproject.
  49 *
  50 * We *could* eventually add a flag that actually does that,
  51 * which would involve:
  52 *  - is the subproject actually checked out?
  53 *  - if so, see if the subproject has already been added
  54 *    to the alternates list, and add it if not.
  55 *  - process the commit (or tag) the gitlink points to
  56 *    recursively.
  57 *
  58 * However, it's unclear whether there is really ever any
  59 * reason to see superprojects and subprojects as such a
  60 * "unified" object pool (potentially resulting in a totally
  61 * humongous pack - avoiding which was the whole point of
  62 * having gitlinks in the first place!).
  63 *
  64 * So for now, there is just a note that we *could* follow
  65 * the link, and how to do it. Whether it necessarily makes
  66 * any sense what-so-ever to ever do that is another issue.
  67 */
  68static void process_gitlink(struct rev_info *revs,
  69                            const unsigned char *sha1,
  70                            show_object_fn show,
  71                            struct strbuf *path,
  72                            const char *name,
  73                            void *cb_data)
  74{
  75        /* Nothing to do */
  76}
  77
  78static void process_tree(struct rev_info *revs,
  79                         struct tree *tree,
  80                         show_object_fn show,
  81                         struct strbuf *base,
  82                         const char *name,
  83                         void *cb_data,
  84                         filter_object_fn filter_fn,
  85                         void *filter_data)
  86{
  87        struct object *obj = &tree->object;
  88        struct tree_desc desc;
  89        struct name_entry entry;
  90        enum interesting match = revs->diffopt.pathspec.nr == 0 ?
  91                all_entries_interesting: entry_not_interesting;
  92        int baselen = base->len;
  93        enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
  94
  95        if (!revs->tree_objects)
  96                return;
  97        if (!obj)
  98                die("bad tree object");
  99        if (obj->flags & (UNINTERESTING | SEEN))
 100                return;
 101        if (parse_tree_gently(tree, revs->ignore_missing_links) < 0) {
 102                if (revs->ignore_missing_links)
 103                        return;
 104                die("bad tree object %s", oid_to_hex(&obj->oid));
 105        }
 106
 107        strbuf_addstr(base, name);
 108        if (filter_fn)
 109                r = filter_fn(LOFS_BEGIN_TREE, obj,
 110                              base->buf, &base->buf[baselen],
 111                              filter_data);
 112        if (r & LOFR_MARK_SEEN)
 113                obj->flags |= SEEN;
 114        if (r & LOFR_DO_SHOW)
 115                show(obj, base->buf, cb_data);
 116        if (base->len)
 117                strbuf_addch(base, '/');
 118
 119        init_tree_desc(&desc, tree->buffer, tree->size);
 120
 121        while (tree_entry(&desc, &entry)) {
 122                if (match != all_entries_interesting) {
 123                        match = tree_entry_interesting(&entry, base, 0,
 124                                                       &revs->diffopt.pathspec);
 125                        if (match == all_entries_not_interesting)
 126                                break;
 127                        if (match == entry_not_interesting)
 128                                continue;
 129                }
 130
 131                if (S_ISDIR(entry.mode))
 132                        process_tree(revs,
 133                                     lookup_tree(entry.oid),
 134                                     show, base, entry.path,
 135                                     cb_data, filter_fn, filter_data);
 136                else if (S_ISGITLINK(entry.mode))
 137                        process_gitlink(revs, entry.oid->hash,
 138                                        show, base, entry.path,
 139                                        cb_data);
 140                else
 141                        process_blob(revs,
 142                                     lookup_blob(entry.oid),
 143                                     show, base, entry.path,
 144                                     cb_data, filter_fn, filter_data);
 145        }
 146
 147        if (filter_fn) {
 148                r = filter_fn(LOFS_END_TREE, obj,
 149                              base->buf, &base->buf[baselen],
 150                              filter_data);
 151                if (r & LOFR_MARK_SEEN)
 152                        obj->flags |= SEEN;
 153                if (r & LOFR_DO_SHOW)
 154                        show(obj, base->buf, cb_data);
 155        }
 156
 157        strbuf_setlen(base, baselen);
 158        free_tree_buffer(tree);
 159}
 160
 161static void mark_edge_parents_uninteresting(struct commit *commit,
 162                                            struct rev_info *revs,
 163                                            show_edge_fn show_edge)
 164{
 165        struct commit_list *parents;
 166
 167        for (parents = commit->parents; parents; parents = parents->next) {
 168                struct commit *parent = parents->item;
 169                if (!(parent->object.flags & UNINTERESTING))
 170                        continue;
 171                mark_tree_uninteresting(parent->tree);
 172                if (revs->edge_hint && !(parent->object.flags & SHOWN)) {
 173                        parent->object.flags |= SHOWN;
 174                        show_edge(parent);
 175                }
 176        }
 177}
 178
 179void mark_edges_uninteresting(struct rev_info *revs, show_edge_fn show_edge)
 180{
 181        struct commit_list *list;
 182        int i;
 183
 184        for (list = revs->commits; list; list = list->next) {
 185                struct commit *commit = list->item;
 186
 187                if (commit->object.flags & UNINTERESTING) {
 188                        mark_tree_uninteresting(commit->tree);
 189                        if (revs->edge_hint_aggressive && !(commit->object.flags & SHOWN)) {
 190                                commit->object.flags |= SHOWN;
 191                                show_edge(commit);
 192                        }
 193                        continue;
 194                }
 195                mark_edge_parents_uninteresting(commit, revs, show_edge);
 196        }
 197        if (revs->edge_hint_aggressive) {
 198                for (i = 0; i < revs->cmdline.nr; i++) {
 199                        struct object *obj = revs->cmdline.rev[i].item;
 200                        struct commit *commit = (struct commit *)obj;
 201                        if (obj->type != OBJ_COMMIT || !(obj->flags & UNINTERESTING))
 202                                continue;
 203                        mark_tree_uninteresting(commit->tree);
 204                        if (!(obj->flags & SHOWN)) {
 205                                obj->flags |= SHOWN;
 206                                show_edge(commit);
 207                        }
 208                }
 209        }
 210}
 211
 212static void add_pending_tree(struct rev_info *revs, struct tree *tree)
 213{
 214        add_pending_object(revs, &tree->object, "");
 215}
 216
 217static void traverse_trees_and_blobs(struct rev_info *revs,
 218                                     struct strbuf *base,
 219                                     show_object_fn show_object,
 220                                     void *show_data,
 221                                     filter_object_fn filter_fn,
 222                                     void *filter_data)
 223{
 224        int i;
 225
 226        assert(base->len == 0);
 227
 228        for (i = 0; i < revs->pending.nr; i++) {
 229                struct object_array_entry *pending = revs->pending.objects + i;
 230                struct object *obj = pending->item;
 231                const char *name = pending->name;
 232                const char *path = pending->path;
 233                if (obj->flags & (UNINTERESTING | SEEN))
 234                        continue;
 235                if (obj->type == OBJ_TAG) {
 236                        obj->flags |= SEEN;
 237                        show_object(obj, name, show_data);
 238                        continue;
 239                }
 240                if (!path)
 241                        path = "";
 242                if (obj->type == OBJ_TREE) {
 243                        process_tree(revs, (struct tree *)obj, show_object,
 244                                     base, path, show_data,
 245                                     filter_fn, filter_data);
 246                        continue;
 247                }
 248                if (obj->type == OBJ_BLOB) {
 249                        process_blob(revs, (struct blob *)obj, show_object,
 250                                     base, path, show_data,
 251                                     filter_fn, filter_data);
 252                        continue;
 253                }
 254                die("unknown pending object %s (%s)",
 255                    oid_to_hex(&obj->oid), name);
 256        }
 257        object_array_clear(&revs->pending);
 258}
 259
 260static void do_traverse(struct rev_info *revs,
 261                        show_commit_fn show_commit,
 262                        show_object_fn show_object,
 263                        void *show_data,
 264                        filter_object_fn filter_fn,
 265                        void *filter_data)
 266{
 267        struct commit *commit;
 268        struct strbuf csp; /* callee's scratch pad */
 269        strbuf_init(&csp, PATH_MAX);
 270
 271        while ((commit = get_revision(revs)) != NULL) {
 272                /*
 273                 * an uninteresting boundary commit may not have its tree
 274                 * parsed yet, but we are not going to show them anyway
 275                 */
 276                if (commit->tree)
 277                        add_pending_tree(revs, commit->tree);
 278                show_commit(commit, show_data);
 279
 280                if (revs->tree_blobs_in_commit_order)
 281                        /*
 282                         * NEEDSWORK: Adding the tree and then flushing it here
 283                         * needs a reallocation for each commit. Can we pass the
 284                         * tree directory without allocation churn?
 285                         */
 286                        traverse_trees_and_blobs(revs, &csp,
 287                                                 show_object, show_data,
 288                                                 filter_fn, filter_data);
 289        }
 290        traverse_trees_and_blobs(revs, &csp,
 291                                 show_object, show_data,
 292                                 filter_fn, filter_data);
 293        strbuf_release(&csp);
 294}
 295
 296void traverse_commit_list(struct rev_info *revs,
 297                          show_commit_fn show_commit,
 298                          show_object_fn show_object,
 299                          void *show_data)
 300{
 301        do_traverse(revs, show_commit, show_object, show_data, NULL, NULL);
 302}
 303
 304void traverse_commit_list_filtered(
 305        struct list_objects_filter_options *filter_options,
 306        struct rev_info *revs,
 307        show_commit_fn show_commit,
 308        show_object_fn show_object,
 309        void *show_data,
 310        struct oidset *omitted)
 311{
 312        filter_object_fn filter_fn = NULL;
 313        filter_free_fn filter_free_fn = NULL;
 314        void *filter_data = NULL;
 315
 316        filter_data = list_objects_filter__init(omitted, filter_options,
 317                                                &filter_fn, &filter_free_fn);
 318        do_traverse(revs, show_commit, show_object, show_data,
 319                    filter_fn, filter_data);
 320        if (filter_data && filter_free_fn)
 321                filter_free_fn(filter_data);
 322}