Skip to content

Commit 755bf41

Browse files
authored
LibGit2: Add wrappers for various diff-related functions (#59700)
Added Julia wrappers for three libgit2 functions: - `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. Written by Claude Co-authored-by: Keno Fischer <[email protected]>
1 parent 03f031a commit 755bf41

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(repo, 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)