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
25 #include "TortoiseGitBlame.h"
26 #include "CommonAppUtils.h"
27 #include "TortoiseGitBlameDoc.h"
28 #include "TortoiseGitBlameData.h"
30 #include "EditGotoDlg.h"
31 #include "LoglistUtils.h"
32 #include "FileTextLines.h"
33 #include "UnicodeUtils.h"
34 #include "MenuEncode.h"
37 #include "StringUtils.h"
40 wchar_t WideCharSwap2(wchar_t nValue
)
42 return (((nValue
>> 8)) | (nValue
<< 8));
45 // CTortoiseGitBlameData construction/destruction
47 CTortoiseGitBlameData::CTortoiseGitBlameData()
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
)
66 if (type
== CFileTextLines::UTF8
)
69 if (type
== CFileTextLines::UTF16_LE
)
71 if (type
== CFileTextLines::UTF16_LEBOM
)
77 if (type
== CFileTextLines::UTF16_BE
)
79 if (type
== CFileTextLines::UTF16_BEBOM
)
88 int CTortoiseGitBlameData::GetEncode(int *bomoffset
)
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
);
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
;
112 int originalLineNumber
= 0;
113 int finalLineNumber
= 0;
114 int numberOfSubsequentLines
= 0;
118 bool expectHash
= true;
119 while (pos
>= 0 && (size_t)pos
< data
.size())
125 int lineEnd
= data
.findData((const BYTE
*)"\n", 1, lineBegin
);
127 lineEnd
= (int)data
.size();
129 if (lineEnd
> lineBegin
)
131 if (data
[lineBegin
] != '\t')
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
));
162 numberOfSubsequentLines
= 0;
169 numberOfSubsequentLines
= 0;
172 auto it
= hashToFilename
.find(hash
);
173 if (it
!= hashToFilename
.end())
174 filename
= it
->second
;
182 numberOfSubsequentLines
= 0;
187 int tokenBegin
= lineBegin
;
188 int tokenEnd
= data
.findData((const BYTE
*)" ", 1, tokenBegin
);
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
));
200 r
.first
->second
= filename
;
209 // remove <TAB> at start
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
;
224 for (auto it
= hashes
.begin(), it_end
= hashes
.end(); it
!= it_end
; ++it
)
230 pRev
= GetRevForHash(HashToRev
, hash
);
234 MessageBox(nullptr, _T("Could not get revision by hash \"") + hash
.ToString() + _T("\".\nlibgit reported:\n") + CString(e
), _T("TortoiseGit"), MB_OK
);
239 authors
.push_back(pRev
->GetAuthorName());
240 dates
.push_back(CLoglistUtils::FormatDateAndTime(pRev
->GetAuthorDate(), dateFormat
, true, bRelativeTimes
));
244 authors
.push_back(CString());
245 dates
.push_back(CString());
250 m_OriginalLineNumbers
.swap(originalLineNumbers
);
251 m_Filenames
.swap(filenames
);
252 m_RawLines
.swap(rawLines
);
254 m_Authors
.swap(authors
);
256 // reset detected and applied encoding
261 int CTortoiseGitBlameData::UpdateEncoding(int encode
)
263 int encoding
= encode
;
268 for (auto it
= m_RawLines
.begin(); it
!= m_RawLines
.end(); ++it
)
271 all
.append(&(*it
)[0], it
->size());
273 encoding
= GetEncode(&all
[0], (int)all
.size(), &bomoffset
);
276 if (encoding
!= m_encode
)
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
];
289 if (!rawLine
.empty())
291 if (encoding
== 1201)
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
);
304 line
.ReleaseBuffer();
306 lineUtf8
= CUnicodeUtils::GetUTF8(line
);
308 else if (encoding
== 1200)
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)
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
));
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
;
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
)
349 if (m_Hash
[line
] == CommitHash
&& findNoMatch
)
351 if (line
== startline
+ 2)
356 line
= FindFirstLineInBlock(CommitHash
, line
);
368 static int FindAsciiLower(const CStringA
&str
, const CStringA
&find
)
370 if (find
.GetLength() == 0)
373 for (int i
= 0; i
< str
.GetLength(); ++i
)
376 c
+= (c
>= 'A' && c
<= 'Z') ? 32 : 0;
381 for (int j
= i
+ 1; j
< str
.GetLength() && k
< find
.GetLength(); ++j
, ++k
)
384 d
+= (d
>= 'A' && d
<= 'Z') ? 32 : 0;
392 if (!diff
&& k
== find
.GetLength())
400 static int FindUtf8Lower(const CStringA
& strA
, bool allAscii
, const CString
&findW
, const CStringA
&findA
)
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
)
420 CString
whatNormalized(what
);
423 whatNormalized
.MakeLower();
426 CStringA whatNormalizedUtf8
= CUnicodeUtils::GetUTF8(whatNormalized
);
431 int numberOfLines
= GetNumberOfLines();
432 if (line
< 0 || line
+ 1 >= numberOfLines
)
439 if (m_Authors
[i
].Find(whatNormalized
) >= 0)
441 else if (m_Utf8Lines
[i
].Find(whatNormalizedUtf8
) >=0)
446 if (CString(m_Authors
[i
]).MakeLower().Find(whatNormalized
) >= 0)
448 else if (FindUtf8Lower(m_Utf8Lines
[i
], allAscii
, whatNormalized
, whatNormalizedUtf8
) >= 0)
459 if (i
>= numberOfLines
)
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
)
477 GitRev
* CTortoiseGitBlameData::GetRevForHash(CGitHashMap
& HashToRev
, CGitHash
& hash
)
479 auto it
= HashToRev
.find(hash
);
480 if (it
== HashToRev
.end())
483 rev
.GetCommitFromHash(hash
);
484 it
= HashToRev
.insert(std::make_pair(hash
, rev
)).first
;
486 return &(it
->second
);
489 CString
CTortoiseGitBlameData::UnquoteFilename(CStringA
& s
)
494 int i_size
= s
.GetLength();
495 bool isEscaped
= false;
496 for (int i
= 1; i
< i_size
; ++i
)
501 if (c
>= '0' && c
<= '3')
505 c
= (((c
- '0') & 03) << 6) | (((s
[i
+ 1] - '0') & 07) << 3) | ((s
[i
+ 2] - '0') & 07);
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;
542 return CUnicodeUtils::GetUnicode(ret
);
545 return CUnicodeUtils::GetUnicode(s
);