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
) {
299 QList
<QByteArray
> lines
;
301 int bytesToInspect
= 100;
303 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
304 if (lineLength
== -1)
306 if (bytesToInspect
> 0) { // detect binary files
307 const int max
= qMin(bytesToInspect
, (int) lineLength
);
308 for (int i
= 0; i
< max
; ++i
) {
310 m_file
.setBinary(true);
314 bytesToInspect
-= max
;
317 line
.reserve(lineLength
+ 1);
318 line
.append(prefix
, 1);
319 line
.append(buf
, lineLength
);
323 Hunk hunk
; // its one hunk.
324 QByteArray
header("@@ -1,", 6);
325 header
.append(QString::number(lines
.count()).toLatin1());
326 header
.append(" +0,0 @@\n", 9);
327 hunk
.addLine(header
);
328 foreach (const QByteArray
&line
, lines
) {
331 m_file
.addHunk(hunk
);
339 if (fileName().isEmpty()) { // deleted file.
340 DiffCreator
dc(*this);
341 dc
.wholeFileAsDiff(false);
345 QStringList arguments
;
346 if (sha1().startsWith("0000000") || oldSha1().startsWith("0000000")) {
347 // if this is changed without ever being added to the index, sha1() is 000.
348 // if added as a new file oldSha1() is 000
350 arguments
<< "diff-index" << "-p" << "HEAD" << QString::fromUtf8(fileName());
352 DiffCreator
dc(*this);
353 dc
.wholeFileAsDiff(true);
358 arguments
<< "diff" << oldSha1() << sha1();
360 GitRunner
runner(git
, arguments
);
361 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
364 ChangeSet::readGitDiff(git
, this);
367 void File::outputWhatsChanged(QTextStream
&out
, Configuration
&config
, bool printSummary
, bool unified
)
369 const bool deleted
= d
->filename
.isEmpty() && !d
->oldFilename
.isEmpty();
370 const bool added
= !deleted
&& !d
->filename
.isEmpty() && d
->oldFilename
.isEmpty();
371 const bool renamed
= !deleted
&& !added
&& d
->filename
!= d
->oldFilename
;
382 if (deleted
|| renamed
)
383 out
<< QString::fromUtf8(d
->oldFilename
);
385 out
<< QString::fromUtf8(d
->filename
);
387 out
<< " => " << QString::fromUtf8(d
->filename
);
389 int removed
= linesRemoved();
390 int added
= linesAdded();
392 out
<< " -" << QString::number(removed
);
394 out
<< " +" << QString::number(added
);
399 if (deleted
) { // file deleted.
401 config
.colorize(out
);
403 config
.normalColor(out
);
404 out
<< QString::fromUtf8(d
->oldFilename
) << endl
;
407 else if (added
) { // file added.
409 config
.colorize(out
);
411 config
.normalColor(out
);
412 out
<< QString::fromUtf8(d
->filename
) << endl
;
416 if(renamed
) { // rename
417 if (!unified
) config
.colorize(out
);
419 if (!unified
) config
.normalColor(out
);
420 out
<< "`" << QString::fromUtf8(d
->oldFilename
) << "' `" << QString::fromUtf8(d
->filename
) << "'" << endl
;
422 if (oldProtection() != protection()) {
423 if (!unified
) config
.colorize(out
);
424 out
<<"mode change ";
425 if (!unified
) config
.normalColor(out
);
426 out
<< QString::fromUtf8(d
->filename
) << " " << oldProtection() << " => " << protection() << endl
;
429 if (isBinary()) { // will not have any hunks
430 if (!unified
) config
.colorize(out
);
431 out
<< "binary modification ";
432 if (!unified
) config
.normalColor(out
);
433 out
<< QString::fromUtf8(d
->filename
) << endl
;
438 if (d
->oldFilename
.isEmpty())
439 out
<< "/dev/null\n";
441 out
<< QString::fromUtf8(d
->oldFilename
) << endl
;
443 if (d
->filename
.isEmpty())
444 out
<< "/dev/null\n";
446 out
<< QString::fromUtf8(d
->filename
) << endl
;
448 foreach(Hunk hunk
, hunks()) {
450 out
<< QString(hunk
.header());
451 out
<< QString(hunk
.patch());
454 for (int i
= 0; i
< hunk
.subHunkCount(); i
++) {
455 config
.colorize(out
);
457 QByteArray patch
= hunk
.subHunk(i
);
458 config
.normalColor(out
);
459 out
<< QString::fromUtf8(d
->filename
) <<" "<< QString::number(hunk
.lineNumber(i
)) << endl
;
460 if (patch
.contains((char) 0)) { // binary
461 config
.colorize(out
);
462 out
<< "binary data\n";
463 config
.normalColor(out
);
467 const char *data
= patch
.constData();
469 for (int index
= 0;index
< patch
.count(); ++index
) {
470 if (patch
[index
] == '\n' || patch
[index
] == '\r') { // next line!
471 QString string
= QString::fromLocal8Bit(data
+ lineStart
, index
- lineStart
);
473 if (config
.colorTerm() && string
[string
.length()-1].isSpace()) {
474 config
.colorize2(out
);
476 config
.normalColor(out
);
488 void File::cleanHunksData()
493 Vng::Acceptance
File::changesAcceptance()
495 class AccaptenceManager
{
497 AccaptenceManager() : m_init(false), m_result(Vng::Accepted
) { }
498 void add(Vng::Acceptance a
) {
505 m_result
= Vng::MixedAcceptance
;
506 // do I need something special for undecided?
508 Vng::Acceptance
result() { return m_result
; }
511 Vng::Acceptance m_result
;
513 AccaptenceManager am
;
515 if (d
->protection
!= d
->oldProtection
)
516 am
.add(d
->protectionAcceptance
);
517 if (d
->filename
!= d
->oldFilename
)
518 am
.add(d
->renameAcceptance
);
520 am
.add(d
->binChangeAcceptance
);
521 foreach (Hunk hunk
, d
->hunks
) {
522 am
.add(hunk
.acceptance());
523 if (am
.result() == Vng::MixedAcceptance
)
530 bool File::fileKnownToGit(const QString
&path
)
532 return fileKnownToGit(QFileInfo(path
));
536 bool File::fileKnownToGit(const QFileInfo
&path
)
538 QStringList arguments
;
539 arguments
<< "ls-files" << path
.filePath();
541 GitRunner
runner(git
, arguments
);
542 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
543 if (rc
!= AbstractCommand::Ok
)
545 bool unknownFile
= true;
548 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
549 if (lineLength
== -1)
551 unknownFile
= false; // lets assume here that the script just doesn't print anything if its not added yet.
554 git
.waitForFinished();
559 QByteArray
File::escapeGitFilename(const QByteArray
&fileName
)
561 // find any backslash escape chars and resolve them
562 // we have 3 options;
563 // \123 -> 1 byte resolving that octal number
564 // \x -> just remove the backslash
565 // \n -> special char like the newline, resolve them
566 if (fileName
.isEmpty() || fileName
[0] != '"')
570 const int length
= fileName
.length();
571 int firstEscapedChar
= -1;
573 for (int index
= 1; index
< length
; ++index
) {
574 const char byte
= fileName
[index
];
575 if (firstEscapedChar
< 0 && byte
== '\\') {
576 firstEscapedChar
= index
;
577 } else if (firstEscapedChar
>= 0) {
578 if (byte
>= '0' && byte
<= '9') { // decode octal num
579 if (index
- firstEscapedChar
< 3) // need more digitis
582 const int num
= QString::fromAscii(fileName
.constData() + firstEscapedChar
+ 1, index
- firstEscapedChar
).toInt(&ok
, 8);
583 Q_ASSERT(num
<= 256);
584 answer
[destIndex
++] = num
;
585 firstEscapedChar
= -1;
587 answer
[destIndex
++] = byte
;
588 firstEscapedChar
= -1;
590 } else if (index
< length
-1) { // last one is a double quote
591 answer
[destIndex
++] = byte
;
594 answer
.resize(destIndex
);