def get_dirty_paths_by_status(self) -> Dict[str, List[Path]]: """ Returns all paths that have a git status, grouped by change type. These can be staged, unstaged, or untracked. """ if self._dirty_paths_by_status is not None: return self._dirty_paths_by_status debug_echo("Initializing dirty paths") sub_out = subprocess.run( ["git", "status", "--porcelain", "-z", "':!.semgrep_logs/'"], timeout=GIT_SH_TIMEOUT, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) git_status_output = sub_out.stdout.decode("utf-8", errors="replace") debug_echo(f"Git status output: {git_status_output}") output = zsplit(git_status_output) debug_echo("finished getting dirty paths") dirty_paths = bucketize( output, key=lambda line: line[0], value_transform=lambda line: Path(line[3:]), ) debug_echo(str(dirty_paths)) # Cache dirty paths self._dirty_paths_by_status = dirty_paths return dirty_paths
def get_dirty_paths_by_status(self) -> Dict[str, List[Path]]: """ Returns all paths that have a git status, grouped by change type. These can be staged, unstaged, or untracked. """ output = zsplit(git.status("--porcelain", "-z").stdout.decode()) return bucketize( output, key=lambda line: line[0], value_transform=lambda line: Path(line[3:]), )
def get_git_status(self) -> GitStatus: """ Returns Absolute Paths to all files that are staged Ignores files that are symlinks to directories """ import gitdb.exc # type: ignore repo = get_git_repo() if not repo or self._base_commit is None: return GitStatus([], [], [], []) try: repo.rev_parse(self._base_commit) except gitdb.exc.BadName: raise ActionFailure(f"Unknown git ref '{self._base_commit}'") # Output of git command will be relative to git project root status_output = zsplit( git.diff( "--cached", "--name-status", "--no-ext-diff", "-z", "--diff-filter=ACDMRTUXB", "--ignore-submodules", self._base_commit, ).stdout.decode()) added = [] modified = [] removed = [] unmerged = [] while status_output: code = status_output[0] fname = status_output[1] trim_size = 2 if not code.strip(): continue if code == StatusCode.Untracked or code == StatusCode.Ignored: continue resolved_name = self._fname_to_path(repo, fname) # If file is symlink to directory, skip absolute_name = Path(repo.working_tree_dir) / fname if absolute_name.is_symlink() and resolved_name.is_dir(): click.echo( f"| Skipping {absolute_name} since it is a symlink to a directory: {resolved_name}", err=True, ) else: # The following detection for unmerged codes comes from `man git-status` if code == StatusCode.Unmerged: unmerged.append(resolved_name) if (code[0] == StatusCode.Renamed ): # code is RXXX, where XXX is percent similarity removed.append(resolved_name) fname = status_output[2] trim_size += 1 added.append(resolved_name) if code == StatusCode.Added: added.append(resolved_name) if code == StatusCode.Modified: modified.append(resolved_name) if code == StatusCode.Deleted: removed.append(resolved_name) status_output = status_output[trim_size:] debug_echo( f"Git status:\nadded: {added}\nmodified: {modified}\nremoved: {removed}\nunmerged: {unmerged}" ) return GitStatus(added, modified, removed, unmerged)