forked from mirrors/gecko-dev
		
	 8f59316975
			
		
	
	
		8f59316975
		
	
	
	
	
		
			
			# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D162657
		
			
				
	
	
		
			196 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			196 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # 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 # noqa E501
 | |
|     # 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)
 |