From 0359063816ada6d31f5b75bfd85d673907452aef Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 28 May 2024 08:15:10 +0200 Subject: [PATCH 01/13] Fix element serialization for collections that aren't populated yet Fixes https://github.com/galaxyproject/galaxy/pull/17818#issuecomment-2134060961: ``` AttributeError 'NoneType' object has no attribute 'dataset' ``` --- lib/galaxy/webapps/galaxy/services/dataset_collections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/services/dataset_collections.py b/lib/galaxy/webapps/galaxy/services/dataset_collections.py index 40d32be49e7e..52b3a37fa7f3 100644 --- a/lib/galaxy/webapps/galaxy/services/dataset_collections.py +++ b/lib/galaxy/webapps/galaxy/services/dataset_collections.py @@ -273,7 +273,7 @@ def serialize_element(dsc_element) -> DCESummary: hdca_id=self.encode_id(hdca.id), parent_id=self.encode_id(result["object"]["id"]), ) - else: + elif result["element_type"] == DCEType.hda: result["object"]["accessible"] = self.hda_manager.is_accessible(dsc_element.element_object, trans.user) return result From fa73f2e9f41aaf042db52502322686d73caddca1 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 28 May 2024 13:31:19 +0200 Subject: [PATCH 02/13] Fix conditions for userOwnsHistory and isRegisteredUser --- client/src/api/index.test.ts | 113 +++++++++++++++++++++++++++++++++++ client/src/api/index.ts | 24 ++++++-- 2 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 client/src/api/index.test.ts diff --git a/client/src/api/index.test.ts b/client/src/api/index.test.ts new file mode 100644 index 000000000000..42b666b2f03f --- /dev/null +++ b/client/src/api/index.test.ts @@ -0,0 +1,113 @@ +import { + type AnonymousUser, + type AnyHistory, + type HistorySummary, + type HistorySummaryExtended, + isRegisteredUser, + type User, + userOwnsHistory, +} from "."; + +const REGISTERED_USER_ID = "fake-user-id"; +const ANOTHER_USER_ID = "another-fake-user-id"; +const ANONYMOUS_USER_ID = null; + +const REGISTERED_USER: User = { + id: REGISTERED_USER_ID, + email: "test@mail.test", + tags_used: [], + isAnonymous: false, + total_disk_usage: 0, +}; + +const ANONYMOUS_USER: AnonymousUser = { + isAnonymous: true, +}; + +const SESSIONLESS_USER = null; + +function createFakeHistory(historyId: string = "fake-id", user_id?: string | null): T { + const history: AnyHistory = { + id: historyId, + name: "test", + model_class: "History", + deleted: false, + archived: false, + purged: false, + published: false, + annotation: null, + update_time: "2021-09-01T00:00:00.000Z", + tags: [], + url: `/history/${historyId}`, + contents_active: { active: 0, deleted: 0, hidden: 0 }, + count: 0, + size: 0, + }; + if (user_id !== undefined) { + (history as HistorySummaryExtended).user_id = user_id; + } + return history as T; +} + +const HISTORY_OWNED_BY_REGISTERED_USER = createFakeHistory("1234", REGISTERED_USER_ID); +const HISTORY_OWNED_BY_ANOTHER_USER = createFakeHistory("5678", ANOTHER_USER_ID); +const HISTORY_OWNED_BY_ANONYMOUS_USER = createFakeHistory("1234", ANONYMOUS_USER_ID); +const HISTORY_SUMMARY_WITHOUT_USER_ID = createFakeHistory("1234"); + +describe("API Types Helpers", () => { + describe("isRegisteredUser", () => { + it("should return true for a registered user", () => { + expect(isRegisteredUser(REGISTERED_USER)).toBe(true); + }); + + it("should return false for an anonymous user", () => { + expect(isRegisteredUser(ANONYMOUS_USER)).toBe(false); + }); + + it("should return false for sessionless users", () => { + expect(isRegisteredUser(SESSIONLESS_USER)).toBe(false); + }); + }); + + describe("isAnonymousUser", () => { + it("should return true for an anonymous user", () => { + expect(isRegisteredUser(ANONYMOUS_USER)).toBe(false); + }); + + it("should return false for a registered user", () => { + expect(isRegisteredUser(REGISTERED_USER)).toBe(true); + }); + + it("should return false for sessionless users", () => { + expect(isRegisteredUser(SESSIONLESS_USER)).toBe(false); + }); + }); + + describe("userOwnsHistory", () => { + it("should return true for a registered user owning the history", () => { + expect(userOwnsHistory(REGISTERED_USER, HISTORY_OWNED_BY_REGISTERED_USER)).toBe(true); + }); + + it("should return false for a registered user not owning the history", () => { + expect(userOwnsHistory(REGISTERED_USER, HISTORY_OWNED_BY_ANOTHER_USER)).toBe(false); + }); + + it("should return true for a registered user owning a history without user_id", () => { + expect(userOwnsHistory(REGISTERED_USER, HISTORY_SUMMARY_WITHOUT_USER_ID)).toBe(true); + }); + + it("should return true for an anonymous user owning a history with null user_id", () => { + expect(userOwnsHistory(ANONYMOUS_USER, HISTORY_OWNED_BY_ANONYMOUS_USER)).toBe(true); + }); + + it("should return false for an anonymous user not owning a history", () => { + expect(userOwnsHistory(ANONYMOUS_USER, HISTORY_OWNED_BY_REGISTERED_USER)).toBe(false); + }); + + it("should return false for sessionless users", () => { + expect(userOwnsHistory(SESSIONLESS_USER, HISTORY_OWNED_BY_REGISTERED_USER)).toBe(false); + expect(userOwnsHistory(SESSIONLESS_USER, HISTORY_SUMMARY_WITHOUT_USER_ID)).toBe(false); + expect(userOwnsHistory(SESSIONLESS_USER, HISTORY_OWNED_BY_ANONYMOUS_USER)).toBe(false); + }); + }); +}); diff --git a/client/src/api/index.ts b/client/src/api/index.ts index 730f03f5ddec..304573a4c81c 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -26,7 +26,8 @@ export interface HistoryContentsStats { * Data returned by the API when requesting `?view=summary&keys=size,contents_active,user_id`. */ export interface HistorySummaryExtended extends HistorySummary, HistoryContentsStats { - user_id: string; + /** The ID of the user that owns the history. Null if the history is owned by an anonymous user. */ + user_id: string | null; } type HistoryDetailedModel = components["schemas"]["HistoryDetailed"]; @@ -207,6 +208,7 @@ export function isHistorySummaryExtended(history: AnyHistory): history is Histor type QuotaUsageResponse = components["schemas"]["UserQuotaUsage"]; +/** Represents a registered user.**/ export interface User extends QuotaUsageResponse { id: string; email: string; @@ -224,18 +226,30 @@ export interface AnonymousUser { export type GenericUser = User | AnonymousUser; -export function isRegisteredUser(user: User | AnonymousUser | null): user is User { - return !user?.isAnonymous; +/** Represents any user, including anonymous users or session-less (null) users.**/ +export type AnyUser = GenericUser | null; + +export function isRegisteredUser(user: AnyUser): user is User { + return user !== null && !user?.isAnonymous; +} + +export function isAnonymousUser(user: AnyUser): user is AnonymousUser { + return user !== null && user.isAnonymous; } -export function userOwnsHistory(user: User | AnonymousUser | null, history: AnyHistory) { +export function userOwnsHistory(user: AnyUser, history: AnyHistory) { return ( // Assuming histories without user_id are owned by the current user (isRegisteredUser(user) && !hasOwner(history)) || - (isRegisteredUser(user) && hasOwner(history) && user.id === history.user_id) + (isRegisteredUser(user) && hasOwner(history) && user.id === history.user_id) || + (isAnonymousUser(user) && hasAnonymousOwner(history)) ); } function hasOwner(history: AnyHistory): history is HistorySummaryExtended { return "user_id" in history && history.user_id !== null; } + +function hasAnonymousOwner(history: AnyHistory): history is HistorySummaryExtended { + return "user_id" in history && history.user_id === null; +} From 1637e202e5073b9a4ddd4fe54dde0b3dc2ca7fcc Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 28 May 2024 14:30:19 +0200 Subject: [PATCH 03/13] Adapt History SelectorModal test --- .../History/Modals/SelectorModal.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/src/components/History/Modals/SelectorModal.test.js b/client/src/components/History/Modals/SelectorModal.test.js index e6c4e708b1f9..a78afe094f7b 100644 --- a/client/src/components/History/Modals/SelectorModal.test.js +++ b/client/src/components/History/Modals/SelectorModal.test.js @@ -7,6 +7,8 @@ import { createPinia } from "pinia"; import { useHistoryStore } from "stores/historyStore"; import { getLocalVue } from "tests/jest/helpers"; +import { useUserStore } from "@/stores/userStore"; + import SelectorModal from "./SelectorModal"; const localVue = getLocalVue(); @@ -35,6 +37,14 @@ const PROPS_FOR_MODAL_MULTIPLE_SELECT = { const CURRENT_HISTORY_INDICATION_TEXT = "(Current)"; +const CURRENT_USER = { + email: "email", + id: "user_id", + tags_used: [], + isAnonymous: false, + total_disk_usage: 0, +}; + describe("History SelectorModal.vue", () => { let wrapper; let axiosMock; @@ -60,6 +70,10 @@ describe("History SelectorModal.vue", () => { icon: { template: "
" }, }, }); + + const userStore = useUserStore(); + userStore.setCurrentUser(CURRENT_USER); + historyStore = useHistoryStore(); axiosMock = new MockAdapter(axios); getUpdatedAxiosMock(); From 229b1c5bcbd3985bf6247d7736407b151a68b2f6 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 29 May 2024 13:29:17 +0200 Subject: [PATCH 04/13] Skip doi unit test if request fails --- lib/galaxy/managers/citations.py | 1 + .../app/managers/test_CitationsManager_db.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/managers/citations.py b/lib/galaxy/managers/citations.py index 09a4e137cbee..958617289ebb 100644 --- a/lib/galaxy/managers/citations.py +++ b/lib/galaxy/managers/citations.py @@ -55,6 +55,7 @@ def _raw_get_bibtex(self, doi): # content encoding from the Content-Type header (res.encoding), and if # that fails, falls back to guessing from the content itself (res.apparent_encoding). # The guessed encoding is sometimes wrong, better to default to utf-8. + res.raise_for_status() if res.encoding is None: res.encoding = "utf-8" return res.text diff --git a/test/unit/app/managers/test_CitationsManager_db.py b/test/unit/app/managers/test_CitationsManager_db.py index 8c7b8f8eb29c..96d39a89135d 100644 --- a/test/unit/app/managers/test_CitationsManager_db.py +++ b/test/unit/app/managers/test_CitationsManager_db.py @@ -1,3 +1,6 @@ +from unittest import SkipTest + +import requests from beaker.cache import CacheManager from beaker.util import parse_cache_config_options @@ -29,8 +32,11 @@ def test_DoiCache(url_factory): # noqa: F811 with create_and_drop_database(db_url): doi_cache = MockDoiCache(galaxy.config.GalaxyAppConfiguration(override_tempdir=False), db_url) assert is_cache_empty(db_url, "doi") - assert "Jörg" in doi_cache.get_bibtex("10.1093/bioinformatics/bts252") - assert "Özkurt" in doi_cache.get_bibtex("10.1101/2021.12.24.474111") - assert not is_cache_empty(db_url, "doi") - doi_cache._cache.clear() - assert is_cache_empty(db_url, "doi") + try: + assert "Jörg" in doi_cache.get_bibtex("10.1093/bioinformatics/bts252") + assert "Özkurt" in doi_cache.get_bibtex("10.1101/2021.12.24.474111") + assert not is_cache_empty(db_url, "doi") + doi_cache._cache.clear() + assert is_cache_empty(db_url, "doi") + except requests.exceptions.RequestException as e: + raise SkipTest(f"dx.doi failed to respond: {e}") From 5f1ec2b06fe6f5d013ea68b1535b4ab715368d12 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 29 May 2024 13:29:58 +0200 Subject: [PATCH 05/13] Skip tests that require toolshed to be up if toolshed down --- lib/galaxy_test/driver/uses_shed.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/galaxy_test/driver/uses_shed.py b/lib/galaxy_test/driver/uses_shed.py index e592de25aefd..b8db5296848e 100644 --- a/lib/galaxy_test/driver/uses_shed.py +++ b/lib/galaxy_test/driver/uses_shed.py @@ -5,7 +5,10 @@ import tempfile from typing import ClassVar +from unittest import SkipTest + from galaxy.app import UniverseApplication +from galaxy.util.unittest_utils import is_site_up from galaxy.model.base import transaction from galaxy_test.base.populators import DEFAULT_TIMEOUT from galaxy_test.base.uses_shed_api import UsesShedApi @@ -34,6 +37,8 @@ """ +TOOLSHED_URL = "https://toolshed.g2.bx.psu.edu/" + class UsesShed(UsesShedApi): @property @@ -47,6 +52,8 @@ def _app(self) -> UniverseApplication: ... @classmethod def configure_shed(cls, config): + if not is_site_up(TOOLSHED_URL): + raise SkipTest("Test depends on [{TOOLSHED_URL}] being up and it appears to be down.") cls.shed_tools_dir = tempfile.mkdtemp() cls.shed_tool_data_dir = tempfile.mkdtemp() cls._test_driver.temp_directories.extend([cls.shed_tool_data_dir, cls.shed_tools_dir]) From 74e8d3257b56b8fa876426cd1c14136503d5a2c8 Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Wed, 29 May 2024 13:35:52 +0200 Subject: [PATCH 06/13] Make it an f-string Co-authored-by: Nicola Soranzo --- lib/galaxy_test/driver/uses_shed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy_test/driver/uses_shed.py b/lib/galaxy_test/driver/uses_shed.py index b8db5296848e..bbfb4c247544 100644 --- a/lib/galaxy_test/driver/uses_shed.py +++ b/lib/galaxy_test/driver/uses_shed.py @@ -53,7 +53,7 @@ def _app(self) -> UniverseApplication: ... @classmethod def configure_shed(cls, config): if not is_site_up(TOOLSHED_URL): - raise SkipTest("Test depends on [{TOOLSHED_URL}] being up and it appears to be down.") + raise SkipTest(f"Test depends on [{TOOLSHED_URL}] being up and it appears to be down.") cls.shed_tools_dir = tempfile.mkdtemp() cls.shed_tool_data_dir = tempfile.mkdtemp() cls._test_driver.temp_directories.extend([cls.shed_tool_data_dir, cls.shed_tools_dir]) From d8379a79a102a73a70523d942c86aaf31d6b2a12 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 29 May 2024 13:39:05 +0200 Subject: [PATCH 07/13] Also skip unit test --- test/unit/app/test_galaxy_install.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/app/test_galaxy_install.py b/test/unit/app/test_galaxy_install.py index 1695e8faebc2..6d6a2fffe823 100644 --- a/test/unit/app/test_galaxy_install.py +++ b/test/unit/app/test_galaxy_install.py @@ -17,8 +17,10 @@ from galaxy.tool_shed.unittest_utils import StandaloneInstallationTarget from galaxy.tool_shed.util.repository_util import check_for_updates from galaxy.util.tool_shed.tool_shed_registry import DEFAULT_TOOL_SHED_URL +from galaxy.util.unittest_utils import skip_if_site_down +@skip_if_site_down("toolshed.g2.bx.psu.edu") def test_against_production_shed(tmp_path: Path): repo_owner = "iuc" repo_name = "featurecounts" From 5d2ccdfbe79461a9df8a56d5c3f156232a6956f1 Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Wed, 29 May 2024 13:49:10 +0200 Subject: [PATCH 08/13] Use variable Co-authored-by: Nicola Soranzo --- test/unit/app/test_galaxy_install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/app/test_galaxy_install.py b/test/unit/app/test_galaxy_install.py index 6d6a2fffe823..a44fc823ad5e 100644 --- a/test/unit/app/test_galaxy_install.py +++ b/test/unit/app/test_galaxy_install.py @@ -20,7 +20,7 @@ from galaxy.util.unittest_utils import skip_if_site_down -@skip_if_site_down("toolshed.g2.bx.psu.edu") +@skip_if_site_down(DEFAULT_TOOL_SHED_URL) def test_against_production_shed(tmp_path: Path): repo_owner = "iuc" repo_name = "featurecounts" From ce1582f46a0e42aff8c42c36d07970cf29775525 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 29 May 2024 13:50:29 +0200 Subject: [PATCH 09/13] Move assertions out request error handling --- test/unit/app/managers/test_CitationsManager_db.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/app/managers/test_CitationsManager_db.py b/test/unit/app/managers/test_CitationsManager_db.py index 96d39a89135d..d159015949a9 100644 --- a/test/unit/app/managers/test_CitationsManager_db.py +++ b/test/unit/app/managers/test_CitationsManager_db.py @@ -35,8 +35,8 @@ def test_DoiCache(url_factory): # noqa: F811 try: assert "Jörg" in doi_cache.get_bibtex("10.1093/bioinformatics/bts252") assert "Özkurt" in doi_cache.get_bibtex("10.1101/2021.12.24.474111") - assert not is_cache_empty(db_url, "doi") - doi_cache._cache.clear() - assert is_cache_empty(db_url, "doi") except requests.exceptions.RequestException as e: raise SkipTest(f"dx.doi failed to respond: {e}") + assert not is_cache_empty(db_url, "doi") + doi_cache._cache.clear() + assert is_cache_empty(db_url, "doi") From b0ea69e4185dd83875181431491f271d3e520b8a Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 29 May 2024 13:58:00 +0200 Subject: [PATCH 10/13] Fix import order --- lib/galaxy_test/driver/uses_shed.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/galaxy_test/driver/uses_shed.py b/lib/galaxy_test/driver/uses_shed.py index bbfb4c247544..e31237d5f166 100644 --- a/lib/galaxy_test/driver/uses_shed.py +++ b/lib/galaxy_test/driver/uses_shed.py @@ -4,12 +4,11 @@ import string import tempfile from typing import ClassVar - from unittest import SkipTest from galaxy.app import UniverseApplication -from galaxy.util.unittest_utils import is_site_up from galaxy.model.base import transaction +from galaxy.util.unittest_utils import is_site_up from galaxy_test.base.populators import DEFAULT_TIMEOUT from galaxy_test.base.uses_shed_api import UsesShedApi from galaxy_test.driver.driver_util import ( From 2ea3d342cc6e855eba2292a8ad77c63d11753571 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 29 May 2024 15:29:39 +0200 Subject: [PATCH 11/13] Import tool shed url --- lib/galaxy_test/driver/uses_shed.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/galaxy_test/driver/uses_shed.py b/lib/galaxy_test/driver/uses_shed.py index e31237d5f166..f563d516d5a6 100644 --- a/lib/galaxy_test/driver/uses_shed.py +++ b/lib/galaxy_test/driver/uses_shed.py @@ -8,6 +8,7 @@ from galaxy.app import UniverseApplication from galaxy.model.base import transaction +from galaxy.util.tool_shed.tool_shed_registry import DEFAULT_TOOL_SHED_URL from galaxy.util.unittest_utils import is_site_up from galaxy_test.base.populators import DEFAULT_TIMEOUT from galaxy_test.base.uses_shed_api import UsesShedApi @@ -36,8 +37,6 @@ """ -TOOLSHED_URL = "https://toolshed.g2.bx.psu.edu/" - class UsesShed(UsesShedApi): @property @@ -51,8 +50,8 @@ def _app(self) -> UniverseApplication: ... @classmethod def configure_shed(cls, config): - if not is_site_up(TOOLSHED_URL): - raise SkipTest(f"Test depends on [{TOOLSHED_URL}] being up and it appears to be down.") + if not is_site_up(DEFAULT_TOOL_SHED_URL): + raise SkipTest(f"Test depends on [{DEFAULT_TOOL_SHED_URL}] being up and it appears to be down.") cls.shed_tools_dir = tempfile.mkdtemp() cls.shed_tool_data_dir = tempfile.mkdtemp() cls._test_driver.temp_directories.extend([cls.shed_tool_data_dir, cls.shed_tools_dir]) From b00beec3d26b7dff76b32d56078bf7c90eff915b Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 29 May 2024 17:44:55 +0200 Subject: [PATCH 12/13] Allow configuring hgweb repo prefix --- lib/galaxy/config/sample/tool_shed.yml.sample | 4 ++++ lib/galaxy/config/schemas/tool_shed_config_schema.yml | 8 ++++++++ lib/tool_shed/util/repository_util.py | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/config/sample/tool_shed.yml.sample b/lib/galaxy/config/sample/tool_shed.yml.sample index 3b04d57f94a7..e909ec253575 100644 --- a/lib/galaxy/config/sample/tool_shed.yml.sample +++ b/lib/galaxy/config/sample/tool_shed.yml.sample @@ -15,6 +15,10 @@ tool_shed: # installation directory. #hgweb_config_dir: null + # Default URL prefix for repositories served via hgweb. If running an + # external hgweb server you should set this to an empty string. + #hgweb_repo_prefix: repos/ + # Where Tool Shed repositories are stored. #file_path: database/community_files diff --git a/lib/galaxy/config/schemas/tool_shed_config_schema.yml b/lib/galaxy/config/schemas/tool_shed_config_schema.yml index 116f0521f483..47f97b1253c5 100644 --- a/lib/galaxy/config/schemas/tool_shed_config_schema.yml +++ b/lib/galaxy/config/schemas/tool_shed_config_schema.yml @@ -31,6 +31,14 @@ mapping: Where the hgweb.config file is stored. The default is the Galaxy installation directory. + hgweb_repo_prefix: + type: str + required: false + default: repos/ + desc: | + Default URL prefix for repositories served via hgweb. + If running an external hgweb server you should set this to an empty string. + file_path: type: str default: database/community_files diff --git a/lib/tool_shed/util/repository_util.py b/lib/tool_shed/util/repository_util.py index 9d9e805ae669..c84b182ae993 100644 --- a/lib/tool_shed/util/repository_util.py +++ b/lib/tool_shed/util/repository_util.py @@ -218,7 +218,7 @@ def create_repository( # Create the local repository. init_repository(repo_path=repository_path) # Add an entry in the hgweb.config file for the local repository. - lhs = f"repos/{repository.user.username}/{repository.name}" + lhs = f"{app.config.hgweb_repo_prefix}{repository.user.username}/{repository.name}" app.hgweb_config_manager.add_entry(lhs, repository_path) # Create a .hg/hgrc file for the local repository. create_hgrc_file(app, repository) From 410ccb9ab2218678e2ad878264812a2c4d3e155d Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 29 May 2024 17:45:23 +0200 Subject: [PATCH 13/13] Fix up lock handling in hgweb_config_manager --- lib/tool_shed/util/hgweb_config.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/tool_shed/util/hgweb_config.py b/lib/tool_shed/util/hgweb_config.py index f39d9abcc3d3..8c1656406b0f 100644 --- a/lib/tool_shed/util/hgweb_config.py +++ b/lib/tool_shed/util/hgweb_config.py @@ -19,11 +19,11 @@ class HgWebConfigManager: def __init__(self): self.hgweb_config_dir = None self.in_memory_config = None + self.lock = threading.Lock() def add_entry(self, lhs, rhs): """Add an entry in the hgweb.config file for a new repository.""" - lock = threading.Lock() - lock.acquire(True) + self.lock.acquire(True) try: # Since we're changing the config, make sure the latest is loaded into memory. self.read_config(force_read=True) @@ -38,12 +38,11 @@ def add_entry(self, lhs, rhs): except Exception as e: log.debug("Exception in HgWebConfigManager.add_entry(): %s", unicodify(e)) finally: - lock.release() + self.lock.release() def change_entry(self, old_lhs, new_lhs, new_rhs): """Change an entry in the hgweb.config file for a repository - this only happens when the owner changes the name of the repository.""" - lock = threading.Lock() - lock.acquire(True) + self.lock.acquire(True) try: self.make_backup() # Remove the old entry. @@ -55,7 +54,7 @@ def change_entry(self, old_lhs, new_lhs, new_rhs): except Exception as e: log.debug("Exception in HgWebConfigManager.change_entry(): %s", unicodify(e)) finally: - lock.release() + self.lock.release() def get_entry(self, lhs): """Return an entry in the hgweb.config file for a repository"""