tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

tree.py (6792B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import re
      6 import tempfile
      7 
      8 from wptrunner import update as wptupdate
      9 from wptrunner.update.tree import Commit, CommitMessage, get_unique_name
     10 
     11 
     12 class HgTree(wptupdate.tree.HgTree):
     13    def __init__(self, *args, **kwargs):
     14        self.commit_cls = kwargs.pop("commit_cls", Commit)
     15        wptupdate.tree.HgTree.__init__(self, *args, **kwargs)
     16 
     17    # TODO: The extra methods for upstreaming patches from a
     18    # hg checkout
     19 
     20 
     21 class GitTree(wptupdate.tree.GitTree):
     22    def __init__(self, *args, **kwargs):
     23        """Extension of the basic GitTree with extra methods for
     24        transfering patches"""
     25        commit_cls = kwargs.pop("commit_cls", Commit)
     26        wptupdate.tree.GitTree.__init__(self, *args, **kwargs)
     27        self.commit_cls = commit_cls
     28 
     29    def rev_from_hg(self, rev):
     30        return self.git("cinnabar", "hg2git", rev).strip()
     31 
     32    def rev_to_hg(self, rev):
     33        return self.git("cinnabar", "git2hg", rev).strip()
     34 
     35    def create_branch(self, name, ref=None):
     36        """Create a named branch,
     37 
     38        :param name: String representing the branch name.
     39        :param ref: None to use current HEAD or rev that the branch should point to"""
     40 
     41        args = []
     42        if ref is not None:
     43            if hasattr(ref, "sha1"):
     44                ref = ref.sha1
     45            args.append(ref)
     46        self.git("branch", name, *args)
     47 
     48    def commits_by_message(self, message, path=None):
     49        """List of commits with messages containing a given string.
     50 
     51        :param message: The string that must be contained in the message.
     52        :param path: Path to a file or directory the commit touches
     53        """
     54        args = ["--pretty=format:%H", "--reverse", "-z", "--grep=%s" % message]
     55        if path is not None:
     56            args.append("--")
     57            args.append(path)
     58        data = self.git("log", *args)
     59        return [self.commit_cls(self, sha1) for sha1 in data.split("\0")]
     60 
     61    def log(self, base_commit=None, path=None):
     62        """List commits touching a certian path from a given base commit.
     63 
     64        :base_param commit: Commit object for the base commit from which to log
     65        :param path: Path that the commits must touch
     66        """
     67        args = ["--pretty=format:%H", "--reverse", "-z"]
     68        if base_commit is not None:
     69            args.append("%s.." % base_commit.sha1)
     70        if path is not None:
     71            args.append("--")
     72            args.append(path)
     73        data = self.git("log", *args)
     74        return [self.commit_cls(self, sha1) for sha1 in data.split("\0") if sha1]
     75 
     76    def import_patch(self, patch):
     77        """Import a patch file into the tree and commit it
     78 
     79        :param patch: a Patch object containing the patch to import
     80        """
     81 
     82        with tempfile.NamedTemporaryFile() as f:
     83            f.write(patch.diff)
     84            f.flush()
     85            f.seek(0)
     86            self.git("apply", "--index", f.name)
     87        self.git("commit", "-m", patch.message.text, "--author=%s" % patch.full_author)
     88 
     89    def rebase(self, ref, continue_rebase=False):
     90        """Rebase the current branch onto another commit.
     91 
     92        :param ref: A Commit object for the commit to rebase onto
     93        :param continue_rebase: Continue an in-progress rebase"""
     94        if continue_rebase:
     95            args = ["--continue"]
     96        else:
     97            if hasattr(ref, "sha1"):
     98                ref = ref.sha1
     99            args = [ref]
    100        self.git("rebase", *args)
    101 
    102    def push(self, remote, local_ref, remote_ref, force=False):
    103        """Push local changes to a remote.
    104 
    105        :param remote: URL of the remote to push to
    106        :param local_ref: Local branch to push
    107        :param remote_ref: Name of the remote branch to push to
    108        :param force: Do a force push
    109        """
    110        args = []
    111        if force:
    112            args.append("-f")
    113        args.extend([remote, "%s:%s" % (local_ref, remote_ref)])
    114        self.git("push", *args)
    115 
    116    def unique_branch_name(self, prefix):
    117        """Get an unused branch name in the local tree
    118 
    119        :param prefix: Prefix to use at the start of the branch name"""
    120        branches = [
    121            ref[len("refs/heads/") :]
    122            for sha1, ref in self.list_refs()
    123            if ref.startswith("refs/heads/")
    124        ]
    125        return get_unique_name(branches, prefix)
    126 
    127 
    128 class Patch:
    129    def __init__(self, author, email, message, diff):
    130        self.author = author
    131        self.email = email
    132        if isinstance(message, CommitMessage):
    133            self.message = message
    134        else:
    135            self.message = GeckoCommitMessage(message)
    136        self.diff = diff
    137 
    138    def __repr__(self):
    139        return "<Patch (%s)>" % self.message.full_summary
    140 
    141    @property
    142    def full_author(self):
    143        return "%s <%s>" % (self.author, self.email)
    144 
    145    @property
    146    def empty(self):
    147        return bool(self.diff.strip())
    148 
    149 
    150 class GeckoCommitMessage(CommitMessage):
    151    """Commit message following the Gecko conventions for identifying bug number
    152    and reviewer"""
    153 
    154    # c.f. http://hg.mozilla.org/hgcustom/version-control-tools/file/tip/hghooks/mozhghooks/commit-message.py # noqa E501
    155    # which has the regexps that are actually enforced by the VCS hooks. These are
    156    # slightly different because we need to parse out specific parts of the message rather
    157    # than just enforce a general pattern.
    158 
    159    _bug_re = re.compile(
    160        r"^Bug (\d+)[^\w]*(?:Part \d+[^\w]*)?(.*?)\s*(?:r=(\w*))?$", re.IGNORECASE
    161    )
    162 
    163    _backout_re = re.compile(
    164        r"^(?:Back(?:ing|ed)\s+out)|Backout|(?:Revert|(?:ed|ing))", re.IGNORECASE
    165    )
    166    _backout_sha1_re = re.compile(r"(?:\s|\:)(0-9a-f){12}")
    167 
    168    def _parse_message(self):
    169        CommitMessage._parse_message(self)
    170 
    171        if self._backout_re.match(self.full_summary):
    172            self.backouts = self._backout_re.findall(self.full_summary)
    173        else:
    174            self.backouts = []
    175 
    176        m = self._bug_re.match(self.full_summary)
    177        if m is not None:
    178            self.bug, self.summary, self.reviewer = m.groups()
    179        else:
    180            self.bug, self.summary, self.reviewer = None, self.full_summary, None
    181 
    182 
    183 class GeckoCommit(Commit):
    184    msg_cls = GeckoCommitMessage
    185 
    186    def export_patch(self, path=None):
    187        """Convert a commit in the tree to a Patch with the bug number and
    188        reviewer stripped from the message"""
    189        args = ["--binary", "--patch", "--format=format:", "%s" % (self.sha1,)]
    190        if path is not None:
    191            args.append("--")
    192            args.append(path)
    193 
    194        diff = self.git("show", *args)
    195 
    196        return Patch(self.author, self.email, self.message, diff)