#!/usr/bin/python # lame_vcs_abstraction.py: # # Licensed under the new-BSD license (http://www.opensource.org/licenses/bsd-license.php) # Copyright (C) 2010 Red Hat, Inc. # Written by Colin Walters # Feel free to replace the bits here with something better... import os import sys import re import urlparse import getopt import subprocess import shutil import hashlib import logging from . import async_subprocess class Vcs(object): def __init__(self, parsedurl, directory=None): self._parsed_url = parsedurl self._url_string = urlparse.urlunsplit(parsedurl) # Deliberately drop params/query self._nonfragment_url_string = urlparse.urlunparse((parsedurl.scheme, parsedurl.netloc, parsedurl.path, '', '', '')) self._branch = self._parsed_url.fragment self._dir = directory def get_url(self): """Retrieve the SplitResult (broken up components tuple) URL for this repository.""" return self._parsed_url def get_base_url_string(self): """Retrieve the non-branched URL for this repository.""" return self._nonfragment_url_string def get_url_string(self): """Retrieve the stringified form of the repository URL.""" return '%s:%s' % (self.vcstype, self._url_string) def checkout_async(self, destdir): """Retrieve a new copy of the source tree, saving as destdir""" raise Exception("not implemented") def set_directory(self, dirpath): """Set the checkout directory.""" self._dir = dirpath def get_directory(self): return self._dir def update_async(self): """Update directory from the latest upstream""" raise Exception("not implemented") def export_archive(self, commit_id, prefix, target_filename): """Export a tarball with minimal (or no) version control data.""" raise Exception("not implemented") def get_scheme(self): return self._parsed_url.scheme def get_id(self): raise Exception("not implemented") def resolve_id(self): raise Exception("not implemented") def get_abbreviated_id(self): raise Exception("not implemented") def _vcs_exec_sync_log_error(self, args): logging.info("Synchronously executing: %r in cwd %r" % (args, self._dir)) proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True, cwd=self._dir) output, err = proc.communicate() ecode = proc.poll() if ecode: raise subprocess.CalledProcessError(ecode, args, output=output) return output def _vcs_exec_async(self, args, logfile_path, on_exited): logging.info("Asynchronously executing: %r in cwd %r" % (args, self._dir)) return async_subprocess.spawn_async_output_to_file(args, logfile_path, on_exited, cwd=self._dir) @classmethod def new_from_spec(cls, spec): """See http://maven.apache.org/scm/scm-url-format.html ; we use this format, but without the "scm:" prefix.""" # Hack for backwards compatibility if spec.startswith('git://'): (vcstype, url) = ('git', spec) else: (vcstype, url) = spec.split(':', 1) orig = urlparse.urlsplit(url) # We want to support fragments, even if the URL type isn't recognized. So change the # scheme to http temporarily. temp = urlparse.urlunsplit(('http', orig.netloc, orig.path, orig.query, orig.fragment)) new = urlparse.urlsplit(temp) combined = urlparse.SplitResult(orig.scheme, new.netloc, new.path, new.query, new.fragment) if vcstype == 'git': return GitVcs(combined) class GitVcs(Vcs): vcstype = "git" def checkout_async(self, destdir, logfile, on_exited): assert self._dir is None args = ['git', 'clone'] if self._branch: args.extend(['-b', self._branch]) args.extend([self._nonfragment_url_string, destdir]) def _wrap_on_exited(process, condition): if condition == 0: self._dir = destdir on_exited(process, condition) return self._vcs_exec_async(args, logfile, _wrap_on_exited) def update_async(self, logfile, on_exited): assert self._dir is not None return self._vcs_exec_async(['git', 'pull', '-r'], logfile, on_exited) def export_directory(self, commit_id, target_directory, log_output_stream): assert self._dir is not None basename = os.path.basename(target_directory) if not basename.endswith('/'): basename += '/' basedir = os.path.dirname(target_directory) args = ['git', 'archive', '--format=tar', '--prefix=%s' % (basename,), commit_id] logging.info("Synchronously executing: %r" % (args, )) gitproc = subprocess.Popen(args, cwd=self._dir, stdout=subprocess.PIPE, stderr=log_output_stream) args = ['tar', 'xf', '-'] logging.info("Synchronously executing: %r" % (args, )) tarproc = subprocess.Popen(args, cwd=basedir, stdout=None, stdin=gitproc.stdout, stderr=log_output_stream) tarproc.wait() def export_archive(self, commit_id, prefix, target_filename, log_output_stream): assert self._dir is not None if not prefix.endswith('/'): prefix += '/' args = ['git', 'archive', '--format=tar', '--prefix=%s' % (prefix,), commit_id] logging.info("Synchronously executing: %r" % (args, )) gitproc = subprocess.Popen(args, cwd=self._dir, stdout=subprocess.PIPE, stderr=log_output_stream) if target_filename.endswith('.bz2'): zipbin = 'bzip2' elif target_filename.endswith('.gz'): zipbin = 'gzip' else: raise ValueError("Unknown compression for filename %r" % (target_filename,)) args = [zipbin, '-c'] logging.info("Synchronously executing: %r" % (args, )) f = open(target_filename, 'w') zipproc = subprocess.Popen(args, cwd=self._dir, stdout=f, stdin=gitproc.stdout, stderr=log_output_stream) zipproc.wait() def get_commit_as_patch(self, commitid, destfile): assert self._dir is not None output = self._vcs_exec_sync_log_error(['git', 'format-patch', '--stdout', commitid + '^..' + commitid]) f = open(destfile, 'w') f.write(output) f.close() def get_id(self): assert self._dir is not None output = self._vcs_exec_sync_log_error(['git', 'show', '--format=%H']) return output.split('\n')[0] def resolve_id(self, commit_id): assert self._dir is not None output = self._vcs_exec_sync_log_error(['git', 'rev-parse', commit_id]) return output.split('\n')[0] def get_abbreviated_id(self): assert self._dir is not None full_id = self.get_id() return full_id[0:8] def get_commit_summary_as_filename(self, commitid): assert self._dir is not None output = self._vcs_exec_sync_log_error(['git', 'show', '--format=%f', commitid]) return output.split('\n')[0]