Exemple #1
0
    def pop_patch(self, name, keep=False):
        """Pops the top patch from the stack
        """
        applied = self.get_applied()
        applied.reverse()
        assert(name in applied)

        patch = self.get_patch(name)

        if git.get_head_file() == self.get_name():
            if keep and not git.apply_diff(git.get_head(), patch.get_bottom(),
                                           check_index=False):
                raise StackException(
                    'Failed to pop patches while preserving the local changes')
            git.switch(patch.get_bottom(), keep)
        else:
            git.set_branch(self.get_name(), patch.get_bottom())

        # save the new applied list
        idx = applied.index(name) + 1

        popped = applied[:idx]
        popped.reverse()
        unapplied = popped + self.get_unapplied()
        write_strings(self.__unapplied_file, unapplied)

        del applied[:idx]
        applied.reverse()
        write_strings(self.__applied_file, applied)
Exemple #2
0
    def pop_patch(self, name, keep = False):
        """Pops the top patch from the stack
        """
        applied = self.get_applied()
        applied.reverse()
        assert(name in applied)

        patch = self.get_patch(name)

        if git.get_head_file() == self.get_name():
            if keep and not git.apply_diff(git.get_head(), patch.get_bottom(),
                                           check_index = False):
                raise StackException(
                    'Failed to pop patches while preserving the local changes')
            git.switch(patch.get_bottom(), keep)
        else:
            git.set_branch(self.get_name(), patch.get_bottom())

        # save the new applied list
        idx = applied.index(name) + 1

        popped = applied[:idx]
        popped.reverse()
        unapplied = popped + self.get_unapplied()
        write_strings(self.__unapplied_file, unapplied)

        del applied[:idx]
        applied.reverse()
        write_strings(self.__applied_file, applied)
Exemple #3
0
def func(parser, options, args):
    """Merge the applied patches into the base of the current stack
       and remove them from the series while advancing the base
    """
    if len(args) != 0:
        parser.error('incorrect number of arguments')

    check_local_changes()
    check_conflicts()
    check_head_top_equal()

    applied = crt_series.get_applied()
    if not applied:
        raise CmdException, 'No patches applied'

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

    crt_head = git.get_head()

    out.start('Committing %d patches' % len(applied))

    crt_series.pop_patch(applied[0])
    git.switch(crt_head)

    for patch in applied:
        crt_series.delete_patch(patch)

    out.done()
Exemple #4
0
def rebase(target):
    if target == git.get_head():
        out.info('Already at "%s", no need for rebasing.' % target)
        return
    out.start('Rebasing to "%s"' % target)
    git.reset(tree_id = git_id(target))
    out.done()
Exemple #5
0
 def get_base(self):
     # Return the parent of the bottommost patch, if there is one.
     if os.path.isfile(self.__applied_file):
         bottommost = file(self.__applied_file).readline().strip()
         if bottommost:
             return self.get_patch(bottommost).get_bottom()
     # No bottommost patch, so just return HEAD
     return git.get_head()
Exemple #6
0
 def head_top_equal(self):
     """Return true if the head and the top are the same
     """
     crt = self.get_current_patch()
     if not crt:
         # we don't care, no patches applied
         return True
     return git.get_head() == crt.get_top()
Exemple #7
0
 def head_top_equal(self):
     """Return true if the head and the top are the same
     """
     crt = self.get_current_patch()
     if not crt:
         # we don't care, no patches applied
         return True
     return git.get_head() == crt.get_top()
Exemple #8
0
 def get_base(self):
     # Return the parent of the bottommost patch, if there is one.
     if os.path.isfile(self.__applied_file):
         bottommost = file(self.__applied_file).readline().strip()
         if bottommost:
             return self.get_patch(bottommost).get_bottom()
     # No bottommost patch, so just return HEAD
     return git.get_head()
Exemple #9
0
    def clone(self, target_series):
        """Clones a series
        """
        try:
            # allow cloning of branches not under StGIT control
            base = self.get_base()
        except BaseException:
            base = git.get_head()
        Series(target_series).init(create_at=base)
        new_series = Series(target_series)

        # generate an artificial description file
        new_series.set_description('clone of "%s"' % self.get_name())

        # clone self's entire series as unapplied patches
        try:
            # allow cloning of branches not under StGIT control
            applied = self.get_applied()
            unapplied = self.get_unapplied()
            patches = applied + unapplied
            patches.reverse()
        except BaseException:
            patches = applied = unapplied = []
        for p in patches:
            patch = self.get_patch(p)
            newpatch = new_series.new_patch(
                p,
                message=patch.get_description(),
                can_edit=False,
                unapplied=True,
                bottom=patch.get_bottom(),
                top=patch.get_top(),
                author_name=patch.get_authname(),
                author_email=patch.get_authemail(),
                author_date=patch.get_authdate(),
            )
            if patch.get_log():
                out.info('Setting log to %s' % patch.get_log())
                newpatch.set_log(patch.get_log())
            else:
                out.info('No log for %s' % p)

        # fast forward the cloned series to self's top
        new_series.forward_patches(applied)

        # Clone parent informations
        value = config.get('branch.%s.remote' % self.get_name())
        if value:
            config.set('branch.%s.remote' % target_series, value)

        value = config.get('branch.%s.merge' % self.get_name())
        if value:
            config.set('branch.%s.merge' % target_series, value)

        value = config.get('branch.%s.stgit.parentbranch' % self.get_name())
        if value:
            config.set('branch.%s.stgit.parentbranch' % target_series, value)
Exemple #10
0
def func(parser, options, args):
    """Assimilate a number of patches.
    """
    def nothing_to_do():
        out.info('No commits to assimilate')

    top_patch = crt_series.get_current_patch()
    if not top_patch:
        return nothing_to_do()

    victims = []
    victim = git.get_commit(git.get_head())
    while victim.get_id_hash() != top_patch.get_top():
        victims.append(victim)
        parents = victim.get_parents()
        if not parents:
            raise CmdException, 'Commit %s has no parents, aborting' % victim
        elif len(parents) > 1:
            raise CmdException, 'Commit %s is a merge, aborting' % victim
        victim = git.get_commit(parents[0])

    if not victims:
        return nothing_to_do()

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

    patch2name = {}
    name2patch = {}

    def name_taken(name):
        return name in name2patch or crt_series.patch_exists(name)

    for victim in victims:
        patchname = make_patch_name(victim.get_log(), name_taken)
        patch2name[victim] = patchname
        name2patch[patchname] = victim

    victims.reverse()
    for victim in victims:
        out.info('Creating patch "%s" from commit %s' %
                 (patch2name[victim], victim))
        aname, amail, adate = name_email_date(victim.get_author())
        cname, cmail, cdate = name_email_date(victim.get_committer())
        crt_series.new_patch(patch2name[victim],
                             can_edit=False,
                             before_existing=False,
                             commit=False,
                             top=victim.get_id_hash(),
                             bottom=victim.get_parent(),
                             message=victim.get_log(),
                             author_name=aname,
                             author_email=amail,
                             author_date=adate,
                             committer_name=cname,
                             committer_email=cmail)
Exemple #11
0
    def clone(self, target_series):
        """Clones a series
        """
        try:
            # allow cloning of branches not under StGIT control
            base = self.get_base()
        except:
            base = git.get_head()
        Series(target_series).init(create_at=base)
        new_series = Series(target_series)

        # generate an artificial description file
        new_series.set_description('clone of "%s"' % self.get_name())

        # clone self's entire series as unapplied patches
        try:
            # allow cloning of branches not under StGIT control
            applied = self.get_applied()
            unapplied = self.get_unapplied()
            patches = applied + unapplied
            patches.reverse()
        except:
            patches = applied = unapplied = []
        for p in patches:
            patch = self.get_patch(p)
            newpatch = new_series.new_patch(
                p,
                message=patch.get_description(),
                can_edit=False,
                unapplied=True,
                bottom=patch.get_bottom(),
                top=patch.get_top(),
                author_name=patch.get_authname(),
                author_email=patch.get_authemail(),
                author_date=patch.get_authdate(),
            )
            if patch.get_log():
                out.info("Setting log to %s" % patch.get_log())
                newpatch.set_log(patch.get_log())
            else:
                out.info("No log for %s" % p)

        # fast forward the cloned series to self's top
        new_series.forward_patches(applied)

        # Clone parent informations
        value = config.get("branch.%s.remote" % self.get_name())
        if value:
            config.set("branch.%s.remote" % target_series, value)

        value = config.get("branch.%s.merge" % self.get_name())
        if value:
            config.set("branch.%s.merge" % target_series, value)

        value = config.get("branch.%s.stgit.parentbranch" % self.get_name())
        if value:
            config.set("branch.%s.stgit.parentbranch" % target_series, value)
Exemple #12
0
def func(parser, options, args):
    """Assimilate a number of patches.
    """

    def nothing_to_do():
        out.info('No commits to assimilate')

    top_patch = crt_series.get_current_patch()
    if not top_patch:
        return nothing_to_do()

    victims = []
    victim = git.get_commit(git.get_head())
    while victim.get_id_hash() != top_patch.get_top():
        victims.append(victim)
        parents = victim.get_parents()
        if not parents:
            raise CmdException, 'Commit %s has no parents, aborting' % victim
        elif len(parents) > 1:
            raise CmdException, 'Commit %s is a merge, aborting' % victim
        victim = git.get_commit(parents[0])

    if not victims:
        return nothing_to_do()

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

    patch2name = {}
    name2patch = {}

    def name_taken(name):
        return name in name2patch or crt_series.patch_exists(name)

    for victim in victims:
        patchname = make_patch_name(victim.get_log(), name_taken)
        patch2name[victim] = patchname
        name2patch[patchname] = victim

    victims.reverse()
    for victim in victims:
        out.info('Creating patch "%s" from commit %s'
                 % (patch2name[victim], victim))
        aname, amail, adate = name_email_date(victim.get_author())
        cname, cmail, cdate = name_email_date(victim.get_committer())
        crt_series.new_patch(
            patch2name[victim],
            can_edit = False, before_existing = False, commit = False,
            top = victim.get_id_hash(), bottom = victim.get_parent(),
            message = victim.get_log(),
            author_name = aname, author_email = amail, author_date = adate,
            committer_name = cname, committer_email = cmail)
