Skip to content

Commit 334beab

Browse files
committed
Add support for a few more remote operations
* Detached remote * Read remote reference advertisement list * Read remote default branch
1 parent 0450c1e commit 334beab

File tree

5 files changed

+159
-1
lines changed

5 files changed

+159
-1
lines changed

stdlib/LibGit2/src/LibGit2.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,44 @@ function clone(repo_url::AbstractString, repo_path::AbstractString;
594594
return repo
595595
end
596596

597+
"""
598+
connect(rmt::GitRemote, direction::Consts.GIT_DIRECTION; kwargs...)
599+
600+
Open a connection to a remote. `direction` can be either `DIRECTION_FETCH`
601+
or `DIRECTION_PUSH`.
602+
603+
The keyword arguments are:
604+
* `credentials::Creds=nothing`: provides credentials and/or settings when authenticating
605+
against a private repository.
606+
* `callbacks::Callbacks=Callbacks()`: user provided callbacks and payloads.
607+
"""
608+
function connect(rmt::GitRemote, direction::Consts.GIT_DIRECTION;
609+
credentials::Creds=nothing,
610+
callbacks::Callbacks=Callbacks())
611+
cred_payload = reset!(CredentialPayload(credentials))
612+
if !haskey(callbacks, :credentials)
613+
callbacks[:credentials] = (credentials_cb(), cred_payload)
614+
elseif haskey(callbacks, :credentials) && credentials !== nothing
615+
throw(ArgumentError(string(
616+
"Unable to both use the provided `credentials` as a payload when the ",
617+
"`callbacks` also contain a credentials payload.")))
618+
end
619+
620+
remote_callbacks = RemoteCallbacks(callbacks)
621+
try
622+
connect(rmt, direction, remote_callbacks)
623+
catch err
624+
if isa(err, GitError) && err.code === Error.EAUTH
625+
reject(cred_payload)
626+
else
627+
Base.shred!(cred_payload)
628+
end
629+
rethrow()
630+
end
631+
approve(cred_payload)
632+
return rmt
633+
end
634+
597635
""" git reset [<committish>] [--] <pathspecs>... """
598636
function reset!(repo::GitRepo, committish::AbstractString, pathspecs::AbstractString...)
599637
obj = GitObject(repo, isempty(committish) ? Consts.HEAD_FILE : committish)

stdlib/LibGit2/src/consts.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,9 @@ end
508508
_OID_DEFAULT = 0,
509509
OID_SHA1 = 1)
510510

511+
# Direction of the connection.
512+
@enum(GIT_DIRECTION,
513+
DIRECTION_FETCH = 0,
514+
DIRECTION_PUSH = 1)
515+
511516
end

stdlib/LibGit2/src/remote.jl

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ function GitRemoteAnon(repo::GitRepo, url::AbstractString)
6363
return GitRemote(repo, rmt_ptr_ptr[])
6464
end
6565

