From a9418ca1e255343aacd3789eef9bdea893adb6b9 Mon Sep 17 00:00:00 2001 From: embs Date: Sat, 21 Oct 2017 16:39:10 -0300 Subject: [PATCH 1/5] Allow tracking empty directories Handle creating and removing placeholders files for versioning empty directories as suggested in [1]. [1]: https://git.wiki.kernel.org/index.php/GitFaq#Can_I_add_empty_directories.3F --- gitless/cli/file_cmd.py | 11 +++++++++-- gitless/cli/helpers.py | 3 +++ gitless/core.py | 2 ++ gitless/tests/test_e2e.py | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/gitless/cli/file_cmd.py b/gitless/cli/file_cmd.py index e1ea45a..41b3e4f 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,16 @@ def f(args, repo): for fp in args.files: try: + empty_dir = fp.endswith(core.GL_KEEP_FILENAME) getattr(curr_b, subcmd + '_file')(fp) + if empty_dir: + fp = fp.replace(core.GL_KEEP_FILENAME, '') 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/helpers.py b/gitless/cli/helpers.py index 61fc134..4070b27 100644 --- a/gitless/cli/helpers.py +++ b/gitless/cli/helpers.py @@ -129,6 +129,9 @@ 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) for fp in fps: yield os.path.join(curr_dir_rel, fp) else: diff --git a/gitless/core.py b/gitless/core.py index 793bec1..1648a29 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. diff --git a/gitless/tests/test_e2e.py b/gitless/tests/test_e2e.py index 5ea3309..ee36b0d 100755 --- a/gitless/tests/test_e2e.py +++ b/gitless/tests/test_e2e.py @@ -683,6 +683,20 @@ def test_uncommitted_tracked_changes_that_conflict_append(self): self.assertTrue('contents 2' in contents) +class TestEmptyDir(TestEndToEnd): + + def test_track_empty_dir(self): + dir_to_track = 'wanted_empty_dir' + dir_to_track_path = os.path.join(self.path, dir_to_track) + os.mkdir(dir_to_track_path) + expected_out = 'Empty directory {0} is now a tracked directory'.format( + os.path.join(dir_to_track, '')) + + out = utils.stdout(gl.track(dir_to_track_path)) + + self.assertIn(expected_out, out, 'Empty dir wasn\'t tracked') + + class TestPerformance(TestEndToEnd): FPS_QTY = 10000 From bb3484695c8fe7ad96e2c438021b0fa1047fbed0 Mon Sep 17 00:00:00 2001 From: embs Date: Sat, 2 Dec 2017 12:03:17 -0300 Subject: [PATCH 2/5] Handle empty dirs in gl status So users can operate (track, untrack, etc.) on them if they wish. --- gitless/cli/gl_status.py | 8 +++++- gitless/core.py | 8 ++++++ gitless/tests/test_e2e.py | 53 +++++++++++++++++++++++++++++++++++---- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/gitless/cli/gl_status.py b/gitless/cli/gl_status.py index c5a6158..98f8f43 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,8 @@ 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 + if is_keep_file: + fp = fp.replace(core.GL_KEEP_FILENAME, '') pprint.item(color(fp), opt_text=exp) @@ -128,6 +132,8 @@ 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 + if fp.endswith(core.GL_KEEP_FILENAME): + fp = fp.replace(core.GL_KEEP_FILENAME, '') pprint.item(color(fp), opt_text=exp) diff --git a/gitless/core.py b/gitless/core.py index 1648a29..1ff3383 100644 --- a/gitless/core.py +++ b/gitless/core.py @@ -771,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 ee36b0d..236d906 100755 --- a/gitless/tests/test_e2e.py +++ b/gitless/tests/test_e2e.py @@ -685,17 +685,60 @@ def test_uncommitted_tracked_changes_that_conflict_append(self): 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 = 'wanted_empty_dir' - dir_to_track_path = os.path.join(self.path, dir_to_track) - os.mkdir(dir_to_track_path) + dir_to_track = self._mk_empty_dir('wanted_empty_dir') expected_out = 'Empty directory {0} is now a tracked directory'.format( - os.path.join(dir_to_track, '')) + self._dir_path(dir_to_track)) - out = utils.stdout(gl.track(dir_to_track_path)) + out = utils.stdout(gl.track(dir_to_track)) self.assertIn(expected_out, out, 'Empty dir wasn\'t tracked') + 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_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): From d27a0f593ba69296b057a712284c744cd551571c Mon Sep 17 00:00:00 2001 From: embs Date: Sat, 2 Dec 2017 23:02:15 -0300 Subject: [PATCH 3/5] Do not track empty dir's children When tracking empty dirs. --- gitless/cli/helpers.py | 1 + gitless/tests/test_e2e.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/gitless/cli/helpers.py b/gitless/cli/helpers.py index 4070b27..f26b6fd 100644 --- a/gitless/cli/helpers.py +++ b/gitless/cli/helpers.py @@ -132,6 +132,7 @@ def process_paths(): 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: diff --git a/gitless/tests/test_e2e.py b/gitless/tests/test_e2e.py index 236d906..1621734 100755 --- a/gitless/tests/test_e2e.py +++ b/gitless/tests/test_e2e.py @@ -710,6 +710,16 @@ def test_track_empty_dir(self): 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) From 94db988efd6060118d348c70149915ddada675f7 Mon Sep 17 00:00:00 2001 From: embs Date: Sun, 3 Dec 2017 16:19:33 -0300 Subject: [PATCH 4/5] Hid keep file name in commit dialogue --- gitless/cli/commit_dialog.py | 3 +++ gitless/tests/test_e2e.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/gitless/cli/commit_dialog.py b/gitless/cli/commit_dialog.py index e0b570f..61295d8 100644 --- a/gitless/cli/commit_dialog.py +++ b/gitless/cli/commit_dialog.py @@ -14,6 +14,7 @@ import sys import shlex +from gitless import core from . import pprint @@ -56,6 +57,8 @@ def show(files, repo): pprint.msg( 'These are the files whose changes will be committed:', stream=cf.write) for f in files: + if f.endswith(core.GL_KEEP_FILENAME): + f = f.replace(core.GL_KEEP_FILENAME, '') pprint.item(f, stream=cf.write) pprint.sep(stream=cf.write) cf.close() diff --git a/gitless/tests/test_e2e.py b/gitless/tests/test_e2e.py index 1621734..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 @@ -730,6 +731,25 @@ def test_tracked_empty_dir_status(self): 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) From 1e390592c5b3d6368d7c07a4f88bc3c57727ccb4 Mon Sep 17 00:00:00 2001 From: embs Date: Sun, 3 Dec 2017 16:40:31 -0300 Subject: [PATCH 5/5] Extract replacement of .glkeep filename to helper --- gitless/cli/commit_dialog.py | 6 ++---- gitless/cli/file_cmd.py | 3 +-- gitless/cli/gl_status.py | 6 ++---- gitless/cli/helpers.py | 6 ++++++ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/gitless/cli/commit_dialog.py b/gitless/cli/commit_dialog.py index 61295d8..a965d20 100644 --- a/gitless/cli/commit_dialog.py +++ b/gitless/cli/commit_dialog.py @@ -14,9 +14,8 @@ import sys import shlex -from gitless import core -from . import pprint +from . import pprint, helpers IS_PY2 = sys.version_info[0] == 2 @@ -57,8 +56,7 @@ def show(files, repo): pprint.msg( 'These are the files whose changes will be committed:', stream=cf.write) for f in files: - if f.endswith(core.GL_KEEP_FILENAME): - f = f.replace(core.GL_KEEP_FILENAME, '') + 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 41b3e4f..c54c871 100644 --- a/gitless/cli/file_cmd.py +++ b/gitless/cli/file_cmd.py @@ -39,8 +39,7 @@ def f(args, repo): try: empty_dir = fp.endswith(core.GL_KEEP_FILENAME) getattr(curr_b, subcmd + '_file')(fp) - if empty_dir: - fp = fp.replace(core.GL_KEEP_FILENAME, '') + fp = helpers.remove_keep_file_name(fp) pprint.ok( '{0} {1} is now a{2} {3}{4}d {5}'.format( 'Empty directory' if empty_dir else 'File', diff --git a/gitless/cli/gl_status.py b/gitless/cli/gl_status.py index 98f8f43..54b3b63 100644 --- a/gitless/cli/gl_status.py +++ b/gitless/cli/gl_status.py @@ -99,8 +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 - if is_keep_file: - fp = fp.replace(core.GL_KEEP_FILENAME, '') + fp = helpers.remove_keep_file_name(fp) pprint.item(color(fp), opt_text=exp) @@ -132,8 +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 - if fp.endswith(core.GL_KEEP_FILENAME): - fp = fp.replace(core.GL_KEEP_FILENAME, '') + 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 f26b6fd..06de5e6 100644 --- a/gitless/cli/helpers.py +++ b/gitless/cli/helpers.py @@ -251,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