Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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),
),
]
8 changes: 8 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,14 @@ def set_landed_revision_diffs(self):
revision=revision, landing_job=self
).update(diff_id=revision.diff_id)

def set_landed_commit_ids(self):
"""Assign diff_ids, if available, to each association row."""
# Update association table records with current diff_id values.
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