From 387d1b9b964561d61682c821ce2d40b0a60f0aae Mon Sep 17 00:00:00 2001 From: Gregor Lichtner <33544440+glichtner@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:24:06 +0100 Subject: [PATCH] add proxy (#2) --- R/fetch.R | 7 +++-- R/push.R | 8 +++-- R/remote.R | 9 ++++-- R/repository.R | 8 +++-- man/clone.Rd | 7 ++++- man/fetch.Rd | 7 ++++- man/push.Rd | 7 ++++- man/remote_ls.Rd | 6 +++- src/git2r.c | 8 ++--- src/git2r_arg.c | 21 +++++++++++++ src/git2r_arg.h | 1 + src/git2r_clone.c | 19 +++++++++++- src/git2r_clone.h | 3 +- src/git2r_error.c | 3 ++ src/git2r_error.h | 2 ++ src/git2r_proxy.c | 54 ++++++++++++++++++++++++++++++++++ src/git2r_proxy.h | 10 +++++++ src/git2r_push.c | 18 +++++++++++- src/git2r_push.h | 2 +- src/git2r_remote.c | 34 +++++++++++++++++++-- src/git2r_remote.h | 5 ++-- tests/clone_bare.R | 17 +++++++++++ tests/fetch.R | 73 +++++++++++++++++++++++++++++++++++++++------- tests/push.R | 13 +++++++++ tests/remotes.R | 25 ++++++++++++++++ 25 files changed, 330 insertions(+), 37 deletions(-) create mode 100644 src/git2r_proxy.c create mode 100644 src/git2r_proxy.h diff --git a/R/fetch.R b/R/fetch.R index 56ddd5c90..43f4c4b9c 100644 --- a/R/fetch.R +++ b/R/fetch.R @@ -27,6 +27,9 @@ ##' see examples. Pass NULL to use the ##' \code{remote..fetch} variable. Default is ##' \code{NULL}. +##' @param proxy Either \code{NULL} (the default) to disable proxy usage, +##' \code{TRUE} for automatic proxy detection, or a character string +##' with a proxy URL (for example, \code{"http://proxy.example.org:3128"}). ##' @return invisible list of class \code{git_transfer_progress} ##' with statistics from the fetch operation: ##' \describe{ @@ -96,9 +99,9 @@ ##' summary(repo) ##' } fetch <- function(repo = ".", name = NULL, credentials = NULL, - verbose = TRUE, refspec = NULL) { + verbose = TRUE, refspec = NULL, proxy = NULL) { invisible(.Call(git2r_remote_fetch, lookup_repository(repo), - name, credentials, "fetch", verbose, refspec)) + name, credentials, "fetch", verbose, refspec, proxy)) } ##' Get updated heads during the last fetch. diff --git a/R/push.R b/R/push.R index 022a69ae7..b9f943ff0 100644 --- a/R/push.R +++ b/R/push.R @@ -38,6 +38,9 @@ get_upstream_name <- function(object) { ##' ssh key credentials, let this parameter be NULL (the default). ##' @param set_upstream Set the current local branch to track the ##' remote branch. Default is FALSE. +##' @param proxy Either \code{NULL} (the default) to disable proxy usage, +##' \code{TRUE} for automatic proxy detection, or a character string +##' with a proxy URL (for example, \code{"http://proxy.example.org:3128"}). ##' @return invisible(NULL) ##' @seealso \code{\link{cred_user_pass}}, \code{\link{cred_ssh_key}} ##' @export @@ -98,7 +101,8 @@ push <- function(object = ".", refspec = NULL, force = FALSE, credentials = NULL, - set_upstream = FALSE) { + set_upstream = FALSE, + proxy = NULL) { if (is_branch(object)) { name <- get_upstream_name(object) @@ -126,7 +130,7 @@ push <- function(object = ".", refspec <- tmp$refspec } - .Call(git2r_push, object, name, refspec, credentials) + .Call(git2r_push, object, name, refspec, credentials, proxy) if (isTRUE(set_upstream)) { b <- repository_head(object) diff --git a/R/remote.R b/R/remote.R index 6f01f17cf..caf85902f 100644 --- a/R/remote.R +++ b/R/remote.R @@ -112,6 +112,9 @@ remote_url <- function(repo = ".", remote = NULL) { ##' @param credentials The credentials for remote repository ##' access. Default is NULL. To use and query an ssh-agent for the ##' ssh key credentials, let this parameter be NULL (the default). +##' @param proxy Either \code{NULL} (the default) to disable proxy usage, +##' \code{TRUE} for automatic proxy detection, or a character string +##' with a proxy URL (for example, \code{"http://proxy.example.org:3128"}). ##' @return Character vector for each reference with the associated ##' commit IDs. ##' @export @@ -120,7 +123,7 @@ remote_url <- function(repo = ".", remote = NULL) { ##' \dontrun{ ##' remote_ls("https://github.com/ropensci/git2r") ##' } -remote_ls <- function(name = NULL, repo = NULL, credentials = NULL) { +remote_ls <- function(name = NULL, repo = NULL, credentials = NULL, proxy = NULL) { if (is.null(repo)) { ver <- libgit2_version() if (ver$major == 0 && ver$minor < 27) { @@ -132,5 +135,5 @@ remote_ls <- function(name = NULL, repo = NULL, credentials = NULL) { repo <- lookup_repository(repo) } - .Call(git2r_remote_ls, name, repo, credentials) -} + .Call(git2r_remote_ls, name, repo, credentials, proxy) +} \ No newline at end of file diff --git a/R/repository.R b/R/repository.R index ccb175aa1..080e12fad 100644 --- a/R/repository.R +++ b/R/repository.R @@ -219,6 +219,9 @@ init <- function(path = ".", bare = FALSE, branch = NULL) { ##' access. Default is NULL. To use and query an ssh-agent for the ##' ssh key credentials, let this parameter be NULL (the default). ##' @param progress Show progress. Default is TRUE. +##' @param proxy Either \code{NULL} (the default) to disable proxy usage, +##' \code{TRUE} for automatic proxy detection, or a character string +##' with a proxy URL (for example, \code{"http://proxy.example.org:3128"}). ##' @return A \code{git_repository} object. ##' @seealso \link{repository}, \code{\link{cred_user_pass}}, ##' \code{\link{cred_ssh_key}} @@ -273,9 +276,10 @@ clone <- function(url, branch = NULL, checkout = TRUE, credentials = NULL, - progress = TRUE) { + progress = TRUE, + proxy = NULL) { .Call(git2r_clone, url, local_path, bare, - branch, checkout, credentials, progress) + branch, checkout, credentials, progress, proxy) repository(local_path) } diff --git a/man/clone.Rd b/man/clone.Rd index d3745ec1d..cc8d22126 100644 --- a/man/clone.Rd +++ b/man/clone.Rd @@ -11,7 +11,8 @@ clone( branch = NULL, checkout = TRUE, credentials = NULL, - progress = TRUE + progress = TRUE, + proxy = NULL ) } \arguments{ @@ -32,6 +33,10 @@ access. Default is NULL. To use and query an ssh-agent for the ssh key credentials, let this parameter be NULL (the default).} \item{progress}{Show progress. Default is TRUE.} + +\item{proxy}{Either \code{NULL} (the default) to disable proxy usage, +\code{TRUE} for automatic proxy detection, or a character string +with a proxy URL (for example, \code{"http://proxy.example.org:3128"}).} } \value{ A \code{git_repository} object. diff --git a/man/fetch.Rd b/man/fetch.Rd index d042d0552..cab15f38c 100644 --- a/man/fetch.Rd +++ b/man/fetch.Rd @@ -9,7 +9,8 @@ fetch( name = NULL, credentials = NULL, verbose = TRUE, - refspec = NULL + refspec = NULL, + proxy = NULL ) } \arguments{ @@ -29,6 +30,10 @@ locally. Default is \code{TRUE}.} see examples. Pass NULL to use the \code{remote..fetch} variable. Default is \code{NULL}.} + +\item{proxy}{Either \code{NULL} (the default) to disable proxy usage, +\code{TRUE} for automatic proxy detection, or a character string +with a proxy URL (for example, \code{"http://proxy.example.org:3128"}).} } \value{ invisible list of class \code{git_transfer_progress} diff --git a/man/push.Rd b/man/push.Rd index 5a976800c..92b8bcba7 100644 --- a/man/push.Rd +++ b/man/push.Rd @@ -10,7 +10,8 @@ push( refspec = NULL, force = FALSE, credentials = NULL, - set_upstream = FALSE + set_upstream = FALSE, + proxy = NULL ) } \arguments{ @@ -30,6 +31,10 @@ ssh key credentials, let this parameter be NULL (the default).} \item{set_upstream}{Set the current local branch to track the remote branch. Default is FALSE.} + +\item{proxy}{Either \code{NULL} (the default) to disable proxy usage, +\code{TRUE} for automatic proxy detection, or a character string +with a proxy URL (for example, \code{"http://proxy.example.org:3128"}).} } \value{ invisible(NULL) diff --git a/man/remote_ls.Rd b/man/remote_ls.Rd index 271eab294..ebc0502a2 100644 --- a/man/remote_ls.Rd +++ b/man/remote_ls.Rd @@ -4,7 +4,7 @@ \alias{remote_ls} \title{List references in a remote repository} \usage{ -remote_ls(name = NULL, repo = NULL, credentials = NULL) +remote_ls(name = NULL, repo = NULL, credentials = NULL, proxy = NULL) } \arguments{ \item{name}{Character vector with the "remote" repository URL to @@ -17,6 +17,10 @@ specified by name.} \item{credentials}{The credentials for remote repository access. Default is NULL. To use and query an ssh-agent for the ssh key credentials, let this parameter be NULL (the default).} + +\item{proxy}{Either \code{NULL} (the default) to disable proxy usage, +\code{TRUE} for automatic proxy detection, or a character string +with a proxy URL (for example, \code{"http://proxy.example.org:3128"}).} } \value{ Character vector for each reference with the associated diff --git a/src/git2r.c b/src/git2r.c index 74150f0bd..f01d90d4e 100644 --- a/src/git2r.c +++ b/src/git2r.c @@ -81,7 +81,7 @@ static const R_CallMethodDef callMethods[] = CALLDEF(git2r_branch_upstream_canonical_name, 1), CALLDEF(git2r_checkout_path, 2), CALLDEF(git2r_checkout_tree, 3), - CALLDEF(git2r_clone, 7), + CALLDEF(git2r_clone, 8), CALLDEF(git2r_commit, 4), CALLDEF(git2r_commit_parent_list, 1), CALLDEF(git2r_commit_tree, 1), @@ -109,18 +109,18 @@ static const R_CallMethodDef callMethods[] = CALLDEF(git2r_odb_hash, 1), CALLDEF(git2r_odb_hashfile, 1), CALLDEF(git2r_odb_objects, 1), - CALLDEF(git2r_push, 4), + CALLDEF(git2r_push, 5), CALLDEF(git2r_reference_dwim, 2), CALLDEF(git2r_reference_list, 1), CALLDEF(git2r_reflog_list, 2), CALLDEF(git2r_remote_add, 3), - CALLDEF(git2r_remote_fetch, 6), + CALLDEF(git2r_remote_fetch, 7), CALLDEF(git2r_remote_list, 1), CALLDEF(git2r_remote_remove, 2), CALLDEF(git2r_remote_rename, 3), CALLDEF(git2r_remote_set_url, 3), CALLDEF(git2r_remote_url, 2), - CALLDEF(git2r_remote_ls, 3), + CALLDEF(git2r_remote_ls, 4), CALLDEF(git2r_repository_can_open, 1), CALLDEF(git2r_repository_discover, 2), CALLDEF(git2r_repository_fetch_heads, 1), diff --git a/src/git2r_arg.c b/src/git2r_arg.c index 62dc2a03f..557edb919 100644 --- a/src/git2r_arg.c +++ b/src/git2r_arg.c @@ -377,6 +377,27 @@ git2r_arg_check_real( return 0; } +/** + * Check proxy argument + * + * proxy may be NULL, TRUE or a string + * + * @param arg the arg to check + * @return 0 if OK, else -1 + */ +int attribute_hidden +git2r_arg_check_proxy( + SEXP arg) +{ + if (Rf_isNull(arg)) + return 0; + if (Rf_isLogical(arg) && 1 == Rf_length(arg) && NA_LOGICAL != LOGICAL(arg)[0]) + return 0; + if (Rf_isString(arg) && 1 == Rf_length(arg) && NA_STRING != STRING_ELT(arg, 0)) + return 0; + return -1; +} + /** * Check repository argument * diff --git a/src/git2r_arg.h b/src/git2r_arg.h index 384220455..f274f2915 100644 --- a/src/git2r_arg.h +++ b/src/git2r_arg.h @@ -38,6 +38,7 @@ int git2r_arg_check_integer_gte_zero(SEXP arg); int git2r_arg_check_list(SEXP arg); int git2r_arg_check_logical(SEXP arg); int git2r_arg_check_note(SEXP arg); +int git2r_arg_check_proxy(SEXP arg); int git2r_arg_check_repository(SEXP arg); int git2r_arg_check_same_repo(SEXP arg1, SEXP arg2); int git2r_arg_check_signature(SEXP arg); diff --git a/src/git2r_clone.c b/src/git2r_clone.c index a4f0f3b83..79a87da74 100644 --- a/src/git2r_clone.c +++ b/src/git2r_clone.c @@ -23,6 +23,7 @@ #include "git2r_clone.h" #include "git2r_cred.h" #include "git2r_error.h" +#include "git2r_proxy.h" #include "git2r_transfer.h" /** @@ -76,6 +77,7 @@ git2r_clone_progress( * @param checkout Checkout HEAD after the clone is complete. * @param credentials The credentials for remote repository access. * @param progress show progress + * @param proxy_val The proxy settings * @return R_NilValue */ SEXP attribute_hidden @@ -86,13 +88,15 @@ git2r_clone( SEXP branch, SEXP checkout, SEXP credentials, - SEXP progress) + SEXP progress, + SEXP proxy_val) { int error; git_repository *repository = NULL; git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; git2r_transfer_data payload = GIT2R_TRANSFER_DATA_INIT; + git_proxy_options proxy_opts = GIT_PROXY_OPTIONS_INIT; if (git2r_arg_check_string(url)) git2r_error(__func__, NULL, "'url'", git2r_err_string_arg); @@ -108,12 +112,25 @@ git2r_clone( git2r_error(__func__, NULL, "'credentials'", git2r_err_credentials_arg); if (git2r_arg_check_logical(progress)) git2r_error(__func__, NULL, "'progress'", git2r_err_logical_arg); + if (git2r_arg_check_proxy(proxy_val)) + git2r_error(__func__, NULL, "'proxy_val'", git2r_err_proxy_arg); if (LOGICAL(checkout)[0]) checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; else checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; + /* Initialize proxy options */ + error = git2r_set_proxy_options(&proxy_opts, proxy_val); + if (error) + git2r_error( + __func__, + git_error_last(), + git2r_err_unable_to_set_proxy_options, + NULL); + + clone_opts.fetch_opts.proxy_opts = proxy_opts; + clone_opts.checkout_opts = checkout_opts; payload.credentials = credentials; clone_opts.fetch_opts.callbacks.payload = &payload; diff --git a/src/git2r_clone.h b/src/git2r_clone.h index b45f6c1f6..4a7b3e7a0 100644 --- a/src/git2r_clone.h +++ b/src/git2r_clone.h @@ -29,6 +29,7 @@ SEXP git2r_clone( SEXP branch, SEXP checkout, SEXP credentials, - SEXP progress); + SEXP progress, + SEXP proxy_val); #endif diff --git a/src/git2r_error.c b/src/git2r_error.c index a5ea8ecd9..ddabe87ad 100644 --- a/src/git2r_error.c +++ b/src/git2r_error.c @@ -41,6 +41,7 @@ const char git2r_err_ssl_cert_locations[] = "Either 'filename' or 'path' may be 'NULL', but not both"; const char git2r_err_unexpected_config_level[] = "Unexpected config level"; const char git2r_err_unable_to_authenticate[] = "Unable to authenticate with supplied credentials"; +const char git2r_err_unable_to_set_proxy_options[] = "Unable to set proxy options"; /** * Error messages specific to argument checking @@ -55,6 +56,8 @@ const char git2r_err_commit_stash_arg[] = "must be an S3 class git_commit or an S3 class git_stash"; const char git2r_err_credentials_arg[] = "must be an S3 class with credentials"; +const char git2r_err_proxy_arg[] = + "must be either 1) NULL, or 2) TRUE or 3) a character vector"; const char git2r_err_diff_arg[] = "Invalid diff parameters"; const char git2r_err_fetch_heads_arg[] = diff --git a/src/git2r_error.h b/src/git2r_error.h index e5234531e..0b14379bc 100644 --- a/src/git2r_error.h +++ b/src/git2r_error.h @@ -40,6 +40,7 @@ extern const char git2r_err_revparse_single[]; extern const char git2r_err_ssl_cert_locations[]; extern const char git2r_err_unexpected_config_level[]; extern const char git2r_err_unable_to_authenticate[]; +extern const char git2r_err_unable_to_set_proxy_options[]; /** * Error messages specific to argument checking @@ -49,6 +50,7 @@ extern const char git2r_err_branch_arg[]; extern const char git2r_err_commit_arg[]; extern const char git2r_err_commit_stash_arg[]; extern const char git2r_err_credentials_arg[]; +extern const char git2r_err_proxy_arg[]; extern const char git2r_err_diff_arg[]; extern const char git2r_err_fetch_heads_arg[]; extern const char git2r_err_filename_arg[]; diff --git a/src/git2r_proxy.c b/src/git2r_proxy.c new file mode 100644 index 000000000..5b5aa318f --- /dev/null +++ b/src/git2r_proxy.c @@ -0,0 +1,54 @@ +#include "git2r_proxy.h" +#include +#include + +/** + * Initialize and set libgit2 proxy options from an R object, storing + * the allocated proxy URL in a payload struct. + * + * This function populates \p proxy_opts based on \p proxy_val: + * \li If \p proxy_val is \c R_NilValue (i.e., NULL in R), proxy is + * set to \c GIT_PROXY_NONE (disabled). + * \li If \p proxy_val is a logical \c TRUE, proxy is set to + * \c GIT_PROXY_AUTO (automatic detection). + * \li If \p proxy_val is a character vector of length 1, proxy is + * set to \c GIT_PROXY_SPECIFIED with that URL. + * Otherwise, the function returns \c -1 to indicate invalid + * or unsupported input. + * + * @param proxy_opts A pointer to a \c git_proxy_options struct, which + * is initialized via \c git_proxy_options_init + * and then populated according to \p proxy_val. + * @param proxy_val An R object specifying the desired proxy + * configuration: \c NULL (no proxy), a logical + * \c TRUE (auto detection), or a character string + * (URL). + * @return 0 on success, or -1 if \p proxy_val is invalid + * or memory allocation fails. + * + * \note The caller is responsible for calling + * \c git2r_proxy_payload_free() on the \p payload struct + * to deallocate the proxy URL after \c git_remote_connect() + * or \c git_remote_fetch() has finished using it. + */ +int git2r_set_proxy_options(git_proxy_options *proxy_opts, SEXP proxy_val) +{ + git_proxy_options_init(proxy_opts, GIT_PROXY_OPTIONS_VERSION); + proxy_opts->type = GIT_PROXY_NONE; + proxy_opts->url = NULL; + + if (Rf_isNull(proxy_val)) { + return 0; /* No proxy */ + } else if (Rf_isLogical(proxy_val) && LOGICAL(proxy_val)[0]) { + proxy_opts->type = GIT_PROXY_AUTO; /* Auto-detect proxy */ + return 0; + } else if (Rf_isString(proxy_val) && Rf_length(proxy_val) == 1) { + const char *url = CHAR(STRING_ELT(proxy_val, 0)); + if (url) { + proxy_opts->type = GIT_PROXY_SPECIFIED; + proxy_opts->url = url; + } + return 0; + } + return -1; /* Invalid input */ +} diff --git a/src/git2r_proxy.h b/src/git2r_proxy.h new file mode 100644 index 000000000..29c73e60d --- /dev/null +++ b/src/git2r_proxy.h @@ -0,0 +1,10 @@ +#ifndef GIT2R_PROXY_H +#define GIT2R_PROXY_H + +#include +#include + +/* Function to initialize proxy options */ +int git2r_set_proxy_options(git_proxy_options *proxy_opts, SEXP proxy_val); + +#endif /* GIT2R_PROXY_H */ diff --git a/src/git2r_push.c b/src/git2r_push.c index f0f85139a..8671e1a1a 100644 --- a/src/git2r_push.c +++ b/src/git2r_push.c @@ -22,6 +22,7 @@ #include "git2r_arg.h" #include "git2r_cred.h" #include "git2r_error.h" +#include "git2r_proxy.h" #include "git2r_push.h" #include "git2r_repository.h" #include "git2r_signature.h" @@ -59,6 +60,7 @@ git2r_nothing_to_push( * @param name The remote to push to * @param refspec The string vector of refspec to push * @param credentials The credentials for remote repository access. + * @param proxy_val The proxy settings * @return R_NilValue */ SEXP attribute_hidden @@ -66,7 +68,8 @@ git2r_push( SEXP repo, SEXP name, SEXP refspec, - SEXP credentials) + SEXP credentials, + SEXP proxy_val) { int error; git_remote *remote = NULL; @@ -74,6 +77,7 @@ git2r_push( git_strarray c_refspecs = {0}; git_push_options opts = GIT_PUSH_OPTIONS_INIT; git2r_transfer_data payload = GIT2R_TRANSFER_DATA_INIT; + git_proxy_options proxy_opts = GIT_PROXY_OPTIONS_INIT; if (git2r_arg_check_string(name)) git2r_error(__func__, NULL, "'name'", git2r_err_string_arg); @@ -81,6 +85,8 @@ git2r_push( git2r_error(__func__, NULL, "'refspec'", git2r_err_string_vec_arg); if (git2r_arg_check_credentials(credentials)) git2r_error(__func__, NULL, "'credentials'", git2r_err_credentials_arg); + if (git2r_arg_check_proxy(proxy_val)) + git2r_error(__func__, NULL, "'proxy_val'", git2r_err_proxy_arg); if (git2r_nothing_to_push(refspec)) return R_NilValue; @@ -93,6 +99,16 @@ git2r_push( if (error) goto cleanup; + /* Initialize proxy options */ + error = git2r_set_proxy_options(&proxy_opts, proxy_val); + if (error) + git2r_error( + __func__, + git_error_last(), + git2r_err_unable_to_set_proxy_options, + NULL); + opts.proxy_opts = proxy_opts; + payload.credentials = credentials; opts.callbacks.payload = &payload; opts.callbacks.credentials = &git2r_cred_acquire_cb; diff --git a/src/git2r_push.h b/src/git2r_push.h index 6e6ec258c..b062ed327 100644 --- a/src/git2r_push.h +++ b/src/git2r_push.h @@ -22,6 +22,6 @@ #include #include -SEXP git2r_push(SEXP repo, SEXP name, SEXP refspec, SEXP credentials); +SEXP git2r_push(SEXP repo, SEXP name, SEXP refspec, SEXP credentials, SEXP proxy_val); #endif diff --git a/src/git2r_remote.c b/src/git2r_remote.c index f7a3fafad..2269e80db 100644 --- a/src/git2r_remote.c +++ b/src/git2r_remote.c @@ -22,6 +22,7 @@ #include "git2r_arg.h" #include "git2r_cred.h" #include "git2r_error.h" +#include "git2r_proxy.h" #include "git2r_remote.h" #include "git2r_repository.h" #include "git2r_S3.h" @@ -123,6 +124,7 @@ git2r_update_tips_cb( * @param verbose Print information each time a reference is updated locally. * @param refspecs The refspecs to use for this fetch. Pass R_NilValue * to use the base refspecs. + * @param proxy_val The proxy settings * @return R_NilValue */ SEXP attribute_hidden @@ -132,7 +134,8 @@ git2r_remote_fetch( SEXP credentials, SEXP msg, SEXP verbose, - SEXP refspecs) + SEXP refspecs, + SEXP proxy_val) { int error, nprotect = 0; SEXP result = R_NilValue; @@ -142,6 +145,7 @@ git2r_remote_fetch( git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; git2r_transfer_data payload = GIT2R_TRANSFER_DATA_INIT; git_strarray refs = {0}; + git_proxy_options proxy_opts = GIT_PROXY_OPTIONS_INIT; if (git2r_arg_check_string(name)) git2r_error(__func__, NULL, "'name'", git2r_err_string_arg); @@ -153,6 +157,19 @@ git2r_remote_fetch( git2r_error(__func__, NULL, "'verbose'", git2r_err_logical_arg); if ((!Rf_isNull(refspecs)) && git2r_arg_check_string_vec(refspecs)) git2r_error(__func__, NULL, "'refspecs'", git2r_err_string_vec_arg); + if (git2r_arg_check_proxy(proxy_val)) + git2r_error(__func__, NULL, "'proxy_val'", git2r_err_proxy_arg); + + /* Initialize proxy options */ + error = git2r_set_proxy_options(&proxy_opts, proxy_val); + if (error) + git2r_error( + __func__, + git_error_last(), + git2r_err_unable_to_set_proxy_options, + NULL); + + fetch_opts.proxy_opts = proxy_opts; repository = git2r_repository_open(repo); if (!repository) @@ -452,13 +469,17 @@ git2r_remote_url( * Based on https://github.com/libgit2/libgit2/blob/babdc376c7/examples/network/ls-remote.c * @param repo S3 class git_repository * @param name Character vector with URL of remote. + * @param credentials The credentials for remote repository access. + * @param proxy_val The proxy settings for the remote (NULL, TRUE, or a string with proxy URL). * @return Character vector for each reference with the associated commit IDs. */ SEXP attribute_hidden git2r_remote_ls( SEXP name, SEXP repo, - SEXP credentials) + SEXP credentials, + SEXP proxy_val +) { const char *name_ = NULL; SEXP result = R_NilValue; @@ -470,11 +491,14 @@ git2r_remote_ls( git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; git2r_transfer_data payload = GIT2R_TRANSFER_DATA_INIT; git_repository *repository = NULL; + git_proxy_options proxy_opts; 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); + if (git2r_arg_check_proxy(proxy_val)) + git2r_error(__func__, NULL, "'proxy_val'", git2r_err_proxy_arg); if (!Rf_isNull(repo)) { repository = git2r_repository_open(repo); @@ -501,7 +525,11 @@ git2r_remote_ls( callbacks.payload = &payload; callbacks.credentials = &git2r_cred_acquire_cb; - error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, NULL, NULL); + error = git2r_set_proxy_options(&proxy_opts, proxy_val); + if (error) + goto cleanup; + + error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, &proxy_opts, NULL); if (error) goto cleanup; diff --git a/src/git2r_remote.h b/src/git2r_remote.h index 800dce6a0..b5b06fa0c 100644 --- a/src/git2r_remote.h +++ b/src/git2r_remote.h @@ -29,12 +29,13 @@ SEXP git2r_remote_fetch( SEXP credentials, SEXP msg, SEXP verbose, - SEXP refspecs); + SEXP refspecs, + SEXP proxy_val); SEXP git2r_remote_list(SEXP repo); 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); +SEXP git2r_remote_ls(SEXP name, SEXP repo, SEXP credentials, SEXP proxy_val); #endif diff --git a/tests/clone_bare.R b/tests/clone_bare.R index 0a5a1f272..3507d503d 100644 --- a/tests/clone_bare.R +++ b/tests/clone_bare.R @@ -60,6 +60,23 @@ stopifnot(identical(commit_1$summary, bare_commit_1$summary)) stopifnot(identical(commit_1$message, bare_commit_1$message)) stopifnot(!identical(commit_1$repo, bare_commit_1$repo)) +if (identical(Sys.getenv("NOT_CRAN"), "true")) { + ## Minimal check: test that calling `clone` with `proxy=TRUE` doesn't crash + test_path_proxy <- tempfile(pattern = "git2r-clone-proxy-") + dir.create(test_path_proxy) + + message("Testing clone() with proxy=TRUE (auto-detect)") + tryCatch({ + proxy_repo <- clone(path_repo, test_path_proxy, proxy = TRUE) + # If we get here without error, the parameter was accepted; + # further checks might be minimal if there's no real proxy in use. + stopifnot(identical(is_bare(proxy_repo), FALSE)) + stopifnot(length(commits(proxy_repo)) == 1L) + }, error = function(e) { + message("clone() with proxy=TRUE failed as expected if no actual proxy is set: ", e$message) + }) +} + ## Cleanup unlink(path_bare, recursive = TRUE) unlink(path_repo, recursive = TRUE) diff --git a/tests/fetch.R b/tests/fetch.R index a57d050cf..a782d0494 100644 --- a/tests/fetch.R +++ b/tests/fetch.R @@ -67,51 +67,102 @@ stopifnot(identical(sha(fh), fh$sha)) ## Test show method of non-empty repository where head is null show(repo_2) +if (identical(Sys.getenv("NOT_CRAN"), "true")) { + ## Only do proxy parameter tests when networking is allowed (not on CRAN). + + message("Testing fetch() with proxy=TRUE (auto-detect)") + tryCatch({ + fetch(repo_2, "origin", proxy = TRUE) + ## If no error, the parameter was accepted. + ## (We won't see new commits unless there's an actual update, but + ## the call at least shouldn’t fail purely because of the proxy parameter.) + }, error = function(e) { + message( + "fetch() with proxy=TRUE failed (likely no real proxy or other net issues): ", + e$message) + }) + +} + +## Check that 'git2r_arg_check_proxy' raise error +res <- tools::assertError( + .Call( + git2r:::git2r_remote_fetch, + repo_1, + "origin", + NULL, # credentials (valid or NULL) + "fetch", # msg + FALSE, # verbose + NULL, # refspec + 1L # <-- invalid proxy argument (should be NULL, TRUE, or a string) + ) +) +stopifnot( + length(grep("'proxy_val' must be either 1) NULL, or 2) TRUE or 3) a character vector", res[[1]]$message)) > 0 +) + +## Another invalid case: vector of length 2 +res <- tools::assertError( + .Call( + git2r:::git2r_remote_fetch, + repo_1, + "origin", + NULL, + "fetch", + FALSE, + NULL, + c("http://example", "http://example2") # invalid, more than one string + ) +) +stopifnot( + length(grep("'proxy_val' must be either 1) NULL, or 2) TRUE or 3) a character vector", res[[1]]$message)) > 0 +) + ## Check that 'git2r_arg_check_credentials' raise error res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", - 3, "fetch", FALSE, NULL)) + 3, "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", repo_1, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) credentials <- cred_env(c("username", "username"), "password") res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) credentials <- cred_env("username", c("password", "passowrd")) res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) credentials <- cred_user_pass(c("username", "username"), "password") res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) credentials <- cred_user_pass("username", c("password", "passowrd")) res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) credentials <- cred_token(c("GITHUB_PAT", "GITHUB_PAT")) res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) @@ -121,7 +172,7 @@ credentials <- structure(list(publickey = c("id_rsa.pub", "id_rsa.pub"), class = "cred_ssh_key") res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) @@ -131,7 +182,7 @@ credentials <- structure(list(publickey = "id_rsa.pub", class = "cred_ssh_key") res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) @@ -141,7 +192,7 @@ credentials <- structure(list(publickey = "id_rsa.pub", class = "cred_ssh_key") res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) @@ -151,7 +202,7 @@ credentials <- structure(list(publickey = "id_rsa.pub", class = "cred_ssh_key") res <- tools::assertError( .Call(git2r:::git2r_remote_fetch, repo_1, "origin", credentials, - "fetch", FALSE, NULL)) + "fetch", FALSE, NULL, NULL)) stopifnot(length(grep("'credentials' must be an S3 class with credentials", res[[1]]$message)) > 0) diff --git a/tests/push.R b/tests/push.R index d7996265e..62fdd0cdc 100644 --- a/tests/push.R +++ b/tests/push.R @@ -113,6 +113,19 @@ stopifnot(is.null(branch_get_upstream(repository_head(repo)))) push(repo, "origin", paste0("refs/heads/", branch_name), set_upstream = TRUE) stopifnot(!is.null(branch_get_upstream(repository_head(repo)))) +if (identical(Sys.getenv("NOT_CRAN"), "true")) { + ## Attempt to push with proxy=TRUE. If no actual proxy is configured, it may + ## still succeed for local operations, or fail if there's a real network call. + ## We'll wrap it in a tryCatch to avoid a hard failure. + tryCatch({ + push(repo, "origin", paste0("refs/heads/", branch_name), proxy = TRUE) + message("push() with proxy=TRUE succeeded (or no-op if local).") + }, error = function(e) { + message("push() with proxy=TRUE gave an error (likely no real proxy set): ", + e$message) + }) +} + ## Cleanup unlink(path_bare, recursive = TRUE) unlink(path_repo, recursive = TRUE) diff --git a/tests/remotes.R b/tests/remotes.R index 3f79e0ce3..b5e776325 100644 --- a/tests/remotes.R +++ b/tests/remotes.R @@ -66,13 +66,38 @@ stopifnot(identical(remotes(repo), character(0))) if (identical(Sys.getenv("NOT_CRAN"), "true")) { if (isTRUE(libgit2_features()$https)) { + ## Standard test for remote_ls refs <- remote_ls("https://github.com/ropensci/git2r") stopifnot(length(refs) > 0) stopifnot(names(refs) > 0) stopifnot(any(names(refs) == "HEAD")) + + ## -- PROXY TESTS -- + ## Proxy = TRUE => automatic detection. We expect no error. + if (nzchar(Sys.getenv("http_proxy")) || nzchar(Sys.getenv("HTTP_PROXY")) || + nzchar(Sys.getenv("https_proxy")) || nzchar(Sys.getenv("HTTPS_PROXY"))) { + ## If a proxy is actually set, we can test the code path that picks it up. + refs_proxy_auto <- remote_ls("https://github.com/ropensci/git2r", proxy = TRUE) + stopifnot(length(refs_proxy_auto) > 0) + stopifnot(any(names(refs_proxy_auto) == "HEAD")) + } + + ## Proxy = "string" => a manual proxy URL. This will fail with a fake URL. + test_proxy_url <- Sys.getenv("TEST_PROXY_URL", "http://fake-proxy.example.com:8080") + message("Testing remote_ls with proxy='", test_proxy_url, "'") + tryCatch( + { + refs_proxy_str <- remote_ls("https://github.com/ropensci/git2r", proxy = test_proxy_url) + stopifnot(length(refs_proxy_str) >= 0) # minimal check + }, + error = function(e) { + message("As expected, connecting via a fake/invalid proxy might fail: ", e$message) + } + ) } } + # an invalid URL should throw an error tools::assertError(remote_ls("bad"))