]> git.ipfire.org Git - thirdparty/git.git/commitdiff
read-cache: submodule add need --force given ignore=all configuration
authorClaus Schneider(Eficode) <claus.schneider@eficode.com>
Fri, 6 Feb 2026 13:22:57 +0000 (13:22 +0000)
committerJunio C Hamano <gitster@pobox.com>
Fri, 6 Feb 2026 17:43:26 +0000 (09:43 -0800)
Submodules configured with ignore=all are now skipped during add operations
unless overridden by --force and the submodule path is explicitly specified.

A message is printed (like ignored files) guiding the user to use the
--force flag if the user explicitly wants to update the submodule reference.

The reason for the change is to support branch tracking in submodules
with configuration `submdule.<name>.branch` or similar workflows where the
user is not interested in tracking each update of the sha1 in the submdule.
You can additionally set `submodule.<name>.ignore=all` and the `git status`
will state nothing and, with this patch, the `git add` does not either - as
the default behaviour. This patch changes the workflow to a more logical
behaviour and similar to workflow for ignored files.

The patch gives more scenarios for submodules to be used effectively with
less friction similar to the "repo" tool. A submodule can be added for many
different reasons than a hard dependency. It can be added as loosely
coupled dependencies whereas the user wants the latest based on the
configuration `submoule.<name>.branch`, but are not interested to track
each commit in the `super-repo`. Currently it gives friction of handling
conflicts between branches even the sha1's are fast-forward and the user
just wants the latest in any way. The user can still add a sha1 explicitly
to track updates.

Signed-off-by: Claus Schneider(Eficode) <claus.schneider@eficode.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
read-cache.c

index 5349b94e59176983e9de3bde94f880c7e117aebc..b5ed66f6b79e9e9184ac1152d39a1c86de1cab7f 100644 (file)
@@ -47,6 +47,9 @@
 #include "csum-file.h"
 #include "promisor-remote.h"
 #include "hook.h"
+#include "submodule.h"
+#include "submodule-config.h"
+#include "advice.h"
 
 /* Mask for the name length in ce_flags in the on-disk index */
 
@@ -3907,8 +3910,68 @@ static int fix_unmerged_status(struct diff_filepair *p,
                return DIFF_STATUS_MODIFIED;
 }
 
+static int skip_submodule(const char *path,
+                                               struct repository *repo,
+                                               struct pathspec *pathspec,
+                                               int ignored_too)
+{
+    struct stat st;
+    const struct submodule *sub;
+    int pathspec_matches = 0;
+    int ps_i;
+    char *norm_pathspec = NULL;
+
+    /* Only consider if path is a directory */
+    if (lstat(path, &st) || !S_ISDIR(st.st_mode))
+               return 0;
+
+    /* Check if it's a submodule with ignore=all */
+    sub = submodule_from_path(repo, null_oid(the_hash_algo), path);
+    if (!sub || !sub->name || !sub->ignore || strcmp(sub->ignore, "all"))
+               return 0;
+
+    trace_printf("ignore=all: %s\n", path);
+    trace_printf("pathspec %s\n", (pathspec && pathspec->nr)
+                                                                       ? "has pathspec"
+                                                                       : "no pathspec");
+
+    /* Check if submodule path is explicitly mentioned in pathspec */
+    if (pathspec) {
+               for (ps_i = 0; ps_i < pathspec->nr; ps_i++) {
+                       const char *m = pathspec->items[ps_i].match;
+                       if (!m)
+                               continue;
+                       norm_pathspec = xstrdup(m);
+                       strip_dir_trailing_slashes(norm_pathspec);
+                       if (!strcmp(path, norm_pathspec)) {
+                               pathspec_matches = 1;
+                               FREE_AND_NULL(norm_pathspec);
+                               break;
+                       }
+                       FREE_AND_NULL(norm_pathspec);
+               }
+    }
+
+    /* If explicitly matched and forced, allow adding */
+    if (pathspec_matches) {
+               if (ignored_too && ignored_too > 0) {
+                       trace_printf("Add submodule due to --force: %s\n", path);
+                       return 0;
+               } else {
+                       advise_if_enabled(ADVICE_ADD_IGNORED_FILE,
+                               _("Skipping submodule due to ignore=all: %s\n"
+                                       "Use --force if you really want to add the submodule."), path);
+                       return 1;
+               }
+    }
+
+    /* No explicit pathspec match -> skip silently */
+    trace_printf("Pathspec to submodule does not match explicitly: %s\n", path);
+    return 1;
+}
+
 static void update_callback(struct diff_queue_struct *q,
-                           struct diff_options *opt UNUSED, void *cbdata)
+                                                       struct diff_options *opt UNUSED, void *cbdata)
 {
        int i;
        struct update_callback_data *data = cbdata;
@@ -3918,14 +3981,19 @@ static void update_callback(struct diff_queue_struct *q,
                const char *path = p->one->path;
 
                if (!data->include_sparse &&
-                   !path_in_sparse_checkout(path, data->index))
+                       !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: {
+               case DIFF_STATUS_TYPE_CHANGED:
+                       if (skip_submodule(path, data->repo,
+                                                               data->pathspec,
+                                                               data->ignored_too))
+                               continue;
+
                        if (add_file_to_index(data->index, path, data->flags)) {
                                if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
                                        die(_("updating files failed"));