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.
20 #include "fullHistory.h"
25 #include "UnicodeUtils.h"
26 #include "PathUtils.h"
31 #include "CachedLogInfo.h"
32 #include "RepositoryInfo.h"
33 #include "RevisionIndex.h"
34 #include "CopyFollowingLogIterator.h"
35 #include "ProgressDlg.h"
40 static char THIS_FILE
[] = __FILE__
;
43 CFullHistory::CFullHistory(void)
46 , headRevision ((revision_t
)NO_REVISION
)
47 , pegRevision ((revision_t
)NO_REVISION
)
48 , firstRevision ((revision_t
)NO_REVISION
)
49 , wcRevision ((revision_t
)NO_REVISION
)
51 , copyInfoPool (sizeof (SCopyInfo
), 1024)
52 , copyToRelation (NULL
)
53 , copyToRelationEnd (NULL
)
54 , copyFromRelation (NULL
)
55 , copyFromRelationEnd (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
65 Err
= svn_config_get_config (&(ctx
.config
), g_pConfigDir
, pool
);
69 ::MessageBox(NULL
, this->GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR
);
71 svn_pool_destroy (pool
);
72 svn_pool_destroy (parentpool
);
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
,
91 svn_config_set(cfg
, SVN_CONFIG_SECTION_TUNNELS
, "ssh", CUnicodeUtils::GetUTF8(tsvn_ssh
));
95 CFullHistory::~CFullHistory(void)
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
;
125 temp
.LoadString(IDS_SVN_USERCANCELLED
);
126 return svn_error_create(SVN_ERR_CANCELLED
, NULL
, CUnicodeUtils::GetUTF8(temp
));
131 // implement ILogReceiver
133 void CFullHistory::ReceiveLog ( LogChangedPathArray
* changes
135 , const StandardRevProps
* stdRevProps
136 , UserRevPropArray
* userRevProps
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
))
151 // update progress bar and check for user pressing "Cancel" somewhere
153 static DWORD lastProgressCall
= 0;
154 if (lastProgressCall
< GetTickCount() - 200)
156 lastProgressCall
= GetTickCount();
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())
174 throw SVNError (cancel (this));
180 bool CFullHistory::FetchRevisionData ( CString path
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
;
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
209 if (FALSE
== svn
.GetRootAndHead (svnPath
, rootPath
, head
))
211 Err
= svn_error_dup(svn
.Err
);
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())
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)
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
;
269 query
.reset (new CCacheLogQuery (svn
, svnQuery
.get()));
274 // actually fetch the data
276 query
->Log ( CTSVNPathList (rootPath
)
281 , false // strictNodeHistory
283 , false // includeChanges (log cache fetches them automatically)
284 , false // includeMerges
285 , true // includeStandardRevProps
286 , false // includeUserRevProps
292 cache
= query
->GetCache();
294 const CPathDictionary
* paths
= &cache
->GetLogInfo().GetPaths();
295 wcPath
.reset (new CDictionaryBasedTempPath (paths
, (const char*)relPath
));
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
);
310 temp
.LoadString (IDS_REVGRAPH_PROGREADINGWC
);
311 progress
->SetLine(2, temp
);
313 if (svn
.GetWCRevisionStatus ( CTSVNPath (path
)
314 , true // get the "commit" revision
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
328 wcModified
= modified
;
335 AnalyzeRevisionData();
337 // pre-process log data (invert copy-relationship)
339 BuildForwardCopies();
343 Err
= svn_error_create (e
.GetCode(), NULL
, e
.GetMessage());
350 void CFullHistory::AnalyzeRevisionData()
352 svn_error_clear(Err
);
357 // special case: empty log
359 if (headRevision
== NO_REVISION
)
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
);
375 startRevision
= pegRevision
;
377 while ((iterator
.GetRevision() > 0) && !iterator
.EndOfPath())
379 if (iterator
.DataIsMissing())
381 iterator
.ToNextAvailableData();
385 startRevision
= iterator
.GetRevision();
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()
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
)
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
476 , revision_t revision
) const
478 // find first entry for this revision (or first beyond)
480 while ( (first
!= copyFromRelationEnd
)
481 && ((*first
)->fromRevision
< revision
))
484 // find first beyond this revision
487 while ( (last
!= copyFromRelationEnd
)
488 && ((*last
)->fromRevision
<= revision
))
492 void CFullHistory::GetCopyToRange ( SCopyInfo
**& first
494 , revision_t revision
) const
496 // find first entry for this revision (or first beyond)
498 while ( (first
!= copyToRelationEnd
)
499 && ((*first
)->toRevision
< revision
))
502 // find first beyond this revision
505 while ( (last
!= copyToRelationEnd
)
506 && ((*last
)->toRevision
<= revision
))