Skip to content

Commit

Permalink
Add support for a few more remote operations
Browse files Browse the repository at this point in the history
* Detached remote
* Read remote reference advertisement list
* Read remote default branch
  • Loading branch information
yuyichao committed Sep 14, 2023
1 parent 2c4c068 commit ce0c49f
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 1 deletion.
38 changes: 38 additions & 0 deletions stdlib/LibGit2/src/LibGit2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,44 @@ function clone(repo_url::AbstractString, repo_path::AbstractString;
return repo
end

"""
connect(rmt::GitRemote, direction::Consts.GIT_DIRECTION; kwargs...)
Open a connection to a remote. `direction` can be either `DIRECTION_FETCH`
or `DIRECTION_PUSH`.
The keyword arguments are:
* `credentials::Creds=nothing`: provides credentials and/or settings when authenticating
against a private repository.
* `callbacks::Callbacks=Callbacks()`: user provided callbacks and payloads.
"""
function connect(rmt::GitRemote, direction::Consts.GIT_DIRECTION;
credentials::Creds=nothing,
callbacks::Callbacks=Callbacks())
cred_payload = reset!(CredentialPayload(credentials))
if !haskey(callbacks, :credentials)
callbacks[:credentials] = (credentials_cb(), cred_payload)
elseif haskey(callbacks, :credentials) && credentials !== nothing
throw(ArgumentError(string(
"Unable to both use the provided `credentials` as a payload when the ",
"`callbacks` also contain a credentials payload.")))
end

remote_callbacks = RemoteCallbacks(callbacks)
try
connect(rmt, direction, remote_callbacks)
catch err
if isa(err, GitError) && err.code === Error.EAUTH
reject(cred_payload)
else
Base.shred!(cred_payload)
end
rethrow()
end
approve(cred_payload)
return rmt
end

""" git reset [<committish>] [--] <pathspecs>... """
function reset!(repo::GitRepo, committish::AbstractString, pathspecs::AbstractString...)
obj = GitObject(repo, isempty(committish) ? Consts.HEAD_FILE : committish)
Expand Down
5 changes: 5 additions & 0 deletions stdlib/LibGit2/src/consts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -507,4 +507,9 @@ end
OID_DEFAULT = 0,
OID_SHA1 = 1)

# Direction of the connection.
@enum(GIT_DIRECTION,
DIRECTION_FETCH = 0,
DIRECTION_PUSH = 1)

end
75 changes: 75 additions & 0 deletions stdlib/LibGit2/src/remote.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ function GitRemoteAnon(repo::GitRepo, url::AbstractString)
return GitRemote(repo, rmt_ptr_ptr[])
end

"""
GitRemoteDetached(url::AbstractString) -> GitRemote
Create a remote without a connected local repo.
"""
function GitRemoteDetached(url::AbstractString)
ensure_initialized()
rmt_ptr_ptr = Ref{Ptr{Cvoid}}(C_NULL)
@check ccall((:git_remote_create_detached, libgit2), Cint,
(Ptr{Ptr{Cvoid}}, Cstring), rmt_ptr_ptr, url)
return GitRemote(rmt_ptr_ptr[])
end

