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/>.
20 #include "ChangeSet.h"
21 #include "../GitRunner.h"
22 #include "../AbstractCommand.h"
32 renameAcceptance(Vng::Undecided
),
33 protectionAcceptance(Vng::Undecided
),
34 binChangeAcceptance(Vng::Undecided
),
39 QString protection
, oldProtection
;
40 QString sha1
, oldSha1
;
42 QByteArray oldFilename
;
44 #if QT_VERSION >= 0x040400
49 Vng::Acceptance renameAcceptance
;
50 Vng::Acceptance protectionAcceptance
;
51 Vng::Acceptance binChangeAcceptance
;
60 File::File(const File
&other
)
63 #if QT_VERSION >= 0x040400
72 #if QT_VERSION >= 0x040400
80 void File::addHunk(const Hunk
&hunk
)
86 QList
<Hunk
> File::hunks() const
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
107 File
& File::operator=(const File
&other
)
109 #if QT_VERSION >= 0x040400
121 bool File::operator==(const File
&other
) const
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
)
175 void File::setSha1(const QString
&string
)
180 QString
File::oldSha1() const
185 QString
File::sha1() const
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
)
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)
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
;
250 int File::linesAdded() const
253 foreach(Hunk hunk
, d
->hunks
)
254 total
+= hunk
.linesAdded();
258 int File::linesRemoved() const
261 foreach(Hunk hunk
, d
->hunks
)
262 total
+= hunk
.linesRemoved();
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())
280 if (d
->oldSha1
== d
->sha1
)
281 return; // no change.
285 DiffCreator(File file
) : m_file(file
) { }
286 void wholeFileAsDiff(bool added
)
289 QStringList arguments
;
290 arguments
<< "cat-file" << "blob";
292 arguments
<< m_file
.sha1();
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
) {
300 Hunk hunk
; // its one hunk.
301 QByteArray
header("@@ -1,0 +1,0 @@", 15);
302 hunk
.addLine(header
);
304 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
305 if (lineLength
== -1)
307 QByteArray
array(prefix
, 1);
311 m_file
.addHunk(hunk
);
319 if (fileName().isEmpty()) { // deleted file.
320 DiffCreator
dc(*this);
321 dc
.wholeFileAsDiff(false);
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
330 arguments
<< "diff-index" << "-p" << "HEAD" << QString::fromUtf8(fileName());
332 DiffCreator
dc(*this);
333 dc
.wholeFileAsDiff(true);
338 arguments
<< "diff" << oldSha1() << sha1();
340 GitRunner
runner(git
, arguments
);
341 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
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
;
362 if (deleted
|| renamed
)
363 out
<< QString::fromUtf8(d
->oldFilename
);
365 out
<< QString::fromUtf8(d
->filename
);
367 out
<< " => " << QString::fromUtf8(d
->filename
);
369 int removed
= linesRemoved();
370 int added
= linesAdded();
372 out
<< " -" << QString::number(removed
);
374 out
<< " +" << QString::number(added
);
379 if (deleted
) { // file deleted.
381 config
.colorize(out
);
383 config
.normalColor(out
);
384 out
<< QString::fromUtf8(d
->oldFilename
) << endl
;
387 else if (added
) { // file added.
389 config
.colorize(out
);
391 config
.normalColor(out
);
392 out
<< QString::fromUtf8(d
->filename
) << endl
;
396 if(renamed
) { // rename
397 if (!unified
) config
.colorize(out
);
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
;
418 if (d
->oldFilename
.isEmpty())
419 out
<< "/dev/null\n";
421 out
<< QString::fromUtf8(d
->oldFilename
) << endl
;
423 if (d
->filename
.isEmpty())
424 out
<< "/dev/null\n";
426 out
<< QString::fromUtf8(d
->filename
) << endl
;
428 foreach(Hunk hunk
, hunks()) {
430 out
<< QString(hunk
.header());
431 out
<< QString(hunk
.patch());
434 for (int i
= 0; i
< hunk
.subHunkCount(); i
++) {
435 config
.colorize(out
);
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
);
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);
451 if (config
.colorTerm() && (!trailingLinefeed
|| string
[string
.length()-1].isSpace())) {
452 config
.colorize2(out
);
454 config
.normalColor(out
);
463 void File::cleanHunksData()
468 Vng::Acceptance
File::changesAcceptance()
470 class AccaptenceManager
{
472 AccaptenceManager() : m_init(false), m_result(Vng::Accepted
) { }
473 void add(Vng::Acceptance a
) {
480 m_result
= Vng::MixedAcceptance
;
481 // do I need something special for undecided?
483 Vng::Acceptance
result() { return m_result
; }
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
);
495 am
.add(d
->binChangeAcceptance
);
496 foreach (Hunk hunk
, d
->hunks
) {
497 am
.add(hunk
.acceptance());
498 if (am
.result() == Vng::MixedAcceptance
)
505 bool File::fileKnownToGit(const QString
&path
)
507 return fileKnownToGit(QFileInfo(path
));
511 bool File::fileKnownToGit(const QFileInfo
&path
)
513 QStringList arguments
;
514 arguments
<< "ls-files" << path
.filePath();
516 GitRunner
runner(git
, arguments
);
517 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
518 if (rc
!= AbstractCommand::Ok
)
520 bool unknownFile
= true;
523 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
524 if (lineLength
== -1)
526 unknownFile
= false; // lets assume here that the script just doesn't print anything if its not added yet.
529 git
.waitForFinished();
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] != '"')
545 const int length
= fileName
.length();
546 int firstEscapedChar
= -1;
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
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;
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
);