Add: julia-0.6.2

Former-commit-id: ccc667cf67d569f3fb3df39aa57c2134755a7551
This commit is contained in:
2018-02-10 10:27:19 -07:00
parent 94220957d7
commit 019f8e3064
723 changed files with 276164 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function Base.length(blob::GitBlob)
return ccall((:git_blob_rawsize, :libgit2), Int64, (Ptr{Void},), blob.ptr)
end
function rawcontent(blob::GitBlob)
ptr = ccall((:git_blob_rawcontent, :libgit2), Ptr{UInt8}, (Ptr{Void},), blob.ptr)
copy(unsafe_wrap(Array, ptr, (length(blob),), false))
end
"""
content(blob::GitBlob)
Fetch the contents of the `GitBlob` `blob`. If the `blob` contains
binary data (which can be determined using [`isbinary`](@ref)),
throw an error. Otherwise, return a `String` containing the contents
of the `blob`.
"""
function content(blob::GitBlob)
s = String(rawcontent(blob))
isvalid(s) || error("Blob does not contain valid UTF-8 data")
return s
end
"""
Use a heuristic to guess if a file is binary: searching for NULL bytes and
looking for a reasonable ratio of printable to non-printable characters among
the first 8000 bytes.
"""
function isbinary(blob::GitBlob)
bin_flag = ccall((:git_blob_is_binary, :libgit2), Cint, (Ptr{Void},), blob.ptr)
return bin_flag == 1
end
"""
LibGit2.addblob!(repo::GitRepo, path::AbstractString)
Reads the file at `path` and adds it to the object database of `repo` as a loose blob.
Returns the `GitHash` of the resulting blob.
# Example
```julia
hash_str = hex(commit_oid)
blob_file = joinpath(repo_path, ".git", "objects", hash_str[1:2], hash_str[3:end])
id = LibGit2.addblob!(repo, blob_file)
```
"""
function addblob!(repo::GitRepo, path::AbstractString)
id_ref = Ref{GitHash}()
@check ccall((:git_blob_create_fromdisk, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Void}, Cstring),
id_ref, repo.ptr, path)
return id_ref[]
end
function Base.show(io::IO, blob::GitBlob)
if !isbinary(blob)
conts = split(content(blob), "\n")
showlen = max(length(conts), 3)
println(io, "GitBlob:\nBlob id: ", GitHash(blob), "\nContents:")
for i in 1:showlen
println(io, conts[i])
end
else
println(io, "GitBlob:\nBlob id: ", GitHash(blob), "\nContents are binary.")
end
end

View File

