Browse Source

implement --timestamp, iso8601-like utc timestamp string or reference file/dir

note: this needs bug #282 to be fixed first, because it will create timestamps with microseconds==0.
Thomas Waldmann 10 years ago
parent
commit
0ffee1f1ee
3 changed files with 31 additions and 4 deletions
  1. 4 2
      attic/archive.py
  2. 7 2
      attic/archiver.py
  3. 20 0
      attic/helpers.py

+ 4 - 2
attic/archive.py

@@ -184,11 +184,13 @@ class Archive:
         del self.manifest.archives[self.checkpoint_name]
         self.cache.chunk_decref(self.id, self.stats)
 
-    def save(self, name=None):
+    def save(self, name=None, timestamp=None):
         name = name or self.name
         if name in self.manifest.archives:
             raise self.AlreadyExists(name)
         self.items_buffer.flush(flush=True)
+        if timestamp is None:
+            timestamp = datetime.utcnow()
         metadata = StableDict({
             'version': 1,
             'name': name,
@@ -196,7 +198,7 @@ class Archive:
             'cmdline': sys.argv,
             'hostname': socket.gethostname(),
             'username': getuser(),
-            'time': datetime.utcnow().isoformat(),
+            'time': timestamp.isoformat(),
         })
         data = msgpack.packb(metadata, unicode_errors='surrogateescape')
         self.id = self.key.id_hash(data)

+ 7 - 2
attic/archiver.py

@@ -15,7 +15,7 @@ from attic.repository import Repository
 from attic.cache import Cache
 from attic.key import key_creator
 from attic.helpers import Error, location_validator, format_time, \
-    format_file_mode, ExcludePattern, exclude_path, adjust_patterns, to_localtime, \
+    format_file_mode, ExcludePattern, exclude_path, adjust_patterns, to_localtime, timestamp, \
     get_cache_dir, get_keys_dir, format_timedelta, prune_within, prune_split, \
     Manifest, remove_surrogates, update_excludes, format_archive, check_extension_modules, Statistics, \
     is_cachedir, bigint_to_int
@@ -127,7 +127,7 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
             else:
                 restrict_dev = None
             self._process(archive, cache, args.excludes, args.exclude_caches, skip_inodes, path, restrict_dev)
-        archive.save()
+        archive.save(timestamp=args.timestamp)
         if args.stats:
             t = datetime.now()
             diff = t - t0
@@ -551,6 +551,11 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
         subparser.add_argument('--numeric-owner', dest='numeric_owner',
                                action='store_true', default=False,
                                help='only store numeric user and group identifiers')
+        subparser.add_argument('--timestamp', dest='timestamp',
+                               type=timestamp, default=None,
+                               metavar='yyyy-mm-ddThh:mm:ss',
+                               help='manually specify the archive creation date/time (UTC). '
+                                    'alternatively, give a reference file/directory.')
         subparser.add_argument('archive', metavar='ARCHIVE',
                                type=location_validator(archive=True),
                                help='archive to create')

+ 20 - 0
attic/helpers.py

@@ -257,6 +257,26 @@ class ExcludePattern(IncludePattern):
         return '%s(%s)' % (type(self), self.pattern)
 
 
+def timestamp(s):
+    """Convert a --timestamp=s argument to a datetime object"""
+    try:
+        # is it pointing to a file / directory?
+        ts = os.stat(s).st_mtime
+        return datetime.utcfromtimestamp(ts)
+    except OSError:
+        # didn't work, try parsing as timestamp. UTC, no TZ, no microsecs support.
+        for format in ('%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S+00:00',
+                       '%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S',
+                       '%Y-%m-%dT%H:%M', '%Y-%m-%d %H:%M',
+                       '%Y-%m-%d', '%Y-%j',
+                       ):
+            try:
+                return datetime.strptime(s, format)
+            except ValueError:
+                continue
+        raise ValueError
+
+
 def is_cachedir(path):
     """Determines whether the specified path is a cache directory (and
     therefore should potentially be excluded from the backup) according to