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