Beispiel #1
0
def func(parser, options, args):
    """Commit a number of patches."""
    stack = directory.repository.current_stack
    args = common.parse_patches(args, list(stack.patchorder.all_visible))
    if len([x for x in [args, options.number is not None, options.all] if x]) > 1:
        parser.error('too many options')
    if args:
        patches = [pn for pn in stack.patchorder.all_visible if pn in args]
        bad = set(args) - set(patches)
        if bad:
            raise common.CmdException('Nonexistent or hidden patch names: %s'
                                      % ', '.join(sorted(bad)))
    elif options.number is not None:
        if options.number <= len(stack.patchorder.applied):
            patches = stack.patchorder.applied[:options.number]
        else:
            raise common.CmdException('There are not that many applied patches')
    elif options.all:
        patches = stack.patchorder.applied
    else:
        patches = stack.patchorder.applied[:1]
    if not patches:
        raise common.CmdException('No patches to commit')

    iw = stack.repository.default_iw
    def allow_conflicts(trans):
        # As long as the topmost patch stays where it is, it's OK to
        # run "stg commit" with conflicts in the index.
        return len(trans.applied) >= 1
    trans = transaction.StackTransaction(stack, 'commit',
                                         allow_conflicts = allow_conflicts)
    try:
        common_prefix = 0
        for i in range(min(len(stack.patchorder.applied), len(patches))):
            if stack.patchorder.applied[i] == patches[i]:
                common_prefix += 1
            else:
                break
        if common_prefix < len(patches):
            to_push = [pn for pn in stack.patchorder.applied[common_prefix:]
                       if pn not in patches[common_prefix:]]
            # this pops all the applied patches from common_prefix
            trans.pop_patches(lambda pn: pn in to_push)
            for pn in patches[common_prefix:]:
                trans.push_patch(pn, iw)
        else:
            to_push = []
        new_base = trans.patches[patches[-1]]
        for pn in patches:
            trans.patches[pn] = None
        trans.applied = [pn for pn in trans.applied if pn not in patches]
        trans.base = new_base
        out.info('Committed %d patch%s' % (len(patches),
                                           ['es', ''][len(patches) == 1]))
        for pn in to_push:
            trans.push_patch(pn, iw)
    except transaction.TransactionHalted:
        pass
    return trans.run(iw)
Beispiel #2
0
def func(parser, options, args):
    """Pushes the given patches or the first unapplied onto the stack."""
    stack = directory.repository.current_stack
    iw = stack.repository.default_iw
    clean_iw = (not options.keep and iw) or None
    trans = transaction.StackTransaction(stack,
                                         'push',
                                         check_clean_iw=clean_iw)

    if options.number == 0:
        # explicitly allow this without any warning/error message
        return

    if not trans.unapplied:
        raise common.CmdException('No patches to push')

    if options.all:
        patches = list(trans.unapplied)
    elif options.number is not None:
        patches = trans.unapplied[:options.number]
    elif not args:
        patches = [trans.unapplied[0]]
    else:
        try:
            patches = common.parse_patches(args, trans.unapplied)
        except common.CmdException as e:
            try:
                patches = common.parse_patches(args, trans.applied)
            except common.CmdException:
                raise e
            else:
                raise common.CmdException(
                    'Patch%s already applied: %s' %
                    ('es' if len(patches) > 1 else '', ', '.join(patches)))

    assert patches

    if options.reverse:
        patches.reverse()

    if options.set_tree:
        for pn in patches:
            trans.push_tree(pn)
    else:
        try:
            if options.merged:
                merged = set(trans.check_merged(patches))
            else:
                merged = set()
            for pn in patches:
                trans.push_patch(pn,
                                 iw,
                                 allow_interactive=True,
                                 already_merged=pn in merged)
        except transaction.TransactionHalted:
            pass
    return trans.run(iw)
Beispiel #3
0
def get_patch(stack, given_patch):
    """Get the name of the patch we are to refresh."""
    if given_patch:
        patch_name = given_patch
        if not stack.patches.exists(patch_name):
            raise common.CmdException('%s: no such patch' % patch_name)
        return patch_name
    else:
        if not stack.patchorder.applied:
            raise common.CmdException(
                'Cannot refresh top patch because no patches are applied')
        return stack.patchorder.applied[-1]
