: The previous release version number."
+
+
+def get_log(git_dir, commit_range):
+ """ Return the set of lines corresponding to the git log for the specified
+ commit range.
+ """
+
+ os.chdir(git_dir)
+ cmd = "git log --no-color '--pretty=format:%s' '" + commit_range + "'"
+ return os.popen(cmd).readlines()
+
+
+def sanitize_log(in_log):
+ """ 'sanitize' the log.
+ Some entries do not have a separate subject and body by accident.
+ Return a new log that only includes the first sentence of each
+ subject. (Note that we also usually have a 'SQOOP-nn.' before this
+ sentence.)
+ """
+ out_log = []
+ for line in in_log:
+ line = line.strip()
+ sentences = line.split(". ")
+ if len(sentences) <= 2:
+ out_log.append(line) # Unchanged original input.
+ else:
+ out_log.append(sentences[0] + ". " + sentences[1] + ".")
+
+ return out_log
+
+
+def get_jira_doc(issue):
+ """ Get the XML document from JIRA for a specified issue. """
+
+ xml = os.popen("curl -s 'https://issues.cloudera.org/si/jira.issueviews:" \
+ + "issue-xml/%s/%s.xml?field=key&field=type&field=parent'" % (issue, issue)).read()
+ return ElementTree.fromstring(xml)
+
+
+def get_jira_issue_types(log):
+ """ Return a dict from issue-type -> ((issue-name, summary) list) by looking
+ up the issues in our JIRA.
+ """
+
+ d = {}
+
+ def add_issue(issue, typ, line):
+ try:
+ d[typ].append((issue, line))
+ except KeyError:
+ # This issue type hasn't been seen yet. Add a new list.
+ d[typ] = [ (issue, line) ]
+
+ jira_reg = r"^(SQOOP-\d+)"
+ for line in log:
+ matched_line = False
+ for m in re.finditer(jira_reg, line, re.M):
+ matched_line = True
+ jira = m.group(1)
+ doc = get_jira_doc(jira)
+ issue_type = doc.find('./channel/item/type').text
+ # Subtasks use the type of their parent item.
+ if issue_type == "Sub-task":
+ parent_doc = get_jira_doc(doc.find('./channel/item/parent').text)
+ issue_type = parent_doc.find('./channel/item/type').text
+
+ add_issue(jira, issue_type, line)
+ if not matched_line and not line.startswith("CLOUDERA-BUILD."):
+ # This line did not start with "SQOOP-.."
+ # Unless it's a CDH buildfix, add it in as a "Task".
+ add_issue("", "Task", line)
+
+ return d
+
+
+def get_date():
+ """ Return the current month and year formatted as a string. """
+ return datetime.date.today().strftime("%B, %Y")
+
+
+def add_links(summary_line):
+ """ Given a line like "SQOOP-40. Do something", add links to the JIRA
+ and any appropriate SIPs, and return the line with links.
+ """
+
+ initial_jira_reg = r"^(SQOOP-\d+)\. (.*)"
+
+ # Reformat the issue id away from the summary.
+ m = re.match(initial_jira_reg, summary_line)
+ if m == None:
+ # Line in unexpected format. Return as-is.
+ return summary_line
+ jira = m.group(1)
+ text = m.group(2)
+
+ # Add links to JIRA and SIP wiki.
+
+ issue_reg = r"(SQOOP-\d+)"
+ issue_subst = r'\1'
+
+ sip_reg = r"(SIP-\d+)"
+ sip_subst = r'\1'
+
+ output = "[" + jira + "] - " + text
+ output = re.sub(issue_reg, issue_subst, output)
+ output = re.sub(sip_reg, sip_subst, output)
+
+ return output
+
+
+__user_types = {
+ "Bug" : "Bug fixes",
+ "Improvement" : "Improvements",
+ "New Feature" : "New features",
+ "Task" : "Tasks"
+}
+
+def user_issue_type(typ):
+ """ Return a user-friendly issue type string based on the JIRA issue
+ type string.
+ """
+ global __user_types
+
+ try:
+ return __user_types[typ]
+ except KeyError:
+ # If we don't have a plural-form string set, just use the input.
+ return typ
+
+
+
+def format_html(newversion, oldversion, log, jira_info):
+ """ Creates the HTML representation of the release notes and returns
+ it as a string.
+ """
+
+ output_lines = []
+ output_lines.append("""
+
+Sqoop %(newversion)s Release Notes
+
+Release Notes for Sqoop %(newversion)s: %(date)s
+
+This document lists all Sqoop issues included in version %(newversion)s
+not present in the previous release, %(oldversion)s.
+""" % { "newversion" : newversion,
+ "oldversion" : oldversion,
+ "date" : get_date() })
+
+
+ # Sort the output list by issue type.
+ types = jira_info.keys()
+ types.sort()
+ for typ in types:
+ output_lines.append("" + user_issue_type(typ) + ":
\n")
+ for (issue, summary) in jira_info[typ]:
+ output_lines.append("- ")
+ output_lines.append(add_links(summary))
+ output_lines.append("
\n")
+ output_lines.append("
\n")
+
+ output_lines.append("\n")
+ return "".join(output_lines)
+
+
+def main(argv):
+ if len(argv) > 1 and argv[1] == '-h':
+ print_usage(argv[0])
+ return 0
+
+ if len(argv) < NUM_ARGS:
+ print "Missing required argument(s). Try " + argv[0] + " -h"
+ return 1
+
+ target_dir = os.path.abspath(os.path.expanduser(argv[1]))
+ git_src = os.path.abspath(os.path.expanduser(argv[2]))
+ commit_range = argv[3]
+ newversion = argv[4]
+ oldversion = argv[5]
+
+ log = get_log(git_src, commit_range)
+ log = sanitize_log(log)
+ jira_info = get_jira_issue_types(log)
+ html = format_html(newversion, oldversion, log, jira_info)
+
+ os.system("mkdir -p \"" + target_dir + "\"")
+ handle = open(os.path.join(target_dir, \
+ "sqoop-" + newversion + ".releasenotes.html"), "w")
+ handle.write(html)
+ handle.close()
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
+
+