git2svn 0.5.2__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {git2svn-0.5.2/git2svn.egg-info → git2svn-0.6.0}/PKG-INFO +1 -1
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/__init__.py +2 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/_version.py +2 -2
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/cli.py +20 -1
- git2svn-0.6.0/git2svn/colors.py +104 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/core.py +45 -28
- {git2svn-0.5.2 → git2svn-0.6.0/git2svn.egg-info}/PKG-INFO +1 -1
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn.egg-info/SOURCES.txt +1 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn.egg-info/scm_file_list.json +1 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/tests/test_git2svn.py +53 -1
- {git2svn-0.5.2 → git2svn-0.6.0}/.githooks/pre-commit +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/.github/dependabot.yml +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/.github/workflows/ci.yml +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/.github/workflows/release.yml +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/.gitignore +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/.pre-commit-config.yaml +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/CONTRIBUTING.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/LICENSE.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/README.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/docs/arc42/README.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/docs/arc42/adrs.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/docs/arc42/concepts.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/docs/req42.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/docs/user-guide/README.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/docs/user-guide/commands.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/docs/user-guide/integration-workflow.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/docs/user-guide/troubleshooting.md +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/__main__.py +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/eol.py +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/git.py +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/patcher.py +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/py.typed +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/state.py +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn/svn.py +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn.egg-info/dependency_links.txt +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn.egg-info/entry_points.txt +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn.egg-info/requires.txt +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn.egg-info/top_level.txt +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/git2svn.py +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/pyproject.toml +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/setup.cfg +0 -0
- {git2svn-0.5.2 → git2svn-0.6.0}/tests/test_e2e.py +0 -0
|
@@ -6,6 +6,7 @@ to a local Subversion (SVN) working copy.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
from .cli import build_parser, main, parse_cli_args
|
|
9
|
+
from .colors import TerminalColor
|
|
9
10
|
from .core import Synchronizer
|
|
10
11
|
from .eol import detect_file_eol, normalize_file_eol
|
|
11
12
|
from .git import FileChange, GitRepo, parse_name_status, parse_name_status_z, parse_ref_arguments
|
|
@@ -43,6 +44,7 @@ __all__ = [
|
|
|
43
44
|
"SvnLockError",
|
|
44
45
|
"SvnOutOfDateError",
|
|
45
46
|
"Patcher",
|
|
47
|
+
"TerminalColor",
|
|
46
48
|
"FileChange",
|
|
47
49
|
"parse_ref_arguments",
|
|
48
50
|
"parse_name_status",
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.6.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 6, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -67,6 +67,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
67
67
|
default=argparse.SUPPRESS,
|
|
68
68
|
help="Enable verbose output",
|
|
69
69
|
)
|
|
70
|
+
common_parser.add_argument(
|
|
71
|
+
"--color",
|
|
72
|
+
choices=["auto", "always", "never"],
|
|
73
|
+
default=argparse.SUPPRESS,
|
|
74
|
+
help="Control colored output (auto, always, never; default: git config git2svn.color or auto)",
|
|
75
|
+
)
|
|
70
76
|
|
|
71
77
|
parser = argparse.ArgumentParser(
|
|
72
78
|
prog="git2svn",
|
|
@@ -435,8 +441,21 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
435
441
|
print(f"Error: '{svn_dir}' does not appear to be an SVN working copy (no .svn found).", file=sys.stderr)
|
|
436
442
|
return 1
|
|
437
443
|
|
|
444
|
+
# Color mode: CLI --color -> git config git2svn.color -> "auto"
|
|
445
|
+
color_mode = getattr(args, "color", None)
|
|
446
|
+
if not color_mode:
|
|
447
|
+
config_color = git_repo.get_config("git2svn.color")
|
|
448
|
+
color_mode = config_color if config_color in ("auto", "always", "never") else "auto"
|
|
449
|
+
|
|
438
450
|
patcher = Patcher(svn_dir, dry_run=dry_run)
|
|
439
|
-
sync_mgr = Synchronizer(
|
|
451
|
+
sync_mgr = Synchronizer(
|
|
452
|
+
git_repo,
|
|
453
|
+
svn_workspace,
|
|
454
|
+
patcher,
|
|
455
|
+
dry_run=dry_run,
|
|
456
|
+
auto_update=auto_update,
|
|
457
|
+
color_mode=color_mode,
|
|
458
|
+
)
|
|
440
459
|
|
|
441
460
|
# Fallback to git2svn.defaultRange if ref1 is omitted
|
|
442
461
|
ref1 = getattr(args, "ref1", None)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TerminalColor:
|
|
8
|
+
"""ANSI terminal color formatting with auto/always/never detection and NO_COLOR support."""
|
|
9
|
+
|
|
10
|
+
# ANSI Styles & Colors
|
|
11
|
+
RESET = "\033[0m"
|
|
12
|
+
BOLD = "\033[1m"
|
|
13
|
+
DIM = "\033[2m"
|
|
14
|
+
|
|
15
|
+
RED = "\033[31m"
|
|
16
|
+
GREEN = "\033[32m"
|
|
17
|
+
YELLOW = "\033[33m"
|
|
18
|
+
BLUE = "\033[34m"
|
|
19
|
+
MAGENTA = "\033[35m"
|
|
20
|
+
CYAN = "\033[36m"
|
|
21
|
+
GRAY = "\033[90m"
|
|
22
|
+
|
|
23
|
+
BOLD_RED = "\033[1;31m"
|
|
24
|
+
BOLD_GREEN = "\033[1;32m"
|
|
25
|
+
BOLD_YELLOW = "\033[1;33m"
|
|
26
|
+
BOLD_CYAN = "\033[1;36m"
|
|
27
|
+
|
|
28
|
+
def __init__(self, mode: str = "auto"):
|
|
29
|
+
self.mode = mode.lower()
|
|
30
|
+
self.enabled = self._resolve_enabled()
|
|
31
|
+
|
|
32
|
+
def _resolve_enabled(self) -> bool:
|
|
33
|
+
if self.mode == "always":
|
|
34
|
+
return True
|
|
35
|
+
if self.mode == "never":
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
# Mode is "auto"
|
|
39
|
+
# 1. Respect NO_COLOR standard (https://no-color.org/)
|
|
40
|
+
if "NO_COLOR" in os.environ and os.environ["NO_COLOR"] != "":
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
# 2. Check for dumb terminals
|
|
44
|
+
if os.environ.get("TERM") == "dumb":
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
# 3. Check if stdout is an interactive TTY
|
|
48
|
+
try:
|
|
49
|
+
return sys.stdout.isatty()
|
|
50
|
+
except Exception:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
def colorize(self, text: str, ansi_code: str) -> str:
|
|
54
|
+
"""Wrap text in ANSI color escape codes if coloring is enabled."""
|
|
55
|
+
if not self.enabled:
|
|
56
|
+
return text
|
|
57
|
+
return f"{ansi_code}{text}{self.RESET}"
|
|
58
|
+
|
|
59
|
+
# Style helpers
|
|
60
|
+
def bold(self, text: str) -> str:
|
|
61
|
+
return self.colorize(text, self.BOLD)
|
|
62
|
+
|
|
63
|
+
def dim(self, text: str) -> str:
|
|
64
|
+
return self.colorize(text, self.DIM)
|
|
65
|
+
|
|
66
|
+
def red(self, text: str) -> str:
|
|
67
|
+
return self.colorize(text, self.RED)
|
|
68
|
+
|
|
69
|
+
def green(self, text: str) -> str:
|
|
70
|
+
return self.colorize(text, self.GREEN)
|
|
71
|
+
|
|
72
|
+
def yellow(self, text: str) -> str:
|
|
73
|
+
return self.colorize(text, self.YELLOW)
|
|
74
|
+
|
|
75
|
+
def cyan(self, text: str) -> str:
|
|
76
|
+
return self.colorize(text, self.CYAN)
|
|
77
|
+
|
|
78
|
+
def gray(self, text: str) -> str:
|
|
79
|
+
return self.colorize(text, self.GRAY)
|
|
80
|
+
|
|
81
|
+
def bold_red(self, text: str) -> str:
|
|
82
|
+
return self.colorize(text, self.BOLD_RED)
|
|
83
|
+
|
|
84
|
+
def bold_green(self, text: str) -> str:
|
|
85
|
+
return self.colorize(text, self.BOLD_GREEN)
|
|
86
|
+
|
|
87
|
+
def bold_yellow(self, text: str) -> str:
|
|
88
|
+
return self.colorize(text, self.BOLD_YELLOW)
|
|
89
|
+
|
|
90
|
+
def bold_cyan(self, text: str) -> str:
|
|
91
|
+
return self.colorize(text, self.BOLD_CYAN)
|
|
92
|
+
|
|
93
|
+
# Semantic badges
|
|
94
|
+
def ok(self, text: str = "OK") -> str:
|
|
95
|
+
return self.bold_green(f"✔ {text}")
|
|
96
|
+
|
|
97
|
+
def clean(self, text: str = "Clean") -> str:
|
|
98
|
+
return self.bold_green(f"✔ {text}")
|
|
99
|
+
|
|
100
|
+
def warn(self, text: str = "Warning") -> str:
|
|
101
|
+
return self.bold_yellow(f"⚠ {text}")
|
|
102
|
+
|
|
103
|
+
def error(self, text: str = "Error") -> str:
|
|
104
|
+
return self.bold_red(f"✖ {text}")
|
|
@@ -7,6 +7,7 @@ import sys
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Dict, List, Optional
|
|
9
9
|
|
|
10
|
+
from .colors import TerminalColor
|
|
10
11
|
from .eol import detect_file_eol, normalize_file_eol
|
|
11
12
|
from .git import FileChange, GitRepo, parse_ref_arguments
|
|
12
13
|
from .patcher import Patcher
|
|
@@ -32,12 +33,14 @@ class Synchronizer:
|
|
|
32
33
|
patcher: Patcher,
|
|
33
34
|
dry_run: bool = False,
|
|
34
35
|
auto_update: bool = False,
|
|
36
|
+
color_mode: str = "auto",
|
|
35
37
|
):
|
|
36
38
|
self.git = git_repo
|
|
37
39
|
self.svn = svn_workspace
|
|
38
40
|
self.patcher = patcher
|
|
39
41
|
self.dry_run = dry_run
|
|
40
42
|
self.auto_update = auto_update
|
|
43
|
+
self.color = TerminalColor(color_mode)
|
|
41
44
|
|
|
42
45
|
def show_identity_banner(self, target_ref_spec: str) -> None:
|
|
43
46
|
"""Display identity banner showing active Git and SVN target branches/URLs."""
|
|
@@ -522,6 +525,7 @@ class Synchronizer:
|
|
|
522
525
|
1 if errors, working copy locked, merge conflicts, or linear violations exist
|
|
523
526
|
"""
|
|
524
527
|
has_error = False
|
|
528
|
+
c = self.color
|
|
525
529
|
|
|
526
530
|
# 1. Check in-progress replay state
|
|
527
531
|
state = load_replay_state(self.svn.workspace_dir)
|
|
@@ -534,59 +538,69 @@ class Synchronizer:
|
|
|
534
538
|
completed = state.get("completed_commits", 0)
|
|
535
539
|
total = state.get("total_commits", len(remaining) + 1)
|
|
536
540
|
|
|
537
|
-
print("[Replay In Progress]")
|
|
538
|
-
print(
|
|
541
|
+
print(c.bold_red("[Replay In Progress]"))
|
|
542
|
+
print(
|
|
543
|
+
f' State : {c.bold_red("PAUSED")} (conflict at commit {c.yellow(current_commit[:8])} "{first_msg}")'
|
|
544
|
+
)
|
|
539
545
|
print(f" Progress : {completed} of {total} commits applied ({len(remaining)} remaining)")
|
|
540
546
|
|
|
541
547
|
rej_files = find_conflict_artifacts(self.svn.workspace_dir)
|
|
542
548
|
if rej_files:
|
|
543
549
|
rel_rejs = [str(r.relative_to(self.svn.workspace_dir)) for r in rej_files]
|
|
544
|
-
print(f" Conflicts : {', '.join(rel_rejs)}")
|
|
545
|
-
|
|
550
|
+
print(f" Conflicts : {c.bold_red(', '.join(rel_rejs))}")
|
|
551
|
+
action_text = (
|
|
552
|
+
f"Resolve conflicts and run {c.bold_cyan('git2svn replay --continue')} (or '--abort' / '--skip')"
|
|
553
|
+
)
|
|
554
|
+
print(f" Action : {action_text}\n")
|
|
546
555
|
|
|
547
556
|
# 2. Git Workspace Status
|
|
548
557
|
git_clean = self.git.is_clean()
|
|
549
558
|
branch = self.git.get_current_branch()
|
|
550
559
|
head_commit = self.git.get_head_commit()
|
|
551
560
|
head_subject = self.git.get_head_subject()
|
|
552
|
-
head_desc = f'{head_commit} "{head_subject}"' if head_subject else head_commit
|
|
561
|
+
head_desc = f'{c.yellow(head_commit)} "{head_subject}"' if head_subject else c.yellow(head_commit)
|
|
553
562
|
|
|
554
|
-
print("[Git Workspace]")
|
|
563
|
+
print(c.bold_cyan("[Git Workspace]"))
|
|
555
564
|
print(f" Repository: {self.git.repo_dir}")
|
|
556
|
-
print(f" Branch : {branch} (at {head_desc})")
|
|
557
|
-
|
|
565
|
+
print(f" Branch : {c.bold(branch)} (at {head_desc})")
|
|
566
|
+
if git_clean:
|
|
567
|
+
print(f" Tree : {c.bold_green('Clean')}")
|
|
568
|
+
else:
|
|
569
|
+
print(f" Tree : {c.bold_yellow('Dirty')} (uncommitted changes present)")
|
|
558
570
|
|
|
559
571
|
# 3. SVN Working Copy Status
|
|
560
|
-
print("\n[SVN Working Copy]")
|
|
572
|
+
print(f"\n{c.bold_cyan('[SVN Working Copy]')}")
|
|
561
573
|
print(f" Path : {self.svn.workspace_dir}")
|
|
562
574
|
svn_info = self.svn.get_info()
|
|
563
575
|
svn_url = svn_info.get("URL") or svn_info.get("Relative URL") or "unknown"
|
|
564
576
|
svn_rev = svn_info.get("Revision")
|
|
565
|
-
rev_str = f" (r{svn_rev})" if svn_rev else ""
|
|
577
|
+
rev_str = f" ({c.dim(f'r{svn_rev}')})" if svn_rev else ""
|
|
566
578
|
print(f" Target : {svn_url}{rev_str}")
|
|
567
579
|
|
|
568
580
|
try:
|
|
569
581
|
svn_clean = self.svn.is_clean()
|
|
570
582
|
if svn_clean:
|
|
571
|
-
print(" Tree : Clean (no uncommitted changes, unlocked)")
|
|
583
|
+
print(f" Tree : {c.bold_green('Clean')} (no uncommitted changes, unlocked)")
|
|
572
584
|
else:
|
|
573
585
|
uncommitted = self.svn.get_status_summary()
|
|
574
|
-
print(f" Tree : Dirty ({len(uncommitted)} uncommitted changes)")
|
|
586
|
+
print(f" Tree : {c.bold_yellow(f'Dirty ({len(uncommitted)} uncommitted changes)')}")
|
|
575
587
|
for item in uncommitted[:5]:
|
|
576
588
|
print(f" {item}")
|
|
577
589
|
if len(uncommitted) > 5:
|
|
578
|
-
print(f" ... and {len(uncommitted) - 5} more")
|
|
590
|
+
print(f" {c.dim(f'... and {len(uncommitted) - 5} more')}")
|
|
579
591
|
except SvnError as e:
|
|
580
592
|
has_error = True
|
|
581
|
-
print(f" Tree : Error: {e.args[0].splitlines()[0]}")
|
|
593
|
+
print(f" Tree : {c.bold_red(f'Error: {e.args[0].splitlines()[0]}')}")
|
|
582
594
|
|
|
583
595
|
# 4. Synchronization Queue & Default Range
|
|
584
596
|
default_range = self.git.get_config("git2svn.defaultRange")
|
|
585
|
-
print("\n[Synchronization]")
|
|
597
|
+
print(f"\n{c.bold_cyan('[Synchronization]')}")
|
|
586
598
|
if not default_range:
|
|
587
|
-
print(
|
|
599
|
+
print(
|
|
600
|
+
f" Range : {c.dim('Not configured')} (run 'git2svn setup' or 'git config git2svn.defaultRange <range>')"
|
|
601
|
+
)
|
|
588
602
|
else:
|
|
589
|
-
print(f" Range : {default_range}")
|
|
603
|
+
print(f" Range : {c.bold(default_range)}")
|
|
590
604
|
try:
|
|
591
605
|
is_single, start_ref, end_ref = parse_ref_arguments(default_range)
|
|
592
606
|
if is_single:
|
|
@@ -595,11 +609,13 @@ class Synchronizer:
|
|
|
595
609
|
else:
|
|
596
610
|
assert end_ref is not None
|
|
597
611
|
if not self.git.ref_exists(start_ref):
|
|
598
|
-
|
|
612
|
+
warn_msg = f"Range start ref '{start_ref}' not found in Git."
|
|
613
|
+
print(f" Warning : {c.bold_yellow(warn_msg)}")
|
|
599
614
|
commits = []
|
|
600
615
|
merges = []
|
|
601
616
|
elif not self.git.ref_exists(end_ref):
|
|
602
|
-
|
|
617
|
+
warn_msg = f"Range end ref '{end_ref}' not found in Git."
|
|
618
|
+
print(f" Warning : {c.bold_yellow(warn_msg)}")
|
|
603
619
|
commits = []
|
|
604
620
|
merges = []
|
|
605
621
|
else:
|
|
@@ -608,24 +624,25 @@ class Synchronizer:
|
|
|
608
624
|
|
|
609
625
|
if merges:
|
|
610
626
|
has_error = True
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
)
|
|
627
|
+
inv_msg = f"INVALID ({len(merges)} merge commits found in range - linear rebase required)"
|
|
628
|
+
print(f" Linearity : {c.bold_red(inv_msg)}")
|
|
614
629
|
else:
|
|
615
|
-
print(" Linearity : OK (strictly linear)")
|
|
630
|
+
print(f" Linearity : {c.bold_green('OK')} (strictly linear)")
|
|
616
631
|
|
|
617
632
|
if not commits:
|
|
618
|
-
print(" Pending : In sync (0 commits to replay)")
|
|
633
|
+
print(f" Pending : {c.bold_green('In sync')} (0 commits to replay)")
|
|
619
634
|
else:
|
|
620
|
-
|
|
635
|
+
pending_title = f"{len(commits)} commit(s) ready to replay:"
|
|
636
|
+
print(f" Pending : {c.bold_yellow(pending_title)}")
|
|
621
637
|
for idx, c_hash in enumerate(commits[:10], start=1):
|
|
622
638
|
msg = self.git.get_commit_message(c_hash)
|
|
623
639
|
subject = msg.splitlines()[0] if msg else ""
|
|
624
|
-
print(f" {idx}. [{c_hash[:8]}] {subject}")
|
|
640
|
+
print(f" {c.dim(f'{idx}.')} [{c.yellow(c_hash[:8])}] {subject}")
|
|
625
641
|
if len(commits) > 10:
|
|
626
|
-
print(f" ... and {len(commits) - 10} more")
|
|
642
|
+
print(f" {c.dim(f'... and {len(commits) - 10} more')}")
|
|
627
643
|
except Exception as e:
|
|
628
|
-
|
|
644
|
+
err_msg = f"Could not parse range '{default_range}': {e}"
|
|
645
|
+
print(f" Error : {c.bold_red(err_msg)}")
|
|
629
646
|
has_error = True
|
|
630
647
|
|
|
631
648
|
if has_error:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import os
|
|
2
2
|
import subprocess
|
|
3
3
|
import tempfile
|
|
4
4
|
import unittest
|
|
@@ -1263,5 +1263,57 @@ class TestStatus(unittest.TestCase):
|
|
|
1263
1263
|
mock_status.assert_called_once()
|
|
1264
1264
|
|
|
1265
1265
|
|
|
1266
|
+
class TestTerminalColor(unittest.TestCase):
|
|
1267
|
+
def test_color_always(self):
|
|
1268
|
+
color = git2svn.TerminalColor(mode="always")
|
|
1269
|
+
self.assertTrue(color.enabled)
|
|
1270
|
+
self.assertIn("\033[", color.bold_cyan("Test"))
|
|
1271
|
+
self.assertIn("\033[", color.clean())
|
|
1272
|
+
self.assertIn("\033[", color.ok())
|
|
1273
|
+
self.assertIn("\033[", color.warn())
|
|
1274
|
+
self.assertIn("\033[", color.error())
|
|
1275
|
+
|
|
1276
|
+
def test_color_never(self):
|
|
1277
|
+
color = git2svn.TerminalColor(mode="never")
|
|
1278
|
+
self.assertFalse(color.enabled)
|
|
1279
|
+
self.assertEqual(color.bold_cyan("Test"), "Test")
|
|
1280
|
+
self.assertEqual(color.clean(), "✔ Clean")
|
|
1281
|
+
|
|
1282
|
+
def test_color_auto_no_color_env(self):
|
|
1283
|
+
with patch.dict(os.environ, {"NO_COLOR": "1"}):
|
|
1284
|
+
color = git2svn.TerminalColor(mode="auto")
|
|
1285
|
+
self.assertFalse(color.enabled)
|
|
1286
|
+
self.assertEqual(color.bold("Hello"), "Hello")
|
|
1287
|
+
|
|
1288
|
+
def test_color_auto_dumb_term(self):
|
|
1289
|
+
with patch.dict(os.environ, {"TERM": "dumb", "NO_COLOR": ""}):
|
|
1290
|
+
color = git2svn.TerminalColor(mode="auto")
|
|
1291
|
+
self.assertFalse(color.enabled)
|
|
1292
|
+
|
|
1293
|
+
def test_color_cli_and_git_config(self):
|
|
1294
|
+
temp_dir = tempfile.TemporaryDirectory()
|
|
1295
|
+
try:
|
|
1296
|
+
repo_path = Path(temp_dir.name).resolve()
|
|
1297
|
+
(repo_path / ".svn").mkdir()
|
|
1298
|
+
subprocess.run(["git", "init"], cwd=repo_path, check=True, capture_output=True)
|
|
1299
|
+
subprocess.run(["git", "config", "user.name", "Test"], cwd=repo_path, check=True)
|
|
1300
|
+
subprocess.run(["git", "config", "user.email", "test@test.com"], cwd=repo_path, check=True)
|
|
1301
|
+
subprocess.run(["git", "config", "git2svn.color", "always"], cwd=repo_path, check=True)
|
|
1302
|
+
|
|
1303
|
+
with patch("git2svn.cli.Synchronizer") as mock_sync_cls:
|
|
1304
|
+
mock_sync_cls.return_value.status.return_value = 0
|
|
1305
|
+
# When no --color is passed, config 'always' should be used
|
|
1306
|
+
git2svn.main(["--git-dir", str(repo_path), "--svn-dir", str(repo_path), "status"])
|
|
1307
|
+
_, kwargs = mock_sync_cls.call_args
|
|
1308
|
+
self.assertEqual(kwargs.get("color_mode"), "always")
|
|
1309
|
+
|
|
1310
|
+
# When CLI --color never is passed, it overrides git config
|
|
1311
|
+
git2svn.main(["--git-dir", str(repo_path), "--svn-dir", str(repo_path), "--color", "never", "status"])
|
|
1312
|
+
_, kwargs = mock_sync_cls.call_args
|
|
1313
|
+
self.assertEqual(kwargs.get("color_mode"), "never")
|
|
1314
|
+
finally:
|
|
1315
|
+
temp_dir.cleanup()
|
|
1316
|
+
|
|
1317
|
+
|
|
1266
1318
|
if __name__ == "__main__":
|
|
1267
1319
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|