]> source.dussan.org Git - vaadin-framework.git/commitdiff
Merge from 5.3 to 6.0:
authorHenri Sara <henri.sara@itmill.com>
Wed, 25 Mar 2009 07:10:17 +0000 (07:10 +0000)
committerHenri Sara <henri.sara@itmill.com>
Wed, 25 Mar 2009 07:10:17 +0000 (07:10 +0000)
[7015] Updated browser support list in Release Notes. For #2538.
[7028] Merge from branches/release/5.3.0 to versions/5.3 (multiple changesets concerning build)
[7039] Updated tutorial PDF, also added the header logo element.

svn changeset:7166/svn branch:6.0

WebContent/release-notes.html
build/VERSION.properties
build/bin/mergetool.py [new file with mode: 0755]
build/bin/svnlog-to-rn.py [new file with mode: 0755]
build/bin/svnlog-to-rn.xsl [new file with mode: 0644]

index 4ac661912d110526cd1c8ecf59e028eda5c4bd1f..3ed8e8f2596d85e2606512393b42bbbc1f9cf68f 100644 (file)
@@ -331,7 +331,7 @@ for your platform.</p>
 </ul>
 
 <p>IT Mill Toolkit supports Java Servlet API 2.3 and later versions and should work with
-any Java application servers that conform to the standard. It supports the following
+any Java application server that conforms to the standard. It supports the following
 application servers:</p>
 
 <ul>
@@ -348,30 +348,46 @@ it:</p>
 
 <ul>
        <li>Mozilla Firefox releases 2, and 3 (see notice above about
-       Gecco 1.7).</li>
-       <li>Internet Explorer releases 6, 7, and 8.</li>
+       Gecko 1.7).</li>
+       <li>Internet Explorer releases 6, 7, and 8<sup>&#8224;</sup>.</li>
        <li>Safari 3</li>
-       <li>Opera 9.6</li>
+       <li>Opera 9.6<sup>&#8225;</sup></li>
 </ul>
 