Exemple #13
0
    def push_patch(self, name, empty=False):
        """Pushes a patch on the stack
        """
        unapplied = self.get_unapplied()
        assert (name in unapplied)

        patch = Patch(name, self.__patch_dir, self.__refs_dir)

        head = git.get_head()
        bottom = patch.get_bottom()
        top = patch.get_top()

        ex = None
        modified = False

        # top != bottom always since we have a commit for each patch
        if empty:
            # just make an empty patch (top = bottom = HEAD). This
            # option is useful to allow undoing already merged
            # patches. The top is updated by refresh_patch since we
            # need an empty commit
            patch.set_bottom(head, backup=True)
            patch.set_top(head, backup=True)
            modified = True
        elif head == bottom:
            # reset the backup information. No need for logging
            patch.set_bottom(bottom, backup=True)
            patch.set_top(top, backup=True)

            git.switch(top)
        else:
            # new patch needs to be refreshed.
            # The current patch is empty after merge.
            patch.set_bottom(head, backup=True)
            patch.set_top(head, backup=True)

            # Try the fast applying first. If this fails, fall back to the
            # three-way merge
            if not git.apply_diff(bottom, top):
                # if git.apply_diff() fails, the patch requires a diff3
                # merge and can be reported as modified
                modified = True

                # merge can fail but the patch needs to be pushed
                try:
                    git.merge(bottom, head, top, recursive=True)
                except git.GitException, ex:
                    out.error(
                        'The merge failed during "push".',
                        'Use "refresh" after fixing the conflicts or'
                        ' revert the operation with "push --undo".')
Exemple #14
0
    def push_empty_patch(self, name):
        """Pushes an empty patch on the stack
        """
        unapplied = self.get_unapplied()
        assert(name in unapplied)

        # patch = self.get_patch(name)
        head = git.get_head()

        append_string(self.__applied_file, name)

        unapplied.remove(name)
        write_strings(self.__unapplied_file, unapplied)

        self.refresh_patch(bottom=head, cache_update=False, log='push(m)')
Exemple #15
0
    def push_empty_patch(self, name):
        """Pushes an empty patch on the stack
        """
        unapplied = self.get_unapplied()
        assert(name in unapplied)

        # patch = self.get_patch(name)
        head = git.get_head()

        append_string(self.__applied_file, name)

        unapplied.remove(name)
        write_strings(self.__unapplied_file, unapplied)

        self.refresh_patch(bottom = head, cache_update = False, log = 'push(m)')
Exemple #16
0
    def push_patch(self, name):
        """Pushes a patch on the stack
        """
        unapplied = self.get_unapplied()
        assert(name in unapplied)

        patch = self.get_patch(name)

        head = git.get_head()
        bottom = patch.get_bottom()
        top = patch.get_top()
        # top != bottom always since we have a commit for each patch

        if head == bottom:
            # A fast-forward push. Just reset the backup
            # information. No need for logging
            patch.set_top(top, backup = True)

            git.switch(top)
            append_string(self.__applied_file, name)

            unapplied.remove(name)
            write_strings(self.__unapplied_file, unapplied)
            return False

        # Need to create a new commit an merge in the old patch
        ex = None
        modified = False

        # Try the fast applying first. If this fails, fall back to the
        # three-way merge
        if not git.apply_diff(bottom, top):
            # if git.apply_diff() fails, the patch requires a diff3
            # merge and can be reported as modified
            modified = True

            # merge can fail but the patch needs to be pushed
            try:
                git.merge_recursive(bottom, head, top)
            except git.GitException, ex:
                out.error('The merge failed during "push".',
                          'Revert the operation with "stg undo".')
Exemple #17
0
    def init(self, create_at=False, parent_remote=None, parent_branch=None):
        """Initialises the stgit series
        """
        if self.is_initialised():
            raise StackException, '%s already initialized' % self.get_name()
        for d in [self._dir(), self.__refs_dir]:
            if os.path.exists(d):
                raise StackException, '%s already exists' % d

        if (create_at != False):
            git.create_branch(self.get_name(), create_at)

        os.makedirs(self.__patch_dir)

        self.set_parent(parent_remote, parent_branch)

        self.create_empty_field('applied')
        self.create_empty_field('unapplied')
        os.makedirs(self.__refs_dir)
        self._set_field('orig-base', git.get_head())

        config.set(self.format_version_key(), str(FORMAT_VERSION))
Exemple #18
0
    def forward_patches(self, names):
        """Try to fast-forward an array of patches.

        On return, patches in names[0:returned_value] have been pushed on the
        stack. Apply the rest with push_patch
        """
        unapplied = self.get_unapplied()

        forwarded = 0
        top = git.get_head()

        for name in names:
            assert(name in unapplied)

            patch = self.get_patch(name)

            head = top
            bottom = patch.get_bottom()
            top = patch.get_top()

            # top != bottom always since we have a commit for each patch
            if head == bottom:
                # reset the backup information. No logging since the
                # patch hasn't changed
                patch.set_top(top, backup=True)

            else:
                head_tree = git.get_commit(head).get_tree()
                bottom_tree = git.get_commit(bottom).get_tree()
                if head_tree == bottom_tree:
                    # We must just reparent this patch and create a new commit
                    # for it
                    descr = patch.get_description()
                    author_name = patch.get_authname()
                    author_email = patch.get_authemail()
                    author_date = patch.get_authdate()
                    committer_name = patch.get_commname()
                    committer_email = patch.get_commemail()

                    top_tree = git.get_commit(top).get_tree()

                    top = git.commit(
                        message=descr,
                        parents=[head],
                        cache_update=False,
                        tree_id=top_tree,
                        allowempty=True,
                        author_name=author_name,
                        author_email=author_email,
                        author_date=author_date,
                        committer_name=committer_name,
                        committer_email=committer_email,
                    )

                    patch.set_top(top, backup=True)

                    self.log_patch(patch, 'push(f)')
                else:
                    top = head
                    # stop the fast-forwarding, must do a real merge
                    break

            forwarded += 1
            unapplied.remove(name)

        if forwarded == 0:
            return 0

        git.switch(top)

        append_strings(self.__applied_file, names[0:forwarded])
        write_strings(self.__unapplied_file, unapplied)

        return forwarded
Exemple #19
0
    def forward_patches(self, names):
        """Try to fast-forward an array of patches.

        On return, patches in names[0:returned_value] have been pushed on the
        stack. Apply the rest with push_patch
        """
        unapplied = self.get_unapplied()

        forwarded = 0
        top = git.get_head()

        for name in names:
            assert(name in unapplied)

            patch = self.get_patch(name)

            head = top
            bottom = patch.get_bottom()
            top = patch.get_top()

            # top != bottom always since we have a commit for each patch
            if head == bottom:
                # reset the backup information. No logging since the
                # patch hasn't changed
                patch.set_top(top, backup = True)

            else:
                head_tree = git.get_commit(head).get_tree()
                bottom_tree = git.get_commit(bottom).get_tree()
                if head_tree == bottom_tree:
                    # We must just reparent this patch and create a new commit
                    # for it
                    descr = patch.get_description()
                    author_name = patch.get_authname()
                    author_email = patch.get_authemail()
                    author_date = patch.get_authdate()
                    committer_name = patch.get_commname()
                    committer_email = patch.get_commemail()

                    top_tree = git.get_commit(top).get_tree()

                    top = git.commit(message = descr, parents = [head],
                                     cache_update = False,
                                     tree_id = top_tree,
                                     allowempty = True,
                                     author_name = author_name,
                                     author_email = author_email,
                                     author_date = author_date,
                                     committer_name = committer_name,
                                     committer_email = committer_email)

                    patch.set_top(top, backup = True)

                    self.log_patch(patch, 'push(f)')
                else:
                    top = head
                    # stop the fast-forwarding, must do a real merge
                    break

            forwarded+=1
            unapplied.remove(name)

        if forwarded == 0:
            return 0

        git.switch(top)

        append_strings(self.__applied_file, names[0:forwarded])
        write_strings(self.__unapplied_file, unapplied)

        return forwarded
Exemple #20
0
    def new_patch(self, name, message = None, can_edit = True,
                  unapplied = False, show_patch = False,
                  top = None, bottom = None, commit = True,
                  author_name = None, author_email = None, author_date = None,
                  committer_name = None, committer_email = None,
                  before_existing = False, sign_str = None):
        """Creates a new patch, either pointing to an existing commit object,
        or by creating a new commit object.
        """

        assert commit or (top and bottom)
        assert not before_existing or (top and bottom)
        assert not (commit and before_existing)
        assert (top and bottom) or (not top and not bottom)
        assert commit or (not top or (bottom == git.get_commit(top).get_parent()))

        if name is not None:
            self.__patch_name_valid(name)
            if self.patch_exists(name):
                raise StackException('Patch "%s" already exists' % name)

        # TODO: move this out of the stgit.stack module, it is really
        # for higher level commands to handle the user interaction
        def sign(msg):
            return add_sign_line(msg, sign_str,
                                 committer_name or git.committer().name,
                                 committer_email or git.committer().email)
        if not message and can_edit:
            descr = edit_file(
                self, sign(''),
                'Please enter the description for the patch above.',
                show_patch)
        else:
            descr = sign(message)

        head = git.get_head()

        if name is None:
            name = make_patch_name(descr, self.patch_exists)

        patch = self.get_patch(name)
        patch.create()

        patch.set_description(descr)
        patch.set_authname(author_name)
        patch.set_authemail(author_email)
        patch.set_authdate(author_date)
        patch.set_commname(committer_name)
        patch.set_commemail(committer_email)

        if before_existing:
            insert_string(self.__applied_file, patch.get_name())
        elif unapplied:
            patches = [patch.get_name()] + self.get_unapplied()
            write_strings(self.__unapplied_file, patches)
            set_head = False
        else:
            append_string(self.__applied_file, patch.get_name())
            set_head = True

        if commit:
            if top:
                top_commit = git.get_commit(top)
            else:
                bottom = head
                top_commit = git.get_commit(head)

            # create a commit for the patch (may be empty if top == bottom);
            # only commit on top of the current branch
            assert(unapplied or bottom == head)
            commit_id = git.commit(message = descr, parents = [bottom],
                                   cache_update = False,
                                   tree_id = top_commit.get_tree(),
                                   allowempty = True, set_head = set_head,
                                   author_name = author_name,
                                   author_email = author_email,
                                   author_date = author_date,
                                   committer_name = committer_name,
                                   committer_email = committer_email)
            # set the patch top to the new commit
            patch.set_top(commit_id)
        else:
            patch.set_top(top)

        self.log_patch(patch, 'new')

        return patch
