diff --git a/product/ERP5Subversion/SubversionClient.py b/product/ERP5Subversion/SubversionClient.py index 770247c5001a3c752b8616133f02562a5d0fe9e0..59c836751d206021a0129dcc7c715fa9b7fe96d4 100644 --- a/product/ERP5Subversion/SubversionClient.py +++ b/product/ERP5Subversion/SubversionClient.py @@ -34,6 +34,7 @@ from MethodObject import Method from Globals import InitializeClass from AccessControl import ClassSecurityInfo from Products.ERP5Type import Permissions +from zLOG import LOG try: import pysvn @@ -229,7 +230,11 @@ try: def diff(self, path): self._getPreferences() os.system('mkdir -p /tmp/tmp-svn/') - return self.client.diff('/tmp/tmp-svn',path) + return self.client.diff(tmp_path='/tmp/tmp-svn/', url_or_path=path, recurse=False) + + def revert(self, path): + self._getPreferences() + return self.client.revert(path) def newSubversionClient(container, **kw): return SubversionClient(**kw).__of__(container) diff --git a/product/ERP5Subversion/Tool/SubversionTool.py b/product/ERP5Subversion/Tool/SubversionTool.py index aa3989610de4968a2125161dd77ed04bbaa440f1..1a4d8485d39b218f52fb6fb8dad99ab7045ccb9c 100644 --- a/product/ERP5Subversion/Tool/SubversionTool.py +++ b/product/ERP5Subversion/Tool/SubversionTool.py @@ -34,7 +34,7 @@ from Products.ERP5Type import Permissions from Products.ERP5Subversion import _dtmldir from zLOG import LOG, WARNING, INFO from Products.ERP5Subversion.SubversionClient import newSubversionClient -import os +import os, re from DateTime import DateTime from cPickle import dumps, loads from App.config import getConfiguration @@ -73,6 +73,221 @@ class Dir : if d.name == name: return d ## End of Dir Class + +class DiffFile: + # Members : + # - path : path of the modified file + # - children : sub codes modified + # - old_revision + # - new_revision + + def __init__(this, raw_diff): + this.header = raw_diff.split('@@')[0][:-1] + # Getting file path in header + this.path = this.header.split('====')[0][:-1].strip() + # Getting revisions in header + for line in this.header.split('\n'): + if line.startswith('--- '): + tmp = re.search('\\([\w\s]+\\)$', line) + this.old_revision = tmp.string[tmp.start():tmp.end()][1:-1].strip() + if line.startswith('+++ '): + tmp = re.search('\\([\w\s]+\\)$', line) + this.new_revision = tmp.string[tmp.start():tmp.end()][1:-1].strip() + # Splitting the body from the header + this.body = '\n'.join(raw_diff.strip().split('\n')[4:]) + # Now splitting modifications + this.children = [] + first = True + tmp = [] + for line in this.body.split('\n'): + if line: + if line.startswith('@@') and not first: + this.children.append(CodeBlock('\n'.join(tmp))) + tmp = [line,] + else: + first = False + tmp.append(line) + this.children.append(CodeBlock('\n'.join(tmp))) + + + def _escape(this, data): + """ + Escape &, <, and > in a string of data. + This is a copy of the xml.sax.saxutils.escape function. + """ + # must do ampersand first + if data: + #data = data.replace("&", "&") + data = data.replace(">", ">") + data = data.replace("<", "<") + return data + + def toHTML(this): + # Adding header of the table + html = '''<font color='black'><b>%s</b><br> + <hr><br> + <table style="text-align: left; width: 100%%;" border="0" cellpadding="0" cellspacing="0"> + <tbody> + <tr height="18px"> + <td style="background-color: grey"><b><center>%s</center></b></td> + <td style="background-color: grey"><b><center>%s</center></b></td> + </tr>'''%(this.path, this.old_revision, this.new_revision) + First = True + for child in this.children: + # Adding line number of the modification + if First: + html += '''<tr height="18px"><td style="background-color: grey"> </td><td style="background-color: grey"> </td></tr> <tr> + <td style="background-color: rgb(68, 132, 255);"><b>Line %s</b></td> + <td style="background-color: rgb(68, 132, 255);"><b>Line %s</b></td> + </tr>'''%(child.old_line, child.new_line) + First = False + else: + html += '''<tr height="18px"><td style="background-color: white"> </td><td style="background-color: white"> </td></tr> <tr> + <td style="background-color: rgb(68, 132, 255);"><b>Line %s</b></td> + <td style="background-color: rgb(68, 132, 255);"><b>Line %s</b></td> + </tr>'''%(child.old_line, child.new_line) + # Adding diff of the modification + old_code_list = child.getOldCodeList() + new_code_list = child.getNewCodeList() + i=0 + for old_line_tuple in old_code_list: + new_line_tuple = new_code_list[i] + if new_line_tuple[0]: + new_line = new_line_tuple[0] + else: + new_line = ' ' + if old_line_tuple[0]: + old_line = old_line_tuple[0] + else: + old_line = ' ' + i+=1 + html += ''' <tr height="18px"> + <td style="background-color: %s">%s</td> + <td style="background-color: %s">%s</td> + </tr>'''%(old_line_tuple[1], this._escape(old_line).replace(' ', ' ').replace('\t', ' '), new_line_tuple[1], this._escape(new_line).replace(' ', ' ').replace('\t', ' ')) + html += ''' </tbody> +</table></font><br><br>''' + return html + + +# A code block contains several SubCodeBlocks +class CodeBlock: + # Members : + # - old_line : line in old code (before modif) + # - new line : line in new code (after modif) + # + # Methods : + # - getOldCodeList() : return code before modif + # - getNewCodeList() : return code after modif + # Note: the code returned is a list of tuples (code line, background color) + + def __init__(this, raw_diff): + # Splitting body and header + this.body = '\n'.join(raw_diff.split('\n')[1:]) + this.header = raw_diff.split('\n')[0] + # Getting modifications lines + tmp = re.search('^@@ -\d+', this.header) + this.old_line = tmp.string[tmp.start():tmp.end()][4:] + tmp = re.search('\+\d+,', this.header) + this.new_line = tmp.string[tmp.start():tmp.end()][1:-1] + # Splitting modifications in SubCodeBlocks + in_modif = False + this.children = [] + tmp=[] + for line in this.body.split('\n'): + if line: + if (line.startswith('+') or line.startswith('-')): + if in_modif: + tmp.append(line) + else: + this.children.append(SubCodeBlock('\n'.join(tmp))) + tmp = [line,] + in_modif = True + else: + if in_modif: + this.children.append(SubCodeBlock('\n'.join(tmp))) + tmp = [line,] + in_modif = False + else: + tmp.append(line) + this.children.append(SubCodeBlock('\n'.join(tmp))) + + # Return code before modification + def getOldCodeList(this): + tmp = [] + for child in this.children: + tmp.extend(child.getOldCodeList()) + return tmp + + # Return code after modification + def getNewCodeList(this): + tmp = [] + for child in this.children: + tmp.extend(child.getNewCodeList()) + return tmp + +# a SubCodeBlock contain 0 or 1 modification (not more) +class SubCodeBlock: + def __init__(this, code): + this.body=code + this.modification=this._getModif() + # Choosing background color + if this.modification == 'none': + this.color = 'white' + elif this.modification == 'change': + this.color = 'rgb(253, 228, 6);'#light orange + elif this.modification == 'deletion': + this.color = 'rgb(253, 117, 74);'#light red + else: + this.color = 'rgb(83, 253, 74);'#light green + + def _getModif(this): + nb_plus = 0 + nb_minus = 0 + for line in this.body.split('\n'): + if line.startswith("-"): + nb_minus-=1 + elif line.startswith("+"): + nb_plus+=1 + if (nb_plus!=0 and nb_minus==0): + return 'addition' + if (nb_plus==0 and nb_minus!=0): + return 'deletion' + if (nb_plus==0 and nb_minus==0): + return 'none' + return 'change' + + # Return code before modification + def getOldCodeList(this): + if this.modification=='none': + return [(x, 'white') for x in this.body.split('\n')] + elif this.modification=='change': + return [this._getOldCodeList(x) for x in this.body.split('\n') if this._getOldCodeList(x)[0]] + else: # deletion or addition + return [this._getOldCodeList(x) for x in this.body.split('\n')] + + def _getOldCodeList(this, line): + if line.startswith('+'): + return (None, this.color) + if line.startswith('-'): + return (' '+line[1:], this.color) + return (line, this.color) + + # Return code after modification + def getNewCodeList(this): + if this.modification=='none': + return [(x, 'white') for x in this.body.split('\n')] + elif this.modification=='change': + return [this._getNewCodeList(x) for x in this.body.split('\n') if this._getNewCodeList(x)[0]] + else: # deletion or addition + return [this._getNewCodeList(x) for x in this.body.split('\n')] + + def _getNewCodeList(this, line): + if line.startswith('-'): + return (None, this.color) + if line.startswith('+'): + return (' '+line[1:], this.color) + return (line, this.color) class SubversionTool(UniqueObject, Folder): """The SubversionTool provides a Subversion interface to ERP5. @@ -192,6 +407,10 @@ class SubversionTool(UniqueObject, Folder): # Decode login information. trust_item_list, permanent = loads(b64decode(login)) return dict(trust_item_list), permanent + + def diffHTML(self, file_path): + raw_diff = self.diff(file_path) + return DiffFile(raw_diff).toHTML() security.declareProtected(Permissions.ManagePortal, 'acceptSSLServer') def acceptSSLServer(self, trust_dict, permanent=False):