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.
Files changed (58) hide show
  1. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/PKG-INFO +1 -1
  2. git_project_core_plugins-0.0.25/_version.py +24 -0
  3. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/__init__.py +27 -0
  4. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/artifact.py +338 -0
  5. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/branch.py +222 -0
  6. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/clone.py +98 -0
  7. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/common.py +22 -0
  8. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/config.py +169 -0
  9. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/help.py +298 -0
  10. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/init.py +74 -0
  11. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/run.py +399 -0
  12. git_project_core_plugins-0.0.25/build/lib/git_project_core_plugins/worktree.py +643 -0
  13. git_project_core_plugins-0.0.25/docs/README-template.rst +104 -0
  14. git_project_core_plugins-0.0.25/gen-readme.py +55 -0
  15. git_project_core_plugins-0.0.25/gen-readme.py~ +2 -0
  16. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/PKG-INFO +1 -4
  17. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/SOURCES.txt +1 -9
  18. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/run.py +1 -1
  19. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/worktree.py +5 -1
  20. git_project_core_plugins-0.0.25/src/git_project_core_plugins/worktree.py~ +647 -0
  21. git_project_core_plugins-0.0.25/test.out +8 -0
  22. git_project_core_plugins-0.0.23/.git +0 -1
  23. git_project_core_plugins-0.0.23/_version.py +0 -21
  24. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/COPYING +0 -0
  25. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/MANIFEST.in +0 -0
  26. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/Makefile +0 -0
  27. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/authors.rst +0 -0
  28. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/changelog.rst +0 -0
  29. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/conf.py +0 -0
  30. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/index.rst +0 -0
  31. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/intro.rst +0 -0
  32. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/license.rst +0 -0
  33. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/docs/make.bat +0 -0
  34. {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
  35. {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
  36. {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
  37. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/git_project_core_plugins.egg-info/requires.txt +0 -0
  38. {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
  39. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/pyproject.toml +0 -0
  40. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/requirements.txt +0 -0
  41. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/__init__.py +0 -0
  42. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/artifact.py +0 -0
  43. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/branch.py +0 -0
  44. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/clone.py +0 -0
  45. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/common.py +0 -0
  46. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/config.py +0 -0
  47. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/help.py +0 -0
  48. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/src/git_project_core_plugins/init.py +0 -0
  49. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/common.py +0 -0
  50. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/conftest.py +0 -0
  51. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_artifact.py +0 -0
  52. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_branch.py +0 -0
  53. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_clone.py +0 -0
  54. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_config.py +0 -0
  55. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_help.py +0 -0
  56. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_init.py +0 -0
  57. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_run.py +0 -0
  58. {git_project_core_plugins-0.0.23 → git_project_core_plugins-0.0.25}/tests/test_worktree.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-project-core-plugins
3
- Version: 0.0.23
3
+ Version: 0.0.25
4
4
  Summary: The extensible stupid project manager - core functionality
5
5
  Author-email: "David A. Greene" <dag@obbligato.org>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -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')