Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Implement storing runtime state in repo level Git config #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
509a2ad
Add initial impl of storinig state in Git config
webknjaz Nov 20, 2018
9f626e6
Drop test for find_project_root
webknjaz Jan 7, 2019
91959cb
🐛 Fix final path construction in load_config
webknjaz Jan 7, 2019
cee3fee
🎨 Validate input in from_git_rev_read function
webknjaz Jan 7, 2019
66240f3
🚑🐛 Fix all existing tests to match new reality
webknjaz Jan 7, 2019
de10340
🎨 Move conf path from global scope to CherryPicker
webknjaz Jan 7, 2019
1cd2dd0
🎨 Use Enum for ALLOWED_STATES
webknjaz Jan 7, 2019
1395bf1
🎨 Make check_output line shorter
webknjaz Jan 9, 2019
a9e302b
🐛 Improve error processing in from_git_rev_read
webknjaz Jan 9, 2019
42e51d4
✅🎨 Add tests for from_git_rev_read
webknjaz Jan 9, 2019
d50bf6f
✅ Add tests for low-level state management
webknjaz Jan 9, 2019
037aed4
🚑 Refer to set_paused_state correctly
webknjaz Jan 9, 2019
c6b6784
🚑 Fix set_paused_state method args
webknjaz Jan 9, 2019
2bc6ca4
✅ Test paused flow
webknjaz Jan 9, 2019
b7d02ff
✅ Cover a test case with unknown sha and fs path
webknjaz Jan 9, 2019
d479240
✅ Test find_config w/o Git
webknjaz Jan 9, 2019
b9f6bc6
✅ Add tests for two-stage methods
webknjaz Jan 9, 2019
a550746
🎨 Drop unused fixtures from test_start_end_states
webknjaz Jan 9, 2019
6a903ae
✅ Add tests for cleanup_branch
webknjaz Jan 9, 2019
3759a2d
✅ Add cherry-pick fail test
webknjaz Jan 11, 2019
1a5d76f
✅ Add cherry-pick success test
webknjaz Feb 9, 2019
60c2c17
✅ Add get_state_and_verify fail test
webknjaz Feb 10, 2019
98c4620
✅ Add push_to_remote tests
webknjaz Feb 10, 2019
d142450
✅ Add backport test with no branch
webknjaz Feb 10, 2019
e2ca39d
🐛 Interrupt cherry-pick loop on no-push
webknjaz Feb 10, 2019
d2aefc7
🐛 Ignore missing config pointer on wipe
webknjaz Feb 10, 2019
0180085
✅ Cover backport method with tests
webknjaz Feb 10, 2019
69db409
✅ Cover ``--continue`` with tests
webknjaz Feb 10, 2019
c12ed58
🎨 Improve test_backport_pause_and_continue
webknjaz Feb 10, 2019
0eed5ed
🎨 Use raw-string for regex
webknjaz Feb 10, 2019
bbebd41
✅ Cover ``--abort`` with tests
webknjaz Feb 10, 2019
3630ad4
🎨 Store all states in Enum structure
webknjaz Feb 10, 2019
4673edc
🔥 Drop garbage comments
webknjaz Feb 10, 2019
fdf45ae
🎨 Use match instead of message in pytest.raises
webknjaz Feb 10, 2019
af69d6d
f-stringify concatenation in tests
Mariatta Feb 12, 2019
6e68f8b
📝💡 Add change notes
webknjaz Feb 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
🎨 Store all states in Enum structure
  • Loading branch information
webknjaz committed Feb 10, 2019
commit 3630ad421e46080b55422e51fa3be28500b35ab1
119 changes: 83 additions & 36 deletions 119 cherry_picker/cherry_picker/cherry_picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,47 @@
})


WORKFLOW_STATES = enum.Enum(
'Workflow states',
"""
FETCHING_UPSTREAM
FETCHED_UPSTREAM

CHECKING_OUT_DEFAULT_BRANCH
CHECKED_OUT_DEFAULT_BRANCH

PUSHING_TO_REMOTE
PUSHED_TO_REMOTE
PUSHING_TO_REMOTE_FAILED

PR_CREATING
PR_OPENING

REMOVING_BACKPORT_BRANCH
REMOVING_BACKPORT_BRANCH_FAILED
REMOVED_BACKPORT_BRANCH

BACKPORT_STARTING
BACKPORT_LOOPING
BACKPORT_LOOP_START
BACKPORT_LOOP_END
BACKPORT_COMPLETE

ABORTING
ABORTED
ABORTING_FAILED

CONTINUATION_STARTED
BACKPORTING_CONTINUATION_SUCCEED
CONTINUATION_FAILED

BACKPORT_PAUSED

UNSET
""",
)


class BranchCheckoutException(Exception):
pass

