Make reverting/unreverting a binary file work.
[vng.git] / src / hunks / ChangeSet.cpp
blob7f7b9cecd4b2a3e0252d44dd9ef5d92f75e173b9
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 const bool emptyRepo = refs.count() == 2; // only '.' and '..'
184 if (emptyRepo) { // all files added are new, just add all.
185 QProcess git;
186 QStringList arguments;
187 arguments << "ls-files" << "-s";
188 GitRunner runner(git, arguments);
189 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
190 if (rc)
191 return rc;
192 char buf[1024];
193 while(true) {
194 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
195 if (lineLength == -1)
196 break;
197 File file;
198 file.setProtection(QString::fromAscii(buf, 6));
199 file.setSha1(QString::fromAscii(buf+7, 40));
200 file.setFileName(File::escapeGitFilename(QByteArray(buf + 50, lineLength - 51)));
201 d->files.append(file);
203 return AbstractCommand::Ok;
206 // TODO the below misses the usecase of vng add and then a filesystem rm. Use diff-index --cached to show those.
207 QProcess git;
208 QStringList arguments;
209 arguments << "diff-index" << "-M" << "HEAD";
210 if (! paths.isEmpty())
211 arguments << "--" << paths;
213 // for each line
214 // new file:
215 // :000000 100644 0000000000000000000000000000000000000000 8c3ae1d344f18b23c3bdde5d26658b70b03c65d9 A bar
216 // rename main.cpp => notmain.cpp
217 // :100644 100644 e58cfe72cb7a9559a0090886bea5b0ce00db6b47 477c729e5abbd701eb708df563e7e4f749b50435 R074 main.cpp notmain.cpp
218 // normal change
219 // :100644 100644 0bdd73e9ea0ba026f6796799946c4bfc9dd1b0b8 0000000000000000000000000000000000000000 M test
221 GitRunner runner(git, arguments);
222 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
223 if (rc)
224 return rc;
225 char buf[1024];
226 while(true) {
227 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
228 if (lineLength == -1)
229 break;
230 if (lineLength > 0 && buf[0] != ':') // not a diff line, ignore.
231 continue;
233 File file;
234 file.setOldProtection(QString::fromAscii(buf+1, 6));
235 file.setProtection(QString::fromAscii(buf+8, 6));
236 file.setOldSha1(QString::fromAscii(buf+15, 40));
237 file.setSha1(QString::fromAscii(buf+56, 40));
238 int offset = 98;
239 while (buf[offset] != '\t' && offset < lineLength)
240 offset++;
241 if (buf[97] == 'R') { // rename
242 int tab = offset + 1;
243 while (buf[tab] != '\t' && tab < lineLength)
244 tab++;
245 file.setOldFileName(File::escapeGitFilename(QByteArray(buf + offset + 1, tab - offset - 1)));
246 file.setFileName(File::escapeGitFilename(QByteArray(buf + tab + 1, lineLength - tab - 2)));
248 else if (buf[97] == 'C') { // Copied file
249 int tab = offset + 1;
250 while (buf[tab] != '\t' && tab < lineLength)
251 tab++;
252 QByteArray filename(buf + offset + 1, tab - offset - 1);
253 filename = File::escapeGitFilename(filename);
254 file.setOldFileName(filename);
255 file.setFileName(filename);
257 else {
258 QByteArray filename(buf + offset + 1, lineLength - offset - 2);
259 filename = File::escapeGitFilename(filename);
260 if (buf[97] != 'A') // Add
261 file.setOldFileName(filename);
262 if (buf[97] != 'D') // Delete
263 file.setFileName(filename);
265 d->files.append(file);
267 if (doGenerateHunks)
268 generateHunks();
270 // call git-diff-files which will find all files that have been removed from the filesystem but are in the index.
271 // Since we don't like the index we just remove them from the index here.
272 arguments.clear();
273 arguments << "diff-files";
274 if (! paths.isEmpty())
275 arguments << "--" << paths;
276 runner.setArguments(arguments);
277 rc = runner.start(GitRunner::WaitForStandardOutput);
278 if (rc)
279 return rc;
280 arguments.clear();
281 arguments << "update-index" << "--remove";
282 while(true) {
283 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
284 if (lineLength == -1)
285 break;
286 if (lineLength > 0 && buf[0] != ':') // not a diff line, ignore.
287 continue;
288 if (lineLength < 97 ||buf[97] != 'D')
289 continue;
291 int offset = 98;
292 while (buf[offset] != '\t' && offset < lineLength)
293 offset++;
294 arguments.append(QString::fromUtf8(File::escapeGitFilename(QByteArray(buf + offset + 1, lineLength - offset - 2))));
296 if (arguments.count() > 2) {
297 runner.setArguments(arguments);
298 runner.start(GitRunner::WaitUntilFinished);
301 return AbstractCommand::Ok;
304 void ChangeSet::generateHunks()
306 Q_ASSERT(d->hunksFetcher == 0);
307 d->hunksFetcher = new HunksFetcher(d->files, *this, d->changeSetOnIndex);
308 d->finishedOneHunk = false;
309 d->hunksFetcher->start();
312 void ChangeSet::lockFile(const File &file)
314 // qDebug() << "ChangeSet::lockFile";
315 QMutexLocker ml(&d->fileAccessLock);
316 d->lockedFile = file;
317 if (d->files.count() == 0 || d->files.at(0) != file) { // as soon as we have done a file, we can start the interaction
318 // qDebug() << " unlock cursorAccessWaiter";
319 d->cursorAccessLock.lock();
320 d->finishedOneHunk = true;
321 d->cursorAccessWaiter.wakeAll();
322 d->cursorAccessLock.unlock();
325 d->fileAccessWaiter.wakeAll();
326 // qDebug() << "~ChangeSet::lockFile";
329 void ChangeSet::removeFile(const File &file)
331 QMutexLocker ml(&d->fileAccessLock);
332 // TODO move the cursor if this file is the current file.
333 d->files.removeAll(file);
334 d->fileAccessWaiter.wakeAll();
337 void ChangeSet::allHunksFetched()
339 // qDebug() << "ChangeSet::allHunksFetched";
340 QMutexLocker ml(&d->fileAccessLock);
341 d->lockedFile = File();
342 d->fileAccessWaiter.wakeAll();
343 // qDebug() << "~ChangeSet::allHunksFetched";
346 bool ChangeSet::hasAllHunks() const
348 return d->hunksFetcher == 0 || d->hunksFetcher->isFinished();
351 // static
352 QList<File> ChangeSet::readGitDiff(QIODevice &git, File *fileToDiff)
354 class States {
355 public:
356 States(QIODevice &device, File *fileToDiff)
357 : m_input(device),
358 m_fileToDiff(fileToDiff),
359 m_state(Empty)
363 void start() {
364 Q_ASSERT(m_filesInDiff.isEmpty()); // only call start once, please ;)
365 m_state = Empty;
366 while(true) {
367 qint64 lineLength = Vng::readLine(&m_input, buf, sizeof(buf));
368 if (lineLength == -1 || m_state == Empty)
369 storeFile();
371 if (lineLength == -1)
372 break;
373 if (addLine(QString::fromLocal8Bit(buf, lineLength)))
374 break;
375 if (m_state == InPatch) {
376 QByteArray array(buf, lineLength);
377 m_hunk.addLine(array);
380 m_input.close();
382 // try to find out if there are renames
383 foreach (File addedFile, m_newFiles) {
384 foreach (File removedFile, m_removedFiles) {
385 if (!addedFile.sha1().isEmpty() && removedFile.oldSha1() == addedFile.sha1()) {
386 // TODO if this is a partial sha1 we may want to check some of the content
387 m_removedFiles.removeAll(removedFile);
388 addedFile.setOldSha1(removedFile.oldSha1());
389 addedFile.setOldFileName(removedFile.oldFileName());
390 addedFile.setOldProtection(removedFile.oldProtection());
391 break;
395 m_filesInDiff << m_newFiles;
396 m_filesInDiff << m_removedFiles;
397 m_newFiles.clear();
398 m_removedFiles.clear();
401 QList<File> results() const {
402 return m_filesInDiff;
404 private:
405 enum State {
406 InPatch,
407 Empty,
408 InHeader
411 void storeFile() {
412 m_file.addHunk(m_hunk);
413 m_hunk = Hunk();
414 if (m_file.isValid()) {
415 if (m_file.oldFileName().isEmpty())
416 m_newFiles << m_file;
417 else if (m_file.fileName().isEmpty())
418 m_removedFiles << m_file;
419 else
420 m_filesInDiff << m_file;
422 if (m_fileToDiff)
423 m_file = File(*m_fileToDiff);
424 else
425 m_file = File();
426 m_state = Empty;
429 // returns true if we should exit.
430 bool addLine(const QString &line) {
431 const bool newfile = line.startsWith("--- /dev/null");
432 if (line.length() > 6 && (newfile || line.startsWith("--- a/")
433 || line.startsWith("--- \"a/"))) {
434 if (m_state == InPatch)
435 storeFile();
436 m_state = InHeader;
437 if (!newfile && m_fileToDiff == 0) {
438 if (line[4].unicode() == '"') { // git-encoding...
439 QByteArray array(buf + 7, strlen(buf) - 8);
440 array.prepend('"');
441 m_file.setOldFileName(File::escapeGitFilename(array));
442 } else {
443 QByteArray array(buf + 6, strlen(buf) - 7);
444 m_file.setOldFileName(File::escapeGitFilename(array));
448 else if (m_fileToDiff == 0 && line.length() > 6 &&
449 (line.startsWith("+++ b/") || line.startsWith("+++ \"b"))) {
450 if (m_state == InPatch)
451 storeFile();
452 m_state = InHeader;
453 if (line[4].unicode() == '"') { // git-encoding...
454 QByteArray array(buf + 7, strlen(buf) - 8);
455 array.prepend('"');
456 m_file.setFileName(File::escapeGitFilename(array));
457 } else {
458 m_file.setFileName(QByteArray(buf + 6, strlen(buf) - 7));
461 else if (line.length() > 5 && line.startsWith("@@ -")) {
462 m_file.addHunk(m_hunk);
463 m_hunk = Hunk();
464 m_state = InPatch;
466 else if (line.startsWith("diff --git ")) {
467 if (m_state == InPatch)
468 storeFile();
469 m_state = InHeader;
471 else if (line.startsWith("Binary files ") && line.indexOf(" differ") > 0) {
472 Q_ASSERT(m_fileToDiff);
473 m_fileToDiff->setBinary(true);
474 return true;
476 else if (line.startsWith("index ")) {
477 if (m_state == InPatch)
478 storeFile();
479 m_state = InHeader;
480 int dot = line.indexOf(QLatin1Char('.'), 6);
481 if (dot > 0 && line.length() > dot+3) {
482 m_file.setOldSha1(line.mid(6, dot-6));
483 int space = line.indexOf(QLatin1Char(' '), dot);
484 if (space == -1) {
485 space = line.length()-1; // cut off the linefeed
486 } else {
487 m_file.setProtection(line.mid(space+1).trimmed());
489 m_file.setSha1(line.mid(dot+2, space - dot - 2));
492 else if (line.startsWith("deleted file mode ")) {
493 if (m_state == InPatch)
494 storeFile();
495 m_state = InHeader;
496 m_file.setProtection(line.mid(18).trimmed());
498 else if (line.startsWith("new file mode ")) {
499 if (m_state == InPatch)
500 storeFile();
501 m_state = InHeader;
502 m_file.setProtection(line.mid(13).trimmed());
504 return false;
507 QList<File> m_filesInDiff;
508 QList<File> m_newFiles;
509 QList<File> m_removedFiles;
511 QIODevice &m_input;
512 File *m_fileToDiff;
513 State m_state;
514 File m_file;
515 Hunk m_hunk;
516 char buf[10240];
519 States stateMachine(git, fileToDiff);
520 stateMachine.start();
521 return stateMachine.results();
524 void ChangeSet::addFile(const File &file)
526 if (file.isValid())
527 d->files << file;
530 int ChangeSet::count() const
532 return d->files.count();
535 void ChangeSet::writeDiff(QIODevice &outDevice, ChangeSet::Selection selection) const
537 waitFinishGenerateHunks();
538 outDevice.open(QIODevice::WriteOnly | QIODevice::Truncate);
539 QDataStream diff(&outDevice);
540 foreach(File file, d->files) {
541 if ((selection == AllHunks
542 || (selection == UserSelection && file.renameAcceptance() == Vng::Accepted))
543 && !file.oldFileName().isEmpty() && !file.fileName().isEmpty()
544 && file.oldFileName() != file.fileName()) {
545 writeRenameDiff(diff, file);
546 continue;
548 if (file.isBinary() && (selection == AllHunks || (selection == UserSelection
549 && file.binaryChangeAcceptance() == Vng::Accepted))) {
550 diff.writeRawData("diff --git a/", 13);
551 QByteArray fileName = file.oldFileName();
552 if (fileName.isEmpty())
553 fileName = file.fileName();
554 diff.writeRawData(fileName.data(), fileName.size());
555 diff.writeRawData(" b/", 3);
556 fileName = file.fileName();
557 if (fileName.isEmpty())
558 fileName = file.oldFileName();
559 diff.writeRawData(fileName.data(), fileName.size());
560 if (file.oldFileName().isEmpty()) {
561 diff.writeRawData("\nnew file mode ", 15);
562 QByteArray protection = file.protection().toLatin1();
563 diff.writeRawData(protection.data(), protection.size());
564 } else if (file.fileName().isEmpty()) {
565 diff.writeRawData("\ndeleted file mode ", 19);
566 QByteArray protection = file.oldProtection().toLatin1();
567 diff.writeRawData(protection.data(), protection.size());
569 diff.writeRawData("\nindex ", 7);
570 QByteArray sha1 = file.oldSha1().toLatin1();
571 diff.writeRawData(sha1.data(), sha1.size());
572 diff.writeRawData("..", 2);
573 sha1 = file.sha1().toLatin1();
574 diff.writeRawData(sha1.data(), sha1.size());
575 diff.writeRawData("\nGIT binary patch\n", 18);
576 //file.writeBinaryDataAsPatch(outDevice); // TODO :)
577 diff.writeRawData("literal 0\nHcmV?d00001\n\n", 23);
578 continue;
581 bool fileHeaderWritten = false;
582 foreach(Hunk hunk, file.hunks()) {
583 if (selection == AllHunks
584 || (selection == UserSelection
585 && (hunk.acceptance() == Vng::Accepted || hunk.acceptance() == Vng::MixedAcceptance))
586 || (selection == InvertedUserSelection && hunk.acceptance() != Vng::Accepted)) {
587 if (!fileHeaderWritten) {
588 if (file.oldFileName().isEmpty()) { // new file
589 diff.writeRawData("--- /dev/null", 13);
590 } else {
591 diff.writeRawData("--- a/", 6);
592 diff.writeRawData(file.oldFileName().data(), file.oldFileName().size());
594 if (file.fileName().isEmpty()) { // deleted file
595 diff.writeRawData("\n+++ /dev/null\n", 15);
596 } else {
597 diff.writeRawData("\n+++ b/", 7);
598 diff.writeRawData(file.fileName().data(), file.fileName().size());
599 diff.writeRawData("\n", 1);
601 fileHeaderWritten = true;
603 QByteArray acceptedPatch;
604 if (selection == InvertedUserSelection)
605 acceptedPatch =hunk.rejectedPatch();
606 else if (selection == AllHunks) {
607 acceptedPatch = hunk.header();
608 acceptedPatch.append(hunk.patch());
610 else
611 acceptedPatch = hunk.acceptedPatch();
612 diff.writeRawData(acceptedPatch.data(), acceptedPatch.size());
616 outDevice.close();
619 void ChangeSet::writeRenameDiff(QDataStream &out, const File &file) const
621 out.writeRawData("diff --git a/", 13);
622 out.writeRawData(file.fileName().data(), file.fileName().size());
623 out.writeRawData(" b/", 3);
624 out.writeRawData(file.fileName().data(), file.fileName().size());
625 out.writeRawData("\nnew file mode ", 15);
626 QByteArray tmp = file.protection().toLatin1();
627 out.writeRawData(tmp.data(), tmp.length());
628 out.writeRawData("\nindex 0000000..", 16);
629 tmp = file.sha1().toLatin1();
630 out.writeRawData(tmp.data(), 7);
631 out.writeRawData("\n--- /dev/null\n+++ b/", 21);
632 out.writeRawData(file.fileName().data(), file.fileName().size());
634 QProcess git;
635 QStringList arguments;
636 arguments << "cat-file" << "blob" << file.sha1();
637 GitRunner runner(git, arguments);
638 runner.start(GitRunner::WaitForStandardOutput);
639 QList<QByteArray> lines;
640 char buf[4096];
641 while(true) {
642 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
643 if (lineLength == -1)
644 break;
645 lines.append(QByteArray(buf, lineLength));
648 QByteArray countAsString = QString::number(lines.count()).toLatin1();
649 out.writeRawData("\n@@ -0,0 +1,", 12);
650 out.writeRawData(countAsString.data(), countAsString.length());
651 out.writeRawData(" @@\n", 4);
652 foreach (const QByteArray &line, lines) {
653 out.writeRawData("+", 1);
654 out.writeRawData(line.data(), line.length());
657 // removed file.
658 out.writeRawData("\ndiff --git a/", 14);
659 out.writeRawData(file.oldFileName().data(), file.oldFileName().size());
660 out.writeRawData(" b/", 3);
661 out.writeRawData(file.oldFileName().data(), file.oldFileName().size());
662 out.writeRawData("\ndeleted file mode ", 19);
663 tmp = file.oldProtection().toLatin1();
664 out.writeRawData(tmp.data(), tmp.length());
665 out.writeRawData("\nindex ", 7);
666 tmp = file.oldSha1().toLatin1();
667 out.writeRawData(tmp.data(), 7);
668 out.writeRawData("..0000000\n--- a/", 16);
669 out.writeRawData(file.oldFileName().data(), file.oldFileName().size());
671 out.writeRawData("\n+++ /dev/null\n@@ -1,", 21);
672 out.writeRawData(countAsString.data(), countAsString.length());
673 out.writeRawData(" +0,0 @@\n", 9);
675 foreach (const QByteArray &line, lines) {
676 out.writeRawData("-", 1);
677 out.writeRawData(line.data(), line.length());
681 bool ChangeSet::hasAcceptedChanges() const
683 waitFinishGenerateHunks();
684 foreach(File file, d->files) {
685 if (file.renameAcceptance() == Vng::Accepted && file.fileName() != file.oldFileName())
686 return true;
687 if (file.protectionAcceptance() == Vng::Accepted && file.protection() != file.oldProtection())
688 return true;
689 if (file.isBinary() && file.binaryChangeAcceptance() == Vng::Accepted)
690 return true;
691 foreach(Hunk hunk, file.hunks()) {
692 Vng::Acceptance a = hunk.acceptance();
693 if (a == Vng::Accepted || a == Vng::MixedAcceptance)
694 return true;
697 return false;
700 File ChangeSet::file(int index) const
702 // qDebug() << "ChangeSet::file" << index << d->finishedOneHunk;
703 waitForFinishFirstFile();
704 QMutexLocker ml2(&d->fileAccessLock);
705 while (d->files.count() > index && d->files[index] == d->lockedFile)
706 // { qDebug() << " waiting for file to be unlocked";
707 d->fileAccessWaiter.wait(&d->fileAccessLock);
708 // }
709 if (d->files.count() <= index)
710 return File();
712 // qDebug() << "ChangeSet::~file";
713 return d->files[index];
716 ChangeSet &ChangeSet::operator=(const ChangeSet &other)
718 #if QT_VERSION >= 0x040400
719 other.d->ref.ref();
720 if (!d->ref.deref())
721 #else
722 other.d->ref++;
723 if (--d->ref == 0)
724 #endif
725 delete d;
726 d = other.d;
727 return *this;
730 void ChangeSet::waitFinishGenerateHunks() const
732 if (d->hunksFetcher)
733 d->hunksFetcher->wait();
736 void ChangeSet::waitForFinishFirstFile() const
738 d->cursorAccessLock.lock();
739 if (! d->finishedOneHunk)
740 d->cursorAccessWaiter.wait(&d->cursorAccessLock);
741 d->cursorAccessLock.unlock();