X-Git-Url: http://andersk.mit.edu/gitweb/svn-all-fast-export.git/blobdiff_plain/5b8a1c0c71e75fd613fb1a27c61bdedf11803729..5871af719f7ede0db0e661a34a6f485dc4488e3b:/src/svn.cpp diff --git a/src/svn.cpp b/src/svn.cpp index 9cb0b98..de3a977 100644 --- a/src/svn.cpp +++ b/src/svn.cpp @@ -160,6 +160,51 @@ int SvnPrivate::openRepository(const QString &pathToRepository) return EXIT_SUCCESS; } +enum RuleType { AnyRule = 0, NoIgnoreRule = 0x01 }; + +static MatchRuleList::ConstIterator +findMatchRule(const MatchRuleList &matchRules, int revnum, const QString ¤t, + int ruleMask = AnyRule) +{ + MatchRuleList::ConstIterator it = matchRules.constBegin(), + end = matchRules.constEnd(); + for ( ; it != end; ++it) { + if (it->minRevision > revnum) + continue; + if (it->maxRevision != -1 && it->maxRevision < revnum) + continue; + if (it->action == Rules::Match::Ignore && ruleMask & NoIgnoreRule) + continue; + if (it->rx.indexIn(current) == 0) + return it; + } + + // no match + return end; +} + +static void splitPathName(const Rules::Match &rule, const QString &pathName, QString *svnprefix_p, + QString *repository_p, QString *branch_p, QString *path_p) +{ + QString svnprefix = pathName; + svnprefix.truncate(rule.rx.matchedLength()); + if (svnprefix_p) + *svnprefix_p = svnprefix; + + if (repository_p) { + *repository_p = svnprefix; + repository_p->replace(rule.rx, rule.repository); + } + + if (branch_p) { + *branch_p = svnprefix; + branch_p->replace(rule.rx, rule.branch); + } + + if (path_p) + *path_p = pathName.mid(svnprefix.length()); +} + static int pathMode(svn_fs_root_t *fs_root, const char *pathname, apr_pool_t *pool) { svn_string_t *propvalue; @@ -197,22 +242,23 @@ static svn_stream_t *streamForDevice(QIODevice *device, apr_pool_t *pool) static int dumpBlob(Repository::Transaction *txn, svn_fs_root_t *fs_root, const char *pathname, const QString &finalPathName, apr_pool_t *pool) { + AprAutoPool dumppool(pool); // what type is it? - int mode = pathMode(fs_root, pathname, pool); + int mode = pathMode(fs_root, pathname, dumppool); svn_filesize_t stream_length; - SVN_ERR(svn_fs_file_length(&stream_length, fs_root, pathname, pool)); + SVN_ERR(svn_fs_file_length(&stream_length, fs_root, pathname, dumppool)); QIODevice *io = txn->addFile(finalPathName, mode, stream_length); #ifndef DRY_RUN // open the file svn_stream_t *in_stream, *out_stream; - SVN_ERR(svn_fs_file_contents(&in_stream, fs_root, pathname, pool)); + SVN_ERR(svn_fs_file_contents(&in_stream, fs_root, pathname, dumppool)); // open a generic svn_stream_t for the QIODevice - out_stream = streamForDevice(io, pool); - SVN_ERR(svn_stream_copy(in_stream, out_stream, pool)); + out_stream = streamForDevice(io, dumppool); + SVN_ERR(svn_stream_copy(in_stream, out_stream, dumppool)); // print an ending newline io->putChar('\n'); @@ -238,16 +284,35 @@ static int recursiveDumpDir(Repository::Transaction *txn, svn_fs_root_t *fs_root svn_fs_dirent_t *dirent = reinterpret_cast(value); QByteArray entryName = pathname + '/' + dirent->name; - QString entryFinalName = finalPathName + '/' + dirent->name; + QString entryFinalName = finalPathName + dirent->name; if (dirent->kind == svn_node_dir) { + entryFinalName += '/'; if (recursiveDumpDir(txn, fs_root, entryName, entryFinalName, dirpool) == EXIT_FAILURE) return EXIT_FAILURE; } else if (dirent->kind == svn_node_file) { + printf("+"); + fflush(stdout); if (dumpBlob(txn, fs_root, entryName, entryFinalName, dirpool) == EXIT_FAILURE) return EXIT_FAILURE; } } + + return EXIT_SUCCESS; +} + +static bool wasDir(svn_fs_t *fs, int revnum, const char *pathname, apr_pool_t *pool) +{ + AprAutoPool subpool(pool); + svn_fs_root_t *fs_root; + if (svn_fs_revision_root(&fs_root, fs, revnum, subpool) != SVN_NO_ERROR) + return false; + + svn_boolean_t is_dir; + if (svn_fs_is_dir(&is_dir, fs_root, pathname, subpool) != SVN_NO_ERROR) + return false; + + return is_dir; } time_t get_epoch(char *svn_date) @@ -259,110 +324,92 @@ time_t get_epoch(char *svn_date) return mktime(&tm); } +class SvnRevision +{ +public: + AprAutoPool pool; + QHash transactions; + MatchRuleList matchRules; + RepositoryHash repositories; + IdentityHash identities; + + svn_fs_t *fs; + svn_fs_root_t *fs_root; + int revnum; + + SvnRevision(int revision, svn_fs_t *f, apr_pool_t *parent_pool) + : pool(parent_pool), fs(f), fs_root(0), revnum(revision) + { + } + + int open() + { + SVN_ERR(svn_fs_revision_root(&fs_root, fs, revnum, pool)); + return EXIT_SUCCESS; + } + + int prepareTransactions(); + int commit(); + + int exportEntry(const char *path, const svn_fs_path_change_t *change, apr_hash_t *changes); + int exportInternal(const char *path, const svn_fs_path_change_t *change, + const char *path_from, svn_revnum_t rev_from, + const QString ¤t, const Rules::Match &rule); + int recurse(const char *path, const svn_fs_path_change_t *change, + const char *path_from, svn_revnum_t rev_from, + apr_hash_t *changes, apr_pool_t *pool); +}; + int SvnPrivate::exportRevision(int revnum) { - AprAutoPool pool(global_pool.data()); + SvnRevision rev(revnum, fs, global_pool); + rev.matchRules = matchRules; + rev.repositories = repositories; + rev.identities = identities; // open this revision: - qDebug() << "Exporting revision" << revnum; - svn_fs_root_t *fs_root; - SVN_ERR(svn_fs_revision_root(&fs_root, fs, revnum, pool)); + printf("Exporting revision %d ", revnum); + fflush(stdout); + if (rev.open() == EXIT_FAILURE) + return EXIT_FAILURE; + + if (rev.prepareTransactions() == EXIT_FAILURE) + return EXIT_FAILURE; + + if (rev.transactions.isEmpty()) { + printf(" nothing to do\n"); + return EXIT_SUCCESS; // no changes? + } + + if (rev.commit() == EXIT_FAILURE) + return EXIT_FAILURE; + + printf(" done\n"); + return EXIT_SUCCESS; +} + +int SvnRevision::prepareTransactions() +{ // find out what was changed in this revision: - QHash transactions; apr_hash_t *changes; SVN_ERR(svn_fs_paths_changed(&changes, fs_root, pool)); - AprAutoPool revpool(pool.data()); for (apr_hash_index_t *i = apr_hash_first(pool, changes); i; i = apr_hash_next(i)) { - revpool.clear(); - const void *vkey; void *value; apr_hash_this(i, &vkey, NULL, &value); const char *key = reinterpret_cast(vkey); + svn_fs_path_change_t *change = reinterpret_cast(value); - // is this a directory? - svn_boolean_t is_dir; - SVN_ERR(svn_fs_is_dir(&is_dir, fs_root, key, revpool)); - if (is_dir) { - // was this directory copied from somewhere? - svn_revnum_t rev_from; - const char *path_from; - SVN_ERR(svn_fs_copied_from(&rev_from, &path_from, fs_root, key, revpool)); - - if (path_from == NULL) - // no, it's a new directory being added - // Git doesn't handle directories, so we don't either - continue; - - qDebug() << "..." << key << "was copied from" << path_from; - } - - QString current = QString::fromUtf8(key); - - // find the first rule that matches this pathname - bool foundMatch = false; - foreach (Rules::Match rule, matchRules) { - if (rule.minRevision > revnum) - continue; - if (rule.maxRevision != -1 && rule.maxRevision < revnum) - continue; - if (rule.rx.exactMatch(current)) { - foundMatch = true; - QString repository = current; - QString branch = current; - QString path = current; - - // do the replacement - repository.replace(rule.rx, rule.repository); - branch.replace(rule.rx, rule.branch); - path.replace(rule.rx, rule.path); - - qDebug() << "..." << qPrintable(current) << "rev" << revnum << "->" - << qPrintable(repository) << qPrintable(branch) << qPrintable(path); - - Repository::Transaction *txn = transactions.value(repository, 0); - if (!txn) { - Repository *repo = repositories.value(repository, 0); - if (!repo) { - qCritical() << "Rule" << rule.rx.pattern() - << "references unknown repository" << repository; - return EXIT_FAILURE; - } - - QString svnprefix = current; - if (svnprefix.endsWith(path)) - svnprefix.chop(path.length()); - - txn = repo->newTransaction(branch, svnprefix, revnum); - if (!txn) - return EXIT_FAILURE; - - transactions.insert(repository, txn); - } - - svn_fs_path_change_t *change = reinterpret_cast(value); - if (change->change_kind == svn_fs_path_change_delete) - txn->deleteFile(path); - else if (!is_dir) - dumpBlob(txn, fs_root, key, path, revpool); - else - recursiveDumpDir(txn, fs_root, key, path, revpool); - - break; - } - } - - if (!foundMatch) { - qCritical() << current << "did not match any rules; cannot continue"; + if (exportEntry(key, change, changes) == EXIT_FAILURE) return EXIT_FAILURE; - } } - revpool.clear(); - if (transactions.isEmpty()) - return EXIT_SUCCESS; // no changes? + return EXIT_SUCCESS; +} +int SvnRevision::commit() +{ // now create the commit apr_hash_t *revprops; SVN_ERR(svn_fs_revision_proplist(&revprops, fs, revnum, pool)); @@ -371,7 +418,7 @@ int SvnPrivate::exportRevision(int revnum) svn_string_t *svnlog = (svn_string_t*)apr_hash_get(revprops, "svn:log", APR_HASH_KEY_STRING); QByteArray log = (char *)svnlog->data; - QByteArray authorident = identities.value((char *)svnauthor->data); + QByteArray authorident = svnauthor ? identities.value((char *)svnauthor->data) : QByteArray(); time_t epoch = get_epoch((char*)svndate->data); if (authorident.isEmpty()) { if (!svnauthor || svn_string_isempty(svnauthor)) @@ -392,3 +439,192 @@ int SvnPrivate::exportRevision(int revnum) return EXIT_SUCCESS; } + +int SvnRevision::exportEntry(const char *key, const svn_fs_path_change_t *change, + apr_hash_t *changes) +{ + AprAutoPool revpool(pool.data()); + QString current = QString::fromUtf8(key); + + // was this copied from somewhere? + svn_revnum_t rev_from; + const char *path_from; + SVN_ERR(svn_fs_copied_from(&rev_from, &path_from, fs_root, key, revpool)); + + // is this a directory? + svn_boolean_t is_dir; + SVN_ERR(svn_fs_is_dir(&is_dir, fs_root, key, revpool)); + if (is_dir) { + if (path_from == NULL) { + // no, it's a new directory being added + // Git doesn't handle directories, so we don't either + //qDebug() << " mkdir ignored:" << key; + return EXIT_SUCCESS; + } + + current += '/'; + qDebug() << " " << key << "was copied from" << path_from; + } + + // find the first rule that matches this pathname + MatchRuleList::ConstIterator match = findMatchRule(matchRules, revnum, current); + if (match != matchRules.constEnd()) { + const Rules::Match &rule = *match; + return exportInternal(key, change, path_from, rev_from, current, rule); + } + + if (is_dir && path_from != NULL) { + qDebug() << current << "is a copy-with-history, auto-recursing"; + return recurse(key, change, path_from, rev_from, changes, revpool); + } else if (wasDir(fs, revnum - 1, key, revpool)) { + qDebug() << current << "was a directory; ignoring"; + } else if (change->change_kind == svn_fs_path_change_delete) { + qDebug() << current << "is being deleted but I don't know anything about it; ignoring"; + } else { + qCritical() << current << "did not match any rules; cannot continue"; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +int SvnRevision::exportInternal(const char *key, const svn_fs_path_change_t *change, + const char *path_from, svn_revnum_t rev_from, + const QString ¤t, const Rules::Match &rule) +{ + if (rule.action == Rules::Match::Ignore) { + // ignore rule + qDebug() << " " << qPrintable(current) << "rev" << revnum + << "-> ignored (rule" << rule << ")"; + return EXIT_SUCCESS; + } + + QString svnprefix, repository, branch, path; + splitPathName(rule, current, &svnprefix, &repository, &branch, &path); + + printf("."); + fflush(stdout); +// qDebug() << " " << qPrintable(current) << "rev" << revnum << "->" +// << qPrintable(repository) << qPrintable(branch) << qPrintable(path); + + if (path.isEmpty() && path_from != NULL) { + QString previous = QString::fromUtf8(path_from) + '/'; + MatchRuleList::ConstIterator prevmatch = + findMatchRule(matchRules, rev_from, previous, NoIgnoreRule); + if (prevmatch != matchRules.constEnd()) { + QString prevsvnprefix, prevrepository, prevbranch, prevpath; + splitPathName(*prevmatch, previous, &prevsvnprefix, &prevrepository, + &prevbranch, &prevpath); + + if (!prevpath.isEmpty()) { + qDebug() << qPrintable(current) << "is a partial branch of repository" + << qPrintable(prevrepository) << "branch" + << qPrintable(prevbranch) << "subdir" + << qPrintable(prevpath); + } else if (prevrepository != repository) { + qWarning() << qPrintable(current) << "rev" << revnum + << "is a cross-repository copy (from repository" + << qPrintable(prevrepository) << "branch" + << qPrintable(prevbranch) << "path" + << qPrintable(prevpath) << "rev" << rev_from << ")"; + } else if (prevbranch == branch) { + // same branch and same repository + qDebug() << qPrintable(current) << "rev" << revnum + << "is an SVN rename from" + << qPrintable(previous) << "rev" << rev_from; + return EXIT_SUCCESS; + } else { + // same repository but not same branch + // this means this is a plain branch + qDebug() << qPrintable(repository) << ": branch" + << qPrintable(branch) << "is branching from" + << qPrintable(prevbranch); + + Repository *repo = repositories.value(repository, 0); + if (!repo) { + qCritical() << "Rule" << rule + << "references unknown repository" << repository; + return EXIT_FAILURE; + } + + repo->createBranch(branch, revnum, prevbranch, rev_from); + return EXIT_SUCCESS; + } + } + } + + Repository::Transaction *txn = transactions.value(repository, 0); + if (!txn) { + Repository *repo = repositories.value(repository, 0); + if (!repo) { + qCritical() << "Rule" << rule + << "references unknown repository" << repository; + return EXIT_FAILURE; + } + + txn = repo->newTransaction(branch, svnprefix, revnum); + if (!txn) + return EXIT_FAILURE; + + transactions.insert(repository, txn); + } + + if (change->change_kind == svn_fs_path_change_delete) { + txn->deleteFile(path); + } else if (!current.endsWith('/')) { + dumpBlob(txn, fs_root, key, path, pool); + } else { + QString pathNoSlash = path; + pathNoSlash.chop(1); + txn->deleteFile(pathNoSlash); + recursiveDumpDir(txn, fs_root, key, path, pool); + } + + return EXIT_SUCCESS; +} + +int SvnRevision::recurse(const char *path, const svn_fs_path_change_t *change, + const char *path_from, svn_revnum_t rev_from, + apr_hash_t *changes, apr_pool_t *pool) +{ + // get the dir listing + apr_hash_t *entries; + SVN_ERR(svn_fs_dir_entries(&entries, fs_root, path, pool)); + + AprAutoPool dirpool(pool); + for (apr_hash_index_t *i = apr_hash_first(pool, entries); i; i = apr_hash_next(i)) { + dirpool.clear(); + const void *vkey; + void *value; + apr_hash_this(i, &vkey, NULL, &value); + + svn_fs_dirent_t *dirent = reinterpret_cast(value); + QByteArray entry = path + QByteArray("/") + dirent->name; + QByteArray entryFrom; + if (path_from) + entryFrom = path_from + QByteArray("/") + dirent->name; + + // check if this entry is in the changelist for this revision already + if (apr_hash_get(changes, entry.constData(), APR_HASH_KEY_STRING)) { + qDebug() << entry << "rev" << revnum + << "is in the change-list, deferring to that one"; + continue; + } + + QString current = QString::fromUtf8(entry); + if (dirent->kind == svn_node_dir) + current += '/'; + + // find the first rule that matches this pathname + MatchRuleList::ConstIterator match = findMatchRule(matchRules, revnum, current); + if (match != matchRules.constEnd()) { + if (exportInternal(entry, change, entryFrom.isNull() ? 0 : entryFrom.constData(), + rev_from, current, *match) == EXIT_FAILURE) + return EXIT_FAILURE; + } else { + qCritical() << current << "rev" << revnum + << "did not match any rules; cannot continue"; + return EXIT_FAILURE; + } + } +}