@@ -0,0 +1,265 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""Mirror callback function
Function sets `+refs/*:refs/*` refspecs and `mirror` flag for remote reference.
"""
function mirror_callback(remote::Ptr{Ptr{Void}}, repo_ptr::Ptr{Void},
name::Cstring, url::Cstring, payload::Ptr{Void})
# Create the remote with a mirroring url
fetch_spec = "+refs/*:refs/*"
err = ccall((:git_remote_create_with_fetchspec, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring, Cstring, Cstring),
remote, repo_ptr, name, url, fetch_spec)
err != 0 && return Cint(err)
# And set the configuration option to true for the push command
config = GitConfig(GitRepo(repo_ptr,false))
name_str = unsafe_string(name)
err= try set!(config, "remote.$name_str.mirror", true)
catch -1
finally close(config)
end
err != 0 && return Cint(err)
return Cint(0)
end
function authenticate_ssh(creds::SSHCredentials, libgit2credptr::Ptr{Ptr{Void}},
username_ptr, schema, host)
isusedcreds = checkused!(creds)
# Note: The same SSHCredentials can be used to authenticate separate requests using the
# same credential cache. e.g. using Pkg.update when there are two private packages.
errcls, errmsg = Error.last_error()
if errcls != Error.None
# Check if we used ssh-agent
if creds.usesshagent == "U"
creds.usesshagent = "E" # reported ssh-agent error, disables ssh agent use for the future
end
end
# first try ssh-agent if credentials support its usage
if creds.usesshagent == "Y" || creds.usesshagent == "U"
err = ccall((:git_cred_ssh_key_from_agent, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring), libgit2credptr, username_ptr)
creds.usesshagent = "U" # used ssh-agent only one time
err == 0 && return Cint(0)
end
if creds.prompt_if_incorrect
# if username is not provided, then prompt for it
username = if username_ptr == Cstring(C_NULL)
uname = creds.user # check if credentials were already used
!isusedcreds ? uname : prompt("Username for '$schema$host'", default=uname)
else
unsafe_string(username_ptr)
end
isempty(username) && return Cint(Error.EAUTH)
# For SSH we need a private key location
privatekey = if haskey(ENV,"SSH_KEY_PATH")
ENV["SSH_KEY_PATH"]
else
keydefpath = creds.prvkey # check if credentials were already used
if isempty(keydefpath) || isusedcreds
defaultkeydefpath = joinpath(homedir(),".ssh","id_rsa")
if isempty(keydefpath) && isfile(defaultkeydefpath)
keydefpath = defaultkeydefpath
else
keydefpath =
prompt("Private key location for '$schema$username@$host'", default=keydefpath)
end
end
keydefpath
end
# If the private key changed, invalidate the cached public key
(privatekey != creds.prvkey) &&
(creds.pubkey = "")
# For SSH we need a public key location, look for environment vars SSH_* as well
publickey = if haskey(ENV,"SSH_PUB_KEY_PATH")
ENV["SSH_PUB_KEY_PATH"]
else
keydefpath = creds.pubkey # check if credentials were already used
if isempty(keydefpath) || isusedcreds
if isempty(keydefpath)
keydefpath = privatekey*".pub"
end
if !isfile(keydefpath)
prompt("Public key location for '$schema$username@$host'", default=keydefpath)
end
end
keydefpath
end
passphrase_required = true
if !isfile(privatekey)
warn("Private key not found")
else
# In encrypted private keys, the second line is "Proc-Type: 4,ENCRYPTED"
open(privatekey) do f
readline(f)
passphrase_required = readline(f) == "Proc-Type: 4,ENCRYPTED"
end
end
passphrase = if haskey(ENV,"SSH_KEY_PASS")
ENV["SSH_KEY_PASS"]
else
passdef = creds.pass # check if credentials were already used
if passphrase_required && (isempty(passdef) || isusedcreds)
if is_windows()
passdef = Base.winprompt(
"Your SSH Key requires a password, please enter it now:",
"Passphrase required", privatekey; prompt_username = false)
isnull(passdef) && return Cint(Error.EAUTH)
passdef = Base.get(passdef)[2]
else
passdef = prompt("Passphrase for $privatekey", password=true)
end
end
passdef
end
((creds.user != username) || (creds.pass != passphrase) ||
(creds.prvkey != privatekey) || (creds.pubkey != publickey)) && reset!(creds)
creds.user = username # save credentials
creds.prvkey = privatekey # save credentials
creds.pubkey = publickey # save credentials
creds.pass = passphrase
else
isusedcreds && return Cint(Error.EAUTH)
end
return ccall((:git_cred_ssh_key_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cstring, Cstring, Cstring),
libgit2credptr, creds.user, creds.pubkey, creds.prvkey, creds.pass)
end
function authenticate_userpass(creds::UserPasswordCredentials, libgit2credptr::Ptr{Ptr{Void}},
schema, host, urlusername)
isusedcreds = checkused!(creds)
if creds.prompt_if_incorrect
username = creds.user
userpass = creds.pass
if is_windows()
if isempty(username) || isempty(userpass) || isusedcreds
res = Base.winprompt("Please enter your credentials for '$schema$host'", "Credentials required",
isempty(username) ? urlusername : username; prompt_username = true)
isnull(res) && return Cint(Error.EAUTH)
username, userpass = Base.get(res)
end
elseif isusedcreds
username = prompt("Username for '$schema$host'",
default=isempty(username) ? urlusername : username)
userpass = prompt("Password for '$schema$username@$host'", password=true)
end
((creds.user != username) || (creds.pass != userpass)) && reset!(creds)
creds.user = username # save credentials
creds.pass = userpass # save credentials
isempty(username) && isempty(userpass) && return Cint(Error.EAUTH)
else
isusedcreds && return Cint(Error.EAUTH)
end
return ccall((:git_cred_userpass_plaintext_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cstring),
libgit2credptr, creds.user, creds.pass)
end
"""Credentials callback function
Function provides different credential acquisition functionality w.r.t. a connection protocol.
If a payload is provided then `payload_ptr` should contain a `LibGit2.AbstractCredentials` object.
For `LibGit2.Consts.CREDTYPE_USERPASS_PLAINTEXT` type, if the payload contains fields:
`user` & `pass`, they are used to create authentication credentials.
Empty `user` name and `pass`word trigger an authentication error.
For `LibGit2.Consts.CREDTYPE_SSH_KEY` type, if the payload contains fields:
`user`, `prvkey`, `pubkey` & `pass`, they are used to create authentication credentials.
Empty `user` name triggers an authentication error.
Credentials are checked in the following order (if supported):
- ssh key pair (`ssh-agent` if specified in payload's `usesshagent` field)
- plain text
**Note**: Due to the specifics of the `libgit2` authentication procedure, when
authentication fails, this function is called again without any indication whether
authentication was successful or not. To avoid an infinite loop from repeatedly
using the same faulty credentials, the `checkused!` function can be called. This
function returns `true` if the credentials were used.
Using credentials triggers a user prompt for (re)entering required information.
`UserPasswordCredentials` and `CachedCredentials` are implemented using a call
counting strategy that prevents repeated usage of faulty credentials.
"""
function credentials_callback(libgit2credptr::Ptr{Ptr{Void}}, url_ptr::Cstring,
username_ptr::Cstring,
allowed_types::Cuint, payload_ptr::Ptr{Void})
err = Cint(0)
url = unsafe_string(url_ptr)
# parse url for schema and host
urlparts = match(URL_REGEX, url)
schema = urlparts[:scheme] === nothing ? "" : urlparts[:scheme] * "://"
urlusername = urlparts[:user] === nothing ? "" : urlparts[:user]
host = urlparts[:host]
# get credentials object from payload pointer
@assert payload_ptr != C_NULL
creds = unsafe_pointer_to_objref(payload_ptr)
explicit = !isnull(creds[]) && !isa(Base.get(creds[]), CachedCredentials)
# use ssh key or ssh-agent
if isset(allowed_types, Cuint(Consts.CREDTYPE_SSH_KEY))
sshcreds = get_creds!(creds, "ssh://$host", reset!(SSHCredentials(true), -1))
if isa(sshcreds, SSHCredentials)
err = authenticate_ssh(sshcreds, libgit2credptr, username_ptr, schema, host)
err == 0 && return err
end
end
if isset(allowed_types, Cuint(Consts.CREDTYPE_USERPASS_PLAINTEXT))
defaultcreds = reset!(UserPasswordCredentials(true), -1)
credid = "$schema$host"
upcreds = get_creds!(creds, credid, defaultcreds)
# If there were stored SSH credentials, but we ended up here that must
# mean that something went wrong. Replace the SSH credentials by user/pass
# credentials
if !isa(upcreds, UserPasswordCredentials)
upcreds = defaultcreds
isa(Base.get(creds[]), CachedCredentials) && (Base.get(creds[]).creds[credid] = upcreds)
end
return authenticate_userpass(upcreds, libgit2credptr, schema, host, urlusername)
end
# No authentication method we support succeeded. The most likely cause is
# that explicit credentials were passed in, but said credentials are incompatible
# with the remote host.
if err == 0
if explicit
warn("The explicitly provided credentials were incompatible with " *
"the server's supported authentication methods")
end
err = Cint(Error.EAUTH)
end
return err
end
function fetchhead_foreach_callback(ref_name::Cstring, remote_url::Cstring,
oid_ptr::Ptr{GitHash}, is_merge::Cuint, payload::Ptr{Void})
fhead_vec = unsafe_pointer_to_objref(payload)::Vector{FetchHead}
push!(fhead_vec, FetchHead(unsafe_string(ref_name), unsafe_string(remote_url),
unsafe_load(oid_ptr), is_merge == 1))
return Cint(0)
end
"C function pointer for `mirror_callback`"
mirror_cb() = cfunction(mirror_callback, Cint, Tuple{Ptr{Ptr{Void}}, Ptr{Void}, Cstring, Cstring, Ptr{Void}})
"C function pointer for `credentials_callback`"
credentials_cb() = cfunction(credentials_callback, Cint, Tuple{Ptr{Ptr{Void}}, Cstring, Cstring, Cuint, Ptr{Void}})
"C function pointer for `fetchhead_foreach_callback`"
fetchhead_foreach_cb() = cfunction(fetchhead_foreach_callback, Cint, Tuple{Cstring, Cstring, Ptr{GitHash}, Cuint, Ptr{Void}})

View File

@@ -0,0 +1,95 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function message(c::GitCommit, raw::Bool=false)
local msg_ptr::Cstring
msg_ptr = raw? ccall((:git_commit_message_raw, :libgit2), Cstring, (Ptr{Void},), c.ptr) :
ccall((:git_commit_message, :libgit2), Cstring, (Ptr{Void},), c.ptr)
if msg_ptr == C_NULL
return nothing
end
return unsafe_string(msg_ptr)
end
function author(c::GitCommit)
ptr = ccall((:git_commit_author, :libgit2), Ptr{SignatureStruct}, (Ptr{Void},), c.ptr)
@assert ptr != C_NULL
return Signature(ptr)
end
function committer(c::GitCommit)
ptr = ccall((:git_commit_committer, :libgit2), Ptr{SignatureStruct}, (Ptr{Void},), c.ptr)
@assert ptr != C_NULL
return Signature(ptr)
end
function Base.show(io::IO, c::GitCommit)
authstr = sprint(show, author(c))
cmtrstr = sprint(show, committer(c))
print(io, "Git Commit:\nCommit Author: $authstr\nCommitter: $cmtrstr\nSHA: $(GitHash(c))\nMessage:\n$(message(c))")
end
""" Wrapper around `git_commit_create` """
function commit(repo::GitRepo,
refname::AbstractString,
msg::AbstractString,
author::GitSignature,
committer::GitSignature,
tree::GitTree,
parents::GitCommit...)
commit_id_ptr = Ref(GitHash())
nparents = length(parents)
parentptrs = Ptr{Void}[c.ptr for c in parents]
@check ccall((:git_commit_create, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Void}, Ptr{UInt8},
Ptr{SignatureStruct}, Ptr{SignatureStruct},
Ptr{UInt8}, Ptr{UInt8}, Ptr{Void},
Csize_t, Ptr{Ptr{Void}}),
commit_id_ptr, repo.ptr, isempty(refname) ? C_NULL : refname,
author.ptr, committer.ptr,
C_NULL, msg, tree.ptr,
nparents, nparents > 0 ? parentptrs : C_NULL)
return commit_id_ptr[]
end
"""Commit changes to repository"""
function commit(repo::GitRepo, msg::AbstractString;
refname::AbstractString=Consts.HEAD_FILE,
author::Signature = Signature(repo),
committer::Signature = Signature(repo),
tree_id::GitHash = GitHash(),
parent_ids::Vector{GitHash}=GitHash[])
# Retrieve tree identifier
if iszero(tree_id)
tree_id = with(GitIndex, repo) do idx; write_tree!(idx) end
end
# Retrieve parents from HEAD
if isempty(parent_ids)
try # if throws then HEAD not found -> empty repo
push!(parent_ids, GitHash(repo, refname))
end
end
# return commit id
commit_id = GitHash()
# get necessary objects
tree = GitTree(repo, tree_id)
auth_sig = convert(GitSignature, author)
comm_sig = convert(GitSignature, committer)
parents = GitCommit[]
try
for id in parent_ids
push!(parents, GitCommit(repo, id))
end
commit_id = commit(repo, refname, msg, auth_sig, comm_sig, tree, parents...)
finally
for parent in parents
close(parent)
end
close(tree)
close(auth_sig)
close(comm_sig)
end
return commit_id
end

View File

@@ -0,0 +1,130 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function GitConfig(path::AbstractString,
level::Consts.GIT_CONFIG = Consts.CONFIG_LEVEL_APP,
force::Bool=false)
# create new config object
cfg_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_config_new, :libgit2), Cint, (Ptr{Ptr{Void}},), cfg_ptr_ptr)
cfg = GitConfig(cfg_ptr_ptr[])
try
addfile(cfg, path, level, force)
catch ex
close(cfg)
rethrow(ex)
end
return cfg
end
function GitConfig(repo::GitRepo)
cfg_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_repository_config, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}), cfg_ptr_ptr, repo.ptr)
return GitConfig(repo, cfg_ptr_ptr[])
end
function GitConfig(level::Consts.GIT_CONFIG = Consts.CONFIG_LEVEL_DEFAULT)
cfg_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_config_open_default, :libgit2), Cint,
(Ptr{Ptr{Void}},), cfg_ptr_ptr)
cfg = GitConfig(cfg_ptr_ptr[])
if level != Consts.CONFIG_LEVEL_DEFAULT
glb_cfg_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
tmpcfg = cfg
try
@check ccall((:git_config_open_level, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cint),
glb_cfg_ptr_ptr, cfg.ptr, Cint(level))
cfg = GitConfig(glb_cfg_ptr_ptr[])
finally
close(tmpcfg)
end
end
return cfg
end
function addfile(cfg::GitConfig, path::AbstractString,
level::Consts.GIT_CONFIG = Consts.CONFIG_LEVEL_APP,
force::Bool=false)
@check ccall((:git_config_add_file_ondisk, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cint, Cint),
cfg.ptr, path, Cint(level), Cint(force))
end
function get(::Type{<:AbstractString}, c::GitConfig, name::AbstractString)
buf_ref = Ref(Buffer())
@check ccall((:git_config_get_string_buf, :libgit2), Cint,
(Ptr{Buffer}, Ptr{Void}, Cstring), buf_ref, c.ptr, name)
buf = buf_ref[]
str = unsafe_string(buf.ptr, buf.size)
free(buf_ref)
str
end
function get(::Type{Bool}, c::GitConfig, name::AbstractString)
val_ptr = Ref(Cint(0))
@check ccall((:git_config_get_bool, :libgit2), Cint,
(Ptr{Cint}, Ptr{Void}, Cstring), val_ptr, c.ptr, name)
return Bool(val_ptr[])
end
function get(::Type{Int32}, c::GitConfig, name::AbstractString)
val_ptr = Ref(Cint(0))
@check ccall((:git_config_get_int32, :libgit2), Cint,
(Ptr{Cint}, Ptr{Void}, Cstring), val_ptr, c.ptr, name)
return val_ptr[]
end
function get(::Type{Int64}, c::GitConfig, name::AbstractString)
val_ptr = Ref(Cintmax_t(0))
@check ccall((:git_config_get_int64, :libgit2), Cint,
(Ptr{Cintmax_t}, Ptr{Void}, Cstring), val_ptr, c.ptr, name)
return val_ptr[]
end
function get(c::GitConfig, name::AbstractString, default::T) where T
res = default
try res = get(T,c,name) end
return res
end
function getconfig(r::GitRepo, name::AbstractString, default)
with(GitConfig, r) do cfg
get(cfg, name, default)
end
end
function getconfig(rname::AbstractString, name::AbstractString, default)
with(GitRepo, rname) do r
with(GitConfig, r) do cfg
get(cfg, name, default)
end
end
end
function getconfig(name::AbstractString, default)
with(GitConfig) do cfg
get(cfg, name, default)
end
end
function set!(c::GitConfig, name::AbstractString, value::AbstractString)
@check ccall((:git_config_set_string, :libgit2), Cint,
(Ptr{Void}, Cstring, Cstring), c.ptr, name, value)
end
function set!(c::GitConfig, name::AbstractString, value::Bool)
bval = Int32(value)
@check ccall((:git_config_set_bool, :libgit2), Cint,
(Ptr{Void}, Cstring, Cint), c.ptr, name, bval)
end
function set!(c::GitConfig, name::AbstractString, value::Int32)
@check ccall((:git_config_set_int32, :libgit2), Cint,
(Ptr{Void}, Cstring, Cint), c.ptr, name, value)
end
function set!(c::GitConfig, name::AbstractString, value::Int64)
@check ccall((:git_config_set_int64, :libgit2), Cint,
(Ptr{Void}, Cstring, Cintmax_t), c.ptr, name, value)
end

View File

@@ -0,0 +1,353 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
module Consts
const HEAD_FILE = "HEAD"
const FETCH_HEAD = "FETCH_HEAD"
const REMOTE_ORIGIN = "origin"
# objs
@enum(OBJECT,
OBJ_ANY = -2,
OBJ_BAD = -1,
OBJ_COMMIT = 1,
OBJ_TREE = 2,
OBJ_BLOB = 3,
OBJ_TAG = 4)
#revwalk
const SORT_NONE = Cint(0)
const SORT_TOPOLOGICAL = Cint(1 << 0)
const SORT_TIME = Cint(1 << 1)
const SORT_REVERSE = Cint(1 << 2)
# refs
const REF_INVALID = Cint(0)
const REF_OID = Cint(1)
const REF_SYMBOLIC = Cint(2)
const REF_LISTALL = REF_OID | REF_SYMBOLIC
# checkout
const CHECKOUT_NONE = Cuint(0)
const CHECKOUT_SAFE = Cuint(1 << 0)
const CHECKOUT_FORCE = Cuint(1 << 1)
const CHECKOUT_RECREATE_MISSING = Cuint(1 << 2)
const CHECKOUT_ALLOW_CONFLICTS = Cuint(1 << 4)
const CHECKOUT_REMOVE_UNTRACKED = Cuint(1 << 5)
const CHECKOUT_REMOVE_IGNORED = Cuint(1 << 6)
const CHECKOUT_UPDATE_ONLY = Cuint(1 << 7)
const CHECKOUT_DONT_UPDATE_INDEX = Cuint(1 << 8)
const CHECKOUT_NO_REFRESH = Cuint(1 << 9)
const CHECKOUT_SKIP_UNMERGED = Cuint(1 << 10)
const CHECKOUT_USE_OURS = Cuint(1 << 11)
const CHECKOUT_USE_THEIRS = Cuint(1 << 12)
const CHECKOUT_DISABLE_PATHSPEC_MATCH = Cuint(1 << 13)
const CHECKOUT_SKIP_LOCKED_DIRECTORIES = Cuint(1 << 18)
const CHECKOUT_DONT_OVERWRITE_IGNORED = Cuint(1 << 19)
const CHECKOUT_CONFLICT_STYLE_MERGE = Cuint(1 << 20)
const CHECKOUT_CONFLICT_STYLE_DIFF3 = Cuint(1 << 21)
const CHECKOUT_DONT_REMOVE_EXISTING = Cuint(1 << 22)
const CHECKOUT_UPDATE_SUBMODULES = Cuint(1 << 16)
const CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = Cuint(1 << 17)
const CHECKOUT_NOTIFY_NONE = Cuint(0)
const CHECKOUT_NOTIFY_CONFLICT = Cuint(1 << 0)
const CHECKOUT_NOTIFY_DIRTY = Cuint(1 << 1)
const CHECKOUT_NOTIFY_UPDATED = Cuint(1 << 2)
const CHECKOUT_NOTIFY_UNTRACKED = Cuint(1 << 3)
const CHECKOUT_NOTIFY_IGNORED = Cuint(1 << 4)
const CHECKOUT_NOTIFY_ALL = 0x0FFFF
# diff
const DIFF_OPTIONS_VERSION = Cuint(1)
const DIFF_NORMAL = Cuint(0)
const DIFF_REVERSE = Cuint(1 << 0)
const DIFF_INCLUDE_IGNORED = Cuint(1 << 1)
const DIFF_RECURSE_IGNORED_DIRS = Cuint(1 << 2)
const DIFF_INCLUDE_UNTRACKED = Cuint(1 << 3)
const DIFF_RECURSE_UNTRACKED_DIRS = Cuint(1 << 4)
const DIFF_INCLUDE_UNMODIFIED = Cuint(1 << 5)
const DIFF_INCLUDE_TYPECHANGE = Cuint(1 << 6)
const DIFF_INCLUDE_TYPECHANGE_TREES = Cuint(1 << 7)
const DIFF_IGNORE_FILEMODE = Cuint(1 << 8)
const DIFF_IGNORE_SUBMODULES = Cuint(1 << 9)
const DIFF_IGNORE_CASE = Cuint(1 << 10)
const DIFF_DISABLE_PATHSPEC_MATCH = Cuint(1 << 12)
const DIFF_SKIP_BINARY_CHECK = Cuint(1 << 13)
const DIFF_ENABLE_FAST_UNTRACKED_DIRS = Cuint(1 << 14)
const DIFF_FORCE_TEXT = Cuint(1 << 20)
const DIFF_FORCE_BINARY = Cuint(1 << 21)
const DIFF_IGNORE_WHITESPACE = Cuint(1 << 22)
const DIFF_IGNORE_WHITESPACE_CHANGE = Cuint(1 << 23)
const DIFF_IGNORE_WHITESPACE_EOL = Cuint(1 << 24)
const DIFF_SHOW_UNTRACKED_CONTENT = Cuint(1 << 25)
const DIFF_SHOW_UNMODIFIED = Cuint(1 << 26)
const DIFF_PATIENCE = Cuint(1 << 28)
const DIFF_MINIMAL = Cuint(1 << 29)
const DIFF_FLAG_BINARY = Cuint(1 << 0)
const DIFF_FLAG_NOT_BINARY = Cuint(1 << 1)
const DIFF_FLAG_VALID_OID = Cuint(1 << 2)
const DIFF_FORMAT_PATCH = Cuint(1)
const DIFF_FORMAT_PATCH_HEADER = Cuint(2)
const DIFF_FORMAT_RAW = Cuint(3)
const DIFF_FORMAT_NAME_ONLY = Cuint(4)
const DIFF_FORMAT_NAME_STATUS = Cuint(5)
@enum(DELTA_STATUS, DELTA_UNMODIFIED = Cint(0),
DELTA_ADDED = Cint(1),
DELTA_DELETED = Cint(2),
DELTA_MODIFIED = Cint(3),
DELTA_RENAMED = Cint(4),
DELTA_COPIED = Cint(5),
DELTA_IGNORED = Cint(6),
DELTA_UNTRACKED = Cint(7),
DELTA_TYPECHANGE = Cint(8))
# index
const IDXENTRY_NAMEMASK = (0x0fff)
const IDXENTRY_STAGEMASK = (0x3000)
const IDXENTRY_EXTENDED = (0x4000)
const IDXENTRY_VALID = (0x8000)
const IDXENTRY_STAGESHIFT = Cint(12)
const IDXENTRY_UPDATE = Cint(1 << 0)
const IDXENTRY_REMOVE = Cint(1 << 1)
const IDXENTRY_UPTODATE = Cint(1 << 2)
const IDXENTRY_ADDED = Cint(1 << 3)
const IDXENTRY_HASHED = Cint(1 << 4)
const IDXENTRY_UNHASHED = Cint(1 << 5)
const IDXENTRY_WT_REMOVE = Cint(1 << 6)
const IDXENTRY_CONFLICTED = Cint(1 << 7)
const IDXENTRY_UNPACKED = Cint(1 << 8)
const IDXENTRY_NEW_SKIP_WORKTREE = Cint(1 << 9)
const INDEXCAP_IGNORE_CASE = Cuint(1)
const INDEXCAP_NO_FILEMODE = Cuint(2)
const INDEXCAP_NO_SYMLINKS = Cuint(4)
const INDEXCAP_FROM_OWNER = ~Cuint(0)
const INDEX_ADD_DEFAULT = Cuint(0)
const INDEX_ADD_FORCE = Cuint(1 << 0)
const INDEX_ADD_DISABLE_PATHSPEC_MATCH = Cuint(1 << 1)
const INDEX_ADD_CHECK_PATHSPEC = Cuint(1 << 2)
const INDEX_STAGE_ANY = Cint(-1)
# merge
@enum(GIT_MERGE, MERGE_FIND_RENAMES = 1 << 0,
MERGE_FAIL_ON_CONFLICT = 1 << 1,
MERGE_SKIP_REUC = 1 << 2,
MERGE_NO_RECURSIVE = 1 << 3)
@enum(GIT_MERGE_FILE, MERGE_FILE_DEFAULT = 0, # Defaults
MERGE_FILE_STYLE_MERGE = 1 << 0, # Create standard conflicted merge files
MERGE_FILE_STYLE_DIFF3 = 1 << 1, # Create diff3-style files
MERGE_FILE_SIMPLIFY_ALNUM = 1 << 2, # Condense non-alphanumeric regions for simplified diff file
MERGE_FILE_IGNORE_WHITESPACE = 1 << 3, # Ignore all whitespace
MERGE_FILE_IGNORE_WHITESPACE_CHANGE = 1 << 4, # Ignore changes in amount of whitespace
MERGE_FILE_IGNORE_WHITESPACE_EOL = 1 << 5, # Ignore whitespace at end of line
MERGE_FILE_DIFF_PATIENCE = 1 << 6, # Use the "patience diff" algorithm
MERGE_FILE_DIFF_MINIMAL = 1 << 7) # Take extra time to find minimal diff
@enum(GIT_MERGE_FILE_FAVOR, MERGE_FILE_FAVOR_NORMAL = 0,
MERGE_FILE_FAVOR_OURS = 1,
MERGE_FILE_FAVOR_THEIRS = 2,
MERGE_FILE_FAVOR_UNION = 3)
@enum(GIT_MERGE_PREFERENCE, MERGE_PREFERENCE_NONE = 0,
MERGE_PREFERENCE_NO_FASTFORWARD = 1,
MERGE_PREFERENCE_FASTFORWARD_ONLY = 2)
@enum(GIT_MERGE_ANALYSIS, MERGE_ANALYSIS_NONE = 0,
MERGE_ANALYSIS_NORMAL = 1 << 0,
MERGE_ANALYSIS_UP_TO_DATE = 1 << 1,
MERGE_ANALYSIS_FASTFORWARD = 1 << 2,
MERGE_ANALYSIS_UNBORN = 1 << 3)
# reset
const RESET_SOFT = Cint(1) # Move the head to the given commit
const RESET_MIXED = Cint(2) # SOFT plus reset index to the commit
const RESET_HARD = Cint(3) # MIXED plus changes in working tree discarded
#rebase
@enum(GIT_REBASE_OPERATION, REBASE_OPERATION_PICK = Cint(0),
REBASE_OPERATION_REWORD = Cint(1),
REBASE_OPERATION_EDIT = Cint(2),
REBASE_OPERATION_SQUASH = Cint(3),
REBASE_OPERATION_FIXUP = Cint(4),
REBASE_OPERATION_EXEC = Cint(5))
# fetch_prune
const FETCH_PRUNE_UNSPECIFIED = Cint(0)
const FETCH_PRUNE = Cint(1)
const FETCH_NO_PRUNE = Cint(2)
# remote_autotag
const REMOTE_DOWNLOAD_TAGS_UNSPECIFIED = Cint(0)
const REMOTE_DOWNLOAD_TAGS_AUTO = Cint(1)
const REMOTE_DOWNLOAD_TAGS_NONE = Cint(2)
const REMOTE_DOWNLOAD_TAGS_ALL = Cint(3)
# clone
const CLONE_LOCAL_AUTO = Cint(0)
const CLONE_LOCAL = Cint(1)
const CLONE_NO_LOCAL = Cint(2)
const CLONE_LOCAL_NO_LINKS = Cint(3)
# status
const STATUS_CURRENT = Cuint(0)
const STATUS_INDEX_NEW = Cuint(1 << 0)
const STATUS_INDEX_MODIFIED = Cuint(1 << 1)
const STATUS_INDEX_DELETED = Cuint(1 << 2)
const STATUS_INDEX_RENAMED = Cuint(1 << 3)
const STATUS_INDEX_TYPECHANGE = Cuint(1 << 4)
const STATUS_WT_NEW = Cuint(1 << 7)
const STATUS_WT_MODIFIED = Cuint(1 << 8)
const STATUS_WT_DELETED = Cuint(1 << 9)
const STATUS_WT_TYPECHANGE = Cuint(1 << 10)
const STATUS_WT_RENAMED = Cuint(1 << 11)
const STATUS_WT_UNREADABLE = Cuint(1 << 12)
const STATUS_IGNORED = Cuint(1 << 14)
const STATUS_CONFLICTED = Cuint(1 << 15)
# status show
const STATUS_SHOW_INDEX_AND_WORKDIR = Cint(0)
const STATUS_SHOW_INDEX_ONLY = Cint(1)
const STATUS_SHOW_WORKDIR_ONLY = Cint(2)
# status options
const STATUS_OPT_INCLUDE_UNTRACKED = Cuint(1 << 0)
const STATUS_OPT_INCLUDE_IGNORED = Cuint(1 << 1)
const STATUS_OPT_INCLUDE_UNMODIFIED = Cuint(1 << 2)
const STATUS_OPT_EXCLUDE_SUBMODULES = Cuint(1 << 3)
const STATUS_OPT_RECURSE_UNTRACKED_DIRS = Cuint(1 << 4)
const STATUS_OPT_DISABLE_PATHSPEC_MATCH = Cuint(1 << 5)
const STATUS_OPT_RECURSE_IGNORED_DIRS = Cuint(1 << 6)
const STATUS_OPT_RENAMES_HEAD_TO_INDEX = Cuint(1 << 7)
const STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = Cuint(1 << 8)
const STATUS_OPT_SORT_CASE_SENSITIVELY = Cuint(1 << 9)
const STATUS_OPT_SORT_CASE_INSENSITIVELY = Cuint(1 << 10)
const STATUS_OPT_RENAMES_FROM_REWRITES = Cuint(1 << 11)
const STATUS_OPT_NO_REFRESH = Cuint(1 << 12)
const STATUS_OPT_UPDATE_INDEX = Cuint(1 << 13)
const STATUS_OPT_INCLUDE_UNREADABLE = Cuint(1 << 14)
const STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED = Cuint(1 << 15)
@enum(GIT_SUBMODULE_IGNORE, SUBMODULE_IGNORE_UNSPECIFIED = -1, # use the submodule's configuration
SUBMODULE_IGNORE_NONE = 1, # any change or untracked == dirty
SUBMODULE_IGNORE_UNTRACKED = 2, # dirty if tracked files change
SUBMODULE_IGNORE_DIRTY = 3, # only dirty if HEAD moved
SUBMODULE_IGNORE_ALL = 4) # never dirty
"""
Option flags for `GitRepo`.
* `REPOSITORY_OPEN_NO_SEARCH` - Only open the repository if it can be immediately found in the `path`. Do not walk up from the `path` looking at parent directories.
* `REPOSITORY_OPEN_CROSS_FS` - Unless this flag is set, open will not continue searching across filesystem boundaries. (E.g. Searching in a user's home directory `/home/user/source/` will not return `/.git/` as the found repo if `/` is a different filesystem than `/home`.)
* `REPOSITORY_OPEN_BARE` - Open repository as a bare repo regardless of core.bare config, and defer loading config file for faster setup.
"""
@enum(GIT_REPOSITORY_OPEN, REPOSITORY_OPEN_DEFAULT = 0,
REPOSITORY_OPEN_NO_SEARCH = 1<<0,
REPOSITORY_OPEN_CROSS_FS = 1<<1,
REPOSITORY_OPEN_BARE = 1<<2)
@enum(GIT_BRANCH, BRANCH_LOCAL = 1, BRANCH_REMOTE = 2)
@enum(GIT_FILEMODE, FILEMODE_UNREADABLE = 0o000000,
FILEMODE_TREE = 0o040000,
FILEMODE_BLOB = 0o100644,
FILEMODE_BLOB_EXECUTABLE = 0o100755,
FILEMODE_LINK = 0o120000,
FILEMODE_COMMIT = 0o160000)
@enum(GIT_CREDTYPE, CREDTYPE_USERPASS_PLAINTEXT = Cuint(1 << 0),
CREDTYPE_SSH_KEY = Cuint(1 << 1),
CREDTYPE_SSH_CUSTOM = Cuint(1 << 2),
CREDTYPE_DEFAULT = Cuint(1 << 3),
CREDTYPE_SSH_INTERACTIVE = Cuint(1 << 4),
CREDTYPE_USERNAME = Cuint(1 << 5),
CREDTYPE_SSH_MEMORY = Cuint(1 << 6))
@enum(GIT_FEATURE, FEATURE_THREADS = Cuint(1 << 0),
FEATURE_HTTPS = Cuint(1 << 1),
FEATURE_SSH = Cuint(1 << 2),
FEATURE_NSEC = Cuint(1 << 3))
if LibGit2.version() >= v"0.24.0"
"""
Priority level of a config file.
These priority levels correspond to the natural escalation logic (from higher to lower) when searching for config entries in git.
* `CONFIG_LEVEL_DEFAULT` - Open the global, XDG and system configuration files if any available.
* `CONFIG_LEVEL_PROGRAMDATA` - System-wide on Windows, for compatibility with portable git
* `CONFIG_LEVEL_SYSTEM` - System-wide configuration file; `/etc/gitconfig` on Linux systems
* `CONFIG_LEVEL_XDG` - XDG compatible configuration file; typically `~/.config/git/config`
* `CONFIG_LEVEL_GLOBAL` - User-specific configuration file (also called Global configuration file); typically `~/.gitconfig`
* `CONFIG_LEVEL_LOCAL` - Repository specific configuration file; `\$WORK_DIR/.git/config` on non-bare repos
* `CONFIG_LEVEL_APP` - Application specific configuration file; freely defined by applications
* `CONFIG_HIGHEST_LEVEL` - Represents the highest level available config file (i.e. the most specific config file available that actually is loaded)
"""
@enum(GIT_CONFIG, CONFIG_LEVEL_DEFAULT = 0,
CONFIG_LEVEL_PROGRAMDATA = 1,
CONFIG_LEVEL_SYSTEM = 2,
CONFIG_LEVEL_XDG = 3,
CONFIG_LEVEL_GLOBAL = 4,
CONFIG_LEVEL_LOCAL = 5,
CONFIG_LEVEL_APP = 6,
CONFIG_HIGHEST_LEVEL =-1)
else
"""
Priority level of a config file.
These priority levels correspond to the natural escalation logic (from higher to lower) when searching for config entries in git.
* `CONFIG_LEVEL_DEFAULT` - Open the global, XDG and system configuration files if any available.
* `CONFIG_LEVEL_SYSTEM` - System-wide configuration file; `/etc/gitconfig` on Linux systems
* `CONFIG_LEVEL_XDG` - XDG compatible configuration file; typically `~/.config/git/config`
* `CONFIG_LEVEL_GLOBAL` - User-specific configuration file (also called Global configuration file); typically `~/.gitconfig`
* `CONFIG_LEVEL_LOCAL` - Repository specific configuration file; `\$WORK_DIR/.git/config` on non-bare repos
* `CONFIG_LEVEL_APP` - Application specific configuration file; freely defined by applications
* `CONFIG_HIGHEST_LEVEL` - Represents the highest level available config file (i.e. the most specific config file available that actually is loaded)
"""
@enum(GIT_CONFIG, CONFIG_LEVEL_DEFAULT = 0,
CONFIG_LEVEL_SYSTEM = 1,
CONFIG_LEVEL_XDG = 2,
CONFIG_LEVEL_GLOBAL = 3,
CONFIG_LEVEL_LOCAL = 4,
CONFIG_LEVEL_APP = 5,
CONFIG_HIGHEST_LEVEL =-1)
end
"""
Global library options.
These are used to select which global option to set or get and are used in `git_libgit2_opts()`.
"""
@enum(GIT_OPT, GET_MWINDOW_SIZE = 0,
SET_MWINDOW_SIZE = 1,
GET_MWINDOW_MAPPED_LIMIT = 2,
SET_MWINDOW_MAPPED_LIMIT = 3,
GET_SEARCH_PATH = 4,
SET_SEARCH_PATH = 5,
SET_CACHE_OBJECT_LIMIT = 6,
SET_CACHE_MAX_SIZE = 7,
ENABLE_CACHING = 8,
GET_CACHED_MEMORY = 9,
GET_TEMPLATE_PATH = 10,
SET_TEMPLATE_PATH = 11,
SET_SSL_CERT_LOCATIONS = 12)
@enum(GIT_PROXY, PROXY_NONE,
PROXY_AUTO,
PROXY_SPECIFIED)
end

View File

@@ -0,0 +1,82 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# TODO: make this a general purpose solution
function Base.cconvert(::Type{Ptr{DiffOptionsStruct}}, pathspecs::AbstractString)
str_ref = Base.cconvert(Ref{Cstring}, [pathspecs])
sa = StrArrayStruct(Base.unsafe_convert(Ref{Cstring}, str_ref), 1)
do_ref = Ref(DiffOptionsStruct(pathspec = sa))
do_ref, str_ref
end
function Base.unsafe_convert(::Type{Ptr{DiffOptionsStruct}}, rr::Tuple{Ref{DiffOptionsStruct}, Ref{Cstring}})
Base.unsafe_convert(Ptr{DiffOptionsStruct}, first(rr))
end
function diff_tree(repo::GitRepo, tree::GitTree, pathspecs::AbstractString=""; cached::Bool=false)
diff_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
if cached
@check ccall((:git_diff_tree_to_index, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{Void}, Ptr{Void}, Ptr{DiffOptionsStruct}),
diff_ptr_ptr, repo.ptr, tree.ptr, C_NULL, isempty(pathspecs) ? C_NULL : pathspecs)
else
@check ccall((:git_diff_tree_to_workdir_with_index, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{Void}, Ptr{DiffOptionsStruct}),
diff_ptr_ptr, repo.ptr, tree.ptr, isempty(pathspecs) ? C_NULL : pathspecs)
end
return GitDiff(repo, diff_ptr_ptr[])
end
function diff_tree(repo::GitRepo, oldtree::GitTree, newtree::GitTree)
diff_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_diff_tree_to_tree, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{Void}, Ptr{Void}, Ptr{DiffOptionsStruct}),
diff_ptr_ptr, repo.ptr, oldtree.ptr, newtree.ptr, C_NULL)
return GitDiff(repo, diff_ptr_ptr[])
end
function GitDiffStats(diff::GitDiff)
diff_stat_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_diff_get_stats, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}),
diff_stat_ptr_ptr, diff.ptr)
return GitDiffStats(diff.owner, diff_stat_ptr_ptr[])
end
function files_changed(diff_stat::GitDiffStats)
return ccall((:git_diff_stats_files_changed, :libgit2), Csize_t, (Ptr{Void},), diff_stat.ptr)
end
function insertions(diff_stat::GitDiffStats)
return ccall((:git_diff_stats_insertions, :libgit2), Csize_t, (Ptr{Void},), diff_stat.ptr)
end
function deletions(diff_stat::GitDiffStats)
return ccall((:git_diff_stats_deletions, :libgit2), Csize_t, (Ptr{Void},), diff_stat.ptr)
end
function Base.count(diff::GitDiff)
return ccall((:git_diff_num_deltas, :libgit2), Cint, (Ptr{Void},), diff.ptr)
end
function Base.getindex(diff::GitDiff, i::Integer)
if i < 1 || i > count(diff)
throw(BoundsError(diff, (i,)))
end
delta_ptr = ccall((:git_diff_get_delta, :libgit2),
Ptr{DiffDelta},
(Ptr{Void}, Csize_t), diff.ptr, i-1)
return unsafe_load(delta_ptr)
end
function Base.show(io::IO, diff_stat::GitDiffStats)
println(io, "GitDiffStats:")
println(io, "Files changed: $(files_changed(diff_stat))")
println(io, "Insertions: $(insertions(diff_stat))")
println(io, "Deletions: $(deletions(diff_stat))")
end
function Base.show(io::IO, diff::GitDiff)
println(io, "GitDiff:")
println(io, "Number of deltas: $(count(diff))")
show(io, GitDiffStats(diff))
end

View File

@@ -0,0 +1,103 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
module Error
export GitError
@enum(Code, GIT_OK = Cint(0), # no error
ERROR = Cint(-01), # generic error
ENOTFOUND = Cint(-03), # requested object could not be found
EEXISTS = Cint(-04), # object exits preventing op
EAMBIGUOUS = Cint(-05), # more than one object matches
EBUFS = Cint(-06), # output buffer too small to hold data
EUSER = Cint(-07), # user callback generated error
EBAREREPO = Cint(-08), # operation not allowed on bare repo
EUNBORNBRANCH = Cint(-09), # HEAD refers to branch with 0 commits
EUNMERGED = Cint(-10), # merge in progress prevented op
ENONFASTFORWARD = Cint(-11), # ref not fast-forwardable
EINVALIDSPEC = Cint(-12), # name / ref not in valid format
EMERGECONFLICT = Cint(-13), # merge conflict prevented op
ELOCKED = Cint(-14), # lock file prevented op
EMODIFIED = Cint(-15), # ref value does not match expected
EAUTH = Cint(-16), # authentication error
ECERTIFICATE = Cint(-17), # server certificate is invalid
EAPPLIED = Cint(-18), # patch/merge has already been applied
EPEEL = Cint(-19), # the requested peel operation is not possible
EEOF = Cint(-20), # Unexpted EOF
PASSTHROUGH = Cint(-30), # internal only
ITEROVER = Cint(-31)) # signals end of iteration
@enum(Class, None,
NoMemory,
OS,
Invalid,
Reference,
Zlib,
Repository,
Config,
Regex,
Odb,
Index,
Object,
Net,
Tag,
Tree,
Indexer,
SSL,
Submodule,
Thread,
Stash,
Checkout,
FetchHead,
Merge,
SSH,
Filter,
Revert,
Callback,
CherryPick,
Describe,
Rebase)
struct ErrorStruct
message::Ptr{UInt8}
class::Cint
end
struct GitError <: Exception
class::Class
code::Code
msg::AbstractString
end
Base.show(io::IO, err::GitError) = print(io, "GitError(Code:$(err.code), Class:$(err.class), $(err.msg))")
function last_error()
err = ccall((:giterr_last, :libgit2), Ptr{ErrorStruct}, ())
if err != C_NULL
err_obj = unsafe_load(err)
err_class = Class[err_obj.class][]
err_msg = unsafe_string(err_obj.message)
else
err_class = Class[0][]
err_msg = "No errors"
end
return (err_class, err_msg)
end
function GitError(code::Integer)
err_code = Code[code][]
err_class, err_msg = last_error()
return GitError(err_class, err_code, err_msg)
end
end # Error module
macro check(git_func)
quote
local err::Cint
err = $(esc(git_func::Expr))
if err < 0
throw(Error.GitError(err))
end
err
end
end

View File

@@ -0,0 +1,124 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function GitIndex(repo::GitRepo)
idx_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_repository_index, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}), idx_ptr_ptr, repo.ptr)
return GitIndex(repo, idx_ptr_ptr[])
end
function read!(idx::GitIndex, force::Bool = false)
@check ccall((:git_index_read, :libgit2), Cint, (Ptr{Void}, Cint), idx.ptr, Cint(force))
return idx
end
function write!(idx::GitIndex)
@check ccall((:git_index_write, :libgit2), Cint, (Ptr{Void},), idx.ptr)
return idx
end
function write_tree!(idx::GitIndex)
oid_ptr = Ref(GitHash())
@check ccall((:git_index_write_tree, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Void}), oid_ptr, idx.ptr)
return oid_ptr[]
end
function repository(idx::GitIndex)
if isnull(idx.owner)
throw(GitError(Error.Index, Error.ENOTFOUND, "Index does not have an owning repository."))
else
return Base.get(idx.owner)
end
end
"""
LibGit2.read_tree!(idx::GitIndex, tree::GitTree)
LibGit2.read_tree!(idx::GitIndex, treehash::AbstractGitHash)
Read the tree `tree` (or the tree pointed to by `treehash` in the repository owned by
`idx`) into the index `idx`. The current index contents will be replaced.
"""
function read_tree!(idx::GitIndex, tree::GitTree)
@check ccall((:git_index_read_tree, :libgit2), Cint,
(Ptr{Void}, Ptr{Void}), idx.ptr, tree.ptr)
end
read_tree!(idx::GitIndex, hash::AbstractGitHash) =
read_tree!(idx, GitTree(repository(idx), hash))
function add!(idx::GitIndex, files::AbstractString...;
flags::Cuint = Consts.INDEX_ADD_DEFAULT)
@check ccall((:git_index_add_all, :libgit2), Cint,
(Ptr{Void}, Ptr{StrArrayStruct}, Cuint, Ptr{Void}, Ptr{Void}),
idx.ptr, collect(files), flags, C_NULL, C_NULL)
end
function update!(idx::GitIndex, files::AbstractString...)
@check ccall((:git_index_update_all, :libgit2), Cint,
(Ptr{Void}, Ptr{StrArrayStruct}, Ptr{Void}, Ptr{Void}),
idx.ptr, collect(files), C_NULL, C_NULL)
end
function remove!(idx::GitIndex, files::AbstractString...)
@check ccall((:git_index_remove_all, :libgit2), Cint,
(Ptr{Void}, Ptr{StrArrayStruct}, Ptr{Void}, Ptr{Void}),
idx.ptr, collect(files), C_NULL, C_NULL)
end
function add!(repo::GitRepo, files::AbstractString...;
flags::Cuint = Consts.INDEX_ADD_DEFAULT)
with(GitIndex, repo) do idx
add!(idx, files..., flags = flags)
write!(idx)
end
return
end
function update!(repo::GitRepo, files::AbstractString...)
with(GitIndex, repo) do idx
update!(idx, files...)
write!(idx)
end
return
end
function remove!(repo::GitRepo, files::AbstractString...)
with(GitIndex, repo) do idx
remove!(idx, files...)
write!(idx)
end
return
end
function read!(repo::GitRepo, force::Bool = false)
with(GitIndex, repo) do idx
read!(idx, force)
end
return
end
function Base.count(idx::GitIndex)
return ccall((:git_index_entrycount, :libgit2), Csize_t, (Ptr{Void},), idx.ptr)
end
function Base.getindex(idx::GitIndex, i::Integer)
ie_ptr = ccall((:git_index_get_byindex, :libgit2),
Ptr{IndexEntry},
(Ptr{Void}, Csize_t), idx.ptr, i-1)
ie_ptr == C_NULL && return nothing
return unsafe_load(ie_ptr)
end
function Base.find(path::String, idx::GitIndex)
pos_ref = Ref{Csize_t}(0)
ret = ccall((:git_index_find, :libgit2), Cint,
(Ref{Csize_t}, Ptr{Void}, Cstring), pos_ref, idx.ptr, path)
ret == Cint(Error.ENOTFOUND) && return Nullable{Csize_t}()
return Nullable(pos_ref[]+1)
end
stage(ie::IndexEntry) = ccall((:git_index_entry_stage, :libgit2), Cint, (Ptr{IndexEntry},), Ref(ie))
function Base.show(io::IO, idx::GitIndex)
println(io, "GitIndex:\nRepository: ", repository(idx), "\nNumber of elements: ", count(idx))
end

View File

@@ -0,0 +1,949 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
module LibGit2
import Base: merge!, ==
export with, GitRepo, GitConfig
const GITHUB_REGEX =
r"^(?:git@|git://|https://(?:[\w\.\+\-]+@)?)github.com[:/](([^/].+)/(.+?))(?:\.git)?$"i
const REFCOUNT = Threads.Atomic{UInt}()
include("utils.jl")
include("consts.jl")
include("types.jl")
include("error.jl")
include("signature.jl")
include("oid.jl")
include("reference.jl")
include("commit.jl")
include("repository.jl")
include("config.jl")
include("walker.jl")
include("remote.jl")
include("strarray.jl")
include("index.jl")
include("merge.jl")
include("tag.jl")
include("blob.jl")
include("diff.jl")
include("rebase.jl")
include("status.jl")
include("tree.jl")
include("callbacks.jl")
using .Error
struct State
head::GitHash
index::GitHash
work::GitHash
end
"""
head(pkg::AbstractString) -> String
Return current HEAD [`GitHash`](@ref) of
the `pkg` repo as a string.
"""
function head(pkg::AbstractString)
with(GitRepo, pkg) do repo
string(head_oid(repo))
end
end
"""
need_update(repo::GitRepo)
Equivalent to `git update-index`. Returns `true`
if `repo` needs updating.
"""
function need_update(repo::GitRepo)
if !isbare(repo)
# read updates index from filesystem
read!(repo, true)
end
end
"""
iscommit(id::AbstractString, repo::GitRepo) -> Bool
Checks if commit `id` (which is a [`GitHash`](@ref) in string form)
is in the repository.
# Example
```julia-repl
julia> repo = LibGit2.GitRepo(repo_path);
julia> LibGit2.add!(repo, test_file);
julia> commit_oid = LibGit2.commit(repo, "add test_file");
julia> LibGit2.iscommit(string(commit_oid), repo)
true
```
"""
function iscommit(id::AbstractString, repo::GitRepo)
res = true
try
c = GitCommit(repo, id)
if c === nothing
res = false
else
close(c)
end
catch
res = false
end
return res
end
"""
LibGit2.isdirty(repo::GitRepo, pathspecs::AbstractString=""; cached::Bool=false) -> Bool
Checks if there have been any changes to tracked files in the working tree (if
`cached=false`) or the index (if `cached=true`).
`pathspecs` are the specifications for options for the diff.
# Example
```julia
repo = LibGit2.GitRepo(repo_path)
LibGit2.isdirty(repo) # should be false
open(joinpath(repo_path, new_file), "a") do f
println(f, "here's my cool new file")
end
LibGit2.isdirty(repo) # now true
LibGit2.isdirty(repo, new_file) # now true
```
Equivalent to `git diff-index HEAD [-- <pathspecs>]`.
"""
isdirty(repo::GitRepo, paths::AbstractString=""; cached::Bool=false) =
isdiff(repo, Consts.HEAD_FILE, paths, cached=cached)
"""
LibGit2.isdiff(repo::GitRepo, treeish::AbstractString, pathspecs::AbstractString=""; cached::Bool=false)
Checks if there are any differences between the tree specified by `treeish` and the
tracked files in the working tree (if `cached=false`) or the index (if `cached=true`).
`pathspecs` are the specifications for options for the diff.
# Example
```julia
repo = LibGit2.GitRepo(repo_path)
LibGit2.isdiff(repo, "HEAD") # should be false
open(joinpath(repo_path, new_file), "a") do f
println(f, "here's my cool new file")
end
LibGit2.isdiff(repo, "HEAD") # now true
```
Equivalent to `git diff-index <treeish> [-- <pathspecs>]`.
"""
function isdiff(repo::GitRepo, treeish::AbstractString, paths::AbstractString=""; cached::Bool=false)
tree = GitTree(repo, "$treeish^{tree}")
try
diff = diff_tree(repo, tree, paths, cached=cached)
result = count(diff) > 0
close(diff)
return result
finally
close(tree)
end
end
"""
diff_files(repo::GitRepo, branch1::AbstractString, branch2::AbstractString; kwarg...) -> Vector{AbstractString}
Show which files have changed in the git repository `repo` between branches `branch1`
and `branch2`.
The keyword argument is:
* `filter::Set{Consts.DELTA_STATUS}=Set([Consts.DELTA_ADDED, Consts.DELTA_MODIFIED, Consts.DELTA_DELETED]))`,
and it sets options for the diff. The default is to show files added, modified, or deleted.
Returns only the *names* of the files which have changed, *not* their contents.
# Example
```julia
LibGit2.branch!(repo, "branch/a")
LibGit2.branch!(repo, "branch/b")
# add a file to repo
open(joinpath(LibGit2.path(repo),"file"),"w") do f
write(f, "hello repo\n")
end
LibGit2.add!(repo, "file")
LibGit2.commit(repo, "add file")
# returns ["file"]
filt = Set([LibGit2.Consts.DELTA_ADDED])
files = LibGit2.diff_files(repo, "branch/a", "branch/b", filter=filt)
# returns [] because existing files weren't modified
filt = Set([LibGit2.Consts.DELTA_MODIFIED])
files = LibGit2.diff_files(repo, "branch/a", "branch/b", filter=filt)
```
Equivalent to `git diff --name-only --diff-filter=<filter> <branch1> <branch2>`.
"""
function diff_files(repo::GitRepo, branch1::AbstractString, branch2::AbstractString;
filter::Set{Consts.DELTA_STATUS}=Set([Consts.DELTA_ADDED, Consts.DELTA_MODIFIED, Consts.DELTA_DELETED]))
b1_id = revparseid(repo, branch1*"^{tree}")
b2_id = revparseid(repo, branch2*"^{tree}")
tree1 = GitTree(repo, b1_id)
tree2 = GitTree(repo, b2_id)
files = AbstractString[]
try
diff = diff_tree(repo, tree1, tree2)
for i in 1:count(diff)
delta = diff[i]
delta === nothing && break
if Consts.DELTA_STATUS(delta.status) in filter
push!(files, unsafe_string(delta.new_file.path))
end
end
close(diff)
finally
close(tree1)
close(tree2)
end
return files
end
"""
is_ancestor_of(a::AbstractString, b::AbstractString, repo::GitRepo) -> Bool
Returns `true` if `a`, a [`GitHash`](@ref) in string form, is an ancestor of
`b`, a [`GitHash`](@ref) in string form.
# Example
```julia-repl
julia> repo = LibGit2.GitRepo(repo_path);
julia> LibGit2.add!(repo, test_file1);
julia> commit_oid1 = LibGit2.commit(repo, "commit1");
julia> LibGit2.add!(repo, test_file2);
julia> commit_oid2 = LibGit2.commit(repo, "commit2");
julia> LibGit2.is_ancestor_of(string(commit_oid1), string(commit_oid2), repo)
true
```
"""
function is_ancestor_of(a::AbstractString, b::AbstractString, repo::GitRepo)
A = revparseid(repo, a)
merge_base(repo, a, b) == A
end
"""
set_remote_url(repo::GitRepo, url::AbstractString; remote::AbstractString="origin")
Set the `url` for `remote` for the git repository `repo`.
The default name of the remote is `"origin"`.
# Examples
```julia
repo_path = joinpath("test_directory", "Example")
repo = LibGit2.init(repo_path)
url1 = "https://github.com/JuliaLang/Example.jl"
LibGit2.set_remote_url(repo, url1, remote="upstream")
url2 = "https://github.com/JuliaLang/Example2.jl"
LibGit2.set_remote_url(repo_path, url2, remote="upstream2")
```
"""
function set_remote_url(repo::GitRepo, url::AbstractString; remote::AbstractString="origin")
with(GitConfig, repo) do cfg
set!(cfg, "remote.$remote.url", url)
set!(cfg, "remote.$remote.pushurl", url)
end
end
"""
set_remote_url(path::AbstractString, url::AbstractString; remote::AbstractString="origin")
Set the `url` for `remote` for the git repository located at `path`.
The default name of the remote is `"origin"`.
"""
function set_remote_url(path::AbstractString, url::AbstractString; remote::AbstractString="origin")
with(GitRepo, path) do repo
set_remote_url(repo, url, remote=remote)
end
end
function make_payload(payload::Nullable{<:AbstractCredentials})
Ref{Nullable{AbstractCredentials}}(payload)
end
"""
fetch(repo::GitRepo; kwargs...)
Fetches updates from an upstream of the repository `repo`.
The keyword arguments are:
* `remote::AbstractString="origin"`: which remote, specified by name,
of `repo` to fetch from. If this is empty, the URL will be used to
construct an anonymous remote.
* `remoteurl::AbstractString=""`: the URL of `remote`. If not specified,
will be assumed based on the given name of `remote`.
* `refspecs=AbstractString[]`: determines properties of the fetch.
* `payload=Nullable{AbstractCredentials}()`: provides credentials, if necessary,
for instance if `remote` is a private repository.
Equivalent to `git fetch [<remoteurl>|<repo>] [<refspecs>]`.
"""
function fetch(repo::GitRepo; remote::AbstractString="origin",
remoteurl::AbstractString="",
refspecs::Vector{<:AbstractString}=AbstractString[],
payload::Nullable{<:AbstractCredentials}=Nullable{AbstractCredentials}())
rmt = if isempty(remoteurl)
get(GitRemote, repo, remote)
else
GitRemoteAnon(repo, remoteurl)
end
try
payload = make_payload(payload)
fo = FetchOptions(callbacks=RemoteCallbacks(credentials_cb(), payload))
fetch(rmt, refspecs, msg="from $(url(rmt))", options = fo)
finally
close(rmt)
end
end
"""
push(repo::GitRepo; kwargs...)
Pushes updates to an upstream of `repo`.
The keyword arguments are:
* `remote::AbstractString="origin"`: the name of the upstream remote to push to.
* `remoteurl::AbstractString=""`: the URL of `remote`.
* `refspecs=AbstractString[]`: determines properties of the push.
* `force::Bool=false`: determines if the push will be a force push,
overwriting the remote branch.
* `payload=Nullable{AbstractCredentials}()`: provides credentials, if necessary,
for instance if `remote` is a private repository.
Equivalent to `git push [<remoteurl>|<repo>] [<refspecs>]`.
"""
function push(repo::GitRepo; remote::AbstractString="origin",
remoteurl::AbstractString="",
refspecs::Vector{<:AbstractString}=AbstractString[],
force::Bool=false,
payload::Nullable{<:AbstractCredentials}=Nullable{AbstractCredentials}())
rmt = if isempty(remoteurl)
get(GitRemote, repo, remote)
else
GitRemoteAnon(repo, remoteurl)
end
try
payload = make_payload(payload)
push_opts=PushOptions(callbacks=RemoteCallbacks(credentials_cb(), payload))
push(rmt, refspecs, force=force, options=push_opts)
finally
close(rmt)
end
end
"""
branch(repo::GitRepo)
Equivalent to `git branch`.
Create a new branch from the current HEAD.
"""
function branch(repo::GitRepo)
head_ref = head(repo)
try
branch(head_ref)
finally
close(head_ref)
end
end
"""
branch!(repo::GitRepo, branch_name::AbstractString, commit::AbstractString=""; kwargs...)
Checkout a new git branch in the `repo` repository. `commit` is the [`GitHash`](@ref),
in string form, which will be the start of the new branch.
If `commit` is an empty string, the current HEAD will be used.
The keyword arguments are:
* `track::AbstractString=""`: the name of the
remote branch this new branch should track, if any.
If empty (the default), no remote branch
will be tracked.
* `force::Bool=false`: if `true`, branch creation will
be forced.
* `set_head::Bool=true`: if `true`, after the branch creation
finishes the branch head will be set as the HEAD of `repo`.
Equivalent to `git checkout [-b|-B] <branch_name> [<commit>] [--track <track>]`.
# Example
```julia
repo = LibGit2.GitRepo(repo_path)
LibGit2.branch!(repo, "new_branch", set_head=false)
```
"""
function branch!(repo::GitRepo, branch_name::AbstractString,
commit::AbstractString = ""; # start point
track::AbstractString = "", # track remote branch
force::Bool=false, # force branch creation
set_head::Bool=true) # set as head reference on exit
# try to lookup branch first
branch_ref = force ? Nullable{GitReference}() : lookup_branch(repo, branch_name)
if isnull(branch_ref)
branch_rmt_ref = isempty(track) ? Nullable{GitReference}() : lookup_branch(repo, "$track/$branch_name", true)
# if commit is empty get head commit oid
commit_id = if isempty(commit)
if isnull(branch_rmt_ref)
with(head(repo)) do head_ref
with(peel(GitCommit, head_ref)) do hrc
GitHash(hrc)
end
end
else
tmpcmt = with(peel(GitCommit, Base.get(branch_rmt_ref))) do hrc
GitHash(hrc)
end
close(Base.get(branch_rmt_ref))
tmpcmt
end
else
GitHash(commit)
end
iszero(commit_id) && return
cmt = GitCommit(repo, commit_id)
new_branch_ref = nothing
try
new_branch_ref = Nullable(create_branch(repo, branch_name, cmt, force=force))
finally
close(cmt)
isnull(new_branch_ref) && throw(GitError(Error.Object, Error.ERROR, "cannot create branch `$branch_name` with `$commit_id`"))
branch_ref = new_branch_ref
end
end
try
#TODO: what if branch tracks other then "origin" remote
if !isempty(track) # setup tracking
try
with(GitConfig, repo) do cfg
set!(cfg, "branch.$branch_name.remote", Consts.REMOTE_ORIGIN)
set!(cfg, "branch.$branch_name.merge", name(Base.get(branch_ref)))
end
catch
warn("Please provide remote tracking for branch '$branch_name' in '$(path(repo))'")
end
end
if set_head
# checkout selected branch
with(peel(GitTree, Base.get(branch_ref))) do btree
checkout_tree(repo, btree)
end
# switch head to the branch
head!(repo, Base.get(branch_ref))
end
finally
close(Base.get(branch_ref))
end
return
end
"""
checkout!(repo::GitRepo, commit::AbstractString=""; force::Bool=true)
Equivalent to `git checkout [-f] --detach <commit>`.
Checkout the git commit `commit` (a [`GitHash`](@ref) in string form)
in `repo`. If `force` is `true`, force the checkout and discard any
current changes. Note that this detaches the current HEAD.
# Example
```julia
repo = LibGit2.init(repo_path)
open(joinpath(LibGit2.path(repo), "file1"), "w") do f
write(f, "111\n")
end
LibGit2.add!(repo, "file1")
commit_oid = LibGit2.commit(repo, "add file1")
open(joinpath(LibGit2.path(repo), "file1"), "w") do f
write(f, "112\n")
end
# would fail without the force=true
# since there are modifications to the file
LibGit2.checkout!(repo, string(commit_oid), force=true)
```
"""
function checkout!(repo::GitRepo, commit::AbstractString = "";
force::Bool = true)
# nothing to do
isempty(commit) && return
# grab head name
head_name = Consts.HEAD_FILE
try
with(head(repo)) do head_ref
head_name = shortname(head_ref)
# if it is HEAD use short OID instead
if head_name == Consts.HEAD_FILE
head_name = string(GitHash(head_ref))
end
end
end
# search for commit to get a commit object
obj = GitObject(repo, GitHash(commit))
peeled = peel(GitCommit, obj)
obj_oid = GitHash(peeled)
# checkout commit
checkout_tree(repo, peeled, options = force ? CheckoutOptions(checkout_strategy = Consts.CHECKOUT_FORCE) : CheckoutOptions())
GitReference(repo, obj_oid, force=force,
msg="libgit2.checkout: moving from $head_name to $(obj_oid))")
return nothing
end
"""
clone(repo_url::AbstractString, repo_path::AbstractString; kwargs...)
Clone a remote repository located at `repo_url` to the local filesystem location `repo_path`.
The keyword arguments are:
* `branch::AbstractString=""`: which branch of the remote to clone,
if not the default repository branch (usually `master`).
* `isbare::Bool=false`: if `true`, clone the remote as a bare repository,
which will make `repo_path` itself the git directory instead of `repo_path/.git`.
This means that a working tree cannot be checked out. Plays the role of the
git CLI argument `--bare`.
* `remote_cb::Ptr{Void}=C_NULL`: a callback which will be used to create the remote
before it is cloned. If `C_NULL` (the default), no attempt will be made to create
the remote - it will be assumed to already exist.
* `payload::Nullable{P<:AbstractCredentials}=Nullable{AbstractCredentials}()`:
provides credentials if necessary, for instance if the remote is a private
repository.
Equivalent to `git clone [-b <branch>] [--bare] <repo_url> <repo_path>`.
# Examples
```julia
repo_url = "https://github.com/JuliaLang/Example.jl"
repo1 = LibGit2.clone(repo_url, "test_path")
repo2 = LibGit2.clone(repo_url, "test_path", isbare=true)
julia_url = "https://github.com/JuliaLang/julia"
julia_repo = LibGit2.clone(julia_url, "julia_path", branch="release-0.6")
```
"""
function clone(repo_url::AbstractString, repo_path::AbstractString;
branch::AbstractString="",
isbare::Bool = false,
remote_cb::Ptr{Void} = C_NULL,
payload::Nullable{<:AbstractCredentials}=Nullable{AbstractCredentials}())
# setup clone options
lbranch = Base.cconvert(Cstring, branch)
payload = make_payload(payload)
fetch_opts=FetchOptions(callbacks = RemoteCallbacks(credentials_cb(), payload))
clone_opts = CloneOptions(
bare = Cint(isbare),
checkout_branch = isempty(lbranch) ? Cstring(C_NULL) : Base.unsafe_convert(Cstring, lbranch),
fetch_opts=fetch_opts,
remote_cb = remote_cb
)
return clone(repo_url, repo_path, clone_opts)
end
""" git reset [<committish>] [--] <pathspecs>... """
function reset!(repo::GitRepo, committish::AbstractString, pathspecs::AbstractString...)
obj = GitObject(repo, isempty(committish) ? Consts.HEAD_FILE : committish)
# do not remove entries in the index matching the provided pathspecs with empty target commit tree
reset!(repo, Nullable(obj), pathspecs...)
end
"""
reset!(repo::GitRepo, id::GitHash, mode::Cint = Consts.RESET_MIXED)
Reset the repository `repo` to its state at `id`, using one of three modes
set by `mode`:
1. `Consts.RESET_SOFT` - move HEAD to `id`.
2. `Consts.RESET_MIXED` - default, move HEAD to `id` and reset the index to `id`.
3. `Consts.RESET_HARD` - move HEAD to `id`, reset the index to `id`, and discard all working changes.
Equivalent to `git reset [--soft | --mixed | --hard] <id>`.
# Example
```julia
repo = LibGit2.GitRepo(repo_path)
head_oid = LibGit2.head_oid(repo)
open(joinpath(repo_path, "file1"), "w") do f
write(f, "111\n")
end
LibGit2.add!(repo, "file1")
mode = LibGit2.Consts.RESET_HARD
# will discard the changes to file1
# and unstage it
new_head = LibGit2.reset!(repo, head_oid, mode)
```
"""
reset!(repo::GitRepo, id::GitHash, mode::Cint = Consts.RESET_MIXED) =
reset!(repo, GitObject(repo, id), mode)
"""
LibGit2.revcount(repo::GitRepo, commit1::AbstractString, commit2::AbstractString)
List the number of revisions between `commit1` and `commit2` (committish OIDs in string form).
Since `commit1` and `commit2` may be on different branches, `revcount` performs a "left-right"
revision list (and count), returning a tuple of `Int`s - the number of left and right
commits, respectively. A left (or right) commit refers to which side of a symmetric
difference in a tree the commit is reachable from.
Equivalent to `git rev-list --left-right --count <commit1> <commit2>`.
"""
function revcount(repo::GitRepo, commit1::AbstractString, commit2::AbstractString)
commit1_id = revparseid(repo, commit1)
commit2_id = revparseid(repo, commit2)
base_id = merge_base(repo, string(commit1_id), string(commit2_id))
fc = with(GitRevWalker(repo)) do walker
count((i,r)->i!=base_id, walker, oid=commit1_id, by=Consts.SORT_TOPOLOGICAL)
end
sc = with(GitRevWalker(repo)) do walker
count((i,r)->i!=base_id, walker, oid=commit2_id, by=Consts.SORT_TOPOLOGICAL)
end
return (fc-1, sc-1)
end
"""
merge!(repo::GitRepo; kwargs...) -> Bool
Perform a git merge on the repository `repo`, merging commits
with diverging history into the current branch. Returns `true`
if the merge succeeded, `false` if not.
The keyword arguments are:
* `committish::AbstractString=""`: Merge the named commit(s) in `committish`.
* `branch::AbstractString=""`: Merge the branch `branch` and all its commits
since it diverged from the current branch.
* `fastforward::Bool=false`: If `fastforward` is `true`, only merge if the
merge is a fast-forward (the current branch head is an ancestor of the
commits to be merged), otherwise refuse to merge and return `false`.
This is equivalent to the git CLI option `--ff-only`.
* `merge_opts::MergeOptions=MergeOptions()`: `merge_opts` specifies options
for the merge, such as merge strategy in case of conflicts.
* `checkout_opts::CheckoutOptions=CheckoutOptions()`: `checkout_opts` specifies
options for the checkout step.
Equivalent to `git merge [--ff-only] [<committish> | <branch>]`.
!!! note
If you specify a `branch`, this must be done in reference format, since
the string will be turned into a `GitReference`. For example, if you
wanted to merge branch `branch_a`, you would call
`merge!(repo, branch="refs/heads/branch_a")`.
"""
function merge!(repo::GitRepo;
committish::AbstractString = "",
branch::AbstractString = "",
fastforward::Bool = false,
merge_opts::MergeOptions = MergeOptions(),
checkout_opts::CheckoutOptions = CheckoutOptions())
# merge into head branch
upst_anns = if !isempty(committish) # merge committish into HEAD
if committish == Consts.FETCH_HEAD # merge FETCH_HEAD
fheads = fetchheads(repo)
filter!(fh->fh.ismerge, fheads)
if isempty(fheads)
throw(GitError(Error.Merge, Error.ERROR,
"There is no fetch reference for this branch."))
end
map(fh->GitAnnotated(repo,fh), fheads)
else # merge commitish
[GitAnnotated(repo, committish)]
end
else
if !isempty(branch) # merge provided branch into HEAD
with(GitReference(repo, branch)) do brn_ref
[GitAnnotated(repo, brn_ref)]
end
else # try to get tracking remote branch for the head
if !isattached(repo)
throw(GitError(Error.Merge, Error.ERROR,
"Repository HEAD is detached. Remote tracking branch cannot be used."))
end
if isorphan(repo)
# this isn't really a merge, but really moving HEAD
# https://github.com/libgit2/libgit2/issues/2135#issuecomment-35997764
# try to figure out remote tracking of orphan head
m = with(GitReference(repo, Consts.HEAD_FILE)) do head_sym_ref
match(r"refs/heads/(.*)", fullname(head_sym_ref))
end
if m === nothing
throw(GitError(Error.Merge, Error.ERROR,
"Unable to determine name of orphan branch."))
end
branchname = m.captures[1]
remotename = with(GitConfig, repo) do cfg
LibGit2.get(String, cfg, "branch.$branchname.remote")
end
oid = with(GitReference(repo, "refs/remotes/$remotename/$branchname")) do ref
LibGit2.GitHash(ref)
end
with(GitCommit(repo, oid)) do cmt
LibGit2.create_branch(repo, branchname, cmt)
end
return true
else
with(head(repo)) do head_ref
tr_brn_ref = upstream(head_ref)
if isnull(tr_brn_ref)
throw(GitError(Error.Merge, Error.ERROR,
"There is no tracking information for the current branch."))
end
try
[GitAnnotated(repo, Base.get(tr_brn_ref))]
finally
close(Base.get(tr_brn_ref))
end
end
end
end
end
try
merge!(repo, upst_anns, fastforward,
merge_opts=merge_opts,
checkout_opts=checkout_opts)
finally
map(close, upst_anns)
end
end
"""
LibGit2.rebase!(repo::GitRepo, upstream::AbstractString="", newbase::AbstractString="")
Attempt an automatic merge rebase of the current branch, from `upstream` if provided, or
otherwise from the upstream tracking branch.
`newbase` is the branch to rebase onto. By default this is `upstream`.
If any conflicts arise which cannot be automatically resolved, the rebase will abort,
leaving the repository and working tree in its original state, and the function will throw
a `GitError`. This is roughly equivalent to the following command line statement:
git rebase --merge [<upstream>]
if [ -d ".git/rebase-merge" ]; then
git rebase --abort
fi
"""
function rebase!(repo::GitRepo, upstream::AbstractString="", newbase::AbstractString="")
with(head(repo)) do head_ref
head_ann = GitAnnotated(repo, head_ref)
upst_ann = if isempty(upstream)
brn_ref = LibGit2.upstream(head_ref)
if isnull(brn_ref)
throw(GitError(Error.Rebase, Error.ERROR,
"There is no tracking information for the current branch."))
end
try
GitAnnotated(repo, Base.get(brn_ref))
finally
close(brn_ref)
end
else
GitAnnotated(repo, upstream)
end
onto_ann = Nullable{GitAnnotated}(isempty(newbase) ? nothing : GitAnnotated(repo, newbase))
try
sig = default_signature(repo)
try
rbs = GitRebase(repo, head_ann, upst_ann, onto=onto_ann)
try
while (rbs_op = next(rbs)) !== nothing
commit(rbs, sig)
end
finish(rbs, sig)
catch err
abort(rbs)
rethrow(err)
finally
close(rbs)
end
finally
#!isnull(onto_ann) && close(get(onto_ann))
close(sig)
end
finally
if !isempty(newbase)
close(Base.get(onto_ann))
end
close(upst_ann)
close(head_ann)
end
end
return head_oid(repo)
end
"""
authors(repo::GitRepo) -> Vector{Signature}
Returns all authors of commits to the `repo` repository.
# Example
```julia
repo = LibGit2.GitRepo(repo_path)
repo_file = open(joinpath(repo_path, test_file), "a")
println(repo_file, commit_msg)
flush(repo_file)
LibGit2.add!(repo, test_file)
sig = LibGit2.Signature("TEST", "TEST@TEST.COM", round(time(), 0), 0)
commit_oid1 = LibGit2.commit(repo, "commit1"; author=sig, committer=sig)
println(repo_file, randstring(10))
flush(repo_file)
LibGit2.add!(repo, test_file)
commit_oid2 = LibGit2.commit(repo, "commit2"; author=sig, committer=sig)
# will be a Vector of [sig, sig]
auths = LibGit2.authors(repo)
```
"""
function authors(repo::GitRepo)
return with(GitRevWalker(repo)) do walker
map((oid,repo)->with(GitCommit(repo, oid)) do cmt
author(cmt)::Signature
end,
walker) #, by = Consts.SORT_TIME)
end
end
"""
snapshot(repo::GitRepo) -> State
Take a snapshot of the current state of the repository `repo`,
storing the current HEAD, index, and any uncommitted work.
The output `State` can be used later during a call to [`restore`](@ref)
to return the repository to the snapshotted state.
"""
function snapshot(repo::GitRepo)
head = GitHash(repo, Consts.HEAD_FILE)
index = with(GitIndex, repo) do idx; write_tree!(idx) end
work = try
with(GitIndex, repo) do idx
if length(readdir(path(repo))) > 1
add!(idx, ".")
write!(idx)
end
write_tree!(idx)
end
finally
# restore index
with(GitIndex, repo) do idx
read_tree!(idx, index)
write!(idx)
end
end
State(head, index, work)
end
"""
restore(s::State, repo::GitRepo)
Return a repository `repo` to a previous `State` `s`, for
example the HEAD of a branch before a merge attempt. `s`
can be generated using the [`snapshot`](@ref) function.
"""
function restore(s::State, repo::GitRepo)
head = reset!(repo, Consts.HEAD_FILE, "*") # unstage everything
with(GitIndex, repo) do idx
read_tree!(idx, s.work) # move work tree to index
opts = CheckoutOptions(
checkout_strategy = Consts.CHECKOUT_FORCE | # check the index out to work
Consts.CHECKOUT_REMOVE_UNTRACKED) # remove everything else
checkout_index(repo, Nullable(idx), options = opts)
read_tree!(idx, s.index) # restore index
end
reset!(repo, s.head, Consts.RESET_SOFT) # restore head
end
function transact(f::Function, repo::GitRepo)
state = snapshot(repo)
try f(repo) catch
restore(state, repo)
rethrow()
finally
close(repo)
end
end
function set_ssl_cert_locations(cert_loc)
cert_file = isfile(cert_loc) ? cert_loc : Cstring(C_NULL)
cert_dir = isdir(cert_loc) ? cert_loc : Cstring(C_NULL)
cert_file == C_NULL && cert_dir == C_NULL && return
# TODO FIX https://github.com/libgit2/libgit2/pull/3935#issuecomment-253910017
#ccall((:git_libgit2_opts, :libgit2), Cint,
# (Cint, Cstring, Cstring),
# Cint(Consts.SET_SSL_CERT_LOCATIONS), cert_file, cert_dir)
ENV["SSL_CERT_FILE"] = cert_file
ENV["SSL_CERT_DIR"] = cert_dir
end
function __init__()
# Look for OpenSSL env variable for CA bundle (linux only)
# windows and macOS use the OS native security backends
old_ssl_cert_dir = Base.get(ENV, "SSL_CERT_DIR", nothing)
old_ssl_cert_file = Base.get(ENV, "SSL_CERT_FILE", nothing)
@static if is_linux()
cert_loc = if "SSL_CERT_DIR" in keys(ENV)
ENV["SSL_CERT_DIR"]
elseif "SSL_CERT_FILE" in keys(ENV)
ENV["SSL_CERT_FILE"]
else
# If we have a bundled ca cert file, point libgit2 at that so SSL connections work.
abspath(ccall(:jl_get_julia_home, Any, ()),Base.DATAROOTDIR,"julia","cert.pem")
end
set_ssl_cert_locations(cert_loc)
end
err = ccall((:git_libgit2_init, :libgit2), Cint, ())
err > 0 || throw(ErrorException("error initializing LibGit2 module"))
REFCOUNT[] = 1
atexit() do
if Threads.atomic_sub!(REFCOUNT, UInt(1)) == 1
# refcount zero, no objects to be finalized
ccall((:git_libgit2_shutdown, :libgit2), Cint, ())
end
end
@static if is_linux()
if old_ssl_cert_dir != Base.get(ENV, "SSL_CERT_DIR", "")
if old_ssl_cert_dir === nothing
delete!(ENV, "SSL_CERT_DIR")
else
ENV["SSL_CERT_DIR"] = old_ssl_cert_dir
end
end
if old_ssl_cert_file != Base.get(ENV, "SSL_CERT_FILE", "")
if old_ssl_cert_file === nothing
delete!(ENV, "SSL_CERT_FILE")
else
ENV["SSL_CERT_FILE"] = old_ssl_cert_file
end
end
end
end
end # module

View File

@@ -0,0 +1,154 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function GitAnnotated(repo::GitRepo, commit_id::GitHash)
ann_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_annotated_commit_lookup, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{GitHash}),
ann_ptr_ptr, repo.ptr, Ref(commit_id))
return GitAnnotated(repo, ann_ptr_ptr[])
end
function GitAnnotated(repo::GitRepo, ref::GitReference)
ann_ref_ref = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_annotated_commit_from_ref, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{Void}),
ann_ref_ref, repo.ptr, ref.ptr)
return GitAnnotated(repo, ann_ref_ref[])
end
function GitAnnotated(repo::GitRepo, fh::FetchHead)
ann_ref_ref = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_annotated_commit_from_fetchhead, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring, Cstring, Ptr{GitHash}),
ann_ref_ref, repo.ptr, fh.name, fh.url, Ref(fh.oid))
return GitAnnotated(repo, ann_ref_ref[])
end
function GitAnnotated(repo::GitRepo, comittish::AbstractString)
obj = GitObject(repo, comittish)
cmt = peel(GitCommit, obj)
return GitAnnotated(repo, GitHash(cmt))
end
function GitHash(ann::GitAnnotated)
unsafe_load(ccall((:git_annotated_commit_id, :libgit2), Ptr{GitHash}, (Ptr{Void},), ann.ptr))
end
function merge_analysis(repo::GitRepo, anns::Vector{GitAnnotated})
analysis = Ref{Cint}(0)
preference = Ref{Cint}(0)
anns_ref = Ref(map(a->a.ptr, anns))
anns_size = Csize_t(length(anns))
@check ccall((:git_merge_analysis, :libgit2), Cint,
(Ptr{Cint}, Ptr{Cint}, Ptr{Void}, Ptr{Ptr{Void}}, Csize_t),
analysis, preference, repo.ptr, anns_ref, anns_size)
return analysis[], preference[]
end
"""Fastforward merge changes into current head """
function ffmerge!(repo::GitRepo, ann::GitAnnotated)
cmt = GitCommit(repo, GitHash(ann))
checkout_tree(repo, cmt)
with(head(repo)) do head_ref
cmt_oid = GitHash(cmt)
msg = "libgit2.merge: fastforward $(string(cmt_oid)) into $(name(head_ref))"
new_head_ref = if reftype(head_ref) == Consts.REF_OID
target!(head_ref, cmt_oid, msg=msg)
else
GitReference(repo, cmt_oid, fullname(head_ref), msg=msg)
end
close(new_head_ref)
end
return true
end
""" Merge changes into current head """
function merge!(repo::GitRepo, anns::Vector{GitAnnotated};
merge_opts::MergeOptions = MergeOptions(),
checkout_opts::CheckoutOptions = CheckoutOptions())
anns_size = Csize_t(length(anns))
@check ccall((:git_merge, :libgit2), Cint,
(Ptr{Void}, Ptr{Ptr{Void}}, Csize_t,
Ptr{MergeOptions}, Ptr{CheckoutOptions}),
repo.ptr, map(x->x.ptr, anns), anns_size,
Ref(merge_opts), Ref(checkout_opts))
info("Review and commit merged changes.")
return true
end
"""Internal implementation of merge.
Returns `true` if merge was successful, otherwise `false`
"""
function merge!(repo::GitRepo, anns::Vector{GitAnnotated}, fastforward::Bool;
merge_opts::MergeOptions = MergeOptions(),
checkout_opts::CheckoutOptions = CheckoutOptions())
ma, mp = merge_analysis(repo, anns)
if isset(ma, Cint(Consts.MERGE_ANALYSIS_UP_TO_DATE))
return true # no merge - everything is up to date
end
ffpref = if fastforward
Consts.MERGE_PREFERENCE_FASTFORWARD_ONLY
elseif isset(mp, Cint(Consts.MERGE_PREFERENCE_NONE))
Consts.MERGE_PREFERENCE_NONE
elseif isset(mp, Cint(Consts.MERGE_PREFERENCE_NO_FASTFORWARD))
Consts.MERGE_PREFERENCE_NO_FASTFORWARD
elseif isset(mp, Cint(Consts.MERGE_PREFERENCE_FASTFORWARD_ONLY))
Consts.MERGE_PREFERENCE_FASTFORWARD_ONLY
else
throw(ArgumentError("unknown merge preference: $(mp)."))
end
merge_result = if ffpref == Consts.MERGE_PREFERENCE_NONE
if isset(ma, Cint(Consts.MERGE_ANALYSIS_FASTFORWARD))
if length(anns) > 1
warn("Unable to perform Fast-Forward merge with mith multiple merge heads.")
false
else
ffmerge!(repo, anns[1])
end
elseif isset(ma, Cint(Consts.MERGE_ANALYSIS_NORMAL))
merge!(repo, anns,
merge_opts=merge_opts,
checkout_opts=checkout_opts)
end
elseif ffpref == Consts.MERGE_PREFERENCE_FASTFORWARD_ONLY
if isset(ma, Cint(Consts.MERGE_ANALYSIS_FASTFORWARD))
if length(anns) > 1
warn("Unable to perform Fast-Forward merge with mith multiple merge heads.")
false
else
ffmerge!(repo, anns[1])
end
else
warn("Cannot perform fast-forward merge.")
false
end
elseif ffpref == Consts.MERGE_PREFERENCE_NO_FASTFORWARD
if isset(ma, Cint(Consts.MERGE_ANALYSIS_NORMAL))
merge!(repo, anns,
merge_opts=merge_opts,
checkout_opts=checkout_opts)
end
else
throw(ArgumentError("unknown merge analysis result: $(ma)"))
end
return merge_result
end
function merge_base(repo::GitRepo, one::AbstractString, two::AbstractString)
oid1_ptr = Ref(GitHash(one))
oid2_ptr = Ref(GitHash(two))
moid_ptr = Ref(GitHash())
moid = try
@check ccall((:git_merge_base, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Void}, Ptr{GitHash}, Ptr{GitHash}),
moid_ptr, repo.ptr, oid1_ptr, oid2_ptr)
moid_ptr[]
catch e
#warn("Pkg:",path(repo),"=>",e.msg)
GitHash()
end
return moid
end

View File

@@ -0,0 +1,106 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function GitHash(ptr::Ptr{UInt8})
if ptr == C_NULL
throw(ArgumentError("NULL pointer passed to GitHash() constructor"))
end
oid_ptr = Ref(GitHash())
ccall((:git_oid_fromraw, :libgit2), Void, (Ptr{GitHash}, Ptr{UInt8}), oid_ptr, ptr)
return oid_ptr[]
end
function GitHash(id::Array{UInt8,1})
if length(id) != OID_RAWSZ
throw(ArgumentError("invalid raw buffer size"))
end
return GitHash(pointer(id))
end
function GitHash(id::AbstractString)
bstr = String(id)
len = sizeof(bstr)
if len < OID_HEXSZ
throw(ArgumentError("Input string is too short, use `GitShortHash` for partial hashes"))
end
oid_ptr = Ref{GitHash}()
@check ccall((:git_oid_fromstrn, :libgit2), Cint,
(Ptr{GitHash}, Ptr{UInt8}, Csize_t), oid_ptr, bstr, len)
return oid_ptr[]
end
function GitShortHash(id::AbstractString)
bstr = String(id)
len = sizeof(bstr)
oid_ptr = Ref{GitHash}()
@check ccall((:git_oid_fromstrn, :libgit2), Cint,
(Ptr{GitHash}, Ptr{UInt8}, Csize_t), oid_ptr, bstr, len)
GitShortHash(oid_ptr[], len)
end
macro githash_str(id)
bstr = String(id)
if sizeof(bstr) < OID_HEXSZ
GitShortHash(id)
else
GitHash(id)
end
end
function GitHash(ref::GitReference)
isempty(ref) && return GitHash()
reftype(ref) != Consts.REF_OID && return GitHash()
oid_ptr = ccall((:git_reference_target, :libgit2), Ptr{UInt8}, (Ptr{Void},), ref.ptr)
oid_ptr == C_NULL && return GitHash()
return GitHash(oid_ptr)
end
function GitHash(repo::GitRepo, ref_name::AbstractString)
isempty(repo) && return GitHash()
oid_ptr = Ref(GitHash())
@check ccall((:git_reference_name_to_id, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Void}, Cstring),
oid_ptr, repo.ptr, ref_name)
return oid_ptr[]
end
function GitHash(obj::GitObject)
GitHash(ccall((:git_object_id, :libgit2), Ptr{UInt8}, (Ptr{Void},), obj.ptr))
end
Base.hex(id::GitHash) = join([hex(i,2) for i in id.val])
Base.hex(id::GitShortHash) = hex(id.hash)[1:id.len]
raw(id::GitHash) = collect(id.val)
Base.string(id::AbstractGitHash) = hex(id)
Base.show(io::IO, id::GitHash) = print(io, "GitHash(\"$(string(id))\")")
Base.show(io::IO, id::GitShortHash) = print(io, "GitShortHash(\"$(string(id))\")")
Base.hash(id::GitHash, h::UInt) = hash(id.val, h)
function Base.cmp(id1::GitHash, id2::GitHash)
Int(ccall((:git_oid_cmp, :libgit2), Cint,
(Ptr{GitHash}, Ptr{GitHash}),
Ref(id1), Ref(id2)))
end
function Base.cmp(id1::GitShortHash, id2::GitShortHash)
# shortened hashes appear at the beginning of the order, i.e.
# 000 < 01 < 010 < 011 < 0112
c = Int(ccall((:git_oid_ncmp, :libgit2), Cint,
(Ptr{GitHash}, Ptr{GitHash}, Csize_t),
Ref(id1.hash), Ref(id2.hash), min(id1.len, id2.len)))
return c == 0 ? cmp(id1.len, id2.len) : c
end
Base.cmp(id1::GitHash, id2::GitShortHash) = cmp(GitShortHash(id1, OID_HEXSZ), id2)
Base.cmp(id1::GitShortHash, id2::GitHash) = cmp(id1, GitShortHash(id2, OID_HEXSZ))
==(id1::GitHash, id2::GitHash) = cmp(id1, id2) == 0
Base.isless(id1::AbstractGitHash, id2::AbstractGitHash) = cmp(id1, id2) < 0
function iszero(id::GitHash)
for i in 1:OID_RAWSZ
id.val[i] != zero(UInt8) && return false
end
return true
end
Base.zero(::Type{GitHash}) = GitHash()

View File

@@ -0,0 +1,81 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function GitRebase(repo::GitRepo, branch::GitAnnotated, upstream::GitAnnotated;
onto::Nullable{GitAnnotated}=Nullable{GitAnnotated}(),
opts::RebaseOptions = RebaseOptions())
rebase_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_rebase_init, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{Void}, Ptr{Void},
Ptr{Void}, Ptr{RebaseOptions}),
rebase_ptr_ptr, repo.ptr, branch.ptr, upstream.ptr,
isnull(onto) ? C_NULL : Base.get(onto).ptr, Ref(opts))
return GitRebase(repo, rebase_ptr_ptr[])
end
function Base.count(rb::GitRebase)
return ccall((:git_rebase_operation_entrycount, :libgit2), Csize_t, (Ptr{Void},), rb.ptr)
end
function current(rb::GitRebase)
return ccall((:git_rebase_operation_current, :libgit2), Csize_t, (Ptr{Void},), rb.ptr)
end
function Base.getindex(rb::GitRebase, i::Integer)
if !(1 <= i <= count(rb))
throw(BoundsError(rb, (i,)))
end
rb_op_ptr = ccall((:git_rebase_operation_byindex, :libgit2),
Ptr{RebaseOperation},
(Ptr{Void}, Csize_t), rb.ptr, i-1)
return unsafe_load(rb_op_ptr)
end
function Base.next(rb::GitRebase)
rb_op_ptr_ptr = Ref{Ptr{RebaseOperation}}(C_NULL)
try
@check ccall((:git_rebase_next, :libgit2), Cint,
(Ptr{Ptr{RebaseOperation}}, Ptr{Void}),
rb_op_ptr_ptr, rb.ptr)
catch err
err.code == Error.ITEROVER && return nothing
rethrow(err)
end
return unsafe_load(rb_op_ptr_ptr[])
end
function Base.show(io::IO, rb::GitRebase)
println(io, "GitRebase:")
println(io, "Number: ", count(rb))
println(io, "Currently performing operation: ", current(rb)+1)
end
"""
LibGit2.commit(rb::GitRebase, sig::GitSignature)
Commits the current patch to the rebase `rb`, using `sig` as the committer. Is silent if
the commit has already been applied.
"""
function commit(rb::GitRebase, sig::GitSignature)
oid_ptr = Ref(GitHash())
try
@check ccall((:git_rebase_commit, :libgit2), Error.Code,
(Ptr{GitHash}, Ptr{Void}, Ptr{SignatureStruct}, Ptr{SignatureStruct}, Ptr{UInt8}, Ptr{UInt8}),
oid_ptr, rb.ptr, C_NULL, sig.ptr, C_NULL, C_NULL)
catch err
# TODO: return current HEAD instead
err.code == Error.EAPPLIED && return nothing
rethrow(err)
end
return oid_ptr[]
end
function abort(rb::GitRebase)
return ccall((:git_rebase_abort, :libgit2), Csize_t,
(Ptr{Void},), rb.ptr)
end
function finish(rb::GitRebase, sig::GitSignature)
return ccall((:git_rebase_finish, :libgit2), Csize_t,
(Ptr{Void}, Ptr{SignatureStruct}),
rb.ptr, sig.ptr)
end

View File

@@ -0,0 +1,347 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function GitReference(repo::GitRepo, refname::AbstractString)
ref_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_reference_lookup, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring),
ref_ptr_ptr, repo.ptr, refname)
return GitReference(repo, ref_ptr_ptr[])
end
function GitReference(repo::GitRepo, obj_oid::GitHash, refname::AbstractString = Consts.HEAD_FILE;
force::Bool=false, msg::AbstractString="")
ref_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_reference_create, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{UInt8}, Ptr{GitHash}, Cint, Cstring),
ref_ptr_ptr, repo.ptr, refname, Ref(obj_oid), Cint(force),
isempty(msg) ? C_NULL : msg)
return GitReference(repo, ref_ptr_ptr[])
end
"""
LibGit2.isorphan(repo::GitRepo)
Checks if the current branch is an "orphan" branch, i.e. has no commits. The first commit
to this branch will have no parents.
"""
function isorphan(repo::GitRepo)
r = @check ccall((:git_repository_head_unborn, :libgit2), Cint,
(Ptr{Void},), repo.ptr)
r != 0
end
"""
LibGit2.head(repo::GitRepo) -> GitReference
Returns a `GitReference` to the current HEAD of `repo`.
"""
function head(repo::GitRepo)
head_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_repository_head, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}), head_ptr_ptr, repo.ptr)
return GitReference(repo, head_ptr_ptr[])
end
"""
LibGit2.shortname(ref::GitReference)
Returns a shortened version of the name of `ref` that's
"human-readable".
```julia-repl
julia> repo = LibGit2.GitRepo(path_to_repo);
julia> branch_ref = LibGit2.head(repo);
julia> LibGit2.name(branch_ref)
"refs/heads/master"
julia> LibGit2.shortname(branch_ref)
"master"
```
"""
function shortname(ref::GitReference)
isempty(ref) && return ""
name_ptr = ccall((:git_reference_shorthand, :libgit2), Cstring, (Ptr{Void},), ref.ptr)
name_ptr == C_NULL && return ""
return unsafe_string(name_ptr)
end
"""
LibGit2.reftype(ref::GitReference) -> Cint
Returns a `Cint` corresponding to the type of `ref`:
* `0` if the reference is invalid
* `1` if the reference is an object id
* `2` if the reference is symbolic
"""
function reftype(ref::GitReference)
return ccall((:git_reference_type, :libgit2), Cint, (Ptr{Void},), ref.ptr)
end
"""
LibGit2.fullname(ref::GitReference)
Return the name of the reference pointed to by the
symbolic reference `ref`. If `ref` is not a symbolic
reference, returns an empty string.
"""
function fullname(ref::GitReference)
isempty(ref) && return ""
reftype(ref) == Consts.REF_OID && return ""
rname = ccall((:git_reference_symbolic_target, :libgit2), Cstring, (Ptr{Void},), ref.ptr)
rname == C_NULL && return ""
return unsafe_string(rname)
end
"""
LibGit2.name(ref::GitReference)
Return the full name of `ref`.
"""
function name(ref::GitReference)
isempty(ref) && return ""
name_ptr = ccall((:git_reference_name, :libgit2), Cstring, (Ptr{Void},), ref.ptr)
name_ptr == C_NULL && return ""
return unsafe_string(name_ptr)
end
function branch(ref::GitReference)
isempty(ref) && return ""
str_ptr_ptr = Ref{Cstring}()
@check ccall((:git_branch_name, :libgit2), Cint,
(Ptr{Cstring}, Ptr{Void},), str_ptr_ptr, ref.ptr)
return unsafe_string(str_ptr_ptr[])
end
function ishead(ref::GitReference)
isempty(ref) && return false
err = ccall((:git_branch_is_head, :libgit2), Cint,
(Ptr{Void},), ref.ptr)
return err == 1
end
function isbranch(ref::GitReference)
isempty(ref) && return false
err = ccall((:git_reference_is_branch, :libgit2), Cint,
(Ptr{Void},), ref.ptr)
return err == 1
end
function istag(ref::GitReference)
isempty(ref) && return false
err = ccall((:git_reference_is_tag, :libgit2), Cint,
(Ptr{Void},), ref.ptr)
return err == 1
end
function isremote(ref::GitReference)
isempty(ref) && return false
err = ccall((:git_reference_is_remote, :libgit2), Cint,
(Ptr{Void},), ref.ptr)
return err == 1
end
function Base.show(io::IO, ref::GitReference)
println(io, "GitReference:")
if isremote(ref)
println(io, "Remote with name ", name(ref))
elseif isbranch(ref)
println(io, "Branch with name ", name(ref))
if ishead(ref)
println(io, "Branch is HEAD.")
else
println(io, "Branch is not HEAD.")
end
elseif istag(ref)
println(io, "Tag with name ", name(ref))
end
end
"""
peel([T,] ref::GitReference)
Recursively peel `ref` until an object of type `T` is obtained. If no `T` is provided,
then `ref` will be peeled until an object other than a [`GitTag`](@ref) is obtained.
- A `GitTag` will be peeled to the object it references.
- A [`GitCommit`](@ref) will be peeled to a [`GitTree`](@ref).
!!! note
Only annotated tags can be peeled to `GitTag` objects. Lightweight tags (the default)
are references under `refs/tags/` which point directly to `GitCommit` objects.
"""
function peel{T<:GitObject}(::Type{T}, ref::GitReference)
obj_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_reference_peel, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cint), obj_ptr_ptr, ref.ptr, Consts.OBJECT(T))
return T(ref.owner, obj_ptr_ptr[])
end
peel(ref::GitReference) = peel(GitObject, ref)
"""
LibGit2.ref_list(repo::GitRepo) -> Vector{String}
Get a list of all reference names in the `repo` repository.
"""
function ref_list(repo::GitRepo)
sa_ref = Ref(StrArrayStruct())
@check ccall((:git_reference_list, :libgit2), Cint,
(Ptr{StrArrayStruct}, Ptr{Void}), sa_ref, repo.ptr)
res = convert(Vector{String}, sa_ref[])
free(sa_ref)
res
end
"""
LibGit2.create_branch(repo::GitRepo, bname::AbstractString, commit_obj::GitCommit; force::Bool=false)
Create a new branch in the repository `repo` with name `bname`, which
points to commit `commit_obj` (which has to be part of `repo`). If
`force` is `true`, overwrite an existing branch named `bname` if it
exists. If `force` is `false` and a branch already exists named `bname`,
this function will throw an error.
"""
function create_branch(repo::GitRepo,
bname::AbstractString,
commit_obj::GitCommit;
force::Bool=false)
ref_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_branch_create, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring, Ptr{Void}, Cint),
ref_ptr_ptr, repo.ptr, bname, commit_obj.ptr, Cint(force))
return GitReference(repo, ref_ptr_ptr[])
end
"""
LibGit2.delete_branch(branch::GitReference)
Delete the branch pointed to by `branch`.
"""
function delete_branch(branch::GitReference)
@check ccall((:git_branch_delete, :libgit2), Cint, (Ptr{Void},), branch.ptr)
end
"""
LibGit2.head!(repo::GitRepo, ref::GitReference) -> GitReference
Set the HEAD of `repo` to the object pointed to by `ref`.
"""
function head!(repo::GitRepo, ref::GitReference)
ref_name = name(ref)
@check ccall((:git_repository_set_head, :libgit2), Cint,
(Ptr{Void}, Cstring), repo.ptr, ref_name)
return ref
end
"""
lookup_branch(repo::GitRepo, branch_name::AbstractString, remote::Bool=false) -> Nullable{GitReference}
Determine if the branch specified by `branch_name` exists in the repository `repo`.
If `remote` is `true`, `repo` is assumed to be a remote git repository. Otherwise, it
is part of the local filesystem.
`lookup_branch` returns a [`Nullable`](@ref), which will be null if the requested branch does
not exist yet. If the branch does exist, the `Nullable` contains a `GitReference` to
the branch.
"""
function lookup_branch(repo::GitRepo,
branch_name::AbstractString,
remote::Bool=false)
ref_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
branch_type = remote ? Consts.BRANCH_REMOTE : Consts.BRANCH_LOCAL
err = ccall((:git_branch_lookup, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{UInt8}, Cint),
ref_ptr_ptr, repo.ptr, branch_name, branch_type)
if err != Int(Error.GIT_OK)
if err == Int(Error.ENOTFOUND)
return Nullable{GitReference}()
end
if ref_ptr_ptr[] != C_NULL
close(GitReference(repo, ref_ptr_ptr[]))
end
throw(Error.GitError(err))
end
return Nullable{GitReference}(GitReference(repo, ref_ptr_ptr[]))
end
"""
upstream(ref::GitReference) -> Nullable{GitReference}
Determine if the branch containing `ref` has a specified upstream branch.
`upstream` returns a [`Nullable`](@ref), which will be null if the requested branch does
not have an upstream counterpart. If the upstream branch does exist, the `Nullable`
contains a `GitReference` to the upstream branch.
"""
function upstream(ref::GitReference)
isempty(ref) && return nothing
ref_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
err = ccall((:git_branch_upstream, :libgit2), Cint,
(Ref{Ptr{Void}}, Ptr{Void},), ref_ptr_ptr, ref.ptr)
if err != Int(Error.GIT_OK)
if err == Int(Error.ENOTFOUND)
return Nullable{GitReference}()
end
if ref_ptr_ptr[] != C_NULL
close(GitReference(ref.owner, ref_ptr_ptr[]))
end
throw(Error.GitError(err))
end
return Nullable{GitReference}(GitReference(ref.owner, ref_ptr_ptr[]))
end
repository(ref::GitReference) = ref.owner
function target!(ref::GitReference, new_oid::GitHash; msg::AbstractString="")
ref_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_reference_set_target, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{GitHash}, Cstring),
ref_ptr_ptr, ref.ptr, Ref(new_oid), isempty(msg) ? C_NULL : msg)
return GitReference(ref.owner, ref_ptr_ptr[])
end
function GitBranchIter(repo::GitRepo, flags::Cint=Cint(Consts.BRANCH_LOCAL))
bi_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_branch_iterator_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cint), bi_ptr, repo.ptr, flags)
return GitBranchIter(repo, bi_ptr[])
end
function Base.start(bi::GitBranchIter)
ref_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
btype = Ref{Cint}()
err = ccall((:git_branch_next, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Cint}, Ptr{Void}),
ref_ptr_ptr, btype, bi.ptr)
err != Int(Error.GIT_OK) && return (nothing, -1, true)
return (GitReference(bi.owner, ref_ptr_ptr[]), btype[], false)
end
Base.done(bi::GitBranchIter, state) = Bool(state[3])
function Base.next(bi::GitBranchIter, state)
ref_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
btype = Ref{Cint}()
err = ccall((:git_branch_next, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Cint}, Ptr{Void}),
ref_ptr_ptr, btype, bi.ptr)
err != Int(Error.GIT_OK) && return (state[1:2], (nothing, -1, true))
return (state[1:2], (GitReference(bi.owner, ref_ptr_ptr[]), btype[], false))
end
Base.iteratorsize(::Type{GitBranchIter}) = Base.SizeUnknown()
function Base.map(f::Function, bi::GitBranchIter)
res = nothing
s = start(bi)
while !done(bi, s)
val = f(s[1:2])
if res === nothing
res = Vector{typeof(val)}(0)
end
push!(res, val)
val, s = next(bi, s)
end
return res
end

View File

@@ -0,0 +1,254 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
GitRemote(repo::GitRepo, rmt_name::AbstractString, rmt_url::AbstractString) -> GitRemote
Look up a remote git repository using its name and URL. Uses the default fetch refspec.
# Example
```julia
repo = LibGit2.init(repo_path)
remote = LibGit2.GitRemote(repo, "upstream", repo_url)
```
"""
function GitRemote(repo::GitRepo, rmt_name::AbstractString, rmt_url::AbstractString)
rmt_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_remote_create, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring, Cstring),
rmt_ptr_ptr, repo.ptr, rmt_name, rmt_url)
return GitRemote(repo, rmt_ptr_ptr[])
end
"""
GitRemote(repo::GitRepo, rmt_name::AbstractString, rmt_url::AbstractString, fetch_spec::AbstractString) -> GitRemote
Look up a remote git repository using the repository's name and URL,
as well as specifications for how to fetch from the remote
(e.g. which remote branch to fetch from).
# Example
```julia
repo = LibGit2.init(repo_path)
refspec = "+refs/heads/mybranch:refs/remotes/origin/mybranch"
remote = LibGit2.GitRemote(repo, "upstream", repo_url, refspec)
```
"""
function GitRemote(repo::GitRepo, rmt_name::AbstractString, rmt_url::AbstractString, fetch_spec::AbstractString)
rmt_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_remote_create_with_fetchspec, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring, Cstring, Cstring),
rmt_ptr_ptr, repo.ptr, rmt_name, rmt_url, fetch_spec)
return GitRemote(repo, rmt_ptr_ptr[])
end
"""
GitRemoteAnon(repo::GitRepo, url::AbstractString) -> GitRemote
Look up a remote git repository using only its URL, not its name.
# Example
```julia
repo = LibGit2.init(repo_path)
remote = LibGit2.GitRemoteAnon(repo, repo_url)
```
"""
function GitRemoteAnon(repo::GitRepo, url::AbstractString)
rmt_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_remote_create_anonymous, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring),
rmt_ptr_ptr, repo.ptr, url)
return GitRemote(repo, rmt_ptr_ptr[])
end
function get(::Type{GitRemote}, repo::GitRepo, rmt_name::AbstractString)
rmt_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_remote_lookup, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring),
rmt_ptr_ptr, repo.ptr, rmt_name)
return GitRemote(repo, rmt_ptr_ptr[])
end
"""
url(rmt::GitRemote)
Get the fetch URL of a remote git repository.
# Example
```julia-repl
julia> repo_url = "https://github.com/JuliaLang/Example.jl";
julia> repo = LibGit2.init(mktempdir());
julia> remote = LibGit2.GitRemote(repo, "origin", repo_url);
julia> LibGit2.url(remote)
"https://github.com/JuliaLang/Example.jl"
```
"""
function url(rmt::GitRemote)
url_ptr = ccall((:git_remote_url, :libgit2), Cstring, (Ptr{Void},), rmt.ptr)
url_ptr == C_NULL && return ""
return unsafe_string(url_ptr)
end
"""
push_url(rmt::GitRemote)
Get the push URL of a remote git repository.
"""
function push_url(rmt::GitRemote)
url_ptr = ccall((:git_remote_pushurl, :libgit2), Cstring, (Ptr{Void},), rmt.ptr)
url_ptr == C_NULL && return ""
return unsafe_string(url_ptr)
end
"""
name(rmt::GitRemote)
Get the name of a remote repository, for instance `"origin"`.
If the remote is anonymous (see [`GitRemoteAnon`](@ref))
the name will be an empty string `""`.
# Example
```julia-repl
julia> repo_url = "https://github.com/JuliaLang/Example.jl";
julia> repo = LibGit2.clone(cache_repo, "test_directory");
julia> remote = LibGit2.GitRemote(repo, "origin", repo_url);
julia> name(remote)
"origin"
```
"""
function name(rmt::GitRemote)
name_ptr = ccall((:git_remote_name, :libgit2), Cstring, (Ptr{Void},), rmt.ptr)
name_ptr == C_NULL && return ""
return unsafe_string(name_ptr)
end
"""
fetch_refspecs(rmt::GitRemote) -> Vector{String}
Get the *fetch* refspecs for the specified `rmt`. These refspecs contain
information about which branch(es) to fetch from.
"""
function fetch_refspecs(rmt::GitRemote)
sa_ref = Ref(StrArrayStruct())
@check ccall((:git_remote_get_fetch_refspecs, :libgit2), Cint,
(Ptr{StrArrayStruct}, Ptr{Void}), sa_ref, rmt.ptr)
res = convert(Vector{String}, sa_ref[])
free(sa_ref)
res
end
"""
push_refspecs(rmt::GitRemote) -> Vector{String}
Get the *push* refspecs for the specified `rmt`. These refspecs contain
information about which branch(es) to push to.
"""
function push_refspecs(rmt::GitRemote)
sa_ref = Ref(StrArrayStruct())
@check ccall((:git_remote_get_push_refspecs, :libgit2), Cint,
(Ptr{StrArrayStruct}, Ptr{Void}), sa_ref, rmt.ptr)
res = convert(Vector{String}, sa_ref[])
free(sa_ref)
res
end
"""
add_fetch!(repo::GitRepo, rmt::GitRemote, fetch_spec::String)
Add a *fetch* refspec for the specified `rmt`. This refspec will contain
information about which branch(es) to fetch from.
# Example
```julia-repl
julia> LibGit2.add_fetch!(repo, remote, "upstream");
julia> LibGit2.fetch_refspecs(remote)
String["+refs/heads/*:refs/remotes/upstream/*"]
```
"""
function add_fetch!(repo::GitRepo, rmt::GitRemote, fetch_spec::String)
@check ccall((:git_remote_add_fetch, :libgit2), Cint,
(Ptr{Void}, Cstring, Cstring), repo.ptr,
name(rmt), fetch_spec)
end
"""
add_push!(repo::GitRepo, rmt::GitRemote, push_spec::String)
Add a *push* refspec for the specified `rmt`. This refspec will contain
information about which branch(es) to push to.
# Example
```julia-repl
julia> LibGit2.add_push!(repo, remote, "refs/heads/master");
julia> remote = LibGit2.get(LibGit2.GitRemote, repo, branch);
julia> LibGit2.push_refspecs(remote)
String["refs/heads/master"]
```
!!! note
You may need to [`close`](@ref) and reopen the `GitRemote`
in question after updating its push refspecs in order for
the change to take effect and for calls to [`push`](@ref)
to work.
"""
function add_push!(repo::GitRepo, rmt::GitRemote, push_spec::String)
@check ccall((:git_remote_add_push, :libgit2), Cint,
(Ptr{Void}, Cstring, Cstring), repo.ptr,
name(rmt), push_spec)
end
"""
fetch(rmt::GitRemote, refspecs; options::FetchOptions=FetchOptions(), msg="")
Fetch from the specified `rmt` remote git repository, using `refspecs` to
determine which remote branch(es) to fetch.
The keyword arguments are:
* `options`: determines the options for the fetch, e.g. whether to prune afterwards.
* `msg`: a message to insert into the reflogs.
"""
function fetch(rmt::GitRemote, refspecs::Vector{<:AbstractString};
options::FetchOptions = FetchOptions(),
msg::AbstractString="")
msg = "libgit2.fetch: $msg"
@check ccall((:git_remote_fetch, :libgit2), Cint,
(Ptr{Void}, Ptr{StrArrayStruct}, Ptr{FetchOptions}, Cstring),
rmt.ptr, isempty(refspecs) ? C_NULL : refspecs, Ref(options), msg)
end
"""
push(rmt::GitRemote, refspecs; force::Bool=false, options::PushOptions=PushOptions())
Push to the specified `rmt` remote git repository, using `refspecs` to
determine which remote branch(es) to push to.
The keyword arguments are:
* `force`: if `true`, a force-push will occur, disregarding conflicts.
* `options`: determines the options for the push, e.g. which proxy headers to use.
!!! note
You can add information about the push refspecs in two other ways: by setting
an option in the repository's `GitConfig` (with `push.default` as the key) or
by calling [`add_push!`](@ref). Otherwise you will need to explicitly specify
a push refspec in the call to `push` for it to have any effect, like so:
`LibGit2.push(repo, refspecs=["refs/heads/master"])`.
"""
function push(rmt::GitRemote, refspecs::Vector{<:AbstractString};
force::Bool = false, options::PushOptions = PushOptions())
@check ccall((:git_remote_push, :libgit2), Cint,
(Ptr{Void}, Ptr{StrArrayStruct}, Ptr{PushOptions}),
rmt.ptr, isempty(refspecs) ? C_NULL : refspecs, Ref(options))
end
Base.show(io::IO, rmt::GitRemote) = print(io, "GitRemote:\nRemote name: ", name(rmt), " url: ", url(rmt))

