diff --git a/AUTHORS.rst b/AUTHORS.rst index 2cdbe9b0e..817d96bd8 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -159,3 +159,4 @@ Authors:: Yu Jianjian chengyuhang earl + Dan Sully diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 23ec19596..7a0b84163 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,8 @@ 0.28.3 (UNRELEASED) ------------------------- +- New support for ``Remote.ls_remotes(..)`` + - Support for ``/`` operator to traverse trees `#903 `_ diff --git a/pygit2/decl/oid.h b/pygit2/decl/oid.h index da0527081..db420f7c5 100644 --- a/pygit2/decl/oid.h +++ b/pygit2/decl/oid.h @@ -1,3 +1,12 @@ typedef struct git_oid { unsigned char id[20]; } git_oid; + +// This should go to net.h but due to h_order in _run.py, ffi won't compile properly. +typedef struct git_remote_head { + int local; + git_oid oid; + git_oid loid; + char *name; + char *symref_target; +} git_remote_head; diff --git a/pygit2/decl/remote.h b/pygit2/decl/remote.h index 24d7fc890..c43f6f7c1 100644 --- a/pygit2/decl/remote.h +++ b/pygit2/decl/remote.h @@ -123,3 +123,11 @@ const git_refspec * git_remote_get_refspec(const git_remote *remote, size_t n); int git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote); int git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote); void git_remote_free(git_remote *remote); + +int git_remote_connect( + git_remote *remote, + int direction, + const git_remote_callbacks *callbacks, + const git_proxy_options *proxy_opts, + const git_strarray *custom_headers); +int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote); diff --git a/pygit2/remote.py b/pygit2/remote.py index d873e8603..94957ab66 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -216,6 +216,12 @@ def _fill_prune_callbacks(self, prune_callbacks): self._self_handle = ffi.new_handle(self) prune_callbacks.payload = self._self_handle + def _fill_connect_callbacks(self, connect_callbacks): + connect_callbacks.credentials = self._credentials_cb + # We need to make sure that this handle stays alive + self._self_handle = ffi.new_handle(self) + connect_callbacks.payload = self._self_handle + # These functions exist to be called by the git_remote as # callbacks. They proxy the call to whatever the user set @@ -372,6 +378,19 @@ def push_url(self): return maybe_string(C.git_remote_pushurl(self._remote)) + def connect(self, callbacks=None, direction=C.GIT_DIRECTION_FETCH): + """Connect to the remote.""" + + remote_callbacks = ffi.new('git_remote_callbacks *') + C.git_remote_init_callbacks(remote_callbacks, C.GIT_REMOTE_CALLBACKS_VERSION) + + if callbacks is None: + callbacks = RemoteCallbacks() + + callbacks._fill_connect_callbacks(remote_callbacks) + err = C.git_remote_connect(self._remote, direction, remote_callbacks, ffi.NULL, ffi.NULL); + check_error(err) + def save(self): """Save a remote to its repository's configuration.""" @@ -412,6 +431,40 @@ def fetch(self, refspecs=None, message=None, callbacks=None, prune=C.GIT_FETCH_P return TransferProgress(C.git_remote_stats(self._remote)) + def ls_remotes(self, callbacks=None): + """Return a list of dicts that maps to `git_remote_head` from a `ls_remotes` call.""" + + self.connect(callbacks=callbacks) + + refs = ffi.new('git_remote_head ***') + refs_len = ffi.new('size_t *') + + err = C.git_remote_ls(refs, refs_len, self._remote) + check_error(err) + + results = [] + + for i in range(int(refs_len[0])): + + local = bool(refs[0][i].local) + + if local: + loid = Oid(raw=bytes(ffi.buffer(refs[0][i].loid.id)[:])) + else: + loid = None + + remote = { + "local": local, + "loid": loid, + "name": maybe_string(refs[0][i].name), + "symref_target": maybe_string(refs[0][i].symref_target), + "oid": Oid(raw=bytes(ffi.buffer(refs[0][i].oid.id)[:])), + } + + results.append(remote) + + return results + def prune(self, callbacks=None): """Perform a prune against this remote.""" diff --git a/test/test_remote.py b/test/test_remote.py index e89f1ff42..f276ecd1a 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -175,6 +175,18 @@ def test_remote_list(self): remote = self.repo.create_remote(name, url) assert remote.name in [x.name for x in self.repo.remotes] + @unittest.skipIf(utils.no_network(), "Requires network") + def test_ls_remotes(self): + assert 1 == len(self.repo.remotes) + remote = self.repo.remotes[0] + + refs = remote.ls_remotes() + + assert refs + + # Check that a known ref is returned. + assert next(iter(r for r in refs if r['name'] == 'refs/tags/v0.28.2')) + def test_remote_collection(self): remote = self.repo.remotes['origin'] assert REMOTE_NAME == remote.name @@ -249,7 +261,7 @@ def setUp(self): def tearDown(self): self.clone_repo = None super(PruneTestCase, self).tearDown() - + def test_fetch_remote_default(self): self.clone_repo.remotes[0].fetch() assert 'origin/i18n' in self.clone_repo.branches