def test_run_shell_command_kwargs_delegation(self): with self.assertRaises(TypeError): run_shell_command("super-cool-command", weird_parameter2="abc") # Test one of the forbidden parameters. with self.assertRaises(TypeError): run_shell_command("super-cool-command", universal_newlines=False)
def handle_thread(thread): """ Handler for notification thread :param thread: Thread object of where coafile mention is made :return: coafile string """ post_comment( thread, 'Greetings! I\'m the coafile bot and I\'m here to get your coafile ready. Sit Tight!') clone_url = 'https://github.com/' + thread.repository.full_name + '.git' cmd = ['git', 'clone', '--depth=100', clone_url] directory = tempfile.mkdtemp(dir=TEMP_DIR) run_shell_command(cmd, cwd=directory) quickstart_cmd = ["coala-quickstart", "--ci"] run_shell_command(quickstart_cmd, cwd=directory) with open(directory + os.sep + '.coafile', 'r') as pointer: coafile = pointer.read() pointer.close() shutil.rmtree(directory) return coafile
def test_get_head_commit_sha(self): exception_msg = r'The directory is not a git repository.' Path('testfile.txt').touch() run_shell_command('git add testfile.txt') run_shell_command('git commit -m "Add testfile"') with self.assertRaisesRegex(RuntimeError, exception_msg): self.uut.get_head_commit_sha()
def analyze_commit(self, head_commit_sha): commit_type = COMMIT_TYPE.simple_commit command = 'git log -1 --pretty=%B ' + head_commit_sha head_commit = run_shell_command(command)[0] get_parent_commits = 'git log --pretty=%P -n 1 ' + head_commit_sha all_parent_commits = run_shell_command(get_parent_commits)[0] parent_commits_list = all_parent_commits.split(' ') if len(parent_commits_list) >= 2: commit_type |= COMMIT_TYPE.merge_commit get_all_committed_files = ('git show --pretty="" --name-status ' + head_commit_sha) all_committed_files = run_shell_command(get_all_committed_files)[0] all_committed_files = all_committed_files.split('\n') modified_files_list = [] added_files_list = [] deleted_files_list = [] for line in all_committed_files: pos = line.find('\t') change = line[:pos] if change == 'M': modified_files_list.append(line[pos+1:]) elif change == 'A': added_files_list.append(line[pos+1:]) elif change == 'D': deleted_files_list.append(line[pos+1:]) yield (head_commit, head_commit_sha, parent_commits_list, commit_type, modified_files_list, added_files_list, deleted_files_list)
def get_head_commit(self): with change_directory(self.get_config_dir() or os.getcwd()): command = self.check_github_pull_request_temporary_merge_commit() if command: return run_shell_command(command) return run_shell_command('git log -1 --pretty=%B')
def test_hg_failure(self): # The only case where hg log gives error is in case its not a repo # so do a log on non-existent repo to perform failure check run_shell_command('mv .hg .hgback') self.assertEqual(self.run_uut(), []) run_shell_command('mv .hgback .hg') hg_error = self.msg_queue.get().message self.assertEqual(hg_error[:3], 'hg:') self.assert_no_msgs()
def test_run_shell_command_with_stdin(self): command = RunShellCommandTest.construct_testscript_command( "test_input_program.py") stdout, stderr = run_shell_command(command, "1 4 10 22") self.assertEqual(stdout, "37\n") self.assertEqual(stderr, "") stdout, stderr = run_shell_command(command, "1 p 5") self.assertEqual(stdout, "") self.assertEqual(stderr, "INVALID INPUT\n")
def test_run_shell_command_with_stdin(self): command = RunShellCommandTest.construct_testscript_command( 'test_input_program.py') stdout, stderr = run_shell_command(command, '1 4 10 22') self.assertEqual(stdout, '37\n') self.assertEqual(stderr, '') stdout, stderr = run_shell_command(command, '1 p 5') self.assertEqual(stdout, '') self.assertEqual(stderr, 'INVALID INPUT\n')
def setUp(self): self.msg_queue = Queue() self.section = Section('') self.uut = HgCommitBear(None, self.section, self.msg_queue) self._old_cwd = os.getcwd() self.hgdir = mkdtemp() os.chdir(self.hgdir) self.run_hg_command('init') run_shell_command('touch ' + HgCommitBearTest.test_file) self.run_hg_command('add', HgCommitBearTest.test_file) new_config_file = ('[ui]\n' 'username = user.email <*****@*****.**>') self.changeHgConfig(new_config_file) self.run_hg_command('commit -m "testInit Commit"')
def check_github_pull_request_temporary_merge_commit(self): """ This function creates a git command to fetch the unmerged parent commit shortlog from a commit generated by GitHub in a refs/pull/(\\d+)/merge git remote reference. Visit https://github.com/travis-ci/travis-ci/issues/8400 for more details. :return: A git command (str) to fetch the unmerged parent commit if HEAD commit is a GitHub PR temporary merge commit, otherwise None" """ stdout, _ = run_shell_command('git log -1 --pretty=%B') pos = stdout.find('\n') shortlog = stdout[:pos] if pos != -1 else stdout github_pull_request_temporary_merge_commit_regex = re.compile( r'^Merge ([0-9a-f]{40}) into ([0-9a-f]{40})$') match = re.fullmatch(github_pull_request_temporary_merge_commit_regex, shortlog) if match: unmerged_commit_sha = match.group(1) command = ('git log -n 1 --pretty=%B ' + unmerged_commit_sha) return command
def run(self, allow_empty_commit_message: bool = False, **kwargs): """ Check the current git commit message at HEAD. This bear ensures automatically that the shortlog and body do not exceed a given line-length and that a newline lies between them. :param allow_empty_commit_message: Whether empty commit messages are allowed or not. """ with change_directory(self.get_config_dir() or os.getcwd()): stdout, stderr = run_shell_command("git log -1 --pretty=%B") if stderr: self.err("git:", repr(stderr)) return stdout = stdout.rstrip("\n").splitlines() if len(stdout) == 0: if not allow_empty_commit_message: yield Result(self, "HEAD commit has no message.") return yield from self.check_shortlog( stdout[0], **self.get_shortlog_checks_metadata().filter_parameters(kwargs)) yield from self.check_body( stdout[1:], **self.get_body_checks_metadata().filter_parameters(kwargs))
def lint(self, filename=None, file=None): """ Takes a file and lints it using the linter variables defined apriori. :param filename: The name of the file to execute. :param file: The contents of the file as a list of strings. """ assert ((self.use_stdin and file is not None) or (not self.use_stdin and filename is not None)) config_file = self.generate_config_file() self.command = self._create_command(filename=filename, config_file=config_file) stdin_input = "".join(file) if self.use_stdin else None stdout_output, stderr_output = run_shell_command(self.command, stdin=stdin_input) self.stdout_output = tuple(stdout_output.splitlines(keepends=True)) self.stderr_output = tuple(stderr_output.splitlines(keepends=True)) results_output = (self.stderr_output if self.use_stderr else self.stdout_output) results = self.process_output(results_output, filename, file) if not self.use_stderr: self._print_errors(self.stderr_output) if config_file: os.remove(config_file) return results
def run(self, filename, file, **kwargs): # Get the **kwargs params to forward to `generate_config()` # (from `_create_config()`). generate_config_kwargs = FunctionMetadata.filter_parameters( self._get_generate_config_metadata(), kwargs) with self._create_config( filename, file, **generate_config_kwargs) as config_file: # And now retrieve the **kwargs for `create_arguments()`. create_arguments_kwargs = ( FunctionMetadata.filter_parameters( self._get_create_arguments_metadata(), kwargs)) arguments = (self.get_executable(),) + tuple( self.create_arguments( filename, file, config_file, **create_arguments_kwargs)) self.debug("Running '{}'".format(' '.join(arguments))) output = run_shell_command( arguments, stdin="".join(file) if options["use_stdin"] else None) output = tuple(compress( output, (options["use_stdout"], options["use_stderr"]))) if len(output) == 1: output = output[0] process_output_kwargs = FunctionMetadata.filter_parameters( self._get_process_output_metadata(), kwargs) return self.process_output(output, filename, file, **process_output_kwargs)
def lint(self, filename=None, file=None): """ Takes a file and lints it using the linter variables defined apriori. :param filename: The name of the file to execute. :param file: The contents of the file as a list of strings. """ assert ((self.use_stdin and file is not None) or (not self.use_stdin and filename is not None)) config_file = self.generate_config_file() self.command = self._create_command(filename=filename, config_file=config_file) stdin_input = "".join(file) if self.use_stdin else None stdout_output, stderr_output = run_shell_command(self.command, stdin=stdin_input, shell=True) self.stdout_output = tuple(stdout_output.splitlines(keepends=True)) self.stderr_output = tuple(stderr_output.splitlines(keepends=True)) results_output = (self.stderr_output if self.use_stderr else self.stdout_output) results = self.process_output(results_output, filename, file) if not self.use_stderr: self._print_errors(self.stderr_output) if config_file: os.remove(config_file) return results
def run(self, shortlog_length: int=50, body_line_length: int=73, force_body: bool=False, allow_empty_commit_message: bool=False, shortlog_regex: str="", shortlog_trailing_period: bool=None): """ Checks the current git commit message at HEAD. This bear ensures that the shortlog and body do not exceed a given line-length and that a newline lies between them. :param shortlog_length: The maximum length of the shortlog. The shortlog is the first line of the commit message. The newline character at end does not count to the length. :param body_line_length: The maximum line-length of the body. The newline character at each line end does not count to the length. :param force_body: Whether a body shall exist or not. :param allow_empty_commit_message: Whether empty commit messages are allowed or not. :param shortlog_regex: A regex to check the shortlog with. A full match of this regex is then required. Passing an empty string disable the regex-check. :param shortlog_trailing_period: Whether a dot shall be enforced at the end of the shortlog line. Providing ``None`` means "doesn't care". """ config_dir = self.get_config_dir() old_dir = os.getcwd() if config_dir: os.chdir(config_dir) stdout, stderr = run_shell_command(self._git_command) if stderr: self.err("git:", repr(stderr)) return # git automatically removes trailing whitespaces. Also we need to # remove the last \n printed to align the prompt onto the next line. stdout = stdout.splitlines()[:-1] if len(stdout) == 0: if not allow_empty_commit_message: yield Result(self, "HEAD commit has no message.") return yield from self.check_shortlog(shortlog_length, shortlog_regex, shortlog_trailing_period, stdout[0]) yield from self.check_body(body_line_length, force_body, stdout[1:]) os.chdir(old_dir)
def run(self, filename=None, file=None, **kwargs): """ Runs the wrapped tool. :param filename: The filename of the file being linted. ``None`` for project scope. :param file: The content of the file being linted. ``None`` for project scope. """ # Get the **kwargs params to forward to `generate_config()` # (from `_create_config()`). generate_config_kwargs = FunctionMetadata.filter_parameters( self._get_generate_config_metadata(), kwargs) with self._create_config(filename, file, **generate_config_kwargs) as config_file: # And now retrieve the **kwargs for `create_arguments()`. create_arguments_kwargs = (FunctionMetadata.filter_parameters( self._get_create_arguments_metadata(), kwargs)) # The interface of create_arguments is different for local # and global bears, therefore we must check here, what kind # of bear we have. if isinstance(self, LocalBear): args = self.create_arguments(filename, file, config_file, **create_arguments_kwargs) else: args = self.create_arguments(config_file, **create_arguments_kwargs) try: args = tuple(args) except TypeError: self.err('The given arguments ' '{!r} are not iterable.'.format(args)) return arguments = (self.get_executable(), ) + args self.debug("Running '{}'".format(' '.join( str(arg) for arg in arguments))) output = run_shell_command( arguments, stdin=''.join(file) if options['use_stdin'] else None, cwd=self.get_config_dir()) output = tuple( compress(output, (options['use_stdout'], options['use_stderr']))) if options['strip_ansi']: output = tuple(map(strip_ansi, output)) if len(output) == 1: output = output[0] process_output_kwargs = FunctionMetadata.filter_parameters( self._get_process_output_metadata(), kwargs) return self.process_output(output, filename, file, **process_output_kwargs)
def run(self, shortlog_length: int = 50, body_line_length: int = 73, force_body: bool = False, allow_empty_commit_message: bool = False, shortlog_regex: str = "", shortlog_trailing_period: bool = None): """ Checks the current git commit message at HEAD. This bear ensures that the shortlog and body do not exceed a given line-length and that a newline lies between them. :param shortlog_length: The maximum length of the shortlog. The shortlog is the first line of the commit message. The newline character at end does not count to the length. :param body_line_length: The maximum line-length of the body. The newline character at each line end does not count to the length. :param force_body: Whether a body shall exist or not. :param allow_empty_commit_message: Whether empty commit messages are allowed or not. :param shortlog_regex: A regex to check the shortlog with. A full match of this regex is then required. Passing an empty string disable the regex-check. :param shortlog_trailing_period: Whether a dot shall be enforced at the end of the shortlog line. Providing ``None`` means "doesn't care". """ config_dir = self.get_config_dir() old_dir = os.getcwd() if config_dir: os.chdir(config_dir) stdout, stderr = run_shell_command(self._git_command) if stderr: self.err("git:", repr(stderr)) return # git automatically removes trailing whitespaces. Also we need to # remove the last \n printed to align the prompt onto the next line. stdout = stdout.splitlines()[:-1] if len(stdout) == 0: if not allow_empty_commit_message: yield Result(self, "HEAD commit has no message.") return yield from self.check_shortlog(shortlog_length, shortlog_regex, shortlog_trailing_period, stdout[0]) yield from self.check_body(body_line_length, force_body, stdout[1:]) os.chdir(old_dir)
def is_installed(self): """ Checks if the dependency is installed. :param return: True if dependency is installed, false otherwise. """ return True if run_shell_command( ('R -e \'library(\"{}\", quietly=TRUE)\'' .format(self.package)))[1] is '' else False
def is_installed(self): """ Checks if the dependency is installed. :param return: True if dependency is installed, false otherwise. """ return True if run_shell_command( ('R -e \'library(\"{}\", quietly=TRUE)\''.format( self.package)))[1] is '' else False
def test_analyze_git_revert_commit(self): Path('testfile3.txt').touch() run_shell_command('git add testfile3.txt') run_shell_command('git commit -m "another commit [skip ci]"') test_sha3 = run_shell_command('git rev-parse HEAD')[0].strip('\n') run_shell_command('git revert HEAD --no-edit') test_sha4 = run_shell_command('git rev-parse HEAD')[0].strip('\n') get_parents = 'git log --pretty=%P -n 1 ' + test_sha4 parents = run_shell_command(get_parents)[0].split(' ') test_raw_commit_msg = ('Revert "another commit [skip ci]"\n\n' 'This reverts commit %s.\n\n' % (test_sha3)) self.assertEqual( self.run_uut(), [(test_raw_commit_msg, test_sha4, parents, COMMIT_TYPE.simple_commit, [], [], ['testfile3.txt'])])
def get_ignored_files(): """ This function checks for the files that are being tracked but are ignored in .gitignore file. Visit https://github.com/coala/coala-bears/issues/2610 for more details. :return: A list of details of tracked files that are ignored in .gitignore file. """ files, _ = run_shell_command('git ls-files') files = files.strip().split('\n') ignored = list( map( lambda file: run_shell_command( 'git check-ignore --no-index -v {}'.format(file))[0].strip( ), files)) return list(filter(lambda f: f != '', ignored))
def get_head_commit_sha(self): with change_directory(self.get_config_dir() or os.getcwd()): (stdout, stderr) = run_shell_command('git rev-parse HEAD') if stderr: vcs_name = list(self.LANGUAGES)[0].lower()+':' self.err(vcs_name, repr(stderr)) raise RuntimeError('The directory is not a git repository.') head_commit_sha = stdout.strip('\n') return head_commit_sha
def test_run_shell_command_without_stdin(self): command = RunShellCommandTest.construct_testscript_command( 'test_program.py') stdout, stderr = run_shell_command(command) expected = ('test_program Z\n' 'non-interactive mode.\n' 'Exiting...\n') self.assertEqual(stdout, expected) self.assertEqual(stderr, '')
def test_run_shell_command_without_stdin(self): command = RunShellCommandTest.construct_testscript_command( "test_program.py") stdout, stderr = run_shell_command(command) expected = ("test_program Z\n" "non-interactive mode.\n" "Exiting...\n") self.assertEqual(stdout, expected) self.assertEqual(stderr, "")
def test_check_revert_commit_not_allowed(self): Path('testfile1.txt').touch() run_shell_command('git add testfile1.txt') run_shell_command('git commit -m "Add testfile1"') run_shell_command('git revert HEAD --no-edit') self.assertEqual(self.run_uut(allow_git_revert_commit=False), ['Revert commit is not allowed.'])
def run(self, shortlog_length: int=50, body_line_length: int=73, force_body: bool=False, allow_empty_commit_message: bool=False, shortlog_regex: str="", shortlog_trailing_period: bool=None): """ Checks the current git commit message at HEAD. This bear ensures that the shortlog and body do not exceed a given line-length and that a newline lies between them. :param shortlog_length: The maximum length of the shortlog. The shortlog is the first line of the commit message. The newline character at end does not count to the length. :param body_line_length: The maximum line-length of the body. The newline character at each line end does not count to the length. :param force_body: Whether a body shall exist or not. :param allow_empty_commit_message: Whether empty commit messages are allowed or not. :param shortlog_regex: A regex to check the shortlog with. A full match of this regex is then required. Passing an empty string disable the regex-check. :param shortlog_trailing_period: Whether a dot shall be enforced at the end of the shortlog line. Providing ``None`` means "doesn't care". """ with change_directory(self.get_config_dir() or os.getcwd()): stdout, stderr = run_shell_command("git log -1 --pretty=%B") if stderr: self.err("git:", repr(stderr)) return stdout = stdout.rstrip("\n").splitlines() if len(stdout) == 0: if not allow_empty_commit_message: yield Result(self, "HEAD commit has no message.") return yield from self.check_shortlog(shortlog_length, shortlog_regex, shortlog_trailing_period, stdout[0]) yield from self.check_body(body_line_length, force_body, stdout[1:])
def test_github_pull_request_temporary_merge_commit_check(self): self.run_git_command('remote', 'add', 'upstream', 'https://github.com/coala/coala-quickstart.git') run_shell_command('git fetch upstream pull/259/merge:pytest36') run_shell_command('git checkout pytest36') self.assertEqual(self.run_uut(), []) run_shell_command('git fetch upstream pull/257/merge:patch-1') run_shell_command('git checkout patch-1') self.assertEqual(self.run_uut(), ["Shortlog of HEAD commit isn't in imperative" " mood! Bad words are 'Fixed'"]) self.git_commit('Simple git commit') self.assertEqual(self.run_uut(), [])
def test_check_revert_commit_not_allowed(self): Path('testfile1.txt').touch() run_shell_command('git add testfile1.txt') run_shell_command('git commit -m "Add testfile1"') run_shell_command('git revert HEAD --no-edit') self.assertEqual(self.run_uut( allow_git_revert_commit=False), ['Revert commit is not allowed.'])
def run(self, shortlog_length: int = 50, body_line_length: int = 73, force_body: bool = False, allow_empty_commit_message: bool = False, shortlog_regex: str = "", shortlog_trailing_period: bool = None): """ Checks the current git commit message at HEAD. This bear ensures that the shortlog and body do not exceed a given line-length and that a newline lies between them. :param shortlog_length: The maximum length of the shortlog. The shortlog is the first line of the commit message. The newline character at end does not count to the length. :param body_line_length: The maximum line-length of the body. The newline character at each line end does not count to the length. :param force_body: Whether a body shall exist or not. :param allow_empty_commit_message: Whether empty commit messages are allowed or not. :param shortlog_regex: A regex to check the shortlog with. A full match of this regex is then required. Passing an empty string disable the regex-check. :param shortlog_trailing_period: Whether a dot shall be enforced at the end of the shortlog line. Providing ``None`` means "doesn't care". """ with change_directory(self.get_config_dir() or os.getcwd()): stdout, stderr = run_shell_command("git log -1 --pretty=%B") if stderr: self.err("git:", repr(stderr)) return stdout = stdout.rstrip("\n").splitlines() if len(stdout) == 0: if not allow_empty_commit_message: yield Result(self, "HEAD commit has no message.") return yield from self.check_shortlog(shortlog_length, shortlog_regex, shortlog_trailing_period, stdout[0]) yield from self.check_body(body_line_length, force_body, stdout[1:])
def get_host_from_remotes(): """ Retrieve the first host from the list of git remotes. """ remotes, _ = run_shell_command( "git config --get-regex '^remote.*.url$'") remotes = [url.split()[-1] for url in remotes.splitlines()] if len(remotes) == 0: return None url = remotes[0] parsed_url = parse(url) netloc = parsed_url.host return netloc.split('.')[0]
def run(self, filename, file, **settings): self._prepare_settings(settings) json_string = json.dumps({"filename": filename, "file": file, "settings": settings}) args = self.create_arguments() try: args = tuple(args) except TypeError: self.err("The given arguments " "{!r} are not iterable.".format(args)) return shell_command = (self.get_executable(),) + args out, err = run_shell_command(shell_command, json_string) return self.parse_output(out, filename)
def setUp(self): self.msg_queue = Queue() self.section = Section('') self.uut = GitCommitMetadataBear(None, self.section, self.msg_queue) self._old_cwd = os.getcwd() self.gitdir = mkdtemp() os.chdir(self.gitdir) run_shell_command('git init') run_shell_command('git config -- user.email [email protected]') run_shell_command('git config -- user.name coala')
def run(self): """ Check Python code for unused variables and functions using `vulture`. See <https://bitbucket.org/jendrikseipp/vulture> for more information. """ stdout_output, _ = run_shell_command( (self.EXECUTABLE,) + tuple(filename for filename in self.file_dict.keys()), cwd=self.get_config_dir()) for match in re.finditer(self.OUTPUT_REGEX, stdout_output): groups = match.groupdict() yield Result.from_values(origin=self, message=groups['message'], file=groups['filename'], line=int(groups['line']))
def get_host_from_remotes(): """ Retrieve the first host from the list of git remotes. """ remotes, _ = run_shell_command( "git config --get-regex '^remote.*.url$'") remotes = [url.split()[-1] for url in remotes.splitlines()] if len(remotes) == 0: return None url = remotes[0] if 'git@' in url: netloc = re.findall(r'@(\S+):', url)[0] else: netloc = urlparse(url)[1] return netloc.split('.')[0]
def _check_modified_file_similarity(self, file_path, reverted_commit_sha, minimum_similarity_ratio): """ Compare the changes in file modified by the revert commit with the changes actually expected in the revert commit. :param file_path: Relative path to the modified file. :param reverted_commit_sha: Commit hash of reverted commit. :param minimum_similarity_ratio: Minimum similarity ratio required by files in revert commit. """ def clean_inspect_revert_branch(): run_shell_command('git checkout master') run_shell_command('git branch -D inspectrevertbranch') with open(file_path, 'r') as f: revert_file_content = f.read() create_new_branch_command = ( 'git checkout -b inspectrevertbranch HEAD^') run_shell_command(create_new_branch_command) create_expected_revert_commit = ('git revert %s --no-' 'edit' % reverted_commit_sha) _, err = run_shell_command(create_expected_revert_commit) if err: self.warn('Cannot compare the modified files.') run_shell_command('git revert --abort') clean_inspect_revert_branch() return expected_revert_commit_sha = run_shell_command( 'git rev-parse HEAD')[0].strip('\n') with open(file_path, 'r') as f: expected_revert_file_content = f.read() matcher = SequenceMatcher( None, revert_file_content, expected_revert_file_content) similarity_ratio = matcher.real_quick_ratio() if similarity_ratio < minimum_similarity_ratio: yield Result(self, 'Changes in modified file %s of ' 'the revert commit are not exactly ' 'revert of changes in the reverted ' 'commit.' % file_path) clean_inspect_revert_branch()
def run(self, filename, file, **settings): self._prepare_settings(settings) json_string = json.dumps({'filename': filename, 'file': file, 'settings': settings}) args = self.create_arguments() try: args = tuple(args) except TypeError: self.err("The given arguments " "{!r} are not iterable.".format(args)) return shell_command = (self.get_executable(),) + args out, err = run_shell_command(shell_command, json_string) return self.parse_output(out, filename)
def run(self, filename, file, **kwargs): # Get the **kwargs params to forward to `generate_config()` # (from `_create_config()`). generate_config_kwargs = FunctionMetadata.filter_parameters( self._get_generate_config_metadata(), kwargs) with self._create_config( filename, file, **generate_config_kwargs) as config_file: # And now retrieve the **kwargs for `create_arguments()`. create_arguments_kwargs = ( FunctionMetadata.filter_parameters( self._get_create_arguments_metadata(), kwargs)) args = self.create_arguments(filename, file, config_file, **create_arguments_kwargs) try: args = tuple(args) except TypeError: self.err("The given arguments " "{!r} are not iterable.".format(args)) return arguments = (self.get_executable(),) + args self.debug("Running '{}'".format(' '.join(arguments))) output = run_shell_command( arguments, stdin="".join(file) if options["use_stdin"] else None, cwd=self.get_config_dir()) output = tuple(compress( output, (options["use_stdout"], options["use_stderr"]))) if len(output) == 1: output = output[0] process_output_kwargs = FunctionMetadata.filter_parameters( self._get_process_output_metadata(), kwargs) return self.process_output(output, filename, file, **process_output_kwargs)
def run(self, filename, file, **kwargs): # Get the **kwargs params to forward to `generate_config()` # (from `_create_config()`). generate_config_kwargs = FunctionMetadata.filter_parameters( self._get_generate_config_metadata(), kwargs) with self._create_config( filename, file, **generate_config_kwargs) as config_file: # And now retrieve the **kwargs for `create_arguments()`. create_arguments_kwargs = ( FunctionMetadata.filter_parameters( self._get_create_arguments_metadata(), kwargs)) args = self.create_arguments(filename, file, config_file, **create_arguments_kwargs) try: args = tuple(args) except TypeError: self.err('The given arguments ' '{!r} are not iterable.'.format(args)) return arguments = (self.get_executable(),) + args self.debug("Running '{}'".format(' '.join(arguments))) output = run_shell_command( arguments, stdin=''.join(file) if options['use_stdin'] else None, cwd=self.get_config_dir()) output = tuple(compress( output, (options['use_stdout'], options['use_stderr']))) if len(output) == 1: output = output[0] process_output_kwargs = FunctionMetadata.filter_parameters( self._get_process_output_metadata(), kwargs) return self.process_output(output, filename, file, **process_output_kwargs)
def test_github_pull_request_temporary_merge_commit(self): self.run_git_command('remote', 'add', 'upstream', 'git://github.com/kriti21/coala-bears.git') run_shell_command('git fetch upstream pull/3/merge:good_test') run_shell_command('git checkout good_test') self.assertEqual(self.run_uut(), []) # If parent of this merge commit is wrong, message is # displayed accordingly. self.run_git_command('remote', 'add', 'upstream', 'git://github.com/kriti21/coala-bears.git') run_shell_command('git fetch upstream pull/2/merge:bad_test') run_shell_command('git checkout bad_test') self.assertEqual(self.run_uut(), [ 'Shortlog of the HEAD commit contains 70 ' 'character(s). This is 20 character(s) longer ' 'than the limit (70 > 50).' ]) self.git_commit('Simple git commit') self.assertEqual(self.run_uut(), [])
def run(self, shortlog_length: int=50, body_line_length: int=73, force_body: bool=False, allow_empty_commit_message: bool=False): """ Checks the current git commit message at HEAD. This bear ensures that the shortlog and body do not exceed a given line-length and that a newline lies between them. :param shortlog_length: The maximum length of the shortlog. The shortlog is the first line of the commit message. The newline character at end does not count to the length. :param body_line_length: The maximum line-length of the body. The newline character at each line end does not count to the length. :param force_body: Whether a body shall exist or not. :param allow_empty_commit_message: Whether empty commit messages are allowed or not. """ stdout, stderr = run_shell_command(self._git_command) if stderr: self.err("git:", repr(stderr)) return # git automatically removes trailing whitespaces. Also we need to # remove the last \n printed to align the prompt onto the next line. stdout = stdout.splitlines()[:-1] if len(stdout) == 0: if not allow_empty_commit_message: yield Result(self, "HEAD commit has no message.") return yield from self.check_shortlog(shortlog_length, stdout[0]) yield from self.check_body(body_line_length, force_body, stdout[1:])
def run(self, allow_empty_commit_message: bool = False, **kwargs): """ Check the current git commit message at HEAD. This bear ensures automatically that the shortlog and body do not exceed a given line-length and that a newline lies between them. :param allow_empty_commit_message: Whether empty commit messages are allowed or not. """ with change_directory(self.get_config_dir() or os.getcwd()): stdout, stderr = run_shell_command('git log -1 --pretty=%B') if stderr: self.err('git:', repr(stderr)) return stdout = stdout.rstrip('\n') pos = stdout.find('\n') shortlog = stdout[:pos] if pos != -1 else stdout body = stdout[pos+1:] if pos != -1 else '' if len(stdout) == 0: if not allow_empty_commit_message: yield Result(self, 'HEAD commit has no message.') return yield from self.check_shortlog( shortlog, **self.get_shortlog_checks_metadata().filter_parameters(kwargs)) yield from self.check_body( body, **self.get_body_checks_metadata().filter_parameters(kwargs)) yield from self.check_issue_reference( body, **self.get_issue_checks_metadata().filter_parameters(kwargs))
def test_check_file_similarity_with_correct_revert_commit(self): Path('testfile6.txt').touch() with open('testfile6.txt', 'w') as f: f.write('Some other text\n') run_shell_command('git add testfile6.txt') run_shell_command('git commit -m "Initial commit"') with open('testfile6.txt', 'w') as f: f.write('Changed text\n') run_shell_command('git add testfile6.txt') run_shell_command('git commit -m "modify testfile6"') run_shell_command('git revert HEAD --no-edit') with open('testfile6.txt', 'w') as f: f.write('Some more text\n') run_shell_command('git add testfile6.txt') run_shell_command('git commit --amend --allow-empty --no-edit') self.assertEqual(self.run_uut(), [])
def test_check_simple_git_commit(self): Path('testfile1.txt').touch() run_shell_command('git add testfile1.txt') run_shell_command('git commit -m "Add testfile1"') self.assertEqual(self.run_uut(), [])
def test_run_shell_command_kwargs_delegation(self): with self.assertRaises(TypeError): run_shell_command("super-cool-command", weird_parameter2="abc")
def test_check_file_similarity_with_invalid_revert_commit(self): Path('testfile5.txt').touch() with open('testfile5.txt', 'w') as f: f.write('Some text\n') run_shell_command('git add testfile5.txt') run_shell_command('git commit -m "Initial commit"') with open('testfile5.txt', 'a') as f: f.write('Changed text\n') run_shell_command('git add testfile5.txt') run_shell_command('git commit -m "modify testfile5"') run_shell_command('git revert HEAD --no-edit') with open('testfile5.txt', 'a') as f: f.write('Some text\nSome more text\n') run_shell_command('git add testfile5.txt') run_shell_command('git commit --amend --allow-empty --no-edit') with open('testfile5.txt', 'a') as f: f.write('Even more text.\n') run_shell_command('git add testfile5.txt') run_shell_command('git commit --amend --allow-empty --no-edit') with open('testfile5.txt', 'a') as f: f.write('Last line\n') run_shell_command('git add testfile5.txt') run_shell_command('git commit --amend --allow-empty --no-edit') self.assertEqual(self.run_uut(), ['Changes in modified file testfile5.txt of the ' 'revert commit are not exactly revert of changes ' 'in the reverted commit.'])
def test_check_git_revert_commit_with_extra_added_file(self): Path('testfile1.txt').touch() with open('testfile1.txt', 'w') as f: f.write('Modify text') Path('testfile2.txt').touch() with open('testfile2.txt', 'w') as f: f.write('Modify text') run_shell_command('git add testfile1.txt') run_shell_command('git add testfile2.txt') run_shell_command('git commit -m "reverted commit"') run_shell_command('git revert HEAD --no-edit') Path('testfile3.txt').touch() run_shell_command('git add testfile3.txt') run_shell_command('git commit --amend --allow-empty --no-edit') self.assertEqual(self.run_uut(), ['Revert commit has a added file testfile3.txt that ' 'is not in the reverted commit.'])
def test_check_git_revert_commit_with_extra_deleted_file(self): Path('testfile1.txt').touch() with open('testfile1.txt', 'w') as f: f.write('Some text') Path('testfile2.txt').touch() with open('testfile2.txt', 'w') as f: f.write('Some more text') run_shell_command('git add testfile1.txt') run_shell_command('git add testfile2.txt') run_shell_command('git commit -m "intial commit"') run_shell_command('git rm testfile1.txt') run_shell_command('git commit -m "delete file"') run_shell_command('git revert HEAD --no-edit') run_shell_command('git rm testfile2.txt') run_shell_command('git commit --amend --allow-empty --no-edit') self.assertEqual(self.run_uut(), ['Revert commit has a deleted file testfile2.txt ' 'that is not in the reverted commit.'])
def test_check_modified_file_similarity_error(self, mock_run_shell_command): mock_run_shell_command.side_effect = [ ShellCommandResult(0, 'M\ttestfile7.txt', sys.stderr), ShellCommandResult(0, None, sys.stderr), ShellCommandResult(0, ('', 'errors'), sys.stderr), ShellCommandResult(0, None, sys.stderr), ShellCommandResult(0, None, sys.stderr), ShellCommandResult(0, None, sys.stderr) ] Path('testfile7.txt').touch() with open('testfile7.txt', 'w') as f: f.write('Some other text\n') run_shell_command('git add testfile7.txt') run_shell_command('git commit -m "Initial commit"') with open('testfile7.txt', 'w') as f: f.write('Changed text\n') run_shell_command('git add testfile7.txt') run_shell_command('git commit -m "modify testfile6"') run_shell_command('git revert HEAD --no-edit') with open('testfile7.txt', 'w') as f: f.write('Some more text\n') run_shell_command('git add testfile7.txt') run_shell_command('git commit --amend --allow-empty --no-edit') assert self.run_uut() == [] mock_run_shell_command.assert_has_calls( [unittest.mock.call('git revert --abort')])