Compare commits

...

44 Commits

Author SHA1 Message Date
c9877c7cf6 Merge "Only fetch current branch on shallow clients" 2014-01-29 21:12:34 +00:00
69e04d8953 Only fetch current branch on shallow clients
Fetching a new branch on a shallow client may download the entire
project history, as the depth parameter is not passed to git
fetch. Force the fetch to only download the current branch.

Change-Id: Ie17ce8eb5e3487c24d90b2cae8227319dea482c8
2014-01-29 12:48:54 -08:00
f1f1137d61 Merge "Don't backtrace when current branch is not uploadable." 2014-01-14 00:41:35 +00:00
f77ef2edb0 Merge "hooks/pre-auto-gc: fix AC detection on OSX Maverick" 2014-01-10 02:50:53 +00:00
e695338e21 Merge "repo: Support multiple branches for the same project." 2014-01-10 01:20:13 +00:00
bd80f7eedd Merge "Canonicalize project hooks path before use" 2014-01-09 02:11:10 +00:00
bf79c6618e Fix os.mkdir race condition.
This code checks whether a dir exists before creating it. In between the
check and the mkdir call, it is possible that another process will have
created the directory. We have seen this bug occur many times in
practice during our 'repo init' tests.

Change-Id: Ia47d39955739aa38fd303f4e90be7b4c50d9d4ba
2013-12-26 14:59:00 -08:00
f045d49a71 Merge "Add --archive option to init to sync using git archive" 2013-12-18 17:44:59 +00:00
719757d6a8 hooks/pre-auto-gc: fix AC detection on OSX Maverick
The output of pmset has been changed to "Now drawing from 'AC Power'"

Change-Id: Id425d3bcd6a28656736a6d2c3096623a3ec053cc
2013-12-17 09:48:20 +07:00
011d4f426c Don't backtrace when current branch is not uploadable.
The backtrace currently occurs when one uses the "--cbr" argument with
the repo upload subcommand if the current branch is not tracking an
upstream branch. There may be other cases that would backtrace as well,
but this is the only one I found so far.

Change-Id: Ie712fbb0ce3e7fe3b72769fca89cc4c0e3d2fce0
2013-12-11 23:24:01 -08:00
53d6a7b895 Fix error in xml manifest doc.
The docs on the annotations say that zero or more may exist as a child
of a project, so that means that a "*" instead of a "?" should be used.

Change-Id: Iff855d003dfb05cd980f285a237332914e1dad70
2013-12-10 15:30:03 -08:00
335f5ef4ad Add --archive option to init to sync using git archive
This significantly reduces sync time and used brandwidth as only
a tar of each project's revision is checked out, but git is not
accessible from projects anymore.

This is relevant when git is not needed in projects but sync
speed/brandwidth may be important like on CI servers when building
several versions from scratch regularly for example.

Archive is not supported over http/https.

Change-Id: I48c3c7de2cd5a1faec33e295fcdafbc7807d0e4d
Signed-off-by: Julien Campergue <julien.campergue@parrot.com>
2013-12-10 08:27:07 +00:00
672cc499b9 Canonicalize project hooks path before use
If the top-level .repo directory is moved somewhere else (e.g. a
different drive) and replaced with a symlink, _InitHooks() will create
broken symlinks. Resolving symlinks before computing the relative path
for the symlink keeps the path within the repo tree, so the tree can
be moved anywhere.

Change-Id: Ifa5c07869e3477186ddd2c255c6c607f547bc1fe
2013-12-03 09:02:16 -08:00
61df418c59 Update the commit-msg hook to the version from Gerrit 2.6
Change-Id: Iaf21ba8d2ceea58973dbc56f0b4ece54500cd997
2013-11-29 19:17:23 +09:00
4534120628 Merge "Allow using repo with python3" 2013-11-22 10:25:35 +00:00
cbc0798f67 Fix print of git-remote-persistent-https error
If git-remote-persistent-https fails, we use an iter() and then
subsequently a .read() on stderr.  Python doesn't like this and
gives the following error message:
ValueError: Mixing iteration and read methods would lose data

This change removes the use of iter() to avoid the issue.

