# 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 [-- ]`. """ 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 [-- ]`. """ 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= `. """ 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 [|] []`. """ 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 [|] []`. """ 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] [] [--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 `. 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 ] [--bare] `. # 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 [] [--] ... """ 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] `. # 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 `. """ 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] [ | ]`. !!! 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 [] 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