|
| 1 | +""" Module containing all ref based objects """ |
| 2 | +fromsymbolicimportSymbolicReference |
| 3 | +fromreferenceimportReference |
| 4 | + |
| 5 | +fromgit.configimportSectionConstraint |
| 6 | + |
| 7 | +fromgit.excimportGitCommandError |
| 8 | + |
| 9 | +__all__= ["HEAD","Head"] |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +classHEAD(SymbolicReference): |
| 14 | +"""Special case of a Symbolic Reference as it represents the repository's |
| 15 | +HEAD reference.""" |
| 16 | +_HEAD_NAME='HEAD' |
| 17 | +_ORIG_HEAD_NAME='ORIG_HEAD' |
| 18 | +__slots__=tuple() |
| 19 | + |
| 20 | +def__init__(self,repo,path=_HEAD_NAME): |
| 21 | +ifpath!=self._HEAD_NAME: |
| 22 | +raiseValueError("HEAD instance must point to %r, got %r"% (self._HEAD_NAME,path)) |
| 23 | +super(HEAD,self).__init__(repo,path) |
| 24 | + |
| 25 | +deforig_head(self): |
| 26 | +""" |
| 27 | +:return: SymbolicReference pointing at the ORIG_HEAD, which is maintained |
| 28 | +to contain the previous value of HEAD""" |
| 29 | +returnSymbolicReference(self.repo,self._ORIG_HEAD_NAME) |
| 30 | + |
| 31 | +def_set_reference(self,ref): |
| 32 | +"""If someone changes the reference through us, we must manually update |
| 33 | +the ORIG_HEAD if we are detached. The underlying implementation can only |
| 34 | +handle un-detached heads as it has to check whether the current head |
| 35 | +is the checked-out one""" |
| 36 | +ifself.is_detached: |
| 37 | +prev_commit=self.commit |
| 38 | +super(HEAD,self)._set_reference(ref) |
| 39 | +SymbolicReference.create(self.repo,self._ORIG_HEAD_NAME,prev_commit,force=True) |
| 40 | +else: |
| 41 | +super(HEAD,self)._set_reference(ref) |
| 42 | +# END handle detached mode |
| 43 | + |
| 44 | +# aliased reference |
| 45 | +reference=property(SymbolicReference._get_reference,_set_reference,doc="Returns the Reference we point to") |
| 46 | +ref=reference |
| 47 | + |
| 48 | +defreset(self,commit='HEAD',index=True,working_tree=False, |
| 49 | +paths=None,**kwargs): |
| 50 | +"""Reset our HEAD to the given commit optionally synchronizing |
| 51 | +the index and working tree. The reference we refer to will be set to |
| 52 | +commit as well. |
| 53 | +
|
| 54 | +:param commit: |
| 55 | +Commit object, Reference Object or string identifying a revision we |
| 56 | +should reset HEAD to. |
| 57 | +
|
| 58 | +:param index: |
| 59 | +If True, the index will be set to match the given commit. Otherwise |
| 60 | +it will not be touched. |
| 61 | +
|
| 62 | +:param working_tree: |
| 63 | +If True, the working tree will be forcefully adjusted to match the given |
| 64 | +commit, possibly overwriting uncommitted changes without warning. |
| 65 | +If working_tree is True, index must be true as well |
| 66 | +
|
| 67 | +:param paths: |
| 68 | +Single path or list of paths relative to the git root directory |
| 69 | +that are to be reset. This allows to partially reset individual files. |
| 70 | +
|
| 71 | +:param kwargs: |
| 72 | +Additional arguments passed to git-reset. |
| 73 | +
|
| 74 | +:return: self""" |
| 75 | +mode="--soft" |
| 76 | +add_arg=None |
| 77 | +ifindex: |
| 78 | +mode="--mixed" |
| 79 | + |
| 80 | +# it appears, some git-versions declare mixed and paths deprecated |
| 81 | +# see http://github.com/Byron/GitPython/issues#issue/2 |
| 82 | +ifpaths: |
| 83 | +mode=None |
| 84 | +# END special case |
| 85 | +# END handle index |
| 86 | + |
| 87 | +ifworking_tree: |
| 88 | +mode="--hard" |
| 89 | +ifnotindex: |
| 90 | +raiseValueError("Cannot reset the working tree if the index is not reset as well") |
| 91 | + |
| 92 | +# END working tree handling |
| 93 | + |
| 94 | +ifpaths: |
| 95 | +add_arg="--" |
| 96 | +# END nicely separate paths from rest |
| 97 | + |
| 98 | +try: |
| 99 | +self.repo.git.reset(mode,commit,add_arg,paths,**kwargs) |
| 100 | +exceptGitCommandError,e: |
| 101 | +# git nowadays may use 1 as status to indicate there are still unstaged |
| 102 | +# modifications after the reset |
| 103 | +ife.status!=1: |
| 104 | +raise |
| 105 | +# END handle exception |
| 106 | + |
| 107 | +returnself |
| 108 | + |
| 109 | + |
| 110 | +classHead(Reference): |
| 111 | +"""A Head is a named reference to a Commit. Every Head instance contains a name |
| 112 | +and a Commit object. |
| 113 | +
|
| 114 | +Examples:: |
| 115 | +
|
| 116 | +>>> repo = Repo("/path/to/repo") |
| 117 | +>>> head = repo.heads[0] |
| 118 | +
|
| 119 | +>>> head.name |
| 120 | +'master' |
| 121 | +
|
| 122 | +>>> head.commit |
| 123 | +<git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455"> |
| 124 | +
|
| 125 | +>>> head.commit.hexsha |
| 126 | +'1c09f116cbc2cb4100fb6935bb162daa4723f455'""" |
| 127 | +_common_path_default="refs/heads" |
| 128 | +k_config_remote="remote" |
| 129 | +k_config_remote_ref="merge"# branch to merge from remote |
| 130 | + |
| 131 | +@classmethod |
| 132 | +defcreate(cls,repo,path,commit='HEAD',force=False,**kwargs): |
| 133 | +"""Create a new head. |
| 134 | +:param repo: Repository to create the head in |
| 135 | +:param path: |
| 136 | +The name or path of the head, i.e. 'new_branch' or |
| 137 | +feature/feature1. The prefix refs/heads is implied. |
| 138 | +
|
| 139 | +:param commit: |
| 140 | +Commit to which the new head should point, defaults to the |
| 141 | +current HEAD |
| 142 | +
|
| 143 | +:param force: |
| 144 | +if True, force creation even if branch with that name already exists. |
| 145 | +
|
| 146 | +:param kwargs: |
| 147 | +Additional keyword arguments to be passed to git-branch, i.e. |
| 148 | +track, no-track, l |
| 149 | +
|
| 150 | +:return: Newly created Head |
| 151 | +:note: This does not alter the current HEAD, index or Working Tree""" |
| 152 | +ifclsisnotHead: |
| 153 | +raiseTypeError("Only Heads can be created explicitly, not objects of type %s"%cls.__name__) |
| 154 | + |
| 155 | +args= (path,commit ) |
| 156 | +ifforce: |
| 157 | +kwargs['f']=True |
| 158 | + |
| 159 | +repo.git.branch(*args,**kwargs) |
| 160 | +returncls(repo,"%s/%s"% (cls._common_path_default,path)) |
| 161 | + |
| 162 | + |
| 163 | +@classmethod |
| 164 | +defdelete(cls,repo,*heads,**kwargs): |
| 165 | +"""Delete the given heads |
| 166 | +:param force: |
| 167 | +If True, the heads will be deleted even if they are not yet merged into |
| 168 | +the main development stream. |
| 169 | +Default False""" |
| 170 | +force=kwargs.get("force",False) |
| 171 | +flag="-d" |
| 172 | +ifforce: |
| 173 | +flag="-D" |
| 174 | +repo.git.branch(flag,*heads) |
| 175 | + |
| 176 | + |
| 177 | +defset_tracking_branch(self,remote_reference): |
| 178 | +""" |
| 179 | +Configure this branch to track the given remote reference. This will alter |
| 180 | +this branch's configuration accordingly. |
| 181 | +
|
| 182 | +:param remote_reference: The remote reference to track or None to untrack |
| 183 | +any references |
| 184 | +:return: self""" |
| 185 | +ifremote_referenceisnotNoneandnotisinstance(remote_reference,RemoteReference): |
| 186 | +raiseValueError("Incorrect parameter type: %r"%remote_reference) |
| 187 | +# END handle type |
| 188 | + |
| 189 | +writer=self.config_writer() |
| 190 | +ifremote_referenceisNone: |
| 191 | +writer.remove_option(self.k_config_remote) |
| 192 | +writer.remove_option(self.k_config_remote_ref) |
| 193 | +iflen(writer.options())==0: |
| 194 | +writer.remove_section() |
| 195 | +# END handle remove section |
| 196 | +else: |
| 197 | +writer.set_value(self.k_config_remote,remote_reference.remote_name) |
| 198 | +writer.set_value(self.k_config_remote_ref,Head.to_full_path(remote_reference.remote_head)) |
| 199 | +# END handle ref value |
| 200 | + |
| 201 | +returnself |
| 202 | + |
| 203 | + |
| 204 | +deftracking_branch(self): |
| 205 | +""" |
| 206 | +:return: The remote_reference we are tracking, or None if we are |
| 207 | +not a tracking branch""" |
| 208 | +reader=self.config_reader() |
| 209 | +ifreader.has_option(self.k_config_remote)andreader.has_option(self.k_config_remote_ref): |
| 210 | +ref=Head(self.repo,Head.to_full_path(reader.get_value(self.k_config_remote_ref))) |
| 211 | +remote_refpath=RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote),ref.name)) |
| 212 | +returnRemoteReference(self.repo,remote_refpath) |
| 213 | +# END handle have tracking branch |
| 214 | + |
| 215 | +# we are not a tracking branch |
| 216 | +returnNone |
| 217 | + |
| 218 | +defrename(self,new_path,force=False): |
| 219 | +"""Rename self to a new path |
| 220 | +
|
| 221 | +:param new_path: |
| 222 | +Either a simple name or a path, i.e. new_name or features/new_name. |
| 223 | +The prefix refs/heads is implied |
| 224 | +
|
| 225 | +:param force: |
| 226 | +If True, the rename will succeed even if a head with the target name |
| 227 | +already exists. |
| 228 | +
|
| 229 | +:return: self |
| 230 | +:note: respects the ref log as git commands are used""" |
| 231 | +flag="-m" |
| 232 | +ifforce: |
| 233 | +flag="-M" |
| 234 | + |
| 235 | +self.repo.git.branch(flag,self,new_path) |
| 236 | +self.path="%s/%s"% (self._common_path_default,new_path) |
| 237 | +returnself |
| 238 | + |
| 239 | +defcheckout(self,force=False,**kwargs): |
| 240 | +"""Checkout this head by setting the HEAD to this reference, by updating the index |
| 241 | +to reflect the tree we point to and by updating the working tree to reflect |
| 242 | +the latest index. |
| 243 | +
|
| 244 | +The command will fail if changed working tree files would be overwritten. |
| 245 | +
|
| 246 | +:param force: |
| 247 | +If True, changes to the index and the working tree will be discarded. |
| 248 | +If False, GitCommandError will be raised in that situation. |
| 249 | +
|
| 250 | +:param kwargs: |
| 251 | +Additional keyword arguments to be passed to git checkout, i.e. |
| 252 | +b='new_branch' to create a new branch at the given spot. |
| 253 | +
|
| 254 | +:return: |
| 255 | +The active branch after the checkout operation, usually self unless |
| 256 | +a new branch has been created. |
| 257 | +
|
| 258 | +:note: |
| 259 | +By default it is only allowed to checkout heads - everything else |
| 260 | +will leave the HEAD detached which is allowed and possible, but remains |
| 261 | +a special state that some tools might not be able to handle.""" |
| 262 | +args=list() |
| 263 | +kwargs['f']=force |
| 264 | +ifkwargs['f']==False: |
| 265 | +kwargs.pop('f') |
| 266 | + |
| 267 | +self.repo.git.checkout(self,**kwargs) |
| 268 | +returnself.repo.active_branch |
| 269 | + |
| 270 | +#{ Configruation |
| 271 | + |
| 272 | +def_config_parser(self,read_only): |
| 273 | +ifread_only: |
| 274 | +parser=self.repo.config_reader() |
| 275 | +else: |
| 276 | +parser=self.repo.config_writer() |
| 277 | +# END handle parser instance |
| 278 | + |
| 279 | +returnSectionConstraint(parser,'branch "%s"'%self.name) |
| 280 | + |
| 281 | +defconfig_reader(self): |
| 282 | +""" |
| 283 | +:return: A configuration parser instance constrained to only read |
| 284 | +this instance's values""" |
| 285 | +returnself._config_parser(read_only=True) |
| 286 | + |
| 287 | +defconfig_writer(self): |
| 288 | +""" |
| 289 | +:return: A configuration writer instance with read-and write acccess |
| 290 | +to options of this head""" |
| 291 | +returnself._config_parser(read_only=False) |
| 292 | + |
| 293 | +#} END configuration |
| 294 | + |
| 295 | + |