Revision 2e9bddbf

b/devflow/flow.py
1 1
import os
2
import re
2 3

  
3 4
import logging
4 5
logging.basicConfig()
5
from optparse import OptionParser
6
#from optparse import OptionParser
7
from argparse import ArgumentParser
6 8

  
7 9
os.environ["GIT_PYTHON_TRACE"] = "full"
8 10
from devflow import utils, versioning
9 11
from devflow.version import __version__
10 12
from devflow.autopkg import call
11
from devflow.ui import query_action
13
from devflow.ui import query_action, query_user, query_yes_no
12 14
from functools import wraps, partial
13 15
from contextlib import contextmanager
14 16
from git.exc import GitCommandError
17
from sh import mktemp
18

  
19

  
20
def create_temp_file(suffix):
21
    create_dir_cmd = mktemp("/tmp/" + suffix + "-XXXXX")
22
    return create_dir_cmd.stdout.strip()
15 23

  
16 24

  
17 25
def cleanup(func):
......
38 46
        yield
39 47
    except GitCommandError as e:
40 48
        if e.status != 128:
41
            print "An error occured. Resolve it and type 'exit'"
42
            call("bash")
49
            print "An error occured. Resolve it and type 'exit 0'"
50
            tmpbashrc=create_temp_file("bashrc")
51
            f = open(tmpbashrc, 'w')
52
            f.write("source $HOME/.bashrc ; export PS1=(Conflict)\"$PS1\"")
53
            f.close()
54
            call('bash --rcfile %s' % tmpbashrc)
55
            os.unlink(tmpbashrc)
43 56
        else:
44 57
            raise
45 58

  
59
def get_release_version(develop_version):
60
    version = develop_version.rstrip('next')
61
    parts = version.split('.')
62
    major_version = int(parts[0])
63
    minor_version = int(parts[1])
64
    #return str(major_version) + '.' + str(minor_version+1) + 'rc1'
65
    return str(major_version) + '.' + str(minor_version+1)
66

  
67
def get_develop_version_from_release(release_version):
68
    #version = re.sub('rc[0-9]+$', '', release_version)
69
    version = release_version
70
    parts = version.split('.')
71
    major_version = int(parts[0])
72
    minor_version = int(parts[1])
73
    return str(major_version) + '.' + str(minor_version+1) + 'next'
74

  
75
def get_hotfix_version(version):
76
    parts = version.split('.')
77
    major_version = int(parts[0])
78
    minor_version = int(parts[1])
79
    if (len(parts) > 2):
80
        hotfix_version = int(parts[2])
81
    else:
82
        hotfix_version = 0
83

  
84
    return str(major_version) + '.' + str(minor_version) + '.'\
85
            + str(hotfix_version+1)
46 86

  
47 87
class GitManager(object):
48 88
    def __init__(self):
......
54 94
        self.log.info("Repository: %s. HEAD: %s", self.repo, self.start_hex)
55 95
        self.new_branches = []
56 96
        self.new_tags = []
57
        self.repo.git.pull("origin")
97
        #self.repo.git.pull("origin")
58 98

  
59 99
    def get_branch(self, mode, version):
60 100
        if mode not in ["release", "hotfix"]:
......
66 106
            raise ValueError("Unknown mode: %s" % mode)
67 107
        return "debian-%s-%s" % (mode, version)
68 108

  
69
    @cleanup
70
    def start_release(self, version):
71
        self.start_common("release", version)
109
    def doit(self, action_yes=None, action_no=None, question="Do it", args=None,
110
            default=False):
111
        if not args.defaults:
112
            ret = query_yes_no(question, default = "yes" if default else "no")
113
        else:
114
            ret = default
72 115

  
73
    @cleanup
74
    def start_hotfix(self, version):
75
        self.start_common("hotfix", version)
116
        if ret and action_yes:
117
            action_yes()
118
        elif not ret and action_no:
119
            action_no()
120

  
121
    def __print_cleanup(self, branches):
122
        print "To remove obsolete branches run:"
123
        for b in branches:
124
            print "git branch -D %s" % b
125

  
126

  
127
    def __cleanup_branches(self, branches):
128
        repo = self.repo
129
        for b in branches:
130
            repo.git.branch("-D", b)
131

  
132
    def cleanup_branches(self, branches, args, default=False):
133
        if args.cleanup is not None:
134
            if args.cleanup:
135
                self.__cleanup_branches(branches)
136
            else:
137
                self.__print_cleanup(branches)
138
            return
139

  
140
        question="Remove branches %s" % branches
141
        action_yes = partial(self.__cleanup_branches, branches)
