]> git.ipfire.org Git - thirdparty/git.git/blobdiff - read-cache.c
read-cache: move shared add/checkout/commit code
[thirdparty/git.git] / read-cache.c
index f4c31a68c85a62705c370b0a34bf021e91538de6..bfbd531ea60d4c374faddfb580e76c0826fe69f9 100644 (file)
@@ -5,6 +5,7 @@
  */
 #include "cache.h"
 #include "alloc.h"
+#include "bulk-checkin.h"
 #include "config.h"
 #include "date.h"
 #include "diff.h"
@@ -26,6 +27,7 @@
 #include "mem-pool.h"
 #include "object-name.h"
 #include "resolve-undo.h"
+#include "revision.h"
 #include "run-command.h"
 #include "strbuf.h"
 #include "trace2.h"
@@ -3806,3 +3808,240 @@ void prefetch_cache_entries(const struct index_state *istate,
                                   to_fetch.oid, to_fetch.nr);
        oid_array_clear(&to_fetch);
 }
+
+static int read_one_entry_opt(struct index_state *istate,
+                             const struct object_id *oid,
+                             struct strbuf *base,
+                             const char *pathname,
+                             unsigned mode, int opt)
+{
+       int len;
+       struct cache_entry *ce;
+
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
+
+       len = strlen(pathname);
+       ce = make_empty_cache_entry(istate, base->len + len);
+
+       ce->ce_mode = create_ce_mode(mode);
+       ce->ce_flags = create_ce_flags(1);
+       ce->ce_namelen = base->len + len;
+       memcpy(ce->name, base->buf, base->len);
+       memcpy(ce->name + base->len, pathname, len+1);
+       oidcpy(&ce->oid, oid);
+       return add_index_entry(istate, ce, opt);
+}
+
+static int read_one_entry(const struct object_id *oid, struct strbuf *base,
+                         const char *pathname, unsigned mode,
+                         void *context)
+{
+       struct index_state *istate = context;
+       return read_one_entry_opt(istate, oid, base, pathname,
+                                 mode,
+                                 ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+}
+
+/*
+ * This is used when the caller knows there is no existing entries at
+ * the stage that will conflict with the entry being added.
+ */
+static int read_one_entry_quick(const struct object_id *oid, struct strbuf *base,
+                               const char *pathname, unsigned mode,
+                               void *context)
+{
+       struct index_state *istate = context;
+       return read_one_entry_opt(istate, oid, base, pathname,
+                                 mode, ADD_CACHE_JUST_APPEND);
+}
+
+/*
+ * Read the tree specified with --with-tree option
+ * (typically, HEAD) into stage #1 and then
+ * squash them down to stage #0.  This is used for
+ * --error-unmatch to list and check the path patterns
+ * that were given from the command line.  We are not
+ * going to write this index out.
+ */
+void overlay_tree_on_index(struct index_state *istate,
+                          const char *tree_name, const char *prefix)
+{
+       struct tree *tree;
+       struct object_id oid;
+       struct pathspec pathspec;
+       struct cache_entry *last_stage0 = NULL;
+       int i;
+       read_tree_fn_t fn = NULL;
+       int err;
+
+       if (repo_get_oid(the_repository, tree_name, &oid))
+               die("tree-ish %s not found.", tree_name);
+       tree = parse_tree_indirect(&oid);
+       if (!tree)
+               die("bad tree-ish %s", tree_name);
+
+       /* Hoist the unmerged entries up to stage #3 to make room */
+       /* TODO: audit for interaction with sparse-index. */
+       ensure_full_index(istate);
+       for (i = 0; i < istate->cache_nr; i++) {
+               struct cache_entry *ce = istate->cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= CE_STAGEMASK;
+       }
+
+       if (prefix) {
+               static const char *(matchbuf[1]);
+               matchbuf[0] = NULL;
+               parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC,
+                              PATHSPEC_PREFER_CWD, prefix, matchbuf);
+       } else
+               memset(&pathspec, 0, sizeof(pathspec));
+
+       /*
+        * See if we have cache entry at the stage.  If so,
+        * do it the original slow way, otherwise, append and then
+        * sort at the end.
+        */
+       for (i = 0; !fn && i < istate->cache_nr; i++) {
+               const struct cache_entry *ce = istate->cache[i];
+               if (ce_stage(ce) == 1)
+                       fn = read_one_entry;
+       }
+
+       if (!fn)
+               fn = read_one_entry_quick;
+       err = read_tree(the_repository, tree, &pathspec, fn, istate);
+       clear_pathspec(&pathspec);
+       if (err)
+               die("unable to read tree entries %s", tree_name);
+
+       /*
+        * Sort the cache entry -- we need to nuke the cache tree, though.
+        */
+       if (fn == read_one_entry_quick) {
+               cache_tree_free(&istate->cache_tree);
+               QSORT(istate->cache, istate->cache_nr, cmp_cache_name_compare);
+       }
+
+       for (i = 0; i < istate->cache_nr; i++) {
+               struct cache_entry *ce = istate->cache[i];
+               switch (ce_stage(ce)) {
+               case 0:
+                       last_stage0 = ce;
+                       /* fallthru */
+               default:
+                       continue;
+               case 1:
+                       /*
+                        * If there is stage #0 entry for this, we do not
+                        * need to show it.  We use CE_UPDATE bit to mark
+                        * such an entry.
+                        */
+                       if (last_stage0 &&
+                           !strcmp(last_stage0->name, ce->name))
+                               ce->ce_flags |= CE_UPDATE;
+               }
+       }
+}
+
+struct update_callback_data {
+       struct index_state *index;
+       int include_sparse;
+       int flags;
+       int add_errors;
+};
+
+static int fix_unmerged_status(struct diff_filepair *p,
+                              struct update_callback_data *data)
+{
+       if (p->status != DIFF_STATUS_UNMERGED)
+               return p->status;
+       if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL) && !p->two->mode)
+               /*
+                * This is not an explicit add request, and the
+                * path is missing from the working tree (deleted)
+                */
+               return DIFF_STATUS_DELETED;
+       else
+               /*
+                * Either an explicit add request, or path exists
+                * in the working tree.  An attempt to explicitly
+                * add a path that does not exist in the working tree
+                * will be caught as an error by the caller immediately.
+                */
+               return DIFF_STATUS_MODIFIED;
+}
+
+static void update_callback(struct diff_queue_struct *q,
+                           struct diff_options *opt UNUSED, void *cbdata)
+{
+       int i;
+       struct update_callback_data *data = cbdata;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               const char *path = p->one->path;
+
+               if (!data->include_sparse &&
+                   !path_in_sparse_checkout(path, data->index))
+                       continue;
+
+               switch (fix_unmerged_status(p, data)) {
+               default:
+                       die(_("unexpected diff status %c"), p->status);
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_TYPE_CHANGED:
+                       if (add_file_to_index(data->index, path, data->flags)) {
+                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
+                                       die(_("updating files failed"));
+                               data->add_errors++;
+                       }
+                       break;
+               case DIFF_STATUS_DELETED:
+                       if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
+                               break;
+                       if (!(data->flags & ADD_CACHE_PRETEND))
+                               remove_file_from_index(data->index, path);
+                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
+                               printf(_("remove '%s'\n"), path);
+                       break;
+               }
+       }
+}
+
+int add_files_to_cache(struct repository *repo, const char *prefix,
+                      const struct pathspec *pathspec, int include_sparse,
+                      int flags)
+{
+       struct update_callback_data data;
+       struct rev_info rev;
+
+       memset(&data, 0, sizeof(data));
+       data.index = repo->index;
+       data.include_sparse = include_sparse;
+       data.flags = flags;
+
+       repo_init_revisions(repo, &rev, prefix);
+       setup_revisions(0, NULL, &rev, NULL);
+       if (pathspec)
+               copy_pathspec(&rev.prune_data, pathspec);
+       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = update_callback;
+       rev.diffopt.format_callback_data = &data;
+       rev.diffopt.flags.override_submodule_config = 1;
+       rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
+
+       /*
+        * Use an ODB transaction to optimize adding multiple objects.
+        * This function is invoked from commands other than 'add', which
+        * may not have their own transaction active.
+        */
+       begin_odb_transaction();
+       run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+       end_odb_transaction();
+
+       release_revisions(&rev);
+       return !!data.add_errors;
+}