OSDN Git Service

Add Setting Dialog
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / RevisionGraph / FullHistory.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 "fullHistory.h"\r
21 \r
22 #include "resource.h"\r
23 #include "client.h"\r
24 #include "registry.h"\r
25 #include "UnicodeUtils.h"\r
26 #include "PathUtils.h"\r
27 #include "SVN.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
36 \r
37 #ifdef _DEBUG\r
38 #define new DEBUG_NEW\r
39 #undef THIS_FILE\r
40 static char THIS_FILE[] = __FILE__;\r
41 #endif\r
42 \r
43 CFullHistory::CFullHistory(void) \r
44     : cancelled (false)\r
45     , progress (NULL)\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
54     , cache (NULL)\r
55 {\r
56         memset (&ctx, 0, sizeof (ctx));\r
57         parentpool = svn_pool_create(NULL);\r
58 \r
59         Err = svn_config_ensure(NULL, parentpool);\r
60         pool = svn_pool_create (parentpool);\r
61         // set up the configuration\r
62         if (Err == 0)\r
63                 Err = svn_config_get_config (&(ctx.config), g_pConfigDir, pool);\r
64 \r
65         if (Err != 0)\r
66         {\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
71                 exit(-1);\r
72         }\r
73 \r
74         // set up authentication\r
75         prompt.Init(pool, &ctx);\r
76 \r
77         ctx.cancel_func = cancel;\r
78         ctx.cancel_baton = this;\r
79 \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
86         {\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
90         }\r
91 }\r
92 \r
93 CFullHistory::~CFullHistory(void)\r
94 {\r
95     ClearCopyInfo();\r
96 \r
97     svn_error_clear(Err);\r
98         svn_pool_destroy (parentpool);\r
99 }\r
100 \r
101 void CFullHistory::ClearCopyInfo()\r
102 {\r
103         delete copyToRelation;\r
104         delete copyFromRelation;\r
105 \r
106         copyToRelation = NULL;\r
107         copyToRelationEnd = NULL;\r
108         copyFromRelation = NULL;\r
109         copyFromRelationEnd = NULL;\r
110 \r
111     for (size_t i = 0, count = copiesContainer.size(); i < count; ++i)\r
112         copiesContainer[i]->Destroy (copyInfoPool);\r
113 \r
114     copiesContainer.clear();\r
115 }\r
116 \r
117 svn_error_t* CFullHistory::cancel(void *baton)\r
118 {\r
119         CFullHistory * self = (CFullHistory *)baton;\r
120         if (self->cancelled)\r
121         {\r
122                 CString temp;\r
123                 temp.LoadString(IDS_SVN_USERCANCELLED);\r
124                 return svn_error_create(SVN_ERR_CANCELLED, NULL, CUnicodeUtils::GetUTF8(temp));\r
125         }\r
126         return SVN_NO_ERROR;\r
127 }\r
128 \r
129 // implement ILogReceiver\r
130 \r
131 void CFullHistory::ReceiveLog ( LogChangedPathArray* changes\r
132                                                   , svn_revnum_t rev\r
133                               , const StandardRevProps* stdRevProps\r
134                               , UserRevPropArray* userRevProps\r
135                               , bool mergesFollow)\r
136 {\r
137     // fix release mode compiler warning\r
138 \r
139     UNREFERENCED_PARAMETER(changes);\r
140     UNREFERENCED_PARAMETER(stdRevProps);\r
141     UNREFERENCED_PARAMETER(userRevProps);\r
142     UNREFERENCED_PARAMETER(mergesFollow);\r
143 \r
144         // update internal data\r
145 \r
146         if ((headRevision < (revision_t)rev) || (headRevision == NO_REVISION))\r
147                 headRevision = rev;\r
148 \r
149         // update progress bar and check for user pressing "Cancel" somewhere\r
150 \r
151         static DWORD lastProgressCall = 0;\r
152         if (lastProgressCall < GetTickCount() - 200)\r
153         {\r
154                 lastProgressCall = GetTickCount();\r
155 \r
156             if (progress)\r
157             {\r
158                     CString text, text2;\r
159                     text.LoadString(IDS_REVGRAPH_PROGGETREVS);\r
160                     text2.Format(IDS_REVGRAPH_PROGCURRENTREV, rev);\r
161 \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
167 \r
168                     if (progress->HasUserCancelled())\r
169                     {\r
170                             cancelled = true;\r
171                             throw SVNError (cancel (this));\r
172                     }\r
173         }\r
174         }\r
175 }\r
176 \r
177 bool CFullHistory::FetchRevisionData ( CString path\r
178                                      , SVNRev pegRev\r
179                                      , bool showWCRev\r
180                                      , CProgressDlg* progress)\r
181 {\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
185 \r
186         CString temp;\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
191 \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
197 \r
198     // we have to get the log from the repository root\r
199 \r
200         CTSVNPath rootPath;\r
201     svn_revnum_t head;\r
202     if (FALSE == svn.GetRootAndHead (svnPath, rootPath, head))\r
203         {\r
204                 Err = svn_error_dup(svn.Err);\r
205                 return false;\r
206         }\r
207 \r
208     if (pegRev.IsHead())\r
209         pegRev = head;\r
210 \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
215 \r
216         // fix issue #360: use WC revision as peg revision\r
217 \r
218     pegRevision = pegRev;\r
219     if (pegRevision == NO_REVISION)\r
220     {\r
221             CTSVNPath svnPath (path);\r
222             if (!svnPath.IsUrl())\r
223             {\r
224                     SVNInfo info;\r
225                     const SVNInfoData * baseInfo \r
226                             = info.GetFirstFileInfo (svnPath, SVNRev(), SVNRev());\r
227             if (baseInfo != NULL)\r
228                 pegRevision = baseInfo->rev;\r
229             }\r
230     }\r
231 \r
232         // fetch missing data from the repository\r
233         try\r
234         {\r
235         // select / construct query object and optimize revision range to fetch\r
236 \r
237                 svnQuery.reset (new CSVNLogQuery (&ctx, pool));\r
238         revision_t firstRevision = 0;\r
239 \r
240         if (svn.GetLogCachePool()->IsEnabled())\r
241         {\r
242             CLogCachePool* pool = svn.GetLogCachePool();\r
243                     query.reset (new CCacheLogQuery (pool, svnQuery.get()));\r
244 \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
248 \r
249             uuid = pool->GetRepositoryInfo().GetRepositoryUUID (svnPath);\r
250             cache = pool->GetCache (uuid, GetRepositoryRoot());\r
251             firstRevision = cache->GetRevisions().GetFirstMissingRevision(1);\r
252 \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
255 \r
256                         if (firstRevision > headRevision)\r
257                                 firstRevision = headRevision;\r
258         }\r
259         else\r
260         {\r
261                     query.reset (new CCacheLogQuery (svn, svnQuery.get()));\r
262             cache = NULL;\r
263         }\r
264 \r
265         // actually fetch the data\r
266 \r
267                 query->Log ( CTSVNPathList (rootPath)\r
268                                    , headRevision\r
269                                    , headRevision\r
270                                    , firstRevision\r
271                                    , 0\r
272                                    , false              // strictNodeHistory\r
273                                    , this\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
279 \r
280         // store WC path\r
281 \r
282         if (cache == NULL)\r
283                 cache = query->GetCache();\r
284 \r
285             const CPathDictionary* paths = &cache->GetLogInfo().GetPaths();\r
286         wcPath.reset (new CDictionaryBasedTempPath (paths, (const char*)relPath));\r
287         wcRevision = pegRev;\r
288 \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
292 \r
293         if (showWCRev)\r
294         {\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
299                                                                         , true\r
300                                                                         , minrev\r
301                                                                         , maxrev\r
302                                                                         , switched\r
303                                                                         , modified\r
304                                                                         , sparse))\r
305                 {\r
306                         wcRevision = maxrev;\r
307                 }\r
308         }\r
309 \r
310         // analyse the data\r
311 \r
312         AnalyzeRevisionData();\r
313 \r
314         // pre-process log data (invert copy-relationship)\r
315 \r
316         BuildForwardCopies();\r
317         }\r
318         catch (SVNError& e)\r
319         {\r
320                 Err = svn_error_create (e.GetCode(), NULL, e.GetMessage());\r
321                 return false;\r
322         }\r
323 \r
324         return true;\r
325 }\r
326 \r
327 void CFullHistory::AnalyzeRevisionData()\r
328 {\r
329         svn_error_clear(Err);\r
330     Err = NULL;\r
331 \r
332         ClearCopyInfo();\r
333 \r
334     // special case: empty log\r
335 \r
336     if (headRevision == NO_REVISION)\r
337         return;\r
338 \r
339     // we don't have a peg revision yet, set it to HEAD\r
340 \r
341     if (pegRevision == NO_REVISION)\r
342         pegRevision = headRevision;\r
343 \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
347 \r
348     startPath.reset (new CDictionaryBasedTempPath (*wcPath));\r
349 \r
350     CCopyFollowingLogIterator iterator (cache, pegRevision, *startPath);\r
351         iterator.Retry();\r
352         startRevision = pegRevision;\r
353 \r
354         while ((iterator.GetRevision() > 0) && !iterator.EndOfPath())\r
355         {\r
356         if (iterator.DataIsMissing())\r
357         {\r
358             iterator.ToNextAvailableData();\r
359         }\r
360         else\r
361         {\r
362                     startRevision = iterator.GetRevision();\r
363                 iterator.Advance();\r
364         }\r
365         }\r
366 \r
367         *startPath = iterator.GetPath();\r
368 }\r
369 \r
370 inline bool AscendingFromRevision (const SCopyInfo* lhs, const SCopyInfo* rhs)\r
371 {\r
372         return lhs->fromRevision < rhs->fromRevision;\r
373 }\r
374 \r
375 inline bool AscendingToRevision (const SCopyInfo* lhs, const SCopyInfo* rhs)\r
376 {\r
377         return lhs->toRevision < rhs->toRevision;\r
378 }\r
379 \r
380 void CFullHistory::BuildForwardCopies()\r
381 {\r
382         // iterate through all revisions and fill copyToRelation:\r
383         // for every copy-from info found, add an entry\r
384 \r
385         const CRevisionIndex& revisions = cache->GetRevisions();\r
386         const CRevisionInfoContainer& revisionInfo = cache->GetLogInfo();\r
387 \r
388         // for all revisions ...\r
389 \r
390         copiesContainer.reserve (revisions.GetLastRevision());\r
391         for ( revision_t revision = revisions.GetFirstRevision()\r
392                 , last = revisions.GetLastRevision()\r
393                 ; revision < last\r
394                 ; ++revision)\r
395         {\r
396                 // ... in the cache ...\r
397 \r
398                 index_t index = revisions[revision];\r
399                 if (   (index != NO_INDEX) \r
400                         && (revisionInfo.GetSumChanges (index) & CRevisionInfoContainer::HAS_COPY_FROM))\r
401                 {\r
402                         // ... examine all changes ...\r
403 \r
404                         for ( CRevisionInfoContainer::CChangesIterator \r
405                                         iter = revisionInfo.GetChangesBegin (index)\r
406                                 , end = revisionInfo.GetChangesEnd (index)\r
407                                 ; iter != end\r
408                                 ; ++iter)\r
409                         {\r
410                                 // ... and if it has a copy-from info ...\r
411 \r
412                                 if (iter->HasFromPath())\r
413                                 {\r
414                                         // ... add it to the list\r
415 \r
416                     SCopyInfo* copyInfo = SCopyInfo::Create (copyInfoPool);\r
417 \r
418                                         copyInfo->fromRevision = iter->GetFromRevision();\r
419                                         copyInfo->fromPathIndex = iter->GetFromPathID();\r
420                                         copyInfo->toRevision = revision;\r
421                                         copyInfo->toPathIndex = iter->GetPathID();\r
422 \r
423                                         copiesContainer.push_back (copyInfo);\r
424                                 }\r
425                         }\r
426                 }\r
427         }\r
428 \r
429         // sort container by source revision and path\r
430 \r
431     copyToRelation = new SCopyInfo*[copiesContainer.size()];\r
432     copyToRelationEnd = copyToRelation + copiesContainer.size();\r
433 \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
441 \r
442         std::sort (copyToRelation, copyToRelationEnd, &AscendingToRevision);\r
443         std::sort (copyFromRelation, copyFromRelationEnd, &AscendingFromRevision);\r
444 }\r
445 \r
446 CString CFullHistory::GetLastErrorMessage() const\r
447 {\r
448         return SVN::GetErrorString(Err);\r
449 }\r
450 \r
451 void CFullHistory::GetCopyFromRange ( SCopyInfo**& first\r
452                                     , SCopyInfo**& last\r
453                                     , revision_t revision) const\r
454 {\r
455         // find first entry for this revision (or first beyond)\r
456 \r
457         while (   (first != copyFromRelationEnd) \r
458                    && ((*first)->fromRevision < revision))\r
459                 ++first;\r
460 \r
461         // find first beyond this revision\r
462 \r
463         last = first;\r
464         while (   (last != copyFromRelationEnd) \r
465                    && ((*last)->fromRevision <= revision))\r
466                 ++last;\r
467 }\r
468 \r
469 void CFullHistory::GetCopyToRange ( SCopyInfo**& first\r
470                                   , SCopyInfo**& last\r
471                                   , revision_t revision) const\r
472 {\r
473         // find first entry for this revision (or first beyond)\r
474 \r
475         while (   (first != copyToRelationEnd) \r
476                    && ((*first)->toRevision < revision))\r
477                 ++first;\r
478 \r
479         // find first beyond this revision\r
480 \r
481         last = first;\r
482         while (   (last != copyToRelationEnd) \r
483                    && ((*last)->toRevision <= revision))\r
484                 ++last;\r
485 }\r