#!/usr/bin/env python import sys import urllib import urllib2 import subprocess import re import json ################################# # Begin Configuration variables # ################################# # Url of the ttextpro.exe cgi on your web server URL = 'http://localhost/cgi-bin/ttextpro.exe' # Provider key from the Source Control Providers dialog PROVIDER_KEY = '{aa858562-025a-4236-9bf2-6cf4c81e0468}:{5d723ea4-0501-4343-8ee1-60189bcf3314}' # JSON key for the commit hash. This should match the name you used in the Commit URL # and File URL fields in your Source Control Provider configuration. COMMIT_HASH_KEY = 'commitHash' # JSON key for the file hash. This should match the name you used in the File URL field # in your Source Control Provider configuration. FILE_HASH_KEY = 'fileHash' # JSON key for the file parent hash. This should match the name you used in the # File URL field in your Source Control Provider configuration. FILE_PARENT_HASH_KEY = 'fileParentHash' # JSON key for the commit parent hash. This should match the name you used in the # File URL field in your Source Control Provider configuration. COMMIT_PARENT_HASH_KEY = 'commitParentHash' # Tags used to attach to issues, test cases, and requirements. Format is "[TAG-NUMBER]". ISSUE_TAG = 'IS' TEST_CASE_TAG = 'TC' REQUIREMENT_TAG = 'RQ' ############################### # End Configuration variables # ############################### TAG_PATTERN = r'\[(%s|%s|%s)-(\d+)\]' % (ISSUE_TAG, TEST_CASE_TAG, REQUIREMENT_TAG) TAG_REGEX = re.compile(TAG_PATTERN, re.IGNORECASE) TAG_MAP = {ISSUE_TAG: 'issue', TEST_CASE_TAG: 'test case', REQUIREMENT_TAG: 'requirement'} STATUS_MAP = {'A': 'added', 'M': 'modified', 'D': 'removed'} # Run a git command. def git(*args): return subprocess.Popen(('git',) + args, stdout=subprocess.PIPE).communicate()[0].strip() # Get the full CGI URL. def get_url(): return URL + '?' + urllib.urlencode({ 'action': 'AddAttachment' }) # Get the branch name based on the ref name. def get_branch_name(ref_name): return ref_name.replace('refs/heads/', '') # Get the object type based on the tag. def get_object_type(tag): return TAG_MAP[tag.upper()] # Get the change type based on the file status. def get_change_type(status): return STATUS_MAP[status.upper()] # Get JSON data for a ref. def get_data(old_value, new_value, ref_name): return {'providerKey': PROVIDER_KEY, 'attachmentList': get_commits(old_value, new_value, ref_name)} # Get JSON data for commits being pushed to a ref. def get_commits(old_value, new_value, ref_name): revlist = get_revs(old_value, new_value, ref_name) commits = [] for rev in revlist: commit_hash, commit_parent_hashes, commit_author, commit_message = rev.strip().split('\n', 3) commit_parent_hash = commit_parent_hashes.split(' ')[0] commit_message = commit_message.strip() to_attach = get_attachments(commit_message) if len(to_attach) > 0: files = get_files(commit_hash, commit_parent_hash) commits.append({COMMIT_HASH_KEY: commit_hash, 'attachmentAuthor': commit_author, 'attachmentMessage': commit_message, 'branchName': get_branch_name(ref_name), 'toAttachList': to_attach, 'fileList': files}) return commits # Get list of revisions to inspect for attachments. def get_revs(old_value, new_value, ref_name): if old_value == '0000000000000000000000000000000000000000': revs = git('rev-parse', '--not', '--branches').split('\n') revs = [rev for rev in revs if rev != '^' + new_value] revs.append(new_value) else: revs = ['^' + old_value, new_value] revlist = git('rev-list', '--pretty=format:%P%n%an%n%s%n%b', '--reverse', *revs) revlist = revlist.split('commit ')[1:] return revlist # Get JSON data for items to attach to. def get_attachments(commit_message): matches = TAG_REGEX.finditer(commit_message) attachments = [] for match in matches: attachments.append({'objectType': get_object_type(match.group(1)), 'number': int(match.group(2))}) return attachments # Get JSON data for files changed in a commit. def get_files(commit_hash, commit_parent_hash): filelist = git('diff-tree', '--no-commit-id', '--root', '-r', commit_hash) filelist = filelist.split(':')[1:] files = [] for f in filelist: status_line, file_name = f.strip().split('\t') old_mode, new_mode, old_hash, new_hash, status = status_line.split(' ') files.append({'changeType': get_change_type(status), 'fileName': file_name, FILE_HASH_KEY: new_hash, FILE_PARENT_HASH_KEY: old_hash, COMMIT_HASH_KEY: commit_hash, COMMIT_PARENT_HASH_KEY: commit_parent_hash}) return files # Handle attaching for a ref. def handle_ref(old_value, new_value, ref_name): req = urllib2.Request(get_url(), json.dumps(get_data(old_value, new_value, ref_name)), {'Content-Type': 'application/json'}) try: f = urllib2.urlopen(req) res = json.loads(f.read()) f.close() if res['errorCode'] > 1: print(res['errorMessage']) elif res['errorCode'] == 1: for err in res['errorList']: print(err['errorMessage']) except urllib2.URLError as e: print(e) if __name__ == '__main__': for line in sys.stdin: old_value, new_value, ref_name = line.strip().split(' ') if ref_name.startswith('refs/heads/'): handle_ref(old_value, new_value, ref_name)