hgpoll.py

Thu, 15 Jan 2015 19:06:14 +0200

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Thu, 15 Jan 2015 19:06:14 +0200
changeset 118
dbf49689af0d
parent 115
2bb5c4578ee1
child 120
9880bb697149
permissions
-rw-r--r--

- added bridging functionality

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 color_for_repo (repo_name):
	if repo_name == 'zandronum':
		color = 4
	elif repo_name == 'zandronum-stable':
		color = 2
	elif repo_name == 'zandronum-sandbox':
		color = 7
	elif repo_name == 'zandronum-sandbox-stable':
		color = 3
	elif repo_name == 'zfc9000':
		color = 6
	else:
		color = 0
	return color

def prettify_bookmarks (bookmarks):
	return "\0036 [\002%s\002]" % bookmarks

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

	commit_message = commit_message.replace ("\n", " ")

	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'),
					"\003%d%s\003: commit\0035 %s\003 addresses issue\002\0032 %d\002" % (color_for_repo (repo_name), 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', '')
	check_repo_exists ('zfc9000', 'crimsondusk')
	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', 'zfc9000']:
		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 and repo_name != 'zfc9000') 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):
	if len (commit_data) == 0:
		return

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

	# Drop commits that we already have
	i = 0
	while i < len (commit_data):
		try:
			zanrepo.hg_command ('log', '-r', commit_data[i][0])
			del commit_data[i]
		except:
			i += 1

	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)

		if repo_name[0:9] == 'zandronum':
			# 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:
			data = get_commit_data (zanrepo, commit_node, '{author}@@@@@@@@@@{bookmarks}').split ("@@@@@@@@@@")
			commit_author = data[0]
			commit_bookmarks = prettify_bookmarks (data[1])
			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 channel.get_value ('btannounce', False):
						continue

					if (not usesandbox and repo_name != 'zfc9000') or channel.get_value ('btprivate', False):
						irc_client.privmsg (channel.get_value ('name'),
							"\003%d%s\003: new commit\0035 %s%s\003 by\0032 %s\003: %s"
							% (color_for_repo (repo_name), repo_name, commit_node, commit_bookmarks,
							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