gitwise-cli 0.24.2__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.
Files changed (125) hide show
  1. gitwise/__init__.py +11 -0
  2. gitwise/__main__.py +113 -0
  3. gitwise/_cli_completions.py +88 -0
  4. gitwise/_cli_dispatch.py +469 -0
  5. gitwise/_cli_introspection.py +275 -0
  6. gitwise/_cli_parser.py +345 -0
  7. gitwise/_cli_setup_agents.py +439 -0
  8. gitwise/_i18n_data.json +1934 -0
  9. gitwise/_paths.py +22 -0
  10. gitwise/_runtime_config.py +246 -0
  11. gitwise/audit.py +338 -0
  12. gitwise/branches.py +183 -0
  13. gitwise/clean.py +197 -0
  14. gitwise/commit.py +142 -0
  15. gitwise/conflicts.py +112 -0
  16. gitwise/context.py +163 -0
  17. gitwise/design.py +383 -0
  18. gitwise/diff.py +309 -0
  19. gitwise/doctor.py +116 -0
  20. gitwise/git.py +254 -0
  21. gitwise/health.py +345 -0
  22. gitwise/i18n.py +99 -0
  23. gitwise/log.py +329 -0
  24. gitwise/merge.py +193 -0
  25. gitwise/optimize.py +212 -0
  26. gitwise/output.py +652 -0
  27. gitwise/pick.py +102 -0
  28. gitwise/pr.py +543 -0
  29. gitwise/py.typed +0 -0
  30. gitwise/schema.py +49 -0
  31. gitwise/setup.py +551 -0
  32. gitwise/setup_agents/__init__.py +36 -0
  33. gitwise/setup_agents/adapters/__init__.py +17 -0
  34. gitwise/setup_agents/adapters/aider.py +5 -0
  35. gitwise/setup_agents/adapters/base.py +5 -0
  36. gitwise/setup_agents/adapters/codex.py +5 -0
  37. gitwise/setup_agents/adapters/continue_adapter.py +5 -0
  38. gitwise/setup_agents/adapters/cursor.py +5 -0
  39. gitwise/setup_agents/adapters/opencode.py +5 -0
  40. gitwise/setup_agents/adapters/pi.py +5 -0
  41. gitwise/setup_agents/exec.py +449 -0
  42. gitwise/setup_agents/format.py +164 -0
  43. gitwise/setup_agents/plan.py +254 -0
  44. gitwise/setup_agents/plan_gitfiles.py +167 -0
  45. gitwise/setup_agents/plan_skills.py +256 -0
  46. gitwise/setup_agents/providers/__init__.py +96 -0
  47. gitwise/setup_agents/providers/aider.py +11 -0
  48. gitwise/setup_agents/providers/base.py +79 -0
  49. gitwise/setup_agents/providers/claude.py +408 -0
  50. gitwise/setup_agents/providers/codex.py +11 -0
  51. gitwise/setup_agents/providers/continue_adapter.py +11 -0
  52. gitwise/setup_agents/providers/cursor.py +11 -0
  53. gitwise/setup_agents/providers/opencode.py +11 -0
  54. gitwise/setup_agents/providers/pi.py +11 -0
  55. gitwise/setup_agents/state.py +141 -0
  56. gitwise/setup_agents/types.py +48 -0
  57. gitwise/share/agents/skills/git-audit/SKILL.md +25 -0
  58. gitwise/share/agents/skills/git-clean/SKILL.md +22 -0
  59. gitwise/share/agents/skills/git-optimize/SKILL.md +21 -0
  60. gitwise/share/aider/CONVENTIONS.md.template +8 -0
  61. gitwise/share/aider/aider.conf.yml.template +4 -0
  62. gitwise/share/claude/CLAUDE.md.template +9 -0
  63. gitwise/share/claude/rules/gitwise.md +16 -0
  64. gitwise/share/claude/settings.json.template +47 -0
  65. gitwise/share/claude/skills/git-audit/SKILL.md +25 -0
  66. gitwise/share/claude/skills/git-clean/SKILL.md +22 -0
  67. gitwise/share/claude/skills/git-optimize/SKILL.md +21 -0
  68. gitwise/share/codex/agents/gitwise.toml.template +18 -0
  69. gitwise/share/continue/rules/gitwise.md.template +14 -0
  70. gitwise/share/cursor/rules/gitwise.mdc.template +16 -0
  71. gitwise/share/git-config-modern.txt +48 -0
  72. gitwise/share/hooks/commit-msg +22 -0
  73. gitwise/share/hooks/pre-commit +19 -0
  74. gitwise/share/opencode/agents/gitwise.md.template +14 -0
  75. gitwise/share/pi/skills/gitwise.md.template +14 -0
  76. gitwise/share/schemas/v1/input/audit.json +40 -0
  77. gitwise/share/schemas/v1/input/branches.json +51 -0
  78. gitwise/share/schemas/v1/input/clean.json +52 -0
  79. gitwise/share/schemas/v1/input/commands.json +36 -0
  80. gitwise/share/schemas/v1/input/commit.json +63 -0
  81. gitwise/share/schemas/v1/input/completions.json +51 -0
  82. gitwise/share/schemas/v1/input/conflicts.json +46 -0
  83. gitwise/share/schemas/v1/input/context.json +36 -0
  84. gitwise/share/schemas/v1/input/diff.json +56 -0
  85. gitwise/share/schemas/v1/input/doctor.json +36 -0
  86. gitwise/share/schemas/v1/input/health.json +36 -0
  87. gitwise/share/schemas/v1/input/log.json +71 -0
  88. gitwise/share/schemas/v1/input/merge.json +63 -0
  89. gitwise/share/schemas/v1/input/optimize.json +44 -0
  90. gitwise/share/schemas/v1/input/pick.json +63 -0
  91. gitwise/share/schemas/v1/input/pr.json +51 -0
  92. gitwise/share/schemas/v1/input/schema.json +48 -0
  93. gitwise/share/schemas/v1/input/setup-agents.json +108 -0
  94. gitwise/share/schemas/v1/input/setup.json +55 -0
  95. gitwise/share/schemas/v1/input/show.json +46 -0
  96. gitwise/share/schemas/v1/input/snapshot.json +36 -0
  97. gitwise/share/schemas/v1/input/stash.json +68 -0
  98. gitwise/share/schemas/v1/input/status.json +36 -0
  99. gitwise/share/schemas/v1/input/suggest.json +36 -0
  100. gitwise/share/schemas/v1/input/summarize.json +44 -0
  101. gitwise/share/schemas/v1/input/sync.json +55 -0
  102. gitwise/share/schemas/v1/input/tag.json +73 -0
  103. gitwise/share/schemas/v1/input/undo.json +60 -0
  104. gitwise/share/schemas/v1/input/update.json +40 -0
  105. gitwise/share/schemas/v1/input/worktree.json +50 -0
  106. gitwise/show.py +118 -0
  107. gitwise/snapshot.py +110 -0
  108. gitwise/stash.py +188 -0
  109. gitwise/status.py +93 -0
  110. gitwise/suggest.py +148 -0
  111. gitwise/summarize.py +202 -0
  112. gitwise/sync.py +257 -0
  113. gitwise/tag.py +252 -0
  114. gitwise/undo.py +145 -0
  115. gitwise/update.py +42 -0
  116. gitwise/utils/__init__.py +1 -0
  117. gitwise/utils/git_output.py +51 -0
  118. gitwise/utils/json_envelope.py +58 -0
  119. gitwise/utils/parsing.py +34 -0
  120. gitwise/worktree.py +182 -0
  121. gitwise_cli-0.24.2.dist-info/METADATA +151 -0
  122. gitwise_cli-0.24.2.dist-info/RECORD +125 -0
  123. gitwise_cli-0.24.2.dist-info/WHEEL +4 -0
  124. gitwise_cli-0.24.2.dist-info/entry_points.txt +2 -0
  125. gitwise_cli-0.24.2.dist-info/licenses/LICENSE +21 -0
