This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/sbp by this push:
new 517b7bd Resolve conflicts during revision creation, and add a
corresponding test
517b7bd is described below
commit 517b7bd83127683a07a9d6f9b86c29d8e6caf6b9
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Feb 11 10:50:53 2026 +0000
Resolve conflicts during revision creation, and add a corresponding test
---
atr/get/test.py | 41 ++++++++++++++++++++++++++++++++++
atr/storage/writers/revision.py | 27 +++++++++++++++++++++++
tests/e2e/merge/__init__.py | 16 ++++++++++++++
tests/e2e/merge/conftest.py | 49 +++++++++++++++++++++++++++++++++++++++++
tests/e2e/merge/helpers.py | 21 ++++++++++++++++++
tests/e2e/merge/test_get.py | 36 ++++++++++++++++++++++++++++++
6 files changed, 190 insertions(+)
diff --git a/atr/get/test.py b/atr/get/test.py
index a76f623..102b710 100644
--- a/atr/get/test.py
+++ b/atr/get/test.py
@@ -15,10 +15,15 @@
# specific language governing permissions and limitations
# under the License.
+import json
+
+import aiofiles
import asfquart.base as base
+import werkzeug.wrappers.response as response
import atr.blueprints.get as get
import atr.config as config
+import atr.db as db
import atr.form as form
import atr.get.root as root
import atr.get.vote as vote
@@ -26,6 +31,7 @@ import atr.htm as htm
import atr.models.session
import atr.models.sql as sql
import atr.shared as shared
+import atr.storage as storage
import atr.template as template
import atr.util as util
import atr.web as web
@@ -68,6 +74,41 @@ async def test_login(session: web.Committer | None) ->
web.WerkzeugResponse:
return await web.redirect(root.index)
[email protected]("/test/merge/<project_name>/<version_name>")
+async def test_merge(session: web.Committer, project_name: str, version_name:
str) -> web.WerkzeugResponse:
+ if not config.get().ALLOW_TESTS:
+ raise base.ASFQuartException("Test routes not enabled", errorcode=404)
+
+ async with storage.write(session) as write_n:
+ wacp_n = await write_n.as_project_committee_participant(project_name)
+ async with wacp_n.release.create_and_manage_revision(
+ project_name, version_name, "Test merge: new revision"
+ ) as creating_n:
+ async with aiofiles.open(creating_n.interim_path / "from_new.txt",
"w") as f:
+ await f.write("new content")
+
+ async with storage.write(session) as write_p:
+ wacp_p = await
write_p.as_project_committee_participant(project_name)
+ async with wacp_p.release.create_and_manage_revision(
+ project_name, version_name, "Test merge: prior revision"
+ ) as creating_p:
+ async with aiofiles.open(creating_p.interim_path /
"from_prior.txt", "w") as f:
+ await f.write("prior content")
+
+ files: list[str] = []
+ async with db.session() as data:
+ release_name = sql.release_name(project_name, version_name)
+ release = await data.release(name=release_name, _project=True).demand(
+ RuntimeError("Release not found after merge test")
+ )
+ release_dir = util.release_directory(release)
+ async for path in util.paths_recursive(release_dir):
+ files.append(str(path))
+
+ result = json.dumps({"files": sorted(files)})
+ return response.Response(result, status=200, mimetype="application/json")
+
+
@get.public("/test/multiple")
async def test_multiple(session: web.Committer | None) -> str:
apple_form = form.render(
diff --git a/atr/storage/writers/revision.py b/atr/storage/writers/revision.py
index e776fc3..5371a0b 100644
--- a/atr/storage/writers/revision.py
+++ b/atr/storage/writers/revision.py
@@ -33,6 +33,7 @@ import atr.attestable as attestable
import atr.db as db
import atr.db.interaction as interaction
import atr.detection as detection
+import atr.merge as merge
import atr.models.sql as sql
import atr.storage as storage
import atr.storage.types as types
@@ -171,6 +172,13 @@ class CommitteeParticipant(FoundationCommitter):
previous_attestable = None
if parent_revision_number is not None:
previous_attestable = await attestable.load(project_name,
version_name, parent_revision_number)
+ base_inodes: dict[str, int] = {}
+ base_hashes: dict[str, str] = {}
+ if old_revision is not None:
+ base_dir = util.release_directory(release)
+ base_inodes = await asyncio.to_thread(util.paths_to_inodes,
base_dir)
+ base_hashes = dict(previous_attestable.paths) if
(previous_attestable is not None) else {}
+ n_inodes = await asyncio.to_thread(util.paths_to_inodes,
temp_dir_path)
except Exception:
await aioshutil.rmtree(temp_dir)
raise
@@ -203,6 +211,25 @@ class CommitteeParticipant(FoundationCommitter):
# Give the caller details about the new revision
creating.new = new_revision
+ # Merge with the prior revision if there was an intervening
change
+ prior_name = new_revision.parent_name
+ if (old_revision is not None) and (prior_name is not None) and
(prior_name != old_revision.name):
+ prior_number = prior_name.split()[-1]
+ prior_dir = util.release_directory_base(release) /
prior_number
+ await merge.merge(
+ base_inodes,
+ base_hashes,
+ prior_dir,
+ project_name,
+ version_name,
+ prior_number,
+ temp_dir_path,
+ n_inodes,
+ path_to_hash,
+ path_to_size,
+ )
+ previous_attestable = await attestable.load(project_name,
version_name, prior_number)
+
# Rename the directory to the new revision number
await data.refresh(release)
new_revision_dir = util.release_directory(release)
diff --git a/tests/e2e/merge/__init__.py b/tests/e2e/merge/__init__.py
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/tests/e2e/merge/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/tests/e2e/merge/conftest.py b/tests/e2e/merge/conftest.py
new file mode 100644
index 0000000..63241d1
--- /dev/null
+++ b/tests/e2e/merge/conftest.py
@@ -0,0 +1,49 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import e2e.helpers as helpers
+import e2e.merge.helpers as merge_helpers
+import pytest
+
+if TYPE_CHECKING:
+ from collections.abc import Generator
+
+ from playwright.sync_api import Browser, BrowserContext
+
+
[email protected](scope="module")
+def merge_context(browser: Browser) -> Generator[BrowserContext]:
+ context = browser.new_context(ignore_https_errors=True)
+ page = context.new_page()
+
+ helpers.log_in(page)
+ helpers.delete_release_if_exists(page, merge_helpers.PROJECT_NAME,
merge_helpers.VERSION_NAME)
+
+ helpers.visit(page, f"/start/{merge_helpers.PROJECT_NAME}")
+ page.locator("input#version_name").fill(merge_helpers.VERSION_NAME)
+ page.get_by_role("button", name="Start new release").click()
+
page.wait_for_url(f"**/compose/{merge_helpers.PROJECT_NAME}/{merge_helpers.VERSION_NAME}")
+
+ page.close()
+
+ yield context
+
+ context.close()
diff --git a/tests/e2e/merge/helpers.py b/tests/e2e/merge/helpers.py
new file mode 100644
index 0000000..13c4af6
--- /dev/null
+++ b/tests/e2e/merge/helpers.py
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from typing import Final
+
+PROJECT_NAME: Final[str] = "test"
+VERSION_NAME: Final[str] = "0.1+e2e-merge"
diff --git a/tests/e2e/merge/test_get.py b/tests/e2e/merge/test_get.py
new file mode 100644
index 0000000..27cd520
--- /dev/null
+++ b/tests/e2e/merge/test_get.py
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import e2e.helpers as helpers
+import e2e.merge.helpers as merge_helpers
+
+if TYPE_CHECKING:
+ from playwright.sync_api import BrowserContext
+
+
+def test_merge_interleaved_revisions(merge_context: BrowserContext) -> None:
+ result = helpers.api_get(
+ merge_context.request,
+
f"/test/merge/{merge_helpers.PROJECT_NAME}/{merge_helpers.VERSION_NAME}",
+ )
+ files = result["files"]
+ assert "from_new.txt" in files
+ assert "from_prior.txt" in files
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]