Skip to content

Commit 738ee1a

Browse files
committed
LibGit2: Add wrappers for git_apply_to_tree, git_diff_from_buffer, and git_index_write_tree_to
Added Julia wrappers for three libgit2 functions with idiomatic APIs: - `GitDiff(::AbstractString)`: Parse diffs from unified diff format buffers - `apply_to_tree(repo, preimage, diff, options)`: Apply a diff to a tree, returning a GitIndex with the result - `write_tree_to!(repo, idx)`: Write an index as a tree to a repository Made GitDiff and GitDiffStats repository-independent since they can be created from buffers and all operations work purely on object pointers. Added ApplyOptions struct for apply configuration and tests for all new functionality.
1 parent 39e1473 commit 738ee1a

File tree

5 files changed

+130
-5
lines changed

5 files changed

+130
-5
lines changed

stdlib/LibGit2/src/diff.jl

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function diff_tree(repo::GitRepo, tree::GitTree, pathspecs::AbstractString=""; c
3535
(Ptr{Ptr{Cvoid}}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{DiffOptionsStruct}),
3636
diff_ptr_ptr, repo, tree, isempty(pathspecs) ? C_NULL : pathspecs)
3737
end
38-
return GitDiff(repo, diff_ptr_ptr[])
38+
return GitDiff(diff_ptr_ptr[])
3939
end
4040

4141
"""
@@ -54,7 +54,7 @@ function diff_tree(repo::GitRepo, oldtree::GitTree, newtree::GitTree)
5454
@check ccall((:git_diff_tree_to_tree, libgit2), Cint,
5555
(Ptr{Ptr{Cvoid}}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{DiffOptionsStruct}),
5656
diff_ptr_ptr, repo, oldtree, newtree, C_NULL)
57-
return GitDiff(repo, diff_ptr_ptr[])
57+
return GitDiff(diff_ptr_ptr[])
5858
end
5959

6060
"""
@@ -70,7 +70,7 @@ function GitDiffStats(diff::GitDiff)
7070
@check ccall((:git_diff_get_stats, libgit2), Cint,
7171
(Ptr{Ptr{Cvoid}}, Ptr{Cvoid}),
7272
diff_stat_ptr_ptr, diff)
73-
return GitDiffStats(diff.owner, diff_stat_ptr_ptr[])
73+
return GitDiffStats(diff_stat_ptr_ptr[])
7474
end
7575

