Skip to content

Commit d589334

Browse files
committed
Merge pull request presslabs#172 from PressLabs/0.2.4
v0.2.4 changeset * added `hard_ignore` and `ignore_file` options * fix `link` syscall * made `.gitmodules` read-only
2 parents d84e0ae + 4e34c38 commit d589334

File tree

10 files changed

+153
-36
lines changed

10 files changed

+153
-36
lines changed

gitfs/cache/gitignore.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,25 @@
1919

2020

2121
class CachedIgnore(object):
22-
def __init__(self, ignore=False, submodules=False, path="."):
22+
def __init__(self, ignore=False, submodules=False, exclude=False,
23+
hard_ignore=None):
2324
self.items = []
2425

25-
self.ignore = False
26-
if ignore:
27-
self.ignore = os.path.join(path, ".gitignore")
28-
29-
self.submodules = False
30-
if submodules:
31-
self.submodules = os.path.join(path, ".gitmodules")
26+
self.ignore = ignore
27+
self.submodules = submodules
28+
self.exclude = exclude
3229

3330
self.cache = {}
3431
self.permanent = []
32+
self.hard_ignore = self._parse_hard_ignore(hard_ignore)
3533

3634
self.update()
3735

3836
def update(self):
39-
self.items = ['.git', '.git/*', '/.git/*', '*.keep']
37+
self.items = ['.git', '.git/*', '/.git/*', '*.keep', '*.gitmodules']
4038

41-
if self.ignore and os.path.exists(self.ignore):
42-
with open(self.ignore) as gitignore:
43-
for item in gitignore.readlines():
44-
item = item.strip()
45-
if item and not item.startswith('#'):
46-
self.items += item
39+
self.items += self._parse_ignore_file(self.ignore)
40+
self.items += self._parse_ignore_file(self.exclude)
4741

4842
if self.submodules and os.path.exists(self.submodules):
4943
with open(self.submodules) as submodules:
@@ -56,6 +50,24 @@ def update(self):
5650
self.items.append("%s" % result[2])
5751

5852
self.cache = {}
53+
self.items += self.hard_ignore
54+
55+
def _parse_ignore_file(self, ignore_file):
56+
items = []
57+
58+
if ignore_file and os.path.exists(ignore_file):
59+
with open(ignore_file) as gitignore:
60+
for item in gitignore.readlines():
61+
item = item.strip()
62+
if item and not item.startswith('#'):
63+
items.append(item)
64+
return items
65+
66+
def _parse_hard_ignore(self, hard_ignore):
67+
if isinstance(hard_ignore, basestring):
68+
return hard_ignore.split("|")
69+
else:
70+
return []
5971

6072
def __contains__(self, path):
6173
return self.check_key(path)

gitfs/mounter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ def prepare_components(args):
6161
max_size=args.max_size * 1024 * 1024,
6262
max_offset=args.max_size * 1024 * 1024,
6363
commit_queue=commit_queue,
64-
credentials=credentials)
64+
credentials=credentials,
65+
ignore_file=args.ignore_file,
66+
hard_ignore=args.hard_ignore)
6567

6668
# register all the routes
6769
router.register(routes)

gitfs/router.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414

1515

1616
import re
17-
import inspect
18-
import shutil
17+
import os
1918
import time
19+
import shutil
20+
import inspect
2021

2122
from pwd import getpwnam
2223
from grp import getgrnam
@@ -59,8 +60,13 @@ def __init__(self, remote_url, repo_path, mount_path, credentials,
5960
log.info('Done cloning')
6061

6162
self.repo.credentials = credentials
62-
self.repo.ignore = CachedIgnore(submodules=True, ignore=True,
63-
path=self.repo_path)
63+
64+
submodules = os.path.join(self.repo_path, '.gitmodules')
65+
ignore = os.path.join(self.repo_path, '.gitignore')
66+
self.repo.ignore = CachedIgnore(submodules=submodules,
67+
ignore=ignore,
68+
exclude=kwargs['ignore_file'] or None,
69+
hard_ignore=kwargs['hard_ignore'])
6470

6571
self.uid = getpwnam(user).pw_uid
6672
self.gid = getgrnam(group).gr_gid

gitfs/utils/args.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ def __init__(self, parser):
5353
("log_level", ("warning", "string")),
5454
("cache_size", (800, "int")),
5555
("sentry_dsn", (self.get_sentry_dsn, "string")),
56+
("ignore_file", ("", "string")),
57+
("hard_ignore", ("", "string")),
5658
])
5759
self.config = self.build_config(parser.parse_args())
5860

