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
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()
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
)
55 if (type
== CFileTextLines::UTF8
)
58 if (type
== CFileTextLines::UTF16_LE
)
60 if (type
== CFileTextLines::UTF16_LEBOM
)
66 if (type
== CFileTextLines::UTF16_BE
)
68 if (type
== CFileTextLines::UTF16_BEBOM
)
77 int CTortoiseGitBlameData::GetEncode(int *bomoffset
)
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
);
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
;
101 int originalLineNumber
= 0;
102 int finalLineNumber
= 0;
103 int numberOfSubsequentLines
= 0;
107 bool expectHash
= true;
108 while (pos
>= 0 && (size_t)pos
< data
.size())
117 int lineEnd
= data
.find('\n', lineBegin
);
119 lineEnd
= (int)data
.size();
121 if (lineEnd
> lineBegin
)
123 if (data
[lineBegin
] != '\t')
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
));
154 numberOfSubsequentLines
= 0;
161 numberOfSubsequentLines
= 0;
164 auto it
= hashToFilename
.find(hash
);
165 if (it
!= hashToFilename
.end())
166 filename
= it
->second
;
174 numberOfSubsequentLines
= 0;
179 int tokenBegin
= lineBegin
;
180 int tokenEnd
= data
.find(' ', tokenBegin
);
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
));
192 r
.first
->second
= filename
;
201 // remove <TAB> at start
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
;
216 for (auto it
= hashes
.begin(), it_end
= hashes
.end(); it
!= it_end
; ++it
)
218 CGitHash hash2
= *it
;
220 GitRev
* pRev
= GetRevForHash(HashToRev
, hash2
, &err
);
223 authors
.push_back(pRev
->GetAuthorName());
224 dates
.push_back(CLoglistUtils::FormatDateAndTime(pRev
->GetAuthorDate(), dateFormat
, true, bRelativeTimes
));
228 MessageBox(nullptr, err
, _T("TortoiseGit"), MB_ICONERROR
);
229 authors
.push_back(CString());
230 dates
.push_back(CString());
235 m_OriginalLineNumbers
.swap(originalLineNumbers
);
236 m_Filenames
.swap(filenames
);
237 m_RawLines
.swap(rawLines
);
239 m_Authors
.swap(authors
);
241 // reset detected and applied encoding
246 int CTortoiseGitBlameData::UpdateEncoding(int encode
)
248 int encoding
= encode
;
253 for (auto it
= m_RawLines
.begin(); it
!= m_RawLines
.end(); ++it
)
256 all
.append(&(*it
)[0], it
->size());
258 encoding
= GetEncode(&all
[0], (int)all
.size(), &bomoffset
);
261 if (encoding
!= m_encode
)
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;
274 if (!rawLine
.empty())
276 if (encoding
== 1201)
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
);
289 line
.ReleaseBuffer();
291 lineUtf8
= CUnicodeUtils::GetUTF8(line
);
293 else if (encoding
== 1200)
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)
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
));
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
;
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
)
334 if (m_Hash
[line
] == CommitHash
&& findNoMatch
)
336 if (line
== startline
+ 2)
341 line
= FindFirstLineInBlock(CommitHash
, line
);
353 static int FindAsciiLower(const CStringA
&str
, const CStringA
&find
)
358 for (int i
= 0; i
< str
.GetLength(); ++i
)
361 c
+= (c
>= 'A' && c
<= 'Z') ? 32 : 0;
366 for (int j
= i
+ 1; j
< str
.GetLength() && k
< find
.GetLength(); ++j
, ++k
)
369 d
+= (d
>= 'A' && d
<= 'Z') ? 32 : 0;
377 if (!diff
&& k
== find
.GetLength())
385 static int FindUtf8Lower(const CStringA
& strA
, bool allAscii
, const CString
&findW
, const CStringA
&findA
)
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
)
405 CString
whatNormalized(what
);
408 whatNormalized
.MakeLower();
411 CStringA whatNormalizedUtf8
= CUnicodeUtils::GetUTF8(whatNormalized
);
413 int numberOfLines
= GetNumberOfLines();
415 if (direction
== SearchPrevious
)
419 i
= numberOfLines
- 1;
421 else if (line
< 0 || line
+ 1 >= numberOfLines
)
428 if (m_Authors
[i
].Find(whatNormalized
) >= 0)
430 else if (m_Utf8Lines
[i
].Find(whatNormalizedUtf8
) >=0)
435 if (CString(m_Authors
[i
]).MakeLower().Find(whatNormalized
) >= 0)
437 else if (FindUtf8Lower(m_Utf8Lines
[i
], allAscii
, whatNormalized
, whatNormalizedUtf8
) >= 0)
441 if (direction
== SearchNext
)
444 if (i
>= numberOfLines
)
447 else if (direction
== SearchPrevious
)
451 i
= numberOfLines
- 2;
458 bool CTortoiseGitBlameData::ContainsOnlyFilename(const CString
&filename
) const
460 for (auto it
= m_Filenames
.cbegin(); it
!= m_Filenames
.cend(); ++it
)
468 GitRevLoglist
* CTortoiseGitBlameData::GetRevForHash(CGitHashMap
& HashToRev
, CGitHash
& hash
, CString
* err
)
470 auto it
= HashToRev
.find(hash
);
471 if (it
== HashToRev
.end())
474 if (rev
.GetCommitFromHash(hash
))
476 *err
= rev
.GetLastErr();
479 it
= HashToRev
.insert(std::make_pair(hash
, rev
)).first
;
481 return &(it
->second
);
484 CString
CTortoiseGitBlameData::UnquoteFilename(CStringA
& s
)
489 int i_size
= s
.GetLength();
490 bool isEscaped
= false;
491 for (int i
= 1; i
< i_size
; ++i
)
496 if (c
>= '0' && c
<= '3')
500 c
= (((c
- '0') & 03) << 6) | (((s
[i
+ 1] - '0') & 07) << 3) | ((s
[i
+ 2] - '0') & 07);
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;
537 return CUnicodeUtils::GetUnicode(ret
);
540 return CUnicodeUtils::GetUnicode(s
);