-<p>There may be differences between the exact versions of the supported browsers that may
-cause incompatibility with applications made with IT Mill Toolkit. Support for Opera
-has currently some limitations, such as the front page of the Sampler demo.</p>
+<p style="font-size: 80%;">&#8224; Internet Explorer 8 RC1 may not be usable because of a
+serious bug in the RC1. <a href="http://dev.itmill.com/ticket/2578">#2578</a><br/>
+
+&#8225; The current support for Opera has some limitations, such as the front page of
+the Sampler demo. <a href="http://dev.itmill.com/ticket/2652">#2652</a></p>
+
+<p>The support for browsers follows the support by GWT. The browsers are supported on both
+Windows and Mac, if available. Firefox is supported also on Linux (Opera 10a1 works also
+on Linux though also suffers from <a
+href="http://dev.itmill.com/ticket/2652">#2652</a>). There may be differences between the
+exact versions of the supported browsers that may cause incompatibility with applications
+made with IT Mill Toolkit.</p>
 
 <p>The following browsers are not supported but have been found to
 work to a large degree:</p>
+
 <ul>
-       <li>Safari 2</li>
+       <li>Safari 2, and 4 beta</li>
        <li>Firefox 1.5</li>
-       <li>iPhone</li>
-       <li>Epiphany, Galeon, and other Gecco-based browsers (see notice
-       about Gecco 1.7 above).</li>
+    <li>Google Chrome 1.0.x (available only for Windows)</li>
+       <li>iPhone (firmware 2.2)</li>
+    <li>Midori (0.1.2)</li>
+       <li>Epiphany (2.22.3), Galeon, and other Gecko-based browsers (see the notice
+       regarding Gecko 1.7 above). Also WebKit-based Epiphany (2.22.3) works.</li>
+    <li>Konqueror 4.2 (3.5.x does not work)</li>
     <li>Nokia Internet Tablet N800 and N810 (ITOS 2008, Opera-based browser).</li>
 </ul>
 
-<p>Nokia E-series phones with at least 128MB of memory have been known to work with older
-versions, but not with IT Mill Toolkit 5.2 and later.  Konqueror, the default browser in
-many Linux distributions, is known to not work.</p>
+<p>The reported versions are those that have been tested, though other versions may work
+as well.</p>
+
+<p>Nokia E-series phones, such as E90, have been known to work with older versions, but
+not with IT Mill Toolkit 5.2 and later. Links, Lynx, and other text-based browsers do not
+work.</p>
 
 <!-- h2>ChangeLog Between IT Mill Toolkit 5.1.2 and 5.2.0</h2 --></div>
 <!-- /getting-started -->
index 1d7413b23531948638b50237ba9b5373464421dc..85aede4896d3318b97997f120886896ad24697e2 100644 (file)
@@ -1 +1 @@
-version=5.3.0-rc13
+version=5.3.0
diff --git a/build/bin/mergetool.py b/build/bin/mergetool.py
new file mode 100755 (executable)
index 0000000..bc1b23b
--- /dev/null
@@ -0,0 +1,682 @@
+#!/usr/bin/python
+
+import sys,re,os,string,urllib,httplib
+
+################################################################################
+# Configuration
+################################################################################
+
+# Determine repository root
+pin = os.popen("svn info|grep 'Repository Root'|sed -e 's/^.\+: //'", "r")
+REPOSITORY = pin.read().rstrip()+"/"
+pin.close()
+
+print "Repository: %s" % (REPOSITORY)
+
+################################################################################
+# Parse command-line arguments
+################################################################################
+def help(exitvalue = 0):
+       print "Usage: batchmerge [options] <command>\n"
+       print "Options:"
+       print "\t-m\tOnly merge. (For 'single' command.)"
+       print "\t-c\tOnly commit. (For 'single' command.)"
+       print "\t-html\tHTML output. (For 'log' command.)"
+       print "\t-author\tHTML output. (Include author in HTML log.)"
+       print "\t-milestone <ms>\tList tickets in milestone (For 'log' command.)"
+       print "\nCommands:"
+       print "massmerge <cfg> <src> <trg> [<from>] "
+       print "                          - Merges changesets listed in the configuration"
+       print "                            file. The file is in svn log (text) format."
+       print "                            You can comment out unwanted changesets."
+       print "                            Merge is stopped on conflict."
+       print "                            If you give the optional <from> parameter,"
+       print "                            merge is started from the changeset number. "
+       print "single <chg#> <target>    - Merges a single changeset. If -m is given,"
+       print "                            only merge is done. If -c is given, only commit is done."
+       print "revert                    - Reverts all changes made to repository except"
+       print "                            changes in this program and the merge files."
+       print "log <cfg> <src> <trg>     - Prints a ChangeLog as it will appear in the"
+       print "                            commit log. If -html option is given,"
+       print "                            the log is printed in a HTML format"
+       print "                            suitable for the Release Notes."
+       print "commit <cfg> <src> <trg>  - Commits all changes, except changes to this"
+       print "                            program and merge files. The commit log"
+       print "                            comment includes list of all changesets"
+       print "                            listed in the configuration. The <target>"
+       print "                            is the branch name, e.g., \"5.2\".\n"
+       print "Common parameters:"
+       print "    <cfg>                 - Configuration file (svn text log format)."
+       print "    <src>                 - Source branch relative to repository URI."
+       print "    <trg>                 - Target branch relative to repository URI."
+       print "You must run the command in the root directory of the branch."
+       print "The program file contains some basic configuration parameters."
+       sys.exit(exitvalue)
+
+################################################################################
+# Globals
+################################################################################
+tickets = {}
+
+################################################################################
+# Utility Functions
+################################################################################
+def command(cmd, dryrun=0):
+       print cmd
+       if not dryrun:
+               if os.system(cmd):
+                       print "Command failed, exiting."
+                       sys.exit(1)
+       else:
+               print "Dry run - not executing."
+
+def listChangedFiles():
+       # Get Svn status
+       pin = os.popen("svn st", "r")
+       lines = pin.readlines()
+       pin.close()
+
+       changed  = []
+       for line in lines:
+               # Remove trailing newline
+               line = line[:-1]
+
+               # Extract the file state and name
+               (filestate, filename) = re.split(r'[ \+]+', line)
+
+               # Ignore files in build directory
+               if (filename.startswith("build/merge/") \
+                       or filename.startswith("build/bin/mergetool.py") \
+                       or filename.startswith("build/testing")) \
+                       and filestate == "M":
+                       continue
+
+               # File is changed if it is not local
+               if filestate != "?":
+                       changed.append(filename)
+
+       return changed
+
+# Retrieves ticket summary string with HTTP
+# Returns: (summary, milestone)
+def fetchSummary(ticketno):
+       params = urllib.urlencode({'format': 'tab'})
+       conn = httplib.HTTPConnection("dev.itmill.com")
+       conn.request("GET", "/ticket/%d?%s" % (ticketno, params) )
+       response = conn.getresponse()
+       data = response.read()
+       conn.close()
+
+       lines = data.split("\n")
+       data = reduce(lambda x,y: x+"\n"+y, lines[1:])
+       #cols = lines[1].split("\t")
+
+       cols = data.split("\t")
+
+       return (cols[1],cols[8])
+
+# Adds summary to ticket number, unless the context already has it
+# Returns: (summary, milestone)
+def addSummary(m):
+       ticketnum = int(m.group(1))
+       context = m.group(2)
+       if re.match(" *\(", context):
+               # The context already has ticket summary
+               return "#%d%s" % (ticketnum, context)
+
+       (summary,milestone) = fetchSummary(ticketnum)
+
+       # Remove possible " quotation from the summary
+       if summary.startswith('"'):
+               summary = summary.strip('"')
+               summary = summary.replace('""', '"')
+
+       # Add summaries to further ticket numbers recursively
+       context = re.sub(r'#([0-9]+)(.*)', addSummary, context)
+
+       return "#%s (<i>%s</i>) %s" % (ticketnum, summary, context)
+
+################################################################################
+# Change
+################################################################################
+class Ticket:
+       def __init__(self, id, summary=None, milestone=None):
+               self.id        = id
+               self.summary   = summary
+               self.milestone = milestone
+
+       def fetchData(self):
+               (summary, milestone) = fetchSummary(self.id)
+               self.summary   = summary
+               self.milestone = milestone
+
+################################################################################
+# Change
+################################################################################
+class Change:
+       def __init__(self, id, undo=0, author=""):
+               self.id = id
+               self.author = author
+               self.comment = ""
+               self.undo = undo
+               self.tickets = []
+
+       def addCommentLine(self, line):
+               self.comment += line
+
+       def merge(self, trunkURI, dryrun=0):
+               drycmd = ""
+               if dryrun:
+                       drycmd = "--dry-run"
+
+               # Handle negative merge
+               mergesign = ""
+               if self.undo:
+                       mergesign = "-"
+
+               # Build the merge command
+               cmd = "svn merge --non-interactive %s -c %s%d %s" % (drycmd, mergesign, self.id, trunkURI)
+               print cmd
+               
+               # Run the merge command
+               pin = os.popen(cmd, "r")
+               lines = pin.readlines()
+               pin.close()
+
+               # Parse the lines for conflicts
+               conflicts = 0
+               for line in lines:
+                       print line[:-1]
+
+                       # Check for conflict
+                       if line.startswith("C"):
+                               conflicts += 1
+
+                       # Check for skipped file
+                       elif line.startswith("Skipped"):
+                               conflicts += 1
+
+                       filename = line[8:-1]
+
+               # Simply exit if there was any problem
+               if conflicts > 0:
+                       print "Problems detected. Exiting."
+                       sys.exit(1)
+
+       def fetchComment(self, trunkURI):
+               cmd = "svn log -r %d %s" % (self.id, trunkURI)
+               
+               # Run the log command
+               pin = os.popen(cmd, "r")
+               lines = pin.readlines()
+               pin.close()
+
+               STATE_START = 0
+               STATE_COMMENT = 1
+               comment = None
+               state = STATE_START
+               for line in lines:
+                       if state == STATE_START:
+                               if line == "\n":
+                                       state = STATE_COMMENT
+                       elif state == STATE_COMMENT:
+                               if line.startswith("-----------------"):
+                                       self.comment = comment
+                                       return comment
+                               elif comment:
+                                       comment += "\n" + line.rstrip("\n")
+                               else:
+                                       comment = line.rstrip("\n")
+
+               self.comment = comment
+               return comment
+
+       def commit(self):
+               # Write the log comment to a temporary file
+               logtmpname = "/tmp/merge-single-%d.log" % (os.getpid())
+               fout = open(logtmpname, "w")
+               fout.write(self.comment)
+               fout.close()
+
+               # Get listo
+               files = listChangedFiles()
+               if len(files) == 0:
+                       print "Error: Will not do empty commit."
+                       sys.exit(1)
+
+               # Write the list of files to be committed to a temporary file
+               changedfiles = ("\n".join(files)) + "\n"
+               targettmpname = "/tmp/merge-targets-%d.txt" % (os.getpid())
+               fout = open(targettmpname, "w")
+               fout.write(changedfiles)
+               fout.close()
+               print changedfiles,
+
+               command("svn commit --file %s --targets %s" % (logtmpname, targettmpname))
+
+               command("rm %s %s" % (logtmpname, targettmpname))
+
+
+       def getNumber(self):
+               return self.id
+
+       def getComment(self):
+               return self.comment
+
+       def getUndo(self):
+               return self.undo
+
+       def getAuthor(self):
+               return self.author
+
+       def isForMilestone(self, milestone):
+               return self.author
+
+       def addSummary(self, m, target_milestone=None):
+               ticketnum = int(m.group(1))
+               context = m.group(2)
+               if re.match(" *\(", context):
+                       # The context already has ticket summary
+                       return "#%d%s" % (ticketnum, context)
+
+               # Check for cached ticket
+               if tickets.has_key(ticketnum):
+                       summary = tickets[ticketnum].summary
+                       ticket_milestone = tickets[ticketnum].milestone
+               else:
+                       # Fetch ticket from server and add to cache
+                       (summary,ticket_milestone) = fetchSummary(ticketnum)
+                       tickets[ticketnum] = Ticket(ticketnum,summary,ticket_milestone)
+
+               self.tickets.append(ticketnum);
+
+               # Remove possible " quotation from the summary
+               if summary.startswith('"'):
+                       summary = summary.strip('"')
+                       summary = summary.replace('""', '"')
+
+               ticketnum = "#%s" % (ticketnum)
+                       
+               # Emphasize tickets matching the target milestone
+               if target_milestone:
+                       if ticket_milestone.find(target_milestone) != -1:
+                               ticketnum = "<b>%s</b>" % (ticketnum)
+
+               # Add summaries to further ticket numbers recursively
+               context = re.sub(r'#([0-9]+)(.*)', lambda m: self.addSummary(m, target_milestone=target_milestone), context)
+
+               return "%s (<i>%s</i>) %s" % (ticketnum, summary, context)
+
+       def registerTicket(self, m, ticketNumbers):
+               ticketNumbers[int(m.group(1))] = 1
+               return ""
+
+       # Returns a list of ticket numbers referenced by this change
+       def listTickets(self):
+               ticketNumbers = {}
+               re.sub(r'#([0-9]+)', lambda m: self.registerTicket(m,ticketNumbers=ticketNumbers), self.comment)
+               return ticketNumbers.keys()
+
+################################################################################
+# Read configuration file
+################################################################################
+class Configuration:
+       def __init__(self, cfgfilename, startfrom=0):
+               self.changes = []
+               self.readConfig(cfgfilename, startfrom)
+
+       def readConfig(self, cfgfilename, startfrom=0):
+               fin = open(cfgfilename, "r")
+               content = fin.readlines()
+               fin.close()
+
+               # Parse configuration
+               currentChange = None
+               skipChange    = 0
+               for line in content:
+                       m_changestart = re.match(r'(-?)r([0-9]+)', line)
+                       m_endofchange = re.match(r'------+', line)
+                       m_emptyline = re.match(r'^$', line)
+                       if m_changestart:
+                               # Parse negative merge
+                               undo = 0
+                               if m_changestart.group(1) == "-":
+                                       undo = 1
+
+                               # Get changeset number
+                               id = int(m_changestart.group(2))
+                               
+                               # Skip the target if its number is too small
+                               if startfrom != 0 and id < startfrom:
+                                       skipChange = 1
+
+                               # Get the author
+                               author = re.sub(r'\@.+', '', line.split("|")[1].strip())
+
+                               currentChange = Change(id, undo=undo, author=author)
+                               
+                       elif m_endofchange:
+                               # Register changeset, unless it is marked
+                               # for skipping.
+                               if currentChange:
+                                       if skipChange:
+                                               skipChange = 0
+                                       else:
+                                               self.changes.append(currentChange)
+                                       currentChange = None
+                       elif m_emptyline:
+                               pass
+                       else:
+                               if currentChange:
+                                       currentChange.addCommentLine(line)
+
+       def massMerge(self, trunkURI, dryrun=0):
+               for change in self.changes:
+                       change.merge(trunkURI, dryrun=dryrun)
+
+       def createLogComment(self):
+               # Create a log comment that lists all merged changesets with
+               # comments
+               logcomment = "Merge from %s to %s:\n" % (sourcebranch, targetbranch)
+               for change in self.changes:
+                       changeno      = change.getNumber()
+                       changecomment = change.getComment().rstrip("\n")
+                       if change.getUndo():
+                               logcomment += "Reverted [%d] merge: %s\n" % (changeno, changecomment)
+                       else:
+                               logcomment += "Merged [%d]: %s\n" % (changeno, changecomment)
+               return logcomment
+
+       def logHtml(self, author=0, milestone=None):
+               # In author inclusion mode, include some styles to make a printout look better
+               if author:
+                       print "<head>\n<style type=\"text/css\">\n"+ \
+                                 "tr {\n         vertical-align: top;\n}\ntd {\n         font-size: 8pt;\n}\n</style>\n</head>\n"
+                       
+               # Print header
+               print "<table id=\"changelog-table\">"
+               authorcolumnheader = ""
+               if author:
+                       authorcolumnheader = "<td>Author</td>"
+               print "  <tr><td>#</td><td>Changeset Comment</td>%s</tr>" % (authorcolumnheader)
+
+               # Print body: the changes
+               for change in self.changes:
+                       changeno      = change.getNumber()
+                       changecomment = change.getComment().rstrip("\n")
+                       
+                       changeref     = "[%d]" % (changeno)
+
+                       # Handle merge undo markup
+                       if change.getUndo():
+                               changeref     = "<font class=\"changeset-merge-undone\">%s</font>" % (changeref)
+                               changecomment = "<font class=\"changeset-merge-undone\">%s</font>" % (changecomment)
+                               changecomment = "Reverted a change: "+changecomment
+
+                       authorcolumn = ""
+                       if author:
+                               authorcolumn = "<td>%s</td>" % (change.getAuthor())
+
+                       # Add ticket summary after ticket number, if missing
+                       # TODO: this handles only one
+                       changecomment = re.sub(r'#([0-9]+)(.*)', lambda m: change.addSummary(m, target_milestone=milestone), changecomment, 100)
+                       # item = re.sub(r'#([0-9]+)(.*)', '#\\1 (SUMMARY) \\2', item)
+       
+                       # Change ticket numbers to references to tickets
+                       changecomment = re.sub(r'#([0-9]+)', '#<a href="http://dev.itmill.com/ticket/\\1">\\1</a>', changecomment)
+       
+                       # Change changeset numbers to references to changesets
+                       changecomment = re.sub(r'\[([0-9]+)\]', '[<a href="http://dev.itmill.com/changeset/\\1">\\1</a>]', changecomment)
+                       changeref     = re.sub(r'\[([0-9]+)\]', '[<a href="http://dev.itmill.com/changeset/\\1">\\1</a>]', changeref)
+
+                       # See if any of the tickets have milestone under work.
+                       if milestone:
+                               for ticketnum in change.tickets:
+                                       ticket = tickets[ticketnum]
+                                       if ticket.milestone.find(milestone) != -1:
+                                               changeref = "<b>%s</b>" % (changeref)
+
+                       # Make basic HTML formatting
+                       item = "  <tr><td>%s:</td><td>%s</td>%s</tr>" % (changeref, changecomment, authorcolumn)
+
+                       print item
+                       sys.stdout.flush()
+               print "</table>"
+
+       # Prints a commit log to standard output
+       def log(self, sourcebranch, targetbranch, html=0, author=0, milestone=None):
+               if html:
+                       self.logHtml(author=author,milestone=milestone)
+                       return
+               sys.stdout.write(self.createLogComment())
+
+       def commit(self, sourcebranch, targetbranch, dryrun=0):
+               logcomment = self.createLogComment()
+
+               # Write the log comment to a temporary file
+               logtmpname = "/tmp/massmerge-pid-%d.log" % (os.getpid())
+               fout = open(logtmpname, "w")
+               fout.write(logcomment)
+               fout.close()
+
+               # Write the list of files to be committed to a temporary file
+               changedfiles = "\n".join(listChangedFiles())
+               targettmpname = "/tmp/massmerge-targets-%d.txt" % (os.getpid())
+               fout = open(targettmpname, "w")
+               fout.write(changedfiles)
+               fout.close()
+
+               if dryrun:
+                       print "Log:"
+                       os.system("cat %s" % (logtmpname))
+                       print "\nChanged Files:"
+                       os.system("cat %s" % (targettmpname))
+                       print "\n"
+
+               command("svn commit --file %s --targets %s" % (logtmpname, targettmpname), dryrun=dryrun)
+
+               command("rm %s %s" % (logtmpname, targettmpname))
+
+       def listTickets(self):
+               fixed = {}
+               
+               for change in self.changes:
+                       changeno      = change.getNumber()
+                       changeTickets = change.listTickets()
+                       if len(changeTickets)>0 and change.comment.lower().find("fix") != -1:
+                               for ticket in changeTickets:
+                                       fixed[ticket] = 1
+
+               fixedlist = fixed.keys()
+               fixedlist.sort()
+               # print "Fixed:", fixedlist
+
+               print "<ul>"
+               
+               for ticketNum in fixedlist:
+                       if not tickets.has_key(ticketNum):
+                               ticket = Ticket(ticketNum)
+                               ticket.fetchData()
+                               tickets[ticketNum] = ticket
+                               
+                       ticket = tickets[ticketNum]
+                       # print "%d: %s" % (ticket.id, ticket.summary)
+                       print "  <li><a href=\"http://dev.itmill.com/ticket/%d\">#%d</a>: %s</li>" % (ticket.id, ticket.id, ticket.summary)
+                       sys.stdout.flush()
+
+               print "</ul>"
+
+################################################################################
+# Commands
+################################################################################
+
+# Command: revert
+def commandRevert():
+       # Get Svn status
+       pin = os.popen("svn st", "r")
+       lines = pin.readlines()
+       pin.close()
+
+       reverted = []
+       removed  = []
+       for line in lines:
+               # Remove trailing newline
+               line = line[:-1]
+
+               # Extract the file state and name
+               (filestate, filename) = re.split(r'[ \+]+', line)
+
+               # Ignore files in build directory
+               if (filename.startswith("build/merge/") \
+                       or filename.startswith("build/bin/")) \
+                       and filestate == "M":
+                       continue
+
+               # Added files are simply deleted
+               if filestate != "?":
+                       reverted.append(filename)
+
+                       # Added files have to be removed in addition to reverting
+                       if filestate == "A":
+                               removed.append(filename)
+
+               # Remove conflict choises
+               elif filestate == "?" and \
+                        (filename.find(".merge-left.r") != -1 or \
+                         filename.find(".merge-right.r") != -1):
+                       removed.append(filename)
+
+       # Revert files marked for reverting
+       donework = 0
+       if len(reverted) > 0:
+               files = " ".join(reverted)
+               command("svn revert -R %s" % (files))
+               donework = 1
+
+       # Remove files marked for deletion
+       if len(removed) > 0:
+               files = " ".join(removed)
+               command("rm %s" % (files))
+               donework = 1
+
+       if not donework:
+               print "Nothing to do."
+
+# Command: massmerge
+def commandMassmerge(cfgfilename, sourceuri, startfrom, dryrun=0):
+       cfg = Configuration(cfgfilename, startfrom=startfrom)
+       cfg.massMerge(sourceuri, dryrun=dryrun)
+
+# Command: single
+def commandSingle(trunkuri, changeset, sourcebranch, targetbranch, onlymerge=0, onlycommit=0):
+       change = Change(changeset)
+       print "Found changeset with log comment:\n  "+change.fetchComment(trunkuri) + "\n"
+
+       change.merge(trunkuri, dryrun=onlycommit)
+       if onlycommit:
+               print "Got file list."
+       else:
+               print "Merge successful."
+
+       # Change the comment
+       change.comment = "Merged [%d] from %s to %s branch: %s" % (change.id, sourcebranch, branchname, change.comment)
+       print "\nLog comment: \"%s\"" % (change.comment)
+       
+       if not onlymerge:
+               print "Committing."
+               change.commit()
+
+# Command: commit
+def commandCommit(cfgfilename, sourcebranch, targetbranch, dryrun=0):
+       cfg = Configuration(cfgfilename)
+       cfg.commit(sourcebranch, targetbranch, dryrun=dryrun)
+
+# Command: log
+def commandLog(cfgfilename, sourcebranch, targetbranch, html=0, author=0, milestone=None):
+       cfg = Configuration(cfgfilename)
+       cfg.log(sourcebranch, targetbranch, html=html, author=author, milestone=milestone)
+
+# Command: tickets
+def commandTickets(cfgfilename):
+       cfg = Configuration(cfgfilename)
+       cfg.listTickets()
+
+################################################################################
+# Main Program
+################################################################################
+
+# Handle switches
+dryrun = 0
+html   = 0
+html_author = 0
+html_milestone = None
+onlymerge = 0
+onlycommit = 0
+while len(sys.argv)>1 and sys.argv[1][0] == '-':
+       if sys.argv[1] == "-d":
+               dryrun = 1
+               del sys.argv[1:2]
+
+       elif sys.argv[1] == "-html":
+               html = 1
+               del sys.argv[1:2]
+
+       elif sys.argv[1] == "-author":
+               html_author = 1
+               del sys.argv[1:2]
+
+       elif sys.argv[1] == "-milestone":
+               html_milestone = sys.argv[2]
+               del sys.argv[1:3]
+
+       elif sys.argv[1] == "-m":
+               onlymerge = 1
+               del sys.argv[1:2]
+
+       elif sys.argv[1] == "-c":
+               onlycommit = 1
+               del sys.argv[1:2]
+
+       else:
+               print "Invalid option '%s'." % (sys.argv[1])
+               sys.exit(1)
+
+if len(sys.argv) < 2:
+       help(1)
+
+# Handle commands
+if sys.argv[1] == "revert":
+       commandRevert()
+
+elif (len(sys.argv) == 4 or len(sys.argv)==5) and sys.argv[1] == "massmerge":
+       cfgfilename = sys.argv[2]
+       sourcebranch = sys.argv[3]
+       startfrom = None
+       if len(sys.argv)>4:
+               startfrom = int(sys.argv[4])
+       commandMassmerge(cfgfilename, sourceuri=REPOSITORY+sourcebranch, startfrom=startfrom, dryrun=dryrun)
+
+elif len(sys.argv) == 5 and sys.argv[1] == "single":
+       changeset = int(sys.argv[2])
+       sourcebranch = sys.argv[3]
+       targetbranch = sys.argv[4]
+       commandSingle(REPOSITORY+sourcebranch, changeset, targetbranch, onlymerge=onlymerge, onlycommit=onlycommit)
+
+elif len(sys.argv) == 5 and sys.argv[1] == "commit":
+       cfgfilename = sys.argv[2]
+       sourcebranch = sys.argv[3]
+       targetbranch = sys.argv[4]
+       commandCommit(cfgfilename, sourcebranch, targetbranch, dryrun=dryrun)
+
+elif len(sys.argv) == 5 and sys.argv[1] == "log":
+       cfgfilename = sys.argv[2]
+       sourcebranch = sys.argv[3]
+       targetbranch = sys.argv[4]
+       commandLog(cfgfilename, sourcebranch, targetbranch, html=html, author=html_author, milestone=html_milestone)
+
+elif len(sys.argv) == 3 and sys.argv[1] == "tickets":
+       cfgfilename = sys.argv[2]
+       commandTickets(cfgfilename)
+
+else:
+       help(1)
diff --git a/build/bin/svnlog-to-rn.py b/build/bin/svnlog-to-rn.py
new file mode 100755 (executable)
index 0000000..5cca370
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+################################################################################
+# SVN Log to ChangeLog generator for Release Notes
+#
+# Generates list of changes in HTML for ChangeLog
+# from SVN Log in XML format. You typically generate the log with
+# a command such as:
+#     svn log -v -r 1234:HEAD > svnlog-1234:HEAD.log.xml
+# The command must be executed in the root directory of Toolkit project,
+# either in the trunk or in the proper branch. The converter is then
+# used as follows:
+#     ./build/bin/svnlog-to-rn.py svnlog-1234:HEAD.log.xml
+#
+# The ChangeLog generator will strip away any merges that begin with
+# "Merged [...] from trunk to x.x branch."
+#
+# The generator will handle the following markup:
+#   - Changeset tags such as [1234] to links to dev.itmill.com/changeset/1234
+#   - Ticket references such as #1234 to links to dev.itmill.com/ticket/1234
+#   - If ticket reference does not have explanation in parentheses,
+#     the script will fetch the summary of the ticket from Trac and
+#     add it in parentheses after the reference, such as:
+#     "fixes #1234 (A nasty bug I found)".
+#
+# Requirements:
+#   - Xalan
+################################################################################
+
+import sys,re,os,httplib,urllib
+
+################################################################################
+# Convert XML to XHTML
+#  - The transformation includes various relevent information
+#    and does basic formatting
+################################################################################
+
+# Determine path to XSLT file
+pathToScript = sys.argv[0]
+sloc = pathToScript.rfind("/")
+pathToScript = pathToScript[:sloc]
+
+if len(sys.argv) != 2:
+    print "Usage: svnlog-to-rn.py <logfile.xml>"
+    print "Read the svnlog-to-rn.py header for more info."
+    sys.exit(1)
+
+# Open Xalan 
+filename = sys.argv[1]
+fin = open(filename, "r")
+(pout,pin) = os.popen2("xalan -xsl %s/svnlog-to-rn.xsl" % (pathToScript))
+
+# Preprocessing before feeding to XSLT Processor
+lines = fin.readlines()
+out = ""
+for line in lines:
+       if line.find("action") != -1:
+               line = line.replace(r'>[^<]+/', '')
+       #print line,
+       pout.write(line)
+pout.close()
+
+################################################################################
+# Helper functions for postprocessing
+################################################################################
+
+# Retrieves summary string with HTTP
+def fetchSummary(ticketno):
+       params = urllib.urlencode({'format': 'tab'})
+       conn = httplib.HTTPConnection("dev.itmill.com")
+       conn.request("GET", "/ticket/%d?%s" % (ticketno, params) )
+       response = conn.getresponse()
+       data = response.read()
+       conn.close()
+
+       lines = data.split("\n")
+       cols = lines[1].split("\t")
+       return cols[1]
+
+# Adds summary to ticket number, unless the context already has it
+def addSummary(m):
+       ticketnum = int(m.group(1))
+       context = m.group(2)
+       if re.match(" *\(", context):
+               # The context already has ticket summary
+               return "#%d%s" % (ticketnum, context)
+
+       summary = fetchSummary(ticketnum)
+
+       return "#%s (<i>%s</i>) %s" % (ticketnum, summary, context)
+
+################################################################################
+# Postprocessing for XSLT output
+################################################################################
+
+lines = pin.readlines()
+for line in lines:
+       # Add ticket summary after ticket number, if missing
+       line = re.sub(r'#([0-9]+)(.*)', addSummary, line)
+       
+       # Change ticket numbers to references to tickets
+       line = re.sub(r'#([0-9]+)', '#<a href="http://dev.itmill.com/ticket/\\1">\\1</a>', line)
+       
+       # Change changeset numbers to references to changesets
+       #line = re.sub(r'\[([0-9]+)\]', '[<a href="http://dev.itmill.com/changeset/\\1">\\1</a>]', line)
+
+       # Remove prefix about merging
+       line = re.sub(r'Merged.+from trunk to [0-9]+.[0-9]+ branch: ', '', line)
+       
+       print line,
diff --git a/build/bin/svnlog-to-rn.xsl b/build/bin/svnlog-to-rn.xsl
new file mode 100644 (file)
index 0000000..75dd762
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                version="1.0">
+
+  <xsl:output method="html"/>
+
+  <xsl:template match="logentry">
+    <li>[<xsl:element name="a"><xsl:attribute name="href">http://dev.itmill.com/changeset/<xsl:value-of select="@revision"/></xsl:attribute><xsl:value-of select="@revision"/></xsl:element>]: <xsl:value-of select="msg"/></li>
+  </xsl:template>
+
+  <xsl:template match="/">
+    <html>
+      <body bgcolor="#FFFFFF">
+        <xsl:apply-templates/>
+      </body>
+    </html>
+  </xsl:template>
+
+</xsl:stylesheet>