@@ -85,7 +85,9 @@ const makeFileChanges = async (
8585 | "with-executable-file"
8686 | "with-ignored-symlink"
8787 | "with-included-valid-symlink"
88- | "with-included-invalid-symlink" ,
88+ | "with-included-invalid-symlink"
89+ | "with-unchanged-symlink"
90+ | "with-changed-symlink" ,
8991) => {
9092 // Update an existing file
9193 await fs . promises . writeFile (
@@ -164,6 +166,37 @@ const makeFileChanges = async (
164166 path . join ( repoDirectory , "some-dir" , "nested" ) ,
165167 ) ;
166168 }
169+ if (
170+ changegroup === "with-unchanged-symlink" ||
171+ changegroup === "with-changed-symlink"
172+ ) {
173+ await fs . promises . mkdir ( path . join ( repoDirectory , "some-dir" ) , {
174+ recursive : true ,
175+ } ) ;
176+ await fs . promises . symlink (
177+ path . join ( repoDirectory , "README.md" ) ,
178+ path . join ( repoDirectory , "some-dir" , "nested" ) ,
179+ ) ;
180+ await git . add ( {
181+ fs,
182+ dir : repoDirectory ,
183+ filepath : "some-dir/nested" ,
184+ } ) ;
185+ await git . commit ( {
186+ fs,
187+ dir : repoDirectory ,
188+ message : "Add symlink" ,
189+ author : { name : "Test" , email : "test@test.com" } ,
190+ } ) ;
191+
192+ if ( changegroup === "with-changed-symlink" ) {
193+ await fs . promises . rm ( path . join ( repoDirectory , "some-dir" , "nested" ) ) ;
194+ await fs . promises . symlink (
195+ path . join ( repoDirectory , "LICENSE" ) ,
196+ path . join ( repoDirectory , "some-dir" , "nested" ) ,
197+ ) ;
198+ }
199+ }
167200} ;
168201
169202const makeFileChangeAssertions = async ( branch : string ) => {
@@ -324,6 +357,100 @@ describe("git", () => {
324357 } ) ;
325358 }
326359
360+ it ( `should allow unchanged symlinks without throwing` , async ( ) => {
361+ const branch = `${ TEST_BRANCH_PREFIX } -unchanged-symlink` ;
362+ branches . push ( branch ) ;
363+
364+ await fs . promises . mkdir ( testDir , { recursive : true } ) ;
365+ const repoDirectory = path . join ( testDir , `repo-unchanged-symlink` ) ;
366+
367+ await new Promise < void > ( ( resolve , reject ) => {
368+ const p = execFile (
369+ "git" ,
370+ [ "clone" , process . cwd ( ) , `repo-unchanged-symlink` ] ,
371+ { cwd : testDir } ,
372+ ( error ) => {
373+ if ( error ) {
374+ reject ( error ) ;
375+ } else {
376+ resolve ( ) ;
377+ }
378+ } ,
379+ ) ;
380+ p . stdout ?. pipe ( process . stdout ) ;
381+ p . stderr ?. pipe ( process . stderr ) ;
382+ } ) ;
383+
384+ await makeFileChanges ( repoDirectory , "with-unchanged-symlink" ) ;
385+
386+ // The symlink was committed locally and is unchanged in workdir.
387+ // The tree walk should skip it since oids match.
388+ // GitHub push may fail because local commit doesn't exist on GitHub,
389+ // but the key is that no symlink error is thrown.
390+ try {
391+ await commitChangesFromRepo ( {
392+ octokit,
393+ ...REPO ,
394+ branch,
395+ message : {
396+ headline : "Test commit" ,
397+ body : "This is a test commit" ,
398+ } ,
399+ cwd : repoDirectory ,
400+ log,
401+ } ) ;
402+
403+ await waitForGitHubToBeReady ( ) ;
404+ await makeFileChangeAssertions ( branch ) ;
405+ } catch ( error ) {
406+ expect ( ( error as Error ) . message ) . not . toContain ( "Unexpected symlink" ) ;
407+ expect ( ( error as Error ) . message ) . not . toContain ( "Unexpected executable" ) ;
408+ }
409+ } ) ;
410+
411+ it ( `should throw error when symlink is changed` , async ( ) => {
412+ const branch = `${ TEST_BRANCH_PREFIX } -changed-symlink` ;
413+ branches . push ( branch ) ;
414+
415+ await fs . promises . mkdir ( testDir , { recursive : true } ) ;
416+ const repoDirectory = path . join ( testDir , `repo-changed-symlink` ) ;
417+
418+ await new Promise < void > ( ( resolve , reject ) => {
419+ const p = execFile (
420+ "git" ,
421+ [ "clone" , process . cwd ( ) , `repo-changed-symlink` ] ,
422+ { cwd : testDir } ,
423+ ( error ) => {
424+ if ( error ) {
425+ reject ( error ) ;
426+ } else {
427+ resolve ( ) ;
428+ }
429+ } ,
430+ ) ;
431+ p . stdout ?. pipe ( process . stdout ) ;
432+ p . stderr ?. pipe ( process . stderr ) ;
433+ } ) ;
434+
435+ await makeFileChanges ( repoDirectory , "with-changed-symlink" ) ;
436+
437+ await expect ( ( ) =>
438+ commitChangesFromRepo ( {
439+ octokit,
440+ ...REPO ,
441+ branch,
442+ message : {
443+ headline : "Test commit" ,
444+ body : "This is a test commit" ,
445+ } ,
446+ cwd : repoDirectory ,
447+ log,
448+ } ) ,
449+ ) . rejects . toThrow (
450+ "Unexpected symlink at some-dir/nested, GitHub API only supports files and directories. You may need to add this file to .gitignore" ,
451+ ) ;
452+ } ) ;
453+
327454 describe ( `should throw appropriate error when symlink is present` , ( ) => {
328455 it ( `and file does not exist` , async ( ) => {
329456 const branch = `${ TEST_BRANCH_PREFIX } -invalid-symlink-error` ;
0 commit comments