142
        action_no = partial(self.__print_cleanup, branches)
143
        self.doit(action_yes=action_yes, action_no=action_no,
144
                  question=question, args=args, default=default)
145

  
146

  
147
    def check_edit_changelog(self, edit_action, args, default=True):
148
        if args.edit_changelog is not None:
149
            if args.edit_changelog:
150
                edit_action()
151
            return
152
        question = "Edit changelog ?"
153
        self.doit(action_yes=edit_action, question=question, args=args,
154
                  default=default)
155

  
156
    def _merge_branches(self, branch_to, branch_from):
157
        repo = self.repo
158
        cur_branch = repo.active_branch.name
159
        repo.git.checkout(branch_to)
160
        with conflicts():
161
            repo.git.merge("--no-ff", branch_from)
162
        repo.git.checkout(cur_branch)
163

  
164
    def merge_branches(self, branch_to, branch_from, args, default=True):
165
        action = partial(self._merge_branches, branch_to, branch_from)
166
        question = "Merge branch %s to %s ?" % (branch_from, branch_to)
167
        self.doit(action_yes=action, question=question, args=args,
168
                  default=default)
169

  
170
    def edit_changelog(self, branch, base_branch=None):
171
        repo = self.repo
172
        if not branch in repo.branches:
173
            raise ValueError("Branch %s does not exist." % branch)
174
        if base_branch and not base_branch in repo.branches:
175
            raise ValueError("Branch %s does not exist." % base_branch)
176

  
177
        repo.git.checkout(branch)
178
        topdir = repo.working_dir
179
        changelog = os.path.join(topdir, "Changelog")
180

  
181
        lines = []
182
        lines.append("#Changelog for %s\n" % branch)
183
        if base_branch:
184
            commits = repo.git.rev_list("%s..%s" % (base_branch, branch)).split("\n")
185
            for c in commits:
186
                commit = repo.commit(c)
187
                lines.append(commit.message)
188
        lines.append("\n")
189

  
190
        f = open(changelog, 'rw+')
191
        lines.extend(f.readlines())
192
        f.seek(0)
193
        f.truncate(0)
194
        f.writelines(lines)
195
        f.close()
196

  
197
        editor = os.getenv('EDITOR')
198
        if not editor:
199
            editor = 'vim'
200
        call("%s %s" % (editor, changelog))
201
        repo.git.add(changelog)
202
        repo.git.commit(m="Update changelog")
203
        print "Updated changelog on branch %s" % branch
76 204

  
77 205
    @cleanup
78
    def end_release(self, version):
206
    def start_release(self, args):
79 207
        repo = self.repo
80
        master = "master"
81
        debian_master = "debian"
82 208
        upstream = "develop"
83 209
        debian = "debian-develop"
210
        repo.git.checkout(upstream)
211

  
212
        vcs = utils.get_vcs_info()
213
        develop_version = versioning.get_base_version(vcs)
214
        if not args.version:
215
            version = get_release_version(develop_version)
216
            if not args.defaults:
217
                version = query_user("Release version", default=version)
218
        else:
219
            #validate version?
220
            pass
221
        rc_version = "%src1" % version
222
        new_develop_version = "%snext" % version
223

  
84 224
        upstream_branch = self.get_branch("release", version)
85 225
        debian_branch = self.get_debian_branch("release", version)
86
        repo.git.checkout(upstream)
87
        with conflicts():
88
            repo.git.merge("--no-ff", upstream_branch)
226

  
227
        #create release branch
228
        repo.git.branch(upstream_branch, upstream)
229
        self.new_branches.append(upstream_branch)
230
        repo.git.checkout(upstream_branch)
231
        versioning.bump_version(rc_version)
232

  
233
        #create debian release branch
89 234
        repo.git.checkout(debian)
90
        with conflicts():
91
            repo.git.merge("--no-ff", debian_branch)
235
        repo.git.branch(debian_branch, debian)
236
        self.new_branches.append(debian_branch)
92 237

  
93
        repo.git.checkout(master)
94
        with conflicts():
95
            repo.git.merge("--no-ff", upstream_branch)
96
        repo.git.checkout(debian_master)
97
        with conflicts():
98
            repo.git.merge("--no-ff", debian_branch)
238
        repo.git.checkout(upstream_branch)
239
        repo.git.checkout(debian)
99 240

  
241
        #bump develop version
100 242
        repo.git.checkout(upstream)
101
        print "To remove obsolete branches run:"
102
        print "git branch -d %s" % upstream_branch
103
        print "git branch -d %s" % debian_branch
243
        versioning.bump_version(new_develop_version)