View File

@@ -0,0 +1,309 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
LibGit2.GitRepo(path::AbstractString)
Opens a git repository at `path`.
"""
function GitRepo(path::AbstractString)
repo_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_repository_open, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring), repo_ptr_ptr, path)
return GitRepo(repo_ptr_ptr[])
end
"""
LibGit2.GitRepoExt(path::AbstractString, flags::Cuint = Cuint(Consts.REPOSITORY_OPEN_DEFAULT))
Opens a git repository at `path` with extended controls (for instance, if the current
user must be a member of a special access group to read `path`).
"""
function GitRepoExt(path::AbstractString, flags::Cuint = Cuint(Consts.REPOSITORY_OPEN_DEFAULT))
separator = @static is_windows() ? ";" : ":"
repo_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_repository_open_ext, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cuint, Cstring),
repo_ptr_ptr, path, flags, separator)
return GitRepo(repo_ptr_ptr[])
end
function cleanup(r::GitRepo)
if r.ptr != C_NULL
ccall((:git_repository__cleanup, :libgit2), Void, (Ptr{Void},), r.ptr)
end
end
"""
LibGit2.init(path::AbstractString, bare::Bool=false) -> GitRepo
Opens a new git repository at `path`. If `bare` is `false`,
the working tree will be created in `path/.git`. If `bare`
is `true`, no working directory will be created.
"""
function init(path::AbstractString, bare::Bool=false)
repo_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_repository_init, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cuint), repo_ptr_ptr, path, bare)
return GitRepo(repo_ptr_ptr[])
end
"""
LibGit2.head_oid(repo::GitRepo) -> GitHash
Lookup the object id of the current HEAD of git
repository `repo`.
"""
function head_oid(repo::GitRepo)
head_ref = head(repo)
try
return GitHash(head_ref)
finally
close(head_ref)
end
end
"""
LibGit2.headname(repo::GitRepo)
Lookup the name of the current HEAD of git
repository `repo`. If `repo` is currently
detached, returns the name of the HEAD it's
detached from.
"""
function headname(repo::GitRepo)
with(head(repo)) do href
if isattached(repo)
shortname(href)
else
"(detached from $(string(GitHash(href))[1:7]))"
end
end
end
function isbare(repo::GitRepo)
return ccall((:git_repository_is_bare, :libgit2), Cint, (Ptr{Void},), repo.ptr) == 1
end
function isattached(repo::GitRepo)
ccall((:git_repository_head_detached, :libgit2), Cint, (Ptr{Void},), repo.ptr) != 1
end
@doc """
GitObject(repo::GitRepo, hash::AbstractGitHash)
GitObject(repo::GitRepo, spec::AbstractString)
Return the specified object ([`GitCommit`](@ref), [`GitBlob`](@ref), [`GitTree`](@ref) or [`GitTag`](@ref)) from `repo`
specified by `hash`/`spec`.
- `hash` is a full (`GitHash`) or partial (`GitShortHash`) hash.
- `spec` is a textual specification: see [the git docs](https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions) for a full list.
""" GitObject
for T in (:GitCommit, :GitBlob, :GitTree, :GitTag)
@eval @doc $"""
$T(repo::GitRepo, hash::AbstractGitHash)
$T(repo::GitRepo, spec::AbstractString)
Return a `$T` object from `repo` specified by `hash`/`spec`.
- `hash` is a full (`GitHash`) or partial (`GitShortHash`) hash.
- `spec` is a textual specification: see [the git docs](https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions) for a full list.
""" $T
end
function (::Type{T})(repo::GitRepo, spec::AbstractString) where T<:GitObject
obj_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_revparse_single, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring), obj_ptr_ptr, repo.ptr, spec)
# check object is of correct type
if T != GitObject && T != GitUnknownObject
t = Consts.OBJECT(obj_ptr_ptr[])
t == Consts.OBJECT(T) || throw(GitError(Error.Object, Error.ERROR, "Expected object of type $T, received object of type $(objtype(t))"))
end
return T(repo, obj_ptr_ptr[])
end
function (::Type{T})(repo::GitRepo, oid::GitHash) where T<:GitObject
oid_ptr = Ref(oid)
obj_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_object_lookup, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{GitHash}, Consts.OBJECT),
obj_ptr_ptr, repo.ptr, oid_ptr, Consts.OBJECT(T))
return T(repo, obj_ptr_ptr[])
end
function (::Type{T})(repo::GitRepo, oid::GitShortHash) where T<:GitObject
oid_ptr = Ref(oid.hash)
obj_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_object_lookup_prefix, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{GitHash}, Csize_t, Consts.OBJECT),
obj_ptr_ptr, repo.ptr, oid_ptr, oid.len, Consts.OBJECT(T))
return T(repo, obj_ptr_ptr[])
end
# TODO: deprecate this function
revparseid(repo::GitRepo, spec) = GitHash(GitUnknownObject(repo, spec))
"""
LibGit2.gitdir(repo::GitRepo)
Returns the location of the "git" files of `repo`:
- for normal repositories, this is the location of the `.git` folder.
- for bare repositories, this is the location of the repository itself.
See also [`workdir`](@ref), [`path`](@ref).
"""
function gitdir(repo::GitRepo)
return unsafe_string(ccall((:git_repository_path, :libgit2), Cstring,
(Ptr{Void},), repo.ptr))
end
"""
LibGit2.workdir(repo::GitRepo)
The location of the working directory of `repo`. This will throw an error for bare
repositories.
!!! note
This will typically be the parent directory of `gitdir(repo)`, but can be different in
some cases: e.g. if either the `core.worktree` configuration variable or the
`GIT_WORK_TREE` environment variable is set.
See also [`gitdir`](@ref), [`path`](@ref).
"""
function workdir(repo::GitRepo)
sptr = ccall((:git_repository_workdir, :libgit2), Cstring,
(Ptr{Void},), repo.ptr)
sptr == C_NULL && throw(GitError(Error.Object, Error.ERROR, "No working directory found."))
return unsafe_string(sptr)
end
"""
LibGit2.path(repo::GitRepo)
The base file path of the repository `repo`.
- for normal repositories, this will typically be the parent directory of the ".git"
directory (note: this may be different than the working directory, see `workdir` for
more details).
- for bare repositories, this is the location of the "git" files.
See also [`gitdir`](@ref), [`workdir`](@ref).
"""
function path(repo::GitRepo)
d = gitdir(repo)
if isdirpath(d)
d = dirname(d) # strip trailing separator
end
if isbare(repo)
return d
else
parent, base = splitdir(d)
return base == ".git" ? parent : d
end
end
"""
peel([T,] obj::GitObject)
Recursively peel `obj` until an object of type `T` is obtained. If no `T` is provided,
then `obj` will be peeled until the type changes.
- A `GitTag` will be peeled to the object it references.
- A `GitCommit` will be peeled to a `GitTree`.
"""
function peel(::Type{T}, obj::GitObject) where T<:GitObject
new_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_object_peel, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cint), new_ptr_ptr, obj.ptr, Consts.OBJECT(T))
return T(obj.owner, new_ptr_ptr[])
end
peel(obj::GitObject) = peel(GitObject, obj)
function checkout_tree(repo::GitRepo, obj::GitObject;
options::CheckoutOptions = CheckoutOptions())
@check ccall((:git_checkout_tree, :libgit2), Cint,
(Ptr{Void}, Ptr{Void}, Ptr{CheckoutOptions}),
repo.ptr, obj.ptr, Ref(options))
end
function checkout_index(repo::GitRepo, idx::Nullable{GitIndex} = Nullable{GitIndex}();
options::CheckoutOptions = CheckoutOptions())
@check ccall((:git_checkout_index, :libgit2), Cint,
(Ptr{Void}, Ptr{Void}, Ptr{CheckoutOptions}),
repo.ptr,
isnull(idx) ? C_NULL : Base.get(idx).ptr,
Ref(options))
end
function checkout_head(repo::GitRepo; options::CheckoutOptions = CheckoutOptions())
@check ccall((:git_checkout_head, :libgit2), Cint,
(Ptr{Void}, Ptr{CheckoutOptions}),
repo.ptr, Ref(options))
end
"""Updates some entries, determined by the `pathspecs`, in the index from the target commit tree."""
function reset!(repo::GitRepo, obj::Nullable{<:GitObject}, pathspecs::AbstractString...)
@check ccall((:git_reset_default, :libgit2), Cint,
(Ptr{Void}, Ptr{Void}, Ptr{StrArrayStruct}),
repo.ptr,
isnull(obj) ? C_NULL: Base.get(obj).ptr,
collect(pathspecs))
return head_oid(repo)
end
"""Sets the current head to the specified commit oid and optionally resets the index and working tree to match."""
function reset!(repo::GitRepo, obj::GitObject, mode::Cint;
checkout_opts::CheckoutOptions = CheckoutOptions())
@check ccall((:git_reset, :libgit2), Cint,
(Ptr{Void}, Ptr{Void}, Cint, Ptr{CheckoutOptions}),
repo.ptr, obj.ptr, mode, Ref(checkout_opts))
return head_oid(repo)
end
function clone(repo_url::AbstractString, repo_path::AbstractString,
clone_opts::CloneOptions)
clone_opts_ref = Ref(clone_opts)
repo_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_clone, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cstring, Ref{CloneOptions}),
repo_ptr_ptr, repo_url, repo_path, clone_opts_ref)
return GitRepo(repo_ptr_ptr[])
end
function fetchheads(repo::GitRepo)
fhr = Ref{Vector{FetchHead}}(FetchHead[])
ffcb = fetchhead_foreach_cb()
@check ccall((:git_repository_fetchhead_foreach, :libgit2), Cint,
(Ptr{Void}, Ptr{Void}, Ptr{Void}),
repo.ptr, ffcb, fhr)
return fhr[]
end
"""
LibGit2.remotes(repo::GitRepo)
Returns a vector of the names of the remotes of `repo`.
"""
function remotes(repo::GitRepo)
sa_ref = Ref(StrArrayStruct())
@check ccall((:git_remote_list, :libgit2), Cint,
(Ptr{StrArrayStruct}, Ptr{Void}), sa_ref, repo.ptr)
res = convert(Vector{String}, sa_ref[])
free(sa_ref)
return res
end
function Base.show(io::IO, repo::GitRepo)
print(io, "LibGit2.GitRepo(")
show(io, path(repo))
print(io, ")")
end

View File

@@ -0,0 +1,51 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function Signature(ptr::Ptr{SignatureStruct})
sig = unsafe_load(ptr)::SignatureStruct
name = unsafe_string(sig.name)
email = unsafe_string(sig.email)
time = sig.when.time
offset = sig.when.offset
return Signature(name, email, time, offset)
end
Signature(sig::GitSignature) = Signature(sig.ptr)
function Signature(name::AbstractString, email::AbstractString)
sig_ptr_ptr = Ref{Ptr{SignatureStruct}}(C_NULL)
@check ccall((:git_signature_now, :libgit2), Cint,
(Ptr{Ptr{SignatureStruct}}, Cstring, Cstring), sig_ptr_ptr, name, email)
sig = GitSignature(sig_ptr_ptr[])
s = Signature(sig.ptr)
close(sig)
return s
end
function Signature(repo::GitRepo)
sig = default_signature(repo)
s = Signature(sig.ptr)
close(sig)
return s
end
function Base.convert(::Type{GitSignature}, sig::Signature)
sig_ptr_ptr = Ref{Ptr{SignatureStruct}}(C_NULL)
@check ccall((:git_signature_new, :libgit2), Cint,
(Ptr{Ptr{SignatureStruct}}, Cstring, Cstring, Int64, Cint),
sig_ptr_ptr, sig.name, sig.email, sig.time, sig.time_offset)
return GitSignature(sig_ptr_ptr[])
end
function Base.show(io::IO, sig::Signature)
print(io, "Name: ", sig.name, ", ")
print(io, "Email: ", sig.email, ", ")
print(io, "Time: ", Dates.unix2datetime(sig.time + 60*sig.time_offset))
@printf(io, "%+03i:%02i", divrem(sig.time_offset, 60)...)
end
"""Return signature object. Free it after use."""
function default_signature(repo::GitRepo)
sig_ptr_ptr = Ref{Ptr{SignatureStruct}}(C_NULL)
@check ccall((:git_signature_default, :libgit2), Cint,
(Ptr{Ptr{SignatureStruct}}, Ptr{Void}), sig_ptr_ptr, repo.ptr)
return GitSignature(sig_ptr_ptr[])
end

View File

@@ -0,0 +1,50 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
LibGit2.GitStatus(repo::GitRepo; status_opts=StatusOptions())
Collect information about the status of each file in the git
repository `repo` (e.g. is the file modified, staged, etc.).
`status_opts` can be used to set various options, for instance
whether or not to look at untracked files or whether to include
submodules or not.
"""
function GitStatus(repo::GitRepo; status_opts=StatusOptions())
stat_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_status_list_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ptr{StatusOptions}),
stat_ptr_ptr, repo.ptr, Ref(status_opts))
return GitStatus(repo, stat_ptr_ptr[])
end
function Base.length(status::GitStatus)
return Int(ccall((:git_status_list_entrycount, :libgit2), Csize_t,
(Ptr{Ptr{Void}},), status.ptr))
end
function Base.getindex(status::GitStatus, i::Integer)
1 <= i <= length(status) || throw(BoundsError())
entry_ptr = ccall((:git_status_byindex, :libgit2),
Ptr{StatusEntry},
(Ptr{Void}, Csize_t),
status.ptr, i-1)
entry_ptr == C_NULL && throw(Error.GitError(Error.ERROR))
return unsafe_load(entry_ptr)
end
"""
LibGit2.status(repo::GitRepo, path::String)
Lookup the status of the file at `path` in the git
repository `repo`. For instance, this can be used
to check if the file at `path` has been modified
and needs to be staged and committed.
"""
function status(repo::GitRepo, path::String)
status_ptr = Ref{Cuint}(0)
ret = ccall((:git_status_file, :libgit2), Cint,
(Ref{Cuint}, Ptr{Void}, Cstring),
status_ptr, repo.ptr, path)
(ret == Cint(Error.ENOTFOUND) || ret == Cint(Error.EAMBIGUOUS)) && return Nullable{Cuint}()
return Nullable(status_ptr[])
end

