Encoding fixes
[vng.git] / src / hunks / File.cpp
blob6d00d4d2203fb9b32180ade7e88162853a351c5d
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 char buf[1024];
300 Hunk hunk; // its one hunk.
301 QByteArray header("@@ -1,0 +1,0 @@", 15);
302 hunk.addLine(header);
303 while(true) {
304 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
305 if (lineLength == -1)
306 break;
307 QByteArray array(prefix, 1);
308 array.append(buf);
309 hunk.addLine(array);
311 m_file.addHunk(hunk);
315 private:
316 File m_file;
319 if (fileName().isEmpty()) { // deleted file.
320 DiffCreator dc(*this);
321 dc.wholeFileAsDiff(false);
322 return;
325 QStringList arguments;
326 if (sha1().startsWith("0000000") || oldSha1().startsWith("0000000")) {
327 // if this is changed without ever being added to the index, sha1() is 000.
328 // if added as a new file oldSha1() is 000
329 if (againstHead) {
330 arguments << "diff-index" << "-p" << "HEAD" << QString::fromUtf8(fileName());
331 } else {
332 DiffCreator dc(*this);
333 dc.wholeFileAsDiff(true);
334 return;
337 else
338 arguments << "diff" << oldSha1() << sha1();
339 QProcess git;
340 GitRunner runner(git, arguments);
341 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
342 if (rc)
343 return;
344 ChangeSet::readGitDiff(git, this);
347 void File::outputWhatsChanged(QTextStream &out, Configuration &config, bool printSummary, bool unified)
349 const bool deleted = d->filename.isEmpty() && !d->oldFilename.isEmpty();
350 const bool added = !deleted && !d->filename.isEmpty() && d->oldFilename.isEmpty();
351 const bool renamed = !deleted && !added && d->filename != d->oldFilename;
352 if (printSummary) {
353 if (deleted)
354 out <<"D ";
355 else if (added)
356 out <<"A ";
357 else if (renamed)
358 out << "R ";
359 else
360 out << "M ";
362 if (deleted || renamed)
363 out << QString::fromUtf8(d->oldFilename);
364 else
365 out << QString::fromUtf8(d->filename);
366 if (renamed)
367 out << " => " << QString::fromUtf8(d->filename);
369 int removed = linesRemoved();
370 int added = linesAdded();
371 if (removed)
372 out << " -" << QString::number(removed);
373 if (added)
374 out << " +" << QString::number(added);
375 out << endl;
376 return;
379 if (deleted) { // file deleted.
380 if (!unified) {
381 config.colorize(out);
382 out <<"rmfile ";
383 config.normalColor(out);
384 out << QString::fromUtf8(d->oldFilename) << endl;
387 else if (added) { // file added.
388 if (!unified) {
389 config.colorize(out);
390 out <<"addfile ";
391 config.normalColor(out);
392 out << QString::fromUtf8(d->filename) << endl;
395 else {
396 if(renamed) { // rename
397 if (!unified) config.colorize(out);
398 out <<"move ";
399 if (!unified) config.normalColor(out);
400 out << "`" << QString::fromUtf8(d->oldFilename) << "' `" << QString::fromUtf8(d->filename) << "'" << endl;
402 if (oldProtection() != protection()) {
403 if (!unified) config.colorize(out);
404 out <<"mode change ";
405 if (!unified) config.normalColor(out);
406 out << QString::fromUtf8(d->filename) << " " << oldProtection() << " => " << protection() << endl;
409 if (isBinary()) { // will not have any hunks
410 if (!unified) config.colorize(out);
411 out << "binary modification ";
412 if (!unified) config.normalColor(out);
413 out << QString::fromUtf8(d->filename) << endl;
414 return;
416 if (unified) {
417 out << "--- ";
418 if (d->oldFilename.isEmpty())
419 out << "/dev/null\n";
420 else
421 out << QString::fromUtf8(d->oldFilename) << endl;
422 out << "+++ ";
423 if (d->filename.isEmpty())
424 out << "/dev/null\n";
425 else
426 out << QString::fromUtf8(d->filename) << endl;
428 foreach(Hunk hunk, hunks()) {
429 if (unified) {
430 out << QString(hunk.header());
431 out << QString(hunk.patch());
432 continue;
434 for (int i = 0; i < hunk.subHunkCount(); i++) {
435 config.colorize(out);
436 out <<"hunk ";
437 QByteArray patch = hunk.subHunk(i);
438 config.normalColor(out);
439 out << QString::fromUtf8(d->filename) <<" "<< QString::number(hunk.lineNumber(i)) << endl;
440 if (patch.contains((char) 0)) { // binary
441 config.colorize(out);
442 out << "binary data\n";
443 config.normalColor(out);
445 else {
446 QString string = QString::fromLocal8Bit(patch);
447 const bool trailingLinefeed = string.length() > 1 && string[string.length()-1].unicode() == '\n';
448 if (config.colorTerm() && trailingLinefeed)
449 string = string.left(string.length()-1);
450 out << string;
451 if (config.colorTerm() && (!trailingLinefeed || string[string.length()-1].isSpace())) {
452 config.colorize2(out);
453 out << "$\n";
454 config.normalColor(out);
456 else
457 out << "\n";
463 void File::cleanHunksData()
465 d->hunks.clear();
468 Vng::Acceptance File::changesAcceptance()
470 class AccaptenceManager {
471 public:
472 AccaptenceManager() : m_init(false), m_result(Vng::Accepted) { }
473 void add(Vng::Acceptance a) {
474 if (!m_init) {
475 m_result = a;
476 m_init = true;
477 return;
479 if (m_result != a)
480 m_result = Vng::MixedAcceptance;
481 // do I need something special for undecided?
483 Vng::Acceptance result() { return m_result; }
484 private:
485 bool m_init;
486 Vng::Acceptance m_result;
488 AccaptenceManager am;
490 if (d->protection != d->oldProtection)
491 am.add(d->protectionAcceptance);
492 if (d->filename != d->oldFilename)
493 am.add(d->renameAcceptance);
494 if (d->isBinaryFile)
495 am.add(d->binChangeAcceptance);
496 foreach (Hunk hunk, d->hunks) {
497 am.add(hunk.acceptance());
498 if (am.result() == Vng::MixedAcceptance)
499 break;
501 return am.result();
504 // static
505 bool File::fileKnownToGit(const QString &path)
507 return fileKnownToGit(QFileInfo(path));
510 // static
511 bool File::fileKnownToGit(const QFileInfo &path)
513 QStringList arguments;
514 arguments << "ls-files" << path.filePath();
515 QProcess git;
516 GitRunner runner(git, arguments);
517 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
518 if (rc != AbstractCommand::Ok)
519 return false;
520 bool unknownFile = true;
521 char buf[1024];
522 while(true) {
523 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
524 if (lineLength == -1)
525 break;
526 unknownFile = false; // lets assume here that the script just doesn't print anything if its not added yet.
527 break;
529 git.waitForFinished();
530 return !unknownFile;
533 // static
534 QByteArray File::escapeGitFilename(const QByteArray &fileName)
536 // find any backslash escape chars and resolve them
537 // we have 3 options;
538 // \123 -> 1 byte resolving that octal number
539 // \x -> just remove the backslash
540 // \n -> special char like the newline, resolve them
541 if (fileName.isEmpty() || fileName[0] != '"')
542 return fileName;
544 QByteArray answer;
545 const int length = fileName.length();
546 int firstEscapedChar = -1;
547 int destIndex = 0;
548 for (int index = 1; index < length; ++index) {
549 const char byte = fileName[index];
550 if (firstEscapedChar < 0 && byte == '\\') {
551 firstEscapedChar = index;
552 } else if (firstEscapedChar >= 0) {
553 if (byte >= '0' && byte <= '9') { // decode octal num
554 if (index - firstEscapedChar < 3) // need more digitis
555 continue;
556 bool ok;
557 const int num = QString::fromAscii(fileName.constData() + firstEscapedChar + 1, index - firstEscapedChar).toInt(&ok, 8);
558 Q_ASSERT(num <= 256);
559 answer[destIndex++] = num;
560 firstEscapedChar = -1;
561 } else {
562 answer[destIndex++] = byte;
563 firstEscapedChar = -1;
565 } else if (index < length-1) { // last one is a double quote
566 answer[destIndex++] = byte;
569 answer.resize(destIndex);
570 return answer;