@@ -29,22 +29,38 @@ async function setupGit(cwd: string) {
2929describe ( "getFileChanges" , ( ) => {
3030 it ( "should get changes since a specific ref" , async ( ) => {
3131 await using fixture = await createFixture ( {
32- "foo.txt" : "Hello, world!" ,
32+ ".gitignore" : ".env\nignored" ,
33+ "a.txt" : "Hello, world!" ,
34+ "b.txt" : "Hello, world!" ,
3335 } ) ;
3436 await setupGit ( fixture . path ) ;
3537
36- await fixture . rm ( "foo.txt" ) ;
37- await fixture . writeFile ( "bar.txt" , "This is a new file!" ) ;
38+ await fixture . writeFile ( "a.txt" , "This is an updated file!" ) ;
39+ await fixture . rm ( "b.txt" ) ;
40+ await fixture . writeFile ( "c.txt" , "This is a new file!" ) ;
41+ await fixture . mkdir ( "nested" ) ;
42+ await fixture . writeFile ( "nested/file.txt" , "This is a nested file" ) ;
43+ await fixture . mkdir ( "ignored" ) ;
44+ await fixture . writeFile ( "ignored/file.txt" , "This file should be ignored" ) ;
45+ await fixture . writeFile ( ".env" , "This file should be ignored" ) ;
3846
3947 const result = await getFileChanges ( fixture . path , fixture . path , "HEAD" ) ;
4048 expect ( result ) . toEqual ( {
4149 additions : [
4250 {
43- path : "bar.txt" ,
44- contents : await fixture . readFile ( "bar.txt" , "base64" ) ,
51+ path : "a.txt" ,
52+ contents : await fixture . readFile ( "a.txt" , "base64" ) ,
53+ } ,
54+ {
55+ path : "c.txt" ,
56+ contents : await fixture . readFile ( "c.txt" , "base64" ) ,
57+ } ,
58+ {
59+ path : "nested/file.txt" ,
60+ contents : await fixture . readFile ( "nested/file.txt" , "base64" ) ,
4561 } ,
4662 ] ,
47- deletions : [ { path : "foo .txt" } ] ,
63+ deletions : [ { path : "b .txt" } ] ,
4864 } ) ;
4965 } ) ;
5066
@@ -105,4 +121,111 @@ describe("getFileChanges", () => {
105121 deletions : [ { path : "nested/foo.txt" } ] ,
106122 } ) ;
107123 } ) ;
124+
125+ it ( "should allow existing symlinks" , async ( ) => {
126+ await using fixture = await createFixture ( {
127+ "foo.txt" : "Hello, world!" ,
128+ "bar.txt" : "Hello, world!" ,
129+ } ) ;
130+ await setupGit ( fixture . path ) ;
131+
132+ await fixture . mkdir ( "some-dir" ) ;
133+ await fs . symlink (
134+ fixture . getPath ( "foo.txt" ) ,
135+ fixture . getPath ( "some-dir/nested" ) ,
136+ ) ;
137+ await exec ( "git" , [ "add" , "." ] , { nodeOptions : { cwd : fixture . path } } ) ;
138+ await exec ( "git" , [ "commit" , "-m" , "Add symlink" ] , {
139+ nodeOptions : { cwd : fixture . path } ,
140+ } ) ;
141+
142+ // Since we committed, HEAD points to the last commit and there's no change since then
143+ const result = await getFileChanges ( fixture . path , fixture . path , "HEAD" ) ;
144+ expect ( result ) . toEqual ( { additions : [ ] , deletions : [ ] } ) ;
145+
146+ await fixture . rm ( "some-dir/nested" ) ;
147+ await fs . symlink (
148+ fixture . getPath ( "bar.txt" ) ,
149+ fixture . getPath ( "some-dir/nested" ) ,
150+ ) ;
151+
152+ // We made symlink changes since the last commit, so this should error now
153+ await expect (
154+ getFileChanges ( fixture . path , fixture . path , "HEAD" ) ,
155+ ) . rejects . toThrow (
156+ "Unexpected symlink at some-dir/nested, GitHub API only supports files and directories. You may need to add this file to .gitignore" ,
157+ ) ;
158+ } ) ;
159+
160+ it ( "should not error when symlink is present but ignored" , async ( ) => {
161+ await using fixture = await createFixture ( {
162+ "foo.txt" : "Hello, world!" ,
163+ } ) ;
164+ await setupGit ( fixture . path ) ;
165+
166+ await fixture . writeFile ( ".gitignore" , "some-dir" ) ;
167+ await exec ( "git" , [ "add" , "." ] , { nodeOptions : { cwd : fixture . path } } ) ;
168+ await exec ( "git" , [ "commit" , "-m" , "Add gitignore" ] , {
169+ nodeOptions : { cwd : fixture . path } ,
170+ } ) ;
171+
172+ await fixture . mkdir ( "some-dir" ) ;
173+ await fs . symlink (
174+ fixture . getPath ( "foo.txt" ) ,
175+ fixture . getPath ( "some-dir/nested" ) ,
176+ ) ;
177+
178+ const result = await getFileChanges ( fixture . path , fixture . path , "HEAD" ) ;
179+ expect ( result ) . toEqual ( { additions : [ ] , deletions : [ ] } ) ;
180+ } ) ;
181+
182+ it ( "should throw error when symlink is present with non-existent path" , async ( ) => {
183+ await using fixture = await createFixture ( ) ;
184+ await setupGit ( fixture . path ) ;
185+
186+ await fixture . mkdir ( "some-dir" ) ;
187+ await fs . symlink (
188+ fixture . getPath ( "non-existent" ) ,
189+ fixture . getPath ( "some-dir/nested" ) ,
190+ ) ;
191+
192+ await expect (
193+ getFileChanges ( fixture . path , fixture . path , "HEAD" ) ,
194+ ) . rejects . toThrow (
195+ "Unexpected symlink at some-dir/nested, GitHub API only supports files and directories. You may need to add this file to .gitignore" ,
196+ ) ;
197+ } ) ;
198+
199+ it ( "should throw error when symlink is present with existing path" , async ( ) => {
200+ await using fixture = await createFixture ( {
201+ "foo.txt" : "Hello, world!" ,
202+ } ) ;
203+ await setupGit ( fixture . path ) ;
204+
205+ await fixture . mkdir ( "some-dir" ) ;
206+ await fs . symlink (
207+ fixture . getPath ( "foo.txt" ) ,
208+ fixture . getPath ( "some-dir/nested" ) ,
209+ ) ;
210+
211+ await expect (
212+ getFileChanges ( fixture . path , fixture . path , "HEAD" ) ,
213+ ) . rejects . toThrow (
214+ "Unexpected symlink at some-dir/nested, GitHub API only supports files and directories. You may need to add this file to .gitignore" ,
215+ ) ;
216+ } ) ;
217+
218+ it ( "should throw error when executable file is present" , async ( ) => {
219+ await using fixture = await createFixture ( ) ;
220+ await setupGit ( fixture . path ) ;
221+
222+ await fixture . writeFile ( "executable-file.sh" , "#!/bin/bash\necho hello" ) ;
223+ await fs . chmod ( fixture . getPath ( "executable-file.sh" ) , 0o755 ) ;
224+
225+ await expect (
226+ getFileChanges ( fixture . path , fixture . path , "HEAD" ) ,
227+ ) . rejects . toThrow (
228+ "Unexpected executable file at executable-file.sh, GitHub API only supports non-executable files and directories. You may need to add this file to .gitignore" ,
229+ ) ;
230+ } ) ;
108231} ) ;
0 commit comments