mirror of https://github.com/vitalif/grive2
parent
a0aff5b146
commit
63bb138b2d
19
README.md
19
README.md
|
@ -85,6 +85,7 @@ Grive uses cmake to build. Basic install sequence is
|
|||
|
||||
### Grive2 v0.5.1-dev
|
||||
|
||||
- support for .griveignore
|
||||
- no-remote-new and upload-only modes
|
||||
- ignore regexp does not persist anymore (note that Grive will still track it to not
|
||||
accidentally delete remote files when changing ignore regexp)
|
||||
|
@ -137,3 +138,21 @@ New features:
|
|||
- #87: support for revisions
|
||||
- #86: ~~partial sync (contributed by justin at tierramedia.com)~~ that's not partial sync,
|
||||
that's only support for specifying local path on command line
|
||||
|
||||
## .griveignore
|
||||
|
||||
Rules are similar to Git's .gitignore, but may differ slightly due to the different
|
||||
implementation.
|
||||
|
||||
- lines that start with # are comments
|
||||
- leading and trailing spaces ignored unless escaped with \
|
||||
- non-empty lines without ! in front are treated as "exclude" patterns
|
||||
- non-empty lines with ! in front are treated as "include" patterns
|
||||
and have a priority over all "exclude" ones
|
||||
- patterns are matched against the filenames relative to the grive root
|
||||
- a/**/b matches any number of subpaths between a and b, including 0
|
||||
- **/a matches `a` inside any directory
|
||||
- b/** matches everything inside `b`, but not b itself
|
||||
- \* matches any number of any characters except /
|
||||
- ? matches any character except /
|
||||
- .griveignore itself isn't ignored by default, but you can include it in itself to ignore
|
||||
|
|
|
@ -46,10 +46,6 @@ to download only files that are changed in Google Drive and already exist locall
|
|||
\fB\-h\fR, \fB\-\-help\fR
|
||||
Produces help message
|
||||
.TP
|
||||
\fB\-\-ignore\fR <perl_regexp>
|
||||
Ignore files with relative paths matching this Perl Regular Expression.
|
||||
Value is remembered for next runs.
|
||||
.TP
|
||||
\fB\-l\fR <filename>, \fB\-\-log\fR <filename>
|
||||
Write log output to
|
||||
.I <filename>
|
||||
|
@ -81,6 +77,37 @@ Print ASCII progress bar for each downloaded/uploaded file.
|
|||
\fB\-V\fR, \fB\-\-verbose\fR
|
||||
Verbose mode. Enables more messages than usual.
|
||||
|
||||
.SH .griveignore
|
||||
.PP
|
||||
You may create .griveignore in your Grive root and use it to setup
|
||||
exclusion/inclusion rules.
|
||||
.PP
|
||||
Rules are similar to Git's .gitignore, but may differ slightly due to the different
|
||||
implementation.
|
||||
.IP \[bu]
|
||||
lines that start with # are comments
|
||||
.IP \[bu]
|
||||
leading and trailing spaces ignored unless escaped with \\
|
||||
.IP \[bu]
|
||||
non-empty lines without ! in front are treated as "exclude" patterns
|
||||
.IP \[bu]
|
||||
non-empty lines with ! in front are treated as "include" patterns
|
||||
and have a priority over all "exclude" ones
|
||||
.IP \[bu]
|
||||
patterns are matched against the filenames relative to the grive root
|
||||
.IP \[bu]
|
||||
a/**/b matches any number of subpaths between a and b, including 0
|
||||
.IP \[bu]
|
||||
**/a matches `a` inside any directory
|
||||
.IP \[bu]
|
||||
b/** matches everything inside `b`, but not b itself
|
||||
.IP \[bu]
|
||||
* matches any number of any characters except /
|
||||
.IP \[bu]
|
||||
? matches any character except /
|
||||
.IP \[bu]
|
||||
\[char46]griveignore itself isn't ignored by default, but you can include it in itself to ignore
|
||||
|
||||
.SH AUTHORS
|
||||
.PP
|
||||
Current maintainer is Vitaliy Filippov.
|
||||
|
|
|
@ -124,15 +124,22 @@ int Main( int argc, char **argv )
|
|||
( "no-remote-new,n", "Download only files that are changed in Google Drive and already exist locally" )
|
||||
( "dry-run", "Only detect which files need to be uploaded/downloaded, "
|
||||
"without actually performing them." )
|
||||
( "ignore", po::value<std::string>(), "Perl RegExp to ignore files (matched against relative paths)." )
|
||||
( "upload-speed,U", po::value<unsigned>(), "Limit upload speed in kbytes per second" )
|
||||
( "download-speed,D", po::value<unsigned>(), "Limit download speed in kbytes per second" )
|
||||
( "progress-bar,P", "Enable progress bar for upload/download of files")
|
||||
;
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line( argc, argv, desc), vm );
|
||||
po::notify(vm);
|
||||
try
|
||||
{
|
||||
po::store( po::parse_command_line( argc, argv, desc ), vm );
|
||||
}
|
||||
catch( po::error &e )
|
||||
{
|
||||
std::cerr << "Options are incorrect. Use -h for help\n";
|
||||
return -1;
|
||||
}
|
||||
po::notify( vm );
|
||||
|
||||
// simple commands that doesn't require log or config
|
||||
if ( vm.count("help") )
|
||||
|
@ -148,9 +155,9 @@ int Main( int argc, char **argv )
|
|||
}
|
||||
|
||||
// initialize logging
|
||||
InitLog(vm) ;
|
||||
InitLog( vm ) ;
|
||||
|
||||
Config config(vm) ;
|
||||
Config config( vm ) ;
|
||||
|
||||
Log( "config file name %1%", config.Filename(), log::verbose );
|
||||
|
||||
|
|
|
@ -41,15 +41,10 @@
|
|||
|
||||
namespace gr {
|
||||
|
||||
namespace
|
||||
{
|
||||
const std::string state_file = ".grive_state" ;
|
||||
}
|
||||
|
||||
Drive::Drive( Syncer *syncer, const Val& options ) :
|
||||
m_syncer ( syncer ),
|
||||
m_root ( options["path"].Str() ),
|
||||
m_state ( m_root / state_file, options ),
|
||||
m_state ( m_root, options ),
|
||||
m_options ( options )
|
||||
{
|
||||
assert( m_syncer ) ;
|
||||
|
@ -72,7 +67,7 @@ void Drive::FromChange( const Entry& entry )
|
|||
|
||||
void Drive::SaveState()
|
||||
{
|
||||
m_state.Write( m_root / state_file ) ;
|
||||
m_state.Write() ;
|
||||
}
|
||||
|
||||
void Drive::DetectChanges()
|
||||
|
|
|
@ -32,17 +32,28 @@
|
|||
|
||||
namespace gr {
|
||||
|
||||
State::State( const fs::path& filename, const Val& options ) :
|
||||
const std::string state_file = ".grive_state" ;
|
||||
const std::string ignore_file = ".griveignore" ;
|
||||
const int MAX_IGN = 65536 ;
|
||||
const char* regex_escape_chars = ".^$|()[]{}*+?\\";
|
||||
const boost::regex regex_escape_re( "[.^$|()\\[\\]{}*+?\\\\]" );
|
||||
|
||||
inline std::string regex_escape( std::string s )
|
||||
{
|
||||
return regex_replace( s, regex_escape_re, "\\\\&", boost::format_sed );
|
||||
}
|
||||
|
||||
State::State( const fs::path& root, const Val& options ) :
|
||||
m_root ( root ),
|
||||
m_res ( options["path"].Str() ),
|
||||
m_cstamp ( -1 )
|
||||
{
|
||||
Read( filename ) ;
|
||||
Read() ;
|
||||
|
||||
// the "-f" option will make grive always think remote is newer
|
||||
m_force = options.Has( "force" ) ? options["force"].Bool() : false ;
|
||||
|
||||
std::string m_orig_ign = m_ign;
|
||||
m_ign = "";
|
||||
if ( options.Has( "ignore" ) && options["ignore"].Str() != m_ign )
|
||||
m_ign = options["ignore"].Str();
|
||||
else if ( options.Has( "dir" ) )
|
||||
|
@ -52,8 +63,7 @@ State::State( const fs::path& filename, const Val& options ) :
|
|||
if ( !m_dir.empty() )
|
||||
{
|
||||
// "-s" is internally converted to an ignore regexp
|
||||
const boost::regex esc( "[.^$|()\\[\\]{}*+?\\\\]" );
|
||||
m_dir = regex_replace( m_dir, esc, "\\\\&", boost::format_sed );
|
||||
m_dir = regex_escape( m_dir );
|
||||
size_t pos = 0;
|
||||
while ( ( pos = m_dir.find( '/', pos ) ) != std::string::npos )
|
||||
{
|
||||
|
@ -66,7 +76,7 @@ State::State( const fs::path& filename, const Val& options ) :
|
|||
}
|
||||
|
||||
m_ign_changed = m_orig_ign != "" && m_orig_ign != m_ign;
|
||||
m_ign_re = boost::regex( m_ign.empty() ? "^\\.(grive|grive_state|trash)" : ( m_ign+"|^\\.(grive|grive_state|trash)" ) );
|
||||
m_ign_re = boost::regex( m_ign.empty() ? "^\\.(grive$|grive_state$|trash)" : ( m_ign+"|^\\.(grive|grive_state|trash)" ) );
|
||||
}
|
||||
|
||||
State::~State()
|
||||
|
@ -277,27 +287,92 @@ State::iterator State::end()
|
|||
return m_res.end() ;
|
||||
}
|
||||
|
||||
void State::Read( const fs::path& filename )
|
||||
void State::Read()
|
||||
{
|
||||
try
|
||||
{
|
||||
File file( filename ) ;
|
||||
|
||||
m_st = ParseJson( file );
|
||||
m_ign = m_st.Has( "ignore_regexp" ) ? m_st["ignore_regexp"].Str() : std::string();
|
||||
|
||||
File st_file( m_root / state_file ) ;
|
||||
m_st = ParseJson( st_file );
|
||||
m_cstamp = m_st["change_stamp"].Int() ;
|
||||
}
|
||||
catch ( Exception& )
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File ign_file( m_root / ignore_file ) ;
|
||||
char ign[MAX_IGN] = { 0 };
|
||||
int s = ign_file.Read( ign, MAX_IGN-1 ) ;
|
||||
ParseIgnoreFile( ign, s );
|
||||
}
|
||||
catch ( Exception& e )
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void State::Write( const fs::path& filename )
|
||||
bool State::ParseIgnoreFile( const char* buffer, int size )
|
||||
{
|
||||
const boost::regex re1( "/\\\\\\*\\\\\\*$" );
|
||||
const boost::regex re2( "^\\\\\\*\\\\\\*/" );
|
||||
const boost::regex re3( "([^\\\\](\\\\\\\\)*)/\\\\\\*\\\\\\*/" );
|
||||
const boost::regex re4( "([^\\\\](\\\\\\\\)*|^)\\\\\\*" );
|
||||
const boost::regex re5( "([^\\\\](\\\\\\\\)*|^)\\\\\\?" );
|
||||
std::string exclude_re, include_re;
|
||||
int prev = 0;
|
||||
for ( int i = 0; i <= size; i++ )
|
||||
{
|
||||
if ( buffer[i] == '\n' || ( i == size && i > prev ) )
|
||||
{
|
||||
while ( prev < i && ( buffer[prev] == ' ' || buffer[prev] == '\t' || buffer[prev] == '\r' ) )
|
||||
prev++;
|
||||
if ( buffer[prev] != '#' )
|
||||
{
|
||||
int j;
|
||||
for ( j = i-1; j > prev; j-- )
|
||||
if ( buffer[j-1] == '\\' || ( buffer[j] != ' ' && buffer[j] != '\t' && buffer[j] != '\r' ) )
|
||||
break;
|
||||
std::string str( buffer+prev, j+1-prev );
|
||||
bool inc = str[0] == '!';
|
||||
if ( inc )
|
||||
str = str.substr( 1 );
|
||||
str = regex_escape( str );
|
||||
str = regex_replace( str, re1, "/.*", boost::format_perl );
|
||||
str = regex_replace( str, re2, ".*/", boost::format_perl );
|
||||
str = regex_replace( str, re3, "$1/(.*/)*", boost::format_perl );
|
||||
str = regex_replace( str, re4, "$1[^/]*", boost::format_perl );
|
||||
std::string str1;
|
||||
while (1)
|
||||
{
|
||||
str1 = regex_replace( str, re5, "$1[^/]", boost::format_perl );
|
||||
if ( str1.size() == str.size() )
|
||||
break;
|
||||
str = str1;
|
||||
}
|
||||
if ( !inc )
|
||||
exclude_re = exclude_re + ( exclude_re.size() > 0 ? "|" : "" ) + str;
|
||||
else
|
||||
include_re = include_re + ( include_re.size() > 0 ? "|" : "" ) + str;
|
||||
}
|
||||
prev = i+1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( exclude_re.size() > 0 )
|
||||
{
|
||||
m_ign = "^" + ( include_re.size() > 0 ? "(?!" + include_re + ")" : std::string() ) + "(" + exclude_re + ")$";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void State::Write()
|
||||
{
|
||||
m_st.Set( "change_stamp", Val( m_cstamp ) ) ;
|
||||
m_st.Set( "ignore_regexp", Val( m_ign ) ) ;
|
||||
|
||||
fs::path filename = m_root / state_file ;
|
||||
std::ofstream fs( filename.string().c_str() ) ;
|
||||
fs << m_st ;
|
||||
}
|
||||
|
|
|
@ -42,15 +42,15 @@ public :
|
|||
typedef ResourceTree::iterator iterator ;
|
||||
|
||||
public :
|
||||
explicit State( const fs::path& filename, const Val& options ) ;
|
||||
explicit State( const fs::path& root, const Val& options ) ;
|
||||
~State() ;
|
||||
|
||||
void FromLocal( const fs::path& p ) ;
|
||||
void FromRemote( const Entry& e ) ;
|
||||
void ResolveEntry() ;
|
||||
|
||||
void Read( const fs::path& filename ) ;
|
||||
void Write( const fs::path& filename ) ;
|
||||
void Read() ;
|
||||
void Write() ;
|
||||
|
||||
Resource* FindByHref( const std::string& href ) ;
|
||||
Resource* FindByID( const std::string& id ) ;
|
||||
|
@ -64,6 +64,7 @@ public :
|
|||
void ChangeStamp( long cstamp ) ;
|
||||
|
||||
private :
|
||||
bool ParseIgnoreFile( const char* buffer, int size ) ;
|
||||
void FromLocal( const fs::path& p, Resource *folder, Val& tree ) ;
|
||||
void FromChange( const Entry& e ) ;
|
||||
bool Update( const Entry& e ) ;
|
||||
|
@ -72,6 +73,7 @@ private :
|
|||
bool IsIgnore( const std::string& filename ) ;
|
||||
|
||||
private :
|
||||
fs::path m_root ;
|
||||
ResourceTree m_res ;
|
||||
int m_cstamp ;
|
||||
std::string m_ign ;
|
||||
|
|
Loading…
Reference in New Issue