]> git.ipfire.org Git - thirdparty/git.git/commitdiff
shallow: set borders which are all reachable after clone shallow since
authorSamo Pogačnik <samo_pogacnik@t-2.net>
Sun, 23 Nov 2025 19:35:52 +0000 (19:35 +0000)
committerJunio C Hamano <gitster@pobox.com>
Mon, 24 Nov 2025 05:35:17 +0000 (21:35 -0800)
When shallow cloning based on a date, it happens that not all
shallow border commits are reachable.

Original implementation of a generic shallow boundary finder
based on rev-list sets a commit (from the initial list of border
commit candidates) to be the border commit as soon as it finds one
of its parentis that wasn't on the list of initial candidates. This
results in a successful shallow clone, where some of its declared
border commits may not be reachable and they would not actually exist
in the cloned repository. Thus the result may contradict existing
comment in the code, which correctly states that such commmit should
not be considered border.

One can inspect such case by running the added test scenario:
- 'clone shallow since all borders reachable'

The modified implementation of a generic shallow boundary finder
based on rev-list ensures that all shallow border commits are reachable
also after being grafted. This is achieved by inspecting all parents
of each initial border commit candidate. The border commit candidate
is set border only when all its parents wern't on the initial list of
candidates. Otherwise the border commit candidate is not set as border
however its parents that weren't on the list of candidates are set as
borders.

Signed-off-by: Samo Pogačnik <samo_pogacnik@t-2.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
shallow.c
t/t5500-fetch-pack.sh

index d9cd4e219cb07dcce18356668420362086deb745..a12c031ce8605fdd5da96a67d1d8185928fbdbb7 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -251,21 +251,57 @@ struct commit_list *get_shallow_commits_by_rev_list(struct strvec *argv,
         * commit A is processed first, then commit B, whose parent is
         * A, later. If NOT_SHALLOW on A is cleared at step 1, B
         * itself is considered border at step 2, which is incorrect.
+        *
+        * We must also consider that B has multiple parents which may
+        * not all be marked NOT_SHALLOW (as they weren't traversed into
+        * the not_shallow_list from revs in the first place). Because of
+        * that an additional step is required to reconsider B as border.
+        * A commit from the not_shallow_list is considered border only
+        * when ALL its parents weren't on the not_shallow_list.
+        * When one or more parents of a commit from the not_shellow_list
+        * also come from that list, the commit is not considered border,
+        * but its non-listed parents are considered border commits.
+        *
+        * The general processing goes like this:
+        * 1. Above we've painted the whole not_shallow_list of commits
+        *    NOT_SHALLOW.
+        * 2. For each commit from the not_shallow_list (the code below)
+        *    we paint SHALLOW this commit and its parent for all its
+        *    parents that had not yet been painted NOT_SHALLOW.
+        * 3. Commits with all parents being painted only SHALLOW remain
+        *    shallow and are being added to result list.
+        * 4. Commits without all parents being painted only SHALLOW are
+        *    being excluded as borders, however their parents painted only
+        *    SHALLOW are being added to the result borders list.
         */
        for (p = not_shallow_list; p; p = p->next) {
                struct commit *c = p->item;
                struct commit_list *parent;
+               int must_not_be_shallow = 0;
 
                if (repo_parse_commit(the_repository, c))
                        die("unable to parse commit %s",
                            oid_to_hex(&c->object.oid));
 
                for (parent = c->parents; parent; parent = parent->next)
-                       if (!(parent->item->object.flags & not_shallow_flag)) {
+                       if (parent->item->object.flags & not_shallow_flag) {
+                               must_not_be_shallow = 1;
+                       } else {
                                c->object.flags |= shallow_flag;
-                               commit_list_insert(c, &result);
-                               break;
+                               parent->item->object.flags |= shallow_flag;
                        }
+               if (must_not_be_shallow) {
+                       c->object.flags &= ~shallow_flag;
+                       for (parent = c->parents; parent; parent = parent->next)
+                               if (parent->item->object.flags & shallow_flag) {
+                                       parent->item->object.flags |= not_shallow_flag;
+                                       commit_list_insert(parent->item, &result);
+                               }
+               } else {
+                       for (parent = c->parents; parent; parent = parent->next)
+                               parent->item->object.flags &= ~shallow_flag;
+                       commit_list_insert(c, &result);
+               }
        }
        free_commit_list(not_shallow_list);
 
index 2677cd5faa8253131a7b36cb08c20e6f223099e6..12209887fb664d4a6cd1c19893477c192b9deb83 100755 (executable)
@@ -904,6 +904,25 @@ test_expect_success 'shallow since with commit graph and already-seen commit' '
        )
 '
 
+test_expect_success 'clone shallow since all borders reachable' '
+       test_create_repo shallow-since-all-borders-reachable &&
+       (
+       rm -rf shallow123 &&
+       cd shallow-since-all-borders-reachable &&
+       GIT_COMMITTER_DATE="2025-08-19 12:34:56" git commit --allow-empty -m one &&
+       GIT_COMMITTER_DATE="2025-08-20 12:34:56" git switch -c branch &&
+       GIT_COMMITTER_DATE="2025-08-21 12:34:56" git commit --allow-empty -m two &&
+       GIT_COMMITTER_DATE="2025-08-22 12:34:56" git commit --allow-empty -m three &&
+       GIT_COMMITTER_DATE="2025-08-23 12:34:56" git switch main &&
+       GIT_COMMITTER_DATE="2025-08-24 12:34:56" git merge branch --no-ff &&
+       GIT_COMMITTER_DATE="2025-08-26 12:34:56" git clone --shallow-since "2025-08-21 12:34:56" "file://$(pwd)/." ../shallow123 &&
+       cd ../shallow123 &&
+       echo "Shallow borders:" &&
+       cat .git/shallow &&
+       $(for commit in $(cat .git/shallow); do git rev-list $commit 1>/dev/null || exit 1; done)
+       )
+'
+
 test_expect_success 'shallow clone exclude tag two' '
        test_create_repo shallow-exclude &&
        (