Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package git-repo for openSUSE:Factory 
checked in at 2026-08-06 16:26:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/git-repo (Old)
 and      /work/SRC/openSUSE:Factory/.git-repo.new.16738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "git-repo"

Thu Aug  6 16:26:46 2026 rev:19 rq:1369939 version:2.66

Changes:
--------
--- /work/SRC/openSUSE:Factory/git-repo/git-repo.changes        2026-07-14 
13:57:07.073779680 +0200
+++ /work/SRC/openSUSE:Factory/.git-repo.new.16738/git-repo.changes     
2026-08-06 16:28:45.546149234 +0200
@@ -1,0 +2,17 @@
+Wed Aug 05 16:32:25 UTC 2026 - BenoĆ®t Monin <[email protected]>
+
+- Update to version 2.66:
+  * project: preserve -c optimization when revision is a SHA-1
+  * command: Respect smart sync override declaratively by default
+  * project: derive HEAD fallback from git's own default branch
+  * project: make GetHead file-read fallback reftable-aware
+  * sync: allow syncing groups with repo sync -g group
+  * git_superproject: don't filter rewritten manifest
+  * sync: Deprecate fetch-submodules flag names
+  * hooks: pass yes flag when available
+  * project: Skip superproject upstream check for MetaProjects
+  * sync: Add CLI flag for globally disabling submodule fetch
+  * color: Replace anonymous sentinel with named class
+  * rebase: Resolve revisionExpr to tracking branch for --onto-manifest
+
+-------------------------------------------------------------------

Old:
----
  git-repo-2.65.tar.xz

New:
----
  git-repo-2.66.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ git-repo.spec ++++++
--- /var/tmp/diff_new_pack.3PaNvU/_old  2026-08-06 16:28:46.566184837 +0200
+++ /var/tmp/diff_new_pack.3PaNvU/_new  2026-08-06 16:28:46.570184977 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           git-repo
-Version:        2.65
+Version:        2.66
 Release:        0
 Summary:        The Multiple Git Repository Tool
 License:        Apache-2.0

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.3PaNvU/_old  2026-08-06 16:28:46.614186513 +0200
+++ /var/tmp/diff_new_pack.3PaNvU/_new  2026-08-06 16:28:46.618186653 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://gerrit.googlesource.com/git-repo</param>
-              <param 
name="changesrevision">35bbf701d04de5c6a71937279bc3d16f6ce36808</param></service></servicedata>
+              <param 
name="changesrevision">d9da609d8c120bb882a43196a4a6b7f183418304</param></service></servicedata>
 (No newline at EOF)
 

++++++ git-repo-2.65.tar.xz -> git-repo-2.66.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/color.py new/git-repo-2.66/color.py
--- old/git-repo-2.65/color.py  2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/color.py  2026-08-05 00:00:19.000000000 +0200
@@ -84,9 +84,14 @@
 
 DEFAULT = None
 
+
+class _CheckConsoleSentinel:
+    """Sentinel for checking console coloring."""
+
+
 # Placholder value that indicates we need to check if the user is in an
 # interactive terminal session to determine if we turn on color or not.
-_CHECK_CONSOLE = object()
+_CHECK_CONSOLE = _CheckConsoleSentinel()
 
 # https://git-scm.com/docs/git-config#Documentation/git-config.txt-colorui
 _CONFIG_TO_COLOR_SETTING = {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/command.py new/git-repo-2.66/command.py
--- old/git-repo-2.65/command.py        2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/command.py        2026-08-05 00:00:19.000000000 +0200
@@ -17,6 +17,7 @@
 import optparse
 import os
 import re
+from typing import TYPE_CHECKING
 
 from error import InvalidProjectGroupsError
 from error import NoSuchProjectError
@@ -25,6 +26,10 @@
 import progress
 
 
+if TYPE_CHECKING:
+    from project import Project
+
+
 # Are we generating man-pages?
 GENERATE_MANPAGES = os.environ.get("_REPO_GENERATE_MANPAGES_") == " indeed! "
 
@@ -61,6 +66,10 @@
     # command to show short-vs-full summaries.
     COMMON = False
 
+    # Whether this command should respect the smart sync override manifest if
+    # it exists.
+    RESPECT_SMART_SYNC_OVERRIDE = True
+
     # Whether this command supports running in parallel. If greater than 0,
     # it is the number of parallel jobs to default to.
     PARALLEL_JOBS = None
@@ -242,6 +251,12 @@
             # from the user's perspective.
             opt.outer_manifest = True
 
+        if self.RESPECT_SMART_SYNC_OVERRIDE:
+            if self.manifest:
+                self.TryOverrideManifestWithSmartSync(self.manifest)
+            if self.outer_manifest and self.outer_manifest != self.manifest:
+                self.TryOverrideManifestWithSmartSync(self.outer_manifest)
+
     def ValidateOptions(self, opt, args):
         """Validate the user options & arguments before executing.
 
@@ -375,7 +390,7 @@
         manifest=None,
         groups="",
         missing_ok=False,
-        submodules_ok=False,
+        submodules_ok=None,
         all_manifests=False,
     ):
         """A list of projects that match the arguments.
@@ -385,7 +400,9 @@
             manifest: an XmlManifest, the manifest to use, or None for default.
             groups: a string, the manifest groups in use.
             missing_ok: a boolean, whether to allow missing projects.
-            submodules_ok: a boolean, whether to allow submodules.
+            submodules_ok: whether to allow submodules. True allows them for
+                all projects, False disallows them for all projects, and None
+                defers to each project's sync-s setting.
             all_manifests: a boolean, if True then all manifests and
                 submanifests are used. If False, then only the local
                 (sub)manifest is used.
@@ -403,6 +420,11 @@
             all_projects_list = manifest.projects
         result = []
 
+        def should_include_submodules(project: "Project") -> bool:
+            if submodules_ok is None:
+                return project.sync_s
+            return submodules_ok
+
         if not groups:
             groups = manifest.GetManifestGroupsStr()
         groups = [x for x in re.split(r"[,\s]+", groups) if x]
@@ -410,7 +432,7 @@
         if not args:
             derived_projects = {}
             for project in all_projects_list:
-                if submodules_ok or project.sync_s:
+                if should_include_submodules(project):
                     derived_projects.update(
                         (p.RelPath(local=False), p)
                         for p in project.GetDerivedSubprojects()
@@ -452,7 +474,7 @@
                     if (
                         project
                         and not project.Derived
-                        and (submodules_ok or project.sync_s)
+                        and should_include_submodules(project)
                     ):
                         search_again = False
                         for subproject in project.GetDerivedSubprojects():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/completion.zsh 
new/git-repo-2.66/completion.zsh
--- old/git-repo-2.65/completion.zsh    2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/completion.zsh    2026-08-05 00:00:19.000000000 +0200
@@ -322,7 +322,10 @@
         '--no-clone-bundle[Do not use clone bundle]' \
         '(-u 
--manifest-server-username)'{-u,--manifest-server-username=}'[Username for 
manifest server]:username:' \
         '(-p 
--manifest-server-password)'{-p,--manifest-server-password=}'[Password for 
manifest server]:password:' \
-        '--fetch-submodules[Fetch submodules]' \
+        '--recurse-submodules[Sync submodules]' \
+        '--no-recurse-submodules[Do not sync submodules]' \
+        '--fetch-submodules[Deprecated alias for --recurse-submodules]' \
+        '--no-fetch-submodules[Deprecated alias for --no-recurse-submodules]' \
         '--use-superproject[Use superproject]' \
         '--no-use-superproject[Do not use superproject]' \
         '--tags[Sync tags]' \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/docs/repo-hooks.md 
new/git-repo-2.66/docs/repo-hooks.md
--- old/git-repo-2.65/docs/repo-hooks.md        2026-07-02 00:59:45.000000000 
+0200
+++ new/git-repo-2.66/docs/repo-hooks.md        2026-08-05 00:00:19.000000000 
+0200
@@ -83,6 +83,13 @@
 the user.  Although user interaction is discouraged in the common case, it can
 be useful when deploying automatic fixes.
 
+### Safe Prompts
+
+If the repo command that triggered the hook supports a "yes" option (e.g.,
+`repo upload --yes`), this option is propagated to the hook's `main` function
+as `yes` parameter (defaulting to `False`).  Hooks can use this to bypass
+interactive confirmation prompts when they can automatically fix issues.
+
 ### Shebang Handling
 
 *** note
@@ -119,7 +126,7 @@
 The `pre-upload.py` file should be defined like:
 
 ```py
-def main(project_list, worktree_list=None, **kwargs):
+def main(project_list, worktree_list=None, yes=False, **kwargs):
     """Main function invoked directly by repo.
 
     We must use the name "main" as that is what repo requires.
@@ -130,6 +137,8 @@
           project_list, so that each entry in project_list matches with a
           directory in worktree_list.  If None, we will attempt to calculate
           the directories automatically.
+      yes: Whether to answer yes to all safe prompts (see
+          [Safe Prompts](#safe-prompts)).
       kwargs: Leave this here for forward-compatibility.
     """
 ```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/git_superproject.py 
new/git-repo-2.66/git_superproject.py
--- old/git-repo-2.65/git_superproject.py       2026-07-02 00:59:45.000000000 
+0200
+++ new/git-repo-2.66/git_superproject.py       2026-08-05 00:00:19.000000000 
+0200
@@ -473,7 +473,7 @@
             )
             return None
         manifest_str = self._manifest.ToXml(
-            filter_groups=self._manifest.GetManifestGroupsStr(),
+            filter_groups="all",
             omit_local=True,
         ).toxml()
         manifest_path = self._manifest_path
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/hooks.py new/git-repo-2.66/hooks.py
--- old/git-repo-2.65/hooks.py  2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/hooks.py  2026-08-05 00:00:19.000000000 +0200
@@ -68,6 +68,7 @@
         allow_all_hooks=False,
         ignore_hooks=False,
         abort_if_user_denies=False,