66+
"""
67+
GitRemoteDetached(url::AbstractString) -> GitRemote
68+
69+
Create a remote without a connected local repo.
70+
"""
71+
function GitRemoteDetached(url::AbstractString)
72+
ensure_initialized()
73+
rmt_ptr_ptr = Ref{Ptr{Cvoid}}(C_NULL)
74+
@check ccall((:git_remote_create_detached, :libgit2), Cint,
75+
(Ptr{Ptr{Cvoid}}, Cstring), rmt_ptr_ptr, url)
76+
return GitRemote(rmt_ptr_ptr[])
77+
end
78+
6679
"""
6780
lookup_remote(repo::GitRepo, remote_name::AbstractString) -> Union{GitRemote, Nothing}
6881
@@ -414,3 +427,65 @@ function set_remote_url(path::AbstractString, remote_name::AbstractString, url::
414427
set_remote_url(repo, remote_name, url)
415428
end
416429
end
430+
431+
function connect(rmt::GitRemote, direction::Consts.GIT_DIRECTION,
432+
callbacks::RemoteCallbacks)
433+
@check ccall((:git_remote_connect, :libgit2),
434+
Cint, (Ptr{Cvoid}, Cint, Ref{RemoteCallbacks}, Ptr{Cvoid}, Ptr{Cvoid}),
435+
rmt.ptr, direction, callbacks, C_NULL, C_NULL)
436+
return rmt
437+
end
438+
439+
"""
440+
connected(rmt::GitRemote)
441+
442+
Check whether the remote is connected
443+
"""
444+
function connected(rmt::GitRemote)
445+
return ccall((:git_remote_connected, :libgit2), Cint, (Ptr{Cvoid},), rmt.ptr) != 0
446+
end
447+
448+
"""
449+
disconnect(rmt::GitRemote)
450+
451+
Close the connection to the remote.
452+
"""
453+
function disconnect(rmt::GitRemote)
454+
@check ccall((:git_remote_disconnect, :libgit2), Cint, (Ptr{Cvoid},), rmt.ptr)
455+
return
456+
end
457+
458+
"""
459+
default_branch(rmt::GitRemote)
460+
461+
Retrieve the name of the remote's default branch.
462+
463+
This function must only be called after connecting (See [`connect`](@ref)).
464+
"""
465+
function default_branch(rmt::GitRemote)
466+
buf_ref = Ref(Buffer())
467+
@check ccall((:git_remote_default_branch, :libgit2), Cint,
468+
(Ptr{Buffer}, Ptr{Cvoid}), buf_ref, rmt.ptr)
469+
buf = buf_ref[]
470+
str = unsafe_string(buf.ptr, buf.size)
471+
free(buf_ref)
472+
return str
473+
end
474+
475+
"""
476+
ls(rmt::GitRemote) -> Vector{GitRemoteHead}
477+
478+
Get the remote repository's reference advertisement list.
479+
480+
This function must only be called after connecting (See [`connect`](@ref)).
481+
"""
482+
function ls(rmt::GitRemote)
483+
nheads = Ref{Csize_t}()
484+
head_refs = Ref{Ptr{Ptr{_GitRemoteHead}}}()
485+
@check ccall((:git_remote_ls, :libgit2), Cint,
486+
(Ptr{Ptr{Ptr{_GitRemoteHead}}}, Ptr{Csize_t}, Ptr{Cvoid}),
487+
head_refs, nheads, rmt.ptr)
488+
head_ptr = head_refs[]
489+
return [GitRemoteHead(unsafe_load(unsafe_load(head_ptr, i)))
490+
for i in 1:nheads[]]
491+
end

stdlib/LibGit2/src/types.jl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ for (typ, owntyp, sup, cname) in Tuple{Symbol,Any,Symbol,Symbol}[
10101010
(:GitRepo, nothing, :AbstractGitObject, :git_repository),
10111011
(:GitConfig, :(Union{GitRepo, Nothing}), :AbstractGitObject, :git_config),
10121012
(:GitIndex, :(Union{GitRepo, Nothing}), :AbstractGitObject, :git_index),
1013-
(:GitRemote, :GitRepo, :AbstractGitObject, :git_remote),
1013+
(:GitRemote, :(Union{GitRepo, Nothing}), :AbstractGitObject, :git_remote),
10141014
(:GitRevWalker, :GitRepo, :AbstractGitObject, :git_revwalk),
10151015
(:GitReference, :GitRepo, :AbstractGitObject, :git_reference),
10161016
(:GitDescribeResult, :GitRepo, :AbstractGitObject, :git_describe_result),
@@ -1486,3 +1486,26 @@ end
14861486

14871487
# Useful for functions which can handle various kinds of credentials
14881488
const Creds = Union{CredentialPayload, AbstractCredential, CachedCredentials, Nothing}
1489+
1490+
struct _GitRemoteHead
1491+
available_local::Cint
1492+
oid::GitHash
1493+
loid::GitHash
1494+
name::Cstring
1495+
symref_target::Cstring
1496+
end
1497+
1498+
struct GitRemoteHead
1499+
available_local::Bool
1500+
oid::GitHash
1501+
loid::GitHash
1502+
name::String
1503+
symref_target::Union{Nothing,String}
1504+
function GitRemoteHead(head::_GitRemoteHead)
1505+
name = unsafe_string(head.name)
1506+
symref_target = (head.symref_target != C_NULL ?
1507+
unsafe_string(head.symref_target) : nothing)
1508+
return new(head.available_local != 0,
1509+
head.oid, head.loid, name, symref_target)
1510+
end
1511+
end

stdlib/LibGit2/test/online-tests.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ mktempdir() do dir
9090
end
9191
end
9292

93+
@testset "Remote" begin
94+
repo_url = "https://github.com/JuliaLang/Example.jl"
95+
LibGit2.with(LibGit2.GitRemoteDetached(repo_url)) do remote
96+
@test !LibGit2.connected(remote)
97+
c = LibGit2.CredentialPayload(allow_prompt=false, allow_git_helpers=false)
98+
LibGit2.connect(remote, LibGit2.Consts.DIRECTION_FETCH, credentials=c)
99+
@test LibGit2.connected(remote)
100+
remote_heads = LibGit2.ls(remote)
101+
default_branch = LibGit2.default_branch(remote)
102+
@test !isempty(remote_heads)
103+
@test startswith(default_branch, "refs/heads/")
104+
@test any(head.name == default_branch for head in remote_heads)
105+
LibGit2.disconnect(remote)
106+
@test !LibGit2.connected(remote)
107+
end
108+
end
109+
93110
# needs to be run in separate process so it can re-initialize libgit2
94111
# with a useless self-signed certificate authority root certificate
95112
file = joinpath(@__DIR__, "bad_ca_roots.jl")

0 commit comments

Comments
 (0)