This file is indexed.

/usr/share/monotone/scripts/monotone-ciabot.py is in monotone-extras 1.1-4+deb8u2.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/env python
#
# Copyright (C) Nathaniel Smith <njs@pobox.com>
#               Timothy Brownawell <tbrownaw@gmail.com>
#               Thomas Moschny <thomas.moschny@gmx.de>
#               Richard Levitte <richard@levitte.org>
# Licensed under the MIT license:
#   http://www.opensource.org/licenses/mit-license.html
# I.e., do what you like, but keep copyright and there's NO WARRANTY.
#
# CIA bot client script for Monotone repositories, written in python.  This
# generates commit messages using CIA's XML commit format, and can deliver
# them using either XML-RPC or email.  Based on the script 'ciabot_svn.py' by
# Micah Dowty <micah@navi.cx>.

# This version is modified to be called by a server hook, instead of a cron job.

# To use:
#   -- create a configuration file called ciabot.conf in the server's
#      configuration directory (whatever is returned by get_confdir()).
#      That configuration file follows basic-io syntax, the comments in
#      the config class is the best documentation for now.
#   -- include monotone-ciabot.lua in the server's monotonerc
#   -- add a variable 'ciabot_python_script in the server's monotonerc,
#      that points to this script.

import sys
import re
import os
import fnmatch

TOKEN = re.compile(r'''
    "(?P<str>(\\\\|\\"|[^"])*)"
    |\[(?P<id>[a-f0-9]{40}|)\]
    |(?P<key>\w+)
    |(?P<ws>\s+)
''', re.VERBOSE)

def parse_basic_io(raw):
    parsed = []
    key = None
    for m in TOKEN.finditer(raw):
        if m.lastgroup == 'key':
            if key:
                parsed.append((key, values))
            key = m.group('key')
            values = []
        elif m.lastgroup == 'id':
            values.append(m.group('id'))
        elif m.lastgroup == 'str':
            value = m.group('str')
            # dequote: replace \" with "
            value = re.sub(r'\\"', '"', value)
            # dequote: replace \\ with \
            value = re.sub(r'\\\\', r'\\', value)
            values.append(value)
    if key:
        parsed.append((key, values))
    return parsed

class config:
    # The server to deliver XML-RPC messages to, if using XML-RPC delivery.
    # In the configuration file, this is set with the key 'xmlrpc_server'
    xmlrpc_server = "http://cia.navi.cx"

    # The 'from' address to put on email, if using email delivery.
    # In the configuration file, this is set with the key 'from_address'
    from_address = "cia-user@FIXME"

    # The email address to deliver messages to, if using email delivery.
    # In the configuration file, this is set with the key 'to_address'
    smtp_address = "cia@cia.navi.cx"

    # The SMTP server to connect to, if using email delivery.
    # In the configuration file, this is set with the key 'smtp_server'
    smtp_server = "localhost"

    # Set to one of "xmlrpc", "email", "debug".
    # In the configuration file, this is set with the key 'delivery'
    delivery = "debug"

    # The default URL template to be used when none other is found.
    # In the configuration file, this is set with the key 'url'.
    # NOTE: THIS MUST COME BEFORE ANY 'pattern'
    # The URL template can contain %s constructs as follows:
    #   %(revision)s      is replaced with the revision identity
    #   %(branch)s        is replaced with the branch name
    #   %(projectid)s     is replaced with the project identity
    #   %(projectname)s   is replaced with the project name
    default_url = None
    
    # These are three dicionaries, where the first stores project identities
    # keyed with globs and the others store project names for CIA and commit
    # URLs, both keyed with project identities.
    # In the configuration file, this is set with stanzas started with
    # the key(s) 'pattern' followed by the keys 'projectid' (project id),
    # 'projectname' (project name) and 'url' (URL template) to set the
    # values.  Each stanza can have several patterns but only one of
    # 'projectid', 'projectname' and 'url'.
    # You MUST define a project identity in each stanza.
    # Project names are names that are used officially with CIA.  if
    # undefined, the project identity is used.
    projectids = {}
    projectnames = {}
    urls = {}

    # This is internal, an array to keep the patterns in order.
    # THIS MEANS THE ORDER IN THE CONFIGURATION FILE IS SIGNIFICANT!
    patterns = []

    def __init__(self, config_file):
        s = ""
        f = open(config_file)
        for line in f:
            s = s + line
        f.close()
        previous_key = ""
        current_patterns = []
        for key, value in parse_basic_io(s):
            if key == 'xmlrpc_server':
                self.xmlrcp_server = value[0]
            elif key == 'to_address':
                self.smtp_address = value[0]
            elif key == 'from_address':
                self.from_address = value[0]
            elif key == 'smtp_server':
                self.smtp_server = value[0]
            elif key == 'delivery':
                self.delivery = value[0]
            elif key == 'pattern':
                if previous_key != key:
                    # new series of patterns, new stanza
                    current_patterns = []
                self.patterns.append(value[0])
                current_patterns.append(value[0])
            elif key == 'projectid':
                for p in current_patterns:
                    self.projectids[p] = value[0]
            elif key == 'projectname':
                for p in current_patterns:
                    self.projectnames[p] = value[0]
            elif key == 'url':
                url_set = False
                for p in current_patterns:
                    self.urls[p] = value[0]
                    url_set = True
                if not url_set:
                    # It means we haven't seen any pattern yet, and
                    # therefore have a default URL
                    self.default_url = value[0]
            previous_key = key

    def _pattern_for_branch(self, branchname):
        l = [ p for p in self.patterns if fnmatch.fnmatchcase(branchname, p) ]
        if len(l) > 0:
            return l[0]
        return None
        
    def projectid_for_branch(self, branchname):
        pat = self._pattern_for_branch(branchname)
        if pat:
            return self.projectids[pat]
        return None

    def projectname_for_branch(self, branchname):
        pat = self._pattern_for_branch(branchname)
        if pat:
            return self.projectnames[pat] if pat in self.projectnames else self.projectids[pat]
        return None

    def url_for_revision(self, branchname, revid):
        pat = self._pattern_for_branch(branchname)
        if pat:
            substs = {}
            substs["revision"] = revid;
            substs["branch"] = branchname;
            substs["projectid"] = self.projectids[pat];
            substs["projectname"] = self.projectnames[pat] if pat in self.projectnames else self.projectids[pat]
            return (self.urls[pat] if pat in self.urls else self.default_url) % substs
        return ""

