Compare commits

...

13 Commits
v1.0.8 ... v1.2

Author SHA1 Message Date
5d40e26201 Treat missing attributes as None when parsing the manifest
Some of our code assumes that a property is None.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-06 11:23:27 -08:00
70939e2f73 Add <add-remote to-project="..."> to inject additional remotes
This way users can add forks they know about to an existing project
that was already declared in the primary manifest.  This is mostly
useful with the Linux kernel project, where multiple forks is quite
common for the main upstream tree (e.g. Linus' tree), a platform
architecture tree (e.g. ARM) and a device specific tree (e.g. the
msm7k tree used by Android).

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-06 11:23:08 -08:00
ae6e0949d1 Add <remote project-name="..."> attribute within projects
By setting a project-name on a remote nested within a project forks
of a project like the Linux kernel can be easily handled by fetching
all relevant forks into the same client side project under different
remote names.  Developers can create branches off different remotes
using `git checkout --track -b $myname $remote/$branch` and later
`repo upload` automatically redirects to the proper fork project
in the code review server.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-06 11:23:06 -08:00
339ba9f6f7 Use remote.*.projectname to indicate the target project for upload
This way "forks" of a project, e.g. the linux kernel, can be setup to
use different destination projects in the review server by creating
different remotes in the client side Git repository.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-06 09:52:51 -08:00
70cd4ab270 Add some short documentation about the local manifest
Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-06 08:48:44 -08:00
e284ad1d1a Add 'repo init --mirror' to download a complete forrest
The mirror option downloads a complete forrest (as described by the
manifest) and creates a replica of the remote repositories rather
than a client working directory.  This permits other clients to
sync off the mirror site.

A mirror can be positioned in a "DMZ", where the mirror executes
"repo sync" to obtain changes from the external upstream and
clients inside the protected zone operate off the mirror only,
and therefore do not require direct git:// access to the external
upstream repositories.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-05 18:08:32 -08:00
3e5481999d Add a basic outline of the repo manifest file format
Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-04 11:19:36 -08:00
d3c388391e Update proto_client to notify the user when auth cookies are accessed
Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-04 10:49:04 -08:00
2450a2987a Assume the manifest branch matches the branch name in Git
Whatever branch name we checked the manifest out from is the name
we want to reflect throughout the rest of the projects, e.g. within
the special "m/" remote space.

This reduces the difference between different branches within the
manifest file.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-04 09:45:59 -08:00
f5c25a68d8 Cleanup stale manifest migration code from manifest.py
Prior to open-sourcing repo we had manifests in two different
layouts; one where the manifest was a straight-up git clone, and
one where the manifest was our bare repository with symlink work
tree format (identical to what our projects use).  Only the latter
form is created or used by repo at this point, so the transition
code to handle the straight-up git clone is not necessary.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-04 09:45:59 -08:00
9fa44db94b Introduce 'repo abandon <branchname>' as an alias for 'git branch -D'
This destroys a local development branch, removing all history
of that branch from ever existing.  If the branch is currently
checked out we move back to the upstream revision.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-03 11:24:59 -08:00
c9ef744c7b Install a default pre-auto-gc hook in all repositories
This hook is evaluated by `git gc --auto` to determine if it is a
good idea to execute a GC at this time, or defer it to some later
date.  When working on a laptop its a good idea to avoid GC if you
are on battery power as the extra CPU and disk IO would consume a
decent amount of the charge.

The hook is the standard sample hook from git.git contrib/hooks,
last modified in git.git by 84ed4c5d117d72f02cc918e413b9861a9d2846d7.
I added the GPLv2 header to the script to ensure the license notice
is clear, as it does not match repo's own APLv2 license.

We only update hooks during initial repository creation or on
a repo sync.  This way we don't incur huge overheads from the
hook stat operations during "repo status" or even the normal
"repo sync" cases.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-03 11:00:44 -08:00
438ee1cad9 Catch symlink creation failures and report a better error
Some users have noticed that repo doesn't work on VFAT, as we
require a POSIX filesystem with POSIX symlink support.  Catching the
OSError during our symlink creation and raising a GitError with a
more descriptive message will help users to troubleshoot and fix
their own installation problems.

Signed-off-by: Shawn O. Pearce <sop@google.com>
2008-11-03 09:59:36 -08:00
14 changed files with 565 additions and 87 deletions

View File

@ -1 +1 @@
__version__ = 'v1.0-14-gc4f226bc'
__version__ = 'v1.0-69-gd1f8508c'

View File

@ -20,6 +20,7 @@ import md5
import os
import random
import socket
import sys
import time
import urllib
import urllib2
@ -29,6 +30,38 @@ from froofle.protobuf.service import RpcChannel
from froofle.protobuf.service import RpcController
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):
"""Raised to indicate an error authenticating with ClientLogin."""
@ -269,6 +302,9 @@ class HttpRpc(RpcChannel):
self._GetAuthCookie(auth_token)
self.authenticated = True
if self.cookie_file is not None:
print >>sys.stderr, \
'Saving authentication cookies to %s' \
% self.cookie_file
self.cookie_jar.save()
return
@ -337,24 +373,8 @@ class HttpRpc(RpcChannel):
opener.add_handler(urllib2.HTTPDefaultErrorHandler())
opener.add_handler(urllib2.HTTPSHandler())
opener.add_handler(urllib2.HTTPErrorProcessor())
if self.cookie_file is not None:
self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
if os.path.exists(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()
self.cookie_jar, \
self.authenticated = _open_jar(self.cookie_file)
opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
return opener

184
docs/manifest-format.txt Normal file
View File

@ -0,0 +1,184 @@
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?,
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>
]>
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.
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.

