Speed up vng Add a LOT
[vng.git] / hunks / File.cpp
blob603d4a3e1167e3477afd15a8750c8a9626a30770
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 {
28 public:
29 Private() : ref(1), renameAcceptance(Vng::Undecided), protectionAcceptance(Vng::Undecided), isBinaryFile(false)
33 QString protection, oldProtection;
34 QString sha1, oldSha1;
35 QByteArray filename;
36 QByteArray oldFilename;
37 QList<Hunk> hunks;
38 int ref;
39 Vng::Acceptance renameAcceptance;
40 Vng::Acceptance protectionAcceptance;
41 bool isBinaryFile;
44 File::File()
45 : d(new Private())
49 File::File(const File &other)
50 : d(other.d)
52 d->ref++;
55 File::~File()
57 if (--d->ref == 0)
58 delete d;
61 void File::addHunk(const Hunk &hunk)
63 if (! hunk.isEmpty())
64 d->hunks << hunk;
67 QList<Hunk> File::hunks() const
69 return d->hunks;
72 bool File::isValid() const
74 return !d->filename.isEmpty();
77 void File::setFileName(const QByteArray &filename)
79 d->filename = filename;
80 d->renameAcceptance = d->oldFilename == d->filename ? Vng::Accepted : Vng::Undecided;
83 QByteArray File::fileName() const
85 return d->filename;
88 File & File::operator=(const File &other)
90 other.d->ref++;
91 if (--d->ref == 0)
92 delete d;
93 d = other.d;
94 return *this;
97 bool File::operator==(const File &other)
99 return other.d == d;
102 int File::count() const
104 return d->hunks.count();
107 void File::setOldFileName(const QByteArray &filename)
109 d->oldFilename = filename;
110 d->renameAcceptance = d->oldFilename == d->filename ? Vng::Accepted : Vng::Undecided;
113 QByteArray File::oldFileName() const
115 return d->oldFilename;
118 void File::setProtection(const QString &string)
120 d->protection = string;
121 d->protectionAcceptance = string == d->oldProtection ? Vng::Accepted : Vng::Undecided;
124 void File::setOldProtection(const QString &string)
126 d->oldProtection = string;
127 d->protectionAcceptance = string == d->protection ? Vng::Accepted : Vng::Undecided;
130 QString File::protection() const
132 return d->protection;
135 QString File::oldProtection() const
137 return d->oldProtection;
141 void File::setOldSha1(const QString &string)
143 d->oldSha1 = string;
146 void File::setSha1(const QString &string)
148 d->sha1 = string;
151 QString File::oldSha1() const
153 return d->oldSha1;
156 QString File::sha1() const
158 return d->sha1;
161 bool File::hasChanged() const
163 return count() || d->protection != d->oldProtection ||
164 d->filename != d->oldFilename;
167 void File::setRenameAcceptance(Vng::Acceptance accepted)
169 if (d->filename != d->oldFilename)
170 d->renameAcceptance = accepted;
173 void File::setProtectionAcceptance(Vng::Acceptance accepted)
175 if (d->protection != d->oldProtection)
176 d->protectionAcceptance = accepted;
179 Vng::Acceptance File::renameAcceptance() const
181 return d->renameAcceptance;
184 Vng::Acceptance File::protectionAcceptance() const
186 return d->protectionAcceptance;
189 QFile::Permissions File::permissions() const
191 QFile::Permissions perms;
192 if (d->protection.length() != 6)
193 return perms;
194 Q_ASSERT(d->protection.length() == 6);
195 const int user = d->protection.mid(3,1).toInt();
196 if (user & 1) perms |= QFile::ExeOwner;
197 if (user & 2) perms |= QFile::WriteOwner;
198 if (user & 4) perms |= QFile::ReadOwner;
199 const int group = d->protection.mid(4,1).toInt();
200 if (group & 1) perms |= QFile::ExeGroup;
201 if (group & 2) perms |= QFile::WriteGroup;
202 if (group & 4) perms |= QFile::ReadGroup;
203 const int other = d->protection.mid(5,1).toInt();
204 if (other & 1) perms |= QFile::ExeOther;
205 if (other & 2) perms |= QFile::WriteOther;
206 if (other & 4) perms |= QFile::ReadOther;
207 return perms;
210 int File::linesAdded() const
212 int total = 0;
213 foreach(Hunk hunk, d->hunks)
214 total += hunk.linesAdded();
215 return total;
218 int File::linesRemoved() const
220 int total = 0;
221 foreach(Hunk hunk, d->hunks)
222 total += hunk.linesRemoved();
223 return total;
226 void File::setBinary(bool binary)
228 d->isBinaryFile = binary;
231 bool File::isBinary() const
233 return d->isBinaryFile;
236 void File::fetchHunks(bool againstHead)
238 if (d->hunks.count())
239 return;
240 if (d->oldSha1 == d->sha1)
241 return; // no change.
243 class DiffCreator {
244 public:
245 DiffCreator(File file) : m_file(file) { }
246 void wholeFileAsDiff(bool added)
248 QProcess git;
249 QStringList arguments;
250 arguments << "cat-file" << "blob";
251 if (added)
252 arguments << m_file.sha1();
253 else
254 arguments << m_file.oldSha1();
255 GitRunner runner(git, arguments);
256 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
257 const char * prefix = (added ? "+" : "-");
258 if (rc == AbstractCommand::Ok) {
259 char buf[1024];
260 Hunk hunk; // its one hunk.
261 QByteArray header("@@ -1,0 +1,0 @@", 15);
262 hunk.addLine(header);
263 while(true) {
264 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
265 if (lineLength == -1)
266 break;
267 QByteArray array(prefix, 1);
268 array.append(buf);
269 hunk.addLine(array);
271 m_file.addHunk(hunk);
275 private:
276 File m_file;
279 if (fileName().isEmpty()) { // deleted file.
280 DiffCreator dc(*this);
281 dc.wholeFileAsDiff(false);
282 return;
285 QStringList arguments;
286 if (sha1().startsWith("0000000") || oldSha1().startsWith("0000000")) {
287 // if this is changed without ever being added to the index, sha1() is 000.
288 // if added as a new file oldSha1() is 000
289 if (againstHead)
290 arguments << "diff-index" << "-p" << "HEAD" << fileName();
291 else {
292 DiffCreator dc(*this);
293 dc.wholeFileAsDiff(true);
294 return;
297 else
298 arguments << "diff" << oldSha1() << sha1();
299 QProcess git;
300 GitRunner runner(git, arguments);
301 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
302 if (rc)
303 return;
304 ChangeSet::readGitDiff(git, this);
307 void File::outputWhatsChanged(QTextStream &out, Configuration &config, bool printSummary, bool unified)
309 const bool deleted = d->filename.isEmpty() && !d->oldFilename.isEmpty();
310 const bool added = !deleted && !d->filename.isEmpty() && d->oldFilename.isEmpty();
311 const bool renamed = !deleted && !added && d->filename != d->oldFilename;
312 if (printSummary) {
313 if (deleted)
314 out <<"D ";
315 else if (added)
316 out <<"A ";
317 else if (renamed)
318 out << "R ";
319 else
320 out << "M ";
322 if (deleted || renamed)
323 out << d->oldFilename;
324 else
325 out << d->filename;
326 if (renamed)
327 out << " => " << d->filename;
329 int removed = linesRemoved();
330 int added = linesAdded();
331 if (removed)
332 out << " -" << QString::number(removed);
333 if (added)
334 out << " +" << QString::number(added);
335 out << endl;
336 return;
339 if (deleted) { // file deleted.
340 if (!unified) {
341 config.colorize(out);
342 out <<"rmfile ";
343 config.normalColor(out);
344 out << d->oldFilename << endl;
347 else if (added) { // file added.
348 if (!unified) {
349 config.colorize(out);
350 out <<"addfile ";
351 config.normalColor(out);
352 out << d->filename << endl;
355 else {
356 if(renamed) { // rename
357 if (!unified) config.colorize(out);
358 out <<"move ";
359 if (!unified) config.normalColor(out);
360 out << "`" << d->oldFilename << "' `" << d->filename << "'" << endl;
362 if (oldProtection() != protection()) {
363 if (!unified) config.colorize(out);
364 out <<"mode change ";
365 if (!unified) config.normalColor(out);
366 out << d->filename << " " << oldProtection() << " => " << protection() << endl;
369 if (isBinary()) { // will not have any hunks
370 if (!unified) config.colorize(out);
371 out << "binary modification ";
372 if (!unified) config.normalColor(out);
373 out << d->filename << endl;
374 return;
376 if (unified) {
377 out << "--- ";
378 if (d->oldFilename.isEmpty())
379 out << "/dev/null\n";
380 else
381 out << d->oldFilename << endl;
382 out << "--- ";
383 if (d->filename.isEmpty())
384 out << "/dev/null\n";
385 else
386 out << d->filename << endl;
388 foreach(Hunk hunk, hunks()) {
389 if (unified) {
390 out << QString(hunk.header());
391 out << QString(hunk.patch());
392 continue;
394 for (int i = 0; i < hunk.subHunkCount(); i++) {
395 config.colorize(out);
396 out <<"hunk ";
397 QByteArray patch = hunk.subHunk(i);
398 config.normalColor(out);
399 out << d->filename <<" "<< QString::number(hunk.lineNumber(i)) << endl;
400 if (patch.contains((char) 0)) { // binary
401 config.colorize(out);
402 out << "binary data\n";
403 config.normalColor(out);
405 else {
406 QString string (patch);
407 const bool trailingLinefeed = string.length() > 1 && string[string.length()-1].unicode() == '\n';
408 if (config.colorTerm() && trailingLinefeed)
409 string = string.left(string.length()-1);
410 out << string;
411 if (config.colorTerm() && (!trailingLinefeed || string[string.length()-1].isSpace())) {
412 config.colorize2(out);
413 out << "$\n";
414 config.normalColor(out);
416 else
417 out << "\n";
423 // static
424 bool File::fileKnownToGit(const QString &path)
426 return fileKnownToGit(QFileInfo(path));
429 // static
430 bool File::fileKnownToGit(const QFileInfo &path)
432 QStringList arguments;
433 arguments << "ls-files" << path.filePath();
434 QProcess git;
435 GitRunner runner(git, arguments);
436 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
437 if (rc != AbstractCommand::Ok)
438 return false;
439 bool unknownFile = true;
440 char buf[1024];
441 while(true) {
442 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
443 if (lineLength == -1)
444 break;
445 unknownFile = false; // lets assume here that the script just doesn't print anything if its not added yet.
446 break;
448 git.waitForFinished();
449 return !unknownFile;