gitbetter.git
1import shlex 2import subprocess 3 4 5class Git: 6 def __init__(self, capture_stdout: bool = False): 7 """If `capture_stdout` is `True`, all functions will return their generated `stdout` as a string. 8 Otherwise, the functions return the call's exit code.""" 9 self.capture_stdout = capture_stdout 10 11 @property 12 def capture_stdout(self) -> bool: 13 """If `True`, member functions will return the generated `stdout` as a string, 14 otherwise they return the command's exit code.""" 15 return self._capture_stdout 16 17 @capture_stdout.setter 18 def capture_stdout(self, should_capture: bool): 19 self._capture_stdout = should_capture 20 21 def _run(self, args: list[str]) -> str | int: 22 if self._capture_stdout: 23 return subprocess.run(args, stdout=subprocess.PIPE, text=True).stdout 24 else: 25 return subprocess.run(args).returncode 26 27 def execute(self, command: str) -> str | int: 28 """Execute git command. 29 30 Equivalent to executing `git {command}` in the shell.""" 31 args = ["git"] + shlex.split(command) 32 return self._run(args) 33 34 def new_repo(self) -> str | int: 35 """Executes `git init -b main`.""" 36 return self.execute("init -b main") 37 38 def loggy(self) -> str | int: 39 """Equivalent to `git log --oneline --name-only --abbrev-commit --graph`.""" 40 return self.execute("log --oneline --name-only --abbrev-commit --graph") 41 42 def status(self) -> str | int: 43 """Execute `git status`.""" 44 return self.execute("status") 45 46 # ======================================Staging/Committing====================================== 47 def commit(self, args: str) -> str | int: 48 """>>> git commit {args}""" 49 return self.execute(f"commit {args}") 50 51 def add(self, files: list[str] | None = None) -> str | int: 52 """Stage a list of files. 53 54 If no files are given (`files=None`), all files will be staged.""" 55 if not files: 56 return self.execute("add .") 57 else: 58 files = " ".join(f'"{file}"' for file in files) # type: ignore 59 return self.execute(f"add {files}") 60 61 def commit_files(self, files: list[str], message: str) -> str | int: 62 """Stage and commit a list of files with commit message `message`.""" 63 return self.add(files) + self.commit(f'-m "{message}"') # type: ignore 64 65 def initcommit(self) -> str | int: 66 """Equivalent to 67 >>> git add . 68 >>> git commit -m "Initial commit" """ 69 return self.add() + self.commit('-m "Initial commit"') # type: ignore 70 71 def amend(self, files: list[str] | None = None) -> str | int: 72 """Stage and commit changes to the previous commit. 73 74 If `files` is `None`, all files will be staged. 75 76 Equivalent to: 77 >>> git add {files} 78 >>> git commit --amend --no-edit 79 """ 80 return self.add(files) + self.commit("--amend --no-edit") # type: ignore 81 82 def tag(self, args: str = "") -> str | int: 83 """Execute the `tag` command with `args`. 84 85 e.g. 86 87 `self.tag("--sort=-committerdate")` 88 89 will list all the tags for this repository in descending commit date.""" 90 return self.execute(f"tag {args}") 91 92 # ==========================================Push/Pull========================================== 93 def add_remote_url(self, url: str, name: str = "origin") -> str | int: 94 """Add remote url to repo.""" 95 return self.execute(f"remote add {name} {url}") 96 97 def push(self, args: str = "") -> str | int: 98 """Equivalent to `git push {args}`.""" 99 return self.execute(f"push {args}") 100 101 def pull(self, args: str = "") -> str | int: 102 """Equivalent to `git pull {args}`.""" 103 return self.execute(f"pull {args}") 104 105 def push_new_branch(self, branch: str) -> str | int: 106 """Push a new branch to origin with tracking. 107 108 Equivalent to `git push -u origin {branch}`.""" 109 return self.push(f"-u origin {branch}") 110 111 def pull_branch(self, branch: str) -> str | int: 112 """Pull `branch` from origin.""" 113 return self.pull(f"origin {branch}") 114 115 # ============================================Checkout/Branches============================================ 116 def branch(self, args: str) -> str | int: 117 """Equivalent to `git branch {args}`.""" 118 return self.execute(f"branch {args}") 119 120 def list_branches(self) -> str | int: 121 """Print a list of branches.""" 122 return self.branch("-vva") 123 124 def checkout(self, args: str) -> str | int: 125 """Equivalent to `git checkout {args}`.""" 126 return self.execute(f"checkout {args}") 127 128 def switch_branch(self, branch_name: str) -> str | int: 129 """Switch to the branch specified by `branch_name`. 130 131 Equivalent to `git checkout {branch_name}`.""" 132 return self.checkout(branch_name) 133 134 def create_new_branch(self, branch_name: str) -> str | int: 135 """Create and switch to a new branch named with `branch_name`. 136 137 Equivalent to `git checkout -b {branch_name} --track`.""" 138 return self.checkout(f"-b {branch_name} --track") 139 140 def delete_branch(self, branch_name: str, local_only: bool = True) -> str | int: 141 """Delete `branch_name` from repo. 142 143 #### :params: 144 145 `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.""" 146 output = self.branch(f"--delete {branch_name}") 147 if not local_only: 148 return output + self.push(f"origin --delete {branch_name}") # type:ignore 149 return output 150 151 def undo(self) -> str | int: 152 """Undo uncommitted changes. 153 154 Equivalent to `git checkout .`.""" 155 return self.checkout(".") 156 157 def merge(self, branch_name: str) -> str | int: 158 """Merge branch `branch_name` with currently active branch.""" 159 return self.execute(f"merge {branch_name}") 160 161 # ===============================Requires GitHub CLI to be installed and configured=============================== 162 163 def create_remote(self, name: str, public: bool = False) -> str | int: 164 """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo. 165 166 #### :params: 167 168 `name`: The name for the repo. 169 170 `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.""" 171 visibility = "--public" if public else "--private" 172 return self._run(["gh", "repo", "create", name, visibility]) 173 174 def create_remote_from_cwd(self, public: bool = False) -> str | int: 175 """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from 176 the current working directory repo and add its url as this repo's remote origin. 177 178 #### :params: 179 180 `public`: Create the GitHub repo as a public repo, default is to create it as private.""" 181 visibility = "public" if public else "private" 182 return self._run( 183 ["gh", "repo", "create", "--source", ".", f"--{visibility}", "--push"] 184 ) 185 186 def _change_visibility(self, owner: str, name: str, visibility: str) -> str | int: 187 return self._run( 188 ["gh", "repo", "edit", f"{owner}/{name}", "--visibility", visibility] 189 ) 190 191 def make_private(self, owner: str, name: str) -> str | int: 192 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private. 193 194 #### :params: 195 196 `owner`: The repo owner. 197 198 `name`: The name of the repo to edit.""" 199 return self._change_visibility(owner, name, "private") 200 201 def make_public(self, owner: str, name: str) -> str | int: 202 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public. 203 204 #### :params: 205 206 `owner`: The repo owner. 207 208 `name`: The name of the repo to edit.""" 209 return self._change_visibility(owner, name, "public") 210 211 def delete_remote(self, owner: str, name: str) -> str | int: 212 """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo. 213 214 #### :params: 215 216 `owner`: The repo owner. 217 218 `name`: The name of the remote repo to delete.""" 219 return self._run(["gh", "repo", "delete", f"{owner}/{name}", "--yes"])
6class Git: 7 def __init__(self, capture_stdout: bool = False): 8 """If `capture_stdout` is `True`, all functions will return their generated `stdout` as a string. 9 Otherwise, the functions return the call's exit code.""" 10 self.capture_stdout = capture_stdout 11 12 @property 13 def capture_stdout(self) -> bool: 14 """If `True`, member functions will return the generated `stdout` as a string, 15 otherwise they return the command's exit code.""" 16 return self._capture_stdout 17 18 @capture_stdout.setter 19 def capture_stdout(self, should_capture: bool): 20 self._capture_stdout = should_capture 21 22 def _run(self, args: list[str]) -> str | int: 23 if self._capture_stdout: 24 return subprocess.run(args, stdout=subprocess.PIPE, text=True).stdout 25 else: 26 return subprocess.run(args).returncode 27 28 def execute(self, command: str) -> str | int: 29 """Execute git command. 30 31 Equivalent to executing `git {command}` in the shell.""" 32 args = ["git"] + shlex.split(command) 33 return self._run(args) 34 35 def new_repo(self) -> str | int: 36 """Executes `git init -b main`.""" 37 return self.execute("init -b main") 38 39 def loggy(self) -> str | int: 40 """Equivalent to `git log --oneline --name-only --abbrev-commit --graph`.""" 41 return self.execute("log --oneline --name-only --abbrev-commit --graph") 42 43 def status(self) -> str | int: 44 """Execute `git status`.""" 45 return self.execute("status") 46 47 # ======================================Staging/Committing====================================== 48 def commit(self, args: str) -> str | int: 49 """>>> git commit {args}""" 50 return self.execute(f"commit {args}") 51 52 def add(self, files: list[str] | None = None) -> str | int: 53 """Stage a list of files. 54 55 If no files are given (`files=None`), all files will be staged.""" 56 if not files: 57 return self.execute("add .") 58 else: 59 files = " ".join(f'"{file}"' for file in files) # type: ignore 60 return self.execute(f"add {files}") 61 62 def commit_files(self, files: list[str], message: str) -> str | int: 63 """Stage and commit a list of files with commit message `message`.""" 64 return self.add(files) + self.commit(f'-m "{message}"') # type: ignore 65 66 def initcommit(self) -> str | int: 67 """Equivalent to 68 >>> git add . 69 >>> git commit -m "Initial commit" """ 70 return self.add() + self.commit('-m "Initial commit"') # type: ignore 71 72 def amend(self, files: list[str] | None = None) -> str | int: 73 """Stage and commit changes to the previous commit. 74 75 If `files` is `None`, all files will be staged. 76 77 Equivalent to: 78 >>> git add {files} 79 >>> git commit --amend --no-edit 80 """ 81 return self.add(files) + self.commit("--amend --no-edit") # type: ignore 82 83 def tag(self, args: str = "") -> str | int: 84 """Execute the `tag` command with `args`. 85 86 e.g. 87 88 `self.tag("--sort=-committerdate")` 89 90 will list all the tags for this repository in descending commit date.""" 91 return self.execute(f"tag {args}") 92 93 # ==========================================Push/Pull========================================== 94 def add_remote_url(self, url: str, name: str = "origin") -> str | int: 95 """Add remote url to repo.""" 96 return self.execute(f"remote add {name} {url}") 97 98 def push(self, args: str = "") -> str | int: 99 """Equivalent to `git push {args}`.""" 100 return self.execute(f"push {args}") 101 102 def pull(self, args: str = "") -> str | int: 103 """Equivalent to `git pull {args}`.""" 104 return self.execute(f"pull {args}") 105 106 def push_new_branch(self, branch: str) -> str | int: 107 """Push a new branch to origin with tracking. 108 109 Equivalent to `git push -u origin {branch}`.""" 110 return self.push(f"-u origin {branch}") 111 112 def pull_branch(self, branch: str) -> str | int: 113 """Pull `branch` from origin.""" 114 return self.pull(f"origin {branch}") 115 116 # ============================================Checkout/Branches============================================ 117 def branch(self, args: str) -> str | int: 118 """Equivalent to `git branch {args}`.""" 119 return self.execute(f"branch {args}") 120 121 def list_branches(self) -> str | int: 122 """Print a list of branches.""" 123 return self.branch("-vva") 124 125 def checkout(self, args: str) -> str | int: 126 """Equivalent to `git checkout {args}`.""" 127 return self.execute(f"checkout {args}") 128 129 def switch_branch(self, branch_name: str) -> str | int: 130 """Switch to the branch specified by `branch_name`. 131 132 Equivalent to `git checkout {branch_name}`.""" 133 return self.checkout(branch_name) 134 135 def create_new_branch(self, branch_name: str) -> str | int: 136 """Create and switch to a new branch named with `branch_name`. 137 138 Equivalent to `git checkout -b {branch_name} --track`.""" 139 return self.checkout(f"-b {branch_name} --track") 140 141 def delete_branch(self, branch_name: str, local_only: bool = True) -> str | int: 142 """Delete `branch_name` from repo. 143 144 #### :params: 145 146 `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.""" 147 output = self.branch(f"--delete {branch_name}") 148 if not local_only: 149 return output + self.push(f"origin --delete {branch_name}") # type:ignore 150 return output 151 152 def undo(self) -> str | int: 153 """Undo uncommitted changes. 154 155 Equivalent to `git checkout .`.""" 156 return self.checkout(".") 157 158 def merge(self, branch_name: str) -> str | int: 159 """Merge branch `branch_name` with currently active branch.""" 160 return self.execute(f"merge {branch_name}") 161 162 # ===============================Requires GitHub CLI to be installed and configured=============================== 163 164 def create_remote(self, name: str, public: bool = False) -> str | int: 165 """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo. 166 167 #### :params: 168 169 `name`: The name for the repo. 170 171 `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.""" 172 visibility = "--public" if public else "--private" 173 return self._run(["gh", "repo", "create", name, visibility]) 174 175 def create_remote_from_cwd(self, public: bool = False) -> str | int: 176 """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from 177 the current working directory repo and add its url as this repo's remote origin. 178 179 #### :params: 180 181 `public`: Create the GitHub repo as a public repo, default is to create it as private.""" 182 visibility = "public" if public else "private" 183 return self._run( 184 ["gh", "repo", "create", "--source", ".", f"--{visibility}", "--push"] 185 ) 186 187 def _change_visibility(self, owner: str, name: str, visibility: str) -> str | int: 188 return self._run( 189 ["gh", "repo", "edit", f"{owner}/{name}", "--visibility", visibility] 190 ) 191 192 def make_private(self, owner: str, name: str) -> str | int: 193 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private. 194 195 #### :params: 196 197 `owner`: The repo owner. 198 199 `name`: The name of the repo to edit.""" 200 return self._change_visibility(owner, name, "private") 201 202 def make_public(self, owner: str, name: str) -> str | int: 203 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public. 204 205 #### :params: 206 207 `owner`: The repo owner. 208 209 `name`: The name of the repo to edit.""" 210 return self._change_visibility(owner, name, "public") 211 212 def delete_remote(self, owner: str, name: str) -> str | int: 213 """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo. 214 215 #### :params: 216 217 `owner`: The repo owner. 218 219 `name`: The name of the remote repo to delete.""" 220 return self._run(["gh", "repo", "delete", f"{owner}/{name}", "--yes"])
7 def __init__(self, capture_stdout: bool = False): 8 """If `capture_stdout` is `True`, all functions will return their generated `stdout` as a string. 9 Otherwise, the functions return the call's exit code.""" 10 self.capture_stdout = capture_stdout
If capture_stdout
is True
, all functions will return their generated stdout
as a string.
Otherwise, the functions return the call's exit code.
If True
, member functions will return the generated stdout
as a string,
otherwise they return the command's exit code.
28 def execute(self, command: str) -> str | int: 29 """Execute git command. 30 31 Equivalent to executing `git {command}` in the shell.""" 32 args = ["git"] + shlex.split(command) 33 return self._run(args)
Execute git command.
Equivalent to executing git {command}
in the shell.
35 def new_repo(self) -> str | int: 36 """Executes `git init -b main`.""" 37 return self.execute("init -b main")
Executes git init -b main
.
39 def loggy(self) -> str | int: 40 """Equivalent to `git log --oneline --name-only --abbrev-commit --graph`.""" 41 return self.execute("log --oneline --name-only --abbrev-commit --graph")
Equivalent to git log --oneline --name-only --abbrev-commit --graph
.
48 def commit(self, args: str) -> str | int: 49 """>>> git commit {args}""" 50 return self.execute(f"commit {args}")
>>> git commit {args}
52 def add(self, files: list[str] | None = None) -> str | int: 53 """Stage a list of files. 54 55 If no files are given (`files=None`), all files will be staged.""" 56 if not files: 57 return self.execute("add .") 58 else: 59 files = " ".join(f'"{file}"' for file in files) # type: ignore 60 return self.execute(f"add {files}")
Stage a list of files.
If no files are given (files=None
), all files will be staged.
62 def commit_files(self, files: list[str], message: str) -> str | int: 63 """Stage and commit a list of files with commit message `message`.""" 64 return self.add(files) + self.commit(f'-m "{message}"') # type: ignore
Stage and commit a list of files with commit message message
.
66 def initcommit(self) -> str | int: 67 """Equivalent to 68 >>> git add . 69 >>> git commit -m "Initial commit" """ 70 return self.add() + self.commit('-m "Initial commit"') # type: ignore
Equivalent to
>>> git add .
>>> git commit -m "Initial commit"
72 def amend(self, files: list[str] | None = None) -> str | int: 73 """Stage and commit changes to the previous commit. 74 75 If `files` is `None`, all files will be staged. 76 77 Equivalent to: 78 >>> git add {files} 79 >>> git commit --amend --no-edit 80 """ 81 return self.add(files) + self.commit("--amend --no-edit") # type: ignore
Stage and commit changes to the previous commit.
If files
is None
, all files will be staged.
Equivalent to:
>>> git add {files}
>>> git commit --amend --no-edit
83 def tag(self, args: str = "") -> str | int: 84 """Execute the `tag` command with `args`. 85 86 e.g. 87 88 `self.tag("--sort=-committerdate")` 89 90 will list all the tags for this repository in descending commit date.""" 91 return self.execute(f"tag {args}")
Execute the tag
command with args
.
e.g.
self.tag("--sort=-committerdate")
will list all the tags for this repository in descending commit date.
94 def add_remote_url(self, url: str, name: str = "origin") -> str | int: 95 """Add remote url to repo.""" 96 return self.execute(f"remote add {name} {url}")
Add remote url to repo.
98 def push(self, args: str = "") -> str | int: 99 """Equivalent to `git push {args}`.""" 100 return self.execute(f"push {args}")
Equivalent to git push {args}
.
102 def pull(self, args: str = "") -> str | int: 103 """Equivalent to `git pull {args}`.""" 104 return self.execute(f"pull {args}")
Equivalent to git pull {args}
.
106 def push_new_branch(self, branch: str) -> str | int: 107 """Push a new branch to origin with tracking. 108 109 Equivalent to `git push -u origin {branch}`.""" 110 return self.push(f"-u origin {branch}")
Push a new branch to origin with tracking.
Equivalent to git push -u origin {branch}
.
112 def pull_branch(self, branch: str) -> str | int: 113 """Pull `branch` from origin.""" 114 return self.pull(f"origin {branch}")
Pull branch
from origin.
117 def branch(self, args: str) -> str | int: 118 """Equivalent to `git branch {args}`.""" 119 return self.execute(f"branch {args}")
Equivalent to git branch {args}
.
121 def list_branches(self) -> str | int: 122 """Print a list of branches.""" 123 return self.branch("-vva")
Print a list of branches.
125 def checkout(self, args: str) -> str | int: 126 """Equivalent to `git checkout {args}`.""" 127 return self.execute(f"checkout {args}")
Equivalent to git checkout {args}
.
129 def switch_branch(self, branch_name: str) -> str | int: 130 """Switch to the branch specified by `branch_name`. 131 132 Equivalent to `git checkout {branch_name}`.""" 133 return self.checkout(branch_name)
Switch to the branch specified by branch_name
.
Equivalent to git checkout {branch_name}
.
135 def create_new_branch(self, branch_name: str) -> str | int: 136 """Create and switch to a new branch named with `branch_name`. 137 138 Equivalent to `git checkout -b {branch_name} --track`.""" 139 return self.checkout(f"-b {branch_name} --track")
Create and switch to a new branch named with branch_name
.
Equivalent to git checkout -b {branch_name} --track
.
141 def delete_branch(self, branch_name: str, local_only: bool = True) -> str | int: 142 """Delete `branch_name` from repo. 143 144 #### :params: 145 146 `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.""" 147 output = self.branch(f"--delete {branch_name}") 148 if not local_only: 149 return output + self.push(f"origin --delete {branch_name}") # type:ignore 150 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.
152 def undo(self) -> str | int: 153 """Undo uncommitted changes. 154 155 Equivalent to `git checkout .`.""" 156 return self.checkout(".")
Undo uncommitted changes.
Equivalent to git checkout .
.
158 def merge(self, branch_name: str) -> str | int: 159 """Merge branch `branch_name` with currently active branch.""" 160 return self.execute(f"merge {branch_name}")
Merge branch branch_name
with currently active branch.
164 def create_remote(self, name: str, public: bool = False) -> str | int: 165 """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo. 166 167 #### :params: 168 169 `name`: The name for the repo. 170 171 `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.""" 172 visibility = "--public" if public else "--private" 173 return self._run(["gh", "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.
175 def create_remote_from_cwd(self, public: bool = False) -> str | int: 176 """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from 177 the current working directory repo and add its url as this repo's remote origin. 178 179 #### :params: 180 181 `public`: Create the GitHub repo as a public repo, default is to create it as private.""" 182 visibility = "public" if public else "private" 183 return self._run( 184 ["gh", "repo", "create", "--source", ".", f"--{visibility}", "--push"] 185 )
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.
192 def make_private(self, owner: str, name: str) -> str | int: 193 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private. 194 195 #### :params: 196 197 `owner`: The repo owner. 198 199 `name`: The name of the repo to edit.""" 200 return self._change_visibility(owner, name, "private")
Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private.
:params:
owner
: The repo owner.
name
: The name of the repo to edit.
202 def make_public(self, owner: str, name: str) -> str | int: 203 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public. 204 205 #### :params: 206 207 `owner`: The repo owner. 208 209 `name`: The name of the repo to edit.""" 210 return self._change_visibility(owner, name, "public")
Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public.
:params:
owner
: The repo owner.
name
: The name of the repo to edit.
212 def delete_remote(self, owner: str, name: str) -> str | int: 213 """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo. 214 215 #### :params: 216 217 `owner`: The repo owner. 218 219 `name`: The name of the remote repo to delete.""" 220 return self._run(["gh", "repo", "delete", f"{owner}/{name}", "--yes"])
Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo.
:params:
owner
: The repo owner.
name
: The name of the remote repo to delete.