project_root_spec.lua

  1package.path = package.path .. ";./?.lua"
  2local wt = dofile("src/main.lua")
  3
  4describe("find_project_root", function()
  5	local temp_dir
  6
  7	setup(function()
  8		local handle = io.popen("mktemp -d")
  9		if handle then
 10			temp_dir = handle:read("*l")
 11			handle:close()
 12		end
 13	end)
 14
 15	teardown(function()
 16		if temp_dir then
 17			os.execute("rm -rf " .. temp_dir)
 18		end
 19	end)
 20
 21	describe("wt-managed repository (.bare + .git file)", function()
 22		it("finds root when cwd is project root", function()
 23			if not temp_dir then
 24				pending("temp_dir not available")
 25				return
 26			end
 27			local project = temp_dir .. "/project"
 28			os.execute("mkdir -p " .. project .. "/.bare")
 29			os.execute("git init --bare " .. project .. "/.bare")
 30			local f = io.open(project .. "/.git", "w")
 31			if f then
 32				f:write("gitdir: ./.bare\n")
 33				f:close()
 34			end
 35
 36			local root, err = wt.find_project_root(project)
 37			assert.is_nil(err)
 38			assert.are.equal(project, root)
 39		end)
 40
 41		it("finds root when cwd is inside worktree", function()
 42			if not temp_dir then
 43				pending("temp_dir not available")
 44				return
 45			end
 46			local project = temp_dir .. "/project2"
 47			os.execute("mkdir -p " .. project .. "/.bare")
 48			os.execute("git init --bare " .. project .. "/.bare")
 49			local f = io.open(project .. "/.git", "w")
 50			if f then
 51				f:write("gitdir: ./.bare\n")
 52				f:close()
 53			end
 54
 55			os.execute("mkdir -p " .. project .. "/main/src/lib")
 56			local wt_git = io.open(project .. "/main/.git", "w")
 57			if wt_git then
 58				wt_git:write("gitdir: ../.bare/worktrees/main\n")
 59				wt_git:close()
 60			end
 61
 62			local root, err = wt.find_project_root(project .. "/main/src/lib")
 63			assert.is_nil(err)
 64			assert.are.equal(project, root)
 65		end)
 66
 67		it("finds root when cwd is in nested branch worktree", function()
 68			if not temp_dir then
 69				pending("temp_dir not available")
 70				return
 71			end
 72			local project = temp_dir .. "/nested-wt"
 73			os.execute("mkdir -p " .. project .. "/.bare")
 74			os.execute("git init --bare " .. project .. "/.bare")
 75			local f = io.open(project .. "/.git", "w")
 76			if f then
 77				f:write("gitdir: ./.bare\n")
 78				f:close()
 79			end
 80
 81			os.execute("mkdir -p " .. project .. "/feature/auth/src")
 82			local wt_git = io.open(project .. "/feature/auth/.git", "w")
 83			if wt_git then
 84				wt_git:write("gitdir: ../../.bare/worktrees/feature-auth\n")
 85				wt_git:close()
 86			end
 87
 88			local root, err = wt.find_project_root(project .. "/feature/auth/src")
 89			assert.is_nil(err)
 90			assert.are.equal(project, root)
 91		end)
 92	end)
 93
 94	describe("non-wt repository", function()
 95		it("returns error for normal git repo", function()
 96			if not temp_dir then
 97				pending("temp_dir not available")
 98				return
 99			end
100			local project = temp_dir .. "/normal-git"
101			os.execute("mkdir -p " .. project)
102			os.execute("git init " .. project)
103
104			local root, err = wt.find_project_root(project)
105			assert.is_nil(root)
106			assert.is_not_nil(err)
107			assert.is_truthy(err:match("not in a wt%-managed repository"))
108		end)
109
110		it("returns error for non-git directory", function()
111			if not temp_dir then
112				pending("temp_dir not available")
113				return
114			end
115			local project = temp_dir .. "/not-git"
116			os.execute("mkdir -p " .. project)
117
118			local root, err = wt.find_project_root(project)
119			assert.is_nil(root)
120			assert.is_not_nil(err)
121		end)
122	end)
123
124	describe("edge cases", function()
125		it("finds root with .bare but relative .git reference", function()
126			if not temp_dir then
127				pending("temp_dir not available")
128				return
129			end
130			local project = temp_dir .. "/rel-git"
131			os.execute("mkdir -p " .. project .. "/.bare")
132			os.execute("git init --bare " .. project .. "/.bare")
133
134			local f = io.open(project .. "/.git", "w")
135			if f then
136				f:write("gitdir: .bare\n")
137				f:close()
138			end
139
140			local root, err = wt.find_project_root(project)
141			assert.is_nil(err)
142			assert.are.equal(project, root)
143		end)
144	end)
145end)
146
147describe("detect_source_worktree", function()
148	local temp_dir
149
150	setup(function()
151		local handle = io.popen("mktemp -d")
152		if handle then
153			temp_dir = handle:read("*l")
154			handle:close()
155		end
156	end)
157
158	teardown(function()
159		if temp_dir then
160			os.execute("rm -rf " .. temp_dir)
161		end
162	end)
163
164	describe("at project root", function()
165		it("returns nil when cwd equals root", function()
166			if not temp_dir then
167				pending("temp_dir not available")
168				return
169			end
170			local project = temp_dir .. "/proj1"
171			os.execute("mkdir -p " .. project .. "/.bare")
172
173			local result = wt.detect_source_worktree(project, project)
174			assert.is_nil(result)
175		end)
176	end)
177
178	describe("inside a worktree", function()
179		it("returns worktree path when cwd has .git file", function()
180			if not temp_dir then
181				pending("temp_dir not available")
182				return
183			end
184			local project = temp_dir .. "/proj2"
185			local worktree = project .. "/main"
186			os.execute("mkdir -p " .. project .. "/.bare")
187			os.execute("mkdir -p " .. worktree .. "/src")
188
189			local f = io.open(worktree .. "/.git", "w")
190			if f then
191				f:write("gitdir: ../.bare/worktrees/main\n")
192				f:close()
193			end
194
195			local result = wt.detect_source_worktree(project, worktree)
196			assert.are.equal(worktree, result)
197		end)
198
199		it("finds worktree root when cwd is deep inside worktree", function()
200			if not temp_dir then
201				pending("temp_dir not available")
202				return
203			end
204			local project = temp_dir .. "/proj3"
205			local worktree = project .. "/main"
206			local deep_path = worktree .. "/src/lib/utils"
207			os.execute("mkdir -p " .. project .. "/.bare")
208			os.execute("mkdir -p " .. deep_path)
209
210			local f = io.open(worktree .. "/.git", "w")
211			if f then
212				f:write("gitdir: ../.bare/worktrees/main\n")
213				f:close()
214			end
215
216			local result = wt.detect_source_worktree(project, deep_path)
217			assert.are.equal(worktree, result)
218		end)
219
220		it("finds nested branch worktree", function()
221			if not temp_dir then
222				pending("temp_dir not available")
223				return
224			end
225			local project = temp_dir .. "/proj4"
226			local worktree = project .. "/feature/auth"
227			os.execute("mkdir -p " .. project .. "/.bare")
228			os.execute("mkdir -p " .. worktree .. "/src")
229
230			local f = io.open(worktree .. "/.git", "w")
231			if f then
232				f:write("gitdir: ../../.bare/worktrees/feature-auth\n")
233				f:close()
234			end
235
236			local result = wt.detect_source_worktree(project, worktree .. "/src")
237			assert.are.equal(worktree, result)
238		end)
239	end)
240
241	describe("not inside a worktree", function()
242		it("returns nil when in bare repo directory", function()
243			if not temp_dir then
244				pending("temp_dir not available")
245				return
246			end
247			local project = temp_dir .. "/proj5"
248			local bare = project .. "/.bare"
249			os.execute("mkdir -p " .. bare)
250
251			local result = wt.detect_source_worktree(project, bare)
252			assert.is_nil(result)
253		end)
254	end)
255end)