gitbetter.git

  1from datetime import datetime
  2from urllib.parse import urlparse
  3
  4from morbin import Morbin, Output
  5from pathier import Pathier, Pathish
  6
  7
  8class Git(Morbin):
  9    # Seat |===================================================Core===================================================|
 10    @property
 11    def program(self) -> str:
 12        return "git"
 13
 14    # def git(self, command: str) -> Output:
 15    #    """Base function for executing git commands.
 16    #    Use this if another function doesn't meet your needs.
 17    #    >>> git {command}"""
 18    #    return self.run(command)
 19
 20    # Seat
 21
 22    def add(self, args: str = "") -> Output:
 23        """>>> git add {args}"""
 24        return self.run(f"add {args}")
 25
 26    def am(self, args: str = "") -> Output:
 27        """>>> git am {args}"""
 28        return self.run(f"am {args}")
 29
 30    def annotate(self, args: str = "") -> Output:
 31        """>>> git annotate {args}"""
 32        return self.run(f"annotate {args}")
 33
 34    def archive(self, args: str = "") -> Output:
 35        """>>> git archive {args}"""
 36        return self.run(f"archive {args}")
 37
 38    def bisect(self, args: str = "") -> Output:
 39        """>>> git bisect {args}"""
 40        return self.run(f"bisect {args}")
 41
 42    def blame(self, args: str = "") -> Output:
 43        """>>> git blame {args}"""
 44        return self.run(f"blame {args}")
 45
 46    def branch(self, args: str = "") -> Output:
 47        """>>> git branch {args}"""
 48        return self.run(f"branch {args}")
 49
 50    def bugreport(self, args: str = "") -> Output:
 51        """>>> git bugreport {args}"""
 52        return self.run(f"bugreport {args}")
 53
 54    def bundle(self, args: str = "") -> Output:
 55        """>>> git bundle {args}"""
 56        return self.run(f"bundle {args}")
 57
 58    def checkout(self, args: str = "") -> Output:
 59        """>>> git checkout {args}"""
 60        return self.run(f"checkout {args}")
 61
 62    def cherry_pick(self, args: str = "") -> Output:
 63        """>>> git cherry-pick {args}"""
 64        return self.run(f"cherry-pick {args}")
 65
 66    def citool(self, args: str = "") -> Output:
 67        """>>> git citool {args}"""
 68        return self.run(f"citool {args}")
 69
 70    def clean(self, args: str = "") -> Output:
 71        """>>> git clean {args}"""
 72        return self.run(f"clean {args}")
 73
 74    def clone(self, args: str = "") -> Output:
 75        """>>> git clone {args}"""
 76        return self.run(f"clone {args}")
 77
 78    def commit(self, args: str = "") -> Output:
 79        """>>> git commit {args}"""
 80        return self.run(f"commit {args}")
 81
 82    def config(self, args: str = "") -> Output:
 83        """>>> git config {args}"""
 84        return self.run(f"config {args}")
 85
 86    def count_objects(self, args: str = "") -> Output:
 87        """>>> git count-objects {args}"""
 88        return self.run(f"count-objects {args}")
 89
 90    def describe(self, args: str = "") -> Output:
 91        """>>> git describe {args}"""
 92        return self.run(f"describe {args}")
 93
 94    def diagnose(self, args: str = "") -> Output:
 95        """>>> git diagnose {args}"""
 96        return self.run(f"diagnose {args}")
 97
 98    def diff(self, args: str = "") -> Output:
 99        """>>> git diff {args}"""
