Skip to content

Commit a79cf67

Browse files
committed
repo-TCs, #519: FIX config resource leaks
+ Modify lock/read-config-file code to ensure files closed. + Use `with GitConfigarser()` more systematically in TCs. + Clear any locks left hanging from prev Tcs. + Util: mark lock-files as SHORT_LIVED; save some SSDs...
1 parent 13d399f commit a79cf67

File tree

5 files changed

+58
-52
lines changed

5 files changed

+58
-52
lines changed

Diff for: git/repo/base.py

+6-12
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,13 @@ def __hash__(self):
210210
# Description property
211211
def _get_description(self):
212212
filename = join(self.git_dir, 'description')
213-
return open(filename, 'rb').read().rstrip().decode(defenc)
213+
with open(filename, 'rb') as fp:
214+
return fp.read().rstrip().decode(defenc)
214215

215216
def _set_description(self, descr):
216217
filename = join(self.git_dir, 'description')
217-
open(filename, 'wb').write((descr + '\n').encode(defenc))
218+
with open(filename, 'wb') as fp:
219+
fp.write((descr + '\n').encode(defenc))
218220

219221
description = property(_get_description, _set_description,
220222
doc="the project's description")
@@ -548,11 +550,8 @@ def _get_alternates(self):
548550
alternates_path = join(self.git_dir, 'objects', 'info', 'alternates')
549551

550552
if os.path.exists(alternates_path):
551-
try:
552-
f = open(alternates_path, 'rb')
553+
with open(alternates_path, 'rb') as f:
553554
alts = f.read().decode(defenc)
554-
finally:
555-
f.close()
556555
return alts.strip().splitlines()
557556
else:
558557
return list()
@@ -573,13 +572,8 @@ def _set_alternates(self, alts):
573572
if isfile(alternates_path):
574573
os.remove(alternates_path)
575574
else:
576-
try:
577-
f = open(alternates_path, 'wb')
575+
with open(alternates_path, 'wb') as f:
578576
f.write("\n".join(alts).encode(defenc))
579-
finally:
580-
f.close()
581-
# END file handling
582-
# END alts handling
583577

584578
alternates = property(_get_alternates, _set_alternates,
585579
doc="Retrieve a list of alternates paths or set a list paths to be used as alternates")

Diff for: git/repo/fun.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525

2626

2727
def touch(filename):
28-
fp = open(filename, "ab")
29-
fp.close()
28+
with open(filename, "ab"):
29+
pass
3030
return filename
3131

3232

