incubator-allura-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From john...@apache.org
Subject [2/2] git commit: Partial commit, WIP
Date Tue, 27 Nov 2012 16:18:54 GMT
Partial commit, WIP


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/e4e9fe84
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/e4e9fe84
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/e4e9fe84

Branch: refs/heads/cj/4691
Commit: e4e9fe84ead0035a39b57a3b3c2a7912d18637d0
Parents: 7eb7956
Author: Cory Johns <johnsca@geek.net>
Authored: Wed Oct 10 02:28:07 2012 +0000
Committer: Cory Johns <johnsca@geek.net>
Committed: Tue Nov 27 14:53:45 2012 +0000

----------------------------------------------------------------------
 Allura/allura/model/repo.py                        |  191 ++++++-
 Allura/allura/model/repo_refresh.py                |  101 ++++-
 Allura/allura/model/repository.py                  |   38 ++-
 .../allura/templates/widgets/repo/tree_widget.html |   20 +-
 Allura/allura/tests/model/test_repo.py             |  451 +++++++++++++++
 ForgeGit/forgegit/model/git_repo.py                |    4 +
 ForgeHg/forgehg/model/hg.py                        |    4 +
 ForgeSVN/forgesvn/model/svn.py                     |    5 +-
 8 files changed, 790 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4e9fe84/Allura/allura/model/repo.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo.py b/Allura/allura/model/repo.py
index b46f3fa..bb9784f 100644
--- a/Allura/allura/model/repo.py
+++ b/Allura/allura/model/repo.py
@@ -11,7 +11,7 @@ from difflib import SequenceMatcher, unified_diff
 from pylons import c
 import pymongo.errors
 
-from ming import Field, collection
+from ming import Field, collection, Index
 from ming import schema as S
 from ming.base import Object
 from ming.utils import LazyProperty
@@ -61,9 +61,7 @@ TreeDoc = collection(
     Field('blob_ids', [dict(name=str, id=str)]),
     Field('other_ids', [dict(name=str, id=str, type=SObjType)]))
 
-# Information about the last commit to touch a tree/blob
-# LastCommitDoc.object_id = TreeDoc._id
-LastCommitDoc = collection(
+LastCommitDoc_old = collection(
     'repo_last_commit', project_doc_session,
     Field('_id', str),
     Field('object_id', str, index=True),
@@ -77,6 +75,26 @@ LastCommitDoc = collection(
         shortlink=str,
         summary=str)))
 
+# Information about the last commit to touch a tree
+LastCommitDoc = collection(
+    'repo_last_commit', main_doc_session,
+    Field('_id', S.ObjectId()),
+    Field('commit_ids', [str]),
+    Field('path', str),
+    Index('commit_ids', 'path'),
+    Field('entries', {
+        str:dict(
+            type=str,
+            name=str,
+            commit_info=dict(
+                id=str,
+                date=datetime,
+                author=str,
+                author_email=str,
+                author_url=str,
+                shortlink=str,
+                summary=str))}))
+
 # List of all trees contained within a commit
 # TreesDoc._id = CommitDoc._id
 # TreesDoc.tree_ids = [ TreeDoc._id, ... ]
@@ -182,13 +200,28 @@ class Commit(RepoObject):
     def symbolic_ids(self):
         return self.repo.symbolics_for_commit(self)
 
-    def parent(self, index=0):
-        ci = None
-        if self.parent_ids:
+    def get_parent(self, index=0):
+        '''Get the parent of this commit.
+
+        If there is no parent commit, or if an invalid index is given,
+        returns None.
+        '''
+        try:
             ci = self.query.get(_id=self.parent_ids[index])
-        if ci:
             ci.set_context(self.repo)
-        return ci
+            return ci
+        except IndexError as e:
+            return None
+
+    def climb_commit_tree(self):
+        '''
+        Returns a generator that walks up the commit tree along
+        the first-parent ancestory, starting with this commit.'''
+        yield self
+        ancestor = self.get_parent()
+        while ancestor:
+            yield ancestor
+            ancestor = ancestor.get_parent()
 
     def url(self):
         if self.repo is None: self.repo = self.guess_repo()
@@ -293,7 +326,7 @@ class Commit(RepoObject):
         if not removed:
             return []
         copied = []
-        prev_commit = self.parent()
+        prev_commit = self.get_parent()
         for removed_name in removed[:]:
             removed_blob = prev_commit.tree.get_obj_by_path(removed_name)
             rename_info = None
@@ -316,6 +349,26 @@ class Commit(RepoObject):
             cur = cur[part]
         return cur
 
+    def changed(self, path):
+        '''Test whether a given path was changed in this commit.'''
+        di = DiffInfoDoc.m.get(_id=self._id)
+        for change in di.differences:
+            if change.name.strip('/') == path.strip('/'):
+                return True
+        return False
+
+    @LazyProperty
+    def info(self):
+        return dict(
+            id=self._id,
+            author=self.authored.name,
+            author_email=self.authored.email,
+            date=self.authored.date,
+            author_url=self.author_url,
+            shortlink=self.shorthand_id(),
+            summary=self.summary
+            )
+
 class Tree(RepoObject):
     # Ephemeral attrs
     repo=None
@@ -386,6 +439,15 @@ class Tree(RepoObject):
         return None, None
 
     def ls(self):
+        last_commit = LastCommitDoc.m.get(
+                commit_ids=self.commit._id,
+                path=self.path(),
+            )
+        if not last_commit:
+            from .repo_refresh import build_last_commit_doc
+            last_commit = build_last_commit_doc(self)
+        return sorted(last_commit.entries.values(), cmp=lambda a,b: cmp(b.type,a.type) or
cmp(a.name,b.name))
+
         # Load last commit info
         id_re = re.compile("^{0}:{1}:".format(
             self.repo._id,
@@ -569,5 +631,114 @@ class Blob(object):
         differ = SequenceMatcher(v0, v1)
         return differ.get_opcodes()
 
+class LastCommit(RepoObject):
+    @classmethod
+    def build(cls, tree):
+        '''
+        We need a LCD for this tree, for which there are two possibilities:
+
+          1) This tree was modified in an ancestor commit but the commit ID chain
+             was not filled in.  In this case, as we walk back up the tree, we'll
+             find a LCD record when or before we find the commit in which this tree
+             was changed.  If we find this, we save the new commit IDs in the LCD
+             record for faster access in the future.
+
+          2) The LCD record for the commit in which this tree was changed does not
+             exist.  We'll find the commit and still not have a LCD record, which
+             means we have to construct it.  The LCD record will only contain the
+             commit IDs up to the commit where the tree was most recently changed.
+
+             Constructing it differs for SVN and Git / Hg.  SVN can pull all the info
+             from a single SVN call.  Git / Hg have to walk up the tree.  (SVN could
+             walk up the tree as well, except that the TreesDoc and DiffInfoDoc
+             records are not correctly populated, making it hard to tell when a tree
+             was changed in a given commit, plus it's unnecessary.)
+
+             To walk up the tree, we have to keep track of which entries we still
+             need info about.  At each step of the walk, we check the following:
+
+               1) If the current tree has a LCD record, we can pull all the remaining
+                  info we need from it, and we're done.
+
+               2) If the tree was modified in this commit, then we pull the info for
+                  all changed entries, then making the parent tree the new active
+                  tree and continuing the walk.  Once we have data for all entries,
+                  we're done.
+        '''
+        unfilled = set([n.name for n in chain(tree.tree_ids, tree.blob_ids, tree.other_ids)])
+        tree_nodes = set([n.name for n in tree.tree_ids])
+        entires = []
+        commit_ids = []
+        commit = tree.commit
+        path = tree.path().strip('/')
+        lcd = None
+        has_changes = False
+        for commit in commit.climb_commit_tree():
+            last_commit = LastCommitDoc.m.get(
+                    commit_ids=commit._id,
+                    path=path,
+                )
+            if not has_changes:
+                # no changes found yet, so look to see if we have a matching LCD
+                # that just doesn't have all the commit_ids filled in
+                if last_commit:
+                    # found a complete LCD for our tree
+                    last_commit.commit_ids.extend(commit_ids)
+                    last_commit.m.save()
+                    return last_commit
+                # LCD is only valid for the most recent commit
+                # that changed the tree, so once we have changes,
+                # stop recording commit_ids
+                commit_ids.append(commit._id)
+
+            # look for changes to the tree in this commit,
+            # meaning our LCD is missing and must be built
+            diff_info = DiffInfoDoc.m.get(_id=commit._id)
+            diffs = set()
+            for d in diff_info.differences:
+                diffs.add(d.name)
+                node_path = os.path.dirname(d.name)
+                while node_path:
+                    diffs.add(node_path)
+                    node_path = os.path.dirname(node_path)
+                diffs.add('')  # include '/' if there are any changes
+            #import ipdb; ipdb.set_trace()
+            if path in diffs:
+                has_changes = True
+            for name in list(unfilled):
+                full_name = os.path.join(path, name)
+                if full_name in diffs:
+                    if lcd is None:
+                        lcd = LastCommitDoc(dict(
+                                    commit_ids=commit_ids,
+                                    path=path,
+                                    entries=dict(),
+                                ))
+                    lcd.entries[name] = dict(
+                            type=name in tree_nodes and 'DIR' or 'BLOB',
+                            name=name,
+                            commit_info=commit.info,
+                        )
+                    unfilled.remove(name)
+
+            # if we have changes but this commit has an LCD for our
+            # path, it should have all the remaining info we need
+            if last_commit:
+                for name in list(unfilled):
+                    lcd.entries[name] = last_commit.entries[name]
+                    unfilled.remove(name)
+
+            if has_changes and not unfilled:
+                break
+        if lcd is None:
+            lcd = LastCommitDoc(dict(
+                        commit_ids=commit_ids,
+                        path=path,
+                        entries=dict(),
+                    ))
+        lcd.m.save()
+        return lcd
+
 mapper(Commit, CommitDoc, repository_orm_session)
 mapper(Tree, TreeDoc, repository_orm_session)
+mapper(LastCommit, LastCommitDoc, repository_orm_session)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4e9fe84/Allura/allura/model/repo_refresh.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 6e0db59..3fa41fc 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -2,6 +2,7 @@ import logging
 from itertools import chain
 from cPickle import dumps
 import re
+import os
 
 import bson
 
@@ -348,7 +349,7 @@ def compute_diffs(repo_id, tree_cache, rhs_ci):
             dict(name=name, lhs_id=lhs_id, rhs_id=rhs_id))
     # Set last commit data
     rhs_tree = tree_index[rhs_ci.tree_id]
-    refresh_last_commit(repo_id, '/', rhs_tree, lhs_tree, None, commit_info)
+    #refresh_last_commit(repo_id, '/', rhs_tree, lhs_tree, None, commit_info)
     # Build the diffinfo
     di = DiffInfoDoc(dict(
             _id=rhs_ci._id,
@@ -522,3 +523,101 @@ def last_known_commit_id(all_commit_ids, new_commit_ids):
     if not all_commit_ids: return None
     if not new_commit_ids: return all_commit_ids[-1]
     return all_commit_ids[all_commit_ids.index(new_commit_ids[0]) - 1]
+
+def build_last_commit_doc(tree):
+    '''
+    We need a LCD for this tree, for which there are two possibilities:
+
+      1) This tree was modified in an ancestor commit but the commit ID chain
+         was not filled in.  In this case, as we walk back up the tree, we'll
+         find a LCD record when or before we find the commit in which this tree
+         was changed.  If we find this, we save the new commit IDs in the LCD
+         record for faster access in the future.
+
+      2) The LCD record for the commit in which this tree was changed does not
+         exist.  We'll find the commit and still not have a LCD record, which
+         means we have to construct it.  The LCD record will only contain the
+         commit IDs up to the commit where the tree was most recently changed.
+
+         Constructing it differs for SVN and Git / Hg.  SVN can pull all the info
+         from a single SVN call.  Git / Hg have to walk up the tree.  (SVN could
+         walk up the tree as well, except that the TreesDoc and DiffInfoDoc
+         records are not correctly populated, making it hard to tell when a tree
+         was changed in a given commit, plus it's unnecessary.)
+
+         To walk up the tree, we have to keep track of which entries we still
+         need info about.  At each step of the walk, we check the following:
+
+           1) If the current tree has a LCD record, we can pull all the remaining
+              info we need from it, and we're done.
+
+           2) If the tree was modified in this commit, then we pull the info for
+              all changed entries, then making the parent tree the new active
+              tree and continuing the walk.  Once we have data for all entries,
+              we're done.
+    '''
+    unfilled = set([n.name for n in chain(tree.tree_ids, tree.blob_ids, tree.other_ids)])
+    tree_nodes = set([n.name for n in tree.tree_ids])
+    entires = []
+    commit_ids = []
+    commit = tree.commit
+    path = tree.path().strip('/')
+    lcd = None
+    has_changes = False
+    while unfilled:
+        last_commit = LastCommitDoc.m.get(
+                commit_ids=commit._id,
+                path=path,
+            )
+        if not has_changes:
+            # no changes found yet, so look to see if we have a matching LCD
+            # that just doesn't have all the commit_ids filled in
+            if last_commit:
+                # found a complete LCD for our tree
+                last_commit.commit_ids.extend(commit_ids)
+                last_commit.m.save()
+                return last_commit
+            # LCD is only valid for the most recent commit
+            # that changed the tree, so once we have changes,
+            # stop recording commit_ids
+            commit_ids.append(commit._id)
+
+        # look for changes to the tree in this commit,
+        # meaning our LCD is missing and must be built
+        diff_info = DiffInfoDoc.m.get(_id=commit._id)
+        diffs = set()
+        for d in diff_info.differences:
+            diffs.add(d.name)
+            node_path = os.path.dirname(d.name)
+            while node_path:
+                diffs.add(node_path)
+                node_path = os.path.dirname(node_path)
+        import ipdb; ipdb.set_trace()
+        for name in list(unfilled):
+            full_name = path + '/' + name
+            if full_name in diffs:
+                has_changes = True
+                if lcd is None:
+                    lcd = LastCommitDoc(dict(
+                                commit_ids=commit_ids,
+                                path=path,
+                                entries=dict(),
+                            ))
+                lcd.entries[name] = dict(
+                        type=name in tree_nodes and 'DIR' or 'BLOB',
+                        name=name,
+                        commit_info=get_commit_info(commit),
+                    )
+                unfilled.remove(name)
+
+        # if we have changes but this commit has an LCD for our
+        # path, it should have all the remaining info we need
+        if last_commit:
+            for name in list(unfilled):
+                lcd.entries[name] = last_commit.entries[name]
+                unfilled.remove(name)
+
+        # walk up the tree
+        commit = commit.get_parent()
+    lcd.m.save()
+    return lcd

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4e9fe84/Allura/allura/model/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index 9867cf0..45125df 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -433,8 +433,42 @@ class Repository(Artifact, ActivityObject):
         with self.push_upstream_context():
             return MergeRequest.query.find(q).count()
 
-    def get_last_commit(self, obj):
-        from .repo import LastCommitDoc
+    def get_last_commit(self, tree):
+        '''Find the LastCommitDoc for the given tree.
+
+        Walks up the commit tree until either:
+
+        1) A LCD is found for the given tree.  (If the LCD was not found for the
+           tree's commit, the commits traversed while searching for it are
+           added to the LCD for faster retrieval in the future.)
+
+        2) The commit in which the tree was most recently modified is found.
+           In this case, we know that the LCD hasn't been constructed for this
+           (chain of) commit(s), and it will have to be built.
+        '''
+        from .repo import Commit, LastCommitDoc
+        last_commit = None
+        commit = tree.commit
+        path = tree.path()
+        orig_tree = tree
+        other_commit_ids = []
+        while commit is not None:
+            last_commit = LastCommitDoc.m.find(dict(
+                    commit_ids=commit._id,
+                    path=path,
+                )).first()
+            if last_commit is not None:
+                # found our LCD; add any traversed commits to it
+                last_commit.commit_ids.append(commit_ids)
+                last_commit.m.save()
+                return last_commit
+            elif commit.changed(path):
+                # tree was changed but no LCD found; have to build
+                return self.compute_last_commit(commit, path, other_commit_ids)
+            else:
+                other_commit_ids.append(commit._id)
+                commit = commit.get_parent()
+
         lc = LastCommitDoc.m.get(
             repo_id=self._id, object_id=obj._id)
         if lc is None:

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4e9fe84/Allura/allura/templates/widgets/repo/tree_widget.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/widgets/repo/tree_widget.html b/Allura/allura/templates/widgets/repo/tree_widget.html
index 09b0758..a2d8024 100644
--- a/Allura/allura/templates/widgets/repo/tree_widget.html
+++ b/Allura/allura/templates/widgets/repo/tree_widget.html
@@ -25,24 +25,24 @@
     {% for dirent in tree.ls() %}
     <tr>
       <td class="nowrap">
-        <a href="{{h.urlquote(dirent.href)}}">
-          <b data-icon="{{dirent.kind == 'DIR' and 'o' or 'n'}}" class="ico {{dirent.kind
== 'DIR' and 'folder' or 'table'}}"></b>
+        <a href="{{h.urlquote(dirent.name)}}">
+          <b data-icon="{{dirent.type == 'DIR' and 'o' or 'n'}}" class="ico {{dirent.type
== 'DIR' and 'folder' or 'table'}}"></b>
           <span>{{h.really_unicode(dirent.name)}}</span>
         </a>
       </td>
