Compare commits

..

No commits in common. "main" and "v0.11.0" have entirely different histories.

13 changed files with 244 additions and 909 deletions

View file

@ -1,23 +0,0 @@
This is a Go based repository buliding a command line tool that converts the status of a git working tree for a specific directory into a tmux status string the end user can add to their tmux status line. It is designed to be used with the `tmux` terminal multiplexer.
## Code Standards
### Required Before Each Commit
- Run `go test ./...` before committing any changes to ensure tests pass
- Go code should be formatted with `gofmt`
## Repository Structure
- `./`: repository root, main package, default configuration file, and main entry point
- `tmux/`: tmux formatting
- `json/`: used by gitmux -dbg to print the git working tree status as a json object, for debugging purposes
- `testdata/`: testscripts fixtures when actual gitmux output is checked against some specific conditions.
## Key Guidelines
1. Follow Go best practices and idiomatic patterns
2. Maintain existing code structure and organization
3. Write unit tests for new functionality. Use table-driven unit tests when possible.
4. Document public options
5. Keep table aligned in the README.md file, for a nice experience even when reading the file in a terminal

View file

@ -2,14 +2,6 @@
_Describe the problem or feature in addition to a link to the issues._
## Approach
_How does this Pull Request address the problem?_
_How does this change address the problem?_
## Testing
- There should be some unit tests for every behaviour change or new feature
- If you're adding a new configuration option, the default configuration file
(.gitmux.yml) should be modified with the new option
- There's a test that verifies that gitmux output always remains the same
across versions when using the default configuration. You can run it with:
`go test -run TestScriptst`
- `README.md` should always be updated.
Thank you!

View file

@ -7,10 +7,10 @@ jobs:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version-file: "go.mod"
go-version: "1.20.x"
- name: Run tests
run: go test -race ./...
@ -21,17 +21,32 @@ jobs:
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: "go.mod"
go-version: "1.20.x"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v2
with:
distribution: goreleaser
version: "~> v2"
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
check_brew:
name: Check Brew Tap
needs: goreleaser
runs-on: macos-latest
steps:
- name: Install gitmux with brew
run: |
brew tap arl/arl
brew install gitmux
gitmux -V
- name: Check installed gitmux version
run: |
[ v$(gitmux -V) == "$GITHUB_REF_NAME" ]

1
.gitignore vendored
View file

@ -1,2 +1 @@
dist
gitmux

View file

@ -85,7 +85,3 @@ tmux:
swap_divergence: false
# Add a space between behind & ahead upstream counts.
divergence_space: false
# Show flags symbols without counts.
# When true, shows only symbols (empty symbols show nothing).
# When false (default), shows symbols with counts (empty symbols show counts only).
flags_without_count: false

View file

@ -1,4 +1,3 @@
version: 2
before:
hooks:
- go mod tidy
@ -21,6 +20,7 @@ archives:
files:
- LICENSE
- README.md
rlcp: true
checksum:
name_template: "checksums.txt"
snapshot:
@ -31,3 +31,20 @@ changelog:
exclude:
- "^docs:"
- "^test:"
brews:
- name: gitmux
homepage: "https://github.com/arl/gitmux"
description: "Git in your tmux status bar."
license: "MIT"
url_template: "https://github.com/arl/gitmux/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}"
folder: Formula
skip_upload: false
tap:
owner: arl
name: homebrew-arl
branch: main
commit_author:
name: goreleaser-bot
email: bot@goreleaser.com

View file

