Example #1
0
def compat_log_entry(msg):
    """Write a new log entry. (Convenience function intended for use by
    code not yet converted to the new infrastructure.)"""
    repo = default_repo()
    try:
        stack = repo.get_stack(repo.current_branch_name)
    except libstack.StackException, e:
        out.warn(str(e), 'Could not write to stack log')
Example #2
0
    def delete(self, force=False, cleanup=False):
        """Deletes an stgit series
        """
        if self.is_initialised():
            patches = (
                self.get_unapplied()
                + self.get_applied()
                + self.get_hidden()
            )
            if not force and patches:
                raise StackException(
                    'Cannot %s: the series still contains patches' %
                    ('delete', 'clean up')[cleanup])
            for p in patches:
                self.get_patch(p).delete()

            # remove the trash directory if any
            if os.path.exists(self.__trash_dir):
                for fname in os.listdir(self.__trash_dir):
                    os.remove(os.path.join(self.__trash_dir, fname))
                os.rmdir(self.__trash_dir)

            # FIXME: find a way to get rid of those manual removals
            # (move functionality to StgitObject ?)
            if os.path.exists(self.__applied_file):
                os.remove(self.__applied_file)
            if os.path.exists(self.__unapplied_file):
                os.remove(self.__unapplied_file)
            if os.path.exists(self.__hidden_file):
                os.remove(self.__hidden_file)
            if os.path.exists(self._dir() + '/orig-base'):
                os.remove(self._dir() + '/orig-base')

            if not os.listdir(self.__patch_dir):
                os.rmdir(self.__patch_dir)
            else:
                out.warn('Patch directory %s is not empty' % self.__patch_dir)

            try:
                os.removedirs(self._dir())
            except OSError:
                raise StackException('Series directory %s is not empty'
                                     % self._dir())

        if not cleanup:
            try:
                git.delete_branch(self.get_name())
            except git.GitException:
                out.warn('Could not delete branch "%s"' % self.get_name())
            config.remove_section('branch.%s' % self.get_name())

        config.remove_section('branch.%s.stgit' % self.get_name())
Example #3
0
def get_log_mode(spec):
    if ':' not in spec:
        spec += ':'
    (log_mode, outfile) = spec.split(':', 1)
    all_log_modes = ['debug', 'profile']
    if log_mode and log_mode not in all_log_modes:
        out.warn(('Unknown log mode "%s" specified in $STGIT_SUBPROCESS_LOG.' %
                  log_mode), 'Valid values are: %s' % ', '.join(all_log_modes))
    if outfile:
        f = MessagePrinter(io.open(outfile, 'a', encoding='utf-8'))
    else:
        f = out
    return (log_mode, f)
Example #4
0
 def delete(self, keep_log=False):
     if os.path.isdir(self._dir()):
         for f in os.listdir(self._dir()):
             os.remove(os.path.join(self._dir(), f))
         os.rmdir(self._dir())
     else:
         out.warn('Patch directory "%s" does not exist' % self._dir())
     try:
         # the reference might not exist if the repository was corrupted
         git.delete_ref(self.__top_ref)
     except git.GitException as e:
         out.warn(str(e))
     if not keep_log and git.ref_exists(self.__log_ref):
         git.delete_ref(self.__log_ref)
Example #5
0
File: stack.py Project: snits/stgit
 def delete(self, keep_log = False):
     if os.path.isdir(self._dir()):
         for f in os.listdir(self._dir()):
             os.remove(os.path.join(self._dir(), f))
         os.rmdir(self._dir())
     else:
         out.warn('Patch directory "%s" does not exist' % self._dir())
     try:
         # the reference might not exist if the repository was corrupted
         git.delete_ref(self.__top_ref)
     except git.GitException as e:
         out.warn(str(e))
     if not keep_log and git.ref_exists(self.__log_ref):
         git.delete_ref(self.__log_ref)
Example #6
0
File: run.py Project: snits/stgit
def get_log_mode(spec):
    if ':' not in spec:
        spec += ':'
    (log_mode, outfile) = spec.split(':', 1)
    all_log_modes = ['debug', 'profile']
    if log_mode and log_mode not in all_log_modes:
        out.warn(('Unknown log mode "%s" specified in $STGIT_SUBPROCESS_LOG.'
                  % log_mode),
                 'Valid values are: %s' % ', '.join(all_log_modes))
    if outfile:
        f = MessagePrinter(open(outfile, 'a'))
    else:
        f = out
    return (log_mode, f)
Example #7
0
File: log.py Project: snits/stgit
def compat_log_entry(msg):
    """Write a new log entry. (Convenience function intended for use by
    code not yet converted to the new infrastructure.)"""
    try:
        repo = default_repo()
        stack = repo.get_stack(repo.current_branch_name)
    except (StackException, git.RepositoryException) as e:
        out.warn(str(e), 'Could not write to stack log')
    else:
        if repo.default_index.conflicts() and stack.patchorder.applied:
            log_entry(Fakestack(stack), msg)
            log_entry(stack, msg + ' (CONFLICT)')
        else:
            log_entry(stack, msg)
Example #8
0
def compat_log_entry(msg):
    """Write a new log entry. (Convenience function intended for use by
    code not yet converted to the new infrastructure.)"""
    try:
        repo = default_repo()
        stack = repo.get_stack(repo.current_branch_name)
    except (StackException, RepositoryException) as e:
        out.warn(str(e), 'Could not write to stack log')
    else:
        if repo.default_index.conflicts() and stack.patchorder.applied:
            log_entry(Fakestack(stack), msg)
            log_entry(stack, msg + ' (CONFLICT)')
        else:
            log_entry(stack, msg)
Example #9
0
File: stack.py Project: snits/stgit
    def delete(self, force = False, cleanup = False):
        """Deletes an stgit series
        """
        if self.is_initialised():
            patches = self.get_unapplied() + self.get_applied() + \
                    self.get_hidden()
            if not force and patches:
                raise StackException(
                    'Cannot %s: the series still contains patches' %
                    ('delete', 'clean up')[cleanup])
            for p in patches:
                self.get_patch(p).delete()

            # remove the trash directory if any
            if os.path.exists(self.__trash_dir):
                for fname in os.listdir(self.__trash_dir):
                    os.remove(os.path.join(self.__trash_dir, fname))
                os.rmdir(self.__trash_dir)

            # FIXME: find a way to get rid of those manual removals
            # (move functionality to StgitObject ?)
            if os.path.exists(self.__applied_file):
                os.remove(self.__applied_file)
            if os.path.exists(self.__unapplied_file):
                os.remove(self.__unapplied_file)
            if os.path.exists(self.__hidden_file):
                os.remove(self.__hidden_file)
            if os.path.exists(self._dir()+'/orig-base'):
                os.remove(self._dir()+'/orig-base')

            if not os.listdir(self.__patch_dir):
                os.rmdir(self.__patch_dir)
            else:
                out.warn('Patch directory %s is not empty' % self.__patch_dir)

            try:
                os.removedirs(self._dir())
            except OSError:
                raise StackException('Series directory %s is not empty'
                                     % self._dir())

        if not cleanup:
            try:
                git.delete_branch(self.get_name())
            except git.GitException:
                out.warn('Could not delete branch "%s"' % self.get_name())
            config.remove_section('branch.%s' % self.get_name())

        config.remove_section('branch.%s.stgit' % self.get_name())
Example #10
0
def log_stack_state(stack, msg):
    """Write a new metadata entry for the stack."""
    try:
        prev_state = get_stack_state(stack.repository, stack.state_ref)
    except KeyError:
        prev_state = None

    try:
        new_state = StackState.from_stack(prev_state.commit, stack)
    except LogException as e:
        out.warn(str(e), 'No log entry written.')
    else:
        if not prev_state or not new_state.same_state(prev_state):
            state_commit = new_state.commit_state(stack.repository, msg)
            stack.repository.refs.set(stack.state_ref, state_commit, msg)
Example #11
0
def log_entry(stack, msg):
    """Write a new log entry for the stack."""
    ref = log_ref(stack.name)
    try:
        last_log_commit = stack.repository.refs.get(ref)
    except KeyError:
        last_log_commit = None
    try:
        if last_log_commit:
            last_log = get_log_entry(stack.repository, ref, last_log_commit)
        else:
            last_log = None
        new_log = LogEntry.from_stack(last_log, stack, msg)
    except LogException, e:
        out.warn(str(e), 'No log entry written.')
        return
Example #12
0
def log_entry(stack, msg):
    """Write a new log entry for the stack."""
    ref = log_ref(stack.name)
    try:
        last_log_commit = stack.repository.refs.get(ref)
    except KeyError:
        last_log_commit = None
    try:
        if last_log_commit:
            last_log = get_log_entry(stack.repository, ref, last_log_commit)
        else:
            last_log = None
        new_log = LogEntry.from_stack(last_log, stack, msg)
    except LogException as e:
        out.warn(str(e), 'No log entry written.')
        return
    if last_log and same_state(last_log, new_log):
        return
    new_log.write_commit()
    stack.repository.refs.set(ref, new_log.commit, msg)
