mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-28 20:17:26 +00:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
a8e98a6962 | |||
b54a392c9a | |||
21f7385400 | |||
24d8dfbc34 | |||
a6df7d284c | |||
67092448c2 | |||
e92ceebde0 | |||
03eaf07ec6 | |||
2896a79120 | |||
8c6eef4713 | |||
34d237fbfb | |||
c99883fee9 | |||
ec18b4bac4 | |||
35f2596c27 | |||
5d40e26201 | |||
70939e2f73 | |||
ae6e0949d1 | |||
339ba9f6f7 | |||
70cd4ab270 | |||
e284ad1d1a | |||
3e5481999d | |||
d3c388391e | |||
2450a2987a | |||
f5c25a68d8 | |||
9fa44db94b | |||
c9ef744c7b | |||
438ee1cad9 | |||
23d7781c0b | |||
a54c527ae9 |
@ -1 +1 @@
|
|||||||
__version__ = 'v1.0-14-gc4f226bc'
|
__version__ = 'v1.0-112-gbcd4db5a'
|
||||||
|
@ -20,6 +20,7 @@ import md5
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib
|
import urllib
|
||||||
import urllib2
|
import urllib2
|
||||||
@ -29,6 +30,38 @@ from froofle.protobuf.service import RpcChannel
|
|||||||
from froofle.protobuf.service import RpcController
|
from froofle.protobuf.service import RpcController
|
||||||
from need_retry_pb2 import RetryRequestLaterResponse;
|
from need_retry_pb2 import RetryRequestLaterResponse;
|
||||||
|
|
||||||
|
_cookie_jars = {}
|
||||||
|
|
||||||
|
def _open_jar(path):
|
||||||
|
auth = False
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
c = cookielib.CookieJar()
|
||||||
|
else:
|
||||||
|
c = _cookie_jars.get(path)
|
||||||
|
if c is None:
|
||||||
|
c = cookielib.MozillaCookieJar(path)
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
try:
|
||||||
|
c.load()
|
||||||
|
auth = True
|
||||||
|
except (cookielib.LoadError, IOError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if auth:
|
||||||
|
print >>sys.stderr, \
|
||||||
|
'Loaded authentication cookies from %s' \
|
||||||
|
% path
|
||||||
|
else:
|
||||||
|
os.close(os.open(path, os.O_CREAT, 0600))
|
||||||
|
os.chmod(path, 0600)
|
||||||
|
_cookie_jars[path] = c
|
||||||
|
else:
|
||||||
|
auth = True
|
||||||
|
return c, auth
|
||||||
|
|
||||||
|
|
||||||
class ClientLoginError(urllib2.HTTPError):
|
class ClientLoginError(urllib2.HTTPError):
|
||||||
"""Raised to indicate an error authenticating with ClientLogin."""
|
"""Raised to indicate an error authenticating with ClientLogin."""
|
||||||
|
|
||||||
@ -269,6 +302,9 @@ class HttpRpc(RpcChannel):
|
|||||||
self._GetAuthCookie(auth_token)
|
self._GetAuthCookie(auth_token)
|
||||||
self.authenticated = True
|
self.authenticated = True
|
||||||
if self.cookie_file is not None:
|
if self.cookie_file is not None:
|
||||||
|
print >>sys.stderr, \
|
||||||
|
'Saving authentication cookies to %s' \
|
||||||
|
% self.cookie_file
|
||||||
self.cookie_jar.save()
|
self.cookie_jar.save()
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -337,24 +373,8 @@ class HttpRpc(RpcChannel):
|
|||||||
opener.add_handler(urllib2.HTTPDefaultErrorHandler())
|
opener.add_handler(urllib2.HTTPDefaultErrorHandler())
|
||||||
opener.add_handler(urllib2.HTTPSHandler())
|
opener.add_handler(urllib2.HTTPSHandler())
|
||||||
opener.add_handler(urllib2.HTTPErrorProcessor())
|
opener.add_handler(urllib2.HTTPErrorProcessor())
|
||||||
if self.cookie_file is not None:
|
|
||||||
self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
|
self.cookie_jar, \
|
||||||
if os.path.exists(self.cookie_file):
|
self.authenticated = _open_jar(self.cookie_file)
|
||||||
try:
|
|
||||||
self.cookie_jar.load()
|
|
||||||
self.authenticated = True
|
|
||||||
except (cookielib.LoadError, IOError):
|
|
||||||
# Failed to load cookies - just ignore them.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Create an empty cookie file with mode 600
|
|
||||||
fd = os.open(self.cookie_file, os.O_CREAT, 0600)
|
|
||||||
os.close(fd)
|
|
||||||
# Always chmod the cookie file
|
|
||||||
os.chmod(self.cookie_file, 0600)
|
|
||||||
else:
|
|
||||||
# Don't save cookies across runs of update.py.
|
|
||||||
self.cookie_jar = cookielib.CookieJar()
|
|
||||||
opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
|
opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
|
||||||
return opener
|
return opener
|
||||||
|
|
||||||
|
@ -27,23 +27,35 @@ _UPLOADBUNDLERESPONSE_CODETYPE = descriptor.EnumDescriptor(
|
|||||||
options=None,
|
options=None,
|
||||||
type=None),
|
type=None),
|
||||||
descriptor.EnumValueDescriptor(
|
descriptor.EnumValueDescriptor(
|
||||||
name='UNKNOWN_PROJECT', index=3, number=2,
|
name='UNKNOWN_CHANGE', index=3, number=9,
|
||||||
options=None,
|
options=None,
|
||||||
type=None),
|
type=None),
|
||||||
descriptor.EnumValueDescriptor(
|
descriptor.EnumValueDescriptor(
|
||||||
name='UNKNOWN_BRANCH', index=4, number=3,
|
name='CHANGE_CLOSED', index=4, number=10,
|
||||||
options=None,
|
options=None,
|
||||||
type=None),
|
type=None),
|
||||||
descriptor.EnumValueDescriptor(
|
descriptor.EnumValueDescriptor(
|
||||||
name='UNKNOWN_BUNDLE', index=5, number=5,
|
name='UNKNOWN_EMAIL', index=5, number=11,
|
||||||
options=None,
|
options=None,
|
||||||
type=None),
|
type=None),
|
||||||
descriptor.EnumValueDescriptor(
|
descriptor.EnumValueDescriptor(
|
||||||
name='NOT_BUNDLE_OWNER', index=6, number=6,
|
name='UNKNOWN_PROJECT', index=6, number=2,
|
||||||
options=None,
|
options=None,
|
||||||
type=None),
|
type=None),
|
||||||
descriptor.EnumValueDescriptor(
|
descriptor.EnumValueDescriptor(
|
||||||
name='BUNDLE_CLOSED', index=7, number=8,
|
name='UNKNOWN_BRANCH', index=7, number=3,
|
||||||
|
options=None,
|
||||||
|
type=None),
|
||||||
|
descriptor.EnumValueDescriptor(
|
||||||
|
name='UNKNOWN_BUNDLE', index=8, number=5,
|
||||||
|
options=None,
|
||||||
|
type=None),
|
||||||
|
descriptor.EnumValueDescriptor(
|
||||||
|
name='NOT_BUNDLE_OWNER', index=9, number=6,
|
||||||
|
options=None,
|
||||||
|
type=None),
|
||||||
|
descriptor.EnumValueDescriptor(
|
||||||
|
name='BUNDLE_CLOSED', index=10, number=8,
|
||||||
options=None,
|
options=None,
|
||||||
type=None),
|
type=None),
|
||||||
],
|
],
|
||||||
@ -51,6 +63,35 @@ _UPLOADBUNDLERESPONSE_CODETYPE = descriptor.EnumDescriptor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_REPLACEPATCHSET = descriptor.Descriptor(
|
||||||
|
name='ReplacePatchSet',
|
||||||
|
full_name='codereview.ReplacePatchSet',
|
||||||
|
filename='upload_bundle.proto',
|
||||||
|
containing_type=None,
|
||||||
|
fields=[
|
||||||
|
descriptor.FieldDescriptor(
|
||||||
|
name='change_id', full_name='codereview.ReplacePatchSet.change_id', index=0,
|
||||||
|
number=1, type=9, cpp_type=9, label=2,
|
||||||
|
default_value=unicode("", "utf-8"),
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
|
descriptor.FieldDescriptor(
|
||||||
|
name='object_id', full_name='codereview.ReplacePatchSet.object_id', index=1,
|
||||||
|
number=2, type=9, cpp_type=9, label=2,
|
||||||
|
default_value=unicode("", "utf-8"),
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
|
],
|
||||||
|
extensions=[
|
||||||
|
],
|
||||||
|
nested_types=[], # TODO(robinson): Implement.
|
||||||
|
enum_types=[
|
||||||
|
],
|
||||||
|
options=None)
|
||||||
|
|
||||||
|
|
||||||
_UPLOADBUNDLEREQUEST = descriptor.Descriptor(
|
_UPLOADBUNDLEREQUEST = descriptor.Descriptor(
|
||||||
name='UploadBundleRequest',
|
name='UploadBundleRequest',
|
||||||
full_name='codereview.UploadBundleRequest',
|
full_name='codereview.UploadBundleRequest',
|
||||||
@ -92,6 +133,27 @@ _UPLOADBUNDLEREQUEST = descriptor.Descriptor(
|
|||||||
message_type=None, enum_type=None, containing_type=None,
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
is_extension=False, extension_scope=None,
|
is_extension=False, extension_scope=None,
|
||||||
options=None),
|
options=None),
|
||||||
|
descriptor.FieldDescriptor(
|
||||||
|
name='replace', full_name='codereview.UploadBundleRequest.replace', index=5,
|
||||||
|
number=2, type=11, cpp_type=10, label=3,
|
||||||
|
default_value=[],
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
|
descriptor.FieldDescriptor(
|
||||||
|
name='reviewers', full_name='codereview.UploadBundleRequest.reviewers', index=6,
|
||||||
|
number=3, type=9, cpp_type=9, label=3,
|
||||||
|
default_value=[],
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
|
descriptor.FieldDescriptor(
|
||||||
|
name='cc', full_name='codereview.UploadBundleRequest.cc', index=7,
|
||||||
|
number=4, type=9, cpp_type=9, label=3,
|
||||||
|
default_value=[],
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
],
|
],
|
||||||
extensions=[
|
extensions=[
|
||||||
],
|
],
|
||||||
@ -121,6 +183,20 @@ _UPLOADBUNDLERESPONSE = descriptor.Descriptor(
|
|||||||
message_type=None, enum_type=None, containing_type=None,
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
is_extension=False, extension_scope=None,
|
is_extension=False, extension_scope=None,
|
||||||
options=None),
|
options=None),
|
||||||
|
descriptor.FieldDescriptor(
|
||||||
|
name='invalid_reviewers', full_name='codereview.UploadBundleResponse.invalid_reviewers', index=2,
|
||||||
|
number=12, type=9, cpp_type=9, label=3,
|
||||||
|
default_value=[],
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
|
descriptor.FieldDescriptor(
|
||||||
|
name='invalid_cc', full_name='codereview.UploadBundleResponse.invalid_cc', index=3,
|
||||||
|
number=13, type=9, cpp_type=9, label=3,
|
||||||
|
default_value=[],
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
],
|
],
|
||||||
extensions=[
|
extensions=[
|
||||||
],
|
],
|
||||||
@ -174,8 +250,13 @@ _UPLOADBUNDLECONTINUE = descriptor.Descriptor(
|
|||||||
options=None)
|
options=None)
|
||||||
|
|
||||||
|
|
||||||
|
_UPLOADBUNDLEREQUEST.fields_by_name['replace'].message_type = _REPLACEPATCHSET
|
||||||
_UPLOADBUNDLERESPONSE.fields_by_name['status_code'].enum_type = _UPLOADBUNDLERESPONSE_CODETYPE
|
_UPLOADBUNDLERESPONSE.fields_by_name['status_code'].enum_type = _UPLOADBUNDLERESPONSE_CODETYPE
|
||||||
|
|
||||||
|
class ReplacePatchSet(message.Message):
|
||||||
|
__metaclass__ = reflection.GeneratedProtocolMessageType
|
||||||
|
DESCRIPTOR = _REPLACEPATCHSET
|
||||||
|
|
||||||
class UploadBundleRequest(message.Message):
|
class UploadBundleRequest(message.Message):
|
||||||
__metaclass__ = reflection.GeneratedProtocolMessageType
|
__metaclass__ = reflection.GeneratedProtocolMessageType
|
||||||
DESCRIPTOR = _UPLOADBUNDLEREQUEST
|
DESCRIPTOR = _UPLOADBUNDLEREQUEST
|
||||||
|
2
color.py
2
color.py
@ -137,7 +137,7 @@ class Coloring(object):
|
|||||||
if v is None:
|
if v is None:
|
||||||
return _Color(fg, bg, attr)
|
return _Color(fg, bg, attr)
|
||||||
|
|
||||||
v = v.trim().lowercase()
|
v = v.strip().lower()
|
||||||
if v == "reset":
|
if v == "reset":
|
||||||
return RESET
|
return RESET
|
||||||
elif v == '':
|
elif v == '':
|
||||||
|
200
docs/manifest-format.txt
Normal file
200
docs/manifest-format.txt
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
repo Manifest Format
|
||||||
|
====================
|
||||||
|
|
||||||
|
A repo manifest describes the structure of a repo client; that is
|
||||||
|
the directories that are visible and where they should be obtained
|
||||||
|
from with git.
|
||||||
|
|
||||||
|
The basic structure of a manifest is a bare Git repository holding
|
||||||
|
a single 'default.xml' XML file in the top level directory.
|
||||||
|
|
||||||
|
Manifests are inherently version controlled, since they are kept
|
||||||
|
within a Git repository. Updates to manifests are automatically
|
||||||
|
obtained by clients during `repo sync`.
|
||||||
|
|
||||||
|
|
||||||
|
XML File Format
|
||||||
|
---------------
|
||||||
|
|
||||||
|
A manifest XML file (e.g. 'default.xml') roughly conforms to the
|
||||||
|
following DTD:
|
||||||
|
|
||||||
|
<!DOCTYPE manifest [
|
||||||
|
<!ELEMENT manifest (remote*,
|
||||||
|
default?,
|
||||||
|
remove-project*,
|
||||||
|
project*,
|
||||||
|
add-remote*)>
|
||||||
|
|
||||||
|
<!ELEMENT remote (EMPTY)>
|
||||||
|
<!ATTLIST remote name ID #REQUIRED>
|
||||||
|
<!ATTLIST remote fetch CDATA #REQUIRED>
|
||||||
|
<!ATTLIST remote review CDATA #IMPLIED>
|
||||||
|
<!ATTLIST remote project-name CDATA #IMPLIED>
|
||||||
|
|
||||||
|
<!ELEMENT default (EMPTY)>
|
||||||
|
<!ATTLIST default remote IDREF #IMPLIED>
|
||||||
|
<!ATTLIST default revision CDATA #IMPLIED>
|
||||||
|
|
||||||
|
<!ELEMENT project (remote*)>
|
||||||
|
<!ATTLIST project name CDATA #REQUIRED>
|
||||||
|
<!ATTLIST project path CDATA #IMPLIED>
|
||||||
|
<!ATTLIST project remote IDREF #IMPLIED>
|
||||||
|
<!ATTLIST project revision CDATA #IMPLIED>
|
||||||
|
|
||||||
|
<!ELEMENT add-remote (EMPTY)>
|
||||||
|
<!ATTLIST add-remote to-project ID #REQUIRED>
|
||||||
|
<!ATTLIST add-remote name ID #REQUIRED>
|
||||||
|
<!ATTLIST add-remote fetch CDATA #REQUIRED>
|
||||||
|
<!ATTLIST add-remote review CDATA #IMPLIED>
|
||||||
|
<!ATTLIST add-remote project-name CDATA #IMPLIED>
|
||||||
|
|
||||||
|
<!ELEMENT remove-project (EMPTY)>
|
||||||
|
<!ATTLIST remove-project name CDATA #REQUIRED>
|
||||||
|
]>
|
||||||
|
|
||||||
|
A description of the elements and their attributes follows.
|
||||||
|
|
||||||
|
|
||||||
|
Element manifest
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The root element of the file.
|
||||||
|
|
||||||
|
|
||||||
|
Element remote
|
||||||
|
--------------
|
||||||
|
|
||||||
|
One or more remote elements may be specified. Each remote element
|
||||||
|
specifies a Git URL shared by one or more projects and (optionally)
|
||||||
|
the Gerrit review server those projects upload changes through.
|
||||||
|
|
||||||
|
Attribute `name`: A short name unique to this manifest file. The
|
||||||
|
name specified here is used as the remote name in each project's
|
||||||
|
.git/config, and is therefore automatically available to commands
|
||||||
|
like `git fetch`, `git remote`, `git pull` and `git push`.
|
||||||
|
|
||||||
|
Attribute `fetch`: The Git URL prefix for all projects which use
|
||||||
|
this remote. Each project's name is appended to this prefix to
|
||||||
|
form the actual URL used to clone the project.
|
||||||
|
|
||||||
|
Attribute `review`: Hostname of the Gerrit server where reviews
|
||||||
|
are uploaded to by `repo upload`. This attribute is optional;
|
||||||
|
if not specified then `repo upload` will not function.
|
||||||
|
|
||||||
|
Attribute `project-name`: Specifies the name of this project used
|
||||||
|
by the review server given in the review attribute of this element.
|
||||||
|
Only permitted when the remote element is nested inside of a project
|
||||||
|
element (see below). If not given, defaults to the name supplied
|
||||||
|
in the project's name attribute.
|
||||||
|
|
||||||
|
Element add-remote
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Adds a remote to an existing project, whose name is given by the
|
||||||
|
to-project attribute. This is functionally equivalent to nesting
|
||||||
|
a remote element under the project, but has the advantage that it
|
||||||
|
can be specified in the uesr's `local_manifest.xml` to add a remote
|
||||||
|
to a project declared by the normal manifest.
|
||||||
|
|
||||||
|
The element can be used to add a fork of an existing project that
|
||||||
|
the user needs to work with.
|
||||||
|
|
||||||
|
|
||||||
|
Element default
|
||||||
|
---------------
|
||||||
|
|
||||||
|
At most one default element may be specified. Its remote and
|
||||||
|
revision attributes are used when a project element does not
|
||||||
|
specify its own remote or revision attribute.
|
||||||
|
|
||||||
|
Attribute `remote`: Name of a previously defined remote element.
|
||||||
|
Project elements lacking a remote attribute of their own will use
|
||||||
|
this remote.
|
||||||
|
|
||||||
|
Attribute `revision`: Name of a Git branch (e.g. `master` or
|
||||||
|
`refs/heads/master`). Project elements lacking their own
|
||||||
|
revision attribute will use this revision.
|
||||||
|
|
||||||
|
|
||||||
|
Element project
|
||||||
|
---------------
|
||||||
|
|
||||||
|
One or more project elements may be specified. Each element
|
||||||
|
describes a single Git repository to be cloned into the repo
|
||||||
|
client workspace.
|
||||||
|
|
||||||
|
Attribute `name`: A unique name for this project. The project's
|
||||||
|
name is appended onto its remote's fetch URL to generate the actual
|
||||||
|
URL to configure the Git remote with. The URL gets formed as:
|
||||||
|
|
||||||
|
${remote_fetch}/${project_name}.git
|
||||||
|
|
||||||
|
where ${remote_fetch} is the remote's fetch attribute and
|
||||||
|
${project_name} is the project's name attribute. The suffix ".git"
|
||||||
|
is always appended as repo assumes the upstream is a forrest of
|
||||||
|
bare Git repositories.
|
||||||
|
|
||||||
|
The project name must match the name Gerrit knows, if Gerrit is
|
||||||
|
being used for code reviews.
|
||||||
|
|
||||||
|
Attribute `path`: An optional path relative to the top directory
|
||||||
|
of the repo client where the Git working directory for this project
|
||||||
|
should be placed. If not supplied the project name is used.
|
||||||
|
|
||||||
|
Attribute `remote`: Name of a previously defined remote element.
|
||||||
|
If not supplied the remote given by the default element is used.
|
||||||
|
|
||||||
|
Attribute `revision`: Name of the Git branch the manifest wants
|
||||||
|
to track for this project. Names can be relative to refs/heads
|
||||||
|
(e.g. just "master") or absolute (e.g. "refs/heads/master").
|
||||||
|
Tags and/or explicit SHA-1s should work in theory, but have not
|
||||||
|
been extensively tested. If not supplied the revision given by
|
||||||
|
the default element is used.
|
||||||
|
|
||||||
|
Child element `remote`: Described like the top-level remote element,
|
||||||
|
but adds an additional remote to only this project. These additional
|
||||||
|
remotes are fetched from first on the initial `repo sync`, causing
|
||||||
|
the majority of the project's object database to be obtained through
|
||||||
|
these additional remotes.
|
||||||
|
|
||||||
|
|
||||||
|
Element remove-project
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Deletes the named project from the internal manifest table, possibly
|
||||||
|
allowing a subsequent project element in the same manifest file to
|
||||||
|
replace the project with a different source.
|
||||||
|
|
||||||
|
This element is mostly useful in the local_manifest.xml, where
|
||||||
|
the user can remove a project, and possibly replace it with their
|
||||||
|
own definition.
|
||||||
|
|
||||||
|
|
||||||
|
Local Manifest
|
||||||
|
==============
|
||||||
|
|
||||||
|
Additional remotes and projects may be added through a local
|
||||||
|
manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
----
|
||||||
|
$ cat .repo/local_manifest.xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<manifest>
|
||||||
|
<project path="manifest"
|
||||||
|
name="tools/manifest" />
|
||||||
|
<project path="platform-manifest"
|
||||||
|
name="platform/manifest" />
|
||||||
|
</manifest>
|
||||||
|
----
|
||||||
|
|
||||||
|
Users may add projects to the local manifest prior to a `repo sync`
|
||||||
|
invocation, instructing repo to automatically download and manage
|
||||||
|
these extra projects.
|
||||||
|
|
||||||
|
Currently the only supported feature of a local manifest is to
|
||||||
|
add new remotes and/or projects. In the future a local manifest
|
||||||
|
may support picking different revisions of a project, or deleting
|
||||||
|
projects specified in the default manifest.
|
@ -69,14 +69,14 @@ least one of these before using this command."""
|
|||||||
Returns:
|
Returns:
|
||||||
new value of edited text; None if editing did not succeed
|
new value of edited text; None if editing did not succeed
|
||||||
"""
|
"""
|
||||||
editor = cls._GetEditor()
|
editor = cls._GetEditor().split()
|
||||||
fd, path = tempfile.mkstemp()
|
fd, path = tempfile.mkstemp()
|
||||||
try:
|
try:
|
||||||
os.write(fd, data)
|
os.write(fd, data)
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
fd = None
|
fd = None
|
||||||
|
|
||||||
if subprocess.Popen([editor, path]).wait() != 0:
|
if subprocess.Popen(editor + [path]).wait() != 0:
|
||||||
raise EditorError()
|
raise EditorError()
|
||||||
return open(path).read()
|
return open(path).read()
|
||||||
finally:
|
finally:
|
||||||
|
2
error.py
2
error.py
@ -64,3 +64,5 @@ class RepoChangedException(Exception):
|
|||||||
repo or manifest repositories. In this special case we must
|
repo or manifest repositories. In this special case we must
|
||||||
use exec to re-execute repo with the new code and manifest.
|
use exec to re-execute repo with the new code and manifest.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, extra_args=[]):
|
||||||
|
self.extra_args = extra_args
|
||||||
|
@ -75,6 +75,8 @@ def UploadBundle(project,
|
|||||||
dest_branch,
|
dest_branch,
|
||||||
src_branch,
|
src_branch,
|
||||||
bases,
|
bases,
|
||||||
|
people,
|
||||||
|
replace_changes = None,
|
||||||
save_cookies=True):
|
save_cookies=True):
|
||||||
|
|
||||||
srv = _GetRpcServer(email, server, save_cookies)
|
srv = _GetRpcServer(email, server, save_cookies)
|
||||||
@ -111,8 +113,17 @@ def UploadBundle(project,
|
|||||||
req = UploadBundleRequest()
|
req = UploadBundleRequest()
|
||||||
req.dest_project = str(dest_project)
|
req.dest_project = str(dest_project)
|
||||||
req.dest_branch = str(dest_branch)
|
req.dest_branch = str(dest_branch)
|
||||||
|
for e in people[0]:
|
||||||
|
req.reviewers.append(e)
|
||||||
|
for e in people[1]:
|
||||||
|
req.cc.append(e)
|
||||||
for c in revlist:
|
for c in revlist:
|
||||||
req.contained_object.append(c)
|
req.contained_object.append(c)
|
||||||
|
if replace_changes:
|
||||||
|
for change_id,commit_id in replace_changes.iteritems():
|
||||||
|
r = req.replace.add()
|
||||||
|
r.change_id = change_id
|
||||||
|
r.object_id = commit_id
|
||||||
else:
|
else:
|
||||||
req = UploadBundleContinue()
|
req = UploadBundleContinue()
|
||||||
req.bundle_id = bundle_id
|
req.bundle_id = bundle_id
|
||||||
@ -148,6 +159,14 @@ def UploadBundle(project,
|
|||||||
elif rsp.status_code == UploadBundleResponse.UNAUTHORIZED_USER:
|
elif rsp.status_code == UploadBundleResponse.UNAUTHORIZED_USER:
|
||||||
reason = ('Unauthorized user. Visit http://%s/hello to sign up.'
|
reason = ('Unauthorized user. Visit http://%s/hello to sign up.'
|
||||||
% server)
|
% server)
|
||||||
|
elif rsp.status_code == UploadBundleResponse.UNKNOWN_CHANGE:
|
||||||
|
reason = 'invalid change id'
|
||||||
|
elif rsp.status_code == UploadBundleResponse.CHANGE_CLOSED:
|
||||||
|
reason = 'one or more changes are closed'
|
||||||
|
elif rsp.status_code == UploadBundleResponse.UNKNOWN_EMAIL:
|
||||||
|
emails = [x for x in rsp.invalid_reviewers] + [
|
||||||
|
x for x in rsp.invalid_cc]
|
||||||
|
reason = 'invalid email addresses: %s' % ", ".join(emails)
|
||||||
else:
|
else:
|
||||||
reason = 'unknown error ' + str(rsp.status_code)
|
reason = 'unknown error ' + str(rsp.status_code)
|
||||||
raise UploadError(reason)
|
raise UploadError(reason)
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from error import GitError
|
from urllib2 import urlopen, HTTPError
|
||||||
|
from error import GitError, UploadError
|
||||||
from git_command import GitCommand
|
from git_command import GitCommand
|
||||||
|
|
||||||
R_HEADS = 'refs/heads/'
|
R_HEADS = 'refs/heads/'
|
||||||
@ -258,8 +259,48 @@ class Remote(object):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.url = self._Get('url')
|
self.url = self._Get('url')
|
||||||
self.review = self._Get('review')
|
self.review = self._Get('review')
|
||||||
|
self.projectname = self._Get('projectname')
|
||||||
self.fetch = map(lambda x: RefSpec.FromString(x),
|
self.fetch = map(lambda x: RefSpec.FromString(x),
|
||||||
self._Get('fetch', all=True))
|
self._Get('fetch', all=True))
|
||||||
|
self._review_protocol = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ReviewProtocol(self):
|
||||||
|
if self._review_protocol is None:
|
||||||
|
if self.review is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
u = self.review
|
||||||
|
if not u.startswith('http:') and not u.startswith('https:'):
|
||||||
|
u = 'http://%s' % u
|
||||||
|
if not u.endswith('/'):
|
||||||
|
u += '/'
|
||||||
|
u += 'ssh_info'
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = urlopen(u).read()
|
||||||
|
if info == 'NOT_AVAILABLE':
|
||||||
|
raise UploadError('Upload over ssh unavailable')
|
||||||
|
|
||||||
|
self._review_protocol = 'ssh'
|
||||||
|
self._review_host = info.split(" ")[0]
|
||||||
|
self._review_port = info.split(" ")[1]
|
||||||
|
|
||||||
|
except HTTPError, e:
|
||||||
|
if e.code == 404:
|
||||||
|
self._review_protocol = 'http-post'
|
||||||
|
else:
|
||||||
|
raise UploadError('Cannot guess Gerrit version')
|
||||||
|
return self._review_protocol
|
||||||
|
|
||||||
|
def SshReviewUrl(self, userEmail):
|
||||||
|
if self.ReviewProtocol != 'ssh':
|
||||||
|
return None
|
||||||
|
return 'ssh://%s@%s:%s/%s' % (
|
||||||
|
userEmail.split("@")[0],
|
||||||
|
self._review_host,
|
||||||
|
self._review_port,
|
||||||
|
self.projectname)
|
||||||
|
|
||||||
def ToLocal(self, rev):
|
def ToLocal(self, rev):
|
||||||
"""Convert a remote revision string to something we have locally.
|
"""Convert a remote revision string to something we have locally.
|
||||||
@ -285,18 +326,21 @@ class Remote(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def ResetFetch(self):
|
def ResetFetch(self, mirror=False):
|
||||||
"""Set the fetch refspec to its default value.
|
"""Set the fetch refspec to its default value.
|
||||||
"""
|
"""
|
||||||
self.fetch = [RefSpec(True,
|
if mirror:
|
||||||
'refs/heads/*',
|
dst = 'refs/heads/*'
|
||||||
'refs/remotes/%s/*' % self.name)]
|
else:
|
||||||
|
dst = 'refs/remotes/%s/*' % self.name
|
||||||
|
self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
|
||||||
|
|
||||||
def Save(self):
|
def Save(self):
|
||||||
"""Save this remote to the configuration.
|
"""Save this remote to the configuration.
|
||||||
"""
|
"""
|
||||||
self._Set('url', self.url)
|
self._Set('url', self.url)
|
||||||
self._Set('review', self.review)
|
self._Set('review', self.review)
|
||||||
|
self._Set('projectname', self.projectname)
|
||||||
self._Set('fetch', map(lambda x: str(x), self.fetch))
|
self._Set('fetch', map(lambda x: str(x), self.fetch))
|
||||||
|
|
||||||
def _Set(self, key, value):
|
def _Set(self, key, value):
|
||||||
|
44
hooks/pre-auto-gc
Executable file
44
hooks/pre-auto-gc
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify if you are on battery, in case you
|
||||||
|
# are running Linux or OS X. Called by git-gc --auto with no arguments.
|
||||||
|
# The hook should exit with non-zero status after issuing an appropriate
|
||||||
|
# message if it wants to stop the auto repacking.
|
||||||
|
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
if test -x /sbin/on_ac_power && /sbin/on_ac_power
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
elif test "$(cat /sys/class/power_supply/AC/online 2>/dev/null)" = 1
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
elif grep -q 'on-line' /proc/acpi/ac_adapter/AC/state 2>/dev/null
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
elif grep -q '0x01$' /proc/apm 2>/dev/null
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt |
|
||||||
|
grep -q "Currently drawing from 'AC Power'"
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Auto packing deferred; not on AC"
|
||||||
|
exit 1
|
8
main.py
8
main.py
@ -186,11 +186,13 @@ def _Main(argv):
|
|||||||
repo._Run(argv)
|
repo._Run(argv)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except RepoChangedException:
|
except RepoChangedException, rce:
|
||||||
# If the repo or manifest changed, re-exec ourselves.
|
# If repo changed, re-exec ourselves.
|
||||||
#
|
#
|
||||||
|
argv = list(sys.argv)
|
||||||
|
argv.extend(rce.extra_args)
|
||||||
try:
|
try:
|
||||||
os.execv(__file__, sys.argv)
|
os.execv(__file__, argv)
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
print >>sys.stderr, 'fatal: cannot restart repo after upgrade'
|
print >>sys.stderr, 'fatal: cannot restart repo after upgrade'
|
||||||
print >>sys.stderr, 'fatal: %s' % e
|
print >>sys.stderr, 'fatal: %s' % e
|
||||||
|
127
manifest.py
127
manifest.py
@ -18,7 +18,7 @@ import sys
|
|||||||
import xml.dom.minidom
|
import xml.dom.minidom
|
||||||
|
|
||||||
from git_config import GitConfig, IsId
|
from git_config import GitConfig, IsId
|
||||||
from project import Project, MetaProject, R_TAGS
|
from project import Project, MetaProject, R_HEADS
|
||||||
from remote import Remote
|
from remote import Remote
|
||||||
from error import ManifestParseError
|
from error import ManifestParseError
|
||||||
|
|
||||||
@ -45,16 +45,9 @@ class Manifest(object):
|
|||||||
gitdir = os.path.join(repodir, 'repo/.git'),
|
gitdir = os.path.join(repodir, 'repo/.git'),
|
||||||
worktree = os.path.join(repodir, 'repo'))
|
worktree = os.path.join(repodir, 'repo'))
|
||||||
|
|
||||||
wt = os.path.join(repodir, 'manifests')
|
|
||||||
gd_new = os.path.join(repodir, 'manifests.git')
|
|
||||||
gd_old = os.path.join(wt, '.git')
|
|
||||||
if os.path.exists(gd_new) or not os.path.exists(gd_old):
|
|
||||||
gd = gd_new
|
|
||||||
else:
|
|
||||||
gd = gd_old
|
|
||||||
self.manifestProject = MetaProject(self, 'manifests',
|
self.manifestProject = MetaProject(self, 'manifests',
|
||||||
gitdir = gd,
|
gitdir = os.path.join(repodir, 'manifests.git'),
|
||||||
worktree = wt)
|
worktree = os.path.join(repodir, 'manifests'))
|
||||||
|
|
||||||
self._Unload()
|
self._Unload()
|
||||||
|
|
||||||
@ -95,6 +88,10 @@ class Manifest(object):
|
|||||||
self._Load()
|
self._Load()
|
||||||
return self._default
|
return self._default
|
||||||
|
|
||||||
|
@property
|
||||||
|
def IsMirror(self):
|
||||||
|
return self.manifestProject.config.GetBoolean('repo.mirror')
|
||||||
|
|
||||||
def _Unload(self):
|
def _Unload(self):
|
||||||
self._loaded = False
|
self._loaded = False
|
||||||
self._projects = {}
|
self._projects = {}
|
||||||
@ -104,6 +101,12 @@ class Manifest(object):
|
|||||||
|
|
||||||
def _Load(self):
|
def _Load(self):
|
||||||
if not self._loaded:
|
if not self._loaded:
|
||||||
|
m = self.manifestProject
|
||||||
|
b = m.GetBranch(m.CurrentBranch).merge
|
||||||
|
if b.startswith(R_HEADS):
|
||||||
|
b = b[len(R_HEADS):]
|
||||||
|
self.branch = b
|
||||||
|
|
||||||
self._ParseManifest(True)
|
self._ParseManifest(True)
|
||||||
|
|
||||||
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
|
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
|
||||||
@ -115,6 +118,10 @@ class Manifest(object):
|
|||||||
finally:
|
finally:
|
||||||
self.manifestFile = real
|
self.manifestFile = real
|
||||||
|
|
||||||
|
if self.IsMirror:
|
||||||
|
self._AddMetaProjectMirror(self.repoProject)
|
||||||
|
self._AddMetaProjectMirror(self.manifestProject)
|
||||||
|
|
||||||
self._loaded = True
|
self._loaded = True
|
||||||
|
|
||||||
def _ParseManifest(self, is_root_file):
|
def _ParseManifest(self, is_root_file):
|
||||||
@ -130,10 +137,15 @@ class Manifest(object):
|
|||||||
"no <manifest> in %s" % \
|
"no <manifest> in %s" % \
|
||||||
self.manifestFile
|
self.manifestFile
|
||||||
|
|
||||||
if is_root_file:
|
for node in config.childNodes:
|
||||||
self.branch = config.getAttribute('branch')
|
if node.nodeName == 'remove-project':
|
||||||
if not self.branch:
|
name = self._reqatt(node, 'name')
|
||||||
self.branch = 'default'
|
try:
|
||||||
|
del self._projects[name]
|
||||||
|
except KeyError:
|
||||||
|
raise ManifestParseError, \
|
||||||
|
'project %s not found' % \
|
||||||
|
(name)
|
||||||
|
|
||||||
for node in config.childNodes:
|
for node in config.childNodes:
|
||||||
if node.nodeName == 'remote':
|
if node.nodeName == 'remote':
|
||||||
@ -163,6 +175,50 @@ class Manifest(object):
|
|||||||
(project.name, self.manifestFile)
|
(project.name, self.manifestFile)
|
||||||
self._projects[project.name] = project
|
self._projects[project.name] = project
|
||||||
|
|
||||||
|
for node in config.childNodes:
|
||||||
|
if node.nodeName == 'add-remote':
|
||||||
|
pn = self._reqatt(node, 'to-project')
|
||||||
|
project = self._projects.get(pn)
|
||||||
|
if not project:
|
||||||
|
raise ManifestParseError, \
|
||||||
|
'project %s not defined in %s' % \
|
||||||
|
(pn, self.manifestFile)
|
||||||
|
self._ParseProjectExtraRemote(project, node)
|
||||||
|
|
||||||
|
def _AddMetaProjectMirror(self, m):
|
||||||
|
name = None
|
||||||
|
m_url = m.GetRemote(m.remote.name).url
|
||||||
|
if m_url.endswith('/.git'):
|
||||||
|
raise ManifestParseError, 'refusing to mirror %s' % m_url
|
||||||
|
|
||||||
|
if self._default and self._default.remote:
|
||||||
|
url = self._default.remote.fetchUrl
|
||||||
|
if not url.endswith('/'):
|
||||||
|
url += '/'
|
||||||
|
if m_url.startswith(url):
|
||||||
|
remote = self._default.remote
|
||||||
|
name = m_url[len(url):]
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
s = m_url.rindex('/') + 1
|
||||||
|
remote = Remote('origin', fetch = m_url[:s])
|
||||||
|
name = m_url[s:]
|
||||||
|
|
||||||
|
if name.endswith('.git'):
|
||||||
|
name = name[:-4]
|
||||||
|
|
||||||
|
if name not in self._projects:
|
||||||
|
m.PreSync()
|
||||||
|
gitdir = os.path.join(self.topdir, '%s.git' % name)
|
||||||
|
project = Project(manifest = self,
|
||||||
|
name = name,
|
||||||
|
remote = remote,
|
||||||
|
gitdir = gitdir,
|
||||||
|
worktree = None,
|
||||||
|
relpath = None,
|
||||||
|
revision = m.revision)
|
||||||
|
self._projects[project.name] = project
|
||||||
|
|
||||||
def _ParseRemote(self, node):
|
def _ParseRemote(self, node):
|
||||||
"""
|
"""
|
||||||
reads a <remote> element from the manifest file
|
reads a <remote> element from the manifest file
|
||||||
@ -170,10 +226,17 @@ class Manifest(object):
|
|||||||
name = self._reqatt(node, 'name')
|
name = self._reqatt(node, 'name')
|
||||||
fetch = self._reqatt(node, 'fetch')
|
fetch = self._reqatt(node, 'fetch')
|
||||||
review = node.getAttribute('review')
|
review = node.getAttribute('review')
|
||||||
|
if review == '':
|
||||||
|
review = None
|
||||||
|
|
||||||
|
projectName = node.getAttribute('project-name')
|
||||||
|
if projectName == '':
|
||||||
|
projectName = None
|
||||||
|
|
||||||
r = Remote(name=name,
|
r = Remote(name=name,
|
||||||
fetch=fetch,
|
fetch=fetch,
|
||||||
review=review)
|
review=review,
|
||||||
|
projectName=projectName)
|
||||||
|
|
||||||
for n in node.childNodes:
|
for n in node.childNodes:
|
||||||
if n.nodeName == 'require':
|
if n.nodeName == 'require':
|
||||||
@ -188,6 +251,8 @@ class Manifest(object):
|
|||||||
d = _Default()
|
d = _Default()
|
||||||
d.remote = self._get_remote(node)
|
d.remote = self._get_remote(node)
|
||||||
d.revision = node.getAttribute('revision')
|
d.revision = node.getAttribute('revision')
|
||||||
|
if d.revision == '':
|
||||||
|
d.revision = None
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def _ParseProject(self, node):
|
def _ParseProject(self, node):
|
||||||
@ -220,8 +285,13 @@ class Manifest(object):
|
|||||||
"project %s path cannot be absolute in %s" % \
|
"project %s path cannot be absolute in %s" % \
|
||||||
(name, self.manifestFile)
|
(name, self.manifestFile)
|
||||||
|
|
||||||
worktree = os.path.join(self.topdir, path)
|
if self.IsMirror:
|
||||||
gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
|
relpath = None
|
||||||
|
worktree = None
|
||||||
|
gitdir = os.path.join(self.topdir, '%s.git' % name)
|
||||||
|
else:
|
||||||
|
worktree = os.path.join(self.topdir, path)
|
||||||
|
gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
|
||||||
|
|
||||||
project = Project(manifest = self,
|
project = Project(manifest = self,
|
||||||
name = name,
|
name = name,
|
||||||
@ -233,23 +303,28 @@ class Manifest(object):
|
|||||||
|
|
||||||
for n in node.childNodes:
|
for n in node.childNodes:
|
||||||
if n.nodeName == 'remote':
|
if n.nodeName == 'remote':
|
||||||
r = self._ParseRemote(n)
|
self._ParseProjectExtraRemote(project, n)
|
||||||
if project.extraRemotes.get(r.name) \
|
|
||||||
or project.remote.name == r.name:
|
|
||||||
raise ManifestParseError, \
|
|
||||||
'duplicate remote %s in project %s in %s' % \
|
|
||||||
(r.name, project.name, self.manifestFile)
|
|
||||||
project.extraRemotes[r.name] = r
|
|
||||||
elif n.nodeName == 'copyfile':
|
elif n.nodeName == 'copyfile':
|
||||||
self._ParseCopyFile(project, n)
|
self._ParseCopyFile(project, n)
|
||||||
|
|
||||||
return project
|
return project
|
||||||
|
|
||||||
|
def _ParseProjectExtraRemote(self, project, n):
|
||||||
|
r = self._ParseRemote(n)
|
||||||
|
if project.extraRemotes.get(r.name) \
|
||||||
|
or project.remote.name == r.name:
|
||||||
|
raise ManifestParseError, \
|
||||||
|
'duplicate remote %s in project %s in %s' % \
|
||||||
|
(r.name, project.name, self.manifestFile)
|
||||||
|
project.extraRemotes[r.name] = r
|
||||||
|
|
||||||
def _ParseCopyFile(self, project, node):
|
def _ParseCopyFile(self, project, node):
|
||||||
src = self._reqatt(node, 'src')
|
src = self._reqatt(node, 'src')
|
||||||
dest = self._reqatt(node, 'dest')
|
dest = self._reqatt(node, 'dest')
|
||||||
# src is project relative, and dest is relative to the top of the tree
|
if not self.IsMirror:
|
||||||
project.AddCopyFile(src, os.path.join(self.topdir, dest))
|
# src is project relative;
|
||||||
|
# dest is relative to the top of the tree
|
||||||
|
project.AddCopyFile(src, os.path.join(self.topdir, dest))
|
||||||
|
|
||||||
def _get_remote(self, node):
|
def _get_remote(self, node):
|
||||||
name = node.getAttribute('remote')
|
name = node.getAttribute('remote')
|
||||||
|
260
project.py
260
project.py
@ -12,6 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import errno
|
||||||
import filecmp
|
import filecmp
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -45,6 +46,34 @@ def _info(fmt, *args):
|
|||||||
def not_rev(r):
|
def not_rev(r):
|
||||||
return '^' + r
|
return '^' + r
|
||||||
|
|
||||||
|
def sq(r):
|
||||||
|
return "'" + r.replace("'", "'\''") + "'"
|
||||||
|
|
||||||
|
hook_list = None
|
||||||
|
def repo_hooks():
|
||||||
|
global hook_list
|
||||||
|
if hook_list is None:
|
||||||
|
d = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
d = os.path.join(d , 'hooks')
|
||||||
|
hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
|
||||||
|
return hook_list
|
||||||
|
|
||||||
|
def relpath(dst, src):
|
||||||
|
src = os.path.dirname(src)
|
||||||
|
top = os.path.commonprefix([dst, src])
|
||||||
|
if top.endswith('/'):
|
||||||
|
top = top[:-1]
|
||||||
|
else:
|
||||||
|
top = os.path.dirname(top)
|
||||||
|
|
||||||
|
tmp = src
|
||||||
|
rel = ''
|
||||||
|
while top != tmp:
|
||||||
|
rel += '../'
|
||||||
|
tmp = os.path.dirname(tmp)
|
||||||
|
return rel + dst[len(top) + 1:]
|
||||||
|
|
||||||
|
|
||||||
class DownloadedChange(object):
|
class DownloadedChange(object):
|
||||||
_commit_cache = None
|
_commit_cache = None
|
||||||
|
|
||||||
@ -77,6 +106,7 @@ class ReviewableBranch(object):
|
|||||||
self.project = project
|
self.project = project
|
||||||
self.branch = branch
|
self.branch = branch
|
||||||
self.base = base
|
self.base = base
|
||||||
|
self.replace_changes = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -96,6 +126,16 @@ class ReviewableBranch(object):
|
|||||||
'--')
|
'--')
|
||||||
return self._commit_cache
|
return self._commit_cache
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unabbrev_commits(self):
|
||||||
|
r = dict()
|
||||||
|
for commit in self.project.bare_git.rev_list(
|
||||||
|
not_rev(self.base),
|
||||||
|
R_HEADS + self.name,
|
||||||
|
'--'):
|
||||||
|
r[commit[0:8]] = commit
|
||||||
|
return r
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date(self):
|
def date(self):
|
||||||
return self.project.bare_git.log(
|
return self.project.bare_git.log(
|
||||||
@ -104,8 +144,10 @@ class ReviewableBranch(object):
|
|||||||
R_HEADS + self.name,
|
R_HEADS + self.name,
|
||||||
'--')
|
'--')
|
||||||
|
|
||||||
def UploadForReview(self):
|
def UploadForReview(self, people):
|
||||||
self.project.UploadForReview(self.name)
|
self.project.UploadForReview(self.name,
|
||||||
|
self.replace_changes,
|
||||||
|
people)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tip_url(self):
|
def tip_url(self):
|
||||||
@ -184,7 +226,10 @@ class Project(object):
|
|||||||
gitdir = self.gitdir,
|
gitdir = self.gitdir,
|
||||||
defaults = self.manifest.globalConfig)
|
defaults = self.manifest.globalConfig)
|
||||||
|
|
||||||
self.work_git = self._GitGetByExec(self, bare=False)
|
if self.worktree:
|
||||||
|
self.work_git = self._GitGetByExec(self, bare=False)
|
||||||
|
else:
|
||||||
|
self.work_git = None
|
||||||
self.bare_git = self._GitGetByExec(self, bare=True)
|
self.bare_git = self._GitGetByExec(self, bare=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -398,15 +443,23 @@ class Project(object):
|
|||||||
if branch in pubed and pubed[branch] == id:
|
if branch in pubed and pubed[branch] == id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
branch = self.GetBranch(branch)
|
rb = self.GetUploadableBranch(branch)
|
||||||
base = branch.LocalMerge
|
if rb:
|
||||||
if branch.LocalMerge:
|
ready.append(rb)
|
||||||
rb = ReviewableBranch(self, branch, base)
|
|
||||||
if rb.commits:
|
|
||||||
ready.append(rb)
|
|
||||||
return ready
|
return ready
|
||||||
|
|
||||||
def UploadForReview(self, branch=None):
|
def GetUploadableBranch(self, branch_name):
|
||||||
|
"""Get a single uploadable branch, or None.
|
||||||
|
"""
|
||||||
|
branch = self.GetBranch(branch_name)
|
||||||
|
base = branch.LocalMerge
|
||||||
|
if branch.LocalMerge:
|
||||||
|
rb = ReviewableBranch(self, branch, base)
|
||||||
|
if rb.commits:
|
||||||
|
return rb
|
||||||
|
return None
|
||||||
|
|
||||||
|
def UploadForReview(self, branch=None, replace_changes=None, people=([],[])):
|
||||||
"""Uploads the named branch for code review.
|
"""Uploads the named branch for code review.
|
||||||
"""
|
"""
|
||||||
if branch is None:
|
if branch is None:
|
||||||
@ -424,27 +477,58 @@ class Project(object):
|
|||||||
if not dest_branch.startswith(R_HEADS):
|
if not dest_branch.startswith(R_HEADS):
|
||||||
dest_branch = R_HEADS + dest_branch
|
dest_branch = R_HEADS + dest_branch
|
||||||
|
|
||||||
base_list = []
|
if not branch.remote.projectname:
|
||||||
for name, id in self._allrefs.iteritems():
|
branch.remote.projectname = self.name
|
||||||
if branch.remote.WritesTo(name):
|
branch.remote.Save()
|
||||||
base_list.append(not_rev(name))
|
|
||||||
if not base_list:
|
|
||||||
raise GitError('no base refs, cannot upload %s' % branch.name)
|
|
||||||
|
|
||||||
print >>sys.stderr, ''
|
if branch.remote.ReviewProtocol == 'http-post':
|
||||||
_info("Uploading %s to %s:", branch.name, self.name)
|
base_list = []
|
||||||
try:
|
for name, id in self._allrefs.iteritems():
|
||||||
UploadBundle(project = self,
|
if branch.remote.WritesTo(name):
|
||||||
server = branch.remote.review,
|
base_list.append(not_rev(name))
|
||||||
email = self.UserEmail,
|
if not base_list:
|
||||||
dest_project = self.name,
|
raise GitError('no base refs, cannot upload %s' % branch.name)
|
||||||
dest_branch = dest_branch,
|
|
||||||
src_branch = R_HEADS + branch.name,
|
print >>sys.stderr, ''
|
||||||
bases = base_list)
|
_info("Uploading %s to %s:", branch.name, self.name)
|
||||||
except proto_client.ClientLoginError:
|
try:
|
||||||
raise UploadError('Login failure')
|
UploadBundle(project = self,
|
||||||
except urllib2.HTTPError, e:
|
server = branch.remote.review,
|
||||||
raise UploadError('HTTP error %d' % e.code)
|
email = self.UserEmail,
|
||||||
|
dest_project = branch.remote.projectname,
|
||||||
|
dest_branch = dest_branch,
|
||||||
|
src_branch = R_HEADS + branch.name,
|
||||||
|
bases = base_list,
|
||||||
|
people = people,
|
||||||
|
replace_changes = replace_changes)
|
||||||
|
except proto_client.ClientLoginError:
|
||||||
|
raise UploadError('Login failure')
|
||||||
|
except urllib2.HTTPError, e:
|
||||||
|
raise UploadError('HTTP error %d' % e.code)
|
||||||
|
|
||||||
|
elif branch.remote.ReviewProtocol == 'ssh':
|
||||||
|
if dest_branch.startswith(R_HEADS):
|
||||||
|
dest_branch = dest_branch[len(R_HEADS):]
|
||||||
|
|
||||||
|
rp = ['gerrit receive-pack']
|
||||||
|
for e in people[0]:
|
||||||
|
rp.append('--reviewer=%s' % sq(e))
|
||||||
|
for e in people[1]:
|
||||||
|
rp.append('--cc=%s' % sq(e))
|
||||||
|
|
||||||
|
cmd = ['push']
|
||||||
|
cmd.append('--receive-pack=%s' % " ".join(rp))
|
||||||
|
cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
|
||||||
|
cmd.append('%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch))
|
||||||
|
if replace_changes:
|
||||||
|
for change_id,commit_id in replace_changes.iteritems():
|
||||||
|
cmd.append('%s:refs/changes/%s/new' % (commit_id, change_id))
|
||||||
|
if GitCommand(self, cmd, bare = True).Wait() != 0:
|
||||||
|
raise UploadError('Upload failed')
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise UploadError('Unsupported protocol %s' \
|
||||||
|
% branch.remote.review)
|
||||||
|
|
||||||
msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
|
msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
|
||||||
self.bare_git.UpdateRef(R_PUB + branch.name,
|
self.bare_git.UpdateRef(R_PUB + branch.name,
|
||||||
@ -462,16 +546,28 @@ class Project(object):
|
|||||||
print >>sys.stderr
|
print >>sys.stderr
|
||||||
print >>sys.stderr, 'Initializing project %s ...' % self.name
|
print >>sys.stderr, 'Initializing project %s ...' % self.name
|
||||||
self._InitGitDir()
|
self._InitGitDir()
|
||||||
|
|
||||||
self._InitRemote()
|
self._InitRemote()
|
||||||
for r in self.extraRemotes.values():
|
for r in self.extraRemotes.values():
|
||||||
if not self._RemoteFetch(r.name):
|
if not self._RemoteFetch(r.name):
|
||||||
return False
|
return False
|
||||||
if not self._RemoteFetch():
|
if not self._RemoteFetch():
|
||||||
return False
|
return False
|
||||||
self._RepairAndroidImportErrors()
|
|
||||||
self._InitMRef()
|
if self.worktree:
|
||||||
|
self._RepairAndroidImportErrors()
|
||||||
|
self._InitMRef()
|
||||||
|
else:
|
||||||
|
self._InitMirrorHead()
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def PostRepoUpgrade(self):
|
||||||
|
self._InitHooks()
|
||||||
|
|
||||||
def _CopyFiles(self):
|
def _CopyFiles(self):
|
||||||
for file in self.copyfiles:
|
for file in self.copyfiles:
|
||||||
file._Copy()
|
file._Copy()
|
||||||
@ -563,6 +659,19 @@ class Project(object):
|
|||||||
_info("[%s] Consider merging or rebasing the"
|
_info("[%s] Consider merging or rebasing the"
|
||||||
" unpublished commits.", self.name)
|
" unpublished commits.", self.name)
|
||||||
return True
|
return True
|
||||||
|
elif upstream_gain:
|
||||||
|
# We can fast-forward safely.
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
self._FastForward(rev)
|
||||||
|
except GitError:
|
||||||
|
return False
|
||||||
|
self._CopyFiles()
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# Trivially no changes in the upstream.
|
||||||
|
#
|
||||||
|
return True
|
||||||
|
|
||||||
if merge == rev:
|
if merge == rev:
|
||||||
try:
|
try:
|
||||||
@ -667,6 +776,22 @@ class Project(object):
|
|||||||
else:
|
else:
|
||||||
raise GitError('%s checkout %s ' % (self.name, rev))
|
raise GitError('%s checkout %s ' % (self.name, rev))
|
||||||
|
|
||||||
|
def AbandonBranch(self, name):
|
||||||
|
"""Destroy a local topic branch.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tip_rev = self.bare_git.rev_parse(R_HEADS + name)
|
||||||
|
except GitError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.CurrentBranch == name:
|
||||||
|
self._Checkout(
|
||||||
|
self.GetRemote(self.remote.name).ToLocal(self.revision),
|
||||||
|
quiet=True)
|
||||||
|
|
||||||
|
cmd = ['branch', '-D', name]
|
||||||
|
GitCommand(self, cmd, capture_stdout=True).Wait()
|
||||||
|
|
||||||
def PruneHeads(self):
|
def PruneHeads(self):
|
||||||
"""Prune any topic branches already merged into upstream.
|
"""Prune any topic branches already merged into upstream.
|
||||||
"""
|
"""
|
||||||
@ -733,9 +858,11 @@ class Project(object):
|
|||||||
def _RemoteFetch(self, name=None):
|
def _RemoteFetch(self, name=None):
|
||||||
if not name:
|
if not name:
|
||||||
name = self.remote.name
|
name = self.remote.name
|
||||||
return GitCommand(self,
|
cmd = ['fetch']
|
||||||
['fetch', name],
|
if not self.worktree:
|
||||||
bare = True).Wait() == 0
|
cmd.append('--update-head-ok')
|
||||||
|
cmd.append(name)
|
||||||
|
return GitCommand(self, cmd, bare = True).Wait() == 0
|
||||||
|
|
||||||
def _Checkout(self, rev, quiet=False):
|
def _Checkout(self, rev, quiet=False):
|
||||||
cmd = ['checkout']
|
cmd = ['checkout']
|
||||||
@ -781,14 +908,29 @@ class Project(object):
|
|||||||
to_rm = []
|
to_rm = []
|
||||||
for old_hook in to_rm:
|
for old_hook in to_rm:
|
||||||
os.remove(os.path.join(hooks, old_hook))
|
os.remove(os.path.join(hooks, old_hook))
|
||||||
|
self._InitHooks()
|
||||||
# TODO(sop) install custom repo hooks
|
|
||||||
|
|
||||||
m = self.manifest.manifestProject.config
|
m = self.manifest.manifestProject.config
|
||||||
for key in ['user.name', 'user.email']:
|
for key in ['user.name', 'user.email']:
|
||||||
if m.Has(key, include_defaults = False):
|
if m.Has(key, include_defaults = False):
|
||||||
self.config.SetString(key, m.GetString(key))
|
self.config.SetString(key, m.GetString(key))
|
||||||
|
|
||||||
|
def _InitHooks(self):
|
||||||
|
hooks = self._gitdir_path('hooks')
|
||||||
|
if not os.path.exists(hooks):
|
||||||
|
os.makedirs(hooks)
|
||||||
|
for stock_hook in repo_hooks():
|
||||||
|
dst = os.path.join(hooks, os.path.basename(stock_hook))
|
||||||
|
try:
|
||||||
|
os.symlink(relpath(stock_hook, dst), dst)
|
||||||
|
except OSError, e:
|
||||||
|
if e.errno == errno.EEXIST:
|
||||||
|
pass
|
||||||
|
elif e.errno == errno.EPERM:
|
||||||
|
raise GitError('filesystem must support symlinks')
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
def _InitRemote(self):
|
def _InitRemote(self):
|
||||||
if self.remote.fetchUrl:
|
if self.remote.fetchUrl:
|
||||||
remote = self.GetRemote(self.remote.name)
|
remote = self.GetRemote(self.remote.name)
|
||||||
@ -799,14 +941,23 @@ class Project(object):
|
|||||||
url += '/%s.git' % self.name
|
url += '/%s.git' % self.name
|
||||||
remote.url = url
|
remote.url = url
|
||||||
remote.review = self.remote.reviewUrl
|
remote.review = self.remote.reviewUrl
|
||||||
|
if remote.projectname is None:
|
||||||
|
remote.projectname = self.name
|
||||||
|
|
||||||
remote.ResetFetch()
|
if self.worktree:
|
||||||
|
remote.ResetFetch(mirror=False)
|
||||||
|
else:
|
||||||
|
remote.ResetFetch(mirror=True)
|
||||||
remote.Save()
|
remote.Save()
|
||||||
|
|
||||||
for r in self.extraRemotes.values():
|
for r in self.extraRemotes.values():
|
||||||
remote = self.GetRemote(r.name)
|
remote = self.GetRemote(r.name)
|
||||||
remote.url = r.fetchUrl
|
remote.url = r.fetchUrl
|
||||||
remote.review = r.reviewUrl
|
remote.review = r.reviewUrl
|
||||||
|
if r.projectName:
|
||||||
|
remote.projectname = r.projectName
|
||||||
|
elif remote.projectname is None:
|
||||||
|
remote.projectname = self.name
|
||||||
remote.ResetFetch()
|
remote.ResetFetch()
|
||||||
remote.Save()
|
remote.Save()
|
||||||
|
|
||||||
@ -816,31 +967,23 @@ class Project(object):
|
|||||||
ref = R_M + self.manifest.branch
|
ref = R_M + self.manifest.branch
|
||||||
|
|
||||||
if IsId(self.revision):
|
if IsId(self.revision):
|
||||||
dst = self.revision + '^0',
|
dst = self.revision + '^0'
|
||||||
self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
|
self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
|
||||||
else:
|
else:
|
||||||
remote = self.GetRemote(self.remote.name)
|
remote = self.GetRemote(self.remote.name)
|
||||||
dst = remote.ToLocal(self.revision)
|
dst = remote.ToLocal(self.revision)
|
||||||
self.bare_git.symbolic_ref('-m', msg, ref, dst)
|
self.bare_git.symbolic_ref('-m', msg, ref, dst)
|
||||||
|
|
||||||
|
def _InitMirrorHead(self):
|
||||||
|
dst = self.GetRemote(self.remote.name).ToLocal(self.revision)
|
||||||
|
msg = 'manifest set to %s' % self.revision
|
||||||
|
self.bare_git.SetHead(dst, message=msg)
|
||||||
|
|
||||||
def _InitWorkTree(self):
|
def _InitWorkTree(self):
|
||||||
dotgit = os.path.join(self.worktree, '.git')
|
dotgit = os.path.join(self.worktree, '.git')
|
||||||
if not os.path.exists(dotgit):
|
if not os.path.exists(dotgit):
|
||||||
os.makedirs(dotgit)
|
os.makedirs(dotgit)
|
||||||
|
|
||||||
topdir = os.path.commonprefix([self.gitdir, dotgit])
|
|
||||||
if topdir.endswith('/'):
|
|
||||||
topdir = topdir[:-1]
|
|
||||||
else:
|
|
||||||
topdir = os.path.dirname(topdir)
|
|
||||||
|
|
||||||
tmpdir = dotgit
|
|
||||||
relgit = ''
|
|
||||||
while topdir != tmpdir:
|
|
||||||
relgit += '../'
|
|
||||||
tmpdir = os.path.dirname(tmpdir)
|
|
||||||
relgit += self.gitdir[len(topdir) + 1:]
|
|
||||||
|
|
||||||
for name in ['config',
|
for name in ['config',
|
||||||
'description',
|
'description',
|
||||||
'hooks',
|
'hooks',
|
||||||
@ -851,8 +994,15 @@ class Project(object):
|
|||||||
'refs',
|
'refs',
|
||||||
'rr-cache',
|
'rr-cache',
|
||||||
'svn']:
|
'svn']:
|
||||||
os.symlink(os.path.join(relgit, name),
|
try:
|
||||||
os.path.join(dotgit, name))
|
src = os.path.join(self.gitdir, name)
|
||||||
|
dst = os.path.join(dotgit, name)
|
||||||
|
os.symlink(relpath(src, dst), dst)
|
||||||
|
except OSError, e:
|
||||||
|
if e.errno == errno.EPERM:
|
||||||
|
raise GitError('filesystem must support symlinks')
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
|
rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
|
||||||
rev = self.bare_git.rev_parse('%s^0' % rev)
|
rev = self.bare_git.rev_parse('%s^0' % rev)
|
||||||
|
@ -14,8 +14,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
class Remote(object):
|
class Remote(object):
|
||||||
def __init__(self, name, fetch=None, review=None):
|
def __init__(self, name,
|
||||||
|
fetch=None,
|
||||||
|
review=None,
|
||||||
|
projectName=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.fetchUrl = fetch
|
self.fetchUrl = fetch
|
||||||
self.reviewUrl = review
|
self.reviewUrl = review
|
||||||
|
self.projectName = projectName
|
||||||
self.requiredCommits = []
|
self.requiredCommits = []
|
||||||
|
7
repo
7
repo
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## repo default configuration
|
## repo default configuration
|
||||||
##
|
##
|
||||||
REPO_URL='git://android.kernel.org/tools/repo.git'
|
REPO_URL='git://android.git.kernel.org/tools/repo.git'
|
||||||
REPO_REV='stable'
|
REPO_REV='stable'
|
||||||
|
|
||||||
# Copyright (C) 2008 Google Inc.
|
# Copyright (C) 2008 Google Inc.
|
||||||
@ -28,7 +28,7 @@ if __name__ == '__main__':
|
|||||||
del magic
|
del magic
|
||||||
|
|
||||||
# increment this whenever we make important changes to this script
|
# increment this whenever we make important changes to this script
|
||||||
VERSION = (1, 6)
|
VERSION = (1, 8)
|
||||||
|
|
||||||
# increment this if the MAINTAINER_KEYS block is modified
|
# increment this if the MAINTAINER_KEYS block is modified
|
||||||
KEYRING_VERSION = (1,0)
|
KEYRING_VERSION = (1,0)
|
||||||
@ -115,6 +115,9 @@ group.add_option('-b', '--manifest-branch',
|
|||||||
group.add_option('-m', '--manifest-name',
|
group.add_option('-m', '--manifest-name',
|
||||||
dest='manifest_name',
|
dest='manifest_name',
|
||||||
help='initial manifest file', metavar='NAME.xml')
|
help='initial manifest file', metavar='NAME.xml')
|
||||||
|
group.add_option('--mirror',
|
||||||
|
dest='mirror', action='store_true',
|
||||||
|
help='mirror the forrest')
|
||||||
|
|
||||||
# Tool
|
# Tool
|
||||||
group = init_optparse.add_option_group('Version options')
|
group = init_optparse.add_option_group('Version options')
|
||||||
|
42
subcmds/abandon.py
Normal file
42
subcmds/abandon.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2008 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 sys
|
||||||
|
from command import Command
|
||||||
|
from git_command import git
|
||||||
|
|
||||||
|
class Abandon(Command):
|
||||||
|
common = True
|
||||||
|
helpSummary = "Permanently abandon a development branch"
|
||||||
|
helpUsage = """
|
||||||
|
%prog <branchname> [<project>...]
|
||||||
|
|
||||||
|
This subcommand permanently abandons a development branch by
|
||||||
|
deleting it (and all its history) from your local repository.
|
||||||
|
|
||||||
|
It is equivalent to "git branch -D <branchname>".
|
||||||
|
"""
|
||||||
|
|
||||||
|
def Execute(self, opt, args):
|
||||||
|
if not args:
|
||||||
|
self.Usage()
|
||||||
|
|
||||||
|
nb = args[0]
|
||||||
|
if not git.check_ref_format('heads/%s' % nb):
|
||||||
|
print >>sys.stderr, "error: '%s' is not a valid name" % nb
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
for project in self.GetProjects(args[1:]):
|
||||||
|
project.AbandonBranch(nb)
|
@ -57,6 +57,10 @@ default.xml will be used.
|
|||||||
g.add_option('-m', '--manifest-name',
|
g.add_option('-m', '--manifest-name',
|
||||||
dest='manifest_name', default='default.xml',
|
dest='manifest_name', default='default.xml',
|
||||||
help='initial manifest file', metavar='NAME.xml')
|
help='initial manifest file', metavar='NAME.xml')
|
||||||
|
g.add_option('--mirror',
|
||||||
|
dest='mirror', action='store_true',
|
||||||
|
help='mirror the forrest')
|
||||||
|
|
||||||
|
|
||||||
# Tool
|
# Tool
|
||||||
g = p.add_option_group('Version options')
|
g = p.add_option_group('Version options')
|
||||||
@ -112,6 +116,9 @@ default.xml will be used.
|
|||||||
r.ResetFetch()
|
r.ResetFetch()
|
||||||
r.Save()
|
r.Save()
|
||||||
|
|
||||||
|
if opt.mirror:
|
||||||
|
m.config.SetString('repo.mirror', 'true')
|
||||||
|
|
||||||
m.Sync_NetworkHalf()
|
m.Sync_NetworkHalf()
|
||||||
m.Sync_LocalHalf()
|
m.Sync_LocalHalf()
|
||||||
m.StartBranch('default')
|
m.StartBranch('default')
|
||||||
@ -185,9 +192,14 @@ default.xml will be used.
|
|||||||
self._SyncManifest(opt)
|
self._SyncManifest(opt)
|
||||||
self._LinkManifest(opt.manifest_name)
|
self._LinkManifest(opt.manifest_name)
|
||||||
|
|
||||||
if os.isatty(0) and os.isatty(1):
|
if os.isatty(0) and os.isatty(1) and not opt.mirror:
|
||||||
self._ConfigureUser()
|
self._ConfigureUser()
|
||||||
self._ConfigureColor()
|
self._ConfigureColor()
|
||||||
|
|
||||||
|
if opt.mirror:
|
||||||
|
type = 'mirror '
|
||||||
|
else:
|
||||||
|
type = ''
|
||||||
|
|
||||||
print ''
|
print ''
|
||||||
print 'repo initialized in %s' % self.manifest.topdir
|
print 'repo %sinitialized in %s' % (type, self.manifest.topdir)
|
||||||
|
@ -49,6 +49,9 @@ the manifest.
|
|||||||
p.add_option('--no-repo-verify',
|
p.add_option('--no-repo-verify',
|
||||||
dest='no_repo_verify', action='store_true',
|
dest='no_repo_verify', action='store_true',
|
||||||
help='do not verify repo source code')
|
help='do not verify repo source code')
|
||||||
|
p.add_option('--repo-upgraded',
|
||||||
|
dest='repo_upgraded', action='store_true',
|
||||||
|
help='perform additional actions after a repo upgrade')
|
||||||
|
|
||||||
def _Fetch(self, *projects):
|
def _Fetch(self, *projects):
|
||||||
fetched = set()
|
fetched = set()
|
||||||
@ -67,6 +70,11 @@ the manifest.
|
|||||||
mp = self.manifest.manifestProject
|
mp = self.manifest.manifestProject
|
||||||
mp.PreSync()
|
mp.PreSync()
|
||||||
|
|
||||||
|
if opt.repo_upgraded:
|
||||||
|
for project in self.manifest.projects.values():
|
||||||
|
if project.Exists:
|
||||||
|
project.PostRepoUpgrade()
|
||||||
|
|
||||||
all = self.GetProjects(args, missing_ok=True)
|
all = self.GetProjects(args, missing_ok=True)
|
||||||
fetched = self._Fetch(rp, mp, *all)
|
fetched = self._Fetch(rp, mp, *all)
|
||||||
|
|
||||||
@ -77,7 +85,7 @@ the manifest.
|
|||||||
if not rp.Sync_LocalHalf():
|
if not rp.Sync_LocalHalf():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print >>sys.stderr, 'info: Restarting repo with latest version'
|
print >>sys.stderr, 'info: Restarting repo with latest version'
|
||||||
raise RepoChangedException()
|
raise RepoChangedException(['--repo-upgraded'])
|
||||||
else:
|
else:
|
||||||
print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
|
print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
|
||||||
|
|
||||||
@ -94,8 +102,9 @@ the manifest.
|
|||||||
self._Fetch(*missing)
|
self._Fetch(*missing)
|
||||||
|
|
||||||
for project in all:
|
for project in all:
|
||||||
if not project.Sync_LocalHalf():
|
if project.worktree:
|
||||||
sys.exit(1)
|
if not project.Sync_LocalHalf():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def _VerifyTag(project):
|
def _VerifyTag(project):
|
||||||
|
@ -25,11 +25,17 @@ def _die(fmt, *args):
|
|||||||
print >>sys.stderr, 'error: %s' % msg
|
print >>sys.stderr, 'error: %s' % msg
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
def _SplitEmails(values):
|
||||||
|
result = []
|
||||||
|
for str in values:
|
||||||
|
result.extend([s.strip() for s in str.split(',')])
|
||||||
|
return result
|
||||||
|
|
||||||
class Upload(InteractiveCommand):
|
class Upload(InteractiveCommand):
|
||||||
common = True
|
common = True
|
||||||
helpSummary = "Upload changes for code review"
|
helpSummary = "Upload changes for code review"
|
||||||
helpUsage="""
|
helpUsage="""
|
||||||
%prog [<project>]...
|
%prog [--re --cc] {[<project>]... | --replace <project>}
|
||||||
"""
|
"""
|
||||||
helpDescription = """
|
helpDescription = """
|
||||||
The '%prog' command is used to send changes to the Gerrit code
|
The '%prog' command is used to send changes to the Gerrit code
|
||||||
@ -44,9 +50,31 @@ at the command line. Projects can be specified either by name, or
|
|||||||
by a relative or absolute path to the project's local directory. If
|
by a relative or absolute path to the project's local directory. If
|
||||||
no projects are specified, '%prog' will search for uploadable
|
no projects are specified, '%prog' will search for uploadable
|
||||||
changes in all projects listed in the manifest.
|
changes in all projects listed in the manifest.
|
||||||
|
|
||||||
|
If the --reviewers or --cc options are passed, those emails are
|
||||||
|
added to the respective list of users, and emails are sent to any
|
||||||
|
new users. Users passed to --reviewers must be already registered
|
||||||
|
with the code review system, or the upload will fail.
|
||||||
|
|
||||||
|
If the --replace option is passed the user can designate which
|
||||||
|
existing change(s) in Gerrit match up to the commits in the branch
|
||||||
|
being uploaded. For each matched pair of change,commit the commit
|
||||||
|
will be added as a new patch set, completely replacing the set of
|
||||||
|
files and description associated with the change in Gerrit.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _SingleBranch(self, branch):
|
def _Options(self, p):
|
||||||
|
p.add_option('--replace',
|
||||||
|
dest='replace', action='store_true',
|
||||||
|
help='Upload replacement patchesets from this branch')
|
||||||
|
p.add_option('--re', '--reviewers',
|
||||||
|
type='string', action='append', dest='reviewers',
|
||||||
|
help='Request reviews from these people.')
|
||||||
|
p.add_option('--cc',
|
||||||
|
type='string', action='append', dest='cc',
|
||||||
|
help='Also send email to these email addresses.')
|
||||||
|
|
||||||
|
def _SingleBranch(self, branch, people):
|
||||||
project = branch.project
|
project = branch.project
|
||||||
name = branch.name
|
name = branch.name
|
||||||
date = branch.date
|
date = branch.date
|
||||||
@ -64,11 +92,11 @@ changes in all projects listed in the manifest.
|
|||||||
sys.stdout.write('(y/n)? ')
|
sys.stdout.write('(y/n)? ')
|
||||||
answer = sys.stdin.readline().strip()
|
answer = sys.stdin.readline().strip()
|
||||||
if answer in ('y', 'Y', 'yes', '1', 'true', 't'):
|
if answer in ('y', 'Y', 'yes', '1', 'true', 't'):
|
||||||
self._UploadAndReport([branch])
|
self._UploadAndReport([branch], people)
|
||||||
else:
|
else:
|
||||||
_die("upload aborted by user")
|
_die("upload aborted by user")
|
||||||
|
|
||||||
def _MultipleBranches(self, pending):
|
def _MultipleBranches(self, pending, people):
|
||||||
projects = {}
|
projects = {}
|
||||||
branches = {}
|
branches = {}
|
||||||
|
|
||||||
@ -127,13 +155,62 @@ changes in all projects listed in the manifest.
|
|||||||
todo.append(branch)
|
todo.append(branch)
|
||||||
if not todo:
|
if not todo:
|
||||||
_die("nothing uncommented for upload")
|
_die("nothing uncommented for upload")
|
||||||
self._UploadAndReport(todo)
|
self._UploadAndReport(todo, people)
|
||||||
|
|
||||||
def _UploadAndReport(self, todo):
|
def _ReplaceBranch(self, project, people):
|
||||||
|
branch = project.CurrentBranch
|
||||||
|
if not branch:
|
||||||
|
print >>sys.stdout, "no branches ready for upload"
|
||||||
|
return
|
||||||
|
branch = project.GetUploadableBranch(branch)
|
||||||
|
if not branch:
|
||||||
|
print >>sys.stdout, "no branches ready for upload"
|
||||||
|
return
|
||||||
|
|
||||||
|
script = []
|
||||||
|
script.append('# Replacing from branch %s' % branch.name)
|
||||||
|
for commit in branch.commits:
|
||||||
|
script.append('[ ] %s' % commit)
|
||||||
|
script.append('')
|
||||||
|
script.append('# Insert change numbers in the brackets to add a new patch set.')
|
||||||
|
script.append('# To create a new change record, leave the brackets empty.')
|
||||||
|
|
||||||
|
script = Editor.EditString("\n".join(script)).split("\n")
|
||||||
|
|
||||||
|
change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$')
|
||||||
|
to_replace = dict()
|
||||||
|
full_hashes = branch.unabbrev_commits
|
||||||
|
|
||||||
|
for line in script:
|
||||||
|
m = change_re.match(line)
|
||||||
|
if m:
|
||||||
|
c = m.group(1)
|
||||||
|
f = m.group(2)
|
||||||
|
try:
|
||||||
|
f = full_hashes[f]
|
||||||
|
except KeyError:
|
||||||
|
print 'fh = %s' % full_hashes
|
||||||
|
print >>sys.stderr, "error: commit %s not found" % f
|
||||||
|
sys.exit(1)
|
||||||
|
if c in to_replace:
|
||||||
|
print >>sys.stderr,\
|
||||||
|
"error: change %s cannot accept multiple commits" % c
|
||||||
|
sys.exit(1)
|
||||||
|
to_replace[c] = f
|
||||||
|
|
||||||
|
if not to_replace:
|
||||||
|
print >>sys.stderr, "error: no replacements specified"
|
||||||
|
print >>sys.stderr, " use 'repo upload' without --replace"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
branch.replace_changes = to_replace
|
||||||
|
self._UploadAndReport([branch], people)
|
||||||
|
|
||||||
|
def _UploadAndReport(self, todo, people):
|
||||||
have_errors = False
|
have_errors = False
|
||||||
for branch in todo:
|
for branch in todo:
|
||||||
try:
|
try:
|
||||||
branch.UploadForReview()
|
branch.UploadForReview(people)
|
||||||
branch.uploaded = True
|
branch.uploaded = True
|
||||||
except UploadError, e:
|
except UploadError, e:
|
||||||
branch.error = e
|
branch.error = e
|
||||||
@ -167,6 +244,22 @@ changes in all projects listed in the manifest.
|
|||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
project_list = self.GetProjects(args)
|
project_list = self.GetProjects(args)
|
||||||
pending = []
|
pending = []
|
||||||
|
reviewers = []
|
||||||
|
cc = []
|
||||||
|
|
||||||
|
if opt.reviewers:
|
||||||
|
reviewers = _SplitEmails(opt.reviewers)
|
||||||
|
if opt.cc:
|
||||||
|
cc = _SplitEmails(opt.cc)
|
||||||
|
people = (reviewers,cc)
|
||||||
|
|
||||||
|
if opt.replace:
|
||||||
|
if len(project_list) != 1:
|
||||||
|
print >>sys.stderr, \
|
||||||
|
'error: --replace requires exactly one project'
|
||||||
|
sys.exit(1)
|
||||||
|
self._ReplaceBranch(project_list[0], people)
|
||||||
|
return
|
||||||
|
|
||||||
for project in project_list:
|
for project in project_list:
|
||||||
avail = project.GetUploadableBranches()
|
avail = project.GetUploadableBranches()
|
||||||
@ -176,6 +269,6 @@ changes in all projects listed in the manifest.
|
|||||||
if not pending:
|
if not pending:
|
||||||
print >>sys.stdout, "no branches ready for upload"
|
print >>sys.stdout, "no branches ready for upload"
|
||||||
elif len(pending) == 1 and len(pending[0][1]) == 1:
|
elif len(pending) == 1 and len(pending[0][1]) == 1:
|
||||||
self._SingleBranch(pending[0][1][0])
|
self._SingleBranch(pending[0][1][0], people)
|
||||||
else:
|
else:
|
||||||
self._MultipleBranches(pending)
|
self._MultipleBranches(pending, people)
|
||||||
|
Reference in New Issue
Block a user