mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-04-20 14:09:30 +00:00
Compare commits
No commits in common. "main" and "v2.44" have entirely different histories.
1
color.py
1
color.py
@ -210,7 +210,6 @@ class Coloring:
|
|||||||
if have_fg:
|
if have_fg:
|
||||||
bg = a
|
bg = a
|
||||||
else:
|
else:
|
||||||
have_fg = True
|
|
||||||
fg = a
|
fg = a
|
||||||
elif is_attr(a):
|
elif is_attr(a):
|
||||||
attr = a
|
attr = a
|
||||||
|
58
command.py
58
command.py
@ -12,7 +12,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
@ -71,14 +70,6 @@ class Command:
|
|||||||
# migrated subcommands can set it to False.
|
# migrated subcommands can set it to False.
|
||||||
MULTI_MANIFEST_SUPPORT = True
|
MULTI_MANIFEST_SUPPORT = True
|
||||||
|
|
||||||
# Shared data across parallel execution workers.
|
|
||||||
_parallel_context = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_parallel_context(cls):
|
|
||||||
assert cls._parallel_context is not None
|
|
||||||
return cls._parallel_context
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
repodir=None,
|
repodir=None,
|
||||||
@ -251,39 +242,9 @@ class Command:
|
|||||||
"""Perform the action, after option parsing is complete."""
|
"""Perform the action, after option parsing is complete."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
@contextlib.contextmanager
|
|
||||||
def ParallelContext(cls):
|
|
||||||
"""Obtains the context, which is shared to ExecuteInParallel workers.
|
|
||||||
|
|
||||||
Callers can store data in the context dict before invocation of
|
|
||||||
ExecuteInParallel. The dict will then be shared to child workers of
|
|
||||||
ExecuteInParallel.
|
|
||||||
"""
|
|
||||||
assert cls._parallel_context is None
|
|
||||||
cls._parallel_context = {}
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
cls._parallel_context = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _InitParallelWorker(cls, context, initializer):
|
|
||||||
cls._parallel_context = context
|
|
||||||
if initializer:
|
|
||||||
initializer()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def ExecuteInParallel(
|
def ExecuteInParallel(
|
||||||
cls,
|
jobs, func, inputs, callback, output=None, ordered=False
|
||||||
jobs,
|
|
||||||
func,
|
|
||||||
inputs,
|
|
||||||
callback,
|
|
||||||
output=None,
|
|
||||||
ordered=False,
|
|
||||||
chunksize=WORKER_BATCH_SIZE,
|
|
||||||
initializer=None,
|
|
||||||
):
|
):
|
||||||
"""Helper for managing parallel execution boiler plate.
|
"""Helper for managing parallel execution boiler plate.
|
||||||
|
|
||||||
@ -308,9 +269,6 @@ class Command:
|
|||||||
output: An output manager. May be progress.Progess or
|
output: An output manager. May be progress.Progess or
|
||||||
color.Coloring.
|
color.Coloring.
|
||||||
ordered: Whether the jobs should be processed in order.
|
ordered: Whether the jobs should be processed in order.
|
||||||
chunksize: The number of jobs processed in batch by parallel
|
|
||||||
workers.
|
|
||||||
initializer: Worker initializer.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The |callback| function's results are returned.
|
The |callback| function's results are returned.
|
||||||
@ -320,16 +278,12 @@ class Command:
|
|||||||
if len(inputs) == 1 or jobs == 1:
|
if len(inputs) == 1 or jobs == 1:
|
||||||
return callback(None, output, (func(x) for x in inputs))
|
return callback(None, output, (func(x) for x in inputs))
|
||||||
else:
|
else:
|
||||||
with multiprocessing.Pool(
|
with multiprocessing.Pool(jobs) as pool:
|
||||||
jobs,
|
|
||||||
initializer=cls._InitParallelWorker,
|
|
||||||
initargs=(cls._parallel_context, initializer),
|
|
||||||
) as pool:
|
|
||||||
submit = pool.imap if ordered else pool.imap_unordered
|
submit = pool.imap if ordered else pool.imap_unordered
|
||||||
return callback(
|
return callback(
|
||||||
pool,
|
pool,
|
||||||
output,
|
output,
|
||||||
submit(func, inputs, chunksize=chunksize),
|
submit(func, inputs, chunksize=WORKER_BATCH_SIZE),
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
if isinstance(output, progress.Progress):
|
if isinstance(output, progress.Progress):
|
||||||
@ -547,3 +501,7 @@ class MirrorSafeCommand:
|
|||||||
"""Command permits itself to run within a mirror, and does not require a
|
"""Command permits itself to run within a mirror, and does not require a
|
||||||
working directory.
|
working directory.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class GitcClientCommand:
|
||||||
|
"""Command that requires the local client to be a GITC client."""
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
# NB: Keep in sync with run_tests.vpython3.
|
|
||||||
black<26
|
|
@ -141,7 +141,7 @@ Instead, you should use standard Git workflows like [git worktree] or
|
|||||||
(e.g. a local mirror & a public review server) while avoiding duplicating
|
(e.g. a local mirror & a public review server) while avoiding duplicating
|
||||||
the content. However, this can run into problems if different remotes use
|
the content. However, this can run into problems if different remotes use
|
||||||
the same path on their respective servers. Best to avoid that.
|
the same path on their respective servers. Best to avoid that.
|
||||||
* `modules/`: Like `projects/`, but for git submodules.
|
* `subprojects/`: Like `projects/`, but for git submodules.
|
||||||
* `subproject-objects/`: Like `project-objects/`, but for git submodules.
|
* `subproject-objects/`: Like `project-objects/`, but for git submodules.
|
||||||
* `worktrees/`: Bare checkouts of every project synced by the manifest. The
|
* `worktrees/`: Bare checkouts of every project synced by the manifest. The
|
||||||
filesystem layout matches the `<project name=...` setting in the manifest
|
filesystem layout matches the `<project name=...` setting in the manifest
|
||||||
|
@ -107,13 +107,11 @@ following DTD:
|
|||||||
<!ATTLIST extend-project remote CDATA #IMPLIED>
|
<!ATTLIST extend-project remote CDATA #IMPLIED>
|
||||||
<!ATTLIST extend-project dest-branch CDATA #IMPLIED>
|
<!ATTLIST extend-project dest-branch CDATA #IMPLIED>
|
||||||
<!ATTLIST extend-project upstream CDATA #IMPLIED>
|
<!ATTLIST extend-project upstream CDATA #IMPLIED>
|
||||||
<!ATTLIST extend-project base-rev CDATA #IMPLIED>
|
|
||||||
|
|
||||||
<!ELEMENT remove-project EMPTY>
|
<!ELEMENT remove-project EMPTY>
|
||||||
<!ATTLIST remove-project name CDATA #IMPLIED>
|
<!ATTLIST remove-project name CDATA #IMPLIED>
|
||||||
<!ATTLIST remove-project path CDATA #IMPLIED>
|
<!ATTLIST remove-project path CDATA #IMPLIED>
|
||||||
<!ATTLIST remove-project optional CDATA #IMPLIED>
|
<!ATTLIST remove-project optional CDATA #IMPLIED>
|
||||||
<!ATTLIST remove-project base-rev CDATA #IMPLIED>
|
|
||||||
|
|
||||||
<!ELEMENT repo-hooks EMPTY>
|
<!ELEMENT repo-hooks EMPTY>
|
||||||
<!ATTLIST repo-hooks in-project CDATA #REQUIRED>
|
<!ATTLIST repo-hooks in-project CDATA #REQUIRED>
|
||||||
@ -231,7 +229,26 @@ At most one manifest-server may be specified. The url attribute
|
|||||||
is used to specify the URL of a manifest server, which is an
|
is used to specify the URL of a manifest server, which is an
|
||||||
XML RPC service.
|
XML RPC service.
|
||||||
|
|
||||||
See the [smart sync documentation](./smart-sync.md) for more details.
|
The manifest server should implement the following RPC methods:
|
||||||
|
|
||||||
|
GetApprovedManifest(branch, target)
|
||||||
|
|
||||||
|
Return a manifest in which each project is pegged to a known good revision
|
||||||
|
for the current branch and target. This is used by repo sync when the
|
||||||
|
--smart-sync option is given.
|
||||||
|
|
||||||
|
The target to use is defined by environment variables TARGET_PRODUCT
|
||||||
|
and TARGET_BUILD_VARIANT. These variables are used to create a string
|
||||||
|
of the form $TARGET_PRODUCT-$TARGET_BUILD_VARIANT, e.g. passion-userdebug.
|
||||||
|
If one of those variables or both are not present, the program will call
|
||||||
|
GetApprovedManifest without the target parameter and the manifest server
|
||||||
|
should choose a reasonable default target.
|
||||||
|
|
||||||
|
GetManifest(tag)
|
||||||
|
|
||||||
|
Return a manifest in which each project is pegged to the revision at
|
||||||
|
the specified tag. This is used by repo sync when the --smart-tag option
|
||||||
|
is given.
|
||||||
|
|
||||||
|
|
||||||
### Element submanifest
|
### Element submanifest
|
||||||
@ -416,14 +433,6 @@ project. Same syntax as the corresponding element of `project`.
|
|||||||
Attribute `upstream`: If specified, overrides the upstream of the original
|
Attribute `upstream`: If specified, overrides the upstream of the original
|
||||||
project. Same syntax as the corresponding element of `project`.
|
project. Same syntax as the corresponding element of `project`.
|
||||||
|
|
||||||
Attribute `base-rev`: If specified, adds a check against the revision
|
|
||||||
to be extended. Manifest parse will fail and give a list of mismatch extends
|
|
||||||
if the revisions being extended have changed since base-rev was set.
|
|
||||||
Intended for use with layered manifests using hash revisions to prevent
|
|
||||||
patch branches hiding newer upstream revisions. Also compares named refs
|
|
||||||
like branches or tags but is misleading if branches are used as base-rev.
|
|
||||||
Same syntax as the corresponding element of `project`.
|
|
||||||
|
|
||||||
### Element annotation
|
### Element annotation
|
||||||
|
|
||||||
Zero or more annotation elements may be specified as children of a
|
Zero or more annotation elements may be specified as children of a
|
||||||
@ -487,14 +496,6 @@ name. Logic otherwise behaves like both are specified.
|
|||||||
Attribute `optional`: Set to true to ignore remove-project elements with no
|
Attribute `optional`: Set to true to ignore remove-project elements with no
|
||||||
matching `project` element.
|
matching `project` element.
|
||||||
|
|
||||||
Attribute `base-rev`: If specified, adds a check against the revision
|
|
||||||
to be removed. Manifest parse will fail and give a list of mismatch removes
|
|
||||||
if the revisions being removed have changed since base-rev was set.
|
|
||||||
Intended for use with layered manifests using hash revisions to prevent
|
|
||||||
patch branches hiding newer upstream revisions. Also compares named refs
|
|
||||||
like branches or tags but is misleading if branches are used as base-rev.
|
|
||||||
Same syntax as the corresponding element of `project`.
|
|
||||||
|
|
||||||
### Element repo-hooks
|
### Element repo-hooks
|
||||||
|
|
||||||
NB: See the [practical documentation](./repo-hooks.md) for using repo hooks.
|
NB: See the [practical documentation](./repo-hooks.md) for using repo hooks.
|
||||||
|
@ -96,9 +96,6 @@ If that tag is valid, then repo will warn and use that commit instead.
|
|||||||
|
|
||||||
If that tag cannot be verified, it gives up and forces the user to resolve.
|
If that tag cannot be verified, it gives up and forces the user to resolve.
|
||||||
|
|
||||||
If env variable `REPO_SKIP_SELF_UPDATE` is defined, this will
|
|
||||||
bypass the self update algorithm.
|
|
||||||
|
|
||||||
### Force an update
|
### Force an update
|
||||||
|
|
||||||
The `repo selfupdate` command can be used to force an immediate update.
|
The `repo selfupdate` command can be used to force an immediate update.
|
||||||
@ -205,7 +202,7 @@ still support them.
|
|||||||
Things in italics are things we used to care about but probably don't anymore.
|
Things in italics are things we used to care about but probably don't anymore.
|
||||||
|
|
||||||
| Date | EOL | [Git][rel-g] | [Python][rel-p] | [SSH][rel-o] | [Ubuntu][rel-u] / [Debian][rel-d] | Git | Python | SSH |
|
| Date | EOL | [Git][rel-g] | [Python][rel-p] | [SSH][rel-o] | [Ubuntu][rel-u] / [Debian][rel-d] | Git | Python | SSH |
|
||||||
|:--------:|:------------:|:------------:|:---------------:|:------------:|-----------------------------------|:---:|:------:|:---:|
|
|:--------:|:------------:|:------------:|:---------------:|:------------:|-----------------------------------|-----|--------|-----|
|
||||||
| Apr 2008 | | | | 5.0 |
|
| Apr 2008 | | | | 5.0 |
|
||||||
| Jun 2008 | | | | 5.1 |
|
| Jun 2008 | | | | 5.1 |
|
||||||
| Oct 2008 | *Oct 2013* | | 2.6.0 | | *10.04 Lucid* - 10.10 Maverick / *Squeeze* |
|
| Oct 2008 | *Oct 2013* | | 2.6.0 | | *10.04 Lucid* - 10.10 Maverick / *Squeeze* |
|
||||||
@ -244,7 +241,7 @@ Things in italics are things we used to care about but probably don't anymore.
|
|||||||
| Feb 2014 | *Dec 2014* | **1.9.0** | | | *14.04 Trusty* |
|
| Feb 2014 | *Dec 2014* | **1.9.0** | | | *14.04 Trusty* |
|
||||||
| Mar 2014 | *Mar 2019* | | *3.4.0* | | *14.04 Trusty* - 15.10 Wily / *Jessie* |
|
| Mar 2014 | *Mar 2019* | | *3.4.0* | | *14.04 Trusty* - 15.10 Wily / *Jessie* |
|
||||||
| Mar 2014 | | | | 6.6 | *14.04 Trusty* - 14.10 Utopic |
|
| Mar 2014 | | | | 6.6 | *14.04 Trusty* - 14.10 Utopic |
|
||||||
| Apr 2014 | *Apr 2024* | | | | *14.04 Trusty* | 1.9.1 | 2.7.5 3.4.0 | 6.6 |
|
| Apr 2014 | *Apr 2022* | | | | *14.04 Trusty* | 1.9.1 | 2.7.5 3.4.0 | 6.6 |
|
||||||
| May 2014 | *Dec 2014* | 2.0.0 |
|
| May 2014 | *Dec 2014* | 2.0.0 |
|
||||||
| Aug 2014 | *Dec 2014* | *2.1.0* | | | 14.10 Utopic - 15.04 Vivid / *Jessie* |
|
| Aug 2014 | *Dec 2014* | *2.1.0* | | | 14.10 Utopic - 15.04 Vivid / *Jessie* |
|
||||||
| Oct 2014 | | | | 6.7 | 15.04 Vivid |
|
| Oct 2014 | | | | 6.7 | 15.04 Vivid |
|
||||||
@ -265,7 +262,7 @@ Things in italics are things we used to care about but probably don't anymore.
|
|||||||
| Jan 2016 | *Jul 2017* | *2.7.0* | | | *16.04 Xenial* |
|
| Jan 2016 | *Jul 2017* | *2.7.0* | | | *16.04 Xenial* |
|
||||||
| Feb 2016 | | | | 7.2 | *16.04 Xenial* |
|
| Feb 2016 | | | | 7.2 | *16.04 Xenial* |
|
||||||
| Mar 2016 | *Jul 2017* | 2.8.0 |
|
| Mar 2016 | *Jul 2017* | 2.8.0 |
|
||||||
| Apr 2016 | *Apr 2026* | | | | *16.04 Xenial* | 2.7.4 | 2.7.11 3.5.1 | 7.2 |
|
| Apr 2016 | *Apr 2024* | | | | *16.04 Xenial* | 2.7.4 | 2.7.11 3.5.1 | 7.2 |
|
||||||
| Jun 2016 | *Jul 2017* | 2.9.0 | | | 16.10 Yakkety |
|
| Jun 2016 | *Jul 2017* | 2.9.0 | | | 16.10 Yakkety |
|
||||||
| Jul 2016 | | | | 7.3 | 16.10 Yakkety |
|
| Jul 2016 | | | | 7.3 | 16.10 Yakkety |
|
||||||
| Sep 2016 | *Sep 2017* | 2.10.0 |
|
| Sep 2016 | *Sep 2017* | 2.10.0 |
|
||||||
@ -315,33 +312,14 @@ Things in italics are things we used to care about but probably don't anymore.
|
|||||||
| Oct 2020 | | | | | 20.10 Groovy | 2.27.0 | 2.7.18 3.8.6 | 8.3 |
|
| Oct 2020 | | | | | 20.10 Groovy | 2.27.0 | 2.7.18 3.8.6 | 8.3 |
|
||||||
| Oct 2020 | **Oct 2025** | | 3.9.0 | | 21.04 Hirsute / **Bullseye** |
|
| Oct 2020 | **Oct 2025** | | 3.9.0 | | 21.04 Hirsute / **Bullseye** |
|
||||||
| Dec 2020 | *Mar 2021* | 2.30.0 | | | 21.04 Hirsute / **Bullseye** |
|
| Dec 2020 | *Mar 2021* | 2.30.0 | | | 21.04 Hirsute / **Bullseye** |
|
||||||
| Mar 2021 | | 2.31.0 | | 8.5 |
|
| Mar 2021 | | 2.31.0 |
|
||||||
|
| Mar 2021 | | | | 8.5 |
|
||||||
| Apr 2021 | | | | 8.6 |
|
| Apr 2021 | | | | 8.6 |
|
||||||
| Apr 2021 | *Jan 2022* | | | | 21.04 Hirsute | 2.30.2 | 2.7.18 3.9.4 | 8.4 |
|
| Apr 2021 | *Jan 2022* | | | | 21.04 Hirsute | 2.30.2 | 2.7.18 3.9.4 | 8.4 |
|
||||||
| Jun 2021 | | 2.32.0 |
|
| Jun 2021 | | 2.32.0 |
|
||||||
| Aug 2021 | | 2.33.0 | | 8.7 |
|
| Aug 2021 | | 2.33.0 |
|
||||||
|
| Aug 2021 | | | | 8.7 |
|
||||||
| Aug 2021 | **Aug 2026** | | | | **Debian 11 Bullseye** | 2.30.2 | 2.7.18 3.9.2 | 8.4 |
|
| Aug 2021 | **Aug 2026** | | | | **Debian 11 Bullseye** | 2.30.2 | 2.7.18 3.9.2 | 8.4 |
|
||||||
| Sep 2021 | | | | 8.8 |
|
|
||||||
| Oct 2021 | | 2.34.0 | 3.10.0 | | **22.04 Jammy** |
|
|
||||||
| Jan 2022 | | 2.35.0 |
|
|
||||||
| Feb 2022 | | | | 8.9 | **22.04 Jammy** |
|
|
||||||
| Apr 2022 | | 2.36.0 | | 9.0 |
|
|
||||||
| Apr 2022 | **Apr 2032** | | | | **22.04 Jammy** | 2.34.1 | 2.7.18 3.10.6 | 8.9 |
|
|
||||||
| Jun 2022 | | 2.37.0 |
|
|
||||||
| Oct 2022 | | 2.38.0 | | 9.1 |
|
|
||||||
| Oct 2022 | | | 3.11.0 | | **Bookworm** |
|
|
||||||
| Dec 2022 | | 2.39.0 | | | **Bookworm** |
|
|
||||||
| Feb 2023 | | | | 9.2 | **Bookworm** |
|
|
||||||
| Mar 2023 | | 2.40.0 | | 9.3 |
|
|
||||||
| Jun 2023 | | 2.41.0 |
|
|
||||||
| Jun 2023 | **Jun 2028** | | | | **Debian 12 Bookworm** | 2.39.2 | 3.11.2 | 9.2 |
|
|
||||||
| Aug 2023 | | 2.42.0 | | 9.4 |
|
|
||||||
| Oct 2023 | | | 3.12.0 | 9.5 |
|
|
||||||
| Nov 2022 | | 2.43.0 |
|
|
||||||
| Dec 2023 | | | | 9.6 |
|
|
||||||
| Feb 2024 | | 2.44.0 |
|
|
||||||
| Mar 2024 | | | | 9.7 |
|
|
||||||
| Oct 2024 | | | 3.13.0 |
|
|
||||||
| **Date** | **EOL** | **[Git][rel-g]** | **[Python][rel-p]** | **[SSH][rel-o]** | **[Ubuntu][rel-u] / [Debian][rel-d]** | **Git** | **Python** | **SSH** |
|
| **Date** | **EOL** | **[Git][rel-g]** | **[Python][rel-p]** | **[SSH][rel-o]** | **[Ubuntu][rel-u] / [Debian][rel-d]** | **Git** | **Python** | **SSH** |
|
||||||
|
|
||||||
|
|
||||||
@ -350,7 +328,7 @@ Things in italics are things we used to care about but probably don't anymore.
|
|||||||
[rel-g]: https://en.wikipedia.org/wiki/Git#Releases
|
[rel-g]: https://en.wikipedia.org/wiki/Git#Releases
|
||||||
[rel-o]: https://www.openssh.com/releasenotes.html
|
[rel-o]: https://www.openssh.com/releasenotes.html
|
||||||
[rel-p]: https://en.wikipedia.org/wiki/History_of_Python#Table_of_versions
|
[rel-p]: https://en.wikipedia.org/wiki/History_of_Python#Table_of_versions
|
||||||
[rel-u]: https://wiki.ubuntu.com/Releases
|
[rel-u]: https://en.wikipedia.org/wiki/Ubuntu_version_history#Table_of_versions
|
||||||
[example announcement]: https://groups.google.com/d/topic/repo-discuss/UGBNismWo1M/discussion
|
[example announcement]: https://groups.google.com/d/topic/repo-discuss/UGBNismWo1M/discussion
|
||||||
[repo-discuss@googlegroups.com]: https://groups.google.com/forum/#!forum/repo-discuss
|
[repo-discuss@googlegroups.com]: https://groups.google.com/forum/#!forum/repo-discuss
|
||||||
[go/repo-release]: https://goto.google.com/repo-release
|
[go/repo-release]: https://goto.google.com/repo-release
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
# repo Smart Syncing
|
|
||||||
|
|
||||||
Repo normally fetches & syncs manifests from the same URL specified during
|
|
||||||
`repo init`, and that often fetches the latest revisions of all projects in
|
|
||||||
the manifest. This flow works well for tracking and developing with the
|
|
||||||
latest code, but often it's desirable to sync to other points. For example,
|
|
||||||
to get a local build matching a specific release or build to reproduce bugs
|
|
||||||
reported by other people.
|
|
||||||
|
|
||||||
Repo's sync subcommand has support for fetching manifests from a server over
|
|
||||||
an XML-RPC connection. The local configuration and network API are defined by
|
|
||||||
repo, but individual projects have to host their own server for the client to
|
|
||||||
communicate with.
|
|
||||||
|
|
||||||
This process is called "smart syncing" -- instead of blindly fetching the latest
|
|
||||||
revision of all projects and getting an unknown state to develop against, the
|
|
||||||
client passes a request to the server and is given a matching manifest that
|
|
||||||
typically specifies specific commits for every project to fetch a known source
|
|
||||||
state.
|
|
||||||
|
|
||||||
[TOC]
|
|
||||||
|
|
||||||
## Manifest Configuration
|
|
||||||
|
|
||||||
The manifest specifies the server to communicate with via the
|
|
||||||
the [`<manifest-server>` element](manifest-format.md#Element-manifest_server)
|
|
||||||
element. This is how the client knows what service to talk to.
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<manifest-server url="https://example.com/your/manifest/server/url" />
|
|
||||||
```
|
|
||||||
|
|
||||||
If the URL starts with `persistent-`, then the
|
|
||||||
[`git-remote-persistent-https` helper](https://github.com/git/git/blob/HEAD/contrib/persistent-https/README)
|
|
||||||
is used to communicate with the server.
|
|
||||||
|
|
||||||
## Credentials
|
|
||||||
|
|
||||||
Credentials may be specified directly in typical `username:password`
|
|
||||||
[URI syntax](https://en.wikipedia.org/wiki/URI#Syntax) in the
|
|
||||||
`<manifest-server>` element directly in the manifest.
|
|
||||||
|
|
||||||
If they are not specified, `repo sync` has `--manifest-server-username=USERNAME`
|
|
||||||
and `--manifest-server-password=PASSWORD` options.
|
|
||||||
|
|
||||||
If those are not used, then repo will look up the host in your
|
|
||||||
[`~/.netrc`](https://docs.python.org/3/library/netrc.html) database.
|
|
||||||
|
|
||||||
When making the connection, cookies matching the host are automatically loaded
|
|
||||||
from the cookiejar specified in
|
|
||||||
[Git's `http.cookiefile` setting](https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpcookieFile).
|
|
||||||
|
|
||||||
## Manifest Server
|
|
||||||
|
|
||||||
Unfortunately, there are no public reference implementations. Google has an
|
|
||||||
internal one for Android, but it is written using Google's internal systems,
|
|
||||||
so wouldn't be that helpful as a reference.
|
|
||||||
|
|
||||||
That said, the XML-RPC API is pretty simple, so any standard XML-RPC server
|
|
||||||
example would do. Google's internal server uses Python's
|
|
||||||
[xmlrpc.server.SimpleXMLRPCDispatcher](https://docs.python.org/3/library/xmlrpc.server.html).
|
|
||||||
|
|
||||||
## Network API
|
|
||||||
|
|
||||||
The manifest server should implement the following RPC methods.
|
|
||||||
|
|
||||||
### GetApprovedManifest
|
|
||||||
|
|
||||||
> `GetApprovedManifest(branch: str, target: Optional[str]) -> str`
|
|
||||||
|
|
||||||
The meaning of `branch` and `target` is not strictly defined. The server may
|
|
||||||
interpret them however it wants. The recommended interpretation is that the
|
|
||||||
`branch` matches the manifest branch, and `target` is an identifier for your
|
|
||||||
project that matches something users would build.
|
|
||||||
|
|
||||||
See the client section below for how repo typically generates these values.
|
|
||||||
|
|
||||||
The server will return a manifest or an error. If it's an error, repo will
|
|
||||||
show the output directly to the user to provide a limited feedback channel.
|
|
||||||
|
|
||||||
If the user's request is ambiguous and could match multiple manifests, the
|
|
||||||
server has to decide whether to pick one automatically (and silently such that
|
|
||||||
the user won't know there were multiple matches), or return an error and force
|
|
||||||
the user to be more specific.
|
|
||||||
|
|
||||||
### GetManifest
|
|
||||||
|
|
||||||
> `GetManifest(tag: str) -> str`
|
|
||||||
|
|
||||||
The meaning of `tag` is not strictly defined. Projects are encouraged to use
|
|
||||||
a system where the tag matches a unique source state.
|
|
||||||
|
|
||||||
See the client section below for how repo typically generates these values.
|
|
||||||
|
|
||||||
The server will return a manifest or an error. If it's an error, repo will
|
|
||||||
show the output directly to the user to provide a limited feedback channel.
|
|
||||||
|
|
||||||
If the user's request is ambiguous and could match multiple manifests, the
|
|
||||||
server has to decide whether to pick one automatically (and silently such that
|
|
||||||
the user won't know there were multiple matches), or return an error and force
|
|
||||||
the user to be more specific.
|
|
||||||
|
|
||||||
## Client Options
|
|
||||||
|
|
||||||
Once repo has successfully downloaded the manifest from the server, it saves a
|
|
||||||
copy into `.repo/manifests/smart_sync_override.xml` so users can examine it.
|
|
||||||
The next time `repo sync` is run, this file is automatically replaced or removed
|
|
||||||
based on the current set of options.
|
|
||||||
|
|
||||||
### --smart-sync
|
|
||||||
|
|
||||||
Repo will call `GetApprovedManifest(branch[, target])`.
|
|
||||||
|
|
||||||
The `branch` is determined by the current manifest branch as specified by
|
|
||||||
`--manifest-branch=BRANCH` when running `repo init`.
|
|
||||||
|
|
||||||
The `target` is defined by environment variables in the order below. If none
|
|
||||||
of them match, then `target` is omitted. These variables were decided as they
|
|
||||||
match the settings Android build environments automatically setup.
|
|
||||||
|
|
||||||
1. `${SYNC_TARGET}`: If defined, the value is used directly.
|
|
||||||
2. `${TARGET_PRODUCT}-${TARGET_RELEASE}-${TARGET_BUILD_VARIANT}`: If these
|
|
||||||
variables are all defined, then they are merged with `-` and used.
|
|
||||||
3. `${TARGET_PRODUCT}-${TARGET_BUILD_VARIANT}`: If these variables are all
|
|
||||||
defined, then they are merged with `-` and used.
|
|
||||||
|
|
||||||
### --smart-tag=TAG
|
|
||||||
|
|
||||||
Repo will call `GetManifest(TAG)`.
|
|
4
error.py
4
error.py
@ -107,8 +107,8 @@ class GitError(RepoError):
|
|||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
class GitAuthError(RepoExitError):
|
class GitcUnsupportedError(RepoExitError):
|
||||||
"""Cannot talk to remote due to auth issue."""
|
"""Gitc no longer supported."""
|
||||||
|
|
||||||
|
|
||||||
class UploadError(RepoError):
|
class UploadError(RepoError):
|
||||||
|
12
event_log.py
12
event_log.py
@ -168,10 +168,8 @@ class EventLog:
|
|||||||
f.write("\n")
|
f.write("\n")
|
||||||
|
|
||||||
|
|
||||||
# An integer id that is unique across this invocation of the program, to be set
|
# An integer id that is unique across this invocation of the program.
|
||||||
# by the first Add event. We can't set it here since it results in leaked
|
_EVENT_ID = multiprocessing.Value("i", 1)
|
||||||
# resources (see: https://issues.gerritcodereview.com/353656374).
|
|
||||||
_EVENT_ID = None
|
|
||||||
|
|
||||||
|
|
||||||
def _NextEventId():
|
def _NextEventId():
|
||||||
@ -180,12 +178,6 @@ def _NextEventId():
|
|||||||
Returns:
|
Returns:
|
||||||
A unique, to this invocation of the program, integer id.
|
A unique, to this invocation of the program, integer id.
|
||||||
"""
|
"""
|
||||||
global _EVENT_ID
|
|
||||||
if _EVENT_ID is None:
|
|
||||||
# There is a small chance of race condition - two parallel processes
|
|
||||||
# setting up _EVENT_ID. However, we expect TASK_COMMAND to happen before
|
|
||||||
# mp kicks in.
|
|
||||||
_EVENT_ID = multiprocessing.Value("i", 1)
|
|
||||||
with _EVENT_ID.get_lock():
|
with _EVENT_ID.get_lock():
|
||||||
val = _EVENT_ID.value
|
val = _EVENT_ID.value
|
||||||
_EVENT_ID.value += 1
|
_EVENT_ID.value += 1
|
||||||
|
@ -33,6 +33,17 @@ from wrapper import Wrapper
|
|||||||
|
|
||||||
|
|
||||||
GIT = "git"
|
GIT = "git"
|
||||||
|
# NB: These do not need to be kept in sync with the repo launcher script.
|
||||||
|
# These may be much newer as it allows the repo launcher to roll between
|
||||||
|
# different repo releases while source versions might require a newer git.
|
||||||
|
#
|
||||||
|
# The soft version is when we start warning users that the version is old and
|
||||||
|
# we'll be dropping support for it. We'll refuse to work with versions older
|
||||||
|
# than the hard version.
|
||||||
|
#
|
||||||
|
# git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty.
|
||||||
|
MIN_GIT_VERSION_SOFT = (1, 9, 1)
|
||||||
|
MIN_GIT_VERSION_HARD = (1, 7, 2)
|
||||||
GIT_DIR = "GIT_DIR"
|
GIT_DIR = "GIT_DIR"
|
||||||
|
|
||||||
LAST_GITDIR = None
|
LAST_GITDIR = None
|
||||||
@ -238,9 +249,9 @@ def _build_env(
|
|||||||
s = p + " " + s
|
s = p + " " + s
|
||||||
env["GIT_CONFIG_PARAMETERS"] = s
|
env["GIT_CONFIG_PARAMETERS"] = s
|
||||||
if "GIT_ALLOW_PROTOCOL" not in env:
|
if "GIT_ALLOW_PROTOCOL" not in env:
|
||||||
env["GIT_ALLOW_PROTOCOL"] = (
|
env[
|
||||||
"file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc"
|
"GIT_ALLOW_PROTOCOL"
|
||||||
)
|
] = "file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc"
|
||||||
env["GIT_HTTP_USER_AGENT"] = user_agent.git
|
env["GIT_HTTP_USER_AGENT"] = user_agent.git
|
||||||
|
|
||||||
if objdir:
|
if objdir:
|
||||||
@ -313,13 +324,10 @@ class GitCommand:
|
|||||||
cwd = None
|
cwd = None
|
||||||
command_name = cmdv[0]
|
command_name = cmdv[0]
|
||||||
command.append(command_name)
|
command.append(command_name)
|
||||||
|
|
||||||
if command_name in ("fetch", "clone"):
|
|
||||||
env["GIT_TERMINAL_PROMPT"] = "0"
|
|
||||||
# Need to use the --progress flag for fetch/clone so output will be
|
# Need to use the --progress flag for fetch/clone so output will be
|
||||||
# displayed as by default git only does progress output if stderr is
|
# displayed as by default git only does progress output if stderr is a
|
||||||
# a TTY.
|
# TTY.
|
||||||
if sys.stderr.isatty():
|
if sys.stderr.isatty() and command_name in ("fetch", "clone"):
|
||||||
if "--progress" not in cmdv and "--quiet" not in cmdv:
|
if "--progress" not in cmdv and "--quiet" not in cmdv:
|
||||||
command.append("--progress")
|
command.append("--progress")
|
||||||
command.extend(cmdv[1:])
|
command.extend(cmdv[1:])
|
||||||
@ -350,9 +358,9 @@ class GitCommand:
|
|||||||
"Project": e.project,
|
"Project": e.project,
|
||||||
"CommandName": command_name,
|
"CommandName": command_name,
|
||||||
"Message": str(e),
|
"Message": str(e),
|
||||||
"ReturnCode": (
|
"ReturnCode": str(e.git_rc)
|
||||||
str(e.git_rc) if e.git_rc is not None else None
|
if e.git_rc is not None
|
||||||
),
|
else None,
|
||||||
"IsError": log_as_error,
|
"IsError": log_as_error,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -90,20 +90,6 @@ class GitConfig:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _getUserConfig():
|
def _getUserConfig():
|
||||||
"""Get the user-specific config file.
|
|
||||||
|
|
||||||
Prefers the XDG config location if available, with fallback to
|
|
||||||
~/.gitconfig
|
|
||||||
|
|
||||||
This matches git behavior:
|
|
||||||
https://git-scm.com/docs/git-config#FILES
|
|
||||||
"""
|
|
||||||
xdg_config_home = os.getenv(
|
|
||||||
"XDG_CONFIG_HOME", os.path.expanduser("~/.config")
|
|
||||||
)
|
|
||||||
xdg_config_file = os.path.join(xdg_config_home, "git", "config")
|
|
||||||
if os.path.exists(xdg_config_file):
|
|
||||||
return xdg_config_file
|
|
||||||
return os.path.expanduser("~/.gitconfig")
|
return os.path.expanduser("~/.gitconfig")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -307,6 +307,8 @@ class Superproject:
|
|||||||
)
|
)
|
||||||
return SyncResult(False, False)
|
return SyncResult(False, False)
|
||||||
|
|
||||||
|
_PrintBetaNotice()
|
||||||
|
|
||||||
should_exit = True
|
should_exit = True
|
||||||
if not self._remote_url:
|
if not self._remote_url:
|
||||||
self._LogWarning(
|
self._LogWarning(
|
||||||
@ -450,6 +452,16 @@ class Superproject:
|
|||||||
return UpdateProjectsResult(manifest_path, False)
|
return UpdateProjectsResult(manifest_path, False)
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=10)
|
||||||
|
def _PrintBetaNotice():
|
||||||
|
"""Print the notice of beta status."""
|
||||||
|
print(
|
||||||
|
"NOTICE: --use-superproject is in beta; report any issues to the "
|
||||||
|
"address described in `repo version`",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=None)
|
@functools.lru_cache(maxsize=None)
|
||||||
def _UseSuperprojectFromConfiguration():
|
def _UseSuperprojectFromConfiguration():
|
||||||
"""Returns the user choice of whether to use superproject."""
|
"""Returns the user choice of whether to use superproject."""
|
||||||
|
@ -130,10 +130,10 @@ class BaseEventLog:
|
|||||||
"time": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
"time": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def StartEvent(self, argv):
|
def StartEvent(self):
|
||||||
"""Append a 'start' event to the current log."""
|
"""Append a 'start' event to the current log."""
|
||||||
start_event = self._CreateEventDict("start")
|
start_event = self._CreateEventDict("start")
|
||||||
start_event["argv"] = argv
|
start_event["argv"] = sys.argv
|
||||||
self._log.append(start_event)
|
self._log.append(start_event)
|
||||||
|
|
||||||
def ExitEvent(self, result):
|
def ExitEvent(self, result):
|
||||||
@ -159,11 +159,9 @@ class BaseEventLog:
|
|||||||
name: Name of the primary command (ex: repo, git)
|
name: Name of the primary command (ex: repo, git)
|
||||||
subcommands: List of the sub-commands (ex: version, init, sync)
|
subcommands: List of the sub-commands (ex: version, init, sync)
|
||||||
"""
|
"""
|
||||||
command_event = self._CreateEventDict("cmd_name")
|
command_event = self._CreateEventDict("command")
|
||||||
name = f"{name}-"
|
|
||||||
name += "-".join(subcommands)
|
|
||||||
command_event["name"] = name
|
command_event["name"] = name
|
||||||
command_event["hierarchy"] = name
|
command_event["subcommands"] = subcommands
|
||||||
self._log.append(command_event)
|
self._log.append(command_event)
|
||||||
|
|
||||||
def LogConfigEvents(self, config, event_dict_name):
|
def LogConfigEvents(self, config, event_dict_name):
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# DO NOT EDIT THIS FILE
|
# From Gerrit Code Review 3.10.0 d5403dbf335ba7d48977fc95170c3f7027c34659
|
||||||
# All updates should be sent upstream: https://gerrit.googlesource.com/gerrit/
|
|
||||||
# This is synced from commit: 62f5bbea67f6dafa6e22a601a0c298214c510caf
|
|
||||||
# DO NOT EDIT THIS FILE
|
|
||||||
#
|
#
|
||||||
# Part of Gerrit Code Review (https://www.gerritcodereview.com/)
|
# Part of Gerrit Code Review (https://www.gerritcodereview.com/)
|
||||||
#
|
#
|
||||||
@ -34,7 +31,8 @@ if test ! -f "$1" ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Do not create a change id if requested
|
# Do not create a change id if requested
|
||||||
case "$(git config --get gerrit.createChangeId)" in
|
create_setting=$(git config --get gerrit.createChangeId)
|
||||||
|
case "$create_setting" in
|
||||||
false)
|
false)
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
|
@ -1,25 +1,33 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# DO NOT EDIT THIS FILE
|
|
||||||
# All updates should be sent upstream: https://github.com/git/git
|
|
||||||
# This is synced from commit: 00e10ef10e161a913893b8cb33aa080d4ca5baa6
|
|
||||||
# DO NOT EDIT THIS FILE
|
|
||||||
#
|
#
|
||||||
# An example hook script to verify if you are on battery, in case you
|
# An example hook script to verify if you are on battery, in case you
|
||||||
# are running Linux or OS X. Called by git-gc --auto with no arguments.
|
# are running Windows, Linux or OS X. Called by git-gc --auto with no
|
||||||
# The hook should exit with non-zero status after issuing an appropriate
|
# arguments. The hook should exit with non-zero status after issuing an
|
||||||
# message if it wants to stop the auto repacking.
|
# appropriate message if it wants to stop the auto repacking.
|
||||||
|
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# This hook is stored in the contrib/hooks directory. Your distribution
|
# This program is distributed in the hope that it will be useful,
|
||||||
# may have put this somewhere else. If you want to use this hook, you
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# should make this script executable then link to it in the repository
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# you would like to use it in.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# For example, if the hook is stored in
|
# You should have received a copy of the GNU General Public License
|
||||||
# /usr/share/git-core/contrib/hooks/pre-auto-gc-battery:
|
# along with this program; if not, write to the Free Software
|
||||||
#
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
# cd /path/to/your/repository.git
|
|
||||||
# ln -sf /usr/share/git-core/contrib/hooks/pre-auto-gc-battery \
|
if uname -s | grep -q "_NT-"
|
||||||
# hooks/pre-auto-gc
|
then
|
||||||
|
if test -x $SYSTEMROOT/System32/Wbem/wmic
|
||||||
|
then
|
||||||
|
STATUS=$(wmic path win32_battery get batterystatus /format:list | tr -d '\r\n')
|
||||||
|
[ "$STATUS" = "BatteryStatus=2" ] && exit 0 || exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
if test -x /sbin/on_ac_power && (/sbin/on_ac_power;test $? -ne 1)
|
if test -x /sbin/on_ac_power && (/sbin/on_ac_power;test $? -ne 1)
|
||||||
then
|
then
|
||||||
@ -40,6 +48,11 @@ elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt |
|
|||||||
grep -q "drawing from 'AC Power'"
|
grep -q "drawing from 'AC Power'"
|
||||||
then
|
then
|
||||||
exit 0
|
exit 0
|
||||||
|
elif test -d /sys/bus/acpi/drivers/battery && test 0 = \
|
||||||
|
"$(find /sys/bus/acpi/drivers/battery/ -type l | wc -l)";
|
||||||
|
then
|
||||||
|
# No battery exists.
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Auto packing deferred; not on AC"
|
echo "Auto packing deferred; not on AC"
|
||||||
|
9
main.py
9
main.py
@ -45,6 +45,7 @@ from command import InteractiveCommand
|
|||||||
from command import MirrorSafeCommand
|
from command import MirrorSafeCommand
|
||||||
from editor import Editor
|
from editor import Editor
|
||||||
from error import DownloadError
|
from error import DownloadError
|
||||||
|
from error import GitcUnsupportedError
|
||||||
from error import InvalidProjectGroupsError
|
from error import InvalidProjectGroupsError
|
||||||
from error import ManifestInvalidRevisionError
|
from error import ManifestInvalidRevisionError
|
||||||
from error import ManifestParseError
|
from error import ManifestParseError
|
||||||
@ -307,6 +308,10 @@ class _Repo:
|
|||||||
outer_client=outer_client,
|
outer_client=outer_client,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if Wrapper().gitc_parse_clientdir(os.getcwd()):
|
||||||
|
logger.error("GITC is not supported.")
|
||||||
|
raise GitcUnsupportedError()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd = self.commands[name](
|
cmd = self.commands[name](
|
||||||
repodir=self.repodir,
|
repodir=self.repodir,
|
||||||
@ -352,7 +357,7 @@ class _Repo:
|
|||||||
start = time.time()
|
start = time.time()
|
||||||
cmd_event = cmd.event_log.Add(name, event_log.TASK_COMMAND, start)
|
cmd_event = cmd.event_log.Add(name, event_log.TASK_COMMAND, start)
|
||||||
cmd.event_log.SetParent(cmd_event)
|
cmd.event_log.SetParent(cmd_event)
|
||||||
git_trace2_event_log.StartEvent(["repo", name] + argv)
|
git_trace2_event_log.StartEvent()
|
||||||
git_trace2_event_log.CommandEvent(name="repo", subcommands=[name])
|
git_trace2_event_log.CommandEvent(name="repo", subcommands=[name])
|
||||||
|
|
||||||
def execute_command_helper():
|
def execute_command_helper():
|
||||||
@ -420,7 +425,7 @@ class _Repo:
|
|||||||
error_info = json.dumps(
|
error_info = json.dumps(
|
||||||
{
|
{
|
||||||
"ErrorType": type(error).__name__,
|
"ErrorType": type(error).__name__,
|
||||||
"Project": str(project),
|
"Project": project,
|
||||||
"Message": str(error),
|
"Message": str(error),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,24 +1,21 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "December 2024" "repo gc" "Repo Manual"
|
.TH REPO "1" "July 2022" "repo gitc-delete" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo gc - manual page for repo gc
|
repo \- repo gitc-delete - manual page for repo gitc-delete
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B repo
|
.B repo
|
||||||
\fI\,gc\/\fR
|
\fI\,gitc-delete\/\fR
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Summary
|
Summary
|
||||||
.PP
|
.PP
|
||||||
Cleaning up internal repo state.
|
Delete a GITC Client.
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.TP
|
.TP
|
||||||
\fB\-h\fR, \fB\-\-help\fR
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
show this help message and exit
|
show this help message and exit
|
||||||
.TP
|
.TP
|
||||||
\fB\-n\fR, \fB\-\-dry\-run\fR
|
\fB\-f\fR, \fB\-\-force\fR
|
||||||
do everything except actually delete
|
force the deletion (no prompt)
|
||||||
.TP
|
|
||||||
\fB\-y\fR, \fB\-\-yes\fR
|
|
||||||
answer yes to all safe prompts
|
|
||||||
.SS Logging options:
|
.SS Logging options:
|
||||||
.TP
|
.TP
|
||||||
\fB\-v\fR, \fB\-\-verbose\fR
|
\fB\-v\fR, \fB\-\-verbose\fR
|
||||||
@ -40,4 +37,8 @@ only operate on this (sub)manifest
|
|||||||
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
|
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
|
||||||
operate on this manifest and its submanifests
|
operate on this manifest and its submanifests
|
||||||
.PP
|
.PP
|
||||||
Run `repo help gc` to view the detailed manual.
|
Run `repo help gitc\-delete` to view the detailed manual.
|
||||||
|
.SH DETAILS
|
||||||
|
.PP
|
||||||
|
This subcommand deletes the current GITC client, deleting the GITC manifest and
|
||||||
|
all locally downloaded sources.
|
175
man/repo-gitc-init.1
Normal file
175
man/repo-gitc-init.1
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
|
.TH REPO "1" "October 2022" "repo gitc-init" "Repo Manual"
|
||||||
|
.SH NAME
|
||||||
|
repo \- repo gitc-init - manual page for repo gitc-init
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B repo
|
||||||
|
\fI\,gitc-init \/\fR[\fI\,options\/\fR] [\fI\,client name\/\fR]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
Summary
|
||||||
|
.PP
|
||||||
|
Initialize a GITC Client.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
|
show this help message and exit
|
||||||
|
.SS Logging options:
|
||||||
|
.TP
|
||||||
|
\fB\-v\fR, \fB\-\-verbose\fR
|
||||||
|
show all output
|
||||||
|
.TP
|
||||||
|
\fB\-q\fR, \fB\-\-quiet\fR
|
||||||
|
only show errors
|
||||||
|
.SS Manifest options:
|
||||||
|
.TP
|
||||||
|
\fB\-u\fR URL, \fB\-\-manifest\-url\fR=\fI\,URL\/\fR
|
||||||
|
manifest repository location
|
||||||
|
.TP
|
||||||
|
\fB\-b\fR REVISION, \fB\-\-manifest\-branch\fR=\fI\,REVISION\/\fR
|
||||||
|
manifest branch or revision (use HEAD for default)
|
||||||
|
.TP
|
||||||
|
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
|
||||||
|
initial manifest file
|
||||||
|
.TP
|
||||||
|
\fB\-g\fR GROUP, \fB\-\-groups\fR=\fI\,GROUP\/\fR
|
||||||
|
restrict manifest projects to ones with specified
|
||||||
|
group(s) [default|all|G1,G2,G3|G4,\-G5,\-G6]
|
||||||
|
.TP
|
||||||
|
\fB\-p\fR PLATFORM, \fB\-\-platform\fR=\fI\,PLATFORM\/\fR
|
||||||
|
restrict manifest projects to ones with a specified
|
||||||
|
platform group [auto|all|none|linux|darwin|...]
|
||||||
|
.TP
|
||||||
|
\fB\-\-submodules\fR
|
||||||
|
sync any submodules associated with the manifest repo
|
||||||
|
.TP
|
||||||
|
\fB\-\-standalone\-manifest\fR
|
||||||
|
download the manifest as a static file rather then
|
||||||
|
create a git checkout of the manifest repo
|
||||||
|
.TP
|
||||||
|
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
|
||||||
|
create a shallow clone of the manifest repo with given
|
||||||
|
depth (0 for full clone); see git clone (default: 0)
|
||||||
|
.SS Manifest (only) checkout options:
|
||||||
|
.TP
|
||||||
|
\fB\-\-current\-branch\fR
|
||||||
|
fetch only current manifest branch from server
|
||||||
|
(default)
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-current\-branch\fR
|
||||||
|
fetch all manifest branches from server
|
||||||
|
.TP
|
||||||
|
\fB\-\-tags\fR
|
||||||
|
fetch tags in the manifest
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-tags\fR
|
||||||
|
don't fetch tags in the manifest
|
||||||
|
.SS Checkout modes:
|
||||||
|
.TP
|
||||||
|
\fB\-\-mirror\fR
|
||||||
|
create a replica of the remote repositories rather
|
||||||
|
than a client working directory
|
||||||
|
.TP
|
||||||
|
\fB\-\-archive\fR
|
||||||
|
checkout an archive instead of a git repository for
|
||||||
|
each project. See git archive.
|
||||||
|
.TP
|
||||||
|
\fB\-\-worktree\fR
|
||||||
|
use git\-worktree to manage projects
|
||||||
|
.SS Project checkout optimizations:
|
||||||
|
.TP
|
||||||
|
\fB\-\-reference\fR=\fI\,DIR\/\fR
|
||||||
|
location of mirror directory
|
||||||
|
.TP
|
||||||
|
\fB\-\-dissociate\fR
|
||||||
|
dissociate from reference mirrors after clone
|
||||||
|
.TP
|
||||||
|
\fB\-\-depth\fR=\fI\,DEPTH\/\fR
|
||||||
|
create a shallow clone with given depth; see git clone
|
||||||
|
.TP
|
||||||
|
\fB\-\-partial\-clone\fR
|
||||||
|
perform partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-partial\-clone\fR
|
||||||
|
disable use of partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
|
||||||
|
.TP
|
||||||
|
\fB\-\-partial\-clone\-exclude\fR=\fI\,PARTIAL_CLONE_EXCLUDE\/\fR
|
||||||
|
exclude the specified projects (a comma\-delimited
|
||||||
|
project names) from partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
|
||||||
|
.TP
|
||||||
|
\fB\-\-clone\-filter\fR=\fI\,CLONE_FILTER\/\fR
|
||||||
|
filter for use with \fB\-\-partial\-clone\fR [default:
|
||||||
|
blob:none]
|
||||||
|
.TP
|
||||||
|
\fB\-\-use\-superproject\fR
|
||||||
|
use the manifest superproject to sync projects;
|
||||||
|
implies \fB\-c\fR
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-use\-superproject\fR
|
||||||
|
disable use of manifest superprojects
|
||||||
|
.TP
|
||||||
|
\fB\-\-clone\-bundle\fR
|
||||||
|
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
|
||||||
|
not \fB\-\-partial\-clone\fR)
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-clone\-bundle\fR
|
||||||
|
disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
|
||||||
|
\fB\-\-partial\-clone\fR)
|
||||||
|
.TP
|
||||||
|
\fB\-\-git\-lfs\fR
|
||||||
|
enable Git LFS support
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-git\-lfs\fR
|
||||||
|
disable Git LFS support
|
||||||
|
.SS repo Version options:
|
||||||
|
.TP
|
||||||
|
\fB\-\-repo\-url\fR=\fI\,URL\/\fR
|
||||||
|
repo repository location ($REPO_URL)
|
||||||
|
.TP
|
||||||
|
\fB\-\-repo\-rev\fR=\fI\,REV\/\fR
|
||||||
|
repo branch or revision ($REPO_REV)
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-repo\-verify\fR
|
||||||
|
do not verify repo source code
|
||||||
|
.SS Other options:
|
||||||
|
.TP
|
||||||
|
\fB\-\-config\-name\fR
|
||||||
|
Always prompt for name/e\-mail
|
||||||
|
.SS GITC options:
|
||||||
|
.TP
|
||||||
|
\fB\-f\fR MANIFEST_FILE, \fB\-\-manifest\-file\fR=\fI\,MANIFEST_FILE\/\fR
|
||||||
|
Optional manifest file to use for this GITC client.
|
||||||
|
.TP
|
||||||
|
\fB\-c\fR GITC_CLIENT, \fB\-\-gitc\-client\fR=\fI\,GITC_CLIENT\/\fR
|
||||||
|
Name of the gitc_client instance to create or modify.
|
||||||
|
.SS Multi\-manifest:
|
||||||
|
.TP
|
||||||
|
\fB\-\-outer\-manifest\fR
|
||||||
|
operate starting at the outermost manifest
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-outer\-manifest\fR
|
||||||
|
do not operate on outer manifests
|
||||||
|
.TP
|
||||||
|
\fB\-\-this\-manifest\-only\fR
|
||||||
|
only operate on this (sub)manifest
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
|
||||||
|
operate on this manifest and its submanifests
|
||||||
|
.PP
|
||||||
|
Run `repo help gitc\-init` to view the detailed manual.
|
||||||
|
.SH DETAILS
|
||||||
|
.PP
|
||||||
|
The 'repo gitc\-init' command is ran to initialize a new GITC client for use with
|
||||||
|
the GITC file system.
|
||||||
|
.PP
|
||||||
|
This command will setup the client directory, initialize repo, just like repo
|
||||||
|
init does, and then downloads the manifest collection and installs it in the
|
||||||
|
\&.repo/directory of the GITC client.
|
||||||
|
.PP
|
||||||
|
Once this is done, a GITC manifest is generated by pulling the HEAD SHA for each
|
||||||
|
project and generates the properly formatted XML file and installs it as
|
||||||
|
\&.manifest in the GITC client directory.
|
||||||
|
.PP
|
||||||
|
The \fB\-c\fR argument is required to specify the GITC client name.
|
||||||
|
.PP
|
||||||
|
The optional \fB\-f\fR argument can be used to specify the manifest file to use for
|
||||||
|
this GITC client.
|
@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "September 2024" "repo init" "Repo Manual"
|
.TH REPO "1" "October 2022" "repo init" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo init - manual page for repo init
|
repo \- repo init - manual page for repo init
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -28,11 +28,6 @@ manifest repository location
|
|||||||
\fB\-b\fR REVISION, \fB\-\-manifest\-branch\fR=\fI\,REVISION\/\fR
|
\fB\-b\fR REVISION, \fB\-\-manifest\-branch\fR=\fI\,REVISION\/\fR
|
||||||
manifest branch or revision (use HEAD for default)
|
manifest branch or revision (use HEAD for default)
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-manifest\-upstream\-branch\fR=\fI\,BRANCH\/\fR
|
|
||||||
when a commit is provided to \fB\-\-manifest\-branch\fR, this
|
|
||||||
is the name of the git ref in which the commit can be
|
|
||||||
found
|
|
||||||
.TP
|
|
||||||
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
|
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
|
||||||
initial manifest file
|
initial manifest file
|
||||||
.TP
|
.TP
|
||||||
@ -168,10 +163,6 @@ The optional \fB\-b\fR argument can be used to select the manifest branch to che
|
|||||||
and use. If no branch is specified, the remote's default branch is used. This is
|
and use. If no branch is specified, the remote's default branch is used. This is
|
||||||
equivalent to using \fB\-b\fR HEAD.
|
equivalent to using \fB\-b\fR HEAD.
|
||||||
.PP
|
.PP
|
||||||
The optional \fB\-\-manifest\-upstream\-branch\fR argument can be used when a commit is
|
|
||||||
provided to \fB\-\-manifest\-branch\fR (or \fB\-b\fR), to specify the name of the git ref in
|
|
||||||
which the commit can be found.
|
|
||||||
.PP
|
|
||||||
The optional \fB\-m\fR argument can be used to specify an alternate manifest to be
|
The optional \fB\-m\fR argument can be used to specify an alternate manifest to be
|
||||||
used. If no manifest is specified, the manifest default.xml will be used.
|
used. If no manifest is specified, the manifest default.xml will be used.
|
||||||
.PP
|
.PP
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "December 2024" "repo manifest" "Repo Manual"
|
.TH REPO "1" "October 2022" "repo manifest" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo manifest - manual page for repo manifest
|
repo \- repo manifest - manual page for repo manifest
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -192,13 +192,10 @@ CDATA #IMPLIED>
|
|||||||
<!ATTLIST extend\-project remote CDATA #IMPLIED>
|
<!ATTLIST extend\-project remote CDATA #IMPLIED>
|
||||||
<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
|
<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
|
||||||
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
|
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
|
||||||
<!ATTLIST extend\-project base\-rev CDATA #IMPLIED>
|
|
||||||
.IP
|
.IP
|
||||||
<!ELEMENT remove\-project EMPTY>
|
<!ELEMENT remove\-project EMPTY>
|
||||||
<!ATTLIST remove\-project name CDATA #IMPLIED>
|
<!ATTLIST remove\-project name CDATA #REQUIRED>
|
||||||
<!ATTLIST remove\-project path CDATA #IMPLIED>
|
|
||||||
<!ATTLIST remove\-project optional CDATA #IMPLIED>
|
<!ATTLIST remove\-project optional CDATA #IMPLIED>
|
||||||
<!ATTLIST remove\-project base\-rev CDATA #IMPLIED>
|
|
||||||
.IP
|
.IP
|
||||||
<!ELEMENT repo\-hooks EMPTY>
|
<!ELEMENT repo\-hooks EMPTY>
|
||||||
<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED>
|
<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED>
|
||||||
@ -215,7 +212,6 @@ CDATA #IMPLIED>
|
|||||||
<!ELEMENT include EMPTY>
|
<!ELEMENT include EMPTY>
|
||||||
<!ATTLIST include name CDATA #REQUIRED>
|
<!ATTLIST include name CDATA #REQUIRED>
|
||||||
<!ATTLIST include groups CDATA #IMPLIED>
|
<!ATTLIST include groups CDATA #IMPLIED>
|
||||||
<!ATTLIST include revision CDATA #IMPLIED>
|
|
||||||
.PP
|
.PP
|
||||||
]>
|
]>
|
||||||
```
|
```
|
||||||
@ -497,14 +493,6 @@ project. Same syntax as the corresponding element of `project`.
|
|||||||
Attribute `upstream`: If specified, overrides the upstream of the original
|
Attribute `upstream`: If specified, overrides the upstream of the original
|
||||||
project. Same syntax as the corresponding element of `project`.
|
project. Same syntax as the corresponding element of `project`.
|
||||||
.PP
|
.PP
|
||||||
Attribute `base\-rev`: If specified, adds a check against the revision to be
|
|
||||||
extended. Manifest parse will fail and give a list of mismatch extends if the
|
|
||||||
revisions being extended have changed since base\-rev was set. Intended for use
|
|
||||||
with layered manifests using hash revisions to prevent patch branches hiding
|
|
||||||
newer upstream revisions. Also compares named refs like branches or tags but is
|
|
||||||
misleading if branches are used as base\-rev. Same syntax as the corresponding
|
|
||||||
element of `project`.
|
|
||||||
.PP
|
|
||||||
Element annotation
|
Element annotation
|
||||||
.PP
|
.PP
|
||||||
Zero or more annotation elements may be specified as children of a project or
|
Zero or more annotation elements may be specified as children of a project or
|
||||||
@ -545,35 +533,16 @@ the repo client.
|
|||||||
.PP
|
.PP
|
||||||
Element remove\-project
|
Element remove\-project
|
||||||
.PP
|
.PP
|
||||||
Deletes a project from the internal manifest table, possibly allowing a
|
Deletes the named project from the internal manifest table, possibly allowing a
|
||||||
subsequent project element in the same manifest file to replace the project with
|
subsequent project element in the same manifest file to replace the project with
|
||||||
a different source.
|
a different source.
|
||||||
.PP
|
.PP
|
||||||
This element is mostly useful in a local manifest file, where the user can
|
This element is mostly useful in a local manifest file, where the user can
|
||||||
remove a project, and possibly replace it with their own definition.
|
remove a project, and possibly replace it with their own definition.
|
||||||
.PP
|
.PP
|
||||||
The project `name` or project `path` can be used to specify the remove target
|
|
||||||
meaning one of them is required. If only name is specified, all projects with
|
|
||||||
that name are removed.
|
|
||||||
.PP
|
|
||||||
If both name and path are specified, only projects with the same name and path
|
|
||||||
are removed, meaning projects with the same name but in other locations are
|
|
||||||
kept.
|
|
||||||
.PP
|
|
||||||
If only path is specified, a matching project is removed regardless of its name.
|
|
||||||
Logic otherwise behaves like both are specified.
|
|
||||||
.PP
|
|
||||||
Attribute `optional`: Set to true to ignore remove\-project elements with no
|
Attribute `optional`: Set to true to ignore remove\-project elements with no
|
||||||
matching `project` element.
|
matching `project` element.
|
||||||
.PP
|
.PP
|
||||||
Attribute `base\-rev`: If specified, adds a check against the revision to be
|
|
||||||
removed. Manifest parse will fail and give a list of mismatch removes if the
|
|
||||||
revisions being removed have changed since base\-rev was set. Intended for use
|
|
||||||
with layered manifests using hash revisions to prevent patch branches hiding
|
|
||||||
newer upstream revisions. Also compares named refs like branches or tags but is
|
|
||||||
misleading if branches are used as base\-rev. Same syntax as the corresponding
|
|
||||||
element of `project`.
|
|
||||||
.PP
|
|
||||||
Element repo\-hooks
|
Element repo\-hooks
|
||||||
.PP
|
.PP
|
||||||
NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks.
|
NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks.
|
||||||
@ -639,9 +608,6 @@ included manifest belong. This appends and recurses, meaning all projects in
|
|||||||
included manifests carry all parent include groups. Same syntax as the
|
included manifests carry all parent include groups. Same syntax as the
|
||||||
corresponding element of `project`.
|
corresponding element of `project`.
|
||||||
.PP
|
.PP
|
||||||
Attribute `revision`: Name of a Git branch (e.g. `main` or `refs/heads/main`)
|
|
||||||
default to which all projects in the included manifest belong.
|
|
||||||
.PP
|
|
||||||
Local Manifests
|
Local Manifests
|
||||||
.PP
|
.PP
|
||||||
Additional remotes and projects may be added through local manifest files stored
|
Additional remotes and projects may be added through local manifest files stored
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "September 2024" "repo smartsync" "Repo Manual"
|
.TH REPO "1" "November 2022" "repo smartsync" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo smartsync - manual page for repo smartsync
|
repo \- repo smartsync - manual page for repo smartsync
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -37,20 +37,11 @@ overwrite an existing git directory if it needs to
|
|||||||
point to a different object directory. WARNING: this
|
point to a different object directory. WARNING: this
|
||||||
may cause loss of data
|
may cause loss of data
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-force\-checkout\fR
|
|
||||||
force checkout even if it results in throwing away
|
|
||||||
uncommitted modifications. WARNING: this may cause
|
|
||||||
loss of data
|
|
||||||
.TP
|
|
||||||
\fB\-\-force\-remove\-dirty\fR
|
\fB\-\-force\-remove\-dirty\fR
|
||||||
force remove projects with uncommitted modifications
|
force remove projects with uncommitted modifications
|
||||||
if projects no longer exist in the manifest. WARNING:
|
if projects no longer exist in the manifest. WARNING:
|
||||||
this may cause loss of data
|
this may cause loss of data
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-rebase\fR
|
|
||||||
rebase local commits regardless of whether they are
|
|
||||||
published
|
|
||||||
.TP
|
|
||||||
\fB\-l\fR, \fB\-\-local\-only\fR
|
\fB\-l\fR, \fB\-\-local\-only\fR
|
||||||
only update working tree, don't fetch
|
only update working tree, don't fetch
|
||||||
.TP
|
.TP
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "September 2024" "repo sync" "Repo Manual"
|
.TH REPO "1" "November 2022" "repo sync" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo sync - manual page for repo sync
|
repo \- repo sync - manual page for repo sync
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -37,20 +37,11 @@ overwrite an existing git directory if it needs to
|
|||||||
point to a different object directory. WARNING: this
|
point to a different object directory. WARNING: this
|
||||||
may cause loss of data
|
may cause loss of data
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-force\-checkout\fR
|
|
||||||
force checkout even if it results in throwing away
|
|
||||||
uncommitted modifications. WARNING: this may cause
|
|
||||||
loss of data
|
|
||||||
.TP
|
|
||||||
\fB\-\-force\-remove\-dirty\fR
|
\fB\-\-force\-remove\-dirty\fR
|
||||||
force remove projects with uncommitted modifications
|
force remove projects with uncommitted modifications
|
||||||
if projects no longer exist in the manifest. WARNING:
|
if projects no longer exist in the manifest. WARNING:
|
||||||
this may cause loss of data
|
this may cause loss of data
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-rebase\fR
|
|
||||||
rebase local commits regardless of whether they are
|
|
||||||
published
|
|
||||||
.TP
|
|
||||||
\fB\-l\fR, \fB\-\-local\-only\fR
|
\fB\-l\fR, \fB\-\-local\-only\fR
|
||||||
only update working tree, don't fetch
|
only update working tree, don't fetch
|
||||||
.TP
|
.TP
|
||||||
@ -194,11 +185,6 @@ The \fB\-\-force\-sync\fR option can be used to overwrite existing git directori
|
|||||||
they have previously been linked to a different object directory. WARNING: This
|
they have previously been linked to a different object directory. WARNING: This
|
||||||
may cause data to be lost since refs may be removed when overwriting.
|
may cause data to be lost since refs may be removed when overwriting.
|
||||||
.PP
|
.PP
|
||||||
The \fB\-\-force\-checkout\fR option can be used to force git to switch revs even if the
|
|
||||||
index or the working tree differs from HEAD, and if there are untracked files.
|
|
||||||
WARNING: This may cause data to be lost since uncommitted changes may be
|
|
||||||
removed.
|
|
||||||
.PP
|
|
||||||
The \fB\-\-force\-remove\-dirty\fR option can be used to remove previously used projects
|
The \fB\-\-force\-remove\-dirty\fR option can be used to remove previously used projects
|
||||||
with uncommitted changes. WARNING: This may cause data to be lost since
|
with uncommitted changes. WARNING: This may cause data to be lost since
|
||||||
uncommitted changes may be removed with projects that no longer exist in the
|
uncommitted changes may be removed with projects that no longer exist in the
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "June 2024" "repo upload" "Repo Manual"
|
.TH REPO "1" "August 2022" "repo upload" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo upload - manual page for repo upload
|
repo \- repo upload - manual page for repo upload
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -18,11 +18,8 @@ show this help message and exit
|
|||||||
number of jobs to run in parallel (default: based on
|
number of jobs to run in parallel (default: based on
|
||||||
number of CPU cores)
|
number of CPU cores)
|
||||||
.TP
|
.TP
|
||||||
\fB\-t\fR, \fB\-\-topic\-branch\fR
|
\fB\-t\fR
|
||||||
set the topic to the local branch name
|
send local branch name to Gerrit Code Review
|
||||||
.TP
|
|
||||||
\fB\-\-topic\fR=\fI\,TOPIC\/\fR
|
|
||||||
set topic for the change
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-hashtag\fR=\fI\,HASHTAGS\/\fR, \fB\-\-ht\fR=\fI\,HASHTAGS\/\fR
|
\fB\-\-hashtag\fR=\fI\,HASHTAGS\/\fR, \fB\-\-ht\fR=\fI\,HASHTAGS\/\fR
|
||||||
add hashtags (comma delimited) to the review
|
add hashtags (comma delimited) to the review
|
||||||
@ -33,9 +30,6 @@ add local branch name as a hashtag
|
|||||||
\fB\-l\fR LABELS, \fB\-\-label\fR=\fI\,LABELS\/\fR
|
\fB\-l\fR LABELS, \fB\-\-label\fR=\fI\,LABELS\/\fR
|
||||||
add a label when uploading
|
add a label when uploading
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-pd\fR=\fI\,PATCHSET_DESCRIPTION\/\fR, \fB\-\-patchset\-description\fR=\fI\,PATCHSET_DESCRIPTION\/\fR
|
|
||||||
description for patchset
|
|
||||||
.TP
|
|
||||||
\fB\-\-re\fR=\fI\,REVIEWERS\/\fR, \fB\-\-reviewers\fR=\fI\,REVIEWERS\/\fR
|
\fB\-\-re\fR=\fI\,REVIEWERS\/\fR, \fB\-\-reviewers\fR=\fI\,REVIEWERS\/\fR
|
||||||
request reviews from these people
|
request reviews from these people
|
||||||
.TP
|
.TP
|
||||||
@ -204,12 +198,6 @@ review.URL.uploadnotify:
|
|||||||
Control e\-mail notifications when uploading.
|
Control e\-mail notifications when uploading.
|
||||||
https://gerrit\-review.googlesource.com/Documentation/user\-upload.html#notify
|
https://gerrit\-review.googlesource.com/Documentation/user\-upload.html#notify
|
||||||
.PP
|
.PP
|
||||||
review.URL.uploadwarningthreshold:
|
|
||||||
.PP
|
|
||||||
Repo will warn you if you are attempting to upload a large number of commits in
|
|
||||||
one or more branches. By default, the threshold is five commits. This option
|
|
||||||
allows you to override the warning threshold to a different value.
|
|
||||||
.PP
|
|
||||||
References
|
References
|
||||||
.PP
|
.PP
|
||||||
Gerrit Code Review: https://www.gerritcodereview.com/
|
Gerrit Code Review: https://www.gerritcodereview.com/
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "December 2024" "repo" "Repo Manual"
|
.TH REPO "1" "June 2023" "repo" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repository management tool built on top of git
|
repo \- repository management tool built on top of git
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -79,8 +79,11 @@ Download and checkout a change
|
|||||||
forall
|
forall
|
||||||
Run a shell command in each project
|
Run a shell command in each project
|
||||||
.TP
|
.TP
|
||||||
gc
|
gitc\-delete
|
||||||
Cleaning up internal repo state.
|
Delete a GITC Client.
|
||||||
|
.TP
|
||||||
|
gitc\-init
|
||||||
|
Initialize a GITC Client.
|
||||||
.TP
|
.TP
|
||||||
grep
|
grep
|
||||||
Print lines matching a pattern
|
Print lines matching a pattern
|
||||||
|
@ -435,6 +435,11 @@ class XmlManifest:
|
|||||||
self.parent_groups = parent_groups
|
self.parent_groups = parent_groups
|
||||||
self.default_groups = default_groups
|
self.default_groups = default_groups
|
||||||
|
|
||||||
|
if outer_client and self.isGitcClient:
|
||||||
|
raise ManifestParseError(
|
||||||
|
"Multi-manifest is incompatible with `gitc-init`"
|
||||||
|
)
|
||||||
|
|
||||||
if submanifest_path and not outer_client:
|
if submanifest_path and not outer_client:
|
||||||
# If passing a submanifest_path, there must be an outer_client.
|
# If passing a submanifest_path, there must be an outer_client.
|
||||||
raise ManifestParseError(f"Bad call to {self.__class__.__name__}")
|
raise ManifestParseError(f"Bad call to {self.__class__.__name__}")
|
||||||
@ -1014,9 +1019,9 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
|
|
||||||
def SetManifestOverride(self, path):
|
def SetManifestOverride(self, path):
|
||||||
"""Override manifestFile. The caller must call Unload()"""
|
"""Override manifestFile. The caller must call Unload()"""
|
||||||
self._outer_client.manifest.manifestFileOverrides[self.path_prefix] = (
|
self._outer_client.manifest.manifestFileOverrides[
|
||||||
path
|
self.path_prefix
|
||||||
)
|
] = path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def UseLocalManifests(self):
|
def UseLocalManifests(self):
|
||||||
@ -1445,7 +1450,6 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
|
|
||||||
repo_hooks_project = None
|
repo_hooks_project = None
|
||||||
enabled_repo_hooks = None
|
enabled_repo_hooks = None
|
||||||
failed_revision_changes = []
|
|
||||||
for node in itertools.chain(*node_list):
|
for node in itertools.chain(*node_list):
|
||||||
if node.nodeName == "project":
|
if node.nodeName == "project":
|
||||||
project = self._ParseProject(node)
|
project = self._ParseProject(node)
|
||||||
@ -1472,7 +1476,6 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
remote = self._get_remote(node)
|
remote = self._get_remote(node)
|
||||||
dest_branch = node.getAttribute("dest-branch")
|
dest_branch = node.getAttribute("dest-branch")
|
||||||
upstream = node.getAttribute("upstream")
|
upstream = node.getAttribute("upstream")
|
||||||
base_revision = node.getAttribute("base-rev")
|
|
||||||
|
|
||||||
named_projects = self._projects[name]
|
named_projects = self._projects[name]
|
||||||
if dest_path and not path and len(named_projects) > 1:
|
if dest_path and not path and len(named_projects) > 1:
|
||||||
@ -1486,13 +1489,6 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
if groups:
|
if groups:
|
||||||
p.groups.extend(groups)
|
p.groups.extend(groups)
|
||||||
if revision:
|
if revision:
|
||||||
if base_revision:
|
|
||||||
if p.revisionExpr != base_revision:
|
|
||||||
failed_revision_changes.append(
|
|
||||||
"extend-project name %s mismatch base "
|
|
||||||
"%s vs revision %s"
|
|
||||||
% (name, base_revision, p.revisionExpr)
|
|
||||||
)
|
|
||||||
p.SetRevision(revision)
|
p.SetRevision(revision)
|
||||||
|
|
||||||
if remote_name:
|
if remote_name:
|
||||||
@ -1567,7 +1563,6 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
if node.nodeName == "remove-project":
|
if node.nodeName == "remove-project":
|
||||||
name = node.getAttribute("name")
|
name = node.getAttribute("name")
|
||||||
path = node.getAttribute("path")
|
path = node.getAttribute("path")
|
||||||
base_revision = node.getAttribute("base-rev")
|
|
||||||
|
|
||||||
# Name or path needed.
|
# Name or path needed.
|
||||||
if not name and not path:
|
if not name and not path:
|
||||||
@ -1581,13 +1576,6 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
for projname, projects in list(self._projects.items()):
|
for projname, projects in list(self._projects.items()):
|
||||||
for p in projects:
|
for p in projects:
|
||||||
if name == projname and not path:
|
if name == projname and not path:
|
||||||
if base_revision:
|
|
||||||
if p.revisionExpr != base_revision:
|
|
||||||
failed_revision_changes.append(
|
|
||||||
"remove-project name %s mismatch base "
|
|
||||||
"%s vs revision %s"
|
|
||||||
% (name, base_revision, p.revisionExpr)
|
|
||||||
)
|
|
||||||
del self._paths[p.relpath]
|
del self._paths[p.relpath]
|
||||||
if not removed_project:
|
if not removed_project:
|
||||||
del self._projects[name]
|
del self._projects[name]
|
||||||
@ -1595,17 +1583,6 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
elif path == p.relpath and (
|
elif path == p.relpath and (
|
||||||
name == projname or not name
|
name == projname or not name
|
||||||
):
|
):
|
||||||
if base_revision:
|
|
||||||
if p.revisionExpr != base_revision:
|
|
||||||
failed_revision_changes.append(
|
|
||||||
"remove-project path %s mismatch base "
|
|
||||||
"%s vs revision %s"
|
|
||||||
% (
|
|
||||||
p.relpath,
|
|
||||||
base_revision,
|
|
||||||
p.revisionExpr,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._projects[projname].remove(p)
|
self._projects[projname].remove(p)
|
||||||
del self._paths[p.relpath]
|
del self._paths[p.relpath]
|
||||||
removed_project = p.name
|
removed_project = p.name
|
||||||
@ -1625,13 +1602,6 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
"project: %s" % node.toxml()
|
"project: %s" % node.toxml()
|
||||||
)
|
)
|
||||||
|
|
||||||
if failed_revision_changes:
|
|
||||||
raise ManifestParseError(
|
|
||||||
"revision base check failed, rebase patches and update "
|
|
||||||
"base revs for: ",
|
|
||||||
failed_revision_changes,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Store repo hooks project information.
|
# Store repo hooks project information.
|
||||||
if repo_hooks_project:
|
if repo_hooks_project:
|
||||||
# Store a reference to the Project.
|
# Store a reference to the Project.
|
||||||
@ -2056,12 +2026,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
path = path.rstrip("/")
|
path = path.rstrip("/")
|
||||||
name = name.rstrip("/")
|
name = name.rstrip("/")
|
||||||
relpath = self._JoinRelpath(parent.relpath, path)
|
relpath = self._JoinRelpath(parent.relpath, path)
|
||||||
subprojects = os.path.join(parent.gitdir, "subprojects", f"{path}.git")
|
gitdir = os.path.join(parent.gitdir, "subprojects", "%s.git" % path)
|
||||||
modules = os.path.join(parent.gitdir, "modules", path)
|
|
||||||
if platform_utils.isdir(subprojects):
|
|
||||||
gitdir = subprojects
|
|
||||||
else:
|
|
||||||
gitdir = modules
|
|
||||||
objdir = os.path.join(
|
objdir = os.path.join(
|
||||||
parent.gitdir, "subproject-objects", "%s.git" % name
|
parent.gitdir, "subproject-objects", "%s.git" % name
|
||||||
)
|
)
|
||||||
@ -2112,22 +2077,22 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
# implementation:
|
# implementation:
|
||||||
# https://eclipse.googlesource.com/jgit/jgit/+/9110037e3e9461ff4dac22fee84ef3694ed57648/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java#884
|
# https://eclipse.googlesource.com/jgit/jgit/+/9110037e3e9461ff4dac22fee84ef3694ed57648/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java#884
|
||||||
BAD_CODEPOINTS = {
|
BAD_CODEPOINTS = {
|
||||||
"\u200c", # ZERO WIDTH NON-JOINER
|
"\u200C", # ZERO WIDTH NON-JOINER
|
||||||
"\u200d", # ZERO WIDTH JOINER
|
"\u200D", # ZERO WIDTH JOINER
|
||||||
"\u200e", # LEFT-TO-RIGHT MARK
|
"\u200E", # LEFT-TO-RIGHT MARK
|
||||||
"\u200f", # RIGHT-TO-LEFT MARK
|
"\u200F", # RIGHT-TO-LEFT MARK
|
||||||
"\u202a", # LEFT-TO-RIGHT EMBEDDING
|
"\u202A", # LEFT-TO-RIGHT EMBEDDING
|
||||||
"\u202b", # RIGHT-TO-LEFT EMBEDDING
|
"\u202B", # RIGHT-TO-LEFT EMBEDDING
|
||||||
"\u202c", # POP DIRECTIONAL FORMATTING
|
"\u202C", # POP DIRECTIONAL FORMATTING
|
||||||
"\u202d", # LEFT-TO-RIGHT OVERRIDE
|
"\u202D", # LEFT-TO-RIGHT OVERRIDE
|
||||||
"\u202e", # RIGHT-TO-LEFT OVERRIDE
|
"\u202E", # RIGHT-TO-LEFT OVERRIDE
|
||||||
"\u206a", # INHIBIT SYMMETRIC SWAPPING
|
"\u206A", # INHIBIT SYMMETRIC SWAPPING
|
||||||
"\u206b", # ACTIVATE SYMMETRIC SWAPPING
|
"\u206B", # ACTIVATE SYMMETRIC SWAPPING
|
||||||
"\u206c", # INHIBIT ARABIC FORM SHAPING
|
"\u206C", # INHIBIT ARABIC FORM SHAPING
|
||||||
"\u206d", # ACTIVATE ARABIC FORM SHAPING
|
"\u206D", # ACTIVATE ARABIC FORM SHAPING
|
||||||
"\u206e", # NATIONAL DIGIT SHAPES
|
"\u206E", # NATIONAL DIGIT SHAPES
|
||||||
"\u206f", # NOMINAL DIGIT SHAPES
|
"\u206F", # NOMINAL DIGIT SHAPES
|
||||||
"\ufeff", # ZERO WIDTH NO-BREAK SPACE
|
"\uFEFF", # ZERO WIDTH NO-BREAK SPACE
|
||||||
}
|
}
|
||||||
if BAD_CODEPOINTS & path_codepoints:
|
if BAD_CODEPOINTS & path_codepoints:
|
||||||
# This message is more expansive than reality, but should be fine.
|
# This message is more expansive than reality, but should be fine.
|
||||||
@ -2325,6 +2290,7 @@ class RepoClient(XmlManifest):
|
|||||||
submanifest_path: The submanifest root relative to the repo root.
|
submanifest_path: The submanifest root relative to the repo root.
|
||||||
**kwargs: Additional keyword arguments, passed to XmlManifest.
|
**kwargs: Additional keyword arguments, passed to XmlManifest.
|
||||||
"""
|
"""
|
||||||
|
self.isGitcClient = False
|
||||||
submanifest_path = submanifest_path or ""
|
submanifest_path = submanifest_path or ""
|
||||||
if submanifest_path:
|
if submanifest_path:
|
||||||
self._CheckLocalPath(submanifest_path)
|
self._CheckLocalPath(submanifest_path)
|
||||||
|
2
pager.py
2
pager.py
@ -40,7 +40,7 @@ def RunPager(globalConfig):
|
|||||||
|
|
||||||
|
|
||||||
def TerminatePager():
|
def TerminatePager():
|
||||||
global pager_process
|
global pager_process, old_stdout, old_stderr
|
||||||
if pager_process:
|
if pager_process:
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
@ -156,12 +156,6 @@ def remove(path, missing_ok=False):
|
|||||||
os.rmdir(longpath)
|
os.rmdir(longpath)
|
||||||
else:
|
else:
|
||||||
os.remove(longpath)
|
os.remove(longpath)
|
||||||
elif (
|
|
||||||
e.errno == errno.EROFS
|
|
||||||
and missing_ok
|
|
||||||
and not os.path.exists(longpath)
|
|
||||||
):
|
|
||||||
pass
|
|
||||||
elif missing_ok and e.errno == errno.ENOENT:
|
elif missing_ok and e.errno == errno.ENOENT:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@ -257,3 +251,32 @@ def readlink(path):
|
|||||||
return platform_utils_win32.readlink(_makelongpath(path))
|
return platform_utils_win32.readlink(_makelongpath(path))
|
||||||
else:
|
else:
|
||||||
return os.readlink(path)
|
return os.readlink(path)
|
||||||
|
|
||||||
|
|
||||||
|
def realpath(path):
|
||||||
|
"""Return the canonical path of the specified filename, eliminating
|
||||||
|
any symbolic links encountered in the path.
|
||||||
|
|
||||||
|
Availability: Windows, Unix.
|
||||||
|
"""
|
||||||
|
if isWindows():
|
||||||
|
current_path = os.path.abspath(path)
|
||||||
|
path_tail = []
|
||||||
|
for c in range(0, 100): # Avoid cycles
|
||||||
|
if islink(current_path):
|
||||||
|
target = readlink(current_path)
|
||||||
|
current_path = os.path.join(
|
||||||
|
os.path.dirname(current_path), target
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
basename = os.path.basename(current_path)
|
||||||
|
if basename == "":
|
||||||
|
path_tail.append(current_path)
|
||||||
|
break
|
||||||
|
path_tail.append(basename)
|
||||||
|
current_path = os.path.dirname(current_path)
|
||||||
|
path_tail.reverse()
|
||||||
|
result = os.path.normpath(os.path.join(*path_tail))
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return os.path.realpath(path)
|
||||||
|
13
progress.py
13
progress.py
@ -100,7 +100,6 @@ class Progress:
|
|||||||
self._show = not delay
|
self._show = not delay
|
||||||
self._units = units
|
self._units = units
|
||||||
self._elide = elide and _TTY
|
self._elide = elide and _TTY
|
||||||
self._quiet = quiet
|
|
||||||
|
|
||||||
# Only show the active jobs section if we run more than one in parallel.
|
# Only show the active jobs section if we run more than one in parallel.
|
||||||
self._show_jobs = False
|
self._show_jobs = False
|
||||||
@ -115,7 +114,13 @@ class Progress:
|
|||||||
)
|
)
|
||||||
self._update_thread.daemon = True
|
self._update_thread.daemon = True
|
||||||
|
|
||||||
if not quiet and show_elapsed:
|
# When quiet, never show any output. It's a bit hacky, but reusing the
|
||||||
|
# existing logic that delays initial output keeps the rest of the class
|
||||||
|
# clean. Basically we set the start time to years in the future.
|
||||||
|
if quiet:
|
||||||
|
self._show = False
|
||||||
|
self._start += 2**32
|
||||||
|
elif show_elapsed:
|
||||||
self._update_thread.start()
|
self._update_thread.start()
|
||||||
|
|
||||||
def _update_loop(self):
|
def _update_loop(self):
|
||||||
@ -155,7 +160,7 @@ class Progress:
|
|||||||
msg = self._last_msg
|
msg = self._last_msg
|
||||||
self._last_msg = msg
|
self._last_msg = msg
|
||||||
|
|
||||||
if not _TTY or IsTraceToStderr() or self._quiet:
|
if not _TTY or IsTraceToStderr():
|
||||||
return
|
return
|
||||||
|
|
||||||
elapsed_sec = time.time() - self._start
|
elapsed_sec = time.time() - self._start
|
||||||
@ -197,7 +202,7 @@ class Progress:
|
|||||||
|
|
||||||
def end(self):
|
def end(self):
|
||||||
self._update_event.set()
|
self._update_event.set()
|
||||||
if not _TTY or IsTraceToStderr() or self._quiet:
|
if not _TTY or IsTraceToStderr() or not self._show:
|
||||||
return
|
return
|
||||||
|
|
||||||
duration = duration_str(time.time() - self._start)
|
duration = duration_str(time.time() - self._start)
|
||||||
|
342
project.py
342
project.py
@ -32,7 +32,6 @@ import urllib.parse
|
|||||||
|
|
||||||
from color import Coloring
|
from color import Coloring
|
||||||
from error import DownloadError
|
from error import DownloadError
|
||||||
from error import GitAuthError
|
|
||||||
from error import GitError
|
from error import GitError
|
||||||
from error import ManifestInvalidPathError
|
from error import ManifestInvalidPathError
|
||||||
from error import ManifestInvalidRevisionError
|
from error import ManifestInvalidRevisionError
|
||||||
@ -146,7 +145,7 @@ def _ProjectHooks():
|
|||||||
"""
|
"""
|
||||||
global _project_hook_list
|
global _project_hook_list
|
||||||
if _project_hook_list is None:
|
if _project_hook_list is None:
|
||||||
d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
|
d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
|
||||||
d = os.path.join(d, "hooks")
|
d = os.path.join(d, "hooks")
|
||||||
_project_hook_list = [
|
_project_hook_list = [
|
||||||
os.path.join(d, x) for x in platform_utils.listdir(d)
|
os.path.join(d, x) for x in platform_utils.listdir(d)
|
||||||
@ -258,7 +257,7 @@ class ReviewableBranch:
|
|||||||
self,
|
self,
|
||||||
people,
|
people,
|
||||||
dryrun=False,
|
dryrun=False,
|
||||||
topic=None,
|
auto_topic=False,
|
||||||
hashtags=(),
|
hashtags=(),
|
||||||
labels=(),
|
labels=(),
|
||||||
private=False,
|
private=False,
|
||||||
@ -274,7 +273,7 @@ class ReviewableBranch:
|
|||||||
branch=self.name,
|
branch=self.name,
|
||||||
people=people,
|
people=people,
|
||||||
dryrun=dryrun,
|
dryrun=dryrun,
|
||||||
topic=topic,
|
auto_topic=auto_topic,
|
||||||
hashtags=hashtags,
|
hashtags=hashtags,
|
||||||
labels=labels,
|
labels=labels,
|
||||||
private=private,
|
private=private,
|
||||||
@ -576,6 +575,7 @@ class Project:
|
|||||||
dest_branch=None,
|
dest_branch=None,
|
||||||
optimized_fetch=False,
|
optimized_fetch=False,
|
||||||
retry_fetches=0,
|
retry_fetches=0,
|
||||||
|
old_revision=None,
|
||||||
):
|
):
|
||||||
"""Init a Project object.
|
"""Init a Project object.
|
||||||
|
|
||||||
@ -608,6 +608,7 @@ class Project:
|
|||||||
only fetch from the remote if the sha1 is not present locally.
|
only fetch from the remote if the sha1 is not present locally.
|
||||||
retry_fetches: Retry remote fetches n times upon receiving transient
|
retry_fetches: Retry remote fetches n times upon receiving transient
|
||||||
error with exponential backoff and jitter.
|
error with exponential backoff and jitter.
|
||||||
|
old_revision: saved git commit id for open GITC projects.
|
||||||
"""
|
"""
|
||||||
self.client = self.manifest = manifest
|
self.client = self.manifest = manifest
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -637,15 +638,12 @@ class Project:
|
|||||||
self.linkfiles = []
|
self.linkfiles = []
|
||||||
self.annotations = []
|
self.annotations = []
|
||||||
self.dest_branch = dest_branch
|
self.dest_branch = dest_branch
|
||||||
|
self.old_revision = old_revision
|
||||||
|
|
||||||
# This will be filled in if a project is later identified to be the
|
# This will be filled in if a project is later identified to be the
|
||||||
# project containing repo hooks.
|
# project containing repo hooks.
|
||||||
self.enabled_repo_hooks = []
|
self.enabled_repo_hooks = []
|
||||||
|
|
||||||
# This will be updated later if the project has submodules and
|
|
||||||
# if they will be synced.
|
|
||||||
self.has_subprojects = False
|
|
||||||
|
|
||||||
def RelPath(self, local=True):
|
def RelPath(self, local=True):
|
||||||
"""Return the path for the project relative to a manifest.
|
"""Return the path for the project relative to a manifest.
|
||||||
|
|
||||||
@ -729,34 +727,12 @@ class Project:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def IsRebaseInProgress(self):
|
def IsRebaseInProgress(self):
|
||||||
"""Returns true if a rebase or "am" is in progress"""
|
|
||||||
# "rebase-apply" is used for "git rebase".
|
|
||||||
# "rebase-merge" is used for "git am".
|
|
||||||
return (
|
return (
|
||||||
os.path.exists(self.work_git.GetDotgitPath("rebase-apply"))
|
os.path.exists(self.work_git.GetDotgitPath("rebase-apply"))
|
||||||
or os.path.exists(self.work_git.GetDotgitPath("rebase-merge"))
|
or os.path.exists(self.work_git.GetDotgitPath("rebase-merge"))
|
||||||
or os.path.exists(os.path.join(self.worktree, ".dotest"))
|
or os.path.exists(os.path.join(self.worktree, ".dotest"))
|
||||||
)
|
)
|
||||||
|
|
||||||
def IsCherryPickInProgress(self):
|
|
||||||
"""Returns True if a cherry-pick is in progress."""
|
|
||||||
return os.path.exists(self.work_git.GetDotgitPath("CHERRY_PICK_HEAD"))
|
|
||||||
|
|
||||||
def _AbortRebase(self):
|
|
||||||
"""Abort ongoing rebase, cherry-pick or patch apply (am).
|
|
||||||
|
|
||||||
If no rebase, cherry-pick or patch apply was in progress, this method
|
|
||||||
ignores the status and continues.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _git(*args):
|
|
||||||
# Ignore return code, in case there was no rebase in progress.
|
|
||||||
GitCommand(self, args, log_as_error=False).Wait()
|
|
||||||
|
|
||||||
_git("cherry-pick", "--abort")
|
|
||||||
_git("rebase", "--abort")
|
|
||||||
_git("am", "--abort")
|
|
||||||
|
|
||||||
def IsDirty(self, consider_untracked=True):
|
def IsDirty(self, consider_untracked=True):
|
||||||
"""Is the working directory modified in some way?"""
|
"""Is the working directory modified in some way?"""
|
||||||
self.work_git.update_index(
|
self.work_git.update_index(
|
||||||
@ -1106,7 +1082,7 @@ class Project:
|
|||||||
branch=None,
|
branch=None,
|
||||||
people=([], []),
|
people=([], []),
|
||||||
dryrun=False,
|
dryrun=False,
|
||||||
topic=None,
|
auto_topic=False,
|
||||||
hashtags=(),
|
hashtags=(),
|
||||||
labels=(),
|
labels=(),
|
||||||
private=False,
|
private=False,
|
||||||
@ -1169,6 +1145,7 @@ class Project:
|
|||||||
# This stops git from pushing all reachable annotated tags when
|
# This stops git from pushing all reachable annotated tags when
|
||||||
# push.followTags is configured. Gerrit does not accept any tags
|
# push.followTags is configured. Gerrit does not accept any tags
|
||||||
# pushed to a CL.
|
# pushed to a CL.
|
||||||
|
if git_require((1, 8, 3)):
|
||||||
cmd.append("--no-follow-tags")
|
cmd.append("--no-follow-tags")
|
||||||
|
|
||||||
for push_option in push_options or []:
|
for push_option in push_options or []:
|
||||||
@ -1182,8 +1159,8 @@ class Project:
|
|||||||
|
|
||||||
ref_spec = f"{R_HEADS + branch.name}:refs/for/{dest_branch}"
|
ref_spec = f"{R_HEADS + branch.name}:refs/for/{dest_branch}"
|
||||||
opts = []
|
opts = []
|
||||||
if topic is not None:
|
if auto_topic:
|
||||||
opts += [f"topic={topic}"]
|
opts += ["topic=" + branch.name]
|
||||||
opts += ["t=%s" % p for p in hashtags]
|
opts += ["t=%s" % p for p in hashtags]
|
||||||
# NB: No need to encode labels as they've been validated above.
|
# NB: No need to encode labels as they've been validated above.
|
||||||
opts += ["l=%s" % p for p in labels]
|
opts += ["l=%s" % p for p in labels]
|
||||||
@ -1485,6 +1462,8 @@ class Project:
|
|||||||
self._InitHooks()
|
self._InitHooks()
|
||||||
|
|
||||||
def _CopyAndLinkFiles(self):
|
def _CopyAndLinkFiles(self):
|
||||||
|
if self.client.isGitcClient:
|
||||||
|
return
|
||||||
for copyfile in self.copyfiles:
|
for copyfile in self.copyfiles:
|
||||||
copyfile._Copy()
|
copyfile._Copy()
|
||||||
for linkfile in self.linkfiles:
|
for linkfile in self.linkfiles:
|
||||||
@ -1537,7 +1516,6 @@ class Project:
|
|||||||
syncbuf,
|
syncbuf,
|
||||||
force_sync=False,
|
force_sync=False,
|
||||||
force_checkout=False,
|
force_checkout=False,
|
||||||
force_rebase=False,
|
|
||||||
submodules=False,
|
submodules=False,
|
||||||
errors=None,
|
errors=None,
|
||||||
verbose=False,
|
verbose=False,
|
||||||
@ -1564,11 +1542,6 @@ class Project:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._InitWorkTree(force_sync=force_sync, submodules=submodules)
|
self._InitWorkTree(force_sync=force_sync, submodules=submodules)
|
||||||
# TODO(https://git-scm.com/docs/git-worktree#_bugs): Re-evaluate if
|
|
||||||
# submodules can be init when using worktrees once its support is
|
|
||||||
# complete.
|
|
||||||
if self.has_subprojects and not self.use_git_worktrees:
|
|
||||||
self._InitSubmodules()
|
|
||||||
all_refs = self.bare_ref.all
|
all_refs = self.bare_ref.all
|
||||||
self.CleanPublishedCache(all_refs)
|
self.CleanPublishedCache(all_refs)
|
||||||
revid = self.GetRevisionId(all_refs)
|
revid = self.GetRevisionId(all_refs)
|
||||||
@ -1613,15 +1586,7 @@ class Project:
|
|||||||
if branch is None or syncbuf.detach_head:
|
if branch is None or syncbuf.detach_head:
|
||||||
# Currently on a detached HEAD. The user is assumed to
|
# Currently on a detached HEAD. The user is assumed to
|
||||||
# not have any local modifications worth worrying about.
|
# not have any local modifications worth worrying about.
|
||||||
rebase_in_progress = (
|
if self.IsRebaseInProgress():
|
||||||
self.IsRebaseInProgress() or self.IsCherryPickInProgress()
|
|
||||||
)
|
|
||||||
if rebase_in_progress and force_checkout:
|
|
||||||
self._AbortRebase()
|
|
||||||
rebase_in_progress = (
|
|
||||||
self.IsRebaseInProgress() or self.IsCherryPickInProgress()
|
|
||||||
)
|
|
||||||
if rebase_in_progress:
|
|
||||||
fail(_PriorSyncFailedError(project=self.name))
|
fail(_PriorSyncFailedError(project=self.name))
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1688,22 +1653,19 @@ class Project:
|
|||||||
if pub:
|
if pub:
|
||||||
not_merged = self._revlist(not_rev(revid), pub)
|
not_merged = self._revlist(not_rev(revid), pub)
|
||||||
if not_merged:
|
if not_merged:
|
||||||
if upstream_gain and not force_rebase:
|
if upstream_gain:
|
||||||
# The user has published this branch and some of those
|
# The user has published this branch and some of those
|
||||||
# commits are not yet merged upstream. We do not want
|
# commits are not yet merged upstream. We do not want
|
||||||
# to rewrite the published commits so we punt.
|
# to rewrite the published commits so we punt.
|
||||||
fail(
|
fail(
|
||||||
LocalSyncFail(
|
LocalSyncFail(
|
||||||
"branch %s is published (but not merged) and is "
|
"branch %s is published (but not merged) and is "
|
||||||
"now %d commits behind. Fix this manually or rerun "
|
"now %d commits behind"
|
||||||
"with the --rebase option to force a rebase."
|
|
||||||
% (branch.name, len(upstream_gain)),
|
% (branch.name, len(upstream_gain)),
|
||||||
project=self.name,
|
project=self.name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
syncbuf.later1(self, _doff, not verbose)
|
|
||||||
return
|
|
||||||
elif pub == head:
|
elif pub == head:
|
||||||
# All published commits are merged, and thus we are a
|
# All published commits are merged, and thus we are a
|
||||||
# strict subset. We can fast-forward safely.
|
# strict subset. We can fast-forward safely.
|
||||||
@ -1851,11 +1813,11 @@ class Project:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
msg = (
|
msg = (
|
||||||
"error: %s: Cannot remove project: uncommitted "
|
"error: %s: Cannot remove project: uncommitted"
|
||||||
"changes are present.\n" % self.RelPath(local=False)
|
"changes are present.\n" % self.RelPath(local=False)
|
||||||
)
|
)
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
raise DeleteDirtyWorktreeError(msg, project=self.name)
|
raise DeleteDirtyWorktreeError(msg, project=self)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"{self.RelPath(local=False)}: Deleting obsolete checkout.")
|
print(f"{self.RelPath(local=False)}: Deleting obsolete checkout.")
|
||||||
@ -1864,7 +1826,7 @@ class Project:
|
|||||||
# remove because it will recursively delete projects -- we handle that
|
# remove because it will recursively delete projects -- we handle that
|
||||||
# ourselves below. https://crbug.com/git/48
|
# ourselves below. https://crbug.com/git/48
|
||||||
if self.use_git_worktrees:
|
if self.use_git_worktrees:
|
||||||
needle = os.path.realpath(self.gitdir)
|
needle = platform_utils.realpath(self.gitdir)
|
||||||
# Find the git worktree commondir under .repo/worktrees/.
|
# Find the git worktree commondir under .repo/worktrees/.
|
||||||
output = self.bare_git.worktree("list", "--porcelain").splitlines()[
|
output = self.bare_git.worktree("list", "--porcelain").splitlines()[
|
||||||
0
|
0
|
||||||
@ -1878,7 +1840,7 @@ class Project:
|
|||||||
with open(gitdir) as fp:
|
with open(gitdir) as fp:
|
||||||
relpath = fp.read().strip()
|
relpath = fp.read().strip()
|
||||||
# Resolve the checkout path and see if it matches this project.
|
# Resolve the checkout path and see if it matches this project.
|
||||||
fullpath = os.path.realpath(
|
fullpath = platform_utils.realpath(
|
||||||
os.path.join(configs, name, relpath)
|
os.path.join(configs, name, relpath)
|
||||||
)
|
)
|
||||||
if fullpath == needle:
|
if fullpath == needle:
|
||||||
@ -2197,27 +2159,24 @@ class Project:
|
|||||||
|
|
||||||
def get_submodules(gitdir, rev):
|
def get_submodules(gitdir, rev):
|
||||||
# Parse .gitmodules for submodule sub_paths and sub_urls.
|
# Parse .gitmodules for submodule sub_paths and sub_urls.
|
||||||
sub_paths, sub_urls, sub_shallows = parse_gitmodules(gitdir, rev)
|
sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
|
||||||
if not sub_paths:
|
if not sub_paths:
|
||||||
return []
|
return []
|
||||||
# Run `git ls-tree` to read SHAs of submodule object, which happen
|
# Run `git ls-tree` to read SHAs of submodule object, which happen
|
||||||
# to be revision of submodule repository.
|
# to be revision of submodule repository.
|
||||||
sub_revs = git_ls_tree(gitdir, rev, sub_paths)
|
sub_revs = git_ls_tree(gitdir, rev, sub_paths)
|
||||||
submodules = []
|
submodules = []
|
||||||
for sub_path, sub_url, sub_shallow in zip(
|
for sub_path, sub_url in zip(sub_paths, sub_urls):
|
||||||
sub_paths, sub_urls, sub_shallows
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
sub_rev = sub_revs[sub_path]
|
sub_rev = sub_revs[sub_path]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Ignore non-exist submodules.
|
# Ignore non-exist submodules.
|
||||||
continue
|
continue
|
||||||
submodules.append((sub_rev, sub_path, sub_url, sub_shallow))
|
submodules.append((sub_rev, sub_path, sub_url))
|
||||||
return submodules
|
return submodules
|
||||||
|
|
||||||
re_path = re.compile(r"^submodule\.(.+)\.path=(.*)$")
|
re_path = re.compile(r"^submodule\.(.+)\.path=(.*)$")
|
||||||
re_url = re.compile(r"^submodule\.(.+)\.url=(.*)$")
|
re_url = re.compile(r"^submodule\.(.+)\.url=(.*)$")
|
||||||
re_shallow = re.compile(r"^submodule\.(.+)\.shallow=(.*)$")
|
|
||||||
|
|
||||||
def parse_gitmodules(gitdir, rev):
|
def parse_gitmodules(gitdir, rev):
|
||||||
cmd = ["cat-file", "blob", "%s:.gitmodules" % rev]
|
cmd = ["cat-file", "blob", "%s:.gitmodules" % rev]
|
||||||
@ -2231,9 +2190,9 @@ class Project:
|
|||||||
gitdir=gitdir,
|
gitdir=gitdir,
|
||||||
)
|
)
|
||||||
except GitError:
|
except GitError:
|
||||||
return [], [], []
|
return [], []
|
||||||
if p.Wait() != 0:
|
if p.Wait() != 0:
|
||||||
return [], [], []
|
return [], []
|
||||||
|
|
||||||
gitmodules_lines = []
|
gitmodules_lines = []
|
||||||
fd, temp_gitmodules_path = tempfile.mkstemp()
|
fd, temp_gitmodules_path = tempfile.mkstemp()
|
||||||
@ -2250,17 +2209,16 @@ class Project:
|
|||||||
gitdir=gitdir,
|
gitdir=gitdir,
|
||||||
)
|
)
|
||||||
if p.Wait() != 0:
|
if p.Wait() != 0:
|
||||||
return [], [], []
|
return [], []
|
||||||
gitmodules_lines = p.stdout.split("\n")
|
gitmodules_lines = p.stdout.split("\n")
|
||||||
except GitError:
|
except GitError:
|
||||||
return [], [], []
|
return [], []
|
||||||
finally:
|
finally:
|
||||||
platform_utils.remove(temp_gitmodules_path)
|
platform_utils.remove(temp_gitmodules_path)
|
||||||
|
|
||||||
names = set()
|
names = set()
|
||||||
paths = {}
|
paths = {}
|
||||||
urls = {}
|
urls = {}
|
||||||
shallows = {}
|
|
||||||
for line in gitmodules_lines:
|
for line in gitmodules_lines:
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
@ -2274,16 +2232,10 @@ class Project:
|
|||||||
names.add(m.group(1))
|
names.add(m.group(1))
|
||||||
urls[m.group(1)] = m.group(2)
|
urls[m.group(1)] = m.group(2)
|
||||||
continue
|
continue
|
||||||
m = re_shallow.match(line)
|
|
||||||
if m:
|
|
||||||
names.add(m.group(1))
|
|
||||||
shallows[m.group(1)] = m.group(2)
|
|
||||||
continue
|
|
||||||
names = sorted(names)
|
names = sorted(names)
|
||||||
return (
|
return (
|
||||||
[paths.get(name, "") for name in names],
|
[paths.get(name, "") for name in names],
|
||||||
[urls.get(name, "") for name in names],
|
[urls.get(name, "") for name in names],
|
||||||
[shallows.get(name, "") for name in names],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def git_ls_tree(gitdir, rev, paths):
|
def git_ls_tree(gitdir, rev, paths):
|
||||||
@ -2312,9 +2264,7 @@ class Project:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
rev = self.GetRevisionId()
|
rev = self.GetRevisionId()
|
||||||
except (GitError, ManifestInvalidRevisionError):
|
except GitError:
|
||||||
# The git repo may be outdated (i.e. not fetched yet) and querying
|
|
||||||
# its submodules using the revision may not work; so return here.
|
|
||||||
return []
|
return []
|
||||||
return get_submodules(self.gitdir, rev)
|
return get_submodules(self.gitdir, rev)
|
||||||
|
|
||||||
@ -2324,7 +2274,7 @@ class Project:
|
|||||||
# If git repo does not exist yet, querying its submodules will
|
# If git repo does not exist yet, querying its submodules will
|
||||||
# mess up its states; so return here.
|
# mess up its states; so return here.
|
||||||
return result
|
return result
|
||||||
for rev, path, url, shallow in self._GetSubmodules():
|
for rev, path, url in self._GetSubmodules():
|
||||||
name = self.manifest.GetSubprojectName(self, path)
|
name = self.manifest.GetSubprojectName(self, path)
|
||||||
(
|
(
|
||||||
relpath,
|
relpath,
|
||||||
@ -2346,7 +2296,6 @@ class Project:
|
|||||||
review=self.remote.review,
|
review=self.remote.review,
|
||||||
revision=self.remote.revision,
|
revision=self.remote.revision,
|
||||||
)
|
)
|
||||||
clone_depth = 1 if shallow.lower() == "true" else None
|
|
||||||
subproject = Project(
|
subproject = Project(
|
||||||
manifest=self.manifest,
|
manifest=self.manifest,
|
||||||
name=name,
|
name=name,
|
||||||
@ -2363,13 +2312,10 @@ class Project:
|
|||||||
sync_s=self.sync_s,
|
sync_s=self.sync_s,
|
||||||
sync_tags=self.sync_tags,
|
sync_tags=self.sync_tags,
|
||||||
parent=self,
|
parent=self,
|
||||||
clone_depth=clone_depth,
|
|
||||||
is_derived=True,
|
is_derived=True,
|
||||||
)
|
)
|
||||||
result.append(subproject)
|
result.append(subproject)
|
||||||
result.extend(subproject.GetDerivedSubprojects())
|
result.extend(subproject.GetDerivedSubprojects())
|
||||||
if result:
|
|
||||||
self.has_subprojects = True
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def EnableRepositoryExtension(self, key, value="true", version=1):
|
def EnableRepositoryExtension(self, key, value="true", version=1):
|
||||||
@ -2418,25 +2364,26 @@ class Project:
|
|||||||
try:
|
try:
|
||||||
# if revision (sha or tag) is not present then following function
|
# if revision (sha or tag) is not present then following function
|
||||||
# throws an error.
|
# throws an error.
|
||||||
revs = [f"{self.revisionExpr}^0"]
|
|
||||||
upstream_rev = None
|
|
||||||
if self.upstream:
|
|
||||||
upstream_rev = self.GetRemote().ToLocal(self.upstream)
|
|
||||||
revs.append(upstream_rev)
|
|
||||||
|
|
||||||
self.bare_git.rev_list(
|
self.bare_git.rev_list(
|
||||||
"-1",
|
"-1",
|
||||||
"--missing=allow-any",
|
"--missing=allow-any",
|
||||||
*revs,
|
"%s^0" % self.revisionExpr,
|
||||||
"--",
|
"--",
|
||||||
log_as_error=False,
|
log_as_error=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.upstream:
|
if self.upstream:
|
||||||
|
rev = self.GetRemote().ToLocal(self.upstream)
|
||||||
|
self.bare_git.rev_list(
|
||||||
|
"-1",
|
||||||
|
"--missing=allow-any",
|
||||||
|
"%s^0" % rev,
|
||||||
|
"--",
|
||||||
|
log_as_error=False,
|
||||||
|
)
|
||||||
self.bare_git.merge_base(
|
self.bare_git.merge_base(
|
||||||
"--is-ancestor",
|
"--is-ancestor",
|
||||||
self.revisionExpr,
|
self.revisionExpr,
|
||||||
upstream_rev,
|
rev,
|
||||||
log_as_error=False,
|
log_as_error=False,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
@ -2620,7 +2567,12 @@ class Project:
|
|||||||
branch = None
|
branch = None
|
||||||
else:
|
else:
|
||||||
branch = self.revisionExpr
|
branch = self.revisionExpr
|
||||||
if not self.manifest.IsMirror and is_sha1 and depth:
|
if (
|
||||||
|
not self.manifest.IsMirror
|
||||||
|
and is_sha1
|
||||||
|
and depth
|
||||||
|
and git_require((1, 8, 3))
|
||||||
|
):
|
||||||
# Shallow checkout of a specific commit, fetch from that commit and
|
# Shallow checkout of a specific commit, fetch from that commit and
|
||||||
# not the heads only as the commit might be deeper in the history.
|
# not the heads only as the commit might be deeper in the history.
|
||||||
spec.append(branch)
|
spec.append(branch)
|
||||||
@ -2683,20 +2635,6 @@ class Project:
|
|||||||
# Fallthru to sleep+retry logic at the bottom.
|
# Fallthru to sleep+retry logic at the bottom.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# TODO(b/360889369#comment24): git may gc commits incorrectly.
|
|
||||||
# Until the root cause is fixed, retry fetch with --refetch which
|
|
||||||
# will bring the repository into a good state.
|
|
||||||
elif gitcmd.stdout and (
|
|
||||||
"could not parse commit" in gitcmd.stdout
|
|
||||||
or "unable to parse commit" in gitcmd.stdout
|
|
||||||
):
|
|
||||||
cmd.insert(1, "--refetch")
|
|
||||||
print(
|
|
||||||
"could not parse commit error, retrying with refetch",
|
|
||||||
file=output_redir,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Try to prune remote branches once in case there are conflicts.
|
# Try to prune remote branches once in case there are conflicts.
|
||||||
# For example, if the remote had refs/heads/upstream, but deleted
|
# For example, if the remote had refs/heads/upstream, but deleted
|
||||||
# that and now has refs/heads/upstream/foo.
|
# that and now has refs/heads/upstream/foo.
|
||||||
@ -2722,47 +2660,12 @@ class Project:
|
|||||||
)
|
)
|
||||||
# Continue right away so we don't sleep as we shouldn't need to.
|
# Continue right away so we don't sleep as we shouldn't need to.
|
||||||
continue
|
continue
|
||||||
elif (
|
|
||||||
ret == 128
|
|
||||||
and gitcmd.stdout
|
|
||||||
and "fatal: could not read Username" in gitcmd.stdout
|
|
||||||
):
|
|
||||||
# User needs to be authenticated, and Git wants to prompt for
|
|
||||||
# username and password.
|
|
||||||
print(
|
|
||||||
"git requires authentication, but repo cannot perform "
|
|
||||||
"interactive authentication. Check git credentials.",
|
|
||||||
file=output_redir,
|
|
||||||
)
|
|
||||||
break
|
|
||||||
elif (
|
|
||||||
ret == 128
|
|
||||||
and gitcmd.stdout
|
|
||||||
and "remote helper 'sso' aborted session" in gitcmd.stdout
|
|
||||||
):
|
|
||||||
# User needs to be authenticated, and Git wants to prompt for
|
|
||||||
# username and password.
|
|
||||||
print(
|
|
||||||
"git requires authentication, but repo cannot perform "
|
|
||||||
"interactive authentication.",
|
|
||||||
file=output_redir,
|
|
||||||
)
|
|
||||||
raise GitAuthError(gitcmd.stdout)
|
|
||||||
break
|
|
||||||
elif current_branch_only and is_sha1 and ret == 128:
|
elif current_branch_only and is_sha1 and ret == 128:
|
||||||
# Exit code 128 means "couldn't find the ref you asked for"; if
|
# Exit code 128 means "couldn't find the ref you asked for"; if
|
||||||
# we're in sha1 mode, we just tried sync'ing from the upstream
|
# we're in sha1 mode, we just tried sync'ing from the upstream
|
||||||
# field; it doesn't exist, thus abort the optimization attempt
|
# field; it doesn't exist, thus abort the optimization attempt
|
||||||
# and do a full sync.
|
# and do a full sync.
|
||||||
break
|
break
|
||||||
elif depth and is_sha1 and ret == 1:
|
|
||||||
# In sha1 mode, when depth is enabled, syncing the revision
|
|
||||||
# from upstream may not work because some servers only allow
|
|
||||||
# fetching named refs. Fetching a specific sha1 may result
|
|
||||||
# in an error like 'server does not allow request for
|
|
||||||
# unadvertised object'. In this case, attempt a full sync
|
|
||||||
# without depth.
|
|
||||||
break
|
|
||||||
elif ret < 0:
|
elif ret < 0:
|
||||||
# Git died with a signal, exit immediately.
|
# Git died with a signal, exit immediately.
|
||||||
break
|
break
|
||||||
@ -2881,16 +2784,7 @@ class Project:
|
|||||||
def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
|
def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
|
||||||
platform_utils.remove(dstPath, missing_ok=True)
|
platform_utils.remove(dstPath, missing_ok=True)
|
||||||
|
|
||||||
# We do not use curl's --retry option since it generally doesn't
|
cmd = ["curl", "--fail", "--output", tmpPath, "--netrc", "--location"]
|
||||||
# actually retry anything; code 18 for example, it will not retry on.
|
|
||||||
cmd = [
|
|
||||||
"curl",
|
|
||||||
"--fail",
|
|
||||||
"--output",
|
|
||||||
tmpPath,
|
|
||||||
"--netrc-optional",
|
|
||||||
"--location",
|
|
||||||
]
|
|
||||||
if quiet:
|
if quiet:
|
||||||
cmd += ["--silent", "--show-error"]
|
cmd += ["--silent", "--show-error"]
|
||||||
if os.path.exists(tmpPath):
|
if os.path.exists(tmpPath):
|
||||||
@ -2926,18 +2820,11 @@ class Project:
|
|||||||
(output, _) = proc.communicate()
|
(output, _) = proc.communicate()
|
||||||
curlret = proc.returncode
|
curlret = proc.returncode
|
||||||
|
|
||||||
if curlret in (22, 35, 56, 92):
|
if curlret == 22:
|
||||||
# We use --fail so curl exits with unique status.
|
|
||||||
# From curl man page:
|
# From curl man page:
|
||||||
# 22: HTTP page not retrieved. The requested url was not found
|
# 22: HTTP page not retrieved. The requested url was not found
|
||||||
# or returned another error with the HTTP error code being
|
# or returned another error with the HTTP error code being 400
|
||||||
# 400 or above.
|
# or above. This return code only appears if -f, --fail is used.
|
||||||
# 35: SSL connect error. The SSL handshaking failed. This can
|
|
||||||
# be thrown by Google storage sometimes.
|
|
||||||
# 56: Failure in receiving network data. This shows up with
|
|
||||||
# HTTP/404 on Google storage.
|
|
||||||
# 92: Stream error in HTTP/2 framing layer. Basically the same
|
|
||||||
# as 22 -- Google storage sometimes throws 500's.
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print(
|
print(
|
||||||
"%s: Unable to retrieve clone.bundle; ignoring."
|
"%s: Unable to retrieve clone.bundle; ignoring."
|
||||||
@ -3035,17 +2922,6 @@ class Project:
|
|||||||
project=self.name,
|
project=self.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _InitSubmodules(self, quiet=True):
|
|
||||||
"""Initialize the submodules for the project."""
|
|
||||||
cmd = ["submodule", "init"]
|
|
||||||
if quiet:
|
|
||||||
cmd.append("-q")
|
|
||||||
if GitCommand(self, cmd).Wait() != 0:
|
|
||||||
raise GitError(
|
|
||||||
f"{self.name} submodule init",
|
|
||||||
project=self.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _Rebase(self, upstream, onto=None):
|
def _Rebase(self, upstream, onto=None):
|
||||||
cmd = ["rebase"]
|
cmd = ["rebase"]
|
||||||
if onto is not None:
|
if onto is not None:
|
||||||
@ -3099,12 +2975,14 @@ class Project:
|
|||||||
"Retrying clone after deleting %s", self.gitdir
|
"Retrying clone after deleting %s", self.gitdir
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
platform_utils.rmtree(os.path.realpath(self.gitdir))
|
platform_utils.rmtree(
|
||||||
|
platform_utils.realpath(self.gitdir)
|
||||||
|
)
|
||||||
if self.worktree and os.path.exists(
|
if self.worktree and os.path.exists(
|
||||||
os.path.realpath(self.worktree)
|
platform_utils.realpath(self.worktree)
|
||||||
):
|
):
|
||||||
platform_utils.rmtree(
|
platform_utils.rmtree(
|
||||||
os.path.realpath(self.worktree)
|
platform_utils.realpath(self.worktree)
|
||||||
)
|
)
|
||||||
return self._InitGitDir(
|
return self._InitGitDir(
|
||||||
mirror_git=mirror_git,
|
mirror_git=mirror_git,
|
||||||
@ -3190,7 +3068,7 @@ class Project:
|
|||||||
self._InitHooks(quiet=quiet)
|
self._InitHooks(quiet=quiet)
|
||||||
|
|
||||||
def _InitHooks(self, quiet=False):
|
def _InitHooks(self, quiet=False):
|
||||||
hooks = os.path.realpath(os.path.join(self.objdir, "hooks"))
|
hooks = platform_utils.realpath(os.path.join(self.objdir, "hooks"))
|
||||||
if not os.path.exists(hooks):
|
if not os.path.exists(hooks):
|
||||||
os.makedirs(hooks)
|
os.makedirs(hooks)
|
||||||
|
|
||||||
@ -3333,9 +3211,9 @@ class Project:
|
|||||||
dst_path = os.path.join(destdir, name)
|
dst_path = os.path.join(destdir, name)
|
||||||
src_path = os.path.join(srcdir, name)
|
src_path = os.path.join(srcdir, name)
|
||||||
|
|
||||||
dst = os.path.realpath(dst_path)
|
dst = platform_utils.realpath(dst_path)
|
||||||
if os.path.lexists(dst):
|
if os.path.lexists(dst):
|
||||||
src = os.path.realpath(src_path)
|
src = platform_utils.realpath(src_path)
|
||||||
# Fail if the links are pointing to the wrong place.
|
# Fail if the links are pointing to the wrong place.
|
||||||
if src != dst:
|
if src != dst:
|
||||||
logger.error(
|
logger.error(
|
||||||
@ -3371,10 +3249,10 @@ class Project:
|
|||||||
if copy_all:
|
if copy_all:
|
||||||
to_copy = platform_utils.listdir(gitdir)
|
to_copy = platform_utils.listdir(gitdir)
|
||||||
|
|
||||||
dotgit = os.path.realpath(dotgit)
|
dotgit = platform_utils.realpath(dotgit)
|
||||||
for name in set(to_copy).union(to_symlink):
|
for name in set(to_copy).union(to_symlink):
|
||||||
try:
|
try:
|
||||||
src = os.path.realpath(os.path.join(gitdir, name))
|
src = platform_utils.realpath(os.path.join(gitdir, name))
|
||||||
dst = os.path.join(dotgit, name)
|
dst = os.path.join(dotgit, name)
|
||||||
|
|
||||||
if os.path.lexists(dst):
|
if os.path.lexists(dst):
|
||||||
@ -3421,25 +3299,20 @@ class Project:
|
|||||||
setting = fp.read()
|
setting = fp.read()
|
||||||
assert setting.startswith("gitdir:")
|
assert setting.startswith("gitdir:")
|
||||||
git_worktree_path = setting.split(":", 1)[1].strip()
|
git_worktree_path = setting.split(":", 1)[1].strip()
|
||||||
|
|
||||||
# `gitdir` maybe be either relative or absolute depending on the
|
|
||||||
# behavior of the local copy of git, so only convert the path to
|
|
||||||
# relative if it needs to be converted.
|
|
||||||
if os.path.isabs(git_worktree_path):
|
|
||||||
# Some platforms (e.g. Windows) won't let us update dotgit in situ
|
# Some platforms (e.g. Windows) won't let us update dotgit in situ
|
||||||
# because of file permissions. Delete it and recreate it from
|
# because of file permissions. Delete it and recreate it from scratch
|
||||||
# scratch to avoid.
|
# to avoid.
|
||||||
platform_utils.remove(dotgit)
|
platform_utils.remove(dotgit)
|
||||||
# Use relative path from checkout->worktree & maintain Unix line
|
# Use relative path from checkout->worktree & maintain Unix line endings
|
||||||
# endings on all OS's to match git behavior.
|
# on all OS's to match git behavior.
|
||||||
with open(dotgit, "w", newline="\n") as fp:
|
with open(dotgit, "w", newline="\n") as fp:
|
||||||
print(
|
print(
|
||||||
"gitdir:",
|
"gitdir:",
|
||||||
os.path.relpath(git_worktree_path, self.worktree),
|
os.path.relpath(git_worktree_path, self.worktree),
|
||||||
file=fp,
|
file=fp,
|
||||||
)
|
)
|
||||||
# Use relative path from worktree->checkout & maintain Unix line
|
# Use relative path from worktree->checkout & maintain Unix line endings
|
||||||
# endings on all OS's to match git behavior.
|
# on all OS's to match git behavior.
|
||||||
with open(
|
with open(
|
||||||
os.path.join(git_worktree_path, "gitdir"), "w", newline="\n"
|
os.path.join(git_worktree_path, "gitdir"), "w", newline="\n"
|
||||||
) as fp:
|
) as fp:
|
||||||
@ -3464,91 +3337,46 @@ class Project:
|
|||||||
"""
|
"""
|
||||||
dotgit = os.path.join(self.worktree, ".git")
|
dotgit = os.path.join(self.worktree, ".git")
|
||||||
|
|
||||||
# If bare checkout of the submodule is stored under the subproject dir,
|
|
||||||
# migrate it.
|
|
||||||
if self.parent:
|
|
||||||
self._MigrateOldSubmoduleDir()
|
|
||||||
|
|
||||||
# If using an old layout style (a directory), migrate it.
|
# If using an old layout style (a directory), migrate it.
|
||||||
if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
|
if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
|
||||||
self._MigrateOldWorkTreeGitDir(dotgit, project=self.name)
|
self._MigrateOldWorkTreeGitDir(dotgit, project=self.name)
|
||||||
|
|
||||||
init_dotgit = not os.path.lexists(dotgit)
|
init_dotgit = not os.path.exists(dotgit)
|
||||||
if self.use_git_worktrees:
|
if self.use_git_worktrees:
|
||||||
if init_dotgit:
|
if init_dotgit:
|
||||||
self._InitGitWorktree()
|
self._InitGitWorktree()
|
||||||
self._CopyAndLinkFiles()
|
self._CopyAndLinkFiles()
|
||||||
else:
|
else:
|
||||||
# Remove old directory symbolic links for submodules.
|
|
||||||
if self.parent and platform_utils.islink(dotgit):
|
|
||||||
platform_utils.remove(dotgit)
|
|
||||||
init_dotgit = True
|
|
||||||
|
|
||||||
if not init_dotgit:
|
if not init_dotgit:
|
||||||
# See if the project has changed.
|
# See if the project has changed.
|
||||||
self._removeBadGitDirLink(dotgit)
|
if platform_utils.realpath(
|
||||||
|
self.gitdir
|
||||||
|
) != platform_utils.realpath(dotgit):
|
||||||
|
platform_utils.remove(dotgit)
|
||||||
|
|
||||||
if init_dotgit or not os.path.exists(dotgit):
|
if init_dotgit or not os.path.exists(dotgit):
|
||||||
self._createDotGit(dotgit)
|
os.makedirs(self.worktree, exist_ok=True)
|
||||||
|
platform_utils.symlink(
|
||||||
|
os.path.relpath(self.gitdir, self.worktree), dotgit
|
||||||
|
)
|
||||||
|
|
||||||
if init_dotgit:
|
if init_dotgit:
|
||||||
_lwrite(
|
_lwrite(
|
||||||
os.path.join(self.gitdir, HEAD), f"{self.GetRevisionId()}\n"
|
os.path.join(dotgit, HEAD), "%s\n" % self.GetRevisionId()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Finish checking out the worktree.
|
# Finish checking out the worktree.
|
||||||
cmd = ["read-tree", "--reset", "-u", "-v", HEAD]
|
cmd = ["read-tree", "--reset", "-u", "-v", HEAD]
|
||||||
try:
|
|
||||||
if GitCommand(self, cmd).Wait() != 0:
|
if GitCommand(self, cmd).Wait() != 0:
|
||||||
raise GitError(
|
raise GitError(
|
||||||
"Cannot initialize work tree for " + self.name,
|
"Cannot initialize work tree for " + self.name,
|
||||||
project=self.name,
|
project=self.name,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
# Something went wrong with read-tree (perhaps fetching
|
|
||||||
# missing blobs), so remove .git to avoid half initialized
|
|
||||||
# workspace from which repo can't recover on its own.
|
|
||||||
platform_utils.remove(dotgit)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
if submodules:
|
if submodules:
|
||||||
self._SyncSubmodules(quiet=True)
|
self._SyncSubmodules(quiet=True)
|
||||||
self._CopyAndLinkFiles()
|
self._CopyAndLinkFiles()
|
||||||
|
|
||||||
def _createDotGit(self, dotgit):
|
|
||||||
"""Initialize .git path.
|
|
||||||
|
|
||||||
For submodule projects, create a '.git' file using the gitfile
|
|
||||||
mechanism, and for the rest, create a symbolic link.
|
|
||||||
"""
|
|
||||||
os.makedirs(self.worktree, exist_ok=True)
|
|
||||||
if self.parent:
|
|
||||||
_lwrite(
|
|
||||||
dotgit,
|
|
||||||
f"gitdir: {os.path.relpath(self.gitdir, self.worktree)}\n",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
platform_utils.symlink(
|
|
||||||
os.path.relpath(self.gitdir, self.worktree), dotgit
|
|
||||||
)
|
|
||||||
|
|
||||||
def _removeBadGitDirLink(self, dotgit):
|
|
||||||
"""Verify .git is initialized correctly, otherwise delete it."""
|
|
||||||
if self.parent and os.path.isfile(dotgit):
|
|
||||||
with open(dotgit) as fp:
|
|
||||||
setting = fp.read()
|
|
||||||
if not setting.startswith("gitdir:"):
|
|
||||||
raise GitError(
|
|
||||||
f"'.git' in {self.worktree} must start with 'gitdir:'",
|
|
||||||
project=self.name,
|
|
||||||
)
|
|
||||||
gitdir = setting.split(":", 1)[1].strip()
|
|
||||||
dotgit_path = os.path.normpath(os.path.join(self.worktree, gitdir))
|
|
||||||
else:
|
|
||||||
dotgit_path = os.path.realpath(dotgit)
|
|
||||||
if os.path.realpath(self.gitdir) != dotgit_path:
|
|
||||||
platform_utils.remove(dotgit)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _MigrateOldWorkTreeGitDir(cls, dotgit, project=None):
|
def _MigrateOldWorkTreeGitDir(cls, dotgit, project=None):
|
||||||
"""Migrate the old worktree .git/ dir style to a symlink.
|
"""Migrate the old worktree .git/ dir style to a symlink.
|
||||||
@ -3637,28 +3465,6 @@ class Project:
|
|||||||
dotgit,
|
dotgit,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _MigrateOldSubmoduleDir(self):
|
|
||||||
"""Move the old bare checkout in 'subprojects' to 'modules'
|
|
||||||
as bare checkouts of submodules are now in 'modules' dir.
|
|
||||||
"""
|
|
||||||
subprojects = os.path.join(self.parent.gitdir, "subprojects")
|
|
||||||
if not platform_utils.isdir(subprojects):
|
|
||||||
return
|
|
||||||
|
|
||||||
modules = os.path.join(self.parent.gitdir, "modules")
|
|
||||||
old = self.gitdir
|
|
||||||
new = os.path.splitext(self.gitdir.replace(subprojects, modules))[0]
|
|
||||||
|
|
||||||
if all(map(platform_utils.isdir, [old, new])):
|
|
||||||
platform_utils.rmtree(old, ignore_errors=True)
|
|
||||||
else:
|
|
||||||
os.makedirs(modules, exist_ok=True)
|
|
||||||
platform_utils.rename(old, new)
|
|
||||||
self.gitdir = new
|
|
||||||
self.UpdatePaths(self.relpath, self.worktree, self.gitdir, self.objdir)
|
|
||||||
if platform_utils.isdir(subprojects) and not os.listdir(subprojects):
|
|
||||||
platform_utils.rmtree(subprojects, ignore_errors=True)
|
|
||||||
|
|
||||||
def _get_symlink_error_message(self):
|
def _get_symlink_error_message(self):
|
||||||
if platform_utils.isWindows():
|
if platform_utils.isWindows():
|
||||||
return (
|
return (
|
||||||
|
@ -16,8 +16,3 @@
|
|||||||
line-length = 80
|
line-length = 80
|
||||||
# NB: Keep in sync with tox.ini.
|
# NB: Keep in sync with tox.ini.
|
||||||
target-version = ['py36', 'py37', 'py38', 'py39', 'py310', 'py311'] #, 'py312'
|
target-version = ['py36', 'py37', 'py38', 'py39', 'py310', 'py311'] #, 'py312'
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
|
||||||
markers = """
|
|
||||||
skip_cq: Skip tests in the CQ. Should be rarely used!
|
|
||||||
"""
|
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# Copyright (C) 2024 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.
|
|
||||||
|
|
||||||
"""Helper tool for updating hooks from their various upstreams."""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
|
||||||
from typing import List, Optional
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
|
|
||||||
assert sys.version_info >= (3, 8), "Python 3.8+ required"
|
|
||||||
|
|
||||||
|
|
||||||
TOPDIR = Path(__file__).resolve().parent.parent
|
|
||||||
HOOKS_DIR = TOPDIR / "hooks"
|
|
||||||
|
|
||||||
|
|
||||||
def update_hook_commit_msg() -> None:
|
|
||||||
"""Update commit-msg hook from Gerrit."""
|
|
||||||
hook = HOOKS_DIR / "commit-msg"
|
|
||||||
print(
|
|
||||||
f"{hook.name}: Updating from https://gerrit.googlesource.com/gerrit/"
|
|
||||||
"+/HEAD/resources/com/google/gerrit/server/tools/root/hooks/commit-msg"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the current commit.
|
|
||||||
url = "https://gerrit.googlesource.com/gerrit/+/HEAD?format=JSON"
|
|
||||||
with urllib.request.urlopen(url) as fp:
|
|
||||||
data = fp.read()
|
|
||||||
# Discard the xss protection.
|
|
||||||
data = data.split(b"\n", 1)[1]
|
|
||||||
data = json.loads(data)
|
|
||||||
commit = data["commit"]
|
|
||||||
|
|
||||||
# Fetch the data for that commit.
|
|
||||||
url = (
|
|
||||||
f"https://gerrit.googlesource.com/gerrit/+/{commit}/"
|
|
||||||
"resources/com/google/gerrit/server/tools/root/hooks/commit-msg"
|
|
||||||
)
|
|
||||||
with urllib.request.urlopen(f"{url}?format=TEXT") as fp:
|
|
||||||
data = fp.read()
|
|
||||||
|
|
||||||
# gitiles base64 encodes text data.
|
|
||||||
data = base64.b64decode(data)
|
|
||||||
|
|
||||||
# Inject header into the hook.
|
|
||||||
lines = data.split(b"\n")
|
|
||||||
lines = (
|
|
||||||
lines[:1]
|
|
||||||
+ [
|
|
||||||
b"# DO NOT EDIT THIS FILE",
|
|
||||||
(
|
|
||||||
b"# All updates should be sent upstream: "
|
|
||||||
b"https://gerrit.googlesource.com/gerrit/"
|
|
||||||
),
|
|
||||||
f"# This is synced from commit: {commit}".encode("utf-8"),
|
|
||||||
b"# DO NOT EDIT THIS FILE",
|
|
||||||
]
|
|
||||||
+ lines[1:]
|
|
||||||
)
|
|
||||||
data = b"\n".join(lines)
|
|
||||||
|
|
||||||
# Update the hook.
|
|
||||||
hook.write_bytes(data)
|
|
||||||
hook.chmod(0o755)
|
|
||||||
|
|
||||||
|
|
||||||
def update_hook_pre_auto_gc() -> None:
|
|
||||||
"""Update pre-auto-gc hook from git."""
|
|
||||||
hook = HOOKS_DIR / "pre-auto-gc"
|
|
||||||
print(
|
|
||||||
f"{hook.name}: Updating from https://github.com/git/git/"
|
|
||||||
"HEAD/contrib/hooks/pre-auto-gc-battery"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the current commit.
|
|
||||||
headers = {
|
|
||||||
"Accept": "application/vnd.github+json",
|
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
|
||||||
}
|
|
||||||
url = "https://api.github.com/repos/git/git/git/refs/heads/master"
|
|
||||||
req = urllib.request.Request(url, headers=headers)
|
|
||||||
with urllib.request.urlopen(req) as fp:
|
|
||||||
data = fp.read()
|
|
||||||
data = json.loads(data)
|
|
||||||
|
|
||||||
# Fetch the data for that commit.
|
|
||||||
commit = data["object"]["sha"]
|
|
||||||
url = (
|
|
||||||
f"https://raw.githubusercontent.com/git/git/{commit}/"
|
|
||||||
"contrib/hooks/pre-auto-gc-battery"
|
|
||||||
)
|
|
||||||
with urllib.request.urlopen(url) as fp:
|
|
||||||
data = fp.read()
|
|
||||||
|
|
||||||
# Inject header into the hook.
|
|
||||||
lines = data.split(b"\n")
|
|
||||||
lines = (
|
|
||||||
lines[:1]
|
|
||||||
+ [
|
|
||||||
b"# DO NOT EDIT THIS FILE",
|
|
||||||
(
|
|
||||||
b"# All updates should be sent upstream: "
|
|
||||||
b"https://github.com/git/git/"
|
|
||||||
),
|
|
||||||
f"# This is synced from commit: {commit}".encode("utf-8"),
|
|
||||||
b"# DO NOT EDIT THIS FILE",
|
|
||||||
]
|
|
||||||
+ lines[1:]
|
|
||||||
)
|
|
||||||
data = b"\n".join(lines)
|
|
||||||
|
|
||||||
# Update the hook.
|
|
||||||
hook.write_bytes(data)
|
|
||||||
hook.chmod(0o755)
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Optional[List[str]] = None) -> Optional[int]:
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.parse_args(argv)
|
|
||||||
|
|
||||||
update_hook_commit_msg()
|
|
||||||
update_hook_pre_auto_gc()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main(sys.argv[1:]))
|
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -36,7 +35,12 @@ KEYID_ECC = "E1F9040D7A3F6DAFAC897CD3D3B95DA243E48A39"
|
|||||||
|
|
||||||
def cmdstr(cmd):
|
def cmdstr(cmd):
|
||||||
"""Get a nicely quoted shell command."""
|
"""Get a nicely quoted shell command."""
|
||||||
return " ".join(shlex.quote(x) for x in cmd)
|
ret = []
|
||||||
|
for arg in cmd:
|
||||||
|
if not re.match(r"^[a-zA-Z0-9/_.=-]+$", arg):
|
||||||
|
arg = f'"{arg}"'
|
||||||
|
ret.append(arg)
|
||||||
|
return " ".join(ret)
|
||||||
|
|
||||||
|
|
||||||
def run(opts, cmd, check=True, **kwargs):
|
def run(opts, cmd, check=True, **kwargs):
|
||||||
|
166
repo
166
repo
@ -27,7 +27,6 @@ import platform
|
|||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import NamedTuple
|
|
||||||
|
|
||||||
|
|
||||||
# These should never be newer than the main.py version since this needs to be a
|
# These should never be newer than the main.py version since this needs to be a
|
||||||
@ -57,14 +56,9 @@ class Trace:
|
|||||||
trace = Trace()
|
trace = Trace()
|
||||||
|
|
||||||
|
|
||||||
def cmdstr(cmd):
|
|
||||||
"""Get a nicely quoted shell command."""
|
|
||||||
return " ".join(shlex.quote(x) for x in cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def exec_command(cmd):
|
def exec_command(cmd):
|
||||||
"""Execute |cmd| or return None on failure."""
|
"""Execute |cmd| or return None on failure."""
|
||||||
trace.print(":", cmdstr(cmd))
|
trace.print(":", " ".join(cmd))
|
||||||
try:
|
try:
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
ret = subprocess.call(cmd)
|
ret = subprocess.call(cmd)
|
||||||
@ -130,7 +124,7 @@ if not REPO_REV:
|
|||||||
BUG_URL = "https://issues.gerritcodereview.com/issues/new?component=1370071"
|
BUG_URL = "https://issues.gerritcodereview.com/issues/new?component=1370071"
|
||||||
|
|
||||||
# increment this whenever we make important changes to this script
|
# increment this whenever we make important changes to this script
|
||||||
VERSION = (2, 54)
|
VERSION = (2, 42)
|
||||||
|
|
||||||
# increment this if the MAINTAINER_KEYS block is modified
|
# increment this if the MAINTAINER_KEYS block is modified
|
||||||
KEYRING_VERSION = (2, 3)
|
KEYRING_VERSION = (2, 3)
|
||||||
@ -216,13 +210,18 @@ GIT = "git" # our git command
|
|||||||
# NB: The version of git that the repo launcher requires may be much older than
|
# NB: The version of git that the repo launcher requires may be much older than
|
||||||
# the version of git that the main repo source tree requires. Keeping this at
|
# the version of git that the main repo source tree requires. Keeping this at
|
||||||
# an older version also makes it easier for users to upgrade/rollback as needed.
|
# an older version also makes it easier for users to upgrade/rollback as needed.
|
||||||
MIN_GIT_VERSION = (1, 7, 9) # minimum supported git version
|
#
|
||||||
|
# git-1.7 is in (EOL) Ubuntu Precise.
|
||||||
|
MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version
|
||||||
repodir = ".repo" # name of repo's private directory
|
repodir = ".repo" # name of repo's private directory
|
||||||
S_repo = "repo" # special repo repository
|
S_repo = "repo" # special repo repository
|
||||||
S_manifests = "manifests" # special manifest repository
|
S_manifests = "manifests" # special manifest repository
|
||||||
REPO_MAIN = S_repo + "/main.py" # main script
|
REPO_MAIN = S_repo + "/main.py" # main script
|
||||||
|
GITC_CONFIG_FILE = "/gitc/.config"
|
||||||
|
GITC_FS_ROOT_DIR = "/gitc/manifest-rw/"
|
||||||
|
|
||||||
|
|
||||||
|
import collections
|
||||||
import errno
|
import errno
|
||||||
import json
|
import json
|
||||||
import optparse
|
import optparse
|
||||||
@ -238,8 +237,11 @@ home_dot_repo = os.path.join(repo_config_dir, ".repoconfig")
|
|||||||
gpg_dir = os.path.join(home_dot_repo, "gnupg")
|
gpg_dir = os.path.join(home_dot_repo, "gnupg")
|
||||||
|
|
||||||
|
|
||||||
def GetParser():
|
def GetParser(gitc_init=False):
|
||||||
"""Setup the CLI parser."""
|
"""Setup the CLI parser."""
|
||||||
|
if gitc_init:
|
||||||
|
sys.exit("repo: fatal: GITC not supported.")
|
||||||
|
else:
|
||||||
usage = "repo init [options] [-u] url"
|
usage = "repo init [options] [-u] url"
|
||||||
|
|
||||||
parser = optparse.OptionParser(usage=usage)
|
parser = optparse.OptionParser(usage=usage)
|
||||||
@ -282,12 +284,6 @@ def InitParser(parser):
|
|||||||
metavar="REVISION",
|
metavar="REVISION",
|
||||||
help="manifest branch or revision (use HEAD for default)",
|
help="manifest branch or revision (use HEAD for default)",
|
||||||
)
|
)
|
||||||
group.add_option(
|
|
||||||
"--manifest-upstream-branch",
|
|
||||||
help="when a commit is provided to --manifest-branch, this "
|
|
||||||
"is the name of the git ref in which the commit can be found",
|
|
||||||
metavar="BRANCH",
|
|
||||||
)
|
|
||||||
group.add_option(
|
group.add_option(
|
||||||
"-m",
|
"-m",
|
||||||
"--manifest-name",
|
"--manifest-name",
|
||||||
@ -487,6 +483,16 @@ def InitParser(parser):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
# This is a poor replacement for subprocess.run until we require Python 3.6+.
|
||||||
|
RunResult = collections.namedtuple(
|
||||||
|
"RunResult", ("returncode", "stdout", "stderr")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RunError(Exception):
|
||||||
|
"""Error when running a command failed."""
|
||||||
|
|
||||||
|
|
||||||
def run_command(cmd, **kwargs):
|
def run_command(cmd, **kwargs):
|
||||||
"""Run |cmd| and return its output."""
|
"""Run |cmd| and return its output."""
|
||||||
check = kwargs.pop("check", False)
|
check = kwargs.pop("check", False)
|
||||||
@ -511,7 +517,7 @@ def run_command(cmd, **kwargs):
|
|||||||
# Run & package the results.
|
# Run & package the results.
|
||||||
proc = subprocess.Popen(cmd, **kwargs)
|
proc = subprocess.Popen(cmd, **kwargs)
|
||||||
(stdout, stderr) = proc.communicate(input=cmd_input)
|
(stdout, stderr) = proc.communicate(input=cmd_input)
|
||||||
dbg = ": " + cmdstr(cmd)
|
dbg = ": " + " ".join(cmd)
|
||||||
if cmd_input is not None:
|
if cmd_input is not None:
|
||||||
dbg += " 0<|"
|
dbg += " 0<|"
|
||||||
if stdout == subprocess.PIPE:
|
if stdout == subprocess.PIPE:
|
||||||
@ -521,9 +527,7 @@ def run_command(cmd, **kwargs):
|
|||||||
elif stderr == subprocess.STDOUT:
|
elif stderr == subprocess.STDOUT:
|
||||||
dbg += " 2>&1"
|
dbg += " 2>&1"
|
||||||
trace.print(dbg)
|
trace.print(dbg)
|
||||||
ret = subprocess.CompletedProcess(
|
ret = RunResult(proc.returncode, decode(stdout), decode(stderr))
|
||||||
cmd, proc.returncode, decode(stdout), decode(stderr)
|
|
||||||
)
|
|
||||||
|
|
||||||
# If things failed, print useful debugging output.
|
# If things failed, print useful debugging output.
|
||||||
if check and ret.returncode:
|
if check and ret.returncode:
|
||||||
@ -544,13 +548,56 @@ def run_command(cmd, **kwargs):
|
|||||||
|
|
||||||
_print_output("stdout", ret.stdout)
|
_print_output("stdout", ret.stdout)
|
||||||
_print_output("stderr", ret.stderr)
|
_print_output("stderr", ret.stderr)
|
||||||
# This will raise subprocess.CalledProcessError for us.
|
raise RunError(ret)
|
||||||
ret.check_returncode()
|
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
_gitc_manifest_dir = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_gitc_manifest_dir():
|
||||||
|
global _gitc_manifest_dir
|
||||||
|
if _gitc_manifest_dir is None:
|
||||||
|
_gitc_manifest_dir = ""
|
||||||
|
try:
|
||||||
|
with open(GITC_CONFIG_FILE) as gitc_config:
|
||||||
|
for line in gitc_config:
|
||||||
|
match = re.match("gitc_dir=(?P<gitc_manifest_dir>.*)", line)
|
||||||
|
if match:
|
||||||
|
_gitc_manifest_dir = match.group("gitc_manifest_dir")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return _gitc_manifest_dir
|
||||||
|
|
||||||
|
|
||||||
|
def gitc_parse_clientdir(gitc_fs_path):
|
||||||
|
"""Parse a path in the GITC FS and return its client name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gitc_fs_path: A subdirectory path within the GITC_FS_ROOT_DIR.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The GITC client name.
|
||||||
|
"""
|
||||||
|
if gitc_fs_path == GITC_FS_ROOT_DIR:
|
||||||
|
return None
|
||||||
|
if not gitc_fs_path.startswith(GITC_FS_ROOT_DIR):
|
||||||
|
manifest_dir = get_gitc_manifest_dir()
|
||||||
|
if manifest_dir == "":
|
||||||
|
return None
|
||||||
|
if manifest_dir[-1] != "/":
|
||||||
|
manifest_dir += "/"
|
||||||
|
if gitc_fs_path == manifest_dir:
|
||||||
|
return None
|
||||||
|
if not gitc_fs_path.startswith(manifest_dir):
|
||||||
|
return None
|
||||||
|
return gitc_fs_path.split(manifest_dir)[1].split("/")[0]
|
||||||
|
return gitc_fs_path.split(GITC_FS_ROOT_DIR)[1].split("/")[0]
|
||||||
|
|
||||||
|
|
||||||
class CloneFailure(Exception):
|
class CloneFailure(Exception):
|
||||||
|
|
||||||
"""Indicate the remote clone of repo itself failed."""
|
"""Indicate the remote clone of repo itself failed."""
|
||||||
|
|
||||||
|
|
||||||
@ -587,9 +634,9 @@ def check_repo_rev(dst, rev, repo_verify=True, quiet=False):
|
|||||||
return (remote_ref, rev)
|
return (remote_ref, rev)
|
||||||
|
|
||||||
|
|
||||||
def _Init(args):
|
def _Init(args, gitc_init=False):
|
||||||
"""Installs repo by cloning it over the network."""
|
"""Installs repo by cloning it over the network."""
|
||||||
parser = GetParser()
|
parser = GetParser(gitc_init=gitc_init)
|
||||||
opt, args = parser.parse_args(args)
|
opt, args = parser.parse_args(args)
|
||||||
if args:
|
if args:
|
||||||
if not opt.manifest_url:
|
if not opt.manifest_url:
|
||||||
@ -669,20 +716,15 @@ def run_git(*args, **kwargs):
|
|||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except subprocess.CalledProcessError:
|
except RunError:
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
|
|
||||||
|
|
||||||
class GitVersion(NamedTuple):
|
# The git version info broken down into components for easy analysis.
|
||||||
"""The git version info broken down into components for easy analysis.
|
# Similar to Python's sys.version_info.
|
||||||
|
GitVersion = collections.namedtuple(
|
||||||
Similar to Python's sys.version_info.
|
"GitVersion", ("major", "minor", "micro", "full")
|
||||||
"""
|
)
|
||||||
|
|
||||||
major: int
|
|
||||||
minor: int
|
|
||||||
micro: int
|
|
||||||
full: int
|
|
||||||
|
|
||||||
|
|
||||||
def ParseGitVersion(ver_str=None):
|
def ParseGitVersion(ver_str=None):
|
||||||
@ -848,11 +890,10 @@ def _GetRepoConfig(name):
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"repo: error: git {cmdstr(cmd)} failed:\n{ret.stderr}",
|
f"repo: error: git {' '.join(cmd)} failed:\n{ret.stderr}",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
# This will raise subprocess.CalledProcessError for us.
|
raise RunError()
|
||||||
ret.check_returncode()
|
|
||||||
|
|
||||||
|
|
||||||
def _InitHttp():
|
def _InitHttp():
|
||||||
@ -1119,7 +1160,7 @@ class _Options:
|
|||||||
def _ExpandAlias(name):
|
def _ExpandAlias(name):
|
||||||
"""Look up user registered aliases."""
|
"""Look up user registered aliases."""
|
||||||
# We don't resolve aliases for existing subcommands. This matches git.
|
# We don't resolve aliases for existing subcommands. This matches git.
|
||||||
if name in {"help", "init"}:
|
if name in {"gitc-init", "help", "init"}:
|
||||||
return name, []
|
return name, []
|
||||||
|
|
||||||
alias = _GetRepoConfig(f"alias.{name}")
|
alias = _GetRepoConfig(f"alias.{name}")
|
||||||
@ -1197,13 +1238,13 @@ class Requirements:
|
|||||||
|
|
||||||
return cls(json_data)
|
return cls(json_data)
|
||||||
|
|
||||||
def get_soft_ver(self, pkg):
|
def _get_soft_ver(self, pkg):
|
||||||
"""Return the soft version for |pkg| if it exists."""
|
"""Return the soft version for |pkg| if it exists."""
|
||||||
return tuple(self.requirements.get(pkg, {}).get("soft", ()))
|
return self.requirements.get(pkg, {}).get("soft", ())
|
||||||
|
|
||||||
def get_hard_ver(self, pkg):
|
def _get_hard_ver(self, pkg):
|
||||||
"""Return the hard version for |pkg| if it exists."""
|
"""Return the hard version for |pkg| if it exists."""
|
||||||
return tuple(self.requirements.get(pkg, {}).get("hard", ()))
|
return self.requirements.get(pkg, {}).get("hard", ())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _format_ver(ver):
|
def _format_ver(ver):
|
||||||
@ -1213,8 +1254,8 @@ class Requirements:
|
|||||||
def assert_ver(self, pkg, curr_ver):
|
def assert_ver(self, pkg, curr_ver):
|
||||||
"""Verify |pkg|'s |curr_ver| is new enough."""
|
"""Verify |pkg|'s |curr_ver| is new enough."""
|
||||||
curr_ver = tuple(curr_ver)
|
curr_ver = tuple(curr_ver)
|
||||||
soft_ver = tuple(self.get_soft_ver(pkg))
|
soft_ver = tuple(self._get_soft_ver(pkg))
|
||||||
hard_ver = tuple(self.get_hard_ver(pkg))
|
hard_ver = tuple(self._get_hard_ver(pkg))
|
||||||
if curr_ver < hard_ver:
|
if curr_ver < hard_ver:
|
||||||
print(
|
print(
|
||||||
f'repo: error: Your version of "{pkg}" '
|
f'repo: error: Your version of "{pkg}" '
|
||||||
@ -1247,6 +1288,10 @@ class Requirements:
|
|||||||
|
|
||||||
|
|
||||||
def _Usage():
|
def _Usage():
|
||||||
|
gitc_usage = ""
|
||||||
|
if get_gitc_manifest_dir():
|
||||||
|
gitc_usage = " gitc-init Initialize a GITC Client.\n"
|
||||||
|
|
||||||
print(
|
print(
|
||||||
"""usage: repo COMMAND [ARGS]
|
"""usage: repo COMMAND [ARGS]
|
||||||
|
|
||||||
@ -1255,7 +1300,9 @@ repo is not yet installed. Use "repo init" to install it here.
|
|||||||
The most commonly used repo commands are:
|
The most commonly used repo commands are:
|
||||||
|
|
||||||
init Install repo in the current working directory
|
init Install repo in the current working directory
|
||||||
help Display detailed help on a command
|
"""
|
||||||
|
+ gitc_usage
|
||||||
|
+ """ help Display detailed help on a command
|
||||||
|
|
||||||
For access to the full online help, install repo ("repo init").
|
For access to the full online help, install repo ("repo init").
|
||||||
"""
|
"""
|
||||||
@ -1266,8 +1313,8 @@ For access to the full online help, install repo ("repo init").
|
|||||||
|
|
||||||
def _Help(args):
|
def _Help(args):
|
||||||
if args:
|
if args:
|
||||||
if args[0] in {"init"}:
|
if args[0] in {"init", "gitc-init"}:
|
||||||
parser = GetParser()
|
parser = GetParser(gitc_init=args[0] == "gitc-init")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
@ -1284,11 +1331,10 @@ def _Help(args):
|
|||||||
|
|
||||||
def _Version():
|
def _Version():
|
||||||
"""Show version information."""
|
"""Show version information."""
|
||||||
git_version = ParseGitVersion()
|
|
||||||
print("<repo not installed>")
|
print("<repo not installed>")
|
||||||
print(f"repo launcher version {'.'.join(str(x) for x in VERSION)}")
|
print(f"repo launcher version {'.'.join(str(x) for x in VERSION)}")
|
||||||
print(f" (from {__file__})")
|
print(f" (from {__file__})")
|
||||||
print(f"git {git_version.full}" if git_version else "git not installed")
|
print(f"git {ParseGitVersion().full}")
|
||||||
print(f"Python {sys.version}")
|
print(f"Python {sys.version}")
|
||||||
uname = platform.uname()
|
uname = platform.uname()
|
||||||
print(f"OS {uname.system} {uname.release} ({uname.version})")
|
print(f"OS {uname.system} {uname.release} ({uname.version})")
|
||||||
@ -1321,11 +1367,11 @@ def _RunSelf(wrapper_path):
|
|||||||
my_main = os.path.join(my_dir, "main.py")
|
my_main = os.path.join(my_dir, "main.py")
|
||||||
my_git = os.path.join(my_dir, ".git")
|
my_git = os.path.join(my_dir, ".git")
|
||||||
|
|
||||||
if os.path.isfile(my_main):
|
if os.path.isfile(my_main) and os.path.isdir(my_git):
|
||||||
for name in ["git_config.py", "project.py", "subcmds"]:
|
for name in ["git_config.py", "project.py", "subcmds"]:
|
||||||
if not os.path.exists(os.path.join(my_dir, name)):
|
if not os.path.exists(os.path.join(my_dir, name)):
|
||||||
return None, None
|
return None, None
|
||||||
return my_main, my_git if os.path.isdir(my_git) else None
|
return my_main, my_git
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
@ -1356,11 +1402,23 @@ def main(orig_args):
|
|||||||
# We run this early as we run some git commands ourselves.
|
# We run this early as we run some git commands ourselves.
|
||||||
SetGitTrace2ParentSid()
|
SetGitTrace2ParentSid()
|
||||||
|
|
||||||
|
repo_main, rel_repo_dir = None, None
|
||||||
|
# Don't use the local repo copy, make sure to switch to the gitc client first.
|
||||||
|
if cmd != "gitc-init":
|
||||||
repo_main, rel_repo_dir = _FindRepo()
|
repo_main, rel_repo_dir = _FindRepo()
|
||||||
|
|
||||||
wrapper_path = os.path.abspath(__file__)
|
wrapper_path = os.path.abspath(__file__)
|
||||||
my_main, my_git = _RunSelf(wrapper_path)
|
my_main, my_git = _RunSelf(wrapper_path)
|
||||||
|
|
||||||
|
cwd = os.getcwd()
|
||||||
|
if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()):
|
||||||
|
print(
|
||||||
|
"error: repo cannot be used in the GITC local manifest directory."
|
||||||
|
"\nIf you want to work on this GITC client please rerun this "
|
||||||
|
"command from the corresponding client under /gitc/",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
if not repo_main:
|
if not repo_main:
|
||||||
# Only expand aliases here since we'll be parsing the CLI ourselves.
|
# Only expand aliases here since we'll be parsing the CLI ourselves.
|
||||||
# If we had repo_main, alias expansion would happen in main.py.
|
# If we had repo_main, alias expansion would happen in main.py.
|
||||||
@ -1375,11 +1433,11 @@ def main(orig_args):
|
|||||||
_Version()
|
_Version()
|
||||||
if not cmd:
|
if not cmd:
|
||||||
_NotInstalled()
|
_NotInstalled()
|
||||||
if cmd == "init":
|
if cmd == "init" or cmd == "gitc-init":
|
||||||
if my_git:
|
if my_git:
|
||||||
_SetDefaultsTo(my_git)
|
_SetDefaultsTo(my_git)
|
||||||
try:
|
try:
|
||||||
_Init(args)
|
_Init(args, gitc_init=(cmd == "gitc-init"))
|
||||||
except CloneFailure:
|
except CloneFailure:
|
||||||
path = os.path.join(repodir, S_repo)
|
path = os.path.join(repodir, S_repo)
|
||||||
print(
|
print(
|
||||||
|
@ -39,8 +39,8 @@ class _LogColoring(Coloring):
|
|||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super().__init__(config, "logs")
|
super().__init__(config, "logs")
|
||||||
self.error = self.nofmt_colorer("error", fg="red")
|
self.error = self.colorer("error", fg="red")
|
||||||
self.warning = self.nofmt_colorer("warn", fg="yellow")
|
self.warning = self.colorer("warn", fg="yellow")
|
||||||
self.levelMap = {
|
self.levelMap = {
|
||||||
"WARNING": self.warning,
|
"WARNING": self.warning,
|
||||||
"ERROR": self.error,
|
"ERROR": self.error,
|
||||||
|
@ -46,14 +46,12 @@
|
|||||||
|
|
||||||
# Supported git versions.
|
# Supported git versions.
|
||||||
#
|
#
|
||||||
|
# git-1.7.2 is in Debian Squeeze.
|
||||||
|
# git-1.7.9 is in Ubuntu Precise.
|
||||||
# git-1.9.1 is in Ubuntu Trusty.
|
# git-1.9.1 is in Ubuntu Trusty.
|
||||||
# git-2.1.4 is in Debian Jessie.
|
# git-1.7.10 is in Debian Wheezy.
|
||||||
# git-2.7.4 is in Ubuntu Xenial.
|
|
||||||
# git-2.11.0 is in Debian Stretch.
|
|
||||||
# git-2.17.0 is in Ubuntu Bionic.
|
|
||||||
# git-2.20.1 is in Debian Buster.
|
|
||||||
"git": {
|
"git": {
|
||||||
"hard": [1, 9, 1],
|
"hard": [1, 7, 2],
|
||||||
"soft": [2, 7, 4]
|
"soft": [1, 9, 1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
62
run_tests
62
run_tests
@ -15,57 +15,16 @@
|
|||||||
|
|
||||||
"""Wrapper to run linters and pytest with the right settings."""
|
"""Wrapper to run linters and pytest with the right settings."""
|
||||||
|
|
||||||
import functools
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
|
ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache()
|
|
||||||
def is_ci() -> bool:
|
|
||||||
"""Whether we're running in our CI system."""
|
|
||||||
return os.getenv("LUCI_CQ") == "yes"
|
|
||||||
|
|
||||||
|
|
||||||
def run_pytest(argv: List[str]) -> int:
|
|
||||||
"""Returns the exit code from pytest."""
|
|
||||||
if is_ci():
|
|
||||||
argv = ["-m", "not skip_cq"] + argv
|
|
||||||
|
|
||||||
return subprocess.run(
|
|
||||||
[sys.executable, "-m", "pytest"] + argv,
|
|
||||||
check=False,
|
|
||||||
cwd=ROOT_DIR,
|
|
||||||
).returncode
|
|
||||||
|
|
||||||
|
|
||||||
def run_pytest_py38(argv: List[str]) -> int:
|
|
||||||
"""Returns the exit code from pytest under Python 3.8."""
|
|
||||||
if is_ci():
|
|
||||||
argv = ["-m", "not skip_cq"] + argv
|
|
||||||
|
|
||||||
try:
|
|
||||||
return subprocess.run(
|
|
||||||
[
|
|
||||||
"vpython3",
|
|
||||||
"-vpython-spec",
|
|
||||||
"run_tests.vpython3.8",
|
|
||||||
"-m",
|
|
||||||
"pytest",
|
|
||||||
]
|
|
||||||
+ argv,
|
|
||||||
check=False,
|
|
||||||
cwd=ROOT_DIR,
|
|
||||||
).returncode
|
|
||||||
except FileNotFoundError:
|
|
||||||
# Skip if the user doesn't have vpython from depot_tools.
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def run_black():
|
def run_black():
|
||||||
"""Returns the exit code from black."""
|
"""Returns the exit code from black."""
|
||||||
# Black by default only matches .py files. We have to list standalone
|
# Black by default only matches .py files. We have to list standalone
|
||||||
@ -73,46 +32,37 @@ def run_black():
|
|||||||
extra_programs = [
|
extra_programs = [
|
||||||
"repo",
|
"repo",
|
||||||
"run_tests",
|
"run_tests",
|
||||||
"release/update-hooks",
|
|
||||||
"release/update-manpages",
|
"release/update-manpages",
|
||||||
]
|
]
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
[sys.executable, "-m", "black", "--check", ROOT_DIR] + extra_programs,
|
[sys.executable, "-m", "black", "--check", ROOT_DIR] + extra_programs,
|
||||||
check=False,
|
check=False,
|
||||||
cwd=ROOT_DIR,
|
|
||||||
).returncode
|
).returncode
|
||||||
|
|
||||||
|
|
||||||
def run_flake8():
|
def run_flake8():
|
||||||
"""Returns the exit code from flake8."""
|
"""Returns the exit code from flake8."""
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
[sys.executable, "-m", "flake8", ROOT_DIR],
|
[sys.executable, "-m", "flake8", ROOT_DIR], check=False
|
||||||
check=False,
|
|
||||||
cwd=ROOT_DIR,
|
|
||||||
).returncode
|
).returncode
|
||||||
|
|
||||||
|
|
||||||
def run_isort():
|
def run_isort():
|
||||||
"""Returns the exit code from isort."""
|
"""Returns the exit code from isort."""
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
[sys.executable, "-m", "isort", "--check", ROOT_DIR],
|
[sys.executable, "-m", "isort", "--check", ROOT_DIR], check=False
|
||||||
check=False,
|
|
||||||
cwd=ROOT_DIR,
|
|
||||||
).returncode
|
).returncode
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
"""The main entry."""
|
"""The main entry."""
|
||||||
checks = (
|
checks = (
|
||||||
functools.partial(run_pytest, argv),
|
lambda: pytest.main(argv),
|
||||||
functools.partial(run_pytest_py38, argv),
|
|
||||||
run_black,
|
run_black,
|
||||||
run_flake8,
|
run_flake8,
|
||||||
run_isort,
|
run_isort,
|
||||||
)
|
)
|
||||||
# Run all the tests all the time to get full feedback. Don't exit on the
|
return 0 if all(not c() for c in checks) else 1
|
||||||
# first error as that makes it more difficult to iterate in the CQ.
|
|
||||||
return 1 if sum(c() for c in checks) else 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -5,92 +5,97 @@
|
|||||||
# List of available wheels:
|
# List of available wheels:
|
||||||
# https://chromium.googlesource.com/infra/infra/+/main/infra/tools/dockerbuild/wheels.md
|
# https://chromium.googlesource.com/infra/infra/+/main/infra/tools/dockerbuild/wheels.md
|
||||||
|
|
||||||
python_version: "3.11"
|
python_version: "3.8"
|
||||||
|
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/pytest-py3"
|
name: "infra/python/wheels/pytest-py3"
|
||||||
version: "version:8.3.4"
|
version: "version:6.2.2"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
# Required by pytest==6.2.2
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/py-py2_py3"
|
name: "infra/python/wheels/py-py2_py3"
|
||||||
version: "version:1.11.0"
|
version: "version:1.10.0"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
# Required by pytest==6.2.2
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/iniconfig-py3"
|
name: "infra/python/wheels/iniconfig-py3"
|
||||||
version: "version:1.1.1"
|
version: "version:1.1.1"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
# Required by pytest==6.2.2
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/packaging-py3"
|
name: "infra/python/wheels/packaging-py3"
|
||||||
version: "version:23.0"
|
version: "version:23.0"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
# Required by pytest==6.2.2
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/pluggy-py3"
|
name: "infra/python/wheels/pluggy-py3"
|
||||||
version: "version:1.5.0"
|
version: "version:0.13.1"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
# Required by pytest==6.2.2
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/toml-py3"
|
name: "infra/python/wheels/toml-py3"
|
||||||
version: "version:0.10.1"
|
version: "version:0.10.1"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
# Required by pytest==6.2.2
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/pyparsing-py3"
|
name: "infra/python/wheels/pyparsing-py3"
|
||||||
version: "version:3.0.7"
|
version: "version:3.0.7"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
# Required by pytest==6.2.2
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/attrs-py2_py3"
|
name: "infra/python/wheels/attrs-py2_py3"
|
||||||
version: "version:21.4.0"
|
version: "version:21.4.0"
|
||||||
>
|
>
|
||||||
|
|
||||||
# NB: Keep in sync with constraints.txt.
|
# Required by packaging==16.8
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/black-py3"
|
name: "infra/python/wheels/six-py2_py3"
|
||||||
version: "version:25.1.0"
|
version: "version:1.16.0"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by black==25.1.0
|
wheel: <
|
||||||
|
name: "infra/python/wheels/black-py3"
|
||||||
|
version: "version:23.1.0"
|
||||||
|
>
|
||||||
|
|
||||||
|
# Required by black==23.1.0
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/mypy-extensions-py3"
|
name: "infra/python/wheels/mypy-extensions-py3"
|
||||||
version: "version:0.4.3"
|
version: "version:0.4.3"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by black==25.1.0
|
# Required by black==23.1.0
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/tomli-py3"
|
name: "infra/python/wheels/tomli-py3"
|
||||||
version: "version:2.0.1"
|
version: "version:2.0.1"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by black==25.1.0
|
# Required by black==23.1.0
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/platformdirs-py3"
|
name: "infra/python/wheels/platformdirs-py3"
|
||||||
version: "version:2.5.2"
|
version: "version:2.5.2"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by black==25.1.0
|
# Required by black==23.1.0
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/pathspec-py3"
|
name: "infra/python/wheels/pathspec-py3"
|
||||||
version: "version:0.9.0"
|
version: "version:0.9.0"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by black==25.1.0
|
# Required by black==23.1.0
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/typing-extensions-py3"
|
name: "infra/python/wheels/typing-extensions-py3"
|
||||||
version: "version:4.3.0"
|
version: "version:4.3.0"
|
||||||
>
|
>
|
||||||
|
|
||||||
# Required by black==25.1.0
|
# Required by black==23.1.0
|
||||||
wheel: <
|
wheel: <
|
||||||
name: "infra/python/wheels/click-py3"
|
name: "infra/python/wheels/click-py3"
|
||||||
version: "version:8.0.3"
|
version: "version:8.0.3"
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
# This is a vpython "spec" file.
|
|
||||||
#
|
|
||||||
# Read more about `vpython` and how to modify this file here:
|
|
||||||
# https://chromium.googlesource.com/infra/infra/+/main/doc/users/vpython.md
|
|
||||||
# List of available wheels:
|
|
||||||
# https://chromium.googlesource.com/infra/infra/+/main/infra/tools/dockerbuild/wheels.md
|
|
||||||
|
|
||||||
python_version: "3.8"
|
|
||||||
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/pytest-py3"
|
|
||||||
version: "version:8.3.4"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/py-py2_py3"
|
|
||||||
version: "version:1.11.0"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/iniconfig-py3"
|
|
||||||
version: "version:1.1.1"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/packaging-py3"
|
|
||||||
version: "version:23.0"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/pluggy-py3"
|
|
||||||
version: "version:1.5.0"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/toml-py3"
|
|
||||||
version: "version:0.10.1"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/tomli-py3"
|
|
||||||
version: "version:2.1.0"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/pyparsing-py3"
|
|
||||||
version: "version:3.0.7"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/attrs-py2_py3"
|
|
||||||
version: "version:21.4.0"
|
|
||||||
>
|
|
||||||
|
|
||||||
# Required by pytest==8.3.4
|
|
||||||
wheel: <
|
|
||||||
name: "infra/python/wheels/exceptiongroup-py3"
|
|
||||||
version: "version:1.1.2"
|
|
||||||
>
|
|
58
ssh.py
58
ssh.py
@ -24,7 +24,6 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from git_command import git
|
|
||||||
import platform_utils
|
import platform_utils
|
||||||
from repo_trace import Trace
|
from repo_trace import Trace
|
||||||
|
|
||||||
@ -212,33 +211,7 @@ class ProxyManager:
|
|||||||
# and print to the log there.
|
# and print to the log there.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Git protocol V2 is a new feature in git 2.18.0, made default in
|
command = command_base[:1] + ["-M", "-N"] + command_base[1:]
|
||||||
# git 2.26.0
|
|
||||||
# It is faster and more efficient than V1.
|
|
||||||
# To enable it when using SSH, the environment variable GIT_PROTOCOL
|
|
||||||
# must be set in the SSH side channel when establishing the connection
|
|
||||||
# to the git server.
|
|
||||||
# See https://git-scm.com/docs/protocol-v2#_ssh_and_file_transport
|
|
||||||
# Normally git does this by itself. But here, where the SSH connection
|
|
||||||
# is established manually over ControlMaster via the repo-tool, it must
|
|
||||||
# be passed in explicitly instead.
|
|
||||||
# Based on https://git-scm.com/docs/gitprotocol-pack#_extra_parameters,
|
|
||||||
# GIT_PROTOCOL is considered an "Extra Parameter" and must be ignored
|
|
||||||
# by servers that do not understand it. This means that it is safe to
|
|
||||||
# set it even when connecting to older servers.
|
|
||||||
# It should also be safe to set the environment variable for older
|
|
||||||
# local git versions, since it is only part of the ssh side channel.
|
|
||||||
git_protocol_version = _get_git_protocol_version()
|
|
||||||
ssh_git_protocol_args = [
|
|
||||||
"-o",
|
|
||||||
f"SetEnv GIT_PROTOCOL=version={git_protocol_version}",
|
|
||||||
]
|
|
||||||
|
|
||||||
command = (
|
|
||||||
command_base[:1]
|
|
||||||
+ ["-M", "-N", *ssh_git_protocol_args]
|
|
||||||
+ command_base[1:]
|
|
||||||
)
|
|
||||||
p = None
|
p = None
|
||||||
try:
|
try:
|
||||||
with Trace("Call to ssh: %s", " ".join(command)):
|
with Trace("Call to ssh: %s", " ".join(command)):
|
||||||
@ -320,32 +293,3 @@ class ProxyManager:
|
|||||||
tempfile.mkdtemp("", "ssh-", tmp_dir), "master-" + tokens
|
tempfile.mkdtemp("", "ssh-", tmp_dir), "master-" + tokens
|
||||||
)
|
)
|
||||||
return self._sock_path
|
return self._sock_path
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=1)
|
|
||||||
def _get_git_protocol_version() -> str:
|
|
||||||
"""Return the git protocol version.
|
|
||||||
|
|
||||||
The version is found by first reading the global git config.
|
|
||||||
If no git config for protocol version exists, try to deduce the default
|
|
||||||
protocol version based on the git version.
|
|
||||||
|
|
||||||
See https://git-scm.com/docs/gitprotocol-v2 for details.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return subprocess.check_output(
|
|
||||||
["git", "config", "--get", "--global", "protocol.version"],
|
|
||||||
encoding="utf-8",
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
).strip()
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
if e.returncode == 1:
|
|
||||||
# Exit code 1 means that the git config key was not found.
|
|
||||||
# Try to imitate the defaults that git would have used.
|
|
||||||
git_version = git.version_tuple()
|
|
||||||
if git_version >= (2, 26, 0):
|
|
||||||
# Since git version 2.26, protocol v2 is the default.
|
|
||||||
return "2"
|
|
||||||
return "1"
|
|
||||||
# Other exit codes indicate error with reading the config.
|
|
||||||
raise
|
|
||||||
|
@ -70,10 +70,8 @@ It is equivalent to "git branch -D <branchname>".
|
|||||||
else:
|
else:
|
||||||
args.insert(0, "'All local branches'")
|
args.insert(0, "'All local branches'")
|
||||||
|
|
||||||
@classmethod
|
def _ExecuteOne(self, all_branches, nb, project):
|
||||||
def _ExecuteOne(cls, all_branches, nb, project_idx):
|
|
||||||
"""Abandon one project."""
|
"""Abandon one project."""
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
if all_branches:
|
if all_branches:
|
||||||
branches = project.GetBranches()
|
branches = project.GetBranches()
|
||||||
else:
|
else:
|
||||||
@ -91,7 +89,7 @@ It is equivalent to "git branch -D <branchname>".
|
|||||||
if status is not None:
|
if status is not None:
|
||||||
ret[name] = status
|
ret[name] = status
|
||||||
|
|
||||||
return (ret, project_idx, errors)
|
return (ret, project, errors)
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
nb = args[0].split()
|
nb = args[0].split()
|
||||||
@ -104,8 +102,7 @@ It is equivalent to "git branch -D <branchname>".
|
|||||||
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
|
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
|
||||||
|
|
||||||
def _ProcessResults(_pool, pm, states):
|
def _ProcessResults(_pool, pm, states):
|
||||||
for results, project_idx, errors in states:
|
for results, project, errors in states:
|
||||||
project = all_projects[project_idx]
|
|
||||||
for branch, status in results.items():
|
for branch, status in results.items():
|
||||||
if status:
|
if status:
|
||||||
success[branch].append(project)
|
success[branch].append(project)
|
||||||
@ -114,17 +111,14 @@ It is equivalent to "git branch -D <branchname>".
|
|||||||
aggregate_errors.extend(errors)
|
aggregate_errors.extend(errors)
|
||||||
pm.update(msg="")
|
pm.update(msg="")
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = all_projects
|
|
||||||
self.ExecuteInParallel(
|
self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
functools.partial(self._ExecuteOne, opt.all, nb),
|
functools.partial(self._ExecuteOne, opt.all, nb),
|
||||||
range(len(all_projects)),
|
all_projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
output=Progress(
|
output=Progress(
|
||||||
f"Abandon {nb}", len(all_projects), quiet=opt.quiet
|
f"Abandon {nb}", len(all_projects), quiet=opt.quiet
|
||||||
),
|
),
|
||||||
chunksize=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
width = max(
|
width = max(
|
||||||
|
@ -98,22 +98,6 @@ is shown, then the branch appears in all projects.
|
|||||||
"""
|
"""
|
||||||
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
|
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _ExpandProjectToBranches(cls, project_idx):
|
|
||||||
"""Expands a project into a list of branch names & associated info.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_idx: project.Project index
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Tuple[str, git_config.Branch, int]]
|
|
||||||
"""
|
|
||||||
branches = []
|
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
for name, b in project.GetBranches().items():
|
|
||||||
branches.append((name, b, project_idx))
|
|
||||||
return branches
|
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
projects = self.GetProjects(
|
projects = self.GetProjects(
|
||||||
args, all_manifests=not opt.this_manifest_only
|
args, all_manifests=not opt.this_manifest_only
|
||||||
@ -123,18 +107,15 @@ is shown, then the branch appears in all projects.
|
|||||||
project_cnt = len(projects)
|
project_cnt = len(projects)
|
||||||
|
|
||||||
def _ProcessResults(_pool, _output, results):
|
def _ProcessResults(_pool, _output, results):
|
||||||
for name, b, project_idx in itertools.chain.from_iterable(results):
|
for name, b in itertools.chain.from_iterable(results):
|
||||||
b.project = projects[project_idx]
|
|
||||||
if name not in all_branches:
|
if name not in all_branches:
|
||||||
all_branches[name] = BranchInfo(name)
|
all_branches[name] = BranchInfo(name)
|
||||||
all_branches[name].add(b)
|
all_branches[name].add(b)
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = projects
|
|
||||||
self.ExecuteInParallel(
|
self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
self._ExpandProjectToBranches,
|
expand_project_to_branches,
|
||||||
range(len(projects)),
|
projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -167,10 +148,7 @@ is shown, then the branch appears in all projects.
|
|||||||
else:
|
else:
|
||||||
published = " "
|
published = " "
|
||||||
|
|
||||||
# A branch name can contain a percent sign, so we need to escape it.
|
hdr("%c%c %-*s" % (current, published, width, name))
|
||||||
# Escape after f-string formatting to properly account for leading
|
|
||||||
# spaces.
|
|
||||||
hdr(f"{current}{published} {name:{width}}".replace("%", "%%"))
|
|
||||||
out.write(" |")
|
out.write(" |")
|
||||||
|
|
||||||
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
|
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
|
||||||
@ -213,3 +191,19 @@ is shown, then the branch appears in all projects.
|
|||||||
else:
|
else:
|
||||||
out.write(" in all projects")
|
out.write(" in all projects")
|
||||||
out.nl()
|
out.nl()
|
||||||
|
|
||||||
|
|
||||||
|
def expand_project_to_branches(project):
|
||||||
|
"""Expands a project into a list of branch names & associated information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project: project.Project
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[str, git_config.Branch]]
|
||||||
|
"""
|
||||||
|
branches = []
|
||||||
|
for name, b in project.GetBranches().items():
|
||||||
|
b.project = project
|
||||||
|
branches.append((name, b))
|
||||||
|
return branches
|
||||||
|
@ -20,6 +20,7 @@ from command import DEFAULT_LOCAL_JOBS
|
|||||||
from error import GitError
|
from error import GitError
|
||||||
from error import RepoExitError
|
from error import RepoExitError
|
||||||
from progress import Progress
|
from progress import Progress
|
||||||
|
from project import Project
|
||||||
from repo_logging import RepoLogger
|
from repo_logging import RepoLogger
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ logger = RepoLogger(__file__)
|
|||||||
class CheckoutBranchResult(NamedTuple):
|
class CheckoutBranchResult(NamedTuple):
|
||||||
# Whether the Project is on the branch (i.e. branch exists and no errors)
|
# Whether the Project is on the branch (i.e. branch exists and no errors)
|
||||||
result: bool
|
result: bool
|
||||||
project_idx: int
|
project: Project
|
||||||
error: Exception
|
error: Exception
|
||||||
|
|
||||||
|
|
||||||
@ -61,17 +62,15 @@ The command is equivalent to:
|
|||||||
if not args:
|
if not args:
|
||||||
self.Usage()
|
self.Usage()
|
||||||
|
|
||||||
@classmethod
|
def _ExecuteOne(self, nb, project):
|
||||||
def _ExecuteOne(cls, nb, project_idx):
|
|
||||||
"""Checkout one project."""
|
"""Checkout one project."""
|
||||||
error = None
|
error = None
|
||||||
result = None
|
result = None
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
try:
|
try:
|
||||||
result = project.CheckoutBranch(nb)
|
result = project.CheckoutBranch(nb)
|
||||||
except GitError as e:
|
except GitError as e:
|
||||||
error = e
|
error = e
|
||||||
return CheckoutBranchResult(result, project_idx, error)
|
return CheckoutBranchResult(result, project, error)
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
nb = args[0]
|
nb = args[0]
|
||||||
@ -84,20 +83,17 @@ The command is equivalent to:
|
|||||||
|
|
||||||
def _ProcessResults(_pool, pm, results):
|
def _ProcessResults(_pool, pm, results):
|
||||||
for result in results:
|
for result in results:
|
||||||
project = all_projects[result.project_idx]
|
|
||||||
if result.error is not None:
|
if result.error is not None:
|
||||||
err.append(result.error)
|
err.append(result.error)
|
||||||
err_projects.append(project)
|
err_projects.append(result.project)
|
||||||
elif result.result:
|
elif result.result:
|
||||||
success.append(project)
|
success.append(result.project)
|
||||||
pm.update(msg="")
|
pm.update(msg="")
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = all_projects
|
|
||||||
self.ExecuteInParallel(
|
self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
functools.partial(self._ExecuteOne, nb),
|
functools.partial(self._ExecuteOne, nb),
|
||||||
range(len(all_projects)),
|
all_projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
output=Progress(
|
output=Progress(
|
||||||
f"Checkout {nb}", len(all_projects), quiet=opt.quiet
|
f"Checkout {nb}", len(all_projects), quiet=opt.quiet
|
||||||
|
@ -40,8 +40,7 @@ to the Unix 'patch' command.
|
|||||||
help="paths are relative to the repository root",
|
help="paths are relative to the repository root",
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
def _ExecuteOne(self, absolute, local, project):
|
||||||
def _ExecuteOne(cls, absolute, local, project_idx):
|
|
||||||
"""Obtains the diff for a specific project.
|
"""Obtains the diff for a specific project.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -49,13 +48,12 @@ to the Unix 'patch' command.
|
|||||||
local: a boolean, if True, the path is relative to the local
|
local: a boolean, if True, the path is relative to the local
|
||||||
(sub)manifest. If false, the path is relative to the outermost
|
(sub)manifest. If false, the path is relative to the outermost
|
||||||
manifest.
|
manifest.
|
||||||
project_idx: Project index to get status of.
|
project: Project to get status of.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The status of the project.
|
The status of the project.
|
||||||
"""
|
"""
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
ret = project.PrintWorkTreeDiff(absolute, output_redir=buf, local=local)
|
ret = project.PrintWorkTreeDiff(absolute, output_redir=buf, local=local)
|
||||||
return (ret, buf.getvalue())
|
return (ret, buf.getvalue())
|
||||||
|
|
||||||
@ -73,15 +71,12 @@ to the Unix 'patch' command.
|
|||||||
ret = 1
|
ret = 1
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = all_projects
|
|
||||||
return self.ExecuteInParallel(
|
return self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
functools.partial(
|
functools.partial(
|
||||||
self._ExecuteOne, opt.absolute, opt.this_manifest_only
|
self._ExecuteOne, opt.absolute, opt.this_manifest_only
|
||||||
),
|
),
|
||||||
range(len(all_projects)),
|
all_projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
ordered=True,
|
ordered=True,
|
||||||
chunksize=1,
|
|
||||||
)
|
)
|
||||||
|
@ -233,9 +233,9 @@ synced and their revisions won't be found.
|
|||||||
)
|
)
|
||||||
self.printRevision = self.out.nofmt_printer("revision", fg="yellow")
|
self.printRevision = self.out.nofmt_printer("revision", fg="yellow")
|
||||||
else:
|
else:
|
||||||
self.printProject = self.printAdded = self.printRemoved = (
|
self.printProject = (
|
||||||
self.printRevision
|
self.printAdded
|
||||||
) = self.printText
|
) = self.printRemoved = self.printRevision = self.printText
|
||||||
|
|
||||||
manifest1 = RepoClient(self.repodir)
|
manifest1 = RepoClient(self.repodir)
|
||||||
manifest1.Override(args[0], load_local_manifests=False)
|
manifest1.Override(args[0], load_local_manifests=False)
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
import errno
|
import errno
|
||||||
import functools
|
import functools
|
||||||
import io
|
import io
|
||||||
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
@ -25,6 +26,7 @@ from color import Coloring
|
|||||||
from command import Command
|
from command import Command
|
||||||
from command import DEFAULT_LOCAL_JOBS
|
from command import DEFAULT_LOCAL_JOBS
|
||||||
from command import MirrorSafeCommand
|
from command import MirrorSafeCommand
|
||||||
|
from command import WORKER_BATCH_SIZE
|
||||||
from error import ManifestInvalidRevisionError
|
from error import ManifestInvalidRevisionError
|
||||||
from repo_logging import RepoLogger
|
from repo_logging import RepoLogger
|
||||||
|
|
||||||
@ -239,6 +241,7 @@ without iterating through the remaining projects.
|
|||||||
cmd.insert(cmd.index(cn) + 1, "--color")
|
cmd.insert(cmd.index(cn) + 1, "--color")
|
||||||
|
|
||||||
mirror = self.manifest.IsMirror
|
mirror = self.manifest.IsMirror
|
||||||
|
rc = 0
|
||||||
|
|
||||||
smart_sync_manifest_name = "smart_sync_override.xml"
|
smart_sync_manifest_name = "smart_sync_override.xml"
|
||||||
smart_sync_manifest_path = os.path.join(
|
smart_sync_manifest_path = os.path.join(
|
||||||
@ -261,10 +264,18 @@ without iterating through the remaining projects.
|
|||||||
|
|
||||||
os.environ["REPO_COUNT"] = str(len(projects))
|
os.environ["REPO_COUNT"] = str(len(projects))
|
||||||
|
|
||||||
def _ProcessResults(_pool, _output, results):
|
try:
|
||||||
rc = 0
|
config = self.manifest.manifestProject.config
|
||||||
|
with multiprocessing.Pool(opt.jobs, InitWorker) as pool:
|
||||||
|
results_it = pool.imap(
|
||||||
|
functools.partial(
|
||||||
|
DoWorkWrapper, mirror, opt, cmd, shell, config
|
||||||
|
),
|
||||||
|
enumerate(projects),
|
||||||
|
chunksize=WORKER_BATCH_SIZE,
|
||||||
|
)
|
||||||
first = True
|
first = True
|
||||||
for r, output in results:
|
for r, output in results_it:
|
||||||
if output:
|
if output:
|
||||||
if first:
|
if first:
|
||||||
first = False
|
first = False
|
||||||
@ -279,26 +290,9 @@ without iterating through the remaining projects.
|
|||||||
rc = rc or r
|
rc = rc or r
|
||||||
if r != 0 and opt.abort_on_errors:
|
if r != 0 and opt.abort_on_errors:
|
||||||
raise Exception("Aborting due to previous error")
|
raise Exception("Aborting due to previous error")
|
||||||
return rc
|
|
||||||
|
|
||||||
try:
|
|
||||||
config = self.manifest.manifestProject.config
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = projects
|
|
||||||
rc = self.ExecuteInParallel(
|
|
||||||
opt.jobs,
|
|
||||||
functools.partial(
|
|
||||||
self.DoWorkWrapper, mirror, opt, cmd, shell, config
|
|
||||||
),
|
|
||||||
range(len(projects)),
|
|
||||||
callback=_ProcessResults,
|
|
||||||
ordered=True,
|
|
||||||
initializer=self.InitWorker,
|
|
||||||
chunksize=1,
|
|
||||||
)
|
|
||||||
except (KeyboardInterrupt, WorkerKeyboardInterrupt):
|
except (KeyboardInterrupt, WorkerKeyboardInterrupt):
|
||||||
# Catch KeyboardInterrupt raised inside and outside of workers
|
# Catch KeyboardInterrupt raised inside and outside of workers
|
||||||
rc = errno.EINTR
|
rc = rc or errno.EINTR
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Catch any other exceptions raised
|
# Catch any other exceptions raised
|
||||||
logger.error(
|
logger.error(
|
||||||
@ -306,16 +300,20 @@ without iterating through the remaining projects.
|
|||||||
type(e).__name__,
|
type(e).__name__,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
rc = getattr(e, "errno", 1)
|
rc = rc or getattr(e, "errno", 1)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
sys.exit(rc)
|
sys.exit(rc)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def InitWorker(cls):
|
class WorkerKeyboardInterrupt(Exception):
|
||||||
|
"""Keyboard interrupt exception for worker processes."""
|
||||||
|
|
||||||
|
|
||||||
|
def InitWorker():
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def DoWorkWrapper(cls, mirror, opt, cmd, shell, config, project_idx):
|
def DoWorkWrapper(mirror, opt, cmd, shell, config, args):
|
||||||
"""A wrapper around the DoWork() method.
|
"""A wrapper around the DoWork() method.
|
||||||
|
|
||||||
Catch the KeyboardInterrupt exceptions here and re-raise them as a
|
Catch the KeyboardInterrupt exceptions here and re-raise them as a
|
||||||
@ -323,18 +321,14 @@ without iterating through the remaining projects.
|
|||||||
with stacktraces and making the parent hang indefinitely.
|
with stacktraces and making the parent hang indefinitely.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
cnt, project = args
|
||||||
try:
|
try:
|
||||||
return DoWork(project, mirror, opt, cmd, shell, project_idx, config)
|
return DoWork(project, mirror, opt, cmd, shell, cnt, config)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("%s: Worker interrupted" % project.name)
|
print("%s: Worker interrupted" % project.name)
|
||||||
raise WorkerKeyboardInterrupt()
|
raise WorkerKeyboardInterrupt()
|
||||||
|
|
||||||
|
|
||||||
class WorkerKeyboardInterrupt(Exception):
|
|
||||||
"""Keyboard interrupt exception for worker processes."""
|
|
||||||
|
|
||||||
|
|
||||||
def DoWork(project, mirror, opt, cmd, shell, cnt, config):
|
def DoWork(project, mirror, opt, cmd, shell, cnt, config):
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
|
|
||||||
|
294
subcmds/gc.py
294
subcmds/gc.py
@ -1,294 +0,0 @@
|
|||||||
# Copyright (C) 2024 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.
|
|
||||||
|
|
||||||
import os
|
|
||||||
from typing import List, Set
|
|
||||||
|
|
||||||
from command import Command
|
|
||||||
from git_command import GitCommand
|
|
||||||
import platform_utils
|
|
||||||
from progress import Progress
|
|
||||||
from project import Project
|
|
||||||
|
|
||||||
|
|
||||||
class Gc(Command):
|
|
||||||
COMMON = True
|
|
||||||
helpSummary = "Cleaning up internal repo and Git state."
|
|
||||||
helpUsage = """
|
|
||||||
%prog
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _Options(self, p):
|
|
||||||
p.add_option(
|
|
||||||
"-n",
|
|
||||||
"--dry-run",
|
|
||||||
dest="dryrun",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="do everything except actually delete",
|
|
||||||
)
|
|
||||||
p.add_option(
|
|
||||||
"-y",
|
|
||||||
"--yes",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="answer yes to all safe prompts",
|
|
||||||
)
|
|
||||||
p.add_option(
|
|
||||||
"--repack",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="repack all projects that use partial clone with "
|
|
||||||
"filter=blob:none",
|
|
||||||
)
|
|
||||||
|
|
||||||
def _find_git_to_delete(
|
|
||||||
self, to_keep: Set[str], start_dir: str
|
|
||||||
) -> Set[str]:
|
|
||||||
"""Searches no longer needed ".git" directories.
|
|
||||||
|
|
||||||
Scans the file system starting from `start_dir` and removes all
|
|
||||||
directories that end with ".git" that are not in the `to_keep` set.
|
|
||||||
"""
|
|
||||||
to_delete = set()
|
|
||||||
for root, dirs, _ in platform_utils.walk(start_dir):
|
|
||||||
for directory in dirs:
|
|
||||||
if not directory.endswith(".git"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
path = os.path.join(root, directory)
|
|
||||||
if path not in to_keep:
|
|
||||||
to_delete.add(path)
|
|
||||||
|
|
||||||
return to_delete
|
|
||||||
|
|
||||||
def delete_unused_projects(self, projects: List[Project], opt):
|
|
||||||
print(f"Scanning filesystem under {self.repodir}...")
|
|
||||||
|
|
||||||
project_paths = set()
|
|
||||||
project_object_paths = set()
|
|
||||||
|
|
||||||
for project in projects:
|
|
||||||
project_paths.add(project.gitdir)
|
|
||||||
project_object_paths.add(project.objdir)
|
|
||||||
|
|
||||||
to_delete = self._find_git_to_delete(
|
|
||||||
project_paths, os.path.join(self.repodir, "projects")
|
|
||||||
)
|
|
||||||
|
|
||||||
to_delete.update(
|
|
||||||
self._find_git_to_delete(
|
|
||||||
project_object_paths,
|
|
||||||
os.path.join(self.repodir, "project-objects"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not to_delete:
|
|
||||||
print("Nothing to clean up.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
print("Identified the following projects are no longer used:")
|
|
||||||
print("\n".join(to_delete))
|
|
||||||
print("")
|
|
||||||
if not opt.yes:
|
|
||||||
print(
|
|
||||||
"If you proceed, any local commits in those projects will be "
|
|
||||||
"destroyed!"
|
|
||||||
)
|
|
||||||
ask = input("Proceed? [y/N] ")
|
|
||||||
if ask.lower() != "y":
|
|
||||||
return 1
|
|
||||||
|
|
||||||
pm = Progress(
|
|
||||||
"Deleting",
|
|
||||||
len(to_delete),
|
|
||||||
delay=False,
|
|
||||||
quiet=opt.quiet,
|
|
||||||
show_elapsed=True,
|
|
||||||
elide=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
for path in to_delete:
|
|
||||||
if opt.dryrun:
|
|
||||||
print(f"\nWould have deleted ${path}")
|
|
||||||
else:
|
|
||||||
tmp_path = os.path.join(
|
|
||||||
os.path.dirname(path),
|
|
||||||
f"to_be_deleted_{os.path.basename(path)}",
|
|
||||||
)
|
|
||||||
platform_utils.rename(path, tmp_path)
|
|
||||||
platform_utils.rmtree(tmp_path)
|
|
||||||
pm.update(msg=path)
|
|
||||||
pm.end()
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def _generate_promisor_files(self, pack_dir: str):
|
|
||||||
"""Generates promisor files for all pack files in the given directory.
|
|
||||||
|
|
||||||
Promisor files are empty files with the same name as the corresponding
|
|
||||||
pack file but with the ".promisor" extension. They are used by Git.
|
|
||||||
"""
|
|
||||||
for root, _, files in platform_utils.walk(pack_dir):
|
|
||||||
for file in files:
|
|
||||||
if not file.endswith(".pack"):
|
|
||||||
continue
|
|
||||||
with open(os.path.join(root, f"{file[:-4]}promisor"), "w"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def repack_projects(self, projects: List[Project], opt):
|
|
||||||
repack_projects = []
|
|
||||||
# Find all projects eligible for repacking:
|
|
||||||
# - can't be shared
|
|
||||||
# - have a specific fetch filter
|
|
||||||
for project in projects:
|
|
||||||
if project.config.GetBoolean("extensions.preciousObjects"):
|
|
||||||
continue
|
|
||||||
if not project.clone_depth:
|
|
||||||
continue
|
|
||||||
if project.manifest.CloneFilterForDepth != "blob:none":
|
|
||||||
continue
|
|
||||||
|
|
||||||
repack_projects.append(project)
|
|
||||||
|
|
||||||
if opt.dryrun:
|
|
||||||
print(f"Would have repacked {len(repack_projects)} projects.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
pm = Progress(
|
|
||||||
"Repacking (this will take a while)",
|
|
||||||
len(repack_projects),
|
|
||||||
delay=False,
|
|
||||||
quiet=opt.quiet,
|
|
||||||
show_elapsed=True,
|
|
||||||
elide=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
for project in repack_projects:
|
|
||||||
pm.update(msg=f"{project.name}")
|
|
||||||
|
|
||||||
pack_dir = os.path.join(project.gitdir, "tmp_repo_repack")
|
|
||||||
if os.path.isdir(pack_dir):
|
|
||||||
platform_utils.rmtree(pack_dir)
|
|
||||||
os.mkdir(pack_dir)
|
|
||||||
|
|
||||||
# Prepare workspace for repacking - remove all unreachable refs and
|
|
||||||
# their objects.
|
|
||||||
GitCommand(
|
|
||||||
project,
|
|
||||||
["reflog", "expire", "--expire-unreachable=all"],
|
|
||||||
verify_command=True,
|
|
||||||
).Wait()
|
|
||||||
pm.update(msg=f"{project.name} | gc", inc=0)
|
|
||||||
GitCommand(
|
|
||||||
project,
|
|
||||||
["gc"],
|
|
||||||
verify_command=True,
|
|
||||||
).Wait()
|
|
||||||
|
|
||||||
# Get all objects that are reachable from the remote, and pack them.
|
|
||||||
pm.update(msg=f"{project.name} | generating list of objects", inc=0)
|
|
||||||
remote_objects_cmd = GitCommand(
|
|
||||||
project,
|
|
||||||
[
|
|
||||||
"rev-list",
|
|
||||||
"--objects",
|
|
||||||
f"--remotes={project.remote.name}",
|
|
||||||
"--filter=blob:none",
|
|
||||||
"--tags",
|
|
||||||
],
|
|
||||||
capture_stdout=True,
|
|
||||||
verify_command=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get all local objects and pack them.
|
|
||||||
local_head_objects_cmd = GitCommand(
|
|
||||||
project,
|
|
||||||
["rev-list", "--objects", "HEAD^{tree}"],
|
|
||||||
capture_stdout=True,
|
|
||||||
verify_command=True,
|
|
||||||
)
|
|
||||||
local_objects_cmd = GitCommand(
|
|
||||||
project,
|
|
||||||
[
|
|
||||||
"rev-list",
|
|
||||||
"--objects",
|
|
||||||
"--all",
|
|
||||||
"--reflog",
|
|
||||||
"--indexed-objects",
|
|
||||||
"--not",
|
|
||||||
f"--remotes={project.remote.name}",
|
|
||||||
"--tags",
|
|
||||||
],
|
|
||||||
capture_stdout=True,
|
|
||||||
verify_command=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
remote_objects_cmd.Wait()
|
|
||||||
|
|
||||||
pm.update(msg=f"{project.name} | remote repack", inc=0)
|
|
||||||
GitCommand(
|
|
||||||
project,
|
|
||||||
["pack-objects", os.path.join(pack_dir, "pack")],
|
|
||||||
input=remote_objects_cmd.stdout,
|
|
||||||
capture_stderr=True,
|
|
||||||
capture_stdout=True,
|
|
||||||
verify_command=True,
|
|
||||||
).Wait()
|
|
||||||
|
|
||||||
# create promisor file for each pack file
|
|
||||||
self._generate_promisor_files(pack_dir)
|
|
||||||
|
|
||||||
local_head_objects_cmd.Wait()
|
|
||||||
local_objects_cmd.Wait()
|
|
||||||
|
|
||||||
pm.update(msg=f"{project.name} | local repack", inc=0)
|
|
||||||
GitCommand(
|
|
||||||
project,
|
|
||||||
["pack-objects", os.path.join(pack_dir, "pack")],
|
|
||||||
input=local_head_objects_cmd.stdout + local_objects_cmd.stdout,
|
|
||||||
capture_stderr=True,
|
|
||||||
capture_stdout=True,
|
|
||||||
verify_command=True,
|
|
||||||
).Wait()
|
|
||||||
|
|
||||||
# Swap the old pack directory with the new one.
|
|
||||||
platform_utils.rename(
|
|
||||||
os.path.join(project.objdir, "objects", "pack"),
|
|
||||||
os.path.join(project.objdir, "objects", "pack_old"),
|
|
||||||
)
|
|
||||||
platform_utils.rename(
|
|
||||||
pack_dir,
|
|
||||||
os.path.join(project.objdir, "objects", "pack"),
|
|
||||||
)
|
|
||||||
platform_utils.rmtree(
|
|
||||||
os.path.join(project.objdir, "objects", "pack_old")
|
|
||||||
)
|
|
||||||
|
|
||||||
pm.end()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
|
||||||
projects: List[Project] = self.GetProjects(
|
|
||||||
args, all_manifests=not opt.this_manifest_only
|
|
||||||
)
|
|
||||||
|
|
||||||
ret = self.delete_unused_projects(projects, opt)
|
|
||||||
if ret != 0:
|
|
||||||
return ret
|
|
||||||
|
|
||||||
if not opt.repack:
|
|
||||||
return
|
|
||||||
|
|
||||||
return self.repack_projects(projects, opt)
|
|
@ -23,6 +23,7 @@ from error import GitError
|
|||||||
from error import InvalidArgumentsError
|
from error import InvalidArgumentsError
|
||||||
from error import SilentRepoExitError
|
from error import SilentRepoExitError
|
||||||
from git_command import GitCommand
|
from git_command import GitCommand
|
||||||
|
from project import Project
|
||||||
from repo_logging import RepoLogger
|
from repo_logging import RepoLogger
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ class GrepColoring(Coloring):
|
|||||||
class ExecuteOneResult(NamedTuple):
|
class ExecuteOneResult(NamedTuple):
|
||||||
"""Result from an execute instance."""
|
"""Result from an execute instance."""
|
||||||
|
|
||||||
project_idx: int
|
project: Project
|
||||||
rc: int
|
rc: int
|
||||||
stdout: str
|
stdout: str
|
||||||
stderr: str
|
stderr: str
|
||||||
@ -261,10 +262,8 @@ contain a line that matches both expressions:
|
|||||||
help="Show only file names not containing matching lines",
|
help="Show only file names not containing matching lines",
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
def _ExecuteOne(self, cmd_argv, project):
|
||||||
def _ExecuteOne(cls, cmd_argv, project_idx):
|
|
||||||
"""Process one project."""
|
"""Process one project."""
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
try:
|
try:
|
||||||
p = GitCommand(
|
p = GitCommand(
|
||||||
project,
|
project,
|
||||||
@ -275,7 +274,7 @@ contain a line that matches both expressions:
|
|||||||
verify_command=True,
|
verify_command=True,
|
||||||
)
|
)
|
||||||
except GitError as e:
|
except GitError as e:
|
||||||
return ExecuteOneResult(project_idx, -1, None, str(e), e)
|
return ExecuteOneResult(project, -1, None, str(e), e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
error = None
|
error = None
|
||||||
@ -283,12 +282,10 @@ contain a line that matches both expressions:
|
|||||||
except GitError as e:
|
except GitError as e:
|
||||||
rc = 1
|
rc = 1
|
||||||
error = e
|
error = e
|
||||||
return ExecuteOneResult(project_idx, rc, p.stdout, p.stderr, error)
|
return ExecuteOneResult(project, rc, p.stdout, p.stderr, error)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _ProcessResults(
|
def _ProcessResults(full_name, have_rev, opt, _pool, out, results):
|
||||||
full_name, have_rev, opt, projects, _pool, out, results
|
|
||||||
):
|
|
||||||
git_failed = False
|
git_failed = False
|
||||||
bad_rev = False
|
bad_rev = False
|
||||||
have_match = False
|
have_match = False
|
||||||
@ -296,10 +293,9 @@ contain a line that matches both expressions:
|
|||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
project = projects[result.project_idx]
|
|
||||||
if result.rc < 0:
|
if result.rc < 0:
|
||||||
git_failed = True
|
git_failed = True
|
||||||
out.project("--- project %s ---" % _RelPath(project))
|
out.project("--- project %s ---" % _RelPath(result.project))
|
||||||
out.nl()
|
out.nl()
|
||||||
out.fail("%s", result.stderr)
|
out.fail("%s", result.stderr)
|
||||||
out.nl()
|
out.nl()
|
||||||
@ -315,7 +311,9 @@ contain a line that matches both expressions:
|
|||||||
):
|
):
|
||||||
bad_rev = True
|
bad_rev = True
|
||||||
else:
|
else:
|
||||||
out.project("--- project %s ---" % _RelPath(project))
|
out.project(
|
||||||
|
"--- project %s ---" % _RelPath(result.project)
|
||||||
|
)
|
||||||
out.nl()
|
out.nl()
|
||||||
out.fail("%s", result.stderr.strip())
|
out.fail("%s", result.stderr.strip())
|
||||||
out.nl()
|
out.nl()
|
||||||
@ -333,13 +331,13 @@ contain a line that matches both expressions:
|
|||||||
rev, line = line.split(":", 1)
|
rev, line = line.split(":", 1)
|
||||||
out.write("%s", rev)
|
out.write("%s", rev)
|
||||||
out.write(":")
|
out.write(":")
|
||||||
out.project(_RelPath(project))
|
out.project(_RelPath(result.project))
|
||||||
out.write("/")
|
out.write("/")
|
||||||
out.write("%s", line)
|
out.write("%s", line)
|
||||||
out.nl()
|
out.nl()
|
||||||
elif full_name:
|
elif full_name:
|
||||||
for line in r:
|
for line in r:
|
||||||
out.project(_RelPath(project))
|
out.project(_RelPath(result.project))
|
||||||
out.write("/")
|
out.write("/")
|
||||||
out.write("%s", line)
|
out.write("%s", line)
|
||||||
out.nl()
|
out.nl()
|
||||||
@ -383,18 +381,15 @@ contain a line that matches both expressions:
|
|||||||
cmd_argv.extend(opt.revision)
|
cmd_argv.extend(opt.revision)
|
||||||
cmd_argv.append("--")
|
cmd_argv.append("--")
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = projects
|
|
||||||
git_failed, bad_rev, have_match, errors = self.ExecuteInParallel(
|
git_failed, bad_rev, have_match, errors = self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
functools.partial(self._ExecuteOne, cmd_argv),
|
functools.partial(self._ExecuteOne, cmd_argv),
|
||||||
range(len(projects)),
|
projects,
|
||||||
callback=functools.partial(
|
callback=functools.partial(
|
||||||
self._ProcessResults, full_name, have_rev, opt, projects
|
self._ProcessResults, full_name, have_rev, opt
|
||||||
),
|
),
|
||||||
output=out,
|
output=out,
|
||||||
ordered=True,
|
ordered=True,
|
||||||
chunksize=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if git_failed:
|
if git_failed:
|
||||||
|
@ -21,9 +21,10 @@ from command import MirrorSafeCommand
|
|||||||
from error import RepoUnhandledExceptionError
|
from error import RepoUnhandledExceptionError
|
||||||
from error import UpdateManifestError
|
from error import UpdateManifestError
|
||||||
from git_command import git_require
|
from git_command import git_require
|
||||||
|
from git_command import MIN_GIT_VERSION_HARD
|
||||||
|
from git_command import MIN_GIT_VERSION_SOFT
|
||||||
from repo_logging import RepoLogger
|
from repo_logging import RepoLogger
|
||||||
from wrapper import Wrapper
|
from wrapper import Wrapper
|
||||||
from wrapper import WrapperDir
|
|
||||||
|
|
||||||
|
|
||||||
logger = RepoLogger(__file__)
|
logger = RepoLogger(__file__)
|
||||||
@ -52,10 +53,6 @@ The optional -b argument can be used to select the manifest branch
|
|||||||
to checkout and use. If no branch is specified, the remote's default
|
to checkout and use. If no branch is specified, the remote's default
|
||||||
branch is used. This is equivalent to using -b HEAD.
|
branch is used. This is equivalent to using -b HEAD.
|
||||||
|
|
||||||
The optional --manifest-upstream-branch argument can be used when a commit is
|
|
||||||
provided to --manifest-branch (or -b), to specify the name of the git ref in
|
|
||||||
which the commit can be found.
|
|
||||||
|
|
||||||
The optional -m argument can be used to specify an alternate manifest
|
The optional -m argument can be used to specify an alternate manifest
|
||||||
to be used. If no manifest is specified, the manifest default.xml
|
to be used. If no manifest is specified, the manifest default.xml
|
||||||
will be used.
|
will be used.
|
||||||
@ -139,7 +136,6 @@ to update the working directory files.
|
|||||||
# manifest project is special and is created when instantiating the
|
# manifest project is special and is created when instantiating the
|
||||||
# manifest which happens before we parse options.
|
# manifest which happens before we parse options.
|
||||||
self.manifest.manifestProject.clone_depth = opt.manifest_depth
|
self.manifest.manifestProject.clone_depth = opt.manifest_depth
|
||||||
self.manifest.manifestProject.upstream = opt.manifest_upstream_branch
|
|
||||||
clone_filter_for_depth = (
|
clone_filter_for_depth = (
|
||||||
"blob:none" if (_REPO_ALLOW_SHALLOW == "0") else None
|
"blob:none" if (_REPO_ALLOW_SHALLOW == "0") else None
|
||||||
)
|
)
|
||||||
@ -322,12 +318,6 @@ to update the working directory files.
|
|||||||
" be used with --standalone-manifest."
|
" be used with --standalone-manifest."
|
||||||
)
|
)
|
||||||
|
|
||||||
if opt.manifest_upstream_branch and opt.manifest_branch is None:
|
|
||||||
self.OptionParser.error(
|
|
||||||
"--manifest-upstream-branch cannot be used without "
|
|
||||||
"--manifest-branch."
|
|
||||||
)
|
|
||||||
|
|
||||||
if args:
|
if args:
|
||||||
if opt.manifest_url:
|
if opt.manifest_url:
|
||||||
self.OptionParser.error(
|
self.OptionParser.error(
|
||||||
@ -341,17 +331,13 @@ to update the working directory files.
|
|||||||
self.OptionParser.error("too many arguments to init")
|
self.OptionParser.error("too many arguments to init")
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
wrapper = Wrapper()
|
git_require(MIN_GIT_VERSION_HARD, fail=True)
|
||||||
|
if not git_require(MIN_GIT_VERSION_SOFT):
|
||||||
reqs = wrapper.Requirements.from_dir(WrapperDir())
|
|
||||||
git_require(reqs.get_hard_ver("git"), fail=True)
|
|
||||||
min_git_version_soft = reqs.get_soft_ver("git")
|
|
||||||
if not git_require(min_git_version_soft):
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"repo: warning: git-%s+ will soon be required; "
|
"repo: warning: git-%s+ will soon be required; "
|
||||||
"please upgrade your version of git to maintain "
|
"please upgrade your version of git to maintain "
|
||||||
"support.",
|
"support.",
|
||||||
".".join(str(x) for x in min_git_version_soft),
|
".".join(str(x) for x in MIN_GIT_VERSION_SOFT),
|
||||||
)
|
)
|
||||||
|
|
||||||
rp = self.manifest.repoProject
|
rp = self.manifest.repoProject
|
||||||
@ -364,6 +350,7 @@ to update the working directory files.
|
|||||||
|
|
||||||
# Handle new --repo-rev requests.
|
# Handle new --repo-rev requests.
|
||||||
if opt.repo_rev:
|
if opt.repo_rev:
|
||||||
|
wrapper = Wrapper()
|
||||||
try:
|
try:
|
||||||
remote_ref, rev = wrapper.check_repo_rev(
|
remote_ref, rev = wrapper.check_repo_rev(
|
||||||
rp.worktree,
|
rp.worktree,
|
||||||
|
@ -27,10 +27,8 @@ class Prune(PagedCommand):
|
|||||||
"""
|
"""
|
||||||
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
|
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
|
||||||
|
|
||||||
@classmethod
|
def _ExecuteOne(self, project):
|
||||||
def _ExecuteOne(cls, project_idx):
|
|
||||||
"""Process one project."""
|
"""Process one project."""
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
return project.PruneHeads()
|
return project.PruneHeads()
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
@ -43,12 +41,10 @@ class Prune(PagedCommand):
|
|||||||
def _ProcessResults(_pool, _output, results):
|
def _ProcessResults(_pool, _output, results):
|
||||||
return list(itertools.chain.from_iterable(results))
|
return list(itertools.chain.from_iterable(results))
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = projects
|
|
||||||
all_branches = self.ExecuteInParallel(
|
all_branches = self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
self._ExecuteOne,
|
self._ExecuteOne,
|
||||||
range(len(projects)),
|
projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
ordered=True,
|
ordered=True,
|
||||||
)
|
)
|
||||||
|
@ -21,6 +21,7 @@ from error import RepoExitError
|
|||||||
from git_command import git
|
from git_command import git
|
||||||
from git_config import IsImmutable
|
from git_config import IsImmutable
|
||||||
from progress import Progress
|
from progress import Progress
|
||||||
|
from project import Project
|
||||||
from repo_logging import RepoLogger
|
from repo_logging import RepoLogger
|
||||||
|
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ logger = RepoLogger(__file__)
|
|||||||
|
|
||||||
|
|
||||||
class ExecuteOneResult(NamedTuple):
|
class ExecuteOneResult(NamedTuple):
|
||||||
project_idx: int
|
project: Project
|
||||||
error: Exception
|
error: Exception
|
||||||
|
|
||||||
|
|
||||||
@ -79,20 +80,18 @@ revision specified in the manifest.
|
|||||||
if not git.check_ref_format("heads/%s" % nb):
|
if not git.check_ref_format("heads/%s" % nb):
|
||||||
self.OptionParser.error("'%s' is not a valid name" % nb)
|
self.OptionParser.error("'%s' is not a valid name" % nb)
|
||||||
|
|
||||||
@classmethod
|
def _ExecuteOne(self, revision, nb, project):
|
||||||
def _ExecuteOne(cls, revision, nb, default_revisionExpr, project_idx):
|
|
||||||
"""Start one project."""
|
"""Start one project."""
|
||||||
# If the current revision is immutable, such as a SHA1, a tag or
|
# If the current revision is immutable, such as a SHA1, a tag or
|
||||||
# a change, then we can't push back to it. Substitute with
|
# a change, then we can't push back to it. Substitute with
|
||||||
# dest_branch, if defined; or with manifest default revision instead.
|
# dest_branch, if defined; or with manifest default revision instead.
|
||||||
branch_merge = ""
|
branch_merge = ""
|
||||||
error = None
|
error = None
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
if IsImmutable(project.revisionExpr):
|
if IsImmutable(project.revisionExpr):
|
||||||
if project.dest_branch:
|
if project.dest_branch:
|
||||||
branch_merge = project.dest_branch
|
branch_merge = project.dest_branch
|
||||||
else:
|
else:
|
||||||
branch_merge = default_revisionExpr
|
branch_merge = self.manifest.default.revisionExpr
|
||||||
|
|
||||||
try:
|
try:
|
||||||
project.StartBranch(
|
project.StartBranch(
|
||||||
@ -101,7 +100,7 @@ revision specified in the manifest.
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("error: unable to checkout %s: %s", project.name, e)
|
logger.error("error: unable to checkout %s: %s", project.name, e)
|
||||||
error = e
|
error = e
|
||||||
return ExecuteOneResult(project_idx, error)
|
return ExecuteOneResult(project, error)
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
nb = args[0]
|
nb = args[0]
|
||||||
@ -121,27 +120,18 @@ revision specified in the manifest.
|
|||||||
def _ProcessResults(_pool, pm, results):
|
def _ProcessResults(_pool, pm, results):
|
||||||
for result in results:
|
for result in results:
|
||||||
if result.error:
|
if result.error:
|
||||||
project = all_projects[result.project_idx]
|
err_projects.append(result.project)
|
||||||
err_projects.append(project)
|
|
||||||
err.append(result.error)
|
err.append(result.error)
|
||||||
pm.update(msg="")
|
pm.update(msg="")
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = all_projects
|
|
||||||
self.ExecuteInParallel(
|
self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
functools.partial(
|
functools.partial(self._ExecuteOne, opt.revision, nb),
|
||||||
self._ExecuteOne,
|
all_projects,
|
||||||
opt.revision,
|
|
||||||
nb,
|
|
||||||
self.manifest.default.revisionExpr,
|
|
||||||
),
|
|
||||||
range(len(all_projects)),
|
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
output=Progress(
|
output=Progress(
|
||||||
f"Starting {nb}", len(all_projects), quiet=opt.quiet
|
f"Starting {nb}", len(all_projects), quiet=opt.quiet
|
||||||
),
|
),
|
||||||
chunksize=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err_projects:
|
if err_projects:
|
||||||
|
@ -88,8 +88,7 @@ the following meanings:
|
|||||||
"projects",
|
"projects",
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
def _StatusHelper(self, quiet, local, project):
|
||||||
def _StatusHelper(cls, quiet, local, project_idx):
|
|
||||||
"""Obtains the status for a specific project.
|
"""Obtains the status for a specific project.
|
||||||
|
|
||||||
Obtains the status for a project, redirecting the output to
|
Obtains the status for a project, redirecting the output to
|
||||||
@ -100,13 +99,12 @@ the following meanings:
|
|||||||
local: a boolean, if True, the path is relative to the local
|
local: a boolean, if True, the path is relative to the local
|
||||||
(sub)manifest. If false, the path is relative to the outermost
|
(sub)manifest. If false, the path is relative to the outermost
|
||||||
manifest.
|
manifest.
|
||||||
project_idx: Project index to get status of.
|
project: Project to get status of.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The status of the project.
|
The status of the project.
|
||||||
"""
|
"""
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
ret = project.PrintWorkTreeStatus(
|
ret = project.PrintWorkTreeStatus(
|
||||||
quiet=quiet, output_redir=buf, local=local
|
quiet=quiet, output_redir=buf, local=local
|
||||||
)
|
)
|
||||||
@ -145,17 +143,14 @@ the following meanings:
|
|||||||
ret += 1
|
ret += 1
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = all_projects
|
|
||||||
counter = self.ExecuteInParallel(
|
counter = self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
functools.partial(
|
functools.partial(
|
||||||
self._StatusHelper, opt.quiet, opt.this_manifest_only
|
self._StatusHelper, opt.quiet, opt.this_manifest_only
|
||||||
),
|
),
|
||||||
range(len(all_projects)),
|
all_projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
ordered=True,
|
ordered=True,
|
||||||
chunksize=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not opt.quiet and len(all_projects) == counter:
|
if not opt.quiet and len(all_projects) == counter:
|
||||||
|
226
subcmds/sync.py
226
subcmds/sync.py
@ -131,17 +131,12 @@ def _SafeCheckoutOrder(checkouts: List[Project]) -> List[List[Project]]:
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def _chunksize(projects: int, jobs: int) -> int:
|
|
||||||
"""Calculate chunk size for the given number of projects and jobs."""
|
|
||||||
return min(max(1, projects // jobs), WORKER_BATCH_SIZE)
|
|
||||||
|
|
||||||
|
|
||||||
class _FetchOneResult(NamedTuple):
|
class _FetchOneResult(NamedTuple):
|
||||||
"""_FetchOne return value.
|
"""_FetchOne return value.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
success (bool): True if successful.
|
success (bool): True if successful.
|
||||||
project_idx (int): The fetched project index.
|
project (Project): The fetched project.
|
||||||
start (float): The starting time.time().
|
start (float): The starting time.time().
|
||||||
finish (float): The ending time.time().
|
finish (float): The ending time.time().
|
||||||
remote_fetched (bool): True if the remote was actually queried.
|
remote_fetched (bool): True if the remote was actually queried.
|
||||||
@ -149,7 +144,7 @@ class _FetchOneResult(NamedTuple):
|
|||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
errors: List[Exception]
|
errors: List[Exception]
|
||||||
project_idx: int
|
project: Project
|
||||||
start: float
|
start: float
|
||||||
finish: float
|
finish: float
|
||||||
remote_fetched: bool
|
remote_fetched: bool
|
||||||
@ -182,14 +177,14 @@ class _CheckoutOneResult(NamedTuple):
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
success (bool): True if successful.
|
success (bool): True if successful.
|
||||||
project_idx (int): The project index.
|
project (Project): The project.
|
||||||
start (float): The starting time.time().
|
start (float): The starting time.time().
|
||||||
finish (float): The ending time.time().
|
finish (float): The ending time.time().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
errors: List[Exception]
|
errors: List[Exception]
|
||||||
project_idx: int
|
project: Project
|
||||||
start: float
|
start: float
|
||||||
finish: float
|
finish: float
|
||||||
|
|
||||||
@ -350,8 +345,6 @@ later is required to fix a server side protocol bug.
|
|||||||
# value later on.
|
# value later on.
|
||||||
PARALLEL_JOBS = 0
|
PARALLEL_JOBS = 0
|
||||||
|
|
||||||
_JOBS_WARN_THRESHOLD = 100
|
|
||||||
|
|
||||||
def _Options(self, p, show_smart=True):
|
def _Options(self, p, show_smart=True):
|
||||||
p.add_option(
|
p.add_option(
|
||||||
"--jobs-network",
|
"--jobs-network",
|
||||||
@ -407,13 +400,6 @@ later is required to fix a server side protocol bug.
|
|||||||
"projects no longer exist in the manifest. "
|
"projects no longer exist in the manifest. "
|
||||||
"WARNING: this may cause loss of data",
|
"WARNING: this may cause loss of data",
|
||||||
)
|
)
|
||||||
p.add_option(
|
|
||||||
"--rebase",
|
|
||||||
dest="rebase",
|
|
||||||
action="store_true",
|
|
||||||
help="rebase local commits regardless of whether they are "
|
|
||||||
"published",
|
|
||||||
)
|
|
||||||
p.add_option(
|
p.add_option(
|
||||||
"-l",
|
"-l",
|
||||||
"--local-only",
|
"--local-only",
|
||||||
@ -594,8 +580,7 @@ later is required to fix a server side protocol bug.
|
|||||||
branch = branch[len(R_HEADS) :]
|
branch = branch[len(R_HEADS) :]
|
||||||
return branch
|
return branch
|
||||||
|
|
||||||
@classmethod
|
def _GetCurrentBranchOnly(self, opt, manifest):
|
||||||
def _GetCurrentBranchOnly(cls, opt, manifest):
|
|
||||||
"""Returns whether current-branch or use-superproject options are
|
"""Returns whether current-branch or use-superproject options are
|
||||||
enabled.
|
enabled.
|
||||||
|
|
||||||
@ -713,8 +698,7 @@ later is required to fix a server side protocol bug.
|
|||||||
if need_unload:
|
if need_unload:
|
||||||
m.outer_client.manifest.Unload()
|
m.outer_client.manifest.Unload()
|
||||||
|
|
||||||
@classmethod
|
def _FetchProjectList(self, opt, projects):
|
||||||
def _FetchProjectList(cls, opt, projects):
|
|
||||||
"""Main function of the fetch worker.
|
"""Main function of the fetch worker.
|
||||||
|
|
||||||
The projects we're given share the same underlying git object store, so
|
The projects we're given share the same underlying git object store, so
|
||||||
@ -726,23 +710,21 @@ later is required to fix a server side protocol bug.
|
|||||||
opt: Program options returned from optparse. See _Options().
|
opt: Program options returned from optparse. See _Options().
|
||||||
projects: Projects to fetch.
|
projects: Projects to fetch.
|
||||||
"""
|
"""
|
||||||
return [cls._FetchOne(opt, x) for x in projects]
|
return [self._FetchOne(opt, x) for x in projects]
|
||||||
|
|
||||||
@classmethod
|
def _FetchOne(self, opt, project):
|
||||||
def _FetchOne(cls, opt, project_idx):
|
|
||||||
"""Fetch git objects for a single project.
|
"""Fetch git objects for a single project.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
opt: Program options returned from optparse. See _Options().
|
opt: Program options returned from optparse. See _Options().
|
||||||
project_idx: Project index for the project to fetch.
|
project: Project object for the project to fetch.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Whether the fetch was successful.
|
Whether the fetch was successful.
|
||||||
"""
|
"""
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
k = f"{project.name} @ {project.relpath}"
|
k = f"{project.name} @ {project.relpath}"
|
||||||
cls.get_parallel_context()["sync_dict"][k] = start
|
self._sync_dict[k] = start
|
||||||
success = False
|
success = False
|
||||||
remote_fetched = False
|
remote_fetched = False
|
||||||
errors = []
|
errors = []
|
||||||
@ -752,7 +734,7 @@ later is required to fix a server side protocol bug.
|
|||||||
quiet=opt.quiet,
|
quiet=opt.quiet,
|
||||||
verbose=opt.verbose,
|
verbose=opt.verbose,
|
||||||
output_redir=buf,
|
output_redir=buf,
|
||||||
current_branch_only=cls._GetCurrentBranchOnly(
|
current_branch_only=self._GetCurrentBranchOnly(
|
||||||
opt, project.manifest
|
opt, project.manifest
|
||||||
),
|
),
|
||||||
force_sync=opt.force_sync,
|
force_sync=opt.force_sync,
|
||||||
@ -762,7 +744,7 @@ later is required to fix a server side protocol bug.
|
|||||||
optimized_fetch=opt.optimized_fetch,
|
optimized_fetch=opt.optimized_fetch,
|
||||||
retry_fetches=opt.retry_fetches,
|
retry_fetches=opt.retry_fetches,
|
||||||
prune=opt.prune,
|
prune=opt.prune,
|
||||||
ssh_proxy=cls.get_parallel_context()["ssh_proxy"],
|
ssh_proxy=self.ssh_proxy,
|
||||||
clone_filter=project.manifest.CloneFilter,
|
clone_filter=project.manifest.CloneFilter,
|
||||||
partial_clone_exclude=project.manifest.PartialCloneExclude,
|
partial_clone_exclude=project.manifest.PartialCloneExclude,
|
||||||
clone_filter_for_depth=project.manifest.CloneFilterForDepth,
|
clone_filter_for_depth=project.manifest.CloneFilterForDepth,
|
||||||
@ -794,20 +776,24 @@ later is required to fix a server side protocol bug.
|
|||||||
type(e).__name__,
|
type(e).__name__,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
|
del self._sync_dict[k]
|
||||||
errors.append(e)
|
errors.append(e)
|
||||||
raise
|
raise
|
||||||
finally:
|
|
||||||
del cls.get_parallel_context()["sync_dict"][k]
|
|
||||||
|
|
||||||
finish = time.time()
|
finish = time.time()
|
||||||
|
del self._sync_dict[k]
|
||||||
return _FetchOneResult(
|
return _FetchOneResult(
|
||||||
success, errors, project_idx, start, finish, remote_fetched
|
success, errors, project, start, finish, remote_fetched
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _FetchInitChild(cls, ssh_proxy):
|
||||||
|
cls.ssh_proxy = ssh_proxy
|
||||||
|
|
||||||
def _GetSyncProgressMessage(self):
|
def _GetSyncProgressMessage(self):
|
||||||
earliest_time = float("inf")
|
earliest_time = float("inf")
|
||||||
earliest_proj = None
|
earliest_proj = None
|
||||||
items = self.get_parallel_context()["sync_dict"].items()
|
items = self._sync_dict.items()
|
||||||
for project, t in items:
|
for project, t in items:
|
||||||
if t < earliest_time:
|
if t < earliest_time:
|
||||||
earliest_time = t
|
earliest_time = t
|
||||||
@ -815,7 +801,7 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
if not earliest_proj:
|
if not earliest_proj:
|
||||||
# This function is called when sync is still running but in some
|
# This function is called when sync is still running but in some
|
||||||
# cases (by chance), sync_dict can contain no entries. Return some
|
# cases (by chance), _sync_dict can contain no entries. Return some
|
||||||
# text to indicate that sync is still working.
|
# text to indicate that sync is still working.
|
||||||
return "..working.."
|
return "..working.."
|
||||||
|
|
||||||
@ -823,19 +809,10 @@ later is required to fix a server side protocol bug.
|
|||||||
jobs = jobs_str(len(items))
|
jobs = jobs_str(len(items))
|
||||||
return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
|
return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def InitWorker(cls):
|
|
||||||
# Force connect to the manager server now.
|
|
||||||
# This is good because workers are initialized one by one. Without this,
|
|
||||||
# multiple workers may connect to the manager when handling the first
|
|
||||||
# job at the same time. Then the connection may fail if too many
|
|
||||||
# connections are pending and execeeded the socket listening backlog,
|
|
||||||
# especially on MacOS.
|
|
||||||
len(cls.get_parallel_context()["sync_dict"])
|
|
||||||
|
|
||||||
def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
|
def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
|
||||||
ret = True
|
ret = True
|
||||||
|
|
||||||
|
jobs = opt.jobs_network
|
||||||
fetched = set()
|
fetched = set()
|
||||||
remote_fetched = set()
|
remote_fetched = set()
|
||||||
pm = Progress(
|
pm = Progress(
|
||||||
@ -847,6 +824,7 @@ later is required to fix a server side protocol bug.
|
|||||||
elide=True,
|
elide=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._sync_dict = multiprocessing.Manager().dict()
|
||||||
sync_event = _threading.Event()
|
sync_event = _threading.Event()
|
||||||
|
|
||||||
def _MonitorSyncLoop():
|
def _MonitorSyncLoop():
|
||||||
@ -857,13 +835,19 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
|
sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
|
||||||
sync_progress_thread.daemon = True
|
sync_progress_thread.daemon = True
|
||||||
|
sync_progress_thread.start()
|
||||||
|
|
||||||
def _ProcessResults(pool, pm, results_sets):
|
objdir_project_map = dict()
|
||||||
|
for project in projects:
|
||||||
|
objdir_project_map.setdefault(project.objdir, []).append(project)
|
||||||
|
projects_list = list(objdir_project_map.values())
|
||||||
|
|
||||||
|
def _ProcessResults(results_sets):
|
||||||
ret = True
|
ret = True
|
||||||
for results in results_sets:
|
for results in results_sets:
|
||||||
for result in results:
|
for result in results:
|
||||||
success = result.success
|
success = result.success
|
||||||
project = projects[result.project_idx]
|
project = result.project
|
||||||
start = result.start
|
start = result.start
|
||||||
finish = result.finish
|
finish = result.finish
|
||||||
self._fetch_times.Set(project, finish - start)
|
self._fetch_times.Set(project, finish - start)
|
||||||
@ -887,50 +871,58 @@ later is required to fix a server side protocol bug.
|
|||||||
fetched.add(project.gitdir)
|
fetched.add(project.gitdir)
|
||||||
pm.update()
|
pm.update()
|
||||||
if not ret and opt.fail_fast:
|
if not ret and opt.fail_fast:
|
||||||
if pool:
|
|
||||||
pool.close()
|
|
||||||
break
|
break
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = projects
|
|
||||||
self.get_parallel_context()[
|
|
||||||
"sync_dict"
|
|
||||||
] = multiprocessing.Manager().dict()
|
|
||||||
|
|
||||||
objdir_project_map = dict()
|
|
||||||
for index, project in enumerate(projects):
|
|
||||||
objdir_project_map.setdefault(project.objdir, []).append(index)
|
|
||||||
projects_list = list(objdir_project_map.values())
|
|
||||||
|
|
||||||
jobs = max(1, min(opt.jobs_network, len(projects_list)))
|
|
||||||
|
|
||||||
# We pass the ssh proxy settings via the class. This allows
|
# We pass the ssh proxy settings via the class. This allows
|
||||||
# multiprocessing to pickle it up when spawning children. We can't
|
# multiprocessing to pickle it up when spawning children. We can't pass
|
||||||
# pass it as an argument to _FetchProjectList below as
|
# it as an argument to _FetchProjectList below as multiprocessing is
|
||||||
# multiprocessing is unable to pickle those.
|
# unable to pickle those.
|
||||||
self.get_parallel_context()["ssh_proxy"] = ssh_proxy
|
Sync.ssh_proxy = None
|
||||||
|
|
||||||
sync_progress_thread.start()
|
# NB: Multiprocessing is heavy, so don't spin it up for one job.
|
||||||
if not opt.quiet:
|
if len(projects_list) == 1 or jobs == 1:
|
||||||
|
self._FetchInitChild(ssh_proxy)
|
||||||
|
if not _ProcessResults(
|
||||||
|
self._FetchProjectList(opt, x) for x in projects_list
|
||||||
|
):
|
||||||
|
ret = False
|
||||||
|
else:
|
||||||
|
# Favor throughput over responsiveness when quiet. It seems that
|
||||||
|
# imap() will yield results in batches relative to chunksize, so
|
||||||
|
# even as the children finish a sync, we won't see the result until
|
||||||
|
# one child finishes ~chunksize jobs. When using a large --jobs
|
||||||
|
# with large chunksize, this can be jarring as there will be a large
|
||||||
|
# initial delay where repo looks like it isn't doing anything and
|
||||||
|
# sits at 0%, but then suddenly completes a lot of jobs all at once.
|
||||||
|
# Since this code is more network bound, we can accept a bit more
|
||||||
|
# CPU overhead with a smaller chunksize so that the user sees more
|
||||||
|
# immediate & continuous feedback.
|
||||||
|
if opt.quiet:
|
||||||
|
chunksize = WORKER_BATCH_SIZE
|
||||||
|
else:
|
||||||
pm.update(inc=0, msg="warming up")
|
pm.update(inc=0, msg="warming up")
|
||||||
try:
|
chunksize = 4
|
||||||
ret = self.ExecuteInParallel(
|
with multiprocessing.Pool(
|
||||||
jobs,
|
jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)
|
||||||
|
) as pool:
|
||||||
|
results = pool.imap_unordered(
|
||||||
functools.partial(self._FetchProjectList, opt),
|
functools.partial(self._FetchProjectList, opt),
|
||||||
projects_list,
|
projects_list,
|
||||||
callback=_ProcessResults,
|
chunksize=chunksize,
|
||||||
output=pm,
|
|
||||||
# Use chunksize=1 to avoid the chance that some workers are
|
|
||||||
# idle while other workers still have more than one job in
|
|
||||||
# their chunk queue.
|
|
||||||
chunksize=1,
|
|
||||||
initializer=self.InitWorker,
|
|
||||||
)
|
)
|
||||||
finally:
|
if not _ProcessResults(results):
|
||||||
sync_event.set()
|
ret = False
|
||||||
sync_progress_thread.join()
|
pool.close()
|
||||||
|
|
||||||
|
# Cleanup the reference now that we're done with it, and we're going to
|
||||||
|
# release any resources it points to. If we don't, later
|
||||||
|
# multiprocessing usage (e.g. checkouts) will try to pickle and then
|
||||||
|
# crash.
|
||||||
|
del Sync.ssh_proxy
|
||||||
|
|
||||||
|
sync_event.set()
|
||||||
|
pm.end()
|
||||||
self._fetch_times.Save()
|
self._fetch_times.Save()
|
||||||
self._local_sync_state.Save()
|
self._local_sync_state.Save()
|
||||||
|
|
||||||
@ -971,8 +963,6 @@ later is required to fix a server side protocol bug.
|
|||||||
if not success:
|
if not success:
|
||||||
err_event.set()
|
err_event.set()
|
||||||
|
|
||||||
# Call self update, unless requested not to
|
|
||||||
if os.environ.get("REPO_SKIP_SELF_UPDATE", "0") == "0":
|
|
||||||
_PostRepoFetch(rp, opt.repo_verify)
|
_PostRepoFetch(rp, opt.repo_verify)
|
||||||
if opt.network_only:
|
if opt.network_only:
|
||||||
# Bail out now; the rest touches the working tree.
|
# Bail out now; the rest touches the working tree.
|
||||||
@ -1018,15 +1008,8 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
return _FetchMainResult(all_projects)
|
return _FetchMainResult(all_projects)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _CheckoutOne(
|
def _CheckoutOne(
|
||||||
cls,
|
self, detach_head, force_sync, force_checkout, verbose, project
|
||||||
detach_head,
|
|
||||||
force_sync,
|
|
||||||
force_checkout,
|
|
||||||
force_rebase,
|
|
||||||
verbose,
|
|
||||||
project_idx,
|
|
||||||
):
|
):
|
||||||
"""Checkout work tree for one project
|
"""Checkout work tree for one project
|
||||||
|
|
||||||
@ -1036,14 +1019,12 @@ later is required to fix a server side protocol bug.
|
|||||||
existing git directory that was previously linked to a different
|
existing git directory that was previously linked to a different
|
||||||
object directory).
|
object directory).
|
||||||
force_checkout: Force checking out of the repo content.
|
force_checkout: Force checking out of the repo content.
|
||||||
force_rebase: Force rebase.
|
|
||||||
verbose: Whether to show verbose messages.
|
verbose: Whether to show verbose messages.
|
||||||
project_idx: Project index for the project to checkout.
|
project: Project object for the project to checkout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Whether the fetch was successful.
|
Whether the fetch was successful.
|
||||||
"""
|
"""
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
syncbuf = SyncBuffer(
|
syncbuf = SyncBuffer(
|
||||||
project.manifest.manifestProject.config, detach_head=detach_head
|
project.manifest.manifestProject.config, detach_head=detach_head
|
||||||
@ -1055,13 +1036,10 @@ later is required to fix a server side protocol bug.
|
|||||||
syncbuf,
|
syncbuf,
|
||||||
force_sync=force_sync,
|
force_sync=force_sync,
|
||||||
force_checkout=force_checkout,
|
force_checkout=force_checkout,
|
||||||
force_rebase=force_rebase,
|
|
||||||
errors=errors,
|
errors=errors,
|
||||||
verbose=verbose,
|
verbose=verbose,
|
||||||
)
|
)
|
||||||
success = syncbuf.Finish()
|
success = syncbuf.Finish()
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger.error("Keyboard interrupt while processing %s", project.name)
|
|
||||||
except GitError as e:
|
except GitError as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
"error.GitError: Cannot checkout %s: %s", project.name, e
|
"error.GitError: Cannot checkout %s: %s", project.name, e
|
||||||
@ -1079,7 +1057,7 @@ later is required to fix a server side protocol bug.
|
|||||||
if not success:
|
if not success:
|
||||||
logger.error("error: Cannot checkout %s", project.name)
|
logger.error("error: Cannot checkout %s", project.name)
|
||||||
finish = time.time()
|
finish = time.time()
|
||||||
return _CheckoutOneResult(success, errors, project_idx, start, finish)
|
return _CheckoutOneResult(success, errors, project, start, finish)
|
||||||
|
|
||||||
def _Checkout(self, all_projects, opt, err_results, checkout_errors):
|
def _Checkout(self, all_projects, opt, err_results, checkout_errors):
|
||||||
"""Checkout projects listed in all_projects
|
"""Checkout projects listed in all_projects
|
||||||
@ -1097,9 +1075,7 @@ later is required to fix a server side protocol bug.
|
|||||||
ret = True
|
ret = True
|
||||||
for result in results:
|
for result in results:
|
||||||
success = result.success
|
success = result.success
|
||||||
project = self.get_parallel_context()["projects"][
|
project = result.project
|
||||||
result.project_idx
|
|
||||||
]
|
|
||||||
start = result.start
|
start = result.start
|
||||||
finish = result.finish
|
finish = result.finish
|
||||||
self.event_log.AddSync(
|
self.event_log.AddSync(
|
||||||
@ -1126,8 +1102,6 @@ later is required to fix a server side protocol bug.
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
for projects in _SafeCheckoutOrder(all_projects):
|
for projects in _SafeCheckoutOrder(all_projects):
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = projects
|
|
||||||
proc_res = self.ExecuteInParallel(
|
proc_res = self.ExecuteInParallel(
|
||||||
opt.jobs_checkout,
|
opt.jobs_checkout,
|
||||||
functools.partial(
|
functools.partial(
|
||||||
@ -1135,18 +1109,13 @@ later is required to fix a server side protocol bug.
|
|||||||
opt.detach_head,
|
opt.detach_head,
|
||||||
opt.force_sync,
|
opt.force_sync,
|
||||||
opt.force_checkout,
|
opt.force_checkout,
|
||||||
opt.rebase,
|
|
||||||
opt.verbose,
|
opt.verbose,
|
||||||
),
|
),
|
||||||
range(len(projects)),
|
projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
output=Progress(
|
output=Progress(
|
||||||
"Checking out", len(all_projects), quiet=opt.quiet
|
"Checking out", len(all_projects), quiet=opt.quiet
|
||||||
),
|
),
|
||||||
# Use chunksize=1 to avoid the chance that some workers are
|
|
||||||
# idle while other workers still have more than one job in
|
|
||||||
# their chunk queue.
|
|
||||||
chunksize=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._local_sync_state.Save()
|
self._local_sync_state.Save()
|
||||||
@ -1446,10 +1415,7 @@ later is required to fix a server side protocol bug.
|
|||||||
for need_remove_file in need_remove_files:
|
for need_remove_file in need_remove_files:
|
||||||
# Try to remove the updated copyfile or linkfile.
|
# Try to remove the updated copyfile or linkfile.
|
||||||
# So, if the file is not exist, nothing need to do.
|
# So, if the file is not exist, nothing need to do.
|
||||||
platform_utils.remove(
|
platform_utils.remove(need_remove_file, missing_ok=True)
|
||||||
os.path.join(self.client.topdir, need_remove_file),
|
|
||||||
missing_ok=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create copy-link-files.json, save dest path of "copyfile" and
|
# Create copy-link-files.json, save dest path of "copyfile" and
|
||||||
# "linkfile".
|
# "linkfile".
|
||||||
@ -1504,7 +1470,6 @@ later is required to fix a server side protocol bug.
|
|||||||
if manifest_server.startswith("persistent-"):
|
if manifest_server.startswith("persistent-"):
|
||||||
manifest_server = manifest_server[len("persistent-") :]
|
manifest_server = manifest_server[len("persistent-") :]
|
||||||
|
|
||||||
# Changes in behavior should update docs/smart-sync.md accordingly.
|
|
||||||
try:
|
try:
|
||||||
server = xmlrpc.client.Server(manifest_server, transport=transport)
|
server = xmlrpc.client.Server(manifest_server, transport=transport)
|
||||||
if opt.smart_sync:
|
if opt.smart_sync:
|
||||||
@ -1515,19 +1480,6 @@ later is required to fix a server side protocol bug.
|
|||||||
[success, manifest_str] = server.GetApprovedManifest(
|
[success, manifest_str] = server.GetApprovedManifest(
|
||||||
branch, target
|
branch, target
|
||||||
)
|
)
|
||||||
elif (
|
|
||||||
"TARGET_PRODUCT" in os.environ
|
|
||||||
and "TARGET_BUILD_VARIANT" in os.environ
|
|
||||||
and "TARGET_RELEASE" in os.environ
|
|
||||||
):
|
|
||||||
target = "%s-%s-%s" % (
|
|
||||||
os.environ["TARGET_PRODUCT"],
|
|
||||||
os.environ["TARGET_RELEASE"],
|
|
||||||
os.environ["TARGET_BUILD_VARIANT"],
|
|
||||||
)
|
|
||||||
[success, manifest_str] = server.GetApprovedManifest(
|
|
||||||
branch, target
|
|
||||||
)
|
|
||||||
elif (
|
elif (
|
||||||
"TARGET_PRODUCT" in os.environ
|
"TARGET_PRODUCT" in os.environ
|
||||||
and "TARGET_BUILD_VARIANT" in os.environ
|
and "TARGET_BUILD_VARIANT" in os.environ
|
||||||
@ -1730,24 +1682,6 @@ later is required to fix a server side protocol bug.
|
|||||||
opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
|
opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
|
||||||
opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
|
opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
|
||||||
|
|
||||||
# Warn once if effective job counts seem excessively high.
|
|
||||||
# Prioritize --jobs, then --jobs-network, then --jobs-checkout.
|
|
||||||
job_options_to_check = (
|
|
||||||
("--jobs", opt.jobs),
|
|
||||||
("--jobs-network", opt.jobs_network),
|
|
||||||
("--jobs-checkout", opt.jobs_checkout),
|
|
||||||
)
|
|
||||||
for name, value in job_options_to_check:
|
|
||||||
if value > self._JOBS_WARN_THRESHOLD:
|
|
||||||
logger.warning(
|
|
||||||
"High job count (%d > %d) specified for %s; this may "
|
|
||||||
"lead to excessive resource usage or diminishing returns.",
|
|
||||||
value,
|
|
||||||
self._JOBS_WARN_THRESHOLD,
|
|
||||||
name,
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
errors = []
|
errors = []
|
||||||
try:
|
try:
|
||||||
@ -2019,8 +1953,6 @@ def _PostRepoFetch(rp, repo_verify=True, verbose=False):
|
|||||||
# We also have to make sure this will switch to an older commit if
|
# We also have to make sure this will switch to an older commit if
|
||||||
# that's the latest tag in order to support release rollback.
|
# that's the latest tag in order to support release rollback.
|
||||||
try:
|
try:
|
||||||
# Refresh index since reset --keep won't do it.
|
|
||||||
rp.work_git.update_index("-q", "--refresh")
|
|
||||||
rp.work_git.reset("--keep", new_rev)
|
rp.work_git.reset("--keep", new_rev)
|
||||||
except GitError as e:
|
except GitError as e:
|
||||||
raise RepoUnhandledExceptionError(e)
|
raise RepoUnhandledExceptionError(e)
|
||||||
|
@ -218,14 +218,9 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
def _Options(self, p):
|
def _Options(self, p):
|
||||||
p.add_option(
|
p.add_option(
|
||||||
"-t",
|
"-t",
|
||||||
"--topic-branch",
|
|
||||||
dest="auto_topic",
|
dest="auto_topic",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="set the topic to the local branch name",
|
help="send local branch name to Gerrit Code Review",
|
||||||
)
|
|
||||||
p.add_option(
|
|
||||||
"--topic",
|
|
||||||
help="set topic for the change",
|
|
||||||
)
|
)
|
||||||
p.add_option(
|
p.add_option(
|
||||||
"--hashtag",
|
"--hashtag",
|
||||||
@ -554,14 +549,42 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
people = copy.deepcopy(original_people)
|
people = copy.deepcopy(original_people)
|
||||||
self._AppendAutoList(branch, people)
|
self._AppendAutoList(branch, people)
|
||||||
|
|
||||||
|
# Check if there are local changes that may have been forgotten.
|
||||||
|
changes = branch.project.UncommitedFiles()
|
||||||
|
if opt.ignore_untracked_files:
|
||||||
|
untracked = set(branch.project.UntrackedFiles())
|
||||||
|
changes = [x for x in changes if x not in untracked]
|
||||||
|
|
||||||
|
if changes:
|
||||||
|
key = "review.%s.autoupload" % branch.project.remote.review
|
||||||
|
answer = branch.project.config.GetBoolean(key)
|
||||||
|
|
||||||
|
# If they want to auto upload, let's not ask because it
|
||||||
|
# could be automated.
|
||||||
|
if answer is None:
|
||||||
|
print()
|
||||||
|
print(
|
||||||
|
"Uncommitted changes in %s (did you forget to "
|
||||||
|
"amend?):" % branch.project.name
|
||||||
|
)
|
||||||
|
print("\n".join(changes))
|
||||||
|
print("Continue uploading? (y/N) ", end="", flush=True)
|
||||||
|
if opt.yes:
|
||||||
|
print("<--yes>")
|
||||||
|
a = "yes"
|
||||||
|
else:
|
||||||
|
a = sys.stdin.readline().strip().lower()
|
||||||
|
if a not in ("y", "yes", "t", "true", "on"):
|
||||||
|
print("skipping upload", file=sys.stderr)
|
||||||
|
branch.uploaded = False
|
||||||
|
branch.error = "User aborted"
|
||||||
|
return
|
||||||
|
|
||||||
# Check if topic branches should be sent to the server during
|
# Check if topic branches should be sent to the server during
|
||||||
# upload.
|
# upload.
|
||||||
if opt.topic is None:
|
|
||||||
if opt.auto_topic is not True:
|
if opt.auto_topic is not True:
|
||||||
key = "review.%s.uploadtopic" % branch.project.remote.review
|
key = "review.%s.uploadtopic" % branch.project.remote.review
|
||||||
opt.auto_topic = branch.project.config.GetBoolean(key)
|
opt.auto_topic = branch.project.config.GetBoolean(key)
|
||||||
if opt.auto_topic:
|
|
||||||
opt.topic = branch.name
|
|
||||||
|
|
||||||
def _ExpandCommaList(value):
|
def _ExpandCommaList(value):
|
||||||
"""Split |value| up into comma delimited entries."""
|
"""Split |value| up into comma delimited entries."""
|
||||||
@ -603,22 +626,19 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
full_dest = destination
|
full_dest = destination
|
||||||
if not full_dest.startswith(R_HEADS):
|
if not full_dest.startswith(R_HEADS):
|
||||||
full_dest = R_HEADS + full_dest
|
full_dest = R_HEADS + full_dest
|
||||||
full_revision = branch.project.revisionExpr
|
|
||||||
if not full_revision.startswith(R_HEADS):
|
|
||||||
full_revision = R_HEADS + full_revision
|
|
||||||
|
|
||||||
# If the merge branch of the local branch is different from
|
# If the merge branch of the local branch is different from
|
||||||
# the project's revision AND destination, this might not be
|
# the project's revision AND destination, this might not be
|
||||||
# intentional.
|
# intentional.
|
||||||
if (
|
if (
|
||||||
merge_branch
|
merge_branch
|
||||||
and merge_branch != full_revision
|
and merge_branch != branch.project.revisionExpr
|
||||||
and merge_branch != full_dest
|
and merge_branch != full_dest
|
||||||
):
|
):
|
||||||
print(
|
print(
|
||||||
f"For local branch {branch.name}: merge branch "
|
f"For local branch {branch.name}: merge branch "
|
||||||
f"{merge_branch} does not match destination branch "
|
f"{merge_branch} does not match destination branch "
|
||||||
f"{destination} and revision {branch.project.revisionExpr}"
|
f"{destination}"
|
||||||
)
|
)
|
||||||
print("skipping upload.")
|
print("skipping upload.")
|
||||||
print(
|
print(
|
||||||
@ -631,7 +651,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
branch.UploadForReview(
|
branch.UploadForReview(
|
||||||
people,
|
people,
|
||||||
dryrun=opt.dryrun,
|
dryrun=opt.dryrun,
|
||||||
topic=opt.topic,
|
auto_topic=opt.auto_topic,
|
||||||
hashtags=hashtags,
|
hashtags=hashtags,
|
||||||
labels=labels,
|
labels=labels,
|
||||||
private=opt.private,
|
private=opt.private,
|
||||||
@ -716,17 +736,16 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
merge_branch = p.stdout.strip()
|
merge_branch = p.stdout.strip()
|
||||||
return merge_branch
|
return merge_branch
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
def _GatherOne(cls, opt, project_idx):
|
def _GatherOne(opt, project):
|
||||||
"""Figure out the upload status for |project|."""
|
"""Figure out the upload status for |project|."""
|
||||||
project = cls.get_parallel_context()["projects"][project_idx]
|
|
||||||
if opt.current_branch:
|
if opt.current_branch:
|
||||||
cbr = project.CurrentBranch
|
cbr = project.CurrentBranch
|
||||||
up_branch = project.GetUploadableBranch(cbr)
|
up_branch = project.GetUploadableBranch(cbr)
|
||||||
avail = [up_branch] if up_branch else None
|
avail = [up_branch] if up_branch else None
|
||||||
else:
|
else:
|
||||||
avail = project.GetUploadableBranches(opt.branch)
|
avail = project.GetUploadableBranches(opt.branch)
|
||||||
return (project_idx, avail)
|
return (project, avail)
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
projects = self.GetProjects(
|
projects = self.GetProjects(
|
||||||
@ -736,8 +755,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
def _ProcessResults(_pool, _out, results):
|
def _ProcessResults(_pool, _out, results):
|
||||||
pending = []
|
pending = []
|
||||||
for result in results:
|
for result in results:
|
||||||
project_idx, avail = result
|
project, avail = result
|
||||||
project = projects[project_idx]
|
|
||||||
if avail is None:
|
if avail is None:
|
||||||
logger.error(
|
logger.error(
|
||||||
'repo: error: %s: Unable to upload branch "%s". '
|
'repo: error: %s: Unable to upload branch "%s". '
|
||||||
@ -748,15 +766,13 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
project.manifest.branch,
|
project.manifest.branch,
|
||||||
)
|
)
|
||||||
elif avail:
|
elif avail:
|
||||||
pending.append((project, avail))
|
pending.append(result)
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
with self.ParallelContext():
|
|
||||||
self.get_parallel_context()["projects"] = projects
|
|
||||||
pending = self.ExecuteInParallel(
|
pending = self.ExecuteInParallel(
|
||||||
opt.jobs,
|
opt.jobs,
|
||||||
functools.partial(self._GatherOne, opt),
|
functools.partial(self._GatherOne, opt),
|
||||||
range(len(projects)),
|
projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
1
tests/fixtures/gitc_config
vendored
Normal file
1
tests/fixtures/gitc_config
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
gitc_dir=/test/usr/local/google/gitc
|
8
tests/fixtures/test.gitconfig
vendored
8
tests/fixtures/test.gitconfig
vendored
@ -11,11 +11,3 @@
|
|||||||
intk = 10k
|
intk = 10k
|
||||||
intm = 10m
|
intm = 10m
|
||||||
intg = 10g
|
intg = 10g
|
||||||
|
|
||||||
[color "status"]
|
|
||||||
one = yellow
|
|
||||||
two = magenta cyan
|
|
||||||
three = black red ul
|
|
||||||
reset = reset
|
|
||||||
none
|
|
||||||
empty =
|
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
# Copyright (C) 2024 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 color.py module."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import color
|
|
||||||
import git_config
|
|
||||||
|
|
||||||
|
|
||||||
def fixture(*paths):
|
|
||||||
"""Return a path relative to test/fixtures."""
|
|
||||||
return os.path.join(os.path.dirname(__file__), "fixtures", *paths)
|
|
||||||
|
|
||||||
|
|
||||||
class ColoringTests(unittest.TestCase):
|
|
||||||
"""tests of the Coloring class."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Create a GitConfig object using the test.gitconfig fixture."""
|
|
||||||
config_fixture = fixture("test.gitconfig")
|
|
||||||
self.config = git_config.GitConfig(config_fixture)
|
|
||||||
color.SetDefaultColoring("true")
|
|
||||||
self.color = color.Coloring(self.config, "status")
|
|
||||||
|
|
||||||
def test_Color_Parse_all_params_none(self):
|
|
||||||
"""all params are None"""
|
|
||||||
val = self.color._parse(None, None, None, None)
|
|
||||||
self.assertEqual("", val)
|
|
||||||
|
|
||||||
def test_Color_Parse_first_parameter_none(self):
|
|
||||||
"""check fg & bg & attr"""
|
|
||||||
val = self.color._parse(None, "black", "red", "ul")
|
|
||||||
self.assertEqual("\x1b[4;30;41m", val)
|
|
||||||
|
|
||||||
def test_Color_Parse_one_entry(self):
|
|
||||||
"""check fg"""
|
|
||||||
val = self.color._parse("one", None, None, None)
|
|
||||||
self.assertEqual("\033[33m", val)
|
|
||||||
|
|
||||||
def test_Color_Parse_two_entry(self):
|
|
||||||
"""check fg & bg"""
|
|
||||||
val = self.color._parse("two", None, None, None)
|
|
||||||
self.assertEqual("\033[35;46m", val)
|
|
||||||
|
|
||||||
def test_Color_Parse_three_entry(self):
|
|
||||||
"""check fg & bg & attr"""
|
|
||||||
val = self.color._parse("three", None, None, None)
|
|
||||||
self.assertEqual("\033[4;30;41m", val)
|
|
||||||
|
|
||||||
def test_Color_Parse_reset_entry(self):
|
|
||||||
"""check reset entry"""
|
|
||||||
val = self.color._parse("reset", None, None, None)
|
|
||||||
self.assertEqual("\033[m", val)
|
|
||||||
|
|
||||||
def test_Color_Parse_empty_entry(self):
|
|
||||||
"""check empty entry"""
|
|
||||||
val = self.color._parse("none", "blue", "white", "dim")
|
|
||||||
self.assertEqual("\033[2;34;47m", val)
|
|
||||||
val = self.color._parse("empty", "green", "white", "bold")
|
|
||||||
self.assertEqual("\033[1;32;47m", val)
|
|
@ -21,8 +21,6 @@ import subprocess
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import git_command
|
import git_command
|
||||||
import wrapper
|
import wrapper
|
||||||
|
|
||||||
@ -265,7 +263,6 @@ class UserAgentUnitTest(unittest.TestCase):
|
|||||||
m = re.match(r"^[^ ]+$", os_name)
|
m = re.match(r"^[^ ]+$", os_name)
|
||||||
self.assertIsNotNone(m)
|
self.assertIsNotNone(m)
|
||||||
|
|
||||||
@pytest.mark.skip_cq("TODO(b/266734831): Find out why this fails in CQ")
|
|
||||||
def test_smoke_repo(self):
|
def test_smoke_repo(self):
|
||||||
"""Make sure repo UA returns something useful."""
|
"""Make sure repo UA returns something useful."""
|
||||||
ua = git_command.user_agent.repo
|
ua = git_command.user_agent.repo
|
||||||
@ -274,7 +271,6 @@ class UserAgentUnitTest(unittest.TestCase):
|
|||||||
m = re.match(r"^git-repo/[^ ]+ ([^ ]+) git/[^ ]+ Python/[0-9.]+", ua)
|
m = re.match(r"^git-repo/[^ ]+ ([^ ]+) git/[^ ]+ Python/[0-9.]+", ua)
|
||||||
self.assertIsNotNone(m)
|
self.assertIsNotNone(m)
|
||||||
|
|
||||||
@pytest.mark.skip_cq("TODO(b/266734831): Find out why this fails in CQ")
|
|
||||||
def test_smoke_git(self):
|
def test_smoke_git(self):
|
||||||
"""Make sure git UA returns something useful."""
|
"""Make sure git UA returns something useful."""
|
||||||
ua = git_command.user_agent.git
|
ua = git_command.user_agent.git
|
||||||
|
@ -21,7 +21,6 @@ import tempfile
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
|
||||||
from test_manifest_xml import sort_attributes
|
from test_manifest_xml import sort_attributes
|
||||||
|
|
||||||
import git_superproject
|
import git_superproject
|
||||||
@ -146,7 +145,6 @@ class SuperprojectTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertIsNone(manifest.superproject)
|
self.assertIsNone(manifest.superproject)
|
||||||
|
|
||||||
@pytest.mark.skip_cq("TODO(b/266734831): Find out why this takes 8m+ in CQ")
|
|
||||||
def test_superproject_get_superproject_invalid_url(self):
|
def test_superproject_get_superproject_invalid_url(self):
|
||||||
"""Test with an invalid url."""
|
"""Test with an invalid url."""
|
||||||
manifest = self.getXmlManifest(
|
manifest = self.getXmlManifest(
|
||||||
@ -170,7 +168,6 @@ class SuperprojectTestCase(unittest.TestCase):
|
|||||||
self.assertFalse(sync_result.success)
|
self.assertFalse(sync_result.success)
|
||||||
self.assertTrue(sync_result.fatal)
|
self.assertTrue(sync_result.fatal)
|
||||||
|
|
||||||
@pytest.mark.skip_cq("TODO(b/266734831): Find out why this takes 8m+ in CQ")
|
|
||||||
def test_superproject_get_superproject_invalid_branch(self):
|
def test_superproject_get_superproject_invalid_branch(self):
|
||||||
"""Test with an invalid branch."""
|
"""Test with an invalid branch."""
|
||||||
manifest = self.getXmlManifest(
|
manifest = self.getXmlManifest(
|
||||||
|
@ -150,7 +150,7 @@ class EventLogTestCase(unittest.TestCase):
|
|||||||
<version event>
|
<version event>
|
||||||
<start event>
|
<start event>
|
||||||
"""
|
"""
|
||||||
self._event_log_module.StartEvent([])
|
self._event_log_module.StartEvent()
|
||||||
with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
|
with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
|
||||||
log_path = self._event_log_module.Write(path=tempdir)
|
log_path = self._event_log_module.Write(path=tempdir)
|
||||||
self._log_data = self.readLog(log_path)
|
self._log_data = self.readLog(log_path)
|
||||||
@ -213,8 +213,10 @@ class EventLogTestCase(unittest.TestCase):
|
|||||||
<version event>
|
<version event>
|
||||||
<command event>
|
<command event>
|
||||||
"""
|
"""
|
||||||
|
name = "repo"
|
||||||
|
subcommands = ["init" "this"]
|
||||||
self._event_log_module.CommandEvent(
|
self._event_log_module.CommandEvent(
|
||||||
name="repo", subcommands=["init", "this"]
|
name="repo", subcommands=subcommands
|
||||||
)
|
)
|
||||||
with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
|
with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
|
||||||
log_path = self._event_log_module.Write(path=tempdir)
|
log_path = self._event_log_module.Write(path=tempdir)
|
||||||
@ -223,10 +225,12 @@ class EventLogTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(len(self._log_data), 2)
|
self.assertEqual(len(self._log_data), 2)
|
||||||
command_event = self._log_data[1]
|
command_event = self._log_data[1]
|
||||||
self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
|
self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
|
||||||
self.verifyCommonKeys(command_event, expected_event_name="cmd_name")
|
self.verifyCommonKeys(command_event, expected_event_name="command")
|
||||||
# Check for 'command' event specific fields.
|
# Check for 'command' event specific fields.
|
||||||
self.assertIn("name", command_event)
|
self.assertIn("name", command_event)
|
||||||
self.assertEqual(command_event["name"], "repo-init-this")
|
self.assertIn("subcommands", command_event)
|
||||||
|
self.assertEqual(command_event["name"], name)
|
||||||
|
self.assertEqual(command_event["subcommands"], subcommands)
|
||||||
|
|
||||||
def test_def_params_event_repo_config(self):
|
def test_def_params_event_repo_config(self):
|
||||||
"""Test 'def_params' event data outputs only repo config keys.
|
"""Test 'def_params' event data outputs only repo config keys.
|
||||||
@ -378,17 +382,17 @@ class EventLogTestCase(unittest.TestCase):
|
|||||||
socket_path = os.path.join(tempdir, "server.sock")
|
socket_path = os.path.join(tempdir, "server.sock")
|
||||||
server_ready = threading.Condition()
|
server_ready = threading.Condition()
|
||||||
# Start "server" listening on Unix domain socket at socket_path.
|
# Start "server" listening on Unix domain socket at socket_path.
|
||||||
|
try:
|
||||||
server_thread = threading.Thread(
|
server_thread = threading.Thread(
|
||||||
target=serverLoggingThread,
|
target=serverLoggingThread,
|
||||||
args=(socket_path, server_ready, received_traces),
|
args=(socket_path, server_ready, received_traces),
|
||||||
)
|
)
|
||||||
try:
|
|
||||||
server_thread.start()
|
server_thread.start()
|
||||||
|
|
||||||
with server_ready:
|
with server_ready:
|
||||||
server_ready.wait(timeout=120)
|
server_ready.wait(timeout=120)
|
||||||
|
|
||||||
self._event_log_module.StartEvent([])
|
self._event_log_module.StartEvent()
|
||||||
path = self._event_log_module.Write(
|
path = self._event_log_module.Write(
|
||||||
path=f"af_unix:{socket_path}"
|
path=f"af_unix:{socket_path}"
|
||||||
)
|
)
|
||||||
|
@ -51,7 +51,7 @@ INVALID_FS_PATHS = (
|
|||||||
"foo~",
|
"foo~",
|
||||||
"blah/foo~",
|
"blah/foo~",
|
||||||
# Block Unicode characters that get normalized out by filesystems.
|
# Block Unicode characters that get normalized out by filesystems.
|
||||||
"foo\u200cbar",
|
"foo\u200Cbar",
|
||||||
# Block newlines.
|
# Block newlines.
|
||||||
"f\n/bar",
|
"f\n/bar",
|
||||||
"f\r/bar",
|
"f\r/bar",
|
||||||
@ -1049,91 +1049,6 @@ class RemoveProjectElementTests(ManifestParseTestCase):
|
|||||||
self.assertTrue(found_proj1_path1)
|
self.assertTrue(found_proj1_path1)
|
||||||
self.assertTrue(found_proj2)
|
self.assertTrue(found_proj2)
|
||||||
|
|
||||||
def test_base_revision_checks_on_patching(self):
|
|
||||||
manifest_fail_wrong_tag = self.getXmlManifest(
|
|
||||||
"""
|
|
||||||
<manifest>
|
|
||||||
<remote name="default-remote" fetch="http://localhost" />
|
|
||||||
<default remote="default-remote" revision="tag.002" />
|
|
||||||
<project name="project1" path="tests/path1" />
|
|
||||||
<extend-project name="project1" revision="new_hash" base-rev="tag.001" />
|
|
||||||
</manifest>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
with self.assertRaises(error.ManifestParseError):
|
|
||||||
manifest_fail_wrong_tag.ToXml()
|
|
||||||
|
|
||||||
manifest_fail_remove = self.getXmlManifest(
|
|
||||||
"""
|
|
||||||
<manifest>
|
|
||||||
<remote name="default-remote" fetch="http://localhost" />
|
|
||||||
<default remote="default-remote" revision="refs/heads/main" />
|
|
||||||
<project name="project1" path="tests/path1" revision="hash1" />
|
|
||||||
<remove-project name="project1" base-rev="wrong_hash" />
|
|
||||||
</manifest>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
with self.assertRaises(error.ManifestParseError):
|
|
||||||
manifest_fail_remove.ToXml()
|
|
||||||
|
|
||||||
manifest_fail_extend = self.getXmlManifest(
|
|
||||||
"""
|
|
||||||
<manifest>
|
|
||||||
<remote name="default-remote" fetch="http://localhost" />
|
|
||||||
<default remote="default-remote" revision="refs/heads/main" />
|
|
||||||
<project name="project1" path="tests/path1" revision="hash1" />
|
|
||||||
<extend-project name="project1" revision="new_hash" base-rev="wrong_hash" />
|
|
||||||
</manifest>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
with self.assertRaises(error.ManifestParseError):
|
|
||||||
manifest_fail_extend.ToXml()
|
|
||||||
|
|
||||||
manifest_fail_unknown = self.getXmlManifest(
|
|
||||||
"""
|
|
||||||
<manifest>
|
|
||||||
<remote name="default-remote" fetch="http://localhost" />
|
|
||||||
<default remote="default-remote" revision="refs/heads/main" />
|
|
||||||
<project name="project1" path="tests/path1" />
|
|
||||||
<extend-project name="project1" revision="new_hash" base-rev="any_hash" />
|
|
||||||
</manifest>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
with self.assertRaises(error.ManifestParseError):
|
|
||||||
manifest_fail_unknown.ToXml()
|
|
||||||
|
|
||||||
manifest_ok = self.getXmlManifest(
|
|
||||||
"""
|
|
||||||
<manifest>
|
|
||||||
<remote name="default-remote" fetch="http://localhost" />
|
|
||||||
<default remote="default-remote" revision="refs/heads/main" />
|
|
||||||
<project name="project1" path="tests/path1" revision="hash1" />
|
|
||||||
<project name="project2" path="tests/path2" revision="hash2" />
|
|
||||||
<project name="project3" path="tests/path3" revision="hash3" />
|
|
||||||
<project name="project4" path="tests/path4" revision="hash4" />
|
|
||||||
|
|
||||||
<remove-project name="project1" />
|
|
||||||
<remove-project name="project2" base-rev="hash2" />
|
|
||||||
<project name="project2" path="tests/path2" revision="new_hash2" />
|
|
||||||
<extend-project name="project3" base-rev="hash3" revision="new_hash3" />
|
|
||||||
<extend-project name="project3" base-rev="new_hash3" revision="newer_hash3" />
|
|
||||||
<remove-project path="tests/path4" base-rev="hash4" />
|
|
||||||
</manifest>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
found_proj2 = False
|
|
||||||
found_proj3 = False
|
|
||||||
for proj in manifest_ok.projects:
|
|
||||||
if proj.name == "project2":
|
|
||||||
found_proj2 = True
|
|
||||||
if proj.name == "project3":
|
|
||||||
found_proj3 = True
|
|
||||||
self.assertNotEqual(proj.name, "project1")
|
|
||||||
self.assertNotEqual(proj.name, "project4")
|
|
||||||
self.assertTrue(found_proj2)
|
|
||||||
self.assertTrue(found_proj3)
|
|
||||||
self.assertTrue(len(manifest_ok.projects) == 2)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendProjectElementTests(ManifestParseTestCase):
|
class ExtendProjectElementTests(ManifestParseTestCase):
|
||||||
"""Tests for <extend-project>."""
|
"""Tests for <extend-project>."""
|
||||||
|
@ -13,14 +13,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""Unit test for repo_logging module."""
|
"""Unit test for repo_logging module."""
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import io
|
|
||||||
import logging
|
|
||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from color import SetDefaultColoring
|
|
||||||
from error import RepoExitError
|
from error import RepoExitError
|
||||||
from repo_logging import RepoLogger
|
from repo_logging import RepoLogger
|
||||||
|
|
||||||
@ -67,35 +62,3 @@ class TestRepoLogger(unittest.TestCase):
|
|||||||
mock.call("Repo command failed: %s", "RepoExitError"),
|
mock.call("Repo command failed: %s", "RepoExitError"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_log_with_format_string(self):
|
|
||||||
"""Test different log levels with format strings."""
|
|
||||||
|
|
||||||
# Set color output to "always" for consistent test results.
|
|
||||||
# This ensures the logger's behavior is uniform across different
|
|
||||||
# environments and git configurations.
|
|
||||||
SetDefaultColoring("always")
|
|
||||||
|
|
||||||
# Regex pattern to match optional ANSI color codes.
|
|
||||||
# \033 - Escape character
|
|
||||||
# \[ - Opening square bracket
|
|
||||||
# [0-9;]* - Zero or more digits or semicolons
|
|
||||||
# m - Ending 'm' character
|
|
||||||
# ? - Makes the entire group optional
|
|
||||||
opt_color = r"(\033\[[0-9;]*m)?"
|
|
||||||
|
|
||||||
for level in (logging.INFO, logging.WARN, logging.ERROR):
|
|
||||||
name = logging.getLevelName(level)
|
|
||||||
|
|
||||||
with self.subTest(level=level, name=name):
|
|
||||||
output = io.StringIO()
|
|
||||||
|
|
||||||
with contextlib.redirect_stderr(output):
|
|
||||||
logger = RepoLogger(__name__)
|
|
||||||
logger.log(level, "%s", "100% pass")
|
|
||||||
|
|
||||||
self.assertRegex(
|
|
||||||
output.getvalue().strip(),
|
|
||||||
f"^{opt_color}100% pass{opt_color}$",
|
|
||||||
f"failed for level {name}",
|
|
||||||
)
|
|
||||||
|
@ -1,156 +0,0 @@
|
|||||||
# Copyright (C) 2024 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 forall subcmd."""
|
|
||||||
|
|
||||||
from io import StringIO
|
|
||||||
import os
|
|
||||||
from shutil import rmtree
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import git_command
|
|
||||||
import manifest_xml
|
|
||||||
import project
|
|
||||||
import subcmds
|
|
||||||
|
|
||||||
|
|
||||||
class AllCommands(unittest.TestCase):
|
|
||||||
"""Check registered all_commands."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Common setup."""
|
|
||||||
self.tempdirobj = tempfile.TemporaryDirectory(prefix="forall_tests")
|
|
||||||
self.tempdir = self.tempdirobj.name
|
|
||||||
self.repodir = os.path.join(self.tempdir, ".repo")
|
|
||||||
self.manifest_dir = os.path.join(self.repodir, "manifests")
|
|
||||||
self.manifest_file = os.path.join(
|
|
||||||
self.repodir, manifest_xml.MANIFEST_FILE_NAME
|
|
||||||
)
|
|
||||||
self.local_manifest_dir = os.path.join(
|
|
||||||
self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME
|
|
||||||
)
|
|
||||||
os.mkdir(self.repodir)
|
|
||||||
os.mkdir(self.manifest_dir)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Common teardown."""
|
|
||||||
rmtree(self.tempdir, ignore_errors=True)
|
|
||||||
|
|
||||||
def initTempGitTree(self, git_dir):
|
|
||||||
"""Create a new empty git checkout for testing."""
|
|
||||||
|
|
||||||
# Tests need to assume, that main is default branch at init,
|
|
||||||
# which is not supported in config until 2.28.
|
|
||||||
cmd = ["git", "init", "-q"]
|
|
||||||
if git_command.git_require((2, 28, 0)):
|
|
||||||
cmd += ["--initial-branch=main"]
|
|
||||||
else:
|
|
||||||
# Use template dir for init
|
|
||||||
templatedir = os.path.join(self.tempdirobj.name, ".test-template")
|
|
||||||
os.makedirs(templatedir)
|
|
||||||
with open(os.path.join(templatedir, "HEAD"), "w") as fp:
|
|
||||||
fp.write("ref: refs/heads/main\n")
|
|
||||||
cmd += ["--template", templatedir]
|
|
||||||
cmd += [git_dir]
|
|
||||||
subprocess.check_call(cmd)
|
|
||||||
|
|
||||||
def getXmlManifestWith8Projects(self):
|
|
||||||
"""Create and return a setup of 8 projects with enough dummy
|
|
||||||
files and setup to execute forall."""
|
|
||||||
|
|
||||||
# Set up a manifest git dir for parsing to work
|
|
||||||
gitdir = os.path.join(self.repodir, "manifests.git")
|
|
||||||
os.mkdir(gitdir)
|
|
||||||
with open(os.path.join(gitdir, "config"), "w") as fp:
|
|
||||||
fp.write(
|
|
||||||
"""[remote "origin"]
|
|
||||||
url = https://localhost:0/manifest
|
|
||||||
verbose = false
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add the manifest data
|
|
||||||
manifest_data = """
|
|
||||||
<manifest>
|
|
||||||
<remote name="origin" fetch="http://localhost" />
|
|
||||||
<default remote="origin" revision="refs/heads/main" />
|
|
||||||
<project name="project1" path="tests/path1" />
|
|
||||||
<project name="project2" path="tests/path2" />
|
|
||||||
<project name="project3" path="tests/path3" />
|
|
||||||
<project name="project4" path="tests/path4" />
|
|
||||||
<project name="project5" path="tests/path5" />
|
|
||||||
<project name="project6" path="tests/path6" />
|
|
||||||
<project name="project7" path="tests/path7" />
|
|
||||||
<project name="project8" path="tests/path8" />
|
|
||||||
</manifest>
|
|
||||||
"""
|
|
||||||
with open(self.manifest_file, "w", encoding="utf-8") as fp:
|
|
||||||
fp.write(manifest_data)
|
|
||||||
|
|
||||||
# Set up 8 empty projects to match the manifest
|
|
||||||
for x in range(1, 9):
|
|
||||||
os.makedirs(
|
|
||||||
os.path.join(
|
|
||||||
self.repodir, "projects/tests/path" + str(x) + ".git"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
os.makedirs(
|
|
||||||
os.path.join(
|
|
||||||
self.repodir, "project-objects/project" + str(x) + ".git"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
git_path = os.path.join(self.tempdir, "tests/path" + str(x))
|
|
||||||
self.initTempGitTree(git_path)
|
|
||||||
|
|
||||||
return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
|
|
||||||
|
|
||||||
# Use mock to capture stdout from the forall run
|
|
||||||
@unittest.mock.patch("sys.stdout", new_callable=StringIO)
|
|
||||||
def test_forall_all_projects_called_once(self, mock_stdout):
|
|
||||||
"""Test that all projects get a command run once each."""
|
|
||||||
|
|
||||||
manifest_with_8_projects = self.getXmlManifestWith8Projects()
|
|
||||||
|
|
||||||
cmd = subcmds.forall.Forall()
|
|
||||||
cmd.manifest = manifest_with_8_projects
|
|
||||||
|
|
||||||
# Use echo project names as the test of forall
|
|
||||||
opts, args = cmd.OptionParser.parse_args(["-c", "echo $REPO_PROJECT"])
|
|
||||||
opts.verbose = False
|
|
||||||
|
|
||||||
# Mock to not have the Execute fail on remote check
|
|
||||||
with mock.patch.object(
|
|
||||||
project.Project, "GetRevisionId", return_value="refs/heads/main"
|
|
||||||
):
|
|
||||||
# Run the forall command
|
|
||||||
cmd.Execute(opts, args)
|
|
||||||
|
|
||||||
# Verify that we got every project name in the prints
|
|
||||||
for x in range(1, 9):
|
|
||||||
self.assertIn("project" + str(x), mock_stdout.getvalue())
|
|
||||||
|
|
||||||
# Split the captured output into lines to count them
|
|
||||||
line_count = 0
|
|
||||||
for line in mock_stdout.getvalue().split("\n"):
|
|
||||||
# A commented out print to stderr as a reminder
|
|
||||||
# that stdout is mocked, include sys and uncomment if needed
|
|
||||||
# print(line, file=sys.stderr)
|
|
||||||
if len(line) > 0:
|
|
||||||
line_count += 1
|
|
||||||
|
|
||||||
# Verify that we didn't get more lines than expected
|
|
||||||
assert line_count == 8
|
|
@ -355,30 +355,6 @@ class SafeCheckoutOrder(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Chunksize(unittest.TestCase):
|
|
||||||
"""Tests for _chunksize."""
|
|
||||||
|
|
||||||
def test_single_project(self):
|
|
||||||
"""Single project."""
|
|
||||||
self.assertEqual(sync._chunksize(1, 1), 1)
|
|
||||||
|
|
||||||
def test_low_project_count(self):
|
|
||||||
"""Multiple projects, low number of projects to sync."""
|
|
||||||
self.assertEqual(sync._chunksize(10, 1), 10)
|
|
||||||
self.assertEqual(sync._chunksize(10, 2), 5)
|
|
||||||
self.assertEqual(sync._chunksize(10, 4), 2)
|
|
||||||
self.assertEqual(sync._chunksize(10, 8), 1)
|
|
||||||
self.assertEqual(sync._chunksize(10, 16), 1)
|
|
||||||
|
|
||||||
def test_high_project_count(self):
|
|
||||||
"""Multiple projects, high number of projects to sync."""
|
|
||||||
self.assertEqual(sync._chunksize(2800, 1), 32)
|
|
||||||
self.assertEqual(sync._chunksize(2800, 16), 32)
|
|
||||||
self.assertEqual(sync._chunksize(2800, 32), 32)
|
|
||||||
self.assertEqual(sync._chunksize(2800, 64), 32)
|
|
||||||
self.assertEqual(sync._chunksize(2800, 128), 21)
|
|
||||||
|
|
||||||
|
|
||||||
class GetPreciousObjectsState(unittest.TestCase):
|
class GetPreciousObjectsState(unittest.TestCase):
|
||||||
"""Tests for _GetPreciousObjectsState."""
|
"""Tests for _GetPreciousObjectsState."""
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
@ -73,11 +72,84 @@ class RepoWrapperUnitTest(RepoWrapperTestCase):
|
|||||||
|
|
||||||
def test_init_parser(self):
|
def test_init_parser(self):
|
||||||
"""Make sure 'init' GetParser works."""
|
"""Make sure 'init' GetParser works."""
|
||||||
parser = self.wrapper.GetParser()
|
parser = self.wrapper.GetParser(gitc_init=False)
|
||||||
opts, args = parser.parse_args([])
|
opts, args = parser.parse_args([])
|
||||||
self.assertEqual([], args)
|
self.assertEqual([], args)
|
||||||
self.assertIsNone(opts.manifest_url)
|
self.assertIsNone(opts.manifest_url)
|
||||||
|
|
||||||
|
def test_gitc_init_parser(self):
|
||||||
|
"""Make sure 'gitc-init' GetParser raises."""
|
||||||
|
with self.assertRaises(SystemExit):
|
||||||
|
self.wrapper.GetParser(gitc_init=True)
|
||||||
|
|
||||||
|
def test_get_gitc_manifest_dir_no_gitc(self):
|
||||||
|
"""
|
||||||
|
Test reading a missing gitc config file
|
||||||
|
"""
|
||||||
|
self.wrapper.GITC_CONFIG_FILE = fixture("missing_gitc_config")
|
||||||
|
val = self.wrapper.get_gitc_manifest_dir()
|
||||||
|
self.assertEqual(val, "")
|
||||||
|
|
||||||
|
def test_get_gitc_manifest_dir(self):
|
||||||
|
"""
|
||||||
|
Test reading the gitc config file and parsing the directory
|
||||||
|
"""
|
||||||
|
self.wrapper.GITC_CONFIG_FILE = fixture("gitc_config")
|
||||||
|
val = self.wrapper.get_gitc_manifest_dir()
|
||||||
|
self.assertEqual(val, "/test/usr/local/google/gitc")
|
||||||
|
|
||||||
|
def test_gitc_parse_clientdir_no_gitc(self):
|
||||||
|
"""
|
||||||
|
Test parsing the gitc clientdir without gitc running
|
||||||
|
"""
|
||||||
|
self.wrapper.GITC_CONFIG_FILE = fixture("missing_gitc_config")
|
||||||
|
self.assertEqual(self.wrapper.gitc_parse_clientdir("/something"), None)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/test"), "test"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_gitc_parse_clientdir(self):
|
||||||
|
"""
|
||||||
|
Test parsing the gitc clientdir
|
||||||
|
"""
|
||||||
|
self.wrapper.GITC_CONFIG_FILE = fixture("gitc_config")
|
||||||
|
self.assertEqual(self.wrapper.gitc_parse_clientdir("/something"), None)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/test"), "test"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/test/"), "test"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/test/extra"),
|
||||||
|
"test",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir(
|
||||||
|
"/test/usr/local/google/gitc/test"
|
||||||
|
),
|
||||||
|
"test",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir(
|
||||||
|
"/test/usr/local/google/gitc/test/"
|
||||||
|
),
|
||||||
|
"test",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir(
|
||||||
|
"/test/usr/local/google/gitc/test/extra"
|
||||||
|
),
|
||||||
|
"test",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/"), None
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.wrapper.gitc_parse_clientdir("/test/usr/local/google/gitc/"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SetGitTrace2ParentSid(RepoWrapperTestCase):
|
class SetGitTrace2ParentSid(RepoWrapperTestCase):
|
||||||
"""Check SetGitTrace2ParentSid behavior."""
|
"""Check SetGitTrace2ParentSid behavior."""
|
||||||
@ -126,7 +198,7 @@ class RunCommand(RepoWrapperTestCase):
|
|||||||
self.wrapper.run_command(["true"], check=False)
|
self.wrapper.run_command(["true"], check=False)
|
||||||
self.wrapper.run_command(["true"], check=True)
|
self.wrapper.run_command(["true"], check=True)
|
||||||
self.wrapper.run_command(["false"], check=False)
|
self.wrapper.run_command(["false"], check=False)
|
||||||
with self.assertRaises(subprocess.CalledProcessError):
|
with self.assertRaises(self.wrapper.RunError):
|
||||||
self.wrapper.run_command(["false"], check=True)
|
self.wrapper.run_command(["false"], check=True)
|
||||||
|
|
||||||
|
|
||||||
@ -359,8 +431,8 @@ class VerifyRev(RepoWrapperTestCase):
|
|||||||
|
|
||||||
def test_verify_passes(self):
|
def test_verify_passes(self):
|
||||||
"""Check when we have a valid signed tag."""
|
"""Check when we have a valid signed tag."""
|
||||||
desc_result = subprocess.CompletedProcess([], 0, "v1.0\n", "")
|
desc_result = self.wrapper.RunResult(0, "v1.0\n", "")
|
||||||
gpg_result = subprocess.CompletedProcess([], 0, "", "")
|
gpg_result = self.wrapper.RunResult(0, "", "")
|
||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
|
self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
|
||||||
):
|
):
|
||||||
@ -371,8 +443,8 @@ class VerifyRev(RepoWrapperTestCase):
|
|||||||
|
|
||||||
def test_unsigned_commit(self):
|
def test_unsigned_commit(self):
|
||||||
"""Check we fall back to signed tag when we have an unsigned commit."""
|
"""Check we fall back to signed tag when we have an unsigned commit."""
|
||||||
desc_result = subprocess.CompletedProcess([], 0, "v1.0-10-g1234\n", "")
|
desc_result = self.wrapper.RunResult(0, "v1.0-10-g1234\n", "")
|
||||||
gpg_result = subprocess.CompletedProcess([], 0, "", "")
|
gpg_result = self.wrapper.RunResult(0, "", "")
|
||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
|
self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
|
||||||
):
|
):
|
||||||
@ -383,7 +455,7 @@ class VerifyRev(RepoWrapperTestCase):
|
|||||||
|
|
||||||
def test_verify_fails(self):
|
def test_verify_fails(self):
|
||||||
"""Check we fall back to signed tag when we have an unsigned commit."""
|
"""Check we fall back to signed tag when we have an unsigned commit."""
|
||||||
desc_result = subprocess.CompletedProcess([], 0, "v1.0-10-g1234\n", "")
|
desc_result = self.wrapper.RunResult(0, "v1.0-10-g1234\n", "")
|
||||||
gpg_result = Exception
|
gpg_result = Exception
|
||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
|
self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
|
||||||
|
7
tox.ini
7
tox.ini
@ -30,7 +30,6 @@ python =
|
|||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
-c constraints.txt
|
|
||||||
black
|
black
|
||||||
flake8
|
flake8
|
||||||
isort
|
isort
|
||||||
@ -45,19 +44,17 @@ setenv =
|
|||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
-c constraints.txt
|
|
||||||
black
|
black
|
||||||
flake8
|
flake8
|
||||||
commands =
|
commands =
|
||||||
black --check {posargs:. repo run_tests release/update-hooks release/update-manpages}
|
black --check {posargs:.}
|
||||||
flake8
|
flake8
|
||||||
|
|
||||||
[testenv:format]
|
[testenv:format]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
-c constraints.txt
|
|
||||||
black
|
black
|
||||||
flake8
|
flake8
|
||||||
commands =
|
commands =
|
||||||
black {posargs:. repo run_tests release/update-hooks release/update-manpages}
|
black {posargs:.}
|
||||||
flake8
|
flake8
|
||||||
|
@ -18,12 +18,8 @@ import importlib.util
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
def WrapperDir():
|
|
||||||
return os.path.dirname(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
def WrapperPath():
|
def WrapperPath():
|
||||||
return os.path.join(WrapperDir(), "repo")
|
return os.path.join(os.path.dirname(__file__), "repo")
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=None)
|
@functools.lru_cache(maxsize=None)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user