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