зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1413922 - [tryselect] Merge vcs.py into mozversioncontrol r=gps
Differential Revision: https://phabricator.services.mozilla.com/D1808 --HG-- rename : tools/tryselect/vcs.py => tools/tryselect/push.py extra : moz-landing-system : lando
This commit is contained in:
Родитель
cd200f6d34
Коммит
23c2416671
|
@ -26,6 +26,15 @@ class MissingConfigureInfo(MissingVCSInfo):
|
||||||
"""Represents error finding VCS info from configure data."""
|
"""Represents error finding VCS info from configure data."""
|
||||||
|
|
||||||
|
|
||||||
|
class MissingVCSExtension(MissingVCSInfo):
|
||||||
|
"""Represents error finding a required VCS extension."""
|
||||||
|
|
||||||
|
def __init__(self, ext):
|
||||||
|
self.ext = ext
|
||||||
|
msg = "Could not detect required extension '{}'".format(self.ext)
|
||||||
|
super(MissingVCSExtension, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
class InvalidRepoPath(Exception):
|
class InvalidRepoPath(Exception):
|
||||||
"""Represents a failure to find a VCS repo at a specified path."""
|
"""Represents a failure to find a VCS repo at a specified path."""
|
||||||
|
|
||||||
|
@ -108,6 +117,11 @@ class Repository(object):
|
||||||
self.version = LooseVersion(match.group(1))
|
self.version = LooseVersion(match.group(1))
|
||||||
return self.version
|
return self.version
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_git_cinnabar(self):
|
||||||
|
"""True if the repository is using git cinnabar."""
|
||||||
|
return False
|
||||||
|
|
||||||
@abc.abstractproperty
|
@abc.abstractproperty
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Name of the tool."""
|
"""Name of the tool."""
|
||||||
|
@ -188,6 +202,16 @@ class Repository(object):
|
||||||
to factor these file classes into consideration.
|
to factor these file classes into consideration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def push_to_try(self, message):
|
||||||
|
"""Create a temporary commit, push it to try and clean it up
|
||||||
|
afterwards.
|
||||||
|
|
||||||
|
With mercurial, MissingVCSExtension will be raised if the `push-to-try`
|
||||||
|
extension is not installed. On git, MissingVCSExtension will be raised
|
||||||
|
if git cinnabar is not present.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class HgRepository(Repository):
|
class HgRepository(Repository):
|
||||||
'''An implementation of `Repository` for Mercurial repositories.'''
|
'''An implementation of `Repository` for Mercurial repositories.'''
|
||||||
|
@ -306,7 +330,7 @@ class HgRepository(Repository):
|
||||||
self._run(b'files', b'-0').split(b'\0') if p)
|
self._run(b'files', b'-0').split(b'\0') if p)
|
||||||
|
|
||||||
def working_directory_clean(self, untracked=False, ignored=False):
|
def working_directory_clean(self, untracked=False, ignored=False):
|
||||||
args = [b'status', b'\0', b'--modified', b'--added', b'--removed',
|
args = [b'status', b'--modified', b'--added', b'--removed',
|
||||||
b'--deleted']
|
b'--deleted']
|
||||||
if untracked:
|
if untracked:
|
||||||
args.append(b'--unknown')
|
args.append(b'--unknown')
|
||||||
|
@ -317,6 +341,18 @@ class HgRepository(Repository):
|
||||||
# means we are clean.
|
# means we are clean.
|
||||||
return not len(self._run(*args).strip())
|
return not len(self._run(*args).strip())
|
||||||
|
|
||||||
|
def push_to_try(self, message):
|
||||||
|
try:
|
||||||
|
subprocess.check_call((self._tool, 'push-to-try', '-m', message), cwd=self.path)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
try:
|
||||||
|
self._run('showconfig', 'extensions.push-to-try')
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
raise MissingVCSExtension('push-to-try')
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
self._run('revert', '-a')
|
||||||
|
|
||||||
|
|
||||||
class GitRepository(Repository):
|
class GitRepository(Repository):
|
||||||
'''An implementation of `Repository` for Git repositories.'''
|
'''An implementation of `Repository` for Git repositories.'''
|
||||||
|
@ -340,6 +376,14 @@ class GitRepository(Repository):
|
||||||
refs.remove(head)
|
refs.remove(head)
|
||||||
return self._run('merge-base', 'HEAD', *refs).strip()
|
return self._run('merge-base', 'HEAD', *refs).strip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_git_cinnabar(self):
|
||||||
|
try:
|
||||||
|
self._run('cinnabar', '--version')
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def sparse_checkout_present(self):
|
def sparse_checkout_present(self):
|
||||||
# Not yet implemented.
|
# Not yet implemented.
|
||||||
return False
|
return False
|
||||||
|
@ -393,6 +437,17 @@ class GitRepository(Repository):
|
||||||
|
|
||||||
return not len(self._run(*args).strip())
|
return not len(self._run(*args).strip())
|
||||||
|
|
||||||
|
def push_to_try(self, message):
|
||||||
|
if not self.has_git_cinnabar:
|
||||||
|
raise MissingVCSExtension('cinnabar')
|
||||||
|
|
||||||
|
self._run('commit', '--allow-empty', '-m', message)
|
||||||
|
try:
|
||||||
|
subprocess.check_call((self._tool, 'push', 'hg::ssh://hg.mozilla.org/try',
|
||||||
|
'+HEAD:refs/heads/branches/default/tip'), cwd=self.path)
|
||||||
|
finally:
|
||||||
|
self._run('reset', 'HEAD~')
|
||||||
|
|
||||||
|
|
||||||
def get_repository_object(path, hg='hg', git='git'):
|
def get_repository_object(path, hg='hg', git='git'):
|
||||||
'''Get a repository object for the repository at `path`.
|
'''Get a repository object for the repository at `path`.
|
||||||
|
|
|
@ -3,4 +3,5 @@ subsuite=mozversioncontrol
|
||||||
skip-if = python == 3
|
skip-if = python == 3
|
||||||
|
|
||||||
[test_context_manager.py]
|
[test_context_manager.py]
|
||||||
|
[test_push_to_try.py]
|
||||||
[test_workdir_outgoing.py]
|
[test_workdir_outgoing.py]
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
# 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, unicode_literals
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import mozunit
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mozversioncontrol import (
|
||||||
|
get_repository_object,
|
||||||
|
MissingVCSExtension,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_push_to_try(repo, monkeypatch):
|
||||||
|
commit_message = "commit message"
|
||||||
|
vcs = get_repository_object(repo.strpath)
|
||||||
|
|
||||||
|
captured_commands = []
|
||||||
|
|
||||||
|
def fake_run(*args, **kwargs):
|
||||||
|
captured_commands.append(args[0])
|
||||||
|
|
||||||
|
monkeypatch.setattr(subprocess, 'check_output', fake_run)
|
||||||
|
monkeypatch.setattr(subprocess, 'check_call', fake_run)
|
||||||
|
|
||||||
|
vcs.push_to_try(commit_message)
|
||||||
|
tool = vcs._tool
|
||||||
|
|
||||||
|
if repo.vcs == 'hg':
|
||||||
|
expected = [
|
||||||
|
(tool, 'push-to-try', '-m', commit_message),
|
||||||
|
(tool, 'revert', '-a'),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
expected = [
|
||||||
|
(tool, 'cinnabar', '--version'),
|
||||||
|
(tool, 'commit', '--allow-empty', '-m', commit_message),
|
||||||
|
(tool, 'push', 'hg::ssh://hg.mozilla.org/try',
|
||||||
|
'+HEAD:refs/heads/branches/default/tip'),
|
||||||
|
(tool, 'reset', 'HEAD~'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, value in enumerate(captured_commands):
|
||||||
|
assert value == expected[i]
|
||||||
|
|
||||||
|
assert len(captured_commands) == len(expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_push_to_try_missing_extensions(repo, monkeypatch):
|
||||||
|
vcs = get_repository_object(repo.strpath)
|
||||||
|
|
||||||
|
orig = vcs._run
|
||||||
|
|
||||||
|
def cinnabar_raises(*args, **kwargs):
|
||||||
|
# Simulate not having git cinnabar
|
||||||
|
if args[0] == 'cinnabar':
|
||||||
|
raise subprocess.CalledProcessError(1, args)
|
||||||
|
return orig(*args, **kwargs)
|
||||||
|
|
||||||
|
monkeypatch.setattr(vcs, '_run', cinnabar_raises)
|
||||||
|
|
||||||
|
with pytest.raises(MissingVCSExtension):
|
||||||
|
vcs.push_to_try("commit message")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mozunit.main()
|
|
@ -0,0 +1,104 @@
|
||||||
|
# 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 sys
|
||||||
|
|
||||||
|
from mozbuild.base import MozbuildObject
|
||||||
|
from mozversioncontrol import get_repository_object, MissingVCSExtension
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
build = MozbuildObject.from_environment(cwd=here)
|
||||||
|
vcs = get_repository_object(build.topsrcdir)
|
||||||
|
|
||||||
|
|
||||||
|
def write_task_config(labels, templates=None):
|
||||||
|
config = os.path.join(vcs.path, '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(push=True):
|
||||||
|
if not push:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not vcs.working_directory_clean():
|
||||||
|
print(UNCOMMITTED_CHANGES)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def push_to_try(method, msg, labels=None, templates=None, push=True, closed_tree=False):
|
||||||
|
check_working_directory(push)
|
||||||
|
|
||||||
|
# Format the commit message
|
||||||
|
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))
|
||||||
|
|
||||||
|
config = None
|
||||||
|
if labels or labels == []:
|
||||||
|
config = 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
|
||||||
|
|
||||||
|
if config:
|
||||||
|
vcs.add_remove_files(config)
|
||||||
|
|
||||||
|
try:
|
||||||
|
vcs.push_to_try(commit_message)
|
||||||
|
except MissingVCSExtension as e:
|
||||||
|
if e.ext == 'push-to-try':
|
||||||
|
print(HG_PUSH_TO_TRY_NOT_FOUND)
|
||||||
|
elif e.ext == 'cinnabar':
|
||||||
|
print(GIT_CINNABAR_NOT_FOUND)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
if config and os.path.isfile(config):
|
||||||
|
os.remove(config)
|
|
@ -5,7 +5,7 @@
|
||||||
from __future__ import absolute_import, print_function, unicode_literals
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
from ..cli import BaseTryParser
|
from ..cli import BaseTryParser
|
||||||
from ..vcs import VCSHelper
|
from ..push import push_to_try
|
||||||
|
|
||||||
|
|
||||||
class EmptyParser(BaseTryParser):
|
class EmptyParser(BaseTryParser):
|
||||||
|
@ -14,7 +14,6 @@ class EmptyParser(BaseTryParser):
|
||||||
|
|
||||||
|
|
||||||
def run_empty_try(message='{msg}', push=True, **kwargs):
|
def run_empty_try(message='{msg}', push=True, **kwargs):
|
||||||
vcs = VCSHelper.create()
|
|
||||||
msg = 'No try selector specified, use "Add New Jobs" to select tasks.'
|
msg = 'No try selector specified, use "Add New Jobs" to select tasks.'
|
||||||
return vcs.push_to_try('empty', message.format(msg=msg), [], push=push,
|
return push_to_try('empty', message.format(msg=msg), [], push=push,
|
||||||
closed_tree=kwargs["closed_tree"])
|
closed_tree=kwargs["closed_tree"])
|
||||||
|
|
|
@ -19,7 +19,7 @@ from six import string_types
|
||||||
from .. import preset as pset
|
from .. import preset as pset
|
||||||
from ..cli import BaseTryParser
|
from ..cli import BaseTryParser
|
||||||
from ..tasks import generate_tasks
|
from ..tasks import generate_tasks
|
||||||
from ..vcs import VCSHelper
|
from ..push import check_working_directory, push_to_try, vcs
|
||||||
|
|
||||||
terminal = Terminal()
|
terminal = Terminal()
|
||||||
|
|
||||||
|
@ -221,10 +221,8 @@ def run_fuzzy_try(update=False, query=None, templates=None, full=False, paramete
|
||||||
print(FZF_NOT_FOUND)
|
print(FZF_NOT_FOUND)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
vcs = VCSHelper.create()
|
check_working_directory(push)
|
||||||
vcs.check_working_directory(push)
|
all_tasks = generate_tasks(parameters, full, root=vcs.path)
|
||||||
|
|
||||||
all_tasks = generate_tasks(parameters, full, root=vcs.root)
|
|
||||||
|
|
||||||
if paths:
|
if paths:
|
||||||
all_tasks = filter_by_paths(all_tasks, paths)
|
all_tasks = filter_by_paths(all_tasks, paths)
|
||||||
|
@ -281,5 +279,5 @@ def run_fuzzy_try(update=False, query=None, templates=None, full=False, paramete
|
||||||
args.extend(["query={}".format(q) for q in queries])
|
args.extend(["query={}".format(q) for q in queries])
|
||||||
if args:
|
if args:
|
||||||
msg = "{} {}".format(msg, '&'.join(args))
|
msg = "{} {}".format(msg, '&'.join(args))
|
||||||
return vcs.push_to_try('fuzzy', message.format(msg=msg), selected, templates, push=push,
|
return push_to_try('fuzzy', message.format(msg=msg), selected, templates, push=push,
|
||||||
closed_tree=kwargs["closed_tree"])
|
closed_tree=kwargs["closed_tree"])
|
||||||
|
|
|
@ -14,7 +14,7 @@ from moztest.resolve import TestResolver
|
||||||
|
|
||||||
from .. import preset
|
from .. import preset
|
||||||
from ..cli import BaseTryParser
|
from ..cli import BaseTryParser
|
||||||
from ..vcs import VCSHelper
|
from ..push import push_to_try
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
@ -314,7 +314,6 @@ class AutoTry(object):
|
||||||
self.topsrcdir = topsrcdir
|
self.topsrcdir = topsrcdir
|
||||||
self._resolver = None
|
self._resolver = None
|
||||||
self.mach_context = mach_context
|
self.mach_context = mach_context
|
||||||
self.vcs = VCSHelper.create()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def resolver(self):
|
def resolver(self):
|
||||||
|
@ -620,7 +619,7 @@ class AutoTry(object):
|
||||||
if kwargs["verbose"]:
|
if kwargs["verbose"]:
|
||||||
print('The following try syntax was calculated:\n%s' % msg)
|
print('The following try syntax was calculated:\n%s' % msg)
|
||||||
|
|
||||||
self.vcs.push_to_try('syntax', kwargs["message"].format(msg=msg), push=kwargs['push'],
|
push_to_try('syntax', kwargs["message"].format(msg=msg), push=kwargs['push'],
|
||||||
closed_tree=kwargs["closed_tree"])
|
closed_tree=kwargs["closed_tree"])
|
||||||
|
|
||||||
if kwargs["save"]:
|
if kwargs["save"]:
|
||||||
|
|
|
@ -1,212 +0,0 @@
|
||||||
# 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,
|
|
||||||
}
|
|
Загрузка…
Ссылка в новой задаче