diff --git a/hgitaly/service/mercurial_operations.py b/hgitaly/service/mercurial_operations.py index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_aGdpdGFseS9zZXJ2aWNlL21lcmN1cmlhbF9vcGVyYXRpb25zLnB5..20e3404f3141b9ff06a8ace0b039dee6beea96ec_aGdpdGFseS9zZXJ2aWNlL21lcmN1cmlhbF9vcGVyYXRpb25zLnB5 100644 --- a/hgitaly/service/mercurial_operations.py +++ b/hgitaly/service/mercurial_operations.py @@ -6,6 +6,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later from grpc import StatusCode import logging +import os import time from mercurial.merge import merge @@ -39,6 +40,11 @@ gitlab_revision_changeset, validate_oid, ) +from ..workdir import ( + ClientMismatch, + reserve_prepare_workdir, + release_workdir_by_id, +) from ..stub.errors_pb2 import ( MergeConflictError, ReferenceUpdateError, @@ -56,6 +62,10 @@ PublishChangesetError, PublishChangesetRequest, PublishChangesetResponse, + ReleaseWorkingDirectoryRequest, + ReleaseWorkingDirectoryResponse, + GetWorkingDirectoryRequest, + GetWorkingDirectoryResponse, ) from ..stub.mercurial_operations_pb2_grpc import ( MercurialOperationsServiceServicer, @@ -85,6 +95,45 @@ The ordering of methods in this source file is the same as in the proto file. """ + def GetWorkingDirectory(self, + request: GetWorkingDirectoryRequest, + context) -> GetWorkingDirectoryResponse: + gl_repo = request.repository + rev = request.revision + repo = self.load_repo(gl_repo, context) + workdirs_root = self.repo_workdirs_root(gl_repo, context) + if rev: + changeset = gitlab_revision_changeset(repo, rev) + if changeset is None: + context.abort(StatusCode.NOT_FOUND, "Revision not found") + else: + changeset = None + + wd = reserve_prepare_workdir(workdirs_root, repo, + client_id=request.client_id, + incarnation_id=request.incarnation_id, + changeset=changeset) + repos_root = os.fsdecode(self.storages[gl_repo.storage_name]) + wd_rpath = str(wd.path.relative_to(repos_root)) + + return GetWorkingDirectoryResponse(working_directory_id=wd.id, + relative_path=wd_rpath) + + def ReleaseWorkingDirectory(self, + request: ReleaseWorkingDirectoryRequest, + context) -> ReleaseWorkingDirectoryResponse: + gl_repo = request.repository + repo = self.load_repo(gl_repo, context) + wd_id = request.working_directory_id + try: + release_workdir_by_id(repo, wd_id, request.client_id) + except ClientMismatch: + context.abort( + StatusCode.PERMISSION_DENIED, + f"Not the owner of the lease on working directory {wd_id}" + ) + return ReleaseWorkingDirectoryResponse() + def MergeAnalysis(self, request: MergeAnalysisRequest, context) -> MergeAnalysisResponse: diff --git a/hgitaly/service/tests/test_mercurial_operations.py b/hgitaly/service/tests/test_mercurial_operations.py index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_aGdpdGFseS9zZXJ2aWNlL3Rlc3RzL3Rlc3RfbWVyY3VyaWFsX29wZXJhdGlvbnMucHk=..20e3404f3141b9ff06a8ace0b039dee6beea96ec_aGdpdGFseS9zZXJ2aWNlL3Rlc3RzL3Rlc3RfbWVyY3VyaWFsX29wZXJhdGlvbnMucHk= 100644 --- a/hgitaly/service/tests/test_mercurial_operations.py +++ b/hgitaly/service/tests/test_mercurial_operations.py @@ -9,8 +9,9 @@ StatusCode, ) import pytest +import uuid from mercurial import ( error as hg_error, phases, ) @@ -12,8 +13,9 @@ from mercurial import ( error as hg_error, phases, ) +from mercurial_testhelpers import RepoWrapper from google.protobuf.timestamp_pb2 import Timestamp @@ -25,6 +27,10 @@ from hgitaly.errors import ( parse_assert_structured_error, ) +from hgitaly.identification import INCARNATION_ID +from hgitaly.servicer import ( + PY_HEPTAPOD_SKIP_HOOKS, +) from hgitaly.stub.errors_pb2 import ( ReferenceUpdateError, ) @@ -37,6 +43,8 @@ PublishChangesetRequest, MergeAnalysisRequest, MergeAnalysisResponse, + GetWorkingDirectoryRequest, + ReleaseWorkingDirectoryRequest, ) from hgitaly.stub.mercurial_operations_pb2_grpc import ( MercurialOperationsServiceStub, @@ -54,6 +62,8 @@ stub_cls = MercurialOperationsServiceStub + client_id = str(uuid.uuid4()) + def merge_analysis(self, **kw): return self.stub.MergeAnalysis(MergeAnalysisRequest( repository=self.grpc_repo, **kw)) @@ -76,6 +86,19 @@ return self.stub.MergeBranch(MergeBranchRequest(**kw), metadata=self.grpc_metadata()) + def get_workdir(self, **kw): + kw.setdefault('client_id', self.client_id) + kw.setdefault('incarnation_id', INCARNATION_ID) + kw.setdefault('repository', self.grpc_repo) + return self.stub.GetWorkingDirectory( + GetWorkingDirectoryRequest(**kw)) + + def release_workdir(self, **kw): + kw.setdefault('client_id', self.client_id) + kw.setdefault('repository', self.grpc_repo) + return self.stub.ReleaseWorkingDirectory( + ReleaseWorkingDirectoryRequest(**kw)) + @pytest.fixture def operations_fixture(grpc_channel, server_repos_root): @@ -603,3 +626,47 @@ assert exc.code() == StatusCode.INVALID_ARGUMENT assert 'changeset' in exc.details().lower() assert 'not found' in exc.details().lower() + + +def test_working_directories(operations_fixture, server_repos_root): + fixture = operations_fixture + gl_rev = b'branch/default' + wrapper = fixture.repo_wrapper + cs0 = wrapper.commit_file('foo') + + resp = fixture.get_workdir(revision=gl_rev) + wd_id = resp.working_directory_id + wd_path = server_repos_root / 'default' / resp.relative_path + + # let us check that the working directory works by committing a file + # in there and retrieve it from the main repo + wd_wrapper = RepoWrapper.load(wd_path) + wd_wrapper.repo.ui.environ[PY_HEPTAPOD_SKIP_HOOKS] = b'yes' + cs_done_in_wd = wd_wrapper.commit_file('bar', "done in wd") + wrapper.reload() + cs_in_main = wrapper.repo[cs_done_in_wd.hex()] + assert cs_in_main.description() == b'done in wd' + assert cs_in_main.p1() == cs0 + + # releasing without being the correct client + with pytest.raises(RpcError) as exc_info: + fixture.release_workdir(working_directory_id=wd_id, + client_id='some other client') + exc = exc_info.value + assert exc.code() == StatusCode.PERMISSION_DENIED + + wd_id2 = fixture.get_workdir(revision=gl_rev).working_directory_id + assert wd_id2 != wd_id + + # releasing for good + fixture.release_workdir(working_directory_id=wd_id) + + # reuse of first workdir (also tests that no revison ends up selecting + # the same Mercurial branch as `branch/default`, hence `default` + assert fixture.get_workdir().working_directory_id == wd_id + + # unknown revision + with pytest.raises(RpcError) as exc_info: + fixture.get_workdir(revision=b'ca34fe12') + exc = exc_info.value + assert exc.code() == StatusCode.NOT_FOUND diff --git a/hgitaly/stub/mercurial_operations_pb2.py b/hgitaly/stub/mercurial_operations_pb2.py index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_aGdpdGFseS9zdHViL21lcmN1cmlhbF9vcGVyYXRpb25zX3BiMi5weQ==..20e3404f3141b9ff06a8ace0b039dee6beea96ec_aGdpdGFseS9zdHViL21lcmN1cmlhbF9vcGVyYXRpb25zX3BiMi5weQ== 100644 --- a/hgitaly/stub/mercurial_operations_pb2.py +++ b/hgitaly/stub/mercurial_operations_pb2.py @@ -19,7 +19,7 @@ from . import operations_pb2 as operations__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1amercurial-operations.proto\x12\x07hgitaly\x1a\x0c\x65rrors.proto\x1a\nlint.proto\x1a\x0cshared.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x10operations.proto\"\x94\x01\n\x14MergeAnalysisRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x17\n\x0fsource_revision\x18\x02 \x01(\x0c\x12\x17\n\x0ftarget_revision\x18\x03 \x01(\x0c\x12\x1c\n\x14skip_conflicts_check\x18\x04 \x01(\x08\"\xad\x02\n\x15MergeAnalysisResponse\x12\x17\n\x0fis_fast_forward\x18\x01 \x01(\x08\x12\x1f\n\x17has_obsolete_changesets\x18\x02 \x01(\x08\x12\x1f\n\x17has_unstable_changesets\x18\x03 \x01(\x08\x12\x15\n\rhas_conflicts\x18\x04 \x01(\x08\x12\x18\n\x10target_is_public\x18\x05 \x01(\x08\x12\x16\n\x0etarget_node_id\x18\x06 \x01(\t\x12\x15\n\rtarget_branch\x18\x07 \x01(\x0c\x12\x14\n\x0ctarget_topic\x18\x08 \x01(\x0c\x12\x16\n\x0esource_node_id\x18\t \x01(\t\x12\x15\n\rsource_branch\x18\n \x01(\x0c\x12\x14\n\x0csource_topic\x18\x0b \x01(\x0c\"\xad\x01\n\x17PublishChangesetRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x1a\n\x04user\x18\x02 \x01(\x0b\x32\x0c.gitaly.User\x12/\n\x08hg_perms\x18\x03 \x01(\x0e\x32\x1d.hgitaly.MercurialPermissions\x12\x17\n\x0fgitlab_revision\x18\x04 \x01(\x0c\"\x1a\n\x18PublishChangesetResponse\"P\n\x15PublishChangesetError\x12.\n\x0bgitlab_hook\x18\x01 \x01(\x0b\x32\x17.gitaly.CustomHookErrorH\x00\x42\x07\n\x05\x65rror\"\xa1\x02\n\x12MergeBranchRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x1a\n\x04user\x18\x02 \x01(\x0b\x32\x0c.gitaly.User\x12/\n\x08hg_perms\x18\x03 \x01(\x0e\x32\x1d.hgitaly.MercurialPermissions\x12\x11\n\tcommit_id\x18\x04 \x01(\t\x12\x0e\n\x06\x62ranch\x18\x05 \x01(\x0c\x12\x0f\n\x07message\x18\x06 \x01(\x0c\x12-\n\ttimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x65xpected_old_oid\x18\x08 \x01(\t\x12\x13\n\x0bsemi_linear\x18\t \x01(\x08\"K\n\x13MergeBranchResponse\x12\x34\n\rbranch_update\x18\x01 \x01(\x0b\x32\x1d.gitaly.OperationBranchUpdate\"\xe7\x01\n\x10MergeBranchError\x12.\n\x0bgitlab_hook\x18\x01 \x01(\x0b\x32\x17.gitaly.CustomHookErrorH\x00\x12.\n\x08\x63onflict\x18\x02 \x01(\x0b\x32\x1a.gitaly.MergeConflictErrorH\x00\x12\x37\n\x0freference_check\x18\x03 \x01(\x0b\x32\x1c.gitaly.ReferenceUpdateErrorH\x00\x12\x31\n\tpre_check\x18\x04 \x01(\x0e\x32\x1c.hgitaly.PreCheckUpdateErrorH\x00\x42\x07\n\x05\x65rror\"\x9a\x01\n\rCensorRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x1a\n\x04user\x18\x02 \x01(\x0b\x32\x0c.gitaly.User\x12\x19\n\x11\x63hangeset_node_id\x18\x03 \x01(\t\x12\x11\n\tfile_path\x18\x04 \x01(\x0c\x12\x11\n\ttombstone\x18\x05 \x01(\x0c\"\x10\n\x0e\x43\x65nsorResponse*8\n\x14MercurialPermissions\x12\x08\n\x04READ\x10\x00\x12\t\n\x05WRITE\x10\x01\x12\x0b\n\x07PUBLISH\x10\x02*k\n\x13PreCheckUpdateError\x12\x0e\n\nNO_PROBLEM\x10\x00\x12\x14\n\x10NOT_FAST_FORWARD\x10\x01\x12\x16\n\x12OBSOLETE_CHANGESET\x10\x02\x12\x16\n\x12UNSTABLE_CHANGESET\x10\x03\x32\xea\x02\n\x1aMercurialOperationsService\x12V\n\rMergeAnalysis\x12\x1d.hgitaly.MergeAnalysisRequest\x1a\x1e.hgitaly.MergeAnalysisResponse\"\x06\xfa\x97(\x02\x08\x02\x12_\n\x10PublishChangeset\x12 .hgitaly.PublishChangesetRequest\x1a!.hgitaly.PublishChangesetResponse\"\x06\xfa\x97(\x02\x08\x01\x12\x41\n\x06\x43\x65nsor\x12\x16.hgitaly.CensorRequest\x1a\x17.hgitaly.CensorResponse\"\x06\xfa\x97(\x02\x08\x01\x12P\n\x0bMergeBranch\x12\x1b.hgitaly.MergeBranchRequest\x1a\x1c.hgitaly.MergeBranchResponse\"\x06\xfa\x97(\x02\x08\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1amercurial-operations.proto\x12\x07hgitaly\x1a\x0c\x65rrors.proto\x1a\nlint.proto\x1a\x0cshared.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x10operations.proto\"\x94\x01\n\x14MergeAnalysisRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x17\n\x0fsource_revision\x18\x02 \x01(\x0c\x12\x17\n\x0ftarget_revision\x18\x03 \x01(\x0c\x12\x1c\n\x14skip_conflicts_check\x18\x04 \x01(\x08\"\xad\x02\n\x15MergeAnalysisResponse\x12\x17\n\x0fis_fast_forward\x18\x01 \x01(\x08\x12\x1f\n\x17has_obsolete_changesets\x18\x02 \x01(\x08\x12\x1f\n\x17has_unstable_changesets\x18\x03 \x01(\x08\x12\x15\n\rhas_conflicts\x18\x04 \x01(\x08\x12\x18\n\x10target_is_public\x18\x05 \x01(\x08\x12\x16\n\x0etarget_node_id\x18\x06 \x01(\t\x12\x15\n\rtarget_branch\x18\x07 \x01(\x0c\x12\x14\n\x0ctarget_topic\x18\x08 \x01(\x0c\x12\x16\n\x0esource_node_id\x18\t \x01(\t\x12\x15\n\rsource_branch\x18\n \x01(\x0c\x12\x14\n\x0csource_topic\x18\x0b \x01(\x0c\"\xad\x01\n\x17PublishChangesetRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x1a\n\x04user\x18\x02 \x01(\x0b\x32\x0c.gitaly.User\x12/\n\x08hg_perms\x18\x03 \x01(\x0e\x32\x1d.hgitaly.MercurialPermissions\x12\x17\n\x0fgitlab_revision\x18\x04 \x01(\x0c\"\x1a\n\x18PublishChangesetResponse\"P\n\x15PublishChangesetError\x12.\n\x0bgitlab_hook\x18\x01 \x01(\x0b\x32\x17.gitaly.CustomHookErrorH\x00\x42\x07\n\x05\x65rror\"\xa1\x02\n\x12MergeBranchRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x1a\n\x04user\x18\x02 \x01(\x0b\x32\x0c.gitaly.User\x12/\n\x08hg_perms\x18\x03 \x01(\x0e\x32\x1d.hgitaly.MercurialPermissions\x12\x11\n\tcommit_id\x18\x04 \x01(\t\x12\x0e\n\x06\x62ranch\x18\x05 \x01(\x0c\x12\x0f\n\x07message\x18\x06 \x01(\x0c\x12-\n\ttimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x65xpected_old_oid\x18\x08 \x01(\t\x12\x13\n\x0bsemi_linear\x18\t \x01(\x08\"K\n\x13MergeBranchResponse\x12\x34\n\rbranch_update\x18\x01 \x01(\x0b\x32\x1d.gitaly.OperationBranchUpdate\"\xe7\x01\n\x10MergeBranchError\x12.\n\x0bgitlab_hook\x18\x01 \x01(\x0b\x32\x17.gitaly.CustomHookErrorH\x00\x12.\n\x08\x63onflict\x18\x02 \x01(\x0b\x32\x1a.gitaly.MergeConflictErrorH\x00\x12\x37\n\x0freference_check\x18\x03 \x01(\x0b\x32\x1c.gitaly.ReferenceUpdateErrorH\x00\x12\x31\n\tpre_check\x18\x04 \x01(\x0e\x32\x1c.hgitaly.PreCheckUpdateErrorH\x00\x42\x07\n\x05\x65rror\"\x9a\x01\n\rCensorRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x1a\n\x04user\x18\x02 \x01(\x0b\x32\x0c.gitaly.User\x12\x19\n\x11\x63hangeset_node_id\x18\x03 \x01(\t\x12\x11\n\tfile_path\x18\x04 \x01(\x0c\x12\x11\n\ttombstone\x18\x05 \x01(\x0c\"\x10\n\x0e\x43\x65nsorResponse\"\x87\x01\n\x1aGetWorkingDirectoryRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x11\n\tclient_id\x18\x02 \x01(\t\x12\x16\n\x0eincarnation_id\x18\x03 \x01(\t\x12\x10\n\x08revision\x18\x04 \x01(\x0c\"R\n\x1bGetWorkingDirectoryResponse\x12\x1c\n\x14working_directory_id\x18\x01 \x01(\r\x12\x15\n\rrelative_path\x18\x02 \x01(\t\"\x97\x01\n\x1eReleaseWorkingDirectoryRequest\x12,\n\nrepository\x18\x01 \x01(\x0b\x32\x12.gitaly.RepositoryB\x04\x98\xc6,\x01\x12\x11\n\tclient_id\x18\x02 \x01(\t\x12\x16\n\x0eincarnation_id\x18\x03 \x01(\t\x12\x1c\n\x14working_directory_id\x18\x04 \x01(\r\"!\n\x1fReleaseWorkingDirectoryResponse*8\n\x14MercurialPermissions\x12\x08\n\x04READ\x10\x00\x12\t\n\x05WRITE\x10\x01\x12\x0b\n\x07PUBLISH\x10\x02*k\n\x13PreCheckUpdateError\x12\x0e\n\nNO_PROBLEM\x10\x00\x12\x14\n\x10NOT_FAST_FORWARD\x10\x01\x12\x16\n\x12OBSOLETE_CHANGESET\x10\x02\x12\x16\n\x12UNSTABLE_CHANGESET\x10\x03\x32\xca\x04\n\x1aMercurialOperationsService\x12V\n\rMergeAnalysis\x12\x1d.hgitaly.MergeAnalysisRequest\x1a\x1e.hgitaly.MergeAnalysisResponse\"\x06\xfa\x97(\x02\x08\x02\x12_\n\x10PublishChangeset\x12 .hgitaly.PublishChangesetRequest\x1a!.hgitaly.PublishChangesetResponse\"\x06\xfa\x97(\x02\x08\x01\x12\x41\n\x06\x43\x65nsor\x12\x16.hgitaly.CensorRequest\x1a\x17.hgitaly.CensorResponse\"\x06\xfa\x97(\x02\x08\x01\x12P\n\x0bMergeBranch\x12\x1b.hgitaly.MergeBranchRequest\x1a\x1c.hgitaly.MergeBranchResponse\"\x06\xfa\x97(\x02\x08\x01\x12h\n\x13GetWorkingDirectory\x12#.hgitaly.GetWorkingDirectoryRequest\x1a$.hgitaly.GetWorkingDirectoryResponse\"\x06\xfa\x97(\x02\x08\x01\x12t\n\x17ReleaseWorkingDirectory\x12\'.hgitaly.ReleaseWorkingDirectoryRequest\x1a(.hgitaly.ReleaseWorkingDirectoryResponse\"\x06\xfa\x97(\x02\x08\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -34,6 +34,10 @@ _globals['_MERGEBRANCHREQUEST'].fields_by_name['repository']._serialized_options = b'\230\306,\001' _globals['_CENSORREQUEST'].fields_by_name['repository']._loaded_options = None _globals['_CENSORREQUEST'].fields_by_name['repository']._serialized_options = b'\230\306,\001' + _globals['_GETWORKINGDIRECTORYREQUEST'].fields_by_name['repository']._loaded_options = None + _globals['_GETWORKINGDIRECTORYREQUEST'].fields_by_name['repository']._serialized_options = b'\230\306,\001' + _globals['_RELEASEWORKINGDIRECTORYREQUEST'].fields_by_name['repository']._loaded_options = None + _globals['_RELEASEWORKINGDIRECTORYREQUEST'].fields_by_name['repository']._serialized_options = b'\230\306,\001' _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['MergeAnalysis']._loaded_options = None _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['MergeAnalysis']._serialized_options = b'\372\227(\002\010\002' _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['PublishChangeset']._loaded_options = None @@ -42,10 +46,14 @@ _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['Censor']._serialized_options = b'\372\227(\002\010\001' _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['MergeBranch']._loaded_options = None _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['MergeBranch']._serialized_options = b'\372\227(\002\010\001' - _globals['_MERCURIALPERMISSIONS']._serialized_start=1649 - _globals['_MERCURIALPERMISSIONS']._serialized_end=1705 - _globals['_PRECHECKUPDATEERROR']._serialized_start=1707 - _globals['_PRECHECKUPDATEERROR']._serialized_end=1814 + _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['GetWorkingDirectory']._loaded_options = None + _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['GetWorkingDirectory']._serialized_options = b'\372\227(\002\010\001' + _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['ReleaseWorkingDirectory']._loaded_options = None + _globals['_MERCURIALOPERATIONSSERVICE'].methods_by_name['ReleaseWorkingDirectory']._serialized_options = b'\372\227(\002\010\001' + _globals['_MERCURIALPERMISSIONS']._serialized_start=2060 + _globals['_MERCURIALPERMISSIONS']._serialized_end=2116 + _globals['_PRECHECKUPDATEERROR']._serialized_start=2118 + _globals['_PRECHECKUPDATEERROR']._serialized_end=2225 _globals['_MERGEANALYSISREQUEST']._serialized_start=131 _globals['_MERGEANALYSISREQUEST']._serialized_end=279 _globals['_MERGEANALYSISRESPONSE']._serialized_start=282 @@ -66,6 +74,14 @@ _globals['_CENSORREQUEST']._serialized_end=1629 _globals['_CENSORRESPONSE']._serialized_start=1631 _globals['_CENSORRESPONSE']._serialized_end=1647 - _globals['_MERCURIALOPERATIONSSERVICE']._serialized_start=1817 - _globals['_MERCURIALOPERATIONSSERVICE']._serialized_end=2179 + _globals['_GETWORKINGDIRECTORYREQUEST']._serialized_start=1650 + _globals['_GETWORKINGDIRECTORYREQUEST']._serialized_end=1785 + _globals['_GETWORKINGDIRECTORYRESPONSE']._serialized_start=1787 + _globals['_GETWORKINGDIRECTORYRESPONSE']._serialized_end=1869 + _globals['_RELEASEWORKINGDIRECTORYREQUEST']._serialized_start=1872 + _globals['_RELEASEWORKINGDIRECTORYREQUEST']._serialized_end=2023 + _globals['_RELEASEWORKINGDIRECTORYRESPONSE']._serialized_start=2025 + _globals['_RELEASEWORKINGDIRECTORYRESPONSE']._serialized_end=2058 + _globals['_MERCURIALOPERATIONSSERVICE']._serialized_start=2228 + _globals['_MERCURIALOPERATIONSSERVICE']._serialized_end=2814 # @@protoc_insertion_point(module_scope) diff --git a/hgitaly/stub/mercurial_operations_pb2_grpc.py b/hgitaly/stub/mercurial_operations_pb2_grpc.py index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_aGdpdGFseS9zdHViL21lcmN1cmlhbF9vcGVyYXRpb25zX3BiMl9ncnBjLnB5..20e3404f3141b9ff06a8ace0b039dee6beea96ec_aGdpdGFseS9zdHViL21lcmN1cmlhbF9vcGVyYXRpb25zX3BiMl9ncnBjLnB5 100644 --- a/hgitaly/stub/mercurial_operations_pb2_grpc.py +++ b/hgitaly/stub/mercurial_operations_pb2_grpc.py @@ -59,6 +59,16 @@ request_serializer=mercurial__operations__pb2.MergeBranchRequest.SerializeToString, response_deserializer=mercurial__operations__pb2.MergeBranchResponse.FromString, _registered_method=True) + self.GetWorkingDirectory = channel.unary_unary( + '/hgitaly.MercurialOperationsService/GetWorkingDirectory', + request_serializer=mercurial__operations__pb2.GetWorkingDirectoryRequest.SerializeToString, + response_deserializer=mercurial__operations__pb2.GetWorkingDirectoryResponse.FromString, + _registered_method=True) + self.ReleaseWorkingDirectory = channel.unary_unary( + '/hgitaly.MercurialOperationsService/ReleaseWorkingDirectory', + request_serializer=mercurial__operations__pb2.ReleaseWorkingDirectoryRequest.SerializeToString, + response_deserializer=mercurial__operations__pb2.ReleaseWorkingDirectoryResponse.FromString, + _registered_method=True) class MercurialOperationsServiceServicer(object): @@ -102,6 +112,26 @@ context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def GetWorkingDirectory(self, request, context): + """GetWorkingDirectory hands over a working directory, checked out to + the wished changeset. + + The server manages a pool of working directories for efficient update. + The client has exclusivity over the working directory it has been handed, + and is expected to release it. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ReleaseWorkingDirectory(self, request, context): + """ReleaseWorkingDirectory allows the server to put a working directoy back + in the pool for later reuse. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_MercurialOperationsServiceServicer_to_server(servicer, server): rpc_method_handlers = { @@ -125,6 +155,16 @@ request_deserializer=mercurial__operations__pb2.MergeBranchRequest.FromString, response_serializer=mercurial__operations__pb2.MergeBranchResponse.SerializeToString, ), + 'GetWorkingDirectory': grpc.unary_unary_rpc_method_handler( + servicer.GetWorkingDirectory, + request_deserializer=mercurial__operations__pb2.GetWorkingDirectoryRequest.FromString, + response_serializer=mercurial__operations__pb2.GetWorkingDirectoryResponse.SerializeToString, + ), + 'ReleaseWorkingDirectory': grpc.unary_unary_rpc_method_handler( + servicer.ReleaseWorkingDirectory, + request_deserializer=mercurial__operations__pb2.ReleaseWorkingDirectoryRequest.FromString, + response_serializer=mercurial__operations__pb2.ReleaseWorkingDirectoryResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'hgitaly.MercurialOperationsService', rpc_method_handlers) @@ -242,3 +282,57 @@ timeout, metadata, _registered_method=True) + + @staticmethod + def GetWorkingDirectory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/hgitaly.MercurialOperationsService/GetWorkingDirectory', + mercurial__operations__pb2.GetWorkingDirectoryRequest.SerializeToString, + mercurial__operations__pb2.GetWorkingDirectoryResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ReleaseWorkingDirectory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/hgitaly.MercurialOperationsService/ReleaseWorkingDirectory', + mercurial__operations__pb2.ReleaseWorkingDirectoryRequest.SerializeToString, + mercurial__operations__pb2.ReleaseWorkingDirectoryResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/hgitaly/workdir.py b/hgitaly/workdir.py index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_aGdpdGFseS93b3JrZGlyLnB5..20e3404f3141b9ff06a8ace0b039dee6beea96ec_aGdpdGFseS93b3JrZGlyLnB5 100644 --- a/hgitaly/workdir.py +++ b/hgitaly/workdir.py @@ -123,8 +123,7 @@ @contextmanager -def working_directory(workdirs_root, repo, client_id, incarnation_id, - changeset=None): +def working_directory(workdirs_root, repo, *args, **kwargs): """Context manager for temporary working directories Entering this context manager, (typically with the ``with`` statement @@ -151,6 +150,17 @@ the ``default`` branch. This should be used only in the case of empty repositories. """ + + wd = reserve_prepare_workdir(workdirs_root, repo, *args, **kwargs) + try: + yield wd + finally: + release_workdir(repo, wd) + + +def reserve_prepare_workdir(workdirs_root, repo, + client_id, incarnation_id, + changeset=None): branch = b'default' if changeset is None else changeset.branch() wd = reserve_workdir(workdirs_root, repo.ui, repo, branch, client_id=client_id, @@ -159,11 +169,7 @@ wd.purge() if changeset is not None: wd.clean_update(changeset.hex()) - - try: - yield wd - finally: - release_workdir(repo, wd) + return wd class rosterlock(lock.lock): @@ -364,4 +370,26 @@ return wd +class ClientMismatch(ValueError): + """Indicates that the Client ID was not as expected""" + + +def release_workdir_by_id(repo, wd_id, client_id): + """Release a working directory specified by ID. + + For sanity / authorization, the provided Client ID is checked against + the one in the roster file. + """ + with locked_roster(repo) as (rosterf, new_rosterf): + for (read_wd_id, client, _ts, branch), line in roster_iter(rosterf): + if read_wd_id == wd_id: + holder_id = client[0] + if holder_id != client_id: + raise ClientMismatch(holder_id, client_id) + + wd = WorkingDirectory(id=wd_id, branch=branch, path=None) + line = wd.roster_line(client_id=None, incarnation_id=None) + new_rosterf.write(line) + + def release_workdir(repo, wd): @@ -367,4 +395,9 @@ def release_workdir(repo, wd): + """Release a working directory object. + + No Client ID check is needed in this case, as the `WorkingDirectory` + object is considered proof enough. + """ with locked_roster(repo) as (rosterf, new_rosterf): for parsed, line in roster_iter(rosterf): if parsed[0] == wd.id: diff --git a/protos/mercurial-operations.proto b/protos/mercurial-operations.proto index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_cHJvdG9zL21lcmN1cmlhbC1vcGVyYXRpb25zLnByb3Rv..20e3404f3141b9ff06a8ace0b039dee6beea96ec_cHJvdG9zL21lcmN1cmlhbC1vcGVyYXRpb25zLnByb3Rv 100644 --- a/protos/mercurial-operations.proto +++ b/protos/mercurial-operations.proto @@ -48,6 +48,26 @@ op: MUTATOR }; } + + // GetWorkingDirectory hands over a working directory, checked out to + // the wished changeset. + // + // The server manages a pool of working directories for efficient update. + // The client has exclusivity over the working directory it has been handed, + // and is expected to release it. + rpc GetWorkingDirectory(GetWorkingDirectoryRequest) returns (GetWorkingDirectoryResponse) { + option (.gitaly.op_type) = { + op: MUTATOR // because of roster file + }; + } + + // ReleaseWorkingDirectory allows the server to put a working directoy back + // in the pool for later reuse. + rpc ReleaseWorkingDirectory(ReleaseWorkingDirectoryRequest) returns (ReleaseWorkingDirectoryResponse) { + option (.gitaly.op_type) = { + op: MUTATOR // because of roster file + }; + } } // Permissions enforced directly within Mercurial @@ -161,3 +181,33 @@ message CensorResponse { } + +message GetWorkingDirectoryRequest { + .gitaly.Repository repository = 1 [(.gitaly.target_repository)=true]; + string client_id = 2; // persistent identifier + // incarnation_id is a volatile identifier whose change means that the + // client has restarted, hence that any lingering working directory + // reservation should be ignored (typically the client was killed abruptly). + string incarnation_id = 3; + bytes revision = 4; +} + +message GetWorkingDirectoryResponse { + uint32 working_directory_id = 1; + // path relative to the repository root. In some cases, it can + // be used by clients instead of the (main) repository relative path + string relative_path = 2; +} + +message ReleaseWorkingDirectoryRequest { + .gitaly.Repository repository = 1 [(.gitaly.target_repository)=true]; + string client_id = 2; // persistent identifier + // incarnation_id is a volatile identifier whose change means that the + // client has restarted, hence that any lingering working directory + // reservation should be ignored (typically the client was killed abruptly). + string incarnation_id = 3; + uint32 working_directory_id = 4; +} + +message ReleaseWorkingDirectoryResponse { +} diff --git a/ruby/lib/hgitaly/mercurial-operations_pb.rb b/ruby/lib/hgitaly/mercurial-operations_pb.rb index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_cnVieS9saWIvaGdpdGFseS9tZXJjdXJpYWwtb3BlcmF0aW9uc19wYi5yYg==..20e3404f3141b9ff06a8ace0b039dee6beea96ec_cnVieS9saWIvaGdpdGFseS9tZXJjdXJpYWwtb3BlcmF0aW9uc19wYi5yYg== 100644 --- a/ruby/lib/hgitaly/mercurial-operations_pb.rb +++ b/ruby/lib/hgitaly/mercurial-operations_pb.rb @@ -73,6 +73,24 @@ end add_message "hgitaly.CensorResponse" do end + add_message "hgitaly.GetWorkingDirectoryRequest" do + optional :repository, :message, 1, "gitaly.Repository" + optional :client_id, :string, 2 + optional :incarnation_id, :string, 3 + optional :revision, :bytes, 4 + end + add_message "hgitaly.GetWorkingDirectoryResponse" do + optional :working_directory_id, :uint32, 1 + optional :relative_path, :string, 2 + end + add_message "hgitaly.ReleaseWorkingDirectoryRequest" do + optional :repository, :message, 1, "gitaly.Repository" + optional :client_id, :string, 2 + optional :incarnation_id, :string, 3 + optional :working_directory_id, :uint32, 4 + end + add_message "hgitaly.ReleaseWorkingDirectoryResponse" do + end add_enum "hgitaly.MercurialPermissions" do value :READ, 0 value :WRITE, 1 @@ -98,6 +116,10 @@ MergeBranchError = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.MergeBranchError").msgclass CensorRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.CensorRequest").msgclass CensorResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.CensorResponse").msgclass + GetWorkingDirectoryRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.GetWorkingDirectoryRequest").msgclass + GetWorkingDirectoryResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.GetWorkingDirectoryResponse").msgclass + ReleaseWorkingDirectoryRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.ReleaseWorkingDirectoryRequest").msgclass + ReleaseWorkingDirectoryResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.ReleaseWorkingDirectoryResponse").msgclass MercurialPermissions = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.MercurialPermissions").enummodule PreCheckUpdateError = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("hgitaly.PreCheckUpdateError").enummodule end diff --git a/ruby/lib/hgitaly/mercurial-operations_services_pb.rb b/ruby/lib/hgitaly/mercurial-operations_services_pb.rb index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_cnVieS9saWIvaGdpdGFseS9tZXJjdXJpYWwtb3BlcmF0aW9uc19zZXJ2aWNlc19wYi5yYg==..20e3404f3141b9ff06a8ace0b039dee6beea96ec_cnVieS9saWIvaGdpdGFseS9tZXJjdXJpYWwtb3BlcmF0aW9uc19zZXJ2aWNlc19wYi5yYg== 100644 --- a/ruby/lib/hgitaly/mercurial-operations_services_pb.rb +++ b/ruby/lib/hgitaly/mercurial-operations_services_pb.rb @@ -32,6 +32,16 @@ # - It is not a 2-phase operation, hence not streaming # - The Request message has a slighly different set of options rpc :MergeBranch, ::Hgitaly::MergeBranchRequest, ::Hgitaly::MergeBranchResponse + # GetWorkingDirectory hands over a working directory, checked out to + # the wished changeset. + # + # The server manages a pool of working directories for efficient update. + # The client has exclusivity over the working directory it has been handed, + # and is expected to release it. + rpc :GetWorkingDirectory, ::Hgitaly::GetWorkingDirectoryRequest, ::Hgitaly::GetWorkingDirectoryResponse + # ReleaseWorkingDirectory allows the server to put a working directoy back + # in the pool for later reuse. + rpc :ReleaseWorkingDirectory, ::Hgitaly::ReleaseWorkingDirectoryRequest, ::Hgitaly::ReleaseWorkingDirectoryResponse end Stub = Service.rpc_stub_class diff --git a/ruby/lib/hgitaly/version.rb b/ruby/lib/hgitaly/version.rb index 79d794cab80c3f7e0660ec088ba9f8ad3b002850_cnVieS9saWIvaGdpdGFseS92ZXJzaW9uLnJi..20e3404f3141b9ff06a8ace0b039dee6beea96ec_cnVieS9saWIvaGdpdGFseS92ZXJzaW9uLnJi 100644 --- a/ruby/lib/hgitaly/version.rb +++ b/ruby/lib/hgitaly/version.rb @@ -1,4 +1,4 @@ # This file is generated by generate-grpc-lib. Do not edit. module Hgitaly - VERSION = '17.9.0' + VERSION = '17.9.1dev0' end