Update RevisionGraph to 15570
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / FullHistory.cpp
blob247032a1872c2da10b68fab0f2c4d2c822aff3e9
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2009 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include "fullHistory.h"
22 #include "resource.h"
23 #include "client.h"
24 #include "registry.h"
25 #include "UnicodeUtils.h"
26 #include "PathUtils.h"
27 #include "SVN.h"
28 #include "TSVNPath.h"
29 #include "SVNError.h"
30 #include "SVNInfo.h"
31 #include "CachedLogInfo.h"
32 #include "RepositoryInfo.h"
33 #include "RevisionIndex.h"
34 #include "CopyFollowingLogIterator.h"
35 #include "ProgressDlg.h"
37 #ifdef _DEBUG
38 #define new DEBUG_NEW
39 #undef THIS_FILE
40 static char THIS_FILE[] = __FILE__;
41 #endif
43 CFullHistory::CFullHistory(void)
44 : cancelled (false)
45 , progress (NULL)
46 , headRevision ((revision_t)NO_REVISION)
47 , pegRevision ((revision_t)NO_REVISION)
48 , firstRevision ((revision_t)NO_REVISION)
49 , wcRevision ((revision_t)NO_REVISION)
50 , wcModified (false)
51 , copyInfoPool (sizeof (SCopyInfo), 1024)
52 , copyToRelation (NULL)
53 , copyToRelationEnd (NULL)
54 , copyFromRelation (NULL)
55 , copyFromRelationEnd (NULL)
56 , cache (NULL)
58 memset (&ctx, 0, sizeof (ctx));
59 parentpool = svn_pool_create(NULL);
61 Err = svn_config_ensure(NULL, parentpool);
62 pool = svn_pool_create (parentpool);
63 // set up the configuration
64 if (Err == 0)
65 Err = svn_config_get_config (&(ctx.config), g_pConfigDir, pool);
67 if (Err != 0)
69 ::MessageBox(NULL, this->GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
70 svn_error_clear(Err);
71 svn_pool_destroy (pool);
72 svn_pool_destroy (parentpool);
73 exit(-1);
76 // set up authentication
77 prompt.Init(pool, &ctx);
79 ctx.cancel_func = cancel;
80 ctx.cancel_baton = this;
82 //set up the SVN_SSH param
83 CString tsvn_ssh = CRegString(_T("Software\\TortoiseSVN\\SSH"));
84 if (tsvn_ssh.IsEmpty())
85 tsvn_ssh = CPathUtils::GetAppDirectory() + _T("TortoisePlink.exe");
86 tsvn_ssh.Replace('\\', '/');
87 if (!tsvn_ssh.IsEmpty())
89 svn_config_t * cfg = (svn_config_t *)apr_hash_get (ctx.config, SVN_CONFIG_CATEGORY_CONFIG,
90 APR_HASH_KEY_STRING);
91 svn_config_set(cfg, SVN_CONFIG_SECTION_TUNNELS, "ssh", CUnicodeUtils::GetUTF8(tsvn_ssh));
95 CFullHistory::~CFullHistory(void)
97 ClearCopyInfo();
99 svn_error_clear(Err);
100 svn_pool_destroy (parentpool);
103 void CFullHistory::ClearCopyInfo()
105 delete copyToRelation;
106 delete copyFromRelation;
108 copyToRelation = NULL;
109 copyToRelationEnd = NULL;
110 copyFromRelation = NULL;
111 copyFromRelationEnd = NULL;
113 for (size_t i = 0, count = copiesContainer.size(); i < count; ++i)
114 copiesContainer[i]->Destroy (copyInfoPool);
116 copiesContainer.clear();
119 svn_error_t* CFullHistory::cancel(void *baton)
121 CFullHistory * self = (CFullHistory *)baton;
122 if (self->cancelled)
124 CString temp;
125 temp.LoadString(IDS_SVN_USERCANCELLED);
126 return svn_error_create(SVN_ERR_CANCELLED, NULL, CUnicodeUtils::GetUTF8(temp));
128 return SVN_NO_ERROR;
131 // implement ILogReceiver
133 void CFullHistory::ReceiveLog ( LogChangedPathArray* changes
134 , svn_revnum_t rev
135 , const StandardRevProps* stdRevProps
136 , UserRevPropArray* userRevProps
137 , bool mergesFollow)
139 // fix release mode compiler warning
141 UNREFERENCED_PARAMETER(changes);
142 UNREFERENCED_PARAMETER(stdRevProps);
143 UNREFERENCED_PARAMETER(userRevProps);
144 UNREFERENCED_PARAMETER(mergesFollow);
146 // update internal data
148 if ((headRevision < (revision_t)rev) || (headRevision == NO_REVISION))
149 headRevision = rev;
151 // update progress bar and check for user pressing "Cancel" somewhere
153 static DWORD lastProgressCall = 0;
154 if (lastProgressCall < GetTickCount() - 200)
156 lastProgressCall = GetTickCount();
158 if (progress)
160 CString text, text2;
161 text.LoadString(IDS_REVGRAPH_PROGGETREVS);
162 text2.Format(IDS_REVGRAPH_PROGCURRENTREV, rev);
164 DWORD revisionCount = headRevision - firstRevision+1;
165 progress->SetLine(1, text);
166 progress->SetLine(2, text2);
167 progress->SetProgress (headRevision - rev, revisionCount);
168 if (!progress->IsVisible())
169 progress->ShowModeless ((CWnd*)NULL);
171 if (progress->HasUserCancelled())
173 cancelled = true;
174 throw SVNError (cancel (this));
180 bool CFullHistory::FetchRevisionData ( CString path
181 , SVNRev pegRev
182 , bool showWCRev
183 , bool showWCModification
184 , CProgressDlg* progress)
186 // set some text on the progress dialog, before we wait
187 // for the log operation to start
188 this->progress = progress;
190 CString temp;
191 temp.LoadString (IDS_REVGRAPH_PROGGETREVS);
192 progress->SetLine(1, temp);
194 temp.LoadString (IDS_REVGRAPH_PROGPREPARING);
195 progress->SetLine(2, temp);
196 progress->SetProgress(0, 1);
197 progress->ShowModeless ((CWnd*)NULL);
199 // prepare the path for Subversion
200 CTSVNPath svnPath (path);
201 CStringA url = CPathUtils::PathEscape
202 (CUnicodeUtils::GetUTF8
203 (svn.GetURLFromPath (svnPath)));
205 // we have to get the log from the repository root
207 CTSVNPath rootPath;
208 svn_revnum_t head;
209 if (FALSE == svn.GetRootAndHead (svnPath, rootPath, head))
211 Err = svn_error_dup(svn.Err);
212 return false;
215 if (pegRev.IsHead())
216 pegRev = head;
218 headRevision = head;
219 repoRoot = rootPath.GetSVNPathString();
220 relPath = CPathUtils::PathUnescape (url.Mid (repoRoot.GetLength()));
221 repoRoot = CPathUtils::PathUnescape (repoRoot);
223 // fix issue #360: use WC revision as peg revision
225 pegRevision = pegRev;
226 if (pegRevision == NO_REVISION)
228 if (!svnPath.IsUrl())
230 SVNInfo info;
231 const SVNInfoData * baseInfo
232 = info.GetFirstFileInfo (svnPath, SVNRev(), SVNRev());
233 if (baseInfo != NULL)
234 pegRevision = baseInfo->rev;
238 // fetch missing data from the repository
241 // select / construct query object and optimize revision range to fetch
243 svnQuery.reset (new CSVNLogQuery (&ctx, pool));
245 if (svn.GetLogCachePool()->IsEnabled())
247 CLogCachePool* pool = svn.GetLogCachePool();
248 query.reset (new CCacheLogQuery (pool, svnQuery.get()));
250 // get the cache and the lowest missing revision
251 // (in off-line mode, the query may not find the cache as
252 // it cannot contact the server to get the UUID)
254 uuid = pool->GetRepositoryInfo().GetRepositoryUUID (svnPath);
255 cache = pool->GetCache (uuid, GetRepositoryRoot());
257 firstRevision = cache != NULL
258 ? cache->GetRevisions().GetFirstMissingRevision(1)
259 : 0;
261 // if the cache is already complete, the firstRevision here is
262 // HEAD+1 - that revision does not exist and would throw an error later
264 if (firstRevision > headRevision)
265 firstRevision = headRevision;
267 else
269 query.reset (new CCacheLogQuery (svn, svnQuery.get()));
270 cache = NULL;
271 firstRevision = 0;
274 // actually fetch the data
276 query->Log ( CTSVNPathList (rootPath)
277 , headRevision
278 , headRevision
279 , firstRevision
281 , false // strictNodeHistory
282 , this
283 , false // includeChanges (log cache fetches them automatically)
284 , false // includeMerges
285 , true // includeStandardRevProps
286 , false // includeUserRevProps
287 , TRevPropNames());
289 // store WC path
291 if (cache == NULL)
292 cache = query->GetCache();
294 const CPathDictionary* paths = &cache->GetLogInfo().GetPaths();
295 wcPath.reset (new CDictionaryBasedTempPath (paths, (const char*)relPath));
296 wcRevision = pegRev;
298 // Find the revision the working copy is on, we mark that revision
299 // later in the graph (handle option changes properly!).
300 // For performance reasons, we only don't do it if we want to display it.
302 if (showWCRev || showWCModification)
304 svn_revnum_t maxrev = wcRevision;
305 svn_revnum_t minrev = 0;
306 bool switched, modified, sparse;
307 CTSVNPath tpath = CTSVNPath (path);
308 if (!tpath.IsUrl())
310 temp.LoadString (IDS_REVGRAPH_PROGREADINGWC);
311 progress->SetLine(2, temp);
313 if (svn.GetWCRevisionStatus ( CTSVNPath (path)
314 , true // get the "commit" revision
315 , minrev
316 , maxrev
317 , switched
318 , modified
319 , sparse))
321 // we want to report the oldest revision as WC revision:
322 // If you commit at $WC/path/ and after that ask for the
323 // rev graph at $WC/, we want to display the revision of
324 // the base path ($WC/ is now "older") instead of the
325 // newest one.
327 wcRevision = maxrev;
328 wcModified = modified;
333 // analyse the data
335 AnalyzeRevisionData();
337 // pre-process log data (invert copy-relationship)
339 BuildForwardCopies();
341 catch (SVNError& e)
343 Err = svn_error_create (e.GetCode(), NULL, e.GetMessage());
344 return false;
347 return true;
350 void CFullHistory::AnalyzeRevisionData()
352 svn_error_clear(Err);
353 Err = NULL;
355 ClearCopyInfo();
357 // special case: empty log
359 if (headRevision == NO_REVISION)
360 return;
362 // we don't have a peg revision yet, set it to HEAD
364 if (pegRevision == NO_REVISION)
365 pegRevision = headRevision;
367 // in case our path was renamed and had a different name in the past,
368 // we have to find out that name now, because we will analyze the data
369 // from lower to higher revisions
371 startPath.reset (new CDictionaryBasedTempPath (*wcPath));
373 CCopyFollowingLogIterator iterator (cache, pegRevision, *startPath);
374 iterator.Retry();
375 startRevision = pegRevision;
377 while ((iterator.GetRevision() > 0) && !iterator.EndOfPath())
379 if (iterator.DataIsMissing())
381 iterator.ToNextAvailableData();
383 else
385 startRevision = iterator.GetRevision();
386 iterator.Advance();
390 *startPath = iterator.GetPath();
393 inline bool AscendingFromRevision (const SCopyInfo* lhs, const SCopyInfo* rhs)
395 return lhs->fromRevision < rhs->fromRevision;
398 inline bool AscendingToRevision (const SCopyInfo* lhs, const SCopyInfo* rhs)
400 return lhs->toRevision < rhs->toRevision;
403 void CFullHistory::BuildForwardCopies()
405 // iterate through all revisions and fill copyToRelation:
406 // for every copy-from info found, add an entry
408 const CRevisionIndex& revisions = cache->GetRevisions();
409 const CRevisionInfoContainer& revisionInfo = cache->GetLogInfo();
411 // for all revisions ...
413 copiesContainer.reserve (revisions.GetLastRevision());
414 for ( revision_t revision = revisions.GetFirstRevision()
415 , last = revisions.GetLastRevision()
416 ; revision < last
417 ; ++revision)
419 // ... in the cache ...
421 index_t index = revisions[revision];
422 if ( (index != NO_INDEX)
423 && (revisionInfo.GetSumChanges (index) & CRevisionInfoContainer::HAS_COPY_FROM))
425 // ... examine all changes ...
427 for ( CRevisionInfoContainer::CChangesIterator
428 iter = revisionInfo.GetChangesBegin (index)
429 , end = revisionInfo.GetChangesEnd (index)
430 ; iter != end
431 ; ++iter)
433 // ... and if it has a copy-from info ...
435 if (iter->HasFromPath())
437 // ... add it to the list
439 SCopyInfo* copyInfo = SCopyInfo::Create (copyInfoPool);
441 copyInfo->fromRevision = iter->GetFromRevision();
442 copyInfo->fromPathIndex = iter->GetFromPathID();
443 copyInfo->toRevision = revision;
444 copyInfo->toPathIndex = iter->GetPathID();
446 copiesContainer.push_back (copyInfo);
452 // sort container by source revision and path
454 copyToRelation = new SCopyInfo*[copiesContainer.size()];
455 copyToRelationEnd = copyToRelation + copiesContainer.size();
457 copyFromRelation = new SCopyInfo*[copiesContainer.size()];
458 copyFromRelationEnd = copyFromRelation + copiesContainer.size();
459 #pragma warning( push )
460 #pragma warning( disable : 4996 )
461 std::copy (copiesContainer.begin(), copiesContainer.end(), copyToRelation);
462 std::copy (copiesContainer.begin(), copiesContainer.end(), copyFromRelation);
463 #pragma warning( pop )
465 std::sort (copyToRelation, copyToRelationEnd, &AscendingToRevision);
466 std::sort (copyFromRelation, copyFromRelationEnd, &AscendingFromRevision);
469 CString CFullHistory::GetLastErrorMessage() const
471 return SVN::GetErrorString(Err);
474 void CFullHistory::GetCopyFromRange ( SCopyInfo**& first
475 , SCopyInfo**& last
476 , revision_t revision) const
478 // find first entry for this revision (or first beyond)
480 while ( (first != copyFromRelationEnd)
481 && ((*first)->fromRevision < revision))
482 ++first;
484 // find first beyond this revision
486 last = first;
487 while ( (last != copyFromRelationEnd)
488 && ((*last)->fromRevision <= revision))
489 ++last;
492 void CFullHistory::GetCopyToRange ( SCopyInfo**& first
493 , SCopyInfo**& last
494 , revision_t revision) const
496 // find first entry for this revision (or first beyond)
498 while ( (first != copyToRelationEnd)
499 && ((*first)->toRevision < revision))
500 ++first;
502 // find first beyond this revision
504 last = first;
505 while ( (last != copyToRelationEnd)
506 && ((*last)->toRevision <= revision))
507 ++last;