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/>.
21 #include "../GitRunner.h"
22 #include "../Logger.h"
27 static QDateTime
timeFromSecondsSinceEpoch(const QString
&string
) {
28 const int split
= string
.lastIndexOf('>');
29 const int tz
= string
.lastIndexOf(' ');
30 if (split
> 0 && tz
> split
)
31 return QDateTime::fromTime_t(string
.mid(split
+ 1, tz
- split
).toLong());
35 Commit::Commit(CommitPrivate
*priv
)
45 Commit::Commit(const QString
&treeIsm
, const Commit
&nextCommit
)
48 fillFromTreeIsm(treeIsm
);
50 d
->child
= nextCommit
;
53 Commit::Commit(const Commit
&other
)
62 if (d
&& --d
->ref
== 0)
66 QList
<Commit
> Commit::previous()
68 if (d
->previousCommits
.count() != d
->parentTreeisms
.count()) {
69 foreach(QString id
, d
->parentTreeisms
)
70 d
->previousCommits
.append(Commit(id
, *this));
72 return d
->previousCommits
;
75 int Commit::previousCommitsCount() const
77 return d
->parentTreeisms
.count();
80 QString
Commit::author() const
82 int split
= d
->author
.lastIndexOf('>');
84 return d
->author
.left(split
+ 1);
88 QString
Commit::committer() const
90 int split
= d
->committer
.lastIndexOf('>');
92 return d
->committer
.left(split
+ 1);
96 QDateTime
Commit::commitTime() const
98 return timeFromSecondsSinceEpoch(d
->committer
);
101 QDateTime
Commit::authorTime() const
103 return timeFromSecondsSinceEpoch(d
->author
);
106 QByteArray
Commit::logMessage() const
108 return d
->logMessage
;
111 QString
Commit::tree() const
116 void Commit::clearParents()
118 d
->previousCommits
.clear();
121 Commit
&Commit::operator=(const Commit
&other
)
125 if (d
&& --d
->ref
== 0)
131 bool Commit::operator==(const Commit
&other
)
133 return other
.d
== d
|| (other
.d
&& d
&& other
.d
->tree
== d
->tree
);
136 Vng::Acceptance
Commit::acceptance() const
138 return d
->acceptance
;
141 void Commit::setAcceptance(Vng::Acceptance accepted
)
143 d
->acceptance
= accepted
;
146 bool Commit::isValid() const
148 return d
&& !d
->tree
.isEmpty();
151 QString
Commit::commitTreeIsm() const
156 QString
Commit::commitTreeIsmSha1() const
158 if (! d
->resolvedTreeIsm
.isEmpty())
159 return d
->resolvedTreeIsm
;
162 QStringList arguments
;
163 if (d
->treeIsm
== "HEAD") // grmbl; git requires us to special case this...
164 arguments
<< "show-ref" << "--hash" << "--head";
166 arguments
<< "show-ref" << "--hash" << d
->treeIsm
;
167 GitRunner
runner(git
, arguments
);
168 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
, GitRunner::FailureAccepted
);
170 d
->resolvedTreeIsm
= d
->treeIsm
;
176 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
177 if (lineLength
== -1)
179 d
->resolvedTreeIsm
+= QString::fromLatin1(buf
, lineLength
);
180 if (d
->resolvedTreeIsm
.length() > 40)
183 d
->resolvedTreeIsm
= d
->resolvedTreeIsm
.trimmed();
184 if (d
->resolvedTreeIsm
.length() != 40) {
185 Logger::warn() << "Commit::commitTreeIsmSha1: show-ref gave unexpected; '" << d
->resolvedTreeIsm
<< "`\n";
186 d
->resolvedTreeIsm
.clear();
188 return d
->resolvedTreeIsm
;
191 Commit
Commit::next()
196 Commit
Commit::firstCommitInBranch()
201 if (previousCommitsCount() == 0) {
202 Logger::debug() << "Commit::firstCommitInBranch: No parent commit\n";
203 return Commit(); // this is the first commit in the repo
206 Commit currentBranch
;
208 QList
<Commit
> otherBranches
;
210 QDir
heads(".git/refs/heads");
211 foreach (QString head
, heads
.entryList(QDir::Files
| QDir::NoDotAndDotDot
)) {
213 if (operator==(branch
)) { // this only works if this commit is the curent HEAD...
214 currentBranch
= (*this);
217 otherBranches
.append(branch
);
219 if (!currentBranch
.isValid()) { // we are not on a branch...
220 Logger::debug() << "Commit::firstCommitInBranch: we are not on a branch\n";
224 currentBranch
= currentBranch
.previous()[0];
226 QDateTime time
= currentBranch
.commitTime();
228 foreach (Commit c
, otherBranches
) {
229 Q_ASSERT(c
.previousCommitsCount());
230 while(c
.commitTime() > time
)
232 if (c
== currentBranch
&& currentBranch
.previousCommitsCount() == 1)
233 return currentBranch
;
236 otherBranches
= list
;
237 if (currentBranch
.previousCommitsCount() == 0)
238 return currentBranch
;
239 currentBranch
= currentBranch
.previous()[0];
245 ChangeSet
Commit::changeSet() const
250 void Commit::fillFromTreeIsm(const QString
&treeIsm
)
253 QStringList arguments
;
254 arguments
<< "cat-file" << "commit" << treeIsm
;
255 GitRunner
runner(git
, arguments
);
256 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
258 Logger::info() << "Invalid treeIsm passed " << treeIsm
<< endl
;
263 Commit c
= createFromStream(&git
, d
);
265 d
->treeIsm
= treeIsm
;
269 if (d
->treeIsm
.length() == 40) // more checks?
270 d
->resolvedTreeIsm
= d
->treeIsm
;
274 Commit
Commit::createFromStream(QIODevice
*device
)
276 return Commit::createFromStream(device
, 0);
280 Commit
Commit::createFromStream(QIODevice
*device
, CommitPrivate
*priv
)
283 priv
= new CommitPrivate();
286 enum State
{ Init
, Header
, Comment
, Files
, Done
};
288 while (state
!= Done
) {
289 qint64 lineLength
= Vng::readLine(device
, buf
, sizeof(buf
));
290 if (lineLength
== -1)
292 QString line
= QString::fromLocal8Bit(buf
, lineLength
);
293 if (state
== Init
|| state
== Header
) {
295 if (line
.length() == 1) {
299 else if (line
.startsWith("commit ")) {
300 priv
->treeIsm
= line
.mid(7).trimmed();
301 priv
->resolvedTreeIsm
= priv
->treeIsm
;
303 else if (line
.startsWith("parent "))
304 priv
->parentTreeisms
.append(line
.mid(7).trimmed());
305 else if (line
.startsWith("author "))
306 priv
->author
= line
.mid(7).trimmed();
307 else if (line
.startsWith("committer "))
308 priv
->committer
= line
.mid(10).trimmed();
309 else if (line
.startsWith("tree "))
310 priv
->tree
= line
.mid(5).trimmed();
314 if ((state
== Init
|| state
== Files
) && line
.length() > 0) {
317 if (line
.length() > 100) {
318 if (priv
->parentTreeisms
.count() <= 1) { // skip file listings for merges
320 file
.setOldProtection(line
.mid(1, 6));
321 file
.setProtection(line
.mid(8, 6));
322 file
.setOldSha1(line
.mid(15, 40));
323 file
.setSha1(line
.mid(56, 40));
324 file
.setFileName(File::escapeGitFilename(QByteArray(buf
+ 99, lineLength
- 100))); // immediately cut off the linefeed
326 case 'M': file
.setOldFileName(file
.fileName()); break;
327 case 'D': file
.setOldFileName(file
.fileName()); file
.setFileName(QByteArray()); break;
331 priv
->changeSet
.addFile(file
);
335 device
->waitForReadyRead(-1);
336 lineLength
= device
->peek(buf
, 2);
337 if (lineLength
== -1 || buf
[0] == 'c') { // exit, the next line is not part of this commit no more.
343 if (state
== Comment
) {
344 if (line
.length() == 1)
345 state
= Init
; // can be part of the comment, or we will start the Files section soon.
347 if (state
== Init
&& ! priv
->logMessage
.isEmpty())
348 priv
->logMessage
.append('\n');
349 priv
->logMessage
+= line
.toLocal8Bit();
356 Q_ASSERT(answer
.d
== 0); // make sure there will be no mem-leak
362 QList
<File
> Commit::allFiles(const QString
&commitTreeIsm
)
367 QStringList arguments
;
368 arguments
<< "ls-tree" << "-r" << commitTreeIsm
;
369 GitRunner
runner(git
, arguments
);
370 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
371 if (rc
!= AbstractCommand::Ok
)
375 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
376 if (lineLength
== -1)
378 Q_ASSERT(lineLength
> 53);
379 buf
[lineLength
- 1] = 0; // remove the linefeed
381 file
.setFileName(QByteArray(buf
+ 53, lineLength
- 54));
382 file
.setProtection(QString::fromLatin1(buf
, 6));
383 file
.setSha1(QString::fromLatin1(buf
+ 12, 40));
386 git
.waitForFinished();