-      <td class="nowrap">{{lib.abbr_date(dirent.last_commit.date)}}</td>
+      <td class="nowrap">{{lib.abbr_date(dirent.commit_info.date)}}</td>
       <td class="nowrap">
-        {% if dirent.last_commit.author_url %}
-          <a href="{{dirent.last_commit.author_url}}">{{lib.email_gravatar(dirent.last_commit.author_email,
title=h.really_unicode(dirent.last_commit.author), size=16)}}</a>
-          <a href="{{dirent.last_commit.author_url}}">{{h.really_unicode(dirent.last_commit.author)}}</a>
+        {% if dirent.commit_info.author_url %}
+          <a href="{{dirent.commit_info.author_url}}">{{lib.email_gravatar(dirent.commit_info.author_email,
title=h.really_unicode(dirent.commit_info.author), size=16)}}</a>
+          <a href="{{dirent.commit_info.author_url}}">{{h.really_unicode(dirent.commit_info.author)}}</a>
         {% else %}
-          {{lib.email_gravatar(dirent.last_commit.author_email, title=h.really_unicode(dirent.last_commit.author),
size=16)}} {{h.really_unicode(dirent.last_commit.author)}}
+          {{lib.email_gravatar(dirent.commit_info.author_email, title=h.really_unicode(dirent.commit_info.author),
size=16)}} {{h.really_unicode(dirent.commit_info.author)}}
         {% endif %}
       </td>
       <td>
