Make test more complete
[vng.git] / patches / Commit.cpp
bloba1c73299e9342bd46255416ff7a04030ab10cff6
1 /*
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/>.
19 #include "Commit.h"
20 #include "Commit_p.h"
21 #include "../GitRunner.h"
22 #include "../Logger.h"
24 #include <QProcess>
25 #include <QDebug>
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());
32 return QDateTime();
35 Commit::Commit(CommitPrivate *priv)
36 : d(priv)
40 Commit::Commit()
41 : d(0)
45 Commit::Commit(const QString &treeism, const Commit &nextCommit)
46 : d(0)
48 fillFromTreeIsm(treeism);
49 if (d)
50 d->child = nextCommit;
53 Commit::Commit(const Commit &other)
54 : d(other.d)
56 if (d)
57 d->ref++;
60 Commit::~Commit()
62 if (d && --d->ref == 0)
63 delete d;
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('>');
83 if (split > 0)
84 return d->author.left(split + 1);
85 return d->author;
88 QString Commit::committer() const
90 int split = d->committer.lastIndexOf('>');
91 if (split > 0)
92 return d->committer.left(split + 1);
93 return d->committer;
96 QDateTime Commit::commitTime() const
98 return timeFromSecondsSinceEpoch(d->committer);
101 QDateTime Commit::authorTime() const
103 return timeFromSecondsSinceEpoch(d->author);
106 QString Commit::logMessage() const
108 return d->logMessage;
111 QString Commit::tree() const
113 return d->tree;
116 void Commit::clearParents()
118 d->previousCommits.clear();
121 Commit &Commit::operator=(const Commit &other)
123 if (other.d)
124 other.d->ref++;
125 if (d && --d->ref == 0)
126 delete d;
127 d = other.d;
128 return *this;
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
153 return d->treeism;
156 Commit Commit::next()
158 return d->child;
161 Commit Commit::firstCommitInBranch()
163 Q_ASSERT(isValid());
164 int max = 50;
165 Q_ASSERT(isValid());
166 if (previousCommitsCount() == 0) {
167 Logger::debug() << "Commit::firstCommitInBranch: No parent commit\n";
168 return Commit(); // this is the first commit in the repo
171 Commit currentBranch;
173 QList<Commit> otherBranches;
175 QDir heads(".git/refs/heads");
176 foreach (QString head, heads.entryList(QDir::Files | QDir::NoDotAndDotDot)) {
177 Commit branch(head);
178 if (operator==(branch)) { // this only works if this commit is the curent HEAD...
179 currentBranch = (*this);
180 continue;
182 otherBranches.append(branch);
184 if (!currentBranch.isValid()) { // we are not on a branch...
185 Logger::debug() << "Commit::firstCommitInBranch: we are not on a branch\n";
186 return Commit();
189 currentBranch = currentBranch.previous()[0];
190 while(true) {
191 QDateTime time = currentBranch.commitTime();
192 QList<Commit> list;
193 foreach (Commit c, otherBranches) {
194 Q_ASSERT(c.previousCommitsCount());
195 while(c.commitTime() > time)
196 c = c.previous()[0];
197 if (c == currentBranch && currentBranch.previousCommitsCount() == 1)
198 return currentBranch;
199 list.append(c);
201 otherBranches = list;
202 if (currentBranch.previousCommitsCount() == 0)
203 return currentBranch;
204 currentBranch = currentBranch.previous()[0];
205 if (--max <= 0)
206 return Commit();
210 ChangeSet Commit::changeSet() const
212 return d->changeSet;
215 void Commit::fillFromTreeIsm(const QString &treeism)
217 QProcess git;
218 QStringList arguments;
219 arguments << "cat-file" << "commit" << treeism;
220 GitRunner runner(git, arguments);
221 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
222 if (rc) {
223 Logger::info() << "Invalid treeism passed " << treeism << endl;
224 delete d;
225 d = 0;
226 return;
228 Commit c = createFromStream(&git, d);
229 d = c.d;
230 d->treeism = treeism;
231 if (d)
232 d->ref++;
235 // static
236 Commit Commit::createFromStream(QIODevice *device)
238 return Commit::createFromStream(device, 0);
241 // static
242 Commit Commit::createFromStream(QIODevice *device, CommitPrivate *priv)
244 if (priv == 0)
245 priv = new CommitPrivate();
246 char buf[1024];
247 bool endingLinefeed = false;
248 for (int index = 0; true; index++) {
249 qint64 lineLength = Vng::readLine(device, buf, sizeof(buf));
250 if (lineLength == -1)
251 break;
252 QString line = QString::fromLocal8Bit(buf, lineLength);
253 if (index == 0) {
254 if (line.startsWith("commit ")) {
255 priv->treeism = line.mid(7).trimmed();
256 index--;
257 continue;
259 priv->tree = line.mid(5).trimmed();
260 continue;
262 if (index == 1) {
263 if (line.startsWith("parent ")) {
264 priv->parentTreeisms.append(line.mid(7).trimmed());
265 index--;
266 continue;
268 else
269 index++;
271 if (index == 2)
272 priv->author = line.mid(7).trimmed();
273 else if (index == 3)
274 priv->committer = line.mid(10).trimmed();
275 else if (index >= 5) {
276 if (buf[0] != ':') {
277 if (buf[1] == 0) { // TODO does this work on windows?
278 if (! endingLinefeed) {
279 endingLinefeed = true;
280 device->waitForReadyRead(-1);
281 lineLength = device->peek(buf, 2);
282 if (lineLength == -1)
283 break;
284 if (buf[0] == 'c') // exit, the next line is not part of this commit no more.
285 break;
286 continue;
289 else if (endingLinefeed) {
290 priv->logMessage = priv->logMessage + "\n";
291 endingLinefeed = false;
293 priv->logMessage = priv->logMessage + line;
295 else if (line.length() > 100) {
296 if (priv->parentTreeisms.count() <= 1) { // skip file listings for merges
297 File file;
298 file.setOldProtection(line.mid(1, 6));
299 file.setProtection(line.mid(8, 6));
300 file.setOldSha1(line.mid(15, 40));
301 file.setSha1(line.mid(56, 40));
302 file.setFileName(QByteArray(buf + 99, lineLength - 100)); // immediately cut off the linefeed
303 switch (buf[97]) {
304 case 'M': file.setOldFileName(file.fileName()); break;
305 case 'D': file.setOldFileName(file.fileName()); file.setFileName(QByteArray()); break;
306 default:
307 break;
309 priv->changeSet.addFile(file);
311 endingLinefeed = false;
316 Commit answer;
317 Q_ASSERT(answer.d == 0); // make sure there will be no mem-leak
318 answer.d = priv;
319 return answer;
322 // static
323 QList<File> Commit::allFiles(const QString &commitTreeIsm)
325 QList<File> files;
327 QProcess git;
328 QStringList arguments;
329 arguments << "ls-tree" << "-r" << commitTreeIsm;
330 GitRunner runner(git, arguments);
331 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
332 if (rc != AbstractCommand::Ok)
333 return files;
334 char buf[4000];
335 while(true) {
336 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
337 if (lineLength == -1)
338 break;
339 Q_ASSERT(lineLength > 53);
340 buf[lineLength - 1] = 0; // remove the linefeed
341 File file;
342 file.setFileName(QByteArray(buf + 53, lineLength - 54));
343 file.setProtection(QString::fromLatin1(buf, 6));
344 file.setSha1(QString::fromLatin1(buf + 12, 40));
345 files << file;
347 git.waitForFinished();
348 return files;