git-project 0.0.25__tar.gz → 0.0.26__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.
- git_project-0.0.26/.git +1 -0
- {git_project-0.0.25 → git_project-0.0.26}/PKG-INFO +2 -2
- {git_project-0.0.25 → git_project-0.0.26}/_version.py +2 -2
- git_project-0.0.26/build/lib/git_project/__init__.py +33 -0
- git_project-0.0.26/build/lib/git_project/commandline.py +164 -0
- git_project-0.0.26/build/lib/git_project/configobj.py +300 -0
- git_project-0.0.26/build/lib/git_project/exception.py +30 -0
- git_project-0.0.26/build/lib/git_project/git.py +873 -0
- git_project-0.0.26/build/lib/git_project/gitproject.py +61 -0
- git_project-0.0.26/build/lib/git_project/main.py +63 -0
- git_project-0.0.26/build/lib/git_project/parsermanager.py +360 -0
- git_project-0.0.26/build/lib/git_project/plugin.py +118 -0
- git_project-0.0.26/build/lib/git_project/pluginmanager.py +54 -0
- git_project-0.0.26/build/lib/git_project/project.py +197 -0
- git_project-0.0.26/build/lib/git_project/runnable.py +71 -0
- git_project-0.0.26/build/lib/git_project/scopedobj.py +207 -0
- git_project-0.0.26/build/lib/git_project/shell.py +119 -0
- git_project-0.0.26/build/lib/git_project/substitutable.py +139 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/git.py +8 -4
- git_project-0.0.26/test.out +17 -0
- git_project-0.0.25/.git +0 -1
- git_project-0.0.25/test.out +0 -131
- {git_project-0.0.25 → git_project-0.0.26}/.gitignore +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/CONTRIBUTORS +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/COPYING +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/MANIFEST.in +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/docs/Makefile +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/docs/authors.rst +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/docs/changelog.rst +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/docs/conf.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/docs/index.rst +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/docs/intro.rst +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/docs/license.rst +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/docs/make.bat +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/pyproject.toml +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/requirements.txt +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/__init__.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/commandline.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/configobj.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/exception.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/gitproject.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/main.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/parsermanager.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/plugin.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/pluginmanager.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/project.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/runnable.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/scopedobj.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/shell.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/substitutable.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/test_support/__init__.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/src/git_project/test_support/common.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/conftest.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/test_confobj.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/test_git.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/test_gitproject.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/test_main.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/test_project.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/test_runnable.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/test_scopedobj.py +0 -0
- {git_project-0.0.25 → git_project-0.0.26}/tests/test_substitutable.py +0 -0
git_project-0.0.26/.git
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitdir: /Users/dag/projects/git-project/.git/worktrees/master/
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2020 David A. Greene
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
# the terms of the GNU General Public License as published by the Free Software
|
|
7
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
|
8
|
+
# version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
12
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License along with
|
|
16
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from .commandline import parse_arguments, add_top_level_command
|
|
20
|
+
from .commandline import get_or_add_top_level_command
|
|
21
|
+
from .configobj import ConfigObject
|
|
22
|
+
from .exception import GitProjectException
|
|
23
|
+
from .git import Git
|
|
24
|
+
from .gitproject import GitProject
|
|
25
|
+
from .main import main_impl
|
|
26
|
+
from .parsermanager import ParserManager
|
|
27
|
+
from .plugin import Plugin
|
|
28
|
+
from .pluginmanager import PluginManager
|
|
29
|
+
from .project import Project
|
|
30
|
+
from .runnable import RunnableConfigObject
|
|
31
|
+
from .scopedobj import ScopedConfigObject
|
|
32
|
+
from .substitutable import SubstitutableConfigObject
|
|
33
|
+
from .shell import run_command_with_shell, iter_command, capture_command
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2020 David A. Greene
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
# the terms of the GNU General Public License as published by the Free Software
|
|
7
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
|
8
|
+
# version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
12
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License along with
|
|
16
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""git-project: Manage project work.
|
|
19
|
+
|
|
20
|
+
Summary:
|
|
21
|
+
|
|
22
|
+
git project <command> [<options>]
|
|
23
|
+
|
|
24
|
+
Commands are registered by plugins and show with 'git project --help.' Each
|
|
25
|
+
command also has a --help option to explore workings of th command.
|
|
26
|
+
|
|
27
|
+
Users may link to git-project with a name appropriate to the project (for
|
|
28
|
+
example, git-fizzbin to invoke as 'git fizzbin <command> [<options>]).
|
|
29
|
+
git-project will then place all configuration in a section named after the link
|
|
30
|
+
('fizzbin' in this case). This way one may have multiple projects active in a
|
|
31
|
+
single repository by invoking git-project via various links.
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
import argparse
|
|
36
|
+
import getpass
|
|
37
|
+
import io
|
|
38
|
+
import os
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
import pygit2
|
|
41
|
+
import re
|
|
42
|
+
import shlex
|
|
43
|
+
import subprocess
|
|
44
|
+
import sys
|
|
45
|
+
import urllib.parse
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
from importlib import metadata
|
|
49
|
+
except ImportError:
|
|
50
|
+
# Running on pre-3.8 Python; use importlib-metadata package
|
|
51
|
+
import importlib_metadata as metadata
|
|
52
|
+
|
|
53
|
+
from .parsermanager import ParserManager
|
|
54
|
+
|
|
55
|
+
def get_version_string(package):
|
|
56
|
+
version = metadata.version(package)
|
|
57
|
+
|
|
58
|
+
return f'{package} version {version}'
|
|
59
|
+
|
|
60
|
+
def add_version_argument(parser, package):
|
|
61
|
+
parser.add_argument('--version',
|
|
62
|
+
action='version',
|
|
63
|
+
version=get_version_string(package),
|
|
64
|
+
help='Print version')
|
|
65
|
+
|
|
66
|
+
def parse_arguments(git, gitproject, project, plugin_manager, args):
|
|
67
|
+
"""Register plugin comand-line arguments. Parse command-link arguments and
|
|
68
|
+
invoke command functions.
|
|
69
|
+
|
|
70
|
+
git: Git object to examine the repository.
|
|
71
|
+
|
|
72
|
+
gitproject: GitProject object to provide cross-project configuration.
|
|
73
|
+
|
|
74
|
+
project: Project object to provide project-specific configuration.
|
|
75
|
+
|
|
76
|
+
plugin_manager: A manager holding available plugins.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
parser_manager = ParserManager(gitproject, project)
|
|
80
|
+
|
|
81
|
+
parser = parser_manager.find_parser('__main__')
|
|
82
|
+
|
|
83
|
+
# --version
|
|
84
|
+
add_version_argument(parser, 'git-project')
|
|
85
|
+
|
|
86
|
+
command_subparser = parser_manager.add_subparser(parser,
|
|
87
|
+
'command',
|
|
88
|
+
dest='command',
|
|
89
|
+
help='commands')
|
|
90
|
+
command_subparser.required = True
|
|
91
|
+
|
|
92
|
+
for plugin in plugin_manager.iterplugins():
|
|
93
|
+
plugin.add_arguments(git,
|
|
94
|
+
gitproject,
|
|
95
|
+
project,
|
|
96
|
+
parser_manager,
|
|
97
|
+
plugin_manager)
|
|
98
|
+
|
|
99
|
+
# Once all plugins have added arguments, give them a chance to modify
|
|
100
|
+
# arguments other plugsin may have added.
|
|
101
|
+
for plugin in plugin_manager.iterplugins():
|
|
102
|
+
plugin.modify_arguments(git,
|
|
103
|
+
gitproject,
|
|
104
|
+
project,
|
|
105
|
+
parser_manager,
|
|
106
|
+
plugin_manager)
|
|
107
|
+
|
|
108
|
+
clargs = parser_manager.parse_args(args)
|
|
109
|
+
|
|
110
|
+
if not hasattr(clargs, 'func'):
|
|
111
|
+
parser_manager.error()
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
return clargs
|
|
115
|
+
|
|
116
|
+
def add_top_level_command(parser_manager,
|
|
117
|
+
name,
|
|
118
|
+
key,
|
|
119
|
+
**kwargs):
|
|
120
|
+
"""Create a top-level command. Throw an exception if the command already
|
|
121
|
+
exists.
|
|
122
|
+
|
|
123
|
+
parser_manager - The active ParserManager.
|
|
124
|
+
|
|
125
|
+
name - The name of the command to be invoked.
|
|
126
|
+
|
|
127
|
+
key - A unique identifier for the created parser.
|
|
128
|
+
|
|
129
|
+
kwargs - Passthrough to argparse parser.
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
command_subparser = parser_manager.find_subparser('command')
|
|
134
|
+
assert command_subparser
|
|
135
|
+
parser = parser_manager.add_parser(command_subparser,
|
|
136
|
+
name,
|
|
137
|
+
key,
|
|
138
|
+
**kwargs)
|
|
139
|
+
|
|
140
|
+
return parser
|
|
141
|
+
|
|
142
|
+
def get_or_add_top_level_command(parser_manager,
|
|
143
|
+
name,
|
|
144
|
+
key,
|
|
145
|
+
**kwargs):
|
|
146
|
+
"""Retrieve a top-level command parser or create one if none exists.
|
|
147
|
+
|
|
148
|
+
parser_manager - The active ParserManager.
|
|
149
|
+
|
|
150
|
+
name - The name of the command to be invoked.
|
|
151
|
+
|
|
152
|
+
key - A unique identifier for the created parser.
|
|
153
|
+
|
|
154
|
+
kwargs - Passthrough to argparse parser.
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
parser = parser_manager.find_parser(key)
|
|
159
|
+
if not parser:
|
|
160
|
+
parser = add_top_level_command(parser_manager,
|
|
161
|
+
name,
|
|
162
|
+
key,
|
|
163
|
+
**kwargs)
|
|
164
|
+
return parser
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2020 David A. Greene
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
# the terms of the GNU General Public License as published by the Free Software
|
|
7
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
|
8
|
+
# version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
12
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License along with
|
|
16
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
import collections
|
|
20
|
+
import inspect
|
|
21
|
+
|
|
22
|
+
class ConfigObject(object):
|
|
23
|
+
"""Base class for objects that use git-config as a backing store. Specified
|
|
24
|
+
property values are saved to the git config file and read from the config
|
|
25
|
+
file upon instantiation.
|
|
26
|
+
"""
|
|
27
|
+
def __init__(self,
|
|
28
|
+
git,
|
|
29
|
+
project_section,
|
|
30
|
+
subsection,
|
|
31
|
+
ident,
|
|
32
|
+
**kwargs):
|
|
33
|
+
"""ConfigObject construction. This should be treated as a private method and
|
|
34
|
+
all construction should occur through the get method.
|
|
35
|
+
|
|
36
|
+
git: An object to query the repository and make config changes.
|
|
37
|
+
|
|
38
|
+
project_section: git config section of the active project.
|
|
39
|
+
|
|
40
|
+
subsection: An arbitrarily-long subsection appended to project_section
|
|
41
|
+
|
|
42
|
+
ident: The name of this specific ConfigObject.
|
|
43
|
+
|
|
44
|
+
**kwargs: Keyword arguments of property values to set upon construction.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# We don't want the section to introduce subsections, so translate . to
|
|
49
|
+
# -. Since underscore is not allowed in a config key, translate them to -
|
|
50
|
+
# as well.
|
|
51
|
+
|
|
52
|
+
tr_table = project_section.maketrans('._', '--')
|
|
53
|
+
project_section = project_section.translate(tr_table)
|
|
54
|
+
|
|
55
|
+
# Also with pygit2 the key can't start with - so prepend a legal string.
|
|
56
|
+
|
|
57
|
+
if project_section.startswith('-'):
|
|
58
|
+
project_section = 'ZZ' + project_section
|
|
59
|
+
|
|
60
|
+
self._git = git
|
|
61
|
+
self._project_section = project_section
|
|
62
|
+
self._subsection = subsection
|
|
63
|
+
self._ident = ident
|
|
64
|
+
self._section = ConfigObject._get_full_section(self._project_section,
|
|
65
|
+
self._subsection,
|
|
66
|
+
self._ident)
|
|
67
|
+
self._init_from_dict(kwargs)
|
|
68
|
+
|
|
69
|
+
def __setattr__(self, name, value):
|
|
70
|
+
if name.startswith('_'):
|
|
71
|
+
super().__setattr__(name, value)
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# The git config can only handle string values.
|
|
75
|
+
assert(isinstance(value, str))
|
|
76
|
+
self._set_item(name, value)
|
|
77
|
+
|
|
78
|
+
def __delattr__(self, name):
|
|
79
|
+
if name.startswith('_'):
|
|
80
|
+
super().__delattr__(name)
|
|
81
|
+
return
|
|
82
|
+
self.rm_items(name)
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def get(cls,
|
|
86
|
+
git,
|
|
87
|
+
project_section,
|
|
88
|
+
subsection,
|
|
89
|
+
ident,
|
|
90
|
+
**kwargs):
|
|
91
|
+
"""Factory to construct ConfigObjects.
|
|
92
|
+
|
|
93
|
+
cls: The derived class being constructed.
|
|
94
|
+
|
|
95
|
+
git: An object to query the repository and make config changes.
|
|
96
|
+
|
|
97
|
+
project_section: git config section of the active project.
|
|
98
|
+
|
|
99
|
+
subsection: An arbitrarily-long subsection appended to project_section
|
|
100
|
+
|
|
101
|
+
ident: The name of this specific ConfigObject.
|
|
102
|
+
|
|
103
|
+
**kwargs: Keyword arguments of property values to set upon construction.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
inits = dict()
|
|
107
|
+
|
|
108
|
+
gitsection = ConfigObject._get_full_section(project_section,
|
|
109
|
+
subsection,
|
|
110
|
+
ident)
|
|
111
|
+
if git.has_repo():
|
|
112
|
+
config_section = git.config.get_section(gitsection)
|
|
113
|
+
if config_section:
|
|
114
|
+
for key, item in config_section:
|
|
115
|
+
name = key.rsplit('.', 1)[-1]
|
|
116
|
+
if item.is_multival():
|
|
117
|
+
inits[name] = [value for value in item.itervalues()]
|
|
118
|
+
else:
|
|
119
|
+
inits[name] = item.get_value()
|
|
120
|
+
|
|
121
|
+
for key, value in kwargs.items():
|
|
122
|
+
inits[key] = value
|
|
123
|
+
|
|
124
|
+
result = cls(git,
|
|
125
|
+
project_section,
|
|
126
|
+
subsection,
|
|
127
|
+
ident,
|
|
128
|
+
**inits)
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def exists(cls,
|
|
134
|
+
git,
|
|
135
|
+
project_section,
|
|
136
|
+
subsection,
|
|
137
|
+
ident):
|
|
138
|
+
"""Return whether an existing git config exists for the ConfigObject.
|
|
139
|
+
|
|
140
|
+
cls: The derived class being checked.
|
|
141
|
+
|
|
142
|
+
git: An object to query the repository and make config changes.
|
|
143
|
+
|
|
144
|
+
project_section: git config section of the active project.
|
|
145
|
+
|
|
146
|
+
subsection: An arbitrarily-long subsection appended to project_section
|
|
147
|
+
|
|
148
|
+
ident: The name of this specific ConfigObject.
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
if not git.has_repo():
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
gitsection = ConfigObject._get_full_section(project_section,
|
|
155
|
+
subsection,
|
|
156
|
+
ident)
|
|
157
|
+
|
|
158
|
+
section = git.config.get_section(gitsection)
|
|
159
|
+
return True if section else False
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def _get_full_section(section, subsection, ident):
|
|
163
|
+
"""Construct a full git section name by appending subsection and ident (if not
|
|
164
|
+
None) to section.
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
result = section
|
|
168
|
+
if subsection:
|
|
169
|
+
result += '.' + subsection
|
|
170
|
+
if ident:
|
|
171
|
+
result += '.' + ident
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def get_managing_command(cls):
|
|
176
|
+
"""Return a command-line command key that manages the ConfigObject. Derived
|
|
177
|
+
classes should override this method if they are managed by a
|
|
178
|
+
command-line command.
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def _add_property(cls, name):
|
|
185
|
+
"""Add a property name that reads the git config when accessed and writes the
|
|
186
|
+
git config when written."""
|
|
187
|
+
def fun_get(self):
|
|
188
|
+
result = {item for item in self.iter_multival(name)}
|
|
189
|
+
if not result:
|
|
190
|
+
return None
|
|
191
|
+
if len(result) == 1:
|
|
192
|
+
return result.pop()
|
|
193
|
+
return frozenset(result)
|
|
194
|
+
def fun_set(self, value):
|
|
195
|
+
self._set_item(name, value)
|
|
196
|
+
prop = property(fun_get, fun_set)
|
|
197
|
+
setattr(cls, name, prop)
|
|
198
|
+
|
|
199
|
+
def has_item(self, name):
|
|
200
|
+
"""Return whether self.<name> exists. We don't want to use hasattr because
|
|
201
|
+
<name> is actually set on the class, not the instance.
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
if not isinstance(getattr(type(self), name, None), property):
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
# It's not quite enough to know we have a property. We might have a
|
|
208
|
+
# property from a previous instance but the git config entry in this
|
|
209
|
+
# instance might not be set. This should not happen during normal
|
|
210
|
+
# operation as the command is quickly executed and the program
|
|
211
|
+
# terminates. However, testing can have long-lived classes with many
|
|
212
|
+
# properties added to them.
|
|
213
|
+
if not self._git.has_repo():
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
return self._git.config.has_item(self.get_section(), name)
|
|
217
|
+
|
|
218
|
+
def get_ident(self):
|
|
219
|
+
"""Return the identifier of this object."""
|
|
220
|
+
return self._ident
|
|
221
|
+
|
|
222
|
+
def get_section(self):
|
|
223
|
+
"""Return the section of this object."""
|
|
224
|
+
return self._section
|
|
225
|
+
|
|
226
|
+
def get_subsection(self):
|
|
227
|
+
"""Return the subsection of this object."""
|
|
228
|
+
return self._subsection
|
|
229
|
+
|
|
230
|
+
def _set_item(self, name, value):
|
|
231
|
+
"""Set property name to value and write it to the git config."""
|
|
232
|
+
if not hasattr(self.__class__, name):
|
|
233
|
+
self._add_property(name)
|
|
234
|
+
self._git.config.set_item(self._section, name, value)
|
|
235
|
+
|
|
236
|
+
def rm_item(self, name, pattern):
|
|
237
|
+
"""Remove property name values matching pattern from the git config."""
|
|
238
|
+
self._git.config.rm_item(self._section, name, pattern)
|
|
239
|
+
|
|
240
|
+
def rm_items(self, name):
|
|
241
|
+
"""Remove property name completely from the git config."""
|
|
242
|
+
self._git.config.rm_items(self._section, name)
|
|
243
|
+
delattr(self.__class__, name)
|
|
244
|
+
|
|
245
|
+
def _get_item(self, name):
|
|
246
|
+
"""Get the value for property 'name.'"""
|
|
247
|
+
return self._git.config.get_item(self._section, name)
|
|
248
|
+
|
|
249
|
+
def add_item(self, name, value):
|
|
250
|
+
"""Add an item with key name set to value. Rather than overwriting an existing
|
|
251
|
+
entry, add a new one to the git config, creeating a multi-value key.
|
|
252
|
+
|
|
253
|
+
"""
|
|
254
|
+
if not hasattr(self, name):
|
|
255
|
+
self._add_property(name)
|
|
256
|
+
self._git.config.add_item(self._section, name, value)
|
|
257
|
+
|
|
258
|
+
def _init_from_dict(self, values):
|
|
259
|
+
"""Set properties of the object using the mapping in dictionary valuees. Mapped
|
|
260
|
+
valuees that are sequences create multi-value keys.
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
def issequence(obj):
|
|
264
|
+
if isinstance(obj, str):
|
|
265
|
+
return False
|
|
266
|
+
return isinstance(obj, collections.abc.Sequence)
|
|
267
|
+
|
|
268
|
+
for key, value in values.items():
|
|
269
|
+
if issequence(value):
|
|
270
|
+
for v in value:
|
|
271
|
+
self.add_item(key, v)
|
|
272
|
+
else:
|
|
273
|
+
self._set_item(key, value)
|
|
274
|
+
|
|
275
|
+
def iter_multival(self, name):
|
|
276
|
+
"""Iterate over the multiple values of a multi-value git config key."""
|
|
277
|
+
for value in self._git.config.iter_multival(self._section, name):
|
|
278
|
+
yield value
|
|
279
|
+
|
|
280
|
+
def __repr__(self):
|
|
281
|
+
"""Serialize the object as a string."""
|
|
282
|
+
return str({key:value for (key, value) in
|
|
283
|
+
self.__dict__.items() if not key.startswith('_')})
|
|
284
|
+
|
|
285
|
+
def __str__(self):
|
|
286
|
+
"""Serialize the object as a string."""
|
|
287
|
+
return str(self.__repr__())
|
|
288
|
+
|
|
289
|
+
def rm(self):
|
|
290
|
+
"""Remove the entire section of this object from the git config."""
|
|
291
|
+
for key, value in self.iteritems():
|
|
292
|
+
self.rm_items(key)
|
|
293
|
+
# Removing all section entries removes the section.
|
|
294
|
+
#self._git.config.rm_section(self._section)
|
|
295
|
+
|
|
296
|
+
def iteritems(self):
|
|
297
|
+
"""Iterate over all key, value items."""
|
|
298
|
+
for key, value in inspect.getmembers(self):
|
|
299
|
+
if self.has_item(key):
|
|
300
|
+
yield key, value
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2020 David A. Greene
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
# the terms of the GNU General Public License as published by the Free Software
|
|
7
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
|
8
|
+
# version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
12
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License along with
|
|
16
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
class GitProjectException(Exception):
|
|
23
|
+
def __init__(self, message):
|
|
24
|
+
super().__init__(message)
|
|
25
|
+
self._message = message
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def message(self):
|
|
29
|
+
command = Path(sys.argv[0]).name
|
|
30
|
+
return f'{command}: {self._message}'
|