Initial import of prfromtransifex script.

Meant to be run regularily as part of a user cronjob. Pulls updated
translations from transifex and if anything relevant changed creates
a pull request with the translation update to the mumble master repository.

Completely untested at this stage. Very unlikely to even run.
This commit is contained in:
Stefan Hacker 2014-08-09 09:43:34 +02:00
commit c658a41775
3 changed files with 261 additions and 0 deletions

5
README.txt Normal file
View file

@ -0,0 +1,5 @@
Requires pygithub, plumbum
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.

58
prfromtransifex.ini Normal file
View file

@ -0,0 +1,58 @@
[github]
; Github username of bot
user = MumbleTransifexBot
; Github password of bot
password =
[transifex]
; Modes: default fetches all string, reviewed only reviewed ones
mode = default
; Minimum number of percent needed for a translation to be included
minpercent = 0
[workingrepo]
owner = MumbleTransifexBot
repo = mumble
branch = master
; Clone URL for working repo
url = git@github.com:MumbleTransifexBot/mumble.git
; Local path the repo can be found
path = /var/transifex/mumble
[targetrepo]
owner = mumble-voip
repo = mumble
branch = master
; Clone URL for pull request target repository
url = https://github.com/mumble-voip/mumble.git
[pullrequest]
; Template string for pull request title
title = Transifex translation update
; Template string for pull request body
body = New translation updates available from transifex
https://www.transifex.com/organization/mumble/dashboard/mumble
; Template string for commits in PR. Available variables
; %(mode)s See transifex.mode above
; %(minpercent)s See transifex.minpercent above
; %(langcount)d Number of languages matched with given settings
commit = Transifex translation update
Mode: %(mode)s
Minimum percent translated: %(minpercent)s
Matched %(langcount)d languages
[misc]
; File to store language bookkeeping data in
; Will be re-written and commited to the repository
; if changes occur.
file = src/mumble/translations.pri
; Template to generate file from, available variables $(files)s
; for a space separated list of all files.
template = # Do not change manually
# Autogenerated by mumble transifex bot
TRANSLATIONS = %(files)s
; Space separated translation files not retrieved from transifex
additionaltsfiles = mumble_en.ts

198
prfromtransifex.py Normal file
View file

@ -0,0 +1,198 @@
#!/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
from plumbum.cmd import git, tx, cd
from plumbum import ProcessExecutionError
import sys
import os
def getExistingPullRequest(g, user, repo):
s = g.search("type:pr is:open repo:%(repo)s author:%(user)s" % {'user': user,
'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')
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')
translationsfile = cfg.get('misc', 'file')
translationstemplate = cfg.get('misc', 'template')
additionaltsfiles = cfg.get('misc', 'additionaltsfiles')
if args.setup:
info("Setting up git repo")
debug(git["clone", wr_url, wr_path]())
debug(git["remote", "add", "target", tr_url]())
info("Done")
sys.exit(0)
info("Checking for pending PR")
g = Github(user, password)
pr = getExistingPullRequest(user = user,
repo = tr_repo)
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
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"]())
info("Pulling translations")
txout = tx["pull", "-f", "--mode=" + mode, "--minimum-perc" + minpercent]()
debug(txout)
tfilepath = os.path.join(wr_path, translationsfile)
info("Updating translations listing file '%s'", tfilepath)
paths, files = zip(*re.findall(r"^\s->\s[\w_]+:\s([\w/\_]+/([\w_]+\.ts))$", t, flags=re.MULTILINE))
debug(git["add"](*paths))
translations = additionaltsfiles + " ".join(files)"
with open(tfilepath, "w") as f:
f.write(translationstemplate % {'files': translations})
debug(git["add"](tfilepath))
debug("Checking for modifications")
changed, changedfiles, _ = git["diff", "--cached", "--name-only", "--exit-code"].run()
if not changed:
info("No changes to translations, done")
sys.exit(0)
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]())
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)