"""
lookup_remote(repo::GitRepo, remote_name::AbstractString) -> Union{GitRemote, Nothing}
Expand Down Expand Up @@ -414,3 +427,65 @@ function set_remote_url(path::AbstractString, remote_name::AbstractString, url::
set_remote_url(repo, remote_name, url)
end
end

function connect(rmt::GitRemote, direction::Consts.GIT_DIRECTION,
callbacks::RemoteCallbacks)
@check ccall((:git_remote_connect, libgit2),
Cint, (Ptr{Cvoid}, Cint, Ref{RemoteCallbacks}, Ptr{Cvoid}, Ptr{Cvoid}),
rmt.ptr, direction, callbacks, C_NULL, C_NULL)
return rmt
end

"""
connected(rmt::GitRemote)
Check whether the remote is connected
"""
function connected(rmt::GitRemote)
return ccall((:git_remote_connected, libgit2), Cint, (Ptr{Cvoid},), rmt.ptr) != 0
end

"""
disconnect(rmt::GitRemote)
Close the connection to the remote.
"""
function disconnect(rmt::GitRemote)
@check ccall((:git_remote_disconnect, libgit2), Cint, (Ptr{Cvoid},), rmt.ptr)
return
end

"""
default_branch(rmt::GitRemote)
Retrieve the name of the remote's default branch.
This function must only be called after connecting (See [`connect`](@ref)).
"""
function default_branch(rmt::GitRemote)
buf_ref = Ref(Buffer())
@check ccall((:git_remote_default_branch, libgit2), Cint,
(Ptr{Buffer}, Ptr{Cvoid}), buf_ref, rmt.ptr)
buf = buf_ref[]
str = unsafe_string(buf.ptr, buf.size)
free(buf_ref)
return str
end

"""
ls(rmt::GitRemote) -> Vector{GitRemoteHead}
Get the remote repository's reference advertisement list.
This function must only be called after connecting (See [`connect`](@ref)).
"""
function ls(rmt::GitRemote)
nheads = Ref{Csize_t}()
head_refs = Ref{Ptr{Ptr{_GitRemoteHead}}}()
@check ccall((:git_remote_ls, libgit2), Cint,
(Ptr{Ptr{Ptr{_GitRemoteHead}}}, Ptr{Csize_t}, Ptr{Cvoid}),
head_refs, nheads, rmt.ptr)
head_ptr = head_refs[]
return [GitRemoteHead(unsafe_load(unsafe_load(head_ptr, i)))
for i in 1:nheads[]]
end
25 changes: 24 additions & 1 deletion stdlib/LibGit2/src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ for (typ, owntyp, sup, cname) in Tuple{Symbol,Any,Symbol,Symbol}[
(:GitRepo, nothing, :AbstractGitObject, :git_repository),
(:GitConfig, :(Union{GitRepo, Nothing}), :AbstractGitObject, :git_config),
(:GitIndex, :(Union{GitRepo, Nothing}), :AbstractGitObject, :git_index),
(:GitRemote, :GitRepo, :AbstractGitObject, :git_remote),
(:GitRemote, :(Union{GitRepo, Nothing}), :AbstractGitObject, :git_remote),
(:GitRevWalker, :GitRepo, :AbstractGitObject, :git_revwalk),
(:GitReference, :GitRepo, :AbstractGitObject, :git_reference),
(:GitDescribeResult, :GitRepo, :AbstractGitObject, :git_describe_result),
Expand Down Expand Up @@ -1486,3 +1486,26 @@ end

# Useful for functions which can handle various kinds of credentials
const Creds = Union{CredentialPayload, AbstractCredential, CachedCredentials, Nothing}

struct _GitRemoteHead
available_local::Cint
oid::GitHash
loid::GitHash
name::Cstring
symref_target::Cstring
end

struct GitRemoteHead
available_local::Bool
oid::GitHash
loid::GitHash
name::String
symref_target::Union{Nothing,String}
function GitRemoteHead(head::_GitRemoteHead)
name = unsafe_string(head.name)
symref_target = (head.symref_target != C_NULL ?
unsafe_string(head.symref_target) : nothing)
return new(head.available_local != 0,
head.oid, head.loid, name, symref_target)
end
end
17 changes: 17 additions & 0 deletions stdlib/LibGit2/test/online-tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,23 @@ mktempdir() do dir
end
end

@testset "Remote" begin
repo_url = "https://github.com/JuliaLang/Example.jl"
LibGit2.with(LibGit2.GitRemoteDetached(repo_url)) do remote
@test !LibGit2.connected(remote)
c = LibGit2.CredentialPayload(allow_prompt=false, allow_git_helpers=false)
LibGit2.connect(remote, LibGit2.Consts.DIRECTION_FETCH, credentials=c)
@test LibGit2.connected(remote)
remote_heads = LibGit2.ls(remote)
default_branch = LibGit2.default_branch(remote)
@test !isempty(remote_heads)
@test startswith(default_branch, "refs/heads/")
@test any(head.name == default_branch for head in remote_heads)
LibGit2.disconnect(remote)
@test !LibGit2.connected(remote)
end
end

# needs to be run in separate process so it can re-initialize libgit2
# with a useless self-signed certificate authority root certificate
file = joinpath(@__DIR__, "bad_ca_roots.jl")
Expand Down

0 comments on commit ce0c49f

Please sign in to comment.