mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-26 20:17:52 +00:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
c5b0e23490 | |||
d92464e8ef | |||
0968570df2 | |||
f25a370a14 | |||
b554838ce8 | |||
2d095da4f1 | |||
266f74c888 | |||
1f1596b473 | |||
0d9b16d1d8 | |||
a84df06160 | |||
e57f1146de | |||
01019d94af | |||
834d308a2b | |||
c18ee35da6 | |||
d3c0f5914f | |||
41a26837d0 | |||
e7379dc5f7 | |||
13f323b2c2 | |||
12ee5446e9 | |||
e158e3802d | |||
3bbbcaf99d | |||
d4b13c280b |
@ -5,6 +5,6 @@
|
||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||
<path>/git-repo</path>
|
||||
</pydev_pathproperty>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
</pydev_project>
|
||||
|
@ -7,7 +7,7 @@ easier to work with Git. The repo command is an executable Python script
|
||||
that you can put anywhere in your path.
|
||||
|
||||
* Homepage: https://gerrit.googlesource.com/git-repo/
|
||||
* Bug reports: https://code.google.com/p/git-repo/issues/
|
||||
* Bug reports: https://bugs.chromium.org/p/gerrit/issues/list?q=component:repo
|
||||
* Source: https://gerrit.googlesource.com/git-repo/
|
||||
* Overview: https://source.android.com/source/developing.html
|
||||
* Docs: https://source.android.com/source/using-repo.html
|
||||
|
@ -1,3 +1,5 @@
|
||||
[TOC]
|
||||
|
||||
# Short Version
|
||||
|
||||
- Make small logical changes.
|
||||
@ -52,17 +54,29 @@ Run `flake8` on changes modules:
|
||||
|
||||
flake8 file.py
|
||||
|
||||
Note that repo generally follows [Google's python style guide]
|
||||
(https://google.github.io/styleguide/pyguide.html) rather than [PEP 8]
|
||||
(https://www.python.org/dev/peps/pep-0008/), so it's possible that
|
||||
the output of `flake8` will be quite noisy. It's not mandatory to
|
||||
avoid all warnings, but at least the maximum line length should be
|
||||
followed.
|
||||
Note that repo generally follows [Google's python style guide] rather than
|
||||
[PEP 8], so it's possible that the output of `flake8` will be quite noisy.
|
||||
It's not mandatory to 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
|
||||
avoided without going against the Google style guide, these may be
|
||||
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
|
||||
|
||||
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
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -66,7 +66,7 @@ following DTD:
|
||||
<!ATTLIST project groups CDATA #IMPLIED>
|
||||
<!ATTLIST project sync-c CDATA #IMPLIED>
|
||||
<!ATTLIST project sync-s CDATA #IMPLIED>
|
||||
<!ATTLIST default sync-tags CDATA #IMPLIED>
|
||||
<!ATTLIST project sync-tags CDATA #IMPLIED>
|
||||
<!ATTLIST project upstream CDATA #IMPLIED>
|
||||
<!ATTLIST project clone-depth CDATA #IMPLIED>
|
||||
<!ATTLIST project force-path CDATA #IMPLIED>
|
||||
|
32
docs/python-support.md
Normal file
32
docs/python-support.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Supported Python Versions
|
||||
|
||||
With Python 2.7 officially going EOL on [01 Jan 2020](https://pythonclock.org/),
|
||||
we need a support plan for the repo project itself.
|
||||
Inevitably, there will be a long tail of users who still want to use Python 2 on
|
||||
their old LTS/corp systems and have little power to change the system.
|
||||
|
||||
## Summary
|
||||
|
||||
* Python 3.6 (released Dec 2016) is required by default starting with repo-1.14.
|
||||
* Older versions of Python (e.g. v2.7) may use the legacy feature-frozen branch
|
||||
based on repo-1.13.
|
||||
|
||||
## Overview
|
||||
|
||||
We provide a branch for Python 2 users that is feature-frozen.
|
||||
Bugfixes may be added on a best-effort basis or from the community, but largely
|
||||
no new features will be added, nor is support guaranteed.
|
||||
|
||||
Users can select this during `repo init` time via the [repo launcher].
|
||||
Otherwise the default branches (e.g. stable & master) will be used which will
|
||||
require Python 3.
|
||||
|
||||
This means the [repo launcher] needs to support both Python 2 & Python 3, but
|
||||
since it doesn't import any other repo code, this shouldn't be too problematic.
|
||||
|
||||
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
|
||||
the legacy Python 2 branch instead.
|
||||
|
||||
|
||||
[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
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# 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
|
||||
#
|
||||
|
26
event_log.py
26
event_log.py
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2017 The Android Open Source Project
|
||||
#
|
||||
@ -18,8 +19,6 @@ from __future__ import print_function
|
||||
import json
|
||||
import multiprocessing
|
||||
|
||||
from pyversion import is_python3
|
||||
|
||||
TASK_COMMAND = 'command'
|
||||
TASK_SYNC_NETWORK = 'sync-network'
|
||||
TASK_SYNC_LOCAL = 'sync-local'
|
||||
@ -53,7 +52,6 @@ class EventLog(object):
|
||||
def __init__(self):
|
||||
"""Initializes the event log."""
|
||||
self._log = []
|
||||
self._next_id = _EventIdGenerator()
|
||||
self._parent = None
|
||||
|
||||
def Add(self, name, task_name, start, finish=None, success=None,
|
||||
@ -73,7 +71,7 @@ class EventLog(object):
|
||||
A dictionary of the event added to the log.
|
||||
"""
|
||||
event = {
|
||||
'id': (kind, self._next_id.__next__() if is_python3() else self._next_id.next()),
|
||||
'id': (kind, _NextEventId()),
|
||||
'name': name,
|
||||
'task_name': task_name,
|
||||
'start_time': start,
|
||||
@ -164,16 +162,16 @@ class EventLog(object):
|
||||
f.write('\n')
|
||||
|
||||
|
||||
def _EventIdGenerator():
|
||||
"""Returns multi-process safe iterator that generates locally unique id.
|
||||
# An integer id that is unique across this invocation of the program.
|
||||
_EVENT_ID = multiprocessing.Value('i', 1)
|
||||
|
||||
Yields:
|
||||
def _NextEventId():
|
||||
"""Helper function for grabbing the next unique id.
|
||||
|
||||
Returns:
|
||||
A unique, to this invocation of the program, integer id.
|
||||
"""
|
||||
eid = multiprocessing.Value('i', 1)
|
||||
|
||||
while True:
|
||||
with eid.get_lock():
|
||||
val = eid.value
|
||||
eid.value += 1
|
||||
yield val
|
||||
with _EVENT_ID.get_lock():
|
||||
val = _EVENT_ID.value
|
||||
_EVENT_ID.value += 1
|
||||
return val
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -79,22 +80,12 @@ def terminate_ssh_clients():
|
||||
_git_version = None
|
||||
|
||||
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):
|
||||
global _git_version
|
||||
if _git_version is None:
|
||||
ver_str = git.version()
|
||||
_git_version = Wrapper().ParseGitVersion(ver_str)
|
||||
_git_version = Wrapper().ParseGitVersion()
|
||||
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)
|
||||
return _git_version
|
||||
|
||||
@ -107,13 +98,15 @@ class _GitCall(object):
|
||||
return fun
|
||||
git = _GitCall()
|
||||
|
||||
def git_require(min_version, fail=False):
|
||||
def git_require(min_version, fail=False, msg=''):
|
||||
git_version = git.version_tuple()
|
||||
if min_version <= git_version:
|
||||
return True
|
||||
if fail:
|
||||
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)
|
||||
return False
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -656,13 +657,14 @@ class Remote(object):
|
||||
info = urllib.request.urlopen(info_url, context=context).read()
|
||||
else:
|
||||
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
|
||||
# of HTML response back, like maybe a login page.
|
||||
#
|
||||
# Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
|
||||
self._review_url = http_url
|
||||
else:
|
||||
info = info.decode('utf-8')
|
||||
host, port = info.split()
|
||||
self._review_url = self._SshReviewUrl(userEmail, host, port)
|
||||
except urllib.error.HTTPError as e:
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
|
15
git_ssh
15
git_ssh
@ -1,2 +1,17 @@
|
||||
#!/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" "$@"
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 The Android Open Source Project
|
||||
#
|
||||
@ -58,8 +59,8 @@ def _set_project_revisions(projects):
|
||||
sys.exit(1)
|
||||
revisionExpr = gitcmd.stdout.split('\t')[0]
|
||||
if not revisionExpr:
|
||||
raise(ManifestParseError('Invalid SHA-1 revision project %s (%s)' %
|
||||
(proj.remote.url, proj.revisionExpr)))
|
||||
raise ManifestParseError('Invalid SHA-1 revision project %s (%s)' %
|
||||
(proj.remote.url, proj.revisionExpr))
|
||||
proj.revisionExpr = revisionExpr
|
||||
|
||||
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 '
|
||||
'project.')
|
||||
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]
|
||||
|
||||
@ -96,7 +97,7 @@ def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
|
||||
projects = [p for p in projects if p.MatchesGroups(groups)]
|
||||
|
||||
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):
|
||||
continue
|
||||
|
||||
@ -124,7 +125,7 @@ def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
|
||||
index += NUM_BATCH_RETRIEVE_REVISIONID
|
||||
|
||||
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 we updated a project that has been started, keep the old-revision
|
||||
# updated.
|
||||
@ -133,7 +134,7 @@ def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
|
||||
repo_proj.revisionExpr = None
|
||||
|
||||
# Convert URLs from relative to absolute.
|
||||
for _name, remote in manifest.remotes.iteritems():
|
||||
for _name, remote in manifest.remotes.items():
|
||||
remote.fetchUrl = remote.resolvedFetchUrl
|
||||
|
||||
# Save the manifest.
|
||||
|
9
main.py
9
main.py
@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -14,6 +15,12 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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
|
||||
import getpass
|
||||
import imp
|
||||
@ -316,7 +323,7 @@ def _UserAgent():
|
||||
_user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
|
||||
repo_version,
|
||||
os_name,
|
||||
'.'.join(map(str, git.version_tuple())),
|
||||
git.version_tuple().full,
|
||||
py_version[0], py_version[1], py_version[2])
|
||||
return _user_agent
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -135,6 +136,7 @@ class XmlManifest(object):
|
||||
self.globalConfig = GitConfig.ForUser()
|
||||
self.localManifestWarning = False
|
||||
self.isGitcClient = False
|
||||
self._load_local_manifests = True
|
||||
|
||||
self.repoProject = MetaProject(self, 'repo',
|
||||
gitdir = os.path.join(repodir, 'repo/.git'),
|
||||
@ -146,15 +148,26 @@ class XmlManifest(object):
|
||||
|
||||
self._Unload()
|
||||
|
||||
def Override(self, name):
|
||||
def Override(self, name, load_local_manifests=True):
|
||||
"""Use a different manifest, just for the current instantiation.
|
||||
"""
|
||||
path = os.path.join(self.manifestProject.worktree, name)
|
||||
if not os.path.isfile(path):
|
||||
raise ManifestParseError('manifest %s not found' % name)
|
||||
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)
|
||||
if not os.path.isfile(path):
|
||||
raise ManifestParseError('manifest %s not found' % name)
|
||||
|
||||
old = self.manifestFile
|
||||
try:
|
||||
self._load_local_manifests = load_local_manifests
|
||||
self.manifestFile = path
|
||||
self._Unload()
|
||||
self._Load()
|
||||
@ -400,6 +413,12 @@ class XmlManifest(object):
|
||||
self._Load()
|
||||
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
|
||||
def IsMirror(self):
|
||||
return self.manifestProject.config.GetBoolean('repo.mirror')
|
||||
@ -435,23 +454,26 @@ class XmlManifest(object):
|
||||
nodes.append(self._ParseManifestXml(self.manifestFile,
|
||||
self.manifestProject.worktree))
|
||||
|
||||
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
|
||||
if os.path.exists(local):
|
||||
if not self.localManifestWarning:
|
||||
self.localManifestWarning = True
|
||||
print('warning: %s is deprecated; put local manifests in `%s` instead'
|
||||
% (LOCAL_MANIFEST_NAME, os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)),
|
||||
file=sys.stderr)
|
||||
nodes.append(self._ParseManifestXml(local, self.repodir))
|
||||
if self._load_local_manifests:
|
||||
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
|
||||
if os.path.exists(local):
|
||||
if not self.localManifestWarning:
|
||||
self.localManifestWarning = True
|
||||
print('warning: %s is deprecated; put local manifests '
|
||||
'in `%s` instead' % (LOCAL_MANIFEST_NAME,
|
||||
os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)),
|
||||
file=sys.stderr)
|
||||
nodes.append(self._ParseManifestXml(local, self.repodir))
|
||||
|
||||
local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
|
||||
try:
|
||||
for local_file in sorted(platform_utils.listdir(local_dir)):
|
||||
if local_file.endswith('.xml'):
|
||||
local = os.path.join(local_dir, local_file)
|
||||
nodes.append(self._ParseManifestXml(local, self.repodir))
|
||||
except OSError:
|
||||
pass
|
||||
local_dir = os.path.abspath(os.path.join(self.repodir,
|
||||
LOCAL_MANIFESTS_DIR_NAME))
|
||||
try:
|
||||
for local_file in sorted(platform_utils.listdir(local_dir)):
|
||||
if local_file.endswith('.xml'):
|
||||
local = os.path.join(local_dir, local_file)
|
||||
nodes.append(self._ParseManifestXml(local, self.repodir))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
self._ParseManifest(nodes)
|
||||
@ -498,7 +520,7 @@ class XmlManifest(object):
|
||||
raise
|
||||
except Exception as e:
|
||||
raise ManifestParseError(
|
||||
"failed parsing included manifest %s: %s", (name, e))
|
||||
"failed parsing included manifest %s: %s" % (name, e))
|
||||
else:
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
1
pager.py
1
pager.py
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2016 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# 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 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 byref
|
||||
|
||||
@ -33,7 +34,7 @@ ERROR_PRIVILEGE_NOT_HELD = 1314
|
||||
|
||||
# Win32 API entry points
|
||||
CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
|
||||
CreateSymbolicLinkW.restype = BOOL
|
||||
CreateSymbolicLinkW.restype = BOOLEAN
|
||||
CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
|
||||
LPCWSTR, # lpTargetFileName In
|
||||
DWORD) # dwFlags In
|
||||
@ -145,19 +146,12 @@ def create_dirsymlink(source, link_name):
|
||||
|
||||
|
||||
def _create_symlink(source, link_name, dwFlags):
|
||||
# Note: Win32 documentation for CreateSymbolicLink is incorrect.
|
||||
# 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:
|
||||
if not CreateSymbolicLinkW(link_name, source, dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE):
|
||||
# See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0
|
||||
# "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972).
|
||||
# retry without it."
|
||||
CreateSymbolicLinkW(link_name, source, dwFlags)
|
||||
code = get_last_error()
|
||||
if code != ERROR_SUCCESS:
|
||||
if not CreateSymbolicLinkW(link_name, source, dwFlags):
|
||||
code = get_last_error()
|
||||
error_desc = FormatError(code).strip()
|
||||
if code == ERROR_PRIVILEGE_NOT_HELD:
|
||||
raise OSError(errno.EPERM, error_desc, link_name)
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
|
67
project.py
67
project.py
@ -1,3 +1,5 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -1172,10 +1174,11 @@ class Project(object):
|
||||
|
||||
ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
|
||||
dest_branch)
|
||||
opts = []
|
||||
if auto_topic:
|
||||
ref_spec = ref_spec + '/' + branch.name
|
||||
opts += ['topic=' + branch.name]
|
||||
|
||||
opts = ['r=%s' % p for p in people[0]]
|
||||
opts += ['r=%s' % p for p in people[0]]
|
||||
opts += ['cc=%s' % p for p in people[1]]
|
||||
if notify:
|
||||
opts += ['notify=' + notify]
|
||||
@ -1223,7 +1226,8 @@ class Project(object):
|
||||
archive=False,
|
||||
optimized_fetch=False,
|
||||
prune=False,
|
||||
submodules=False):
|
||||
submodules=False,
|
||||
clone_filter=None):
|
||||
"""Perform only the network IO portion of the sync process.
|
||||
Local working directory/branch state is not affected.
|
||||
"""
|
||||
@ -1306,7 +1310,8 @@ class Project(object):
|
||||
not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
|
||||
current_branch_only=current_branch_only,
|
||||
no_tags=no_tags, prune=prune, depth=depth,
|
||||
submodules=submodules)):
|
||||
submodules=submodules, force_sync=force_sync,
|
||||
clone_filter=clone_filter)):
|
||||
return False
|
||||
|
||||
mp = self.manifest.manifestProject
|
||||
@ -1807,8 +1812,8 @@ class Project(object):
|
||||
submodules.append((sub_rev, sub_path, sub_url))
|
||||
return submodules
|
||||
|
||||
re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
|
||||
re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
|
||||
re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
|
||||
re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
|
||||
|
||||
def parse_gitmodules(gitdir, rev):
|
||||
cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
|
||||
@ -1955,7 +1960,9 @@ class Project(object):
|
||||
no_tags=False,
|
||||
prune=False,
|
||||
depth=None,
|
||||
submodules=False):
|
||||
submodules=False,
|
||||
force_sync=False,
|
||||
clone_filter=None):
|
||||
|
||||
is_sha1 = False
|
||||
tag_name = None
|
||||
@ -1979,8 +1986,9 @@ class Project(object):
|
||||
|
||||
if is_sha1 or tag_name is not None:
|
||||
if self._CheckForImmutableRevision():
|
||||
print('Skipped fetching project %s (already have persistent ref)'
|
||||
% self.name)
|
||||
if not quiet:
|
||||
print('Skipped fetching project %s (already have persistent ref)'
|
||||
% self.name)
|
||||
return True
|
||||
if is_sha1 and not depth:
|
||||
# When syncing a specific commit and --depth is not set:
|
||||
@ -2045,6 +2053,11 @@ class Project(object):
|
||||
|
||||
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:
|
||||
cmd.append('--depth=%s' % depth)
|
||||
else:
|
||||
@ -2068,6 +2081,9 @@ class Project(object):
|
||||
else:
|
||||
cmd.append('--tags')
|
||||
|
||||
if force_sync:
|
||||
cmd.append('--force')
|
||||
|
||||
if prune:
|
||||
cmd.append('--prune')
|
||||
|
||||
@ -2142,12 +2158,12 @@ class Project(object):
|
||||
return self._RemoteFetch(name=name,
|
||||
current_branch_only=current_branch_only,
|
||||
initial=False, quiet=quiet, alt_dir=alt_dir,
|
||||
depth=None)
|
||||
depth=None, clone_filter=clone_filter)
|
||||
else:
|
||||
# Avoid infinite recursion: sync all branches with depth set to None
|
||||
return self._RemoteFetch(name=name, current_branch_only=False,
|
||||
initial=False, quiet=quiet, alt_dir=alt_dir,
|
||||
depth=None)
|
||||
depth=None, clone_filter=clone_filter)
|
||||
|
||||
return ok
|
||||
|
||||
@ -2209,13 +2225,17 @@ class Project(object):
|
||||
cmd += ['--continue-at', '%d' % (size,)]
|
||||
else:
|
||||
platform_utils.remove(tmpPath)
|
||||
if 'http_proxy' in os.environ and 'darwin' == sys.platform:
|
||||
cmd += ['--proxy', os.environ['http_proxy']]
|
||||
with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
|
||||
with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
|
||||
if cookiefile:
|
||||
cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
|
||||
if srcUrl.startswith('persistent-'):
|
||||
srcUrl = srcUrl[len('persistent-'):]
|
||||
if proxy:
|
||||
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]
|
||||
|
||||
if IsTrace():
|
||||
@ -2249,8 +2269,8 @@ class Project(object):
|
||||
|
||||
def _IsValidBundle(self, path, quiet):
|
||||
try:
|
||||
with open(path) as f:
|
||||
if f.read(16) == '# v2 git bundle\n':
|
||||
with open(path, 'rb') as f:
|
||||
if f.read(16) == b'# v2 git bundle\n':
|
||||
return True
|
||||
else:
|
||||
if not quiet:
|
||||
@ -2393,6 +2413,7 @@ class Project(object):
|
||||
if m.Has(key, include_defaults=False):
|
||||
self.config.SetString(key, m.GetString(key))
|
||||
self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
|
||||
self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
|
||||
if self.manifest.IsMirror:
|
||||
self.config.SetString('core.bare', 'true')
|
||||
else:
|
||||
@ -2584,7 +2605,7 @@ class Project(object):
|
||||
cmd.append('-v')
|
||||
cmd.append(HEAD)
|
||||
if GitCommand(self, cmd).Wait() != 0:
|
||||
raise GitError("cannot initialize work tree")
|
||||
raise GitError("cannot initialize work tree for " + self.name)
|
||||
|
||||
if submodules:
|
||||
self._SyncSubmodules(quiet=True)
|
||||
@ -2684,6 +2705,7 @@ class Project(object):
|
||||
def DiffZ(self, name, *args):
|
||||
cmd = [name]
|
||||
cmd.append('-z')
|
||||
cmd.append('--ignore-submodules')
|
||||
cmd.extend(args)
|
||||
p = GitCommand(self._project,
|
||||
cmd,
|
||||
@ -2801,15 +2823,10 @@ class Project(object):
|
||||
gitdir=self._gitdir,
|
||||
capture_stdout=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:
|
||||
raise GitError('%s rev-list %s: %s' %
|
||||
(self._project.name, str(args), p.stderr))
|
||||
return r
|
||||
return p.stdout.splitlines()
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Allow arbitrary git commands using pythonic syntax.
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 The Android Open Source Project
|
||||
#
|
||||
|
222
repo
222
repo
@ -1,4 +1,14 @@
|
||||
#!/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
|
||||
#
|
||||
@ -23,7 +33,7 @@ REPO_REV = 'stable'
|
||||
# limitations under the License.
|
||||
|
||||
# increment this whenever we make important changes to this script
|
||||
VERSION = (1, 24)
|
||||
VERSION = (1, 25)
|
||||
|
||||
# increment this if the MAINTAINER_KEYS block is modified
|
||||
KEYRING_VERSION = (1, 2)
|
||||
@ -113,11 +123,12 @@ repodir = '.repo' # name of repo's private directory
|
||||
S_repo = 'repo' # special repo repository
|
||||
S_manifests = 'manifests' # special manifest repository
|
||||
REPO_MAIN = S_repo + '/main.py' # main script
|
||||
MIN_PYTHON_VERSION = (2, 6) # minimum supported python version
|
||||
MIN_PYTHON_VERSION = (2, 7) # minimum supported python version
|
||||
GITC_CONFIG_FILE = '/gitc/.config'
|
||||
GITC_FS_ROOT_DIR = '/gitc/manifest-rw/'
|
||||
|
||||
|
||||
import collections
|
||||
import errno
|
||||
import optparse
|
||||
import platform
|
||||
@ -138,23 +149,15 @@ else:
|
||||
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
|
||||
ver = sys.version_info
|
||||
if (ver[0], ver[1]) < MIN_PYTHON_VERSION:
|
||||
_print('error: Python version %s unsupported.\n'
|
||||
'Please use Python 2.6 - 2.7 instead.'
|
||||
% sys.version.split(' ')[0], file=sys.stderr)
|
||||
print('error: Python version {} unsupported.\n'
|
||||
'Please use Python {}.{} instead.'.format(
|
||||
sys.version.split(' ')[0],
|
||||
MIN_PYTHON_VERSION[0],
|
||||
MIN_PYTHON_VERSION[1],
|
||||
), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
home_dot_repo = os.path.expanduser('~/.repoconfig')
|
||||
@ -180,7 +183,7 @@ group.add_option('-b', '--manifest-branch',
|
||||
group.add_option('-m', '--manifest-name',
|
||||
dest='manifest_name',
|
||||
help='initial manifest file', metavar='NAME.xml')
|
||||
group.add_option('-c', '--current-branch',
|
||||
group.add_option('--current-branch',
|
||||
dest='current_branch_only', action='store_true',
|
||||
help='fetch only current manifest branch from server')
|
||||
group.add_option('--mirror',
|
||||
@ -196,6 +199,13 @@ group.add_option('--dissociate',
|
||||
group.add_option('--depth', type='int', default=None,
|
||||
dest='depth',
|
||||
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',
|
||||
dest='archive', action='store_true',
|
||||
help='checkout an archive instead of a git repository for '
|
||||
@ -320,21 +330,21 @@ def _Init(args, gitc_init=False):
|
||||
if branch.startswith('refs/heads/'):
|
||||
branch = branch[len('refs/heads/'):]
|
||||
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()
|
||||
|
||||
try:
|
||||
if gitc_init:
|
||||
gitc_manifest_dir = get_gitc_manifest_dir()
|
||||
if not gitc_manifest_dir:
|
||||
_print('fatal: GITC filesystem is not available. Exiting...',
|
||||
file=sys.stderr)
|
||||
print('fatal: GITC filesystem is not available. Exiting...',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
gitc_client = opt.gitc_client
|
||||
if not gitc_client:
|
||||
gitc_client = gitc_parse_clientdir(os.getcwd())
|
||||
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)
|
||||
client_dir = os.path.join(gitc_manifest_dir, gitc_client)
|
||||
if not os.path.exists(client_dir):
|
||||
@ -347,8 +357,8 @@ def _Init(args, gitc_init=False):
|
||||
os.mkdir(repodir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
_print('fatal: cannot make %s directory: %s'
|
||||
% (repodir, e.strerror), file=sys.stderr)
|
||||
print('fatal: cannot make %s directory: %s'
|
||||
% (repodir, e.strerror), file=sys.stderr)
|
||||
# Don't raise CloneFailure; that would delete the
|
||||
# name. Instead exit immediately.
|
||||
#
|
||||
@ -372,55 +382,73 @@ def _Init(args, gitc_init=False):
|
||||
_Checkout(dst, branch, rev, opt.quiet)
|
||||
|
||||
if not os.path.isfile(os.path.join(dst, 'repo')):
|
||||
_print("warning: '%s' does not look like a git-repo repository, is "
|
||||
"REPO_URL set correctly?" % url, file=sys.stderr)
|
||||
print("warning: '%s' does not look like a git-repo repository, is "
|
||||
"REPO_URL set correctly?" % url, file=sys.stderr)
|
||||
|
||||
except CloneFailure:
|
||||
if opt.quiet:
|
||||
_print('fatal: repo init failed; run without --quiet to see why',
|
||||
file=sys.stderr)
|
||||
print('fatal: repo init failed; run without --quiet to see why',
|
||||
file=sys.stderr)
|
||||
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 '):
|
||||
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 = []
|
||||
for num_str in num_ver_str.split('.')[:3]:
|
||||
if num_str.isdigit():
|
||||
to_tuple.append(int(num_str))
|
||||
else:
|
||||
to_tuple.append(0)
|
||||
return tuple(to_tuple)
|
||||
to_tuple.append(full_version)
|
||||
return GitVersion(*to_tuple)
|
||||
|
||||
|
||||
def _CheckGitVersion():
|
||||
def _GetGitVersion():
|
||||
cmd = [GIT, '--version']
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
except OSError as e:
|
||||
_print(file=sys.stderr)
|
||||
_print("fatal: '%s' is not available" % GIT, file=sys.stderr)
|
||||
_print('fatal: %s' % e, file=sys.stderr)
|
||||
_print(file=sys.stderr)
|
||||
_print('Please make sure %s is installed and in your path.' % GIT,
|
||||
file=sys.stderr)
|
||||
raise CloneFailure()
|
||||
print(file=sys.stderr)
|
||||
print("fatal: '%s' is not available" % GIT, file=sys.stderr)
|
||||
print('fatal: %s' % e, file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
print('Please make sure %s is installed and in your path.' % GIT,
|
||||
file=sys.stderr)
|
||||
raise
|
||||
|
||||
ver_str = proc.stdout.read().strip()
|
||||
proc.stdout.close()
|
||||
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:
|
||||
_print('error: "%s" unsupported' % ver_str, file=sys.stderr)
|
||||
print('error: "%s" unsupported' % ver_str, file=sys.stderr)
|
||||
raise CloneFailure()
|
||||
|
||||
if ver_act < 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()
|
||||
|
||||
|
||||
@ -447,16 +475,16 @@ def SetupGnuPG(quiet):
|
||||
os.mkdir(home_dot_repo)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
_print('fatal: cannot make %s directory: %s'
|
||||
% (home_dot_repo, e.strerror), file=sys.stderr)
|
||||
print('fatal: cannot make %s directory: %s'
|
||||
% (home_dot_repo, e.strerror), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
os.mkdir(gpg_dir, stat.S_IRWXU)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
_print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
|
||||
file=sys.stderr)
|
||||
print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
env = os.environ.copy()
|
||||
@ -472,18 +500,18 @@ def SetupGnuPG(quiet):
|
||||
stdin=subprocess.PIPE)
|
||||
except OSError as e:
|
||||
if not quiet:
|
||||
_print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
|
||||
_print('warning: Installing it is strongly encouraged.', file=sys.stderr)
|
||||
_print(file=sys.stderr)
|
||||
print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
|
||||
print('warning: Installing it is strongly encouraged.', file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
return False
|
||||
|
||||
proc.stdin.write(MAINTAINER_KEYS)
|
||||
proc.stdin.close()
|
||||
|
||||
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)
|
||||
_print()
|
||||
print()
|
||||
|
||||
fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
|
||||
fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
|
||||
@ -526,7 +554,7 @@ def _InitHttp():
|
||||
|
||||
def _Fetch(url, local, src, quiet):
|
||||
if not quiet:
|
||||
_print('Get %s' % url, file=sys.stderr)
|
||||
print('Get %s' % url, file=sys.stderr)
|
||||
|
||||
cmd = [GIT, 'fetch']
|
||||
if quiet:
|
||||
@ -576,19 +604,19 @@ def _DownloadBundle(url, local, quiet):
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in [401, 403, 404, 501]:
|
||||
return False
|
||||
_print('fatal: Cannot get %s' % url, file=sys.stderr)
|
||||
_print('fatal: HTTP error %s' % e.code, file=sys.stderr)
|
||||
print('fatal: Cannot get %s' % url, file=sys.stderr)
|
||||
print('fatal: HTTP error %s' % e.code, file=sys.stderr)
|
||||
raise CloneFailure()
|
||||
except urllib.error.URLError as e:
|
||||
_print('fatal: Cannot get %s' % url, file=sys.stderr)
|
||||
_print('fatal: error %s' % e.reason, file=sys.stderr)
|
||||
print('fatal: Cannot get %s' % url, file=sys.stderr)
|
||||
print('fatal: error %s' % e.reason, file=sys.stderr)
|
||||
raise CloneFailure()
|
||||
try:
|
||||
if not quiet:
|
||||
_print('Get %s' % url, file=sys.stderr)
|
||||
print('Get %s' % url, file=sys.stderr)
|
||||
while True:
|
||||
buf = r.read(8192)
|
||||
if buf == '':
|
||||
if not buf:
|
||||
return True
|
||||
dest.write(buf)
|
||||
finally:
|
||||
@ -611,23 +639,23 @@ def _Clone(url, local, quiet, clone_bundle):
|
||||
try:
|
||||
os.mkdir(local)
|
||||
except OSError as e:
|
||||
_print('fatal: cannot make %s directory: %s' % (local, e.strerror),
|
||||
file=sys.stderr)
|
||||
print('fatal: cannot make %s directory: %s' % (local, e.strerror),
|
||||
file=sys.stderr)
|
||||
raise CloneFailure()
|
||||
|
||||
cmd = [GIT, 'init', '--quiet']
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, cwd=local)
|
||||
except OSError as e:
|
||||
_print(file=sys.stderr)
|
||||
_print("fatal: '%s' is not available" % GIT, file=sys.stderr)
|
||||
_print('fatal: %s' % e, file=sys.stderr)
|
||||
_print(file=sys.stderr)
|
||||
_print('Please make sure %s is installed and in your path.' % GIT,
|
||||
file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
print("fatal: '%s' is not available" % GIT, file=sys.stderr)
|
||||
print('fatal: %s' % e, file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
print('Please make sure %s is installed and in your path.' % GIT,
|
||||
file=sys.stderr)
|
||||
raise CloneFailure()
|
||||
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()
|
||||
|
||||
_InitHttp()
|
||||
@ -655,18 +683,18 @@ def _Verify(cwd, branch, quiet):
|
||||
proc.stderr.close()
|
||||
|
||||
if proc.wait() != 0 or not cur:
|
||||
_print(file=sys.stderr)
|
||||
_print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
|
||||
raise CloneFailure()
|
||||
|
||||
m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
|
||||
if m:
|
||||
cur = m.group(1)
|
||||
if not quiet:
|
||||
_print(file=sys.stderr)
|
||||
_print("info: Ignoring branch '%s'; using tagged release '%s'"
|
||||
% (branch, cur), file=sys.stderr)
|
||||
_print(file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
print("info: Ignoring branch '%s'; using tagged release '%s'"
|
||||
% (branch, cur), file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
|
||||
env = os.environ.copy()
|
||||
try:
|
||||
@ -687,10 +715,10 @@ def _Verify(cwd, branch, quiet):
|
||||
proc.stderr.close()
|
||||
|
||||
if proc.wait() != 0:
|
||||
_print(file=sys.stderr)
|
||||
_print(out, file=sys.stderr)
|
||||
_print(err, file=sys.stderr)
|
||||
_print(file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
print(out, file=sys.stderr)
|
||||
print(err, file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
raise CloneFailure()
|
||||
return '%s^0' % cur
|
||||
|
||||
@ -761,7 +789,7 @@ def _Usage():
|
||||
if get_gitc_manifest_dir():
|
||||
gitc_usage = " gitc-init Initialize a GITC Client.\n"
|
||||
|
||||
_print(
|
||||
print(
|
||||
"""usage: repo COMMAND [ARGS]
|
||||
|
||||
repo is not yet installed. Use "repo init" to install it here.
|
||||
@ -773,8 +801,8 @@ The most commonly used repo commands are:
|
||||
""" help Display detailed help on a command
|
||||
|
||||
For access to the full online help, install repo ("repo init").
|
||||
""", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
""")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def _Help(args):
|
||||
@ -787,23 +815,23 @@ def _Help(args):
|
||||
init_optparse.print_help()
|
||||
sys.exit(0)
|
||||
else:
|
||||
_print("error: '%s' is not a bootstrap command.\n"
|
||||
' For access to online help, install repo ("repo init").'
|
||||
% args[0], file=sys.stderr)
|
||||
print("error: '%s' is not a bootstrap command.\n"
|
||||
' For access to online help, install repo ("repo init").'
|
||||
% args[0], file=sys.stderr)
|
||||
else:
|
||||
_Usage()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _NotInstalled():
|
||||
_print('error: repo is not installed. Use "repo init" to install it here.',
|
||||
file=sys.stderr)
|
||||
print('error: repo is not installed. Use "repo init" to install it here.',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _NoCommands(cmd):
|
||||
_print("""error: command '%s' requires repo to be installed first.
|
||||
Use "repo init" to install it here.""" % cmd, file=sys.stderr)
|
||||
print("""error: command '%s' requires repo to be installed first.
|
||||
Use "repo init" to install it here.""" % cmd, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -840,7 +868,7 @@ def _SetDefaultsTo(gitdir):
|
||||
proc.stderr.close()
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@ -857,10 +885,10 @@ def main(orig_args):
|
||||
|
||||
cwd = os.getcwd()
|
||||
if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()):
|
||||
_print('error: repo cannot be used in the GITC local manifest directory.'
|
||||
'\nIf you want to work on this GITC client please rerun this '
|
||||
'command from the corresponding client under /gitc/',
|
||||
file=sys.stderr)
|
||||
print('error: repo cannot be used in the GITC local manifest directory.'
|
||||
'\nIf you want to work on this GITC client please rerun this '
|
||||
'command from the corresponding client under /gitc/',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not repo_main:
|
||||
if opt.help:
|
||||
@ -876,8 +904,8 @@ def main(orig_args):
|
||||
_Init(args, gitc_init=(cmd == 'gitc-init'))
|
||||
except CloneFailure:
|
||||
path = os.path.join(repodir, S_repo)
|
||||
_print("fatal: cloning the git-repo repository failed, will remove "
|
||||
"'%s' " % path, file=sys.stderr)
|
||||
print("fatal: cloning the git-repo repository failed, will remove "
|
||||
"'%s' " % path, file=sys.stderr)
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
sys.exit(1)
|
||||
repo_main, rel_repo_dir = _FindRepo()
|
||||
@ -901,14 +929,10 @@ def main(orig_args):
|
||||
else:
|
||||
os.execv(sys.executable, me)
|
||||
except OSError as e:
|
||||
_print("fatal: unable to start %s" % repo_main, file=sys.stderr)
|
||||
_print("fatal: %s" % e, file=sys.stderr)
|
||||
print("fatal: unable to start %s" % repo_main, file=sys.stderr)
|
||||
print("fatal: %s" % e, file=sys.stderr)
|
||||
sys.exit(148)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if ver[0] == 3:
|
||||
_print('warning: Python 3 support is currently experimental. YMMV.\n'
|
||||
'Please use Python 2.6 - 2.7 instead.',
|
||||
file=sys.stderr)
|
||||
main(sys.argv[1:])
|
||||
|
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
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -58,7 +59,7 @@ It is equivalent to "git branch -D <branchname>".
|
||||
pm.update()
|
||||
|
||||
if opt.all:
|
||||
branches = project.GetBranches().keys()
|
||||
branches = list(project.GetBranches().keys())
|
||||
else:
|
||||
branches = [nb]
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2010 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
@ -190,12 +191,12 @@ synced and their revisions won't be found.
|
||||
self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText
|
||||
|
||||
manifest1 = XmlManifest(self.manifest.repodir)
|
||||
manifest1.Override(args[0])
|
||||
manifest1.Override(args[0], load_local_manifests=False)
|
||||
if len(args) == 1:
|
||||
manifest2 = self.manifest
|
||||
else:
|
||||
manifest2 = XmlManifest(self.manifest.repodir)
|
||||
manifest2.Override(args[1])
|
||||
manifest2.Override(args[1], load_local_manifests=False)
|
||||
|
||||
diff = manifest1.projectsDiff(manifest2)
|
||||
if opt.raw:
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -104,7 +105,7 @@ following <command>.
|
||||
|
||||
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
|
||||
the context of running <command> instead of in the calling shell.
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012 The Android Open Source Project
|
||||
#
|
||||
@ -45,7 +46,7 @@ class Info(PagedCommand):
|
||||
def Execute(self, opt, args):
|
||||
self.out = _Coloring(self.manifest.globalConfig)
|
||||
self.heading = self.out.printer('heading', attr = 'bold')
|
||||
self.headtext = self.out.printer('headtext', fg = 'yellow')
|
||||
self.headtext = self.out.nofmt_printer('headtext', fg = 'yellow')
|
||||
self.redtext = self.out.printer('redtext', fg = 'red')
|
||||
self.sha = self.out.printer("sha", fg = 'yellow')
|
||||
self.text = self.out.nofmt_printer('text')
|
||||
@ -99,7 +100,7 @@ class Info(PagedCommand):
|
||||
self.headtext(p.revisionExpr)
|
||||
self.out.nl()
|
||||
|
||||
localBranches = p.GetBranches().keys()
|
||||
localBranches = list(p.GetBranches().keys())
|
||||
self.heading("Local Branches: ")
|
||||
self.redtext(str(len(localBranches)))
|
||||
if len(localBranches) > 0:
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -95,7 +96,7 @@ to update the working directory files.
|
||||
g.add_option('-b', '--manifest-branch',
|
||||
dest='manifest_branch',
|
||||
help='manifest branch or revision', metavar='REVISION')
|
||||
g.add_option('-c', '--current-branch',
|
||||
g.add_option('--current-branch',
|
||||
dest='current_branch_only', action='store_true',
|
||||
help='fetch only current manifest branch from server')
|
||||
g.add_option('-m', '--manifest-name',
|
||||
@ -114,6 +115,13 @@ to update the working directory files.
|
||||
g.add_option('--depth', type='int', default=None,
|
||||
dest='depth',
|
||||
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',
|
||||
dest='archive', action='store_true',
|
||||
help='checkout an archive instead of a git repository for '
|
||||
@ -197,6 +205,8 @@ to update the working directory files.
|
||||
else:
|
||||
m.PreSync()
|
||||
|
||||
self._ConfigureDepth(opt)
|
||||
|
||||
if opt.manifest_url:
|
||||
r = m.GetRemote(m.remote.name)
|
||||
r.url = opt.manifest_url
|
||||
@ -250,13 +260,25 @@ to update the working directory files.
|
||||
'in another location.', file=sys.stderr)
|
||||
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:
|
||||
m.config.SetString('repo.submodules', 'true')
|
||||
|
||||
if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet,
|
||||
clone_bundle=not opt.no_clone_bundle,
|
||||
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)
|
||||
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
|
||||
|
||||
@ -291,7 +313,9 @@ to update the working directory files.
|
||||
sys.exit(1)
|
||||
|
||||
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()
|
||||
if a == '':
|
||||
return value
|
||||
@ -325,7 +349,9 @@ to update the working directory files.
|
||||
|
||||
print()
|
||||
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()
|
||||
if a in ('yes', 'y', 't', 'true'):
|
||||
break
|
||||
@ -367,7 +393,9 @@ to update the working directory files.
|
||||
out.printer(fg='black', attr=c)(' %-6s ', c)
|
||||
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()
|
||||
if a in ('y', 'yes', 't', 'true', 'on'):
|
||||
gc.SetString('color.ui', 'auto')
|
||||
@ -429,6 +457,4 @@ to update the working directory files.
|
||||
self._ConfigureUser()
|
||||
self._ConfigureColor()
|
||||
|
||||
self._ConfigureDepth(opt)
|
||||
|
||||
self._DisplayResult()
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2011 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2010 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2010 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -13,6 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from command import PagedCommand
|
||||
|
||||
try:
|
||||
|
204
subcmds/sync.py
204
subcmds/sync.py
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# 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."""
|
||||
pass
|
||||
|
||||
class _CheckoutError(Exception):
|
||||
"""Internal error thrown in _CheckoutOne() when we don't want stack trace."""
|
||||
|
||||
class Sync(Command, MirrorSafeCommand):
|
||||
jobs = 1
|
||||
common = True
|
||||
@ -136,6 +140,11 @@ directories if they have previously been linked to a different
|
||||
object direcotry. WARNING: This may cause data to be lost since
|
||||
refs may be removed when overwriting.
|
||||
|
||||
The --force-remove-dirty option can be used to remove previously used
|
||||
projects with uncommitted changes. WARNING: This may cause data to be
|
||||
lost since uncommitted changes may be removed with projects that no longer
|
||||
exist in the manifest.
|
||||
|
||||
The --no-clone-bundle option disables any attempt to use
|
||||
$URL/clone.bundle to bootstrap a new Git repository from a
|
||||
resumeable bundle file on a content delivery network. This
|
||||
@ -197,6 +206,11 @@ later is required to fix a server side protocol bug.
|
||||
help="overwrite an existing git directory if it needs to "
|
||||
"point to a different object directory. WARNING: this "
|
||||
"may cause loss of data")
|
||||
p.add_option('--force-remove-dirty',
|
||||
dest='force_remove_dirty', action='store_true',
|
||||
help="force remove projects with uncommitted modifications if "
|
||||
"projects no longer exist in the manifest. "
|
||||
"WARNING: this may cause loss of data")
|
||||
p.add_option('-l', '--local-only',
|
||||
dest='local_only', action='store_true',
|
||||
help="only update working tree, don't fetch")
|
||||
@ -255,7 +269,7 @@ later is required to fix a server side protocol bug.
|
||||
help=SUPPRESS_HELP)
|
||||
|
||||
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.
|
||||
|
||||
@ -275,7 +289,8 @@ later is required to fix a server side protocol bug.
|
||||
finally:
|
||||
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.
|
||||
|
||||
Args:
|
||||
@ -289,6 +304,7 @@ later is required to fix a server side protocol bug.
|
||||
lock held).
|
||||
err_event: We'll set this event in the case of an error (after printing
|
||||
out info about the error).
|
||||
clone_filter: Filter for use in a partial clone.
|
||||
|
||||
Returns:
|
||||
Whether the fetch was successful.
|
||||
@ -301,7 +317,6 @@ later is required to fix a server side protocol bug.
|
||||
|
||||
# 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 call sem.release().
|
||||
# - We always make sure we unlock the lock if we locked it.
|
||||
start = time.time()
|
||||
success = False
|
||||
@ -314,7 +329,8 @@ later is required to fix a server side protocol bug.
|
||||
clone_bundle=not opt.no_clone_bundle,
|
||||
no_tags=opt.no_tags, archive=self.manifest.IsArchive,
|
||||
optimized_fetch=opt.optimized_fetch,
|
||||
prune=opt.prune)
|
||||
prune=opt.prune,
|
||||
clone_filter=clone_filter)
|
||||
self._fetch_times.Set(project, time.time() - start)
|
||||
|
||||
# Lock around all the rest of the code, since printing, updating a set
|
||||
@ -378,7 +394,8 @@ later is required to fix a server side protocol bug.
|
||||
lock=lock,
|
||||
fetched=fetched,
|
||||
pm=pm,
|
||||
err_event=err_event)
|
||||
err_event=err_event,
|
||||
clone_filter=self.manifest.CloneFilter)
|
||||
if self.jobs > 1:
|
||||
t = _threading.Thread(target = self._FetchProjectList,
|
||||
kwargs = kwargs)
|
||||
@ -405,6 +422,148 @@ later is required to fix a server side protocol bug.
|
||||
|
||||
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:
|
||||
success = self._CheckoutOne(opt, project, *args, **kwargs)
|
||||
if not success:
|
||||
sys.exit(1)
|
||||
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 not opt.force_broken:
|
||||
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):
|
||||
gc_gitdirs = {}
|
||||
for project in projects:
|
||||
@ -425,7 +584,7 @@ later is required to fix a server side protocol bug.
|
||||
bare_git.gc('--auto')
|
||||
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()
|
||||
sem = _threading.Semaphore(jobs)
|
||||
@ -525,7 +684,7 @@ later is required to fix a server side protocol bug.
|
||||
|
||||
return 0
|
||||
|
||||
def UpdateProjectList(self):
|
||||
def UpdateProjectList(self, opt):
|
||||
new_project_paths = []
|
||||
for project in self.GetProjects(None, missing_ok=True):
|
||||
if project.relpath:
|
||||
@ -540,7 +699,8 @@ later is required to fix a server side protocol bug.
|
||||
old_project_paths = fd.read().split('\n')
|
||||
finally:
|
||||
fd.close()
|
||||
for path in old_project_paths:
|
||||
# In reversed order, so subfolders are deleted before parent folder.
|
||||
for path in sorted(old_project_paths, reverse=True):
|
||||
if not path:
|
||||
continue
|
||||
if path not in new_project_paths:
|
||||
@ -559,7 +719,11 @@ later is required to fix a server side protocol bug.
|
||||
revisionId = None,
|
||||
groups = None)
|
||||
|
||||
if project.IsDirty():
|
||||
if project.IsDirty() and opt.force_remove_dirty:
|
||||
print('WARNING: Removing dirty project "%s": uncommitted changes '
|
||||
'erased' % project.relpath, file=sys.stderr)
|
||||
self._DeleteProject(project.worktree)
|
||||
elif project.IsDirty():
|
||||
print('error: Cannot remove project "%s": uncommitted changes '
|
||||
'are present' % project.relpath, file=sys.stderr)
|
||||
print(' commit changes, then run sync again',
|
||||
@ -582,7 +746,7 @@ later is required to fix a server side protocol bug.
|
||||
self.jobs = opt.jobs
|
||||
if self.jobs > 1:
|
||||
soft_limit, _ = _rlimit_nofile()
|
||||
self.jobs = min(self.jobs, (soft_limit - 5) / 3)
|
||||
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)
|
||||
@ -730,7 +894,8 @@ later is required to fix a server side protocol bug.
|
||||
current_branch_only=opt.current_branch_only,
|
||||
no_tags=opt.no_tags,
|
||||
optimized_fetch=opt.optimized_fetch,
|
||||
submodules=self.manifest.HasSubmodules)
|
||||
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)
|
||||
@ -827,23 +992,10 @@ later is required to fix a server side protocol bug.
|
||||
# bail out now, we have no working tree
|
||||
return
|
||||
|
||||
if self.UpdateProjectList():
|
||||
if self.UpdateProjectList(opt):
|
||||
sys.exit(1)
|
||||
|
||||
syncbuf = SyncBuffer(mp.config,
|
||||
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)
|
||||
self._Checkout(all_projects, opt)
|
||||
|
||||
# If there's a notice that's supposed to print at the end of the sync, print
|
||||
# it now...
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
@ -220,7 +221,9 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
for commit in commit_list:
|
||||
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 = 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 answer is None:
|
||||
sys.stdout.write('Uncommitted changes in ' + branch.project.name)
|
||||
sys.stdout.write(' (did you forget to amend?):\n')
|
||||
sys.stdout.write('\n'.join(changes) + '\n')
|
||||
sys.stdout.write('Continue uploading? (y/N) ')
|
||||
print()
|
||||
print('Uncommitted changes in %s (did you forget to amend?):'
|
||||
% branch.project.name)
|
||||
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()
|
||||
if a not in ('y', 'yes', 't', 'true', 'on'):
|
||||
print("skipping upload", file=sys.stderr)
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2009 The Android Open Source Project
|
||||
#
|
||||
@ -40,5 +41,5 @@ class Version(Command, MirrorSafeCommand):
|
||||
print('repo launcher version %s' % Version.wrapper_version)
|
||||
print(' (from %s)' % Version.wrapper_path)
|
||||
|
||||
print(git.version().strip())
|
||||
print('git %s' % git.version_tuple().full)
|
||||
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
|
45
tests/test_git_command.py
Normal file
45
tests/test_git_command.py
Normal file
@ -0,0 +1,45 @@
|
||||
# -*- 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.
|
||||
|
||||
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)
|
@ -1,3 +1,19 @@
|
||||
# -*- 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.
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 The Android Open Source Project
|
||||
#
|
||||
|
1
trace.py
1
trace.py
@ -1,3 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2008 The Android Open Source Project
|
||||
#
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
|
Reference in New Issue
Block a user