Example #13
0
def func(parser, options, args):
    """Hide a range of patch in the series."""
    stack = directory.repository.current_stack
    trans = transaction.StackTransaction(stack, 'hide')

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

    patches = common.parse_patches(args, trans.all_patches)
    for p in patches:
        if p in trans.hidden:
            out.warn('Patch "%s" already hidden' % p)
    patches = [p for p in patches if p not in set(trans.hidden)]

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

    trans.reorder_patches(applied, unapplied, hidden)
    return trans.run()
Example #14
0
def fetch_head():
    """Return the git id for the tip of the parent branch as left by
    'git fetch'.
    """
    fetch_head = None
    stream = open(os.path.join(basedir.get(), 'FETCH_HEAD'), "r")
    for line in stream:
        # Only consider lines not tagged not-for-merge
        m = re.match('^([^\t]*)\t\t', line)
        if m:
            if fetch_head:
                raise GitException(
                    'StGit does not support multiple FETCH_HEAD')
            else:
                fetch_head = m.group(1)
    stream.close()

    if not fetch_head:
        out.warn('No for-merge remote head found in FETCH_HEAD')

    # here we are sure to have a single fetch_head
    return fetch_head
Example #15
0
File: git.py Project: snits/stgit
def fetch_head():
    """Return the git id for the tip of the parent branch as left by
    'git fetch'.
    """

    fetch_head=None
    stream = open(os.path.join(basedir.get(), 'FETCH_HEAD'), "r")
    for line in stream:
        # Only consider lines not tagged not-for-merge
        m = re.match('^([^\t]*)\t\t', line)
        if m:
            if fetch_head:
                raise GitException('StGit does not support multiple FETCH_HEAD')
            else:
                fetch_head=m.group(1)
    stream.close()

    if not fetch_head:
        out.warn('No for-merge remote head found in FETCH_HEAD')

    # here we are sure to have a single fetch_head
    return fetch_head
Example #16
0
def absorb(stack, patch_name, temp_name, edit_fun, annotate=None):
    """Absorb the temp patch into the target patch."""
    log_msg = 'refresh'
    if annotate:
        log_msg += '\n\n' + annotate

    check_head_top_equal(stack)
    trans = StackTransaction(stack)
    iw = stack.repository.default_iw
    if patch_name in trans.applied:
        absorbed = absorb_applied(trans, iw, patch_name, temp_name, edit_fun)
    else:
        absorbed = absorb_unapplied(trans, iw, patch_name, temp_name, edit_fun)

    r = trans.execute(log_msg, iw)

    if not absorbed:
        out.warn(
            'The new changes did not apply cleanly to %s.' % patch_name,
            'They were saved in %s.' % temp_name,
        )

    return r
