Fix typos
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphDlgFunc.cpp
blobec4bd29ae8662bef0ec3d598450b765536c603f7
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011 - TortoiseSVN
4 // Copyright (C) 2012-2023 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include <gdiplus.h>
23 #include "Revisiongraphdlg.h"
24 #include "Git.h"
25 #include "TempFile.h"
26 #include "UnicodeUtils.h"
27 #include "TGitPath.h"
28 #include "DPIAware.h"
29 #include <regex>
31 #ifdef _DEBUG
32 #define new DEBUG_NEW
33 #undef THIS_FILE
34 static char THIS_FILE[] = __FILE__;
35 #endif
37 using namespace Gdiplus;
39 void CRevisionGraphWnd::InitView()
41 m_bIsCanvasMove = false;
43 SetScrollbars();
46 void CRevisionGraphWnd::BuildPreview()
48 m_Preview.DeleteObject();
49 if (!m_bShowOverview)
50 return;
52 // is there a point in drawing this at all?
54 int nodeCount = this->m_Graph.numberOfNodes();
55 if ((nodeCount > REVGRAPH_PREVIEW_MAX_NODES) || (nodeCount == 0))
56 return;
58 float origZoom = m_fZoomFactor;
60 CRect clientRect = GetClientRect();
61 CSize preViewSize(max(CDPIAware::Instance().ScaleX(GetSafeHwnd(), REVGRAPH_PREVIEW_WIDTH), clientRect.Width() / 4),
62 max(CDPIAware::Instance().ScaleY(GetSafeHwnd(), REVGRAPH_PREVIEW_HEIGHT), clientRect.Height() / 4));
64 // zoom the graph so that it is completely visible in the window
65 CRect graphRect = GetGraphRect();
66 float horzfact = float(graphRect.Width())/float(preViewSize.cx);
67 float vertfact = float(graphRect.Height())/float(preViewSize.cy);
68 m_previewZoom = min (DEFAULT_ZOOM, 1.0f/(max(horzfact, vertfact)));
70 // make sure the preview window has a minimal size
72 m_previewWidth = min(static_cast<LONG>(max(graphRect.Width() * m_previewZoom, 30.0f)), preViewSize.cx);
73 m_previewHeight = min(static_cast<LONG>(max(graphRect.Height() * m_previewZoom, 30.0f)), preViewSize.cy);
75 CClientDC ddc(this);
76 CDC dc;
77 if (!dc.CreateCompatibleDC(&ddc))
78 return;
80 m_Preview.CreateCompatibleBitmap(&ddc, m_previewWidth, m_previewHeight);
81 HBITMAP oldbm = static_cast<HBITMAP>(dc.SelectObject (m_Preview));
83 // paint the whole graph
84 DoZoom (m_previewZoom, false);
85 CRect rect (0, 0, m_previewWidth, m_previewHeight);
86 GraphicsDevice dev;
87 dev.pDC = &dc;
88 DrawGraph(dev, rect, 0, 0, true);
90 // now we have a bitmap the size of the preview window
91 dc.SelectObject(oldbm);
92 dc.DeleteDC();
94 DoZoom (origZoom, false);
97 void CRevisionGraphWnd::SetScrollbar (int bar, int newPos, int clientMax, int graphMax)
99 SCROLLINFO ScrollInfo = {sizeof(SCROLLINFO), SIF_ALL};
100 GetScrollInfo (bar, &ScrollInfo);
102 clientMax = max(1, clientMax);
103 int oldHeight = ScrollInfo.nMax <= 0 ? clientMax : ScrollInfo.nMax;
104 int newHeight = static_cast<int>(graphMax * m_fZoomFactor);
105 int maxPos = max (0, newHeight - clientMax);
106 int pos = min (maxPos, newPos >= 0
107 ? newPos
108 : ScrollInfo.nPos * newHeight / oldHeight);
110 ScrollInfo.nPos = pos;
111 ScrollInfo.nMin = 0;
112 ScrollInfo.nMax = newHeight;
113 ScrollInfo.nPage = clientMax;
114 ScrollInfo.nTrackPos = pos;
116 SetScrollInfo(bar, &ScrollInfo);
119 void CRevisionGraphWnd::SetScrollbars (int nVert, int nHorz)
121 CRect clientrect = GetClientRect();
122 const CRect& pRect = GetGraphRect();
124 SetScrollbar (SB_VERT, nVert, clientrect.Height(), pRect.Height());
125 SetScrollbar (SB_HORZ, nHorz, clientrect.Width(), pRect.Width());
128 CRect CRevisionGraphWnd::GetGraphRect()
130 return m_GraphRect;
133 CRect CRevisionGraphWnd::GetClientRect()
135 CRect clientRect;
136 CWnd::GetClientRect (&clientRect);
137 return clientRect;
140 CRect CRevisionGraphWnd::GetWindowRect()
142 CRect windowRect;
143 CWnd::GetWindowRect (&windowRect);
144 return windowRect;
147 CRect CRevisionGraphWnd::GetViewRect()
149 CRect result;
150 result.UnionRect (GetClientRect(), GetGraphRect());
151 return result;
154 int CRevisionGraphWnd::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
156 UINT num = 0; // number of image encoders
157 UINT size = 0; // size of the image encoder array in bytes
159 if (GetImageEncodersSize(&num, &size)!=Ok)
160 return -1;
161 if(size == 0)
162 return -1; // Failure
164 auto pImageCodecInfo = reinterpret_cast<ImageCodecInfo*>(malloc(size));
165 if (!pImageCodecInfo)
166 return -1; // Failure
168 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
170 for(UINT j = 0; j < num; ++j)
172 if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
174 *pClsid = pImageCodecInfo[j].Clsid;
175 free(pImageCodecInfo);
176 return j; // Success
181 free(pImageCodecInfo);
182 return -1; // Failure
185 bool CRevisionGraphWnd::FetchRevisionData
186 ( const CString& /*path*/
187 , CProgressDlg* /*progress*/
188 , ITaskbarList3 * /*pTaskbarList*/
189 , HWND /*hWnd*/)
191 this->m_LogCache.ClearAllParent();
192 this->m_logEntries.ClearAll();
193 CString range;
194 DWORD infomask = CGit::LOG_INFO_SIMPILFY_BY_DECORATION | (m_bShowBranchingsMerges ? CGit::LOG_INFO_SPARSE : 0);
196 if (!m_FromRev.IsEmpty())
198 // Format string. (Ex. from "branch1 branch2 branch3" to " ^branch1 ^branch2 ^branch3")
201 const std::wsregex_iterator end;
202 std::wstring string { static_cast<LPCWSTR>(m_FromRev) };
203 std::wregex regex(L"[^\\s]+");
204 for (std::wsregex_iterator it(string.cbegin(), string.cend(), regex); it != end; ++it)
206 range += L" ^" + g_Git.FixBranchName(it->str().c_str());
209 catch (std::exception&) {}
212 if (!m_bLocalBranches && !m_bCurrentBranch && !m_ToRev.IsEmpty())
216 const std::wsregex_iterator end;
217 std::wstring string { static_cast<LPCWSTR>(m_ToRev) };
218 std::wregex regex(L"[^\\s]+");
219 for (std::wsregex_iterator it(string.cbegin(), string.cend(), regex); it != end; ++it)
221 range += L' ' + g_Git.FixBranchName(it->str().c_str());
224 catch (std::exception&) {}
226 else
228 if (m_bLocalBranches)
229 infomask |= CGit::LOG_INFO_ALWAYS_APPLY_RANGE | CGit::LOG_INFO_LOCAL_BRANCHES;
230 else if (m_bCurrentBranch)
231 range += L" HEAD";
232 else // all branch
233 infomask |= CGit::LOG_INFO_ALWAYS_APPLY_RANGE | CGit::LOG_INFO_ALL_BRANCH;
236 m_logEntries.ParserFromLog(nullptr, 0, infomask, &range);
238 ReloadHashMap();
239 this->m_Graph.clear();
241 g_Git.GetSubmodulePointer(m_submoduleInfo);
243 // build child graph
244 if (!m_bShowAllTags || m_bShowBranchingsMerges)
246 std::unordered_map<CGitHash, std::vector<CGitHash>> childMap;
247 for (size_t i = 0; i < m_logEntries.size(); ++i)
249 const GitRev& rev = m_logEntries.GetGitRevAt(i);
250 std::for_each(rev.m_ParentHash.cbegin(), rev.m_ParentHash.cend(), [&](const auto& parent) { childMap[parent].push_back(m_logEntries[i]); });
253 // rewrite history
254 std::unordered_set<CGitHash> skipList;
255 for (size_t i = 0; i < m_logEntries.size(); ++i)
257 auto& rev = m_logEntries.GetGitRevAt(i);
259 // keep labeled commits
260 const bool isSuperRepoHash = m_submoduleInfo.AnyMatches(rev.m_CommitHash);
261 if (auto foundNames = m_HashMap.find(rev.m_CommitHash); foundNames != m_HashMap.cend() || isSuperRepoHash)
263 // by default any label is enough to keep this commit visible
264 if (m_bShowAllTags || isSuperRepoHash)
265 continue;
267 // if hiding tags, check if there are any branch names for this commit
268 bool haveNonTagNames = false;
269 for (auto name : foundNames->second)
271 CGit::REF_TYPE refType;
272 CGit::GetShortName(name, &refType);
273 if (refType != CGit::REF_TYPE::ANNOTATED_TAG && refType != CGit::REF_TYPE::TAG)
275 haveNonTagNames = true;
276 break;
279 if (haveNonTagNames)
280 continue;
283 if (rev.m_ParentHash.size() != 1)
284 continue;
286 auto childIt = childMap.find(rev.m_CommitHash);
287 if (childIt == childMap.cend() || childIt->second.size() != 1)
288 continue;
290 auto& childRev = m_logEntries.GetGitRevAt(m_logEntries.m_HashMap.find(childIt->second[0])->second);
291 if (childRev.m_ParentHash.size() != 1)
292 continue;
294 // it's ok to erase this commit
295 skipList.insert(rev.m_CommitHash);
297 childRev.m_ParentHash[0] = rev.m_ParentHash[0];
299 auto& parentChildren = childMap[rev.m_ParentHash[0]];
300 if (auto it = std::find(parentChildren.begin(), parentChildren.end(), rev.m_CommitHash); it != parentChildren.end())
301 *it = childIt->second[0];
303 childMap.erase(childIt);
306 // cleanup lists
307 m_logEntries.erase(std::remove_if(m_logEntries.begin(), m_logEntries.end(), [&skipList](const auto& hash) { return skipList.find(hash) != skipList.cend(); }), m_logEntries.end());
308 m_logEntries.m_HashMap.clear();
309 for (size_t i = 0; i < m_logEntries.size(); ++i)
310 m_logEntries.m_HashMap[m_logEntries[i]] = i;
313 // build revision graph
314 CArray<ogdf::node> nodes;
315 GraphicsDevice dev;
316 dev.pDC = this->GetDC();
317 dev.graphics = Graphics::FromHDC(dev.pDC->m_hDC);
318 dev.graphics->SetPageUnit (UnitPixel);
320 Gdiplus::Font font(CAppUtils::GetLogFontName(), static_cast<REAL>(m_nFontSize), FontStyleRegular);
321 Rect commitString;
322 MeasureTextLength(dev, font, CString(L'8', g_Git.GetShortHASHLength()), commitString.Width, commitString.Height);
324 m_HeadNode = nullptr;
326 for (const auto& hash : m_logEntries)
328 auto nd = m_Graph.newNode();
329 nodes.Add(nd);
330 SetNodeRect(dev, font, commitString, &nd, hash);
331 if (hash == m_HeadHash)
332 m_HeadNode = nd;
335 for (size_t i = 0; i < m_logEntries.size(); ++i)
337 const GitRev& rev = m_logEntries.GetGitRevAt(i);
338 for (size_t j = 0; j < rev.m_ParentHash.size(); ++j)
340 auto parentId = m_logEntries.m_HashMap.find(rev.m_ParentHash[j]);
341 if (parentId == m_logEntries.m_HashMap.end())
343 TRACE(L"Can't found parent node");
344 //new parent node as new node
345 auto nd = this->m_Graph.newNode();
346 m_Graph.newEdge(nodes[i], nd);
347 m_logEntries.push_back(rev.m_ParentHash[j]);
348 m_logEntries.m_HashMap[rev.m_ParentHash[j]] = m_logEntries.size() - 1;
349 nodes.Add(nd);
350 SetNodeRect(dev, font, commitString, &nd, rev.m_ParentHash[j]);
352 }else
354 TRACE(L"edge %d - %d\n",i, m_logEntries.m_HashMap[rev.m_ParentHash[j]]);
355 m_Graph.newEdge(nodes[i], nodes[parentId->second]);
360 //this->m_OHL.layerDistance(30.0);
361 //this->m_OHL.nodeDistance(25.0);
362 //this->m_OHL.weightBalancing(0.8);
364 m_SugiyamLayout.call(m_GraphAttr);
366 double xmax = 0;
367 double ymax = 0;
368 for (auto v : m_Graph.nodes)
370 double x = m_GraphAttr.x(v) + m_GraphAttr.width(v)/2;
371 double y = m_GraphAttr.y(v) + m_GraphAttr.height(v)/2;
372 if(x>xmax)
373 xmax = x;
374 if(y>ymax)
375 ymax = y;
377 this->m_GraphRect.top=m_GraphRect.left=0;
378 m_GraphRect.bottom = static_cast<LONG>(ymax);
379 m_GraphRect.right = static_cast<LONG>(xmax);
381 return true;
384 bool CRevisionGraphWnd::IsUpdateJobRunning() const
386 return (updateJob.get() != nullptr) && !updateJob->IsDone();
389 bool CRevisionGraphWnd::GetShowOverview() const
391 return m_bShowOverview;
394 void CRevisionGraphWnd::SetShowOverview (bool value)
396 m_bShowOverview = value;
397 if (m_bShowOverview)
398 BuildPreview();
400 CString CRevisionGraphWnd::GetFriendRefName(ogdf::node v) const
402 if (!v)
403 return CString();
404 const CGitHash& hash = this->m_logEntries[v->index()];
405 if (const auto refsIt = m_HashMap.find(hash); refsIt == m_HashMap.end())
406 return hash.ToString();
407 else
408 return refsIt->second[0];
411 STRING_VECTOR CRevisionGraphWnd::GetFriendRefNames(ogdf::node v, const CString* exclude, CGit::REF_TYPE* onlyRefType) const
413 if (!v)
414 return STRING_VECTOR();
415 const CGitHash& hash = m_logEntries[v->index()];
416 if (const auto refsIt = m_HashMap.find(hash); refsIt == m_HashMap.end())
417 return STRING_VECTOR();
418 else
420 STRING_VECTOR list;
421 for (const auto& ref: refsIt->second)
423 CGit::REF_TYPE refType;
424 CString shortName = CGit::GetShortName(ref, &refType);
425 if (exclude && *exclude == shortName)
426 continue;
427 if (!onlyRefType)
428 list.push_back(ref);
429 else if (*onlyRefType == refType)
430 list.push_back(shortName);
432 return list;
436 void CRevisionGraphWnd::CompareRevs(const CString& revTo)
438 ASSERT(m_SelectedEntry1);
439 ASSERT(!revTo.IsEmpty() || m_SelectedEntry2);
441 bool alternativeTool = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
443 CString sCmd;
445 sCmd.Format(L"/command:showcompare %s /revision1:%s /revision2:%s",
446 this->m_sPath.IsEmpty() ? L"" : static_cast<LPCWSTR>(L"/path:\"" + this->m_sPath + L'"'),
447 static_cast<LPCWSTR>(GetFriendRefName(m_SelectedEntry1)),
448 !revTo.IsEmpty() ? static_cast<LPCWSTR>(revTo) : static_cast<LPCWSTR>(GetFriendRefName(m_SelectedEntry2)));
450 if (alternativeTool)
451 sCmd += L" /alternative";
453 CAppUtils::RunTortoiseGitProc(sCmd);
456 void CRevisionGraphWnd::UnifiedDiffRevs(bool bHead)
458 ASSERT(m_SelectedEntry1);
459 ASSERT(bHead || m_SelectedEntry2);
461 bool alternativeTool = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
462 CAppUtils::StartShowUnifiedDiff(m_hWnd, CString(), GetFriendRefName(m_SelectedEntry1), CString(),
463 bHead ? CString("HEAD") : GetFriendRefName(m_SelectedEntry2),
464 alternativeTool);
467 void CRevisionGraphWnd::DoZoom (float fZoomFactor, bool updateScrollbars)
469 float oldzoom = m_fZoomFactor;
470 m_fZoomFactor = fZoomFactor;
472 m_nFontSize = max(1, int(DEFAULT_ZOOM_FONT * fZoomFactor));
473 if (m_nFontSize < SMALL_ZOOM_FONT_THRESHOLD)
474 m_nFontSize = min(static_cast<int>(SMALL_ZOOM_FONT_THRESHOLD), int(SMALL_ZOOM_FONT * fZoomFactor));
476 if (updateScrollbars)
478 SCROLLINFO si1 = {sizeof(SCROLLINFO), SIF_ALL};
479 GetScrollInfo(SB_VERT, &si1);
480 SCROLLINFO si2 = {sizeof(SCROLLINFO), SIF_ALL};
481 GetScrollInfo(SB_HORZ, &si2);
483 InitView();
485 si1.nPos = int(float(si1.nPos)*m_fZoomFactor/oldzoom);
486 si2.nPos = int(float(si2.nPos)*m_fZoomFactor/oldzoom);
487 SetScrollPos (SB_VERT, si1.nPos);
488 SetScrollPos (SB_HORZ, si2.nPos);
491 Invalidate (FALSE);