Beispiel #4
0
def func(parser, options, args):
    """Generate a new commit for the current or given patch."""

    # Catch illegal argument combinations.
    path_limiting = bool(args or options.update)
    if options.index and path_limiting:
        raise common.CmdException(
            'Only full refresh is available with the --index option')

    if options.index and options.force:
        raise common.CmdException(
            'You cannot --force a full refresh when using --index mode')

    stack = directory.repository.current_stack
    patch_name = get_patch(stack, options.patch)
    paths = list_files(stack, patch_name, args, options.index, options.update)

    # Make sure there are no conflicts in the files we want to
    # refresh.
    if stack.repository.default_index.conflicts() & paths:
        raise common.CmdException(
            'Cannot refresh -- resolve conflicts first')

    # Make sure the index is clean before performing a full refresh
    if not options.index and not options.force:
        if not (stack.repository.default_index.is_clean(stack.head) or
                stack.repository.default_iw.worktree_clean()):
            raise common.CmdException(
                'The index is dirty. Did you mean --index? To force a full refresh use --force.')

    # Commit index to temp patch, and absorb it into the target patch.
    retval, temp_name = make_temp_patch(
        stack, patch_name, paths, temp_index = path_limiting)
    if retval != utils.STGIT_SUCCESS:
        return retval
    def edit_fun(cd):
        orig_msg = cd.message
        cd, failed_diff = edit.auto_edit_patch(
            stack.repository, cd, msg = options.message, contains_diff = False,
            author = options.author, committer = lambda p: p,
            sign_str = options.sign_str)
        assert not failed_diff
        if options.edit:
            cd, failed_diff = edit.interactive_edit_patch(
                stack.repository, cd, edit_diff = False,
                diff_flags = [], replacement_diff = None)
            assert not failed_diff
        if not options.no_verify and (options.edit or cd.message != orig_msg):
            cd = common.run_commit_msg_hook(stack.repository, cd, options.edit)
        return cd
    return absorb(stack, patch_name, temp_name, edit_fun,
                  annotate = options.annotate)
Beispiel #5
0
def func(parser, options, args):
    stack = directory.repository.current_stack
    iw = stack.repository.default_iw
    if len(args) >= 1:
        ref, patches = args[0], args[1:]
        state = log.get_log_entry(stack.repository, ref,
                                  stack.repository.rev_parse(ref))
    elif options.hard:
        iw.checkout_hard(stack.head.data.tree)
        return utils.STGIT_SUCCESS
    else:
        raise common.CmdException('Wrong options or number of arguments')

    trans = transaction.StackTransaction(
        stack,
        'reset',
        discard_changes=options.hard,
        allow_bad_head=True,
    )
    try:
        if patches:
            log.reset_stack_partially(trans, iw, state, patches)
        else:
            log.reset_stack(trans, iw, state)
    except transaction.TransactionHalted:
        pass
    return trans.run(iw, allow_bad_head=not patches)
Beispiel #6
0
def func(parser, options, args):
    stack = directory.repository.current_stack
    patches = common.parse_patches(args, list(stack.patchorder.all))
    if len(patches) < 2:
        raise common.CmdException('Need at least two patches')
    return _squash(stack, stack.repository.default_iw, options.name,
                   options.message, options.save_template, patches,
                   options.no_verify)
Beispiel #7
0
def func(parser, options, args):
    """Create a new patch."""
    stack = directory.repository.current_stack
    if stack.repository.default_index.conflicts():
        raise common.CmdException(
            'Cannot create a new patch -- resolve conflicts first')

    # Choose a name for the new patch -- or None, which means make one
    # up later when we've gotten hold of the commit message.
    if len(args) == 0:
        name = None
    elif len(args) == 1:
        name = args[0]
        if stack.patches.exists(name):
            raise common.CmdException('%s: patch already exists' % name)
        elif not stack.patches.is_name_valid(name):
            raise common.CmdException('Invalid patch name: "%s"' % name)
    else:
        parser.error('incorrect number of arguments')

    cd = CommitData(
        tree=stack.head.data.tree,
        parents=[stack.head],
        message='',
        author=Person.author(),
        committer=Person.committer(),
    )
    cd = common.update_commit_data(cd, options)

    if options.save_template:
        options.save_template(cd.message.encode('utf-8'))
        return utils.STGIT_SUCCESS

    if not options.no_verify:
        cd = common.run_commit_msg_hook(stack.repository, cd)

    if name is None:
        name = utils.make_patch_name(cd.message, stack.patches.exists)
        assert stack.patches.is_name_valid(name)

    # Write the new patch.
    stack.repository.default_iw
    trans = StackTransaction(stack, 'new: %s' % name)
    trans.patches[name] = stack.repository.commit(cd)
    trans.applied.append(name)
    return trans.run()
