odoo-version-manager 0.0.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo-version-manager-0.0.10/LICENSE +21 -0
- odoo-version-manager-0.0.10/PKG-INFO +45 -0
- odoo-version-manager-0.0.10/README.md +32 -0
- odoo-version-manager-0.0.10/odoo_version_manager/__init__.py +50 -0
- odoo-version-manager-0.0.10/odoo_version_manager/config.py +86 -0
- odoo-version-manager-0.0.10/odoo_version_manager/consts.py +5 -0
- odoo-version-manager-0.0.10/odoo_version_manager/filelock.py +82 -0
- odoo-version-manager-0.0.10/odoo_version_manager/gitcommands.py +165 -0
- odoo-version-manager-0.0.10/odoo_version_manager/odoo_version_manager.py +274 -0
- odoo-version-manager-0.0.10/odoo_version_manager/repo.py +593 -0
- odoo-version-manager-0.0.10/odoo_version_manager/tools.py +450 -0
- odoo-version-manager-0.0.10/odoo_version_manager.egg-info/PKG-INFO +45 -0
- odoo-version-manager-0.0.10/odoo_version_manager.egg-info/SOURCES.txt +18 -0
- odoo-version-manager-0.0.10/odoo_version_manager.egg-info/dependency_links.txt +1 -0
- odoo-version-manager-0.0.10/odoo_version_manager.egg-info/entry_points.txt +2 -0
- odoo-version-manager-0.0.10/odoo_version_manager.egg-info/requires.txt +7 -0
- odoo-version-manager-0.0.10/odoo_version_manager.egg-info/top_level.txt +1 -0
- odoo-version-manager-0.0.10/setup.cfg +21 -0
- odoo-version-manager-0.0.10/setup.py +61 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Marc Wimmer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: odoo-version-manager
|
|
3
|
+
Version: 0.0.10
|
|
4
|
+
Summary: Manage Versions of module along all odoo version
|
|
5
|
+
Home-page: https://github.com/odoo-module-version-manager.git
|
|
6
|
+
Author: Marc-Christian Wimmer
|
|
7
|
+
Author-email: marc@zebroo.de
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.9.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# odoo-version-manager
|
|
15
|
+
|
|
16
|
+
Helps handling updates on main branch of a module to be deployed to all sub branches.
|
|
17
|
+
It uses github workflows to accomplish this.
|
|
18
|
+
|
|
19
|
+
## installing
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pipx install odoo-version-manager
|
|
23
|
+
odoo-version-manager completion -x
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
# usage
|
|
27
|
+
|
|
28
|
+
## initial setup
|
|
29
|
+
|
|
30
|
+
- create a repository like an OCA repository with some modules on branch **main**
|
|
31
|
+
- decide which version the main branch is for example 16.0
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
odoo-version-manager setup 16.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## set another odoo version for main branch
|
|
38
|
+
|
|
39
|
+
If you move on and main branch becomes odoo version 18.0 instead of 16.0 do the following:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
git checkout main
|
|
43
|
+
git reset --hard origin/18.0
|
|
44
|
+
odoo-version-manager setup 18.0
|
|
45
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# odoo-version-manager
|
|
2
|
+
|
|
3
|
+
Helps handling updates on main branch of a module to be deployed to all sub branches.
|
|
4
|
+
It uses github workflows to accomplish this.
|
|
5
|
+
|
|
6
|
+
## installing
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pipx install odoo-version-manager
|
|
10
|
+
odoo-version-manager completion -x
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
# usage
|
|
14
|
+
|
|
15
|
+
## initial setup
|
|
16
|
+
|
|
17
|
+
- create a repository like an OCA repository with some modules on branch **main**
|
|
18
|
+
- decide which version the main branch is for example 16.0
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
odoo-version-manager setup 16.0
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## set another odoo version for main branch
|
|
25
|
+
|
|
26
|
+
If you move on and main branch becomes odoo version 18.0 instead of 16.0 do the following:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
git checkout main
|
|
30
|
+
git reset --hard origin/18.0
|
|
31
|
+
odoo-version-manager setup 18.0
|
|
32
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import subprocess
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from .config import pass_config
|
|
7
|
+
from .config import Config
|
|
8
|
+
import shellingham
|
|
9
|
+
from click_default_group import DefaultGroup
|
|
10
|
+
|
|
11
|
+
global_data = {
|
|
12
|
+
'config': None
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
@pass_config
|
|
17
|
+
def cli(config):
|
|
18
|
+
global_data['config'] = config
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@cli.command()
|
|
23
|
+
def install_completion():
|
|
24
|
+
def setup_for_shell_generic(shell, shell_call):
|
|
25
|
+
path = Path(f"/etc/{shell}_completion.d")
|
|
26
|
+
NAME = shell_call.upper().replace("-", "_")
|
|
27
|
+
completion = subprocess.check_output([sys.argv[0]], env={f"_{NAME}_COMPLETE": f"{shell}_source"}, shell=True)
|
|
28
|
+
if path.exists():
|
|
29
|
+
if os.access(path, os.W_OK):
|
|
30
|
+
(path / shell_call).write_bytes(completion)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
if not (path / shell_call).exists():
|
|
34
|
+
rc = Path(os.path.expanduser("~")) / f'.{shell}rc'
|
|
35
|
+
if not rc.exists():
|
|
36
|
+
return
|
|
37
|
+
complete_file = rc.parent / f'.{shell_call}-completion.sh'
|
|
38
|
+
complete_file.write_bytes(completion)
|
|
39
|
+
if complete_file.name not in rc.read_text():
|
|
40
|
+
content = rc.read_text()
|
|
41
|
+
content += '\nsource ~/' + complete_file.name
|
|
42
|
+
rc.write_text(content)
|
|
43
|
+
|
|
44
|
+
name = Path(sys.argv[0]).name
|
|
45
|
+
setup_for_shell_generic(shellingham.detect_shell()[0], name)
|
|
46
|
+
sys.exit(0)
|
|
47
|
+
|
|
48
|
+
from . import odoo_version_manager
|
|
49
|
+
from . import gitcommands
|
|
50
|
+
from . import repo
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import click
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import sys
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
import configparser
|
|
10
|
+
from paramiko.config import SSHConfig
|
|
11
|
+
import subprocess
|
|
12
|
+
|
|
13
|
+
BASE_PATH = Path(os.path.expanduser("~/.fetch_latest_file.d"))
|
|
14
|
+
SSH_CONFIG = Path(os.path.expanduser("~/.ssh/config"))
|
|
15
|
+
|
|
16
|
+
class Config(object):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.sources = {}
|
|
20
|
+
self.source = None
|
|
21
|
+
if BASE_PATH.exists():
|
|
22
|
+
for file in BASE_PATH.glob("*"):
|
|
23
|
+
if file.name.startswith('.'):
|
|
24
|
+
continue
|
|
25
|
+
for section, config in self.parse_file(file):
|
|
26
|
+
self.sources[section] = config
|
|
27
|
+
|
|
28
|
+
def cleanup():
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
atexit.register(cleanup)
|
|
32
|
+
|
|
33
|
+
def parse_file(self, file):
|
|
34
|
+
config = configparser.ConfigParser()
|
|
35
|
+
config.read(file)
|
|
36
|
+
for section in config.sections():
|
|
37
|
+
yield (section, config[section])
|
|
38
|
+
|
|
39
|
+
@contextmanager
|
|
40
|
+
def shell(self):
|
|
41
|
+
config = self.get_source()
|
|
42
|
+
|
|
43
|
+
def execute(cmd):
|
|
44
|
+
output = subprocess.check_output([
|
|
45
|
+
"ssh", config['host'],
|
|
46
|
+
] + cmd)
|
|
47
|
+
output = output.decode('utf-8').split("\n")
|
|
48
|
+
return output
|
|
49
|
+
|
|
50
|
+
yield config, execute
|
|
51
|
+
|
|
52
|
+
def add(self, filename, host, username, path, regex, destination):
|
|
53
|
+
path = BASE_PATH / Path(filename).name
|
|
54
|
+
config = configparser.ConfigParser()
|
|
55
|
+
BASE_PATH.mkdir(exist_ok=True, parents=True)
|
|
56
|
+
if path.exists():
|
|
57
|
+
config.read(path)
|
|
58
|
+
config[self.source] = {
|
|
59
|
+
"host": host,
|
|
60
|
+
"path": path,
|
|
61
|
+
"regex": regex,
|
|
62
|
+
"destination": destination,
|
|
63
|
+
}
|
|
64
|
+
if username:
|
|
65
|
+
config[self.source]['username'] = username
|
|
66
|
+
with open(path, 'w') as configfile:
|
|
67
|
+
config.write(configfile)
|
|
68
|
+
|
|
69
|
+
def get_source(self):
|
|
70
|
+
if not self.source:
|
|
71
|
+
raise Exception("Please define a source first!")
|
|
72
|
+
return self.sources[self.source]
|
|
73
|
+
|
|
74
|
+
def setup_logging(self):
|
|
75
|
+
FORMAT = '[%(levelname)s] %(asctime)s %(message)s'
|
|
76
|
+
formatter = logging.Formatter(FORMAT)
|
|
77
|
+
logging.basicConfig(format=FORMAT)
|
|
78
|
+
self.logger = logging.getLogger('') # root handler
|
|
79
|
+
self.logger.setLevel(self.log_level)
|
|
80
|
+
|
|
81
|
+
stdout_handler = logging.StreamHandler(sys.stdout)
|
|
82
|
+
self.logger.addHandler(stdout_handler)
|
|
83
|
+
stdout_handler.setFormatter(formatter)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
pass_config = click.make_pass_decorator(Config, ensure=True)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import errno
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FileLockException(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FileLock(object):
|
|
11
|
+
"""A file locking mechanism that has context-manager support so
|
|
12
|
+
you can use it in a with statement. This should be relatively cross
|
|
13
|
+
compatible as it doesn't rely on msvcrt or fcntl for the locking.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, file_name, timeout=10, delay=0.05):
|
|
17
|
+
"""Prepare the file locker. Specify the file to lock and optionally
|
|
18
|
+
the maximum timeout and the delay between each attempt to lock.
|
|
19
|
+
"""
|
|
20
|
+
if timeout is not None and delay is None:
|
|
21
|
+
raise ValueError("If timeout is not None, then delay must not be None.")
|
|
22
|
+
self.is_locked = False
|
|
23
|
+
self.lockfile = os.path.join(os.getcwd(), "%s.lock" % file_name)
|
|
24
|
+
self.file_name = file_name
|
|
25
|
+
self.timeout = timeout
|
|
26
|
+
self.delay = delay
|
|
27
|
+
|
|
28
|
+
def acquire(self):
|
|
29
|
+
"""Acquire the lock, if possible. If the lock is in use, it check again
|
|
30
|
+
every `wait` seconds. It does this until it either gets the lock or
|
|
31
|
+
exceeds `timeout` number of seconds, in which case it throws
|
|
32
|
+
an exception.
|
|
33
|
+
"""
|
|
34
|
+
start_time = time.time()
|
|
35
|
+
while True:
|
|
36
|
+
try:
|
|
37
|
+
self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
|
38
|
+
self.is_locked = True # moved to ensure tag only when locked
|
|
39
|
+
break
|
|
40
|
+
except OSError as e:
|
|
41
|
+
if e.errno != errno.EEXIST:
|
|
42
|
+
raise
|
|
43
|
+
if self.timeout is None:
|
|
44
|
+
raise FileLockException(
|
|
45
|
+
"Could not acquire lock on {}".format(self.file_name)
|
|
46
|
+
)
|
|
47
|
+
if (time.time() - start_time) >= self.timeout:
|
|
48
|
+
raise FileLockException("Timeout occured.")
|
|
49
|
+
time.sleep(self.delay)
|
|
50
|
+
|
|
51
|
+
# self.is_locked = True
|
|
52
|
+
|
|
53
|
+
def release(self):
|
|
54
|
+
"""Get rid of the lock by deleting the lockfile.
|
|
55
|
+
When working in a `with` statement, this gets automatically
|
|
56
|
+
called at the end.
|
|
57
|
+
"""
|
|
58
|
+
if self.is_locked:
|
|
59
|
+
os.close(self.fd)
|
|
60
|
+
os.unlink(self.lockfile)
|
|
61
|
+
self.is_locked = False
|
|
62
|
+
|
|
63
|
+
def __enter__(self):
|
|
64
|
+
"""Activated when used in the with statement.
|
|
65
|
+
Should automatically acquire a lock to be used in the with block.
|
|
66
|
+
"""
|
|
67
|
+
if not self.is_locked:
|
|
68
|
+
self.acquire()
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def __exit__(self, type, value, traceback):
|
|
72
|
+
"""Activated at the end of the with statement.
|
|
73
|
+
It automatically releases the lock if it isn't locked.
|
|
74
|
+
"""
|
|
75
|
+
if self.is_locked:
|
|
76
|
+
self.release()
|
|
77
|
+
|
|
78
|
+
def __del__(self):
|
|
79
|
+
"""Make sure that the FileLock instance doesn't leave a lockfile
|
|
80
|
+
lying around.
|
|
81
|
+
"""
|
|
82
|
+
self.release()
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from .tools import yieldlist, X, wait_git_lock
|
|
4
|
+
from .consts import gitcmd as git
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GitCommands(object):
|
|
8
|
+
def __init__(self, path=None):
|
|
9
|
+
self.path = Path(path or os.getcwd())
|
|
10
|
+
self.path_absolute = self.path.absolute()
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def configdir(self):
|
|
14
|
+
from .repo import Repo
|
|
15
|
+
|
|
16
|
+
stop_at = Repo(self.path_absolute).root_repo
|
|
17
|
+
here = self.path_absolute
|
|
18
|
+
while True:
|
|
19
|
+
default = here / ".git"
|
|
20
|
+
if default.exists() and default.is_dir():
|
|
21
|
+
return default
|
|
22
|
+
if default.is_file():
|
|
23
|
+
path = default.read_text().strip().split("gitdir:")[1].strip()
|
|
24
|
+
return (here / path).resolve()
|
|
25
|
+
|
|
26
|
+
if here == stop_at:
|
|
27
|
+
break
|
|
28
|
+
here = here.parent
|
|
29
|
+
raise Exception("Config dir not found")
|
|
30
|
+
|
|
31
|
+
def X(self, *params, allow_error=False, env=None, output=None):
|
|
32
|
+
if output is None:
|
|
33
|
+
output = False
|
|
34
|
+
with wait_git_lock(self.path_absolute):
|
|
35
|
+
kwparams = {
|
|
36
|
+
"output": output,
|
|
37
|
+
"allow_error": allow_error,
|
|
38
|
+
"env": env,
|
|
39
|
+
}
|
|
40
|
+
if self.path.exists():
|
|
41
|
+
# case not existing at recreating cache dir e.g.
|
|
42
|
+
kwparams["cwd"] = self.path
|
|
43
|
+
return X(*params, **kwparams)
|
|
44
|
+
|
|
45
|
+
def out(self, *params, allow_error=False, env=None):
|
|
46
|
+
return X(*params, output=True, cwd=self.path, allow_error=allow_error, env=env)
|
|
47
|
+
|
|
48
|
+
def _parse_git_status(self):
|
|
49
|
+
for line in X(
|
|
50
|
+
*(
|
|
51
|
+
git
|
|
52
|
+
+ [
|
|
53
|
+
"status",
|
|
54
|
+
"--porcelain",
|
|
55
|
+
"--untracked-files=all",
|
|
56
|
+
]
|
|
57
|
+
),
|
|
58
|
+
cwd=self.path_absolute,
|
|
59
|
+
output=True,
|
|
60
|
+
).splitlines():
|
|
61
|
+
# splits: A asdas
|
|
62
|
+
# M asdasd
|
|
63
|
+
# M asdsad
|
|
64
|
+
# ?? asasdasd
|
|
65
|
+
modifier = line[:2]
|
|
66
|
+
path = line.strip().split(" ", 1)[1].strip()
|
|
67
|
+
if path.startswith(".."):
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
yield modifier, Path(path)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
@yieldlist
|
|
74
|
+
def staged_files(self):
|
|
75
|
+
for modifier, path in self._parse_git_status():
|
|
76
|
+
if modifier[0] in ["A", "M", "D"]:
|
|
77
|
+
yield path
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
@yieldlist
|
|
81
|
+
def dirty_existing_files(self):
|
|
82
|
+
for modifier, path in self._parse_git_status():
|
|
83
|
+
if modifier[0] == "M" or modifier[1] == "M" or modifier[1] == "D":
|
|
84
|
+
yield path
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
@yieldlist
|
|
88
|
+
def all_dirty_files(self):
|
|
89
|
+
return self.untracked_files + self.dirty_existing_files
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
@yieldlist
|
|
93
|
+
def all_dirty_files_absolute(self):
|
|
94
|
+
res = self.untracked_files + self.dirty_existing_files
|
|
95
|
+
res = list(map(lambda x: self.path_absolute / x, res))
|
|
96
|
+
return res
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
@yieldlist
|
|
100
|
+
def untracked_files(self):
|
|
101
|
+
for modifier, path in self._parse_git_status():
|
|
102
|
+
if modifier == "??" or modifier[0] == "A":
|
|
103
|
+
yield path
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
@yieldlist
|
|
107
|
+
def untracked_files_absolute(self):
|
|
108
|
+
for file in self.untracked_files:
|
|
109
|
+
yield self.path_absolute / file
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def dirty(self):
|
|
113
|
+
return bool(list(self._parse_git_status()))
|
|
114
|
+
|
|
115
|
+
def is_submodule(self, path):
|
|
116
|
+
path = self._combine(path)
|
|
117
|
+
for line in X(
|
|
118
|
+
*(git + ["submodule", "status"]), output=True, cwd=self.path_absolute
|
|
119
|
+
).splitlines():
|
|
120
|
+
line = line.strip()
|
|
121
|
+
_, _path, _ = line.split(" ", 2)
|
|
122
|
+
if _path == str(path.relative_to(self.path_absolute)):
|
|
123
|
+
return path
|
|
124
|
+
|
|
125
|
+
def _combine(self, path):
|
|
126
|
+
"""
|
|
127
|
+
Makes a new path
|
|
128
|
+
"""
|
|
129
|
+
path = self.path / path
|
|
130
|
+
path.relative_to(self.path)
|
|
131
|
+
return path
|
|
132
|
+
|
|
133
|
+
def output_status(self):
|
|
134
|
+
self.X(*(git + ["status"]))
|
|
135
|
+
|
|
136
|
+
def get_all_branches(self):
|
|
137
|
+
res = list(
|
|
138
|
+
map(
|
|
139
|
+
lambda x: x.strip(),
|
|
140
|
+
self.out(
|
|
141
|
+
*(git + ["for-each-ref", "--format=%(refname:short)", "refs/heads"])
|
|
142
|
+
).splitlines(),
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
return res
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def dirty(self):
|
|
149
|
+
files = []
|
|
150
|
+
for modifier, path in self._parse_git_status():
|
|
151
|
+
if str(path) == "gimera.yml":
|
|
152
|
+
continue
|
|
153
|
+
files.append(path)
|
|
154
|
+
return bool(files)
|
|
155
|
+
|
|
156
|
+
def simple_commit_all(self, msg="."):
|
|
157
|
+
self.X(*(git + ["add", "."]))
|
|
158
|
+
self.X(*(git + ["commit", "--allow-empty", "-am", msg]))
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def hex(self):
|
|
162
|
+
return self.out(*(git + ["log", "-n", "1", "--pretty=%H"]))
|
|
163
|
+
|
|
164
|
+
def checkout(self, ref, force=False):
|
|
165
|
+
self.X(*(git + ["checkout", "-f" if force else None, ref]))
|