Archived
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9838c0c99 | ||
|
|
651ab10f51 | ||
|
|
e0f4ac823b | ||
|
|
83824e42c1 |
@@ -0,0 +1,135 @@
|
|||||||
|
# submarine
|
||||||
|
|
||||||
|
**A terminal UI for git repositories with many submodules.**
|
||||||
|
See what needs your attention. Ignore the rest.
|
||||||
|
|
||||||
|
`git submodule status` gives you a wall of commit hashes. submarine answers
|
||||||
|
the questions you actually have:
|
||||||
|
|
||||||
|
- Which branch is this submodule on, and is the pin current?
|
||||||
|
- Which submodules need me, and which are fine?
|
||||||
|
- Is anything dangerous, like a pin nobody else can fetch?
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Why you might like it
|
||||||
|
|
||||||
|
- **Only submodules, as a tree.** Nested submodules at any depth, grouped by
|
||||||
|
directory, collapsed until you open them.
|
||||||
|
- **Branch plus distance, not hashes.** `origin/main ↓2` says more than
|
||||||
|
`b7c8d9e`. A pin on a feature branch shows that branch.
|
||||||
|
- **Problems are loud, healthy is quiet.** Submodules without issues are
|
||||||
|
muted `in sync`; a staged pin shows as blue `staged` (or a `●` after an
|
||||||
|
issue word). A collapsed parent counts the problems hidden below it
|
||||||
|
(`·1`) and shows `●` if a staged pin is hidden.
|
||||||
|
- **Words, not symbols.** Every state is a short word (`unpushed`, `conflict`,
|
||||||
|
`missing`, `off pin`, `changed`), so nothing depends on color alone.
|
||||||
|
- **Live.** Fetches in the background on startup and follows changes you make
|
||||||
|
in other shells within about two seconds.
|
||||||
|
- **Built for dozens to hundreds of submodules.** Each status loads
|
||||||
|
asynchronously, so the UI never freezes.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -fsSL https://git.simon-franken.de/simonfranken/submarine/raw/branch/main/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Installs `sub` and `submarine` (linux/macOS, amd64/arm64) to `~/.local/bin`.
|
||||||
|
Both names start the same program; the examples below use the shorter `sub`.
|
||||||
|
Update later with `sub update`.
|
||||||
|
|
||||||
|
## Try it
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sub # the repository in the current directory
|
||||||
|
sub ~/code/project # any directory inside a repository
|
||||||
|
sub --no-fetch # skip the background fetch
|
||||||
|
sub --mock # built-in sample data, no repository needed
|
||||||
|
```
|
||||||
|
|
||||||
|
`submarine` works everywhere `sub` does, e.g. `submarine --mock`.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Find what needs attention
|
||||||
|
|
||||||
|
Problems stand out: `unpushed` in red, `off pin` and `changed` in yellow,
|
||||||
|
everything else muted. Select a submodule to see the details on the right:
|
||||||
|
every issue in words, the resolved ref and why, the pinned, checked-out and
|
||||||
|
tip commits, and a preview of uncommitted changes.
|
||||||
|
|
||||||
|
```
|
||||||
|
src/ │ src/
|
||||||
|
core off pin origin/main │ net
|
||||||
|
▸ net [2] unpushed origin/main ↑1│
|
||||||
|
ui in sync origin/main │ unpushed pin not pushed to any remote
|
||||||
|
│
|
||||||
|
│ Ref origin/main ↑1
|
||||||
|
│ via checked out branch
|
||||||
|
│ Pinned b7c8d9e Hotfix null pointer
|
||||||
|
│ Checkout b7c8d9e Hotfix null pointer
|
||||||
|
│ Tip 6f5e4d3 Release 2.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Jump to a submodule with `/`
|
||||||
|
|
||||||
|
Press `/` and type. Only matching submodules (and the parents that lead to
|
||||||
|
them) stay. Your selection moves to the next match if it was filtered out.
|
||||||
|
|
||||||
|
```
|
||||||
|
libs/
|
||||||
|
▾ log in sync origin/main
|
||||||
|
modules/
|
||||||
|
nested changed origin/main
|
||||||
|
/ nest enter navigate • esc close
|
||||||
|
```
|
||||||
|
|
||||||
|
`enter` keeps the filter and gives you the normal keys back, `esc` removes
|
||||||
|
it and leaves your selection where it is.
|
||||||
|
|
||||||
|
### Copy a path
|
||||||
|
|
||||||
|
Select a submodule and press `c`. Its absolute directory lands in your
|
||||||
|
clipboard, ready for `cd <paste>`. It works over SSH too (OSC 52; in tmux,
|
||||||
|
`set -g set-clipboard on`).
|
||||||
|
|
||||||
|
### Fetch and reload
|
||||||
|
|
||||||
|
`f` fetches all repositories again, `r` reloads everything after you edited
|
||||||
|
files.
|
||||||
|
|
||||||
|
## Keys
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
|---|---|
|
||||||
|
| `↑` `↓` / `j` `k` | Move |
|
||||||
|
| `←` `→` / `h` `l` | Fold / unfold, parent / first child |
|
||||||
|
| `H` `L` / `⇧←` `⇧→` | Collapse / expand all |
|
||||||
|
| `space` | Toggle the selected submodule |
|
||||||
|
| `home` `end` / `g` `G`, `pgup` `pgdn` | Jump, page |
|
||||||
|
| `/` | Filter |
|
||||||
|
| `c` | Copy path |
|
||||||
|
| `f` / `r` | Fetch / reload |
|
||||||
|
| `?` | Full help |
|
||||||
|
| `q` | Quit |
|
||||||
|
|
||||||
|
The mouse works too: click to select, click `▸` to toggle, wheel to move.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
A prototype. Browsing is complete; the actions `u` (check out the pin),
|
||||||
|
`b` (bump the pin to the tip) and `!` (problems only) are planned.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Requirements, state rules, UX guide, architecture and the decision log are in
|
||||||
|
[docs/](docs/README.md).
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go run ./cmd/submarine --mock # run on mock data
|
||||||
|
scripts/make-fixture.sh DIR # build a test superproject with every state
|
||||||
|
go vet ./... && go test ./...
|
||||||
|
```
|
||||||
@@ -246,3 +246,15 @@ usual keys. A filtered-out selection moves to the next visible submodule
|
|||||||
(previous if none follows) instead of the first, so the cursor stays near
|
(previous if none follows) instead of the first, so the cursor stays near
|
||||||
where it was. This adds the `bubbles/textinput` dependency (and its
|
where it was. This adds the `bubbles/textinput` dependency (and its
|
||||||
`atotto/clipboard` import).
|
`atotto/clipboard` import).
|
||||||
|
|
||||||
|
## Staged pin is shown in the tree, but is not an issue
|
||||||
|
|
||||||
|
A staged pin was only visible in the detail pane, so the header's `1 pin
|
||||||
|
staged` could not be traced to a row, and `in sync nothing to do` was
|
||||||
|
wrong for a submodule whose pin change still has to be committed. The tree
|
||||||
|
now shows it in the status column: blue `staged` replaces `in sync`; next to
|
||||||
|
an issue word it is a blue `●` (an issue wins the word, since the cell holds
|
||||||
|
one word, D3). It is not counted in `+n`, because `+n` and the header count
|
||||||
|
issues and a staged pin stays quiet (D4). A collapsed row shows `●` if a
|
||||||
|
staged pin is hidden below it (`Hidden.Staged`), placed before the `·k`
|
||||||
|
counts so truncation of the 16-column cell cuts the counts first.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
@@ -35,7 +35,7 @@ commit hashes. It doesn't answer the questions users actually have:
|
|||||||
e.g. `[2]`.
|
e.g. `[2]`.
|
||||||
- **F6** A collapsed submodule shows the number of issues **hidden** in all
|
- **F6** A collapsed submodule shows the number of issues **hidden** in all
|
||||||
its descendants (`·1 ·2`), one count per level: errors in red, then
|
its descendants (`·1 ·2`), one count per level: errors in red, then
|
||||||
warnings in yellow.
|
warnings in yellow. A staged pin below shows as a blue `●` before them.
|
||||||
- **F7** Expansion state of descendants is remembered when a parent is
|
- **F7** Expansion state of descendants is remembered when a parent is
|
||||||
collapsed and restored when it is expanded again (except after
|
collapsed and restored when it is expanded again (except after
|
||||||
"collapse all", which resets it).
|
"collapse all", which resets it).
|
||||||
@@ -44,7 +44,8 @@ commit hashes. It doesn't answer the questions users actually have:
|
|||||||
|
|
||||||
- **F10** Each submodule shows at most **one** state word: the most severe
|
- **F10** Each submodule shows at most **one** state word: the most severe
|
||||||
issue, plus `+n` if there are more. Submodules without issues show a
|
issue, plus `+n` if there are more. Submodules without issues show a
|
||||||
muted `in sync`.
|
muted `in sync`, or a blue `staged` if their pin change is staged. With
|
||||||
|
issues, a staged pin adds a blue `●` after the word.
|
||||||
See [state-model.md](state-model.md) for the full rules.
|
See [state-model.md](state-model.md) for the full rules.
|
||||||
- **F11** Each submodule shows its pin as a ref (`origin/main`, a tag, or a
|
- **F11** Each submodule shows its pin as a ref (`origin/main`, a tag, or a
|
||||||
short SHA as a fallback) and the distance to that ref (`↑n`, `↓n`, `↑n↓m`).
|
short SHA as a fallback) and the distance to that ref (`↑n`, `↓n`, `↑n↓m`).
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ Rules:
|
|||||||
changed.
|
changed.
|
||||||
- **Not issues** (quiet): in sync, pin behind/ahead/diverged but pushed,
|
- **Not issues** (quiet): in sync, pin behind/ahead/diverged but pushed,
|
||||||
staged pin, detached HEAD on the pin. They are shown as information only.
|
staged pin, detached HEAD on the pin. They are shown as information only.
|
||||||
|
A staged pin is also visible in the tree (`staged` or a `●` marker), see
|
||||||
|
[ux-guide.md](ux-guide.md#tree-row), but is never counted as an issue.
|
||||||
- A loading error is shown as `error` (Danger) in the UI; it is not an issue
|
- A loading error is shown as `error` (Danger) in the UI; it is not an issue
|
||||||
kind.
|
kind.
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -63,7 +63,7 @@ indent ▸ name [n] status ref delta
|
|||||||
| indent | 2 per depth | |
|
| indent | 2 per depth | |
|
||||||
| toggle | 3 | `▸` collapsed, `▾` expanded, blank for leaves |
|
| toggle | 3 | `▸` collapsed, `▾` expanded, blank for leaves |
|
||||||
| name | fits the widest visible name | name, `[n]` = direct children (collapsed only) |
|
| name | fits the widest visible name | name, `[n]` = direct children (collapsed only) |
|
||||||
| status | 16 | issue word, `+n`, `·k` hidden issues per level (red, then yellow); muted `in sync` without issues; spinner while loading |
|
| status | 16 | issue word, `+n`, `●` staged pin, `·k` hidden issues per level (red, then yellow); blue `staged` or muted `in sync` without issues; spinner while loading |
|
||||||
| ref | widest ref, max 22 | resolved ref; muted short SHA if unresolved |
|
| ref | widest ref, max 22 | resolved ref; muted short SHA if unresolved |
|
||||||
| delta | 6 | `↑n`, `↓n`, `↑n↓m` |
|
| delta | 6 | `↑n`, `↓n`, `↑n↓m` |
|
||||||
|
|
||||||
@@ -77,8 +77,8 @@ indent ▸ name [n] status ref delta
|
|||||||
|
|
||||||
1. Directory (muted) and name (bold)
|
1. Directory (muted) and name (bold)
|
||||||
2. One line per issue: word (colored, 10 columns) + explanation;
|
2. One line per issue: word (colored, 10 columns) + explanation;
|
||||||
`in sync nothing to do` if there are none; `staged` and
|
`in sync nothing to do` if there are none and no pin is staged;
|
||||||
`detached HEAD on pin` as information
|
`staged` and `detached HEAD on pin` as information
|
||||||
3. `Changes` (only with uncommitted changes), see
|
3. `Changes` (only with uncommitted changes), see
|
||||||
[Change preview](#change-preview)
|
[Change preview](#change-preview)
|
||||||
4. `Ref` with distance and `via <rule>`; `Fetched <age>` of the submodule;
|
4. `Ref` with distance and `via <rule>`; `Fetched <age>` of the submodule;
|
||||||
@@ -165,7 +165,7 @@ colors in components.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Danger | ANSI `1` red, bold | `unpushed`, `conflict`, `error`, `↑n` of an unpushed pin |
|
| Danger | ANSI `1` red, bold | `unpushed`, `conflict`, `error`, `↑n` of an unpushed pin |
|
||||||
| Warn | ANSI `3` yellow | `missing`, `off pin`, `changed`, differing checkout SHA |
|
| Warn | ANSI `3` yellow | `missing`, `off pin`, `changed`, differing checkout SHA |
|
||||||
| Info | ANSI `4` blue | `staged` |
|
| Info | ANSI `4` blue | `staged`, the `●` staged marker |
|
||||||
| Primary | ANSI `6` cyan | branch names, commit hashes, spinner |
|
| Primary | ANSI `6` cyan | branch names, commit hashes, spinner |
|
||||||
| Muted | ANSI `8` grey | quiet rows, directory headers, arrows, labels |
|
| Muted | ANSI `8` grey | quiet rows, directory headers, arrows, labels |
|
||||||
| Text | terminal default | normal text |
|
| Text | terminal default | normal text |
|
||||||
|
|||||||
@@ -139,7 +139,13 @@ func TestWatch(t *testing.T) {
|
|||||||
|
|
||||||
// a commit in another shell
|
// a commit in another shell
|
||||||
git(t, filepath.Join(super, "libs", "store"), "commit", "-q", "--allow-empty", "-m", "Local")
|
git(t, filepath.Join(super, "libs", "store"), "commit", "-q", "--allow-empty", "-m", "Local")
|
||||||
if c := next(t, ch); c != (submodule.Change{Path: "libs/store", Local: true}) {
|
c := next(t, ch)
|
||||||
|
if c == (submodule.Change{Path: "src/net"}) {
|
||||||
|
// git fetch writes FETCH_HEAD and the refs at different moments, so
|
||||||
|
// with a 20ms poll it can be reported in two rounds
|
||||||
|
c = next(t, ch)
|
||||||
|
}
|
||||||
|
if c != (submodule.Change{Path: "libs/store", Local: true}) {
|
||||||
t.Errorf("after commit: %+v, want local change of libs/store", c)
|
t.Errorf("after commit: %+v, want local change of libs/store", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,13 +60,14 @@ func (s *Status) Issues() []Issue {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hidden counts issues below a submodule per level.
|
// Hidden counts issues below a submodule per level, plus staged pins (which
|
||||||
type Hidden struct{ Warn, Danger int }
|
// are not issues).
|
||||||
|
type Hidden struct{ Warn, Danger, Staged int }
|
||||||
|
|
||||||
// Total is the number of issues of all levels.
|
// Total is the number of issues of all levels; staged pins are not counted.
|
||||||
func (h Hidden) Total() int { return h.Warn + h.Danger }
|
func (h Hidden) Total() int { return h.Warn + h.Danger }
|
||||||
|
|
||||||
// HiddenIssues counts the issues of all loaded descendants per level. A
|
// HiddenIssues counts the issues and staged pins of all loaded descendants. A
|
||||||
// descendant whose load failed counts as one danger, since its state is
|
// descendant whose load failed counts as one danger, since its state is
|
||||||
// unknown.
|
// unknown.
|
||||||
func (m *Submodule) HiddenIssues() Hidden {
|
func (m *Submodule) HiddenIssues() Hidden {
|
||||||
@@ -75,6 +76,9 @@ func (m *Submodule) HiddenIssues() Hidden {
|
|||||||
if c.Status == nil && c.Err != nil {
|
if c.Status == nil && c.Err != nil {
|
||||||
h.Danger++
|
h.Danger++
|
||||||
}
|
}
|
||||||
|
if c.Status != nil && c.Status.PinStaged {
|
||||||
|
h.Staged++
|
||||||
|
}
|
||||||
for _, is := range c.Status.Issues() {
|
for _, is := range c.Status.Issues() {
|
||||||
switch is.Level {
|
switch is.Level {
|
||||||
case LevelDanger:
|
case LevelDanger:
|
||||||
|
|||||||
@@ -39,15 +39,15 @@ func TestIssues(t *testing.T) {
|
|||||||
|
|
||||||
func TestHiddenIssues(t *testing.T) {
|
func TestHiddenIssues(t *testing.T) {
|
||||||
ok := &Status{Pin: PinInfo{Pushed: true}}
|
ok := &Status{Pin: PinInfo{Pushed: true}}
|
||||||
dirty := &Status{Worktree: WorktreeModified, Pin: PinInfo{Pushed: true}}
|
dirty := &Status{Worktree: WorktreeModified, Pin: PinInfo{Pushed: true}, PinStaged: true}
|
||||||
unpushed := &Status{}
|
unpushed := &Status{}
|
||||||
grandchild := &Submodule{Status: unpushed}
|
grandchild := &Submodule{Status: unpushed}
|
||||||
child := &Submodule{Status: dirty, Children: []*Submodule{grandchild}}
|
child := &Submodule{Status: dirty, Children: []*Submodule{grandchild}}
|
||||||
loading := &Submodule{}
|
loading := &Submodule{}
|
||||||
root := &Submodule{Status: ok, Children: []*Submodule{child, {Status: ok}, loading}}
|
root := &Submodule{Status: ok, Children: []*Submodule{child, {Status: ok}, loading}}
|
||||||
|
|
||||||
if got := root.HiddenIssues(); got != (Hidden{Warn: 1, Danger: 1}) {
|
if got := root.HiddenIssues(); got != (Hidden{Warn: 1, Danger: 1, Staged: 1}) {
|
||||||
t.Errorf("HiddenIssues() = %+v; want 1 warn, 1 danger", got)
|
t.Errorf("HiddenIssues() = %+v; want 1 warn, 1 danger, 1 staged", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,11 +97,11 @@ func (m Model) lines(s *submodule.Submodule) []string {
|
|||||||
for _, is := range issues {
|
for _, is := range issues {
|
||||||
out = append(out, t.LevelStyle(is.Level, lipgloss.NewStyle()).Width(labelWidth).Render(theme.IssueWord(is.Kind))+text.Render(is.Detail))
|
out = append(out, t.LevelStyle(is.Level, lipgloss.NewStyle()).Width(labelWidth).Render(theme.IssueWord(is.Kind))+text.Render(is.Detail))
|
||||||
}
|
}
|
||||||
if len(issues) == 0 {
|
if len(issues) == 0 && !st.PinStaged {
|
||||||
out = append(out, label(theme.InSync)+muted.Render("nothing to do"))
|
out = append(out, label(theme.InSync)+muted.Render("nothing to do"))
|
||||||
}
|
}
|
||||||
if st.PinStaged {
|
if st.PinStaged {
|
||||||
out = append(out, theme.Fg(t.Info).Width(labelWidth).Render("staged")+text.Render("pin change staged, not committed"))
|
out = append(out, theme.Fg(t.Info).Width(labelWidth).Render(theme.Staged)+text.Render("pin change staged, not committed"))
|
||||||
}
|
}
|
||||||
if st.Checkout == submodule.CheckoutDetached {
|
if st.Checkout == submodule.CheckoutDetached {
|
||||||
out = append(out, label("")+muted.Render("detached HEAD on pin"))
|
out = append(out, label("")+muted.Render("detached HEAD on pin"))
|
||||||
@@ -170,6 +170,9 @@ func (m Model) lines(s *submodule.Submodule) []string {
|
|||||||
if h.Warn > 0 {
|
if h.Warn > 0 {
|
||||||
parts = append(parts, t.LevelStyle(submodule.LevelWarn, lipgloss.NewStyle()).Render(fmt.Sprintf("%d warning(s)", h.Warn)))
|
parts = append(parts, t.LevelStyle(submodule.LevelWarn, lipgloss.NewStyle()).Render(fmt.Sprintf("%d warning(s)", h.Warn)))
|
||||||
}
|
}
|
||||||
|
if h.Staged > 0 {
|
||||||
|
parts = append(parts, theme.Fg(t.Info).Render(fmt.Sprintf("%d %s", h.Staged, theme.Staged)))
|
||||||
|
}
|
||||||
if len(parts) > 0 {
|
if len(parts) > 0 {
|
||||||
children += muted.Render(" · ") + strings.Join(parts, muted.Render(", ")) + muted.Render(" below")
|
children += muted.Render(" · ") + strings.Join(parts, muted.Render(", ")) + muted.Render(" below")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import (
|
|||||||
// InSync is the word for a submodule without issues.
|
// InSync is the word for a submodule without issues.
|
||||||
const InSync = "in sync"
|
const InSync = "in sync"
|
||||||
|
|
||||||
|
// Staged is the word for a pin change staged in the superproject. It is not
|
||||||
|
// an issue: it replaces InSync, or follows an issue word as a marker.
|
||||||
|
const Staged = "staged"
|
||||||
|
|
||||||
// IssueWord is the short label for an issue kind shown in the tree.
|
// IssueWord is the short label for an issue kind shown in the tree.
|
||||||
func IssueWord(k submodule.IssueKind) string {
|
func IssueWord(k submodule.IssueKind) string {
|
||||||
switch k {
|
switch k {
|
||||||
|
|||||||
@@ -410,7 +410,12 @@ func (m Model) nodeLine(r row, nameColW, refW int) string {
|
|||||||
nameCell := seg(textColor, name) + seg(t.Muted, count) + pad(nameW-lipgloss.Width(name+count))
|
nameCell := seg(textColor, name) + seg(t.Muted, count) + pad(nameW-lipgloss.Width(name+count))
|
||||||
|
|
||||||
// status: the most severe issue as a word, "+n" for further issues;
|
// status: the most severe issue as a word, "+n" for further issues;
|
||||||
// a muted "in sync" without issues, so an empty cell never looks broken
|
// a muted "in sync" without issues, so an empty cell never looks broken.
|
||||||
|
// A staged pin is no issue: it replaces "in sync", or follows the issue
|
||||||
|
// as a blue marker (also for staged pins below a collapsed row).
|
||||||
|
staged := s.Status != nil && s.Status.PinStaged
|
||||||
|
h := s.HiddenIssues()
|
||||||
|
stagedMark := staged && len(issues) > 0
|
||||||
var status string
|
var status string
|
||||||
switch {
|
switch {
|
||||||
case s.Err != nil:
|
case s.Err != nil:
|
||||||
@@ -422,10 +427,15 @@ func (m Model) nodeLine(r row, nameColW, refW int) string {
|
|||||||
if len(issues) > 1 {
|
if len(issues) > 1 {
|
||||||
status += seg(t.Muted, fmt.Sprintf(" +%d", len(issues)-1))
|
status += seg(t.Muted, fmt.Sprintf(" +%d", len(issues)-1))
|
||||||
}
|
}
|
||||||
|
case staged:
|
||||||
|
status = seg(t.Info, theme.Staged)
|
||||||
default:
|
default:
|
||||||
status = seg(t.Muted, theme.InSync)
|
status = seg(t.Muted, theme.InSync)
|
||||||
}
|
}
|
||||||
if h := s.HiddenIssues(); collapsed && h.Total() > 0 {
|
if stagedMark || collapsed && h.Staged > 0 {
|
||||||
|
status += pad(1) + seg(t.Info, t.Glyphs.Staged)
|
||||||
|
}
|
||||||
|
if collapsed && h.Total() > 0 {
|
||||||
for _, c := range []struct {
|
for _, c := range []struct {
|
||||||
level submodule.Level
|
level submodule.Level
|
||||||
n int
|
n int
|
||||||
|
|||||||
Reference in New Issue
Block a user