gitbetter.gitbetter
1import os 2 3from argshell import ArgShell, ArgShellParser, Namespace, with_parser 4from pathier import Pathier 5 6from gitbetter import git 7 8 9def new_remote_parser() -> ArgShellParser: 10 parser = ArgShellParser() 11 parser.add_argument( 12 "--public", 13 action="store_true", 14 help=""" Set the new remote visibility as public. Defaults to private. """, 15 ) 16 return parser 17 18 19def commit_files_parser() -> ArgShellParser: 20 parser = ArgShellParser() 21 parser.add_argument( 22 "files", type=str, nargs="*", help=""" List of files to stage and commit. """ 23 ) 24 parser.add_argument( 25 "-m", 26 "--message", 27 type=str, 28 required=True, 29 help=""" The commit message to use. """, 30 ) 31 parser.add_argument( 32 "-r", 33 "--recursive", 34 action="store_true", 35 help=""" If a file name is not found in the current working directory, 36 search for it in subfolders. This avoids having to type paths to files in subfolders, 37 but if you have multiple files in different subfolders with the same name that have changes they 38 will all be staged and committed.""", 39 ) 40 return parser 41 42 43def amend_files_parser() -> ArgShellParser: 44 parser = ArgShellParser() 45 parser.add_argument( 46 "-f", 47 "--files", 48 type=str, 49 nargs="*", 50 help=""" List of files to stage and commit. """, 51 ) 52 parser.add_argument( 53 "-r", 54 "--recursive", 55 action="store_true", 56 help=""" If a file name is not found in the current working directory, 57 search for it in subfolders. This avoids having to type paths to files in subfolders, 58 but if you have multiple files in different subfolders with the same name that have changes they 59 will all be staged and committed.""", 60 ) 61 return parser 62 63 64def delete_branch_parser() -> ArgShellParser: 65 parser = ArgShellParser() 66 parser.add_argument( 67 "branch", type=str, help=""" The name of the branch to delete. """ 68 ) 69 parser.add_argument( 70 "-r", 71 "--remote", 72 action="store_true", 73 help=""" Delete the remote and remote-tracking branches along with the local branch. 74 By default only the local branch is deleted.""", 75 ) 76 return parser 77 78 79def recurse_files(filenames: list[str]) -> list[str]: 80 files = [] 81 for filename in filenames: 82 if not Pathier(filename).exists(): 83 results = list(Pathier.cwd().rglob(f"{filename}")) 84 if not results: 85 print(f"WARNING: Could not find any files with name {filename}") 86 else: 87 files.extend([str(result) for result in results]) 88 else: 89 files.append(filename) 90 return files 91 92 93def files_postparser(args: Namespace) -> Namespace: 94 if args.recursive: 95 args.files = recurse_files(args.files) 96 return args 97 98 99class GitBetter(ArgShell): 100 """GitBetter Shell.""" 101 102 intro = "Starting gitbetter...\nEnter 'help' or '?' for command help." 103 prompt = "gitbetter>" 104 105 def do_cd(self, path: str): 106 """Change current working directory to `path`.""" 107 os.chdir(path) 108 109 def do_cwd(self, _: str): 110 """Print the current working directory.""" 111 print(Pathier.cwd()) 112 113 def do_cmd(self, command: str): 114 """Execute arbitrary command in the terminal so you don't have to quit gitbetter to run other programs/commands.""" 115 os.system(command) 116 117 def do_git(self, arg: str): 118 """Directly execute `git {arg}`. 119 120 i.e. You can still do everything directly invoking git can do.""" 121 git.execute(arg) 122 123 def do_new_repo(self, _: str): 124 """Create a new git repo in this directory.""" 125 git.new_repo() 126 127 def do_new_branch(self, name: str): 128 """Create and switch to a new branch named after the supplied arg.""" 129 git.create_new_branch(name) 130 131 @with_parser(new_remote_parser) 132 def do_new_gh_remote(self, args: Namespace): 133 """Create a remote GitHub repository for this repo. 134 135 GitHub CLI must be installed and configured for this to work.""" 136 name = Pathier.cwd().stem 137 git.create_remote(name, args.public) 138 139 def do_initcommit(self, _: str): 140 """Stage and commit all files with message "Initial Commit".""" 141 git.initcommit() 142 143 def do_undo(self, _: str): 144 """Undo all uncommitted changes.""" 145 git.undo() 146 147 @with_parser(amend_files_parser, [files_postparser]) 148 def do_add(self, args: Namespace): 149 """Stage a list of files. 150 If no files are given, all files will be added.""" 151 git.add(None if not args.files else args.files) 152 153 def do_commit(self, message: str): 154 """Commit staged files with this message.""" 155 if not message.startswith('"'): 156 message = '"' + message 157 if not message.endswith('"'): 158 message += '"' 159 git.commit(f"-m {message}") 160 161 @with_parser(commit_files_parser, [files_postparser]) 162 def do_commitf(self, args: Namespace): 163 """Stage and commit a list of files.""" 164 git.commit_files(args.files, args.message) 165 166 def do_commitall(self, message: str): 167 """Stage and commit all files with this message.""" 168 if not message.startswith('"'): 169 message = '"' + message 170 if not message.endswith('"'): 171 message += '"' 172 git.add() 173 git.commit(f"-m {message}") 174 175 def do_switch(self, branch_name: str): 176 """Switch to this branch.""" 177 git.switch_branch(branch_name) 178 179 def do_add_url(self, url: str): 180 """Add remote url for repo and push repo.""" 181 git.add_remote_url(url) 182 git.push("-u origin main") 183 184 def do_push_new(self, branch_name: str): 185 """Push this new branch to origin with -u flag.""" 186 git.push_new_branch(branch_name) 187 188 def do_push(self, args: str): 189 """Execute `git push`. 190 191 `args` can be any additional args that `git push` accepts.""" 192 git.push(args) 193 194 def do_pull(self, args: str): 195 """Execute `git pull`. 196 197 `args` can be any additional args that `git pull` accepts.""" 198 git.pull(args) 199 200 def do_list_branches(self, _: str): 201 """Show local and remote branches.""" 202 git.list_branches() 203 204 def do_loggy(self, _: str): 205 """Execute `git --oneline --name-only --abbrev-commit --graph`.""" 206 git.loggy() 207 208 def do_merge(self, branch_name: str): 209 """Merge supplied `branch_name` with the currently active branch.""" 210 git.merge(branch_name) 211 212 def do_tag(self, tag_id: str): 213 """Tag current commit with `tag_id`.""" 214 git.tag(tag_id) 215 216 @with_parser(amend_files_parser, [files_postparser]) 217 def do_amend(self, args: Namespace): 218 """Stage files and add to previous commit.""" 219 git.amend(args.files) 220 221 @with_parser(delete_branch_parser) 222 def do_delete_branch(self, args: Namespace): 223 """Delete branch.""" 224 git.delete_branch(args.branch, not args.remote) 225 226 def do_pull_branch(self, branch: str): 227 """Pull this branch from the origin.""" 228 git.pull_branch(branch) 229 230 def do_ignore(self, patterns: str): 231 """Add the list of patterns to `.gitignore`.""" 232 patterns = "\n".join(patterns.split()) 233 path = Pathier(".gitignore") 234 path.append("\n") 235 path.append(patterns, False) 236 237 def do_make_private(self, owner: str): 238 """Make the GitHub remote for this repo private. 239 240 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 241 242 This repo must exist and GitHub CLI must be installed and configured.""" 243 git.make_private(owner, Pathier.cwd().stem) 244 245 def do_make_public(self, owner: str): 246 """Make the GitHub remote for this repo public. 247 248 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 249 250 This repo must exist and GitHub CLI must be installed and configured.""" 251 git.make_public(owner, Pathier.cwd().stem) 252 253 def do_delete_gh_repo(self, owner: str): 254 """Delete this repo from GitHub. 255 256 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 257 258 GitHub CLI must be installed and configured. 259 260 May require you to reauthorize and rerun command.""" 261 git.delete_remote(owner, Pathier.cwd().stem) 262 263 264def main(): 265 GitBetter().cmdloop() 266 267 268if __name__ == "__main__": 269 main()
20def commit_files_parser() -> ArgShellParser: 21 parser = ArgShellParser() 22 parser.add_argument( 23 "files", type=str, nargs="*", help=""" List of files to stage and commit. """ 24 ) 25 parser.add_argument( 26 "-m", 27 "--message", 28 type=str, 29 required=True, 30 help=""" The commit message to use. """, 31 ) 32 parser.add_argument( 33 "-r", 34 "--recursive", 35 action="store_true", 36 help=""" If a file name is not found in the current working directory, 37 search for it in subfolders. This avoids having to type paths to files in subfolders, 38 but if you have multiple files in different subfolders with the same name that have changes they 39 will all be staged and committed.""", 40 ) 41 return parser
44def amend_files_parser() -> ArgShellParser: 45 parser = ArgShellParser() 46 parser.add_argument( 47 "-f", 48 "--files", 49 type=str, 50 nargs="*", 51 help=""" List of files to stage and commit. """, 52 ) 53 parser.add_argument( 54 "-r", 55 "--recursive", 56 action="store_true", 57 help=""" If a file name is not found in the current working directory, 58 search for it in subfolders. This avoids having to type paths to files in subfolders, 59 but if you have multiple files in different subfolders with the same name that have changes they 60 will all be staged and committed.""", 61 ) 62 return parser
65def delete_branch_parser() -> ArgShellParser: 66 parser = ArgShellParser() 67 parser.add_argument( 68 "branch", type=str, help=""" The name of the branch to delete. """ 69 ) 70 parser.add_argument( 71 "-r", 72 "--remote", 73 action="store_true", 74 help=""" Delete the remote and remote-tracking branches along with the local branch. 75 By default only the local branch is deleted.""", 76 ) 77 return parser
80def recurse_files(filenames: list[str]) -> list[str]: 81 files = [] 82 for filename in filenames: 83 if not Pathier(filename).exists(): 84 results = list(Pathier.cwd().rglob(f"{filename}")) 85 if not results: 86 print(f"WARNING: Could not find any files with name {filename}") 87 else: 88 files.extend([str(result) for result in results]) 89 else: 90 files.append(filename) 91 return files
100class GitBetter(ArgShell): 101 """GitBetter Shell.""" 102 103 intro = "Starting gitbetter...\nEnter 'help' or '?' for command help." 104 prompt = "gitbetter>" 105 106 def do_cd(self, path: str): 107 """Change current working directory to `path`.""" 108 os.chdir(path) 109 110 def do_cwd(self, _: str): 111 """Print the current working directory.""" 112 print(Pathier.cwd()) 113 114 def do_cmd(self, command: str): 115 """Execute arbitrary command in the terminal so you don't have to quit gitbetter to run other programs/commands.""" 116 os.system(command) 117 118 def do_git(self, arg: str): 119 """Directly execute `git {arg}`. 120 121 i.e. You can still do everything directly invoking git can do.""" 122 git.execute(arg) 123 124 def do_new_repo(self, _: str): 125 """Create a new git repo in this directory.""" 126 git.new_repo() 127 128 def do_new_branch(self, name: str): 129 """Create and switch to a new branch named after the supplied arg.""" 130 git.create_new_branch(name) 131 132 @with_parser(new_remote_parser) 133 def do_new_gh_remote(self, args: Namespace): 134 """Create a remote GitHub repository for this repo. 135 136 GitHub CLI must be installed and configured for this to work.""" 137 name = Pathier.cwd().stem 138 git.create_remote(name, args.public) 139 140 def do_initcommit(self, _: str): 141 """Stage and commit all files with message "Initial Commit".""" 142 git.initcommit() 143 144 def do_undo(self, _: str): 145 """Undo all uncommitted changes.""" 146 git.undo() 147 148 @with_parser(amend_files_parser, [files_postparser]) 149 def do_add(self, args: Namespace): 150 """Stage a list of files. 151 If no files are given, all files will be added.""" 152 git.add(None if not args.files else args.files) 153 154 def do_commit(self, message: str): 155 """Commit staged files with this message.""" 156 if not message.startswith('"'): 157 message = '"' + message 158 if not message.endswith('"'): 159 message += '"' 160 git.commit(f"-m {message}") 161 162 @with_parser(commit_files_parser, [files_postparser]) 163 def do_commitf(self, args: Namespace): 164 """Stage and commit a list of files.""" 165 git.commit_files(args.files, args.message) 166 167 def do_commitall(self, message: str): 168 """Stage and commit all files with this message.""" 169 if not message.startswith('"'): 170 message = '"' + message 171 if not message.endswith('"'): 172 message += '"' 173 git.add() 174 git.commit(f"-m {message}") 175 176 def do_switch(self, branch_name: str): 177 """Switch to this branch.""" 178 git.switch_branch(branch_name) 179 180 def do_add_url(self, url: str): 181 """Add remote url for repo and push repo.""" 182 git.add_remote_url(url) 183 git.push("-u origin main") 184 185 def do_push_new(self, branch_name: str): 186 """Push this new branch to origin with -u flag.""" 187 git.push_new_branch(branch_name) 188 189 def do_push(self, args: str): 190 """Execute `git push`. 191 192 `args` can be any additional args that `git push` accepts.""" 193 git.push(args) 194 195 def do_pull(self, args: str): 196 """Execute `git pull`. 197 198 `args` can be any additional args that `git pull` accepts.""" 199 git.pull(args) 200 201 def do_list_branches(self, _: str): 202 """Show local and remote branches.""" 203 git.list_branches() 204 205 def do_loggy(self, _: str): 206 """Execute `git --oneline --name-only --abbrev-commit --graph`.""" 207 git.loggy() 208 209 def do_merge(self, branch_name: str): 210 """Merge supplied `branch_name` with the currently active branch.""" 211 git.merge(branch_name) 212 213 def do_tag(self, tag_id: str): 214 """Tag current commit with `tag_id`.""" 215 git.tag(tag_id) 216 217 @with_parser(amend_files_parser, [files_postparser]) 218 def do_amend(self, args: Namespace): 219 """Stage files and add to previous commit.""" 220 git.amend(args.files) 221 222 @with_parser(delete_branch_parser) 223 def do_delete_branch(self, args: Namespace): 224 """Delete branch.""" 225 git.delete_branch(args.branch, not args.remote) 226 227 def do_pull_branch(self, branch: str): 228 """Pull this branch from the origin.""" 229 git.pull_branch(branch) 230 231 def do_ignore(self, patterns: str): 232 """Add the list of patterns to `.gitignore`.""" 233 patterns = "\n".join(patterns.split()) 234 path = Pathier(".gitignore") 235 path.append("\n") 236 path.append(patterns, False) 237 238 def do_make_private(self, owner: str): 239 """Make the GitHub remote for this repo private. 240 241 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 242 243 This repo must exist and GitHub CLI must be installed and configured.""" 244 git.make_private(owner, Pathier.cwd().stem) 245 246 def do_make_public(self, owner: str): 247 """Make the GitHub remote for this repo public. 248 249 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 250 251 This repo must exist and GitHub CLI must be installed and configured.""" 252 git.make_public(owner, Pathier.cwd().stem) 253 254 def do_delete_gh_repo(self, owner: str): 255 """Delete this repo from GitHub. 256 257 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 258 259 GitHub CLI must be installed and configured. 260 261 May require you to reauthorize and rerun command.""" 262 git.delete_remote(owner, Pathier.cwd().stem)
GitBetter Shell.
106 def do_cd(self, path: str): 107 """Change current working directory to `path`.""" 108 os.chdir(path)
Change current working directory to path
.
110 def do_cwd(self, _: str): 111 """Print the current working directory.""" 112 print(Pathier.cwd())
Print the current working directory.
114 def do_cmd(self, command: str): 115 """Execute arbitrary command in the terminal so you don't have to quit gitbetter to run other programs/commands.""" 116 os.system(command)
Execute arbitrary command in the terminal so you don't have to quit gitbetter to run other programs/commands.
118 def do_git(self, arg: str): 119 """Directly execute `git {arg}`. 120 121 i.e. You can still do everything directly invoking git can do.""" 122 git.execute(arg)
Directly execute git {arg}
.
i.e. You can still do everything directly invoking git can do.
124 def do_new_repo(self, _: str): 125 """Create a new git repo in this directory.""" 126 git.new_repo()
Create a new git repo in this directory.
128 def do_new_branch(self, name: str): 129 """Create and switch to a new branch named after the supplied arg.""" 130 git.create_new_branch(name)
Create and switch to a new branch named after the supplied arg.
132 @with_parser(new_remote_parser) 133 def do_new_gh_remote(self, args: Namespace): 134 """Create a remote GitHub repository for this repo. 135 136 GitHub CLI must be installed and configured for this to work.""" 137 name = Pathier.cwd().stem 138 git.create_remote(name, args.public)
Create a remote GitHub repository for this repo.
GitHub CLI must be installed and configured for this to work.
140 def do_initcommit(self, _: str): 141 """Stage and commit all files with message "Initial Commit".""" 142 git.initcommit()
Stage and commit all files with message "Initial Commit".
148 @with_parser(amend_files_parser, [files_postparser]) 149 def do_add(self, args: Namespace): 150 """Stage a list of files. 151 If no files are given, all files will be added.""" 152 git.add(None if not args.files else args.files)
Stage a list of files. If no files are given, all files will be added.
154 def do_commit(self, message: str): 155 """Commit staged files with this message.""" 156 if not message.startswith('"'): 157 message = '"' + message 158 if not message.endswith('"'): 159 message += '"' 160 git.commit(f"-m {message}")
Commit staged files with this message.
162 @with_parser(commit_files_parser, [files_postparser]) 163 def do_commitf(self, args: Namespace): 164 """Stage and commit a list of files.""" 165 git.commit_files(args.files, args.message)
Stage and commit a list of files.
167 def do_commitall(self, message: str): 168 """Stage and commit all files with this message.""" 169 if not message.startswith('"'): 170 message = '"' + message 171 if not message.endswith('"'): 172 message += '"' 173 git.add() 174 git.commit(f"-m {message}")
Stage and commit all files with this message.
176 def do_switch(self, branch_name: str): 177 """Switch to this branch.""" 178 git.switch_branch(branch_name)
Switch to this branch.
180 def do_add_url(self, url: str): 181 """Add remote url for repo and push repo.""" 182 git.add_remote_url(url) 183 git.push("-u origin main")
Add remote url for repo and push repo.
185 def do_push_new(self, branch_name: str): 186 """Push this new branch to origin with -u flag.""" 187 git.push_new_branch(branch_name)
Push this new branch to origin with -u flag.
189 def do_push(self, args: str): 190 """Execute `git push`. 191 192 `args` can be any additional args that `git push` accepts.""" 193 git.push(args)
Execute git push
.
args
can be any additional args that git push
accepts.
195 def do_pull(self, args: str): 196 """Execute `git pull`. 197 198 `args` can be any additional args that `git pull` accepts.""" 199 git.pull(args)
Execute git pull
.
args
can be any additional args that git pull
accepts.
201 def do_list_branches(self, _: str): 202 """Show local and remote branches.""" 203 git.list_branches()
Show local and remote branches.
205 def do_loggy(self, _: str): 206 """Execute `git --oneline --name-only --abbrev-commit --graph`.""" 207 git.loggy()
Execute git --oneline --name-only --abbrev-commit --graph
.
209 def do_merge(self, branch_name: str): 210 """Merge supplied `branch_name` with the currently active branch.""" 211 git.merge(branch_name)
Merge supplied branch_name
with the currently active branch.
217 @with_parser(amend_files_parser, [files_postparser]) 218 def do_amend(self, args: Namespace): 219 """Stage files and add to previous commit.""" 220 git.amend(args.files)
Stage files and add to previous commit.
222 @with_parser(delete_branch_parser) 223 def do_delete_branch(self, args: Namespace): 224 """Delete branch.""" 225 git.delete_branch(args.branch, not args.remote)
Delete branch.
227 def do_pull_branch(self, branch: str): 228 """Pull this branch from the origin.""" 229 git.pull_branch(branch)
Pull this branch from the origin.
231 def do_ignore(self, patterns: str): 232 """Add the list of patterns to `.gitignore`.""" 233 patterns = "\n".join(patterns.split()) 234 path = Pathier(".gitignore") 235 path.append("\n") 236 path.append(patterns, False)
Add the list of patterns to .gitignore
.
238 def do_make_private(self, owner: str): 239 """Make the GitHub remote for this repo private. 240 241 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 242 243 This repo must exist and GitHub CLI must be installed and configured.""" 244 git.make_private(owner, Pathier.cwd().stem)
Make the GitHub remote for this repo private.
Expects an argument for the repo owner, i.e. the OWNER
in github.com/{OWNER}/{repo-name}
This repo must exist and GitHub CLI must be installed and configured.
246 def do_make_public(self, owner: str): 247 """Make the GitHub remote for this repo public. 248 249 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 250 251 This repo must exist and GitHub CLI must be installed and configured.""" 252 git.make_public(owner, Pathier.cwd().stem)
Make the GitHub remote for this repo public.
Expects an argument for the repo owner, i.e. the OWNER
in github.com/{OWNER}/{repo-name}
This repo must exist and GitHub CLI must be installed and configured.
254 def do_delete_gh_repo(self, owner: str): 255 """Delete this repo from GitHub. 256 257 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 258 259 GitHub CLI must be installed and configured. 260 261 May require you to reauthorize and rerun command.""" 262 git.delete_remote(owner, Pathier.cwd().stem)
Delete this repo from GitHub.
Expects an argument for the repo owner, i.e. the OWNER
in github.com/{OWNER}/{repo-name}
GitHub CLI must be installed and configured.
May require you to reauthorize and rerun command.
Inherited Members
- cmd.Cmd
- Cmd
- precmd
- postcmd
- preloop
- postloop
- parseline
- onecmd
- emptyline
- default
- completedefault
- completenames
- complete
- get_names
- complete_help
- print_topics
- columnize
- argshell.argshell.ArgShell
- do_quit
- do_help
- cmdloop