Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export(libgit2_features)
export(libgit2_sha)
export(libgit2_version)
export(punch_card)
export(remote_ls)
exportClasses(cred_env)
exportClasses(cred_ssh_key)
exportClasses(cred_token)
Expand Down Expand Up @@ -96,6 +97,7 @@ exportMethods(push)
exportMethods(references)
exportMethods(reflog)
exportMethods(remote_add)
exportMethods(remote_ls)
exportMethods(remote_remove)
exportMethods(remote_rename)
exportMethods(remote_set_url)
Expand Down
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ git2r 0.11.0.9000

NEW FEATURES

* Add 'remote_ls' method to list references in a remote repository akin to the
`git ls-remote` command.

* Add 'remote_set_url' method to set the remote's url in the
configuration.

Expand Down
41 changes: 41 additions & 0 deletions R/remote.r
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,44 @@ setMethod("remote_url",
.Call(git2r_remote_url, repo, remote)
}
)


##' List references in a remote repository
##'
##' Displays references available in a remote repository along with the
##' associated commit IDs. Akin to the 'git ls-remote' command.
##' @rdname remote_ls-methods
##' @docType methods
##' @param name Character vector with the "remote" repository URL to query or
##' the name of the remote if a \code{repo} argument is given.
##' @param repo an optional repository object used if remotes are specified by name.
##' @param credentials The credentials for the remote repository.
##' @keywords methods
##' @return Character vector for each reference with the associated commit IDs.
##' @examples
##' \dontrun{
##' remote_ls("https://github.com/ropensci/git2r")
##' }
##' @export
setGeneric("remote_ls",
signature = c("name"),
function(name,
repo = NULL,
credentials = NULL)
standardGeneric("remote_ls"))

##' @rdname remote_ls-methods
##' @export
setMethod("remote_ls",
signature(name = "character"),
function(name, repo, credentials)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to cleanup the temporary repository? One way could be to change the default repo argument to NULL and then something like this in the method:

path <- NULL
if (is.null(repo)) {
    path <- tempdir()
    repo <- git2r::init(path)
}

.Call(git2r_remote_ls, name, repo, credentials)

if (!is.null(path))
   unlink(path, recursive=TRUE)

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial repository does not have a large size footprint and the tempdir will be removed when the R session ends, which is why I didn't clean it up explicitly. I added cleanup with jimhester@5b2067d

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usage of on.exit for the cleanup and also realize that my suggestion would actually fail to cleanup if .Call(...) fails.

if (is.null(repo)) {
path <- tempdir()
repo <- git2r::init(path)
on.exit(unlink(file.path(path, ".git"), recursive = TRUE))
}

.Call(git2r_remote_ls, name, repo, credentials)
}
)
34 changes: 34 additions & 0 deletions man/remote_ls-methods.Rd
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
% Generated by roxygen2 (4.1.1): do not edit by hand
% Please edit documentation in R/remote.r
\docType{methods}
\name{remote_ls}
\alias{remote_ls}
\alias{remote_ls,character-method}
\title{List references in a remote repository}
\usage{
remote_ls(name, repo = NULL, credentials = NULL)

\S4method{remote_ls}{character}(name, repo = NULL, credentials = NULL)
}
\arguments{
\item{name}{Character vector with the "remote" repository URL to query or
the name of the remote if a \code{repo} argument is given.}

\item{repo}{an optional repository object used if remotes are specified by name.}

\item{credentials}{The credentials for the remote repository.}
}
\value{
Character vector for each reference with the associated commit IDs.
}
\description{
Displays references available in a remote repository along with the
associated commit IDs. Akin to the 'git ls-remote' command.
}
\examples{
\dontrun{
remote_ls("https://github.com/ropensci/git2r")
}
}
\keyword{methods}

