Changeset 3185
- Timestamp:
- 02/06/08 08:39:49 (10 months ago)
- Files:
-
- gitplugin/0.11/gitplugin/git_fs.py (modified) (14 diffs)
- gitplugin/0.11/gitplugin/PyGIT.py (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
gitplugin/0.11/gitplugin/git_fs.py
r3178 r3185 19 19 Changeset, Node, Repository, IRepositoryConnector, NoSuchChangeset, NoSuchNode 20 20 from trac.wiki import IWikiSyntaxProvider 21 from trac.versioncontrol.cache import CachedRepository 21 22 from trac.versioncontrol.web_ui import IPropertyRenderer 23 from trac.config import _TRUE_VALUES as TRUE 22 24 23 25 from genshi.builder import tag … … 29 31 pkg_resources.require('Trac>=0.11dev') 30 32 31 from genshi.builder import tag32 33 33 import PyGIT 34 34 35 35 class GitConnector(Component): 36 36 implements(IRepositoryConnector, IWikiSyntaxProvider, IPropertyRenderer) 37 38 def __init__(self): 39 self._version = None 37 40 38 41 def _format_sha_link(self, formatter, ns, sha, label, fullmatch=None): … … 53 56 54 57 def match_property(self, name, mode): 55 if (name == 'Parents'and mode == 'revprop'):58 if (name in ('Parents','Children') and mode == 'revprop'): 56 59 return 8 # default renderer has priority 1 57 60 return 0 58 61 59 62 def render_property(self, name, mode, context, props): 60 assert name == 'Parents'63 assert name in ('Parents','Children') 61 64 62 65 revs = props[name] … … 87 90 88 91 def get_repository(self, type, dir, authname): 92 """GitRepository factory method""" 93 if not self._version: 94 self._version = PyGIT.git_version() 95 self.env.systeminfo.append(('GIT', self._version)) 96 89 97 options = dict(self.config.options(type)) 90 return GitRepository(dir, self.log, options) 98 99 repos = GitRepository(dir, self.log, options) 100 101 cached_repository = 'cached_repository' in options and options['cached_repository'] in TRUE 102 103 if cached_repository: 104 repos = CachedRepository(self.env.get_db_cnx(), repos, None, self.log) 105 self.log.info("enabled CachedRepository for '%s'" % dir) 106 else: 107 self.log.info("disabled CachedRepository for '%s'" % dir) 108 109 return repos 91 110 92 111 class GitRepository(Repository): 93 112 def __init__(self, path, log, options): 113 self.logger = log 94 114 self.gitrepo = path 95 self.git = PyGIT.Storage(path) 115 116 persistent_cache = 'persistent_cache' in options and options['persistent_cache'] in TRUE 117 118 self.git = PyGIT.StorageFactory(path, log, not persistent_cache).getInstance() 96 119 Repository.__init__(self, "git:"+path, None, log) 97 120 … … 99 122 self.git = None 100 123 124 def clear(self, youngest_rev=None): 125 self.youngest = None 126 if youngest_rev is not None: 127 self.youngest = self.normalize_rev(youngest_rev) 128 self.oldest = None 129 101 130 def get_youngest_rev(self): 102 return self.git.head() 131 return self.git.youngest_rev() 132 133 def get_oldest_rev(self): 134 return self.git.oldest_rev() 103 135 104 136 def normalize_path(self, path): … … 116 148 return self.git.shortrev(self.normalize_rev(rev)) 117 149 118 def get_oldest_rev(self):119 return ""120 121 150 def get_node(self, path, rev=None): 122 151 #print "get_node", path, rev … … 128 157 return time.mktime(dt.timetuple()) + dt.microsecond/1e6 129 158 130 for rev in self.git.history_ all(to_unix(start), to_unix(stop)):159 for rev in self.git.history_timerange(to_unix(start), to_unix(stop)): 131 160 yield self.get_changeset(rev) 132 161 133 162 def get_changeset(self, rev): 134 163 """GitChangeset factory method""" 135 #print "get_changeset", rev136 164 return GitChangeset(self.git, rev) 137 165 … … 142 170 143 171 for chg in self.git.diff_tree(old_rev, new_rev, self.normalize_path(new_path)): 144 #print chg145 172 (mode1,mode2,obj1,obj2,action,path) = chg 146 kind = Node.FILE 173 147 174 if mode2[0] == '1' or mode2[0] == '1': 148 175 kind = Node.DIRECTORY 149 150 if action == 'A':151 change = Changeset.ADD152 elif action == 'M':153 change = Changeset.EDIT154 elif action == 'D':155 change = Changeset.DELETE156 176 else: 157 raise "OhOh" 177 kind = Node.FILE 178 179 change = GitChangeset.action_map[action] 158 180 159 181 old_node = None … … 168 190 169 191 def next_rev(self, rev, path=''): 170 #print "next_rev" 171 for c in self.git.children(rev): 172 return c 173 return None 192 return self.git.hist_next_revision(rev) 174 193 175 194 def previous_rev(self, rev): 176 #print "previous_rev" 177 for p in self.git.parents(rev): 178 return p 179 return None 195 return self.git.hist_prev_revision(rev) 180 196 181 197 def rev_older_than(self, rev1, rev2): 182 rc = self.git.rev_is_anchestor(rev1,rev2) 183 #rc = rev1 in self.git.history(rev2, '', skip=1) 198 rc = self.git.rev_is_anchestor_of(rev1, rev2) 184 199 return rc 185 200 186 def sync(self): 187 #print "GitRepository.sync" 188 pass 189 201 def sync(self, rev_callback=None): 202 if rev_callback: 203 revs = set(self.git.all_revs()) 204 205 if not self.git.sync(): 206 return None # nothing expected to change 207 208 if rev_callback: 209 revs = set(self.git.all_revs()) - revs 210 for r in revs: 211 rev_callback(r) 190 212 191 213 class GitNode(Node): … … 270 292 271 293 class GitChangeset(Changeset): 294 295 action_map = { 296 'A': Changeset.ADD, 297 'M': Changeset.EDIT, 298 'D': Changeset.DELETE 299 } 300 272 301 def __init__(self, git, sha): 273 302 self.git = git … … 279 308 280 309 committer = props['committer'][0] 310 311 assert 'children' not in props 312 _children = list(git.children(sha)) 313 if _children: 314 props['children'] = _children 315 281 316 (user,time,tz) = committer.rsplit(None, 2) 282 317 … … 288 323 if 'parent' in self.props: 289 324 properties['Parents'] = self.props['parent'] 325 if 'children' in self.props: 326 properties['Children'] = self.props['children'] 290 327 if 'committer' in self.props: 291 328 properties['git-committer'] = "\n".join(self.props['committer']) … … 302 339 prev = self.props.has_key('parent') and self.props['parent'][0] or None 303 340 for chg in self.git.diff_tree(prev, self.rev): 304 #print chg305 341 (mode1,mode2,obj1,obj2,action,path) = chg 306 342 kind = Node.FILE … … 308 344 kind = Node.DIRECTORY 309 345 310 if action == 'A': 311 change = Changeset.ADD 312 elif action == 'M': 313 change = Changeset.EDIT 314 elif action == 'D': 315 change = Changeset.DELETE 316 else: 317 raise "OhOh" 346 change = GitChangeset.action_map[action] 318 347 319 348 yield (path, kind, change, path, prev) gitplugin/0.11/gitplugin/PyGIT.py
r3177 r3185 13 13 # GNU General Public License for more details. 14 14 15 import os, re, sys, time 15 import os, re, sys, time, weakref, threading 16 from collections import deque 16 17 #from traceback import print_stack 17 18 … … 24 25 pass 25 26 27 def git_version(): 28 try: 29 (input, output, error) = os.popen3('git --version') 30 [v] = output.readlines() 31 [a,b,c] = v.strip().split() 32 return c 33 except: 34 raise GitError 35 36 class StorageFactory: 37 __dict = weakref.WeakValueDictionary() 38 __dict_nonweak = dict() 39 __dict_lock = threading.Lock() 40 41 def __init__(self, repo, log, weak=True): 42 self.logger = log 43 44 StorageFactory.__dict_lock.acquire() 45 46 try: 47 i = StorageFactory.__dict[repo] 48 except KeyError: 49 i = Storage(repo, log) 50 StorageFactory.__dict[repo] = i 51 52 # create or remove additional reference depending on 'weak' argument 53 if weak: 54 try: 55 del StorageFactory.__dict_nonweak[repo] 56 except KeyError: 57 pass 58 else: 59 StorageFactory.__dict_nonweak[repo] = i 60 61 StorageFactory.__dict_lock.release() 62 63 self.__inst = i 64 self.__repo = repo 65 66 def getInstance(self): 67 is_weak = self.__repo not in StorageFactory.__dict_nonweak 68 self.logger.debug("requested %sPyGIT.Storage instance %d for '%s'" 69 % (("","weak ")[is_weak], id(self.__inst), self.__repo)) 70 return self.__inst 71 26 72 class Storage: 27 def __init__(self,repo): 73 def __init__(self, repo, log): 74 self.logger = log 75 self.logger.debug("PyGIT.Storage instance %d constructed" % id(self)) 76 28 77 self.repo = repo 29 78 self.commit_encoding = None 79 80 self._lock = threading.Lock() 81 self.last_youngest_rev = -1 82 self._invalidate_caches() 83 84 def __del__(self): 85 self.logger.debug("PyGIT.Storage instance %d destructed" % id(self)) 86 87 def _invalidate_caches(self,youngest_rev=None): 88 self._lock.acquire() 89 90 rc = False 91 92 if self.last_youngest_rev != youngest_rev: 93 self.logger.debug("invalidated caches (%s != %s)" % (self.last_youngest_rev, youngest_rev)) 94 rc = True 95 self._commit_db = None 96 self._oldest_rev = None 97 self.last_youngest_rev = None 98 99 self._lock.release() 100 return rc 101 102 def get_commits(self): 103 self._lock.acquire() 104 if self._commit_db is None: 105 self.logger.debug("triggered rebuild of commit tree db for %d" % id(self)) 106 new_db = {} 107 parent = None 108 youngest = None 109 ord_rev = 0 110 for revs in self._git_call_f("git-rev-list --parents --all").readlines(): 111 revs = revs.strip().split() 112 113 rev = revs[0] 114 parents = set(revs[1:]) 115 116 ord_rev += 1 117 118 if not youngest: 119 youngest = rev 120 121 # new_db[rev] = (children(rev), parents(rev), ordinal_id(rev)) 122 if new_db.has_key(rev): 123 _children,_parents,_ord_rev = new_db[rev] 124 assert _children 125 assert not _parents 126 assert _ord_rev == 0 127 new_db[rev] = (_children, parents, ord_rev) 128 else: 129 new_db[rev] = (set(), parents, ord_rev) 130 131 # update all parents(rev)'s children 132 for parent in parents: 133 if new_db.has_key(parent): 134 new_db[parent][0].add(rev) 135 else: 136 new_db[parent] = (set([rev]), set(), 0) # dummy ordinal_id 137 138 self._commit_db = new_db, parent 139 self.last_youngest_rev = youngest 140 self.logger.debug("rebuilt commit tree db for %d with %d entries" % (id(self),len(new_db))) 141 142 self._lock.release() 143 144 assert self._commit_db[1] is not None 145 assert self._commit_db[0] is not None 146 147 return self._commit_db[0] 148 149 def sync(self): 150 rev = self._git_call("git-rev-list -n1 --all").strip() 151 return self._invalidate_caches(rev) 152 153 def oldest_rev(self): 154 self.get_commits() # trigger commit tree db build 155 return self._commit_db[1] 156 #return self._git_call("git-rev-list --reverse --all | head -1").strip() 157 158 def youngest_rev(self): 159 self.get_commits() # trigger commit tree db build 160 return self.last_youngest_rev 161 162 def history_relative_rev(self, sha, rel_pos): 163 db = self.get_commits() 164 165 if sha not in db: 166 raise GitErrorSha 167 168 if rel_pos == 0: 169 return sha 170 171 lin_rev = db[sha][2] + rel_pos 172 173 if lin_rev < 1 or lin_rev > len(db): 174 return None 175 176 for k,v in db.iteritems(): 177 if v[2] == lin_rev: 178 return k 179 180 # should never be reached if db is consistent 181 raise GitError 182 183 def hist_next_revision(self, sha): 184 return self.history_relative_rev(sha, -1) 185 186 def hist_prev_revision(self, sha): 187 return self.history_relative_rev(sha, +1) 30 188 31 189 def _git_call_f(self,cmd): … … 38 196 39 197 if _profile_git_calls: 40 t = time.time() - t 198 t = time.time() - t # doesn't work actually, as popen3 runs async 41 199 print >>sys.stderr, "GIT: took %6.2fs for '%s'" % (t, cmd) 42 200 pass … … 54 212 return self.commit_encoding 55 213 214 56 215 def head(self): 57 216 "get current HEAD commit id" … … 60 219 def verifyrev(self,rev): 61 220 "verify/lookup given revision object and return a sha id or None if lookup failed" 221 222 db = self.get_commits() 223 if db.has_key(rev): 224 return rev 225 62 226 rc=self._git_call("git-rev-parse --verify '%s'" % rev).strip() 63 227 if len(rc)==0: … … 86 250 87 251 def read_commit(self, sha): 252 db = self.get_commits() 253 if sha not in db: 254 self.logger.info("read_commit failed for '%s'" % sha) 255 raise GitErrorSha 256 88 257 raw = self._git_call("git-cat-file commit "+sha) 89 258 raw = unicode(raw, self.get_commit_encoding(), 'replace') … … 110 279 return int(self._git_call("git-cat-file -s "+sha).strip()) 111 280 281 def children(self, sha): 282 db = self.get_commits() 283 284 try: 285 return list(db[sha][0]) 286 except KeyError: 287 return [] 288 289 def children_recursive(self, sha): 290 db = self.get_commits() 291 292 work_list = deque() 293 seen = set() 294 295 seen.update(db[sha][0]) 296 work_list.extend(db[sha][0]) 297 298 while work_list: 299 p = work_list.popleft() 300 yield p 301 302 #_children = db[p][0] 303 _children = db[p][0] - seen 304 305 seen.update(_children) 306 work_list.extend(_children) 307 308 assert len(work_list) == 0 309 112 310 def parents(self, sha): 113 tmp=self._git_call("git-rev-list --max-count=1 --parents "+sha) 114 tmp=tmp.strip() 115 tmp=tmp.split() 116 return tmp[1:] 117 118 def children(self, sha): 119 for revs in self._git_call_f("git-rev-list --parents HEAD").readlines(): 120 revs = revs.strip() 121 revs = revs.split() 122 if sha in revs[1:]: 123 yield revs[0] 311 db = self.get_commits() 312 313 try: 314 return list(db[sha][1]) 315 except KeyError: 316 return [] 124 317 125 318 def history(self, sha, path, limit=None, skip=0): … … 133 326 yield rev.strip() 134 327 135 def history_all(self, start, stop): 328 def all_revs(self): 329 return self.get_commits().iterkeys() 330 331 def history_timerange(self, start, stop): 136 332 for rev in self._git_call_f("git-rev-list --reverse --max-age=%d --min-age=%d --all" \ 137 333 % (start,stop)).readlines(): 138 334 yield rev.strip() 139 335 140 def rev_is_anchestor(self, rev1, rev2): 336 def rev_is_anchestor_of(self, rev1, rev2): 337 """return True if rev2 is successor of rev1""" 141 338 rev1 = rev1.strip() 142 339 rev2 = rev2.strip() 143 for rev in self._git_call_f("git-rev-list %s ^%s^" % (rev2,rev1)).readlines(): 144 if rev1 == rev.strip(): 145 return True 146 return False 340 return rev2 in self.children_recursive(rev1) 147 341 148 342 def last_change(self, sha, path): … … 172 366 print g.read_commit(g.head()) 173 367 print "--------------" 174 print g.parents(g.head()) 175 368 p = g.parents(g.head()) 369 print list(p) 370 print "--------------" 371 print list(g.children(list(p)[0])) 372 print list(g.children(list(p)[0])) 176 373 print "--------------" 177 374 print g.get_commit_encoding() 178 375 print "--------------" 179 376 print g.get_branches() 377 print "--------------" 378 print g.hist_prev_revision(g.oldest_rev()), g.oldest_rev(), g.hist_next_revision(g.oldest_rev()) 379 380 print "--------------" 381 p = g.youngest_rev() 382 print g.hist_prev_revision(p), p, g.hist_next_revision(p) 383 print "--------------" 384 p = g.head() 385 for i in range(-5,5): 386 print i, g.history_relative_rev(p, i) 387 388 # check for loops 389 def check4loops(head): 390 print "check4loops", head 391 seen = set([head]) 392 for sha in g.children_recursive(head): 393 if sha in seen: 394 print "dupe detected :-/", sha, len(seen) 395 #print seen 396 #break 397 seen.add(sha) 398 return seen 399 400 print len(check4loops(g.parents(g.head())[0])) 401 402 #print len(check4loops(g.oldest_rev())) 403 404 #print len(list(g.children_recursive(g.oldest_rev())))
