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