Beispiel #8
0
def func(parser, options, args):
    """Pop the given patches or the topmost one from the stack."""
    stack = directory.repository.current_stack
    iw = stack.repository.default_iw
    clean_iw = (not options.keep and not options.spill and iw) or None
    trans = transaction.StackTransaction(stack, 'pop', check_clean_iw=clean_iw)

    if options.number == 0:
        # explicitly allow this without any warning/error message
        return

    if not trans.applied:
        raise common.CmdException('No patches applied')

    if options.all:
        patches = trans.applied
    elif options.number is not None:
        # reverse it twice to also work with negative or bigger than
        # the length numbers
        patches = trans.applied[::-1][:options.number][::-1]
    elif not args:
        patches = [trans.applied[-1]]
    else:
        patches = common.parse_patches(args, trans.applied, ordered=True)

    if not patches:
        #FIXME: Why is this an error, and not just a noop ?
        raise common.CmdException('No patches to pop')

    if options.spill:
        if set(stack.patchorder.applied[-len(patches):]) != set(patches):
            parser.error('Can only spill topmost applied patches')
        iw = None  # don't touch index+worktree

    applied = [p for p in trans.applied if p not in set(patches)]
    unapplied = patches + trans.unapplied
    try:
        trans.reorder_patches(applied,
                              unapplied,
                              iw=iw,
                              allow_interactive=True)
    except transaction.TransactionException:
        pass
    return trans.run(iw)
Beispiel #9
0
 def check_and_append(c, n):
     next = n.data.parents
     try:
         [next] = next
     except ValueError:
         out.done()
         raise common.CmdException(
             'Trying to uncommit %s, which does not have exactly one parent'
             % n.sha1)
     return c.append(n)
Beispiel #10
0
def func(parser, options, args):
    """Sink patches down the stack.
    """
    stack = directory.repository.current_stack

    if options.to and options.to not in stack.patchorder.applied:
        raise common.CmdException(
            'Cannot sink below %s since it is not applied' % options.to
        )

    if len(args) > 0:
        patches = common.parse_patches(args, stack.patchorder.all)
    else:
        # current patch
        patches = list(stack.patchorder.applied[-1:])

    if not patches:
        raise common.CmdException('No patches to sink')
    if options.to and options.to in patches:
        raise common.CmdException('Cannot have a sinked patch as target')

    applied = [p for p in stack.patchorder.applied if p not in patches]
    if options.to:
        insert_idx = applied.index(options.to)
    else:
        insert_idx = 0
    applied = applied[:insert_idx] + patches + applied[insert_idx:]
    unapplied = [p for p in stack.patchorder.unapplied if p not in patches]

    iw = stack.repository.default_iw
    clean_iw = (not options.keep and iw) or None
    trans = transaction.StackTransaction(
        stack, 'sink', check_clean_iw=clean_iw
    )

    try:
        trans.reorder_patches(
            applied, unapplied, iw=iw, allow_interactive=True
        )
    except transaction.TransactionHalted:
        pass
    return trans.run(iw)
Beispiel #11
0
def func(parser, options, args):
    """Show the name of the previous patch
    """
    if len(args) != 0:
        parser.error('incorrect number of arguments')

    stack = directory.repository.get_stack(options.branch)
    applied = stack.patchorder.applied

    if applied and len(applied) >= 2:
        out.stdout(applied[-2])
    else:
        raise common.CmdException('Not enough applied patches')
Beispiel #12
0
def func(parser, options, args):
    """Show the name of the next patch
    """
    if len(args) != 0:
        parser.error('incorrect number of arguments')

    stack = directory.repository.get_stack(options.branch)
    unapplied = stack.patchorder.unapplied

    if unapplied:
        out.stdout(unapplied[0])
    else:
        raise common.CmdException('No unapplied patches')
Beispiel #13
0
def func(parser, options, args):
    if len(args) != 1:
        parser.error('incorrect number of arguments')
    patch = args[0]

    stack = directory.repository.current_stack
    iw = stack.repository.default_iw
    clean_iw = (not options.keep and iw) or None
    trans = transaction.StackTransaction(stack,
                                         'goto',
                                         check_clean_iw=clean_iw)

    if patch not in trans.all_patches:
        candidate = common.get_patch_from_list(patch, trans.all_patches)
        if candidate is None:
            raise common.CmdException('Patch "%s" does not exist' % patch)
        patch = candidate

    if patch in trans.applied:
        to_pop = set(trans.applied[trans.applied.index(patch) + 1:])
        assert not trans.pop_patches(lambda pn: pn in to_pop)
    elif patch in trans.unapplied:
        try:
            to_push = trans.unapplied[:trans.unapplied.index(patch) + 1]
            if options.merged:
                merged = set(trans.check_merged(to_push))
            else:
                merged = set()
            for pn in to_push:
                trans.push_patch(pn,
                                 iw,
                                 allow_interactive=True,
                                 already_merged=pn in merged)
        except transaction.TransactionHalted:
            pass
    else:
        raise common.CmdException('Cannot goto a hidden patch')
    return trans.run(iw)