Example #17
0
def func(parser, options, args):
    """Repair inconsistencies in StGit metadata."""

    orig_applied = crt_series.get_applied()
    orig_unapplied = crt_series.get_unapplied()
    orig_hidden = crt_series.get_hidden()

    if crt_series.get_protected():
        raise CmdException(
            'This branch is protected. Modification is not permitted.')

    # Find commits that aren't patches, and applied patches.
    head = git.get_commit(git.get_head()).get_id_hash()
    commits, patches = read_commit_dag(crt_series.get_name())
    c = commits[head]
    patchify = []  # commits to definitely patchify
    maybe_patchify = []  # commits to patchify if we find a patch below them
    applied = []
    while len(c.parents) == 1:
        parent, = c.parents
        if c.patch:
            applied.append(c)
            patchify.extend(maybe_patchify)
            maybe_patchify = []
        else:
            maybe_patchify.append(c)
        c = parent
    applied.reverse()
    patchify.reverse()

    # Find patches hidden behind a merge.
    merge = c
    todo = set([c])
    seen = set()
    hidden = set()
    while todo:
        c = todo.pop()
        seen.add(c)
        todo |= c.parents - seen
        if c.patch:
            hidden.add(c)
    if hidden:
        out.warn(('%d patch%s are hidden below the merge commit' %
                  (len(hidden), ['es', ''][len(hidden) == 1])),
                 '%s,' % merge.id, 'and will be considered unapplied.')

    # Make patches of any linear sequence of commits on top of a patch.
    names = set(p.patch for p in patches)

    def name_taken(name):
        return name in names

    if applied and patchify:
        out.start('Creating %d new patch%s' %
                  (len(patchify), ['es', ''][len(patchify) == 1]))
        for p in patchify:
            name = make_patch_name(p.commit.get_log(), name_taken)
            out.info('Creating patch %s from commit %s' % (name, p.id))
            aname, amail, adate = name_email_date(p.commit.get_author())
            cname, cmail, cdate = name_email_date(p.commit.get_committer())
            parent, = p.parents
            crt_series.new_patch(
                name,
                can_edit=False,
                commit=False,
                top=p.id,
                bottom=parent.id,
                message=p.commit.get_log(),
                author_name=aname,
                author_email=amail,
                author_date=adate,
                committer_name=cname,
                committer_email=cmail,
            )
            p.patch = name
            applied.append(p)
            names.add(name)
        out.done()

    # Figure out hidden
    orig_patches = orig_applied + orig_unapplied + orig_hidden
    orig_applied_name_set = set(orig_applied)
    orig_unapplied_name_set = set(orig_unapplied)
    orig_hidden_name_set = set(orig_hidden)
    orig_patches_name_set = set(orig_patches)
    hidden = [p for p in patches if p.patch in orig_hidden_name_set]

    # Write the applied/unapplied files.
    out.start('Checking patch appliedness')
    unapplied = patches - set(applied) - set(hidden)
    applied_name_set = set(p.patch for p in applied)
    unapplied_name_set = set(p.patch for p in unapplied)
    hidden_name_set = set(p.patch for p in hidden)
    patches_name_set = set(p.patch for p in patches)
    for name in orig_patches_name_set - patches_name_set:
        out.info('%s is gone' % name)
    for name in applied_name_set - orig_applied_name_set:
        out.info('%s is now applied' % name)
    for name in unapplied_name_set - orig_unapplied_name_set:
        out.info('%s is now unapplied' % name)
    for name in hidden_name_set - orig_hidden_name_set:
        out.info('%s is now hidden' % name)
    orig_order = dict(zip(orig_patches, range(len(orig_patches))))

    def patchname_key(p):
        i = orig_order.get(p, len(orig_order))
        return i, p

    crt_series.set_applied(p.patch for p in applied)
    crt_series.set_unapplied(sorted(unapplied_name_set, key=patchname_key))
    crt_series.set_hidden(sorted(hidden_name_set, key=patchname_key))
    out.done()
Example #18
0
 def info_msg():
     out.warn(
         'The new changes did not apply cleanly to %s.' % patch_name,
         'They were saved in %s.' % temp_name)
Example #19
0
def func(parser, options, args):
    """Pull the changes from a remote repository"""
    repository = directory.repository
    iw = repository.default_iw
    stack = repository.get_stack()
    policy = config.get('branch.%s.stgit.pull-policy' %
                        stack.name) or config.get('stgit.pull-policy')

    if policy not in ['pull', 'fetch-rebase', 'rebase']:
        raise GitConfigException('Unsupported pull-policy "%s"' % policy)

    remote_name = None
    if policy == 'rebase':
        # parent is local
        if len(args) == 1:
            parser.error(
                'specifying a repository is meaningless for policy="%s"' %
                (policy, ))
        elif len(args) > 0:
            parser.error('incorrect number of arguments')
    else:
        # parent is remote
        if len(args) > 1:
            parser.error('incorrect number of arguments')

        if len(args) >= 1:
            remote_name = args[0]
        else:
            remote_name = stack.parent_remote

    if policy in ['pull', 'fetch-rebase'] and remote_name is None:
        parser.error(
            'There is no tracking information for the current branch.\n'
            'Please specify the remote repository to pull from.')

    if stack.protected:
        raise CmdException('This branch is protected. Pulls are not permitted')

    applied = stack.patchorder.applied

    retval = prepare_rebase(stack, 'pull')
    if retval:
        return retval

    # pull the remote changes
    if policy == 'pull':
        out.info('Pulling from "%s"' % remote_name)
        pull(repository, remote_name)
    elif policy == 'fetch-rebase':
        out.info('Fetching from "%s"' % remote_name)
        fetch(repository, remote_name)
        try:
            target = repository.rev_parse('FETCH_HEAD')
        except RepositoryException:
            out.error('Could not find the remote head to rebase onto - '
                      'fix branch.%s.merge in .git/config' % stack.name)
            out.error('Pushing any patches back...')
            post_rebase(stack, applied, 'pull', check_merged=False)
            raise

        rebase(stack, iw, target)
    elif policy == 'rebase':
        value = config.get('branch.%s.stgit.parentbranch' % stack.name)
        if value:
            parent_commit = git_commit(value, repository)
        else:
            try:
                parent_commit = repository.rev_parse('heads/origin')
            except RepositoryException:
                raise CmdException('Cannot find a parent branch for "%s"' %
                                   stack.name)
            else:
                out.warn(
                    'No parent branch declared for stack "%s", defaulting to'
                    '"heads/origin".' % stack.name,
                    'Consider setting "branch.%s.stgit.parentbranch" with '
                    '"git config".' % stack.name,
                )
        rebase(stack, iw, parent_commit)

    if not options.nopush:
        post_rebase(stack, applied, 'pull', check_merged=options.merged)

    # maybe tidy up
    if config.getbool('stgit.keepoptimized'):
        repository.repack()
