@@ -92,8 +92,13 @@ describe('getFileSourceForRepo', () => {
9292 mockSimpleGit . mockReturnValue ( { cwd : mockCwd } ) ;
9393 mockFindFirst . mockResolvedValue ( MOCK_REPO ) ;
9494
95- // Default: file show succeeds; .gitattributes not present
95+ // Default: ref resolves to a concrete sha, file show succeeds, and
96+ // .gitattributes is absent. The SUT resolves the ref first (rev-parse),
97+ // then reads content + .gitattributes at the resolved sha.
9698 mockGitRaw . mockImplementation ( async ( args : string [ ] ) => {
99+ if ( args [ 0 ] === 'rev-parse' ) {
100+ return 'resolvedsha\n' ;
101+ }
97102 if ( args [ 1 ] ?. endsWith ( '.gitattributes' ) ) {
98103 throw new Error ( 'does not exist in HEAD' ) ;
99104 }
@@ -170,7 +175,7 @@ describe('getFileSourceForRepo', () => {
170175
171176 describe ( 'git error handling' , ( ) => {
172177 it ( 'returns FILE_NOT_FOUND when git reports the file does not exist' , async ( ) => {
173- mockGitRaw . mockRejectedValueOnce (
178+ mockGitRaw . mockRejectedValue (
174179 new Error ( "fatal: path 'src/missing.ts' does not exist in 'main'" ) ,
175180 ) ;
176181
@@ -183,7 +188,7 @@ describe('getFileSourceForRepo', () => {
183188 } ) ;
184189
185190 it ( 'returns FILE_NOT_FOUND for "fatal: path" errors' , async ( ) => {
186- mockGitRaw . mockRejectedValueOnce ( new Error ( 'fatal: path not found' ) ) ;
191+ mockGitRaw . mockRejectedValue ( new Error ( 'fatal: path not found' ) ) ;
187192
188193 const result = await getFileSourceForRepo (
189194 { path : 'src/index.ts' , repo : 'github.com/owner/repo' } ,
@@ -196,9 +201,9 @@ describe('getFileSourceForRepo', () => {
196201 it ( 'returns INVALID_GIT_REF with an unresolved-ref message when head_sha has not been fetched ("unknown revision")' , async ( ) => {
197202 // This is the scenario from the v4.16.14 regression: the review agent passes
198203 // pr_payload.head_sha as ref, but the bare clone hasn't fetched it yet.
199- mockGitRaw . mockRejectedValueOnce (
204+ mockGitRaw . mockRejectedValue (
200205 new Error ( "fatal: ambiguous argument 'deadbeef': unknown revision or path not in the working tree" ) ,
201- ) ;
206+ ) ; // rejects rev-parse (swallowed) and the show, which drives the result
202207
203208 const result = await getFileSourceForRepo (
204209 { path : 'src/index.ts' , repo : 'github.com/owner/repo' , ref : 'deadbeef' } ,
@@ -212,7 +217,7 @@ describe('getFileSourceForRepo', () => {
212217 } ) ;
213218
214219 it ( 'returns INVALID_GIT_REF with an unresolved-ref message for "bad revision" errors' , async ( ) => {
215- mockGitRaw . mockRejectedValueOnce ( new Error ( 'fatal: bad revision' ) ) ;
220+ mockGitRaw . mockRejectedValue ( new Error ( 'fatal: bad revision' ) ) ;
216221
217222 const result = await getFileSourceForRepo (
218223 { path : 'src/index.ts' , repo : 'github.com/owner/repo' , ref : 'nonexistent' } ,
@@ -226,7 +231,7 @@ describe('getFileSourceForRepo', () => {
226231 } ) ;
227232
228233 it ( 'returns INVALID_GIT_REF with an unresolved-ref message for "invalid object name" errors' , async ( ) => {
229- mockGitRaw . mockRejectedValueOnce ( new Error ( 'fatal: invalid object name HEAD' ) ) ;
234+ mockGitRaw . mockRejectedValue ( new Error ( 'fatal: invalid object name HEAD' ) ) ;
230235
231236 const result = await getFileSourceForRepo (
232237 { path : 'src/index.ts' , repo : 'github.com/owner/repo' } ,
@@ -243,7 +248,7 @@ describe('getFileSourceForRepo', () => {
243248 // Before the fix, getFileSourceForRepo re-threw unknown errors.
244249 // Outside sew(), this caused a fatal Next.js task-runner exception.
245250 // After the fix, all errors are returned as ServiceError.
246- mockGitRaw . mockRejectedValueOnce ( new Error ( 'I/O error: device busy' ) ) ;
251+ mockGitRaw . mockRejectedValue ( new Error ( 'I/O error: device busy' ) ) ;
247252
248253 const result = await getFileSourceForRepo (
249254 { path : 'src/index.ts' , repo : 'github.com/owner/repo' } ,
@@ -254,7 +259,7 @@ describe('getFileSourceForRepo', () => {
254259 } ) ;
255260
256261 it ( 'never rejects its returned promise for unrecognised git errors' , async ( ) => {
257- mockGitRaw . mockRejectedValueOnce ( new Error ( 'transient I/O error' ) ) ;
262+ mockGitRaw . mockRejectedValue ( new Error ( 'transient I/O error' ) ) ;
258263
259264 await expect (
260265 getFileSourceForRepo (
@@ -280,13 +285,16 @@ describe('getFileSourceForRepo', () => {
280285 } ) ;
281286 } ) ;
282287
283- it ( 'uses the provided ref for the git show command ' , async ( ) => {
288+ it ( 'resolves the provided ref to a commit, then reads content at it ' , async ( ) => {
284289 await getFileSourceForRepo (
285290 { path : 'src/index.ts' , repo : 'github.com/owner/repo' , ref : 'abc123sha' } ,
286291 { org : MOCK_ORG , prisma : mockPrisma } ,
287292 ) ;
288293
289- expect ( mockGitRaw ) . toHaveBeenCalledWith ( [ 'show' , 'abc123sha:src/index.ts' ] ) ;
294+ // The provided ref is resolved up front...
295+ expect ( mockGitRaw ) . toHaveBeenCalledWith ( [ 'rev-parse' , 'abc123sha^{commit}' ] ) ;
296+ // ...and content is read at the resolved sha, not the symbolic ref.
297+ expect ( mockGitRaw ) . toHaveBeenCalledWith ( [ 'show' , 'resolvedsha:src/index.ts' ] ) ;
290298 } ) ;
291299
292300 it ( 'falls back to defaultBranch when ref is omitted' , async ( ) => {
@@ -295,7 +303,7 @@ describe('getFileSourceForRepo', () => {
295303 { org : MOCK_ORG , prisma : mockPrisma } ,
296304 ) ;
297305
298- expect ( mockGitRaw ) . toHaveBeenCalledWith ( [ 'show ' , 'main:src/index.ts ' ] ) ;
306+ expect ( mockGitRaw ) . toHaveBeenCalledWith ( [ 'rev-parse ' , 'main^{commit} ' ] ) ;
299307 } ) ;
300308
301309 it ( 'falls back to HEAD when both ref and defaultBranch are absent' , async ( ) => {
@@ -306,7 +314,28 @@ describe('getFileSourceForRepo', () => {
306314 { org : MOCK_ORG , prisma : mockPrisma } ,
307315 ) ;
308316
309- expect ( mockGitRaw ) . toHaveBeenCalledWith ( [ 'show' , 'HEAD:src/index.ts' ] ) ;
317+ expect ( mockGitRaw ) . toHaveBeenCalledWith ( [ 'rev-parse' , 'HEAD^{commit}' ] ) ;
318+ } ) ;
319+
320+ it ( 'reads content at the symbolic ref when rev-parse fails' , async ( ) => {
321+ mockGitRaw . mockImplementation ( async ( args : string [ ] ) => {
322+ if ( args [ 0 ] === 'rev-parse' ) {
323+ throw new Error ( 'unknown revision' ) ;
324+ }
325+ if ( args [ 1 ] ?. endsWith ( '.gitattributes' ) ) {
326+ throw new Error ( 'does not exist' ) ;
327+ }
328+ return 'console.log("hello");' ;
329+ } ) ;
330+
331+ const result = await getFileSourceForRepo (
332+ { path : 'src/index.ts' , repo : 'github.com/owner/repo' , ref : 'main' } ,
333+ { org : MOCK_ORG , prisma : mockPrisma } ,
334+ ) ;
335+
336+ expect ( mockGitRaw ) . toHaveBeenCalledWith ( [ 'show' , 'main:src/index.ts' ] ) ;
337+ expect ( result ) . toMatchObject ( { source : 'console.log("hello");' } ) ;
338+ expect ( ( result as { commitSha ?: string } ) . commitSha ) . toBeUndefined ( ) ;
310339 } ) ;
311340
312341 it ( 'uses the repo path from getRepoPath for the git working directory' , async ( ) => {
0 commit comments