diff options
| author | 2026-06-07 11:01:26 +0000 | |
|---|---|---|
| committer | 2026-06-07 11:04:43 +0000 | |
| commit | c2d883c7c58a3bd64d799dd000fe7254e450a4e5 (patch) | |
| tree | a4456d8fc3718f235c2e934d42c056b3bcd3e1f9 | |
| parent | object/tag: Add (diff) | |
| signature | No signature | |
object/tree/mode: Initialize
| -rw-r--r-- | object/tree/mode/append.go | 8 | ||||
| -rw-r--r-- | object/tree/mode/details.go | 48 | ||||
| -rw-r--r-- | object/tree/mode/doc.go | 6 | ||||
| -rw-r--r-- | object/tree/mode/mode.go | 24 | ||||
| -rw-r--r-- | object/tree/mode/ops.go | 38 | ||||
| -rw-r--r-- | object/tree/mode/parse.go | 43 |
6 files changed, 167 insertions, 0 deletions
diff --git a/object/tree/mode/append.go b/object/tree/mode/append.go new file mode 100644 index 00000000..e8f118ee --- /dev/null +++ b/object/tree/mode/append.go @@ -0,0 +1,8 @@ +package mode + +import "strconv" + +// Append appends the canonical octal encoding of the mode to dst. +func (mode Mode) Append(dst []byte) []byte { + return strconv.AppendUint(dst, uint64(mode), 8) +} diff --git a/object/tree/mode/details.go b/object/tree/mode/details.go new file mode 100644 index 00000000..90ead2ed --- /dev/null +++ b/object/tree/mode/details.go @@ -0,0 +1,48 @@ +package mode + +import "lindenii.org/go/furgit/object/typ" + +type modeDetails struct { + valid bool + isBlobLike bool + isRegularFile bool + objectType typ.Type +} + +func (mode Mode) details() modeDetails { + return modeTable[mode] +} + +//nolint:gochecknoglobals +var modeTable = map[Mode]modeDetails{ + Directory: { + valid: true, + isBlobLike: false, + isRegularFile: false, + objectType: typ.TypeTree, + }, + Regular: { + valid: true, + isBlobLike: true, + isRegularFile: true, + objectType: typ.TypeBlob, + }, + Executable: { + valid: true, + isBlobLike: true, + isRegularFile: true, + objectType: typ.TypeBlob, + }, + Symlink: { + valid: true, + isBlobLike: true, + isRegularFile: false, + objectType: typ.TypeBlob, + }, + Gitlink: { + valid: true, + isBlobLike: false, + isRegularFile: false, + objectType: typ.TypeCommit, + }, +} diff --git a/object/tree/mode/doc.go b/object/tree/mode/doc.go new file mode 100644 index 00000000..8e3f5984 --- /dev/null +++ b/object/tree/mode/doc.go @@ -0,0 +1,6 @@ +// Package mode provides canonical Git tree entry modes. +// +// A [Mode] is the octal file mode stored in a tree entry, +// such as a regular file, executable, symlink, directory, or gitlink. +// It rejects malformed, unsupported, and zero-padded encodings. +package mode diff --git a/object/tree/mode/mode.go b/object/tree/mode/mode.go new file mode 100644 index 00000000..70b6fd38 --- /dev/null +++ b/object/tree/mode/mode.go @@ -0,0 +1,24 @@ +package mode + +// Mode represents the mode of an entry in a Git tree. +type Mode uint32 + +const ( + // Directory is the mode of a subtree entry. + Directory Mode = 0o40000 + + // Regular is the mode of a non-executable regular file. + Regular Mode = 0o100644 + + // Executable is the mode of an executable regular file. + Executable Mode = 0o100755 + + // Symlink is the mode of a symbolic link. + Symlink Mode = 0o120000 + + // Gitlink is the mode of a gitlink (submodule commit) entry. + Gitlink Mode = 0o160000 +) + +// maxModeDigits is the largest number of octal digits in any canonical mode. +const maxModeDigits = 6 diff --git a/object/tree/mode/ops.go b/object/tree/mode/ops.go new file mode 100644 index 00000000..149d95db --- /dev/null +++ b/object/tree/mode/ops.go @@ -0,0 +1,38 @@ +package mode + +import "lindenii.org/go/furgit/object/typ" + +// IsValid reports whether mode is a canonical mode that Git writes. +func (mode Mode) IsValid() bool { + return mode.details().valid +} + +// IsBlobLike reports whether mode names one blob-like tree entry kind. +// +// Blob-like entries store blob object IDs as their targets. +func (mode Mode) IsBlobLike() bool { + return mode.details().isBlobLike +} + +// IsRegularFile reports whether mode names one regular-file variant. +func (mode Mode) IsRegularFile() bool { + return mode.details().isRegularFile +} + +// HasSameType reports whether mode and other describe the same tree entry kind. +// +// Regular files and executable files have the same type for diff-status purposes. +func (mode Mode) HasSameType(other Mode) bool { + if mode == other { + return true + } + + return mode.details().isRegularFile && other.details().isRegularFile +} + +// ObjectType returns the type of object that an entry with this mode targets. +// +// It returns [typ.TypeUnknown] for invalid modes. +func (mode Mode) ObjectType() typ.Type { + return mode.details().objectType +} diff --git a/object/tree/mode/parse.go b/object/tree/mode/parse.go new file mode 100644 index 00000000..7839a89a --- /dev/null +++ b/object/tree/mode/parse.go @@ -0,0 +1,43 @@ +package mode + +import ( + "errors" + "fmt" +) + +// ErrInvalidMode indicates a malformed or unsupported tree entry mode. +var ErrInvalidMode = errors.New("object/tree/mode: invalid mode") + +// Parse decodes a canonical octal tree entry mode. +// +// It accepts only the modes Git itself writes +// and rejects malformed, unsupported, and zero-padded encodings. +func Parse(raw []byte) (Mode, error) { + if len(raw) == 0 { + return 0, fmt.Errorf("%w: empty mode", ErrInvalidMode) + } + + if raw[0] == '0' { + return 0, fmt.Errorf("%w: zero-padded mode %q", ErrInvalidMode, raw) + } + + if len(raw) > maxModeDigits { + return 0, fmt.Errorf("%w: mode %q too long", ErrInvalidMode, raw) + } + + var mode Mode + + for _, c := range raw { + if c < '0' || c > '7' { + return 0, fmt.Errorf("%w: non-octal byte in mode %q", ErrInvalidMode, raw) + } + + mode = mode<<3 | Mode(c-'0') + } + + if !mode.IsValid() { + return 0, fmt.Errorf("%w: unsupported mode %q", ErrInvalidMode, raw) + } + + return mode, nil +} |
