Skip to content
98 changes: 90 additions & 8 deletions cherry_picker/cherry_picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import webbrowser

import click
from click.termui import _ansi_colors as color_names
import requests
from gidgethub import sansio

Expand All @@ -31,6 +32,8 @@
"check_sha": "7f777ed95a19224294949e1b4ce56bbffcb1fe9f",
"fix_commit_msg": True,
"default_branch": "main",
"bright": "red",
"dim": "0x808080",
}
)

Expand Down Expand Up @@ -77,7 +80,6 @@
""",
)


class BranchCheckoutException(Exception):
def __init__(self, branch_name):
self.branch_name = branch_name
Expand Down Expand Up @@ -109,6 +111,7 @@
config=DEFAULT_CONFIG,
chosen_config_path=None,
auto_pr=True,
use_color=True,
):
self.chosen_config_path = chosen_config_path
"""The config reference used in the current runtime.
Expand Down Expand Up @@ -137,6 +140,14 @@
self.push = push
self.auto_pr = auto_pr
self.prefix_commit = prefix_commit
self.dim = config["dim"]
self.bright = config["bright"]

if not use_color or os.environ.get("NO_COLOR") is not None:
self.dim = self.bright = None

Check warning on line 147 in cherry_picker/cherry_picker.py

View check run for this annotation

Codecov / codecov/patch

cherry_picker/cherry_picker.py#L147

Added line #L147 was not covered by tests

self.dim = normalize_color(self.dim)
self.bright = normalize_color(self.bright)

# the cached calculated value of self.upstream property
self._upstream = None
Expand Down Expand Up @@ -306,7 +317,7 @@
click.echo(self.run_cmd(cmd))
except subprocess.CalledProcessError as err:
click.echo(f"Error cherry-pick {self.commit_sha1}.")
click.echo(err.output)
click.secho(err.output, fg=self.dim)
raise CherryPickException(f"Error cherry-pick {self.commit_sha1}.")

def get_exit_message(self, branch):
Expand Down Expand Up @@ -387,7 +398,7 @@
return updated_commit_message

