hgpoll.py

Mon, 12 Jan 2015 10:55:45 +0200

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Mon, 12 Jan 2015 10:55:45 +0200
changeset 117
6c0609395889
parent 115
2bb5c4578ee1
child 120
9880bb697149
permissions
-rw-r--r--

- added a confirm system, probably useful in the future

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