aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/filemode/filemode.go
blob: 0994bc4d755c001f6f063eacd69f677f7781d9f3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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 deprecatred 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 behaviuor.
	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, Excutable 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)
}