def pause_after_committing(self, cherry_pick_branch):
click.echo(
click.secho(
f"""
Finished cherry-pick {self.commit_sha1} into {cherry_pick_branch} \U0001F600
--no-push option used.
Expand All @@ -397,7 +408,8 @@

To abort the cherry-pick and cleanup:
$ cherry_picker --abort
"""
""",
fg=self.bright,
)
self.set_paused_state()

Expand Down Expand Up @@ -468,8 +480,8 @@
if self.dry_run:
click.echo(f" dry-run: Create new PR: {url}")
else:
click.echo("Backport PR URL:")
click.echo(url)
click.secho("Backport PR URL:", fg=self.bright)
click.secho(url, fg=self.bright)

Check warning on line 484 in cherry_picker/cherry_picker.py

View check run for this annotation

Codecov / codecov/patch

cherry_picker/cherry_picker.py#L483-L484

Added lines #L483 - L484 were not covered by tests
webbrowser.open_new_tab(url)

def delete_branch(self, branch):
Expand Down Expand Up @@ -530,9 +542,9 @@
commit_message = self.amend_commit_message(cherry_pick_branch)
except subprocess.CalledProcessError as cpe:
click.echo(cpe.output)
click.echo(self.get_exit_message(maint_branch))
click.secho(self.get_exit_message(maint_branch), fg=self.bright)
except CherryPickException:
click.echo(self.get_exit_message(maint_branch))
click.secho(self.get_exit_message(maint_branch), fg=self.bright)
self.set_paused_state()
raise
else:
Expand Down Expand Up @@ -776,6 +788,19 @@
)
@click.argument("commit_sha1", nargs=1, default="")
@click.argument("branches", nargs=-1)
@click.option(
"--color/--no-color",
"color",
is_flag=True,
default=True,
help=(
"If color display is enabled (the default), cherry-picker will use color to"
" distinguish its output from that of 'git cherry-pick'. Color display can"
" also be suppressed by setting NO_COLOR environment variable to non-empty value."
),
)
@click.argument("bright", nargs=1)
@click.argument("dim", nargs=1)
@click.pass_context
def cherry_pick_cli(
ctx,
Expand All @@ -789,6 +814,9 @@
config_path,
commit_sha1,
branches,
color,
bright,
dim,
):
"""cherry-pick COMMIT_SHA1 into target BRANCHES."""

Expand All @@ -807,6 +835,7 @@
auto_pr=auto_pr,
config=config,
chosen_config_path=chosen_config_path,
use_color=color,
)
except InvalidRepoException:
click.echo(f"You're not inside a {config['repo']} repo right now! \U0001F645")
Expand Down Expand Up @@ -1071,5 +1100,58 @@
return WORKFLOW_STATES.__members__[state_str]


def normalize_color(color):
"""Produce colors in Click's format from strings.

Supported formats are:

* color names understood by Click. See:
https://click.palletsprojects.com/en/8.1.x/api/#click.style
* RGB values of the form 0xRRGGBB or 0xRGB.
* Tuple of ints (base 10), must have leading and trailing parens,
and values separated by commas, e.g., "( 44, 88, 255)".

"""

if color is None:
return color

Check warning on line 1117 in cherry_picker/cherry_picker.py

View check run for this annotation

Codecov / codecov/patch

cherry_picker/cherry_picker.py#L1117

Added line #L1117 was not covered by tests

color = color.strip().lower()

if color.lower().startswith("0x"):
# Assume it's a hex string and convert it to Click's tuple of ints
# form. It's kind of surprising Click doesn't support hex strings
# directly.
if len(color) == 8:
# assume six-digit hex, 0xRRGGBB
return (
int(color[2:4], 16),
int(color[4:6], 16),
int(color[6:8], 16),
)
elif len(color) == 5:

Check warning on line 1132 in cherry_picker/cherry_picker.py

View check run for this annotation

Codecov / codecov/patch

cherry_picker/cherry_picker.py#L1132

Added line #L1132 was not covered by tests
# assume three-digit hex, 0xRGB
return (

Check warning on line 1134 in cherry_picker/cherry_picker.py

View check run for this annotation

Codecov / codecov/patch

cherry_picker/cherry_picker.py#L1134

Added line #L1134 was not covered by tests
int(color[2], 16) * 16,
int(color[3], 16) * 16,
int(color[4], 16) * 16,
)
else:
raise ValueError(f"unrecognized hex string: {color!r}")

Check warning on line 1140 in cherry_picker/cherry_picker.py

View check run for this annotation

Codecov / codecov/patch

cherry_picker/cherry_picker.py#L1140

Added line #L1140 was not covered by tests

if color[0] == "(" and color[-1] == ")":
# let's hope it's a tuple of ints...
try:
red, green, blue = [int(x.strip()) for x in color[1:-1].split(",")]
except ValueError as exc:
raise ValueError(f"unrecognized tuple of ints string: {color!r}") from exc
return (red, green, blue)

Check warning on line 1148 in cherry_picker/cherry_picker.py

View check run for this annotation

Codecov / codecov/patch

cherry_picker/cherry_picker.py#L1144-L1148

Added lines #L1144 - L1148 were not covered by tests

# fallback is to assume it's a color name supported by click.
if color in color_names:
return color
raise ValueError(f"unrecognized color: '{color!r}'")

Check warning on line 1153 in cherry_picker/cherry_picker.py

View check run for this annotation

Codecov / codecov/patch

cherry_picker/cherry_picker.py#L1153

Added line #L1153 was not covered by tests


if __name__ == "__main__":
cherry_pick_cli()
6 changes: 6 additions & 0 deletions cherry_picker/test_cherry_picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ def test_load_full_config(tmp_git_repo_dir, git_add, git_commit):
"team": "python",
"fix_commit_msg": True,
"default_branch": "devel",
"bright": "red",
"dim": "0x808080",
},
)

Expand All @@ -479,6 +481,8 @@ def test_load_partial_config(tmp_git_repo_dir, git_add, git_commit):
"team": "python",
"fix_commit_msg": True,
"default_branch": "main",
"bright": "red",
"dim": "0x808080",
},
)

Expand Down Expand Up @@ -507,6 +511,8 @@ def test_load_config_no_head_sha(tmp_git_repo_dir, git_add, git_commit):
"team": "python",
"fix_commit_msg": True,
"default_branch": "devel",
"bright": "red",
"dim": "0x808080",
},
)

Expand Down