mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-03-05 20:49:04 -08:00
feat(git): ignore repo based on exclude_folders
This commit is contained in:
parent
37659aab70
commit
cdc2998ed8
|
@ -72,11 +72,10 @@ func (b *Block) setStringValues() {
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(len(b.Segments))
|
wg.Add(len(b.Segments))
|
||||||
defer wg.Wait()
|
defer wg.Wait()
|
||||||
cwd := b.env.getcwd()
|
|
||||||
for _, segment := range b.Segments {
|
for _, segment := range b.Segments {
|
||||||
go func(s *Segment) {
|
go func(s *Segment) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
s.setStringValue(b.env, cwd)
|
s.setStringValue(b.env)
|
||||||
}(segment)
|
}(segment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +183,7 @@ func (b *Block) debug() (int, []*SegmentTiming) {
|
||||||
largestSegmentNameLength := 0
|
largestSegmentNameLength := 0
|
||||||
for _, segment := range b.Segments {
|
for _, segment := range b.Segments {
|
||||||
err := segment.mapSegmentWithWriter(b.env)
|
err := segment.mapSegmentWithWriter(b.env)
|
||||||
if err != nil || !segment.shouldIncludeFolder(b.env.getcwd()) {
|
if err != nil || !segment.shouldIncludeFolder() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var segmentTiming SegmentTiming
|
var segmentTiming SegmentTiming
|
||||||
|
|
24
src/regex.go
24
src/regex.go
|
@ -1,7 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -77,3 +79,25 @@ func matchString(pattern, text string) bool {
|
||||||
re := getCompiledRegex(pattern)
|
re := getCompiledRegex(pattern)
|
||||||
return re.MatchString(text)
|
return re.MatchString(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dirMatchesOneOf(env environmentInfo, dir string, regexes []string) bool {
|
||||||
|
normalizedCwd := strings.ReplaceAll(dir, "\\", "/")
|
||||||
|
normalizedHomeDir := strings.ReplaceAll(env.homeDir(), "\\", "/")
|
||||||
|
|
||||||
|
for _, element := range regexes {
|
||||||
|
normalizedElement := strings.ReplaceAll(element, "\\\\", "/")
|
||||||
|
if strings.HasPrefix(normalizedElement, "~") {
|
||||||
|
normalizedElement = normalizedHomeDir + normalizedElement[1:]
|
||||||
|
}
|
||||||
|
pattern := fmt.Sprintf("^%s$", normalizedElement)
|
||||||
|
goos := env.getRuntimeGOOS()
|
||||||
|
if goos == windowsPlatform || goos == darwinPlatform {
|
||||||
|
pattern = "(?i)" + pattern
|
||||||
|
}
|
||||||
|
matched := matchString(pattern, normalizedCwd)
|
||||||
|
if matched {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
40
src/regex_test.go
Normal file
40
src/regex_test.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDirMatchesOneOf(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
GOOS string
|
||||||
|
HomeDir string
|
||||||
|
Dir string
|
||||||
|
Pattern string
|
||||||
|
Expected bool
|
||||||
|
}{
|
||||||
|
{GOOS: linuxPlatform, HomeDir: "/home/bill", Dir: "/home/bill", Pattern: "/home/bill", Expected: true},
|
||||||
|
{GOOS: linuxPlatform, HomeDir: "/home/bill", Dir: "/home/bill/foo", Pattern: "~/foo", Expected: true},
|
||||||
|
{GOOS: linuxPlatform, HomeDir: "/home/bill", Dir: "/home/bill/foo", Pattern: "~/Foo", Expected: false},
|
||||||
|
{GOOS: linuxPlatform, HomeDir: "/home/bill", Dir: "/home/bill/foo", Pattern: "~\\\\foo", Expected: true},
|
||||||
|
{GOOS: linuxPlatform, HomeDir: "/home/bill", Dir: "/home/bill/foo/bar", Pattern: "~/fo.*", Expected: true},
|
||||||
|
{GOOS: linuxPlatform, HomeDir: "/home/bill", Dir: "/home/bill/foo", Pattern: "~/fo\\w", Expected: true},
|
||||||
|
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill", Pattern: "C:\\\\Users\\\\Bill", Expected: true},
|
||||||
|
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill", Pattern: "C:/Users/Bill", Expected: true},
|
||||||
|
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill", Pattern: "c:/users/bill", Expected: true},
|
||||||
|
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill", Pattern: "~", Expected: true},
|
||||||
|
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill\\Foo", Pattern: "~/Foo", Expected: true},
|
||||||
|
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill\\Foo", Pattern: "~/foo", Expected: true},
|
||||||
|
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill\\Foo\\Bar", Pattern: "~/fo.*", Expected: true},
|
||||||
|
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill\\Foo", Pattern: "~/fo\\w", Expected: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
env := new(MockedEnvironment)
|
||||||
|
env.On("getRuntimeGOOS", nil).Return(tc.GOOS)
|
||||||
|
env.On("homeDir", nil).Return(tc.HomeDir)
|
||||||
|
got := dirMatchesOneOf(env, tc.Dir, []string{tc.Pattern})
|
||||||
|
assert.Equal(t, tc.Expected, got)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -146,13 +145,13 @@ func (segment *Segment) getValue(property Property, defaultValue string) string
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (segment *Segment) shouldIncludeFolder(cwd string) bool {
|
func (segment *Segment) shouldIncludeFolder() bool {
|
||||||
cwdIncluded := segment.cwdIncluded(cwd)
|
cwdIncluded := segment.cwdIncluded()
|
||||||
cwdExcluded := segment.cwdExcluded(cwd)
|
cwdExcluded := segment.cwdExcluded()
|
||||||
return (cwdIncluded && !cwdExcluded)
|
return (cwdIncluded && !cwdExcluded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (segment *Segment) cwdIncluded(cwd string) bool {
|
func (segment *Segment) cwdIncluded() bool {
|
||||||
value, ok := segment.Properties[IncludeFolders]
|
value, ok := segment.Properties[IncludeFolders]
|
||||||
if !ok {
|
if !ok {
|
||||||
// IncludeFolders isn't specified, everything is included
|
// IncludeFolders isn't specified, everything is included
|
||||||
|
@ -166,38 +165,16 @@ func (segment *Segment) cwdIncluded(cwd string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return segment.cwdMatchesOneOf(cwd, list)
|
return dirMatchesOneOf(segment.env, segment.env.getcwd(), list)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (segment *Segment) cwdExcluded(cwd string) bool {
|
func (segment *Segment) cwdExcluded() bool {
|
||||||
value, ok := segment.Properties[ExcludeFolders]
|
value, ok := segment.Properties[ExcludeFolders]
|
||||||
if !ok {
|
if !ok {
|
||||||
value = segment.Properties[IgnoreFolders]
|
value = segment.Properties[IgnoreFolders]
|
||||||
}
|
}
|
||||||
list := parseStringArray(value)
|
list := parseStringArray(value)
|
||||||
return segment.cwdMatchesOneOf(cwd, list)
|
return dirMatchesOneOf(segment.env, segment.env.getcwd(), list)
|
||||||
}
|
|
||||||
|
|
||||||
func (segment *Segment) cwdMatchesOneOf(cwd string, regexes []string) bool {
|
|
||||||
normalizedCwd := strings.ReplaceAll(cwd, "\\", "/")
|
|
||||||
normalizedHomeDir := strings.ReplaceAll(segment.env.homeDir(), "\\", "/")
|
|
||||||
|
|
||||||
for _, element := range regexes {
|
|
||||||
normalizedElement := strings.ReplaceAll(element, "\\\\", "/")
|
|
||||||
if strings.HasPrefix(normalizedElement, "~") {
|
|
||||||
normalizedElement = normalizedHomeDir + normalizedElement[1:]
|
|
||||||
}
|
|
||||||
pattern := fmt.Sprintf("^%s$", normalizedElement)
|
|
||||||
goos := segment.env.getRuntimeGOOS()
|
|
||||||
if goos == windowsPlatform || goos == darwinPlatform {
|
|
||||||
pattern = "(?i)" + pattern
|
|
||||||
}
|
|
||||||
matched := matchString(pattern, normalizedCwd)
|
|
||||||
if matched {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (segment *Segment) getColor(templates []string, defaultColor string) string {
|
func (segment *Segment) getColor(templates []string, defaultColor string) string {
|
||||||
|
@ -297,7 +274,7 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
|
||||||
return errors.New("unable to map writer")
|
return errors.New("unable to map writer")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (segment *Segment) setStringValue(env environmentInfo, cwd string) {
|
func (segment *Segment) setStringValue(env environmentInfo) {
|
||||||
defer func() {
|
defer func() {
|
||||||
err := recover()
|
err := recover()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -310,7 +287,7 @@ func (segment *Segment) setStringValue(env environmentInfo, cwd string) {
|
||||||
segment.active = true
|
segment.active = true
|
||||||
}()
|
}()
|
||||||
err := segment.mapSegmentWithWriter(env)
|
err := segment.mapSegmentWithWriter(env)
|
||||||
if err != nil || !segment.shouldIncludeFolder(cwd) {
|
if err != nil || !segment.shouldIncludeFolder() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if segment.enabled() {
|
if segment.enabled() {
|
||||||
|
|
|
@ -135,6 +135,10 @@ func (g *git) enabled() bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if g.shouldIgnoreRootRepository(gitdir.parentFolder) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
g.repo = &gitRepo{}
|
g.repo = &gitRepo{}
|
||||||
if gitdir.isDir {
|
if gitdir.isDir {
|
||||||
g.repo.gitWorkingFolder = gitdir.path
|
g.repo.gitWorkingFolder = gitdir.path
|
||||||
|
@ -159,6 +163,18 @@ func (g *git) enabled() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *git) shouldIgnoreRootRepository(rootDir string) bool {
|
||||||
|
if g.props == nil || g.props.values == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
value, ok := g.props.values[ExcludeFolders]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
excludedFolders := parseStringArray(value)
|
||||||
|
return dirMatchesOneOf(g.env, rootDir, excludedFolders)
|
||||||
|
}
|
||||||
|
|
||||||
func (g *git) string() string {
|
func (g *git) string() string {
|
||||||
statusColorsEnabled := g.props.getBool(StatusColorsEnabled, false)
|
statusColorsEnabled := g.props.getBool(StatusColorsEnabled, false)
|
||||||
displayStatus := g.props.getBool(DisplayStatus, false)
|
displayStatus := g.props.getBool(DisplayStatus, false)
|
||||||
|
|
|
@ -925,3 +925,36 @@ func TestTruncateBranch(t *testing.T) {
|
||||||
assert.Equal(t, tc.Expected, g.truncateBranch(tc.Branch), tc.Case)
|
assert.Equal(t, tc.Expected, g.truncateBranch(tc.Branch), tc.Case)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldIgnoreRootRepository(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 {
|
||||||
|
props := 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)
|
||||||
|
git := &git{
|
||||||
|
props: &properties{
|
||||||
|
values: props,
|
||||||
|
},
|
||||||
|
env: env,
|
||||||
|
}
|
||||||
|
got := git.shouldIgnoreRootRepository(tc.Dir)
|
||||||
|
assert.Equal(t, tc.Expected, got, tc.Case)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@ func TestShouldIncludeFolder(t *testing.T) {
|
||||||
env := new(MockedEnvironment)
|
env := new(MockedEnvironment)
|
||||||
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
|
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
|
||||||
env.On("homeDir", nil).Return("")
|
env.On("homeDir", nil).Return("")
|
||||||
|
env.On("getcwd", nil).Return(cwd)
|
||||||
segment := &Segment{
|
segment := &Segment{
|
||||||
Properties: map[Property]interface{}{
|
Properties: map[Property]interface{}{
|
||||||
IncludeFolders: tc.IncludeFolders,
|
IncludeFolders: tc.IncludeFolders,
|
||||||
|
@ -91,7 +92,7 @@ func TestShouldIncludeFolder(t *testing.T) {
|
||||||
},
|
},
|
||||||
env: env,
|
env: env,
|
||||||
}
|
}
|
||||||
got := segment.shouldIncludeFolder(cwd)
|
got := segment.shouldIncludeFolder()
|
||||||
assert.Equal(t, tc.Expected, got, tc.Case)
|
assert.Equal(t, tc.Expected, got, tc.Case)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,6 +101,7 @@ func TestShouldIncludeFolderRegexInverted(t *testing.T) {
|
||||||
env := new(MockedEnvironment)
|
env := new(MockedEnvironment)
|
||||||
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
|
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
|
||||||
env.On("homeDir", nil).Return("")
|
env.On("homeDir", nil).Return("")
|
||||||
|
env.On("getcwd", nil).Return(cwd)
|
||||||
segment := &Segment{
|
segment := &Segment{
|
||||||
Properties: map[Property]interface{}{
|
Properties: map[Property]interface{}{
|
||||||
ExcludeFolders: []string{"(?!Projects[\\/]).*"},
|
ExcludeFolders: []string{"(?!Projects[\\/]).*"},
|
||||||
|
@ -113,13 +115,14 @@ func TestShouldIncludeFolderRegexInverted(t *testing.T) {
|
||||||
assert.Equal(t, "regexp: Compile(`^(?!Projects[\\/]).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err)
|
assert.Equal(t, "regexp: Compile(`^(?!Projects[\\/]).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
segment.shouldIncludeFolder(cwd)
|
segment.shouldIncludeFolder()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldIncludeFolderRegexInvertedNonEscaped(t *testing.T) {
|
func TestShouldIncludeFolderRegexInvertedNonEscaped(t *testing.T) {
|
||||||
env := new(MockedEnvironment)
|
env := new(MockedEnvironment)
|
||||||
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
|
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
|
||||||
env.On("homeDir", nil).Return("")
|
env.On("homeDir", nil).Return("")
|
||||||
|
env.On("getcwd", nil).Return(cwd)
|
||||||
segment := &Segment{
|
segment := &Segment{
|
||||||
Properties: map[Property]interface{}{
|
Properties: map[Property]interface{}{
|
||||||
ExcludeFolders: []string{"(?!Projects/).*"},
|
ExcludeFolders: []string{"(?!Projects/).*"},
|
||||||
|
@ -133,7 +136,7 @@ func TestShouldIncludeFolderRegexInvertedNonEscaped(t *testing.T) {
|
||||||
assert.Equal(t, "regexp: Compile(`^(?!Projects/).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err)
|
assert.Equal(t, "regexp: Compile(`^(?!Projects/).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
segment.shouldIncludeFolder(cwd)
|
segment.shouldIncludeFolder()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetColors(t *testing.T) {
|
func TestGetColors(t *testing.T) {
|
||||||
|
@ -208,39 +211,3 @@ func TestGetColors(t *testing.T) {
|
||||||
assert.Equal(t, tc.ExpectedColor, color, tc.Case)
|
assert.Equal(t, tc.ExpectedColor, color, tc.Case)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCwdMatchesOneOf(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
GOOS string
|
|
||||||
HomeDir string
|
|
||||||
Cwd string
|
|
||||||
Pattern string
|
|
||||||
Expected bool
|
|
||||||
}{
|
|
||||||
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill", Pattern: "/home/bill", Expected: true},
|
|
||||||
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/foo", Expected: true},
|
|
||||||
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/Foo", Expected: false},
|
|
||||||
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~\\\\foo", Expected: true},
|
|
||||||
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo/bar", Pattern: "~/fo.*", Expected: true},
|
|
||||||
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/fo\\w", Expected: true},
|
|
||||||
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "C:\\\\Users\\\\Bill", Expected: true},
|
|
||||||
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "C:/Users/Bill", Expected: true},
|
|
||||||
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "c:/users/bill", Expected: true},
|
|
||||||
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "~", Expected: true},
|
|
||||||
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/Foo", Expected: true},
|
|
||||||
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/foo", Expected: true},
|
|
||||||
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo\\Bar", Pattern: "~/fo.*", Expected: true},
|
|
||||||
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/fo\\w", Expected: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
env := new(MockedEnvironment)
|
|
||||||
env.On("getRuntimeGOOS", nil).Return(tc.GOOS)
|
|
||||||
env.On("homeDir", nil).Return(tc.HomeDir)
|
|
||||||
segment := &Segment{
|
|
||||||
env: env,
|
|
||||||
}
|
|
||||||
got := segment.cwdMatchesOneOf(tc.Cwd, []string{tc.Pattern})
|
|
||||||
assert.Equal(t, tc.Expected, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue