mollusk 0e4acfb8f2 fix incorrect folder name for julia-0.6.x
Former-commit-id: ef2c7401e0876f22d2f7762d182cfbcd5a7d9c70
2018-06-11 03:28:36 -07:00

950 lines
31 KiB
Julia

# 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