Beispiel #14
0
def func(parser, options, args):
    stack = directory.repository.current_stack
    if options.number < 1:
        raise common.CmdException('Bad number of commands to undo')
    state = log.undo_state(stack, options.number)
    trans = transaction.StackTransaction(stack,
                                         'undo %d' % options.number,
                                         discard_changes=options.hard,
                                         allow_bad_head=True)
    try:
        log.reset_stack(trans, stack.repository.default_iw, state)
    except transaction.TransactionHalted:
        pass
    return trans.run(stack.repository.default_iw, allow_bad_head=True)
Beispiel #15
0
def _squash(stack, iw, name, msg, save_template, patches, no_verify=False):

    # If a name was supplied on the command line, make sure it's OK.
    def bad_name(pn):
        return pn not in patches and stack.patches.exists(pn)

    def get_name(cd):
        return name or utils.make_patch_name(cd.message, bad_name)

    if name and bad_name(name):
        raise common.CmdException('Patch name "%s" already taken')

    def make_squashed_patch(trans, new_commit_data):
        name = get_name(new_commit_data)
        trans.patches[name] = stack.repository.commit(new_commit_data)
        trans.unapplied.insert(0, name)

    trans = transaction.StackTransaction(stack, 'squash', allow_conflicts=True)
    push_new_patch = bool(set(patches) & set(trans.applied))
    try:
        new_commit_data = _squash_patches(trans, patches, msg, save_template,
                                          no_verify)
        if new_commit_data:
            # We were able to construct the squashed commit
            # automatically. So just delete its constituent patches.
            to_push = trans.delete_patches(lambda pn: pn in patches)
        else:
            # Automatic construction failed. So push the patches
            # consecutively, so that a second construction attempt is
            # guaranteed to work.
            to_push = trans.pop_patches(lambda pn: pn in patches)
            for pn in patches:
                trans.push_patch(pn, iw)
            new_commit_data = _squash_patches(trans, patches, msg,
                                              save_template, no_verify)
            assert not trans.delete_patches(lambda pn: pn in patches)
        make_squashed_patch(trans, new_commit_data)

        # Push the new patch if necessary, and any unrelated patches we've
        # had to pop out of the way.
        if push_new_patch:
            trans.push_patch(get_name(new_commit_data), iw)
        for pn in to_push:
            trans.push_patch(pn, iw)
    except SaveTemplateDone:
        trans.abort(iw)
        return
    except transaction.TransactionHalted:
        pass
    return trans.run(iw)
Beispiel #16
0
def func(parser, options, args):
    """Delete one or more patches."""
    stack = directory.repository.get_stack(options.branch)
    if options.branch:
        iw = None  # can't use index/workdir to manipulate another branch
    else:
        iw = stack.repository.default_iw
    if args and options.top:
        parser.error('Either --top or patches must be specified')
    elif args:
        patches = set(
            common.parse_patches(args, list(stack.patchorder.all),
                                 len(stack.patchorder.applied)))
    elif options.top:
        applied = stack.patchorder.applied
        if applied:
            patches = set([applied[-1]])
        else:
            raise common.CmdException('No patches applied')
    else:
        parser.error('No patches specified')

    if options.spill:
        if set(stack.patchorder.applied[-len(patches):]) != patches:
            parser.error('Can only spill topmost applied patches')
        iw = None  # don't touch index+worktree

    def allow_conflicts(trans):
        # Allow conflicts if the topmost patch stays the same.
        if stack.patchorder.applied:
            return (trans.applied
                    and trans.applied[-1] == stack.patchorder.applied[-1])
        else:
            return not trans.applied

    trans = transaction.StackTransaction(stack,
                                         'delete',
                                         allow_conflicts=allow_conflicts)
    try:
        to_push = trans.delete_patches(lambda pn: pn in patches)
        for pn in to_push:
            trans.push_patch(pn, iw)
    except transaction.TransactionHalted:
        pass
    return trans.run(iw)
