def run_finish(args): gitflow = GitFlow() repo = gitflow.repo git = repo.git f_prefix = gitflow.get_prefix('feature') name = gitflow.nameprefix_or_current('feature', args.nameprefix) full_name = f_prefix + name #+++ PT story stuff sys.stdout.write('Getting data from Pivotal Tracker ... ') story_id = pivotal.get_story_id_from_branch_name('feature', name) story = pivotal.Story(story_id) release = story.get_release() print 'OK' # Decide on the upstream branch. if release: # Merge into the release branch. r_prefix = gitflow.get_prefix('release') short_name = gitflow.nameprefix_or_current('release', release) upstream = r_prefix + short_name else: # Merge into the develop branch. upstream = gitflow.develop_name() # Fail as soon as possible if something is not right so that we don't # get Pivotal Tracker into an inconsistent state. rev_range = [get_feature_ancestor(full_name, upstream), repo.commit(full_name).hexsha] #+++ Git manipulation sys.stdout.write('Finishing feature branch ... upstream %s ... ' \ % upstream) gitflow.finish('feature', name, upstream=upstream, fetch=True, rebase=args.rebase, keep=True, force_delete=args.force_delete, tagging_info=None, push=(not args.no_push)) print 'OK' #+++ Review Request if not args.no_review: sys.stdout.write('Posting review ... upstream %s ... ' % upstream) review = BranchReview.from_identifier('feature', name, rev_range) review.post(story, summary_from_story=(not args.summary_from_commit)) print('OK') sys.stdout.write('Posting code review url into Pivotal Tracker ... ') comment = 'New patch was uploaded into Review Board: ' + review.get_url() story.add_comment(comment) print('OK') #+++ Finish PT story sys.stdout.write('Updating Pivotal Tracker ... ') if story.is_finished(): sys.stdout.write('story already finished, skipping ... ') else: sys.stdout.write('finishing %s ... ' % story.get_id()) story.finish() print 'OK'
def test_custom_branchnames(self): gitflow = GitFlow(self.repo).init() self.assertEquals('production', gitflow.master_name()) self.assertEquals('master', gitflow.develop_name()) self.assertEquals('origin', gitflow.origin_name()) self.assertEquals('f-', gitflow.get_prefix('feature')) self.assertEquals('rel-', gitflow.get_prefix('release')) self.assertEquals('hf-', gitflow.get_prefix('hotfix')) self.assertEquals('supp-', gitflow.get_prefix('support')) self.assertEquals('v', gitflow.get_prefix('versiontag'))
def test_gitflow_init_initializes_default_config(self): repo = create_git_repo(self) gitflow = GitFlow(repo).init() self.assertEquals('master', gitflow.master_name()) self.assertEquals('develop', gitflow.develop_name()) self.assertEquals('origin', gitflow.origin_name()) self.assertEquals('feature/', gitflow.get_prefix('feature')) self.assertEquals('hotfix/', gitflow.get_prefix('hotfix')) self.assertEquals('release/', gitflow.get_prefix('release')) self.assertEquals('support/', gitflow.get_prefix('support')) self.assertEquals('', gitflow.get_prefix('versiontag'))
def test_gitflow_init_config_with_partly_inited(self): gitflow = GitFlow(self.repo).init() # Already set in fixture, shouldn't change self.assertEquals('production', gitflow.master_name()) self.assertEquals('f-', gitflow.get_prefix('feature')) # Implicit defaults self.assertEquals('develop', gitflow.develop_name()) self.assertEquals('origin', gitflow.origin_name()) self.assertEquals('release/', gitflow.get_prefix('release')) self.assertEquals('hotfix/', gitflow.get_prefix('hotfix')) self.assertEquals('support/', gitflow.get_prefix('support')) self.assertEquals('', gitflow.get_prefix('versiontag'))
def test_gitflow_force_reinit_partly_inited(self): gitflow = GitFlow(self.repo) gitflow.init(force_defaults=True) # Implicit defaults self.assertEquals('develop', gitflow.develop_name()) self.assertEquals('origin', gitflow.origin_name()) self.assertEquals('release/', gitflow.get_prefix('release')) self.assertEquals('hotfix/', gitflow.get_prefix('hotfix')) self.assertEquals('support/', gitflow.get_prefix('support')) self.assertEquals('', gitflow.get_prefix('versiontag')) # Explicitly forced back to defaults self.assertEquals('master', gitflow.master_name()) self.assertEquals('feature/', gitflow.get_prefix('feature'))
def run_append(args): # Print info and ask for confirmation. pivotal.prompt_user_to_confirm_release(args.version) # Merge, push and insert PT labels. gitflow = GitFlow() git = gitflow.git current_branch = gitflow.repo.active_branch develop = gitflow.develop_name() gitflow.name_or_current('release', args.version) release = gitflow.get_prefix('release') + str(args.version) print('') sys.stdout.write('Merging develop into ' + release + ' ... ') gitflow.checkout('release', str(args.version)) git.merge(gitflow.develop_name()) print('OK') sys.stdout.write('Pushing ' + release + ' ... ') gitflow.origin().push([release]) print('OK') sys.stdout.write('Moving back to ' + str(current_branch) + ' ... ') current_branch.checkout() print('OK') sys.stdout.write('Marking Pivotal Tracker stories ... ') pivotal.start_release(args.version) print('OK')
def test_gitflow_init_with_alternative_config(self): repo = create_git_repo(self) gitflow = GitFlow(repo) prefixes = dict(feature='f-', hotfix='hf-', release='rel-', support='supp-', versiontag='ver') names = dict(origin='somewhereelse') gitflow.init(master='foo', develop='bar', prefixes=prefixes, names=names) self.assertEquals('foo', gitflow.master_name()) self.assertEquals('bar', gitflow.develop_name()) self.assertEquals('somewhereelse', gitflow.origin_name()) self.assertEquals('f-', gitflow.get_prefix('feature')) self.assertEquals('rel-', gitflow.get_prefix('release')) self.assertEquals('hf-', gitflow.get_prefix('hotfix')) self.assertEquals('supp-', gitflow.get_prefix('support')) self.assertEquals('ver', gitflow.get_prefix('versiontag'))
class Release(object): def __init__(self, stories): self._G = GitFlow() self._stories = stories def try_stage(self, ignore_missing_reviews): assert self._stories feature_prefix = self._G.get_prefix('feature') self._reviews = [] reviews_expected = 0 err = None for story in self._stories: label = None for l in ('no review', 'dupe', 'wontfix', 'cannot reproduce'): if story.is_labeled(l): label = l print(" Story {0} labeled '{1}', skipping...".format( story.get_id(), l)) break if label is not None: continue prefix = feature_prefix + str(story.get_id()) try: reviews_expected += 1 review = BranchReview.from_prefix(prefix) review.verify_submit() print(' ' + str(review.get_id())) self._reviews.append(review) except (ReviewNotAcceptedYet, NoSuchBranchError) as e: print(' ' + str(e)) except Exception as e: print(' ' + str(e)) err = e if err is not None: raise SystemExit(str(err)) if not ignore_missing_reviews and len( self._reviews) != reviews_expected: raise SystemExit('Some stories have not been reviewed yet,' \ ' aborting...') try_finish = try_stage def finish(self): assert self._reviews is not None for review in self._reviews: review.submit()
class Release(object): def __init__(self, stories): self._G = GitFlow() self._stories = stories def try_stage(self, ignore_missing_reviews): assert self._stories feature_prefix = self._G.get_prefix('feature') self._reviews = [] reviews_expected = 0 err = None for story in self._stories: label = None for l in ('no review', 'dupe', 'wontfix', 'cannot reproduce'): if story.is_labeled(l): label = l print(" Story {0} labeled '{1}', skipping...".format(story.get_id(), l)) break if label is not None: continue prefix = feature_prefix + str(story.get_id()) try: reviews_expected += 1 review = BranchReview.from_prefix(prefix) review.verify_submit() print(' ' + str(review.get_id())) self._reviews.append(review) except (ReviewNotAcceptedYet, NoSuchBranchError) as e: print(' ' + str(e)) except Exception as e: print(' ' + str(e)) err = e if err is not None: raise SystemExit(str(err)) if not ignore_missing_reviews and len(self._reviews) != reviews_expected: raise SystemExit('Some stories have not been reviewed yet,' \ ' aborting...') try_finish = try_stage def finish(self): assert self._reviews is not None for review in self._reviews: review.submit()
class Release(object): def __init__(self, stories): self._G = GitFlow() self._stories = stories def try_deliver(self, ignore_missing_reviews): assert self._stories feature_prefix = self._G.get_prefix('feature') self._reviews = [] reviews_expected = 0 err = None for story in self._stories: prefix = feature_prefix + str(story.get_id()) try: reviews_expected += 1 review = BranchReview.from_prefix(prefix) review.verify_submit() print(' ' + str(review.get_id())) self._reviews.append(review) except (ReviewNotAcceptedYet, NoSuchBranchError) as e: print(' ' + str(e)) except Exception as e: print(' ' + str(e)) err = e if err is not None: raise SystemExit('An error detected, aborting...') if not ignore_missing_reviews and len(self._reviews) != reviews_expected: raise SystemExit('Some stories have not been reviewed yet,' \ ' aborting...') try_finish = try_deliver def deliver(self): assert self._reviews is not None for review in self._reviews: review.submit()
def run_finish(args): gitflow = GitFlow() repo = gitflow.repo git = repo.git f_prefix = gitflow.get_prefix('feature') name = gitflow.nameprefix_or_current('feature', args.nameprefix) full_name = f_prefix + name #+++ PT story stuff sys.stdout.write('Getting data from Pivotal Tracker ... ') story_id = pivotal.get_story_id_from_branch_name('feature', name) story = pivotal.Story(story_id) release = story.get_release() print 'OK' # Decide on the upstream branch. if release: # Merge into the release branch. r_prefix = gitflow.get_prefix('release') short_name = gitflow.nameprefix_or_current('release', release) upstream = r_prefix + short_name else: # Merge into the develop branch. upstream = gitflow.develop_name() # Fail as soon as possible if something is not right so that we don't # get Pivotal Tracker into an inconsistent state. rev_range = [ get_feature_ancestor(full_name, upstream), repo.commit(full_name).hexsha ] #+++ Git manipulation sys.stdout.write('Finishing feature branch ... upstream %s ... ' \ % upstream) gitflow.finish('feature', name, upstream=upstream, fetch=True, rebase=args.rebase, keep=True, force_delete=args.force_delete, tagging_info=None, push=(not args.no_push)) print 'OK' #+++ Review Request if not args.no_review: sys.stdout.write('Posting review ... upstream %s ... ' % upstream) review = BranchReview.from_identifier('feature', name, rev_range) review.post(story, summary_from_story=(not args.summary_from_commit)) print('OK') sys.stdout.write( 'Posting code review url into Pivotal Tracker ... ') comment = 'New patch was uploaded into Review Board: ' + review.get_url( ) story.add_comment(comment) print('OK') #+++ Finish PT story sys.stdout.write('Updating Pivotal Tracker ... ') if story.is_finished(): sys.stdout.write('story already finished, skipping ... ') else: sys.stdout.write('finishing %s ... ' % story.get_id()) story.finish() print 'OK'
def run_finish(args): gitflow = GitFlow() git = gitflow.git origin = gitflow.origin() version = gitflow.name_or_current('release', args.version) #+++ Check if all stories were QA'd pt_release = pivotal.Release(args.version) print('Checking Pivotal Tracker stories ... ') pt_release.try_finish() print('OK') #+++ Check all relevant review requests in Review Board, to be sure rb_release = review.Release(pt_release) print('Checking Review Board review requests ... ') rb_release.try_finish(args.ignore_missing_reviews) print('OK') #+++ Merge release branch into develop and master sys.stdout.write('Merging release branch %s ... ' % version) tagging_info = None if not args.notag: tagging_info = dict(sign=args.sign or args.signingkey, signingkey=args.signingkey, message=args.message) gitflow.finish('release', version, fetch=(not args.no_fetch), rebase=False, keep=True, force_delete=False, tagging_info=tagging_info, push=False) print('OK') #+++ Close all relevant review requests sys.stdout.write('Submitting all relevant review requests ... ') rb_release.finish() print('OK') #+++ Collect local and remote branches to be deleted sys.stdout.write('Collecting branches to be deleted ... ') local_branches = list() remote_branches = list() #+ Collect features to be deleted. origin_prefix = str(origin) + '/' feature_prefix = gitflow.get_prefix('feature') # refs = [<type>/<id>/...] refs = [str(ref)[len(origin_prefix):] for ref in origin.refs] for story in pt_release: if story.is_rejected(): continue # prefix = <feature-prefix>/<id> prefix = feature_prefix + str(story.get_id()) base_marker = gitflow.managers['feature'].base_marker_name(prefix) try: name = gitflow.nameprefix_or_current('feature', prefix) local_branches.append(feature_prefix + name) if base_marker in gitflow.repo.refs: local_branches.append(base_marker) except NoSuchBranchError: pass for ref in refs: # if <feature-prefix>/... startswith <feature-prefix>/<id> if ref.startswith(prefix) or ref == base_marker: remote_branches.append(ref) #+ Collect releases to be deleted. if not args.keep: release_branch = gitflow.get_prefix('release') + version try: gitflow.name_or_current('release', version) local_branches.append(release_branch) except NoSuchBranchError: pass if release_branch in refs: remote_branches.append(release_branch) print 'OK' #+++ Delete local and remote branches that are a part of this release sys.stdout.write('Checking out %s ... ' % gitflow.develop_name()) git.checkout(gitflow.develop_name()) print 'OK' #+ Delete local branches. print 'Deleting local branches ...' for branch in local_branches: git.branch('-D', branch) print ' ' + branch print ' OK' #+ Delete remote branches. print 'Deleting remote branches and pushing the rest ...' for branch in remote_branches: print ' ' + branch refspecs = [(':' + b) for b in remote_branches] refspecs.append(gitflow.develop_name()) refspecs.append(gitflow.master_name()) git.push(str(origin), '--tags', *refspecs) print ' OK'
def run_start(args): if args.for_release: pivotal.check_version_format(args.for_release) gitflow = GitFlow() git = gitflow.git base = None if args.for_release is not None: # Make sure --for-release matches the requirements. pivotal.check_version_format(args.for_release) base = gitflow.get_prefix('release') + args.for_release else: base = gitflow.managers['feature'].default_base() if not args.no_fetch: sys.stderr.write('Fetching origin ... ') gitflow.origin().fetch() print 'OK' # Check if the base exists and is in sync as soon as possible. sys.stdout.write('Checking the base branch ({0}) ... '.format(base)) origin_base = gitflow.require_origin_branch(base) try: gitflow.must_be_uptodate(base) except NoSuchBranchError: sys.stdout.write('found remote counterpart ... ') git.branch(base, origin_base.name) print('OK') [story, name] = pivotal.prompt_user_to_select_story(match=args.match) sys.stdout.write('Setting myself as the story owner ... ') try: story.set_me_as_owner() except: print('FAIL') print('OK') if args.for_release is not None: sys.stdout.write('Assigning the chosen story to release {0} ... '.format(args.for_release)) story.assign_to_release(args.for_release) print('OK') if story.is_rejected(): sid = str(story.get_id()) gitflow.start_transaction('restart story {0}'.format(sid)) sys.stdout.write('Checking out the feature branch ... ') try: gitflow.checkout('feature', sid) print('OK') except NoSuchBranchError as e: print('FAIL') raise InconsistencyDetected( 'The branch is missing for story {0}.'.format(sid)) sys.stdout.write('Updating Pivotal Tracker ... ') story.start() print('OK') return # :fixme: Why does the sh-version not require a clean working dir? gitflow.start_transaction('start feature branch %s (from %s)' % \ (name, base)) try: # fetch=False because we are already fetching at the beginning. branch = gitflow.create('feature', name, base, fetch=False) except (NotInitialized, BaseNotOnBranch): # printed in main() raise except Exception, e: die("Could not create feature branch %r" % name, e)
def run_finish(args): gitflow = GitFlow() git = gitflow.git origin = gitflow.origin() version = gitflow.name_or_current('release', args.version) #+++ Check if all stories were QA'd pt_release = pivotal.Release(args.version) print('Checking Pivotal Tracker stories ... ') pt_release.try_finish() print('OK') #+++ Check all relevant review requests in Review Board, to be sure rb_release = review.Release(pt_release) print('Checking Review Board review requests ... ') rb_release.try_finish(args.ignore_missing_reviews) print('OK') #+++ Merge release branch into develop and master sys.stdout.write('Merging release branch %s ... ' % version) tagging_info = None if not args.notag: tagging_info = dict( sign=args.sign or args.signingkey, signingkey=args.signingkey, message=args.message) gitflow.finish('release', version, fetch=(not args.no_fetch), rebase=False, keep=True, force_delete=False, tagging_info=tagging_info, push=False) print('OK') #+++ Close all relevant review requests sys.stdout.write('Submitting all relevant review requests ... ') rb_release.finish() print('OK') #+++ Collect local and remote branches to be deleted sys.stdout.write('Collecting branches to be deleted ... ') local_branches = list() remote_branches = list() #+ Collect features to be deleted. origin_prefix = str(origin) + '/' feature_prefix = gitflow.get_prefix('feature') # refs = [<type>/<id>/...] refs = [str(ref)[len(origin_prefix):] for ref in origin.refs] for story in pt_release: if story.is_rejected(): continue # prefix = <feature-prefix>/<id> prefix = feature_prefix + str(story.get_id()) base_marker = gitflow.managers['feature'].base_marker_name(prefix) try: name = gitflow.nameprefix_or_current('feature', prefix) local_branches.append(feature_prefix + name) if base_marker in gitflow.repo.refs: local_branches.append(base_marker) except NoSuchBranchError: pass for ref in refs: # if <feature-prefix>/... startswith <feature-prefix>/<id> if ref.startswith(prefix) or ref == base_marker: remote_branches.append(ref) #+ Collect releases to be deleted. if not args.keep: release_branch = gitflow.get_prefix('release') + version try: gitflow.name_or_current('release', version) local_branches.append(release_branch) except NoSuchBranchError: pass if release_branch in refs: remote_branches.append(release_branch) print 'OK' #+++ Delete local and remote branches that are a part of this release sys.stdout.write('Checking out %s ... ' % gitflow.develop_name()) git.checkout(gitflow.develop_name()) print 'OK' #+ Delete local branches. print 'Deleting local branches ...' for branch in local_branches: git.branch('-D', branch) print ' ' + branch print ' OK' #+ Delete remote branches. print 'Deleting remote branches and pushing the rest ...' for branch in remote_branches: print ' ' + branch refspecs = [(':' + b) for b in remote_branches] refspecs.append(gitflow.develop_name()) refspecs.append(gitflow.master_name()) git.push(str(origin), '--tags', *refspecs) print ' OK'
def run_start(args): if args.for_release: pivotal.check_version_format(args.for_release) gitflow = GitFlow() git = gitflow.git base = None if args.for_release is not None: # Make sure --for-release matches the requirements. pivotal.check_version_format(args.for_release) base = gitflow.get_prefix('release') + args.for_release else: base = gitflow.managers['feature'].default_base() if not args.no_fetch: sys.stderr.write('Fetching origin ... ') gitflow.origin().fetch() print 'OK' # Check if the base exists and is in sync as soon as possible. sys.stdout.write('Checking the base branch ({0}) ... '.format(base)) origin_base = gitflow.require_origin_branch(base) try: gitflow.must_be_uptodate(base) except NoSuchBranchError: sys.stdout.write('found remote counterpart ... ') git.branch(base, origin_base.name) print('OK') [story, name] = pivotal.prompt_user_to_select_story(match=args.match) sys.stdout.write('Setting myself as the story owner ... ') try: story.set_me_as_owner() except: print('FAIL') print('OK') if args.for_release is not None: sys.stdout.write( 'Assigning the chosen story to release {0} ... '.format( args.for_release)) story.assign_to_release(args.for_release) print('OK') if story.is_rejected(): sid = str(story.get_id()) gitflow.start_transaction('restart story {0}'.format(sid)) sys.stdout.write('Checking out the feature branch ... ') try: gitflow.checkout('feature', sid) print('OK') except NoSuchBranchError as e: print('FAIL') raise InconsistencyDetected( 'The branch is missing for story {0}.'.format(sid)) sys.stdout.write('Updating Pivotal Tracker ... ') story.start() print('OK') return # :fixme: Why does the sh-version not require a clean working dir? gitflow.start_transaction('start feature branch %s (from %s)' % \ (name, base)) try: # fetch=False because we are already fetching at the beginning. branch = gitflow.create('feature', name, base, fetch=False) except (NotInitialized, BaseNotOnBranch): # printed in main() raise except Exception, e: die("Could not create feature branch %r" % name, e)