View File

@@ -0,0 +1,15 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function Base.cconvert(::Type{Ptr{StrArrayStruct}}, x::Vector)
str_ref = Base.cconvert(Ref{Cstring}, x)
sa_ref = Ref(StrArrayStruct(Base.unsafe_convert(Ref{Cstring}, str_ref), length(x)))
sa_ref, str_ref
end
function Base.unsafe_convert(::Type{Ptr{StrArrayStruct}}, rr::Tuple{Ref{StrArrayStruct}, Ref{Cstring}})
Base.unsafe_convert(Ptr{StrArrayStruct}, first(rr))
end
function Base.convert(::Type{Vector{String}}, sa::StrArrayStruct)
[unsafe_string(unsafe_load(sa.strings, i)) for i = 1:sa.count]
end

View File

@@ -0,0 +1,77 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
LibGit2.tag_list(repo::GitRepo) -> Vector{String}
Get a list of all tags in the git repository `repo`.
"""
function tag_list(repo::GitRepo)
sa_ref = Ref(StrArrayStruct())
@check ccall((:git_tag_list, :libgit2), Cint,
(Ptr{StrArrayStruct}, Ptr{Void}), sa_ref, repo.ptr)
res = convert(Vector{String}, sa_ref[])
free(sa_ref)
res
end
"""
LibGit2.tag_delete(repo::GitRepo, tag::AbstractString)
Remove the git tag `tag` from the repository `repo`.
"""
function tag_delete(repo::GitRepo, tag::AbstractString)
@check ccall((:git_tag_delete, :libgit2), Cint,
(Ptr{Void}, Cstring), repo.ptr, tag)
end
"""
LibGit2.tag_create(repo::GitRepo, tag::AbstractString, commit; kwargs...)
Create a new git tag `tag` (e.g. `"v0.5"`) in the repository `repo`, at
the commit `commit`.
The keyword arguments are:
* `msg::AbstractString=""`: the message for the tag.
* `force::Bool=false`: if `true`, existing references will be overwritten.
* `sig::Signature=Signature(repo)`: the tagger's signature.
"""
function tag_create(repo::GitRepo, tag::AbstractString, commit::Union{AbstractString,AbstractGitHash};
msg::AbstractString = "",
force::Bool = false,
sig::Signature = Signature(repo))
oid_ptr = Ref(GitHash())
with(GitCommit(repo, commit)) do commit_obj
commit_obj === nothing && return oid_ptr[] # return empty oid
with(convert(GitSignature, sig)) do git_sig
@check ccall((:git_tag_create, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Void}, Cstring, Ptr{Void}, Ptr{SignatureStruct}, Cstring, Cint),
oid_ptr, repo.ptr, tag, commit_obj.ptr, git_sig.ptr, msg, Cint(force))
end
end
return oid_ptr[]
end
"""
LibGit2.name(tag::GitTag)
The name of `tag` (e.g. `"v0.5"`).
"""
function name(tag::GitTag)
str_ptr = ccall((:git_tag_name, :libgit2), Cstring, (Ptr{Void},), tag.ptr)
str_ptr == C_NULL && throw(Error.GitError(Error.ERROR))
return unsafe_string(str_ptr)
end
# should we return the actual object? i.e. git_tag_target?
"""
LibGit2.target(tag::GitTag)
The `GitHash` of the target object of `tag`.
"""
function target(tag::GitTag)
oid_ptr = ccall((:git_tag_target_id, :libgit2), Ptr{GitHash}, (Ptr{Void},), tag.ptr)
oid_ptr == C_NULL && throw(Error.GitError(Error.ERROR))
return unsafe_load(oid_ptr)
end
Base.show(io::IO, tag::GitTag) = print(io, "GitTag:\nTag name: $(name(tag)) target: $(target(tag))")

View File

@@ -0,0 +1,76 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
Traverse the entries in a tree and its subtrees in post or pre order.
Function parameter should have following signature:
(Cstring, Ptr{Void}, Ptr{Void}) -> Cint
"""
function treewalk(f::Function, tree::GitTree, payload=Any[], post::Bool = false)
cbf = cfunction(f, Cint, Tuple{Cstring, Ptr{Void}, Ptr{Void}})
cbf_payload = Ref{typeof(payload)}(payload)
@check ccall((:git_tree_walk, :libgit2), Cint,
(Ptr{Void}, Cint, Ptr{Void}, Ptr{Void}),
tree.ptr, post, cbf, cbf_payload)
return cbf_payload
end
repository(tree::GitTree) = tree.owner
repository(te::GitTreeEntry) = repository(te.owner)
function filename(te::GitTreeEntry)
str = ccall((:git_tree_entry_name, :libgit2), Cstring, (Ptr{Void},), te.ptr)
str != C_NULL && return unsafe_string(str)
return nothing
end
function filemode(te::GitTreeEntry)
return ccall((:git_tree_entry_filemode, :libgit2), Cint, (Ptr{Void},), te.ptr)
end
function entrytype(te::GitTreeEntry)
otype = ccall((:git_tree_entry_type, :libgit2), Cint, (Ptr{Void},), te.ptr)
return objtype(Consts.OBJECT(otype))
end
function entryid(te::GitTreeEntry)
oid_ptr = ccall((:git_tree_entry_id, :libgit2), Ptr{UInt8}, (Ptr{Void},), te.ptr)
return GitHash(oid_ptr)
end
function Base.count(tree::GitTree)
return ccall((:git_tree_entrycount, :libgit2), Csize_t, (Ptr{Void},), tree.ptr)
end
function Base.getindex(tree::GitTree, i::Integer)
if i < 1 || i > count(tree)
throw(BoundsError(tree, i))
end
te_ptr = ccall((:git_tree_entry_byindex, :libgit2),
Ptr{Void},
(Ptr{Void}, Csize_t), tree.ptr, i-1)
return GitTreeEntry(tree, te_ptr, false)
end
function (::Type{T})(te::GitTreeEntry) where T<:GitObject
repo = repository(te)
obj_ptr_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_tree_entry_to_object, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Ref{Void}),
obj_ptr_ptr, repo.ptr, te.ptr)
return T(repo, obj_ptr_ptr[])
end
function Base.show(io::IO, te::GitTreeEntry)
println(io, "GitTreeEntry:")
println(io, "Entry name: ", filename(te))
println(io, "Entry type: ", entrytype(te))
println(io, "Entry OID: ", entryid(te))
end
function Base.show(io::IO, tree::GitTree)
println(io, "GitTree:")
println(io, "Owner: ", repository(tree))
println(io, "Number of entries: ", count(tree))
end

View File

@@ -0,0 +1,751 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
import Base.@kwdef
import .Consts: GIT_SUBMODULE_IGNORE, GIT_MERGE_FILE_FAVOR, GIT_MERGE_FILE
const OID_RAWSZ = 20
const OID_HEXSZ = OID_RAWSZ * 2
const OID_MINPREFIXLEN = 4
abstract type AbstractGitHash end
"""
GitHash
A git object identifier, based on the sha-1 hash. It is a $OID_RAWSZ byte string
($OID_HEXSZ hex digits) used to identify a `GitObject` in a repository.
"""
struct GitHash <: AbstractGitHash
val::NTuple{OID_RAWSZ, UInt8}
GitHash(val::NTuple{OID_RAWSZ, UInt8}) = new(val)
end
GitHash() = GitHash(ntuple(i->zero(UInt8), OID_RAWSZ))
"""
GitShortHash
This is a shortened form of `GitHash`, which can be used to identify a git object when it
is unique.
Internally it is stored as two fields: a full-size `GitHash` (`hash`) and a length
(`len`). Only the initial `len` hex digits of `hash` are used.
"""
struct GitShortHash <: AbstractGitHash
hash::GitHash # underlying hash: unused digits are ignored
len::Csize_t # length in hex digits
end
"""
LibGit2.TimeStruct
Time in a signature.
Matches the [`git_time`](https://libgit2.github.com/libgit2/#HEAD/type/git_time) struct.
"""
struct TimeStruct
time::Int64 # time in seconds from epoch
offset::Cint # timezone offset in minutes
end
"""
LibGit2.SignatureStruct
An action signature (e.g. for committers, taggers, etc).
Matches the [`git_signature`](https://libgit2.github.com/libgit2/#HEAD/type/git_signature) struct.
"""
struct SignatureStruct
name::Ptr{UInt8} # full name of the author
email::Ptr{UInt8} # email of the author
when::TimeStruct # time when the action happened
end
"""
LibGit2.StrArrayStruct
A LibGit2 representation of an array of strings.
Matches the [`git_strarray`](https://libgit2.github.com/libgit2/#HEAD/type/git_strarray) struct.
When fetching data from LibGit2, a typical usage would look like:
```julia
sa_ref = Ref(StrArrayStruct())
@check ccall(..., (Ptr{StrArrayStruct},), sa_ref)
res = convert(Vector{String}, sa_ref[])
free(sa_ref)
```
In particular, note that `LibGit2.free` should be called afterward on the `Ref` object.
Conversely, when passing a vector of strings to LibGit2, it is generally simplest to rely
on implicit conversion:
```julia
strs = String[...]
@check ccall(..., (Ptr{StrArrayStruct},), strs)
```
Note that no call to `free` is required as the data is allocated by Julia.
"""
struct StrArrayStruct
strings::Ptr{Cstring}
count::Csize_t
end
StrArrayStruct() = StrArrayStruct(C_NULL, 0)
function free(sa_ref::Base.Ref{StrArrayStruct})
ccall((:git_strarray_free, :libgit2), Void, (Ptr{StrArrayStruct},), sa_ref)
end
"""
LibGit2.Buffer
A data buffer for exporting data from libgit2.
Matches the [`git_buf`](https://libgit2.github.com/libgit2/#HEAD/type/git_buf) struct.
When fetching data from LibGit2, a typical usage would look like:
```julia
buf_ref = Ref(Buffer())
@check ccall(..., (Ptr{Buffer},), buf_ref)
# operation on buf_ref
free(buf_ref)
```
In particular, note that `LibGit2.free` should be called afterward on the `Ref` object.
"""
struct Buffer
ptr::Ptr{Cchar}
asize::Csize_t
size::Csize_t
end
Buffer() = Buffer(C_NULL, 0, 0)
function free(buf_ref::Base.Ref{Buffer})
ccall((:git_buf_free, :libgit2), Void, (Ptr{Buffer},), buf_ref)
end
"Abstract credentials payload"
abstract type AbstractCredentials end
"Checks if credentials were used"
checkused!(p::AbstractCredentials) = true
checkused!(p::Void) = false
"Resets credentials for another use"
reset!(p::AbstractCredentials, cnt::Int=3) = nothing
"""
LibGit2.CheckoutOptions
Matches the [`git_checkout_options`](https://libgit2.github.com/libgit2/#HEAD/type/git_checkout_options) struct.
"""
@kwdef struct CheckoutOptions
version::Cuint = 1
checkout_strategy::Cuint = Consts.CHECKOUT_SAFE
disable_filters::Cint
dir_mode::Cuint
file_mode::Cuint
file_open_flags::Cint
notify_flags::Cuint = Consts.CHECKOUT_NOTIFY_NONE
notify_cb::Ptr{Void}
notify_payload::Ptr{Void}
progress_cb::Ptr{Void}
progress_payload::Ptr{Void}
paths::StrArrayStruct
baseline::Ptr{Void}
baseline_index::Ptr{Void}
target_directory::Cstring
ancestor_label::Cstring
our_label::Cstring
their_label::Cstring
perfdata_cb::Ptr{Void}
perfdata_payload::Ptr{Void}
end
"""
LibGit2.RemoteCallbacks
Callback settings.
Matches the [`git_remote_callbacks`](https://libgit2.github.com/libgit2/#HEAD/type/git_remote_callbacks) struct.
"""
@kwdef struct RemoteCallbacks
version::Cuint = 1
sideband_progress::Ptr{Void}
completion::Ptr{Void}
credentials::Ptr{Void}
certificate_check::Ptr{Void}
transfer_progress::Ptr{Void}
update_tips::Ptr{Void}
pack_progress::Ptr{Void}
push_transfer_progress::Ptr{Void}
push_update_reference::Ptr{Void}
push_negotiation::Ptr{Void}
transport::Ptr{Void}
payload::Ptr{Void}
end
function RemoteCallbacks(credentials::Ptr{Void}, payload::Ref{Nullable{AbstractCredentials}})
RemoteCallbacks(credentials=credentials_cb(), payload=pointer_from_objref(payload))
end
"""
LibGit2.ProxyOptions
Options for connecting through a proxy.
Matches the [`git_proxy_options`](https://libgit2.github.com/libgit2/#HEAD/type/git_proxy_options) struct.
"""
@kwdef struct ProxyOptions
version::Cuint = 1
proxytype::Consts.GIT_PROXY = Consts.PROXY_AUTO
url::Cstring
credential_cb::Ptr{Void}
certificate_cb::Ptr{Void}
payload::Ptr{Void}
end
"""
LibGit2.FetchOptions
Matches the [`git_fetch_options`](https://libgit2.github.com/libgit2/#HEAD/type/git_fetch_options) struct.
"""
@kwdef struct FetchOptions
version::Cuint = 1
callbacks::RemoteCallbacks
prune::Cint = Consts.FETCH_PRUNE_UNSPECIFIED
update_fetchhead::Cint = 1
download_tags::Cint = Consts.REMOTE_DOWNLOAD_TAGS_AUTO
@static if LibGit2.VERSION >= v"0.25.0"
proxy_opts::ProxyOptions
end
@static if LibGit2.VERSION >= v"0.24.0"
custom_headers::StrArrayStruct
end
end
"""
LibGit2.CloneOptions
Matches the [`git_clone_options`](https://libgit2.github.com/libgit2/#HEAD/type/git_clone_options) struct.
"""
@kwdef struct CloneOptions
version::Cuint = 1
checkout_opts::CheckoutOptions
fetch_opts::FetchOptions
bare::Cint
localclone::Cint = Consts.CLONE_LOCAL_AUTO
checkout_branch::Cstring
repository_cb::Ptr{Void}
repository_cb_payload::Ptr{Void}
remote_cb::Ptr{Void}
remote_cb_payload::Ptr{Void}
end
"""
LibGit2.DiffOptionsStruct
Matches the [`git_diff_options`](https://libgit2.github.com/libgit2/#HEAD/type/git_diff_options) struct.
"""
@kwdef struct DiffOptionsStruct
version::Cuint = Consts.DIFF_OPTIONS_VERSION
flags::UInt32 = Consts.DIFF_NORMAL
# options controlling which files are in the diff
ignore_submodules::GIT_SUBMODULE_IGNORE = Consts.SUBMODULE_IGNORE_UNSPECIFIED
pathspec::StrArrayStruct
notify_cb::Ptr{Void}
@static if LibGit2.VERSION >= v"0.24.0"
progress_cb::Ptr{Void}
end
payload::Ptr{Void}
# options controlling how the diff text is generated
context_lines::UInt32 = UInt32(3)
interhunk_lines::UInt32
id_abbrev::UInt16 = UInt16(7)
max_size::Int64 = Int64(512*1024*1024) #512Mb
old_prefix::Cstring
new_prefix::Cstring
end
"""
LibGit2.DiffFile
Description of one side of a delta.
Matches the [`git_diff_file`](https://libgit2.github.com/libgit2/#HEAD/type/git_diff_file) struct.
"""
struct DiffFile
id::GitHash
path::Cstring
size::Int64
flags::UInt32
mode::UInt16
@static if LibGit2.VERSION >= v"0.25.0"
id_abbrev::UInt16
end
end
function Base.show(io::IO, df::DiffFile)
println(io, "DiffFile:")
println(io, "Oid: $(df.id))")
println(io, "Path: $(df.path)")
println(io, "Size: $(df.size)")
end
"""
LibGit2.DiffDelta
Description of changes to one entry.
Matches the [`git_diff_delta`](https://libgit2.github.com/libgit2/#HEAD/type/git_diff_delta) struct.
The fields represent:
* `status`: One of `Consts.DELTA_STATUS`, indicating whether the file has been added/modified/deleted.
* `flags`: Flags for the delta and the objects on each side. Determines whether to treat the file(s)
as binary/text, whether they exist on each side of the diff, and whether the object ids are known
to be correct.
* `similarity`: Used to indicate if a file has been renamed or copied.
* `nfiles`: The number of files in the delta (for instance, if the delta
was run on a submodule commit id, it may contain more than one file).
* `old_file`: A [`DiffFile`](@ref) containing information about the file(s) before the changes.
* `new_file`: A [`DiffFile`](@ref) containing information about the file(s) after the changes.
"""
struct DiffDelta
status::Cint
flags::UInt32
similarity::UInt16
nfiles::UInt16
old_file::DiffFile
new_file::DiffFile
end
function Base.show(io::IO, dd::DiffDelta)
println(io, "DiffDelta:")
println(io, "Status: $(Consts.DELTA_STATUS(dd.status))")
println(io, "Number of files: $(dd.nfiles)")
println(io, "Old file:\n$(dd.old_file)")
println(io, "New file:\n$(dd.new_file)")
end
"""
LibGit2.MergeOptions
Matches the [`git_merge_options`](https://libgit2.github.com/libgit2/#HEAD/type/git_merge_options) struct.
"""
@kwdef struct MergeOptions
version::Cuint = 1
flags::Cint
rename_threshold::Cuint = 50
target_limit::Cuint = 200
metric::Ptr{Void}
@static if LibGit2.VERSION >= v"0.24.0"
recursion_limit::Cuint
end
@static if LibGit2.VERSION >= v"0.25.0"
default_driver::Cstring
end
file_favor::GIT_MERGE_FILE_FAVOR = Consts.MERGE_FILE_FAVOR_NORMAL
file_flags::GIT_MERGE_FILE = Consts.MERGE_FILE_DEFAULT
end
"""
LibGit2.PushOptions
Matches the [`git_push_options`](https://libgit2.github.com/libgit2/#HEAD/type/git_push_options) struct.
"""
@kwdef struct PushOptions
version::Cuint = 1
parallelism::Cint = 1
callbacks::RemoteCallbacks
@static if LibGit2.VERSION >= v"0.25.0"
proxy_opts::ProxyOptions
end
@static if LibGit2.VERSION >= v"0.24.0"
custom_headers::StrArrayStruct
end
end
"""
LibGit2.IndexTime
Matches the [`git_index_time`](https://libgit2.github.com/libgit2/#HEAD/type/git_index_time) struct.
"""
struct IndexTime
seconds::Int64
nanoseconds::Cuint
end
"""
LibGit2.IndexEntry
In-memory representation of a file entry in the index.
Matches the [`git_index_entry`](https://libgit2.github.com/libgit2/#HEAD/type/git_index_entry) struct.
"""
struct IndexEntry
ctime::IndexTime
mtime::IndexTime
dev::UInt32
ino::UInt32
mode::UInt32
uid::UInt32
gid::UInt32
file_size::Int64
id::GitHash
flags::UInt16
flags_extended::UInt16
path::Ptr{UInt8}
end
Base.show(io::IO, ie::IndexEntry) = print(io, "IndexEntry($(string(ie.id)))")
"""
LibGit2.RebaseOptions
Matches the `git_rebase_options` struct.
"""
@kwdef struct RebaseOptions
version::Cuint = 1
quiet::Cint = 1
@static if LibGit2.VERSION >= v"0.24.0"
inmemory::Cint
end
rewrite_notes_ref::Cstring
@static if LibGit2.VERSION >= v"0.24.0"
merge_opts::MergeOptions
end
checkout_opts::CheckoutOptions
end
"""
LibGit2.RebaseOperation
Describes a single instruction/operation to be performed during the rebase.
Matches the [`git_rebase_operation`](https://libgit2.github.com/libgit2/#HEAD/type/git_rebase_operation_t) struct.
"""
struct RebaseOperation
optype::Cint
id::GitHash
exec::Cstring
end
function Base.show(io::IO, rbo::RebaseOperation)
println(io, "RebaseOperation($(string(rbo.id)))")
println(io, "Operation type: $(Consts.GIT_REBASE_OPERATION(rbo.optype))")
end
"""
LibGit2.StatusOptions
Options to control how `git_status_foreach_ext()` will issue callbacks.
Matches the [`git_status_opt_t`](https://libgit2.github.com/libgit2/#HEAD/type/git_status_opt_t) struct.
"""
@kwdef struct StatusOptions
version::Cuint = 1
show::Cint = Consts.STATUS_SHOW_INDEX_AND_WORKDIR
flags::Cuint = Consts.STATUS_OPT_INCLUDE_UNTRACKED |
Consts.STATUS_OPT_RECURSE_UNTRACKED_DIRS |
Consts.STATUS_OPT_RENAMES_HEAD_TO_INDEX |
Consts.STATUS_OPT_SORT_CASE_SENSITIVELY
pathspec::StrArrayStruct
end
"""
LibGit2.StatusEntry
Providing the differences between the file as it exists in HEAD and the index, and
providing the differences between the index and the working directory.
Matches the `git_status_entry` struct.
"""
struct StatusEntry
status::Cuint
head_to_index::Ptr{DiffDelta}
index_to_workdir::Ptr{DiffDelta}
end
"""
LibGit2.FetchHead
Contains the information about HEAD during a fetch, including the name and URL
of the branch fetched from, the oid of the HEAD, and whether the fetched HEAD
has been merged locally.
"""
struct FetchHead
name::String
url::String
oid::GitHash
ismerge::Bool
end
function Base.show(io::IO, fh::FetchHead)
println(io, "FetchHead:")
println(io, "Name: $(fh.name)")
println(io, "URL: $(fh.url)")
print(io, "OID: ")
show(io, fh.oid)
println(io)
println(io, "Merged: $(fh.ismerge)")
end
# Abstract object types
abstract type AbstractGitObject end
Base.isempty(obj::AbstractGitObject) = (obj.ptr == C_NULL)
abstract type GitObject <: AbstractGitObject end
for (typ, owntyp, sup, cname) in [
(:GitRepo, nothing, :AbstractGitObject, :git_repository),
(:GitConfig, :(Nullable{GitRepo}), :AbstractGitObject, :git_config),
(:GitIndex, :(Nullable{GitRepo}), :AbstractGitObject, :git_index),
(:GitRemote, :GitRepo, :AbstractGitObject, :git_remote),
(:GitRevWalker, :GitRepo, :AbstractGitObject, :git_revwalk),
(:GitReference, :GitRepo, :AbstractGitObject, :git_reference),
(:GitDiff, :GitRepo, :AbstractGitObject, :git_diff),
(:GitDiffStats, :GitRepo, :AbstractGitObject, :git_diff_stats),
(:GitAnnotated, :GitRepo, :AbstractGitObject, :git_annotated_commit),
(:GitRebase, :GitRepo, :AbstractGitObject, :git_rebase),
(:GitStatus, :GitRepo, :AbstractGitObject, :git_status_list),
(:GitBranchIter, :GitRepo, :AbstractGitObject, :git_branch_iterator),
(:GitUnknownObject, :GitRepo, :GitObject, :git_object),
(:GitCommit, :GitRepo, :GitObject, :git_commit),
(:GitBlob, :GitRepo, :GitObject, :git_blob),
(:GitTree, :GitRepo, :GitObject, :git_tree),
(:GitTag, :GitRepo, :GitObject, :git_tag),
(:GitTreeEntry, :GitTree, :AbstractGitObject, :git_tree_entry),
]
if owntyp === nothing
@eval mutable struct $typ <: $sup
ptr::Ptr{Void}
function $typ(ptr::Ptr{Void}, fin::Bool=true)
# fin=false should only be used when the pointer should not be free'd
# e.g. from within callback functions which are passed a pointer
@assert ptr != C_NULL
obj = new(ptr)
if fin
Threads.atomic_add!(REFCOUNT, UInt(1))
finalizer(obj, Base.close)
end
return obj
end
end
else
@eval mutable struct $typ <: $sup
owner::$owntyp
ptr::Ptr{Void}
function $typ(owner::$owntyp, ptr::Ptr{Void}, fin::Bool=true)
@assert ptr != C_NULL
obj = new(owner, ptr)
if fin
Threads.atomic_add!(REFCOUNT, UInt(1))
finalizer(obj, Base.close)
end
return obj
end
end
if isa(owntyp, Expr) && owntyp.args[1] == :Nullable
@eval begin
$typ(ptr::Ptr{Void}, fin::Bool=true) = $typ($owntyp(), ptr, fin)
$typ(owner::$(owntyp.args[2]), ptr::Ptr{Void}, fin::Bool=true) =
$typ($owntyp(owner), ptr, fin)
end
end
end
@eval function Base.close(obj::$typ)
if obj.ptr != C_NULL
ccall(($(string(cname, :_free)), :libgit2), Void, (Ptr{Void},), obj.ptr)
obj.ptr = C_NULL
if Threads.atomic_sub!(REFCOUNT, UInt(1)) == 1
# will the last finalizer please turn out the lights?
ccall((:git_libgit2_shutdown, :libgit2), Cint, ())
end
end
end
end
## Calling `GitObject(repo, ...)` will automatically resolve to the appropriate type.
function GitObject(repo::GitRepo, ptr::Ptr{Void})
T = objtype(Consts.OBJECT(ptr))
T(repo, ptr)
end
"""
LibGit2.GitSignature
This is a Julia wrapper around a pointer to a
[`git_signature`](https://libgit2.github.com/libgit2/#HEAD/type/git_signature) object.
"""
mutable struct GitSignature <: AbstractGitObject
ptr::Ptr{SignatureStruct}
function GitSignature(ptr::Ptr{SignatureStruct})
@assert ptr != C_NULL
obj = new(ptr)
finalizer(obj, Base.close)
return obj
end
end
function Base.close(obj::GitSignature)
if obj.ptr != C_NULL
ccall((:git_signature_free, :libgit2), Void, (Ptr{SignatureStruct},), obj.ptr)
obj.ptr = C_NULL
end
end
# Structure has the same layout as SignatureStruct
mutable struct Signature
name::String
email::String
time::Int64
time_offset::Cint
end
""" Resource management helper function
"""
function with(f::Function, obj)
try
f(obj)
finally
close(obj)
end
end
with{T}(f::Function, ::Type{T}, args...) = with(f, T(args...))
function with_warn{T}(f::Function, ::Type{T}, args...)
obj = T(args...)
try
with(f, obj)
catch err
warn("$(string(T)) thrown exception: $err")
end
end
"""
LibGit2.Consts.OBJECT{T<:GitObject}(::Type{T})
The `OBJECT` enum value corresponding to type `T`.
"""
Consts.OBJECT(::Type{GitCommit}) = Consts.OBJ_COMMIT
Consts.OBJECT(::Type{GitTree}) = Consts.OBJ_TREE
Consts.OBJECT(::Type{GitBlob}) = Consts.OBJ_BLOB
Consts.OBJECT(::Type{GitTag}) = Consts.OBJ_TAG
Consts.OBJECT(::Type{GitUnknownObject}) = Consts.OBJ_ANY
Consts.OBJECT(::Type{GitObject}) = Consts.OBJ_ANY
Consts.OBJECT(ptr::Ptr{Void}) =
ccall((:git_object_type, :libgit2), Consts.OBJECT, (Ptr{Void},), ptr)
"""
objtype(obj_type::Consts.OBJECT)
Returns the type corresponding to the enum value.
"""
function objtype(obj_type::Consts.OBJECT)
if obj_type == Consts.OBJ_COMMIT
GitCommit
elseif obj_type == Consts.OBJ_TREE
GitTree
elseif obj_type == Consts.OBJ_BLOB
GitBlob
elseif obj_type == Consts.OBJ_TAG
GitTag
elseif obj_type == Consts.OBJ_ANY #this name comes from the header
GitUnknownObject
else
throw(GitError(Error.Object, Error.ENOTFOUND, "Object type $obj_type is not supported"))
end
end
import Base.securezero!
"Credentials that support only `user` and `password` parameters"
mutable struct UserPasswordCredentials <: AbstractCredentials
user::String
pass::String
prompt_if_incorrect::Bool # Whether to allow interactive prompting if the credentials are incorrect
count::Int # authentication failure protection count
function UserPasswordCredentials(u::AbstractString,p::AbstractString,prompt_if_incorrect::Bool=false)
c = new(u,p,prompt_if_incorrect,3)
finalizer(c, securezero!)
return c
end
UserPasswordCredentials(prompt_if_incorrect::Bool=false) = UserPasswordCredentials("","",prompt_if_incorrect)
end
function securezero!(cred::UserPasswordCredentials)
securezero!(cred.user)
securezero!(cred.pass)
cred.count = 0
return cred
end
function Base.:(==)(a::UserPasswordCredentials, b::UserPasswordCredentials)
a.user == b.user && a.pass == b.pass
end
"SSH credentials type"
mutable struct SSHCredentials <: AbstractCredentials
user::String
pass::String
prvkey::String
pubkey::String
usesshagent::String # used for ssh-agent authentication
prompt_if_incorrect::Bool # Whether to allow interactive prompting if the credentials are incorrect
count::Int
function SSHCredentials(u::AbstractString,p::AbstractString,prvkey::AbstractString,pubkey::AbstractString,prompt_if_incorrect::Bool=false)
c = new(u,p,prvkey,pubkey,"Y",prompt_if_incorrect,3)
finalizer(c, securezero!)
return c
end
SSHCredentials(u::AbstractString,p::AbstractString,prompt_if_incorrect::Bool=false) = SSHCredentials(u,p,"","",prompt_if_incorrect)
SSHCredentials(prompt_if_incorrect::Bool=false) = SSHCredentials("","","","",prompt_if_incorrect)
end
function securezero!(cred::SSHCredentials)
securezero!(cred.user)
securezero!(cred.pass)
securezero!(cred.prvkey)
securezero!(cred.pubkey)
cred.count = 0
return cred
end
function Base.:(==)(a::SSHCredentials, b::SSHCredentials)
a.user == b.user && a.pass == b.pass && a.prvkey == b.prvkey && a.pubkey == b.pubkey
end
"Credentials that support caching"
mutable struct CachedCredentials <: AbstractCredentials
cred::Dict{String,AbstractCredentials}
count::Int # authentication failure protection count
CachedCredentials() = new(Dict{String,AbstractCredentials}(),3)
end
"Checks if credentials were used or failed authentication, see `LibGit2.credentials_callback`"
function checkused!(p::Union{UserPasswordCredentials, SSHCredentials})
p.count <= 0 && return true
p.count -= 1
return false
end
reset!(p::Union{UserPasswordCredentials, SSHCredentials}, cnt::Int=3) = (p.count = cnt; p)
reset!(p::CachedCredentials) = (foreach(reset!, values(p.cred)); p)
"Obtain the cached credentials for the given host+protocol (credid), or return and store the default if not found"
get_creds!(collection::CachedCredentials, credid, default) = get!(collection.cred, credid, default)
get_creds!(creds::AbstractCredentials, credid, default) = creds
get_creds!(creds::Void, credid, default) = default
function get_creds!(creds::Ref{Nullable{AbstractCredentials}}, credid, default)
if isnull(creds[])
creds[] = Nullable{AbstractCredentials}(default)
return default
else
get_creds!(Base.get(creds[]), credid, default)
end
end
function securezero!(p::CachedCredentials)
foreach(securezero!, values(p.cred))
return p
end

View File

@@ -0,0 +1,67 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# Parse "GIT URLs" syntax (URLs and a scp-like syntax). For details see:
# https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a
const URL_REGEX = r"""
^(?:(?<scheme>ssh|git|https?)://)?
(?:
(?<user>.*?)
(?:\:(?<password>.*?))?@
)?
(?<host>[A-Za-z0-9\-\.]+)
(?(<scheme>)
(?:\:(?<port>\d+))? # only parse port when not using SCP-like syntax
|
:?
)
(?<path>.*?)$
"""x
function version()
major = Ref{Cint}(0)
minor = Ref{Cint}(0)
patch = Ref{Cint}(0)
ccall((:git_libgit2_version, :libgit2), Void,
(Ptr{Cint}, Ptr{Cint}, Ptr{Cint}), major, minor, patch)
return VersionNumber(major[], minor[], patch[])
end
const VERSION = version()
isset(val::Integer, flag::Integer) = (val & flag == flag)
reset(val::Integer, flag::Integer) = (val &= ~flag)
toggle(val::Integer, flag::Integer) = (val |= flag)
function prompt(msg::AbstractString; default::AbstractString="", password::Bool=false)
if is_windows() && password
error("Command line prompt not supported for password entry on windows. Use winprompt instead")
end
msg = !isempty(default) ? msg*" [$default]:" : msg*":"
uinput = if password
Base.getpass(msg)
else
print(msg)
readline()
end
isempty(uinput) ? default : uinput
end
function features()
feat = ccall((:git_libgit2_features, :libgit2), Cint, ())
res = Consts.GIT_FEATURE[]
for f in instances(Consts.GIT_FEATURE)
isset(feat, Cuint(f)) && push!(res, f)
end
return res
end
"""
LibGit2.posixpath(path)
Standardise the path string `path` to use POSIX separators.
"""
function posixpath end
if is_windows()
posixpath(path) = replace(path,'\\','/')
else is_unix()
posixpath(path) = path
end

View File

@@ -0,0 +1,102 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function GitRevWalker(repo::GitRepo)
w_ptr = Ref{Ptr{Void}}(C_NULL)
@check ccall((:git_revwalk_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}), w_ptr, repo.ptr)
return GitRevWalker(repo, w_ptr[])
end
function Base.start(w::GitRevWalker)
id_ptr = Ref(GitHash())
err = ccall((:git_revwalk_next, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Void}), id_ptr, w.ptr)
err != Int(Error.GIT_OK) && return (nothing, true)
return (id_ptr[], false)
end
Base.done(w::GitRevWalker, state) = Bool(state[2])
function Base.next(w::GitRevWalker, state)
id_ptr = Ref(GitHash())
err = ccall((:git_revwalk_next, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Void}), id_ptr, w.ptr)
err != Int(Error.GIT_OK) && return (state[1], (nothing, true))
return (state[1], (id_ptr[], false))
end
Base.iteratorsize(::Type{GitRevWalker}) = Base.SizeUnknown()
function push_head!(w::GitRevWalker)
@check ccall((:git_revwalk_push_head, :libgit2), Cint, (Ptr{Void},), w.ptr)
return w
end
function Base.push!(w::GitRevWalker, cid::GitHash)
@check ccall((:git_revwalk_push, :libgit2), Cint, (Ptr{Void}, Ptr{GitHash}), w.ptr, Ref(cid))
return w
end
function Base.push!(w::GitRevWalker, range::AbstractString)
@check ccall((:git_revwalk_push_range, :libgit2), Cint, (Ptr{Void}, Ptr{UInt8}), w.ptr, range)
return w
end
function Base.sort!(w::GitRevWalker; by::Cint = Consts.SORT_NONE, rev::Bool=false)
rev && (by |= Consts.SORT_REVERSE)
ccall((:git_revwalk_sorting, :libgit2), Void, (Ptr{Void}, Cint), w.ptr, by)
return w
end
repository(w::GitRevWalker) = w.owner
function Base.map(f::Function, walker::GitRevWalker;
oid::GitHash=GitHash(),
range::AbstractString="",
by::Cint = Consts.SORT_NONE,
rev::Bool=false,
count::Int=0)
res = []
sort!(walker, by=by, rev=rev)
if !iszero(oid)
push!(walker, oid)
elseif !isempty(range)
push!(walker, range)
else
push_head!(walker)
end
s = start(walker)
c = 0
repo = repository(walker)
while !done(walker, s)
val = f(s[1], repo)
push!(res, val)
val, s = next(walker, s)
c +=1
count == c && break
end
return res
end
function Base.count(f::Function, walker::GitRevWalker;
oid::GitHash=GitHash(),
by::Cint = Consts.SORT_NONE,
rev::Bool=false)
c = 0
sort!(walker, by=by, rev=rev)
if !iszero(oid)
push!(walker, oid)
else
push_head!(walker)
end
s = start(walker)
repo = repository(walker)
while !done(walker, s)
val = f(s[1], repo)
_, s = next(walker, s)
c += (val == true)
end
return c
end