Sync translations with Transifex
[TortoiseGit.git] / src / TortoiseGitBlame / TortoiseGitBlameData.cpp
blob8e6237a573130d8d49f84f954ebbb9da15229ec6
1 // TortoiseGitBlame - a Viewer for Git Blames
3 // Copyright (C) 2008-2013 - TortoiseGit
4 // Copyright (C) 2010-2013 Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2003 Don HO <donho@altern.org>
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // CTortoiseGitBlameData.cpp : implementation of the CTortoiseGitBlameData class
24 #include "stdafx.h"
25 #include "TortoiseGitBlame.h"
26 #include "CommonAppUtils.h"
27 #include "TortoiseGitBlameDoc.h"
28 #include "TortoiseGitBlameData.h"
29 #include "MainFrm.h"
30 #include "EditGotoDlg.h"
31 #include "LoglistUtils.h"
32 #include "FileTextLines.h"
33 #include "UnicodeUtils.h"
34 #include "MenuEncode.h"
35 #include "gitdll.h"
36 #include "SysInfo.h"
37 #include "StringUtils.h"
38 #include "TGitPath.h"
40 wchar_t WideCharSwap2(wchar_t nValue)
42 return (((nValue>> 8)) | (nValue << 8));
45 // CTortoiseGitBlameData construction/destruction
47 CTortoiseGitBlameData::CTortoiseGitBlameData()
49 m_encode = -1;
52 CTortoiseGitBlameData::~CTortoiseGitBlameData()
56 int CTortoiseGitBlameData::GetEncode(unsigned char *buff, int size, int *bomoffset)
58 CFileTextLines textlines;
59 CFileTextLines::UnicodeType type = textlines.CheckUnicodeType(buff, size);
61 if (type == CFileTextLines::UTF8BOM)
63 *bomoffset = 3;
64 return CP_UTF8;
66 if (type == CFileTextLines::UTF8)
67 return CP_UTF8;
69 if (type == CFileTextLines::UTF16_LE)
70 return 1200;
71 if (type == CFileTextLines::UTF16_LEBOM)
73 *bomoffset = 2;
74 return 1200;
77 if (type == CFileTextLines::UTF16_BE)
78 return 1201;
79 if (type == CFileTextLines::UTF16_BEBOM)
81 *bomoffset = 2;
82 return 1201;
85 return GetACP();
88 int CTortoiseGitBlameData::GetEncode(int *bomoffset)
90 int encoding = 0;
91 BYTE_VECTOR rawAll;
92 for (auto it = m_RawLines.begin(), it_end = m_RawLines.end(); it != it_end; ++it)
94 rawAll.append(&(*it)[0], it->size());
96 encoding = GetEncode(&rawAll[0], (int)rawAll.size(), bomoffset);
97 return encoding;
100 void CTortoiseGitBlameData::ParseBlameOutput(BYTE_VECTOR &data, CGitHashMap & HashToRev, DWORD dateFormat, bool bRelativeTimes)
102 std::map<CGitHash, CString> hashToFilename;
104 std::vector<CGitHash> hashes;
105 std::vector<int> originalLineNumbers;
106 std::vector<CString> filenames;
107 std::vector<BYTE_VECTOR> rawLines;
108 std::vector<CString> authors;
109 std::vector<CString> dates;
111 CGitHash hash;
112 int originalLineNumber = 0;
113 int finalLineNumber = 0;
114 int numberOfSubsequentLines = 0;
115 CString filename;
117 int pos = 0;
118 bool expectHash = true;
119 while (pos >= 0 && (size_t)pos < data.size())
121 if (data[pos] == 0)
122 continue;
124 int lineBegin = pos;
125 int lineEnd = data.findData((const BYTE*)"\n", 1, lineBegin);
126 if (lineEnd < 0)
127 lineEnd = (int)data.size();
129 if (lineEnd > lineBegin)
131 if (data[lineBegin] != '\t')
133 if (expectHash)
135 expectHash = false;
136 if (lineEnd - lineBegin > 40)
138 hash.ConvertFromStrA((char*)&data[lineBegin]);
140 int hashEnd = lineBegin + 40;
141 int originalLineNumberBegin = hashEnd + 1;
142 int originalLineNumberEnd = data.findData((const BYTE*)" ", 1, originalLineNumberBegin);
143 if (originalLineNumberEnd >= 0)
145 originalLineNumber = atoi(CStringA((LPCSTR)&data[originalLineNumberBegin], originalLineNumberEnd - originalLineNumberBegin));
146 int finalLineNumberBegin = originalLineNumberEnd + 1;
147 int finalLineNumberEnd = (numberOfSubsequentLines == 0) ? data.findData((const BYTE*)" ", 1, finalLineNumberBegin) : lineEnd;
148 if (finalLineNumberEnd >= 0)
150 finalLineNumber = atoi(CStringA((LPCSTR)&data[finalLineNumberBegin], finalLineNumberEnd - finalLineNumberBegin));
151 if (numberOfSubsequentLines == 0)
153 int numberOfSubsequentLinesBegin = finalLineNumberEnd + 1;
154 int numberOfSubsequentLinesEnd = lineEnd;
155 numberOfSubsequentLines = atoi(CStringA((LPCSTR)&data[numberOfSubsequentLinesBegin], numberOfSubsequentLinesEnd - numberOfSubsequentLinesBegin));
158 else
160 // parse error
161 finalLineNumber = 0;
162 numberOfSubsequentLines = 0;
165 else
167 // parse error
168 finalLineNumber = 0;
169 numberOfSubsequentLines = 0;
172 auto it = hashToFilename.find(hash);
173 if (it != hashToFilename.end())
174 filename = it->second;
175 else
176 filename.Empty();
178 else
180 // parse error
181 finalLineNumber = 0;
182 numberOfSubsequentLines = 0;
185 else
187 int tokenBegin = lineBegin;
188 int tokenEnd = data.findData((const BYTE*)" ", 1, tokenBegin);
189 if (tokenEnd >= 0)
191 if (!strncmp("filename", (const char*)&data[tokenBegin], tokenEnd - tokenBegin))
193 int filenameBegin = tokenEnd + 1;
194 int filenameEnd = lineEnd;
195 CStringA filenameA = CStringA((LPCSTR)&data[filenameBegin], filenameEnd - filenameBegin);
196 filename = UnquoteFilename(filenameA);
197 auto r = hashToFilename.insert(std::make_pair(hash, filename));
198 if (!r.second)
200 r.first->second = filename;
206 else
208 expectHash = true;
209 // remove <TAB> at start
210 BYTE_VECTOR line;
211 if (lineEnd - 1 > lineBegin)
212 line.append(&data[lineBegin + 1], lineEnd-lineBegin - 1);
214 hashes.push_back(hash);
215 filenames.push_back(filename);
216 originalLineNumbers.push_back(originalLineNumber);
217 rawLines.push_back(line);
218 --numberOfSubsequentLines;
221 pos = lineEnd + 1;
224 for (auto it = hashes.begin(), it_end = hashes.end(); it != it_end; ++it)
226 CGitHash hash = *it;
227 GitRev *pRev;
230 pRev = GetRevForHash(HashToRev, hash);
232 catch (char* e)
234 MessageBox(nullptr, _T("Could not get revision by hash \"") + hash.ToString() + _T("\".\nlibgit reported:\n") + CString(e), _T("TortoiseGit"), MB_OK);
235 return;
237 if (pRev)
239 authors.push_back(pRev->GetAuthorName());
240 dates.push_back(CLoglistUtils::FormatDateAndTime(pRev->GetAuthorDate(), dateFormat, true, bRelativeTimes));
242 else
244 authors.push_back(CString());
245 dates.push_back(CString());
249 m_Hash.swap(hashes);
250 m_OriginalLineNumbers.swap(originalLineNumbers);
251 m_Filenames.swap(filenames);
252 m_RawLines.swap(rawLines);
254 m_Authors.swap(authors);
255 m_Dates.swap(dates);
256 // reset detected and applied encoding
257 m_encode = -1;
258 m_Utf8Lines.clear();
261 int CTortoiseGitBlameData::UpdateEncoding(int encode)
263 int encoding = encode;
264 int bomoffset = 0;
265 if (encoding==0)
267 BYTE_VECTOR all;
268 for (auto it = m_RawLines.begin(); it != m_RawLines.end(); ++it)
270 if (!it->empty())
271 all.append(&(*it)[0], it->size());
273 encoding = GetEncode(&all[0], (int)all.size(), &bomoffset);
276 if (encoding != m_encode)
278 m_encode = encoding;
280 m_Utf8Lines.resize(m_RawLines.size());
281 for (size_t i_Lines = 0; i_Lines < m_RawLines.size(); ++i_Lines)
283 const BYTE_VECTOR& rawLine = m_RawLines[i_Lines];
285 int bomoffset = 0;
286 CStringA lineUtf8;
287 lineUtf8.Empty();
289 if (!rawLine.empty())
291 if (encoding == 1201)
293 CString line;
294 int size = (int)((rawLine.size() - bomoffset)/2);
295 TCHAR *buffer = line.GetBuffer(size);
296 memcpy(buffer, &rawLine[bomoffset], sizeof(TCHAR)*size);
297 // swap the bytes to little-endian order to get proper strings in wchar_t format
298 wchar_t * pSwapBuf = buffer;
299 for (int i = 0; i < size; ++i)
301 *pSwapBuf = WideCharSwap2(*pSwapBuf);
302 ++pSwapBuf;
304 line.ReleaseBuffer();
306 lineUtf8 = CUnicodeUtils::GetUTF8(line);
308 else if (encoding == 1200)
310 CString line;
311 // the first bomoffset is 2, after that it's 1 (see issue #920)
312 // also: don't set bomoffset if called from Encodings menu (i.e. start == 42 and bomoffset == 0); bomoffset gets only set if autodetected
313 if (bomoffset == 0 && i_Lines != 0)
315 bomoffset = 1;
317 int size = (int)((rawLine.size() - bomoffset)/2);
318 TCHAR *buffer = line.GetBuffer(size);
319 memcpy(buffer, &rawLine[bomoffset], sizeof(TCHAR) * size);
320 line.ReleaseBuffer();
322 lineUtf8 = CUnicodeUtils::GetUTF8(line);
324 else if (encoding == CP_UTF8)
325 lineUtf8 = CStringA((LPCSTR)&rawLine[bomoffset], (int)(rawLine.size() - bomoffset));
326 else
328 CString line = CUnicodeUtils::GetUnicode(CStringA((LPCSTR)&rawLine[bomoffset], (int)(rawLine.size() - bomoffset)), encoding);
329 lineUtf8 = CUnicodeUtils::GetUTF8(line);
333 m_Utf8Lines[i_Lines] = lineUtf8;
334 bomoffset = 0;
337 return encoding;
340 int CTortoiseGitBlameData::FindNextLine(CGitHash& CommitHash, int line, bool bUpOrDown)
342 int startline = line;
343 bool findNoMatch = false;
344 while (line >= 0 && line < (int)m_Hash.size())
346 if (m_Hash[line] != CommitHash)
347 findNoMatch = true;
349 if (m_Hash[line] == CommitHash && findNoMatch)
351 if (line == startline + 2)
352 findNoMatch = false;
353 else
355 if (bUpOrDown)
356 line = FindFirstLineInBlock(CommitHash, line);
357 return line;
360 if (bUpOrDown)
361 --line;
362 else
363 ++line;
365 return -1;
368 static int FindAsciiLower(const CStringA &str, const CStringA &find)
370 if (find.GetLength() == 0)
371 return 0;
373 for (int i = 0; i < str.GetLength(); ++i)
375 char c = str[i];
376 c += (c >= 'A' && c <= 'Z') ? 32 : 0;
377 if (c == find[0])
379 bool diff = false;
380 int k = 1;
381 for (int j = i + 1; j < str.GetLength() && k < find.GetLength(); ++j, ++k)
383 char d = str[j];
384 d += (d >= 'A' && d <= 'Z') ? 32 : 0;
385 if (d != find[k])
387 diff = true;
388 break;
392 if (!diff && k == find.GetLength())
393 return i;
397 return -1;
400 static int FindUtf8Lower(const CStringA& strA, bool allAscii, const CString &findW, const CStringA &findA)
402 if (allAscii)
403 return FindAsciiLower(strA, findA);
405 CString strW = CUnicodeUtils::GetUnicode(strA);
406 return strW.MakeLower().Find(findW);
409 int CTortoiseGitBlameData::FindFirstLineWrapAround(const CString& what, int line, bool bCaseSensitive)
411 bool allAscii = true;
412 for (int i = 0; i < what.GetLength(); ++i)
414 if (what[i] > 0x7f)
416 allAscii = false;
417 break;
420 CString whatNormalized(what);
421 if (!bCaseSensitive)
423 whatNormalized.MakeLower();
426 CStringA whatNormalizedUtf8 = CUnicodeUtils::GetUTF8(whatNormalized);
428 bool bFound = false;
430 int i = line;
431 int numberOfLines = GetNumberOfLines();
432 if (line < 0 || line + 1 >= numberOfLines)
433 i = 0;
437 if (bCaseSensitive)
439 if (m_Authors[i].Find(whatNormalized) >= 0)
440 bFound = true;
441 else if (m_Utf8Lines[i].Find(whatNormalizedUtf8) >=0)
442 bFound = true;
444 else
446 if (CString(m_Authors[i]).MakeLower().Find(whatNormalized) >= 0)
447 bFound = true;
448 else if (FindUtf8Lower(m_Utf8Lines[i], allAscii, whatNormalized, whatNormalizedUtf8) >= 0)
449 bFound = true;
452 if(bFound)
454 break;
456 else
458 ++i;
459 if (i >= numberOfLines)
460 i = 0;
462 } while (i != line);
464 return bFound ? i : -1;
467 bool CTortoiseGitBlameData::ContainsOnlyFilename(const CString &filename) const
469 for (auto it = m_Filenames.cbegin(); it != m_Filenames.cend(); ++it)
471 if (filename != *it)
472 return false;
474 return true;
477 GitRev* CTortoiseGitBlameData::GetRevForHash(CGitHashMap & HashToRev, CGitHash& hash)
479 auto it = HashToRev.find(hash);
480 if (it == HashToRev.end())
482 GitRev rev;
483 rev.GetCommitFromHash(hash);
484 it = HashToRev.insert(std::make_pair(hash, rev)).first;
486 return &(it->second);
489 CString CTortoiseGitBlameData::UnquoteFilename(CStringA& s)
491 if (s[0] == '"')
493 CStringA ret;
494 int i_size = s.GetLength();
495 bool isEscaped = false;
496 for (int i = 1; i < i_size; ++i)
498 char c = s[i];
499 if (isEscaped)
501 if (c >= '0' && c <= '3')
503 if (i + 2 < i_size)
505 c = (((c - '0') & 03) << 6) | (((s[i + 1] - '0') & 07) << 3) | ((s[i + 2] - '0') & 07);
506 i += 2;
507 ret += c;
510 else
512 switch (c)
514 case 'a' : c = '\a'; break;
515 case 'b' : c = '\b'; break;
516 case 't' : c = '\t'; break;
517 case 'n' : c = '\n'; break;
518 case 'v' : c = '\v'; break;
519 case 'f' : c = '\f'; break;
520 case 'r' : c = '\r'; break;
522 ret += c;
524 isEscaped = false;
526 else
528 if (c == '\\')
530 isEscaped = true;
532 else if(c == '"')
534 break;
536 else
538 ret += c;
542 return CUnicodeUtils::GetUnicode(ret);
544 else
545 return CUnicodeUtils::GetUnicode(s);