def test_restore_nomaster_nohome(self, tmp_path, caplog): home, repo = self.setup_home_repo(tmp_path) calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.restore({'file': ['cat1', 'cat2']}).apply() assert 'unable to find "file" in repo, skipping' in caplog.text assert not (home / 'file').is_file()
def test_restore_master_linkedhome(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') open(repo / 'cat1' / 'file', 'w').close() os.symlink(repo / 'cat1' / 'file', home / 'file') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) fops = calc.restore({'file': ['cat1', 'cat2']}) assert fops.ops == []
def test_clean_repo_categories(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') assert (repo / 'cat1').is_dir() calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.clean_repo([]).apply() assert not (repo / 'cat1').is_dir()
def test_update_master_linkedslave(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') os.makedirs(repo / 'cat2') open(repo / 'cat1' / 'file', 'w').close() os.symlink(Path('..') / 'cat1' / 'file', repo / 'cat2' / 'file') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) assert calc.update({'file': ['cat1', 'cat2']}).ops == []
def test_clean_hard_nohome(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') open(repo / 'cat1' / 'file', 'w').close() calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data', hard=True)) calc.clean({'file': ['cat1', 'cat2']}).apply() assert not (home / 'file').is_file() assert (repo / 'cat1' / 'file').is_file()
def test_clean_norepo_filehome(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) open(home / 'file', 'w').close() calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.clean({'file': ['cat1', 'cat2']}).apply() assert (home / 'file').is_file() assert not (home / 'file').is_symlink() assert not (repo / 'cat1' / 'file').exists()
def test_clean_linkedotherhome(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') open(repo / 'cat1' / 'file', 'w').close() os.symlink(Path('cat1') / 'file', home / 'file') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.clean({'file': ['cat1', 'cat2']}).apply() assert (home / 'file').is_symlink() assert (repo / 'cat1' / 'file').is_file()
def test_restore_master_nohome(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') open(repo / 'cat1' / 'file', 'w').close() calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.restore({'file': ['cat1', 'cat2']}).apply() assert (home / 'file').is_file() assert (home / 'file').is_symlink() assert (home / 'file').samefile(repo / 'cat1' / 'file') assert not (repo / 'cat1' / 'file').is_symlink()
def test_restore_dangling_home(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat') (repo / 'cat' / 'foo').touch() (home / 'foo').symlink_to('/non/existent/path') assert not (home / 'foo').exists() calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.restore({'foo': ['cat']}).apply() assert (home / 'foo').is_symlink() assert (home / 'foo').exists()
def test_update_home_nomaster_noslave(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) open(home / 'file', 'w').close() calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.update({'file': ['cat1', 'cat2']}).apply() assert (repo / 'cat1').is_dir() assert not (repo / 'cat1' / 'file').is_symlink() assert (repo / 'cat2').is_dir() assert (repo / 'cat2' / 'file').is_symlink() assert (repo / 'cat2' / 'file').samefile(repo / 'cat1' / 'file') assert not (home / 'file').exists()
def test_clean_hard_difffilehome(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') open(repo / 'cat1' / 'file', 'w').close() with open(home / 'file', 'w') as f: f.write('test data') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data', hard=True)) calc.clean({'file': ['cat1', 'cat2']}).apply() assert (home / 'file').is_file() assert (home / 'file').read_text() == 'test data' assert (repo / 'cat1' / 'file').is_file()
def test_clean_hard_linkedhome(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') open(repo / 'cat1' / 'file', 'w').close() os.symlink(repo / 'cat1' / 'file', home / 'file') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data', hard=True)) calc.clean({'file': ['cat1', 'cat2']}).apply() # shouldn't remove symlinks since they are not hard-copied files from # the repo assert (home / 'file').is_file() assert (repo / 'cat1' / 'file').is_file()
def test_clean_repo(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') open(repo / 'cat1' / 'file1', 'w').close() open(repo / 'cat1' / 'file2', 'w').close() os.makedirs(repo / 'cat2') open(repo / 'cat2' / 'file1', 'w').close() calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.clean_repo(['cat1/file1']).apply() assert (repo / 'cat1' / 'file1').is_file() assert not (repo / 'cat1' / 'file2').is_file() assert not (repo / 'cat2' / 'file2').is_file()
def test_update_master_brokenlinkslave(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') os.makedirs(repo / 'cat2') open(repo / 'cat1' / 'file', 'w').close() os.symlink(Path('..') / 'cat1' / 'nonexistent', repo / 'cat2' / 'file') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.update({'file': ['cat1', 'cat2']}).apply() assert (repo / 'cat1').is_dir() assert not (repo / 'cat1' / 'file').is_symlink() assert (repo / 'cat2').is_dir() assert (repo / 'cat2' / 'file').is_symlink() assert (repo / 'cat2' / 'file').samefile(repo / 'cat1' / 'file')
def test_restore_master_home_noreplace(self, tmp_path, monkeypatch): home, repo = self.setup_home_repo(tmp_path) os.makedirs(repo / 'cat1') open(repo / 'cat1' / 'file', 'w').close() open(home / 'file', 'w').close() monkeypatch.setattr('builtins.input', lambda p: 'n') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.restore({'file': ['cat1', 'cat2']}).apply() assert (home / 'file').is_file() assert not (home / 'file').is_symlink() assert (repo / 'cat1' / 'file').is_file() assert not (repo / 'cat1' / 'file').is_symlink()
def test_update_multiple_candidates(self, tmp_path, monkeypatch): home, repo = self.setup_home_repo(tmp_path) (repo / 'cat1').mkdir() (repo / 'cat2').mkdir() (repo / 'cat1' / 'file').write_text('file1') (repo / 'cat2' / 'file').write_text('file2') monkeypatch.setattr('builtins.input', lambda p: '1') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.update({'file': ['cat1', 'cat2']}).apply() assert (repo / 'cat1' / 'file').exists() assert not (repo / 'cat1' / 'file').is_symlink() assert (repo / 'cat2' / 'file').is_symlink()
def test_diff(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) (home / 'file').touch() (home / 'file2').touch() calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data', hard=True)) calc.update({'file': ['common'], 'file2': ['common']}).apply() calc.restore({'file': ['common'], 'file2': ['common']}).apply() (home / 'file').write_text('hello world') (home / 'file2').unlink() assert calc.diff(['common']) == [f'modified {home / "file"}']
def test_update_externallinkedhome_nomaster_noslave(self, tmp_path): home, repo = self.setup_home_repo(tmp_path) (home / 'foo').touch() (home / 'file').symlink_to(home / 'foo') calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.update({'file': ['cat']}).apply() assert (repo / 'cat').is_dir() assert (repo / 'cat' / 'file').exists() assert not (repo / 'cat' / 'file').is_symlink() calc.restore({'file': ['cat']}).apply() assert (home / 'file').is_symlink() assert (home / 'file').samefile(repo / 'cat' / 'file') assert repo in (home / 'file').resolve().parents assert (home / 'foo').exists() assert not (home / 'foo').is_symlink()
def main(args=None, cwd=os.getcwd(), home=info.home): if args is None: args = sys.argv[1:] # parse cmd arguments args = Arguments(args) logging.basicConfig(format='%(message)s ', level=args.verbose_level) logging.debug(f'ran with arguments {args}') repo = cwd flist_fname = os.path.join(repo, 'filelist') # run safety checks if not safety_checks(repo, home, args.action == Actions.INIT): logging.error(f'safety checks failed for {os.getcwd()}, exiting') return 1 # check for init if args.action == Actions.INIT: init_repo(repo, flist_fname) return 0 # parse filelist filelist = Filelist(flist_fname) # generate manifest for later cleaning manifest = filelist.manifest() # activate categories on filelist try: filelist = filelist.activate(args.categories) except RuntimeError: return 1 # set up git interface git = Git(repo) # set the dotfiles repo dotfiles = os.path.join(repo, 'dotfiles') logging.debug(f'dotfiles path is {dotfiles}') # init plugins plugins_data_dir = os.path.join(repo, '.plugins') plugins = { 'plain': PlainPlugin( data_dir=os.path.join(plugins_data_dir, 'plain'), repo_dir=os.path.join(dotfiles, 'plain'), hard=args.hard_mode), 'encrypt': EncryptPlugin( data_dir=os.path.join(plugins_data_dir, 'encrypt'), repo_dir=os.path.join(dotfiles, 'encrypt')) } plugin_dirs = {plugin: os.path.join(dotfiles, plugin) for plugin in plugins} if args.action in [Actions.UPDATE, Actions.RESTORE, Actions.CLEAN]: clean_ops = [] # calculate and apply file operations for plugin in plugins: # filter out filelist paths that use current plugin flist = {path: filelist[path]['categories'] for path in filelist if filelist[path]['plugin'] == plugin} if not flist: continue logging.debug(f'active filelist for plugin {plugin}: {flist}') plugin_dir = plugin_dirs[plugin] calc_ops = CalcOps(plugin_dir, home, plugins[plugin]) if args.action == Actions.UPDATE: calc_ops.update(flist).apply(args.dry_run) calc_ops.restore(flist).apply(args.dry_run) elif args.action == Actions.RESTORE: calc_ops.restore(flist).apply(args.dry_run) elif args.action == Actions.CLEAN: calc_ops.clean(flist).apply(args.dry_run) clean_ops.append(calc_ops.clean_repo(manifest[plugin])) plugins[plugin].clean_data(manifest[plugin]) # execute cleaning ops after everything else for clean_op in clean_ops: clean_op.apply(args.dry_run) elif args.action in [Actions.DIFF, Actions.COMMIT]: # calculate and apply git operations if args.action == Actions.DIFF: print('\n'.join(git.diff(ignore=['.plugins/']))) for plugin in plugins: calc_ops = CalcOps(plugin_dirs[plugin], home, plugins[plugin]) diff = calc_ops.diff(args.categories) if diff: print(f'\n{plugin}-plugin updates not yet in repo:') print('\n'.join(diff)) elif args.action == Actions.COMMIT: if not git.has_changes(): logging.warning('no changes detected in repo, not creating ' 'commit') return 0 git.add() msg = git.gen_commit_message(ignore=['.plugins/']) git.commit(msg) if git.has_remote(): ans = input('remote for repo detected, push to remote? [Yn] ') ans = ans if ans else 'y' if ans.lower() == 'y': git.push() logging.info('successfully pushed to git remote') elif args.action == Actions.PASSWD: logging.debug('attempting to change encryption password') repo = os.path.join(dotfiles, 'encrypt') if os.path.exists(repo): plugins['encrypt'].init_password() plugins['encrypt'].change_password(repo=repo) else: plugins['encrypt'].change_password() return 0
def test_update_no_cands(self, tmp_path, caplog): home, repo = self.setup_home_repo(tmp_path) calc = CalcOps(repo, home, PlainPlugin(tmp_path / '.data')) calc.update({'file': ['cat1', 'cat2']}) assert 'unable to find any candidates' in caplog.text