244

  
245
        repo.git.checkout(upstream_branch)
246

  
104 247

  
105 248
    @cleanup
106
    def end_hotfix(self, version):
249
    def start_hotfix(self, args):
107 250
        repo = self.repo
108 251
        upstream = "master"
109 252
        debian = "debian"
110
        upstream_branch = self.get_branch("hotfix", version)
111
        debian_branch = self.get_debian_branch("hotfix", version)
112

  
113 253
        repo.git.checkout(upstream)
114
        with conflicts():
115
            repo.git.merge("--no-ff", upstream_branch)
116
        repo.git.checkout(debian)
117
        with conflicts():
118
            repo.git.merge("--no-ff", debian_branch)
254
        #maybe provide major.minor version, find the latest release/hotfix and
255
        #branch from there ?
256

  
257
        vcs = utils.get_vcs_info()
258
        version = versioning.get_base_version(vcs)
259
        if not args.version:
260
            version = get_hotfix_version(version)
261
            if not args.defaults:
262
                version = query_user("Hotfix version", default=version)
263
        else:
264
            #validate version?
265
            pass
119 266

  
120
        repo.git.checkout(upstream)
121
        print "To remove obsolete branches run:"
122
        print "git branch -d %s" % upstream_branch
123
        print "git branch -d %s" % debian_branch
267
        rc_version = "%src1" % version
268
        new_develop_version = "%snext" % version
124 269

  
125
    def start_common(self, mode, version):
126
        if mode not in ["release", "hotfix"]:
127
            raise ValueError("Unknown mode: %s" % mode)
128
        repo = self.repo
129
        upstream = "develop" if mode == "release" else "master"
130
        debian = "debian-develop" if mode == "release" else "debian"
131
        upstream_branch = "%s-%s" % (mode, version)
132
        debian_branch = "debian-%s-%s" % (mode, version)
133
        repo.git.checkout(upstream)
270
        upstream_branch = self.get_branch("hotfix", version)
271
        debian_branch = self.get_debian_branch("hotfix", version)
272

  
273
        #create hotfix branch
134 274
        repo.git.branch(upstream_branch, upstream)
135 275
        self.new_branches.append(upstream_branch)
136
        versioning.bump_version("%snext" % version)
137 276
        repo.git.checkout(upstream_branch)
138
        versioning.bump_version("%src1" % version)
277
        versioning.bump_version(rc_version)
278

  
279
        #create debian hotfix branch
139 280
        repo.git.checkout(debian)
140 281
        repo.git.branch(debian_branch, debian)
141 282
        self.new_branches.append(debian_branch)
283

  
142 284
        repo.git.checkout(upstream_branch)
143 285
        repo.git.checkout(debian)
144 286

  
287
        #bump develop version. Ask first or verify we have the same
288
        #major.minornext?
289
        #repo.git.checkout(upstream)
290
        #versioning.bump_version(new_develop_version)
291

  
292
        repo.git.checkout(upstream_branch)
293

  
294
    @cleanup
295
    def end_release(self, args):
296
        version = args.version
297
        repo = self.repo
298
        master = "master"
299
        debian_master = "debian"
300
        upstream = "develop"
301
        debian = "debian-develop"
302
        upstream_branch = self.get_branch("release", version)
303
        debian_branch = self.get_debian_branch("release", version)
304
        tag = upstream_branch
305
        debial_tag = "debian/" + tag
306

  
307
        edit_action = partial(self.edit_changelog, upstream_branch, "develop")
308
        self.check_edit_changelog(edit_action, args, default=True)
309

  
310
        #merge to master
311
        self._merge_branches(master, upstream_branch)
312
        self._merge_branches(debian_master, debian_branch)
313

  
314
        #create tags
315
        repo.git.checkout(master)
316
        repo.git.tag("%s" % tag)
317
        repo.git.checkout(debian)
318
        repo.git.tag("%s" % debian)
319

  
320
        #merge release changes to upstream
321
        self.merge_branches(upstream, upstream_branch, args, default=True)
322
        self.merge_branches(debian, debian_branch, args, default=True)
323

  
324
        repo.git.checkout(upstream)
325

  
326
        branches = [upstream_branch, debian_branch]
327
        self.cleanup_branches(branches, args, default=True)
328

  
329
    @cleanup
330
    def end_hotfix(self, args):
331
        version = args.version
332

  
333
        repo = self.repo
334
        upstream = "master"
335
        debian = "debian"
336
        upstream_branch = self.get_branch("hotfix", version)
337
        debian_branch = self.get_debian_branch("hotfix", version)
338

  
339
        #create tags?
