2018-09-02 21:48:12 +02:00
|
|
|
# This file is part of the pycalver project
|
|
|
|
|
# https://github.com/mbarkhau/pycalver
|
|
|
|
|
#
|
|
|
|
|
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
|
#
|
|
|
|
|
# pycalver/vcs.py (this file) is based on code from the
|
|
|
|
|
# bumpversion project: https://github.com/peritus/bumpversion
|
|
|
|
|
# MIT License - (C) 2013-2014 Filip Noetzel
|
|
|
|
|
|
|
|
|
|
import os
|
2018-09-03 22:23:51 +02:00
|
|
|
import sys
|
2018-09-02 21:48:12 +02:00
|
|
|
import logging
|
|
|
|
|
import tempfile
|
2018-09-03 22:23:51 +02:00
|
|
|
import typing as typ
|
2018-09-02 21:48:12 +02:00
|
|
|
import subprocess as sp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger("pycalver.vcs")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BaseVCS:
|
|
|
|
|
|
2018-09-04 20:16:46 +02:00
|
|
|
_TEST_USABLE_COMMAND: typ.List[str]
|
2018-11-04 21:11:42 +01:00
|
|
|
_COMMIT_COMMAND : typ.List[str]
|
|
|
|
|
_STATUS_COMMAND : typ.List[str]
|
2018-09-04 20:16:46 +02:00
|
|
|
|
2018-09-02 21:48:12 +02:00
|
|
|
@classmethod
|
2018-09-04 20:16:46 +02:00
|
|
|
def commit(cls, message: str) -> None:
|
|
|
|
|
message_data = message.encode("utf-8")
|
|
|
|
|
|
|
|
|
|
tmp_file = tempfile.NamedTemporaryFile("wb", delete=False)
|
|
|
|
|
|
|
|
|
|
with tmp_file as fh:
|
|
|
|
|
fh.write(message_data)
|
|
|
|
|
|
|
|
|
|
cmd = cls._COMMIT_COMMAND + [tmp_file.name]
|
|
|
|
|
env = os.environ.copy()
|
|
|
|
|
# TODO (mb 2018-09-04): check that this works on py27,
|
|
|
|
|
# might need to be bytes there, idk.
|
2018-11-04 21:11:42 +01:00
|
|
|
env['HGENCODING'] = "utf-8"
|
2018-09-04 20:16:46 +02:00
|
|
|
sp.check_output(cmd, env=env)
|
|
|
|
|
os.unlink(tmp_file.name)
|
2018-09-02 21:48:12 +02:00
|
|
|
|
|
|
|
|
@classmethod
|
2018-09-04 20:16:46 +02:00
|
|
|
def is_usable(cls) -> bool:
|
2018-09-02 21:48:12 +02:00
|
|
|
try:
|
2018-11-04 21:11:42 +01:00
|
|
|
return sp.call(cls._TEST_USABLE_COMMAND, stderr=sp.PIPE, stdout=sp.PIPE) == 0
|
2018-09-02 21:48:12 +02:00
|
|
|
except OSError as e:
|
|
|
|
|
if e.errno == 2:
|
|
|
|
|
# mercurial is not installed then, ok.
|
|
|
|
|
return False
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2018-09-04 20:16:46 +02:00
|
|
|
def dirty_files(cls) -> typ.List[str]:
|
2018-09-02 21:48:12 +02:00
|
|
|
status_output = sp.check_output(cls._STATUS_COMMAND)
|
2018-09-03 00:14:10 +02:00
|
|
|
return [
|
|
|
|
|
line.decode("utf-8")[2:].strip()
|
2018-09-02 21:48:12 +02:00
|
|
|
for line in status_output.splitlines()
|
|
|
|
|
if not line.strip().startswith(b"??")
|
|
|
|
|
]
|
|
|
|
|
|
2018-09-03 22:23:51 +02:00
|
|
|
@classmethod
|
|
|
|
|
def assert_not_dirty(cls, filepaths: typ.Set[str], allow_dirty=False) -> None:
|
|
|
|
|
dirty_files = cls.dirty_files()
|
|
|
|
|
|
|
|
|
|
if dirty_files:
|
|
|
|
|
log.warn(f"{cls.__name__} working directory is not clean:")
|
2018-09-05 09:24:01 +02:00
|
|
|
for dirty_file in dirty_files:
|
|
|
|
|
log.warn(" " + dirty_file)
|
2018-09-03 22:23:51 +02:00
|
|
|
|
|
|
|
|
if not allow_dirty and dirty_files:
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
dirty_pattern_files = set(dirty_files) & filepaths
|
|
|
|
|
if dirty_pattern_files:
|
|
|
|
|
log.error("Not commiting when pattern files are dirty:")
|
2018-09-05 09:24:01 +02:00
|
|
|
for dirty_file in dirty_pattern_files:
|
|
|
|
|
log.warn(" " + dirty_file)
|
2018-09-03 22:23:51 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
2018-09-02 21:48:12 +02:00
|
|
|
|
|
|
|
|
class Git(BaseVCS):
|
|
|
|
|
|
|
|
|
|
_TEST_USABLE_COMMAND = ["git", "rev-parse", "--git-dir"]
|
2018-11-04 21:11:42 +01:00
|
|
|
_COMMIT_COMMAND = ["git", "commit" , "-F"]
|
|
|
|
|
_STATUS_COMMAND = ["git", "status" , "--porcelain"]
|
2018-09-02 21:48:12 +02:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def tag(cls, name):
|
|
|
|
|
sp.check_output(["git", "tag", name])
|
|
|
|
|
|
2018-09-03 00:14:10 +02:00
|
|
|
@classmethod
|
|
|
|
|
def add_path(cls, path):
|
|
|
|
|
sp.check_output(["git", "add", "--update", path])
|
|
|
|
|
|
2018-09-02 21:48:12 +02:00
|
|
|
|
|
|
|
|
class Mercurial(BaseVCS):
|
|
|
|
|
|
2018-11-04 21:11:42 +01:00
|
|
|
_TEST_USABLE_COMMAND = ["hg", 'root']
|
|
|
|
|
_COMMIT_COMMAND = ["hg", "commit", "--logfile"]
|
|
|
|
|
_STATUS_COMMAND = ["hg", "status", "-mard"]
|
2018-09-02 21:48:12 +02:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def tag(cls, name):
|
|
|
|
|
sp.check_output(["hg", "tag", name])
|
|
|
|
|
|
2018-09-03 00:14:10 +02:00
|
|
|
@classmethod
|
|
|
|
|
def add_path(cls, path):
|
|
|
|
|
pass
|
|
|
|
|
|
2018-09-02 21:48:12 +02:00
|
|
|
|
|
|
|
|
VCS = [Git, Mercurial]
|
|
|
|
|
|
|
|
|
|
|
2018-09-04 20:16:46 +02:00
|
|
|
def get_vcs() -> typ.Optional[typ.Type[BaseVCS]]:
|
2018-09-02 21:48:12 +02:00
|
|
|
for vcs in VCS:
|
2018-09-03 00:14:10 +02:00
|
|
|
if vcs.is_usable():
|
|
|
|
|
return vcs
|
2018-09-02 21:48:12 +02:00
|
|
|
|
|
|
|
|
return None
|