# # # patch "README" # from [17d7783d5f2bc559a5a396006da5af2b867dcffc] # to [6948dc9e6234ccdc6ac91e105a7f7ce565ca6b1b] # # patch "plain.py" # from [1eebae8b2b6f7cf6c0aef8ad8e63470ffe211f5c] # to [4f57fdb4db70aecc41c6fa383c36f4ac44a052ad] # # patch "setup.py" # from [0330702aab0e264622587bc829d9cb57205dd4a2] # to [cc4b8f4c21488a2f51bb505a8f3e5b9187b03b5d] # ============================================================ --- README 17d7783d5f2bc559a5a396006da5af2b867dcffc +++ README 6948dc9e6234ccdc6ac91e105a7f7ce565ca6b1b @@ -24,4 +24,35 @@ All synchronization is done between the two merkle-directories: the monotone database interacts only with the transit merkle-directory. +CONFIGURATION +------------------------- +Configuration is stored in ini-like file. Configuration file can be changed using '--config' option, default is +$HOME/.mtnplain (USERPROFILE is used instead of HOME if it's not existent - win32). + +If "-s|--storeconfig" option is specified then command line settings (repository, local dir etc.) are stored +in config file for future use. WARNING this option rewrites current config file. + +All options that are configurable by command line can be configured in file. Configuration is +readed from sections [default] and [DATABASEPATH] in this order. As a result effective precedence of +options is: + - command line options + - [DATABASEPATH] section of config file + - [default] section of config file + - hardcoded defaults + +Example configuration: +------- .mtnplain +[default] +verbose = 1 +rsakey = /home/zbigg/id_rsa + +[d:/repo/monotone-dumb.mtn] +verbose = 0 +repository = "dws://zzagorski.strony.wi.ps.pl/mtdumb/index.php:monotone-plain/" +local = file:C:\DOCUME~1\zbigg\USTAWI~1\Temp\_d_\repo\monotone-dumb.mtn-mtndumbtemp + +[d:/repo/xxx.mtn] +rsakey = /home/zbigg/id_rsa_other +------- + ============================================================ --- plain.py 1eebae8b2b6f7cf6c0aef8ad8e63470ffe211f5c +++ plain.py 4f57fdb4db70aecc41c6fa383c36f4ac44a052ad @@ -12,6 +12,7 @@ Simpler sync for monotone """ from optparse import OptionParser +from ConfigParser import SafeConfigParser from dumb import Dumbtone import os.path import monotone @@ -19,12 +20,28 @@ ACTIONS = [ "pull", "push", "sync"] + def readConfig(cfgfile): - cfp = ConfigParser.SafeConfigParser() - cfp.addSection("default") - sfp.set("default","verbose","0") - sfp.set("default","hostKeys","~/.ssh/known_hosts") - cfp.read(cfgfile) + class NoneReturningOptionParser(SafeConfigParser): + def get(self, section, name): + if self.has_option(section,name): + return SafeConfigParser.get(self,section,name) + else: + return None + + def set(self, section,name, value): + if not self.has_section(section): + SafeConfigParser.add_section(self, section) + SafeConfigParser.set(self,section,name,value) + + def items(self, section): + if self.has_section(section): + return SafeConfigParser.items(self,section) + else: + return [] + cfg = NoneReturningOptionParser() + cfg.read(cfgfile) + return cfg def getTempDir(database): try: @@ -34,6 +51,7 @@ tmpDir = os.environ['TMP'] except KeyError: tmpDir = "/tmp" + tmpDir = os.path.normpath(tmpDir) if database[0].isalpha() and database[1] == ':': database = "_" + database[0] + "_" + database[2:] database = os.path.normpath(database) @@ -48,7 +66,7 @@ try: for stanza in monotone.basic_io_parser(fh.read()): for k,v in stanza: - if k == "database": + if k == "database": return v[0] finally: fh.close() @@ -56,27 +74,18 @@ if not os.path.exists(dir): break return None -def getDefaultUrl(): +def getDefaultConfigFile(): try: - fh = file(".mtnplain","r") + homeDir = os.environ['HOME'] + except KeyError: try: - for stanza in monotone.basic_io_parser(fh.read()): - for k,v in stanza: - if k == "repository": - return v[0] - finally: - fh.close() - except IOError: - pass - return None - -def setDefaultUrl(url): - f = file(".mtnplain","w+") - print >> f, 'repository "%s"' % url - f.close() - return - + homeDir = os.environ['USERPROFILE'] + except KeyError: + homeDir = "." + return os.path.normpath(os.path.join(homeDir, ".mtnplain")) + def parseOpt(): + par = OptionParser(usage= """%prog [options] pull|push|sync remote-URL @@ -86,55 +95,92 @@ file:// http[s]:// (pull only) sftp://[user[:address@hidden:port] - When used with -dsskey or --rsakey the optional password argument - is used to decrypt the private key file. -""") + When used with --dsskey or --rsakey the optional password argument + is used to decrypt the private key file.""") + par.add_option("-d","--db", help="monotone db to use", metavar="STRING") par.add_option("-l","--local", help="local transit directory", metavar="PATH") par.add_option("--dsskey", help="optional, sftp only. DSS private key file. Can't be specified with --rsakey", metavar="FILE") par.add_option("--rsakey", help="optional, sftp only. RSA private key file. Can't be specified with --dsskey", metavar="FILE") - par.add_option("--hostkey", default="~/.ssh/known_hosts", + par.add_option("--hostkey", help="sftp only. File containing host keys. On unices defaults to %default. Must be specified on Win32.", metavar="FILE") par.add_option("--proxy", help="http(s),ftp only. Proxy to use for connecting.", metavar="http://[proxyuser[:address@hidden:proxyport]") - par.add_option("-v", "--verbose", type="int", default=0, + par.add_option("--config", + help="config file to use (default %default)", metavar="FILE") + par.add_option("-s","--storeconfig", + help="store 'repository' and 'local' settings in config file (default %default)", action="store_true") + par.add_option("-v", "--verbose", type="int", help="verbosity level from 0 (normal) to 2 (debug)", metavar="NUM") + # defaults are read from config according to selected database, so + # args must be parsed twice: + # - first to determine database and config file + defaultConfigFile = getDefaultConfigFile() + hardCodedDefaults = { + 'config': defaultConfigFile, + 'hostkey': "~/.ssh/known_hosts", + 'rsskey': None, + 'dsskey': None, + 'verbose': 0, + 'storeconfig': False + } + par.set_defaults(**hardCodedDefaults) (options, args) = par.parse_args() - if len(args)!=2 or args[0] not in ACTIONS: - if not len(args): - par.print_help() - sys.exit(1) - elif args[0] not in ACTIONS: - par.print_help() - sys.exit("\nERROR: Invalid operation specified\n") - elif len(args)==1: - defaultUrl = getDefaultUrl() - if defaultUrl is None: - par.print_help() - sys.exit("\nERROR: Missing remote-URL\n") - args = [ args[0], defaultUrl ] - else: - par.print_help() - sys.exit("\nERROR: Only one remote-URL allowed\n") - + # + # - second to determine rest params and possibly overwrite those set in config + # + defaults = {} + config = readConfig(options.config) if options.db is None: options.db = getDefaultDatabase() + defaults['db'] = options.db if options.db is None: - sys.exit("\nERROR: monotone db not specified and not in workspace\n") - if options.local is None: - import urlparse + par.error("executed outside workspace and no monotone database specified") + defaults.update(config.items("default")) + defaults.update(config.items(options.db)) + + par.set_defaults(**defaults) + (options, args) = par.parse_args() + + if len(args) == 0: + par.error("no action specified") + + action = args[0] + url = None + if action not in ACTIONS: + par.error("invalid operation specified") + + if len(args) == 1: + url = config.get(options.db, "repository") + if not url: + par.error("missing remote-URL") + elif len(args) == 2: + url = args[1] + else: + par.error("only one remote-URL allowed") + + if options.local is None: defaultTmpDir = getTempDir(options.db) if defaultTmpDir is None: - sys.exit("\nERROR: local transit directory not specified\n") - options.local = "file://" + defaultTmpDir - return (options, args) + par.error("local transit directory not specified") + options.local = "file:" + defaultTmpDir + + config.set(options.db, "repository", url) + config.set(options.db, "local", options.local) + return (options, config, action, url) -def main(): - (options, args) = parseOpt() +def saveConfig(options,config): + if not options.storeconfig: return + try: + config.write(file(options.config, "w+")) + except IOError: + pass +def main(): + (options, config, action, url) = parseOpt() optdict = {"dsskey":options.dsskey, "rsakey":options.rsakey, "hostkey":options.hostkey, @@ -142,13 +188,14 @@ "proxy":options.proxy} mtn = Dumbtone(options.db, options.verbose) - if args[0]=="pull": - mtn.do_pull(options.local, args[1], **optdict) - elif args[0]=="push": - mtn.do_push(options.local, args[1], **optdict) - elif args[0]=="sync": - mtn.do_sync(options.local, args[1], **optdict) - setDefaultUrl(args[1]) + if action=="pull": + mtn.do_pull(options.local, url, **optdict) + elif action=="push": + mtn.do_push(options.local, url, **optdict) + elif action=="sync": + mtn.do_sync(options.local, url, **optdict) + + saveConfig(options,config) if __name__ == "__main__": main() ============================================================ --- setup.py 0330702aab0e264622587bc829d9cb57205dd4a2 +++ setup.py cc4b8f4c21488a2f51bb505a8f3e5b9187b03b5d @@ -17,7 +17,7 @@ and one-way (pull) via http(s) and ftp.""" license = "LGPL" -version = "0.0.3" +version = "0.0.4" _authors = [ 'Nathaniel Smith', 'Zbynek Winkler', 'Riccardo "rghetta"',