Use IsEmpty()/empty() where possible
[TortoiseGit.git] / src / TortoiseGitBlame / TortoiseGitBlameData.cpp
blob9c3495539ef6d2b83491a8da00ac22f573512218
1 // TortoiseGitBlame - a Viewer for Git Blames
3 // Copyright (C) 2008-2015 - TortoiseGit
4 // Copyright (C) 2003 Don HO <donho@altern.org>
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 // CTortoiseGitBlameData.cpp : implementation of the CTortoiseGitBlameData class
23 #include "stdafx.h"
24 #include "TortoiseGitBlameData.h"
25 #include "LoglistUtils.h"
26 #include "FileTextLines.h"
27 #include "UnicodeUtils.h"
29 wchar_t WideCharSwap2(wchar_t nValue)
31 return (((nValue>> 8)) | (nValue << 8));
34 // CTortoiseGitBlameData construction/destruction
36 CTortoiseGitBlameData::CTortoiseGitBlameData()
38 m_encode = -1;
41 CTortoiseGitBlameData::~CTortoiseGitBlameData()
45 int CTortoiseGitBlameData::GetEncode(unsigned char *buff, int size, int *bomoffset)
47 CFileTextLines textlines;
48 CFileTextLines::UnicodeType type = textlines.CheckUnicodeType(buff, size);
50 if (type == CFileTextLines::UTF8BOM)
52 *bomoffset = 3;
53 return CP_UTF8;
55 if (type == CFileTextLines::UTF8)
56 return CP_UTF8;
58 if (type == CFileTextLines::UTF16_LE)
59 return 1200;
60 if (type == CFileTextLines::UTF16_LEBOM)
62 *bomoffset = 2;
63 return 1200;
66 if (type == CFileTextLines::UTF16_BE)
67 return 1201;
68 if (type == CFileTextLines::UTF16_BEBOM)
70 *bomoffset = 2;
71 return 1201;
74 return GetACP();
77 int CTortoiseGitBlameData::GetEncode(int *bomoffset)
79 int encoding = 0;
80 BYTE_VECTOR rawAll;
81 for (auto it = m_RawLines.begin(), it_end = m_RawLines.end(); it != it_end; ++it)
83 rawAll.append(&(*it)[0], it->size());
85 encoding = GetEncode(&rawAll[0], (int)rawAll.size(), bomoffset);
86 return encoding;
89 void CTortoiseGitBlameData::ParseBlameOutput(BYTE_VECTOR &data, CGitHashMap & HashToRev, DWORD dateFormat, bool bRelativeTimes)
91 std::map<CGitHash, CString> hashToFilename;
93 std::vector<CGitHash> hashes;
94 std::vector<int> originalLineNumbers;
95 std::vector<CString> filenames;
96 std::vector<BYTE_VECTOR> rawLines;
97 std::vector<CString> authors;
98 std::vector<CString> dates;
100 CGitHash hash;
101 int originalLineNumber = 0;
102 int finalLineNumber = 0;
103 int numberOfSubsequentLines = 0;
104 CString filename;
106 int pos = 0;
107 bool expectHash = true;
108 while (pos >= 0 && (size_t)pos < data.size())
110 if (data[pos] == 0)
112 ++pos;
113 continue;
116 int lineBegin = pos;
117 int lineEnd = data.find('\n', lineBegin);
118 if (lineEnd < 0)
119 lineEnd = (int)data.size();
121 if (lineEnd > lineBegin)
123 if (data[lineBegin] != '\t')
125 if (expectHash)
127 expectHash = false;
128 if (lineEnd - lineBegin > 40)
130 hash.ConvertFromStrA((char*)&data[lineBegin]);
132 int hashEnd = lineBegin + 40;
133 int originalLineNumberBegin = hashEnd + 1;
134 int originalLineNumberEnd = data.find(' ', originalLineNumberBegin);
135 if (originalLineNumberEnd >= 0)
137 originalLineNumber = atoi(CStringA((LPCSTR)&data[originalLineNumberBegin], originalLineNumberEnd - originalLineNumberBegin));
138 int finalLineNumberBegin = originalLineNumberEnd + 1;
139 int finalLineNumberEnd = (numberOfSubsequentLines == 0) ? data.find(' ', finalLineNumberBegin) : lineEnd;
140 if (finalLineNumberEnd >= 0)
142 finalLineNumber = atoi(CStringA((LPCSTR)&data[finalLineNumberBegin], finalLineNumberEnd - finalLineNumberBegin));
143 if (numberOfSubsequentLines == 0)
145 int numberOfSubsequentLinesBegin = finalLineNumberEnd + 1;
146 int numberOfSubsequentLinesEnd = lineEnd;
147 numberOfSubsequentLines = atoi(CStringA((LPCSTR)&data[numberOfSubsequentLinesBegin], numberOfSubsequentLinesEnd - numberOfSubsequentLinesBegin));
150 else
152 // parse error
153 finalLineNumber = 0;
154 numberOfSubsequentLines = 0;
157 else
159 // parse error
160 finalLineNumber = 0;
161 numberOfSubsequentLines = 0;
164 auto it = hashToFilename.find(hash);
165 if (it != hashToFilename.end())
166 filename = it->second;
167 else
168 filename.Empty();
170 else
172 // parse error
173 finalLineNumber = 0;
174 numberOfSubsequentLines = 0;
177 else
179 int tokenBegin = lineBegin;
180 int tokenEnd = data.find(' ', tokenBegin);
181 if (tokenEnd >= 0)
183 if (!strncmp("filename", (const char*)&data[tokenBegin], tokenEnd - tokenBegin))
185 int filenameBegin = tokenEnd + 1;
186 int filenameEnd = lineEnd;
187 CStringA filenameA = CStringA((LPCSTR)&data[filenameBegin], filenameEnd - filenameBegin);
188 filename = UnquoteFilename(filenameA);
189 auto r = hashToFilename.insert(std::make_pair(hash, filename));
190 if (!r.second)
192 r.first->second = filename;
198 else
200 expectHash = true;
201 // remove <TAB> at start
202 BYTE_VECTOR line;
203 if (lineEnd - 1 > lineBegin)
204 line.append(&data[lineBegin + 1], lineEnd-lineBegin - 1);
206 hashes.push_back(hash);
207 filenames.push_back(filename);
208 originalLineNumbers.push_back(originalLineNumber);
209 rawLines.push_back(line);
210 --numberOfSubsequentLines;
213 pos = lineEnd + 1;
216 for (auto it = hashes.begin(), it_end = hashes.end(); it != it_end; ++it)
218 CGitHash hash2 = *it;
219 CString err;
220 GitRev* pRev = GetRevForHash(HashToRev, hash2, &err);
221 if (pRev)
223 authors.push_back(pRev->GetAuthorName());
224 dates.push_back(CLoglistUtils::FormatDateAndTime(pRev->GetAuthorDate(), dateFormat, true, bRelativeTimes));
226 else
228 MessageBox(nullptr, err, _T("TortoiseGit"), MB_ICONERROR);
229 authors.push_back(CString());
230 dates.push_back(CString());
234 m_Hash.swap(hashes);
235 m_OriginalLineNumbers.swap(originalLineNumbers);
236 m_Filenames.swap(filenames);
237 m_RawLines.swap(rawLines);
239 m_Authors.swap(authors);
240 m_Dates.swap(dates);
241 // reset detected and applied encoding
242 m_encode = -1;
243 m_Utf8Lines.clear();
246 int CTortoiseGitBlameData::UpdateEncoding(int encode)
248 int encoding = encode;
249 int bomoffset = 0;
250 if (encoding==0)
252 BYTE_VECTOR all;
253 for (auto it = m_RawLines.begin(); it != m_RawLines.end(); ++it)
255 if (!it->empty())
256 all.append(&(*it)[0], it->size());
258 encoding = GetEncode(&all[0], (int)all.size(), &bomoffset);
261 if (encoding != m_encode)
263 m_encode = encoding;
265 m_Utf8Lines.resize(m_RawLines.size());
266 for (size_t i_Lines = 0; i_Lines < m_RawLines.size(); ++i_Lines)
268 const BYTE_VECTOR& rawLine = m_RawLines[i_Lines];
270 int linebomoffset = 0;
271 CStringA lineUtf8;
272 lineUtf8.Empty();
274 if (!rawLine.empty())
276 if (encoding == 1201)
278 CString line;
279 int size = (int)((rawLine.size() - linebomoffset) / 2);
280 TCHAR *buffer = line.GetBuffer(size);
281 memcpy(buffer, &rawLine[linebomoffset], sizeof(TCHAR) * size);
282 // swap the bytes to little-endian order to get proper strings in wchar_t format
283 wchar_t * pSwapBuf = buffer;
284 for (int i = 0; i < size; ++i)
286 *pSwapBuf = WideCharSwap2(*pSwapBuf);
287 ++pSwapBuf;
289 line.ReleaseBuffer();
291 lineUtf8 = CUnicodeUtils::GetUTF8(line);
293 else if (encoding == 1200)
295 CString line;
296 // the first bomoffset is 2, after that it's 1 (see issue #920)
297 // also: don't set bomoffset if called from Encodings menu (i.e. start == 42 and bomoffset == 0); bomoffset gets only set if autodetected
298 if (linebomoffset == 0 && i_Lines != 0)
300 linebomoffset = 1;
302 int size = (int)((rawLine.size() - linebomoffset) / 2);
303 TCHAR *buffer = line.GetBuffer(size);
304 memcpy(buffer, &rawLine[linebomoffset], sizeof(TCHAR) * size);
305 line.ReleaseBuffer();
307 lineUtf8 = CUnicodeUtils::GetUTF8(line);
309 else if (encoding == CP_UTF8)
310 lineUtf8 = CStringA((LPCSTR)&rawLine[linebomoffset], (int)(rawLine.size() - linebomoffset));
311 else
313 CString line = CUnicodeUtils::GetUnicode(CStringA((LPCSTR)&rawLine[linebomoffset], (int)(rawLine.size() - linebomoffset)), encoding);
314 lineUtf8 = CUnicodeUtils::GetUTF8(line);
318 m_Utf8Lines[i_Lines] = lineUtf8;
319 linebomoffset = 0;
322 return encoding;
325 int CTortoiseGitBlameData::FindNextLine(CGitHash& CommitHash, int line, bool bUpOrDown)
327 int startline = line;
328 bool findNoMatch = false;
329 while (line >= 0 && line < (int)m_Hash.size())
331 if (m_Hash[line] != CommitHash)
332 findNoMatch = true;
334 if (m_Hash[line] == CommitHash && findNoMatch)
336 if (line == startline + 2)
337 findNoMatch = false;
338 else
340 if (bUpOrDown)
341 line = FindFirstLineInBlock(CommitHash, line);
342 return line;
345 if (bUpOrDown)
346 --line;
347 else
348 ++line;
350 return -1;
353 static int FindAsciiLower(const CStringA &str, const CStringA &find)
355 if (find.IsEmpty())
356 return 0;
358 for (int i = 0; i < str.GetLength(); ++i)
360 char c = str[i];
361 c += (c >= 'A' && c <= 'Z') ? 32 : 0;
362 if (c == find[0])
364 bool diff = false;
365 int k = 1;
366 for (int j = i + 1; j < str.GetLength() && k < find.GetLength(); ++j, ++k)
368 char d = str[j];
369 d += (d >= 'A' && d <= 'Z') ? 32 : 0;
370 if (d != find[k])
372 diff = true;
373 break;
377 if (!diff && k == find.GetLength())
378 return i;
382 return -1;
385 static int FindUtf8Lower(const CStringA& strA, bool allAscii, const CString &findW, const CStringA &findA)
387 if (allAscii)
388 return FindAsciiLower(strA, findA);
390 CString strW = CUnicodeUtils::GetUnicode(strA);
391 return strW.MakeLower().Find(findW);
394 int CTortoiseGitBlameData::FindFirstLineWrapAround(SearchDirection direction, const CString& what, int line, bool bCaseSensitive)
396 bool allAscii = true;
397 for (int i = 0; i < what.GetLength(); ++i)
399 if (what[i] > 0x7f)
401 allAscii = false;
402 break;
405 CString whatNormalized(what);
406 if (!bCaseSensitive)
408 whatNormalized.MakeLower();
411 CStringA whatNormalizedUtf8 = CUnicodeUtils::GetUTF8(whatNormalized);
413 int numberOfLines = GetNumberOfLines();
414 int i = line;
415 if (direction == SearchPrevious)
417 i -= 2;
418 if (i < 0)
419 i = numberOfLines - 1;
421 else if (line < 0 || line + 1 >= numberOfLines)
422 i = 0;
426 if (bCaseSensitive)
428 if (m_Authors[i].Find(whatNormalized) >= 0)
429 return i;
430 else if (m_Utf8Lines[i].Find(whatNormalizedUtf8) >=0)
431 return i;
433 else
435 if (CString(m_Authors[i]).MakeLower().Find(whatNormalized) >= 0)
436 return i;
437 else if (FindUtf8Lower(m_Utf8Lines[i], allAscii, whatNormalized, whatNormalizedUtf8) >= 0)
438 return i;
441 if (direction == SearchNext)
443 ++i;
444 if (i >= numberOfLines)
445 i = 0;
447 else if (direction == SearchPrevious)
449 --i;
450 if (i < 0)
451 i = numberOfLines - 2;
453 } while (i != line);
455 return -1;
458 bool CTortoiseGitBlameData::ContainsOnlyFilename(const CString &filename) const
460 for (auto it = m_Filenames.cbegin(); it != m_Filenames.cend(); ++it)
462 if (filename != *it)
463 return false;
465 return true;
468 GitRevLoglist* CTortoiseGitBlameData::GetRevForHash(CGitHashMap& HashToRev, CGitHash& hash, CString* err)
470 auto it = HashToRev.find(hash);
471 if (it == HashToRev.end())
473 GitRevLoglist rev;
474 if (rev.GetCommitFromHash(hash))
476 *err = rev.GetLastErr();
477 return nullptr;
479 it = HashToRev.insert(std::make_pair(hash, rev)).first;
481 return &(it->second);
484 CString CTortoiseGitBlameData::UnquoteFilename(CStringA& s)
486 if (s[0] == '"')
488 CStringA ret;
489 int i_size = s.GetLength();
490 bool isEscaped = false;
491 for (int i = 1; i < i_size; ++i)
493 char c = s[i];
494 if (isEscaped)
496 if (c >= '0' && c <= '3')
498 if (i + 2 < i_size)
500 c = (((c - '0') & 03) << 6) | (((s[i + 1] - '0') & 07) << 3) | ((s[i + 2] - '0') & 07);
501 i += 2;
502 ret += c;
505 else
507 switch (c)
509 case 'a' : c = '\a'; break;
510 case 'b' : c = '\b'; break;
511 case 't' : c = '\t'; break;
512 case 'n' : c = '\n'; break;
513 case 'v' : c = '\v'; break;
514 case 'f' : c = '\f'; break;
515 case 'r' : c = '\r'; break;
517 ret += c;
519 isEscaped = false;
521 else
523 if (c == '\\')
525 isEscaped = true;
527 else if(c == '"')
529 break;
531 else
533 ret += c;
537 return CUnicodeUtils::GetUnicode(ret);
539 else
540 return CUnicodeUtils::GetUnicode(s);