/
walker.jl
173 lines (153 loc) · 6.39 KB
/
walker.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
GitRevWalker(repo::GitRepo)
A `GitRevWalker` *walks* through the *revisions* (i.e. commits) of
a git repository `repo`. It is a collection of the commits
in the repository, and supports iteration and calls to [`LibGit2.map`](@ref)
and [`LibGit2.count`](@ref) (for instance, `LibGit2.count` could be used to determine
what percentage of commits in a repository were made by a certain
author).
```julia
cnt = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker
LibGit2.count((oid,repo)->(oid == commit_oid1), walker, oid=commit_oid1, by=LibGit2.Consts.SORT_TIME)
end
```
Here, `LibGit2.count` finds the number of commits along the walk with a certain `GitHash`.
Since the `GitHash` is unique to a commit, `cnt` will be `1`.
"""
function GitRevWalker(repo::GitRepo)
ensure_initialized()
w_ptr = Ref{Ptr{Cvoid}}(C_NULL)
@check ccall((:git_revwalk_new, :libgit2), Cint,
(Ptr{Ptr{Cvoid}}, Ptr{Cvoid}), w_ptr, repo.ptr)
return GitRevWalker(repo, w_ptr[])
end
function Base.iterate(w::GitRevWalker, state=nothing)
ensure_initialized()
id_ptr = Ref(GitHash())
err = ccall((:git_revwalk_next, :libgit2), Cint,
(Ptr{GitHash}, Ptr{Cvoid}), id_ptr, w.ptr)
if err == Cint(Error.GIT_OK)
return (id_ptr[], nothing)
elseif err == Cint(Error.ITEROVER)
return nothing
else
throw(GitError(err))
end
end
Base.IteratorSize(::Type{GitRevWalker}) = Base.SizeUnknown()
"""
LibGit2.push_head!(w::GitRevWalker)
Push the HEAD commit and its ancestors onto the [`GitRevWalker`](@ref)
`w`. This ensures that HEAD and all its ancestor commits will be encountered
during the walk.
"""
function push_head!(w::GitRevWalker)
ensure_initialized()
@check ccall((:git_revwalk_push_head, :libgit2), Cint, (Ptr{Cvoid},), w.ptr)
return w
end
"""
LibGit2.push!(w::GitRevWalker, cid::GitHash)
Start the [`GitRevWalker`](@ref) `walker` at commit `cid`. This function can be used
to apply a function to all commits since a certain year, by passing the first commit
of that year as `cid` and then passing the resulting `w` to [`LibGit2.map`](@ref).
"""
function push!(w::GitRevWalker, cid::GitHash)
ensure_initialized()
@check ccall((:git_revwalk_push, :libgit2), Cint, (Ptr{Cvoid}, Ptr{GitHash}), w.ptr, Ref(cid))
return w
end
function push!(w::GitRevWalker, range::AbstractString)
ensure_initialized()
@check ccall((:git_revwalk_push_range, :libgit2), Cint, (Ptr{Cvoid}, Ptr{UInt8}), w.ptr, range)
return w
end
function Base.sort!(w::GitRevWalker; by::Cint = Consts.SORT_NONE, rev::Bool=false)
ensure_initialized()
rev && (by |= Consts.SORT_REVERSE)
@check ccall((:git_revwalk_sorting, :libgit2), Cint, (Ptr{Cvoid}, Cint), w.ptr, by)
return w
end
repository(w::GitRevWalker) = w.owner
"""
LibGit2.map(f::Function, walker::GitRevWalker; oid::GitHash=GitHash(), range::AbstractString="", by::Cint=Consts.SORT_NONE, rev::Bool=false)
Using the [`GitRevWalker`](@ref) `walker` to "walk" over every commit in the repository's history,
apply `f` to each commit in the walk. The keyword arguments are:
* `oid`: The [`GitHash`](@ref) of the commit to begin the walk from. The default is to use
[`push_head!`](@ref) and therefore the HEAD commit and all its ancestors.
* `range`: A range of `GitHash`s in the format `oid1..oid2`. `f` will be
applied to all commits between the two.
* `by`: The sorting method. The default is not to sort. Other options are to sort by
topology (`LibGit2.Consts.SORT_TOPOLOGICAL`), to sort forwards in time
(`LibGit2.Consts.SORT_TIME`, most ancient first) or to sort backwards in time
(`LibGit2.Consts.SORT_REVERSE`, most recent first).
* `rev`: Whether to reverse the sorted order (for instance, if topological sorting is used).
# Examples
```julia
oids = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker
LibGit2.map((oid, repo)->string(oid), walker, by=LibGit2.Consts.SORT_TIME)
end
```
Here, `LibGit2.map` visits each commit using the `GitRevWalker` and finds its `GitHash`.
"""
function map(f::Function, walker::GitRevWalker;
oid::GitHash=GitHash(),
range::AbstractString="",
by::Cint = Consts.SORT_NONE,
rev::Bool=false,
count::Int=0)
res = []
sort!(walker, by=by, rev=rev)
if !iszero(oid)
push!(walker, oid)
elseif !isempty(range)
push!(walker, range)
else
push_head!(walker)
end
repo = repository(walker)
for val in (count == 0 ? walker : Iterators.take(walker, count))
Base.push!(res, f(val, repo))
end
return res
end
"""
LibGit2.count(f::Function, walker::GitRevWalker; oid::GitHash=GitHash(), by::Cint=Consts.SORT_NONE, rev::Bool=false)
Using the [`GitRevWalker`](@ref) `walker` to "walk" over every commit in the repository's history,
find the number of commits which return `true` when `f` is applied to them. The keyword arguments
are:
* `oid`: The [`GitHash`](@ref) of the commit to begin the walk from. The default is to use
[`push_head!`](@ref) and therefore the HEAD commit and all its ancestors.
* `by`: The sorting method. The default is not to sort. Other options are to sort by
topology (`LibGit2.Consts.SORT_TOPOLOGICAL`), to sort forwards in time
(`LibGit2.Consts.SORT_TIME`, most ancient first) or to sort backwards in time
(`LibGit2.Consts.SORT_REVERSE`, most recent first).
* `rev`: Whether to reverse the sorted order (for instance, if topological sorting is used).
# Examples
```julia
cnt = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker
LibGit2.count((oid, repo)->(oid == commit_oid1), walker, oid=commit_oid1, by=LibGit2.Consts.SORT_TIME)
end
```
`LibGit2.count` finds the number of commits along the walk with a certain `GitHash` `commit_oid1`, starting
the walk from that commit and moving forwards in time from it. Since the `GitHash` is unique to
a commit, `cnt` will be `1`.
"""
function count(f::Function, walker::GitRevWalker;
oid::GitHash=GitHash(),
by::Cint = Consts.SORT_NONE,
rev::Bool=false)
c = 0
sort!(walker, by=by, rev=rev)
if !iszero(oid)
push!(walker, oid)
else
push_head!(walker)
end
repo = repository(walker)
for val in walker
c += f(val, repo) == true
end
return c
end