gitfs/views/current.py

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ def symlink(self, name, target):
6161
log.debug("CurrentView: Created symlink to %s from %s", name, target)
6262
return result
6363

64+
@write_operation
65+
@not_in("ignore", check=["target"])
66+
def link(self, name, target):
67+
if target.startswith('/current/'):
68+
target = target.replace('/current/', '/')
69+
70+
result = super(CurrentView, self).link(target, name)
71+
72+
message = "Create link to %s for %s" % (target, name)
73+
self._stage(add=name, message=message)
74+
75+
log.debug("CurrentView: Created link to %s from %s", name, target)
76+
return result
77+
6478
def readlink(self, path):
6579
log.debug("CurrentView: Read link %s", path)
6680
return os.readlink(self.repo._full_path(path))
@@ -241,21 +255,52 @@ def unlink(self, path):
241255
def _stage(self, message, add=None, remove=None):
242256
non_empty = False
243257

244-
add = self._sanitize(add)
245-
remove = self._sanitize(remove)
246-
247258
if remove is not None:
248-
self.repo.index.remove(self._sanitize(remove))
259+
remove = self._sanitize(remove)
260+
if add is not None:
261+
add = self._sanitize(add)
262+
paths = self._get_files_from_path(add)
263+
if paths:
264+
for path in paths:
265+
path = path.replace("%s/" % add, "%s/" % remove)
266+
self.repo.index.remove(path)
267+
else:
268+
self.repo.index.remove(remove)
269+
else:
270+
self.repo.index.remove(remove)
249271
non_empty = True
250272

251273
if add is not None:
252-
self.repo.index.add(self._sanitize(add))
274+
add = self._sanitize(add)
275+
paths = self._get_files_from_path(add)
276+
if paths:
277+
for path in paths:
278+
self.repo.index.add(path)
279+
else:
280+
self.repo.index.add(add)
253281
non_empty = True
254282

255283
if non_empty:
256284
self.queue.commit(add=add, remove=remove, message=message)
257285

286+
def _get_files_from_path(self, path):
287+
paths = []
288+
289+
full_path = self.repo._full_path(self._sanitize(path))
290+
workdir = self.repo._repo.workdir
291+
292+
if os.path.isdir(full_path):
293+
for (dirpath, dirs, files) in os.walk(full_path):
294+
for filename in files:
295+
paths.append("%s/%s" % (dirpath.replace(workdir, ''),
296+
filename))
297+
return paths
298+
258299
def _sanitize(self, path):
259-
if path is not None and path.startswith("/"):
260-
path = path[1:]
300+
if path is None:
301+
return path
302+
303+
if path.startswith("/"):
304+
return path[1:]
305+
261306
return path

tests/cache/test_gitignore.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def test_init(self):
3333
gitignore = CachedIgnore("some_file", "some_file")
3434

3535
assert gitignore.items == ['.git', '.git/*', '/.git/*',
36-
'*.keep', '/found/*',
37-
'/found', 'found']
36+
'*.keep', '*.gitmodules',
37+
'/found/*', '/found', 'found']
3838

3939
def test_update(self):
4040
gitignore = CachedIgnore()

tests/integrations/current/test_write.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,44 @@
1919
import string
2020
import shutil
2121

22-
import pytest
23-
2422
from tests.integrations.base import BaseTest, pull
2523

2624

2725
class TestWriteCurrentView(BaseTest):
26+
def test_rename_directory(self):
27+
old_dir = "%s/a_directory/" % self.current_path
28+
new_dir = "%s/some_directory/" % self.current_path
29+
os.makedirs(old_dir)
30+
31+
time.sleep(5)
32+
with pull(self.sh):
33+
self.assert_new_commit()
34+
35+
os.rename(old_dir, new_dir)
36+
37+
time.sleep(5)
38+
with pull(self.sh):
39+
self.assert_new_commit()
40+
41+
assert os.path.isdir(new_dir) is not False
42+
assert os.path.exists(old_dir) is False
43+
44+
def test_link_a_file(self):
45+
filename = "%s/link_file" % self.current_path
46+
link_name = "%s/new_link" % self.current_path
47+
48+
with open(filename, "w") as f:
49+
f.write("some content")
50+
51+
os.link(filename, link_name)
52+
53+
time.sleep(5)
54+
with pull(self.sh):
55+
self.assert_commit_message("Update 2 items")
56+
57+
is_link = os.path.isfile(link_name)
58+
assert is_link is not False
59+
2860
def test_write_a_file(self):
2961
content = "Just a small file"
3062
filename = "%s/new_file" % self.current_path

