2014-08-09 07:43:34 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8
|
|
|
|
|
|
|
|
# Copyright (C) 2014 Stefan Hacker <dd0t@users.sourceforge.net>
|
|
|
|
# All rights reserved.
|
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
|
|
# modification, are permitted provided that the following conditions
|
|
|
|
# are met:
|
|
|
|
|
|
|
|
# - Redistributions of source code must retain the above copyright notice,
|
|
|
|
# this list of conditions and the following disclaimer.
|
|
|
|
# - Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
|
|
# and/or other materials provided with the distribution.
|
|
|
|
# - Neither the name of the Mumble Developers nor the names of its
|
|
|
|
# contributors may be used to endorse or promote products derived from this
|
|
|
|
# software without specific prior written permission.
|
|
|
|
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
# `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
|
|
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
|
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
|
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
|
|
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
"""
|
|
|
|
Meant to be run regularily as part of a root cronjob. Pulls updated translations from
|
|
|
|
transifex and if anything relevant changed creates a pull request with the translation
|
|
|
|
update to the mumble master repository.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import ConfigParser
|
|
|
|
from argparse import ArgumentParser
|
|
|
|
from logging import basicConfig, getLogger, DEBUG, INFO, WARNING, ERROR, debug, error, info, warning, exception
|
|
|
|
from github import Github
|
2014-08-10 21:46:53 +00:00
|
|
|
from plumbum.cmd import git, tx
|
|
|
|
from plumbum import ProcessExecutionError, local
|
2014-08-09 07:43:34 +00:00
|
|
|
import sys
|
|
|
|
import os
|
2014-08-10 21:46:53 +00:00
|
|
|
import re
|
2014-08-09 07:43:34 +00:00
|
|
|
|
|
|
|
def getExistingPullRequest(g, user, repo):
|
2014-08-10 21:46:53 +00:00
|
|
|
s = g.search_issues("type:pr is:open repo:%(repo)s author:%(user)s" % {'user': user,
|
2014-08-09 07:43:34 +00:00
|
|
|
'repo': repo})
|
|
|
|
pullrequests = s.get_page(0)
|
|
|
|
total = len(pullrequests)
|
|
|
|
if total == 0:
|
|
|
|
# We have to create a PR
|
|
|
|
debug("No open pull request found for %s in %s", user, repo)
|
|
|
|
return None
|
|
|
|
elif total == 1:
|
|
|
|
# We can reuse the existing PR
|
|
|
|
pr = pullrequests[0]
|
|
|
|
debug("Reusing existing PR %d from %s", pr.number, pr.created_at)
|
|
|
|
return pr
|
|
|
|
else:
|
|
|
|
# More then one PR pending. That's not right. Abort
|
|
|
|
raise Exception("Have %d PRs pending. This is unexpected." % total)
|
|
|
|
|
|
|
|
def createNewPullRequest(g,
|
|
|
|
target_owner, target_repo, target_branch,
|
|
|
|
base_owner, base_branch,
|
|
|
|
request_title, request_body):
|
|
|
|
|
|
|
|
u = g.get_user(target_owner)
|
|
|
|
r = u.get_repo(target_repo)
|
|
|
|
pr = r.create_pull(title = request_title,
|
|
|
|
body = request_body,
|
|
|
|
head = base_owner + ":" + base_branch,
|
|
|
|
base = target_branch)
|
|
|
|
return pr
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
parent_parser = ArgumentParser(
|
|
|
|
description = 'Create pull requests to mumble from transifex translation updates',
|
|
|
|
epilog = __doc__)
|
|
|
|
|
|
|
|
parent_parser.add_argument('-c', '--config', help = 'Configuration file (default: %(default)s)', default = '/etc/prfromtransifex.ini')
|
|
|
|
parent_parser.add_argument('--setup', help = "If set sets up needed git clone and then exits", action='store_true')
|
|
|
|
parent_parser.add_argument('-v', '--verbose', help = 'Verbose logging', action='store_true')
|
|
|
|
|
|
|
|
args = parent_parser.parse_args()
|
|
|
|
basicConfig(level = (DEBUG if args.verbose else INFO),
|
|
|
|
format='%(asctime)s %(levelname)s %(message)s')
|
|
|
|
|
|
|
|
debug("Loading configuration from: %s", args.config)
|
|
|
|
|
|
|
|
cfg = ConfigParser.RawConfigParser()
|
|
|
|
cfg.read(args.config)
|
|
|
|
|
|
|
|
user = cfg.get('github', 'user')
|
|
|
|
password = cfg.get('github', 'password')
|
2014-08-10 21:46:53 +00:00
|
|
|
email = cfg.get('github', 'email')
|
2014-08-09 07:43:34 +00:00
|
|
|
|
|
|
|
mode = cfg.get('transifex', 'mode')
|
|
|
|
minpercent = cfg.get('transifex', 'minpercent')
|
|
|
|
|
|
|
|
wr_owner = cfg.get('workingrepo', 'owner')
|
|
|
|
wr_repo = cfg.get('workingrepo', 'repo')
|
|
|
|
wr_branch = cfg.get('workingrepo', 'branch')
|
|
|
|
wr_url = cfg.get('workingrepo', 'url')
|
|
|
|
wr_path = cfg.get('workingrepo', 'path')
|
|
|
|
|
|
|
|
tr_owner = cfg.get('targetrepo', 'owner')
|
|
|
|
tr_repo = cfg.get('targetrepo', 'repo')
|
|
|
|
tr_branch = cfg.get('targetrepo', 'branch')
|
|
|
|
tr_url = cfg.get('targetrepo', 'url')
|
|
|
|
|
|
|
|
pr_title = cfg.get('pullrequest', 'title')
|
|
|
|
pr_body = cfg.get('pullrequest', 'body')
|
|
|
|
pr_commit = cfg.get('pullrequest', 'commit')
|
|
|
|
|
2014-08-16 04:00:11 +00:00
|
|
|
prifile = cfg.get('misc', 'prifile')
|
|
|
|
pritemplate = cfg.get('misc', 'pritemplate')
|
|
|
|
qrcfile = cfg.get('misc', 'qrcfile')
|
|
|
|
qrctemplate = cfg.get('misc', 'qrctemplate')
|
2014-08-09 07:43:34 +00:00
|
|
|
additionaltsfiles = cfg.get('misc', 'additionaltsfiles')
|
|
|
|
|
2014-08-10 21:46:53 +00:00
|
|
|
if args.setup or not os.path.exists(wr_path):
|
2014-08-09 07:43:34 +00:00
|
|
|
info("Setting up git repo")
|
|
|
|
debug(git["clone", wr_url, wr_path]())
|
2014-08-10 21:46:53 +00:00
|
|
|
with local.cwd(wr_path):
|
|
|
|
debug(git["config", "user.name", user]())
|
|
|
|
debug(git["config", "user.email", email]())
|
|
|
|
debug(git["remote", "add", "target", tr_url]())
|
|
|
|
|
2014-08-09 07:43:34 +00:00
|
|
|
info("Done")
|
2014-08-10 21:46:53 +00:00
|
|
|
|
|
|
|
if args.setup:
|
|
|
|
sys.exit(0)
|
2014-08-09 07:43:34 +00:00
|
|
|
|
|
|
|
info("Checking for pending PR")
|
|
|
|
g = Github(user, password)
|
2014-08-10 21:46:53 +00:00
|
|
|
pr = getExistingPullRequest(g,
|
|
|
|
user = user,
|
|
|
|
repo = tr_owner + "/" + tr_repo)
|
2014-08-09 07:43:34 +00:00
|
|
|
if pr:
|
|
|
|
info("Already have pending PR %d", pr.number)
|
|
|
|
# As long as we have a pending PR we want to make sure we
|
|
|
|
# keep working on that basis so potential review inside of
|
|
|
|
# of the PR isn't disturbed. Changes should be added on as
|
|
|
|
# additional commits on top of the existing PR.
|
|
|
|
remote = "origin"
|
|
|
|
branch = wr_branch
|
|
|
|
else:
|
|
|
|
info("No pending PR, will be creating a new one")
|
|
|
|
# When we have no pending requests we want to base our branch
|
|
|
|
# on the most recent mumble version to make fast-forward application
|
|
|
|
# of our patches as easy as possible.
|
|
|
|
remote = "target"
|
|
|
|
branch = tr_branch
|
|
|
|
|
2014-08-10 21:46:53 +00:00
|
|
|
with local.cwd(wr_path):
|
|
|
|
info("Updating remote '%s'", remote)
|
|
|
|
debug(git["fetch", remote]())
|
|
|
|
info("Resetting to branch '%s'", branch)
|
|
|
|
debug(git["reset", remote + "/" + branch, "--hard"]())
|
|
|
|
info("Cleaning repository")
|
|
|
|
debug(git["clean", "-f", "-x", "-d"]())
|
2014-08-09 07:43:34 +00:00
|
|
|
|
2014-08-16 04:00:11 +00:00
|
|
|
info("Pulling translations")
|
2014-08-10 21:46:53 +00:00
|
|
|
txout = tx["pull", "-f", "-a", "--mode=" + mode, "--minimum-perc=" + minpercent]()
|
|
|
|
debug(txout)
|
2014-08-09 07:43:34 +00:00
|
|
|
|
2014-08-16 04:00:11 +00:00
|
|
|
# Add all .ts files tx pull got to repo
|
2014-08-10 21:46:53 +00:00
|
|
|
paths, files = zip(*re.findall(r"^\s->\s[\w_]+:\s([\w/\_]+/([\w_]+\.ts))$", txout, flags=re.MULTILINE))
|
|
|
|
debug(git["add"](*paths))
|
|
|
|
|
2014-08-16 04:00:11 +00:00
|
|
|
# Add additional ts files not in control of transifex (e.g. English source translation)
|
|
|
|
files = list(files)
|
|
|
|
files.extend(additionaltsfiles.split(" "))
|
|
|
|
files.sort()
|
|
|
|
|
|
|
|
# Write pri file listing ts files for build
|
|
|
|
prifilepath = os.path.join(wr_path, prifile)
|
|
|
|
info("Updating translations listing file '%s'", prifilepath)
|
|
|
|
tsfiles = (" ".join(files))
|
|
|
|
with open(prifilepath, "w") as f:
|
|
|
|
f.write(pritemplate % {'files': tsfiles})
|
|
|
|
debug(git["add"](prifilepath))
|
|
|
|
|
|
|
|
# Write qrc file listing qm files built from ts files for build
|
|
|
|
qrcfilepath = os.path.join(wr_path, qrcfile)
|
|
|
|
info("Updating translations listing file '%s'", qrcfilepath)
|
|
|
|
tstoqm = lambda f: " <file>%s</file>" % re.sub(r"(^.*)\.ts$", r"\1.qm", f)
|
|
|
|
qmfiles = os.linesep.join([tstoqm(f) for f in files])
|
|
|
|
with open(qrcfilepath, "w") as f:
|
|
|
|
f.write(qrctemplate % {'files': qmfiles})
|
|
|
|
debug(git["add"](qrcfilepath))
|
|
|
|
|
|
|
|
# Check if the repo changed
|
2014-08-10 21:46:53 +00:00
|
|
|
debug("Checking for modifications")
|
|
|
|
changed, changedfiles, _ = git["diff", "--cached", "--name-only", "--exit-code"].run(retcode=(0,1))
|
|
|
|
if not changed:
|
|
|
|
info("No changes to translations, done")
|
|
|
|
sys.exit(0)
|
2014-08-16 04:00:11 +00:00
|
|
|
|
2014-08-10 21:46:53 +00:00
|
|
|
debug("Changed files: %s", " ".join(os.linesep.split(changedfiles)))
|
|
|
|
|
|
|
|
info("Things changed & force pushing")
|
|
|
|
debug(git["commit", "-m", pr_commit % {'mode': mode,
|
|
|
|
'minpercent': minpercent,
|
|
|
|
'langcount': len(files)}]())
|
|
|
|
|
|
|
|
debug(git["push", "-f", "origin", wr_branch]())
|
2014-08-09 07:43:34 +00:00
|
|
|
|
|
|
|
if not pr:
|
|
|
|
info("No existing PR, creating new one")
|
|
|
|
pr = createNewPullRequest(g,
|
|
|
|
target_owner = tr_owner,
|
|
|
|
target_repo = tr_repo,
|
|
|
|
target_branch = tr_branch,
|
|
|
|
base_owner = wr_owner,
|
|
|
|
base_branch = wr_branch,
|
|
|
|
request_title = pr_title,
|
|
|
|
request_body = pr_body)
|
|
|
|
|
|
|
|
info("Created PR %d", pr.number)
|
|
|
|
|
|
|
|
|