hgpoll.py

Sun, 30 Nov 2014 03:55:47 +0200

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Sun, 30 Nov 2014 03:55:47 +0200
changeset 107
14e1d18d2a2a
parent 101
5e32ab7ae823
child 108
8cf31b4d5fcb
permissions
-rw-r--r--

- add more decimals to the mathematical constants

import hgapi
import time
import re
import bt as Bt
import irc as Irc
import os
from datetime import datetime
from configfile import Config
g_needCommitsTxtRebuild = True

def make_commits_txt():
	global g_needCommitsTxtRebuild

	if g_needCommitsTxtRebuild == False:
		return

	print 'Building commits.txt...'
	# Update zandronum-everything
	repo = hgapi.Repo ('zandronum-everything')
	repo.hg_command ('pull', '../zandronum-sandbox')
	repo.hg_command ('pull', '../zandronum-sandbox-stable')
	data = repo.hg_command ('log', '--template', '{node} {date|hgdate}\n')
	f = open ('commits.txt', 'w')

	for line in data.split ('\n'):
		if line == '':
			continue

		words = line.split (' ')
		timestamp = int (words[1])
		f.write ('%s %s\n' % (words[0], datetime.utcfromtimestamp (timestamp).strftime
('%y%m%d-%H%M')))

	f.close()
	g_needCommitsTxtRebuild = False

' Check if a repository exists '
def check_repo_exists (repo_name, repo_owner):
	print 'Checking that %s exists...' % repo_name
	repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name)
	zanrepo = hgapi.Repo (repo_name)

	try:
		zanrepo.hg_command ('id', '.')
	except hgapi.hgapi.HgException:
		# If the repo does not exist, clone it. zandronum-everything can be spawned
		# off other repos though
		if repo_name == 'zandronum-everything':
			if not os.path.exists (repo_name):
				os.makedirs (repo_name)

			global g_needCommitsTxtRebuild
			g_needCommitsTxtRebuild = True
			print 'Init %s' % repo_name
			zanrepo.hg_command ('init')
			print 'Cloning zandronum-sandbox into %s' % repo_name
			zanrepo.hg_command ('pull', '../zandronum-sandbox')
			print 'Cloning zandronum-sandbox-stable into %s' % repo_name
			zanrepo.hg_command ('pull', '../zandronum-sandbox-stable')
			print 'Done'
			make_commits_txt()
			return

		try:
			print 'Cloning %s...' % repo_name
			zanrepo.hg_clone (repo_url, repo_name)
			print 'Cloning done.'
		except Exception as e:
			print 'Unable to clone %s from %s: %s' % (repo_name, repo_url, str (`e`))
			quit (1)


class HgProcessError (Exception):
	def __init__ (self, value):
		self.message = value
	def __str__ (self):
		return self.message

