Ticket #4586: sync_using_youngest_rev_metadata-r4871.2.diff
| File sync_using_youngest_rev_metadata-r4871.2.diff, 13.8 KB (added by cboos, 5 years ago) |
|---|
-
trac/versioncontrol/api.py
137 137 """Close the connection to the repository.""" 138 138 raise NotImplementedError 139 139 140 def clear(self): 141 """Clear any data that may have been cached in instance properties.""" 140 def clear(self, youngest_rev=None): 141 """Clear any data that may have been cached in instance properties. 142 143 `youngest_rev` can be specified as a way to force the value 144 of the `youngest_rev` property (''will change in 0.12''). 145 """ 142 146 pass 143 147 144 148 def get_quickjump_entries(self, rev): … … 222 226 The way revisions are sequenced is version control specific. 223 227 By default, one assumes that the revisions are sequenced in time 224 228 (... which is ''not'' correct for most VCS, including Subversion). 229 230 (Deprecated, will not be used anymore in Trac 0.12) 225 231 """ 226 232 cursor = db.cursor() 227 233 cursor.execute("SELECT rev FROM revision ORDER BY time DESC LIMIT 1") -
trac/versioncontrol/tests/cache.py
31 31 def setUp(self): 32 32 self.db = InMemoryDatabase() 33 33 self.log = logger_factory('test') 34 cursor = self.db.cursor() 35 cursor.execute("INSERT INTO system (name, value) VALUES (%s,%s)", 36 ('youngest_rev', '')) 34 37 35 38 def test_initial_sync_with_empty_repos(self): 36 39 t = datetime(2001, 1, 1, 1, 1, 1, 0, utc) … … 96 99 "VALUES ('1',%s,%s,%s,%s,%s)", 97 100 [('trunk', 'D', 'A', None, None), 98 101 ('trunk/README', 'F', 'A', None, None)]) 102 cursor.execute("UPDATE system SET value='1' WHERE name='youngest_rev'") 99 103 100 104 changes = [('trunk/README', Node.FILE, Changeset.EDIT, 'trunk/README', 1)] 101 105 changeset = Mock(Changeset, 2, 'Update', 'joe', t3, … … 103 107 repos = Mock(Repository, 'test-repos', None, self.log, 104 108 get_changeset=lambda x: changeset, 105 109 get_youngest_rev=lambda: 2, 106 next_rev=lambda x: int(x) == 1 and 2 or None) 110 get_oldest_rev=lambda: 0, 111 normalize_rev=lambda x: x, 112 next_rev=lambda x: x and int(x) == 1 and 2 or None) 107 113 cache = CachedRepository(self.db, repos, None, self.log) 108 114 cache.sync() 109 115 … … 130 136 "VALUES ('1',%s,%s,%s,%s,%s)", 131 137 [('trunk', 'D', 'A', None, None), 132 138 ('trunk/README', 'F', 'A', None, None)]) 139 cursor.execute("UPDATE system SET value='1' WHERE name='youngest_rev'") 133 140 134 141 repos = Mock(Repository, 'test-repos', None, self.log, 135 142 get_changeset=lambda x: None, 136 143 get_youngest_rev=lambda: 1, 137 next_rev=lambda x: None, normalize_rev=lambda rev: rev) 144 get_oldest_rev=lambda: 0, 145 next_rev=lambda x: None, 146 normalize_rev=lambda rev: rev) 138 147 cache = CachedRepository(self.db, repos, None, self.log) 139 148 self.assertEqual('1', cache.youngest_rev) 140 149 changeset = cache.get_changeset(1) -
trac/versioncontrol/svn_fs.py
392 392 assert self.scope[0] == '/' 393 393 self.clear() 394 394 395 def clear(self ):395 def clear(self, youngest_rev=None): 396 396 self.youngest = None 397 if youngest_rev is not None: 398 self.youngest = self.normalize_rev(youngest_rev) 397 399 self.oldest = None 398 400 399 401 def __del__(self): -
trac/versioncontrol/cache.py
27 27 'D': Changeset.DELETE, 'E': Changeset.EDIT, 28 28 'M': Changeset.MOVE} 29 29 30 CACHE_REPOSITORY_DIR = 'repository_dir' 31 CACHE_YOUNGEST_REV = 'youngest_rev' 30 32 31 33 class CachedRepository(Repository): 32 34 … … 34 36 Repository.__init__(self, repos.name, authz, log) 35 37 self.db = db 36 38 self.repos = repos 37 try: 38 self.sync() 39 except TracError: 40 raise 41 except Exception, e: # most probably 2 concurrent resync attempts 42 log.warning('Error during sync(): %s' % e) 39 self.sync() 43 40 44 41 def close(self): 45 42 self.repos.close() … … 64 61 def sync(self): 65 62 cursor = self.db.cursor() 66 63 67 # -- repository used for populating the cache 68 cursor.execute("SELECT value FROM system WHERE name='repository_dir'") 69 for previous_repository_dir, in cursor: 70 if previous_repository_dir != self.name: 64 cursor.execute("SELECT name, value FROM system " 65 "WHERE name IN ('%s', '%s')" % 66 (CACHE_REPOSITORY_DIR, CACHE_YOUNGEST_REV)) 67 metadata = {} 68 for name, value in cursor: 69 metadata[name] = value 70 71 # -- check that we're populating the cache for the correct repository 72 repository_dir = metadata.get(CACHE_REPOSITORY_DIR) 73 if repository_dir: 74 if repository_dir != self.name: 71 75 raise TracError("The 'repository_dir' has changed, " 72 76 "a 'trac-admin resync' operation is needed.") 73 break74 77 else: # no 'repository_dir' stored yet, assume everything's OK 75 cursor.execute("INSERT INTO system (name,value) "76 "VALUES ('repository_dir',%s)", (self.name,))78 cursor.execute("INSERT INTO system (name,value) VALUES (%s,%s)", 79 (CACHE_REPOSITORY_DIR, self.name,)) 77 80 81 # -- retrieve the youngest revision cached so far 82 if CACHE_YOUNGEST_REV not in metadata: 83 # ''upgrade'' using the legacy `get_youngest_rev_in_cache` method 84 self.youngest = self.repos.get_youngest_rev_in_cache(self.db) or '' 85 cursor.execute("INSERT INTO system (name, value) VALUES (%s, %s)", 86 (CACHE_YOUNGEST_REV, self.youngest)) 87 self.log.info('Upgraded cache metadata (youngest_rev=%s)' % 88 self.youngest_rev) 89 else: 90 self.youngest = metadata[CACHE_YOUNGEST_REV] 91 92 # -- retrieve the youngest revision in the repository 78 93 self.repos.clear() 79 youngest_stored = self.repos.get_youngest_rev_in_cache(self.db)94 repos_youngest = self.repos.youngest_rev 80 95 81 if youngest_stored != str(self.repos.youngest_rev): 96 # -- compare them and try to resync if different 97 if self.youngest != str(repos_youngest): 98 if self.youngest: 99 next_youngest = self.repos.next_rev(self.youngest) 100 else: 101 next_youngest = None 102 try: 103 next_youngest = self.repos.oldest_rev 104 next_youngest = self.repos.normalize_rev(next_youngest) 105 except TracError: 106 pass 107 108 if next_youngest is None: # nothing to cache yet 109 return 110 111 # 0. first check if there's no (obvious) resync in progress 112 cursor.execute("SELECT rev FROM revision WHERE rev=%s", 113 (str(next_youngest),)) 114 for rev, in cursor: 115 # already there, but in progress, so keep ''previous'' 116 # notion of 'youngest' 117 self.repos.clear(youngest_rev=self.youngest) 118 return 119 120 # 1. prepare for resyncing 121 # (there still might be a race condition at this point) 122 82 123 authz = self.repos.authz 83 124 self.repos.authz = Authorizer() # remove permission checking 84 125 85 126 kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 86 127 actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) 87 self.log.info("Syncing with repository (%s to %s)"88 % (youngest_stored, self.repos.youngest_rev))89 if youngest_stored:90 current_rev = self.repos.next_rev(youngest_stored)91 else:92 try:93 current_rev = self.repos.oldest_rev94 current_rev = self.repos.normalize_rev(current_rev)95 except TracError:96 current_rev = None97 while current_rev is not None:98 changeset = self.repos.get_changeset(current_rev)99 cursor.execute("INSERT INTO revision (rev,time,author,message) "100 "VALUES (%s,%s,%s,%s)", (str(current_rev),101 to_timestamp(changeset.date),102 changeset.author,103 changeset.message))104 for path,kind,action,base_path,base_rev in changeset.get_changes():105 self.log.debug("Caching node change in [%s]: %s"106 % (current_rev, (path, kind, action,107 base_path, base_rev)))108 kind = kindmap[kind]109 action = actionmap[action]110 cursor.execute("INSERT INTO node_change (rev,path,"111 "node_type,change_type,base_path,base_rev) "112 "VALUES (%s,%s,%s,%s,%s,%s)",113 (str(current_rev), path, kind, action,114 base_path, base_rev))115 current_rev = self.repos.next_rev(current_rev)116 self.db.commit()117 self.repos.authz = authz # restore permission checking118 128 129 try: 130 while next_youngest is not None: 131 132 # 1.1 Attempt to resync the 'revision' table 133 self.log.info("Trying to sync revision [%s]" % 134 next_youngest) 135 cset = self.repos.get_changeset(next_youngest) 136 try: 137 cursor.execute("INSERT INTO revision " 138 " (rev,time,author,message) " 139 "VALUES (%s,%s,%s,%s)", 140 (str(next_youngest), 141 to_timestamp(cset.date), 142 cset.author, cset.message)) 143 self.db.commit() 144 except Exception, e: # *another* 1.1. resync attempt won 145 log.warning('Revision %s already cached: %s' % e) 146 # also potentially in progress, so keep ''previous'' 147 # notion of 'youngest' 148 return 149 150 # 1.2. now *only* one process was able to get there 151 # (i.e. there *shouldn't* be any race condition here) 152 153 self.youngest = str(next_youngest) 154 155 for path,kind,action,bpath,brev in cset.get_changes(): 156 self.log.debug("Caching node change in [%s]: %s" 157 % (next_youngest, 158 (path,kind,action,bpath,brev))) 159 kind = kindmap[kind] 160 action = actionmap[action] 161 cursor.execute("INSERT INTO node_change " 162 " (rev,path,node_type,change_type, " 163 " base_path,base_rev) " 164 "VALUES (%s,%s,%s,%s,%s,%s)", 165 (self.youngest, 166 path, kind, action, bpath, brev)) 167 168 # 1.3. iterate (1.1 should always succeed now) 169 next_youngest = self.repos.next_rev(next_youngest) 170 171 # 2. update 'youngest_rev' metadata (minimize failures at 0.) 172 cursor.execute("UPDATE system SET value=%s WHERE name=%s", 173 (self.youngest, CACHE_YOUNGEST_REV)) 174 self.db.commit() 175 finally: 176 # 3. restore permission checking (after 1.) 177 self.repos.authz = authz 178 119 179 def get_node(self, path, rev=None): 120 180 return self.repos.get_node(path, rev) 121 181 … … 126 186 return self.repos.oldest_rev 127 187 128 188 def get_youngest_rev(self): 129 return self. repos.get_youngest_rev_in_cache(self.db)189 return self.youngest 130 190 131 191 def previous_rev(self, rev): 132 192 return self.repos.previous_rev(rev) -
trac/admin/console.py
600 600 cursor.execute("DELETE FROM revision") 601 601 cursor.execute("DELETE FROM node_change") 602 602 cursor.execute("DELETE FROM system WHERE name='repository_dir'") 603 cursor.execute("DELETE FROM system WHERE name='youngest_rev'") 604 cursor.execute("INSERT INTO system (name, value) " 605 "VALUES ('youngest_rev', '')") 603 606 repos = self.__env.get_repository() # this will do the sync() 604 607 print 'Done.' 605 608
