From 9f0f8f903bc43ba4c93ef986f1ab2681f19bb32d Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 1 Mar 2013 18:38:39 +0800 Subject: [PATCH] Backport Command object --- lib/command.py | 21 ++++- lib/commands/__init__.py | 26 ++++++ lib/commands/backport.py | 212 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 lib/commands/backport.py diff --git a/lib/command.py b/lib/command.py index 216861a..8251800 100644 --- a/lib/command.py +++ b/lib/command.py @@ -24,6 +24,7 @@ http://github.com/FMCorz/mdk import argparse import sys +import workplace class Command(object): @@ -46,20 +47,38 @@ class Command(object): ] _description = 'Undocumented command' + __C = None + __Wp = None + def __init__(self, config): - self.C = config + self.__C = config + self.__Wp = workplace.Workplace() @property def arguments(self): return self._arguments @property + def C(self): + return self.__C + + @property def description(self): return self._description + def resolve(self, name): + return self.Wp.resolve(name) + + def resolveMultiple(self, names): + return self.Wp.resolve(names) + def run(self, args): return True + @property + def Wp(self): + return self.__Wp + class RunCommand(object): """Executes a command""" diff --git a/lib/commands/__init__.py b/lib/commands/__init__.py index e69de29..1b40bf7 100644 --- a/lib/commands/__init__.py +++ b/lib/commands/__init__.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Moodle Development Kit + +Copyright (c) 2013 Frédéric Massart - FMCorz.net + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +http://github.com/FMCorz/mdk +""" + +from alias import AliasCommand +from backport import BackportCommand diff --git a/lib/commands/backport.py b/lib/commands/backport.py new file mode 100644 index 0000000..93c5747 --- /dev/null +++ b/lib/commands/backport.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Moodle Development Kit + +Copyright (c) 2013 Frédéric Massart - FMCorz.net + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +http://github.com/FMCorz/mdk +""" + +from lib import tools +from lib.command import Command +from lib.tools import debug, yesOrNo + + +class BackportCommand(Command): + + _description = 'Backports a branch' + + def __init__(self, *args, **kwargs): + super(BackportCommand, self).__init__(*args, **kwargs) + self._arguments = [ + ( + ['-b', '--branch'], + { + 'help': 'the branch to backport if not the current one. If omitted, guessed from instance name.', + 'metavar': 'branch' + } + ), + ( + ['-f', '--force-push'], + { + 'action': 'store_true', + 'dest': 'forcepush', + 'help': 'force the push' + } + ), + ( + ['-i', '--integration'], + { + 'action': 'store_true', + 'help': 'backport to integration instances' + } + ), + ( + ['-p', '--push'], + { + 'action': 'store_true', + 'help': 'push the branch after successful backport' + } + ), + ( + ['-t', '--push-to'], + { + 'dest': 'pushremote', + 'help': 'the remote to push the branch to. Default is %s.' % self.C.get('myRemote'), + 'metavar': 'remote' + } + ), + ( + ['-v', '--versions'], + { + 'choices': [str(x) for x in range(13, int(self.C.get('masterBranch')))] + ['master'], + 'help': 'versions to backport to', + 'metavar': 'version', + 'nargs': '+', + 'required': True + } + ), + ( + ['name'], + { + 'default': None, + 'help': 'name of the instance to backport from. Can be omitted if branch is specified.', + 'metavar': 'name', + 'nargs': '?' + } + ) + ] + + def run(self, args): + M = None + branch = args.branch + versions = args.versions + integration = args.integration + + # If we don't have a branch, we need an instance + M = self.Wp.resolve(args.name) + if not M and not branch: + raise Exception('This is not a Moodle instance') + + # Getting issue number + if M and not branch: + branch = M.currentBranch() + + # Parsing the branch + parsedbranch = tools.parseBranch(branch, self.C.get('wording.branchRegex')) + if not parsedbranch: + raise Exception('Could not extract issue number from %s' % branch) + issue = parsedbranch['issue'] + suffix = parsedbranch['suffix'] + version = parsedbranch['version'] + + # Original track + originaltrack = tools.stableBranch(version) + + # Integration? + if M: + integration = M.isIntegration() + + def stashPop(stash): + """Small helper to pop the stash has we have to do it in some different places""" + if not stash[1].startswith('No local changes'): + pop = M2.git().stash(command='pop') + if pop[0] != 0: + debug('An error ocured while unstashing your changes') + else: + debug('Popped the stash') + + # Begin backport + for v in versions: + + # Gets the instance to cherry-pick to + name = self.Wp.generateInstanceName(v, integration=integration) + if not self.Wp.isMoodle(name): + debug('Could not find instance %s for version %s' % (name, v)) + continue + M2 = self.Wp.get(name) + + debug("Preparing cherry-pick of %s/%s in %s" % (M.get('identifier'), branch, name)) + + # Get hash list + cherry = '%s/%s..%s' % (self.C.get('upstreamRemote'), originaltrack, branch) + hashes = M.git().hashes(cherry) + hashes.reverse() + + # Stash + stash = M2.git().stash(untracked=False) + if stash[0] != 0: + debug('Error while trying to stash your changes. Skipping %s.' % M2.get('identifier')) + debug(stash[2]) + continue + elif not stash[1].startswith('No local changes'): + debug('Stashed your local changes') + + # Fetch the remote to get reference to the branch to backport + debug("Fetching remote %s..." % (M.get('path'))) + M2.git().fetch(M.get('path'), branch) + + # Creates a new branch if necessary + newbranch = M2.generateBranchName(issue, suffix=suffix) + track = '%s/%s' % (self.C.get('upstreamRemote'), M2.get('stablebranch')) + if not M2.git().hasBranch(newbranch): + debug('Creating branch %s' % newbranch) + if not M2.git().createBranch(newbranch, track=track): + debug('Could not create branch %s tracking %s in %s' % (newbranch, track, name)) + stashPop(stash) + continue + M2.git().checkout(newbranch) + else: + M2.git().checkout(newbranch) + debug('Hard reset %s to %s' % (newbranch, track)) + M2.git().reset(to=track, hard=True) + + # Picking the diff upstream/MOODLE_23_STABLE..github/MDL-12345-master + debug('Cherry-picking %s' % (cherry)) + result = M2.git().pick(hashes) + if result[0] != 0: + debug('Error while cherry-picking %s in %s.' % (cherry, name)) + debug(result[2]) + if yesOrNo('The cherry-pick might still be in progress, would you like to abort it?'): + result = M2.git().pick(abort=True) + if result[0] > 0 and result[0] != 128: + debug('Could not abort the cherry-pick!') + else: + stashPop(stash) + debug('') + continue + + # Pushing branch + if args.push: + pushremote = args.pushremote + if pushremote == None: + pushremote = self.C.get('myRemote') + debug('Pushing %s to %s' % (newbranch, pushremote)) + result = M2.git().push(remote=pushremote, branch=newbranch, force=args.forcepush) + if result[0] != 0: + debug('Error while pushing to remote %s' % (pushremote)) + debug(result[2]) + stashPop(stash) + continue + + stashPop(stash) + + debug('Instance %s successfully patched!' % name) + debug('') + + debug('Done.') -- 2.11.0