100        return self.run(f"diff {args}")
101
102    def difftool(self, args: str = "") -> Output:
103        """>>> git difftool {args}"""
104        return self.run(f"difftool {args}")
105
106    def fast_export(self, args: str = "") -> Output:
107        """>>> git fast-export {args}"""
108        return self.run(f"fast-export {args}")
109
110    def fast_import(self, args: str = "") -> Output:
111        """>>> git fast-import {args}"""
112        return self.run(f"fast-import {args}")
113
114    def fetch(self, args: str = "") -> Output:
115        """>>> git fetch {args}"""
116        return self.run(f"fetch {args}")
117
118    def filter_branch(self, args: str = "") -> Output:
119        """>>> git filter-branch {args}"""
120        return self.run(f"filter-branch {args}")
121
122    def format_patch(self, args: str = "") -> Output:
123        """>>> git format-patch {args}"""
124        return self.run(f"format-patch {args}")
125
126    def fsck(self, args: str = "") -> Output:
127        """>>> git fsck {args}"""
128        return self.run(f"fsck {args}")
129
130    def gc(self, args: str = "") -> Output:
131        """>>> git gc {args}"""
132        return self.run(f"gc {args}")
133
134    def gitk(self, args: str = "") -> Output:
135        """>>> git gitk {args}"""
136        return self.run(f"gitk {args}")
137
138    def gitweb(self, args: str = "") -> Output:
139        """>>> git gitweb {args}"""
140        return self.run(f"gitweb {args}")
141
142    def grep(self, args: str = "") -> Output:
143        """>>> git grep {args}"""
144        return self.run(f"grep {args}")
145
146    def gui(self, args: str = "") -> Output:
147        """>>> git gui {args}"""
148        return self.run(f"gui {args}")
149
150    def help(self, args: str = "") -> Output:
151        """>>> git help {args}"""
152        return self.run(f"help {args}")
153
154    def init(self, args: str = "") -> Output:
155        """>>> git init {args}"""
156        return self.run(f"init {args}")
157
158    def instaweb(self, args: str = "") -> Output:
159        """>>> git instaweb {args}"""
160        return self.run(f"instaweb {args}")
161
162    def log(self, args: str = "") -> Output:
163        """>>> git log {args}"""
164        return self.run(f"log {args}")
165
166    def maintenance(self, args: str = "") -> Output:
167        """>>> git maintenance {args}"""
168        return self.run(f"maintenance {args}")
169
170    def merge(self, args: str = "") -> Output:
171        """>>> git merge {args}"""
172        return self.run(f"merge {args}")
173
174    def merge_tree(self, args: str = "") -> Output:
175        """>>> git merge-tree {args}"""
176        return self.run(f"merge-tree {args}")
177
178    def mergetool(self, args: str = "") -> Output:
179        """>>> git mergetool {args}"""
180        return self.run(f"mergetool {args}")
181
182    def mv(self, args: str = "") -> Output:
183        """>>> git mv {args}"""
184        return self.run(f"mv {args}")
185
186    def notes(self, args: str = "") -> Output:
187        """>>> git notes {args}"""
188        return self.run(f"notes {args}")
189
190    def pack_refs(self, args: str = "") -> Output:
191        """>>> git pack-refs {args}"""
192        return self.run(f"pack-refs {args}")
193
194    def prune(self, args: str = "") -> Output:
195        """>>> git prune {args}"""
196        return self.run(f"prune {args}")
197
198    def pull(self, args: str = "") -> Output:
199        """>>> git pull {args}"""
200        return self.run(f"pull {args}")
201
202    def push(self, args: str = "") -> Output:
203        """>>> git push {args}"""
204        return self.run(f"push {args}")
205
206    def range_diff(self, args: str = "") -> Output:
207        """>>> git range-diff {args}"""
208        return self.run(f"range-diff {args}")
209
210    def rebase(self, args: str = "") -> Output:
211        """>>> git rebase {args}"""
212        return self.run(f"rebase {args}")
213
214    def reflog(self, args: str = "") -> Output:
215        """>>> git reflog {args}"""
216        return self.run(f"reflog {args}")
217
218    def remote(self, args: str = "") -> Output:
219        """>>> git remote {args}"""
220        return self.run(f"remote {args}")
221
222    def repack(self, args: str = "") -> Output:
223        """>>> git repack {args}"""
224        return self.run(f"repack {args}")
225
226    def replace(self, args: str = "") -> Output:
227        """>>> git replace {args}"""
228        return self.run(f"replace {args}")
229
230    def request_pull(self, args: str = "") -> Output:
231        """>>> git request-pull {args}"""
232        return self.run(f"request-pull {args}")
233
234    def rerere(self, args: str = "") -> Output:
235        """>>> git rerere {args}"""
236        return self.run(f"rerere {args}")
237
238    def reset(self, args: str = "") -> Output:
239        """>>> git reset {args}"""
240        return self.run(f"reset {args}")
241
242    def restore(self, args: str = "") -> Output:
243        """>>> git restore {args}"""
244        return self.run(f"restore {args}")
245
246    def revert(self, args: str = "") -> Output:
247        """>>> git revert {args}"""
248        return self.run(f"revert {args}")
249
250    def rm(self, args: str = "") -> Output:
251        """>>> git rm {args}"""
252        return self.run(f"rm {args}")
253
254    def scalar(self, args: str = "") -> Output:
255        """>>> git scalar {args}"""
256        return self.run(f"scalar {args}")
257
258    def shortlog(self, args: str = "") -> Output:
259        """>>> git shortlog {args}"""
260        return self.run(f"shortlog {args}")
261
262    def show(self, args: str = "") -> Output:
263        """>>> git show {args}"""
264        return self.run(f"show {args}")
265
266    def show_branch(self, args: str = "") -> Output:
267        """>>> git show-branch {args}"""
268        return self.run(f"show-branch {args}")
269
270    def sparse_checkout(self, args: str = "") -> Output:
271        """>>> git sparse-checkout {args}"""
272        return self.run(f"sparse-checkout {args}")
273
274    def stash(self, args: str = "") -> Output:
275        """>>> git stash {args}"""
276        return self.run(f"stash {args}")
277
278    def status(self, args: str = "") -> Output:
279        """>>> git status {args}"""
280        return self.run(f"status {args}")
281
282    def submodule(self, args: str = "") -> Output:
283        """>>> git submodule {args}"""
284        return self.run(f"submodule {args}")
285
286    def switch(self, args: str = "") -> Output:
287        """>>> git switch {args}"""
288        return self.run(f"switch {args}")
289
290    def tag(self, args: str = "") -> Output:
291        """>>> git tag {args}"""
292        return self.run(f"tag {args}")
293
294    def verify_commit(self, args: str = "") -> Output:
295        """>>> git verify-commit {args}"""
296        return self.run(f"verify-commit {args}")
297
298    def verify_tag(self, args: str = "") -> Output:
299        """>>> git verify-tag {args}"""
300        return self.run(f"verify-tag {args}")
301
302    def version(self, args: str = "") -> Output:
303        """>>> git version {args}"""
304        return self.run(f"version {args}")
305
306    def whatchanged(self, args: str = "") -> Output:
307        """>>> git whatchanged {args}"""
308        return self.run(f"whatchanged {args}")
309
310    def worktree(self, args: str = "") -> Output:
311        """>>> git worktree {args}"""
312        return self.run(f"worktree {args}")
313
314    # Seat |=================================================Convenience=================================================|
315
316    @property
317    def current_branch(self) -> str:
318        """Returns the name of the currently active branch."""
319        current_branch = ""
320        with self.capturing_output():
321            branches = self.branch().stdout.splitlines()
322            for branch in branches:
323                if branch.startswith("*"):
324                    current_branch = branch[2:]
325                    break
326        return current_branch
327
328    @property
329    def dob(self) -> datetime:
330        """Date of this repo's first commit."""
331        with self.capturing_output():
332            output = self.log("--pretty=format:'%cs'")
333            return datetime.strptime(output.stdout.splitlines()[-1], "%Y-%m-%d")
334
335    @property
336    def origin_url(self) -> Output:
337        """The remote origin url for this repo
338        >>> git remote get-url origin"""
339        return self.remote("get-url origin")
340
341    def add_all(self) -> Output:
342        """Stage all modified and untracked files.
343        >>> git add ."""
344        return self.add(".")
345
346    def add_files(self, files: list[Pathish]) -> Output:
347        """Stage a list of files."""
348        args = " ".join([str(file) for file in files])
349        return self.add(args)
350
351    def add_remote_url(self, url: str, name: str = "origin") -> Output:
352        """Add remote url to repo.
353        >>> git remote add {name} {url}"""
354        return self.remote(f"add {name} {url}")
355
356    def amend(self, files: list[Pathish] | None = None) -> Output:
357        """Stage and commit changes to the previous commit.
358
359        If `files` is `None`, all files will be staged.
360
361        >>> git add {files} or git add .
362        >>> git commit --amend --no-edit
363        """
364        return (self.add_files(files) if files else self.add_all()) + self.commit(
365            "--amend --no-edit"
366        )
367
368    def commit_all(self, message: str) -> Output:
369        """Stage and commit all files with `message`.
370        >>> git add .
371        >>> git commit -m "{message}" """
372        return self.add_all() + self.commit(f'-m "{message}"')
373
374    def commit_files(self, files: list[Pathish], message: str) -> Output:
375        """Commit a list of files or file patterns with commit message `message`.
376        >>> git commit {files} -m "{message}" """
377        files_arg = " ".join(str(file) for file in files)
378        return self.commit(f'{files_arg} -m "{message}"')
379
380    def create_new_branch(self, branch_name: str) -> Output:
381        """Create and switch to a new branch named with `branch_name`.
382        >>> git checkout -b {branch_name} --track"""
383        return self.checkout(f"-b {branch_name} --track")
384
385    def delete_branch(self, branch_name: str, local_only: bool = True) -> Output:
386        """Delete `branch_name` from repo.
387
388        #### :params:
389
390        `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.
391        >>> git branch --delete {branch_name}
392
393        Then if not `local_only`:
394        >>> git push origin --delete {branch_name}
395        """
396        output = self.branch(f"--delete {branch_name}")
397        if not local_only:
398            return output + self.push(f"origin --delete {branch_name}")
399        return output
400
401    def ignore(self, patterns: list[str]):
402        """Add `patterns` to `.gitignore`."""
403        gitignore = Pathier(".gitignore")
404        if not gitignore.exists():
405            gitignore.touch()
406        ignores = gitignore.split()
407        ignores += [pattern for pattern in patterns if pattern not in ignores]
408        gitignore.join(ignores)
409
410    def initcommit(self, files: list[Pathish] | None = None) -> Output:
411        """Stage and commit `files` with the message `Initial commit`.
412
413        If `files` is not given, all files will be added and committed.
414        >>> git add {files} or git add .
415        >>> git commit -m "Initial commit" """
416        return (self.add_files(files) if files else self.add_all()) + self.commit(
417            '-m "Initial commit"'
418        )
419
420    def list_branches(self) -> Output:
421        """>>> git branch -vva"""
422        return self.branch("-vva")
423
424    def loggy(self) -> Output:
425        """>>> git log --graph --abbrev-commit --name-only --pretty=tformat:'%C(auto)%h %C(green)(%cs|%cr)%C(auto)%d %C(magenta)%s'"""
426        return self.log(
427            "--graph --abbrev-commit --name-only --pretty=tformat:'%C(auto)%h %C(green)(%cs|%cr)%C(auto)%d %C(magenta)%s'"
428        )
429
430    def merge_to(self, branch: str = "main") -> Output:
431        """Merge the current branch with `branch` after switching to `branch`.
432
433        i.e. If on branch `my-feature`,
434        >>> git.merge_to()
435
436        will switch to `main` and merge `my-feature` into `main`."""
437        current_branch = self.current_branch
438        output = self.switch(branch)
439        output += self.merge(current_branch)
440        return output
441
442    def new_repo(self) -> Output:
443        """Initialize a new repo in current directory.
444        >>> git init -b main"""
445        return self.init("-b main")
446
447    def push_new_branch(self, branch: str) -> Output:
448        """Push a new branch to origin with tracking.
449        >>> git push -u origin {branch}"""
450        return self.push(f"-u origin {branch}")
451
452    def switch_branch(self, branch_name: str) -> Output:
453        """Switch to the branch specified by `branch_name`.
454        >>> git checkout {branch_name}"""
455        return self.checkout(branch_name)
456
457    def undo(self) -> Output:
458        """Undo uncommitted changes.
459        >>> git checkout ."""
460        return self.checkout(".")
461
462    def untrack(self, *paths: Pathish) -> Output:
463        """Remove any number of `paths` from the index.
464
465        Equivalent to
466        >>> git rm --cached {path}
467
468        for each path in `paths`."""
469        paths_ = [str(path) for path in paths]
470        return sum(
471            [self.rm(f"--cached {path}") for path in paths_[1:]],
472            self.rm(f"--cached {paths_[0]}"),
473        )
474
475    def rename_file(self, file: Pathish, new_name: str) -> Output:
476        """Rename `file` to `new_name` and add renaming to staging index.
477
478        `new_name` should include the file suffix.
479
480        Equivalent to renaming `old_file.py` to `new_file.py` then executing
481        >>> git add new_file.py
482        >>> git rm old_file.py"""
483        file = Pathier(file)
484        new_file = file.replace(file.with_name(new_name))
485        return self.add_files([new_file]) + self.rm(str(file))
486
487
488# |===============================Requires GitHub CLI to be installed and configured===============================|
489class GitHub(Morbin):
490    @property
491    def program(self) -> str:
492        return "gh"
493
494    @property
495    def owner(self) -> str:
496        return self._owner_reponame().split("/")[0]
497
498    @property
499    def repo_name(self) -> str:
500        return self._owner_reponame().split("/")[1]
501
502    def _change_visibility(self, visibility: str) -> Output:
503        return self.run(
504            f"repo edit {self.owner}/{self.repo_name} --visibility {visibility}"
505        )
506
507    def _owner_reponame(self) -> str:
508        """Returns "owner/repo-name", assuming there's one remote origin url and it's for github."""
509        git = Git()
510        with git.capturing_output():
511            return urlparse(git.origin_url.stdout.strip("\n")).path.strip("/")
512
513    def create_remote(self, name: str, public: bool = False) -> Output:
514        """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo.
515
516        #### :params:
517
518        `name`: The name for the repo.
519
520        `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.
521        """
522        visibility = "--public" if public else "--private"
523        return self.run(f"repo create {name} {visibility}")
524
525    def create_remote_from_cwd(self, public: bool = False) -> Output:
526        """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from
527        the current working directory repo and add its url as this repo's remote origin.
528
529        #### :params:
530
531        `public`: Create the GitHub repo as a public repo, default is to create it as private.
532        """
533        visibility = "--public" if public else "--private"
534        return self.run(f"repo create --source . {visibility} --push")
535
536    def delete_remote(self) -> Output:
537        """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo."""
538        return self.run(f"repo delete {self.owner}/{self.repo_name} --yes")
539
540    def make_private(self) -> Output:
541        """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private."""
542        return self._change_visibility("private")
543
544    def make_public(self) -> Output:
545        """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public."""
546        return self._change_visibility("public")
class Git(morbin.morbin.Morbin):
  9class Git(Morbin):
 10    # Seat |===================================================Core===================================================|
 11    @property
 12    def program(self) -> str:
 13        return "git"
 14
 15    # def git(self, command: str) -> Output:
 16    #    """Base function for executing git commands.
 17    #    Use this if another function doesn't meet your needs.
 18    #    >>> git {command}"""
 19    #    return self.run(command)
 20
 21    # Seat
 22
 23    def add(self, args: str = "") -> Output:
 24        """>>> git add {args}"""
 25        return self.run(f"add {args}")
 26
 27    def am(self, args: str = "") -> Output:
 28        """>>> git am {args}"""
 29        return self.run(f"am {args}")
 30
 31    def annotate(self, args: str = "") -> Output:
 32        """>>> git annotate {args}"""
 33        return self.run(f"annotate {args}")
 34
 35    def archive(self, args: str = "") -> Output:
 36        """>>> git archive {args}"""
 37        return self.run(f"archive {args}")
 38
 39    def bisect(self, args: str = "") -> Output:
 40        """>>> git bisect {args}"""
 41        return self.run(f"bisect {args}")
 42
 43    def blame(self, args: str = "") -> Output:
 44        """>>> git blame {args}"""
 45        return self.run(f"blame {args}")
 46
 47    def branch(self, args: str = "") -> Output:
 48        """>>> git branch {args}"""
 49        return self.run(f"branch {args}")
 50
 51    def bugreport(self, args: str = "") -> Output:
 52        """>>> git bugreport {args}"""
 53        return self.run(f"bugreport {args}")
 54
 55    def bundle(self, args: str = "") -> Output:
 56        """>>> git bundle {args}"""
 57        return self.run(f"bundle {args}")
 58
 59    def checkout(self, args: str = "") -> Output:
 60        """>>> git checkout {args}"""
 61        return self.run(f"checkout {args}")
 62
 63    def cherry_pick(self, args: str = "") -> Output:
 64        """>>> git cherry-pick {args}"""
 65        return self.run(f"cherry-pick {args}")
 66
 67    def citool(self, args: str = "") -> Output:
 68        """>>> git citool {args}"""
 69        return self.run(f"citool {args}")
 70
 71    def clean(self, args: str = "") -> Output:
 72        """>>> git clean {args}"""
 73        return self.run(f"clean {args}")
 74
 75    def clone(self, args: str = "") -> Output:
 76        """>>> git clone {args}"""
 77        return self.run(f"clone {args}")
 78
 79    def commit(self, args: str = "") -> Output:
 80        """>>> git commit {args}"""
 81        return self.run(f"commit {args}")
 82
 83    def config(self, args: str = "") -> Output:
 84        """>>> git config {args}"""
 85        return self.run(f"config {args}")
 86
 87    def count_objects(self, args: str = "") -> Output:
 88        """>>> git count-objects {args}"""
 89        return self.run(f"count-objects {args}")
 90
 91    def describe(self, args: str = "") -> Output:
 92        """>>> git describe {args}"""
 93        return self.run(f"describe {args}")
 94
 95    def diagnose(self, args: str = "") -> Output:
 96        """>>> git diagnose {args}"""
 97        return self.run(f"diagnose {args}")
 98
 99    def diff(self, args: str = "") -> Output:
100        """>>> git diff {args}"""
101        return self.run(f"diff {args}")
102
103    def difftool(self, args: str = "") -> Output:
104        """>>> git difftool {args}"""
105        return self.run(f"difftool {args}")
106
107    def fast_export(self, args: str = "") -> Output:
108        """>>> git fast-export {args}"""
109        return self.run(f"fast-export {args}")
110
111    def fast_import(self, args: str = "") -> Output:
112        """>>> git fast-import {args}"""
113        return self.run(f"fast-import {args}")
114
115    def fetch(self, args: str = "") -> Output:
116        """>>> git fetch {args}"""
117        return self.run(f"fetch {args}")
118
119    def filter_branch(self, args: str = "") -> Output:
120        """>>> git filter-branch {args}"""
121        return self.run(f"filter-branch {args}")
122
123    def format_patch(self, args: str = "") -> Output:
124        """>>> git format-patch {args}"""
125        return self.run(f"format-patch {args}")
126
127    def fsck(self, args: str = "") -> Output:
128        """>>> git fsck {args}"""
129        return self.run(f"fsck {args}")
130
131    def gc(self, args: str = "") -> Output:
132        """>>> git gc {args}"""
133        return self.run(f"gc {args}")
134
135    def gitk(self, args: str = "") -> Output:
136        """>>> git gitk {args}"""
137        return self.run(f"gitk {args}")
138
139    def gitweb(self, args: str = "") -> Output:
140        """>>> git gitweb {args}"""
141        return self.run(f"gitweb {args}")
142
143    def grep(self, args: str = "") -> Output:
144        """>>> git grep {args}"""
145        return self.run(f"grep {args}")
146
147    def gui(self, args: str = "") -> Output:
148        """>>> git gui {args}"""
149        return self.run(f"gui {args}")
150
151    def help(self, args: str = "") -> Output:
152        """>>> git help {args}"""
153        return self.run(f"help {args}")
154
155    def init(self, args: str = "") -> Output:
156        """>>> git init {args}"""
157        return self.run(f"init {args}")
158
159    def instaweb(self, args: str = "") -> Output:
160        """>>> git instaweb {args}"""
161        return self.run(f"instaweb {args}")
162
163    def log(self, args: str = "") -> Output:
164        """>>> git log {args}"""
165        return self.run(f"log {args}")
166
167    def maintenance(self, args: str = "") -> Output:
168        """>>> git maintenance {args}"""
169        return self.run(f"maintenance {args}")
170
171    def merge(self, args: str = "") -> Output:
172        """>>> git merge {args}"""
173        return self.run(f"merge {args}")
174
175    def merge_tree(self, args: str = "") -> Output:
176        """>>> git merge-tree {args}"""
177        return self.run(f"merge-tree {args}")
178
179    def mergetool(self, args: str = "") -> Output:
180        """>>> git mergetool {args}"""
181        return self.run(f"mergetool {args}")
182
183    def mv(self, args: str = "") -> Output:
184        """>>> git mv {args}"""
185        return self.run(f"mv {args}")
186
187    def notes(self, args: str = "") -> Output:
188        """>>> git notes {args}"""
189        return self.run(f"notes {args}")
190
191    def pack_refs(self, args: str = "") -> Output:
192        """>>> git pack-refs {args}"""
193        return self.run(f"pack-refs {args}")
194
195    def prune(self, args: str = "") -> Output:
196        """>>> git prune {args}"""
197        return self.run(f"prune {args}")
198
199    def pull(self, args: str = "") -> Output:
200        """>>> git pull {args}"""
201        return self.run(f"pull {args}")
202
203    def push(self, args: str = "") -> Output:
204        """>>> git push {args}"""
205        return self.run(f"push {args}")
206
207    def range_diff(self, args: str = "") -> Output:
208        """>>> git range-diff {args}"""
209        return self.run(f"range-diff {args}")
210
211    def rebase(self, args: str = "") -> Output:
212        """>>> git rebase {args}"""
213        return self.run(f"rebase {args}")
214
215    def reflog(self, args: str = "") -> Output:
216        """>>> git reflog {args}"""
217        return self.run(f"reflog {args}")
218
219    def remote(self, args: str = "") -> Output:
220        """>>> git remote {args}"""
221        return self.run(f"remote {args}")
222
223    def repack(self, args: str = "") -> Output:
224        """>>> git repack {args}"""
225        return self.run(f"repack {args}")
226
227    def replace(self, args: str = "") -> Output:
228        """>>> git replace {args}"""
229        return self.run(f"replace {args}")
230
231    def request_pull(self, args: str = "") -> Output:
232        """>>> git request-pull {args}"""
233        return self.run(f"request-pull {args}")
234
235    def rerere(self, args: str = "") -> Output:
236        """>>> git rerere {args}"""
237        return self.run(f"rerere {args}")
238
239    def reset(self, args: str = "") -> Output:
240        """>>> git reset {args}"""
241        return self.run(f"reset {args}")
242
243    def restore(self, args: str = "") -> Output:
244        """>>> git restore {args}"""
245        return self.run(f"restore {args}")
246
247    def revert(self, args: str = "") -> Output:
248        """>>> git revert {args}"""
249        return self.run(f"revert {args}")
250
251    def rm(self, args: str = "") -> Output:
252        """>>> git rm {args}"""
253        return self.run(f"rm {args}")
254
255    def scalar(self, args: str = "") -> Output:
256        """>>> git scalar {args}"""
257        return self.run(f"scalar {args}")
258
259    def shortlog(self, args: str = "") -> Output:
260        """>>> git shortlog {args}"""
261        return self.run(f"shortlog {args}")
262
263    def show(self, args: str = "") -> Output:
264        """>>> git show {args}"""
265        return self.run(f"show {args}")
266
267    def show_branch(self, args: str = "") -> Output:
268        """>>> git show-branch {args}"""
269        return self.run(f"show-branch {args}")
270
271    def sparse_checkout(self, args: str = "") -> Output:
272        """>>> git sparse-checkout {args}"""
273        return self.run(f"sparse-checkout {args}")
274
275    def stash(self, args: str = "") -> Output:
276        """>>> git stash {args}"""
277        return self.run(f"stash {args}")
278
279    def status(self, args: str = "") -> Output:
280        """>>> git status {args}"""
281        return self.run(f"status {args}")
282
283    def submodule(self, args: str = "") -> Output:
284        """>>> git submodule {args}"""
285        return self.run(f"submodule {args}")
286
287    def switch(self, args: str = "") -> Output:
288        """>>> git switch {args}"""
289        return self.run(f"switch {args}")
290
291    def tag(self, args: str = "") -> Output:
292        """>>> git tag {args}"""
293        return self.run(f"tag {args}")
294
295    def verify_commit(self, args: str = "") -> Output:
296        """>>> git verify-commit {args}"""
297        return self.run(f"verify-commit {args}")
298
299    def verify_tag(self, args: str = "") -> Output:
300        """>>> git verify-tag {args}"""
301        return self.run(f"verify-tag {args}")
302
303    def version(self, args: str = "") -> Output:
304        """>>> git version {args}"""
305        return self.run(f"version {args}")
306
307    def whatchanged(self, args: str = "") -> Output:
308        """>>> git whatchanged {args}"""
309        return self.run(f"whatchanged {args}")
310
311    def worktree(self, args: str = "") -> Output:
312        """>>> git worktree {args}"""
313        return self.run(f"worktree {args}")
314
315    # Seat |=================================================Convenience=================================================|
316
317    @property
318    def current_branch(self) -> str:
319        """Returns the name of the currently active branch."""
320        current_branch = ""
321        with self.capturing_output():
322            branches = self.branch().stdout.splitlines()
323            for branch in branches:
324                if branch.startswith("*"):
325                    current_branch = branch[2:]
326                    break
327        return current_branch
328
329    @property
330    def dob(self) -> datetime:
331        """Date of this repo's first commit."""
332        with self.capturing_output():
333            output = self.log("--pretty=format:'%cs'")
334            return datetime.strptime(output.stdout.splitlines()[-1], "%Y-%m-%d")
335
336    @property
337    def origin_url(self) -> Output:
338        """The remote origin url for this repo
339        >>> git remote get-url origin"""
340        return self.remote("get-url origin")
341
342    def add_all(self) -> Output:
343        """Stage all modified and untracked files.
344        >>> git add ."""
345        return self.add(".")
346
347    def add_files(self, files: list[Pathish]) -> Output:
348        """Stage a list of files."""
349        args = " ".join([str(file) for file in files])
350        return self.add(args)
351
352    def add_remote_url(self, url: str, name: str = "origin") -> Output:
353        """Add remote url to repo.
354        >>> git remote add {name} {url}"""
355        return self.remote(f"add {name} {url}")
356
357    def amend(self, files: list[Pathish] | None = None) -> Output:
358        """Stage and commit changes to the previous commit.
359
360        If `files` is `None`, all files will be staged.
361
362        >>> git add {files} or git add .
363        >>> git commit --amend --no-edit
364        """
365        return (self.add_files(files) if files else self.add_all()) + self.commit(
366            "--amend --no-edit"
367        )
368
369    def commit_all(self, message: str) -> Output:
370        """Stage and commit all files with `message`.
371        >>> git add .
372        >>> git commit -m "{message}" """
373        return self.add_all() + self.commit(f'-m "{message}"')
374
375    def commit_files(self, files: list[Pathish], message: str) -> Output:
376        """Commit a list of files or file patterns with commit message `message`.
377        >>> git commit {files} -m "{message}" """
378        files_arg = " ".join(str(file) for file in files)
379        return self.commit(f'{files_arg} -m "{message}"')
380
381    def create_new_branch(self, branch_name: str) -> Output:
382        """Create and switch to a new branch named with `branch_name`.
383        >>> git checkout -b {branch_name} --track"""
384        return self.checkout(f"-b {branch_name} --track")
385
386    def delete_branch(self, branch_name: str, local_only: bool = True) -> Output:
387        """Delete `branch_name` from repo.
388
389        #### :params:
390
391        `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.
392        >>> git branch --delete {branch_name}
393
394        Then if not `local_only`:
395        >>> git push origin --delete {branch_name}
396        """
397        output = self.branch(f"--delete {branch_name}")
398        if not local_only:
399            return output + self.push(f"origin --delete {branch_name}")
400        return output
401
402    def ignore(self, patterns: list[str]):
403        """Add `patterns` to `.gitignore`."""
404        gitignore = Pathier(".gitignore")
405        if not gitignore.exists():
406            gitignore.touch()
407        ignores = gitignore.split()
408        ignores += [pattern for pattern in patterns if pattern not in ignores]
409        gitignore.join(ignores)
410
411    def initcommit(self, files: list[Pathish] | None = None) -> Output:
412        """Stage and commit `files` with the message `Initial commit`.
413
414        If `files` is not given, all files will be added and committed.
415        >>> git add {files} or git add .
416        >>> git commit -m "Initial commit" """
417        return (self.add_files(files) if files else self.add_all()) + self.commit(
418            '-m "Initial commit"'
419        )
420
421    def list_branches(self) -> Output:
422        """>>> git branch -vva"""
423        return self.branch("-vva")
424
425    def loggy(self) -> Output:
426        """>>> git log --graph --abbrev-commit --name-only --pretty=tformat:'%C(auto)%h %C(green)(%cs|%cr)%C(auto)%d %C(magenta)%s'"""
427        return self.log(
428            "--graph --abbrev-commit --name-only --pretty=tformat:'%C(auto)%h %C(green)(%cs|%cr)%C(auto)%d %C(magenta)%s'"
429        )
430
431    def merge_to(self, branch: str = "main") -> Output:
432        """Merge the current branch with `branch` after switching to `branch`.
433
434        i.e. If on branch `my-feature`,
435        >>> git.merge_to()
436
437        will switch to `main` and merge `my-feature` into `main`."""
438        current_branch = self.current_branch
439        output = self.switch(branch)
440        output += self.merge(current_branch)
441        return output
442
443    def new_repo(self) -> Output:
444        """Initialize a new repo in current directory.
445        >>> git init -b main"""
446        return self.init("-b main")
447
448    def push_new_branch(self, branch: str) -> Output:
449        """Push a new branch to origin with tracking.
450        >>> git push -u origin {branch}"""
451        return self.push(f"-u origin {branch}")
452
453    def switch_branch(self, branch_name: str) -> Output:
454        """Switch to the branch specified by `branch_name`.
455        >>> git checkout {branch_name}"""
456        return self.checkout(branch_name)
457
458    def undo(self) -> Output:
459        """Undo uncommitted changes.
460        >>> git checkout ."""
461        return self.checkout(".")
462
463    def untrack(self, *paths: Pathish) -> Output:
464        """Remove any number of `paths` from the index.
465
466        Equivalent to
467        >>> git rm --cached {path}
468
469        for each path in `paths`."""
470        paths_ = [str(path) for path in paths]
471        return sum(
472            [self.rm(f"--cached {path}") for path in paths_[1:]],
473            self.rm(f"--cached {paths_[0]}"),
474        )
475
476    def rename_file(self, file: Pathish, new_name: str) -> Output:
477        """Rename `file` to `new_name` and add renaming to staging index.
478
479        `new_name` should include the file suffix.
480
481        Equivalent to renaming `old_file.py` to `new_file.py` then executing
482        >>> git add new_file.py
483        >>> git rm old_file.py"""
484        file = Pathier(file)
485        new_file = file.replace(file.with_name(new_name))
486        return self.add_files([new_file]) + self.rm(str(file))

Base class for creating python bindings for cli programs.

At a minimum, any subclass must implement a program property that returns the name used to invoke the cli.

The run function can then be used to build bindings.

>>> class Pip(Morbin):
>>>     @property
>>>     def program(self)->str:
>>>         return 'pip'
>>>
>>>     def install(self, package:str, *args:str)->Output:
>>>         return self.run("install", package, *args)
>>>
>>>     def upgrade(self, package:str)->Output:
>>>         return self.install(package, "--upgrade")
>>>
>>>     def install_requirements(self)->Output:
>>>         return self.install("-r", "requirements.txt")
>>>
>>> pip = Pip()
>>> pip.upgrade("morbin")
program: str

The name used to invoke the program from the command line.

def add(self, args: str = '') -> morbin.morbin.Output:
23    def add(self, args: str = "") -> Output:
24        """>>> git add {args}"""
25        return self.run(f"add {args}")
>>> git add {args}
def am(self, args: str = '') -> morbin.morbin.Output:
27    def am(self, args: str = "") -> Output:
28        """>>> git am {args}"""
29        return self.run(f"am {args}")
>>> git am {args}
def annotate(self, args: str = '') -> morbin.morbin.Output:
31    def annotate(self, args: str = "") -> Output:
32        """>>> git annotate {args}"""
33        return self.run(f"annotate {args}")
>>> git annotate {args}
def archive(self, args: str = '') -> morbin.morbin.Output:
35    def archive(self, args: str = "") -> Output:
36        """>>> git archive {args}"""
37        return self.run(f"archive {args}")
>>> git archive {args}
def bisect(self, args: str = '') -> morbin.morbin.Output:
39    def bisect(self, args: str = "") -> Output:
40        """>>> git bisect {args}"""
41        return self.run(f"bisect {args}")
>>> git bisect {args}
def blame(self, args: str = '') -> morbin.morbin.Output:
43    def blame(self, args: str = "") -> Output:
44        """>>> git blame {args}"""
45        return self.run(f"blame {args}")
>>> git blame {args}
def branch(self, args: str = '') -> morbin.morbin.Output:
47    def branch(self, args: str = "") -> Output:
48        """>>> git branch {args}"""
49        return self.run(f"branch {args}")
>>> git branch {args}
def bugreport(self, args: str = '') -> morbin.morbin.Output:
51    def bugreport(self, args: str = "") -> Output:
52        """>>> git bugreport {args}"""
53        return self.run(f"bugreport {args}")
>>> git bugreport {args}
def bundle(self, args: str = '') -> morbin.morbin.Output:
55    def bundle(self, args: str = "") -> Output:
56        """>>> git bundle {args}"""
57        return self.run(f"bundle {args}")
>>> git bundle {args}
def checkout(self, args: str = '') -> morbin.morbin.Output:
59    def checkout(self, args: str = "") -> Output:
60        """>>> git checkout {args}"""
61        return self.run(f"checkout {args}")
>>> git checkout {args}
def cherry_pick(self, args: str = '') -> morbin.morbin.Output:
63    def cherry_pick(self, args: str = "") -> Output:
64        """>>> git cherry-pick {args}"""
65        return self.run(f"cherry-pick {args}")
>>> git cherry-pick {args}
def citool(self, args: str = '') -> morbin.morbin.Output:
67    def citool(self, args: str = "") -> Output:
68        """>>> git citool {args}"""
69        return self.run(f"citool {args}")
>>> git citool {args}
def clean(self, args: str = '') -> morbin.morbin.Output:
71    def clean(self, args: str = "") -> Output:
72        """>>> git clean {args}"""
73        return self.run(f"clean {args}")
>>> git clean {args}
def clone(self, args: str = '') -> morbin.morbin.Output:
75    def clone(self, args: str = "") -> Output:
76        """>>> git clone {args}"""
77        return self.run(f"clone {args}")
>>> git clone {args}
def commit(self, args: str = '') -> morbin.morbin.Output:
79    def commit(self, args: str = "") -> Output:
80        """>>> git commit {args}"""
81        return self.run(f"commit {args}")
>>> git commit {args}
def config(self, args: str = '') -> morbin.morbin.Output:
83    def config(self, args: str = "") -> Output:
84        """>>> git config {args}"""
85        return self.run(f"config {args}")
>>> git config {args}
def count_objects(self, args: str = '') -> morbin.morbin.Output:
87    def count_objects(self, args: str = "") -> Output:
88        """>>> git count-objects {args}"""
89        return self.run(f"count-objects {args}")
>>> git count-objects {args}
def describe(self, args: str = '') -> morbin.morbin.Output:
91    def describe(self, args: str = "") -> Output:
92        """>>> git describe {args}"""
93        return self.run(f"describe {args}")
>>> git describe {args}
def diagnose(self, args: str = '') -> morbin.morbin.Output:
95    def diagnose(self, args: str = "") -> Output:
96        """>>> git diagnose {args}"""
97        return self.run(f"diagnose {args}")
>>> git diagnose {args}
def diff(self, args: str = '') -> morbin.morbin.Output:
 99    def diff(self, args: str = "") -> Output:
