Skip to content

Commit 9a0eff5

Browse files
committed
refactor!: use reusable classes
1 parent aa3bcc0 commit 9a0eff5

2 files changed

Lines changed: 130 additions & 135 deletions

File tree

src/init.lua

Lines changed: 114 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -63,58 +63,38 @@ local lib = ffi.load(here .. (sep == "\\" and "git2.dll" or "libgit2.so"))
6363

6464
lib.git_libgit2_init()
6565

66-
-- ffi.cdata* phantom types for LuaCATS
66+
-- ffi phantom types
6767

6868
---@class git2.ffi.Oid: ffi.cdata*
69-
---@field id number[]
70-
7169
---@class git2.ffi.Repository: ffi.cdata*
72-
7370
---@class git2.ffi.Commit: ffi.cdata*
74-
7571
---@class git2.ffi.Object: ffi.cdata*
76-
7772
---@class git2.ffi.Reference: ffi.cdata*
78-
7973
---@class git2.ffi.Index: ffi.cdata*
80-
8174
---@class git2.ffi.Remote: ffi.cdata*
82-
8375
---@class git2.ffi.Signature: ffi.cdata*
8476
---@field name string
8577
---@field email string
8678
---@field when_time integer
8779

88-
-- Lua-land value types
80+
-- value types
8981

9082
---@class git2.Sig
9183
---@field name string
9284
---@field email string
93-
---@field time number Unix timestamp
85+
---@field time number
9486

9587
---@class git2.Commit
96-
---@field id string 40-char hex SHA
97-
---@field message string Full commit message
98-
---@field summary string First line of message
88+
---@field id string
89+
---@field message string
90+
---@field summary string
9991
---@field author git2.Sig
10092
---@field committer git2.Sig
101-
---@field time number Unix timestamp
93+
---@field time number
10294
---@field parents string[]
10395

104-
---@class git2.Repo
105-
---@field path fun(): string Absolute path to the .git directory
106-
---@field workdir fun(): string|nil Working directory, nil for bare repos
107-
---@field isBare fun(): boolean
108-
---@field headUnborn fun(): boolean True when repo has no commits yet
109-
---@field head fun(): string SHA of HEAD commit
110-
---@field commitLookup fun(sha: string): git2.Commit
111-
---@field revparse fun(spec: string): string Resolve any refspec to a SHA
112-
---@field indexAdd fun(relpath: string)
113-
---@field indexRemove fun(relpath: string)
114-
---@field indexWrite fun()
115-
---@field indexWriteTree fun(): string Tree SHA
116-
---@field fetch fun(remote: string)
117-
---@field free fun()
96+
-- helpers
97+
11898
local sigLayout = ffi.typeof("struct { char *name; char *email; int64_t when_time; int when_offset; } *")
11999

120100
---@param code integer
@@ -158,121 +138,136 @@ local function wrapCommit(c)
158138
}
159139
end
160140

161-
---@param path string
162-
---@param bare boolean?
163-
---@return git2.Repo
164-
local function openRepo(path, bare)
165-
local rp = ffi.new("git_repository*[1]")
166-
if bare ~= nil then
167-
check(lib.git_repository_init(rp, path, bare and 1 or 0))
168-
else
169-
check(lib.git_repository_open(rp, path))
170-
end
171-
---@type git2.ffi.Repository
172-
local repo = rp[0]
173-
174-
---@type git2.Repo
175-
local M = {}
141+
-- Repo class
176142

177-
---@return string
178-
function M.path() return ffi.string(lib.git_repository_path(repo)) end
143+
---@class git2.Repo
144+
---@field _repo git2.ffi.Repository
145+
local Repo = {}
146+
Repo.__index = Repo
179147

180-
---@return string|nil
181-
function M.workdir()
182-
local p = lib.git_repository_workdir(repo)
183-
return p ~= nil and ffi.string(p) or nil
184-
end
148+
---@param repo git2.ffi.Repository
149+
---@return git2.Repo
150+
function Repo.new(repo)
151+
return setmetatable({ _repo = repo }, Repo)
152+
end
185153

186-
---@return boolean
187-
function M.isBare() return lib.git_repository_is_bare(repo) == 1 end
154+
---@return string
155+
function Repo:path()
156+
return ffi.string(lib.git_repository_path(self._repo))
157+
end
188158

189-
---@return boolean
190-
function M.headUnborn() return lib.git_repository_head_unborn(repo) == 1 end
159+
---@return string|nil
160+
function Repo:workdir()
161+
local p = lib.git_repository_workdir(self._repo)
162+
return p ~= nil and ffi.string(p) or nil
163+
end
191164