Exemple #21
0
def func(parser, options, args):
    """Import a commit object as a new patch
    """
    if len(args) != 1:
        parser.error('incorrect number of arguments')

    if not options.unapplied:
        check_local_changes()
        check_conflicts()
        check_head_top_equal()

    commit_str = args[0]
    commit_id = git_id(commit_str)
    commit = git.Commit(commit_id)

    if options.fold or options.update:
        if not crt_series.get_current():
            raise CmdException, 'No patches applied'
    else:
        patch_branch = commit_str.split('@')
        if options.name:
            patchname = options.name
        elif len(patch_branch) == 2:
            patchname = patch_branch[0]
        else:
            patchname = None

    if options.parent:
        parent = git_id(options.parent)
    else:
        parent = commit.get_parent()

    if not options.reverse:
        bottom = parent
        top = commit_id
    else:
        bottom = commit_id
        top = parent

    if options.fold:
        out.start('Folding commit %s' % commit_id)

        # try a direct git-apply first
        if not git.apply_diff(bottom, top):
            git.merge(bottom, git.get_head(), top, recursive = True)

        out.done()
    elif options.update:
        rev1 = git_id('//bottom')
        rev2 = git_id('//top')
        files = git.barefiles(rev1, rev2).split('\n')

        out.start('Updating with commit %s' % commit_id)

        if not git.apply_diff(bottom, top, files = files):
            raise CmdException, 'Patch updating failed'

        out.done()
    else:
        message = commit.get_log()
        if options.expose:
            message += '(imported from commit %s)\n' % commit.get_id_hash()
        author_name, author_email, author_date = \
                     name_email_date(commit.get_author())

        out.start('Importing commit %s' % commit_id)

        newpatch = crt_series.new_patch(patchname, message = message, can_edit = False,
                                        unapplied = True, bottom = bottom, top = top,
                                        author_name = author_name,
                                        author_email = author_email,
                                        author_date = author_date)
        # in case the patch name was automatically generated
        patchname = newpatch.get_name()

        # find a patchlog to fork from
        (refpatchname, refbranchname, refpatchid) = parse_rev(commit_str)
        if refpatchname and not refpatchid and \
               (not refpatchid or refpatchid == 'top'):
            # FIXME: should also support picking //top.old
            if refbranchname:
                # assume the refseries is OK, since we already resolved
                # commit_str to a git_id
                refseries = Series(refbranchname)
            else:
                refseries = crt_series
            patch = refseries.get_patch(refpatchname)
            if patch.get_log():
                out.info("Log was %s" % newpatch.get_log())
                out.info("Setting log to %s\n" %  patch.get_log())
                newpatch.set_log(patch.get_log())
                out.info("Log is now %s" % newpatch.get_log())
            else:
                out.info("No log for %s\n" % patchname)

        if not options.unapplied:
            modified = crt_series.push_patch(patchname)
        else:
            modified = False

        if crt_series.empty_patch(patchname):
            out.done('empty patch')
        elif modified:
            out.done('modified')
        else:
            out.done()

    print_crt_patch()
Exemple #22
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, xrange(len(orig_patches))))
    def patchname_cmp(p1, p2):
        i1 = orig_order.get(p1, len(orig_order))
        i2 = orig_order.get(p2, len(orig_order))
        return cmp((i1, p1), (i2, p2))
    crt_series.set_applied(p.patch for p in applied)
    crt_series.set_unapplied(sorted(unapplied_name_set, cmp = patchname_cmp))
    crt_series.set_hidden(sorted(hidden_name_set, cmp = patchname_cmp))
    out.done()
Exemple #23
0
    def push_patch(self, name):
        """Pushes a patch on the stack
        """
        unapplied = self.get_unapplied()
        assert(name in unapplied)

        patch = self.get_patch(name)

        head = git.get_head()
        bottom = patch.get_bottom()
        top = patch.get_top()
        # top != bottom always since we have a commit for each patch

        if head == bottom:
            # A fast-forward push. Just reset the backup
            # information. No need for logging
            patch.set_top(top, backup=True)

            git.switch(top)
            append_string(self.__applied_file, name)

            unapplied.remove(name)
            write_strings(self.__unapplied_file, unapplied)
            return False

        # Need to create a new commit an merge in the old patch
        ex = None
        modified = False

        # Try the fast applying first. If this fails, fall back to the
        # three-way merge
        if not git.apply_diff(bottom, top):
            # if git.apply_diff() fails, the patch requires a diff3
            # merge and can be reported as modified
            modified = True

            # merge can fail but the patch needs to be pushed
            try:
                git.merge_recursive(bottom, head, top)
            except git.GitException:
                out.error('The merge failed during "push".',
                          'Revert the operation with "stg undo".')

        append_string(self.__applied_file, name)

        unapplied.remove(name)
        write_strings(self.__unapplied_file, unapplied)

        if not ex:
            # if the merge was OK and no conflicts, just refresh the patch
            # The GIT cache was already updated by the merge operation
            if modified:
                log = 'push(m)'
            else:
                log = 'push'
            self.refresh_patch(bottom=head, cache_update=False, log=log)
        else:
            # we make the patch empty, with the merged state in the
            # working tree.
            self.refresh_patch(
                bottom=head,
                cache_update=False,
                empty=True,
                log='push(c)',
            )
            raise StackException(str(ex))

        return modified