1 change: 1 addition & 0 deletions src/git2r.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ static const R_CallMethodDef callMethods[] =
{"git2r_remote_rename", (DL_FUNC)&git2r_remote_rename, 3},
{"git2r_remote_set_url", (DL_FUNC)&git2r_remote_set_url, 3},
{"git2r_remote_url", (DL_FUNC)&git2r_remote_url, 2},
{"git2r_remote_ls", (DL_FUNC)&git2r_remote_ls, 3},
{"git2r_repository_can_open", (DL_FUNC)&git2r_repository_can_open, 1},
{"git2r_repository_discover", (DL_FUNC)&git2r_repository_discover, 1},
{"git2r_repository_fetch_heads", (DL_FUNC)&git2r_repository_fetch_heads, 1},
Expand Down
75 changes: 75 additions & 0 deletions src/git2r_remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,78 @@ SEXP git2r_remote_url(SEXP repo, SEXP remote)

return url;
}

/**
* Get the remote's url
*
* Based on https://github.com/libgit2/libgit2/blob/babdc376c7/examples/network/ls-remote.c
* @param repo S4 class git_repository
* @param name Character vector with URL of remote.
* @return Character vector for each reference with the associated commit IDs.
*/
SEXP git2r_remote_ls(SEXP name, SEXP repo, SEXP credentials)
{
const char *name_ = CHAR(STRING_ELT(name, 0));
SEXP result = R_NilValue;
SEXP names = R_NilValue;
git_remote *remote = NULL;
int err;
const git_remote_head **refs;
size_t refs_len, i;
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
git2r_transfer_data payload = GIT2R_TRANSFER_DATA_INIT;
git_repository *repository = NULL;

if (git2r_arg_check_string(name))
git2r_error(__func__, NULL, "'name'", git2r_err_string_arg);

if (git2r_arg_check_credentials(credentials))
git2r_error(__func__, NULL, "'credentials'", git2r_err_credentials_arg);

repository = git2r_repository_open(repo);

if (!repository)
git2r_error(__func__, NULL, git2r_err_invalid_repository, NULL);

err = git_remote_lookup(&remote, repository, name_);
if (err < 0) {
err = git_remote_create_anonymous(&remote, repository, name_);
if (err < 0) {
goto cleanup;
}
}

payload.credentials = credentials;
callbacks.payload = &payload;
callbacks.credentials = &git2r_cred_acquire_cb;

err = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks);
if (err < 0)
goto cleanup;

err = git_remote_ls(&refs, &refs_len, remote);
if (err < 0)
goto cleanup;

PROTECT(result = allocVector(STRSXP, refs_len));
setAttrib(result, R_NamesSymbol, names = allocVector(STRSXP, refs_len));

for (i = 0; i < refs_len; i++) {
char oid[GIT_OID_HEXSZ + 1] = {0};
git_oid_fmt(oid, &refs[i]->oid);
SET_STRING_ELT(result, i, mkChar(oid));
SET_STRING_ELT(names, i, mkChar(refs[i]->name));
}

cleanup:
if (repository)
git_repository_free(repository);

if (result != R_NilValue)
UNPROTECT(1);

if (err)
git2r_error(__func__, giterr_last(), NULL, NULL);

return(result);
}
1 change: 1 addition & 0 deletions src/git2r_remote.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ SEXP git2r_remote_remove(SEXP repo, SEXP remote);
SEXP git2r_remote_rename(SEXP repo, SEXP oldname, SEXP newname);
SEXP git2r_remote_set_url(SEXP repo, SEXP name, SEXP url);
SEXP git2r_remote_url(SEXP repo, SEXP remote);
SEXP git2r_remote_ls(SEXP name, SEXP repo, SEXP credentials);

#endif
8 changes: 8 additions & 0 deletions tests/remotes.R
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,13 @@ remote_remove(repo, "foobar")

stopifnot(identical(remotes(repo), character(0)))

refs <- remote_ls("https://github.com/ropensci/git2r")
stopifnot(length(refs) > 0)
stopifnot(names(refs) > 0)
stopifnot(any(names(refs) == "HEAD"))

# an invalid URL should throw an error
tools::assertError(remote_ls("bad"))

## Cleanup
unlink(path, recursive=TRUE)