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
|
// Copyright The gittuf Authors
// SPDX-License-Identifier: Apache-2.0
package gitinterface
import (
"errors"
"fmt"
"os"
"strings"
)
// See https://git-scm.com/docs/git-status#_porcelain_format_version_1.
var (
ErrInvalidStatusCodeLength = errors.New("status code string must be of length 1")
ErrInvalidStatusCode = errors.New("status code string is unrecognized")
)
type StatusCode uint
const (
StatusCodeUnmodified StatusCode = iota + 1 // we use 0 as error code
StatusCodeModified
StatusCodeTypeChanged
StatusCodeAdded
StatusCodeDeleted
StatusCodeRenamed
StatusCodeCopied
StatusCodeUpdatedUnmerged
StatusCodeUntracked
StatusCodeIgnored
)
func (s StatusCode) String() string {
switch s {
case StatusCodeUnmodified:
return " " // is this actually a space or empty string?
case StatusCodeModified:
return "M"
case StatusCodeTypeChanged:
return "T"
case StatusCodeAdded:
return "A"
case StatusCodeDeleted:
return "D"
case StatusCodeRenamed:
return "R"
case StatusCodeCopied:
return "C"
case StatusCodeUpdatedUnmerged:
return "U"
case StatusCodeUntracked:
return "?"
case StatusCodeIgnored:
return "!"
default:
return "invalid-code"
}
}
func NewStatusCodeFromByte(s byte) (StatusCode, error) {
switch s {
case ' ':
return StatusCodeUnmodified, nil
case 'M':
return StatusCodeModified, nil
case 'T':
return StatusCodeTypeChanged, nil
case 'A':
return StatusCodeAdded, nil
case 'D':
return StatusCodeDeleted, nil
case 'C':
return StatusCodeCopied, nil
case 'U':
return StatusCodeUpdatedUnmerged, nil
case '?':
return StatusCodeUntracked, nil
case '!':
return StatusCodeIgnored, nil
default:
return 0, ErrInvalidStatusCode
}
}
type FileStatus struct {
X StatusCode
Y StatusCode
}
func (f *FileStatus) Untracked() bool {
return f.X == StatusCodeUntracked || f.Y == StatusCodeUntracked
}
func (r *Repository) Status() (map[string]FileStatus, error) {
worktree := r.gitDirPath
if !r.IsBare() {
worktree = strings.TrimSuffix(worktree, ".git") // TODO: this doesn't support detached git dir
}
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
if err := os.Chdir(worktree); err != nil {
return nil, err
}
defer os.Chdir(cwd) //nolint:errcheck
output, err := r.executor("status", "--porcelain=1", "-z", "--untracked-files=all", "--ignored").executeString()
if err != nil {
return nil, fmt.Errorf("unable to check status of repository: %w", err)
}
statuses := map[string]FileStatus{}
lines := strings.Split(output, string('\000'))
for _, line := range lines {
if len(line) == 0 {
continue
}
// first two characters are status codes, find the corresponding
// statuses
xb := line[0]
yb := line[1]
// Note: we identify the status after inspecting the path so we can
// provide better error messages
// then, we have a single space followed by the path, ignore space and
// read in the rest as the filepath
filePath := strings.TrimSpace(line[2:])
xStatus, err := NewStatusCodeFromByte(xb)
if err != nil {
return nil, fmt.Errorf("unable to parse status code '%c' for path '%s': %w", xb, filePath, err)
}
yStatus, err := NewStatusCodeFromByte(yb)
if err != nil {
return nil, fmt.Errorf("unable to parse status code '%c' for path '%s': %w", yb, filePath, err)
}
status := FileStatus{X: xStatus, Y: yStatus}
statuses[filePath] = status
}
return statuses, nil
}
|