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, id_: str) -> str | int: 83 """Tag the current commit with `id_`. 84 85 Equivalent to `git tag {id_}`.""" 86 return self.execute(f"tag {id_}") 87 88 # ==========================================Push/Pull========================================== 89 def add_remote_url(self, url: str, name: str = "origin") -> str | int: 90 """Add remote url to repo.""" 91 return self.execute(f"remote add {name} {url}") 92 93 def push(self, args: str = "") -> str | int: 94 """Equivalent to `git push {args}`.""" 95 return self.execute(f"push {args}") 96 97 def pull(self, args: str = "") -> str | int: 98 """Equivalent to `git pull {args}`.""" 99 return self.execute(f"pull {args}") 100 101 def push_new_branch(self, branch: str) -> str | int: 102 """Push a new branch to origin with tracking. 103 104 Equivalent to `git push -u origin {branch}`.""" 105 return self.push(f"-u origin {branch}") 106 107 def pull_branch(self, branch: str) -> str | int: 108 """Pull `branch` from origin.""" 109 return self.pull(f"origin {branch}") 110 111 # ============================================Checkout/Branches============================================ 112 def branch(self, args: str) -> str | int: 113 """Equivalent to `git branch {args}`.""" 114 return self.execute(f"branch {args}") 115 116 def list_branches(self) -> str | int: 117 """Print a list of branches.""" 118 return self.branch("-vva") 119 120 def checkout(self, args: str) -> str | int: 121 """Equivalent to `git checkout {args}`.""" 122 return self.execute(f"checkout {args}") 123 124 def switch_branch(self, branch_name: str) -> str | int: 125 """Switch to the branch specified by `branch_name`. 126 127 Equivalent to `git checkout {branch_name}`.""" 128 return self.checkout(branch_name) 129 130 def create_new_branch(self, branch_name: str) -> str | int: 131 """Create and switch to a new branch named with `branch_name`. 132 133 Equivalent to `git checkout -b {branch_name} --track`.""" 134 return self.checkout(f"-b {branch_name} --track") 135 136 def delete_branch(self, branch_name: str, local_only: bool = True) -> str | int: 137 """Delete `branch_name` from repo. 138 139 #### :params: 140 141 `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.""" 142 output = self.branch(f"--delete {branch_name}") 143 if not local_only: 144 return output + self.push(f"origin --delete {branch_name}") # type:ignore 145 return output 146 147 def undo(self) -> str | int: 148 """Undo uncommitted changes. 149 150 Equivalent to `git checkout .`.""" 151 return self.checkout(".") 152 153 def merge(self, branch_name: str) -> str | int: 154 """Merge branch `branch_name` with currently active branch.""" 155 return self.execute(f"merge {branch_name}") 156 157 # ===============================Requires GitHub CLI to be installed and configured=============================== 158 159 def create_remote(self, name: str, public: bool = False) -> str | int: 160 """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo. 161 162 #### :params: 163 164 `name`: The name for the repo. 165 166 `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.""" 167 visibility = "--public" if public else "--private" 168 return self._run(["gh", "repo", "create", name, visibility]) 169 170 def create_remote_from_cwd(self, public: bool = False) -> str | int: 171 """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from 172 the current working directory repo and add its url as this repo's remote origin. 173 174 #### :params: 175 176 `public`: Create the GitHub repo as a public repo, default is to create it as private.""" 177 visibility = "public" if public else "private" 178 return self._run( 179 ["gh", "repo", "create", "--source", ".", f"--{visibility}", "--push"] 180 ) 181 182 def _change_visibility(self, owner: str, name: str, visibility: str) -> str | int: 183 return self._run( 184 ["gh", "repo", "edit", f"{owner}/{name}", "--visibility", visibility] 185 ) 186 187 def make_private(self, owner: str, name: str) -> str | int: 188 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private. 189 190 #### :params: 191 192 `owner`: The repo owner. 193 194 `name`: The name of the repo to edit.""" 195 return self._change_visibility(owner, name, "private") 196 197 def make_public(self, owner: str, name: str) -> str | int: 198 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public. 199 200 #### :params: 201 202 `owner`: The repo owner. 203 204 `name`: The name of the repo to edit.""" 205 return self._change_visibility(owner, name, "public") 206 207 def delete_remote(self, owner: str, name: str) -> str | int: 208 """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo. 209 210 #### :params: 211 212 `owner`: The repo owner. 213 214 `name`: The name of the remote repo to delete.""" 215 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, id_: str) -> str | int: 84 """Tag the current commit with `id_`. 85 86 Equivalent to `git tag {id_}`.""" 87 return self.execute(f"tag {id_}") 88 89 # ==========================================Push/Pull========================================== 90 def add_remote_url(self, url: str, name: str = "origin") -> str | int: 91 """Add remote url to repo.""" 92 return self.execute(f"remote add {name} {url}") 93 94 def push(self, args: str = "") -> str | int: 95 """Equivalent to `git push {args}`.""" 96 return self.execute(f"push {args}") 97 98 def pull(self, args: str = "") -> str | int: 99 """Equivalent to `git pull {args}`.""" 100 return self.execute(f"pull {args}") 101 102 def push_new_branch(self, branch: str) -> str | int: 103 """Push a new branch to origin with tracking. 104 105 Equivalent to `git push -u origin {branch}`.""" 106 return self.push(f"-u origin {branch}") 107 108 def pull_branch(self, branch: str) -> str | int: 109 """Pull `branch` from origin.""" 110 return self.pull(f"origin {branch}") 111 112 # ============================================Checkout/Branches============================================ 113 def branch(self, args: str) -> str | int: 114 """Equivalent to `git branch {args}`.""" 115 return self.execute(f"branch {args}") 116 117 def list_branches(self) -> str | int: 118 """Print a list of branches.""" 119 return self.branch("-vva") 120 121 def checkout(self, args: str) -> str | int: 122 """Equivalent to `git checkout {args}`.""" 123 return self.execute(f"checkout {args}") 124 125 def switch_branch(self, branch_name: str) -> str | int: 126 """Switch to the branch specified by `branch_name`. 127 128 Equivalent to `git checkout {branch_name}`.""" 129 return self.checkout(branch_name) 130 131 def create_new_branch(self, branch_name: str) -> str | int: 132 """Create and switch to a new branch named with `branch_name`. 133 134 Equivalent to `git checkout -b {branch_name} --track`.""" 135 return self.checkout(f"-b {branch_name} --track") 136 137 def delete_branch(self, branch_name: str, local_only: bool = True) -> str | int: 138 """Delete `branch_name` from repo. 139 140 #### :params: 141 142 `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.""" 143 output = self.branch(f"--delete {branch_name}") 144 if not local_only: 145 return output + self.push(f"origin --delete {branch_name}") # type:ignore 146 return output 147 148 def undo(self) -> str | int: 149 """Undo uncommitted changes. 150 151 Equivalent to `git checkout .`.""" 152 return self.checkout(".") 153 154 def merge(self, branch_name: str) -> str | int: 155 """Merge branch `branch_name` with currently active branch.""" 156 return self.execute(f"merge {branch_name}") 157 158 # ===============================Requires GitHub CLI to be installed and configured=============================== 159 160 def create_remote(self, name: str, public: bool = False) -> str | int: 161 """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo. 162 163 #### :params: 164 165 `name`: The name for the repo. 166 167 `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.""" 168 visibility = "--public" if public else "--private" 169 return self._run(["gh", "repo", "create", name, visibility]) 170 171 def create_remote_from_cwd(self, public: bool = False) -> str | int: 172 """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from 173 the current working directory repo and add its url as this repo's remote origin. 174 175 #### :params: 176 177 `public`: Create the GitHub repo as a public repo, default is to create it as private.""" 178 visibility = "public" if public else "private" 179 return self._run( 180 ["gh", "repo", "create", "--source", ".", f"--{visibility}", "--push"] 181 ) 182 183 def _change_visibility(self, owner: str, name: str, visibility: str) -> str | int: 184 return self._run( 185 ["gh", "repo", "edit", f"{owner}/{name}", "--visibility", visibility] 186 ) 187 188 def make_private(self, owner: str, name: str) -> str | int: 189 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private. 190 191 #### :params: 192 193 `owner`: The repo owner. 194 195 `name`: The name of the repo to edit.""" 196 return self._change_visibility(owner, name, "private") 197 198 def make_public(self, owner: str, name: str) -> str | int: 199 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public. 200 201 #### :params: 202 203 `owner`: The repo owner. 204 205 `name`: The name of the repo to edit.""" 206 return self._change_visibility(owner, name, "public") 207 208 def delete_remote(self, owner: str, name: str) -> str | int: 209 """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo. 210 211 #### :params: 212 213 `owner`: The repo owner. 214 215 `name`: The name of the remote repo to delete.""" 216 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, id_: str) -> str | int: 84 """Tag the current commit with `id_`. 85 86 Equivalent to `git tag {id_}`.""" 87 return self.execute(f"tag {id_}")
Tag the current commit with id_
.
Equivalent to git tag {id_}
.
90 def add_remote_url(self, url: str, name: str = "origin") -> str | int: 91 """Add remote url to repo.""" 92 return self.execute(f"remote add {name} {url}")
Add remote url to repo.
94 def push(self, args: str = "") -> str | int: 95 """Equivalent to `git push {args}`.""" 96 return self.execute(f"push {args}")
Equivalent to git push {args}
.
98 def pull(self, args: str = "") -> str | int: 99 """Equivalent to `git pull {args}`.""" 100 return self.execute(f"pull {args}")
Equivalent to git pull {args}
.
102 def push_new_branch(self, branch: str) -> str | int: 103 """Push a new branch to origin with tracking. 104 105 Equivalent to `git push -u origin {branch}`.""" 106 return self.push(f"-u origin {branch}")
Push a new branch to origin with tracking.
Equivalent to git push -u origin {branch}
.
108 def pull_branch(self, branch: str) -> str | int: 109 """Pull `branch` from origin.""" 110 return self.pull(f"origin {branch}")
Pull branch
from origin.
113 def branch(self, args: str) -> str | int: 114 """Equivalent to `git branch {args}`.""" 115 return self.execute(f"branch {args}")
Equivalent to git branch {args}
.
117 def list_branches(self) -> str | int: 118 """Print a list of branches.""" 119 return self.branch("-vva")
Print a list of branches.
121 def checkout(self, args: str) -> str | int: 122 """Equivalent to `git checkout {args}`.""" 123 return self.execute(f"checkout {args}")
Equivalent to git checkout {args}
.
125 def switch_branch(self, branch_name: str) -> str | int: 126 """Switch to the branch specified by `branch_name`. 127 128 Equivalent to `git checkout {branch_name}`.""" 129 return self.checkout(branch_name)
Switch to the branch specified by branch_name
.
Equivalent to git checkout {branch_name}
.
131 def create_new_branch(self, branch_name: str) -> str | int: 132 """Create and switch to a new branch named with `branch_name`. 133 134 Equivalent to `git checkout -b {branch_name} --track`.""" 135 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
.
137 def delete_branch(self, branch_name: str, local_only: bool = True) -> str | int: 138 """Delete `branch_name` from repo. 139 140 #### :params: 141 142 `local_only`: Only delete the local copy of `branch`, otherwise also delete the remote branch on origin and remote-tracking branch.""" 143 output = self.branch(f"--delete {branch_name}") 144 if not local_only: 145 return output + self.push(f"origin --delete {branch_name}") # type:ignore 146 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.
148 def undo(self) -> str | int: 149 """Undo uncommitted changes. 150 151 Equivalent to `git checkout .`.""" 152 return self.checkout(".")
Undo uncommitted changes.
Equivalent to git checkout .
.
154 def merge(self, branch_name: str) -> str | int: 155 """Merge branch `branch_name` with currently active branch.""" 156 return self.execute(f"merge {branch_name}")
Merge branch branch_name
with currently active branch.
160 def create_remote(self, name: str, public: bool = False) -> str | int: 161 """Uses GitHub CLI (must be installed and configured) to create a remote GitHub repo. 162 163 #### :params: 164 165 `name`: The name for the repo. 166 167 `public`: Set to `True` to create the repo as public, otherwise it'll be created as private.""" 168 visibility = "--public" if public else "--private" 169 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.
171 def create_remote_from_cwd(self, public: bool = False) -> str | int: 172 """Use GitHub CLI (must be installed and configured) to create a remote GitHub repo from 173 the current working directory repo and add its url as this repo's remote origin. 174 175 #### :params: 176 177 `public`: Create the GitHub repo as a public repo, default is to create it as private.""" 178 visibility = "public" if public else "private" 179 return self._run( 180 ["gh", "repo", "create", "--source", ".", f"--{visibility}", "--push"] 181 )
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.
188 def make_private(self, owner: str, name: str) -> str | int: 189 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to private. 190 191 #### :params: 192 193 `owner`: The repo owner. 194 195 `name`: The name of the repo to edit.""" 196 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.
198 def make_public(self, owner: str, name: str) -> str | int: 199 """Uses GitHub CLI (must be installed and configured) to set the repo's visibility to public. 200 201 #### :params: 202 203 `owner`: The repo owner. 204 205 `name`: The name of the repo to edit.""" 206 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.
208 def delete_remote(self, owner: str, name: str) -> str | int: 209 """Uses GitHub CLI (must be isntalled and configured) to delete the remote for this repo. 210 211 #### :params: 212 213 `owner`: The repo owner. 214 215 `name`: The name of the remote repo to delete.""" 216 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.