-        <a href="{{dirent.last_commit.href}}">
-          {{dirent.last_commit.shortlink}}
-          {{dirent.last_commit.summary}}
+        <a href="{{dirent.commit_info.href}}">
+          {{dirent.commit_info.shortlink}}
+          {{dirent.commit_info.summary}}
         </a>
       </td>
     </tr>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4e9fe84/Allura/allura/tests/model/test_repo.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index 90eaac1..484b7d1 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -1,5 +1,10 @@
+from datetime import datetime
+import unittest
+import mock
 from nose.tools import assert_equal
 from pylons import c
+from bson import ObjectId
+from ming.orm import session
 
 from alluratest.controller import setup_basic_test, setup_global_objects
 from allura import model as M
@@ -67,3 +72,449 @@ class RepoImplTestBase(object):
         self.assertEqual(run.commit_ids, commit_ids)
         self.assertEqual(len(run.commit_ids), len(run.commit_times))
         self.assertEqual(run.parent_commit_ids, [])
+
+
+class TestLastCommit(unittest.TestCase):
+    def setUp(self):
+        self.repo = mock.Mock('repo', _commits={}, _last_commit=None)
+        self.repo.shorthand_for_commit = lambda _id: _id[:6]
+
+    def _build_tree(self, commit, path, tree_paths):
+        tree_nodes = []
+        blob_nodes = []
+        def n(p):
+            m = mock.Mock()
+            m.name = p
+            return m
+        for p in tree_paths:
+            if '/' in p:
+                tree_nodes.append(n(p.split('/')[0]))
+            else:
+                blob_nodes.append(n(p))
+        tree = mock.Mock(
+                commit=commit,
+                path=mock.Mock(return_value=path),
+                tree_ids=tree_nodes,
+                blob_ids=blob_nodes,
+                other_ids=[],
+            )
+        return tree
+
+    def _add_commit(self, msg, tree_paths, diff_paths=None, parents=[]):
+        suser = dict(
+                name='test',
+                email='test@example.com',
+                date=datetime(2013, 1, 1 + len(self.repo._commits)),
+            )
+        commit = M.repo.Commit(
+                _id=str(ObjectId()),
+                message=msg,
+                parent_ids=[parent._id for parent in parents],
+                commited=suser,
+                authored=suser,
+                repo=self.repo,
+            )
+        commit.tree = self._build_tree(commit, '/', tree_paths)
+        diffinfo = M.repo.DiffInfoDoc(dict(
+                _id=commit._id,
+                differences=[{'name': p} for p in diff_paths or tree_paths],
+            ))
+        diffinfo.m.save()
+        self.repo._commits[commit._id] = commit
+        return commit
+
+    def test_single_commit(self):
+        commit1 = self._add_commit('Commit 1', [
+                'file1',
+                'dir1/file2',
+            ])
+        lcd = M.repo.LastCommit.build(commit1.tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit1.message])
+        self.assertEqual(lcd.path, '')
+        self.assertEqual(lcd.entries, {
+                'file1': dict(
+                    type='BLOB',
+                    name='file1',
+                    commit_info=dict(
+                        summary='Commit 1',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 1),
+                        author_url=None,
+                        id=commit1._id,
+                        shortlink=self.repo.shorthand_for_commit(commit1._id),
+                    )),
+                'dir1': dict(
+                    type='DIR',
+                    name='dir1',
+                    commit_info=dict(
+                        summary='Commit 1',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 1),
+                        author_url=None,
+                        id=commit1._id,
+                        shortlink=self.repo.shorthand_for_commit(commit1._id),
+                    )),
+            })
+
+    def test_multiple_commits_no_overlap(self):
+        commit1 = self._add_commit('Commit 1', ['file1'])
+        commit2 = self._add_commit('Commit 2', ['file1', 'dir1/file1'], ['dir1/file1'], [commit1])
+        commit3 = self._add_commit('Commit 3', ['file1', 'dir1/file1', 'file2'], ['file2'],
[commit2])
+        lcd = M.repo.LastCommit.build(commit3.tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit3.message])
+        self.assertEqual(lcd.commit_ids, [commit3._id])
+        self.assertEqual(lcd.path, '')
+        self.assertEqual(lcd.entries, {
+                'file1': dict(
+                    type='BLOB',
+                    name='file1',
+                    commit_info=dict(
+                        summary='Commit 1',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 1),
+                        author_url=None,
+                        id=commit1._id,
+                        shortlink=self.repo.shorthand_for_commit(commit1._id),
+                    )),
+                'dir1': dict(
+                    type='DIR',
+                    name='dir1',
+                    commit_info=dict(
+                        summary='Commit 2',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 2),
+                        author_url=None,
+                        id=commit2._id,
+                        shortlink=self.repo.shorthand_for_commit(commit2._id),
+                    )),
+                'file2': dict(
+                    type='BLOB',
+                    name='file2',
+                    commit_info=dict(
+                        summary='Commit 3',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 3),
+                        author_url=None,
+                        id=commit3._id,
+                        shortlink=self.repo.shorthand_for_commit(commit3._id),
+                    )),
+            })
+
+    def test_multiple_commits_with_overlap(self):
+        commit1 = self._add_commit('Commit 1', ['file1'])
+        commit2 = self._add_commit('Commit 2', ['file1', 'dir1/file1'], ['dir1/file1'], [commit1])
+        commit3 = self._add_commit('Commit 3', ['file1', 'dir1/file1', 'file2'], ['file1',
'file2'], [commit2])
+        lcd = M.repo.LastCommit.build(commit3.tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit3.message])
+        self.assertEqual(lcd.path, '')
+        self.assertEqual(lcd.entries, {
+                'file1': dict(
+                    type='BLOB',
+                    name='file1',
+                    commit_info=dict(
+                        summary='Commit 3',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 3),
+                        author_url=None,
+                        id=commit3._id,
+                        shortlink=self.repo.shorthand_for_commit(commit3._id),
+                    )),
+                'dir1': dict(
+                    type='DIR',
+                    name='dir1',
+                    commit_info=dict(
+                        summary='Commit 2',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 2),
+                        author_url=None,
+                        id=commit2._id,
+                        shortlink=self.repo.shorthand_for_commit(commit2._id),
+                    )),
+                'file2': dict(
+                    type='BLOB',
+                    name='file2',
+                    commit_info=dict(
+                        summary='Commit 3',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 3),
+                        author_url=None,
+                        id=commit3._id,
+                        shortlink=self.repo.shorthand_for_commit(commit3._id),
+                    )),
+            })
+
+    def test_multiple_commits_subdir_change(self):
+        commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+        commit2 = self._add_commit('Commit 2', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file2'],
[commit1])
+        commit3 = self._add_commit('Commit 3', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file1'],
[commit2])
+        lcd = M.repo.LastCommit.build(commit3.tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit3.message])
+        self.assertEqual(lcd.path, '')
+        self.assertEqual(lcd.entries, {
+                'file1': dict(
+                    type='BLOB',
+                    name='file1',
+                    commit_info=dict(
+                        summary='Commit 1',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 1),
+                        author_url=None,
+                        id=commit1._id,
+                        shortlink=self.repo.shorthand_for_commit(commit1._id),
+                    )),
+                'dir1': dict(
+                    type='DIR',
+                    name='dir1',
+                    commit_info=dict(
+                        summary='Commit 3',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 3),
+                        author_url=None,
+                        id=commit3._id,
+                        shortlink=self.repo.shorthand_for_commit(commit3._id),
+                    )),
+            })
+
+    def test_subdir_lcd(self):
+        commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+        commit2 = self._add_commit('Commit 2', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file2'],
[commit1])
+        commit3 = self._add_commit('Commit 3', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file1'],
[commit2])
+        tree = self._build_tree(commit3, '/dir1', ['file1', 'file2'])
+        lcd = M.repo.LastCommit.build(tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit3.message])
+        self.assertEqual(lcd.path, 'dir1')
+        self.assertEqual(lcd.entries, {
+                'file1': dict(
+                    type='BLOB',
+                    name='file1',
+                    commit_info=dict(
+                        summary='Commit 3',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 3),
+                        author_url=None,
+                        id=commit3._id,
+                        shortlink=self.repo.shorthand_for_commit(commit3._id),
+                    )),
+                'file2': dict(
+                    type='BLOB',
+                    name='file2',
+                    commit_info=dict(
+                        summary='Commit 2',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 2),
+                        author_url=None,
+                        id=commit2._id,
+                        shortlink=self.repo.shorthand_for_commit(commit2._id),
+                    )),
+            })
+
+    def test_subdir_lcd_prev_commit(self):
+        commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+        commit2 = self._add_commit('Commit 2', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file2'],
[commit1])
+        commit3 = self._add_commit('Commit 3', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file1'],
[commit2])
+        commit4 = self._add_commit('Commit 1', ['file1', 'dir1/file1', 'dir1/file2', 'file2'],
['file2'], [commit3])
+        tree = self._build_tree(commit3, '/dir1', ['file1', 'file2'])
+        lcd = M.repo.LastCommit.build(tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit3.message])
+        self.assertEqual(lcd.path, 'dir1')
+        self.assertEqual(lcd.entries, {
+                'file1': dict(
+                    type='BLOB',
+                    name='file1',
+                    commit_info=dict(
+                        summary='Commit 3',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 3),
+                        author_url=None,
+                        id=commit3._id,
+                        shortlink=self.repo.shorthand_for_commit(commit3._id),
+                    )),
+                'file2': dict(
+                    type='BLOB',
+                    name='file2',
+                    commit_info=dict(
+                        summary='Commit 2',
+                        author='test',
+                        author_email='test@example.com',
+                        date=datetime(2013, 1, 2),
+                        author_url=None,
+                        id=commit2._id,
+                        shortlink=self.repo.shorthand_for_commit(commit2._id),
+                    )),
+            })
+
+    def test_subdir_lcd_always_empty(self):
+        commit1 = self._add_commit('Commit 1', ['file1'])
+        commit2 = self._add_commit('Commit 2', ['file1', 'file2'], ['file2'], [commit1])
+        tree = self._build_tree(commit2, '/dir1', [])
+        lcd = M.repo.LastCommit.build(tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit2.message,
commit1.message])
+        self.assertEqual(lcd.path, 'dir1')
+        self.assertEqual(lcd.entries, {})
+
+    def test_subdir_lcd_emptied(self):
+        commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+        commit2 = self._add_commit('Commit 2', ['file1'], ['dir1/file1'], [commit1])
+        tree = self._build_tree(commit2, '/dir1', [])
+        lcd = M.repo.LastCommit.build(tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit2.message])
+        self.assertEqual(lcd.path, 'dir1')
+        self.assertEqual(lcd.entries, {})
+
+    def test_existing_lcd_unchained(self):
+        commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+        commit2 = self._add_commit('Commit 2', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file2'],
[commit1])
+        commit3 = self._add_commit('Commit 3', ['file1', 'dir1/file1', 'dir1/file2'], ['file1'],
[commit2])
+        prev_lcd = M.repo.LastCommit(
+                path='dir1',
+                commit_ids=[commit2._id],
+                entries={
+                    'file1': dict(
+                        type='BLOB',
+                        name='file1',
+                        commit_info=dict(
+                            summary='Commit 1',
+                            author='test',
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 1),
+                            author_url=None,
+                            id=commit1._id,
+                            shortlink=self.repo.shorthand_for_commit(commit1._id),
+                        )),
+                    'file2': dict(
+                        type='BLOB',
+                        name='file2',
+                        commit_info=dict(
+                            summary='Commit 2',
+                            author='test',
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 2),
+                            author_url=None,
+                            id=commit2._id,
+                            shortlink=self.repo.shorthand_for_commit(commit2._id),
+                        )),
+                },
+            )
+        session(prev_lcd).flush()
+        tree = self._build_tree(commit3, '/dir1', ['file1', 'file2'])
+        lcd = M.repo.LastCommit.build(tree)
+        self.assertEqual(lcd._id, prev_lcd._id)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit2.message,
commit3.message])
+        self.assertEqual(lcd.path, 'dir1')
+        self.assertEqual(lcd.entries, prev_lcd.entries)
+
+    def test_existing_lcd_partial(self):
+        commit1 = self._add_commit('Commit 1', ['file1'])
+        commit2 = self._add_commit('Commit 2', ['file1', 'file2'], ['file2'], [commit1])
+        commit3 = self._add_commit('Commit 3', ['file1', 'file2', 'file3'], ['file3'], [commit2])
+        commit4 = self._add_commit('Commit 4', ['file1', 'file2', 'file3', 'file4'], ['file2',
'file4'], [commit3])
+        prev_lcd = M.repo.LastCommit(
+                path='',
+                commit_ids=[commit3._id],
+                entries={
+                    'file1': dict(
+                        type='BLOB',
+                        name='file1',
+                        commit_info=dict(
+                            summary='Existing LCD',    # lying here to test that it uses
this
+                            author='test',             # data instead of walking up the tree
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 1),
+                            author_url=None,
+                            id=commit1._id,
+                            shortlink=self.repo.shorthand_for_commit(commit1._id),
+                        )),
+                    'file2': dict(
+                        type='BLOB',
+                        name='file2',
+                        commit_info=dict(
+                            summary='Commit 2',
+                            author='test',
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 2),
+                            author_url=None,
+                            id=commit2._id,
+                            shortlink=self.repo.shorthand_for_commit(commit2._id),
+                        )),
+                    'file3': dict(
+                        type='BLOB',
+                        name='file3',
+                        commit_info=dict(
+                            summary='Commit 3',
+                            author='test',
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 3),
+                            author_url=None,
+                            id=commit3._id,
+                            shortlink=self.repo.shorthand_for_commit(commit3._id),
+                        )),
+                },
+            )
+        session(prev_lcd).flush()
+        lcd = M.repo.LastCommit.build(commit4.tree)
+        self.assertEqual([self.repo._commits[c].message for c in lcd.commit_ids], [commit4.message])
+        self.assertEqual(lcd.path, '')
+        self.assertEqual(lcd.entries['file1']['commit_info']['summary'], 'Existing LCD')
+        self.assertEqual(lcd.entries, {
+                    'file1': dict(
+                        type='BLOB',
+                        name='file1',
+                        commit_info=dict(
+                            summary='Existing LCD',
+                            author='test',
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 1),
+                            author_url=None,
+                            id=commit1._id,
+                            shortlink=self.repo.shorthand_for_commit(commit1._id),
+                        )),
+                    'file2': dict(
+                        type='BLOB',
+                        name='file2',
+                        commit_info=dict(
+                            summary='Commit 4',
+                            author='test',
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 4),
+                            author_url=None,
+                            id=commit4._id,
+                            shortlink=self.repo.shorthand_for_commit(commit4._id),
+                        )),
+                    'file3': dict(
+                        type='BLOB',
+                        name='file3',
+                        commit_info=dict(
+                            summary='Commit 3',
+                            author='test',
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 3),
+                            author_url=None,
+                            id=commit3._id,
+                            shortlink=self.repo.shorthand_for_commit(commit3._id),
+                        )),
+                    'file4': dict(
+                        type='BLOB',
+                        name='file4',
+                        commit_info=dict(
+                            summary='Commit 4',
+                            author='test',
+                            author_email='test@example.com',
+                            date=datetime(2013, 1, 4),
+                            author_url=None,
+                            id=commit4._id,
+                            shortlink=self.repo.shorthand_for_commit(commit4._id),
+                        )),
+                })

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4e9fe84/ForgeGit/forgegit/model/git_repo.py
----------------------------------------------------------------------
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index 3501e38..222cc88 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -288,6 +288,10 @@ class GitImplementation(M.RepositoryImplementation):
         tree = self.refresh_tree_info(ci.tree, set())
         return tree._id
 
