aboutsummaryrefslogtreecommitdiffstats
path: root/src/repository.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/repository.cpp')
-rw-r--r--src/repository.cpp621
1 files changed, 505 insertions, 116 deletions
diff --git a/src/repository.cpp b/src/repository.cpp
index be73df6..5bcd8dd 100644
--- a/src/repository.cpp
+++ b/src/repository.cpp
@@ -25,10 +25,161 @@
static const int maxSimultaneousProcesses = 100;
-class ProcessCache: QLinkedList<Repository *>
+static const int maxMark = (1 << 20) - 1; // some versions of git-fast-import are buggy for larger values of maxMark
+
+class FastImportRepository : public Repository
+{
+public:
+ class Transaction : public Repository::Transaction
+ {
+ Q_DISABLE_COPY(Transaction)
+ friend class FastImportRepository;
+
+ FastImportRepository *repository;
+ QByteArray branch;
+ QByteArray svnprefix;
+ QByteArray author;
+ QByteArray log;
+ uint datetime;
+ int revnum;
+
+ QVector<int> merges;
+
+ QStringList deletedFiles;
+ QByteArray modifiedFiles;
+
+ inline Transaction() {}
+ public:
+ ~Transaction();
+ void commit();
+
+ void setAuthor(const QByteArray &author);
+ void setDateTime(uint dt);
+ void setLog(const QByteArray &log);
+
+ void noteCopyFromBranch (const QString &prevbranch, int revFrom);
+
+ void deleteFile(const QString &path);
+ QIODevice *addFile(const QString &path, int mode, qint64 length);
+ };
+ FastImportRepository(const Rules::Repository &rule);
+ int setupIncremental(int &cutoff);
+ void restoreLog();
+ ~FastImportRepository();
+
+ void reloadBranches();
+ int createBranch(const QString &branch, int revnum,
+ const QString &branchFrom, int revFrom);
+ int deleteBranch(const QString &branch, int revnum);
+ Repository::Transaction *newTransaction(const QString &branch, const QString &svnprefix, int revnum);
+
+ void createAnnotatedTag(const QString &name, const QString &svnprefix, int revnum,
+ const QByteArray &author, uint dt,
+ const QByteArray &log);
+ void finalizeTags();
+
+private:
+ struct Branch
+ {
+ int created;
+ QVector<int> commits;
+ QVector<int> marks;
+ };
+ struct AnnotatedTag
+ {
+ QString supportingRef;
+ QByteArray svnprefix;
+ QByteArray author;
+ QByteArray log;
+ uint dt;
+ int revnum;
+ };
+
+ QHash<QString, Branch> branches;
+ QHash<QString, AnnotatedTag> annotatedTags;
+ QString name;
+ QProcess fastImport;
+ int commitCount;
+ int outstandingTransactions;
+
+ /* starts at 0, and counts up. */
+ int last_commit_mark;
+
+ /* starts at maxMark and counts down. Reset after each SVN revision */
+ int next_file_mark;
+
+ bool processHasStarted;
+
+ void startFastImport();
+ void closeFastImport();
+
+ // called when a transaction is deleted
+ void forgetTransaction(Transaction *t);
+
+ int resetBranch(const QString &branch, int revnum, int mark, const QByteArray &resetTo, const QByteArray &comment);
+ int markFrom(const QString &branchFrom, int branchRevNum, QByteArray &desc);
+
+ friend class ProcessCache;
+ Q_DISABLE_COPY(FastImportRepository)
+};
+
+class PrefixingRepository : public Repository
+{
+ Repository *repo;
+ QString prefix;
+public:
+ class Transaction : public Repository::Transaction
+ {
+ Q_DISABLE_COPY(Transaction)
+
+ Repository::Transaction *txn;
+ QString prefix;
+ public:
+ Transaction(Repository::Transaction *t, const QString &p) : txn(t), prefix(p) {}
+ ~Transaction() { delete txn; }
+ void commit() { txn->commit(); }
+
+ void setAuthor(const QByteArray &author) { txn->setAuthor(author); }
+ void setDateTime(uint dt) { txn->setDateTime(dt); }
+ void setLog(const QByteArray &log) { txn->setLog(log); }
+
+ void noteCopyFromBranch (const QString &prevbranch, int revFrom)
+ { txn->noteCopyFromBranch(prevbranch, revFrom); }
+
+ void deleteFile(const QString &path) { txn->deleteFile(prefix + path); }
+ QIODevice *addFile(const QString &path, int mode, qint64 length)
+ { return txn->addFile(prefix + path, mode, length); }
+ };
+
+ PrefixingRepository(Repository *r, const QString &p) : repo(r), prefix(p) {}
+
+ int setupIncremental(int &) { return 1; }
+ void restoreLog() {}
+
+ int createBranch(const QString &branch, int revnum,
+ const QString &branchFrom, int revFrom)
+ { return repo->createBranch(branch, revnum, branchFrom, revFrom); }
+
+ int deleteBranch(const QString &branch, int revnum)
+ { return repo->deleteBranch(branch, revnum); }
+
+ Repository::Transaction *newTransaction(const QString &branch, const QString &svnprefix, int revnum)
+ {
+ Repository::Transaction *t = repo->newTransaction(branch, svnprefix, revnum);
+ return new Transaction(t, prefix);
+ }
+
+ void createAnnotatedTag(const QString &name, const QString &svnprefix, int revnum,
+ const QByteArray &author, uint dt,
+ const QByteArray &log)
+ { repo->createAnnotatedTag(name, svnprefix, revnum, author, dt, log); }
+ void finalizeTags() { /* loop that called this will invoke it on 'repo' too */ }
+};
+
+class ProcessCache: QLinkedList<FastImportRepository *>
{
public:
- void touch(Repository *repo)
+ void touch(FastImportRepository *repo)
{
remove(repo);
@@ -40,7 +191,7 @@ public:
append(repo);
}
- inline void remove(Repository *repo)
+ inline void remove(FastImportRepository *repo)
{
#if QT_VERSION >= 0x040400
removeOne(repo);
@@ -51,8 +202,27 @@ public:
};
static ProcessCache processCache;
-Repository::Repository(const Rules::Repository &rule)
- : name(rule.name), commitCount(0), outstandingTransactions(0), lastmark(0), processHasStarted(false)
+Repository *makeRepository(const Rules::Repository &rule, const QHash<QString, Repository *> &repositories)
+{
+ if (rule.forwardTo.isEmpty())
+ return new FastImportRepository(rule);
+ Repository *r = repositories[rule.forwardTo];
+ if (!r) {
+ qCritical() << "no repository with name" << rule.forwardTo << "found at line" << rule.lineNumber;
+ return r;
+ }
+ return new PrefixingRepository(r, rule.prefix);
+}
+
+static QString marksFileName(QString name)
+{
+ name.replace('/', '_');
+ name.prepend("marks-");
+ return name;
+}
+
+FastImportRepository::FastImportRepository(const Rules::Repository &rule)
+ : name(rule.name), commitCount(0), outstandingTransactions(0), last_commit_mark(0), next_file_mark(maxMark), processHasStarted(false)
{
foreach (Rules::Repository::Branch branchRule, rule.branches) {
Branch branch;
@@ -73,17 +243,167 @@ Repository::Repository(const Rules::Repository &rule)
init.setWorkingDirectory(name);
init.start("git", QStringList() << "--bare" << "init");
init.waitForFinished(-1);
+ {
+ QFile marks(name + "/" + marksFileName(name));
+ marks.open(QIODevice::WriteOnly);
+ marks.close();
+ }
}
}
}
-Repository::~Repository()
+static QString logFileName(QString name)
+{
+ name.replace('/', '_');
+ name.prepend("log-");
+ return name;
+}
+
+static int lastValidMark(QString name)
+{
+ QFile marksfile(name + "/" + marksFileName(name));
+ if (!marksfile.open(QIODevice::ReadOnly))
+ return 0;
+
+ int prev_mark = 0;
+
+ int lineno = 0;
+ while (!marksfile.atEnd()) {
+ QString line = marksfile.readLine();
+ ++lineno;
+ if (line.isEmpty())
+ continue;
+
+ int mark = 0;
+ if (line[0] == ':') {
+ int sp = line.indexOf(' ');
+ if (sp != -1) {
+ QString m = line.mid(1, sp-1);
+ mark = m.toInt();
+ }
+ }
+
+ if (!mark) {
+ qCritical() << marksfile.fileName() << "line" << lineno << "marks file corrupt?";
+ return 0;
+ }
+
+ if (mark == prev_mark) {
+ qCritical() << marksfile.fileName() << "line" << lineno << "marks file has duplicates";
+ return 0;
+ }
+
+ if (mark < prev_mark) {
+ qCritical() << marksfile.fileName() << "line" << lineno << "marks file not sorted";
+ return 0;
+ }
+
+ if (mark > prev_mark + 1)
+ break;
+
+ prev_mark = mark;
+ }
+
+ return prev_mark;
+}
+
+int FastImportRepository::setupIncremental(int &cutoff)
+{
+ QFile logfile(logFileName(name));
+ if (!logfile.exists())
+ return 1;
+
+ logfile.open(QIODevice::ReadWrite);
+
+ QRegExp progress("progress SVN r(\\d+) branch (.*) = :(\\d+)");
+
+ int last_valid_mark = lastValidMark(name);
+
+ int last_revnum = 0;
+ qint64 pos = 0;
+ int retval = 0;
+ QString bkup = logfile.fileName() + ".old";
+
+ while (!logfile.atEnd()) {
+ pos = logfile.pos();
+ QByteArray line = logfile.readLine();
+ int hash = line.indexOf('#');
+ if (hash != -1)
+ line.truncate(hash);
+ line = line.trimmed();
+ if (line.isEmpty())
+ continue;
+ if (!progress.exactMatch(line))
+ continue;
+
+ int revnum = progress.cap(1).toInt();
+ QString branch = progress.cap(2);
+ int mark = progress.cap(3).toInt();
+
+ if (revnum >= cutoff)
+ goto beyond_cutoff;
+
+ if (revnum < last_revnum)
+ qWarning() << name << "revision numbers are not monotonic: "
+ << "got" << QString::number(last_revnum)
+ << "and then" << QString::number(revnum);
+
+ if (mark > last_valid_mark) {
+ qWarning() << name << "unknown commit mark found: rewinding -- did you hit Ctrl-C?";
+ cutoff = revnum;
+ goto beyond_cutoff;
+ }
+
+ last_revnum = revnum;
+
+ if (last_commit_mark < mark)
+ last_commit_mark = mark;
+
+ Branch &br = branches[branch];
+ if (!br.created || !mark || !br.marks.last())
+ br.created = revnum;
+ br.commits.append(revnum);
+ br.marks.append(mark);
+ }
+
+ retval = last_revnum + 1;
+ if (retval == cutoff)
+ /*
+ * If a stale backup file exists already, remove it, so that
+ * we don't confuse ourselves in 'restoreLog()'
+ */
+ QFile::remove(bkup);
+
+ return retval;
+
+ beyond_cutoff:
+ // backup file, since we'll truncate
+ QFile::remove(bkup);
+ logfile.copy(bkup);
+
+ // truncate, so that we ignore the rest of the revisions
+ qDebug() << name << "truncating history to revision" << cutoff;
+ logfile.resize(pos);
+ return cutoff;
+}
+
+void FastImportRepository::restoreLog()
+{
+ QString file = logFileName(name);
+ QString bkup = file + ".old";
+ if (!QFile::exists(bkup))
+ return;
+ QFile::remove(file);
+ QFile::rename(bkup, file);
+}
+
+FastImportRepository::~FastImportRepository()
{
Q_ASSERT(outstandingTransactions == 0);
closeFastImport();
}
-void Repository::closeFastImport()
+void FastImportRepository::closeFastImport()
{
if (fastImport.state() != QProcess::NotRunning) {
fastImport.write("checkpoint\n");
@@ -97,43 +417,52 @@ void Repository::closeFastImport()
}
processHasStarted = false;
processCache.remove(this);
- // Save the exported marks
- QString revsFile = name;
- revsFile.replace('/', '_');
- revsFile.prepend("revisions-");
- QFile exportedMarks(revsFile);
- qDebug() << exportedMarks.open(QIODevice::Truncate | QIODevice::Text | QIODevice::WriteOnly);
-
- int mark;
- foreach(mark, commitMarks.keys())
- {
- exportedMarks.write(QString(":%2 r%1\n").arg(mark).arg(commitMarks.value(mark)).toLocal8Bit());
+}
+
+void FastImportRepository::reloadBranches()
+{
+ foreach (QString branch, branches.keys()) {
+ Branch &br = branches[branch];
+
+ if (!br.marks.count() || !br.marks.last())
+ continue;
+
+ QByteArray branchRef = branch.toUtf8();
+ if (!branchRef.startsWith("refs/"))
+ branchRef.prepend("refs/heads/");
+
+ fastImport.write("reset " + branchRef +
+ "\nfrom :" + QByteArray::number(br.marks.last()) + "\n\n"
+ "progress Branch " + branchRef + " reloaded\n");
}
- exportedMarks.close();
}
-void Repository::reloadBranches()
+int FastImportRepository::markFrom(const QString &branchFrom, int branchRevNum, QByteArray &branchFromDesc)
{
- QProcess revParse;
- revParse.setWorkingDirectory(name);
- revParse.start("git", QStringList() << "rev-parse" << "--symbolic" << "--branches");
- revParse.waitForFinished(-1);
-
- if (revParse.exitCode() == 0 && revParse.bytesAvailable()) {
- while (revParse.canReadLine()) {
- QByteArray branchName = revParse.readLine().trimmed();
-
- //qDebug() << "Repo" << name << "reloaded branch" << branchName;
- branches[branchName].created = 1;
- fastImport.write("reset refs/heads/" + branchName +
- "\nfrom refs/heads/" + branchName + "^0\n\n"
- "progress Branch refs/heads/" + branchName + " reloaded\n");
- }
+ Branch &brFrom = branches[branchFrom];
+ if (!brFrom.created)
+ return -1;
+
+ if (branchRevNum == brFrom.commits.last())
+ return brFrom.marks.last();
+
+ QVector<int>::const_iterator it = qUpperBound(brFrom.commits, branchRevNum);
+ if (it == brFrom.commits.begin())
+ return 0;
+
+ int closestCommit = *--it;
+
+ if (!branchFromDesc.isEmpty()) {
+ branchFromDesc += " at r" + QByteArray::number(branchRevNum);
+ if (closestCommit != branchRevNum)
+ branchFromDesc += " => r" + QByteArray::number(closestCommit);
}
+
+ return brFrom.marks[it - brFrom.commits.begin()];
}
-void Repository::createBranch(const QString &branch, int revnum,
- const QString &branchFrom, int branchRevNum)
+int FastImportRepository::createBranch(const QString &branch, int revnum,
+ const QString &branchFrom, int branchRevNum)
{
startFastImport();
if (!branches.contains(branch)) {
@@ -141,64 +470,64 @@ void Repository::createBranch(const QString &branch, int revnum,
<< "Going to create it automatically";
}
- QByteArray branchRef = branch.toUtf8();
- if (!branchRef.startsWith("refs/"))
- branchRef.prepend("refs/heads/");
-
-
- Branch &br = branches[branch];
- if (br.created && br.created != revnum) {
- QByteArray backupBranch = branchRef + '_' + QByteArray::number(revnum);
- qWarning() << branch << "already exists; backing up to" << backupBranch;
+ QByteArray branchFromDesc = "from branch " + branchFrom.toUtf8();
+ int mark = markFrom(branchFrom, branchRevNum, branchFromDesc);
- fastImport.write("reset " + backupBranch + "\nfrom " + branchRef + "\n\n");
+ if (mark == -1) {
+ qCritical() << branch << "in repository" << name
+ << "is branching from branch" << branchFrom
+ << "but the latter doesn't exist. Can't continue.";
+ return EXIT_FAILURE;
}
- // now create the branch
- br.created = revnum;
- QByteArray branchFromRef;
- const int closestCommit = *qLowerBound(commitMarks.keys(), branchRevNum);
- if(exportedCommits.contains(closestCommit))
- {
- bool pathFound = false;
- QString path;
- foreach(path, exportedCommits[closestCommit]) {
- if(path.contains(branchFrom)) {
- pathFound = true;
- break;
- }
- }
-
- if(pathFound) {
- branchFromRef = ":" + QByteArray::number(commitMarks.value(closestCommit));
- qDebug() << branch << "in repository" << name << "is branching from" << closestCommit << "(svn reports r" << branchRevNum << ") git mark:" << branchFromRef;
- } else {
- qWarning() << branch << "in repository" << name << "is branching from a revision that doesn't touch the branch from path, branching from current revision";
- branchFromRef = branchFrom.toUtf8();
- if (!branchFromRef.startsWith("refs/"))
- branchFromRef.prepend("refs/heads/");
- }
- } else {
+ QByteArray branchFromRef = ":" + QByteArray::number(mark);
+ if (!mark) {
qWarning() << branch << "in repository" << name << "is branching but no exported commits exist in repository"
<< "creating an empty branch.";
branchFromRef = branchFrom.toUtf8();
if (!branchFromRef.startsWith("refs/"))
branchFromRef.prepend("refs/heads/");
+ branchFromDesc += ", deleted/unknown";
}
- if (!branches.contains(branchFrom) || !branches.value(branchFrom).created) {
- qCritical() << branch << "in repository" << name
- << "is branching from branch" << branchFrom
- << "but the latter doesn't exist. Can't continue.";
- exit(1);
+ return resetBranch(branch, revnum, mark, branchFromRef, branchFromDesc);
+}
+
+int FastImportRepository::deleteBranch(const QString &branch, int revnum)
+{
+ startFastImport();
+
+ static QByteArray null_sha(40, '0');
+ return resetBranch(branch, revnum, 0, null_sha, "delete");
+}
+
+int FastImportRepository::resetBranch(const QString &branch, int revnum, int mark, const QByteArray &resetTo, const QByteArray &comment)
+{
+ QByteArray branchRef = branch.toUtf8();
+ if (!branchRef.startsWith("refs/"))
+ branchRef.prepend("refs/heads/");
+
+ Branch &br = branches[branch];
+ if (br.created && br.created != revnum && br.marks.last()) {
+ QByteArray backupBranch = "refs/backups/r" + QByteArray::number(revnum) + branchRef.mid(4);
+ qWarning() << "backing up branch" << branch << "to" << backupBranch;
+
+ fastImport.write("reset " + backupBranch + "\nfrom " + branchRef + "\n\n");
}
- fastImport.write("reset " + branchRef + "\nfrom " + branchFromRef + "\n\n"
- "progress Branch " + branchRef + " created from "
- + branchFromRef + " r" + QByteArray::number(branchRevNum) + "(at SVN" + QByteArray::number(revnum) + ")\n\n");
+ br.created = revnum;
+ br.commits.append(revnum);
+ br.marks.append(mark);
+
+ fastImport.write("reset " + branchRef + "\nfrom " + resetTo + "\n\n"
+ "progress SVN r" + QByteArray::number(revnum)
+ + " branch " + branch.toUtf8() + " = :" + QByteArray::number(mark)
+ + " # " + comment + "\n\n");
+
+ return EXIT_SUCCESS;
}
-Repository::Transaction *Repository::newTransaction(const QString &branch, const QString &svnprefix,
+Repository::Transaction *FastImportRepository::newTransaction(const QString &branch, const QString &svnprefix,
int revnum)
{
startFastImport();
@@ -221,7 +550,13 @@ Repository::Transaction *Repository::newTransaction(const QString &branch, const
return txn;
}
-void Repository::createAnnotatedTag(const QString &ref, const QString &svnprefix,
+void FastImportRepository::forgetTransaction(Transaction *)
+{
+ if (!--outstandingTransactions)
+ next_file_mark = maxMark;
+}
+
+void FastImportRepository::createAnnotatedTag(const QString &ref, const QString &svnprefix,
int revnum,
const QByteArray &author, uint dt,
const QByteArray &log)
@@ -244,7 +579,7 @@ void Repository::createAnnotatedTag(const QString &ref, const QString &svnprefix
tag.dt = dt;
}
-void Repository::finalizeTags()
+void FastImportRepository::finalizeTags()
{
if (annotatedTags.isEmpty())
return;
@@ -291,7 +626,7 @@ void Repository::finalizeTags()
printf("\n");
}
-void Repository::startFastImport()
+void FastImportRepository::startFastImport()
{
if (fastImport.state() == QProcess::NotRunning) {
if (processHasStarted)
@@ -299,15 +634,13 @@ void Repository::startFastImport()
processHasStarted = true;
// start the process
- QString marksFile = name;
- marksFile.replace('/', '_');
- marksFile.prepend("marks-");
+ QString marksFile = marksFileName(name);
QStringList marksOptions;
+ marksOptions << "--import-marks=" + marksFile;
marksOptions << "--export-marks=" + marksFile;
- QString outputFile = name;
- outputFile.replace('/', '_');
- outputFile.prepend("log-");
- fastImport.setStandardOutputFile(outputFile, QIODevice::Append);
+ marksOptions << "--force";
+
+ fastImport.setStandardOutputFile(logFileName(name), QIODevice::Append);
fastImport.setProcessChannelMode(QProcess::MergedChannels);
if (!CommandLineParser::instance()->contains("dry-run")) {
@@ -320,38 +653,60 @@ void Repository::startFastImport()
}
}
-Repository::Transaction::~Transaction()
+FastImportRepository::Transaction::~Transaction()
{
- --repository->outstandingTransactions;
+ repository->forgetTransaction(this);
}
-void Repository::Transaction::setAuthor(const QByteArray &a)
+void FastImportRepository::Transaction::setAuthor(const QByteArray &a)
{
author = a;
}
-void Repository::Transaction::setDateTime(uint dt)
+void FastImportRepository::Transaction::setDateTime(uint dt)
{
datetime = dt;
}
-void Repository::Transaction::setLog(const QByteArray &l)
+void FastImportRepository::Transaction::setLog(const QByteArray &l)
{
log = l;
}
-void Repository::Transaction::deleteFile(const QString &path)
+void FastImportRepository::Transaction::noteCopyFromBranch(const QString &branchFrom, int branchRevNum)
+{
+ static QByteArray dummy;
+ int mark = repository->markFrom(branchFrom, branchRevNum, dummy);
+ Q_ASSERT(dummy.isEmpty());
+
+ if (mark == -1) {
+ qWarning() << branch << "is copying from branch" << branchFrom
+ << "but the latter doesn't exist. Continuing, assuming the files exist.";
+ } else if (mark == 0) {
+ qWarning() << "Unknown revision r" << QByteArray::number(branchRevNum)
+ << ". Continuing, assuming the files exist.";
+ } else {
+ qWarning() << "repository " + repository->name + " branch " + branch + " has some files copied from " + branchFrom + "@" + QByteArray::number(branchRevNum);
+
+ if (!merges.contains(mark))
+ merges.append(mark);
+ }
+}
+
+void FastImportRepository::Transaction::deleteFile(const QString &path)
{
QString pathNoSlash = path;
if(pathNoSlash.endsWith('/'))
pathNoSlash.chop(1);
deletedFiles.append(pathNoSlash);
- modifiedPaths.append(path);
}
-QIODevice *Repository::Transaction::addFile(const QString &path, int mode, qint64 length)
+QIODevice *FastImportRepository::Transaction::addFile(const QString &path, int mode, qint64 length)
{
- int mark = ++repository->lastmark;
+ int mark = repository->next_file_mark--;
+
+ // in case the two mark allocations meet, we might as well just abort
+ Q_ASSERT(mark > repository->last_commit_mark + 1);
if (modifiedFiles.capacity() == 0)
modifiedFiles.reserve(2048);
@@ -370,14 +725,22 @@ QIODevice *Repository::Transaction::addFile(const QString &path, int mode, qint6
repository->fastImport.write(QByteArray::number(length));
repository->fastImport.write("\n", 1);
}
- modifiedPaths.append(branch + "/" + path);
+
return &repository->fastImport;
}
-void Repository::Transaction::commit()
+void FastImportRepository::Transaction::commit()
{
processCache.touch(repository);
+ // We might be tempted to use the SVN revision number as the fast-import commit mark.
+ // However, a single SVN revision can modify multple branches, and thus lead to multiple
+ // commits in the same repo. So, we need to maintain a separate commit mark counter.
+ int mark = ++repository->last_commit_mark;
+
+ // in case the two mark allocations meet, we might as well just abort
+ Q_ASSERT(mark < repository->next_file_mark - 1);
+
// create the commit message
QByteArray message = log;
if (!message.endsWith('\n'))
@@ -385,6 +748,18 @@ void Repository::Transaction::commit()
if (CommandLineParser::instance()->contains("add-metadata"))
message += "\nsvn path=" + svnprefix + "; revision=" + QByteArray::number(revnum) + "\n";
+ int parentmark = 0;
+ Branch &br = repository->branches[branch];
+ if (br.created) {
+ parentmark = br.marks.last();
+ } else {
+ qWarning() << "Branch" << branch << "in repository" << repository->name << "doesn't exist at revision"
+ << revnum << "-- did you resume from the wrong revision?";
+ br.created = revnum;
+ }
+ br.commits.append(revnum);
+ br.marks.append(mark);
+
{
QByteArray branchRef = branch;
if (!branchRef.startsWith("refs/"))
@@ -392,24 +767,38 @@ void Repository::Transaction::commit()
QTextStream s(&repository->fastImport);
s << "commit " << branchRef << endl;
- s << "mark :" << QByteArray::number(++repository->lastmark) << endl;
- repository->commitMarks.insert(revnum, repository->lastmark);
- repository->exportedCommits.insert(revnum, modifiedPaths);
+ s << "mark :" << QByteArray::number(mark) << endl;
s << "committer " << QString::fromUtf8(author) << ' ' << datetime << " -0000" << endl;
- Branch &br = repository->branches[branch];
- if (!br.created) {
- qWarning() << "Branch" << branch << "in repository" << repository->name << "doesn't exist at revision"
- << revnum << "-- did you resume from the wrong revision?";
- br.created = revnum;
- }
-
s << "data " << message.length() << endl;
}
repository->fastImport.write(message);
repository->fastImport.putChar('\n');
+ // note some of the inferred merges
+ QByteArray desc = "";
+ int i = !!parentmark; // if parentmark != 0, there's at least one parent
+ foreach (int merge, merges) {
+ if (merge == parentmark)
+ continue;
+
+ if (++i > 16) {
+ // FIXME: options:
+ // (1) ignore the 16 parent limit
+ // (2) don't emit more than 16 parents
+ // (3) create another commit on branch to soak up additional parents
+ // we've chosen option (2) for now, since only artificial commits
+ // created by cvs2svn seem to have this issue
+ qWarning() << "too many merge parents";
+ break;
+ }
+
+ QByteArray m = " :" + QByteArray::number(merge);
+ desc += m;
+ repository->fastImport.write("merge" + m + "\n");
+ }
+
// write the file deletions
if (deletedFiles.contains(""))
repository->fastImport.write("deleteall\n");
@@ -420,10 +809,10 @@ void Repository::Transaction::commit()
// write the file modifications
repository->fastImport.write(modifiedFiles);
- repository->fastImport.write("\nprogress Commit #" +
- QByteArray::number(repository->commitCount) +
- " branch " + branch +
- " = SVN r" + QByteArray::number(revnum) + "\n\n");
+ repository->fastImport.write("\nprogress SVN r" + QByteArray::number(revnum)
+ + " branch " + branch + " = :" + QByteArray::number(mark)
+ + (desc.isEmpty() ? "" : " # merge from") + desc
+ + "\n\n");
printf(" %d modifications from SVN %s to %s/%s",
deletedFiles.count() + modifiedFiles.count(), svnprefix.data(),
qPrintable(repository->name), branch.data());