Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow copying symlinks as links, rather than copying the target #1929

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## WIP

- Add optional support to copy symlinks as links, rather than copying the target (via @jamesrtnz)

Check failure on line 5 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / mdl

Line length [Expected: 80; Actual: 97]
- Added support for DBeaver (via @or-tal-0)

## Mackup 0.8.40
Expand Down
13 changes: 13 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ path = some/path in your/home
path = /some path/in/your/root
```

### Copying Symlinks as Links

By default, when Mackup backs up the application configuration, any symbolic
links to files results in the destination file being copied into the backup.

You can optionally configure Mackup to ensure that any Symlinks are copied as
Links, and not files:

```ini
[storage]
copy_symlinks = true
```

### Custom Directory Name

You can customize the directory name in which Mackup stores your file. By
Expand Down
28 changes: 28 additions & 0 deletions mackup/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ def __init__(self, filename=None):
# Get the directory replacing 'Mackup', if any
self._directory = self._parse_directory()

# Get the copy_symlinks value
self._copy_symlinks = self._parse_symlinks()

# Get the list of apps to ignore
self._apps_to_ignore = self._parse_apps_to_ignore()

Expand Down Expand Up @@ -129,6 +132,16 @@ def apps_to_sync(self):
"""
return set(self._apps_to_sync)

@property
def copy_symlinks(self):
"""
The copy_symlinks flag status.

Returns:
bool
"""
return bool(self._copy_symlinks)

def _setup_parser(self, filename=None):
"""
Configure the ConfigParser instance the way we want it.
Expand Down Expand Up @@ -221,6 +234,21 @@ def _parse_path(self):

return str(path)

def _parse_symlinks(self):
"""
Parse the copy_symlinks setting in the config.

Returns:
bool
"""
if self._parser.has_option("storage", "copy_symlinks"):
copy_symlinks = self._parser.getboolean("storage", "copy_symlinks", fallback=False)
else:
copy_symlinks = False

return bool(copy_symlinks)


def _parse_directory(self):
"""
Parse the storage directory in the config.
Expand Down
2 changes: 2 additions & 0 deletions mackup/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ def printAppHeader(app_name):
if args["--root"]:
utils.CAN_RUN_AS_ROOT = True

utils.COPY_SYMLINKS = mckp._config._copy_symlinks

dry_run = args["--dry-run"]

verbose = args["--verbose"]
Expand Down
9 changes: 6 additions & 3 deletions mackup/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
# Flag that control if mackup can be run as root
CAN_RUN_AS_ROOT = False

# Are we going to copy symlinks as links?
COPY_SYMLINKS = False

def confirm(question):
"""
Expand Down Expand Up @@ -98,18 +100,19 @@ def copy(src, dst):
# We need to copy a single file
if os.path.isfile(src):
# Copy the src file to dst
shutil.copy(src, dst)
shutil.copy(src, dst, follow_symlinks=not(COPY_SYMLINKS))

# We need to copy a whole folder
elif os.path.isdir(src):
shutil.copytree(src, dst)
shutil.copytree(src, dst, symlinks=COPY_SYMLINKS)

# What the heck is this?
else:
raise ValueError("Unsupported file: {}".format(src))

# Set the good mode to the file or folder recursively
chmod(dst)
if COPY_SYMLINKS == False:
chmod(dst)


def link(target, link_to):
Expand Down
12 changes: 12 additions & 0 deletions tests/config_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def test_config_no_config(self):
assert cfg.apps_to_ignore == set()
assert cfg.apps_to_sync == set()

assert isinstance(cfg.copy_symlinks, bool)
assert cfg.copy_symlinks == False

def test_config_empty(self):
cfg = Config("mackup-empty.cfg")

Expand All @@ -53,6 +56,9 @@ def test_config_empty(self):
assert cfg.apps_to_ignore == set()
assert cfg.apps_to_sync == set()

assert isinstance(cfg.copy_symlinks, bool)
assert cfg.copy_symlinks == False

def test_config_engine_dropbox(self):
cfg = Config("mackup-engine-dropbox.cfg")

Expand Down Expand Up @@ -213,3 +219,9 @@ def test_config_apps_to_ignore_and_sync(self):

def test_config_old_config(self):
self.assertRaises(SystemExit, Config, "mackup-old-config.cfg")

def test_config_copy_symlinks(self):
cfg = Config("mackup-copy-symlinks.cfg")

assert isinstance(cfg.copy_symlinks, bool)
assert cfg.copy_symlinks == True
2 changes: 2 additions & 0 deletions tests/fixtures/mackup-copy-symlinks.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[storage]
copy_symlinks = 1
Loading