Skip to content

Commit

Permalink
Add option --use-original-path to 'borg extract' command: extract the…
Browse files Browse the repository at this point in the history
… content to its original path where it was backup from. 'borg extract' default behaviour always writes to current directory. This option overrides the default behaviour
  • Loading branch information
MarkJoy committed Feb 12, 2024
1 parent 3b43770 commit fb5133e
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 15 deletions.
14 changes: 10 additions & 4 deletions src/borg/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@ def extract_item(
*,
restore_attrs=True,
dry_run=False,
useop=False,
stdout=False,
sparse=False,
hlm=None,
Expand All @@ -795,6 +796,7 @@ def extract_item(
:param item: the item to extract
:param restore_attrs: restore file attributes
:param dry_run: do not write any data
:param useop: extract the content to its original path where it was backup from
:param stdout: write extracted data to stdout
:param sparse: write sparse files (chunk-granularity, independent of the original being sparse)
:param hlm: maps hlid to link_target for extracting subtrees with hardlinks correctly
Expand Down Expand Up @@ -851,8 +853,12 @@ def same_item(item, st):
raise BackupError("File has damaged (all-zero) chunks. Try running borg check --repair.")
return

dest = self.cwd
path = os.path.join(dest, item.path)
if useop:
saved_path = item.orig_path
path = item.orig_path
else:
saved_path = item.path
path = os.path.join(self.cwd, item.path)
# Attempt to remove existing files, ignore errors on failure
try:
st = os.stat(path, follow_symlinks=False)
Expand Down Expand Up @@ -885,7 +891,7 @@ def make_parent(path):
ids = [c.id for c in item.chunks]
for data in self.pipeline.fetch_many(ids, is_preloaded=True, ro_type=ROBJ_FILE_STREAM):
if pi:
pi.show(increase=len(data), info=[remove_surrogates(item.path)])
pi.show(increase=len(data), info=[remove_surrogates(saved_path)])
with backup_io("write"):
if sparse and zeros.startswith(data):
# all-zero chunk: create a hole in a sparse file
Expand Down Expand Up @@ -1378,7 +1384,7 @@ def __init__(
@contextmanager
def create_helper(self, path, st, status=None, hardlinkable=True):
sanitized_path = remove_dotdot_prefixes(path)
item = Item(path=sanitized_path)
item = Item(orig_path=path, path=sanitized_path)
hardlinked = hardlinkable and st.st_nlink > 1
hl_chunks = None
update_map = False
Expand Down
21 changes: 15 additions & 6 deletions src/borg/archiver/extract_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def do_extract(self, args, repository, manifest, archive):
progress = args.progress
output_list = args.output_list
dry_run = args.dry_run
useop = args.useop
stdout = args.stdout
sparse = args.sparse
strip_components = args.strip_components
Expand All @@ -63,21 +64,25 @@ def do_extract(self, args, repository, manifest, archive):
while dirs and not item.path.startswith(dirs[-1].path):
dir_item = dirs.pop(-1)
try:
archive.extract_item(dir_item, stdout=stdout)
archive.extract_item(dir_item, stdout=stdout, useop=useop)
except BackupOSError as e:
self.print_warning("%s: %s", remove_surrogates(dir_item.path), e)
if output_list:
logging.getLogger("borg.output.list").info(remove_surrogates(item.path))
if useop:
logging.getLogger("borg.output.list").info(remove_surrogates(item.orig_path))
else:
logging.getLogger("borg.output.list").info(remove_surrogates(item.path))
try:
if dry_run:
archive.extract_item(item, dry_run=True, hlm=hlm, pi=pi)
archive.extract_item(item, dry_run=True, hlm=hlm, pi=pi, useop=useop)
else:
if stat.S_ISDIR(item.mode):
dirs.append(item)
archive.extract_item(item, stdout=stdout, restore_attrs=False)
archive.extract_item(item, stdout=stdout, restore_attrs=False, useop=useop)
else:
archive.extract_item(
item, stdout=stdout, sparse=sparse, hlm=hlm, pi=pi, continue_extraction=continue_extraction
item, stdout=stdout, sparse=sparse, hlm=hlm, pi=pi,
continue_extraction=continue_extraction, useop=useop
)
except (BackupOSError, BackupError) as e:
self.print_warning("%s: %s", remove_surrogates(orig_path), e)
Expand All @@ -93,7 +98,7 @@ def do_extract(self, args, repository, manifest, archive):
pi.show()
dir_item = dirs.pop(-1)
try:
archive.extract_item(dir_item, stdout=stdout)
archive.extract_item(dir_item, stdout=stdout, useop=useop)
except BackupOSError as e:
self.print_warning("%s: %s", remove_surrogates(dir_item.path), e)
for pattern in matcher.get_unmatched_include_patterns():
Expand Down Expand Up @@ -160,6 +165,10 @@ def build_parser_extract(self, subparsers, common_parser, mid_common_parser):
)
subparser.add_argument("--noacls", dest="noacls", action="store_true", help="do not extract/set ACLs")
subparser.add_argument("--noxattrs", dest="noxattrs", action="store_true", help="do not extract/set xattrs")
subparser.add_argument(
"--use-original-path", dest="useop", action="store_true",
help="extract the content back to its original path where it was backup from"
)
subparser.add_argument(
"--stdout", dest="stdout", action="store_true", help="write all extracted data to stdout"
)
Expand Down
7 changes: 3 additions & 4 deletions src/borg/constants.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# this set must be kept complete, otherwise the RobustUnpacker might malfunction:
# fmt: off
ITEM_KEYS = frozenset(['path', 'source', 'target', 'rdev', 'chunks', 'chunks_healthy', 'hardlink_master', 'hlid',
'mode', 'user', 'group', 'uid', 'gid', 'mtime', 'atime', 'ctime', 'birthtime', 'size',
'xattrs', 'bsdflags', 'acl_nfs4', 'acl_access', 'acl_default', 'acl_extended',
'part'])
ITEM_KEYS = frozenset(['orig_path', 'path', 'source', 'target', 'rdev', 'chunks', 'chunks_healthy', 'hardlink_master',
'hlid', 'mode', 'user', 'group', 'uid', 'gid', 'mtime', 'atime', 'ctime', 'birthtime', 'size',
'xattrs', 'bsdflags', 'acl_nfs4', 'acl_access', 'acl_default', 'acl_extended', 'part'])
# fmt: on

# this is the set of keys that are always present in items:
Expand Down
4 changes: 3 additions & 1 deletion src/borg/item.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,10 @@ cdef class Item(PropDict):

# properties statically defined, so that IDEs can know their names:

orig_path = PropDictProperty(str, 'surrogate-escaped str')
path = PropDictProperty(str, 'surrogate-escaped str', encode=assert_sanitized_path, decode=to_sanitized_path)
source = PropDictProperty(str, 'surrogate-escaped str') # legacy borg 1.x. borg 2: see .target
# legacy borg 1.x. borg 2: see .target https://github.com/borgbackup/borg/issues/7245
source = PropDictProperty(str, 'surrogate-escaped str')
target = PropDictProperty(str, 'surrogate-escaped str')
user = PropDictProperty(str, 'surrogate-escaped str')
group = PropDictProperty(str, 'surrogate-escaped str')
Expand Down

0 comments on commit fb5133e

Please sign in to comment.