123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- # vim: set fileencoding=utf-8 :
- #
- # (C) 2010, 2012, 2015 Guido Günther <agx@sigxcpu.org>
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, please see
- # <http://www.gnu.org/licenses/>
- #
- # Based on the aa-create-git-repo and dom-new-git-repo shell scripts
- """Create a remote Git repository based on the current one"""
- from __future__ import print_function
- from six.moves import configparser
- import sys
- import os, os.path
- from six.moves import urllib
- import subprocess
- import tty, termios
- import re
- import six
- from gbp.deb.changelog import ChangeLog, NoChangeLogError
- from gbp.command_wrappers import (CommandExecFailed, GitCommand)
- from gbp.config import (GbpOptionParserDebian, GbpOptionGroup)
- from gbp.errors import GbpError
- from gbp.git import GitRepositoryError
- from gbp.deb.git import DebianGitRepository
- import gbp.log
- def print_config(remote, branches):
- """
- Print out the git config to push to the newly created repo.
- >>> print_config({'name': 'name', 'url': 'url'}, ['foo', 'bar'])
- [remote "name"]
- url = url
- fetch = +refs/heads/*:refs/remotes/name/*
- push = foo
- push = bar
- [branch "foo"]
- remote = name
- merge = refs/heads/foo
- [branch "bar"]
- remote = name
- merge = refs/heads/bar
- """
- print("""[remote "%(name)s"]
- url = %(url)s
- fetch = +refs/heads/*:refs/remotes/%(name)s/*""" % remote)
- for branch in branches:
- print(" push = %s" % branch)
- for branch in branches:
- print("""[branch "%s"]
- remote = %s
- merge = refs/heads/%s""" % (branch, remote['name'], branch))
- def sort_dict(d):
- """Return a sorted list of (key, value) tuples"""
- s = []
- for key in sorted(six.iterkeys(d)):
- s.append((key, d[key]))
- return s
- def parse_url(remote_url, name, pkg, template_dir=None):
- """
- Sanity check our remote URL
- >>> sort_dict(parse_url("ssh://host/path/%(pkg)s", "origin", "package"))
- [('base', ''), ('dir', '/path/package'), ('host', 'host'), ('name', 'origin'), ('pkg', 'package'), ('port', None), ('scheme', 'ssh'), ('template-dir', None), ('url', 'ssh://host/path/package')]
- >>> sort_dict(parse_url("ssh://host:22/path/repo.git", "origin", "package"))
- [('base', ''), ('dir', '/path/repo.git'), ('host', 'host'), ('name', 'origin'), ('pkg', 'package'), ('port', '22'), ('scheme', 'ssh'), ('template-dir', None), ('url', 'ssh://host:22/path/repo.git')]
- >>> sort_dict(parse_url("ssh://host:22/~/path/%(pkg)s.git", "origin", "package"))
- [('base', '~/'), ('dir', 'path/package.git'), ('host', 'host'), ('name', 'origin'), ('pkg', 'package'), ('port', '22'), ('scheme', 'ssh'), ('template-dir', None), ('url', 'ssh://host:22/~/path/package.git')]
- >>> sort_dict(parse_url("ssh://host:22/~user/path/%(pkg)s.git", "origin", "package", "/doesnot/exist"))
- [('base', '~user/'), ('dir', 'path/package.git'), ('host', 'host'), ('name', 'origin'), ('pkg', 'package'), ('port', '22'), ('scheme', 'ssh'), ('template-dir', '/doesnot/exist'), ('url', 'ssh://host:22/~user/path/package.git')]
- >>> parse_url("git://host/repo.git", "origin", "package")
- Traceback (most recent call last):
- ...
- GbpError: URL must use ssh protocol.
- >>> parse_url("ssh://host/path/repo", "origin", "package")
- Traceback (most recent call last):
- ...
- GbpError: URL needs to contain either a repository name or '%(pkg)s'
- >>> parse_url("ssh://host:asdf/path/%(pkg)s.git", "origin", "package")
- Traceback (most recent call last):
- ...
- GbpError: URL contains invalid port.
- >>> parse_url("ssh://host/~us er/path/%(pkg)s.git", "origin", "package")
- Traceback (most recent call last):
- ...
- GbpError: URL contains invalid ~username expansion.
- """
- frags = urllib.parse.urlparse(remote_url)
- if frags.scheme in ['ssh', 'git+ssh', '']:
- scheme = frags.scheme
- else:
- raise GbpError("URL must use ssh protocol.")
- if not '%(pkg)s' in remote_url and not remote_url.endswith(".git"):
- raise GbpError("URL needs to contain either a repository name or '%(pkg)s'")
- if ":" in frags.netloc:
- (host, port) = frags.netloc.split(":", 1)
- if not re.match(r"^[0-9]+$", port):
- raise GbpError("URL contains invalid port.")
- else:
- host = frags.netloc
- port = None
- if frags.path.startswith("/~"):
- m = re.match(r"/(~[a-zA-Z0-9_-]*/)(.*)", frags.path)
- if not m:
- raise GbpError("URL contains invalid ~username expansion.")
- base = m.group(1)
- path = m.group(2)
- else:
- base = ""
- path = frags.path
- remote = { 'pkg' : pkg,
- 'url' : remote_url % { 'pkg': pkg },
- 'dir' : path % { 'pkg': pkg },
- 'base': base,
- 'host': host,
- 'port': port,
- 'name': name,
- 'scheme': scheme,
- 'template-dir': template_dir}
- return remote
- def build_remote_script(remote, branch):
- """
- Create the script that will be run on the remote side
- >>> build_remote_script({'base': 'base', 'dir': 'dir', 'pkg': 'pkg', 'template-dir': None}, 'branch')
- '\\nset -e\\numask 002\\nif [ -d base"dir" ]; then\\n echo "Repository at "basedir" already exists - giving up."\\n exit 1\\nfi\\nmkdir -p base"dir"\\ncd base"dir"\\ngit init --bare --shared\\necho "pkg packaging" > description\\necho "ref: refs/heads/branch" > HEAD\\n'
- >>> build_remote_script({'base': 'base', 'dir': 'dir', 'pkg': 'pkg', 'template-dir': '/doesnot/exist'}, 'branch')
- '\\nset -e\\numask 002\\nif [ -d base"dir" ]; then\\n echo "Repository at "basedir" already exists - giving up."\\n exit 1\\nfi\\nmkdir -p base"dir"\\ncd base"dir"\\ngit init --bare --shared --template=/doesnot/exist\\necho "pkg packaging" > description\\necho "ref: refs/heads/branch" > HEAD\\n'
- """
- args = remote
- args['branch'] = branch
- args['git-init-args'] = '--bare --shared'
- if args['template-dir']:
- args['git-init-args'] += (' --template=%s'
- % args['template-dir'])
- remote_script_pattern = ['',
- 'set -e',
- 'umask 002',
- 'if [ -d %(base)s"%(dir)s" ]; then',
- ' echo "Repository at \"%(base)s%(dir)s\" already exists - giving up."',
- ' exit 1',
- 'fi',
- 'mkdir -p %(base)s"%(dir)s"',
- 'cd %(base)s"%(dir)s"',
- 'git init %(git-init-args)s',
- 'echo "%(pkg)s packaging" > description',
- 'echo "ref: refs/heads/%(branch)s" > HEAD',
- '' ]
- remote_script = '\n'.join(remote_script_pattern) % args
- return remote_script
- def build_cmd(remote):
- """
- Build the command we pass the script to
- >>> build_cmd({'scheme': ''})
- ['sh']
- >>> build_cmd({'scheme': 'ssh', 'host': 'host', 'port': 80})
- ['ssh', '-p', 80, 'host', 'sh']
- """
- cmd = []
- if remote["scheme"]:
- cmd.append('ssh')
- if remote["port"]:
- cmd.extend(['-p', remote['port']])
- cmd.append(remote["host"])
- cmd.append('sh')
- return cmd
- def read_yn():
- fd = sys.stdin.fileno()
- try:
- old_settings = termios.tcgetattr(fd)
- except termios.error:
- old_settings = None
- try:
- if old_settings:
- tty.setraw(sys.stdin.fileno())
- ch = sys.stdin.read(1)
- finally:
- if old_settings:
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
- if ch in ( 'y', 'Y' ):
- return True
- else:
- return False
- def setup_branch_tracking(repo, remote, branches):
- repo.add_remote_repo(name=remote['name'], url=remote['url'], fetch=True)
- gitTrackRemote = GitCommand('branch', ['--set-upstream-to'])
- for branch in branches:
- gitTrackRemote(['%s/%s' % (remote['name'], branch), branch])
- def push_branches(remote, branches):
- gitPush = GitCommand('push')
- gitPush([remote['url']] + branches)
- gitPush([remote['url'], '--tags'])
- def build_parser(name, sections=[]):
- try:
- parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='',
- usage='%prog [options] - '
- 'create a remote repository',
- sections=sections)
- except configparser.ParsingError as err:
- gbp.log.err(err)
- return None
- branch_group = GbpOptionGroup(parser,
- "branch options",
- "branch layout and tracking options")
- branch_group.add_config_file_option(option_name="remote-url-pattern",
- dest="remote_url")
- parser.add_option_group(branch_group)
- branch_group.add_config_file_option(option_name="upstream-branch",
- dest="upstream_branch")
- branch_group.add_config_file_option(option_name="debian-branch",
- dest="debian_branch")
- branch_group.add_boolean_config_file_option(option_name="pristine-tar",
- dest="pristine_tar")
- branch_group.add_boolean_config_file_option(option_name="track",
- dest='track')
- parser.add_option("-v", "--verbose",
- action="store_true",
- dest="verbose",
- default=False,
- help="verbose command execution")
- parser.add_config_file_option(option_name="color",
- dest="color",
- type='tristate')
- parser.add_config_file_option(option_name="color-scheme",
- dest="color_scheme")
- parser.add_option("--remote-name",
- dest="name",
- default="origin",
- help="The name of the remote, default is 'origin'")
- parser.add_config_file_option(option_name="template-dir",
- dest="template_dir")
- parser.add_config_file_option(option_name="remote-config",
- dest="remote_config")
- return parser
- def parse_args(argv, sections=[]):
- """
- Parse the command line arguments and config files.
- @param argv: the command line arguments
- @type argv: C{list} of C{str}
- @param sections: additional sections to add to the config file parser
- besides the command name
- @type sections: C{list} of C{str}
- """
- # We simpley handle the template section as an additional config file
- # section to parse, this makes e.g. --help work as expected:
- for arg in argv:
- if arg.startswith('--remote-config='):
- sections = ['remote-config %s' % arg.split('=',1)[1]]
- break
- else:
- sections = []
- parser = build_parser(argv[0], sections)
- if not parser:
- return None, None
- return parser.parse_args(argv)
- def main(argv):
- retval = 0
- changelog = 'debian/changelog'
- cmd = []
- try:
- options, args = parse_args(argv)
- except Exception as e:
- print("%s" % e, file=sys.stderr)
- return 1
- gbp.log.setup(options.color, options.verbose, options.color_scheme)
- try:
- repo = DebianGitRepository(os.path.curdir)
- except GitRepositoryError:
- gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
- return 1
- try:
- branches = []
- for branch in [ options.debian_branch, options.upstream_branch ]:
- if repo.has_branch(branch):
- branches += [ branch ]
- if repo.has_pristine_tar_branch() and options.pristine_tar:
- branches += [ repo.pristine_tar_branch ]
- try:
- cp = ChangeLog(filename=changelog)
- pkg = cp['Source']
- except NoChangeLogError:
- pkg = None
- if not pkg:
- gbp.log.warn("Couldn't parse changelog, will use directory name.")
- pkg = os.path.basename(os.path.abspath(os.path.curdir))
- pkg = os.path.splitext(pkg)[0]
- remote = parse_url(options.remote_url,
- options.name,
- pkg,
- options.template_dir)
- if repo.has_remote_repo(options.name):
- raise GbpError("You already have a remote name '%s' defined for this repository." % options.name)
- gbp.log.info("Shall I create a repository for '%(pkg)s' at '%(url)s' now? (y/n)?" % remote)
- if not read_yn():
- raise GbpError("Aborted.")
- remote_script = build_remote_script(remote, branches[0])
- if options.verbose:
- print(remote_script)
- cmd = build_cmd(remote)
- if options.verbose:
- print(cmd)
- proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
- proc.communicate(remote_script)
- if proc.returncode:
- raise GbpError("Error creating remote repository")
- push_branches(remote, branches)
- if options.track:
- setup_branch_tracking(repo, remote, branches)
- else:
- gbp.log.info("You can now add:")
- print_config(remote, branches)
- gbp.log.info("to your .git/config to 'gbp-pull' and 'git push' in the future.")
- except CommandExecFailed:
- retval = 1
- except (GbpError, GitRepositoryError) as err:
- if str(err):
- gbp.log.err(err)
- retval = 1
- return retval
- if __name__ == '__main__':
- sys.exit(main(sys.argv))
- # vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
|