def call(context: Context) -> Result: command_context = get_command_context( context=context, object_arg=context.args['<work-branch>'] ) check_in_repo(command_context) object_arg = context.args['<object>'] args = context.args['<git-arg>'] if object_arg is not None: selected_branch = get_branch_by_branch_name_or_version_tag(context, object_arg, BranchSelection.BRANCH_PREFER_LOCAL) if selected_branch is None: command_context.fail(os.EX_USAGE, _("Log failed."), _("Failed to resolve an object for token {object}.") .format(object=repr(object_arg)) ) else: selected_branch = None log_command = ['log'] if context.pretty: log_command.append('--pretty') if context.dry_run: log_command.append('--dry-run') if context.verbose: log_command.append('--verbose') if selected_branch is not None: log_command.append(selected_branch) proc = repotools.git_interactive(context.repo, *(log_command + args)) proc.wait() return context.result
def call(context) -> Result: command_context = get_command_context(context=context, object_arg=context.args['<object>']) check_in_repo(command_context) unique_codes = set() unique_version_codes = list() upstreams = repotools.git_get_upstreams(context.repo) branch_info_dict = dict() if context.args['--all'] > 0: selected_refs = repotools.git_list_refs( context.repo, repotools.create_ref_name(const.REMOTES_PREFIX, context.config.remote_name)) else: selected_refs = [ command_context.selected_ref or command_context.current_branch ] for branch_ref in selected_refs: branch_match = context.release_branch_matcher.fullmatch( branch_ref.name) if branch_match: branch_version = context.release_branch_matcher.to_version( branch_ref.name) branch_version_string = get_branch_version_component_for_version( context, branch_version) discontinuation_tags, discontinuation_tag_name = get_discontinuation_tags( context, branch_ref) update_branch_info(context, branch_info_dict, upstreams, branch_ref) branch_info = branch_info_dict.get(branch_ref.name) discontinued = len(discontinuation_tags) if discontinued: status_color = colors.partial(colors.color, fg='gray') status_error_color = colors.partial(colors.color, fg='red') status_local_color = colors.partial(colors.color, fg='blue') status_remote_color = colors.partial(colors.color, fg='green') else: status_color = colors.partial(colors.color, fg='white', style='bold') status_error_color = colors.partial(colors.color, fg='red', style='bold') status_local_color = colors.partial(colors.color, fg='blue', style='bold') status_remote_color = colors.partial(colors.color, fg='green', style='bold') error_color = colors.partial(colors.color, fg='white', bg='red', style='bold') cli.fcwrite(sys.stdout, status_color, "version: " + branch_version_string + ' [') if branch_info.local is not None: i = 0 for local in branch_info.local: local_branch_color = status_local_color if not branch_info.upstream.short_name.endswith( '/' + local.short_name): command_context.error( os.EX_DATAERR, _("Local and upstream branch have a mismatching short name." ), None) local_branch_color = error_color if i: cli.fcwrite(sys.stdout, status_color, ', ') if context.verbose: cli.fcwrite(sys.stdout, local_branch_color, local.name) else: cli.fcwrite(sys.stdout, local_branch_color, local.short_name) i += 1 if branch_info.upstream is not None: if branch_info.local is not None and len(branch_info.local): cli.fcwrite(sys.stdout, status_color, ' => ') if context.verbose: cli.fcwrite(sys.stdout, status_remote_color, branch_info.upstream.name) else: cli.fcwrite(sys.stdout, status_remote_color, branch_info.upstream.short_name) cli.fcwrite(sys.stdout, status_color, "]") if discontinued: cli.fcwrite(sys.stdout, status_color, ' (' + _('discontinued') + ')') cli.fcwriteln(sys.stdout, status_color) commit_tags = repotools.git_get_branch_tags( context=context.repo, base_branch=context.config.release_branch_base, branch=branch_ref.name, tag_filter=None, commit_tag_comparator=None) for commit, tags in commit_tags: for tag in tags: if context.version_tag_matcher.group_unique_code is not None: tag_match = context.version_tag_matcher.fullmatch( tag.name) if tag_match is not None: unique_code = tag_match.group( context.version_tag_matcher.group_unique_code) version_string = unique_code unique_version_codes.append(int(unique_code)) if unique_code in unique_codes: command_context.error( os.EX_DATAERR, _("Invalid sequential version tag {tag}."). format(tag=tag.name), _("The code element of version {version_string} is not unique." ).format(version_string=version_string)) else: unique_codes.add(unique_code) cli.fcwriteln(sys.stdout, status_color, " code: " + version_string) # print the version tag version_string = context.version_tag_matcher.format( tag.name) if version_string: version_info = semver.parse_version_info( version_string) if version_info.major == branch_version.major and version_info.minor == branch_version.minor: cli.fcwriteln(sys.stdout, status_color, " " + version_string) else: command_context.error( os.EX_DATAERR, _("Invalid version tag {tag}.").format( tag=repr(tag.name)), _("The major.minor part of the new version {new_version}" " does not match the branch version {branch_version}." ).format(new_version=repr(version_string), branch_version=repr( branch_version_string))) cli.fcwriteln(sys.stdout, status_error_color, " " + version_string) unique_version_codes.sort( key=utils.cmp_to_key(lambda a, b: version.cmp_alnum_token(a, b))) last_unique_code = None for unique_code in unique_version_codes: if not (last_unique_code is None or unique_code > last_unique_code): command_context.error( os.EX_DATAERR, _("Version {version} breaks the sequence.").format( version=unique_code), None) last_unique_code = unique_code return context.result
def call(context: Context) -> Result: command_context = get_command_context( context=context, object_arg=context.args['<base-object>']) check_in_repo(command_context) check_requirements( command_context=command_context, ref=command_context.selected_ref, branch_classes=None, modifiable=True, with_upstream=True, # not context.config.push_to_local in_sync_with_upstream=True, fail_message=_("Version creation failed.")) selected_work_branch = context.args.get('<work-branch>') if selected_work_branch is not None: selected_work_branch = repotools.create_ref_name(selected_work_branch) if not selected_work_branch.startswith(const.LOCAL_BRANCH_PREFIX): selected_work_branch = const.LOCAL_BRANCH_PREFIX + selected_work_branch branch_match = context.work_branch_matcher.fullmatch( selected_work_branch) if branch_match is None: context.fail( os.EX_USAGE, _("Invalid work branch: {branch}.").format( branch=repr(selected_work_branch)), None) groups = branch_match.groupdict() branch_supertype = groups['prefix'] branch_type = groups['type'] branch_short_name = groups['name'] else: branch_supertype = context.args['<supertype>'] branch_type = context.args['<type>'] branch_short_name = context.args['<name>'] if branch_supertype not in [ const.BRANCH_PREFIX_DEV, const.BRANCH_PREFIX_PROD ]: context.fail( os.EX_USAGE, _("Invalid branch super type: {supertype}.").format( supertype=repr(branch_supertype)), None) work_branch_name = repotools.create_ref_name(branch_supertype, branch_type, branch_short_name) work_branch_ref_name = repotools.create_ref_name(const.LOCAL_BRANCH_PREFIX, work_branch_name) work_branch_class = get_branch_class(context, work_branch_ref_name) if True: work_branch_info = get_branch_info(command_context, work_branch_ref_name) if work_branch_info is not None: context.fail( os.EX_USAGE, _("The branch {branch} already exists locally or remotely."). format(branch=repr(work_branch_name)), None) allowed_base_branch_class = const.BRANCHING[work_branch_class] base_branch, base_branch_class = select_ref( command_context.result, command_context.selected_branch, BranchSelection.BRANCH_PREFER_LOCAL) if not command_context.selected_explicitly and branch_supertype == const.BRANCH_PREFIX_DEV: base_branch_info = get_branch_info( command_context, repotools.create_ref_name(const.LOCAL_BRANCH_PREFIX, context.config.release_branch_base)) base_branch, base_branch_class = select_ref( command_context.result, base_branch_info, BranchSelection.BRANCH_PREFER_LOCAL) if allowed_base_branch_class != base_branch_class: context.fail( os.EX_USAGE, _("The branch {branch} is not a valid base for {supertype} branches." ).format(branch=repr(base_branch.name), supertype=repr(branch_supertype)), None) if base_branch is None: context.fail(os.EX_USAGE, _("Base branch undetermined."), None) if context.verbose: cli.print("branch_name: " + command_context.selected_ref.name) cli.print("work_branch_name: " + work_branch_name) cli.print("base_branch_name: " + base_branch.name) if not context.dry_run and not command_context.has_errors(): index_status = git(context.repo, ['diff-index', 'HEAD', '--']) if index_status == 1: context.fail( os.EX_USAGE, _("Branch creation aborted."), _("You have staged changes in your workspace.\n" "Unstage, commit or stash them and try again.")) elif index_status != 0: context.fail(os.EX_DATAERR, _("Failed to determine index status."), None) git_or_fail( context.repo, command_context.result, [ 'update-ref', work_branch_ref_name, command_context.selected_commit, '' ], _("Failed to create branch {branch_name}.").format( branch_name=work_branch_name)) git_or_fail( context.repo, command_context.result, ['checkout', work_branch_name], _("Failed to checkout branch {branch_name}.").format( branch_name=work_branch_name)) return context.result
def call(context: Context) -> Result: arg_work_branch = context.args.get('<work-branch>') if arg_work_branch is None: branch_prefix = context.args['<supertype>'] branch_type = context.args['<type>'] branch_name = context.args['<name>'] if branch_prefix is not None or branch_type is not None or branch_name is not None: arg_work_branch = repotools.create_ref_name(branch_prefix, branch_type, branch_name) command_context = get_command_context( context=context, object_arg=arg_work_branch ) check_in_repo(command_context) base_command_context = get_command_context( context=context, object_arg=context.args['<base-object>'] ) check_requirements(command_context=command_context, ref=command_context.selected_ref, branch_classes=[BranchClass.WORK_DEV, BranchClass.WORK_PROD], modifiable=True, with_upstream=True, # not context.config.push_to_local in_sync_with_upstream=True, fail_message=_("Version creation failed.") ) work_branch = None selected_ref_match = context.work_branch_matcher.fullmatch(command_context.selected_ref.name) if selected_ref_match is not None: work_branch = WorkBranch() work_branch.prefix = selected_ref_match.group('prefix') work_branch.type = selected_ref_match.group('type') work_branch.name = selected_ref_match.group('name') else: if command_context.selected_explicitly: context.fail(os.EX_USAGE, _("The ref {branch} does not refer to a work branch.") .format(branch=repr(command_context.selected_ref.name)), None) work_branch_info = get_branch_info(command_context, work_branch.local_ref_name()) if work_branch_info is None: context.fail(os.EX_USAGE, _("The branch {branch} does neither exist locally nor remotely.") .format(branch=repr(work_branch.branch_name())), None) work_branch_ref, work_branch_class = select_ref(command_context.result, work_branch_info, BranchSelection.BRANCH_PREFER_LOCAL) allowed_base_branch_class = const.BRANCHING[work_branch_class] base_branch_info = get_branch_info(base_command_context, base_command_context.selected_ref) base_branch_ref, base_branch_class = select_ref(command_context.result, base_branch_info, BranchSelection.BRANCH_PREFER_LOCAL) if not base_command_context.selected_explicitly: if work_branch.prefix == const.BRANCH_PREFIX_DEV: base_branch_info = get_branch_info(base_command_context, repotools.create_ref_name(const.LOCAL_BRANCH_PREFIX, context.config.release_branch_base)) base_branch_ref, base_branch_class = select_ref(command_context.result, base_branch_info, BranchSelection.BRANCH_PREFER_LOCAL) elif work_branch.prefix == const.BRANCH_PREFIX_PROD: # discover closest merge base in release branches release_branches = repotools.git_list_refs(context.repo, repotools.create_ref_name(const.REMOTES_PREFIX, context.config.remote_name, 'release')) release_branches = list(release_branches) release_branches.sort(reverse=True, key=utils.cmp_to_key(lambda ref_a, ref_b: semver.compare( context.release_branch_matcher.format(ref_a.name), context.release_branch_matcher.format(ref_b.name) ))) for release_branch_ref in release_branches: merge_base = repotools.git_merge_base(context.repo, base_branch_ref, work_branch_ref.name) if merge_base is not None: base_branch_info = get_branch_info(base_command_context, release_branch_ref) base_branch_ref, base_branch_class = select_ref(command_context.result, base_branch_info, BranchSelection.BRANCH_PREFER_LOCAL) break if allowed_base_branch_class != base_branch_class: context.fail(os.EX_USAGE, _("The branch {branch} is not a valid base for {supertype} branches.") .format(branch=repr(base_branch_ref.name), supertype=repr(work_branch.prefix)), None) if base_branch_ref is None: context.fail(os.EX_USAGE, _("Base branch undetermined."), None) if context.verbose: cli.print("branch_name: " + command_context.selected_ref.name) cli.print("work_branch_name: " + work_branch_ref.name) cli.print("base_branch_name: " + base_branch_ref.name) # check, if already merged merge_base = repotools.git_merge_base(context.repo, base_branch_ref, work_branch_ref.name) if work_branch_ref.obj_name == merge_base: cli.print(_("Branch {branch} is already merged.") .format(branch=repr(work_branch_ref.name))) return context.result # check for staged changes index_status = git(context.repo, ['diff-index', 'HEAD', '--']) if index_status == 1: context.fail(os.EX_USAGE, _("Branch creation aborted."), _("You have staged changes in your workspace.\n" "Unstage, commit or stash them and try again.")) elif index_status != 0: context.fail(os.EX_DATAERR, _("Failed to determine index status."), None) if not context.dry_run and not command_context.has_errors(): # perform merge local_branch_ref_name = repotools.create_local_branch_ref_name(base_branch_ref.name) local_branch_name = repotools.create_local_branch_name(base_branch_ref.name) if local_branch_ref_name == base_branch_ref.name: git_or_fail(context.repo, command_context.result, ['checkout', local_branch_name], _("Failed to checkout branch {branch_name}.") .format(branch_name=repr(base_branch_ref.short_name)) ) else: git_or_fail(context.repo, command_context.result, ['checkout', '-b', local_branch_name, base_branch_ref.name], _("Failed to checkout branch {branch_name}.") .format(branch_name=repr(base_branch_ref.short_name)) ) git_or_fail(context.repo, command_context.result, ['merge', '--no-ff', work_branch_ref], _("Failed to merge work branch.\n" "Rebase {work_branch} on {base_branch} and try again") .format(work_branch=repr(work_branch_ref.short_name), base_branch=repr(base_branch_ref.short_name)) ) git_or_fail(context.repo, command_context.result, ['push', context.config.remote_name, local_branch_name], _("Failed to push branch {branch_name}.") .format(branch_name=repr(base_branch_ref.short_name)) ) return context.result
def call(context: Context) -> Result: result: Result = context.result object_arg = context.args['<object>'] reintegrate = cli.get_boolean_opt(context.args, '--reintegrate') command_context = get_command_context(context=context, object_arg=context.args['<object>']) check_in_repo(command_context) base_branch_ref = repotools.get_branch_by_name( context.repo, {context.config.remote_name}, context.config.release_branch_base, BranchSelection.BRANCH_PREFER_LOCAL) release_branch = command_context.selected_ref release_branch_info = get_branch_info(command_context, release_branch) check_requirements( command_context=command_context, ref=release_branch, branch_classes=[BranchClass.RELEASE], modifiable=True, with_upstream=True, # not context.config.push_to_local in_sync_with_upstream=True, fail_message=_("Build failed.")) if release_branch is None: command_context.fail( os.EX_USAGE, _("Branch discontinuation failed."), _("Failed to resolve an object for token {object}.").format( object=repr(object_arg))) discontinuation_tags, discontinuation_tag_name = get_discontinuation_tags( context, release_branch) if discontinuation_tag_name is None: command_context.fail( os.EX_USAGE, _("Branch discontinuation failed."), _("{branch} cannot be discontinued.").format( branch=repr(release_branch.name))) if context.verbose: cli.print("discontinuation tags:") for discontinuation_tag in discontinuation_tags: print(' - ' + discontinuation_tag.name) pass if len(discontinuation_tags): command_context.fail( os.EX_USAGE, _("Branch discontinuation failed."), _("The branch {branch} is already discontinued.").format( branch=repr(release_branch.name))) # show info and prompt for confirmation print("discontinued_branch : " + cli.if_none(release_branch.name)) if reintegrate is None: prompt_result = prompt( context=context, message=_("Branches may be reintegrated upon discontinuation."), prompt=_("Do you want to reintegrate {branch} into {base_branch}?" ).format(branch=repr(release_branch.short_name), base_branch=repr(base_branch_ref.short_name)), ) command_context.add_subresult(prompt_result) if command_context.has_errors(): return context.result reintegrate = prompt_result.value if not command_context.has_errors(): # run merge on local clone clone_result = clone_repository(context, context.config.release_branch_base) clone_context: Context = create_temp_context(context, result, clone_result.value.dir) clone_context.config.remote_name = 'origin' changes = list() if reintegrate: git_or_fail( clone_context.repo, command_context.result, ['checkout', base_branch_ref.short_name], _("Failed to checkout branch {branch_name}.").format( branch_name=repr(base_branch_ref.short_name))) git_or_fail( clone_context.repo, command_context.result, ['merge', '--no-ff', release_branch_info.upstream.name], _("Failed to merge work branch.\n" "Rebase {work_branch} on {base_branch} and try again" ).format(work_branch=repr(release_branch.short_name), base_branch=repr(base_branch_ref.short_name))) changes.append( _("{branch} reintegrated into {base_branch}").format( branch=repr(release_branch.name), base_branch=repr(base_branch_ref.name))) changes.append(_("Discontinuation tag")) prompt_result = prompt_for_confirmation( context=context, fail_title=_("Failed to discontinue {branch}.").format( branch=repr(release_branch.name)), message=(" - " + (os.linesep + " - ").join([_("Changes to be pushed:")] + changes)), prompt=_("Continue?"), ) command_context.add_subresult(prompt_result) if command_context.has_errors() or not prompt_result.value: return context.result push_command = ['push', '--atomic'] if context.dry_run: push_command.append('--dry-run') if context.verbose: push_command.append('--verbose') push_command.append(context.config.remote_name) push_command.append( base_branch_ref.name + ':' + repotools.create_ref_name( const.LOCAL_BRANCH_PREFIX, base_branch_ref.short_name)) push_command.append('--force-with-lease=' + repotools.create_ref_name( const.LOCAL_TAG_PREFIX, discontinuation_tag_name) + ':') push_command.append( repotools.ref_target(release_branch) + ':' + repotools.create_ref_name(const.LOCAL_TAG_PREFIX, discontinuation_tag_name)) git_or_fail(clone_context.repo, command_context.result, push_command) fetch_all_and_ff(context.repo, command_context.result, context.config.remote_name) return context.result
def call(context: Context, operation: Callable[[VersionConfig, Optional[str], Optional[int]], Result]) -> Result: command_context = get_command_context( context=context, object_arg=context.args['<object>'] ) check_in_repo(command_context) # determine the type of operation to be performed and run according subroutines if operation == scheme_procedures.version_bump_major \ or operation == scheme_procedures.version_bump_minor: check_requirements(command_context=command_context, ref=command_context.selected_ref, branch_classes=[BranchClass.DEVELOPMENT_BASE], modifiable=True, with_upstream=True, # not context.config.push_to_local in_sync_with_upstream=True, fail_message=_("Version creation failed.") ) tag_result = create_version_branch(command_context, operation) command_context.add_subresult(tag_result) elif operation == scheme_procedures.version_bump_patch \ or operation == scheme_procedures.version_bump_qualifier \ or operation == scheme_procedures.version_bump_prerelease \ or operation == scheme_procedures.version_bump_to_release: check_requirements(command_context=command_context, ref=command_context.selected_ref, branch_classes=[BranchClass.RELEASE], modifiable=True, with_upstream=True, # not context.config.push_to_local in_sync_with_upstream=True, fail_message=_("Version creation failed.") ) tag_result = create_version_tag(command_context, operation) command_context.add_subresult(tag_result) elif isinstance(operation, scheme_procedures.VersionSet): check_requirements(command_context=command_context, ref=command_context.selected_ref, branch_classes=None, modifiable=True, with_upstream=True, # not context.config.push_to_local in_sync_with_upstream=True, fail_message=_("Version creation failed.") ) version_result = operation(context.config.version_config, None, get_global_sequence_number(context)) command_context.add_subresult(version_result) new_version = version_result.value if new_version is None: command_context.fail(os.EX_USAGE, _("Illegal argument."), _("Failed to parse version.") ) new_version_info = semver.parse_version_info(new_version) branch_name = get_branch_name_for_version(context, new_version_info) release_branch = repotools.get_branch_by_name(context.repo, {context.config.remote_name}, branch_name, BranchSelection.BRANCH_PREFER_LOCAL) if release_branch is None: tag_result = create_version_branch(command_context, operation) command_context.add_subresult(tag_result) else: selected_ref = release_branch tag_result = create_version_tag(command_context, operation) command_context.add_subresult(tag_result) if not command_context.has_errors() \ and context.config.pull_after_bump \ and not context.config.push_to_local: fetch_all_and_ff(context.repo, command_context.result, context.config.remote_name) return context.result