mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-30 20:17:08 +00:00
Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
e778e57f11 | |||
f1c5dd8a0f | |||
2058c63641 | |||
c8290ad49e | |||
9775a3d5d2 | |||
9bfdfbe117 | |||
2f0951b216 | |||
72ab852ca5 | |||
0a9265e2d6 | |||
dc1b59d2c0 | |||
71b0f312b1 | |||
369814b4a7 | |||
e37aa5f331 | |||
4a07798c82 | |||
fb527e3f52 | |||
6be76337a0 | |||
a2cd6aeae8 | |||
70d861fa29 | |||
9100f7fadd | |||
01d6c3c0c5 | |||
4c263b52e7 | |||
60fdc5cad1 | |||
46702eddc7 | |||
ae6cb08ae5 | |||
3fc157285c | |||
8a11f6f24c | |||
898f4e6217 | |||
d9e5cf0ee7 | |||
3069be2684 | |||
d5c306b404 | |||
a850ca2712 | |||
a34186e481 | |||
600f49278a | |||
1f2462e0d2 | |||
50d27639b5 | |||
c5b172ad6f | |||
87deaefd86 | |||
5fbd1c6053 | |||
1126c4ed86 | |||
f7c51606f0 | |||
745be2ede1 | |||
87fb5a1894 | |||
ab85fe7c53 | |||
4f42a97067 | |||
2b7daff8cb | |||
242fcdd93b | |||
ca540aed19 | |||
f88b2fe569 | |||
6db1b9e282 | |||
490e16385d | |||
ec558df074 | |||
81f5c59671 | |||
1b9adab75a | |||
3698ab7c92 | |||
0c0e934b69 | |||
9e71842fbf | |||
61b2d41f26 | |||
da9e200f1d | |||
c92ce5c7dc | |||
f601376e13 | |||
31067c0ac5 | |||
35159abbeb | |||
24ee29e468 | |||
1b291fc2e7 | |||
a26c49ead4 | |||
c745350ab9 | |||
025704e946 | |||
a84df06160 |
@ -1,3 +1,5 @@
|
|||||||
|
[TOC]
|
||||||
|
|
||||||
# Short Version
|
# Short Version
|
||||||
|
|
||||||
- Make small logical changes.
|
- Make small logical changes.
|
||||||
@ -52,17 +54,29 @@ Run `flake8` on changes modules:
|
|||||||
|
|
||||||
flake8 file.py
|
flake8 file.py
|
||||||
|
|
||||||
Note that repo generally follows [Google's python style guide]
|
Note that repo generally follows [Google's python style guide] rather than
|
||||||
(https://google.github.io/styleguide/pyguide.html) rather than [PEP 8]
|
[PEP 8], so it's possible that the output of `flake8` will be quite noisy.
|
||||||
(https://www.python.org/dev/peps/pep-0008/), so it's possible that
|
It's not mandatory to avoid all warnings, but at least the maximum line
|
||||||
the output of `flake8` will be quite noisy. It's not mandatory to
|
length should be followed.
|
||||||
avoid all warnings, but at least the maximum line length should be
|
|
||||||
followed.
|
|
||||||
|
|
||||||
If there are many occurrences of the same warning that cannot be
|
If there are many occurrences of the same warning that cannot be
|
||||||
avoided without going against the Google style guide, these may be
|
avoided without going against the Google style guide, these may be
|
||||||
suppressed in the included `.flake8` file.
|
suppressed in the included `.flake8` file.
|
||||||
|
|
||||||
|
[Google's python style guide]: https://google.github.io/styleguide/pyguide.html
|
||||||
|
[PEP 8]: https://www.python.org/dev/peps/pep-0008/
|
||||||
|
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
There is a [`./run_tests`](./run_tests) helper script for quickly invoking all
|
||||||
|
of our unittests. The coverage isn't great currently, but it should still be
|
||||||
|
run for all commits.
|
||||||
|
|
||||||
|
Adding more unittests for changes you make would be greatly appreciated :).
|
||||||
|
Check out the [tests/](./tests/) subdirectory for more details.
|
||||||
|
|
||||||
|
|
||||||
## Check the license
|
## Check the license
|
||||||
|
|
||||||
repo is licensed under the Apache License, 2.0.
|
repo is licensed under the Apache License, 2.0.
|
||||||
|
1
color.py
1
color.py
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
18
command.py
18
command.py
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -97,6 +98,16 @@ class Command(object):
|
|||||||
self.OptionParser.print_usage()
|
self.OptionParser.print_usage()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
def ValidateOptions(self, opt, args):
|
||||||
|
"""Validate the user options & arguments before executing.
|
||||||
|
|
||||||
|
This is meant to help break the code up into logical steps. Some tips:
|
||||||
|
* Use self.OptionParser.error to display CLI related errors.
|
||||||
|
* Adjust opt member defaults as makes sense.
|
||||||
|
* Adjust the args list, but do so inplace so the caller sees updates.
|
||||||
|
* Try to avoid updating self state. Leave that to Execute.
|
||||||
|
"""
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
"""Perform the action, after option parsing is complete.
|
"""Perform the action, after option parsing is complete.
|
||||||
"""
|
"""
|
||||||
@ -164,7 +175,10 @@ class Command(object):
|
|||||||
self._ResetPathToProjectMap(all_projects_list)
|
self._ResetPathToProjectMap(all_projects_list)
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
projects = manifest.GetProjectsWithName(arg)
|
# We have to filter by manifest groups in case the requested project is
|
||||||
|
# checked out multiple times or differently based on them.
|
||||||
|
projects = [project for project in manifest.GetProjectsWithName(arg)
|
||||||
|
if project.MatchesGroups(groups)]
|
||||||
|
|
||||||
if not projects:
|
if not projects:
|
||||||
path = os.path.abspath(arg).replace('\\', '/')
|
path = os.path.abspath(arg).replace('\\', '/')
|
||||||
@ -189,7 +203,7 @@ class Command(object):
|
|||||||
|
|
||||||
for project in projects:
|
for project in projects:
|
||||||
if not missing_ok and not project.Exists:
|
if not missing_ok and not project.Exists:
|
||||||
raise NoSuchProjectError(arg)
|
raise NoSuchProjectError('%s (%s)' % (arg, project.relpath))
|
||||||
if not project.MatchesGroups(groups):
|
if not project.MatchesGroups(groups):
|
||||||
raise InvalidProjectGroupsError(arg)
|
raise InvalidProjectGroupsError(arg)
|
||||||
|
|
||||||
|
@ -322,13 +322,29 @@ Zero or more copyfile elements may be specified as children of a
|
|||||||
project element. Each element describes a src-dest pair of files;
|
project element. Each element describes a src-dest pair of files;
|
||||||
the "src" file will be copied to the "dest" place during `repo sync`
|
the "src" file will be copied to the "dest" place during `repo sync`
|
||||||
command.
|
command.
|
||||||
|
|
||||||
"src" is project relative, "dest" is relative to the top of the tree.
|
"src" is project relative, "dest" is relative to the top of the tree.
|
||||||
|
Copying from paths outside of the project or to paths outside of the repo
|
||||||
|
client is not allowed.
|
||||||
|
|
||||||
|
"src" and "dest" must be files. Directories or symlinks are not allowed.
|
||||||
|
Intermediate paths must not be symlinks either.
|
||||||
|
|
||||||
|
Parent directories of "dest" will be automatically created if missing.
|
||||||
|
|
||||||
### Element linkfile
|
### Element linkfile
|
||||||
|
|
||||||
It's just like copyfile and runs at the same time as copyfile but
|
It's just like copyfile and runs at the same time as copyfile but
|
||||||
instead of copying it creates a symlink.
|
instead of copying it creates a symlink.
|
||||||
|
|
||||||
|
The symlink is created at "dest" (relative to the top of the tree) and
|
||||||
|
points to the path specified by "src".
|
||||||
|
|
||||||
|
Parent directories of "dest" will be automatically created if missing.
|
||||||
|
|
||||||
|
The symlink target may be a file or directory, but it may not point outside
|
||||||
|
of the repo client.
|
||||||
|
|
||||||
### Element remove-project
|
### Element remove-project
|
||||||
|
|
||||||
Deletes the named project from the internal manifest table, possibly
|
Deletes the named project from the internal manifest table, possibly
|
||||||
|
@ -28,5 +28,20 @@ The master branch will require Python 3.6 at a minimum.
|
|||||||
If the system has an older version of Python 3, then users will have to select
|
If the system has an older version of Python 3, then users will have to select
|
||||||
the legacy Python 2 branch instead.
|
the legacy Python 2 branch instead.
|
||||||
|
|
||||||
|
### repo hooks
|
||||||
|
|
||||||
|
Projects that use [repo hooks] run on independent schedules.
|
||||||
|
They might migrate to Python 3 earlier or later than us.
|
||||||
|
To support them, we'll probe the shebang of the hook script and if we find an
|
||||||
|
interpreter in there that indicates a different version than repo is currently
|
||||||
|
running under, we'll attempt to reexec ourselves under that.
|
||||||
|
|
||||||
|
For example, a hook with a header like `#!/usr/bin/python2` will have repo
|
||||||
|
execute `/usr/bin/python2` to execute the hook code specifically if repo is
|
||||||
|
currently running Python 3.
|
||||||
|
|
||||||
|
For more details, consult the [repo hooks] documentation.
|
||||||
|
|
||||||
|
|
||||||
|
[repo hooks]: ./repo-hooks.md
|
||||||
[repo launcher]: ../repo
|
[repo launcher]: ../repo
|
||||||
|
167
docs/release-process.md
Normal file
167
docs/release-process.md
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# repo release process
|
||||||
|
|
||||||
|
This is the process for creating a new release of repo, as well as all the
|
||||||
|
related topics and flows.
|
||||||
|
|
||||||
|
[TOC]
|
||||||
|
|
||||||
|
## Launcher script
|
||||||
|
|
||||||
|
The main repo script serves as a standalone program and is often referred to as
|
||||||
|
the "launcher script".
|
||||||
|
This makes it easy to copy around and install as you don't have to install any
|
||||||
|
other files from the git repo.
|
||||||
|
|
||||||
|
Whenever major changes are made to the launcher script, you should increment the
|
||||||
|
`VERSION` variable in the launcher itself.
|
||||||
|
At runtime, repo will check this to see if it needs to be updated (and notify
|
||||||
|
the user automatically).
|
||||||
|
|
||||||
|
## Key management
|
||||||
|
|
||||||
|
Every release has a git tag that is signed with a key that repo recognizes.
|
||||||
|
Those keys are hardcoded inside of the repo launcher itself -- look for the
|
||||||
|
`KEYRING_VERSION` and `MAINTAINER_KEYS` settings.
|
||||||
|
|
||||||
|
Adding new keys to the repo launcher will allow tags to be recognized by new
|
||||||
|
keys, but only people using that updated version will be able to.
|
||||||
|
Since the majority of users will be using an official launcher version, their
|
||||||
|
version will simply ignore any new signed tags.
|
||||||
|
|
||||||
|
If you want to add new keys, it's best to register them long ahead of time,
|
||||||
|
and then wait for that updated launcher to make its way out to everyone.
|
||||||
|
Even then, there will be a long tail of users with outdated launchers, so be
|
||||||
|
prepared for people asking questions.
|
||||||
|
|
||||||
|
### Registering a new key
|
||||||
|
|
||||||
|
The process of actually adding a new key is quite simple.
|
||||||
|
|
||||||
|
1. Add the public half of the key to `MAINTAINER_KEYS`.
|
||||||
|
2. Increment `KEYRING_VERSION` so repo knows it needs to update.
|
||||||
|
3. Wait a long time after that version is in a release (~months) before trying
|
||||||
|
to create a new release using those new keys.
|
||||||
|
|
||||||
|
## Self update algorithm
|
||||||
|
|
||||||
|
When creating a new repo checkout with `repo init`, there are a few options that
|
||||||
|
control how repo finds updates:
|
||||||
|
|
||||||
|
* `--repo-url`: This tells repo where to clone the full repo project itself.
|
||||||
|
It defaults to the official project (`REPO_URL` in the launcher script).
|
||||||
|
* `--repo-branch`: This tells repo which branch to use for the full project.
|
||||||
|
It defaults to the `stable` branch (`REPO_REV` in the launcher script).
|
||||||
|
|
||||||
|
Whenever `repo sync` is run, repo will check to see if an update is available.
|
||||||
|
It fetches the latest repo-branch from the repo-url.
|
||||||
|
Then it verifies that the latest commit in the branch has a valid signed tag
|
||||||
|
using `git tag -v` (which uses gpg).
|
||||||
|
If the tag is valid, then repo will update its internal checkout to it.
|
||||||
|
|
||||||
|
If the latest commit doesn't have a signed tag, repo will fall back to the
|
||||||
|
most recent tag it can find (via `git describe`).
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Branch management
|
||||||
|
|
||||||
|
All development happens on the `master` branch and should generally be stable.
|
||||||
|
|
||||||
|
Since the repo launcher defaults to tracking the `stable` branch, it is not
|
||||||
|
normally updated until a new release is available.
|
||||||
|
If something goes wrong with a new release, an older release can be force pushed
|
||||||
|
and clients will automatically downgrade.
|
||||||
|
|
||||||
|
The `maint` branch is used to track the previous major release of repo.
|
||||||
|
It is not normally meant to be used by people as `stable` should be good enough.
|
||||||
|
Once a new major release is pushed to the `stable` branch, then the previous
|
||||||
|
major release can be pushed to `maint`.
|
||||||
|
For example, when `stable` moves from `v1.10.x` to `v1.11.x`, then the `maint`
|
||||||
|
branch will be updated from `v1.9.x` to `v1.10.x`.
|
||||||
|
|
||||||
|
We don't have parallel release branches/series.
|
||||||
|
Typically all tags are made against the `master` branch and then pushed to the
|
||||||
|
`stable` branch to make it available to the rest of the world.
|
||||||
|
Since repo doesn't typically see a lot of changes, this tends to be OK.
|
||||||
|
|
||||||
|
## Creating a new release
|
||||||
|
|
||||||
|
When you want to create a new release, you'll need to select a good version and
|
||||||
|
create a signed tag using a key registered in repo itself.
|
||||||
|
Typically we just tag the latest version of the `master` branch.
|
||||||
|
The tag could be pushed now, but it won't be used by clients normally (since the
|
||||||
|
default `repo-branch` setting is `stable`).
|
||||||
|
This would allow some early testing on systems who explicitly select `master`.
|
||||||
|
|
||||||
|
### Creating a signed tag
|
||||||
|
|
||||||
|
Lets assume your keys live in a dedicated directory, e.g. `~/.gnupg/repo/`.
|
||||||
|
|
||||||
|
*** note
|
||||||
|
If you need access to the official keys, check out the internal documentation
|
||||||
|
at [go/repo-release].
|
||||||
|
Note that only official maintainers of repo will have access as it describes
|
||||||
|
internal processes for accessing the restricted keys.
|
||||||
|
***
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Set the gpg key directory.
|
||||||
|
$ export GNUPGHOME=~/.gnupg/repo/
|
||||||
|
|
||||||
|
# Verify the listed key is “Repo Maintainer”.
|
||||||
|
$ gpg -K
|
||||||
|
|
||||||
|
# Pick whatever branch or commit you want to tag.
|
||||||
|
$ r=master
|
||||||
|
|
||||||
|
# Pick the new version.
|
||||||
|
$ t=1.12.10
|
||||||
|
|
||||||
|
# Create the signed tag.
|
||||||
|
$ git tag -s v$t -u "Repo Maintainer <repo@android.kernel.org>" -m "repo $t" $r
|
||||||
|
|
||||||
|
# Verify the signed tag.
|
||||||
|
$ git show v$t
|
||||||
|
```
|
||||||
|
|
||||||
|
### Push the new release
|
||||||
|
|
||||||
|
Once you're ready to make the release available to everyone, push it to the
|
||||||
|
`stable` branch.
|
||||||
|
|
||||||
|
Make sure you never push the tag itself to the stable branch!
|
||||||
|
Only push the commit -- notice the use of `$t` and `$r` below.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ git push https://gerrit-review.googlesource.com/git-repo v$t
|
||||||
|
$ git push https://gerrit-review.googlesource.com/git-repo $r:stable
|
||||||
|
```
|
||||||
|
|
||||||
|
If something goes horribly wrong, you can force push the previous version to the
|
||||||
|
`stable` branch and people should automatically recover.
|
||||||
|
Again, make sure you never push the tag itself!
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ oldrev="whatever-old-commit"
|
||||||
|
$ git push https://gerrit-review.googlesource.com/git-repo $oldrev:stable --force
|
||||||
|
```
|
||||||
|
|
||||||
|
### Announce the release
|
||||||
|
|
||||||
|
Once you do push a new release to `stable`, make sure to announce it on the
|
||||||
|
[repo-discuss@googlegroups.com] group.
|
||||||
|
Here is an [example announcement].
|
||||||
|
|
||||||
|
You can create a short changelog using the command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# If you haven't pushed to the stable branch yet, you can use origin/stable.
|
||||||
|
# If you have pushed, change origin/stable to the previous release tag.
|
||||||
|
$ git log --format="%h (%aN) %s" --no-merges origin/stable..$r
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[example announcement]: https://groups.google.com/d/topic/repo-discuss/UGBNismWo1M/discussion
|
||||||
|
[repo-discuss@googlegroups.com]: https://groups.google.com/forum/#!forum/repo-discuss
|
||||||
|
[go/repo-release]: https://goto.google.com/repo-release
|
@ -83,6 +83,31 @@ then check it directly. Hooks should not normally modify the active git repo
|
|||||||
the user. Although user interaction is discouraged in the common case, it can
|
the user. Although user interaction is discouraged in the common case, it can
|
||||||
be useful when deploying automatic fixes.
|
be useful when deploying automatic fixes.
|
||||||
|
|
||||||
|
### Shebang Handling
|
||||||
|
|
||||||
|
*** note
|
||||||
|
This is intended as a transitional feature. Hooks are expected to eventually
|
||||||
|
migrate to Python 3 only as Python 2 is EOL & deprecated.
|
||||||
|
***
|
||||||
|
|
||||||
|
If the hook is written against a specific version of Python (either 2 or 3),
|
||||||
|
the script can declare that explicitly. Repo will then attempt to execute it
|
||||||
|
under the right version of Python regardless of the version repo itself might
|
||||||
|
be executing under.
|
||||||
|
|
||||||
|
Here are the shebangs that are recognized.
|
||||||
|
|
||||||
|
* `#!/usr/bin/env python` & `#!/usr/bin/python`: The hook is compatible with
|
||||||
|
Python 2 & Python 3. For maximum compatibility, these are recommended.
|
||||||
|
* `#!/usr/bin/env python2` & `#!/usr/bin/python2`: The hook requires Python 2.
|
||||||
|
Version specific names like `python2.7` are also recognized.
|
||||||
|
* `#!/usr/bin/env python3` & `#!/usr/bin/python3`: The hook requires Python 3.
|
||||||
|
Version specific names like `python3.6` are also recognized.
|
||||||
|
|
||||||
|
If no shebang is detected, or does not match the forms above, we assume that the
|
||||||
|
hook is compatible with both Python 2 & Python 3 as if `#!/usr/bin/python` was
|
||||||
|
used.
|
||||||
|
|
||||||
## Hooks
|
## Hooks
|
||||||
|
|
||||||
Here are all the points available for hooking.
|
Here are all the points available for hooking.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
1
error.py
1
error.py
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 The Android Open Source Project
|
# Copyright (C) 2017 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
134
git_command.py
134
git_command.py
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -21,8 +22,9 @@ import tempfile
|
|||||||
from signal import SIGTERM
|
from signal import SIGTERM
|
||||||
|
|
||||||
from error import GitError
|
from error import GitError
|
||||||
|
from git_refs import HEAD
|
||||||
import platform_utils
|
import platform_utils
|
||||||
from trace import REPO_TRACE, IsTrace, Trace
|
from repo_trace import REPO_TRACE, IsTrace, Trace
|
||||||
from wrapper import Wrapper
|
from wrapper import Wrapper
|
||||||
|
|
||||||
GIT = 'git'
|
GIT = 'git'
|
||||||
@ -79,22 +81,12 @@ def terminate_ssh_clients():
|
|||||||
_git_version = None
|
_git_version = None
|
||||||
|
|
||||||
class _GitCall(object):
|
class _GitCall(object):
|
||||||
def version(self):
|
|
||||||
p = GitCommand(None, ['--version'], capture_stdout=True)
|
|
||||||
if p.Wait() == 0:
|
|
||||||
if hasattr(p.stdout, 'decode'):
|
|
||||||
return p.stdout.decode('utf-8')
|
|
||||||
else:
|
|
||||||
return p.stdout
|
|
||||||
return None
|
|
||||||
|
|
||||||
def version_tuple(self):
|
def version_tuple(self):
|
||||||
global _git_version
|
global _git_version
|
||||||
if _git_version is None:
|
if _git_version is None:
|
||||||
ver_str = git.version()
|
_git_version = Wrapper().ParseGitVersion()
|
||||||
_git_version = Wrapper().ParseGitVersion(ver_str)
|
|
||||||
if _git_version is None:
|
if _git_version is None:
|
||||||
print('fatal: "%s" unsupported' % ver_str, file=sys.stderr)
|
print('fatal: unable to detect git version', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return _git_version
|
return _git_version
|
||||||
|
|
||||||
@ -107,13 +99,95 @@ class _GitCall(object):
|
|||||||
return fun
|
return fun
|
||||||
git = _GitCall()
|
git = _GitCall()
|
||||||
|
|
||||||
def git_require(min_version, fail=False):
|
|
||||||
|
def RepoSourceVersion():
|
||||||
|
"""Return the version of the repo.git tree."""
|
||||||
|
ver = getattr(RepoSourceVersion, 'version', None)
|
||||||
|
|
||||||
|
# We avoid GitCommand so we don't run into circular deps -- GitCommand needs
|
||||||
|
# to initialize version info we provide.
|
||||||
|
if ver is None:
|
||||||
|
env = GitCommand._GetBasicEnv()
|
||||||
|
|
||||||
|
proj = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
env[GIT_DIR] = os.path.join(proj, '.git')
|
||||||
|
|
||||||
|
p = subprocess.Popen([GIT, 'describe', HEAD], stdout=subprocess.PIPE,
|
||||||
|
env=env)
|
||||||
|
if p.wait() == 0:
|
||||||
|
ver = p.stdout.read().strip().decode('utf-8')
|
||||||
|
if ver.startswith('v'):
|
||||||
|
ver = ver[1:]
|
||||||
|
else:
|
||||||
|
ver = 'unknown'
|
||||||
|
setattr(RepoSourceVersion, 'version', ver)
|
||||||
|
|
||||||
|
return ver
|
||||||
|
|
||||||
|
|
||||||
|
class UserAgent(object):
|
||||||
|
"""Mange User-Agent settings when talking to external services
|
||||||
|
|
||||||
|
We follow the style as documented here:
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
|
||||||
|
"""
|
||||||
|
|
||||||
|
_os = None
|
||||||
|
_repo_ua = None
|
||||||
|
_git_ua = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def os(self):
|
||||||
|
"""The operating system name."""
|
||||||
|
if self._os is None:
|
||||||
|
os_name = sys.platform
|
||||||
|
if os_name.lower().startswith('linux'):
|
||||||
|
os_name = 'Linux'
|
||||||
|
elif os_name == 'win32':
|
||||||
|
os_name = 'Win32'
|
||||||
|
elif os_name == 'cygwin':
|
||||||
|
os_name = 'Cygwin'
|
||||||
|
elif os_name == 'darwin':
|
||||||
|
os_name = 'Darwin'
|
||||||
|
self._os = os_name
|
||||||
|
|
||||||
|
return self._os
|
||||||
|
|
||||||
|
@property
|
||||||
|
def repo(self):
|
||||||
|
"""The UA when connecting directly from repo."""
|
||||||
|
if self._repo_ua is None:
|
||||||
|
py_version = sys.version_info
|
||||||
|
self._repo_ua = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
|
||||||
|
RepoSourceVersion(),
|
||||||
|
self.os,
|
||||||
|
git.version_tuple().full,
|
||||||
|
py_version.major, py_version.minor, py_version.micro)
|
||||||
|
|
||||||
|
return self._repo_ua
|
||||||
|
|
||||||
|
@property
|
||||||
|
def git(self):
|
||||||
|
"""The UA when running git."""
|
||||||
|
if self._git_ua is None:
|
||||||
|
self._git_ua = 'git/%s (%s) git-repo/%s' % (
|
||||||
|
git.version_tuple().full,
|
||||||
|
self.os,
|
||||||
|
RepoSourceVersion())
|
||||||
|
|
||||||
|
return self._git_ua
|
||||||
|
|
||||||
|
user_agent = UserAgent()
|
||||||
|
|
||||||
|
def git_require(min_version, fail=False, msg=''):
|
||||||
git_version = git.version_tuple()
|
git_version = git.version_tuple()
|
||||||
if min_version <= git_version:
|
if min_version <= git_version:
|
||||||
return True
|
return True
|
||||||
if fail:
|
if fail:
|
||||||
need = '.'.join(map(str, min_version))
|
need = '.'.join(map(str, min_version))
|
||||||
print('fatal: git %s or later required' % need, file=sys.stderr)
|
if msg:
|
||||||
|
msg = ' for ' + msg
|
||||||
|
print('fatal: git %s or later required%s' % (need, msg), file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -132,17 +206,7 @@ class GitCommand(object):
|
|||||||
ssh_proxy = False,
|
ssh_proxy = False,
|
||||||
cwd = None,
|
cwd = None,
|
||||||
gitdir = None):
|
gitdir = None):
|
||||||
env = os.environ.copy()
|
env = self._GetBasicEnv()
|
||||||
|
|
||||||
for key in [REPO_TRACE,
|
|
||||||
GIT_DIR,
|
|
||||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
|
||||||
'GIT_OBJECT_DIRECTORY',
|
|
||||||
'GIT_WORK_TREE',
|
|
||||||
'GIT_GRAFT_FILE',
|
|
||||||
'GIT_INDEX_FILE']:
|
|
||||||
if key in env:
|
|
||||||
del env[key]
|
|
||||||
|
|
||||||
# If we are not capturing std* then need to print it.
|
# If we are not capturing std* then need to print it.
|
||||||
self.tee = {'stdout': not capture_stdout, 'stderr': not capture_stderr}
|
self.tee = {'stdout': not capture_stdout, 'stderr': not capture_stderr}
|
||||||
@ -162,6 +226,7 @@ class GitCommand(object):
|
|||||||
if 'GIT_ALLOW_PROTOCOL' not in env:
|
if 'GIT_ALLOW_PROTOCOL' not in env:
|
||||||
_setenv(env, 'GIT_ALLOW_PROTOCOL',
|
_setenv(env, 'GIT_ALLOW_PROTOCOL',
|
||||||
'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc')
|
'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc')
|
||||||
|
_setenv(env, 'GIT_HTTP_USER_AGENT', user_agent.git)
|
||||||
|
|
||||||
if project:
|
if project:
|
||||||
if not cwd:
|
if not cwd:
|
||||||
@ -234,6 +299,23 @@ class GitCommand(object):
|
|||||||
self.process = p
|
self.process = p
|
||||||
self.stdin = p.stdin
|
self.stdin = p.stdin
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _GetBasicEnv():
|
||||||
|
"""Return a basic env for running git under.
|
||||||
|
|
||||||
|
This is guaranteed to be side-effect free.
|
||||||
|
"""
|
||||||
|
env = os.environ.copy()
|
||||||
|
for key in (REPO_TRACE,
|
||||||
|
GIT_DIR,
|
||||||
|
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
||||||
|
'GIT_OBJECT_DIRECTORY',
|
||||||
|
'GIT_WORK_TREE',
|
||||||
|
'GIT_GRAFT_FILE',
|
||||||
|
'GIT_INDEX_FILE'):
|
||||||
|
env.pop(key, None)
|
||||||
|
return env
|
||||||
|
|
||||||
def Wait(self):
|
def Wait(self):
|
||||||
try:
|
try:
|
||||||
p = self.process
|
p = self.process
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -43,7 +44,7 @@ else:
|
|||||||
from signal import SIGTERM
|
from signal import SIGTERM
|
||||||
from error import GitError, UploadError
|
from error import GitError, UploadError
|
||||||
import platform_utils
|
import platform_utils
|
||||||
from trace import Trace
|
from repo_trace import Trace
|
||||||
if is_python3():
|
if is_python3():
|
||||||
from http.client import HTTPException
|
from http.client import HTTPException
|
||||||
else:
|
else:
|
||||||
@ -656,13 +657,14 @@ class Remote(object):
|
|||||||
info = urllib.request.urlopen(info_url, context=context).read()
|
info = urllib.request.urlopen(info_url, context=context).read()
|
||||||
else:
|
else:
|
||||||
info = urllib.request.urlopen(info_url).read()
|
info = urllib.request.urlopen(info_url).read()
|
||||||
if info == 'NOT_AVAILABLE' or '<' in info:
|
if info == b'NOT_AVAILABLE' or b'<' in info:
|
||||||
# If `info` contains '<', we assume the server gave us some sort
|
# If `info` contains '<', we assume the server gave us some sort
|
||||||
# of HTML response back, like maybe a login page.
|
# of HTML response back, like maybe a login page.
|
||||||
#
|
#
|
||||||
# Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
|
# Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
|
||||||
self._review_url = http_url
|
self._review_url = http_url
|
||||||
else:
|
else:
|
||||||
|
info = info.decode('utf-8')
|
||||||
host, port = info.split()
|
host, port = info.split()
|
||||||
self._review_url = self._SshReviewUrl(userEmail, host, port)
|
self._review_url = self._SshReviewUrl(userEmail, host, port)
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
@ -697,7 +699,8 @@ class Remote(object):
|
|||||||
if not rev.startswith(R_HEADS):
|
if not rev.startswith(R_HEADS):
|
||||||
return rev
|
return rev
|
||||||
|
|
||||||
raise GitError('remote %s does not have %s' % (self.name, rev))
|
raise GitError('%s: remote %s does not have %s' %
|
||||||
|
(self.projectname, self.name, rev))
|
||||||
|
|
||||||
def WritesTo(self, ref):
|
def WritesTo(self, ref):
|
||||||
"""True if the remote stores to the tracking ref.
|
"""True if the remote stores to the tracking ref.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 The Android Open Source Project
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -14,7 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from trace import Trace
|
from repo_trace import Trace
|
||||||
import platform_utils
|
import platform_utils
|
||||||
|
|
||||||
HEAD = 'HEAD'
|
HEAD = 'HEAD'
|
||||||
|
15
git_ssh
15
git_ssh
@ -1,2 +1,17 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (C) 2009 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.
|
||||||
|
|
||||||
exec ssh -o "ControlMaster no" -o "ControlPath $REPO_SSH_SOCK" "$@"
|
exec ssh -o "ControlMaster no" -o "ControlPath $REPO_SSH_SOCK" "$@"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2015 The Android Open Source Project
|
# Copyright (C) 2015 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -58,8 +59,8 @@ def _set_project_revisions(projects):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
revisionExpr = gitcmd.stdout.split('\t')[0]
|
revisionExpr = gitcmd.stdout.split('\t')[0]
|
||||||
if not revisionExpr:
|
if not revisionExpr:
|
||||||
raise(ManifestParseError('Invalid SHA-1 revision project %s (%s)' %
|
raise ManifestParseError('Invalid SHA-1 revision project %s (%s)' %
|
||||||
(proj.remote.url, proj.revisionExpr)))
|
(proj.remote.url, proj.revisionExpr))
|
||||||
proj.revisionExpr = revisionExpr
|
proj.revisionExpr = revisionExpr
|
||||||
|
|
||||||
def _manifest_groups(manifest):
|
def _manifest_groups(manifest):
|
||||||
@ -87,7 +88,7 @@ def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
|
|||||||
print('Generating GITC Manifest by fetching revision SHAs for each '
|
print('Generating GITC Manifest by fetching revision SHAs for each '
|
||||||
'project.')
|
'project.')
|
||||||
if paths is None:
|
if paths is None:
|
||||||
paths = manifest.paths.keys()
|
paths = list(manifest.paths.keys())
|
||||||
|
|
||||||
groups = [x for x in re.split(r'[,\s]+', _manifest_groups(manifest)) if x]
|
groups = [x for x in re.split(r'[,\s]+', _manifest_groups(manifest)) if x]
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
|
|||||||
projects = [p for p in projects if p.MatchesGroups(groups)]
|
projects = [p for p in projects if p.MatchesGroups(groups)]
|
||||||
|
|
||||||
if gitc_manifest is not None:
|
if gitc_manifest is not None:
|
||||||
for path, proj in manifest.paths.iteritems():
|
for path, proj in manifest.paths.items():
|
||||||
if not proj.MatchesGroups(groups):
|
if not proj.MatchesGroups(groups):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -124,7 +125,7 @@ def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
|
|||||||
index += NUM_BATCH_RETRIEVE_REVISIONID
|
index += NUM_BATCH_RETRIEVE_REVISIONID
|
||||||
|
|
||||||
if gitc_manifest is not None:
|
if gitc_manifest is not None:
|
||||||
for path, proj in gitc_manifest.paths.iteritems():
|
for path, proj in gitc_manifest.paths.items():
|
||||||
if proj.old_revision and path in paths:
|
if proj.old_revision and path in paths:
|
||||||
# If we updated a project that has been started, keep the old-revision
|
# If we updated a project that has been started, keep the old-revision
|
||||||
# updated.
|
# updated.
|
||||||
@ -133,7 +134,7 @@ def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
|
|||||||
repo_proj.revisionExpr = None
|
repo_proj.revisionExpr = None
|
||||||
|
|
||||||
# Convert URLs from relative to absolute.
|
# Convert URLs from relative to absolute.
|
||||||
for _name, remote in manifest.remotes.iteritems():
|
for _name, remote in manifest.remotes.items():
|
||||||
remote.fetchUrl = remote.resolvedFetchUrl
|
remote.fetchUrl = remote.resolvedFetchUrl
|
||||||
|
|
||||||
# Save the manifest.
|
# Save the manifest.
|
||||||
|
85
main.py
85
main.py
@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -14,9 +15,14 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
"""The repo tool.
|
||||||
|
|
||||||
|
People shouldn't run this directly; instead, they should use the `repo` wrapper
|
||||||
|
which takes care of execing this entry point.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import getpass
|
import getpass
|
||||||
import imp
|
|
||||||
import netrc
|
import netrc
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
@ -27,6 +33,7 @@ from pyversion import is_python3
|
|||||||
if is_python3():
|
if is_python3():
|
||||||
import urllib.request
|
import urllib.request
|
||||||
else:
|
else:
|
||||||
|
import imp
|
||||||
import urllib2
|
import urllib2
|
||||||
urllib = imp.new_module('urllib')
|
urllib = imp.new_module('urllib')
|
||||||
urllib.request = urllib2
|
urllib.request = urllib2
|
||||||
@ -38,8 +45,8 @@ except ImportError:
|
|||||||
|
|
||||||
from color import SetDefaultColoring
|
from color import SetDefaultColoring
|
||||||
import event_log
|
import event_log
|
||||||
from trace import SetTrace
|
from repo_trace import SetTrace
|
||||||
from git_command import git, GitCommand
|
from git_command import git, GitCommand, user_agent
|
||||||
from git_config import init_ssh, close_ssh
|
from git_config import init_ssh, close_ssh
|
||||||
from command import InteractiveCommand
|
from command import InteractiveCommand
|
||||||
from command import MirrorSafeCommand
|
from command import MirrorSafeCommand
|
||||||
@ -77,7 +84,10 @@ global_options.add_option('--color',
|
|||||||
help='control color usage: auto, always, never')
|
help='control color usage: auto, always, never')
|
||||||
global_options.add_option('--trace',
|
global_options.add_option('--trace',
|
||||||
dest='trace', action='store_true',
|
dest='trace', action='store_true',
|
||||||
help='trace git command execution')
|
help='trace git command execution (REPO_TRACE=1)')
|
||||||
|
global_options.add_option('--trace-python',
|
||||||
|
dest='trace_python', action='store_true',
|
||||||
|
help='trace python command execution')
|
||||||
global_options.add_option('--time',
|
global_options.add_option('--time',
|
||||||
dest='time', action='store_true',
|
dest='time', action='store_true',
|
||||||
help='time repo command execution')
|
help='time repo command execution')
|
||||||
@ -95,8 +105,8 @@ class _Repo(object):
|
|||||||
# add 'branch' as an alias for 'branches'
|
# add 'branch' as an alias for 'branches'
|
||||||
all_commands['branch'] = all_commands['branches']
|
all_commands['branch'] = all_commands['branches']
|
||||||
|
|
||||||
def _Run(self, argv):
|
def _ParseArgs(self, argv):
|
||||||
result = 0
|
"""Parse the main `repo` command line options."""
|
||||||
name = None
|
name = None
|
||||||
glob = []
|
glob = []
|
||||||
|
|
||||||
@ -113,6 +123,12 @@ class _Repo(object):
|
|||||||
argv = []
|
argv = []
|
||||||
gopts, _gargs = global_options.parse_args(glob)
|
gopts, _gargs = global_options.parse_args(glob)
|
||||||
|
|
||||||
|
return (name, gopts, argv)
|
||||||
|
|
||||||
|
def _Run(self, name, gopts, argv):
|
||||||
|
"""Execute the requested subcommand."""
|
||||||
|
result = 0
|
||||||
|
|
||||||
if gopts.trace:
|
if gopts.trace:
|
||||||
SetTrace()
|
SetTrace()
|
||||||
if gopts.show_version:
|
if gopts.show_version:
|
||||||
@ -181,6 +197,7 @@ class _Repo(object):
|
|||||||
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)
|
||||||
try:
|
try:
|
||||||
|
cmd.ValidateOptions(copts, cargs)
|
||||||
result = cmd.Execute(copts, cargs)
|
result = cmd.Execute(copts, cargs)
|
||||||
except (DownloadError, ManifestInvalidRevisionError,
|
except (DownloadError, ManifestInvalidRevisionError,
|
||||||
NoManifestException) as e:
|
NoManifestException) as e:
|
||||||
@ -227,10 +244,6 @@ class _Repo(object):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _MyRepoPath():
|
|
||||||
return os.path.dirname(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
def _CheckWrapperVersion(ver, repo_path):
|
def _CheckWrapperVersion(ver, repo_path):
|
||||||
if not repo_path:
|
if not repo_path:
|
||||||
repo_path = '~/bin/repo'
|
repo_path = '~/bin/repo'
|
||||||
@ -282,51 +295,13 @@ def _PruneOptions(argv, opt):
|
|||||||
continue
|
continue
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
_user_agent = None
|
|
||||||
|
|
||||||
def _UserAgent():
|
|
||||||
global _user_agent
|
|
||||||
|
|
||||||
if _user_agent is None:
|
|
||||||
py_version = sys.version_info
|
|
||||||
|
|
||||||
os_name = sys.platform
|
|
||||||
if os_name == 'linux2':
|
|
||||||
os_name = 'Linux'
|
|
||||||
elif os_name == 'win32':
|
|
||||||
os_name = 'Win32'
|
|
||||||
elif os_name == 'cygwin':
|
|
||||||
os_name = 'Cygwin'
|
|
||||||
elif os_name == 'darwin':
|
|
||||||
os_name = 'Darwin'
|
|
||||||
|
|
||||||
p = GitCommand(
|
|
||||||
None, ['describe', 'HEAD'],
|
|
||||||
cwd = _MyRepoPath(),
|
|
||||||
capture_stdout = True)
|
|
||||||
if p.Wait() == 0:
|
|
||||||
repo_version = p.stdout
|
|
||||||
if len(repo_version) > 0 and repo_version[-1] == '\n':
|
|
||||||
repo_version = repo_version[0:-1]
|
|
||||||
if len(repo_version) > 0 and repo_version[0] == 'v':
|
|
||||||
repo_version = repo_version[1:]
|
|
||||||
else:
|
|
||||||
repo_version = 'unknown'
|
|
||||||
|
|
||||||
_user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
|
|
||||||
repo_version,
|
|
||||||
os_name,
|
|
||||||
'.'.join(map(str, git.version_tuple())),
|
|
||||||
py_version[0], py_version[1], py_version[2])
|
|
||||||
return _user_agent
|
|
||||||
|
|
||||||
class _UserAgentHandler(urllib.request.BaseHandler):
|
class _UserAgentHandler(urllib.request.BaseHandler):
|
||||||
def http_request(self, req):
|
def http_request(self, req):
|
||||||
req.add_header('User-Agent', _UserAgent())
|
req.add_header('User-Agent', user_agent.repo)
|
||||||
return req
|
return req
|
||||||
|
|
||||||
def https_request(self, req):
|
def https_request(self, req):
|
||||||
req.add_header('User-Agent', _UserAgent())
|
req.add_header('User-Agent', user_agent.repo)
|
||||||
return req
|
return req
|
||||||
|
|
||||||
def _AddPasswordFromUserInput(handler, msg, req):
|
def _AddPasswordFromUserInput(handler, msg, req):
|
||||||
@ -519,7 +494,15 @@ def _Main(argv):
|
|||||||
try:
|
try:
|
||||||
init_ssh()
|
init_ssh()
|
||||||
init_http()
|
init_http()
|
||||||
result = repo._Run(argv) or 0
|
name, gopts, argv = repo._ParseArgs(argv)
|
||||||
|
run = lambda: repo._Run(name, gopts, argv) or 0
|
||||||
|
if gopts.trace_python:
|
||||||
|
import trace
|
||||||
|
tracer = trace.Trace(count=False, trace=True, timing=True,
|
||||||
|
ignoredirs=set(sys.path[1:]))
|
||||||
|
result = tracer.runfunc(run)
|
||||||
|
else:
|
||||||
|
result = run()
|
||||||
finally:
|
finally:
|
||||||
close_ssh()
|
close_ssh()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -135,6 +136,7 @@ class XmlManifest(object):
|
|||||||
self.globalConfig = GitConfig.ForUser()
|
self.globalConfig = GitConfig.ForUser()
|
||||||
self.localManifestWarning = False
|
self.localManifestWarning = False
|
||||||
self.isGitcClient = False
|
self.isGitcClient = False
|
||||||
|
self._load_local_manifests = True
|
||||||
|
|
||||||
self.repoProject = MetaProject(self, 'repo',
|
self.repoProject = MetaProject(self, 'repo',
|
||||||
gitdir = os.path.join(repodir, 'repo/.git'),
|
gitdir = os.path.join(repodir, 'repo/.git'),
|
||||||
@ -146,15 +148,26 @@ class XmlManifest(object):
|
|||||||
|
|
||||||
self._Unload()
|
self._Unload()
|
||||||
|
|
||||||
def Override(self, name):
|
def Override(self, name, load_local_manifests=True):
|
||||||
"""Use a different manifest, just for the current instantiation.
|
"""Use a different manifest, just for the current instantiation.
|
||||||
"""
|
"""
|
||||||
|
path = None
|
||||||
|
|
||||||
|
# Look for a manifest by path in the filesystem (including the cwd).
|
||||||
|
if not load_local_manifests:
|
||||||
|
local_path = os.path.abspath(name)
|
||||||
|
if os.path.isfile(local_path):
|
||||||
|
path = local_path
|
||||||
|
|
||||||
|
# Look for manifests by name from the manifests repo.
|
||||||
|
if path is None:
|
||||||
path = os.path.join(self.manifestProject.worktree, name)
|
path = os.path.join(self.manifestProject.worktree, name)
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
raise ManifestParseError('manifest %s not found' % name)
|
raise ManifestParseError('manifest %s not found' % name)
|
||||||
|
|
||||||
old = self.manifestFile
|
old = self.manifestFile
|
||||||
try:
|
try:
|
||||||
|
self._load_local_manifests = load_local_manifests
|
||||||
self.manifestFile = path
|
self.manifestFile = path
|
||||||
self._Unload()
|
self._Unload()
|
||||||
self._Load()
|
self._Load()
|
||||||
@ -400,6 +413,12 @@ class XmlManifest(object):
|
|||||||
self._Load()
|
self._Load()
|
||||||
return self._manifest_server
|
return self._manifest_server
|
||||||
|
|
||||||
|
@property
|
||||||
|
def CloneFilter(self):
|
||||||
|
if self.manifestProject.config.GetBoolean('repo.partialclone'):
|
||||||
|
return self.manifestProject.config.GetString('repo.clonefilter')
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def IsMirror(self):
|
def IsMirror(self):
|
||||||
return self.manifestProject.config.GetBoolean('repo.mirror')
|
return self.manifestProject.config.GetBoolean('repo.mirror')
|
||||||
@ -435,16 +454,19 @@ class XmlManifest(object):
|
|||||||
nodes.append(self._ParseManifestXml(self.manifestFile,
|
nodes.append(self._ParseManifestXml(self.manifestFile,
|
||||||
self.manifestProject.worktree))
|
self.manifestProject.worktree))
|
||||||
|
|
||||||
|
if self._load_local_manifests:
|
||||||
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
|
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
|
||||||
if os.path.exists(local):
|
if os.path.exists(local):
|
||||||
if not self.localManifestWarning:
|
if not self.localManifestWarning:
|
||||||
self.localManifestWarning = True
|
self.localManifestWarning = True
|
||||||
print('warning: %s is deprecated; put local manifests in `%s` instead'
|
print('warning: %s is deprecated; put local manifests '
|
||||||
% (LOCAL_MANIFEST_NAME, os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)),
|
'in `%s` instead' % (LOCAL_MANIFEST_NAME,
|
||||||
|
os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)),
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
nodes.append(self._ParseManifestXml(local, self.repodir))
|
nodes.append(self._ParseManifestXml(local, self.repodir))
|
||||||
|
|
||||||
local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
|
local_dir = os.path.abspath(os.path.join(self.repodir,
|
||||||
|
LOCAL_MANIFESTS_DIR_NAME))
|
||||||
try:
|
try:
|
||||||
for local_file in sorted(platform_utils.listdir(local_dir)):
|
for local_file in sorted(platform_utils.listdir(local_dir)):
|
||||||
if local_file.endswith('.xml'):
|
if local_file.endswith('.xml'):
|
||||||
@ -498,7 +520,7 @@ class XmlManifest(object):
|
|||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ManifestParseError(
|
raise ManifestParseError(
|
||||||
"failed parsing included manifest %s: %s", (name, e))
|
"failed parsing included manifest %s: %s" % (name, e))
|
||||||
else:
|
else:
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
return nodes
|
return nodes
|
||||||
|
1
pager.py
1
pager.py
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016 The Android Open Source Project
|
# Copyright (C) 2016 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016 The Android Open Source Project
|
# Copyright (C) 2016 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -17,7 +18,7 @@ import errno
|
|||||||
|
|
||||||
from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
|
from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
|
||||||
from ctypes import c_buffer
|
from ctypes import c_buffer
|
||||||
from ctypes.wintypes import BOOL, LPCWSTR, DWORD, HANDLE, POINTER, c_ubyte
|
from ctypes.wintypes import BOOL, BOOLEAN, LPCWSTR, DWORD, HANDLE, POINTER, c_ubyte
|
||||||
from ctypes.wintypes import WCHAR, USHORT, LPVOID, Structure, Union, ULONG
|
from ctypes.wintypes import WCHAR, USHORT, LPVOID, Structure, Union, ULONG
|
||||||
from ctypes.wintypes import byref
|
from ctypes.wintypes import byref
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ ERROR_PRIVILEGE_NOT_HELD = 1314
|
|||||||
|
|
||||||
# Win32 API entry points
|
# Win32 API entry points
|
||||||
CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
|
CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
|
||||||
CreateSymbolicLinkW.restype = BOOL
|
CreateSymbolicLinkW.restype = BOOLEAN
|
||||||
CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
|
CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
|
||||||
LPCWSTR, # lpTargetFileName In
|
LPCWSTR, # lpTargetFileName In
|
||||||
DWORD) # dwFlags In
|
DWORD) # dwFlags In
|
||||||
@ -145,19 +146,12 @@ def create_dirsymlink(source, link_name):
|
|||||||
|
|
||||||
|
|
||||||
def _create_symlink(source, link_name, dwFlags):
|
def _create_symlink(source, link_name, dwFlags):
|
||||||
# Note: Win32 documentation for CreateSymbolicLink is incorrect.
|
if not CreateSymbolicLinkW(link_name, source, dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE):
|
||||||
# On success, the function returns "1".
|
|
||||||
# On error, the function returns some random value (e.g. 1280).
|
|
||||||
# The best bet seems to use "GetLastError" and check for error/success.
|
|
||||||
CreateSymbolicLinkW(link_name, source, dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
|
|
||||||
code = get_last_error()
|
|
||||||
if code != ERROR_SUCCESS:
|
|
||||||
# See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0
|
# See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0
|
||||||
# "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972).
|
# "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972).
|
||||||
# retry without it."
|
# retry without it."
|
||||||
CreateSymbolicLinkW(link_name, source, dwFlags)
|
if not CreateSymbolicLinkW(link_name, source, dwFlags):
|
||||||
code = get_last_error()
|
code = get_last_error()
|
||||||
if code != ERROR_SUCCESS:
|
|
||||||
error_desc = FormatError(code).strip()
|
error_desc = FormatError(code).strip()
|
||||||
if code == ERROR_PRIVILEGE_NOT_HELD:
|
if code == ERROR_PRIVILEGE_NOT_HELD:
|
||||||
raise OSError(errno.EPERM, error_desc, link_name)
|
raise OSError(errno.EPERM, error_desc, link_name)
|
||||||
|
20
progress.py
20
progress.py
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 The Android Open Source Project
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -16,10 +17,15 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from time import time
|
from time import time
|
||||||
from trace import IsTrace
|
from repo_trace import IsTrace
|
||||||
|
|
||||||
_NOT_TTY = not os.isatty(2)
|
_NOT_TTY = not os.isatty(2)
|
||||||
|
|
||||||
|
# This will erase all content in the current line (wherever the cursor is).
|
||||||
|
# It does not move the cursor, so this is usually followed by \r to move to
|
||||||
|
# column 0.
|
||||||
|
CSI_ERASE_LINE = '\x1b[2K'
|
||||||
|
|
||||||
class Progress(object):
|
class Progress(object):
|
||||||
def __init__(self, title, total=0, units='', print_newline=False,
|
def __init__(self, title, total=0, units='', print_newline=False,
|
||||||
always_print_percentage=False):
|
always_print_percentage=False):
|
||||||
@ -46,7 +52,8 @@ class Progress(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self._total <= 0:
|
if self._total <= 0:
|
||||||
sys.stderr.write('\r%s: %d, ' % (
|
sys.stderr.write('%s\r%s: %d,' % (
|
||||||
|
CSI_ERASE_LINE,
|
||||||
self._title,
|
self._title,
|
||||||
self._done))
|
self._done))
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
@ -55,7 +62,8 @@ class Progress(object):
|
|||||||
|
|
||||||
if self._lastp != p or self._always_print_percentage:
|
if self._lastp != p or self._always_print_percentage:
|
||||||
self._lastp = p
|
self._lastp = p
|
||||||
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s)%s' % (
|
sys.stderr.write('%s\r%s: %3d%% (%d%s/%d%s)%s' % (
|
||||||
|
CSI_ERASE_LINE,
|
||||||
self._title,
|
self._title,
|
||||||
p,
|
p,
|
||||||
self._done, self._units,
|
self._done, self._units,
|
||||||
@ -68,13 +76,15 @@ class Progress(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self._total <= 0:
|
if self._total <= 0:
|
||||||
sys.stderr.write('\r%s: %d, done. \n' % (
|
sys.stderr.write('%s\r%s: %d, done.\n' % (
|
||||||
|
CSI_ERASE_LINE,
|
||||||
self._title,
|
self._title,
|
||||||
self._done))
|
self._done))
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
else:
|
else:
|
||||||
p = (100 * self._done) / self._total
|
p = (100 * self._done) / self._total
|
||||||
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s), done. \n' % (
|
sys.stderr.write('%s\r%s: %3d%% (%d%s/%d%s), done.\n' % (
|
||||||
|
CSI_ERASE_LINE,
|
||||||
self._title,
|
self._title,
|
||||||
p,
|
p,
|
||||||
self._done, self._units,
|
self._done, self._units,
|
||||||
|
241
project.py
241
project.py
@ -1,3 +1,5 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -16,6 +18,7 @@ from __future__ import print_function
|
|||||||
import errno
|
import errno
|
||||||
import filecmp
|
import filecmp
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
@ -36,7 +39,8 @@ from error import GitError, HookError, UploadError, DownloadError
|
|||||||
from error import ManifestInvalidRevisionError
|
from error import ManifestInvalidRevisionError
|
||||||
from error import NoManifestException
|
from error import NoManifestException
|
||||||
import platform_utils
|
import platform_utils
|
||||||
from trace import IsTrace, Trace
|
import progress
|
||||||
|
from repo_trace import IsTrace, Trace
|
||||||
|
|
||||||
from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
|
from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
|
||||||
|
|
||||||
@ -226,6 +230,7 @@ class DiffColoring(Coloring):
|
|||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
Coloring.__init__(self, config, 'diff')
|
Coloring.__init__(self, config, 'diff')
|
||||||
self.project = self.printer('header', attr='bold')
|
self.project = self.printer('header', attr='bold')
|
||||||
|
self.fail = self.printer('fail', fg='red')
|
||||||
|
|
||||||
|
|
||||||
class _Annotation(object):
|
class _Annotation(object):
|
||||||
@ -542,6 +547,105 @@ class RepoHook(object):
|
|||||||
prompt % (self._GetMustVerb(), self._script_fullpath),
|
prompt % (self._GetMustVerb(), self._script_fullpath),
|
||||||
'Scripts have changed since %s was allowed.' % (self._hook_type,))
|
'Scripts have changed since %s was allowed.' % (self._hook_type,))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ExtractInterpFromShebang(data):
|
||||||
|
"""Extract the interpreter used in the shebang.
|
||||||
|
|
||||||
|
Try to locate the interpreter the script is using (ignoring `env`).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: The file content of the script.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The basename of the main script interpreter, or None if a shebang is not
|
||||||
|
used or could not be parsed out.
|
||||||
|
"""
|
||||||
|
firstline = data.splitlines()[:1]
|
||||||
|
if not firstline:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# The format here can be tricky.
|
||||||
|
shebang = firstline[0].strip()
|
||||||
|
m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# If the using `env`, find the target program.
|
||||||
|
interp = m.group(1)
|
||||||
|
if os.path.basename(interp) == 'env':
|
||||||
|
interp = m.group(2)
|
||||||
|
|
||||||
|
return interp
|
||||||
|
|
||||||
|
def _ExecuteHookViaReexec(self, interp, context, **kwargs):
|
||||||
|
"""Execute the hook script through |interp|.
|
||||||
|
|
||||||
|
Note: Support for this feature should be dropped ~Jun 2021.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interp: The Python program to run.
|
||||||
|
context: Basic Python context to execute the hook inside.
|
||||||
|
kwargs: Arbitrary arguments to pass to the hook script.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HookError: When the hooks failed for any reason.
|
||||||
|
"""
|
||||||
|
# This logic needs to be kept in sync with _ExecuteHookViaImport below.
|
||||||
|
script = """
|
||||||
|
import json, os, sys
|
||||||
|
path = '''%(path)s'''
|
||||||
|
kwargs = json.loads('''%(kwargs)s''')
|
||||||
|
context = json.loads('''%(context)s''')
|
||||||
|
sys.path.insert(0, os.path.dirname(path))
|
||||||
|
data = open(path).read()
|
||||||
|
exec(compile(data, path, 'exec'), context)
|
||||||
|
context['main'](**kwargs)
|
||||||
|
""" % {
|
||||||
|
'path': self._script_fullpath,
|
||||||
|
'kwargs': json.dumps(kwargs),
|
||||||
|
'context': json.dumps(context),
|
||||||
|
}
|
||||||
|
|
||||||
|
# We pass the script via stdin to avoid OS argv limits. It also makes
|
||||||
|
# unhandled exception tracebacks less verbose/confusing for users.
|
||||||
|
cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
|
||||||
|
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
|
||||||
|
proc.communicate(input=script.encode('utf-8'))
|
||||||
|
if proc.returncode:
|
||||||
|
raise HookError('Failed to run %s hook.' % (self._hook_type,))
|
||||||
|
|
||||||
|
def _ExecuteHookViaImport(self, data, context, **kwargs):
|
||||||
|
"""Execute the hook code in |data| directly.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: The code of the hook to execute.
|
||||||
|
context: Basic Python context to execute the hook inside.
|
||||||
|
kwargs: Arbitrary arguments to pass to the hook script.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HookError: When the hooks failed for any reason.
|
||||||
|
"""
|
||||||
|
# Exec, storing global context in the context dict. We catch exceptions
|
||||||
|
# and convert to a HookError w/ just the failing traceback.
|
||||||
|
try:
|
||||||
|
exec(compile(data, self._script_fullpath, 'exec'), context)
|
||||||
|
except Exception:
|
||||||
|
raise HookError('%s\nFailed to import %s hook; see traceback above.' %
|
||||||
|
(traceback.format_exc(), self._hook_type))
|
||||||
|
|
||||||
|
# Running the script should have defined a main() function.
|
||||||
|
if 'main' not in context:
|
||||||
|
raise HookError('Missing main() in: "%s"' % self._script_fullpath)
|
||||||
|
|
||||||
|
# Call the main function in the hook. If the hook should cause the
|
||||||
|
# build to fail, it will raise an Exception. We'll catch that convert
|
||||||
|
# to a HookError w/ just the failing traceback.
|
||||||
|
try:
|
||||||
|
context['main'](**kwargs)
|
||||||
|
except Exception:
|
||||||
|
raise HookError('%s\nFailed to run main() for %s hook; see traceback '
|
||||||
|
'above.' % (traceback.format_exc(), self._hook_type))
|
||||||
|
|
||||||
def _ExecuteHook(self, **kwargs):
|
def _ExecuteHook(self, **kwargs):
|
||||||
"""Actually execute the given hook.
|
"""Actually execute the given hook.
|
||||||
|
|
||||||
@ -566,19 +670,8 @@ class RepoHook(object):
|
|||||||
# hooks can't import repo files.
|
# hooks can't import repo files.
|
||||||
sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
|
sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
|
||||||
|
|
||||||
# Exec, storing global context in the context dict. We catch exceptions
|
# Initial global context for the hook to run within.
|
||||||
# and convert to a HookError w/ just the failing traceback.
|
|
||||||
context = {'__file__': self._script_fullpath}
|
context = {'__file__': self._script_fullpath}
|
||||||
try:
|
|
||||||
exec(compile(open(self._script_fullpath).read(),
|
|
||||||
self._script_fullpath, 'exec'), context)
|
|
||||||
except Exception:
|
|
||||||
raise HookError('%s\nFailed to import %s hook; see traceback above.' %
|
|
||||||
(traceback.format_exc(), self._hook_type))
|
|
||||||
|
|
||||||
# Running the script should have defined a main() function.
|
|
||||||
if 'main' not in context:
|
|
||||||
raise HookError('Missing main() in: "%s"' % self._script_fullpath)
|
|
||||||
|
|
||||||
# Add 'hook_should_take_kwargs' to the arguments to be passed to main.
|
# Add 'hook_should_take_kwargs' to the arguments to be passed to main.
|
||||||
# We don't actually want hooks to define their main with this argument--
|
# We don't actually want hooks to define their main with this argument--
|
||||||
@ -590,15 +683,31 @@ class RepoHook(object):
|
|||||||
kwargs = kwargs.copy()
|
kwargs = kwargs.copy()
|
||||||
kwargs['hook_should_take_kwargs'] = True
|
kwargs['hook_should_take_kwargs'] = True
|
||||||
|
|
||||||
# Call the main function in the hook. If the hook should cause the
|
# See what version of python the hook has been written against.
|
||||||
# build to fail, it will raise an Exception. We'll catch that convert
|
data = open(self._script_fullpath).read()
|
||||||
# to a HookError w/ just the failing traceback.
|
interp = self._ExtractInterpFromShebang(data)
|
||||||
|
reexec = False
|
||||||
|
if interp:
|
||||||
|
prog = os.path.basename(interp)
|
||||||
|
if prog.startswith('python2') and sys.version_info.major != 2:
|
||||||
|
reexec = True
|
||||||
|
elif prog.startswith('python3') and sys.version_info.major == 2:
|
||||||
|
reexec = True
|
||||||
|
|
||||||
|
# Attempt to execute the hooks through the requested version of Python.
|
||||||
|
if reexec:
|
||||||
try:
|
try:
|
||||||
context['main'](**kwargs)
|
self._ExecuteHookViaReexec(interp, context, **kwargs)
|
||||||
except Exception:
|
except OSError as e:
|
||||||
raise HookError('%s\nFailed to run main() for %s hook; see traceback '
|
if e.errno == errno.ENOENT:
|
||||||
'above.' % (traceback.format_exc(),
|
# We couldn't find the interpreter, so fallback to importing.
|
||||||
self._hook_type))
|
reexec = False
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Run the hook by importing directly.
|
||||||
|
if not reexec:
|
||||||
|
self._ExecuteHookViaImport(data, context, **kwargs)
|
||||||
finally:
|
finally:
|
||||||
# Restore sys.path and CWD.
|
# Restore sys.path and CWD.
|
||||||
sys.path = orig_syspath
|
sys.path = orig_syspath
|
||||||
@ -757,10 +866,17 @@ class Project(object):
|
|||||||
@property
|
@property
|
||||||
def CurrentBranch(self):
|
def CurrentBranch(self):
|
||||||
"""Obtain the name of the currently checked out branch.
|
"""Obtain the name of the currently checked out branch.
|
||||||
|
|
||||||
The branch name omits the 'refs/heads/' prefix.
|
The branch name omits the 'refs/heads/' prefix.
|
||||||
None is returned if the project is on a detached HEAD.
|
None is returned if the project is on a detached HEAD, or if the work_git is
|
||||||
|
otheriwse inaccessible (e.g. an incomplete sync).
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
b = self.work_git.GetHead()
|
b = self.work_git.GetHead()
|
||||||
|
except NoManifestException:
|
||||||
|
# If the local checkout is in a bad state, don't barf. Let the callers
|
||||||
|
# process this like the head is unreadable.
|
||||||
|
return None
|
||||||
if b.startswith(R_HEADS):
|
if b.startswith(R_HEADS):
|
||||||
return b[len(R_HEADS):]
|
return b[len(R_HEADS):]
|
||||||
return None
|
return None
|
||||||
@ -1028,19 +1144,29 @@ class Project(object):
|
|||||||
cmd.append('--src-prefix=a/%s/' % self.relpath)
|
cmd.append('--src-prefix=a/%s/' % self.relpath)
|
||||||
cmd.append('--dst-prefix=b/%s/' % self.relpath)
|
cmd.append('--dst-prefix=b/%s/' % self.relpath)
|
||||||
cmd.append('--')
|
cmd.append('--')
|
||||||
|
try:
|
||||||
p = GitCommand(self,
|
p = GitCommand(self,
|
||||||
cmd,
|
cmd,
|
||||||
capture_stdout=True,
|
capture_stdout=True,
|
||||||
capture_stderr=True)
|
capture_stderr=True)
|
||||||
|
except GitError as e:
|
||||||
|
out.nl()
|
||||||
|
out.project('project %s/' % self.relpath)
|
||||||
|
out.nl()
|
||||||
|
out.fail('%s', str(e))
|
||||||
|
out.nl()
|
||||||
|
return False
|
||||||
has_diff = False
|
has_diff = False
|
||||||
for line in p.process.stdout:
|
for line in p.process.stdout:
|
||||||
|
if not hasattr(line, 'encode'):
|
||||||
|
line = line.decode()
|
||||||
if not has_diff:
|
if not has_diff:
|
||||||
out.nl()
|
out.nl()
|
||||||
out.project('project %s/' % self.relpath)
|
out.project('project %s/' % self.relpath)
|
||||||
out.nl()
|
out.nl()
|
||||||
has_diff = True
|
has_diff = True
|
||||||
print(line[:-1])
|
print(line[:-1])
|
||||||
p.Wait()
|
return p.Wait() == 0
|
||||||
|
|
||||||
|
|
||||||
# Publish / Upload ##
|
# Publish / Upload ##
|
||||||
@ -1224,7 +1350,8 @@ class Project(object):
|
|||||||
archive=False,
|
archive=False,
|
||||||
optimized_fetch=False,
|
optimized_fetch=False,
|
||||||
prune=False,
|
prune=False,
|
||||||
submodules=False):
|
submodules=False,
|
||||||
|
clone_filter=None):
|
||||||
"""Perform only the network IO portion of the sync process.
|
"""Perform only the network IO portion of the sync process.
|
||||||
Local working directory/branch state is not affected.
|
Local working directory/branch state is not affected.
|
||||||
"""
|
"""
|
||||||
@ -1307,7 +1434,8 @@ class Project(object):
|
|||||||
not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
|
not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
|
||||||
current_branch_only=current_branch_only,
|
current_branch_only=current_branch_only,
|
||||||
no_tags=no_tags, prune=prune, depth=depth,
|
no_tags=no_tags, prune=prune, depth=depth,
|
||||||
submodules=submodules, force_sync=force_sync)):
|
submodules=submodules, force_sync=force_sync,
|
||||||
|
clone_filter=clone_filter)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
mp = self.manifest.manifestProject
|
mp = self.manifest.manifestProject
|
||||||
@ -1486,7 +1614,7 @@ class Project(object):
|
|||||||
last_mine = None
|
last_mine = None
|
||||||
cnt_mine = 0
|
cnt_mine = 0
|
||||||
for commit in local_changes:
|
for commit in local_changes:
|
||||||
commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
|
commit_id, committer_email = commit.split(' ', 1)
|
||||||
if committer_email == self.UserEmail:
|
if committer_email == self.UserEmail:
|
||||||
last_mine = commit_id
|
last_mine = commit_id
|
||||||
cnt_mine += 1
|
cnt_mine += 1
|
||||||
@ -1586,7 +1714,7 @@ class Project(object):
|
|||||||
|
|
||||||
# Branch Management ##
|
# Branch Management ##
|
||||||
|
|
||||||
def StartBranch(self, name, branch_merge=''):
|
def StartBranch(self, name, branch_merge='', revision=None):
|
||||||
"""Create a new branch off the manifest's revision.
|
"""Create a new branch off the manifest's revision.
|
||||||
"""
|
"""
|
||||||
if not branch_merge:
|
if not branch_merge:
|
||||||
@ -1607,7 +1735,11 @@ class Project(object):
|
|||||||
branch.merge = branch_merge
|
branch.merge = branch_merge
|
||||||
if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
|
if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
|
||||||
branch.merge = R_HEADS + branch_merge
|
branch.merge = R_HEADS + branch_merge
|
||||||
|
|
||||||
|
if revision is None:
|
||||||
revid = self.GetRevisionId(all_refs)
|
revid = self.GetRevisionId(all_refs)
|
||||||
|
else:
|
||||||
|
revid = self.work_git.rev_parse(revision)
|
||||||
|
|
||||||
if head.startswith(R_HEADS):
|
if head.startswith(R_HEADS):
|
||||||
try:
|
try:
|
||||||
@ -1957,7 +2089,8 @@ class Project(object):
|
|||||||
prune=False,
|
prune=False,
|
||||||
depth=None,
|
depth=None,
|
||||||
submodules=False,
|
submodules=False,
|
||||||
force_sync=False):
|
force_sync=False,
|
||||||
|
clone_filter=None):
|
||||||
|
|
||||||
is_sha1 = False
|
is_sha1 = False
|
||||||
tag_name = None
|
tag_name = None
|
||||||
@ -2048,6 +2181,11 @@ class Project(object):
|
|||||||
|
|
||||||
cmd = ['fetch']
|
cmd = ['fetch']
|
||||||
|
|
||||||
|
if clone_filter:
|
||||||
|
git_require((2, 19, 0), fail=True, msg='partial clones')
|
||||||
|
cmd.append('--filter=%s' % clone_filter)
|
||||||
|
self.config.SetString('extensions.partialclone', self.remote.name)
|
||||||
|
|
||||||
if depth:
|
if depth:
|
||||||
cmd.append('--depth=%s' % depth)
|
cmd.append('--depth=%s' % depth)
|
||||||
else:
|
else:
|
||||||
@ -2064,12 +2202,15 @@ class Project(object):
|
|||||||
cmd.append('--update-head-ok')
|
cmd.append('--update-head-ok')
|
||||||
cmd.append(name)
|
cmd.append(name)
|
||||||
|
|
||||||
|
spec = []
|
||||||
|
|
||||||
# If using depth then we should not get all the tags since they may
|
# If using depth then we should not get all the tags since they may
|
||||||
# be outside of the depth.
|
# be outside of the depth.
|
||||||
if no_tags or depth:
|
if no_tags or depth:
|
||||||
cmd.append('--no-tags')
|
cmd.append('--no-tags')
|
||||||
else:
|
else:
|
||||||
cmd.append('--tags')
|
cmd.append('--tags')
|
||||||
|
spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
|
||||||
|
|
||||||
if force_sync:
|
if force_sync:
|
||||||
cmd.append('--force')
|
cmd.append('--force')
|
||||||
@ -2080,7 +2221,6 @@ class Project(object):
|
|||||||
if submodules:
|
if submodules:
|
||||||
cmd.append('--recurse-submodules=on-demand')
|
cmd.append('--recurse-submodules=on-demand')
|
||||||
|
|
||||||
spec = []
|
|
||||||
if not current_branch_only:
|
if not current_branch_only:
|
||||||
# Fetch whole repo
|
# Fetch whole repo
|
||||||
spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
|
spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
|
||||||
@ -2148,12 +2288,12 @@ class Project(object):
|
|||||||
return self._RemoteFetch(name=name,
|
return self._RemoteFetch(name=name,
|
||||||
current_branch_only=current_branch_only,
|
current_branch_only=current_branch_only,
|
||||||
initial=False, quiet=quiet, alt_dir=alt_dir,
|
initial=False, quiet=quiet, alt_dir=alt_dir,
|
||||||
depth=None)
|
depth=None, clone_filter=clone_filter)
|
||||||
else:
|
else:
|
||||||
# Avoid infinite recursion: sync all branches with depth set to None
|
# Avoid infinite recursion: sync all branches with depth set to None
|
||||||
return self._RemoteFetch(name=name, current_branch_only=False,
|
return self._RemoteFetch(name=name, current_branch_only=False,
|
||||||
initial=False, quiet=quiet, alt_dir=alt_dir,
|
initial=False, quiet=quiet, alt_dir=alt_dir,
|
||||||
depth=None)
|
depth=None, clone_filter=clone_filter)
|
||||||
|
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
@ -2215,13 +2355,17 @@ class Project(object):
|
|||||||
cmd += ['--continue-at', '%d' % (size,)]
|
cmd += ['--continue-at', '%d' % (size,)]
|
||||||
else:
|
else:
|
||||||
platform_utils.remove(tmpPath)
|
platform_utils.remove(tmpPath)
|
||||||
if 'http_proxy' in os.environ and 'darwin' == sys.platform:
|
with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
|
||||||
cmd += ['--proxy', os.environ['http_proxy']]
|
|
||||||
with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
|
|
||||||
if cookiefile:
|
if cookiefile:
|
||||||
cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
|
cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
|
||||||
if srcUrl.startswith('persistent-'):
|
if proxy:
|
||||||
srcUrl = srcUrl[len('persistent-'):]
|
cmd += ['--proxy', proxy]
|
||||||
|
elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
|
||||||
|
cmd += ['--proxy', os.environ['http_proxy']]
|
||||||
|
if srcUrl.startswith('persistent-https'):
|
||||||
|
srcUrl = 'http' + srcUrl[len('persistent-https'):]
|
||||||
|
elif srcUrl.startswith('persistent-http'):
|
||||||
|
srcUrl = 'http' + srcUrl[len('persistent-http'):]
|
||||||
cmd += [srcUrl]
|
cmd += [srcUrl]
|
||||||
|
|
||||||
if IsTrace():
|
if IsTrace():
|
||||||
@ -2255,8 +2399,8 @@ class Project(object):
|
|||||||
|
|
||||||
def _IsValidBundle(self, path, quiet):
|
def _IsValidBundle(self, path, quiet):
|
||||||
try:
|
try:
|
||||||
with open(path) as f:
|
with open(path, 'rb') as f:
|
||||||
if f.read(16) == '# v2 git bundle\n':
|
if f.read(16) == b'# v2 git bundle\n':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
@ -2287,9 +2431,6 @@ class Project(object):
|
|||||||
cmd = ['ls-remote', self.remote.name, refs]
|
cmd = ['ls-remote', self.remote.name, refs]
|
||||||
p = GitCommand(self, cmd, capture_stdout=True)
|
p = GitCommand(self, cmd, capture_stdout=True)
|
||||||
if p.Wait() == 0:
|
if p.Wait() == 0:
|
||||||
if hasattr(p.stdout, 'decode'):
|
|
||||||
return p.stdout.decode('utf-8')
|
|
||||||
else:
|
|
||||||
return p.stdout
|
return p.stdout
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -2701,6 +2842,8 @@ class Project(object):
|
|||||||
capture_stderr=True)
|
capture_stderr=True)
|
||||||
try:
|
try:
|
||||||
out = p.process.stdout.read()
|
out = p.process.stdout.read()
|
||||||
|
if not hasattr(out, 'encode'):
|
||||||
|
out = out.decode()
|
||||||
r = {}
|
r = {}
|
||||||
if out:
|
if out:
|
||||||
out = iter(out[:-1].split('\0'))
|
out = iter(out[:-1].split('\0'))
|
||||||
@ -2809,15 +2952,10 @@ class Project(object):
|
|||||||
gitdir=self._gitdir,
|
gitdir=self._gitdir,
|
||||||
capture_stdout=True,
|
capture_stdout=True,
|
||||||
capture_stderr=True)
|
capture_stderr=True)
|
||||||
r = []
|
|
||||||
for line in p.process.stdout:
|
|
||||||
if line[-1] == '\n':
|
|
||||||
line = line[:-1]
|
|
||||||
r.append(line)
|
|
||||||
if p.Wait() != 0:
|
if p.Wait() != 0:
|
||||||
raise GitError('%s rev-list %s: %s' %
|
raise GitError('%s rev-list %s: %s' %
|
||||||
(self._project.name, str(args), p.stderr))
|
(self._project.name, str(args), p.stderr))
|
||||||
return r
|
return p.stdout.splitlines()
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""Allow arbitrary git commands using pythonic syntax.
|
"""Allow arbitrary git commands using pythonic syntax.
|
||||||
@ -2865,10 +3003,6 @@ class Project(object):
|
|||||||
raise GitError('%s %s: %s' %
|
raise GitError('%s %s: %s' %
|
||||||
(self._project.name, name, p.stderr))
|
(self._project.name, name, p.stderr))
|
||||||
r = p.stdout
|
r = p.stdout
|
||||||
try:
|
|
||||||
r = r.decode('utf-8')
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
if r.endswith('\n') and r.index('\n') == len(r) - 1:
|
if r.endswith('\n') and r.index('\n') == len(r) - 1:
|
||||||
return r[:-1]
|
return r[:-1]
|
||||||
return r
|
return r
|
||||||
@ -2996,6 +3130,11 @@ class SyncBuffer(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _PrintMessages(self):
|
def _PrintMessages(self):
|
||||||
|
if self._messages or self._failures:
|
||||||
|
if os.isatty(2):
|
||||||
|
self.out.write(progress.CSI_ERASE_LINE)
|
||||||
|
self.out.write('\r')
|
||||||
|
|
||||||
for m in self._messages:
|
for m in self._messages:
|
||||||
m.Print(self)
|
m.Print(self)
|
||||||
for m in self._failures:
|
for m in self._failures:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2013 The Android Open Source Project
|
# Copyright (C) 2013 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
186
repo
186
repo
@ -1,4 +1,14 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
"""Repo launcher.
|
||||||
|
|
||||||
|
This is a standalone tool that people may copy to anywhere in their system.
|
||||||
|
It is used to get an initial repo client checkout, and after that it runs the
|
||||||
|
copy of repo in the checkout.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
# repo default configuration
|
# repo default configuration
|
||||||
#
|
#
|
||||||
@ -23,7 +33,7 @@ REPO_REV = 'stable'
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# increment this whenever we make important changes to this script
|
# increment this whenever we make important changes to this script
|
||||||
VERSION = (1, 25)
|
VERSION = (1, 26)
|
||||||
|
|
||||||
# increment this if the MAINTAINER_KEYS block is modified
|
# increment this if the MAINTAINER_KEYS block is modified
|
||||||
KEYRING_VERSION = (1, 2)
|
KEYRING_VERSION = (1, 2)
|
||||||
@ -118,6 +128,7 @@ GITC_CONFIG_FILE = '/gitc/.config'
|
|||||||
GITC_FS_ROOT_DIR = '/gitc/manifest-rw/'
|
GITC_FS_ROOT_DIR = '/gitc/manifest-rw/'
|
||||||
|
|
||||||
|
|
||||||
|
import collections
|
||||||
import errno
|
import errno
|
||||||
import optparse
|
import optparse
|
||||||
import platform
|
import platform
|
||||||
@ -138,21 +149,10 @@ else:
|
|||||||
urllib.error = urllib2
|
urllib.error = urllib2
|
||||||
|
|
||||||
|
|
||||||
def _print(*objects, **kwargs):
|
|
||||||
sep = kwargs.get('sep', ' ')
|
|
||||||
end = kwargs.get('end', '\n')
|
|
||||||
out = kwargs.get('file', sys.stdout)
|
|
||||||
out.write(sep.join(objects) + end)
|
|
||||||
|
|
||||||
# On Windows stderr is buffered, so flush to maintain the order of error messages.
|
|
||||||
if out == sys.stderr and platform.system() == "Windows":
|
|
||||||
out.flush()
|
|
||||||
|
|
||||||
|
|
||||||
# Python version check
|
# Python version check
|
||||||
ver = sys.version_info
|
ver = sys.version_info
|
||||||
if (ver[0], ver[1]) < MIN_PYTHON_VERSION:
|
if (ver[0], ver[1]) < MIN_PYTHON_VERSION:
|
||||||
_print('error: Python version {} unsupported.\n'
|
print('error: Python version {} unsupported.\n'
|
||||||
'Please use Python {}.{} instead.'.format(
|
'Please use Python {}.{} instead.'.format(
|
||||||
sys.version.split(' ')[0],
|
sys.version.split(' ')[0],
|
||||||
MIN_PYTHON_VERSION[0],
|
MIN_PYTHON_VERSION[0],
|
||||||
@ -199,6 +199,13 @@ group.add_option('--dissociate',
|
|||||||
group.add_option('--depth', type='int', default=None,
|
group.add_option('--depth', type='int', default=None,
|
||||||
dest='depth',
|
dest='depth',
|
||||||
help='create a shallow clone with given depth; see git clone')
|
help='create a shallow clone with given depth; see git clone')
|
||||||
|
group.add_option('--partial-clone', action='store_true',
|
||||||
|
dest='partial_clone',
|
||||||
|
help='perform partial clone (https://git-scm.com/'
|
||||||
|
'docs/gitrepository-layout#_code_partialclone_code)')
|
||||||
|
group.add_option('--clone-filter', action='store', default='blob:none',
|
||||||
|
dest='clone_filter',
|
||||||
|
help='filter for use with --partial-clone [default: %default]')
|
||||||
group.add_option('--archive',
|
group.add_option('--archive',
|
||||||
dest='archive', action='store_true',
|
dest='archive', action='store_true',
|
||||||
help='checkout an archive instead of a git repository for '
|
help='checkout an archive instead of a git repository for '
|
||||||
@ -323,21 +330,21 @@ def _Init(args, gitc_init=False):
|
|||||||
if branch.startswith('refs/heads/'):
|
if branch.startswith('refs/heads/'):
|
||||||
branch = branch[len('refs/heads/'):]
|
branch = branch[len('refs/heads/'):]
|
||||||
if branch.startswith('refs/'):
|
if branch.startswith('refs/'):
|
||||||
_print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
|
print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if gitc_init:
|
if gitc_init:
|
||||||
gitc_manifest_dir = get_gitc_manifest_dir()
|
gitc_manifest_dir = get_gitc_manifest_dir()
|
||||||
if not gitc_manifest_dir:
|
if not gitc_manifest_dir:
|
||||||
_print('fatal: GITC filesystem is not available. Exiting...',
|
print('fatal: GITC filesystem is not available. Exiting...',
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
gitc_client = opt.gitc_client
|
gitc_client = opt.gitc_client
|
||||||
if not gitc_client:
|
if not gitc_client:
|
||||||
gitc_client = gitc_parse_clientdir(os.getcwd())
|
gitc_client = gitc_parse_clientdir(os.getcwd())
|
||||||
if not gitc_client:
|
if not gitc_client:
|
||||||
_print('fatal: GITC client (-c) is required.', file=sys.stderr)
|
print('fatal: GITC client (-c) is required.', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
client_dir = os.path.join(gitc_manifest_dir, gitc_client)
|
client_dir = os.path.join(gitc_manifest_dir, gitc_client)
|
||||||
if not os.path.exists(client_dir):
|
if not os.path.exists(client_dir):
|
||||||
@ -350,7 +357,7 @@ def _Init(args, gitc_init=False):
|
|||||||
os.mkdir(repodir)
|
os.mkdir(repodir)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno != errno.EEXIST:
|
if e.errno != errno.EEXIST:
|
||||||
_print('fatal: cannot make %s directory: %s'
|
print('fatal: cannot make %s directory: %s'
|
||||||
% (repodir, e.strerror), file=sys.stderr)
|
% (repodir, e.strerror), file=sys.stderr)
|
||||||
# Don't raise CloneFailure; that would delete the
|
# Don't raise CloneFailure; that would delete the
|
||||||
# name. Instead exit immediately.
|
# name. Instead exit immediately.
|
||||||
@ -375,55 +382,73 @@ def _Init(args, gitc_init=False):
|
|||||||
_Checkout(dst, branch, rev, opt.quiet)
|
_Checkout(dst, branch, rev, opt.quiet)
|
||||||
|
|
||||||
if not os.path.isfile(os.path.join(dst, 'repo')):
|
if not os.path.isfile(os.path.join(dst, 'repo')):
|
||||||
_print("warning: '%s' does not look like a git-repo repository, is "
|
print("warning: '%s' does not look like a git-repo repository, is "
|
||||||
"REPO_URL set correctly?" % url, file=sys.stderr)
|
"REPO_URL set correctly?" % url, file=sys.stderr)
|
||||||
|
|
||||||
except CloneFailure:
|
except CloneFailure:
|
||||||
if opt.quiet:
|
if opt.quiet:
|
||||||
_print('fatal: repo init failed; run without --quiet to see why',
|
print('fatal: repo init failed; run without --quiet to see why',
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def ParseGitVersion(ver_str):
|
# The git version info broken down into components for easy analysis.
|
||||||
|
# Similar to Python's sys.version_info.
|
||||||
|
GitVersion = collections.namedtuple(
|
||||||
|
'GitVersion', ('major', 'minor', 'micro', 'full'))
|
||||||
|
|
||||||
|
def ParseGitVersion(ver_str=None):
|
||||||
|
if ver_str is None:
|
||||||
|
# Load the version ourselves.
|
||||||
|
ver_str = _GetGitVersion()
|
||||||
|
|
||||||
if not ver_str.startswith('git version '):
|
if not ver_str.startswith('git version '):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
num_ver_str = ver_str[len('git version '):].strip().split('-')[0]
|
full_version = ver_str[len('git version '):].strip()
|
||||||
|
num_ver_str = full_version.split('-')[0]
|
||||||
to_tuple = []
|
to_tuple = []
|
||||||
for num_str in num_ver_str.split('.')[:3]:
|
for num_str in num_ver_str.split('.')[:3]:
|
||||||
if num_str.isdigit():
|
if num_str.isdigit():
|
||||||
to_tuple.append(int(num_str))
|
to_tuple.append(int(num_str))
|
||||||
else:
|
else:
|
||||||
to_tuple.append(0)
|
to_tuple.append(0)
|
||||||
return tuple(to_tuple)
|
to_tuple.append(full_version)
|
||||||
|
return GitVersion(*to_tuple)
|
||||||
|
|
||||||
|
|
||||||
def _CheckGitVersion():
|
def _GetGitVersion():
|
||||||
cmd = [GIT, '--version']
|
cmd = [GIT, '--version']
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
_print("fatal: '%s' is not available" % GIT, file=sys.stderr)
|
print("fatal: '%s' is not available" % GIT, file=sys.stderr)
|
||||||
_print('fatal: %s' % e, file=sys.stderr)
|
print('fatal: %s' % e, file=sys.stderr)
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
_print('Please make sure %s is installed and in your path.' % GIT,
|
print('Please make sure %s is installed and in your path.' % GIT,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise
|
||||||
|
|
||||||
ver_str = proc.stdout.read().strip()
|
ver_str = proc.stdout.read().strip()
|
||||||
proc.stdout.close()
|
proc.stdout.close()
|
||||||
proc.wait()
|
proc.wait()
|
||||||
|
return ver_str.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def _CheckGitVersion():
|
||||||
|
try:
|
||||||
|
ver_act = ParseGitVersion()
|
||||||
|
except OSError:
|
||||||
|
raise CloneFailure()
|
||||||
|
|
||||||
ver_act = ParseGitVersion(ver_str)
|
|
||||||
if ver_act is None:
|
if ver_act is None:
|
||||||
_print('error: "%s" unsupported' % ver_str, file=sys.stderr)
|
print('fatal: unable to detect git version', file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
|
|
||||||
if ver_act < MIN_GIT_VERSION:
|
if ver_act < MIN_GIT_VERSION:
|
||||||
need = '.'.join(map(str, MIN_GIT_VERSION))
|
need = '.'.join(map(str, MIN_GIT_VERSION))
|
||||||
_print('fatal: git %s or later required' % need, file=sys.stderr)
|
print('fatal: git %s or later required' % need, file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +475,7 @@ def SetupGnuPG(quiet):
|
|||||||
os.mkdir(home_dot_repo)
|
os.mkdir(home_dot_repo)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno != errno.EEXIST:
|
if e.errno != errno.EEXIST:
|
||||||
_print('fatal: cannot make %s directory: %s'
|
print('fatal: cannot make %s directory: %s'
|
||||||
% (home_dot_repo, e.strerror), file=sys.stderr)
|
% (home_dot_repo, e.strerror), file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -458,7 +483,7 @@ def SetupGnuPG(quiet):
|
|||||||
os.mkdir(gpg_dir, stat.S_IRWXU)
|
os.mkdir(gpg_dir, stat.S_IRWXU)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno != errno.EEXIST:
|
if e.errno != errno.EEXIST:
|
||||||
_print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
|
print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -475,18 +500,18 @@ def SetupGnuPG(quiet):
|
|||||||
stdin=subprocess.PIPE)
|
stdin=subprocess.PIPE)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
_print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
|
print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
|
||||||
_print('warning: Installing it is strongly encouraged.', file=sys.stderr)
|
print('warning: Installing it is strongly encouraged.', file=sys.stderr)
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
proc.stdin.write(MAINTAINER_KEYS)
|
proc.stdin.write(MAINTAINER_KEYS.encode('utf-8'))
|
||||||
proc.stdin.close()
|
proc.stdin.close()
|
||||||
|
|
||||||
if proc.wait() != 0:
|
if proc.wait() != 0:
|
||||||
_print('fatal: registering repo maintainer keys failed', file=sys.stderr)
|
print('fatal: registering repo maintainer keys failed', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
_print()
|
print()
|
||||||
|
|
||||||
fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
|
fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
|
||||||
fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
|
fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
|
||||||
@ -529,7 +554,7 @@ def _InitHttp():
|
|||||||
|
|
||||||
def _Fetch(url, local, src, quiet):
|
def _Fetch(url, local, src, quiet):
|
||||||
if not quiet:
|
if not quiet:
|
||||||
_print('Get %s' % url, file=sys.stderr)
|
print('Get %s' % url, file=sys.stderr)
|
||||||
|
|
||||||
cmd = [GIT, 'fetch']
|
cmd = [GIT, 'fetch']
|
||||||
if quiet:
|
if quiet:
|
||||||
@ -559,6 +584,7 @@ def _DownloadBundle(url, local, quiet):
|
|||||||
cwd=local,
|
cwd=local,
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
|
line = line.decode('utf-8')
|
||||||
m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line)
|
m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line)
|
||||||
if m:
|
if m:
|
||||||
new_url = m.group(1)
|
new_url = m.group(1)
|
||||||
@ -579,19 +605,19 @@ def _DownloadBundle(url, local, quiet):
|
|||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
if e.code in [401, 403, 404, 501]:
|
if e.code in [401, 403, 404, 501]:
|
||||||
return False
|
return False
|
||||||
_print('fatal: Cannot get %s' % url, file=sys.stderr)
|
print('fatal: Cannot get %s' % url, file=sys.stderr)
|
||||||
_print('fatal: HTTP error %s' % e.code, file=sys.stderr)
|
print('fatal: HTTP error %s' % e.code, file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
except urllib.error.URLError as e:
|
except urllib.error.URLError as e:
|
||||||
_print('fatal: Cannot get %s' % url, file=sys.stderr)
|
print('fatal: Cannot get %s' % url, file=sys.stderr)
|
||||||
_print('fatal: error %s' % e.reason, file=sys.stderr)
|
print('fatal: error %s' % e.reason, file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
try:
|
try:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
_print('Get %s' % url, file=sys.stderr)
|
print('Get %s' % url, file=sys.stderr)
|
||||||
while True:
|
while True:
|
||||||
buf = r.read(8192)
|
buf = r.read(8192)
|
||||||
if buf == '':
|
if not buf:
|
||||||
return True
|
return True
|
||||||
dest.write(buf)
|
dest.write(buf)
|
||||||
finally:
|
finally:
|
||||||
@ -614,7 +640,7 @@ def _Clone(url, local, quiet, clone_bundle):
|
|||||||
try:
|
try:
|
||||||
os.mkdir(local)
|
os.mkdir(local)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
_print('fatal: cannot make %s directory: %s' % (local, e.strerror),
|
print('fatal: cannot make %s directory: %s' % (local, e.strerror),
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
|
|
||||||
@ -622,15 +648,15 @@ def _Clone(url, local, quiet, clone_bundle):
|
|||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(cmd, cwd=local)
|
proc = subprocess.Popen(cmd, cwd=local)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
_print("fatal: '%s' is not available" % GIT, file=sys.stderr)
|
print("fatal: '%s' is not available" % GIT, file=sys.stderr)
|
||||||
_print('fatal: %s' % e, file=sys.stderr)
|
print('fatal: %s' % e, file=sys.stderr)
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
_print('Please make sure %s is installed and in your path.' % GIT,
|
print('Please make sure %s is installed and in your path.' % GIT,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
if proc.wait() != 0:
|
if proc.wait() != 0:
|
||||||
_print('fatal: could not create %s' % local, file=sys.stderr)
|
print('fatal: could not create %s' % local, file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
|
|
||||||
_InitHttp()
|
_InitHttp()
|
||||||
@ -651,25 +677,25 @@ def _Verify(cwd, branch, quiet):
|
|||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
cwd=cwd)
|
cwd=cwd)
|
||||||
cur = proc.stdout.read().strip()
|
cur = proc.stdout.read().strip().decode('utf-8')
|
||||||
proc.stdout.close()
|
proc.stdout.close()
|
||||||
|
|
||||||
proc.stderr.read()
|
proc.stderr.read()
|
||||||
proc.stderr.close()
|
proc.stderr.close()
|
||||||
|
|
||||||
if proc.wait() != 0 or not cur:
|
if proc.wait() != 0 or not cur:
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
_print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
|
print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
|
|
||||||
m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
|
m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
|
||||||
if m:
|
if m:
|
||||||
cur = m.group(1)
|
cur = m.group(1)
|
||||||
if not quiet:
|
if not quiet:
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
_print("info: Ignoring branch '%s'; using tagged release '%s'"
|
print("info: Ignoring branch '%s'; using tagged release '%s'"
|
||||||
% (branch, cur), file=sys.stderr)
|
% (branch, cur), file=sys.stderr)
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
try:
|
try:
|
||||||
@ -683,17 +709,17 @@ def _Verify(cwd, branch, quiet):
|
|||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
cwd=cwd,
|
cwd=cwd,
|
||||||
env=env)
|
env=env)
|
||||||
out = proc.stdout.read()
|
out = proc.stdout.read().decode('utf-8')
|
||||||
proc.stdout.close()
|
proc.stdout.close()
|
||||||
|
|
||||||
err = proc.stderr.read()
|
err = proc.stderr.read().decode('utf-8')
|
||||||
proc.stderr.close()
|
proc.stderr.close()
|
||||||
|
|
||||||
if proc.wait() != 0:
|
if proc.wait() != 0:
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
_print(out, file=sys.stderr)
|
print(out, file=sys.stderr)
|
||||||
_print(err, file=sys.stderr)
|
print(err, file=sys.stderr)
|
||||||
_print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
raise CloneFailure()
|
raise CloneFailure()
|
||||||
return '%s^0' % cur
|
return '%s^0' % cur
|
||||||
|
|
||||||
@ -764,7 +790,7 @@ def _Usage():
|
|||||||
if get_gitc_manifest_dir():
|
if get_gitc_manifest_dir():
|
||||||
gitc_usage = " gitc-init Initialize a GITC Client.\n"
|
gitc_usage = " gitc-init Initialize a GITC Client.\n"
|
||||||
|
|
||||||
_print(
|
print(
|
||||||
"""usage: repo COMMAND [ARGS]
|
"""usage: repo COMMAND [ARGS]
|
||||||
|
|
||||||
repo is not yet installed. Use "repo init" to install it here.
|
repo is not yet installed. Use "repo init" to install it here.
|
||||||
@ -776,8 +802,8 @@ The most commonly used repo commands are:
|
|||||||
""" help Display detailed help on a command
|
""" 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").
|
||||||
""", file=sys.stderr)
|
""")
|
||||||
sys.exit(1)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
def _Help(args):
|
def _Help(args):
|
||||||
@ -790,7 +816,7 @@ def _Help(args):
|
|||||||
init_optparse.print_help()
|
init_optparse.print_help()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
_print("error: '%s' is not a bootstrap command.\n"
|
print("error: '%s' is not a bootstrap command.\n"
|
||||||
' For access to online help, install repo ("repo init").'
|
' For access to online help, install repo ("repo init").'
|
||||||
% args[0], file=sys.stderr)
|
% args[0], file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
@ -799,13 +825,13 @@ def _Help(args):
|
|||||||
|
|
||||||
|
|
||||||
def _NotInstalled():
|
def _NotInstalled():
|
||||||
_print('error: repo is not installed. Use "repo init" to install it here.',
|
print('error: repo is not installed. Use "repo init" to install it here.',
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def _NoCommands(cmd):
|
def _NoCommands(cmd):
|
||||||
_print("""error: command '%s' requires repo to be installed first.
|
print("""error: command '%s' requires repo to be installed first.
|
||||||
Use "repo init" to install it here.""" % cmd, file=sys.stderr)
|
Use "repo init" to install it here.""" % cmd, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -836,14 +862,14 @@ def _SetDefaultsTo(gitdir):
|
|||||||
'HEAD'],
|
'HEAD'],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
REPO_REV = proc.stdout.read().strip()
|
REPO_REV = proc.stdout.read().strip().decode('utf-8')
|
||||||
proc.stdout.close()
|
proc.stdout.close()
|
||||||
|
|
||||||
proc.stderr.read()
|
proc.stderr.read()
|
||||||
proc.stderr.close()
|
proc.stderr.close()
|
||||||
|
|
||||||
if proc.wait() != 0:
|
if proc.wait() != 0:
|
||||||
_print('fatal: %s has no current branch' % gitdir, file=sys.stderr)
|
print('fatal: %s has no current branch' % gitdir, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -860,7 +886,7 @@ def main(orig_args):
|
|||||||
|
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()):
|
if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()):
|
||||||
_print('error: repo cannot be used in the GITC local manifest directory.'
|
print('error: repo cannot be used in the GITC local manifest directory.'
|
||||||
'\nIf you want to work on this GITC client please rerun this '
|
'\nIf you want to work on this GITC client please rerun this '
|
||||||
'command from the corresponding client under /gitc/',
|
'command from the corresponding client under /gitc/',
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
@ -879,7 +905,7 @@ def main(orig_args):
|
|||||||
_Init(args, gitc_init=(cmd == 'gitc-init'))
|
_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("fatal: cloning the git-repo repository failed, will remove "
|
print("fatal: cloning the git-repo repository failed, will remove "
|
||||||
"'%s' " % path, file=sys.stderr)
|
"'%s' " % path, file=sys.stderr)
|
||||||
shutil.rmtree(path, ignore_errors=True)
|
shutil.rmtree(path, ignore_errors=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@ -904,14 +930,10 @@ def main(orig_args):
|
|||||||
else:
|
else:
|
||||||
os.execv(sys.executable, me)
|
os.execv(sys.executable, me)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
_print("fatal: unable to start %s" % repo_main, file=sys.stderr)
|
print("fatal: unable to start %s" % repo_main, file=sys.stderr)
|
||||||
_print("fatal: %s" % e, file=sys.stderr)
|
print("fatal: %s" % e, file=sys.stderr)
|
||||||
sys.exit(148)
|
sys.exit(148)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if ver[0] == 3:
|
|
||||||
_print('warning: Python 3 support is currently experimental. YMMV.\n'
|
|
||||||
'Please use Python 2.7 instead.',
|
|
||||||
file=sys.stderr)
|
|
||||||
main(sys.argv[1:])
|
main(sys.argv[1:])
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -13,15 +14,19 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
"""Logic for tracing repo interactions.
|
||||||
|
|
||||||
|
Activated via `repo --trace ...` or `REPO_TRACE=1 repo ...`.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# Env var to implicitly turn on tracing.
|
||||||
REPO_TRACE = 'REPO_TRACE'
|
REPO_TRACE = 'REPO_TRACE'
|
||||||
|
|
||||||
try:
|
_TRACE = os.environ.get(REPO_TRACE) == '1'
|
||||||
_TRACE = os.environ[REPO_TRACE] == '1'
|
|
||||||
except KeyError:
|
|
||||||
_TRACE = False
|
|
||||||
|
|
||||||
def IsTrace():
|
def IsTrace():
|
||||||
return _TRACE
|
return _TRACE
|
54
run_tests
Executable file
54
run_tests
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Copyright 2019 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.
|
||||||
|
|
||||||
|
"""Wrapper to run pytest with the right settings."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def run_pytest(cmd, argv):
|
||||||
|
"""Run the unittests via |cmd|."""
|
||||||
|
try:
|
||||||
|
subprocess.check_call([cmd] + argv)
|
||||||
|
return 0
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
print('%s: unable to run `%s`: %s' % (__file__, cmd, e), file=sys.stderr)
|
||||||
|
print('%s: Try installing pytest: sudo apt-get install python-pytest' %
|
||||||
|
(__file__,), file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
"""The main entry."""
|
||||||
|
# Add the repo tree to PYTHONPATH as the tests expect to be able to import
|
||||||
|
# modules directly.
|
||||||
|
topdir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
pythonpath = os.environ.get('PYTHONPATH', '')
|
||||||
|
os.environ['PYTHONPATH'] = '%s:%s' % (topdir, pythonpath)
|
||||||
|
|
||||||
|
return run_pytest('pytest', argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -36,19 +37,19 @@ It is equivalent to "git branch -D <branchname>".
|
|||||||
dest='all', action='store_true',
|
dest='all', action='store_true',
|
||||||
help='delete all branches in all projects')
|
help='delete all branches in all projects')
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
if not opt.all and not args:
|
if not opt.all and not args:
|
||||||
self.Usage()
|
self.Usage()
|
||||||
|
|
||||||
if not opt.all:
|
if not opt.all:
|
||||||
nb = args[0]
|
nb = args[0]
|
||||||
if not git.check_ref_format('heads/%s' % nb):
|
if not git.check_ref_format('heads/%s' % nb):
|
||||||
print("error: '%s' is not a valid name" % nb, file=sys.stderr)
|
self.OptionParser.error("'%s' is not a valid branch name" % nb)
|
||||||
sys.exit(1)
|
|
||||||
else:
|
else:
|
||||||
args.insert(0,None)
|
args.insert(0, "'All local branches'")
|
||||||
nb = "'All local branches'"
|
|
||||||
|
|
||||||
|
def Execute(self, opt, args):
|
||||||
|
nb = args[0]
|
||||||
err = defaultdict(list)
|
err = defaultdict(list)
|
||||||
success = defaultdict(list)
|
success = defaultdict(list)
|
||||||
all_projects = self.GetProjects(args[1:])
|
all_projects = self.GetProjects(args[1:])
|
||||||
@ -58,7 +59,7 @@ It is equivalent to "git branch -D <branchname>".
|
|||||||
pm.update()
|
pm.update()
|
||||||
|
|
||||||
if opt.all:
|
if opt.all:
|
||||||
branches = project.GetBranches().keys()
|
branches = list(project.GetBranches().keys())
|
||||||
else:
|
else:
|
||||||
branches = [nb]
|
branches = [nb]
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 The Android Open Source Project
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 The Android Open Source Project
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -33,10 +34,11 @@ The command is equivalent to:
|
|||||||
repo forall [<project>...] -c git checkout <branchname>
|
repo forall [<project>...] -c git checkout <branchname>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
if not args:
|
if not args:
|
||||||
self.Usage()
|
self.Usage()
|
||||||
|
|
||||||
|
def Execute(self, opt, args):
|
||||||
nb = args[0]
|
nb = args[0]
|
||||||
err = []
|
err = []
|
||||||
success = []
|
success = []
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2010 The Android Open Source Project
|
# Copyright (C) 2010 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -36,10 +37,11 @@ change id will be added.
|
|||||||
def _Options(self, p):
|
def _Options(self, p):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
if len(args) != 1:
|
if len(args) != 1:
|
||||||
self.Usage()
|
self.Usage()
|
||||||
|
|
||||||
|
def Execute(self, opt, args):
|
||||||
reference = args[0]
|
reference = args[0]
|
||||||
|
|
||||||
p = GitCommand(None,
|
p = GitCommand(None,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -36,5 +37,8 @@ to the Unix 'patch' command.
|
|||||||
help='Paths are relative to the repository root')
|
help='Paths are relative to the repository root')
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
|
ret = 0
|
||||||
for project in self.GetProjects(args):
|
for project in self.GetProjects(args):
|
||||||
project.PrintWorkTreeDiff(opt.absolute)
|
if not project.PrintWorkTreeDiff(opt.absolute):
|
||||||
|
ret = 1
|
||||||
|
return ret
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2014 The Android Open Source Project
|
# Copyright (C) 2014 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -175,10 +176,11 @@ synced and their revisions won't be found.
|
|||||||
self.printText(log)
|
self.printText(log)
|
||||||
self.out.nl()
|
self.out.nl()
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
if not args or len(args) > 2:
|
if not args or len(args) > 2:
|
||||||
self.Usage()
|
self.OptionParser.error('missing manifests to diff')
|
||||||
|
|
||||||
|
def Execute(self, opt, args):
|
||||||
self.out = _Coloring(self.manifest.globalConfig)
|
self.out = _Coloring(self.manifest.globalConfig)
|
||||||
self.printText = self.out.nofmt_printer('text')
|
self.printText = self.out.nofmt_printer('text')
|
||||||
if opt.color:
|
if opt.color:
|
||||||
@ -190,12 +192,12 @@ synced and their revisions won't be found.
|
|||||||
self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText
|
self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText
|
||||||
|
|
||||||
manifest1 = XmlManifest(self.manifest.repodir)
|
manifest1 = XmlManifest(self.manifest.repodir)
|
||||||
manifest1.Override(args[0])
|
manifest1.Override(args[0], load_local_manifests=False)
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
manifest2 = self.manifest
|
manifest2 = self.manifest
|
||||||
else:
|
else:
|
||||||
manifest2 = XmlManifest(self.manifest.repodir)
|
manifest2 = XmlManifest(self.manifest.repodir)
|
||||||
manifest2.Override(args[1])
|
manifest2.Override(args[1], load_local_manifests=False)
|
||||||
|
|
||||||
diff = manifest1.projectsDiff(manifest2)
|
diff = manifest1.projectsDiff(manifest2)
|
||||||
if opt.raw:
|
if opt.raw:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -104,7 +105,7 @@ following <command>.
|
|||||||
|
|
||||||
Example: to list projects:
|
Example: to list projects:
|
||||||
|
|
||||||
%prog% forall -c 'echo $REPO_PROJECT'
|
%prog -c 'echo $REPO_PROJECT'
|
||||||
|
|
||||||
Notice that $REPO_PROJECT is quoted to ensure it is expanded in
|
Notice that $REPO_PROJECT is quoted to ensure it is expanded in
|
||||||
the context of running <command> instead of in the calling shell.
|
the context of running <command> instead of in the calling shell.
|
||||||
@ -176,10 +177,11 @@ without iterating through the remaining projects.
|
|||||||
'worktree': project.worktree,
|
'worktree': project.worktree,
|
||||||
}
|
}
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
if not opt.command:
|
if not opt.command:
|
||||||
self.Usage()
|
self.Usage()
|
||||||
|
|
||||||
|
def Execute(self, opt, args):
|
||||||
cmd = [opt.command[0]]
|
cmd = [opt.command[0]]
|
||||||
|
|
||||||
shell = True
|
shell = True
|
||||||
@ -321,10 +323,10 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
|
|||||||
cwd = project['worktree']
|
cwd = project['worktree']
|
||||||
|
|
||||||
if not os.path.exists(cwd):
|
if not os.path.exists(cwd):
|
||||||
if (opt.project_header and opt.verbose) \
|
if ((opt.project_header and opt.verbose)
|
||||||
or not opt.project_header:
|
or not opt.project_header):
|
||||||
print('skipping %s/' % project['relpath'], file=sys.stderr)
|
print('skipping %s/' % project['relpath'], file=sys.stderr)
|
||||||
return
|
return 1
|
||||||
|
|
||||||
if opt.project_header:
|
if opt.project_header:
|
||||||
stdin = subprocess.PIPE
|
stdin = subprocess.PIPE
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2015 The Android Open Source Project
|
# Copyright (C) 2015 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2015 The Android Open Source Project
|
# Copyright (C) 2015 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 The Android Open Source Project
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -14,15 +15,19 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from color import Coloring
|
from color import Coloring
|
||||||
from command import PagedCommand
|
from command import PagedCommand
|
||||||
|
from error import GitError
|
||||||
from git_command import git_require, GitCommand
|
from git_command import git_require, GitCommand
|
||||||
|
|
||||||
class GrepColoring(Coloring):
|
class GrepColoring(Coloring):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
Coloring.__init__(self, config, 'grep')
|
Coloring.__init__(self, config, 'grep')
|
||||||
self.project = self.printer('project', attr='bold')
|
self.project = self.printer('project', attr='bold')
|
||||||
|
self.fail = self.printer('fail', fg='red')
|
||||||
|
|
||||||
class Grep(PagedCommand):
|
class Grep(PagedCommand):
|
||||||
common = True
|
common = True
|
||||||
@ -183,15 +188,25 @@ contain a line that matches both expressions:
|
|||||||
cmd_argv.extend(opt.revision)
|
cmd_argv.extend(opt.revision)
|
||||||
cmd_argv.append('--')
|
cmd_argv.append('--')
|
||||||
|
|
||||||
|
git_failed = False
|
||||||
bad_rev = False
|
bad_rev = False
|
||||||
have_match = False
|
have_match = False
|
||||||
|
|
||||||
for project in projects:
|
for project in projects:
|
||||||
|
try:
|
||||||
p = GitCommand(project,
|
p = GitCommand(project,
|
||||||
cmd_argv,
|
cmd_argv,
|
||||||
bare=False,
|
bare=False,
|
||||||
capture_stdout=True,
|
capture_stdout=True,
|
||||||
capture_stderr=True)
|
capture_stderr=True)
|
||||||
|
except GitError as e:
|
||||||
|
git_failed = True
|
||||||
|
out.project('--- project %s ---' % project.relpath)
|
||||||
|
out.nl()
|
||||||
|
out.fail('%s', str(e))
|
||||||
|
out.nl()
|
||||||
|
continue
|
||||||
|
|
||||||
if p.Wait() != 0:
|
if p.Wait() != 0:
|
||||||
# no results
|
# no results
|
||||||
#
|
#
|
||||||
@ -201,7 +216,7 @@ contain a line that matches both expressions:
|
|||||||
else:
|
else:
|
||||||
out.project('--- project %s ---' % project.relpath)
|
out.project('--- project %s ---' % project.relpath)
|
||||||
out.nl()
|
out.nl()
|
||||||
out.write("%s", p.stderr)
|
out.fail('%s', p.stderr.strip())
|
||||||
out.nl()
|
out.nl()
|
||||||
continue
|
continue
|
||||||
have_match = True
|
have_match = True
|
||||||
@ -230,7 +245,9 @@ contain a line that matches both expressions:
|
|||||||
for line in r:
|
for line in r:
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
if have_match:
|
if git_failed:
|
||||||
|
sys.exit(1)
|
||||||
|
elif have_match:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif have_rev and bad_rev:
|
elif have_rev and bad_rev:
|
||||||
for r in opt.revision:
|
for r in opt.revision:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -87,7 +88,7 @@ Displays detailed usage information about a command.
|
|||||||
"See 'repo help <command>' for more information on a specific command.\n"
|
"See 'repo help <command>' for more information on a specific command.\n"
|
||||||
"See 'repo help --all' for a complete list of recognized commands.")
|
"See 'repo help --all' for a complete list of recognized commands.")
|
||||||
|
|
||||||
def _PrintCommandHelp(self, cmd):
|
def _PrintCommandHelp(self, cmd, header_prefix=''):
|
||||||
class _Out(Coloring):
|
class _Out(Coloring):
|
||||||
def __init__(self, gc):
|
def __init__(self, gc):
|
||||||
Coloring.__init__(self, gc, 'help')
|
Coloring.__init__(self, gc, 'help')
|
||||||
@ -105,7 +106,7 @@ Displays detailed usage information about a command.
|
|||||||
|
|
||||||
self.nl()
|
self.nl()
|
||||||
|
|
||||||
self.heading('%s', heading)
|
self.heading('%s%s', header_prefix, heading)
|
||||||
self.nl()
|
self.nl()
|
||||||
self.nl()
|
self.nl()
|
||||||
|
|
||||||
@ -123,7 +124,7 @@ Displays detailed usage information about a command.
|
|||||||
|
|
||||||
m = asciidoc_hdr.match(para)
|
m = asciidoc_hdr.match(para)
|
||||||
if m:
|
if m:
|
||||||
self.heading(m.group(1))
|
self.heading('%s%s', header_prefix, m.group(1))
|
||||||
self.nl()
|
self.nl()
|
||||||
self.nl()
|
self.nl()
|
||||||
continue
|
continue
|
||||||
@ -137,14 +138,25 @@ Displays detailed usage information about a command.
|
|||||||
cmd.OptionParser.print_help()
|
cmd.OptionParser.print_help()
|
||||||
out._PrintSection('Description', 'helpDescription')
|
out._PrintSection('Description', 'helpDescription')
|
||||||
|
|
||||||
|
def _PrintAllCommandHelp(self):
|
||||||
|
for name in sorted(self.commands):
|
||||||
|
cmd = self.commands[name]
|
||||||
|
cmd.manifest = self.manifest
|
||||||
|
self._PrintCommandHelp(cmd, header_prefix='[%s] ' % (name,))
|
||||||
|
|
||||||
def _Options(self, p):
|
def _Options(self, p):
|
||||||
p.add_option('-a', '--all',
|
p.add_option('-a', '--all',
|
||||||
dest='show_all', action='store_true',
|
dest='show_all', action='store_true',
|
||||||
help='show the complete list of commands')
|
help='show the complete list of commands')
|
||||||
|
p.add_option('--help-all',
|
||||||
|
dest='show_all_help', action='store_true',
|
||||||
|
help='show the --help of all commands')
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
if len(args) == 0:
|
if len(args) == 0:
|
||||||
if opt.show_all:
|
if opt.show_all_help:
|
||||||
|
self._PrintAllCommandHelp()
|
||||||
|
elif opt.show_all:
|
||||||
self._PrintAllCommands()
|
self._PrintAllCommands()
|
||||||
else:
|
else:
|
||||||
self._PrintCommonCommands()
|
self._PrintCommonCommands()
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2012 The Android Open Source Project
|
# Copyright (C) 2012 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -15,7 +16,6 @@
|
|||||||
|
|
||||||
from command import PagedCommand
|
from command import PagedCommand
|
||||||
from color import Coloring
|
from color import Coloring
|
||||||
from error import NoSuchProjectError
|
|
||||||
from git_refs import R_M
|
from git_refs import R_M
|
||||||
|
|
||||||
class _Coloring(Coloring):
|
class _Coloring(Coloring):
|
||||||
@ -81,10 +81,8 @@ class Info(PagedCommand):
|
|||||||
self.out.nl()
|
self.out.nl()
|
||||||
|
|
||||||
def printDiffInfo(self, args):
|
def printDiffInfo(self, args):
|
||||||
try:
|
# We let exceptions bubble up to main as they'll be well structured.
|
||||||
projs = self.GetProjects(args)
|
projs = self.GetProjects(args)
|
||||||
except NoSuchProjectError:
|
|
||||||
return
|
|
||||||
|
|
||||||
for p in projs:
|
for p in projs:
|
||||||
self.heading("Project: ")
|
self.heading("Project: ")
|
||||||
@ -96,13 +94,19 @@ class Info(PagedCommand):
|
|||||||
self.out.nl()
|
self.out.nl()
|
||||||
|
|
||||||
self.heading("Current revision: ")
|
self.heading("Current revision: ")
|
||||||
self.headtext(p.revisionExpr)
|
self.headtext(p.GetRevisionId())
|
||||||
self.out.nl()
|
self.out.nl()
|
||||||
|
|
||||||
localBranches = p.GetBranches().keys()
|
currentBranch = p.CurrentBranch
|
||||||
|
if currentBranch:
|
||||||
|
self.heading('Current branch: ')
|
||||||
|
self.headtext(currentBranch)
|
||||||
|
self.out.nl()
|
||||||
|
|
||||||
|
localBranches = list(p.GetBranches().keys())
|
||||||
self.heading("Local Branches: ")
|
self.heading("Local Branches: ")
|
||||||
self.redtext(str(len(localBranches)))
|
self.redtext(str(len(localBranches)))
|
||||||
if len(localBranches) > 0:
|
if localBranches:
|
||||||
self.text(" [")
|
self.text(" [")
|
||||||
self.text(", ".join(localBranches))
|
self.text(", ".join(localBranches))
|
||||||
self.text("]")
|
self.text("]")
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -114,6 +115,13 @@ to update the working directory files.
|
|||||||
g.add_option('--depth', type='int', default=None,
|
g.add_option('--depth', type='int', default=None,
|
||||||
dest='depth',
|
dest='depth',
|
||||||
help='create a shallow clone with given depth; see git clone')
|
help='create a shallow clone with given depth; see git clone')
|
||||||
|
g.add_option('--partial-clone', action='store_true',
|
||||||
|
dest='partial_clone',
|
||||||
|
help='perform partial clone (https://git-scm.com/'
|
||||||
|
'docs/gitrepository-layout#_code_partialclone_code)')
|
||||||
|
g.add_option('--clone-filter', action='store', default='blob:none',
|
||||||
|
dest='clone_filter',
|
||||||
|
help='filter for use with --partial-clone [default: %default]')
|
||||||
g.add_option('--archive',
|
g.add_option('--archive',
|
||||||
dest='archive', action='store_true',
|
dest='archive', action='store_true',
|
||||||
help='checkout an archive instead of a git repository for '
|
help='checkout an archive instead of a git repository for '
|
||||||
@ -252,13 +260,25 @@ to update the working directory files.
|
|||||||
'in another location.', file=sys.stderr)
|
'in another location.', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
if opt.partial_clone:
|
||||||
|
if opt.mirror:
|
||||||
|
print('fatal: --mirror and --partial-clone are mutually exclusive',
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
m.config.SetString('repo.partialclone', 'true')
|
||||||
|
if opt.clone_filter:
|
||||||
|
m.config.SetString('repo.clonefilter', opt.clone_filter)
|
||||||
|
else:
|
||||||
|
opt.clone_filter = None
|
||||||
|
|
||||||
if opt.submodules:
|
if opt.submodules:
|
||||||
m.config.SetString('repo.submodules', 'true')
|
m.config.SetString('repo.submodules', 'true')
|
||||||
|
|
||||||
if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet,
|
if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet,
|
||||||
clone_bundle=not opt.no_clone_bundle,
|
clone_bundle=not opt.no_clone_bundle,
|
||||||
current_branch_only=opt.current_branch_only,
|
current_branch_only=opt.current_branch_only,
|
||||||
no_tags=opt.no_tags, submodules=opt.submodules):
|
no_tags=opt.no_tags, submodules=opt.submodules,
|
||||||
|
clone_filter=opt.clone_filter):
|
||||||
r = m.GetRemote(m.remote.name)
|
r = m.GetRemote(m.remote.name)
|
||||||
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
|
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
|
||||||
|
|
||||||
@ -293,7 +313,9 @@ to update the working directory files.
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def _Prompt(self, prompt, value):
|
def _Prompt(self, prompt, value):
|
||||||
sys.stdout.write('%-10s [%s]: ' % (prompt, value))
|
print('%-10s [%s]: ' % (prompt, value), end='')
|
||||||
|
# TODO: When we require Python 3, use flush=True w/print above.
|
||||||
|
sys.stdout.flush()
|
||||||
a = sys.stdin.readline().strip()
|
a = sys.stdin.readline().strip()
|
||||||
if a == '':
|
if a == '':
|
||||||
return value
|
return value
|
||||||
@ -327,7 +349,9 @@ to update the working directory files.
|
|||||||
|
|
||||||
print()
|
print()
|
||||||
print('Your identity is: %s <%s>' % (name, email))
|
print('Your identity is: %s <%s>' % (name, email))
|
||||||
sys.stdout.write('is this correct [y/N]? ')
|
print('is this correct [y/N]? ', end='')
|
||||||
|
# TODO: When we require Python 3, use flush=True w/print above.
|
||||||
|
sys.stdout.flush()
|
||||||
a = sys.stdin.readline().strip().lower()
|
a = sys.stdin.readline().strip().lower()
|
||||||
if a in ('yes', 'y', 't', 'true'):
|
if a in ('yes', 'y', 't', 'true'):
|
||||||
break
|
break
|
||||||
@ -369,7 +393,9 @@ to update the working directory files.
|
|||||||
out.printer(fg='black', attr=c)(' %-6s ', c)
|
out.printer(fg='black', attr=c)(' %-6s ', c)
|
||||||
out.nl()
|
out.nl()
|
||||||
|
|
||||||
sys.stdout.write('Enable color display in this user account (y/N)? ')
|
print('Enable color display in this user account (y/N)? ', end='')
|
||||||
|
# TODO: When we require Python 3, use flush=True w/print above.
|
||||||
|
sys.stdout.flush()
|
||||||
a = sys.stdin.readline().strip().lower()
|
a = sys.stdin.readline().strip().lower()
|
||||||
if a in ('y', 'yes', 't', 'true', 'on'):
|
if a in ('y', 'yes', 't', 'true', 'on'):
|
||||||
gc.SetString('color.ui', 'auto')
|
gc.SetString('color.ui', 'auto')
|
||||||
@ -410,18 +436,17 @@ to update the working directory files.
|
|||||||
print(' rm -r %s/.repo' % self.manifest.topdir)
|
print(' rm -r %s/.repo' % self.manifest.topdir)
|
||||||
print('and try again.')
|
print('and try again.')
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
git_require(MIN_GIT_VERSION, fail=True)
|
|
||||||
|
|
||||||
if opt.reference:
|
if opt.reference:
|
||||||
opt.reference = os.path.expanduser(opt.reference)
|
opt.reference = os.path.expanduser(opt.reference)
|
||||||
|
|
||||||
# Check this here, else manifest will be tagged "not new" and init won't be
|
# Check this here, else manifest will be tagged "not new" and init won't be
|
||||||
# possible anymore without removing the .repo/manifests directory.
|
# possible anymore without removing the .repo/manifests directory.
|
||||||
if opt.archive and opt.mirror:
|
if opt.archive and opt.mirror:
|
||||||
print('fatal: --mirror and --archive cannot be used together.',
|
self.OptionParser.error('--mirror and --archive cannot be used together.')
|
||||||
file=sys.stderr)
|
|
||||||
sys.exit(1)
|
def Execute(self, opt, args):
|
||||||
|
git_require(MIN_GIT_VERSION, fail=True)
|
||||||
|
|
||||||
self._SyncManifest(opt)
|
self._SyncManifest(opt)
|
||||||
self._LinkManifest(opt.manifest_name)
|
self._LinkManifest(opt.manifest_name)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2011 The Android Open Source Project
|
# Copyright (C) 2011 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -48,6 +49,10 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
|
|||||||
dest='path_only', action='store_true',
|
dest='path_only', action='store_true',
|
||||||
help="Display only the path of the repository")
|
help="Display only the path of the repository")
|
||||||
|
|
||||||
|
def ValidateOptions(self, opt, args):
|
||||||
|
if opt.fullpath and opt.name_only:
|
||||||
|
self.OptionParser.error('cannot combine -f and -n')
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
"""List all projects and the associated directories.
|
"""List all projects and the associated directories.
|
||||||
|
|
||||||
@ -59,11 +64,6 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
|
|||||||
opt: The options.
|
opt: The options.
|
||||||
args: Positional args. Can be a list of projects to list, or empty.
|
args: Positional args. Can be a list of projects to list, or empty.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if opt.fullpath and opt.name_only:
|
|
||||||
print('error: cannot combine -f and -n', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not opt.regex:
|
if not opt.regex:
|
||||||
projects = self.GetProjects(args, groups=opt.groups)
|
projects = self.GetProjects(args, groups=opt.groups)
|
||||||
else:
|
else:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 The Android Open Source Project
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -72,14 +73,9 @@ in a Git repository for use during future 'repo init' invocations.
|
|||||||
if opt.output_file != '-':
|
if opt.output_file != '-':
|
||||||
print('Saved manifest to %s' % opt.output_file, file=sys.stderr)
|
print('Saved manifest to %s' % opt.output_file, file=sys.stderr)
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
if args:
|
if args:
|
||||||
self.Usage()
|
self.Usage()
|
||||||
|
|
||||||
if opt.output_file is not None:
|
def Execute(self, opt, args):
|
||||||
self._Output(opt)
|
self._Output(opt)
|
||||||
return
|
|
||||||
|
|
||||||
print('error: no operation to perform', file=sys.stderr)
|
|
||||||
print('error: see repo help manifest', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2012 The Android Open Source Project
|
# Copyright (C) 2012 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2010 The Android Open Source Project
|
# Copyright (C) 2010 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -16,9 +17,18 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from color import Coloring
|
||||||
from command import Command
|
from command import Command
|
||||||
from git_command import GitCommand
|
from git_command import GitCommand
|
||||||
|
|
||||||
|
|
||||||
|
class RebaseColoring(Coloring):
|
||||||
|
def __init__(self, config):
|
||||||
|
Coloring.__init__(self, config, 'rebase')
|
||||||
|
self.project = self.printer('project', attr='bold')
|
||||||
|
self.fail = self.printer('fail', fg='red')
|
||||||
|
|
||||||
|
|
||||||
class Rebase(Command):
|
class Rebase(Command):
|
||||||
common = True
|
common = True
|
||||||
helpSummary = "Rebase local branches on upstream branch"
|
helpSummary = "Rebase local branches on upstream branch"
|
||||||
@ -36,6 +46,9 @@ branch but need to incorporate new upstream changes "underneath" them.
|
|||||||
dest="interactive", action="store_true",
|
dest="interactive", action="store_true",
|
||||||
help="interactive rebase (single project only)")
|
help="interactive rebase (single project only)")
|
||||||
|
|
||||||
|
p.add_option('--fail-fast',
|
||||||
|
dest='fail_fast', action='store_true',
|
||||||
|
help='Stop rebasing after first error is hit')
|
||||||
p.add_option('-f', '--force-rebase',
|
p.add_option('-f', '--force-rebase',
|
||||||
dest='force_rebase', action='store_true',
|
dest='force_rebase', action='store_true',
|
||||||
help='Pass --force-rebase to git rebase')
|
help='Pass --force-rebase to git rebase')
|
||||||
@ -70,15 +83,38 @@ branch but need to incorporate new upstream changes "underneath" them.
|
|||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
print('note: project %s is mapped to more than one path' % (args[0],),
|
print('note: project %s is mapped to more than one path' % (args[0],),
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
return -1
|
return 1
|
||||||
|
|
||||||
|
# Setup the common git rebase args that we use for all projects.
|
||||||
|
common_args = ['rebase']
|
||||||
|
if opt.whitespace:
|
||||||
|
common_args.append('--whitespace=%s' % opt.whitespace)
|
||||||
|
if opt.quiet:
|
||||||
|
common_args.append('--quiet')
|
||||||
|
if opt.force_rebase:
|
||||||
|
common_args.append('--force-rebase')
|
||||||
|
if opt.no_ff:
|
||||||
|
common_args.append('--no-ff')
|
||||||
|
if opt.autosquash:
|
||||||
|
common_args.append('--autosquash')
|
||||||
|
if opt.interactive:
|
||||||
|
common_args.append('-i')
|
||||||
|
|
||||||
|
config = self.manifest.manifestProject.config
|
||||||
|
out = RebaseColoring(config)
|
||||||
|
out.redirect(sys.stdout)
|
||||||
|
|
||||||
|
ret = 0
|
||||||
for project in all_projects:
|
for project in all_projects:
|
||||||
|
if ret and opt.fail_fast:
|
||||||
|
break
|
||||||
|
|
||||||
cb = project.CurrentBranch
|
cb = project.CurrentBranch
|
||||||
if not cb:
|
if not cb:
|
||||||
if one_project:
|
if one_project:
|
||||||
print("error: project %s has a detached HEAD" % project.relpath,
|
print("error: project %s has a detached HEAD" % project.relpath,
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
return -1
|
return 1
|
||||||
# ignore branches with detatched HEADs
|
# ignore branches with detatched HEADs
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -87,38 +123,21 @@ branch but need to incorporate new upstream changes "underneath" them.
|
|||||||
if one_project:
|
if one_project:
|
||||||
print("error: project %s does not track any remote branches"
|
print("error: project %s does not track any remote branches"
|
||||||
% project.relpath, file=sys.stderr)
|
% project.relpath, file=sys.stderr)
|
||||||
return -1
|
return 1
|
||||||
# ignore branches without remotes
|
# ignore branches without remotes
|
||||||
continue
|
continue
|
||||||
|
|
||||||
args = ["rebase"]
|
args = common_args[:]
|
||||||
|
|
||||||
if opt.whitespace:
|
|
||||||
args.append('--whitespace=%s' % opt.whitespace)
|
|
||||||
|
|
||||||
if opt.quiet:
|
|
||||||
args.append('--quiet')
|
|
||||||
|
|
||||||
if opt.force_rebase:
|
|
||||||
args.append('--force-rebase')
|
|
||||||
|
|
||||||
if opt.no_ff:
|
|
||||||
args.append('--no-ff')
|
|
||||||
|
|
||||||
if opt.autosquash:
|
|
||||||
args.append('--autosquash')
|
|
||||||
|
|
||||||
if opt.interactive:
|
|
||||||
args.append("-i")
|
|
||||||
|
|
||||||
if opt.onto_manifest:
|
if opt.onto_manifest:
|
||||||
args.append('--onto')
|
args.append('--onto')
|
||||||
args.append(project.revisionExpr)
|
args.append(project.revisionExpr)
|
||||||
|
|
||||||
args.append(upbranch.LocalMerge)
|
args.append(upbranch.LocalMerge)
|
||||||
|
|
||||||
print('# %s: rebasing %s -> %s'
|
out.project('project %s: rebasing %s -> %s',
|
||||||
% (project.relpath, cb, upbranch.LocalMerge), file=sys.stderr)
|
project.relpath, cb, upbranch.LocalMerge)
|
||||||
|
out.nl()
|
||||||
|
out.flush()
|
||||||
|
|
||||||
needs_stash = False
|
needs_stash = False
|
||||||
if opt.auto_stash:
|
if opt.auto_stash:
|
||||||
@ -130,13 +149,21 @@ branch but need to incorporate new upstream changes "underneath" them.
|
|||||||
stash_args = ["stash"]
|
stash_args = ["stash"]
|
||||||
|
|
||||||
if GitCommand(project, stash_args).Wait() != 0:
|
if GitCommand(project, stash_args).Wait() != 0:
|
||||||
return -1
|
ret += 1
|
||||||
|
continue
|
||||||
|
|
||||||
if GitCommand(project, args).Wait() != 0:
|
if GitCommand(project, args).Wait() != 0:
|
||||||
return -1
|
ret += 1
|
||||||
|
continue
|
||||||
|
|
||||||
if needs_stash:
|
if needs_stash:
|
||||||
stash_args.append('pop')
|
stash_args.append('pop')
|
||||||
stash_args.append('--quiet')
|
stash_args.append('--quiet')
|
||||||
if GitCommand(project, stash_args).Wait() != 0:
|
if GitCommand(project, stash_args).Wait() != 0:
|
||||||
return -1
|
ret += 1
|
||||||
|
|
||||||
|
if ret:
|
||||||
|
out.fail('%i projects had errors', ret)
|
||||||
|
out.nl()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 The Android Open Source Project
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2010 The Android Open Source Project
|
# Copyright (C) 2010 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -39,16 +40,21 @@ revision specified in the manifest.
|
|||||||
p.add_option('--all',
|
p.add_option('--all',
|
||||||
dest='all', action='store_true',
|
dest='all', action='store_true',
|
||||||
help='begin branch in all projects')
|
help='begin branch in all projects')
|
||||||
|
p.add_option('-r', '--rev', '--revision', dest='revision',
|
||||||
|
help='point branch at this revision instead of upstream')
|
||||||
|
p.add_option('--head', dest='revision', action='store_const', const='HEAD',
|
||||||
|
help='abbreviation for --rev HEAD')
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
if not args:
|
if not args:
|
||||||
self.Usage()
|
self.Usage()
|
||||||
|
|
||||||
nb = args[0]
|
nb = args[0]
|
||||||
if not git.check_ref_format('heads/%s' % nb):
|
if not git.check_ref_format('heads/%s' % nb):
|
||||||
print("error: '%s' is not a valid name" % nb, file=sys.stderr)
|
self.OptionParser.error("'%s' is not a valid name" % nb)
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
def Execute(self, opt, args):
|
||||||
|
nb = args[0]
|
||||||
err = []
|
err = []
|
||||||
projects = []
|
projects = []
|
||||||
if not opt.all:
|
if not opt.all:
|
||||||
@ -106,7 +112,8 @@ revision specified in the manifest.
|
|||||||
else:
|
else:
|
||||||
branch_merge = self.manifest.default.revisionExpr
|
branch_merge = self.manifest.default.revisionExpr
|
||||||
|
|
||||||
if not project.StartBranch(nb, branch_merge=branch_merge):
|
if not project.StartBranch(
|
||||||
|
nb, branch_merge=branch_merge, revision=opt.revision):
|
||||||
err.append(project)
|
err.append(project)
|
||||||
pm.end()
|
pm.end()
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -13,6 +14,8 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from command import PagedCommand
|
from command import PagedCommand
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
332
subcmds/sync.py
332
subcmds/sync.py
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -84,6 +85,9 @@ class _FetchError(Exception):
|
|||||||
"""Internal error thrown in _FetchHelper() when we don't want stack trace."""
|
"""Internal error thrown in _FetchHelper() when we don't want stack trace."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class _CheckoutError(Exception):
|
||||||
|
"""Internal error thrown in _CheckoutOne() when we don't want stack trace."""
|
||||||
|
|
||||||
class Sync(Command, MirrorSafeCommand):
|
class Sync(Command, MirrorSafeCommand):
|
||||||
jobs = 1
|
jobs = 1
|
||||||
common = True
|
common = True
|
||||||
@ -128,8 +132,8 @@ from the user's .netrc file.
|
|||||||
if the manifest server specified in the manifest file already includes
|
if the manifest server specified in the manifest file already includes
|
||||||
credentials.
|
credentials.
|
||||||
|
|
||||||
The -f/--force-broken option can be used to proceed with syncing
|
By default, all projects will be synced. The --fail-fast option can be used
|
||||||
other projects if a project sync fails.
|
to halt syncing as soon as possible when the the first project fails to sync.
|
||||||
|
|
||||||
The --force-sync option can be used to overwrite existing git
|
The --force-sync option can be used to overwrite existing git
|
||||||
directories if they have previously been linked to a different
|
directories if they have previously been linked to a different
|
||||||
@ -196,7 +200,10 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
p.add_option('-f', '--force-broken',
|
p.add_option('-f', '--force-broken',
|
||||||
dest='force_broken', action='store_true',
|
dest='force_broken', action='store_true',
|
||||||
help="continue sync even if a project fails to sync")
|
help='obsolete option (to be deleted in the future)')
|
||||||
|
p.add_option('--fail-fast',
|
||||||
|
dest='fail_fast', action='store_true',
|
||||||
|
help='stop syncing after first error is hit')
|
||||||
p.add_option('--force-sync',
|
p.add_option('--force-sync',
|
||||||
dest='force_sync', action='store_true',
|
dest='force_sync', action='store_true',
|
||||||
help="overwrite an existing git directory if it needs to "
|
help="overwrite an existing git directory if it needs to "
|
||||||
@ -265,7 +272,7 @@ later is required to fix a server side protocol bug.
|
|||||||
help=SUPPRESS_HELP)
|
help=SUPPRESS_HELP)
|
||||||
|
|
||||||
def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
|
def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
|
||||||
"""Main function of the fetch threads when jobs are > 1.
|
"""Main function of the fetch threads.
|
||||||
|
|
||||||
Delegates most of the work to _FetchHelper.
|
Delegates most of the work to _FetchHelper.
|
||||||
|
|
||||||
@ -280,12 +287,13 @@ later is required to fix a server side protocol bug.
|
|||||||
try:
|
try:
|
||||||
for project in projects:
|
for project in projects:
|
||||||
success = self._FetchHelper(opt, project, *args, **kwargs)
|
success = self._FetchHelper(opt, project, *args, **kwargs)
|
||||||
if not success and not opt.force_broken:
|
if not success and opt.fail_fast:
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
sem.release()
|
sem.release()
|
||||||
|
|
||||||
def _FetchHelper(self, opt, project, lock, fetched, pm, err_event):
|
def _FetchHelper(self, opt, project, lock, fetched, pm, err_event,
|
||||||
|
clone_filter):
|
||||||
"""Fetch git objects for a single project.
|
"""Fetch git objects for a single project.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -299,6 +307,7 @@ later is required to fix a server side protocol bug.
|
|||||||
lock held).
|
lock held).
|
||||||
err_event: We'll set this event in the case of an error (after printing
|
err_event: We'll set this event in the case of an error (after printing
|
||||||
out info about the error).
|
out info about the error).
|
||||||
|
clone_filter: Filter for use in a partial clone.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Whether the fetch was successful.
|
Whether the fetch was successful.
|
||||||
@ -311,7 +320,6 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
# Encapsulate everything in a try/except/finally so that:
|
# Encapsulate everything in a try/except/finally so that:
|
||||||
# - We always set err_event in the case of an exception.
|
# - We always set err_event in the case of an exception.
|
||||||
# - We always make sure we call sem.release().
|
|
||||||
# - We always make sure we unlock the lock if we locked it.
|
# - We always make sure we unlock the lock if we locked it.
|
||||||
start = time.time()
|
start = time.time()
|
||||||
success = False
|
success = False
|
||||||
@ -324,7 +332,8 @@ later is required to fix a server side protocol bug.
|
|||||||
clone_bundle=not opt.no_clone_bundle,
|
clone_bundle=not opt.no_clone_bundle,
|
||||||
no_tags=opt.no_tags, archive=self.manifest.IsArchive,
|
no_tags=opt.no_tags, archive=self.manifest.IsArchive,
|
||||||
optimized_fetch=opt.optimized_fetch,
|
optimized_fetch=opt.optimized_fetch,
|
||||||
prune=opt.prune)
|
prune=opt.prune,
|
||||||
|
clone_filter=clone_filter)
|
||||||
self._fetch_times.Set(project, time.time() - start)
|
self._fetch_times.Set(project, time.time() - start)
|
||||||
|
|
||||||
# Lock around all the rest of the code, since printing, updating a set
|
# Lock around all the rest of the code, since printing, updating a set
|
||||||
@ -337,10 +346,7 @@ later is required to fix a server side protocol bug.
|
|||||||
print('error: Cannot fetch %s from %s'
|
print('error: Cannot fetch %s from %s'
|
||||||
% (project.name, project.remote.url),
|
% (project.name, project.remote.url),
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
if opt.force_broken:
|
if opt.fail_fast:
|
||||||
print('warn: --force-broken, continuing to sync',
|
|
||||||
file=sys.stderr)
|
|
||||||
else:
|
|
||||||
raise _FetchError()
|
raise _FetchError()
|
||||||
|
|
||||||
fetched.add(project.gitdir)
|
fetched.add(project.gitdir)
|
||||||
@ -378,7 +384,7 @@ later is required to fix a server side protocol bug.
|
|||||||
for project_list in objdir_project_map.values():
|
for project_list in objdir_project_map.values():
|
||||||
# Check for any errors before running any more tasks.
|
# Check for any errors before running any more tasks.
|
||||||
# ...we'll let existing threads finish, though.
|
# ...we'll let existing threads finish, though.
|
||||||
if err_event.isSet() and not opt.force_broken:
|
if err_event.isSet() and opt.fail_fast:
|
||||||
break
|
break
|
||||||
|
|
||||||
sem.acquire()
|
sem.acquire()
|
||||||
@ -388,7 +394,8 @@ later is required to fix a server side protocol bug.
|
|||||||
lock=lock,
|
lock=lock,
|
||||||
fetched=fetched,
|
fetched=fetched,
|
||||||
pm=pm,
|
pm=pm,
|
||||||
err_event=err_event)
|
err_event=err_event,
|
||||||
|
clone_filter=self.manifest.CloneFilter)
|
||||||
if self.jobs > 1:
|
if self.jobs > 1:
|
||||||
t = _threading.Thread(target = self._FetchProjectList,
|
t = _threading.Thread(target = self._FetchProjectList,
|
||||||
kwargs = kwargs)
|
kwargs = kwargs)
|
||||||
@ -403,7 +410,7 @@ later is required to fix a server side protocol bug.
|
|||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
# If we saw an error, exit with code 1 so that other scripts can check.
|
# If we saw an error, exit with code 1 so that other scripts can check.
|
||||||
if err_event.isSet() and not opt.force_broken:
|
if err_event.isSet() and opt.fail_fast:
|
||||||
print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
|
print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -415,6 +422,146 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
return fetched
|
return fetched
|
||||||
|
|
||||||
|
def _CheckoutWorker(self, opt, sem, project, *args, **kwargs):
|
||||||
|
"""Main function of the fetch threads.
|
||||||
|
|
||||||
|
Delegates most of the work to _CheckoutOne.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
opt: Program options returned from optparse. See _Options().
|
||||||
|
projects: Projects to fetch.
|
||||||
|
sem: We'll release() this semaphore when we exit so that another thread
|
||||||
|
can be started up.
|
||||||
|
*args, **kwargs: Remaining arguments to pass to _CheckoutOne. See the
|
||||||
|
_CheckoutOne docstring for details.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self._CheckoutOne(opt, project, *args, **kwargs)
|
||||||
|
finally:
|
||||||
|
sem.release()
|
||||||
|
|
||||||
|
def _CheckoutOne(self, opt, project, lock, pm, err_event):
|
||||||
|
"""Checkout work tree for one project
|
||||||
|
|
||||||
|
Args:
|
||||||
|
opt: Program options returned from optparse. See _Options().
|
||||||
|
project: Project object for the project to checkout.
|
||||||
|
lock: Lock for accessing objects that are shared amongst multiple
|
||||||
|
_CheckoutWorker() threads.
|
||||||
|
pm: Instance of a Project object. We will call pm.update() (with our
|
||||||
|
lock held).
|
||||||
|
err_event: We'll set this event in the case of an error (after printing
|
||||||
|
out info about the error).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether the fetch was successful.
|
||||||
|
"""
|
||||||
|
# We'll set to true once we've locked the lock.
|
||||||
|
did_lock = False
|
||||||
|
|
||||||
|
if not opt.quiet:
|
||||||
|
print('Checking out project %s' % project.name)
|
||||||
|
|
||||||
|
# Encapsulate everything in a try/except/finally so that:
|
||||||
|
# - We always set err_event in the case of an exception.
|
||||||
|
# - We always make sure we unlock the lock if we locked it.
|
||||||
|
start = time.time()
|
||||||
|
syncbuf = SyncBuffer(self.manifest.manifestProject.config,
|
||||||
|
detach_head=opt.detach_head)
|
||||||
|
success = False
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
|
||||||
|
success = syncbuf.Finish()
|
||||||
|
|
||||||
|
# Lock around all the rest of the code, since printing, updating a set
|
||||||
|
# and Progress.update() are not thread safe.
|
||||||
|
lock.acquire()
|
||||||
|
did_lock = True
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
err_event.set()
|
||||||
|
print('error: Cannot checkout %s' % (project.name),
|
||||||
|
file=sys.stderr)
|
||||||
|
raise _CheckoutError()
|
||||||
|
|
||||||
|
pm.update()
|
||||||
|
except _CheckoutError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print('error: Cannot checkout %s: %s: %s' %
|
||||||
|
(project.name, type(e).__name__, str(e)),
|
||||||
|
file=sys.stderr)
|
||||||
|
err_event.set()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if did_lock:
|
||||||
|
lock.release()
|
||||||
|
finish = time.time()
|
||||||
|
self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
|
||||||
|
start, finish, success)
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def _Checkout(self, all_projects, opt):
|
||||||
|
"""Checkout projects listed in all_projects
|
||||||
|
|
||||||
|
Args:
|
||||||
|
all_projects: List of all projects that should be checked out.
|
||||||
|
opt: Program options returned from optparse. See _Options().
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Perform checkouts in multiple threads when we are using partial clone.
|
||||||
|
# Without partial clone, all needed git objects are already downloaded,
|
||||||
|
# in this situation it's better to use only one process because the checkout
|
||||||
|
# would be mostly disk I/O; with partial clone, the objects are only
|
||||||
|
# downloaded when demanded (at checkout time), which is similar to the
|
||||||
|
# Sync_NetworkHalf case and parallelism would be helpful.
|
||||||
|
if self.manifest.CloneFilter:
|
||||||
|
syncjobs = self.jobs
|
||||||
|
else:
|
||||||
|
syncjobs = 1
|
||||||
|
|
||||||
|
lock = _threading.Lock()
|
||||||
|
pm = Progress('Syncing work tree', len(all_projects))
|
||||||
|
|
||||||
|
threads = set()
|
||||||
|
sem = _threading.Semaphore(syncjobs)
|
||||||
|
err_event = _threading.Event()
|
||||||
|
|
||||||
|
for project in all_projects:
|
||||||
|
# Check for any errors before running any more tasks.
|
||||||
|
# ...we'll let existing threads finish, though.
|
||||||
|
if err_event.isSet() and opt.fail_fast:
|
||||||
|
break
|
||||||
|
|
||||||
|
sem.acquire()
|
||||||
|
if project.worktree:
|
||||||
|
kwargs = dict(opt=opt,
|
||||||
|
sem=sem,
|
||||||
|
project=project,
|
||||||
|
lock=lock,
|
||||||
|
pm=pm,
|
||||||
|
err_event=err_event)
|
||||||
|
if syncjobs > 1:
|
||||||
|
t = _threading.Thread(target=self._CheckoutWorker,
|
||||||
|
kwargs=kwargs)
|
||||||
|
# Ensure that Ctrl-C will not freeze the repo process.
|
||||||
|
t.daemon = True
|
||||||
|
threads.add(t)
|
||||||
|
t.start()
|
||||||
|
else:
|
||||||
|
self._CheckoutWorker(**kwargs)
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
pm.end()
|
||||||
|
# If we saw an error, exit with code 1 so that other scripts can check.
|
||||||
|
if err_event.isSet():
|
||||||
|
print('\nerror: Exited sync due to checkout errors', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def _GCProjects(self, projects):
|
def _GCProjects(self, projects):
|
||||||
gc_gitdirs = {}
|
gc_gitdirs = {}
|
||||||
for project in projects:
|
for project in projects:
|
||||||
@ -435,7 +582,7 @@ later is required to fix a server side protocol bug.
|
|||||||
bare_git.gc('--auto')
|
bare_git.gc('--auto')
|
||||||
return
|
return
|
||||||
|
|
||||||
config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
|
config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
|
||||||
|
|
||||||
threads = set()
|
threads = set()
|
||||||
sem = _threading.Semaphore(jobs)
|
sem = _threading.Semaphore(jobs)
|
||||||
@ -488,7 +635,7 @@ later is required to fix a server side protocol bug.
|
|||||||
print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr)
|
print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr)
|
||||||
print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
|
print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
|
||||||
print(' remove manually, then run sync again', file=sys.stderr)
|
print(' remove manually, then run sync again', file=sys.stderr)
|
||||||
return -1
|
return 1
|
||||||
|
|
||||||
# Delete everything under the worktree, except for directories that contain
|
# Delete everything under the worktree, except for directories that contain
|
||||||
# another git project
|
# another git project
|
||||||
@ -522,7 +669,7 @@ later is required to fix a server side protocol bug.
|
|||||||
if failed:
|
if failed:
|
||||||
print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
|
print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
|
||||||
print(' remove manually, then run sync again', file=sys.stderr)
|
print(' remove manually, then run sync again', file=sys.stderr)
|
||||||
return -1
|
return 1
|
||||||
|
|
||||||
# Try deleting parent dirs if they are empty
|
# Try deleting parent dirs if they are empty
|
||||||
project_dir = path
|
project_dir = path
|
||||||
@ -579,9 +726,9 @@ later is required to fix a server side protocol bug.
|
|||||||
'are present' % project.relpath, file=sys.stderr)
|
'are present' % project.relpath, file=sys.stderr)
|
||||||
print(' commit changes, then run sync again',
|
print(' commit changes, then run sync again',
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
return -1
|
return 1
|
||||||
elif self._DeleteProject(project.worktree):
|
elif self._DeleteProject(project.worktree):
|
||||||
return -1
|
return 1
|
||||||
|
|
||||||
new_project_paths.sort()
|
new_project_paths.sort()
|
||||||
fd = open(file_path, 'w')
|
fd = open(file_path, 'w')
|
||||||
@ -592,43 +739,7 @@ later is required to fix a server side protocol bug.
|
|||||||
fd.close()
|
fd.close()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
|
||||||
if opt.jobs:
|
|
||||||
self.jobs = opt.jobs
|
|
||||||
if self.jobs > 1:
|
|
||||||
soft_limit, _ = _rlimit_nofile()
|
|
||||||
self.jobs = min(self.jobs, (soft_limit - 5) / 3)
|
|
||||||
|
|
||||||
if opt.network_only and opt.detach_head:
|
|
||||||
print('error: cannot combine -n and -d', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
if opt.network_only and opt.local_only:
|
|
||||||
print('error: cannot combine -n and -l', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
if opt.manifest_name and opt.smart_sync:
|
|
||||||
print('error: cannot combine -m and -s', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
if opt.manifest_name and opt.smart_tag:
|
|
||||||
print('error: cannot combine -m and -t', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
if opt.manifest_server_username or opt.manifest_server_password:
|
|
||||||
if not (opt.smart_sync or opt.smart_tag):
|
|
||||||
print('error: -u and -p may only be combined with -s or -t',
|
|
||||||
file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
if None in [opt.manifest_server_username, opt.manifest_server_password]:
|
|
||||||
print('error: both -u and -p must be given', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if opt.manifest_name:
|
|
||||||
self.manifest.Override(opt.manifest_name)
|
|
||||||
|
|
||||||
manifest_name = opt.manifest_name
|
|
||||||
smart_sync_manifest_name = "smart_sync_override.xml"
|
|
||||||
smart_sync_manifest_path = os.path.join(
|
|
||||||
self.manifest.manifestProject.worktree, smart_sync_manifest_name)
|
|
||||||
|
|
||||||
if opt.smart_sync or opt.smart_tag:
|
|
||||||
if not self.manifest.manifest_server:
|
if not self.manifest.manifest_server:
|
||||||
print('error: cannot smart sync: no manifest server defined in '
|
print('error: cannot smart sync: no manifest server defined in '
|
||||||
'manifest', file=sys.stderr)
|
'manifest', file=sys.stderr)
|
||||||
@ -696,7 +807,7 @@ later is required to fix a server side protocol bug.
|
|||||||
[success, manifest_str] = server.GetManifest(opt.smart_tag)
|
[success, manifest_str] = server.GetManifest(opt.smart_tag)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
manifest_name = smart_sync_manifest_name
|
manifest_name = os.path.basename(smart_sync_manifest_path)
|
||||||
try:
|
try:
|
||||||
f = open(smart_sync_manifest_path, 'w')
|
f = open(smart_sync_manifest_path, 'w')
|
||||||
try:
|
try:
|
||||||
@ -722,7 +833,71 @@ later is required to fix a server side protocol bug.
|
|||||||
% (self.manifest.manifest_server, e.errcode, e.errmsg),
|
% (self.manifest.manifest_server, e.errcode, e.errmsg),
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else: # Not smart sync or smart tag mode
|
|
||||||
|
return manifest_name
|
||||||
|
|
||||||
|
def _UpdateManifestProject(self, opt, mp, manifest_name):
|
||||||
|
"""Fetch & update the local manifest project."""
|
||||||
|
if not opt.local_only:
|
||||||
|
start = time.time()
|
||||||
|
success = mp.Sync_NetworkHalf(quiet=opt.quiet,
|
||||||
|
current_branch_only=opt.current_branch_only,
|
||||||
|
no_tags=opt.no_tags,
|
||||||
|
optimized_fetch=opt.optimized_fetch,
|
||||||
|
submodules=self.manifest.HasSubmodules,
|
||||||
|
clone_filter=self.manifest.CloneFilter)
|
||||||
|
finish = time.time()
|
||||||
|
self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
|
||||||
|
start, finish, success)
|
||||||
|
|
||||||
|
if mp.HasChanges:
|
||||||
|
syncbuf = SyncBuffer(mp.config)
|
||||||
|
start = time.time()
|
||||||
|
mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
|
||||||
|
clean = syncbuf.Finish()
|
||||||
|
self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
|
||||||
|
start, time.time(), clean)
|
||||||
|
if not clean:
|
||||||
|
sys.exit(1)
|
||||||
|
self._ReloadManifest(opt.manifest_name)
|
||||||
|
if opt.jobs is None:
|
||||||
|
self.jobs = self.manifest.default.sync_j
|
||||||
|
|
||||||
|
def ValidateOptions(self, opt, args):
|
||||||
|
if opt.force_broken:
|
||||||
|
print('warning: -f/--force-broken is now the default behavior, and the '
|
||||||
|
'options are deprecated', file=sys.stderr)
|
||||||
|
if opt.network_only and opt.detach_head:
|
||||||
|
self.OptionParser.error('cannot combine -n and -d')
|
||||||
|
if opt.network_only and opt.local_only:
|
||||||
|
self.OptionParser.error('cannot combine -n and -l')
|
||||||
|
if opt.manifest_name and opt.smart_sync:
|
||||||
|
self.OptionParser.error('cannot combine -m and -s')
|
||||||
|
if opt.manifest_name and opt.smart_tag:
|
||||||
|
self.OptionParser.error('cannot combine -m and -t')
|
||||||
|
if opt.manifest_server_username or opt.manifest_server_password:
|
||||||
|
if not (opt.smart_sync or opt.smart_tag):
|
||||||
|
self.OptionParser.error('-u and -p may only be combined with -s or -t')
|
||||||
|
if None in [opt.manifest_server_username, opt.manifest_server_password]:
|
||||||
|
self.OptionParser.error('both -u and -p must be given')
|
||||||
|
|
||||||
|
def Execute(self, opt, args):
|
||||||
|
if opt.jobs:
|
||||||
|
self.jobs = opt.jobs
|
||||||
|
if self.jobs > 1:
|
||||||
|
soft_limit, _ = _rlimit_nofile()
|
||||||
|
self.jobs = min(self.jobs, (soft_limit - 5) // 3)
|
||||||
|
|
||||||
|
if opt.manifest_name:
|
||||||
|
self.manifest.Override(opt.manifest_name)
|
||||||
|
|
||||||
|
manifest_name = opt.manifest_name
|
||||||
|
smart_sync_manifest_path = os.path.join(
|
||||||
|
self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
|
||||||
|
|
||||||
|
if opt.smart_sync or opt.smart_tag:
|
||||||
|
manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
|
||||||
|
else:
|
||||||
if os.path.isfile(smart_sync_manifest_path):
|
if os.path.isfile(smart_sync_manifest_path):
|
||||||
try:
|
try:
|
||||||
platform_utils.remove(smart_sync_manifest_path)
|
platform_utils.remove(smart_sync_manifest_path)
|
||||||
@ -739,29 +914,7 @@ later is required to fix a server side protocol bug.
|
|||||||
if opt.repo_upgraded:
|
if opt.repo_upgraded:
|
||||||
_PostRepoUpgrade(self.manifest, quiet=opt.quiet)
|
_PostRepoUpgrade(self.manifest, quiet=opt.quiet)
|
||||||
|
|
||||||
if not opt.local_only:
|
self._UpdateManifestProject(opt, mp, manifest_name)
|
||||||
start = time.time()
|
|
||||||
success = mp.Sync_NetworkHalf(quiet=opt.quiet,
|
|
||||||
current_branch_only=opt.current_branch_only,
|
|
||||||
no_tags=opt.no_tags,
|
|
||||||
optimized_fetch=opt.optimized_fetch,
|
|
||||||
submodules=self.manifest.HasSubmodules)
|
|
||||||
finish = time.time()
|
|
||||||
self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
|
|
||||||
start, finish, success)
|
|
||||||
|
|
||||||
if mp.HasChanges:
|
|
||||||
syncbuf = SyncBuffer(mp.config)
|
|
||||||
start = time.time()
|
|
||||||
mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
|
|
||||||
clean = syncbuf.Finish()
|
|
||||||
self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
|
|
||||||
start, time.time(), clean)
|
|
||||||
if not clean:
|
|
||||||
sys.exit(1)
|
|
||||||
self._ReloadManifest(manifest_name)
|
|
||||||
if opt.jobs is None:
|
|
||||||
self.jobs = self.manifest.default.sync_j
|
|
||||||
|
|
||||||
if self.gitc_manifest:
|
if self.gitc_manifest:
|
||||||
gitc_manifest_projects = self.GetProjects(args,
|
gitc_manifest_projects = self.GetProjects(args,
|
||||||
@ -845,20 +998,7 @@ later is required to fix a server side protocol bug.
|
|||||||
if self.UpdateProjectList(opt):
|
if self.UpdateProjectList(opt):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
syncbuf = SyncBuffer(mp.config,
|
self._Checkout(all_projects, opt)
|
||||||
detach_head = opt.detach_head)
|
|
||||||
pm = Progress('Syncing work tree', len(all_projects))
|
|
||||||
for project in all_projects:
|
|
||||||
pm.update()
|
|
||||||
if project.worktree:
|
|
||||||
start = time.time()
|
|
||||||
project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
|
|
||||||
self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
|
|
||||||
start, time.time(), syncbuf.Recently())
|
|
||||||
pm.end()
|
|
||||||
print(file=sys.stderr)
|
|
||||||
if not syncbuf.Finish():
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# If there's a notice that's supposed to print at the end of the sync, print
|
# If there's a notice that's supposed to print at the end of the sync, print
|
||||||
# it now...
|
# it now...
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 The Android Open Source Project
|
# Copyright (C) 2008 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -220,7 +221,9 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
for commit in commit_list:
|
for commit in commit_list:
|
||||||
print(' %s' % commit)
|
print(' %s' % commit)
|
||||||
|
|
||||||
sys.stdout.write('to %s (y/N)? ' % remote.review)
|
print('to %s (y/N)? ' % remote.review, end='')
|
||||||
|
# TODO: When we require Python 3, use flush=True w/print above.
|
||||||
|
sys.stdout.flush()
|
||||||
answer = sys.stdin.readline().strip().lower()
|
answer = sys.stdin.readline().strip().lower()
|
||||||
answer = answer in ('y', 'yes', '1', 'true', 't')
|
answer = answer in ('y', 'yes', '1', 'true', 't')
|
||||||
|
|
||||||
@ -359,10 +362,13 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
|
|
||||||
# if they want to auto upload, let's not ask because it could be automated
|
# if they want to auto upload, let's not ask because it could be automated
|
||||||
if answer is None:
|
if answer is None:
|
||||||
sys.stdout.write('Uncommitted changes in ' + branch.project.name)
|
print()
|
||||||
sys.stdout.write(' (did you forget to amend?):\n')
|
print('Uncommitted changes in %s (did you forget to amend?):'
|
||||||
sys.stdout.write('\n'.join(changes) + '\n')
|
% branch.project.name)
|
||||||
sys.stdout.write('Continue uploading? (y/N) ')
|
print('\n'.join(changes))
|
||||||
|
print('Continue uploading? (y/N) ', end='')
|
||||||
|
# TODO: When we require Python 3, use flush=True w/print above.
|
||||||
|
sys.stdout.flush()
|
||||||
a = sys.stdin.readline().strip().lower()
|
a = sys.stdin.readline().strip().lower()
|
||||||
if a not in ('y', 'yes', 't', 'true', 'on'):
|
if a not in ('y', 'yes', 't', 'true', 'on'):
|
||||||
print("skipping upload", file=sys.stderr)
|
print("skipping upload", file=sys.stderr)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 The Android Open Source Project
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -16,7 +17,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
from command import Command, MirrorSafeCommand
|
from command import Command, MirrorSafeCommand
|
||||||
from git_command import git
|
from git_command import git, RepoSourceVersion, user_agent
|
||||||
from git_refs import HEAD
|
from git_refs import HEAD
|
||||||
|
|
||||||
class Version(Command, MirrorSafeCommand):
|
class Version(Command, MirrorSafeCommand):
|
||||||
@ -33,12 +34,20 @@ class Version(Command, MirrorSafeCommand):
|
|||||||
rp = self.manifest.repoProject
|
rp = self.manifest.repoProject
|
||||||
rem = rp.GetRemote(rp.remote.name)
|
rem = rp.GetRemote(rp.remote.name)
|
||||||
|
|
||||||
print('repo version %s' % rp.work_git.describe(HEAD))
|
# These might not be the same. Report them both.
|
||||||
|
src_ver = RepoSourceVersion()
|
||||||
|
rp_ver = rp.bare_git.describe(HEAD)
|
||||||
|
print('repo version %s' % rp_ver)
|
||||||
print(' (from %s)' % rem.url)
|
print(' (from %s)' % rem.url)
|
||||||
|
|
||||||
if Version.wrapper_path is not None:
|
if Version.wrapper_path is not None:
|
||||||
print('repo launcher version %s' % Version.wrapper_version)
|
print('repo launcher version %s' % Version.wrapper_version)
|
||||||
print(' (from %s)' % Version.wrapper_path)
|
print(' (from %s)' % Version.wrapper_path)
|
||||||
|
|
||||||
print(git.version().strip())
|
if src_ver != rp_ver:
|
||||||
|
print(' (currently at %s)' % src_ver)
|
||||||
|
|
||||||
|
print('repo User-Agent %s' % user_agent.repo)
|
||||||
|
print('git %s' % git.version_tuple().full)
|
||||||
|
print('git User-Agent %s' % user_agent.git)
|
||||||
print('Python %s' % sys.version)
|
print('Python %s' % sys.version)
|
||||||
|
2
tests/fixtures/.gitignore
vendored
Normal file
2
tests/fixtures/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/.repo_not.present.gitconfig.json
|
||||||
|
/.repo_test.gitconfig.json
|
78
tests/test_git_command.py
Normal file
78
tests/test_git_command.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2019 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 git_command.py module."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import re
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import git_command
|
||||||
|
|
||||||
|
|
||||||
|
class GitCallUnitTest(unittest.TestCase):
|
||||||
|
"""Tests the _GitCall class (via git_command.git)."""
|
||||||
|
|
||||||
|
def test_version_tuple(self):
|
||||||
|
"""Check git.version_tuple() handling."""
|
||||||
|
ver = git_command.git.version_tuple()
|
||||||
|
self.assertIsNotNone(ver)
|
||||||
|
|
||||||
|
# We don't dive too deep into the values here to avoid having to update
|
||||||
|
# whenever git versions change. We do check relative to this min version
|
||||||
|
# as this is what `repo` itself requires via MIN_GIT_VERSION.
|
||||||
|
MIN_GIT_VERSION = (1, 7, 2)
|
||||||
|
self.assertTrue(isinstance(ver.major, int))
|
||||||
|
self.assertTrue(isinstance(ver.minor, int))
|
||||||
|
self.assertTrue(isinstance(ver.micro, int))
|
||||||
|
|
||||||
|
self.assertGreater(ver.major, MIN_GIT_VERSION[0] - 1)
|
||||||
|
self.assertGreaterEqual(ver.micro, 0)
|
||||||
|
self.assertGreaterEqual(ver.major, 0)
|
||||||
|
|
||||||
|
self.assertGreaterEqual(ver, MIN_GIT_VERSION)
|
||||||
|
self.assertLess(ver, (9999, 9999, 9999))
|
||||||
|
|
||||||
|
self.assertNotEqual('', ver.full)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAgentUnitTest(unittest.TestCase):
|
||||||
|
"""Tests the UserAgent function."""
|
||||||
|
|
||||||
|
def test_smoke_os(self):
|
||||||
|
"""Make sure UA OS setting returns something useful."""
|
||||||
|
os_name = git_command.user_agent.os
|
||||||
|
# We can't dive too deep because of OS/tool differences, but we can check
|
||||||
|
# the general form.
|
||||||
|
m = re.match(r'^[^ ]+$', os_name)
|
||||||
|
self.assertIsNotNone(m)
|
||||||
|
|
||||||
|
def test_smoke_repo(self):
|
||||||
|
"""Make sure repo UA returns something useful."""
|
||||||
|
ua = git_command.user_agent.repo
|
||||||
|
# We can't dive too deep because of OS/tool differences, but we can check
|
||||||
|
# the general form.
|
||||||
|
m = re.match(r'^git-repo/[^ ]+ ([^ ]+) git/[^ ]+ Python/[0-9.]+', ua)
|
||||||
|
self.assertIsNotNone(m)
|
||||||
|
|
||||||
|
def test_smoke_git(self):
|
||||||
|
"""Make sure git UA returns something useful."""
|
||||||
|
ua = git_command.user_agent.git
|
||||||
|
# We can't dive too deep because of OS/tool differences, but we can check
|
||||||
|
# the general form.
|
||||||
|
m = re.match(r'^git/[^ ]+ ([^ ]+) git-repo/[^ ]+', ua)
|
||||||
|
self.assertIsNotNone(m)
|
@ -1,3 +1,23 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2009 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 git_config.py module."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
62
tests/test_project.py
Normal file
62
tests/test_project.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2019 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 project.py module."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import project
|
||||||
|
|
||||||
|
|
||||||
|
class RepoHookShebang(unittest.TestCase):
|
||||||
|
"""Check shebang parsing in RepoHook."""
|
||||||
|
|
||||||
|
def test_no_shebang(self):
|
||||||
|
"""Lines w/out shebangs should be rejected."""
|
||||||
|
DATA = (
|
||||||
|
'',
|
||||||
|
'# -*- coding:utf-8 -*-\n',
|
||||||
|
'#\n# foo\n',
|
||||||
|
'# Bad shebang in script\n#!/foo\n'
|
||||||
|
)
|
||||||
|
for data in DATA:
|
||||||
|
self.assertIsNone(project.RepoHook._ExtractInterpFromShebang(data))
|
||||||
|
|
||||||
|
def test_direct_interp(self):
|
||||||
|
"""Lines whose shebang points directly to the interpreter."""
|
||||||
|
DATA = (
|
||||||
|
('#!/foo', '/foo'),
|
||||||
|
('#! /foo', '/foo'),
|
||||||
|
('#!/bin/foo ', '/bin/foo'),
|
||||||
|
('#! /usr/foo ', '/usr/foo'),
|
||||||
|
('#! /usr/foo -args', '/usr/foo'),
|
||||||
|
)
|
||||||
|
for shebang, interp in DATA:
|
||||||
|
self.assertEqual(project.RepoHook._ExtractInterpFromShebang(shebang),
|
||||||
|
interp)
|
||||||
|
|
||||||
|
def test_env_interp(self):
|
||||||
|
"""Lines whose shebang launches through `env`."""
|
||||||
|
DATA = (
|
||||||
|
('#!/usr/bin/env foo', 'foo'),
|
||||||
|
('#!/bin/env foo', 'foo'),
|
||||||
|
('#! /bin/env /bin/foo ', '/bin/foo'),
|
||||||
|
)
|
||||||
|
for shebang, interp in DATA:
|
||||||
|
self.assertEqual(project.RepoHook._ExtractInterpFromShebang(shebang),
|
||||||
|
interp)
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2015 The Android Open Source Project
|
# Copyright (C) 2015 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -13,6 +14,10 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
"""Unittests for the wrapper.py module."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
# -*- coding:utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2014 The Android Open Source Project
|
# Copyright (C) 2014 The Android Open Source Project
|
||||||
#
|
#
|
||||||
@ -15,7 +15,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
try:
|
||||||
|
from importlib.machinery import SourceFileLoader
|
||||||
|
_loader = lambda *args: SourceFileLoader(*args).load_module()
|
||||||
|
except ImportError:
|
||||||
import imp
|
import imp
|
||||||
|
_loader = lambda *args: imp.load_source(*args)
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
@ -26,5 +31,5 @@ _wrapper_module = None
|
|||||||
def Wrapper():
|
def Wrapper():
|
||||||
global _wrapper_module
|
global _wrapper_module
|
||||||
if not _wrapper_module:
|
if not _wrapper_module:
|
||||||
_wrapper_module = imp.load_source('wrapper', WrapperPath())
|
_wrapper_module = _loader('wrapper', WrapperPath())
|
||||||
return _wrapper_module
|
return _wrapper_module
|
||||||
|
Reference in New Issue
Block a user