summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/manifest-format.md5
-rw-r--r--man/repo-manifest.17
-rw-r--r--manifest_xml.py11
-rw-r--r--subcmds/sync.py34
-rw-r--r--tests/test_manifest_xml.py26
-rw-r--r--tests/test_subcmds_sync.py29
6 files changed, 101 insertions, 11 deletions
diff --git a/docs/manifest-format.md b/docs/manifest-format.md
index 1eead91de..c3cbe0707 100644
--- a/docs/manifest-format.md
+++ b/docs/manifest-format.md
@@ -51,6 +51,7 @@ following DTD:
51 <!ATTLIST default dest-branch CDATA #IMPLIED> 51 <!ATTLIST default dest-branch CDATA #IMPLIED>
52 <!ATTLIST default upstream CDATA #IMPLIED> 52 <!ATTLIST default upstream CDATA #IMPLIED>
53 <!ATTLIST default sync-j CDATA #IMPLIED> 53 <!ATTLIST default sync-j CDATA #IMPLIED>
54 <!ATTLIST default sync-j-max CDATA #IMPLIED>
54 <!ATTLIST default sync-c CDATA #IMPLIED> 55 <!ATTLIST default sync-c CDATA #IMPLIED>
55 <!ATTLIST default sync-s CDATA #IMPLIED> 56 <!ATTLIST default sync-s CDATA #IMPLIED>
56 <!ATTLIST default sync-tags CDATA #IMPLIED> 57 <!ATTLIST default sync-tags CDATA #IMPLIED>
@@ -213,7 +214,9 @@ can be found. Used when syncing a revision locked manifest in
213-c mode to avoid having to sync the entire ref space. Project elements 214-c mode to avoid having to sync the entire ref space. Project elements
214not setting their own `upstream` will inherit this value. 215not setting their own `upstream` will inherit this value.
215 216
216Attribute `sync-j`: Number of parallel jobs to use when synching. 217Attribute `sync-j`: Number of parallel jobs to use when syncing.
218
219Attribute `sync-j-max`: Maximum number of parallel jobs to use when syncing.
217 220
218Attribute `sync-c`: Set to true to only sync the given Git 221Attribute `sync-c`: Set to true to only sync the given Git
219branch (specified in the `revision` attribute) rather than the 222branch (specified in the `revision` attribute) rather than the
diff --git a/man/repo-manifest.1 b/man/repo-manifest.1
index dfb0160ec..4d650c155 100644
--- a/man/repo-manifest.1
+++ b/man/repo-manifest.1
@@ -1,5 +1,5 @@
1.\" DO NOT MODIFY THIS FILE! It was generated by help2man. 1.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
2.TH REPO "1" "December 2025" "repo manifest" "Repo Manual" 2.TH REPO "1" "February 2026" "repo manifest" "Repo Manual"
3.SH NAME 3.SH NAME
4repo \- repo manifest - manual page for repo manifest 4repo \- repo manifest - manual page for repo manifest
5.SH SYNOPSIS 5.SH SYNOPSIS
@@ -131,6 +131,7 @@ include*)>
131<!ATTLIST default dest\-branch CDATA #IMPLIED> 131<!ATTLIST default dest\-branch CDATA #IMPLIED>
132<!ATTLIST default upstream CDATA #IMPLIED> 132<!ATTLIST default upstream CDATA #IMPLIED>
133<!ATTLIST default sync\-j CDATA #IMPLIED> 133<!ATTLIST default sync\-j CDATA #IMPLIED>
134<!ATTLIST default sync\-j\-max CDATA #IMPLIED>
134<!ATTLIST default sync\-c CDATA #IMPLIED> 135<!ATTLIST default sync\-c CDATA #IMPLIED>
135<!ATTLIST default sync\-s CDATA #IMPLIED> 136<!ATTLIST default sync\-s CDATA #IMPLIED>
136<!ATTLIST default sync\-tags CDATA #IMPLIED> 137<!ATTLIST default sync\-tags CDATA #IMPLIED>
@@ -309,7 +310,9 @@ when syncing a revision locked manifest in \fB\-c\fR mode to avoid having to syn
309entire ref space. Project elements not setting their own `upstream` will inherit 310entire ref space. Project elements not setting their own `upstream` will inherit
310this value. 311this value.
311.PP 312.PP
312Attribute `sync\-j`: Number of parallel jobs to use when synching. 313Attribute `sync\-j`: Number of parallel jobs to use when syncing.
314.PP
315Attribute `sync\-j\-max`: Maximum number of parallel jobs to use when syncing.
313.PP 316.PP
314Attribute `sync\-c`: Set to true to only sync the given Git branch (specified in 317Attribute `sync\-c`: Set to true to only sync the given Git branch (specified in
315the `revision` attribute) rather than the whole ref space. Project elements 318the `revision` attribute) rather than the whole ref space. Project elements
diff --git a/manifest_xml.py b/manifest_xml.py
index 6989aad53..084ca5ab2 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -155,6 +155,7 @@ class _Default:
155 upstreamExpr = None 155 upstreamExpr = None
156 remote = None 156 remote = None
157 sync_j = None 157 sync_j = None
158 sync_j_max = None
158 sync_c = False 159 sync_c = False
159 sync_s = False 160 sync_s = False
160 sync_tags = True 161 sync_tags = True
@@ -631,6 +632,9 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
631 if d.sync_j is not None: 632 if d.sync_j is not None:
632 have_default = True 633 have_default = True
633 e.setAttribute("sync-j", "%d" % d.sync_j) 634 e.setAttribute("sync-j", "%d" % d.sync_j)
635 if d.sync_j_max is not None:
636 have_default = True
637 e.setAttribute("sync-j-max", "%d" % d.sync_j_max)
634 if d.sync_c: 638 if d.sync_c:
635 have_default = True 639 have_default = True
636 e.setAttribute("sync-c", "true") 640 e.setAttribute("sync-c", "true")
@@ -1763,6 +1767,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
1763 % (self.manifestFile, d.sync_j) 1767 % (self.manifestFile, d.sync_j)
1764 ) 1768 )
1765 1769
1770 d.sync_j_max = XmlInt(node, "sync-j-max", None)
1771 if d.sync_j_max is not None and d.sync_j_max <= 0:
1772 raise ManifestParseError(
1773 '%s: sync-j-max must be greater than 0, not "%s"'
1774 % (self.manifestFile, d.sync_j_max)
1775 )
1776
1766 d.sync_c = XmlBool(node, "sync-c", False) 1777 d.sync_c = XmlBool(node, "sync-c", False)
1767 d.sync_s = XmlBool(node, "sync-s", False) 1778 d.sync_s = XmlBool(node, "sync-s", False)
1768 d.sync_tags = XmlBool(node, "sync-tags", True) 1779 d.sync_tags = XmlBool(node, "sync-tags", True)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 726e6d079..89b58e6aa 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -1940,15 +1940,33 @@ later is required to fix a server side protocol bug.
1940 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit) 1940 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1941 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit) 1941 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1942 1942
1943 # Warn once if effective job counts seem excessively high. 1943 sync_j_max = mp.manifest.default.sync_j_max or None
1944
1945 # Check for shared options.
1944 # Prioritize --jobs, then --jobs-network, then --jobs-checkout. 1946 # Prioritize --jobs, then --jobs-network, then --jobs-checkout.
1945 job_options_to_check = ( 1947 job_attributes = (
1946 ("--jobs", opt.jobs), 1948 ("--jobs", "jobs"),
1947 ("--jobs-network", opt.jobs_network), 1949 ("--jobs-network", "jobs_network"),
1948 ("--jobs-checkout", opt.jobs_checkout), 1950 ("--jobs-checkout", "jobs_checkout"),
1949 ) 1951 )
1950 for name, value in job_options_to_check: 1952
1951 if value > self._JOBS_WARN_THRESHOLD: 1953 warned = False
1954 limit_warned = False
1955 for name, attr in job_attributes:
1956 value = getattr(opt, attr)
1957
1958 if sync_j_max and value > sync_j_max:
1959 if not limit_warned:
1960 logger.warning(
1961 "warning: manifest limits %s to %d",
1962 name,
1963 sync_j_max,
1964 )
1965 limit_warned = True
1966 setattr(opt, attr, sync_j_max)
1967 value = sync_j_max
1968
1969 if not warned and value > self._JOBS_WARN_THRESHOLD:
1952 logger.warning( 1970 logger.warning(
1953 "High job count (%d > %d) specified for %s; this may " 1971 "High job count (%d > %d) specified for %s; this may "
1954 "lead to excessive resource usage or diminishing returns.", 1972 "lead to excessive resource usage or diminishing returns.",
@@ -1956,7 +1974,7 @@ later is required to fix a server side protocol bug.
1956 self._JOBS_WARN_THRESHOLD, 1974 self._JOBS_WARN_THRESHOLD,
1957 name, 1975 name,
1958 ) 1976 )
1959 break 1977 warned = True
1960 1978
1961 def Execute(self, opt, args): 1979 def Execute(self, opt, args):
1962 errors = [] 1980 errors = []
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py
index 97fea3da3..75efa95fc 100644
--- a/tests/test_manifest_xml.py
+++ b/tests/test_manifest_xml.py
@@ -401,6 +401,32 @@ class XmlManifestTests(ManifestParseTestCase):
401 self.assertEqual(len(manifest.projects), 1) 401 self.assertEqual(len(manifest.projects), 1)
402 self.assertEqual(manifest.projects[0].name, "test-project") 402 self.assertEqual(manifest.projects[0].name, "test-project")
403 403
404 def test_sync_j_max(self):
405 """Check sync-j-max handling."""
406 # Check valid value.
407 manifest = self.getXmlManifest(
408 '<manifest><default sync-j-max="5" /></manifest>'
409 )
410 self.assertEqual(manifest.default.sync_j_max, 5)
411 self.assertEqual(
412 manifest.ToXml().toxml(),
413 '<?xml version="1.0" ?>'
414 '<manifest><default sync-j-max="5"/></manifest>',
415 )
416
417 # Check invalid values.
418 with self.assertRaises(error.ManifestParseError):
419 manifest = self.getXmlManifest(
420 '<manifest><default sync-j-max="0" /></manifest>'
421 )
422 manifest.ToXml()
423
424 with self.assertRaises(error.ManifestParseError):
425 manifest = self.getXmlManifest(
426 '<manifest><default sync-j-max="-1" /></manifest>'
427 )
428 manifest.ToXml()
429
404 430
405class IncludeElementTests(ManifestParseTestCase): 431class IncludeElementTests(ManifestParseTestCase):
406 """Tests for <include>.""" 432 """Tests for <include>."""
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py
index 6eb8a5a71..9fef68425 100644
--- a/tests/test_subcmds_sync.py
+++ b/tests/test_subcmds_sync.py
@@ -97,6 +97,35 @@ def test_cli_jobs(argv, jobs_manifest, jobs, jobs_net, jobs_check):
97 """Tests --jobs option behavior.""" 97 """Tests --jobs option behavior."""
98 mp = mock.MagicMock() 98 mp = mock.MagicMock()
99 mp.manifest.default.sync_j = jobs_manifest 99 mp.manifest.default.sync_j = jobs_manifest
100 mp.manifest.default.sync_j_max = None
101
102 cmd = sync.Sync()
103 opts, args = cmd.OptionParser.parse_args(argv)
104 cmd.ValidateOptions(opts, args)
105
106 with mock.patch.object(sync, "_rlimit_nofile", return_value=(256, 256)):
107 with mock.patch.object(os, "cpu_count", return_value=OS_CPU_COUNT):
108 cmd._ValidateOptionsWithManifest(opts, mp)
109 assert opts.jobs == jobs
110 assert opts.jobs_network == jobs_net
111 assert opts.jobs_checkout == jobs_check
112
113
114@pytest.mark.parametrize(
115 "argv, jobs_manifest, jobs_manifest_max, jobs, jobs_net, jobs_check",
116 [
117 (["--jobs=10"], None, 5, 5, 5, 5),
118 (["--jobs=10", "--jobs-network=10"], None, 5, 5, 5, 5),
119 (["--jobs=10", "--jobs-checkout=10"], None, 5, 5, 5, 5),
120 ],
121)
122def test_cli_jobs_sync_j_max(
123 argv, jobs_manifest, jobs_manifest_max, jobs, jobs_net, jobs_check
124):
125 """Tests --jobs option behavior with sync-j-max."""
126 mp = mock.MagicMock()
127 mp.manifest.default.sync_j = jobs_manifest
128 mp.manifest.default.sync_j_max = jobs_manifest_max
100 129
101 cmd = sync.Sync() 130 cmd = sync.Sync()
102 opts, args = cmd.OptionParser.parse_args(argv) 131 opts, args = cmd.OptionParser.parse_args(argv)