View File

@ -64,3 +64,5 @@ class RepoChangedException(Exception):
repo or manifest repositories. In this special case we must
use exec to re-execute repo with the new code and manifest.
"""
def __init__(self, extra_args=[]):
self.extra_args = extra_args

View File

@ -258,6 +258,7 @@ class Remote(object):
self.name = name
self.url = self._Get('url')
self.review = self._Get('review')
self.projectname = self._Get('projectname')
self.fetch = map(lambda x: RefSpec.FromString(x),
self._Get('fetch', all=True))
@ -285,18 +286,21 @@ class Remote(object):
return True
return False
def ResetFetch(self):
def ResetFetch(self, mirror=False):
"""Set the fetch refspec to its default value.
"""
self.fetch = [RefSpec(True,
'refs/heads/*',
'refs/remotes/%s/*' % self.name)]
if mirror:
dst = 'refs/heads/*'
else:
dst = 'refs/remotes/%s/*' % self.name
self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
def Save(self):
"""Save this remote to the configuration.
"""
self._Set('url', self.url)
self._Set('review', self.review)
self._Set('projectname', self.projectname)
self._Set('fetch', map(lambda x: str(x), self.fetch))
def _Set(self, key, value):

44
hooks/pre-auto-gc Executable file
View 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

View File

@ -186,11 +186,13 @@ def _Main(argv):
repo._Run(argv)
except KeyboardInterrupt:
sys.exit(1)
except RepoChangedException:
# If the repo or manifest changed, re-exec ourselves.
except RepoChangedException, rce:
# If repo changed, re-exec ourselves.
#
argv = list(sys.argv)
argv.extend(rce.extra_args)
try:
os.execv(__file__, sys.argv)
os.execv(__file__, argv)
except OSError, e:
print >>sys.stderr, 'fatal: cannot restart repo after upgrade'
print >>sys.stderr, 'fatal: %s' % e

View File

@ -18,7 +18,7 @@ import sys
import xml.dom.minidom
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 error import ManifestParseError
@ -45,16 +45,9 @@ class Manifest(object):
gitdir = os.path.join(repodir, 'repo/.git'),
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',
gitdir = gd,
worktree = wt)
gitdir = os.path.join(repodir, 'manifests.git'),
worktree = os.path.join(repodir, 'manifests'))
self._Unload()
@ -95,6 +88,10 @@ class Manifest(object):
self._Load()
return self._default
@property
def IsMirror(self):
return self.manifestProject.config.GetBoolean('repo.mirror')
def _Unload(self):
self._loaded = False
self._projects = {}
@ -104,6 +101,12 @@ class Manifest(object):
def _Load(self):
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)
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
@ -115,6 +118,10 @@ class Manifest(object):
finally:
self.manifestFile = real
if self.IsMirror:
self._AddMetaProjectMirror(self.repoProject)
self._AddMetaProjectMirror(self.manifestProject)
self._loaded = True
def _ParseManifest(self, is_root_file):
@ -130,11 +137,6 @@ class Manifest(object):
"no <manifest> in %s" % \
self.manifestFile
if is_root_file:
self.branch = config.getAttribute('branch')
if not self.branch:
self.branch = 'default'
for node in config.childNodes:
if node.nodeName == 'remote':
remote = self._ParseRemote(node)
@ -163,6 +165,50 @@ class Manifest(object):
(project.name, self.manifestFile)
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):
"""
reads a <remote> element from the manifest file
@ -170,10 +216,17 @@ class Manifest(object):
name = self._reqatt(node, 'name')
fetch = self._reqatt(node, 'fetch')
review = node.getAttribute('review')
if review == '':
review = None
projectName = node.getAttribute('project-name')
if projectName == '':
projectName = None
r = Remote(name=name,
fetch=fetch,
review=review)
review=review,
projectName=projectName)
for n in node.childNodes:
if n.nodeName == 'require':
@ -188,6 +241,8 @@ class Manifest(object):
d = _Default()
d.remote = self._get_remote(node)
d.revision = node.getAttribute('revision')
if d.revision == '':
d.revision = None
return d
def _ParseProject(self, node):
@ -220,8 +275,13 @@ class Manifest(object):
"project %s path cannot be absolute in %s" % \
(name, self.manifestFile)
worktree = os.path.join(self.topdir, path)
gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
if self.IsMirror:
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,
name = name,
@ -233,23 +293,28 @@ class Manifest(object):
for n in node.childNodes:
if n.nodeName == 'remote':
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
self._ParseProjectExtraRemote(project, n)
elif n.nodeName == 'copyfile':
self._ParseCopyFile(project, n)
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):
src = self._reqatt(node, 'src')
dest = self._reqatt(node, 'dest')
# src is project relative, and dest is relative to the top of the tree
project.AddCopyFile(src, os.path.join(self.topdir, dest))
if not self.IsMirror:
# 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):
name = node.getAttribute('remote')

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
import filecmp
import os
import re
@ -45,6 +46,32 @@ def _info(fmt, *args):
def not_rev(r):
return '^' + r
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):
_commit_cache = None
@ -184,7 +211,10 @@ class Project(object):
gitdir = self.gitdir,
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)
@property
@ -431,13 +461,17 @@ class Project(object):
if not base_list:
raise GitError('no base refs, cannot upload %s' % branch.name)
if not branch.remote.projectname:
branch.remote.projectname = self.name
branch.remote.Save()
print >>sys.stderr, ''
_info("Uploading %s to %s:", branch.name, self.name)
try:
UploadBundle(project = self,
server = branch.remote.review,
email = self.UserEmail,
dest_project = self.name,
dest_project = branch.remote.projectname,
dest_branch = dest_branch,
src_branch = R_HEADS + branch.name,
bases = base_list)
@ -462,16 +496,28 @@ class Project(object):
print >>sys.stderr
print >>sys.stderr, 'Initializing project %s ...' % self.name
self._InitGitDir()
self._InitRemote()
for r in self.extraRemotes.values():
if not self._RemoteFetch(r.name):
return False
if not self._RemoteFetch():
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
def PostRepoUpgrade(self):
self._InitHooks()
def _CopyFiles(self):
for file in self.copyfiles:
file._Copy()
@ -680,6 +726,22 @@ class Project(object):
else:
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):
"""Prune any topic branches already merged into upstream.
"""
@ -746,9 +808,11 @@ class Project(object):
def _RemoteFetch(self, name=None):
if not name:
name = self.remote.name
return GitCommand(self,
['fetch', name],
bare = True).Wait() == 0
cmd = ['fetch']
if not self.worktree:
cmd.append('--update-head-ok')
cmd.append(name)
return GitCommand(self, cmd, bare = True).Wait() == 0
def _Checkout(self, rev, quiet=False):
cmd = ['checkout']
@ -794,14 +858,29 @@ class Project(object):
to_rm = []
for old_hook in to_rm:
os.remove(os.path.join(hooks, old_hook))
# TODO(sop) install custom repo hooks
self._InitHooks()
m = self.manifest.manifestProject.config
for key in ['user.name', 'user.email']:
if m.Has(key, include_defaults = False):
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):
if self.remote.fetchUrl:
remote = self.GetRemote(self.remote.name)
@ -812,14 +891,23 @@ class Project(object):
url += '/%s.git' % self.name
remote.url = url
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()
for r in self.extraRemotes.values():
remote = self.GetRemote(r.name)
remote.url = r.fetchUrl
remote.review = r.reviewUrl
if r.projectName:
remote.projectname = r.projectName
elif remote.projectname is None:
remote.projectname = self.name
remote.ResetFetch()
remote.Save()
@ -836,24 +924,16 @@ class Project(object):
dst = remote.ToLocal(self.revision)
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):
dotgit = os.path.join(self.worktree, '.git')
if not os.path.exists(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',
'description',
'hooks',
@ -864,8 +944,15 @@ class Project(object):
'refs',
'rr-cache',
'svn']:
os.symlink(os.path.join(relgit, name),
os.path.join(dotgit, name))
try:
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.bare_git.rev_parse('%s^0' % rev)

View File

@ -14,8 +14,12 @@
# limitations under the License.
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.fetchUrl = fetch
self.reviewUrl = review
self.projectName = projectName
self.requiredCommits = []

5
repo
View File

@ -28,7 +28,7 @@ if __name__ == '__main__':
del magic
# increment this whenever we make important changes to this script
VERSION = (1, 6)
VERSION = (1, 7)
# increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (1,0)
@ -115,6 +115,9 @@ group.add_option('-b', '--manifest-branch',
group.add_option('-m', '--manifest-name',
dest='manifest_name',
help='initial manifest file', metavar='NAME.xml')
group.add_option('--mirror',
dest='mirror', action='store_true',
help='mirror the forrest')
# Tool
group = init_optparse.add_option_group('Version options')

42
subcmds/abandon.py Normal file
View 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)

View File

@ -57,6 +57,10 @@ default.xml will be used.
g.add_option('-m', '--manifest-name',
dest='manifest_name', default='default.xml',
help='initial manifest file', metavar='NAME.xml')
g.add_option('--mirror',
dest='mirror', action='store_true',
help='mirror the forrest')
# Tool
g = p.add_option_group('Version options')
@ -112,6 +116,9 @@ default.xml will be used.
r.ResetFetch()
r.Save()
if opt.mirror:
m.config.SetString('repo.mirror', 'true')
m.Sync_NetworkHalf()
m.Sync_LocalHalf()
m.StartBranch('default')
@ -185,9 +192,14 @@ default.xml will be used.
self._SyncManifest(opt)
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._ConfigureColor()
if opt.mirror:
type = 'mirror '
else:
type = ''
print ''
print 'repo initialized in %s' % self.manifest.topdir
print 'repo %sinitialized in %s' % (type, self.manifest.topdir)

View File

@ -49,6 +49,9 @@ the manifest.
p.add_option('--no-repo-verify',
dest='no_repo_verify', action='store_true',
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):
fetched = set()
@ -67,6 +70,11 @@ the manifest.
mp = self.manifest.manifestProject
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)
fetched = self._Fetch(rp, mp, *all)
@ -77,7 +85,7 @@ the manifest.
if not rp.Sync_LocalHalf():
sys.exit(1)
print >>sys.stderr, 'info: Restarting repo with latest version'
raise RepoChangedException()
raise RepoChangedException(['--repo-upgraded'])
else:
print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
@ -94,8 +102,9 @@ the manifest.
self._Fetch(*missing)
for project in all:
if not project.Sync_LocalHalf():
sys.exit(1)
if project.worktree:
if not project.Sync_LocalHalf():
sys.exit(1)
def _VerifyTag(project):