path-sync 0.7.5__tar.gz → 0.7.6__tar.gz
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.
- {path_sync-0.7.5 → path_sync-0.7.6}/PKG-INFO +1 -1
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/__init__.py +1 -1
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/auto_merge.py +28 -10
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/cmd_copy.py +16 -5
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/git_ops.py +2 -1
- {path_sync-0.7.5 → path_sync-0.7.6}/pyproject.toml +1 -1
- {path_sync-0.7.5 → path_sync-0.7.6}/.gitignore +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/LICENSE +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/README.md +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/__main__.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/__init__.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/cmd_boot.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/cmd_dep_update.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/cmd_options.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/cmd_validate.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/file_utils.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/header.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/log_capture.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/models.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/models_dep.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/prompt_utils.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/repo_utils.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/typer_app.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/validation.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/verify.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/_internal/yaml_utils.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/config.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/copy.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/dep_update.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/sections.py +0 -0
- {path_sync-0.7.5 → path_sync-0.7.6}/path_sync/validate_no_changes.py +0 -0
|
@@ -70,15 +70,16 @@ class PRMergeResult(BaseModel):
|
|
|
70
70
|
return [c for c in self.checks if c.pending]
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def enable_auto_merge(repo_path: Path, pr_ref: str, config: AutoMergeConfig) -> None:
|
|
73
|
+
def enable_auto_merge(repo_path: Path, pr_ref: str, config: AutoMergeConfig, dest_name: str = "") -> None:
|
|
74
|
+
label = dest_name or pr_ref
|
|
74
75
|
cmd = ["gh", "pr", "merge", "--auto", f"--{config.method}", pr_ref]
|
|
75
76
|
if config.delete_branch:
|
|
76
77
|
cmd.append("--delete-branch")
|
|
77
78
|
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
78
79
|
if result.returncode != 0:
|
|
79
|
-
logger.warning(f"
|
|
80
|
+
logger.warning(f" {label}: auto-merge enable failed: {result.stderr.strip()}")
|
|
80
81
|
else:
|
|
81
|
-
logger.info(f"
|
|
82
|
+
logger.info(f" {label}: enabled auto-merge ({config.method})")
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
def get_pr_checks(repo_path: Path, pr_ref: str) -> list[CheckRun]:
|
|
@@ -103,6 +104,7 @@ def get_pr_url(repo_path: Path, pr_ref: str) -> str:
|
|
|
103
104
|
cmd = ["gh", "pr", "view", pr_ref, "--json", "url", "-q", ".url"]
|
|
104
105
|
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
105
106
|
if result.returncode != 0:
|
|
107
|
+
logger.warning(f"Failed to get PR URL for {pr_ref}: {result.stderr.strip()}")
|
|
106
108
|
return pr_ref
|
|
107
109
|
return result.stdout.strip() or pr_ref
|
|
108
110
|
|
|
@@ -110,21 +112,31 @@ def get_pr_url(repo_path: Path, pr_ref: str) -> str:
|
|
|
110
112
|
def wait_for_merge(repo_path: Path, pr_ref: str, config: AutoMergeConfig, dest_name: str = "") -> PRMergeResult:
|
|
111
113
|
pr_url = get_pr_url(repo_path, pr_ref)
|
|
112
114
|
deadline = time.monotonic() + config.timeout_seconds
|
|
115
|
+
label = dest_name or pr_ref
|
|
116
|
+
poll_count = 0
|
|
113
117
|
|
|
114
118
|
while time.monotonic() < deadline:
|
|
115
119
|
state = get_pr_state(repo_path, pr_ref)
|
|
116
120
|
if state == PRState.MERGED:
|
|
121
|
+
logger.info(f"{label}: merged ({pr_url})")
|
|
117
122
|
return PRMergeResult(dest_name=dest_name, pr_url=pr_url, branch=pr_ref, state=PRState.MERGED)
|
|
118
123
|
if state == PRState.CLOSED:
|
|
119
124
|
checks = get_pr_checks(repo_path, pr_ref)
|
|
125
|
+
logger.warning(f"{label}: PR closed without merging ({pr_url})")
|
|
120
126
|
return PRMergeResult(dest_name=dest_name, pr_url=pr_url, branch=pr_ref, state=PRState.CLOSED, checks=checks)
|
|
127
|
+
poll_count += 1
|
|
128
|
+
elapsed = int(config.timeout_seconds - (deadline - time.monotonic()))
|
|
129
|
+
logger.info(f"{label}: still open (poll #{poll_count}, {elapsed}s elapsed)")
|
|
121
130
|
time.sleep(config.poll_interval_seconds)
|
|
122
131
|
|
|
123
132
|
checks = get_pr_checks(repo_path, pr_ref)
|
|
124
|
-
logger.warning(f"Timeout waiting for {
|
|
133
|
+
logger.warning(f"Timeout waiting for {label} after {config.timeout_seconds}s ({pr_url})")
|
|
125
134
|
return PRMergeResult(dest_name=dest_name, pr_url=pr_url, branch=pr_ref, state=PRState.OPEN, checks=checks)
|
|
126
135
|
|
|
127
136
|
|
|
137
|
+
SEPARATOR_WIDTH = 40
|
|
138
|
+
|
|
139
|
+
|
|
128
140
|
def handle_auto_merge(
|
|
129
141
|
pr_refs: list[PRRef],
|
|
130
142
|
config: AutoMergeConfig,
|
|
@@ -133,22 +145,27 @@ def handle_auto_merge(
|
|
|
133
145
|
if not pr_refs:
|
|
134
146
|
return []
|
|
135
147
|
|
|
148
|
+
line = "─" * SEPARATOR_WIDTH
|
|
149
|
+
logger.info(f"\n{line}")
|
|
150
|
+
logger.info(" Auto-merge")
|
|
151
|
+
logger.info(line)
|
|
152
|
+
|
|
136
153
|
pending_refs: list[PRRef] = []
|
|
137
154
|
for ref in pr_refs:
|
|
138
155
|
state = get_pr_state(ref.repo_path, ref.branch_or_url)
|
|
139
156
|
if state == PRState.MERGED:
|
|
140
|
-
logger.info(f"{ref.dest_name}: already merged")
|
|
157
|
+
logger.info(f" {ref.dest_name}: already merged")
|
|
141
158
|
continue
|
|
142
|
-
enable_auto_merge(ref.repo_path, ref.branch_or_url, config)
|
|
159
|
+
enable_auto_merge(ref.repo_path, ref.branch_or_url, config, dest_name=ref.dest_name)
|
|
143
160
|
pending_refs.append(ref)
|
|
144
161
|
|
|
145
162
|
if no_wait:
|
|
146
|
-
logger.info("--no-wait: skipping merge polling")
|
|
163
|
+
logger.info(" --no-wait: skipping merge polling")
|
|
147
164
|
return []
|
|
148
165
|
|
|
149
166
|
results: list[PRMergeResult] = []
|
|
150
167
|
for ref in pending_refs:
|
|
151
|
-
logger.info(f"Waiting for {ref.dest_name} to merge...")
|
|
168
|
+
logger.info(f" Waiting for {ref.dest_name} to merge...")
|
|
152
169
|
result = wait_for_merge(ref.repo_path, ref.branch_or_url, config, dest_name=ref.dest_name)
|
|
153
170
|
results.append(result)
|
|
154
171
|
|
|
@@ -160,9 +177,10 @@ def _log_summary(results: list[PRMergeResult]) -> None:
|
|
|
160
177
|
if not results:
|
|
161
178
|
return
|
|
162
179
|
max_name = max(len(r.dest_name) for r in results)
|
|
163
|
-
|
|
180
|
+
max_url = max(len(r.pr_url) for r in results)
|
|
181
|
+
header = f"{'Repo':<{max_name}} {'PR':<{max_url}} State Failed Checks"
|
|
164
182
|
logger.info(header)
|
|
165
183
|
logger.info("-" * len(header))
|
|
166
184
|
for r in results:
|
|
167
185
|
failed = ", ".join(c.name for c in r.failed_checks)
|
|
168
|
-
logger.info(f"{r.dest_name:<{max_name}} {r.state:<8} {failed}")
|
|
186
|
+
logger.info(f"{r.dest_name:<{max_name}} {r.pr_url:<{max_url}} {r.state:<8} {failed}")
|
|
@@ -258,8 +258,10 @@ def _sync_destination(
|
|
|
258
258
|
dest_root = resolve_repo_path(dest, src_root, opts.work_dir)
|
|
259
259
|
dest_repo = ensure_repo(dest, dest_root, dry_run=opts.dry_run)
|
|
260
260
|
copy_branch = dest.resolved_copy_branch(config.name)
|
|
261
|
+
_print_dest_header(dest)
|
|
261
262
|
|
|
262
263
|
if _skip_already_synced(dest.name, dest_root, copy_branch, commit_ts, opts, config):
|
|
264
|
+
typer.echo(" (already synced, skipped)", err=True)
|
|
263
265
|
return 0, None
|
|
264
266
|
|
|
265
267
|
if not opts.no_checkout and prompt_utils.prompt_confirm(f"Switch {dest.name} to {copy_branch}?", opts.no_prompt):
|
|
@@ -270,10 +272,10 @@ def _sync_destination(
|
|
|
270
272
|
from_default=opts.checkout_from_default,
|
|
271
273
|
)
|
|
272
274
|
result = _sync_paths(config, dest, src_root, dest_root, opts)
|
|
273
|
-
_print_sync_summary(
|
|
275
|
+
_print_sync_summary(result)
|
|
274
276
|
|
|
275
277
|
if result.total == 0:
|
|
276
|
-
|
|
278
|
+
typer.echo(" No changes", err=True)
|
|
277
279
|
_close_stale_pr(dest_root, copy_branch, opts, config)
|
|
278
280
|
return 0, None
|
|
279
281
|
|
|
@@ -304,14 +306,23 @@ def _sync_destination(
|
|
|
304
306
|
return result.total, pr_ref
|
|
305
307
|
|
|
306
308
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
+
SEPARATOR_WIDTH = 40
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _print_dest_header(dest: Destination) -> None:
|
|
313
|
+
line = "─" * SEPARATOR_WIDTH
|
|
314
|
+
typer.echo(f"\n{line}", err=True)
|
|
315
|
+
typer.echo(f" {dest.name}", err=True)
|
|
316
|
+
typer.echo(line, err=True)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _print_sync_summary(result: SyncResult) -> None:
|
|
309
320
|
if result.content_changes > 0:
|
|
310
321
|
typer.echo(f" [{result.content_changes} files synced]", err=True)
|
|
311
322
|
if result.orphans_deleted > 0:
|
|
312
323
|
typer.echo(f" [-] {result.orphans_deleted} orphans deleted", err=True)
|
|
313
324
|
if result.total > 0:
|
|
314
|
-
typer.echo(f"
|
|
325
|
+
typer.echo(f" {result.total} changes ready.", err=True)
|
|
315
326
|
|
|
316
327
|
|
|
317
328
|
def _sync_paths(
|
|
@@ -202,7 +202,8 @@ def update_pr_body(repo_path: Path, branch: str, body: str) -> bool:
|
|
|
202
202
|
if result.returncode != 0:
|
|
203
203
|
logger.warning(f"Failed to update PR body: {result.stderr}")
|
|
204
204
|
return False
|
|
205
|
-
|
|
205
|
+
pr_url = f"https://github.com/{repo_full}/pull/{pr_number}"
|
|
206
|
+
logger.info(f"Updated PR body: {pr_url}")
|
|
206
207
|
return True
|
|
207
208
|
|
|
208
209
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|