Revision: 76444
          http://sourceforge.net/p/brlcad/code/76444
Author:   starseeker
Date:     2020-07-23 14:15:54 +0000 (Thu, 23 Jul 2020)
Log Message:
-----------
Stash the repowork tool being used to post-process git fast-import files.  
Unlike the rest of the repoconv code this has potential use down the road, so 
give it its own directory.

Modified Paths:
--------------
    brlcad/trunk/misc/CMakeLists.txt

Added Paths:
-----------
    brlcad/trunk/misc/repowork/
    brlcad/trunk/misc/repowork/CMakeLists.txt
    brlcad/trunk/misc/repowork/COPYING
    brlcad/trunk/misc/repowork/README
    brlcad/trunk/misc/repowork/TextFlow.hpp
    brlcad/trunk/misc/repowork/blob.cpp
    brlcad/trunk/misc/repowork/commit.cpp
    brlcad/trunk/misc/repowork/cxxopts.hpp
    brlcad/trunk/misc/repowork/misc_cmds.cpp
    brlcad/trunk/misc/repowork/repowork.cpp
    brlcad/trunk/misc/repowork/repowork.h
    brlcad/trunk/misc/repowork/reset.cpp
    brlcad/trunk/misc/repowork/tag.cpp

Modified: brlcad/trunk/misc/CMakeLists.txt
===================================================================
--- brlcad/trunk/misc/CMakeLists.txt    2020-07-23 13:08:27 UTC (rev 76443)
+++ brlcad/trunk/misc/CMakeLists.txt    2020-07-23 14:15:54 UTC (rev 76444)
@@ -253,6 +253,18 @@
   repoconv/sync_commit_trunk.sh
   repoconv/tagmap.sh
   repoconv/terra.dsp
+  repowork/COPYING
+  repowork/README
+  repowork/blob.cpp
+  repowork/TextFlow.hpp
+  repowork/repowork.cpp
+  repowork/reset.cpp
+  repowork/misc_cmds.cpp
+  repowork/cxxopts.hpp
+  repowork/tag.cpp
+  repowork/repowork.h
+  repowork/commit.cpp
+  repowork/CMakeLists.txt
   win32-msvc/CMakeLists.txt
   win32-msvc/Dll/BrlcadCore.def
   win32-msvc/Dll/BrlcadCore.rc

Added: brlcad/trunk/misc/repowork/CMakeLists.txt
===================================================================
--- brlcad/trunk/misc/repowork/CMakeLists.txt                           (rev 0)
+++ brlcad/trunk/misc/repowork/CMakeLists.txt   2020-07-23 14:15:54 UTC (rev 
76444)
@@ -0,0 +1,38 @@
+# Minimum required version of CMake
+cmake_minimum_required(VERSION 3.1.3)
+
+# set CMake project name
+project(repowork)
+
+add_definitions(-g)
+
+include_directories(
+  ${CMAKE_CURRENT_BINARY_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  )
+
+set(repowork_srcs
+  blob.cpp
+  commit.cpp
+  misc_cmds.cpp
+  repowork.cpp
+  reset.cpp
+  tag.cpp
+  )
+
+add_executable(repowork ${repowork_srcs})
+
+include(CheckCXXCompilerFlag)
+check_cxx_compiler_flag(-O3 O3_COMPILER_FLAG)
+if (O3_COMPILER_FLAG)
+  target_compile_options(repowork PRIVATE "-O3")
+endif (O3_COMPILER_FLAG)
+
+# Local Variables:
+# tab-width: 8
+# mode: cmake
+# indent-tabs-mode: t
+# End:
+# ex: shiftwidth=2 tabstop=8
+
+


Property changes on: brlcad/trunk/misc/repowork/CMakeLists.txt
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/COPYING
===================================================================
--- brlcad/trunk/misc/repowork/COPYING                          (rev 0)
+++ brlcad/trunk/misc/repowork/COPYING  2020-07-23 14:15:54 UTC (rev 76444)
@@ -0,0 +1,7 @@
+This code uses cxxopts.hpp and TextFlow.hpp, which have their
+own licenses as documented in the respective files.
+
+The remainder of the code is public domain / CC0 1.0.
+
+(https://creativecommons.org/publicdomain/zero/1.0/)
+


Property changes on: brlcad/trunk/misc/repowork/COPYING
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/README
===================================================================
--- brlcad/trunk/misc/repowork/README                           (rev 0)
+++ brlcad/trunk/misc/repowork/README   2020-07-23 14:15:54 UTC (rev 76444)
@@ -0,0 +1,27 @@
+This is a basic C++ program for reading Git fast-import streams and writing
+them back out after doing various operations on the data.  It's not as
+generally powerful as something like https://github.com/newren/git-filter-repo
+- it was put together to handle a few specific desired post-processing
+  operations on the BRL-CAD repository after it's main CVS/SVN -> GIT
+conversion was completed.  Features:
+
+* Trim spaces and extra line endings from commit messages
+
+* Wrap long single-line commit messages to 72 characters
+
+* Replace committer ids according to a mapping file
+
+* Append git notes to commit messages and remove the notes (essentially
+  preserves data while migrating a repository away from the git notes feature.)
+
+
+There are a variety of known unimplemented features - work was "done" for
+BRL-CAD once our conversion could be handled - but it shouldn't be too hard to
+expand if there is need/interest.
+
+Unlike most of the repository conversion code, this will stay in BRL-CAD after
+the conversion is complete as a guarantor against future needs to change the
+repository (say for example, another migration which requires new email
+addresses for proper integration with the hosting platform - that was one issue
+encountered with the migration to github.com)
+


Property changes on: brlcad/trunk/misc/repowork/README
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/TextFlow.hpp
===================================================================
--- brlcad/trunk/misc/repowork/TextFlow.hpp                             (rev 0)
+++ brlcad/trunk/misc/repowork/TextFlow.hpp     2020-07-23 14:15:54 UTC (rev 
76444)
@@ -0,0 +1,339 @@
+// TextFlowCpp
+//
+// A single-header library for wrapping and laying out basic text, by Phil Nash
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// This project is hosted at https://github.com/philsquared/textflowcpp
+
+#ifndef TEXTFLOW_HPP_INCLUDED
+#define TEXTFLOW_HPP_INCLUDED
+
+#include <cassert>
+#include <ostream>
+#include <sstream>
+#include <vector>
+
+#ifndef TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#define TEXTFLOW_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+
+namespace TextFlow {
+
+    inline auto isWhitespace( char c ) -> bool {
+        static std::string chars = " \t\n\r";
+        return chars.find( c ) != std::string::npos;
+    }
+    inline auto isBreakableBefore( char c ) -> bool {
+        static std::string chars = "[({<|";
+        return chars.find( c ) != std::string::npos;
+    }
+    inline auto isBreakableAfter( char c ) -> bool {
+        static std::string chars = "])}>.,:;*+-=&/\\";
+        return chars.find( c ) != std::string::npos;
+    }
+
+    class Columns;
+
+    class Column {
+        std::vector<std::string> m_strings;
+        size_t m_width = TEXTFLOW_CONFIG_CONSOLE_WIDTH;
+        size_t m_indent = 0;
+        size_t m_initialIndent = std::string::npos;
+
+    public:
+        class iterator {
+            friend Column;
+
+            Column const& m_column;
+            size_t m_stringIndex = 0;
+            size_t m_pos = 0;
+
+            size_t m_len = 0;
+            size_t m_end = 0;
+            bool m_suffix = false;
+
+            iterator( Column const& column, size_t stringIndex )
+            :   m_column( column ),
+                m_stringIndex( stringIndex )
+            {}
+
+            auto line() const -> std::string const& { return 
m_column.m_strings[m_stringIndex]; }
+
+            auto isBoundary( size_t at ) const -> bool {
+                assert( at > 0 );
+                assert( at <= line().size() );
+
+                return at == line().size() ||
+                       ( isWhitespace( line()[at] ) && !isWhitespace( 
line()[at-1] ) ) ||
+                       isBreakableBefore( line()[at] ) ||
+                       isBreakableAfter( line()[at-1] );
+            }
+
+            void calcLength() {
+                assert( m_stringIndex < m_column.m_strings.size() );
+
+                m_suffix = false;
+                auto width = m_column.m_width-indent();
+                m_end = m_pos;
+                if(!line().empty() && line()[m_pos] == '\n')
+                    ++m_end;
+                while( m_end < line().size() && line()[m_end] != '\n' )
+                    ++m_end;
+
+                if( m_end < m_pos + width ) {
+                    m_len = m_end - m_pos;
+                }
+                else {
+                    size_t len = width;
+                    while (len > 0 && !isBoundary(m_pos + len))
+                        --len;
+                    while (len > 0 && isWhitespace( line()[m_pos + len - 1] ))
+                        --len;
+
+                    if (len > 0) {
+                        m_len = len;
+                    } else {
+                        m_suffix = true;
+                        m_len = width - 1;
+                    }
+                }
+            }
+
+            auto indent() const -> size_t {
+                auto initial = m_pos == 0 && m_stringIndex == 0 ? 
m_column.m_initialIndent : std::string::npos;
+                return initial == std::string::npos ? m_column.m_indent : 
initial;
+            }
+
+            auto addIndentAndSuffix(std::string const &plain) const -> 
std::string {
+                return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" 
: plain);
+            }
+
+        public:
+            using difference_type = std::ptrdiff_t;
+            using value_type = std::string;
+            using pointer = value_type*;
+            using reference = value_type&;
+            using iterator_category = std::forward_iterator_tag;
+
+            explicit iterator( Column const& column ) : m_column( column ) {
+                assert( m_column.m_width > m_column.m_indent );
+                assert( m_column.m_initialIndent == std::string::npos || 
m_column.m_width > m_column.m_initialIndent );
+                calcLength();
+                if( m_len == 0 )
+                    m_stringIndex++; // Empty string
+            }
+
+            auto operator *() const -> std::string {
+                assert( m_stringIndex < m_column.m_strings.size() );
+                assert( m_pos <= m_end );
+                return addIndentAndSuffix(line().substr(m_pos, m_len));
+            }
+
+            auto operator ++() -> iterator& {
+                m_pos += m_len;
+                if( m_pos < line().size() && line()[m_pos] == '\n' )
+                    m_pos += 1;
+                else
+                    while( m_pos < line().size() && isWhitespace( 
line()[m_pos] ) )
+                        ++m_pos;
+
+                if( m_pos == line().size() ) {
+                    m_pos = 0;
+                    ++m_stringIndex;
+                }
+                if( m_stringIndex < m_column.m_strings.size() )
+                    calcLength();
+                return *this;
+            }
+            auto operator ++(int) -> iterator {
+                iterator prev( *this );
+                operator++();
+                return prev;
+            }
+
+            auto operator ==( iterator const& other ) const -> bool {
+                return
+                    m_pos == other.m_pos &&
+                    m_stringIndex == other.m_stringIndex &&
+                    &m_column == &other.m_column;
+            }
+            auto operator !=( iterator const& other ) const -> bool {
+                return !operator==( other );
+            }
+        };
+        using const_iterator = iterator;
+
+        explicit Column( std::string const& text ) { m_strings.push_back( text 
); }
+
+        auto width( size_t newWidth ) -> Column& {
+            assert( newWidth > 0 );
+            m_width = newWidth;
+            return *this;
+        }
+        auto indent( size_t newIndent ) -> Column& {
+            m_indent = newIndent;
+            return *this;
+        }
+        auto initialIndent( size_t newIndent ) -> Column& {
+            m_initialIndent = newIndent;
+            return *this;
+        }
+
+        auto width() const -> size_t { return m_width; }
+        auto begin() const -> iterator { return iterator( *this ); }
+        auto end() const -> iterator { return { *this, m_strings.size() }; }
+
+        inline friend std::ostream& operator << ( std::ostream& os, Column 
const& col ) {
+            bool first = true;
+            for( auto line : col ) {
+                if( first )
+                    first = false;
+                else
+                    os << "\n";
+                os <<  line;
+            }
+            return os;
+        }
+
+        auto operator + ( Column const& other ) -> Columns;
+
+        auto toString() const -> std::string {
+            std::ostringstream oss;
+            oss << *this;
+            return oss.str();
+        }
+    };
+
+    class Spacer : public Column {
+
+    public:
+        explicit Spacer( size_t spaceWidth ) : Column( "" ) {
+            width( spaceWidth );
+        }
+    };
+
+    class Columns {
+        std::vector<Column> m_columns;
+
+    public:
+
+        class iterator {
+            friend Columns;
+            struct EndTag {};
+
+            std::vector<Column> const& m_columns;
+            std::vector<Column::iterator> m_iterators;
+            size_t m_activeIterators;
+
+            iterator( Columns const& columns, EndTag )
+            :   m_columns( columns.m_columns ),
+                m_activeIterators( 0 )
+            {
+                m_iterators.reserve( m_columns.size() );
+
+                for( auto const& col : m_columns )
+                    m_iterators.push_back( col.end() );
+            }
+
+        public:
+            using difference_type = std::ptrdiff_t;
+            using value_type = std::string;
+            using pointer = value_type*;
+            using reference = value_type&;
+            using iterator_category = std::forward_iterator_tag;
+
+            explicit iterator( Columns const& columns )
+            :   m_columns( columns.m_columns ),
+                m_activeIterators( m_columns.size() )
+            {
+                m_iterators.reserve( m_columns.size() );
+
+                for( auto const& col : m_columns )
+                    m_iterators.push_back( col.begin() );
+            }
+
+            auto operator ==( iterator const& other ) const -> bool {
+                return m_iterators == other.m_iterators;
+            }
+            auto operator !=( iterator const& other ) const -> bool {
+                return m_iterators != other.m_iterators;
+            }
+            auto operator *() const -> std::string {
+                std::string row, padding;
+
+                for( size_t i = 0; i < m_columns.size(); ++i ) {
+                    auto width = m_columns[i].width();
+                    if( m_iterators[i] != m_columns[i].end() ) {
+                        std::string col = *m_iterators[i];
+                        row += padding + col;
+                        if( col.size() < width )
+                            padding = std::string( width - col.size(), ' ' );
+                        else
+                            padding = "";
+                    }
+                    else {
+                        padding += std::string( width, ' ' );
+                    }
+                }
+                return row;
+            }
+            auto operator ++() -> iterator& {
+                for( size_t i = 0; i < m_columns.size(); ++i ) {
+                    if (m_iterators[i] != m_columns[i].end())
+                        ++m_iterators[i];
+                }
+                return *this;
+            }
+            auto operator ++(int) -> iterator {
+                iterator prev( *this );
+                operator++();
+                return prev;
+            }
+        };
+        using const_iterator = iterator;
+
+        auto begin() const -> iterator { return iterator( *this ); }
+        auto end() const -> iterator { return { *this, iterator::EndTag() }; }
+
+        auto operator += ( Column const& col ) -> Columns& {
+            m_columns.push_back( col );
+            return *this;
+        }
+        auto operator + ( Column const& col ) -> Columns {
+            Columns combined = *this;
+            combined += col;
+            return combined;
+        }
+
+        inline friend std::ostream& operator << ( std::ostream& os, Columns 
const& cols ) {
+
+            bool first = true;
+            for( auto line : cols ) {
+                if( first )
+                    first = false;
+                else
+                    os << "\n";
+                os << line;
+            }
+            return os;
+        }
+
+        auto toString() const -> std::string {
+            std::ostringstream oss;
+            oss << *this;
+            return oss.str();
+        }
+    };
+
+    inline auto Column::operator + ( Column const& other ) -> Columns {
+        Columns cols;
+        cols += *this;
+        cols += other;
+        return cols;
+    }
+}
+
+#endif // TEXTFLOW_HPP_INCLUDED


Property changes on: brlcad/trunk/misc/repowork/TextFlow.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/blob.cpp
===================================================================
--- brlcad/trunk/misc/repowork/blob.cpp                         (rev 0)
+++ brlcad/trunk/misc/repowork/blob.cpp 2020-07-23 14:15:54 UTC (rev 76444)
@@ -0,0 +1,168 @@
+/*                        B L O B . C P P
+ * BRL-CAD
+ *
+ * Published in 2020 by the United States Government.
+ * This work is in the public domain.
+ *
+ */
+/** @file blob.cpp
+ *
+ * Logic for handling git blobs
+ *
+ */
+
+#include "repowork.h"
+
+typedef int (*blobcmd_t)(git_blob_data *, std::ifstream &);
+blobcmd_t
+blob_find_cmd(std::string &line, std::map<std::string, blobcmd_t> &cmdmap)
+{
+    blobcmd_t cc = NULL;
+    std::map<std::string, blobcmd_t>::iterator c_it;
+    for (c_it = cmdmap.begin(); c_it != cmdmap.end(); c_it++) {
+       if (!ficmp(line, c_it->first)) {
+           cc = c_it->second;
+           break;
+       }
+    }
+    return cc;
+}
+
+int
+blob_parse_blob(git_blob_data *cd, std::ifstream &infile)
+{
+    /* No other information on line - just consume it and continue */
+    std::string line;
+    std::getline(infile, line);
+    return 0;
+}
+
+int
+blob_parse_data(git_blob_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 5);  // Remove "data " prefix
+    cd->length = std::stoi(line);
+    cd->offset = infile.tellg();
+    long offset = cd->offset + cd->length;
+    infile.seekg(offset);
+    return 0;
+}
+
+int
+blob_parse_mark(git_blob_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    //std::cout << "mark line: " << line << "\n";
+    line.erase(0, 5); // Remove "mark " prefix
+    //std::cout << "mark line: " << line << "\n";
+    if (line.c_str()[0] != ':') {
+       std::cerr << "Mark without \":\" character??: " <<  line << "\n";
+       return -1;
+    }
+    line.erase(0, 1); // Remove ":" prefix
+    cd->id.mark = cd->s->next_mark(std::stol(line));
+    //std::cout << "Mark id :" << line << " -> " << cd->id.mark << "\n";
+    return 0;
+}
+
+int
+blob_parse_original_oid(git_blob_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 13);  // Remove "original-oid " prefix
+    cd->id.sha1 = line;
+    return 0;
+}
+
+int
+parse_blob(git_fi_data *fi_data, std::ifstream &infile)
+{
+    //std::cout << "Found command: blob\n";
+
+    git_blob_data gbd;
+    gbd.s = fi_data;
+
+    // Tell the blob where it will be in the vector.
+    gbd.id.index = fi_data->blobs.size();
+
+    std::map<std::string, blobcmd_t> cmdmap;
+    cmdmap[std::string("blob")] = blob_parse_blob;
+    cmdmap[std::string("data")] = blob_parse_data;
+    cmdmap[std::string("mark")] = blob_parse_mark;
+    cmdmap[std::string("original-oid")] = blob_parse_original_oid;
+
+    std::string line;
+    size_t offset = infile.tellg();
+    int blob_done = 0;
+    while (!blob_done && std::getline(infile, line)) {
+
+       blobcmd_t cc = blob_find_cmd(line, cmdmap);
+
+       // If we found a command, process it.  Otherwise, we are done
+       // with the blob and need to clean up.
+       if (cc) {
+           //std::cout << "blob line: " << line << "\n";
+           infile.seekg(offset);
+           (*cc)(&gbd, infile);
+           offset = infile.tellg();
+       } else {
+           // Whatever was on that line, it's not a blob input.
+           // Reset input to allow the parent routine to deal with
+           // it, and return.
+           infile.seekg(offset);
+           blob_done = 1;
+       }
+    }
+
+    gbd.id.mark = fi_data->next_mark(gbd.id.mark);
+    fi_data->mark_to_index[gbd.id.mark] = gbd.id.index;
+
+    // If we have an original-oid sha1, associate it with the mark
+    if (gbd.id.sha1.length() == 40) {
+       fi_data->sha1_to_mark[gbd.id.sha1] = gbd.id.mark;
+    }
+
+    // Add the blob to the data
+    fi_data->blobs.push_back(gbd);
+
+    return 0;
+}
+
+int
+write_blob(std::ofstream &outfile, git_blob_data *b, std::ifstream &infile)
+{
+    if (!infile.good()) {
+        return -1;
+    }
+
+    // Header
+    outfile << "blob\n";
+    outfile << "mark :" << b->id.mark << "\n";
+    if (b->id.sha1.length()) {
+       outfile << "original-oid " << b->id.sha1 << "\n";
+    } 
+    outfile << "data " << b->length << "\n";
+
+    // Contents
+    /* TODO - probably don't really need to read this into memory... */
+    char *buffer = new char [b->length];
+    infile.seekg(b->offset);
+    infile.read(buffer, b->length);
+    outfile.write(buffer, b->length);
+    delete[] buffer;
+    outfile << "\n";
+    return 0;
+}
+
+// Local Variables:
+// tab-width: 8
+// mode: C++
+// c-basic-offset: 4
+// indent-tabs-mode: t
+// c-file-style: "stroustrup"
+// End:
+// ex: shiftwidth=4 tabstop=8


Property changes on: brlcad/trunk/misc/repowork/blob.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/commit.cpp
===================================================================
--- brlcad/trunk/misc/repowork/commit.cpp                               (rev 0)
+++ brlcad/trunk/misc/repowork/commit.cpp       2020-07-23 14:15:54 UTC (rev 
76444)
@@ -0,0 +1,495 @@
+/*                      C O M M I T . C P P
+ * BRL-CAD
+ *
+ * Published in 2020 by the United States Government.
+ * This work is in the public domain.
+ *
+ */
+/** @file commit.cpp
+ *
+ * The majority of the work is in processing
+ * Git commits.
+ *
+ */
+
+#include "TextFlow.hpp"
+#include "repowork.h"
+
+typedef int (*commitcmd_t)(git_commit_data *, std::ifstream &);
+
+commitcmd_t
+commit_find_cmd(std::string &line, std::map<std::string, commitcmd_t> &cmdmap)
+{
+    commitcmd_t cc = NULL;
+    std::map<std::string, commitcmd_t>::iterator c_it;
+    for (c_it = cmdmap.begin(); c_it != cmdmap.end(); c_it++) {
+       if (!ficmp(line, c_it->first)) {
+           cc = c_it->second;
+           break;
+       }
+    }
+    return cc;
+}
+
+int
+commit_parse_author(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 7); // Remove "author " prefix
+    size_t spos = line.find_first_of(">");
+    if (spos == std::string::npos) {
+       std::cerr << "Invalid author entry! " << line << "\n";
+       exit(EXIT_FAILURE);
+    }
+    cd->author = line.substr(0, spos+1);
+    cd->author_timestamp = line.substr(spos+2, std::string::npos);
+    return 0;
+}
+
+int
+commit_parse_committer(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 10); // Remove "committer " prefix
+    size_t spos = line.find_first_of(">");
+    if (spos == std::string::npos) {
+       std::cerr << "Invalid committer entry! " << line << "\n";
+       exit(EXIT_FAILURE);
+    }
+    cd->committer = line.substr(0, spos+1);
+    cd->committer_timestamp = line.substr(spos+2, std::string::npos);
+    //std::cout << "Committer: " << cd->committer << "\n";
+    //std::cout << "Committer timestamp: " << cd->committer_timestamp << "\n";
+    return 0;
+}
+
+int
+commit_parse_commit(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 7);  // Remove "commit " prefix
+    if (!ficmp(line, std::string("refs/notes/"))) {
+       // Notes commit - flag accordingly
+       cd->notes_commit = 1;
+       return 0;
+    }
+    size_t spos = line.find_last_of("/");
+    line.erase(0, spos+1); // Remove "refs/..." prefix
+    cd->branch = line;
+    //std::cout << "Branch: " << cd->branch << "\n";
+    return 0;
+}
+
+int
+commit_parse_data(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 5); // Remove "data " prefix
+    size_t data_len = std::stoi(line);
+    // This is the commit message - read it in
+    char *cbuffer = new char [data_len+1];
+    cbuffer[data_len] = '\0';
+    infile.read(cbuffer, data_len);
+    cd->commit_msg = std::string(cbuffer);
+    delete[] cbuffer;
+    //std::cout << "Commit message:\n" << cd->commit_msg << "\n";
+    return 0;
+}
+
+int
+commit_parse_encoding(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    std::cerr << "TODO - support encoding\n";
+    exit(EXIT_FAILURE);
+    return 0;
+}
+
+int
+commit_parse_from(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 5); // Remove "from " prefix
+    //std::cout << "from line: " << line << "\n";
+    int ret = git_parse_commitish(cd->from, cd->s, line);
+    if (!ret) {
+       return 0;
+    }
+    std::cerr << "TODO - unsupported \"from\" specifier: " << line << "\n";
+    exit(EXIT_FAILURE);
+}
+
+int
+commit_parse_mark(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    //std::cout << "mark line: " << line << "\n";
+    line.erase(0, 5); // Remove "mark " prefix
+    //std::cout << "mark line: " << line << "\n";
+    if (line.c_str()[0] != ':') {
+       std::cerr << "Mark without \":\" character??: " <<  line << "\n";
+       return -1;
+    }
+    line.erase(0, 1); // Remove ":" prefix
+    cd->id.mark = cd->s->next_mark(std::stol(line));
+    //std::cout << "Mark id :" << line << " -> " << cd->id.mark << "\n";
+    return 0;
+}
+
+int
+commit_parse_merge(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 6); // Remove "merge " prefix
+    //std::cout << "merge line: " << line << "\n";
+    git_commitish merge_id;
+    int ret = git_parse_commitish(merge_id, cd->s, line);
+    if (!ret) {
+       cd->merges.push_back(merge_id);
+       return 0;
+    }
+    std::cerr << "TODO - unsupported \"merge\" specifier: " << line << "\n";
+    ret = git_parse_commitish(merge_id, cd->s, line);
+    exit(EXIT_FAILURE);
+}
+
+int
+commit_parse_original_oid(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 13);  // Remove "original-oid " prefix
+    cd->id.sha1 = line;
+    return 0;
+}
+
+int
+commit_parse_filecopy(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 2); // Remove "C " prefix
+    size_t spos = line.find_first_of(" ");
+    if (spos == std::string::npos) {
+       std::cerr << "Invalid copy specifier: " << line << "\n";
+       return -1;
+    }
+    size_t qpos = line.find_first_of("\"");
+    if (spos != std::string::npos) {
+       std::cerr << "quoted path specifiers currently unsupported:" << line << 
"\n";
+       exit(EXIT_FAILURE);
+    }
+    git_op op;
+    op.type = filecopy;
+    op.path = line.substr(0, spos);
+    op.dest_path = line.substr(spos+1, std::string::npos);
+    //std::cout << "filecopy: " << op.path << " -> " << op.dest_path << "\n";
+    cd->fileops.push_back(op);
+    return 0;
+}
+
+int
+commit_parse_filedelete(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 2); // Remove "D " prefix
+    git_op op;
+    op.type = filedelete;
+    op.path = line;
+    cd->fileops.push_back(op);
+    //std::cout << "filedelete: " << line << "\n";
+    return 0;
+}
+
+int
+commit_parse_filemodify(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 2); // Remove "M " prefix
+    std::regex fmod("([0-9]+) ([:A-Za-z0-9]+) (.*)");
+    std::smatch fmodvar;
+    if (!std::regex_search(line, fmodvar, fmod)) {
+       std::cerr << "Invalid modification specifier: " << line << "\n";
+       return -1;
+    }
+    git_op op;
+    op.type = filemodify;
+    op.mode = std::string(fmodvar[1]);
+    std::string dataref = std::string(fmodvar[2]);
+    if (dataref == std::string("inline")) {
+       std::cerr << "inline data unsupported\n";
+       exit(EXIT_FAILURE);
+    }
+    int ret = git_parse_commitish(op.dataref, cd->s, dataref);
+    if (ret || (op.dataref.mark == -1 && !op.dataref.sha1.length())) {
+       std::cerr << "Invalid data ref!: " << dataref << "\n";
+    }
+    op.path = std::string(fmodvar[3]);
+
+    //std::cout << "filemodify: " << op.mode << "," << op.dataref.index << "," 
<< op.path << "\n";
+
+    cd->fileops.push_back(op);
+
+    return 0;
+}
+
+int
+commit_parse_notemodify(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    std::cerr << "notemodify currently unsupported:" << line << "\n";
+    exit(EXIT_FAILURE);
+}
+
+int
+commit_parse_filerename(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 2); // Remove "R " prefix
+    size_t spos = line.find_first_of(" ");
+    if (spos == std::string::npos) {
+       std::cerr << "Invalid copy specifier: " << line << "\n";
+       return -1;
+    }
+    size_t qpos = line.find_first_of("\"");
+    if (spos != std::string::npos) {
+       std::cerr << "quoted path specifiers currently unsupported:" << line << 
"\n";
+       exit(EXIT_FAILURE);
+    }
+    git_op op;
+    op.type = filerename;
+    op.path = line.substr(0, spos);
+    op.dest_path = line.substr(spos+1, std::string::npos);
+    //std::cout << "filerename: " << op.path << " -> " << op.dest_path << 
"\n"; 
+    cd->fileops.push_back(op);
+    return 0;
+}
+
+int
+commit_parse_deleteall(git_commit_data *cd, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    if (line != std::string("deleteall")) {
+       std::cerr << "warning - invalid deleteall specifier:" << line << "\n";
+    }
+    git_op op;
+    op.type = filedeleteall;
+    cd->fileops.push_back(op);
+    return 0;
+}
+
+int
+parse_commit(git_fi_data *fi_data, std::ifstream &infile)
+{
+    //std::cout << "Found command: commit\n";
+
+    git_commit_data gcd;
+    gcd.s = fi_data;
+
+    // Tell the commit where it will be in the vector - this
+    // uniquely identifies this specific commit, regardless of
+    // its sha1.
+    gcd.id.index = fi_data->commits.size();
+
+    std::map<std::string, commitcmd_t> cmdmap;
+    // Commit info modification commands
+    cmdmap[std::string("author")] = commit_parse_author;
+    cmdmap[std::string("commit ")] = commit_parse_commit; // Note - need space 
after commit to avoid matching committer!
+    cmdmap[std::string("committer")] = commit_parse_committer;
+    cmdmap[std::string("data")] = commit_parse_data;
+    cmdmap[std::string("encoding")] = commit_parse_encoding;
+    cmdmap[std::string("from")] = commit_parse_from;
+    cmdmap[std::string("mark")] = commit_parse_mark;
+    cmdmap[std::string("merge")] = commit_parse_merge;
+    cmdmap[std::string("original-oid")] = commit_parse_original_oid;
+
+    // tree modification commands
+    cmdmap[std::string("C ")] = commit_parse_filecopy;
+    cmdmap[std::string("D ")] = commit_parse_filedelete;
+    cmdmap[std::string("M ")] = commit_parse_filemodify;
+    cmdmap[std::string("N ")] = commit_parse_notemodify;
+    cmdmap[std::string("R ")] = commit_parse_filerename;
+    cmdmap[std::string("deleteall")] = commit_parse_deleteall;
+
+    std::string line;
+    size_t offset = infile.tellg();
+    int commit_done = 0;
+    while (!commit_done && std::getline(infile, line)) {
+
+       commitcmd_t cc = commit_find_cmd(line, cmdmap);
+
+       // If we found a command, process it.  Otherwise, we are done
+       // with the commit and need to clean up.
+       if (cc) {
+           //std::cout << "commit line: " << line << "\n";
+           infile.seekg(offset);
+           (*cc)(&gcd, infile);
+           offset = infile.tellg();
+       } else {
+           // Whatever was on that line, it's not a commit input.
+           // Reset input to allow the parent routine to deal with
+           // it, and return.
+           infile.seekg(offset);
+           commit_done = 1;
+       }
+    }
+
+    gcd.id.mark = fi_data->next_mark(gcd.id.mark);
+    fi_data->mark_to_index[gcd.id.mark] = gcd.id.index;
+
+    // If we have a sha1 and this is not a notes commit, we need to map it to
+    // this commit's mark
+    if (!gcd.notes_commit && gcd.id.sha1.length()) {
+       fi_data->sha1_to_mark[gcd.id.sha1] = gcd.id.mark;
+    }
+
+    //std::cout << "commit new mark: " << gcd.id.mark << "\n";
+
+    // Add the commit to the data
+    fi_data->commits.push_back(gcd);
+
+    return 0;
+}
+
+void
+write_op(std::ofstream &outfile, git_op *o)
+{
+    switch (o->type) {
+       case filemodify:
+           outfile << "M " << o->mode << " :" << o->dataref.mark << " " << 
o->path << "\n";
+           break;
+       case filedelete:
+           outfile << "D " << o->path << "\n";
+           break;
+       case filecopy:
+           outfile << "C " << o->path << " " << o->dest_path << "\n";
+           break;
+       case filerename:
+           outfile << "R " << o->path << " " << o->dest_path << "\n";
+           break;
+       case filedeleteall:
+           outfile << "deleteall\n";
+           break;
+       case notemodify:
+           std::cerr << "TODO - write notemodify\n";
+           break;
+    }
+}
+
+// trim from end (in place) - https://stackoverflow.com/a/217605
+static inline void rtrim(std::string &s) {
+    s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
+        return !(std::isspace(ch) || ch == '\n' || ch == '\r');
+    }).base(), s.end());
+}
+
+int
+write_commit(std::ofstream &outfile, git_commit_data *c, std::ifstream &infile)
+{
+    if (!infile.good()) {
+        return -1;
+    }
+
+    // If this is a reset commit, it's handled quite differently
+    if (c->reset_commit) {
+       outfile << "reset " << c->branch << "\n";
+       if (c->from.mark != -1) {
+           outfile << "from :" << c->from.mark << "\n";
+       }
+       outfile << "\n";
+       return 0;
+    }
+
+    // Header
+    if (c->notes_commit) {
+       // Don't output notes commits - we're handling things differently.
+       return 0;
+    } else {
+       outfile << "commit refs/heads/" << c->branch << "\n";
+    }
+    outfile << "mark :" << c->id.mark << "\n";
+#if 0
+    if (c->id.sha1.length()) {
+       outfile << "original-oid " << c->id.sha1 << "\n";
+    }
+#endif
+    if (c->author.length()) {
+       outfile << "author " << c->author << " " << c->author_timestamp << "\n";
+    } else {
+       outfile << "author " << c->committer << " " << c->committer_timestamp 
<< "\n";
+    }
+    outfile << "committer " << c->committer << " " << c->committer_timestamp 
<< "\n";
+
+    if (c->s->trim_whitespace) {
+       rtrim(c->commit_msg);
+    }
+    if (c->s->wrap_commit_lines) {
+       // Wrap the commit messages - gitk doesn't like long one liners by
+       // default.  Don't know why the line wrap ISN'T on by default, but we
+       // might as well deal with it while we're here...
+       //
+       // TODO - the width could easily be a parameter, and probably should
+       // be...
+       size_t spos = c->commit_msg.find_first_of('\n');
+       if (spos == std::string::npos) {
+           std::string wmsg = 
TextFlow::Column(c->commit_msg).width(72).toString();
+           c->commit_msg = wmsg;
+       }
+    }
+    std::string nmsg;
+    if (c->notes_string.length()) {
+       std::string nstr = c->notes_string;
+       if (c->s->trim_whitespace) rtrim(nstr);
+       if (c->s->wrap_commit_lines) {
+           size_t spos = nstr.find_first_of('\n');
+           if (spos == std::string::npos) {
+               std::string wmsg = TextFlow::Column(nstr).width(72).toString();
+               nstr = wmsg;
+           }
+       }
+       nmsg = c->commit_msg + std::string("\n\n") + nstr + std::string("\n");
+    } else {
+       if (c->s->trim_whitespace) {
+           nmsg = c->commit_msg + std::string("\n");
+       } else {
+           nmsg = c->commit_msg;
+       }
+    }
+    outfile << "data " << nmsg.length() << "\n";
+    outfile << nmsg;
+
+    if (c->from.mark != -1) {
+       outfile << "from :" << c->from.mark << "\n";
+    }
+    for (size_t i = 0; i < c->merges.size(); i++) {
+       outfile << "merge :" << c->merges[i].mark << "\n";
+    }
+    for (size_t i = 0; i < c->fileops.size(); i++) {
+       write_op(outfile, &c->fileops[i]);
+    }
+    outfile << "\n";
+    return 0;
+}
+
+
+// Local Variables:
+// tab-width: 8
+// mode: C++
+// c-basic-offset: 4
+// indent-tabs-mode: t
+// c-file-style: "stroustrup"
+// End:
+// ex: shiftwidth=4 tabstop=8


Property changes on: brlcad/trunk/misc/repowork/commit.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/cxxopts.hpp
===================================================================
--- brlcad/trunk/misc/repowork/cxxopts.hpp                              (rev 0)
+++ brlcad/trunk/misc/repowork/cxxopts.hpp      2020-07-23 14:15:54 UTC (rev 
76444)
@@ -0,0 +1,2197 @@
+/*
+
+Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+#ifndef CXXOPTS_HPP_INCLUDED
+#define CXXOPTS_HPP_INCLUDED
+
+#include <cctype>
+#include <cstring>
+#include <exception>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <memory>
+#include <regex>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#ifdef __cpp_lib_optional
+#include <optional>
+#define CXXOPTS_HAS_OPTIONAL
+#endif
+
+#ifndef CXXOPTS_VECTOR_DELIMITER
+#define CXXOPTS_VECTOR_DELIMITER ','
+#endif
+
+#define CXXOPTS__VERSION_MAJOR 2
+#define CXXOPTS__VERSION_MINOR 2
+#define CXXOPTS__VERSION_PATCH 0
+
+namespace cxxopts
+{
+  static constexpr struct {
+    uint8_t major, minor, patch;
+  } version = {
+    CXXOPTS__VERSION_MAJOR,
+    CXXOPTS__VERSION_MINOR,
+    CXXOPTS__VERSION_PATCH
+  };
+} // namespace cxxopts
+
+//when we ask cxxopts to use Unicode, help strings are processed using ICU,
+//which results in the correct lengths being computed for strings when they
+//are formatted for the help output
+//it is necessary to make sure that <unicode/unistr.h> can be found by the
+//compiler, and that icu-uc is linked in to the binary.
+
+#ifdef CXXOPTS_USE_UNICODE
+#include <unicode/unistr.h>
+
+namespace cxxopts
+{
+  typedef icu::UnicodeString String;
+
+  inline
+  String
+  toLocalString(std::string s)
+  {
+    return icu::UnicodeString::fromUTF8(std::move(s));
+  }
+
+  class UnicodeStringIterator : public
+    std::iterator<std::forward_iterator_tag, int32_t>
+  {
+    public:
+
+    UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
+    : s(string)
+    , i(pos)
+    {
+    }
+
+    value_type
+    operator*() const
+    {
+      return s->char32At(i);
+    }
+
+    bool
+    operator==(const UnicodeStringIterator& rhs) const
+    {
+      return s == rhs.s && i == rhs.i;
+    }
+
+    bool
+    operator!=(const UnicodeStringIterator& rhs) const
+    {
+      return !(*this == rhs);
+    }
+
+    UnicodeStringIterator&
+    operator++()
+    {
+      ++i;
+      return *this;
+    }
+
+    UnicodeStringIterator
+    operator+(int32_t v)
+    {
+      return UnicodeStringIterator(s, i + v);
+    }
+
+    private:
+    const icu::UnicodeString* s;
+    int32_t i;
+  };
+
+  inline
+  String&
+  stringAppend(String&s, String a)
+  {
+    return s.append(std::move(a));
+  }
+
+  inline
+  String&
+  stringAppend(String& s, int n, UChar32 c)
+  {
+    for (int i = 0; i != n; ++i)
+    {
+      s.append(c);
+    }
+
+    return s;
+  }
+
+  template <typename Iterator>
+  String&
+  stringAppend(String& s, Iterator begin, Iterator end)
+  {
+    while (begin != end)
+    {
+      s.append(*begin);
+      ++begin;
+    }
+
+    return s;
+  }
+
+  inline
+  size_t
+  stringLength(const String& s)
+  {
+    return s.length();
+  }
+
+  inline
+  std::string
+  toUTF8String(const String& s)
+  {
+    std::string result;
+    s.toUTF8String(result);
+
+    return result;
+  }
+
+  inline
+  bool
+  empty(const String& s)
+  {
+    return s.isEmpty();
+  }
+}
+
+namespace std
+{
+  inline
+  cxxopts::UnicodeStringIterator
+  begin(const icu::UnicodeString& s)
+  {
+    return cxxopts::UnicodeStringIterator(&s, 0);
+  }
+
+  inline
+  cxxopts::UnicodeStringIterator
+  end(const icu::UnicodeString& s)
+  {
+    return cxxopts::UnicodeStringIterator(&s, s.length());
+  }
+}
+
+//ifdef CXXOPTS_USE_UNICODE
+#else
+
+namespace cxxopts
+{
+  typedef std::string String;
+
+  template <typename T>
+  T
+  toLocalString(T&& t)
+  {
+    return std::forward<T>(t);
+  }
+
+  inline
+  size_t
+  stringLength(const String& s)
+  {
+    return s.length();
+  }
+
+  inline
+  String&
+  stringAppend(String&s, const String& a)
+  {
+    return s.append(a);
+  }
+
+  inline
+  String&
+  stringAppend(String& s, size_t n, char c)
+  {
+    return s.append(n, c);
+  }
+
+  template <typename Iterator>
+  String&
+  stringAppend(String& s, Iterator begin, Iterator end)
+  {
+    return s.append(begin, end);
+  }
+
+  template <typename T>
+  std::string
+  toUTF8String(T&& t)
+  {
+    return std::forward<T>(t);
+  }
+
+  inline
+  bool
+  empty(const std::string& s)
+  {
+    return s.empty();
+  }
+} // namespace cxxopts
+
+//ifdef CXXOPTS_USE_UNICODE
+#endif
+
+namespace cxxopts
+{
+  namespace
+  {
+#ifdef _WIN32
+    const std::string LQUOTE("\'");
+    const std::string RQUOTE("\'");
+#else
+    const std::string LQUOTE("‘");
+    const std::string RQUOTE("’");
+#endif
+  } // namespace
+
+  class Value : public std::enable_shared_from_this<Value>
+  {
+    public:
+
+    virtual ~Value() = default;
+
+    virtual
+    std::shared_ptr<Value>
+    clone() const = 0;
+
+    virtual void
+    parse(const std::string& text) const = 0;
+
+    virtual void
+    parse() const = 0;
+
+    virtual bool
+    has_default() const = 0;
+
+    virtual bool
+    is_container() const = 0;
+
+    virtual bool
+    has_implicit() const = 0;
+
+    virtual std::string
+    get_default_value() const = 0;
+
+    virtual std::string
+    get_implicit_value() const = 0;
+
+    virtual std::shared_ptr<Value>
+    default_value(const std::string& value) = 0;
+
+    virtual std::shared_ptr<Value>
+    implicit_value(const std::string& value) = 0;
+
+    virtual std::shared_ptr<Value>
+    no_implicit_value() = 0;
+
+    virtual bool
+    is_boolean() const = 0;
+  };
+
+  class OptionException : public std::exception
+  {
+    public:
+    explicit OptionException(std::string  message)
+    : m_message(std::move(message))
+    {
+    }
+
+    const char*
+    what() const noexcept override
+    {
+      return m_message.c_str();
+    }
+
+    private:
+    std::string m_message;
+  };
+
+  class OptionSpecException : public OptionException
+  {
+    public:
+
+    explicit OptionSpecException(const std::string& message)
+    : OptionException(message)
+    {
+    }
+  };
+
+  class OptionParseException : public OptionException
+  {
+    public:
+    explicit OptionParseException(const std::string& message)
+    : OptionException(message)
+    {
+    }
+  };
+
+  class option_exists_error : public OptionSpecException
+  {
+    public:
+    explicit option_exists_error(const std::string& option)
+    : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already 
exists")
+    {
+    }
+  };
+
+  class invalid_option_format_error : public OptionSpecException
+  {
+    public:
+    explicit invalid_option_format_error(const std::string& format)
+    : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE)
+    {
+    }
+  };
+
+  class option_syntax_exception : public OptionParseException {
+    public:
+    explicit option_syntax_exception(const std::string& text)
+    : OptionParseException("Argument " + LQUOTE + text + RQUOTE +
+        " starts with a - but has incorrect syntax")
+    {
+    }
+  };
+
+  class option_not_exists_exception : public OptionParseException
+  {
+    public:
+    explicit option_not_exists_exception(const std::string& option)
+    : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not 
exist")
+    {
+    }
+  };
+
+  class missing_argument_exception : public OptionParseException
+  {
+    public:
+    explicit missing_argument_exception(const std::string& option)
+    : OptionParseException(
+        "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
+      )
+    {
+    }
+  };
+
+  class option_requires_argument_exception : public OptionParseException
+  {
+    public:
+    explicit option_requires_argument_exception(const std::string& option)
+    : OptionParseException(
+        "Option " + LQUOTE + option + RQUOTE + " requires an argument"
+      )
+    {
+    }
+  };
+
+  class option_not_has_argument_exception : public OptionParseException
+  {
+    public:
+    option_not_has_argument_exception
+    (
+      const std::string& option,
+      const std::string& arg
+    )
+    : OptionParseException(
+        "Option " + LQUOTE + option + RQUOTE +
+        " does not take an argument, but argument " +
+        LQUOTE + arg + RQUOTE + " given"
+      )
+    {
+    }
+  };
+
+  class option_not_present_exception : public OptionParseException
+  {
+    public:
+    explicit option_not_present_exception(const std::string& option)
+    : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not 
present")
+    {
+    }
+  };
+
+  class argument_incorrect_type : public OptionParseException
+  {
+    public:
+    explicit argument_incorrect_type
+    (
+      const std::string& arg
+    )
+    : OptionParseException(
+        "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
+      )
+    {
+    }
+  };
+
+  class option_required_exception : public OptionParseException
+  {
+    public:
+    explicit option_required_exception(const std::string& option)
+    : OptionParseException(
+        "Option " + LQUOTE + option + RQUOTE + " is required but not present"
+      )
+    {
+    }
+  };
+
+  template <typename T>
+  void throw_or_mimic(const std::string& text)
+  {
+    static_assert(std::is_base_of<std::exception, T>::value,
+                  "throw_or_mimic only works on std::exception and "
+                  "deriving classes");
+
+#ifndef CXXOPTS_NO_EXCEPTIONS
+    // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
+    throw T{text};
+#else
+    // Otherwise manually instantiate the exception, print what() to stderr,
+    // and exit
+    T exception{text};
+    std::cerr << exception.what() << std::endl;
+    std::exit(EXIT_FAILURE);
+#endif
+  }
+
+  namespace values
+  {
+    namespace
+    {
+      std::basic_regex<char> integer_pattern
+        ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
+      std::basic_regex<char> truthy_pattern
+        ("(t|T)(rue)?|1");
+      std::basic_regex<char> falsy_pattern
+        ("(f|F)(alse)?|0");
+    } // namespace
+
+    namespace detail
+    {
+      template <typename T, bool B>
+      struct SignedCheck;
+
+      template <typename T>
+      struct SignedCheck<T, true>
+      {
+        template <typename U>
+        void
+        operator()(bool negative, U u, const std::string& text)
+        {
+          if (negative)
+          {
+            if (u > static_cast<U>((std::numeric_limits<T>::min)()))
+            {
+              throw_or_mimic<argument_incorrect_type>(text);
+            }
+          }
+          else
+          {
+            if (u > static_cast<U>((std::numeric_limits<T>::max)()))
+            {
+              throw_or_mimic<argument_incorrect_type>(text);
+            }
+          }
+        }
+      };
+
+      template <typename T>
+      struct SignedCheck<T, false>
+      {
+        template <typename U>
+        void
+        operator()(bool, U, const std::string&) {}
+      };
+
+      template <typename T, typename U>
+      void
+      check_signed_range(bool negative, U value, const std::string& text)
+      {
+        SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, 
text);
+      }
+    } // namespace detail
+
+    template <typename R, typename T>
+    R
+    checked_negate(T&& t, const std::string&, std::true_type)
+    {
+      // if we got to here, then `t` is a positive number that fits into
+      // `R`. So to avoid MSVC C4146, we first cast it to `R`.
+      // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
+      return static_cast<R>(-static_cast<R>(t-1)-1);
+    }
+
+    template <typename R, typename T>
+    T
+    checked_negate(T&& t, const std::string& text, std::false_type)
+    {
+      throw_or_mimic<argument_incorrect_type>(text);
+      return t;
+    }
+
+    template <typename T>
+    void
+    integer_parser(const std::string& text, T& value)
+    {
+      std::smatch match;
+      std::regex_match(text, match, integer_pattern);
+
+      if (match.length() == 0)
+      {
+        throw_or_mimic<argument_incorrect_type>(text);
+      }
+
+      if (match.length(4) > 0)
+      {
+        value = 0;
+        return;
+      }
+
+      using US = typename std::make_unsigned<T>::type;
+
+      constexpr bool is_signed = std::numeric_limits<T>::is_signed;
+      const bool negative = match.length(1) > 0;
+      const uint8_t base = match.length(2) > 0 ? 16 : 10;
+
+      auto value_match = match[3];
+
+      US result = 0;
+
+      for (auto iter = value_match.first; iter != value_match.second; ++iter)
+      {
+        US digit = 0;
+
+        if (*iter >= '0' && *iter <= '9')
+        {
+          digit = static_cast<US>(*iter - '0');
+        }
+        else if (base == 16 && *iter >= 'a' && *iter <= 'f')
+        {
+          digit = static_cast<US>(*iter - 'a' + 10);
+        }
+        else if (base == 16 && *iter >= 'A' && *iter <= 'F')
+        {
+          digit = static_cast<US>(*iter - 'A' + 10);
+        }
+        else
+        {
+          throw_or_mimic<argument_incorrect_type>(text);
+        }
+
+        const US next = static_cast<US>(result * base + digit);
+        if (result > next)
+        {
+          throw_or_mimic<argument_incorrect_type>(text);
+        }
+
+        result = next;
+      }
+
+      detail::check_signed_range<T>(negative, result, text);
+
+      if (negative)
+      {
+        value = checked_negate<T>(result,
+          text,
+          std::integral_constant<bool, is_signed>());
+      }
+      else
+      {
+        value = static_cast<T>(result);
+      }
+    }
+
+    template <typename T>
+    void stringstream_parser(const std::string& text, T& value)
+    {
+      std::stringstream in(text);
+      in >> value;
+      if (!in) {
+        throw_or_mimic<argument_incorrect_type>(text);
+      }
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, uint8_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, int8_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, uint16_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, int16_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, uint32_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, int32_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, uint64_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, int64_t& value)
+    {
+      integer_parser(text, value);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, bool& value)
+    {
+      std::smatch result;
+      std::regex_match(text, result, truthy_pattern);
+
+      if (!result.empty())
+      {
+        value = true;
+        return;
+      }
+
+      std::regex_match(text, result, falsy_pattern);
+      if (!result.empty())
+      {
+        value = false;
+        return;
+      }
+
+      throw_or_mimic<argument_incorrect_type>(text);
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, std::string& value)
+    {
+      value = text;
+    }
+
+    // The fallback parser. It uses the stringstream parser to parse all types
+    // that have not been overloaded explicitly.  It has to be placed in the
+    // source code before all other more specialized templates.
+    template <typename T>
+    void
+    parse_value(const std::string& text, T& value) {
+      stringstream_parser(text, value);
+    }
+
+    template <typename T>
+    void
+    parse_value(const std::string& text, std::vector<T>& value)
+    {
+      std::stringstream in(text);
+      std::string token;
+      while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
+        T v;
+        parse_value(token, v);
+        value.emplace_back(std::move(v));
+      }
+    }
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+    template <typename T>
+    void
+    parse_value(const std::string& text, std::optional<T>& value)
+    {
+      T result;
+      parse_value(text, result);
+      value = std::move(result);
+    }
+#endif
+
+    inline
+    void parse_value(const std::string& text, char& c)
+    {
+      if (text.length() != 1)
+      {
+        throw_or_mimic<argument_incorrect_type>(text);
+      }
+
+      c = text[0];
+    }
+
+    template <typename T>
+    struct type_is_container
+    {
+      static constexpr bool value = false;
+    };
+
+    template <typename T>
+    struct type_is_container<std::vector<T>>
+    {
+      static constexpr bool value = true;
+    };
+
+    template <typename T>
+    class abstract_value : public Value
+    {
+      using Self = abstract_value<T>;
+
+      public:
+      abstract_value()
+      : m_result(std::make_shared<T>())
+      , m_store(m_result.get())
+      {
+      }
+
+      explicit abstract_value(T* t)
+      : m_store(t)
+      {
+      }
+
+      ~abstract_value() override = default;
+
+      abstract_value(const abstract_value& rhs)
+      {
+        if (rhs.m_result)
+        {
+          m_result = std::make_shared<T>();
+          m_store = m_result.get();
+        }
+        else
+        {
+          m_store = rhs.m_store;
+        }
+
+        m_default = rhs.m_default;
+        m_implicit = rhs.m_implicit;
+        m_default_value = rhs.m_default_value;
+        m_implicit_value = rhs.m_implicit_value;
+      }
+
+      void
+      parse(const std::string& text) const override
+      {
+        parse_value(text, *m_store);
+      }
+
+      bool
+      is_container() const override
+      {
+        return type_is_container<T>::value;
+      }
+
+      void
+      parse() const override
+      {
+        parse_value(m_default_value, *m_store);
+      }
+
+      bool
+      has_default() const override
+      {
+        return m_default;
+      }
+
+      bool
+      has_implicit() const override
+      {
+        return m_implicit;
+      }
+
+      std::shared_ptr<Value>
+      default_value(const std::string& value) override
+      {
+        m_default = true;
+        m_default_value = value;
+        return shared_from_this();
+      }
+
+      std::shared_ptr<Value>
+      implicit_value(const std::string& value) override
+      {
+        m_implicit = true;
+        m_implicit_value = value;
+        return shared_from_this();
+      }
+
+      std::shared_ptr<Value>
+      no_implicit_value() override
+      {
+        m_implicit = false;
+        return shared_from_this();
+      }
+
+      std::string
+      get_default_value() const override
+      {
+        return m_default_value;
+      }
+
+      std::string
+      get_implicit_value() const override
+      {
+        return m_implicit_value;
+      }
+
+      bool
+      is_boolean() const override
+      {
+        return std::is_same<T, bool>::value;
+      }
+
+      const T&
+      get() const
+      {
+        if (m_store == nullptr)
+        {
+          return *m_result;
+        }
+        return *m_store;
+      }
+
+      protected:
+      std::shared_ptr<T> m_result;
+      T* m_store;
+
+      bool m_default = false;
+      bool m_implicit = false;
+
+      std::string m_default_value;
+      std::string m_implicit_value;
+    };
+
+    template <typename T>
+    class standard_value : public abstract_value<T>
+    {
+      public:
+      using abstract_value<T>::abstract_value;
+
+      std::shared_ptr<Value>
+      clone() const
+      {
+        return std::make_shared<standard_value<T>>(*this);
+      }
+    };
+
+    template <>
+    class standard_value<bool> : public abstract_value<bool>
+    {
+      public:
+      ~standard_value() override = default;
+
+      standard_value()
+      {
+        set_default_and_implicit();
+      }
+
+      explicit standard_value(bool* b)
+      : abstract_value(b)
+      {
+        set_default_and_implicit();
+      }
+
+      std::shared_ptr<Value>
+      clone() const override
+      {
+        return std::make_shared<standard_value<bool>>(*this);
+      }
+
+      private:
+
+      void
+      set_default_and_implicit()
+      {
+        m_default = true;
+        m_default_value = "false";
+        m_implicit = true;
+        m_implicit_value = "true";
+      }
+    };
+  } // namespace values
+
+  template <typename T>
+  std::shared_ptr<Value>
+  value()
+  {
+    return std::make_shared<values::standard_value<T>>();
+  }
+
+  template <typename T>
+  std::shared_ptr<Value>
+  value(T& t)
+  {
+    return std::make_shared<values::standard_value<T>>(&t);
+  }
+
+  class OptionAdder;
+
+  class OptionDetails
+  {
+    public:
+    OptionDetails
+    (
+      std::string short_,
+      std::string long_,
+      String desc,
+      std::shared_ptr<const Value> val
+    )
+    : m_short(std::move(short_))
+    , m_long(std::move(long_))
+    , m_desc(std::move(desc))
+    , m_value(std::move(val))
+    , m_count(0)
+    {
+    }
+
+    OptionDetails(const OptionDetails& rhs)
+    : m_desc(rhs.m_desc)
+    , m_count(rhs.m_count)
+    {
+      m_value = rhs.m_value->clone();
+    }
+
+    OptionDetails(OptionDetails&& rhs) = default;
+
+    const String&
+    description() const
+    {
+      return m_desc;
+    }
+
+    const Value& value() const {
+        return *m_value;
+    }
+
+    std::shared_ptr<Value>
+    make_storage() const
+    {
+      return m_value->clone();
+    }
+
+    const std::string&
+    short_name() const
+    {
+      return m_short;
+    }
+
+    const std::string&
+    long_name() const
+    {
+      return m_long;
+    }
+
+    private:
+    std::string m_short;
+    std::string m_long;
+    String m_desc;
+    std::shared_ptr<const Value> m_value;
+    int m_count;
+  };
+
+  struct HelpOptionDetails
+  {
+    std::string s;
+    std::string l;
+    String desc;
+    bool has_default;
+    std::string default_value;
+    bool has_implicit;
+    std::string implicit_value;
+    std::string arg_help;
+    bool is_container;
+    bool is_boolean;
+  };
+
+  struct HelpGroupDetails
+  {
+    std::string name;
+    std::string description;
+    std::vector<HelpOptionDetails> options;
+  };
+
+  class OptionValue
+  {
+    public:
+    void
+    parse
+    (
+      const std::shared_ptr<const OptionDetails>& details,
+      const std::string& text
+    )
+    {
+      ensure_value(details);
+      ++m_count;
+      m_value->parse(text);
+    }
+
+    void
+    parse_default(const std::shared_ptr<const OptionDetails>& details)
+    {
+      ensure_value(details);
+      m_default = true;
+      m_value->parse();
+    }
+
+    size_t
+    count() const noexcept
+    {
+      return m_count;
+    }
+
+    // TODO: maybe default options should count towards the number of arguments
+    bool
+    has_default() const noexcept
+    {
+      return m_default;
+    }
+
+    template <typename T>
+    const T&
+    as() const
+    {
+      if (m_value == nullptr) {
+        throw_or_mimic<std::domain_error>("No value");
+      }
+
+#ifdef CXXOPTS_NO_RTTI
+      return static_cast<const values::standard_value<T>&>(*m_value).get();
+#else
+      return dynamic_cast<const values::standard_value<T>&>(*m_value).get();
+#endif
+    }
+
+    private:
+    void
+    ensure_value(const std::shared_ptr<const OptionDetails>& details)
+    {
+      if (m_value == nullptr)
+      {
+        m_value = details->make_storage();
+      }
+    }
+
+    std::shared_ptr<Value> m_value;
+    size_t m_count = 0;
+    bool m_default = false;
+  };
+
+  class KeyValue
+  {
+    public:
+    KeyValue(std::string key_, std::string value_)
+    : m_key(std::move(key_))
+    , m_value(std::move(value_))
+    {
+    }
+
+    const
+    std::string&
+    key() const
+    {
+      return m_key;
+    }
+
+    const
+    std::string&
+    value() const
+    {
+      return m_value;
+    }
+
+    template <typename T>
+    T
+    as() const
+    {
+      T result;
+      values::parse_value(m_value, result);
+      return result;
+    }
+
+    private:
+    std::string m_key;
+    std::string m_value;
+  };
+
+  class ParseResult
+  {
+    public:
+
+    ParseResult(
+      std::shared_ptr<
+        std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
+      >,
+      std::vector<std::string>,
+      bool allow_unrecognised,
+      int&, char**&);
+
+    size_t
+    count(const std::string& o) const
+    {
+      auto iter = m_options->find(o);
+      if (iter == m_options->end())
+      {
+        return 0;
+      }
+
+      auto riter = m_results.find(iter->second);
+
+      return riter->second.count();
+    }
+
+    const OptionValue&
+    operator[](const std::string& option) const
+    {
+      auto iter = m_options->find(option);
+
+      if (iter == m_options->end())
+      {
+        throw_or_mimic<option_not_present_exception>(option);
+      }
+
+      auto riter = m_results.find(iter->second);
+
+      return riter->second;
+    }
+
+    const std::vector<KeyValue>&
+    arguments() const
+    {
+      return m_sequential;
+    }
+
+    private:
+
+    void
+    parse(int& argc, char**& argv);
+
+    void
+    add_to_option(const std::string& option, const std::string& arg);
+
+    bool
+    consume_positional(const std::string& a);
+
+    void
+    parse_option
+    (
+      const std::shared_ptr<OptionDetails>& value,
+      const std::string& name,
+      const std::string& arg = ""
+    );
+
+    void
+    parse_default(const std::shared_ptr<OptionDetails>& details);
+
+    void
+    checked_parse_arg
+    (
+      int argc,
+      char* argv[],
+      int& current,
+      const std::shared_ptr<OptionDetails>& value,
+      const std::string& name
+    );
+
+    const std::shared_ptr<
+      std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
+    > m_options;
+    std::vector<std::string> m_positional;
+    std::vector<std::string>::iterator m_next_positional;
+    std::unordered_set<std::string> m_positional_set;
+    std::unordered_map<std::shared_ptr<OptionDetails>, OptionValue> m_results;
+
+    bool m_allow_unrecognised;
+
+    std::vector<KeyValue> m_sequential;
+  };
+
+  struct Option
+  {
+    Option
+    (
+      std::string opts,
+      std::string desc,
+      std::shared_ptr<const Value>  value = ::cxxopts::value<bool>(),
+      std::string arg_help = ""
+    )
+    : opts_(std::move(opts))
+    , desc_(std::move(desc))
+    , value_(std::move(value))
+    , arg_help_(std::move(arg_help))
+    {
+    }
+
+    std::string opts_;
+    std::string desc_;
+    std::shared_ptr<const Value> value_;
+    std::string arg_help_;
+  };
+
+  class Options
+  {
+    using OptionMap = std::unordered_map<std::string, 
std::shared_ptr<OptionDetails>>;
+    public:
+
+    explicit Options(std::string program, std::string help_string = "")
+    : m_program(std::move(program))
+    , m_help_string(toLocalString(std::move(help_string)))
+    , m_custom_help("[OPTION...]")
+    , m_positional_help("positional parameters")
+    , m_show_positional(false)
+    , m_allow_unrecognised(false)
+    , m_options(std::make_shared<OptionMap>())
+    , m_next_positional(m_positional.end())
+    {
+    }
+
+    Options&
+    positional_help(std::string help_text)
+    {
+      m_positional_help = std::move(help_text);
+      return *this;
+    }
+
+    Options&
+    custom_help(std::string help_text)
+    {
+      m_custom_help = std::move(help_text);
+      return *this;
+    }
+
+    Options&
+    show_positional_help()
+    {
+      m_show_positional = true;
+      return *this;
+    }
+
+    Options&
+    allow_unrecognised_options()
+    {
+      m_allow_unrecognised = true;
+      return *this;
+    }
+
+    ParseResult
+    parse(int& argc, char**& argv);
+
+    OptionAdder
+    add_options(std::string group = "");
+
+    void
+    add_options
+    (
+      const std::string& group,
+      std::initializer_list<Option> options
+    );
+
+    void
+    add_option
+    (
+      const std::string& group,
+      const Option& option
+    );
+
+    void
+    add_option
+    (
+      const std::string& group,
+      const std::string& s,
+      const std::string& l,
+      std::string desc,
+      const std::shared_ptr<const Value>& value,
+      std::string arg_help
+    );
+
+    //parse positional arguments into the given option
+    void
+    parse_positional(std::string option);
+
+    void
+    parse_positional(std::vector<std::string> options);
+
+    void
+    parse_positional(std::initializer_list<std::string> options);
+
+    template <typename Iterator>
+    void
+    parse_positional(Iterator begin, Iterator end) {
+      parse_positional(std::vector<std::string>{begin, end});
+    }
+
+    std::string
+    help(const std::vector<std::string>& groups = {}) const;
+
+    std::vector<std::string>
+    groups() const;
+
+    const HelpGroupDetails&
+    group_help(const std::string& group) const;
+
+    private:
+
+    void
+    add_one_option
+    (
+      const std::string& option,
+      const std::shared_ptr<OptionDetails>& details
+    );
+
+    String
+    help_one_group(const std::string& group) const;
+
+    void
+    generate_group_help
+    (
+      String& result,
+      const std::vector<std::string>& groups
+    ) const;
+
+    void
+    generate_all_groups_help(String& result) const;
+
+    std::string m_program;
+    String m_help_string;
+    std::string m_custom_help;
+    std::string m_positional_help;
+    bool m_show_positional;
+    bool m_allow_unrecognised;
+
+    std::shared_ptr<OptionMap> m_options;
+    std::vector<std::string> m_positional;
+    std::vector<std::string>::iterator m_next_positional;
+    std::unordered_set<std::string> m_positional_set;
+
+    //mapping from groups to help options
+    std::map<std::string, HelpGroupDetails> m_help;
+  };
+
+  class OptionAdder
+  {
+    public:
+
+    OptionAdder(Options& options, std::string group)
+    : m_options(options), m_group(std::move(group))
+    {
+    }
+
+    OptionAdder&
+    operator()
+    (
+      const std::string& opts,
+      const std::string& desc,
+      const std::shared_ptr<const Value>& value
+        = ::cxxopts::value<bool>(),
+      std::string arg_help = ""
+    );
+
+    private:
+    Options& m_options;
+    std::string m_group;
+  };
+
+  namespace
+  {
+    constexpr int OPTION_LONGEST = 30;
+    constexpr int OPTION_DESC_GAP = 2;
+
+    std::basic_regex<char> option_matcher
+      ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
+
+    std::basic_regex<char> option_specifier
+      ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?");
+
+    String
+    format_option
+    (
+      const HelpOptionDetails& o
+    )
+    {
+      const auto& s = o.s;
+      const auto& l = o.l;
+
+      String result = "  ";
+
+      if (!s.empty())
+      {
+        result += "-" + toLocalString(s);
+        if (!l.empty())
+        {
+          result += ",";
+        }
+      }
+      else
+      {
+        result += "   ";
+      }
+
+      if (!l.empty())
+      {
+        result += " --" + toLocalString(l);
+      }
+
+      auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
+
+      if (!o.is_boolean)
+      {
+        if (o.has_implicit)
+        {
+          result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + 
")]";
+        }
+        else
+        {
+          result += " " + arg;
+        }
+      }
+
+      return result;
+    }
+
+    String
+    format_description
+    (
+      const HelpOptionDetails& o,
+      size_t start,
+      size_t width
+    )
+    {
+      auto desc = o.desc;
+
+      if (o.has_default && (!o.is_boolean || o.default_value != "false"))
+      {
+        if(!o.default_value.empty())
+        {
+          desc += toLocalString(" (default: " + o.default_value + ")");
+        }
+        else
+        {
+          desc += toLocalString(" (default: \"\")");
+        }
+      }
+
+      String result;
+
+      auto current = std::begin(desc);
+      auto startLine = current;
+      auto lastSpace = current;
+
+      auto size = size_t{};
+
+      while (current != std::end(desc))
+      {
+        if (*current == ' ')
+        {
+          lastSpace = current;
+        }
+
+        if (*current == '\n')
+        {
+          startLine = current + 1;
+          lastSpace = startLine;
+        }
+        else if (size > width)
+        {
+          if (lastSpace == startLine)
+          {
+            stringAppend(result, startLine, current + 1);
+            stringAppend(result, "\n");
+            stringAppend(result, start, ' ');
+            startLine = current + 1;
+            lastSpace = startLine;
+          }
+          else
+          {
+            stringAppend(result, startLine, lastSpace);
+            stringAppend(result, "\n");
+            stringAppend(result, start, ' ');
+            startLine = lastSpace + 1;
+            lastSpace = startLine;
+          }
+          size = 0;
+        }
+        else
+        {
+          ++size;
+        }
+
+        ++current;
+      }
+
+      //append whatever is left
+      stringAppend(result, startLine, current);
+
+      return result;
+    }
+  } // namespace
+
+inline
+ParseResult::ParseResult
+(
+  std::shared_ptr<
+    std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
+  > options,
+  std::vector<std::string> positional,
+  bool allow_unrecognised,
+  int& argc, char**& argv
+)
+: m_options(std::move(options))
+, m_positional(std::move(positional))
+, m_next_positional(m_positional.begin())
+, m_allow_unrecognised(allow_unrecognised)
+{
+  parse(argc, argv);
+}
+
+inline
+void
+Options::add_options
+(
+  const std::string &group,
+  std::initializer_list<Option> options
+)
+{
+ OptionAdder option_adder(*this, group);
+ for (const auto &option: options)
+ {
+   option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);
+ }
+}
+
+inline
+OptionAdder
+Options::add_options(std::string group)
+{
+  return OptionAdder(*this, std::move(group));
+}
+
+inline
+OptionAdder&
+OptionAdder::operator()
+(
+  const std::string& opts,
+  const std::string& desc,
+  const std::shared_ptr<const Value>& value,
+  std::string arg_help
+)
+{
+  std::match_results<const char*> result;
+  std::regex_match(opts.c_str(), result, option_specifier);
+
+  if (result.empty())
+  {
+    throw_or_mimic<invalid_option_format_error>(opts);
+  }
+
+  const auto& short_match = result[2];
+  const auto& long_match = result[3];
+
+  if (!short_match.length() && !long_match.length())
+  {
+    throw_or_mimic<invalid_option_format_error>(opts);
+  } else if (long_match.length() == 1 && short_match.length())
+  {
+    throw_or_mimic<invalid_option_format_error>(opts);
+  }
+
+  auto option_names = []
+  (
+    const std::sub_match<const char*>& short_,
+    const std::sub_match<const char*>& long_
+  )
+  {
+    if (long_.length() == 1)
+    {
+      return std::make_tuple(long_.str(), short_.str());
+    }
+    return std::make_tuple(short_.str(), long_.str());
+  }(short_match, long_match);
+
+  m_options.add_option
+  (
+    m_group,
+    std::get<0>(option_names),
+    std::get<1>(option_names),
+    desc,
+    value,
+    std::move(arg_help)
+  );
+
+  return *this;
+}
+
+inline
+void
+ParseResult::parse_default(const std::shared_ptr<OptionDetails>& details)
+{
+  m_results[details].parse_default(details);
+}
+
+inline
+void
+ParseResult::parse_option
+(
+  const std::shared_ptr<OptionDetails>& value,
+  const std::string& /*name*/,
+  const std::string& arg
+)
+{
+  auto& result = m_results[value];
+  result.parse(value, arg);
+
+  m_sequential.emplace_back(value->long_name(), arg);
+}
+
+inline
+void
+ParseResult::checked_parse_arg
+(
+  int argc,
+  char* argv[],
+  int& current,
+  const std::shared_ptr<OptionDetails>& value,
+  const std::string& name
+)
+{
+  if (current + 1 >= argc)
+  {
+    if (value->value().has_implicit())
+    {
+      parse_option(value, name, value->value().get_implicit_value());
+    }
+    else
+    {
+      throw_or_mimic<missing_argument_exception>(name);
+    }
+  }
+  else
+  {
+    if (value->value().has_implicit())
+    {
+      parse_option(value, name, value->value().get_implicit_value());
+    }
+    else
+    {
+      parse_option(value, name, argv[current + 1]);
+      ++current;
+    }
+  }
+}
+
+inline
+void
+ParseResult::add_to_option(const std::string& option, const std::string& arg)
+{
+  auto iter = m_options->find(option);
+
+  if (iter == m_options->end())
+  {
+    throw_or_mimic<option_not_exists_exception>(option);
+  }
+
+  parse_option(iter->second, option, arg);
+}
+
+inline
+bool
+ParseResult::consume_positional(const std::string& a)
+{
+  while (m_next_positional != m_positional.end())
+  {
+    auto iter = m_options->find(*m_next_positional);
+    if (iter != m_options->end())
+    {
+      auto& result = m_results[iter->second];
+      if (!iter->second->value().is_container())
+      {
+        if (result.count() == 0)
+        {
+          add_to_option(*m_next_positional, a);
+          ++m_next_positional;
+          return true;
+        }
+        ++m_next_positional;
+        continue;
+      }
+      add_to_option(*m_next_positional, a);
+      return true;
+    }
+    throw_or_mimic<option_not_exists_exception>(*m_next_positional);
+  }
+
+  return false;
+}
+
+inline
+void
+Options::parse_positional(std::string option)
+{
+  parse_positional(std::vector<std::string>{std::move(option)});
+}
+
+inline
+void
+Options::parse_positional(std::vector<std::string> options)
+{
+  m_positional = std::move(options);
+  m_next_positional = m_positional.begin();
+
+  m_positional_set.insert(m_positional.begin(), m_positional.end());
+}
+
+inline
+void
+Options::parse_positional(std::initializer_list<std::string> options)
+{
+  parse_positional(std::vector<std::string>(options));
+}
+
+inline
+ParseResult
+Options::parse(int& argc, char**& argv)
+{
+  ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, 
argv);
+  return result;
+}
+
+inline
+void
+ParseResult::parse(int& argc, char**& argv)
+{
+  int current = 1;
+
+  int nextKeep = 1;
+
+  bool consume_remaining = false;
+
+  while (current != argc)
+  {
+    if (strcmp(argv[current], "--") == 0)
+    {
+      consume_remaining = true;
+      ++current;
+      break;
+    }
+
+    std::match_results<const char*> result;
+    std::regex_match(argv[current], result, option_matcher);
+
+    if (result.empty())
+    {
+      //not a flag
+
+      // but if it starts with a `-`, then it's an error
+      if (argv[current][0] == '-' && argv[current][1] != '\0') {
+        if (!m_allow_unrecognised) {
+          throw_or_mimic<option_syntax_exception>(argv[current]);
+        }
+      }
+
+      //if true is returned here then it was consumed, otherwise it is
+      //ignored
+      if (consume_positional(argv[current]))
+      {
+      }
+      else
+      {
+        argv[nextKeep] = argv[current];
+        ++nextKeep;
+      }
+      //if we return from here then it was parsed successfully, so continue
+    }
+    else
+    {
+      //short or long option?
+      if (result[4].length() != 0)
+      {
+        const std::string& s = result[4];
+
+        for (std::size_t i = 0; i != s.size(); ++i)
+        {
+          std::string name(1, s[i]);
+          auto iter = m_options->find(name);
+
+          if (iter == m_options->end())
+          {
+            if (m_allow_unrecognised)
+            {
+              continue;
+            }
+            //error
+            throw_or_mimic<option_not_exists_exception>(name);
+          }
+
+          auto value = iter->second;
+
+          if (i + 1 == s.size())
+          {
+            //it must be the last argument
+            checked_parse_arg(argc, argv, current, value, name);
+          }
+          else if (value->value().has_implicit())
+          {
+            parse_option(value, name, value->value().get_implicit_value());
+          }
+          else
+          {
+            //error
+            throw_or_mimic<option_requires_argument_exception>(name);
+          }
+        }
+      }
+      else if (result[1].length() != 0)
+      {
+        const std::string& name = result[1];
+
+        auto iter = m_options->find(name);
+
+        if (iter == m_options->end())
+        {
+          if (m_allow_unrecognised)
+          {
+            // keep unrecognised options in argument list, skip to next 
argument
+            argv[nextKeep] = argv[current];
+            ++nextKeep;
+            ++current;
+            continue;
+          }
+          //error
+          throw_or_mimic<option_not_exists_exception>(name);
+        }
+
+        auto opt = iter->second;
+
+        //equals provided for long option?
+        if (result[2].length() != 0)
+        {
+          //parse the option given
+
+          parse_option(opt, name, result[3]);
+        }
+        else
+        {
+          //parse the next argument
+          checked_parse_arg(argc, argv, current, opt, name);
+        }
+      }
+
+    }
+
+    ++current;
+  }
+
+  for (auto& opt : *m_options)
+  {
+    auto& detail = opt.second;
+    const auto& value = detail->value();
+
+    auto& store = m_results[detail];
+
+    if(value.has_default() && !store.count() && !store.has_default()){
+      parse_default(detail);
+    }
+  }
+
+  if (consume_remaining)
+  {
+    while (current < argc)
+    {
+      if (!consume_positional(argv[current])) {
+        break;
+      }
+      ++current;
+    }
+
+    //adjust argv for any that couldn't be swallowed
+    while (current != argc) {
+      argv[nextKeep] = argv[current];
+      ++nextKeep;
+      ++current;
+    }
+  }
+
+  argc = nextKeep;
+
+}
+
+inline
+void
+Options::add_option
+(
+  const std::string& group,
+  const Option& option
+)
+{
+    add_options(group, {option});
+}
+
+inline
+void
+Options::add_option
+(
+  const std::string& group,
+  const std::string& s,
+  const std::string& l,
+  std::string desc,
+  const std::shared_ptr<const Value>& value,
+  std::string arg_help
+)
+{
+  auto stringDesc = toLocalString(std::move(desc));
+  auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);
+
+  if (!s.empty())
+  {
+    add_one_option(s, option);
+  }
+
+  if (!l.empty())
+  {
+    add_one_option(l, option);
+  }
+
+  //add the help details
+  auto& options = m_help[group];
+
+  options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
+      value->has_default(), value->get_default_value(),
+      value->has_implicit(), value->get_implicit_value(),
+      std::move(arg_help),
+      value->is_container(),
+      value->is_boolean()});
+}
+
+inline
+void
+Options::add_one_option
+(
+  const std::string& option,
+  const std::shared_ptr<OptionDetails>& details
+)
+{
+  auto in = m_options->emplace(option, details);
+
+  if (!in.second)
+  {
+    throw_or_mimic<option_exists_error>(option);
+  }
+}
+
+inline
+String
+Options::help_one_group(const std::string& g) const
+{
+  using OptionHelp = std::vector<std::pair<String, String>>;
+
+  auto group = m_help.find(g);
+  if (group == m_help.end())
+  {
+    return "";
+  }
+
+  OptionHelp format;
+
+  size_t longest = 0;
+
+  String result;
+
+  if (!g.empty())
+  {
+    result += toLocalString(" " + g + " options:\n");
+  }
+
+  for (const auto& o : group->second.options)
+  {
+    if (m_positional_set.find(o.l) != m_positional_set.end() &&
+        !m_show_positional)
+    {
+      continue;
+    }
+
+    auto s = format_option(o);
+    longest = (std::max)(longest, stringLength(s));
+    format.push_back(std::make_pair(s, String()));
+  }
+
+  longest = (std::min)(longest, static_cast<size_t>(OPTION_LONGEST));
+
+  //widest allowed description
+  auto allowed = size_t{76} - longest - OPTION_DESC_GAP;
+
+  auto fiter = format.begin();
+  for (const auto& o : group->second.options)
+  {
+    if (m_positional_set.find(o.l) != m_positional_set.end() &&
+        !m_show_positional)
+    {
+      continue;
+    }
+
+    auto d = format_description(o, longest + OPTION_DESC_GAP, allowed);
+
+    result += fiter->first;
+    if (stringLength(fiter->first) > longest)
+    {
+      result += '\n';
+      result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
+    }
+    else
+    {
+      result += toLocalString(std::string(longest + OPTION_DESC_GAP -
+        stringLength(fiter->first),
+        ' '));
+    }
+    result += d;
+    result += '\n';
+
+    ++fiter;
+  }
+
+  return result;
+}
+
+inline
+void
+Options::generate_group_help
+(
+  String& result,
+  const std::vector<std::string>& print_groups
+) const
+{
+  for (size_t i = 0; i != print_groups.size(); ++i)
+  {
+    const String& group_help_text = help_one_group(print_groups[i]);
+    if (empty(group_help_text))
+    {
+      continue;
+    }
+    result += group_help_text;
+    if (i < print_groups.size() - 1)
+    {
+      result += '\n';
+    }
+  }
+}
+
+inline
+void
+Options::generate_all_groups_help(String& result) const
+{
+  std::vector<std::string> all_groups;
+  all_groups.reserve(m_help.size());
+
+  for (const auto& group : m_help)
+  {
+    all_groups.push_back(group.first);
+  }
+
+  generate_group_help(result, all_groups);
+}
+
+inline
+std::string
+Options::help(const std::vector<std::string>& help_groups) const
+{
+  String result = m_help_string + "\nUsage:\n  " +
+    toLocalString(m_program) + " " + toLocalString(m_custom_help);
+
+  if (!m_positional.empty() && !m_positional_help.empty()) {
+    result += " " + toLocalString(m_positional_help);
+  }
+
+  result += "\n\n";
+
+  if (help_groups.empty())
+  {
+    generate_all_groups_help(result);
+  }
+  else
+  {
+    generate_group_help(result, help_groups);
+  }
+
+  return toUTF8String(result);
+}
+
+inline
+std::vector<std::string>
+Options::groups() const
+{
+  std::vector<std::string> g;
+
+  std::transform(
+    m_help.begin(),
+    m_help.end(),
+    std::back_inserter(g),
+    [] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
+    {
+      return pair.first;
+    }
+  );
+
+  return g;
+}
+
+inline
+const HelpGroupDetails&
+Options::group_help(const std::string& group) const
+{
+  return m_help.at(group);
+}
+
+} // namespace cxxopts
+
+#endif //CXXOPTS_HPP_INCLUDED


Property changes on: brlcad/trunk/misc/repowork/cxxopts.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/misc_cmds.cpp
===================================================================
--- brlcad/trunk/misc/repowork/misc_cmds.cpp                            (rev 0)
+++ brlcad/trunk/misc/repowork/misc_cmds.cpp    2020-07-23 14:15:54 UTC (rev 
76444)
@@ -0,0 +1,135 @@
+/*                   M I S C _ C M D S . C P P
+ * BRL-CAD
+ *
+ * Published in 2020 by the United States Government.
+ * This work is in the public domain.
+ *
+ */
+/** @file misc_cmds.cpp
+ *
+ * Various commands not truly handled/implemented
+ * in repowork but mentioned in the git fast-import
+ * documentation.
+ *
+ */
+
+#include "repowork.h"
+
+int
+parse_alias(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 6);  // Remove "alias " prefix
+
+    // For the moment, we don't support aliass so this never works...
+    std::cerr << "Unsupported command \"alias\" (specified: : " << line << 
")\n";
+
+    return -1;
+}
+
+int
+parse_cat_blob(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+
+    // For the moment, we don't support cat_blobs so this never works...
+    std::cerr << "Unsupported command \"cat_blob\" - ignored\n";
+
+    return 0;
+}
+
+int
+parse_checkpoint(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+
+    // For the moment, we don't support checkpoints so this never works...
+    std::cerr << "Unsupported command \"checkpoint\" - ignored\n";
+
+    return 0;
+}
+
+int
+parse_done(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+
+    // For the moment, we don't support dones so this never works...
+    std::cerr << "Unsupported command \"done\"- ignored\n";
+
+    return 0;
+}
+
+int
+parse_feature(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+    line.erase(0, 8);  // Remove "feature " prefix
+
+    // For the moment, we don't support any features so this never works...
+    std::cerr << "Unsupported command \"feature\" (specified: " << line << 
")\n";
+
+    return -1;
+}
+
+int
+parse_get_mark(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+
+    // For the moment, we don't support get_marks so this never works...
+    std::cerr << "Unsupported command \"get_mark\" - ignored\n";
+
+    return -1;
+}
+
+int
+parse_ls(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+
+    // For the moment, we don't support lss so this never works...
+    std::cerr << "Unsupported command \"ls\" - ignored\n";
+
+    return 0;
+}
+
+int
+parse_option(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+
+    // For the moment, we don't support options so this never works...
+    std::cout << "Unsupported command \"option\" - ignored\n";
+
+    return -1;
+}
+
+int
+parse_progress(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::string line;
+    std::getline(infile, line);
+
+    // For the moment, we don't support progresss so this never works...
+    std::cerr << "Unsupported command \"progress\" - ignored\n";
+
+    return 0;
+}
+
+// Local Variables:
+// tab-width: 8
+// mode: C++
+// c-basic-offset: 4
+// indent-tabs-mode: t
+// c-file-style: "stroustrup"
+// End:
+// ex: shiftwidth=4 tabstop=8


Property changes on: brlcad/trunk/misc/repowork/misc_cmds.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/repowork.cpp
===================================================================
--- brlcad/trunk/misc/repowork/repowork.cpp                             (rev 0)
+++ brlcad/trunk/misc/repowork/repowork.cpp     2020-07-23 14:15:54 UTC (rev 
76444)
@@ -0,0 +1,332 @@
+/*                    R E P O W O R K . C P P
+ * BRL-CAD
+ *
+ * Published in 2020 by the United States Government.
+ * This work is in the public domain.
+ *
+ */
+/** @file repowork.cpp
+ *
+ * Utility functions and main processing loop
+ *
+ */
+
+#include "cxxopts.hpp"
+#include "repowork.h"
+
+int
+git_parse_commitish(git_commitish &gc, git_fi_data *s, std::string line)
+{
+       if (line.c_str()[0] == ':') {
+        // If we start with a colon, we have a mark - translate it and zero
+        // from_str.
+        line.erase(0, 1); // Remove ":" prefix
+       long omark = std::stol(line);
+        gc.mark = s->mark_old_to_new[omark];
+       if (s->mark_to_index.find(gc.mark) != s->mark_to_index.end()) {
+           gc.index = s->mark_to_index[gc.mark];
+           //std::cout << "Mark id :" << line << " -> " << gc.index << "\n";
+       } else {
+           std::cerr << "Mark with no index:" << gc.mark << "\n";
+           exit(EXIT_FAILURE);
+       }
+        return 0;
+    }
+    if (!ficmp(line, std::string("refs/heads/"))) {
+        gc.ref = std::stol(line);
+        return 0;
+    }
+    if (line.length() == 40) {
+        // Probably have a SHA1
+        gc.sha1 = line;
+        gc.index = s->mark_to_index[s->sha1_to_mark[gc.sha1]];
+        //std::cout << "SHA1 id :" << gc.sha1 << " -> " << gc.mark << " -> " 
<< gc.index << "\n";
+        return 0;
+    }
+
+    return -1;
+}
+
+int
+git_unpack_notes(git_fi_data *s, std::ifstream &infile, std::string &repo_path)
+{
+    // Iterate over the commits looking for note commits.  If we find one,
+    // find its associated blob with data, read it, find the associated
+    // commit, and stash it in a string in that container.
+    for (size_t i = 0; i < s->commits.size(); i++) {
+       if (s->commits[i].notes_commit) {
+           continue;
+       }
+
+       // This is cheap and clunky, but I've not yet found a document
+       // describing how to reliably unpack git notes...
+       std::string git_notes_cmd = std::string("cd ") + repo_path + 
std::string(" && git log -1 ") + s->commits[i].id.sha1 + std::string(" 
--pretty=format:\"%N\" > ../sha1.txt && cd ..");
+        if (std::system(git_notes_cmd.c_str())) {
+            std::cout << "git_sha1_cmd failed\n";
+           exit(-1);
+        }
+
+       std::ifstream n("sha1.txt");
+       if (!n.good()) {
+           std::cout << "sha1.txt read failed\n";
+           exit(-1);
+       }
+       std::string note((std::istreambuf_iterator<char>(n)), 
std::istreambuf_iterator<char>());
+
+       // Write the message to the commit's string;
+       s->commits[i].notes_string = note;
+
+       n.close();
+    }
+
+    return 0;
+}
+
+int
+git_map_emails(git_fi_data *s, std::string &email_map)
+{
+    // read map
+    std::ifstream infile(email_map, std::ifstream::binary);
+    if (!infile.good()) {
+       std::cerr << "Could not open email_map file: " << email_map << "\n";
+       exit(-1);
+    }
+
+    std::map<std::string, std::string> email_id_map;
+
+    std::string line;
+    while (std::getline(infile, line)) {
+       // Skip empty lines
+       if (!line.length()) {
+           continue;
+       }
+
+       size_t spos = line.find_first_of(";");
+       if (spos == std::string::npos) {
+           std::cerr << "Invalid email map line!: " << line << "\n";
+           exit(-1);
+       }
+
+       std::string id1 = line.substr(0, spos);
+       std::string id2 = line.substr(spos+1, std::string::npos);
+
+       std::cout << "id1: \"" << id1 << "\"\n";
+       std::cout << "id2: \"" << id2 << "\"\n";
+       email_id_map[id1] = id2;
+    }
+
+    // Iterate over the commits looking for note commits.  If we find one,
+    // find its associated blob with data, read it, find the associated
+    // commit, and stash it in a string in that container.
+    for (size_t i = 0; i < s->commits.size(); i++) {
+       git_commit_data *c = &(s->commits[i]);
+       if (email_id_map.find(c->author) != email_id_map.end()) {
+           std::string nauthor = email_id_map[c->author];
+           c->author = nauthor;
+       }
+       if (email_id_map.find(c->committer) != email_id_map.end()) {
+           std::string ncommitter = email_id_map[c->committer];
+           //std::cerr << "Replaced committer \"" << c->committer << "\" with 
\"" << ncommitter << "\"\n";
+           c->committer = ncommitter;
+       }
+    }
+
+    return 0;
+}
+
+
+
+typedef int (*gitcmd_t)(git_fi_data *, std::ifstream &);
+
+gitcmd_t
+gitit_find_cmd(std::string &line, std::map<std::string, gitcmd_t> &cmdmap)
+{
+    gitcmd_t gc = NULL;
+    std::map<std::string, gitcmd_t>::iterator c_it;
+    for (c_it = cmdmap.begin(); c_it != cmdmap.end(); c_it++) {
+       if (!ficmp(line, c_it->first)) {
+           gc = c_it->second;
+           break;
+       }
+    }
+    return gc;
+}
+
+int
+parse_fi_file(git_fi_data *fi_data, std::ifstream &infile)
+{
+    std::map<std::string, gitcmd_t> cmdmap;
+    cmdmap[std::string("alias")] = parse_alias;
+    cmdmap[std::string("blob")] = parse_blob;
+    cmdmap[std::string("cat-blob")] = parse_cat_blob;
+    cmdmap[std::string("checkpoint")] = parse_checkpoint;
+    cmdmap[std::string("commit ")] = parse_commit;
+    cmdmap[std::string("done")] = parse_done;
+    cmdmap[std::string("feature")] = parse_feature;
+    cmdmap[std::string("get-mark")] = parse_get_mark;
+    cmdmap[std::string("ls")] = parse_ls;
+    cmdmap[std::string("option")] = parse_option;
+    cmdmap[std::string("progress")] = parse_progress;
+    cmdmap[std::string("reset")] = parse_reset;
+    cmdmap[std::string("tag")] = parse_tag;
+
+    size_t offset = infile.tellg();
+    std::string line;
+    std::map<std::string, gitcmd_t>::iterator c_it;
+    while (std::getline(infile, line)) {
+       // Skip empty lines
+       if (!line.length()) {
+           offset = infile.tellg();
+           continue;
+       }
+
+       gitcmd_t gc = gitit_find_cmd(line, cmdmap);
+       if (!gc) {
+           //std::cerr << "Unsupported command!\n";
+           offset = infile.tellg();
+           continue;
+       }
+
+       // If we found a command, process it
+       //std::cout << "line: " << line << "\n";
+       // some commands have data on the command line - reset seek so the
+       // callback can process it
+       infile.seekg(offset);
+       (*gc)(fi_data, infile);
+       offset = infile.tellg();
+    }
+
+
+    return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+    git_fi_data fi_data;
+    bool collapse_notes = false;
+    bool wrap_commit_lines = false;
+    bool trim_whitespace = false;
+    std::string repo_path;
+    std::string email_map;
+
+    // TODO - might be good do have a "validate" option that does the fast 
import and then
+    // checks every commit saved from the old repo in the new one...
+    try
+    {
+       cxxopts::Options options(argv[0], " - process git fast-import files");
+
+       options.add_options()
+           ("e,email-map", "Specify replacement username+email mappings (one 
map per line, format is commit-id-1;commit-id-2)", 
cxxopts::value<std::vector<std::string>>(), "path to map file")
+           ("n,collapse-notes", "Take any git-notes contents and append them 
to regular commit messages.", cxxopts::value<bool>(collapse_notes))
+           ("r,repo", "Original git repository path (must support running git 
log)", cxxopts::value<std::vector<std::string>>(), "relative path to Git 
repository")
+           ("t,trim-whitespace", "Trim extra spaces and end-of-line characters 
from the end of commit messages", cxxopts::value<bool>(trim_whitespace))
+           ("w,wrap-commit-lines", "Wrap long commit lines to 72 cols (won't 
wrap messages already having multiple non-empty lines)", 
cxxopts::value<bool>(wrap_commit_lines))
+           ("h,help", "Print help")
+           ;
+
+       auto result = options.parse(argc, argv);
+
+       if (result.count("help"))
+       {
+           std::cout << options.help({""}) << std::endl;
+           return 0;
+       }
+
+       if (result.count("r"))
+       {
+           auto& ff = result["r"].as<std::vector<std::string>>();
+           repo_path = ff[0];
+       }
+
+       if (result.count("e"))
+       {
+           auto& ff = result["e"].as<std::vector<std::string>>();
+           email_map = ff[0];
+       }
+
+    }
+    catch (const cxxopts::OptionException& e)
+    {
+       std::cerr << "error parsing options: " << e.what() << std::endl;
+       return -1;
+    }
+
+    if (collapse_notes && !repo_path.length()) {
+       std::cerr << "Cannot collapse notes into commit messages without 
knowing the path\nto the repository - aborting.  (It is necessary to run git 
log to\ncapture the message information, and for that we need the 
original\nrepository in addition to the fast-import file.)\n\nTo specify a repo 
folder, use the -r option.  Currently the folder must be in the working 
directory.\n";
+       return -1;
+    }
+
+    if (argc != 3) {
+       std::cout << "repowork [opts] <input_file> <output_file>\n";
+       return -1;
+    }
+    std::ifstream infile(argv[1], std::ifstream::binary);
+    if (!infile.good()) {
+       return -1;
+    }
+
+    int ret = parse_fi_file(&fi_data, infile);
+
+    if (collapse_notes) {
+       // Let the output routines know not to write notes commits.
+       // (blobs will have to be taken care of later by git gc).
+       fi_data.write_notes = false;
+
+       // Reset the input stream
+       infile.clear();
+       infile.seekg(0, std::ios::beg);
+
+       // Handle the notes
+       git_unpack_notes(&fi_data, infile, repo_path);
+    }
+
+    if (email_map.length()) {
+       // Reset the input stream
+       infile.clear();
+       infile.seekg(0, std::ios::beg);
+
+       // Handle the notes
+       git_map_emails(&fi_data, email_map);
+    }
+
+    fi_data.wrap_commit_lines = wrap_commit_lines;
+    fi_data.trim_whitespace = trim_whitespace;
+
+    infile.close();
+
+    std::ifstream ifile(argv[1], std::ifstream::binary);
+    std::ofstream ofile(argv[2], std::ios::out | std::ios::binary);
+    for (size_t i = 0; i < fi_data.blobs.size(); i++) {
+       write_blob(ofile, &fi_data.blobs[i], ifile);
+    }
+    for (size_t i = 0; i < fi_data.commits.size(); i++) {
+       write_commit(ofile, &fi_data.commits[i], ifile);
+    }
+    for (size_t i = 0; i < fi_data.tags.size(); i++) {
+       write_tag(ofile, &fi_data.tags[i], ifile);
+    }
+
+    ifile.close();
+    ofile.close();
+
+    std::cout << "Git fast-import file is generated:  " << argv[2] << "\n\n";
+    std::cout << "Note that when imported, compression and packing will be 
suboptimal by default.\n";
+    std::cout << "Some possible steps to take:\n";
+    std::cout << "  mkdir git_repo && cd git_repo && git init\n";
+    std::cout << "  cat ../" << argv[2] << " | git fast-import\n";
+    std::cout << "  git gc --aggressive\n";
+    std::cout << "  git reflog expire --expire-unreachable=now --all\n";
+    std::cout << "  git gc --prune=now\n";
+
+    return 0;
+}
+
+// Local Variables:
+// tab-width: 8
+// mode: C++
+// c-basic-offset: 4
+// indent-tabs-mode: t
+// c-file-style: "stroustrup"
+// End:
+// ex: shiftwidth=4 tabstop=8


Property changes on: brlcad/trunk/misc/repowork/repowork.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/trunk/misc/repowork/repowork.h
===================================================================
--- brlcad/trunk/misc/repowork/repowork.h                               (rev 0)
+++ brlcad/trunk/misc/repowork/repowork.h       2020-07-23 14:15:54 UTC (rev 
76444)
@@ -0,0 +1,195 @@
+/*                      R E P O W O R K . H
+ * BRL-CAD
+ *
+ * Published in 2020 by the United States Government.
+ * This work is in the public domain.
+ *
+ */
+/** @file repowork.h
+ *
+ * Brief description
+ *
+ */
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <regex>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <stdlib.h>
+
+#ifndef REPOWORK_H
+#define REPOWORK_H
+
+/* Parser of Git fast-export information, based on the format described by:
+ * https://git-scm.com/docs/git-fast-import */
+
+
+/* Convenience macro for comparing a substring to the first part of a longer
+ * string - used primarily to find commands */
+#define ficmp(_s1, _s2) _s1.compare(0, _s2.size(), _s2) && _s1.size() >= 
_s2.size()
+
+class git_commitish {
+    public:
+       long index = -1;  // all commits must have an index into the master 
commit vector
+       long mark  = -1;  // globally unique numerical identifier
+       std::string sha1 = std::string(); // Original sha1 or part thereof, if 
available.  If the commit is modified this string should be invalidated.
+       std::string ref = std::string(); //  branch reference or other Git 
reference
+};
+
+
+/* Types of git file actions */
+enum git_action_t { filemodify, filedelete, filecopy, filerename, 
filedeleteall, notemodify };
+class git_op {
+    public:
+       git_action_t type;
+       std::string mode;
+       git_commitish dataref;
+       std::string path;
+       std::string dest_path;
+};
+
+struct git_fi_data;
+
+class git_commit_data {
+    public:
+       struct git_fi_data *s;
+
+       // Basic commit data
+       git_commitish id;
+       std::string commit_msg;
+       std::string branch;
+       std::vector<git_op> fileops;  // ordered set of file operations
+
+       // Authorship
+       std::string author;
+       std::string author_timestamp;
+       std::string committer;
+       std::string committer_timestamp;
+
+       // Relationships with other commits
+       git_commitish from;
+       std::vector<git_commitish> merges;
+
+       // Notes present a problem - they're stored using a separate
+       // structure commits, and data but are associated with commits
+       // using the SHA1 hash *only* - they aren't tied in to the mark
+       // ids in fast export.  Therefore, we need a --show-original-ids
+       // fast export to be able to make the association during parsing.
+       //
+       // A note commit will have one file op, whose path will be the
+       // SHA1 of the commit it notates and its dataref will be the
+       // note contents.
+       int notes_commit = 0;
+
+       // It's not maximally efficient space wise, but rather than trying
+       // to keep track of where the note content associated with this commit
+       // is whenever we need it, just provide a convenient place to stash it.
+       // Then we only have to untangle things once.
+       std::string notes_string;
+
+       // Resets are order dependent - treat them as pseudo-commits for
+       // storage purpose, but they are written differently
+       int reset_commit = 0;
+};
+
+class git_tag_data {
+    public:
+       struct git_fi_data *s;
+       std::string tag;
+       git_commitish id;
+       git_commitish from;
+       std::string tag_msg;
+       std::string tagger;
+       std::string tagger_timestamp;
+};
+
+class git_blob_data {
+    public:
+       struct git_fi_data *s;
+       size_t offset;
+       size_t length;
+       git_commitish id;
+
+       /* If a blob is needed that is not in the original fi file,
+        * we need a local buffer to hold the data */
+       char *cbuffer = NULL;
+};
+
+class git_fi_data {
+
+    public:
+       bool write_notes = true;
+       bool wrap_commit_lines = false;
+       bool trim_whitespace = false;
+
+       std::vector<git_blob_data> blobs;
+       std::vector<git_tag_data> tags;
+       std::vector<git_commit_data> commits;
+
+       // SHA1s are static in this environment, since it is too
+       // difficult to calculate the SHA1 after changing commit
+       // data - therefore, we need to be able to map from old
+       // SHA1 references to mark values.

@@ Diff output truncated at 100000 characters. @@
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