1package filemode 2 3import ( 4 "encoding/binary" 5 "fmt" 6 "os" 7 "strconv" 8) 9 10// A FileMode represents the kind of tree entries used by git. It 11// resembles regular file systems modes, although FileModes are 12// considerably simpler (there are not so many), and there are some, 13// like Submodule that has no file system equivalent. 14type FileMode uint32 15 16const ( 17 // Empty is used as the FileMode of tree elements when comparing 18 // trees in the following situations: 19 // 20 // - the mode of tree elements before their creation. - the mode of 21 // tree elements after their deletion. - the mode of unmerged 22 // elements when checking the index. 23 // 24 // Empty has no file system equivalent. As Empty is the zero value 25 // of FileMode, it is also returned by New and 26 // NewFromOsNewFromOSFileMode along with an error, when they fail. 27 Empty FileMode = 0 28 // Dir represent a Directory. 29 Dir FileMode = 0040000 30 // Regular represent non-executable files. Please note this is not 31 // the same as golang regular files, which include executable files. 32 Regular FileMode = 0100644 33 // Deprecated represent non-executable files with the group writable 34 // bit set. This mode was supported by the first versions of git, 35 // but it has been deprecated nowadays. This library uses them 36 // internally, so you can read old packfiles, but will treat them as 37 // Regulars when interfacing with the outside world. This is the 38 // standard git behaviour. 39 Deprecated FileMode = 0100664 40 // Executable represents executable files. 41 Executable FileMode = 0100755 42 // Symlink represents symbolic links to files. 43 Symlink FileMode = 0120000 44 // Submodule represents git submodules. This mode has no file system 45 // equivalent. 46 Submodule FileMode = 0160000 47) 48 49// New takes the octal string representation of a FileMode and returns 50// the FileMode and a nil error. If the string can not be parsed to a 51// 32 bit unsigned octal number, it returns Empty and the parsing error. 52// 53// Example: "40000" means Dir, "100644" means Regular. 54// 55// Please note this function does not check if the returned FileMode 56// is valid in git or if it is malformed. For instance, "1" will 57// return the malformed FileMode(1) and a nil error. 58func New(s string) (FileMode, error) { 59 n, err := strconv.ParseUint(s, 8, 32) 60 if err != nil { 61 return Empty, err 62 } 63 64 return FileMode(n), nil 65} 66 67// NewFromOSFileMode returns the FileMode used by git to represent 68// the provided file system modes and a nil error on success. If the 69// file system mode cannot be mapped to any valid git mode (as with 70// sockets or named pipes), it will return Empty and an error. 71// 72// Note that some git modes cannot be generated from os.FileModes, like 73// Deprecated and Submodule; while Empty will be returned, along with an 74// error, only when the method fails. 75func NewFromOSFileMode(m os.FileMode) (FileMode, error) { 76 if m.IsRegular() { 77 if isSetTemporary(m) { 78 return Empty, fmt.Errorf("no equivalent git mode for %s", m) 79 } 80 if isSetCharDevice(m) { 81 return Empty, fmt.Errorf("no equivalent git mode for %s", m) 82 } 83 if isSetUserExecutable(m) { 84 return Executable, nil 85 } 86 return Regular, nil 87 } 88 89 if m.IsDir() { 90 return Dir, nil 91 } 92 93 if isSetSymLink(m) { 94 return Symlink, nil 95 } 96 97 return Empty, fmt.Errorf("no equivalent git mode for %s", m) 98} 99 100func isSetCharDevice(m os.FileMode) bool { 101 return m&os.ModeCharDevice != 0 102} 103 104func isSetTemporary(m os.FileMode) bool { 105 return m&os.ModeTemporary != 0 106} 107 108func isSetUserExecutable(m os.FileMode) bool { 109 return m&0100 != 0 110} 111 112func isSetSymLink(m os.FileMode) bool { 113 return m&os.ModeSymlink != 0 114} 115 116// Bytes return a slice of 4 bytes with the mode in little endian 117// encoding. 118func (m FileMode) Bytes() []byte { 119 ret := make([]byte, 4) 120 binary.LittleEndian.PutUint32(ret, uint32(m)) 121 return ret 122} 123 124// IsMalformed returns if the FileMode should not appear in a git packfile, 125// this is: Empty and any other mode not mentioned as a constant in this 126// package. 127func (m FileMode) IsMalformed() bool { 128 return m != Dir && 129 m != Regular && 130 m != Deprecated && 131 m != Executable && 132 m != Symlink && 133 m != Submodule 134} 135 136// String returns the FileMode as a string in the standatd git format, 137// this is, an octal number padded with ceros to 7 digits. Malformed 138// modes are printed in that same format, for easier debugging. 139// 140// Example: Regular is "0100644", Empty is "0000000". 141func (m FileMode) String() string { 142 return fmt.Sprintf("%07o", uint32(m)) 143} 144 145// IsRegular returns if the FileMode represents that of a regular file, 146// this is, either Regular or Deprecated. Please note that Executable 147// are not regular even though in the UNIX tradition, they usually are: 148// See the IsFile method. 149func (m FileMode) IsRegular() bool { 150 return m == Regular || 151 m == Deprecated 152} 153 154// IsFile returns if the FileMode represents that of a file, this is, 155// Regular, Deprecated, Executable or Link. 156func (m FileMode) IsFile() bool { 157 return m == Regular || 158 m == Deprecated || 159 m == Executable || 160 m == Symlink 161} 162 163// ToOSFileMode returns the os.FileMode to be used when creating file 164// system elements with the given git mode and a nil error on success. 165// 166// When the provided mode cannot be mapped to a valid file system mode 167// (e.g. Submodule) it returns os.FileMode(0) and an error. 168// 169// The returned file mode does not take into account the umask. 170func (m FileMode) ToOSFileMode() (os.FileMode, error) { 171 switch m { 172 case Dir: 173 return os.ModePerm | os.ModeDir, nil 174 case Submodule: 175 return os.ModePerm | os.ModeDir, nil 176 case Regular: 177 return os.FileMode(0644), nil 178 // Deprecated is no longer allowed: treated as a Regular instead 179 case Deprecated: 180 return os.FileMode(0644), nil 181 case Executable: 182 return os.FileMode(0755), nil 183 case Symlink: 184 return os.ModePerm | os.ModeSymlink, nil 185 } 186 187 return os.FileMode(0), fmt.Errorf("malformed mode (%s)", m) 188} 189