Change-Id: I980659b83229e2a559c20dcc7b116f8d2476abd5
2013-11-21 10:38:03 -08:00
d5a5b19efd Remove trailing whitespace
Change-Id: I56bcb559431277d40070fa33c580c6c3525ff9bc
2013-11-21 19:16:08 +05:30
5d6cb80b8f Allow using repo with python3
* Switching from python2 to python3 in the same workspace isn't
  currently supported, due to a change in the pickle version (which
  isn't supported by python2)
* Basic functionality does work with python3, however not everything
  is expected to

Change-Id: I4256b5a9861562d0260b503f972c1569190182aa
2013-11-21 18:44:52 +05:30
0eb35cbe50 Fix some python3 encoding issues
* Add .decode('utf-8') where needed
* Add 'b' to `open` where needed, and remove where unnecessary

Change-Id: I0f03ecf9ed1a78e3b2f15f9469deb9aaab698657
2013-11-21 06:03:22 +00:00
ce201a5311 Fix a small whitespace consistency issue
Change-Id: Ie98c79833ca5e7ef71666489135f7491223f779c
2013-10-16 14:42:42 -07:00
12fd10c201 Merge "Dan't accessing attr of None (manifest subcmd)" 2013-10-16 21:41:33 +00:00
a17d7af4d9 Dan't accessing attr of None (manifest subcmd)
If d.remote is None, this code failed for obvious reasons.  This is a
simple fix.

Change-Id: I413756121e444111f1e3c7dc8bc8032467946c13
2013-10-16 14:38:09 -07:00
fbd3f2a10b Only check merge destination if it isn't None
Change-Id: Ifb1dcd07142933489e93a1f4f03e38289087b609
2013-10-15 12:59:00 -07:00
37128b6f70 Fix indentation
git-repo uses 2 space indentation.  A couple of recent changes
introduced 4 space indentation in some modules.

Change-Id: Ia4250157c1824c1b5e7d555068c4608f995be9da
2013-10-15 10:48:40 +09:00
143b4cc992 Merge "Better handling of duplicate default" 2013-10-15 01:40:08 +00:00
8d20116038 repo: Support multiple branches for the same project.
It is often useful to be able to include the same project more than
once, but with different branches and placed in different paths in the
workspace. Add this feature.

This CL adds the concept of an object directory. The object directory
stores objects that can be shared amongst several working trees. For
newly synced repositories, we set up the git repo now to share its
objects with an object repo.

Each worktree for a given repo shares objects, but has an independent
set of references and branches. This ensures that repo only has to
update the objects once; however the references for each worktree are
updated separately. Storing the references separately is needed to
ensure that commits to a branch on one worktree will not change the
HEAD commits of the others.

One nice side effect of sharing objects between different worktrees is
that you can easily cherry-pick changes between the two worktrees
without needing to fetch them.

Bug: Issue 141
Change-Id: I5e2f4e1a7abb56f9d3f310fa6fd0c17019330ecd
2013-10-14 15:34:32 -07:00
53263d873d Merge "repo: use explicit Python executable to run main.py" 2013-10-10 18:42:59 +00:00
7487992bd3 Better handling of duplicate default
Currently, an error is raised if more than one default is defined.

When including another manifest, it is likely that a default has
been defined in both manifests.

Don't raise an error if all the defaults defined have the same
attributes.

Change-Id: I2603020687e2ba04c2c62c3268ee375279b34a08
Signed-off-by: Julien Campergue <julien.campergue@parrot.com>
2013-10-10 18:14:27 +02:00
b25ea555c3 Merge "Respect remote aliases" 2013-10-10 16:08:42 +00:00
3bfd72158c Don't upload when dest branch is not merge branch
Example:
- `repo init -b master` / sync a project
- In one project: `git checkout -b work origin/branch-thats-not-master`
- make some changes, `git commit`
- `repo upload .`
- Upload will now be skipped with a warning instead of being uploaded to
  master

Change-Id: I990b36217b75fe3c8b4d776e7fefa1c7d9ab7282
2013-10-10 09:06:38 -07:00
59b31cb6e0 don't pass project revision to UploadForReview
Passing a project revisionExpr to UploadForReview will cause it to
try to push to refs/for/<sha> if the revision points to a sha
instead of a branch.  Pass None for dest_branch if no destination
branch has been specified, which will cause UploadForReview to
upload to the merge branch.

There is room for further improvement, the user prompts will
still print "Upload project <project> to remote branch <sha>",
and then upload to the merge branch and not the sha, but that
is the same behavior that was in 1.12.2.

Change-Id: I06c510336ae67ff7e68b5b69e929693179d15c0b
2013-10-08 23:14:29 -07:00
1e7ab2a63f Respect remote aliases
Previously, change I7150e449341ed8655d398956a095261978d95870
had broken alias support in order to fix the manifest command to keep
it from spitting projects that point to an alias that wasn't recorded.
This commit reverts that commit and instead solves the issue more
correctly, outputting the alias in the remote node of the manifest and
respecting that alias when outputting the list of projects.

Change-Id: I941fc4adb7121d2e61cedc5838e80d3918c977c3
2013-10-08 17:26:57 -07:00
e76efdd7b3 Merge "Accept all UTF-8 committer names" 2013-09-27 17:28:45 +00:00
730ce4c3c2 Merge "Do not use print_function from __future__" 2013-09-27 17:28:08 +00:00
745a39ba3d Assume http upload if ssh_info cannot be parsed
When uploading a change for review, we sometimes request /ssh_info to
get the correct port number for uploading via ssh (regardless of
whether or not we intend to upload over ssh).

If we have trouble accessing /ssh_info (for authentication reasons,
etc), we now press on under the assumption that we will upload via http
instead of aborting.

Change-Id: Ica6bbeac1279e89424a903529649b7f4af0b6937
2013-09-27 19:15:34 +09:00
efc986c508 Merge changes I4b77af22,Ib5bc2de5
* changes:
  Sync: Improved error message when manifest server RPC call fails
  Sync: Print name of manifest server used for smart sync/smart tag
2013-09-27 02:24:39 +00:00
edd0151a26 Accept all UTF-8 committer names
Change-Id: I7d9d49a8bacf2dc332614d26cdfcc905be7a5290
2013-09-27 00:35:35 +00:00
5e0ee14575 Do not use print_function from __future__
Python 2.4 and 2.5 do not have a print_function available, so we need a
compatible print function for displaying an error message when the user
has an older version of Python.

Change-Id: I54d7297be98bb53970e873b36c6605e6dad386c3
2013-09-27 09:32:02 +09:00
70df18944a Merge "Wait for git-remote-persistent-https -print_config to exit" 2013-09-26 16:30:02 +00:00
0836a22d38 Wait for git-remote-persistent-https -print_config to exit
Change-Id: I5ab96e7c8575682217d440ddc52ecfdc8c35f179
2013-09-25 17:46:01 -07:00
b6a16e6390 Give the node _Default class a destBranchExpr
This is to avoid the following AttributeError:

line 681, in _ParseProject
AttributeError: '_Default' object has no attribute 'destBranchExpr'

Change-Id: Ia9f7e2cce1409d22d71bc8a74b33edf2b27702ca
2013-09-25 15:07:22 -07:00
351fe2c793 Sync: Improved error message when manifest server RPC call fails
When the RPC call fails, the error message returned by the server
is printed, but it is not obvious that this is caused by RPC call
failure.

Prefix the error message with a descriptive message that explains
what went wrong.

Change-Id: I4b77af22aacc2e9843c4df9d06bf54e41d9692ff
2013-09-25 19:12:13 +09:00
fb99c71939 Sync: Print name of manifest server used for smart sync/smart tag
When syncing using smart sync or smart tag mode, print the url of
the manifest server that is being used.

This is useful in organisations that have multiple manifest servers
used in different manifest branches.

Change-Id: Ib5bc2de5af6f4a942d0ef735c65cbc0721059a61
2013-09-25 19:12:06 +09:00
3a2a59eb87 repo: use explicit Python executable to run main.py
Small step to support non-POSIX platforms.

Change-Id: I3bdb9c82c2dfbacb1da328caaa1a406ab91ad675
2013-09-21 20:03:57 +03:00
15 changed files with 480 additions and 206 deletions

View File

@ -129,7 +129,7 @@ class Command(object):
def GetProjects(self, args, missing_ok=False, submodules_ok=False):
"""A list of projects that match the arguments.
"""
all_projects = self.manifest.projects
all_projects_list = self.manifest.projects
result = []
mp = self.manifest.manifestProject
@ -140,7 +140,6 @@ class Command(object):
groups = [x for x in re.split(r'[,\s]+', groups) if x]
if not args:
all_projects_list = list(all_projects.values())
derived_projects = {}
for project in all_projects_list:
if submodules_ok or project.sync_s:
@ -152,12 +151,12 @@ class Command(object):
project.MatchesGroups(groups)):
result.append(project)
else:
self._ResetPathToProjectMap(all_projects.values())
self._ResetPathToProjectMap(all_projects_list)
for arg in args:
project = all_projects.get(arg)
projects = self.manifest.GetProjectsWithName(arg)
if not project:
if not projects:
path = os.path.abspath(arg).replace('\\', '/')
project = self._GetProjectByPath(path)
@ -172,14 +171,19 @@ class Command(object):
if search_again:
project = self._GetProjectByPath(path) or project
if not project:
raise NoSuchProjectError(arg)
if not missing_ok and not project.Exists:
raise NoSuchProjectError(arg)
if not project.MatchesGroups(groups):
raise InvalidProjectGroupsError(arg)
if project:
projects = [project]
result.append(project)
if not projects:
raise NoSuchProjectError(arg)
for project in projects:
if not missing_ok and not project.Exists:
raise NoSuchProjectError(arg)
if not project.MatchesGroups(groups):
raise InvalidProjectGroupsError(arg)
result.extend(projects)
def _getpath(x):
return x.relpath