Example #20
0
 def info_msg():
     out.warn("The new changes did not apply cleanly to %s." % patch_name, "They were saved in %s." % temp_name)
Example #21
0
def func(parser, options, args):
    """Publish the stack changes."""
    out.warn(
        'DEPRECATED: stg publish will be removed in a future version of StGit.'
    )
    repository = directory.repository
    stack = repository.get_stack(options.branch)

    if not args:
        public_ref = 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 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 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)
def func(parser, options, args):
    """Repair inconsistencies in StGit metadata."""
    if args:
        parser.error('incorrect number of arguments')

    repository = directory.repository
    stack = repository.get_stack()

    if stack.protected:
        raise CmdException(
            'This branch is protected. Modification is not permitted.')

    patchorder = stack.patchorder
    patches = list(patchorder.all)

    # Find commits that aren't patches, and applied patches.
    patchify = []  # commits to definitely patchify
    maybe_patchify = []  # commits to patchify if we find a patch below them
    applied = []
    c = stack.head
    while len(c.data.parents) == 1:
        for pn in patchorder.all:
            if stack.patches[pn] == c:
                applied.append(pn)
                patchify.extend(maybe_patchify)
                maybe_patchify = []
                break
        else:
            maybe_patchify.append(c)

        c = c.data.parent

        if stack.base == c:
            # Reaching the original stack base can happen if, for example, the first
            # applied patch is amended. In this case, any commits descending from the
            # stack base should be patchified.
            patchify.extend(maybe_patchify)
            maybe_patchify = []

            # Once the base commit has been found, we know that no existing patches
            # can be found be searching further.
            break

    applied.reverse()
    patchify.reverse()

    # Find patches unreachable behind a merge.
    if c != stack.base:
        merge = c
        todo = set([c])
        seen = set()
        unreachable = set()
        while todo:
            c = todo.pop()
            seen.add(c)
            todo |= set(c.data.parents) - seen
            if any(stack.patches[pn] == c for pn in patches):
                unreachable.add(c)
        if unreachable:
            out.warn(
                ('%d patch%s are hidden below the merge commit' %
                 (len(unreachable), ['es', ''][len(unreachable) == 1])),
                '%s,' % merge.sha1,
                'and will be considered unapplied.',
            )

    # Make patches of any linear sequence of commits on top of a patch.
    if patchify:
        out.start('Creating %d new patch%s' %
                  (len(patchify), ['es', ''][len(patchify) == 1]))

        for c in patchify:
            pn = stack.patches.make_name(c.data.message_str)
            out.info('Creating patch %s from commit %s' % (pn, c.sha1))
            stack.patches.new(pn, c, 'repair')
            applied.append(pn)
        out.done()

    # Figure out hidden
    hidden = [pn for pn in patches if pn in patchorder.hidden]

    # Write the applied/unapplied files.
    out.start('Checking patch appliedness')
    unapplied = [
        pn for pn in patches if pn not in applied and pn not in hidden
    ]
    for pn in patchorder.all:
        if pn not in patches:
            out.info('%s is gone' % pn)
    for pn in applied:
        if pn not in patchorder.applied:
            out.info('%s is now applied' % pn)
    for pn in unapplied:
        if pn not in patchorder.unapplied:
            out.info('%s is now unapplied' % pn)
    for pn in hidden:
        if pn not in patchorder.hidden:
            out.info('%s is now hidden' % pn)
    out.done()

    orig_order = {pn: i for i, pn in enumerate(patchorder.all)}

    def patchname_key(p):
        i = orig_order.get(p, len(orig_order))
        return i, p

    trans = StackTransaction(stack)
    try:
        trans.applied = applied
        trans.unapplied = sorted(unapplied, key=patchname_key)
        trans.hidden = sorted(hidden, key=patchname_key)
    except TransactionHalted:
        pass
    return trans.execute('repair')
Example #23
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')

    if not options.stdout:
        if not os.path.isdir(dirname):
            os.makedirs(dirname)
        series = file(os.path.join(dirname, 'series'), 'w+')

    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:
        tmpl = file(options.template).read()
    else:
        tmpl = templates.get_template('patchexport.tmpl')
        if not tmpl:
            tmpl = ''

    # note the base commit for this series
    if not options.stdout:
        base_commit = stack.patches.get(patches[0]).commit.sha1
        print >> series, '# This series applies on GIT commit %s' % base_commit

    patch_no = 1;
    for p in patches:
        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 >> series, pname

        # 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 = reduce(lambda x, y: x + '\n' + y,
                            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}
        for key in tmpl_dict:
            if not tmpl_dict[key]:
                tmpl_dict[key] = ''

        try:
            descr = tmpl % tmpl_dict
        except KeyError, 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'
