1 | import socket |
---|
2 | import time |
---|
3 | from subprocess import check_call |
---|
4 | |
---|
5 | from genshi.builder import tag |
---|
6 | |
---|
7 | from trac.core import implements, Component |
---|
8 | from trac.ticket.api import ITicketActionController |
---|
9 | from trac.ticket.default_workflow import ConfigurableTicketWorkflow |
---|
10 | from trac.web.chrome import add_warning |
---|
11 | |
---|
12 | from BranchActor import BranchActor |
---|
13 | from RebranchActor import RebranchActor |
---|
14 | from MergeActor import MergeActor |
---|
15 | from CheckMergeActor import CheckMergeActor |
---|
16 | from action_logic import is_branchable, is_rebranchable, is_mergeable, is_checkmergeable |
---|
17 | |
---|
18 | class MergebotActionController(Component): |
---|
19 | """Support branching and merging operations for tickets. |
---|
20 | """ |
---|
21 | implements(ITicketActionController) |
---|
22 | |
---|
23 | # ITicketActionController |
---|
24 | def get_ticket_actions(self, req, ticket): |
---|
25 | controller = ConfigurableTicketWorkflow(self.env) |
---|
26 | mergebot_operations = self._get_available_operations(req, ticket) |
---|
27 | if mergebot_operations: |
---|
28 | actions_we_handle = controller.get_actions_by_operation_for_req(req, |
---|
29 | ticket, 'mergebot') |
---|
30 | else: |
---|
31 | actions_we_handle = [] |
---|
32 | return actions_we_handle |
---|
33 | |
---|
34 | def get_all_status(self): |
---|
35 | return [] |
---|
36 | |
---|
37 | def render_ticket_action_control(self, req, ticket, action): |
---|
38 | actions = ConfigurableTicketWorkflow(self.env).actions |
---|
39 | label = actions[action]['name'] |
---|
40 | hint = '' |
---|
41 | |
---|
42 | control_id = action + '_mergebot_op' |
---|
43 | mergebot_operations = self._get_available_operations(req, ticket) |
---|
44 | |
---|
45 | # TODO: allow the configuration to specify a sub-set of permitted |
---|
46 | # mergebot actions |
---|
47 | #self.config.get('ticket-workflow', action + '.mergebot') |
---|
48 | |
---|
49 | self.env.log.debug('render_ticket_action_control ops: %s', mergebot_operations) |
---|
50 | selected_value = req.args.get(control_id) or mergebot_operations[0] |
---|
51 | control = tag.select([tag.option(option, selected=(option == selected_value or None)) for option in mergebot_operations], name=control_id, id=control_id) |
---|
52 | |
---|
53 | return (label, control, hint) |
---|
54 | |
---|
55 | |
---|
56 | def get_ticket_changes(self, req, ticket, action): |
---|
57 | control_id = action + '_mergebot_op' |
---|
58 | selected_value = req.args.get(control_id) |
---|
59 | add_warning(req, 'Mergebot operation %s will be triggered' % selected_value) |
---|
60 | return {} |
---|
61 | |
---|
62 | def daemon_address(self): |
---|
63 | host = self.env.config.get('mergebot', 'listen.ip') |
---|
64 | port = self.env.config.getint('mergebot', 'listen.port') |
---|
65 | return (host, port) |
---|
66 | |
---|
67 | def start_daemon(self): |
---|
68 | check_call(['mergebotdaemon', self.env.path]) |
---|
69 | time.sleep(1) # bleh |
---|
70 | |
---|
71 | def _daemon_cmd(self, cmd): |
---|
72 | self.log.debug('Sending mergebotdaemon: %r' % cmd) |
---|
73 | try: |
---|
74 | info_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
---|
75 | info_socket.connect(self.daemon_address()) |
---|
76 | except socket.error, e: |
---|
77 | # if we're refused, try starting the daemon and re-try |
---|
78 | if e and e[0] == 111: |
---|
79 | self.log.debug('connection to mergebotdaemon refused, trying to start mergebotdaemon') |
---|
80 | self.start_daemon() |
---|
81 | self.log.debug('Resending mergebotdaemon: %r' % cmd) |
---|
82 | info_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
---|
83 | info_socket.connect(self.daemon_address()) |
---|
84 | info_socket.sendall(cmd) |
---|
85 | self.log.debug('Reading mergebotdaemon response') |
---|
86 | raw = info_socket.recv(4096) |
---|
87 | info_socket.close() |
---|
88 | self.log.debug('Reading mergebotdaemon response was %r' % raw) |
---|
89 | return raw |
---|
90 | |
---|
91 | def apply_action_side_effects(self, req, ticket, action): |
---|
92 | self.log.info('applying mergebot side effect for ticket %s on action %s' % (ticket.id, action)) |
---|
93 | control_id = action + '_mergebot_op' |
---|
94 | selected_value = req.args.get(control_id) |
---|
95 | |
---|
96 | result = self._daemon_cmd('ADD %s %s %s %s %s\n\QUIT\n' % (ticket.id, selected_value, ticket['component'], ticket['version'], req.authname or 'anonymous')) |
---|
97 | if 'OK' not in result: |
---|
98 | add_warning(req, result) # adding warnings in this method doesn't seem to work |
---|
99 | |
---|
100 | def _get_available_operations(self, req, ticket): |
---|
101 | mergebot_ops = [] |
---|
102 | if req.perm.has_permission("MERGEBOT_BRANCH"): |
---|
103 | if is_branchable(ticket): |
---|
104 | mergebot_ops.append('branch') |
---|
105 | if is_rebranchable(ticket): |
---|
106 | mergebot_ops.append('rebranch') |
---|
107 | if is_checkmergeable(ticket) and req.perm.has_permission('MERGEBOT_VIEW'): |
---|
108 | mergebot_ops.append('checkmerge') |
---|
109 | if ticket['version'].startswith("#"): |
---|
110 | may_merge = req.perm.has_permission("MERGEBOT_MERGE_TICKET") |
---|
111 | else: |
---|
112 | may_merge = req.perm.has_permission("MERGEBOT_MERGE_RELEASE") |
---|
113 | if may_merge and is_mergeable(ticket): |
---|
114 | mergebot_ops.append('merge') |
---|
115 | return mergebot_ops |
---|