55package git
66
77import (
8- "io "
8+ "path "
99 "sort"
1010 "strings"
1111
@@ -24,77 +24,57 @@ func (te *TreeEntry) Type() string {
2424 }
2525}
2626
27- // FollowLink returns the entry pointed to by a symlink
28- func (te * TreeEntry ) FollowLink () (* TreeEntry , error ) {
27+ type EntryFollowResult struct {
28+ SymlinkContent string
29+ TargetFullPath string
30+ TargetEntry * TreeEntry
31+ }
32+
33+ func EntryFollowLink (commit * Commit , fullPath string , te * TreeEntry ) (* EntryFollowResult , error ) {
2934 if ! te .IsLink () {
30- return nil , ErrSymlinkUnresolved { te . Name () , "not a symlink" }
35+ return nil , util . ErrorWrap ( util . ErrUnprocessableContent , "%q is not a symlink" , fullPath )
3136 }
3237
33- // read the link
34- r , err := te . Blob (). DataAsync ()
35- if err != nil {
36- return nil , err
38+ // git's filename max length is 4096, hopefully a link won't be longer than multiple of that
39+ const maxSymlinkSize = 20 * 4096
40+ if te . Blob (). Size () > maxSymlinkSize {
41+ return nil , util . ErrorWrap ( util . ErrUnprocessableContent , "%q content exceeds symlink limit" , fullPath )
3742 }
38- closed := false
39- defer func () {
40- if ! closed {
41- _ = r .Close ()
42- }
43- }()
44- buf := make ([]byte , te .Size ())
45- _ , err = io .ReadFull (r , buf )
43+
44+ link , err := te .Blob ().GetBlobContent (maxSymlinkSize )
4645 if err != nil {
4746 return nil , err
4847 }
49- _ = r .Close ()
50- closed = true
51-
52- lnk := string (buf )
53- t := te .ptree
54-
55- // traverse up directories
56- for ; t != nil && strings .HasPrefix (lnk , "../" ); lnk = lnk [3 :] {
57- t = t .ptree
48+ if strings .HasPrefix (link , "/" ) {
49+ // It's said that absolute path will be stored as is in Git
50+ return & EntryFollowResult {SymlinkContent : link }, util .ErrorWrap (util .ErrUnprocessableContent , "%q is an absolute symlink" , fullPath )
5851 }
5952
60- if t == nil {
61- return nil , ErrSymlinkUnresolved {te .Name (), "points outside of repo" }
62- }
63-
64- target , err := t .GetTreeEntryByPath (lnk )
53+ targetFullPath := path .Join (path .Dir (fullPath ), link )
54+ targetEntry , err := commit .GetTreeEntryByPath (targetFullPath )
6555 if err != nil {
66- if IsErrNotExist (err ) {
67- return nil , ErrSymlinkUnresolved {te .Name (), "broken link" }
68- }
69- return nil , err
56+ return & EntryFollowResult {SymlinkContent : link }, err
7057 }
71- return target , nil
58+ return & EntryFollowResult { SymlinkContent : link , TargetFullPath : targetFullPath , TargetEntry : targetEntry } , nil
7259}
7360
74- // FollowLinks returns the entry ultimately pointed to by a symlink
75- func (te * TreeEntry ) FollowLinks (optLimit ... int ) (* TreeEntry , error ) {
76- if ! te .IsLink () {
77- return nil , ErrSymlinkUnresolved {te .Name (), "not a symlink" }
78- }
61+ func EntryFollowLinks (commit * Commit , firstFullPath string , firstTreeEntry * TreeEntry , optLimit ... int ) (res * EntryFollowResult , err error ) {
7962 limit := util .OptionalArg (optLimit , 10 )
80- entry := te
63+ treeEntry , fullPath := firstTreeEntry , firstFullPath
8164 for range limit {
82- if ! entry .IsLink () {
83- break
84- }
85- next , err := entry .FollowLink ()
65+ res , err = EntryFollowLink (commit , fullPath , treeEntry )
8666 if err != nil {
87- return nil , err
67+ return res , err
8868 }
89- if next .ID == entry .ID {
90- return nil , ErrSymlinkUnresolved {entry .Name (), "recursive link" }
69+ treeEntry , fullPath = res .TargetEntry , res .TargetFullPath
70+ if ! treeEntry .IsLink () {
71+ break
9172 }
92- entry = next
9373 }
94- if entry .IsLink () {
95- return nil , ErrSymlinkUnresolved { te . Name () , "too many levels of symbolic links" }
74+ if treeEntry .IsLink () {
75+ return res , util . ErrorWrap ( util . ErrUnprocessableContent , "%q has too many links" , firstFullPath )
9676 }
97- return entry , nil
77+ return res , nil
9878}
9979
10080// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
0 commit comments