Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/lando/api/legacy/workers/landing_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def run_job(self, job: LandingJob) -> bool:
except TemporaryFailureException:
return False

job.set_landed_commit_ids()
job.transition_status(JobAction.LAND, commit_id=commit_id)

mots_path = Path(repo.path) / "mots.yaml"
Expand Down Expand Up @@ -241,6 +242,11 @@ def apply_and_push(
raise PermanentFailureException(message) from exc
else:
new_commit = scm.describe_commit()

# Record the commit ID on the revision object.
revision.commit_id = new_commit.hash
revision.save()

logger.debug(f"Created new commit {new_commit}")

# Get the changeset titles for the stack.
Expand Down
99 changes: 99 additions & 0 deletions src/lando/api/tests/test_landings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
LandingJob,
Repo,
Revision,
RevisionLandingJob,
)
from lando.main.scm import SCM_TYPE_GIT, SCM_TYPE_HG
from lando.main.scm.exceptions import SCMInternalServerError
Expand Down Expand Up @@ -381,6 +382,104 @@ def test_integrated_execute_job(
assert new_push_count == 1, "Incorrect number of additional pushes in the PushLog"


@pytest.mark.parametrize(
"repo_type",
[
SCM_TYPE_GIT,
SCM_TYPE_HG,
],
)
@pytest.mark.django_db
def test_revisionlandingjob_commit_ids_updated_on_success(
repo_mc,
treestatusdouble,
mock_phab_trigger_repo_update_apply_async,
create_patch_revision,
make_landing_job,
get_landing_worker,
repo_type: str,
):
"""Ensure landed commit SHAs are copied onto RevisionLandingJob rows."""
repo = repo_mc(repo_type)
treestatusdouble.open_tree(repo.name)

revisions = [
create_patch_revision(1, patch=None),
create_patch_revision(2, patch=None),
]
job_params = {
"status": JobStatus.IN_PROGRESS,
"requester_email": "test@example.com",
"target_repo": repo,
"attempts": 1,
}
job = make_landing_job(revisions=revisions, **job_params)

worker = get_landing_worker(repo_type)
assert worker.run_job(job)
assert job.status == JobStatus.LANDED

revision_jobs = list(
RevisionLandingJob.objects.filter(landing_job=job).order_by("index")
)
assert len(revision_jobs) == len(revisions)

ordered_revisions = list(job.revisions)
for revision, revision_job in zip(ordered_revisions, revision_jobs, strict=False):
assert revision.commit_id, "`commit_id` should be set on `Revision` object."
assert (
revision_job.commit_id
), "`commit_id` should be set on `RevisionLandingJob` object."


@pytest.mark.parametrize(
"repo_type",
[
SCM_TYPE_GIT,
SCM_TYPE_HG,
],
)
@pytest.mark.django_db
def test_revisionlandingjob_commit_ids_unset_without_landing(
repo_mc,
treestatusdouble,
mock_phab_trigger_repo_update_apply_async,
create_patch_revision,
make_landing_job,
get_landing_worker,
repo_type: str,
):
"""Ensure `commit_id` is not tracked for incomplete job."""
repo = repo_mc(repo_type)
treestatusdouble.open_tree(repo.name)
scm = repo.scm

job_params = {
"status": JobStatus.IN_PROGRESS,
"requester_email": "test@example.com",
"target_repo": repo,
"attempts": 1,
}
job = make_landing_job(revisions=[create_patch_revision(1)], **job_params)

scm.push = mock.MagicMock(side_effect=SCMInternalServerError("push failed", "500"))

worker = get_landing_worker(repo_type)
assert not worker.run_job(job)
assert job.status == JobStatus.DEFERRED

revision_jobs = list(
RevisionLandingJob.objects.filter(landing_job=job).order_by("index")
)
assert len(revision_jobs) == 1

revision = job.revisions.first()
assert revision.commit_id, "`commit_id` should still be set on `Revision` object."
assert (
revision_jobs[0].commit_id is None
), "`commit_id` should not be set for un-landed job."


@pytest.mark.parametrize(
"repo_type",
[
Expand Down
18 changes: 18 additions & 0 deletions src/lando/main/migrations/0032_revisionlandingjob_commit_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2025-10-23 17:57

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("main", "0031_alter_landingjob_requester_email"),
]

operations = [
migrations.AddField(
model_name="revisionlandingjob",
name="commit_id",
field=models.CharField(blank=True, max_length=40, null=True),
),
]
7 changes: 7 additions & 0 deletions src/lando/main/models/landing_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ def set_landed_revision_diffs(self):
revision=revision, landing_job=self
).update(diff_id=revision.diff_id)

def set_landed_commit_ids(self):
"""Assign `commit_id`, if available, to each association row."""
for revision in self.unsorted_revisions.all():
RevisionLandingJob.objects.filter(
revision=revision, landing_job=self
).update(commit_id=revision.commit_id)

def set_landed_reviewers(self, path: Path):
"""Set approving peers and owners at time of landing."""
directory = Directory(FileConfig(path))
Expand Down
3 changes: 3 additions & 0 deletions src/lando/main/models/revision.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class RevisionLandingJob(BaseModel):
# See also: LandingJob.set_landed_revision_diffs, called by transplants.post.
diff_id = models.IntegerField(null=True, blank=True)

# The commit ID generated by the landing worker, before pushing to remote repo.
commit_id = models.CharField(max_length=40, null=True, blank=True)


class Revision(BaseModel):
"""
Expand Down