View File

@ -27,15 +27,15 @@ following DTD:
remove-project*,
project*,
repo-hooks?)>
<!ELEMENT notice (#PCDATA)>
<!ELEMENT remote (EMPTY)>
<!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED>
<!ATTLIST remote review CDATA #IMPLIED>
<!ELEMENT default (EMPTY)>
<!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED>
@ -46,8 +46,8 @@ following DTD:
<!ELEMENT manifest-server (EMPTY)>
<!ATTLIST url CDATA #REQUIRED>
<!ELEMENT project (annotation?,
<!ELEMENT project (annotation*,
project*)>
<!ATTLIST project name CDATA #REQUIRED>
<!ATTLIST project path CDATA #IMPLIED>
@ -65,7 +65,7 @@ following DTD:
<!ATTLIST annotation name CDATA #REQUIRED>
<!ATTLIST annotation value CDATA #REQUIRED>
<!ATTLIST annotation keep CDATA "true">
<!ELEMENT remove-project (EMPTY)>
<!ATTLIST remove-project name CDATA #REQUIRED>

View File

@ -86,7 +86,7 @@ class _GitCall(object):
global _git_version
if _git_version is None:
ver_str = git.version()
ver_str = git.version().decode('utf-8')
if ver_str.startswith('git version '):
_git_version = tuple(
map(int,

View File

@ -304,8 +304,8 @@ class GitConfig(object):
d = self._do('--null', '--list')
if d is None:
return c
for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401
# Backslash is not anomalous
for line in d.decode('utf-8').rstrip('\0').split('\0'): # pylint: disable=W1401
# Backslash is not anomalous
if '\n' in line:
key, val = line.split('\n', 1)
else:
@ -596,14 +596,11 @@ class Remote(object):
try:
info_url = u + 'ssh_info'
info = urllib.request.urlopen(info_url).read()
if '<' in info:
# Assume the server gave us some sort of HTML
# response back, like maybe a login page.
if info == 'NOT_AVAILABLE' or '<' in info:
# If `info` contains '<', we assume the server gave us some sort
# of HTML response back, like maybe a login page.
#
raise UploadError('%s: Cannot parse response' % info_url)
if info == 'NOT_AVAILABLE':
# Assume HTTP if SSH is not enabled.
# Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
self._review_url = http_url + 'p/'
else:
host, port = info.split()

View File

@ -100,7 +100,7 @@ class GitRefs(object):
def _ReadPackedRefs(self):
path = os.path.join(self._gitdir, 'packed-refs')
try:
fd = open(path, 'rb')
fd = open(path, 'r')
mtime = os.path.getmtime(path)
except IOError:
return

View File

@ -1,5 +1,5 @@
#!/bin/sh
# From Gerrit Code Review 2.5.2
# From Gerrit Code Review 2.6
#
# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
#
@ -154,7 +154,7 @@ add_ChangeId() {
if (unprinted) {
print "Change-Id: I'"$id"'"
}
}' "$MSG" > $T && mv $T "$MSG" || rm -f $T
}' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
}
_gen_ChangeIdInput() {
echo "tree `git write-tree`"

View File

@ -35,7 +35,7 @@ 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'"
grep -q "drawing from 'AC Power'"
then
exit 0
elif test -d /sys/bus/acpi/drivers/battery && test 0 = \

View File

@ -45,11 +45,18 @@ class _Default(object):
"""Project defaults within the manifest."""
revisionExpr = None
destBranchExpr = None
remote = None
sync_j = 1
sync_c = False
sync_s = False
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __ne__(self, other):
return self.__dict__ != other.__dict__
class _XmlRemote(object):
def __init__(self,
name,
@ -90,6 +97,8 @@ class _XmlRemote(object):
def ToRemoteSpec(self, projectName):
url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
remoteName = self.name
if self.remoteAlias:
remoteName = self.remoteAlias
return RemoteSpec(remoteName, url, self.reviewUrl)
class XmlManifest(object):
@ -144,6 +153,8 @@ class XmlManifest(object):
root.appendChild(e)
e.setAttribute('name', r.name)
e.setAttribute('fetch', r.fetchUrl)
if r.remoteAlias is not None:
e.setAttribute('alias', r.remoteAlias)
if r.reviewUrl is not None:
e.setAttribute('review', r.reviewUrl)
@ -204,8 +215,9 @@ class XmlManifest(object):
root.appendChild(doc.createTextNode(''))
def output_projects(parent, parent_node, projects):
for p in projects:
output_project(parent, parent_node, self.projects[p])
for project_name in projects:
for project in self._projects[project_name]:
output_project(parent, parent_node, project)
def output_project(parent, parent_node, p):
if not p.MatchesGroups(groups):
@ -222,7 +234,10 @@ class XmlManifest(object):
e.setAttribute('name', name)
if relpath != name:
e.setAttribute('path', relpath)
if not d.remote or p.remote.name != d.remote.name:
remoteName = None
if d.remote:
remoteName = d.remote.remoteAlias or d.remote.name
if not d.remote or p.remote.name != remoteName:
e.setAttribute('remote', p.remote.name)
if peg_rev:
if self.IsMirror:
@ -263,13 +278,11 @@ class XmlManifest(object):
e.setAttribute('sync-s', 'true')
if p.subprojects:
sort_projects = list(sorted([subp.name for subp in p.subprojects]))
output_projects(p, e, sort_projects)
subprojects = set(subp.name for subp in p.subprojects)
output_projects(p, e, list(sorted(subprojects)))
sort_projects = list(sorted([key for key, value in self.projects.items()
if not value.parent]))
sort_projects.sort()
output_projects(None, root, sort_projects)
projects = set(p.name for p in self._paths.values() if not p.parent)
output_projects(None, root, list(sorted(projects)))
if self._repo_hooks_project:
root.appendChild(doc.createTextNode(''))
@ -281,10 +294,15 @@ class XmlManifest(object):
doc.writexml(fd, '', ' ', '\n', 'UTF-8')
@property
def paths(self):
self._Load()
return self._paths
@property
def projects(self):
self._Load()
return self._projects
return self._paths.values()
@property
def remotes(self):
@ -315,9 +333,14 @@ class XmlManifest(object):
def IsMirror(self):
return self.manifestProject.config.GetBoolean('repo.mirror')
@property
def IsArchive(self):
return self.manifestProject.config.GetBoolean('repo.archive')
def _Unload(self):
self._loaded = False
self._projects = {}
self._paths = {}
self._remotes = {}
self._default = None
self._repo_hooks_project = None
@ -421,11 +444,13 @@ class XmlManifest(object):
for node in itertools.chain(*node_list):
if node.nodeName == 'default':
if self._default is not None:
raise ManifestParseError(
'duplicate default in %s' %
(self.manifestFile))
self._default = self._ParseDefault(node)
new_default = self._ParseDefault(node)
if self._default is None:
self._default = new_default
elif new_default != self._default:
raise ManifestParseError('duplicate default in %s' %
(self.manifestFile))
if self._default is None:
self._default = _Default()
@ -447,11 +472,17 @@ class XmlManifest(object):
self._manifest_server = url
def recursively_add_projects(project):
if self._projects.get(project.name):
projects = self._projects.setdefault(project.name, [])
if project.relpath is None:
raise ManifestParseError(
'duplicate project %s in %s' %
'missing path for %s in %s' %
(project.name, self.manifestFile))
self._projects[project.name] = project
if project.relpath in self._paths:
raise ManifestParseError(
'duplicate path %s in %s' %
(project.relpath, self.manifestFile))
self._paths[project.relpath] = project
projects.append(project)
for subproject in project.subprojects:
recursively_add_projects(subproject)
@ -472,12 +503,18 @@ class XmlManifest(object):
# Store a reference to the Project.
try:
self._repo_hooks_project = self._projects[repo_hooks_project]
repo_hooks_projects = self._projects[repo_hooks_project]
except KeyError:
raise ManifestParseError(
'project %s not found for repo-hooks' %
(repo_hooks_project))
if len(repo_hooks_projects) != 1:
raise ManifestParseError(
'internal error parsing repo-hooks in %s' %
(self.manifestFile))
self._repo_hooks_project = repo_hooks_projects[0]
# Store the enabled hooks in the Project object.
self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
if node.nodeName == 'remove-project':
@ -524,11 +561,12 @@ class XmlManifest(object):
name = name,
remote = remote.ToRemoteSpec(name),
gitdir = gitdir,
objdir = gitdir,
worktree = None,
relpath = None,
revisionExpr = m.revisionExpr,
revisionId = None)
self._projects[project.name] = project
self._projects[project.name] = [project]
def _ParseRemote(self, node):
"""
@ -688,9 +726,10 @@ class XmlManifest(object):
groups = [x for x in re.split(r'[,\s]+', groups) if x]
if parent is None:
relpath, worktree, gitdir = self.GetProjectPaths(name, path)
relpath, worktree, gitdir, objdir = self.GetProjectPaths(name, path)
else:
relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
relpath, worktree, gitdir, objdir = \
self.GetSubprojectPaths(parent, name, path)
default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
groups.extend(set(default_groups).difference(groups))
@ -703,6 +742,7 @@ class XmlManifest(object):
name = name,
remote = remote.ToRemoteSpec(name),
gitdir = gitdir,
objdir = objdir,
worktree = worktree,
relpath = relpath,
revisionExpr = revisionExpr,
@ -731,10 +771,15 @@ class XmlManifest(object):
if self.IsMirror:
worktree = None
gitdir = os.path.join(self.topdir, '%s.git' % name)
objdir = gitdir
else:
worktree = os.path.join(self.topdir, path).replace('\\', '/')
gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
return relpath, worktree, gitdir
objdir = os.path.join(self.repodir, 'project-objects', '%s.git' % name)
return relpath, worktree, gitdir, objdir
def GetProjectsWithName(self, name):
return self._projects.get(name, [])
def GetSubprojectName(self, parent, submodule_path):
return os.path.join(parent.name, submodule_path)
@ -745,14 +790,15 @@ class XmlManifest(object):
def _UnjoinRelpath(self, parent_relpath, relpath):
return os.path.relpath(relpath, parent_relpath)
def GetSubprojectPaths(self, parent, path):
def GetSubprojectPaths(self, parent, name, path):
relpath = self._JoinRelpath(parent.relpath, path)
gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
objdir = os.path.join(parent.gitdir, 'subproject-objects', '%s.git' % name)
if self.IsMirror:
worktree = None
else:
worktree = os.path.join(parent.worktree, path).replace('\\', '/')
return relpath, worktree, gitdir
return relpath, worktree, gitdir, objdir
def _ParseCopyFile(self, project, node):
src = self._reqatt(node, 'src')

View File

@ -23,6 +23,7 @@ import shutil
import stat
import subprocess
import sys
import tarfile
import tempfile
import time
@ -82,7 +83,7 @@ def _ProjectHooks():
"""
global _project_hook_list
if _project_hook_list is None:
d = os.path.abspath(os.path.dirname(__file__))
d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
d = os.path.join(d , 'hooks')
_project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
return _project_hook_list
@ -487,6 +488,7 @@ class Project(object):
name,
remote,
gitdir,
objdir,
worktree,
relpath,
revisionExpr,
@ -507,6 +509,7 @@ class Project(object):
name: The `name` attribute of manifest.xml's project element.
remote: RemoteSpec object specifying its remote's properties.
gitdir: Absolute path of git directory.
objdir: Absolute path of directory to store git objects.
worktree: Absolute path of git working tree.
relpath: Relative path of git working tree to repo's top directory.
revisionExpr: The `revision` attribute of manifest.xml's project element.
@ -525,6 +528,7 @@ class Project(object):
self.name = name
self.remote = remote
self.gitdir = gitdir.replace('\\', '/')
self.objdir = objdir.replace('\\', '/')
if worktree:
self.worktree = worktree.replace('\\', '/')
else:
@ -557,11 +561,12 @@ class Project(object):
defaults = self.manifest.globalConfig)
if self.worktree:
self.work_git = self._GitGetByExec(self, bare=False)
self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
else:
self.work_git = None
self.bare_git = self._GitGetByExec(self, bare=True)
self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
self.bare_ref = GitRefs(gitdir)
self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
self.dest_branch = dest_branch
# This will be filled in if a project is later identified to be the
@ -982,15 +987,62 @@ class Project(object):
## Sync ##
def _ExtractArchive(self, tarpath, path=None):
"""Extract the given tar on its current location
Args:
- tarpath: The path to the actual tar file
"""
try:
with tarfile.open(tarpath, 'r') as tar:
tar.extractall(path=path)
return True
except (IOError, tarfile.TarError) as e:
print("error: Cannot extract archive %s: "
"%s" % (tarpath, str(e)), file=sys.stderr)
return False
def Sync_NetworkHalf(self,
quiet=False,
is_new=None,
current_branch_only=False,
clone_bundle=True,
no_tags=False):
no_tags=False,
archive=False):
"""Perform only the network IO portion of the sync process.
Local working directory/branch state is not affected.
"""
if archive and not isinstance(self, MetaProject):
if self.remote.url.startswith(('http://', 'https://')):
print("error: %s: Cannot fetch archives from http/https "
"remotes." % self.name, file=sys.stderr)
return False
name = self.relpath.replace('\\', '/')
name = name.replace('/', '_')
tarpath = '%s.tar' % name
topdir = self.manifest.topdir
try:
self._FetchArchive(tarpath, cwd=topdir)
except GitError as e:
print('error: %s' % str(e), file=sys.stderr)
return False
# From now on, we only need absolute tarpath
tarpath = os.path.join(topdir, tarpath)
if not self._ExtractArchive(tarpath, path=topdir):
return False
try:
os.remove(tarpath)
except OSError as e:
print("warn: Cannot remove archive %s: "
"%s" % (tarpath, str(e)), file=sys.stderr)
self._CopyFiles()
return True
if is_new is None:
is_new = not self.Exists
if is_new:
@ -1069,6 +1121,7 @@ class Project(object):
"""Perform only the local IO portion of the sync process.
Network access is not required.
"""
self._InitWorkTree()
all_refs = self.bare_ref.all
self.CleanPublishedCache(all_refs)
revid = self.GetRevisionId(all_refs)
@ -1077,7 +1130,6 @@ class Project(object):
self._FastForward(revid)
self._CopyFiles()
self._InitWorkTree()
head = self.work_git.GetHead()
if head.startswith(R_HEADS):
branch = head[len(R_HEADS):]
@ -1165,7 +1217,7 @@ class Project(object):
last_mine = None
cnt_mine = 0
for commit in local_changes:
commit_id, committer_email = commit.split(' ', 1)
commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
if committer_email == self.UserEmail:
last_mine = commit_id
cnt_mine += 1
@ -1544,11 +1596,13 @@ class Project(object):
return result
for rev, path, url in self._GetSubmodules():
name = self.manifest.GetSubprojectName(self, path)
project = self.manifest.projects.get(name)
relpath, worktree, gitdir, objdir = \
self.manifest.GetSubprojectPaths(self, name, path)
project = self.manifest.paths.get(relpath)
if project:
result.extend(project.GetDerivedSubprojects())
continue
relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
remote = RemoteSpec(self.remote.name,
url = url,
review = self.remote.review)
@ -1556,6 +1610,7 @@ class Project(object):
name = name,
remote = remote,
gitdir = gitdir,
objdir = objdir,
worktree = worktree,
relpath = relpath,
revisionExpr = self.revisionExpr,
@ -1573,6 +1628,19 @@ class Project(object):
## Direct Git Commands ##
def _FetchArchive(self, tarpath, cwd=None):
cmd = ['archive', '-v', '-o', tarpath]
cmd.append('--remote=%s' % self.remote.url)
cmd.append('--prefix=%s/' % self.relpath)
cmd.append(self.revisionExpr)
command = GitCommand(self, cmd, cwd=cwd,
capture_stdout=True,
capture_stderr=True)
if command.Wait() != 0:
raise GitError('git archive %s: %s' % (self.name, command.stderr))
def _RemoteFetch(self, name=None,
current_branch_only=False,
initial=False,
@ -1593,6 +1661,13 @@ class Project(object):
# There is no such persistent revision. We have to fetch it.
return False
if self.clone_depth:
depth = self.clone_depth
else:
depth = self.manifest.manifestProject.config.GetString('repo.depth')
if depth:
current_branch_only = True
if current_branch_only:
if ID_RE.match(self.revisionExpr) is not None:
is_sha1 = True
@ -1656,10 +1731,6 @@ class Project(object):
# The --depth option only affects the initial fetch; after that we'll do
# full fetches of changes.
if self.clone_depth:
depth = self.clone_depth
else:
depth = self.manifest.manifestProject.config.GetString('repo.depth')
if depth and initial:
cmd.append('--depth=%s' % depth)
@ -1831,17 +1902,22 @@ class Project(object):
['git-remote-persistent-https', '-print_config', url],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
p.stdin.close() # Tell subprocess it's ok to close.
prefix = 'http.cookiefile='
cookiefile = None
for line in p.stdout:
line = line.strip()
if line.startswith(prefix):
return line[len(prefix):]
cookiefile = line[len(prefix):]
break
if p.wait():
line = iter(p.stderr).next()
if ' -print_config' in line:
err_msg = p.stderr.read()
if ' -print_config' in err_msg:
pass # Persistent proxy doesn't support -print_config.
else:
print(line + p.stderr.read(), file=sys.stderr)
print(err_msg, file=sys.stderr)
if cookiefile:
return cookiefile
except OSError as e:
if e.errno == errno.ENOENT:
pass # No persistent proxy.
@ -1900,8 +1976,17 @@ class Project(object):
def _InitGitDir(self, mirror_git=None):
if not os.path.exists(self.gitdir):
os.makedirs(self.gitdir)
self.bare_git.init()
# Initialize the bare repository, which contains all of the objects.
if not os.path.exists(self.objdir):
os.makedirs(self.objdir)
self.bare_objdir.init()
# If we have a separate directory to hold refs, initialize it as well.
if self.objdir != self.gitdir:
os.makedirs(self.gitdir)
self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
copy_all=True)
mp = self.manifest.manifestProject
ref_dir = mp.config.GetString('repo.reference') or ''
@ -1950,7 +2035,7 @@ class Project(object):
self._InitHooks()
def _InitHooks(self):
hooks = self._gitdir_path('hooks')
hooks = os.path.realpath(self._gitdir_path('hooks'))
if not os.path.exists(hooks):
os.makedirs(hooks)
for stock_hook in _ProjectHooks():
@ -2017,33 +2102,61 @@ class Project(object):
msg = 'manifest set to %s' % self.revisionExpr
self.bare_git.symbolic_ref('-m', msg, ref, dst)
def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
"""Update |dotgit| to reference |gitdir|, using symlinks where possible.
Args:
gitdir: The bare git repository. Must already be initialized.
dotgit: The repository you would like to initialize.
share_refs: If true, |dotgit| will store its refs under |gitdir|.
Only one work tree can store refs under a given |gitdir|.
copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
This saves you the effort of initializing |dotgit| yourself.
"""
# These objects can be shared between several working trees.
symlink_files = ['description', 'info']
symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
if share_refs:
# These objects can only be used by a single working tree.
symlink_files += ['config', 'packed-refs']
symlink_dirs += ['logs', 'refs']
to_symlink = symlink_files + symlink_dirs
to_copy = []
if copy_all:
to_copy = os.listdir(gitdir)
for name in set(to_copy).union(to_symlink):
try:
src = os.path.realpath(os.path.join(gitdir, name))
dst = os.path.realpath(os.path.join(dotgit, name))
if os.path.lexists(dst) and not os.path.islink(dst):
raise GitError('cannot overwrite a local work tree')
# If the source dir doesn't exist, create an empty dir.
if name in symlink_dirs and not os.path.lexists(src):
os.makedirs(src)
if name in to_symlink:
os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
elif copy_all and not os.path.islink(dst):
if os.path.isdir(src):
shutil.copytree(src, dst)
elif os.path.isfile(src):
shutil.copy(src, dst)
except OSError as e:
if e.errno == errno.EPERM:
raise GitError('filesystem must support symlinks')
else:
raise
def _InitWorkTree(self):
dotgit = os.path.join(self.worktree, '.git')
if not os.path.exists(dotgit):
os.makedirs(dotgit)
for name in ['config',
'description',
'hooks',
'info',
'logs',
'objects',
'packed-refs',
'refs',
'rr-cache',
'svn']:
try:
src = os.path.join(self.gitdir, name)
dst = os.path.join(dotgit, name)
if os.path.islink(dst) or not os.path.exists(dst):
os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
else:
raise GitError('cannot overwrite a local work tree')
except OSError as e:
if e.errno == errno.EPERM:
raise GitError('filesystem must support symlinks')
else:
raise
self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
copy_all=False)
_lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
@ -2053,14 +2166,10 @@ class Project(object):
if GitCommand(self, cmd).Wait() != 0:
raise GitError("cannot initialize work tree")
rr_cache = os.path.join(self.gitdir, 'rr-cache')
if not os.path.exists(rr_cache):
os.makedirs(rr_cache)
self._CopyFiles()
def _gitdir_path(self, path):
return os.path.join(self.gitdir, path)
return os.path.realpath(os.path.join(self.gitdir, path))
def _revlist(self, *args, **kw):
a = []
@ -2073,9 +2182,10 @@ class Project(object):
return self.bare_ref.all
class _GitGetByExec(object):
def __init__(self, project, bare):
def __init__(self, project, bare, gitdir):
self._project = project
self._bare = bare
self._gitdir = gitdir
def LsOthers(self):
p = GitCommand(self._project,
@ -2084,6 +2194,7 @@ class Project(object):
'--others',
'--exclude-standard'],
bare = False,
gitdir=self._gitdir,
capture_stdout = True,
capture_stderr = True)
if p.Wait() == 0:
@ -2099,6 +2210,7 @@ class Project(object):
cmd.extend(args)
p = GitCommand(self._project,
cmd,
gitdir=self._gitdir,
bare = False,
capture_stdout = True,
capture_stderr = True)
@ -2208,6 +2320,7 @@ class Project(object):
p = GitCommand(self._project,
cmdv,
bare = self._bare,
gitdir=self._gitdir,
capture_stdout = True,
capture_stderr = True)
r = []
@ -2260,6 +2373,7 @@ class Project(object):
p = GitCommand(self._project,
cmdv,
bare = self._bare,
gitdir=self._gitdir,
capture_stdout = True,
capture_stderr = True)
if p.Wait() != 0:
@ -2269,7 +2383,7 @@ class Project(object):
p.stderr))
r = p.stdout
try:
r = r.decode()
r = r.decode('utf-8')
except AttributeError:
pass
if r.endswith('\n') and r.index('\n') == len(r) - 1:
@ -2393,6 +2507,7 @@ class MetaProject(Project):
manifest = manifest,
name = name,
gitdir = gitdir,
objdir = gitdir,
worktree = worktree,
remote = RemoteSpec('origin'),
relpath = '.repo/%s' % name,