+    def compute_last_commit_info(self, commit, path, other_commit_ids=None):
+        if other_commit_ids is None:
+            other_commit_ids = []
+
 class _OpenedGitBlob(object):
     CHUNK_SIZE=4096
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4e9fe84/ForgeHg/forgehg/model/hg.py
----------------------------------------------------------------------
diff --git a/ForgeHg/forgehg/model/hg.py b/ForgeHg/forgehg/model/hg.py
index 22d9227..bc0330d 100644
--- a/ForgeHg/forgehg/model/hg.py
+++ b/ForgeHg/forgehg/model/hg.py
@@ -291,4 +291,8 @@ class HgImplementation(M.RepositoryImplementation):
         tree = self.refresh_tree_info(fake_tree, set())
         return tree._id
 
+    def compute_last_commit_info(self, commit, path, other_commit_ids=None):
+        if other_commit_ids is None:
+            other_commit_ids = []
+
 Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/e4e9fe84/ForgeSVN/forgesvn/model/svn.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/forgesvn/model/svn.py b/ForgeSVN/forgesvn/model/svn.py
index 48d2c9c..6af7aa1 100644
--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -76,7 +76,7 @@ class Repository(M.Repository):
         while ci is not None and limit > 0:
             yield ci._id
             limit -= 1
-            ci = ci.parent()
+            ci = ci.get_parent()
 
     def latest(self, branch=None):
         if self._impl is None: return None
@@ -383,6 +383,9 @@ class SVNImplementation(M.RepositoryImplementation):
             trees_doc.m.save(safe=False)
         return tree_id
 
+    def compute_last_commit_info(self, commit, path, other_commit_ids=None):
+        pass
+
     def _tree_oid(self, commit_id, path):
         data = 'tree\n%s\n%s' % (commit_id, h.really_unicode(path))
         return sha1(data.encode('utf-8')).hexdigest()


Mime
View raw message