gitwise/log.py ADDED
@@ -0,0 +1,329 @@
1
+ """gitwise log — pretty git log with filters and JSON output."""
2
+
3
+ import subprocess
4
+ from pathlib import Path
5
+
6
+ from .git import require_root, validate_author_pattern, validate_grep_pattern
7
+ from .git import run as git_run
8
+ from .i18n import t
9
+ from .output import bat_pipe, error, info, print_json, print_table
10
+ from .utils.json_envelope import ok_envelope
11
+
12
+
13
+ def _build_log_args(
14
+ *,
15
+ author: str | None = None,
16
+ grep: str | None = None,
17
+ since: str | None = None,
18
+ until: str | None = None,
19
+ file: str | None = None,
20
+ oneline: bool = False,
21
+ graph: bool = False,
22
+ max_count: int = 20,
23
+ ) -> list[str]:
24
+ args = ["log", f"--max-count={max_count}"]
25
+ if graph:
26
+ args.append("--graph")
27
+ if oneline:
28
+ args.append("--oneline")
29
+ else:
30
+ args.append("--format=%h\t%an\t%ad\t%s")
31
+ args.append("--date=short")
32
+ if author:
33
+ if not validate_author_pattern(author):
34
+ error(t("invalid_author_pattern", pattern=author[:50]))
35
+ raise ValueError(f"unsafe author pattern: {author[:50]}")
36
+ args.append(f"--author={author}")
37
+ if grep:
38
+ if not validate_grep_pattern(grep):
39
+ error(t("invalid_grep_pattern", pattern=grep[:50]))
40
+ raise ValueError(f"unsafe grep pattern: {grep[:50]}")
41
+ args.append(f"--grep={grep}")
42
+ if since:
43
+ args.append(f"--since={since}")
44
+ if until:
45
+ args.append(f"--until={until}")
46
+ if file:
47
+ args.append("--")
48
+ args.append(file)
49
+ return args
50
+
51
+
52
+ def _build_log_json_args(
53
+ *,
54
+ author: str | None = None,
55
+ grep: str | None = None,
56
+ since: str | None = None,
57
+ until: str | None = None,
58
+ file: str | None = None,
59
+ max_count: int = 20,
60
+ ) -> list[str]:
61
+ args = [
62
+ "log",
63
+ f"--max-count={max_count}",
64
+ "--format=%H%n%h%n%an%n%ae%n%ad%n%s%n%P%n---END---",
65
+ "--date=iso",
66
+ ]
67
+ if author:
68
+ if not validate_author_pattern(author):
69
+ error(t("invalid_author_pattern", pattern=author[:50]))
70
+ raise ValueError(f"unsafe author pattern: {author[:50]}")
71
+ args.append(f"--author={author}")
72
+ if grep:
73
+ if not validate_grep_pattern(grep):
74
+ error(t("invalid_grep_pattern", pattern=grep[:50]))
75
+ raise ValueError(f"unsafe grep pattern: {grep[:50]}")
76
+ args.append(f"--grep={grep}")
77
+ if since:
78
+ args.append(f"--since={since}")
79
+ if until:
80
+ args.append(f"--until={until}")
81
+ if file:
82
+ args.append("--")
83
+ args.append(file)
84
+ return args
85
+
86
+
87
+ def _parse_log_json(raw: str) -> list[dict[str, str]]:
88
+ commits: list[dict[str, str]] = []
89
+ entries = raw.split("---END---")
90
+ for entry in entries:
91
+ lines = entry.strip().splitlines()
92
+ if len(lines) >= 7:
93
+ commits.append(
94
+ {
95
+ "hash": lines[0],
96
+ "short_hash": lines[1],
97
+ "author": lines[2],
98
+ "email": lines[3],
99
+ "date": lines[4],
100
+ "subject": lines[5],
101
+ "parents": lines[6],
102
+ }
103
+ )
104
+ elif len(lines) == 6:
105
+ commits.append(
106
+ {
107
+ "hash": lines[0],
108
+ "short_hash": lines[1],
109
+ "author": lines[2],
110
+ "email": lines[3],
111
+ "date": lines[4],
112
+ "subject": lines[5],
113
+ "parents": "",
114
+ }
115
+ )
116
+ return commits
117
+
118
+
119
+ def _enrich_with_stats(commits: list[dict[str, str]], cwd: Path) -> None:
120
+ if not commits:
121
+ return
122
+ hashes = "\n".join(c["hash"] for c in commits)
123
+ r = subprocess.run(
124
+ ["git", "diff-tree", "--stdin", "--no-commit-id", "--stat", "-r"],
125
+ input=hashes,
126
+ capture_output=True,
127
+ text=True,
128
+ cwd=cwd,
129
+ timeout=120,
130
+ )
131
+ if r.returncode != 0:
132
+ for c in commits:
133
+ c["stats"] = ""
134
+ return
135
+ stats_by_hash: dict[str, str] = {}
136
+ current_hash = ""
137
+ current_lines: list[str] = []
138
+ for line in r.stdout.splitlines():
139
+ if not line and current_hash:
140
+ stats_by_hash[current_hash] = "\n".join(current_lines).strip()
141
+ current_hash = ""
142
+ current_lines = []
143
+ continue
144
+ if len(line) >= 40 and all(c in "0123456789abcdef" for c in line[:40]):
145
+ if current_hash and current_lines:
146
+ stats_by_hash[current_hash] = "\n".join(current_lines).strip()
147
+ current_hash = line.strip()
148
+ current_lines = []
149
+ else:
150
+ current_lines.append(line)
151
+ if current_hash and current_lines:
152
+ stats_by_hash[current_hash] = "\n".join(current_lines).strip()
153
+ for c in commits:
154
+ c["stats"] = stats_by_hash.get(c["hash"], "")
155
+
156
+
157
+ def _parse_log_table(raw: str) -> list[list[str]]:
158
+ rows: list[list[str]] = []
159
+ for line in raw.splitlines():
160
+ if not line.strip():
161
+ continue
162
+ parts = line.split("\t")
163
+ if len(parts) >= 4:
164
+ rows.append(
165
+ [
166
+ parts[0],
167
+ parts[1],
168
+ parts[2],
169
+ parts[3],
170
+ ]
171
+ )
172
+ elif len(parts) >= 3:
173
+ rows.append(
174
+ [
175
+ parts[0],
176
+ parts[1],
177
+ parts[2],
178
+ "",
179
+ ]
180
+ )
181
+ return rows
182
+
183
+
184
+ def _run_log_json(
185
+ *,
186
+ root: Path,
187
+ author: str | None,
188
+ grep: str | None,
189
+ since: str | None,
190
+ until: str | None,
191
+ file: str | None,
192
+ max_count: int,
193
+ ) -> int:
194
+ try:
195
+ args = _build_log_json_args(
196
+ author=author,
197
+ grep=grep,
198
+ since=since,
199
+ until=until,
200
+ file=file,
201
+ max_count=max_count,
202
+ )
203
+ except ValueError:
204
+ return 1
205
+ result = git_run(args, cwd=root, check=False)
206
+ if result.returncode != 0:
207
+ if "does not have any commits yet" in result.stderr:
208
+ print_json(ok_envelope(commits=[], count=0))
209
+ return 0
210
+ error(t("git_log_failed", error=result.stderr.strip()))
211
+ return 1
212
+ commits = _parse_log_json(result.stdout)
213
+ _enrich_with_stats(commits, root)
214
+ print_json(ok_envelope(commits=commits, count=len(commits)))
215
+ return 0
216
+
217
+
218
+ def _run_log_human(
219
+ *,
220
+ root: Path,
221
+ oneline: bool,
222
+ graph: bool,
223
+ author: str | None,
224
+ grep: str | None,
225
+ since: str | None,
226
+ until: str | None,
227
+ file: str | None,
228
+ max_count: int,
229
+ ) -> int:
230
+ try:
231
+ args = _build_log_args(
232
+ oneline=oneline,
233
+ graph=graph,
234
+ author=author,
235
+ grep=grep,
236
+ since=since,
237
+ until=until,
238
+ file=file,
239
+ max_count=max_count,
240
+ )
241
+ except ValueError:
242
+ return 1
243
+ result = git_run(args, cwd=root, check=False)
244
+ if result.returncode != 0:
245
+ if result.stderr and "does not have any commits yet" in result.stderr:
246
+ info(t("no_commits_yet"))
247
+ return 0
248
+ error(t("git_log_failed", error=result.stderr.strip()))
249
+ return 1
250
+ if not result.stdout.strip():
251
+ info(t("no_commits_yet"))
252
+ return 0
253
+ if _run_log_human_plain_or_graph(raw=result.stdout, graph=graph, oneline=oneline):
254
+ return 0
255
+
256
+ return _print_log_table(result.stdout)
257
+
258
+
259
+ def _print_log_table(raw: str) -> int:
260
+ rows = _parse_log_table(raw)
261
+ if not rows:
262
+ info(t("no_commits_yet"))
263
+ return 0
264
+ columns = [
265
+ (t("col_sha"), "sha"),
266
+ (t("col_author"), "author"),
267
+ (t("col_date"), "date"),
268
+ (t("col_subject"), "subject"),
269
+ ]
270
+ print_table(
271
+ title=t("git_log_title"),
272
+ columns=columns,
273
+ rows=rows,
274
+ no_wrap_columns={0, 2},
275
+ min_widths={0: 7, 2: 10},
276
+ max_widths={0: 10, 1: 20},
277
+ overflow_columns={0: "crop", 1: "ellipsis", 2: "crop", 3: "ellipsis"},
278
+ column_ratios={3: 4},
279
+ )
280
+ return 0
281
+
282
+
283
+ def _run_log_human_plain_or_graph(*, raw: str, graph: bool, oneline: bool) -> bool:
284
+ if graph or oneline:
285
+ bat_pipe(raw, language="gitlog")
286
+ return True
287
+ return False
288
+
289
+
290
+ def run_log(
291
+ *,
292
+ as_json: bool = False,
293
+ oneline: bool = False,
294
+ graph: bool = False,
295
+ author: str | None = None,
296
+ grep: str | None = None,
297
+ since: str | None = None,
298
+ until: str | None = None,
299
+ file: str | None = None,
300
+ max_count: int = 20,
301
+ ) -> int:
302
+ root, err = require_root()
303
+ if err:
304
+ return err
305
+ if root is None:
306
+ return 1
307
+
308
+ if as_json:
309
+ return _run_log_json(
310
+ root=root,
311
+ author=author,
312
+ grep=grep,
313
+ since=since,
314
+ until=until,
315
+ file=file,
316
+ max_count=max_count,
317
+ )
318
+
319
+ return _run_log_human(
320
+ root=root,
321
+ oneline=oneline,
322
+ graph=graph,
323
+ author=author,
324
+ grep=grep,
325
+ since=since,
326
+ until=until,
327
+ file=file,
328
+ max_count=max_count,
329
+ )
gitwise/merge.py ADDED
@@ -0,0 +1,193 @@
1
+ """gitwise merge — merge/rebase with pre-flight checks."""
2
+
3
+ from pathlib import Path
4
+
5
+ from .git import current_branch, require_root, validate_ref
6
+ from .git import run as git_run
7
+ from .i18n import t
8
+ from .output import (
9
+ confirm,
10
+ error,
11
+ ok,
12
+ print_bracket,
13
+ print_header,
14
+ print_json,
15
+ warn,
16
+ )
17
+ from .utils.json_envelope import error_envelope, ok_envelope
18
+ from .utils.parsing import to_int
19
+
20
+
21
+ def _has_uncommitted(root: Path) -> bool:
22
+ r = git_run(["status", "--porcelain"], cwd=root, check=False)
23
+ return r.returncode == 0 and bool(r.stdout.strip())
24
+
25
+
26
+ def _branch_exists(root: Path, name: str) -> bool:
27
+ r = git_run(["rev-parse", "--verify", "refs/heads/" + name], cwd=root, check=False)
28
+ return r.returncode == 0
29
+
30
+
31
+ def _validate_merge_target(*, root: Path, branch: str, cur: str | None) -> str | None:
32
+ if cur is None:
33
+ return t("merge_detached_head")
34
+ if not validate_ref(branch):
35
+ return t("invalid_ref", ref=branch)
36
+ if not _branch_exists(root, branch):
37
+ return t("merge_branch_not_found", branch=branch)
38
+ if branch == cur:
39
+ return t("merge_same_branch")
40
+ return None
41
+
42
+
43
+ def _ahead_behind_counts(*, root: Path, branch: str) -> tuple[int, int]:
44
+ ahead = git_run(["rev-list", "--count", f"{branch}..HEAD"], cwd=root, check=False)
45
+ behind = git_run(["rev-list", "--count", f"HEAD..{branch}"], cwd=root, check=False)
46
+ ahead_count = to_int(ahead.stdout, default=0) if ahead.returncode == 0 else 0
47
+ behind_count = to_int(behind.stdout, default=0) if behind.returncode == 0 else 0
48
+ return ahead_count, behind_count
49
+
50
+
51
+ def _merge_warnings(*, root: Path, branch: str, ahead_count: int, behind_count: int) -> list[str]:
52
+ warnings: list[str] = []
53
+ if _has_uncommitted(root):
54
+ warnings.append(t("merge_uncommitted"))
55
+ if ahead_count > 0 and behind_count > 0:
56
+ warnings.append(t("merge_diverged", ahead=str(ahead_count), behind=str(behind_count)))
57
+ return warnings
58
+
59
+
60
+ def _handle_merge_dry_run(
61
+ *,
62
+ as_json: bool,
63
+ rebase: bool,
64
+ branch: str,
65
+ cur: str,
66
+ ahead_count: int,
67
+ behind_count: int,
68
+ warnings: list[str],
69
+ ) -> int:
70
+ if as_json:
71
+ print_json(
72
+ ok_envelope(
73
+ dry_run=True,
74
+ action="rebase" if rebase else "merge",
75
+ branch=branch,
76
+ current=cur,
77
+ ahead=ahead_count,
78
+ behind=behind_count,
79
+ warnings=warnings,
80
+ )
81
+ )
82
+ return 0
83
+ action = t("merge_rebase_label") if rebase else t("merge_merge_label")
84
+ print_header(f"{action}: {branch} -> {cur}")
85
+ if ahead_count or behind_count:
86
+ print_bracket(t("status_ahead_label"), str(ahead_count))
87
+ print_bracket(t("status_behind_label"), str(behind_count))
88
+ for warning_msg in warnings:
89
+ warn(warning_msg)
90
+ return 0
91
+
92
+
93
+ def _execute_merge(*, root: Path, branch: str, rebase: bool, no_ff: bool) -> tuple[bool, str]:
94
+ if rebase:
95
+ args = ["rebase", branch]
96
+ else:
97
+ args = ["merge"]
98
+ if no_ff:
99
+ args.append("--no-ff")
100
+ args.append(branch)
101
+ result = git_run(args, cwd=root, check=False)
102
+ if result.returncode == 0:
103
+ return True, ""
104
+ err = (
105
+ t("merge_conflicts")
106
+ if ("CONFLICT" in result.stdout or "CONFLICT" in result.stderr)
107
+ else t("git_command_failed", cmd="merge/rebase", error=result.stderr.strip())
108
+ )
109
+ return False, err
110
+
111
+
112
+ def _confirm_merge_warnings(*, warnings: list[str], yes: bool) -> bool:
113
+ if not warnings:
114
+ return True
115
+ for warning_msg in warnings:
116
+ warn(warning_msg)
117
+ if yes:
118
+ return True
119
+ if confirm(t("merge_proceed")):
120
+ return True
121
+ warn(t("aborted"))
122
+ return False
123
+
124
+
125
+ def _report_merge_success(*, as_json: bool, branch: str, cur: str, rebase: bool) -> int:
126
+ if as_json:
127
+ print_json(ok_envelope(merged=branch, into=cur))
128
+ return 0
129
+ label = (
130
+ t("merge_rebased", branch=branch, into=cur)
131
+ if rebase
132
+ else t("merge_ok", branch=branch, into=cur)
133
+ )
134
+ ok(label)
135
+ return 0
136
+
137
+
138
+ def _report_merge_error(*, as_json: bool, err: str) -> int:
139
+ if as_json:
140
+ print_json(error_envelope(error=err, code="merge_error", hint=t("merge_hint")))
141
+ else:
142
+ error(err, hint=t("merge_hint"))
143
+ return 1
144
+
145
+
146
+ def run_merge(
147
+ branch: str,
148
+ *,
149
+ rebase: bool = False,
150
+ no_ff: bool = False,
151
+ dry_run: bool = False,
152
+ yes: bool = False,
153
+ as_json: bool = False,
154
+ ) -> int:
155
+ root, err = require_root()
156
+ if err:
157
+ return err
158
+ if root is None:
159
+ return 1
160
+
161
+ cur = current_branch(root)
162
+ target_err = _validate_merge_target(root=root, branch=branch, cur=cur)
163
+ if target_err is not None:
164
+ return _report_merge_error(as_json=as_json, err=target_err)
165
+ assert cur is not None
166
+
167
+ ahead_count, behind_count = _ahead_behind_counts(root=root, branch=branch)
168
+ warnings = _merge_warnings(
169
+ root=root,
170
+ branch=branch,
171
+ ahead_count=ahead_count,
172
+ behind_count=behind_count,
173
+ )
174
+
175
+ if dry_run:
176
+ return _handle_merge_dry_run(
177
+ as_json=as_json,
178
+ rebase=rebase,
179
+ branch=branch,
180
+ cur=cur,
181
+ ahead_count=ahead_count,
182
+ behind_count=behind_count,
183
+ warnings=warnings,
184
+ )
185
+
186
+ if not _confirm_merge_warnings(warnings=warnings, yes=yes):
187
+ return 1
188
+
189
+ success, err = _execute_merge(root=root, branch=branch, rebase=rebase, no_ff=no_ff)
190
+ if not success:
191
+ return _report_merge_error(as_json=as_json, err=err)
192
+
193
+ return _report_merge_success(as_json=as_json, branch=branch, cur=cur, rebase=rebase)