Exemple #24
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()
Exemple #25
0
    def push_patch(self, name):
        """Pushes a patch on the stack
        """
        unapplied = self.get_unapplied()
        assert(name in unapplied)

        patch = self.get_patch(name)

        head = git.get_head()
        bottom = patch.get_bottom()
        top = patch.get_top()
        # top != bottom always since we have a commit for each patch

        if head == bottom:
            # A fast-forward push. Just reset the backup
            # information. No need for logging
            patch.set_top(top, backup = True)

            git.switch(top)
            append_string(self.__applied_file, name)

            unapplied.remove(name)
            write_strings(self.__unapplied_file, unapplied)
            return False

        # Need to create a new commit an merge in the old patch
        ex = None
        modified = False

        # Try the fast applying first. If this fails, fall back to the
        # three-way merge
        if not git.apply_diff(bottom, top):
            # if git.apply_diff() fails, the patch requires a diff3
            # merge and can be reported as modified
            modified = True

            # merge can fail but the patch needs to be pushed
            try:
                git.merge_recursive(bottom, head, top)
            except git.GitException:
                out.error('The merge failed during "push".',
                          'Revert the operation with "stg undo".')

        append_string(self.__applied_file, name)

        unapplied.remove(name)
        write_strings(self.__unapplied_file, unapplied)

        if not ex:
            # if the merge was OK and no conflicts, just refresh the patch
            # The GIT cache was already updated by the merge operation
            if modified:
                log = 'push(m)'
            else:
                log = 'push'
            self.refresh_patch(bottom = head, cache_update = False, log = log)
        else:
            # we make the patch empty, with the merged state in the
            # working tree.
            self.refresh_patch(bottom = head, cache_update = False,
                               empty = True, log = 'push(c)')
            raise StackException(str(ex))

        return modified
Exemple #26
0
def post_rebase(applied, nopush, merged):
    # memorize that we rebased to here
    crt_series._set_field('orig-base', git.get_head())
    # push the patches back
    if not nopush:
        push_patches(applied, merged)
Exemple #27
0
def __branch_merge_patch(remote_series, pname):
    """Merge a patch from a remote branch into the current tree.
    """
    patch = remote_series.get_patch(pname)
    git.merge(patch.get_bottom(), git.get_head(), patch.get_top())
Exemple #28
0
def __pick_commit(commit_id, patchname, options):
    """Pick a commit id.
    """
    commit = git.Commit(commit_id)

    if options.name:
        patchname = options.name
    elif patchname and options.revert:
        patchname = 'revert-' + patchname
    if patchname:
        patchname = find_patch_name(patchname, crt_series.patch_exists)

    if options.parent:
        parent = git_id(crt_series, options.parent)
    else:
        parent = commit.get_parent()

    if not options.revert:
        bottom = parent
        top = commit_id
    else:
        bottom = commit_id
        top = parent

    if options.fold:
        out.start('Folding commit %s' % commit_id)

        # try a direct git apply first
        if not git.apply_diff(bottom, top, files=options.file):
            if options.file:
                raise CmdException('Patch folding failed')
            else:
                git.merge_recursive(bottom, git.get_head(), top)

        out.done()
    elif options.update:
        rev1 = git_id(crt_series, 'HEAD^')
        rev2 = git_id(crt_series, 'HEAD')
        files = git.barefiles(rev1, rev2).split('\n')

        out.start('Updating with commit %s' % commit_id)

        if not git.apply_diff(bottom, top, files=files):
            raise CmdException('Patch updating failed')

        out.done()
    else:
        message = commit.get_log()
        if options.revert:
            if message:
                lines = message.splitlines()
                subject = lines[0]
                body = '\n'.join(lines[2:])
            else:
                subject = commit.get_id_hash()
                body = ''
            message = 'Revert "%s"\n\nThis reverts commit %s.\n\n%s\n' % (
                subject, commit.get_id_hash(), body)
        elif options.expose:
            if not message.endswith('\n'):
                message += '\n'
            message += '(imported from commit %s)\n' % commit.get_id_hash()
        (author_name, author_email,
         author_date) = name_email_date(commit.get_author())
        if options.revert:
            author_name = author_email = None

        out.start('Importing commit %s' % commit_id)

        newpatch = crt_series.new_patch(
            patchname,
            message=message,
            can_edit=False,
            unapplied=True,
            bottom=bottom,
            top=top,
            author_name=author_name,
            author_email=author_email,
            author_date=author_date,
        )
        # in case the patch name was automatically generated
        patchname = newpatch.get_name()

        # find a patchlog to fork from
        refbranchname, refpatchname = parse_rev(patchname)
        if refpatchname:
            if refbranchname:
                # assume the refseries is OK, since we already resolved
                # commit_str to a git_id
                refseries = Series(refbranchname)
            else:
                refseries = crt_series
            patch = refseries.get_patch(refpatchname)
            if patch.get_log():
                out.info("Log was %s" % newpatch.get_log())
                out.info("Setting log to %s\n" % patch.get_log())
                newpatch.set_log(patch.get_log())
                out.info("Log is now %s" % newpatch.get_log())
            else:
                out.info("No log for %s\n" % patchname)

        if not options.unapplied:
            modified = crt_series.push_patch(patchname)
        else:
            modified = False

        if crt_series.empty_patch(patchname):
            out.done('empty patch')
        elif modified:
            out.done('modified')
        else:
            out.done()
