From 2c23ca3cd9b9bbeaca1b79068dee1eae045be5b6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 23 Oct 2009 14:46:18 +0200 Subject: refs: added create, delete and rename methods where appropriate. Tests are marked, implementation is needed for most of them --- lib/git/refs.py | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) (limited to 'lib/git/refs.py') diff --git a/lib/git/refs.py b/lib/git/refs.py index c8857e97..29468d1c 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -182,6 +182,7 @@ class Reference(LazyMixin, Iterable): # obj.size = object_size # return cls(repo, full_path, obj) + class SymbolicReference(object): """ @@ -384,12 +385,80 @@ class Head(Reference): """ _common_path_default = "refs/heads" + @classmethod + def create(cls, repo, path, commit=HEAD(), **kwargs ): + """ + Create a new head. + ``repo`` + Repository to create the head in + + ``path`` + The name or path of the head, i.e. 'new_branch' or + feature/feature1. The prefix refs/heads is implied. + + ``commit`` + Commit to which the new head should point, defaults to the + current HEAD + + ``**kwargs`` + Additional keyword arguments to be passed to git-branch, i.e. + track, no-track, l, f to force creation even if branch with that + name already exists. + + Returns + Newly created Head + + Note + This does not alter the current HEAD, index or Working Tree + """ + raise NotImplementedError("todo") + + + @classmethod + def delete(cls, repo, *heads, force=False): + """ + Delete the given heads + + ``force`` + If True, the heads will be deleted even if they are not yet merged into + the main development stream + """ + flag = "-d" + if force: + flag = "-D" + repo.git.branch(flag, *heads) + + + def rename(self, new_path, force=False): + """ + Rename self to a new path + + ``new_path`` + Either a simple name or a path, i.e. new_name or features/new_name. + The prefix refs/heads is implied + + ``force`` + If True, the rename will succeed even if a head with the target name + already exists. + + Returns + self + """ + flag = "-m" + if force: + flag = "-M" + + self.repo.git.branch(flag, new_path) + self.path = "%s/%s" % (self._common_path_default, new_path) + return self + + class TagReference(Reference): """ Class representing a lightweight tag reference which either points to a commit - or to a tag object. In the latter case additional information, like the signature - or the tag-creator, is available. + ,a tag object or any other object. In the latter case additional information, + like the signature or the tag-creator, is available. This tag object will always point to a commit object, but may carray additional information in a tag object:: @@ -427,6 +496,43 @@ class TagReference(Reference): if self.object.type == "tag": return self.object return None + + @classmethod + def create(cls, repo, path, ref, message=None, **kwargs): + """ + Create a new tag object. + + ``path`` + The name of the tag, i.e. 1.0 or releases/1.0. + The prefix refs/tags is implied + + ``ref`` + A reference to the object you want to tag. It can be a commit, tree or + blob. + + ``message`` + If not None, the message will be used in your tag object. This will also + create an additional tag object that allows to obtain that information, i.e.:: + tagref.tag.message + + ``**kwargs`` + Additional keyword arguments to be passed to git-tag, i.e. f to force creation + of a tag. + + Returns + A new TagReference + """ + raise NotImplementedError("todo") + + @classmethod + def delete(cls, repo, *tags): + """ + Delete the given existing tag or tags + """ + repo.git.tag("-d", *tags) + + + # provide an alias @@ -460,3 +566,10 @@ class RemoteReference(Head): """ tokens = self.path.split('/') return '/'.join(tokens[3:]) + + @classmethod + def delete(cls, repo, *remotes): + """ + Delete the given remote references. + """ + repo.git.branch("-d", "-r", *remotes) -- cgit v1.2.1 From ddc5496506f0484e4f1331261aa8782c7e606bf2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 23 Oct 2009 15:22:07 +0200 Subject: Implemented head methods: create, delete, rename, including tests --- lib/git/refs.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'lib/git/refs.py') diff --git a/lib/git/refs.py b/lib/git/refs.py index 29468d1c..7b58f215 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -19,7 +19,7 @@ class Reference(LazyMixin, Iterable): _common_path_default = "refs" _id_attribute_ = "name" - def __init__(self, repo, path, object = None): + def __init__(self, repo, path): """ Initialize this instance ``repo`` @@ -29,16 +29,12 @@ class Reference(LazyMixin, Iterable): Path relative to the .git/ directory pointing to the ref in question, i.e. refs/heads/master - ``object`` - Object instance, will be retrieved on demand if None """ if not path.startswith(self._common_path_default): raise ValueError("Cannot instantiate %s Reference from path %s" % ( self.__class__.__name__, path )) self.repo = repo self.path = path - if object is not None: - self.object = object def __str__(self): return self.name @@ -386,7 +382,7 @@ class Head(Reference): _common_path_default = "refs/heads" @classmethod - def create(cls, repo, path, commit=HEAD(), **kwargs ): + def create(cls, repo, path, commit='HEAD', force=False, **kwargs ): """ Create a new head. ``repo`` @@ -399,11 +395,13 @@ class Head(Reference): ``commit`` Commit to which the new head should point, defaults to the current HEAD + + ``force`` + if True, force creation even if branch with that name already exists. ``**kwargs`` Additional keyword arguments to be passed to git-branch, i.e. - track, no-track, l, f to force creation even if branch with that - name already exists. + track, no-track, l Returns Newly created Head @@ -411,18 +409,28 @@ class Head(Reference): Note This does not alter the current HEAD, index or Working Tree """ - raise NotImplementedError("todo") + if cls is not Head: + raise TypeError("Only Heads can be created explicitly, not objects of type %s" % cls.__name__) + + args = ( path, commit ) + if force: + kwargs['f'] = True + + repo.git.branch(*args, **kwargs) + return cls(repo, "%s/%s" % ( cls._common_path_default, path)) @classmethod - def delete(cls, repo, *heads, force=False): + def delete(cls, repo, *heads, **kwargs): """ Delete the given heads ``force`` If True, the heads will be deleted even if they are not yet merged into - the main development stream + the main development stream. + Default False """ + force = kwargs.get("force", False) flag = "-d" if force: flag = "-D" @@ -448,7 +456,7 @@ class Head(Reference): if force: flag = "-M" - self.repo.git.branch(flag, new_path) + self.repo.git.branch(flag, self, new_path) self.path = "%s/%s" % (self._common_path_default, new_path) return self @@ -568,7 +576,7 @@ class RemoteReference(Head): return '/'.join(tokens[3:]) @classmethod - def delete(cls, repo, *remotes): + def delete(cls, repo, *remotes, **kwargs): """ Delete the given remote references. """ -- cgit v1.2.1 From 1047b41e2e925617474e2e7c9927314f71ce7365 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 23 Oct 2009 15:45:23 +0200 Subject: Added TagRefernce creation and deletion including tests Added RemoteReference deletion and test --- lib/git/refs.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'lib/git/refs.py') diff --git a/lib/git/refs.py b/lib/git/refs.py index 7b58f215..0efee52e 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -506,7 +506,7 @@ class TagReference(Reference): return None @classmethod - def create(cls, repo, path, ref, message=None, **kwargs): + def create(cls, repo, path, ref='HEAD', message=None, force=False, **kwargs): """ Create a new tag object. @@ -523,14 +523,23 @@ class TagReference(Reference): create an additional tag object that allows to obtain that information, i.e.:: tagref.tag.message + ``force`` + If True, to force creation of a tag even though that tag already exists. + ``**kwargs`` - Additional keyword arguments to be passed to git-tag, i.e. f to force creation - of a tag. + Additional keyword arguments to be passed to git-tag Returns A new TagReference """ - raise NotImplementedError("todo") + args = ( path, ref ) + if message: + kwargs['m'] = message + if force: + kwargs['f'] = True + + repo.git.tag(*args, **kwargs) + return TagReference(repo, "%s/%s" % (cls._common_path_default, path)) @classmethod def delete(cls, repo, *tags): @@ -579,5 +588,9 @@ class RemoteReference(Head): def delete(cls, repo, *remotes, **kwargs): """ Delete the given remote references. + + Note + kwargs are given for compatability with the base class method as we + should not narrow the signature. """ repo.git.branch("-d", "-r", *remotes) -- cgit v1.2.1 From 9b9776e88f7abb59cebac8733c04cccf6eee1c60 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 23 Oct 2009 16:07:45 +0200 Subject: Refs can now set the reference they are pointing to in a controlled fashion by writing their ref file directly --- lib/git/refs.py | 48 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) (limited to 'lib/git/refs.py') diff --git a/lib/git/refs.py b/lib/git/refs.py index 0efee52e..47c37af6 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -65,8 +65,7 @@ class Reference(LazyMixin, Iterable): return '/'.join(tokens[2:]) - @property - def object(self): + def _get_object(self): """ Returns The object our ref currently refers to. Refs can be cached, they will @@ -76,17 +75,54 @@ class Reference(LazyMixin, Iterable): # Our path will be resolved to the hexsha which will be used accordingly return Object.new(self.repo, self.path) - @property - def commit(self): + def _set_object(self, ref, type=None): """ + Set our reference to point to the given ref. It will be converted + to a specific hexsha. + + ``type`` + If not None, string type of that the object must have, other we raise + a type error. Only used internally + Returns - Commit object the head points to + Object we have set. This is used internally only to reduce the amount + of calls to the git command + """ + obj = Object.new(self.repo, ref) + if type is not None and obj.type != type: + raise TypeError("Reference %r cannot point to object of type %r" % (self,obj.type)) + + full_ref_path = os.path.join(self.repo.path, self.path) + fp = open(full_ref_path, "w") + try: + fp.write(str(obj)) + finally: + fp.close() + return obj + + object = property(_get_object, _set_object, doc="Return the object our ref currently refers to") + + def _set_commit(self, commit): + """ + Set ourselves to point to the given commit. + + Raise + ValueError if commit does not actually point to a commit + """ + self._set_object(commit, type="commit") + + def _get_commit(self): + """ + Returns + Commit object the reference points to """ commit = self.object if commit.type != "commit": raise TypeError("Object of reference %s did not point to a commit" % self) return commit + commit = property(_get_commit, _set_commit, doc="Return Commit object the reference points to") + @classmethod def iter_items(cls, repo, common_path = None, **kwargs): """ @@ -244,7 +280,7 @@ class SymbolicReference(object): try: tokens = fp.readline().rstrip().split(' ') if tokens[0] != 'ref:': - raise TypeError("%s is a detached symbolic reference as it points to %r" % tokens[0]) + raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, tokens[0])) return Reference.from_path(self.repo, tokens[1]) finally: fp.close() -- cgit v1.2.1