diff options
| -rw-r--r-- | docs/manifest-format.md | 5 | ||||
| -rw-r--r-- | man/repo-manifest.1 | 7 | ||||
| -rw-r--r-- | manifest_xml.py | 11 | ||||
| -rw-r--r-- | subcmds/sync.py | 34 | ||||
| -rw-r--r-- | tests/test_manifest_xml.py | 26 | ||||
| -rw-r--r-- | tests/test_subcmds_sync.py | 29 |
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 |
| 214 | not setting their own `upstream` will inherit this value. | 215 | not setting their own `upstream` will inherit this value. |
| 215 | 216 | ||
| 216 | Attribute `sync-j`: Number of parallel jobs to use when synching. | 217 | Attribute `sync-j`: Number of parallel jobs to use when syncing. |
| 218 | |||
| 219 | Attribute `sync-j-max`: Maximum number of parallel jobs to use when syncing. | ||
| 217 | 220 | ||
| 218 | Attribute `sync-c`: Set to true to only sync the given Git | 221 | Attribute `sync-c`: Set to true to only sync the given Git |
| 219 | branch (specified in the `revision` attribute) rather than the | 222 | branch (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 |
| 4 | repo \- repo manifest - manual page for repo manifest | 4 | repo \- 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 | |||
| 309 | entire ref space. Project elements not setting their own `upstream` will inherit | 310 | entire ref space. Project elements not setting their own `upstream` will inherit |
| 310 | this value. | 311 | this value. |
| 311 | .PP | 312 | .PP |
| 312 | Attribute `sync\-j`: Number of parallel jobs to use when synching. | 313 | Attribute `sync\-j`: Number of parallel jobs to use when syncing. |
| 314 | .PP | ||
| 315 | Attribute `sync\-j\-max`: Maximum number of parallel jobs to use when syncing. | ||
| 313 | .PP | 316 | .PP |
| 314 | Attribute `sync\-c`: Set to true to only sync the given Git branch (specified in | 317 | Attribute `sync\-c`: Set to true to only sync the given Git branch (specified in |
| 315 | the `revision` attribute) rather than the whole ref space. Project elements | 318 | the `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 | ||
| 405 | class IncludeElementTests(ManifestParseTestCase): | 431 | class 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 | ) | ||
| 122 | def 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) |