Exemple #29
0
def __pick_commit(commit_id, patchname, options):
    """Pick a commit id.
    """
    commit = git.Commit(commit_id)

    if options.name:
        patchname = options.name
    elif patchname and options.revert:
        patchname = 'revert-' + patchname
    if patchname:
        patchname = find_patch_name(patchname, crt_series.patch_exists)

    if options.parent:
        parent = git_id(crt_series, options.parent)
    else:
        parent = commit.get_parent()

    if not options.revert:
        bottom = parent
        top = commit_id
    else:
        bottom = commit_id
        top = parent

    if options.fold:
        out.start('Folding commit %s' % commit_id)

        # try a direct git apply first
        if not git.apply_diff(bottom, top, files = options.file):
            if options.file:
                raise CmdException('Patch folding failed')
            else:
                git.merge_recursive(bottom, git.get_head(), top)

        out.done()
    elif options.update:
        rev1 = git_id(crt_series, 'HEAD^')
        rev2 = git_id(crt_series, 'HEAD')
        files = git.barefiles(rev1, rev2).split('\n')

        out.start('Updating with commit %s' % commit_id)

        if not git.apply_diff(bottom, top, files = files):
            raise CmdException, 'Patch updating failed'

        out.done()
    else:
        message = commit.get_log()
        if options.revert:
            if message:
                lines = message.splitlines()
                subject = lines[0]
                body = '\n'.join(lines[2:])
            else:
                subject = commit.get_id_hash()
                body = ''
            message = 'Revert "%s"\n\nThis reverts commit %s.\n\n%s\n' \
                    % (subject, commit.get_id_hash(), body)
        elif options.expose:
            message += '(imported from commit %s)\n' % commit.get_id_hash()
        author_name, author_email, author_date = \
                     name_email_date(commit.get_author())
        if options.revert:
            author_name = author_email = None

        out.start('Importing commit %s' % commit_id)

        newpatch = crt_series.new_patch(patchname, message = message, can_edit = False,
                                        unapplied = True, bottom = bottom, top = top,
                                        author_name = author_name,
                                        author_email = author_email,
                                        author_date = author_date)
        # in case the patch name was automatically generated
        patchname = newpatch.get_name()

        # find a patchlog to fork from
        refbranchname, refpatchname = parse_rev(patchname)
        if refpatchname:
            if refbranchname:
                # assume the refseries is OK, since we already resolved
                # commit_str to a git_id
                refseries = Series(refbranchname)
            else:
                refseries = crt_series
            patch = refseries.get_patch(refpatchname)
            if patch.get_log():
                out.info("Log was %s" % newpatch.get_log())
                out.info("Setting log to %s\n" %  patch.get_log())
                newpatch.set_log(patch.get_log())
                out.info("Log is now %s" % newpatch.get_log())
            else:
                out.info("No log for %s\n" % patchname)

        if not options.unapplied:
            modified = crt_series.push_patch(patchname)
        else:
            modified = False

        if crt_series.empty_patch(patchname):
            out.done('empty patch')
        elif modified:
            out.done('modified')
        else:
            out.done()
Exemple #30
0
    def new_patch(self,
                  name,
                  message=None,
                  can_edit=True,
                  unapplied=False,
                  show_patch=False,
                  top=None,
                  bottom=None,
                  commit=True,
                  author_name=None,
                  author_email=None,
                  author_date=None,
                  committer_name=None,
                  committer_email=None,
                  before_existing=False):
        """Creates a new patch
        """

        if name != None:
            self.__patch_name_valid(name)
            if self.patch_exists(name):
                raise StackException, 'Patch "%s" already exists' % name

        if not message and can_edit:
            descr = edit_file(
                self, None,
                'Please enter the description for the patch above.',
                show_patch)
        else:
            descr = message

        head = git.get_head()

        if name == None:
            name = make_patch_name(descr, self.patch_exists)

        patch = Patch(name, self.__patch_dir, self.__refs_dir)
        patch.create()

        if not bottom:
            bottom = head
        if not top:
            top = head

        patch.set_bottom(bottom)
        patch.set_top(top)
        patch.set_description(descr)
        patch.set_authname(author_name)
        patch.set_authemail(author_email)
        patch.set_authdate(author_date)
        patch.set_commname(committer_name)
        patch.set_commemail(committer_email)

        if before_existing:
            insert_string(self.__applied_file, patch.get_name())
            # no need to commit anything as the object is already
            # present (mainly used by 'uncommit')
            commit = False
        elif unapplied:
            patches = [patch.get_name()] + self.get_unapplied()
            write_strings(self.__unapplied_file, patches)
            set_head = False
        else:
            append_string(self.__applied_file, patch.get_name())
            set_head = True

        if commit:
            # create a commit for the patch (may be empty if top == bottom);
            # only commit on top of the current branch
            assert (unapplied or bottom == head)
            top_commit = git.get_commit(top)
            commit_id = git.commit(message=descr,
                                   parents=[bottom],
                                   cache_update=False,
                                   tree_id=top_commit.get_tree(),
                                   allowempty=True,
                                   set_head=set_head,
                                   author_name=author_name,
                                   author_email=author_email,
                                   author_date=author_date,
                                   committer_name=committer_name,
                                   committer_email=committer_email)
            # set the patch top to the new commit
            patch.set_top(commit_id)

        self.log_patch(patch, 'new')

        return patch
