Add support for non-latin1 filenames
[vng.git] / src / hunks / HunksCursor.cpp
blob9045eacadcc7fbd472d24cdee6bd36f5048b0422
1 /*
2 * This file is part of the vng project
3 * Copyright (C) 2008 Thomas Zander <tzander@trolltech.com>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "HunksCursor.h"
20 #include "ChangeSet.h"
22 #include <QDebug>
24 HunksCursor::HunksCursor(ChangeSet &changeSet)
25 : m_changeSet(changeSet),
26 m_totalHunks(-1),
27 m_fileIndex(0),
28 m_hunkIndex(0),
29 m_subHunkIndex(0)
31 changeSet.waitForFinishFirstFile();
32 if (changeSet.count()) {
33 // make sure the first hunk is an actual change
34 File file = changeSet.file(0);
35 if (file.fileName() == file.oldFileName())
36 forward(ItemScope, true);
38 else {
39 m_totalHunks = 0;
43 HunksCursor::~HunksCursor()
47 int HunksCursor::forward(Scope scope, bool skipAnswered)
49 if (m_changeSet.count() == 0 || m_fileIndex >= m_changeSet.count())
50 return currentIndex();
52 struct Last {
53 Last(const ChangeSet &changeSet)
54 : hunk(0), subHunk(0)
56 file = changeSet.count() - 1;
57 if (! changeSet.hasAllHunks())
58 return;
59 File f = changeSet.file(file);
60 hunk = f.count() - 1;
61 if (hunk >= 0) {
62 Hunk h = f.hunks()[hunk];
63 hunk += 2;
64 subHunk = h.subHunkCount() - 1;
67 int file, hunk, subHunk;
69 Last last(m_changeSet);
70 if (m_fileIndex > last.file) {
71 m_fileIndex = last.file +1;
72 m_hunkIndex = 0;
73 m_subHunkIndex = 0;
74 return currentIndex(); // already at end.
77 switch(scope) {
78 case ItemScope: {
79 if (++m_subHunkIndex < currentSubHunkCount())
80 break;
82 --m_subHunkIndex;
83 File file = m_changeSet.file(m_fileIndex);
84 if (m_hunkIndex + 1 - 2 >= file.count() && !(m_hunkIndex == 1 && file.isBinary()))
85 return forward(BlockScope, skipAnswered);
86 else {
87 m_hunkIndex++;
88 m_subHunkIndex = 0;
90 break;
92 case BlockScope:
93 m_fileIndex++;
94 m_hunkIndex = 0;
95 m_subHunkIndex = 0;
96 if (m_fileIndex > last.file)
97 skipAnswered = false; // there is nothing left;
98 break;
99 case FullRange:
100 m_fileIndex = last.file + 1;
101 m_hunkIndex = 0;
102 m_subHunkIndex = 0;
103 break;
106 if (! isValid())
107 return currentIndex();
109 File file = m_changeSet.file(m_fileIndex);
110 const bool renamed = file.fileName() != file.oldFileName();
111 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
112 if (m_hunkIndex == 0 && !renamed)
113 m_hunkIndex++;
114 if (m_hunkIndex == 1 && !protectionChanged)
115 return forward(ItemScope, skipAnswered);
117 if (skipAnswered) {
118 if ((m_hunkIndex == 0 && file.renameAcceptance() != Vng::Undecided)
119 || (m_hunkIndex == 1 && file.protectionAcceptance() != Vng::Undecided))
120 return forward(ItemScope, skipAnswered);
122 if (m_hunkIndex == 2 && file.isBinary())
124 else if (m_hunkIndex >= 2) { // a hunk;
125 Hunk hunk = file.hunks()[m_hunkIndex - 2];
126 Vng::Acceptance a = hunk.acceptance();
127 if (a == Vng::Accepted || a == Vng::Rejected || hunk.acceptance(m_subHunkIndex) != Vng::Undecided) {
128 if (hunk.subHunkCount() == m_subHunkIndex + 1)
129 return forward(BlockScope, skipAnswered);
130 return forward(ItemScope, skipAnswered);
134 return currentIndex();
137 int HunksCursor::back(Scope scope)
139 if (m_changeSet.count() == 0
140 || (m_fileIndex == 0 && m_hunkIndex == 0 && m_subHunkIndex == 0))
141 return currentIndex();
142 switch(scope) {
143 case ItemScope:
144 if (--m_subHunkIndex >= 0)
145 break;
146 ++m_subHunkIndex;
147 if (--m_hunkIndex < 1) {
148 m_hunkIndex++;
149 return back(BlockScope);
151 m_subHunkIndex = currentSubHunkCount() - 1;
152 break;
153 case BlockScope:
154 if (--m_fileIndex < 0)
155 m_fileIndex = 0;
156 m_hunkIndex = m_changeSet.file(m_fileIndex).count() -1+2;
157 Q_ASSERT(m_hunkIndex >= 0);
158 m_subHunkIndex = currentSubHunkCount() - 1;
159 break;
160 case FullRange:
161 m_hunkIndex = 0;
162 m_fileIndex = 0;
163 m_subHunkIndex = 0;
164 break;
167 File file = m_changeSet.file(m_fileIndex);
168 const bool renamed = file.fileName() != file.oldFileName();
169 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
170 if (m_hunkIndex == 1 && !protectionChanged)
171 m_hunkIndex--;
172 if (m_hunkIndex == 0 && !renamed)
173 return back(BlockScope);
175 return currentIndex();
178 void HunksCursor::setResponse(bool response, Scope scope)
180 if (m_changeSet.count() == 0)
181 return;
182 const Vng::Acceptance accept = response ? Vng::Accepted : Vng::Rejected;
184 if (scope == ItemScope) {
185 File file = m_changeSet.file(m_fileIndex);
186 if (m_hunkIndex == 0) {
187 if ((!response && file.oldFileName().isEmpty())
188 || (response && file.fileName().isEmpty())) { // new file that is not added, removed file that we want added.
189 setResponse(response, BlockScope);
190 return;
192 file.setRenameAcceptance(accept);
194 else if (m_hunkIndex == 1)
195 file.setProtectionAcceptance(accept);
196 else if (m_hunkIndex == 2 && file.isBinary())
197 file.setBinaryChangeAcceptance(accept);
198 else {
199 Hunk hunk = file.hunks()[m_hunkIndex - 2];
200 hunk.setAcceptance(m_subHunkIndex, accept);
202 return;
205 int fileIndex = m_fileIndex;
206 int hunkIndex = m_hunkIndex;
208 bool firstSubHunk = m_subHunkIndex > 0;
209 do {
210 File file = m_changeSet.file(fileIndex);
211 QList<Hunk> hunks = file.hunks();
212 do {
213 if (hunkIndex == 0)
214 file.setRenameAcceptance(accept);
215 else if (hunkIndex == 1)
216 file.setProtectionAcceptance(accept);
217 else if (firstSubHunk) { // if this is the first hunk, we have to accept only the appropriate subhunks.
218 Hunk hunk = hunks[hunkIndex-2];
219 for (int i = m_subHunkIndex; i < hunk.subHunkCount(); ++i)
220 hunk.setAcceptance(i, accept);
222 else
223 hunks[hunkIndex - 2].setAcceptance(accept);
224 hunkIndex++;
225 firstSubHunk = false;
226 } while(scope >= BlockScope && hunkIndex -2 < hunks.count());
227 hunkIndex = 0;
228 fileIndex++;
229 } while(scope == FullRange && fileIndex < m_changeSet.count());
232 int HunksCursor::count()
234 if (m_totalHunks == -1 && m_changeSet.hasAllHunks())
235 forceCount();
236 return m_totalHunks;
239 void HunksCursor::forceCount()
241 if (m_totalHunks < 0) {
242 int total = 0;
243 for (int i=0; i < m_changeSet.count(); ++i) {
244 File file = m_changeSet.file(i);
245 if (file.fileName() != file.oldFileName()
246 || file.protection() != file.oldProtection()
247 || file.isBinary())
248 total++;
249 foreach(Hunk hunk, file.hunks())
250 total += hunk.subHunkCount();
252 m_totalHunks = total;
256 int HunksCursor::currentIndex() const
258 int answer = 1;
259 const int end = qMin(m_changeSet.count(), m_fileIndex+1);
260 int i=0;
261 while(i < end) {
262 File file = m_changeSet.file(i);
263 const bool renamed = file.fileName() != file.oldFileName();
264 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
265 QList<Hunk> hunks = file.hunks();
266 ++i;
267 if (i == end && m_fileIndex < m_changeSet.count()) {
268 if (m_hunkIndex > 0 && renamed)
269 answer++;
270 if (m_hunkIndex > 1 && protectionChanged)
271 answer++;
272 hunks = hunks.mid(0, m_hunkIndex - 2);
274 else {
275 if (renamed)
276 answer++;
277 if (protectionChanged)
278 answer++;
280 QList<Hunk>::Iterator iter2 = hunks.begin();
281 int index = 2;
282 while(iter2 != hunks.end()) {
283 if (isValid() && i == end && m_hunkIndex < index++)
284 break;
285 answer += (*iter2).subHunkCount();
286 ++iter2;
289 return answer + m_subHunkIndex;
292 QString HunksCursor::currentText() const
294 QByteArray bytes;
295 if (m_changeSet.count() <= m_fileIndex)
296 return QString();
297 File file = m_changeSet.file(m_fileIndex);
299 QString output;
300 QTextStream ts(&output);
301 if (m_config)
302 m_config->colorize(ts);
303 if (m_hunkIndex == 0) { // file rename
304 if (file.fileName().isEmpty())
305 ts << "remove ";
306 else if (file.oldFileName().isEmpty())
307 ts << "add ";
308 else
309 ts << "move ";
311 else if (m_hunkIndex == 1) // protections change
312 ts << "mode change ";
313 else
314 ts << "hunk ";
315 if (m_config)
316 m_config->normalColor(ts);
317 if (m_hunkIndex == 0) {
318 if (!file.oldFileName().isEmpty())
319 ts << "`" << QString::fromUtf8(file.oldFileName()) << "' ";
320 if (! file.fileName().isEmpty())
321 ts << "`" << QString::fromUtf8(file.fileName()) << "'";
323 else if (m_hunkIndex == 1)
324 ts << QString::fromUtf8(file.fileName()) << " " << file.oldProtection() << " => " << file.protection() << endl;
326 if (m_hunkIndex < 2) {
327 ts << endl;
328 ts.flush();
329 return output;
332 if (file.count() <= m_hunkIndex - 2) {
333 if (file.isBinary())
334 return "binary data\n";
335 else
336 return "no change\n";
338 Hunk hunk = file.hunks()[m_hunkIndex - 2];
339 bytes.append(QString::fromUtf8(file.fileName()));
340 bytes.append(" ");
341 bytes.append(QString::number(hunk.lineNumber(m_subHunkIndex)));
342 //bytes.append("[subhunk "+ QString::number(m_subHunkIndex + 1) +"/"+ QString::number(hunk.subHunkCount()) +"]");
343 bytes.append("\n");
345 QByteArray patch = hunk.subHunk(m_subHunkIndex);
346 //m_config.normalColor(out);
347 if (patch.contains((char) 0)) { // binary
348 //m_config.colorize(out);
349 bytes.append(QString("binary data\n"));
350 //m_config.normalColor(out);
352 else
353 bytes.append(patch);
355 ts.flush();
356 return output + QString::fromLocal8Bit(bytes);
359 QString HunksCursor::helpMessage() const
361 return QString(
362 "How to use revert...\n"
363 "y: revert this patch\n"
364 "n: don't revert it\n\n"
366 "s: don't revert the rest of the changes to this file\n"
367 "f: revert the rest of the changes to this file\n\n"
369 "d: revert selected patches, skipping all the remaining patches\n"
370 "a: revert all the remaining patches\n"
371 "q: cancel revert\n\n"
373 "j: skip to next patch\n"
374 "k: back up to previous patch\n"
375 "c: calculate number of patches\n"
376 "h or ?: show this help\n");
379 int HunksCursor::currentSubHunkCount()
381 File file = m_changeSet.file(m_fileIndex);
382 if (m_hunkIndex < 2 || file.isBinary())
383 return 1;
384 Hunk hunk = file.hunks()[m_hunkIndex - 2];
385 return hunk.subHunkCount();
388 bool HunksCursor::isValid() const
390 return m_changeSet.count() > 0 && m_changeSet.count() > m_fileIndex;
393 QString HunksCursor::allowedOptions() const
395 QString allowed = "ynsfqadjk";
396 if (m_totalHunks == -1)
397 allowed += "c";
398 return allowed;