340

  
341
        self._merge_branches(upstream, upstream_branch)
342
        self._merge_branches(debian, debian_branch)
343

  
344
        repo.git.checkout(upstream)
345

  
346
        branches = [upstream_branch, debian_branch]
347
        self.cleanup_branches(branches, args, default=True)
348

  
145 349
    @cleanup
146
    def start_feature(self, feature_name):
350
    def start_feature(self, args):
351
        feature_name = args.feature_name
147 352
        repo = self.repo
148 353
        feature_upstream = "feature-%s" % feature_name
149 354
        feature_debian = "debian-%s" % feature_upstream
......
153 358
        self.new_branches.append(feature_debian)
154 359

  
155 360
    @cleanup
156
    def end_feature(self, feature_name):
361
    def end_feature(self, args):
362
        feature_name = args.feature_name
157 363
        repo = self.repo
158 364
        feature_upstream = "feature-%s" % feature_name
159 365
        if not feature_upstream in repo.branches:
160 366
            raise ValueError("Branch %s does not exist." % feature_upstream)
161 367
        feature_debian = "debian-%s" % feature_upstream
162
        action = partial(self.edit_changelog, feature_upstream, "develop")
163
        query_action("Edit changelog", action = action)
164
#        self.edit_changelog(feature_upstream)
165
        repo.git.checkout("develop")
166
        with conflicts():
167
            repo.git.merge(feature_upstream)
168
        repo.git.checkout("debian-develop")
368

  
369
        edit_action = partial(self.edit_changelog, feature_upstream, "develop")
370
        self.check_edit_changelog(edit_action, args, default=True)
371

  
372
        #merge to develop
373
        self._merge_branches("develop", feature_upstream)
169 374
        if feature_debian in repo.branches:
170
            with conflicts():
171
                repo.git.merge(feature_debian)
375
            self._merge_branches("debian-develop", feature_debian)
172 376
        repo.git.checkout("develop")
173
        print "To remove obsolete branches run:"
174
        print "git branch -D %s" % feature_upstream
377

  
378
        branches = [feature_upstream]
175 379
        if feature_debian in repo.branches:
176
            print "git branch -D %s" % feature_debian
380
            branches.append(feature_debian)
381
        self.cleanup_branches(branches, args, default=True)
177 382

  
178
    def edit_changelog(self, branch, base_branch=None):
179
        repo = self.repo
180
        if not branch in repo.branches:
181
            raise ValueError("Branch %s does not exist." % branch)
182
        if base_branch and not base_branch in repo.branches:
183
            raise ValueError("Branch %s does not exist." % base_branch)
184 383

  
185
        repo.git.checkout(branch)
186
        topdir = repo.working_dir
187
        changelog = os.path.join(topdir, "Changelog")
384
def refhead(repo):
385
    return repo.head.log[-1].newhexsha
188 386

  
189
        lines = []
190
        lines.append("#Changelog for %s\n" % branch)
191
        if base_branch:
192
            commits = repo.git.rev_list("%s..%s" % (base_branch, branch)).split("\n")
193
            for c in commits:
194
                commit = repo.commit(c)
195
                lines.append(commit.message)
196
        lines.append("\n")
197 387

  
198
        f = open(changelog, 'rw+')
199
        lines.extend(f.readlines())
200
        f.seek(0)
201
        f.truncate(0)
202
        f.writelines(lines)
203
        f.close()
204
 
388
def main():
389
    parser = ArgumentParser(description="Devflow tool")
390
    parser.add_argument('-V', '--version', action='version',
391
            version='devflow-flow %s' % __version__)
392
    parser.add_argument('-d', '--defaults', action='store_true', default=False,
393
            help="Assume default on every choice, unless a value is provided")
205 394

  
206
        editor = os.getenv('EDITOR')
207
        if not editor:
208
            editor = 'vim'
209
        call("%s %s" % (editor, changelog))
210
        repo.git.add(changelog)
211
        repo.git.commit(m="Update changelog")
212
        print "Updated changelog on branch %s" % branch
395
    subparsers = parser.add_subparsers()
213 396

  
214
    def end_common(self, mode, version):
215
        pass
216 397

  
398
    init_parser = subparsers.add_parser('init',
399
            help="Initialize a new devflow repo")
400
    init_parser.add_argument('-m', '--master', type=str, nargs='?',
401
            help="Master branch")
402
    init_parser.add_argument('-d', '--develop', type=str, nargs='?',
403
            help="Develop branch")
404
    init_parser.set_defaults(func='init_repo')
217 405

  
218
def refhead(repo):
219
    return repo.head.log[-1].newhexsha
