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 | Pathier]: 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(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_git(self, arg: str): 114 """Directly execute `git {arg}`. 115 116 i.e. You can still do everything directly invoking git can do.""" 117 git.execute(arg) 118 119 def do_new_repo(self, _: str): 120 """Create a new git repo in this directory.""" 121 git.new_repo() 122 123 def do_new_branch(self, name: str): 124 """Create and switch to a new branch named after the supplied arg.""" 125 git.create_new_branch(name) 126 127 @with_parser(new_remote_parser) 128 def do_new_remote(self, args: Namespace): 129 """Create a remote GitHub repository for this repo. 130 131 GitHub CLI must be installed and configured for this to work.""" 132 name = Pathier.cwd().stem 133 git.create_remote(name, args.public) 134 135 def do_initcommit(self, _: str): 136 """Stage and commit all files with message "Initial Commit".""" 137 git.initcommit() 138 139 def do_undo(self, _: str): 140 """Undo all uncommitted changes.""" 141 git.undo() 142 143 @with_parser(amend_files_parser, [files_postparser]) 144 def do_add(self, args: Namespace): 145 """Stage a list of files. 146 If no files are given, all files will be added.""" 147 git.add(None if not args.files else args.files) 148 149 def do_commit(self, message: str): 150 """Commit staged files with this message.""" 151 git.commit(f'-m "{message}"') 152 153 @with_parser(commit_files_parser, [files_postparser]) 154 def do_commitf(self, args: Namespace): 155 """Stage and commit a list of files.""" 156 git.commit_files(args.files, args.message) 157 158 def do_commit_all(self, message: str): 159 """Stage and commit all files with this message.""" 160 git.add() 161 git.commit(f"-m {message}") 162 163 def do_switch(self, branch_name: str): 164 """Switch to this branch.""" 165 git.switch_branch(branch_name) 166 167 def do_add_url(self, url: str): 168 """Add remote url for repo and push repo.""" 169 git.add_remote_url(url) 170 git.push("-u origin main") 171 172 def do_push_new(self, branch_name: str): 173 """Push this new branch to origin with -u flag.""" 174 git.push_new_branch(branch_name) 175 176 def do_push(self, args: str): 177 """Execute `git push`. 178 179 `args` can be any additional args that `git push` accepts.""" 180 git.push(args) 181 182 def do_pull(self, args: str): 183 """Execute `git pull`. 184 185 `args` can be any additional args that `git pull` accepts.""" 186 git.pull(args) 187 188 def do_list_branches(self, _: str): 189 """Show local and remote branches.""" 190 git.list_branches() 191 192 def do_loggy(self, _: str): 193 """Execute `git --oneline --name-only --abbrev-commit --graph`.""" 194 git.loggy() 195 196 def do_merge(self, branch_name: str): 197 """Merge supplied `branch_name` with the currently active branch.""" 198 git.merge(branch_name) 199 200 def do_tag(self, tag_id: str): 201 """Tag current commit with `tag_id`.""" 202 git.tag(tag_id) 203 204 @with_parser(amend_files_parser, [files_postparser]) 205 def do_amend(self, args: Namespace): 206 """Stage files and add to previous commit.""" 207 git.amend(args.files) 208 209 @with_parser(delete_branch_parser) 210 def do_delete_branch(self, args: Namespace): 211 """Delete branch.""" 212 git.delete_branch(args.branch, not args.remote) 213 214 def do_pull_branch(self, branch: str): 215 """Pull this branch from the origin.""" 216 git.pull_branch(branch) 217 218 def do_ignore(self, patterns: str): 219 """Add the list of patterns to `.gitignore`.""" 220 patterns = "\n".join(patterns.split()) 221 path = Pathier(".gitignore") 222 path.append("\n") 223 path.append(patterns, False) 224 225 def do_make_private(self, owner: str): 226 """Make the GitHub remote for this repo private. 227 228 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 229 230 This repo must exist and GitHub CLI must be installed and configured.""" 231 git.make_private(owner, Pathier.cwd().stem) 232 233 def do_make_public(self, owner: str): 234 """Make the GitHub remote for this repo public. 235 236 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 237 238 This repo must exist and GitHub CLI must be installed and configured.""" 239 git.make_public(owner, Pathier.cwd().stem) 240 241 def do_delete_github(self, owner: str): 242 """Delete this repo from GitHub. 243 244 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 245 246 GitHub CLI must be installed and configured.""" 247 git.delete_remote(owner, Pathier.cwd().stem) 248 249 250def main(): 251 GitBetter().cmdloop() 252 253 254if __name__ == "__main__": 255 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 | Pathier]: 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(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_git(self, arg: str): 115 """Directly execute `git {arg}`. 116 117 i.e. You can still do everything directly invoking git can do.""" 118 git.execute(arg) 119 120 def do_new_repo(self, _: str): 121 """Create a new git repo in this directory.""" 122 git.new_repo() 123 124 def do_new_branch(self, name: str): 125 """Create and switch to a new branch named after the supplied arg.""" 126 git.create_new_branch(name) 127 128 @with_parser(new_remote_parser) 129 def do_new_remote(self, args: Namespace): 130 """Create a remote GitHub repository for this repo. 131 132 GitHub CLI must be installed and configured for this to work.""" 133 name = Pathier.cwd().stem 134 git.create_remote(name, args.public) 135 136 def do_initcommit(self, _: str): 137 """Stage and commit all files with message "Initial Commit".""" 138 git.initcommit() 139 140 def do_undo(self, _: str): 141 """Undo all uncommitted changes.""" 142 git.undo() 143 144 @with_parser(amend_files_parser, [files_postparser]) 145 def do_add(self, args: Namespace): 146 """Stage a list of files. 147 If no files are given, all files will be added.""" 148 git.add(None if not args.files else args.files) 149 150 def do_commit(self, message: str): 151 """Commit staged files with this message.""" 152 git.commit(f'-m "{message}"') 153 154 @with_parser(commit_files_parser, [files_postparser]) 155 def do_commitf(self, args: Namespace): 156 """Stage and commit a list of files.""" 157 git.commit_files(args.files, args.message) 158 159 def do_commit_all(self, message: str): 160 """Stage and commit all files with this message.""" 161 git.add() 162 git.commit(f"-m {message}") 163 164 def do_switch(self, branch_name: str): 165 """Switch to this branch.""" 166 git.switch_branch(branch_name) 167 168 def do_add_url(self, url: str): 169 """Add remote url for repo and push repo.""" 170 git.add_remote_url(url) 171 git.push("-u origin main") 172 173 def do_push_new(self, branch_name: str): 174 """Push this new branch to origin with -u flag.""" 175 git.push_new_branch(branch_name) 176 177 def do_push(self, args: str): 178 """Execute `git push`. 179 180 `args` can be any additional args that `git push` accepts.""" 181 git.push(args) 182 183 def do_pull(self, args: str): 184 """Execute `git pull`. 185 186 `args` can be any additional args that `git pull` accepts.""" 187 git.pull(args) 188 189 def do_list_branches(self, _: str): 190 """Show local and remote branches.""" 191 git.list_branches() 192 193 def do_loggy(self, _: str): 194 """Execute `git --oneline --name-only --abbrev-commit --graph`.""" 195 git.loggy() 196 197 def do_merge(self, branch_name: str): 198 """Merge supplied `branch_name` with the currently active branch.""" 199 git.merge(branch_name) 200 201 def do_tag(self, tag_id: str): 202 """Tag current commit with `tag_id`.""" 203 git.tag(tag_id) 204 205 @with_parser(amend_files_parser, [files_postparser]) 206 def do_amend(self, args: Namespace): 207 """Stage files and add to previous commit.""" 208 git.amend(args.files) 209 210 @with_parser(delete_branch_parser) 211 def do_delete_branch(self, args: Namespace): 212 """Delete branch.""" 213 git.delete_branch(args.branch, not args.remote) 214 215 def do_pull_branch(self, branch: str): 216 """Pull this branch from the origin.""" 217 git.pull_branch(branch) 218 219 def do_ignore(self, patterns: str): 220 """Add the list of patterns to `.gitignore`.""" 221 patterns = "\n".join(patterns.split()) 222 path = Pathier(".gitignore") 223 path.append("\n") 224 path.append(patterns, False) 225 226 def do_make_private(self, owner: str): 227 """Make the GitHub remote for this repo private. 228 229 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 230 231 This repo must exist and GitHub CLI must be installed and configured.""" 232 git.make_private(owner, Pathier.cwd().stem) 233 234 def do_make_public(self, owner: str): 235 """Make the GitHub remote for this repo public. 236 237 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 238 239 This repo must exist and GitHub CLI must be installed and configured.""" 240 git.make_public(owner, Pathier.cwd().stem) 241 242 def do_delete_github(self, owner: str): 243 """Delete this repo from GitHub. 244 245 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 246 247 GitHub CLI must be installed and configured.""" 248 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_git(self, arg: str): 115 """Directly execute `git {arg}`. 116 117 i.e. You can still do everything directly invoking git can do.""" 118 git.execute(arg)
Directly execute git {arg}
.
i.e. You can still do everything directly invoking git can do.
120 def do_new_repo(self, _: str): 121 """Create a new git repo in this directory.""" 122 git.new_repo()
Create a new git repo in this directory.
124 def do_new_branch(self, name: str): 125 """Create and switch to a new branch named after the supplied arg.""" 126 git.create_new_branch(name)
Create and switch to a new branch named after the supplied arg.
128 @with_parser(new_remote_parser) 129 def do_new_remote(self, args: Namespace): 130 """Create a remote GitHub repository for this repo. 131 132 GitHub CLI must be installed and configured for this to work.""" 133 name = Pathier.cwd().stem 134 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.
136 def do_initcommit(self, _: str): 137 """Stage and commit all files with message "Initial Commit".""" 138 git.initcommit()
Stage and commit all files with message "Initial Commit".
144 @with_parser(amend_files_parser, [files_postparser]) 145 def do_add(self, args: Namespace): 146 """Stage a list of files. 147 If no files are given, all files will be added.""" 148 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.
150 def do_commit(self, message: str): 151 """Commit staged files with this message.""" 152 git.commit(f'-m "{message}"')
Commit staged files with this message.
154 @with_parser(commit_files_parser, [files_postparser]) 155 def do_commitf(self, args: Namespace): 156 """Stage and commit a list of files.""" 157 git.commit_files(args.files, args.message)
Stage and commit a list of files.
159 def do_commit_all(self, message: str): 160 """Stage and commit all files with this message.""" 161 git.add() 162 git.commit(f"-m {message}")
Stage and commit all files with this message.
164 def do_switch(self, branch_name: str): 165 """Switch to this branch.""" 166 git.switch_branch(branch_name)
Switch to this branch.
168 def do_add_url(self, url: str): 169 """Add remote url for repo and push repo.""" 170 git.add_remote_url(url) 171 git.push("-u origin main")
Add remote url for repo and push repo.
173 def do_push_new(self, branch_name: str): 174 """Push this new branch to origin with -u flag.""" 175 git.push_new_branch(branch_name)
Push this new branch to origin with -u flag.
177 def do_push(self, args: str): 178 """Execute `git push`. 179 180 `args` can be any additional args that `git push` accepts.""" 181 git.push(args)
Execute git push
.
args
can be any additional args that git push
accepts.
183 def do_pull(self, args: str): 184 """Execute `git pull`. 185 186 `args` can be any additional args that `git pull` accepts.""" 187 git.pull(args)
Execute git pull
.
args
can be any additional args that git pull
accepts.
189 def do_list_branches(self, _: str): 190 """Show local and remote branches.""" 191 git.list_branches()
Show local and remote branches.
193 def do_loggy(self, _: str): 194 """Execute `git --oneline --name-only --abbrev-commit --graph`.""" 195 git.loggy()
Execute git --oneline --name-only --abbrev-commit --graph
.
197 def do_merge(self, branch_name: str): 198 """Merge supplied `branch_name` with the currently active branch.""" 199 git.merge(branch_name)
Merge supplied branch_name
with the currently active branch.
205 @with_parser(amend_files_parser, [files_postparser]) 206 def do_amend(self, args: Namespace): 207 """Stage files and add to previous commit.""" 208 git.amend(args.files)
Stage files and add to previous commit.
210 @with_parser(delete_branch_parser) 211 def do_delete_branch(self, args: Namespace): 212 """Delete branch.""" 213 git.delete_branch(args.branch, not args.remote)
Delete branch.
215 def do_pull_branch(self, branch: str): 216 """Pull this branch from the origin.""" 217 git.pull_branch(branch)
Pull this branch from the origin.
219 def do_ignore(self, patterns: str): 220 """Add the list of patterns to `.gitignore`.""" 221 patterns = "\n".join(patterns.split()) 222 path = Pathier(".gitignore") 223 path.append("\n") 224 path.append(patterns, False)
Add the list of patterns to .gitignore
.
226 def do_make_private(self, owner: str): 227 """Make the GitHub remote for this repo private. 228 229 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 230 231 This repo must exist and GitHub CLI must be installed and configured.""" 232 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.
234 def do_make_public(self, owner: str): 235 """Make the GitHub remote for this repo public. 236 237 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 238 239 This repo must exist and GitHub CLI must be installed and configured.""" 240 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.
242 def do_delete_github(self, owner: str): 243 """Delete this repo from GitHub. 244 245 Expects an argument for the repo owner, i.e. the `OWNER` in `github.com/{OWNER}/{repo-name}` 246 247 GitHub CLI must be installed and configured.""" 248 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.
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