diff options
-rw-r--r-- | src/main.cpp | 15 | ||||
-rw-r--r-- | src/repository.cpp | 90 | ||||
-rw-r--r-- | src/repository.h | 11 | ||||
-rw-r--r-- | src/ruleparser.cpp | 68 | ||||
-rw-r--r-- | src/ruleparser.h | 12 | ||||
-rw-r--r-- | src/svn.cpp | 6 |
6 files changed, 193 insertions, 9 deletions
diff --git a/src/main.cpp b/src/main.cpp index 6125171..681824f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,12 +55,18 @@ QHash<QByteArray, QByteArray> loadIdentityMapFile(const QString &fileName) // Support git-svn author files, too // - svn2git native: loginname Joe User <user@example.com> // - git-svn: loginname = Joe User <user@example.com> - int rightspace = space; - if (line.indexOf(" = ") == space) - rightspace += 2; + int rightspace = line.indexOf(" = "); + int leftspace = space; + if (rightspace == -1) { + rightspace = space; + } else { + leftspace = rightspace; + rightspace += 2; + } QByteArray realname = line.mid(rightspace).trimmed(); - line.truncate(space); + line.truncate(leftspace); + result.insert(line, realname); }; file.close(); @@ -124,6 +130,7 @@ static const CommandLineOption options[] = { {"--revisions-file FILENAME", "provide a file with revision number that should be processed"}, {"--rules FILENAME[,FILENAME]", "the rules file(s) that determines what goes where"}, {"--add-metadata", "if passed, each git commit will have svn commit info"}, + {"--add-metadata-notes", "if passed, each git commit will have notes with svn commit info"}, {"--resume-from revision", "start importing at svn revision number"}, {"--max-rev revision", "stop importing at svn revision number"}, {"--dry-run", "don't actually write anything"}, diff --git a/src/repository.cpp b/src/repository.cpp index e6c0f55..8f64c28 100644 --- a/src/repository.cpp +++ b/src/repository.cpp @@ -85,6 +85,15 @@ Repository::Repository(const Rules::Repository &rule) init.setWorkingDirectory(name); init.start("git", QStringList() << "--bare" << "init"); init.waitForFinished(-1); + // Write description + if (!rule.description.isEmpty()) { + QFile fDesc(QDir(name).filePath("description")); + if (fDesc.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + fDesc.write(rule.description.toUtf8()); + fDesc.putChar('\n'); + fDesc.close(); + } + } { QFile marks(name + "/" + marksFileName(name)); marks.open(QIODevice::WriteOnly); @@ -334,6 +343,9 @@ int Repository::createBranch(const QString &branch, int revnum, qDebug() << "Creating branch:" << branch << "from" << branchFrom << "(" << branchRevNum << branchFromDesc << ")"; + // Preserve note + branches[branch].note = branches.value(branchFrom).note; + return resetBranch(branch, revnum, mark, branchFromRef, branchFromDesc); } @@ -461,7 +473,7 @@ void Repository::finalizeTags() if (!message.endsWith('\n')) message += '\n'; if (CommandLineParser::instance()->contains("add-metadata")) - message += "\nsvn path=" + tag.svnprefix + "; revision=" + QByteArray::number(tag.revnum) + "\n"; + message += "\n" + formatMetadataMessage(tag.svnprefix, tag.revnum, tagName.toUtf8()); { QByteArray branchRef = tag.supportingRef.toUtf8(); @@ -481,6 +493,19 @@ void Repository::finalizeTags() if (!fastImport.waitForBytesWritten(-1)) qFatal("Failed to write to process: %s", qPrintable(fastImport.errorString())); + // Append note to the tip commit of the supporting ref. There is no + // easy way to attach a note to the tag itself with fast-import. + if (CommandLineParser::instance()->contains("add-metadata-notes")) { + Repository::Transaction *txn = newTransaction(tag.supportingRef, tag.svnprefix, tag.revnum); + txn->setAuthor(tag.author); + txn->setDateTime(tag.dt); + txn->commitNote(formatMetadataMessage(tag.svnprefix, tag.revnum, tagName.toUtf8()), true); + delete txn; + + if (!fastImport.waitForBytesWritten(-1)) + qFatal("Failed to write to process: %s", qPrintable(fastImport.errorString())); + } + printf(" %s", qPrintable(tagName)); fflush(stdout); } @@ -521,6 +546,31 @@ void Repository::startFastImport() } } +QByteArray Repository::formatMetadataMessage(const QByteArray &svnprefix, int revnum, const QByteArray &tag) +{ + QByteArray msg = "svn path=" + svnprefix + "; revision=" + QByteArray::number(revnum); + if (!tag.isEmpty()) + msg += "; tag=" + tag; + msg += "\n"; + return msg; +} + +bool Repository::branchExists(const QString& branch) const +{ + return branches.contains(branch); +} + +const QByteArray Repository::branchNote(const QString& branch) const +{ + return branches.value(branch).note; +} + +void Repository::setBranchNote(const QString& branch, const QByteArray& noteText) +{ + if (branches.contains(branch)) + branches[branch].note = noteText; +} + Repository::Transaction::~Transaction() { repository->forgetTransaction(this); @@ -606,6 +656,37 @@ QIODevice *Repository::Transaction::addFile(const QString &path, int mode, qint6 return &repository->fastImport; } +void Repository::Transaction::commitNote(const QByteArray ¬eText, bool append, const QByteArray &commit) +{ + QByteArray branchRef = branch; + if (!branchRef.startsWith("refs/")) + branchRef.prepend("refs/heads/"); + const QByteArray &commitRef = commit.isNull() ? branchRef : commit; + QByteArray message = "Adding Git note for current " + commitRef + "\n"; + QByteArray text = noteText; + + if (append && commit.isNull() && + repository->branchExists(branch) && + !repository->branchNote(branch).isEmpty()) + { + text = repository->branchNote(branch) + text; + message = "Appending Git note for current " + commitRef + "\n"; + } + + QTextStream s(&repository->fastImport); + s << "commit refs/notes/commits" << endl + << "committer " << QString::fromUtf8(author) << ' ' << datetime << " -0000" << endl + << "data " << message.length() << endl + << message << endl + << "N inline " << commitRef << endl + << "data " << text.length() << endl + << text << endl; + + if (commit.isNull()) { + repository->setBranchNote(QString::fromUtf8(branch), text); + } +} + void Repository::Transaction::commit() { repository->startFastImport(); @@ -623,7 +704,7 @@ void Repository::Transaction::commit() if (!message.endsWith('\n')) message += '\n'; if (CommandLineParser::instance()->contains("add-metadata")) - message += "\nsvn path=" + svnprefix + "; revision=" + QByteArray::number(revnum) + "\n"; + message += "\n" + Repository::formatMetadataMessage(svnprefix, revnum); int parentmark = 0; Branch &br = repository->branches[branch]; @@ -641,7 +722,6 @@ void Repository::Transaction::commit() QByteArray branchRef = branch; if (!branchRef.startsWith("refs/")) branchRef.prepend("refs/heads/"); - QTextStream s(&repository->fastImport); s.setCodec("UTF-8"); s << "commit " << branchRef << endl; @@ -705,6 +785,10 @@ void Repository::Transaction::commit() deletedFiles.count() + modifiedFiles.count('\n'), svnprefix.data(), qPrintable(repository->name), branch.data()); + // Commit metadata note if requested + if (CommandLineParser::instance()->contains("add-metadata-notes")) + commitNote(Repository::formatMetadataMessage(svnprefix, revnum), false); + while (repository->fastImport.bytesToWrite()) if (!repository->fastImport.waitForBytesWritten(-1)) qFatal("Failed to write to process: %s for repository %s", qPrintable(repository->fastImport.errorString()), qPrintable(repository->name)); diff --git a/src/repository.h b/src/repository.h index 349cf97..4716ced 100644 --- a/src/repository.h +++ b/src/repository.h @@ -126,6 +126,9 @@ public: void deleteFile(const QString &path); QIODevice *addFile(const QString &path, int mode, qint64 length); + + void commitNote(const QByteArray ¬eText, bool append, + const QByteArray &commit = QByteArray()); }; Repository(const Rules::Repository &rule); int setupIncremental(int &cutoff); @@ -144,12 +147,20 @@ public: void finalizeTags(); void commit(); + static QByteArray formatMetadataMessage(const QByteArray &svnprefix, int revnum, + const QByteArray &tag = QByteArray()); + + bool branchExists(const QString& branch) const; + const QByteArray branchNote(const QString& branch) const; + void setBranchNote(const QString& branch, const QByteArray& noteText); + private: struct Branch { int created; QVector<int> commits; QVector<int> marks; + QByteArray note; }; struct AnnotatedTag { diff --git a/src/ruleparser.cpp b/src/ruleparser.cpp index eb309bf..437f3f8 100644 --- a/src/ruleparser.cpp +++ b/src/ruleparser.cpp @@ -77,6 +77,48 @@ const QList<Rules::Match> Rules::matchRules() const return m_matchRules; } +Rules::Match::Substitution Rules::parseSubstitution(const QString &string) +{ + if (string.at(0) != 's' || string.length() < 5) + return Match::Substitution(); + + const QChar sep = string.at(1); + + if (string.at(string.length() - 1) != sep) + return Match::Substitution(); + + int i = 2, end = 0; + Match::Substitution subst; + + // Separator might have been escaped with a backslash + while (i > end) { + int backslashCount = 0; + if ((end = string.indexOf(sep, i)) > -1) { + for (i = end - 1; i >= 2; i--) { + if (string.at(i) == '\\') + backslashCount++; + else + break; + } + } else { + return Match::Substitution(); // error + } + + if (backslashCount % 2 != 0) { + // Separator was escaped. Search for another one + i = end + 1; + } + } + + // Found the end of the pattern + subst.pattern = QRegExp(string.mid(2, end - 2)); + if (!subst.pattern.isValid()) + return Match::Substitution(); // error + subst.replacement = string.mid(end + 1, string.length() - 1 - end - 1); + + return subst; +} + void Rules::load() { load(filename); @@ -93,10 +135,13 @@ void Rules::load(const QString &filename) QRegExp matchLine("match\\s+(.*)", Qt::CaseInsensitive); QRegExp matchActionLine("action\\s+(\\w+)", Qt::CaseInsensitive); QRegExp matchRepoLine("repository\\s+(\\S+)", Qt::CaseInsensitive); + QRegExp matchDescLine("description\\s+(.+)$", Qt::CaseInsensitive); + QRegExp matchRepoSubstLine("substitute repository\\s+(.+)$", Qt::CaseInsensitive); QRegExp matchBranchLine("branch\\s+(\\S+)", Qt::CaseInsensitive); + QRegExp matchBranchSubstLine("substitute branch\\s+(.+)$", Qt::CaseInsensitive); QRegExp matchRevLine("(min|max) revision (\\d+)", Qt::CaseInsensitive); QRegExp matchAnnotateLine("annotated\\s+(\\S+)", Qt::CaseInsensitive); - QRegExp matchPrefixLine("prefix\\s+(\\S+)", Qt::CaseInsensitive); + QRegExp matchPrefixLine("prefix\\s+(.*)$", Qt::CaseInsensitive); QRegExp declareLine("declare\\s+("+varRegex+")\\s*=\\s*(\\S+)", Qt::CaseInsensitive); QRegExp variableLine("\\$\\{("+varRegex+")(\\|[^}$]*)?\\}", Qt::CaseInsensitive); QRegExp includeLine("include\\s+(.*)", Qt::CaseInsensitive); @@ -143,7 +188,7 @@ void Rules::load(const QString &filename) qFatal("Undeclared variable: %s", qPrintable(variableLine.cap(1))); } } - line = line.replace(variableLine, replacement); + line = line.replace(variableLine.cap(0), replacement); } if (state == ReadingRepository) { if (matchBranchLine.exactMatch(line)) { @@ -152,6 +197,9 @@ void Rules::load(const QString &filename) repo.branches += branch; continue; + } else if (matchDescLine.exactMatch(line)) { + repo.description = matchDescLine.cap(1); + continue; } else if (matchRepoLine.exactMatch(line)) { repo.forwardTo = matchRepoLine.cap(1); continue; @@ -175,6 +223,22 @@ void Rules::load(const QString &filename) } else if (matchBranchLine.exactMatch(line)) { match.branch = matchBranchLine.cap(1); continue; + } else if (matchRepoSubstLine.exactMatch(line)) { + Match::Substitution subst = parseSubstitution(matchRepoSubstLine.cap(1)); + if (!subst.isValid()) { + qFatal("Malformed substitution in rules file: line %d: %s", + lineNumber, qPrintable(origLine)); + } + match.repo_substs += subst; + continue; + } else if (matchBranchSubstLine.exactMatch(line)) { + Match::Substitution subst = parseSubstitution(matchBranchSubstLine.cap(1)); + if (!subst.isValid()) { + qFatal("Malformed substitution in rules file: line %d: %s", + lineNumber, qPrintable(origLine)); + } + match.branch_substs += subst; + continue; } else if (matchRevLine.exactMatch(line)) { if (matchRevLine.cap(1) == "min") match.minRevision = matchRevLine.cap(2).toInt(); diff --git a/src/ruleparser.h b/src/ruleparser.h index 9d21937..9878735 100644 --- a/src/ruleparser.h +++ b/src/ruleparser.h @@ -43,6 +43,7 @@ public: QString name; QList<Branch> branches; + QString description; QString forwardTo; QString prefix; @@ -57,9 +58,19 @@ public: struct Match : Rule { + struct Substitution { + QRegExp pattern; + QString replacement; + + bool isValid() { return !pattern.isEmpty(); } + QString& apply(QString &string) { return string.replace(pattern, replacement); } + }; + QRegExp rx; QString repository; + QList<Substitution> repo_substs; QString branch; + QList<Substitution> branch_substs; QString prefix; int minRevision; int maxRevision; @@ -83,6 +94,7 @@ public: const QList<Repository> repositories() const; const QList<Match> matchRules() const; + Match::Substitution parseSubstitution(const QString &string); void load(); private: diff --git a/src/svn.cpp b/src/svn.cpp index 3933df3..6c61e3c 100644 --- a/src/svn.cpp +++ b/src/svn.cpp @@ -214,11 +214,17 @@ static void splitPathName(const Rules::Match &rule, const QString &pathName, QSt if (repository_p) { *repository_p = svnprefix; repository_p->replace(rule.rx, rule.repository); + foreach (Rules::Match::Substitution subst, rule.repo_substs) { + subst.apply(*repository_p); + } } if (branch_p) { *branch_p = svnprefix; branch_p->replace(rule.rx, rule.branch); + foreach (Rules::Match::Substitution subst, rule.branch_substs) { + subst.apply(*branch_p); + } } if (path_p) { |