8
8
import typing as t
9
9
from collections .abc import Sequence
10
10
11
+ from libvcs ._internal .query_list import QueryList
11
12
from libvcs ._internal .run import ProgressCallbackProtocol , run
12
13
from libvcs ._internal .types import StrOrBytesPath , StrPath
13
14
@@ -23,6 +24,7 @@ class Git:
23
24
submodule : GitSubmoduleCmd
24
25
remote : GitRemoteCmd
25
26
stash : GitStashCmd
27
+ branch : GitBranchManager
26
28
27
29
def __init__ (
28
30
self ,
@@ -83,6 +85,7 @@ def __init__(
83
85
self .submodule = GitSubmoduleCmd (path = self .path , cmd = self )
84
86
self .remote = GitRemoteCmd (path = self .path , cmd = self )
85
87
self .stash = GitStashCmd (path = self .path , cmd = self )
88
+ self .branches = GitBranchManager (path = self .path , cmd = self )
86
89
87
90
def __repr__ (self ) -> str :
88
91
"""Representation of Git repo command object."""
@@ -2950,3 +2953,315 @@ def save(
2950
2953
check_returncode = check_returncode ,
2951
2954
log_in_real_time = log_in_real_time ,
2952
2955
)
2956
+
2957
+
2958
+ GitBranchCommandLiteral = t .Literal [
2959
+ # "create", # checkout -b
2960
+ # "checkout", # checkout
2961
+ "--list" ,
2962
+ "move" , # branch -m, or branch -M with force
2963
+ "copy" , # branch -c, or branch -C with force
2964
+ "delete" , # branch -d, or branch -D /ith force
2965
+ "set_upstream" ,
2966
+ "unset_upstream" ,
2967
+ "track" ,
2968
+ "no_track" ,
2969
+ "edit_description" ,
2970
+ ]
2971
+
2972
+
2973
+ class GitBranchCmd :
2974
+ """Run commands directly against a git branch for a git repo."""
2975
+
2976
+ branch_name : str
2977
+
2978
+ def __init__ (
2979
+ self ,
2980
+ * ,
2981
+ path : StrPath ,
2982
+ branch_name : str ,
2983
+ cmd : Git | None = None ,
2984
+ ) -> None :
2985
+ """Lite, typed, pythonic wrapper for git-branch(1).
2986
+
2987
+ Parameters
2988
+ ----------
2989
+ path :
2990
+ Operates as PATH in the corresponding git subcommand.
2991
+ branch_name:
2992
+ Name of branch.
2993
+
2994
+ Examples
2995
+ --------
2996
+ >>> GitBranchCmd(path=tmp_path, branch_name='master')
2997
+ <GitBranchCmd path=... branch_name=master>
2998
+
2999
+ >>> GitBranchCmd(path=tmp_path, branch_name="master").run(quiet=True)
3000
+ 'fatal: not a git repository (or any of the parent directories): .git'
3001
+
3002
+ >>> GitBranchCmd(
3003
+ ... path=git_local_clone.path, branch_name="master").run(quiet=True)
3004
+ '* master'
3005
+ """
3006
+ #: Directory to check out
3007
+ self .path : pathlib .Path
3008
+ if isinstance (path , pathlib .Path ):
3009
+ self .path = path
3010
+ else :
3011
+ self .path = pathlib .Path (path )
3012
+
3013
+ self .cmd = cmd if isinstance (cmd , Git ) else Git (path = self .path )
3014
+
3015
+ self .branch_name = branch_name
3016
+
3017
+ def __repr__ (self ) -> str :
3018
+ """Representation of git branch command object."""
3019
+ return f"<GitBranchCmd path={ self .path } branch_name={ self .branch_name } >"
3020
+
3021
+ def run (
3022
+ self ,
3023
+ command : GitBranchCommandLiteral | None = None ,
3024
+ local_flags : list [str ] | None = None ,
3025
+ * ,
3026
+ quiet : bool | None = None ,
3027
+ cached : bool | None = None , # Only when no command entered and status
3028
+ # Pass-through to run()
3029
+ log_in_real_time : bool = False ,
3030
+ check_returncode : bool | None = None ,
3031
+ ** kwargs : t .Any ,
3032
+ ) -> str :
3033
+ """Run a command against a git repository's branch.
3034
+
3035
+ Wraps `git branch <https://git-scm.com/docs/git-branch>`_.
3036
+
3037
+ Examples
3038
+ --------
3039
+ >>> GitBranchCmd(path=git_local_clone.path, branch_name='master').run()
3040
+ '* master'
3041
+ """
3042
+ local_flags = local_flags if isinstance (local_flags , list ) else []
3043
+ if command is not None :
3044
+ local_flags .insert (0 , command )
3045
+
3046
+ if quiet is True :
3047
+ local_flags .append ("--quiet" )
3048
+ if cached is True :
3049
+ local_flags .append ("--cached" )
3050
+
3051
+ return self .cmd .run (
3052
+ ["branch" , * local_flags ],
3053
+ check_returncode = check_returncode ,
3054
+ log_in_real_time = log_in_real_time ,
3055
+ )
3056
+
3057
+ def checkout (self ) -> str :
3058
+ """Git branch checkout.
3059
+
3060
+ Examples
3061
+ --------
3062
+ >>> GitBranchCmd(path=git_local_clone.path, branch_name='master').checkout()
3063
+ "Your branch is up to date with 'origin/master'."
3064
+ """
3065
+ return self .cmd .run (
3066
+ [
3067
+ "checkout" ,
3068
+ * [self .branch_name ],
3069
+ ],
3070
+ )
3071
+
3072
+ def create (self ) -> str :
3073
+ """Create a git branch.
3074
+
3075
+ Examples
3076
+ --------
3077
+ >>> GitBranchCmd(path=git_local_clone.path, branch_name='master').create()
3078
+ "fatal: a branch named 'master' already exists"
3079
+ """
3080
+ return self .cmd .run (
3081
+ [
3082
+ "checkout" ,
3083
+ * ["-b" , self .branch_name ],
3084
+ ],
3085
+ # Pass-through to run()
3086
+ check_returncode = False ,
3087
+ )
3088
+
3089
+
3090
+ class GitBranchManager :
3091
+ """Run commands directly related to git branches of a git repo."""
3092
+
3093
+ branch_name : str
3094
+
3095
+ def __init__ (
3096
+ self ,
3097
+ * ,
3098
+ path : StrPath ,
3099
+ cmd : Git | None = None ,
3100
+ ) -> None :
3101
+ """Wrap some of git-branch(1), git-checkout(1), manager.
3102
+
3103
+ Parameters
3104
+ ----------
3105
+ path :
3106
+ Operates as PATH in the corresponding git subcommand.
3107
+
3108
+ Examples
3109
+ --------
3110
+ >>> GitBranchManager(path=tmp_path)
3111
+ <GitBranchManager path=...>
3112
+
3113
+ >>> GitBranchManager(path=tmp_path).run(quiet=True)
3114
+ 'fatal: not a git repository (or any of the parent directories): .git'
3115
+
3116
+ >>> GitBranchManager(
3117
+ ... path=git_local_clone.path).run(quiet=True)
3118
+ '* master'
3119
+ """
3120
+ #: Directory to check out
3121
+ self .path : pathlib .Path
3122
+ if isinstance (path , pathlib .Path ):
3123
+ self .path = path
3124
+ else :
3125
+ self .path = pathlib .Path (path )
3126
+
3127
+ self .cmd = cmd if isinstance (cmd , Git ) else Git (path = self .path )
3128
+
3129
+ def __repr__ (self ) -> str :
3130
+ """Representation of git branch manager object."""
3131
+ return f"<GitBranchManager path={ self .path } >"
3132
+
3133
+ def run (
3134
+ self ,
3135
+ command : GitBranchCommandLiteral | None = None ,
3136
+ local_flags : list [str ] | None = None ,
3137
+ * ,
3138
+ quiet : bool | None = None ,
3139
+ cached : bool | None = None , # Only when no command entered and status
3140
+ # Pass-through to run()
3141
+ log_in_real_time : bool = False ,
3142
+ check_returncode : bool | None = None ,
3143
+ ** kwargs : t .Any ,
3144
+ ) -> str :
3145
+ """Run a command against a git repository's branches.
3146
+
3147
+ Wraps `git branch <https://git-scm.com/docs/git-branch>`_.
3148
+
3149
+ Examples
3150
+ --------
3151
+ >>> GitBranchManager(path=git_local_clone.path).run()
3152
+ '* master'
3153
+ """
3154
+ local_flags = local_flags if isinstance (local_flags , list ) else []
3155
+ if command is not None :
3156
+ local_flags .insert (0 , command )
3157
+
3158
+ if quiet is True :
3159
+ local_flags .append ("--quiet" )
3160
+ if cached is True :
3161
+ local_flags .append ("--cached" )
3162
+
3163
+ return self .cmd .run (
3164
+ ["branch" , * local_flags ],
3165
+ check_returncode = check_returncode ,
3166
+ log_in_real_time = log_in_real_time ,
3167
+ )
3168
+
3169
+ def checkout (self , * , branch : str ) -> str :
3170
+ """Git branch checkout.
3171
+
3172
+ Examples
3173
+ --------
3174
+ >>> GitBranchManager(path=git_local_clone.path).checkout(branch='master')
3175
+ "Your branch is up to date with 'origin/master'."
3176
+ """
3177
+ return self .cmd .run (
3178
+ [
3179
+ "checkout" ,
3180
+ * [branch ],
3181
+ ],
3182
+ )
3183
+
3184
+ def create (self , * , branch : str ) -> str :
3185
+ """Create a git branch.
3186
+
3187
+ Examples
3188
+ --------
3189
+ >>> GitBranchManager(path=git_local_clone.path).create(branch='master')
3190
+ "fatal: a branch named 'master' already exists"
3191
+ """
3192
+ return self .cmd .run (
3193
+ [
3194
+ "checkout" ,
3195
+ * ["-b" , branch ],
3196
+ ],
3197
+ # Pass-through to run()
3198
+ check_returncode = False ,
3199
+ )
3200
+
3201
+ def _ls (self ) -> list [str ]:
3202
+ """List branches.
3203
+
3204
+ Examples
3205
+ --------
3206
+ >>> GitBranchManager(path=git_local_clone.path)._ls()
3207
+ ['* master']
3208
+ """
3209
+ return self .run (
3210
+ "--list" ,
3211
+ ).splitlines ()
3212
+
3213
+ def ls (self ) -> QueryList [GitBranchCmd ]:
3214
+ """List branches.
3215
+
3216
+ Examples
3217
+ --------
3218
+ >>> GitBranchManager(path=git_local_clone.path).ls()
3219
+ [<GitBranchCmd path=... branch_name=master>]
3220
+ """
3221
+ return QueryList (
3222
+ [
3223
+ GitBranchCmd (path = self .path , branch_name = branch_name .lstrip ("* " ))
3224
+ for branch_name in self ._ls ()
3225
+ ],
3226
+ )
3227
+
3228
+ def get (self , * args : t .Any , ** kwargs : t .Any ) -> GitBranchCmd | None :
3229
+ """Get branch via filter lookup.
3230
+
3231
+ Examples
3232
+ --------
3233
+ >>> GitBranchManager(
3234
+ ... path=git_local_clone.path
3235
+ ... ).get(branch_name='master')
3236
+ <GitBranchCmd path=... branch_name=master>
3237
+
3238
+ >>> GitBranchManager(
3239
+ ... path=git_local_clone.path
3240
+ ... ).get(branch_name='unknown')
3241
+ Traceback (most recent call last):
3242
+ exec(compile(example.source, filename, "single",
3243
+ ...
3244
+ return self.ls().get(*args, **kwargs)
3245
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3246
+ File "..._internal/query_list.py", line ..., in get
3247
+ raise ObjectDoesNotExist
3248
+ libvcs._internal.query_list.ObjectDoesNotExist
3249
+ """
3250
+ return self .ls ().get (* args , ** kwargs )
3251
+
3252
+ def filter (self , * args : t .Any , ** kwargs : t .Any ) -> list [GitBranchCmd ]:
3253
+ """Get branches via filter lookup.
3254
+
3255
+ Examples
3256
+ --------
3257
+ >>> GitBranchManager(
3258
+ ... path=git_local_clone.path
3259
+ ... ).filter(branch_name__contains='master')
3260
+ [<GitBranchCmd path=... branch_name=master>]
3261
+
3262
+ >>> GitBranchManager(
3263
+ ... path=git_local_clone.path
3264
+ ... ).filter(branch_name__contains='unknown')
3265
+ []
3266
+ """
3267
+ return self .ls ().filter (* args , ** kwargs )
0 commit comments