cmd_init_spec.lua

  1-- SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2--
  3-- SPDX-License-Identifier: GPL-3.0-or-later
  4
  5package.path = package.path .. ";./?.lua"
  6local wt = dofile("src/main.lua")
  7local helper = require("spec.test_helper")
  8local git = helper.git
  9
 10describe("cmd_init", function()
 11	local temp_dir
 12	local original_cwd
 13
 14	setup(function()
 15		local handle = io.popen("pwd")
 16		if handle then
 17			original_cwd = handle:read("*l")
 18			handle:close()
 19		end
 20		handle = io.popen("mktemp -d")
 21		if handle then
 22			temp_dir = handle:read("*l")
 23			handle:close()
 24		end
 25	end)
 26
 27	teardown(function()
 28		if original_cwd then
 29			os.execute("cd " .. original_cwd)
 30		end
 31		if temp_dir then
 32			os.execute("rm -rf " .. temp_dir)
 33		end
 34	end)
 35
 36	describe("already wt-managed repository", function()
 37		it("reports already using wt structure when .git file points to .bare", function()
 38			if not temp_dir then
 39				pending("temp_dir not available")
 40				return
 41			end
 42			local project = temp_dir .. "/already-wt"
 43			os.execute("mkdir -p " .. project .. "/.bare")
 44			git("init --bare " .. project .. "/.bare")
 45			local f = io.open(project .. "/.git", "w")
 46			if f then
 47				f:write("gitdir: ./.bare\n")
 48				f:close()
 49			end
 50
 51			local output, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init")
 52			assert.are.equal(0, code)
 53			assert.is_truthy(output:match("[Aa]lready"))
 54		end)
 55	end)
 56
 57	describe("inside a worktree", function()
 58		it("errors when run from inside a worktree", function()
 59			if not temp_dir then
 60				pending("temp_dir not available")
 61				return
 62			end
 63			local project = temp_dir .. "/wt-project"
 64			local worktree = project .. "/main"
 65			os.execute("mkdir -p " .. project .. "/.bare")
 66			git("init --bare " .. project .. "/.bare")
 67			os.execute("mkdir -p " .. worktree)
 68			local f = io.open(worktree .. "/.git", "w")
 69			if f then
 70				f:write("gitdir: ../.bare/worktrees/main\n")
 71				f:close()
 72			end
 73
 74			local output, code = wt.run_cmd("cd " .. worktree .. " && lua " .. original_cwd .. "/src/main.lua init 2>&1")
 75			assert.are_not.equal(0, code)
 76			assert.is_truthy(output:match("worktree") or output:match("not a git"))
 77		end)
 78	end)
 79
 80	describe("not a git repository", function()
 81		it("errors when run in non-git directory", function()
 82			if not temp_dir then
 83				pending("temp_dir not available")
 84				return
 85			end
 86			local project = temp_dir .. "/not-git"
 87			os.execute("mkdir -p " .. project)
 88
 89			local output, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init 2>&1")
 90			assert.are_not.equal(0, code)
 91			assert.is_truthy(output:match("not a git"))
 92		end)
 93	end)
 94
 95	describe(".bare exists but no .git", function()
 96		it("errors and suggests creating .git file", function()
 97			if not temp_dir then
 98				pending("temp_dir not available")
 99				return
100			end
101			local project = temp_dir .. "/bare-only"
102			os.execute("mkdir -p " .. project .. "/.bare")
103			git("init --bare " .. project .. "/.bare")
104
105			local output, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init 2>&1")
106			assert.are_not.equal(0, code)
107			assert.is_truthy(output:match("%.git") or output:match("gitdir"))
108		end)
109	end)
110
111	describe("existing git worktrees", function()
112		it("errors when .git/worktrees/ exists", function()
113			if not temp_dir then
114				pending("temp_dir not available")
115				return
116			end
117			local project = temp_dir .. "/has-worktrees"
118			os.execute("mkdir -p " .. project)
119			git("init " .. project)
120			git("-C " .. project .. " commit --allow-empty -m 'initial'")
121			os.execute("mkdir -p " .. project .. "/.git/worktrees/feature-branch")
122
123			local output, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init 2>&1")
124			assert.are_not.equal(0, code)
125			assert.is_truthy(output:match("worktree"))
126		end)
127	end)
128
129	describe("dirty repository", function()
130		it("errors when uncommitted changes exist", function()
131			if not temp_dir then
132				pending("temp_dir not available")
133				return
134			end
135			local project = temp_dir .. "/dirty-repo"
136			os.execute("mkdir -p " .. project)
137			git("init " .. project)
138			git("-C " .. project .. " commit --allow-empty -m 'initial'")
139			local f = io.open(project .. "/dirty.txt", "w")
140			if f then
141				f:write("uncommitted\n")
142				f:close()
143			end
144			git("-C " .. project .. " add dirty.txt")
145
146			local output, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init 2>&1")
147			assert.are_not.equal(0, code)
148			assert.is_truthy(output:match("uncommitted") or output:match("stash") or output:match("commit"))
149		end)
150	end)
151
152	describe("--dry-run", function()
153		it("prints plan without modifying filesystem", function()
154			if not temp_dir then
155				pending("temp_dir not available")
156				return
157			end
158			local project = temp_dir .. "/dry-run-test"
159			os.execute("mkdir -p " .. project)
160			git("init " .. project)
161			git("-C " .. project .. " commit --allow-empty -m 'initial'")
162
163			local cmd = "cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init --dry-run 2>&1"
164			local output, code = wt.run_cmd(cmd)
165			assert.are.equal(0, code)
166			assert.is_truthy(output:match("[Dd]ry run") or output:match("planned"))
167
168			local git_still_dir = io.open(project .. "/.git/HEAD", "r")
169			assert.is_not_nil(git_still_dir)
170			if git_still_dir then
171				git_still_dir:close()
172			end
173
174			local bare_exists = io.open(project .. "/.bare/HEAD", "r")
175			assert.is_nil(bare_exists)
176		end)
177
178		it("lists orphaned files that would be removed", function()
179			if not temp_dir then
180				pending("temp_dir not available")
181				return
182			end
183			local project = temp_dir .. "/dry-orphans"
184			os.execute("mkdir -p " .. project)
185			git("init " .. project)
186			git("-C " .. project .. " commit --allow-empty -m 'initial'")
187
188			local f = io.open(project .. "/README.md", "w")
189			if f then
190				f:write("# Test\n")
191				f:close()
192			end
193			git("-C " .. project .. " add README.md")
194			git("-C " .. project .. " commit -m 'add readme'")
195
196			local cmd = "cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init --dry-run 2>&1"
197			local output, code = wt.run_cmd(cmd)
198			assert.are.equal(0, code)
199			assert.is_truthy(output:match("README") or output:match("orphan"))
200		end)
201
202		it("shows target worktree path", function()
203			if not temp_dir then
204				pending("temp_dir not available")
205				return
206			end
207			local project = temp_dir .. "/dry-worktree"
208			os.execute("mkdir -p " .. project)
209			git("init " .. project)
210			git("-C " .. project .. " commit --allow-empty -m 'initial'")
211
212			local cmd = "cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init --dry-run 2>&1"
213			local output, code = wt.run_cmd(cmd)
214			assert.are.equal(0, code)
215			assert.is_truthy(output:match("worktree") or output:match("main") or output:match("master"))
216		end)
217	end)
218
219	describe("-y/--yes flag", function()
220		it("bypasses confirmation prompt", function()
221			if not temp_dir then
222				pending("temp_dir not available")
223				return
224			end
225			local project = temp_dir .. "/yes-test"
226			os.execute("mkdir -p " .. project)
227			git("init " .. project)
228			git("-C " .. project .. " commit --allow-empty -m 'initial'")
229
230			local output, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init -y 2>&1")
231			assert.are.equal(0, code)
232			assert.is_truthy(output:match("[Cc]onverted") or output:match("[Bb]are"))
233
234			local bare_exists = io.open(project .. "/.bare/HEAD", "r")
235			assert.is_not_nil(bare_exists)
236			if bare_exists then
237				bare_exists:close()
238			end
239
240			local git_is_file = io.open(project .. "/.git", "r")
241			if git_is_file then
242				local content = git_is_file:read("*a")
243				git_is_file:close()
244				assert.is_truthy(content:match("gitdir"))
245			end
246		end)
247	end)
248
249	describe("successful conversion", function()
250		it("moves .git to .bare", function()
251			if not temp_dir then
252				pending("temp_dir not available")
253				return
254			end
255			local project = temp_dir .. "/convert-test"
256			os.execute("mkdir -p " .. project)
257			git("init " .. project)
258			git("-C " .. project .. " commit --allow-empty -m 'initial'")
259
260			local _, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init -y 2>&1")
261			assert.are.equal(0, code)
262
263			local bare_head = io.open(project .. "/.bare/HEAD", "r")
264			assert.is_not_nil(bare_head)
265			if bare_head then
266				bare_head:close()
267			end
268		end)
269
270		it("creates .git file pointing to .bare", function()
271			if not temp_dir then
272				pending("temp_dir not available")
273				return
274			end
275			local project = temp_dir .. "/git-file-test"
276			os.execute("mkdir -p " .. project)
277			git("init " .. project)
278			git("-C " .. project .. " commit --allow-empty -m 'initial'")
279
280			local _, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init -y 2>&1")
281			assert.are.equal(0, code)
282
283			local git_file = io.open(project .. "/.git", "r")
284			assert.is_not_nil(git_file)
285			if git_file then
286				local content = git_file:read("*a")
287				git_file:close()
288				assert.is_truthy(content:match("gitdir:.*%.bare"))
289			end
290		end)
291
292		it("creates worktree for default branch", function()
293			if not temp_dir then
294				pending("temp_dir not available")
295				return
296			end
297			local project = temp_dir .. "/worktree-created"
298			os.execute("mkdir -p " .. project)
299			git("init " .. project)
300			git("-C " .. project .. " commit --allow-empty -m 'initial'")
301
302			local cmd = "cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init -y 2>&1"
303			local _, code = wt.run_cmd(cmd)
304			assert.are.equal(0, code)
305
306			local worktrees_output, _ = wt.run_cmd("GIT_DIR=" .. project .. "/.bare git worktree list")
307			assert.is_truthy(worktrees_output:match("main") or worktrees_output:match("master"))
308		end)
309
310		it("removes orphaned files from root", function()
311			if not temp_dir then
312				pending("temp_dir not available")
313				return
314			end
315			local project = temp_dir .. "/orphan-cleanup"
316			os.execute("mkdir -p " .. project)
317			git("init " .. project)
318			local f = io.open(project .. "/tracked.txt", "w")
319			if f then
320				f:write("tracked\n")
321				f:close()
322			end
323			git("-C " .. project .. " add tracked.txt")
324			git("-C " .. project .. " commit -m 'add file'")
325
326			local _, code = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init -y 2>&1")
327			assert.are.equal(0, code)
328
329			local orphan_check = io.open(project .. "/tracked.txt", "r")
330			assert.is_nil(orphan_check)
331		end)
332	end)
333
334	describe("warnings", function()
335		it("warns about submodules", function()
336			if not temp_dir then
337				pending("temp_dir not available")
338				return
339			end
340			local project = temp_dir .. "/submodule-warn"
341			os.execute("mkdir -p " .. project)
342			git("init " .. project)
343			git("-C " .. project .. " commit --allow-empty -m 'initial'")
344			local f = io.open(project .. "/.gitmodules", "w")
345			if f then
346				f:write("[submodule \"lib\"]\n\tpath = lib\n\turl = https://example.com/lib.git\n")
347				f:close()
348			end
349			git("-C " .. project .. " add .gitmodules")
350			git("-C " .. project .. " commit -m 'add submodules'")
351
352			local output, _ = wt.run_cmd("cd " .. project .. " && lua " .. original_cwd .. "/src/main.lua init --dry-run 2>&1")
353			assert.is_truthy(output:match("submodule") or output:match("[Ww]arning"))
354		end)
355	end)
356end)