7
7
"io"
8
8
"os"
9
9
"path/filepath"
10
+ "runtime"
10
11
"strings"
11
12
12
13
"github.com/go-git/go-billy/v5"
@@ -394,6 +395,9 @@ func (w *Worktree) resetWorktree(t *object.Tree) error {
394
395
b := newIndexBuilder (idx )
395
396
396
397
for _ , ch := range changes {
398
+ if err := w .validChange (ch ); err != nil {
399
+ return err
400
+ }
397
401
if err := w .checkoutChange (ch , t , b ); err != nil {
398
402
return err
399
403
}
@@ -403,6 +407,104 @@ func (w *Worktree) resetWorktree(t *object.Tree) error {
403
407
return w .r .Storer .SetIndex (idx )
404
408
}
405
409
410
+ // worktreeDeny is a list of paths that are not allowed
411
+ // to be used when resetting the worktree.
412
+ var worktreeDeny = map [string ]struct {}{
413
+ // .git
414
+ GitDirName : {},
415
+
416
+ // For other historical reasons, file names that do not conform to the 8.3
417
+ // format (up to eight characters for the basename, three for the file
418
+ // extension, certain characters not allowed such as `+`, etc) are associated
419
+ // with a so-called "short name", at least on the `C:` drive by default.
420
+ // Which means that `git~1/` is a valid way to refer to `.git/`.
421
+ "git~1" : {},
422
+ }
423
+
424
+ // validPath checks whether paths are valid.
425
+ // The rules around invalid paths could differ from upstream based on how
426
+ // filesystems are managed within go-git, but they are largely the same.
427
+ //
428
+ // For upstream rules:
429
+ // https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/read-cache.c#L946
430
+ // https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/path.c#L1383
431
+ func validPath (paths ... string ) error {
432
+ for _ , p := range paths {
433
+ parts := strings .FieldsFunc (p , func (r rune ) bool { return (r == '\\' || r == '/' ) })
434
+ if _ , denied := worktreeDeny [strings .ToLower (parts [0 ])]; denied {
435
+ return fmt .Errorf ("invalid path prefix: %q" , p )
436
+ }
437
+
438
+ if runtime .GOOS == "windows" {
439
+ // Volume names are not supported, in both formats: \\ and <DRIVE_LETTER>:.
440
+ if vol := filepath .VolumeName (p ); vol != "" {
441
+ return fmt .Errorf ("invalid path: %q" , p )
442
+ }
443
+
444
+ if ! windowsValidPath (parts [0 ]) {
445
+ return fmt .Errorf ("invalid path: %q" , p )
446
+ }
447
+ }
448
+
449
+ for _ , part := range parts {
450
+ if part == ".." {
451
+ return fmt .Errorf ("invalid path %q: cannot use '..'" , p )
452
+ }
453
+ }
454
+ }
455
+ return nil
456
+ }
457
+
458
+ // windowsPathReplacer defines the chars that need to be replaced
459
+ // as part of windowsValidPath.
460
+ var windowsPathReplacer * strings.Replacer
461
+
462
+ func init () {
463
+ windowsPathReplacer = strings .NewReplacer (" " , "" , "." , "" )
464
+ }
465
+
466
+ func windowsValidPath (part string ) bool {
467
+ if len (part ) > 3 && strings .EqualFold (part [:4 ], GitDirName ) {
468
+ // For historical reasons, file names that end in spaces or periods are
469
+ // automatically trimmed. Therefore, `.git . . ./` is a valid way to refer
470
+ // to `.git/`.
471
+ if windowsPathReplacer .Replace (part [4 :]) == "" {
472
+ return false
473
+ }
474
+
475
+ // For yet other historical reasons, NTFS supports so-called "Alternate Data
476
+ // Streams", i.e. metadata associated with a given file, referred to via
477
+ // `<filename>:<stream-name>:<stream-type>`. There exists a default stream
478
+ // type for directories, allowing `.git/` to be accessed via
479
+ // `.git::$INDEX_ALLOCATION/`.
480
+ //
481
+ // For performance reasons, _all_ Alternate Data Streams of `.git/` are
482
+ // forbidden, not just `::$INDEX_ALLOCATION`.
483
+ if len (part ) > 4 && part [4 :5 ] == ":" {
484
+ return false
485
+ }
486
+ }
487
+ return true
488
+ }
489
+
490
+ func (w * Worktree ) validChange (ch merkletrie.Change ) error {
491
+ action , err := ch .Action ()
492
+ if err != nil {
493
+ return nil
494
+ }
495
+
496
+ switch action {
497
+ case merkletrie .Delete :
498
+ return validPath (ch .From .String ())
499
+ case merkletrie .Insert :
500
+ return validPath (ch .To .String ())
501
+ case merkletrie .Modify :
502
+ return validPath (ch .From .String (), ch .To .String ())
503
+ }
504
+
505
+ return nil
506
+ }
507
+
406
508
func (w * Worktree ) checkoutChange (ch merkletrie.Change , t * object.Tree , idx * indexBuilder ) error {
407
509
a , err := ch .Action ()
408
510
if err != nil {
@@ -575,6 +677,11 @@ func (w *Worktree) checkoutFile(f *object.File) (err error) {
575
677
}
576
678
577
679
func (w * Worktree ) checkoutFileSymlink (f * object.File ) (err error ) {
680
+ // https://github.com/git/git/commit/10ecfa76491e4923988337b2e2243b05376b40de
681
+ if strings .EqualFold (f .Name , gitmodulesFile ) {
682
+ return ErrGitModulesSymlink
683
+ }
684
+
578
685
from , err := f .Reader ()
579
686
if err != nil {
580
687
return
0 commit comments