diff --git a/trac/db_default.py b/trac/db_default.py
|
a
|
b
|
|
| 17 | 17 | from trac.db import Table, Column, Index |
| 18 | 18 | |
| 19 | 19 | # Database version identifier. Used for automatic upgrades. |
| 20 | | db_version = 25 |
| | 20 | db_version = 26 |
| 21 | 21 | |
| 22 | 22 | def __mkreports(reports): |
| 23 | 23 | """Utility function used to create report data in same syntax as the |
diff --git a/trac/upgrades/db26.py b/trac/upgrades/db26.py
new file mode 100644
|
-
|
+
|
|
| | 1 | |
| | 2 | def do_upgrade(env, ver, cursor): |
| | 3 | """Zero-pad Subversion revision numbers in the cache.""" |
| | 4 | cursor.execute(""" |
| | 5 | SELECT id, value FROM repository WHERE name='repository_dir' |
| | 6 | """) |
| | 7 | for id in [id for id, dir in cursor if dir.startswith('svn:')]: |
| | 8 | cursor.execute("SELECT DISTINCT rev FROM revision WHERE repos=%s", |
| | 9 | (id,)) |
| | 10 | for rev in set(row[0] for row in cursor): |
| | 11 | cursor.execute(""" |
| | 12 | UPDATE revision SET rev=%s WHERE repos=%s AND rev=%s |
| | 13 | """, ('%010d' % int(rev), id, rev)) |
| | 14 | |
| | 15 | cursor.execute("SELECT DISTINCT rev FROM node_change WHERE repos=%s", |
| | 16 | (id,)) |
| | 17 | for rev in set(row[0] for row in cursor): |
| | 18 | cursor.execute(""" |
| | 19 | UPDATE node_change SET rev=%s WHERE repos=%s AND rev=%s |
| | 20 | """, ('%010d' % int(rev), id, rev)) |
diff --git a/trac/versioncontrol/cache.py b/trac/versioncontrol/cache.py
|
a
|
b
|
|
| 83 | 83 | |
| 84 | 84 | def sync_changeset(self, rev): |
| 85 | 85 | cset = self.repos.get_changeset(rev) |
| | 86 | srev = '%010d' % cset.rev |
| 86 | 87 | old_cset = [None] |
| 87 | 88 | |
| 88 | 89 | @with_transaction(self.env) |
| … |
… |
|
| 91 | 92 | cursor.execute(""" |
| 92 | 93 | SELECT time,author,message FROM revision |
| 93 | 94 | WHERE repos=%s AND rev=%s |
| 94 | | """, (self.id, str(cset.rev))) |
| | 95 | """, (self.id, srev)) |
| 95 | 96 | for time, author, message in cursor: |
| 96 | 97 | old_cset[0] = Changeset(self.repos, cset.rev, message, author, |
| 97 | 98 | from_utimestamp(time)) |
| … |
… |
|
| 99 | 100 | UPDATE revision SET time=%s, author=%s, message=%s |
| 100 | 101 | WHERE repos=%s AND rev=%s |
| 101 | 102 | """, (to_utimestamp(cset.date), cset.author, cset.message, |
| 102 | | self.id, str(cset.rev))) |
| | 103 | self.id, srev)) |
| 103 | 104 | return old_cset[0] |
| 104 | 105 | |
| 105 | 106 | def _metadata(self, db): |
| … |
… |
|
| 205 | 206 | |
| 206 | 207 | if next_youngest is None: # nothing to cache yet |
| 207 | 208 | return |
| | 209 | srev = '%010d' % next_youngest |
| 208 | 210 | |
| 209 | 211 | # 0. first check if there's no (obvious) resync in progress |
| 210 | 212 | cursor.execute(""" |
| 211 | 213 | SELECT rev FROM revision WHERE repos=%s AND rev=%s |
| 212 | | """, (self.id, str(next_youngest))) |
| | 214 | """, (self.id, srev)) |
| 213 | 215 | for rev, in cursor: |
| 214 | 216 | # already there, but in progress, so keep ''previous'' |
| 215 | 217 | # notion of 'youngest' |
| … |
… |
|
| 223 | 225 | actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) |
| 224 | 226 | |
| 225 | 227 | while next_youngest is not None: |
| | 228 | srev = '%010d' % next_youngest |
| 226 | 229 | |
| 227 | 230 | # 1.1 Attempt to resync the 'revision' table |
| 228 | 231 | self.log.info("Trying to sync revision [%s]", |
| … |
… |
|
| 233 | 236 | INSERT INTO revision |
| 234 | 237 | (repos,rev,time,author,message) |
| 235 | 238 | VALUES (%s,%s,%s,%s,%s) |
| 236 | | """, (self.id, str(next_youngest), |
| 237 | | to_utimestamp(cset.date), |
| | 239 | """, (self.id, srev, to_utimestamp(cset.date), |
| 238 | 240 | cset.author, cset.message)) |
| 239 | 241 | except Exception, e: # *another* 1.1. resync attempt won |
| 240 | 242 | self.log.warning('Revision %s already cached: %r', |
| … |
… |
|
| 251 | 253 | for path, kind, action, bpath, brev in cset.get_changes(): |
| 252 | 254 | self.log.debug("Caching node change in [%s]: %r", |
| 253 | 255 | next_youngest, |
| 254 | | (path,kind,action,bpath,brev)) |
| | 256 | (path, kind, action, bpath, brev)) |
| 255 | 257 | kind = kindmap[kind] |
| 256 | 258 | action = actionmap[action] |
| 257 | 259 | cursor.execute(""" |
| … |
… |
|
| 259 | 261 | (repos,rev,path,node_type, |
| 260 | 262 | change_type,base_path,base_rev) |
| 261 | 263 | VALUES (%s,%s,%s,%s,%s,%s,%s) |
| 262 | | """, (self.id, str(next_youngest), |
| 263 | | path, kind, action, bpath, brev)) |
| | 264 | """, (self.id, srev, path, kind, action, bpath, brev)) |
| 264 | 265 | |
| 265 | 266 | # 1.3. iterate (1.1 should always succeed now) |
| 266 | 267 | youngest = next_youngest |
| … |
… |
|
| 286 | 287 | revisions. |
| 287 | 288 | """ |
| 288 | 289 | last = self.normalize_rev(last) |
| 289 | | node = self.get_node(path, last) # Check node existence and perms |
| | 290 | slast = '%010d' % last |
| | 291 | node = self.get_node(path, last) # Check node existence |
| 290 | 292 | db = self.env.get_db_cnx() |
| 291 | 293 | cursor = db.cursor() |
| 292 | | rev_as_int = db.cast('rev', 'int') |
| 293 | 294 | if first is None: |
| 294 | 295 | cursor.execute("SELECT rev FROM node_change " |
| 295 | | "WHERE repos=%%s AND %s<=%%s " |
| 296 | | " AND path=%%s " |
| | 296 | "WHERE repos=%s AND rev<=%s " |
| | 297 | " AND path=%s " |
| 297 | 298 | " AND change_type IN ('A', 'C', 'M') " |
| 298 | | "ORDER BY %s DESC " |
| 299 | | "LIMIT 1" % ((rev_as_int,) * 2), |
| 300 | | (self.id, last, path)) |
| | 299 | "ORDER BY rev DESC LIMIT 1", |
| | 300 | (self.id, slast, path)) |
| 301 | 301 | first = 0 |
| 302 | 302 | for row in cursor: |
| 303 | 303 | first = int(row[0]) |
| | 304 | sfirst = '%010d' % first |
| 304 | 305 | cursor.execute("SELECT DISTINCT rev FROM node_change " |
| 305 | | "WHERE repos=%%s AND %s>=%%s AND %s<=%%s " |
| 306 | | " AND (path=%%s OR path %s)" % |
| 307 | | (rev_as_int, rev_as_int, db.like()), |
| 308 | | (self.id, first, last, path, |
| | 306 | "WHERE repos=%%s AND rev>=%%s AND rev<=%%s " |
| | 307 | " AND (path=%%s OR path %s)" % db.like(), |
| | 308 | (self.id, sfirst, slast, path, |
| 309 | 309 | db.like_escape(path + '/') + '%')) |
| 310 | 310 | return [int(row[0]) for row in cursor] |
| 311 | 311 | |
| … |
… |
|
| 331 | 331 | return self.repos.next_rev(self.normalize_rev(rev), path) |
| 332 | 332 | |
| 333 | 333 | def _next_prev_rev(self, direction, rev, path=''): |
| | 334 | srev = '%010d' % rev |
| 334 | 335 | db = self.env.get_db_cnx() |
| 335 | 336 | # the changeset revs are sequence of ints: |
| 336 | 337 | sql = "SELECT rev FROM node_change WHERE repos=%s AND " + \ |
| 337 | | db.cast('rev', 'int') + direction + "%s" |
| 338 | | args = [self.id, rev] |
| | 338 | "rev" + direction + "%s" |
| | 339 | args = [self.id, srev] |
| 339 | 340 | |
| 340 | 341 | if path: |
| 341 | 342 | path = path.lstrip('/') |
| … |
… |
|
| 349 | 350 | for i in range(1, len(components) + 1): |
| 350 | 351 | args.append('/'.join(components[:i])) |
| 351 | 352 | |
| 352 | | sql += " ORDER BY " + db.cast('rev', 'int') + \ |
| 353 | | (direction == '<' and " DESC" or "") + " LIMIT 1" |
| | 353 | sql += " ORDER BY rev" + (direction == '<' and " DESC" or "") \ |
| | 354 | + " LIMIT 1" |
| 354 | 355 | |
| 355 | 356 | cursor = db.cursor() |
| 356 | 357 | cursor.execute(sql, args) |
| … |
… |
|
| 396 | 397 | cursor = db.cursor() |
| 397 | 398 | cursor.execute("SELECT time,author,message FROM revision " |
| 398 | 399 | "WHERE repos=%s AND rev=%s", |
| 399 | | (repos.id, str(rev))) |
| | 400 | (repos.id, '%010d' % rev)) |
| 400 | 401 | row = cursor.fetchone() |
| 401 | 402 | if row: |
| 402 | 403 | _date, author, message = row |
| … |
… |
|
| 411 | 412 | cursor = db.cursor() |
| 412 | 413 | cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " |
| 413 | 414 | "FROM node_change WHERE repos=%s AND rev=%s " |
| 414 | | "ORDER BY path", (self.repos.id, str(self.rev))) |
| | 415 | "ORDER BY path", (self.repos.id, '%010d' % self.rev)) |
| 415 | 416 | for path, kind, change, base_path, base_rev in cursor: |
| 416 | 417 | kind = _kindmap[kind] |
| 417 | 418 | change = _actionmap[change] |
diff --git a/trac/versioncontrol/tests/cache.py b/trac/versioncontrol/tests/cache.py
|
a
|
b
|
|
| 71 | 71 | """, [(rev[0],) + change for change in changes]) |
| 72 | 72 | cursor.execute(""" |
| 73 | 73 | UPDATE repository SET value=%s WHERE id=1 AND name='youngest_rev' |
| 74 | | """, (args[-1][0][0],)) |
| | 74 | """, (str(int(args[-1][0][0])),)) |
| 75 | 75 | |
| 76 | 76 | # Tests |
| 77 | 77 | |
| … |
… |
|
| 102 | 102 | |
| 103 | 103 | cursor = self.db.cursor() |
| 104 | 104 | cursor.execute("SELECT rev,time,author,message FROM revision") |
| 105 | | self.assertEquals(('0', to_utimestamp(t1), '', ''), cursor.fetchone()) |
| 106 | | self.assertEquals(('1', to_utimestamp(t2), 'joe', 'Import'), |
| | 105 | self.assertEquals(('0000000000', to_utimestamp(t1), '', ''), |
| | 106 | cursor.fetchone()) |
| | 107 | self.assertEquals(('0000000001', to_utimestamp(t2), 'joe', 'Import'), |
| 107 | 108 | cursor.fetchone()) |
| 108 | 109 | self.assertEquals(None, cursor.fetchone()) |
| 109 | 110 | cursor.execute(""" |
| 110 | 111 | SELECT rev,path,node_type,change_type,base_path,base_rev |
| 111 | 112 | FROM node_change |
| 112 | 113 | """) |
| 113 | | self.assertEquals(('1', 'trunk', 'D', 'A', None, None), |
| | 114 | self.assertEquals(('0000000001', 'trunk', 'D', 'A', None, None), |
| 114 | 115 | cursor.fetchone()) |
| 115 | | self.assertEquals(('1', 'trunk/README', 'F', 'A', None, None), |
| | 116 | self.assertEquals(('0000000001', 'trunk/README', 'F', 'A', None, None), |
| 116 | 117 | cursor.fetchone()) |
| 117 | 118 | self.assertEquals(None, cursor.fetchone()) |
| 118 | 119 | |
| … |
… |
|
| 121 | 122 | t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) |
| 122 | 123 | t3 = datetime(2003, 1, 1, 1, 1, 1, 0, utc) |
| 123 | 124 | self.preset_cache( |
| 124 | | (('0', to_utimestamp(t1), '', ''), []), |
| 125 | | (('1', to_utimestamp(t2), 'joe', 'Import'), |
| | 125 | (('0000000000', to_utimestamp(t1), '', ''), []), |
| | 126 | (('0000000001', to_utimestamp(t2), 'joe', 'Import'), |
| 126 | 127 | [('trunk', 'D', 'A', None, None), |
| 127 | 128 | ('trunk/README', 'F', 'A', None, None)]), |
| 128 | 129 | ) |
| … |
… |
|
| 141 | 142 | |
| 142 | 143 | cursor = self.db.cursor() |
| 143 | 144 | cursor.execute(""" |
| 144 | | SELECT time,author,message FROM revision WHERE rev='2' |
| | 145 | SELECT time,author,message FROM revision WHERE rev='0000000002' |
| 145 | 146 | """) |
| 146 | 147 | self.assertEquals((to_utimestamp(t3), 'joe', 'Update'), |
| 147 | 148 | cursor.fetchone()) |
| 148 | 149 | self.assertEquals(None, cursor.fetchone()) |
| 149 | 150 | cursor.execute(""" |
| 150 | 151 | SELECT path,node_type,change_type,base_path,base_rev |
| 151 | | FROM node_change WHERE rev='2' |
| | 152 | FROM node_change WHERE rev='0000000002' |
| 152 | 153 | """) |
| 153 | 154 | self.assertEquals(('trunk/README', 'F', 'E', 'trunk/README', '1'), |
| 154 | 155 | cursor.fetchone()) |
| … |
… |
|
| 194 | 195 | SELECT rev,path,node_type,change_type,base_path,base_rev |
| 195 | 196 | FROM node_change ORDER BY rev |
| 196 | 197 | """) |
| 197 | | self.assertEquals(('1', 'trunk', 'D', 'A', None, None), |
| | 198 | self.assertEquals(('0000000001', 'trunk', 'D', 'A', None, None), |
| 198 | 199 | cursor.fetchone()) |
| 199 | | self.assertEquals(('1', 'trunk/README', 'F', 'A', None, None), |
| | 200 | self.assertEquals(('0000000001', 'trunk/README', 'F', 'A', None, None), |
| 200 | 201 | cursor.fetchone()) |
| 201 | | self.assertEquals(('2', 'trunk/README', 'F', 'E', 'trunk/README', '1'), |
| | 202 | self.assertEquals(('0000000002', 'trunk/README', 'F', 'E', 'trunk/README', |
| | 203 | '1'), |
| 202 | 204 | cursor.fetchone()) |
| 203 | 205 | self.assertEquals(None, cursor.fetchone()) |
| 204 | 206 | |
| … |
… |
|
| 206 | 208 | t1 = datetime(2001, 1, 1, 1, 1, 1, 0, utc) |
| 207 | 209 | t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) |
| 208 | 210 | self.preset_cache( |
| 209 | | (('0', to_utimestamp(t1), '', ''), []), |
| 210 | | (('1', to_utimestamp(t2), 'joe', 'Import'), |
| | 211 | (('0000000000', to_utimestamp(t1), '', ''), []), |
| | 212 | (('0000000001', to_utimestamp(t2), 'joe', 'Import'), |
| 211 | 213 | [('trunk', 'D', 'A', None, None), |
| 212 | 214 | ('trunk/README', 'F', 'A', None, None)]), |
| 213 | 215 | ) |
| … |
… |
|
| 236 | 238 | t1 = datetime(2001, 1, 1, 1, 1, 1, 0, utc) |
| 237 | 239 | t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) |
| 238 | 240 | self.preset_cache( |
| 239 | | (('0', to_utimestamp(t1), '', ''), []), |
| 240 | | (('1', to_utimestamp(t2), 'joe', 'Import'), |
| | 241 | (('0000000000', to_utimestamp(t1), '', ''), []), |
| | 242 | (('0000000001', to_utimestamp(t2), 'joe', 'Import'), |
| 241 | 243 | [('trunk', 'D', 'A', None, None), |
| 242 | 244 | ('trunk/RDME', 'F', 'A', None, None)]), |
| 243 | 245 | ) |