OSDN Git Service

Show Ignore Sub Menu
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / RevisionGraph / FullGraphBuilder.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2008 - TortoiseSVN\r
4 \r
5 // This program is free software; you can redistribute it and/or\r
6 // modify it under the terms of the GNU General Public License\r
7 // as published by the Free Software Foundation; either version 2\r
8 // of the License, or (at your option) any later version.\r
9 \r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 \r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, write to the Free Software Foundation,\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
18 //\r
19 #include "StdAfx.h"\r
20 #include "FullGraphBuilder.h"\r
21 #include "CachedLogInfo.h"\r
22 #include "RevisionIndex.h"\r
23 #include "SearchPathTree.h"\r
24 #include "FullHistory.h"\r
25 #include "FullGraph.h"\r
26 \r
27 #ifdef _DEBUG\r
28 #define new DEBUG_NEW\r
29 #undef THIS_FILE\r
30 static char THIS_FILE[] = __FILE__;\r
31 #endif\r
32 \r
33 CFullGraphBuilder::CFullGraphBuilder (const CFullHistory& history, CFullGraph& graph)\r
34     : history (history)\r
35     , graph (graph)\r
36 {\r
37 }\r
38 \r
39 CFullGraphBuilder::~CFullGraphBuilder(void)\r
40 {\r
41 }\r
42 \r
43 void CFullGraphBuilder::Run()\r
44 {\r
45     // special case: empty log\r
46 \r
47     if (history.GetHeadRevision() == NO_REVISION)\r
48         return;\r
49 \r
50     // frequently used objects\r
51 \r
52         const CCachedLogInfo* cache = history.GetCache();\r
53         const CRevisionIndex& revisions = cache->GetRevisions();\r
54         const CRevisionInfoContainer& revisionInfo = cache->GetLogInfo();\r
55 \r
56         // initialize the paths we have to search for\r
57 \r
58         std::auto_ptr<CSearchPathTree> searchTree \r
59                 (new CSearchPathTree (&revisionInfo.GetPaths()));\r
60     searchTree->Insert (*history.GetStartPath(), history.GetStartRevision());\r
61 \r
62         // the range of copy-to info that applies to the current revision\r
63 \r
64     SCopyInfo** lastFromCopy = history.GetFirstCopyFrom();\r
65     SCopyInfo** lastToCopy = history.GetFirstCopyTo();\r
66 \r
67         // collect nodes to draw ... revision by revision\r
68 \r
69     for ( revision_t revision = history.GetStartRevision()\r
70         , head = history.GetHeadRevision()\r
71         ; revision <= head\r
72         ; ++revision)\r
73         {\r
74         // any known changes in this revision?\r
75 \r
76                 index_t index = revisions[revision];\r
77                 if (index == NO_INDEX)\r
78                         continue;\r
79 \r
80                 CDictionaryBasedPath basePath = revisionInfo.GetRootPath (index);\r
81                 if (!basePath.IsValid())\r
82             continue;\r
83 \r
84             // collect search paths that have been deleted in this container\r
85             // (delay potential node deletion until we finished tree traversal)\r
86 \r
87             std::vector<CSearchPathTree*> toRemove;\r
88 \r
89         // special handling for replacements: \r
90         // we must delete the old branches first and *then* add (some of) these\r
91         // again in the same revision\r
92 \r
93         if ((   revisionInfo.GetSumChanges (index) \r
94               & CRevisionInfoContainer::ACTION_REPLACED) != 0)\r
95         {\r
96                     CSearchPathTree* startNode \r
97                             = searchTree->FindCommonParent (basePath.GetIndex());\r
98 \r
99             // crawl (possibly) affected sub-tree\r
100 \r
101                         AnalyzeReplacements ( revision\r
102                                                         , revisionInfo.GetChangesBegin (index)\r
103                                                         , revisionInfo.GetChangesEnd (index)\r
104                                                         , startNode\r
105                                                         , toRemove);\r
106 \r
107                     // remove deleted search paths\r
108 \r
109                     for (size_t i = 0, count = toRemove.size(); i < count; ++i)\r
110                             toRemove[i]->Remove();\r
111 \r
112             toRemove.clear();\r
113         }\r
114 \r
115                 // handle remaining copy-to entries\r
116                 // (some may have a fromRevision that does not touch the fromPath)\r
117 \r
118                 AddCopiedPaths ( revision\r
119                                            , searchTree.get()\r
120                                            , lastToCopy);\r
121 \r
122                 // we are looking for search paths that (may) overlap \r
123                 // with the revisions' changes\r
124 \r
125             // pre-order search-tree traversal\r
126 \r
127                 CSearchPathTree* startNode \r
128                         = searchTree->FindCommonParent (basePath.GetIndex());\r
129 \r
130                 if (startNode->GetPath().IsSameOrChildOf (basePath))\r
131                 {\r
132                         CSearchPathTree* searchNode = startNode;\r
133 \r
134                         AnalyzeRevisions ( revision\r
135                                                      , revisionInfo.GetChangesBegin (index)\r
136                                                      , revisionInfo.GetChangesEnd (index)\r
137                                                      , searchNode\r
138                                                      , toRemove);\r
139 \r
140             startNode = startNode->GetParent();\r
141                 }\r
142                 else\r
143                 {\r
144                         CDictionaryBasedPath commonRoot\r
145                                 = basePath.GetCommonRoot (startNode->GetPath().GetBasePath());\r
146                         startNode = searchTree->FindCommonParent (commonRoot.GetIndex());\r
147                 }\r
148 \r
149     #ifdef _DEBUG\r
150         if (startNode != NULL)\r
151         {\r
152             // only valid for parents of the uppermost modified path\r
153 \r
154                 for (CRevisionInfoContainer::CChangesIterator iter = revisionInfo.GetChangesBegin (index)\r
155                 , last = revisionInfo.GetChangesEnd (index)\r
156                 ; iter != last\r
157                 ; ++iter)\r
158             {\r
159                 assert (startNode->GetPath().IsSameOrParentOf (iter->GetPathID()));\r
160             }\r
161         }\r
162     #endif\r
163 \r
164                 // mark changes on parent search nodes\r
165 \r
166         assert (revisionInfo.GetChangesBegin (index) != revisionInfo.GetChangesEnd (index));\r
167 \r
168                 for ( CSearchPathTree* searchNode = startNode\r
169                         ; searchNode != NULL\r
170                         ; searchNode = searchNode->GetParent())\r
171             {\r
172                         if (searchNode->IsActive())\r
173                                 AnalyzeAsChanges (revision, searchNode);\r
174             }\r
175 \r
176                 // remove deleted search paths\r
177 \r
178                 for (size_t i = 0, count = toRemove.size(); i < count; ++i)\r
179                         toRemove[i]->Remove();\r
180 \r
181                 // handle remaining copy-to entries\r
182                 // (some may have a fromRevision that does not touch the fromPath)\r
183 \r
184                 FillCopyTargets ( revision\r
185                                                 , searchTree.get()\r
186                                                 , lastFromCopy);\r
187         }\r
188 }\r
189 \r
190 void CFullGraphBuilder::AnalyzeReplacements ( revision_t revision\r
191                                                                                   , CRevisionInfoContainer::CChangesIterator first\r
192                                                                                   , CRevisionInfoContainer::CChangesIterator last\r
193                                                                                   , CSearchPathTree* startNode\r
194                                                                                   , std::vector<CSearchPathTree*>& toRemove)\r
195 {\r
196         typedef CRevisionInfoContainer::CChangesIterator IT;\r
197 \r
198         CSearchPathTree* searchNode = startNode;\r
199         do\r
200         {\r
201                 // in many cases, we want only to see additions, \r
202                 // deletions and replacements\r
203 \r
204                 bool skipSubTree = true;\r
205 \r
206                 const CDictionaryBasedTempPath& path = searchNode->GetPath();\r
207 \r
208                 // we must not modify inactive nodes\r
209 \r
210                 if (searchNode->IsActive())\r
211                 {\r
212                         // looking for the closet change that affected the path\r
213 \r
214                         for (IT iter = first; iter != last; ++iter)\r
215                         {\r
216                                 index_t changePathID = iter->GetPathID();\r
217 \r
218                 if (   (iter->GetAction() == CRevisionInfoContainer::ACTION_REPLACED)\r
219                                     && (path.GetBasePath().IsSameOrChildOf (changePathID)))\r
220                                 {\r
221                                         skipSubTree = false;\r
222 \r
223                                         // create & init the new graph node\r
224 \r
225                     CFullGraphNode* newNode \r
226                         = graph.Add ( path\r
227                                     , revision\r
228                                     , CNodeClassification::IS_DELETED\r
229                                     , searchNode->GetLastEntry());\r
230 \r
231                                         // link entries for the same search path\r
232 \r
233                                         searchNode->ChainEntries (newNode);\r
234 \r
235                     // end of path\r
236 \r
237                                         toRemove.push_back (searchNode);\r
238 \r
239                                         // we will create at most one node per path and revision\r
240 \r
241                                         break;\r
242                                 }\r
243                         }\r
244                 }\r
245                 else\r
246                 {\r
247                         // can we skip the whole sub-tree?\r
248 \r
249                         for (IT iter = first; iter != last; ++iter)\r
250                         {\r
251                                 if (path.IsSameOrParentOf (iter->GetPathID()))\r
252                                 {\r
253                                         skipSubTree = false;\r
254                                         break;\r
255                                 }\r
256                         }\r
257                 }\r
258 \r
259                 // to the next node\r
260 \r
261                 searchNode = skipSubTree\r
262                                    ? searchNode->GetSkipSubTreeNext (startNode)\r
263                                    : searchNode->GetPreOrderNext (startNode);\r
264         }\r
265         while (searchNode != startNode);\r
266 \r
267 }\r
268 \r
269 void CFullGraphBuilder::AnalyzeRevisions ( revision_t revision\r
270                                                                           , CRevisionInfoContainer::CChangesIterator first\r
271                                                                           , CRevisionInfoContainer::CChangesIterator last\r
272                                                                           , CSearchPathTree* startNode\r
273                                                                           , std::vector<CSearchPathTree*>& toRemove)\r
274 {\r
275         typedef CRevisionInfoContainer::CChangesIterator IT;\r
276 \r
277         CSearchPathTree* searchNode = startNode;\r
278         do\r
279         {\r
280                 // in many cases, we want only to see additions, \r
281                 // deletions and replacements\r
282 \r
283                 bool skipSubTree = true;\r
284 \r
285                 const CDictionaryBasedTempPath& path = searchNode->GetPath();\r
286 \r
287                 // we must not modify inactive nodes\r
288 \r
289                 if (searchNode->IsActive())\r
290                 {\r
291                         // looking for the closet change that affected the path\r
292 \r
293                         for (IT iter = first; iter != last; ++iter)\r
294                         {\r
295                                 index_t changePathID = iter->GetPathID();\r
296 \r
297                                 if (   (path.IsSameOrParentOf (changePathID))\r
298                                         || (  (iter->GetAction() != CRevisionInfoContainer::ACTION_CHANGED)\r
299                                            && path.GetBasePath().IsSameOrChildOf (changePathID)))\r
300                                 {\r
301                                         skipSubTree = false;\r
302 \r
303                                         CDictionaryBasedPath changePath = iter->GetPath();\r
304 \r
305                                         // construct the classification member\r
306 \r
307                                     // show modifications within the sub-tree as "modified"\r
308                                     // (otherwise, deletions would terminate the path)\r
309 \r
310                     CNodeClassification classification \r
311                         = CNodeClassification::IS_MODIFIED;\r
312                                         if (path.GetBasePath().GetIndex() >= changePath.GetIndex())\r
313                     {\r
314                         switch (iter->GetRawChange())\r
315                         {\r
316                         case CRevisionInfoContainer::ACTION_CHANGED: \r
317                             break;\r
318 \r
319                         case CRevisionInfoContainer::ACTION_DELETED: \r
320                             classification = CNodeClassification::IS_DELETED;\r
321                             break;\r
322 \r
323                         case  CRevisionInfoContainer::ACTION_ADDED\r
324                             + CRevisionInfoContainer::HAS_COPY_FROM: \r
325                         case  CRevisionInfoContainer::ACTION_REPLACED \r
326                             + CRevisionInfoContainer::HAS_COPY_FROM: \r
327                             classification = CNodeClassification::IS_ADDED \r
328                                            + CNodeClassification::IS_COPY_TARGET;\r
329                             break;\r
330 \r
331                         case CRevisionInfoContainer::ACTION_ADDED: \r
332                         case CRevisionInfoContainer::ACTION_REPLACED: \r
333                             classification = CNodeClassification::IS_ADDED;\r
334                             break;\r
335                         }\r
336                     }\r
337 \r
338                     // handle copy-from special case:\r
339 \r
340                     if (   (classification.Is (CNodeClassification::IS_ADDED))\r
341                                                 && (searchNode->GetLastEntry() != NULL)\r
342                         && !iter->HasFromPath())\r
343                                         {\r
344                                                 // we may not add paths that already exist:\r
345                                                 // D /trunk/OldSub\r
346                                                 // A /trunk/New\r
347                                                 // A /trunk/New/OldSub  /trunk/OldSub@r-1\r
348                         // don't add /trunk/New again\r
349 \r
350                                                 continue;\r
351                                         }\r
352 \r
353                                         // create & init the new graph node\r
354 \r
355                     CFullGraphNode* newNode \r
356                         = graph.Add (path, revision, classification, searchNode->GetLastEntry());\r
357 \r
358                                         // link entries for the same search path\r
359 \r
360                                         searchNode->ChainEntries (newNode);\r
361 \r
362                                         // end of path?\r
363 \r
364                                         if (classification.Is (CNodeClassification::IS_DELETED))\r
365                                                 toRemove.push_back (searchNode);\r
366 \r
367                                         // we will create at most one node per path and revision\r
368 \r
369                                         break;\r
370                                 }\r
371                         }\r
372                 }\r
373                 else\r
374                 {\r
375                         // can we skip the whole sub-tree?\r
376 \r
377                         for (IT iter = first; iter != last; ++iter)\r
378                         {\r
379                                 index_t changePathID = iter->GetPathID();\r
380 \r
381                                 if (   path.IsSameOrParentOf (changePathID)\r
382                                         || (  (iter->GetAction() != CRevisionInfoContainer::ACTION_CHANGED)\r
383                                            && path.GetBasePath().IsSameOrChildOf (changePathID)))\r
384                                 {\r
385                                         skipSubTree = false;\r
386                                         break;\r
387                                 }\r
388                         }\r
389                 }\r
390 \r
391                 // to the next node\r
392 \r
393                 searchNode = skipSubTree\r
394                                    ? searchNode->GetSkipSubTreeNext (startNode)\r
395                                    : searchNode->GetPreOrderNext (startNode);\r
396         }\r
397         while (searchNode != startNode);\r
398 \r
399 }\r
400 \r
401 void CFullGraphBuilder::AnalyzeAsChanges ( revision_t revision\r
402                                                                          , CSearchPathTree* searchNode)\r
403 {\r
404         // create & init the new graph node\r
405 \r
406     CFullGraphNode* newNode = graph.Add ( searchNode->GetPath()\r
407                                         , revision\r
408                                         , CNodeClassification::IS_MODIFIED\r
409                                         , searchNode->GetLastEntry());\r
410 \r
411         // link entries for the same search path\r
412 \r
413         searchNode->ChainEntries (newNode);\r
414 }\r
415 \r
416 void CFullGraphBuilder::AddCopiedPaths ( revision_t revision\r
417                                                                          , CSearchPathTree* rootNode\r
418                                                                          , SCopyInfo**& lastToCopy)\r
419 {\r
420     // find range of copies that point to this revision\r
421 \r
422         SCopyInfo** firstToCopy = lastToCopy;\r
423     history.GetCopyToRange (firstToCopy, lastToCopy, revision);\r
424 \r
425         // create search paths for all *relevant* paths added in this revision\r
426 \r
427         for (SCopyInfo** iter = firstToCopy; iter != lastToCopy; ++iter)\r
428         {\r
429                 const std::vector<SCopyInfo::STarget>& targets = (*iter)->targets;\r
430                 for (size_t i = 0, count = targets.size(); i < count; ++i)\r
431                 {\r
432                         const SCopyInfo::STarget& target = targets[i];\r
433                         CSearchPathTree* node = rootNode->Insert (target.path, revision);\r
434                         node->ChainEntries (target.source);\r
435                 }\r
436         }\r
437 }\r
438 \r
439 void CFullGraphBuilder::FillCopyTargets ( revision_t revision\r
440                                                                           , CSearchPathTree* rootNode\r
441                                                                           , SCopyInfo**& lastFromCopy)\r
442 {\r
443     // find range of copies that start from this revision\r
444 \r
445     SCopyInfo** firstFromCopy = lastFromCopy;\r
446     history.GetCopyFromRange (firstFromCopy, lastFromCopy, revision);\r
447 \r
448         // create search paths for all *relevant* paths added in this revision\r
449 \r
450         for (SCopyInfo** iter = firstFromCopy; iter != lastFromCopy; ++iter)\r
451         {\r
452                 SCopyInfo* copy = *iter;\r
453                 std::vector<SCopyInfo::STarget>& targets = copy->targets;\r
454 \r
455                 // crawl the whole sub-tree for path matches\r
456 \r
457                 CSearchPathTree* startNode \r
458                         = rootNode->FindCommonParent (copy->fromPathIndex);\r
459                 if (!startNode->GetPath().IsSameOrChildOf (copy->fromPathIndex))\r
460                         continue;\r
461 \r
462                 CSearchPathTree* searchNode = startNode;\r
463                 do\r
464                 {\r
465                         const CDictionaryBasedTempPath& path = searchNode->GetPath();\r
466             assert (path.IsSameOrChildOf (copy->fromPathIndex));\r
467 \r
468                         // got this path copied?\r
469 \r
470                         if (searchNode->IsActive())\r
471                         {\r
472                                 CDictionaryBasedPath fromPath ( path.GetDictionary()\r
473                                                                                           , copy->fromPathIndex);\r
474 \r
475                 // is there a better match in that target revision?\r
476                 // example log @r106:\r
477                 // A /trunk/F    /trunk/branches/b/F    100\r
478                 // R /trunk/F/a  /trunk/branches/b/F/a  105\r
479                 // -> don't copy from r100 but from r105\r
480 \r
481                 if (IsLatestCopySource ( revision\r
482                                        , copy->toRevision\r
483                                        , fromPath\r
484                                        , path))\r
485                 {\r
486                     // check for another special case:\r
487                     // A /branches/b    /trunk 100\r
488                     // D /branches/b/a\r
489                     // -> don't add a path if we are following /trunk/a\r
490 \r
491                                 CDictionaryBasedTempPath targetPath\r
492                                             = path.ReplaceParent ( fromPath\r
493                                                                                  , CDictionaryBasedPath ( path.GetDictionary()\r
494                                                                                                                                 , copy->toPathIndex));\r
495 \r
496                     if (TargetPathExists (copy->toRevision, targetPath.GetBasePath()))\r
497                     {\r
498                         // o.k. this is actual a copy we have to add to the tree\r
499 \r
500                         CFullGraphNode* entry = searchNode->GetLastEntry();\r
501                         if ((entry == NULL) || (entry->GetRevision() < revision))\r
502                                         {\r
503                                                 // the copy source graph node has yet to be created\r
504 \r
505                             entry = graph.Add ( path\r
506                                               , revision\r
507                                               , CNodeClassification::IS_COPY_SOURCE\r
508                                               , entry);\r
509 \r
510                                                 // link entries for the same search path\r
511 \r
512                                                 searchNode->ChainEntries (entry);\r
513                                         }\r
514 \r
515                         // add & schedule the new search path\r
516 \r
517                                         SCopyInfo::STarget target (entry, targetPath);\r
518                                         targets.push_back (target);\r
519                     }\r
520                             }\r
521             }\r
522 \r
523                         // select next node\r
524 \r
525                         searchNode = searchNode->GetPreOrderNext (startNode);\r
526                 }\r
527                 while (searchNode != startNode);\r
528         }\r
529 }\r
530 \r
531 bool CFullGraphBuilder::IsLatestCopySource ( revision_t fromRevision\r
532                                              , revision_t toRevision\r
533                                              , const CDictionaryBasedPath& fromPath\r
534                                              , const CDictionaryBasedTempPath& currentPath)\r
535 {\r
536     // try to find a "later" / "closer" copy source\r
537 \r
538     // example log @r106 (toRevision):\r
539     // A /trunk/F    /trunk/branches/b/F    100\r
540     // R /trunk/F/a  /trunk/branches/b/F/a  105\r
541     // -> return r105\r
542 \r
543     const CCachedLogInfo* cache = history.GetCache();\r
544     const CRevisionInfoContainer& logInfo = cache->GetLogInfo();\r
545     index_t index = cache->GetRevisions()[toRevision];\r
546 \r
547     // search it\r
548 \r
549     for ( CRevisionInfoContainer::CChangesIterator \r
550           iter = logInfo.GetChangesBegin (index)\r
551         , end = logInfo.GetChangesEnd (index)\r
552         ; iter != end\r
553         ; ++iter)\r
554     {\r
555         // is this a copy of the current path?\r
556 \r
557         if (   iter->HasFromPath() \r
558             && currentPath.IsSameOrChildOf (iter->GetFromPathID()))\r
559         {\r
560             // a later change?\r
561 \r
562             if (iter->GetFromRevision() > fromRevision)\r
563                 return false;\r
564 \r
565             // a closer sub-path?\r
566 \r
567             if (iter->GetFromPathID() > fromPath.GetIndex())\r
568                 return false;\r
569         }\r
570     }\r
571 \r
572     // (fromRevision, fromGraph) is the best match\r
573 \r
574     return true;\r
575 }\r
576 \r
577 bool CFullGraphBuilder::TargetPathExists ( revision_t revision\r
578                                          , const CDictionaryBasedPath& path)\r
579 {\r
580     // follow additions and deletions to determine whether the path exists\r
581     // after the given revision\r
582 \r
583     // A /branches/b    /trunk 100\r
584     // D /branches/b/a\r
585     // -> /branches/b/a does not exist\r
586 \r
587     const CCachedLogInfo* cache = history.GetCache();\r
588     const CRevisionInfoContainer& logInfo = cache->GetLogInfo();\r
589     index_t index = cache->GetRevisions()[revision];\r
590 \r
591     // short-cut: if there are no deletions, we should be fine\r
592 \r
593     if (!(logInfo.GetSumChanges (index) & CRevisionInfoContainer::ACTION_DELETED))\r
594         return true;\r
595 \r
596     // crawl changes and update this flag:\r
597 \r
598     bool exists = false;\r
599     for ( CRevisionInfoContainer::CChangesIterator \r
600           iter = logInfo.GetChangesBegin (index)\r
601         , end = logInfo.GetChangesEnd (index)\r
602         ; iter != end\r
603         ; ++iter)\r
604     {\r
605         // does this change affect the path?\r
606 \r
607         if (path.IsSameOrChildOf (iter->GetPathID()))\r
608         {\r
609             switch (iter->GetRawChange())\r
610             {\r
611             case CRevisionInfoContainer::ACTION_DELETED :\r
612                 // deletion? -> does not exist\r
613 \r
614                 exists = false;\r
615                 break;\r
616 \r
617             case CRevisionInfoContainer::ACTION_ADDED\r
618                + CRevisionInfoContainer::HAS_COPY_FROM:\r
619             case CRevisionInfoContainer::ACTION_REPLACED\r
620                + CRevisionInfoContainer::HAS_COPY_FROM:\r
621                 // copy? -> does exist\r
622 \r
623                 // We can safely assume here, that the source tree\r
624                 // contains the path in question as this is why we\r
625                 // called this function at all.\r
626 \r
627                 exists = true;\r
628                 break;\r
629 \r
630             case CRevisionInfoContainer::ACTION_ADDED:\r
631                 // exact addition? -> does exist\r
632 \r
633                 if (iter->GetPathID() == path.GetIndex())\r
634                     exists = true;\r
635 \r
636                 break;\r
637             }\r
638         }\r
639     }\r
640 \r
641     return exists;\r
642 }\r
643 \r