Properly print the end-of-line-has-space warnings
[vng.git] / src / hunks / File.cpp
blobafb5088deeb5cc912316819864ea49628b858790
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 "File.h"
20 #include "ChangeSet.h"
21 #include "../GitRunner.h"
22 #include "../AbstractCommand.h"
24 #include <QProcess>
25 #include <QDebug>
27 class File::Private
29 public:
30 Private()
31 : ref(1),
32 renameAcceptance(Vng::Undecided),
33 protectionAcceptance(Vng::Undecided),
34 binChangeAcceptance(Vng::Undecided),
35 isBinaryFile(false)
39 QString protection, oldProtection;
40 QString sha1, oldSha1;
41 QByteArray filename;
42 QByteArray oldFilename;
43 QList<Hunk> hunks;
44 #if QT_VERSION >= 0x040400
45 QAtomicInt ref;
46 #else
47 int ref;
48 #endif
49 Vng::Acceptance renameAcceptance;
50 Vng::Acceptance protectionAcceptance;
51 Vng::Acceptance binChangeAcceptance;
52 bool isBinaryFile;
55 File::File()
56 : d(new Private())
60 File::File(const File &other)
61 : d(other.d)
63 #if QT_VERSION >= 0x040400
64 d->ref.ref();
65 #else
66 d->ref++;
67 #endif
70 File::~File()
72 #if QT_VERSION >= 0x040400
73 if (! d->ref.deref())
74 #else
75 if (--d->ref == 0)
76 #endif
77 delete d;
80 void File::addHunk(const Hunk &hunk)
82 if (! hunk.isEmpty())
83 d->hunks << hunk;
86 QList<Hunk> File::hunks() const
88 return d->hunks;
91 bool File::isValid() const
93 return !(d->filename.isEmpty() && d->oldFilename.isEmpty());
96 void File::setFileName(const QByteArray &filename)
98 d->filename = filename;
99 d->renameAcceptance = d->oldFilename == d->filename ? Vng::Accepted : Vng::Undecided;
102 QByteArray File::fileName() const
104 return d->filename;
107 File & File::operator=(const File &other)
109 #if QT_VERSION >= 0x040400
110 other.d->ref.ref();
111 if (!d->ref.deref())
112 #else
113 other.d->ref++;
114 if (--d->ref == 0)
115 #endif
116 delete d;
117 d = other.d;
118 return *this;
121 bool File::operator==(const File &other) const
123 return other.d == d;
126 bool File::operator!=(const File &other) const
128 return ! (other.d == d);
131 int File::count() const
133 return d->hunks.count();
136 void File::setOldFileName(const QByteArray &filename)
138 d->oldFilename = filename;
139 d->renameAcceptance = d->oldFilename == d->filename ? Vng::Accepted : Vng::Undecided;
142 QByteArray File::oldFileName() const
144 return d->oldFilename;
147 void File::setProtection(const QString &string)
149 d->protection = string;
150 d->protectionAcceptance = string == d->oldProtection ? Vng::Accepted : Vng::Undecided;
153 void File::setOldProtection(const QString &string)
155 d->oldProtection = string;
156 d->protectionAcceptance = string == d->protection ? Vng::Accepted : Vng::Undecided;
159 QString File::protection() const
161 return d->protection;
164 QString File::oldProtection() const
166 return d->oldProtection;
170 void File::setOldSha1(const QString &string)
172 d->oldSha1 = string;
175 void File::setSha1(const QString &string)
177 d->sha1 = string;
180 QString File::oldSha1() const
182 return d->oldSha1;
185 QString File::sha1() const
187 return d->sha1;
190 bool File::hasChanged() const
192 return count() || d->protection != d->oldProtection ||
193 d->filename != d->oldFilename;
196 void File::setRenameAcceptance(Vng::Acceptance accepted)
198 if (d->filename != d->oldFilename)
199 d->renameAcceptance = accepted;
202 void File::setProtectionAcceptance(Vng::Acceptance accepted)
204 if (d->protection != d->oldProtection)
205 d->protectionAcceptance = accepted;
208 Vng::Acceptance File::renameAcceptance() const
210 return d->renameAcceptance;
213 Vng::Acceptance File::protectionAcceptance() const
215 return d->protectionAcceptance;
218 void File::setBinaryChangeAcceptance(Vng::Acceptance accepted)
220 if (d->isBinaryFile)
221 d->binChangeAcceptance = accepted;
224 Vng::Acceptance File::binaryChangeAcceptance() const
226 return d->binChangeAcceptance;
229 QFile::Permissions File::permissions() const
231 QFile::Permissions perms;
232 if (d->protection.length() != 6)
233 return perms;
234 Q_ASSERT(d->protection.length() == 6);
235 const int user = d->protection.mid(3,1).toInt();
236 if (user & 1) perms |= QFile::ExeOwner;
237 if (user & 2) perms |= QFile::WriteOwner;
238 if (user & 4) perms |= QFile::ReadOwner;
239 const int group = d->protection.mid(4,1).toInt();
240 if (group & 1) perms |= QFile::ExeGroup;
241 if (group & 2) perms |= QFile::WriteGroup;
242 if (group & 4) perms |= QFile::ReadGroup;
243 const int other = d->protection.mid(5,1).toInt();
244 if (other & 1) perms |= QFile::ExeOther;
245 if (other & 2) perms |= QFile::WriteOther;
246 if (other & 4) perms |= QFile::ReadOther;
247 return perms;
250 int File::linesAdded() const
252 int total = 0;
253 foreach(Hunk hunk, d->hunks)
254 total += hunk.linesAdded();
255 return total;
258 int File::linesRemoved() const
260 int total = 0;
261 foreach(Hunk hunk, d->hunks)
262 total += hunk.linesRemoved();
263 return total;
266 void File::setBinary(bool binary)
268 d->isBinaryFile = binary;
271 bool File::isBinary() const
273 return d->isBinaryFile;
276 void File::fetchHunks(bool againstHead)
278 if (d->hunks.count())
279 return;
280 if (d->oldSha1 == d->sha1)
281 return; // no change.
283 class DiffCreator {
284 public:
285 DiffCreator(File file) : m_file(file) { }
286 void wholeFileAsDiff(bool added)
288 QProcess git;
289 QStringList arguments;
290 arguments << "cat-file" << "blob";
291 if (added)
292 arguments << m_file.sha1();
293 else
294 arguments << m_file.oldSha1();
295 GitRunner runner(git, arguments);
296 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
297 const char * prefix = (added ? "+" : "-");
298 if (rc == AbstractCommand::Ok) {
299 QList<QByteArray> lines;
300 char buf[8096];
301 int bytesToInspect = 100;
302 while(true) {
303 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
304 if (lineLength == -1)
305 break;
306 if (bytesToInspect > 0) { // detect binary files
307 const int max = qMin(bytesToInspect, (int) lineLength);
308 for (int i = 0; i < max; ++i) {
309 if (buf[i] == 0) {
310 m_file.setBinary(true);
311 return;
314 bytesToInspect -= max;
316 QByteArray line;
317 line.reserve(lineLength + 1);
318 line.append(prefix, 1);
319 line.append(buf, lineLength);
320 lines.append(line);
323 Hunk hunk; // its one hunk.
324 QByteArray header("@@ -1,", 6);
325 header.append(QString::number(lines.count()).toLatin1());
326 header.append(" +0,0 @@\n", 9);
327 hunk.addLine(header);
328 foreach (const QByteArray &line, lines) {
329 hunk.addLine(line);
331 m_file.addHunk(hunk);
335 private:
336 File m_file;
339 if (fileName().isEmpty()) { // deleted file.
340 DiffCreator dc(*this);
341 dc.wholeFileAsDiff(false);
342 return;
345 QStringList arguments;
346 if (sha1().startsWith("0000000") || oldSha1().startsWith("0000000")) {
347 // if this is changed without ever being added to the index, sha1() is 000.
348 // if added as a new file oldSha1() is 000
349 if (againstHead) {
350 arguments << "diff-index" << "-p" << "HEAD" << QString::fromUtf8(fileName());
351 } else {
352 DiffCreator dc(*this);
353 dc.wholeFileAsDiff(true);
354 return;
357 else
358 arguments << "diff" << oldSha1() << sha1();
359 QProcess git;
360 GitRunner runner(git, arguments);
361 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
362 if (rc)
363 return;
364 ChangeSet::readGitDiff(git, this);
367 void File::outputWhatsChanged(QTextStream &out, Configuration &config, bool printSummary, bool unified)
369 const bool deleted = d->filename.isEmpty() && !d->oldFilename.isEmpty();
370 const bool added = !deleted && !d->filename.isEmpty() && d->oldFilename.isEmpty();
371 const bool renamed = !deleted && !added && d->filename != d->oldFilename;
372 if (printSummary) {
373 if (deleted)
374 out <<"D ";
375 else if (added)
376 out <<"A ";
377 else if (renamed)
378 out << "R ";
379 else
380 out << "M ";
382 if (deleted || renamed)
383 out << QString::fromUtf8(d->oldFilename);
384 else
385 out << QString::fromUtf8(d->filename);
386 if (renamed)
387 out << " => " << QString::fromUtf8(d->filename);
389 int removed = linesRemoved();
390 int added = linesAdded();
391 if (removed)
392 out << " -" << QString::number(removed);
393 if (added)
394 out << " +" << QString::number(added);
395 out << endl;
396 return;
399 if (deleted) { // file deleted.
400 if (!unified) {
401 config.colorize(out);
402 out <<"rmfile ";
403 config.normalColor(out);
404 out << QString::fromUtf8(d->oldFilename) << endl;
407 else if (added) { // file added.
408 if (!unified) {
409 config.colorize(out);
410 out <<"addfile ";
411 config.normalColor(out);
412 out << QString::fromUtf8(d->filename) << endl;
415 else {
416 if(renamed) { // rename
417 if (!unified) config.colorize(out);
418 out <<"move ";
419 if (!unified) config.normalColor(out);
420 out << "`" << QString::fromUtf8(d->oldFilename) << "' `" << QString::fromUtf8(d->filename) << "'" << endl;
422 if (oldProtection() != protection()) {
423 if (!unified) config.colorize(out);
424 out <<"mode change ";
425 if (!unified) config.normalColor(out);
426 out << QString::fromUtf8(d->filename) << " " << oldProtection() << " => " << protection() << endl;
429 if (isBinary()) { // will not have any hunks
430 if (!unified) config.colorize(out);
431 out << "binary modification ";
432 if (!unified) config.normalColor(out);
433 out << QString::fromUtf8(d->filename) << endl;
434 return;
436 if (unified) {
437 out << "--- ";
438 if (d->oldFilename.isEmpty())
439 out << "/dev/null\n";
440 else
441 out << QString::fromUtf8(d->oldFilename) << endl;
442 out << "+++ ";
443 if (d->filename.isEmpty())
444 out << "/dev/null\n";
445 else
446 out << QString::fromUtf8(d->filename) << endl;
448 foreach(Hunk hunk, hunks()) {
449 if (unified) {
450 out << QString(hunk.header());
451 out << QString(hunk.patch());
452 continue;
454 for (int i = 0; i < hunk.subHunkCount(); i++) {
455 config.colorize(out);
456 out <<"hunk ";
457 QByteArray patch = hunk.subHunk(i);
458 config.normalColor(out);
459 out << QString::fromUtf8(d->filename) <<" "<< QString::number(hunk.lineNumber(i)) << endl;
460 if (patch.contains((char) 0)) { // binary
461 config.colorize(out);
462 out << "binary data\n";
463 config.normalColor(out);
465 else {
466 // per line.
467 const char *data = patch.constData();
468 int lineStart = 0;
469 for (int index = 0;index < patch.count(); ++index) {
470 if (patch[index] == '\n' || patch[index] == '\r') { // next line!
471 QString string = QString::fromLocal8Bit(data + lineStart, index - lineStart);
472 out << string;
473 if (config.colorTerm() && string[string.length()-1].isSpace()) {
474 config.colorize2(out);
475 out << "$\n";
476 config.normalColor(out);
477 } else {
478 out << "\n";
480 lineStart = index+1;
488 void File::cleanHunksData()
490 d->hunks.clear();
493 Vng::Acceptance File::changesAcceptance()
495 class AccaptenceManager {
496 public:
497 AccaptenceManager() : m_init(false), m_result(Vng::Accepted) { }
498 void add(Vng::Acceptance a) {
499 if (!m_init) {
500 m_result = a;
501 m_init = true;
502 return;
504 if (m_result != a)
505 m_result = Vng::MixedAcceptance;
506 // do I need something special for undecided?
508 Vng::Acceptance result() { return m_result; }
509 private:
510 bool m_init;
511 Vng::Acceptance m_result;
513 AccaptenceManager am;
515 if (d->protection != d->oldProtection)
516 am.add(d->protectionAcceptance);
517 if (d->filename != d->oldFilename)
518 am.add(d->renameAcceptance);
519 if (d->isBinaryFile)
520 am.add(d->binChangeAcceptance);
521 foreach (Hunk hunk, d->hunks) {
522 am.add(hunk.acceptance());
523 if (am.result() == Vng::MixedAcceptance)
524 break;
526 return am.result();
529 // static
530 bool File::fileKnownToGit(const QString &path)
532 return fileKnownToGit(QFileInfo(path));
535 // static
536 bool File::fileKnownToGit(const QFileInfo &path)
538 QStringList arguments;
539 arguments << "ls-files" << path.filePath();
540 QProcess git;
541 GitRunner runner(git, arguments);
542 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
543 if (rc != AbstractCommand::Ok)
544 return false;
545 bool unknownFile = true;
546 char buf[1024];
547 while(true) {
548 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
549 if (lineLength == -1)
550 break;
551 unknownFile = false; // lets assume here that the script just doesn't print anything if its not added yet.
552 break;
554 git.waitForFinished();
555 return !unknownFile;
558 // static
559 QByteArray File::escapeGitFilename(const QByteArray &fileName)
561 // find any backslash escape chars and resolve them
562 // we have 3 options;
563 // \123 -> 1 byte resolving that octal number
564 // \x -> just remove the backslash
565 // \n -> special char like the newline, resolve them
566 if (fileName.isEmpty() || fileName[0] != '"')
567 return fileName;
569 QByteArray answer;
570 const int length = fileName.length();
571 int firstEscapedChar = -1;
572 int destIndex = 0;
573 for (int index = 1; index < length; ++index) {
574 const char byte = fileName[index];
575 if (firstEscapedChar < 0 && byte == '\\') {
576 firstEscapedChar = index;
577 } else if (firstEscapedChar >= 0) {
578 if (byte >= '0' && byte <= '9') { // decode octal num
579 if (index - firstEscapedChar < 3) // need more digitis
580 continue;
581 bool ok;
582 const int num = QString::fromAscii(fileName.constData() + firstEscapedChar + 1, index - firstEscapedChar).toInt(&ok, 8);
583 Q_ASSERT(num <= 256);
584 answer[destIndex++] = num;
585 firstEscapedChar = -1;
586 } else {
587 answer[destIndex++] = byte;
588 firstEscapedChar = -1;
590 } else if (index < length-1) { // last one is a double quote
591 answer[destIndex++] = byte;
594 answer.resize(destIndex);
595 return answer;