Be memory efficient and clear hunk data after displaying it
[vng.git] / hunks / File.cpp
blob7d0e1d5cfc0b697ef7852d6147c927a196b548cf
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 QAtomicInt 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.ref();
55 File::~File()
57 if (! d->ref.deref())
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.ref();
91 if (!d->ref.deref())
92 delete d;
93 d = other.d;
94 return *this;
97 bool File::operator==(const File &other) const
99 return other.d == d;
102 bool File::operator!=(const File &other) const
104 return ! (other.d == d);
107 int File::count() const
109 return d->hunks.count();
112 void File::setOldFileName(const QByteArray &filename)
114 d->oldFilename = filename;
115 d->renameAcceptance = d->oldFilename == d->filename ? Vng::Accepted : Vng::Undecided;
118 QByteArray File::oldFileName() const
120 return d->oldFilename;
123 void File::setProtection(const QString &string)
125 d->protection = string;
126 d->protectionAcceptance = string == d->oldProtection ? Vng::Accepted : Vng::Undecided;
129 void File::setOldProtection(const QString &string)
131 d->oldProtection = string;
132 d->protectionAcceptance = string == d->protection ? Vng::Accepted : Vng::Undecided;
135 QString File::protection() const
137 return d->protection;
140 QString File::oldProtection() const
142 return d->oldProtection;
146 void File::setOldSha1(const QString &string)
148 d->oldSha1 = string;
151 void File::setSha1(const QString &string)
153 d->sha1 = string;
156 QString File::oldSha1() const
158 return d->oldSha1;
161 QString File::sha1() const
163 return d->sha1;
166 bool File::hasChanged() const
168 return count() || d->protection != d->oldProtection ||
169 d->filename != d->oldFilename;
172 void File::setRenameAcceptance(Vng::Acceptance accepted)
174 if (d->filename != d->oldFilename)
175 d->renameAcceptance = accepted;
178 void File::setProtectionAcceptance(Vng::Acceptance accepted)
180 if (d->protection != d->oldProtection)
181 d->protectionAcceptance = accepted;
184 Vng::Acceptance File::renameAcceptance() const
186 return d->renameAcceptance;
189 Vng::Acceptance File::protectionAcceptance() const
191 return d->protectionAcceptance;
194 QFile::Permissions File::permissions() const
196 QFile::Permissions perms;
197 if (d->protection.length() != 6)
198 return perms;
199 Q_ASSERT(d->protection.length() == 6);
200 const int user = d->protection.mid(3,1).toInt();
201 if (user & 1) perms |= QFile::ExeOwner;
202 if (user & 2) perms |= QFile::WriteOwner;
203 if (user & 4) perms |= QFile::ReadOwner;
204 const int group = d->protection.mid(4,1).toInt();
205 if (group & 1) perms |= QFile::ExeGroup;
206 if (group & 2) perms |= QFile::WriteGroup;
207 if (group & 4) perms |= QFile::ReadGroup;
208 const int other = d->protection.mid(5,1).toInt();
209 if (other & 1) perms |= QFile::ExeOther;
210 if (other & 2) perms |= QFile::WriteOther;
211 if (other & 4) perms |= QFile::ReadOther;
212 return perms;
215 int File::linesAdded() const
217 int total = 0;
218 foreach(Hunk hunk, d->hunks)
219 total += hunk.linesAdded();
220 return total;
223 int File::linesRemoved() const
225 int total = 0;
226 foreach(Hunk hunk, d->hunks)
227 total += hunk.linesRemoved();
228 return total;
231 void File::setBinary(bool binary)
233 d->isBinaryFile = binary;
236 bool File::isBinary() const
238 return d->isBinaryFile;
241 void File::fetchHunks(bool againstHead)
243 if (d->hunks.count())
244 return;
245 if (d->oldSha1 == d->sha1)
246 return; // no change.
248 class DiffCreator {
249 public:
250 DiffCreator(File file) : m_file(file) { }
251 void wholeFileAsDiff(bool added)
253 QProcess git;
254 QStringList arguments;
255 arguments << "cat-file" << "blob";
256 if (added)
257 arguments << m_file.sha1();
258 else
259 arguments << m_file.oldSha1();
260 GitRunner runner(git, arguments);
261 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
262 const char * prefix = (added ? "+" : "-");
263 if (rc == AbstractCommand::Ok) {
264 char buf[1024];
265 Hunk hunk; // its one hunk.
266 QByteArray header("@@ -1,0 +1,0 @@", 15);
267 hunk.addLine(header);
268 while(true) {
269 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
270 if (lineLength == -1)
271 break;
272 QByteArray array(prefix, 1);
273 array.append(buf);
274 hunk.addLine(array);
276 m_file.addHunk(hunk);
280 private:
281 File m_file;
284 if (fileName().isEmpty()) { // deleted file.
285 DiffCreator dc(*this);
286 dc.wholeFileAsDiff(false);
287 return;
290 QStringList arguments;
291 if (sha1().startsWith("0000000") || oldSha1().startsWith("0000000")) {
292 // if this is changed without ever being added to the index, sha1() is 000.
293 // if added as a new file oldSha1() is 000
294 if (againstHead)
295 arguments << "diff-index" << "-p" << "HEAD" << fileName();
296 else {
297 DiffCreator dc(*this);
298 dc.wholeFileAsDiff(true);
299 return;
302 else
303 arguments << "diff" << oldSha1() << sha1();
304 QProcess git;
305 GitRunner runner(git, arguments);
306 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
307 if (rc)
308 return;
309 ChangeSet::readGitDiff(git, this);
312 void File::outputWhatsChanged(QTextStream &out, Configuration &config, bool printSummary, bool unified)
314 const bool deleted = d->filename.isEmpty() && !d->oldFilename.isEmpty();
315 const bool added = !deleted && !d->filename.isEmpty() && d->oldFilename.isEmpty();
316 const bool renamed = !deleted && !added && d->filename != d->oldFilename;
317 if (printSummary) {
318 if (deleted)
319 out <<"D ";
320 else if (added)
321 out <<"A ";
322 else if (renamed)
323 out << "R ";
324 else
325 out << "M ";
327 if (deleted || renamed)
328 out << d->oldFilename;
329 else
330 out << d->filename;
331 if (renamed)
332 out << " => " << d->filename;
334 int removed = linesRemoved();
335 int added = linesAdded();
336 if (removed)
337 out << " -" << QString::number(removed);
338 if (added)
339 out << " +" << QString::number(added);
340 out << endl;
341 return;
344 if (deleted) { // file deleted.
345 if (!unified) {
346 config.colorize(out);
347 out <<"rmfile ";
348 config.normalColor(out);
349 out << d->oldFilename << endl;
352 else if (added) { // file added.
353 if (!unified) {
354 config.colorize(out);
355 out <<"addfile ";
356 config.normalColor(out);
357 out << d->filename << endl;
360 else {
361 if(renamed) { // rename
362 if (!unified) config.colorize(out);
363 out <<"move ";
364 if (!unified) config.normalColor(out);
365 out << "`" << d->oldFilename << "' `" << d->filename << "'" << endl;
367 if (oldProtection() != protection()) {
368 if (!unified) config.colorize(out);
369 out <<"mode change ";
370 if (!unified) config.normalColor(out);
371 out << d->filename << " " << oldProtection() << " => " << protection() << endl;
374 if (isBinary()) { // will not have any hunks
375 if (!unified) config.colorize(out);
376 out << "binary modification ";
377 if (!unified) config.normalColor(out);
378 out << d->filename << endl;
379 return;
381 if (unified) {
382 out << "--- ";
383 if (d->oldFilename.isEmpty())
384 out << "/dev/null\n";
385 else
386 out << d->oldFilename << endl;
387 out << "--- ";
388 if (d->filename.isEmpty())
389 out << "/dev/null\n";
390 else
391 out << d->filename << endl;
393 foreach(Hunk hunk, hunks()) {
394 if (unified) {
395 out << QString(hunk.header());
396 out << QString(hunk.patch());
397 continue;
399 for (int i = 0; i < hunk.subHunkCount(); i++) {
400 config.colorize(out);
401 out <<"hunk ";
402 QByteArray patch = hunk.subHunk(i);
403 config.normalColor(out);
404 out << d->filename <<" "<< QString::number(hunk.lineNumber(i)) << endl;
405 if (patch.contains((char) 0)) { // binary
406 config.colorize(out);
407 out << "binary data\n";
408 config.normalColor(out);
410 else {
411 QString string (patch);
412 const bool trailingLinefeed = string.length() > 1 && string[string.length()-1].unicode() == '\n';
413 if (config.colorTerm() && trailingLinefeed)
414 string = string.left(string.length()-1);
415 out << string;
416 if (config.colorTerm() && (!trailingLinefeed || string[string.length()-1].isSpace())) {
417 config.colorize2(out);
418 out << "$\n";
419 config.normalColor(out);
421 else
422 out << "\n";
428 void File::cleanHunksData()
430 d->hunks.clear();
433 // static
434 bool File::fileKnownToGit(const QString &path)
436 return fileKnownToGit(QFileInfo(path));
439 // static
440 bool File::fileKnownToGit(const QFileInfo &path)
442 QStringList arguments;
443 arguments << "ls-files" << path.filePath();
444 QProcess git;
445 GitRunner runner(git, arguments);
446 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
447 if (rc != AbstractCommand::Ok)
448 return false;
449 bool unknownFile = true;
450 char buf[1024];
451 while(true) {
452 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
453 if (lineLength == -1)
454 break;
455 unknownFile = false; // lets assume here that the script just doesn't print anything if its not added yet.
456 break;
458 git.waitForFinished();
459 return !unknownFile;