<p>Kevin Harwell has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/8290">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">digium_git/version_parser: Update branch and tag functionality<br><br>Updated the utility and other support methods to the following modules:<br><br>digium_git -<br>  * Made it so the local_path expands the '~' for the current user.<br>  * Added ability to add/push multiple branches<br>  * Add abitlity to delete local and remote branches<br>  * Added a find_last_tag function that finds the last tag created tag before a<br>    given version.<br><br>version_parser -<br>  * Added a branch_name method that returns a branch name from the version.<br>  * Added two functions to check if the version represents either a first pre-<br>    release, or a first branch release version.<br><br>Change-Id: I5a7f44e75ae3d867f9795b7fb0ea5bff526b5429<br>---<br>M digium_git.py<br>M version_parser.py<br>2 files changed, 173 insertions(+), 22 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/repotools refs/changes/90/8290/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/digium_git.py b/digium_git.py<br>index 3536522..e28642f 100644<br>--- a/digium_git.py<br>+++ b/digium_git.py<br>@@ -17,6 +17,7 @@<br> from progressbar import ProgressBar<br> from digium_commits import DigiumCommitMessageParser<br> from git import Repo, RemoteProgress<br>+from version_parser import AsteriskVersion<br> <br> LOGGER = logging.getLogger(__name__)<br> <br>@@ -166,8 +167,11 @@<br> <br>         self.show_progress = show_progress<br>         self.current_branch = None<br>+        self.updated_branches = [] # Local new or modified branches<br>+        self.deleted_branches = [] # Locally deleted branches<br>         progress = None<br> <br>+        local_path = os.path.expanduser(local_path)<br>         if os.path.isdir(local_path):<br>             self.repo = Repo(local_path)<br>             origin = self.repo.remotes.origin<br>@@ -183,7 +187,6 @@<br> <br>         origin.fetch(progress=self.progress)<br>         origin.fetch(progress=self.progress)<br>-<br> <br>     @property<br>     def progress(self):<br>@@ -208,17 +211,6 @@<br>             pass<br>         return False<br> <br>-    def _push_branch(self):<br>-        """Push the currently checked out local branch to the remote"""<br>-<br>-        assert self.current_branch is not None<br>-<br>-        LOGGER.debug("Pushing branch {0} to remote".format(self.current_branch))<br>-        self.repo.remotes.origin.push(refspec='refs/heads/{0}:refs/heads/{1}'.format(<br>-            self.current_branch, self.current_branch), progress=self.progress)<br>-        self._set_tracking(self.repo.heads[self.current_branch])<br>-<br>-<br>     def branch_exists(self, name):<br>         """Determine if a branch exists<br> <br>@@ -232,13 +224,62 @@<br> <br>         return name in self.repo.heads<br> <br>+    def _push_updated_branches(self):<br>+        """Push new or updated branches to the remote repository."""<br>+<br>+        if not self.updated_branches:<br>+            return<br>+<br>+        for b in self.updated_branches:<br>+            LOGGER.debug("Pushing branch '{0}' to remote".format(b))<br>+<br>+            self.repo.remotes.origin.push(<br>+                refspec='refs/heads/{0}:refs/heads/{1}'.format(<br>+                    b, b), progress=self.progress)<br>+<br>+            self._set_tracking(self.repo.heads[b])<br>+<br>+        self.updated_branches = []<br>+<br>+    def _push_deleted_branches(self):<br>+        """Delete a branch(es) from the remote repository."""<br>+<br>+        if not self.deleted_branches:<br>+            return<br>+<br>+        for b in self.deleted_branches:<br>+            try:<br>+                LOGGER.debug("Pushing/Deleting remote branch '{0}'".format(b))<br>+                self.repo.remotes.origin.push(':{0}'.format(b), self.progress)<br>+            except:<br>+                LOGGER.debug("Remote branch '{0}' does not exist. "<br>+                             "Nothing to delete".format(b))<br>+<br>+        self.deleted_branches = []<br>+<br>+    def delete_branch(self, branch):<br>+        """Delete a branch from the local repository.<br>+<br>+        Note: The branch to delete cannot be currently checked out.<br>+<br>+        Keyword Arguments:<br>+        branch - The branch to remove<br>+        """<br>+<br>+        self.deleted_branches.append(branch)<br>+        self.updated_branches.remove(branch)<br>+        try:<br>+            LOGGER.debug("Deleting local branch '{0}'".format(branch))<br>+            self.repo.delete_head(branch)<br>+        except:<br>+            LOGGER.debug("Local branch '{0}' does not exist. "<br>+                         "Nothing to delete".format(branch))<br> <br>     def _push_tags(self):<br>         """Push any locally created tags to the remote"""<br> <br>         LOGGER.debug("Pushing any locally created tag(s) to remote")<br>         self.repo.remotes.origin.push(progress=self.progress, tags=True)<br>-<br> <br>     def tag_exists(self, name):<br>         """Determine if a tag exists locally<br>@@ -253,6 +294,58 @@<br> <br>         return name in self.repo.tags<br> <br>+    def find_tags(self, sub):<br>+        """Find all tags matching starting with given substring<br>+<br>+        Keyword Arguments:<br>+        sub - The sub string to match against<br>+<br>+        Returns:<br>+        A list of tags that contain the given substring<br>+        """<br>+<br>+        tags = [t for t in self.repo.tags if t.name.startswith(sub)]<br>+<br>+        try:<br>+            tags.sort(key=lambda t: t.tag.tagged_date) # Sort by creation date<br>+            return tags<br>+        except AttributeError as e:<br>+            raise AttributeError("{0} - tag not annotated".format(e))<br>+<br>+    def find_last_tag(self, version):<br>+        """Find the last tag prior to the given version on the same major branch.<br>+<br>+        Keyword Arguments:<br>+        version - A version object used to start the search from<br>+<br>+        Returns:<br>+        A version object representing the located tag, or None if a tag<br>+        cannot be located.<br>+        """<br>+<br>+        prev = version.get_previous_version()<br>+        if prev.major != version.major:<br>+            return None<br>+<br>+        if prev.minor == version.minor:<br>+            # Last tag is the previous version.<br>+            if self.tag_exists(str(prev)):<br>+                return prev<br>+<br>+            raise ValueError("Tag '{0}' does not exist!".format(prev))<br>+<br>+        # Find the last set of tags for a previous version<br>+        tags = []<br>+        while prev.major == version.major:<br>+            tags = self.find_tags(prev.branch_name())<br>+            if tags: break<br>+            prev = prev.get_previous_version()<br>+<br>+        if prev.major != version.major:<br>+            return None<br>+<br>+        # Last item in list is the last tag<br>+        return AsteriskVersion.create_from_string(tags[-1].name)<br> <br>     def create_tag(self, name):<br>         """Create a new local tag<br>@@ -266,13 +359,15 @@<br>         self.repo.create_tag(name, message="Create '{0}'".format(name))<br>         LOGGER.debug("Created tag {0}".format(name))<br> <br>-<br>     def checkout(self, name):<br>         """Checkout the specified tag or branch<br> <br>         Keyword Arguments:<br>         name - The tag or branch to checkout<br>         """<br>+<br>+        if name == self.current_branch:<br>+            return<br> <br>         self.current_branch = name<br> <br>@@ -286,6 +381,7 @@<br>             tracking = self.repo.remotes.origin.refs[name]<br>         except:<br>             tracking = 'HEAD'<br>+            self.updated_branches.append(name)<br> <br>         try:<br>             branch = self.repo.heads[name]<br>@@ -302,7 +398,6 @@<br>             self.repo.remotes.origin.pull()<br> <br>         LOGGER.debug("Local branch set to {0}".format(name))<br>-<br> <br>     def _convert_git_to_digium_commit(self, git_commit):<br>         """Convert a Git commit into a Digium/Asterisk commit<br>@@ -322,7 +417,6 @@<br>                 git_commit.author.email)<br>         return digium_commit<br> <br>-<br>     def _convert_git_to_digium_commits(self, git_commits):<br>         """Convert a series of Git commits to Digium/Asterisk commits<br> <br>@@ -339,7 +433,6 @@<br>             digium_commits.append(digium_commit)<br> <br>         return digium_commits<br>-<br> <br>     def get_commits_by_tags(self, start, end):<br>         """Retrieve a sequence of commits between two tags<br>@@ -362,7 +455,6 @@<br>                 list(self.repo.iter_commits(rev='{0}..{1}'.format(<br>                         commit_start, commit_end))))<br> <br>-<br>     def get_commits_by_date(self, branch, start, end):<br>         """Retrieve a sequence of commits between two dates<br> <br>@@ -384,7 +476,6 @@<br>                 list(self.repo.iter_commits(rev=branch,<br>                     after=commit_start, before=commit_end)))<br> <br>-<br>     def add_and_commit(self, files, commit_msg):<br>         """Add and commit modified files<br> <br>@@ -399,9 +490,9 @@<br>         self.repo.index.add(files)<br>         self.repo.index.commit(commit_msg)<br> <br>-<br>     def push_changes(self):<br>-        """Push any changes (branch, tags, etc...) upstream"""<br>+        """Push any changes (branches, tags, etc...) upstream"""<br> <br>         self._push_tags()<br>-        self._push_branch()<br>+        self._push_updated_branches()<br>+        self._push_deleted_branches()<br>diff --git a/version_parser.py b/version_parser.py<br>index 3a6188b..fcb116e 100644<br>--- a/version_parser.py<br>+++ b/version_parser.py<br>@@ -136,6 +136,66 @@<br>         previous.major -= 1<br>         return previous<br> <br>+    def has_modifier(self, mods):<br>+        """Check to see if any of the given modifiers are part of<br>+        this version.<br>+<br>+        Keyword Arguments:<br>+        mods - a list of modifiers to search for<br>+        """<br>+<br>+        if not isinstance(mods, list):<br>+            mods = [mods]<br>+<br>+        for m in mods:<br>+            for p, n in self.modifiers:<br>+                if m in "{0}{1}".format(p, n):<br>+                    return True<br>+        return False<br>+<br>+    def branch_name(self, prefix=True):<br>+        """Retrieve the name of a branch.<br>+<br>+        Branches are name from the major.minor numbers, so we only have to<br>+        use those values when building the branch name.<br>+        """<br>+<br>+        name = '{0}.{1}'.format(self.major, self.minor)<br>+<br>+        if prefix and self.prefix:<br>+            name = '{0}/{1}'.format(self.prefix, name)<br>+<br>+        return name<br>+<br>+    def is_first(self):<br>+        """Retrieve whether or not this version represents the first patch<br>+        release for a branch.<br>+<br>+        Returns:<br>+ True if the version has a patch number of '0' or has a 'cert1'<br>+        modifier, and does not contain other modifiers (like RCs).<br>+        """<br>+<br>+        return ((self.patch == 0 or self.has_modifier('cert1'))<br>+                and not self.has_modifier(['rc', 'alpha', 'beta']))<br>+<br>+    def is_first_pre(self):<br>+        """Retrieve whether or not this version represents the first pre-<br>+        release for a branch.<br>+<br>+        Returns:<br>+        True if the version that has a patch number of '0' or has a 'cert1'<br>+        modifier, and contains an 'rc1' (or similar) modifier.<br>+        """<br>+<br>+        # Major branch releases start with beta1 so check that first<br>+        if self.minor == 0 and self.patch == 0:<br>+            return self.has_modifier('beta1')<br>+<br>+        # Otherwise it might be a branch release, so check for rc1<br>+        return ((self.patch == 0 or self.has_modifier('cert1')) and<br>+                self.has_modifier('rc1'))<br>+<br>     def __repr__(self):<br>         """Return a representation of the version"""<br>         ver = ''<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/8290">change 8290</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/8290"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: repotools </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I5a7f44e75ae3d867f9795b7fb0ea5bff526b5429 </div>
<div style="display:none"> Gerrit-Change-Number: 8290 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Kevin Harwell <kharwell@digium.com> </div>