tests/test_mount.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ def test_prepare_components(self):
6060
'merge_timeout': 10,
6161
'commiter_name': 'commit',
6262
'commiter_email': 'commiter@commiting.org',
63-
'log': 'syslog'
63+
'log': 'syslog',
64+
'ignore_file': '',
65+
'module_file': '',
66+
'hard_ignore': None,
6467
})
6568

6669
mocked_argparse.Argumentparser.return_value = mocked_parser
@@ -91,7 +94,7 @@ def test_prepare_components(self):
9194
'branch': 'branch',
9295
'timeout': 10,
9396
'repo_path': 'repo_path',
94-
'commit_queue': mocked_queue
97+
'commit_queue': mocked_queue,
9598
}
9699
mocked_merger.assert_called_once_with('commit',
97100
'commiter@commiting.org',

tests/test_router.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ def get_new_router(self):
7272
'commit_queue': mocked_queue,
7373
'max_size': 10,
7474
'max_offset': 10,
75+
'ignore_file': '',
76+
'module_file': '',
77+
'hard_ignore': None,
7578
}
7679

7780
with patch.multiple('gitfs.router', Repository=mocked_repository,
@@ -92,9 +95,10 @@ def test_constructor(self):
9295
mocks['branch'], mocks['credentials'])
9396
mocks['repository'].clone.assert_called_once_with(*asserted_call)
9497
mocks['ignore'].assert_called_once_with(**{
95-
'submodules': True,
96-
'ignore': True,
97-
'path': mocks['repo_path'],
98+
'ignore': 'repository_path/.gitignore',
99+
'exclude': None,
100+
'hard_ignore': None,
101+
'submodules': 'repository_path/.gitmodules'
98102
})
99103
mocks['getpwnam'].assert_called_once_with(mocks['user'])
100104
mocks['getgrnam'].assert_called_once_with(mocks['group'])

tests/views/test_current.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ def test_write_in_git_dir(self):
136136
ignore=CachedIgnore())
137137
current.write(".git/index", "buf", "offset", 1)
138138

139+
def test_write_in_modules_dir(self):
140+
with pytest.raises(FuseOSError):
141+
current = CurrentView(repo="repo", uid=1, gid=1,
142+
repo_path="repo_path",
143+
read_only=Event(),
144+
ignore=CachedIgnore())
145+
current.write(".gitmodules", "buf", "offset", 1)
146+
139147
def test_write_to_large_file(self):
140148
current = CurrentView(repo="repo", uid=1, gid=1,
141149
repo_path="repo_path",
@@ -360,13 +368,15 @@ def test_stage(self):
360368
mocked_repo = MagicMock()
361369
mocked_sanitize = MagicMock()
362370
mocked_queue = MagicMock()
371+
mocked_files = MagicMock(return_value=None)
363372

364373
mocked_sanitize.return_value = ["to-stage"]
365374

366375
current = CurrentView(repo=mocked_repo,
367376
repo_path="repo_path",
368377
queue=mocked_queue, ignore=CachedIgnore())
369378
current._sanitize = mocked_sanitize
379+
current._get_files_from_path = mocked_files
370380
current._stage("message", ["add"], ["remove"])
371381

372382
mocked_queue.commit.assert_called_once_with(add=['to-stage'],
@@ -375,6 +385,7 @@ def test_stage(self):
375385
mocked_repo.index.add.assert_called_once_with(["to-stage"])
376386
mocked_repo.index.remove.assert_called_once_with(["to-stage"])
377387

388+
mocked_files.has_calls([call(['add'])])
378389
mocked_sanitize.has_calls([call(['add']), call(['remove'])])
379390

380391
def test_sanitize(self):

0 commit comments

Comments
 (0)