1
0
Fork 0

Adding upstream version 1.3.3.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
Daniel Baumann 2025-06-21 14:36:38 +02:00
parent c61b4bcb22
commit 4eec748022
Signed by: daniel.baumann
GPG key ID: BCC918A2ABD66424
7 changed files with 467 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
*.egg-info
*.pyc
build/*
dist/*
build_howto.md

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 - 2018 Jasper N. Brouwer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

199
README.md Normal file
View file

@ -0,0 +1,199 @@
Powerline Gitstatus
===================
A [Powerline][1] segment for showing the status of a Git working copy.
By [Jasper N. Brouwer][2].
It will show the branch-name, or the commit hash if in detached head state.
It will also show the number of commits behind, commits ahead, staged files,
unmerged files (conflicts), changed files, untracked files and stashed files
if that number is greater than zero.
![screenshot][4]
Glossary
--------
- ``: branch name or commit hash
- `★`: most recent tag (if enabled)
- `↓`: n commits behind
- `↑`: n commits ahead
- `●`: n staged files
- `✖`: n unmerged files (conflicts)
- `✚`: n changed files
- `…`: n untracked files
- `⚑`: n stashed files
Requirements
------------
The Gitstatus segment requires [git][5]! Preferably, but not limited to, version 1.8.5 or higher.
Version 1.8.5 will enable the usage of the `-C` parameter, which is more performant and accurate.
Installation
------------
### On Debian/Ubuntu
On a recent enough Debian (at least Stretch with backports enabled) or Ubuntu (at least 18.10) there is an official package available.
```txt
apt install powerline-gitstatus
```
This command will also instruct your package manager to install Powerline, if it's not already available.
Powerline will be automatically configured to use the Gitstatus highlight groups and add the segment to the default
shell theme.
### Using pip
```txt
pip install powerline-gitstatus
```
Configuration
-------------
The Gitstatus segment uses a couple of custom highlight groups. You'll need to define those groups in your colorscheme,
for example in `.config/powerline/colorschemes/default.json`:
```json
{
"groups": {
"gitstatus": { "fg": "gray8", "bg": "gray2", "attrs": [] },
"gitstatus_branch": { "fg": "gray8", "bg": "gray2", "attrs": [] },
"gitstatus_branch_clean": { "fg": "green", "bg": "gray2", "attrs": [] },
"gitstatus_branch_dirty": { "fg": "gray8", "bg": "gray2", "attrs": [] },
"gitstatus_branch_detached": { "fg": "mediumpurple", "bg": "gray2", "attrs": [] },
"gitstatus_tag": { "fg": "darkcyan", "bg": "gray2", "attrs": [] },
"gitstatus_behind": { "fg": "gray10", "bg": "gray2", "attrs": [] },
"gitstatus_ahead": { "fg": "gray10", "bg": "gray2", "attrs": [] },
"gitstatus_staged": { "fg": "green", "bg": "gray2", "attrs": [] },
"gitstatus_unmerged": { "fg": "brightred", "bg": "gray2", "attrs": [] },
"gitstatus_changed": { "fg": "mediumorange", "bg": "gray2", "attrs": [] },
"gitstatus_untracked": { "fg": "brightestorange", "bg": "gray2", "attrs": [] },
"gitstatus_stashed": { "fg": "darkblue", "bg": "gray2", "attrs": [] },
"gitstatus:divider": { "fg": "gray8", "bg": "gray2", "attrs": [] }
}
}
```
Then you can activate the Gitstatus segment by adding it to your segment configuration,
for example in `.config/powerline/themes/shell/default.json`:
```json
{
"function": "powerline_gitstatus.gitstatus",
"priority": 40
}
```
The Gitstatus segment will use the `-C` argument by default, but this requires git 1.8.5 or higher.
If you cannot meet that requirement, you'll have to disable the usage of `-C`.
Do this by passing `false` to the `use_dash_c` argument, for example in `.config/powerline/themes/shell/__main__.json`:
```json
"gitstatus": {
"args": {
"use_dash_c": false
}
}
```
It's strongly recommended to define the `trusted_paths` argument. This will
restrict the locations where git commands will be invoked, limiting the
exposure to remote code execution via malicious repositories. Navigating the
shell to repositories outside these trusted paths will not display the segment.
```json
"gitstatus": {
"args": {
"trusted_paths": [
"/home/foo/code",
"/home/foo/projects"
]
}
}
```
Optionally, a tag description for the current branch may be displayed using the `show_tag` option. Valid values for this
argument are:
* `last` : shows the most recent tag
* `annotated` : shows the most recent annotated tag
* `contains` : shows the closest tag that comes after the current commit
* `exact` : shows a tag only if it matches the current commit
You can enable this by passing one of these to the `show_tag` argument, for example in `.config/powerline/themes/shell/__main__.json`:
```json
"gitstatus": {
"args": {
"show_tag": "exact"
}
}
```
Git is executed an additional time to find this tag, so it is disabled by default.
Note: before v1.3.0, the behavior when the value is `True` was `last`. As of v1.3.0 onwards, `True` behaves as `exact`.
Optionally the format in which Gitstatus shows information can be customized.
This allows to use a different symbol or remove a fragment if desired. You can
customize string formats for _branch_, _tag_, _behind_, _ahead_, _staged_, _unmerged_,
_changed_, _untracked_ and _stash_ fragments with the following arguments in a
theme configuration file, for example `.config/powerline/themes/shell/__main__.json`:
```json
"gitstatus": {
"args": {
"formats": {
"branch": "\ue0a0 {}",
"tag": " ★ {}",
"behind": " ↓ {}",
"ahead": " ↑ {}",
"staged": " ● {}",
"unmerged": " ✖ {}",
"changed": " ✚ {}",
"untracked": " … {}",
"stashed": " ⚑ {}"
}
}
}
```
By default, when in detached head state (current revision is not a branch tip), Gitstatus shows a short commit hash in
place of the branch name. This can be replaced with a description of the closest reachable ref using the
`detached_head_style` argument, for example in `.config/powerline/themes/shell/__main__.json`:
```json
"gitstatus": {
"args": {
"detached_head_style": "ref"
}
}
```
By default, if your local branch has untracked files but no other changes, the branch status will be highlighted as dirty in the segment. You can disable this behavior by setting the `untracked_not_dirty` argument to `true`, for example in `.config/powerline/themes/shell/__main__.json`:
```json
"gitstatus": {
"args": {
"untracked_not_dirty": true
}
}
```
License
-------
Licensed under [the MIT License][3].
[1]: https://powerline.readthedocs.org/en/master/
[2]: https://github.com/jaspernbrouwer
[3]: https://github.com/jaspernbrouwer/powerline-gitstatus/blob/master/LICENSE
[4]: https://github.com/jaspernbrouwer/powerline-gitstatus/blob/master/screenshot.png
[5]: https://git-scm.com/

View file

@ -0,0 +1 @@
from .segments import gitstatus

View file

@ -0,0 +1,213 @@
# vim:fileencoding=utf-8:noet
from powerline.segments import Segment, with_docstring
from powerline.theme import requires_segment_info
from subprocess import PIPE, Popen
from pathlib import PurePath
import os, re, string
@requires_segment_info
class GitStatusSegment(Segment):
def execute(self, pl, command):
pl.debug('Executing command: %s' % ' '.join(command))
git_env = os.environ.copy()
git_env['LC_ALL'] = 'C'
proc = Popen(command, stdout=PIPE, stderr=PIPE, env=git_env)
out, err = [item.decode('utf-8') for item in proc.communicate()]
if out:
pl.debug('Command output: %s' % out.strip(string.whitespace))
if err:
pl.debug('Command errors: %s' % err.strip(string.whitespace))
return (out.splitlines(), err.splitlines())
def get_base_command(self, cwd, use_dash_c):
if use_dash_c:
return ['git', '-c', 'core.fsmonitor=', '-C', cwd]
while cwd and cwd != os.sep:
gitdir = os.path.join(cwd, '.git')
if os.path.isdir(gitdir):
return ['git', '-c', 'core.fsmonitor=', '--git-dir=%s' % gitdir, '--work-tree=%s' % cwd]
cwd = os.path.dirname(cwd)
return None
def parse_branch(self, line):
if not line:
return ('', False, 0, 0)
if line.startswith('## '):
line = line[3:]
match = re.search(r'^Initial commit on (.+)$', line)
if match is not None:
return (match.group(1), False, 0, 0)
match = re.search(r'^(.+) \(no branch\)$', line)
if match is not None:
return (match.group(1), True, 0, 0)
match = re.search(r'^(.+?)\.\.\.', line)
if match is not None:
branch = match.group(1)
match = re.search(r'\[ahead (\d+), behind (\d+)\]$', line)
if match is not None:
return (branch, False, int(match.group(2)), int(match.group(1)))
match = re.search(r'\[ahead (\d+)\]$', line)
if match is not None:
return (branch, False, 0, int(match.group(1)))
match = re.search(r'\[behind (\d+)\]$', line)
if match is not None:
return (branch, False, int(match.group(1)), 0)
return (branch, False, 0, 0)
return (line, False, 0, 0)
def parse_status(self, lines):
staged = len([True for l in lines if l[0] in 'MRC' or (l[0] == 'D' and l[1] != 'D') or (l[0] == 'A' and l[1] != 'A')])
unmerged = len([True for l in lines if l[0] == 'U' or l[1] == 'U' or (l[0] == 'A' and l[1] == 'A') or (l[0] == 'D' and l[1] == 'D')])
changed = len([True for l in lines if l[1] == 'M' or (l[1] == 'D' and l[0] != 'D')])
untracked = len([True for l in lines if l[0] == '?'])
return (staged, unmerged, changed, untracked)
def build_segments(self, formats, branch, detached, tag, behind, ahead, staged, unmerged, changed, untracked, stashed, untracked_not_dirty):
if detached:
branch_group = 'gitstatus_branch_detached'
elif staged or unmerged or changed or (untracked and not untracked_not_dirty):
branch_group = 'gitstatus_branch_dirty'
else:
branch_group = 'gitstatus_branch_clean'
segments = [
{'contents': formats.get('branch', u'\ue0a0 {}').format(branch), 'highlight_groups': [branch_group, 'gitstatus_branch', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'}
]
if tag:
segments.append({'contents': formats.get('tag', u' \u2605 {}').format(tag), 'highlight_groups': ['gitstatus_tag', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'})
if behind:
segments.append({'contents': formats.get('behind', '{}').format(behind), 'highlight_groups': ['gitstatus_behind', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'})
if ahead:
segments.append({'contents': formats.get('ahead', '{}').format(ahead), 'highlight_groups': ['gitstatus_ahead', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'})
if staged:
segments.append({'contents': formats.get('staged', '{}').format(staged), 'highlight_groups': ['gitstatus_staged', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'})
if unmerged:
segments.append({'contents': formats.get('unmerged', '{}').format(unmerged), 'highlight_groups': ['gitstatus_unmerged', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'})
if changed:
segments.append({'contents': formats.get('changed', '{}').format(changed), 'highlight_groups': ['gitstatus_changed', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'})
if untracked:
segments.append({'contents': formats.get('untracked', '{}').format(untracked), 'highlight_groups': ['gitstatus_untracked', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'})
if stashed:
segments.append({'contents': formats.get('stashed', '{}').format(stashed), 'highlight_groups': ['gitstatus_stashed', 'gitstatus'], 'divider_highlight_group': 'gitstatus:divider'})
return segments
def path_is_trusted(self, cwd, trusted_paths, pl):
for trusted_path in trusted_paths:
cwd_path = PurePath(cwd)
try:
cwd_path.relative_to(trusted_path)
return True
except ValueError:
pass
return False
def __call__(self, pl, segment_info, use_dash_c=True, show_tag=False, formats={}, detached_head_style='revision', untracked_not_dirty=False, trusted_paths=[]):
cwd = segment_info['getcwd']()
if not cwd:
return
if trusted_paths and not self.path_is_trusted(cwd, trusted_paths, pl):
pl.debug("cwd not in trusted paths")
return
base = self.get_base_command(cwd, use_dash_c)
if not base:
return
pl.debug('Running gitstatus %s -C' % ('with' if use_dash_c else 'without'))
status, err = self.execute(pl, base + ['status', '--branch', '--porcelain'])
if err and ('error' in err[0] or 'fatal' in err[0]):
return
branch, detached, behind, ahead = self.parse_branch(status.pop(0))
if not branch:
return
if branch == 'HEAD':
if detached_head_style == 'revision':
branch = self.execute(pl, base + ['rev-parse', '--short', 'HEAD'])[0][0]
elif detached_head_style == 'ref':
branch = self.execute(pl, base + ['describe', '--contains', '--all'])[0][0]
staged, unmerged, changed, untracked = self.parse_status(status)
stashed = len(self.execute(pl, base + ['stash', 'list', '--no-decorate'])[0])
if not show_tag:
tag, err = [''], False
elif show_tag == 'contains':
tag, err = self.execute(pl, base + ['describe', '--contains'])
elif show_tag == 'last':
tag, err = self.execute(pl, base + ['describe', '--tags'])
elif show_tag == 'annotated':
tag, err = self.execute(pl, base + ['describe'])
else:
tag, err = self.execute(pl, base + ['describe', '--tags', '--exact-match', '--abbrev=0'])
if err and ('error' in err[0] or 'fatal' in err[0] or 'Could not get sha1 for HEAD' in err[0]):
tag = ''
else:
tag = tag[0]
return self.build_segments(formats, branch, detached, tag, behind, ahead, staged, unmerged, changed, untracked, stashed, untracked_not_dirty)
gitstatus = with_docstring(GitStatusSegment(),
'''Return the status of a Git working copy.
It will show the branch-name, or the commit hash if in detached head state.
It will also show the number of commits behind, commits ahead, staged files,
unmerged files (conflicts), changed files, untracked files and stashed files
if that number is greater than zero.
:param bool use_dash_c:
Call git with ``-C``, which is more performant and accurate, but requires git 1.8.5 or higher.
Otherwise it will traverse the current working directory up towards the root until it finds a ``.git`` directory, then use ``--git-dir`` and ``--work-tree``.
True by default.
:param bool show_tag:
Show tag description. Valid options are``contains``, ``last``, ``annotated`` and ``exact``. A value of True behaves the same as ``exact``, which only displays a tag when it's assigned to the currently checked-out revision.
False by default, because it needs to execute git an additional time.
:param dict formats:
A string-to-string dictionary for customizing Git status formats. Valid keys include ``branch``, ``tag``, ``ahead``, ``behind``, ``staged``, ``unmerged``, ``changes``, ``untracked``, and ``stashed``.
Empty dictionary by default, which means the default formats are used.
:param detached_head_style:
Display style when in detached HEAD state. Valid values are ``revision``, which shows the current revision id, and ``ref``, which shows the closest reachable ref object.
The default is ``revision``.
:param untracked_not_dirty:
Untracked files alone will not mark the git branch status as dirty.
False by default.
Divider highlight group used: ``gitstatus:divider``.
Highlight groups used: ``gitstatus_branch_detached``, ``gitstatus_branch_dirty``, ``gitstatus_branch_clean``, ``gitstatus_branch``, ``gitstatus_tag``, ``gitstatus_behind``, ``gitstatus_ahead``, ``gitstatus_staged``, ``gitstatus_unmerged``, ``gitstatus_changed``, ``gitstatus_untracked``, ``gitstatus_stashed``, ``gitstatus``.
''')

28
pyproject.toml Normal file
View file

@ -0,0 +1,28 @@
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "powerline-gitstatus"
description = "A Powerline segment for showing the status of a Git working copy"
version = "1.3.3"
keywords = ["powerline git status prompt"]
license = {text = "MIT License"}
authors = [
{name = "Jasper N. Brouwer", email = "jasper@nerdsweide.nl"},
]
maintainers = [
{name ="Jérôme Charaoui", email = "jerome@riseup.net"},
]
classifiers = [
"Development Status :: 6 - Mature",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Topic :: Terminals"
]
[project.urls]
Homepage = "https://github.com/jaspernbrouwer/powerline-gitstatus"
Repository = "https://github.com/jaspernbrouwer/powerline-gitstatus.git"
Issues = "https://github.com/jaspernbrouwer/powerline-gitstatus"

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB