1313from pre_commit .util import clean_path_on_failure
1414from pre_commit .util import cmd_output
1515from pre_commit .util import resource_text
16+ from pre_commit .util import rmtree
1617
1718
1819logger = logging .getLogger ('pre_commit' )
@@ -33,10 +34,43 @@ def _get_default_directory():
3334
3435class Store (object ):
3536 get_default_directory = staticmethod (_get_default_directory )
36- __created = False
3737
3838 def __init__ (self , directory = None ):
3939 self .directory = directory or Store .get_default_directory ()
40+ self .db_path = os .path .join (self .directory , 'db.db' )
41+
42+ if not os .path .exists (self .directory ):
43+ os .makedirs (self .directory )
44+ with io .open (os .path .join (self .directory , 'README' ), 'w' ) as f :
45+ f .write (
46+ 'This directory is maintained by the pre-commit project.\n '
47+ 'Learn more: https://github.com/pre-commit/pre-commit\n ' ,
48+ )
49+
50+ if os .path .exists (self .db_path ):
51+ return
52+ with self .exclusive_lock ():
53+ # Another process may have already completed this work
54+ if os .path .exists (self .db_path ): # pragma: no cover (race)
55+ return
56+ # To avoid a race where someone ^Cs between db creation and
57+ # execution of the CREATE TABLE statement
58+ fd , tmpfile = tempfile .mkstemp (dir = self .directory )
59+ # We'll be managing this file ourselves
60+ os .close (fd )
61+ with self .connect (db_path = tmpfile ) as db :
62+ db .executescript (
63+ 'CREATE TABLE repos ('
64+ ' repo TEXT NOT NULL,'
65+ ' ref TEXT NOT NULL,'
66+ ' path TEXT NOT NULL,'
67+ ' PRIMARY KEY (repo, ref)'
68+ ');' ,
69+ )
70+ self ._create_config_table_if_not_exists (db )
71+
72+ # Atomic file move
73+ os .rename (tmpfile , self .db_path )
4074
4175 @contextlib .contextmanager
4276 def exclusive_lock (self ):
@@ -46,62 +80,30 @@ def blocked_cb(): # pragma: no cover (tests are single-process)
4680 with file_lock .lock (os .path .join (self .directory , '.lock' ), blocked_cb ):
4781 yield
4882
49- def _write_readme (self ):
50- with io .open (os .path .join (self .directory , 'README' ), 'w' ) as readme :
51- readme .write (
52- 'This directory is maintained by the pre-commit project.\n '
53- 'Learn more: https://github.com/pre-commit/pre-commit\n ' ,
54- )
55-
56- def _write_sqlite_db (self ):
57- # To avoid a race where someone ^Cs between db creation and execution
58- # of the CREATE TABLE statement
59- fd , tmpfile = tempfile .mkstemp (dir = self .directory )
60- # We'll be managing this file ourselves
61- os .close (fd )
83+ @contextlib .contextmanager
84+ def connect (self , db_path = None ):
85+ db_path = db_path or self .db_path
6286 # sqlite doesn't close its fd with its contextmanager >.<
6387 # contextlib.closing fixes this.
6488 # See: https://stackoverflow.com/a/28032829/812183
65- with contextlib .closing (sqlite3 .connect (tmpfile )) as db :
66- db .executescript (
67- 'CREATE TABLE repos ('
68- ' repo TEXT NOT NULL,'
69- ' ref TEXT NOT NULL,'
70- ' path TEXT NOT NULL,'
71- ' PRIMARY KEY (repo, ref)'
72- ');' ,
73- )
89+ with contextlib .closing (sqlite3 .connect (db_path )) as db :
90+ # this creates a transaction
91+ with db :
92+ yield db
7493
75- # Atomic file move
76- os .rename (tmpfile , self .db_path )
77-
78- def _create (self ):
79- if not os .path .exists (self .directory ):
80- os .makedirs (self .directory )
81- self ._write_readme ()
82-
83- if os .path .exists (self .db_path ):
84- return
85- with self .exclusive_lock ():
86- # Another process may have already completed this work
87- if os .path .exists (self .db_path ): # pragma: no cover (race)
88- return
89- self ._write_sqlite_db ()
90-
91- def require_created (self ):
92- """Require the pre-commit file store to be created."""
93- if not self .__created :
94- self ._create ()
95- self .__created = True
94+ @classmethod
95+ def db_repo_name (cls , repo , deps ):
96+ if deps :
97+ return '{}:{}' .format (repo , ',' .join (sorted (deps )))
98+ else :
99+ return repo
96100
97101 def _new_repo (self , repo , ref , deps , make_strategy ):
98- self .require_created ()
99- if deps :
100- repo = '{}:{}' .format (repo , ',' .join (sorted (deps )))
102+ repo = self .db_repo_name (repo , deps )
101103
102104 def _get_result ():
103105 # Check if we already exist
104- with sqlite3 .connect (self . db_path ) as db :
106+ with self .connect () as db :
105107 result = db .execute (
106108 'SELECT path FROM repos WHERE repo = ? AND ref = ?' ,
107109 (repo , ref ),
@@ -125,7 +127,7 @@ def _get_result():
125127 make_strategy (directory )
126128
127129 # Update our db with the created repo
128- with sqlite3 .connect (self . db_path ) as db :
130+ with self .connect () as db :
129131 db .execute (
130132 'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)' ,
131133 [repo , ref , directory ],
@@ -175,6 +177,43 @@ def _git_cmd(*args):
175177 'local' , C .LOCAL_REPO_VERSION , deps , make_local_strategy ,
176178 )
177179
178- @property
179- def db_path (self ):
180- return os .path .join (self .directory , 'db.db' )
180+ def _create_config_table_if_not_exists (self , db ):
181+ db .executescript (
182+ 'CREATE TABLE IF NOT EXISTS configs ('
183+ ' path TEXT NOT NULL,'
184+ ' PRIMARY KEY (path)'
185+ ');' ,
186+ )
187+
188+ def mark_config_used (self , path ):
189+ path = os .path .realpath (path )
190+ # don't insert config files that do not exist
191+ if not os .path .exists (path ):
192+ return
193+ with self .connect () as db :
194+ # TODO: eventually remove this and only create in _create
195+ self ._create_config_table_if_not_exists (db )
196+ db .execute ('INSERT OR IGNORE INTO configs VALUES (?)' , (path ,))
197+
198+ def select_all_configs (self ):
199+ with self .connect () as db :
200+ self ._create_config_table_if_not_exists (db )
201+ rows = db .execute ('SELECT path FROM configs' ).fetchall ()
202+ return [path for path , in rows ]
203+
204+ def delete_configs (self , configs ):
205+ with self .connect () as db :
206+ rows = [(path ,) for path in configs ]
207+ db .executemany ('DELETE FROM configs WHERE path = ?' , rows )
208+
209+ def select_all_repos (self ):
210+ with self .connect () as db :
211+ return db .execute ('SELECT repo, ref, path from repos' ).fetchall ()
212+
213+ def delete_repo (self , db_repo_name , ref , path ):
214+ with self .connect () as db :
215+ db .execute (
216+ 'DELETE FROM repos WHERE repo = ? and ref = ?' ,
217+ (db_repo_name , ref ),
218+ )
219+ rmtree (path )
0 commit comments