Hello community, here is the log from the commit of package duplicity for openSUSE:Factory checked in at 2015-12-17 15:52:08 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/duplicity (Old) and /work/SRC/openSUSE:Factory/.duplicity.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "duplicity" Changes: -------- --- /work/SRC/openSUSE:Factory/duplicity/duplicity.changes 2015-10-03 20:29:55.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.duplicity.new/duplicity.changes 2015-12-17 15:52:09.000000000 +0100 @@ -1,0 +2,29 @@ +Wed Dec 9 09:44:40 UTC 2015 - w...@rosenauer.org + +- update to 0.7.06 + * This adds support for AWS S3's newly announced Infrequent Access + storage class and is intended to implement Blueprint: + https://blueprints.launchpad.net/duplicity/+spec/aws-s3-std-ia-class + * A new command line option, --s3-use-ia, is added, and boto backend + will automatically use the correct storage class value depending on + whether --s3-use-rrs and --s3-use-ia is set. Command line parser will + prompt error if both --s3-use-ia and --s3-use-rrs are used together, + as they conflict with each other. + * Upgrade to newest version of pep8 and pylint + * WindowsAzureMissingResourceError and WindowsAzureConflictError + changed due to SDK changes. + * make sure packages using python's tempfile create temp files in + duplicity's temp dir + * Fixed bug #1511308 - Cannot restore no-encryption, no-compression backup + * Fix missing SWIFT_ENDPOINT_TYPE env var, bug #1519694. + * Fix bug #1520691 - Shell Code Injection in hsi backend + * Support new version of Azure Storage SDK + * Adds a backend for BackBlaze's (currently beta) B2 backup service. + +------------------------------------------------------------------- +Mon Nov 23 10:53:35 UTC 2015 - w...@rosenauer.org + +- current FTP default is lftp; therefore recommend that instead + of ncftp + +------------------------------------------------------------------- Old: ---- duplicity-0.7.05.tar.gz New: ---- duplicity-0.7.06.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ duplicity.spec ++++++ --- /var/tmp/diff_new_pack.75ffJb/_old 2015-12-17 15:52:10.000000000 +0100 +++ /var/tmp/diff_new_pack.75ffJb/_new 2015-12-17 15:52:10.000000000 +0100 @@ -19,7 +19,7 @@ %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} %{!?python_sitearch: %global python_sitearch %(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} Name: duplicity -Version: 0.7.05 +Version: 0.7.06 Release: 0 Summary: Encrypted bandwidth-efficient backup using the rsync algorithm License: GPL-3.0+ @@ -31,7 +31,7 @@ BuildRequires: python-devel BuildRequires: python-setuptools Requires: gpg -Recommends: ncftp +Recommends: lftp Requires: python-lockfile Recommends: python-boto BuildRoot: %{_tmppath}/%{name}-%{version}-build ++++++ duplicity-0.7.05.tar.gz -> duplicity-0.7.06.tar.gz ++++++ ++++ 2334 lines of diff (skipped) ++++ retrying with extended exclude list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/.bzrignore new/duplicity-0.7.06/.bzrignore --- old/duplicity-0.7.05/.bzrignore 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/.bzrignore 2015-12-07 13:03:38.000000000 +0100 @@ -6,6 +6,7 @@ .pydevproject .settings .tox +__pycache__ build config.py duplicity.egg-info diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/CHANGELOG new/duplicity-0.7.06/CHANGELOG --- old/duplicity-0.7.05/CHANGELOG 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/CHANGELOG 2015-12-07 13:03:38.000000000 +0100 @@ -1,3 +1,60 @@ +New in v0.7.06 (2015/12/07) +--------------------------- +* Merged in lp:~mnjul/duplicity/s3-infreq-access + - This adds support for AWS S3's newly announced Infrequent Access + storage class and is intended to implement Blueprint: + https://blueprints.launchpad.net/duplicity/+spec/aws-s3-std-ia-class . + - A new command line option, --s3-use-ia, is added, and boto backend + will automatically use the correct storage class value depending on + whether --s3-use-rrs and --s3-use-ia is set. Command line parser will + prompt error if both --s3-use-ia and --s3-use-rrs are used together, + as they conflict with each other. + - The manpage has been updated giving a short explanation on the new + option. Its wording derives from Amazon's official announcement: + https://aws.amazon.com/about-aws/whats-new/2015/09/announcing-new-\ + amazon-s3-storage-class-and-lower-glacier-prices/ +* The ptyprocess module no longer supports Python 2.6, so fix tox.ini to + use an older version. Make explicit environs for all tests. +* Upgrade to newest version of pep8 and pylint. Add three ignores + to test_pep8 and one to test_pylint to get the rest to pass. They +* Applied patch from Alexander Zangerl to update to changes in lockfile + API 0.9 and later. Updated README to notify users. +* Modded tox.ini to use the latest lockfile. +* Merged in lp:~ed.so/duplicity/setup.shebang + - Having the python interpreter searched in the PATH is much more + flexible than the /usr/bin/python inserted into our scripts shebang + by setuptools. This patch prevents that. don't touch my shebang! :) +* Cleanup issues around Launchpad build, mainly lockfile >= 0.9. +* Merged in lp:~michal-s/duplicity/duplicity + - WindowsAzureMissingResourceError and WindowsAzureConflictError + changed due to SDK changes. + are all valid in our case. +* Reversed previous changes to lockfile. Now it will take any version + extant in the LP build repository. (PyPi is not avail in LP build). +* Merged in lp:~ed.so/duplicity/tempfile.tempdir + - make sure packages using python's tempfile create temp files in + duplicity's temp dir +* Fixed bug #1511308 - Cannot restore no-encryption, no-compression backup + - Corrected code to include plain file in write_multivolume() + - Added PlainWriteFile() to gpg.py +* Merged in lp:~michal-s/duplicity/duplicity + - Fix azurebackend storage class import +* Merged in lp:~feraudet/duplicity/fix + - Fix missing SWIFT_ENDPOINT_TYPE env var, bug 1519694. +* Fix bug #1520691 - Shell Code Injection in hsi backend + - Replace use of os.popen3() with subprocess equivalent. + - Added code to expand relative program path to full path. + - Fix hisbackend where it expected a list not a string. +* Merged in lp:~noizyland/duplicity/azurebackend-fixes + - Support new version of Azure Storage SDK + - Refactor _list method to support containers with >5000 blobs +* Merged in lp:~matthew-t-bentley/duplicity/b2 + - Adds a backed for BackBlaze's (currently beta) B2 backup service. + - This adds backends/b2backend.py, modifies log.py to add an + error code and modifies commandline.py to add the b2:// + example to the help text. + + New in v0.7.05 (2015/09/15) --------------------------- * Merged in lp:~aaron-whitehouse/duplicity/fix_patch_error diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/Changelog.GNU new/duplicity-0.7.06/Changelog.GNU --- old/duplicity-0.7.05/Changelog.GNU 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/Changelog.GNU 2015-12-07 13:03:38.000000000 +0100 @@ -1,3 +1,101 @@ +2015-12-07 Kenneth Loafman <kenn...@loafman.com> + + * Prep for 0.7.06 + +2015-12-04 Kenneth Loafman <kenn...@loafman.com> + + * Merged in lp:~matthew-t-bentley/duplicity/b2 + - Adds a backed for BackBlaze's (currently beta) B2 backup service. + - This adds backends/b2backend.py, modifies log.py to add an + error code and modifies commandline.py to add the b2:// + example to the help text. + +2015-12-03 Kenneth Loafman <kenn...@loafman.com> + + * Merged in lp:~noizyland/duplicity/azurebackend-fixes + - Support new version of Azure Storage SDK + - Refactor _list method to support containers with >5000 blobs + +2015-11-30 Kenneth Loafman <kenn...@loafman.com> + + * Fix bug #1520691 - Shell Code Injection in hsi backend (2) + - Added code to expand relative program path to full path. + - Fix hisbackend where it expected a list not a string. + +2015-11-28 Kenneth Loafman <kenn...@loafman.com> + + * Fix bug #1520691 - Shell Code Injection in hsi backend + - Replace use of os.popen3() with subprocess equivalent. + +2015-11-25 Kenneth Loafman <kenn...@loafman.com> + + * Merged in lp:~feraudet/duplicity/fix + - Fix missing SWIFT_ENDPOINT_TYPE env var, bug 1519694. + +2015-11-24 Kenneth Loafman <kenn...@loafman.com> + + * Merged in lp:~michal-s/duplicity/duplicity + - Fix azurebackend storage class import + +2015-11-05 Kenneth Loafman <kenn...@loafman.com> + + * Fixed bug #1511308 - Cannot restore no-encryption, no-compression backup + - Corrected code to include plain file in write_multivolume() + - Added PlainWriteFile() to gpg.py + +2015-11-01 Kenneth Loafman <kenn...@loafman.com> + + * Merged in lp:~ed.so/duplicity/tempfile.tempdir + - make sure packages using python's tempfile create temp files in + duplicity's temp dir + +2015-10-31 Kenneth Loafman <kenn...@loafman.com> + + * Reversed previous changes to lockfile. Now it will take any version + extant in the LP build repository. (PyPi is not avail in LP build). + +2015-10-27 Kenneth Loafman <kenn...@loafman.com> + + * Cleanup issues around Launchpad build, mainly lockfile >= 0.9. + * Merged in lp:~michal-s/duplicity/duplicity + - WindowsAzureMissingResourceError and WindowsAzureConflictError + changed due to SDK changes. + +2015-10-26 Kenneth Loafman <kenn...@loafman.com> + + * Applied patch from Alexander Zangerl to update to changes in lockfile + API 0.9 and later. Updated README to notify users. + * Modded tox.ini to use the latest lockfile. + * Merged in lp:~ed.so/duplicity/setup.shebang + - Having the python interpreter searched in the PATH is much more + flexible than the /usr/bin/python inserted into our scripts shebang + by setuptools. This patch prevents that. don't touch my shebang! :) + +2015-10-12 Kenneth Loafman <kenn...@loafman.com> + + * Upgrade to newest version of pep8 and pylint. Add three ignores + to test_pep8 and one to test_pylint to get the rest to pass. They + are all valid in our case. + +2015-10-10 Kenneth Loafman <kenn...@loafman.com> + + * Merged in lp:~mnjul/duplicity/s3-infreq-access + - This adds support for AWS S3's newly announced Infrequent Access + storage class and is intended to implement Blueprint: + https://blueprints.launchpad.net/duplicity/+spec/aws-s3-std-ia-class . + - A new command line option, --s3-use-ia, is added, and boto backend + will automatically use the correct storage class value depending on + whether --s3-use-rrs and --s3-use-ia is set. Command line parser will + prompt error if both --s3-use-ia and --s3-use-rrs are used together, + as they conflict with each other. + - The manpage has been updated giving a short explanation on the new + option. Its wording derives from Amazon's official announcement: + https://aws.amazon.com/about-aws/whats-new/2015/09/announcing-new-\ + amazon-s3-storage-class-and-lower-glacier-prices/ + * The ptyprocess module no longer supports Python 2.6, so fix tox.ini to + use an older version. Make explicit environs for all tests. + + 2015-09-15 Kenneth Loafman <kenn...@loafman.com> * Merged in lp:~duplicity-team/duplicity/po-updates diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/README new/duplicity-0.7.06/README --- old/duplicity-0.7.05/README 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/README 2015-12-07 13:03:38.000000000 +0100 @@ -22,7 +22,7 @@ * Python v2.6 or later * librsync v0.9.6 or later * GnuPG v1.x for encryption - * python-lockfile for concurrency locking + * python-lockfile v0.9 or later for concurrency locking * for scp/sftp -- python-paramiko and python-pycryptopp * for ftp -- lftp version 3.7.15 or later * Boto 2.0 or later for single-processing S3 or GCS access (default) @@ -74,4 +74,4 @@ or post to the mailing list at - http://mail.nongnu.org/mailman/listinfo/duplicity-talk/ \ No newline at end of file + http://mail.nongnu.org/mailman/listinfo/duplicity-talk/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/bin/duplicity new/duplicity-0.7.06/bin/duplicity --- old/duplicity-0.7.05/bin/duplicity 2015-09-15 16:32:52.000000000 +0200 +++ new/duplicity-0.7.06/bin/duplicity 2015-12-07 13:03:39.000000000 +0100 @@ -2,7 +2,7 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # # duplicity -- Encrypted bandwidth efficient backup -# Version 0.7.05 released September 15, 2015 +# Version 0.7.06 released December 07, 2015 # # Copyright 2002 Ben Escoto <b...@emerose.org> # Copyright 2007 Kenneth Loafman <kenn...@loafman.com> @@ -39,12 +39,15 @@ import re import threading from datetime import datetime -from lockfile import FileLock +try: + from lockfile import FileLock +except: + from lockfile import LockFile as FileLock -if '--pydevd' in sys.argv: +if '--pydevd' in sys.argv or os.getenv('PYDEVD', None): # The following is for starting remote debugging in Eclipse with Pydev. # Adjust the path to your location and version of Eclipse and Pydev. - pysrc = "/opt/eclipse/plugins/org.python.pydev_3.9.2.201502050007/pysrc" + pysrc = "/opt/liclipse/plugins/org.python.pydev_4.4.0.201510052047/pysrc" sys.path.append(pysrc) import pydevd # @UnresolvedImport pydevd.settrace() @@ -124,15 +127,15 @@ and (globals.gpg_profile.sign_key in globals.gpg_profile.recipients or globals.gpg_profile.sign_key in globals.gpg_profile.hidden_recipients) and 'PASSPHRASE' in os.environ): # noqa - log.Notice(_("Reuse configured PASSPHRASE as SIGN_PASSPHRASE")) - return os.environ['PASSPHRASE'] + log.Notice(_("Reuse configured PASSPHRASE as SIGN_PASSPHRASE")) + return os.environ['PASSPHRASE'] # if one encryption key is also the signing key assume that the passphrase is identical if (not for_signing and (globals.gpg_profile.sign_key in globals.gpg_profile.recipients or globals.gpg_profile.sign_key in globals.gpg_profile.hidden_recipients) and 'SIGN_PASSPHRASE' in os.environ): # noqa - log.Notice(_("Reuse configured SIGN_PASSPHRASE as PASSPHRASE")) - return os.environ['SIGN_PASSPHRASE'] + log.Notice(_("Reuse configured SIGN_PASSPHRASE as PASSPHRASE")) + return os.environ['SIGN_PASSPHRASE'] # Next, verify we need to ask the user @@ -419,10 +422,11 @@ # write volume if globals.encryption: - at_end = gpg.GPGWriteFile(tarblock_iter, tdp.name, - globals.gpg_profile, globals.volsize) - else: + at_end = gpg.GPGWriteFile(tarblock_iter, tdp.name, globals.gpg_profile, globals.volsize) + elif globals.compression: at_end = gpg.GzipWriteFile(tarblock_iter, tdp.name, globals.volsize) + else: + at_end = gpg.PlainWriteFile(tarblock_iter, tdp.name, globals.volsize) tdp.setdata() # Add volume information to manifest @@ -717,7 +721,7 @@ if not patchdir.Write_ROPaths(globals.local_path, restore_get_patched_rop_iter(col_stats)): if globals.restore_dir: - log.FatalError(_("%s not found in archive, no files restored.") + log.FatalError(_("%s not found in archive - no files restored.") % (util.ufn(globals.restore_dir)), log.ErrorCode.restore_dir_not_found) else: @@ -1082,6 +1086,7 @@ """ Data block to return from SrcIter """ + def __init__(self, data): self.data = data @@ -1089,6 +1094,7 @@ """ Iterate over source and return Block of data. """ + def __init__(self, fileobj): self.fileobj = fileobj @@ -1261,7 +1267,7 @@ log Python, duplicity, and system versions """ log.Log(u'=' * 80, verbosity) - log.Log(u"duplicity 0.7.05 (September 15, 2015)", verbosity) + log.Log(u"duplicity 0.7.06 (December 07, 2015)", verbosity) log.Log(u"Args: %s" % util.ufn(' '.join(sys.argv)), verbosity) log.Log(u' '.join(platform.uname()), verbosity) log.Log(u"%s %s" % (sys.executable or sys.platform, sys.version), verbosity) @@ -1273,6 +1279,7 @@ Class to aid in restart of inc or full backup. Instance in globals.restart if restart in progress. """ + def __init__(self, last_backup): self.type = None self.start_time = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/bin/duplicity.1 new/duplicity-0.7.06/bin/duplicity.1 --- old/duplicity-0.7.05/bin/duplicity.1 2015-09-15 16:32:52.000000000 +0200 +++ new/duplicity-0.7.06/bin/duplicity.1 2015-12-07 13:03:39.000000000 +0100 @@ -1,4 +1,4 @@ -.TH DUPLICITY 1 "September 15, 2015" "Version 0.7.05" "User Manuals" \" -*- nroff -*- +.TH DUPLICITY 1 "December 07, 2015" "Version 0.7.06" "User Manuals" \" -*- nroff -*- .\" disable justification (adjust text to left margin only) .\" command line examples stay readable through that .ad l @@ -726,6 +726,14 @@ Storage on S3. .TP +.BI "--s3-use-ia" +Store volumes using Standard - Infrequent Access when uploading to Amazon S3. +This storage class has a lower storage cost but a higher per-request cost, and +the storage cost is calculated against a 30-day storage minimum. According to +Amazon, this storage is ideal for long-term file storage, backups, and disaster +recovery. + +.TP .BI "--s3-use-multiprocessing" Allow multipart volumne uploads to S3 through multiprocessing. This option requires Python 2.6 and can be used to make uploads to S3 more efficient. @@ -1002,6 +1010,12 @@ .B "A NOTE ON AZURE ACCESS" .RE .PP +.BR "B2" +.PP +.RS +b2://account_id[:application_key]@bucket_name/[folder/] +.RE +.PP .BR "Cloud Files" " (Rackspace)" .PP .RS @@ -1457,8 +1471,8 @@ if /home/ben/1234567 existed. .SH A NOTE ON AZURE ACCESS -The Azure backend requires the Microsoft Azure SDK for Python to be installed -on the system. +The Azure backend requires the Microsoft Azure Storage SDK for Python to be +installed on the system. See .B REQUIREMENTS above. @@ -1952,8 +1966,8 @@ Some backends also require additional components (probably available as packages for your specific platform): .TP .BR "azure backend" " (Azure Blob Storage Service)" -.B Microsoft Azure SDK for Python -- https://github.com/Azure/azure-sdk-for-python +.B Microsoft Azure Storage SDK for Python +- https://pypi.python.org/pypi/azure-storage/ .TP .BR "boto backend" " (S3 Amazon Web Services, Google Cloud Storage)" .B boto version 2.0+ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/bin/rdiffdir new/duplicity-0.7.06/bin/rdiffdir --- old/duplicity-0.7.05/bin/rdiffdir 2015-09-15 16:32:52.000000000 +0200 +++ new/duplicity-0.7.06/bin/rdiffdir 2015-12-07 13:03:39.000000000 +0100 @@ -1,6 +1,6 @@ #!/usr/bin/env python2 # rdiffdir -- Extend rdiff functionality to directories -# Version 0.7.05 released September 15, 2015 +# Version 0.7.06 released December 07, 2015 # # Copyright 2002 Ben Escoto <b...@emerose.org> # Copyright 2007 Kenneth Loafman <kenn...@loafman.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/bin/rdiffdir.1 new/duplicity-0.7.06/bin/rdiffdir.1 --- old/duplicity-0.7.05/bin/rdiffdir.1 2015-09-15 16:32:52.000000000 +0200 +++ new/duplicity-0.7.06/bin/rdiffdir.1 2015-12-07 13:03:39.000000000 +0100 @@ -1,4 +1,4 @@ -.TH RDIFFDIR 1 "September 15, 2015" "Version 0.7.05" "User Manuals" \" -*- nroff -*- +.TH RDIFFDIR 1 "December 07, 2015" "Version 0.7.06" "User Manuals" \" -*- nroff -*- .\" disable justification (adjust text to left margin only) .\" command line examples stay readable through that .ad l diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/backend.py new/duplicity-0.7.06/duplicity/backend.py --- old/duplicity-0.7.05/duplicity/backend.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/backend.py 2015-12-07 13:03:38.000000000 +0100 @@ -452,8 +452,11 @@ Execute the given command line, interpreted as a shell command. Returns int Exitcode, string StdOut, string StdErr """ + import shlex from subprocess import Popen, PIPE - p = Popen(commandline, shell=True, stdout=PIPE, stderr=PIPE) + args = shlex.split(commandline) + args[0] = self.which(args[0]) + p = Popen(args, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() return p.returncode, stdout, stderr @@ -485,6 +488,28 @@ (private, result, stdout + '\n' + stderr)) return result, stdout, stderr + def which(self, program): + """ + Return absolute path for program name. + Returns None if program not found. + """ + + def is_exe(fpath): + return os.path.isfile(fpath) and os.path.isabs(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.getenv("PATH").split(os.pathsep): + path = path.strip('"') + exe_file = os.path.abspath(os.path.join(path, program)) + if is_exe(exe_file): + return exe_file + + return None + class BackendWrapper(object): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/backends/_boto_single.py new/duplicity-0.7.06/duplicity/backends/_boto_single.py --- old/duplicity-0.7.05/duplicity/backends/_boto_single.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/backends/_boto_single.py 2015-12-07 13:03:38.000000000 +0100 @@ -221,6 +221,8 @@ if globals.s3_use_rrs: storage_class = 'REDUCED_REDUNDANCY' + elif globals.s3_use_ia: + storage_class = 'STANDARD_IA' else: storage_class = 'STANDARD' log.Info("Uploading %s/%s to %s Storage" % (self.straight_url, remote_filename, storage_class)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/backends/azurebackend.py new/duplicity-0.7.06/duplicity/backends/azurebackend.py --- old/duplicity-0.7.05/duplicity/backends/azurebackend.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/backends/azurebackend.py 2015-12-07 13:03:38.000000000 +0100 @@ -33,29 +33,36 @@ def __init__(self, parsed_url): duplicity.backend.Backend.__init__(self, parsed_url) - # Import Microsoft Azure SDK for Python library. + # Import Microsoft Azure Storage SDK for Python library. try: import azure - from azure.storage import BlobService + import azure.storage + if hasattr(azure.storage, 'BlobService'): + # v0.11.1 and below + from azure.storage import BlobService + self.AzureMissingResourceError = azure.WindowsAzureMissingResourceError + self.AzureConflictError = azure.WindowsAzureConflictError + else: + # v1.0.0 and above + from azure.storage.blob import BlobService + self.AzureMissingResourceError = azure.common.AzureMissingResourceHttpError + self.AzureConflictError = azure.common.AzureConflictHttpError except ImportError: - raise BackendException('Azure backend requires Microsoft Azure SDK for Python ' - '(https://github.com/Azure/azure-sdk-for-python).') + raise BackendException('Azure backend requires Microsoft Azure Storage SDK for Python ' + '(https://pypi.python.org/pypi/azure-storage/).') if 'AZURE_ACCOUNT_NAME' not in os.environ: raise BackendException('AZURE_ACCOUNT_NAME environment variable not set.') - if 'AZURE_ACCOUNT_KEY' not in os.environ: raise BackendException('AZURE_ACCOUNT_KEY environment variable not set.') + self.blob_service = BlobService(account_name=os.environ['AZURE_ACCOUNT_NAME'], + account_key=os.environ['AZURE_ACCOUNT_KEY']) - account_name = os.environ['AZURE_ACCOUNT_NAME'] - account_key = os.environ['AZURE_ACCOUNT_KEY'] - self.WindowsAzureMissingResourceError = azure.WindowsAzureMissingResourceError - self.blob_service = BlobService(account_name=account_name, account_key=account_key) # TODO: validate container name self.container = parsed_url.path.lstrip('/') try: self.blob_service.create_container(self.container, fail_on_exist=True) - except azure.WindowsAzureConflictError: + except self.AzureConflictError: # Indicates that the resource could not be created because it already exists. pass except Exception as e: @@ -64,16 +71,23 @@ log.ErrorCode.connection_failed) def _put(self, source_path, remote_filename): - # http://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#upload-blob + # https://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#upload-a-blob-into-a-container self.blob_service.put_block_blob_from_path(self.container, remote_filename, source_path.name) def _get(self, remote_filename, local_path): - # http://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#download-blobs + # https://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#download-blobs self.blob_service.get_blob_to_path(self.container, remote_filename, local_path.name) def _list(self): - # http://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#list-blob - blobs = self.blob_service.list_blobs(self.container) + # https://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#list-the-blobs-in-a-container + blobs = [] + marker = None + while True: + batch = self.blob_service.list_blobs(self.container, marker=marker) + blobs.extend(batch) + if not batch.next_marker: + break + marker = batch.next_marker return [blob.name for blob in blobs] def _delete(self, filename): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/backends/b2backend.py new/duplicity-0.7.06/duplicity/backends/b2backend.py --- old/duplicity-0.7.05/duplicity/backends/b2backend.py 1970-01-01 01:00:00.000000000 +0100 +++ new/duplicity-0.7.06/duplicity/backends/b2backend.py 2015-12-07 13:03:38.000000000 +0100 @@ -0,0 +1,340 @@ +# +# Copyright (c) 2015 Matthew Bentley +# +# +# 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. + +import os +import hashlib + +import duplicity.backend +from duplicity.errors import BackendException, FatalBackendException + +import json +import urllib2 +import base64 + + +class B2Backend(duplicity.backend.Backend): + """ + Backend for BackBlaze's B2 storage service + """ + + def __init__(self, parsed_url): + """ + Authorize to B2 api and set up needed variables + """ + duplicity.backend.Backend.__init__(self, parsed_url) + + self.account_id = parsed_url.username + account_key = self.get_password() + + self.url_parts = [ + x for x in parsed_url.path.replace("@", "/").split('/') if x != '' + ] + if self.url_parts: + self.username = self.url_parts.pop(0) + self.bucket_name = self.url_parts.pop(0) + else: + raise BackendException("B2 requires a bucket name") + self.path = "/".join(self.url_parts) + + id_and_key = self.account_id + ":" + account_key + basic_auth_string = 'Basic ' + base64.b64encode(id_and_key) + headers = {'Authorization': basic_auth_string} + + request = urllib2.Request( + 'https://api.backblaze.com/b2api/v1/b2_authorize_account', + headers=headers + ) + + response = urllib2.urlopen(request) + response_data = json.loads(response.read()) + response.close() + + self.auth_token = response_data['authorizationToken'] + self.api_url = response_data['apiUrl'] + self.download_url = response_data['downloadUrl'] + + try: + self.find_or_create_bucket(self.bucket_name) + except urllib2.HTTPError: + raise FatalBackendException("Bucket cannot be created") + + def _get(self, remote_filename, local_path): + """ + Download remote_filename to local_path + """ + remote_filename = self.full_filename(remote_filename) + url = self.download_url + \ + '/file/' + self.bucket_name + '/' + \ + remote_filename + resp = self.get_or_post(url, None) + + to_file = open(local_path.name, 'wb') + to_file.write(resp) + to_file.close() + + def _put(self, source_path, remote_filename): + """ + Copy source_path to remote_filename + """ + self._delete(remote_filename) + digest = self.hex_sha1_of_file(source_path) + content_type = 'application/pgp-encrypted' + remote_filename = self.full_filename(remote_filename) + + info = self.get_upload_info(self.bucket_id) + url = info['uploadUrl'] + + headers = { + 'Authorization': info['authorizationToken'], + 'X-Bz-File-Name': remote_filename, + 'Content-Type': content_type, + 'X-Bz-Content-Sha1': digest, + 'Content-Length': str(os.path.getsize(source_path.name)), + } + data_file = source_path.open() + self.get_or_post(url, None, headers, data_file=data_file) + + def _list(self): + """ + List files on remote server + """ + endpoint = 'b2_list_file_names' + url = self.formatted_url(endpoint) + params = { + 'bucketId': self.bucket_id, + 'maxFileCount': 1000, + } + try: + resp = self.get_or_post(url, params) + except urllib2.HTTPError: + return [] + + files = [x['fileName'].split('/')[-1] for x in resp['files']] + + next_file = resp['nextFileName'] + while next_file: + params['startFileName'] = next_file + try: + resp = self.get_or_post(url, params) + except urllib2.HTTPError: + return files + + files += [x['fileName'].split('/')[-1] for x in resp['files']] + next_file = resp['nextFileName'] + + return files + + def _delete(self, filename): + """ + Delete filename from remote server + """ + endpoint = 'b2_delete_file_version' + url = self.formatted_url(endpoint) + fileid = self.get_file_id(filename) + if fileid is None: + return + filename = self.full_filename(filename) + params = {'fileName': filename, 'fileId': fileid} + try: + self.get_or_post(url, params) + except urllib2.HTTPError as e: + if e.code == 400: + return + else: + raise e + + def _query(self, filename): + """ + Get size info of filename + """ + info = self.get_file_info(filename) + if not info: + return {'size': -1} + + return {'size': info['size']} + + def _error_code(self, operation, e): + if isinstance(e, urllib2.HTTPError): + if e.code == 400: + return log.ErrorCode.bad_request + if e.code == 500: + return log.ErrorCode.backed_error + if e.code == 403: + return log.ErrorCode.backed_permission_denied + + def find_or_create_bucket(self, bucket_name): + """ + Find a bucket with name bucket_name and save its id. + If it doesn't exist, create it + """ + endpoint = 'b2_list_buckets' + url = self.formatted_url(endpoint) + + params = {'accountId': self.account_id} + resp = self.get_or_post(url, params) + + bucket_names = [x['bucketName'] for x in resp['buckets']] + + if bucket_name not in bucket_names: + self.create_bucket(bucket_name) + else: + self.bucket_id = { + x[ + 'bucketName' + ]: x['bucketId'] for x in resp['buckets'] + }[bucket_name] + + def create_bucket(self, bucket_name): + """ + Create a bucket with name bucket_name and save its id + """ + endpoint = 'b2_create_bucket' + url = self.formatted_url(endpoint) + params = { + 'accountId': self.account_id, + 'bucketName': bucket_name, + 'bucketType': 'allPrivate' + } + resp = self.get_or_post(url, params) + + self.bucket_id = resp['bucketId'] + + def formatted_url(self, endpoint): + """ + Return the full api endpoint from just the last part + """ + return '%s/b2api/v1/%s' % (self.api_url, endpoint) + + def get_upload_info(self, bucket_id): + """ + Get an upload url for a bucket + """ + endpoint = 'b2_get_upload_url' + url = self.formatted_url(endpoint) + return self.get_or_post(url, {'bucketId': bucket_id}) + + def get_or_post(self, url, data, headers=None, data_file=None): + """ + Sends the request, either get or post. + If data and data_file are None, send a get request. + data_file takes precedence over data. + If headers are not supplied, just send with an auth key + """ + if headers is None: + headers = {'Authorization': self.auth_token} + if data_file is not None: + data = data_file + else: + data = json.dumps(data) if data else None + + encoded_headers = dict( + (k, urllib2.quote(v.encode('utf-8'))) + for (k, v) in headers.iteritems() + ) + + with OpenUrl(url, data, encoded_headers) as resp: + out = resp.read() + try: + return json.loads(out) + except ValueError: + return out + + def get_file_info(self, filename): + """ + Get a file info from filename + """ + endpoint = 'b2_list_file_names' + url = self.formatted_url(endpoint) + filename = self.full_filename(filename) + params = { + 'bucketId': self.bucket_id, + 'maxFileCount': 1, + 'startFileName': filename, + } + resp = self.get_or_post(url, params) + + try: + return resp['files'][0] + except IndexError: + return None + except TypeError: + return None + + def get_file_id(self, filename): + """ + Get a file id form filename + """ + try: + return self.get_file_info(filename)['fileId'] + except IndexError: + return None + except TypeError: + return None + + def full_filename(self, filename): + if self.path: + return self.path + '/' + filename + else: + return filename + + @staticmethod + def hex_sha1_of_file(path): + """ + Calculate the sha1 of a file to upload + """ + f = path.open() + block_size = 1024 * 1024 + digest = hashlib.sha1() + while True: + data = f.read(block_size) + if len(data) == 0: + break + digest.update(data) + f.close() + return digest.hexdigest() + + +class OpenUrl(object): + """ + Context manager that handles an open urllib2.Request, and provides + the file-like object that is the response. + """ + + def __init__(self, url, data, headers): + self.url = url + self.data = data + self.headers = headers + self.file = None + + def __enter__(self): + request = urllib2.Request(self.url, self.data, self.headers) + self.file = urllib2.urlopen(request) + return self.file + + def __exit__(self, exception_type, exception, traceback): + if self.file is not None: + self.file.close() + + +duplicity.backend.register_backend("b2", B2Backend) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/backends/hsibackend.py new/duplicity-0.7.06/duplicity/backends/hsibackend.py --- old/duplicity-0.7.05/duplicity/backends/hsibackend.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/backends/hsibackend.py 2015-12-07 13:03:38.000000000 +0100 @@ -44,10 +44,13 @@ self.subprocess_popen(commandline) def _list(self): + import sys commandline = '%s "ls -l %s"' % (hsi_command, self.remote_dir) - l = os.popen3(commandline)[2].readlines()[3:] + l = self.subprocess_popen(commandline)[2] + l = l.split(os.linesep)[3:] for i in range(0, len(l)): - l[i] = l[i].split()[-1] + if l[i]: + l[i] = l[i].split()[-1] return [x for x in l if x] def _delete(self, filename): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/backends/swiftbackend.py new/duplicity-0.7.06/duplicity/backends/swiftbackend.py --- old/duplicity-0.7.05/duplicity/backends/swiftbackend.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/backends/swiftbackend.py 2015-12-07 13:03:38.000000000 +0100 @@ -78,6 +78,8 @@ os_options.update({'project_domain_id': os.environ['SWIFT_PROJECT_DOMAIN_ID']}) if 'SWIFT_TENANTNAME' in os.environ: os_options.update({'tenant_name': os.environ['SWIFT_TENANTNAME']}) + if 'SWIFT_ENDPOINT_TYPE' in os.environ: + os_options.update({'endpoint_type': os.environ['SWIFT_ENDPOINT_TYPE']}) else: conn_kwargs['auth_version'] = '1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/commandline.py new/duplicity-0.7.06/duplicity/commandline.py --- old/duplicity-0.7.05/duplicity/commandline.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/commandline.py 2015-12-07 13:03:38.000000000 +0100 @@ -523,6 +523,9 @@ # Whether to use S3 Reduced Redudancy Storage parser.add_option("--s3-use-rrs", action="store_true") + # Whether to use S3 Infrequent Access Storage + parser.add_option("--s3-use-ia", action="store_true") + # Whether to use "new-style" subdomain addressing for S3 buckets. Such # use is not backwards-compatible with upper-case buckets, or buckets # that are otherwise not expressable in a valid hostname. @@ -864,7 +867,14 @@ # TRANSL: Used in usage help to represent a user name (i.e. login). # Example: # ftp://user[:password]@other.host[:port]/some_dir - 'user': _("user") + 'user': _("user"), + + # TRANSL: account id for b2. Example: b2://account_id@bucket/ + 'account_id': _("account_id"), + + # TRANSL: application_key for b2. + # Example: b2://account_id:application_key@bucket/ + 'application_key': _("application_key"), } # TRANSL: Header in usage help @@ -907,6 +917,7 @@ dpbx:///%(some_dir)s onedrive://%(some_dir)s azure://%(container_name)s + b2://%(account_id)s[:%(application_key)s]@%(bucket_name)s/[%(some_dir)s/] """ % dict @@ -1057,6 +1068,8 @@ if globals.restore_dir: command_line_error("restore option incompatible with %s backup" % (action,)) + if globals.s3_use_rrs and globals.s3_use_ia: + command_line_error("--s3-use-rrs and --s3-use-ia cannot be used together") def ProcessCommandLine(cmdline_list): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/globals.py new/duplicity-0.7.06/duplicity/globals.py --- old/duplicity-0.7.05/duplicity/globals.py 2015-09-15 16:32:52.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/globals.py 2015-12-07 13:03:39.000000000 +0100 @@ -26,7 +26,7 @@ # The current version of duplicity -version = "0.7.05" +version = "0.7.06" # Prefix for all files (appended before type-specific prefixes) file_prefix = "" @@ -191,6 +191,9 @@ # Whether to use S3 Reduced Redudancy Storage s3_use_rrs = False +# Whether to use S3 Infrequent Access Storage +s3_use_ia = False + # True if we should use boto multiprocessing version s3_use_multiprocessing = False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/gpg.py new/duplicity-0.7.06/duplicity/gpg.py --- old/duplicity-0.7.05/duplicity/gpg.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/gpg.py 2015-12-07 13:03:38.000000000 +0100 @@ -357,9 +357,7 @@ raise -def GzipWriteFile(block_iter, filename, - size=200 * 1024 * 1024, - max_footer_size=16 * 1024): +def GzipWriteFile(block_iter, filename, size=200 * 1024 * 1024, gzipped=True): """ Write gzipped compressed file of given size @@ -388,7 +386,12 @@ return self.fileobj.close() file_counted = FileCounted(open(filename, "wb")) - gzip_file = gzip.GzipFile(None, "wb", 6, file_counted) + + # if gzipped wrap with GzipFile else plain file out + if gzipped: + outfile = gzip.GzipFile(None, "wb", 6, file_counted) + else: + outfile = file_counted at_end_of_blockiter = 0 while True: bytes_to_go = size - file_counted.byte_count @@ -399,12 +402,27 @@ except StopIteration: at_end_of_blockiter = 1 break - gzip_file.write(new_block.data) + outfile.write(new_block.data) - assert not gzip_file.close() and not file_counted.close() + assert not outfile.close() and not file_counted.close() return at_end_of_blockiter +def PlainWriteFile(block_iter, filename, size=200 * 1024 * 1024, gzipped=False): + """ + Write plain uncompressed file of given size + + This is like the earlier GPGWriteFile except it writes a gzipped + file instead of a gpg'd file. This function is somewhat out of + place, because it doesn't deal with GPG at all, but it is very + similar to GPGWriteFile so they might as well be defined together. + + The input requirements on block_iter and the output is the same as + GPGWriteFile (returns true if wrote until end of block_iter). + """ + return GzipWriteFile(block_iter, filename, size, gzipped) + + def get_hash(hash, path, hex=1): """ Return hash of path diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/log.py new/duplicity-0.7.06/duplicity/log.py --- old/duplicity-0.7.05/duplicity/log.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/log.py 2015-12-07 13:03:38.000000000 +0100 @@ -307,6 +307,8 @@ dpbx_nologin = 47 + bad_request = 48 + # 50->69 reserved for backend errors backend_error = 50 backend_permission_denied = 51 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/tempdir.py new/duplicity-0.7.06/duplicity/tempdir.py --- old/duplicity-0.7.05/duplicity/tempdir.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/tempdir.py 2015-12-07 13:03:38.000000000 +0100 @@ -38,6 +38,9 @@ # instance _defaultLock = threading.Lock() _defaultInstance = None +# backup the initial tmp dir path because we will force tempfile +# later to use our generated _defaultInstance.dir() as temproot +_initialSystemTempRoot = tempfile.gettempdir() def default(): @@ -57,6 +60,8 @@ try: if _defaultInstance is None or _defaultInstance.dir() is None: _defaultInstance = TemporaryDirectory(temproot=globals.temproot) + # set the temp dir to be the default in tempfile module from now on + tempfile.tempdir = _defaultInstance.dir() return _defaultInstance finally: _defaultLock.release() @@ -116,6 +121,12 @@ tempbase - The temp root directory, or None to use system default (recommended). """ + if temproot is None: + if globals.temproot: + temproot = globals.temproot + else: + global _initialSystemTempRoot + temproot = _initialSystemTempRoot self.__dir = tempfile.mkdtemp("-tempdir", "duplicity-", temproot) log.Info(_("Using temporary directory %s") % util.ufn(self.__dir)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/duplicity/util.py new/duplicity-0.7.06/duplicity/util.py --- old/duplicity-0.7.05/duplicity/util.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/duplicity/util.py 2015-12-07 13:03:38.000000000 +0100 @@ -31,7 +31,7 @@ import string import traceback -from lockfile import FileLock, UnlockError +from lockfile import UnlockError from duplicity import tarfile @@ -104,6 +104,7 @@ class BlackHoleList(list): + def append(self, x): pass @@ -161,7 +162,7 @@ def release_lockfile(): if globals.lockfile and globals.lockfile.is_locked(): - log.Debug(_("Releasing lockfile %s") % globals.lockfile) + log.Debug(_("Releasing lockfile %s") % globals.lockfile.lock_file) try: globals.lockfile.release() except UnlockError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/po/POTFILES.in new/duplicity-0.7.06/po/POTFILES.in --- old/duplicity-0.7.05/po/POTFILES.in 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/po/POTFILES.in 2015-12-07 13:03:38.000000000 +0100 @@ -15,38 +15,40 @@ duplicity/robust.py duplicity/diffdir.py duplicity/lazy.py -duplicity/backends/copycombackend.py -duplicity/backends/_cf_pyrax.py -duplicity/backends/ssh_paramiko_backend.py -duplicity/backends/localbackend.py -duplicity/backends/imapbackend.py -duplicity/backends/pydrivebackend.py duplicity/backends/azurebackend.py -duplicity/backends/par2backend.py +duplicity/backends/b2backend.py duplicity/backends/botobackend.py -duplicity/backends/swiftbackend.py -duplicity/backends/hubicbackend.py -duplicity/backends/_boto_single.py -duplicity/backends/multibackend.py duplicity/backends/_boto_multi.py -duplicity/backends/__init__.py +duplicity/backends/_boto_single.py +duplicity/backends/cfbackend.py +duplicity/backends/_cf_cloudfiles.py +duplicity/backends/_cf_pyrax.py +duplicity/backends/copycombackend.py duplicity/backends/dpbxbackend.py +duplicity/backends/gdocsbackend.py +duplicity/backends/giobackend.py duplicity/backends/hsibackend.py -duplicity/backends/tahoebackend.py -duplicity/backends/sxbackend.py -duplicity/backends/rsyncbackend.py -duplicity/backends/ssh_pexpect_backend.py -duplicity/backends/_cf_cloudfiles.py -duplicity/backends/onedrivebackend.py -duplicity/backends/ncftpbackend.py +duplicity/backends/hubicbackend.py +duplicity/backends/imapbackend.py +duplicity/backends/__init__.py duplicity/backends/lftpbackend.py -duplicity/backends/gdocsbackend.py +duplicity/backends/localbackend.py duplicity/backends/megabackend.py -duplicity/backends/giobackend.py +duplicity/backends/multibackend.py +duplicity/backends/ncftpbackend.py +duplicity/backends/onedrivebackend.py +duplicity/backends/par2backend.py +duplicity/backends/pydrivebackend.py +duplicity/backends/README +duplicity/backends/rsyncbackend.py +duplicity/backends/ssh_paramiko_backend.py +duplicity/backends/ssh_pexpect_backend.py +duplicity/backends/swiftbackend.py +duplicity/backends/sxbackend.py +duplicity/backends/tahoebackend.py duplicity/backends/webdavbackend.py duplicity/backends/pyrax_identity/hubic.py duplicity/backends/pyrax_identity/__init__.py -duplicity/backends/cfbackend.py duplicity/__init__.py duplicity/librsync.py duplicity/errors.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/setup.py new/duplicity-0.7.06/setup.py --- old/duplicity-0.7.05/setup.py 2015-09-15 16:32:52.000000000 +0200 +++ new/duplicity-0.7.06/setup.py 2015-12-07 13:03:39.000000000 +0100 @@ -26,11 +26,12 @@ from setuptools.command.test import test from setuptools.command.install import install from setuptools.command.sdist import sdist +from distutils.command.build_scripts import build_scripts -version_string = "0.7.05" +version_string = "0.7.06" -if sys.version_info[:2] < (2, 6): - print("Sorry, duplicity requires version 2.6 or later of python") +if sys.version_info[:2] < (2, 6) or sys.version_info[:2] > (2, 7): + print("Sorry, duplicity requires version 2.6 or 2.7 of python.") sys.exit(1) incdir_list = libdir_list = None @@ -70,6 +71,7 @@ class TestCommand(test): + def run(self): # Make sure all modules are ready build_cmd = self.get_finalized_command("build_py") @@ -96,6 +98,7 @@ class InstallCommand(install): + def run(self): # Normally, install will call build(). But we want to delete the # testing dir between building and installing. So we manually build @@ -113,6 +116,7 @@ # TODO: move logic from dist/makedist inline class SDistCommand(sdist): + def run(self): version = version_string if version[0] == '$': @@ -123,6 +127,47 @@ os.system("mv duplicity-" + version + ".tar.gz " + self.dist_dir) +# don't touch my shebang +class BSCommand (build_scripts): + + def run(self): + """ + Copy, chmod each script listed in 'self.scripts' + essentially this is the stripped + distutils.command.build_scripts.copy_scripts() + routine + """ + from stat import ST_MODE + from distutils.dep_util import newer + from distutils import log + + self.mkpath(self.build_dir) + outfiles = [] + for script in self.scripts: + outfile = os.path.join(self.build_dir, os.path.basename(script)) + outfiles.append(outfile) + + if not self.force and not newer(script, outfile): + log.debug("not copying %s (up-to-date)", script) + continue + + log.info("copying and NOT adjusting %s -> %s", script, + self.build_dir) + self.copy_file(script, outfile) + + if os.name == 'posix': + for file in outfiles: + if self.dry_run: + log.info("changing mode of %s", file) + else: + oldmode = os.stat(file)[ST_MODE] & 0o7777 + newmode = (oldmode | 0o555) & 0o7777 + if newmode != oldmode: + log.info("changing mode of %s from %o to %o", + file, oldmode, newmode) + os.chmod(file, newmode) + + setup(name="duplicity", version=version_string, description="Encrypted backup using rsync algorithm", @@ -132,12 +177,12 @@ maintainer_email="kenn...@loafman.com", url="http://duplicity.nongnu.org/index.html", packages=['duplicity', - 'duplicity.backends', - 'duplicity.backends.pyrax_identity', - 'testing', - 'testing.functional', - 'testing.overrides', - 'testing.unit'], + 'duplicity.backends', + 'duplicity.backends.pyrax_identity', + 'testing', + 'testing.functional', + 'testing.overrides', + 'testing.unit'], package_dir={"duplicity": "duplicity", "duplicity.backends": "duplicity/backends", }, ext_modules=[Extension("duplicity._librsync", @@ -147,9 +192,11 @@ libraries=["rsync"])], scripts=['bin/rdiffdir', 'bin/duplicity'], data_files=data_files, + install_requires=['lockfile'], tests_require=['lockfile', 'mock', 'pexpect'], test_suite='testing', cmdclass={'test': TestCommand, 'install': InstallCommand, - 'sdist': SDistCommand}, + 'sdist': SDistCommand, + 'build_scripts': BSCommand}, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/testing/functional/__init__.py new/duplicity-0.7.06/testing/functional/__init__.py --- old/duplicity-0.7.05/testing/functional/__init__.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/testing/functional/__init__.py 2015-12-07 13:03:38.000000000 +0100 @@ -85,8 +85,10 @@ child.wait() return_val = child.exitstatus - # output = child.read() - # print "Ran duplicity command: ", cmdline, "\n with return_val: ", return_val, "\n and output:\n", output +# output = child.read() +# print "\nduplicity command: ", cmdline, \ +# "\n with return_val: ", return_val, \ +# "\n and output:\n", output if fail: self.assertEqual(30, return_val) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/testing/test_code.py new/duplicity-0.7.06/testing/test_code.py --- old/duplicity-0.7.05/testing/test_code.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/testing/test_code.py 2015-12-07 13:03:38.000000000 +0100 @@ -75,7 +75,9 @@ "--disable=E0611", # No name in module "--disable=E1101", # Has no member "--disable=E1103", # Maybe has no member + "--disable=E0712", # Catching an exception which doesn't inherit from BaseException "--ignore=_librsync.so", + "--msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'", os.path.join(_top_dir, 'duplicity'), os.path.join(_top_dir, 'bin/duplicity'), os.path.join(_top_dir, 'bin/rdiffdir')], @@ -87,7 +89,10 @@ 'Must set environment var RUN_CODE_TESTS=1') def test_pep8(self): ignores = [ - "E501", + "E402", # module level import not at top of file + "E501", # line too long + "E731", # do not assign a lambda expression, use a def + "W503", # line break before binary operator ] self.run_checker(["pep8", "--ignore=" + ','.join(ignores), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/testing/unit/test_tempdir.py new/duplicity-0.7.06/testing/unit/test_tempdir.py --- old/duplicity-0.7.05/testing/unit/test_tempdir.py 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/testing/unit/test_tempdir.py 2015-12-07 13:03:38.000000000 +0100 @@ -20,6 +20,7 @@ # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os +import tempfile import unittest from duplicity import tempdir @@ -30,20 +31,57 @@ def test_all(self): td = tempdir.default() + # are generated temp files unique? self.assertTrue(td.mktemp() != td.mktemp()) + # create and remove a temp dir dir = td.mktemp() os.mkdir(dir) os.rmdir(dir) + # test mkstemp() fd, fname = td.mkstemp() os.close(fd) os.unlink(fname) td.forget(fname) + # test mkstemp_file() fo, fname = td.mkstemp_file() fo.close() # don't forget, leave to cleanup() + # cleanup + td.cleanup() + + def test_dirname(self): + """ + test if we generated a dirname + """ + td = tempdir.default() + dirname = td.dir() + self.assertTrue( dirname is not None ) + + """ + test if duplicity's temp files are created in our temp dir + """ + f1d, f1_name = tempdir.default().mkstemp() + f1_dirname = os.path.dirname( f1_name ) + + self.assertTrue( dirname == f1_dirname ) + + """ + test if tempfile creates in our temp dir now as well by default + """ + f2 = tempfile.NamedTemporaryFile() + f2_dirname = os.path.dirname( f2.name ) + + self.assertTrue( dirname == f2_dirname ) + + # cleanup + os.close(f1d) + os.unlink(f1_name) + td.forget(f1_name) + f2.close() + td.cleanup() if __name__ == "__main__": diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/duplicity-0.7.05/tox.ini new/duplicity-0.7.06/tox.ini --- old/duplicity-0.7.05/tox.ini 2015-09-15 16:32:51.000000000 +0200 +++ new/duplicity-0.7.06/tox.ini 2015-12-07 13:03:38.000000000 +0100 @@ -1,24 +1,32 @@ [tox] envlist=py26,py27,lpbuildd-precise -[testenv:lpbuildd-precise] +[testenv:py26] setenv= - RUN_CODE_TESTS=0 + RUN_CODE_TESTS=0 deps= - lockfile==0.8 mock==0.7.2 pexpect==2.4 - python==2.7 + ptyprocess==0.4 + python==2.6 + unittest2 -[testenv] +[testenv:py27] setenv= - RUN_CODE_TESTS=1 + RUN_CODE_TESTS=1 deps= - lockfile mock pexpect - unittest2 -# conditional deps (py26: unittest2) don't work on tox <1.8 + python==2.7 + +[testenv:lpbuildd-precise] +setenv= + RUN_CODE_TESTS=0 +deps= + mock==0.7.2 + pexpect==2.4 + python==2.7 +[testenv] commands= - {envpython} {toxinidir}/setup.py test {posargs} + {envpython} {toxinidir}/setup.py test {posargs}