mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-27 03:49:40 -08:00
feat(plastic): add Plastic SCM segment
* feat(plastic): added Plastic SCM segment * refactor(plastic): polished new Plastic SCM segment * refactor: moved common scm segment code into base type git and plastic share some common methods and status properties. So moving them in a base type keeps the code base DRY * doc(plastic): Added docs for manual testing Plastic SCM * fix(plastic): Show files with merge conflicts as unmerged * fix(plastic): adhere to empty string check guidelines * fix(plastic): fixed linter errors * fix(pwsh): alert when we can't download dependencies resolves #1382 * refactor(plastic): polished new Plastic SCM segment * refactor: moved common scm segment code into base type git and plastic share some common methods and status properties * docs(plastic): added docs for manual testing Plastic SCM * fix(plastic): show files with merge conflicts as unmerged * fix(plastic): adhere to empty string check guidelines * fix(plastic): fixed linter errors Co-authored-by: Jan De Dobbeleer <jan.de.dobbeleer@gmail.com> Co-authored-by: Jan De Dobbeleer <2492783+JanDeDobbeleer@users.noreply.github.com>
This commit is contained in:
parent
d52f917782
commit
457f439a9f
352
docs/docs/contributing-plastic.md
Normal file
352
docs/docs/contributing-plastic.md
Normal file
|
@ -0,0 +1,352 @@
|
|||
---
|
||||
id: contributing_plastic
|
||||
title: Setup for Plastic SCM testing
|
||||
sidebar_label: Plastic SCM testing
|
||||
---
|
||||
|
||||
When changig the `segment_plastic.go` file, you may need to test your changes against an actual instance of
|
||||
[Plastic SCM][plastic]. This doc should bring you up to speed with Plastic SCM.
|
||||
|
||||
In the [contributing doc][contributing] there is a section about [dev containers & codespaces][devcontainer].
|
||||
You can setup Plastic SCM inside these as well.
|
||||
|
||||
## Sever Setup
|
||||
|
||||
Here you can find the [official setup instructions][setup-instructions]. I'll describe it in short:
|
||||
|
||||
### Installation on Debian or in dev-container
|
||||
|
||||
First add the repo:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y apt-transport-https
|
||||
echo "deb https://www.plasticscm.com/plasticrepo/stable/debian/ ./" | sudo tee /etc/apt/sources.list.d/plasticscm-stable.list
|
||||
wget https://www.plasticscm.com/plasticrepo/stable/debian/Release.key -O - | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
```
|
||||
|
||||
Then install the server: *this might throw an error at the end of the setup **see below***
|
||||
|
||||
```bash
|
||||
sudo apt-get install plasticscm-server-core
|
||||
```
|
||||
|
||||
This might show an error while configuring the installed package. In that case the server was nod registered as a service.
|
||||
**Ignore it!**
|
||||
|
||||
### Server configuration
|
||||
|
||||
Configuring the server is done via:
|
||||
|
||||
```bash
|
||||
cd /opt/plasticscm5/server/
|
||||
sudo ./plasticd configure
|
||||
```
|
||||
|
||||
You are asked 5 questions. Choose these options:
|
||||
|
||||
1. **1**: English
|
||||
2. 8087 (default port, **just hit return**)
|
||||
3. 8088 (default ssl port, **just hit return**)
|
||||
4. **1**: NameWorkingMode (use local users and groups)
|
||||
5. skip license token (**just hit return**)
|
||||
|
||||
**Concrats!** Your server is configured. You can find out more in the [official configuration instructions][server-config].
|
||||
|
||||
### Run Server
|
||||
|
||||
If your server installed without an error, it was correctly registered as a server and can be started via:
|
||||
|
||||
```bash
|
||||
sudo service plasticd start
|
||||
```
|
||||
|
||||
If not, you need to start it manually (for example inside the dev-container):
|
||||
|
||||
```bash
|
||||
cd /opt/plasticscm5/server/
|
||||
sudo ./plasticd start
|
||||
```
|
||||
|
||||
This will lock the current shell until the server process finishes. You might need to open another terminal to continue ;)
|
||||
|
||||
Your Plastic SCM server should be started now.
|
||||
|
||||
## Client Setup
|
||||
|
||||
Plastic SCM comes, much like git, with a CLI (+ client UI \[optional\])
|
||||
|
||||
### Installation on Debian or in dev-container
|
||||
|
||||
These are the steps to install the **Plastic SCM CLI** on Debian or in the dev-container:
|
||||
|
||||
```bash
|
||||
sudo apt-get install plasticscm-client-core
|
||||
```
|
||||
|
||||
### Client configuration
|
||||
|
||||
To connect the client to the server and setup an account run:
|
||||
|
||||
```bash
|
||||
clconfigureclient
|
||||
```
|
||||
|
||||
You are asked a few questions. Choose these options:
|
||||
|
||||
1. **1**: English
|
||||
2. localhost (**just hit return**)
|
||||
3. default port 8087 (**just hit return**)
|
||||
4. No SSL (**just hit return**)
|
||||
5. No Proxy (**just hit return**)
|
||||
|
||||
**Concrats!** Your client should now be connected to your server.
|
||||
|
||||
You can test if it worked and display some license info via:
|
||||
|
||||
```bash
|
||||
cm li
|
||||
```
|
||||
|
||||
## Testing stuff
|
||||
|
||||
Now to the fun part! The server is automatically setup to host a `default` repo with the branch `/main`.
|
||||
|
||||
The Plastic SCM CLI command is: `cm`
|
||||
|
||||
If you ever wonder what you can do with it call:
|
||||
|
||||
```bash
|
||||
cm showcommands --all`
|
||||
```
|
||||
|
||||
### Creating a local workspace
|
||||
|
||||
You need a local workspace to work with plastic:
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
mkdir dev
|
||||
cd dev
|
||||
cm wk create workspace workspace rep:default
|
||||
cd workspace
|
||||
cm status
|
||||
```
|
||||
|
||||
### Adding files
|
||||
|
||||
Start by creating local, private files
|
||||
|
||||
```bash
|
||||
echo "test" > myfile.txt
|
||||
cm status --all
|
||||
```
|
||||
|
||||
Add the file to your local changes
|
||||
|
||||
```bash
|
||||
cm add myfile.txt
|
||||
cm status
|
||||
```
|
||||
|
||||
**Test hint:** Both `Private` and `Added` files should be counted towards the `Added` property of the `plastic` segment.
|
||||
|
||||
### Commiting changes
|
||||
|
||||
After locally adding, changing, moving or delting files you want to commit them to create a new changeset.
|
||||
Run this command to commit all local changes:
|
||||
|
||||
```bash
|
||||
cm status | cm ci . -c "my first commit"
|
||||
```
|
||||
|
||||
### Unding local changes
|
||||
|
||||
Just in case you don't want or can commit your local changes, there is an undo command.
|
||||
This will undo all local changes:
|
||||
|
||||
```bash
|
||||
cm status | cm undo .
|
||||
```
|
||||
|
||||
### Changing, moving or deleting files
|
||||
|
||||
All these actions are done on the file level. You can run `cm status` to see your actions beeing tracked by plastic.
|
||||
Use the commit method described above to commit your changes.
|
||||
|
||||
**Test hint:** All these changes should be counted by the designated property (`Modified`, `Moved`, `Deleted`)
|
||||
of the `plastic` segment.
|
||||
|
||||
### Branching
|
||||
|
||||
Above the basics of handling the Plastic SCM client are described.
|
||||
But you would want to dive deeper and use branches or labels and merge them.
|
||||
|
||||
#### Create a new branch
|
||||
|
||||
To create a new branch based on the latest changeset on branch `/main` call
|
||||
|
||||
```bash
|
||||
cm br /main/new-branch
|
||||
```
|
||||
|
||||
Hint: To list all branches use
|
||||
|
||||
```bash
|
||||
cm find branches
|
||||
```
|
||||
|
||||
#### Set a label to the current changeset
|
||||
|
||||
Your workspace will always reflect one specific changeset (see `cm status`). You can set a label on that changeset for
|
||||
fast navigation or documentation purposes
|
||||
|
||||
```bash
|
||||
cm label mk "BL0001"
|
||||
```
|
||||
|
||||
Hint: To list all labels use
|
||||
|
||||
```bash
|
||||
cm find labels
|
||||
```
|
||||
|
||||
#### Switch your local workspace to a branch
|
||||
|
||||
To switch to a branch use
|
||||
|
||||
```bash
|
||||
cm switch /main/new-branch
|
||||
cm status
|
||||
```
|
||||
|
||||
**Test Hint:** the branch name should be reflected in the `Selector` property of the `plastic` segment
|
||||
|
||||
#### Switch to a changeset
|
||||
|
||||
Each commit gets a uniqe changeset number. You can switch to these via
|
||||
|
||||
```bash
|
||||
cm switch cs:1
|
||||
```
|
||||
|
||||
**Test Hint:** the changeset should be reflected in the `Selector` property of the `plastic` segment
|
||||
|
||||
#### Switch to a label
|
||||
|
||||
You can also switch to a label via
|
||||
|
||||
```bash
|
||||
cm switch BL00001
|
||||
```
|
||||
|
||||
**Test Hint:** the label should be reflected in the `Selector` property of the `plastic` segment
|
||||
|
||||
#### Merge a branch
|
||||
|
||||
To merge a branch you have to switch to the *destionation* branch of the merge. After that you can merge another branch via
|
||||
|
||||
```bash
|
||||
cm switch /main
|
||||
cm merge /main/new-branch --merge
|
||||
cm status
|
||||
```
|
||||
|
||||
Hint: This will only prepare the merge locally. You will have to commit the changes to complete the merge!
|
||||
|
||||
**Test Hint:** A pending merge should be reflected in the `MergePending` property of the `plastic` segment
|
||||
|
||||
#### Cherry-pick merge
|
||||
|
||||
While the merge above will merge all changes from a branch (and his parents), there is a cherry-pick merge,
|
||||
which will merge only the changes of one single changeset
|
||||
|
||||
```bach
|
||||
cm merge cs:8 --merge --cherrypicking
|
||||
```
|
||||
|
||||
Hint: This will only prepare the merge locally. You will have to commit the changes to complete the merge!
|
||||
|
||||
**Test Hint:** A pending cherry-pick merge should be reflected in the `MergePending` property of the `plastic` segment
|
||||
|
||||
#### Merge conflicts
|
||||
|
||||
There are multiple causes for conflicts while merging
|
||||
|
||||
##### Evil Twin
|
||||
|
||||
this happens when a merge is performed where two files with the same name were added on both the source and desitation branch.
|
||||
|
||||
```bash
|
||||
cm br mk /main/sub-branch
|
||||
cm switch /main/sub-branch
|
||||
echo "1" > twin.txt
|
||||
cm add twin.txt
|
||||
cm ci twin.txt
|
||||
|
||||
cm switch /main
|
||||
echo "2" > twin.txt
|
||||
cm add twin.txt
|
||||
cm ci twin.txt
|
||||
|
||||
cm merge /main/sub-branch --merge
|
||||
```
|
||||
|
||||
Hint: this will prompt you to directly resolve the conflict
|
||||
|
||||
##### Changed on both sides
|
||||
|
||||
this happens when a merge is performed where a file was changed on both sides: source and destination
|
||||
|
||||
```bash
|
||||
cm switch /main
|
||||
echo "base" > file.txt
|
||||
cm add file.txt
|
||||
cm ci file.txt
|
||||
|
||||
cm br mk /main/test
|
||||
|
||||
echo "on main" > file.txt
|
||||
cm ci file.txt
|
||||
|
||||
cm switch /main/test
|
||||
echo "on test" > file.txt
|
||||
cm ci file.txt
|
||||
|
||||
cm switch /main
|
||||
cm merge /main/test --merge
|
||||
```
|
||||
|
||||
Hint: this will try to open `gtkmergetool` which will fail inside the dev-container!
|
||||
|
||||
##### changed vs. deleted file
|
||||
|
||||
this happens when a merge is performed where a file was modified on one side and deleted on the other side of the merge
|
||||
|
||||
```bash
|
||||
cm switch /main
|
||||
echo "base" > deleteme.txt
|
||||
cm add deleteme.txt
|
||||
cm ci deleteme.txt
|
||||
|
||||
cm br mk /main/del
|
||||
|
||||
rm deleteme.txt
|
||||
cm ci --all
|
||||
|
||||
cm switch /main/del
|
||||
echo "on del" > deleteme.txt
|
||||
cm ci deleteme.txt
|
||||
|
||||
cm switch /main
|
||||
cm merge /main/del --merge
|
||||
```
|
||||
|
||||
Hint: This will prompt you to directly resolve the merge conflict
|
||||
|
||||
[plastic]: https://www.plasticscm.com/
|
||||
[setup-instructions]: https://www.plasticscm.com/documentation/administration/plastic-scm-version-control-administrator-guide#Chapter3:PlasticSCMinstallation
|
||||
[server-config]: https://www.plasticscm.com/documentation/administration/plastic-scm-version-control-administrator-guide#Serverconfiguration
|
||||
[contributing]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
|
||||
[devcontainer]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md#codespaces--devcontainer-development-environment
|
97
docs/docs/segment-plastic.md
Normal file
97
docs/docs/segment-plastic.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
id: plastic
|
||||
title: Plastic SCM
|
||||
sidebar_label: Plastic SCM
|
||||
---
|
||||
|
||||
## What
|
||||
|
||||
Display Plastic SCM information when in a plastic repository. Also works for subfolders.
|
||||
For maximum compatibility, make sure your `cm` executable is up-to-date
|
||||
(when branch or status information is incorrect for example).
|
||||
|
||||
Local changes can also be displayed which uses the following syntax (see `.Status` property below):
|
||||
|
||||
- `+` added
|
||||
- `~` modified
|
||||
- `-` deleted
|
||||
- `>` moved
|
||||
- `x` unmerged
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "plastic",
|
||||
"style": "powerline",
|
||||
"powerline_symbol": "\uE0B0",
|
||||
"foreground": "#193549",
|
||||
"background": "#ffeb3b",
|
||||
"background_templates": [
|
||||
"{{ if .MergePending }}#006060{{ end }}",
|
||||
"{{ if .Changed }}#FF9248{{ end }}",
|
||||
"{{ if and .Changed .Behind }}#ff4500{{ end }}",
|
||||
"{{ if .Behind }}#B388FF{{ end }}"
|
||||
],
|
||||
"properties": {
|
||||
"fetch_status": true,
|
||||
"branch_max_length": 25,
|
||||
"truncate_symbol": "\u2026",
|
||||
"template": "{{ .Selector }}{{ if .Status.Changed }} \uF044 {{ end }}{{ .Status.String }}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Plastic SCM Icon
|
||||
|
||||
If you want to use the icon of Plastic SCM in the segment, then please help me push the icon in this [issue][fa-issue]
|
||||
by leaving a like!
|
||||
![icon](https://www.plasticscm.com/images/icon-logo-plasticscm.svg)
|
||||
|
||||
## Properties
|
||||
|
||||
- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the
|
||||
properties below - defaults to empty.
|
||||
|
||||
### Fetching information
|
||||
|
||||
As doing multiple `cm` calls can slow down the prompt experience, we do not fetch information by default.
|
||||
You can set the following property to `true` to enable fetching additional information (and populate the template).
|
||||
|
||||
- fetch_status: `boolean` - fetch the local changes - defaults to `false`
|
||||
|
||||
### Icons
|
||||
|
||||
#### Branch
|
||||
|
||||
- branch_icon: `string` - the icon to use in front of the git branch name - defaults to `\uE0A0 `
|
||||
- full_branch_path: `bool` - display the full branch path: */main/fix-001* instead of *fix-001* - defaults to `true`
|
||||
- branch_max_length: `int` - the max length for the displayed branch name where `0` implies full length - defaults to `0`
|
||||
- truncate_symbol: `string` - the icon to display when a branch name is truncated - defaults to empty
|
||||
|
||||
#### Selector
|
||||
|
||||
- commit_icon: `string` - icon/text to display before the commit context (detached HEAD) - defaults to `\uF417`
|
||||
- tag_icon: `string` - icon/text to display before the tag context - defaults to `\uF412`
|
||||
|
||||
## Template Properties
|
||||
|
||||
- `.Selector`: `string` - the current selector context (branch/changeset/label)
|
||||
- `.Behind`: `bool` - the current workspace is behind and changes are incoming
|
||||
- `.Status`: `PlasticStatus` - changes in the workspace (see below)
|
||||
- `.MergePending`: `bool` - if a merge is pending and needs to be commited
|
||||
(kown issue: when no file is left after a *Change/Delete conflict* merge, the `MergePending` property is not set)
|
||||
|
||||
### PlasticStatus
|
||||
|
||||
- `.Unmerged`: `int` - number of unmerged changes
|
||||
- `.Deleted`: `int` - number of deleted changes
|
||||
- `.Added`: `int` - number of added changes
|
||||
- `.Modified`: `int` - number of modified changes
|
||||
- `.Moved`: `int` - number of moved changes
|
||||
- `.Changed`: `boolean` - if the status contains changes or not
|
||||
- `.String`: `string` - a string representation of the changes above
|
||||
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
[sprig]: https://masterminds.github.io/sprig/
|
||||
[fa-issue]: https://github.com/FortAwesome/Font-Awesome/issues/18504
|
|
@ -61,6 +61,7 @@ module.exports = {
|
|||
"owm",
|
||||
"path",
|
||||
"php",
|
||||
"plastic",
|
||||
"python",
|
||||
"root",
|
||||
"ruby",
|
||||
|
@ -85,6 +86,7 @@ module.exports = {
|
|||
"contributing_started",
|
||||
"contributing_segment",
|
||||
"contributing_git",
|
||||
"contributing_plastic",
|
||||
],
|
||||
},
|
||||
"themes",
|
||||
|
|
81
src/scm.go
Normal file
81
src/scm.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ScmStatus represents part of the status of a repository
|
||||
type ScmStatus struct {
|
||||
Unmerged int
|
||||
Deleted int
|
||||
Added int
|
||||
Modified int
|
||||
Moved int
|
||||
}
|
||||
|
||||
func (s *ScmStatus) Changed() bool {
|
||||
return s.Added > 0 || s.Deleted > 0 || s.Modified > 0 || s.Unmerged > 0 || s.Moved > 0
|
||||
}
|
||||
|
||||
func (s *ScmStatus) String() string {
|
||||
var status string
|
||||
stringIfValue := func(value int, prefix string) string {
|
||||
if value > 0 {
|
||||
return fmt.Sprintf(" %s%d", prefix, value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
status += stringIfValue(s.Added, "+")
|
||||
status += stringIfValue(s.Modified, "~")
|
||||
status += stringIfValue(s.Deleted, "-")
|
||||
status += stringIfValue(s.Moved, ">")
|
||||
status += stringIfValue(s.Unmerged, "x")
|
||||
return strings.TrimSpace(status)
|
||||
}
|
||||
|
||||
type scm struct {
|
||||
props properties
|
||||
env environmentInfo
|
||||
}
|
||||
|
||||
const (
|
||||
// BranchMaxLength truncates the length of the branch name
|
||||
BranchMaxLength Property = "branch_max_length"
|
||||
// TruncateSymbol appends the set symbol to a truncated branch name
|
||||
TruncateSymbol Property = "truncate_symbol"
|
||||
// FullBranchPath displays the full path of a branch
|
||||
FullBranchPath Property = "full_branch_path"
|
||||
)
|
||||
|
||||
func (s *scm) init(props properties, env environmentInfo) {
|
||||
s.props = props
|
||||
s.env = env
|
||||
}
|
||||
|
||||
func (s *scm) truncateBranch(branch string) string {
|
||||
fullBranchPath := s.props.getBool(FullBranchPath, true)
|
||||
maxLength := s.props.getInt(BranchMaxLength, 0)
|
||||
if !fullBranchPath && len(branch) > 0 {
|
||||
index := strings.LastIndex(branch, "/")
|
||||
branch = branch[index+1:]
|
||||
}
|
||||
if maxLength == 0 || len(branch) <= maxLength {
|
||||
return branch
|
||||
}
|
||||
symbol := s.props.getString(TruncateSymbol, "")
|
||||
return branch[0:maxLength] + symbol
|
||||
}
|
||||
|
||||
func (s *scm) shouldIgnoreRootRepository(rootDir string) bool {
|
||||
value, ok := s.props[ExcludeFolders]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
excludedFolders := parseStringArray(value)
|
||||
return dirMatchesOneOf(s.env, rootDir, excludedFolders)
|
||||
}
|
||||
|
||||
func (s *scm) getFileContents(folder, file string) string {
|
||||
return strings.Trim(s.env.getFileContent(folder+"/"+file), " \r\n")
|
||||
}
|
186
src/scm_test.go
Normal file
186
src/scm_test.go
Normal file
|
@ -0,0 +1,186 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScmStatusChanged(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected bool
|
||||
Status ScmStatus
|
||||
}{
|
||||
{
|
||||
Case: "No changes",
|
||||
Expected: false,
|
||||
Status: ScmStatus{},
|
||||
},
|
||||
{
|
||||
Case: "Added",
|
||||
Expected: true,
|
||||
Status: ScmStatus{
|
||||
Added: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "Moved",
|
||||
Expected: true,
|
||||
Status: ScmStatus{
|
||||
Moved: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "Modified",
|
||||
Expected: true,
|
||||
Status: ScmStatus{
|
||||
Modified: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "Deleted",
|
||||
Expected: true,
|
||||
Status: ScmStatus{
|
||||
Deleted: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "Unmerged",
|
||||
Expected: true,
|
||||
Status: ScmStatus{
|
||||
Unmerged: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
assert.Equal(t, tc.Expected, tc.Status.Changed(), tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScmStatusUnmerged(t *testing.T) {
|
||||
expected := "x1"
|
||||
status := &ScmStatus{
|
||||
Unmerged: 1,
|
||||
}
|
||||
assert.Equal(t, expected, status.String())
|
||||
}
|
||||
|
||||
func TestScmStatusUnmergedModified(t *testing.T) {
|
||||
expected := "~3 x1"
|
||||
status := &ScmStatus{
|
||||
Unmerged: 1,
|
||||
Modified: 3,
|
||||
}
|
||||
assert.Equal(t, expected, status.String())
|
||||
}
|
||||
|
||||
func TestScmStatusEmpty(t *testing.T) {
|
||||
expected := ""
|
||||
status := &ScmStatus{}
|
||||
assert.Equal(t, expected, status.String())
|
||||
}
|
||||
|
||||
func TestTruncateBranch(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected string
|
||||
Branch string
|
||||
FullBranch bool
|
||||
MaxLength interface{}
|
||||
}{
|
||||
{Case: "No limit", Expected: "are-belong-to-us", Branch: "/all-your-base/are-belong-to-us", FullBranch: false},
|
||||
{Case: "No limit - larger", Expected: "are-belong", Branch: "/all-your-base/are-belong-to-us", FullBranch: false, MaxLength: 10.0},
|
||||
{Case: "No limit - smaller", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: 13.0},
|
||||
{Case: "Invalid setting", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: "burp"},
|
||||
{Case: "Lower than limit", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: 20.0},
|
||||
|
||||
{Case: "No limit - full branch", Expected: "/all-your-base/are-belong-to-us", Branch: "/all-your-base/are-belong-to-us", FullBranch: true},
|
||||
{Case: "No limit - larger - full branch", Expected: "/all-your-base", Branch: "/all-your-base/are-belong-to-us", FullBranch: true, MaxLength: 14.0},
|
||||
{Case: "No limit - smaller - full branch ", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: 14.0},
|
||||
{Case: "Invalid setting - full branch", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: "burp"},
|
||||
{Case: "Lower than limit - full branch", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: 20.0},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
var props properties = map[Property]interface{}{
|
||||
BranchMaxLength: tc.MaxLength,
|
||||
FullBranchPath: tc.FullBranch,
|
||||
}
|
||||
p := &plastic{
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, tc.Expected, p.truncateBranch(tc.Branch), tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateBranchWithSymbol(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected string
|
||||
Branch string
|
||||
FullBranch bool
|
||||
MaxLength interface{}
|
||||
TruncateSymbol interface{}
|
||||
}{
|
||||
{Case: "No limit", Expected: "are-belong-to-us", Branch: "/all-your-base/are-belong-to-us", FullBranch: false, TruncateSymbol: "..."},
|
||||
{Case: "No limit - larger", Expected: "are-belong...", Branch: "/all-your-base/are-belong-to-us", FullBranch: false, MaxLength: 10.0, TruncateSymbol: "..."},
|
||||
{Case: "No limit - smaller", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: 13.0, TruncateSymbol: "..."},
|
||||
{Case: "Invalid setting", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: "burp", TruncateSymbol: "..."},
|
||||
{Case: "Lower than limit", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: 20.0, TruncateSymbol: "..."},
|
||||
|
||||
{Case: "No limit - full branch", Expected: "/all-your-base/are-belong-to-us", Branch: "/all-your-base/are-belong-to-us", FullBranch: true, TruncateSymbol: "..."},
|
||||
{Case: "No limit - larger - full branch", Expected: "/all-your-base...", Branch: "/all-your-base/are-belong-to-us", FullBranch: true, MaxLength: 14.0, TruncateSymbol: "..."},
|
||||
{Case: "No limit - smaller - full branch ", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: 14.0, TruncateSymbol: "..."},
|
||||
{Case: "Invalid setting - full branch", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: "burp", TruncateSymbol: "..."},
|
||||
{Case: "Lower than limit - full branch", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: 20.0, TruncateSymbol: "..."},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
var props properties = map[Property]interface{}{
|
||||
BranchMaxLength: tc.MaxLength,
|
||||
TruncateSymbol: tc.TruncateSymbol,
|
||||
FullBranchPath: tc.FullBranch,
|
||||
}
|
||||
p := &plastic{
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, tc.Expected, p.truncateBranch(tc.Branch), tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScmShouldIgnoreRootRepository(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Dir string
|
||||
Expected bool
|
||||
}{
|
||||
{Case: "inside excluded", Dir: "/home/bill/repo"},
|
||||
{Case: "oustide excluded", Dir: "/home/melinda"},
|
||||
{Case: "excluded exact match", Dir: "/home/gates", Expected: true},
|
||||
{Case: "excluded inside match", Dir: "/home/gates/bill", Expected: true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
var props properties = map[Property]interface{}{
|
||||
ExcludeFolders: []string{
|
||||
"/home/bill",
|
||||
"/home/gates.*",
|
||||
},
|
||||
}
|
||||
env := new(MockedEnvironment)
|
||||
env.On("homeDir", nil).Return("/home/bill")
|
||||
env.On("getRuntimeGOOS", nil).Return(windowsPlatform)
|
||||
s := &scm{
|
||||
props: props,
|
||||
env: env,
|
||||
}
|
||||
got := s.shouldIgnoreRootRepository(tc.Dir)
|
||||
assert.Equal(t, tc.Expected, got, tc.Case)
|
||||
}
|
||||
}
|
|
@ -57,6 +57,8 @@ const (
|
|||
Path SegmentType = "path"
|
||||
// Git represents the git status and information
|
||||
Git SegmentType = "git"
|
||||
// Plastic represents the plastic scm status and information
|
||||
Plastic SegmentType = "plastic"
|
||||
// Exit writes the last exit code
|
||||
Exit SegmentType = "exit"
|
||||
// Python writes the virtual env name
|
||||
|
@ -230,6 +232,7 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
|
|||
Session: &session{},
|
||||
Path: &path{},
|
||||
Git: &git{},
|
||||
Plastic: &plastic{},
|
||||
Exit: &exit{},
|
||||
Python: &python{},
|
||||
Root: &root{},
|
||||
|
|
|
@ -13,7 +13,9 @@ import (
|
|||
func TestGetStatusDetailStringDefault(t *testing.T) {
|
||||
expected := "icon +1"
|
||||
status := &GitStatus{
|
||||
Added: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 1,
|
||||
},
|
||||
}
|
||||
g := &git{}
|
||||
assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon"))
|
||||
|
@ -22,13 +24,17 @@ func TestGetStatusDetailStringDefault(t *testing.T) {
|
|||
func TestGetStatusDetailStringDefaultColorOverride(t *testing.T) {
|
||||
expected := "<#123456>icon +1</>"
|
||||
status := &GitStatus{
|
||||
Added: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 1,
|
||||
},
|
||||
}
|
||||
var props properties = map[Property]interface{}{
|
||||
WorkingColor: "#123456",
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon"))
|
||||
}
|
||||
|
@ -36,14 +42,18 @@ func TestGetStatusDetailStringDefaultColorOverride(t *testing.T) {
|
|||
func TestGetStatusDetailStringDefaultColorOverrideAndIconColorOverride(t *testing.T) {
|
||||
expected := "<#789123>work</> <#123456>+1</>"
|
||||
status := &GitStatus{
|
||||
Added: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 1,
|
||||
},
|
||||
}
|
||||
var props properties = map[Property]interface{}{
|
||||
WorkingColor: "#123456",
|
||||
LocalWorkingIcon: "<#789123>work</>",
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon"))
|
||||
}
|
||||
|
@ -51,14 +61,18 @@ func TestGetStatusDetailStringDefaultColorOverrideAndIconColorOverride(t *testin
|
|||
func TestGetStatusDetailStringDefaultColorOverrideNoIconColorOverride(t *testing.T) {
|
||||
expected := "<#123456>work +1</>"
|
||||
status := &GitStatus{
|
||||
Added: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 1,
|
||||
},
|
||||
}
|
||||
var props properties = map[Property]interface{}{
|
||||
WorkingColor: "#123456",
|
||||
LocalWorkingIcon: "work",
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon"))
|
||||
}
|
||||
|
@ -66,13 +80,17 @@ func TestGetStatusDetailStringDefaultColorOverrideNoIconColorOverride(t *testing
|
|||
func TestGetStatusDetailStringNoStatus(t *testing.T) {
|
||||
expected := "icon"
|
||||
status := &GitStatus{
|
||||
Added: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 1,
|
||||
},
|
||||
}
|
||||
var props properties = map[Property]interface{}{
|
||||
DisplayStatusDetail: false,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon"))
|
||||
}
|
||||
|
@ -80,14 +98,18 @@ func TestGetStatusDetailStringNoStatus(t *testing.T) {
|
|||
func TestGetStatusDetailStringNoStatusColorOverride(t *testing.T) {
|
||||
expected := "<#123456>icon</>"
|
||||
status := &GitStatus{
|
||||
Added: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 1,
|
||||
},
|
||||
}
|
||||
var props properties = map[Property]interface{}{
|
||||
DisplayStatusDetail: false,
|
||||
WorkingColor: "#123456",
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon"))
|
||||
}
|
||||
|
@ -98,9 +120,13 @@ func TestGetStatusColorLocalChangesStaging(t *testing.T) {
|
|||
LocalChangesColor: expected,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Staging: &GitStatus{
|
||||
Modified: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Modified: 1,
|
||||
},
|
||||
},
|
||||
Working: &GitStatus{},
|
||||
}
|
||||
|
@ -113,10 +139,14 @@ func TestGetStatusColorLocalChangesWorking(t *testing.T) {
|
|||
LocalChangesColor: expected,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Staging: &GitStatus{},
|
||||
Working: &GitStatus{
|
||||
Modified: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Modified: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, g.getStatusColor("#fg1111"))
|
||||
|
@ -128,7 +158,9 @@ func TestGetStatusColorAheadAndBehind(t *testing.T) {
|
|||
AheadAndBehindColor: expected,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Staging: &GitStatus{},
|
||||
Working: &GitStatus{},
|
||||
Ahead: 1,
|
||||
|
@ -143,7 +175,9 @@ func TestGetStatusColorAhead(t *testing.T) {
|
|||
AheadColor: expected,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Staging: &GitStatus{},
|
||||
Working: &GitStatus{},
|
||||
Ahead: 1,
|
||||
|
@ -158,7 +192,9 @@ func TestGetStatusColorBehind(t *testing.T) {
|
|||
BehindColor: expected,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Staging: &GitStatus{},
|
||||
Working: &GitStatus{},
|
||||
Ahead: 0,
|
||||
|
@ -173,7 +209,9 @@ func TestGetStatusColorDefault(t *testing.T) {
|
|||
BehindColor: changesColor,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Staging: &GitStatus{},
|
||||
Working: &GitStatus{},
|
||||
Ahead: 0,
|
||||
|
@ -189,9 +227,13 @@ func TestSetStatusColorForeground(t *testing.T) {
|
|||
ColorBackground: false,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Staging: &GitStatus{
|
||||
Added: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 1,
|
||||
},
|
||||
},
|
||||
Working: &GitStatus{},
|
||||
}
|
||||
|
@ -206,10 +248,14 @@ func TestSetStatusColorBackground(t *testing.T) {
|
|||
ColorBackground: true,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Staging: &GitStatus{},
|
||||
Working: &GitStatus{
|
||||
Modified: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Modified: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
g.SetStatusColor()
|
||||
|
@ -234,13 +280,15 @@ func TestStatusColorsWithoutDisplayStatus(t *testing.T) {
|
|||
env.mockGitCommand("", "describe", "--tags", "--exact-match")
|
||||
env.mockGitCommand(status, "status", "-unormal", "--branch", "--porcelain=2")
|
||||
g := &git{
|
||||
env: env,
|
||||
gitWorkingFolder: "",
|
||||
props: map[Property]interface{}{
|
||||
DisplayStatus: false,
|
||||
StatusColorsEnabled: true,
|
||||
LocalChangesColor: expected,
|
||||
scm: scm{
|
||||
env: env,
|
||||
props: map[Property]interface{}{
|
||||
DisplayStatus: false,
|
||||
StatusColorsEnabled: true,
|
||||
LocalChangesColor: expected,
|
||||
},
|
||||
},
|
||||
gitWorkingFolder: "",
|
||||
}
|
||||
g.Working = &GitStatus{}
|
||||
g.Staging = &GitStatus{}
|
||||
|
|
|
@ -10,10 +10,7 @@ import (
|
|||
|
||||
// GitStatus represents part of the status of a git repository
|
||||
type GitStatus struct {
|
||||
Unmerged int
|
||||
Deleted int
|
||||
Added int
|
||||
Modified int
|
||||
ScmStatus
|
||||
}
|
||||
|
||||
func (s *GitStatus) add(code string) {
|
||||
|
@ -31,28 +28,8 @@ func (s *GitStatus) add(code string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *GitStatus) Changed() bool {
|
||||
return s.Added > 0 || s.Deleted > 0 || s.Modified > 0 || s.Unmerged > 0
|
||||
}
|
||||
|
||||
func (s *GitStatus) String() string {
|
||||
var status string
|
||||
stringIfValue := func(value int, prefix string) string {
|
||||
if value > 0 {
|
||||
return fmt.Sprintf(" %s%d", prefix, value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
status += stringIfValue(s.Added, "+")
|
||||
status += stringIfValue(s.Modified, "~")
|
||||
status += stringIfValue(s.Deleted, "-")
|
||||
status += stringIfValue(s.Unmerged, "x")
|
||||
return strings.TrimSpace(status)
|
||||
}
|
||||
|
||||
type git struct {
|
||||
props properties
|
||||
env environmentInfo
|
||||
scm
|
||||
|
||||
Working *GitStatus
|
||||
Staging *GitStatus
|
||||
|
@ -85,10 +62,6 @@ const (
|
|||
// FetchUpstreamIcon fetches the upstream icon
|
||||
FetchUpstreamIcon Property = "fetch_upstream_icon"
|
||||
|
||||
// BranchMaxLength truncates the length of the branch name
|
||||
BranchMaxLength Property = "branch_max_length"
|
||||
// TruncateSymbol appends the set symbol to a truncated branch name
|
||||
TruncateSymbol Property = "truncate_symbol"
|
||||
// BranchIcon the icon to use as branch indicator
|
||||
BranchIcon Property = "branch_icon"
|
||||
// BranchIdenticalIcon the icon to display when the remote and local branch are identical
|
||||
|
@ -163,15 +136,6 @@ func (g *git) enabled() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (g *git) shouldIgnoreRootRepository(rootDir string) bool {
|
||||
value, ok := g.props[ExcludeFolders]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
excludedFolders := parseStringArray(value)
|
||||
return dirMatchesOneOf(g.env, rootDir, excludedFolders)
|
||||
}
|
||||
|
||||
func (g *git) string() string {
|
||||
statusColorsEnabled := g.props.getBool(StatusColorsEnabled, false)
|
||||
displayStatus := g.props.getOneOfBool(FetchStatus, DisplayStatus, false)
|
||||
|
@ -218,11 +182,6 @@ func (g *git) templateString(segmentTemplate string) string {
|
|||
return text
|
||||
}
|
||||
|
||||
func (g *git) init(props properties, env environmentInfo) {
|
||||
g.props = props
|
||||
g.env = env
|
||||
}
|
||||
|
||||
func (g *git) setBranchStatus() {
|
||||
getBranchStatus := func() string {
|
||||
if g.Ahead > 0 && g.Behind > 0 {
|
||||
|
@ -352,7 +311,7 @@ func (g *git) setGitHEADContext() {
|
|||
|
||||
getPrettyNameOrigin := func(file string) string {
|
||||
var origin string
|
||||
head := g.getGitFileContents(g.gitWorkingFolder, file)
|
||||
head := g.getFileContents(g.gitWorkingFolder, file)
|
||||
if head == "detached HEAD" {
|
||||
origin = formatDetached()
|
||||
} else {
|
||||
|
@ -366,16 +325,16 @@ func (g *git) setGitHEADContext() {
|
|||
origin := getPrettyNameOrigin("rebase-merge/head-name")
|
||||
onto := g.getGitRefFileSymbolicName("rebase-merge/onto")
|
||||
onto = g.formatHEAD(onto)
|
||||
step := g.getGitFileContents(g.gitWorkingFolder, "rebase-merge/msgnum")
|
||||
total := g.getGitFileContents(g.gitWorkingFolder, "rebase-merge/end")
|
||||
step := g.getFileContents(g.gitWorkingFolder, "rebase-merge/msgnum")
|
||||
total := g.getFileContents(g.gitWorkingFolder, "rebase-merge/end")
|
||||
icon := g.props.getString(RebaseIcon, "\uE728 ")
|
||||
g.HEAD = fmt.Sprintf("%s%s onto %s%s (%s/%s) at %s", icon, origin, branchIcon, onto, step, total, g.HEAD)
|
||||
return
|
||||
}
|
||||
if g.env.hasFolder(g.gitWorkingFolder + "/rebase-apply") {
|
||||
origin := getPrettyNameOrigin("rebase-apply/head-name")
|
||||
step := g.getGitFileContents(g.gitWorkingFolder, "rebase-apply/next")
|
||||
total := g.getGitFileContents(g.gitWorkingFolder, "rebase-apply/last")
|
||||
step := g.getFileContents(g.gitWorkingFolder, "rebase-apply/next")
|
||||
total := g.getFileContents(g.gitWorkingFolder, "rebase-apply/last")
|
||||
icon := g.props.getString(RebaseIcon, "\uE728 ")
|
||||
g.HEAD = fmt.Sprintf("%s%s (%s/%s) at %s", icon, origin, step, total, g.HEAD)
|
||||
return
|
||||
|
@ -384,7 +343,7 @@ func (g *git) setGitHEADContext() {
|
|||
commitIcon := g.props.getString(CommitIcon, "\uF417")
|
||||
if g.hasGitFile("MERGE_MSG") {
|
||||
icon := g.props.getString(MergeIcon, "\uE727 ")
|
||||
mergeContext := g.getGitFileContents(g.gitWorkingFolder, "MERGE_MSG")
|
||||
mergeContext := g.getFileContents(g.gitWorkingFolder, "MERGE_MSG")
|
||||
matches := findNamedRegexMatch(`Merge (remote-tracking )?(?P<type>branch|commit|tag) '(?P<theirs>.*)'`, mergeContext)
|
||||
// head := g.getGitRefFileSymbolicName("ORIG_HEAD")
|
||||
if matches != nil && matches["theirs"] != "" {
|
||||
|
@ -410,19 +369,19 @@ func (g *git) setGitHEADContext() {
|
|||
// reverts then CHERRY_PICK_HEAD/REVERT_HEAD will not exist so we have to read
|
||||
// the todo file.
|
||||
if g.hasGitFile("CHERRY_PICK_HEAD") {
|
||||
sha := g.getGitFileContents(g.gitWorkingFolder, "CHERRY_PICK_HEAD")
|
||||
sha := g.getFileContents(g.gitWorkingFolder, "CHERRY_PICK_HEAD")
|
||||
cherry := g.props.getString(CherryPickIcon, "\uE29B ")
|
||||
g.HEAD = fmt.Sprintf("%s%s%s onto %s", cherry, commitIcon, g.formatSHA(sha), formatDetached())
|
||||
return
|
||||
}
|
||||
if g.hasGitFile("REVERT_HEAD") {
|
||||
sha := g.getGitFileContents(g.gitWorkingFolder, "REVERT_HEAD")
|
||||
sha := g.getFileContents(g.gitWorkingFolder, "REVERT_HEAD")
|
||||
revert := g.props.getString(RevertIcon, "\uF0E2 ")
|
||||
g.HEAD = fmt.Sprintf("%s%s%s onto %s", revert, commitIcon, g.formatSHA(sha), formatDetached())
|
||||
return
|
||||
}
|
||||
if g.hasGitFile("sequencer/todo") {
|
||||
todo := g.getGitFileContents(g.gitWorkingFolder, "sequencer/todo")
|
||||
todo := g.getFileContents(g.gitWorkingFolder, "sequencer/todo")
|
||||
matches := findNamedRegexMatch(`^(?P<action>p|pick|revert)\s+(?P<sha>\S+)`, todo)
|
||||
if matches != nil && matches["sha"] != "" {
|
||||
action := matches["action"]
|
||||
|
@ -462,19 +421,15 @@ func (g *git) hasGitFile(file string) bool {
|
|||
return g.env.hasFilesInDir(g.gitWorkingFolder, file)
|
||||
}
|
||||
|
||||
func (g *git) getGitFileContents(folder, file string) string {
|
||||
return strings.Trim(g.env.getFileContent(folder+"/"+file), " \r\n")
|
||||
}
|
||||
|
||||
func (g *git) getGitRefFileSymbolicName(refFile string) string {
|
||||
ref := g.getGitFileContents(g.gitWorkingFolder, refFile)
|
||||
ref := g.getFileContents(g.gitWorkingFolder, refFile)
|
||||
return g.getGitCommandOutput("name-rev", "--name-only", "--exclude=tags/*", ref)
|
||||
}
|
||||
|
||||
func (g *git) setPrettyHEADName() {
|
||||
// we didn't fetch status, fallback to parsing the HEAD file
|
||||
if len(g.Hash) == 0 {
|
||||
HEADRef := g.getGitFileContents(g.gitWorkingFolder, "HEAD")
|
||||
HEADRef := g.getFileContents(g.gitWorkingFolder, "HEAD")
|
||||
if strings.HasPrefix(HEADRef, BRANCHPREFIX) {
|
||||
branchName := strings.TrimPrefix(HEADRef, BRANCHPREFIX)
|
||||
g.HEAD = fmt.Sprintf("%s%s", g.props.getString(BranchIcon, "\uE0A0"), g.formatHEAD(branchName))
|
||||
|
@ -500,7 +455,7 @@ func (g *git) setPrettyHEADName() {
|
|||
}
|
||||
|
||||
func (g *git) getStashContext() int {
|
||||
stashContent := g.getGitFileContents(g.gitRootFolder, "logs/refs/stash")
|
||||
stashContent := g.getFileContents(g.gitRootFolder, "logs/refs/stash")
|
||||
if stashContent == "" {
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ func TestEnabledGitNotFound(t *testing.T) {
|
|||
env.On("getRuntimeGOOS", nil).Return("")
|
||||
env.On("isWsl", nil).Return(false)
|
||||
g := &git{
|
||||
env: env,
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
assert.False(t, g.enabled())
|
||||
}
|
||||
|
@ -36,7 +38,9 @@ func TestEnabledInWorkingDirectory(t *testing.T) {
|
|||
}
|
||||
env.On("hasParentFilePath", ".git").Return(fileInfo, nil)
|
||||
g := &git{
|
||||
env: env,
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
assert.True(t, g.enabled())
|
||||
assert.Equal(t, fileInfo.path, g.gitWorkingFolder)
|
||||
|
@ -56,7 +60,9 @@ func TestEnabledInWorkingTree(t *testing.T) {
|
|||
env.On("getFileContent", "/dev/folder_worktree/.git").Return("gitdir: /dev/real_folder/.git/worktrees/folder_worktree")
|
||||
env.On("getFileContent", "/dev/real_folder/.git/worktrees/folder_worktree/gitdir").Return("/dev/folder_worktree.git\n")
|
||||
g := &git{
|
||||
env: env,
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
assert.True(t, g.enabled())
|
||||
assert.Equal(t, "/dev/real_folder/.git/worktrees/folder_worktree", g.gitWorkingFolder)
|
||||
|
@ -72,7 +78,9 @@ func TestGetGitOutputForCommand(t *testing.T) {
|
|||
env.On("runCommand", "git", append(args, commandArgs...)).Return(want, nil)
|
||||
env.On("getRuntimeGOOS", nil).Return("unix")
|
||||
g := &git{
|
||||
env: env,
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
got := g.getGitCommandOutput(commandArgs...)
|
||||
assert.Equal(t, want, got)
|
||||
|
@ -217,15 +225,17 @@ func TestSetGitHEADContextClean(t *testing.T) {
|
|||
env.On("getFileContent", "/sequencer/todo").Return(tc.Theirs)
|
||||
|
||||
g := &git{
|
||||
env: env,
|
||||
props: map[Property]interface{}{
|
||||
BranchIcon: "branch ",
|
||||
CommitIcon: "commit ",
|
||||
RebaseIcon: "rebase ",
|
||||
MergeIcon: "merge ",
|
||||
CherryPickIcon: "pick ",
|
||||
TagIcon: "tag ",
|
||||
RevertIcon: "revert ",
|
||||
scm: scm{
|
||||
env: env,
|
||||
props: map[Property]interface{}{
|
||||
BranchIcon: "branch ",
|
||||
CommitIcon: "commit ",
|
||||
RebaseIcon: "rebase ",
|
||||
MergeIcon: "merge ",
|
||||
CherryPickIcon: "pick ",
|
||||
TagIcon: "tag ",
|
||||
RevertIcon: "revert ",
|
||||
},
|
||||
},
|
||||
Hash: "1234567",
|
||||
Ref: tc.Ref,
|
||||
|
@ -257,11 +267,13 @@ func TestSetPrettyHEADName(t *testing.T) {
|
|||
env.On("isWsl", nil).Return(false)
|
||||
env.mockGitCommand(tc.Tag, "describe", "--tags", "--exact-match")
|
||||
g := &git{
|
||||
env: env,
|
||||
props: map[Property]interface{}{
|
||||
BranchIcon: "branch ",
|
||||
CommitIcon: "commit ",
|
||||
TagIcon: "tag ",
|
||||
scm: scm{
|
||||
env: env,
|
||||
props: map[Property]interface{}{
|
||||
BranchIcon: "branch ",
|
||||
CommitIcon: "commit ",
|
||||
TagIcon: "tag ",
|
||||
},
|
||||
},
|
||||
Hash: tc.Hash,
|
||||
}
|
||||
|
@ -297,8 +309,8 @@ func TestSetGitStatus(t *testing.T) {
|
|||
1 .U N...
|
||||
1 A. N...
|
||||
`,
|
||||
ExpectedWorking: &GitStatus{Modified: 4, Added: 2, Deleted: 1, Unmerged: 1},
|
||||
ExpectedStaging: &GitStatus{Added: 1},
|
||||
ExpectedWorking: &GitStatus{ScmStatus: ScmStatus{Modified: 4, Added: 2, Deleted: 1, Unmerged: 1}},
|
||||
ExpectedStaging: &GitStatus{ScmStatus: ScmStatus{Added: 1}},
|
||||
ExpectedHash: "1234567",
|
||||
ExpectedRef: "rework-git-status",
|
||||
},
|
||||
|
@ -318,8 +330,8 @@ func TestSetGitStatus(t *testing.T) {
|
|||
1 .U N...
|
||||
1 A. N...
|
||||
`,
|
||||
ExpectedWorking: &GitStatus{Modified: 4, Added: 2, Deleted: 1, Unmerged: 1},
|
||||
ExpectedStaging: &GitStatus{Added: 1},
|
||||
ExpectedWorking: &GitStatus{ScmStatus: ScmStatus{Modified: 4, Added: 2, Deleted: 1, Unmerged: 1}},
|
||||
ExpectedStaging: &GitStatus{ScmStatus: ScmStatus{Added: 1}},
|
||||
ExpectedUpstream: "origin/rework-git-status",
|
||||
ExpectedHash: "1234567",
|
||||
ExpectedRef: "rework-git-status",
|
||||
|
@ -356,7 +368,9 @@ func TestSetGitStatus(t *testing.T) {
|
|||
env.On("isWsl", nil).Return(false)
|
||||
env.mockGitCommand(strings.ReplaceAll(tc.Output, "\t", ""), "status", "-unormal", "--branch", "--porcelain=2")
|
||||
g := &git{
|
||||
env: env,
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
if tc.ExpectedWorking == nil {
|
||||
tc.ExpectedWorking = &GitStatus{}
|
||||
|
@ -388,7 +402,9 @@ func TestGetStashContextZeroEntries(t *testing.T) {
|
|||
env := new(MockedEnvironment)
|
||||
env.On("getFileContent", "/logs/refs/stash").Return(tc.StashContent)
|
||||
g := &git{
|
||||
env: env,
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
gitWorkingFolder: "",
|
||||
}
|
||||
got := g.getStashContext()
|
||||
|
@ -396,29 +412,6 @@ func TestGetStashContextZeroEntries(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGitStatusUnmerged(t *testing.T) {
|
||||
expected := "x1"
|
||||
status := &GitStatus{
|
||||
Unmerged: 1,
|
||||
}
|
||||
assert.Equal(t, expected, status.String())
|
||||
}
|
||||
|
||||
func TestGitStatusUnmergedModified(t *testing.T) {
|
||||
expected := "~3 x1"
|
||||
status := &GitStatus{
|
||||
Unmerged: 1,
|
||||
Modified: 3,
|
||||
}
|
||||
assert.Equal(t, expected, status.String())
|
||||
}
|
||||
|
||||
func TestGitStatusEmpty(t *testing.T) {
|
||||
expected := ""
|
||||
status := &GitStatus{}
|
||||
assert.Equal(t, expected, status.String())
|
||||
}
|
||||
|
||||
func TestGitUpstream(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
|
@ -446,8 +439,10 @@ func TestGitUpstream(t *testing.T) {
|
|||
GitIcon: "G",
|
||||
}
|
||||
g := &git{
|
||||
env: env,
|
||||
props: props,
|
||||
scm: scm{
|
||||
env: env,
|
||||
props: props,
|
||||
},
|
||||
Upstream: "origin/main",
|
||||
}
|
||||
upstreamIcon := g.getUpstreamIcon()
|
||||
|
@ -479,7 +474,9 @@ func TestGetBranchStatus(t *testing.T) {
|
|||
BranchGoneIcon: "gone",
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
scm: scm{
|
||||
props: props,
|
||||
},
|
||||
Ahead: tc.Ahead,
|
||||
Behind: tc.Behind,
|
||||
Upstream: tc.Upstream,
|
||||
|
@ -512,66 +509,16 @@ func TestShouldIgnoreRootRepository(t *testing.T) {
|
|||
env.On("homeDir", nil).Return("/home/bill")
|
||||
env.On("getRuntimeGOOS", nil).Return(windowsPlatform)
|
||||
git := &git{
|
||||
props: props,
|
||||
env: env,
|
||||
scm: scm{
|
||||
props: props,
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
got := git.shouldIgnoreRootRepository(tc.Dir)
|
||||
assert.Equal(t, tc.Expected, got, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateBranch(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected string
|
||||
Branch string
|
||||
MaxLength interface{}
|
||||
}{
|
||||
{Case: "No limit", Expected: "all-your-base-are-belong-to-us", Branch: "all-your-base-are-belong-to-us"},
|
||||
{Case: "No limit - larger", Expected: "all-your-base", Branch: "all-your-base-are-belong-to-us", MaxLength: 13.0},
|
||||
{Case: "No limit - smaller", Expected: "all-your-base", Branch: "all-your-base", MaxLength: 13.0},
|
||||
{Case: "Invalid setting", Expected: "all-your-base", Branch: "all-your-base", MaxLength: "burp"},
|
||||
{Case: "Lower than limit", Expected: "all-your-base", Branch: "all-your-base", MaxLength: 20.0},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
var props properties = map[Property]interface{}{
|
||||
BranchMaxLength: tc.MaxLength,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
}
|
||||
assert.Equal(t, tc.Expected, g.formatHEAD(tc.Branch), tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateBranchWithSymbol(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected string
|
||||
Branch string
|
||||
MaxLength interface{}
|
||||
TruncateSymbol interface{}
|
||||
}{
|
||||
{Case: "No limit", Expected: "all-your-base-are-belong-to-us", Branch: "all-your-base-are-belong-to-us", TruncateSymbol: "..."},
|
||||
{Case: "No limit - larger", Expected: "all-your-base...", Branch: "all-your-base-are-belong-to-us", MaxLength: 13.0, TruncateSymbol: "..."},
|
||||
{Case: "No limit - smaller", Expected: "all-your-base", Branch: "all-your-base", MaxLength: 16.0, TruncateSymbol: "..."},
|
||||
{Case: "Invalid setting", Expected: "all-your-base", Branch: "all-your-base", MaxLength: "burp", TruncateSymbol: "..."},
|
||||
{Case: "Lower than limit", Expected: "all-your-base", Branch: "all-your-base", MaxLength: 20.0, TruncateSymbol: "..."},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
var props properties = map[Property]interface{}{
|
||||
BranchMaxLength: tc.MaxLength,
|
||||
TruncateSymbol: tc.TruncateSymbol,
|
||||
}
|
||||
g := &git{
|
||||
props: props,
|
||||
}
|
||||
assert.Equal(t, tc.Expected, g.formatHEAD(tc.Branch), tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGitCommand(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
|
@ -599,7 +546,9 @@ func TestGetGitCommand(t *testing.T) {
|
|||
}
|
||||
env.On("runCommand", "uname", []string{"-r"}).Return(wslUname, nil)
|
||||
g := &git{
|
||||
env: env,
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, tc.Expected, g.getGitCommand(), tc.Case)
|
||||
}
|
||||
|
@ -628,8 +577,10 @@ func TestGitTemplateString(t *testing.T) {
|
|||
Git: &git{
|
||||
HEAD: branchName,
|
||||
Working: &GitStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -649,12 +600,16 @@ func TestGitTemplateString(t *testing.T) {
|
|||
Git: &git{
|
||||
HEAD: branchName,
|
||||
Working: &GitStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
},
|
||||
},
|
||||
Staging: &GitStatus{
|
||||
Added: 5,
|
||||
Modified: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 5,
|
||||
Modified: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -665,12 +620,16 @@ func TestGitTemplateString(t *testing.T) {
|
|||
Git: &git{
|
||||
HEAD: branchName,
|
||||
Working: &GitStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
},
|
||||
},
|
||||
Staging: &GitStatus{
|
||||
Added: 5,
|
||||
Modified: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 5,
|
||||
Modified: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -681,12 +640,16 @@ func TestGitTemplateString(t *testing.T) {
|
|||
Git: &git{
|
||||
HEAD: branchName,
|
||||
Working: &GitStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
},
|
||||
},
|
||||
Staging: &GitStatus{
|
||||
Added: 5,
|
||||
Modified: 1,
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 5,
|
||||
Modified: 1,
|
||||
},
|
||||
},
|
||||
StashCount: 3,
|
||||
},
|
||||
|
|
192
src/segment_plastic.go
Normal file
192
src/segment_plastic.go
Normal file
|
@ -0,0 +1,192 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PlasticStatus struct {
|
||||
ScmStatus
|
||||
}
|
||||
|
||||
func (s *PlasticStatus) add(code string) {
|
||||
switch code {
|
||||
case "LD":
|
||||
s.Deleted++
|
||||
case "AD", "PR":
|
||||
s.Added++
|
||||
case "LM":
|
||||
s.Moved++
|
||||
case "CH", "CO":
|
||||
s.Modified++
|
||||
}
|
||||
}
|
||||
|
||||
type plastic struct {
|
||||
scm
|
||||
|
||||
Status *PlasticStatus
|
||||
Behind bool
|
||||
Selector string
|
||||
MergePending bool
|
||||
|
||||
plasticWorkspaceFolder string // root folder of workspace
|
||||
}
|
||||
|
||||
func (p *plastic) init(props properties, env environmentInfo) {
|
||||
p.props = props
|
||||
p.env = env
|
||||
}
|
||||
|
||||
func (p *plastic) enabled() bool {
|
||||
if !p.env.hasCommand("cm") {
|
||||
return false
|
||||
}
|
||||
wkdir, err := p.env.hasParentFilePath(".plastic")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if p.shouldIgnoreRootRepository(wkdir.parentFolder) {
|
||||
return false
|
||||
}
|
||||
|
||||
if wkdir.isDir {
|
||||
p.plasticWorkspaceFolder = wkdir.parentFolder
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *plastic) string() string {
|
||||
displayStatus := p.props.getOneOfBool(FetchStatus, DisplayStatus, false)
|
||||
|
||||
p.setSelector()
|
||||
if displayStatus {
|
||||
p.setPlasticStatus()
|
||||
}
|
||||
|
||||
// use template if available
|
||||
segmentTemplate := p.props.getString(SegmentTemplate, "")
|
||||
if len(segmentTemplate) > 0 {
|
||||
return p.templateString(segmentTemplate)
|
||||
}
|
||||
|
||||
// default: only selector is returned
|
||||
return p.Selector
|
||||
}
|
||||
|
||||
func (p *plastic) templateString(segmentTemplate string) string {
|
||||
template := &textTemplate{
|
||||
Template: segmentTemplate,
|
||||
Context: p,
|
||||
Env: p.env,
|
||||
}
|
||||
text, err := template.render()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func (p *plastic) setPlasticStatus() {
|
||||
output := p.getCmCommandOutput("status", "--all", "--machinereadable")
|
||||
splittedOutput := strings.Split(output, "\n")
|
||||
// compare to head
|
||||
currentChangeset := p.parseStatusChangeset(splittedOutput[0])
|
||||
headChangeset := p.getHeadChangeset()
|
||||
p.Behind = headChangeset > currentChangeset
|
||||
|
||||
// parse file state
|
||||
p.MergePending = false
|
||||
p.Status = &PlasticStatus{}
|
||||
p.parseFilesStatus(splittedOutput)
|
||||
}
|
||||
|
||||
func (p *plastic) parseFilesStatus(output []string) {
|
||||
if len(output) <= 1 {
|
||||
return
|
||||
}
|
||||
for _, line := range output[1:] {
|
||||
if len(line) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(line, "NO_MERGES") {
|
||||
p.Status.Unmerged++
|
||||
continue
|
||||
}
|
||||
|
||||
p.MergePending = p.MergePending || matchString(`(?i)\smerge\s+from\s+[0-9]+\s*$`, line)
|
||||
|
||||
code := line[:2]
|
||||
p.Status.add(code)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *plastic) parseStringPattern(output, pattern, name string) string {
|
||||
match := findNamedRegexMatch(pattern, output)
|
||||
if sValue, ok := match[name]; ok {
|
||||
return sValue
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *plastic) parseIntPattern(output, pattern, name string, defValue int) int {
|
||||
sValue := p.parseStringPattern(output, pattern, name)
|
||||
if len(sValue) > 0 {
|
||||
iValue, _ := strconv.Atoi(sValue)
|
||||
return iValue
|
||||
}
|
||||
return defValue
|
||||
}
|
||||
|
||||
func (p *plastic) parseStatusChangeset(status string) int {
|
||||
return p.parseIntPattern(status, `STATUS\s+(?P<cs>[0-9]+?)\s`, "cs", 0)
|
||||
}
|
||||
|
||||
func (p *plastic) getHeadChangeset() int {
|
||||
output := p.getCmCommandOutput("status", "--head", "--machinereadable")
|
||||
return p.parseIntPattern(output, `\bcs:(?P<cs>[0-9]+?)\s`, "cs", 0)
|
||||
}
|
||||
|
||||
func (p *plastic) setSelector() {
|
||||
var ref string
|
||||
selector := p.getFileContents(p.plasticWorkspaceFolder+"/.plastic/", "plastic.selector")
|
||||
// changeset
|
||||
ref = p.parseChangesetSelector(selector)
|
||||
if len(ref) > 0 {
|
||||
p.Selector = fmt.Sprintf("%s%s", p.props.getString(CommitIcon, "\uF417"), ref)
|
||||
return
|
||||
}
|
||||
// fallback to label
|
||||
ref = p.parseLabelSelector(selector)
|
||||
if len(ref) > 0 {
|
||||
p.Selector = fmt.Sprintf("%s%s", p.props.getString(TagIcon, "\uF412"), ref)
|
||||
return
|
||||
}
|
||||
// fallback to branch/smartbranch
|
||||
ref = p.parseBranchSelector(selector)
|
||||
if len(ref) > 0 {
|
||||
ref = p.truncateBranch(ref)
|
||||
}
|
||||
p.Selector = fmt.Sprintf("%s%s", p.props.getString(BranchIcon, "\uE0A0"), ref)
|
||||
}
|
||||
|
||||
func (p *plastic) parseChangesetSelector(selector string) string {
|
||||
return p.parseStringPattern(selector, `\bchangeset "(?P<cs>[0-9]+?)"`, "cs")
|
||||
}
|
||||
|
||||
func (p *plastic) parseLabelSelector(selector string) string {
|
||||
return p.parseStringPattern(selector, `label "(?P<label>[a-zA-Z0-9\-\_]+?)"`, "label")
|
||||
}
|
||||
|
||||
func (p *plastic) parseBranchSelector(selector string) string {
|
||||
return p.parseStringPattern(selector, `branch "(?P<branch>[\/a-zA-Z0-9\-\_]+?)"`, "branch")
|
||||
}
|
||||
|
||||
func (p *plastic) getCmCommandOutput(args ...string) string {
|
||||
val, _ := p.env.runCommand("cm", args...)
|
||||
return val
|
||||
}
|
330
src/segment_plastic_test.go
Normal file
330
src/segment_plastic_test.go
Normal file
|
@ -0,0 +1,330 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPlasticEnabledNotFound(t *testing.T) {
|
||||
env := new(MockedEnvironment)
|
||||
env.On("hasCommand", "cm").Return(false)
|
||||
env.On("getRuntimeGOOS", nil).Return("")
|
||||
env.On("isWsl", nil).Return(false)
|
||||
p := &plastic{
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
assert.False(t, p.enabled())
|
||||
}
|
||||
|
||||
func TestPlasticEnabledInWorkspaceDirectory(t *testing.T) {
|
||||
env := new(MockedEnvironment)
|
||||
env.On("hasCommand", "cm").Return(true)
|
||||
env.On("getRuntimeGOOS", nil).Return("")
|
||||
env.On("isWsl", nil).Return(false)
|
||||
fileInfo := &fileInfo{
|
||||
path: "/dir/hello",
|
||||
parentFolder: "/dir",
|
||||
isDir: true,
|
||||
}
|
||||
env.On("hasParentFilePath", ".plastic").Return(fileInfo, nil)
|
||||
p := &plastic{
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
assert.True(t, p.enabled())
|
||||
assert.Equal(t, fileInfo.parentFolder, p.plasticWorkspaceFolder)
|
||||
}
|
||||
|
||||
func setupCmStatusEnv(status, headStatus string) *plastic {
|
||||
env := new(MockedEnvironment)
|
||||
env.On("runCommand", "cm", []string{"status", "--all", "--machinereadable"}).Return(status, nil)
|
||||
env.On("runCommand", "cm", []string{"status", "--head", "--machinereadable"}).Return(headStatus, nil)
|
||||
p := &plastic{
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func TestPlasticGetCmOutputForCommand(t *testing.T) {
|
||||
want := "je suis le output"
|
||||
p := setupCmStatusEnv(want, "")
|
||||
got := p.getCmCommandOutput("status", "--all", "--machinereadable")
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestPlasticStatusBehind(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected bool
|
||||
Status string
|
||||
Head string
|
||||
}{
|
||||
{
|
||||
Case: "Not behind",
|
||||
Expected: false,
|
||||
Status: "STATUS 20 default localhost:8087",
|
||||
Head: "STATUS cs:20 rep:default repserver:localhost:8087",
|
||||
},
|
||||
{
|
||||
Case: "Behind",
|
||||
Expected: true,
|
||||
Status: "STATUS 2 default localhost:8087",
|
||||
Head: "STATUS cs:20 rep:default repserver:localhost:8087",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
p := setupCmStatusEnv(tc.Status, tc.Head)
|
||||
p.setPlasticStatus()
|
||||
assert.Equal(t, tc.Expected, p.Behind, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlasticStatusChanged(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected bool
|
||||
Status string
|
||||
}{
|
||||
{
|
||||
Case: "No changes",
|
||||
Expected: false,
|
||||
Status: "STATUS 1 default localhost:8087",
|
||||
},
|
||||
{
|
||||
Case: "Changed file",
|
||||
Expected: true,
|
||||
Status: "STATUS 1 default localhost:8087\r\nCH /some.file",
|
||||
},
|
||||
{
|
||||
Case: "Added file",
|
||||
Expected: true,
|
||||
Status: "STATUS 1 default localhost:8087\r\nAD /some.file",
|
||||
},
|
||||
{
|
||||
Case: "Added (pivate) file",
|
||||
Expected: true,
|
||||
Status: "STATUS 1 default localhost:8087\r\nPR /some.file",
|
||||
},
|
||||
{
|
||||
Case: "Moved file",
|
||||
Expected: true,
|
||||
Status: "STATUS 1 default localhost:8087\r\nLM /some.file",
|
||||
},
|
||||
{
|
||||
Case: "Deleted file",
|
||||
Expected: true,
|
||||
Status: "STATUS 1 default localhost:8087\r\nLD /some.file",
|
||||
},
|
||||
{
|
||||
Case: "Unmerged file",
|
||||
Expected: true,
|
||||
Status: "STATUS 1 default localhost:8087\r\nCO /some.file NO_MERGES",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
p := setupCmStatusEnv(tc.Status, "")
|
||||
p.setPlasticStatus()
|
||||
assert.Equal(t, tc.Expected, p.Status.Changed(), tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlasticStatusCounts(t *testing.T) {
|
||||
status := "STATUS 1 default localhost:8087" +
|
||||
"\r\nCO /some.file NO_MERGES" +
|
||||
"\r\nAD /some.file" +
|
||||
"\r\nCH /some.file\r\nCH /some.file" +
|
||||
"\r\nLD /some.file\r\nLD /some.file\r\nLD /some.file" +
|
||||
"\r\nLM /some.file\r\nLM /some.file\r\nLM /some.file\r\nLM /some.file"
|
||||
p := setupCmStatusEnv(status, "")
|
||||
p.setPlasticStatus()
|
||||
s := p.Status
|
||||
assert.Equal(t, 1, s.Unmerged)
|
||||
assert.Equal(t, 1, s.Added)
|
||||
assert.Equal(t, 2, s.Modified)
|
||||
assert.Equal(t, 3, s.Deleted)
|
||||
assert.Equal(t, 4, s.Moved)
|
||||
}
|
||||
|
||||
func TestPlasticMergePending(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected bool
|
||||
Status string
|
||||
}{
|
||||
{
|
||||
Case: "No pending merge",
|
||||
Expected: false,
|
||||
Status: "STATUS 1 default localhost:8087",
|
||||
},
|
||||
{
|
||||
Case: "Pending merge",
|
||||
Expected: true,
|
||||
Status: "STATUS 1 default localhost:8087\r\nCH /some.file merge from 8",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
p := setupCmStatusEnv(tc.Status, "")
|
||||
p.setPlasticStatus()
|
||||
assert.Equal(t, tc.Expected, p.MergePending, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlasticParseIntPattern(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected int
|
||||
Text string
|
||||
Pattern string
|
||||
Name string
|
||||
Default int
|
||||
}{
|
||||
{
|
||||
Case: "int found",
|
||||
Expected: 123,
|
||||
Text: "Some number 123 in text",
|
||||
Pattern: `\s(?P<x>[0-9]+?)\s`,
|
||||
Name: "x",
|
||||
Default: 0,
|
||||
},
|
||||
{
|
||||
Case: "int not found",
|
||||
Expected: 0,
|
||||
Text: "No number in text",
|
||||
Pattern: `\s(?P<x>[0-9]+?)\s`,
|
||||
Name: "x",
|
||||
Default: 0,
|
||||
},
|
||||
{
|
||||
Case: "empty text",
|
||||
Expected: 0,
|
||||
Text: "",
|
||||
Pattern: `\s(?P<x>[0-9]+?)\s`,
|
||||
Name: "x",
|
||||
Default: 0,
|
||||
},
|
||||
}
|
||||
|
||||
p := &plastic{}
|
||||
for _, tc := range cases {
|
||||
value := p.parseIntPattern(tc.Text, tc.Pattern, tc.Name, tc.Default)
|
||||
assert.Equal(t, tc.Expected, value, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlasticParseStatusChangeset(t *testing.T) {
|
||||
p := &plastic{}
|
||||
cs := p.parseStatusChangeset("STATUS 321 default localhost:8087")
|
||||
assert.Equal(t, 321, cs)
|
||||
}
|
||||
|
||||
func TestPlasticGetHeadChangeset(t *testing.T) {
|
||||
head := "STATUS cs:321 rep:default repserver:localhost:8087"
|
||||
p := setupCmStatusEnv("", head)
|
||||
cs := p.getHeadChangeset()
|
||||
assert.Equal(t, 321, cs)
|
||||
}
|
||||
|
||||
func TestPlasticParseChangesetSelector(t *testing.T) {
|
||||
content := "repository \"default\"\r\n path \"/\"\r\n smartbranch \"/main\" changeset \"321\""
|
||||
p := &plastic{}
|
||||
selector := p.parseChangesetSelector(content)
|
||||
assert.Equal(t, "321", selector)
|
||||
}
|
||||
|
||||
func TestPlasticParseLabelSelector(t *testing.T) {
|
||||
content := "repository \"default\"\r\n path \"/\"\r\n label \"BL003\""
|
||||
p := &plastic{}
|
||||
selector := p.parseLabelSelector(content)
|
||||
assert.Equal(t, "BL003", selector)
|
||||
}
|
||||
|
||||
func TestPlasticParseBranchSelector(t *testing.T) {
|
||||
content := "repository \"default\"\r\n path \"/\"\r\n branch \"/main/fix-004\""
|
||||
p := &plastic{}
|
||||
selector := p.parseBranchSelector(content)
|
||||
assert.Equal(t, "/main/fix-004", selector)
|
||||
}
|
||||
|
||||
func TestPlasticParseSmartbranchSelector(t *testing.T) {
|
||||
content := "repository \"default\"\r\n path \"/\"\r\n smartbranch \"/main/fix-002\""
|
||||
p := &plastic{}
|
||||
selector := p.parseBranchSelector(content)
|
||||
assert.Equal(t, "/main/fix-002", selector)
|
||||
}
|
||||
|
||||
func TestPlasticStatus(t *testing.T) {
|
||||
p := &plastic{
|
||||
Status: &PlasticStatus{
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 1,
|
||||
Modified: 2,
|
||||
Deleted: 3,
|
||||
Moved: 4,
|
||||
Unmerged: 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
status := p.Status.String()
|
||||
expected := "+1 ~2 -3 >4 x5"
|
||||
assert.Equal(t, expected, status)
|
||||
}
|
||||
|
||||
func TestPlasticTemplateString(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected string
|
||||
Template string
|
||||
Plastic *plastic
|
||||
}{
|
||||
{
|
||||
Case: "Only Selector name",
|
||||
Expected: "/main",
|
||||
Template: "{{ .Selector }}",
|
||||
Plastic: &plastic{
|
||||
Selector: "/main",
|
||||
Behind: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "Workspace changes",
|
||||
Expected: "/main \uF044 +2 ~3 -1 >4",
|
||||
Template: "{{ .Selector }}{{ if .Status.Changed }} \uF044 {{ .Status.String }}{{ end }}",
|
||||
Plastic: &plastic{
|
||||
Selector: "/main",
|
||||
Status: &PlasticStatus{
|
||||
ScmStatus: ScmStatus{
|
||||
Added: 2,
|
||||
Modified: 3,
|
||||
Deleted: 1,
|
||||
Moved: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "No workspace changes",
|
||||
Expected: "/main",
|
||||
Template: "{{ .Selector }}{{ if .Status.Changed }} \uF044 {{ .Status.String }}{{ end }}",
|
||||
Plastic: &plastic{
|
||||
Selector: "/main",
|
||||
Status: &PlasticStatus{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
var props properties = map[Property]interface{}{
|
||||
FetchStatus: true,
|
||||
}
|
||||
tc.Plastic.props = props
|
||||
assert.Equal(t, tc.Expected, tc.Plastic.templateString(tc.Template), tc.Case)
|
||||
}
|
||||
}
|
|
@ -182,7 +182,8 @@
|
|||
"angular",
|
||||
"php",
|
||||
"wifi",
|
||||
"winreg"
|
||||
"winreg",
|
||||
"plastic"
|
||||
]
|
||||
},
|
||||
"style": {
|
||||
|
@ -1705,6 +1706,62 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": { "const": "plastic" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"title": "Plastic SCM Segment",
|
||||
"description": "https://ohmyposh.dev/docs/plastic",
|
||||
"properties": {
|
||||
"properties": {
|
||||
"properties": {
|
||||
"template": {
|
||||
"$ref": "#/definitions/template"
|
||||
},
|
||||
"fetch_status": {
|
||||
"type": "boolean",
|
||||
"title": "Display Status",
|
||||
"description": "Display the local changes or not",
|
||||
"default": false
|
||||
},
|
||||
"branch_icon": {
|
||||
"type": "string",
|
||||
"title": "Branch Icon",
|
||||
"description": "The icon to use in front of the selector branch name",
|
||||
"default": "\uE0A0 "
|
||||
},
|
||||
"commit_icon": {
|
||||
"type": "string",
|
||||
"title": "Commit Icon",
|
||||
"description": "Icon/text to display before the selector changeset",
|
||||
"default": "\uF417"
|
||||
},
|
||||
"tag_icon": {
|
||||
"type": "string",
|
||||
"title": "Tag Icon",
|
||||
"description": "Icon/text to display before the seletor label",
|
||||
"default": "\uF412"
|
||||
},
|
||||
"branch_max_length": {
|
||||
"type": "integer",
|
||||
"title": "Branch max length",
|
||||
"description": "the max length for the displayed branch name where 0 implies full length",
|
||||
"default": 0
|
||||
},
|
||||
"full_branch_path": {
|
||||
"type": "boolean",
|
||||
"title": "Full branch path",
|
||||
"description": "display the full branch path instead of only the branch name",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue