aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/filemode
diff options
context:
space:
mode:
Diffstat (limited to 'plumbing/filemode')
-rw-r--r--plumbing/filemode/filemode.go188
-rw-r--r--plumbing/filemode/filemode_test.go348
2 files changed, 536 insertions, 0 deletions
diff --git a/plumbing/filemode/filemode.go b/plumbing/filemode/filemode.go
new file mode 100644
index 0000000..0994bc4
--- /dev/null
+++ b/plumbing/filemode/filemode.go
@@ -0,0 +1,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)
+}
diff --git a/plumbing/filemode/filemode_test.go b/plumbing/filemode/filemode_test.go
new file mode 100644
index 0000000..299c96a
--- /dev/null
+++ b/plumbing/filemode/filemode_test.go
@@ -0,0 +1,348 @@
+package filemode
+
+import (
+ "os"
+ "testing"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type ModeSuite struct{}
+
+var _ = Suite(&ModeSuite{})
+
+func (s *ModeSuite) TestNew(c *C) {
+ for _, test := range [...]struct {
+ input string
+ expected FileMode
+ }{
+ // these are the ones used in the packfile codification
+ // of the tree entries
+ {input: "40000", expected: Dir},
+ {input: "100644", expected: Regular},
+ {input: "100664", expected: Deprecated},
+ {input: "100755", expected: Executable},
+ {input: "120000", expected: Symlink},
+ {input: "160000", expected: Submodule},
+ // these are are not used by standard git to codify modes in
+ // packfiles, but they often appear when parsing some git
+ // outputs ("git diff-tree", for instance).
+ {input: "000000", expected: Empty},
+ {input: "040000", expected: Dir},
+ // these are valid inputs, but probably means there is a bug
+ // somewhere.
+ {input: "0", expected: Empty},
+ {input: "42", expected: FileMode(042)},
+ {input: "00000000000100644", expected: Regular},
+ } {
+ comment := Commentf("input = %q", test.input)
+ obtained, err := New(test.input)
+ c.Assert(obtained, Equals, test.expected, comment)
+ c.Assert(err, IsNil, comment)
+ }
+}
+
+func (s *ModeSuite) TestNewErrors(c *C) {
+ for _, input := range [...]string{
+ "0x81a4", // Regular in hex
+ "-rw-r--r--", // Regular in default UNIX representation
+ "",
+ "-42",
+ "9", // this is no octal
+ "09", // looks like octal, but it is not
+ "mode",
+ "-100644",
+ "+100644",
+ } {
+ comment := Commentf("input = %q", input)
+ obtained, err := New(input)
+ c.Assert(obtained, Equals, Empty, comment)
+ c.Assert(err, Not(IsNil), comment)
+ }
+}
+
+// fixtures for testing NewModeFromOSFileMode
+type fixture struct {
+ input os.FileMode
+ expected FileMode
+ err string // error regexp, empty string for nil error
+}
+
+func (f fixture) test(c *C) {
+ obtained, err := NewFromOSFileMode(f.input)
+ comment := Commentf("input = %s (%07o)", f.input, uint32(f.input))
+ c.Assert(obtained, Equals, f.expected, comment)
+ if f.err != "" {
+ c.Assert(err, ErrorMatches, f.err, comment)
+ } else {
+ c.Assert(err, IsNil, comment)
+ }
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeSimplePerms(c *C) {
+ for _, f := range [...]fixture{
+ {os.FileMode(0755) | os.ModeDir, Dir, ""}, // drwxr-xr-x
+ {os.FileMode(0700) | os.ModeDir, Dir, ""}, // drwx------
+ {os.FileMode(0500) | os.ModeDir, Dir, ""}, // dr-x------
+ {os.FileMode(0644), Regular, ""}, // -rw-r--r--
+ {os.FileMode(0660), Regular, ""}, // -rw-rw----
+ {os.FileMode(0640), Regular, ""}, // -rw-r-----
+ {os.FileMode(0600), Regular, ""}, // -rw-------
+ {os.FileMode(0400), Regular, ""}, // -r--------
+ {os.FileMode(0000), Regular, ""}, // ----------
+ {os.FileMode(0755), Executable, ""}, // -rwxr-xr-x
+ {os.FileMode(0700), Executable, ""}, // -rwx------
+ {os.FileMode(0500), Executable, ""}, // -r-x------
+ {os.FileMode(0744), Executable, ""}, // -rwxr--r--
+ {os.FileMode(0540), Executable, ""}, // -r-xr-----
+ {os.FileMode(0550), Executable, ""}, // -r-xr-x---
+ {os.FileMode(0777) | os.ModeSymlink, Symlink, ""}, // Lrwxrwxrwx
+ } {
+ f.test(c)
+ }
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeAppend(c *C) {
+ // append files are just regular files
+ fixture{
+ input: os.FileMode(0644) | os.ModeAppend, // arw-r--r--
+ expected: Regular, err: "",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeExclusive(c *C) {
+ // exclusive files are just regular or executable files
+ fixture{
+ input: os.FileMode(0644) | os.ModeExclusive, // lrw-r--r--
+ expected: Regular, err: "",
+ }.test(c)
+
+ fixture{
+ input: os.FileMode(0755) | os.ModeExclusive, // lrwxr-xr-x
+ expected: Executable, err: "",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeTemporary(c *C) {
+ // temporaty files are ignored
+ fixture{
+ input: os.FileMode(0644) | os.ModeTemporary, // Trw-r--r--
+ expected: Empty, err: "no equivalent.*",
+ }.test(c)
+
+ fixture{
+ input: os.FileMode(0755) | os.ModeTemporary, // Trwxr-xr-x
+ expected: Empty, err: "no equivalent.*",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeDevice(c *C) {
+ // device files has no git equivalent
+ fixture{
+ input: os.FileMode(0644) | os.ModeDevice, // Drw-r--r--
+ expected: Empty, err: "no equivalent.*",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileNamedPipe(c *C) {
+ // named pipes files has not git equivalent
+ fixture{
+ input: os.FileMode(0644) | os.ModeNamedPipe, // prw-r--r--
+ expected: Empty, err: "no equivalent.*",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeSocket(c *C) {
+ // sockets has no git equivalent
+ fixture{
+ input: os.FileMode(0644) | os.ModeSocket, // Srw-r--r--
+ expected: Empty, err: "no equivalent.*",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeSetuid(c *C) {
+ // Setuid are just executables
+ fixture{
+ input: os.FileMode(0755) | os.ModeSetuid, // urwxr-xr-x
+ expected: Executable, err: "",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeSetgid(c *C) {
+ // Setguid are regular or executables, depending on the owner perms
+ fixture{
+ input: os.FileMode(0644) | os.ModeSetgid, // grw-r--r--
+ expected: Regular, err: "",
+ }.test(c)
+
+ fixture{
+ input: os.FileMode(0755) | os.ModeSetgid, // grwxr-xr-x
+ expected: Executable, err: "",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeCharDevice(c *C) {
+ // char devices has no git equivalent
+ fixture{
+ input: os.FileMode(0644) | os.ModeCharDevice, // crw-r--r--
+ expected: Empty, err: "no equivalent.*",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestNewFromOsFileModeSticky(c *C) {
+ // dirs with the sticky bit are just dirs
+ fixture{
+ input: os.FileMode(0755) | os.ModeDir | os.ModeSticky, // dtrwxr-xr-x
+ expected: Dir, err: "",
+ }.test(c)
+}
+
+func (s *ModeSuite) TestByte(c *C) {
+ for _, test := range [...]struct {
+ input FileMode
+ expected []byte
+ }{
+ {FileMode(0), []byte{0x00, 0x00, 0x00, 0x00}},
+ {FileMode(1), []byte{0x01, 0x00, 0x00, 0x00}},
+ {FileMode(15), []byte{0x0f, 0x00, 0x00, 0x00}},
+ {FileMode(16), []byte{0x10, 0x00, 0x00, 0x00}},
+ {FileMode(255), []byte{0xff, 0x00, 0x00, 0x00}},
+ {FileMode(256), []byte{0x00, 0x01, 0x00, 0x00}},
+ {Empty, []byte{0x00, 0x00, 0x00, 0x00}},
+ {Dir, []byte{0x00, 0x40, 0x00, 0x00}},
+ {Regular, []byte{0xa4, 0x81, 0x00, 0x00}},
+ {Deprecated, []byte{0xb4, 0x81, 0x00, 0x00}},
+ {Executable, []byte{0xed, 0x81, 0x00, 0x00}},
+ {Symlink, []byte{0x00, 0xa0, 0x00, 0x00}},
+ {Submodule, []byte{0x00, 0xe0, 0x00, 0x00}},
+ } {
+ c.Assert(test.input.Bytes(), DeepEquals, test.expected,
+ Commentf("input = %s", test.input))
+ }
+}
+
+func (s *ModeSuite) TestIsMalformed(c *C) {
+ for _, test := range [...]struct {
+ mode FileMode
+ expected bool
+ }{
+ {Empty, true},
+ {Dir, false},
+ {Regular, false},
+ {Deprecated, false},
+ {Executable, false},
+ {Symlink, false},
+ {Submodule, false},
+ {FileMode(01), true},
+ {FileMode(010), true},
+ {FileMode(0100), true},
+ {FileMode(01000), true},
+ {FileMode(010000), true},
+ {FileMode(0100000), true},
+ } {
+ c.Assert(test.mode.IsMalformed(), Equals, test.expected)
+ }
+}
+
+func (s *ModeSuite) TestString(c *C) {
+ for _, test := range [...]struct {
+ mode FileMode
+ expected string
+ }{
+ {Empty, "0000000"},
+ {Dir, "0040000"},
+ {Regular, "0100644"},
+ {Deprecated, "0100664"},
+ {Executable, "0100755"},
+ {Symlink, "0120000"},
+ {Submodule, "0160000"},
+ {FileMode(01), "0000001"},
+ {FileMode(010), "0000010"},
+ {FileMode(0100), "0000100"},
+ {FileMode(01000), "0001000"},
+ {FileMode(010000), "0010000"},
+ {FileMode(0100000), "0100000"},
+ } {
+ c.Assert(test.mode.String(), Equals, test.expected)
+ }
+}
+
+func (s *ModeSuite) TestIsRegular(c *C) {
+ for _, test := range [...]struct {
+ mode FileMode
+ expected bool
+ }{
+ {Empty, false},
+ {Dir, false},
+ {Regular, true},
+ {Deprecated, true},
+ {Executable, false},
+ {Symlink, false},
+ {Submodule, false},
+ {FileMode(01), false},
+ {FileMode(010), false},
+ {FileMode(0100), false},
+ {FileMode(01000), false},
+ {FileMode(010000), false},
+ {FileMode(0100000), false},
+ } {
+ c.Assert(test.mode.IsRegular(), Equals, test.expected)
+ }
+}
+
+func (s *ModeSuite) TestIsFile(c *C) {
+ for _, test := range [...]struct {
+ mode FileMode
+ expected bool
+ }{
+ {Empty, false},
+ {Dir, false},
+ {Regular, true},
+ {Deprecated, true},
+ {Executable, true},
+ {Symlink, true},
+ {Submodule, false},
+ {FileMode(01), false},
+ {FileMode(010), false},
+ {FileMode(0100), false},
+ {FileMode(01000), false},
+ {FileMode(010000), false},
+ {FileMode(0100000), false},
+ } {
+ c.Assert(test.mode.IsFile(), Equals, test.expected)
+ }
+}
+
+func (s *ModeSuite) TestToOSFileMode(c *C) {
+ for _, test := range [...]struct {
+ input FileMode
+ expected os.FileMode
+ errRegExp string // empty string for nil error
+ }{
+ {Empty, os.FileMode(0), "malformed.*"},
+ {Dir, os.ModePerm | os.ModeDir, ""},
+ {Regular, os.FileMode(0644), ""},
+ {Deprecated, os.FileMode(0644), ""},
+ {Executable, os.FileMode(0755), ""},
+ {Symlink, os.ModePerm | os.ModeSymlink, ""},
+ {Submodule, os.ModePerm | os.ModeDir, ""},
+ {FileMode(01), os.FileMode(0), "malformed.*"},
+ {FileMode(010), os.FileMode(0), "malformed.*"},
+ {FileMode(0100), os.FileMode(0), "malformed.*"},
+ {FileMode(01000), os.FileMode(0), "malformed.*"},
+ {FileMode(010000), os.FileMode(0), "malformed.*"},
+ {FileMode(0100000), os.FileMode(0), "malformed.*"},
+ } {
+ obtained, err := test.input.ToOSFileMode()
+ comment := Commentf("input = %s", test.input)
+ if test.errRegExp != "" {
+ c.Assert(obtained, Equals, os.FileMode(0), comment)
+ c.Assert(err, ErrorMatches, test.errRegExp, comment)
+ } else {
+ c.Assert(obtained, Equals, test.expected, comment)
+ c.Assert(err, IsNil, comment)
+ }
+ }
+}