WIP vhost support
[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).replace('%instancename%', self.identifier) + "'"
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 if isinstance(cfgValue, basestring):
430 cfgValue = cfgValue.replace('%instancename%', self.identifier)
431
432 logging.info('Setting up forced $CFG->%s to \'%s\' in config.php', cfgKey, cfgValue)
433 self.addConfig(cfgKey, cfgValue)
434 except Exception:
435 logging.warning('Could not append $CFG->%s to config.php', cfgKey)
436
437 self.reload()
438
439 def isInstalled(self):
440 """Returns whether this instance is installed or not"""
441 # Reload the configuration if necessary.
442 self._load()
443 return self.installed == True
444
445 @staticmethod
446 def isInstance(path):
447 """Check whether the path is a Moodle web directory"""
448 version = os.path.join(path, 'version.php')
449 try:
450 f = open(version, 'r')
451 lines = f.readlines()
452 f.close()
453 except:
454 return False
455 found = False
456 for line in lines:
457 if line.find('MOODLE VERSION INFORMATION') > -1:
458 found = True
459 break
460 if not found:
461 return False
462
463 return True
464
465 def isIntegration(self):
466 """Returns whether an instance is an integration one or not"""
467 r = C.get('upstreamRemote') or 'upstream'
468 if not self.git().getRemote(r):
469 r = 'origin'
470 remote = self.git().getConfig('remote.%s.url' % r)
471 if remote != None and remote.endswith('integration.git'):
472 return True
473 return False
474
475 def isStable(self):
476 """Assume an instance is stable if not integration"""
477 return not self.isIntegration()
478
479 def _load(self):
480 """Loads the information"""
481 if not self.isInstance(self.path):
482 return False
483
484 if self._loaded:
485 return True
486
487 # Extracts information from version.php
488 self.version = {}
489 version = os.path.join(self.path, 'version.php')
490 if os.path.isfile(version):
491
492 reVersion = re.compile(r'^\s*\$version\s*=\s*([0-9.]+)\s*;')
493 reRelease = re.compile(r'^\s*\$release\s*=\s*(?P<brackets>[\'"])?(.+)(?P=brackets)\s*;')
494 reMaturity = re.compile(r'^\s*\$maturity\s*=\s*([a-zA-Z0-9_]+)\s*;')
495 reBranch = re.compile(r'^\s*\$branch\s*=\s*(?P<brackets>[\'"])?([0-9]+)(?P=brackets)\s*;')
496
497 f = open(version, 'r')
498 for line in f:
499 if reVersion.search(line):
500 self.version['version'] = reVersion.search(line).group(1)
501 elif reRelease.search(line):
502 self.version['release'] = reRelease.search(line).group(2)
503 elif reMaturity.search(line):
504 self.version['maturity'] = reMaturity.search(line).group(1).replace('MATURITY_', '').lower()
505 elif reBranch.search(line):
506 self.version['branch'] = reBranch.search(line).group(2)
507
508 # Several checks about the branch
509 try:
510 # Has it been set?
511 branch = self.version['branch']
512 except:
513 self.version['branch'] = self.version['release'].replace('.', '')[0:2]
514 branch = self.version['branch']
515 if int(branch) >= int(C.get('masterBranch')):
516 self.version['branch'] = 'master'
517
518 # Stable branch
519 if self.version['branch'] == 'master':
520 self.version['stablebranch'] = 'master'
521 else:
522 self.version['stablebranch'] = 'MOODLE_%s_STABLE' % self.version['branch']
523
524 # Integration or stable?
525 self.version['integration'] = self.isIntegration()
526
527 f.close()
528 else:
529 # Should never happen
530 raise Exception('This does not appear to be a Moodle instance')
531
532 # Extracts parameters from config.php, does not handle params over multiple lines
533 self.config = {}
534 config = os.path.join(self.path, 'config.php')
535 if os.path.isfile(config):
536 self.installed = True
537 prog = re.compile(r'^\s*\$CFG->([a-z_]+)\s*=\s*((?P<brackets>[\'"])?(.+)(?P=brackets)|([0-9.]+)|(true|false|null))\s*;$', re.I)
538 try:
539 f = open(config, 'r')
540 for line in f:
541 match = prog.search(line)
542 if match == None:
543 continue
544
545 if match.group(5) != None:
546 # Number
547 value = float(match.group(5)) if '.' in str(match.group(5)) else int(match.group(5))
548 elif match.group(6) != None:
549 # Boolean or null
550 value = str(match.group(6)).lower()
551 if value == 'true':
552 value = True
553 elif value == 'false':
554 value = False
555 else:
556 value = None
557 else:
558 # Likely to be a string
559 value = match.group(4)
560
561 self.config[match.group(1)] = value
562
563 f.close()
564
565 except IOError:
566 self.installed = False
567 logging.error('Could not read config file')
568
569 else:
570 self.installed = False
571
572 self._loaded = True
573 return True
574
575 def purge(self, manual=False):
576 """Purge the cache of an instance"""
577 if not self.isInstalled():
578 raise Exception('Instance not installed, cannot purge.')
579 elif self.branch_compare('22', '<'):
580 raise Exception('Instance does not support cache purging.')
581
582 try:
583 dataroot = self.get('dataroot', False)
584 if manual and dataroot != False:
585 logging.debug('Removing directories [dataroot]/cache and [dataroot]/localcache')
586 shutil.rmtree(os.path.join(dataroot, 'cache'), True)
587 shutil.rmtree(os.path.join(dataroot, 'localcache'), True)
588
589 self.cli('admin/cli/purge_caches.php', stderr=None, stdout=None)
590
591 except Exception:
592 raise Exception('Error while purging cache!')
593
594 def pushPatch(self, branch=None):
595 """Push a patch on the tracker, and remove the previous one"""
596
597 if branch == None:
598 branch = self.currentBranch()
599 if branch == 'HEAD':
600 raise Exception('Cannot create a patch from a detached branch')
601
602 # Parsing the branch
603 parsedbranch = parseBranch(branch)
604 if not parsedbranch:
605 raise Exception('Could not extract issue number from %s' % branch)
606 issue = 'MDL-%s' % (parsedbranch['issue'])
607 headcommit = self.headcommit(branch)
608
609 # Creating a patch file.
610 fileName = branch + '.mdk.patch'
611 tmpPatchFile = os.path.join(gettempdir(), fileName)
612 if self.git().createPatch('%s...%s' % (headcommit, branch), saveTo=tmpPatchFile):
613
614 J = Jira()
615
616 # Checking if file with same name exists.
617 existingAttachmentId = None
618 existingAttachments = J.getIssue(issue, fields='attachment')
619 for existingAttachment in existingAttachments.get('fields', {}).get('attachment', {}):
620 if existingAttachment.get('filename') == fileName:
621 # Found an existing attachment with the same name, we keep track of it.
622 existingAttachmentId = existingAttachment.get('id')
623 break
624
625 # Pushing patch to the tracker.
626 try:
627 logging.info('Uploading %s to the tracker' % (fileName))
628 J.upload(issue, tmpPatchFile)
629 except JiraException:
630 logging.error('Error while uploading the patch to the tracker')
631 return False
632 else:
633 if existingAttachmentId != None:
634 # On success, deleting file that was there before.
635 try:
636 logging.info('Deleting older patch...')
637 J.deleteAttachment(existingAttachmentId)
638 except JiraException:
639 logging.info('Could not delete older attachment')
640
641 else:
642 logging.error('Could not create a patch file')
643 return False
644
645 return True
646
647 def reload(self):
648 """Sets the value to be reloaded"""
649 self._loaded = False
650
651 def removeConfig(self, name):
652 """Remove a configuration setting from the config file."""
653 configFile = os.path.join(self.path, 'config.php')
654 if not os.path.isfile(configFile):
655 return None
656
657 try:
658 f = open(configFile, 'r')
659 lines = f.readlines()
660 f.close()
661
662 for line in lines:
663 if re.search(r'\$CFG->%s\s*=.*;' % (name), line):
664 lines.remove(line)
665 break
666
667 f = open(configFile, 'w')
668 f.writelines(lines)
669 f.close()
670 except:
671 raise Exception('Error while writing to config file')
672
673 self.reload()
674
675 def runScript(self, scriptname, arguments=None, **kwargs):
676 """Runs a script on the instance"""
677 return Scripts.run(scriptname, self.get('path'), arguments=arguments, cmdkwargs=kwargs)
678
679 def update(self, remote=None):
680 """Update the instance from the remote"""
681
682 if remote == None:
683 remote = C.get('upstreamRemote')
684
685 # Fetch
686 if not self.git().fetch(remote):
687 raise Exception('Could not fetch remote %s' % remote)
688
689 # Checkout stable
690 self.checkout_stable(True)
691
692 # Reset HARD
693 upstream = '%s/%s' % (remote, self.get('stablebranch'))
694 if not self.git().reset(to=upstream, hard=True):
695 raise Exception('Error while executing git reset.')
696
697 # Return to previous branch
698 self.checkout_stable(False)
699
700 def updateConfig(self, name, value):
701 """Update a setting in the config file."""
702 self.removeConfig(name)
703 self.addConfig(name, value)
704
705 def uninstall(self):
706 """Uninstall the instance"""
707
708 if not self.isInstalled():
709 raise Exception('The instance is not installed')
710
711 # Delete the content in moodledata
712 dataroot = self.get('dataroot')
713 if os.path.isdir(dataroot):
714 logging.debug('Deleting dataroot content (%s)' % (dataroot))
715 shutil.rmtree(dataroot)
716 mkdir(dataroot, 0777)
717
718 # Drop the database
719 dbname = self.get('dbname')
720 if self.dbo().dbexists(dbname):
721 logging.debug('Droping database (%s)' % (dbname))
722 self.dbo().dropdb(dbname)
723
724 # Remove the config file
725 configFile = os.path.join(self.get('path'), 'config.php')
726 if os.path.isfile(configFile):
727 logging.debug('Deleting config.php')
728 os.remove(configFile)
729
730 def updateTrackerGitInfo(self, branch=None, ref=None):
731 """Updates the git info on the tracker issue"""
732
733 if branch == None:
734 branch = self.currentBranch()
735 if branch == 'HEAD':
736 raise Exception('Cannot update the tracker when on detached branch')
737
738 # Parsing the branch
739 parsedbranch = parseBranch(branch)
740 if not parsedbranch:
741 raise Exception('Could not extract issue number from %s' % branch)
742 issue = 'MDL-%s' % (parsedbranch['issue'])
743 version = parsedbranch['version']
744
745 # Get the jira config
746 repositoryurl = C.get('repositoryUrl')
747 diffurltemplate = C.get('diffUrlTemplate')
748 stablebranch = self.get('stablebranch')
749
750 # Get the hash of the last upstream commit
751 headcommit = None
752 logging.info('Searching for the head commit...')
753 if ref:
754 try:
755 headcommit = self.git().hashes(ref=ref, limit=1, format='%H')[0]
756 except GitException:
757 logging.warning('Could not resolve a head commit using the reference: %s' % (ref))
758 headcommit = None
759
760 # No reference was passed, or it was invalid.
761 if not headcommit:
762 headcommit = self.headcommit(branch)
763
764 # Head commit not resolved
765 if not headcommit:
766 logging.error('Head commit not resolved, aborting update of tracker fields')
767 return False
768
769 headcommit = headcommit[:10]
770 logging.debug('Head commit resolved to %s' % (headcommit))
771
772 J = Jira()
773 diffurl = diffurltemplate.replace('%branch%', branch).replace('%stablebranch%', stablebranch).replace('%headcommit%', headcommit)
774
775 fieldrepositoryurl = C.get('tracker.fieldnames.repositoryurl')
776 fieldbranch = C.get('tracker.fieldnames.%s.branch' % version)
777 fielddiffurl = C.get('tracker.fieldnames.%s.diffurl' % version)
778
779 if not fieldrepositoryurl or not fieldbranch or not fielddiffurl:
780 logging.error('Cannot set tracker fields for this version (%s). The field names are not set in the config file.', version)
781 else:
782 logging.info('Setting tracker fields: \n %s: %s \n %s: %s \n %s: %s' %
783 (fieldrepositoryurl, repositoryurl, fieldbranch, branch, fielddiffurl, diffurl))
784 J.setCustomFields(issue, {fieldrepositoryurl: repositoryurl, fieldbranch: branch, fielddiffurl: diffurl})
785
786 def upgrade(self, nocheckout=False):
787 """Calls the upgrade script"""
788 if not self.isInstalled():
789 raise Exception('Cannot upgrade an instance which is not installed.')
790 elif not self.branch_compare(20):
791 raise Exception('Upgrade command line tool not supported by this version.')
792 elif os.path.isfile(os.path.join(self.get('path'), '.noupgrade')):
793 raise UpgradeNotAllowed('Upgrade not allowed, found .noupgrade.')
794
795 # Checkout stable
796 if not nocheckout:
797 self.checkout_stable(True)
798
799 cli = '/admin/cli/upgrade.php'
800 args = '--non-interactive --allow-unstable'
801 result = self.cli(cli, args, stdout=None, stderr=None)
802 if result[0] != 0:
803 raise Exception('Error while running the upgrade.')
804
805 # Return to previous branch
806 if not nocheckout:
807 self.checkout_stable(False)