Beispiel #17
0
def func(parser, options, args):
    """Clone the <repository> into the local <dir> and initialises the
    stack
    """
    if len(args) != 2:
        parser.error('incorrect number of arguments')

    repository = args[0]
    local_dir = args[1]

    if os.path.exists(local_dir):
        raise common.CmdException('"%s" exists. Remove it first' % local_dir)

    clone(repository, local_dir)
    os.chdir(local_dir)
    directory = common.DirectoryHasRepositoryLib()
    directory.setup()
    Stack.initialise(directory.repository)
Beispiel #18
0
def func(parser, options, args):
    """Unhide a range of patch in the series."""
    stack = directory.repository.current_stack
    trans = transaction.StackTransaction(stack, 'unhide')

    if not args:
        parser.error('No patches specified')

    patches = common.parse_patches(args, trans.all_patches)
    for p in patches:
        if p not in trans.hidden:
            raise common.CmdException('Patch "%s" not hidden' % p)

    applied = list(trans.applied)
    unapplied = trans.unapplied + patches
    hidden = [p for p in trans.hidden if p not in set(patches)]

    trans.reorder_patches(applied, unapplied, hidden)
    return trans.run()
Beispiel #19
0
def func(parser, options, args):
    """Reorder patches to make the named patch the topmost one.
    """
    if options.series and args:
        parser.error('<patches> cannot be used with --series')
    elif not options.series and not args:
        parser.error('incorrect number of arguments')

    stack = directory.repository.current_stack

    if options.series:
        if options.series == '-':
            f = io.open(sys.stdin.fileno())
        else:
            f = io.open(options.series)

        patches = []
        for line in f:
            patch = re.sub('#.*$', '', line).strip()
            if patch:
                patches.append(patch)
    else:
        patches = common.parse_patches(args, stack.patchorder.all)

    if not patches:
        raise common.CmdException('No patches to float')

    applied = [p for p in stack.patchorder.applied if p not in patches] + \
            patches
    unapplied = [p for p in stack.patchorder.unapplied if p not in patches]

    iw = stack.repository.default_iw
    clean_iw = (not options.keep and iw) or None
    trans = transaction.StackTransaction(stack,
                                         'float',
                                         check_clean_iw=clean_iw)

    try:
        trans.reorder_patches(applied, unapplied, iw=iw)
    except transaction.TransactionHalted:
        pass
    return trans.run(iw)
Beispiel #20
0
def func(parser, options, args):
    """Export a range of patches.
    """
    stack = directory.repository.get_stack(options.branch)

    if options.dir:
        dirname = options.dir
    else:
        dirname = 'patches-%s' % stack.name
        directory.cd_to_topdir()

    if not options.branch and git.local_changes():
        out.warn('Local changes in the tree;'
                 ' you might want to commit them first')

    applied = stack.patchorder.applied
    unapplied = stack.patchorder.unapplied
    if len(args) != 0:
        patches = common.parse_patches(args, applied + unapplied, len(applied))
    else:
        patches = applied

    num = len(patches)
    if num == 0:
        raise common.CmdException('No patches applied')

    zpadding = len(str(num))
    if zpadding < 2:
        zpadding = 2

    # get the template
    if options.template:
        with io.open(options.template, 'r') as f:
            tmpl = f.read()
    else:
        tmpl = templates.get_template('patchexport.tmpl')
        if not tmpl:
            tmpl = ''

    if not options.stdout:
        if not os.path.isdir(dirname):
            os.makedirs(dirname)
        series = io.open(os.path.join(dirname, 'series'), 'w')
        # note the base commit for this series
        base_commit = stack.base.sha1
        print('# This series applies on GIT commit %s' % base_commit,
              file=series)

    for patch_no, p in enumerate(patches, 1):
        pname = p
        if options.patch:
            pname = '%s.patch' % pname
        elif options.extension:
            pname = '%s.%s' % (pname, options.extension)
        if options.numbered:
            pname = '%s-%s' % (str(patch_no).zfill(zpadding), pname)
        pfile = os.path.join(dirname, pname)
        if not options.stdout:
            print(pname, file=series)

        # get the patch description
        patch = stack.patches.get(p)
        cd = patch.commit.data

        descr = cd.message.strip()
        descr_lines = descr.split('\n')

        short_descr = descr_lines[0].rstrip()
        long_descr = '\n'.join(descr_lines[1:]).strip()

        diff = stack.repository.diff_tree(cd.parent.data.tree, cd.tree,
                                          options.diff_flags)

        tmpl_dict = {
            'description': descr,
            'shortdescr': short_descr,
            'longdescr': long_descr,
            'diffstat': gitlib.diffstat(diff).rstrip(),
            'authname': cd.author.name,
            'authemail': cd.author.email,
            'authdate': cd.author.date.isoformat(),
            'commname': cd.committer.name,
            'commemail': cd.committer.email
        }

        try:
            descr = templates.specialize_template(tmpl, tmpl_dict)
        except KeyError as err:
            raise common.CmdException('Unknown patch template variable: %s' %
                                      err)
        except TypeError:
            raise common.CmdException('Only "%(name)s" variables are '
                                      'supported in the patch template')

        if options.stdout:
            if hasattr(sys.stdout, 'buffer'):
                f = sys.stdout.buffer
            else:
                f = sys.stdout
        else:
            f = io.open(pfile, 'wb')

        if options.stdout and num > 1:
            f.write('\n'.join(['-' * 79, patch.name, '-' * 79,
                               '']).encode('utf-8'))

        f.write(descr)
        f.write(diff)
        if not options.stdout:
            f.close()

    if not options.stdout:
        series.close()
