Correct removal of faildump
[mdk.git] / mdk / moodle.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Moodle Development Kit
6
7 Copyright (c) 2012 Frédéric Massart - FMCorz.net
8
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 http://github.com/FMCorz/mdk
23 """
24
25 import os
26 import re
27 import logging
28 import shutil
29 from tempfile import gettempdir
30
31 from .tools import getMDLFromCommitMessage, mkdir, process, parseBranch
32 from .db import DB
33 from .config import Conf
34 from .git import Git, GitException
35 from .exceptions import InstallException, UpgradeNotAllowed
36 from .jira import Jira, JiraException
37 from .scripts import Scripts
38
39 C = Conf()
40
41
42 class Moodle(object):
43
44 identifier = None
45 path = None
46 installed = False
47 version = None
48 config = None
49
50 _dbo = None
51 _git = None
52 _loaded = False
53
54 _cos_hasstash = False
55 _cos_oldbranch = None
56
57 _reservedKeywords = [
58 'branch',
59 'identifier',
60 'installed',
61 'integration',
62 'maturity',
63 'path',
64 'release',
65 'stablebranch',
66 'version'
67 ]
68
69 def __init__(self, path, identifier=None):
70 self.path = path
71 self.identifier = identifier
72 self.version = {}
73 self.config = {}
74 self._load()
75
76 def addConfig(self, name, value):
77 """Add a parameter to the config file
78 Will attempt to write them before the inclusion of lib/setup.php"""
79 configFile = os.path.join(self.path, 'config.php')
80 if not os.path.isfile(configFile):
81 return None
82
83 if name in self._reservedKeywords:
84 raise Exception('Cannot use reserved keywords for settings in config.php')
85
86 if type(value) == bool:
87 value = 'true' if value else 'false'
88 elif type(value) != int:
89 value = "'" + str(value) + "'"
90 value = str(value)
91
92 try:
93 f = open(configFile, 'r')
94 lines = f.readlines()
95 f.close()
96
97 for i, line in enumerate(lines):
98 if re.search(r'^// MDK Edit\.$', line.rstrip()):
99 break
100 elif re.search(r'require_once.*/lib/setup\.php', line):
101 lines.insert(i, '// MDK Edit.\n')
102 lines.insert(i + 1, '\n')
103 # As we've added lines, let's move the index
104 break
105
106 i += 1
107 if i > len(lines):
108 i = len(lines)
109 lines.insert(i, '$CFG->%s = %s;\n' % (name, value))
110
111 f = open(configFile, 'w')
112 f.writelines(lines)
113 f.close()
114 except:
115 raise Exception('Error while writing to config file')
116
117 self.reload()
118
119 def branch_compare(self, branch, compare='>='):
120 """Compare the branch of the current instance with the one passed"""
121 try:
122 branch = int(branch)
123 except:
124 raise Exception('Could not convert branch to int, got %s' % branch)
125 b = self.get('branch')
126 if b == None:
127 raise Exception('Error while reading the branch')
128 elif b == 'master':
129 b = C.get('masterBranch')
130 b = int(b)
131 if compare == '>=':
132 return b >= branch
133 elif compare == '>':
134 return b > branch
135 elif compare == '=' or compare == '==':
136 return b == branch
137 if compare == '<=':
138 return b <= branch
139 elif compare == '<':
140 return b < branch
141 return False
142
143 def checkout_stable(self, checkout=True):
144 """Checkout the stable branch, do a stash if required. Needs to be called again to pop the stash!"""
145
146 # Checkout the branch
147 if checkout:
148 stablebranch = self.get('stablebranch')
149 if self.currentBranch() == stablebranch:
150 self._cos_oldbranch = None
151 return True
152
153 self._cos_oldbranch = self.currentBranch()
154 self._cos_hasstash = False
155
156 # Stash
157 stash = self.git().stash(untracked=True)
158 if stash[0] != 0:
159 raise Exception('Error while stashing your changes')
160 if not stash[1].startswith('No local changes'):
161 self._cos_hasstash = True
162
163 # Checkout STABLE
164 if not self.git().checkout(stablebranch):
165 raise Exception('Could not checkout %s' % stablebranch)
166
167 # Checkout the previous branch
168 elif self._cos_oldbranch != None:
169 if not self.git().checkout(self._cos_oldbranch):
170 raise Exception('Could not checkout working branch %s' % self._cos_oldbranch)
171
172 # Unstash
173 if self._cos_hasstash:
174 pop = self.git().stash('pop')
175 if pop[0] != 0:
176 raise Exception('Error while popping the stash. Probably got conflicts.')
177 self._cos_hasstash = False
178
179 def cli(self, cli, args='', **kwargs):
180 """Executes a command line tool script"""
181 cli = os.path.join(self.get('path'), cli.lstrip('/'))
182 if not os.path.isfile(cli):
183 raise Exception('Could not find script to call')
184 if type(args) == 'list':
185 args = ' '.join(args)
186 cmd = '%s %s %s' % (C.get('php'), cli, args)
187 return process(cmd, cwd=self.get('path'), **kwargs)
188
189 def currentBranch(self):
190 """Returns the current branch on the git repository"""
191 return self.git().currentBranch()
192
193 def dbo(self):
194 """Returns a Database object"""
195 if self._dbo == None:
196 engine = self.get('dbtype')
197 db = self.get('dbname')
198 if engine != None and db != None:
199 try:
200 self._dbo = DB(engine, C.get('db.%s' % engine))
201 except:
202 pass
203 return self._dbo
204
205 def generateBranchName(self, issue, suffix='', version=''):
206 """Generates a branch name"""
207 mdl = re.sub(r'(MDL|mdl)(-|_)?', '', issue)
208 if version == '':
209 version = self.get('branch')
210 args = {
211 'issue': mdl,
212 'version': version
213 }
214 branch = C.get('wording.branchFormat') % args
215 if suffix != None and suffix != '':
216 branch += C.get('wording.branchSuffixSeparator') + suffix
217 return branch
218
219 def get(self, param, default=None):
220 """Returns a property of this instance"""
221 info = self.info()
222 try:
223 return info[param]
224 except:
225 return default
226
227 def git(self):
228 """Returns a Git object"""
229 if self._git == None:
230 self._git = Git(self.path, C.get('git'))
231 if not self._git.isRepository():
232 raise Exception('Could not find the Git repository')
233 return self._git
234
235 def headcommit(self, branch=None):
236 """Try to resolve the head commit of branch of this instance"""
237
238 if branch == None:
239 branch = self.currentBranch()
240 if branch == 'HEAD':
241 raise Exception('Cannot update the tracker when on detached branch')
242
243 smartSearch = C.get('smartHeadCommitSearch')
244
245 # Parsing the branch
246 parsedbranch = parseBranch(branch)
247 if parsedbranch:
248 issue = 'MDL-%s' % (parsedbranch['issue'])
249 else:
250 logging.debug('Cannot smart resolve using the branch %s' % (branch))
251 smartSearch = False
252
253 headcommit = None
254 try:
255 # Trying to smart guess the last commit needed
256 if smartSearch:
257 commits = self.git().log(since=branch, count=C.get('smartHeadCommitLimit'), format='%s_____%h').split('\n')[:-1]
258
259 # Looping over the last commits to find the commit messages that match the MDL-12345.
260 candidate = None
261 for commit in commits:
262 match = getMDLFromCommitMessage(commit) == issue
263 if not candidate and not match:
264 # The first commit does not match a hash, let's ignore this method.
265 break
266 candidate = commit.split('_____')[-1]
267 if not match:
268 # The commit does not match any more, we found it!
269 headcommit = candidate
270 break
271
272 # We could not smart find the last commit, let's use the default mechanism.
273 if not headcommit:
274 upstreamremote = C.get('upstreamRemote')
275 stablebranch = self.get('stablebranch')
276 headcommit = self.git().hashes(ref='%s/%s' % (upstreamremote, stablebranch), limit=1, format='%h')[0]
277
278 except GitException:
279 logging.warning('Could not resolve the head commit')
280 headcommit = False
281
282 return headcommit
283
284 def initPHPUnit(self, force=False, prefix=None):
285 """Initialise the PHPUnit environment"""
286 raise Exception('This method is deprecated, use phpunit.PHPUnit.init() instead.')
287
288 def initBehat(self, switchcompletely=False, force=False, prefix=None, faildumppath=None):
289 """Initialise the Behat environment"""
290
291 if self.branch_compare(25, '<'):
292 raise Exception('Behat is only available from Moodle 2.5')
293
294 # Force switch completely for PHP < 5.4
295 (none, phpVersion, none) = process('%s -r "echo version_compare(phpversion(), \'5.4\');"' % (C.get('php')))
296 if int(phpVersion) <= 0:
297 switchcompletely = True
298
299 # Set Behat data root
300 behat_dataroot = self.get('dataroot') + '_behat'
301 self.updateConfig('behat_dataroot', behat_dataroot)
302
303 # Set Behat DB prefix
304 currentPrefix = self.get('behat_prefix')
305 behat_prefix = prefix or 'zbehat_'
306
307 # Set behat_faildump_path
308 currentFailDumpPath = self.get('behat_faildump_path')
309 if faildumppath and currentFailDumpPath != faildumppath:
310 self.updateConfig('behat_faildump_path', faildumppath)
311 elif (not faildumppath and currentFailDumpPath):
312 self.removeConfig('behat_faildump_path')
313
314 if not currentPrefix or force:
315 self.updateConfig('behat_prefix', behat_prefix)
316 elif currentPrefix != behat_prefix and self.get('dbtype') != 'oci':
317 # Warn that a prefix is already set and we did not change it.
318 # No warning for Oracle as we need to set it to something else.
319 logging.warning('Behat prefix not changed, already set to \'%s\', expected \'%s\'.' % (currentPrefix, behat_prefix))
320
321 # Switch completely?
322 if self.branch_compare(26, '<'):
323 if switchcompletely:
324 self.updateConfig('behat_switchcompletely', switchcompletely)
325 self.updateConfig('behat_wwwroot', self.get('wwwroot'))
326 else:
327 self.removeConfig('behat_switchcompletely')
328 self.removeConfig('behat_wwwroot')
329 else:
330 # Defining wwwroot.
331 wwwroot = '%s://%s/' % (C.get('scheme'), C.get('behat.host'))
332 if C.get('path') != '' and C.get('path') != None:
333 wwwroot = wwwroot + C.get('path') + '/'
334 wwwroot = wwwroot + self.identifier
335 currentWwwroot = self.get('behat_wwwroot')
336 if not currentWwwroot or force:
337 self.updateConfig('behat_wwwroot', wwwroot)
338 elif currentWwwroot != wwwroot:
339 logging.warning('Behat wwwroot not changed, already set to \'%s\', expected \'%s\'.' % (currentWwwroot, wwwroot))
340
341 # Force a cache purge
342 self.purge()
343
344 # Force dropping the tables if there are any.
345 if force:
346 result = self.cli('admin/tool/behat/cli/util.php', args='--drop', stdout=None, stderr=None)
347 if result[0] != 0:
348 raise Exception('Error while initialising Behat. Please try manually.')
349
350 # Run the init script.
351 result = self.cli('admin/tool/behat/cli/init.php', stdout=None, stderr=None)
352 if result[0] != 0:
353 raise Exception('Error while initialising Behat. Please try manually.')
354
355 # Force a cache purge
356 self.purge()
357
358 def info(self):
359 """Returns a dictionary of information about this instance"""
360 self._load()
361 info = {
362 'path': self.path,
363 'installed': self.isInstalled(),
364 'identifier': self.identifier
365 }
366 for (k, v) in self.config.items():
367 info[k] = v
368 for (k, v) in self.version.items():
369 info[k] = v
370 return info
371
372 def install(self, dbname=None, engine=None, dataDir=None, fullname=None, dropDb=False, wwwroot=None):
373 """Launch the install script of an Instance"""
374
375 if self.isInstalled():
376 raise InstallException('Instance already installed!')
377
378 if not wwwroot:
379 raise InstallException('Cannot install without a value for wwwroot')
380 if dataDir == None or not os.path.isdir(dataDir):
381 raise InstallException('Cannot install instance without knowing where the data directory is')
382 if dbname == None:
383 dbname = re.sub(r'[^a-zA-Z0-9]', '', self.identifier).lower()
384 prefixDbname = C.get('db.namePrefix')
385 if prefixDbname:
386 dbname = prefixDbname + dbname
387 dbname = dbname[:28]
388 if engine == None:
389 engine = C.get('defaultEngine')
390 if fullname == None:
391 fullname = self.identifier.replace('-', ' ').replace('_', ' ').title()
392 fullname = fullname + ' ' + C.get('wording.%s' % engine)
393
394 logging.info('Creating database...')
395 db = DB(engine, C.get('db.%s' % engine))
396 if db.dbexists(dbname):
397 if dropDb:
398 db.dropdb(dbname)
399 db.createdb(dbname)
400 else:
401 raise InstallException('Cannot install an instance on an existing database (%s)' % dbname)
402 else:
403 db.createdb(dbname)
404 db.selectdb(dbname)
405
406 logging.info('Installing %s...' % self.identifier)
407 cli = 'admin/cli/install.php'
408 params = (wwwroot, dataDir, engine, dbname, C.get('db.%s.user' % engine), C.get('db.%s.passwd' % engine), C.get('db.%s.host' % engine), fullname, self.identifier, C.get('login'), C.get('passwd'))
409 args = '--wwwroot="%s" --dataroot="%s" --dbtype="%s" --dbname="%s" --dbuser="%s" --dbpass="%s" --dbhost="%s" --fullname="%s" --shortname="%s" --adminuser="%s" --adminpass="%s" --allow-unstable --agree-license --non-interactive' % params
410 result = self.cli(cli, args, stdout=None, stderr=None)
411 if result[0] != 0:
412 raise InstallException('Error while running the install, please manually fix the problem.\n- Command was: %s %s %s' % (C.get('php'), cli, args))
413
414 configFile = os.path.join(self.path, 'config.php')
415 os.chmod(configFile, 0666)
416 try:
417 if C.get('path') != '' and C.get('path') != None:
418 self.addConfig('sessioncookiepath', '/%s/%s/' % (C.get('path'), self.identifier))
419 else:
420 self.addConfig('sessioncookiepath', '/%s/' % self.identifier)
421 except Exception:
422 logging.warning('Could not append $CFG->sessioncookiepath to config.php')
423
424 # Add forced $CFG to the config.php if some are globally defined.
425 forceCfg = C.get('forceCfg')
426 if isinstance(forceCfg, dict):
427 for cfgKey, cfgValue in forceCfg.iteritems():
428 try:
429 logging.info('Setting up forced $CFG->%s to \'%s\' in config.php', cfgKey, cfgValue)
430 self.addConfig(cfgKey, cfgValue)
431 except Exception:
432 logging.warning('Could not append $CFG->%s to config.php', cfgKey)
433
434 self.reload()
435
436 def isInstalled(self):
437 """Returns whether this instance is installed or not"""
438 # Reload the configuration if necessary.
439 self._load()
440 return self.installed == True
441
442 @staticmethod
443 def isInstance(path):
444 """Check whether the path is a Moodle web directory"""
445 version = os.path.join(path, 'version.php')
446 try:
447 f = open(version, 'r')
448 lines = f.readlines()
449 f.close()
450 except:
451 return False
452 found = False
453 for line in lines:
454 if line.find('MOODLE VERSION INFORMATION') > -1:
455 found = True
456 break
457 if not found:
458 return False
459
460 return True
461
462 def isIntegration(self):
463 """Returns whether an instance is an integration one or not"""
464 r = C.get('upstreamRemote') or 'upstream'
465 if not self.git().getRemote(r):
466 r = 'origin'
467 remote = self.git().getConfig('remote.%s.url' % r)
468 if remote != None and remote.endswith('integration.git'):
469 return True
470 return False
471
472 def isStable(self):
473 """Assume an instance is stable if not integration"""
474 return not self.isIntegration()
475
476 def _load(self):
477 """Loads the information"""
478 if not self.isInstance(self.path):
479 return False
480
481 if self._loaded:
482 return True
483
484 # Extracts information from version.php
485 self.version = {}
486 version = os.path.join(self.path, 'version.php')
487 if os.path.isfile(version):
488
489 reVersion = re.compile(r'^\s*\$version\s*=\s*([0-9.]+)\s*;')
490 reRelease = re.compile(r'^\s*\$release\s*=\s*(?P<brackets>[\'"])?(.+)(?P=brackets)\s*;')
491 reMaturity = re.compile(r'^\s*\$maturity\s*=\s*([a-zA-Z0-9_]+)\s*;')
492 reBranch = re.compile(r'^\s*\$branch\s*=\s*(?P<brackets>[\'"])?([0-9]+)(?P=brackets)\s*;')
493
494 f = open(version, 'r')
495 for line in f:
496 if reVersion.search(line):
497 self.version['version'] = reVersion.search(line).group(1)
498 elif reRelease.search(line):
499 self.version['release'] = reRelease.search(line).group(2)
500 elif reMaturity.search(line):
501 self.version['maturity'] = reMaturity.search(line).group(1).replace('MATURITY_', '').lower()
502 elif reBranch.search(line):
503 self.version['branch'] = reBranch.search(line).group(2)
504
505 # Several checks about the branch
506 try:
507 # Has it been set?
508 branch = self.version['branch']
509 except:
510 self.version['branch'] = self.version['release'].replace('.', '')[0:2]
511 branch = self.version['branch']
512 if int(branch) >= int(C.get('masterBranch')):
513 self.version['branch'] = 'master'
514
515 # Stable branch
516 if self.version['branch'] == 'master':
517 self.version['stablebranch'] = 'master'
518 else:
519 self.version['stablebranch'] = 'MOODLE_%s_STABLE' % self.version['branch']
520
521 # Integration or stable?
522 self.version['integration'] = self.isIntegration()
523
524 f.close()
525 else:
526 # Should never happen
527 raise Exception('This does not appear to be a Moodle instance')
528
529 # Extracts parameters from config.php, does not handle params over multiple lines
530 self.config = {}
531 config = os.path.join(self.path, 'config.php')
532 if os.path.isfile(config):
533 self.installed = True
534 prog = re.compile(r'^\s*\$CFG->([a-z_]+)\s*=\s*((?P<brackets>[\'"])?(.+)(?P=brackets)|([0-9.]+)|(true|false|null))\s*;$', re.I)
535 try:
536 f = open(config, 'r')
537 for line in f:
538 match = prog.search(line)
539 if match == None:
540 continue
541
542 if match.group(5) != None:
543 # Number
544 value = float(match.group(5)) if '.' in str(match.group(5)) else int(match.group(5))
545 elif match.group(6) != None:
546 # Boolean or null
547 value = str(match.group(6)).lower()
548 if value == 'true':
549 value = True
550 elif value == 'false':
551 value = False
552 else:
553 value = None
554 else:
555 # Likely to be a string
556 value = match.group(4)
557
558 self.config[match.group(1)] = value
559
560 f.close()
561
562 except IOError:
563 self.installed = False
564 logging.error('Could not read config file')
565
566 else:
567 self.installed = False
568
569 self._loaded = True
570 return True
571
572 def purge(self, manual=False):
573 """Purge the cache of an instance"""
574 if not self.isInstalled():
575 raise Exception('Instance not installed, cannot purge.')
576 elif self.branch_compare('22', '<'):
577 raise Exception('Instance does not support cache purging.')
578
579 try:
580 dataroot = self.get('dataroot', False)
581 if manual and dataroot != False:
582 logging.debug('Removing directories [dataroot]/cache and [dataroot]/localcache')
583 shutil.rmtree(os.path.join(dataroot, 'cache'), True)
584 shutil.rmtree(os.path.join(dataroot, 'localcache'), True)
585
586 self.cli('admin/cli/purge_caches.php', stderr=None, stdout=None)
587
588 except Exception:
589 raise Exception('Error while purging cache!')
590
591 def pushPatch(self, branch=None):
592 """Push a patch on the tracker, and remove the previous one"""
593
594 if branch == None:
595 branch = self.currentBranch()
596 if branch == 'HEAD':
597 raise Exception('Cannot create a patch from a detached branch')
598
599 # Parsing the branch
600 parsedbranch = parseBranch(branch)
601 if not parsedbranch:
602 raise Exception('Could not extract issue number from %s' % branch)
603 issue = 'MDL-%s' % (parsedbranch['issue'])
604 headcommit = self.headcommit(branch)
605
606 # Creating a patch file.
607 fileName = branch + '.mdk.patch'
608 tmpPatchFile = os.path.join(gettempdir(), fileName)
609 if self.git().createPatch('%s...%s' % (headcommit, branch), saveTo=tmpPatchFile):
610
611 J = Jira()
612
613 # Checking if file with same name exists.
614 existingAttachmentId = None
615 existingAttachments = J.getIssue(issue, fields='attachment')
616 for existingAttachment in existingAttachments.get('fields', {}).get('attachment', {}):
617 if existingAttachment.get('filename') == fileName:
618 # Found an existing attachment with the same name, we keep track of it.
619 existingAttachmentId = existingAttachment.get('id')
620 break
621
622 # Pushing patch to the tracker.
623 try:
624 logging.info('Uploading %s to the tracker' % (fileName))
625 J.upload(issue, tmpPatchFile)
626 except JiraException:
627 logging.error('Error while uploading the patch to the tracker')
628 return False
629 else:
630 if existingAttachmentId != None:
631 # On success, deleting file that was there before.
632 try:
633 logging.info('Deleting older patch...')
634 J.deleteAttachment(existingAttachmentId)
635 except JiraException:
636 logging.info('Could not delete older attachment')
637
638 else:
639 logging.error('Could not create a patch file')
640 return False
641
642 return True
643
644 def reload(self):
645 """Sets the value to be reloaded"""
646 self._loaded = False
647
648 def removeConfig(self, name):
649 """Remove a configuration setting from the config file."""
650 configFile = os.path.join(self.path, 'config.php')
651 if not os.path.isfile(configFile):
652 return None
653
654 try:
655 f = open(configFile, 'r')
656 lines = f.readlines()
657 f.close()
658
659 for line in lines:
660 if re.search(r'\$CFG->%s\s*=.*;' % (name), line):
661 lines.remove(line)
662 break
663
664 f = open(configFile, 'w')
665 f.writelines(lines)
666 f.close()
667 except:
668 raise Exception('Error while writing to config file')
669
670 self.reload()
671
672 def runScript(self, scriptname, arguments=None, **kwargs):
673 """Runs a script on the instance"""
674 return Scripts.run(scriptname, self.get('path'), arguments=arguments, cmdkwargs=kwargs)
675
676 def update(self, remote=None):
677 """Update the instance from the remote"""
678
679 if remote == None:
680 remote = C.get('upstreamRemote')
681
682 # Fetch
683 if not self.git().fetch(remote):
684 raise Exception('Could not fetch remote %s' % remote)
685
686 # Checkout stable
687 self.checkout_stable(True)
688
689 # Reset HARD
690 upstream = '%s/%s' % (remote, self.get('stablebranch'))
691 if not self.git().reset(to=upstream, hard=True):
692 raise Exception('Error while executing git reset.')
693
694 # Return to previous branch
695 self.checkout_stable(False)
696
697 def updateConfig(self, name, value):
698 """Update a setting in the config file."""
699 self.removeConfig(name)
700 self.addConfig(name, value)
701
702 def uninstall(self):
703 """Uninstall the instance"""
704
705 if not self.isInstalled():
706 raise Exception('The instance is not installed')
707
708 # Delete the content in moodledata
709 dataroot = self.get('dataroot')
710 if os.path.isdir(dataroot):
711 logging.debug('Deleting dataroot content (%s)' % (dataroot))
712 shutil.rmtree(dataroot)
713 mkdir(dataroot, 0777)
714
715 # Drop the database
716 dbname = self.get('dbname')
717 if self.dbo().dbexists(dbname):
718 logging.debug('Droping database (%s)' % (dbname))
719 self.dbo().dropdb(dbname)
720
721 # Remove the config file
722 configFile = os.path.join(self.get('path'), 'config.php')
723 if os.path.isfile(configFile):
724 logging.debug('Deleting config.php')
725 os.remove(configFile)
726
727 def updateTrackerGitInfo(self, branch=None, ref=None):
728 """Updates the git info on the tracker issue"""
729
730 if branch == None:
731 branch = self.currentBranch()
732 if branch == 'HEAD':
733 raise Exception('Cannot update the tracker when on detached branch')
734
735 # Parsing the branch
736 parsedbranch = parseBranch(branch)
737 if not parsedbranch:
738 raise Exception('Could not extract issue number from %s' % branch)
739 issue = 'MDL-%s' % (parsedbranch['issue'])
740 version = parsedbranch['version']
741
742 # Get the jira config
743 repositoryurl = C.get('repositoryUrl')
744 diffurltemplate = C.get('diffUrlTemplate')
745 stablebranch = self.get('stablebranch')
746
747 # Get the hash of the last upstream commit
748 headcommit = None
749 logging.info('Searching for the head commit...')
750 if ref:
751 try:
752 headcommit = self.git().hashes(ref=ref, limit=1, format='%h')[0]
753 except GitException:
754 logging.warning('Could not resolve a head commit using the reference: %s' % (ref))
755 headcommit = None
756
757 # No reference was passed, or it was invalid.
758 if not headcommit:
759 headcommit = self.headcommit(branch)
760
761 # Head commit not resolved
762 if not headcommit:
763 logging.error('Head commit not resolved, aborting update of tracker fields')
764 return False
765
766 logging.debug('Head commit resolved to %s' % (headcommit))
767
768 J = Jira()
769 diffurl = diffurltemplate.replace('%branch%', branch).replace('%stablebranch%', stablebranch).replace('%headcommit%', headcommit)
770
771 fieldrepositoryurl = C.get('tracker.fieldnames.repositoryurl')
772 fieldbranch = C.get('tracker.fieldnames.%s.branch' % version)
773 fielddiffurl = C.get('tracker.fieldnames.%s.diffurl' % version)
774
775 if not fieldrepositoryurl or not fieldbranch or not fielddiffurl:
776 logging.error('Cannot set tracker fields for this version (%s). The field names are not set in the config file.', version)
777 else:
778 logging.info('Setting tracker fields: \n %s: %s \n %s: %s \n %s: %s' %
779 (fieldrepositoryurl, repositoryurl, fieldbranch, branch, fielddiffurl, diffurl))
780 J.setCustomFields(issue, {fieldrepositoryurl: repositoryurl, fieldbranch: branch, fielddiffurl: diffurl})
781
782 def upgrade(self, nocheckout=False):
783 """Calls the upgrade script"""
784 if not self.isInstalled():
785 raise Exception('Cannot upgrade an instance which is not installed.')
786 elif not self.branch_compare(20):
787 raise Exception('Upgrade command line tool not supported by this version.')
788 elif os.path.isfile(os.path.join(self.get('path'), '.noupgrade')):
789 raise UpgradeNotAllowed('Upgrade not allowed, found .noupgrade.')
790
791 # Checkout stable
792 if not nocheckout:
793 self.checkout_stable(True)
794
795 cli = '/admin/cli/upgrade.php'
796 args = '--non-interactive --allow-unstable'
797 result = self.cli(cli, args, stdout=None, stderr=None)
798 if result[0] != 0:
799 raise Exception('Error while running the upgrade.')
800
801 # Return to previous branch
802 if not nocheckout:
803 self.checkout_stable(False)