7676
"""
@@ -142,3 +142,34 @@ function Base.show(io::IO, diff::GitDiff)
142142
println(io, "Number of deltas: $(count(diff))")
143143
show(io, GitDiffStats(diff))
144144
end
145+
146+
"""
147+
GitDiff(content::AbstractString)
148+
149+
Parse a diff from a buffer. The `content` should be in unified diff format.
150+
Returns a [`GitDiff`](@ref) object.
151+
152+
This is equivalent to [`git_diff_from_buffer`](https://libgit2.org/libgit2/#HEAD/group/diff/git_diff_from_buffer).
153+
154+
# Examples
155+
```julia
156+
diff_str = \"\"\"
157+
diff --git a/file.txt b/file.txt
158+
index 1234567..abcdefg 100644
159+
--- a/file.txt
160+
+++ b/file.txt
161+
@@ -1 +1 @@
162+
-old content
163+
+new content
164+
\"\"\"
165+
diff = LibGit2.GitDiff(diff_str)
166+
```
167+
"""
168+
function GitDiff(content::AbstractString)
169+
ensure_initialized()
170+
diff_ptr_ptr = Ref{Ptr{Cvoid}}(C_NULL)
171+
@check ccall((:git_diff_from_buffer, libgit2), Cint,
172+
(Ptr{Ptr{Cvoid}}, Cstring, Csize_t),
173+
diff_ptr_ptr, content, sizeof(content))
174+
return GitDiff(diff_ptr_ptr[])
175+
end

stdlib/LibGit2/src/index.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,32 @@ function write_tree!(idx::GitIndex)
5656
return oid_ptr[]
5757
end
5858

59+
"""
60+
write_tree_to!(repo::GitRepo, idx::GitIndex)::GitHash
61+
62+
Write the index `idx` as a [`GitTree`](@ref) to the given repository `repo`.
63+
This is similar to [`write_tree!`](@ref) but allows writing the index to a
64+
different repository than the one it may be associated with.
65+
66+
Trees will be recursively created for each subtree in `idx`. The returned
67+
[`GitHash`](@ref) can be used to create a [`GitCommit`](@ref).
68+
69+
This is equivalent to [`git_index_write_tree_to`](https://libgit2.org/libgit2/#HEAD/group/index/git_index_write_tree_to).
70+
71+
# Examples
72+
```julia
73+
idx = LibGit2.GitIndex(source_repo)
74+
tree_oid = LibGit2.write_tree_to!(target_repo, idx)
75+
```
76+
"""
77+
function write_tree_to!(repo::GitRepo, idx::GitIndex)
78+
ensure_initialized()
79+
oid_ptr = Ref(GitHash())
80+
@check ccall((:git_index_write_tree_to, libgit2), Cint,
81+
(Ptr{GitHash}, Ptr{Cvoid}, Ptr{Cvoid}), oid_ptr, idx, repo)
82+
return oid_ptr[]
83+
end
84+
5985
function repository(idx::GitIndex)
6086
if idx.owner === nothing
6187
throw(GitError(Error.Index, Error.ENOTFOUND, "Index does not have an owning repository."))

stdlib/LibGit2/src/tree.jl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,35 @@ end
194194
function Base.haskey(tree::GitTree, target::AbstractString)
195195
return _getindex(tree, target) !== nothing
196196
end
197+
198+
"""
199+
apply_to_tree(repo::GitRepo, preimage::GitTree, diff::GitDiff, options::ApplyOptions=ApplyOptions())
200+
201+
Apply a [`GitDiff`](@ref) to a [`GitTree`](@ref), returning the resulting index.
202+
The `preimage` is the tree to which the diff will be applied. The `diff` should be
203+
generated from the `preimage` to some other tree.
204+
205+
The returned [`GitIndex`](@ref) contains the result of applying the diff and can be
206+
written as a tree using [`write_tree_to!`](@ref).
207+
208+
This is equivalent to [`git_apply_to_tree`](https://libgit2.org/libgit2/#HEAD/group/apply/git_apply_to_tree).
209+
210+
# Examples
211+
```julia
212+
repo = LibGit2.GitRepo(repo_path)
213+
tree1 = LibGit2.GitTree(repo, "HEAD^{tree}")
214+
tree2 = LibGit2.GitTree(repo, "HEAD~1^{tree}")
215+
diff = LibGit2.diff_tree(repo, tree1, tree2)
216+
result_index = LibGit2.apply_to_tree(repo, tree1, diff)
217+
tree_oid = LibGit2.write_tree_to!(repo, result_index)
218+
```
219+
"""
220+
function apply_to_tree(repo::GitRepo, preimage::GitTree, diff::GitDiff, options::ApplyOptions=ApplyOptions())
221+
ensure_initialized()
222+
out_index_ptr = Ref{Ptr{Cvoid}}(C_NULL)
223+
opts_ptr = Ref(options)
224+
@check ccall((:git_apply_to_tree, libgit2), Cint,
225+
(Ptr{Ptr{Cvoid}}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{ApplyOptions}),
226+
out_index_ptr, repo, preimage, diff, opts_ptr)
227+
return GitIndex(nothing, out_index_ptr[])
228+
end

stdlib/LibGit2/src/types.jl

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,28 @@ The fields represent:
859859
end
860860
@assert Base.allocatedinline(StatusOptions)
861861

862+
"""
863+
LibGit2.ApplyOptions
864+
865+
Options for applying a diff.
866+
Matches the [`git_apply_options`](https://libgit2.org/libgit2/#HEAD/type/git_apply_options) struct.
867+
868+
The fields represent:
869+
* `version`: version of the struct in use, in case this changes later. For now, always `1`.
870+
* `delta_cb`: optional callback that will be made before each delta is applied.
871+
* `hunk_cb`: optional callback that will be made before each hunk is applied.
872+
* `payload`: the payload for the callback functions.
873+
* `flags`: flags controlling how the apply is performed (e.g., check mode).
874+
"""
875+
@kwdef struct ApplyOptions
876+
version::Cuint = Cuint(1)
877+
delta_cb::Ptr{Cvoid} = C_NULL
878+
hunk_cb::Ptr{Cvoid} = C_NULL
879+
payload::Any = nothing
880+
flags::Cuint = Cuint(0)
881+
end
882+
@assert Base.allocatedinline(ApplyOptions)
883+
862884
"""
863885
LibGit2.StatusEntry
864886
@@ -1042,8 +1064,8 @@ for (typ, owntyp, sup, cname) in Tuple{Symbol,Any,Symbol,Symbol}[
10421064
(:GitRevWalker, :GitRepo, :AbstractGitObject, :git_revwalk),
10431065
(:GitReference, :GitRepo, :AbstractGitObject, :git_reference),
10441066
(:GitDescribeResult, :GitRepo, :AbstractGitObject, :git_describe_result),
1045-
(:GitDiff, :GitRepo, :AbstractGitObject, :git_diff),
1046-
(:GitDiffStats, :GitRepo, :AbstractGitObject, :git_diff_stats),
1067+
(:GitDiff, nothing, :AbstractGitObject, :git_diff),
1068+
(:GitDiffStats, nothing, :AbstractGitObject, :git_diff_stats),
10471069
(:GitAnnotated, :GitRepo, :AbstractGitObject, :git_annotated_commit),
10481070
(:GitRebase, :GitRepo, :AbstractGitObject, :git_rebase),
10491071
(:GitBlame, :GitRepo, :AbstractGitObject, :git_blame),

stdlib/LibGit2/test/libgit2-tests.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,20 @@ mktempdir() do dir
11471147
@test !LibGit2.isdirty(repo, cached=true)
11481148
@test !LibGit2.isdiff(repo, "HEAD", cached=true)
11491149
end
1150+
1151+
LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo
1152+
diff_str = "diff --git a/test.txt b/test.txt\nindex 0000000..1111111 100644\n"
1153+
@test_throws LibGit2.GitError LibGit2.GitDiff(diff_str)
1154+
1155+
tree1 = LibGit2.GitTree(repo, "HEAD~1^{tree}")
1156+
tree2 = LibGit2.GitTree(repo, "HEAD^{tree}")
1157+
diff = LibGit2.diff_tree(repo, tree1, tree2)
1158+
idx = LibGit2.apply_to_tree(repo, tree1, diff)
1159+
@test idx isa LibGit2.GitIndex
1160+
oid = LibGit2.write_tree_to!(repo, idx)
1161+
@test oid isa LibGit2.GitHash
1162+
close(idx)
1163+
end
11501164
end
11511165
end
11521166

0 commit comments

Comments
 (0)