100        """>>> git diff {args}"""
101        return self.run(f"diff {args}")
>>> git diff {args}
def difftool(self, args: str = '') -> morbin.morbin.Output:
103    def difftool(self, args: str = "") -> Output:
104        """>>> git difftool {args}"""
105        return self.run(f"difftool {args}")
>>> git difftool {args}
def fast_export(self, args: str = '') -> morbin.morbin.Output:
107    def fast_export(self, args: str = "") -> Output:
108        """>>> git fast-export {args}"""
109        return self.run(f"fast-export {args}")
>>> git fast-export {args}
def fast_import(self, args: str = '') -> morbin.morbin.Output:
111    def fast_import(self, args: str = "") -> Output:
112        """>>> git fast-import {args}"""
113        return self.run(f"fast-import {args}")
>>> git fast-import {args}
def fetch(self, args: str = '') -> morbin.morbin.Output:
115    def fetch(self, args: str = "") -> Output:
116        """>>> git fetch {args}"""
117        return self.run(f"fetch {args}")
>>> git fetch {args}
def filter_branch(self, args: str = '') -> morbin.morbin.Output:
119    def filter_branch(self, args: str = "") -> Output:
120        """>>> git filter-branch {args}"""
121        return self.run(f"filter-branch {args}")
>>> git filter-branch {args}
def format_patch(self, args: str = '') -> morbin.morbin.Output:
123    def format_patch(self, args: str = "") -> Output:
124        """>>> git format-patch {args}"""
125        return self.run(f"format-patch {args}")
>>> git format-patch {args}
def fsck(self, args: str = '') -> morbin.morbin.Output:
127    def fsck(self, args: str = "") -> Output:
128        """>>> git fsck {args}"""
129        return self.run(f"fsck {args}")
>>> git fsck {args}
def gc(self, args: str = '') -> morbin.morbin.Output:
131    def gc(self, args: str = "") -> Output:
132        """>>> git gc {args}"""
133        return self.run(f"gc {args}")
>>> git gc {args}
def gitk(self, args: str = '') -> morbin.morbin.Output:
135    def gitk(self, args: str = "") -> Output:
136        """>>> git gitk {args}"""
137        return self.run(f"gitk {args}")
>>> git gitk {args}
def gitweb(self, args: str = '') -> morbin.morbin.Output:
139    def gitweb(self, args: str = "") -> Output:
140        """>>> git gitweb {args}"""
141        return self.run(f"gitweb {args}")
>>> git gitweb {args}
def grep(self, args: str = '') -> morbin.morbin.Output:
143    def grep(self, args: str = "") -> Output:
144        """>>> git grep {args}"""
145        return self.run(f"grep {args}")
>>> git grep {args}
def gui(self, args: str = '') -> morbin.morbin.Output:
147    def gui(self, args: str = "") -> Output:
148        """>>> git gui {args}"""
149        return self.run(f"gui {args}")
>>> git gui {args}
def help(self, args: str = '') -> morbin.morbin.Output:
151    def help(self, args: str = "") -> Output:
152        """>>> git help {args}"""
153        return self.run(f"help {args}")
>>> git help {args}
def init(self, args: str = '') -> morbin.morbin.Output:
155    def init(self, args: str = "") -> Output:
156        """>>> git init {args}"""
157        return self.run(f"init {args}")
>>> git init {args}
def instaweb(self, args: str = '') -> morbin.morbin.Output:
159    def instaweb(self, args: str = "") -> Output:
160        """>>> git instaweb {args}"""
161        return self.run(f"instaweb {args}")
>>> git instaweb {args}
def log(self, args: str = '') -> morbin.morbin.Output:
163    def log(self, args: str = "") -> Output:
164        """>>> git log {args}"""
165        return self.run(f"log {args}")
>>> git log {args}
def maintenance(self, args: str = '') -> morbin.morbin.Output:
167    def maintenance(self, args: str = "") -> Output:
168        """>>> git maintenance {args}"""
169        return self.run(f"maintenance {args}")
>>> git maintenance {args}
def merge(self, args: str = '') -> morbin.morbin.Output:
171    def merge(self, args: str = "") -> Output:
172        """>>> git merge {args}"""
173        return self.run(f"merge {args}")
>>> git merge {args}
def merge_tree(self, args: str = '') -> morbin.morbin.Output:
175    def merge_tree(self, args: str = "") -> Output:
176        """>>> git merge-tree {args}"""
177        return self.run(f"merge-tree {args}")
>>> git merge-tree {args}
def mergetool(self, args: str = '') -> morbin.morbin.Output:
179    def mergetool(self, args: str = "") -> Output:
180        """>>> git mergetool {args}"""
181        return self.run(f"mergetool {args}")
>>> git mergetool {args}
def mv(self, args: str = '') -> morbin.morbin.Output:
183    def mv(self, args: str = "") -> Output:
184        """>>> git mv {args}"""
185        return self.run(f"mv {args}")
>>> git mv {args}
def notes(self, args: str = '') -> morbin.morbin.Output:
187    def notes(self, args: str = "") -> Output:
188        """>>> git notes {args}"""
189        return self.run(f"notes {args}")
>>> git notes {args}
def pack_refs(self, args: str = '') -> morbin.morbin.Output:
191    def pack_refs(self, args: str = "") -> Output:
192        """>>> git pack-refs {args}"""
193        return self.run(f"pack-refs {args}")
>>> git pack-refs {args}
def prune(self, args: str = '') -> morbin.morbin.Output:
195    def prune(self, args: str = "") -> Output:
196        """>>> git prune {args}"""
197        return self.run(f"prune {args}")
>>> git prune {args}
def pull(self, args: str = '') -> morbin.morbin.Output:
199    def pull(self, args: str = "") -> Output:
200        """>>> git pull {args}"""
201        return self.run(f"pull {args}")
>>> git pull {args}
def push(self, args: str = '') -> morbin.morbin.Output:
203    def push(self, args: str = "") -> Output:
204        """>>> git push {args}"""
205        return self.run(f"push {args}")
>>> git push {args}
def range_diff(self, args: str = '') -> morbin.morbin.Output:
207    def range_diff(self, args: str = "") -> Output:
208        """>>> git range-diff {args}"""
209        return self.run(f"range-diff {args}")
>>> git range-diff {args}
def rebase(self, args: str = '') -> morbin.morbin.Output:
211    def rebase(self, args: str = "") -> Output:
212        """>>> git rebase {args}"""
213        return self.run(f"rebase {args}")
>>> git rebase {args}
def reflog(self, args: str = '') -> morbin.morbin.Output:
215    def reflog(self, args: str = "") -> Output:
216        """>>> git reflog {args}"""
217        return self.run(f"reflog {args}")
>>> git reflog {args}
def remote(self, args: str = '') -> morbin.morbin.Output:
219    def remote(self, args: str = "") -> Output:
220        """>>> git remote {args}"""
221        return self.run(f"remote {args}")
>>> git remote {args}
def repack(self, args: str = '') -> morbin.morbin.Output:
223    def repack(self, args: str = "") -> Output:
224        """>>> git repack {args}"""
225        return self.run(f"repack {args}")
>>> git repack {args}
def replace(self, args: str = '') -> morbin.morbin.Output:
227    def replace(self, args: str = "") -> Output:
228        """>>> git replace {args}"""
229        return self.run(f"replace {args}")
>>> git replace {args}
def request_pull(self, args: str = '') -> morbin.morbin.Output:
231    def request_pull(self, args: str = "") -> Output:
232        """>>> git request-pull {args}"""
233        return self.run(f"request-pull {args}")
>>> git request-pull {args}
def rerere(self, args: str = '') -> morbin.morbin.Output:
235    def rerere(self, args: str = "") -> Output:
236        """>>> git rerere {args}"""
237        return self.run(f"rerere {args}")
>>> git rerere {args}
def reset(self, args: str = '') -> morbin.morbin.Output:
239    def reset(self, args: str = "") -> Output:
240        """>>> git reset {args}"""
241        return self.run(f"reset {args}")
>>> git reset {args}
def restore(self, args: str = '') -> morbin.morbin.Output:
243    def restore(self, args: str = "") -> Output:
244        """>>> git restore {args}"""
245        return self.run(f"restore {args}")
>>> git restore {args}
def revert(self, args: str = '') -> morbin.morbin.Output:
247    def revert(self, args: str = "") -> Output:
248        """>>> git revert {args}"""
249        return self.run(f"revert {args}")
>>> git revert {args}
def rm(self, args: str = '') -> morbin.morbin.Output:
251    def rm(self, args: str = "") -> Output:
252        """>>> git rm {args}"""
253        return self.run(f"rm {args}")
>>> git rm {args}
def scalar(self, args: str = '') -> morbin.morbin.Output:
255    def scalar(self, args: str = "") -> Output:
256        """>>> git scalar {args}"""
257        return self.run(f"scalar {args}")
>>> git scalar {args}
def shortlog(self, args: str = '') -> morbin.morbin.Output:
259    def shortlog(self, args: str = "") -> Output:
260        """>>> git shortlog {args}"""
261        return self.run(f"shortlog {args}")
>>> git shortlog {args}
def show(self, args: str = '') -> morbin.morbin.Output:
263    def show(self, args: str = "") -> Output:
264        """>>> git show {args}"""
265        return self.run(f"show {args}")
>>> git show {args}
def show_branch(self, args: str = '') -> morbin.morbin.Output:
267    def show_branch(self, args: str = "") -> Output:
268        """>>> git show-branch {args}"""
269        return self.run(f"show-branch {args}")
>>> git show-branch {args}
def sparse_checkout(self, args: str = '') -> morbin.morbin.Output:
271    def sparse_checkout(self, args: str = "") -> Output:
272        """>>> git sparse-checkout {args}"""
273        return self.run(f"sparse-checkout {args}")
>>> git sparse-checkout {args}
def stash(self, args: str = '') -> morbin.morbin.Output:
275    def stash(self, args: str = "") -> Output:
276        """>>> git stash {args}"""
277        return self.run(f"stash {args}")
>>> git stash {args}
def status(self, args: str = '') -> morbin.morbin.Output:
279    def status(self, args: str = "") -> Output:
280        """>>> git status {args}"""
281        return self.run(f"status {args}")
>>> git status {args}
def submodule(self, args: str = '') -> morbin.morbin.Output:
283    def submodule(self, args: str = "") -> Output:
284        """>>> git submodule {args}"""
285        return self.run(f"submodule {args}")
>>> git submodule {args}
def switch(self, args: str = '') -> morbin.morbin.Output:
287    def switch(self, args: str = "") -> Output:
288        """>>> git switch {args}"""
289        return self.run(f"switch {args}")
>>> git switch {args}
def tag(self, args: str = '') -> morbin.morbin.Output:
291    def tag(self, args: str = "") -> Output:
292        """>>> git tag {args}"""
293        return self.run(f"tag {args}")
>>> git tag {args}
def verify_commit(self, args: str = '') -> morbin.morbin.Output:
295    def verify_commit(self, args: str = "") -> Output:
296        """>>> git verify-commit {args}"""
297        return self.run(f"verify-commit {args}")
>>> git verify-commit {args}
def verify_tag(self, args: str = '') -> morbin.morbin.Output:
299    def verify_tag(self, args: str = "") -> Output:
300        """>>> git verify-tag {args}"""
301        return self.run(f"verify-tag {args}")
>>> git verify-tag {args}
def version(self, args: str = '') -> morbin.morbin.Output:
303    def version(self, args: str = "") -> Output:
304        """>>> git version {args}"""
305        return self.run(f"version {args}")
>>> git version {args}
def whatchanged(self, args: str = '') -> morbin.morbin.Output:
307    def whatchanged(self, args: str = "") -> Output:
308        """>>> git whatchanged {args}"""
309        return self.run(f"whatchanged {args}")
>>> git whatchanged {args}
def worktree(self, args: str = '') -> morbin.morbin.Output:
311    def worktree(self, args: str = "") -> Output:
312        """>>> git worktree {args}"""
313        return self.run(f"worktree {args}")
>>> git worktree {args}
current_branch: str

