зеркало из https://github.com/mozilla/gecko-dev.git
213 строки
6.6 KiB
Python
213 строки
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/.
|
|
|
|
from __future__ import absolute_import, print_function
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from abc import ABCMeta, abstractmethod, abstractproperty
|
|
|
|
GIT_CINNABAR_NOT_FOUND = """
|
|
Could not detect `git-cinnabar`.
|
|
|
|
The `mach try` command requires git-cinnabar to be installed when
|
|
pushing from git. For more information and installation instruction,
|
|
please see:
|
|
|
|
https://github.com/glandium/git-cinnabar
|
|
""".lstrip()
|
|
|
|
HG_PUSH_TO_TRY_NOT_FOUND = """
|
|
Could not detect `push-to-try`.
|
|
|
|
The `mach try` command requires the push-to-try extension enabled
|
|
when pushing from hg. Please install it by running:
|
|
|
|
$ ./mach mercurial-setup
|
|
""".lstrip()
|
|
|
|
VCS_NOT_FOUND = """
|
|
Could not detect version control. Only `hg` or `git` are supported.
|
|
""".strip()
|
|
|
|
UNCOMMITTED_CHANGES = """
|
|
ERROR please commit changes before continuing
|
|
""".strip()
|
|
|
|
|
|
class VCSHelper(object):
|
|
"""A abstract base VCS helper that detects hg or git"""
|
|
__metaclass__ = ABCMeta
|
|
|
|
def __init__(self, root):
|
|
self.root = root
|
|
|
|
@classmethod
|
|
def find_vcs(cls):
|
|
# First check if we're in an hg repo, if not try git
|
|
commands = (
|
|
['hg', 'root'],
|
|
['git', 'rev-parse', '--show-toplevel'],
|
|
)
|
|
|
|
for cmd in commands:
|
|
try:
|
|
output = subprocess.check_output(cmd, stderr=open(os.devnull, 'w')).strip()
|
|
except (subprocess.CalledProcessError, OSError):
|
|
continue
|
|
|
|
return cmd[0], output
|
|
return None, ''
|
|
|
|
@classmethod
|
|
def create(cls):
|
|
vcs, root = cls.find_vcs()
|
|
if not vcs:
|
|
print(VCS_NOT_FOUND)
|
|
sys.exit(1)
|
|
return vcs_class[vcs](root)
|
|
|
|
def run(self, cmd):
|
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
out, err = proc.communicate()
|
|
|
|
if proc.returncode:
|
|
print("Error running `{}`:".format(' '.join(cmd)))
|
|
if out:
|
|
print("stdout:\n{}".format(out))
|
|
if err:
|
|
print("stderr:\n{}".format(err))
|
|
raise subprocess.CalledProcessError(proc.returncode, cmd, out)
|
|
return out
|
|
|
|
def write_task_config(self, labels, templates=None):
|
|
config = os.path.join(self.root, 'try_task_config.json')
|
|
with open(config, 'w') as fh:
|
|
try_task_config = {'tasks': sorted(labels)}
|
|
if templates:
|
|
try_task_config['templates'] = templates
|
|
|
|
json.dump(try_task_config, fh, indent=2, separators=(',', ':'))
|
|
fh.write('\n')
|
|
return config
|
|
|
|
def check_working_directory(self, push=True):
|
|
if not push:
|
|
return
|
|
|
|
if self.has_uncommitted_changes:
|
|
print(UNCOMMITTED_CHANGES)
|
|
sys.exit(1)
|
|
|
|
def push_to_try(self, method, msg, labels=None, templates=None, push=True,
|
|
closed_tree=False):
|
|
closed_tree_string = " ON A CLOSED TREE" if closed_tree else ""
|
|
commit_message = ('%s%s\n\nPushed via `mach try %s`' %
|
|
(msg, closed_tree_string, method))
|
|
|
|
self.check_working_directory(push)
|
|
|
|
config = None
|
|
if labels or labels == []:
|
|
config = self.write_task_config(labels, templates)
|
|
|
|
try:
|
|
if not push:
|
|
print("Commit message:")
|
|
print(commit_message)
|
|
if config:
|
|
print("Calculated try_task_config.json:")
|
|
with open(config) as fh:
|
|
print(fh.read())
|
|
return
|
|
|
|
self._push_to_try(commit_message, config)
|
|
finally:
|
|
if config and os.path.isfile(config):
|
|
os.remove(config)
|
|
|
|
@abstractmethod
|
|
def _push_to_try(self, msg, config):
|
|
pass
|
|
|
|
@abstractproperty
|
|
def files_changed(self):
|
|
pass
|
|
|
|
@abstractproperty
|
|
def has_uncommitted_changes(self):
|
|
pass
|
|
|
|
|
|
class HgHelper(VCSHelper):
|
|
|
|
def _push_to_try(self, msg, config):
|
|
try:
|
|
if config:
|
|
self.run(['hg', 'add', config])
|
|
return subprocess.check_call(['hg', 'push-to-try', '-m', msg])
|
|
except subprocess.CalledProcessError:
|
|
try:
|
|
self.run(['hg', 'showconfig', 'extensions.push-to-try'])
|
|
except subprocess.CalledProcessError:
|
|
print(HG_PUSH_TO_TRY_NOT_FOUND)
|
|
return 1
|
|
finally:
|
|
self.run(['hg', 'revert', '-a'])
|
|
|
|
@property
|
|
def files_changed(self):
|
|
return self.run(['hg', 'log', '-r', '::. and not public()',
|
|
'--template', '{join(files, "\n")}\n']).splitlines()
|
|
|
|
@property
|
|
def has_uncommitted_changes(self):
|
|
stat = [s for s in self.run(['hg', 'status', '-amrn']).split() if s]
|
|
return len(stat) > 0
|
|
|
|
|
|
class GitHelper(VCSHelper):
|
|
|
|
def _push_to_try(self, msg, config):
|
|
try:
|
|
subprocess.check_output(['git', 'cinnabar', '--version'], stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError:
|
|
print(GIT_CINNABAR_NOT_FOUND)
|
|
return 1
|
|
|
|
if config:
|
|
self.run(['git', 'add', config])
|
|
subprocess.check_call(['git', 'commit', '--allow-empty', '-m', msg])
|
|
try:
|
|
return subprocess.call(['git', 'push', 'hg::ssh://hg.mozilla.org/try',
|
|
'+HEAD:refs/heads/branches/default/tip'])
|
|
finally:
|
|
self.run(['git', 'reset', 'HEAD~'])
|
|
|
|
@property
|
|
def files_changed(self):
|
|
# This finds the files changed on the current branch based on the
|
|
# diff of the current branch its merge-base base with other branches.
|
|
current_branch = self.run(['git', 'rev-parse', 'HEAD']).strip()
|
|
all_branches = self.run(['git', 'for-each-ref', 'refs/heads', 'refs/remotes',
|
|
'--format=%(objectname)']).splitlines()
|
|
other_branches = set(all_branches) - set([current_branch])
|
|
base_commit = self.run(['git', 'merge-base', 'HEAD'] + list(other_branches)).strip()
|
|
return self.run(['git', 'diff', '--name-only', '-z', 'HEAD',
|
|
base_commit]).strip('\0').split('\0')
|
|
|
|
@property
|
|
def has_uncommitted_changes(self):
|
|
stat = [s for s in self.run(['git', 'diff', '--cached', '--name-only',
|
|
'--diff-filter=AMD']).split() if s]
|
|
return len(stat) > 0
|
|
|
|
|
|
vcs_class = {
|
|
'git': GitHelper,
|
|
'hg': HgHelper,
|
|
}
|