+        yes=False,
     ):
         """RepoHook constructor.
 
@@ -89,6 +90,7 @@
             ignore_hooks: If True, then 'Do not abort action if hooks fail'.
             abort_if_user_denies: If True, we'll abort running the hook if the
                 user doesn't allow us to run the hook.
+            yes: If True, then 'Yes' is assumed for any prompts.
         """
         self._hook_type = hook_type
         self._hooks_project = hooks_project
@@ -99,6 +101,7 @@
         self._allow_all_hooks = allow_all_hooks
         self._ignore_hooks = ignore_hooks
         self._abort_if_user_denies = abort_if_user_denies
+        self._yes = yes
 
         # Store the full path to the script for convenience.
         self._script_fullpath = None
@@ -374,8 +377,11 @@
             #   def main(project_list, **kwargs):
             #
             # This allows us to later expand the API without breaking old 
hooks.
-            kwargs = kwargs.copy()
-            kwargs["hook_should_take_kwargs"] = True
+            kwargs = {
+                **kwargs,
+                "hook_should_take_kwargs": True,
+                "yes": self._yes,
+            }
 
             # See what version of python the hook has been written against.
             data = open(self._script_fullpath).read()
@@ -497,6 +503,7 @@
                     "origin"
                 ).url,
                 "bug_url": manifest.contactinfo.bugurl,
