def main(): conf = config.read_cfg() parser = cli(data) # Show help if no arg provided if len(sys.argv) == 1: parser.print_help(sys.stderr) raise SystemExit() try: args = parser.parse_args() except TypeError: out.error("Command is required") raise SystemExit() if args.name: conf.update({"name": args.name}) if args.version: warnings.warn( "'cz --version' will be deprecated in next major version. " "Please use 'cz version' command from your scripts") logging.getLogger("commitizen").setLevel(logging.DEBUG) if args.debug: warnings.warn("Debug will be deprecated in next major version. " "Please remove it from your scripts") logging.getLogger("commitizen").setLevel(logging.DEBUG) # TODO: This try block can be removed after command is required in 2.0 try: args.func(conf, vars(args))() except AttributeError: out.error("Command is required")
def _ask_tag(self) -> str: latest_tag = get_latest_tag_name() if not latest_tag: out.error("No Existing Tag. Set tag to v0.0.1") return "0.0.1" is_correct_tag = questionary.confirm( f"Is {latest_tag} the latest tag?", style=self.cz.style, default=False).ask() if not is_correct_tag: tags = get_tag_names() if not tags: out.error("No Existing Tag. Set tag to v0.0.1") return "0.0.1" latest_tag = questionary.select( "Please choose the latest tag: ", choices=get_tag_names(), # type: ignore style=self.cz.style, ).ask() if not latest_tag: raise NoAnswersError("Tag is required!") return latest_tag
def _valid_command_argument(self): if bool(self.commit_msg_file) is bool(self.rev_range): out.error( ( "One and only one argument is required for check command! " "See 'cz check -h' for more information" ) ) raise SystemExit()
def read_backup_message(self) -> str: # Check the commit backup file exists if not os.path.isfile(self.temp_file): out.error("No commit backup found") raise SystemExit(NO_COMMIT_BACKUP) # Read commit message from backup with open(self.temp_file, "r") as f: return f.read().strip()
def find_increment(self, commits: List[git.GitCommit]) -> Optional[str]: bump_pattern = self.cz.bump_pattern bump_map = self.cz.bump_map if not bump_map or not bump_pattern: out.error(f"'{self.config.settings['name']}' rule does not support bump") raise SystemExit(NO_PATTERN_MAP) increment = bump.find_increment( commits, regex=bump_pattern, increments_map=bump_map ) return increment
def commiter_factory(config: BaseConfig) -> BaseCommitizen: """Return the correct commitizen existing in the registry.""" name: str = config.settings["name"] try: _cz = registry[name](config) except KeyError: msg_error = ("The committer has not been found in the system.\n\n" f"Try running 'pip install {name}'\n") out.error(msg_error) raise SystemExit(NO_COMMITIZEN_FOUND) else: return _cz
def __call__(self): commit_parser = self.cz.commit_parser changelog_pattern = self.cz.changelog_pattern start_rev = self.start_rev unreleased_version = self.unreleased_version changelog_meta: Dict = {} if not changelog_pattern or not commit_parser: out.error( f"'{self.config.settings['name']}' rule does not support changelog" ) raise SystemExit(NO_PATTERN_MAP) tags = git.get_tags() if not tags: tags = [] if self.incremental: changelog_meta = changelog.get_metadata(self.file_name) latest_version = changelog_meta.get("latest_version") if latest_version: start_rev = self._find_incremental_rev(latest_version, tags) commits = git.get_commits(start=start_rev, args="--author-date-order") if not commits: out.error("No commits found") raise SystemExit(NO_COMMITS_FOUND) tree = changelog.generate_tree_from_commits(commits, tags, commit_parser, changelog_pattern, unreleased_version) changelog_out = changelog.render_changelog(tree) if self.dry_run: out.write(changelog_out) raise SystemExit(0) lines = [] if self.incremental and os.path.isfile(self.file_name): with open(self.file_name, "r") as changelog_file: lines = changelog_file.readlines() with open(self.file_name, "w") as changelog_file: if self.incremental: new_lines = changelog.incremental_build( changelog_out, lines, changelog_meta) changelog_file.writelines(new_lines) else: changelog_file.write(changelog_out)
def __init__(self, config: BaseConfig): super(CustomizeCommitsCz, self).__init__(config) if "customize" not in self.config.settings: out.error("fatal: customize is not set in configuration file.") raise SystemExit(MISSING_CONFIG) self.custom_settings = self.config.settings["customize"] custom_bump_pattern = self.custom_settings.get("bump_pattern") if custom_bump_pattern: self.bump_pattern = custom_bump_pattern custom_bump_map = self.custom_settings.get("bump_map") if custom_bump_map: self.bump_map = custom_bump_map
def prompt_commit_questions(self) -> str: # Prompt user for the commit message cz = self.cz questions = cz.questions() try: answers = questionary.prompt(questions, style=cz.style) except ValueError as err: root_err = err.__context__ if isinstance(root_err, CzException): out.error(root_err.__str__()) raise SystemExit(CUSTOM_ERROR) raise err if not answers: raise SystemExit(NO_ANSWERS) return cz.message(answers)
def _install_pre_commit_hook(self): pre_commit_config_filename = ".pre-commit-config.yaml" cz_hook_config = { "repo": "https://github.com/commitizen-tools/commitizen", "rev": f"v{__version__}", "hooks": [{ "id": "commitizen", "stages": ["commit-msg"] }], } config_data = {} if not os.path.isfile(pre_commit_config_filename): # .pre-commit-config does not exist config_data["repos"] = [cz_hook_config] else: # breakpoint() with open(pre_commit_config_filename) as config_file: yaml_data = yaml.safe_load(config_file) if yaml_data: config_data = yaml_data if "repos" in config_data: for pre_commit_hook in config_data["repos"]: if "commitizen" in pre_commit_hook["repo"]: out.write("commitizen already in pre-commit config") break else: config_data["repos"].append(cz_hook_config) else: # .pre-commit-config exists but there's no "repos" key config_data["repos"] = [cz_hook_config] with open(pre_commit_config_filename, "w") as config_file: yaml.safe_dump(config_data, stream=config_file) c = cmd.run("pre-commit install --hook-type commit-msg") if c.return_code == 127: out.error( "pre-commit is not installed in current environement.\n" "Run 'pre-commit install --hook-type commit-msg' again after it's installed" ) elif c.return_code != 0: out.error(c.err) else: out.write( "commitizen pre-commit hook is now installed in your '.git'\n")
def __call__(self): if self.parameter.get("project"): version = self.config.settings["version"] if version: out.write(f"{version}") else: out.error(f"No project information in this project.") elif self.parameter.get("verbose"): out.write(f"Installed Commitizen Version: {__version__}") version = self.config.settings["version"] if version: out.write(f"Project Version: {version}") else: out.error(f"No project information in this project.") else: # if no argument is given, show installed commitizen version out.write(f"{__version__}")
def __call__(self): """Validate if a commit message follows the conventional pattern. Raises ------ SystemExit if the commit provided not follows the conventional pattern """ commit_msg_content = self._get_commit_msg() if self._is_conventional(PATTERN, commit_msg_content) is not None: out.success("Conventional commit validation: successful!") else: out.error("conventional commit validation: failed!") out.error( "please enter a commit message in the conventional format.") raise SystemExit(INVALID_COMMIT_MSG)
def update_version_in_files(current_version: str, new_version: str, files: List[str], *, check_consistency=False): """Change old version to the new one in every file given. Note that this version is not the tag formatted one. So for example, your tag could look like `v1.0.0` while your version in the package like `1.0.0`. """ # TODO: separate check step and write step for location in files: filepath, *regexes = location.split(":", maxsplit=1) regex = regexes[0] if regexes else None # Read in the file file_content = [] current_version_found = False with open(filepath, "r") as version_file: for line in version_file: if regex: match = re.search(regex, line) if not match: file_content.append(line) continue # Replace the target string if current_version in line: current_version_found = True file_content.append( line.replace(current_version, new_version)) else: file_content.append(line) if check_consistency and not current_version_found: out.error( f"Current version {current_version} is not found in {location}.\n" "The version defined in commitizen configuration and the ones in " "version_files are possibly inconsistent.") raise SystemExit(CURRENT_VERSION_NOT_FOUND) # Write the file out again with open(filepath, "w") as file: file.write("".join(file_content))
def read_cfg() -> BaseConfig: conf = BaseConfig() git_project_root = git.find_git_project_root() if not git_project_root: out.error( "fatal: not a git repository (or any of the parent directories): .git" ) raise SystemExit(NOT_A_GIT_PROJECT) allowed_cfg_files = defaults.config_files cfg_paths = ( path / Path(filename) for path in [Path("."), git_project_root] for filename in allowed_cfg_files ) for filename in cfg_paths: if not filename.exists(): continue with open(filename, "r") as f: data: str = f.read() if "toml" in filename.suffix: _conf = TomlConfig(data=data, path=filename) else: warnings.warn( ".cz, setup.cfg, and .cz.cfg will be deprecated " "in next major version. \n" 'Please use "pyproject.toml", ".cz.toml" instead' ) _conf = IniConfig(data=data, path=filename) if _conf.is_empty_config: continue else: conf = _conf break if not conf.path: global_conf = load_global_conf() if global_conf: conf = global_conf return conf
def main(): conf = config.read_cfg() parser = cli(data) # Show help if no arg provided if len(sys.argv) == 1: parser.print_help(sys.stderr) raise SystemExit() # This is for the command required constraint in 2.0 try: args = parser.parse_args() except TypeError: out.error("Command is required") raise SystemExit() if args.name: conf.update({"name": args.name}) elif not args.name and not conf.path: conf.update({"name": "cz_conventional_commits"}) if args.version: warnings.warn( ("'cz --version' will be deprecated in next major version. " "Please use 'cz version' command from your scripts"), category=DeprecationWarning, ) args.func = commands.Version if args.debug: warnings.warn( ("Debug will be deprecated in next major version. " "Please remove it from your scripts"), category=DeprecationWarning, ) logging.getLogger("commitizen").setLevel(logging.DEBUG) # TODO: This try block can be removed after command is required in 2.0 # Handle the case that argument is given, but no command is provided try: args.func(conf, vars(args))() except AttributeError: out.error("Command is required") raise SystemExit()
def __call__(self): dry_run: bool = self.arguments.get("dry_run") if git.is_staging_clean() and not dry_run: raise NothingToCommitError("No files added to staging!") retry: bool = self.arguments.get("retry") if retry: m = self.read_backup_message() else: m = self.prompt_commit_questions() out.info(f"\n{m}\n") if dry_run: raise DryRunExit() signoff: bool = self.arguments.get("signoff") if signoff: c = git.commit(m, "-s") else: c = git.commit(m) if c.return_code != 0: out.error(c.err) # Create commit backup with open(self.temp_file, "w") as f: f.write(m) raise CommitError() if "nothing added" in c.out or "no changes added to commit" in c.out: out.error(c.out) else: with contextlib.suppress(FileNotFoundError): os.remove(self.temp_file) out.write(c.err) out.write(c.out) out.success("Commit successful!")
def __call__(self): """Validate if commit messages follows the conventional pattern. Raises ------ SystemExit if the commit provided not follows the conventional pattern """ commit_msgs = self._get_commit_messages() pattern = self.cz.schema_pattern() for commit_msg in commit_msgs: if not Check.validate_commit_message(commit_msg, pattern): out.error( "commit validation: failed!\n" "please enter a commit message in the commitizen format.\n" f"commit: {commit_msg}\n" f"pattern: {pattern}") raise SystemExit(INVALID_COMMIT_MSG) out.success("Commit validation: successful!")
def __call__(self): if git.is_staging_clean(): out.write("No files added to staging!") raise SystemExit(NOTHING_TO_COMMIT) retry: bool = self.arguments.get("retry") if retry: m = self.read_backup_message() else: m = self.prompt_commit_questions() out.info(f"\n{m}\n") c = git.commit(m) if c.err: out.error(c.err) # Create commit backup with open(self.temp_file, "w") as f: f.write(m) raise SystemExit(COMMIT_ERROR) if "nothing added" in c.out or "no changes added to commit" in c.out: out.error(c.out) elif c.err: out.error(c.err) else: with contextlib.suppress(FileNotFoundError): os.remove(self.temp_file) out.write(c.out) out.success("Commit successful!")
def read_cfg() -> BaseConfig: conf = BaseConfig() git_project_root = git.find_git_project_root() if not git_project_root: out.error( "fatal: not a git repository (or any of the parent directories): .git" ) raise SystemExit(NOT_A_GIT_PROJECT) cfg_paths = ( path / Path(filename) for path in [Path("."), git_project_root] for filename in defaults.config_files ) for filename in cfg_paths: if not filename.exists(): continue with open(filename, "r") as f: data: str = f.read() _conf: Union[TomlConfig, IniConfig] if "toml" in filename.suffix: _conf = TomlConfig(data=data, path=filename) else: _conf = IniConfig(data=data, path=filename) if _conf.is_empty_config: continue else: conf = _conf break if not conf.path: global_conf = load_global_conf() if global_conf: conf = global_conf return conf
def __call__(self): cz = self.cz questions = cz.questions() answers = questionary.prompt(questions) if not answers: raise SystemExit(NO_ANSWERS) m = cz.message(answers) out.info(f"\n{m}\n") c = git.commit(m) if c.err: out.error(c.err) raise SystemExit(COMMIT_ERROR) if "nothing added" in c.out or "no changes added to commit" in c.out: out.error(c.out) elif c.err: out.error(c.err) else: out.write(c.out) out.success("Commit successful!")
def __call__(self): # noqa: C901 """Steps executed to bump.""" try: current_version_instance: Version = Version(self.bump_settings["version"]) except TypeError: out.error( "[NO_VERSION_SPECIFIED]\n" "Check if current version is specified in config file, like:\n" "version = 0.4.3\n" ) raise SystemExit(NO_VERSION_SPECIFIED) # Initialize values from sources (conf) current_version: str = self.config.settings["version"] tag_format: str = self.bump_settings["tag_format"] bump_commit_message: str = self.bump_settings["bump_message"] version_files: List[str] = self.bump_settings["version_files"] dry_run: bool = self.arguments["dry_run"] is_yes: bool = self.arguments["yes"] increment: Optional[str] = self.arguments["increment"] prerelease: str = self.arguments["prerelease"] is_files_only: Optional[bool] = self.arguments["files_only"] current_tag_version: str = bump.create_tag( current_version, tag_format=tag_format ) is_initial = self.is_initial_tag(current_tag_version, is_yes) if is_initial: commits = git.get_commits() else: commits = git.get_commits(current_tag_version) # No commits, there is no need to create an empty tag. # Unless we previously had a prerelease. if not commits and not current_version_instance.is_prerelease: out.error("[NO_COMMITS_FOUND]\n" "No new commits found.") raise SystemExit(NO_COMMITS_FOUND) if increment is None: increment = self.find_increment(commits) # Increment is removed when current and next version # are expected to be prereleases. if prerelease and current_version_instance.is_prerelease: increment = None new_version = bump.generate_version( current_version, increment, prerelease=prerelease ) new_tag_version = bump.create_tag(new_version, tag_format=tag_format) message = bump.create_commit_message( current_version, new_version, bump_commit_message ) # Report found information out.write( f"message\n" f"tag to create: {new_tag_version}\n" f"increment detected: {increment}\n" ) # Do not perform operations over files or git. if dry_run: raise SystemExit() bump.update_version_in_files( current_version, new_version.public, version_files, check_consistency=self.check_consistency, ) if is_files_only: raise SystemExit() if self.changelog: changelog = Changelog( self.config, { "unreleased_version": new_tag_version, "incremental": True, "dry_run": dry_run, }, ) changelog() self.config.set_key("version", new_version.public) c = git.commit(message, args=self._get_commit_args()) if c.err: out.error('git.commit error: "{}"'.format(c.err.strip())) raise SystemExit(COMMIT_FAILED) c = git.tag(new_tag_version) if c.err: out.error(c.err) raise SystemExit(TAG_FAILED) out.success("Done!")
def __call__(self): """Steps executed to bump.""" try: current_version_instance: Version = Version(self.parameters["version"]) except TypeError: out.error("[NO_VERSION_SPECIFIED]") out.error("Check if current version is specified in config file, like:") out.error("version = 0.4.3") raise SystemExit(NO_VERSION_SPECIFIED) # Initialize values from sources (conf) current_version: str = self.config["version"] tag_format: str = self.parameters["tag_format"] bump_commit_message: str = self.parameters["bump_message"] current_tag_version: str = bump.create_tag( current_version, tag_format=tag_format ) files: list = self.parameters["files"] dry_run: bool = self.parameters["dry_run"] is_yes: bool = self.arguments["yes"] prerelease: str = self.arguments["prerelease"] increment: Optional[str] = self.arguments["increment"] is_initial = self.is_initial_tag(current_tag_version, is_yes) commits = git.get_commits(current_tag_version, from_beginning=is_initial) # No commits, there is no need to create an empty tag. # Unless we previously had a prerelease. if not commits and not current_version_instance.is_prerelease: out.error("[NO_COMMITS_FOUND]") out.error("No new commits found.") raise SystemExit(NO_COMMITS_FOUND) if increment is None: bump_pattern = self.cz.bump_pattern bump_map = self.cz.bump_map if not bump_map or not bump_pattern: out.error(f"'{self.config['name']}' rule does not support bump") raise SystemExit(NO_PATTERN_MAP) increment = bump.find_increment( commits, regex=bump_pattern, increments_map=bump_map ) # Increment is removed when current and next version # are expected to be prereleases. if prerelease and current_version_instance.is_prerelease: increment = None new_version = bump.generate_version( current_version, increment, prerelease=prerelease ) new_tag_version = bump.create_tag(new_version, tag_format=tag_format) message = bump.create_commit_message( current_version, new_version, bump_commit_message ) # Report found information out.write(message) out.write(f"tag to create: {new_tag_version}") out.write(f"increment detected: {increment}") # Do not perform operations over files or git. if dry_run: raise SystemExit() config.set_key("version", new_version.public) bump.update_version_in_files(current_version, new_version.public, files) c = git.commit(message, args="-a") if c.err: out.error(c.err) raise SystemExit(COMMIT_FAILED) c = git.tag(new_tag_version) if c.err: out.error(c.err) raise SystemExit(TAG_FAILED) out.success("Done!")
def __call__(self): commit_parser = self.cz.commit_parser changelog_pattern = self.cz.changelog_pattern start_rev = self.start_rev unreleased_version = self.unreleased_version changelog_meta: Dict = {} change_type_map: Optional[Dict] = self.change_type_map changelog_message_builder_hook: Optional[ Callable] = self.cz.changelog_message_builder_hook changelog_hook: Optional[Callable] = self.cz.changelog_hook if not changelog_pattern or not commit_parser: out.error( f"'{self.config.settings['name']}' rule does not support changelog" ) raise SystemExit(NO_PATTERN_MAP) tags = git.get_tags() if not tags: tags = [] if self.incremental: changelog_meta = changelog.get_metadata(self.file_name) latest_version = changelog_meta.get("latest_version") if latest_version: start_rev = self._find_incremental_rev(latest_version, tags) commits = git.get_commits(start=start_rev, args="--author-date-order") if not commits: out.error("No commits found") raise SystemExit(NO_COMMITS_FOUND) tree = changelog.generate_tree_from_commits( commits, tags, commit_parser, changelog_pattern, unreleased_version, change_type_map=change_type_map, changelog_message_builder_hook=changelog_message_builder_hook, ) changelog_out = changelog.render_changelog(tree) changelog_out = changelog_out.lstrip("\n") if self.dry_run: out.write(changelog_out) raise SystemExit(0) lines = [] if self.incremental and os.path.isfile(self.file_name): with open(self.file_name, "r") as changelog_file: lines = changelog_file.readlines() with open(self.file_name, "w") as changelog_file: partial_changelog: Optional[str] = None if self.incremental: new_lines = changelog.incremental_build( changelog_out, lines, changelog_meta) changelog_out = "".join(new_lines) partial_changelog = changelog_out if changelog_hook: changelog_out = changelog_hook(changelog_out, partial_changelog) changelog_file.write(changelog_out)