Index: scmtools/clearcase.py
===================================================================
--- scmtools/clearcase.py	(revision 0)
+++ scmtools/clearcase.py	(revision 0)
@@ -0,0 +1,227 @@
+import re
+import urllib
+import urlparse
+import os
+import cleartool  # Please comment out this import if you are running under windows.
+
+
+from reviewboard.diffviewer.parser import DiffParser
+from reviewboard.scmtools.core import \
+    SCMError, FileNotFoundError, SCMTool, HEAD, PRE_CREATION, UNKNOWN
+    
+
+class ClearCaseTool(SCMTool):
+    """
+      The current implementation is only useful for serial format clearcase diff,
+      which means you should add -serial_format parameter when using cleartool 
+      diff command.
+
+
+      For linux platform.
+       1. pycleartool is needed to enable this module, it can be fetch here:
+          http://pypi.python.org/pypi/pycleartool/2005.02.
+       2. As for the setview command is used to fetch the file in clearcase, 
+          the current implementation is only valid on linux platfrom(setview is
+          not available on windows platfrom of clearcase).
+       3. To enable this function, a view name Reviewboard_codeReview should 
+          be created in the clearcase.
+       4. Create a repository and set it's path as the root directory("/").
+
+       For windows platform
+       1. comment out two sentence : 
+            import cleartool 
+            (sts, out, err) = cleartool.cmd('setview  Reviewboard_codeReview')
+       2. Create a repository and set it's path as the clearcase view mapped dirver,
+          for example, I have a clearcase view Reivewboard_codeReview and mapped it 
+	  to the local dirver F:, the path should be set as F:.
+    """
+    AUTHOR_KEYWORDS   = ['Author', 'LastChangedBy']
+    DATE_KEYWORDS     = ['Date', 'LastChangedDate']
+    REVISION_KEYWORDS = ['Revision', 'LastChangedRevision', 'Rev']
+    URL_KEYWORDS      = ['HeadURL', 'URL']
+    ID_KEYWORDS       = ['Id']
+    BEGIN_SEP         = "*"*32
+
+    # Mapping of keywords to known aliases
+    keywords = {
+        # Standard keywords
+        'Author':              AUTHOR_KEYWORDS,
+        'Date':                DATE_KEYWORDS,
+        'Revision':            REVISION_KEYWORDS,
+        'HeadURL':             URL_KEYWORDS,
+        'Id':                  ID_KEYWORDS,
+
+        # Aliases
+        'LastChangedBy':       AUTHOR_KEYWORDS,
+        'LastChangedDate':     DATE_KEYWORDS,
+        'LastChangedRevision': REVISION_KEYWORDS,
+        'Rev':                 REVISION_KEYWORDS,
+        'URL':                 URL_KEYWORDS,
+    }
+
+    def __init__(self, repository):
+        self.repopath = repository.path
+        if self.repopath[-1] == '/':
+            self.repopath = self.repopath[:-1]
+        SCMTool.__init__(self, repository)
+        self.uses_atomic_revisions = True
+
+    def get_file(self, path, revision=HEAD):
+        if not path:
+            raise FileNotFoundError(path, revision)
+        try:
+            normpath = self.__normalize_path(path)
+            # comment out next line if you are using windows platform.
+	    (sts, out, err) = cleartool.cmd('setview  Reviewboard_codeReview')
+	    data = open(normpath).read()
+            return data
+        except IOError, e:
+            stre = str(e)
+            if 'File not found' in stre or 'path not found' in stre:
+                raise FileNotFoundError(path, revision, str(e))
+            else:
+                raise SCMError(e)
+
+    def parse_diff_revision(self, file_str, revision_str):
+         return file_str[1:], 100
+
+
+    def get_filenames_in_revision(self, revision):
+        r = self.__normalize_revision(revision)
+        logs = self.client.log(self.repopath, r, r, True)
+
+        if len(logs) == 0:
+            return []
+        elif len(logs) == 1:
+            return [f['path'] for f in logs[0]['changed_paths']]
+        else:
+            assert False
+
+    def get_repository_info(self):
+        try:
+            info = self.client.info2(self.repopath, recurse=False)
+        except ClientError, e:
+            raise SCMError(e)
+
+        return {
+            'uuid': info[0][1].repos_UUID,
+            'root_url': info[0][1].repos_root_URL,
+            'url': info[0][1].URL
+        }
+
+    def __normalize_revision(self, revision):
+        if revision == HEAD:
+            r = Revision(opt_revision_kind.head)
+        elif revision == PRE_CREATION:
+            raise FileNotFoundError('', revision)
+        else:
+            r = Revision(opt_revision_kind.number, str(revision))
+        return r
+
+    def __normalize_path(self, path):
+        if path.startswith(self.repopath):
+            return path
+        elif path[0] == '/':
+            return self.repopath + path
+        else:
+            return self.repopath + "/" + path
+
+    def get_fields(self):
+        return ['basedir', 'diff_path']
+
+   
+    def get_parser(self, data):
+        """
+        The general idea is to change the clearcase diff file format
+        to gun diff file format first, then leverage the common parser
+        to parse the diff file.
+	"""
+        d = self.ccdiff_to_gnudiff(data)
+        dest_data = ""
+        for line in d:
+           dest_data = dest_data + "\n" + line
+        return DiffParser(dest_data)
+
+    def parse_start_finished(self, line):
+        collection = line.split("-")
+        start = int(collection[0])
+        finish = start
+        if(len(collection) == 2):
+                  finish = int(collection[1])
+        num = finish - start + 1
+        return [start, finish, num] 
+
+    def ccdiff_to_gnudiff(self, data):
+        lines = data.split("\r\n")
+        clearcase_diff_lines = []
+	for line in lines:
+	    if not re.match("^\s*$", line):
+	        clearcase_diff_lines.append(line)
+	gnu_diff_lines = []
+        i = 0
+        while i < len(clearcase_diff_lines):
+           if i < len(clearcase_diff_lines) and \
+               clearcase_diff_lines[i] == self.BEGIN_SEP and \
+               (i+3) < len(clearcase_diff_lines) and \
+               clearcase_diff_lines[i+3] == self.BEGIN_SEP:
+                   original_file_name = clearcase_diff_lines[i+1].split(":")[1][1:]
+                   new_file_name = clearcase_diff_lines[i+2].split(":")[1][1:]
+                   gnu_diff_lines.append("Index: " + original_file_name)
+                   if (original_file_name[0] != '.') and (original_file_name[0] =='\\'):
+                           original_file_name = "." + original_file_name
+                   else:
+                           original_file_name = ".\\" + original_file_name
+
+                   gnu_diff_lines.append("=" * 67)
+                   gnu_diff_lines.append("--- " + original_file_name + "   (revision 100)")
+                   gnu_diff_lines.append("+++ " + original_file_name.split("@@")[0] + "  (working copy)")
+                   i = i + 4
+           if i < len(clearcase_diff_lines):
+               pattern = '-{5}\[after\s+([\d\-]*)\s+inserted\s+([\d\-]*)\]-{5}'
+               match = re.match(pattern, clearcase_diff_lines[i])
+               if match is not None:
+                  old_start = int(match.group(1))
+                  [new_start, new_finish, new_line_num] = self.parse_start_finished(match.group(2))
+                  newline = '@@ -' + str(old_start+1) + ',0 +' + str(new_start) + ',' + str(new_line_num) + ' @@'
+                  gnu_diff_lines.append(newline)
+                  k = 0
+                  i = i + 1
+                  while k < new_line_num:
+                        gnu_diff_lines.append("+" + clearcase_diff_lines[i][2:])
+                        i = i + 1
+                        k = k + 1
+               else:
+                  pattern = '-{5}\[([\d\-]*)\s+changed to\s+([\d\-]*)\]-{5}'
+                  match = re.match(pattern, clearcase_diff_lines[i])
+                  if match is not None:
+                      [old_start, old_finish, old_line_num] = self.parse_start_finished(match.group(1))
+                      [new_start, new_finish, new_line_num] = self.parse_start_finished(match.group(2))
+                      newline ='@@ -' + str(old_start) + "," + str(old_line_num) + ' +'
+                      newline = newline + str(new_start) + ',' + str(new_line_num) + ' @@'
+                      gnu_diff_lines.append(newline)
+                      k = 0
+                      i = i + 1
+                      while k < old_line_num:
+                         gnu_diff_lines.append("-" + clearcase_diff_lines[i][2:])
+                         [i, k] = [i+1, k+1]
+                      k = 0
+                      i = i + 1
+                      while k < new_line_num:
+                          gnu_diff_lines.append("+" + clearcase_diff_lines[i][2:])
+                          [i, k] = [i+1, k+1]
+                  else:
+                      pattern =  '-{5}\[deleted\s+([\d\-]*)\s+after\s+([\d\-]*)\]-{5}'
+                      match = re.match(pattern, clearcase_diff_lines[i])
+                      if match is not None:
+                        [old_start, old_finish, old_line_num] = self.parse_start_finished(match.group(1))
+                        new_start = int(match.group(2))
+                        newline = '@@ -' + str(old_start) + "," + str(old_line_num) + " +" + str(new_start+1) + ",0 @@"
+                        gnu_diff_lines.append(newline)
+                        k = 0
+                        i = i + 1
+                        while k < old_line_num:
+                                gnu_diff_lines.append("-" + clearcase_diff_lines[i][2:])
+                                [i,k] = [i+1, k+1]
+        return gnu_diff_lines
+
+
Index: scmtools/fixtures/initial_data.json
===================================================================
--- scmtools/fixtures/initial_data.json	(revision 1570)
+++ scmtools/fixtures/initial_data.json	(working copy)
@@ -35,4 +35,11 @@
                 "class_name": "reviewboard.scmtools.bzr.BZRTool"
                }
     }
+    {"pk": "7",
+     "model": "scmtools.tool",
+     "fields": {"name": "ClearCase",
+                "class_name": "reviewboard.scmtools.clearcase.ClearCaseTool"
+               }
+    }
+
 ]
