1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017, 2019-2021, 2023 - TortoiseGit
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.
21 // TortoiseGitBlameDoc.cpp : implementation of the CTortoiseGitBlameDoc class
25 #include "TortoiseGitBlame.h"
27 #include "TortoiseGitBlameDoc.h"
28 #include "GitAdminDir.h"
32 #include "TortoiseGitBlameView.h"
33 #include "CmdLineParser.h"
34 #include "CommonAppUtils.h"
35 #include "BlameDetectMovedOrCopiedLines.h"
37 #include "GitMailmap.h"
44 // CTortoiseGitBlameDoc
46 IMPLEMENT_DYNCREATE(CTortoiseGitBlameDoc
, CDocument
)
48 BEGIN_MESSAGE_MAP(CTortoiseGitBlameDoc
, CDocument
)
51 // CTortoiseGitBlameDoc construction/destruction
53 CTortoiseGitBlameDoc::CTortoiseGitBlameDoc()
57 CTortoiseGitBlameDoc::~CTortoiseGitBlameDoc()
61 BOOL
CTortoiseGitBlameDoc::OnNewDocument()
65 BOOL
CTortoiseGitBlameDoc::OnOpenDocument(LPCWSTR lpszPathName
)
67 CCmdLineParser
parser(AfxGetApp()->m_lpCmdLine
);
70 m_Rev
= parser
.GetVal(L
"rev");
71 m_lLine
= static_cast<int>(parser
.GetLongVal(L
"line"));
72 m_bFirstStartup
= false;
80 return OnOpenDocument(lpszPathName
,m_Rev
);
83 BOOL
CTortoiseGitBlameDoc::OnOpenDocument(LPCWSTR lpszPathName
, CString Rev
)
88 // enable blame for files which do not exist in current working tree
89 if (!PathFileExists(lpszPathName
) && Rev
!= L
"HEAD")
91 if (!CDocument::OnOpenDocument(CTempFiles::Instance().GetTempFilePath(true).GetWinPathString()))
96 if (!CDocument::OnOpenDocument(lpszPathName
))
100 m_CurrentFileName
= lpszPathName
;
104 // (SDI documents will reuse this document)
105 if(!g_Git
.CheckMsysGitDir())
107 CCommonAppUtils::RunTortoiseGitProc(L
" /command:settings");
111 if (!GitAdminDir::HasAdminDir(m_CurrentFileName
, &topdir
))
114 temp
.Format(IDS_CANNOTBLAMENOGIT
, static_cast<LPCWSTR
>(m_CurrentFileName
));
115 MessageBox(nullptr, temp
, L
"TortoiseGitBlame", MB_OK
| MB_ICONERROR
);
120 sOrigCWD
= g_Git
.m_CurrentDir
= topdir
;
122 CString PathName
= m_CurrentFileName
;
123 if (topdir
[topdir
.GetLength() - 1] == L
'\\' || topdir
[topdir
.GetLength() - 1] == L
'/')
124 PathName
=PathName
.Right(PathName
.GetLength()-g_Git
.m_CurrentDir
.GetLength());
126 PathName
=PathName
.Right(PathName
.GetLength()-g_Git
.m_CurrentDir
.GetLength()-1);
129 path
.SetFromWin(PathName
);
131 if(!g_Git
.m_CurrentDir
.IsEmpty())
132 SetCurrentDirectory(g_Git
.m_CurrentDir
);
136 // make sure all config files are read in order to check that none contains an error
137 g_Git
.GetConfigValue(L
"doesnot.exist");
139 // make sure git_init() works and that .git-dir is ok, even if we open a file from another working tree
140 CAutoLocker
lock(g_Git
.m_critGitDllSec
);
141 g_Git
.ForceReInitDll();
143 catch (const char* libgiterr
)
145 MessageBox(nullptr, CUnicodeUtils::GetUnicode(libgiterr
), L
"TortoiseGitBlame", MB_ICONERROR
);
150 int dwDetectMovedOrCopiedLines
= theApp
.GetInt(L
"DetectMovedOrCopiedLines", BLAME_DETECT_MOVED_OR_COPIED_LINES_DISABLED
);
151 int dwDetectMovedOrCopiedLinesNumCharactersWithinFile
= theApp
.GetInt(L
"DetectMovedOrCopiedLinesNumCharactersWithinFile", BLAME_DETECT_MOVED_OR_COPIED_LINES_NUM_CHARACTERS_WITHIN_FILE_DEFAULT
);
152 int dwDetectMovedOrCopiedLinesNumCharactersFromFiles
= theApp
.GetInt(L
"DetectMovedOrCopiedLinesNumCharactersFromFiles", BLAME_DETECT_MOVED_OR_COPIED_LINES_NUM_CHARACTERS_FROM_FILES_DEFAULT
);
153 switch(dwDetectMovedOrCopiedLines
)
156 case BLAME_DETECT_MOVED_OR_COPIED_LINES_DISABLED
:
159 case BLAME_DETECT_MOVED_OR_COPIED_LINES_WITHIN_FILE
:
160 option
.Format(L
"-M%d", dwDetectMovedOrCopiedLinesNumCharactersWithinFile
);
162 case BLAME_DETECT_MOVED_OR_COPIED_LINES_FROM_MODIFIED_FILES
:
163 option
.Format(L
"-C%d", dwDetectMovedOrCopiedLinesNumCharactersFromFiles
);
165 case BLAME_DETECT_MOVED_OR_COPIED_LINES_FROM_EXISTING_FILES_AT_FILE_CREATION
:
166 option
.Format(L
"-C -C%d", dwDetectMovedOrCopiedLinesNumCharactersFromFiles
);
168 case BLAME_DETECT_MOVED_OR_COPIED_LINES_FROM_EXISTING_FILES
:
169 option
.Format(L
"-C -C -C%d", dwDetectMovedOrCopiedLinesNumCharactersFromFiles
);
173 if (theApp
.GetInt(L
"IgnoreWhitespace", 0) == 1)
176 bool onlyFirstParent
= theApp
.GetInt(L
"OnlyFirstParent", 0) == 1;
179 CString tmpfile
= CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
180 cmd
.Format(L
"git.exe rev-list --first-parent --end-of-options %s --", static_cast<LPCWSTR
>(Rev
));
182 CAutoFILE file
= _wfsopen(tmpfile
, L
"wb", SH_DENYWR
);
185 MessageBox(nullptr, CString(MAKEINTRESOURCE(IDS_BLAMEERROR
)) + L
"\n\nCould not create temp file!", L
"TortoiseGitBlame", MB_OK
| MB_ICONERROR
);
190 if (g_Git
.Run(cmd
, [&](const CStringA
& line
)
192 fwrite(lastline
+ ' ' + line
+ '\n', sizeof(char), lastline
.GetLength() + 1 + line
.GetLength() + 1, file
);
197 MessageBox(nullptr, CString(MAKEINTRESOURCE(IDS_BLAMEERROR
)) + L
"\n\n" + err
, L
"TortoiseGitBlame", MB_OK
| MB_ICONERROR
);
200 option
.AppendFormat(L
" -S \"%s\"", static_cast<LPCWSTR
>(tmpfile
));
203 cmd
.Format(L
"git.exe blame -p %s %s -- \"%s\"", static_cast<LPCWSTR
>(option
), static_cast<LPCWSTR
>(Rev
), static_cast<LPCWSTR
>(path
.GetGitPathString()));
206 if(g_Git
.Run(cmd
, &m_BlameData
, &err
))
208 MessageBox(nullptr, CString(MAKEINTRESOURCE(IDS_BLAMEERROR
)) + L
"\n\n" + err
, L
"TortoiseGitBlame", MB_OK
| MB_ICONERROR
);
212 #ifdef USE_TEMPFILENAME
213 m_TempFileName
= CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
215 cmd
.Format(L
"git.exe cat-file blob %s:\"%s\"", static_cast<LPCWSTR
>(Rev
), static_cast<LPCWSTR
>(path
.GetGitPathString()));
217 if(g_Git
.RunLogFile(cmd
, m_TempFileName
))
220 str
.Format(IDS_CHECKOUTFAILED
, static_cast<LPCWSTR
>(path
.GetGitPathString()));
221 MessageBox(nullptr, CString(MAKEINTRESOURCE(IDS_BLAMEERROR
)) + L
"\n\n" + str
, L
"TortoiseGitBlame", MB_OK
| MB_ICONERROR
);
227 if (CGitMailmap::ShouldLoadMailmap())
228 GitRevLoglist::s_Mailmap
= std::make_shared
<CGitMailmap
>();
229 else if (GitRevLoglist::s_Mailmap
.load())
230 GitRevLoglist::s_Mailmap
.store(nullptr);
232 CTortoiseGitBlameView
*pView
=DYNAMIC_DOWNCAST(CTortoiseGitBlameView
,GetMainFrame()->GetActiveView());
235 CWnd
* pWnd
= GetMainFrame()->GetDescendantWindow(AFX_IDW_PANE_FIRST
, TRUE
);
236 if (pWnd
&& pWnd
->IsKindOf(RUNTIME_CLASS(CTortoiseGitBlameView
)))
237 pView
= static_cast<CTortoiseGitBlameView
*>(pWnd
);
243 BOOL bShowCompleteLog
= (theApp
.GetInt(L
"ShowCompleteLog", 1) == 1);
244 if (bShowCompleteLog
&& BlameIsLimitedToOneFilename(dwDetectMovedOrCopiedLines
) && !onlyFirstParent
)
246 if (GetMainFrame()->m_wndOutput
.LoadHistory(path
.GetGitPathString(), m_Rev
, (theApp
.GetInt(L
"FollowRenames", 0) == 1)))
251 std::unordered_set
<CGitHash
> hashes
;
252 pView
->m_data
.GetHashes(hashes
);
253 if (GetMainFrame()->m_wndOutput
.LoadHistory(hashes
))
257 pView
->MapLineToLogIndex();
260 pView
->GotoLine(m_lLine
);
262 SetPathName(m_CurrentFileName
, FALSE
);
268 void CTortoiseGitBlameDoc::SetPathName(LPCWSTR lpszPathName
, BOOL bAddToMRU
)
270 CDocument::SetPathName(lpszPathName
, bAddToMRU
&& (m_Rev
== L
"HEAD"));
272 this->SetTitle(CString(lpszPathName
) + L
':' + m_Rev
);
275 // CTortoiseGitBlameDoc diagnostics
278 void CTortoiseGitBlameDoc::AssertValid() const
280 CDocument::AssertValid();
283 void CTortoiseGitBlameDoc::Dump(CDumpContext
& dc
) const
290 // CTortoiseGitBlameDoc commands