220 406

  
407
    feature_parser = subparsers.add_parser('feature', help="Feature options")
408
    feature_subparsers = feature_parser.add_subparsers()
409

  
410
    feature_start_parser = feature_subparsers.add_parser('start',
411
            help="Start a new feature")
412
    feature_start_parser.set_defaults(func='start_feature')
413
    feature_start_parser.add_argument('feature_name', type=str,
414
            help="Name of the feature")
415

  
416
    feature_finish_parser = feature_subparsers.add_parser('finish',
417
            help="Finish a feature")
418
    feature_finish_parser.set_defaults(func='end_feature')
419
    feature_finish_parser.add_argument('feature_name', type=str,
420
            help="Name of the feature")
421
    feature_finish_parser.add_argument('--no-edit-changelog',
422
            action='store_const', const=False, dest='edit_changelog',
423
            help="Do not edit the changelog")
424
    feature_finish_parser.add_argument('--no-cleanup', action='store_const',
425
            const=True, dest='cleanup', help="Do not cleanup branches")
426

  
427
    release_parser = subparsers.add_parser('release', help="release options")
428
    release_subparsers = release_parser.add_subparsers()
429

  
430

  
431
    release_start_parser = release_subparsers.add_parser('start',
432
            help="Start a new release")
433
    release_start_parser.add_argument('--version', type=str,
434
            help="Version of the release")
435
    release_start_parser.add_argument('--develop-version', type=str,
436
            help="New develop version")
437
    release_start_parser.set_defaults(func='start_release')
438

  
439

  
440
    release_finish_parser = release_subparsers.add_parser('finish',
441
            help="Finish a release")
442
    release_finish_parser.add_argument('version', type=str,
443
            help="Version of the release")
444
    release_finish_parser.add_argument('--no-edit-changelog',
445
            action='store_const', const=False, dest='edit_changelog',
446
            help="Do not edit the changelog")
447
    release_finish_parser.add_argument('--no-cleanup', action='store_const',
448
            const=True, dest='cleanup', help="Do not cleanup branches")
449

  
450
    release_finish_parser.set_defaults(func='end_release')
451

  
452
    hotfix_parser = subparsers.add_parser('hotfix', help="hotfix options")
453
    hotfix_subparsers = hotfix_parser.add_subparsers()
454

  
455

  
456
    hotfix_start_parser = hotfix_subparsers.add_parser('start',
457
            help="Start a new hotfix")
458
    hotfix_start_parser.add_argument('--version', type=str,
459
            help="Version of the hotfix")
460
    hotfix_start_parser.add_argument('--develop-version', type=str,
461
            help="New develop version")
462
    hotfix_start_parser.set_defaults(func='start_hotfix')
463

  
464

  
465
    hotfix_finish_parser = hotfix_subparsers.add_parser('finish',
466
            help="Finish a hotfix")
467
    hotfix_finish_parser.add_argument('version', type=str,
468
            help="Version of the hotfix")
469
    hotfix_finish_parser.add_argument('--no-edit-changelog',
470
            action='store_const', const=False, dest='edit_changelog',
471
            help="Do not edit the changelog")
472
    hotfix_finish_parser.add_argument('--no-cleanup', action='store_const',
473
            const=True, dest='cleanup', help="Do not cleanup branches")
474
    hotfix_finish_parser.set_defaults(func='end_hotfix')
475

  
476

  
477

  
478
    args = parser.parse_args()
221 479

  
222
def main():
223
    HELP_MSG = "usage: %prog mode=[release|hotfix|feature]"\
224
               " [version|name]"
225
    parser = OptionParser(usage=HELP_MSG,
226
                          version="devflow-flow %s" % __version__,
227
                          add_help_option=True)
228
    options, args = parser.parse_args()
229
    if len(args) != 3:
230
        parser.error("Invalid number of arguments.")
231
    mode, action, version = args
232 480
    gm = GitManager()
233
    func = "%s_%s" % (action, mode)
234
    try:
235
        getattr(gm, func)(version)
236
    except AttributeError:
237
        parser.error("Invalid arguments.")
481
    getattr(gm, args.func)(args)
482

  
238 483

  
239 484
if __name__ == "__main__":
240 485
    main()
b/devflow/ui.py
39 39
    answer = query_yes_no(question, default)
40 40
    if answer and action is not None:
41 41
        action()
42

  
43
def query_user(question, default=""):
44
    prompt = "[" + default + "]"
45
    sys.stdout.write(question + prompt)
46
    answer = raw_input()
47
    if answer == "":
48
        return default
49
    return answer

Also available in: Unified diff