Skip to content

Commit 8fa74bb

Browse files
karajan1001pmrowla
andauthored
Update on push_refspec and fetch_refspec (#58) (#59)
* Update on push_refspec and fetch_refspec (#58) fix: #58 1. Unify the API of `push_refspec` and `fetch_refspec`. 2. Can push multi `refspec` for one time. 3. No repository Error handle for `fetch_refspec` 4. Now the `push_refspec` and `fetch_refspec` will return the status for each refspec. Co-authored-by: * Some review changes on push and fetch refspec 1. rename `push_refspec` to `push_refspecs` as it can receive a list of refspecs 2. Move SyncEnum to base.py 3. Modify abstract methods args of `push_refspecs` and `fetch_refspecs` to match the new api. * Update scmrepo/git/backend/base.py Co-authored-by: Peter Rowlands (변기호) <[email protected]> * Rename DUPLICETE to UP_TO_DATE Co-authored-by: Peter Rowlands (변기호) <[email protected]>
1 parent b5e7b36 commit 8fa74bb

File tree

6 files changed

+175
-111
lines changed

6 files changed

+175
-111
lines changed

scmrepo/git/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ def add_commit(
348348
iter_refs = partialmethod(_backend_func, "iter_refs")
349349
iter_remote_refs = partialmethod(_backend_func, "iter_remote_refs")
350350
get_refs_containing = partialmethod(_backend_func, "get_refs_containing")
351-
push_refspec = partialmethod(_backend_func, "push_refspec")
351+
push_refspecs = partialmethod(_backend_func, "push_refspecs")
352352
fetch_refspecs = partialmethod(_backend_func, "fetch_refspecs")
353353
_stash_iter = partialmethod(_backend_func, "_stash_iter")
354354
_stash_push = partialmethod(_backend_func, "_stash_push")

scmrepo/git/backend/base.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from abc import ABC, abstractmethod
3+
from enum import Enum
34
from typing import (
45
TYPE_CHECKING,
56
Callable,
@@ -25,6 +26,12 @@ def __init__(self, func):
2526
super().__init__(f"No valid Git backend for '{func}'")
2627

2728

29+
class SyncStatus(Enum):
30+
SUCCESS = 0
31+
UP_TO_DATE = 1
32+
DIVERGED = 2
33+
34+
2835
class BaseGitBackend(ABC):
2936
"""Base Git backend class."""
3037

@@ -206,25 +213,21 @@ def get_refs_containing(self, rev: str, pattern: Optional[str] = None):
206213
"""Iterate over all git refs containing the specified revision."""
207214

208215
@abstractmethod
209-
def push_refspec(
216+
def push_refspecs(
210217
self,
211218
url: str,
212-
src: Optional[str],
213-
dest: str,
219+
refspecs: Union[str, Iterable[str]],
214220
force: bool = False,
215221
on_diverged: Optional[Callable[[str, str], bool]] = None,
216222
progress: Callable[["GitProgressEvent"], None] = None,
217223
**kwargs,
218-
):
224+
) -> Mapping[str, SyncStatus]:
219225
"""Push refspec to a remote Git repo.
220226
221227
Args:
222228
url: Git remote name or absolute Git URL.
223-
src: Local refspec. If src ends with "/" it will be treated as a
224-
prefix, and all refs inside src will be pushed using dest
225-
as destination refspec prefix. If src is None, dest will be
226-
deleted from the remote.
227-
dest: Remote refspec.
229+
refspecs: Iterable containing refspecs to push.
230+
Note that this will not match subkeys.
228231
force: If True, remote refs will be overwritten.
229232
on_diverged: Callback function which will be called if local ref
230233
and remote have diverged and force is False. If the callback
@@ -237,12 +240,12 @@ def push_refspec(
237240
def fetch_refspecs(
238241
self,
239242
url: str,
240-
refspecs: Iterable[str],
241-
force: Optional[bool] = False,
243+
refspecs: Union[str, Iterable[str]],
244+
force: bool = False,
242245
on_diverged: Optional[Callable[[str, str], bool]] = None,
243246
progress: Callable[["GitProgressEvent"], None] = None,
244247
**kwargs,
245-
):
248+
) -> Mapping[str, SyncStatus]:
246249
"""Fetch refspecs from a remote Git repo.
247250
248251
Args:

scmrepo/git/backend/dulwich/__init__.py

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from scmrepo.utils import relpath
2525

2626
from ...objects import GitObject
27-
from ..base import BaseGitBackend
27+
from ..base import BaseGitBackend, SyncStatus
2828

2929
if TYPE_CHECKING:
3030
from dulwich.repo import Repo
@@ -488,26 +488,24 @@ def iter_remote_refs(self, url: str, base: Optional[str] = None, **kwargs):
488488
def get_refs_containing(self, rev: str, pattern: Optional[str] = None):
489489
raise NotImplementedError
490490

491-
def push_refspec(
491+
def push_refspecs(
492492
self,
493493
url: str,
494-
src: Optional[str],
495-
dest: str,
494+
refspecs: Union[str, Iterable[str]],
496495
force: bool = False,
497496
on_diverged: Optional[Callable[[str, str], bool]] = None,
498497
progress: Callable[["GitProgressEvent"], None] = None,
499498
**kwargs,
500-
):
499+
) -> Mapping[str, SyncStatus]:
501500
from dulwich.client import HTTPUnauthorized, get_transport_and_path
502501
from dulwich.errors import NotGitRepository, SendPackError
502+
from dulwich.objectspec import parse_reftuples
503503
from dulwich.porcelain import (
504504
DivergedBranches,
505505
check_diverged,
506506
get_remote_repo,
507507
)
508508

509-
dest_refs, values = self._push_dest_refs(src, dest)
510-
511509
try:
512510
_remote, location = get_remote_repo(self.repo, url)
513511
client, path = get_transport_and_path(location, **kwargs)
@@ -516,26 +514,45 @@ def push_refspec(
516514
f"'{url}' is not a valid Git remote or URL"
517515
) from exc
518516

517+
change_result = {}
518+
selected_refs = []
519+
519520
def update_refs(refs):
520521
from dulwich.objects import ZERO_SHA
521522

523+
selected_refs.extend(
524+
parse_reftuples(self.repo.refs, refs, refspecs, force=force)
525+
)
522526
new_refs = {}
523-
for ref, value in zip(dest_refs, values):
524-
if ref in refs and value != ZERO_SHA:
525-
local_sha = self.repo.refs[ref]
526-
remote_sha = refs[ref]
527+
for (lh, rh, _) in selected_refs:
528+
refname = os.fsdecode(rh)
529+
if rh in refs and lh is not None:
530+
if refs[rh] == self.repo.refs[lh]:
531+
change_result[refname] = SyncStatus.UP_TO_DATE
532+
continue
527533
try:
528-
check_diverged(self.repo, remote_sha, local_sha)
534+
check_diverged(self.repo, refs[rh], self.repo.refs[lh])
529535
except DivergedBranches:
530536
if not force:
531-
overwrite = False
532-
if on_diverged:
533-
overwrite = on_diverged(
534-
os.fsdecode(ref), os.fsdecode(remote_sha)
537+
overwrite = (
538+
on_diverged(
539+
os.fsdecode(lh), os.fsdecode(refs[rh])
535540
)
541+
if on_diverged
542+
else False
543+
)
536544
if not overwrite:
545+
change_result[refname] = SyncStatus.DIVERGED
537546
continue
538-
new_refs[ref] = value
547+
548+
if lh is None:
549+
value = ZERO_SHA
550+
else:
551+
value = self.repo.refs[lh]
552+
553+
new_refs[rh] = value
554+
change_result[refname] = SyncStatus.SUCCESS
555+
539556
return new_refs
540557

541558
try:
@@ -548,38 +565,23 @@ def update_refs(refs):
548565
),
549566
)
550567
except (NotGitRepository, SendPackError) as exc:
551-
raise SCMError("Git failed to push '{src}' to '{url}'") from exc
568+
src = [lh for (lh, _, _) in selected_refs]
569+
raise SCMError(f"Git failed to push '{src}' to '{url}'") from exc
552570
except HTTPUnauthorized:
553571
raise AuthError(url)
554-
555-
def _push_dest_refs(
556-
self, src: Optional[str], dest: str
557-
) -> Tuple[Iterable[bytes], Iterable[bytes]]:
558-
from dulwich.objects import ZERO_SHA
559-
560-
if src is not None and src.endswith("/"):
561-
src_b = os.fsencode(src)
562-
keys = self.repo.refs.subkeys(src_b)
563-
values = [self.repo.refs[b"".join([src_b, key])] for key in keys]
564-
dest_refs = [b"".join([os.fsencode(dest), key]) for key in keys]
565-
else:
566-
if src is None:
567-
values = [ZERO_SHA]
568-
else:
569-
values = [self.repo.refs[os.fsencode(src)]]
570-
dest_refs = [os.fsencode(dest)]
571-
return dest_refs, values
572+
return change_result
572573

573574
def fetch_refspecs(
574575
self,
575576
url: str,
576-
refspecs: Iterable[str],
577+
refspecs: Union[str, Iterable[str]],
577578
force: Optional[bool] = False,
578579
on_diverged: Optional[Callable[[str, str], bool]] = None,
579580
progress: Callable[["GitProgressEvent"], None] = None,
580581
**kwargs,
581-
):
582+
) -> Mapping[str, SyncStatus]:
582583
from dulwich.client import get_transport_and_path
584+
from dulwich.errors import NotGitRepository
583585
from dulwich.objectspec import parse_reftuples
584586
from dulwich.porcelain import (
585587
DivergedBranches,
@@ -594,7 +596,7 @@ def determine_wants(remote_refs):
594596
parse_reftuples(
595597
remote_refs,
596598
self.repo.refs,
597-
[os.fsencode(refspec) for refspec in refspecs],
599+
refspecs,
598600
force=force,
599601
)
600602
)
@@ -612,28 +614,47 @@ def determine_wants(remote_refs):
612614
f"'{url}' is not a valid Git remote or URL"
613615
) from exc
614616

615-
fetch_result = client.fetch(
616-
path,
617-
self.repo,
618-
progress=DulwichProgressReporter(progress) if progress else None,
619-
determine_wants=determine_wants,
620-
)
617+
try:
618+
fetch_result = client.fetch(
619+
path,
620+
self.repo,
621+
progress=DulwichProgressReporter(progress)
622+
if progress
623+
else None,
624+
determine_wants=determine_wants,
625+
)
626+
except NotGitRepository as exc:
627+
raise SCMError(f"Git failed to fetch ref from '{url}'") from exc
628+
629+
result = {}
630+
621631
for (lh, rh, _) in fetch_refs:
622-
try:
623-
if rh in self.repo.refs:
632+
refname = os.fsdecode(rh)
633+
if rh in self.repo.refs:
634+
if self.repo.refs[rh] == fetch_result.refs[lh]:
635+
result[refname] = SyncStatus.UP_TO_DATE
636+
continue
637+
try:
624638
check_diverged(
625639
self.repo, self.repo.refs[rh], fetch_result.refs[lh]
626640
)
627-
except DivergedBranches:
628-
if not force:
629-
overwrite = False
630-
if on_diverged:
631-
overwrite = on_diverged(
632-
os.fsdecode(rh), os.fsdecode(fetch_result.refs[lh])
641+
except DivergedBranches:
642+
if not force:
643+
overwrite = (
644+
on_diverged(
645+
os.fsdecode(rh),
646+
os.fsdecode(fetch_result.refs[lh]),
647+
)
648+
if on_diverged
649+
else False
633650
)
634-
if not overwrite:
635-
continue
651+
if not overwrite:
652+
result[refname] = SyncStatus.DIVERGED
653+
continue
654+
636655
self.repo.refs[rh] = fetch_result.refs[lh]
656+
result[refname] = SyncStatus.SUCCESS
657+
return result
637658

638659
def _stash_iter(self, ref: str):
639660
stash = self._get_stash(ref)

scmrepo/git/backend/gitpython.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from scmrepo.utils import relpath
2929

3030
from ..objects import GitCommit, GitObject
31-
from .base import BaseGitBackend
31+
from .base import BaseGitBackend, SyncStatus
3232

3333
if TYPE_CHECKING:
3434
from scmrepo.progress import GitProgressEvent
@@ -474,27 +474,26 @@ def get_refs_containing(self, rev: str, pattern: Optional[str] = None):
474474
except GitCommandError:
475475
pass
476476

477-
def push_refspec(
477+
def push_refspecs(
478478
self,
479479
url: str,
480-
src: Optional[str],
481-
dest: str,
480+
refspecs: Union[str, Iterable[str]],
482481
force: bool = False,
483482
on_diverged: Optional[Callable[[str, str], bool]] = None,
484483
progress: Callable[["GitProgressEvent"], None] = None,
485484
**kwargs,
486-
):
485+
) -> Mapping[str, SyncStatus]:
487486
raise NotImplementedError
488487

489488
def fetch_refspecs(
490489
self,
491490
url: str,
492-
refspecs: Iterable[str],
493-
force: Optional[bool] = False,
491+
refspecs: Union[str, Iterable[str]],
492+
force: bool = False,
494493
on_diverged: Optional[Callable[[str, str], bool]] = None,
495494
progress: Callable[["GitProgressEvent"], None] = None,
496495
**kwargs,
497-
):
496+
) -> Mapping[str, SyncStatus]:
498497
raise NotImplementedError
499498

500499
def _stash_iter(self, ref: str):

scmrepo/git/backend/pygit2.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from scmrepo.utils import relpath
2727

2828
from ..objects import GitCommit, GitObject
29-
from .base import BaseGitBackend
29+
from .base import BaseGitBackend, SyncStatus
3030

3131
logger = logging.getLogger(__name__)
3232

@@ -414,27 +414,26 @@ def _contains(repo, ref, search_commit):
414414
) and _contains(self.repo, ref, search_commit):
415415
yield ref
416416

417-
def push_refspec(
417+
def push_refspecs(
418418
self,
419419
url: str,
420-
src: Optional[str],
421-
dest: str,
420+
refspecs: Union[str, Iterable[str]],
422421
force: bool = False,
423422
on_diverged: Optional[Callable[[str, str], bool]] = None,
424423
progress: Callable[["GitProgressEvent"], None] = None,
425424
**kwargs,
426-
):
425+
) -> Mapping[str, SyncStatus]:
427426
raise NotImplementedError
428427

429428
def fetch_refspecs(
430429
self,
431430
url: str,
432-
refspecs: Iterable[str],
433-
force: Optional[bool] = False,
431+
refspecs: Union[str, Iterable[str]],
432+
force: bool = False,
434433
on_diverged: Optional[Callable[[str, str], bool]] = None,
435434
progress: Callable[["GitProgressEvent"], None] = None,
436435
**kwargs,
437-
):
436+
) -> Mapping[str, SyncStatus]:
438437
raise NotImplementedError
439438

440439
def _stash_iter(self, ref: str):

0 commit comments

Comments
 (0)