Switch to side-by-side view

--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -73,29 +73,6 @@
 
     def compute_diffs(self): return
 
-    def count(self, *args, **kwargs):
-        return super(Repository, self).count(None)
-
-    def count_revisions(self, ci):
-        # since SVN histories are inherently linear and the commit _id
-        # contains the revision, just parse it out from there
-        return int(self._impl._revno(ci._id))
-
-    def log(self, branch='HEAD', offset=0, limit=10):
-        return list(self._log(branch, offset, limit))
-
-    def commitlog(self, commit_ids, skip=0, limit=sys.maxint):
-        ci_id = commit_ids[0]
-        if skip > 0:
-            rid, rev = ci_id.split(':')
-            rev = int(rev) - skip
-            ci_id = '%s:%s' % (rid, rev)
-        ci = self._impl.commit(ci_id)
-        while ci is not None and limit > 0:
-            yield ci._id
-            limit -= 1
-            ci = ci.get_parent()
-
     def latest(self, branch=None):
         if self._impl is None: return None
         return self._impl.commit('HEAD')
@@ -105,6 +82,9 @@
         path = self._impl._path_to_root(path, revision)
         fn += ('-' + '-'.join(path.split('/'))) if path else ''
         return fn
+
+    def rev_to_commit_id(self, rev):
+        return self._impl.rev_parse(rev)
 
 
 class SVNCalledProcessError(Exception):
@@ -186,13 +166,13 @@
         return 'file://%s%s' % (self._repo.fs_path, self._repo.name)
 
     def shorthand_for_commit(self, oid):
-        return '[r%d]' % self._revno(oid)
+        return '[r%d]' % self._revno(self.rev_parse(oid))
 
     def url_for_commit(self, commit, url_type=None):
-        if isinstance(commit, basestring):
-            object_id = commit
-        else:
+        if hasattr(commit, '_id'):
             object_id = commit._id
+        else:
+            object_id = self.rev_parse(commit)
         if ':' in object_id:
             object_id = str(self._revno(object_id))
         return os.path.join(self._repo.url(), object_id) + '/'
@@ -299,16 +279,19 @@
         self._setup_special_files(source_url)
 
     def commit(self, rev):
-        if rev in ('HEAD', None):
-            oid = self._oid(self.head)
-        elif isinstance(rev, int) or rev.isdigit():
-            oid = self._oid(rev)
-        else:
-            oid = rev
+        oid = self.rev_parse(rev)
         result = M.repo.Commit.query.get(_id=oid)
         if result:
             result.set_context(self._repo)
         return result
+
+    def rev_parse(self, rev):
+        if rev in ('HEAD', None):
+            return self._oid(self.head)
+        elif isinstance(rev, int) or rev.isdigit():
+            return self._oid(rev)
+        else:
+            return rev
 
     def all_commit_ids(self):
         """Return a list of commit ids, starting with the head (most recent
@@ -494,20 +477,79 @@
         else:
             return self._blob_oid(commit_id, path)
 
-    def log(self, object_id, skip, count):
-        revno = self._revno(object_id)
-        result = []
-        while count and revno:
-            if skip == 0:
-                result.append(self._oid(revno))
-                count -= 1
-            else:
-                skip -= 1
-            revno -= 1
-        if revno:
-            return result, [ self._oid(revno) ]
-        else:
-            return result, []
+    def log(self, revs=None, path=None, exclude=None, id_only=True, page_size=25, **kw):
+        """
+        Returns a generator that returns information about commits reacable
+        by revs.
+
+        revs can be None or a list or tuple of identifiers, each of which
+        can be anything parsable by self.commit().  If revs is None, the
+        default head will be used.
+
+        If path is not None, only commits which modify files under path
+        will be included.
+
+        Exclude can be None or a list or tuple of identifiers, each of which
+        can be anything parsable by self.commit().  If not None, then any
+        revisions reachable by any of the revisions in exclude will not be
+        included.
+
+        If id_only is True, returns only the commit ID, otherwise it returns
+        detailed information about each commit.
+
+        Since pysvn doesn't have a generator version of log, this tries to
+        balance pulling too much data from SVN with calling SVN too many
+        times by pulling in pages of page_size at a time.
+        """
+        if revs is None:
+            revno = self.head
+        else:
+            revno = max([self._revno(self.rev_parse(r)) for r in revs])
+        if exclude is None:
+            exclude = 0
+        else:
+            exclude = max([self._revno(self.rev_parse(r)) for r in exclude])
+        if path is None:
+            url = self._url
+        else:
+            url = '/'.join([self._url, path])
+        while revno > exclude:
+            rev = pysvn.Revision(pysvn.opt_revision_kind.number, revno)
+            try:
+                logs = self._svn.log(url, revision_start=rev, limit=page_size)
+            except pysvn.ClientError as e:
+                if 'Unable to connect' in e.message:
+                    raise  # repo error
+                return  # no (more) history for this path
+            for ci in logs:
+                if ci.revision.number <= exclude:
+                    return
+                if id_only:
+                    yield ci.revision.number
+                else:
+                    yield self._map_log(ci)
+            if len(logs) < page_size:
+                return  # we didn't get a full page, don't bother calling SVN again
+            revno = ci.revision.number - 1
+
+    def _map_log(self, ci):
+        revno = ci.revision.number
+        return {
+                'id': revno,
+                'message': h.really_unicode(ci.get('message', '--none--')),
+                'authored': {
+                        'name': h.really_unicode(ci.get('author', '--none--')),
+                        'email': '',
+                        'date': datetime.utcfromtimestamp(ci.date),
+                    },
+                'committed': {
+                        'name': h.really_unicode(ci.get('author', '--none--')),
+                        'email': '',
+                        'date': datetime.utcfromtimestamp(ci.date),
+                    },
+                'refs': ['HEAD'] if revno == self.head else [],
+                'parents': [revno-1] if revno > 1 else [],
+            }
 
     def open_blob(self, blob):
         data = self._svn.cat(