diff --git a/gitless/cli/commit_dialog.py b/gitless/cli/commit_dialog.py index e0b570f..a965d20 100644 --- a/gitless/cli/commit_dialog.py +++ b/gitless/cli/commit_dialog.py @@ -15,7 +15,7 @@ import shlex -from . import pprint +from . import pprint, helpers IS_PY2 = sys.version_info[0] == 2 @@ -56,6 +56,7 @@ def show(files, repo): pprint.msg( 'These are the files whose changes will be committed:', stream=cf.write) for f in files: + f = helpers.remove_keep_file_name(f) pprint.item(f, stream=cf.write) pprint.sep(stream=cf.write) cf.close() diff --git a/gitless/cli/file_cmd.py b/gitless/cli/file_cmd.py index e1ea45a..c54c871 100644 --- a/gitless/cli/file_cmd.py +++ b/gitless/cli/file_cmd.py @@ -7,6 +7,8 @@ from __future__ import unicode_literals +from gitless import core + from . import helpers, pprint @@ -35,11 +37,15 @@ def f(args, repo): for fp in args.files: try: + empty_dir = fp.endswith(core.GL_KEEP_FILENAME) getattr(curr_b, subcmd + '_file')(fp) + fp = helpers.remove_keep_file_name(fp) pprint.ok( - 'File {0} is now a{1} {2}{3}d file'.format( + '{0} {1} is now a{2} {3}{4}d {5}'.format( + 'Empty directory' if empty_dir else 'File', fp, 'n' if subcmd.startswith(VOWELS) else '', subcmd, - '' if subcmd.endswith('e') else 'e')) + '' if subcmd.endswith('e') else 'e', + 'directory' if empty_dir else 'file')) except KeyError: pprint.err('Can\'t {0} non-existent file {1}'.format(subcmd, fp)) success = False diff --git a/gitless/cli/gl_status.py b/gitless/cli/gl_status.py index c5a6158..54b3b63 100644 --- a/gitless/cli/gl_status.py +++ b/gitless/cli/gl_status.py @@ -84,8 +84,10 @@ def _print_tracked_mod_files(tracked_mod_list, relative_paths, repo): for f in tracked_mod_list: exp = '' color = colored.yellow + is_keep_file = f.fp.endswith(core.GL_KEEP_FILENAME) if not f.exists_at_head: - exp = ' (new file)' + kind = 'directory' if is_keep_file else 'file' + exp = ' (new {0})'.format(kind) color = colored.green elif not f.exists_in_wd: exp = ' (deleted)' @@ -97,6 +99,7 @@ def _print_tracked_mod_files(tracked_mod_list, relative_paths, repo): fp = os.path.relpath(os.path.join(root, f.fp)) if relative_paths else f.fp if fp == '.': continue + fp = helpers.remove_keep_file_name(fp) pprint.item(color(fp), opt_text=exp) @@ -128,6 +131,7 @@ def _print_untracked_files(untracked_list, relative_paths, repo): fp = os.path.relpath(os.path.join(root, f.fp)) if relative_paths else f.fp if fp == '.': continue + fp = helpers.remove_keep_file_name(fp) pprint.item(color(fp), opt_text=exp) diff --git a/gitless/cli/helpers.py b/gitless/cli/helpers.py index 61fc134..06de5e6 100644 --- a/gitless/cli/helpers.py +++ b/gitless/cli/helpers.py @@ -129,6 +129,10 @@ def process_paths(): self.skip_dir_cb(curr_dir_rel) dirs[:] = [] continue + if not fps: + open(os.path.join(curr_dir, core.GL_KEEP_FILENAME), 'a').close() + fps.append(core.GL_KEEP_FILENAME) + dirs[:] = [] for fp in fps: yield os.path.join(curr_dir_rel, fp) else: @@ -247,3 +251,9 @@ def validate(fps, check_fn, msg): for e in err: pprint.err(e) return False + + +def remove_keep_file_name(fp): + if(fp.endswith(core.GL_KEEP_FILENAME)): + return fp.replace(core.GL_KEEP_FILENAME, '') + return fp diff --git a/gitless/core.py b/gitless/core.py index 793bec1..1ff3383 100644 --- a/gitless/core.py +++ b/gitless/core.py @@ -54,6 +54,8 @@ class ApplyFailedError(GlError): pass GL_STATUS_TRACKED = 2 GL_STATUS_IGNORED = 3 +GL_KEEP_FILENAME = '.glkeep' + def init_repository(url=None): """Creates a new Gitless's repository in the cwd. @@ -769,6 +771,14 @@ def status(self): yield self.FileStatus( fp, GL_STATUS_UNTRACKED, True, exists_in_wd, True, False) + # find untracked empty dirs + for dirpath, dirs, files in os.walk('.', topdown=True): + dirs[:] = [d for d in dirs if d not in ['.git'] and not + self.path_is_ignored(d)] + if not dirs and not files: + yield self.FileStatus(dirpath, GL_STATUS_UNTRACKED, False, True, False, + False) + def status_file(self, path): """Return the status (see FileStatus) of the given path.""" return self._status_file(path)[0] diff --git a/gitless/tests/test_e2e.py b/gitless/tests/test_e2e.py index 5ea3309..297bdb8 100755 --- a/gitless/tests/test_e2e.py +++ b/gitless/tests/test_e2e.py @@ -20,6 +20,7 @@ gl = Command('gl') git = Command('git') +from gitless import core from gitless.tests import utils @@ -683,6 +684,92 @@ def test_uncommitted_tracked_changes_that_conflict_append(self): self.assertTrue('contents 2' in contents) +class TestEmptyDir(TestEndToEnd): + + def test_empty_dir_status(self): + untracked_empty_dir = self._mk_empty_dir('untracked_empty_dir') + + out = utils.stdout(gl.status()) + + self.assertIn(untracked_empty_dir, out, 'Empty dir didn\'t appear in status') + + def test_ignored_empty_dir_status(self): + ignored_empty_dir = self._mk_empty_dir('ignored_empty_dir') + utils.write_file(os.path.join(self.path, '.gitignore'), ignored_empty_dir) + + out = utils.stdout(gl.status()) + + self.assertFalse(ignored_empty_dir in out, + 'Ignored empty dir was listed in status') + + def test_track_empty_dir(self): + dir_to_track = self._mk_empty_dir('wanted_empty_dir') + expected_out = 'Empty directory {0} is now a tracked directory'.format( + self._dir_path(dir_to_track)) + + out = utils.stdout(gl.track(dir_to_track)) + + self.assertIn(expected_out, out, 'Empty dir wasn\'t tracked') + + def test_track_parent_empty_dir(self): + parent_empty_dir = self._mk_empty_dir('parent_empty_dir') + child_dir = self._mk_empty_dir(os.path.join(parent_empty_dir, 'child_dir')) + unexpected_out = 'Empty directory {0} is now a tracked directory'.format( + os.path.join(child_dir, '')) + + out = utils.stdout(gl.track(parent_empty_dir)) + + self.assertFalse(unexpected_out in out, 'Tracked empty dir child') + + def test_tracked_empty_dir_status(self): + tracked_empty_dir = self._mk_empty_dir('tracked_empty_dir') + gl.track(tracked_empty_dir) + expected_out = '{0} (new directory)'.format( + self._dir_path(tracked_empty_dir)) + + out = utils.stdout(gl.status()) + + self.assertIn(expected_out, out, 'Didn\'t report newly tracked dir') + + def test_commit_empty_dir(self): + empty_dir = self._mk_empty_dir('wanted_empty_dir') + gl.track(empty_dir) + pipe = 'std.out' + gl.commit(_out = pipe, _bg = True) + + f = open(pipe) + out = previous_out = '' + while(os.path.getsize(pipe) == 0 or out != previous_out): + time.sleep(0.1) + previous_out = out + f.seek(0) + out = f.read() + f.close() + + self.assertIn(self._dir_path(empty_dir), out) + self.assertFalse(core.GL_KEEP_FILENAME in out, + 'Output included gitless keep file name') + + def test_untracked_empty_dir_status(self): + untracked_empty_dir = self._mk_empty_dir('untracked_empty_dir') + gl.track(untracked_empty_dir) + gl.commit(m = 'Add empty dir') + gl.untrack(untracked_empty_dir) + expected_out = '{0} (exists at head)'.format( + self._dir_path(untracked_empty_dir)) + + out = utils.stdout(gl.status()) + + self.assertIn(expected_out, out, 'Didn\'t report untracked dir') + + def _mk_empty_dir(self, name): + os.mkdir(os.path.join(self.path, name)) + return name + + def _dir_path(self, path): + return os.path.join(path, '') + + class TestPerformance(TestEndToEnd): FPS_QTY = 10000