Beispiel #21
0
def func(parser, options, args):
    """Show the patch series
    """
    if options.all and options.short:
        raise common.CmdException('combining --all and --short is meaningless')

    stack = directory.repository.get_stack(options.branch)
    if options.missing:
        cmp_stack = stack
        stack = directory.repository.get_stack(options.missing)

    # current series patches
    applied = unapplied = hidden = ()
    if options.applied or options.unapplied or options.hidden:
        if options.all:
            raise common.CmdException('--all cannot be used with'
                                      ' --applied/unapplied/hidden')
        if options.applied:
            applied = stack.patchorder.applied
        if options.unapplied:
            unapplied = stack.patchorder.unapplied
        if options.hidden:
            hidden = stack.patchorder.hidden
    elif options.all:
        applied = stack.patchorder.applied
        unapplied = stack.patchorder.unapplied
        hidden = stack.patchorder.hidden
    else:
        applied = stack.patchorder.applied
        unapplied = stack.patchorder.unapplied

    if options.missing:
        cmp_patches = cmp_stack.patchorder.all
    else:
        cmp_patches = ()

    # the filtering range covers the whole series
    if args:
        show_patches = parse_patches(args, applied + unapplied + hidden,
                                     len(applied))
    else:
        show_patches = applied + unapplied + hidden

    # missing filtering
    show_patches = [p for p in show_patches if p not in cmp_patches]

    # filter the patches
    applied = [p for p in applied if p in show_patches]
    unapplied = [p for p in unapplied if p in show_patches]
    hidden = [p for p in hidden if p in show_patches]

    if options.short:
        nr = int(config.get('stgit.shortnr'))
        if len(applied) > nr:
            applied = applied[-(nr + 1):]
        n = len(unapplied)
        if n > nr:
            unapplied = unapplied[:nr]
        elif n < nr:
            hidden = hidden[:nr - n]

    patches = applied + unapplied + hidden

    if options.count:
        out.stdout(len(patches))
        return

    if not patches:
        return

    if options.showbranch:
        branch_str = stack.name + ':'
    else:
        branch_str = ''

    max_len = 0
    if len(patches) > 0:
        max_len = max([len(i + branch_str) for i in patches])

    if applied:
        for p in applied[:-1]:
            __print_patch(
                stack,
                p,
                branch_str,
                '+ ',
                max_len,
                options,
                config.get("stgit.color.applied"),
            )
        __print_patch(
            stack,
            applied[-1],
            branch_str,
            '> ',
            max_len,
            options,
            config.get("stgit.color.current"),
        )

    for p in unapplied:
        __print_patch(
            stack,
            p,
            branch_str,
            '- ',
            max_len,
            options,
            config.get("stgit.color.unapplied"),
        )

    for p in hidden:
        __print_patch(
            stack,
            p,
            branch_str,
            '! ',
            max_len,
            options,
            config.get("stgit.color.hidden"),
        )