Example #24
0
def __create_patch(filename, message, author_name, author_email, author_date,
                   diff, options):
    """Create a new patch on the stack
    """
    if options.name:
        patch = options.name
    elif filename:
        patch = os.path.basename(filename)
    else:
        patch = ''
    if options.stripname:
        patch = __strip_patch_name(patch)

    if not patch:
        if options.ignore or options.replace:

            def unacceptable_name(name):
                return False
        else:
            unacceptable_name = crt_series.patch_exists
        patch = make_patch_name(message, unacceptable_name)
    else:
        # fix possible invalid characters in the patch name
        patch = re.sub(r'[^\w.]+', '-', patch).strip('-')

    if options.ignore and patch in crt_series.get_applied():
        out.info('Ignoring already applied patch "%s"' % patch)
        return
    if options.replace and patch in crt_series.get_unapplied():
        crt_series.delete_patch(patch, keep_log=True)

    # override the automatically parsed settings
    author = options.author(Person())
    if author.name:
        author_name = author.name
    if author.email:
        author_email = author.email
    if author.date:
        author_date = text(author.date)

    sign_str = options.sign_str
    if not options.sign_str:
        sign_str = config.get('stgit.autosign')

    crt_series.new_patch(
        patch,
        message=message,
        can_edit=False,
        author_name=author_name,
        author_email=author_email,
        author_date=author_date,
        sign_str=sign_str,
    )

    if not diff:
        out.warn('No diff found, creating empty patch')
    else:
        out.start('Importing patch "%s"' % patch)
        if options.base:
            base = git_id(crt_series, options.base)
        else:
            base = None
        try:
            git.apply_patch(
                diff=diff,
                base=base,
                reject=options.reject,
                strip=options.strip,
            )
        except git.GitException:
            if not options.reject:
                crt_series.delete_patch(patch)
            raise
        crt_series.refresh_patch(
            edit=options.edit,
            show_patch=options.showdiff,
            author_date=author_date,
            backup=False,
        )
        out.done()
Example #25
0
def func(parser, options, args):
    """Repair inconsistencies in StGit metadata."""

    orig_applied = crt_series.get_applied()
    orig_unapplied = crt_series.get_unapplied()
    orig_hidden = crt_series.get_hidden()

    if crt_series.get_protected():
        raise CmdException(
            'This branch is protected. Modification is not permitted.')

    # Find commits that aren't patches, and applied patches.
    head = git.get_commit(git.get_head()).get_id_hash()
    commits, patches = read_commit_dag(crt_series.get_name())
    c = commits[head]
    patchify = []       # commits to definitely patchify
    maybe_patchify = [] # commits to patchify if we find a patch below them
    applied = []
    while len(c.parents) == 1:
        parent, = c.parents
        if c.patch:
            applied.append(c)
            patchify.extend(maybe_patchify)
            maybe_patchify = []
        else:
            maybe_patchify.append(c)
        c = parent
    applied.reverse()
    patchify.reverse()

    # Find patches hidden behind a merge.
    merge = c
    todo = set([c])
    seen = set()
    hidden = set()
    while todo:
        c = todo.pop()
        seen.add(c)
        todo |= c.parents - seen
        if c.patch:
            hidden.add(c)
    if hidden:
        out.warn(('%d patch%s are hidden below the merge commit'
                  % (len(hidden), ['es', ''][len(hidden) == 1])),
                 '%s,' % merge.id, 'and will be considered unapplied.')

    # Make patches of any linear sequence of commits on top of a patch.
    names = set(p.patch for p in patches)
    def name_taken(name):
        return name in names
    if applied and patchify:
        out.start('Creating %d new patch%s'
                  % (len(patchify), ['es', ''][len(patchify) == 1]))
        for p in patchify:
            name = make_patch_name(p.commit.get_log(), name_taken)
            out.info('Creating patch %s from commit %s' % (name, p.id))
            aname, amail, adate = name_email_date(p.commit.get_author())
            cname, cmail, cdate = name_email_date(p.commit.get_committer())
            parent, = p.parents
            crt_series.new_patch(
                name, can_edit = False, commit = False,
                top = p.id, bottom = parent.id, message = p.commit.get_log(),
                author_name = aname, author_email = amail, author_date = adate,
                committer_name = cname, committer_email = cmail)
            p.patch = name
            applied.append(p)
            names.add(name)
        out.done()

    # Figure out hidden
    orig_patches = orig_applied + orig_unapplied + orig_hidden
    orig_applied_name_set = set(orig_applied)
    orig_unapplied_name_set = set(orig_unapplied)
    orig_hidden_name_set = set(orig_hidden)
    orig_patches_name_set = set(orig_patches)
    hidden = [p for p in patches if p.patch in orig_hidden_name_set]

    # Write the applied/unapplied files.
    out.start('Checking patch appliedness')
    unapplied = patches - set(applied) - set(hidden)
    applied_name_set = set(p.patch for p in applied)
    unapplied_name_set = set(p.patch for p in unapplied)
    hidden_name_set = set(p.patch for p in hidden)
    patches_name_set = set(p.patch for p in patches)
    for name in orig_patches_name_set - patches_name_set:
        out.info('%s is gone' % name)
    for name in applied_name_set - orig_applied_name_set:
        out.info('%s is now applied' % name)
    for name in unapplied_name_set - orig_unapplied_name_set:
        out.info('%s is now unapplied' % name)
    for name in hidden_name_set - orig_hidden_name_set:
        out.info('%s is now hidden' % name)
    orig_order = dict(zip(orig_patches, range(len(orig_patches))))

    def patchname_key(p):
        i = orig_order.get(p, len(orig_order))
        return i, p

    crt_series.set_applied(p.patch for p in applied)
    crt_series.set_unapplied(sorted(unapplied_name_set, key=patchname_key))
    crt_series.set_hidden(sorted(hidden_name_set, key=patchname_key))
    out.done()