192-
---@return string
193-
function M.head()
194-
local ref = ffi.new("git_reference*[1]")
195-
check(lib.git_repository_head(ref, repo))
196-
local sha = oidStr(lib.git_reference_target(ref[0]))
197-
lib.git_reference_free(ref[0])
198-
return sha
199-
end
165+
---@return boolean
166+
function Repo:isBare()
167+
return lib.git_repository_is_bare(self._repo) == 1
168+
end
200169

201-
---@param sha string
202-
---@return git2.Commit
203-
function M.commitLookup(sha)
204-
local oid = ffi.new("git_oid")
205-
check(lib.git_oid_fromstr(oid, sha))
206-
local cp = ffi.new("git_commit*[1]")
207-
check(lib.git_commit_lookup(cp, repo, oid))
208-
local info = wrapCommit(cp[0])
209-
lib.git_commit_free(cp[0])
210-
return info
211-
end
170+
---@return boolean
171+
function Repo:headUnborn()
172+
return lib.git_repository_head_unborn(self._repo) == 1
173+
end
212174

213-
---@param spec string
214-
---@return string
215-
function M.revparse(spec)
216-
local op = ffi.new("git_object*[1]")
217-
check(lib.git_revparse_single(op, repo, spec))
218-
local sha = oidStr(lib.git_object_id(op[0]))
219-
lib.git_object_free(op[0])
220-
return sha
221-
end
175+
---@return string
176+
function Repo:head()
177+
local ref = ffi.new("git_reference*[1]")
178+
check(lib.git_repository_head(ref, self._repo))
179+
local sha = oidStr(lib.git_reference_target(ref[0]))
180+
lib.git_reference_free(ref[0])
181+
return sha
182+
end
222183

223-
---@generic T
224-
---@param fn fun(idx: git2.ffi.Index): T
225-
---@return T
226-
local function withIndex(fn)
227-
local ip = ffi.new("git_index*[1]")
228-
check(lib.git_repository_index(ip, repo))
229-
local result = fn(ip[0])
230-
lib.git_index_free(ip[0])
231-
return result
232-
end
184+
---@param sha string
185+
---@return git2.Commit
186+
function Repo:commitLookup(sha)
187+
local oid = ffi.new("git_oid")
188+
check(lib.git_oid_fromstr(oid, sha))
189+
local cp = ffi.new("git_commit*[1]")
190+
check(lib.git_commit_lookup(cp, self._repo, oid))
191+
local info = wrapCommit(cp[0])
192+
lib.git_commit_free(cp[0])
193+
return info
194+
end
233195

234-
---@param relpath string
235-
function M.indexAdd(relpath) withIndex(function(i) check(lib.git_index_add_bypath(i, relpath)) end) end
196+
---@param spec string
197+
---@return string
198+
function Repo:revparse(spec)
199+
local op = ffi.new("git_object*[1]")
200+
check(lib.git_revparse_single(op, self._repo, spec))
201+
local sha = oidStr(lib.git_object_id(op[0]))
202+
lib.git_object_free(op[0])
203+
return sha
204+
end
236205

237-
---@param relpath string
238-
function M.indexRemove(relpath) withIndex(function(i) check(lib.git_index_remove_bypath(i, relpath)) end) end
206+
---@param relpath string
207+
function Repo:indexAdd(relpath)
208+
local ip = ffi.new("git_index*[1]")
209+
check(lib.git_repository_index(ip, self._repo))
210+
check(lib.git_index_add_bypath(ip[0], relpath))
211+
lib.git_index_free(ip[0])
212+
end
239213

240-
function M.indexWrite() withIndex(function(i) check(lib.git_index_write(i)) end) end
214+
---@param relpath string
215+
function Repo:indexRemove(relpath)
216+
local ip = ffi.new("git_index*[1]")
217+
check(lib.git_repository_index(ip, self._repo))
218+
check(lib.git_index_remove_bypath(ip[0], relpath))
219+
lib.git_index_free(ip[0])
220+
end
241221

242-
---@return string
243-
function M.indexWriteTree()
244-
return withIndex(function(i)
245-
local oid = ffi.new("git_oid")
246-
check(lib.git_index_write_tree(oid, i))
247-
return oidStr(oid)
248-
end)
249-
end
222+
function Repo:indexWrite()
223+
local ip = ffi.new("git_index*[1]")
224+
check(lib.git_repository_index(ip, self._repo))
225+
check(lib.git_index_write(ip[0]))
226+
lib.git_index_free(ip[0])
227+
end
250228

