2
0
Эх сурвалжийг харах

Merge pull request #192 from ThomasWaldmann/create-dryrun

implement borg create --dry-run, attic issue #267
TW 9 жил өмнө
parent
commit
da5923ec04

+ 1 - 0
borg/archive.py

@@ -455,6 +455,7 @@ class Archive:
             b'mtime': int_to_bigint(int(time.time()) * 1000000000)
             b'mtime': int_to_bigint(int(time.time()) * 1000000000)
         }
         }
         self.add_item(item)
         self.add_item(item)
+        return 'i'  # stdin
 
 
     def process_file(self, path, st, cache):
     def process_file(self, path, st, cache):
         status = None
         status = None

+ 62 - 42
borg/archiver.py

@@ -102,17 +102,21 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
 
 
     def do_create(self, args):
     def do_create(self, args):
         """Create new archive"""
         """Create new archive"""
+        dry_run = args.dry_run
         t0 = datetime.now()
         t0 = datetime.now()
-        repository = self.open_repository(args.archive, exclusive=True)
-        manifest, key = Manifest.load(repository)
-        compr_args = dict(buffer=COMPR_BUFFER)
-        compr_args.update(args.compression)
-        key.compressor = Compressor(**compr_args)
-        cache = Cache(repository, key, manifest, do_files=args.cache_files)
-        archive = Archive(repository, key, manifest, args.archive.archive, cache=cache,
-                          create=True, checkpoint_interval=args.checkpoint_interval,
-                          numeric_owner=args.numeric_owner, progress=args.progress,
-                          chunker_params=args.chunker_params)
+        if not dry_run:
+            repository = self.open_repository(args.archive, exclusive=True)
+            manifest, key = Manifest.load(repository)
+            compr_args = dict(buffer=COMPR_BUFFER)
+            compr_args.update(args.compression)
+            key.compressor = Compressor(**compr_args)
+            cache = Cache(repository, key, manifest, do_files=args.cache_files)
+            archive = Archive(repository, key, manifest, args.archive.archive, cache=cache,
+                              create=True, checkpoint_interval=args.checkpoint_interval,
+                              numeric_owner=args.numeric_owner, progress=args.progress,
+                              chunker_params=args.chunker_params)
+        else:
+            archive = cache = None
         # Add cache dir to inode_skip list
         # Add cache dir to inode_skip list
         skip_inodes = set()
         skip_inodes = set()
         try:
         try:
@@ -130,11 +134,14 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
         for path in args.paths:
         for path in args.paths:
             if path == '-':  # stdin
             if path == '-':  # stdin
                 path = 'stdin'
                 path = 'stdin'
-                self.print_verbose(path)
-                try:
-                    archive.process_stdin(path, cache)
-                except IOError as e:
-                    self.print_error('%s: %s', path, e)
+                if not dry_run:
+                    try:
+                        status = archive.process_stdin(path, cache)
+                    except IOError as e:
+                        self.print_error('%s: %s', path, e)
+                else:
+                    status = '-'
+                self.print_verbose("%1s %s", status, path)
                 continue
                 continue
             path = os.path.normpath(path)
             path = os.path.normpath(path)
             if args.dontcross:
             if args.dontcross:
@@ -146,26 +153,27 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
             else:
             else:
                 restrict_dev = None
                 restrict_dev = None
             self._process(archive, cache, args.excludes, args.exclude_caches, skip_inodes, path, restrict_dev,
             self._process(archive, cache, args.excludes, args.exclude_caches, skip_inodes, path, restrict_dev,
-                          read_special=args.read_special)
-        archive.save(timestamp=args.timestamp)
-        if args.progress:
-            archive.stats.show_progress(final=True)
-        if args.stats:
-            t = datetime.now()
-            diff = t - t0
-            print('-' * 78)
-            print('Archive name: %s' % args.archive.archive)
-            print('Archive fingerprint: %s' % hexlify(archive.id).decode('ascii'))
-            print('Start time: %s' % t0.strftime('%c'))
-            print('End time: %s' % t.strftime('%c'))
-            print('Duration: %s' % format_timedelta(diff))
-            print('Number of files: %d' % archive.stats.nfiles)
-            archive.stats.print_('This archive:', cache)
-            print('-' * 78)
+                          read_special=args.read_special, dry_run=dry_run)
+        if not dry_run:
+            archive.save(timestamp=args.timestamp)
+            if args.progress:
+                archive.stats.show_progress(final=True)
+            if args.stats:
+                t = datetime.now()
+                diff = t - t0
+                print('-' * 78)
+                print('Archive name: %s' % args.archive.archive)
+                print('Archive fingerprint: %s' % hexlify(archive.id).decode('ascii'))
+                print('Start time: %s' % t0.strftime('%c'))
+                print('End time: %s' % t.strftime('%c'))
+                print('Duration: %s' % format_timedelta(diff))
+                print('Number of files: %d' % archive.stats.nfiles)
+                archive.stats.print_('This archive:', cache)
+                print('-' * 78)
         return self.exit_code
         return self.exit_code
 
 
     def _process(self, archive, cache, excludes, exclude_caches, skip_inodes, path, restrict_dev,
     def _process(self, archive, cache, excludes, exclude_caches, skip_inodes, path, restrict_dev,
-                 read_special=False):
+                 read_special=False, dry_run=False):
         if exclude_path(path, excludes):
         if exclude_path(path, excludes):
             return
             return
         try:
         try:
@@ -184,14 +192,16 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
             return
             return
         if (stat.S_ISREG(st.st_mode) or
         if (stat.S_ISREG(st.st_mode) or
             read_special and not stat.S_ISDIR(st.st_mode)):
             read_special and not stat.S_ISDIR(st.st_mode)):
-            try:
-                status = archive.process_file(path, st, cache)
-            except IOError as e:
-                self.print_error('%s: %s', path, e)
+            if not dry_run:
+                try:
+                    status = archive.process_file(path, st, cache)
+                except IOError as e:
+                    self.print_error('%s: %s', path, e)
         elif stat.S_ISDIR(st.st_mode):
         elif stat.S_ISDIR(st.st_mode):
             if exclude_caches and is_cachedir(path):
             if exclude_caches and is_cachedir(path):
                 return
                 return
-            status = archive.process_dir(path, st)
+            if not dry_run:
+                status = archive.process_dir(path, st)
             try:
             try:
                 entries = os.listdir(path)
                 entries = os.listdir(path)
             except OSError as e:
             except OSError as e:
@@ -200,13 +210,17 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
                 for filename in sorted(entries):
                 for filename in sorted(entries):
                     entry_path = os.path.normpath(os.path.join(path, filename))
                     entry_path = os.path.normpath(os.path.join(path, filename))
                     self._process(archive, cache, excludes, exclude_caches, skip_inodes,
                     self._process(archive, cache, excludes, exclude_caches, skip_inodes,
-                                  entry_path, restrict_dev, read_special=read_special)
+                                  entry_path, restrict_dev, read_special=read_special,
+                                  dry_run=dry_run)
         elif stat.S_ISLNK(st.st_mode):
         elif stat.S_ISLNK(st.st_mode):
-            status = archive.process_symlink(path, st)
+            if not dry_run:
+                status = archive.process_symlink(path, st)
         elif stat.S_ISFIFO(st.st_mode):
         elif stat.S_ISFIFO(st.st_mode):
-            status = archive.process_fifo(path, st)
+            if not dry_run:
+                status = archive.process_fifo(path, st)
         elif stat.S_ISCHR(st.st_mode) or stat.S_ISBLK(st.st_mode):
         elif stat.S_ISCHR(st.st_mode) or stat.S_ISBLK(st.st_mode):
-            status = archive.process_dev(path, st)
+            if not dry_run:
+                status = archive.process_dev(path, st)
         elif stat.S_ISSOCK(st.st_mode):
         elif stat.S_ISSOCK(st.st_mode):
             # Ignore unix sockets
             # Ignore unix sockets
             return
             return
@@ -222,7 +236,10 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
         # Note: A/M/U is relative to the "files" cache, not to the repo.
         # Note: A/M/U is relative to the "files" cache, not to the repo.
         # This would be an issue if the files cache is not used.
         # This would be an issue if the files cache is not used.
         if status is None:
         if status is None:
-            status = '?'  # need to add a status code somewhere
+            if not dry_run:
+                status = '?'  # need to add a status code somewhere
+            else:
+                status = '-'  # dry run, item was not backed up
         # output ALL the stuff - it can be easily filtered using grep.
         # output ALL the stuff - it can be easily filtered using grep.
         # even stuff considered unchanged might be interesting.
         # even stuff considered unchanged might be interesting.
         self.print_verbose("%1s %s", status, remove_surrogates(path))
         self.print_verbose("%1s %s", status, remove_surrogates(path))
@@ -694,6 +711,9 @@ Type "Yes I am sure" if you understand this and want to continue.\n""")
         subparser.add_argument('--read-special', dest='read_special',
         subparser.add_argument('--read-special', dest='read_special',
                                action='store_true', default=False,
                                action='store_true', default=False,
                                help='open and read special files as if they were regular files')
                                help='open and read special files as if they were regular files')
+        subparser.add_argument('-n', '--dry-run', dest='dry_run',
+                               action='store_true', default=False,
+                               help='do not create a backup archive')
         subparser.add_argument('archive', metavar='ARCHIVE',
         subparser.add_argument('archive', metavar='ARCHIVE',
                                type=location_validator(archive=True),
                                type=location_validator(archive=True),
                                help='archive to create')
                                help='archive to create')

+ 8 - 0
borg/testsuite/archiver.py

@@ -485,6 +485,14 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         mode = os.stat(self.repository_path).st_mode
         mode = os.stat(self.repository_path).st_mode
         self.assertEqual(stat.S_IMODE(mode), 0o700)
         self.assertEqual(stat.S_IMODE(mode), 0o700)
 
 
+    def test_create_dry_run(self):
+        self.cmd('init', self.repository_location)
+        self.cmd('create', '--dry-run', self.repository_location + '::test', 'input')
+        # Make sure no archive has been created
+        repository = Repository(self.repository_path)
+        manifest, key = Manifest.load(repository)
+        self.assert_equal(len(manifest.archives), 0)
+
     def test_cmdline_compatibility(self):
     def test_cmdline_compatibility(self):
         self.create_regular_file('file1', size=1024 * 80)
         self.create_regular_file('file1', size=1024 * 80)
         self.cmd('init', self.repository_location)
         self.cmd('init', self.repository_location)