@ -21,22 +21,21 @@
![demo](https://raw.githubusercontent.com/arl/gitmux/readme-images/demo-small.gif)
- **easy**: install and forget
- **minimal**: shows what you need, when you need it
- **discrete**: shows nothing when you're not in a git-managed directory
- **shell-agnostic**: doesn't rely on any shell-specific features, so will work with all of them
- **customizable**: colors, symbols and layout are all configurable
- **easy**. Install and forget about it
- **minimal**. Just shows what you need, when you need it
- **discrete**. Get out of your way if current directory is not in a Git tree
- **shell-agnostic**. Does not rely on shell-features so works with all of them
- **customizable**. Colors, symbols and layout are configurable
---
- [Prerequisites](#prerequisites)
- [Installing](#installing)
- [Binary release](#binary-release)
- [Homebrew (macOS and linux) (amd64 and arm64)](#homebrew-macos-and-linux-amd64-and-arm64)
- [Homebrew tap (macOS and linux) (amd64 and arm64)](#homebrew-tap-macos-and-linux-amd64-and-arm64)
- [AUR](#aur)
- [From source](#from-source)
- [Getting started](#getting-started)
- [Command line interface](#command-line-interface)
- [Customizing](#customizing)
- [Symbols](#symbols)
- [Styles](#styles)
@ -60,10 +59,11 @@ Works with all reasonably recent [tmux](https://github.com/tmux/tmux) versions (
[Download the latest](https://github.com/arl/gitmux/releases/latest) binary for your platform/architecture and uncompress it.
### Homebrew (macOS and linux) (amd64 and arm64)
### Homebrew tap (macOS and linux) (amd64 and arm64)
Install the latest version with:
brew tap arl/arl
brew install gitmux
### AUR
@ -91,29 +91,6 @@ you run or when you switch windows, however it won't refresh automatically, nor
Note that `tmux v2.1` was released in 2015 so you're probably better off updating to a more recent version anyway 🙂.
### Command line interface
`gitmux` is not meant to be call directly but it still has a command line interface
that can be used to create the default configuration, troubleshoot your installation or during development:
```
$ gitmux -h
gitmux v0.11.5
Usage: gitmux [options] [dir]
gitmux prints the status of a Git working tree as a tmux format string.
If directory is not given, it default to the working directory.
Options:
-cfg FILE read gitmux config from FILE.
-printcfg prints default configuration file.
-dbg outputs Git status as JSON and print errors.
-timeout DUR exits if still running after given duration (ex: 2s, 500ms).
-V prints gitmux version and exits.
```
## Customizing
`gitmux` output can be customized via a configuration file in YAML format.
@ -288,17 +265,14 @@ layout: [branch, "|", flags, "|", stats]
This is the list of additional configuration `options`:
| Option | Description | Default |
| :------------------- | :------------------------------------------------------------------------------ | :----------------: |
| `branch_max_len` | Maximum displayed length for local and remote branch names | `0` (no limit) |
| `branch_trim` | Trim left, right or from the center of the branch (`right`, `left` or `center`) | `right` (trailing) |
| `ellipsis` | Character to show branch name has been truncated | `…` |
| `hide_clean` | Hides the clean flag entirely | `false` |
| `swap_divergence` | Swaps order of behind & ahead upstream counts | `false` |
| `divergence_space` | Add a space between behind & ahead upstream counts | `false` |
| `flags_without_count`| Show flags symbols without counts* | `false` |
*When `flags_without_count` is true, shows only symbols (empty symbols show nothing). When false (default), shows symbols with counts (empty symbols show counts only).
| Option | Description | Default |
| :----------------- | :------------------------------------------------------------------------------ | :----------------: |
| `branch_max_len` | Maximum displayed length for local and remote branch names | `0` (no limit) |
| `branch_trim` | Trim left, right or from the center of the branch (`right`, `left` or `center`) | `right` (trailing) |
| `ellipsis` | Character to show branch name has been truncated | `…` |
| `hide_clean` | Hides the clean flag entirely | `false` |
| `swap_divergence` | Swaps order of behind & ahead upstream counts | `false` |
| `divergence_space` | Add a space between behind & ahead upstream counts | `false` |
## Troubleshooting

View file

@ -23,7 +23,7 @@ gitmux prints the status of a Git working tree as a tmux format string.
If directory is not given, it default to the working directory.
Options:
-cfg FILE read gitmux config from FILE.
-cfg cfgfile use cfgfile when printing git status.
-printcfg prints default configuration file.
-dbg outputs Git status as JSON and print errors.
-timeout DUR exits if still running after given duration (ex: 2s, 500ms).

14
go.mod
View file

@ -1,18 +1,16 @@
module github.com/arl/gitmux
go 1.24
toolchain go1.24.5
go 1.20
require (
github.com/arl/gitstatus v0.7.0
github.com/rogpeppe/go-internal v1.14.1
gopkg.in/yaml.v3 v3.0.1
github.com/arl/gitstatus v0.6.0
github.com/rogpeppe/go-internal v1.9.0
gopkg.in/yaml.v3 v3.0.0
)
require (
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/tools v0.35.0 // indirect
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
github.com/stretchr/testify v1.7.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

27
go.sum
View file

@ -1,20 +1,25 @@
github.com/arl/gitstatus v0.7.0 h1:kaAK5V4v8yXgCHAY1yAM06PsZdve8ChSpqNIkWyFMl8=
github.com/arl/gitstatus v0.7.0/go.mod h1:UTR2HNKuuQOLsDxU8MOQYCwhBDqWZF9DIN/KgZ7WbnE=
github.com/arl/gitstatus v0.6.0 h1:y6DOdk20wHDKRZaQ5cuYNTrwRts8TXAOs8nxxpfrQts=
github.com/arl/gitstatus v0.6.0/go.mod h1:qDNPAKa1vROZfzauCeHLxF4mciRLcluP+d2/a+bcxFo=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab h1:628ME69lBm9C6JY2wXhAph/yjN3jezx1z7BIDLUwxjo=
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,64 +0,0 @@
#!/bin/sh
_have() { type "$1" >/dev/null 2>&1; }
help() {
cat <<EOF
Usage: ${0##*/} [options] [version]
Options:
-h, --help Show this help message and exit.
Arguments:
version Specify the version of gitmux to install. If not provided, the latest version will be installed.
Environment Variables:
INSTALL_PATH Specify the directory where gitmux should be installed. Required
Description:
This script installs the gitmux tool, which provides Git status information in the tmux status line.
It automatically detects the system architecture and downloads the appropriate version of gitmux.
Examples:
INSTALL_PATH=/usr/local/bin ${0##*/} Install the latest version of gitmux to /usr/local/bin.
INSTALL_PATH=/usr/local/bin ${0##*/} v0.7.0 Install version 0.7.0 of gitmux to /usr/local/bin.
EOF
}
gh_install() {
ver="$1"
repo='arl/gitmux'
[ -z "$ver" ] && {
latest="https://api.github.com/repos/$repo/releases/latest"
ver=$(curl -sS "$latest" | grep tarball_url | sed 's>.*: "\(.*\)".*>\1>') && test -n "$ver"
ver=${ver##*/}
}
tarname=''
archi=$(uname -sm)
case "$archi" in
Darwin\ arm64) tarname="gitmux_${ver}_macOS_arm64.tar.gz" ;;
Darwin\ x86_64) tarname="gitmux_${ver}_macOS_amd64.tar.gz" ;;
Linux\ aarch64*) tarname="gitmux_${ver}_linux_arm64.tar.gz" ;;
Linux\ *64) tarname="gitmux_${ver}_linux_amd64.tar.gz" ;;
*) echo "Unsupported architecture" && return 1 ;;
esac
tmpdir="$(mktemp -d)"
cd "$tmpdir" || :
curl -sSLO "https://github.com/$repo/releases/download/$ver/$tarname"
tar -xf gitmux*.tar.gz &&
mv gitmux "${INSTALL_PATH}"
[ -d "$tmpdir" ] && rm -rf "$tmpdir"
}
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then help && exit 0; fi
! _have curl && echo "This script depends on curl" && exit 1
[ -z "$INSTALL_PATH" ] && echo "Please set the INSTALL_PATH envvar to specify installation directory" && exit 1
echo "Installing gitmux..."
if gh_install "$@"; then
echo "Successfully installed gitmux"
else
echo "Could not install gitmux" && exit 1
fi

View file

@ -88,13 +88,12 @@ func (d *direction) UnmarshalYAML(value *yaml.Node) error {
}
type options struct {
BranchMaxLen int `yaml:"branch_max_len"`
BranchTrim direction `yaml:"branch_trim"`
Ellipsis string `yaml:"ellipsis"`
HideClean bool `yaml:"hide_clean"`
DivergenceSpace bool `yaml:"divergence_space"`
SwapDivergence bool `yaml:"swap_divergence"`
FlagsWithoutCount bool `yaml:"flags_without_count"`
BranchMaxLen int `yaml:"branch_max_len"`
BranchTrim direction `yaml:"branch_trim"`
Ellipsis string `yaml:"ellipsis"`
HideClean bool `yaml:"hide_clean"`
DivergenceSpace bool `yaml:"divergence_space"`
SwapDivergence bool `yaml:"swap_divergence"`
}
// A Formater formats git status to a tmux style string.
@ -111,9 +110,9 @@ type Formater struct {
// If max is zero, negative or greater than the number of runes in s, truncate
// just returns s.
//
// NOTE: If max is lower than len(ellipsis), in other words if we're not even
// NOTE: If max is lower than len(ellipsis), in other words it we're not even
// allowed to just return the ellipsis string, then we just return the maximum
// number of runes we can, without inserting ellipsis.
// number of runes we can, without inserting ellpisis.
func truncate(s, ellipsis string, max int, dir direction) string {
slen := utf8.RuneCountInString(s)
if max <= 0 || slen <= max {
@ -145,7 +144,6 @@ func truncate(s, ellipsis string, max int, dir direction) string {
runes = append(runes[:llen], ell...)
runes = append(runes, right...)
}
return string(runes)
}
@ -295,32 +293,15 @@ func (f *Formater) currentRef() string {
return fmt.Sprintf("%s%s%s", f.Styles.Clear, f.Styles.Branch, branch)
}
// appendFlag appends a flag to the flags slice based on configuration options
func (f *Formater) appendFlag(flags []string, style, symbol string, count int) []string {
if count == 0 {
return flags
}
if f.Options.FlagsWithoutCount {
// When flags_without_count is true, show symbol only (empty string if symbol is empty)
if symbol == "" {
return flags
}
return append(flags, fmt.Sprintf("%s%s", style, symbol))
}
// When flags_without_count is false, show symbol + count, or just count if symbol is empty
return append(flags, fmt.Sprintf("%s%s%d", style, symbol, count))
}
func (f *Formater) flags() string {
var flags []string
if f.st.IsClean {
// For stashed in clean state, handle empty symbols properly
flags = f.appendFlag(flags, f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed)
if f.st.NumStashed != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed))
}
// Clean flag only shows if symbol is not empty and hide_clean is false
if !f.Options.HideClean && f.Symbols.Clean != "" {
if !f.Options.HideClean {
flags = append(flags, fmt.Sprintf("%s%s", f.Styles.Clean, f.Symbols.Clean))
}
@ -329,12 +310,30 @@ func (f *Formater) flags() string {
}
}
// For all other flags, handle empty symbols properly
flags = f.appendFlag(flags, f.Styles.Staged, f.Symbols.Staged, f.st.NumStaged)
flags = f.appendFlag(flags, f.Styles.Conflict, f.Symbols.Conflict, f.st.NumConflicts)
flags = f.appendFlag(flags, f.Styles.Modified, f.Symbols.Modified, f.st.NumModified)
flags = f.appendFlag(flags, f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed)
flags = f.appendFlag(flags, f.Styles.Untracked, f.Symbols.Untracked, f.st.NumUntracked)
if f.st.NumStaged != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Staged, f.Symbols.Staged, f.st.NumStaged))
}
if f.st.NumConflicts != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Conflict, f.Symbols.Conflict, f.st.NumConflicts))
}
if f.st.NumModified != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Modified, f.Symbols.Modified, f.st.NumModified))
}
if f.st.NumStashed != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed))
}
if f.st.NumUntracked != 0 {
flags = append(flags,
fmt.Sprintf("%s%s%d", f.Styles.Untracked, f.Symbols.Untracked, f.st.NumUntracked))
}
if len(flags) > 0 {
return f.Styles.Clear + strings.Join(flags, " ")

File diff suppressed because it is too large Load diff