Skip to content

Commit 524ffbc

Browse files
nrjdalalclaude
andcommitted
fix: edge cases for local interactive mode
- Detect local paths with dots (e.g. hello.txt) when -i is set - Skip common heavy dirs: .next, dist, build, .cache, coverage, etc. - Gracefully skip unreadable directories and files - Error when target is inside source directory - Better isLocalPath detection (allow dots in names) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b4a4629 commit 524ffbc

1 file changed

Lines changed: 43 additions & 6 deletions

File tree

bin/index.ts

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,10 @@ const main = async () => {
172172
url.startsWith("../") ||
173173
url.startsWith("/") ||
174174
url.startsWith("~/") ||
175-
(options.interactive && !url.includes("/") && !url.includes(".") && !url.startsWith("http"))
175+
(options.interactive &&
176+
!url.includes("/") &&
177+
!url.startsWith("http") &&
178+
!url.startsWith("git@"))
176179

177180
if (isLocalPath && options.interactive) {
178181
// If url doesn't look like a real path, it's the target (e.g. `gitpick -i hello`)
@@ -199,11 +202,37 @@ const main = async () => {
199202

200203
const targetDir = target ? path.resolve(target) : null
201204

205+
const SKIP_DIRS = new Set([
206+
".git",
207+
"node_modules",
208+
".next",
209+
".nuxt",
210+
".output",
211+
".vercel",
212+
".turbo",
213+
"dist",
214+
"build",
215+
"out",
216+
".cache",
217+
".parcel-cache",
218+
"coverage",
219+
"target",
220+
"vendor",
221+
"__pycache__",
222+
".venv",
223+
"venv",
224+
])
225+
202226
const entries: TreeEntry[] = []
203227
async function walkLocal(dir: string, rel: string) {
204-
const items = await fs.promises.readdir(dir, { withFileTypes: true })
228+
let items
229+
try {
230+
items = await fs.promises.readdir(dir, { withFileTypes: true })
231+
} catch {
232+
return // skip unreadable directories
233+
}
205234
for (const item of items) {
206-
if (item.name === ".git" || item.name === "node_modules") continue
235+
if (SKIP_DIRS.has(item.name)) continue
207236
const itemRel = rel ? `${rel}/${item.name}` : item.name
208237
const itemPath = path.join(dir, item.name)
209238
if (item.isSymbolicLink()) {
@@ -221,8 +250,12 @@ const main = async () => {
221250
entries.push({ path: itemRel, type: "tree" })
222251
await walkLocal(itemPath, itemRel)
223252
} else {
224-
const stat = await fs.promises.stat(itemPath)
225-
entries.push({ path: itemRel, type: "blob", size: stat.size })
253+
try {
254+
const stat = await fs.promises.stat(itemPath)
255+
entries.push({ path: itemRel, type: "blob", size: stat.size })
256+
} catch {
257+
// skip unreadable files
258+
}
226259
}
227260
}
228261
}
@@ -263,9 +296,13 @@ const main = async () => {
263296
process.exit(0)
264297
}
265298

266-
if (path.resolve(resolvedSource) === path.resolve(targetDir)) {
299+
const resolvedTarget = path.resolve(targetDir)
300+
if (resolvedSource === resolvedTarget) {
267301
throw new Error("Source and target directories are the same")
268302
}
303+
if (resolvedTarget.startsWith(resolvedSource + path.sep)) {
304+
throw new Error("Target directory is inside the source directory")
305+
}
269306

270307
console.log(
271308
`\n${green("✔")} Picking ${selected.length} selected path${selected.length !== 1 ? "s" : ""}...`,

0 commit comments

Comments
 (0)