OSDN Git Service

Change Dir Structure to be same as TortoiseSVN'
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / RevisionGraph / FullGraphBuilder.cpp
diff --git a/src/TortoiseProc/RevisionGraph/FullGraphBuilder.cpp b/src/TortoiseProc/RevisionGraph/FullGraphBuilder.cpp
new file mode 100644 (file)
index 0000000..771fcf1
--- /dev/null
@@ -0,0 +1,643 @@
+// TortoiseSVN - a Windows shell extension for easy version control\r
+\r
+// Copyright (C) 2003-2008 - TortoiseSVN\r
+\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License\r
+// as published by the Free Software Foundation; either version 2\r
+// of the License, or (at your option) any later version.\r
+\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software Foundation,\r
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+//\r
+#include "StdAfx.h"\r
+#include "FullGraphBuilder.h"\r
+#include "CachedLogInfo.h"\r
+#include "RevisionIndex.h"\r
+#include "SearchPathTree.h"\r
+#include "FullHistory.h"\r
+#include "FullGraph.h"\r
+\r
+#ifdef _DEBUG\r
+#define new DEBUG_NEW\r
+#undef THIS_FILE\r
+static char THIS_FILE[] = __FILE__;\r
+#endif\r
+\r
+CFullGraphBuilder::CFullGraphBuilder (const CFullHistory& history, CFullGraph& graph)\r
+    : history (history)\r
+    , graph (graph)\r
+{\r
+}\r
+\r
+CFullGraphBuilder::~CFullGraphBuilder(void)\r
+{\r
+}\r
+\r
+void CFullGraphBuilder::Run()\r
+{\r
+    // special case: empty log\r
+\r
+    if (history.GetHeadRevision() == NO_REVISION)\r
+        return;\r
+\r
+    // frequently used objects\r
+\r
+       const CCachedLogInfo* cache = history.GetCache();\r
+       const CRevisionIndex& revisions = cache->GetRevisions();\r
+       const CRevisionInfoContainer& revisionInfo = cache->GetLogInfo();\r
+\r
+       // initialize the paths we have to search for\r
+\r
+       std::auto_ptr<CSearchPathTree> searchTree \r
+               (new CSearchPathTree (&revisionInfo.GetPaths()));\r
+    searchTree->Insert (*history.GetStartPath(), history.GetStartRevision());\r
+\r
+       // the range of copy-to info that applies to the current revision\r
+\r
+    SCopyInfo** lastFromCopy = history.GetFirstCopyFrom();\r
+    SCopyInfo** lastToCopy = history.GetFirstCopyTo();\r
+\r
+       // collect nodes to draw ... revision by revision\r
+\r
+    for ( revision_t revision = history.GetStartRevision()\r
+        , head = history.GetHeadRevision()\r
+        ; revision <= head\r
+        ; ++revision)\r
+       {\r
+        // any known changes in this revision?\r
+\r
+               index_t index = revisions[revision];\r
+               if (index == NO_INDEX)\r
+                       continue;\r
+\r
+               CDictionaryBasedPath basePath = revisionInfo.GetRootPath (index);\r
+               if (!basePath.IsValid())\r
+            continue;\r
+\r
+           // collect search paths that have been deleted in this container\r
+           // (delay potential node deletion until we finished tree traversal)\r
+\r
+           std::vector<CSearchPathTree*> toRemove;\r
+\r
+        // special handling for replacements: \r
+        // we must delete the old branches first and *then* add (some of) these\r
+        // again in the same revision\r
+\r
+        if ((   revisionInfo.GetSumChanges (index) \r
+              & CRevisionInfoContainer::ACTION_REPLACED) != 0)\r
+        {\r
+                   CSearchPathTree* startNode \r
+                           = searchTree->FindCommonParent (basePath.GetIndex());\r
+\r
+            // crawl (possibly) affected sub-tree\r
+\r
+                       AnalyzeReplacements ( revision\r
+                                                       , revisionInfo.GetChangesBegin (index)\r
+                                                       , revisionInfo.GetChangesEnd (index)\r
+                                                       , startNode\r
+                                                       , toRemove);\r
+\r
+                   // remove deleted search paths\r
+\r
+                   for (size_t i = 0, count = toRemove.size(); i < count; ++i)\r
+                           toRemove[i]->Remove();\r
+\r
+            toRemove.clear();\r
+        }\r
+\r
+               // handle remaining copy-to entries\r
+               // (some may have a fromRevision that does not touch the fromPath)\r
+\r
+               AddCopiedPaths ( revision\r
+                                          , searchTree.get()\r
+                                          , lastToCopy);\r
+\r
+               // we are looking for search paths that (may) overlap \r
+               // with the revisions' changes\r
+\r
+           // pre-order search-tree traversal\r
+\r
+               CSearchPathTree* startNode \r
+                       = searchTree->FindCommonParent (basePath.GetIndex());\r
+\r
+               if (startNode->GetPath().IsSameOrChildOf (basePath))\r
+               {\r
+                       CSearchPathTree* searchNode = startNode;\r
+\r
+                       AnalyzeRevisions ( revision\r
+                                                    , revisionInfo.GetChangesBegin (index)\r
+                                                    , revisionInfo.GetChangesEnd (index)\r
+                                                    , searchNode\r
+                                                    , toRemove);\r
+\r
+            startNode = startNode->GetParent();\r
+               }\r
+               else\r
+               {\r
+                       CDictionaryBasedPath commonRoot\r
+                               = basePath.GetCommonRoot (startNode->GetPath().GetBasePath());\r
+                       startNode = searchTree->FindCommonParent (commonRoot.GetIndex());\r
+               }\r
+\r
+    #ifdef _DEBUG\r
+        if (startNode != NULL)\r
+        {\r
+            // only valid for parents of the uppermost modified path\r
+\r
+               for (CRevisionInfoContainer::CChangesIterator iter = revisionInfo.GetChangesBegin (index)\r
+                , last = revisionInfo.GetChangesEnd (index)\r
+                ; iter != last\r
+                ; ++iter)\r
+            {\r
+                assert (startNode->GetPath().IsSameOrParentOf (iter->GetPathID()));\r
+            }\r
+        }\r
+    #endif\r
+\r
+               // mark changes on parent search nodes\r
+\r
+        assert (revisionInfo.GetChangesBegin (index) != revisionInfo.GetChangesEnd (index));\r
+\r
+               for ( CSearchPathTree* searchNode = startNode\r
+                       ; searchNode != NULL\r
+                       ; searchNode = searchNode->GetParent())\r
+           {\r
+                       if (searchNode->IsActive())\r
+                               AnalyzeAsChanges (revision, searchNode);\r
+           }\r
+\r
+               // remove deleted search paths\r
+\r
+               for (size_t i = 0, count = toRemove.size(); i < count; ++i)\r
+                       toRemove[i]->Remove();\r
+\r
+               // handle remaining copy-to entries\r
+               // (some may have a fromRevision that does not touch the fromPath)\r
+\r
+               FillCopyTargets ( revision\r
+                                               , searchTree.get()\r
+                                               , lastFromCopy);\r
+       }\r
+}\r
+\r
+void CFullGraphBuilder::AnalyzeReplacements ( revision_t revision\r
+                                                                                 , CRevisionInfoContainer::CChangesIterator first\r
+                                                                                 , CRevisionInfoContainer::CChangesIterator last\r
+                                                                                 , CSearchPathTree* startNode\r
+                                                                                 , std::vector<CSearchPathTree*>& toRemove)\r
+{\r
+       typedef CRevisionInfoContainer::CChangesIterator IT;\r
+\r
+       CSearchPathTree* searchNode = startNode;\r
+       do\r
+       {\r
+               // in many cases, we want only to see additions, \r
+               // deletions and replacements\r
+\r
+               bool skipSubTree = true;\r
+\r
+               const CDictionaryBasedTempPath& path = searchNode->GetPath();\r
+\r
+               // we must not modify inactive nodes\r
+\r
+               if (searchNode->IsActive())\r
+               {\r
+                       // looking for the closet change that affected the path\r
+\r
+                       for (IT iter = first; iter != last; ++iter)\r
+                       {\r
+                               index_t changePathID = iter->GetPathID();\r
+\r
+                if (   (iter->GetAction() == CRevisionInfoContainer::ACTION_REPLACED)\r
+                                   && (path.GetBasePath().IsSameOrChildOf (changePathID)))\r
+                               {\r
+                                       skipSubTree = false;\r
+\r
+                                       // create & init the new graph node\r
+\r
+                    CFullGraphNode* newNode \r
+                        = graph.Add ( path\r
+                                    , revision\r
+                                    , CNodeClassification::IS_DELETED\r
+                                    , searchNode->GetLastEntry());\r
+\r
+                                       // link entries for the same search path\r
+\r
+                                       searchNode->ChainEntries (newNode);\r
+\r
+                    // end of path\r
+\r
+                                       toRemove.push_back (searchNode);\r
+\r
+                                       // we will create at most one node per path and revision\r
+\r
+                                       break;\r
+                               }\r
+                       }\r
+               }\r
+               else\r
+               {\r
+                       // can we skip the whole sub-tree?\r
+\r
+                       for (IT iter = first; iter != last; ++iter)\r
+                       {\r
+                               if (path.IsSameOrParentOf (iter->GetPathID()))\r
+                               {\r
+                                       skipSubTree = false;\r
+                                       break;\r
+                               }\r
+                       }\r
+               }\r
+\r
+               // to the next node\r
+\r
+               searchNode = skipSubTree\r
+                                  ? searchNode->GetSkipSubTreeNext (startNode)\r
+                                  : searchNode->GetPreOrderNext (startNode);\r
+       }\r
+       while (searchNode != startNode);\r
+\r
+}\r
+\r
+void CFullGraphBuilder::AnalyzeRevisions ( revision_t revision\r
+                                                                         , CRevisionInfoContainer::CChangesIterator first\r
+                                                                         , CRevisionInfoContainer::CChangesIterator last\r
+                                                                         , CSearchPathTree* startNode\r
+                                                                         , std::vector<CSearchPathTree*>& toRemove)\r
+{\r
+       typedef CRevisionInfoContainer::CChangesIterator IT;\r
+\r
+       CSearchPathTree* searchNode = startNode;\r
+       do\r
+       {\r
+               // in many cases, we want only to see additions, \r
+               // deletions and replacements\r
+\r
+               bool skipSubTree = true;\r
+\r
+               const CDictionaryBasedTempPath& path = searchNode->GetPath();\r
+\r
+               // we must not modify inactive nodes\r
+\r
+               if (searchNode->IsActive())\r
+               {\r
+                       // looking for the closet change that affected the path\r
+\r
+                       for (IT iter = first; iter != last; ++iter)\r
+                       {\r
+                               index_t changePathID = iter->GetPathID();\r
+\r
+                               if (   (path.IsSameOrParentOf (changePathID))\r
+                                       || (  (iter->GetAction() != CRevisionInfoContainer::ACTION_CHANGED)\r
+                                          && path.GetBasePath().IsSameOrChildOf (changePathID)))\r
+                               {\r
+                                       skipSubTree = false;\r
+\r
+                                       CDictionaryBasedPath changePath = iter->GetPath();\r
+\r
+                                       // construct the classification member\r
+\r
+                                   // show modifications within the sub-tree as "modified"\r
+                                   // (otherwise, deletions would terminate the path)\r
+\r
+                    CNodeClassification classification \r
+                        = CNodeClassification::IS_MODIFIED;\r
+                                       if (path.GetBasePath().GetIndex() >= changePath.GetIndex())\r
+                    {\r
+                        switch (iter->GetRawChange())\r
+                        {\r
+                        case CRevisionInfoContainer::ACTION_CHANGED: \r
+                            break;\r
+\r
+                        case CRevisionInfoContainer::ACTION_DELETED: \r
+                            classification = CNodeClassification::IS_DELETED;\r
+                            break;\r
+\r
+                        case  CRevisionInfoContainer::ACTION_ADDED\r
+                            + CRevisionInfoContainer::HAS_COPY_FROM: \r
+                        case  CRevisionInfoContainer::ACTION_REPLACED \r
+                            + CRevisionInfoContainer::HAS_COPY_FROM: \r
+                            classification = CNodeClassification::IS_ADDED \r
+                                           + CNodeClassification::IS_COPY_TARGET;\r
+                            break;\r
+\r
+                        case CRevisionInfoContainer::ACTION_ADDED: \r
+                        case CRevisionInfoContainer::ACTION_REPLACED: \r
+                            classification = CNodeClassification::IS_ADDED;\r
+                            break;\r
+                        }\r
+                    }\r
+\r
+                    // handle copy-from special case:\r
+\r
+                    if (   (classification.Is (CNodeClassification::IS_ADDED))\r
+                                               && (searchNode->GetLastEntry() != NULL)\r
+                        && !iter->HasFromPath())\r
+                                       {\r
+                                               // we may not add paths that already exist:\r
+                                               // D /trunk/OldSub\r
+                                               // A /trunk/New\r
+                                               // A /trunk/New/OldSub  /trunk/OldSub@r-1\r
+                        // don't add /trunk/New again\r
+\r
+                                               continue;\r
+                                       }\r
+\r
+                                       // create & init the new graph node\r
+\r
+                    CFullGraphNode* newNode \r
+                        = graph.Add (path, revision, classification, searchNode->GetLastEntry());\r
+\r
+                                       // link entries for the same search path\r
+\r
+                                       searchNode->ChainEntries (newNode);\r
+\r
+                                       // end of path?\r
+\r
+                                       if (classification.Is (CNodeClassification::IS_DELETED))\r
+                                               toRemove.push_back (searchNode);\r
+\r
+                                       // we will create at most one node per path and revision\r
+\r
+                                       break;\r
+                               }\r
+                       }\r
+               }\r
+               else\r
+               {\r
+                       // can we skip the whole sub-tree?\r
+\r
+                       for (IT iter = first; iter != last; ++iter)\r
+                       {\r
+                               index_t changePathID = iter->GetPathID();\r
+\r
+                               if (   path.IsSameOrParentOf (changePathID)\r
+                                       || (  (iter->GetAction() != CRevisionInfoContainer::ACTION_CHANGED)\r
+                                          && path.GetBasePath().IsSameOrChildOf (changePathID)))\r
+                               {\r
+                                       skipSubTree = false;\r
+                                       break;\r
+                               }\r
+                       }\r
+               }\r
+\r
+               // to the next node\r
+\r
+               searchNode = skipSubTree\r
+                                  ? searchNode->GetSkipSubTreeNext (startNode)\r
+                                  : searchNode->GetPreOrderNext (startNode);\r
+       }\r
+       while (searchNode != startNode);\r
+\r
+}\r
+\r
+void CFullGraphBuilder::AnalyzeAsChanges ( revision_t revision\r
+                                                                        , CSearchPathTree* searchNode)\r
+{\r
+       // create & init the new graph node\r
+\r
+    CFullGraphNode* newNode = graph.Add ( searchNode->GetPath()\r
+                                        , revision\r
+                                        , CNodeClassification::IS_MODIFIED\r
+                                        , searchNode->GetLastEntry());\r
+\r
+       // link entries for the same search path\r
+\r
+       searchNode->ChainEntries (newNode);\r
+}\r
+\r
+void CFullGraphBuilder::AddCopiedPaths ( revision_t revision\r
+                                                                        , CSearchPathTree* rootNode\r
+                                                                        , SCopyInfo**& lastToCopy)\r
+{\r
+    // find range of copies that point to this revision\r
+\r
+       SCopyInfo** firstToCopy = lastToCopy;\r
+    history.GetCopyToRange (firstToCopy, lastToCopy, revision);\r
+\r
+       // create search paths for all *relevant* paths added in this revision\r
+\r
+       for (SCopyInfo** iter = firstToCopy; iter != lastToCopy; ++iter)\r
+       {\r
+               const std::vector<SCopyInfo::STarget>& targets = (*iter)->targets;\r
+               for (size_t i = 0, count = targets.size(); i < count; ++i)\r
+               {\r
+                       const SCopyInfo::STarget& target = targets[i];\r
+                       CSearchPathTree* node = rootNode->Insert (target.path, revision);\r
+                       node->ChainEntries (target.source);\r
+               }\r
+       }\r
+}\r
+\r
+void CFullGraphBuilder::FillCopyTargets ( revision_t revision\r
+                                                                         , CSearchPathTree* rootNode\r
+                                                                         , SCopyInfo**& lastFromCopy)\r
+{\r
+    // find range of copies that start from this revision\r
+\r
+    SCopyInfo** firstFromCopy = lastFromCopy;\r
+    history.GetCopyFromRange (firstFromCopy, lastFromCopy, revision);\r
+\r
+       // create search paths for all *relevant* paths added in this revision\r
+\r
+       for (SCopyInfo** iter = firstFromCopy; iter != lastFromCopy; ++iter)\r
+       {\r
+               SCopyInfo* copy = *iter;\r
+               std::vector<SCopyInfo::STarget>& targets = copy->targets;\r
+\r
+               // crawl the whole sub-tree for path matches\r
+\r
+               CSearchPathTree* startNode \r
+                       = rootNode->FindCommonParent (copy->fromPathIndex);\r
+               if (!startNode->GetPath().IsSameOrChildOf (copy->fromPathIndex))\r
+                       continue;\r
+\r
+               CSearchPathTree* searchNode = startNode;\r
+               do\r
+               {\r
+                       const CDictionaryBasedTempPath& path = searchNode->GetPath();\r
+            assert (path.IsSameOrChildOf (copy->fromPathIndex));\r
+\r
+                       // got this path copied?\r
+\r
+                       if (searchNode->IsActive())\r
+                       {\r
+                               CDictionaryBasedPath fromPath ( path.GetDictionary()\r
+                                                                                         , copy->fromPathIndex);\r
+\r
+                // is there a better match in that target revision?\r
+                // example log @r106:\r
+                // A /trunk/F    /trunk/branches/b/F    100\r
+                // R /trunk/F/a  /trunk/branches/b/F/a  105\r
+                // -> don't copy from r100 but from r105\r
+\r
+                if (IsLatestCopySource ( revision\r
+                                       , copy->toRevision\r
+                                       , fromPath\r
+                                       , path))\r
+                {\r
+                    // check for another special case:\r
+                    // A /branches/b    /trunk 100\r
+                    // D /branches/b/a\r
+                    // -> don't add a path if we are following /trunk/a\r
+\r
+                               CDictionaryBasedTempPath targetPath\r
+                                           = path.ReplaceParent ( fromPath\r
+                                                                                , CDictionaryBasedPath ( path.GetDictionary()\r
+                                                                                                                               , copy->toPathIndex));\r
+\r
+                    if (TargetPathExists (copy->toRevision, targetPath.GetBasePath()))\r
+                    {\r
+                        // o.k. this is actual a copy we have to add to the tree\r
+\r
+                        CFullGraphNode* entry = searchNode->GetLastEntry();\r
+                        if ((entry == NULL) || (entry->GetRevision() < revision))\r
+                                       {\r
+                                               // the copy source graph node has yet to be created\r
+\r
+                            entry = graph.Add ( path\r
+                                              , revision\r
+                                              , CNodeClassification::IS_COPY_SOURCE\r
+                                              , entry);\r
+\r
+                                               // link entries for the same search path\r
+\r
+                                               searchNode->ChainEntries (entry);\r
+                                       }\r
+\r
+                        // add & schedule the new search path\r
+\r
+                                       SCopyInfo::STarget target (entry, targetPath);\r
+                                       targets.push_back (target);\r
+                    }\r
+                           }\r
+            }\r
+\r
+                       // select next node\r
+\r
+                       searchNode = searchNode->GetPreOrderNext (startNode);\r
+               }\r
+               while (searchNode != startNode);\r
+       }\r
+}\r
+\r
+bool CFullGraphBuilder::IsLatestCopySource ( revision_t fromRevision\r
+                                             , revision_t toRevision\r
+                                             , const CDictionaryBasedPath& fromPath\r
+                                             , const CDictionaryBasedTempPath& currentPath)\r
+{\r
+    // try to find a "later" / "closer" copy source\r
+\r
+    // example log @r106 (toRevision):\r
+    // A /trunk/F    /trunk/branches/b/F    100\r
+    // R /trunk/F/a  /trunk/branches/b/F/a  105\r
+    // -> return r105\r
+\r
+    const CCachedLogInfo* cache = history.GetCache();\r
+    const CRevisionInfoContainer& logInfo = cache->GetLogInfo();\r
+    index_t index = cache->GetRevisions()[toRevision];\r
+\r
+    // search it\r
+\r
+    for ( CRevisionInfoContainer::CChangesIterator \r
+          iter = logInfo.GetChangesBegin (index)\r
+        , end = logInfo.GetChangesEnd (index)\r
+        ; iter != end\r
+        ; ++iter)\r
+    {\r
+        // is this a copy of the current path?\r
+\r
+        if (   iter->HasFromPath() \r
+            && currentPath.IsSameOrChildOf (iter->GetFromPathID()))\r
+        {\r
+            // a later change?\r
+\r
+            if (iter->GetFromRevision() > fromRevision)\r
+                return false;\r
+\r
+            // a closer sub-path?\r
+\r
+            if (iter->GetFromPathID() > fromPath.GetIndex())\r
+                return false;\r
+        }\r
+    }\r
+\r
+    // (fromRevision, fromGraph) is the best match\r
+\r
+    return true;\r
+}\r
+\r
+bool CFullGraphBuilder::TargetPathExists ( revision_t revision\r
+                                         , const CDictionaryBasedPath& path)\r
+{\r
+    // follow additions and deletions to determine whether the path exists\r
+    // after the given revision\r
+\r
+    // A /branches/b    /trunk 100\r
+    // D /branches/b/a\r
+    // -> /branches/b/a does not exist\r
+\r
+    const CCachedLogInfo* cache = history.GetCache();\r
+    const CRevisionInfoContainer& logInfo = cache->GetLogInfo();\r
+    index_t index = cache->GetRevisions()[revision];\r
+\r
+    // short-cut: if there are no deletions, we should be fine\r
+\r
+    if (!(logInfo.GetSumChanges (index) & CRevisionInfoContainer::ACTION_DELETED))\r
+        return true;\r
+\r
+    // crawl changes and update this flag:\r
+\r
+    bool exists = false;\r
+    for ( CRevisionInfoContainer::CChangesIterator \r
+          iter = logInfo.GetChangesBegin (index)\r
+        , end = logInfo.GetChangesEnd (index)\r
+        ; iter != end\r
+        ; ++iter)\r
+    {\r
+        // does this change affect the path?\r
+\r
+        if (path.IsSameOrChildOf (iter->GetPathID()))\r
+        {\r
+            switch (iter->GetRawChange())\r
+            {\r
+            case CRevisionInfoContainer::ACTION_DELETED :\r
+                // deletion? -> does not exist\r
+\r
+                exists = false;\r
+                break;\r
+\r
+            case CRevisionInfoContainer::ACTION_ADDED\r
+               + CRevisionInfoContainer::HAS_COPY_FROM:\r
+            case CRevisionInfoContainer::ACTION_REPLACED\r
+               + CRevisionInfoContainer::HAS_COPY_FROM:\r
+                // copy? -> does exist\r
+\r
+                // We can safely assume here, that the source tree\r
+                // contains the path in question as this is why we\r
+                // called this function at all.\r
+\r
+                exists = true;\r
+                break;\r
+\r
+            case CRevisionInfoContainer::ACTION_ADDED:\r
+                // exact addition? -> does exist\r
+\r
+                if (iter->GetPathID() == path.GetIndex())\r
+                    exists = true;\r
+\r
+                break;\r
+            }\r
+        }\r
+    }\r
+\r
+    return exists;\r
+}\r
+\r