repoactive 0.2.1__tar.gz → 0.2.3.dev1__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.
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/PKG-INFO +53 -2
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/README.md +52 -1
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/pyproject.toml +1 -1
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/config.py +58 -2
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/jj.py +7 -3
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/runner.py +43 -13
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/.gitignore +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/LICENSE +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/__init__.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/cli.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/constants.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/graph.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/jobtree.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/lock.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/platforms/__init__.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/platforms/base.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/platforms/github.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/platforms/gitlab.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/progress.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/settings.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/ui.py +0 -0
- {repoactive-0.2.1 → repoactive-0.2.3.dev1}/src/repoactive/updates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repoactive
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3.dev1
|
|
4
4
|
Summary: Script-driven code changes with automated merge requests
|
|
5
5
|
License-Expression: GPL-3.0-or-later
|
|
6
6
|
License-File: LICENSE
|
|
@@ -50,6 +50,7 @@ and (with `--mode publish`) the full MR lifecycle.
|
|
|
50
50
|
- [Configuration](#configuration)
|
|
51
51
|
- [Selecting jobs with tags](#selecting-jobs-with-tags)
|
|
52
52
|
- [Disabling jobs](#disabling-jobs)
|
|
53
|
+
- [Gating jobs with `run_only_if_changed`](#gating-jobs-with-run_only_if_changed)
|
|
53
54
|
- [Throttling jobs with `cooldown_period`](#throttling-jobs-with-cooldown_period)
|
|
54
55
|
- [Limiting job runtime with `timeout`](#limiting-job-runtime-with-timeout)
|
|
55
56
|
- [Generating jobs dynamically](#generating-jobs-dynamically)
|
|
@@ -145,7 +146,13 @@ need to write is the script that produces the change.
|
|
|
145
146
|
2. It runs the job's script against the working tree.
|
|
146
147
|
3. If the script produced a diff, it records the change. With `--mode push`
|
|
147
148
|
or `--mode publish`, it pushes the branch; with `--mode publish`, it also
|
|
148
|
-
creates or updates the merge request.
|
|
149
|
+
creates or updates the merge request. On a re-run, if the diff matches
|
|
150
|
+
what is already on the branch, the commit and branch are left untouched —
|
|
151
|
+
avoiding an unnecessary push that would re-trigger CI pipelines. The
|
|
152
|
+
commit is updated (and the branch pushed) only when the diff itself
|
|
153
|
+
changes or when `title`/`commit_title_prefix` changed. Command output
|
|
154
|
+
alone changing does not update the commit. The MR title and description
|
|
155
|
+
are always brought up to date, including any changed command output.
|
|
149
156
|
4. If the script produced no diff, the empty commit is discarded and no MR
|
|
150
157
|
is opened. A branch left over from an earlier run that did produce a diff
|
|
151
158
|
is now stale, so it is deleted; with `--mode push` or `--mode publish`,
|
|
@@ -507,6 +514,11 @@ disabled = false
|
|
|
507
514
|
# Optional: override branch_prefix/mr_title_prefix/commit_title_prefix from
|
|
508
515
|
# job-defaults for this job only.
|
|
509
516
|
mr_title_prefix = "[api] "
|
|
517
|
+
# Optional: only run this job if at least one of the named jobs produced a
|
|
518
|
+
# diff in the current run. If none of them changed anything, this job is
|
|
519
|
+
# skipped (with a no-op result so dependents still run). Names need not be
|
|
520
|
+
# in depends_on; any job that ran before this one can be listed.
|
|
521
|
+
# run_only_if_changed = ["other-job"]
|
|
510
522
|
# Optional: minimum time between landed changes for this job. If a commit
|
|
511
523
|
# from this job landed on the base branch within this window, the job is
|
|
512
524
|
# skipped. Format: <number><unit>, unit one of s, m, h, d, w (e.g. "7d").
|
|
@@ -751,6 +763,45 @@ The lock is an advisory `flock` on `.jj/repoactive.lock`; the OS releases it
|
|
|
751
763
|
automatically if a run is killed, so a crashed run never leaves the
|
|
752
764
|
repository locked.
|
|
753
765
|
|
|
766
|
+
## Gating jobs with `run_only_if_changed`
|
|
767
|
+
|
|
768
|
+
`run_only_if_changed` lets a job declare that it should only run when
|
|
769
|
+
specific upstream jobs actually produced a diff. If none of the named jobs
|
|
770
|
+
changed anything in the current run, the job is skipped.
|
|
771
|
+
|
|
772
|
+
```toml
|
|
773
|
+
[job.prek-autoupdate]
|
|
774
|
+
command = "./scripts/prek.sh autoupdate"
|
|
775
|
+
title = "ci: update pre-commit hooks"
|
|
776
|
+
tags = ["weekly"]
|
|
777
|
+
|
|
778
|
+
[job.prek-run-all]
|
|
779
|
+
command = "sh -c './scripts/prek.sh run -a; true'"
|
|
780
|
+
title = "ci: apply pre-commit fixes"
|
|
781
|
+
depends_on = ["prek-autoupdate"]
|
|
782
|
+
run_only_if_changed = ["prek-autoupdate"]
|
|
783
|
+
tags = ["weekly"]
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Here `prek-run-all` is only useful when `prek-autoupdate` changed something:
|
|
787
|
+
if `prek-autoupdate` found no hooks to update, running `prek-run-all` would
|
|
788
|
+
produce an empty diff anyway. With `run_only_if_changed`, the skip is
|
|
789
|
+
explicit and immediate — `prek-run-all` never even starts.
|
|
790
|
+
|
|
791
|
+
Key behaviour:
|
|
792
|
+
|
|
793
|
+
- **At least one must have changed.** The job runs if any named job produced
|
|
794
|
+
a diff; it is skipped only when _all_ of them were empty.
|
|
795
|
+
- **Missing results count as no diff.** If a named job failed or was itself
|
|
796
|
+
skipped, it is treated as having produced no diff.
|
|
797
|
+
- **Dependents are unaffected.** A skipped job records a no-op result (like
|
|
798
|
+
a job on cooldown), so any job that `depends_on` it still runs on the base
|
|
799
|
+
branch — the skip does not cascade.
|
|
800
|
+
- **No ordering constraint.** Names in `run_only_if_changed` do not have to
|
|
801
|
+
appear in `depends_on`. Any job that runs before this one in topological
|
|
802
|
+
order can be listed; in practice most usages name a direct dependency, as
|
|
803
|
+
in the example above.
|
|
804
|
+
|
|
754
805
|
## Throttling jobs with `cooldown_period`
|
|
755
806
|
|
|
756
807
|
Every commit `repoactive` creates carries a `Repoactive-Job: <name>` trailer
|
|
@@ -23,6 +23,7 @@ and (with `--mode publish`) the full MR lifecycle.
|
|
|
23
23
|
- [Configuration](#configuration)
|
|
24
24
|
- [Selecting jobs with tags](#selecting-jobs-with-tags)
|
|
25
25
|
- [Disabling jobs](#disabling-jobs)
|
|
26
|
+
- [Gating jobs with `run_only_if_changed`](#gating-jobs-with-run_only_if_changed)
|
|
26
27
|
- [Throttling jobs with `cooldown_period`](#throttling-jobs-with-cooldown_period)
|
|
27
28
|
- [Limiting job runtime with `timeout`](#limiting-job-runtime-with-timeout)
|
|
28
29
|
- [Generating jobs dynamically](#generating-jobs-dynamically)
|
|
@@ -118,7 +119,13 @@ need to write is the script that produces the change.
|
|
|
118
119
|
2. It runs the job's script against the working tree.
|
|
119
120
|
3. If the script produced a diff, it records the change. With `--mode push`
|
|
120
121
|
or `--mode publish`, it pushes the branch; with `--mode publish`, it also
|
|
121
|
-
creates or updates the merge request.
|
|
122
|
+
creates or updates the merge request. On a re-run, if the diff matches
|
|
123
|
+
what is already on the branch, the commit and branch are left untouched —
|
|
124
|
+
avoiding an unnecessary push that would re-trigger CI pipelines. The
|
|
125
|
+
commit is updated (and the branch pushed) only when the diff itself
|
|
126
|
+
changes or when `title`/`commit_title_prefix` changed. Command output
|
|
127
|
+
alone changing does not update the commit. The MR title and description
|
|
128
|
+
are always brought up to date, including any changed command output.
|
|
122
129
|
4. If the script produced no diff, the empty commit is discarded and no MR
|
|
123
130
|
is opened. A branch left over from an earlier run that did produce a diff
|
|
124
131
|
is now stale, so it is deleted; with `--mode push` or `--mode publish`,
|
|
@@ -480,6 +487,11 @@ disabled = false
|
|
|
480
487
|
# Optional: override branch_prefix/mr_title_prefix/commit_title_prefix from
|
|
481
488
|
# job-defaults for this job only.
|
|
482
489
|
mr_title_prefix = "[api] "
|
|
490
|
+
# Optional: only run this job if at least one of the named jobs produced a
|
|
491
|
+
# diff in the current run. If none of them changed anything, this job is
|
|
492
|
+
# skipped (with a no-op result so dependents still run). Names need not be
|
|
493
|
+
# in depends_on; any job that ran before this one can be listed.
|
|
494
|
+
# run_only_if_changed = ["other-job"]
|
|
483
495
|
# Optional: minimum time between landed changes for this job. If a commit
|
|
484
496
|
# from this job landed on the base branch within this window, the job is
|
|
485
497
|
# skipped. Format: <number><unit>, unit one of s, m, h, d, w (e.g. "7d").
|
|
@@ -724,6 +736,45 @@ The lock is an advisory `flock` on `.jj/repoactive.lock`; the OS releases it
|
|
|
724
736
|
automatically if a run is killed, so a crashed run never leaves the
|
|
725
737
|
repository locked.
|
|
726
738
|
|
|
739
|
+
## Gating jobs with `run_only_if_changed`
|
|
740
|
+
|
|
741
|
+
`run_only_if_changed` lets a job declare that it should only run when
|
|
742
|
+
specific upstream jobs actually produced a diff. If none of the named jobs
|
|
743
|
+
changed anything in the current run, the job is skipped.
|
|
744
|
+
|
|
745
|
+
```toml
|
|
746
|
+
[job.prek-autoupdate]
|
|
747
|
+
command = "./scripts/prek.sh autoupdate"
|
|
748
|
+
title = "ci: update pre-commit hooks"
|
|
749
|
+
tags = ["weekly"]
|
|
750
|
+
|
|
751
|
+
[job.prek-run-all]
|
|
752
|
+
command = "sh -c './scripts/prek.sh run -a; true'"
|
|
753
|
+
title = "ci: apply pre-commit fixes"
|
|
754
|
+
depends_on = ["prek-autoupdate"]
|
|
755
|
+
run_only_if_changed = ["prek-autoupdate"]
|
|
756
|
+
tags = ["weekly"]
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
Here `prek-run-all` is only useful when `prek-autoupdate` changed something:
|
|
760
|
+
if `prek-autoupdate` found no hooks to update, running `prek-run-all` would
|
|
761
|
+
produce an empty diff anyway. With `run_only_if_changed`, the skip is
|
|
762
|
+
explicit and immediate — `prek-run-all` never even starts.
|
|
763
|
+
|
|
764
|
+
Key behaviour:
|
|
765
|
+
|
|
766
|
+
- **At least one must have changed.** The job runs if any named job produced
|
|
767
|
+
a diff; it is skipped only when _all_ of them were empty.
|
|
768
|
+
- **Missing results count as no diff.** If a named job failed or was itself
|
|
769
|
+
skipped, it is treated as having produced no diff.
|
|
770
|
+
- **Dependents are unaffected.** A skipped job records a no-op result (like
|
|
771
|
+
a job on cooldown), so any job that `depends_on` it still runs on the base
|
|
772
|
+
branch — the skip does not cascade.
|
|
773
|
+
- **No ordering constraint.** Names in `run_only_if_changed` do not have to
|
|
774
|
+
appear in `depends_on`. Any job that runs before this one in topological
|
|
775
|
+
order can be listed; in practice most usages name a direct dependency, as
|
|
776
|
+
in the example above.
|
|
777
|
+
|
|
727
778
|
## Throttling jobs with `cooldown_period`
|
|
728
779
|
|
|
729
780
|
Every commit `repoactive` creates carries a `Repoactive-Job: <name>` trailer
|
|
@@ -20,6 +20,7 @@ from pydantic import (
|
|
|
20
20
|
ConfigDict,
|
|
21
21
|
Field,
|
|
22
22
|
ValidationError,
|
|
23
|
+
ValidationInfo,
|
|
23
24
|
field_validator,
|
|
24
25
|
model_validator,
|
|
25
26
|
)
|
|
@@ -78,6 +79,13 @@ class InvalidTagError(ValueError):
|
|
|
78
79
|
super().__init__(f"invalid tag {tag!r}: only letters, digits, '-', and '_' are allowed")
|
|
79
80
|
|
|
80
81
|
|
|
82
|
+
class NewlineInTitleError(ValueError):
|
|
83
|
+
"""Raised when a title or prefix field contains a newline character."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, field: str, value: str) -> None:
|
|
86
|
+
super().__init__(f"{field} must not contain newline characters, got {value!r}")
|
|
87
|
+
|
|
88
|
+
|
|
81
89
|
class DisabledAndTagsError(ValueError):
|
|
82
90
|
"""Raised when a job sets both 'disabled' and 'tags'."""
|
|
83
91
|
|
|
@@ -95,6 +103,15 @@ class UnknownDependencyError(ValueError):
|
|
|
95
103
|
super().__init__(f"job '{name}' depends_on unknown jobs: {unknown}")
|
|
96
104
|
|
|
97
105
|
|
|
106
|
+
class UnknownRunOnlyIfChangedError(ValueError):
|
|
107
|
+
"""Raised when run_only_if_changed references a job that does not exist."""
|
|
108
|
+
|
|
109
|
+
def __init__(self, name: str, unknown: list[str]) -> None:
|
|
110
|
+
super().__init__(
|
|
111
|
+
f"job '{name}' run_only_if_changed references unknown job(s): {', '.join(unknown)}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
98
115
|
class JobNameInBodyError(ValueError):
|
|
99
116
|
"""Raised when a [job.<name>] table also sets a 'name' field.
|
|
100
117
|
|
|
@@ -253,6 +270,14 @@ class JobDefaults(BaseModel):
|
|
|
253
270
|
cooldown_period: _Duration | None = None
|
|
254
271
|
timeout: _Duration | None = "2m"
|
|
255
272
|
|
|
273
|
+
@field_validator("mr_title_prefix", "commit_title_prefix")
|
|
274
|
+
@classmethod
|
|
275
|
+
def _check_no_newline(cls, value: str, info: ValidationInfo) -> str:
|
|
276
|
+
if "\n" in value:
|
|
277
|
+
assert info.field_name is not None
|
|
278
|
+
raise NewlineInTitleError(info.field_name, value)
|
|
279
|
+
return value
|
|
280
|
+
|
|
256
281
|
|
|
257
282
|
# Fields Job.resolve fills in from JobDefaults when the job does not set them
|
|
258
283
|
# itself. labels is absent on purpose: it is merged, not replaced (see resolve).
|
|
@@ -279,6 +304,7 @@ class Job(BaseModel):
|
|
|
279
304
|
disabled: bool = False
|
|
280
305
|
tags: list[str] = Field(default_factory=list)
|
|
281
306
|
depends_on: list[str] = Field(default_factory=list)
|
|
307
|
+
run_only_if_changed: list[str] = Field(default_factory=list)
|
|
282
308
|
output_in_commit: bool = True
|
|
283
309
|
# When true, the command does not produce a diff to commit; instead it writes
|
|
284
310
|
# one or more *.toml job fragments into the directory named by the
|
|
@@ -305,6 +331,14 @@ class Job(BaseModel):
|
|
|
305
331
|
raise InvalidJobNameError(value)
|
|
306
332
|
return value
|
|
307
333
|
|
|
334
|
+
@field_validator("title", "commit_title_prefix")
|
|
335
|
+
@classmethod
|
|
336
|
+
def _check_no_newline(cls, value: str, info: ValidationInfo) -> str:
|
|
337
|
+
if "\n" in value:
|
|
338
|
+
assert info.field_name is not None
|
|
339
|
+
raise NewlineInTitleError(info.field_name, value)
|
|
340
|
+
return value
|
|
341
|
+
|
|
308
342
|
@field_validator("tags")
|
|
309
343
|
@classmethod
|
|
310
344
|
def _check_tags(cls, value: list[str]) -> list[str]:
|
|
@@ -443,6 +477,15 @@ class Config(BaseModel):
|
|
|
443
477
|
detect_dependency_cycle(self.jobs)
|
|
444
478
|
return self
|
|
445
479
|
|
|
480
|
+
@model_validator(mode="after")
|
|
481
|
+
def validate_run_only_if_changed(self) -> Config:
|
|
482
|
+
names = {j.name for j in self.jobs}
|
|
483
|
+
for job in self.jobs:
|
|
484
|
+
unknown = sorted(set(job.run_only_if_changed) - names)
|
|
485
|
+
if unknown:
|
|
486
|
+
raise UnknownRunOnlyIfChangedError(job.name, unknown)
|
|
487
|
+
return self
|
|
488
|
+
|
|
446
489
|
def _resolved_jobs(self) -> list[Job]:
|
|
447
490
|
return [job.resolve(self.job_defaults) for job in self.jobs]
|
|
448
491
|
|
|
@@ -521,8 +564,21 @@ def _merge_named_tables(*, base: dict, override: dict) -> dict:
|
|
|
521
564
|
|
|
522
565
|
|
|
523
566
|
def merge_jobs(*, base: dict, override: dict) -> dict:
|
|
524
|
-
"""Merge two job tables keyed by name (see ``_merge_named_tables``).
|
|
525
|
-
|
|
567
|
+
"""Merge two job tables keyed by name (see ``_merge_named_tables``).
|
|
568
|
+
|
|
569
|
+
When the override sets ``disabled = true``, any ``tags`` carried over from
|
|
570
|
+
the base are removed, and when the override sets ``tags``, any ``disabled``
|
|
571
|
+
carried over from the base is removed. This keeps the mutual-exclusion
|
|
572
|
+
invariant intact across multi-source merges.
|
|
573
|
+
"""
|
|
574
|
+
result = _merge_named_tables(base=base, override=override)
|
|
575
|
+
for name, override_body in override.items():
|
|
576
|
+
merged_body = result[name]
|
|
577
|
+
if override_body.get("disabled") and "tags" not in override_body:
|
|
578
|
+
merged_body.pop("tags", None)
|
|
579
|
+
elif override_body.get("tags") and "disabled" not in override_body:
|
|
580
|
+
merged_body.pop("disabled", None)
|
|
581
|
+
return result
|
|
526
582
|
|
|
527
583
|
|
|
528
584
|
def merge_platforms(*, base: dict, override: dict) -> dict:
|
|
@@ -273,9 +273,9 @@ class JJ:
|
|
|
273
273
|
"""Abandon a specific revision (not the working copy)."""
|
|
274
274
|
self._run("abandon", revision)
|
|
275
275
|
|
|
276
|
-
def
|
|
277
|
-
"""Return
|
|
278
|
-
return self._run("diff", "
|
|
276
|
+
def same_content(self, rev1: str, rev2: str) -> bool:
|
|
277
|
+
"""Return True if ``rev1`` and ``rev2`` have identical tree contents."""
|
|
278
|
+
return not self._run("diff", "--git", "--from", rev1, "--to", rev2).strip()
|
|
279
279
|
|
|
280
280
|
def bookmark_change_id(self, name: str) -> str | None:
|
|
281
281
|
"""Return the change-id of the local bookmark ``name``, or None if absent."""
|
|
@@ -290,6 +290,10 @@ class JJ:
|
|
|
290
290
|
"""Set the commit message of a specific revision without touching ``@``."""
|
|
291
291
|
self._run("describe", "-r", revision, "--message", message)
|
|
292
292
|
|
|
293
|
+
def get_description(self, revision: str) -> str:
|
|
294
|
+
"""Return the commit message of a specific revision."""
|
|
295
|
+
return self._run("log", "--no-graph", "-r", revision, "-T", "description")
|
|
296
|
+
|
|
293
297
|
def children_job_names(self, bookmarks: list[str]) -> set[str]:
|
|
294
298
|
"""Job names from unmerged commits that are direct children of any of ``bookmarks``.
|
|
295
299
|
|
|
@@ -26,6 +26,7 @@ from repoactive.config import (
|
|
|
26
26
|
jobs_table,
|
|
27
27
|
merge_jobs,
|
|
28
28
|
)
|
|
29
|
+
from repoactive.constants import JOB_TRAILER_KEY
|
|
29
30
|
from repoactive.graph import CircularDependencyError, detect_dependency_cycle, topological_sort
|
|
30
31
|
from repoactive.jj import JJ, workspace_name
|
|
31
32
|
from repoactive.jobtree import format_job_forest, print_job_table
|
|
@@ -374,6 +375,19 @@ def _boxquote(msg: str, title: str = "") -> str:
|
|
|
374
375
|
return f"{top}\n{body}\n`----"
|
|
375
376
|
|
|
376
377
|
|
|
378
|
+
def _strip_boxquote_and_trailers(message: str) -> str:
|
|
379
|
+
"""Strip the boxquote section and trailers paragraph from a commit message.
|
|
380
|
+
|
|
381
|
+
Returns only the title line and description field.
|
|
382
|
+
Note: inaccurate when the description itself contains a ,---- boxquote (ignored edge case).
|
|
383
|
+
"""
|
|
384
|
+
if "\n\n,----" in message:
|
|
385
|
+
return message[: message.index("\n\n,----")]
|
|
386
|
+
if f"\n\n{JOB_TRAILER_KEY}" in message:
|
|
387
|
+
return message[: message.index(f"\n\n{JOB_TRAILER_KEY}")]
|
|
388
|
+
return message
|
|
389
|
+
|
|
390
|
+
|
|
377
391
|
def _build_commit_message(job: Job, command_result: CommandResult) -> str:
|
|
378
392
|
"""Build the commit message recorded for a job's change.
|
|
379
393
|
|
|
@@ -788,6 +802,24 @@ def _dispatch_job( # noqa: PLR0913
|
|
|
788
802
|
blocked.add(job.name)
|
|
789
803
|
return []
|
|
790
804
|
|
|
805
|
+
if job.run_only_if_changed:
|
|
806
|
+
any_changed = any(
|
|
807
|
+
r.produced_diff
|
|
808
|
+
for d in job.run_only_if_changed
|
|
809
|
+
if (r := summary.results.get(d)) is not None
|
|
810
|
+
)
|
|
811
|
+
if not any_changed:
|
|
812
|
+
resolved_job = job.resolve(config.job_defaults)
|
|
813
|
+
parents = _compute_parents(resolved_job, summary.results)
|
|
814
|
+
print(
|
|
815
|
+
f"==> [{job.name}] skipped"
|
|
816
|
+
f" (run_only_if_changed: none of {job.run_only_if_changed} produced changes)"
|
|
817
|
+
)
|
|
818
|
+
summary.results[job.name] = JobResult(
|
|
819
|
+
job=resolved_job, effective_revsets=parents, produced_diff=False
|
|
820
|
+
)
|
|
821
|
+
return []
|
|
822
|
+
|
|
791
823
|
resolved_job = job.resolve(config.job_defaults)
|
|
792
824
|
parents = _compute_parents(resolved_job, summary.results)
|
|
793
825
|
logger.debug("[%s] computed parents: %s", job.name, parents)
|
|
@@ -921,12 +953,6 @@ def _absorb_results(
|
|
|
921
953
|
with contextlib.ExitStack() as stack:
|
|
922
954
|
abs_ws: JJ | None = None
|
|
923
955
|
|
|
924
|
-
def _get_abs_ws() -> JJ:
|
|
925
|
-
nonlocal abs_ws
|
|
926
|
-
if abs_ws is None:
|
|
927
|
-
abs_ws = stack.enter_context(repo.temp_workspace("repoactive-absorb"))
|
|
928
|
-
return abs_ws
|
|
929
|
-
|
|
930
956
|
# Maps each phase-1 new_change_id to the canonical change-id post-absorb so
|
|
931
957
|
# dependent jobs are rebased onto the right commit, not the abandoned fresh one.
|
|
932
958
|
absorbed: dict[str, str] = {}
|
|
@@ -967,19 +993,23 @@ def _absorb_results(
|
|
|
967
993
|
if result.old_change_id:
|
|
968
994
|
old_cid = result.old_change_id
|
|
969
995
|
repo.rebase_revision(old_cid, *canonical_parents)
|
|
970
|
-
|
|
971
|
-
new_diff = repo.diff_content(new_cid)
|
|
996
|
+
content_unchanged = repo.same_content(old_cid, new_cid)
|
|
972
997
|
logger.debug(
|
|
973
998
|
"absorb: [%s] rebased %s onto %s, content %s",
|
|
974
999
|
job.name,
|
|
975
1000
|
old_cid,
|
|
976
1001
|
canonical_parents,
|
|
977
|
-
"unchanged" if
|
|
1002
|
+
"unchanged" if content_unchanged else "differs, restoring",
|
|
978
1003
|
)
|
|
979
|
-
if
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1004
|
+
if not content_unchanged:
|
|
1005
|
+
if abs_ws is None:
|
|
1006
|
+
abs_ws = stack.enter_context(repo.temp_workspace("repoactive-absorb"))
|
|
1007
|
+
abs_ws.edit(old_cid)
|
|
1008
|
+
abs_ws.restore(new_cid)
|
|
1009
|
+
repo.describe_revision(old_cid, message)
|
|
1010
|
+
elif _strip_boxquote_and_trailers(
|
|
1011
|
+
repo.get_description(old_cid)
|
|
1012
|
+
) != _strip_boxquote_and_trailers(message):
|
|
983
1013
|
repo.describe_revision(old_cid, message)
|
|
984
1014
|
repo.abandon_revision(new_cid)
|
|
985
1015
|
# No bookmark_set needed: the bookmark follows old_cid through
|
|
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
|