[asterisk-commits] Change in repotools[master]: digium_git: Add a new module to manage Git
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Thu Apr 16 14:55:15 CDT 2015
Matt Jordan has submitted this change and it was merged.
Change subject: digium_git: Add a new module to manage Git
......................................................................
digium_git: Add a new module to manage Git
This patch adds a new module, diguim_git, that wraps up Git commands in
Python for other script usage. It provides two classes:
* GitProgressBar - this displays a pretty progress bar while the Git
wrapper class manipulates repositories
* DigiumGitRepo - a wrapper around a repo, this provides some handy
accessors for creating/updating an existing repo
and pulling commit history. Commit history obtained
is generally turned into DigiumCommitMessageParser
objects, so that the release summary scripts and
other Asterisk project change logs can obtain
testers, reporters, JIRA issues, license numbers,
etc.
In addition, the patch updates the following modules:
* digium_commits - the DigiumCommitMessageParser class has been updated
to use the digium_jira_user module. All name mapping
information has been moved there.
* digium_jira_user - added equality/not-equality operator callbacks.
This allows for DigiumCommitMessageParser objects
to determine their own equality when used as
members of a collection.
REP-12 #close
Reported by: Matt Jordan <mjordan at digium.com>
Change-Id: I81f66a33f8f2ff7a7d9d0cbb82428e71370536b2
---
M digium_commits.py
A digium_git.py
M digium_jira_user.py
3 files changed, 293 insertions(+), 57 deletions(-)
Approvals:
Scott Griepentrog: Looks good to me, but someone else must approve
Matt Jordan: Looks good to me, approved; Verified
diff --git a/digium_commits.py b/digium_commits.py
index c7745ee..ff6df91 100644
--- a/digium_commits.py
+++ b/digium_commits.py
@@ -2,55 +2,13 @@
'''
Utilities to assist with parsing Digium formatted commit messages.
-Copyright (C) 2009 - 2011, Digium, Inc.
+Copyright (C) 2009 - 2015, Digium, Inc.
Russell Bryant <russell at digium.com>
'''
import re
-
-NAME_MAPPINGS = {
- 'jonathan rose': 'jrose',
- 'matt jordan': 'mjordan',
- 'matthew jordan': 'mjordan',
- 'joshua colp': 'jcolp',
- 'mark michelson': 'mmichelson',
- 'shaun ruffell': 'sruffell',
- 'terry wilson': 'twilson',
- 'kinsey moore': 'kmoore',
- 'richard mudgett': 'rmudgett',
- 'brent eagles': 'beagles',
- 'david lee': 'dlee',
- 'darren sessions': 'dsessions',
- 'john bigelow': 'jbigelow',
- 'walter doekes': 'wdoekes',
- 'michael l. young': 'elguero',
- 'michael young': 'elguero',
- 'ashley sanders': 'asanders',
- 'corey farrell': 'coreyfarrell',
- 'benjamin ford': 'bford',
- 'putnopvut': 'mmichelson',
- 'corydon76': 'tilghman',
- 'north': 'jparker',
- 'qwell': 'jparker',
- 'tempest1': 'bbryant',
- 'otherwiseguy': 'twilson',
- 'drumkilla': 'russell',
- 'russellb': 'russell',
- 'citats': 'jamesgolovich',
- 'blitzrage': 'lmadsen',
- 'opticron': 'kmoore',
- 'file': 'jcolp',
- 'creslin': 'mattf',
- 'tilghman lesher': 'tilghman',
- 'olle johansson': 'oej', }
-
-
-def convert_name(name):
- if name.lower() in NAME_MAPPINGS:
- return NAME_MAPPINGS[name.lower()]
- return name
-
+from digium_jira_user import AsteriskUser
class DigiumCommitMessageParser:
'''
@@ -79,8 +37,8 @@
commit of code. The blocked attribute will be set to True if the
string "Blocked revisions " is found in the commit message.
'''
- self.message = message
- self.author = convert_name(author)
+ self.message = unicode(message).encode('utf-8')
+ self.author = AsteriskUser(author)
self.block = False
if self.message.find("Blocked revisions ") >= 0:
@@ -138,9 +96,9 @@
for match in re.finditer("[Tt]ested [Bb]y:?(.*)", self.message):
testers = match.group(1).split(",")
for t in testers:
- t = convert_name(t.strip())
- if t not in all_testers:
- all_testers.append(t)
+ user = AsteriskUser(t)
+ if user not in all_testers:
+ all_testers.append(user)
return all_testers
def get_coders(self):
@@ -159,6 +117,9 @@
coders = []
tokens = self.message.split('\n')
patches = False
+ realname = None
+ email = None
+ license = None
for token in tokens:
if 'patches:' in token.lower():
patches = True
@@ -172,14 +133,26 @@
if name:
if ':' in name:
name.replace(':', '')
+ if '<' in name:
+ realname = name[:name.index('<')]
+ email = name[(name.index('<') + 1):name.index('>')]
if '(' in name:
- name = name[:name.index('(')]
- name = name.strip()
- c = convert_name(name)
- if len(c) == 0:
+ if not realname:
+ realname = name[:name.index('(')]
+ license = name[(name.index('(') + 1):name.index(')')]
+ if not realname:
+ realname = name
+ if len(realname) == 0:
continue
- if c not in coders:
- coders.append(c)
+ realname = realname.strip()
+ user = AsteriskUser(realname)
+ if email:
+ user.email = email.strip()
+ if license:
+ user.license = license.strip()
+
+ if user not in coders:
+ coders.append(user)
if len(coders) == 0:
coders.append(self.author)
return coders
@@ -195,7 +168,7 @@
reporter = ""
match = re.search("[Rr]eported [Bb]y:?(.*)", self.message)
if match is not None:
- reporter = convert_name(match.group(1))
+ reporter = AsteriskUser(match.group(1))
return reporter
def get_commit_summary(self):
@@ -221,3 +194,7 @@
return message_lines[i].strip()
return "(No Summary Available)"
+
+ def __repr__(self):
+ '''Return a string represenation of the object'''
+ return 'Author: {0}\n{1}'.format(self.author, self.message)
diff --git a/digium_git.py b/digium_git.py
new file mode 100644
index 0000000..d49b991
--- /dev/null
+++ b/digium_git.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python
+"""Asterisk project Git wrapper
+
+This module uses GitPython to provide common operations on Git repositories
+that the Asterisk project commonly requires. Note that GitPython is itself
+simply a wrapper around Git, and requires Git to be installed and in the system
+path.
+
+Copyright (C) 2015, Digium, Inc.
+Matt Jordan <mjordan at digium.com>
+"""
+
+import os
+
+from progressbar import ProgressBar
+from digium_commits import DigiumCommitMessageParser
+from git import Repo, RemoteProgress
+
+
+class GitProgressBar(RemoteProgress):
+ """A progress bar that maintains the state of a Git operation
+ """
+
+ def __init__(self):
+ """Constructor"""
+ super(GitProgressBar, self).__init__()
+
+ self.progress_bar = None
+ self._current_op = -1
+ self._started = False
+ self._max_count = 0
+
+ def operation_to_str(self, code):
+ """Convert a GitPython.RemoteProgress op code to string
+
+ Keyword Arguments:
+ code - The code to convert to a string
+
+ Returns:
+ A string representation of the code
+ """
+ val = None
+ if code == 1:
+ val = "BEGIN"
+ elif code == 2:
+ val = "END"
+ elif code == 3:
+ val = "STAGE_MASK"
+ elif code == 4:
+ val = "Counting"
+ elif code == 8:
+ val = "Compressing"
+ elif code == 16:
+ val = "Writing"
+ elif code == 32:
+ val = "Receiving"
+ elif code == 64:
+ val = "Resolving"
+ elif code == 128:
+ val = "Finding source"
+ else:
+ val = "Stuff and things ({0})".format(code)
+ return val
+
+ def update(self, op_code, cur_count, max_count=None, message=''):
+ """Called when the remote operation updates us
+
+ Override of RemoteProgress.update
+
+ Keyword Arguments:
+ op_code - The current Git operation
+ cur_count - The current number of objects processed
+ max_count - The max number of objects for all operations
+ message - A message received from Git
+ """
+ re_create_bar = False
+
+ op_code &= ~GitProgressBar.STAGE_MASK
+ if op_code == 0:
+ return
+
+ if op_code != self._current_op:
+ print "{0}...".format(self.operation_to_str(op_code))
+ self._current_op = op_code
+ re_create_bar = True
+
+ if op_code == GitProgressBar.COUNTING:
+ # COUNTING - at this point, we don't have a max value.
+ # Bail.
+ return
+
+ if not self._max_count:
+ if not max_count:
+ return
+ self._max_count = max_count
+
+ if re_create_bar:
+ self._current_op = op_code
+ self.progress_bar = ProgressBar()
+ self.progress_bar.maxval = float(max_count)
+ self.progress_bar.start()
+
+ self.progress_bar.update(float(cur_count))
+
+
+class DigiumGitRepo(object):
+ """A managed Git repo
+
+ This class wraps up a Git repo and provides some common operations on it
+ that are useful for common Asterisk operations. This includes cloning
+ and fetching the repo, providing useful logs/summaries, etc.
+ """
+
+ def __init__(self, local_path, repo_url=None, show_progress=False):
+ """Constructor
+
+ Keyword Arguments:
+ local_path - The local location on disk to create or set the repo to.
+ If this location already exists, a fetch will be
+ performed from its repo. If it does not exist, the
+ location will be created, initialized, and set to the
+ repo passed in.
+ repo_url - The repository to pull in. Note that if local_path does
+ not exist, this must be specified. An exception will be
+ thrown if local_path does not exist but no repo was
+ provided.
+ show_progress - If True, print out a progress bar for the various
+ operations being performed.
+ """
+
+ progress = None
+
+ if os.path.isdir(local_path):
+ self.repo = Repo(local_path)
+ origin = self.repo.remotes.origin
+ else:
+ if not repo_url:
+ raise ValueError("local_path {0} does not exist and "
+ "repo is None".format(local_path))
+ os.makedirs(local_path)
+ self.repo = Repo()
+ if show_progress:
+ progress = GitProgressBar()
+ self.repo = self.repo.clone_from(url=repo_url,
+ to_path=local_path,
+ progress=progress)
+ origin = self.repo.remotes.origin
+ if show_progress:
+ progress = GitProgressBar()
+ origin.fetch(progress=progress)
+ if show_progress:
+ progress = GitProgressBar()
+ origin.pull(progress=progress)
+
+ def checkout_remote_branch(self, branch_name):
+ """Set up a local tracking branch of a remote branch
+
+ Keyword Arguments:
+ branch_name - The remote branch to track
+ """
+ origin = self.repo.remotes.origin
+ remote_ref = [ref for ref in origin.refs if branch_name in ref.name][0]
+ local_branch = self.repo.create_head(branch_name, remote_ref)
+ local_branch.set_tracking_branch(remote_ref)
+ local_branch.checkout()
+
+ def get_tag(self, tag_name):
+ """Retrieve a particular tag ref from the repo
+
+ Raises an exception if tag_name is not found.
+
+ Keyword Arguments:
+ tag_name - The name of the tag to retrieve
+
+ Returns:
+ The tag object
+ """
+ tag = [t for t in self.repo.tags if t.name == tag_name][0]
+ return tag
+
+ def _convert_git_to_digium_commit(self, git_commit):
+ """Convert a Git commit into a Digium/Asterisk commit
+
+ Keyword Arguments:
+ git_commit - The Git commit object to convert
+
+ Returns:
+ A DigiumCommitMessageParser object
+ """
+
+ digium_commit = DigiumCommitMessageParser(
+ git_commit.message,
+ unicode(git_commit.author.name).encode('utf-8'))
+ digium_commit.author.email = unicode(
+ git_commit.author.email).encode('utf-8')
+ return digium_commit
+
+ def _convert_git_to_digium_commits(self, git_commits):
+ """Convert a series of Git commits to Digium/Asterisk commits
+
+ Keyword Arguments:
+ git_commits - A list of Git commits to convert
+
+ Returns:
+ A DigiumCommitMessageParser object
+ """
+ digium_commits = []
+ for git_commit in git_commits:
+ digium_commit = self._convert_git_to_digium_commit(git_commit)
+ digium_commits.append(digium_commit)
+ return digium_commits
+
+ def get_commits_by_tags(self, start_tag, end_tag):
+ """Retrieve a sequence of commits between two tags
+
+ Keyword Arguments:
+ start_tag - The start tag to begin pulling history
+ end_tag - The end tag to stop at
+
+ Returns:
+ A list of DigiumCommitMessageParser objects
+ """
+ commit_start = self.repo.commit(start_tag)
+ commit_end = self.repo.commit(end_tag)
+ return self._convert_git_to_digium_commits(
+ list(self.repo.iter_commits(rev='{0}..{1}'.format(
+ commit_start, commit_end))))
+
+ def get_commits_by_date(self, branch, start_date, end_date):
+ """Retrieve a sequence of commits between two dates
+
+ Keyword Arguments:
+ start_date - The start date to pull history from
+ end_date - The end date to stop at
+
+ Returns:
+ A list of DigiumCommitMessageParser objects
+ """
+ return self._convert_git_to_digium_commits(
+ list(self.repo.iter_commits(rev=branch,
+ after=str(start_date), before=str(end_date))))
+
+
diff --git a/digium_jira_user.py b/digium_jira_user.py
index f17839d..47a69fe 100644
--- a/digium_jira_user.py
+++ b/digium_jira_user.py
@@ -59,7 +59,7 @@
"""Constructor
Keyword Arguments:
- username - The unique ID for the user
+ username - The unique ID for the user. Optional.
"""
self.full_name = None
self._username = None
@@ -91,6 +91,22 @@
self.organization = organization
break
+ def __eq__(self, other):
+ """Test for equality
+
+ Keyword Arguments:
+ other - The other item being compared against this one
+ """
+ return self.username == other.username
+
+ def __ne__(self, other):
+ """Test for inequality
+
+ Keyword Arguments:
+ other - The other item being compared against this one
+ """
+ return not (self.username == other.username)
+
def __repr__(self):
"""Create a string representation of the object"""
name = ''
--
To view, visit https://gerrit.asterisk.org/132
To unsubscribe, visit https://gerrit.asterisk.org/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I81f66a33f8f2ff7a7d9d0cbb82428e71370536b2
Gerrit-PatchSet: 7
Gerrit-Project: repotools
Gerrit-Branch: master
Gerrit-Owner: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Scott Griepentrog <sgriepentrog at digium.com>
More information about the asterisk-commits
mailing list