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