Beispiel #22
0
def func(parser, options, args):
    """Publish the stack changes."""
    repository = directory.repository
    stack = repository.get_stack(options.branch)

    if not args:
        public_ref = common.get_public_ref(stack.name)
    elif len(args) == 1:
        public_ref = args[0]
    else:
        parser.error('incorrect number of arguments')

    if not public_ref.startswith('refs/heads/'):
        public_ref = 'refs/heads/' + public_ref

    # just clone the stack if the public ref does not exist
    if not repository.refs.exists(public_ref):
        if options.unpublished or options.last:
            raise common.CmdException('"%s" does not exist' % public_ref)
        repository.refs.set(public_ref, stack.head, 'publish')
        out.info('Created "%s"' % public_ref)
        return

    public_head = repository.refs.get(public_ref)
    public_tree = public_head.data.tree

    # find the last published patch
    if options.last:
        last = __get_last(stack, public_tree)
        if not last:
            raise common.CmdException(
                'Unable to find the last published patch '
                '(possibly rebased stack)'
            )
        out.info('%s' % last)
        return

    # check for same tree (already up to date)
    if public_tree.sha1 == stack.head.data.tree.sha1:
        out.info('"%s" already up to date' % public_ref)
        return

    # check for unpublished patches
    if options.unpublished:
        published = set(__get_published(stack, public_tree))
        for p in stack.patchorder.applied:
            if p not in published:
                out.stdout(p)
        return

    if options.overwrite:
        repository.refs.set(public_ref, stack.head, 'publish')
        out.info('Overwrote "%s"' % public_ref)
        return

    # check for rebased stack. In this case we emulate a merge with the stack
    # base by setting two parents.
    merge_bases = set(repository.get_merge_bases(public_head, stack.base))
    if public_head in merge_bases:
        # fast-forward the public ref
        repository.refs.set(public_ref, stack.head, 'publish')
        out.info('Fast-forwarded "%s"' % public_ref)
        return
    if stack.base not in merge_bases:
        message = 'Merge %s into %s' % (
            repository.describe(stack.base).strip(),
            utils.strip_prefix('refs/heads/', public_ref),
        )
        public_head = __create_commit(
            repository,
            stack.head.data.tree,
            [public_head, stack.base],
            options,
            message,
        )
        repository.refs.set(public_ref, public_head, 'publish')
        out.info('Merged the stack base into "%s"' % public_ref)
        return

    # check for new patches from the last publishing. This is done by checking
    # whether the public tree is the same as the bottom of the checked patch.
    # If older patches were modified, new patches cannot be detected. The new
    # patches and their metadata are pushed directly to the published head.
    for p in stack.patchorder.applied:
        pc = stack.patches.get(p).commit
        if public_tree.sha1 == pc.data.parent.data.tree.sha1:
            if pc.data.is_nochange():
                out.info('Ignored new empty patch "%s"' % p)
                continue
            cd = pc.data.set_parent(public_head)
            public_head = repository.commit(cd)
            public_tree = public_head.data.tree
            out.info('Published new patch "%s"' % p)

    # create a new commit (only happens if no new patches are detected)
    if public_tree.sha1 != stack.head.data.tree.sha1:
        public_head = __create_commit(repository, stack.head.data.tree,
                                      [public_head], options)

    # update the public head
    repository.refs.set(public_ref, public_head, 'publish')
    out.info('Updated "%s"' % public_ref)
Beispiel #23
0
def func(parser, options, args):
    """Uncommit a number of patches.
    """
    stack = directory.repository.current_stack
    if options.to:
        if options.number:
            parser.error('cannot give both --to and --number')
        if len(args) != 0:
            parser.error('cannot specify patch name with --to')
        patch_nr = patchnames = None
        to_commit = stack.repository.rev_parse(options.to)
        # check whether the --to commit is on a different branch
        merge_bases = directory.repository.get_merge_bases(
            to_commit, stack.base)
        if to_commit not in merge_bases:
            to_commit = merge_bases[0]
            options.exclusive = True
    elif options.number:
        if options.number <= 0:
            parser.error('invalid value passed to --number')
        patch_nr = options.number
        if len(args) == 0:
            patchnames = None
        elif len(args) == 1:
            # prefix specified
            patchnames = [
                '%s%d' % (args[0], i) for i in range(patch_nr, 0, -1)
            ]
        else:
            parser.error('when using --number, specify at most one patch name')
    elif len(args) == 0:
        patchnames = None
        patch_nr = 1
    else:
        patchnames = args
        patch_nr = len(patchnames)

    def check_and_append(c, n):
        next = n.data.parents
        try:
            [next] = next
        except ValueError:
            out.done()
            raise common.CmdException(
                'Trying to uncommit %s, which does not have exactly one parent'
                % n.sha1)
        return c.append(n)

    commits = []
    next_commit = stack.base
    if patch_nr:
        out.start('Uncommitting %d patches' % patch_nr)
        for i in range(patch_nr):
            check_and_append(commits, next_commit)
            next_commit = next_commit.data.parent
    else:
        if options.exclusive:
            out.start('Uncommitting to %s (exclusive)' % to_commit.sha1)
        else:
            out.start('Uncommitting to %s' % to_commit.sha1)
        while True:
            if next_commit == to_commit:
                if not options.exclusive:
                    check_and_append(commits, next_commit)
                break
            check_and_append(commits, next_commit)
            next_commit = next_commit.data.parent
        patch_nr = len(commits)

    taken_names = set(stack.patchorder.all)
    if patchnames:
        for pn in patchnames:
            if pn in taken_names:
                raise common.CmdException('Patch name "%s" already taken' % pn)
            taken_names.add(pn)
    else:
        patchnames = []
        for c in reversed(commits):
            pn = utils.make_patch_name(c.data.message,
                                       lambda pn: pn in taken_names)
            patchnames.append(pn)
            taken_names.add(pn)
        patchnames.reverse()

    trans = transaction.StackTransaction(stack,
                                         'uncommit',
                                         allow_conflicts=True,
                                         allow_bad_head=True)
    for commit, pn in zip(commits, patchnames):
        trans.patches[pn] = commit
    trans.applied = list(reversed(patchnames)) + trans.applied
    trans.run(set_head=False)
    out.done()
Beispiel #24
0
def func(parser, options, args):
    """Edit the given patch or the current one.
    """
    stack = directory.repository.current_stack

    if len(args) == 0:
        if not stack.patchorder.applied:
            raise common.CmdException(
                'Cannot edit top patch, because no patches are applied')
        patchname = stack.patchorder.applied[-1]
    elif len(args) == 1:
        [patchname] = args
        if not stack.patches.exists(patchname):
            raise common.CmdException('%s: no such patch' % patchname)
    else:
        parser.error('Cannot edit more than one patch')

    cd = orig_cd = stack.patches.get(patchname).commit.data

    if options.set_tree:
        cd = cd.set_tree(
            stack.repository.rev_parse(
                options.set_tree, discard_stderr=True, object_type='tree'
            )
        )

    cd, failed_diff = edit.auto_edit_patch(
        stack.repository, cd,
        msg=(None if options.message is None else
             options.message.encode('utf-8')),
        contains_diff=True,
        author=options.author,
        committer=lambda p: p,
        sign_str=options.sign_str)

    if options.save_template:
        options.save_template(
            edit.patch_desc(stack.repository, cd,
                            options.diff, options.diff_flags, failed_diff))
        return utils.STGIT_SUCCESS

    use_editor = cd == orig_cd or options.edit
    if use_editor:
        cd, failed_diff = edit.interactive_edit_patch(
            stack.repository,
            cd,
            options.diff,
            options.diff_flags,
            failed_diff,
        )

    def failed(reason='Edited patch did not apply.'):
        fn = '.stgit-failed.patch'
        with io.open(fn, 'wb') as f:
            f.write(edit.patch_desc(stack.repository, cd,
                                    options.diff, options.diff_flags,
                                    failed_diff))
        out.error(reason,
                  'The patch has been saved to "%s".' % fn)
        return utils.STGIT_COMMAND_ERROR

    # If we couldn't apply the patch, fail without even trying to
    # effect any of the changes.
    if failed_diff:
        return failed()

    if not options.no_verify and (use_editor or cd.message != orig_cd.message):
        try:
            cd = common.run_commit_msg_hook(stack.repository, cd, use_editor)
        except Exception:
            if options.diff:
                failed('The commit-msg hook failed.')
            raise

    # The patch applied, so now we have to rewrite the StGit patch
    # (and any patches on top of it).
    iw = stack.repository.default_iw
    trans = transaction.StackTransaction(stack, 'edit', allow_conflicts=True)
    if patchname in trans.applied:
        popped = trans.applied[trans.applied.index(patchname) + 1:]
        assert not trans.pop_patches(lambda pn: pn in popped)
    else:
        popped = []
    trans.patches[patchname] = stack.repository.commit(cd)
    try:
        for pn in popped:
            if options.set_tree:
                trans.push_tree(pn)
            else:
                trans.push_patch(pn, iw, allow_interactive=True)
    except transaction.TransactionHalted:
        pass
    try:
        # Either a complete success, or a conflict during push. But in
        # either case, we've successfully effected the edits the user
        # asked us for.
        return trans.run(iw)
    except transaction.TransactionException:
        # Transaction aborted -- we couldn't check out files due to
        # dirty index/worktree. The edits were not carried out.
        return failed()