Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial

  • Loading branch information...
commit c9f01fae9fef9be963cef1d7e2846d9bea68ab1e 0 parents
@toddlipcon toddlipcon authored
70 crepo.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python2.5
+# (c) Copyright 2009 Cloudera, Inc.
+
+import os
+import sys
+import optparse
+import manifest
+import logging
+from git_command import GitCommand
+from git_config import GitConfig
+
+def load_manifest():
+ return manifest.load_manifest("manifest.json")
+
+def help(args):
+ """shows some help
+
+ asdfsaddsf"""
+ print "hi there" + repr(args)
+
+def workdir_for_project(p):
+ return p.dir # TODO(todd) add root to manifest
+
+def init(args):
+ """Initializes repository"""
+ man = load_manifest()
+
+ for (name, project) in man.projects.iteritems():
+ logging.warn("Initializing project: %s" % name)
+ clone_remote = man.remotes[project.from_remote]
+ clone_url = clone_remote.fetch % name
+ p = GitCommand(None, ["clone", clone_url, project.dir])
+ p.Wait()
+
+def checkout_branches(args):
+ """Checks out the tracking branches listed in the manifest."""
+ man = load_manifest()
+ for (name, project) in man.projects.iteritems():
+ logging.warn("Checking out tracking branch in project: %s" % name)
+ cwd = workdir_for_project(project)
+ gitdir = os.path.join(cwd, ".git")
+ if not os.path.isdir(gitdir):
+ raise Exception("no git dir at " + gitdir)
+ conf = GitConfig.ForRepository()
+ print conf.GetBranch(project.refspec)
+
+
+COMMANDS = {
+ 'help': help,
+ 'init': init,
+ 'checkout-branches': checkout_branches
+ }
+
+def usage():
+ print >>sys.stderr, "you screwed up. here are the commands:"
+ print >>sys.stderr
+
+ for (command, function) in COMMANDS.iteritems():
+ print >>sys.stderr, " %s\t%s" % (command, function.__doc__.split("\n")[0] or "no docs")
+ sys.exit(1)
+
+def main():
+ args = sys.argv[1:]
+ if len(args) == 0 or args[0] not in COMMANDS:
+ usage()
+ command = COMMANDS[args[0]]
+ command.__call__(args[1:])
+
+if __name__ == "__main__":
+ main()
72 error.py
@@ -0,0 +1,72 @@
+#
+# 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.
+
+class ManifestParseError(Exception):
+ """Failed to parse the manifest file.
+ """
+
+class ManifestInvalidRevisionError(Exception):
+ """The revision value in a project is incorrect.
+ """
+
+class EditorError(Exception):
+ """Unspecified error from the user's text editor.
+ """
+
+class GitError(Exception):
+ """Unspecified internal error from git.
+ """
+ def __init__(self, command):
+ self.command = command
+
+ def __str__(self):
+ return self.command
+
+class ImportError(Exception):
+ """An import from a non-Git format cannot be performed.
+ """
+ def __init__(self, reason):
+ self.reason = reason
+
+ def __str__(self):
+ return self.reason
+
+class UploadError(Exception):
+ """A bundle upload to Gerrit did not succeed.
+ """
+ def __init__(self, reason):
+ self.reason = reason
+
+ def __str__(self):
+ return self.reason
+
+class NoSuchProjectError(Exception):
+ """A specified project does not exist in the work tree.
+ """
+ def __init__(self, name=None):
+ self.name = name
+
+ def __str__(self):
+ if self.Name is None:
+ return 'in current directory'
+ return self.name
+
+class RepoChangedException(Exception):
+ """Thrown if 'repo sync' results in repo updating its internal
+ 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
189 git_command.py
@@ -0,0 +1,189 @@
+#
+# 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 os
+import sys
+import subprocess
+import tempfile
+from error import GitError
+from trace import REPO_TRACE, IsTrace, Trace
+
+GIT = 'git'
+MIN_GIT_VERSION = (1, 5, 4)
+GIT_DIR = 'GIT_DIR'
+
+LAST_GITDIR = None
+LAST_CWD = None
+
+_ssh_proxy_path = None
+_ssh_sock_path = None
+
+def _ssh_sock(create=True):
+ global _ssh_sock_path
+ if _ssh_sock_path is None:
+ if not create:
+ return None
+ dir = '/tmp'
+ if not os.path.exists(dir):
+ dir = tempfile.gettempdir()
+ _ssh_sock_path = os.path.join(
+ tempfile.mkdtemp('', 'ssh-', dir),
+ 'master-%r@%h:%p')
+ return _ssh_sock_path
+
+def _ssh_proxy():
+ global _ssh_proxy_path
+ if _ssh_proxy_path is None:
+ _ssh_proxy_path = os.path.join(
+ os.path.dirname(__file__),
+ 'git_ssh')
+ return _ssh_proxy_path
+
+
+class _GitCall(object):
+ def version(self):
+ p = GitCommand(None, ['--version'], capture_stdout=True)
+ if p.Wait() == 0:
+ return p.stdout
+ return None
+
+ def __getattr__(self, name):
+ name = name.replace('_','-')
+ def fun(*cmdv):
+ command = [name]
+ command.extend(cmdv)
+ return GitCommand(None, command).Wait() == 0
+ return fun
+git = _GitCall()
+
+class GitCommand(object):
+ def __init__(self,
+ project,
+ cmdv,
+ bare = False,
+ provide_stdin = False,
+ capture_stdout = False,
+ capture_stderr = False,
+ disable_editor = False,
+ ssh_proxy = False,
+ cwd = None,
+ gitdir = None):
+ env = dict(os.environ)
+
+ for e in [REPO_TRACE,
+ GIT_DIR,
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
+ 'GIT_OBJECT_DIRECTORY',
+ 'GIT_WORK_TREE',
+ 'GIT_GRAFT_FILE',
+ 'GIT_INDEX_FILE']:
+ if e in env:
+ del env[e]
+
+ if disable_editor:
+ env['GIT_EDITOR'] = ':'
+ if ssh_proxy:
+ env['REPO_SSH_SOCK'] = _ssh_sock()
+ env['GIT_SSH'] = _ssh_proxy()
+
+ if project:
+ if not cwd:
+ cwd = project.worktree
+ if not gitdir:
+ gitdir = project.gitdir
+
+ command = [GIT]
+ if bare:
+ if gitdir:
+ env[GIT_DIR] = gitdir
+ cwd = None
+ command.extend(cmdv)
+
+ if provide_stdin:
+ stdin = subprocess.PIPE
+ else:
+ stdin = None
+
+ if capture_stdout:
+ stdout = subprocess.PIPE
+ else:
+ stdout = None
+
+ if capture_stderr:
+ stderr = subprocess.PIPE
+ else:
+ stderr = None
+
+ if IsTrace():
+ global LAST_CWD
+ global LAST_GITDIR
+
+ dbg = ''
+
+ if cwd and LAST_CWD != cwd:
+ if LAST_GITDIR or LAST_CWD:
+ dbg += '\n'
+ dbg += ': cd %s\n' % cwd
+ LAST_CWD = cwd
+
+ if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]:
+ if LAST_GITDIR or LAST_CWD:
+ dbg += '\n'
+ dbg += ': export GIT_DIR=%s\n' % env[GIT_DIR]
+ LAST_GITDIR = env[GIT_DIR]
+
+ dbg += ': '
+ dbg += ' '.join(command)
+ if stdin == subprocess.PIPE:
+ dbg += ' 0<|'
+ if stdout == subprocess.PIPE:
+ dbg += ' 1>|'
+ if stderr == subprocess.PIPE:
+ dbg += ' 2>|'
+ Trace('%s', dbg)
+
+ try:
+ p = subprocess.Popen(command,
+ cwd = cwd,
+ env = env,
+ stdin = stdin,
+ stdout = stdout,
+ stderr = stderr)
+ except Exception, e:
+ raise GitError('%s: %s' % (command[1], e))
+
+ self.process = p
+ self.stdin = p.stdin
+
+ def Wait(self):
+ p = self.process
+
+ if p.stdin:
+ p.stdin.close()
+ self.stdin = None
+
+ if p.stdout:
+ self.stdout = p.stdout.read()
+ p.stdout.close()
+ else:
+ p.stdout = None
+
+ if p.stderr:
+ self.stderr = p.stderr.read()
+ p.stderr.close()
+ else:
+ p.stderr = None
+
+ return self.process.wait()
582 git_config.py
@@ -0,0 +1,582 @@
+#
+# 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 cPickle
+import os
+import re
+import subprocess
+import sys
+import time
+from signal import SIGTERM
+from urllib2 import urlopen, HTTPError
+from error import GitError, UploadError
+from trace import Trace
+from git_command import GitCommand, _ssh_sock
+
+R_HEADS = 'refs/heads/'
+R_TAGS = 'refs/tags/'
+ID_RE = re.compile('^[0-9a-f]{40}$')
+
+REVIEW_CACHE = dict()
+
+def IsId(rev):
+ return ID_RE.match(rev)
+
+def _key(name):
+ parts = name.split('.')
+ if len(parts) < 2:
+ return name.lower()
+ parts[ 0] = parts[ 0].lower()
+ parts[-1] = parts[-1].lower()
+ return '.'.join(parts)
+
+class GitConfig(object):
+ _ForUser = None
+
+ @classmethod
+ def ForUser(cls):
+ if cls._ForUser is None:
+ cls._ForUser = cls(file = os.path.expanduser('~/.gitconfig'))
+ return cls._ForUser
+
+ @classmethod
+ def ForRepository(cls, gitdir, defaults=None):
+ return cls(file = os.path.join(gitdir, 'config'),
+ defaults = defaults)
+
+ def __init__(self, file, defaults=None):
+ self.file = file
+ self.defaults = defaults
+ self._cache_dict = None
+ self._section_dict = None
+ self._remotes = {}
+ self._branches = {}
+ self._pickle = os.path.join(
+ os.path.dirname(self.file),
+ '.repopickle_' + os.path.basename(self.file))
+
+ def Has(self, name, include_defaults = True):
+ """Return true if this configuration file has the key.
+ """
+ if _key(name) in self._cache:
+ return True
+ if include_defaults and self.defaults:
+ return self.defaults.Has(name, include_defaults = True)
+ return False
+
+ def GetBoolean(self, name):
+ """Returns a boolean from the configuration file.
+ None : The value was not defined, or is not a boolean.
+ True : The value was set to true or yes.
+ False: The value was set to false or no.
+ """
+ v = self.GetString(name)
+ if v is None:
+ return None
+ v = v.lower()
+ if v in ('true', 'yes'):
+ return True
+ if v in ('false', 'no'):
+ return False
+ return None
+
+ def GetString(self, name, all=False):
+ """Get the first value for a key, or None if it is not defined.
+
+ This configuration file is used first, if the key is not
+ defined or all = True then the defaults are also searched.
+ """
+ try:
+ v = self._cache[_key(name)]
+ except KeyError:
+ if self.defaults:
+ return self.defaults.GetString(name, all = all)
+ v = []
+
+ if not all:
+ if v:
+ return v[0]
+ return None
+
+ r = []
+ r.extend(v)
+ if self.defaults:
+ r.extend(self.defaults.GetString(name, all = True))
+ return r
+
+ def SetString(self, name, value):
+ """Set the value(s) for a key.
+ Only this configuration file is modified.
+
+ The supplied value should be either a string,
+ or a list of strings (to store multiple values).
+ """
+ key = _key(name)
+
+ try:
+ old = self._cache[key]
+ except KeyError:
+ old = []
+
+ if value is None:
+ if old:
+ del self._cache[key]
+ self._do('--unset-all', name)
+
+ elif isinstance(value, list):
+ if len(value) == 0:
+ self.SetString(name, None)
+
+ elif len(value) == 1:
+ self.SetString(name, value[0])
+
+ elif old != value:
+ self._cache[key] = list(value)
+ self._do('--replace-all', name, value[0])
+ for i in xrange(1, len(value)):
+ self._do('--add', name, value[i])
+
+ elif len(old) != 1 or old[0] != value:
+ self._cache[key] = [value]
+ self._do('--replace-all', name, value)
+
+ def GetRemote(self, name):
+ """Get the remote.$name.* configuration values as an object.
+ """
+ try:
+ r = self._remotes[name]
+ except KeyError:
+ r = Remote(self, name)
+ self._remotes[r.name] = r
+ return r
+
+ def GetBranch(self, name):
+ """Get the branch.$name.* configuration values as an object.
+ """
+ try:
+ b = self._branches[name]
+ except KeyError:
+ b = Branch(self, name)
+ self._branches[b.name] = b
+ return b
+
+ def HasSection(self, section, subsection = ''):
+ """Does at least one key in section.subsection exist?
+ """
+ try:
+ return subsection in self._sections[section]
+ except KeyError:
+ return False
+
+ @property
+ def _sections(self):
+ d = self._section_dict
+ if d is None:
+ d = {}
+ for name in self._cache.keys():
+ p = name.split('.')
+ if 2 == len(p):
+ section = p[0]
+ subsect = ''
+ else:
+ section = p[0]
+ subsect = '.'.join(p[1:-1])
+ if section not in d:
+ d[section] = set()
+ d[section].add(subsect)
+ self._section_dict = d
+ return d
+
+ @property
+ def _cache(self):
+ if self._cache_dict is None:
+ self._cache_dict = self._Read()
+ return self._cache_dict
+
+ def _Read(self):
+ d = self._ReadPickle()
+ if d is None:
+ d = self._ReadGit()
+ self._SavePickle(d)
+ return d
+
+ def _ReadPickle(self):
+ try:
+ if os.path.getmtime(self._pickle) \
+ <= os.path.getmtime(self.file):
+ os.remove(self._pickle)
+ return None
+ except OSError:
+ return None
+ try:
+ Trace(': unpickle %s', self.file)
+ fd = open(self._pickle, 'rb')
+ try:
+ return cPickle.load(fd)
+ finally:
+ fd.close()
+ except IOError:
+ os.remove(self._pickle)
+ return None
+ except cPickle.PickleError:
+ os.remove(self._pickle)
+ return None
+
+ def _SavePickle(self, cache):
+ try:
+ fd = open(self._pickle, 'wb')
+ try:
+ cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
+ finally:
+ fd.close()
+ except IOError:
+ os.remove(self._pickle)
+ except cPickle.PickleError:
+ os.remove(self._pickle)
+
+ def _ReadGit(self):
+ d = self._do('--null', '--list')
+ c = {}
+ while d:
+ lf = d.index('\n')
+ nul = d.index('\0', lf + 1)
+
+ key = _key(d[0:lf])
+ val = d[lf + 1:nul]
+
+ if key in c:
+ c[key].append(val)
+ else:
+ c[key] = [val]
+
+ d = d[nul + 1:]
+ return c
+
+ def _do(self, *args):
+ command = ['config', '--file', self.file]
+ command.extend(args)
+
+ p = GitCommand(None,
+ command,
+ capture_stdout = True,
+ capture_stderr = True)
+ if p.Wait() == 0:
+ return p.stdout
+ else:
+ GitError('git config %s: %s' % (str(args), p.stderr))
+
+
+class RefSpec(object):
+ """A Git refspec line, split into its components:
+
+ forced: True if the line starts with '+'
+ src: Left side of the line
+ dst: Right side of the line
+ """
+
+ @classmethod
+ def FromString(cls, rs):
+ lhs, rhs = rs.split(':', 2)
+ if lhs.startswith('+'):
+ lhs = lhs[1:]
+ forced = True
+ else:
+ forced = False
+ return cls(forced, lhs, rhs)
+
+ def __init__(self, forced, lhs, rhs):
+ self.forced = forced
+ self.src = lhs
+ self.dst = rhs
+
+ def SourceMatches(self, rev):
+ if self.src:
+ if rev == self.src:
+ return True
+ if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
+ return True
+ return False
+
+ def DestMatches(self, ref):
+ if self.dst:
+ if ref == self.dst:
+ return True
+ if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
+ return True
+ return False
+
+ def MapSource(self, rev):
+ if self.src.endswith('/*'):
+ return self.dst[:-1] + rev[len(self.src) - 1:]
+ return self.dst
+
+ def __str__(self):
+ s = ''
+ if self.forced:
+ s += '+'
+ if self.src:
+ s += self.src
+ if self.dst:
+ s += ':'
+ s += self.dst
+ return s
+
+
+_ssh_cache = {}
+_ssh_master = True
+
+def _open_ssh(host, port):
+ global _ssh_master
+
+ key = '%s:%s' % (host, port)
+ if key in _ssh_cache:
+ return True
+
+ if not _ssh_master \
+ or 'GIT_SSH' in os.environ \
+ or sys.platform in ('win32', 'cygwin'):
+ # failed earlier, or cygwin ssh can't do this
+ #
+ return False
+
+ command = ['ssh',
+ '-o','ControlPath %s' % _ssh_sock(),
+ '-p',str(port),
+ '-M',
+ '-N',
+ host]
+ try:
+ Trace(': %s', ' '.join(command))
+ p = subprocess.Popen(command)
+ except Exception, e:
+ _ssh_master = False
+ print >>sys.stderr, \
+ '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
+ % (host,port, str(e))
+ return False
+
+ _ssh_cache[key] = p
+ time.sleep(1)
+ return True
+
+def close_ssh():
+ for key,p in _ssh_cache.iteritems():
+ os.kill(p.pid, SIGTERM)
+ p.wait()
+ _ssh_cache.clear()
+
+ d = _ssh_sock(create=False)
+ if d:
+ try:
+ os.rmdir(os.path.dirname(d))
+ except OSError:
+ pass
+
+URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
+URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
+
+def _preconnect(url):
+ m = URI_ALL.match(url)
+ if m:
+ scheme = m.group(1)
+ host = m.group(2)
+ if ':' in host:
+ host, port = host.split(':')
+ else:
+ port = 22
+ if scheme in ('ssh', 'git+ssh', 'ssh+git'):
+ return _open_ssh(host, port)
+ return False
+
+ m = URI_SCP.match(url)
+ if m:
+ host = m.group(1)
+ return _open_ssh(host, 22)
+
+
+class Remote(object):
+ """Configuration options related to a remote.
+ """
+ def __init__(self, config, name):
+ self._config = config
+ 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))
+ self._review_protocol = None
+
+ def PreConnectFetch(self):
+ return _preconnect(self.url)
+
+ @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 u.endswith('/Gerrit'):
+ u = u[:len(u) - len('/Gerrit')]
+ if not u.endswith('/ssh_info'):
+ if not u.endswith('/'):
+ u += '/'
+ u += 'ssh_info'
+
+ if u in REVIEW_CACHE:
+ info = REVIEW_CACHE[u]
+ self._review_protocol = info[0]
+ self._review_host = info[1]
+ self._review_port = info[2]
+ else:
+ try:
+ info = urlopen(u).read()
+ if info == 'NOT_AVAILABLE':
+ raise UploadError('Upload over ssh unavailable')
+ if '<' in info:
+ # Assume the server gave us some sort of HTML
+ # response back, like maybe a login page.
+ #
+ raise UploadError('Cannot read %s:\n%s' % (u, info))
+
+ 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'
+ self._review_host = None
+ self._review_port = None
+ else:
+ raise UploadError('Cannot guess Gerrit version')
+
+ REVIEW_CACHE[u] = (
+ self._review_protocol,
+ self._review_host,
+ self._review_port)
+ 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):
+ """Convert a remote revision string to something we have locally.
+ """
+ if IsId(rev):
+ return rev
+ if rev.startswith(R_TAGS):
+ return rev
+
+ if not rev.startswith('refs/'):
+ rev = R_HEADS + rev
+
+ for spec in self.fetch:
+ if spec.SourceMatches(rev):
+ return spec.MapSource(rev)
+ raise GitError('remote %s does not have %s' % (self.name, rev))
+
+ def WritesTo(self, ref):
+ """True if the remote stores to the tracking ref.
+ """
+ for spec in self.fetch:
+ if spec.DestMatches(ref):
+ return True
+ return False
+
+ def ResetFetch(self, mirror=False):
+ """Set the fetch refspec to its default value.
+ """
+ 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):
+ key = 'remote.%s.%s' % (self.name, key)
+ return self._config.SetString(key, value)
+
+ def _Get(self, key, all=False):
+ key = 'remote.%s.%s' % (self.name, key)
+ return self._config.GetString(key, all = all)
+
+
+class Branch(object):
+ """Configuration options related to a single branch.
+ """
+ def __init__(self, config, name):
+ self._config = config
+ self.name = name
+ self.merge = self._Get('merge')
+
+ r = self._Get('remote')
+ if r:
+ self.remote = self._config.GetRemote(r)
+ else:
+ self.remote = None
+
+ @property
+ def LocalMerge(self):
+ """Convert the merge spec to a local name.
+ """
+ if self.remote and self.merge:
+ return self.remote.ToLocal(self.merge)
+ return None
+
+ def Save(self):
+ """Save this branch back into the configuration.
+ """
+ if self._config.HasSection('branch', self.name):
+ if self.remote:
+ self._Set('remote', self.remote.name)
+ else:
+ self._Set('remote', None)
+ self._Set('merge', self.merge)
+
+ else:
+ fd = open(self._config.file, 'ab')
+ try:
+ fd.write('[branch "%s"]\n' % self.name)
+ if self.remote:
+ fd.write('\tremote = %s\n' % self.remote.name)
+ if self.merge:
+ fd.write('\tmerge = %s\n' % self.merge)
+ finally:
+ fd.close()
+
+ def _Set(self, key, value):
+ key = 'branch.%s.%s' % (self.name, key)
+ return self._config.SetString(key, value)
+
+ def _Get(self, key, all=False):
+ key = 'branch.%s.%s' % (self.name, key)
+ return self._config.GetString(key, all = all)
160 git_refs.py
@@ -0,0 +1,160 @@
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+from trace import Trace
+
+HEAD = 'HEAD'
+R_HEADS = 'refs/heads/'
+R_TAGS = 'refs/tags/'
+R_PUB = 'refs/published/'
+R_M = 'refs/remotes/m/'
+
+
+class GitRefs(object):
+ def __init__(self, gitdir):
+ self._gitdir = gitdir
+ self._phyref = None
+ self._symref = None
+ self._mtime = {}
+
+ @property
+ def all(self):
+ self._EnsureLoaded()
+ return self._phyref
+
+ def get(self, name):
+ try:
+ return self.all[name]
+ except KeyError:
+ return ''
+
+ def deleted(self, name):
+ if self._phyref is not None:
+ if name in self._phyref:
+ del self._phyref[name]
+
+ if name in self._symref:
+ del self._symref[name]
+
+ if name in self._mtime:
+ del self._mtime[name]
+
+ def symref(self, name):
+ try:
+ self._EnsureLoaded()
+ return self._symref[name]
+ except KeyError:
+ return ''
+
+ def _EnsureLoaded(self):
+ if self._phyref is None or self._NeedUpdate():
+ self._LoadAll()
+
+ def _NeedUpdate(self):
+ Trace(': scan refs %s', self._gitdir)
+
+ for name, mtime in self._mtime.iteritems():
+ try:
+ if mtime != os.path.getmtime(os.path.join(self._gitdir, name)):
+ return True
+ except OSError:
+ return True
+ return False
+
+ def _LoadAll(self):
+ Trace(': load refs %s', self._gitdir)
+
+ self._phyref = {}
+ self._symref = {}
+ self._mtime = {}
+
+ self._ReadPackedRefs()
+ self._ReadLoose('refs/')
+ self._ReadLoose1(os.path.join(self._gitdir, HEAD), HEAD)
+
+ scan = self._symref
+ attempts = 0
+ while scan and attempts < 5:
+ scan_next = {}
+ for name, dest in scan.iteritems():
+ if dest in self._phyref:
+ self._phyref[name] = self._phyref[dest]
+ else:
+ scan_next[name] = dest
+ scan = scan_next
+ attempts += 1
+
+ def _ReadPackedRefs(self):
+ path = os.path.join(self._gitdir, 'packed-refs')
+ try:
+ fd = open(path, 'rb')
+ mtime = os.path.getmtime(path)
+ except IOError:
+ return
+ except OSError:
+ return
+ try:
+ for line in fd:
+ if line[0] == '#':
+ continue
+ if line[0] == '^':
+ continue
+
+ line = line[:-1]
+ p = line.split(' ')
+ id = p[0]
+ name = p[1]
+
+ self._phyref[name] = id
+ finally:
+ fd.close()
+ self._mtime['packed-refs'] = mtime
+
+ def _ReadLoose(self, prefix):
+ base = os.path.join(self._gitdir, prefix)
+ for name in os.listdir(base):
+ p = os.path.join(base, name)
+ if os.path.isdir(p):
+ self._mtime[prefix] = os.path.getmtime(base)
+ self._ReadLoose(prefix + name + '/')
+ elif name.endswith('.lock'):
+ pass
+ else:
+ self._ReadLoose1(p, prefix + name)
+
+ def _ReadLoose1(self, path, name):
+ try:
+ fd = open(path, 'rb')
+ mtime = os.path.getmtime(path)
+ except OSError:
+ return
+ except IOError:
+ return
+ try:
+ id = fd.readline()
+ finally:
+ fd.close()
+
+ if not id:
+ return
+ id = id[:-1]
+
+ if id.startswith('ref: '):
+ self._symref[name] = id[5:]
+ else:
+ self._phyref[name] = id
+ self._mtime[name] = mtime
15 manifest.json
@@ -0,0 +1,15 @@
+{
+ "default-remote": "origin",
+ "default-revision": "master",
+
+ "remotes":
+ {"origin": { "fetch": "ssh://git@git.sf.cloudera.com/%s.git" },
+ "apache": { "fetch": "ssh://git@git.sf.cloudera.com/apache-%s.git" }},
+
+ "projects":
+ {"hadoop": {},
+ "pig": {"refspec": "rapleaf",
+ "dir": "my-pig-dir",
+ "remotes": ["origin", "apache"]}
+ }
+ }
109 manifest.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python2.5
+# (c) Copyright 2009 Cloudera, Inc.
+import simplejson
+import os
+
+class Manifest(object):
+ def __init__(self,
+ remotes=[],
+ projects=[],
+ default_revision="master",
+ default_remote="origin"):
+ self.remotes = remotes
+ self.projects = projects
+ self.default_revision = default_revision
+ self.default_remote = default_remote
+
+ @staticmethod
+ def from_dict(data):
+ remotes = dict([(name, Remote.from_dict(d)) for (name, d) in data.get('remotes', {}).iteritems()])
+
+ default_remote = data.get("default_remote", "origin")
+ assert default_remote in remotes
+
+ projects = dict([
+ (name, Project.from_dict(name=name, data=d, remotes=remotes, default_remote=default_remote))
+ for (name, d) in data.get('projects', {}).iteritems()])
+
+ return Manifest(
+ default_revision=data.get("default-revision", "master"),
+ default_remote=default_remote,
+ projects=projects,
+ remotes=remotes)
+
+ def to_json(self):
+ return simplejson.dumps(self.data_for_json(), indent=2)
+
+ def data_for_json(self):
+ return {
+ "default-revision": self.default_revision,
+ "default-remote": self.default_remote,
+ "remotes": dict( [(name, remote.data_for_json()) for (name, remote) in self.remotes.iteritems()] ),
+ "projects": dict( [(name, project.data_for_json()) for (name, project) in self.projects.iteritems()] ),
+ }
+
+ def __repr__(self):
+ return self.to_json()
+
+
+class Remote(object):
+ def __init__(self,
+ fetch):
+ self.fetch = fetch
+
+ @staticmethod
+ def from_dict(data):
+ return Remote(fetch=data.get('fetch'))
+
+ def to_json(self):
+ return simplejson.dumps(self.data_for_json(), indent=2)
+
+ def data_for_json(self):
+ return {'fetch': self.fetch}
+
+class Project(object):
+ def __init__(self,
+ name=None,
+ remotes=None,
+ refspec="master", # the remote ref to pull
+ from_remote="origin", # where to pull from
+ dir=None,
+ ):
+ self.name = name
+ self.remotes = remotes if remotes else []
+ self.dir = dir if dir else name
+ self.from_remote = from_remote
+ self.refspec = refspec
+
+ @staticmethod
+ def from_dict(name, data, remotes, default_remote):
+ my_remotes = dict([ (r, remotes[r])
+ for r in data.get('remotes', [default_remote])])
+
+ from_remote = data.get('from-remote', default_remote)
+ assert from_remote in remotes
+ return Project(name=name,
+ remotes=my_remotes,
+ refspec=data.get('refspec', 'master'),
+ dir=data.get('dir', name),
+ from_remote=from_remote)
+
+ def to_json(self):
+ return simplejson.dumps(self.data_for_json())
+
+ def data_for_json(self):
+ return {'name': self.name,
+ 'remotes': self.remotes.keys(),
+ 'refspec': self.refspec,
+ 'from-remote': self.from_remote,
+ 'dir': self.dir}
+
+
+def load_manifest(path):
+ data = simplejson.load(file(path))
+ return Manifest.from_dict(data)
+
+
+def test_json_load_store():
+ man = load_manifest(os.path.join(os.path.dirname(__file__), 'test', 'test_manifest.json'))
+ assert len(man.to_json()) > 10
15 test/test_manifest.json
@@ -0,0 +1,15 @@
+{
+ "default-remote": "origin",
+ "default-revision": "master",
+
+ "remotes":
+ {"origin": { "fetch": "ssh://git@git.sf.cloudera.com/%s.git" },
+ "apache": { "fetch": "ssh://git@git.sf.cloudera.com/apache-%s.git" }},
+
+ "projects":
+ {"hadoop": {},
+ "pig": {"refspec": "rapleaf",
+ "dir": "my-pig-dir",
+ "remotes": ["origin", "apache"]}
+ }
+ }
34 trace.py
@@ -0,0 +1,34 @@
+#
+# 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
+import os
+REPO_TRACE = 'REPO_TRACE'
+
+try:
+ _TRACE = os.environ[REPO_TRACE] == '1'
+except KeyError:
+ _TRACE = False
+
+def IsTrace():
+ return _TRACE
+
+def SetTrace():
+ global _TRACE
+ _TRACE = True
+
+def Trace(fmt, *args):
+ if IsTrace():
+ print >>sys.stderr, fmt % args
Please sign in to comment.
Something went wrong with that request. Please try again.