Exemple #31
0
    def new_patch(
        self,
        name,
        message=None,
        can_edit=True,
        unapplied=False,
        show_patch=False,
        top=None,
        bottom=None,
        commit=True,
        author_name=None,
        author_email=None,
        author_date=None,
        committer_name=None,
        committer_email=None,
        sign_str=None,
    ):
        """Creates a new patch, either pointing to an existing commit object,
        or by creating a new commit object.
        """

        assert commit or (top and bottom)
        assert (top and bottom) or (not top and not bottom)
        assert commit or (
            not top
            or bottom == git.get_commit(top).get_parent()
        )

        if name is not None:
            self.__patch_name_valid(name)
            if self.patch_exists(name):
                raise StackException('Patch "%s" already exists' % name)

        # TODO: move this out of the stgit.stack module, it is really
        # for higher level commands to handle the user interaction
        def sign(msg):
            return add_sign_line(msg, sign_str,
                                 committer_name or git.committer().name,
                                 committer_email or git.committer().email)
        if not message and can_edit:
            descr = edit_file(
                self, sign(''),
                'Please enter the description for the patch above.',
                show_patch)
        else:
            descr = sign(message)

        head = git.get_head()

        if name is None:
            name = make_patch_name(descr, self.patch_exists)

        patch = self.get_patch(name)
        patch.create()

        patch.set_description(descr)
        patch.set_authname(author_name)
        patch.set_authemail(author_email)
        patch.set_authdate(author_date)
        patch.set_commname(committer_name)
        patch.set_commemail(committer_email)

        if unapplied:
            patches = [patch.get_name()] + self.get_unapplied()
            write_strings(self.__unapplied_file, patches)
            set_head = False
        else:
            append_string(self.__applied_file, patch.get_name())
            set_head = True

        if commit:
            if top:
                top_commit = git.get_commit(top)
            else:
                bottom = head
                top_commit = git.get_commit(head)

            # create a commit for the patch (may be empty if top == bottom);
            # only commit on top of the current branch
            assert(unapplied or bottom == head)
            commit_id = git.commit(
                message=descr,
                parents=[bottom],
                cache_update=False,
                tree_id=top_commit.get_tree(),
                allowempty=True,
                set_head=set_head,
                author_name=author_name,
                author_email=author_email,
                author_date=author_date,
                committer_name=committer_name,
                committer_email=committer_email,
            )
            # set the patch top to the new commit
            patch.set_top(commit_id)
        else:
            patch.set_top(top)

        self.log_patch(patch, 'new')

        return patch
Exemple #32
0
def func(parser, options, args):
    """Import a commit object as a new patch
    """
    if len(args) != 1:
        parser.error('incorrect number of arguments')

    if not options.unapplied:
        check_local_changes()
        check_conflicts()
        check_head_top_equal()

    commit_str = args[0]
    commit_id = git_id(commit_str)
    commit = git.Commit(commit_id)

    if options.fold or options.update:
        if not crt_series.get_current():
            raise CmdException, 'No patches applied'
    else:
        patch_branch = commit_str.split('@')
        if options.name:
            patchname = options.name
        elif len(patch_branch) == 2:
            patchname = patch_branch[0]
        else:
            patchname = None

    if options.parent:
        parent = git_id(options.parent)
    else:
        parent = commit.get_parent()

    if not options.reverse:
        bottom = parent
        top = commit_id
    else:
        bottom = commit_id
        top = parent

    if options.fold:
        out.start('Folding commit %s' % commit_id)

        # try a direct git-apply first
        if not git.apply_diff(bottom, top):
            git.merge(bottom, git.get_head(), top, recursive=True)

        out.done()
    elif options.update:
        rev1 = git_id('//bottom')
        rev2 = git_id('//top')
        files = git.barefiles(rev1, rev2).split('\n')

        out.start('Updating with commit %s' % commit_id)

        if not git.apply_diff(bottom, top, files=files):
            raise CmdException, 'Patch updating failed'

        out.done()
    else:
        message = commit.get_log()
        if options.expose:
            message += '(imported from commit %s)\n' % commit.get_id_hash()
        author_name, author_email, author_date = \
                     name_email_date(commit.get_author())

        out.start('Importing commit %s' % commit_id)

        newpatch = crt_series.new_patch(patchname,
                                        message=message,
                                        can_edit=False,
                                        unapplied=True,
                                        bottom=bottom,
                                        top=top,
                                        author_name=author_name,
                                        author_email=author_email,
                                        author_date=author_date)
        # in case the patch name was automatically generated
        patchname = newpatch.get_name()

        # find a patchlog to fork from
        (refpatchname, refbranchname, refpatchid) = parse_rev(commit_str)
        if refpatchname and not refpatchid and \
               (not refpatchid or refpatchid == 'top'):
            # FIXME: should also support picking //top.old
            if refbranchname:
                # assume the refseries is OK, since we already resolved
                # commit_str to a git_id
                refseries = Series(refbranchname)
            else:
                refseries = crt_series
            patch = refseries.get_patch(refpatchname)
            if patch.get_log():
                out.info("Log was %s" % newpatch.get_log())
                out.info("Setting log to %s\n" % patch.get_log())
                newpatch.set_log(patch.get_log())
                out.info("Log is now %s" % newpatch.get_log())
            else:
                out.info("No log for %s\n" % patchname)

        if not options.unapplied:
            modified = crt_series.push_patch(patchname)
        else:
            modified = False

        if crt_series.empty_patch(patchname):
            out.done('empty patch')
        elif modified:
            out.done('modified')
        else:
            out.done()

    print_crt_patch()