251-
---@param remoteName string
252-
function M.fetch(remoteName)
253-
local rmt = ffi.new("git_remote*[1]")
254-
check(lib.git_remote_lookup(rmt, repo, remoteName))
255-
check(lib.git_remote_fetch(rmt[0], nil, nil, nil))
256-
lib.git_remote_free(rmt[0])
257-
end
229+
---@return string
230+
function Repo:indexWriteTree()
231+
local ip = ffi.new("git_index*[1]")
232+
check(lib.git_repository_index(ip, self._repo))
233+
local oid = ffi.new("git_oid")
234+
check(lib.git_index_write_tree(oid, ip[0]))
235+
lib.git_index_free(ip[0])
236+
return oidStr(oid)
237+
end
258238

259-
function M.free() lib.git_repository_free(repo) end
239+
---@param remoteName string
240+
function Repo:fetch(remoteName)
241+
local rmt = ffi.new("git_remote*[1]")
242+
check(lib.git_remote_lookup(rmt, self._repo, remoteName))
243+
check(lib.git_remote_fetch(rmt[0], nil, nil, nil))
244+
lib.git_remote_free(rmt[0])
245+
end
260246

261-
return M
247+
function Repo:free()
248+
lib.git_repository_free(self._repo)
262249
end
263250

251+
-- module
252+
264253
---@class git2
265254
local git2 = {}
266255

267-
---Open an existing repository.
268256
---@param path string
269257
---@return git2.Repo
270-
function git2.open(path) return openRepo(path) end
258+
function git2.open(path)
259+
local rp = ffi.new("git_repository*[1]")
260+
check(lib.git_repository_open(rp, path))
261+
return Repo.new(rp[0])
262+
end
271263

272-
---Initialise a new repository.
273264
---@param path string
274265
---@param bare boolean?
275266
---@return git2.Repo
276-
function git2.init(path, bare) return openRepo(path, bare or false) end
267+
function git2.init(path, bare)
268+
local rp = ffi.new("git_repository*[1]")
269+
check(lib.git_repository_init(rp, path, bare and 1 or 0))
270+
return Repo.new(rp[0])
271+
end
277272

278273
return git2

tests/git2.test.lua

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,52 +30,52 @@ end
3030

3131
test.it("init creates a non-bare repo", function()
3232
local repo = git2.init(mkTmp("init"))
33-
test.truthy(repo.path():find(".git"))
34-
test.equal(repo.isBare(), false)
35-
test.equal(repo.headUnborn(), true)
36-
repo.free()
33+
test.truthy(repo:path():find(".git"))
34+
test.equal(repo:isBare(), false)
35+
test.equal(repo:headUnborn(), true)
36+
repo:free()
3737
end)
3838

3939
test.it("init bare creates a bare repo", function()
4040
local repo = git2.init(mkTmp("bare"), true)
41-
test.equal(repo.isBare(), true)
42-
repo.free()
41+
test.equal(repo:isBare(), true)
42+
repo:free()
4343
end)
4444

4545
test.it("open existing repo works", function()
4646
local src = debug.getinfo(1, "S").source:sub(2):match("(.*[/\\])")
4747
local repo = git2.open(src .. "..")
48-
test.truthy(repo.path())
49-
test.equal(repo.isBare(), false)
50-
repo.free()
48+
test.truthy(repo:path())
49+
test.equal(repo:isBare(), false)
50+
repo:free()
5151
end)
5252

5353
test.it("head returns a 40-char sha", function()
5454
local dir = mkTmp("head")
5555
mkCommit(dir, "init")
5656
local repo = git2.open(dir)
57-
test.equal(#repo.head(), 40)
58-
repo.free()
57+
test.equal(#repo:head(), 40)
58+
repo:free()
5959
end)
6060

6161
test.it("commitLookup returns correct metadata", function()
6262
local dir = mkTmp("meta")
6363
mkCommit(dir, "hello world")
6464
local repo = git2.open(dir)
65-
local sha = repo.head()
66-
local c = repo.commitLookup(sha)
65+
local sha = repo:head()
66+
local c = repo:commitLookup(sha)
6767
test.equal(c.id, sha)
6868
test.truthy(c.summary:find("hello world"))
6969
test.equal(c.author.name, "T")
7070
test.equal(c.author.email, "t@t.com")
7171
test.truthy(c.time > 0)
72-
repo.free()
72+
repo:free()
7373
end)
7474

7575
test.it("revparse HEAD matches head()", function()
7676
local dir = mkTmp("rev")
7777
mkCommit(dir, "rev")
7878
local repo = git2.open(dir)
79-
test.equal(repo.revparse("HEAD"), repo.head())
80-
repo.free()
79+
test.equal(repo:revparse("HEAD"), repo:head())
80+
repo:free()
8181
end)

0 commit comments

Comments
 (0)