+                "yes": getattr(opt, "yes", False),
             }
         )
         return cls(*args, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/man/repo-smartsync.1 
new/git-repo-2.66/man/repo-smartsync.1
--- old/git-repo-2.65/man/repo-smartsync.1      2026-07-02 00:59:45.000000000 
+0200
+++ new/git-repo-2.66/man/repo-smartsync.1      2026-08-05 00:00:19.000000000 
+0200
@@ -1,5 +1,5 @@
 .\" DO NOT MODIFY THIS FILE!  It was generated by help2man.
-.TH REPO "1" "June 2026" "repo smartsync" "Repo Manual"
+.TH REPO "1" "July 2026" "repo smartsync" "Repo Manual"
 .SH NAME
 repo \- repo smartsync - manual page for repo smartsync
 .SH SYNOPSIS
@@ -68,6 +68,9 @@
 \fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
 temporary manifest to use for this sync
 .TP
+\fB\-g\fR GROUP, \fB\-\-groups\fR=\fI\,GROUP\/\fR
+sync projects matching the specific groups.  Not persistent unlike when used 
on init
+.TP
 \fB\-\-clone\-bundle\fR
 enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
 .TP
@@ -80,8 +83,11 @@
 \fB\-p\fR MANIFEST_SERVER_PASSWORD, 
\fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR
 password to authenticate with the manifest server
 .TP
-\fB\-\-fetch\-submodules\fR
-fetch submodules from server
+\fB\-\-recurse\-submodules\fR
+sync submodules from server
+.TP
+\fB\-\-no\-recurse\-submodules\fR
+don't sync submodules from server
 .TP
 \fB\-\-use\-superproject\fR
 use the manifest superproject to sync projects; implies \fB\-c\fR
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/man/repo-sync.1 
new/git-repo-2.66/man/repo-sync.1
--- old/git-repo-2.65/man/repo-sync.1   2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/man/repo-sync.1   2026-08-05 00:00:19.000000000 +0200
@@ -1,5 +1,5 @@
 .\" DO NOT MODIFY THIS FILE!  It was generated by help2man.
-.TH REPO "1" "June 2026" "repo sync" "Repo Manual"
+.TH REPO "1" "July 2026" "repo sync" "Repo Manual"
 .SH NAME
 repo \- repo sync - manual page for repo sync
 .SH SYNOPSIS
@@ -68,6 +68,9 @@
 \fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
 temporary manifest to use for this sync
 .TP
+\fB\-g\fR GROUP, \fB\-\-groups\fR=\fI\,GROUP\/\fR
+sync projects matching the specific groups.  Not persistent unlike when used 
on init
+.TP
 \fB\-\-clone\-bundle\fR
 enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
 .TP
@@ -80,8 +83,11 @@
 \fB\-p\fR MANIFEST_SERVER_PASSWORD, 
\fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR
 password to authenticate with the manifest server
 .TP
-\fB\-\-fetch\-submodules\fR
-fetch submodules from server
+\fB\-\-recurse\-submodules\fR
+sync submodules from server
+.TP
+\fB\-\-no\-recurse\-submodules\fR
+don't sync submodules from server
 .TP
 \fB\-\-use\-superproject\fR
 use the manifest superproject to sync projects; implies \fB\-c\fR
@@ -212,8 +218,12 @@
 delivery network. This may be necessary if there are problems with the local
 Python HTTP client or proxy configuration, but the Git binary works.
 .PP
-The \fB\-\-fetch\-submodules\fR option enables fetching Git submodules of a 
project from
-server.
+The \fB\-\-recurse\-submodules\fR option enables syncing Git submodules of all 
projects
+from the server. The \fB\-\-no\-recurse\-submodules\fR option disables syncing 
Git
+submodules, even when a project has sync\-s="true" in the manifest.
+.PP
+The \fB\-\-fetch\-submodules\fR and \fB\-\-no\-fetch\-submodules\fR options 
are deprecated aliases
+for \fB\-\-recurse\-submodules\fR and \fB\-\-no\-recurse\-submodules\fR, 
respectively.
 .PP
 The \fB\-c\fR/\-\-current\-branch option can be used to only fetch objects 
that are on the
 branch specified by a project's revision.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/project.py new/git-repo-2.66/project.py
--- old/git-repo-2.65/project.py        2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/project.py        2026-08-05 00:00:19.000000000 +0200
@@ -15,6 +15,7 @@
 import datetime
 import errno
 import filecmp
+import functools
 import glob
 import os
 import platform
@@ -2674,11 +2675,12 @@
             # throws an error.
             revs = [f"{self.revisionExpr}^0"]
             upstream_rev = None
+            use_superproject_for_upstream = self.upstream and (
+                self._UseSuperprojectForUpstream(use_superproject)
+            )
 
             # Only check upstream when using superproject.
-            if self.upstream and git_superproject.UseSuperproject(
-                use_superproject, self.manifest
-            ):
+            if use_superproject_for_upstream:
                 upstream_rev = self.GetRemote().ToLocal(self.upstream)
                 revs.append(upstream_rev)
 
@@ -2692,9 +2694,7 @@
 
             # Only verify upstream relationship for superproject scenarios
             # without affecting plain usage.
-            if self.upstream and git_superproject.UseSuperproject(
-                use_superproject, self.manifest
-            ):
+            if use_superproject_for_upstream:
                 self.bare_git.merge_base(
                     "--is-ancestor",
                     self.revisionExpr,
@@ -2723,6 +2723,16 @@
                     return True
         return False
 
+    def _UseSuperprojectForUpstream(
+        self, use_superproject: Optional[bool] = None
+    ) -> bool:
+        """Whether to include upstream in the immutability check.
+
+        The upstream ancestry check is only meaningful for projects
+        that participate in a superproject relationship.
+        """
+        return git_superproject.UseSuperproject(use_superproject, 
self.manifest)
+
     def _FetchArchive(self, tarpath, cwd=None):
         cmd = ["archive", "-v", "-o", tarpath]
         cmd.append("--remote=%s" % self.remote.url)
@@ -2847,6 +2857,18 @@
 
         return True
 
+    def _GetUpstreamFallback(self) -> Optional[str]:
+        """Resolve a fallback upstream ref when revisionExpr is a SHA-1."""
+        for cand in (
+            self.dest_branch,
+            self.manifest.default.upstreamExpr,
+            self.manifest.default.destBranchExpr,
+            self.manifest.default.revisionExpr,
+        ):
+            if cand and not IsId(cand):
+                return cand
+        return None
+
     def _RemoteFetch(
         self,
         name=None,
@@ -2880,14 +2902,31 @@
             current_branch_only = True
 
         is_sha1 = IsId(self.revisionExpr)
+        upstream = self.upstream
 
         if current_branch_only:
+            if is_sha1 and not depth:
+                # When syncing a specific commit and --depth is not set:
+                # * if upstream is explicitly specified and is not a sha1, 
fetch
+                #   only upstream as users expect only upstream to be fetch.
+                #   Note: The commit might not be in upstream in which case the
+                #   sync will fail.
+                # * otherwise, fetch all branches to make sure we end up with
+                #   the specific commit.
+                if not upstream:
+                    upstream = self._GetUpstreamFallback()
+
+                if upstream:
+                    current_branch_only = not IsId(upstream)
+                else:
+                    current_branch_only = False
+
             if self.revisionExpr.startswith(R_TAGS):
                 # This is a tag and its commit id should never change.
                 tag_name = self.revisionExpr[len(R_TAGS) :]
-            elif self.upstream and self.upstream.startswith(R_TAGS):
+            elif upstream and upstream.startswith(R_TAGS):
                 # This is a tag and its commit id should never change.
-                tag_name = self.upstream[len(R_TAGS) :]
+                tag_name = upstream[len(R_TAGS) :]
 
             if is_sha1 or tag_name is not None:
                 has_shallow = os.path.exists(
@@ -2905,18 +2944,6 @@
                             "persistent ref)" % self.name
                         )
                     return True
-            if is_sha1 and not depth:
-                # When syncing a specific commit and --depth is not set:
-                # * if upstream is explicitly specified and is not a sha1, 
fetch
-                #   only upstream as users expect only upstream to be fetch.
-                #   Note: The commit might not be in upstream in which case the
-                #   sync will fail.
-                # * otherwise, fetch all branches to make sure we end up with
-                #   the specific commit.
-                if self.upstream:
-                    current_branch_only = not IsId(self.upstream)
-                else:
-                    current_branch_only = False
 
         if not name:
             name = self.remote.name
@@ -3028,11 +3055,11 @@
             # Shallow checkout of a specific commit, fetch from that commit and
             # not the heads only as the commit might be deeper in the history.
             spec.append(branch)
-            if self.upstream:
-                spec.append(self.upstream)
+            if upstream:
+                spec.append(upstream)
         else:
             if is_sha1:
-                branch = self.upstream
+                branch = upstream
             if branch is not None and branch.strip():
                 if not branch.startswith("refs/"):
                     branch = R_HEADS + branch
@@ -4389,8 +4416,12 @@
                 except AttributeError:
                     pass
                 if line.startswith("ref: "):
-                    return line[5:-1]
-                return line[:-1]
+                    ref = line[5:-1]
+                else:
+                    ref = line[:-1]
+                if ref == R_HEADS + ".invalid":
+                    raise NoManifestException(path, str(e))
+                return ref
 
         def SetHead(self, ref, message=None):
             cmdv = []
@@ -4650,6 +4681,26 @@
         self._pending_failures = []
 
 
[email protected]_cache(maxsize=None)
+def _DefaultBranchFallback() -> str:
+    """Return the ref to use when remote default branch can't be resolved."""
+
+    def _git(args: List[str]) -> str:
+        p = GitCommand(
+            None,
+            args,
+            capture_stdout=True,
+            capture_stderr=True,
+            log_as_error=False,
+        )
+        return p.stdout.strip() if p.Wait() == 0 else ""
+
+    branch = _git(["var", "GIT_DEFAULT_BRANCH"]) or _git(
+        ["config", "--get", "init.defaultBranch"]
+    )
+    return f"refs/heads/{branch or 'master'}"
+
+
 class MetaProject(Project):
     """A special project housed under .repo."""
 
@@ -4663,7 +4714,7 @@
             worktree=worktree,
             remote=RemoteSpec("origin"),
             relpath=".repo/%s" % name,
-            revisionExpr="refs/heads/master",
+            revisionExpr=_DefaultBranchFallback(),
             revisionId=None,
             groups=None,
         )
@@ -4677,6 +4728,15 @@
                     self.revisionExpr = base
                     self.revisionId = None
 
+    def _UseSuperprojectForUpstream(
+        self, use_superproject: Optional[bool] = None
+    ) -> bool:
+        # MetaProjects (the manifest repo and repo itself) never
+        # participate in a superproject relationship. Returning False
+        # here also avoids loading the manifest during `repo init`,
+        # before manifest.xml has been linked into .repo/.
+        return False
+
     @property
     def HasChanges(self):
         """Has the remote received new commits not yet checked out?"""
@@ -5112,9 +5172,9 @@
                 if is_new:
                     default_branch = self.ResolveRemoteHead()
                     if default_branch is None:
-                        # If the remote doesn't have HEAD configured, default 
to
-                        # master.
-                        default_branch = "refs/heads/master"
+                        # If the remote doesn't have HEAD configured, fall back
+                        # to whatever git uses as its default branch.
+                        default_branch = _DefaultBranchFallback()
                     self.revisionExpr = default_branch
                 else:
                     self.PreSync()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/subcmds/abandon.py 
new/git-repo-2.66/subcmds/abandon.py
--- old/git-repo-2.65/subcmds/abandon.py        2026-07-02 00:59:45.000000000 
+0200
+++ new/git-repo-2.66/subcmds/abandon.py        2026-08-05 00:00:19.000000000 
+0200
@@ -94,7 +94,6 @@
 
     def Execute(self, opt, args):
         nb = args[0].split()
-        self.TryOverrideManifestWithSmartSync()
         err = collections.defaultdict(list)
         success = collections.defaultdict(list)
         aggregate_errors = []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/subcmds/forall.py 
new/git-repo-2.66/subcmds/forall.py
--- old/git-repo-2.65/subcmds/forall.py 2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/subcmds/forall.py 2026-08-05 00:00:19.000000000 +0200
@@ -243,8 +243,6 @@
 
         mirror = self.manifest.IsMirror
 
-        self.TryOverrideManifestWithSmartSync()
-
         if opt.regex:
             projects = self.FindProjects(args, all_manifests=all_trees)
         elif opt.inverse_regex:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/subcmds/info.py 
new/git-repo-2.66/subcmds/info.py
--- old/git-repo-2.65/subcmds/info.py   2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/subcmds/info.py   2026-08-05 00:00:19.000000000 +0200
@@ -147,8 +147,6 @@
         if not opt.this_manifest_only:
             self.manifest = self.manifest.outer_client
 
-        self.TryOverrideManifestWithSmartSync()
-
         output_format = OutputFormat[opt.format.upper()]
         if output_format == OutputFormat.JSON:
             self._ExecuteJson(opt, args)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/subcmds/init.py 
new/git-repo-2.66/subcmds/init.py
--- old/git-repo-2.65/subcmds/init.py   2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/subcmds/init.py   2026-08-05 00:00:19.000000000 +0200
@@ -33,6 +33,7 @@
 
 class Init(InteractiveCommand, MirrorSafeCommand):
     COMMON = True
+    RESPECT_SMART_SYNC_OVERRIDE = False
     MULTI_MANIFEST_SUPPORT = True
     helpSummary = "Initialize a repo client checkout in the current directory"
     helpUsage = """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/subcmds/rebase.py 
new/git-repo-2.66/subcmds/rebase.py
--- old/git-repo-2.65/subcmds/rebase.py 2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/subcmds/rebase.py 2026-08-05 00:00:19.000000000 +0200
@@ -16,7 +16,9 @@
 
 from color import Coloring
 from command import Command
+from error import GitError
 from git_command import GitCommand
+from project import Project
 from repo_logging import RepoLogger
 
 
@@ -30,6 +32,17 @@
         self.fail = self.printer("fail", fg="red")
 
 
+def _ResolveOntoManifest(project: Project) -> str:
+    """Resolve project's revisionExpr to a local tracking branch.
+
+    Falls back to the raw revisionExpr if ToLocal fails or raises GitError.
+    """
+    try:
+        return project.GetRemote().ToLocal(project.revisionExpr)
+    except GitError:
+        return project.revisionExpr
+
+
 class Rebase(Command):
     COMMON = True
     helpSummary = "Rebase local branches on upstream branch"
@@ -162,7 +175,7 @@
             args = common_args[:]
             if opt.onto_manifest:
                 args.append("--onto")
-                args.append(project.revisionExpr)
+                args.append(_ResolveOntoManifest(project))
 
             args.append(upbranch.LocalMerge)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/subcmds/start.py 
new/git-repo-2.66/subcmds/start.py
--- old/git-repo-2.65/subcmds/start.py  2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/subcmds/start.py  2026-08-05 00:00:19.000000000 +0200
@@ -104,7 +104,6 @@
 
     def Execute(self, opt, args):
         nb = args[0]
-        self.TryOverrideManifestWithSmartSync()
         err_projects = []
         err = []
         projects = []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/subcmds/sync.py 
new/git-repo-2.66/subcmds/sync.py
--- old/git-repo-2.65/subcmds/sync.py   2026-07-02 00:59:45.000000000 +0200
+++ new/git-repo-2.66/subcmds/sync.py   2026-08-05 00:00:19.000000000 +0200
@@ -312,6 +312,7 @@
 
 class Sync(Command, MirrorSafeCommand):
     COMMON = True
+    RESPECT_SMART_SYNC_OVERRIDE = False
     MULTI_MANIFEST_SUPPORT = True
     helpSummary = "Update working tree to the latest revision"
     helpUsage = """
@@ -378,8 +379,12 @@
 may be necessary if there are problems with the local Python
 HTTP client or proxy configuration, but the Git binary works.
 
-The --fetch-submodules option enables fetching Git submodules
-of a project from server.
+The --recurse-submodules option enables syncing Git submodules of all projects
+from the server. The --no-recurse-submodules option disables syncing Git
+submodules, even when a project has sync-s="true" in the manifest.
+
+The --fetch-submodules and --no-fetch-submodules options are deprecated aliases
+for --recurse-submodules and --no-recurse-submodules, respectively.
 
 The -c/--current-branch option can be used to only fetch objects that
 are on the branch specified by a project's revision.
@@ -427,6 +432,15 @@
 
     _JOBS_WARN_THRESHOLD = 100
 
+    @staticmethod
+    def _deprecated_submodules_option(option, opt_str, _value, parser):
+        enabled = opt_str == "--fetch-submodules"
+        replacement = (
+            "--recurse-submodules" if enabled else "--no-recurse-submodules"
+        )
+        logger.warning("%s is deprecated; use %s instead", opt_str, 
replacement)
+        setattr(parser.values, option.dest, enabled)
+
     def _Options(self, p, show_smart=True):
         p.add_option(
             "--jobs-network",
@@ -546,6 +560,13 @@
             metavar="NAME.xml",
         )
         p.add_option(
+            "-g",
+            "--groups",
+            help="sync projects matching the specific groups.  Not persistent "
+            "unlike when used on init",
+            metavar="GROUP",
+        )
+        p.add_option(
             "--clone-bundle",
             action="store_true",
             help="enable use of /clone.bundle on HTTP/HTTPS",
@@ -569,9 +590,29 @@
             help="password to authenticate with the manifest server",
         )
         p.add_option(
-            "--fetch-submodules",
+            "--recurse-submodules",
             action="store_true",
-            help="fetch submodules from server",
+            help="sync submodules from server",
+        )
+        p.add_option(
+            "--no-recurse-submodules",
+            dest="recurse_submodules",
+            action="store_false",
+            help="don't sync submodules from server",
+        )
+        p.add_option(
+            "--fetch-submodules",
+            dest="recurse_submodules",
+            action="callback",
+            callback=self._deprecated_submodules_option,
+            help=optparse.SUPPRESS_HELP,
+        )
+        p.add_option(
+            "--no-fetch-submodules",
+            dest="recurse_submodules",
+            action="callback",
+            callback=self._deprecated_submodules_option,
+            help=optparse.SUPPRESS_HELP,
         )
         p.add_option(
             "--use-superproject",
@@ -741,8 +782,9 @@
 
         all_projects = self.GetProjects(
             args,
+            groups=opt.groups,
             missing_ok=True,
-            submodules_ok=opt.fetch_submodules,
+            submodules_ok=opt.recurse_submodules,
             manifest=manifest,
             all_manifests=not opt.this_manifest_only,
         )
@@ -1070,8 +1112,9 @@
                 self._ReloadManifest(None, manifest)
                 all_projects = self.GetProjects(
                     args,
+                    groups=opt.groups,
                     missing_ok=True,
-                    submodules_ok=opt.fetch_submodules,
+                    submodules_ok=opt.recurse_submodules,
                     manifest=manifest,
                     all_manifests=not opt.this_manifest_only,
                 )
@@ -2317,8 +2360,9 @@
 
         all_projects = self.GetProjects(
             args,
+            groups=opt.groups,
             missing_ok=True,
-            submodules_ok=opt.fetch_submodules,
+            submodules_ok=opt.recurse_submodules,
             manifest=manifest,
             all_manifests=not opt.this_manifest_only,
         )
@@ -2980,8 +3024,9 @@
                             self._ReloadManifest(None, manifest)
                             project_list = self.GetProjects(
                                 args,
+                                groups=opt.groups,
                                 missing_ok=True,
-                                submodules_ok=opt.fetch_submodules,
+                                submodules_ok=opt.recurse_submodules,
                                 manifest=manifest,
                                 all_manifests=not opt.this_manifest_only,
                             )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/tests/test_command.py 
new/git-repo-2.66/tests/test_command.py
--- old/git-repo-2.65/tests/test_command.py     2026-07-02 00:59:45.000000000 
+0200
+++ new/git-repo-2.66/tests/test_command.py     2026-08-05 00:00:19.000000000 
+0200
@@ -14,6 +14,8 @@
 
 """Unittests for the command.py module."""
 
+import pytest
+
 from command import Command
 
 
@@ -86,3 +88,32 @@
     projects = cmd.GetProjects([])
 
     assert set(projects) == {project_a, project_b, submodule_a, submodule_b}
+
+
[email protected](
+    "submodules_ok, sync_s, includes_submodule",
+    [
+        (None, False, False),
+        (None, True, True),
+        (True, False, True),
+        (True, True, True),
+        (False, False, False),
+        (False, True, False),
+    ],
+)
+def test_get_projects_submodule_override(
+    submodules_ok, sync_s, includes_submodule
+):
+    """The CLI override takes precedence over a project's sync-s setting."""
+    submodule = FakeProject("submodule", "project/submodule")
+    project = FakeProject(
+        "project",
+        "project",
+        derived_subprojects=[submodule],
+        sync_s=sync_s,
+    )
+    cmd = Command(manifest=FakeManifest([project]))
+
+    projects = cmd.GetProjects([], submodules_ok=submodules_ok)
+
+    assert (submodule in projects) is includes_submodule
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/tests/test_hooks.py 
new/git-repo-2.66/tests/test_hooks.py
--- old/git-repo-2.65/tests/test_hooks.py       2026-07-02 00:59:45.000000000 
+0200
+++ new/git-repo-2.66/tests/test_hooks.py       2026-08-05 00:00:19.000000000 
+0200
@@ -105,3 +105,37 @@
 
     finally:
         sys.stderr = old_stderr
+
+
[email protected]("yes_val", (True, False))
+def test_repo_upload_yes_arg(tmp_path, yes_val: bool) -> None:
+    """Test that yes is passed in kwargs during hook execution."""
+
+    class FakeProject:
+        def __init__(self, worktree):
+            self.worktree = worktree
+            self.enabled_repo_hooks = ["pre-upload"]
+            self.config = None
+
+    hook_file = tmp_path / "pre-upload.py"
+
+    hook_content = """
+def main(project_list, **kwargs):
+    project_list.append(kwargs.get("yes"))
+"""
+    hook_file.write_text(hook_content)
+
+    hook = hooks.RepoHook(
+        hook_type="pre-upload",
+        hooks_project=FakeProject(str(tmp_path)),
+        repo_topdir=str(tmp_path),
+        manifest_url="https://gerrit";,
+        allow_all_hooks=True,
+        yes=yes_val,
+    )
+
+    project_list = []
+    res = hook.Run(project_list=project_list, worktree_list=[])
+
+    assert res is True
+    assert project_list == [yes_val]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/tests/test_project.py 
new/git-repo-2.66/tests/test_project.py
--- old/git-repo-2.65/tests/test_project.py     2026-07-02 00:59:45.000000000 
+0200
+++ new/git-repo-2.66/tests/test_project.py     2026-08-05 00:00:19.000000000 
+0200
@@ -20,10 +20,11 @@
 import shutil
 import subprocess
 import tempfile
-from typing import Optional
+from typing import Dict, List, Optional, Tuple
 import unittest
 from unittest import mock
 
+import pytest
 import utils_for_test
 
 import error
@@ -716,6 +717,31 @@
             fakeproj.config.SetString("manifest.platform", "auto")
             self.assertEqual(fakeproj.manifest_platform, "auto")
 
+    def test_check_immutable_revision_metaproject_skips_manifest_load(self):
+        """MetaProjects must not parse manifest.xml during immutable check.
+
+        During `repo init` the manifestProject's own Sync_NetworkHalf runs
+        before manifest.xml has been linked into .repo/, so
+        _CheckForImmutableRevision must not touch it.
+        """
+
+        with utils_for_test.TempGitTree() as tempdir:
+            fakeproj = self.setUpManifest(tempdir)
+            manifest_path = os.path.join(
+                tempdir, ".repo", manifest_xml.MANIFEST_FILE_NAME
+            )
+            self.assertFalse(os.path.exists(manifest_path))
+
+            fakeproj.revisionExpr = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+            fakeproj.upstream = "refs/heads/main"
+
+            # Must return False without raising ManifestParseError, and
+            # must leave the absent manifest.xml untouched.
+            self.assertFalse(
+                fakeproj._CheckForImmutableRevision(use_superproject=None)
+            )
+            self.assertFalse(os.path.exists(manifest_path))
+
     def test_sync_use_local_gitdirs_worktree_conflict(self):
         """Test that --use-local-gitdirs conflicts with --worktree."""
         with utils_for_test.TempGitTree() as tempdir:
@@ -789,6 +815,60 @@
                         self.assertFalse(os.path.exists(proj.gitdir))
 
 
+_VAR_CMD: List[str] = ["var", "GIT_DEFAULT_BRANCH"]
+_CONFIG_CMD: List[str] = ["config", "--get", "init.defaultBranch"]
+
+
[email protected](
+    "responses, expected_ref, expected_calls",
+    (
+        # git >= 2.35 answers `git var GIT_DEFAULT_BRANCH`.
+        ({"var": (0, "jellybean\n")}, "refs/heads/jellybean", [_VAR_CMD]),
+        # Older git: `git var` fails, so read init.defaultBranch instead.
+        (
+            {"var": (1, ""), "config": (0, "custom\n")},
+            "refs/heads/custom",
+            [_VAR_CMD, _CONFIG_CMD],
+        ),
+        # Nothing configured anywhere: git's historical built-in default.
+        (
+            {"var": (1, ""), "config": (1, "")},
+            "refs/heads/master",
+            [_VAR_CMD, _CONFIG_CMD],
+        ),
+    ),
+    ids=("git_var", "old_git_reads_config", "unconfigured_defaults_to_master"),
+)
+def test_default_branch_fallback(
+    responses: Dict[str, Tuple[int, str]],
+    expected_ref: str,
+    expected_calls: List[List[str]],
+) -> None:
+    """_DefaultBranchFallback resolves the default branch via git."""
+    seen: List[List[str]] = []
+
+    class FakeGitCommand:
+        # Emulate git by returning the canned response for the subcommand.
+        def __init__(
+            self, project_: Optional[project.Project], cmdv: List[str], 
**kwargs
+        ) -> None:
+            self.returncode, self.stdout = responses[cmdv[0]]
+            seen.append(cmdv)
+
+        def Wait(self) -> int:
+            return self.returncode
+
+    # The result is memoized, so clear it before (to bypass any cached real
+    # value) and after (so the mocked value doesn't leak to other tests).
+    project._DefaultBranchFallback.cache_clear()
+    try:
+        with mock.patch.object(project, "GitCommand", FakeGitCommand):
+            assert project._DefaultBranchFallback() == expected_ref
+        assert seen == expected_calls
+    finally:
+        project._DefaultBranchFallback.cache_clear()
+
+
 def _create_mock_project(
     tempdir,
     use_local_gitdirs=False,
@@ -965,13 +1045,20 @@
 class SyncOptimizationTests(unittest.TestCase):
     """Tests for sync optimization logic involving shallow clones."""
 
-    def _get_project(self, tempdir, depth=None):
+    def _get_project(
+        self,
+        tempdir: str,
+        depth: Optional[int] = None,
+        revisionExpr: Optional[str] = None,
+    ) -> project.Project:
+        if revisionExpr is None:
+            revisionExpr = "0123456789abcdef0123456789abcdef01234567"
         proj = _create_mock_project(
             tempdir,
             depth=depth,
             gitdir=os.path.join(tempdir, "gitdir"),
             objdir=os.path.join(tempdir, "objdir"),
-            revisionExpr="0123456789abcdef0123456789abcdef01234567",
+            revisionExpr=revisionExpr,
         )
         proj._CheckForImmutableRevision = mock.MagicMock(return_value=True)
         proj.DeleteWorktree = mock.MagicMock()
@@ -1198,6 +1285,124 @@
                 self.assertTrue(res)
                 mock_git_cmd.assert_not_called()
 
+    def test_remote_fetch_sha1_upstream_fallback(self) -> None:
+        """Test _RemoteFetch resolves upstream fallback for SHA-1 revisions."""
+        sha = "4f8a3c0000000000000000000000000000000000"
+        with utils_for_test.TempGitTree() as tempdir:
+            proj = self._get_project(tempdir, revisionExpr=sha)
+            proj._CheckForImmutableRevision.side_effect = [False, True]
+            proj.upstream = None
+            proj.dest_branch = "my-dest-branch"
+
+            mock_remote = mock.MagicMock()
+            mock_remote.name = "origin"
+
+            def _to_local(r: str) -> str:
+                if r.startswith("refs/heads/"):
+                    return "refs/remotes/origin/" + r[11:]
+                return r
+
+            mock_remote.ToLocal.side_effect = _to_local
+            mock_remote.PreConnectFetch.return_value = True
+            proj.GetRemote = mock.MagicMock(return_value=mock_remote)
+
+            with mock.patch("project.GitCommand") as mock_git_cmd:
+                mock_cmd_instance = mock.MagicMock()
+                mock_cmd_instance.Wait.return_value = 0
+                mock_git_cmd.return_value = mock_cmd_instance
+
+                res = proj._RemoteFetch(current_branch_only=True)
+
+                self.assertTrue(res)
+                mock_git_cmd.assert_called_once()
+                cmd_args = mock_git_cmd.call_args[0][1]
+                self.assertIn(
+                    "+refs/heads/my-dest-branch:"
+                    "refs/remotes/origin/my-dest-branch",
+                    cmd_args,
+                )
+                self.assertNotIn(
+                    "+refs/heads/*:refs/remotes/origin/*", cmd_args
+                )
+
+    def test_remote_fetch_sha1_manifest_default_fallback(self) -> None:
+        """Test _RemoteFetch upstream fallback from manifest defaults."""
+        sha = "4f8a3c0000000000000000000000000000000000"
+        with utils_for_test.TempGitTree() as tempdir:
+            proj = self._get_project(tempdir, revisionExpr=sha)
+            proj._CheckForImmutableRevision.side_effect = [False, True]
+            proj.upstream = None
+            proj.dest_branch = None
+            proj.manifest.default.upstreamExpr = "manifest-upstream"
+
+            mock_remote = mock.MagicMock()
+            mock_remote.name = "origin"
+
+            def _to_local(r: str) -> str:
+                if r.startswith("refs/heads/"):
+                    return "refs/remotes/origin/" + r[11:]
+                return r
+
+            mock_remote.ToLocal.side_effect = _to_local
+            mock_remote.PreConnectFetch.return_value = True
+            proj.GetRemote = mock.MagicMock(return_value=mock_remote)
+
+            with mock.patch("project.GitCommand") as mock_git_cmd:
+                mock_cmd_instance = mock.MagicMock()
+                mock_cmd_instance.Wait.return_value = 0
+                mock_git_cmd.return_value = mock_cmd_instance
+
+                res = proj._RemoteFetch(current_branch_only=True)
+
+                self.assertTrue(res)
+                mock_git_cmd.assert_called_once()
+                cmd_args = mock_git_cmd.call_args[0][1]
+                self.assertIn(
+                    "+refs/heads/manifest-upstream:"
+                    "refs/remotes/origin/manifest-upstream",
+                    cmd_args,
+                )
+                self.assertNotIn(
+                    "+refs/heads/*:refs/remotes/origin/*", cmd_args
+                )
+
+    def test_remote_fetch_sha1_tag_fallback(self) -> None:
+        """Test _RemoteFetch resolves upstream fallback to tag correctly."""
+        sha = "4f8a3c0000000000000000000000000000000000"
+        with utils_for_test.TempGitTree() as tempdir:
+            proj = self._get_project(tempdir, revisionExpr=sha)
+            proj._CheckForImmutableRevision.side_effect = [False, True]
+            proj.upstream = None
+            proj.dest_branch = "refs/tags/v1.0"
+
+            mock_remote = mock.MagicMock()
+            mock_remote.name = "origin"
+
+            def _to_local(r: str) -> str:
+                if r.startswith("refs/tags/"):
+                    return "refs/tags/" + r[10:]
+                return r
+
+            mock_remote.ToLocal.side_effect = _to_local
+            mock_remote.PreConnectFetch.return_value = True
+            proj.GetRemote = mock.MagicMock(return_value=mock_remote)
+
+            with mock.patch("project.GitCommand") as mock_git_cmd:
+                mock_cmd_instance = mock.MagicMock()
+                mock_cmd_instance.Wait.return_value = 0
+                mock_git_cmd.return_value = mock_cmd_instance
+
+                res = proj._RemoteFetch(current_branch_only=True)
+
+                self.assertTrue(res)
+                mock_git_cmd.assert_called_once()
+                cmd_args = mock_git_cmd.call_args[0][1]
+                self.assertIn("tag", cmd_args)
+                self.assertIn("v1.0", cmd_args)
+                self.assertNotIn(
+                    "+refs/heads/*:refs/remotes/origin/*", cmd_args
+                )
+
 
 class GetEnvVarsTests(unittest.TestCase):
     """Tests for GetEnvVars project environment variable generation."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/tests/test_subcmds_gc.py 
new/git-repo-2.66/tests/test_subcmds_gc.py
--- old/git-repo-2.65/tests/test_subcmds_gc.py  2026-07-02 00:59:45.000000000 
+0200
+++ new/git-repo-2.66/tests/test_subcmds_gc.py  2026-08-05 00:00:19.000000000 
+0200
@@ -24,7 +24,7 @@
     """Tests for gc command."""
 
     def setUp(self):
-        self.cmd = gc.Gc()
+        self.cmd = gc.Gc(manifest=mock.MagicMock())
         self.opt, self.args = self.cmd.OptionParser.parse_args([])
         self.opt.this_manifest_only = False
         self.opt.repack = False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/tests/test_subcmds_rebase.py 
new/git-repo-2.66/tests/test_subcmds_rebase.py
--- old/git-repo-2.65/tests/test_subcmds_rebase.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/git-repo-2.66/tests/test_subcmds_rebase.py      2026-08-05 
00:00:19.000000000 +0200
@@ -0,0 +1,50 @@
+# Copyright (C) 2026 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unittests for the subcmds/rebase.py module."""
+
+from unittest import mock
+
+from error import GitError
+from subcmds import rebase
+
+
+def test_resolve_onto_manifest_success() -> None:
+    """Test _ResolveOntoManifest when ToLocal succeeds."""
+    project = mock.MagicMock()
+    project.revisionExpr = "main"
+
+    remote = mock.MagicMock()
+    remote.ToLocal.return_value = "refs/remotes/goog/main"
+    project.GetRemote.return_value = remote
+
+    res = rebase._ResolveOntoManifest(project)
+    assert res == "refs/remotes/goog/main"
+    project.GetRemote.assert_called_once()
+    remote.ToLocal.assert_called_once_with("main")
+
+
+def test_resolve_onto_manifest_fallback() -> None:
+    """Test _ResolveOntoManifest when ToLocal raises GitError."""
+    project = mock.MagicMock()
+    project.revisionExpr = "main"
+
+    remote = mock.MagicMock()
+    remote.ToLocal.side_effect = GitError("Failed to resolve")
+    project.GetRemote.return_value = remote
+
+    res = rebase._ResolveOntoManifest(project)
+    assert res == "main"
+    project.GetRemote.assert_called_once()
+    remote.ToLocal.assert_called_once_with("main")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/git-repo-2.65/tests/test_subcmds_sync.py 
new/git-repo-2.66/tests/test_subcmds_sync.py
--- old/git-repo-2.65/tests/test_subcmds_sync.py        2026-07-02 
00:59:45.000000000 +0200
+++ new/git-repo-2.66/tests/test_subcmds_sync.py        2026-08-05 
00:00:19.000000000 +0200
@@ -15,6 +15,7 @@
 
 import json
 import os
+from pathlib import Path
 import shutil
 import tempfile
 import time
@@ -26,11 +27,52 @@
 import command
 from error import GitError
 from error import RepoExitError
+import manifest_xml
 from project import SyncNetworkHalfResult
 from subcmds import sync
 
 
 @pytest.mark.parametrize(
+    "cli_args, expected",
+    [
+        ([], None),
+        (["--recurse-submodules"], True),
+        (["--no-recurse-submodules"], False),
+        (["--fetch-submodules"], True),
+        (["--no-fetch-submodules"], False),
+    ],
+)
+def test_recurse_submodules_option(cli_args, expected):
+    """The submodule flags preserve an unset manifest-driven state."""
+    cmd = sync.Sync()
+
+    opts, _ = cmd.OptionParser.parse_args(cli_args)
+
+    assert opts.recurse_submodules is expected
+
+
[email protected](
+    "old_flag, new_flag",
+    [
+        ("--fetch-submodules", "--recurse-submodules"),
+        ("--no-fetch-submodules", "--no-recurse-submodules"),
+    ],
+)
+def test_recurse_submodules_option_deprecation(old_flag, new_flag):
+    """The old submodule flags warn and direct users to their replacements."""
+    cmd = sync.Sync()
+
+    with mock.patch.object(sync.logger, "warning") as warning:
+        cmd.OptionParser.parse_args([old_flag])
+
+    warning.assert_called_once_with(
+        "%s is deprecated; use %s instead", old_flag, new_flag
+    )
+
+    assert old_flag not in cmd.OptionParser.format_help()
+
+
[email protected](
     "use_superproject, cli_args, result",
     [
         (True, ["--current-branch"], True),
@@ -56,6 +98,120 @@
         assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result
 
 
[email protected](
+    "cli_args, expected_groups",
+    [
+        ([], None),
+        (["-g", "groupA"], "groupA"),
+        (["--groups=groupB,groupC"], "groupB,groupC"),
+    ],
+)
+def test_groups_option_parsing(cli_args, expected_groups):
+    """Test --groups / -g option parsing."""
+    cmd = sync.Sync()
+    opts, _ = cmd.OptionParser.parse_args(cli_args)
+    assert opts.groups == expected_groups
+
+
+def _create_manifest_with_groups(topdir: Path) -> manifest_xml.XmlManifest:
+    """Create a test XmlManifest with projects assigned to various groups."""
+    repodir = topdir / ".repo"
+    manifest_dir = repodir / "manifests"
+    manifest_file = repodir / manifest_xml.MANIFEST_FILE_NAME
+
+    repodir.mkdir(exist_ok=True)
+    manifest_dir.mkdir(exist_ok=True)
+
+    gitdir = repodir / "manifests.git"
+    gitdir.mkdir(exist_ok=True)
+    (gitdir / "config").write_text(
+        """[remote "origin"]
+            url = https://localhost:0/manifest
+        """,
+        encoding="utf-8",
+    )
+
+    manifest_file.write_text(
+        """
+            <manifest>
+                <remote name="origin" fetch="http://localhost"; />
+                <default remote="origin" revision="refs/heads/main" />
+                <project name="proj_g1" path="path_g1" groups="group1" />
+                <project name="proj_g2" path="path_g2" groups="group2" />
+                <project name="proj_g1_g2" path="path_g1_g2"
+                         groups="group1,group2" />
+                <project name="proj_default" path="path_default" />
+                <project name="proj_notdefault" path="path_notdefault"
+                         groups="notdefault" />
+            </manifest>
+        """,
+        encoding="utf-8",
+    )
+
+    for p in [
+        "proj_g1",
+        "proj_g2",
+        "proj_g1_g2",
+        "proj_default",
+        "proj_notdefault",
+    ]:
+        (repodir / "projects" / f"{p}.git").mkdir(parents=True, exist_ok=True)
+
+    return manifest_xml.XmlManifest(str(repodir), str(manifest_file))
+
+
[email protected](
+    "cli_args, expected_projects",
+    [
+        (["-g", "group1"], ["proj_g1", "proj_g1_g2"]),
+        (["-g", "group2"], ["proj_g2", "proj_g1_g2"]),
+        (["-g", "group1,group2"], ["proj_g1", "proj_g1_g2", "proj_g2"]),
+        (["-g", "default,-group1"], ["proj_default", "proj_g2"]),
+        ([], ["proj_default", "proj_g1", "proj_g1_g2", "proj_g2"]),
+    ],
+)
+def test_sync_groups_manifest_filtering(
+    tmp_path: Path, cli_args, expected_projects
+):
+    """Test that repo sync -g selects only matching projects."""
+    manifest = _create_manifest_with_groups(tmp_path)
+    cmd = sync.Sync()
+    cmd.manifest = manifest
+
+    opts, args = cmd.OptionParser.parse_args(cli_args)
+    projects = cmd.GetProjects(args, groups=opts.groups, missing_ok=True)
+    project_names = sorted([p.name for p in projects])
+    assert project_names == sorted(expected_projects)
+
+
+def test_sync_update_projects_revision_id_respects_groups(tmp_path: Path):
+    """Test that _UpdateProjectsRevisionId filters projects using 
opt.groups."""
+    manifest = _create_manifest_with_groups(tmp_path)
+    cmd = sync.Sync()
+    cmd.manifest = manifest
+
+    superproject = mock.MagicMock()
+    superproject.UpdateProjectsRevisionId.return_value = mock.MagicMock(
+        manifest_path=None
+    )
+    manifest._superproject = superproject
+
+    opts, args = cmd.OptionParser.parse_args(["-g", "group1"])
+    opts.verbose = False
+    opts.fetch_submodules = False
+    opts.this_manifest_only = True
+    opts.local_only = False
+
+    with mock.patch.object(
+        cmd, "GetProjects", wraps=cmd.GetProjects
+    ) as spy_get_projects:
+        with mock.patch.object(cmd, "ManifestList", return_value=[manifest]):
+            cmd._UpdateProjectsRevisionId(opts, args, {}, manifest)
+            spy_get_projects.assert_called_once()
+            _, kwargs = spy_get_projects.call_args
+            assert kwargs.get("groups") == "group1"
+
+
 # Used to patch os.cpu_count() for reliable results.
 OS_CPU_COUNT = 24
 
@@ -792,6 +948,19 @@
             self.assertIn(self.sync_local_half_error, e.aggregate_errors)
             self.assertIn(self.sync_network_half_error, e.aggregate_errors)
 
+    def test_groups_passed_to_get_projects(self):
+        """Ensure Execute passes opt.groups to GetProjects."""
+        self.opt.groups = "my_group"
+        self.opt.mp_update = False
+        with mock.patch.object(self.cmd, "_UpdateRepoProject"):
+            with mock.patch.object(self.cmd, "_ValidateOptionsWithManifest"):
+                with mock.patch.object(self.cmd, "_SyncInterleaved"):
+                    with mock.patch.object(self.cmd, "_RunPostSyncHook"):
+                        self.cmd.Execute(self.opt, [])
+        self.cmd.GetProjects.assert_called()
+        _, kwargs = self.cmd.GetProjects.call_args
+        self.assertEqual(kwargs.get("groups"), "my_group")
+
 
 class SyncUpdateRepoProject(unittest.TestCase):
     """Tests for Sync._UpdateRepoProject."""

++++++ git-repo.obsinfo ++++++
--- /var/tmp/diff_new_pack.3PaNvU/_old  2026-08-06 16:28:46.830194053 +0200
+++ /var/tmp/diff_new_pack.3PaNvU/_new  2026-08-06 16:28:46.834194192 +0200
@@ -1,5 +1,5 @@
 name: git-repo
-version: 2.65
-mtime: 1782946785
-commit: 35bbf701d04de5c6a71937279bc3d16f6ce36808
+version: 2.66
+mtime: 1785880819
+commit: d9da609d8c120bb882a43196a4a6b7f183418304
 

Reply via email to