Skip to content

Commit 93bbff7

Browse files
committed
Improve usability of Remote.ls_remotes's result
1 parent d7b67e4 commit 93bbff7

File tree

2 files changed

+67
-32
lines changed

2 files changed

+67
-32
lines changed

pygit2/remotes.py

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525

2626
from __future__ import annotations
2727

28-
from typing import TYPE_CHECKING, Any, Generator, Iterator, Literal, TypedDict
28+
import warnings
29+
from typing import TYPE_CHECKING, Any, Generator, Iterator, Literal
2930

3031
# Import from pygit2
3132
from pygit2 import RemoteCallbacks
@@ -50,12 +51,50 @@
5051
from .repository import BaseRepository
5152

5253

53-
class LsRemotesDict(TypedDict):
54+
class RemoteHead:
55+
"""
56+
Description of a reference advertised by a remote server,
57+
given out on `Remote.ls_remotes` calls.
58+
"""
59+
5460
local: bool
55-
loid: None | Oid
61+
"""Available locally"""
62+
63+
oid: Oid
64+
65+
loid: Oid | None
66+
5667
name: str | None
68+
5769
symref_target: str | None
58-
oid: Oid
70+
"""
71+
If the server sent a symref mapping for this ref, this will
72+
point to the target.
73+
"""
74+
75+
def __init__(self, c_struct: Any) -> None:
76+
self.local = bool(c_struct.local)
77+
if self.local:
78+
self.loid = Oid(raw=bytes(ffi.buffer(c_struct.loid.id)[:]))
79+
else:
80+
self.loid = None
81+
82+
self.oid = Oid(raw=bytes(ffi.buffer(c_struct.oid.id)[:]))
83+
self.name = maybe_string(c_struct.name)
84+
self.symref_target = maybe_string(c_struct.symref_target)
85+
86+
def __getitem__(self, item: str) -> Any:
87+
"""
88+
DEPRECATED: Backwards compatibility with legacy user code
89+
that expects this object to be a dictionary with string keys.
90+
"""
91+
warnings.warn(
92+
'ls_remotes no longer returns a dict. '
93+
'Update your code to read from fields instead '
94+
'(e.g. result["name"] --> result.name)',
95+
DeprecationWarning,
96+
)
97+
return getattr(self, item)
5998

6099

61100
class PushUpdate:
@@ -228,10 +267,10 @@ def ls_remotes(
228267
callbacks: RemoteCallbacks | None = None,
229268
proxy: str | None | bool = None,
230269
connect: bool = True,
231-
) -> list[LsRemotesDict]:
270+
) -> list[RemoteHead]:
232271
"""
233-
Return a list of dicts that maps to `git_remote_head` from a
234-
`ls_remotes` call.
272+
Get the list of references with which the server responds to a new
273+
connection.
235274
236275
Parameters:
237276
@@ -247,32 +286,14 @@ def ls_remotes(
247286
if connect:
248287
self.connect(callbacks=callbacks, proxy=proxy)
249288

250-
refs = ffi.new('git_remote_head ***')
251-
refs_len = ffi.new('size_t *')
289+
refs_ptr = ffi.new('git_remote_head ***')
290+
size_ptr = ffi.new('size_t *')
252291

253-
err = C.git_remote_ls(refs, refs_len, self._remote)
292+
err = C.git_remote_ls(refs_ptr, size_ptr, self._remote)
254293
check_error(err)
255294

256-
results = []
257-
for i in range(int(refs_len[0])):
258-
ref = refs[0][i]
259-
local = bool(ref.local)
260-
if local:
261-
loid = Oid(raw=bytes(ffi.buffer(ref.loid.id)[:]))
262-
else:
263-
loid = None
264-
265-
remote = LsRemotesDict(
266-
{
267-
'local': local,
268-
'loid': loid,
269-
'name': maybe_string(ref.name),
270-
'symref_target': maybe_string(ref.symref_target),
271-
'oid': Oid(raw=bytes(ffi.buffer(ref.oid.id)[:])),
272-
}
273-
)
274-
275-
results.append(remote)
295+
num_refs = int(size_ptr[0])
296+
results = [RemoteHead(refs_ptr[0][i]) for i in range(num_refs)]
276297

277298
return results
278299

test/test_remote.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,21 @@ def test_ls_remotes(testrepo: Repository) -> None:
201201
assert refs
202202

203203
# Check that a known ref is returned.
204-
assert next(iter(r for r in refs if r['name'] == 'refs/tags/v0.28.2'))
204+
assert next(iter(r for r in refs if r.name == 'refs/tags/v0.28.2'))
205+
206+
207+
@utils.requires_network
208+
def test_ls_remotes_backwards_compatibility(testrepo: Repository) -> None:
209+
assert 1 == len(testrepo.remotes)
210+
remote = testrepo.remotes[0]
211+
refs = remote.ls_remotes()
212+
ref = refs[0]
213+
214+
for field in ('name', 'oid', 'loid', 'local', 'symref_target'):
215+
new_value = getattr(ref, field)
216+
with pytest.warns(DeprecationWarning, match='no longer returns a dict'):
217+
old_value = ref[field]
218+
assert new_value == old_value
205219

206220

207221
@utils.requires_network
@@ -217,7 +231,7 @@ def test_ls_remotes_without_implicit_connect(testrepo: Repository) -> None:
217231
assert refs
218232

219233
# Check that a known ref is returned.
220-
assert next(iter(r for r in refs if r['name'] == 'refs/tags/v0.28.2'))
234+
assert next(iter(r for r in refs if r.name == 'refs/tags/v0.28.2'))
221235

222236

223237
def test_remote_collection(testrepo: Repository) -> None:

0 commit comments

Comments
 (0)