blob: 5e06eecc193cb567c224525c99684f4f6eea6160 [file] [log] [blame]
Trent Aptedcdc39e32023-06-15 15:40:53 +10001# Copyright 2023 The ChromiumOS Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Shared helpers for cros analyzer commands (fix, lint, format)."""
6
7from abc import ABC
8import logging
Trent Apted72468352023-07-11 16:15:57 +10009import os
Trent Aptedcdc39e32023-06-15 15:40:53 +100010from pathlib import Path
11from typing import List
12
13from chromite.cli import command
14from chromite.lib import commandline
15from chromite.lib import git
16
17
Trent Apted72468352023-07-11 16:15:57 +100018def GetFilesFromCommit(commit: str) -> List[str]:
19 """Returns files changed in the provided git `commit` as absolute paths."""
20 repo_root_path = git.FindGitTopLevel(None)
21 files_in_repo = git.RunGit(
22 repo_root_path,
Trent Aptedcdc39e32023-06-15 15:40:53 +100023 ["diff-tree", "--no-commit-id", "--name-only", "-r", commit],
Trent Apted72468352023-07-11 16:15:57 +100024 ).stdout.splitlines()
25 return [os.path.join(repo_root_path, p) for p in files_in_repo]
26
27
28def HasUncommittedChanges(files: List[str]) -> bool:
29 """Returns whether there are uncommitted changes on any of the `files`.
30
31 `files` can be absolute or relative to the current working directory. If a
32 file is passed that is outside the git repository corresponding to the
33 current working directory, an exception will be thrown.
34 """
35 working_status = git.RunGit(
36 None, ["status", "--porcelain=v1", *files]
37 ).stdout.splitlines()
38 if working_status:
39 logging.warning("%s", "\n".join(working_status))
40 return bool(working_status)
Trent Aptedcdc39e32023-06-15 15:40:53 +100041
42
43class AnalyzerCommand(ABC, command.CliCommand):
44 """Shared argument parsing for cros analyzers (fix, lint, format)."""
45
Trent Apted4d4f1bd2023-06-26 12:24:54 +100046 # Additional aliases to offer for the "--inplace" option.
47 inplace_option_aliases = []
48
Trent Aptedcdc39e32023-06-15 15:40:53 +100049 use_dryrun_options = True
50 # Override base class property to use path filter options.
51 use_filter_options = True
52
53 @classmethod
54 def AddParser(cls, parser):
55 super().AddParser(parser)
56 parser.add_argument(
57 "--check",
58 dest="dryrun",
59 action="store_true",
60 help="Display files with errors & exit non-zero",
61 )
62 parser.add_argument(
63 "--diff",
64 action="store_true",
65 help="Display diff instead of fixed content",
66 )
67 parser.add_argument(
68 "--stdout",
69 dest="inplace",
70 action="store_false",
71 help="Write to stdout",
72 )
73 parser.add_argument(
Trent Apted4d4f1bd2023-06-26 12:24:54 +100074 *(["-i", "--inplace"] + cls.inplace_option_aliases),
Trent Aptedcdc39e32023-06-15 15:40:53 +100075 default=True,
76 action="store_true",
Trent Apted4d4f1bd2023-06-26 12:24:54 +100077 dest="inplace",
Trent Aptedcdc39e32023-06-15 15:40:53 +100078 help="Fix files inplace (default)",
79 )
80 parser.add_argument(
81 "--commit",
82 type=str,
83 help=(
84 "Use files from git commit instead of on disk. If no files are"
85 " provided, the list will be obtained from git diff-tree."
86 ),
87 )
88 parser.add_argument(
89 "--head",
90 dest="commit",
91 action="store_const",
92 const="HEAD",
93 help="Alias for --commit HEAD.",
94 )
95 parser.add_argument(
96 "files",
97 nargs="*",
98 type=Path,
99 help=(
100 "Files to fix. Directories will be expanded, and if in a git"
101 " repository, the .gitignore will be respected."
102 ),
103 )
104
105 @classmethod
106 def ProcessOptions(
107 cls,
108 parser: commandline.ArgumentParser,
109 options: commandline.ArgumentNamespace,
110 ) -> None:
111 """Validate & post-process options before freezing."""
Trent Apted4d4f1bd2023-06-26 12:24:54 +1000112
113 # Whether a committed change is being analyzed. Note "pre-submit" is a
114 # special commit passed by `pre-upload.py --pre-submit` asking to check
115 # changes only staged for a commit, but not yet committed.
116 is_committed = options.commit and options.commit != "pre-submit"
117
118 if is_committed and not options.files:
Trent Apted72468352023-07-11 16:15:57 +1000119 options.files = GetFilesFromCommit(options.commit)
120
Trent Apted4d4f1bd2023-06-26 12:24:54 +1000121 if is_committed and options.inplace:
Trent Apted72468352023-07-11 16:15:57 +1000122 # If a commit is provided, bail when using inplace if any of the
123 # files have uncommitted changes. This is because the input to the
124 # analyzer will not consider any working state changes, so they will
125 # likely be lost. In future this may be supported by attempting to
126 # stash and rebase changes. See also b/290714959.
127 if HasUncommittedChanges(options.files):
128 parser.error("In-place may clobber uncommitted changes.")
Trent Aptedcdc39e32023-06-15 15:40:53 +1000129
130 if not options.files:
131 # Running with no arguments is allowed to make the repo upload hook
132 # simple, but print a warning so that if someone runs this manually
133 # they are aware that nothing was changed.
134 logging.warning("No files provided. Doing nothing.")