Archived
Preview uncommitted changes in the detail pane
For a dirty submodule, list the changed files so the user can remember
what they were doing there:
Changes 5 files · 2M 1A 1D 1R · +37 −12
──────────────────────────────────
M render.go src/render/ +20 −4 3d ago
? scratch.txt 5m ago
- Status.Changes: files changed against HEAD (staged and unstaged
combined) with kind, line counts and modification time; sorted by kind,
then by size; capped at 200 with totals over all files.
- Git provider: parse `status --porcelain=v2 -z` and one
`diff HEAD --numstat -z -M`, only for dirty submodules.
- Detail pane: flat list with a colored status letter per file, like IDE
source-control views; counts and age in compact columns; up to 10 files,
every kind keeps a line, the rest is summarized as "n more".
- Fixture gains libs/work with every kind of change; mock data extended.
- Docs: F24, change preview in the UX guide, D17.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
64fe34b5b0
commit
19c6f17eb2
@@ -56,23 +56,119 @@ func parseGitmodules(out string) []gitmodule {
|
||||
return mods
|
||||
}
|
||||
|
||||
// parseWorktree summarizes `git status --porcelain=v2` output. A conflict
|
||||
// outranks modifications, which outrank untracked files.
|
||||
func parseWorktree(out string) submodule.Worktree {
|
||||
// entry is one changed path of `git status --porcelain=v2 -z`.
|
||||
type entry struct {
|
||||
path, orig string
|
||||
kind submodule.ChangeKind
|
||||
tracked bool // false for untracked files
|
||||
submodule bool // a nested submodule pointer changed
|
||||
}
|
||||
|
||||
// parseStatus parses `git status --porcelain=v2 -z`. Entries that don't
|
||||
// differ from HEAD (e.g. added to the index, then deleted again) are
|
||||
// dropped.
|
||||
func parseStatus(out string) []entry {
|
||||
var es []entry
|
||||
recs := strings.Split(out, "\x00")
|
||||
for i := 0; i < len(recs); i++ {
|
||||
rec := recs[i]
|
||||
if len(rec) < 3 {
|
||||
continue
|
||||
}
|
||||
switch rec[0] {
|
||||
case '?':
|
||||
es = append(es, entry{path: rec[2:], kind: submodule.ChangeUntracked})
|
||||
case 'u': // u XY sub m1 m2 m3 mW h1 h2 h3 path
|
||||
if f := strings.SplitN(rec, " ", 11); len(f) == 11 {
|
||||
es = append(es, entry{path: f[10], kind: submodule.ChangeConflicted, tracked: true})
|
||||
}
|
||||
case '1': // 1 XY sub mH mI mW hH hI path
|
||||
if f := strings.SplitN(rec, " ", 9); len(f) == 9 {
|
||||
if e, ok := trackedEntry(f[1], f[2], f[8], ""); ok {
|
||||
es = append(es, e)
|
||||
}
|
||||
}
|
||||
case '2': // 2 XY sub mH mI mW hH hI Xscore path NUL origPath
|
||||
if f := strings.SplitN(rec, " ", 10); len(f) == 10 && i+1 < len(recs) {
|
||||
i++
|
||||
if e, ok := trackedEntry(f[1], f[2], f[9], recs[i]); ok {
|
||||
es = append(es, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return es
|
||||
}
|
||||
|
||||
func trackedEntry(xy, sub, path, orig string) (entry, bool) {
|
||||
if len(xy) != 2 {
|
||||
return entry{}, false
|
||||
}
|
||||
x, y := xy[0], xy[1]
|
||||
e := entry{path: path, orig: orig, tracked: true, submodule: strings.HasPrefix(sub, "S")}
|
||||
switch {
|
||||
case x == 'R' || y == 'R':
|
||||
e.kind = submodule.ChangeRenamed
|
||||
case x == 'A' && y == 'D':
|
||||
return entry{}, false
|
||||
case x == 'A' || y == 'A' || x == 'C' || y == 'C':
|
||||
e.kind = submodule.ChangeAdded
|
||||
case x == 'D' || y == 'D':
|
||||
e.kind = submodule.ChangeRemoved
|
||||
default:
|
||||
e.kind = submodule.ChangeModified
|
||||
}
|
||||
return e, true
|
||||
}
|
||||
|
||||
// worktreeOf summarizes entries: a conflict outranks tracked changes, which
|
||||
// outrank untracked files.
|
||||
func worktreeOf(es []entry) submodule.Worktree {
|
||||
w := submodule.WorktreeClean
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
for _, e := range es {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "u "):
|
||||
case e.kind == submodule.ChangeConflicted:
|
||||
return submodule.WorktreeConflict
|
||||
case strings.HasPrefix(line, "1 "), strings.HasPrefix(line, "2 "):
|
||||
case e.tracked:
|
||||
w = submodule.WorktreeModified
|
||||
case strings.HasPrefix(line, "? ") && w == submodule.WorktreeClean:
|
||||
case w == submodule.WorktreeClean:
|
||||
w = submodule.WorktreeUntracked
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// numstat holds the line counts of one file.
|
||||
type numstat struct {
|
||||
added, removed int
|
||||
binary bool
|
||||
}
|
||||
|
||||
// parseNumstat parses `git diff --numstat -z -M`, keyed by the (new) path.
|
||||
func parseNumstat(out string) map[string]numstat {
|
||||
m := map[string]numstat{}
|
||||
recs := strings.Split(out, "\x00")
|
||||
for i := 0; i < len(recs); i++ {
|
||||
f := strings.SplitN(recs[i], "\t", 3)
|
||||
if len(f) != 3 {
|
||||
continue
|
||||
}
|
||||
path := f[2]
|
||||
if path == "" { // rename: the next two records are old and new path
|
||||
if i+2 >= len(recs) {
|
||||
break
|
||||
}
|
||||
path = recs[i+2]
|
||||
i += 2
|
||||
}
|
||||
n := numstat{binary: f[0] == "-"}
|
||||
n.added, _ = strconv.Atoi(f[0])
|
||||
n.removed, _ = strconv.Atoi(f[1])
|
||||
m[path] = n
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// parseLeftRight parses `git rev-list --left-right --count a...b`.
|
||||
func parseLeftRight(out string) (left, right int, err error) {
|
||||
f := strings.Fields(out)
|
||||
|
||||
Reference in New Issue
Block a user