Example #26
0
def func(parser, options, args):
    """Repair inconsistencies in StGit metadata."""
    if args:
        parser.error('incorrect number of arguments')

    repository = directory.repository
    stack = repository.get_stack()

    if stack.protected:
        raise CmdException(
            'This branch is protected. Modification is not permitted.')

    patchorder = stack.patchorder
    patches = [stack.patches.get(pn) for pn in patchorder.all]

    # Find commits that aren't patches, and applied patches.
    patchify = []  # commits to definitely patchify
    maybe_patchify = []  # commits to patchify if we find a patch below them
    applied = []
    c = stack.head
    while len(c.data.parents) == 1:
        for p in patches:
            if p.commit == c:
                applied.append(p)
                patchify.extend(maybe_patchify)
                maybe_patchify = []
                break
        else:
            maybe_patchify.append(c)
        c = c.data.parent
    applied.reverse()
    patchify.reverse()

    # Find patches unreachable behind a merge.
    merge = c
    todo = set([c])
    seen = set()
    unreachable = set()
    while todo:
        c = todo.pop()
        seen.add(c)
        todo |= set(c.data.parents) - seen
        if any(p.commit == c for p in patches):
            unreachable.add(c)
    if unreachable:
        out.warn(('%d patch%s are hidden below the merge commit' %
                  (len(unreachable), ['es', ''][len(unreachable) == 1])),
                 '%s,' % merge.sha1, 'and will be considered unapplied.')

    # Make patches of any linear sequence of commits on top of a patch.
    if applied and patchify:
        out.start('Creating %d new patch%s' %
                  (len(patchify), ['es', ''][len(patchify) == 1]))

        for c in patchify:
            pn = make_patch_name(
                c.data.message,
                unacceptable=lambda name: any(p.name == name for p in patches),
            )
            out.info('Creating patch %s from commit %s' % (pn, c.sha1))
            applied.append(stack.patches.new(pn, c, 'repair'))
        out.done()

    # Figure out hidden
    hidden = [p for p in patches if p.name in patchorder.hidden]

    # Write the applied/unapplied files.
    out.start('Checking patch appliedness')
    unapplied = [p for p in patches if p not in applied and p not in hidden]
    for pn in patchorder.all:
        if all(pn != p.name for p in patches):
            out.info('%s is gone' % pn)
    for p in applied:
        if p.name not in patchorder.applied:
            out.info('%s is now applied' % p.name)
    for p in unapplied:
        if p.name not in patchorder.unapplied:
            out.info('%s is now unapplied' % p.name)
    for p in hidden:
        if p.name not in patchorder.hidden:
            out.info('%s is now hidden' % p.name)
    out.done()

    orig_order = dict((pn, i) for i, pn in enumerate(patchorder.all))

    def patchname_key(p):
        i = orig_order.get(p, len(orig_order))
        return i, p

    trans = StackTransaction(stack,
                             'repair',
                             check_clean_iw=False,
                             allow_bad_head=True)
    try:
        trans.applied = [p.name for p in applied]
        trans.unapplied = sorted((p.name for p in unapplied),
                                 key=patchname_key)
        trans.hidden = sorted((p.name for p in hidden), key=patchname_key)
    except TransactionHalted:
        pass
    return trans.run()