Returns the name of the currently active branch.

dob: datetime.datetime

Date of this repo's first commit.

origin_url: morbin.morbin.Output

The remote origin url for this repo

>>> git remote get-url origin
def add_all(self) -> morbin.morbin.Output:
342    def add_all(self) -> Output:
343        """Stage all modified and untracked files.
344        >>> git add ."""
345        return self.add(".")

Stage all modified and untracked files.

>>> git add .
def add_files( self, files: list[pathier.pathier.Pathier | pathlib.Path | str]) -> morbin.morbin.Output:
347    def add_files(self, files: list[Pathish]) -> Output:
348        """Stage a list of files."""
349        args = " ".join([str(file) for file in files])
350        return self.add(args)

Stage a list of files.

def add_remote_url(self, url: str, name: str = 'origin') -> morbin.morbin.Output:
352    def add_remote_url(self, url: str, name: str = "origin") -> Output:
353        """Add remote url to repo.
354        >>> git remote add {name} {url}"""
355        return self.remote(f"add {name} {url}")

Add remote url to repo.

>>> git remote add {name} {url}
def amend( self, files: list[pathier.pathier.Pathier | pathlib.Path | str] | None = None) -> morbin.morbin.Output:
357    def amend(self, files: list[Pathish] | None = None) -> Output:
358        """Stage and commit changes to the previous commit.
359
360        If `files` is `None`, all files will be staged.
361
362        >>> git add {files} or git add .
363        >>> git commit --amend --no-edit
364        """
365        return (self.add_files(files) if files else self.add_all()) + self.commit(
366            "--amend --no-edit"
367        )

