1010from cached_property import cached_property
1111
1212import pre_commit .constants as C
13+ from pre_commit import file_lock
1314from pre_commit .prefixed_command_runner import PrefixedCommandRunner
1415from pre_commit .util import clean_path_on_failure
1516from pre_commit .util import cmd_output
@@ -37,13 +38,20 @@ def _get_default_directory():
3738
3839class Store (object ):
3940 get_default_directory = staticmethod (_get_default_directory )
41+ __created = False
4042
4143 def __init__ (self , directory = None ):
4244 if directory is None :
4345 directory = self .get_default_directory ()
4446
4547 self .directory = directory
46- self .__created = False
48+
49+ @contextlib .contextmanager
50+ def exclusive_lock (self , quiet = False ):
51+ if not quiet :
52+ logger .info ('Locking pre-commit directory' )
53+ with file_lock .lock (os .path .join (self .directory , '.lock' )):
54+ yield
4755
4856 def _write_readme (self ):
4957 with io .open (os .path .join (self .directory , 'README' ), 'w' ) as readme :
@@ -75,12 +83,17 @@ def _write_sqlite_db(self):
7583 os .rename (tmpfile , self .db_path )
7684
7785 def _create (self ):
78- if os .path .exists (self .db_path ):
79- return
8086 if not os .path .exists (self .directory ):
8187 os .makedirs (self .directory )
8288 self ._write_readme ()
83- self ._write_sqlite_db ()
89+
90+ if os .path .exists (self .db_path ):
91+ return
92+ with self .exclusive_lock (quiet = True ):
93+ # Another process may have already completed this work
94+ if os .path .exists (self .db_path ): # pragma: no cover (race)
95+ return
96+ self ._write_sqlite_db ()
8497
8598 def require_created (self ):
8699 """Require the pre-commit file store to be created."""
@@ -91,27 +104,37 @@ def require_created(self):
91104 def _new_repo (self , repo , ref , make_strategy ):
92105 self .require_created ()
93106
94- # Check if we already exist
95- with sqlite3 .connect (self .db_path ) as db :
96- result = db .execute (
97- 'SELECT path FROM repos WHERE repo = ? AND ref = ?' ,
98- [repo , ref ],
99- ).fetchone ()
100- if result :
101- return result [0 ]
102-
103- logger .info ('Initializing environment for {}.' .format (repo ))
104-
105- directory = tempfile .mkdtemp (prefix = 'repo' , dir = self .directory )
106- with clean_path_on_failure (directory ):
107- make_strategy (directory )
108-
109- # Update our db with the created repo
110- with sqlite3 .connect (self .db_path ) as db :
111- db .execute (
112- 'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)' ,
113- [repo , ref , directory ],
114- )
107+ def _get_result ():
108+ # Check if we already exist
109+ with sqlite3 .connect (self .db_path ) as db :
110+ result = db .execute (
111+ 'SELECT path FROM repos WHERE repo = ? AND ref = ?' ,
112+ [repo , ref ],
113+ ).fetchone ()
114+ if result :
115+ return result [0 ]
116+
117+ result = _get_result ()
118+ if result :
119+ return result
120+ with self .exclusive_lock ():
121+ # Another process may have already completed this work
122+ result = _get_result ()
123+ if result : # pragma: no cover (race)
124+ return result
125+
126+ logger .info ('Initializing environment for {}.' .format (repo ))
127+
128+ directory = tempfile .mkdtemp (prefix = 'repo' , dir = self .directory )
129+ with clean_path_on_failure (directory ):
130+ make_strategy (directory )
131+
132+ # Update our db with the created repo
133+ with sqlite3 .connect (self .db_path ) as db :
134+ db .execute (
135+ 'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)' ,
136+ [repo , ref , directory ],
137+ )
115138 return directory
116139
117140 def clone (self , repo , ref ):
0 commit comments