mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-20 11:44:39 +00:00
fix(projects): stop the git origin walk at the folder's own repo
The walk-up skipped a .git without an origin and kept climbing, so a local-only repo nested in another repo, or any folder under a home dotfiles repo, took the ancestor's origin and merged into that project row. Now it stops at the first .git (its origin or none) and never uses home as the repo of a folder below it.
This commit is contained in:
parent
83a18e1308
commit
db71aa8564
2 changed files with 84 additions and 3 deletions
|
|
@ -76,12 +76,15 @@ export function readGitOriginUrl(path: string): string | null {
|
|||
/// (`<repo>/app`, `<repo>/src/lib`), so reading only `<path>/.git` found an
|
||||
/// origin for a handful of the identities on a real machine and left every
|
||||
/// other checkout of the same repository as its own picker row. Walk up to the
|
||||
/// first ancestor that has one.
|
||||
/// first ancestor with a .git and take its origin, or none: a local-only repo
|
||||
/// nested in another one is its own project. Home is never the repo of a folder
|
||||
/// below it, so a dotfiles repo there does not swallow every other folder.
|
||||
function readGitOriginUrlUncached(path: string): string | null {
|
||||
const home = normalizeProxyPath(homedir())
|
||||
let dir = path
|
||||
for (let depth = 0; depth < 64; depth++) {
|
||||
const url = originAt(dir)
|
||||
if (url) return url
|
||||
if (dir !== path && normalizeProxyPath(dir) === home) return null
|
||||
if (entryKind(join(dir, '.git'))) return originAt(dir)
|
||||
const parent = dirname(dir)
|
||||
if (parent === dir) return null
|
||||
dir = parent
|
||||
|
|
|
|||
78
tests/git-origin-repo-boundary.test.ts
Normal file
78
tests/git-origin-repo-boundary.test.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const fake = vi.hoisted(() => ({ home: '' }))
|
||||
vi.mock('node:os', async () => {
|
||||
const actual = await vi.importActual<typeof import('node:os')>('node:os')
|
||||
return { ...actual, homedir: () => fake.home }
|
||||
})
|
||||
|
||||
import { __resetGitOriginCache, gitOriginKey, saveGitOrigins } from '../src/git-origin.js'
|
||||
|
||||
let root: string
|
||||
const savedCacheDir = process.env['CODEBURN_CACHE_DIR']
|
||||
|
||||
function repo(dir: string, origin?: string): string {
|
||||
mkdirSync(join(dir, '.git'), { recursive: true })
|
||||
writeFileSync(join(dir, '.git', 'config'), `[core]\n\tbare = false\n${origin ? `[remote "origin"]\n\turl = ${origin}\n` : ''}`)
|
||||
return dir
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
root = mkdtempSync(join(tmpdir(), 'git-origin-boundary-'))
|
||||
fake.home = join(root, 'home')
|
||||
mkdirSync(fake.home)
|
||||
process.env['CODEBURN_CACHE_DIR'] = join(root, 'cache')
|
||||
mkdirSync(join(root, 'cache'))
|
||||
__resetGitOriginCache()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
__resetGitOriginCache()
|
||||
if (savedCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR']
|
||||
else process.env['CODEBURN_CACHE_DIR'] = savedCacheDir
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe('git origin stops at the repository that owns a folder', () => {
|
||||
it('gives a local-only repo nested in another repo no origin, and never records the outer one', () => {
|
||||
const outer = repo(join(root, 'work', 'monorepo'), 'git@github.com:me/monorepo.git')
|
||||
const inner = repo(join(outer, 'experiments', 'foo'))
|
||||
mkdirSync(join(inner, 'src'))
|
||||
expect(gitOriginKey(inner)).toBeNull()
|
||||
expect(gitOriginKey(join(inner, 'src'))).toBeNull()
|
||||
expect(gitOriginKey(join(outer, 'experiments'))).toBe('github.com/me/monorepo')
|
||||
saveGitOrigins()
|
||||
const paths = JSON.parse(readFileSync(join(root, 'cache', 'git-origins.json'), 'utf8')).paths
|
||||
expect(paths[inner]).toBeUndefined()
|
||||
expect(paths[join(inner, 'src')]).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not hand a dotfiles repo at home to folders below it', () => {
|
||||
repo(fake.home, 'https://github.com/me/dotfiles')
|
||||
const scratch = join(fake.home, 'Desktop', 'scratch')
|
||||
mkdirSync(scratch, { recursive: true })
|
||||
const prototype = repo(join(fake.home, 'Projects', 'prototype'))
|
||||
expect(gitOriginKey(scratch)).toBeNull()
|
||||
expect(gitOriginKey(prototype)).toBeNull()
|
||||
expect(gitOriginKey(fake.home)).toBe('github.com/me/dotfiles')
|
||||
})
|
||||
|
||||
it('still finds the checkout origin from a subfolder and from a worktree', () => {
|
||||
const main = repo(join(fake.home, 'src', 'app'), 'git@github.com:me/app.git')
|
||||
mkdirSync(join(main, 'packages', 'web'), { recursive: true })
|
||||
expect(gitOriginKey(join(main, 'packages', 'web'))).toBe('github.com/me/app')
|
||||
|
||||
const admin = join(main, '.git', 'worktrees', 'wt')
|
||||
mkdirSync(admin, { recursive: true })
|
||||
writeFileSync(join(admin, 'commondir'), '../..\n')
|
||||
const wt = join(fake.home, 'src', 'app-wt')
|
||||
mkdirSync(join(wt, 'lib'), { recursive: true })
|
||||
writeFileSync(join(wt, '.git'), `gitdir: ${admin}\n`)
|
||||
expect(gitOriginKey(wt)).toBe('github.com/me/app')
|
||||
expect(gitOriginKey(join(wt, 'lib'))).toBe('github.com/me/app')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue