Revision: 76489
          http://sourceforge.net/p/brlcad/code/76489
Author:   starseeker
Date:     2020-07-25 10:38:15 +0000 (Sat, 25 Jul 2020)
Log Message:
-----------
Pull latest svn-fast-export changes from upstream

Modified Paths:
--------------
    brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/CMakeLists.txt
    brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/README.md
    brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/main.cpp
    brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/repository.cpp
    brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/repository.h
    brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/svn.cpp

Modified: brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/CMakeLists.txt
===================================================================
--- brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/CMakeLists.txt   
2020-07-25 10:21:40 UTC (rev 76488)
+++ brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/CMakeLists.txt   
2020-07-25 10:38:15 UTC (rev 76489)
@@ -53,7 +53,7 @@
   CommandLineParser.cpp
   )
 
-add_definitions(-DVER="brlcad-svn2git-20140907")
+add_definitions(-DVER="brlcad-svn2git-20200725")
 
 add_executable(svn-all-fast-export ${SVN2GIT_SRCS})
 target_link_libraries(svn-all-fast-export ${SUBVERSION_LIBRARIES} 
${Qt5Core_LIBRARIES})

Modified: brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/README.md
===================================================================
--- brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/README.md        
2020-07-25 10:21:40 UTC (rev 76488)
+++ brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/README.md        
2020-07-25 10:38:15 UTC (rev 76489)
@@ -16,6 +16,15 @@
 
 After it is done you likely want to run `git repack -a -d -f` to compress the 
pack file as it can get quite big.
 
+Running as Docker image
+-----------------------
+Just mount your SVN folder, plus another working directory where Git 
repository will be created.
+Sample usage with input mounted in /tmp and output produced in /workdir:
+```
+docker build -t svn2git .
+docker run --rm -it -v `pwd`/workdir:/workdir -v 
/var/lib/svn/project1:/tmp/svn -v `pwd`/conf:/tmp/conf svn2git 
/usr/local/svn2git/svn-all-fast-export --identity-map 
/tmp/conf/project1.authors --rules /tmp/conf/project1.rules --add-metadata 
--svn-branches --debug-rules --svn-ignore --empty-dirs /tmp/svn/ 
+```
+
 Building the tool
 -----------------
 Run `qmake && make`.  You get `./svn-all-fast-export`.
@@ -24,6 +33,19 @@
 You will need to have some packages to compile it. For Ubuntu distros, use 
this command to install them all:
 `sudo apt-get install build-essential subversion git qtchooser qt5-default 
libapr1 libapr1-dev libsvn-dev`
 
+To run all tests you can simply call the `test.sh` script in the root 
directory.
+This will run all [Bats](https://github.com/bats-core/bats-core) based tests
+found in `.bats` files in the directory `test`. Running the script will 
automatically
+execute `qmake` and `make` first to build the current code if necessary.
+If you want to run tests without running make, you can give `--no-make` as 
first parameter.
+If you want to only run a subset of the tests, you can specify the base-name 
of one
+or multiple `.bats` files to only run these tests like `./test.sh command-line 
svn-ignore`.
+If you want to investigate the temporary files generated during a test run,
+you can set the environment variables `BATSLIB_TEMP_PRESERVE=1` or 
`BATSLIB_TEMP_PRESERVE_ON_FAILURE=1`.
+So if for example some test in `svn-ignore.bats` failed, you can investigate 
the failed case like
+`BATSLIB_TEMP_PRESERVE_ON_FAILURE=1 ./test.sh --no-make svn-ignore` and then 
look
+in `build/tmp` to investigate the situation.
+
 KDE
 ---
 there is a repository kde-ruleset which has several example files and one file 
that should become the final ruleset for the whole of KDE called 
'kde-rules-main'.
@@ -33,6 +55,57 @@
 You need to write a rules file that describes how to slice the Subversion 
history into Git repositories and branches. See 
https://techbase.kde.org/Projects/MoveToGit/UsingSvn2Git.
 The rules are also documented in the 'samples' directory of the svn2git 
repository. Feel free to add more documentation here as well.
 
+Rules
+-----
+### `create respository`
+
+```
+create repository REPOSITORY NAME
+  [PARAMETERS...]
+end repository
+```
+
+`PARAMETERS` is any number of:
+
+- `repository TARGET REPOSITORY` Creates a forwarding repository , which 
allows for redirecting to another repository, typically with some `prefix`.
+- `prefix PREFIX` prefixes each file with `PREFIX`, allowing for merging 
repositories.
+- `description DESCRIPTION TEXT` writes a `DESCRIPTION TEXT` to the 
`description` file in the repository
+
+### `match`
+
+```
+match REGEX
+  [PARAMETERS...]
+end match
+```
+
+Creates a rule that matches paths by `REGEX` and applies some `PARAMETERS` to 
them. Matching groups can be created, and the values used in the parameters.
+
+`PARAMETERS` is any number of:
+
+- `repository TARGET REPOSITORY` determines the repository
+- `branch BRANCH NAME` determines which branch this path will be placed in. 
Can also be used to make lightweight tags with `refs/tags/TAG NAME` although 
note that tags in SVN are not always a single commit, and will not be created 
correctly unless they are a single copy from somewhere else, with no further 
changes. See also `annotate true` to make them annotated tags.
+- `[min|max] revision REVISION NUMBER` only match if revision is above/below 
the specified revision number
+- `prefix PREFIX` prefixes each file with `PREFIX`, allowing for merging 
repositories. Same as when used in a `create repository` stanza.
+  - Note that this will create a separate commit for each prefix matched, even 
if they were in the same SVN revision.
+- `substitute [repository|branch] s/PATTERN/REPLACEMENT/` performs a regex 
substitution on the repository or branch name. Useful when eliminating 
characters not supported in git branch names.
+- `action ACTION` determines the action to take, from the below three:
+
+  - `export` I have no idea what this does
+  - `ignore` ignores this path
+  - `recurse` tells svn2git to ignore this path and continue searching its 
children.
+
+- `annotated true` creates annotated tags instead of lightweight tags. You can 
see the commit log with `git tag -n`.
+
+### `include FILENAME`
+
+Include the contents of another rules file
+
+### `declare VAR=VALUE`
+
+Define variables that can be referenced later. `${VAR}` in any line will be 
replaced by `VALUE`.
+
+
 Work flow
 ---------
 Please feel free to fill this section in.
@@ -40,12 +113,4 @@
 Some SVN tricks
 ---------------
 You can access your newly rsynced SVN repo with commands like `svn ls 
file:///path/to/repo/trunk/KDE`.
-A common issue is tracking when an item left playground for kdereview and then 
went from kdereview to its final destination. There is no straightforward way 
to do this. So the following command comes in handy: `svn log -v 
file:///path/to/repo/kde-svn/kde/trunk/kdereview | grep 
/trunk/kdereview/mplayerthumbs -A 5 -B 5` This will print all commits relevant 
to the package you are trying to track. You can also pipe the above command to 
head or tail to see the the first and last commit it was in that directory.
-
-The version of this program in BRL-CAD's misc directory is included
-for convenience, and defines a CMake build to allow users familiar
-with building BRL-CAD to more easily build this tool.  Users should
-check the main repository to see if a more updated version of this
-code exists (BRL-CAD's copy is not automatically synced):
-
-https://github.com/svn-all-fast-export/svn2git
+A common issue is tracking when an item left playground for kdereview and then 
went from kdereview to its final destination. There is no straightforward way 
to do this. So the following command comes in handy: `svn log -v 
file:///path/to/repo/kde-svn/kde/trunk/kdereview | grep 
/trunk/kdereview/mplayerthumbs -A 5 -B 5` This will print all commits relevant 
to the package you are trying to track. You can also pipe the above command to 
head or tail to see the first and last commit it was in that directory.

Modified: brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/main.cpp
===================================================================
--- brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/main.cpp 2020-07-25 
10:21:40 UTC (rev 76488)
+++ brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/main.cpp 2020-07-25 
10:38:15 UTC (rev 76489)
@@ -42,6 +42,7 @@
         return result;
     }
 
+    bool found_author;
     while (!file.atEnd()) {
         QByteArray line = file.readLine();
         int comment_pos = line.indexOf('#');
@@ -68,9 +69,13 @@
         line.truncate(leftspace);
 
         result.insert(line, realname);
+        found_author = true;
     };
     file.close();
 
+    if(!found_author) {
+        fprintf(stderr, "No authors found in identity-map file. Check 
supported formats.\n");
+    }
     return result;
 }
 
@@ -144,6 +149,7 @@
     {"--empty-dirs", "Add .gitignore-file for empty dirs"},
     {"--svn-ignore", "Import svn-ignore-properties via .gitignore"},
     {"--propcheck", "Check for svn-properties except svn-ignore"},
+    {"--fast-import-timeout SECONDS", "number of seconds to wait before 
terminating fast-import, 0 to wait forever"},
     {"-h, --help", "show help"},
     {"-v, --version", "show version"},
     CommandLineLastOption
@@ -163,10 +169,14 @@
         printf("Git version: %s\n", VER);
         return 0;
     }
-    if (args->contains(QLatin1String("help")) || args->arguments().count() != 
1) {
+    if (args->contains(QLatin1String("help"))) {
         args->usage(QString(), "[Path to subversion repo]");
         return 0;
     }
+    if (args->arguments().count() != 1) {
+        args->usage(QString(), "[Path to subversion repo]");
+        return 12;
+    }
     if (args->undefinedOptions().count()) {
         QTextStream out(stderr);
         out << "svn-all-fast-export failed: ";
@@ -210,6 +220,8 @@
         repositories.insert(rule.name, repo);
 
         int repo_next = repo->setupIncremental(cutoff);
+        repo->restoreAnnotatedTags();
+        repo->restoreBranchNotes();
 
         /*
   * cutoff < resume_from => error exit eventually
@@ -281,6 +293,7 @@
 
     foreach (Repository *repo, repositories) {
         repo->finalizeTags();
+        repo->saveBranchNotes();
         delete repo;
     }
     Stats::instance()->printStats();

Modified: brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/repository.cpp
===================================================================
--- brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/repository.cpp   
2020-07-25 10:21:40 UTC (rev 76488)
+++ brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/repository.cpp   
2020-07-25 10:38:15 UTC (rev 76489)
@@ -19,6 +19,7 @@
 #include "repository.h"
 #include "CommandLineParser.h"
 #include <QTextStream>
+#include <QDataStream>
 #include <QDebug>
 #include <QDir>
 #include <QFile>
@@ -32,6 +33,15 @@
 class FastImportRepository : public Repository
 {
 public:
+    struct AnnotatedTag
+    {
+        QString supportingRef;
+        QByteArray svnprefix;
+        QByteArray author;
+        QByteArray log;
+        uint dt;
+        int revnum;
+    };
     class Transaction : public Repository::Transaction
     {
         Q_DISABLE_COPY(Transaction)
@@ -53,7 +63,7 @@
         inline Transaction() {}
     public:
         ~Transaction();
-        void commit();
+        int commit();
 
         void setAuthor(const QByteArray &author);
         void setDateTime(uint dt);
@@ -64,11 +74,13 @@
         void deleteFile(const QString &path);
         QIODevice *addFile(const QString &path, int mode, qint64 length);
 
-        void commitNote(const QByteArray &noteText, bool append,
+        bool commitNote(const QByteArray &noteText, bool append,
                         const QByteArray &commit = QByteArray());
     };
     FastImportRepository(const Rules::Repository &rule);
     int setupIncremental(int &cutoff);
+    void restoreAnnotatedTags();
+    void restoreBranchNotes();
     void restoreLog();
     ~FastImportRepository();
 
@@ -82,6 +94,7 @@
                             const QByteArray &author, uint dt,
                             const QByteArray &log);
     void finalizeTags();
+    void saveBranchNotes();
     void commit();
 
     bool branchExists(const QString& branch) const;
@@ -98,19 +111,10 @@
         int created;
         QVector<int> commits;
         QVector<int> marks;
-        QByteArray note;
     };
-    struct AnnotatedTag
-    {
-        QString supportingRef;
-        QByteArray svnprefix;
-        QByteArray author;
-        QByteArray log;
-        uint dt;
-        int revnum;
-    };
 
     QHash<QString, Branch> branches;
+    QHash<QString, QByteArray> branchNotes;
     QHash<QString, AnnotatedTag> annotatedTags;
     QString name;
     QString prefix;
@@ -119,6 +123,8 @@
     int outstandingTransactions;
     QByteArray deletedBranches;
     QByteArray resetBranches;
+    QSet<QString> deletedBranchNames;
+    QSet<QString> resetBranchNames;
 
   /* Optional filter to fix up log messages */
     QProcess filterMsg;
@@ -127,7 +133,7 @@
     /* starts at 0, and counts up.  */
     mark_t last_commit_mark;
 
-    /* starts at maxMark and counts down. Reset after each SVN revision */
+    /* starts at maxMark - 1 and counts down. Reset after each SVN revision */
     mark_t next_file_mark;
 
     bool processHasStarted;
@@ -160,7 +166,7 @@
     public:
         Transaction(Repository::Transaction *t, const QString &p) : txn(t), 
prefix(p) {}
         ~Transaction() { delete txn; }
-        void commit() { txn->commit(); }
+        int commit() { return txn->commit(); }
 
         void setAuthor(const QByteArray &author) { txn->setAuthor(author); }
         void setDateTime(uint dt) { txn->setDateTime(dt); }
@@ -173,7 +179,7 @@
         QIODevice *addFile(const QString &path, int mode, qint64 length)
         { return txn->addFile(prefix + path, mode, length); }
 
-        void commitNote(const QByteArray &noteText, bool append,
+        bool commitNote(const QByteArray &noteText, bool append,
                         const QByteArray &commit)
         { return txn->commitNote(noteText, append, commit); }
     };
@@ -181,6 +187,8 @@
     ForwardingRepository(const QString &n, Repository *r, const QString &p) : 
name(n), repo(r), prefix(p) {}
 
     int setupIncremental(int &) { return 1; }
+    void restoreAnnotatedTags() {}
+    void restoreBranchNotes() {}
     void restoreLog() {}
 
     void reloadBranches() { return repo->reloadBranches(); }
@@ -202,6 +210,7 @@
                             const QByteArray &log)
     { repo->createAnnotatedTag(name, svnprefix, revnum, author, dt, log); }
     void finalizeTags() { /* loop that called this will invoke it on 'repo' 
too */ }
+    void saveBranchNotes() { /* loop that called this will invoke it on 'repo' 
too */ }
     void commit() { repo->commit(); }
 
     bool branchExists(const QString& branch) const
@@ -246,6 +255,33 @@
 };
 static ProcessCache processCache;
 
+QDataStream &operator<<(QDataStream &out, const 
FastImportRepository::AnnotatedTag &annotatedTag)
+{
+    out << annotatedTag.supportingRef
+        << annotatedTag.svnprefix
+        << annotatedTag.author
+        << annotatedTag.log
+        << (quint64) annotatedTag.dt
+        << (qint64) annotatedTag.revnum;
+    return out;
+}
+
+QDataStream &operator>>(QDataStream &in, FastImportRepository::AnnotatedTag 
&annotatedTag)
+{
+    quint64 dt;
+    qint64 revnum;
+
+    in >> annotatedTag.supportingRef
+       >> annotatedTag.svnprefix
+       >> annotatedTag.author
+       >> annotatedTag.log
+       >> dt
+       >> revnum;
+    annotatedTag.dt = (uint) dt;
+    annotatedTag.revnum = (int) revnum;
+    return in;
+}
+
 Repository *createRepository(const Rules::Repository &rule, const 
QHash<QString, Repository *> &repositories)
 {
     if (rule.forwardTo.isEmpty())
@@ -265,9 +301,23 @@
     return name;
 }
 
+static QString annotatedTagsFileName(QString name)
+{
+    name.replace('/', '_');
+    name.prepend("annotatedTags-");
+    return name;
+}
+
+static QString branchNotesFileName(QString name)
+{
+    name.replace('/', '_');
+    name.prepend("branchNotes-");
+    return name;
+}
+
 FastImportRepository::FastImportRepository(const Rules::Repository &rule)
     : name(rule.name), prefix(rule.forwardTo), fastImport(name), 
commitCount(0), outstandingTransactions(0),
-      last_commit_mark(0), next_file_mark(maxMark), processHasStarted(false)
+      last_commit_mark(0), next_file_mark(maxMark - 1), 
processHasStarted(false)
 {
     foreach (Rules::Repository::Branch branchRule, rule.branches) {
         Branch branch;
@@ -279,8 +329,8 @@
     // create the default branch
     branches["master"].created = 1;
 
-    fastImport.setWorkingDirectory(name);
     if (!CommandLineParser::instance()->contains("dry-run") && 
!CommandLineParser::instance()->contains("create-dump")) {
+        fastImport.setWorkingDirectory(name);
         if (!QDir(name).exists()) { // repo doesn't exist yet.
             qDebug() << "Creating new repository" << name;
             QDir::current().mkpath(name);
@@ -296,7 +346,7 @@
             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.write(rule.description.toUtf8());
                     fDesc.putChar('\n');
                     fDesc.close();
                 }
@@ -449,6 +499,28 @@
     return cutoff;
 }
 
+void FastImportRepository::restoreAnnotatedTags()
+{
+    QFile annotatedTagsFile(name + "/" + annotatedTagsFileName(name));
+    if (!annotatedTagsFile.exists())
+        return;
+    annotatedTagsFile.open(QIODevice::ReadOnly);
+    QDataStream annotatedTagsStream(&annotatedTagsFile);
+    annotatedTagsStream >> annotatedTags;
+    annotatedTagsFile.close();
+}
+
+void FastImportRepository::restoreBranchNotes()
+{
+    QFile branchNotesFile(name + "/" + branchNotesFileName(name));
+    if (!branchNotesFile.exists())
+        return;
+    branchNotesFile.open(QIODevice::ReadOnly);
+    QDataStream branchNotesStream(&branchNotesFile);
+    branchNotesStream >> branchNotes;
+    branchNotesFile.close();
+}
+
 void FastImportRepository::restoreLog()
 {
     QString file = logFileName(name);
@@ -468,10 +540,18 @@
 void FastImportRepository::closeFastImport()
 {
     if (fastImport.state() != QProcess::NotRunning) {
+        int fastImportTimeout = 
CommandLineParser::instance()->optionArgument(QLatin1String("fast-import-timeout"),
 QLatin1String("30")).toInt();
+        if(fastImportTimeout == 0) {
+            qDebug() << "Waiting forever for fast-import to finish.";
+            fastImportTimeout = -1;
+        } else {
+            qDebug() << "Waiting" << fastImportTimeout << "seconds for 
fast-import to finish.";
+            fastImportTimeout *= 10000;
+        }
         fastImport.write("checkpoint\n");
         fastImport.waitForBytesWritten(-1);
         fastImport.closeWriteChannel();
-        if (!fastImport.waitForFinished()) {
+        if (!fastImport.waitForFinished(fastImportTimeout)) {
             fastImport.terminate();
             if (!fastImport.waitForFinished(200))
                 qWarning() << "WARN: git-fast-import for repository" << name 
<< "did not die";
@@ -490,12 +570,13 @@
         if (br.marks.isEmpty() || !br.marks.last())
             continue;
 
-       reset_notes = true;
+        reset_notes = true;
 
         QByteArray branchRef = branch.toUtf8();
         if (!branchRef.startsWith("refs/"))
             branchRef.prepend("refs/heads/");
 
+        startFastImport();
         fastImport.write("reset " + branchRef +
                         "\nfrom :" + QByteArray::number(br.marks.last()) + 
"\n\n"
                         "progress Branch " + branchRef + " reloaded\n");
@@ -502,10 +583,12 @@
     }
 
     if (reset_notes &&
-       CommandLineParser::instance()->contains("add-metadata-notes")) {
-      fastImport.write("reset refs/notes/commits\nfrom :" +
-                      QByteArray::number(maxMark + 1) +
-                      "\n");
+        CommandLineParser::instance()->contains("add-metadata-notes")) {
+
+        startFastImport();
+        fastImport.write("reset refs/notes/commits\nfrom :" +
+                         QByteArray::number(maxMark) +
+                         "\n");
     }
 }
 
@@ -565,7 +648,7 @@
     qDebug() << "Creating branch:" << branch << "from" << branchFrom << "(" << 
branchRevNum << branchFromDesc << ")";
 
     // Preserve note
-    branches[branch].note = branches.value(branchFrom).note;
+    branchNotes[branch] = branchNotes.value(branchFrom);
 
     return resetBranch(branch, revnum, mark, branchFromRef, branchFromDesc);
 }
@@ -603,10 +686,13 @@
                      "progress SVN r" + QByteArray::number(revnum)
                      + " branch " + branch.toUtf8() + " = :" + 
QByteArray::number(mark)
                      + " # " + comment + "\n\n";
-    if(comment == "delete")
+    if(comment == "delete") {
         deletedBranches.append(backupCmd).append(cmd);
-    else
+        deletedBranchNames.insert(branchRef);
+    } else {
         resetBranches.append(backupCmd).append(cmd);
+        resetBranchNames.insert(branchRef);
+    }
 
     return EXIT_SUCCESS;
 }
@@ -621,6 +707,19 @@
     fastImport.write(resetBranches);
     deletedBranches.clear();
     resetBranches.clear();
+    QSet<QString>::ConstIterator it = deletedBranchNames.constBegin();
+    for ( ; it != deletedBranchNames.constEnd(); ++it) {
+        QString tagName = *it;
+        if (resetBranchNames.contains(tagName))
+            continue;
+        if (tagName.startsWith("refs/tags/"))
+            tagName.remove(0, 10);
+        if (annotatedTags.remove(tagName) > 0) {
+            qDebug() << "Removing annotated tag" << tagName << "for" << name;
+        }
+    }
+    deletedBranchNames.clear();
+    resetBranchNames.clear();
 }
 
 Repository::Transaction *FastImportRepository::newTransaction(const QString 
&branch, const QString &svnprefix,
@@ -642,7 +741,7 @@
         startFastImport();
         // write everything to disk every 10000 commits
         fastImport.write("checkpoint\n");
-        qDebug() << "checkpoint!, marks file trunkated";
+        qDebug() << "checkpoint!, marks file truncated";
     }
     outstandingTransactions++;
     return txn;
@@ -651,7 +750,7 @@
 void FastImportRepository::forgetTransaction(Transaction *)
 {
     if (!--outstandingTransactions)
-        next_file_mark = maxMark;
+        next_file_mark = maxMark - 1;
 }
 
 void FastImportRepository::createAnnotatedTag(const QString &ref, const 
QString &svnprefix,
@@ -664,9 +763,9 @@
         tagName.remove(0, 10);
 
     if (!annotatedTags.contains(tagName))
-        printf("Creating annotated tag %s (%s)\n", qPrintable(tagName), 
qPrintable(ref));
+        printf("\nCreating annotated tag %s (%s) for %s\n", 
qPrintable(tagName), qPrintable(ref), qPrintable(name));
     else
-        printf("Re-creating annotated tag %s\n", qPrintable(tagName));
+        printf("\nRe-creating annotated tag %s for %s\n", qPrintable(tagName), 
qPrintable(name));
 
     AnnotatedTag &tag = annotatedTags[tagName];
     tag.supportingRef = ref;
@@ -682,7 +781,13 @@
     if (annotatedTags.isEmpty())
         return;
 
-    printf("Finalising tags for %s...", qPrintable(name));
+    QFile annotatedTagsFile(name + "/" + annotatedTagsFileName(name));
+    annotatedTagsFile.open(QIODevice::WriteOnly);
+    QDataStream annotatedTagsStream(&annotatedTagsFile);
+    annotatedTagsStream << annotatedTags;
+    annotatedTagsFile.close();
+
+    printf("Finalising annotated tags for %s...", qPrintable(name));
     startFastImport();
 
     QHash<QString, AnnotatedTag>::ConstIterator it = 
annotatedTags.constBegin();
@@ -720,10 +825,10 @@
             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);
+            bool written = 
txn->commitNote(formatMetadataMessage(tag.svnprefix, tag.revnum, 
tagName.toUtf8()), true);
             delete txn;
 
-            if (!fastImport.waitForBytesWritten(-1))
+            if (written && !fastImport.waitForBytesWritten(-1))
                 qFatal("Failed to write to process: %s", 
qPrintable(fastImport.errorString()));
         }
 
@@ -737,7 +842,18 @@
     printf("\n");
 }
 
+void FastImportRepository::saveBranchNotes()
+{
+    if (branchNotes.isEmpty())
+        return;
 
+    QFile branchNotesFile(name + "/" + branchNotesFileName(name));
+    branchNotesFile.open(QIODevice::WriteOnly);
+    QDataStream branchNotesStream(&branchNotesFile);
+    branchNotesStream << branchNotes;
+    branchNotesFile.close();
+}
+
 QByteArray
 FastImportRepository::msgFilter(QByteArray msg)
 {
@@ -744,18 +860,18 @@
     QByteArray output = msg;
 
     if (CommandLineParser::instance()->contains("msg-filter")) {
-       if (filterMsg.state() == QProcess::Running)
-           qFatal("filter process already running?");
+        if (filterMsg.state() == QProcess::Running)
+            qFatal("filter process already running?");
 
-       
filterMsg.start(CommandLineParser::instance()->optionArgument("msg-filter"));
+        
filterMsg.start(CommandLineParser::instance()->optionArgument("msg-filter"));
 
-       if(!(filterMsg.waitForStarted(-1)))
-           qFatal("Failed to Start Filter %d %s", __LINE__, 
qPrintable(filterMsg.errorString()));
+        if(!(filterMsg.waitForStarted(-1)))
+            qFatal("Failed to Start Filter %d %s", __LINE__, 
qPrintable(filterMsg.errorString()));
 
-       filterMsg.write(msg);
-       filterMsg.closeWriteChannel();
-       filterMsg.waitForFinished();
-       output = filterMsg.readAllStandardOutput();
+        filterMsg.write(msg);
+        filterMsg.closeWriteChannel();
+        filterMsg.waitForFinished();
+        output = filterMsg.readAllStandardOutput();
     }
     return output;
 }
@@ -782,7 +898,7 @@
         if (!CommandLineParser::instance()->contains("dry-run") && 
!CommandLineParser::instance()->contains("create-dump")) {
             fastImport.start("git", QStringList() << "fast-import" << 
marksOptions);
         } else {
-            fastImport.start("/bin/cat", QStringList());
+            fastImport.start("cat", QStringList());
         }
         fastImport.waitForStarted(-1);
 
@@ -806,13 +922,13 @@
 
 const QByteArray FastImportRepository::branchNote(const QString& branch) const
 {
-    return branches.value(branch).note;
+    return branchNotes.value(branch);
 }
 
 void FastImportRepository::setBranchNote(const QString& branch, const 
QByteArray& noteText)
 {
     if (branches.contains(branch))
-        branches[branch].note = noteText;
+        branchNotes[branch] = noteText;
 }
 
 bool FastImportRepository::hasPrefix() const
@@ -903,8 +1019,9 @@
     modifiedFiles.append(repository->prefix + path.toUtf8());
     modifiedFiles.append("\n");
 
+    // it is returned for being written to, so start the process in any case
+    repository->startFastImport();
     if (!CommandLineParser::instance()->contains("dry-run")) {
-        repository->startFastImport();
         repository->fastImport.writeNoLog("blob\nmark :");
         repository->fastImport.writeNoLog(QByteArray::number(mark));
         repository->fastImport.writeNoLog("\ndata ");
@@ -915,26 +1032,43 @@
     return &repository->fastImport;
 }
 
-void FastImportRepository::Transaction::commitNote(const QByteArray &noteText, 
bool append, const QByteArray &commit)
+bool FastImportRepository::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 (noteText[noteText.size() - 1] != '\n')
+    {
+        text += '\n';
+    }
 
+    QByteArray branchNote = repository->branchNote(branch);
+    if (!branchNote.isEmpty() && (branchNote[branchNote.size() - 1] != '\n'))
+    {
+        branchNote += '\n';
+    }
     if (append && commit.isNull() &&
         repository->branchExists(branch) &&
-        !repository->branchNote(branch).isEmpty())
+        !branchNote.isEmpty())
     {
-        text = repository->branchNote(branch) + text;
+        int i = branchNote.indexOf(text);
+        if ((i == 0) || ((i != -1) && (branchNote[i - 1] == '\n')))
+        {
+            // note is already present at the start or somewhere within 
following a newline
+            return false;
+        }
+        text = branchNote + text;
         message = "Appending Git note for current " + commitRef + "\n";
     }
 
     QByteArray s("");
     s.append("commit refs/notes/commits\n");
-    s.append("mark :" + QByteArray::number(maxMark + 1) + "\n");
+    s.append("mark :" + QByteArray::number(maxMark) + "\n");
     s.append("committer " + author + " " + QString::number(datetime) + " 
+0000" + "\n");
     s.append("data " + QString::number(message.length()) + "\n");
     s.append(message + "\n");
@@ -941,19 +1075,32 @@
     s.append("N inline " + commitRef + "\n");
     s.append("data " + QString::number(text.length()) + "\n");
     s.append(text + "\n");
+    repository->startFastImport();
     repository->fastImport.write(s);
 
-    if (commit.isNull()) {
+    if (commit.isNull())
+    {
         repository->setBranchNote(QString::fromUtf8(branch), text);
     }
+
+    return true;
 }
 
-void FastImportRepository::Transaction::commit()
+int FastImportRepository::Transaction::commit()
 {
+    foreach (QString branchName, repository->branches.keys())
+    {
+        if (branchName.toUtf8().startsWith(branch + "/") || 
branch.startsWith((branchName + "/").toUtf8()))
+        {
+            qCritical() << "Branch" << branch << "conflicts with already 
existing branch" << branchName;
+            return EXIT_FAILURE;
+        }
+    }
+
     repository->startFastImport();
 
     // 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
+    // However, a single SVN revision can modify multiple branches, and thus 
lead to multiple
     // commits in the same repo.  So, we need to maintain a separate commit 
mark counter.
     mark_t  mark = ++repository->last_commit_mark;
 
@@ -975,8 +1122,11 @@
     if (br.created && !br.marks.isEmpty() && br.marks.last()) {
         parentmark = br.marks.last();
     } else {
-        qWarning() << "WARN: Branch" << branch << "in repository" << 
repository->name << "doesn't exist at revision"
-                   << revnum << "-- did you resume from the wrong revision?";
+        if (revnum > 1) {
+            // Any branch at revision 1 isn't going to exist, so lets not 
alarm the user.
+            qWarning() << "WARN: 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);
@@ -996,7 +1146,7 @@
 
     // note some of the inferred merges
     QByteArray desc = "";
-    mark_t i = !!parentmark;   // if parentmark != 0, there's at least one 
parent
+    mark_t i = !!parentmark;        // if parentmark != 0, there's at least 
one parent
 
     if(log.contains("This commit was manufactured by cvs2svn") && 
merges.count() > 1) {
         qSort(merges);
@@ -1052,4 +1202,6 @@
     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));
+
+    return EXIT_SUCCESS;
 }

Modified: brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/repository.h
===================================================================
--- brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/repository.h     
2020-07-25 10:21:40 UTC (rev 76488)
+++ brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/repository.h     
2020-07-25 10:38:15 UTC (rev 76489)
@@ -101,7 +101,7 @@
         Transaction() {}
     public:
         virtual ~Transaction() {}
-        virtual void commit() = 0;
+        virtual int commit() = 0;
 
         virtual void setAuthor(const QByteArray &author) = 0;
         virtual void setDateTime(uint dt) = 0;
@@ -112,10 +112,12 @@
         virtual void deleteFile(const QString &path) = 0;
         virtual QIODevice *addFile(const QString &path, int mode, qint64 
length) = 0;
 
-        virtual void commitNote(const QByteArray &noteText, bool append,
+        virtual bool commitNote(const QByteArray &noteText, bool append,
                                 const QByteArray &commit = QByteArray()) = 0;
     };
     virtual int setupIncremental(int &cutoff) = 0;
+    virtual void restoreAnnotatedTags() = 0;
+    virtual void restoreBranchNotes() = 0;
     virtual void restoreLog() = 0;
     virtual ~Repository() {}
 
@@ -129,6 +131,7 @@
                                     const QByteArray &author, uint dt,
                                     const QByteArray &log) = 0;
     virtual void finalizeTags() = 0;
+    virtual void saveBranchNotes() = 0;
     virtual void commit() = 0;
 
     static QByteArray formatMetadataMessage(const QByteArray &svnprefix, int 
revnum,

Modified: brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/svn.cpp
===================================================================
--- brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/svn.cpp  2020-07-25 
10:21:40 UTC (rev 76488)
+++ brlcad/trunk/misc/repoconv/svn2git/svn-fast-export/svn.cpp  2020-07-25 
10:38:15 UTC (rev 76489)
@@ -25,10 +25,6 @@
 #define _LARGEFILE_SUPPORT
 #define _LARGEFILE64_SUPPORT
 
-#include <iostream>
-#include <fstream>
-#include <sys/stat.h>
-
 #include "svn.h"
 #include "CommandLineParser.h"
 
@@ -183,7 +179,9 @@
     QString path = pathToRepository;
     while (path.endsWith('/')) // no trailing slash allowed
         path = path.mid(0, path.length()-1);
-#if SVN_VER_MAJOR == 1 && SVN_VER_MINOR < 9
+#if SVN_VER_MAJOR == 1 && SVN_VER_MINOR < 7
+    SVN_ERR(svn_repos_open(&repos, QFile::encodeName(path), global_pool));
+#elif SVN_VER_MAJOR == 1 && SVN_VER_MINOR < 9
     SVN_ERR(svn_repos_open2(&repos, QFile::encodeName(path), NULL, 
global_pool));
 #else
     SVN_ERR(svn_repos_open3(&repos, QFile::encodeName(path), NULL, 
global_pool, scratch_pool));
@@ -307,62 +305,6 @@
     return EXIT_SUCCESS;
 }
 
-static int recursiveDumpDir(Repository::Transaction *txn, svn_fs_root_t 
*fs_root,
-                            const QByteArray &pathname, const QString 
&finalPathName,
-                            apr_pool_t *pool, svn_revnum_t revnum,
-                            const Rules::Match &rule, const MatchRuleList 
&matchRules,
-                            bool ruledebug)
-{
-    // get the dir listing
-    apr_hash_t *entries;
-    SVN_ERR(svn_fs_dir_entries(&entries, fs_root, pathname, pool));
-    AprAutoPool dirpool(pool);
-
-    // While we get a hash, put it in a map for sorted lookup, so we can
-    // repeat the conversions and get the same git commit hashes.
-    QMap<QByteArray, svn_node_kind_t> map;
-    for (apr_hash_index_t *i = apr_hash_first(pool, entries); i; i = 
apr_hash_next(i)) {
-        const void *vkey;
-        void *value;
-        apr_hash_this(i, &vkey, NULL, &value);
-        svn_fs_dirent_t *dirent = reinterpret_cast<svn_fs_dirent_t *>(value);
-        map.insertMulti(QByteArray(dirent->name), dirent->kind);
-    }
-
-    QMapIterator<QByteArray, svn_node_kind_t> i(map);
-    while (i.hasNext()) {
-        dirpool.clear();
-        i.next();
-        QByteArray entryName = pathname + '/' + i.key();
-        QString entryFinalName = finalPathName + QString::fromUtf8(i.key());
-
-        if (i.value() == svn_node_dir) {
-            entryFinalName += '/';
-            QString entryNameQString = entryName + '/';
-
-            MatchRuleList::ConstIterator match = findMatchRule(matchRules, 
revnum, entryNameQString);
-            if (match == matchRules.constEnd()) continue; // no match of 
parent repo? (should not happen)
-
-            const Rules::Match &matchedRule = *match;
-            if (matchedRule.action != Rules::Match::Export || 
matchedRule.repository != rule.repository) {
-                if (ruledebug)
-                    qDebug() << "recursiveDumpDir:" << entryNameQString << 
"skip entry for different/ignored repository";
-                continue;
-            }
-
-            if (recursiveDumpDir(txn, fs_root, entryName, entryFinalName, 
dirpool, revnum, rule, matchRules, ruledebug) == EXIT_FAILURE)
-                return EXIT_FAILURE;
-        } else if (i.value() == 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);
@@ -437,11 +379,21 @@
                 apr_hash_t *changes, apr_pool_t *pool);
     int addGitIgnore(apr_pool_t *pool, const char *key, QString path,
                      svn_fs_root_t *fs_root, Repository::Transaction *txn, 
const char *content = NULL);
+    int checkParentNotEmpty(apr_pool_t *pool, const char *key, QString path,
+                            svn_fs_root_t *fs_root, Repository::Transaction 
*txn);
+    int addGitIgnoreOnBranch(apr_pool_t *pool, QString key, QString path,
+                             svn_fs_root_t *fs_root, Repository::Transaction 
*txn);
     int fetchIgnoreProps(QString *ignore, apr_pool_t *pool, const char *key, 
svn_fs_root_t *fs_root);
     int fetchUnknownProps(apr_pool_t *pool, const char *key, svn_fs_root_t 
*fs_root);
 private:
+    int checkParentNoLongerEmpty(apr_pool_t *pool, const char *key, QString 
path, Repository::Transaction *txn);
     void splitPathName(const Rules::Match &rule, const QString &pathName, 
QString *svnprefix_p,
                        QString *repository_p, QString *effectiveRepository_p, 
QString *branch_p, QString *path_p);
+    int recursiveDumpDir(Repository::Transaction *txn, svn_fs_t *fs, 
svn_fs_root_t *fs_root,
+                         const QByteArray &pathname, const QString 
&finalPathName,
+                         apr_pool_t *pool, svn_revnum_t revnum,
+                         const Rules::Match &rule, const MatchRuleList 
&matchRules,
+                         bool ruledebug, int ignoreSet);
 };
 
 int SvnPrivate::exportRevision(int revnum)
@@ -600,7 +552,8 @@
         txn->setDateTime(epoch);
         txn->setLog(log);
 
-        txn->commit();
+        if (txn->commit() != EXIT_SUCCESS)
+            return EXIT_FAILURE;
         delete txn;
     }
 
@@ -638,7 +591,7 @@
         //qDebug() << "Adding directory:" << key;
     }
     // svn:ignore-properties
-    else if (is_dir && (change->change_kind == svn_fs_path_change_add || 
change->change_kind == svn_fs_path_change_modify)
+    else if (is_dir && (change->change_kind == svn_fs_path_change_add || 
change->change_kind == svn_fs_path_change_modify || change->change_kind == 
svn_fs_path_change_replace)
              && path_from == NULL && 
CommandLineParser::instance()->contains("svn-ignore")) {
         needCommit = true;
     }
@@ -778,6 +731,7 @@
     QString previous;
     QString prevsvnprefix, prevrepository, preveffectiverepository, 
prevbranch, prevpath;
 
+    bool needRecursiveDump = false;
     if (path_from != NULL) {
         previous = QString::fromUtf8(path_from);
         if (wasDir(fs, rev_from, path_from, pool.data())) {
@@ -792,6 +746,7 @@
         } else {
             qWarning() << "WARN: SVN reports a \"copy from\" @" << revnum << 
"from" << path_from << "@" << rev_from << "but no matching rules found! 
Ignoring copy, treating as a modification";
             path_from = NULL;
+            needRecursiveDump = true;
         }
     }
 
@@ -830,9 +785,30 @@
                          << qPrintable(prevbranch);
             }
 
-            if (repo->createBranch(branch, revnum, prevbranch, rev_from) == 
EXIT_FAILURE)
+            if (repo->createBranch(branch, revnum, prevbranch, rev_from) == 
EXIT_FAILURE) {
                 return EXIT_FAILURE;
+            }
 
+            if(CommandLineParser::instance()->contains("empty-dirs")) {
+                Repository::Transaction *txn = transactions.value(repository + 
branch, 0);
+                if (!txn) {
+                    txn = repo->newTransaction(branch, svnprefix, revnum);
+                    if (!txn)
+                        return EXIT_FAILURE;
+                    transactions.insert(repository + branch, txn);
+                }
+
+                AprAutoPool pool_from(pool.data());
+                svn_fs_root_t *fs_root_from;
+                if (svn_fs_revision_root(&fs_root_from, fs, rev_from, 
pool_from) != SVN_NO_ERROR) {
+                    return EXIT_FAILURE;
+                }
+
+                QString qkey = QString::fromUtf8(key);
+                addGitIgnoreOnBranch(pool_from, qkey, "", fs_root_from, txn);
+            }
+
+
             if(CommandLineParser::instance()->contains("svn-branches")) {
                 Repository::Transaction *txn = transactions.value(repository + 
branch, 0);
                 if (!txn) {
@@ -845,7 +821,11 @@
                 if(ruledebug)
                     qDebug() << "Create a true SVN copy of branch (" << key << 
"->" << branch << path << ")";
                 txn->deleteFile(path);
-                recursiveDumpDir(txn, fs_root, key, path, pool, revnum, rule, 
matchRules, ruledebug);
+                checkParentNotEmpty(pool, key, path, fs_root, txn);
+                recursiveDumpDir(txn, fs, fs_root, key, path, pool, revnum, 
rule, matchRules, ruledebug, false);
+                if (CommandLineParser::instance()->contains("empty-dirs")) {
+                    addGitIgnoreOnBranch(pool, key, path, fs_root, txn);
+                }
             }
             if (rule.annotate) {
                 // create an annotated tag
@@ -881,21 +861,24 @@
         if(ruledebug)
             qDebug() << "replaced with empty path (" << branch << path << ")";
         txn->deleteFile(path);
+        checkParentNotEmpty(pool, key, path, fs_root, txn);
     }
     if (change->change_kind == svn_fs_path_change_delete) {
         if(ruledebug)
             qDebug() << "delete (" << branch << path << ")";
         txn->deleteFile(path);
+        checkParentNotEmpty(pool, key, path, fs_root, txn);
     } else if (!current.endsWith('/')) {
         if(ruledebug)
             qDebug() << "add/change file (" << key << "->" << branch << path 
<< ")";
         dumpBlob(txn, fs_root, key, path, pool);
+        checkParentNoLongerEmpty(pool, key, path, txn);
     } else {
         if(ruledebug)
             qDebug() << "add/change dir (" << key << "->" << branch << path << 
")";
 
         // Check unknown svn-properties
-        if (((path_from == NULL && change->prop_mod==1) || (path_from != NULL 
&& change->change_kind == svn_fs_path_change_add))
+        if (((path_from == NULL && change->prop_mod==1) || (path_from != NULL 
&& (change->change_kind == svn_fs_path_change_add || change->change_kind == 
svn_fs_path_change_replace)))
             && CommandLineParser::instance()->contains("propcheck")) {
             if (fetchUnknownProps(pool, key, fs_root) != EXIT_SUCCESS) {
                 qWarning() << "Error checking svn-properties (" << key << ")";
@@ -902,29 +885,39 @@
             }
         }
 
-        int ignoreSet = false;
+        // if adding, modifying or replacing directory from empty path,
+        // we only came here to handle empty-dirs and svn-ignore
+        // do not redump the directory recursively, as all added files are
+        // included as separate file changes anyway, otherwise the whole
+        // directory tree is dumped multiple times
+        //
+        // needRecursiveDump is true if we come here because it actually was
+        // a copy, but the source is not available in the target repo, so
+        // we make a full dump instead here.
+        bool dumpDirectory = needRecursiveDump
+            || !((change->change_kind == svn_fs_path_change_add || 
change->change_kind == svn_fs_path_change_modify || change->change_kind == 
svn_fs_path_change_replace)
+                 && (path_from == NULL));
 
+        if (dumpDirectory) {
+            txn->deleteFile(path);
+            checkParentNotEmpty(pool, key, path, fs_root, txn);
+        }
+        checkParentNoLongerEmpty(pool, key, path, txn);
+
         // Add GitIgnore with svn:ignore
-        if (((path_from == NULL && change->prop_mod==1) || (path_from != NULL 
&& change->change_kind == svn_fs_path_change_add))
+        int ignoreSet = false;
+        if (((path_from == NULL && change->prop_mod==1) || (path_from != NULL 
&& (change->change_kind == svn_fs_path_change_add || change->change_kind == 
svn_fs_path_change_replace)))
             && CommandLineParser::instance()->contains("svn-ignore")) {
             QString svnignore;
             // TODO: Check if svn:ignore or other property was changed, but 
always set on copy/rename (path_from != NULL)
             if (fetchIgnoreProps(&svnignore, pool, key, fs_root) != 
EXIT_SUCCESS) {
                 qWarning() << "Error fetching svn-properties (" << key << ")";
-            } else if (!svnignore.isNull()) {
+            } else if (svnignore.isNull()) {
+                QString gitIgnorePath = path == "/" ? ".gitignore" : path + 
".gitignore";
+                txn->deleteFile(gitIgnorePath);
+            } else {
                 addGitIgnore(pool, key, path, fs_root, txn, 
svnignore.toStdString().c_str());
                 ignoreSet = true;
-               std::string ignorefile = std::to_string(revnum) + 
std::string(".gitignore");
-               struct stat buffer;
-               int fexists = (stat(ignorefile.c_str(), &buffer) == 0);
-               std::ofstream outfile(ignorefile, std::ios::out | 
std::ios::binary | std::fstream::app);
-               if (!fexists) {
-                       std::cout << revnum << ": creating gitignore\n\n";
-               } else {
-                       std::cout << revnum << ": appending to gitignore\n\n";
-               }
-               outfile << svnignore.toStdString();
-               outfile.close();
             }
         }
 
@@ -932,20 +925,99 @@
         if (CommandLineParser::instance()->contains("empty-dirs") && ignoreSet 
== false) {
             if (addGitIgnore(pool, key, path, fs_root, txn) == EXIT_SUCCESS) {
                 return EXIT_SUCCESS;
-            } else {
-                ignoreSet = true;
             }
         }
 
-        if (ignoreSet == false) {
-            txn->deleteFile(path);
+        if (dumpDirectory) {
+            recursiveDumpDir(txn, fs, fs_root, key, path, pool, revnum, rule, 
matchRules, ruledebug, ignoreSet);
+
+            if (CommandLineParser::instance()->contains("empty-dirs")) {
+                addGitIgnoreOnBranch(pool, key, path, fs_root, txn);
+            }
         }
-        recursiveDumpDir(txn, fs_root, key, path, pool, revnum, rule, 
matchRules, ruledebug);
     }
 
+    if (rule.annotate) {
+        // create an annotated tag
+        fetchRevProps();
+        repo->createAnnotatedTag(branch, svnprefix, revnum, authorident,
+                                 epoch, log);
+    }
+
     return EXIT_SUCCESS;
 }
 
+int SvnRevision::recursiveDumpDir(Repository::Transaction *txn, svn_fs_t *fs, 
svn_fs_root_t *fs_root,
+                                  const QByteArray &pathname, const QString 
&finalPathName,
+                                  apr_pool_t *pool, svn_revnum_t revnum,
+                                  const Rules::Match &rule, const 
MatchRuleList &matchRules,
+                                  bool ruledebug, int ignoreSet)
+{
+    if (!wasDir(fs, revnum, pathname.data(), pool)) {
+        if (dumpBlob(txn, fs_root, pathname, finalPathName, pool) == 
EXIT_FAILURE)
+            return EXIT_FAILURE;
+        return EXIT_SUCCESS;
+    }
+
+    if ((ignoreSet == false) && 
CommandLineParser::instance()->contains("svn-ignore")) {
+        QString svnignore;
+        if (fetchIgnoreProps(&svnignore, pool, pathname, fs_root) != 
EXIT_SUCCESS) {
+            qWarning() << "Error fetching svn-properties (" << pathname << ")";
+        } else if (!svnignore.isNull()) {
+            addGitIgnore(pool, pathname, finalPathName, fs_root, txn, 
svnignore.toStdString().c_str());
+        }
+    }
+
+    // get the dir listing
+    apr_hash_t *entries;
+    SVN_ERR(svn_fs_dir_entries(&entries, fs_root, pathname, pool));
+    AprAutoPool dirpool(pool);
+
+    // While we get a hash, put it in a map for sorted lookup, so we can
+    // repeat the conversions and get the same git commit hashes.
+    QMap<QByteArray, svn_node_kind_t> map;
+    for (apr_hash_index_t *i = apr_hash_first(pool, entries); i; i = 
apr_hash_next(i)) {
+        const void *vkey;
+        void *value;
+        apr_hash_this(i, &vkey, NULL, &value);
+        svn_fs_dirent_t *dirent = reinterpret_cast<svn_fs_dirent_t *>(value);
+        map.insertMulti(QByteArray(dirent->name), dirent->kind);
+    }
+
+    QMapIterator<QByteArray, svn_node_kind_t> i(map);
+    while (i.hasNext()) {
+        dirpool.clear();
+        i.next();
+        QByteArray entryName = pathname + '/' + i.key();
+        QString entryFinalName = finalPathName + QString::fromUtf8(i.key());
+
+        if (i.value() == svn_node_dir) {
+            entryFinalName += '/';
+            QString entryNameQString = entryName + '/';
+
+            MatchRuleList::ConstIterator match = findMatchRule(matchRules, 
revnum, entryNameQString);
+            if (match == matchRules.constEnd()) continue; // no match of 
parent repo? (should not happen)
+
+            const Rules::Match &matchedRule = *match;
+            if (matchedRule.action != Rules::Match::Export || 
matchedRule.repository != rule.repository) {
+                if (ruledebug)
+                    qDebug() << "recursiveDumpDir:" << entryNameQString << 
"skip entry for different/ignored repository";
+                continue;
+            }
+
+            if (recursiveDumpDir(txn, fs, fs_root, entryName, entryFinalName, 
dirpool, revnum, rule, matchRules, ruledebug, false) == EXIT_FAILURE)
+                return EXIT_FAILURE;
+        } else if (i.value() == svn_node_file) {
+            printf("+");
+            fflush(stdout);
+            if (dumpBlob(txn, fs_root, entryName, entryFinalName, dirpool) == 
EXIT_FAILURE)
+                return EXIT_FAILURE;
+        }
+    }
+
+    return EXIT_SUCCESS;
+}
+
 int SvnRevision::recurse(const char *path, const svn_fs_path_change2_t *change,
                          const char *path_from, const MatchRuleList 
&matchRules, svn_revnum_t rev_from,
                          apr_hash_t *changes, apr_pool_t *pool)
@@ -1034,22 +1106,197 @@
         if (apr_hash_count(entries)!=0) {
             return EXIT_FAILURE;
         }
+
+        // if svn-ignore should have added a .gitignore file, do not overwrite 
it with an empty one
+        // if svn:ignore could not be determined, stay safe and do not 
overwrite the .gitignore file
+        // even if then an empty directory might be missing
+        QString svnignore;
+        if (CommandLineParser::instance()->contains("svn-ignore")) {
+            if (fetchIgnoreProps(&svnignore, pool, key, fs_root) != 
EXIT_SUCCESS) {
+                qWarning() << "Error fetching svn-properties (" << key << ")";
+                return EXIT_FAILURE;
+            } else if (!svnignore.isNull()) {
+                return EXIT_FAILURE;
+            }
+        }
     }
 
     // Add gitignore-File
-    QString gitIgnorePath = path + ".gitignore";
+    QString gitIgnorePath = path == "/" ? ".gitignore" : path + ".gitignore";
     if (content) {
         QIODevice *io = txn->addFile(gitIgnorePath, 33188, strlen(content));
-        io->write(content);
-        io->putChar('\n');
+        if (!CommandLineParser::instance()->contains("dry-run")) {
+            io->write(content);
+            io->putChar('\n');
+        }
     } else {
+        // no empty placeholder .gitignore for repository root
+        // this should be handled previously already, just a
+        // security measure here.
+        if (path == "/") {
+            return EXIT_FAILURE;
+        }
         QIODevice *io = txn->addFile(gitIgnorePath, 33188, 0);
-        io->putChar('\n');
+        if (!CommandLineParser::instance()->contains("dry-run")) {
+            io->putChar('\n');
+        }
     }
 
     return EXIT_SUCCESS;
 }
 
+int SvnRevision::checkParentNotEmpty(apr_pool_t *pool, const char *key, 
QString path,
+                                     svn_fs_root_t *fs_root, 
Repository::Transaction *txn)
+{
+    if (!CommandLineParser::instance()->contains("empty-dirs")) {
+      return EXIT_FAILURE;
+    }
+    QString slash = "/";
+    QString qkey = QString::fromUtf8(key);
+    while (qkey.endsWith('/'))
+        qkey = qkey.mid(0, qkey.length()-1);
+    int index = qkey.lastIndexOf(slash);
+    if (index == -1) {
+        return EXIT_FAILURE;
+    }
+    QString parentKey = qkey.left(index);
+
+    apr_hash_t *entries;
+    SVN_ERR(svn_fs_dir_entries(&entries, fs_root, 
parentKey.toStdString().c_str(), pool));
+    // directory is not empty
+    if (apr_hash_count(entries)!=0) {
+        return EXIT_FAILURE;
+    }
+
+    QString cleanPath = path;
+    while (cleanPath.endsWith('/'))
+        cleanPath = cleanPath.mid(0, cleanPath.length()-1);
+    index = cleanPath.lastIndexOf(slash);
+    QString parentPath = cleanPath.left(index);
+
+    // we are in the root directory, do not add a .gitignore here
+    if (index == -1) {
+        return EXIT_FAILURE;
+    }
+
+    // if svn-ignore should have added a .gitignore file, do not overwrite it 
with an empty one
+    // if svn:ignore could not be determined, stay safe and do not overwrite 
the .gitignore file
+    // even if then an empty directory might be missing
+    QString svnignore;
+    if (CommandLineParser::instance()->contains("svn-ignore")) {
+        if (fetchIgnoreProps(&svnignore, pool, 
parentKey.toStdString().c_str(), fs_root) != EXIT_SUCCESS) {
+            qWarning() << "Error fetching svn-properties (" << parentKey << 
")";
+            return EXIT_FAILURE;
+        } else if (!svnignore.isNull()) {
+            return EXIT_FAILURE;
+        }
+    }
+
+    // Add gitignore-File
+    QString gitIgnorePath = parentPath + "/.gitignore";
+    QIODevice *io = txn->addFile(gitIgnorePath, 33188, 0);
+    if (!CommandLineParser::instance()->contains("dry-run")) {
+      io->putChar('\n');
+    }
+
+    return EXIT_SUCCESS;
+}
+
+int SvnRevision::checkParentNoLongerEmpty(apr_pool_t *pool, const char *key, 
QString path, Repository::Transaction *txn) {
+    if (!CommandLineParser::instance()->contains("empty-dirs")) {
+      return EXIT_FAILURE;
+    }
+    QString slash = "/";
+    QString qkey = QString::fromUtf8(key);
+    while (qkey.endsWith('/'))
+        qkey = qkey.mid(0, qkey.length()-1);
+    int index = qkey.lastIndexOf(slash);
+    if (index == -1) {
+        return EXIT_FAILURE;
+    }
+    QString parentKey = qkey.left(index);
+
+    AprAutoPool subpool(pool);
+    svn_fs_root_t *previous_fs_root;
+    if (svn_fs_revision_root(&previous_fs_root, fs, revnum - 1, subpool) != 
SVN_NO_ERROR) {
+        return EXIT_FAILURE;
+    }
+
+    apr_hash_t *entries;
+    // directory did not exist
+    if (svn_fs_dir_entries(&entries, previous_fs_root, 
parentKey.toStdString().c_str(), pool) != SVN_NO_ERROR) {
+        return EXIT_FAILURE;
+    }
+
+    // directory was not empty
+    if (apr_hash_count(entries)!=0) {
+        return EXIT_FAILURE;
+    }
+
+    QString cleanPath = path;
+    while (cleanPath.endsWith('/'))
+        cleanPath = cleanPath.mid(0, cleanPath.length()-1);
+    index = cleanPath.lastIndexOf(slash);
+    QString parentPath = cleanPath.left(index);
+
+    // we are in the root directory, we did not add a .gitignore here
+    if (index == -1) {
+        return EXIT_FAILURE;
+    }
+
+    // if svn-ignore should have added a .gitignore file, do not consider the 
directory empty
+    // if svn:ignore could not be determined, stay safe and do not consider 
the directory empty
+    // even if then an empty .gitignore might be left over
+    QString svnignore;
+    if (CommandLineParser::instance()->contains("svn-ignore")) {
+        if (fetchIgnoreProps(&svnignore, pool, 
parentKey.toStdString().c_str(), previous_fs_root) != EXIT_SUCCESS) {
+            qWarning() << "Error fetching svn-properties (" << parentKey << 
")";
+            return EXIT_FAILURE;
+        } else if (!svnignore.isNull()) {
+            return EXIT_FAILURE;
+        }
+    }
+
+    // Delete gitignore-File
+    QString gitIgnorePath = parentPath + "/.gitignore";
+    txn->deleteFile(gitIgnorePath);
+    return EXIT_SUCCESS;
+}
+
+int SvnRevision::addGitIgnoreOnBranch(apr_pool_t *pool, QString key, QString 
path,
+                                      svn_fs_root_t *fs_root, 
Repository::Transaction *txn)
+{
+    apr_hash_t *entries;
+    if (svn_fs_dir_entries(&entries, fs_root, key.toStdString().c_str(), pool) 
!= SVN_NO_ERROR) {
+        return EXIT_FAILURE;
+    }
+
+    QMap<QByteArray, svn_node_kind_t> map;
+    for (apr_hash_index_t *i = apr_hash_first(pool, entries); i; i = 
apr_hash_next(i)) {
+        const void *vkey;
+        void *value;
+        apr_hash_this(i, &vkey, NULL, &value);
+        svn_fs_dirent_t *dirent = reinterpret_cast<svn_fs_dirent_t *>(value);
+        map.insertMulti(QByteArray(dirent->name), dirent->kind);
+    }
+
+    QMapIterator<QByteArray, svn_node_kind_t> i(map);
+    while (i.hasNext()) {
+        i.next();
+        QString entryName = key + "/" + i.key();
+        QString entryFinalName = path + QString::fromUtf8(i.key());
+
+        if (i.value() == svn_node_dir) {
+            entryFinalName += "/";
+            if (addGitIgnore(pool, entryName.toStdString().c_str(), 
entryFinalName, fs_root, txn) == EXIT_FAILURE) {
+                addGitIgnoreOnBranch(pool, entryName, entryFinalName, fs_root, 
txn);
+            }
+        }
+    }
+
+    return EXIT_SUCCESS;
+}
+
 int SvnRevision::fetchIgnoreProps(QString *ignore, apr_pool_t *pool, const 
char *key, svn_fs_root_t *fs_root)
 {
     // Get svn:ignore
@@ -1062,6 +1309,9 @@
         
ignore->remove(QRegExp("^[^\\r\\n]*[\\\\/][^\\r\\n]*(?:[\\r\\n]|$)|[\\r\\n][^\\r\\n]*[\\\\/][^\\r\\n]*(?=[\\r\\n]|$)"));
         // add a slash in front to have the same meaning in Git of only 
working on the direct children
         ignore->replace(QRegExp("(^|[\\r\\n])\\s*(?![\\r\\n]|$)"), "\\1/");
+        if (ignore->trimmed().isEmpty()) {
+            *ignore = QString();
+        }
     } else {
         *ignore = QString();
     }
@@ -1074,7 +1324,9 @@
         // remove patterns with slashes or backslashes,
         // they didn't match anything in Subversion but would in Git eventually
         
global_ignore.remove(QRegExp("^[^\\r\\n]*[\\\\/][^\\r\\n]*(?:[\\r\\n]|$)|[\\r\\n][^\\r\\n]*[\\\\/][^\\r\\n]*(?=[\\r\\n]|$)"));
-        ignore->append(global_ignore);
+        if (!global_ignore.trimmed().isEmpty()) {
+            ignore->append(global_ignore);
+        }
     }
 
     // replace multiple asterisks Subversion meaning by Git meaning
@@ -1093,7 +1345,7 @@
     const void *propKey;
     for (hi = apr_hash_first(pool, table); hi; hi = apr_hash_next(hi)) {
         apr_hash_this(hi, &propKey, NULL, &propVal);
-        if (strcmp((char*)propKey, "svn:ignore")!=0) {
+        if (strcmp((char*)propKey, "svn:ignore")!=0 && strcmp((char*)propKey, 
"svn:global-ignores")!=0 && strcmp((char*)propKey, "svn:mergeinfo") !=0) {
             qWarning() << "WARN: Unknown svn-property" << (char*)propKey << 
"set to" << ((svn_string_t*)propVal)->data << "for" << key;
         }
     }
@@ -1100,4 +1352,3 @@
 
     return EXIT_SUCCESS;
 }
-

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.



_______________________________________________
BRL-CAD Source Commits mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/brlcad-commits

Reply via email to