Be memory efficient and clear hunk data after displaying it
[vng.git] / patches / Commit.cpp
blobc7f261a4711e984ea91a0a8faa042bf5af5d438a
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++;
236 // static
237 Commit Commit::createFromStream(QIODevice *device)
239 return Commit::createFromStream(device, 0);
242 // static
243 Commit Commit::createFromStream(QIODevice *device, CommitPrivate *priv)
245 if (priv == 0)
246 priv = new CommitPrivate();
247 char buf[1024];
248 bool endingLinefeed = false;
249 for (int index = 0; true; index++) {
250 qint64 lineLength = Vng::readLine(device, buf, sizeof(buf));
251 if (lineLength == -1)
252 break;
253 QString line = QString::fromLocal8Bit(buf, lineLength);
254 if (index == 0) {
255 if (line.startsWith("commit ")) {
256 priv->treeism = line.mid(7).trimmed();
257 index--;
258 continue;
260 priv->tree = line.mid(5).trimmed();
261 continue;
263 if (index == 1) {
264 if (line.startsWith("parent ")) {
265 priv->parentTreeisms.append(line.mid(7).trimmed());
266 index--;
267 continue;
269 else
270 index++;
272 if (index == 2)
273 priv->author = line.mid(7).trimmed();
274 else if (index == 3)
275 priv->committer = line.mid(10).trimmed();
276 else if (index >= 5) {
277 if (buf[0] != ':') {
278 if (buf[1] == 0) { // TODO does this work on windows?
279 if (! endingLinefeed) {
280 endingLinefeed = true;
281 device->waitForReadyRead(-1);
282 lineLength = device->peek(buf, 2);
283 if (lineLength == -1)
284 break;
285 if (buf[0] == 'c') // exit, the next line is not part of this commit no more.
286 break;
287 continue;
290 else if (endingLinefeed) {
291 priv->logMessage = priv->logMessage + "\n";
292 endingLinefeed = false;
294 priv->logMessage = priv->logMessage + line;
296 else if (line.length() > 40) {
297 File file;
298 file.setOldProtection(line.mid(1, 6));
299 file.setProtection(line.mid(8, 6));
300 file.setOldSha1(line.mid(15, 7));
301 file.setSha1(line.mid(26, 7));
302 file.setFileName(QByteArray(buf + 39, lineLength - 40)); // immediately cut off the linefeed
303 switch (buf[37]) {
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);
310 endingLinefeed = false;
315 Commit answer;
316 Q_ASSERT(answer.d == 0); // make sure there will be no mem-leak
317 answer.d = priv;
318 return answer;