DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (25c1ba3e39da)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import re
import tempfile

from wptrunner import update as wptupdate

from wptrunner.update.tree import Commit, CommitMessage, get_unique_name

class HgTree(wptupdate.tree.HgTree):
    def __init__(self, *args, **kwargs):
        self.commit_cls = kwargs.pop("commit_cls", Commit)
        wptupdate.tree.HgTree.__init__(self, *args, **kwargs)

    # TODO: The extra methods for upstreaming patches from a
    # hg checkout

class GitTree(wptupdate.tree.GitTree):
    def __init__(self, *args, **kwargs):
        """Extension of the basic GitTree with extra methods for
        transfering patches"""
        commit_cls = kwargs.pop("commit_cls", Commit)
        wptupdate.tree.GitTree.__init__(self, *args, **kwargs)
        self.commit_cls = commit_cls

    def rev_from_hg(self, rev):
        return self.git("cinnabar", "hg2git", rev).strip()

    def rev_to_hg(self, rev):
        return self.git("cinnabar", "git2hg", rev).strip()

    def create_branch(self, name, ref=None):
        """Create a named branch,

        :param name: String representing the branch name.
        :param ref: None to use current HEAD or rev that the branch should point to"""

        args = []
        if ref is not None:
            if hasattr(ref, "sha1"):
                ref = ref.sha1
            args.append(ref)
        self.git("branch", name, *args)

    def commits_by_message(self, message, path=None):
        """List of commits with messages containing a given string.

        :param message: The string that must be contained in the message.
        :param path: Path to a file or directory the commit touches
        """
        args = ["--pretty=format:%H", "--reverse", "-z", "--grep=%s" % message]
        if path is not None:
            args.append("--")
            args.append(path)
        data = self.git("log", *args)
        return [self.commit_cls(self, sha1) for sha1 in data.split("\0")]

    def log(self, base_commit=None, path=None):
        """List commits touching a certian path from a given base commit.

        :base_param commit: Commit object for the base commit from which to log
        :param path: Path that the commits must touch
        """
        args = ["--pretty=format:%H", "--reverse", "-z"]
        if base_commit is not None:
            args.append("%s.." % base_commit.sha1)
        if path is not None:
            args.append("--")
            args.append(path)
        data = self.git("log", *args)
        return [self.commit_cls(self, sha1) for sha1 in data.split("\0") if sha1]

    def import_patch(self, patch):
        """Import a patch file into the tree and commit it

        :param patch: a Patch object containing the patch to import
        """

        with tempfile.NamedTemporaryFile() as f:
            f.write(patch.diff)
            f.flush()
            f.seek(0)
            self.git("apply", "--index", f.name)
        self.git("commit", "-m", patch.message.text, "--author=%s" % patch.full_author)

    def rebase(self, ref, continue_rebase=False):
        """Rebase the current branch onto another commit.

        :param ref: A Commit object for the commit to rebase onto
        :param continue_rebase: Continue an in-progress rebase"""
        if continue_rebase:
            args = ["--continue"]
        else:
            if hasattr(ref, "sha1"):
                ref = ref.sha1
            args = [ref]
        self.git("rebase", *args)

    def push(self, remote, local_ref, remote_ref, force=False):
        """Push local changes to a remote.

        :param remote: URL of the remote to push to
        :param local_ref: Local branch to push
        :param remote_ref: Name of the remote branch to push to
        :param force: Do a force push
        """
        args = []
        if force:
            args.append("-f")
        args.extend([remote, "%s:%s" % (local_ref, remote_ref)])
        self.git("push", *args)

    def unique_branch_name(self, prefix):
        """Get an unused branch name in the local tree

        :param prefix: Prefix to use at the start of the branch name"""
        branches = [ref[len("refs/heads/"):] for sha1, ref in self.list_refs()
                    if ref.startswith("refs/heads/")]
        return get_unique_name(branches, prefix)

class Patch(object):
    def __init__(self, author, email, message, diff):
        self.author = author
        self.email = email
        if isinstance(message, CommitMessage):
            self.message = message
        else:
            self.message = GeckoCommitMessage(message)
        self.diff = diff

    def __repr__(self):
        return "<Patch (%s)>" % self.message.full_summary

    @property
    def full_author(self):
        return "%s <%s>" % (self.author, self.email)

    @property
    def empty(self):
        return bool(self.diff.strip())


class GeckoCommitMessage(CommitMessage):
    """Commit message following the Gecko conventions for identifying bug number
    and reviewer"""

    # c.f. http://hg.mozilla.org/hgcustom/version-control-tools/file/tip/hghooks/mozhghooks/commit-message.py
    # which has the regexps that are actually enforced by the VCS hooks. These are
    # slightly different because we need to parse out specific parts of the message rather
    # than just enforce a general pattern.

    _bug_re = re.compile("^Bug (\d+)[^\w]*(?:Part \d+[^\w]*)?(.*?)\s*(?:r=(\w*))?$",
                         re.IGNORECASE)

    _backout_re = re.compile("^(?:Back(?:ing|ed)\s+out)|Backout|(?:Revert|(?:ed|ing))",
                             re.IGNORECASE)
    _backout_sha1_re = re.compile("(?:\s|\:)(0-9a-f){12}")

    def _parse_message(self):
        CommitMessage._parse_message(self)

        if self._backout_re.match(self.full_summary):
            self.backouts = self._backout_re.findall(self.full_summary)
        else:
            self.backouts = []

        m = self._bug_re.match(self.full_summary)
        if m is not None:
            self.bug, self.summary, self.reviewer = m.groups()
        else:
            self.bug, self.summary, self.reviewer = None, self.full_summary, None


class GeckoCommit(Commit):
    msg_cls = GeckoCommitMessage

    def export_patch(self, path=None):
        """Convert a commit in the tree to a Patch with the bug number and
        reviewer stripped from the message"""
        args = ["--binary", "--patch", "--format=format:", "%s" % (self.sha1,)]
        if path is not None:
            args.append("--")
            args.append(path)

        diff = self.git("show", *args)

        return Patch(self.author, self.email, self.message, diff)