--- Revision None +++ Revision 646465363833 @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to you 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 optparse as op +import os +import re +import subprocess as sp +import sys +import tempfile +import textwrap +import urlparse + + +__usage__ = "%prog [OPTIONS] SVN_PATH GIT_DIR" + +SVN_BASE = "https://svn.apache.org/repos/asf/" +REF_RE = re.compile("refs/remotes/([^@]+)") + + +def options(): + return [ + op.make_option('-a', '--authors', metavar="FILE", dest="authors", + default="/usr/local/etc/asf-authors", + help="Path to the ASF authors file."), + op.make_option('-d', '--description', metavar="DESC", dest='desc', + help="A short project description. ie, 'Apache Jackrabbit'") + ] + + +def main(): + parser = op.OptionParser(usage=__usage__, option_list=options()) + opts, args = parser.parse_args() + + if len(args) == 0: + parser.error("Missing required SVN_URL and GIT_DIR arguments.") + if len(args) == 1: + parser.error("Missing required GIT_DIR argument.") + if len(args) > 2: + parser.error("Unknown arguments: %s" % ", ".join(args[2:])) + + svn_url = urlparse.urljoin(SVN_BASE, args[0].lstrip().lstrip("/")) + git_dir = args[1] + os.putenv("GIT_DIR", git_dir) + + if opts.authors is None: + opts.authors = "/usr/local/etc/asf-authors" + + if os.path.exists(git_dir): + error("Git directory exists: %s" % git_dir) + if not os.path.exists(opts.authors): + error("Missing authors file: %s" % opts.authors) + + init_git_dir(git_dir, svn_url, opts.desc) + clone_svn(git_dir, opts.authors) + cleanup_clone() + configure_asfgit(git_dir) + + +def init_git_dir(git_dir, svn_url, desc): + log("Creating mirror in: %s" % git_dir) + + git("svn", "init", "-s", svn_url) + git("config", "gitweb.owner", "The Apache Software Foundation") + save(os.path.join(git_dir, "HEAD"), "ref: refs/heads/trunk") + git("update-server-info") + + if desc is None: + desc = run_editor(initial="Project description, ie 'Apache Jackrabbit'") + if desc is None: + error("No repository description provided.") + save(os.path.join(git_dir, "description"), desc) + + +def clone_svn(git_dir, authors): + log("Initializing Git repository.") + git("svn", "fetch", "--authors-file", authors, "--log-window-size=10000") + + log("Updating branch refs.") + refs = git("for-each-ref", "refs/remotes", capture=True) + for ref in refs.splitlines(): + match = REF_RE.match(ref.split()[-1]) + if not match: + continue + ref = match.group(1) + if ref.startswith("tags/"): + continue + git("update-ref", "refs/heads/%s" % ref, "refs/remotes/%s" % ref) + + log("Creating Git tags from SVN pseudo-tags") + refs = git("for-each-ref", "refs/remotes/tags", capture=True) + for ref in refs.splitlines(): + ref = ref.split()[-1] + if ref.find("@") >= 0: + continue + tag = ref.split("/", 3)[-1] + + for opt in ("name", "email", "date"): + fmt = "--format=%%(committer%s)" % opt + val = git("for-each-ref", fmt, ref, capture=True).strip() + os.putenv("GIT_COMMITTER_%s" % opt.upper(), val) + git("tag", "-f", "-m", tag, tag, ref) + + +def cleanup_clone(): + log("Cleaning up new Git clone.") + git("update-server-info") + git("gc", "--aggressive") + + +def configure_asfgit(git_dir): + asfgit = os.getenv("ASFGIT_ADMIN") or "/usr/local/etc/asfgit-admin" + if not os.path.exists(asfgit): + log("WARNING: asfgit-admin directory not found.") + log("WARNING: Skipping hosting configuration.") + return + + log("Installing hook symlinks.") + for name in ("pre-receive", "post-receive"): + src = os.path.abspath(os.path.join(asfgit, "hooks", name)) + dst = os.path.join(git_dir, "hooks", name) + if not os.path.exists(src): + error("Missing pre-receive hook: %s" % name) + if os.path.exists(dst): + os.unlink(dst) + os.symlink(src, dst) + + log("Initializaing hosting configuration") + + cfgfile = os.path.join(asfgit, "conf", "gitconfig") + dstfile = os.path.join(git_dir, "config") + + # Make sure it hasn't already been initialized + try: + sp.check_output(["git", "config", "hooks.asfgit.debug"]) + added = True + except sp.CalledProcessError: + added = False + + if not added: + # Get base config + if not os.path.exists(cfgfile): + error("Missing default git configuration: %s" % cfgfile) + with open(cfgfile) as handle: + config = handle.read() + git_repo = os.path.basename(git_dir) + config = config % {"git_repo": git_repo} + + # Append the config data to the git config + # and let the user review it. + if not os.path.exists(dstfile): + error("Missing destinationg git config: %s" % dstfile) + with open(dstfile, "a") as handle: + handle.write(config) + + # Boot the user's editor to review the conifg. + run_editor(filename=dstfile) + + # Final steps + git_repo = os.path.basename(git_dir) + log(textwrap.dedent("""\ + + + To finish the hosting configuration you need to copy %(git_repo)s to + the repository hosting directory and run the following: + + $ sudo chown -R nobody:daemon $(REPOS)/%(git_repo)s + $ sudo chmod g+x $(REPOS)/%(git_repo)s + + At the time of this writing, $(REPOS) should be: + + /usr/local/www/git-wip-us.apache.org/repos/asf + + """ % {"git_repo": git_repo})) + + + +def run_editor(filename=None, initial=""): + editor = os.getenv("EDITOR", "nano") + if filename is None: + with tempfile.NamedTemporaryFile(delete=False) as tf: + fname = tf.name + tf.write(initial) + else: + fname = filename + try: + if sp.call([editor, fname]) != 0: + return None + with open(fname) as handle: + return handle.read() + finally: + if filename is None: + os.remove(fname) + + +def git(cmd, *args, **kwargs): + cmd = ["git", cmd] + list(args) + if kwargs.pop("capture", False): + return run(cmd, stderr=sp.STDOUT) + sp.check_call(cmd) + + +def run(cmd, *args, **kwargs): + if isinstance(cmd, list): + return sp.check_output(cmd, **kwargs) + else: + return sp.check_output([cmd] + list(args), **kwargs) + + +def save(fname, contents): + with open(fname, "w") as handle: + handle.write(contents) + + +def log(mesg): + sys.stderr.write("%s\n" % mesg) + + +def error(mesg, exit_code=1): + sys.stderr.write("ERROR: %s\n" % mesg) + sys.exit(exit_code) + + +if __name__ == '__main__': + main()