* Parses a unified diff into a list of "diff hunks" (each hunk starts with a * line starting with @@ and represents a collection of localized changes). * * @param diff a diff in git's unified diff format * @returns a list of hunk structures * * The diff is assumed to be a collection of hunks, where each hunk has the * following structure: * * METADATA * --- a/SOURCE_PATH * +++ b/T
| 239 | * generates them anyway for files with unresolved conflicts. |
| 240 | */ |
| 241 | static std::vector<DiffHunk> parseHunks(VcsDiff &diff) |
| 242 | { |
| 243 | std::vector<DiffHunk> ret; |
| 244 | int lineNo = -1; |
| 245 | QString curSrcFileName, curTgtFileName; |
| 246 | QStringListIterator lines(diff.diff().split(u'\n')); |
| 247 | while (lines.hasNext()) { |
| 248 | lineNo++; |
| 249 | auto curln = lines.next(); |
| 250 | auto m = DIFF_FILENAME_RE->match(curln); |
| 251 | if (m.hasMatch()) { |
| 252 | if (curln.startsWith(u'-')) |
| 253 | curSrcFileName = m.captured(1); |
| 254 | else if (curln.startsWith(u'+')) |
| 255 | curTgtFileName = m.captured(1); |
| 256 | continue; |
| 257 | } |
| 258 | m = HUNK_HEADER_RE->match(curln); |
| 259 | if (!m.hasMatch()) |
| 260 | continue; |
| 261 | const auto oldRange = parseRange(m.captured(1)); |
| 262 | const auto newRange = parseRange(m.captured(2)); |
| 263 | const auto heading = m.captured(3); |
| 264 | uint firstLineIdx = lineNo; |
| 265 | QStringList hunkLines; |
| 266 | while (lines.hasNext() && (CONFLICT_START_RE->match(lines.peekNext()).hasMatch() || !META_LINE_RE->match(lines.peekNext()).hasMatch())) { |
| 267 | // Consume the conflict |
| 268 | if (CONFLICT_START_RE->match(lines.peekNext()).hasMatch()) { |
| 269 | lineNo++; |
| 270 | hunkLines << lines.next(); |
| 271 | while (lines.hasNext() && !CONFLICT_END_RE->match(lines.peekNext()).hasMatch()) { |
| 272 | lineNo++; |
| 273 | hunkLines << lines.next(); |
| 274 | } |
| 275 | if (!CONFLICT_END_RE->match(lines.peekNext()).hasMatch()) { |
| 276 | qWarning("Invalid diff format, end of file reached before conflict finished"); |
| 277 | qDebug("%ls", qUtf16Printable(diff.diff())); |
| 278 | break; |
| 279 | } |
| 280 | } |
| 281 | lineNo++; |
| 282 | hunkLines << lines.next(); |
| 283 | } |
| 284 | |
| 285 | // The number of filenames present in the diff should match the number |
| 286 | // of hunks |
| 287 | ret.push_back(DiffHunk{.srcStart = oldRange.line, |
| 288 | .srcCount = oldRange.count, |
| 289 | .tgtStart = newRange.line, |
| 290 | .tgtCount = newRange.count, |
| 291 | .headingLineIdx = firstLineIdx, |
| 292 | .srcFile = curSrcFileName, |
| 293 | .tgtFile = curTgtFileName, |
| 294 | .heading = heading, |
| 295 | .lines = hunkLines}); |
| 296 | } |
| 297 | |
| 298 | // If the diff ends with a newline, for the last hunk, when splitting into lines above |
no test coverage detected