mirror of https://github.com/borgbackup/borg.git
Merge pull request #192 from ThomasWaldmann/create-dryrun
implement borg create --dry-run, attic issue #267
This commit is contained in:
commit
da5923ec04
|
@ -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
|
||||||
|
|
104
borg/archiver.py
104
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)
|
if not dry_run:
|
||||||
manifest, key = Manifest.load(repository)
|
repository = self.open_repository(args.archive, exclusive=True)
|
||||||
compr_args = dict(buffer=COMPR_BUFFER)
|
manifest, key = Manifest.load(repository)
|
||||||
compr_args.update(args.compression)
|
compr_args = dict(buffer=COMPR_BUFFER)
|
||||||
key.compressor = Compressor(**compr_args)
|
compr_args.update(args.compression)
|
||||||
cache = Cache(repository, key, manifest, do_files=args.cache_files)
|
key.compressor = Compressor(**compr_args)
|
||||||
archive = Archive(repository, key, manifest, args.archive.archive, cache=cache,
|
cache = Cache(repository, key, manifest, do_files=args.cache_files)
|
||||||
create=True, checkpoint_interval=args.checkpoint_interval,
|
archive = Archive(repository, key, manifest, args.archive.archive, cache=cache,
|
||||||
numeric_owner=args.numeric_owner, progress=args.progress,
|
create=True, checkpoint_interval=args.checkpoint_interval,
|
||||||
chunker_params=args.chunker_params)
|
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)
|
if not dry_run:
|
||||||
try:
|
try:
|
||||||
archive.process_stdin(path, cache)
|
status = archive.process_stdin(path, cache)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
self.print_error('%s: %s', path, 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)
|
read_special=args.read_special, dry_run=dry_run)
|
||||||
archive.save(timestamp=args.timestamp)
|
if not dry_run:
|
||||||
if args.progress:
|
archive.save(timestamp=args.timestamp)
|
||||||
archive.stats.show_progress(final=True)
|
if args.progress:
|
||||||
if args.stats:
|
archive.stats.show_progress(final=True)
|
||||||
t = datetime.now()
|
if args.stats:
|
||||||
diff = t - t0
|
t = datetime.now()
|
||||||
print('-' * 78)
|
diff = t - t0
|
||||||
print('Archive name: %s' % args.archive.archive)
|
print('-' * 78)
|
||||||
print('Archive fingerprint: %s' % hexlify(archive.id).decode('ascii'))
|
print('Archive name: %s' % args.archive.archive)
|
||||||
print('Start time: %s' % t0.strftime('%c'))
|
print('Archive fingerprint: %s' % hexlify(archive.id).decode('ascii'))
|
||||||
print('End time: %s' % t.strftime('%c'))
|
print('Start time: %s' % t0.strftime('%c'))
|
||||||
print('Duration: %s' % format_timedelta(diff))
|
print('End time: %s' % t.strftime('%c'))
|
||||||
print('Number of files: %d' % archive.stats.nfiles)
|
print('Duration: %s' % format_timedelta(diff))
|
||||||
archive.stats.print_('This archive:', cache)
|
print('Number of files: %d' % archive.stats.nfiles)
|
||||||
print('-' * 78)
|
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:
|
if not dry_run:
|
||||||
status = archive.process_file(path, st, cache)
|
try:
|
||||||
except IOError as e:
|
status = archive.process_file(path, st, cache)
|
||||||
self.print_error('%s: %s', path, e)
|
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')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue