We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
fixed availabale typo


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/changes/hgpoller.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 import os
17 import time
18 
19 from twisted.internet import defer
20 from twisted.internet import utils
21 from twisted.python import log
22 
23 from buildbot import config
24 from buildbot.changes import base
25 from buildbot.util import ascii2unicode
26 from buildbot.util import deferredLocked
27 
28 
29 class HgPoller(base.PollingChangeSource):
30 
31     """This source will poll a remote hg repo for changes and submit
32     them to the change master."""
33 
34     compare_attrs = ("repourl", "branch", "workdir",
35                      "pollInterval", "hgpoller", "usetimestamps",
36                      "category", "project", "pollAtLaunch")
37 
38     db_class_name = 'HgPoller'
39 
40     def __init__(self, repourl, branch='default',
41                  workdir=None, pollInterval=10 * 60,
42                  hgbin='hg', usetimestamps=True,
43                  category=None, project='', pollinterval=-2,
44                  encoding='utf-8', name=None, pollAtLaunch=False):
45 
46         # for backward compatibility; the parameter used to be spelled with 'i'
47         if pollinterval != -2:
48             pollInterval = pollinterval
49 
50         if name is None:
51             name = repourl
52 
53         self.repourl = repourl
54         self.branch = branch
55         base.PollingChangeSource.__init__(
56             self, name=name, pollInterval=pollInterval, pollAtLaunch=pollAtLaunch)
57         self.encoding = encoding
58         self.lastChange = time.time()
59         self.lastPoll = time.time()
60         self.hgbin = hgbin
61         self.workdir = workdir
62         self.usetimestamps = usetimestamps
63         self.category = category
64         self.project = project
65         self.commitInfo = {}
66         self.initLock = defer.DeferredLock()
67 
68         if self.workdir is None:
69             config.error("workdir is mandatory for now in HgPoller")
70 
71     def describe(self):
72         status = ""
73         if not self.master:
74             status = "[STOPPED - check log]"
75         return ("HgPoller watching the remote Mercurial repository %r, "
76                 "branch: %r, in workdir %r %s") % (self.repourl, self.branch,
77                                                    self.workdir, status)
78 
79     @deferredLocked('initLock')
80     def poll(self):
81         d = self._getChanges()
82         d.addCallback(self._processChanges)
83         d.addErrback(self._processChangesFailure)
84         return d
85 
86     def _absWorkdir(self):
87         workdir = self.workdir
88         if os.path.isabs(workdir):
89             return workdir
90         return os.path.join(self.master.basedir, workdir)
91 
92     def _getRevDetails(self, rev):
93         """Return a deferred for (date, author, files, comments) of given rev.
94 
95         Deferred will be in error if rev is unknown.
96         """
97         args = ['log', '-r', rev, os.linesep.join((
98             '--template={date|hgdate}',
99             '{author}',
100             "{files % '{file}" + os.pathsep + "'}",
101             '{desc|strip}'))]
102         # Mercurial fails with status 255 if rev is unknown
103         d = utils.getProcessOutput(self.hgbin, args, path=self._absWorkdir(),
104                                    env=os.environ, errortoo=False)
105 
106         def process(output):
107             # all file names are on one line
108             date, author, files, comments = output.decode(self.encoding, "replace").split(
109                 os.linesep, 3)
110 
111             if not self.usetimestamps:
112                 stamp = None
113             else:
114                 try:
115                     stamp = float(date.split()[0])
116                 except:
117                     log.msg('hgpoller: caught exception converting output %r '
118                             'to timestamp' % date)
119                     raise
120             return stamp, author.strip(), files.split(os.pathsep)[:-1], comments.strip()
121 
122         d.addCallback(process)
123         return d
124 
125     def _isRepositoryReady(self):
126         """Easy to patch in tests."""
127         return os.path.exists(os.path.join(self._absWorkdir(), '.hg'))
128 
129     def _initRepository(self):
130         """Have mercurial init the workdir as a repository (hg init) if needed.
131 
132         hg init will also create all needed intermediate directories.
133         """
134         if self._isRepositoryReady():
135             return defer.succeed(None)
136         log.msg('hgpoller: initializing working dir from %s' % self.repourl)
137         d = utils.getProcessOutputAndValue(self.hgbin,
138                                            ['init', self._absWorkdir()],
139                                            env=os.environ)
140         d.addCallback(self._convertNonZeroToFailure)
141         d.addErrback(self._stopOnFailure)
142         d.addCallback(lambda _: log.msg(
143             "hgpoller: finished initializing working dir %r" % self.workdir))
144         return d
145 
146     def _getChanges(self):
147         self.lastPoll = time.time()
148 
149         d = self._initRepository()
150         d.addCallback(lambda _: log.msg(
151             "hgpoller: polling hg repo at %s" % self.repourl))
152 
153         # get a deferred object that performs the fetch
154         args = ['pull', '-b', self.branch, self.repourl]
155 
156         # This command always produces data on stderr, but we actually do not
157         # care about the stderr or stdout from this command.
158         # We set errortoo=True to avoid an errback from the deferred.
159         # The callback which will be added to this
160         # deferred will not use the response.
161         d.addCallback(lambda _: utils.getProcessOutput(
162             self.hgbin, args, path=self._absWorkdir(),
163             env=os.environ, errortoo=True))
164 
165         return d
166 
167     def _getStateObjectId(self):
168         """Return a deferred for object id in state db.
169 
170         Being unique among pollers, workdir is used with branch as instance
171         name for db.
172         """
173         return self.master.db.state.getObjectId(
174             '#'.join((self.workdir, self.branch)), self.db_class_name)
175 
176     def _getCurrentRev(self):
177         """Return a deferred for object id in state db and current numeric rev.
178 
179         If never has been set, current rev is None.
180         """
181         d = self._getStateObjectId()
182 
183         def oid_cb(oid):
184             d = self.master.db.state.getState(oid, 'current_rev', None)
185 
186             def addOid(cur):
187                 if cur is not None:
188                     return oid, int(cur)
189                 return oid, cur
190             d.addCallback(addOid)
191             return d
192         d.addCallback(oid_cb)
193         return d
194 
195     def _setCurrentRev(self, rev, oid=None):
196         """Return a deferred to set current revision in persistent state.
197 
198         oid is self's id for state db. It can be passed to avoid a db lookup."""
199         if oid is None:
200             d = self._getStateObjectId()
201         else:
202             d = defer.succeed(oid)
203 
204         def set_in_state(obj_id):
205             return self.master.db.state.setState(obj_id, 'current_rev', rev)
206         d.addCallback(set_in_state)
207 
208         return d
209 
210     def _getHead(self):
211         """Return a deferred for branch head revision or None.
212 
213         We'll get an error if there is no head for this branch, which is
214         proabably a good thing, since it's probably a mispelling
215         (if really buildbotting a branch that does not have any changeset
216         yet, one shouldn't be surprised to get errors)
217         """
218         d = utils.getProcessOutput(self.hgbin,
219                                    ['heads', self.branch, '--template={rev}' + os.linesep],
220                                    path=self._absWorkdir(), env=os.environ, errortoo=False)
221 
222         def no_head_err(exc):
223             log.err("hgpoller: could not find branch %r in repository %r" % (
224                 self.branch, self.repourl))
225         d.addErrback(no_head_err)
226 
227         def results(heads):
228             if not heads:
229                 return
230 
231             if len(heads.split()) > 1:
232                 log.err(("hgpoller: caught several heads in branch %r "
233                          "from repository %r. Staying at previous revision"
234                          "You should wait until the situation is normal again "
235                          "due to a merge or directly strip if remote repo "
236                          "gets stripped later.") % (self.branch, self.repourl))
237                 return
238 
239             # in case of whole reconstruction, are we sure that we'll get the
240             # same node -> rev assignations ?
241             return int(heads.strip())
242 
243         d.addCallback(results)
244         return d
245 
246     @defer.inlineCallbacks
247     def _processChanges(self, unused_output):
248         """Send info about pulled changes to the master and record current.
249 
250         GitPoller does the recording by moving the working dir to the head
251         of the branch.
252         We don't update the tree (unnecessary treatment and waste of space)
253         instead, we simply store the current rev number in a file.
254         Recall that hg rev numbers are local and incremental.
255         """
256         oid, current = yield self._getCurrentRev()
257         # hg log on a range of revisions is never empty
258         # also, if a numeric revision does not exist, a node may match.
259         # Therefore, we have to check explicitely that branch head > current.
260         head = yield self._getHead()
261         if head <= current:
262             return
263         if current is None:
264             # we could have used current = -1 convention as well (as hg does)
265             revrange = '%d:%d' % (head, head)
266         else:
267             revrange = '%d:%s' % (current + 1, head)
268 
269         # two passes for hg log makes parsing simpler (comments is multi-lines)
270         revListArgs = ['log', '-b', self.branch, '-r', revrange,
271                        r'--template={rev}:{node}\n']
272         results = yield utils.getProcessOutput(self.hgbin, revListArgs,
273                                                path=self._absWorkdir(), env=os.environ, errortoo=False)
274 
275         revNodeList = [rn.split(':', 1) for rn in results.strip().split()]
276 
277         log.msg('hgpoller: processing %d changes: %r in %r'
278                 % (len(revNodeList), revNodeList, self._absWorkdir()))
279         for rev, node in revNodeList:
280             timestamp, author, files, comments = yield self._getRevDetails(
281                 node)
282             yield self.master.data.updates.addChange(
283                 author=author,
284                 revision=unicode(node),
285                 files=files,
286                 comments=comments,
287                 when_timestamp=int(timestamp),
288                 branch=ascii2unicode(self.branch),
289                 category=ascii2unicode(self.category),
290                 project=ascii2unicode(self.project),
291                 repository=ascii2unicode(self.repourl),
292                 src=u'hg')
293             # writing after addChange so that a rev is never missed,
294             # but at once to avoid impact from later errors
295             yield self._setCurrentRev(rev, oid=oid)
296 
297     def _processChangesFailure(self, f):
298         log.msg('hgpoller: repo poll failed')
299         log.err(f)
300         # eat the failure to continue along the deferred chain - we still want to catch up
301         return None
302 
303     def _convertNonZeroToFailure(self, res):
304         "utility method to handle the result of getProcessOutputAndValue"
305         (stdout, stderr, code) = res
306         if code != 0:
307             raise EnvironmentError('command failed with exit code %d: %s' % (code, stderr))
308         return (stdout, stderr, code)
309 
310     def _stopOnFailure(self, f):
311         "utility method to stop the service when a failure occurs"
312         if self.running:
313             d = defer.maybeDeferred(lambda: self.stopService())
314             d.addErrback(log.err, 'while stopping broken HgPoller service')
315         return f
316
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/master/buildbot/changes/hgpoller.py b/master/buildbot/changes/hgpoller.py
--- a/master/buildbot/changes/hgpoller.py
+++ b/master/buildbot/changes/hgpoller.py
@@ -48,7 +48,7 @@
             pollInterval = pollinterval
 
         if name is None:
-            name = repourl
+            name = "%s[%s]" % (repourl, branch)
 
         self.repourl = repourl
         self.branch = branch

{"golden_diff": "diff --git a/master/buildbot/changes/hgpoller.py b/master/buildbot/changes/hgpoller.py\n--- a/master/buildbot/changes/hgpoller.py\n+++ b/master/buildbot/changes/hgpoller.py\n@@ -48,7 +48,7 @@\n             pollInterval = pollinterval\n \n         if name is None:\n-            name = repourl\n+            name = \"%s[%s]\" % (repourl, branch)\n \n         self.repourl = repourl\n         self.branch = branch\n", "issue": "fixed availabale typo\n\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nimport os\nimport time\n\nfrom twisted.internet import defer\nfrom twisted.internet import utils\nfrom twisted.python import log\n\nfrom buildbot import config\nfrom buildbot.changes import base\nfrom buildbot.util import ascii2unicode\nfrom buildbot.util import deferredLocked\n\n\nclass HgPoller(base.PollingChangeSource):\n\n    \"\"\"This source will poll a remote hg repo for changes and submit\n    them to the change master.\"\"\"\n\n    compare_attrs = (\"repourl\", \"branch\", \"workdir\",\n                     \"pollInterval\", \"hgpoller\", \"usetimestamps\",\n                     \"category\", \"project\", \"pollAtLaunch\")\n\n    db_class_name = 'HgPoller'\n\n    def __init__(self, repourl, branch='default',\n                 workdir=None, pollInterval=10 * 60,\n                 hgbin='hg', usetimestamps=True,\n                 category=None, project='', pollinterval=-2,\n                 encoding='utf-8', name=None, pollAtLaunch=False):\n\n        # for backward compatibility; the parameter used to be spelled with 'i'\n        if pollinterval != -2:\n            pollInterval = pollinterval\n\n        if name is None:\n            name = repourl\n\n        self.repourl = repourl\n        self.branch = branch\n        base.PollingChangeSource.__init__(\n            self, name=name, pollInterval=pollInterval, pollAtLaunch=pollAtLaunch)\n        self.encoding = encoding\n        self.lastChange = time.time()\n        self.lastPoll = time.time()\n        self.hgbin = hgbin\n        self.workdir = workdir\n        self.usetimestamps = usetimestamps\n        self.category = category\n        self.project = project\n        self.commitInfo = {}\n        self.initLock = defer.DeferredLock()\n\n        if self.workdir is None:\n            config.error(\"workdir is mandatory for now in HgPoller\")\n\n    def describe(self):\n        status = \"\"\n        if not self.master:\n            status = \"[STOPPED - check log]\"\n        return (\"HgPoller watching the remote Mercurial repository %r, \"\n                \"branch: %r, in workdir %r %s\") % (self.repourl, self.branch,\n                                                   self.workdir, status)\n\n    @deferredLocked('initLock')\n    def poll(self):\n        d = self._getChanges()\n        d.addCallback(self._processChanges)\n        d.addErrback(self._processChangesFailure)\n        return d\n\n    def _absWorkdir(self):\n        workdir = self.workdir\n        if os.path.isabs(workdir):\n            return workdir\n        return os.path.join(self.master.basedir, workdir)\n\n    def _getRevDetails(self, rev):\n        \"\"\"Return a deferred for (date, author, files, comments) of given rev.\n\n        Deferred will be in error if rev is unknown.\n        \"\"\"\n        args = ['log', '-r', rev, os.linesep.join((\n            '--template={date|hgdate}',\n            '{author}',\n            \"{files % '{file}\" + os.pathsep + \"'}\",\n            '{desc|strip}'))]\n        # Mercurial fails with status 255 if rev is unknown\n        d = utils.getProcessOutput(self.hgbin, args, path=self._absWorkdir(),\n                                   env=os.environ, errortoo=False)\n\n        def process(output):\n            # all file names are on one line\n            date, author, files, comments = output.decode(self.encoding, \"replace\").split(\n                os.linesep, 3)\n\n            if not self.usetimestamps:\n                stamp = None\n            else:\n                try:\n                    stamp = float(date.split()[0])\n                except:\n                    log.msg('hgpoller: caught exception converting output %r '\n                            'to timestamp' % date)\n                    raise\n            return stamp, author.strip(), files.split(os.pathsep)[:-1], comments.strip()\n\n        d.addCallback(process)\n        return d\n\n    def _isRepositoryReady(self):\n        \"\"\"Easy to patch in tests.\"\"\"\n        return os.path.exists(os.path.join(self._absWorkdir(), '.hg'))\n\n    def _initRepository(self):\n        \"\"\"Have mercurial init the workdir as a repository (hg init) if needed.\n\n        hg init will also create all needed intermediate directories.\n        \"\"\"\n        if self._isRepositoryReady():\n            return defer.succeed(None)\n        log.msg('hgpoller: initializing working dir from %s' % self.repourl)\n        d = utils.getProcessOutputAndValue(self.hgbin,\n                                           ['init', self._absWorkdir()],\n                                           env=os.environ)\n        d.addCallback(self._convertNonZeroToFailure)\n        d.addErrback(self._stopOnFailure)\n        d.addCallback(lambda _: log.msg(\n            \"hgpoller: finished initializing working dir %r\" % self.workdir))\n        return d\n\n    def _getChanges(self):\n        self.lastPoll = time.time()\n\n        d = self._initRepository()\n        d.addCallback(lambda _: log.msg(\n            \"hgpoller: polling hg repo at %s\" % self.repourl))\n\n        # get a deferred object that performs the fetch\n        args = ['pull', '-b', self.branch, self.repourl]\n\n        # This command always produces data on stderr, but we actually do not\n        # care about the stderr or stdout from this command.\n        # We set errortoo=True to avoid an errback from the deferred.\n        # The callback which will be added to this\n        # deferred will not use the response.\n        d.addCallback(lambda _: utils.getProcessOutput(\n            self.hgbin, args, path=self._absWorkdir(),\n            env=os.environ, errortoo=True))\n\n        return d\n\n    def _getStateObjectId(self):\n        \"\"\"Return a deferred for object id in state db.\n\n        Being unique among pollers, workdir is used with branch as instance\n        name for db.\n        \"\"\"\n        return self.master.db.state.getObjectId(\n            '#'.join((self.workdir, self.branch)), self.db_class_name)\n\n    def _getCurrentRev(self):\n        \"\"\"Return a deferred for object id in state db and current numeric rev.\n\n        If never has been set, current rev is None.\n        \"\"\"\n        d = self._getStateObjectId()\n\n        def oid_cb(oid):\n            d = self.master.db.state.getState(oid, 'current_rev', None)\n\n            def addOid(cur):\n                if cur is not None:\n                    return oid, int(cur)\n                return oid, cur\n            d.addCallback(addOid)\n            return d\n        d.addCallback(oid_cb)\n        return d\n\n    def _setCurrentRev(self, rev, oid=None):\n        \"\"\"Return a deferred to set current revision in persistent state.\n\n        oid is self's id for state db. It can be passed to avoid a db lookup.\"\"\"\n        if oid is None:\n            d = self._getStateObjectId()\n        else:\n            d = defer.succeed(oid)\n\n        def set_in_state(obj_id):\n            return self.master.db.state.setState(obj_id, 'current_rev', rev)\n        d.addCallback(set_in_state)\n\n        return d\n\n    def _getHead(self):\n        \"\"\"Return a deferred for branch head revision or None.\n\n        We'll get an error if there is no head for this branch, which is\n        proabably a good thing, since it's probably a mispelling\n        (if really buildbotting a branch that does not have any changeset\n        yet, one shouldn't be surprised to get errors)\n        \"\"\"\n        d = utils.getProcessOutput(self.hgbin,\n                                   ['heads', self.branch, '--template={rev}' + os.linesep],\n                                   path=self._absWorkdir(), env=os.environ, errortoo=False)\n\n        def no_head_err(exc):\n            log.err(\"hgpoller: could not find branch %r in repository %r\" % (\n                self.branch, self.repourl))\n        d.addErrback(no_head_err)\n\n        def results(heads):\n            if not heads:\n                return\n\n            if len(heads.split()) > 1:\n                log.err((\"hgpoller: caught several heads in branch %r \"\n                         \"from repository %r. Staying at previous revision\"\n                         \"You should wait until the situation is normal again \"\n                         \"due to a merge or directly strip if remote repo \"\n                         \"gets stripped later.\") % (self.branch, self.repourl))\n                return\n\n            # in case of whole reconstruction, are we sure that we'll get the\n            # same node -> rev assignations ?\n            return int(heads.strip())\n\n        d.addCallback(results)\n        return d\n\n    @defer.inlineCallbacks\n    def _processChanges(self, unused_output):\n        \"\"\"Send info about pulled changes to the master and record current.\n\n        GitPoller does the recording by moving the working dir to the head\n        of the branch.\n        We don't update the tree (unnecessary treatment and waste of space)\n        instead, we simply store the current rev number in a file.\n        Recall that hg rev numbers are local and incremental.\n        \"\"\"\n        oid, current = yield self._getCurrentRev()\n        # hg log on a range of revisions is never empty\n        # also, if a numeric revision does not exist, a node may match.\n        # Therefore, we have to check explicitely that branch head > current.\n        head = yield self._getHead()\n        if head <= current:\n            return\n        if current is None:\n            # we could have used current = -1 convention as well (as hg does)\n            revrange = '%d:%d' % (head, head)\n        else:\n            revrange = '%d:%s' % (current + 1, head)\n\n        # two passes for hg log makes parsing simpler (comments is multi-lines)\n        revListArgs = ['log', '-b', self.branch, '-r', revrange,\n                       r'--template={rev}:{node}\\n']\n        results = yield utils.getProcessOutput(self.hgbin, revListArgs,\n                                               path=self._absWorkdir(), env=os.environ, errortoo=False)\n\n        revNodeList = [rn.split(':', 1) for rn in results.strip().split()]\n\n        log.msg('hgpoller: processing %d changes: %r in %r'\n                % (len(revNodeList), revNodeList, self._absWorkdir()))\n        for rev, node in revNodeList:\n            timestamp, author, files, comments = yield self._getRevDetails(\n                node)\n            yield self.master.data.updates.addChange(\n                author=author,\n                revision=unicode(node),\n                files=files,\n                comments=comments,\n                when_timestamp=int(timestamp),\n                branch=ascii2unicode(self.branch),\n                category=ascii2unicode(self.category),\n                project=ascii2unicode(self.project),\n                repository=ascii2unicode(self.repourl),\n                src=u'hg')\n            # writing after addChange so that a rev is never missed,\n            # but at once to avoid impact from later errors\n            yield self._setCurrentRev(rev, oid=oid)\n\n    def _processChangesFailure(self, f):\n        log.msg('hgpoller: repo poll failed')\n        log.err(f)\n        # eat the failure to continue along the deferred chain - we still want to catch up\n        return None\n\n    def _convertNonZeroToFailure(self, res):\n        \"utility method to handle the result of getProcessOutputAndValue\"\n        (stdout, stderr, code) = res\n        if code != 0:\n            raise EnvironmentError('command failed with exit code %d: %s' % (code, stderr))\n        return (stdout, stderr, code)\n\n    def _stopOnFailure(self, f):\n        \"utility method to stop the service when a failure occurs\"\n        if self.running:\n            d = defer.maybeDeferred(lambda: self.stopService())\n            d.addErrback(log.err, 'while stopping broken HgPoller service')\n        return f\n", "path": "master/buildbot/changes/hgpoller.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nimport os\nimport time\n\nfrom twisted.internet import defer\nfrom twisted.internet import utils\nfrom twisted.python import log\n\nfrom buildbot import config\nfrom buildbot.changes import base\nfrom buildbot.util import ascii2unicode\nfrom buildbot.util import deferredLocked\n\n\nclass HgPoller(base.PollingChangeSource):\n\n    \"\"\"This source will poll a remote hg repo for changes and submit\n    them to the change master.\"\"\"\n\n    compare_attrs = (\"repourl\", \"branch\", \"workdir\",\n                     \"pollInterval\", \"hgpoller\", \"usetimestamps\",\n                     \"category\", \"project\", \"pollAtLaunch\")\n\n    db_class_name = 'HgPoller'\n\n    def __init__(self, repourl, branch='default',\n                 workdir=None, pollInterval=10 * 60,\n                 hgbin='hg', usetimestamps=True,\n                 category=None, project='', pollinterval=-2,\n                 encoding='utf-8', name=None, pollAtLaunch=False):\n\n        # for backward compatibility; the parameter used to be spelled with 'i'\n        if pollinterval != -2:\n            pollInterval = pollinterval\n\n        if name is None:\n            name = \"%s[%s]\" % (repourl, branch)\n\n        self.repourl = repourl\n        self.branch = branch\n        base.PollingChangeSource.__init__(\n            self, name=name, pollInterval=pollInterval, pollAtLaunch=pollAtLaunch)\n        self.encoding = encoding\n        self.lastChange = time.time()\n        self.lastPoll = time.time()\n        self.hgbin = hgbin\n        self.workdir = workdir\n        self.usetimestamps = usetimestamps\n        self.category = category\n        self.project = project\n        self.commitInfo = {}\n        self.initLock = defer.DeferredLock()\n\n        if self.workdir is None:\n            config.error(\"workdir is mandatory for now in HgPoller\")\n\n    def describe(self):\n        status = \"\"\n        if not self.master:\n            status = \"[STOPPED - check log]\"\n        return (\"HgPoller watching the remote Mercurial repository %r, \"\n                \"branch: %r, in workdir %r %s\") % (self.repourl, self.branch,\n                                                   self.workdir, status)\n\n    @deferredLocked('initLock')\n    def poll(self):\n        d = self._getChanges()\n        d.addCallback(self._processChanges)\n        d.addErrback(self._processChangesFailure)\n        return d\n\n    def _absWorkdir(self):\n        workdir = self.workdir\n        if os.path.isabs(workdir):\n            return workdir\n        return os.path.join(self.master.basedir, workdir)\n\n    def _getRevDetails(self, rev):\n        \"\"\"Return a deferred for (date, author, files, comments) of given rev.\n\n        Deferred will be in error if rev is unknown.\n        \"\"\"\n        args = ['log', '-r', rev, os.linesep.join((\n            '--template={date|hgdate}',\n            '{author}',\n            \"{files % '{file}\" + os.pathsep + \"'}\",\n            '{desc|strip}'))]\n        # Mercurial fails with status 255 if rev is unknown\n        d = utils.getProcessOutput(self.hgbin, args, path=self._absWorkdir(),\n                                   env=os.environ, errortoo=False)\n\n        def process(output):\n            # all file names are on one line\n            date, author, files, comments = output.decode(self.encoding, \"replace\").split(\n                os.linesep, 3)\n\n            if not self.usetimestamps:\n                stamp = None\n            else:\n                try:\n                    stamp = float(date.split()[0])\n                except:\n                    log.msg('hgpoller: caught exception converting output %r '\n                            'to timestamp' % date)\n                    raise\n            return stamp, author.strip(), files.split(os.pathsep)[:-1], comments.strip()\n\n        d.addCallback(process)\n        return d\n\n    def _isRepositoryReady(self):\n        \"\"\"Easy to patch in tests.\"\"\"\n        return os.path.exists(os.path.join(self._absWorkdir(), '.hg'))\n\n    def _initRepository(self):\n        \"\"\"Have mercurial init the workdir as a repository (hg init) if needed.\n\n        hg init will also create all needed intermediate directories.\n        \"\"\"\n        if self._isRepositoryReady():\n            return defer.succeed(None)\n        log.msg('hgpoller: initializing working dir from %s' % self.repourl)\n        d = utils.getProcessOutputAndValue(self.hgbin,\n                                           ['init', self._absWorkdir()],\n                                           env=os.environ)\n        d.addCallback(self._convertNonZeroToFailure)\n        d.addErrback(self._stopOnFailure)\n        d.addCallback(lambda _: log.msg(\n            \"hgpoller: finished initializing working dir %r\" % self.workdir))\n        return d\n\n    def _getChanges(self):\n        self.lastPoll = time.time()\n\n        d = self._initRepository()\n        d.addCallback(lambda _: log.msg(\n            \"hgpoller: polling hg repo at %s\" % self.repourl))\n\n        # get a deferred object that performs the fetch\n        args = ['pull', '-b', self.branch, self.repourl]\n\n        # This command always produces data on stderr, but we actually do not\n        # care about the stderr or stdout from this command.\n        # We set errortoo=True to avoid an errback from the deferred.\n        # The callback which will be added to this\n        # deferred will not use the response.\n        d.addCallback(lambda _: utils.getProcessOutput(\n            self.hgbin, args, path=self._absWorkdir(),\n            env=os.environ, errortoo=True))\n\n        return d\n\n    def _getStateObjectId(self):\n        \"\"\"Return a deferred for object id in state db.\n\n        Being unique among pollers, workdir is used with branch as instance\n        name for db.\n        \"\"\"\n        return self.master.db.state.getObjectId(\n            '#'.join((self.workdir, self.branch)), self.db_class_name)\n\n    def _getCurrentRev(self):\n        \"\"\"Return a deferred for object id in state db and current numeric rev.\n\n        If never has been set, current rev is None.\n        \"\"\"\n        d = self._getStateObjectId()\n\n        def oid_cb(oid):\n            d = self.master.db.state.getState(oid, 'current_rev', None)\n\n            def addOid(cur):\n                if cur is not None:\n                    return oid, int(cur)\n                return oid, cur\n            d.addCallback(addOid)\n            return d\n        d.addCallback(oid_cb)\n        return d\n\n    def _setCurrentRev(self, rev, oid=None):\n        \"\"\"Return a deferred to set current revision in persistent state.\n\n        oid is self's id for state db. It can be passed to avoid a db lookup.\"\"\"\n        if oid is None:\n            d = self._getStateObjectId()\n        else:\n            d = defer.succeed(oid)\n\n        def set_in_state(obj_id):\n            return self.master.db.state.setState(obj_id, 'current_rev', rev)\n        d.addCallback(set_in_state)\n\n        return d\n\n    def _getHead(self):\n        \"\"\"Return a deferred for branch head revision or None.\n\n        We'll get an error if there is no head for this branch, which is\n        proabably a good thing, since it's probably a mispelling\n        (if really buildbotting a branch that does not have any changeset\n        yet, one shouldn't be surprised to get errors)\n        \"\"\"\n        d = utils.getProcessOutput(self.hgbin,\n                                   ['heads', self.branch, '--template={rev}' + os.linesep],\n                                   path=self._absWorkdir(), env=os.environ, errortoo=False)\n\n        def no_head_err(exc):\n            log.err(\"hgpoller: could not find branch %r in repository %r\" % (\n                self.branch, self.repourl))\n        d.addErrback(no_head_err)\n\n        def results(heads):\n            if not heads:\n                return\n\n            if len(heads.split()) > 1:\n                log.err((\"hgpoller: caught several heads in branch %r \"\n                         \"from repository %r. Staying at previous revision\"\n                         \"You should wait until the situation is normal again \"\n                         \"due to a merge or directly strip if remote repo \"\n                         \"gets stripped later.\") % (self.branch, self.repourl))\n                return\n\n            # in case of whole reconstruction, are we sure that we'll get the\n            # same node -> rev assignations ?\n            return int(heads.strip())\n\n        d.addCallback(results)\n        return d\n\n    @defer.inlineCallbacks\n    def _processChanges(self, unused_output):\n        \"\"\"Send info about pulled changes to the master and record current.\n\n        GitPoller does the recording by moving the working dir to the head\n        of the branch.\n        We don't update the tree (unnecessary treatment and waste of space)\n        instead, we simply store the current rev number in a file.\n        Recall that hg rev numbers are local and incremental.\n        \"\"\"\n        oid, current = yield self._getCurrentRev()\n        # hg log on a range of revisions is never empty\n        # also, if a numeric revision does not exist, a node may match.\n        # Therefore, we have to check explicitely that branch head > current.\n        head = yield self._getHead()\n        if head <= current:\n            return\n        if current is None:\n            # we could have used current = -1 convention as well (as hg does)\n            revrange = '%d:%d' % (head, head)\n        else:\n            revrange = '%d:%s' % (current + 1, head)\n\n        # two passes for hg log makes parsing simpler (comments is multi-lines)\n        revListArgs = ['log', '-b', self.branch, '-r', revrange,\n                       r'--template={rev}:{node}\\n']\n        results = yield utils.getProcessOutput(self.hgbin, revListArgs,\n                                               path=self._absWorkdir(), env=os.environ, errortoo=False)\n\n        revNodeList = [rn.split(':', 1) for rn in results.strip().split()]\n\n        log.msg('hgpoller: processing %d changes: %r in %r'\n                % (len(revNodeList), revNodeList, self._absWorkdir()))\n        for rev, node in revNodeList:\n            timestamp, author, files, comments = yield self._getRevDetails(\n                node)\n            yield self.master.data.updates.addChange(\n                author=author,\n                revision=unicode(node),\n                files=files,\n                comments=comments,\n                when_timestamp=int(timestamp),\n                branch=ascii2unicode(self.branch),\n                category=ascii2unicode(self.category),\n                project=ascii2unicode(self.project),\n                repository=ascii2unicode(self.repourl),\n                src=u'hg')\n            # writing after addChange so that a rev is never missed,\n            # but at once to avoid impact from later errors\n            yield self._setCurrentRev(rev, oid=oid)\n\n    def _processChangesFailure(self, f):\n        log.msg('hgpoller: repo poll failed')\n        log.err(f)\n        # eat the failure to continue along the deferred chain - we still want to catch up\n        return None\n\n    def _convertNonZeroToFailure(self, res):\n        \"utility method to handle the result of getProcessOutputAndValue\"\n        (stdout, stderr, code) = res\n        if code != 0:\n            raise EnvironmentError('command failed with exit code %d: %s' % (code, stderr))\n        return (stdout, stderr, code)\n\n    def _stopOnFailure(self, f):\n        \"utility method to stop the service when a failure occurs\"\n        if self.running:\n            d = defer.maybeDeferred(lambda: self.stopService())\n            d.addErrback(log.err, 'while stopping broken HgPoller service')\n        return f\n", "path": "master/buildbot/changes/hgpoller.py"}]}