Fix git branch name sometimes getting stuck with inotify

If you try to checkout the already current branch in git, git creates
HEAD.lock and renames it to HEAD. This causes the inode of HEAD to
change and so the inotify file watcher stops tracking HEAD.

The fix is to re-create the inotify watch when the file attributes
change. This is a bit of a performance penalty as most of the time the
attribute changes are simple last modified time/size changes, but since
inotify provides no way to know specifically when the inode has changed,
this is the best we can do.
This commit is contained in:
Kovid Goyal 2013-09-25 10:27:48 +05:30
parent 70e279afde
commit 7423b55cc4
3 changed files with 38 additions and 4 deletions

View File

@ -54,7 +54,24 @@ class INotifyWatch(INotify):
self.modified.pop(path, None)
self.last_query.pop(path, None)
else:
self.modified[path] = True
if mask & self.ATTRIB:
# The watched file could have had its inode changed, in
# which case we will not get any more events for this
# file, so re-register the watch. For example by some
# other file being renamed as this file.
try:
self.unwatch(path)
except OSError:
pass
try:
self.watch(path)
except OSError as e:
if getattr(e, 'errno', None) != errno.ENOENT:
raise
else:
self.modified[path] = True
else:
self.modified[path] = True
def unwatch(self, path):
''' Remove the watch for path. Raises an OSError if removing the watch

View File

@ -31,6 +31,15 @@ def file_watcher():
_file_watcher = create_file_watcher()
return _file_watcher
_branch_watcher = None
def branch_watcher():
global _branch_watcher
if _branch_watcher is None:
from powerline.lib.file_watcher import create_file_watcher
_branch_watcher = create_file_watcher()
return _branch_watcher
branch_name_cache = {}
branch_lock = Lock()
file_status_lock = Lock()
@ -39,7 +48,7 @@ def get_branch_name(directory, config_file, get_func):
global branch_name_cache
with branch_lock:
# Check if the repo directory was moved/deleted
fw = file_watcher()
fw = branch_watcher()
is_watched = fw.is_watched(directory)
try:
changed = fw(directory)

View File

@ -54,10 +54,11 @@ class TestFilesystemWatchers(TestCase):
w = create_file_watcher(use_stat=False)
if w.is_stat_based:
raise SkipTest('This test is not suitable for a stat based file watcher')
f1, f2 = os.path.join(INOTIFY_DIR, 'file1'), os.path.join(INOTIFY_DIR, 'file2')
f1, f2, f3 = map(lambda x: os.path.join(INOTIFY_DIR, 'file%d' % x), (1, 2, 3))
with open(f1, 'wb'):
with open(f2, 'wb'):
pass
with open(f3, 'wb'):
pass
ne = os.path.join(INOTIFY_DIR, 'notexists')
self.assertRaises(OSError, w, ne)
self.assertTrue(w(f1))
@ -85,6 +86,13 @@ class TestFilesystemWatchers(TestCase):
# Check that deleting a file registers as a change
os.unlink(f1)
self.do_test_for_change(w, f1)
# Test that changing the inode of a file does not cause it to stop
# being watched
os.rename(f3, f2)
self.do_test_for_change(w, f2)
self.assertFalse(w(f2), 'Spurious change detected')
os.utime(f2, None)
self.do_test_for_change(w, f2)
def test_tree_watcher(self):
from powerline.lib.tree_watcher import TreeWatcher