Fixed issue #3668: "Revert to revision" fails for added files
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphDlgFunc.cpp
blob79f7e54a0865cbad19201878239b9394439a61f8
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011 - TortoiseSVN
4 // Copyright (C) 2012-2019 - 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"
30 #ifdef _DEBUG
31 #define new DEBUG_NEW
32 #undef THIS_FILE
33 static char THIS_FILE[] = __FILE__;
34 #endif
36 using namespace Gdiplus;
38 void CRevisionGraphWnd::InitView()
40 m_bIsCanvasMove = false;
42 SetScrollbars();
45 void CRevisionGraphWnd::BuildPreview()
47 m_Preview.DeleteObject();
48 if (!m_bShowOverview)
49 return;
51 // is there a point in drawing this at all?
53 int nodeCount = this->m_Graph.numberOfNodes();
54 if ((nodeCount > REVGRAPH_PREVIEW_MAX_NODES) || (nodeCount == 0))
55 return;
57 float origZoom = m_fZoomFactor;
59 CRect clientRect = GetClientRect();
60 CSize preViewSize(max(CDPIAware::Instance().ScaleX(REVGRAPH_PREVIEW_WIDTH), clientRect.Width() / 4),
61 max(CDPIAware::Instance().ScaleY(REVGRAPH_PREVIEW_HEIGHT), clientRect.Height() / 4));
63 // zoom the graph so that it is completely visible in the window
64 CRect graphRect = GetGraphRect();
65 float horzfact = float(graphRect.Width())/float(preViewSize.cx);
66 float vertfact = float(graphRect.Height())/float(preViewSize.cy);
67 m_previewZoom = min (DEFAULT_ZOOM, 1.0f/(max(horzfact, vertfact)));
69 // make sure the preview window has a minimal size
71 m_previewWidth = min(static_cast<LONG>(max(graphRect.Width() * m_previewZoom, 30.0f)), preViewSize.cx);
72 m_previewHeight = min(static_cast<LONG>(max(graphRect.Height() * m_previewZoom, 30.0f)), preViewSize.cy);
74 CClientDC ddc(this);
75 CDC dc;
76 if (!dc.CreateCompatibleDC(&ddc))
77 return;
79 m_Preview.CreateCompatibleBitmap(&ddc, m_previewWidth, m_previewHeight);
80 HBITMAP oldbm = static_cast<HBITMAP>(dc.SelectObject (m_Preview));
82 // paint the whole graph
83 DoZoom (m_previewZoom, false);
84 CRect rect (0, 0, m_previewWidth, m_previewHeight);
85 GraphicsDevice dev;
86 dev.pDC = &dc;
87 DrawGraph(dev, rect, 0, 0, true);
89 // now we have a bitmap the size of the preview window
90 dc.SelectObject(oldbm);
91 dc.DeleteDC();
93 DoZoom (origZoom, false);
96 void CRevisionGraphWnd::SetScrollbar (int bar, int newPos, int clientMax, int graphMax)
98 SCROLLINFO ScrollInfo = {sizeof(SCROLLINFO), SIF_ALL};
99 GetScrollInfo (bar, &ScrollInfo);
101 clientMax = max(1, clientMax);
102 int oldHeight = ScrollInfo.nMax <= 0 ? clientMax : ScrollInfo.nMax;
103 int newHeight = static_cast<int>(graphMax * m_fZoomFactor);
104 int maxPos = max (0, newHeight - clientMax);
105 int pos = min (maxPos, newPos >= 0
106 ? newPos
107 : ScrollInfo.nPos * newHeight / oldHeight);
109 ScrollInfo.nPos = pos;
110 ScrollInfo.nMin = 0;
111 ScrollInfo.nMax = newHeight;
112 ScrollInfo.nPage = clientMax;
113 ScrollInfo.nTrackPos = pos;
115 SetScrollInfo(bar, &ScrollInfo);
118 void CRevisionGraphWnd::SetScrollbars (int nVert, int nHorz)
120 CRect clientrect = GetClientRect();
121 const CRect& pRect = GetGraphRect();
123 SetScrollbar (SB_VERT, nVert, clientrect.Height(), pRect.Height());
124 SetScrollbar (SB_HORZ, nHorz, clientrect.Width(), pRect.Width());
127 CRect CRevisionGraphWnd::GetGraphRect()
129 return m_GraphRect;
132 CRect CRevisionGraphWnd::GetClientRect()
134 CRect clientRect;
135 CWnd::GetClientRect (&clientRect);
136 return clientRect;
139 CRect CRevisionGraphWnd::GetWindowRect()
141 CRect windowRect;
142 CWnd::GetWindowRect (&windowRect);
143 return windowRect;
146 CRect CRevisionGraphWnd::GetViewRect()
148 CRect result;
149 result.UnionRect (GetClientRect(), GetGraphRect());
150 return result;
153 int CRevisionGraphWnd::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
155 UINT num = 0; // number of image encoders
156 UINT size = 0; // size of the image encoder array in bytes
158 if (GetImageEncodersSize(&num, &size)!=Ok)
159 return -1;
160 if(size == 0)
161 return -1; // Failure
163 auto pImageCodecInfo = reinterpret_cast<ImageCodecInfo*>(malloc(size));
164 if (!pImageCodecInfo)
165 return -1; // Failure
167 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
169 for(UINT j = 0; j < num; ++j)
171 if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
173 *pClsid = pImageCodecInfo[j].Clsid;
174 free(pImageCodecInfo);
175 return j; // Success
180 free(pImageCodecInfo);
181 return -1; // Failure
184 bool CRevisionGraphWnd::FetchRevisionData
185 ( const CString& /*path*/
186 , CProgressDlg* /*progress*/
187 , ITaskbarList3 * /*pTaskbarList*/
188 , HWND /*hWnd*/)
190 this->m_LogCache.ClearAllParent();
191 this->m_logEntries.ClearAll();
192 CString range;
193 if (!m_ToRev.IsEmpty() && !m_FromRev.IsEmpty())
194 range.Format(L"%s..%s", static_cast<LPCTSTR>(g_Git.FixBranchName(m_FromRev)), static_cast<LPCTSTR>(g_Git.FixBranchName(m_ToRev)));
195 else if (!m_ToRev.IsEmpty())
196 range = m_ToRev;
197 else if (!m_FromRev.IsEmpty())
198 range = m_FromRev;
199 DWORD infomask = CGit::LOG_INFO_SIMPILFY_BY_DECORATION | (m_bCurrentBranch ? 0 : m_bLocalBranches ? CGit::LOG_INFO_LOCAL_BRANCHES : CGit::LOG_INFO_ALL_BRANCH) | (m_bShowBranchingsMerges ? CGit::LOG_ORDER_TOPOORDER | CGit::LOG_INFO_SPARSE : 0);
200 m_logEntries.ParserFromLog(nullptr, 0, infomask, &range);
202 ReloadHashMap();
203 this->m_Graph.clear();
205 m_superProjectHash.Empty();
206 if (CRegDWORD(L"Software\\TortoiseGit\\LogShowSuperProjectSubmodulePointer", TRUE) != FALSE)
207 m_superProjectHash = g_Git.GetSubmodulePointer();
209 // build child graph
210 if (!m_bShowAllTags || m_bShowBranchingsMerges)
212 std::unordered_map<CGitHash, std::vector<CGitHash>> childMap;
213 for (size_t i = 0; i < m_logEntries.size(); ++i)
215 const GitRev& rev = m_logEntries.GetGitRevAt(i);
216 std::for_each(rev.m_ParentHash.cbegin(), rev.m_ParentHash.cend(), [&](const auto& parent) { childMap[parent].push_back(m_logEntries[i]); });
219 // rewrite history
220 std::unordered_set<CGitHash> skipList;
221 for (size_t i = 0; i < m_logEntries.size(); ++i)
223 auto& rev = m_logEntries.GetGitRevAt(i);
225 // keep labeled commits
226 if (auto foundNames = m_HashMap.find(rev.m_CommitHash); foundNames != m_HashMap.cend() || rev.m_CommitHash == m_superProjectHash)
228 // by default any label is enough to keep this commit visible
229 if (m_bShowAllTags || rev.m_CommitHash == m_superProjectHash)
230 continue;
232 // if hiding tags, check if there are any branch names for this commit
233 bool haveNonTagNames = false;
234 for (auto name : foundNames->second)
236 CGit::REF_TYPE refType;
237 CGit::GetShortName(name, &refType);
238 if (refType != CGit::REF_TYPE::ANNOTATED_TAG && refType != CGit::REF_TYPE::TAG)
240 haveNonTagNames = true;
241 break;
244 if (haveNonTagNames)
245 continue;
248 if (rev.m_ParentHash.size() != 1)
249 continue;
251 auto childIt = childMap.find(rev.m_CommitHash);
252 if (childIt == childMap.cend() || childIt->second.size() != 1)
253 continue;
255 auto& childRev = m_logEntries.GetGitRevAt(m_logEntries.m_HashMap.find(childIt->second[0])->second);
256 if (childRev.m_ParentHash.size() != 1)
257 continue;
259 // it's ok to erase this commit
260 skipList.insert(rev.m_CommitHash);
262 childRev.m_ParentHash[0] = rev.m_ParentHash[0];
264 auto& parentChildren = childMap[rev.m_ParentHash[0]];
265 if (auto it = std::find(parentChildren.begin(), parentChildren.end(), rev.m_CommitHash); it != parentChildren.end())
266 *it = childIt->second[0];
268 childMap.erase(childIt);
271 // cleanup lists
272 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());
273 m_logEntries.m_HashMap.clear();
274 for (size_t i = 0; i < m_logEntries.size(); ++i)
275 m_logEntries.m_HashMap[m_logEntries[i]] = i;
278 // build revision graph
279 CArray<ogdf::node> nodes;
280 GraphicsDevice dev;
281 dev.pDC = this->GetDC();
282 dev.graphics = Graphics::FromHDC(dev.pDC->m_hDC);
283 dev.graphics->SetPageUnit (UnitPixel);
285 Gdiplus::Font font(CAppUtils::GetLogFontName(), static_cast<REAL>(m_nFontSize), FontStyleRegular);
286 Rect commitString;
287 MeasureTextLength(dev, font, CString(L'8', g_Git.GetShortHASHLength()), commitString.Width, commitString.Height);
289 m_HeadNode = nullptr;
291 for (const auto& hash : m_logEntries)
293 auto nd = m_Graph.newNode();
294 nodes.Add(nd);
295 SetNodeRect(dev, font, commitString, &nd, hash);
296 if (hash == m_HeadHash)
297 m_HeadNode = nd;
300 for (size_t i = 0; i < m_logEntries.size(); ++i)
302 const GitRev& rev = m_logEntries.GetGitRevAt(i);
303 for (size_t j = 0; j < rev.m_ParentHash.size(); ++j)
305 auto parentId = m_logEntries.m_HashMap.find(rev.m_ParentHash[j]);
306 if (parentId == m_logEntries.m_HashMap.end())
308 TRACE(L"Can't found parent node");
309 //new parent node as new node
310 auto nd = this->m_Graph.newNode();
311 m_Graph.newEdge(nodes[i], nd);
312 m_logEntries.push_back(rev.m_ParentHash[j]);
313 m_logEntries.m_HashMap[rev.m_ParentHash[j]] = m_logEntries.size() - 1;
314 nodes.Add(nd);
315 SetNodeRect(dev, font, commitString, &nd, rev.m_ParentHash[j]);
317 }else
319 TRACE(L"edge %d - %d\n",i, m_logEntries.m_HashMap[rev.m_ParentHash[j]]);
320 m_Graph.newEdge(nodes[i], nodes[parentId->second]);
325 //this->m_OHL.layerDistance(30.0);
326 //this->m_OHL.nodeDistance(25.0);
327 //this->m_OHL.weightBalancing(0.8);
329 m_SugiyamLayout.call(m_GraphAttr);
331 ogdf::node v;
332 double xmax = 0;
333 double ymax = 0;
334 forall_nodes(v,m_Graph)
336 double x = m_GraphAttr.x(v) + m_GraphAttr.width(v)/2;
337 double y = m_GraphAttr.y(v) + m_GraphAttr.height(v)/2;
338 if(x>xmax)
339 xmax = x;
340 if(y>ymax)
341 ymax = y;
344 this->m_GraphRect.top=m_GraphRect.left=0;
345 m_GraphRect.bottom = static_cast<LONG>(ymax);
346 m_GraphRect.right = static_cast<LONG>(xmax);
348 return true;
351 bool CRevisionGraphWnd::IsUpdateJobRunning() const
353 return (updateJob.get() != nullptr) && !updateJob->IsDone();
356 bool CRevisionGraphWnd::GetShowOverview() const
358 return m_bShowOverview;
361 void CRevisionGraphWnd::SetShowOverview (bool value)
363 m_bShowOverview = value;
364 if (m_bShowOverview)
365 BuildPreview();
367 CString CRevisionGraphWnd::GetFriendRefName(ogdf::node v) const
369 if (!v)
370 return CString();
371 const CGitHash& hash = this->m_logEntries[v->index()];
372 if (const auto refsIt = m_HashMap.find(hash); refsIt == m_HashMap.end())
373 return hash.ToString();
374 else
375 return refsIt->second[0];
378 STRING_VECTOR CRevisionGraphWnd::GetFriendRefNames(ogdf::node v, const CString* exclude, CGit::REF_TYPE* onlyRefType) const
380 if (!v)
381 return STRING_VECTOR();
382 const CGitHash& hash = m_logEntries[v->index()];
383 if (const auto refsIt = m_HashMap.find(hash); refsIt == m_HashMap.end())
384 return STRING_VECTOR();
385 else
387 STRING_VECTOR list;
388 for (const auto& ref: refsIt->second)
390 CGit::REF_TYPE refType;
391 CString shortName = CGit::GetShortName(ref, &refType);
392 if (exclude && *exclude == shortName)
393 continue;
394 if (!onlyRefType)
395 list.push_back(ref);
396 else if (*onlyRefType == refType)
397 list.push_back(shortName);
399 return list;
403 void CRevisionGraphWnd::CompareRevs(const CString& revTo)
405 ASSERT(m_SelectedEntry1);
406 ASSERT(!revTo.IsEmpty() || m_SelectedEntry2);
408 bool alternativeTool = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
410 CString sCmd;
412 sCmd.Format(L"/command:showcompare %s /revision1:%s /revision2:%s",
413 this->m_sPath.IsEmpty() ? L"" : static_cast<LPCTSTR>(L"/path:\"" + this->m_sPath + L'"'),
414 static_cast<LPCTSTR>(GetFriendRefName(m_SelectedEntry1)),
415 !revTo.IsEmpty() ? static_cast<LPCTSTR>(revTo) : static_cast<LPCTSTR>(GetFriendRefName(m_SelectedEntry2)));
417 if (alternativeTool)
418 sCmd += L" /alternative";
420 CAppUtils::RunTortoiseGitProc(sCmd);
423 void CRevisionGraphWnd::UnifiedDiffRevs(bool bHead)
425 ASSERT(m_SelectedEntry1);
426 ASSERT(bHead || m_SelectedEntry2);
428 bool alternativeTool = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
429 CAppUtils::StartShowUnifiedDiff(m_hWnd, CString(), GetFriendRefName(m_SelectedEntry1), CString(),
430 bHead ? L"HEAD" : GetFriendRefName(m_SelectedEntry2),
431 alternativeTool);
434 void CRevisionGraphWnd::DoZoom (float fZoomFactor, bool updateScrollbars)
436 float oldzoom = m_fZoomFactor;
437 m_fZoomFactor = fZoomFactor;
439 m_nFontSize = max(1, int(DEFAULT_ZOOM_FONT * fZoomFactor));
440 if (m_nFontSize < SMALL_ZOOM_FONT_THRESHOLD)
441 m_nFontSize = min(static_cast<int>(SMALL_ZOOM_FONT_THRESHOLD), int(SMALL_ZOOM_FONT * fZoomFactor));
443 if (updateScrollbars)
445 SCROLLINFO si1 = {sizeof(SCROLLINFO), SIF_ALL};
446 GetScrollInfo(SB_VERT, &si1);
447 SCROLLINFO si2 = {sizeof(SCROLLINFO), SIF_ALL};
448 GetScrollInfo(SB_HORZ, &si2);
450 InitView();
452 si1.nPos = int(float(si1.nPos)*m_fZoomFactor/oldzoom);
453 si2.nPos = int(float(si2.nPos)*m_fZoomFactor/oldzoom);
454 SetScrollPos (SB_VERT, si1.nPos);
455 SetScrollPos (SB_HORZ, si2.nPos);
458 Invalidate (FALSE);