Commit e88e96e5 authored by Georges Racinet's avatar Georges Racinet 🦑
Browse files

Merged default branch into stable to make Heptapod 0.22 new stable

--HG--
branch : stable
Pipeline #23886 passed with stage
in 1 minute and 49 seconds
......@@ -66,7 +66,6 @@ tests-hg-stable:
extends:
- .tests
- .only_hgitaly_default
allow_failure: true
stage: compat
image: ${BASE_IMAGES_MERCURIAL}/hg-stable:${BASE_IMAGES_TAG}
......
......@@ -17,3 +17,4 @@ dafcae2d9a1626ce8ef01e8338bda53acaa516df 0.11.2
df53b71c17a4bbd1a85ab72e9752fc0b70ea77ab 0.12.0
9058efa8be3929d711cd80806f22785d92ac345a 0.12.1
fd405714088c2746874f119e16e899ee3f1f30e2 0.12.2
ba3cda48dd083b47767fe4d22990ee10f750128e 0.13.0
......@@ -21,9 +21,27 @@ from .stub.shared_pb2 import (
from .stub.commit_pb2 import (
ListLastCommitsForTreeResponse,
)
from google.protobuf.text_format import MessageToString
from google.protobuf.timestamp_pb2 import Timestamp
class Logging:
"""Wrapper of requests and responses for sensible logging.
Still has the formatting happen lazily, only if the message
is emitted. We still pay the price of at least one instantiation though.
"""
def __init__(self, message):
self.msg = message
def __repr__(self):
return '%s { %s }' % (self.msg.__class__.__name__,
MessageToString(self.msg, as_one_line=True))
__str__ = __repr__
def commit_author(ctx):
"""Produce a `GitCommit` message from a :class:`changectx` instance.
"""
......
......@@ -126,7 +126,7 @@ class CommitServicer(CommitServiceServicer, HGitalyServicer):
Actually, it always yields one response message, using the empty
response in case the given path does not resolve.
"""
logger.info("TreeEntry request=%r", request)
logger.info("TreeEntry request=%r", message.Logging(request))
repo = self.load_repo(request.repository, context).unfiltered()
changeset = gitlab_revision_changeset(repo, request.revision)
if changeset is None:
......@@ -243,9 +243,11 @@ class CommitServicer(CommitServiceServicer, HGitalyServicer):
# logging potentially both resolution failures
if rev_from is None:
logger.warning("cannot resolve 'from' revision in %r", request)
logger.warning("cannot resolve 'from' revision in %r",
message.Logging(request))
if rev_to is None:
logger.warning("cannot resolve 'to' revision in %r", request)
logger.warning("cannot resolve 'to' revision in %r",
message.Logging(request))
if rev_from is None or rev_to is None:
revs = []
......@@ -307,9 +309,11 @@ class CommitServicer(CommitServiceServicer, HGitalyServicer):
rev_to = gitlab_revision_changeset(repo, getattr(request, 'to'))
max_count = request.max_count
if rev_from is None:
logger.warning("cannot resolve 'from' revision in %r", request)
logger.warning("cannot resolve 'from' revision in %r",
message.Logging(request))
if rev_to is None:
logger.warning("cannot resolve 'to' revision in %r", request)
logger.warning("cannot resolve 'to' revision in %r",
message.Logging(request))
if rev_from is None or rev_to is None:
return CountDivergingCommitsResponse(left_count=0, right_count=0)
left = rev_from.rev()
......@@ -332,7 +336,7 @@ class CommitServicer(CommitServiceServicer, HGitalyServicer):
def GetTreeEntries(self, request: GetTreeEntriesRequest,
context) -> GetTreeEntriesResponse:
logger.info("GetTreeEntries request=%r", request)
logger.info("GetTreeEntries request=%r", message.Logging(request))
repo = self.load_repo(request.repository, context).unfiltered()
revision = request.revision
changeset = gitlab_revision_changeset(repo, revision)
......
......@@ -37,6 +37,8 @@ from ..revision import (
gitlab_revision_changeset,
)
from ..stub.repository_service_pb2 import (
FindMergeBaseRequest,
FindMergeBaseResponse,
RepositoryExistsRequest,
RepositoryExistsResponse,
GetArchiveRequest,
......@@ -55,6 +57,7 @@ from ..path import (
InvalidPath,
validate_relative_path,
)
from .. import message
logger = logging.getLogger(__name__)
......@@ -71,6 +74,32 @@ class RepositoryServiceServicer(RepositoryServiceServicer, HGitalyServicer):
"""RepositoryServiceService implementation.
"""
def FindMergeBase(self,
request: FindMergeBaseRequest,
context) -> FindMergeBaseResponse:
logger.debug("FindMergeBase request=%r", message.Logging(request))
repo = self.load_repo(request.repository, context)
if len(request.revisions) < 2:
# require at least 2 revisions
return invalid_argument(
context, FindMergeBaseResponse,
'FindMergeBase: at least 2 revisions are required'
)
ctxs = []
for rev in request.revisions:
ctx = gitlab_revision_changeset(repo, rev)
if ctx is None:
return FindMergeBaseResponse()
ctxs.append(ctx)
# As per Gitaly, In future we may support this method for more
# than two revisions, but for now we don't.
ctx_from, ctx_to = ctxs[0], ctxs[1]
gca = repo.revs(
b"ancestor(%d, %d)" % (ctx_from.rev(), ctx_to.rev())
).first()
base = repo[gca].hex() if gca is not None else ''
return FindMergeBaseResponse(base=base)
def RepositoryExists(self,
request: RepositoryExistsRequest,
context) -> RepositoryExistsResponse:
......@@ -91,7 +120,7 @@ class RepositoryServiceServicer(RepositoryServiceServicer, HGitalyServicer):
def GetArchive(self,
request: GetArchiveRequest,
context) -> GetArchiveResponse:
logger.debug("GetArchive request=%r", request)
logger.debug("GetArchive request=%r", message.Logging(request))
repo = self.load_repo(request.repository, context)
ctx = repo[request.commit_id]
......
......@@ -272,6 +272,7 @@ def test_find_commits(grpc_channel, server_repos_root):
# when no revision passed; return revs("reverse(::default_tip)")
assert do_rpc() == [sha4, sha3, sha2, sha0]
# with limit
assert do_rpc(limit=0) == []
assert do_rpc(limit=2) == [sha4, sha3]
# with author
assert do_rpc(all=True, author=b"testuser2") == [sha3]
......
......@@ -21,12 +21,17 @@ from hgext3rd.heptapod.keep_around import (
from hgitaly.tests.common import make_empty_repo
from hgitaly.stub.repository_service_pb2 import (
FindMergeBaseRequest,
GetArchiveRequest,
HasLocalBranchesRequest,
RepositoryExistsRequest,
WriteRefRequest,
)
from hgitaly.stub.repository_service_pb2_grpc import RepositoryServiceStub
from mercurial import (
node as node_mod,
pycompat,
)
def test_repository_exists(grpc_channel, server_repos_root):
......@@ -225,3 +230,52 @@ def test_get_archive_multiple_chunks(grpc_channel, server_repos_root,
assert extract_dir.join('archive-dir', 'foo').read() == large_content
del large_content
def test_find_merge_base(grpc_channel, server_repos_root):
repo_stub = RepositoryServiceStub(grpc_channel)
wrapper, grpc_repo = make_empty_repo(server_repos_root)
# repo structure:
#
# o 2 add animal (branch/stable)
# |
# | 1 add bar
# |/
# |
# o 0 add foo
#
ctx0 = wrapper.write_commit('foo')
sha0 = ctx0.hex()
sha1 = wrapper.write_commit('bar').hex()
sha2 = wrapper.write_commit('animal', branch='stable', parent=ctx0).hex()
# commting new root, to test no gca case
sha3 = wrapper.commit_file('tut', branch='other',
parent=node_mod.nullid).hex()
def do_rpc(revisions):
request = FindMergeBaseRequest(
repository=grpc_repo,
revisions=revisions,
)
response = repo_stub.FindMergeBase(request)
return pycompat.sysbytes(response.base)
# Actual test
assert do_rpc([sha1, sha2]) == sha0
assert do_rpc([b'branch/stable', sha1]) == sha0
# when no merge base (gca) found
assert do_rpc([sha0, sha3]) == b''
# test with invalid_argument, as it requires minimum 2 revisions
with pytest.raises(grpc.RpcError) as exc_info:
do_rpc([sha0])
assert exc_info.value.code() == grpc.StatusCode.INVALID_ARGUMENT
assert (
exc_info.value.details()
==
'FindMergeBase: at least 2 revisions are required'
)
sha_not_exists = b'deadnode' * 5
assert do_rpc([sha0, sha_not_exists]) == b''
......@@ -13,6 +13,7 @@ from heptapod.testhelpers import (
from ..stub.shared_pb2 import (
CommitAuthor,
Repository,
SignatureType,
)
......@@ -50,3 +51,11 @@ def test_tag(tmpdir):
assert tag.message_size == 15
assert tag.tagger == message.commit_author(tagging_ctx)
assert tag.signature_type == SignatureType.PGP
def test_logging_class():
log_msg = message.Logging(Repository(storage_name='thestore',
relative_path='a/b/c'))
assert repr(log_msg) == (
'Repository { storage_name: "thestore" relative_path: "a/b/c" }'
)
......@@ -25,6 +25,8 @@ service BlobService {
// GetLFSPointers retrieves LFS pointers from a given set of object IDs.
// This RPC filters all requested objects and only returns those which refer
// to a valid LFS pointer.
//
// Deprecated in favor of `ListLFSPointers`, passing object IDs as revisions.
rpc GetLFSPointers(GetLFSPointersRequest) returns (stream GetLFSPointersResponse) {
option (op_type) = {
op: ACCESSOR
......@@ -34,6 +36,10 @@ service BlobService {
// GetNewLFSPointers retrieves LFS pointers for a limited subset of the
// commit graph. It will return all LFS pointers which are reachable by the
// provided revision, but not reachable by any of the limiting references.
//
// Deprecated in favor of `ListLFSPointers`. `NotInAll` can be replaced with
// `REVISION` `--not` `--all`, while `NotInRefs` can be replaced with
// `REVISION` `--not` `NotInRevs...`.
rpc GetNewLFSPointers(GetNewLFSPointersRequest) returns (stream GetNewLFSPointersResponse) {
option (op_type) = {
op: ACCESSOR
......@@ -41,11 +47,35 @@ service BlobService {
}
// GetAllLFSPointers retrieves all LFS pointers of the given repository.
//
// Deprecated in favor of `ListLFSPointers`, passing `--all` as revision.
rpc GetAllLFSPointers(GetAllLFSPointersRequest) returns (stream GetAllLFSPointersResponse) {
option (op_type) = {
op: ACCESSOR
};
}
// ListLFSPointers retrieves LFS pointers reachable from a given set of
// revisions by doing a graph walk. This includes both normal revisions like
// an object ID or branch, but also the pseudo-revisions "--all" and "--not"
// as documented in git-rev-parse(1). Revisions which don't directly or
// transitively reference any LFS pointers are ignored. It is not valid to
// pass revisions which do not resolve to an existing object.
rpc ListLFSPointers(ListLFSPointersRequest) returns (stream ListLFSPointersResponse) {
option (op_type) = {
op: ACCESSOR
};
}
// ListAllLFSPointers retrieves all LFS pointers in the repository. In
// contrast to `GetAllLFSPointers`, this RPC also includes LFS pointers which
// are not reachable by any reference.
rpc ListAllLFSPointers(ListAllLFSPointersRequest) returns (stream ListAllLFSPointersResponse) {
option (op_type) = {
op: ACCESSOR
};
}
}
message GetBlobRequest {
......@@ -166,3 +196,36 @@ message GetAllLFSPointersResponse {
// LfsPointers is the list of LFS pointers.
repeated LFSPointer lfs_pointers = 1;
}
// ListLFSPointersRequest is a request for the ListLFSPointers RPC.
message ListLFSPointersRequest {
// Repository is the repository for which LFS pointers should be retrieved
// from.
Repository repository = 1[(target_repository)=true];
// Revisions is the list of revisions to retrieve LFS pointers from. Must be
// a non-empty list.
repeated string revisions = 2;
// Limit limits the number of LFS pointers returned.
int32 limit = 3;
}
// ListLFSPointersResponse is a response for the ListLFSPointers RPC.
message ListLFSPointersResponse {
// LfsPointers is the list of LFS pointers which were requested.
repeated LFSPointer lfs_pointers = 1;
}
// ListAllLFSPointersRequest is a request for the ListAllLFSPointers RPC.
message ListAllLFSPointersRequest {
// Repository is the repository for which LFS pointers should be retrieved
// from.
Repository repository = 1[(target_repository)=true];
// Limit limits the number of LFS pointers returned.
int32 limit = 3;
}
// ListAllLFSPointersResponse is a response for the ListAllLFSPointers RPC.
message ListAllLFSPointersResponse {
// LfsPointers is the list of LFS pointers which were requested.
repeated LFSPointer lfs_pointers = 1;
}
......@@ -176,6 +176,12 @@ message CommitsBetweenRequest {
Repository repository = 1 [(target_repository)=true];
bytes from = 2;
bytes to = 3;
// The page token is the last commit OID that was sent. It's expected to be the
// full object ID to guard against ambigious OIDs. Using the page parameter is
// effectivaly equivalent to setting the `from` parameter to the commit object
// identifier.
PaginationParameter pagination_params = 4;
}
message CommitsBetweenResponse {
......
......@@ -44,6 +44,13 @@ service DiffService {
}
message CommitDiffRequest {
enum DiffMode {
// DEFAULT is the standard diff mode and results in a linewise diff for textfiles.
DEFAULT = 0;
// WORDDIFF is a word diff and computes the diff for whitespace separated words instead of for whole lines.
WORDDIFF = 1;
}
Repository repository = 1 [(target_repository)=true];
string left_commit_id = 2;
string right_commit_id = 3;
......@@ -65,6 +72,9 @@ message CommitDiffRequest {
int32 safe_max_files = 11;
int32 safe_max_lines = 12;
int32 safe_max_bytes = 13;
// DiffMode is the mode used for generating the diff. Please refer to the enum declaration for supported modes.
DiffMode diff_mode = 15;
}
// A CommitDiffResponse corresponds to a single changed file in a commit.
......@@ -176,4 +186,3 @@ message ChangedPaths {
bytes path = 1;
Status status = 2;
}
......@@ -24,9 +24,14 @@ service RemoteService {
};
}
// UpdateRemoteMirror compares the references in the target repository and its remote mirror
// repository. Any differences in the references are then addressed by pushing the differing
// references to the mirror. Created and modified references are updated, removed references are
// deleted from the mirror. UpdateRemoteMirror updates all tags. Branches are updated if they match
// the patterns specified in the requests.
rpc UpdateRemoteMirror(stream UpdateRemoteMirrorRequest) returns (UpdateRemoteMirrorResponse) {
option (op_type) = {
op: MUTATOR
op: ACCESSOR
};
}
rpc FindRemoteRepository(FindRemoteRepositoryRequest) returns (FindRemoteRepositoryResponse) {
......@@ -74,15 +79,29 @@ message FetchInternalRemoteResponse {
}
message UpdateRemoteMirrorRequest {
// repository is the repository whose mirror repository to update.
Repository repository = 1 [(target_repository)=true];
// ref_name is actually the remote to update.
string ref_name = 2;
// only_branches_matching contains patterns to match branches against. Only
// the matched brances are updated in the remote mirror. If no patterns are
// specified, all branches are updated. The patterns should only contain the
// branch name without the 'refs/heads/' prefix. "*" can be used as a wildcard
// to match anything. only_branches_matching can be streamed to the server over multiple
// messages. Optional.
repeated bytes only_branches_matching = 3;
// ssh_key is the SSH key to use for accessing to the mirror repository. Optional.
string ssh_key = 4;
// known_hosts specifies the identities used for strict host key checking. Optional.
string known_hosts = 5;
// keep_divergent_refs specifies whether or not to update diverged references in the
// mirror repository.
bool keep_divergent_refs = 6;
}
message UpdateRemoteMirrorResponse {
// divergent_refs contains a list of references that had diverged in the mirror from the
// source repository.
repeated bytes divergent_refs = 1;
}
......
# Copyright 2021 Sushil Khanchi <sushilkhanchi97@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
#
# SPDX-License-Identifier: GPL-2.0-or-later
from mercurial import (
node as node_mod,
pycompat,
)
import pytest
import grpc
import itertools
# from hgitaly.git import EMPTY_TREE_OID
from hgitaly.stub.repository_service_pb2 import (
FindMergeBaseRequest,
)
from hgitaly.stub.repository_service_pb2_grpc import RepositoryServiceStub
from . import skip_comparison_tests
if skip_comparison_tests(): # pragma no cover
pytestmark = pytest.mark.skip
def test_compare_find_merge_base(gitaly_comparison):
fixture = gitaly_comparison
gitaly_repo = fixture.gitaly_repo
git_repo = fixture.git_repo
wrapper = fixture.hg_repo_wrapper
# repo structure:
#
# o 3 add animal (branch/stable)
# |
# | 2 add bar
# |/
# o 1 add zoo
# |
# o 0 add foo
#
gl_branch = b'branch/default'
sha0 = wrapper.write_commit('foo').hex()
git_shas = {
sha0: git_repo.branches()[gl_branch]['sha']
}
ctx1 = wrapper.write_commit('zoo')
sha1 = ctx1.hex()
git_shas[sha1] = git_repo.branches()[gl_branch]['sha']
sha2 = wrapper.write_commit('bar').hex()
git_shas[sha2] = git_repo.branches()[gl_branch]['sha']
sha3 = wrapper.write_commit('animal', branch='stable', parent=ctx1).hex()
git_shas[sha3] = git_repo.branches()[b'branch/stable']['sha']
# commiting a new root, which will test the case when there
# is no merge_base (gca)
sha4 = wrapper.commit_file('tut', branch='other',
parent=node_mod.nullid).hex()
git_shas[sha4] = git_repo.branches()[b'branch/other']['sha']
diff_stubs = dict(
git=RepositoryServiceStub(fixture.gitaly_channel),
hg=RepositoryServiceStub(fixture.hgitaly_channel),
)
def do_rpc(vcs, revisions):
if vcs == 'git':
revs = [git_shas.get(rev, rev) for rev in revisions]
revisions = revs
request = FindMergeBaseRequest(
repository=gitaly_repo,
revisions=revisions,
)
response = diff_stubs[vcs].FindMergeBase(request)
base = pycompat.sysbytes(response.base)
if not base:
return base
return base if vcs == 'git' else git_shas[base]
list_of_interesting_revs = [b'branch/default', b'branch/stable',
sha0, sha1, sha4]
for rev_pair in itertools.product(list_of_interesting_revs, repeat=2):
assert do_rpc('hg', rev_pair) == do_rpc('git', rev_pair)
# test with invalid_argument, as it requires minimum 2 revisions
with pytest.raises(grpc.RpcError) as exc_info_hg:
do_rpc('hg', [sha0])
with pytest.raises(grpc.RpcError) as exc_info_git:
do_rpc('git', [git_shas[sha0]])
assert exc_info_hg.value.code() == exc_info_git.value.code()
assert exc_info_hg.value.details() == exc_info_git.value.details()
sha_not_exists = b'deadnode' * 5
assert (
do_rpc('hg', [sha0, sha_not_exists])
==
do_rpc('git', [git_shas[sha0], sha_not_exists])
)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment