esys-gitlog 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esys_gitlog/__init__.py +3 -0
- esys_gitlog/__main__.py +6 -0
- esys_gitlog/app.py +215 -0
- esys_gitlog/cli.py +323 -0
- esys_gitlog/config.py +202 -0
- esys_gitlog/detection.py +145 -0
- esys_gitlog/display.py +82 -0
- esys_gitlog/git_adapter.py +370 -0
- esys_gitlog/identifiers.py +46 -0
- esys_gitlog/interpret.py +308 -0
- esys_gitlog/links.py +87 -0
- esys_gitlog/model.py +138 -0
- esys_gitlog/pager.py +106 -0
- esys_gitlog/pipeline.py +44 -0
- esys_gitlog/preferences.py +380 -0
- esys_gitlog/qa.py +196 -0
- esys_gitlog/render.py +372 -0
- esys_gitlog/report.py +117 -0
- esys_gitlog/tool_paths.py +62 -0
- esys_gitlog-0.1.0.dist-info/METADATA +77 -0
- esys_gitlog-0.1.0.dist-info/RECORD +25 -0
- esys_gitlog-0.1.0.dist-info/WHEEL +5 -0
- esys_gitlog-0.1.0.dist-info/entry_points.txt +2 -0
- esys_gitlog-0.1.0.dist-info/licenses/LICENSE.txt +18 -0
- esys_gitlog-0.1.0.dist-info/top_level.txt +1 -0
esys_gitlog/__init__.py
ADDED
esys_gitlog/__main__.py
ADDED
esys_gitlog/app.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Application service. Callable without Typer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TextIO
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from esys_gitlog.config import AnalysisConfig, DisplayMode, QaMode, resolve_config
|
|
12
|
+
from esys_gitlog.git_adapter import GitError, iter_commits, list_commits
|
|
13
|
+
from esys_gitlog.links import resolve_commit_url
|
|
14
|
+
from esys_gitlog.model import CommitResult
|
|
15
|
+
from esys_gitlog.pipeline import analyze_commit
|
|
16
|
+
from esys_gitlog.render import render_history
|
|
17
|
+
from esys_gitlog.report import (
|
|
18
|
+
build_report,
|
|
19
|
+
report_exit_code,
|
|
20
|
+
stream_commit_event,
|
|
21
|
+
stream_end_event,
|
|
22
|
+
stream_exit_code,
|
|
23
|
+
stream_start_event,
|
|
24
|
+
write_report,
|
|
25
|
+
write_stream_event,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def analyze_history(
|
|
30
|
+
cwd: str | Path = ".",
|
|
31
|
+
*,
|
|
32
|
+
profile: str | None = None,
|
|
33
|
+
qa: QaMode | None = None,
|
|
34
|
+
display: DisplayMode | None = None,
|
|
35
|
+
config_path: Path | None = None,
|
|
36
|
+
preferences_path: Path | None = None,
|
|
37
|
+
git_log_args: Sequence[str] | None = None,
|
|
38
|
+
) -> tuple[list[tuple[str, CommitResult, str]], AnalysisConfig]:
|
|
39
|
+
"""Load Git history and run the semantic pipeline.
|
|
40
|
+
|
|
41
|
+
Returns ``(rows, config)`` where each row is
|
|
42
|
+
``(commit_id, result, subject)`` in ``git log`` order.
|
|
43
|
+
"""
|
|
44
|
+
root = Path(cwd)
|
|
45
|
+
resolved = resolve_config(
|
|
46
|
+
profile=profile,
|
|
47
|
+
qa=qa,
|
|
48
|
+
display=display,
|
|
49
|
+
config_path=config_path,
|
|
50
|
+
preferences_path=preferences_path,
|
|
51
|
+
cwd=root,
|
|
52
|
+
)
|
|
53
|
+
analyzed: list[tuple[str, CommitResult, str]] = []
|
|
54
|
+
for raw in list_commits(root, git_log_args=git_log_args):
|
|
55
|
+
result = analyze_commit(raw.subject, raw.body, config=resolved)
|
|
56
|
+
result.message = raw.message or "\n\n".join(
|
|
57
|
+
part for part in (raw.subject, raw.body) if part
|
|
58
|
+
)
|
|
59
|
+
result.author_name = raw.author_name or None
|
|
60
|
+
result.author_email = raw.author_email or None
|
|
61
|
+
result.authored_date = raw.authored_date or None
|
|
62
|
+
result.commit_url = resolve_commit_url(
|
|
63
|
+
raw.id,
|
|
64
|
+
resolved.profile,
|
|
65
|
+
cwd=root,
|
|
66
|
+
)
|
|
67
|
+
analyzed.append((raw.id, result, raw.subject))
|
|
68
|
+
return analyzed, resolved
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run_json(
|
|
72
|
+
stdout: TextIO[str],
|
|
73
|
+
*,
|
|
74
|
+
cwd: str | Path = ".",
|
|
75
|
+
profile: str | None = None,
|
|
76
|
+
qa: QaMode | None = None,
|
|
77
|
+
display: DisplayMode | None = None,
|
|
78
|
+
config_path: Path | None = None,
|
|
79
|
+
preferences_path: Path | None = None,
|
|
80
|
+
git_log_args: Sequence[str] | None = None,
|
|
81
|
+
) -> int:
|
|
82
|
+
"""Write the QA report to ``stdout`` only. Return the process exit code."""
|
|
83
|
+
analyzed, _config = analyze_history(
|
|
84
|
+
cwd,
|
|
85
|
+
profile=profile,
|
|
86
|
+
qa=qa,
|
|
87
|
+
display=display,
|
|
88
|
+
config_path=config_path,
|
|
89
|
+
preferences_path=preferences_path,
|
|
90
|
+
git_log_args=git_log_args,
|
|
91
|
+
)
|
|
92
|
+
pairs = [(commit_id, result) for commit_id, result, _subject in analyzed]
|
|
93
|
+
document = build_report(pairs)
|
|
94
|
+
write_report(stdout, document)
|
|
95
|
+
return report_exit_code(document, walk_finished=True)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def run_json_stream(
|
|
99
|
+
stdout: TextIO[str],
|
|
100
|
+
*,
|
|
101
|
+
cwd: str | Path = ".",
|
|
102
|
+
profile: str | None = None,
|
|
103
|
+
qa: QaMode | None = None,
|
|
104
|
+
display: DisplayMode | None = None,
|
|
105
|
+
config_path: Path | None = None,
|
|
106
|
+
preferences_path: Path | None = None,
|
|
107
|
+
git_log_args: Sequence[str] | None = None,
|
|
108
|
+
) -> int:
|
|
109
|
+
"""Write NDJSON stream events to ``stdout`` as each commit is analyzed."""
|
|
110
|
+
root = Path(cwd)
|
|
111
|
+
resolved = resolve_config(
|
|
112
|
+
profile=profile,
|
|
113
|
+
qa=qa,
|
|
114
|
+
display=display,
|
|
115
|
+
config_path=config_path,
|
|
116
|
+
preferences_path=preferences_path,
|
|
117
|
+
cwd=root,
|
|
118
|
+
)
|
|
119
|
+
write_stream_event(stdout, stream_start_event())
|
|
120
|
+
commits = 0
|
|
121
|
+
warnings = 0
|
|
122
|
+
errors = 0
|
|
123
|
+
walk_finished = True
|
|
124
|
+
try:
|
|
125
|
+
for raw in iter_commits(root, git_log_args=git_log_args):
|
|
126
|
+
result = analyze_commit(raw.subject, raw.body, config=resolved)
|
|
127
|
+
result.message = raw.message or "\n\n".join(
|
|
128
|
+
part for part in (raw.subject, raw.body) if part
|
|
129
|
+
)
|
|
130
|
+
result.author_name = raw.author_name or None
|
|
131
|
+
result.author_email = raw.author_email or None
|
|
132
|
+
result.authored_date = raw.authored_date or None
|
|
133
|
+
result.commit_url = resolve_commit_url(
|
|
134
|
+
raw.id,
|
|
135
|
+
resolved.profile,
|
|
136
|
+
cwd=root,
|
|
137
|
+
)
|
|
138
|
+
for diagnostic in result.diagnostics:
|
|
139
|
+
if diagnostic.severity == "warning":
|
|
140
|
+
warnings += 1
|
|
141
|
+
elif diagnostic.severity == "error":
|
|
142
|
+
errors += 1
|
|
143
|
+
commits += 1
|
|
144
|
+
write_stream_event(stdout, stream_commit_event(raw.id, result))
|
|
145
|
+
except BrokenPipeError:
|
|
146
|
+
walk_finished = False
|
|
147
|
+
return stream_exit_code(errors=errors, walk_finished=False)
|
|
148
|
+
write_stream_event(
|
|
149
|
+
stdout,
|
|
150
|
+
stream_end_event(
|
|
151
|
+
commits=commits,
|
|
152
|
+
warnings=warnings,
|
|
153
|
+
errors=errors,
|
|
154
|
+
walk_finished=walk_finished,
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
return stream_exit_code(errors=errors, walk_finished=walk_finished)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def run_human(
|
|
161
|
+
*,
|
|
162
|
+
cwd: str | Path = ".",
|
|
163
|
+
profile: str | None = None,
|
|
164
|
+
qa: QaMode | None = None,
|
|
165
|
+
display: DisplayMode | None = None,
|
|
166
|
+
config_path: Path | None = None,
|
|
167
|
+
preferences_path: Path | None = None,
|
|
168
|
+
console: Console | None = None,
|
|
169
|
+
show_author: bool | None = None,
|
|
170
|
+
show_date: bool | None = None,
|
|
171
|
+
show_body: bool | None = None,
|
|
172
|
+
show_id: bool | None = None,
|
|
173
|
+
show_conventional: bool | None = None,
|
|
174
|
+
use_pager: bool | None = None,
|
|
175
|
+
git_log_args: Sequence[str] | None = None,
|
|
176
|
+
) -> int:
|
|
177
|
+
"""Render history for a person. Return the process exit code.
|
|
178
|
+
|
|
179
|
+
Explicit ``show_*`` CLI values override preference defaults; ``None`` keeps
|
|
180
|
+
the resolved preference. ``use_pager`` defaults to on when stdout is a TTY.
|
|
181
|
+
"""
|
|
182
|
+
analyzed, config = analyze_history(
|
|
183
|
+
cwd,
|
|
184
|
+
profile=profile,
|
|
185
|
+
qa=qa,
|
|
186
|
+
display=display,
|
|
187
|
+
config_path=config_path,
|
|
188
|
+
preferences_path=preferences_path,
|
|
189
|
+
git_log_args=git_log_args,
|
|
190
|
+
)
|
|
191
|
+
render_history(
|
|
192
|
+
analyzed,
|
|
193
|
+
console=console,
|
|
194
|
+
ref_id_width=config.ref_id_width,
|
|
195
|
+
type_width=config.type_width,
|
|
196
|
+
show_author=(config.show_author if show_author is None else show_author),
|
|
197
|
+
show_date=config.show_date if show_date is None else show_date,
|
|
198
|
+
show_body=config.show_body if show_body is None else show_body,
|
|
199
|
+
show_id=config.show_id if show_id is None else show_id,
|
|
200
|
+
show_conventional=(
|
|
201
|
+
config.show_conventional if show_conventional is None else show_conventional
|
|
202
|
+
),
|
|
203
|
+
use_pager=use_pager,
|
|
204
|
+
)
|
|
205
|
+
pairs = [(commit_id, result) for commit_id, result, _subject in analyzed]
|
|
206
|
+
return report_exit_code(build_report(pairs), walk_finished=True)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
__all__ = [
|
|
210
|
+
"GitError",
|
|
211
|
+
"analyze_history",
|
|
212
|
+
"run_human",
|
|
213
|
+
"run_json",
|
|
214
|
+
"run_json_stream",
|
|
215
|
+
]
|
esys_gitlog/cli.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Thin Typer command boundary for ``git-elog`` / ``git elog``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from typer.core import TyperGroup
|
|
11
|
+
|
|
12
|
+
from esys_gitlog import app as application
|
|
13
|
+
from esys_gitlog.config import DisplayMode, QaMode
|
|
14
|
+
from esys_gitlog.detection import detect_links, dump_detection_debug
|
|
15
|
+
from esys_gitlog.git_adapter import GitError, filter_git_log_args
|
|
16
|
+
from esys_gitlog.preferences import (
|
|
17
|
+
fill_link_gaps,
|
|
18
|
+
load_layered_preferences,
|
|
19
|
+
merge_missing_links_into_user_file,
|
|
20
|
+
preferences_to_object,
|
|
21
|
+
)
|
|
22
|
+
from esys_gitlog.tool_paths import user_preferences_path
|
|
23
|
+
|
|
24
|
+
# Subcommands must not be treated as git log revisions.
|
|
25
|
+
_SUBCOMMANDS = frozenset({"preferences"})
|
|
26
|
+
|
|
27
|
+
# elog options that consume the next argv token as a value.
|
|
28
|
+
_OPTIONS_WITH_VALUE = frozenset(
|
|
29
|
+
{
|
|
30
|
+
"--profile",
|
|
31
|
+
"--qa",
|
|
32
|
+
"--display",
|
|
33
|
+
"--config",
|
|
34
|
+
"--preferences",
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _ElogGroup(TyperGroup):
|
|
40
|
+
"""Group that only resolves known subcommands; other tokens stay as extra args."""
|
|
41
|
+
|
|
42
|
+
def resolve_command(
|
|
43
|
+
self, ctx: object, args: list[str]
|
|
44
|
+
) -> tuple[str | None, object | None, list[str]]:
|
|
45
|
+
if args and args[0] in self.commands:
|
|
46
|
+
return super().resolve_command(ctx, args) # type: ignore[arg-type]
|
|
47
|
+
return None, None, args
|
|
48
|
+
|
|
49
|
+
def invoke(self, ctx: object) -> object:
|
|
50
|
+
# Typer asserts cmd is not None when leftovers look like a subcommand.
|
|
51
|
+
# Unknown leftovers are git log args — fold them into ctx.args and run
|
|
52
|
+
# the group callback. Known names (``preferences``) keep normal dispatch.
|
|
53
|
+
protected = list(getattr(ctx, "_protected_args", None) or [])
|
|
54
|
+
extra = list(getattr(ctx, "args", None) or [])
|
|
55
|
+
combined = [*protected, *extra]
|
|
56
|
+
if combined and combined[0] not in self.commands:
|
|
57
|
+
ctx._protected_args = [] # type: ignore[attr-defined]
|
|
58
|
+
ctx.args = combined # type: ignore[attr-defined]
|
|
59
|
+
ctx.invoked_subcommand = None # type: ignore[attr-defined]
|
|
60
|
+
return super().invoke(ctx) # type: ignore[arg-type]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
app = typer.Typer(
|
|
64
|
+
cls=_ElogGroup,
|
|
65
|
+
add_completion=False,
|
|
66
|
+
context_settings={
|
|
67
|
+
"help_option_names": ["-h", "--help"],
|
|
68
|
+
"allow_extra_args": True,
|
|
69
|
+
# Unknown tokens (``-n``, ``--since``, …) become ctx.args for git log.
|
|
70
|
+
# Known elog flags still bind first; use ``--`` when a token is both.
|
|
71
|
+
"ignore_unknown_options": True,
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
preferences_app = typer.Typer(
|
|
75
|
+
add_completion=False,
|
|
76
|
+
help="Manage layered tool preferences (SADR-0009 / CADR-0012).",
|
|
77
|
+
)
|
|
78
|
+
app.add_typer(preferences_app, name="preferences")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def prepare_argv(argv: list[str]) -> list[str]:
|
|
82
|
+
"""Insert ``--`` before git-log positionals so Typer does not treat them as commands.
|
|
83
|
+
|
|
84
|
+
Bare revisions/pathspecs (``HEAD~5``, ``main``) are accepted without an
|
|
85
|
+
explicit separator. Pure integers after short options (e.g. ``-n 1``) are
|
|
86
|
+
left in place so they stay with the preceding flag. Unknown git log options
|
|
87
|
+
(``--since``, ``-n``, …) rely on ``ignore_unknown_options``.
|
|
88
|
+
"""
|
|
89
|
+
if "--" in argv:
|
|
90
|
+
return argv
|
|
91
|
+
index = 0
|
|
92
|
+
while index < len(argv):
|
|
93
|
+
token = argv[index]
|
|
94
|
+
if token in _OPTIONS_WITH_VALUE:
|
|
95
|
+
index += 2
|
|
96
|
+
continue
|
|
97
|
+
if token.startswith("--") and "=" in token:
|
|
98
|
+
index += 1
|
|
99
|
+
continue
|
|
100
|
+
if token.startswith("-"):
|
|
101
|
+
index += 1
|
|
102
|
+
continue
|
|
103
|
+
# Likely a value for a preceding short option such as ``-n 1``.
|
|
104
|
+
if token.isdigit():
|
|
105
|
+
index += 1
|
|
106
|
+
continue
|
|
107
|
+
# Positional: subcommand or git log revision/path.
|
|
108
|
+
if token in _SUBCOMMANDS:
|
|
109
|
+
return argv
|
|
110
|
+
return [*argv[:index], "--", *argv[index:]]
|
|
111
|
+
return argv
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.callback(invoke_without_command=True)
|
|
115
|
+
def cli(
|
|
116
|
+
ctx: typer.Context,
|
|
117
|
+
json_output: bool = typer.Option(
|
|
118
|
+
False,
|
|
119
|
+
"--json",
|
|
120
|
+
help="Write the QA report as one JSON document to standard output.",
|
|
121
|
+
),
|
|
122
|
+
json_stream: bool = typer.Option(
|
|
123
|
+
False,
|
|
124
|
+
"--json-stream",
|
|
125
|
+
help="Write NDJSON stream events (start/commit/end) to standard output.",
|
|
126
|
+
),
|
|
127
|
+
profile: str | None = typer.Option(
|
|
128
|
+
None,
|
|
129
|
+
"--profile",
|
|
130
|
+
help="Profile name: default, trailer, or profiles/<name>.json under tool roots.",
|
|
131
|
+
),
|
|
132
|
+
qa: str | None = typer.Option(
|
|
133
|
+
None,
|
|
134
|
+
"--qa",
|
|
135
|
+
help="QA mode: strict or permissive (overrides preference defaults).",
|
|
136
|
+
),
|
|
137
|
+
display: str | None = typer.Option(
|
|
138
|
+
None,
|
|
139
|
+
"--display",
|
|
140
|
+
help="Display precedence: subject-precedence or trailer-precedence.",
|
|
141
|
+
),
|
|
142
|
+
config: Path | None = typer.Option(
|
|
143
|
+
None,
|
|
144
|
+
"--config",
|
|
145
|
+
help="Path to a custom profile JSON object for this run.",
|
|
146
|
+
exists=True,
|
|
147
|
+
dir_okay=False,
|
|
148
|
+
readable=True,
|
|
149
|
+
),
|
|
150
|
+
preferences: Path | None = typer.Option(
|
|
151
|
+
None,
|
|
152
|
+
"--preferences",
|
|
153
|
+
help="Path to a preference JSON overlay (highest preference layer).",
|
|
154
|
+
exists=True,
|
|
155
|
+
dir_okay=False,
|
|
156
|
+
readable=True,
|
|
157
|
+
),
|
|
158
|
+
show_author: bool | None = typer.Option(
|
|
159
|
+
None,
|
|
160
|
+
"--author/--no-a",
|
|
161
|
+
"-a",
|
|
162
|
+
help=(
|
|
163
|
+
"Show author on an indented line under each commit (human output). "
|
|
164
|
+
"Overrides defaults.showAuthor when set. Short flags combine (e.g. -abd)."
|
|
165
|
+
),
|
|
166
|
+
),
|
|
167
|
+
show_date: bool | None = typer.Option(
|
|
168
|
+
None,
|
|
169
|
+
"--date/--no-d",
|
|
170
|
+
"-d",
|
|
171
|
+
help=(
|
|
172
|
+
"Show authored date on an indented line under each commit (human output). "
|
|
173
|
+
"Overrides defaults.showDate when set. Short flags combine (e.g. -abd)."
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
show_body: bool | None = typer.Option(
|
|
177
|
+
None,
|
|
178
|
+
"--body/--no-b",
|
|
179
|
+
"-b",
|
|
180
|
+
help=(
|
|
181
|
+
"Show commit body on indented lines under each commit (human output). "
|
|
182
|
+
"Overrides defaults.showBody when set. Short flags combine (e.g. -abd)."
|
|
183
|
+
),
|
|
184
|
+
),
|
|
185
|
+
show_id: bool | None = typer.Option(
|
|
186
|
+
None,
|
|
187
|
+
"--id/--no-i",
|
|
188
|
+
"-i",
|
|
189
|
+
help=(
|
|
190
|
+
"Show ticket / Jira reference ids on the summary line (human output). "
|
|
191
|
+
"Overrides defaults.showId when set (default on). Not the commit SHA."
|
|
192
|
+
),
|
|
193
|
+
),
|
|
194
|
+
show_conventional: bool | None = typer.Option(
|
|
195
|
+
None,
|
|
196
|
+
"--conventional/--no-c",
|
|
197
|
+
"-c",
|
|
198
|
+
help=(
|
|
199
|
+
"Show Type(Scope)[!] on the summary line (human output). "
|
|
200
|
+
"Overrides defaults.showConventional when set (default on)."
|
|
201
|
+
),
|
|
202
|
+
),
|
|
203
|
+
use_pager: bool | None = typer.Option(
|
|
204
|
+
None,
|
|
205
|
+
"--pager/--no-p",
|
|
206
|
+
"-p",
|
|
207
|
+
help=(
|
|
208
|
+
"Page human output one screen at a time (default: on when stdout is "
|
|
209
|
+
"a TTY). Enter/Space = next page, q = quit."
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Read Git history with the shared esys-gitlog contract.
|
|
214
|
+
|
|
215
|
+
Extra arguments after ``--`` (and unknown options / bare revisions) are
|
|
216
|
+
forwarded to ``git log``. Pretty/diff format options such as ``--oneline``
|
|
217
|
+
are ignored with a warning; elog owns framing.
|
|
218
|
+
"""
|
|
219
|
+
if ctx.invoked_subcommand is not None:
|
|
220
|
+
return
|
|
221
|
+
if qa is not None and qa not in ("strict", "permissive"):
|
|
222
|
+
typer.echo(f"unknown qa mode: {qa}", err=True)
|
|
223
|
+
raise typer.Exit(code=2)
|
|
224
|
+
if display is not None and display not in (
|
|
225
|
+
"subject-precedence",
|
|
226
|
+
"trailer-precedence",
|
|
227
|
+
):
|
|
228
|
+
typer.echo(f"unknown display mode: {display}", err=True)
|
|
229
|
+
raise typer.Exit(code=2)
|
|
230
|
+
|
|
231
|
+
cwd = Path.cwd()
|
|
232
|
+
qa_mode: QaMode | None = qa # type: ignore[assignment]
|
|
233
|
+
display_mode: DisplayMode | None = display # type: ignore[assignment]
|
|
234
|
+
if json_output and json_stream:
|
|
235
|
+
typer.echo("use only one of --json or --json-stream", err=True)
|
|
236
|
+
raise typer.Exit(code=2)
|
|
237
|
+
|
|
238
|
+
git_log_args, git_log_warnings = filter_git_log_args(list(ctx.args))
|
|
239
|
+
for message in git_log_warnings:
|
|
240
|
+
typer.echo(message, err=True)
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
if json_stream:
|
|
244
|
+
code = application.run_json_stream(
|
|
245
|
+
sys.stdout,
|
|
246
|
+
cwd=cwd,
|
|
247
|
+
profile=profile,
|
|
248
|
+
qa=qa_mode,
|
|
249
|
+
display=display_mode,
|
|
250
|
+
config_path=config,
|
|
251
|
+
preferences_path=preferences,
|
|
252
|
+
git_log_args=git_log_args,
|
|
253
|
+
)
|
|
254
|
+
elif json_output:
|
|
255
|
+
code = application.run_json(
|
|
256
|
+
sys.stdout,
|
|
257
|
+
cwd=cwd,
|
|
258
|
+
profile=profile,
|
|
259
|
+
qa=qa_mode,
|
|
260
|
+
display=display_mode,
|
|
261
|
+
config_path=config,
|
|
262
|
+
preferences_path=preferences,
|
|
263
|
+
git_log_args=git_log_args,
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
code = application.run_human(
|
|
267
|
+
cwd=cwd,
|
|
268
|
+
profile=profile,
|
|
269
|
+
qa=qa_mode,
|
|
270
|
+
display=display_mode,
|
|
271
|
+
config_path=config,
|
|
272
|
+
preferences_path=preferences,
|
|
273
|
+
show_author=show_author,
|
|
274
|
+
show_date=show_date,
|
|
275
|
+
show_body=show_body,
|
|
276
|
+
show_id=show_id,
|
|
277
|
+
show_conventional=show_conventional,
|
|
278
|
+
use_pager=use_pager,
|
|
279
|
+
git_log_args=git_log_args,
|
|
280
|
+
)
|
|
281
|
+
except GitError as exc:
|
|
282
|
+
typer.echo(str(exc), err=True)
|
|
283
|
+
raise typer.Exit(code=2) from exc
|
|
284
|
+
except ValueError as exc:
|
|
285
|
+
typer.echo(str(exc), err=True)
|
|
286
|
+
raise typer.Exit(code=2) from exc
|
|
287
|
+
raise typer.Exit(code=code)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@preferences_app.command("detect")
|
|
291
|
+
def preferences_detect(
|
|
292
|
+
write: bool = typer.Option(
|
|
293
|
+
True,
|
|
294
|
+
"--write/--no-write",
|
|
295
|
+
help="Merge discovered link bases into the user config.json (missing keys only).",
|
|
296
|
+
),
|
|
297
|
+
show_debug: bool = typer.Option(
|
|
298
|
+
False,
|
|
299
|
+
"--debug",
|
|
300
|
+
help="Print which vendor CLIs are on PATH.",
|
|
301
|
+
),
|
|
302
|
+
) -> None:
|
|
303
|
+
"""Probe local Jira/GitHub/GitLab CLIs and git remote; optionally persist."""
|
|
304
|
+
cwd = Path.cwd()
|
|
305
|
+
prefs = load_layered_preferences(cwd)
|
|
306
|
+
discovered = detect_links(cwd, detection=prefs.detection)
|
|
307
|
+
if show_debug:
|
|
308
|
+
typer.echo(dump_detection_debug())
|
|
309
|
+
merged = fill_link_gaps(prefs, discovered)
|
|
310
|
+
typer.echo(json.dumps(preferences_to_object(merged), indent=2))
|
|
311
|
+
if write:
|
|
312
|
+
path = user_preferences_path()
|
|
313
|
+
merge_missing_links_into_user_file(discovered=discovered, user_path=path)
|
|
314
|
+
typer.echo(f"wrote missing keys to {path}", err=True)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def main() -> None:
|
|
318
|
+
"""Console script entry point."""
|
|
319
|
+
app(args=prepare_argv(sys.argv[1:]), prog_name="git-elog")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if __name__ == "__main__":
|
|
323
|
+
main()
|