aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main.cpp15
-rw-r--r--src/repository.cpp90
-rw-r--r--src/repository.h11
-rw-r--r--src/ruleparser.cpp68
-rw-r--r--src/ruleparser.h12
-rw-r--r--src/svn.cpp6
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 &noteText, 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 &noteText, 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) {