Skip to content

Commit

Permalink
Merge 1b8f375 into 4755adb
Browse files Browse the repository at this point in the history
  • Loading branch information
mih committed Sep 2, 2016
2 parents 4755adb + 1b8f375 commit 635f5ce
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 0 deletions.
9 changes: 9 additions & 0 deletions datalad/interface/common_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,12 @@
metavar='STRING',
constraints=EnsureStr() | EnsureNone(),
doc="""option string to be passed to :command:`git annex copy` calls""")

if_dirty_opt = Parameter(
args=("--if-dirty",),
choices=('fail', 'save-before', 'ignore'),
doc="""desired behavior if a dataset with unsaved changes is discovered:
'fail' will trigger an error and further processing is aborted;
'save-before' will save all changes prior any further action;
'ignore' let's datalad proceed as if the dataset would not have unsaved
changes.""")
82 changes: 82 additions & 0 deletions datalad/interface/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the datalad package for the
# copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Test dirty dataset handling
"""

__docformat__ = 'restructuredtext'

from os.path import join as opj
from nose.tools import assert_raises, assert_equal
from datalad.tests.utils import with_tempfile, assert_not_equal
from datalad.interface.utils import handle_dirty_dataset
from datalad.distribution.dataset import Dataset

_dirty_modes = ('fail', 'ignore', 'save-before')


def _check_all_clean(ds, state):
assert state is not None
for mode in _dirty_modes:
# nothing wrong, nothing saved
handle_dirty_dataset(ds, mode)
assert_equal(state, ds.repo.get_hexsha())


def _check_auto_save(ds, orig_state):
handle_dirty_dataset(ds, 'ignore')
assert_raises(RuntimeError, handle_dirty_dataset, ds, 'fail')
handle_dirty_dataset(ds, 'save-before')
state = ds.repo.get_hexsha()
assert_not_equal(orig_state, state)
_check_all_clean(ds, state)
return state


@with_tempfile(mkdir=True)
def test_dirty(path):
for mode in _dirty_modes:
# does nothing without a dataset
handle_dirty_dataset(None, mode)
# placeholder, but not yet created
ds = Dataset(path)
# unknown mode
assert_raises(ValueError, handle_dirty_dataset, ds, 'MADEUP')
# not yet created is very dirty
assert_raises(RuntimeError, handle_dirty_dataset, ds, 'fail')
handle_dirty_dataset(ds, 'ignore')
assert_raises(RuntimeError, handle_dirty_dataset, ds, 'save-before')
# should yield a clean repo
ds.create()
orig_state = ds.repo.get_hexsha()
_check_all_clean(ds, orig_state)
# tainted: untracked
with open(opj(ds.path, 'something'), 'w') as f:
f.write('some')
orig_state = _check_auto_save(ds, orig_state)
# tainted: staged
with open(opj(ds.path, 'staged'), 'w') as f:
f.write('some')
ds.repo.add('staged', git=True)
orig_state = _check_auto_save(ds, orig_state)
# tainted: submodule
# not added to super on purpose!
subds = Dataset(opj(ds.path, 'subds')).create(add_to_super=False)
_check_all_clean(subds, subds.repo.get_hexsha())
orig_state = _check_auto_save(ds, orig_state)
# XXX surprisingly this is added as a submodule, but there is no .gitmodules
# which confused even Git itself (git submodule call now fails with
# "fatal: no submodule mapping found in .gitmodules for path 'subds'"
assert_equal(ds.get_subdatasets(), [])
# tainted: submodule
# this time add to super
subds = Dataset(opj(ds.path, 'registeredsubds')).create(add_to_super=True)
_check_all_clean(subds, subds.repo.get_hexsha())
orig_state = _check_auto_save(ds, orig_state)
assert_equal(ds.get_subdatasets(), ['registeredsubds'])
53 changes: 53 additions & 0 deletions datalad/interface/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the datalad package for the
# copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Interface utility functions
"""

__docformat__ = 'restructuredtext'


from datalad.api import save


def handle_dirty_dataset(ds, mode, msg=None):
"""Detect and treat unsaved changes as instructed by `mode`
Parameters
----------
ds : Dataset or None
Dataset to be inspected. Does nothing if `None`.
mode : {'fail', 'ignore', 'save-before'}
How to act upon discovering unsaved changes.
msg : str or None
Custom message to use for a potential commit.
Returns
-------
None
"""
if ds is None:
# nothing to be handled
return
if msg is None:
msg = '[DATALAD] auto-saved changes'
if mode == 'ignore':
return
elif mode == 'fail':
if not ds.repo or ds.repo.repo.is_dirty(index=True,
working_tree=True,
untracked_files=True,
submodules=True):
raise RuntimeError('dataset {} has unsaved changes'.format(ds))
elif mode == 'save-before':
if not ds.is_installed():
raise RuntimeError('dataset {} is not yet installed'.format(ds))
ds.save(message=msg, auto_add_changes=True)
else:
raise ValueError("unknown if-dirty mode '{}'".format(mode))

0 comments on commit 635f5ce

Please sign in to comment.