Make detection of empty repo also work after a git gc
[vng.git] / src / hunks / ChangeSet.cpp
blob8378f3c0f4217d239fbc3914ace3958de463f92c
1 /*
2 * This file is part of the vng project
3 * Copyright (C) 2008-2009 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 "ChangeSet.h"
20 #include "../GitRunner.h"
21 #include "../Logger.h" // for debugging level only
22 #include "../AbstractCommand.h"
23 #include "../Vng.h"
25 #include <QProcess>
26 #include <QThread>
27 #include <QMutexLocker>
28 #include <QMutex>
29 #include <QWaitCondition>
30 #include <QDebug>
32 class HunksFetcher : public QThread
34 public:
35 HunksFetcher(const QList<File> &files, ChangeSet &changeSet, bool changeSetOnIndex)
36 : m_files(files),
37 m_changeSet(changeSet),
38 m_changeSetOnIndex(changeSetOnIndex),
39 m_interrupted(false)
43 void run()
45 setPriority(QThread::LowPriority);
46 foreach(File file, m_files) {
47 if (m_interrupted)
48 break;
49 m_changeSet.lockFile(file);
51 file.fetchHunks(m_changeSetOnIndex);
53 if (!file.isBinary() && file.count() == 0 && !file.hasChanged()) { // No change in file at all.
54 Logger::debug() << "file: `" << QString::fromUtf8(file.oldFileName()) << "' => `" << QString::fromUtf8(file.fileName()) << "'\n";
55 Logger::debug() << " +- Unchanged file, skipping\n";
56 m_changeSet.removeFile(file);
57 QProcess git;
58 QStringList arguments;
59 arguments << "update-index" << "-q" << "--refresh" << file.fileName();
60 GitRunner runner(git, arguments);
61 runner.start(GitRunner::WaitUntilFinished);
62 continue;
64 if (file.fileName().isEmpty() || file.oldFileName().isEmpty())
65 file.setProtectionAcceptance(Vng::Accepted);
67 int i=0;
68 if (Logger::verbosity() >= Logger::Debug) {
69 Logger::debug() << "file: `" << QString::fromUtf8(file.oldFileName()) << "' => `" << QString::fromUtf8(file.fileName()) << "'\n";
70 if (file.isBinary())
71 Logger::debug() << " +- is a binary file" << endl;
72 Logger::debug() << " +- " << file.oldProtection() << " => " << file.protection() << endl;
73 foreach(Hunk h, file.hunks()) {
74 Logger::debug() << " +-(" << i++ << ") @" << h.lineNumber() << "; " << h.patch().size() << " bytes\n";
75 for(int i = 0; i < h.subHunkCount(); i++) {
76 Logger::debug() << " " << i <<"/"<< h.subHunkCount() <<"; "<< h.subHunk(i).size() <<" bytes\n";
81 m_changeSet.lockFile(File());
82 m_changeSet.allHunksFetched();
85 void interrupt()
87 m_interrupted = true;
90 private:
91 QList<File> m_files;
92 ChangeSet &m_changeSet;
93 bool m_changeSetOnIndex, m_interrupted;
96 class ChangeSet::Private
98 public:
99 Private() :
100 changeSetOnIndex(false),
101 hunksFetcher(0),
102 ref(1),
103 finishedOneHunk(true)
107 ~Private() {
108 if (hunksFetcher) {
109 hunksFetcher->interrupt();
110 hunksFetcher->wait();
111 delete hunksFetcher;
115 QList<File> files;
116 bool changeSetOnIndex; // the changesSet shows the changes of the working dir
117 QMutex fileAccessLock;
118 QWaitCondition fileAccessWaiter;
119 QWaitCondition cursorAccessWaiter;
120 QMutex cursorAccessLock;
121 File lockedFile;
122 HunksFetcher *hunksFetcher;
123 #if QT_VERSION >= 0x040400
124 QAtomicInt ref;
125 #else
126 int ref;
127 #endif
128 bool finishedOneHunk;
131 ChangeSet::ChangeSet()
132 : d(new Private())
136 ChangeSet::~ChangeSet()
138 #if QT_VERSION >= 0x040400
139 if (!d->ref.deref())
140 #else
141 if (--d->ref == 0)
142 #endif
143 delete d;
146 ChangeSet::ChangeSet(const ChangeSet &other)
147 : d(other.d)
149 #if QT_VERSION >= 0x040400
150 d->ref.ref();
151 #else
152 d->ref++;
153 #endif
156 AbstractCommand::ReturnCodes ChangeSet::fillFromDiffFile(QIODevice &file)
158 file.open(QIODevice::ReadOnly);
160 foreach (File f, readGitDiff(file))
161 addFile(f);
163 if (Logger::verbosity() >= Logger::Debug) {
164 foreach(File f, d->files) {
165 Logger::debug() << "changes in file: " << f.fileName() << endl;
166 int i=0;
167 foreach(Hunk h, f.hunks()) {
168 Logger::debug() << " +-(" << i++ << ") @" << h.lineNumber() << "; " << h.patch().size() << " bytes\n";
169 for(int i = 0; i < h.subHunkCount(); i++) {
170 Logger::debug() << " " << i <<"/"<< h.subHunkCount() <<"; "<< h.subHunk(i).size() <<" bytes\n";
174 Logger::debug().flush();
176 return AbstractCommand::Ok;
179 AbstractCommand::ReturnCodes ChangeSet::fillFromCurrentChanges(const QStringList &paths, bool doGenerateHunks )
181 d->changeSetOnIndex = true;
182 QDir refs(".git/refs/heads");
183 bool emptyRepo = refs.count() == 2; // only '.' and '..'
184 if (emptyRepo) {
185 QFile refs(QLatin1String(".git/packed-refs"));
186 emptyRepo = !refs.exists();
188 if (emptyRepo) { // all files added are new, just add all.
189 QProcess git;
190 QStringList arguments;
191 arguments << "ls-files" << "-s";
192 GitRunner runner(git, arguments);
193 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
194 if (rc)
195 return rc;
196 char buf[1024];
197 while(true) {
198 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
199 if (lineLength == -1)
200 break;
201 File file;
202 file.setProtection(QString::fromAscii(buf, 6));
203 file.setSha1(QString::fromAscii(buf+7, 40));
204 file.setFileName(File::escapeGitFilename(QByteArray(buf + 50, lineLength - 51)));
205 d->files.append(file);
207 return AbstractCommand::Ok;
210 // TODO the below misses the usecase of vng add and then a filesystem rm. Use diff-index --cached to show those.
211 QProcess git;
212 QStringList arguments;
213 arguments << "diff-index" << "-M" << "HEAD";
214 if (! paths.isEmpty())
215 arguments << "--" << paths;
217 // for each line
218 // new file:
219 // :000000 100644 0000000000000000000000000000000000000000 8c3ae1d344f18b23c3bdde5d26658b70b03c65d9 A bar
220 // rename main.cpp => notmain.cpp
221 // :100644 100644 e58cfe72cb7a9559a0090886bea5b0ce00db6b47 477c729e5abbd701eb708df563e7e4f749b50435 R074 main.cpp notmain.cpp
222 // normal change
223 // :100644 100644 0bdd73e9ea0ba026f6796799946c4bfc9dd1b0b8 0000000000000000000000000000000000000000 M test
225 GitRunner runner(git, arguments);
226 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
227 if (rc)
228 return rc;
229 char buf[1024];
230 while(true) {
231 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
232 if (lineLength == -1)
233 break;
234 if (lineLength > 0 && buf[0] != ':') // not a diff line, ignore.
235 continue;
237 File file;
238 file.setOldProtection(QString::fromAscii(buf+1, 6));
239 file.setProtection(QString::fromAscii(buf+8, 6));
240 file.setOldSha1(QString::fromAscii(buf+15, 40));
241 file.setSha1(QString::fromAscii(buf+56, 40));
242 int offset = 98;
243 while (buf[offset] != '\t' && offset < lineLength)
244 offset++;
245 if (buf[97] == 'R') { // rename
246 int tab = offset + 1;
247 while (buf[tab] != '\t' && tab < lineLength)
248 tab++;
249 file.setOldFileName(File::escapeGitFilename(QByteArray(buf + offset + 1, tab - offset - 1)));
250 file.setFileName(File::escapeGitFilename(QByteArray(buf + tab + 1, lineLength - tab - 2)));
252 else if (buf[97] == 'C') { // Copied file
253 int tab = offset + 1;
254 while (buf[tab] != '\t' && tab < lineLength)
255 tab++;
256 QByteArray filename(buf + offset + 1, tab - offset - 1);
257 filename = File::escapeGitFilename(filename);
258 file.setOldFileName(filename);
259 file.setFileName(filename);
261 else {
262 QByteArray filename(buf + offset + 1, lineLength - offset - 2);
263 filename = File::escapeGitFilename(filename);
264 if (buf[97] != 'A') // Add
265 file.setOldFileName(filename);
266 if (buf[97] != 'D') // Delete
267 file.setFileName(filename);
269 d->files.append(file);
271 if (doGenerateHunks)
272 generateHunks();
274 // call git-diff-files which will find all files that have been removed from the filesystem but are in the index.
275 // Since we don't like the index we just remove them from the index here.
276 arguments.clear();
277 arguments << "diff-files";
278 if (! paths.isEmpty())
279 arguments << "--" << paths;
280 runner.setArguments(arguments);
281 rc = runner.start(GitRunner::WaitForStandardOutput);
282 if (rc)
283 return rc;
284 arguments.clear();
285 arguments << "update-index" << "--remove";
286 while(true) {
287 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
288 if (lineLength == -1)
289 break;
290 if (lineLength > 0 && buf[0] != ':') // not a diff line, ignore.
291 continue;
292 if (lineLength < 97 ||buf[97] != 'D')
293 continue;
295 int offset = 98;
296 while (buf[offset] != '\t' && offset < lineLength)
297 offset++;
298 arguments.append(QString::fromUtf8(File::escapeGitFilename(QByteArray(buf + offset + 1, lineLength - offset - 2))));
300 if (arguments.count() > 2) {
301 runner.setArguments(arguments);
302 runner.start(GitRunner::WaitUntilFinished);
305 return AbstractCommand::Ok;
308 void ChangeSet::generateHunks()
310 Q_ASSERT(d->hunksFetcher == 0);
311 d->hunksFetcher = new HunksFetcher(d->files, *this, d->changeSetOnIndex);
312 d->finishedOneHunk = false;
313 d->hunksFetcher->start();
316 void ChangeSet::lockFile(const File &file)
318 // qDebug() << "ChangeSet::lockFile";
319 QMutexLocker ml(&d->fileAccessLock);
320 d->lockedFile = file;
321 if (d->files.count() == 0 || d->files.at(0) != file) { // as soon as we have done a file, we can start the interaction
322 // qDebug() << " unlock cursorAccessWaiter";
323 d->cursorAccessLock.lock();
324 d->finishedOneHunk = true;
325 d->cursorAccessWaiter.wakeAll();
326 d->cursorAccessLock.unlock();
329 d->fileAccessWaiter.wakeAll();
330 // qDebug() << "~ChangeSet::lockFile";
333 void ChangeSet::removeFile(const File &file)
335 QMutexLocker ml(&d->fileAccessLock);
336 // TODO move the cursor if this file is the current file.
337 d->files.removeAll(file);
338 d->fileAccessWaiter.wakeAll();
341 void ChangeSet::allHunksFetched()
343 // qDebug() << "ChangeSet::allHunksFetched";
344 QMutexLocker ml(&d->fileAccessLock);
345 d->lockedFile = File();
346 d->fileAccessWaiter.wakeAll();
347 // qDebug() << "~ChangeSet::allHunksFetched";
350 bool ChangeSet::hasAllHunks() const
352 return d->hunksFetcher == 0 || d->hunksFetcher->isFinished();
355 // static
356 QList<File> ChangeSet::readGitDiff(QIODevice &git, File *fileToDiff)
358 class States {
359 public:
360 States(QIODevice &device, File *fileToDiff)
361 : m_input(device),
362 m_fileToDiff(fileToDiff),
363 m_state(Empty)
367 void start() {
368 Q_ASSERT(m_filesInDiff.isEmpty()); // only call start once, please ;)
369 m_state = Empty;
370 while(true) {
371 qint64 lineLength = Vng::readLine(&m_input, buf, sizeof(buf));
372 if (lineLength == -1 || m_state == Empty)
373 storeFile();
375 if (lineLength == -1)
376 break;
377 if (addLine(QString::fromLocal8Bit(buf, lineLength)))
378 break;
379 if (m_state == InPatch) {
380 QByteArray array(buf, lineLength);
381 m_hunk.addLine(array);
384 m_input.close();
386 // try to find out if there are renames
387 foreach (File addedFile, m_newFiles) {
388 foreach (File removedFile, m_removedFiles) {
389 if (!addedFile.sha1().isEmpty() && removedFile.oldSha1() == addedFile.sha1()) {
390 // TODO if this is a partial sha1 we may want to check some of the content
391 m_removedFiles.removeAll(removedFile);
392 addedFile.setOldSha1(removedFile.oldSha1());
393 addedFile.setOldFileName(removedFile.oldFileName());
394 addedFile.setOldProtection(removedFile.oldProtection());
395 break;
399 m_filesInDiff << m_newFiles;
400 m_filesInDiff << m_removedFiles;
401 m_newFiles.clear();
402 m_removedFiles.clear();
405 QList<File> results() const {
406 return m_filesInDiff;
408 private:
409 enum State {
410 InPatch,
411 Empty,
412 InHeader
415 void storeFile() {
416 m_file.addHunk(m_hunk);
417 m_hunk = Hunk();
418 if (m_file.isValid()) {
419 if (m_file.oldFileName().isEmpty())
420 m_newFiles << m_file;
421 else if (m_file.fileName().isEmpty())
422 m_removedFiles << m_file;
423 else
424 m_filesInDiff << m_file;
426 if (m_fileToDiff)
427 m_file = File(*m_fileToDiff);
428 else
429 m_file = File();
430 m_state = Empty;
433 // returns true if we should exit.
434 bool addLine(const QString &line) {
435 const bool newfile = line.startsWith("--- /dev/null");
436 if (line.length() > 6 && (newfile || line.startsWith("--- a/")
437 || line.startsWith("--- \"a/"))) {
438 if (m_state == InPatch)
439 storeFile();
440 m_state = InHeader;
441 if (!newfile && m_fileToDiff == 0) {
442 if (line[4].unicode() == '"') { // git-encoding...
443 QByteArray array(buf + 7, strlen(buf) - 8);
444 array.prepend('"');
445 m_file.setOldFileName(File::escapeGitFilename(array));
446 } else {
447 QByteArray array(buf + 6, strlen(buf) - 7);
448 m_file.setOldFileName(File::escapeGitFilename(array));
452 else if (m_fileToDiff == 0 && line.length() > 6 &&
453 (line.startsWith("+++ b/") || line.startsWith("+++ \"b"))) {
454 if (m_state == InPatch)
455 storeFile();
456 m_state = InHeader;
457 if (line[4].unicode() == '"') { // git-encoding...
458 QByteArray array(buf + 7, strlen(buf) - 8);
459 array.prepend('"');
460 m_file.setFileName(File::escapeGitFilename(array));
461 } else {
462 m_file.setFileName(QByteArray(buf + 6, strlen(buf) - 7));
465 else if (line.length() > 5 && line.startsWith("@@ -")) {
466 m_file.addHunk(m_hunk);
467 m_hunk = Hunk();
468 m_state = InPatch;
470 else if (line.startsWith("diff --git ")) {
471 if (m_state == InPatch)
472 storeFile();
473 m_state = InHeader;
475 else if (line.startsWith("Binary files ") && line.indexOf(" differ") > 0) {
476 Q_ASSERT(m_fileToDiff);
477 m_fileToDiff->setBinary(true);
478 return true;
480 else if (line.startsWith("index ")) {
481 if (m_state == InPatch)
482 storeFile();
483 m_state = InHeader;
484 int dot = line.indexOf(QLatin1Char('.'), 6);
485 if (dot > 0 && line.length() > dot+3) {
486 m_file.setOldSha1(line.mid(6, dot-6));
487 int space = line.indexOf(QLatin1Char(' '), dot);
488 if (space == -1) {
489 space = line.length()-1; // cut off the linefeed
490 } else {
491 m_file.setProtection(line.mid(space+1).trimmed());
493 m_file.setSha1(line.mid(dot+2, space - dot - 2));
496 else if (line.startsWith("deleted file mode ")) {
497 if (m_state == InPatch)
498 storeFile();
499 m_state = InHeader;
500 m_file.setProtection(line.mid(18).trimmed());
502 else if (line.startsWith("new file mode ")) {
503 if (m_state == InPatch)
504 storeFile();
505 m_state = InHeader;
506 m_file.setProtection(line.mid(13).trimmed());
508 return false;
511 QList<File> m_filesInDiff;
512 QList<File> m_newFiles;
513 QList<File> m_removedFiles;
515 QIODevice &m_input;
516 File *m_fileToDiff;
517 State m_state;
518 File m_file;
519 Hunk m_hunk;
520 char buf[10240];
523 States stateMachine(git, fileToDiff);
524 stateMachine.start();
525 return stateMachine.results();
528 void ChangeSet::addFile(const File &file)
530 if (file.isValid())
531 d->files << file;
534 int ChangeSet::count() const
536 return d->files.count();
539 void ChangeSet::writeDiff(QIODevice &outDevice, ChangeSet::Selection selection) const
541 waitFinishGenerateHunks();
542 outDevice.open(QIODevice::WriteOnly | QIODevice::Truncate);
543 QDataStream diff(&outDevice);
544 foreach(File file, d->files) {
545 if ((selection == AllHunks
546 || (selection == UserSelection && file.renameAcceptance() == Vng::Accepted))
547 && !file.oldFileName().isEmpty() && !file.fileName().isEmpty()
548 && file.oldFileName() != file.fileName()) {
549 writeRenameDiff(diff, file);
550 continue;
552 if (file.isBinary() && (selection == AllHunks || (selection == UserSelection
553 && file.binaryChangeAcceptance() == Vng::Accepted))) {
554 diff.writeRawData("diff --git a/", 13);
555 QByteArray fileName = file.oldFileName();
556 if (fileName.isEmpty())
557 fileName = file.fileName();
558 diff.writeRawData(fileName.data(), fileName.size());
559 diff.writeRawData(" b/", 3);
560 fileName = file.fileName();
561 if (fileName.isEmpty())
562 fileName = file.oldFileName();
563 diff.writeRawData(fileName.data(), fileName.size());
564 if (file.oldFileName().isEmpty()) {
565 diff.writeRawData("\nnew file mode ", 15);
566 QByteArray protection = file.protection().toLatin1();
567 diff.writeRawData(protection.data(), protection.size());
568 } else if (file.fileName().isEmpty()) {
569 diff.writeRawData("\ndeleted file mode ", 19);
570 QByteArray protection = file.oldProtection().toLatin1();
571 diff.writeRawData(protection.data(), protection.size());
573 diff.writeRawData("\nindex ", 7);
574 QByteArray sha1 = file.oldSha1().toLatin1();
575 diff.writeRawData(sha1.data(), sha1.size());
576 diff.writeRawData("..", 2);
577 sha1 = file.sha1().toLatin1();
578 diff.writeRawData(sha1.data(), sha1.size());
579 diff.writeRawData("\nGIT binary patch\n", 18);
580 //file.writeBinaryDataAsPatch(outDevice); // TODO :)
581 diff.writeRawData("literal 0\nHcmV?d00001\n\n", 23);
582 continue;
585 bool fileHeaderWritten = false;
586 foreach(Hunk hunk, file.hunks()) {
587 if (selection == AllHunks
588 || (selection == UserSelection
589 && (hunk.acceptance() == Vng::Accepted || hunk.acceptance() == Vng::MixedAcceptance))
590 || (selection == InvertedUserSelection && hunk.acceptance() != Vng::Accepted)) {
591 if (!fileHeaderWritten) {
592 if (file.oldFileName().isEmpty()) { // new file
593 diff.writeRawData("--- /dev/null", 13);
594 } else {
595 diff.writeRawData("--- a/", 6);
596 diff.writeRawData(file.oldFileName().data(), file.oldFileName().size());
598 if (file.fileName().isEmpty()) { // deleted file
599 diff.writeRawData("\n+++ /dev/null\n", 15);
600 } else {
601 diff.writeRawData("\n+++ b/", 7);
602 diff.writeRawData(file.fileName().data(), file.fileName().size());
603 diff.writeRawData("\n", 1);
605 fileHeaderWritten = true;
607 QByteArray acceptedPatch;
608 if (selection == InvertedUserSelection)
609 acceptedPatch =hunk.rejectedPatch();
610 else if (selection == AllHunks) {
611 acceptedPatch = hunk.header();
612 acceptedPatch.append(hunk.patch());
614 else
615 acceptedPatch = hunk.acceptedPatch();
616 diff.writeRawData(acceptedPatch.data(), acceptedPatch.size());
620 outDevice.close();
623 void ChangeSet::writeRenameDiff(QDataStream &out, const File &file) const
625 out.writeRawData("diff --git a/", 13);
626 out.writeRawData(file.fileName().data(), file.fileName().size());
627 out.writeRawData(" b/", 3);
628 out.writeRawData(file.fileName().data(), file.fileName().size());
629 out.writeRawData("\nnew file mode ", 15);
630 QByteArray tmp = file.protection().toLatin1();
631 out.writeRawData(tmp.data(), tmp.length());
632 out.writeRawData("\nindex 0000000..", 16);
633 tmp = file.sha1().toLatin1();
634 out.writeRawData(tmp.data(), 7);
635 out.writeRawData("\n--- /dev/null\n+++ b/", 21);
636 out.writeRawData(file.fileName().data(), file.fileName().size());
638 QProcess git;
639 QStringList arguments;
640 arguments << "cat-file" << "blob" << file.sha1();
641 GitRunner runner(git, arguments);
642 runner.start(GitRunner::WaitForStandardOutput);
643 QList<QByteArray> lines;
644 char buf[4096];
645 while(true) {
646 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
647 if (lineLength == -1)
648 break;
649 lines.append(QByteArray(buf, lineLength));
652 QByteArray countAsString = QString::number(lines.count()).toLatin1();
653 out.writeRawData("\n@@ -0,0 +1,", 12);
654 out.writeRawData(countAsString.data(), countAsString.length());
655 out.writeRawData(" @@\n", 4);
656 foreach (const QByteArray &line, lines) {
657 out.writeRawData("+", 1);
658 out.writeRawData(line.data(), line.length());
661 // removed file.
662 out.writeRawData("\ndiff --git a/", 14);
663 out.writeRawData(file.oldFileName().data(), file.oldFileName().size());
664 out.writeRawData(" b/", 3);
665 out.writeRawData(file.oldFileName().data(), file.oldFileName().size());
666 out.writeRawData("\ndeleted file mode ", 19);
667 tmp = file.oldProtection().toLatin1();
668 out.writeRawData(tmp.data(), tmp.length());
669 out.writeRawData("\nindex ", 7);
670 tmp = file.oldSha1().toLatin1();
671 out.writeRawData(tmp.data(), 7);
672 out.writeRawData("..0000000\n--- a/", 16);
673 out.writeRawData(file.oldFileName().data(), file.oldFileName().size());
675 out.writeRawData("\n+++ /dev/null\n@@ -1,", 21);
676 out.writeRawData(countAsString.data(), countAsString.length());
677 out.writeRawData(" +0,0 @@\n", 9);
679 foreach (const QByteArray &line, lines) {
680 out.writeRawData("-", 1);
681 out.writeRawData(line.data(), line.length());
685 bool ChangeSet::hasAcceptedChanges() const
687 waitFinishGenerateHunks();
688 foreach(File file, d->files) {
689 if (file.renameAcceptance() == Vng::Accepted && file.fileName() != file.oldFileName())
690 return true;
691 if (file.protectionAcceptance() == Vng::Accepted && file.protection() != file.oldProtection())
692 return true;
693 if (file.isBinary() && file.binaryChangeAcceptance() == Vng::Accepted)
694 return true;
695 foreach(Hunk hunk, file.hunks()) {
696 Vng::Acceptance a = hunk.acceptance();
697 if (a == Vng::Accepted || a == Vng::MixedAcceptance)
698 return true;
701 return false;
704 File ChangeSet::file(int index) const
706 // qDebug() << "ChangeSet::file" << index << d->finishedOneHunk;
707 waitForFinishFirstFile();
708 QMutexLocker ml2(&d->fileAccessLock);
709 while (d->files.count() > index && d->files[index] == d->lockedFile)
710 // { qDebug() << " waiting for file to be unlocked";
711 d->fileAccessWaiter.wait(&d->fileAccessLock);
712 // }
713 if (d->files.count() <= index)
714 return File();
716 // qDebug() << "ChangeSet::~file";
717 return d->files[index];
720 ChangeSet &ChangeSet::operator=(const ChangeSet &other)
722 #if QT_VERSION >= 0x040400
723 other.d->ref.ref();
724 if (!d->ref.deref())
725 #else
726 other.d->ref++;
727 if (--d->ref == 0)
728 #endif
729 delete d;
730 d = other.d;
731 return *this;
734 void ChangeSet::waitFinishGenerateHunks() const
736 if (d->hunksFetcher)
737 d->hunksFetcher->wait();
740 void ChangeSet::waitForFinishFirstFile() const
742 d->cursorAccessLock.lock();
743 if (! d->finishedOneHunk)
744 d->cursorAccessWaiter.wait(&d->cursorAccessLock);
745 d->cursorAccessLock.unlock();