11from __future__ import unicode_literals
22
3+ import contextlib
34import io
45import logging
56import os
67import os .path
8+ import sqlite3
79import tempfile
810
911from cached_property import cached_property
1214from pre_commit .util import clean_path_on_failure
1315from pre_commit .util import cmd_output
1416from pre_commit .util import cwd
15- from pre_commit .util import hex_md5
1617
1718
1819logger = logging .getLogger ('pre_commit' )
@@ -27,7 +28,7 @@ def _get_default_directory():
2728 """
2829 return os .environ .get (
2930 'PRE_COMMIT_HOME' ,
30- os .path .join (os .environ [ 'HOME' ] , '.pre-commit' ),
31+ os .path .join (os .path . expanduser ( '~' ) , '.pre-commit' ),
3132 )
3233
3334
@@ -58,11 +59,35 @@ def _write_readme(self):
5859 'Learn more: https://github.com/pre-commit/pre-commit\n '
5960 )
6061
62+ def _write_sqlite_db (self ):
63+ # To avoid a race where someone ^Cs between db creation and execution
64+ # of the CREATE TABLE statement
65+ fd , tmpfile = tempfile .mkstemp ()
66+ # We'll be managing this file ourselves
67+ os .close (fd )
68+ # sqlite doesn't close its fd with its contextmanager >.<
69+ # contextlib.closing fixes this.
70+ # See: http://stackoverflow.com/a/28032829/812183
71+ with contextlib .closing (sqlite3 .connect (tmpfile )) as db :
72+ db .executescript (
73+ 'CREATE TABLE repos ('
74+ ' repo CHAR(255) NOT NULL,'
75+ ' ref CHAR(255) NOT NULL,'
76+ ' path CHAR(255) NOT NULL,'
77+ ' PRIMARY KEY (repo, ref)'
78+ ');'
79+ )
80+
81+ # Atomic file move
82+ os .rename (tmpfile , self .db_path )
83+
6184 def _create (self ):
62- if os .path .exists (self .directory ):
85+ if os .path .exists (self .db_path ):
6386 return
64- os .makedirs (self .directory )
65- self ._write_readme ()
87+ if not os .path .exists (self .directory ):
88+ os .makedirs (self .directory )
89+ self ._write_readme ()
90+ self ._write_sqlite_db ()
6691
6792 def require_created (self ):
6893 """Require the pre-commit file store to be created."""
@@ -77,9 +102,13 @@ def clone(self, url, sha):
77102 self .require_created ()
78103
79104 # Check if we already exist
80- sha_path = os .path .join (self .directory , sha + '_' + hex_md5 (url ))
81- if os .path .exists (sha_path ):
82- return os .readlink (sha_path )
105+ with sqlite3 .connect (self .db_path ) as db :
106+ result = db .execute (
107+ 'SELECT path FROM repos WHERE repo = ? AND ref = ?' ,
108+ [url , sha ],
109+ ).fetchone ()
110+ if result :
111+ return result [0 ]
83112
84113 logger .info ('Initializing environment for {0}.' .format (url ))
85114
@@ -89,8 +118,12 @@ def clone(self, url, sha):
89118 with cwd (dir ):
90119 cmd_output ('git' , 'checkout' , sha )
91120
92- # Make a symlink from sha->repo
93- os .symlink (dir , sha_path )
121+ # Update our db with the created repo
122+ with sqlite3 .connect (self .db_path ) as db :
123+ db .execute (
124+ 'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)' ,
125+ [url , sha , dir ],
126+ )
94127 return dir
95128
96129 def get_repo_path_getter (self , repo , sha ):
@@ -99,3 +132,7 @@ def get_repo_path_getter(self, repo, sha):
99132 @cached_property
100133 def cmd_runner (self ):
101134 return PrefixedCommandRunner (self .directory )
135+
136+ @cached_property
137+ def db_path (self ):
138+ return os .path .join (self .directory , 'db.db' )
0 commit comments