2 * Copyright (C) 2007 Thiago Macieira <thiago@kde.org>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "repository.h"
20 #include <QTextStream>
22 #include <QLinkedList>
24 static const int maxSimultaneousProcesses
= 100;
26 class ProcessCache
: QLinkedList
<Repository
*>
29 void touch(Repository
*repo
)
33 // if the cache is too big, remove from the front
34 while (size() >= maxSimultaneousProcesses
)
35 takeFirst()->closeFastImport();
41 inline void remove(Repository
*repo
)
43 #if QT_VERSION >= 0x040400
50 static ProcessCache processCache
;
52 Repository::Repository(const Rules::Repository
&rule
)
53 : name(rule
.name
), commitCount(0), outstandingTransactions(0), processHasStarted(false)
55 foreach (Rules::Repository::Branch branchRule
, rule
.branches
) {
57 branch
.created
= 0; // not created
59 branches
.insert(branchRule
.name
, branch
);
62 // create the default branch
63 branches
["master"].created
= 1;
65 fastImport
.setWorkingDirectory(name
);
68 Repository::~Repository()
70 Q_ASSERT(outstandingTransactions
== 0);
74 void Repository::closeFastImport()
76 if (fastImport
.state() != QProcess::NotRunning
) {
77 fastImport
.write("checkpoint\n");
78 fastImport
.waitForBytesWritten(-1);
79 fastImport
.closeWriteChannel();
80 if (!fastImport
.waitForFinished()) {
81 fastImport
.terminate();
82 if (!fastImport
.waitForFinished(200))
83 qWarning() << "git-fast-import for repository" << name
<< "did not die";
86 processHasStarted
= false;
87 processCache
.remove(this);
90 void Repository::reloadBranches()
93 revParse
.setWorkingDirectory(name
);
94 revParse
.start("git", QStringList() << "rev-parse" << "--symbolic" << "--branches");
95 revParse
.waitForFinished(-1);
97 if (revParse
.exitCode() == 0 && revParse
.bytesAvailable()) {
98 while (revParse
.canReadLine()) {
99 QByteArray branchName
= revParse
.readLine().trimmed();
101 //qDebug() << "Repo" << name << "reloaded branch" << branchName;
102 branches
[branchName
].created
= 1;
103 fastImport
.write("reset refs/heads/" + branchName
+
104 "\nfrom refs/heads/" + branchName
+ "^0\n\n"
105 "progress Branch refs/heads/" + branchName
+ " reloaded\n");
110 void Repository::createBranch(const QString
&branch
, int revnum
,
111 const QString
&branchFrom
, int)
114 if (!branches
.contains(branch
)) {
115 qWarning() << branch
<< "is not a known branch in repository" << name
<< endl
116 << "Going to create it automatically";
119 QByteArray branchRef
= branch
.toUtf8();
120 if (!branchRef
.startsWith("refs/"))
121 branchRef
.prepend("refs/heads/");
123 Branch
&br
= branches
[branch
];
124 if (br
.created
&& br
.created
!= revnum
) {
125 QByteArray backupBranch
= branchRef
+ '_' + QByteArray::number(revnum
);
126 qWarning() << branch
<< "already exists; backing up to" << backupBranch
;
128 fastImport
.write("reset " + backupBranch
+ "\nfrom " + branchRef
+ "\n\n");
131 // now create the branch
133 QByteArray branchFromRef
= branchFrom
.toUtf8();
134 if (!branchFromRef
.startsWith("refs/"))
135 branchFromRef
.prepend("refs/heads/");
137 if (!branches
.contains(branchFrom
) || !branches
.value(branchFrom
).created
) {
138 qCritical() << branch
<< "in repository" << name
139 << "is branching from branch" << branchFrom
140 << "but the latter doesn't exist. Can't continue.";
144 fastImport
.write("reset " + branchRef
+ "\nfrom " + branchFromRef
+ "\n\n"
145 "progress Branch " + branchRef
+ " created from "
146 + branchFromRef
+ " r" + QByteArray::number(revnum
) + "\n\n");
149 Repository::Transaction
*Repository::newTransaction(const QString
&branch
, const QString
&svnprefix
,
153 if (!branches
.contains(branch
)) {
154 qWarning() << branch
<< "is not a known branch in repository" << name
<< endl
155 << "Going to create it automatically";
158 Transaction
*txn
= new Transaction
;
159 txn
->repository
= this;
160 txn
->branch
= branch
.toUtf8();
161 txn
->svnprefix
= svnprefix
.toUtf8();
163 txn
->revnum
= revnum
;
165 if ((++commitCount
% 10000) == 0)
166 // write everything to disk every 10000 commits
167 fastImport
.write("checkpoint\n");
168 if (outstandingTransactions
++ == 0)
169 lastmark
= 1; // reset the mark number
173 void Repository::createAnnotatedTag(const QString
&ref
, const QString
&svnprefix
,
175 const QByteArray
&author
, uint dt
,
176 const QByteArray
&log
)
178 QString tagName
= ref
;
179 if (tagName
.startsWith("refs/tags/"))
180 tagName
.remove(0, 10);
182 if (!annotatedTags
.contains(tagName
))
183 printf("Creating annotated tag %s (%s)\n", qPrintable(tagName
), qPrintable(ref
));
185 printf("Re-creating annotated tag %s\n", qPrintable(tagName
));
187 AnnotatedTag
&tag
= annotatedTags
[tagName
];
188 tag
.supportingRef
= ref
;
189 tag
.svnprefix
= svnprefix
.toUtf8();
196 void Repository::finalizeTags()
198 if (annotatedTags
.isEmpty())
201 printf("Finalising tags for %s...", qPrintable(name
));
204 QHash
<QString
, AnnotatedTag
>::ConstIterator it
= annotatedTags
.constBegin();
205 for ( ; it
!= annotatedTags
.constEnd(); ++it
) {
206 const QString
&tagName
= it
.key();
207 const AnnotatedTag
&tag
= it
.value();
209 QByteArray message
= tag
.log
;
210 if (!message
.endsWith('\n'))
212 if (Options::globalOptions
->switches
.value("metadata", true))
213 message
+= "\nsvn path=" + tag
.svnprefix
+ "; revision=" + QByteArray::number(tag
.revnum
) + "\n";
216 QByteArray branchRef
= tag
.supportingRef
.toUtf8();
217 if (!branchRef
.startsWith("refs/"))
218 branchRef
.prepend("refs/heads/");
220 QTextStream
s(&fastImport
);
221 s
<< "progress Creating annotated tag " << tagName
<< " from ref " << branchRef
<< endl
222 << "tag " << tagName
<< endl
223 << "from " << branchRef
<< endl
224 << "tagger " << QString::fromUtf8(tag
.author
) << ' ' << tag
.dt
<< " -0000" << endl
225 << "data " << message
.length() << endl
;
228 fastImport
.write(message
);
229 fastImport
.putChar('\n');
230 if (!fastImport
.waitForBytesWritten(-1))
231 qFatal("Failed to write to process: %s", qPrintable(fastImport
.errorString()));
233 printf(" %s", qPrintable(tagName
));
237 while (fastImport
.bytesToWrite())
238 if (!fastImport
.waitForBytesWritten(-1))
239 qFatal("Failed to write to process: %s", qPrintable(fastImport
.errorString()));
243 void Repository::startFastImport()
245 if (fastImport
.state() == QProcess::NotRunning
) {
246 if (processHasStarted
)
247 qFatal("git-fast-import has been started once and crashed?");
248 processHasStarted
= true;
251 QString outputFile
= name
;
252 outputFile
.replace('/', '_');
253 outputFile
.prepend("log-");
254 fastImport
.setStandardOutputFile(outputFile
, QIODevice::Append
);
255 fastImport
.setProcessChannelMode(QProcess::MergedChannels
);
258 fastImport
.start("git", QStringList() << "fast-import");
260 fastImport
.start("/bin/cat", QStringList());
267 Repository::Transaction::~Transaction()
269 --repository
->outstandingTransactions
;
272 void Repository::Transaction::setAuthor(const QByteArray
&a
)
277 void Repository::Transaction::setDateTime(uint dt
)
282 void Repository::Transaction::setLog(const QByteArray
&l
)
287 void Repository::Transaction::deleteFile(const QString
&path
)
289 deletedFiles
.append(path
);
292 QIODevice
*Repository::Transaction::addFile(const QString
&path
, int mode
, qint64 length
)
294 int mark
= ++repository
->lastmark
;
296 if (modifiedFiles
.capacity() == 0)
297 modifiedFiles
.reserve(2048);
298 modifiedFiles
.append("M ");
299 modifiedFiles
.append(QByteArray::number(mode
, 8));
300 modifiedFiles
.append(" :");
301 modifiedFiles
.append(QByteArray::number(mark
));
302 modifiedFiles
.append(' ');
303 modifiedFiles
.append(path
.toUtf8());
304 modifiedFiles
.append("\n");
307 repository
->fastImport
.write("blob\nmark :");
308 repository
->fastImport
.write(QByteArray::number(mark
));
309 repository
->fastImport
.write("\ndata ");
310 repository
->fastImport
.write(QByteArray::number(length
));
311 repository
->fastImport
.write("\n", 1);
314 return &repository
->fastImport
;
317 void Repository::Transaction::commit()
319 processCache
.touch(repository
);
321 // create the commit message
322 QByteArray message
= log
;
323 if (!message
.endsWith('\n'))
325 if (Options::globalOptions
->switches
.value("metadata", true))
326 message
+= "\nsvn path=" + svnprefix
+ "; revision=" + QByteArray::number(revnum
) + "\n";
329 QByteArray branchRef
= branch
;
330 if (!branchRef
.startsWith("refs/"))
331 branchRef
.prepend("refs/heads/");
333 QTextStream
s(&repository
->fastImport
);
334 s
<< "commit " << branchRef
<< endl
;
335 s
<< "committer " << QString::fromUtf8(author
) << ' ' << datetime
<< " -0000" << endl
;
337 Branch
&br
= repository
->branches
[branch
];
339 qWarning() << "Branch" << branch
<< "in repository" << repository
->name
<< "doesn't exist at revision"
340 << revnum
<< "-- did you resume from the wrong revision?";
344 s
<< "data " << message
.length() << endl
;
347 repository
->fastImport
.write(message
);
348 repository
->fastImport
.putChar('\n');
350 // write the file deletions
351 if (deletedFiles
.contains(""))
352 repository
->fastImport
.write("deleteall\n");
354 foreach (QString df
, deletedFiles
)
355 repository
->fastImport
.write("D " + df
.toUtf8() + "\n");
357 // write the file modifications
358 repository
->fastImport
.write(modifiedFiles
);
360 repository
->fastImport
.write("\nprogress Commit #" +
361 QByteArray::number(repository
->commitCount
) +
362 " branch " + branch
+
363 " = SVN r" + QByteArray::number(revnum
) + "\n\n");
364 printf(" %d modifications to \"%s\"",
365 deletedFiles
.count() + modifiedFiles
.count(),
366 qPrintable(repository
->name
));
368 while (repository
->fastImport
.bytesToWrite())
369 if (!repository
->fastImport
.waitForBytesWritten(-1))
370 qFatal("Failed to write to process: %s", qPrintable(repository
->fastImport
.errorString()));