e3b36b080462b790e35083226baf9b543f02f190
2 # -*- coding: utf-8 -*-
7 Copyright (c) 2013 Frédéric Massart - FMCorz.net
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.
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.
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/>.
22 http://github.com/FMCorz/mdk
26 from .. import tools
, css
, jira
27 from ..command
import Command
28 from ..tools
import yesOrNo
31 class BackportCommand(Command
):
33 _description
= 'Backports a branch'
35 def __init__(self
, *args
, **kwargs
):
36 super(BackportCommand
, self
).__init__(*args
, **kwargs
)
41 'help': 'the branch to backport if not the current one. If omitted, guessed from instance name.',
46 ['-f', '--force-push'],
48 'action': 'store_true',
50 'help': 'force the push'
54 ['-i', '--integration'],
56 'action': 'store_true',
57 'help': 'backport to integration instances'
63 'action': 'store_true',
64 'help': 'push the branch after successful backport'
71 'help': 'the remote to push the branch to. Default is %s.' % self
.C
.get('myRemote'),
78 'action': 'store_true',
80 'help': 'instead of pushing to a remote, this will upload a patch file to the tracker. Security issues use this by default if --push is set. This option discards most other flags.',
84 ['-t', '--update-tracker'],
87 'dest': 'updatetracker',
88 'help': 'to use with --push, also add the diff information to the tracker issue',
96 'choices': [str(x
) for x
in range(13, int(self
.C
.get('masterBranch')))] + ['master'],
97 'help': 'versions to backport to',
107 'help': 'name of the instance to backport from. Can be omitted if branch is specified.',
117 versions
= args
.versions
118 integration
= args
.integration
120 # If we don't have a branch, we need an instance
121 M
= self
.Wp
.resolve(args
.name
)
122 if not M
and not branch
:
123 raise Exception('This is not a Moodle instance')
125 # Getting issue number
127 branch
= M
.currentBranch()
130 parsedbranch
= tools
.parseBranch(branch
)
132 raise Exception('Could not extract issue number from %s' % branch
)
133 issue
= parsedbranch
['issue']
134 suffix
= parsedbranch
['suffix']
135 version
= parsedbranch
['version']
137 if args
.push
and not args
.patch
:
138 mdlIssue
= 'MDL-%s' %
(issue
)
140 args
.patch
= J
.isSecurityIssue(mdlIssue
)
143 logging
.info('%s appears to be a security issue, switching to patch mode...' %
(mdlIssue
))
146 originaltrack
= tools
.stableBranch(version
)
150 integration
= M
.isIntegration()
153 """Small helper to pop the stash has we have to do it in some different places"""
154 if not stash
[1].startswith('No local changes'):
155 pop
= M2
.git().stash(command
='pop')
157 logging
.error('An error ocured while unstashing your changes')
159 logging
.info('Popped the stash')
164 # Gets the instance to cherry-pick to
165 name
= self
.Wp
.generateInstanceName(v
, integration
=integration
)
166 if not self
.Wp
.isMoodle(name
):
167 logging
.warning('Could not find instance %s for version %s' %
(name
, v
))
169 M2
= self
.Wp
.get(name
)
171 logging
.info("Preparing cherry-pick of %s/%s in %s" %
(M
.get('identifier'), branch
, name
))
174 cherry
= '%s/%s..%s' %
(self
.C
.get('upstreamRemote'), originaltrack
, branch
)
175 hashes
= M
.git().hashes(cherry
)
179 stash
= M2
.git().stash(untracked
=False)
181 logging
.error('Error while trying to stash your changes. Skipping %s.' % M2
.get('identifier'))
182 logging
.debug(stash
[2])
184 elif not stash
[1].startswith('No local changes'):
185 logging
.info('Stashed your local changes')
187 # Fetch the remote to get reference to the branch to backport
188 logging
.info("Fetching remote %s..." %
(M
.get('path')))
189 M2
.git().fetch(M
.get('path'), branch
)
191 # Creates a new branch if necessary
192 newbranch
= M2
.generateBranchName(issue
, suffix
=suffix
)
193 track
= '%s/%s' %
(self
.C
.get('upstreamRemote'), M2
.get('stablebranch'))
194 if not M2
.git().hasBranch(newbranch
):
195 logging
.info('Creating branch %s' % newbranch
)
196 if not M2
.git().createBranch(newbranch
, track
=track
):
197 logging
.error('Could not create branch %s tracking %s in %s' %
(newbranch
, track
, name
))
200 M2
.git().checkout(newbranch
)
202 M2
.git().checkout(newbranch
)
203 logging
.info('Hard reset %s to %s' %
(newbranch
, track
))
204 M2
.git().reset(to
=track
, hard
=True)
206 # Picking the diff upstream/MOODLE_23_STABLE..github/MDL-12345-master
207 logging
.info('Cherry-picking %s' %
(cherry
))
208 result
= M2
.git().pick(hashes
)
211 # Try to resolve the conflicts if any.
212 resolveConflicts
= True
213 conflictsResolved
= False
214 while resolveConflicts
:
216 # Check the list of possible conflicting files.
217 conflictingFiles
= M2
.git().conflictingFiles()
218 if conflictingFiles
and len(conflictingFiles
) == 1 and 'theme/bootstrapbase/style/moodle.css' in conflictingFiles
:
219 logging
.info('Conflicts found in bootstrapbase moodle CSS, trying to auto resolve...')
220 cssCompiler
= css
.Css(M2
)
221 if cssCompiler
.compile(theme
='bootstrapbase', sheets
=['moodle']):
222 M2
.git().add('theme/bootstrapbase/style/moodle.css')
223 # We need to commit manually to prevent the editor to open.
224 M2
.git().commit(filepath
='.git/MERGE_MSG')
225 result
= M2
.git().pick(continu
=True)
227 resolveConflicts
= False
228 conflictsResolved
= True
230 resolveConflicts
= False
232 # We still have a dirty repository.
233 if not conflictsResolved
:
234 logging
.error('Error while cherry-picking %s in %s.' %
(cherry
, name
))
235 logging
.debug(result
[2])
236 if yesOrNo('The cherry-pick might still be in progress, would you like to abort it?'):
237 result
= M2
.git().pick(abort
=True)
238 if result
[0] > 0 and result
[0] != 128:
239 logging
.error('Could not abort the cherry-pick!')
247 pushremote
= args
.pushremote
248 if pushremote
== None:
249 pushremote
= self
.C
.get('myRemote')
250 logging
.info('Pushing %s to %s' %
(newbranch
, pushremote
))
251 result
= M2
.git().push(remote
=pushremote
, branch
=newbranch
, force
=args
.forcepush
)
253 logging
.warning('Error while pushing to remote %s' %
(pushremote
))
254 logging
.debug(result
[2])
259 if args
.updatetracker
!= None:
260 ref
= None if args
.updatetracker
== True else args
.updatetracker
261 M2
.updateTrackerGitInfo(branch
=newbranch
, ref
=ref
)
264 if not M2
.pushPatch(newbranch
):
269 logging
.info('Instance %s successfully patched!' % name
)
272 logging
.info('Done.')