Mercurial > september
view september.py @ 0:ee98303e24b8
Initial commit.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Wed, 04 Mar 2015 23:33:07 -0800 |
parents | |
children | 6bbebb01f614 |
line wrap: on
line source
import os import re import sys import json import os.path import logging import argparse import subprocess import configparser from urllib.parse import urlparse logger = logging.getLogger(__name__) class IRepo(object): def clone(self, repo_url, repo_path): raise NotImplementedError() def pull(self, repo_path): raise NotImplementedError() def getTags(self, repo_path): raise NotImplementedError() def update(self, repo_path, rev_id): raise NotImplementedError() class GitRepo(object): def clone(self, repo_url, repo_path): subprocess.check_call(['git', 'clone', repo_url, repo_path]) def pull(self, repo_path): subprocess.check_call(['git', 'pull', 'origin', 'master']) def getTags(self, repo_path): output = subprocess.check_output(['git', 'show-ref', '--tags']) pat = re.compile(r'^(?P<id>[0-9a-f]+) (?P<tag>.+)$') for line in output.split('\n'): m = pat.match(line) if m: yield (m.group('tag'), m.group('id')) def update(self, repo_path, rev_id): rev_id = rev_id or 'master' subprocess.check_call(['git', 'checkout', rev_id]) class MercurialRepo(object): def clone(self, repo_url, repo_path): subprocess.check_call(['hg', 'clone', repo_url, repo_path]) def pull(self, repo_path): subprocess.check_call(['hg', 'pull'], stderr=subprocess.STDOUT) def getTags(self, repo_path): output = subprocess.check_output( 'hg log -r "tag()" --template "{tags} {node}\\n"', stderr=subprocess.STDOUT, universal_newlines=True, shell=True) pat = re.compile(r'^(?P<tag>.+) (?P<id>[0-9a-f]+)$') for line in output.split('\n'): m = pat.match(line) if m: yield (m.group('tag'), m.group('id')) def update(self, repo_path, rev_id): rev_id = rev_id or 'default' subprocess.check_call(['hg', 'update', rev_id], stderr=subprocess.STDOUT) repo_class = { 'git': GitRepo, 'hg': MercurialRepo} def guess_repo_type(repo): # Parse repo as an URL: scheme://netloc/path;parameters?query#fragment scheme, netloc, path, params, query, fragment = urlparse(repo) if scheme == 'ssh': if netloc.startswith('git@'): return 'git' if netloc.startswith('hg@'): return 'hg' elif scheme == 'https': if path.endswith('.git'): return 'git' elif scheme == '' and netloc == '' and os.path.isdir(path): if os.path.isdir(os.path.join(path, '.git')): return 'git' if os.path.isdir(os.path.join(path, '.hg')): return 'hg' return None def main(): # Setup the argument parser. parser = argparse.ArgumentParser( prog='september', description=("An utility that goes back in time and does " "something in the background.")) parser.add_argument( 'repo', help="The repository to observe and process") parser.add_argument( '-t', '--tmp-dir', help="The temporary directory in which to clone the repository.") parser.add_argument( '--scm', default='guess', choices=['guess', 'git', 'mercurial'], help="The type of source control system handling the repository.") parser.add_argument( '--config', help="The configuration file to use.") parser.add_argument( '--command', help="The command to run on each tag.") # Parse arguments, guess repo type. res = parser.parse_args() repo_type = res.scm if not repo_type or repo_type == 'guess': repo_type = guess_repo_type(res.repo) if not repo_type: logger.error("Can't guess the repository type. Please use the " "--scm option to specify it.") sys.exit(1) if repo_type not in repo_class: logger.error("Unknown repository type: %s" % repo_type) sys.exit(1) # Create the repo handler. repo = repo_class[repo_type]() # Clone or update/checkout the repository in the temp directory. clone_dir = os.path.join(res.tmp_dir, 'clone') if not os.path.exists(clone_dir): logger.info("Cloning '%s' into: %s" % (res.repo, clone_dir)) repo.clone(res.repo, clone_dir) else: os.chdir(clone_dir) logger.info("Pulling changes from '%s'." % res.repo) repo.update(res.repo, None) os.chdir(clone_dir) # Find the configuration file in the repository clone. config_file = res.config or os.path.join(clone_dir, '.september.yml') config = configparser.ConfigParser(interpolation=None) if os.path.exists(config_file): logger.info("Loading configuration file: %s" % config_file) config.read(config_file) if not config.has_section('september'): config.add_section('september') config_sec = config['september'] if res.command: config_sec['command'] = res.command if not config.has_option('september', 'command'): logger.error("There is no 'command' configuration setting under the " "'september' section, and no command was passed as an " "option.") sys.exit(1) # Find the cache file in the temp directory. cache_file = os.path.join(res.tmp_dir, 'september.json') if os.path.exists(cache_file): with open(cache_file, 'r') as fp: cache = json.load(fp) else: cache = {'tags': {}} # Update the cache: get any new/moved tags. first_tag = config_sec.get('first_tag', None) tag_pattern = config_sec.get('tag_pattern', None) tag_re = None if tag_pattern: tag_re = re.compile(tag_pattern) previous_tags = cache['tags'] tags = repo.getTags(clone_dir) for t, i in tags: if not tag_re or tag_re.search(t): if t not in previous_tags: logger.info("Adding tag '%s'." % t) previous_tags[t] = {'id': i, 'processed': False} elif previous_tags[t]['id'] != i: logger.info("Moving tag '%s'." % t) previous_tags[t] = {'id': i, 'processed': False} if first_tag and first_tag == t: break logger.info("Updating cache file '%s'." % cache_file) with open(cache_file, 'w') as fp: json.dump(cache, fp) # Process tags! use_shell = config_sec.get('use_shell') in ['1', 'yes', 'true'] for tn, ti in cache['tags'].items(): if ti['processed']: logger.info("Skipping '%s'." % tn) continue logger.info("Updating repo to '%s'." % tn) repo.update(clone_dir, ti['id']) command = config_sec['command'] % { 'rev_id': ti['id'], 'root_dir': clone_dir, 'tag': tn} logger.info("Running: %s" % command) subprocess.check_call(command, shell=use_shell) ti['processed'] = True with open(cache_file, 'w') as fp: json.dump(cache, fp) if __name__ == '__main__': logging.basicConfig(level=logging.INFO, format='%(message)s') main()