<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>