github.com/2lambda123/git-lfs@v2.5.2+incompatible/locking/lockable.go (about) 1 package locking 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 "sync" 9 10 "github.com/git-lfs/git-lfs/errors" 11 "github.com/git-lfs/git-lfs/filepathfilter" 12 "github.com/git-lfs/git-lfs/git" 13 "github.com/git-lfs/git-lfs/tools" 14 ) 15 16 // GetLockablePatterns returns a list of patterns in .gitattributes which are 17 // marked as lockable 18 func (c *Client) GetLockablePatterns() []string { 19 c.ensureLockablesLoaded() 20 return c.lockablePatterns 21 } 22 23 // getLockableFilter returns the internal filter used to check if a file is lockable 24 func (c *Client) getLockableFilter() *filepathfilter.Filter { 25 c.ensureLockablesLoaded() 26 return c.lockableFilter 27 } 28 29 func (c *Client) ensureLockablesLoaded() { 30 c.lockableMutex.Lock() 31 defer c.lockableMutex.Unlock() 32 33 // Only load once 34 if c.lockablePatterns == nil { 35 c.refreshLockablePatterns() 36 } 37 } 38 39 // Internal function to repopulate lockable patterns 40 // You must have locked the c.lockableMutex in the caller 41 func (c *Client) refreshLockablePatterns() { 42 paths := git.GetAttributePaths(c.LocalWorkingDir, c.LocalGitDir) 43 // Always make non-nil even if empty 44 c.lockablePatterns = make([]string, 0, len(paths)) 45 for _, p := range paths { 46 if p.Lockable { 47 c.lockablePatterns = append(c.lockablePatterns, p.Path) 48 } 49 } 50 c.lockableFilter = filepathfilter.New(c.lockablePatterns, nil) 51 } 52 53 // IsFileLockable returns whether a specific file path is marked as Lockable, 54 // ie has the 'lockable' attribute in .gitattributes 55 // Lockable patterns are cached once for performance, unless you call RefreshLockablePatterns 56 // path should be relative to repository root 57 func (c *Client) IsFileLockable(path string) bool { 58 return c.getLockableFilter().Allows(path) 59 } 60 61 // FixAllLockableFileWriteFlags recursively scans the repo looking for files which 62 // are lockable, and makes sure their write flags are set correctly based on 63 // whether they are currently locked or unlocked. 64 // Files which are unlocked are made read-only, files which are locked are made 65 // writeable. 66 // This function can be used after a clone or checkout to ensure that file 67 // state correctly reflects the locking state 68 func (c *Client) FixAllLockableFileWriteFlags() error { 69 return c.fixFileWriteFlags(c.LocalWorkingDir, c.LocalWorkingDir, c.getLockableFilter(), nil) 70 } 71 72 // FixFileWriteFlagsInDir scans dir (which can either be a relative dir 73 // from the root of the repo, or an absolute dir within the repo) looking for 74 // files to change permissions for. 75 // If lockablePatterns is non-nil, then any file matching those patterns will be 76 // checked to see if it is currently locked by the current committer, and if so 77 // it will be writeable, and if not locked it will be read-only. 78 // If unlockablePatterns is non-nil, then any file matching those patterns will 79 // be made writeable if it is not already. This can be used to reset files to 80 // writeable when their 'lockable' attribute is turned off. 81 func (c *Client) FixFileWriteFlagsInDir(dir string, lockablePatterns, unlockablePatterns []string) error { 82 83 // early-out if no patterns 84 if len(lockablePatterns) == 0 && len(unlockablePatterns) == 0 { 85 return nil 86 } 87 88 absPath := dir 89 if !filepath.IsAbs(dir) { 90 absPath = filepath.Join(c.LocalWorkingDir, dir) 91 } 92 stat, err := os.Stat(absPath) 93 if err != nil { 94 return err 95 } 96 if !stat.IsDir() { 97 return fmt.Errorf("%q is not a valid directory", dir) 98 } 99 100 var lockableFilter *filepathfilter.Filter 101 var unlockableFilter *filepathfilter.Filter 102 if lockablePatterns != nil { 103 lockableFilter = filepathfilter.New(lockablePatterns, nil) 104 } 105 if unlockablePatterns != nil { 106 unlockableFilter = filepathfilter.New(unlockablePatterns, nil) 107 } 108 109 return c.fixFileWriteFlags(absPath, c.LocalWorkingDir, lockableFilter, unlockableFilter) 110 } 111 112 // Internal implementation of fixing file write flags with precompiled filters 113 func (c *Client) fixFileWriteFlags(absPath, workingDir string, lockable, unlockable *filepathfilter.Filter) error { 114 115 var errs []error 116 var errMux sync.Mutex 117 118 addErr := func(err error) { 119 errMux.Lock() 120 defer errMux.Unlock() 121 122 errs = append(errs, err) 123 } 124 125 tools.FastWalkGitRepo(absPath, func(parentDir string, fi os.FileInfo, err error) { 126 if err != nil { 127 addErr(err) 128 return 129 } 130 // Skip dirs, we only need to check files 131 if fi.IsDir() { 132 return 133 } 134 abschild := filepath.Join(parentDir, fi.Name()) 135 136 // This is a file, get relative to repo root 137 relpath, err := filepath.Rel(workingDir, abschild) 138 if err != nil { 139 addErr(err) 140 return 141 } 142 143 err = c.fixSingleFileWriteFlags(relpath, lockable, unlockable) 144 if err != nil { 145 addErr(err) 146 } 147 148 }) 149 return errors.Combine(errs) 150 } 151 152 // FixLockableFileWriteFlags checks each file in the provided list, and for 153 // those which are lockable, makes sure their write flags are set correctly 154 // based on whether they are currently locked or unlocked. Files which are 155 // unlocked are made read-only, files which are locked are made writeable. 156 // Files which are not lockable are ignored. 157 // This function can be used after a clone or checkout to ensure that file 158 // state correctly reflects the locking state, and is more efficient than 159 // FixAllLockableFileWriteFlags when you know which files changed 160 func (c *Client) FixLockableFileWriteFlags(files []string) error { 161 // early-out if no lockable patterns 162 if len(c.GetLockablePatterns()) == 0 { 163 return nil 164 } 165 166 var errs []error 167 for _, f := range files { 168 err := c.fixSingleFileWriteFlags(f, c.getLockableFilter(), nil) 169 if err != nil { 170 errs = append(errs, err) 171 } 172 } 173 174 return errors.Combine(errs) 175 } 176 177 // fixSingleFileWriteFlags fixes write flags on a single file 178 // If lockablePatterns is non-nil, then any file matching those patterns will be 179 // checked to see if it is currently locked by the current committer, and if so 180 // it will be writeable, and if not locked it will be read-only. 181 // If unlockablePatterns is non-nil, then any file matching those patterns will 182 // be made writeable if it is not already. This can be used to reset files to 183 // writeable when their 'lockable' attribute is turned off. 184 func (c *Client) fixSingleFileWriteFlags(file string, lockable, unlockable *filepathfilter.Filter) error { 185 // Convert to git-style forward slash separators if necessary 186 // Necessary to match attributes 187 if filepath.Separator == '\\' { 188 file = strings.Replace(file, "\\", "/", -1) 189 } 190 if lockable != nil && lockable.Allows(file) { 191 // Lockable files are writeable only if they're currently locked 192 err := tools.SetFileWriteFlag(file, c.IsFileLockedByCurrentCommitter(file)) 193 // Ignore not exist errors 194 if err != nil && !os.IsNotExist(err) { 195 return err 196 } 197 } else if unlockable != nil && unlockable.Allows(file) { 198 // Unlockable files are always writeable 199 // We only check files which match the incoming patterns to avoid 200 // checking every file in the system all the time, and only do it 201 // when a file has had its lockable attribute removed 202 err := tools.SetFileWriteFlag(file, true) 203 if err != nil && !os.IsNotExist(err) { 204 return err 205 } 206 } 207 return nil 208 }