diff --git a/gitfs/cache/gitignore.py b/gitfs/cache/gitignore.py index 0ff2cb35..9ca7155d 100644 --- a/gitfs/cache/gitignore.py +++ b/gitfs/cache/gitignore.py @@ -36,14 +36,14 @@ def __init__(self, ignore=False, submodules=False, path="."): self.update() def update(self): - self.items = ['/.git', '.git/*', '/.git/*', '*.keep'] + self.items = ['.git', '.git/*', '/.git/*', '*.keep'] if self.ignore and os.path.exists(self.ignore): with open(self.ignore) as gitignore: - new_items = filter(lambda line: line != "", - gitignore.read().split("\n")) - - self.items += new_items + for item in gitignore.readlines(): + item = item.strip() + if item and not item.startswith('#'): + self.items += item if self.submodules and os.path.exists(self.submodules): with open(self.submodules) as submodules: @@ -67,7 +67,13 @@ def check_key(self, key): return False def _check_item_and_key(self, item, key): + if key.startswith("/"): + key = key[1:] + if item == key: return True + if item.endswith("/") and key.startswith(item): + return True + return fnmatch.fnmatch(key, item) diff --git a/gitfs/repository.py b/gitfs/repository.py index 639ee731..1022d375 100644 --- a/gitfs/repository.py +++ b/gitfs/repository.py @@ -77,6 +77,9 @@ def diverge(self, upstream, branch): def checkout(self, ref, *args, **kwargs): result = self._repo.checkout(ref, *args, **kwargs) + # update ignore cache after a checkout + self.ignore.update() + status = self._repo.status() for path, status in status.iteritems(): # path is in current status, move on @@ -85,11 +88,12 @@ def checkout(self, ref, *args, **kwargs): # check if file exists or not if path not in self._repo.index: - os.unlink(self._full_path(path)) + if path not in self.ignore: + os.unlink(self._full_path(path)) continue # check files stats - stats = self.get_git_object_stat(path) + stats = self.get_git_object_default_stats(ref, path) current_stat = os.lstat(self._full_path(path)) if stats['st_mode'] != current_stat.st_mode: diff --git a/gitfs/router.py b/gitfs/router.py index 39cab4fb..3e0ad9c8 100644 --- a/gitfs/router.py +++ b/gitfs/router.py @@ -126,7 +126,13 @@ def __call__(self, operation, *args): view.__class__.__name__)) raise FuseOSError(ENOSYS) - return getattr(view, operation)(*args) + try: + return getattr(view, operation)(*args) + except FuseOSError as e: + raise e + except Exception as exception: + log.exception("A system call failed") + raise exception def register(self, routes): for regex, view in routes: diff --git a/gitfs/utils/args.py b/gitfs/utils/args.py index ada6bd7e..02a520c3 100644 --- a/gitfs/utils/args.py +++ b/gitfs/utils/args.py @@ -52,6 +52,7 @@ def __init__(self, parser): ("log", ("syslog", "string")), ("log_level", ("warning", "string")), ("cache_size", (800, "int")), + ("sentry_dsn", (self.get_sentry_dsn, "string")), ]) self.config = self.build_config(parser.parse_args()) @@ -90,8 +91,17 @@ def check_args(self, args): '%(message)s'.format(mount_point=args.mount_point) handler.setFormatter(Formatter(fmt=logger_fmt)) + if args.sentry_dsn != '': + from raven.conf import setup_logging + from raven.handlers.logging import SentryHandler + + sentry_handler = SentryHandler(args.sentry_dsn) + sentry_handler.setLevel("ERROR") + setup_logging(sentry_handler) + log.addHandler(sentry_handler) + + handler.setLevel(args.log_level.upper()) log.addHandler(handler) - log.setLevel(args.log_level.upper()) # set cache size lru_cache.maxsize = args.cache_size @@ -151,6 +161,9 @@ def get_repo_path(self, args): def get_ssh_key(self, args): return os.environ["HOME"] + "/.ssh/id_rsa" + def get_sentry_dsn(self, args): + return os.environ["SENTRY_DSN"] if "SENTRY_DSN" in os.environ else "" + def get_ssh_user(self, args): url = args.remote_url parse_result = urlparse(url) diff --git a/gitfs/worker/fetch.py b/gitfs/worker/fetch.py index 5bea1cca..8d33a945 100644 --- a/gitfs/worker/fetch.py +++ b/gitfs/worker/fetch.py @@ -7,7 +7,8 @@ # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. @@ -21,7 +22,7 @@ class FetchWorker(Peasant): name = 'FetchWorker' - def run(self): + def work(self): while True: fetch.wait(self.timeout) diff --git a/gitfs/worker/peasant.py b/gitfs/worker/peasant.py index 61764c5c..34defb1e 100644 --- a/gitfs/worker/peasant.py +++ b/gitfs/worker/peasant.py @@ -15,6 +15,8 @@ from threading import Thread +from gitfs.log import log + class Peasant(Thread): def __init__(self, *args, **kwargs): @@ -22,3 +24,9 @@ def __init__(self, *args, **kwargs): for name, value in kwargs.iteritems(): setattr(self, name, value) + + def run(self): + try: + self.work() + except: + log.exception("A worker is not feeling well") diff --git a/gitfs/worker/sync.py b/gitfs/worker/sync.py index c0daf755..4af010ed 100644 --- a/gitfs/worker/sync.py +++ b/gitfs/worker/sync.py @@ -40,7 +40,7 @@ def __init__(self, author_name, author_email, commiter_name, self.strategy = strategy self.commits = [] - def run(self): + def work(self): while True: if shutting_down.is_set(): log.info("Stop sync worker") @@ -67,12 +67,12 @@ def on_idle(self): """ if not syncing.is_set(): - log.debug("Set syncing event (%d pending writes)", writers) + log.debug("Set syncing event (%d pending writes)", writers.value) syncing.set() else: - log.debug("Idling (%d pending writes)", writers) + log.debug("Idling (%d pending writes)", writers.value) - if writers == 0: + if writers.value == 0: if self.commits: log.info("Get some commits") self.commit(self.commits) @@ -97,8 +97,12 @@ def sync(self): if self.repository.behind: log.debug("I'm behind so I start merging") - self.merge() - need_to_push = True + try: + self.merge() + need_to_push = True + except: + log.exception("Merge failed") + return if need_to_push: try: diff --git a/tests/cache/test_gitignore.py b/tests/cache/test_gitignore.py index a8817bb5..6d2a6c27 100644 --- a/tests/cache/test_gitignore.py +++ b/tests/cache/test_gitignore.py @@ -32,9 +32,9 @@ def test_init(self): re=mocked_re): gitignore = CachedIgnore("some_file", "some_file") - assert gitignore.items == ['/.git', '.git/*', '/.git/*', '*.keep', - 'file', '/found/*', '/found', - 'found'] + assert gitignore.items == ['.git', '.git/*', '/.git/*', + '*.keep', 'file', '/found/*', + '/found', 'found'] def test_update(self): gitignore = CachedIgnore() @@ -47,5 +47,5 @@ def test_update(self): def test_contains(self): gitignore = CachedIgnore() - assert '/.git' in gitignore + assert '.git' in gitignore assert 'file' not in gitignore diff --git a/tests/utils/test_args.py b/tests/utils/test_args.py index 4f7dedad..61951956 100644 --- a/tests/utils/test_args.py +++ b/tests/utils/test_args.py @@ -35,6 +35,7 @@ def test_args(self): mocked_file.mkdtemp.return_value = "/tmp" mocked_pass.getuser.return_value = "test_user" mocked_os.getgid.return_value = 1 + mocked_os.environ = {} mocked_os.path.abspath.return_value = "abs/tmp" mocked_grp.getgrgid().gr_name = "test_group" mocked_parser.parse_args.return_value = mocked_args @@ -48,6 +49,7 @@ def test_args(self): mocked_args.user = None mocked_args.branch = None mocked_args.ssh_user = None + mocked_args.sentry_dsn = '' with patch.multiple('gitfs.utils.args', os=mocked_os, grp=mocked_grp, getpass=mocked_pass, tempfile=mocked_file, diff --git a/tests/workers/test_fetch.py b/tests/workers/test_fetch.py index 4028b981..a7d6902f 100644 --- a/tests/workers/test_fetch.py +++ b/tests/workers/test_fetch.py @@ -20,7 +20,7 @@ class TestFetchWorker(object): - def test_run(self): + def test_work(self): mocked_peasant = MagicMock() mocked_fetch = MagicMock(side_effect=ValueError) mocked_fetch_event = MagicMock() @@ -32,7 +32,7 @@ def test_run(self): worker.timeout = 5 with pytest.raises(ValueError): - worker.run() + worker.work() assert mocked_fetch.call_count == 1 mocked_fetch_event.wait.assert_called_once_with(5) diff --git a/tests/workers/test_sync.py b/tests/workers/test_sync.py index 2a8a47a7..463db3a9 100644 --- a/tests/workers/test_sync.py +++ b/tests/workers/test_sync.py @@ -22,7 +22,7 @@ class TestSyncWorker(object): - def test_run(self): + def test_work(self): mocked_queue = MagicMock() mocked_idle = MagicMock(side_effect=ValueError) @@ -34,7 +34,7 @@ def test_run(self): worker.timeout = 1 with pytest.raises(ValueError): - worker.run() + worker.work() mocked_queue.get.assert_called_once_with(timeout=1, block=True) assert mocked_idle.call_count == 1 @@ -47,7 +47,7 @@ def test_on_idle_with_commits_and_merges(self): mocked_syncing.is_set.return_value = False with patch.multiple("gitfs.worker.sync", syncing=mocked_syncing, - writers=0): + writers=MagicMock(value=0)): worker = SyncWorker("name", "email", "name", "email", strategy="strategy") worker.commits = "commits"