#!/usr/bin/env python3
from gitz import git_functions
from gitz.program import PROGRAM
from gitz.program import git
from gitz.program import safe_git
import subprocess

SUMMARY = 'Push a sequence of commit IDs onto upstream branches'

USAGE = """
git stripe [<number-of-commits>] [commit-id]
[-b/--branch_base=<branch_base>] [-r/remove]
"""

HELP = """
Starting with a given commit ID, and moving backwards from there,
push each commit ID to its own disposable branch name.

Useful if CI has missed some of your commit IDs because you rebased or
pushed a sequences of commits too fast.
"""

EXAMPLES = """
Assume current branch is master:

git stripe
    Pushes HEAD~, HEAD~2 and HEAD~3 into their own branches named
    _gitz_stripe_master_0, _gitz_stripe_master_1
    and _gitz_stripe_master_2

git stripe --remove
git stripe -r
    Remove any branches named _gitz_stripe_master_0,
    _gitz_stripe_master_1 and _gitz_stripe_master_2.

    git-stripe -d does not fail if some or all of the branches
    to be removed are missing

git stripe --base-branch=BBBB
git stripe -b=BBBB
    Pushes HEAD~, HEAD~2 and HEAD~3 into their own branches named
    BBBB_0, BBBB_1, BBBB_2

git stripe --count=2 --base-branch=BBBB
git stripe -c=2 -b=BBBB
    Pushes HEAD~ and HEAD~2 into their own branches named BBBB_0
    and BBBB_1
"""

PREFIX = '_gitz_stripe_'
BAD_BRANCH_CHARS = frozenset('~^: ')
_FMT = '{commit_id}~{i}:refs/heads/{branch_base}{i}'
_REMOVE_FMT = ':refs/heads/{branch_base}{i}'


def git_stripe():
    args = PROGRAM.args
    cid = args.commit_id or 'HEAD~'
    commit_id = git_functions.commit_id(cid, short=True)
    if not commit_id:
        PROGRAM.exit('Cannot resolve "%s" to a commit ID' % cid)

    if BAD_BRANCH_CHARS.intersection(args.branch_base):
        PROGRAM.exit(_ERROR_BRANCH_NAME, args.branch_base)
    branch_base = args.branch_base or PREFIX + args.commit_id

    if not args.upstream:
        try:
            upstream, _ = git_functions.upstream_branch()
        except Exception:
            PROGRAM.exit('Branch has no upstream and --upstream not set')

    elif args.upstream in safe_git.remotes():
        upstream = args.upstream

    else:
        PROGRAM.exit('Unknown upstream', args.upstream)

    branch_base = args.branch_base
    if args.branch_base:
        branch_base = args.branch_base

    elif not args.commit_id:
        branch_base = PREFIX

    elif BAD_BRANCH_CHARS.intersection(args.commit_id):
        branch_base = PREFIX + commit_id + '_'

    else:
        branch_base = PREFIX + args.commit_id + '_'

    for i in range(args.count):
        refspec_format = _REMOVE_FMT if args.remove else _FMT
        refspec = refspec_format.format(**locals())
        force = git_functions.force_flags()
        pipe = subprocess.PIPE
        git.push(*force, upstream, refspec, quiet=True)
        action = 'Removed' if args.remove else 'Striped'
        PROGRAM.log.message(action, refspec.split('/')[-1])


def add_arguments(parser):
    add = parser.add_argument

    add('commit_id', default='', nargs='?', help=_HELP_COMMIT_ID)

    add('-b', '--branch_base', type=str, default='', help=_HELP_BRANCH_BASE)
    add('-c', '--count', type=int, default=3, help=_HELP_COUNT)
    add('-f', '--force', action='store_true', help=_HELP_FORCE)
    add('-r', '--remove', action='store_true', help=_HELP_REMOVE)
    add('-u', '--upstream', action='store_true', help=_HELP_UPSTREAM)


_ERROR_BRANCH_NAME = 'Illegal character in branch name'

_HELP_BRANCH_BASE = 'Base name for stripe branches (autogenerated if none)'
_HELP_COMMIT_ID = 'Branch/commit ID of the first stripe (or HEAD~ if none)'
_HELP_COUNT = 'The number of stripe branches to be created'
_HELP_FORCE = 'Force push over existing stripes'
_HELP_REMOVE = 'Remove the striped branches'
_HELP_UPSTREAM = 'Upstream remote to push to'


if __name__ == '__main__':
    PROGRAM.start(**globals())