def announce_ticket_resolved (ticket_id, commit_node):
	ticket_id = int (ticket_id)

	try:
		repo_name = 'zandronum-stable'
		zanrepo = hgapi.Repo (repo_name)
		zanrepo.hg_command ('log', '-r', commit_node)
	except hgapi.hgapi.HgException:
		try:
			repo_name = 'zandronum'
			zanrepo = hgapi.Repo (repo_name)
			zanrepo.hg_command ('log', '-r', commit_node)
		except hgapi.hgapi.HgException:
			raise HgProcessError ('unknown commit %s' % commit_node)

	repo_url = 'https://bitbucket.org/Torr_Samaho/%s' % repo_name

	# Acquire additional data
	moredata = get_commit_data (zanrepo, commit_node,
		'{author|nonempty}\n{date(date, \'%A %d %B %Y %T\')}').split('\n')

	if len (moredata) != 2:
		raise HgProcessError ('malformed hg data while processing %s' % commit_node)

	commit_author = moredata[0]
	commit_date = moredata[1]
	commit_email = ""
	commit_message = zanrepo.hg_command ('log', '-r', commit_node, '--template', '{desc}')

	try:
		ticket_data = Bt.get_issue (ticket_id)
	except Exception as e:
		raise HgProcessError ("error while processing %s: %s" % (commit_node, e))

	# Remove the email address from the author if possible
	rex = re.compile (r'^(.+) <([^>]+)>$.*')
	match = rex.match (commit_author)
	if match:
		commit_author = match.group (1)
		commit_email = match.group (2)

	commit_diffstat = zanrepo.hg_command ('diff', '--change', commit_node, '--stat')

	if len(commit_diffstat) > 0:
		# commit_diffstat = 'Changes in files:\n[code]\n' + commit_diffstat + '\n[/code]'
		commit_diffstat = 'Changes in files:\n' + bbcodify(commit_diffstat)
	else:
		commit_diffstat = 'No changes in files.'

	# Compare the email addresses against known developer usernames
	commit_trackeruser = Config.find_developer_by_email (commit_email)

	if commit_trackeruser != '':
		commit_author += ' [%s]' % commit_trackeruser

	message = 'Issue addressed by commit %s: [b][url=%s/commits/%s]%s[/url][/b]' \
		% (commit_node, repo_url, commit_node, commit_message)
	message += "\nCommitted by %s on %s\n\n%s" \
			% (commit_author, commit_date, commit_diffstat)

	need_update = False

	# If not already set, set handler
	if not 'handler' in ticket_data:
		ticket_data['handler'] = {'name': commit_trackeruser}
		need_update = True

	# Find out the status level of the ticket
	needs_testing_level = 70

	if ticket_data['status']['id'] < needs_testing_level:
		ticket_data.status['id'] = needs_testing_level
		need_update = True

	# Set target version if not set
	if not 'target_version' in ticket_data:
		ticket_data['target_version'] = '1.4' if repo_name == 'zandronum-stable' else '2.0'
		need_update = True
	elif (ticket_data['target_version'] == '2.0' or ticket_data['target_version'] == '2.0-beta') \
	and repo_name == 'zandronum-stable':
		# Target version was 2.0 but this was just committed to zandronum-stable, adjust
		ticket_data['target_version'] = '1.4'
		need_update = True
	elif ticket_data['target_version'] == '2.0-beta':
		# Fix target version from 2.0-beta to 2.0
		ticket_data['target_version'] = '2.0'
		need_update = True

	# Announce on IRC
	for irc_client in Irc.all_clients:
		for channel in irc_client.channels:
			if channel.get_value ('btannounce', default=True):
				irc_client.privmsg (channel.get_value ('name'),
					"%s: commit %s FIXES issue %d" % (repo_name, commit_node, ticket_id))
				irc_client.privmsg (channel.get_value ('name'),
					"Read all about it here: " + Bt.get_ticket_url (ticket_id))

	if need_update:
		# We need to remove the note data, otherwise the ticket notes
		# will get unnecessary updates. WTF, MantisBT?
		ticket_data.notes = []
		Bt.update_issue (ticket_id, ticket_data)

	Bt.post_note (ticket_id, message)

def init():
	check_repo_exists ('zandronum', 'Torr_Samaho')
	check_repo_exists ('zandronum-stable', 'Torr_Samaho')
	check_repo_exists ('zandronum-sandbox', 'crimsondusk')
	check_repo_exists ('zandronum-sandbox-stable', 'crimsondusk')
	check_repo_exists ('zandronum-everything', '')
	global repocheck_timeout
	repocheck_timeout = (time.time()) + 15

def get_commit_data (zanrepo, rev, template):
	return zanrepo.hg_command ('log', '-l', '1', '-r', rev, '--template', template)

def decipher_hgapi_error (e):
	# Blah, hgapi, why must your error messages be so mangled?
	try:
		rawmsg = e.message.replace('\n', '').replace('" +','').replace('\t','')
		errmsg = re.compile (r'.*: tErr: (.*)Out:.*').match (rawmsg).group (1)
		return [True, errmsg]
	except:
		return [False, '']

def bbcodify (commit_diffstat):
	result = ''

	for line in commit_diffstat.split('\n'):
		rex = re.compile (r'^(.*)\|(.*) (\+*)(-*)(.*)$')
		match = rex.match (line)
		if match:
			line = '%s|%s [color=#5F7]%s[/color][color=#F53]%s[/color]%s\n' \
				% (match.group (1), match.group (2), match.group (3), match.group (4), match.group
(5))

			# Tracker doesn't seem to like empty color tags
			line = line.replace ('[color=#5F7][/color]', '').replace ('[color=#F53][/color]', '')

		result += line

	return result

def poll():
	global repocheck_timeout
	if time.time() < repocheck_timeout:
		return

	for n in ['zandronum-stable', 'zandronum', 'zandronum-sandbox', 'zandronum-sandbox-stable']:
		poll_one_repo (n)

	hgns = Config.get_node ('hg')
	repocheck_timeout = time.time() + hgns.get_value ('checkinterval', default=15) * 60

def poll_one_repo (repo_name):
	global repocheck_timeout
	hgns = Config.get_node ('hg')

	if not hgns.get_value ('track', default=True):
		return

	usesandbox = repo_name == 'zandronum-sandbox' or repo_name == 'zandronum-sandbox-stable'
	repo_owner = 'Torr_Samaho' if not usesandbox else 'crimsondusk'
	zanrepo = hgapi.Repo (repo_name)
	commit_data = []
	delimeter = '@@@@@@@@@@'
	print 'Checking %s for updates' % repo_name

	try:
		data = zanrepo.hg_command ('incoming', '--quiet', '--template',
			'{node|short} {desc}' + delimeter)
	except hgapi.hgapi.HgException as e:
		deciphered = decipher_hgapi_error (e)

		if deciphered[0] and len(deciphered[1]) > 0:
			Irc.broadcast ("error while using hg import on %s: %s" % (repo_name, deciphered[1]))

		return
	except Exception as e:
		Irc.broadcast ("%s" % `e`)
		return

	for line in data.split (delimeter):
		if line == '':
			continue

		rex = re.compile (r'([^ ]+) (.+)')
		match = rex.match (line)
		failed = False

		if not match:
			Irc.broadcast ('malformed hg data: %s' % line)
			continue

		commit_node = match.group (1)
		commit_message = match.group (2)
		commit_data.append ([commit_node, commit_message])

	process_new_commits (repo_name, commit_data)

def process_new_commits (repo_name, commit_data):
	usestable = repo_name == 'zandronum-stable'
	usesandbox = repo_name == 'zandronum-sandbox' or repo_name == 'zandronum-sandbox-stable'
	repo_owner = 'Torr_Samaho' if not usesandbox else 'crimsondusk'
	repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name)
	num_commits = 0
	zanrepo = hgapi.Repo (repo_name)
	Irc.broadcast ('%d new commits on %s' % (len (commit_data), repo_name))

	if len (commit_data) > 0:
		pull_args = [];

		for commit in commit_data:
			pull_args.append ('-r');
			pull_args.append (commit[0]);

		print 'Pulling new commits...'
		try:
			zanrepo.hg_command ('pull', *pull_args)

			# Also pull these commits to the zandronum main repository
			if usestable:
				devrepo = hgapi.Repo ('zandronum')
				devrepo.hg_command ('pull', '../zandronum-stable', *pull_args)

			# Pull everything into sandboxes too
			if not usesandbox:
				devrepo = hgapi.Repo ('zandronum-sandbox')
				devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args)

				devrepo = hgapi.Repo ('zandronum-sandbox-stable')
				devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args)

			devrepo = hgapi.Repo ('zandronum-everything')
			devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args)

			global g_needCommitsTxtRebuild
			g_needCommitsTxtRebuild = True
		except Exception as e:
			Irc.broadcast ('Warning: unable to pull: %s' % `e`)
			return

	for commit in commit_data:
		commit_node = commit[0]
		commit_message = commit[1]
		print 'Processing new commit %s...' % commit_node

		try:
			commit_author = get_commit_data (zanrepo, commit_node, '{author}')
			commit_url = '%s/commits/%s' % (repo_url, commit_node)
			commit_email = ''

			# Remove the email address from the author if possible
			rex = re.compile (r'^(.+) <([^>]+)>$.*')
			match = rex.match (commit_author)
			if match:
				commit_author = match.group (1)
				commit_email = match.group (2)

			commit_trackeruser = Config.find_developer_by_email (commit_email)
			committer = commit_trackeruser if commit_trackeruser != '' else commit_author

			for irc_client in Irc.all_clients:
				for channel in irc_client.channels:
					if not usesandbox or channel.get_value ('btprivate', False):
						irc_client.privmsg (channel.get_value ('name'),
							"%s: new commit %s by %s: %s"
							% (repo_name, commit_node, committer, commit_url))

						for line in commit_message.split ('\n'):
							irc_client.privmsg (channel.get_value ('name'), line)

			if not usesandbox:
				rex = re.compile (r'^.*(fixes|resolves|addresses|should fix) ([0-9]+).*$')
				match = rex.match (commit_message)

				if match:
					announce_ticket_resolved (match.group (2), commit_node)

			num_commits += 1
		except Exception as e:
			Irc.broadcast ('Error while processing %s: %s' % (commit_node, e))
			continue

def force_poll():
	global repocheck_timeout
	repocheck_timeout = 0
	poll()

mercurial