Diff for: git/test/test_config.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
from git import (
1212
GitConfigParser
1313
)
14-
from git.compat import (
15-
string_types)
14+
from git.compat import string_types
1615
from git.config import cp
1716
from git.test.lib import (
1817
TestCase,

Diff for: git/test/test_repo.py

+45-35
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,14 @@
44
#
55
# This module is part of GitPython and is released under
66
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
7+
import glob
8+
from io import BytesIO
9+
import itertools
10+
import os
711
import pickle
12+
import sys
13+
import tempfile
814

9-
from git.test.lib import (
10-
patch,
11-
TestBase,
12-
with_rw_repo,
13-
fixture,
14-
assert_false,
15-
assert_equal,
16-
assert_true,
17-
raises
18-
)
1915
from git import (
2016
InvalidGitRepositoryError,
2117
Repo,
@@ -33,23 +29,28 @@
3329
BadName,
3430
GitCommandError
3531
)
36-
from git.repo.fun import touch
37-
from git.util import join_path_native, rmtree
32+
from git.compat import string_types
3833
from git.exc import (
3934
BadObject,
4035
)
41-
from gitdb.util import bin_to_hex
42-
from git.compat import string_types
36+
from git.repo.fun import touch
37+
from git.test.lib import (
38+
patch,
39+
TestBase,
40+
with_rw_repo,
41+
fixture,
42+
assert_false,
43+
assert_equal,
44+
assert_true,
45+
raises
46+
)
4347
from git.test.lib import with_rw_directory
44-
45-
import os
46-
import sys
47-
import tempfile
48-
import itertools
49-
from io import BytesIO
50-
48+
from git.util import join_path_native, rmtree, rmfile
49+
from gitdb.util import bin_to_hex
5150
from nose import SkipTest
5251

52+
import os.path as osp
53+
5354

5455
def iter_flatten(lol):
5556
for items in lol:
@@ -61,9 +62,23 @@ def flatten(lol):
6162
return list(iter_flatten(lol))
6263

6364

65+
_tc_lock_fpaths = osp.join(osp.dirname(__file__), '../../.git/*.lock')
66+
67+
68+
def _rm_lock_files():
69+
for lfp in glob.glob(_tc_lock_fpaths):
70+
rmfile(lfp)
71+
72+
6473
class TestRepo(TestBase):
6574

75+
def setUp(self):
76+
_rm_lock_files()
77+
6678
def tearDown(self):
79+
for lfp in glob.glob(_tc_lock_fpaths):
80+
if osp.isfile(lfp):
81+
raise AssertionError('Previous TC left hanging git-lock file: %s', lfp)
6782
import gc
6883
gc.collect()
6984

@@ -309,10 +324,9 @@ def test_tag(self):
309324

310325
def test_archive(self):
311326
tmpfile = tempfile.mktemp(suffix='archive-test')
312-
stream = open(tmpfile, 'wb')
313-
self.rorepo.archive(stream, '0.1.6', path='doc')
314-
assert stream.tell()
315-
stream.close()
327+
with open(tmpfile, 'wb') as stream:
328+
self.rorepo.archive(stream, '0.1.6', path='doc')
329+
assert stream.tell()
316330
os.remove(tmpfile)
317331

318332
@patch.object(Git, '_call_process')
@@ -401,9 +415,8 @@ def test_untracked_files(self, rwrepo):
401415

402416
num_recently_untracked = 0
403417
for fpath in files:
404-
fd = open(fpath, "wb")
405-
fd.close()
406-
# END for each filename
418+
with open(fpath, "wb"):
419+
pass
407420
untracked_files = rwrepo.untracked_files
408421
num_recently_untracked = len(untracked_files)
409422

@@ -426,19 +439,16 @@ def test_config_reader(self):
426439
def test_config_writer(self):
427440
for config_level in self.rorepo.config_level:
428441
try:
429-
writer = self.rorepo.config_writer(config_level)
430-
assert not writer.read_only
431-
writer.release()
442+
with self.rorepo.config_writer(config_level) as writer:
443+
self.assertFalse(writer.read_only)
432444
except IOError:
433445
# its okay not to get a writer for some configuration files if we
434446
# have no permissions
435447
pass
436-
# END for each config level
437448

438449
def test_config_level_paths(self):
439450
for config_level in self.rorepo.config_level:
440451
assert self.rorepo._get_config_path(config_level)
441-
# end for each config level
442452

443453
def test_creation_deletion(self):
444454
# just a very quick test to assure it generally works. There are
@@ -448,8 +458,8 @@ def test_creation_deletion(self):
448458

449459
tag = self.rorepo.create_tag("new_tag", "HEAD~2")
450460
self.rorepo.delete_tag(tag)
451-
writer = self.rorepo.config_writer()
452-
writer.release()
461+
with self.rorepo.config_writer():
462+
pass
453463
remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")
454464
self.rorepo.delete_remote(remote)
455465

Diff for: git/util.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,10 @@ def _obtain_lock_or_raise(self):
574574
(self._file_path, lock_file))
575575

576576
try:
577-
fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0)
577+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
578+
if is_win:
579+
flags |= getattr(os, 'O_SHORT_LIVED')
580+
fd = os.open(lock_file, flags, 0)
578581
os.close(fd)
579582
except OSError as e:
580583
raise IOError(str(e))

0 commit comments

Comments
 (0)