path-sync 0.7.2__tar.gz → 0.7.4__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.2 → path_sync-0.7.4}/PKG-INFO +31 -2
- {path_sync-0.7.2 → path_sync-0.7.4}/README.md +30 -1
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/__init__.py +1 -1
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/cmd_copy.py +36 -2
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/cmd_dep_update.py +10 -2
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/git_ops.py +31 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/models.py +31 -2
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/models_dep.py +1 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/pyproject.toml +1 -1
- {path_sync-0.7.2 → path_sync-0.7.4}/.gitignore +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/LICENSE +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/__main__.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/__init__.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/auto_merge.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/cmd_boot.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/cmd_options.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/cmd_validate.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/file_utils.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/header.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/log_capture.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/prompt_utils.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/typer_app.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/validation.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/verify.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/_internal/yaml_utils.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/config.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/copy.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/dep_update.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/sections.py +0 -0
- {path_sync-0.7.2 → path_sync-0.7.4}/path_sync/validate_no_changes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: path-sync
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Sync files from a source repo to multiple destination repos
|
|
5
5
|
Author-email: EspenAlbert <espen.albert1@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -43,6 +43,8 @@ Sync files from a source repo to multiple destination repos.
|
|
|
43
43
|
- Files with headers are updated on each sync
|
|
44
44
|
- Remove a header to opt-out (file becomes DEST-owned)
|
|
45
45
|
- Orphaned files (removed from SRC) are deleted in DEST
|
|
46
|
+
- Idempotent: PR body is left unchanged when already synced from an equal or newer source commit
|
|
47
|
+
- Stale PRs auto-close when source and destination are already in sync (zero file changes)
|
|
46
48
|
|
|
47
49
|
## Installation
|
|
48
50
|
|
|
@@ -225,10 +227,24 @@ destinations:
|
|
|
225
227
|
| `paths` | Files/globs to sync (see path options below) |
|
|
226
228
|
| `destinations` | Target repos with sync settings |
|
|
227
229
|
| `header_config` | Comment style per extension (has defaults) |
|
|
228
|
-
| `pr_defaults` | PR title, labels, reviewers, assignees |
|
|
230
|
+
| `pr_defaults` | PR title, body template, labels, reviewers, assignees |
|
|
229
231
|
| `wrap_synced_files` | Wrap synced files in section markers (default: `false`) |
|
|
232
|
+
| `keep_pr_on_no_changes` | Keep stale PR open instead of auto-closing when sync produces zero changes (default: `false`) |
|
|
233
|
+
| `force_resync` | Ignore the "PR already synced from newer commit" check, always run the full sync (default: `false`) |
|
|
230
234
|
| `verify` | Verification steps to run after syncing (see [Verify Steps](#verify-steps-in-copy)) |
|
|
231
235
|
|
|
236
|
+
**`body_template` variables** (available in `pr_defaults.body_template`):
|
|
237
|
+
|
|
238
|
+
| Variable | Description |
|
|
239
|
+
|----------|-------------|
|
|
240
|
+
| `{src_repo_name}` | Source repo name (derived from remote URL) |
|
|
241
|
+
| `{src_repo_url}` | Source repo URL |
|
|
242
|
+
| `{src_sha}` | Full SHA of the source commit |
|
|
243
|
+
| `{src_sha_short}` | Short SHA (first 8 chars) of the source commit |
|
|
244
|
+
| `{src_commit_ts}` | ISO 8601 timestamp of the source commit |
|
|
245
|
+
| `{sync_log}` | Log of synced file operations |
|
|
246
|
+
| `{dest_name}` | Destination name |
|
|
247
|
+
|
|
232
248
|
**Path options**:
|
|
233
249
|
|
|
234
250
|
| Field | Description |
|
|
@@ -301,6 +317,18 @@ Comment style is extension-aware:
|
|
|
301
317
|
|
|
302
318
|
Remove this header to opt-out of future syncs for that file.
|
|
303
319
|
|
|
320
|
+
## PR Body Metadata
|
|
321
|
+
|
|
322
|
+
path-sync embeds a hidden HTML comment in PR bodies to track the source commit:
|
|
323
|
+
|
|
324
|
+
```html
|
|
325
|
+
<!-- path-sync: sha=abc1234 ts=2026-02-12T10:00:00+00:00 -->
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
This is primarily useful when syncing from a feature/PR branch instead of `main`. When a scheduled CI job runs against `main`, the source commit timestamp will be older than the PR branch sync. path-sync detects this and skips the PR body overwrite, preserving the newer sync state.
|
|
329
|
+
|
|
330
|
+
If `copy` or `dep-update` finds zero file changes, any open PR for the sync branch is closed automatically (disable with `keep_pr_on_no_changes: true`). The timestamp check can be bypassed with `force_resync: true` (copy only). Existing PRs without metadata are always overwritten.
|
|
331
|
+
|
|
304
332
|
## GitHub Actions
|
|
305
333
|
|
|
306
334
|
### Source repo workflow
|
|
@@ -440,6 +468,7 @@ pr:
|
|
|
440
468
|
| `updates` | Commands to run (in order) |
|
|
441
469
|
| `verify.on_fail` | Default failure strategy: `skip`, `fail`, `warn` |
|
|
442
470
|
| `verify.steps` | Verification commands with optional commit/on_fail |
|
|
471
|
+
| `keep_pr_on_no_changes` | Keep stale PR open instead of auto-closing when no changes (default: `false`) |
|
|
443
472
|
| `pr.auto_merge` | Enable GitHub auto-merge after PR creation |
|
|
444
473
|
|
|
445
474
|
### CLI Flags
|
|
@@ -25,6 +25,8 @@ Sync files from a source repo to multiple destination repos.
|
|
|
25
25
|
- Files with headers are updated on each sync
|
|
26
26
|
- Remove a header to opt-out (file becomes DEST-owned)
|
|
27
27
|
- Orphaned files (removed from SRC) are deleted in DEST
|
|
28
|
+
- Idempotent: PR body is left unchanged when already synced from an equal or newer source commit
|
|
29
|
+
- Stale PRs auto-close when source and destination are already in sync (zero file changes)
|
|
28
30
|
|
|
29
31
|
## Installation
|
|
30
32
|
|
|
@@ -207,10 +209,24 @@ destinations:
|
|
|
207
209
|
| `paths` | Files/globs to sync (see path options below) |
|
|
208
210
|
| `destinations` | Target repos with sync settings |
|
|
209
211
|
| `header_config` | Comment style per extension (has defaults) |
|
|
210
|
-
| `pr_defaults` | PR title, labels, reviewers, assignees |
|
|
212
|
+
| `pr_defaults` | PR title, body template, labels, reviewers, assignees |
|
|
211
213
|
| `wrap_synced_files` | Wrap synced files in section markers (default: `false`) |
|
|
214
|
+
| `keep_pr_on_no_changes` | Keep stale PR open instead of auto-closing when sync produces zero changes (default: `false`) |
|
|
215
|
+
| `force_resync` | Ignore the "PR already synced from newer commit" check, always run the full sync (default: `false`) |
|
|
212
216
|
| `verify` | Verification steps to run after syncing (see [Verify Steps](#verify-steps-in-copy)) |
|
|
213
217
|
|
|
218
|
+
**`body_template` variables** (available in `pr_defaults.body_template`):
|
|
219
|
+
|
|
220
|
+
| Variable | Description |
|
|
221
|
+
|----------|-------------|
|
|
222
|
+
| `{src_repo_name}` | Source repo name (derived from remote URL) |
|
|
223
|
+
| `{src_repo_url}` | Source repo URL |
|
|
224
|
+
| `{src_sha}` | Full SHA of the source commit |
|
|
225
|
+
| `{src_sha_short}` | Short SHA (first 8 chars) of the source commit |
|
|
226
|
+
| `{src_commit_ts}` | ISO 8601 timestamp of the source commit |
|
|
227
|
+
| `{sync_log}` | Log of synced file operations |
|
|
228
|
+
| `{dest_name}` | Destination name |
|
|
229
|
+
|
|
214
230
|
**Path options**:
|
|
215
231
|
|
|
216
232
|
| Field | Description |
|
|
@@ -283,6 +299,18 @@ Comment style is extension-aware:
|
|
|
283
299
|
|
|
284
300
|
Remove this header to opt-out of future syncs for that file.
|
|
285
301
|
|
|
302
|
+
## PR Body Metadata
|
|
303
|
+
|
|
304
|
+
path-sync embeds a hidden HTML comment in PR bodies to track the source commit:
|
|
305
|
+
|
|
306
|
+
```html
|
|
307
|
+
<!-- path-sync: sha=abc1234 ts=2026-02-12T10:00:00+00:00 -->
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
This is primarily useful when syncing from a feature/PR branch instead of `main`. When a scheduled CI job runs against `main`, the source commit timestamp will be older than the PR branch sync. path-sync detects this and skips the PR body overwrite, preserving the newer sync state.
|
|
311
|
+
|
|
312
|
+
If `copy` or `dep-update` finds zero file changes, any open PR for the sync branch is closed automatically (disable with `keep_pr_on_no_changes: true`). The timestamp check can be bypassed with `force_resync: true` (copy only). Existing PRs without metadata are always overwritten.
|
|
313
|
+
|
|
286
314
|
## GitHub Actions
|
|
287
315
|
|
|
288
316
|
### Source repo workflow
|
|
@@ -422,6 +450,7 @@ pr:
|
|
|
422
450
|
| `updates` | Commands to run (in order) |
|
|
423
451
|
| `verify.on_fail` | Default failure strategy: `skip`, `fail`, `warn` |
|
|
424
452
|
| `verify.steps` | Verification commands with optional commit/on_fail |
|
|
453
|
+
| `keep_pr_on_no_changes` | Keep stale PR open instead of auto-closing when no changes (default: `false`) |
|
|
425
454
|
| `pr.auto_merge` | Enable GitHub auto-merge after PR creation |
|
|
426
455
|
|
|
427
456
|
### CLI Flags
|
|
@@ -20,6 +20,7 @@ from path_sync._internal.models import (
|
|
|
20
20
|
SrcConfig,
|
|
21
21
|
SyncMode,
|
|
22
22
|
find_repo_root,
|
|
23
|
+
pr_already_synced,
|
|
23
24
|
resolve_config_path,
|
|
24
25
|
)
|
|
25
26
|
from path_sync._internal.typer_app import app
|
|
@@ -194,6 +195,7 @@ def copy(
|
|
|
194
195
|
def _run_copy(config: SrcConfig, src_root: Path, dest_filter: str, opts: CopyOptions) -> int:
|
|
195
196
|
src_repo = git_ops.get_repo(src_root)
|
|
196
197
|
current_sha = git_ops.get_current_sha(src_repo)
|
|
198
|
+
commit_ts = git_ops.get_commit_timestamp(src_repo)
|
|
197
199
|
src_repo_url = git_ops.get_remote_url(src_repo, config.git_remote)
|
|
198
200
|
|
|
199
201
|
destinations = config.destinations
|
|
@@ -205,7 +207,9 @@ def _run_copy(config: SrcConfig, src_root: Path, dest_filter: str, opts: CopyOpt
|
|
|
205
207
|
pr_refs: list[PRRef] = []
|
|
206
208
|
for dest in destinations:
|
|
207
209
|
with capture_log(dest.name) as read_log:
|
|
208
|
-
changes, pr_ref = _sync_destination(
|
|
210
|
+
changes, pr_ref = _sync_destination(
|
|
211
|
+
config, dest, src_root, current_sha, commit_ts, src_repo_url, opts, read_log
|
|
212
|
+
)
|
|
209
213
|
total_changes += changes
|
|
210
214
|
if pr_ref:
|
|
211
215
|
pr_refs.append(pr_ref)
|
|
@@ -216,11 +220,33 @@ def _run_copy(config: SrcConfig, src_root: Path, dest_filter: str, opts: CopyOpt
|
|
|
216
220
|
return total_changes
|
|
217
221
|
|
|
218
222
|
|
|
223
|
+
def _close_stale_pr(dest_root: Path, copy_branch: str, opts: CopyOptions, config: SrcConfig) -> None:
|
|
224
|
+
if opts.dry_run or opts.skip_commit or opts.no_pr or config.keep_pr_on_no_changes:
|
|
225
|
+
return
|
|
226
|
+
if git_ops.has_open_pr(dest_root, copy_branch):
|
|
227
|
+
git_ops.close_pr(dest_root, copy_branch, "Closing: source and destination are in sync, no changes needed")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _skip_already_synced(
|
|
231
|
+
dest_name: str, dest_root: Path, copy_branch: str, commit_ts: str, opts: CopyOptions, config: SrcConfig
|
|
232
|
+
) -> bool:
|
|
233
|
+
if opts.skip_commit or opts.dry_run or opts.no_pr or config.force_resync:
|
|
234
|
+
return False
|
|
235
|
+
existing_body = git_ops.get_pr_body(dest_root, copy_branch)
|
|
236
|
+
if metadata := pr_already_synced(existing_body, commit_ts):
|
|
237
|
+
logger.info(
|
|
238
|
+
f"{dest_name}: open PR already synced from {metadata.sha[:8]} ({metadata.ts} >= {commit_ts}), skipping"
|
|
239
|
+
)
|
|
240
|
+
return True
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
|
|
219
244
|
def _sync_destination(
|
|
220
245
|
config: SrcConfig,
|
|
221
246
|
dest: Destination,
|
|
222
247
|
src_root: Path,
|
|
223
248
|
current_sha: str,
|
|
249
|
+
commit_ts: str,
|
|
224
250
|
src_repo_url: str,
|
|
225
251
|
opts: CopyOptions,
|
|
226
252
|
read_log: Callable[[], str],
|
|
@@ -233,6 +259,9 @@ def _sync_destination(
|
|
|
233
259
|
dest_repo = _ensure_dest_repo(dest, dest_root, opts.dry_run)
|
|
234
260
|
copy_branch = dest.resolved_copy_branch(config.name)
|
|
235
261
|
|
|
262
|
+
if _skip_already_synced(dest.name, dest_root, copy_branch, commit_ts, opts, config):
|
|
263
|
+
return 0, None
|
|
264
|
+
|
|
236
265
|
if not opts.no_checkout and prompt_utils.prompt_confirm(f"Switch {dest.name} to {copy_branch}?", opts.no_prompt):
|
|
237
266
|
git_ops.prepare_copy_branch(
|
|
238
267
|
repo=dest_repo,
|
|
@@ -245,6 +274,7 @@ def _sync_destination(
|
|
|
245
274
|
|
|
246
275
|
if result.total == 0:
|
|
247
276
|
logger.info(f"{dest.name}: No changes")
|
|
277
|
+
_close_stale_pr(dest_root, copy_branch, opts, config)
|
|
248
278
|
return 0, None
|
|
249
279
|
|
|
250
280
|
should_skip_commit = opts.skip_commit or opts.dry_run
|
|
@@ -268,7 +298,9 @@ def _sync_destination(
|
|
|
268
298
|
logger.warning(f"{dest.name}: Verification skipped due to failure")
|
|
269
299
|
return result.total, None
|
|
270
300
|
|
|
271
|
-
pr_ref = _push_and_pr(
|
|
301
|
+
pr_ref = _push_and_pr(
|
|
302
|
+
config, dest_repo, dest_root, dest, current_sha, commit_ts, src_repo_url, opts, read_log, verify_result
|
|
303
|
+
)
|
|
272
304
|
return result.total, pr_ref
|
|
273
305
|
|
|
274
306
|
|
|
@@ -552,6 +584,7 @@ def _push_and_pr(
|
|
|
552
584
|
dest_root: Path,
|
|
553
585
|
dest: Destination,
|
|
554
586
|
sha: str,
|
|
587
|
+
commit_ts: str,
|
|
555
588
|
src_repo_url: str,
|
|
556
589
|
opts: CopyOptions,
|
|
557
590
|
read_log: Callable[[], str],
|
|
@@ -585,6 +618,7 @@ def _push_and_pr(
|
|
|
585
618
|
src_sha=sha,
|
|
586
619
|
sync_log=sync_log,
|
|
587
620
|
dest_name=dest.name,
|
|
621
|
+
src_commit_ts=commit_ts,
|
|
588
622
|
)
|
|
589
623
|
|
|
590
624
|
if verify_result.failures:
|
|
@@ -119,8 +119,16 @@ def _update_and_validate(
|
|
|
119
119
|
logger.error(f"{dest.name}: Verification failed, stopping")
|
|
120
120
|
raise typer.Exit(1)
|
|
121
121
|
|
|
122
|
-
if result.status
|
|
123
|
-
|
|
122
|
+
if result.status == Status.NO_CHANGES:
|
|
123
|
+
if (
|
|
124
|
+
not opts.dry_run
|
|
125
|
+
and not config.keep_pr_on_no_changes
|
|
126
|
+
and git_ops.has_open_pr(result.repo_path, config.pr.branch)
|
|
127
|
+
):
|
|
128
|
+
git_ops.close_pr(result.repo_path, config.pr.branch, "Closing: no dependency changes needed")
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
results.append(result)
|
|
124
132
|
|
|
125
133
|
return results
|
|
126
134
|
|
|
@@ -97,6 +97,10 @@ def get_current_sha(repo: Repo) -> str:
|
|
|
97
97
|
return repo.head.commit.hexsha
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def get_commit_timestamp(repo: Repo) -> str:
|
|
101
|
+
return repo.head.commit.committed_datetime.isoformat()
|
|
102
|
+
|
|
103
|
+
|
|
100
104
|
def get_remote_url(repo: Repo, remote_name: str = "origin") -> str:
|
|
101
105
|
try:
|
|
102
106
|
return repo.remote(remote_name).url
|
|
@@ -202,6 +206,33 @@ def update_pr_body(repo_path: Path, branch: str, body: str) -> bool:
|
|
|
202
206
|
return True
|
|
203
207
|
|
|
204
208
|
|
|
209
|
+
def get_pr_body(repo_path: Path, branch: str) -> str | None:
|
|
210
|
+
cmd = ["gh", "pr", "view", branch, "--json", "body,state", "-q", 'select(.state == "OPEN") | .body']
|
|
211
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
212
|
+
if result.returncode != 0:
|
|
213
|
+
return None
|
|
214
|
+
body = result.stdout.strip()
|
|
215
|
+
return body or None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def has_open_pr(repo_path: Path, branch: str) -> bool:
|
|
219
|
+
cmd = ["gh", "pr", "view", branch, "--json", "state", "-q", ".state"]
|
|
220
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
221
|
+
if result.returncode != 0:
|
|
222
|
+
return False
|
|
223
|
+
return result.stdout.strip() == "OPEN"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def close_pr(repo_path: Path, branch: str, comment: str) -> bool:
|
|
227
|
+
cmd = ["gh", "pr", "close", branch, "-c", comment]
|
|
228
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
229
|
+
if result.returncode != 0:
|
|
230
|
+
logger.warning(f"Failed to close PR for {branch}: {result.stderr}")
|
|
231
|
+
return False
|
|
232
|
+
logger.info(f"Closed PR for {branch}")
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
|
|
205
236
|
def create_or_update_pr(
|
|
206
237
|
repo_path: Path,
|
|
207
238
|
branch: str,
|
|
@@ -2,9 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import fnmatch
|
|
4
4
|
import glob as glob_mod
|
|
5
|
+
import re
|
|
5
6
|
from enum import StrEnum
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import ClassVar
|
|
8
|
+
from typing import ClassVar, NamedTuple
|
|
8
9
|
|
|
9
10
|
from pydantic import BaseModel, Field, model_validator
|
|
10
11
|
|
|
@@ -104,6 +105,29 @@ class PathMapping(BaseModel):
|
|
|
104
105
|
|
|
105
106
|
HEADER_TEMPLATE = "path-sync copy -n {config_name}"
|
|
106
107
|
|
|
108
|
+
SYNC_METADATA_PATTERN = re.compile(r"<!-- path-sync: sha=(?P<sha>[0-9a-f]+) ts=(?P<ts>[^\s]+) -->")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SyncMetadata(NamedTuple):
|
|
112
|
+
sha: str
|
|
113
|
+
ts: str
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def parse_sync_metadata(body: str) -> SyncMetadata | None:
|
|
117
|
+
if match := SYNC_METADATA_PATTERN.search(body):
|
|
118
|
+
return SyncMetadata(sha=match["sha"], ts=match["ts"])
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def pr_already_synced(pr_body: str | None, commit_ts: str) -> SyncMetadata | None:
|
|
123
|
+
"""Return metadata if pr_body was synced from a source commit >= commit_ts."""
|
|
124
|
+
if not pr_body:
|
|
125
|
+
return None
|
|
126
|
+
metadata = parse_sync_metadata(pr_body)
|
|
127
|
+
if metadata and metadata.ts >= commit_ts:
|
|
128
|
+
return metadata
|
|
129
|
+
return None
|
|
130
|
+
|
|
107
131
|
|
|
108
132
|
class HeaderConfig(BaseModel):
|
|
109
133
|
comment_prefixes: dict[str, str] = Field(default_factory=dict)
|
|
@@ -111,7 +135,8 @@ class HeaderConfig(BaseModel):
|
|
|
111
135
|
|
|
112
136
|
|
|
113
137
|
DEFAULT_BODY_TEMPLATE = """\
|
|
114
|
-
|
|
138
|
+
<!-- path-sync: sha={src_sha_short} ts={src_commit_ts} -->
|
|
139
|
+
Synced from [{src_repo_name}]({src_repo_url}) @ `{src_sha_short}` ({src_commit_ts})
|
|
115
140
|
|
|
116
141
|
<details>
|
|
117
142
|
<summary>Sync Log</summary>
|
|
@@ -143,6 +168,7 @@ class PRDefaults(PRFieldsBase):
|
|
|
143
168
|
src_sha: str,
|
|
144
169
|
sync_log: str,
|
|
145
170
|
dest_name: str,
|
|
171
|
+
src_commit_ts: str = "",
|
|
146
172
|
) -> str:
|
|
147
173
|
src_repo_name = src_repo_url.rstrip("/").rsplit("/", 1)[-1].removesuffix(".git")
|
|
148
174
|
body = self.body_template.format(
|
|
@@ -152,6 +178,7 @@ class PRDefaults(PRFieldsBase):
|
|
|
152
178
|
src_sha_short=src_sha[:8],
|
|
153
179
|
sync_log=sync_log,
|
|
154
180
|
dest_name=dest_name,
|
|
181
|
+
src_commit_ts=src_commit_ts,
|
|
155
182
|
)
|
|
156
183
|
if self.body_suffix:
|
|
157
184
|
body = f"{body}\n---\n{self.body_suffix}"
|
|
@@ -194,6 +221,8 @@ class SrcConfig(BaseModel):
|
|
|
194
221
|
verify: VerifyConfig | None = None
|
|
195
222
|
wrap_synced_files: bool = False
|
|
196
223
|
auto_merge: AutoMergeConfig | None = None
|
|
224
|
+
keep_pr_on_no_changes: bool = False
|
|
225
|
+
force_resync: bool = False
|
|
197
226
|
|
|
198
227
|
@model_validator(mode="after")
|
|
199
228
|
def _validate_include_groups(self) -> SrcConfig:
|
|
@@ -37,6 +37,7 @@ class DepConfig(BaseModel):
|
|
|
37
37
|
verify: VerifyConfig = Field(default_factory=VerifyConfig)
|
|
38
38
|
pr: PRConfig
|
|
39
39
|
auto_merge: AutoMergeConfig | None = None
|
|
40
|
+
keep_pr_on_no_changes: bool = False
|
|
40
41
|
|
|
41
42
|
def load_destinations(self, repo_root: Path) -> list[Destination]:
|
|
42
43
|
src_config_path = resolve_config_path(repo_root, self.from_config)
|
|
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
|