1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2008 - TortoiseSVN
\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
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
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
20 #include "fullHistory.h"
\r
22 #include "resource.h"
\r
24 #include "registry.h"
\r
25 #include "UnicodeUtils.h"
\r
26 #include "PathUtils.h"
\r
28 #include "TSVNPath.h"
\r
29 #include "SVNError.h"
\r
30 #include "SVNInfo.h"
\r
31 #include "CachedLogInfo.h"
\r
32 #include "RepositoryInfo.h"
\r
33 #include "RevisionIndex.h"
\r
34 #include "CopyFollowingLogIterator.h"
\r
35 #include "ProgressDlg.h"
\r
38 #define new DEBUG_NEW
\r
40 static char THIS_FILE[] = __FILE__;
\r
43 CFullHistory::CFullHistory(void)
\r
46 , headRevision ((revision_t)NO_REVISION)
\r
47 , pegRevision ((revision_t)NO_REVISION)
\r
48 , wcRevision ((revision_t)NO_REVISION)
\r
49 , copyInfoPool (sizeof (SCopyInfo), 1024)
\r
50 , copyToRelation (NULL)
\r
51 , copyToRelationEnd (NULL)
\r
52 , copyFromRelation (NULL)
\r
53 , copyFromRelationEnd (NULL)
\r
56 memset (&ctx, 0, sizeof (ctx));
\r
57 parentpool = svn_pool_create(NULL);
\r
59 Err = svn_config_ensure(NULL, parentpool);
\r
60 pool = svn_pool_create (parentpool);
\r
61 // set up the configuration
\r
63 Err = svn_config_get_config (&(ctx.config), g_pConfigDir, pool);
\r
67 ::MessageBox(NULL, this->GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
\r
68 svn_error_clear(Err);
\r
69 svn_pool_destroy (pool);
\r
70 svn_pool_destroy (parentpool);
\r
74 // set up authentication
\r
75 prompt.Init(pool, &ctx);
\r
77 ctx.cancel_func = cancel;
\r
78 ctx.cancel_baton = this;
\r
80 //set up the SVN_SSH param
\r
81 CString tsvn_ssh = CRegString(_T("Software\\TortoiseGit\\SSH"));
\r
82 if (tsvn_ssh.IsEmpty())
\r
83 tsvn_ssh = CPathUtils::GetAppDirectory() + _T("TortoisePlink.exe");
\r
84 tsvn_ssh.Replace('\\', '/');
\r
85 if (!tsvn_ssh.IsEmpty())
\r
87 svn_config_t * cfg = (svn_config_t *)apr_hash_get (ctx.config, SVN_CONFIG_CATEGORY_CONFIG,
\r
88 APR_HASH_KEY_STRING);
\r
89 svn_config_set(cfg, SVN_CONFIG_SECTION_TUNNELS, "ssh", CUnicodeUtils::GetUTF8(tsvn_ssh));
\r
93 CFullHistory::~CFullHistory(void)
\r
97 svn_error_clear(Err);
\r
98 svn_pool_destroy (parentpool);
\r
101 void CFullHistory::ClearCopyInfo()
\r
103 delete copyToRelation;
\r
104 delete copyFromRelation;
\r
106 copyToRelation = NULL;
\r
107 copyToRelationEnd = NULL;
\r
108 copyFromRelation = NULL;
\r
109 copyFromRelationEnd = NULL;
\r
111 for (size_t i = 0, count = copiesContainer.size(); i < count; ++i)
\r
112 copiesContainer[i]->Destroy (copyInfoPool);
\r
114 copiesContainer.clear();
\r
117 svn_error_t* CFullHistory::cancel(void *baton)
\r
119 CFullHistory * self = (CFullHistory *)baton;
\r
120 if (self->cancelled)
\r
123 temp.LoadString(IDS_SVN_USERCANCELLED);
\r
124 return svn_error_create(SVN_ERR_CANCELLED, NULL, CUnicodeUtils::GetUTF8(temp));
\r
126 return SVN_NO_ERROR;
\r
129 // implement ILogReceiver
\r
131 void CFullHistory::ReceiveLog ( LogChangedPathArray* changes
\r
133 , const StandardRevProps* stdRevProps
\r
134 , UserRevPropArray* userRevProps
\r
135 , bool mergesFollow)
\r
137 // fix release mode compiler warning
\r
139 UNREFERENCED_PARAMETER(changes);
\r
140 UNREFERENCED_PARAMETER(stdRevProps);
\r
141 UNREFERENCED_PARAMETER(userRevProps);
\r
142 UNREFERENCED_PARAMETER(mergesFollow);
\r
144 // update internal data
\r
146 if ((headRevision < (revision_t)rev) || (headRevision == NO_REVISION))
\r
147 headRevision = rev;
\r
149 // update progress bar and check for user pressing "Cancel" somewhere
\r
151 static DWORD lastProgressCall = 0;
\r
152 if (lastProgressCall < GetTickCount() - 200)
\r
154 lastProgressCall = GetTickCount();
\r
158 CString text, text2;
\r
159 text.LoadString(IDS_REVGRAPH_PROGGETREVS);
\r
160 text2.Format(IDS_REVGRAPH_PROGCURRENTREV, rev);
\r
162 progress->SetLine(1, text);
\r
163 progress->SetLine(2, text2);
\r
164 progress->SetProgress (headRevision - rev, headRevision);
\r
165 if (!progress->IsVisible())
\r
166 progress->ShowModeless ((CWnd*)NULL);
\r
168 if (progress->HasUserCancelled())
\r
171 throw SVNError (cancel (this));
\r
177 bool CFullHistory::FetchRevisionData ( CString path
\r
180 , CProgressDlg* progress)
\r
182 // set some text on the progress dialog, before we wait
\r
183 // for the log operation to start
\r
184 this->progress = progress;
\r
187 temp.LoadString(IDS_REVGRAPH_PROGGETREVS);
\r
188 progress->SetLine(1, temp);
\r
189 progress->SetLine(2, _T(""));
\r
190 progress->SetProgress(0, 1);
\r
192 // prepare the path for Subversion
\r
193 CTSVNPath svnPath (path);
\r
194 CStringA url = CPathUtils::PathEscape
\r
195 (CUnicodeUtils::GetUTF8
\r
196 (svn.GetURLFromPath (svnPath)));
\r
198 // we have to get the log from the repository root
\r
200 CTSVNPath rootPath;
\r
202 if (FALSE == svn.GetRootAndHead (svnPath, rootPath, head))
\r
204 Err = svn_error_dup(svn.Err);
\r
208 if (pegRev.IsHead())
\r
211 headRevision = head;
\r
212 repoRoot = rootPath.GetSVNPathString();
\r
213 relPath = CPathUtils::PathUnescape (url.Mid (repoRoot.GetLength()));
\r
214 repoRoot = CPathUtils::PathUnescape (repoRoot);
\r
216 // fix issue #360: use WC revision as peg revision
\r
218 pegRevision = pegRev;
\r
219 if (pegRevision == NO_REVISION)
\r
221 CTSVNPath svnPath (path);
\r
222 if (!svnPath.IsUrl())
\r
225 const SVNInfoData * baseInfo
\r
226 = info.GetFirstFileInfo (svnPath, SVNRev(), SVNRev());
\r
227 if (baseInfo != NULL)
\r
228 pegRevision = baseInfo->rev;
\r
232 // fetch missing data from the repository
\r
235 // select / construct query object and optimize revision range to fetch
\r
237 svnQuery.reset (new CSVNLogQuery (&ctx, pool));
\r
238 revision_t firstRevision = 0;
\r
240 if (svn.GetLogCachePool()->IsEnabled())
\r
242 CLogCachePool* pool = svn.GetLogCachePool();
\r
243 query.reset (new CCacheLogQuery (pool, svnQuery.get()));
\r
245 // get the cache and the lowest missing revision
\r
246 // (in off-line mode, the query may not find the cache as
\r
247 // it cannot contact the server to get the UUID)
\r
249 uuid = pool->GetRepositoryInfo().GetRepositoryUUID (svnPath);
\r
250 cache = pool->GetCache (uuid, GetRepositoryRoot());
\r
251 firstRevision = cache->GetRevisions().GetFirstMissingRevision(1);
\r
253 // if the cache is already complete, the firstRevision here is
\r
254 // HEAD+1 - that revision does not exist and would throw an error later
\r
256 if (firstRevision > headRevision)
\r
257 firstRevision = headRevision;
\r
261 query.reset (new CCacheLogQuery (svn, svnQuery.get()));
\r
265 // actually fetch the data
\r
267 query->Log ( CTSVNPathList (rootPath)
\r
272 , false // strictNodeHistory
\r
274 , false // includeChanges (log cache fetches them automatically)
\r
275 , false // includeMerges
\r
276 , true // includeStandardRevProps
\r
277 , false // includeUserRevProps
\r
278 , TRevPropNames());
\r
283 cache = query->GetCache();
\r
285 const CPathDictionary* paths = &cache->GetLogInfo().GetPaths();
\r
286 wcPath.reset (new CDictionaryBasedTempPath (paths, (const char*)relPath));
\r
287 wcRevision = pegRev;
\r
289 // Find the revision the working copy is on, we mark that revision
\r
290 // later in the graph (handle option changes properly!).
\r
291 // For performance reasons, we only don't do it if we want to display it.
\r
295 svn_revnum_t maxrev = wcRevision;
\r
296 svn_revnum_t minrev;
\r
297 bool switched, modified, sparse;
\r
298 if (svn.GetWCRevisionStatus ( CTSVNPath (path)
\r
306 wcRevision = maxrev;
\r
310 // analyse the data
\r
312 AnalyzeRevisionData();
\r
314 // pre-process log data (invert copy-relationship)
\r
316 BuildForwardCopies();
\r
318 catch (SVNError& e)
\r
320 Err = svn_error_create (e.GetCode(), NULL, e.GetMessage());
\r
327 void CFullHistory::AnalyzeRevisionData()
\r
329 svn_error_clear(Err);
\r
334 // special case: empty log
\r
336 if (headRevision == NO_REVISION)
\r
339 // we don't have a peg revision yet, set it to HEAD
\r
341 if (pegRevision == NO_REVISION)
\r
342 pegRevision = headRevision;
\r
344 // in case our path was renamed and had a different name in the past,
\r
345 // we have to find out that name now, because we will analyze the data
\r
346 // from lower to higher revisions
\r
348 startPath.reset (new CDictionaryBasedTempPath (*wcPath));
\r
350 CCopyFollowingLogIterator iterator (cache, pegRevision, *startPath);
\r
352 startRevision = pegRevision;
\r
354 while ((iterator.GetRevision() > 0) && !iterator.EndOfPath())
\r
356 if (iterator.DataIsMissing())
\r
358 iterator.ToNextAvailableData();
\r
362 startRevision = iterator.GetRevision();
\r
363 iterator.Advance();
\r
367 *startPath = iterator.GetPath();
\r
370 inline bool AscendingFromRevision (const SCopyInfo* lhs, const SCopyInfo* rhs)
\r
372 return lhs->fromRevision < rhs->fromRevision;
\r
375 inline bool AscendingToRevision (const SCopyInfo* lhs, const SCopyInfo* rhs)
\r
377 return lhs->toRevision < rhs->toRevision;
\r
380 void CFullHistory::BuildForwardCopies()
\r
382 // iterate through all revisions and fill copyToRelation:
\r
383 // for every copy-from info found, add an entry
\r
385 const CRevisionIndex& revisions = cache->GetRevisions();
\r
386 const CRevisionInfoContainer& revisionInfo = cache->GetLogInfo();
\r
388 // for all revisions ...
\r
390 copiesContainer.reserve (revisions.GetLastRevision());
\r
391 for ( revision_t revision = revisions.GetFirstRevision()
\r
392 , last = revisions.GetLastRevision()
\r
396 // ... in the cache ...
\r
398 index_t index = revisions[revision];
\r
399 if ( (index != NO_INDEX)
\r
400 && (revisionInfo.GetSumChanges (index) & CRevisionInfoContainer::HAS_COPY_FROM))
\r
402 // ... examine all changes ...
\r
404 for ( CRevisionInfoContainer::CChangesIterator
\r
405 iter = revisionInfo.GetChangesBegin (index)
\r
406 , end = revisionInfo.GetChangesEnd (index)
\r
410 // ... and if it has a copy-from info ...
\r
412 if (iter->HasFromPath())
\r
414 // ... add it to the list
\r
416 SCopyInfo* copyInfo = SCopyInfo::Create (copyInfoPool);
\r
418 copyInfo->fromRevision = iter->GetFromRevision();
\r
419 copyInfo->fromPathIndex = iter->GetFromPathID();
\r
420 copyInfo->toRevision = revision;
\r
421 copyInfo->toPathIndex = iter->GetPathID();
\r
423 copiesContainer.push_back (copyInfo);
\r
429 // sort container by source revision and path
\r
431 copyToRelation = new SCopyInfo*[copiesContainer.size()];
\r
432 copyToRelationEnd = copyToRelation + copiesContainer.size();
\r
434 copyFromRelation = new SCopyInfo*[copiesContainer.size()];
\r
435 copyFromRelationEnd = copyFromRelation + copiesContainer.size();
\r
436 #pragma warning( push )
\r
437 #pragma warning( disable : 4996 )
\r
438 std::copy (copiesContainer.begin(), copiesContainer.end(), copyToRelation);
\r
439 std::copy (copiesContainer.begin(), copiesContainer.end(), copyFromRelation);
\r
440 #pragma warning( pop )
\r
442 std::sort (copyToRelation, copyToRelationEnd, &AscendingToRevision);
\r
443 std::sort (copyFromRelation, copyFromRelationEnd, &AscendingFromRevision);
\r
446 CString CFullHistory::GetLastErrorMessage() const
\r
448 return SVN::GetErrorString(Err);
\r
451 void CFullHistory::GetCopyFromRange ( SCopyInfo**& first
\r
452 , SCopyInfo**& last
\r
453 , revision_t revision) const
\r
455 // find first entry for this revision (or first beyond)
\r
457 while ( (first != copyFromRelationEnd)
\r
458 && ((*first)->fromRevision < revision))
\r
461 // find first beyond this revision
\r
464 while ( (last != copyFromRelationEnd)
\r
465 && ((*last)->fromRevision <= revision))
\r
469 void CFullHistory::GetCopyToRange ( SCopyInfo**& first
\r
470 , SCopyInfo**& last
\r
471 , revision_t revision) const
\r
473 // find first entry for this revision (or first beyond)
\r
475 while ( (first != copyToRelationEnd)
\r
476 && ((*first)->toRevision < revision))
\r
479 // find first beyond this revision
\r
482 while ( (last != copyToRelationEnd)
\r
483 && ((*last)->toRevision <= revision))
\r