Stage and commit changes to the previous commit.

If files is None, all files will be staged.

>>> git add {files} or git add .
>>> git commit --amend --no-edit
def commit_all(self, message: str) -> morbin.morbin.Output:
369    def commit_all(self, message: str) -> Output:
370        """Stage and commit all files with `message`.
371        >>> git add .
372        >>> git commit -m "{message}" """
373        return self.add_all() + self.commit(f'-m "{message}"')

Stage and commit all files with message.

>>> git add .
>>> git commit -m "{message}"
def commit_files( self, files: list[pathier.pathier.Pathier | pathlib.Path | str], message: str) -> morbin.morbin.Output:
375    def commit_files(self, files: list[Pathish], message: str) -> Output:
376        """Commit a list of files or file patterns with commit message `message`.
377        >>> git commit {files} -m "{message}" """
378        files_arg = " ".join(str(file) for file in files)
379        return self.commit(f'{files_arg} -m "{message}"')

Commit a list of files or file patterns with commit message message.

>>> git commit {files} -m "{message}"
def create_new_branch(self, branch_name: str) -> morbin.morbin.Output:
381    def create_new_branch(self, branch_name: str) -> Output:
382        """Create and switch to a new branch named with `branch_name`.
383        >>> git checkout -b {branch_name} --track"""
384        return self.checkout(f"-b {branch_name} --track")

Create and switch to a new branch named with branch_name.

>>> git checkout -b {branch_name} --track
def delete_branch(self, branch_name: str, local_only: bool = True) -> morbin.morbin.Output:
386    def delete_branch(self, branch_name: str, local_only: bool = True) -> Output:
387        """Delete `branch_name` from repo.
388
389        #### :params:
390
391        `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.
392        >>> git branch --delete {branch_name}
393
394        Then if not `local_only`:
395        >>> git push origin --delete {branch_name}
396        """
397        output = self.branch(f"--delete {branch_name}")
398        if not local_only:
399            return output + self.push(f"origin --delete {branch_name}")
400        return output

Delete branch_name from repo.

:params:

local_only: Only delete the local copy of branch, otherwise also delete the remote branch on origin and remote-tracking branch.

>>> git branch --delete {branch_name}

Then if not local_only:

>>> git push origin --delete {branch_name}
def ignore(self, patterns: list[str]):
402    def ignore(self, patterns: list[str]):
403        """Add `patterns` to `.gitignore`."""
404        gitignore = Pathier(".gitignore")
405        if not gitignore.exists():
406            gitignore.touch()
407        ignores = gitignore.split()
408        ignores += [pattern for pattern in patterns if pattern not in ignores]
409        gitignore.join(ignores)

Add patterns to .gitignore.

def initcommit( self, files: list[pathier.pathier.Pathier | pathlib.Path | str] | None = None) -> morbin.morbin.Output:
411    def initcommit(self, files: list[Pathish] | None = None) -> Output:
412        """Stage and commit `files` with the message `Initial commit`.
413
414        If `files` is not given, all files will be added and committed.
415        >>> git add {files} or git add .
416        >>> git commit -m "Initial commit" """
417        return (self.add_files(files) if files else self.add_all()) + self.commit(
418            '-m "Initial commit"'
419        )

Stage and commit files with the message Initial commit.

If files is not given, all files will be added and committed.

>>> git add {files} or git add .
>>> git commit -m "Initial commit"
def list_branches(self) -> morbin.morbin.Output:
421    def list_branches(self) -> Output:
422        """>>> git branch -vva"""
423        return self.branch("-vva")
>>> git branch -vva
def loggy(self) -> morbin.morbin.Output:
425    def loggy(self) -> Output:
426        """>>> git log --graph --abbrev-commit --name-only --pretty=tformat:'%C(auto)%h %C(green)(%cs|%cr)%C(auto)%d %C(magenta)%s'"""
427        return self.log(
428            "--graph --abbrev-commit --name-only --pretty=tformat:'%C(auto)%h %C(green)(%cs|%cr)%C(auto)%d %C(magenta)%s'"
429        )
>>> git log --graph --abbrev-commit --name-only --pretty=tformat:'%C(auto)%h %C(green)(%cs|%cr)%C(auto)%d %C(magenta)%s'
def merge_to(self, branch: str = 'main') -> morbin.morbin.Output:
431    def merge_to(self, branch: str = "main") -> Output:
432        """Merge the current branch with `branch` after switching to `branch`.
433
434        i.e. If on branch `my-feature`,
435        >>> git.merge_to()
436
437        will switch to `main` and merge `my-feature` into `main`."""
438        current_branch = self.current_branch
439        output = self.switch(branch)
440        output += self.merge(current_branch)
441        return output

Merge the current branch with branch after switching to branch.

i.e. If on branch my-feature,

>>> git.merge_to()

will switch to main and merge my-feature into main.

def new_repo(self) -> morbin.morbin.Output:
443    def new_repo(self) -> Output:
444        """Initialize a new repo in current directory.
445        >>> git init -b main"""
446        return self.init("-b main")

Initialize a new repo in current directory.

>>> git init -b main
def push_new_branch(self, branch: str) -> morbin.morbin.Output:
448    def push_new_branch(self, branch: str) -> Output:
449        """Push a new branch to origin with tracking.
450        >>> git push -u origin {branch}"""
451        return self.push(f"-u origin {branch}")

Push a new branch to origin with tracking.

>>> git push -u origin {branch}
def switch_branch(self, branch_name: str) -> morbin.morbin.Output:
453    def switch_branch(self, branch_name: str) -> Output:
454        """Switch to the branch specified by `branch_name`.
455        >>> git checkout {branch_name}"""
456        return self.checkout(branch_name)

Switch to the branch specified by branch_name.

>>> git checkout {branch_name}
def undo(self) -> morbin.morbin.Output:
458    def undo(self) -> Output:
459        """Undo uncommitted changes.
460        >>> git checkout ."""
461        return self.checkout(".")

Undo uncommitted changes.

>>> git checkout .
def untrack( self, *paths: pathier.pathier.Pathier | pathlib.Path | str) -> morbin.morbin.Output:
463    def untrack(self, *paths: Pathish) -> Output:
464        """Remove any number of `paths` from the index.
465
466        Equivalent to
467        >>> git rm --cached {path}
468
469        for each path in `paths`."""
470        paths_ = [str(path) for path in paths]
471        return sum(
472            [self.rm(f"--cached {path}") for path in paths_[1:]],
473            self.rm(f"--cached {paths_[0]}"),
474        )

Remove any number of paths from the index.

Equivalent to

>>> git rm --cached {path}

for each path in paths.

def rename_file( self, file: pathier.pathier.Pathier | pathlib.Path | str, new_name: str) -> morbin.morbin.Output:
476    def rename_file(self, file: Pathish, new_name: str) -> Output:
477        """Rename `file` to `new_name` and add renaming to staging index.
478
479        `new_name` should include the file suffix.
480
481        Equivalent to renaming `old_file.py` to `new_file.py` then executing
482        >>> git add new_file.py
483        >>> git rm old_file.py"""
484        file = Pathier(file)
485        new_file = file.replace(file.with_name(new_name))
486        return self.add_files([new_file]) + self.rm(str(file))

Rename file to new_name and add renaming to staging index.

new_name should include the file suffix.

Equivalent to renaming old_file.py to new_file.py then executing

>>> git add new_file.py
>>> git rm old_file.py
Inherited Members
morbin.morbin.Morbin
Morbin
capture_output
shell
capturing_output
run
class GitHub(morbin.morbin.Morbin):
490class GitHub(Morbin):
491    @property
492    def program(self) -> str:
493        return "gh"
494
495    @property
496    def owner(self) -> str:
497        return self._owner_reponame().split("/")[0]
498
499    @property
500    def repo_name(self) -> str:
501        return self._owner_reponame().split("/")[1]
502
503    def _change_visibility(self, visibility: str) -> Output:
504        return self.run(
505            f"repo edit {self.owner}/{self.repo_name} --visibility {visibility}"
506        )
507
508    def _owner_reponame(self) -> str:
509        """Returns "owner/repo-name", assuming there's one remote origin url and it's for github."""
510        git = Git()
511        with git.capturing_output():
512            return urlparse(git.origin_url.stdout.strip("\n")).path.strip("/")
513
514    def create_remote(self, name: str, public: bool = False) -> Output:
515        """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo.
516
517        #### :params:
518
519        `name`: The name for the repo.
520
521        `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.
522        """
523        visibility = "--public" if public else "--private"
524        return self.run(f"repo create {name} {visibility}")
525
526    def create_remote_from_cwd(self, public: bool = False) -> Output:
527        """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from
528        the current working directory repo and add its url as this repo's remote origin.
529
530        #### :params:
531
532        `public`: Create the GitHub repo as a public repo, default is to create it as private.
533        """
534        visibility = "--public" if public else "--private"
535        return self.run(f"repo create --source . {visibility} --push")
536
537    def delete_remote(self) -> Output:
538        """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo."""
539        return self.run(f"repo delete {self.owner}/{self.repo_name} --yes")
540
541    def make_private(self) -> Output:
542        """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private."""
543        return self._change_visibility("private")
544
545    def make_public(self) -> Output:
546        """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public."""
547        return self._change_visibility("public")

Base class for creating python bindings for cli programs.

At a minimum, any subclass must implement a program property that returns the name used to invoke the cli.

The run function can then be used to build bindings.

>>> class Pip(Morbin):
>>>     @property
>>>     def program(self)->str:
>>>         return 'pip'
>>>
>>>     def install(self, package:str, *args:str)->Output:
>>>         return self.run("install", package, *args)
>>>
>>>     def upgrade(self, package:str)->Output:
>>>         return self.install(package, "--upgrade")
>>>
>>>     def install_requirements(self)->Output:
>>>         return self.install("-r", "requirements.txt")
>>>
>>> pip = Pip()
>>> pip.upgrade("morbin")
program: str

The name used to invoke the program from the command line.

def create_remote(self, name: str, public: bool = False) -> morbin.morbin.Output:
514    def create_remote(self, name: str, public: bool = False) -> Output:
515        """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo.
516
517        #### :params:
518
519        `name`: The name for the repo.
520
521        `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.
522        """
523        visibility = "--public" if public else "--private"
524        return self.run(f"repo create {name} {visibility}")

Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo.

:params:

name: The name for the repo.

public: Set to True to create the repo as public, otherwise it'll be created as private.

def create_remote_from_cwd(self, public: bool = False) -> morbin.morbin.Output:
526    def create_remote_from_cwd(self, public: bool = False) -> Output:
527        """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from
528        the current working directory repo and add its url as this repo's remote origin.
529
530        #### :params:
531
532        `public`: Create the GitHub repo as a public repo, default is to create it as private.
533        """
534        visibility = "--public" if public else "--private"
535        return self.run(f"repo create --source . {visibility} --push")

Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from the current working directory repo and add its url as this repo's remote origin.

:params:

public: Create the GitHub repo as a public repo, default is to create it as private.

def delete_remote(self) -> morbin.morbin.Output:
537    def delete_remote(self) -> Output:
538        """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo."""
539        return self.run(f"repo delete {self.owner}/{self.repo_name} --yes")

Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo.

def make_private(self) -> morbin.morbin.Output:
541    def make_private(self) -> Output:
542        """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private."""
543        return self._change_visibility("private")

Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private.

def make_public(self) -> morbin.morbin.Output:
545    def make_public(self) -> Output:
546        """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public."""
547        return self._change_visibility("public")

Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public.

Inherited Members
morbin.morbin.Morbin
Morbin
capture_output
shell
capturing_output
run