################################################################################

def escape_for_xml(text, is_attrib=0):
    text = text.replace("&", "&amp;")
    text = text.replace("<", "&lt;")
    text = text.replace(">", "&gt;")
    if is_attrib:
        text = text.replace("'", "&apos;")
        text = text.replace("\"", "&quot;")
    return text

def send_message(message, c):
    if c.delivery == "debug":
        print message
    elif c.delivery == "xmlrpc":
        import xmlrpclib
        xmlrpclib.ServerProxy(c.xmlrpc_server).hub.deliver(message)
    elif c.delivery == "email":
        import smtplib
        smtp = smtplib.SMTP(c.smtp_server)
        smtp.sendmail(c.from_address, c.smtp_address,
                      "From: %s\r\nTo: %s\r\n"
                      "Subject: DeliverXML\r\n\r\n%s"
                      % (c.from_address, c.smtp_address, message))
    else:
        sys.exit("delivery option must be one of 'debug', 'xmlrpc', 'email'")

def send_change_for(rid, branch, author, log, rev, c):
    message_tmpl = """<message>
    <generator>
        <name>Monotone CIA Bot client python script</name>
        <version>1.0</version>
    </generator>
    <source>
        <project>%(project)s</project>
        <branch>%(branch)s</branch>
    </source>
    <body>
        <commit>
            <revision>%(rid)s</revision>
            <author>%(author)s</author>
            <files>%(files)s</files>
            <log>%(log)s</log>
            %(extra)s
        </commit>
    </body>
</message>"""
    
    substs = {}
    files = []
    for key, values in parse_basic_io(rev):
        if key == 'old_revision':
            # start a new changeset
            oldpath = None
        if key == 'delete':
            files.append('<file action="remove">%s</file>'
                         % escape_for_xml(values[0]))
        elif key == 'rename':
            oldpath = values[0]
        elif key == 'to':
            if oldpath:
                files.append('<file action="rename" to="%s">%s</file>'
                             % (escape_for_xml(values[0]), escape_for_xml(oldpath)))
                oldpath = None
        elif key == 'add_dir':
            files.append('<file action="add">%s</file>'
                         % escape_for_xml(values[0] + '/'))
        elif key == 'add_file':
            files.append('<file action="add">%s</file>'
                          % escape_for_xml(values[0]))
        elif key == 'patch':
            files.append('<file action="modify">%s</file>'
                         % escape_for_xml(values[0]))
            
    substs["files"] = "\n".join(files)
    changelog = log.strip()
    project = c.projectname_for_branch(branch)
    if project is None:
        return
    commiturl = c.url_for_revision(branch, rid)
    substs["author"] = escape_for_xml(author)
    substs["project"] = escape_for_xml(project)
    substs["branch"] = escape_for_xml(branch)
    substs["rid"] = escape_for_xml(rid)
    substs["log"] = escape_for_xml(changelog)
    substs["extra"] = ""
    if not commiturl is None:
        substs["extra"] += '<url>%s</url>' % escape_for_xml(commiturl)

    message = message_tmpl % substs
    send_message(message, c)

def main(progname, args):
    if len(args) != 6:
        sys.exit("Usage: %s confdir revid branch author changelog revision_text" % (progname, ))
    # We don't want to clutter the process table with zombies; but we also
    # don't want to force the monotone server to wait around while we call the
    # CIA server.  So we fork -- the original process exits immediately, and
    # the child continues (orphaned, so it will eventually be reaped by init).
    if hasattr(os, "fork"):
        if os.fork():
            return
    (confdir, rid, branch, author, log, rev, ) = args
    c = config(confdir + "/ciabot.conf")
    send_change_for(rid, branch, author, log, rev, c)

if __name__ == "__main__":
    main(sys.argv[0], sys.argv[1:])