167
repo
View File

@ -2,7 +2,6 @@
## repo default configuration
##
from __future__ import print_function
REPO_URL = 'https://gerrit.googlesource.com/git-repo'
REPO_REV = 'stable'
@ -111,6 +110,7 @@ REPO_MAIN = S_repo + '/main.py' # main script
MIN_PYTHON_VERSION = (2, 6) # minimum supported python version
import errno
import optparse
import os
import re
@ -128,17 +128,24 @@ else:
urllib.request = urllib2
urllib.error = urllib2
def _print(*objects, **kwargs):
sep = kwargs.get('sep', ' ')
end = kwargs.get('end', '\n')
out = kwargs.get('file', sys.stdout)
out.write(sep.join(objects) + end)
# Python version check
ver = sys.version_info
if ver[0] == 3:
print('error: Python 3 support is not fully implemented in repo yet.\n'
'Please use Python 2.6 - 2.7 instead.',
file=sys.stderr)
sys.exit(1)
_print('warning: Python 3 support is currently experimental. YMMV.\n'
'Please use Python 2.6 - 2.7 instead.',
file=sys.stderr)
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 %s unsupported.\n'
'Please use Python 2.6 - 2.7 instead.'
% sys.version.split(' ')[0], file=sys.stderr)
sys.exit(1)
home_dot_repo = os.path.expanduser('~/.repoconfig')
@ -174,6 +181,10 @@ group.add_option('--reference',
group.add_option('--depth', type='int', default=None,
dest='depth',
help='create a shallow clone with given depth; see git clone')
group.add_option('--archive',
dest='archive', action='store_true',
help='checkout an archive instead of a git repository for '
'each project. See git archive.')
group.add_option('-g', '--groups',
dest='groups', default='default',
help='restrict manifest projects to ones with specified '
@ -230,15 +241,15 @@ def _Init(args):
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()
if not os.path.isdir(repodir):
try:
os.mkdir(repodir)
except OSError as e:
print('fatal: cannot make %s directory: %s'
% (repodir, e.strerror), file=sys.stderr)
try:
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)
# Don't raise CloneFailure; that would delete the
# name. Instead exit immediately.
#
@ -262,8 +273,8 @@ def _Init(args):
_Checkout(dst, branch, rev, opt.quiet)
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
@ -272,12 +283,12 @@ def _CheckGitVersion():
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)
_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()
ver_str = proc.stdout.read().strip()
@ -285,14 +296,14 @@ def _CheckGitVersion():
proc.wait()
if not ver_str.startswith('git version '):
print('error: "%s" unsupported' % ver_str, file=sys.stderr)
_print('error: "%s" unsupported' % ver_str, file=sys.stderr)
raise CloneFailure()
ver_str = ver_str[len('git version '):].strip()
ver_act = tuple(map(int, ver_str.split('.')[0:3]))
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()
@ -315,20 +326,20 @@ def NeedSetupGnuPG():
def SetupGnuPG(quiet):
if not os.path.isdir(home_dot_repo):
try:
os.mkdir(home_dot_repo)
except OSError as e:
print('fatal: cannot make %s directory: %s'
% (home_dot_repo, e.strerror), file=sys.stderr)
try:
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)
sys.exit(1)
if not os.path.isdir(gpg_dir):
try:
os.mkdir(gpg_dir, stat.S_IRWXU)
except OSError as e:
print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
file=sys.stderr)
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)
sys.exit(1)
env = os.environ.copy()
@ -341,18 +352,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')
@ -394,7 +405,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:
@ -443,16 +454,16 @@ def _DownloadBundle(url, local, quiet):
except urllib.error.HTTPError as e:
if e.code in [403, 404]:
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 == '':
@ -476,23 +487,23 @@ def _Clone(url, local, quiet):
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,
_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()
@ -520,18 +531,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'"
_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)
env = os.environ.copy()
env['GNUPGHOME'] = gpg_dir.encode()
@ -549,10 +560,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
@ -619,7 +630,7 @@ def _ParseArguments(args):
def _Usage():
print(
_print(
"""usage: repo COMMAND [ARGS]
repo is not yet installed. Use "repo init" to install it here.
@ -640,23 +651,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)
@ -693,7 +704,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)
@ -732,7 +743,7 @@ def main(orig_args):
repo_main = my_main
ver_str = '.'.join(map(str, VERSION))
me = [repo_main,
me = [sys.executable, repo_main,
'--repo-dir=%s' % rel_repo_dir,
'--wrapper-version=%s' % ver_str,
'--wrapper-path=%s' % wrapper_path,
@ -740,10 +751,10 @@ def main(orig_args):
me.extend(orig_args)
me.extend(extra_args)
try:
os.execv(repo_main, me)
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)

View File

@ -139,7 +139,7 @@ is shown, then the branch appears in all projects.
if in_cnt < project_cnt:
fmt = out.write
paths = []
if in_cnt < project_cnt - in_cnt:
if in_cnt < project_cnt - in_cnt:
in_type = 'in'
for b in i.projects:
paths.append(b.project.relpath)

View File

@ -99,6 +99,10 @@ 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('--archive',
dest='archive', action='store_true',
help='checkout an archive instead of a git repository for '
'each project. See git archive.')
g.add_option('-g', '--groups',
dest='groups', default='default',
help='restrict manifest projects to ones with specified '
@ -198,6 +202,16 @@ to update the working directory files.
if opt.reference:
m.config.SetString('repo.reference', opt.reference)
if opt.archive:
if is_new:
m.config.SetString('repo.archive', 'true')
else:
print('fatal: --archive is only supported when initializing a new '
'workspace.', file=sys.stderr)
print('Either delete the .repo folder in this workspace, or initialize '
'in another location.', file=sys.stderr)
sys.exit(1)
if opt.mirror:
if is_new:
m.config.SetString('repo.mirror', 'true')
@ -366,6 +380,13 @@ to update the working directory files.
if opt.reference:
opt.reference = os.path.expanduser(opt.reference)
# Check this here, else manifest will be tagged "not new" and init won't be
# possible anymore without removing the .repo/manifests directory.
if opt.archive and opt.mirror:
print('fatal: --mirror and --archive cannot be used together.',
file=sys.stderr)
sys.exit(1)
self._SyncManifest(opt)
self._LinkManifest(opt.manifest_name)

View File

@ -62,6 +62,9 @@ branch but need to incorporate new upstream changes "underneath" them.
if opt.interactive and not one_project:
print('error: interactive rebase not supported with multiple projects',
file=sys.stderr)
if len(args) == 1:
print('note: project %s is mapped to more than one path' % (args[0],),
file=sys.stderr)
return -1
for project in all_projects:

View File

@ -219,9 +219,25 @@ later is required to fix a server side protocol bug.
dest='repo_upgraded', action='store_true',
help=SUPPRESS_HELP)
def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
def _FetchProjectList(self, opt, projects, *args):
"""Main function of the fetch threads when jobs are > 1.
Delegates most of the work to _FetchHelper.
Args:
opt: Program options returned from optparse. See _Options().
projects: Projects to fetch.
*args: Remaining arguments to pass to _FetchHelper. See the
_FetchHelper docstring for details.
"""
for project in projects:
success = self._FetchHelper(opt, project, *args)
if not success and not opt.force_broken:
break
def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
"""Fetch git objects for a single project.
Args:
opt: Program options returned from optparse. See _Options().
project: Project object for the project to fetch.
@ -235,6 +251,9 @@ later is required to fix a server side protocol bug.
can be started up.
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
@ -253,7 +272,7 @@ later is required to fix a server side protocol bug.
quiet=opt.quiet,
current_branch_only=opt.current_branch_only,
clone_bundle=not opt.no_clone_bundle,
no_tags=opt.no_tags)
no_tags=opt.no_tags, archive=self.manifest.IsArchive)
self._fetch_times.Set(project, time.time() - start)
# Lock around all the rest of the code, since printing, updating a set
@ -281,6 +300,8 @@ later is required to fix a server side protocol bug.
lock.release()
sem.release()
return success
def _Fetch(self, projects, opt):
fetched = set()
pm = Progress('Fetching projects', len(projects))
@ -294,7 +315,8 @@ later is required to fix a server side protocol bug.
quiet=opt.quiet,
current_branch_only=opt.current_branch_only,
clone_bundle=not opt.no_clone_bundle,
no_tags=opt.no_tags):
no_tags=opt.no_tags,
archive=self.manifest.IsArchive):
fetched.add(project.gitdir)
else:
print('error: Cannot fetch %s' % project.name, file=sys.stderr)
@ -303,20 +325,24 @@ later is required to fix a server side protocol bug.
else:
sys.exit(1)
else:
objdir_project_map = dict()
for project in projects:
objdir_project_map.setdefault(project.objdir, []).append(project)
threads = set()
lock = _threading.Lock()
sem = _threading.Semaphore(self.jobs)
err_event = _threading.Event()
for project in projects:
for project_list in objdir_project_map.values():
# Check for any errors before starting any new threads.
# ...we'll let existing threads finish, though.
if err_event.isSet():
break
sem.acquire()
t = _threading.Thread(target = self._FetchHelper,
t = _threading.Thread(target = self._FetchProjectList,
args = (opt,
project,
project_list,
lock,
fetched,
pm,
@ -338,10 +364,16 @@ later is required to fix a server side protocol bug.
pm.end()
self._fetch_times.Save()
self._GCProjects(projects)
if not self.manifest.IsArchive:
self._GCProjects(projects)
return fetched
def _GCProjects(self, projects):
gitdirs = {}
for project in projects:
gitdirs[project.gitdir] = project.bare_git
has_dash_c = git_require((1, 7, 2))
if multiprocessing and has_dash_c:
cpu_count = multiprocessing.cpu_count()
@ -350,8 +382,8 @@ later is required to fix a server side protocol bug.
jobs = min(self.jobs, cpu_count)
if jobs < 2:
for project in projects:
project.bare_git.gc('--auto')
for bare_git in gitdirs.values():
bare_git.gc('--auto')
return
config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
@ -360,10 +392,10 @@ later is required to fix a server side protocol bug.
sem = _threading.Semaphore(jobs)
err_event = _threading.Event()
def GC(project):
def GC(bare_git):
try:
try:
project.bare_git.gc('--auto', config=config)
bare_git.gc('--auto', config=config)
except GitError:
err_event.set()
except:
@ -372,11 +404,11 @@ later is required to fix a server side protocol bug.
finally:
sem.release()
for project in projects:
for bare_git in gitdirs.values():
if err_event.isSet():
break
sem.acquire()
t = _threading.Thread(target=GC, args=(project,))
t = _threading.Thread(target=GC, args=(bare_git,))
t.daemon = True
threads.add(t)
t.start()
@ -416,12 +448,13 @@ later is required to fix a server side protocol bug.
if path not in new_project_paths:
# If the path has already been deleted, we don't need to do it
if os.path.exists(self.manifest.topdir + '/' + path):
gitdir = os.path.join(self.manifest.topdir, path, '.git')
project = Project(
manifest = self.manifest,
name = path,
remote = RemoteSpec('origin'),
gitdir = os.path.join(self.manifest.topdir,
path, '.git'),
gitdir = gitdir,
objdir = gitdir,
worktree = os.path.join(self.manifest.topdir, path),
relpath = path,
revisionExpr = 'HEAD',
@ -496,6 +529,8 @@ later is required to fix a server side protocol bug.
sys.exit(1)
manifest_server = self.manifest.manifest_server
if not opt.quiet:
print('Using manifest server %s' % manifest_server)
if not '@' in manifest_server:
username = None
@ -564,7 +599,8 @@ later is required to fix a server side protocol bug.
sys.exit(1)
self._ReloadManifest(manifest_name)
else:
print('error: %s' % manifest_str, file=sys.stderr)
print('error: manifest server RPC call failed: %s' %
manifest_str, file=sys.stderr)
sys.exit(1)
except (socket.error, IOError, xmlrpc.client.Fault) as e:
print('error: cannot connect to manifest server %s:\n%s'
@ -638,7 +674,7 @@ later is required to fix a server side protocol bug.
previously_missing_set = missing_set
fetched.update(self._Fetch(missing, opt))
if self.manifest.IsMirror:
if self.manifest.IsMirror or self.manifest.IsArchive:
# bail out now, we have no working tree
return
@ -758,7 +794,7 @@ class _FetchTimes(object):
def _Load(self):
if self._times is None:
try:
f = open(self._path)
f = open(self._path, 'rb')
except IOError:
self._times = {}
return self._times

View File

@ -21,6 +21,7 @@ import sys
from command import InteractiveCommand
from editor import Editor
from error import HookError, UploadError
from git_command import GitCommand
from project import RepoHook
from pyversion import is_python3
@ -344,7 +345,21 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
key = 'review.%s.uploadtopic' % branch.project.remote.review
opt.auto_topic = branch.project.config.GetBoolean(key)
destination = opt.dest_branch or branch.project.dest_branch or branch.project.revisionExpr
destination = opt.dest_branch or branch.project.dest_branch
# Make sure our local branch is not setup to track a different remote branch
merge_branch = self._GetMergeBranch(branch.project)
if destination:
full_dest = 'refs/heads/%s' % destination
if not opt.dest_branch and merge_branch and merge_branch != full_dest:
print('merge branch %s does not match destination branch %s'
% (merge_branch, full_dest))
print('skipping upload.')
print('Please use `--destination %s` if this is intentional'
% destination)
branch.uploaded = False
continue
branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft, dest_branch=destination)
branch.uploaded = True
except UploadError as e:
@ -379,6 +394,21 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
if have_errors:
sys.exit(1)
def _GetMergeBranch(self, project):
p = GitCommand(project,
['rev-parse', '--abbrev-ref', 'HEAD'],
capture_stdout = True,
capture_stderr = True)
p.Wait()
local_branch = p.stdout.strip()
p = GitCommand(project,
['config', '--get', 'branch.%s.merge' % local_branch],
capture_stdout = True,
capture_stderr = True)
p.Wait()
merge_branch = p.stdout.strip()
return merge_branch
def Execute(self, opt, args):
project_list = self.GetProjects(args)
pending = []
@ -392,7 +422,16 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
for project in project_list:
if opt.current_branch:
cbr = project.CurrentBranch
avail = [project.GetUploadableBranch(cbr)] if cbr else None
up_branch = project.GetUploadableBranch(cbr)
if up_branch:
avail = [up_branch]
else:
avail = None
print('ERROR: Current branch (%s) not uploadable. '
'You may be able to type '
'"git branch --set-upstream-to m/master" to fix '
'your branch.' % str(cbr),
file=sys.stderr)
else:
avail = project.GetUploadableBranches(branch)
if avail:
@ -402,8 +441,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
self.manifest.topdir, abort_if_user_denies=True)
pending_proj_names = [project.name for (project, avail) in pending]
pending_worktrees = [project.worktree for (project, avail) in pending]
try:
hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
hook.Run(opt.allow_all_hooks, project_list=pending_proj_names,
worktree_list=pending_worktrees)
except HookError as e:
print("ERROR: %s" % str(e), file=sys.stderr)
return