mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-07-23 04:17:08 +00:00
35bbf701d0
Per the git documentation for color.ui [1], setting color.ui to "true" (or "yes") should behave identically to "auto", enabling color only when output is written to a terminal or an active pager. Previously, repo was equating "true" and "yes" with "always", which caused color escape codes to be emitted unconditionally, even when output was piped or redirected. Replace the duplicated string-matching logic in SetDefaultColoring and Coloring.__init__ with a single CONFIG_TO_COLOR_SETTING dict that maps all git color config values to their behavior. This makes the mapping easy to verify against the git docs and impossible to get out of sync between the two call sites. Added tests for SetDefaultColoring and Coloring.__init__ covering all color mode values (auto, true, yes, always, never, no, false), case insensitivity, TTY vs pipe behavior, active pager detection, and unrecognised input. [1] https://git-scm.com/docs/git-config#Documentation/git-config.txt-colorui Bug: 295841573 Change-Id: I8a04b9c7e4154de37ed7518c010233039e0afdc9 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/602981 Tested-by: Brian Gan <brgan@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com> Commit-Queue: Brian Gan <brgan@google.com>
226 lines
5.1 KiB
Python
226 lines
5.1 KiB
Python
# Copyright (C) 2008 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import os
|
|
import sys
|
|
from typing import Optional
|
|
|
|
import pager
|
|
|
|
|
|
COLORS = {
|
|
None: -1,
|
|
"normal": -1,
|
|
"black": 0,
|
|
"red": 1,
|
|
"green": 2,
|
|
"yellow": 3,
|
|
"blue": 4,
|
|
"magenta": 5,
|
|
"cyan": 6,
|
|
"white": 7,
|
|
}
|
|
|
|
ATTRS = {None: -1, "bold": 1, "dim": 2, "ul": 4, "blink": 5, "reverse": 7}
|
|
|
|
RESET = "\033[m"
|
|
|
|
|
|
def is_color(s):
|
|
return s in COLORS
|
|
|
|
|
|
def is_attr(s):
|
|
return s in ATTRS
|
|
|
|
|
|
def _Color(fg=None, bg=None, attr=None):
|
|
fg = COLORS[fg]
|
|
bg = COLORS[bg]
|
|
attr = ATTRS[attr]
|
|
|
|
if attr >= 0 or fg >= 0 or bg >= 0:
|
|
need_sep = False
|
|
code = "\033["
|
|
|
|
if attr >= 0:
|
|
code += chr(ord("0") + attr)
|
|
need_sep = True
|
|
|
|
if fg >= 0:
|
|
if need_sep:
|
|
code += ";"
|
|
need_sep = True
|
|
|
|
if fg < 8:
|
|
code += "3%c" % (ord("0") + fg)
|
|
else:
|
|
code += "38;5;%d" % fg
|
|
|
|
if bg >= 0:
|
|
if need_sep:
|
|
code += ";"
|
|
|
|
if bg < 8:
|
|
code += "4%c" % (ord("0") + bg)
|
|
else:
|
|
code += "48;5;%d" % bg
|
|
code += "m"
|
|
else:
|
|
code = ""
|
|
return code
|
|
|
|
|
|
DEFAULT = None
|
|
|
|
# Placholder value that indicates we need to check if the user is in an
|
|
# interactive terminal session to determine if we turn on color or not.
|
|
_CHECK_CONSOLE = object()
|
|
|
|
# https://git-scm.com/docs/git-config#Documentation/git-config.txt-colorui
|
|
_CONFIG_TO_COLOR_SETTING = {
|
|
"false": False,
|
|
"never": False,
|
|
"no": False,
|
|
"auto": _CHECK_CONSOLE,
|
|
"true": _CHECK_CONSOLE,
|
|
"yes": _CHECK_CONSOLE,
|
|
"always": True,
|
|
}
|
|
|
|
|
|
def SetDefaultColoring(state: Optional[str]) -> None:
|
|
"""Set coloring behavior to |state|.
|
|
|
|
This is useful for overriding config options via the command line.
|
|
"""
|
|
|
|
global DEFAULT
|
|
|
|
if isinstance(state, str):
|
|
state = state.lower()
|
|
if state in _CONFIG_TO_COLOR_SETTING:
|
|
DEFAULT = state
|
|
|
|
|
|
class Coloring:
|
|
def __init__(self, config, section_type):
|
|
self._section = f"color.{section_type}"
|
|
self._config = config
|
|
self._out = sys.stdout
|
|
|
|
on = DEFAULT
|
|
if on is None:
|
|
on = self._config.GetString(self._section)
|
|
if on is None:
|
|
on = self._config.GetString("color.ui")
|
|
if isinstance(on, str):
|
|
on = on.lower()
|
|
|
|
self._on = _CONFIG_TO_COLOR_SETTING.get(on, _CHECK_CONSOLE)
|
|
if self._on is _CHECK_CONSOLE:
|
|
self._on = pager.active or os.isatty(1)
|
|
|
|
def redirect(self, out):
|
|
self._out = out
|
|
|
|
@property
|
|
def is_on(self):
|
|
return self._on
|
|
|
|
def write(self, fmt, *args):
|
|
self._out.write(fmt % args)
|
|
|
|
def flush(self):
|
|
self._out.flush()
|
|
|
|
def nl(self):
|
|
self._out.write("\n")
|
|
|
|
def printer(self, opt=None, fg=None, bg=None, attr=None):
|
|
s = self
|
|
c = self.colorer(opt, fg, bg, attr)
|
|
|
|
def f(fmt, *args):
|
|
s._out.write(c(fmt, *args))
|
|
|
|
return f
|
|
|
|
def nofmt_printer(self, opt=None, fg=None, bg=None, attr=None):
|
|
s = self
|
|
c = self.nofmt_colorer(opt, fg, bg, attr)
|
|
|
|
def f(fmt):
|
|
s._out.write(c(fmt))
|
|
|
|
return f
|
|
|
|
def colorer(self, opt=None, fg=None, bg=None, attr=None):
|
|
if self._on:
|
|
c = self._parse(opt, fg, bg, attr)
|
|
|
|
def f(fmt, *args):
|
|
output = fmt % args
|
|
return "".join([c, output, RESET])
|
|
|
|
return f
|
|
else:
|
|
|
|
def f(fmt, *args):
|
|
return fmt % args
|
|
|
|
return f
|
|
|
|
def nofmt_colorer(self, opt=None, fg=None, bg=None, attr=None):
|
|
if self._on:
|
|
c = self._parse(opt, fg, bg, attr)
|
|
|
|
def f(fmt):
|
|
return "".join([c, fmt, RESET])
|
|
|
|
return f
|
|
else:
|
|
|
|
def f(fmt):
|
|
return fmt
|
|
|
|
return f
|
|
|
|
def _parse(self, opt, fg, bg, attr):
|
|
if not opt:
|
|
return _Color(fg, bg, attr)
|
|
|
|
v = self._config.GetString(f"{self._section}.{opt}")
|
|
if v is None:
|
|
return _Color(fg, bg, attr)
|
|
|
|
v = v.strip().lower()
|
|
if v == "reset":
|
|
return RESET
|
|
elif v == "":
|
|
return _Color(fg, bg, attr)
|
|
|
|
have_fg = False
|
|
for a in v.split(" "):
|
|
if is_color(a):
|
|
if have_fg:
|
|
bg = a
|
|
else:
|
|
have_fg = True
|
|
fg = a
|
|
elif is_attr(a):
|
|
attr = a
|
|
|
|
return _Color(fg, bg, attr)
|