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"
19 #include <QTextStream>
21 #include <QLinkedList>
23 static const int maxSimultaneousProcesses
= 100;
25 class ProcessCache
: QLinkedList
<Repository
*>
28 void touch(Repository
*repo
)
32 // if the cache is too big, remove from the front
33 while (size() >= maxSimultaneousProcesses
)
34 takeFirst()->closeFastImport();
40 inline void remove(Repository
*repo
)
45 static ProcessCache processCache
;
47 Repository::Repository(const Rules::Repository
&rule
)
48 : name(rule
.name
), commitCount(0), outstandingTransactions(0), processHasStarted(false)
50 foreach (Rules::Repository::Branch branchRule
, rule
.branches
) {
52 branch
.created
= 0; // not created
54 branches
.insert(branchRule
.name
, branch
);
57 // create the default branch
58 branches
["master"].created
= 1;
60 fastImport
.setWorkingDirectory(name
);
63 Repository::~Repository()
65 Q_ASSERT(outstandingTransactions
== 0);
69 void Repository::closeFastImport()
71 if (fastImport
.state() != QProcess::NotRunning
) {
72 fastImport
.write("checkpoint\n");
73 fastImport
.waitForBytesWritten(-1);
74 fastImport
.closeWriteChannel();
75 if (!fastImport
.waitForFinished()) {
76 fastImport
.terminate();
77 if (!fastImport
.waitForFinished(200))
78 qWarning() << "git-fast-import for repository" << name
<< "did not die";
81 processHasStarted
= false;
82 processCache
.remove(this);
85 void Repository::reloadBranches()
88 revParse
.setWorkingDirectory(name
);
89 revParse
.start("git", QStringList() << "rev-parse" << "--symbolic" << "--branches");
90 revParse
.waitForFinished(-1);
92 if (revParse
.exitCode() == 0 && revParse
.bytesAvailable()) {
93 while (revParse
.canReadLine()) {
94 QByteArray branchName
= revParse
.readLine().trimmed();
96 //qDebug() << "Repo" << name << "reloaded branch" << branchName;
97 branches
[branchName
].created
= 1;
98 fastImport
.write("reset refs/heads/" + branchName
+
99 "\nfrom refs/heads/" + branchName
+ "^0\n\n"
100 "progress Branch refs/heads/" + branchName
+ " reloaded\n");
105 void Repository::createBranch(const QString
&branch
, int revnum
,
106 const QString
&branchFrom
, int)
109 if (!branches
.contains(branch
)) {
110 qWarning() << branch
<< "is not a known branch in repository" << name
<< endl
111 << "Going to create it automatically";
114 QByteArray branchRef
= branch
.toUtf8();
115 if (!branchRef
.startsWith("refs/"))
116 branchRef
.prepend("refs/heads/");
118 Branch
&br
= branches
[branch
];
119 if (br
.created
&& br
.created
!= revnum
) {
120 QByteArray backupBranch
= branchRef
+ '_' + QByteArray::number(revnum
);
121 qWarning() << branch
<< "already exists; backing up to" << backupBranch
;
123 fastImport
.write("reset " + backupBranch
+ "\nfrom " + branchRef
+ "\n\n");
126 // now create the branch
128 QByteArray branchFromRef
= branchFrom
.toUtf8();
129 if (!branchFromRef
.startsWith("refs/"))
130 branchFromRef
.prepend("refs/heads/");
132 if (!branches
.contains(branchFrom
) || !branches
.value(branchFrom
).created
) {
133 qCritical() << branch
<< "in repository" << name
134 << "is branching from branch" << branchFrom
135 << "but the latter doesn't exist. Can't continue.";
139 fastImport
.write("reset " + branchRef
+ "\nfrom " + branchFromRef
+ "\n\n"
140 "progress Branch " + branchRef
+ " created from " + branchFromRef
+ "\n\n");
143 Repository::Transaction
*Repository::newTransaction(const QString
&branch
, const QString
&svnprefix
,
147 if (!branches
.contains(branch
)) {
148 qWarning() << branch
<< "is not a known branch in repository" << name
<< endl
149 << "Going to create it automatically";
152 Transaction
*txn
= new Transaction
;
153 txn
->repository
= this;
154 txn
->branch
= branch
.toUtf8();
155 txn
->svnprefix
= svnprefix
.toUtf8();
157 txn
->revnum
= revnum
;
159 if ((++commitCount
% 10000) == 0)
160 // write everything to disk every 10000 commits
161 fastImport
.write("checkpoint\n");
162 if (++outstandingTransactions
== 0)
163 lastmark
= 1; // reset the mark number
167 void Repository::startFastImport()
169 if (fastImport
.state() == QProcess::NotRunning
) {
170 if (processHasStarted
)
171 qFatal("git-fast-import has been started once and crashed?");
172 processHasStarted
= true;
175 QString outputFile
= name
;
176 outputFile
.replace('/', '_');
177 outputFile
.prepend("log-");
178 fastImport
.setStandardOutputFile(outputFile
, QIODevice::Append
);
179 fastImport
.setProcessChannelMode(QProcess::MergedChannels
);
182 fastImport
.start("git", QStringList() << "fast-import");
184 fastImport
.start("/bin/cat", QStringList());
191 Repository::Transaction::~Transaction()
193 --repository
->outstandingTransactions
;
196 void Repository::Transaction::setAuthor(const QByteArray
&a
)
201 void Repository::Transaction::setDateTime(uint dt
)
206 void Repository::Transaction::setLog(const QByteArray
&l
)
211 void Repository::Transaction::deleteFile(const QString
&path
)
213 deletedFiles
.append(path
);
216 QIODevice
*Repository::Transaction::addFile(const QString
&path
, int mode
, qint64 length
)
218 int mark
= ++repository
->lastmark
;
220 if (modifiedFiles
.capacity() == 0)
221 modifiedFiles
.reserve(2048);
222 modifiedFiles
.append("M ");
223 modifiedFiles
.append(QByteArray::number(mode
, 8));
224 modifiedFiles
.append(" :");
225 modifiedFiles
.append(QByteArray::number(mark
));
226 modifiedFiles
.append(' ');
227 modifiedFiles
.append(path
.toUtf8());
228 modifiedFiles
.append("\n");
231 repository
->fastImport
.write("blob\nmark :");
232 repository
->fastImport
.write(QByteArray::number(mark
));
233 repository
->fastImport
.write("\ndata ");
234 repository
->fastImport
.write(QByteArray::number(length
));
235 repository
->fastImport
.write("\n", 1);
238 return &repository
->fastImport
;
241 void Repository::Transaction::commit()
243 processCache
.touch(repository
);
245 // create the commit message
246 QByteArray message
= log
;
247 if (!message
.endsWith('\n'))
249 message
+= "\nsvn path=" + svnprefix
+ "; revision=" + QByteArray::number(revnum
) + "\n";
252 QByteArray branchRef
= branch
;
253 if (!branchRef
.startsWith("refs/"))
254 branchRef
.prepend("refs/heads/");
256 QTextStream
s(&repository
->fastImport
);
257 s
<< "commit " << branchRef
<< endl
;
258 s
<< "committer " << QString::fromUtf8(author
) << ' ' << datetime
<< " -0000" << endl
;
260 Branch
&br
= repository
->branches
[branch
];
262 qWarning() << "Branch" << branch
<< "in repository" << repository
->name
<< "doesn't exist at revision"
263 << revnum
<< "-- did you resume from the wrong revision?";
267 s
<< "data " << message
.length() << endl
;
270 repository
->fastImport
.write(message
);
271 repository
->fastImport
.putChar('\n');
273 // write the file deletions
274 if (deletedFiles
.contains(""))
275 repository
->fastImport
.write("deleteall\n");
277 foreach (QString df
, deletedFiles
)
278 repository
->fastImport
.write("D " + df
.toUtf8() + "\n");
280 // write the file modifications
281 repository
->fastImport
.write(modifiedFiles
);
283 repository
->fastImport
.write("\nprogress Commit #" +
284 QByteArray::number(repository
->commitCount
) +
285 " branch " + branch
+
286 " = SVN r" + QByteArray::number(revnum
) + "\n\n");
287 printf(" %d modifications to \"%s\"",
288 deletedFiles
.count() + modifiedFiles
.count(),
289 qPrintable(repository
->name
));
291 while (repository
->fastImport
.bytesToWrite())
292 if (!repository
->fastImport
.waitForBytesWritten(-1))
293 qFatal("Failed to write to process: %s", qPrintable(repository
->fastImport
.errorString()));