git-project-core-plugins 0.0.23__tar.gz → 0.0.25__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_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/PKG-INFO +1 -1
- git_project_core_plugins-0.0.25/_version.py +24 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/__init__.py +27 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/artifact.py +338 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/branch.py +222 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/clone.py +98 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/common.py +22 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/config.py +169 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/help.py +298 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/init.py +74 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/run.py +399 -0
- git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/worktree.py +643 -0
- git_project_core_plugins-0.0.25/docs/README-template.rst +104 -0
- git_project_core_plugins-0.0.25/gen-readme.py +55 -0
- git_project_core_plugins-0.0.25/gen-readme.py~ +2 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/PKG-INFO +1 -4
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/SOURCES.txt +1 -9
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/run.py +1 -1
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/worktree.py +5 -1
- git_project_core_plugins-0.0.25/src/git_project_core_plugins/worktree.py~ +647 -0
- git_project_core_plugins-0.0.25/test.out +8 -0
- git_project_core_plugins-0.0.23/.git +0 -1
- git_project_core_plugins-0.0.23/_version.py +0 -21
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/COPYING +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/MANIFEST.in +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/Makefile +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/authors.rst +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/changelog.rst +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/conf.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/index.rst +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/intro.rst +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/license.rst +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/make.bat +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/dependency_links.txt +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/entry_points.txt +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/not-zip-safe +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/requires.txt +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/top_level.txt +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/pyproject.toml +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/requirements.txt +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/__init__.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/artifact.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/branch.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/clone.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/common.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/config.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/help.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/init.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/common.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/conftest.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_artifact.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_branch.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_clone.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_config.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_help.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_init.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_run.py +0 -0
- {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_worktree.py +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.0.25'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 25)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,27 @@
|
|
|
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 .artifact import Artifact, ArtifactPlugin
|
|
20
|
+
from .branch import BranchPlugin
|
|
21
|
+
from .run import RunPlugin
|
|
22
|
+
from .clone import ClonePlugin
|
|
23
|
+
from .common import add_plugin_version_argument
|
|
24
|
+
from .config import ConfigPlugin
|
|
25
|
+
from .help import Help, HelpPlugin
|
|
26
|
+
from .init import InitPlugin
|
|
27
|
+
from .worktree import Worktree, WorktreePlugin
|
|
@@ -0,0 +1,338 @@
|
|
|
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
|
+
"""A plugin to add a 'config' command to git-project. The config command sets
|
|
20
|
+
project-wide git configuration values and prints their values to stdout.
|
|
21
|
+
|
|
22
|
+
Summary:
|
|
23
|
+
|
|
24
|
+
git-project config <key> [--unset] [<value>]
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from git_project import ConfigObject, SubstitutableConfigObject, Plugin
|
|
29
|
+
from git_project import run_command_with_shell, add_top_level_command
|
|
30
|
+
|
|
31
|
+
from git_project_core_plugins.common import add_plugin_version_argument
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
import re
|
|
35
|
+
|
|
36
|
+
class Artifact(SubstitutableConfigObject):
|
|
37
|
+
@classmethod
|
|
38
|
+
def _split_ident(cls, ident):
|
|
39
|
+
parts = ident.rsplit('.', 1)
|
|
40
|
+
|
|
41
|
+
ident = parts[-1]
|
|
42
|
+
|
|
43
|
+
subsection = cls.subsection()
|
|
44
|
+
if len(parts) > 1:
|
|
45
|
+
subsection += '.' + '.'.join(parts[:-1])
|
|
46
|
+
|
|
47
|
+
return subsection, ident
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def get(cls, git, project_section, ident, **kwargs):
|
|
51
|
+
"""Factory to construct an Artifact object.
|
|
52
|
+
|
|
53
|
+
git: An object to query the repository and make config changes.
|
|
54
|
+
|
|
55
|
+
project_section: the active project section.
|
|
56
|
+
|
|
57
|
+
ident: The subsection under <project>.artifact where this Artifact will
|
|
58
|
+
live.
|
|
59
|
+
|
|
60
|
+
**kwargs: Keyword arguments of property values to set upon
|
|
61
|
+
construction.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
subsection, ident = cls._split_ident(ident)
|
|
65
|
+
|
|
66
|
+
return super().get(git,
|
|
67
|
+
project_section,
|
|
68
|
+
subsection,
|
|
69
|
+
ident,
|
|
70
|
+
**kwargs)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def exists(cls,
|
|
74
|
+
git,
|
|
75
|
+
project_section,
|
|
76
|
+
ident):
|
|
77
|
+
"""Return whether an existing git config exists for the Artifact.
|
|
78
|
+
|
|
79
|
+
cls: The derived class being checked.
|
|
80
|
+
|
|
81
|
+
git: An object to query the repository and make config changes.
|
|
82
|
+
|
|
83
|
+
project_section: git config section of the active project.
|
|
84
|
+
|
|
85
|
+
ident: The subsection under <project>.artifact where this Artifact lives.
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
subsection, ident = cls._split_ident(ident)
|
|
89
|
+
|
|
90
|
+
return super().exists(git, project_section, subsection, ident)
|
|
91
|
+
|
|
92
|
+
def __init__(self,
|
|
93
|
+
git,
|
|
94
|
+
project_section,
|
|
95
|
+
subsection,
|
|
96
|
+
ident,
|
|
97
|
+
**kwargs):
|
|
98
|
+
"""Artifact construction.
|
|
99
|
+
|
|
100
|
+
cls: The derived class being constructed.
|
|
101
|
+
|
|
102
|
+
git: An object to query the repository and make config changes.
|
|
103
|
+
|
|
104
|
+
project_section: git config section of the active project.
|
|
105
|
+
|
|
106
|
+
subsection: An arbitrarily-long subsection appended to project_section
|
|
107
|
+
|
|
108
|
+
ident: The name of this specific Artifact.
|
|
109
|
+
|
|
110
|
+
**kwargs: Keyword arguments of property values to set upon construction.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
super().__init__(git,
|
|
114
|
+
project_section,
|
|
115
|
+
subsection,
|
|
116
|
+
ident,
|
|
117
|
+
**kwargs)
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def subsection():
|
|
121
|
+
"""ConfigObject protocol subsection."""
|
|
122
|
+
return 'artifact'
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def get_managing_command(cls):
|
|
126
|
+
return 'artifact'
|
|
127
|
+
|
|
128
|
+
def rm(self, git, project, clargs, string):
|
|
129
|
+
"""Remove the paths associated with this Artifact.
|
|
130
|
+
|
|
131
|
+
git: An object to query the repository and make config changes.
|
|
132
|
+
|
|
133
|
+
project: The currently active Project.
|
|
134
|
+
|
|
135
|
+
clargs: Command-line arguments
|
|
136
|
+
|
|
137
|
+
string: The string on which to perform substitution.
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
for path in self.iter_multival('path'):
|
|
141
|
+
path = self.substitute_value(git, poject, clargs, path)
|
|
142
|
+
|
|
143
|
+
def command_artifact_add(git, gitproject, project, clargs):
|
|
144
|
+
"""Implement git-project artifact add."""
|
|
145
|
+
ident = clargs.subsection
|
|
146
|
+
path = clargs.path
|
|
147
|
+
|
|
148
|
+
artifact = Artifact.get(git, project.get_section(), ident)
|
|
149
|
+
artifact.add_item('path', path)
|
|
150
|
+
|
|
151
|
+
def command_artifact_rm(git, gitproject, project, clargs):
|
|
152
|
+
"""Implement git-project artifact rm."""
|
|
153
|
+
ident = clargs.subsection
|
|
154
|
+
|
|
155
|
+
artifact = Artifact.get(git, project.get_section(), ident)
|
|
156
|
+
|
|
157
|
+
if hasattr(clargs, 'path') and clargs.path:
|
|
158
|
+
path = clargs.path
|
|
159
|
+
artifact.rm_item('path', path)
|
|
160
|
+
else:
|
|
161
|
+
artifact.rm_items('path')
|
|
162
|
+
|
|
163
|
+
class ArtifactPlugin(Plugin):
|
|
164
|
+
"""
|
|
165
|
+
The artifact command adds or removes associations between git config objects
|
|
166
|
+
and file-system objects.
|
|
167
|
+
|
|
168
|
+
Summary:
|
|
169
|
+
|
|
170
|
+
git <project> artifact add <subsection> <path>
|
|
171
|
+
git <project> artifact rm <subsection> [<path>]
|
|
172
|
+
|
|
173
|
+
<subsection> is a git config section which will appear under the
|
|
174
|
+
<project>.artifact section. Artifacts look up objects associated with
|
|
175
|
+
<subsection> and perform substitutions on paths to yield the final
|
|
176
|
+
associated file-system object. The ``artifact rm'' command simply removes
|
|
177
|
+
an artifact association, it does not remove the artifact itself. Multiple
|
|
178
|
+
artifact paths may be associated under a single <subsection> and the option
|
|
179
|
+
<path> argument to ``artifact rm'' allows us to remove a single association
|
|
180
|
+
rather than all of them at once.
|
|
181
|
+
|
|
182
|
+
For example:
|
|
183
|
+
|
|
184
|
+
git <project> artifact add worktree.myworktree /path/to/artifact
|
|
185
|
+
|
|
186
|
+
Presumably, /path/to/artifact is in some way created in association with
|
|
187
|
+
myworktree, for example by the ``run'' command. When we delete myworktree,
|
|
188
|
+
the artifact association causes /path/to/artifact to also be removed.
|
|
189
|
+
Substitutions can make artifact associations easier to manage:
|
|
190
|
+
|
|
191
|
+
git <project> artifact add worktree /path/to/{worktree}/artifact
|
|
192
|
+
|
|
193
|
+
Notice that we've added the artifact under the more general ``worktree''
|
|
194
|
+
subsection instead of naming a worktree explicitly as before. Because the
|
|
195
|
+
{worktree} substitution appears in the artifact path, deleting any worktree
|
|
196
|
+
will cause the worktree's name to be substituted into the artifact path,
|
|
197
|
+
forming a unique artifact path to remove.
|
|
198
|
+
|
|
199
|
+
We may make this even more general:
|
|
200
|
+
|
|
201
|
+
git <project> config srcdir "{path}"
|
|
202
|
+
git <project> config builddir "{srcdir}/build/{worktree}"
|
|
203
|
+
git <project> config make "make -C {srcdir} BUILDDIR={builddir} {build}"
|
|
204
|
+
git <project> run --make-alias build
|
|
205
|
+
git <project> add build debug "{make}"
|
|
206
|
+
git <project> add build release "{make}"
|
|
207
|
+
git <project> add build check "{make}"
|
|
208
|
+
git <project> artifact add worktree "{builddir}"
|
|
209
|
+
|
|
210
|
+
We've added a single artifact association that will handle any worktree and
|
|
211
|
+
all of our different build types. When we delete the worktree, all
|
|
212
|
+
artifacts related to debug, release and check builds will also be removed.
|
|
213
|
+
|
|
214
|
+
The worktree plugin also modifies the clone and init commands, adding a
|
|
215
|
+
--worktree option to both. With --worktree, clone will create a ``worktree
|
|
216
|
+
layout'' as so:
|
|
217
|
+
|
|
218
|
+
clonedir
|
|
219
|
+
.git
|
|
220
|
+
master
|
|
221
|
+
|
|
222
|
+
Here, ``master'' is a worktree created from the master branch. ``clonedir''
|
|
223
|
+
becames a bare repository, though with refspecs that make it operate like a
|
|
224
|
+
regular clone for fetch and push operations. That is, the cloned repository
|
|
225
|
+
will still have refs/heads and refs/remotes namespaces.
|
|
226
|
+
|
|
227
|
+
With --worktree, init will take an existing local clone and convert it to a
|
|
228
|
+
bare repository, removing all checked out files and creating a master
|
|
229
|
+
worktree:
|
|
230
|
+
|
|
231
|
+
clonedir
|
|
232
|
+
.git
|
|
233
|
+
master
|
|
234
|
+
|
|
235
|
+
Conversion will abort if the workarea is not in a clean state. Note that
|
|
236
|
+
all files in clonedir will be deleted so if there are important files not
|
|
237
|
+
part of the underlying repository, the user must take care to preserve them.
|
|
238
|
+
If the workarea hd a branch other than master checked out, no worktree for
|
|
239
|
+
it will be created automatically, though the user may easily create one
|
|
240
|
+
after conversion.
|
|
241
|
+
|
|
242
|
+
See also:
|
|
243
|
+
|
|
244
|
+
clone
|
|
245
|
+
config
|
|
246
|
+
init
|
|
247
|
+
run
|
|
248
|
+
worktree
|
|
249
|
+
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def __init__(self): super().__init__('artifact')
|
|
253
|
+
|
|
254
|
+
def add_arguments(self,
|
|
255
|
+
git,
|
|
256
|
+
gitproject,
|
|
257
|
+
project,
|
|
258
|
+
parser_manager,
|
|
259
|
+
plugin_manager):
|
|
260
|
+
"""Add arguments for 'git-project artifact.'"""
|
|
261
|
+
|
|
262
|
+
artifact_parser = add_top_level_command(parser_manager,
|
|
263
|
+
'artifact',
|
|
264
|
+
'artifact',
|
|
265
|
+
help='Manipulate artifacts')
|
|
266
|
+
|
|
267
|
+
add_plugin_version_argument(artifact_parser)
|
|
268
|
+
|
|
269
|
+
artifact_subparser = parser_manager.add_subparser(artifact_parser,
|
|
270
|
+
'artifact-command',
|
|
271
|
+
help='artifact commands')
|
|
272
|
+
|
|
273
|
+
# add
|
|
274
|
+
add_parser = parser_manager.add_parser(artifact_subparser,
|
|
275
|
+
'add',
|
|
276
|
+
'artifact-add',
|
|
277
|
+
help='Add an artifact',
|
|
278
|
+
epilog = """
|
|
279
|
+
The <subsection> argument is appended to the <project>.artifact section to form
|
|
280
|
+
the final git config section that will hold the artifact path.
|
|
281
|
+
""",
|
|
282
|
+
formatter_class = argparse.RawDescriptionHelpFormatter)
|
|
283
|
+
|
|
284
|
+
add_parser.add_argument('subsection',
|
|
285
|
+
help='Subsection under which to add the artifact')
|
|
286
|
+
add_parser.add_argument('path',
|
|
287
|
+
help='Artifact path, may use substitutions')
|
|
288
|
+
|
|
289
|
+
add_parser.set_defaults(func=command_artifact_add)
|
|
290
|
+
|
|
291
|
+
# rm
|
|
292
|
+
rm_parser = parser_manager.add_parser(artifact_subparser,
|
|
293
|
+
'rm',
|
|
294
|
+
'artifact-rm',
|
|
295
|
+
help='Remove an artifact path',
|
|
296
|
+
epilog = """
|
|
297
|
+
The <subsection> argument is appended to the <project>.artifact section to form
|
|
298
|
+
the final git config section that will hold the artifact path.
|
|
299
|
+
""",
|
|
300
|
+
formatter_class =
|
|
301
|
+
argparse.RawDescriptionHelpFormatter)
|
|
302
|
+
|
|
303
|
+
rm_parser.add_argument('subsection',
|
|
304
|
+
help='Subsection under which to add the artifact')
|
|
305
|
+
rm_parser.add_argument('path', nargs='?',
|
|
306
|
+
help='Artifact path, may use substitutions')
|
|
307
|
+
|
|
308
|
+
rm_parser.set_defaults(func=command_artifact_rm)
|
|
309
|
+
|
|
310
|
+
def add_class_hooks(self, git, project, plugin_manager):
|
|
311
|
+
# Enhance ConfigObject rm to also remove artifacts.
|
|
312
|
+
config_object_rm = ConfigObject.rm
|
|
313
|
+
|
|
314
|
+
def artifact_rm(self):
|
|
315
|
+
# See if there is any artifact associated with this ConfigObject.
|
|
316
|
+
artifact = None
|
|
317
|
+
if Artifact.exists(self._git,
|
|
318
|
+
self._project_section,
|
|
319
|
+
self._subsection + '.' + self._ident):
|
|
320
|
+
artifact = Artifact.get(self._git,
|
|
321
|
+
self._project_section,
|
|
322
|
+
self._subsection + '.' + self._ident)
|
|
323
|
+
elif Artifact.exists(self._git,
|
|
324
|
+
self._project_section,
|
|
325
|
+
self._subsection):
|
|
326
|
+
artifact = Artifact.get(self._git,
|
|
327
|
+
self._project_section,
|
|
328
|
+
self._subsection)
|
|
329
|
+
|
|
330
|
+
if artifact:
|
|
331
|
+
for path in artifact.iter_multival('path'):
|
|
332
|
+
fullpath = artifact.substitute_value(self._git, project, path)
|
|
333
|
+
run_command_with_shell(f'rm -rf {fullpath}',
|
|
334
|
+
show_command=True)
|
|
335
|
+
|
|
336
|
+
config_object_rm(self)
|
|
337
|
+
|
|
338
|
+
ConfigObject.rm = artifact_rm
|
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
"""A plugin to add a 'branch' command to git-project. The branch command allows
|
|
20
|
+
query of branch status with respect to the project and pruning of 'finished'
|
|
21
|
+
branches.
|
|
22
|
+
|
|
23
|
+
Summary:
|
|
24
|
+
|
|
25
|
+
git-project branch status [<pattern>]
|
|
26
|
+
git-project branch prune [--no-ask] [<pattern>]
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
from git_project import Git, RunnableConfigObject, Plugin
|
|
30
|
+
from git_project import add_top_level_command, Project, GitProjectException
|
|
31
|
+
|
|
32
|
+
from git_project_core_plugins.common import add_plugin_version_argument
|
|
33
|
+
|
|
34
|
+
import getpass
|
|
35
|
+
|
|
36
|
+
def query_yes_no(question, default="yes"):
|
|
37
|
+
"""Ask a yes/no question via input() and return their answer.
|
|
38
|
+
|
|
39
|
+
"question" is a string that is presented to the user.
|
|
40
|
+
"default" is the presumed answer if the user just hits <Enter>.
|
|
41
|
+
It must be "yes" (the default), "no" or None (meaning
|
|
42
|
+
an answer is required of the user).
|
|
43
|
+
|
|
44
|
+
The "answer" return value is True for "yes" or False for "no".
|
|
45
|
+
"""
|
|
46
|
+
valid = {"yes": True, "y": True, "ye": True,
|
|
47
|
+
"no": False, "n": False}
|
|
48
|
+
if default is None:
|
|
49
|
+
prompt = " [y/n] "
|
|
50
|
+
elif default == "yes":
|
|
51
|
+
prompt = " [Y/n] "
|
|
52
|
+
elif default == "no":
|
|
53
|
+
prompt = " [y/N] "
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError("invalid default answer: '%s'" % default)
|
|
56
|
+
|
|
57
|
+
while True:
|
|
58
|
+
sys.stdout.write(question + prompt)
|
|
59
|
+
choice = input().lower()
|
|
60
|
+
if default is not None and choice == '':
|
|
61
|
+
return valid[default]
|
|
62
|
+
elif choice in valid:
|
|
63
|
+
return valid[choice]
|
|
64
|
+
else:
|
|
65
|
+
sys.stdout.write("Please respond with 'yes' or 'no' "
|
|
66
|
+
"(or 'y' or 'n').\n")
|
|
67
|
+
|
|
68
|
+
def command_branch_status(git, gitproject, project, clargs):
|
|
69
|
+
"""Implement git-project branch status."""
|
|
70
|
+
ref = clargs.name_or_ref
|
|
71
|
+
|
|
72
|
+
if not ref:
|
|
73
|
+
if clargs.all_user:
|
|
74
|
+
ref = 'refs/heads/user/' + getpass.getuser()
|
|
75
|
+
elif clargs.all:
|
|
76
|
+
ref = 'refs/heads'
|
|
77
|
+
else:
|
|
78
|
+
raise GitProjectException('merged requires branch name, pattern, --all-user or --all')
|
|
79
|
+
else:
|
|
80
|
+
if not ref.startswith('refs/'):
|
|
81
|
+
ref = 'refs/heads/' + ref
|
|
82
|
+
|
|
83
|
+
target = clargs.target if clargs.target else None
|
|
84
|
+
|
|
85
|
+
branch_width = 45
|
|
86
|
+
status_width = 8
|
|
87
|
+
separator_width = branch_width + 2 * status_width - (status_width - len('merged'))
|
|
88
|
+
|
|
89
|
+
print('-' * separator_width)
|
|
90
|
+
print('{:<{branch_width}s}{:<{status_width}s}{:<{status_width}s}'.
|
|
91
|
+
format('branch', 'merged', 'pushed',
|
|
92
|
+
branch_width=branch_width, status_width=status_width))
|
|
93
|
+
print('-' * separator_width)
|
|
94
|
+
|
|
95
|
+
for branch in git.iterrefnames([ref]):
|
|
96
|
+
merge_status = 'no'
|
|
97
|
+
if target:
|
|
98
|
+
merge_status = 'yes' if git.branch_is_merged(branch, target) else 'no'
|
|
99
|
+
else:
|
|
100
|
+
merge_status = 'yes' if project.branch_is_merged(branch) else 'no'
|
|
101
|
+
|
|
102
|
+
push_status = 'yes' if project.branch_is_pushed(branch) else 'no'
|
|
103
|
+
|
|
104
|
+
print('{:<{branch_width}s}{:<{status_width}s}{:<{status_width}s}'.
|
|
105
|
+
format(branch[:branch_width-2], merge_status, push_status,
|
|
106
|
+
branch_width=branch_width, status_width=status_width))
|
|
107
|
+
print('-' * separator_width)
|
|
108
|
+
|
|
109
|
+
def command_branch_prune(git, gitproject, project, clargs):
|
|
110
|
+
"""Implement git-project branch prune."""
|
|
111
|
+
ref = clargs.name_or_ref
|
|
112
|
+
if not ref:
|
|
113
|
+
if clargs.all_user:
|
|
114
|
+
ref = 'refs/heads/user/' + getpass.getuser()
|
|
115
|
+
else:
|
|
116
|
+
raise GitProjectException('Prune requires branch name, pattern or --all-user')
|
|
117
|
+
else:
|
|
118
|
+
ref = Git.branch_name_to_refname(ref)
|
|
119
|
+
|
|
120
|
+
branch_width = 45
|
|
121
|
+
status_width = 15
|
|
122
|
+
separator_width = branch_width + 2 * status_width
|
|
123
|
+
|
|
124
|
+
print('-' * separator_width)
|
|
125
|
+
print('{:<{branch_width}s}{:<{status_width}s}{:<{status_width}s}'.
|
|
126
|
+
format('branch', 'local status', 'remote status',
|
|
127
|
+
branch_width=branch_width, status_width=status_width))
|
|
128
|
+
print('-' * separator_width)
|
|
129
|
+
|
|
130
|
+
for branch in git.iterrefnames([ref]):
|
|
131
|
+
status = 'merged' if project.branch_is_merged(branch) else 'unmerged'
|
|
132
|
+
|
|
133
|
+
print('{:<{branch_width}s}{:<{status_width}s}'.
|
|
134
|
+
format(branch[:branch_width-2], status,
|
|
135
|
+
branch_width=branch_width, status_width=status_width))
|
|
136
|
+
if clargs.force or status == 'merged':
|
|
137
|
+
if clargs.no_ask or query_yes_no('Prune?', default=None):
|
|
138
|
+
project.prune_branch(branch)
|
|
139
|
+
|
|
140
|
+
class BranchPlugin(Plugin):
|
|
141
|
+
"""
|
|
142
|
+
The branch command queries the status of branches against known project
|
|
143
|
+
branches and provides methods to prune old branches.
|
|
144
|
+
|
|
145
|
+
Summary:
|
|
146
|
+
|
|
147
|
+
git <project> branch status [--all] [<refish>]
|
|
148
|
+
git <project> branch prune [--force] [--no-ask]
|
|
149
|
+
|
|
150
|
+
The branch status command checks the given <refish> (or all local branches
|
|
151
|
+
with the --all option) against the project-configured branches. The command
|
|
152
|
+
outputs a table of branches and whether they are merged to a project branch
|
|
153
|
+
and/or pushed to a remote. For example:
|
|
154
|
+
|
|
155
|
+
git <project> config --add branch release
|
|
156
|
+
git <project> branch status mybranch
|
|
157
|
+
|
|
158
|
+
The report will show whether mybranch is merged to the release or master
|
|
159
|
+
branches (master is always a configured project branch) and whether the
|
|
160
|
+
commit pointed to mybranch is pushed to a remote.
|
|
161
|
+
|
|
162
|
+
The branch prune command computes the same information and if the branch is
|
|
163
|
+
merged to a project branch and that project branch is pushed to a remote,
|
|
164
|
+
will ask whether mybranch should be deleted. If the user indicates yes,
|
|
165
|
+
both the local mybranch and its remote counterpart, if any, will be deleted.
|
|
166
|
+
|
|
167
|
+
With --force, branches will be pruneed regardless of merge/push status.
|
|
168
|
+
With --no-ask branch prune operates in batch mode, assuming all merged and
|
|
169
|
+
pushed branches should be pruned.
|
|
170
|
+
|
|
171
|
+
See also:
|
|
172
|
+
|
|
173
|
+
config
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
def __init__(self):
|
|
177
|
+
super().__init__('branch')
|
|
178
|
+
|
|
179
|
+
def add_arguments(self,
|
|
180
|
+
git,
|
|
181
|
+
gitproject,
|
|
182
|
+
project,
|
|
183
|
+
parser_manager,
|
|
184
|
+
plugin_manager):
|
|
185
|
+
"""Add arguments for 'git project branch.'"""
|
|
186
|
+
# branch
|
|
187
|
+
branch_parser = add_top_level_command(parser_manager,
|
|
188
|
+
'branch',
|
|
189
|
+
'branch',
|
|
190
|
+
help='Manipulate branches')
|
|
191
|
+
|
|
192
|
+
add_plugin_version_argument(branch_parser)
|
|
193
|
+
|
|
194
|
+
branch_subparser = parser_manager.add_subparser(branch_parser,
|
|
195
|
+
'branch_command',
|
|
196
|
+
help='branch commands')
|
|
197
|
+
|
|
198
|
+
# branch status
|
|
199
|
+
branch_status_parser = parser_manager.add_parser(branch_subparser,
|
|
200
|
+
'status',
|
|
201
|
+
'branch-status',
|
|
202
|
+
help='Show branch status')
|
|
203
|
+
|
|
204
|
+
branch_status_parser.set_defaults(func=command_branch_status)
|
|
205
|
+
|
|
206
|
+
branch_status_parser.add_argument('name_or_ref', nargs='?', help='Branch or pattern to filter branches')
|
|
207
|
+
branch_status_parser.add_argument('target', nargs='?', help='Target branch to check against')
|
|
208
|
+
branch_status_parser.add_argument('--all', action='store_true', help='Show all branches')
|
|
209
|
+
branch_status_parser.add_argument('--all-user', action='store_true', help='Show all user\'s branches')
|
|
210
|
+
|
|
211
|
+
# branch prune
|
|
212
|
+
branch_prune_parser = parser_manager.add_parser(branch_subparser,
|
|
213
|
+
'prune',
|
|
214
|
+
'branch-prune',
|
|
215
|
+
help='Delete merged branches')
|
|
216
|
+
|
|
217
|
+
branch_prune_parser.set_defaults(func=command_branch_prune)
|
|
218
|
+
|
|
219
|
+
branch_prune_parser.add_argument('name_or_ref', nargs='?', help='Branch name or pattern to filter branches')
|
|
220
|
+
branch_prune_parser.add_argument('--all-user', action='store_true', help='Prune all user\'s branches')
|
|
221
|
+
branch_prune_parser.add_argument('--force', action='store_true', help='Prune even if unmerged')
|
|
222
|
+
branch_prune_parser.add_argument('--no-ask', action='store_true', help='Do not ask before pruning')
|