|
11 | 11 |
|
12 | 12 | fromgit.typesimportPathLike,Commit_ish
|
13 | 13 |
|
| 14 | +ifTYPE_CHECKING: |
| 15 | +fromgit.repoimportRepo |
| 16 | +fromgit.objectsimportCommitfromgit.configimportGitConfigParser,SectionConstraint |
| 17 | +fromgit.utilimportjoin_path |
| 18 | +fromgit.excimportGitCommandError |
| 19 | + |
| 20 | +from .symbolicimportSymbolicReference |
| 21 | +from .referenceimportReference |
| 22 | + |
| 23 | +# typinng --------------------------------------------------- |
| 24 | + |
| 25 | +fromtypingimportAny,Sequence,Union,TYPE_CHECKING |
| 26 | + |
| 27 | +fromgit.typesimportPathLike,Commit_ish |
| 28 | + |
14 | 29 | ifTYPE_CHECKING:
|
15 | 30 | fromgit.repoimportRepo
|
16 | 31 | fromgit.objectsimportCommit
|
@@ -106,6 +121,254 @@ def reset(self, commit: Union[Commit_ish, SymbolicReference, str] = 'HEAD',
|
106 | 121 | returnself
|
107 | 122 |
|
108 | 123 |
|
| 124 | +classHead(Reference): |
| 125 | + |
| 126 | +"""A Head is a named reference to a Commit. Every Head instance contains a name |
| 127 | + and a Commit object. |
| 128 | +
|
| 129 | + Examples:: |
| 130 | +
|
| 131 | + >>> repo = Repo("/path/to/repo") |
| 132 | + >>> head = repo.heads[0] |
| 133 | +
|
| 134 | + >>> head.name |
| 135 | + 'master' |
| 136 | +
|
| 137 | + >>> head.commit |
| 138 | + <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455"> |
| 139 | +
|
| 140 | + >>> head.commit.hexsha |
| 141 | + '1c09f116cbc2cb4100fb6935bb162daa4723f455'""" |
| 142 | +_common_path_default="refs/heads" |
| 143 | +k_config_remote="remote" |
| 144 | +k_config_remote_ref="merge"# branch to merge from remote |
| 145 | + |
| 146 | +@classmethod |
| 147 | +defdelete(cls,repo:'Repo',*heads:'Head',**kwargs:Any): |
| 148 | +"""Delete the given heads |
| 149 | +
|
| 150 | + :param force: |
| 151 | + If True, the heads will be deleted even if they are not yet merged into |
| 152 | + the main development stream. |
| 153 | + Default False""" |
| 154 | +force=kwargs.get("force",False) |
| 155 | +flag="-d" |
| 156 | +ifforce: |
| 157 | +flag="-D" |
| 158 | +repo.git.branch(flag,*heads) |
| 159 | + |
| 160 | +defset_tracking_branch(self,remote_reference:Union['RemoteReference',None])->'Head': |
| 161 | +""" |
| 162 | + Configure this branch to track the given remote reference. This will alter |
| 163 | + this branch's configuration accordingly. |
| 164 | +
|
| 165 | + :param remote_reference: The remote reference to track or None to untrack |
| 166 | + any references |
| 167 | + :return: self""" |
| 168 | +from .remoteimportRemoteReference |
| 169 | +ifremote_referenceisnotNoneandnotisinstance(remote_reference,RemoteReference): |
| 170 | +raiseValueError("Incorrect parameter type: %r"%remote_reference) |
| 171 | +# END handle type |
| 172 | + |
| 173 | +withself.config_writer()aswriter: |
| 174 | +ifremote_referenceisNone: |
| 175 | +writer.remove_option(self.k_config_remote) |
| 176 | +writer.remove_option(self.k_config_remote_ref) |
| 177 | +iflen(writer.options())==0: |
| 178 | +writer.remove_section() |
| 179 | +else: |
| 180 | +writer.set_value(self.k_config_remote,remote_reference.remote_name) |
| 181 | +writer.set_value(self.k_config_remote_ref,Head.to_full_path(remote_reference.remote_head)) |
| 182 | + |
| 183 | +returnself |
| 184 | + |
| 185 | +deftracking_branch(self)->Union['RemoteReference',None]: |
| 186 | +""" |
| 187 | + :return: The remote_reference we are tracking, or None if we are |
| 188 | + not a tracking branch""" |
| 189 | +from .remoteimportRemoteReference |
| 190 | +reader=self.config_reader() |
| 191 | +ifreader.has_option(self.k_config_remote)andreader.has_option(self.k_config_remote_ref): |
| 192 | +ref=Head(self.repo,Head.to_full_path(strip_quotes(reader.get_value(self.k_config_remote_ref)))) |
| 193 | +remote_refpath=RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote),ref.name)) |
| 194 | +returnRemoteReference(self.repo,remote_refpath) |
| 195 | +# END handle have tracking branch |
| 196 | + |
| 197 | +# we are not a tracking branch |
| 198 | +returnNone |
| 199 | + |
| 200 | +defrename(self,new_path:PathLike,force:bool=False)->'Head': |
| 201 | +"""Rename self to a new path |
| 202 | +
|
| 203 | + :param new_path: |
| 204 | + Either a simple name or a path, i.e. new_name or features/new_name. |
| 205 | + The prefix refs/heads is implied |
| 206 | +
|
| 207 | + :param force: |
| 208 | + If True, the rename will succeed even if a head with the target name |
| 209 | + already exists. |
| 210 | +
|
| 211 | + :return: self |
| 212 | + :note: respects the ref log as git commands are used""" |
| 213 | +flag="-m" |
| 214 | +ifforce: |
| 215 | +flag="-M" |
| 216 | + |
| 217 | +self.repo.git.branch(flag,self,new_path) |
| 218 | +self.path="%s/%s"% (self._common_path_default,new_path) |
| 219 | +returnself |
| 220 | + |
| 221 | +defcheckout(self,force:bool=False,**kwargs:Any)->Union['HEAD','Head']: |
| 222 | +"""Checkout this head by setting the HEAD to this reference, by updating the index |
| 223 | + to reflect the tree we point to and by updating the working tree to reflect |
| 224 | + the latest index. |
| 225 | +
|
| 226 | + The command will fail if changed working tree files would be overwritten. |
| 227 | +
|
| 228 | + :param force: |
| 229 | + If True, changes to the index and the working tree will be discarded. |
| 230 | + If False, GitCommandError will be raised in that situation. |
| 231 | +
|
| 232 | + :param kwargs: |
| 233 | + Additional keyword arguments to be passed to git checkout, i.e. |
| 234 | + b='new_branch' to create a new branch at the given spot. |
| 235 | +
|
| 236 | + :return: |
| 237 | + The active branch after the checkout operation, usually self unless |
| 238 | + a new branch has been created. |
| 239 | + If there is no active branch, as the HEAD is now detached, the HEAD |
| 240 | + reference will be returned instead. |
| 241 | +
|
| 242 | + :note: |
| 243 | + By default it is only allowed to checkout heads - everything else |
| 244 | + will leave the HEAD detached which is allowed and possible, but remains |
| 245 | + a special state that some tools might not be able to handle.""" |
| 246 | +kwargs['f']=force |
| 247 | +ifkwargs['f']isFalse: |
| 248 | +kwargs.pop('f') |
| 249 | + |
| 250 | +self.repo.git.checkout(self,**kwargs) |
| 251 | +ifself.repo.head.is_detached: |
| 252 | +returnself.repo.head |
| 253 | +else: |
| 254 | +returnself.repo.active_branch |
| 255 | + |
| 256 | +#{ Configuration |
| 257 | +def_config_parser(self,read_only:bool)->SectionConstraint[GitConfigParser]: |
| 258 | +ifread_only: |
| 259 | +parser=self.repo.config_reader() |
| 260 | +else: |
| 261 | +parser=self.repo.config_writer() |
| 262 | +# END handle parser instance |
| 263 | + |
| 264 | +returnSectionConstraint(parser,'branch "%s"'%self.name) |
| 265 | + |
| 266 | +defconfig_reader(self)->SectionConstraint[GitConfigParser]: |
| 267 | +""" |
| 268 | + :return: A configuration parser instance constrained to only read |
| 269 | + this instance's values""" |
| 270 | +returnself._config_parser(read_only=True) |
| 271 | + |
| 272 | +defconfig_writer(self)->SectionConstraint[GitConfigParser]: |
| 273 | +""" |
| 274 | + :return: A configuration writer instance with read-and write access |
| 275 | + to options of this head""" |
| 276 | +returnself._config_parser(read_only=False) |
| 277 | + |
| 278 | +#} END configuration |
| 279 | + |
| 280 | +fromgit.refsimportRemoteReference |
| 281 | + |
| 282 | +# ------------------------------------------------------------------- |
| 283 | + |
| 284 | +__all__= ["HEAD","Head"] |
| 285 | + |
| 286 | + |
| 287 | +defstrip_quotes(string): |
| 288 | +ifstring.startswith('"')andstring.endswith('"'): |
| 289 | +returnstring[1:-1] |
| 290 | +returnstring |
| 291 | + |
| 292 | + |
| 293 | +classHEAD(SymbolicReference): |
| 294 | + |
| 295 | +"""Special case of a Symbolic Reference as it represents the repository's |
| 296 | + HEAD reference.""" |
| 297 | +_HEAD_NAME='HEAD' |
| 298 | +_ORIG_HEAD_NAME='ORIG_HEAD' |
| 299 | +__slots__= () |
| 300 | + |
| 301 | +def__init__(self,repo:'Repo',path:PathLike=_HEAD_NAME): |
| 302 | +ifpath!=self._HEAD_NAME: |
| 303 | +raiseValueError("HEAD instance must point to %r, got %r"% (self._HEAD_NAME,path)) |
| 304 | +super(HEAD,self).__init__(repo,path) |
| 305 | +self.commit:'Commit' |
| 306 | + |
| 307 | +deforig_head(self)->SymbolicReference: |
| 308 | +""" |
| 309 | + :return: SymbolicReference pointing at the ORIG_HEAD, which is maintained |
| 310 | + to contain the previous value of HEAD""" |
| 311 | +returnSymbolicReference(self.repo,self._ORIG_HEAD_NAME) |
| 312 | + |
| 313 | +defreset(self,commit:Union[Commit_ish,SymbolicReference,str]='HEAD', |
| 314 | +index:bool=True,working_tree:bool=False, |
| 315 | +paths:Union[PathLike,Sequence[PathLike],None]=None,**kwargs:Any)->'HEAD': |
| 316 | +"""Reset our HEAD to the given commit optionally synchronizing |
| 317 | + the index and working tree. The reference we refer to will be set to |
| 318 | + commit as well. |
| 319 | +
|
| 320 | + :param commit: |
| 321 | + Commit object, Reference Object or string identifying a revision we |
| 322 | + should reset HEAD to. |
| 323 | +
|
| 324 | + :param index: |
| 325 | + If True, the index will be set to match the given commit. Otherwise |
| 326 | + it will not be touched. |
| 327 | +
|
| 328 | + :param working_tree: |
| 329 | + If True, the working tree will be forcefully adjusted to match the given |
| 330 | + commit, possibly overwriting uncommitted changes without warning. |
| 331 | + If working_tree is True, index must be true as well |
| 332 | +
|
| 333 | + :param paths: |
| 334 | + Single path or list of paths relative to the git root directory |
| 335 | + that are to be reset. This allows to partially reset individual files. |
| 336 | +
|
| 337 | + :param kwargs: |
| 338 | + Additional arguments passed to git-reset. |
| 339 | +
|
| 340 | + :return: self""" |
| 341 | +mode:Union[str,None] |
| 342 | +mode="--soft" |
| 343 | +ifindex: |
| 344 | +mode="--mixed" |
| 345 | + |
| 346 | +# it appears, some git-versions declare mixed and paths deprecated |
| 347 | +# see http://github.com/Byron/GitPython/issues#issue/2 |
| 348 | +ifpaths: |
| 349 | +mode=None |
| 350 | +# END special case |
| 351 | +# END handle index |
| 352 | + |
| 353 | +ifworking_tree: |
| 354 | +mode="--hard" |
| 355 | +ifnotindex: |
| 356 | +raiseValueError("Cannot reset the working tree if the index is not reset as well") |
| 357 | + |
| 358 | +# END working tree handling |
| 359 | + |
| 360 | +try: |
| 361 | +self.repo.git.reset(mode,commit,'--',paths,**kwargs) |
| 362 | +exceptGitCommandErrorase: |
| 363 | +# git nowadays may use 1 as status to indicate there are still unstaged |
| 364 | +# modifications after the reset |
| 365 | +ife.status!=1: |
| 366 | +raise |
| 367 | +# END handle exception |
| 368 | + |
| 369 | +returnself |
| 370 | + |
| 371 | + |
109 | 372 | classHead(Reference):
|
110 | 373 |
|
111 | 374 | """A Head is a named reference to a Commit. Every Head instance contains a name
|
|