bumpver/src/pycalver/vcs.py

123 lines
3.2 KiB
Python
Raw Normal View History

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