def test_added_file_not_in_pre_commits_list(temp_git_dir): with temp_git_dir.as_cwd(): temp_git_dir.join('f.py').write("print('hello world')") cmd_output('git', 'add', 'f.py') # Should pass even with a size of 0 assert find_large_added_files(['g.py'], 0) == 0
def repository_pending_merge(tmpdir): # Make a (non-conflicting) merge repo1 = tmpdir.join('repo1') repo1_f1 = repo1.join('f1') repo2 = tmpdir.join('repo2') repo2_f1 = repo2.join('f1') repo2_f2 = repo2.join('f2') cmd_output('git', 'init', str(repo1)) with repo1.as_cwd(): repo1_f1.ensure() cmd_output('git', 'add', '.') git_commit('-m', 'commit1') cmd_output('git', 'clone', str(repo1), str(repo2)) # Commit in master with repo1.as_cwd(): repo1_f1.write('parent\n') git_commit('-am', 'master commit2') # Commit in clone and pull without committing with repo2.as_cwd(): repo2_f2.write('child\n') cmd_output('git', 'add', '.') git_commit('-m', 'clone commit2') cmd_output('git', 'pull', '--no-commit', '--no-rebase') # We should end up in a pending merge assert repo2_f1.read() == 'parent\n' assert repo2_f2.read() == 'child\n' assert os.path.exists(os.path.join('.git', 'MERGE_HEAD')) yield repo2
def main(argv: Optional[Sequence[str]] = None) -> int: parser = argparse.ArgumentParser() parser.add_argument( '-r', '--report', help='where to store report', ) parser.add_argument( '-c', '--config', help='location of config', ) args = parser.parse_args(argv) report = args.report or None config = args.config or None cmd = "gitleaks --quiet --format=json --path=." if report: cmd += " --report={}".format(report) if config: cmd += " --config-path={}".format(config) try: cmd_output(*cmd.split()) except CalledProcessError as excp: print(excp) return 1 return
def test_adding_something(temp_git_dir): with temp_git_dir.as_cwd(): temp_git_dir.join('f.py').write("print('hello world')") cmd_output('git', 'add', 'f.py') # Should fail with max size of 0 assert find_large_added_files(['f.py'], 0) == 1
def test_adding_file_with_conflicting_directory(temp_git_dir): with temp_git_dir.as_cwd(): temp_git_dir.mkdir('dir').join('x').write('foo') temp_git_dir.join('DIR').write('foo') cmd_output('git', 'add', '-A') assert find_conflicting_filenames([]) == 1
def test_not_on_a_branch(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'commit', '--no-gpg-sign', '--allow-empty', '-m1') head = cmd_output('git', 'rev-parse', 'HEAD').strip() cmd_output('git', 'checkout', head) # we're not on a branch! assert main(()) == 0
def check_author_identity( name_regexp=DEFAULT_NAME_REGEXP, email_regexp=DEFAULT_EMAIL_REGEXP, ): name = os.environ.get('GIT_AUTHOR_NAME') email = os.environ.get('GIT_AUTHOR_EMAIL') name = name or cmd_output( 'git', 'config', '--get', 'user.name', retcode=None) email = email or cmd_output( 'git', 'config', '--get', 'user.email', retcode=None) retv = 0 if not name or not email: print('''name and/or email not configured! Use the commands: git config --global user.name "Your Name" git config --global user.email "*****@*****.**" to introduce yourself to Git before committing. ''') retv = 1 elif not re.match(name_regexp, name): print('User name %r not match %r' % (name, name_regexp)) retv = 2 elif not re.match(email_regexp, email): print('User email %r not match %r' % (email, email_regexp)) retv = 3 return retv
def test_adding_something_with_conflict(temp_git_dir): with cwd(temp_git_dir): write_file('f.py', "print('hello world')") cmd_output('git', 'add', 'f.py') write_file('F.py', "print('hello world')") cmd_output('git', 'add', 'F.py') assert find_conflicting_filenames(['f.py', 'F.py']) == 1
def test_failing_cmdline_email_regexp(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'config', '--add', 'user.name', 'test') cmd_output('git', 'config', '--add', 'user.email', '*****@*****.**') rc = main([ '[email protected]', ]) assert rc == 3
def test_filenames_ignored(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'config', '--add', 'user.name', 'test') cmd_output('git', 'config', '--add', 'user.email', '*****@*****.**') rc = main([ '.gitignore', ]) assert rc == 0
def test_adding_something_with_conflict(temp_git_dir): with temp_git_dir.as_cwd(): temp_git_dir.join('f.py').write("print('hello world')") cmd_output('git', 'add', 'f.py') temp_git_dir.join('F.py').write("print('hello world')") cmd_output('git', 'add', 'F.py') assert find_conflicting_filenames(['f.py', 'F.py']) == 1
def test_passing_cmdline_name_regexp(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'config', '--add', 'user.name', 'Test') cmd_output('git', 'config', '--add', 'user.email', '*****@*****.**') rc = main([ '--name-regexp=[A-Z].+', ]) assert rc == 0
def test_file_conflicts_with_committed_dir(temp_git_dir): with temp_git_dir.as_cwd(): temp_git_dir.mkdir('dir').join('x').write('foo') cmd_output('git', 'add', '-A') git_commit('-m', 'Add f.py') temp_git_dir.join('DIR').write('foo') cmd_output('git', 'add', '-A') assert find_conflicting_filenames([]) == 1
def test_file_conflicts_with_committed_file(temp_git_dir): with temp_git_dir.as_cwd(): temp_git_dir.join('f.py').write("print('hello world')") cmd_output('git', 'add', 'f.py') git_commit('-m', 'Add f.py') temp_git_dir.join('F.py').write("print('hello world')") cmd_output('git', 'add', 'F.py') assert find_conflicting_filenames(['F.py']) == 1
def test_moves_with_gitlfs(temp_git_dir): # pragma: no cover with temp_git_dir.as_cwd(): cmd_output('git', 'lfs', 'install') cmd_output('git', 'lfs', 'track', 'a.bin', 'b.bin') # First add the file we're going to move temp_git_dir.join('a.bin').write('a' * 10000) cmd_output('git', 'add', '--', '.') cmd_output('git', 'commit', '--no-gpg-sign', '-am', 'foo') # Now move it and make sure the hook still succeeds cmd_output('git', 'mv', 'a.bin', 'b.bin') assert main(('--maxkb', '9', 'b.bin')) == 0
def test_check_git_filemode_failing(tmpdir): with tmpdir.as_cwd(): cmd_output('git', 'init', '.') f = tmpdir.join('f').ensure() f.write('#!/usr/bin/env bash') f_path = str(f) cmd_output('git', 'add', f_path) files = (f_path, ) assert _check_git_filemode(files) == 1
def fix_spaces_between_java_functions(argv=None): parser = argparse.ArgumentParser() parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) retv = 0 print('eggs n bacon') cmd_output('eggs n bacon') for filename in args.filenames: print('Found file {0}'.format(filename)) return retv
def test_integration(temp_git_dir): with temp_git_dir.as_cwd(): assert main(argv=[]) == 0 temp_git_dir.join('f.py').write('a' * 10000) cmd_output('git', 'add', 'f.py') # Should not fail with default assert main(argv=['f.py']) == 0 # Should fail with --maxkb assert main(argv=['--maxkb', '9', 'f.py']) == 1
def test_integration(temp_git_dir): with cwd(temp_git_dir): assert main(argv=[]) == 0 write_file('f.py', "print('hello world')") cmd_output('git', 'add', 'f.py') assert main(argv=['f.py']) == 0 write_file('F.py', "print('hello world')") cmd_output('git', 'add', 'F.py') assert main(argv=['F.py']) == 1
def test_integration(temp_git_dir): with temp_git_dir.as_cwd(): assert main(argv=[]) == 0 temp_git_dir.join('f.py').write("print('hello world')") cmd_output('git', 'add', 'f.py') assert main(argv=['f.py']) == 0 temp_git_dir.join('F.py').write("print('hello world')") cmd_output('git', 'add', 'F.py') assert main(argv=['F.py']) == 1
def test_worktree_merge_conflicts(f1_is_a_conflict_file, tmpdir): worktree = tmpdir.join('worktree') cmd_output('git', 'worktree', 'add', str(worktree)) with worktree.as_cwd(): cmd_output( 'git', 'pull', '--no-rebase', 'origin', 'master', retcode=None, ) msg = f1_is_a_conflict_file.join('.git/worktrees/worktree/MERGE_MSG') assert msg.exists() test_merge_conflicts_git()
def find_destroyed_symlinks(files: Sequence[str]) -> List[str]: destroyed_links: List[str] = [] if not files: return destroyed_links for line in zsplit( cmd_output('git', 'status', '--porcelain=v2', '-z', '--', *files), ): splitted = line.split(' ') if splitted and splitted[0] == ORDINARY_CHANGED_ENTRIES_MARKER: # https://git-scm.com/docs/git-status#_changed_tracked_entries ( _, _, _, mode_HEAD, mode_index, _, hash_HEAD, hash_index, *path_splitted, ) = splitted path = ' '.join(path_splitted) if (mode_HEAD == PERMS_LINK and mode_index != PERMS_LINK and mode_index != PERMS_NONEXIST): if hash_HEAD == hash_index: # if old and new hashes are equal, it's not needed to check # anything more, we've found a destroyed symlink for sure destroyed_links.append(path) else: # if old and new hashes are *not* equal, it doesn't mean # that everything is OK - new file may be altered # by something like trailing-whitespace and/or # mixed-line-ending hooks so we need to go deeper SIZE_CMD = ('git', 'cat-file', '-s') size_index = int(cmd_output(*SIZE_CMD, hash_index).strip()) size_HEAD = int(cmd_output(*SIZE_CMD, hash_HEAD).strip()) # in the worst case new file may have CRLF added # so check content only if new file is bigger # not more than 2 bytes compared to the old one if size_index <= size_HEAD + 2: head_content = subprocess.check_output( ('git', 'cat-file', '-p', hash_HEAD), ).rstrip() index_content = subprocess.check_output( ('git', 'cat-file', '-p', hash_index), ).rstrip() if head_content == index_content: destroyed_links.append(path) return destroyed_links
def is_on_branch(protected): try: branch = cmd_output("git", "symbolic-ref", "HEAD") except CalledProcessError: return False chunks = branch.strip().split("/") return "/".join(chunks[2:]) == protected
def find_conflicting_filenames(filenames): repo_files = set(cmd_output("git", "ls-files").splitlines()) relevant_files = set(filenames) | added_files() repo_files -= relevant_files retv = 0 # new file conflicts with existing file conflicts = lower_set(repo_files) & lower_set(relevant_files) # new file conflicts with other new file lowercase_relevant_files = lower_set(relevant_files) for filename in set(relevant_files): if filename.lower() in lowercase_relevant_files: lowercase_relevant_files.remove(filename.lower()) else: conflicts.add(filename.lower()) if conflicts: conflicting_files = [ x for x in repo_files | relevant_files if x.lower() in conflicts ] for filename in sorted(conflicting_files): print("Case-insensitivity conflict found: {}".format(filename)) retv = 1 return retv
def find_conflicting_filenames(filenames): repo_files = set(cmd_output('git', 'ls-files').splitlines()) relevant_files = set(filenames) | added_files() repo_files -= relevant_files retv = 0 # new file conflicts with existing file conflicts = lower_set(repo_files) & lower_set(relevant_files) # new file conflicts with other new file lowercase_relevant_files = lower_set(relevant_files) for filename in set(relevant_files): if filename.lower() in lowercase_relevant_files: lowercase_relevant_files.remove(filename.lower()) else: conflicts.add(filename.lower()) if conflicts: conflicting_files = [ x for x in repo_files | relevant_files if x.lower() in conflicts ] for filename in sorted(conflicting_files): print('Case-insensitivity conflict found: {0}'.format(filename)) retv = 1 return retv
def main(argv=None): # type: (Optional[Sequence[str]]) -> int # `argv` is ignored, pre-commit will send us a list of files that we # don't care about added_diff = cmd_output( 'git', 'diff', '--staged', '--diff-filter=A', '--raw', ) retv = 0 for line in added_diff.splitlines(): metadata, filename = line.split('\t', 1) new_mode = metadata.split(' ')[1] if new_mode == '160000': print('{}: new submodule introduced'.format(filename)) retv = 1 if retv: print() print('This commit introduces new submodules.') print('Did you unintentionally `git add .`?') print('To fix: git rm {thesubmodule} # no trailing slash') print('Also check .gitmodules') return retv
def is_on_branch(protected): try: branch = cmd_output('git', 'symbolic-ref', 'HEAD') except CalledProcessError: return False chunks = branch.strip().split('/') return '/'.join(chunks[2:]) in protected
def is_on_branch(protected): try: branch = cmd_output('git', 'symbolic-ref', 'HEAD') except CalledProcessError: return False chunks = branch.strip().split('/') return '/'.join(chunks[2:]) == protected
def find_conflicting_filenames(filenames: Sequence[str]) -> int: repo_files = set(cmd_output('git', 'ls-files').splitlines()) repo_files |= directories_for(repo_files) relevant_files = set(filenames) | added_files() relevant_files |= directories_for(relevant_files) repo_files -= relevant_files retv = 0 # new file conflicts with existing file conflicts = lower_set(repo_files) & lower_set(relevant_files) # new file conflicts with other new file lowercase_relevant_files = lower_set(relevant_files) for filename in set(relevant_files): if filename.lower() in lowercase_relevant_files: lowercase_relevant_files.remove(filename.lower()) else: conflicts.add(filename.lower()) if conflicts: conflicting_files = [ x for x in repo_files | relevant_files if x.lower() in conflicts ] for filename in sorted(conflicting_files): print(f'Case-insensitivity conflict found: {filename}') retv = 1 return retv
def test_add_something_giant(temp_git_dir): with temp_git_dir.as_cwd(): temp_git_dir.join('f.py').write('a' * 10000) # Should not fail when not added assert find_large_added_files(['f.py'], 0) == 0 cmd_output('git', 'add', 'f.py') # Should fail with strict bound assert find_large_added_files(['f.py'], 0) == 1 # Should also fail with actual bound assert find_large_added_files(['f.py'], 9) == 1 # Should pass with higher bound assert find_large_added_files(['f.py'], 10) == 0
def lfs_files(): try: # Introduced in git-lfs 2.2.0, first working in 2.2.1 lfs_ret = cmd_output('git', 'lfs', 'status', '--json') except CalledProcessError: # pragma: no cover (with git-lfs) lfs_ret = '{"files":{}}' return set(json.loads(lfs_ret)['files'])
def lfs_files(): try: # Introduced in git-lfs 2.2.0, first working in 2.2.1 lfs_ret = cmd_output("git", "lfs", "status", "--json") except CalledProcessError: # pragma: no cover (with git-lfs) lfs_ret = '{"files":{}}' return set(json.loads(lfs_ret)["files"])
def test_allows_gitlfs(temp_git_dir): # pragma: no cover with cwd(temp_git_dir): # Work around https://github.com/github/git-lfs/issues/913 cmd_output('git', 'commit', '--allow-empty', '-m', 'foo') cmd_output('git', 'lfs', 'install') write_file('f.py', 'a' * 10000) cmd_output('git', 'lfs', 'track', 'f.py') cmd_output('git', 'add', '.') # Should succeed assert main(('--maxkb', '9', 'f.py')) == 0
def is_on_branch(protected, patterns=frozenset()): # type: (AbstractSet[str], AbstractSet[str]) -> bool try: ref_name = cmd_output('git', 'symbolic-ref', 'HEAD') except CalledProcessError: return False chunks = ref_name.strip().split('/') branch_name = '/'.join(chunks[2:]) return branch_name in protected or any( re.match(p, branch_name) for p in patterns )
def add_middle_whitespace(argv=None): parser = argparse.ArgumentParser() parser.add_argument( '--no-markdown-linebreak-ext', action='store_const', const=[], default=argparse.SUPPRESS, dest='markdown_linebreak_ext', help='Do not preserve linebreak spaces in Markdown' ) parser.add_argument( '--markdown-linebreak-ext', action='append', const='', default=['md,markdown'], metavar='*|EXT[,EXT,...]', nargs='?', help='Markdown extensions (or *) for linebreak spaces' ) parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) bad_whitespace_files = cmd_output( 'grep', '-l', '[[:space:]]$', *args.filenames, retcode=None ).strip().splitlines() md_args = args.markdown_linebreak_ext if '' in md_args: parser.error('--markdown-linebreak-ext requires a non-empty argument') all_markdown = '*' in md_args # normalize all extensions; split at ',', lowercase, and force 1 leading '.' md_exts = ['.' + x.lower().lstrip('.') for x in ','.join(md_args).split(',')] # reject probable "eaten" filename as extension (skip leading '.' with [1:]) for ext in md_exts: if any(c in ext[1:] for c in r'./\:'): parser.error( "bad --markdown-linebreak-ext extension '{0}' (has . / \\ :)\n" " (probably filename; use '--markdown-linebreak-ext=EXT')" .format(ext) ) if bad_whitespace_files: for bad_whitespace_file in bad_whitespace_files: print('Fixing {0}'.format(bad_whitespace_file)) _, extension = os.path.splitext(bad_whitespace_file.lower()) _fix_file(bad_whitespace_file, all_markdown or extension in md_exts) return 1 else: return 0
def test_allows_gitlfs(temp_git_dir): # pragma: no cover with temp_git_dir.as_cwd(): cmd_output('git', 'lfs', 'install') temp_git_dir.join('f.py').write('a' * 10000) cmd_output('git', 'lfs', 'track', 'f.py') cmd_output('git', 'add', '--', '.') # Should succeed assert main(('--maxkb', '9', 'f.py')) == 0
def fix_comma_whitespace(argv=None): parser = argparse.ArgumentParser() args = parser.parse_args(argv) bad_comma_whitespace_files = cmd_output( 'grep', '-l', ',^[[:space:]]', *args.filenames, retcode=None ).strip().splitlines() if bad_comma_whitespace_files: for bad_comma_whitespace_file in bad_comma_whitespace_files: print('Fixing {0}'.format(bad_comma_whitespace_file)) _fix_file(bad_comma_whitespace_file) return 1 else: return 0
def test_file_conflicts_with_committed_file(temp_git_dir): with cwd(temp_git_dir): write_file('f.py', "print('hello world')") cmd_output('git', 'add', 'f.py') cmd_output('git', 'commit', '--no-verify', '-m', 'Add f.py') write_file('F.py', "print('hello world')") cmd_output('git', 'add', 'F.py') assert find_conflicting_filenames(['F.py']) == 1
def test_file_conflicts_with_committed_file(temp_git_dir): with temp_git_dir.as_cwd(): temp_git_dir.join('f.py').write("print('hello world')") cmd_output('git', 'add', 'f.py') cmd_output('git', 'commit', '--no-gpg-sign', '-n', '-m', 'Add f.py') temp_git_dir.join('F.py').write("print('hello world')") cmd_output('git', 'add', 'F.py') assert find_conflicting_filenames(['F.py']) == 1
def fix_trailing_whitespace(argv=None): parser = argparse.ArgumentParser() parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) bad_whitespace_files = cmd_output( 'grep', '-l', '[[:space:]]$', *args.filenames, retcode=None ).strip().splitlines() if bad_whitespace_files: for bad_whitespace_file in bad_whitespace_files: print('Fixing {0}'.format(bad_whitespace_file)) _fix_file(bad_whitespace_file) return 1 else: return 0
def lfs_files(): try: # pragma: no cover (no git-lfs) lines = cmd_output('git', 'lfs', 'status', '--porcelain').splitlines() except CalledProcessError: lines = [] modes_and_fileparts = [ (line[:3].strip(), line[3:].rpartition(' ')[0]) for line in lines ] def to_file_part(mode, filepart): # pragma: no cover (no git-lfs) assert mode in ('A', 'R') return filepart if mode == 'A' else filepart.split(' -> ')[1] return set( to_file_part(mode, filepart) for mode, filepart in modes_and_fileparts if mode in ('A', 'R') )
def test_main_default_call(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'anotherbranch') assert main(()) == 0
def test_forbid_multiple_branches(temp_git_dir, branch_name): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', branch_name) assert main(('--branch', 'b1', '--branch', 'b2'))
def test_main_branch_call(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'other') assert main(('--branch', 'other')) == 1
def test_multi_branch_fail(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'another/branch') assert is_on_branch({'another/branch'}) is True
def test_multi_branch(temp_git_dir): with temp_git_dir.as_cwd(): cmd_output('git', 'checkout', '-b', 'another/branch') assert is_on_branch({'master'}) is False
def has_gitlfs(): output = cmd_output('git', 'lfs', retcode=None, stderr=subprocess.STDOUT) return 'git lfs status' in output