File: status.go

package info (click to toggle)
gittuf 0.12.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,692 kB
  • sloc: python: 85; makefile: 58; sh: 1
file content (150 lines) | stat: -rw-r--r-- 3,433 bytes parent folder | download | duplicates (2)
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
}