Example #27
0
def __create_patch(filename, message, author_name, author_email,
                   author_date, diff, options):
    """Create a new patch on the stack
    """
    stack = directory.repository.current_stack

    if options.name:
        name = options.name
        if not stack.patches.is_name_valid(name):
            raise CmdException('Invalid patch name: %s' % name)
    elif filename:
        name = os.path.basename(filename)
    else:
        name = ''
    if options.stripname:
        name = __strip_patch_name(name)

    if not name:
        if options.ignore or options.replace:
            def unacceptable_name(name):
                return False
        else:
            unacceptable_name = stack.patches.exists
        name = make_patch_name(message, unacceptable_name)
    else:
        # fix possible invalid characters in the patch name
        name = re.sub(r'[^\w.]+', '-', name).strip('-')

    assert stack.patches.is_name_valid(name)

    if options.ignore and name in stack.patchorder.applied:
        out.info('Ignoring already applied patch "%s"' % name)
        return

    out.start('Importing patch "%s"' % name)

    author = Person(
        author_name,
        author_email,
        Date.maybe(author_date),
    )
    author = options.author(author)

    try:
        if not diff:
            out.warn('No diff found, creating empty patch')
            tree = stack.head.data.tree
        else:
            iw = stack.repository.default_iw
            iw.apply(
                diff, quiet=False, reject=options.reject, strip=options.strip
            )
            tree = iw.index.write_tree()

        cd = CommitData(
            tree=tree,
            parents=[stack.head],
            author=author,
            message=message,
        )
        cd = update_commit_data(
            cd,
            message=None,
            author=None,
            sign_str=options.sign_str,
            edit=options.edit,
        )
        commit = stack.repository.commit(cd)

        trans = StackTransaction(stack, 'import: %s' % name)

        try:
            if options.replace and name in stack.patchorder.unapplied:
                trans.delete_patches(lambda pn: pn == name, quiet=True)

            trans.patches[name] = commit
            trans.applied.append(name)
        except TransactionHalted:
            pass
        trans.run()
    finally:
        out.done()
Example #28
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()
Example #29
0
def __create_patch(filename, message, patch_name, author_name, author_email,
                   author_date, diff, options):
    """Create a new patch on the stack"""
    stack = directory.repository.current_stack

    if patch_name:
        name = patch_name
    elif options.name:
        name = options.name
    elif filename:
        name = os.path.basename(filename)
    else:
        name = ''

    if options.stripname:
        # Removing leading numbers and trailing extension
        name = re.sub(
            r'''^
                (?:[0-9]+-)?           # Optional leading patch number
                (.*?)                  # Patch name group (non-greedy)
                (?:\.(?:diff|patch))?  # Optional .diff or .patch extension
                $
            ''',
            r'\g<1>',
            name,
            flags=re.VERBOSE,
        )

    need_unique = not (options.ignore or options.replace)

    if name:
        name = stack.patches.make_name(name, unique=need_unique, lower=False)
    else:
        name = stack.patches.make_name(message, unique=need_unique, lower=True)

    if options.ignore and name in stack.patchorder.applied:
        out.info('Ignoring already applied patch "%s"' % name)
        return

    out.start('Importing patch "%s"' % name)

    author = options.author(
        Person(
            author_name,
            author_email,
            Date.maybe(author_date),
        ))

    try:
        if not diff:
            out.warn('No diff found, creating empty patch')
            tree = stack.head.data.tree
        else:
            iw = stack.repository.default_iw
            iw.apply(
                diff,
                quiet=False,
                reject=options.reject,
                strip=options.strip,
                context_lines=options.context_lines,
            )
            tree = iw.index.write_tree()

        cd = CommitData(
            tree=tree,
            parents=[stack.head],
            author=author,
            message=message,
        )
        cd = update_commit_data(
            stack.repository,
            cd,
            message=None,
            author=None,
            trailers=options.trailers,
            edit=options.edit,
        )
        commit = stack.repository.commit(cd)

        trans = StackTransaction(stack)

        try:
            if options.replace and name in stack.patchorder.unapplied:
                trans.delete_patches(lambda pn: pn == name, quiet=True)

            trans.patches[name] = commit
            trans.applied.append(name)
        except TransactionHalted:
            pass
        trans.execute('import: %s' % name)
    finally:
        out.done()