Skip to content

Commit 3d02993

Browse files
committed
Add RemoteCallbacks.push_transfer_progress
1 parent 91efd23 commit 3d02993

File tree

3 files changed

+95
-4
lines changed

3 files changed

+95
-4
lines changed

pygit2/callbacks.py

+34-2
Original file line numberDiff line numberDiff line change
@@ -183,15 +183,30 @@ def certificate_check(self, certificate, valid, host):
183183

184184
def transfer_progress(self, stats):
185185
"""
186-
Transfer progress callback. Override with your own function to report
187-
transfer progress.
186+
During the download of new data, this will be regularly called with
187+
the indexer's progress.
188+
189+
Override with your own function to report transfer progress.
188190
189191
Parameters:
190192
191193
stats : TransferProgress
192194
The progress up to now.
193195
"""
194196

197+
def push_transfer_progress(
198+
self, objects_pushed: int, total_objects: int, bytes_pushed: int
199+
):
200+
"""
201+
During the upload portion of a push, this will be regularly called
202+
with progress information.
203+
204+
Be aware that this is called inline with pack building operations,
205+
so performance may be affected.
206+
207+
Override with your own function to report push transfer progress.
208+
"""
209+
195210
def update_tips(self, refname, old, new):
196211
"""
197212
Update tips callback. Override with your own function to report
@@ -370,6 +385,13 @@ def git_push_options(payload, opts=None):
370385
opts.callbacks.credentials = C._credentials_cb
371386
opts.callbacks.certificate_check = C._certificate_check_cb
372387
opts.callbacks.push_update_reference = C._push_update_reference_cb
388+
# Per libgit2 sources, push_transfer_progress may incur a performance hit.
389+
# So, set it only if the user has overridden the no-op stub.
390+
if (
391+
type(payload).push_transfer_progress
392+
is not RemoteCallbacks.push_transfer_progress
393+
):
394+
opts.callbacks.push_transfer_progress = C._push_transfer_progress_cb
373395
# Payload
374396
handle = ffi.new_handle(payload)
375397
opts.callbacks.payload = handle
@@ -554,6 +576,16 @@ def _transfer_progress_cb(stats_ptr, data):
554576
return 0
555577

556578

579+
@libgit2_callback
580+
def _push_transfer_progress_cb(current, total, bytes_pushed, payload):
581+
push_transfer_progress = getattr(payload, 'push_transfer_progress', None)
582+
if not push_transfer_progress:
583+
return 0
584+
585+
push_transfer_progress(current, total, bytes_pushed)
586+
return 0
587+
588+
557589
@libgit2_callback
558590
def _update_tips_cb(refname, a, b, data):
559591
update_tips = getattr(data, 'update_tips', None)

pygit2/decl/callbacks.h

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ extern "Python" int _transfer_progress_cb(
3838
const git_indexer_progress *stats,
3939
void *payload);
4040

41+
extern "Python" int _push_transfer_progress_cb(
42+
unsigned int objects_pushed,
43+
unsigned int total_objects,
44+
size_t bytes_pushed,
45+
void *payload);
46+
4147
extern "Python" int _update_tips_cb(
4248
const char *refname,
4349
const git_oid *a,

test/test_remote.py

+55-2
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ def test_fetch_depth_one(testrepo):
252252

253253
def test_transfer_progress(emptyrepo):
254254
class MyCallbacks(pygit2.RemoteCallbacks):
255-
def transfer_progress(emptyrepo, stats):
256-
emptyrepo.tp = stats
255+
def transfer_progress(self, stats):
256+
self.tp = stats
257257

258258
callbacks = MyCallbacks()
259259
remote = emptyrepo.remotes[0]
@@ -362,6 +362,59 @@ def test_push_when_up_to_date_succeeds(origin, clone, remote):
362362
assert origin_tip == clone_tip
363363

364364

365+
def test_push_transfer_progress(origin, clone, remote):
366+
tip = clone[clone.head.target]
367+
new_tip_id = clone.create_commit(
368+
'refs/heads/master',
369+
tip.author,
370+
tip.author,
371+
'empty commit',
372+
tip.tree.id,
373+
[tip.id],
374+
)
375+
376+
# NOTE: We're currently not testing bytes_pushed due to a bug in libgit2
377+
# 1.9.0: it passes a junk value for bytes_pushed when pushing to a remote
378+
# on the local filesystem, as is the case in this unit test. (When pushing
379+
# to a remote over the network, the value is correct.)
380+
class MyCallbacks(pygit2.RemoteCallbacks):
381+
def push_transfer_progress(self, objects_pushed, total_objects, bytes_pushed):
382+
self.objects_pushed = objects_pushed
383+
self.total_objects = total_objects
384+
385+
assert origin.branches['master'].target == tip.id
386+
387+
callbacks = MyCallbacks()
388+
remote.push(['refs/heads/master'], callbacks=callbacks)
389+
assert callbacks.objects_pushed == 1
390+
assert callbacks.total_objects == 1
391+
assert origin.branches['master'].target == new_tip_id
392+
393+
394+
def test_push_interrupted_from_callbacks(origin, clone, remote):
395+
tip = clone[clone.head.target]
396+
clone.create_commit(
397+
'refs/heads/master',
398+
tip.author,
399+
tip.author,
400+
'empty commit',
401+
tip.tree.id,
402+
[tip.id],
403+
)
404+
405+
class MyCallbacks(pygit2.RemoteCallbacks):
406+
def push_transfer_progress(self, objects_pushed, total_objects, bytes_pushed):
407+
raise InterruptedError('retreat! retreat!')
408+
409+
assert origin.branches['master'].target == tip.id
410+
411+
callbacks = MyCallbacks()
412+
with pytest.raises(InterruptedError, match='retreat! retreat!'):
413+
remote.push(['refs/heads/master'], callbacks=callbacks)
414+
415+
assert origin.branches['master'].target == tip.id
416+
417+
365418
def test_push_non_fast_forward_commits_to_remote_fails(origin, clone, remote):
366419
tip = origin[origin.head.target]
367420
origin.create_commit(

0 commit comments

Comments
 (0)