diff --git a/base/deprecated.jl b/base/deprecated.jl index 5e966bd834efc..c90304fe36128 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -18,13 +18,13 @@ # the name of the function, which is used to ensure that the deprecation warning # is only printed the first time for each call place. -macro deprecate(old,new) +macro deprecate(old,new,ex=true) meta = Expr(:meta, :noinline) if isa(old,Symbol) oldname = Expr(:quote,old) newname = Expr(:quote,new) Expr(:toplevel, - Expr(:export,esc(old)), + ex ? Expr(:export,esc(old)) : nothing, :(function $(esc(old))(args...) $meta depwarn(string($oldname," is deprecated, use ",$newname," instead."), @@ -1555,8 +1555,6 @@ _promote_eltype_op(op, A, B, C, D...) = (@_inline_meta; _promote_eltype_op(op, e _promote_eltype_op(args...) end -# Rename LibGit2.Oid to LibGit2.GitHash (part of #19839) -eval(Base.LibGit2, :(Base.@deprecate_binding Oid GitHash)) function unsafe_wrap(::Type{String}, p::Union{Ptr{UInt8},Ptr{Int8}}, len::Integer, own::Bool=false) Base.depwarn("unsafe_wrap(String, ...) is deprecated; use `unsafe_string` instead.", :unsafe_wrap) @@ -1575,9 +1573,6 @@ unsafe_wrap(::Type{String}, p::Cstring, len::Integer, own::Bool=false) = @deprecate finalize(sa::LibGit2.StrArrayStruct) close(sa) @deprecate finalize(sa::LibGit2.Buffer) close(sa) -# Rename LibGit2.GitAnyObject to LibGit2.GitUnknownObject (part of #19839) -eval(LibGit2, :(Base.@deprecate_binding GitAnyObject GitUnknownObject)) - ## produce, consume, and task iteration # NOTE: When removing produce/consume, also remove field Task.consumers and related code in # task.jl and event.jl @@ -1829,6 +1824,19 @@ function colon{T<:Dates.Period}(start::T, stop::T) colon(start, T(1), stop) end +# LibGit2 refactor (#19839) +eval(Base.LibGit2, quote + Base.@deprecate_binding Oid GitHash + Base.@deprecate_binding GitAnyObject GitUnknownObject + + @deprecate owner(x) repository(x) false + @deprecate get{T<:GitObject}(::Type{T}, repo::GitRepo, x) T(repo, x) false + @deprecate get{T<:GitObject}(::Type{T}, repo::GitRepo, oid::GitHash, oid_size::Int) T(repo, GitShortHash(oid, oid_size)) false + @deprecate revparse(repo::GitRepo, objname::AbstractString) GitObject(repo, objname) false + @deprecate object(repo::GitRepo, te::GitTreeEntry) GitObject(repo, te) false + @deprecate commit(ann::GitAnnotated) GitHash(ann) false +end) + # when this deprecation is deleted, remove all calls to it, and all # negate=nothing keyword arguments, from base/dates/adjusters.jl eval(Dates, quote diff --git a/base/libgit2/blob.jl b/base/libgit2/blob.jl index 087a7a16568fd..b8c9570c827a2 100644 --- a/base/libgit2/blob.jl +++ b/base/libgit2/blob.jl @@ -1,13 +1,20 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license -function content(blob::GitBlob) - return ccall((:git_blob_rawcontent, :libgit2), Ptr{Void}, (Ptr{Void},), blob.ptr) -end - 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 + +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 @@ -26,24 +33,29 @@ function lookup(repo::GitRepo, oid::GitHash) return GitBlob(blob_ptr_ptr[]) end -function GitBlob(repo::GitRepo, path::AbstractString) - blob_id_ptr = Ref(GitHash()) +""" + 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. +""" +function addblob!(repo::GitRepo, path::AbstractString) + id_ref = Ref{GitHash}() @check ccall((:git_blob_create_fromdisk, :libgit2), Cint, - (Ptr{GitHash}, Ptr{Void}, Ptr{UInt8}), blob_id_ptr, - repo.ptr, path) - blob = get(GitBlob, repo, blob_id_ptr[]) - return blob + (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(len(conts), 3) - print(io, "GitBlob:\nBlob id: ", GitHash(blob.ptr), "\nContents:\n") + showlen = max(length(conts), 3) + print(io, "GitBlob:\nBlob id: ", GitHash(blob), "\nContents:\n") for i in 1:showlen print(io, conts[i],"\n") end else - print(io, "GitBlob:\nBlob id: ", GitHash(blob.ptr), "\nContents are binary.") + print(io, "GitBlob:\nBlob id: ", GitHash(blob), "\nContents are binary.\n") end end diff --git a/base/libgit2/commit.jl b/base/libgit2/commit.jl index 7831e0904a290..28592fe933c00 100644 --- a/base/libgit2/commit.jl +++ b/base/libgit2/commit.jl @@ -74,13 +74,13 @@ function commit(repo::GitRepo, msg::AbstractString; commit_id = GitHash() # get necessary objects - tree = get(GitTree, repo, tree_id) + tree = GitTree(repo, tree_id) auth_sig = convert(GitSignature, author) comm_sig = convert(GitSignature, committer) parents = GitCommit[] try - for parent in parent_ids - push!(parents, get(GitCommit, repo, parent)) + for id in parent_ids + push!(parents, GitCommit(repo, id)) end commit_id = commit(repo, refname, msg, auth_sig, comm_sig, tree, parents...) finally diff --git a/base/libgit2/consts.jl b/base/libgit2/consts.jl index 9249e27283632..94b535b0dfce5 100644 --- a/base/libgit2/consts.jl +++ b/base/libgit2/consts.jl @@ -7,12 +7,13 @@ module Consts const REMOTE_ORIGIN = "origin" # objs - const OBJ_ANY = Cint(-2) - const OBJ_BAD = Cint(-1) - const OBJ_COMMIT = Cint(1) - const OBJ_TREE = Cint(2) - const OBJ_BLOB = Cint(3) - const OBJ_TAG = Cint(4) + @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) diff --git a/base/libgit2/index.jl b/base/libgit2/index.jl index 476be19a3c16c..668f67570f3cd 100644 --- a/base/libgit2/index.jl +++ b/base/libgit2/index.jl @@ -32,16 +32,19 @@ function repository(idx::GitIndex) end end -function read_tree!(idx::GitIndex, tree_id::GitHash) - repo = repository(idx) - tree = get(GitTree, repo, tree_id) - try - @check ccall((:git_index_read_tree, :libgit2), Cint, - (Ptr{Void}, Ptr{Void}), idx.ptr, tree.ptr) - finally - close(tree) - 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!{T<:AbstractString}(idx::GitIndex, files::T...; flags::Cuint = Consts.INDEX_ADD_DEFAULT) diff --git a/base/libgit2/libgit2.jl b/base/libgit2/libgit2.jl index b71bb462dcbf6..fba4695547a61 100644 --- a/base/libgit2/libgit2.jl +++ b/base/libgit2/libgit2.jl @@ -76,7 +76,7 @@ is in the repository. function iscommit(id::AbstractString, repo::GitRepo) res = true try - c = get(GitCommit, repo, id) + c = GitCommit(repo, id) if c === nothing res = false else @@ -111,9 +111,8 @@ Equivalent to `git diff-index [-- ]`. """ function isdiff(repo::GitRepo, treeish::AbstractString, paths::AbstractString=""; cached::Bool=false) tree_oid = revparseid(repo, "$treeish^{tree}") - iszero(tree_oid) && error("invalid treeish $treeish") # this can be removed by #20104 result = false - tree = get(GitTree, repo, tree_oid) + tree = GitTree(repo, tree_oid) try diff = diff_tree(repo, tree, paths, cached=cached) result = count(diff) > 0 @@ -129,8 +128,8 @@ function diff_files(repo::GitRepo, branch1::AbstractString, branch2::AbstractStr filter::Set{Cint}=Set([Consts.DELTA_ADDED, Consts.DELTA_MODIFIED, Consts.DELTA_DELETED])) b1_id = revparseid(repo, branch1*"^{tree}") b2_id = revparseid(repo, branch2*"^{tree}") - tree1 = get(GitTree, repo, b1_id) - tree2 = get(GitTree, repo, b2_id) + tree1 = GitTree(repo, b1_id) + tree2 = GitTree(repo, b2_id) files = AbstractString[] try diff = diff_tree(repo, tree1, tree2) @@ -307,7 +306,7 @@ function branch!(repo::GitRepo, branch_name::AbstractString, GitHash(commit) end iszero(commit_id) && return - cmt = get(GitCommit, repo, commit_id) + cmt = GitCommit(repo, commit_id) new_branch_ref = nothing try new_branch_ref = Nullable(create_branch(repo, branch_name, cmt, force=force)) @@ -371,28 +370,17 @@ function checkout!(repo::GitRepo, commit::AbstractString = ""; end # search for commit to get a commit object - obj = get(GitUnknownObject, repo, GitHash(commit)) - obj === nothing && return - try - peeled = peel(obj, Consts.OBJ_COMMIT) - peeled === nothing && return - opts = force ? CheckoutOptions(checkout_strategy = Consts.CHECKOUT_FORCE) : - CheckoutOptions() - try - # detach commit - obj_oid = GitHash(peeled) - ref = GitReference(repo, obj_oid, force=force, - msg="libgit2.checkout: moving from $head_name to $(string(obj_oid))") - close(ref) - - # checkout commit - checkout_tree(repo, peeled, options = opts) - finally - close(peeled) - end - finally - close(obj) - end + obj = GitObject(repo, GitHash(commit)) + peeled = peel(GitCommit, obj) + + opts = force ? CheckoutOptions(checkout_strategy = Consts.CHECKOUT_FORCE) : CheckoutOptions() + # detach commit + obj_oid = GitHash(peeled) + ref = GitReference(repo, obj_oid, force=force, + msg="libgit2.checkout: moving from $head_name to $(string(obj_oid))") + + # checkout commit + checkout_tree(repo, peeled, options = opts) end """ git clone [-b ] [--bare] """ @@ -416,42 +404,22 @@ end """ git reset [] [--] ... """ function reset!(repo::GitRepo, committish::AbstractString, pathspecs::AbstractString...) - obj = revparse(repo, !isempty(committish) ? committish : Consts.HEAD_FILE) + 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 - obj === nothing && throw(GitError(Error.Object, Error.ERROR, "`$committish` not found")) - try - head = reset!(repo, Nullable(obj), pathspecs...) - return head - finally - close(obj) - end - return head_oid(repo) + reset!(repo, Nullable(obj), pathspecs...) end -""" git reset [--soft | --mixed | --hard] """ -function reset!(repo::GitRepo, commit::GitHash, mode::Cint = Consts.RESET_MIXED) - obj = get(GitUnknownObject, repo, commit) - # object must exist for reset - obj === nothing && throw(GitError(Error.Object, Error.ERROR, "Commit `$(string(commit))` object not found")) - try - head = reset!(repo, obj, mode) - return head - finally - close(obj) - end - return head_oid(repo) -end +""" git reset [--soft | --mixed | --hard] """ +reset!(repo::GitRepo, id::GitHash, mode::Cint = Consts.RESET_MIXED) = + reset!(repo, GitObject(repo, id), mode) """ git cat-file """ -function cat{T<:GitObject}(repo::GitRepo, ::Type{T}, object::AbstractString) - obj_id = revparseid(repo, object) - iszero(obj_id) && return nothing - - obj = get(T, repo, obj_id) +function cat(repo::GitRepo, spec) + obj = GitObject(repo, spec) if isa(obj, GitBlob) - return unsafe_string(convert(Ptr{UInt8}, content(obj))) + content(obj) else - return nothing + nothing end end @@ -515,10 +483,10 @@ function merge!(repo::GitRepo; remotename = with(GitConfig, repo) do cfg LibGit2.get(String, cfg, "branch.$branchname.remote") end - obj = with(GitReference(repo, "refs/remotes/$remotename/$branchname")) do ref + oid = with(GitReference(repo, "refs/remotes/$remotename/$branchname")) do ref LibGit2.GitHash(ref) end - with(get(GitCommit, repo, obj)) do cmt + with(GitCommit(repo, oid)) do cmt LibGit2.create_branch(repo, branchname, cmt) end return true @@ -616,7 +584,7 @@ end """ Returns all commit authors """ function authors(repo::GitRepo) return with(GitRevWalker(repo)) do walker - map((oid,repo)->with(get(GitCommit, repo, oid)) do cmt + map((oid,repo)->with(GitCommit(repo, oid)) do cmt author(cmt)::Signature end, walker) #, by = Consts.SORT_TIME) diff --git a/base/libgit2/merge.jl b/base/libgit2/merge.jl index 9c20643b716ed..9031c2bf75fc1 100644 --- a/base/libgit2/merge.jl +++ b/base/libgit2/merge.jl @@ -25,18 +25,13 @@ function GitAnnotated(repo::GitRepo, fh::FetchHead) end function GitAnnotated(repo::GitRepo, comittish::AbstractString) - obj = revparse(repo, comittish) - try - cmt = peel(obj, Consts.OBJ_COMMIT) - cmt === nothing && return nothing - return GitAnnotated(repo, GitHash(cmt)) - finally - close(obj) - end + obj = GitObject(repo, comittish) + cmt = peel(GitCommit, obj) + return GitAnnotated(repo, GitHash(cmt)) end -function commit(ann::GitAnnotated) - return GitHash(ccall((:git_annotated_commit_id, :libgit2), Ptr{GitHash}, (Ptr{Void},), ann.ptr)) +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}) @@ -52,23 +47,18 @@ end """Fastforward merge changes into current head """ function ffmerge!(repo::GitRepo, ann::GitAnnotated) - ann_cmt_oid = commit(ann) - cmt = get(GitCommit, repo, ann_cmt_oid) - cmt === nothing && return false # could not find commit tree - try - 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) + 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 - finally - close(cmt) + close(new_head_ref) end return true end diff --git a/base/libgit2/oid.jl b/base/libgit2/oid.jl index 109a92e649b9f..351a4584da26d 100644 --- a/base/libgit2/oid.jl +++ b/base/libgit2/oid.jl @@ -1,8 +1,5 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license -GitHash(id::GitHash) = id -GitHash(ptr::Ptr{GitHash}) = unsafe_load(ptr)::GitHash - function GitHash(ptr::Ptr{UInt8}) if ptr == C_NULL throw(ArgumentError("NULL pointer passed to GitHash() constructor")) @@ -22,18 +19,31 @@ end function GitHash(id::AbstractString) bstr = String(id) len = sizeof(bstr) - oid_ptr = Ref(GitHash()) - err = if len < OID_HEXSZ - ccall((:git_oid_fromstrn, :libgit2), Cint, - (Ptr{GitHash}, Ptr{UInt8}, Csize_t), oid_ptr, bstr, len) - else - ccall((:git_oid_fromstrp, :libgit2), Cint, - (Ptr{GitHash}, Cstring), oid_ptr, bstr) + if len < OID_HEXSZ + throw(ArgumentError("Input string is too short, use `GitShortHash` for partial hashes")) end - err != 0 && return GitHash() + 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() @@ -51,32 +61,40 @@ function GitHash(repo::GitRepo, ref_name::AbstractString) return oid_ptr[] end -function GitHash(obj::Ptr{Void}) - oid_ptr = ccall((:git_object_id, :libgit2), Ptr{UInt8}, (Ptr{Void},), obj) - oid_ptr == C_NULL && return GitHash() - return GitHash(oid_ptr) -end - -function GitHash{T<:GitObject}(obj::T) - obj === nothing && return GitHash() - return GitHash(obj.ptr) +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::GitHash) = hex(id) +Base.string(id::AbstractGitHash) = hex(id) -Base.show(io::IO, id::GitHash) = print(io, "GitHash($(string(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) -cmp(id1::GitHash, id2::GitHash) = Int(ccall((:git_oid_cmp, :libgit2), Cint, - (Ptr{GitHash}, Ptr{GitHash}), Ref(id1), Ref(id2))) +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::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 diff --git a/base/libgit2/reference.jl b/base/libgit2/reference.jl index 539840f5b868e..115e61e2b6c01 100644 --- a/base/libgit2/reference.jl +++ b/base/libgit2/reference.jl @@ -114,21 +114,23 @@ function Base.show(io::IO, ref::GitReference) println(io, "Tag with name ", name(ref)) end end -function peel{T <: GitObject}(::Type{T}, ref::GitReference) - git_otype = getobjecttype(T) + +""" + 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` is obtained. + +- A `GitTag` will be peeled to the object it references. +- A `GitCommit` will be peeled to a `GitTree`. +""" +function peel{T<:GitObject}(::Type{T}, ref::GitReference) obj_ptr_ptr = Ref{Ptr{Void}}(C_NULL) - err = ccall((:git_reference_peel, :libgit2), Cint, - (Ptr{Ptr{Void}}, Ptr{Void}, Cint), obj_ptr_ptr, ref.ptr, git_otype) - if err == Int(Error.ENOTFOUND) - return GitHash() - elseif err != Int(Error.GIT_OK) - if obj_ptr_ptr[] != C_NULL - close(GitUnknownObject(ref.repo, obj_ptr_ptr[])) - end - throw(Error.GitError(err)) - end + @check ccall((:git_reference_peel, :libgit2), Cint, + (Ptr{Ptr{Void}}, Ptr{Void}, Cint), obj_ptr_ptr, ref.ptr, Consts.OBJECT(T)) return T(ref.repo, obj_ptr_ptr[]) end +peel(ref::GitReference) = peel(GitObject, ref) function ref_list(repo::GitRepo) sa_ref = Ref(StrArrayStruct()) diff --git a/base/libgit2/repository.jl b/base/libgit2/repository.jl index 0da122bca9569..d23d514c817bf 100644 --- a/base/libgit2/repository.jl +++ b/base/libgit2/repository.jl @@ -73,53 +73,60 @@ function isattached(repo::GitRepo) ccall((:git_repository_head_detached, :libgit2), Cint, (Ptr{Void},), repo.ptr) != 1 end -""" Returns a found object """ -function revparse(repo::GitRepo, objname::AbstractString) - obj_ptr_ptr = Ref{Ptr{Void}}(C_NULL) - err = ccall((:git_revparse_single, :libgit2), Cint, - (Ptr{Ptr{Void}}, Ptr{Void}, Cstring), obj_ptr_ptr, repo.ptr, objname) - err != 0 && return nothing - return GitUnknownObject(repo, obj_ptr_ptr[]) +@doc """ + GitObject(repo::GitRepo, hash::AbstractGitHash) + GitObject(repo::GitRepo, spec::AbstractString) + +Return the specified object (`GitCommit`, `GitBlob`, `GitTree` or `GitTag`) from `repo` +specified by `hash`/`spec`. + +- `hash` is a full (`GitHash`) or partial (`GitShortHash`) hash. +- `spec` is a textual specification: see 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 https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for a full list. +""" $T end -""" Returns id of a found object """ -function revparseid(repo::GitRepo, objname::AbstractString) - obj = revparse(repo, objname) - obj === nothing && return GitHash() - oid = GitHash(obj.ptr) - close(obj) - return oid +function (::Type{T}){T<:GitObject}(repo::GitRepo, spec::AbstractString) + 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) + return T(repo, obj_ptr_ptr[]) end -function get{T <: GitObject}(::Type{T}, repo::GitRepo, oid::GitHash, oid_size::Int=OID_HEXSZ) - id_ptr = Ref(oid) +function (::Type{T}){T<:GitObject}(repo::GitRepo, oid::GitHash) + oid_ptr = Ref(oid) obj_ptr_ptr = Ref{Ptr{Void}}(C_NULL) - git_otype = getobjecttype(T) - err = if oid_size != OID_HEXSZ - ccall((:git_object_lookup_prefix, :libgit2), Cint, - (Ptr{Ptr{Void}}, Ptr{Void}, Ptr{GitHash}, Csize_t, Cint), - obj_ptr_ptr, repo.ptr, id_ptr, Csize_t(oid_size), git_otype) - else - ccall((:git_object_lookup, :libgit2), Cint, - (Ptr{Ptr{Void}}, Ptr{Void}, Ptr{GitHash}, Cint), - obj_ptr_ptr, repo.ptr, id_ptr, git_otype) - end - if err == Int(Error.ENOTFOUND) - return nothing - elseif err != Int(Error.GIT_OK) - if obj_ptr_ptr[] != C_NULL - close(GitUnknownObject(repo, obj_ptr_ptr[])) - end - throw(Error.GitError(err)) - end + @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}){T<:GitObject}(repo::GitRepo, oid::GitShortHash) + 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)) -function get{T <: GitObject}(::Type{T}, repo::GitRepo, oid::AbstractString) - return get(T, repo, GitHash(oid), length(oid)) + return T(repo, obj_ptr_ptr[]) end +# TODO: deprecate this function +revparseid(repo::GitRepo, spec) = GitHash(GitUnknownObject(repo, spec)) + """ LibGit2.gitdir(repo::GitRepo) @@ -181,23 +188,25 @@ function path(repo::GitRepo) end end -function peel(obj::GitObject, obj_type::Cint) - peeled_ptr_ptr = Ref{Ptr{Void}}(C_NULL) - git_otype = getobjecttype(obj_type) - err = ccall((:git_object_peel, :libgit2), Cint, - (Ptr{Ptr{Void}}, Ptr{Void}, Cint), peeled_ptr_ptr, obj.ptr, obj_type) - if err == Int(Error.ENOTFOUND) - return GitHash() - elseif err != Int(Error.GIT_OK) - if peeled_ptr_ptr[] != C_NULL - close(GitUnknownObject(obj.repo, peeled_ptr_ptr[])) - end - throw(Error.GitError(err)) - end - return git_otype(obj.repo, peeled_ptr_ptr[]) +""" + 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{T<:GitObject}(::Type{T}, obj::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.repo, new_ptr_ptr[]) end +peel(obj::GitObject) = peel(GitObject, obj) -peel{T <: GitObject}(::Type{T}, obj::GitObject) = peel(obj, getobjecttype(T)) function checkout_tree(repo::GitRepo, obj::GitObject; options::CheckoutOptions = CheckoutOptions()) diff --git a/base/libgit2/tag.jl b/base/libgit2/tag.jl index c0fbe202a120a..3c0d31a2b6b52 100644 --- a/base/libgit2/tag.jl +++ b/base/libgit2/tag.jl @@ -14,12 +14,12 @@ function tag_delete(repo::GitRepo, tag::AbstractString) (Ptr{Void}, Cstring, ), repo.ptr, tag) end -function tag_create(repo::GitRepo, tag::AbstractString, commit::Union{AbstractString,GitHash}; +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(get(GitCommit, repo, commit)) do commit_obj + 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, @@ -36,10 +36,16 @@ function name(tag::GitTag) 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 GitHash(oid_ptr) + return unsafe_load(oid_ptr) end Base.show(io::IO, tag::GitTag) = print(io, "GitTag:\nTag name: $(name(tag)) target: $(target(tag))") diff --git a/base/libgit2/tree.jl b/base/libgit2/tree.jl index 8509f6ae628cb..a1aced556994c 100644 --- a/base/libgit2/tree.jl +++ b/base/libgit2/tree.jl @@ -42,12 +42,12 @@ function entryid(te::GitTreeEntry) return GitHash(oid_ptr[]) end -function object(repo::GitRepo, te::GitTreeEntry) +function (::Type{T}){T<:GitObject}(repo::GitRepo, te::GitTreeEntry) 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 GitUnknownObject(repo, obj_ptr_ptr[]) + return T(repo, obj_ptr_ptr[]) end function Base.show(io::IO, te::GitTreeEntry) diff --git a/base/libgit2/types.jl b/base/libgit2/types.jl index ea7376120769c..ecc4a8586742b 100644 --- a/base/libgit2/types.jl +++ b/base/libgit2/types.jl @@ -6,12 +6,35 @@ const OID_RAWSZ = 20 const OID_HEXSZ = OID_RAWSZ * 2 const OID_MINPREFIXLEN = 4 -immutable GitHash +abstract AbstractGitHash + +""" + 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. +""" +immutable 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. +""" +immutable GitShortHash <: AbstractGitHash + hash::GitHash # underlying hash: unused digits are ignored + len::Csize_t # length in hex digits +end + + """ LibGit2.TimeStruct @@ -511,6 +534,12 @@ for (typ, reporef, sup, cname) in [ end end +## Calling `GitObject(repo, ...)` will automatically resolve to the appropriate type. +function GitObject(repo::GitRepo, ptr::Ptr{Void}) + T = objtype(ccall((:git_object_type, :libgit2), Consts.OBJECT, (Ptr{Void},), ptr)) + T(repo, ptr) +end + """ LibGit2.GitSignature @@ -563,35 +592,24 @@ function with_warn{T}(f::Function, ::Type{T}, args...) end """ - getobjecttype{T<:GitObject}(::Type{T}) + LibGit2.Const.OBJECT{T<:GitObject}(::Type{T}) -Convert between the Julia `Type` of a git object and -the constant integer id code for that object type. +The `OBJECT` enum value corresponding to type `T`. """ -function getobjecttype{T<:GitObject}(::Type{T}) - return if T == GitCommit - Consts.OBJ_COMMIT - elseif T == GitTree - Consts.OBJ_TREE - elseif T == GitBlob - Consts.OBJ_BLOB - elseif T == GitTag - Consts.OBJ_TAG - elseif T == GitUnknownObject - Consts.OBJ_ANY # this name comes from the header - else - throw(GitError(Error.Object, Error.ENOTFOUND, "Type $T is not supported")) - end -end +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 """ - getobjecttype(obj_type::Cint) + objtype(obj_type::Consts.OBJECT) -Convert between the constant integer id code for a git object -and the corresponding Julia type. +Returns the type corresponding to the enum value. """ -function getobjecttype(obj_type::Cint) - return if obj_type == Consts.OBJ_COMMIT +function objtype(obj_type::Consts.OBJECT) + if obj_type == Consts.OBJ_COMMIT GitCommit elseif obj_type == Consts.OBJ_TREE GitTree diff --git a/base/pkg/entry.jl b/base/pkg/entry.jl index 24d25827ffc10..b8c1987efa8f8 100644 --- a/base/pkg/entry.jl +++ b/base/pkg/entry.jl @@ -301,7 +301,7 @@ function pin(pkg::AbstractString, head::AbstractString) else LibGit2.revparseid(repo, head) end - commit = LibGit2.get(LibGit2.GitCommit, repo, id) + commit = LibGit2.GitCommit(repo, id) try # note: changing the following naming scheme requires a corresponding change in Read.ispinned() branch = "pinned.$(string(id)[1:8]).tmp" diff --git a/test/libgit2.jl b/test/libgit2.jl index 8bf024974d9c5..04a2c4dcf713a 100644 --- a/test/libgit2.jl +++ b/test/libgit2.jl @@ -30,8 +30,8 @@ end @test z == LibGit2.GitHash(rr) @test z == LibGit2.GitHash(rs) @test z == LibGit2.GitHash(pointer(rr)) - for i in 11:length(rr); rr[i] = 0; end - @test LibGit2.GitHash(rr) == LibGit2.GitHash(rs[1:20]) + + @test LibGit2.GitShortHash(z, 20) == LibGit2.GitShortHash(rs[1:20]) @test_throws ArgumentError LibGit2.GitHash(Ptr{UInt8}(C_NULL)) end @@ -282,7 +282,7 @@ mktempdir() do dir end # lookup commits - cmt = LibGit2.get(LibGit2.GitCommit, repo, commit_oid1) + cmt = LibGit2.GitCommit(repo, commit_oid1) try @test commit_oid1 == LibGit2.GitHash(cmt) auth = LibGit2.author(cmt) @@ -438,11 +438,13 @@ mktempdir() do dir @testset "blobs" begin repo = LibGit2.GitRepo(cache_repo) try - # clear out the "GitHash( )" part - hash_string = sprint(show, commit_oid1)[9:end] - hash_string = strip(hash_string, ')') + # this is slightly dubious, as it assumes the object has not been packed + # could be replaced by another binary format + hash_string = hex(commit_oid1) blob_file = joinpath(cache_repo,".git/objects", hash_string[1:2], hash_string[3:end]) - blob = LibGit2.GitBlob(repo, blob_file) + + id = LibGit2.addblob!(repo, blob_file) + blob = LibGit2.GitBlob(repo, id) @test LibGit2.isbinary(blob) blob_show_strs = split(sprint(show, blob), "\n") @test blob_show_strs[1] == "GitBlob:"