Last active
October 2, 2021 16:18
-
-
Save orez-/8e119eb09aeb43019881 to your computer and use it in GitHub Desktop.
Run through your git stashes, either committing them to branches or deleting them.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2.7 | |
import collections | |
import os | |
import re | |
import readline | |
import subprocess | |
tty_bold = '\x1b[1m' | |
tty_af = '\x1b[{}m'.format | |
tty_clear = '\x1b[0m' | |
tty_bold_blue = tty_bold + tty_af(34) | |
tty_bold_red = tty_bold + tty_af(31) | |
stash_ref = 'stash@{{{}}}'.format | |
class AdvanceStashNum(Exception): | |
pass | |
class Quit(Exception): | |
pass | |
def has_local_changes(): | |
return bool(subprocess.check_output(['git', 'status', '--porcelain'])) | |
def option_drop(stash_num, **_): | |
"""drop this stash""" | |
if subprocess.call(['git', 'stash-applied', stash_ref(stash_num)]) != 0: | |
if raw_input("Stash may not be applied. Drop anyway? [y/N] ") != 'y': | |
return | |
subprocess.call(['git', 'stash', 'drop', stash_ref(stash_num)]) | |
def option_branch(stash_num, can_save_branch, **_): | |
"""commit this stash to a separate branch and delete it""" | |
stash_name = stash_ref(stash_num) | |
if not can_save_branch: | |
print(tty_bold_red + "ERROR - Can't commit branches with unstaged files!." + tty_clear) | |
return | |
commit_msg_file = '.git/COMMIT_EDITMSG' | |
branch_name = 'stash/__TEMP_STASH__' | |
subprocess.call(['git', 'checkout', '-b', branch_name]) | |
subprocess.call(['git', 'stash', 'apply', stash_name]) | |
subprocess.call(['git', 'add', '.']) | |
if subprocess.call(['git', 'commit', '-n']): | |
subprocess.call(['git', 'reset', 'HEAD']) | |
subprocess.call(['git', 'checkout', '.']) | |
subprocess.call(['git', 'clean', '-f']) | |
subprocess.call(['git', 'checkout', '-']) | |
subprocess.call(['git', 'branch', '-d', branch_name]) | |
return | |
# Change the branch name to the first line of the commit message. | |
with open(commit_msg_file, 'r') as f: | |
subject = next(line for line in f if line and line[0] != '#') | |
subject = re.sub(r'\s+', '_', subject.strip()) | |
new_branch_name = 'stash/' + re.sub(r'\W', '', subject).lower() | |
subprocess.call(['git', 'branch', '-m', new_branch_name]) | |
subprocess.call(['git', 'checkout', '-']) | |
subprocess.call(['git', 'stash', 'drop', stash_name]) | |
def option_skip(**_): | |
"""take no action on this stash""" | |
raise AdvanceStashNum | |
def option_help(**_): | |
"""print help""" | |
message = getattr(option_help, 'help_message', None) | |
if not message: | |
message = option_help.help_message = ( | |
tty_bold_red + | |
'\n'.join( | |
'{} - {}'.format(k, v.__doc__) | |
for k, v in options.iteritems() | |
if k | |
) + tty_clear | |
) | |
print(message) | |
return True | |
def option_apply(stash_num, **_): | |
"""apply; apply the stash and take no further action""" | |
subprocess.call(['git', 'stash', 'apply', stash_ref(stash_num)]) | |
raise Quit | |
def option_quit(**_): | |
"""quit; take no further action on remaining stashes""" | |
raise Quit | |
options = collections.OrderedDict([ | |
('d', option_drop), | |
('b', option_branch), | |
('s', option_skip), | |
('a', option_apply), | |
('q', option_quit), | |
('?', option_help), | |
('', option_help), | |
]) | |
def git_stash_inbox(): | |
can_save_branch = not has_local_changes() | |
if not can_save_branch: | |
print( | |
tty_bold_red + | |
"WARNING - Can't backup stashes as branches with local changes.\n" | |
"Resolve local changes to backup stashes as branches." + | |
tty_clear | |
) | |
action_display = "{}Action on this stash [{}]? {}".format( | |
tty_bold_blue, | |
','.join(o for o in options if o), | |
tty_clear, | |
) | |
stash_num = 0 | |
once = False | |
while True: | |
with open(os.devnull, 'w') as fnull: | |
output = subprocess.call( | |
['git', 'stash', 'show', '-p', stash_ref(stash_num)], | |
stderr=fnull, | |
) | |
if output not in {0, 141}: | |
# Reached end of stash list. | |
if not once: | |
print("No stashes found.") | |
break | |
once = True | |
action = raw_input(action_display) | |
try: | |
options.get(action, lambda **_: None)( | |
can_save_branch=can_save_branch, | |
stash_num=stash_num, | |
) | |
except AdvanceStashNum: | |
stash_num += 1 | |
except Quit: | |
break | |
if __name__ == '__main__': | |
try: | |
git_stash_inbox() | |
except (KeyboardInterrupt, EOFError): | |
print() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment