package filemode import ( "encoding/binary" "fmt" "os" "strconv" ) // A FileMode represents the kind of tree entries used by git. It // resembles regular file systems modes, although FileModes are // considerably simpler (there are not so many), and there are some, // like Submodule that has no file system equivalent. type FileMode uint32 const ( // Empty is used as the FileMode of tree elements when comparing // trees in the following situations: // // - the mode of tree elements before their creation. - the mode of // tree elements after their deletion. - the mode of unmerged // elements when checking the index. // // Empty has no file system equivalent. As Empty is the zero value // of FileMode, it is also returned by New and // NewFromOsNewFromOSFileMode along with an error, when they fail. Empty FileMode = 0 // Dir represent a Directory. Dir FileMode = 0040000 // Regular represent non-executable files. Please note this is not // the same as golang regular files, which include executable files. Regular FileMode = 0100644 // Deprecated represent non-executable files with the group writable // bit set. This mode was supported by the first versions of git, // but it has been deprecated nowadays. This library uses them // internally, so you can read old packfiles, but will treat them as // Regulars when interfacing with the outside world. This is the // standard git behaviour. Deprecated FileMode = 0100664 // Executable represents executable files. Executable FileMode = 0100755 // Symlink represents symbolic links to files. Symlink FileMode = 0120000 // Submodule represents git submodules. This mode has no file system // equivalent. Submodule FileMode = 0160000 ) // New takes the octal string representation of a FileMode and returns // the FileMode and a nil error. If the string can not be parsed to a // 32 bit unsigned octal number, it returns Empty and the parsing error. // // Example: "40000" means Dir, "100644" means Regular. // // Please note this function does not check if the returned FileMode // is valid in git or if it is malformed. For instance, "1" will // return the malformed FileMode(1) and a nil error. func New(s string) (FileMode, error) { n, err := strconv.ParseUint(s, 8, 32) if err != nil { return Empty, err } return FileMode(n), nil } // NewFromOSFileMode returns the FileMode used by git to represent // the provided file system modes and a nil error on success. If the // file system mode cannot be mapped to any valid git mode (as with // sockets or named pipes), it will return Empty and an error. // // Note that some git modes cannot be generated from os.FileModes, like // Deprecated and Submodule; while Empty will be returned, along with an // error, only when the method fails. func NewFromOSFileMode(m os.FileMode) (FileMode, error) { if m.IsRegular() { if isSetTemporary(m) { return Empty, fmt.Errorf("no equivalent git mode for %s", m) } if isSetCharDevice(m) { return Empty, fmt.Errorf("no equivalent git mode for %s", m) } if isSetUserExecutable(m) { return Executable, nil } return Regular, nil } if m.IsDir() { return Dir, nil } if isSetSymLink(m) { return Symlink, nil } return Empty, fmt.Errorf("no equivalent git mode for %s", m) } func isSetCharDevice(m os.FileMode) bool { return m&os.ModeCharDevice != 0 } func isSetTemporary(m os.FileMode) bool { return m&os.ModeTemporary != 0 } func isSetUserExecutable(m os.FileMode) bool { return m&0100 != 0 } func isSetSymLink(m os.FileMode) bool { return m&os.ModeSymlink != 0 } // Bytes return a slice of 4 bytes with the mode in little endian // encoding. func (m FileMode) Bytes() []byte { ret := make([]byte, 4) binary.LittleEndian.PutUint32(ret, uint32(m)) return ret[:] } // IsMalformed returns if the FileMode should not appear in a git packfile, // this is: Empty and any other mode not mentioned as a constant in this // package. func (m FileMode) IsMalformed() bool { return m != Dir && m != Regular && m != Deprecated && m != Executable && m != Symlink && m != Submodule } // String returns the FileMode as a string in the standatd git format, // this is, an octal number padded with ceros to 7 digits. Malformed // modes are printed in that same format, for easier debugging. // // Example: Regular is "0100644", Empty is "0000000". func (m FileMode) String() string { return fmt.Sprintf("%07o", uint32(m)) } // IsRegular returns if the FileMode represents that of a regular file, // this is, either Regular or Deprecated. Please note that Executable // are not regular even though in the UNIX tradition, they usually are: // See the IsFile method. func (m FileMode) IsRegular() bool { return m == Regular || m == Deprecated } // IsFile returns if the FileMode represents that of a file, this is, // Regular, Deprecated, Executable or Link. func (m FileMode) IsFile() bool { return m == Regular || m == Deprecated || m == Executable || m == Symlink } // ToOSFileMode returns the os.FileMode to be used when creating file // system elements with the given git mode and a nil error on success. // // When the provided mode cannot be mapped to a valid file system mode // (e.g. Submodule) it returns os.FileMode(0) and an error. // // The returned file mode does not take into account the umask. func (m FileMode) ToOSFileMode() (os.FileMode, error) { switch m { case Dir: return os.ModePerm | os.ModeDir, nil case Submodule: return os.ModePerm | os.ModeDir, nil case Regular: return os.FileMode(0644), nil // Deprecated is no longer allowed: treated as a Regular instead case Deprecated: return os.FileMode(0644), nil case Executable: return os.FileMode(0755), nil case Symlink: return os.ModePerm | os.ModeSymlink, nil } return os.FileMode(0), fmt.Errorf("malformed mode (%s)", m) }