Expand All @@ -41,10 +82,7 @@ class InvalidRepoException(Exception):

class CherryPicker:

ALLOWED_STATES = enum.Enum(
'Allowed states',
'BACKPORT_PAUSED UNSET',
)
ALLOWED_STATES = WORKFLOW_STATES.BACKPORT_PAUSED, WORKFLOW_STATES.UNSET
"""The list of states expected at the start of the app."""

def __init__(self, pr_remote, commit_sha1, branches,
Expand Down Expand Up @@ -85,7 +123,7 @@ def set_paused_state(self):
"""Save paused progress state into Git config."""
if self.chosen_config_path is not None:
save_cfg_vals_to_git_cfg(config_path=self.chosen_config_path)
set_state('BACKPORT_PAUSED')
set_state(WORKFLOW_STATES.BACKPORT_PAUSED)

@property
def upstream(self):
Expand Down Expand Up @@ -124,10 +162,10 @@ def get_pr_url(self, base_branch, head_branch):

def fetch_upstream(self):
""" git fetch <upstream> """
set_state('FETCHING_UPSTREAM')
set_state(WORKFLOW_STATES.FETCHING_UPSTREAM)
cmd = ['git', 'fetch', self.upstream]
self.run_cmd(cmd)
set_state('FETCHED_UPSTREAM')
set_state(WORKFLOW_STATES.FETCHED_UPSTREAM)

def run_cmd(self, cmd):
assert not isinstance(cmd, str)
Expand Down Expand Up @@ -162,12 +200,12 @@ def get_commit_message(self, commit_sha):

def checkout_default_branch(self):
""" git checkout default branch """
set_state('CHECKING_OUT_DEFAULT_BRANCH')
set_state(WORKFLOW_STATES.CHECKING_OUT_DEFAULT_BRANCH)

cmd = 'git', 'checkout', self.config['default_branch']
self.run_cmd(cmd)

set_state('CHECKED_OUT_DEFAULT_BRANCH')
set_state(WORKFLOW_STATES.CHECKED_OUT_DEFAULT_BRANCH)

def status(self):
"""
Expand Down Expand Up @@ -228,24 +266,24 @@ def amend_commit_message(self, cherry_pick_branch):

def push_to_remote(self, base_branch, head_branch, commit_message=""):
""" git push <origin> <branchname> """
set_state('PUSHING_TO_REMOTE')
set_state(WORKFLOW_STATES.PUSHING_TO_REMOTE)

cmd = ['git', 'push', self.pr_remote, f'{head_branch}:{head_branch}']
try:
self.run_cmd(cmd)
set_state('PUSHED_TO_REMOTE')
set_state(WORKFLOW_STATES.PUSHED_TO_REMOTE)
except subprocess.CalledProcessError:
click.echo(f"Failed to push to {self.pr_remote} \u2639")
set_state('PUSHING_TO_REMOTE_FAILED')
set_state(WORKFLOW_STATES.PUSHING_TO_REMOTE_FAILED)
else:
gh_auth = os.getenv("GH_AUTH")
if gh_auth:
set_state('PR_CREATING')
set_state(WORKFLOW_STATES.PR_CREATING)
self.create_gh_pr(base_branch, head_branch,
commit_message=commit_message,
gh_auth=gh_auth)
else:
set_state('PR_OPENING')
set_state(WORKFLOW_STATES.PR_OPENING)
self.open_pr(self.get_pr_url(base_branch, head_branch))

def create_gh_pr(self, base_branch, head_branch, *,
Expand Down Expand Up @@ -294,26 +332,26 @@ def cleanup_branch(self, branch):

Switch to the default branch before that.
"""
set_state('REMOVING_BACKPORT_BRANCH')
set_state(WORKFLOW_STATES.REMOVING_BACKPORT_BRANCH)
self.checkout_default_branch()
try:
self.delete_branch(branch)
except subprocess.CalledProcessError:
click.echo(f"branch {branch} NOT deleted.")
set_state('REMOVING_BACKPORT_BRANCH_FAILED')
set_state(WORKFLOW_STATES.REMOVING_BACKPORT_BRANCH_FAILED)
else:
click.echo(f"branch {branch} has been deleted.")
set_state('REMOVED_BACKPORT_BRANCH')
set_state(WORKFLOW_STATES.REMOVED_BACKPORT_BRANCH)

def backport(self):
if not self.branches:
raise click.UsageError("At least one branch must be specified.")
set_state('BACKPORT_STARTING')
set_state(WORKFLOW_STATES.BACKPORT_STARTING)
self.fetch_upstream()

set_state('BACKPORT_LOOPING')
set_state(WORKFLOW_STATES.BACKPORT_LOOPING)
for maint_branch in self.sorted_branches:
set_state('BACKPORT_LOOP_START')
set_state(WORKFLOW_STATES.BACKPORT_LOOP_START)
click.echo(f"Now backporting '{self.commit_sha1}' into '{maint_branch}'")

cherry_pick_branch = self.get_cherry_pick_branch(maint_branch)
Expand Down Expand Up @@ -349,24 +387,24 @@ def backport(self):
""")
self.set_paused_state()
return # to preserve the correct state
set_state('BACKPORT_LOOP_END')
set_state('BACKPORT_COMPLETE')
set_state(WORKFLOW_STATES.BACKPORT_LOOP_END)
set_state(WORKFLOW_STATES.BACKPORT_COMPLETE)

def abort_cherry_pick(self):
"""
run `git cherry-pick --abort` and then clean up the branch
"""
if self.initial_state != 'BACKPORT_PAUSED':
if self.initial_state != WORKFLOW_STATES.BACKPORT_PAUSED:
raise ValueError('One can only abort a paused process.')

cmd = ['git', 'cherry-pick', '--abort']
try:
set_state('ABORTING')
set_state(WORKFLOW_STATES.ABORTING)
self.run_cmd(cmd)
set_state('ABORTED')
set_state(WORKFLOW_STATES.ABORTED)
except subprocess.CalledProcessError as cpe:
click.echo(cpe.output)
set_state('ABORTING_FAILED')
set_state(WORKFLOW_STATES.ABORTING_FAILED)
# only delete backport branch created by cherry_picker.py
if get_current_branch().startswith('backport-'):
self.cleanup_branch(get_current_branch())
Expand All @@ -380,12 +418,12 @@ def continue_cherry_pick(self):
open the PR
clean up branch
"""
if self.initial_state != 'BACKPORT_PAUSED':
if self.initial_state != WORKFLOW_STATES.BACKPORT_PAUSED:
raise ValueError('One can only continue a paused process.')

cherry_pick_branch = get_current_branch()
if cherry_pick_branch.startswith('backport-'):
set_state('CONTINUATION_STARTED')
set_state(WORKFLOW_STATES.CONTINUATION_STARTED)
# amend the commit message, prefix with [X.Y]
base = get_base_branch(cherry_pick_branch)
short_sha = cherry_pick_branch[cherry_pick_branch.index('-')+1:cherry_pick_branch.index(base)-1]
Expand All @@ -409,11 +447,11 @@ def continue_cherry_pick(self):

click.echo("\nBackport PR:\n")
click.echo(updated_commit_message)
set_state('BACKPORTING_CONTINUATION_SUCCEED')
set_state(WORKFLOW_STATES.BACKPORTING_CONTINUATION_SUCCEED)

else:
click.echo(f"Current branch ({cherry_pick_branch}) is not a backport branch. Will not continue. \U0001F61B")
set_state('CONTINUATION_FAILED')
set_state(WORKFLOW_STATES.CONTINUATION_FAILED)

reset_stored_config_ref()
reset_state()
Expand All @@ -436,14 +474,19 @@ def get_state_and_verify(self):
Raises ValueError if the retrieved state is not of a form that
cherry_picker would have stored in the config.
"""
state = get_state()
if state not in self.ALLOWED_STATES.__members__:
try:
state = get_state()
except KeyError as ke:
class state:
name = str(ke.args[0])

if state not in self.ALLOWED_STATES:
raise ValueError(
f'Run state cherry-picker.state={state} in Git config '
f'Run state cherry-picker.state={state.name} in Git config '
'is not known.\nPerhaps it has been set by a newer '
'version of cherry-picker. Try upgrading.\n'
'Valid states are: '
f'{", ".join(self.ALLOWED_STATES.__members__.keys())}. '
f'{", ".join(s.name for s in self.ALLOWED_STATES)}. '
'If this looks suspicious, raise an issue at '
'https://github.com/python/core-workflow/issues/new.\n'
'As the last resort you can reset the runtime state '
Expand Down Expand Up @@ -673,12 +716,12 @@ def reset_state():

def set_state(state):
"""Save progress state into Git config."""
save_cfg_vals_to_git_cfg(state=state)
save_cfg_vals_to_git_cfg(state=state.name)


def get_state():
"""Retrieve the progress state from Git config."""
return load_val_from_git_cfg('state') or 'UNSET'
return get_state_from_string(load_val_from_git_cfg('state') or 'UNSET')


def save_cfg_vals_to_git_cfg(**cfg_map):
Expand Down Expand Up @@ -721,5 +764,9 @@ def from_git_rev_read(path):
raise ValueError


def get_state_from_string(state_str):
return WORKFLOW_STATES.__members__[state_str]


if __name__ == '__main__':
cherry_pick_cli()
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.