diff -Nru trac-git-0.0.20090320/0.11/setup.py trac-git-0.0.20100513/0.11/setup.py --- trac-git-0.0.20090320/0.11/setup.py 2008-03-27 09:24:12.000000000 +0100 +++ trac-git-0.0.20100513/0.11/setup.py 2009-03-23 19:00:56.000000000 +0100 @@ -4,13 +4,13 @@ setup( name='TracGit', - install_requires='Trac >=0.11dev, ==0.11b1', + install_requires='Trac ==0.11, ==0.11rc2, ==0.11rc1, ==0.11b2, ==0.11b1, >=0.11dev', description='GIT version control plugin for Trac 0.11', author='Herbert Valerio Riedel', author_email='hvr@gnu.org', keywords='trac scm plugin git', url="http://trac-hacks.org/wiki/GitPlugin", - version='0.11.0.1', + version='0.11.0.2', license="GPL", long_description=""" This Trac 0.11 plugin provides support for the GIT SCM. diff -Nru trac-git-0.0.20090320/0.11/tracext/git/PyGIT.py trac-git-0.0.20100513/0.11/tracext/git/PyGIT.py --- trac-git-0.0.20090320/0.11/tracext/git/PyGIT.py 2009-03-20 16:09:34.000000000 +0100 +++ trac-git-0.0.20100513/0.11/tracext/git/PyGIT.py 2009-08-28 00:02:06.000000000 +0200 @@ -20,7 +20,7 @@ from threading import Lock from subprocess import Popen, PIPE import cStringIO -#from traceback import print_stack +import string __all__ = ["git_version", "GitError", "GitErrorSha", "Storage", "StorageFactory"] @@ -51,25 +51,31 @@ #print >>sys.stderr, "DEBUG:", git_cmd, cmd_args - p = Popen(self.__build_git_cmd(git_cmd, *cmd_args), - stdin=None, stdout=PIPE, stderr=PIPE, close_fds=True) + if sys.platform == "win32": + p = Popen(self.__build_git_cmd(git_cmd, *cmd_args), + stdin=None, stdout=PIPE, stderr=PIPE) + else: + p = Popen(self.__build_git_cmd(git_cmd, *cmd_args), + stdin=None, stdout=PIPE, stderr=PIPE, close_fds=True) stdout_data, stderr_data = p.communicate() #TODO, do something with p.returncode, e.g. raise exception - return cStringIO.StringIO(stdout_data) + return stdout_data def __getattr__(self, name): return partial(self.__execute, name.replace('_','-')) - @staticmethod - def is_sha(sha): + __is_sha_pat = re.compile(r'[0-9A-Fa-f]*$') + + @classmethod + def is_sha(cls,sha): """returns whether sha is a potential sha id (i.e. proper hexstring between 4 and 40 characters""" - if len(sha) < 4 or len(sha) > 40: + if not (4 <= len(sha) <= 40): return False - HEXCHARS = "0123456789abcdefABCDEF" - return all(s in HEXCHARS for s in sha) + + return bool(cls.__is_sha_pat.match(sha)) # helper class for caching... class SizedDict(dict): @@ -145,11 +151,10 @@ @staticmethod def git_version(git_bin="git"): - GIT_VERSION_MIN_REQUIRED = (1,5,2) + GIT_VERSION_MIN_REQUIRED = (1,5,6) try: g = GitCore(git_bin=git_bin) - output = g.version() - [v] = output.readlines() + [v] = g.version().splitlines() _,_,version = v.strip().split() # 'version' has usually at least 3 numeric version components, e.g.:: # 1.5.4.2 @@ -202,9 +207,7 @@ self.__commit_msg_cache = SizedDict(200) self.__commit_msg_lock = Lock() - # cache the last 2000 file sizes - self.__fs_obj_size_cache = SizedDict(2000) - self.__fs_obj_size_lock = Lock() + def __del__(self): self.logger.debug("PyGIT.Storage instance %d destructed" % id(self)) @@ -240,7 +243,7 @@ new_tags = set([]) youngest = None oldest = None - for revs in self.repo.rev_parse("--tags"): + for revs in self.repo.rev_parse("--tags").splitlines(): new_tags.add(revs.strip()) # helper for reusing strings @@ -250,7 +253,7 @@ return __rev_seen.setdefault(rev, rev) rev = ord_rev = 0 - for revs in self.repo.rev_list("--parents", "--all"): + for revs in self.repo.rev_list("--parents", "--all").splitlines(): revs = revs.strip().split() revs = map(__rev_reuse, revs) @@ -372,7 +375,7 @@ def get_commit_encoding(self): if self.commit_encoding is None: self.commit_encoding = \ - self.repo.repo_config("--get", "i18n.commitEncoding").read().strip() or 'utf-8' + self.repo.repo_config("--get", "i18n.commitEncoding").strip() or 'utf-8' return self.commit_encoding @@ -393,7 +396,7 @@ return fullrev # fall back to external git calls - rc = self.repo.rev_parse("--verify", rev).read().strip() + rc = self.repo.rev_parse("--verify", rev).strip() if not rc: return None @@ -401,7 +404,7 @@ return rc if rc in tag_db: - sha=self.repo.cat_file("tag", rc).read().split(None, 2)[:2] + sha=self.repo.cat_file("tag", rc).split(None, 2)[:2] if sha[0] != 'object': self.logger.debug("unexpected result from 'git-cat-file tag %s'" % rc) return None @@ -412,7 +415,7 @@ def shortrev(self, rev, min_len=7): "try to shorten sha id" #try to emulate the following: - #return self.repo.rev_parse("--short", str(rev)).read().strip() + #return self.repo.rev_parse("--short", str(rev)).strip() rev = str(rev) if min_len < self.__SREV_MIN: @@ -466,7 +469,7 @@ def get_branches(self): "returns list of (local) branches, with active (= HEAD) one being the first item" result=[] - for e in self.repo.branch("-v", "--no-abbrev"): + for e in self.repo.branch("-v", "--no-abbrev").splitlines(): (bname,bsha)=e[1:].strip().split()[:2] if e.startswith('*'): result.insert(0,(bname,bsha)) @@ -475,25 +478,28 @@ return result def get_tags(self): - return [e.strip() for e in self.repo.tag("-l")] + return [e.strip() for e in self.repo.tag("-l").splitlines()] def ls_tree(self, rev, path=""): - rev = str(rev) # paranoia + rev = rev and str(rev) or 'HEAD' # paranoia if path.startswith('/'): path = path[1:] - if path: - tree = self.repo.ls_tree("-z", rev, "--", path) - else: - tree = self.repo.ls_tree("-z", rev) + tree = self.repo.ls_tree("-z", "-l", rev, "--", path).split('\0') def split_ls_tree_line(l): - "split according to ' \t'" + "split according to ' \t'" meta,fname = l.split('\t') - _mode,_type,_sha = meta.split(' ') - return _mode,_type,_sha,fname + _mode,_type,_sha,_size = meta.split() - return [split_ls_tree_line(e) for e in tree.read().split('\0') if e] + if _size == '-': + _size = None + else: + _size = int(_size) + + return _mode,_type,_sha,_size,fname + + return [ split_ls_tree_line(e) for e in tree if e ] def read_commit(self, commit_id): if not commit_id: @@ -513,7 +519,7 @@ return result[0], dict(result[1]) # cache miss - raw = self.repo.cat_file("commit", commit_id).read() + raw = self.repo.cat_file("commit", commit_id) raw = unicode(raw, self.get_commit_encoding(), 'replace') lines = raw.splitlines() @@ -534,17 +540,13 @@ return result[0], dict(result[1]) def get_file(self, sha): - return self.repo.cat_file("blob", str(sha)) + return cStringIO.StringIO(self.repo.cat_file("blob", str(sha))) def get_obj_size(self, sha): sha = str(sha) + try: - with self.__fs_obj_size_lock: - if self.__fs_obj_size_cache.has_key(sha): - obj_size = self.__fs_obj_size_cache[sha] - else: - obj_size = int(self.repo.cat_file("-s", sha).read().strip()) - self.__fs_obj_size_cache[sha] = obj_size + obj_size = int(self.repo.cat_file("-s", sha).strip()) except ValueError: raise GitErrorSha("object '%s' not found" % sha) @@ -590,26 +592,26 @@ return self.get_commits().iterkeys() def sync(self): - rev = self.repo.rev_list("--max-count=1", "--all").read().strip() + rev = self.repo.rev_list("--max-count=1", "--all").strip() return self.__rev_cache_sync(rev) def last_change(self, sha, path): return self.repo.rev_list("--max-count=1", - sha, "--", path).read().strip() or None + sha, "--", path).strip() or None def history(self, sha, path, limit=None): if limit is None: limit = -1 - for rev in self.repo.rev_list("--max-count=%d" % limit, - str(sha), "--", path): - yield rev.strip() + + tmp = self.repo.rev_list("--max-count=%d" % limit, str(sha), "--", path) + return [ rev.strip() for rev in tmp.splitlines() ] def history_timerange(self, start, stop): - for rev in self.repo.rev_list("--reverse", - "--max-age=%d" % start, - "--min-age=%d" % stop, - "--all"): - yield rev.strip() + return [ rev.strip() for rev in \ + self.repo.rev_list("--reverse", + "--max-age=%d" % start, + "--min-age=%d" % stop, + "--all").splitlines() ] def rev_is_anchestor_of(self, rev1, rev2): """return True if rev2 is successor of rev1""" @@ -620,7 +622,7 @@ def blame(self, commit_sha, path): in_metadata = False - for line in self.repo.blame("-p", "--", path, str(commit_sha)): + for line in self.repo.blame("-p", "--", path, str(commit_sha)).splitlines(): assert line if in_metadata: in_metadata = not line.startswith('\t') @@ -644,6 +646,7 @@ # diff-tree returns records with the following structure: # : NUL NUL [ NUL ] + path = path.strip("/") diff_tree_args = ["-z", "-r"] if find_renames: diff_tree_args.append("-M") @@ -651,7 +654,7 @@ str(tree2), "--", path]) - lines = self.repo.diff_tree(*diff_tree_args).read().split('\0') + lines = self.repo.diff_tree(*diff_tree_args).split('\0') assert lines[-1] == "" del lines[-1] @@ -690,6 +693,12 @@ if __name__ == '__main__': import sys, logging, timeit + assert not GitCore.is_sha("123") + assert GitCore.is_sha("1a3f") + assert GitCore.is_sha("f"*40) + assert not GitCore.is_sha("x"+"f"*39) + assert not GitCore.is_sha("f"*41) + print "git version [%s]" % str(Storage.git_version()) # custom linux hack reading `/proc//statm` @@ -717,10 +726,10 @@ __data_size_last = __data_size def print_data_usage(): - global __data_size_last - __tmp = proc_statm()[5] + global __data_size_last + __tmp = proc_statm()[5] print "DATA: %6d %+6d" % (__tmp - __data_size, __tmp - __data_size_last) - __data_size_last = __tmp + __data_size_last = __tmp print_data_usage() @@ -763,8 +772,6 @@ for sha in g.children_recursive(head): if sha in seen: print "dupe detected :-/", sha, len(seen) - #print seen - #break seen.add(sha) return seen @@ -795,10 +802,10 @@ # perform typical trac operations: - if 0: + if 1: print "--------------" rev = g.head() - for mode,type,sha,name in g.ls_tree(rev): + for mode,type,sha,_size,name in g.ls_tree(rev): [last_rev] = g.history(rev, name, limit=1) s = g.get_obj_size(sha) if type == "blob" else 0 msg = g.read_commit(last_rev) diff -Nru trac-git-0.0.20090320/0.11/tracext/git/git_fs.py trac-git-0.0.20100513/0.11/tracext/git/git_fs.py --- trac-git-0.0.20090320/0.11/tracext/git/git_fs.py 2009-03-20 16:09:34.000000000 +0100 +++ trac-git-0.0.20100513/0.11/tracext/git/git_fs.py 2010-03-07 14:00:01.000000000 +0100 @@ -14,18 +14,20 @@ from trac.core import * from trac.util import TracError, shorten_line -from trac.util.datefmt import FixedOffset, to_timestamp +from trac.util.datefmt import FixedOffset, to_timestamp, format_datetime +from trac.util.text import to_unicode from trac.versioncontrol.api import \ Changeset, Node, Repository, IRepositoryConnector, NoSuchChangeset, NoSuchNode from trac.wiki import IWikiSyntaxProvider from trac.versioncontrol.cache import CachedRepository from trac.versioncontrol.web_ui import IPropertyRenderer from trac.config import BoolOption, IntOption, PathOption, Option +from trac.web.chrome import Chrome # for some reason CachedRepository doesn't pass-through short_rev()s class CachedRepository2(CachedRepository): - def short_rev(self, path): - return self.repos.short_rev(path) + def short_rev(self, path): + return self.repos.short_rev(path) from genshi.builder import tag from genshi.core import Markup, escape @@ -34,413 +36,415 @@ import time, sys if not sys.version_info[:2] >= (2,5): - raise TracError("python >= 2.5 dependancy not met") + raise TracError("python >= 2.5 dependancy not met") import PyGIT def _last_iterable(iterable): - "helper for detecting last iteration in for-loop" + "helper for detecting last iteration in for-loop" i = iter(iterable) v = i.next() for nextv in i: - yield False, v - v = nextv - yield True, v + yield False, v + v = nextv + yield True, v # helper def _parse_user_time(s): - """parse author/committer attribute lines and return - (user,timestamp)""" - (user,time,tz_str) = s.rsplit(None, 2) - tz = FixedOffset((int(tz_str)*6)/10, tz_str) - time = datetime.fromtimestamp(float(time), tz) - return (user,time) + """parse author/committer attribute lines and return + (user,timestamp)""" + (user,time,tz_str) = s.rsplit(None, 2) + tz = FixedOffset((int(tz_str)*6)/10, tz_str) + time = datetime.fromtimestamp(float(time), tz) + return (user,time) class GitConnector(Component): - implements(IRepositoryConnector, IWikiSyntaxProvider, IPropertyRenderer) + implements(IRepositoryConnector, IWikiSyntaxProvider, IPropertyRenderer) - def __init__(self): - self._version = None + def __init__(self): + self._version = None - try: - self._version = PyGIT.Storage.git_version(git_bin=self._git_bin) - except PyGIT.GitError, e: - self.log.error("GitError: "+e.message) - - if self._version: - self.log.info("detected GIT version %s" % self._version['v_str']) - self.env.systeminfo.append(('GIT', self._version['v_str'])) - if not self._version['v_compatible']: - self.log.error("GIT version %s installed not compatible (need >= %s)" % - (self._version['v_str'], self._version['v_min_str'])) - - def _format_sha_link(self, formatter, ns, sha, label, fullmatch=None): - try: - changeset = self.env.get_repository().get_changeset(sha) - return tag.a(label, class_="changeset", - title=shorten_line(changeset.message), - href=formatter.href.changeset(sha)) - except TracError, e: - return tag.a(label, class_="missing changeset", - href=formatter.href.changeset(sha), - title=unicode(e), rel="nofollow") + try: + self._version = PyGIT.Storage.git_version(git_bin=self._git_bin) + except PyGIT.GitError, e: + self.log.error("GitError: "+e.message) + + if self._version: + self.log.info("detected GIT version %s" % self._version['v_str']) + self.env.systeminfo.append(('GIT', self._version['v_str'])) + if not self._version['v_compatible']: + self.log.error("GIT version %s installed not compatible (need >= %s)" % + (self._version['v_str'], self._version['v_min_str'])) + + def _format_sha_link(self, formatter, ns, sha, label, fullmatch=None): + try: + changeset = self.env.get_repository().get_changeset(sha) + return tag.a(label, class_="changeset", + title=shorten_line(changeset.message), + href=formatter.href.changeset(sha)) + except TracError, e: + return tag.a(label, class_="missing changeset", + href=formatter.href.changeset(sha), + title=unicode(e), rel="nofollow") - ####################### - # IPropertyRenderer + ####################### + # IPropertyRenderer - # relied upon by GitChangeset + # relied upon by GitChangeset def match_property(self, name, mode): - if name in ('Parents','Children','git-committer','git-author') \ - and mode == 'revprop': - return 8 # default renderer has priority 1 - return 0 + if name in ('Parents','Children','git-committer','git-author') \ + and mode == 'revprop': + return 8 # default renderer has priority 1 + return 0 def render_property(self, name, mode, context, props): - def sha_link(sha): - return self._format_sha_link(context, 'sha', sha, sha) + def sha_link(sha): + return self._format_sha_link(context, 'sha', sha, sha) - if name in ('Parents','Children'): - revs = props[name] + if name in ('Parents','Children'): + revs = props[name] - return tag([tag(sha_link(rev), ', ') for rev in revs[:-1]], - sha_link(revs[-1])) + return tag([tag(sha_link(rev), ', ') for rev in revs[:-1]], + sha_link(revs[-1])) - if name in ('git-committer', 'git-author'): - user_,time_ = props[name] - _str = user_ + " / " + time_.strftime('%Y-%m-%dT%H:%M:%SZ%z') - return unicode(_str) + if name in ('git-committer', 'git-author'): + user_, time_ = props[name] + _str = "%s (%s)" % (Chrome(self.env).format_author(context.req, user_), + format_datetime(time_, tzinfo=context.req.tz)) + return unicode(_str) - raise TracError("internal error") + raise TracError("internal error") - ####################### - # IWikiSyntaxProvider + ####################### + # IWikiSyntaxProvider - def get_wiki_syntax(self): - yield (r'(?:\b|!)[0-9a-fA-F]{40,40}\b', - lambda fmt, sha, match: - self._format_sha_link(fmt, 'changeset', sha, sha)) + def get_wiki_syntax(self): + yield (r'(?:\b|!)[0-9a-fA-F]{40,40}\b', + lambda fmt, sha, match: + self._format_sha_link(fmt, 'changeset', sha, sha)) - def get_link_resolvers(self): - yield ('sha', self._format_sha_link) + def get_link_resolvers(self): + yield ('sha', self._format_sha_link) - ####################### - # IRepositoryConnector + ####################### + # IRepositoryConnector - _persistent_cache = BoolOption('git', 'persistent_cache', 'false', - "enable persistent caching of commit tree") + _persistent_cache = BoolOption('git', 'persistent_cache', 'false', + "enable persistent caching of commit tree") - _cached_repository = BoolOption('git', 'cached_repository', 'false', - "wrap `GitRepository` in `CachedRepository`") + _cached_repository = BoolOption('git', 'cached_repository', 'false', + "wrap `GitRepository` in `CachedRepository`") - _shortrev_len = IntOption('git', 'shortrev_len', 7, - "length rev sha sums should be tried to be abbreviated to" - " (must be >= 4 and <= 40)") + _shortrev_len = IntOption('git', 'shortrev_len', 7, + "length rev sha sums should be tried to be abbreviated to" + " (must be >= 4 and <= 40)") - _git_bin = PathOption('git', 'git_bin', '/usr/bin/git', "path to git executable (relative to trac project folder!)") + _git_bin = PathOption('git', 'git_bin', '/usr/bin/git', "path to git executable (relative to trac project folder!)") - def get_supported_types(self): - yield ("git", 8) + def get_supported_types(self): + yield ("git", 8) - def get_repository(self, type, dir, authname): - """GitRepository factory method""" - assert type == "git" + def get_repository(self, type, dir, authname): + """GitRepository factory method""" + assert type == "git" - if not self._version: - raise TracError("GIT backend not available") - elif not self._version['v_compatible']: - raise TracError("GIT version %s installed not compatible (need >= %s)" % - (self._version['v_str'], self._version['v_min_str'])) + if not self._version: + raise TracError("GIT backend not available") + elif not self._version['v_compatible']: + raise TracError("GIT version %s installed not compatible (need >= %s)" % + (self._version['v_str'], self._version['v_min_str'])) - repos = GitRepository(dir, self.log, - persistent_cache=self._persistent_cache, - git_bin=self._git_bin, - shortrev_len=self._shortrev_len) + repos = GitRepository(dir, self.log, + persistent_cache=self._persistent_cache, + git_bin=self._git_bin, + shortrev_len=self._shortrev_len) - if self._cached_repository: - repos = CachedRepository2(self.env.get_db_cnx(), repos, None, self.log) - self.log.info("enabled CachedRepository for '%s'" % dir) - else: - self.log.info("disabled CachedRepository for '%s'" % dir) + if self._cached_repository: + repos = CachedRepository2(self.env.get_db_cnx(), repos, None, self.log) + self.log.info("enabled CachedRepository for '%s'" % dir) + else: + self.log.info("disabled CachedRepository for '%s'" % dir) - return repos + return repos class GitRepository(Repository): - def __init__(self, path, log, persistent_cache=False, git_bin='git', shortrev_len=7): - self.logger = log - self.gitrepo = path - self._shortrev_len = max(4, min(shortrev_len, 40)) + def __init__(self, path, log, persistent_cache=False, git_bin='git', shortrev_len=7): + self.logger = log + self.gitrepo = path + self._shortrev_len = max(4, min(shortrev_len, 40)) - self.git = PyGIT.StorageFactory(path, log, not persistent_cache, - git_bin=git_bin).getInstance() - Repository.__init__(self, "git:"+path, None, log) + self.git = PyGIT.StorageFactory(path, log, not persistent_cache, + git_bin=git_bin).getInstance() + Repository.__init__(self, "git:"+path, None, log) - def close(self): - self.git = None + def close(self): + self.git = None - def clear(self, youngest_rev=None): - self.youngest = None - if youngest_rev is not None: - self.youngest = self.normalize_rev(youngest_rev) - self.oldest = None + def clear(self, youngest_rev=None): + self.youngest = None + if youngest_rev is not None: + self.youngest = self.normalize_rev(youngest_rev) + self.oldest = None - def get_youngest_rev(self): - return self.git.youngest_rev() + def get_youngest_rev(self): + return self.git.youngest_rev() - def get_oldest_rev(self): - return self.git.oldest_rev() + def get_oldest_rev(self): + return self.git.oldest_rev() - def normalize_path(self, path): - return path and path.strip('/') or '' + def normalize_path(self, path): + return path and path.strip('/') or '/' - def normalize_rev(self, rev): - if not rev: - return self.get_youngest_rev() - normrev=self.git.verifyrev(rev) - if normrev is None: - raise NoSuchChangeset(rev) - return normrev + def normalize_rev(self, rev): + if not rev: + return self.get_youngest_rev() + normrev=self.git.verifyrev(rev) + if normrev is None: + raise NoSuchChangeset(rev) + return normrev - def short_rev(self, rev): - return self.git.shortrev(self.normalize_rev(rev), min_len=self._shortrev_len) + def short_rev(self, rev): + return self.git.shortrev(self.normalize_rev(rev), min_len=self._shortrev_len) - def get_node(self, path, rev=None): - return GitNode(self.git, path, rev, self.log) + def get_node(self, path, rev=None): + return GitNode(self.git, path, rev, self.log) - def get_quickjump_entries(self, rev): - for bname,bsha in self.git.get_branches(): - yield 'branches', bname, '/', bsha - for t in self.git.get_tags(): - yield 'tags', t, '/', t + def get_quickjump_entries(self, rev): + for bname,bsha in self.git.get_branches(): + yield 'branches', bname, '/', bsha + for t in self.git.get_tags(): + yield 'tags', t, '/', t - def get_changesets(self, start, stop): - for rev in self.git.history_timerange(to_timestamp(start), to_timestamp(stop)): - yield self.get_changeset(rev) + def get_changesets(self, start, stop): + for rev in self.git.history_timerange(to_timestamp(start), to_timestamp(stop)): + yield self.get_changeset(rev) - def get_changeset(self, rev): - """GitChangeset factory method""" - return GitChangeset(self.git, rev) + def get_changeset(self, rev): + """GitChangeset factory method""" + return GitChangeset(self.git, rev) - def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=0): - # TODO: handle renames/copies, ignore_ancestry - if old_path != new_path: - raise TracError("not supported in git_fs") + def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=0): + # TODO: handle renames/copies, ignore_ancestry + if old_path != new_path: + raise TracError("not supported in git_fs") - for chg in self.git.diff_tree(old_rev, new_rev, self.normalize_path(new_path)): - (mode1,mode2,obj1,obj2,action,path,path2) = chg + for chg in self.git.diff_tree(old_rev, new_rev, self.normalize_path(new_path)): + (mode1,mode2,obj1,obj2,action,path,path2) = chg - kind = Node.FILE - if mode2.startswith('04') or mode1.startswith('04'): - kind = Node.DIRECTORY + kind = Node.FILE + if mode2.startswith('04') or mode1.startswith('04'): + kind = Node.DIRECTORY - change = GitChangeset.action_map[action] + change = GitChangeset.action_map[action] - old_node = None - new_node = None + old_node = None + new_node = None - if change != Changeset.ADD: - old_node = self.get_node(path, old_rev) - if change != Changeset.DELETE: - new_node = self.get_node(path, new_rev) + if change != Changeset.ADD: + old_node = self.get_node(path, old_rev) + if change != Changeset.DELETE: + new_node = self.get_node(path, new_rev) - yield (old_node, new_node, kind, change) + yield (old_node, new_node, kind, change) - def next_rev(self, rev, path=''): - return self.git.hist_next_revision(rev) + def next_rev(self, rev, path=''): + return self.git.hist_next_revision(rev) - def previous_rev(self, rev, path=''): - return self.git.hist_prev_revision(rev) + def previous_rev(self, rev, path=''): + return self.git.hist_prev_revision(rev) - def rev_older_than(self, rev1, rev2): - rc = self.git.rev_is_anchestor_of(rev1, rev2) - return rc + def rev_older_than(self, rev1, rev2): + rc = self.git.rev_is_anchestor_of(rev1, rev2) + return rc - def clear(self, youngest_rev=None): - self.sync() + def clear(self, youngest_rev=None): + self.sync() - def sync(self, rev_callback=None): - if rev_callback: - revs = set(self.git.all_revs()) + def sync(self, rev_callback=None): + if rev_callback: + revs = set(self.git.all_revs()) - if not self.git.sync(): - return None # nothing expected to change + if not self.git.sync(): + return None # nothing expected to change - if rev_callback: - revs = set(self.git.all_revs()) - revs - for rev in revs: - rev_callback(rev) + if rev_callback: + revs = set(self.git.all_revs()) - revs + for rev in revs: + rev_callback(rev) class GitNode(Node): - def __init__(self, git, path, rev, log, ls_tree_info=None): - self.log = log - self.git = git - self.fs_sha = None # points to either tree or blobs - self.fs_perm = None - self.fs_size = None - - kind = Node.DIRECTORY - p = path.strip('/') - if p: # ie. not the root-tree + def __init__(self, git, path, rev, log, ls_tree_info=None): + self.log = log + self.git = git + self.fs_sha = None # points to either tree or blobs + self.fs_perm = None + self.fs_size = None + rev = rev and str(rev) or 'HEAD' + + kind = Node.DIRECTORY + p = path.strip('/') + if p: # ie. not the root-tree if not ls_tree_info: - ls_tree_info = git.ls_tree(rev, p) or None + ls_tree_info = git.ls_tree(rev, p) or None if ls_tree_info: [ls_tree_info] = ls_tree_info - if not ls_tree_info: - raise NoSuchNode(path, rev) + if not ls_tree_info: + raise NoSuchNode(path, rev) - (self.fs_perm, k, self.fs_sha, fn) = ls_tree_info + (self.fs_perm, k, self.fs_sha, self.fs_size, fn) = ls_tree_info - # fix-up to the last commit-rev that touched this node - rev = self.git.last_change(rev, p) + # fix-up to the last commit-rev that touched this node + rev = self.git.last_change(rev, p) - if k=='tree': - pass - elif k=='blob': - kind = Node.FILE - else: - raise TracError("internal error (got unexpected object kind '%s')" % k) + if k=='tree': + pass + elif k=='blob': + kind = Node.FILE + else: + raise TracError("internal error (got unexpected object kind '%s')" % k) - self.created_path = path - self.created_rev = rev + self.created_path = path + self.created_rev = rev - Node.__init__(self, path, rev, kind) + Node.__init__(self, path, rev, kind) - def __git_path(self): - "return path as expected by PyGIT" - p = self.path.strip('/') - if self.isfile: - assert p - return p - if self.isdir: - return p and (p + '/') + def __git_path(self): + "return path as expected by PyGIT" + p = self.path.strip('/') + if self.isfile: + assert p + return p + if self.isdir: + return p and (p + '/') - raise TracError("internal error") + raise TracError("internal error") - def get_content(self): - if not self.isfile: - return None + def get_content(self): + if not self.isfile: + return None - return self.git.get_file(self.fs_sha) + return self.git.get_file(self.fs_sha) - def get_properties(self): - return self.fs_perm and {'mode': self.fs_perm } or {} + def get_properties(self): + return self.fs_perm and {'mode': self.fs_perm } or {} - def get_annotations(self): - if not self.isfile: - return + def get_annotations(self): + if not self.isfile: + return - return [ rev for (rev,lineno) in self.git.blame(self.rev, self.__git_path()) ] + return [ rev for (rev,lineno) in self.git.blame(self.rev, self.__git_path()) ] - def get_entries(self): - if not self.isdir: - return + def get_entries(self): + if not self.isdir: + return - for ent in self.git.ls_tree(self.rev, self.__git_path()): - yield GitNode(self.git, ent[3], self.rev, self.log, ent) + for ent in self.git.ls_tree(self.rev, self.__git_path()): + yield GitNode(self.git, ent[-1], self.rev, self.log, ent) - def get_content_type(self): - if self.isdir: - return None + def get_content_type(self): + if self.isdir: + return None - return '' + return '' - def get_content_length(self): - if not self.isfile: - return None + def get_content_length(self): + if not self.isfile: + return None - if self.fs_size is None: - self.fs_size = self.git.get_obj_size(self.fs_sha) + if self.fs_size is None: + self.fs_size = self.git.get_obj_size(self.fs_sha) - return self.fs_size + return self.fs_size - def get_history(self, limit=None): - # TODO: find a way to follow renames/copies - for is_last,rev in _last_iterable(self.git.history(self.rev, self.__git_path(), limit)): - yield (self.path, rev, Changeset.EDIT if not is_last else Changeset.ADD) + def get_history(self, limit=None): + # TODO: find a way to follow renames/copies + for is_last,rev in _last_iterable(self.git.history(self.rev, self.__git_path(), limit)): + yield (self.path, rev, Changeset.EDIT if not is_last else Changeset.ADD) - def get_last_modified(self): - if not self.isfile: - return None + def get_last_modified(self): + if not self.isfile: + return None - try: - msg, props = self.git.read_commit(self.rev) - user,ts = _parse_user_time(props['committer'][0]) - except: - self.log.error("internal error (could not get timestamp from commit '%s')" % self.rev) - return None + try: + msg, props = self.git.read_commit(self.rev) + user,ts = _parse_user_time(props['committer'][0]) + except: + self.log.error("internal error (could not get timestamp from commit '%s')" % self.rev) + return None - return ts + return ts class GitChangeset(Changeset): - action_map = { # see also git-diff-tree(1) --diff-filter - 'A': Changeset.ADD, - 'M': Changeset.EDIT, # modified - 'T': Changeset.EDIT, # file type (mode) change - 'D': Changeset.DELETE, - 'R': Changeset.MOVE, # renamed - 'C': Changeset.COPY - } # TODO: U, X, B - - def __init__(self, git, sha): - self.git = git - try: - (msg, props) = git.read_commit(sha) - except PyGIT.GitErrorSha: - raise NoSuchChangeset(sha) - self.props = props - - assert 'children' not in props - _children = list(git.children(sha)) - if _children: - props['children'] = _children - - # use 1st committer as changeset owner/timestamp - (user_, time_) = _parse_user_time(props['committer'][0]) - - Changeset.__init__(self, sha, msg, user_, time_) - - def get_properties(self): - properties = {} - if 'parent' in self.props: - properties['Parents'] = self.props['parent'] - if 'children' in self.props: - properties['Children'] = self.props['children'] - if 'committer' in self.props: - properties['git-committer'] = \ - _parse_user_time(self.props['committer'][0]) - if 'author' in self.props: - git_author = _parse_user_time(self.props['author'][0]) - if not properties.get('git-committer') == git_author: - properties['git-author'] = git_author - - return properties - - def get_changes(self): - paths_seen = set() - for parent in self.props.get('parent', [None]): - for mode1,mode2,obj1,obj2,action,path1,path2 in \ - self.git.diff_tree(parent, self.rev, find_renames=True): - path = path2 or path1 - p_path, p_rev = path1, parent - - kind = Node.FILE - if mode2.startswith('04') or mode1.startswith('04'): - kind = Node.DIRECTORY - - action = GitChangeset.action_map[action[0]] - - if action == Changeset.ADD: - p_path = '' - p_rev = None - - # CachedRepository expects unique (rev, path, change_type) key - # this is only an issue in case of merges where files required editing - if path in paths_seen: - continue + action_map = { # see also git-diff-tree(1) --diff-filter + 'A': Changeset.ADD, + 'M': Changeset.EDIT, # modified + 'T': Changeset.EDIT, # file type (mode) change + 'D': Changeset.DELETE, + 'R': Changeset.MOVE, # renamed + 'C': Changeset.COPY + } # TODO: U, X, B + + def __init__(self, git, sha): + self.git = git + try: + (msg, props) = git.read_commit(sha) + except PyGIT.GitErrorSha: + raise NoSuchChangeset(sha) + self.props = props + + assert 'children' not in props + _children = list(git.children(sha)) + if _children: + props['children'] = _children + + # use 1st committer as changeset owner/timestamp + (user_, time_) = _parse_user_time(props['committer'][0]) + + Changeset.__init__(self, sha, msg, user_, time_) + + def get_properties(self): + properties = {} + if 'parent' in self.props: + properties['Parents'] = self.props['parent'] + if 'children' in self.props: + properties['Children'] = self.props['children'] + if 'committer' in self.props: + properties['git-committer'] = \ + _parse_user_time(self.props['committer'][0]) + if 'author' in self.props: + git_author = _parse_user_time(self.props['author'][0]) + if not properties.get('git-committer') == git_author: + properties['git-author'] = git_author + + return properties + + def get_changes(self): + paths_seen = set() + for parent in self.props.get('parent', [None]): + for mode1,mode2,obj1,obj2,action,path1,path2 in \ + self.git.diff_tree(parent, self.rev, find_renames=True): + path = path2 or path1 + p_path, p_rev = path1, parent + + kind = Node.FILE + if mode2.startswith('04') or mode1.startswith('04'): + kind = Node.DIRECTORY + + action = GitChangeset.action_map[action[0]] + + if action == Changeset.ADD: + p_path = '' + p_rev = None + + # CachedRepository expects unique (rev, path, change_type) key + # this is only an issue in case of merges where files required editing + if path in paths_seen: + continue - paths_seen.add(path) + paths_seen.add(path) - yield (path, kind, action, p_path, p_rev) + yield (to_unicode(path), kind, action, to_unicode(p_path), p_rev) diff -Nru trac-git-0.0.20090320/0.11-py2.4/COPYING trac-git-0.0.20100513/0.11-py2.4/COPYING --- trac-git-0.0.20090320/0.11-py2.4/COPYING 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.11-py2.4/COPYING 2006-09-29 12:24:18.000000000 +0200 @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff -Nru trac-git-0.0.20090320/0.11-py2.4/README trac-git-0.0.20100513/0.11-py2.4/README --- trac-git-0.0.20090320/0.11-py2.4/README 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.11-py2.4/README 2006-09-29 12:24:18.000000000 +0200 @@ -0,0 +1,6 @@ +========= +GitPlugin +========= + +see http://www.trac-hacks.org/wiki/GitPlugin for instructions + diff -Nru trac-git-0.0.20090320/0.11-py2.4/setup.py trac-git-0.0.20100513/0.11-py2.4/setup.py --- trac-git-0.0.20090320/0.11-py2.4/setup.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.11-py2.4/setup.py 2009-03-23 19:00:56.000000000 +0100 @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup( + name='TracGit', + install_requires='Trac ==0.11, ==0.11rc2, ==0.11rc1, ==0.11b2, ==0.11b1, >=0.11dev', + description='GIT version control plugin for Trac 0.11', + author='Herbert Valerio Riedel', + author_email='hvr@gnu.org', + keywords='trac scm plugin git', + url="http://trac-hacks.org/wiki/GitPlugin", + version='0.11.0.2', + license="GPL", + long_description=""" + This Trac 0.11 plugin provides support for the GIT SCM. + + See http://trac-hacks.org/wiki/GitPlugin for more details. + """, + packages=['tracext', 'tracext.git'], + namespace_packages=['tracext'], + entry_points = {'trac.plugins': 'git = tracext.git.git_fs'}, + data_files=['COPYING','README']) diff -Nru trac-git-0.0.20090320/0.11-py2.4/tracext/__init__.py trac-git-0.0.20100513/0.11-py2.4/tracext/__init__.py --- trac-git-0.0.20090320/0.11-py2.4/tracext/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.11-py2.4/tracext/__init__.py 2008-02-11 12:22:56.000000000 +0100 @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff -Nru trac-git-0.0.20090320/0.11-py2.4/tracext/git/PyGIT.py trac-git-0.0.20100513/0.11-py2.4/tracext/git/PyGIT.py --- trac-git-0.0.20090320/0.11-py2.4/tracext/git/PyGIT.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.11-py2.4/tracext/git/PyGIT.py 2010-03-07 12:25:47.000000000 +0100 @@ -0,0 +1,867 @@ +# -*- coding: iso-8859-1 -*- +# +# Copyright (C) 2006,2008 Herbert Valerio Riedel +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + + +import os, re, sys, time, weakref +from collections import deque +from threading import Lock +from subprocess import Popen, PIPE +import cStringIO +import string + +__all__ = ["git_version", "GitError", "GitErrorSha", "Storage", "StorageFactory"] + +try: + all +except NameError: + def all(iterable): + for i in iterable: + if not i: + return False + return True + +try: + any +except NameError: + def any(iterable): + for i in iterable: + if i: + return True + return False + +class GitError(Exception): + pass + +class GitErrorSha(GitError): + pass + +class GitCore: + def __init__(self, git_dir=None, git_bin="git"): + self.__git_bin = git_bin + self.__git_dir = git_dir + + def __build_git_cmd(self, gitcmd, *args): + "construct command tuple for git call suitable for Popen()" + + cmd = [self.__git_bin] + if self.__git_dir: + cmd.append('--git-dir=%s' % self.__git_dir) + cmd.append(gitcmd) + cmd.extend(args) + + return cmd + + def __execute(self, git_cmd, *cmd_args): + "execute git command and return file-like object of stdout" + + #print >>sys.stderr, "DEBUG:", git_cmd, cmd_args + + if sys.platform == "win32": + p = Popen(self.__build_git_cmd(git_cmd, *cmd_args), + stdin=None, stdout=PIPE, stderr=PIPE) + else: + p = Popen(self.__build_git_cmd(git_cmd, *cmd_args), + stdin=None, stdout=PIPE, stderr=PIPE, close_fds=True) + + stdout_data, stderr_data = p.communicate() + #TODO, do something with p.returncode, e.g. raise exception + + return stdout_data + + def __getattr__(self, name): + return lambda *args: self.__execute(name.replace('_','-'), *args) + + __is_sha_pat = re.compile(r'[0-9A-Fa-f]*$') + + @classmethod + def is_sha(cls,sha): + """returns whether sha is a potential sha id + (i.e. proper hexstring between 4 and 40 characters""" + if not (4 <= len(sha) <= 40): + return False + + return bool(cls.__is_sha_pat.match(sha)) + +# helper class for caching... +class SizedDict(dict): + def __init__(self, max_size=0): + dict.__init__(self) + self.__max_size = max_size + self.__key_fifo = deque() + self.__lock = Lock() + + def __setitem__(self, name, value): + self.__lock.acquire() + try: + assert len(self) == len(self.__key_fifo) # invariant + + if not self.__contains__(name): + self.__key_fifo.append(name) + + rc = dict.__setitem__(self, name, value) + + while len(self.__key_fifo) > self.__max_size: + self.__delitem__(self.__key_fifo.popleft()) + + assert len(self) == len(self.__key_fifo) # invariant + + return rc + + finally: + self.__lock.release() + + def setdefault(k,d=None): + # TODO + raise AttributeError("SizedDict has no setdefault() method") + +class StorageFactory: + __dict = weakref.WeakValueDictionary() + __dict_nonweak = dict() + __dict_lock = Lock() + + def __init__(self, repo, log, weak=True, git_bin='git'): + self.logger = log + + StorageFactory.__dict_lock.acquire() + try: + try: + i = StorageFactory.__dict[repo] + except KeyError: + i = Storage(repo, log, git_bin) + StorageFactory.__dict[repo] = i + + # create or remove additional reference depending on 'weak' argument + if weak: + try: + del StorageFactory.__dict_nonweak[repo] + except KeyError: + pass + else: + StorageFactory.__dict_nonweak[repo] = i + finally: + StorageFactory.__dict_lock.release() + + self.__inst = i + self.__repo = repo + + def getInstance(self): + is_weak = self.__repo not in StorageFactory.__dict_nonweak + self.logger.debug("requested %sPyGIT.Storage instance %d for '%s'" + % (("","weak ")[is_weak], id(self.__inst), self.__repo)) + return self.__inst + +class Storage: + __SREV_MIN = 4 # minimum short-rev length + + @staticmethod + def __rev_key(rev): + assert len(rev) >= 4 + #assert GitCore.is_sha(rev) + srev_key = int(rev[:4], 16) + assert srev_key >= 0 and srev_key <= 0xffff + return srev_key + + @staticmethod + def git_version(git_bin="git"): + GIT_VERSION_MIN_REQUIRED = (1,5,6) + try: + g = GitCore(git_bin=git_bin) + [v] = g.version().splitlines() + _,_,version = v.strip().split() + # 'version' has usually at least 3 numeric version components, e.g.:: + # 1.5.4.2 + # 1.5.4.3.230.g2db511 + # 1.5.4.GIT + + def try_int(s): + try: + return int(s) + except ValueError: + return s + + split_version = tuple(map(try_int, version.split('.'))) + + result = {} + result['v_str'] = version + result['v_tuple'] = split_version + result['v_min_tuple'] = GIT_VERSION_MIN_REQUIRED + result['v_min_str'] = ".".join(map(str, GIT_VERSION_MIN_REQUIRED)) + result['v_compatible'] = split_version >= GIT_VERSION_MIN_REQUIRED + return result + except: + raise GitError("Could not retrieve GIT version") + + def __init__(self, git_dir, log, git_bin='git'): + self.logger = log + + # simple sanity checking + __git_file_path = lambda *args: os.path.join(git_dir, *args) + if not all(map(os.path.exists, + map(__git_file_path, + ['HEAD','objects','refs']))): + self.logger.error("GIT control files missing in '%s'" % git_dir) + if os.path.exists(__git_file_path('.git')): + self.logger.error("entry '.git' found in '%s'" + " -- maybe use that folder instead..." % git_dir) + raise GitError("GIT control files not found, maybe wrong directory?") + + self.logger.debug("PyGIT.Storage instance %d constructed" % id(self)) + + self.repo = GitCore(git_dir, git_bin=git_bin) + + self.commit_encoding = None + + # caches + self.__rev_cache = None + self.__rev_cache_lock = Lock() + + # cache the last 200 commit messages + self.__commit_msg_cache = SizedDict(200) + self.__commit_msg_lock = Lock() + + + + def __del__(self): + self.logger.debug("PyGIT.Storage instance %d destructed" % id(self)) + + # + # cache handling + # + + # called by Storage.sync() + def __rev_cache_sync(self, youngest_rev=None): + "invalidates revision db cache if necessary" + self.__rev_cache_lock.acquire() + try: + need_update = False + if self.__rev_cache: + last_youngest_rev = self.__rev_cache[0] + if last_youngest_rev != youngest_rev: + self.logger.debug("invalidated caches (%s != %s)" % (last_youngest_rev, youngest_rev)) + need_update = True + else: + need_update = True # almost NOOP + + if need_update: + self.__rev_cache = None + + return need_update + + finally: + self.__rev_cache_lock.release() + + def get_rev_cache(self): + self.__rev_cache_lock.acquire() + try: + if self.__rev_cache is None: # can be cleared by Storage.__rev_cache_sync() + self.logger.debug("triggered rebuild of commit tree db for %d" % id(self)) + new_db = {} + new_sdb = {} + new_tags = set([]) + youngest = None + oldest = None + for revs in self.repo.rev_parse("--tags").splitlines(): + new_tags.add(revs.strip()) + + # helper for reusing strings + __rev_seen = {} + def __rev_reuse(rev): + rev = str(rev) + return __rev_seen.setdefault(rev, rev) + + rev = ord_rev = 0 + for revs in self.repo.rev_list("--parents", "--all").splitlines(): + revs = revs.strip().split() + + revs = map(__rev_reuse, revs) + + rev = revs[0] + + # shortrev "hash" map + srev_key = self.__rev_key(rev) + new_sdb.setdefault(srev_key, []).append(rev) + + parents = tuple(revs[1:]) + + ord_rev += 1 + + # first rev seen is assumed to be the youngest one (and has ord_rev=1) + if not youngest: + youngest = rev + + # new_db[rev] = (children(rev), parents(rev), ordinal_id(rev)) + if new_db.has_key(rev): + _children,_parents,_ord_rev = new_db[rev] + assert _children + assert not _parents + assert _ord_rev == 0 + else: + _children = [] + + # create/update entry + new_db[rev] = _children, parents, ord_rev + + # update all parents(rev)'s children + for parent in parents: + # by default, a dummy ordinal_id is used for the mean-time + _children, _parents, _ord_rev = new_db.setdefault(parent, ([], [], 0)) + if rev not in _children: + _children.append(rev) + + # last rev seen is assumed to be the oldest one (with highest ord_rev) + oldest = rev + + __rev_seen = None + + assert len(new_db) == ord_rev + + # convert children lists to tuples + tmp = {} + try: + while True: + k,v = new_db.popitem() + assert v[2] > 0 + tmp[k] = tuple(v[0]),v[1],v[2] + except KeyError: + pass + + assert len(new_db) == 0 + new_db = tmp + + # convert sdb either to dict or array depending on size + if len(new_sdb) > 5000: + tmp = [()]*(max(new_sdb.keys())+1) + else: + tmp = {} + + try: + while True: + k,v = new_sdb.popitem() + tmp[k] = tuple(v) + except KeyError: + pass + + assert len(new_sdb) == 0 + new_sdb = tmp + + # atomically update self.__rev_cache + self.__rev_cache = youngest, oldest, new_db, new_tags, new_sdb + self.logger.debug("rebuilt commit tree db for %d with %d entries" % (id(self),len(new_db))) + + assert all(e is not None for e in self.__rev_cache) or not any(self.__rev_cache) + + return self.__rev_cache + # try: + + finally: + self.__rev_cache_lock.release() + + # tuple: youngest_rev, oldest_rev, rev_dict, tag_dict, short_rev_dict + rev_cache = property(get_rev_cache) + + def get_commits(self): + return self.rev_cache[2] + + def oldest_rev(self): + return self.rev_cache[1] + + def youngest_rev(self): + return self.rev_cache[0] + + def history_relative_rev(self, sha, rel_pos): + db = self.get_commits() + + if sha not in db: + raise GitErrorSha + + if rel_pos == 0: + return sha + + lin_rev = db[sha][2] + rel_pos + + if lin_rev < 1 or lin_rev > len(db): + return None + + for k,v in db.iteritems(): + if v[2] == lin_rev: + return k + + # should never be reached if db is consistent + raise GitError("internal inconsistency detected") + + def hist_next_revision(self, sha): + return self.history_relative_rev(sha, -1) + + def hist_prev_revision(self, sha): + return self.history_relative_rev(sha, +1) + + def get_commit_encoding(self): + if self.commit_encoding is None: + self.commit_encoding = \ + self.repo.repo_config("--get", "i18n.commitEncoding").strip() or 'utf-8' + + return self.commit_encoding + + def head(self): + "get current HEAD commit id" + return self.verifyrev("HEAD") + + def verifyrev(self, rev): + "verify/lookup given revision object and return a sha id or None if lookup failed" + rev = str(rev) + + db, tag_db = self.rev_cache[2:4] + + if GitCore.is_sha(rev): + # maybe it's a short or full rev + fullrev = self.fullrev(rev) + if fullrev: + return fullrev + + # fall back to external git calls + rc = self.repo.rev_parse("--verify", rev).strip() + if not rc: + return None + + if db.has_key(rc): + return rc + + if rc in tag_db: + sha=self.repo.cat_file("tag", rc).split(None, 2)[:2] + if sha[0] != 'object': + self.logger.debug("unexpected result from 'git-cat-file tag %s'" % rc) + return None + return sha[1] + + return None + + def shortrev(self, rev, min_len=7): + "try to shorten sha id" + #try to emulate the following: + #return self.repo.rev_parse("--short", str(rev)).strip() + rev = str(rev) + + if min_len < self.__SREV_MIN: + min_len = self.__SREV_MIN + + db, tag_db, sdb = self.rev_cache[2:5] + + if rev not in db: + return None + + srev = rev[:min_len] + srevs = set(sdb[self.__rev_key(rev)]) + + if len(srevs) == 1: + return srev # we already got a unique id + + # find a shortened id for which rev doesn't conflict with + # the other ones from srevs + crevs = srevs - set([rev]) + + for l in range(min_len+1, 40): + srev = rev[:l] + if srev not in [ r[:l] for r in crevs ]: + return srev + + return rev # worst-case, all except the last character match + + def fullrev(self, srev): + "try to reverse shortrev()" + srev = str(srev) + db, tag_db, sdb = self.rev_cache[2:5] + + # short-cut + if len(srev) == 40 and srev in db: + return srev + + if not GitCore.is_sha(srev): + return None + + try: + srevs = sdb[self.__rev_key(srev)] + except KeyError: + return None + + srevs = filter(lambda s: s.startswith(srev), srevs) + if len(srevs) == 1: + return srevs[0] + + return None + + def get_branches(self): + "returns list of (local) branches, with active (= HEAD) one being the first item" + result=[] + for e in self.repo.branch("-v", "--no-abbrev").splitlines(): + (bname,bsha)=e[1:].strip().split()[:2] + if e.startswith('*'): + result.insert(0,(bname,bsha)) + else: + result.append((bname,bsha)) + return result + + def get_tags(self): + return [e.strip() for e in self.repo.tag("-l").splitlines()] + + def ls_tree(self, rev, path=""): + rev = rev and str(rev) or 'HEAD' # paranoia + if path.startswith('/'): + path = path[1:] + + tree = self.repo.ls_tree("-z", "-l", rev, "--", path).split('\0') + + def split_ls_tree_line(l): + "split according to ' \t'" + meta,fname = l.split('\t') + _mode,_type,_sha,_size = meta.split() + + if _size == '-': + _size = None + else: + _size = int(_size) + + return _mode,_type,_sha,_size,fname + + return [ split_ls_tree_line(e) for e in tree if e ] + + def read_commit(self, commit_id): + if not commit_id: + raise GitError("read_commit called with empty commit_id") + + commit_id = str(commit_id) + + db = self.get_commits() + if commit_id not in db: + self.logger.info("read_commit failed for '%s'" % commit_id) + raise GitErrorSha + + self.__commit_msg_lock.acquire() + try: + if self.__commit_msg_cache.has_key(commit_id): + # cache hit + result = self.__commit_msg_cache[commit_id] + return result[0], dict(result[1]) + + # cache miss + raw = self.repo.cat_file("commit", commit_id) + raw = unicode(raw, self.get_commit_encoding(), 'replace') + lines = raw.splitlines() + + if not lines: + raise GitErrorSha + + line = lines.pop(0) + props = {} + while line: + (key,value) = line.split(None, 1) + props.setdefault(key,[]).append(value.strip()) + line = lines.pop(0) + + result = ("\n".join(lines), props) + + self.__commit_msg_cache[commit_id] = result + + return result[0], dict(result[1]) + + finally: + self.__commit_msg_lock.release() + + def get_file(self, sha): + return cStringIO.StringIO(self.repo.cat_file("blob", str(sha))) + + def get_obj_size(self, sha): + sha = str(sha) + + try: + obj_size = int(self.repo.cat_file("-s", sha).strip()) + except ValueError: + raise GitErrorSha("object '%s' not found" % sha) + + return obj_size + + def children(self, sha): + db = self.get_commits() + + try: + return list(db[sha][0]) + except KeyError: + return [] + + def children_recursive(self, sha): + db = self.get_commits() + + work_list = deque() + seen = set() + + seen.update(db[sha][0]) + work_list.extend(db[sha][0]) + + while work_list: + p = work_list.popleft() + yield p + + _children = set(db[p][0]) - seen + + seen.update(_children) + work_list.extend(_children) + + assert len(work_list) == 0 + + def parents(self, sha): + db = self.get_commits() + + try: + return list(db[sha][1]) + except KeyError: + return [] + + def all_revs(self): + return self.get_commits().iterkeys() + + def sync(self): + rev = self.repo.rev_list("--max-count=1", "--all").strip() + return self.__rev_cache_sync(rev) + + def last_change(self, sha, path): + return self.repo.rev_list("--max-count=1", + sha, "--", path).strip() or None + + def history(self, sha, path, limit=None): + if limit is None: + limit = -1 + + tmp = self.repo.rev_list("--max-count=%d" % limit, str(sha), "--", path) + return [ rev.strip() for rev in tmp.splitlines() ] + + def history_timerange(self, start, stop): + return [ rev.strip() for rev in \ + self.repo.rev_list("--reverse", + "--max-age=%d" % start, + "--min-age=%d" % stop, + "--all").splitlines() ] + + def rev_is_anchestor_of(self, rev1, rev2): + """return True if rev2 is successor of rev1""" + rev1 = rev1.strip() + rev2 = rev2.strip() + return rev2 in self.children_recursive(rev1) + + def blame(self, commit_sha, path): + in_metadata = False + + for line in self.repo.blame("-p", "--", path, str(commit_sha)).splitlines(): + assert line + if in_metadata: + in_metadata = not line.startswith('\t') + else: + split_line = line.split() + if len(split_line) == 4: + (sha, orig_lineno, lineno, group_size) = split_line + else: + (sha, orig_lineno, lineno) = split_line + + assert len(sha) == 40 + yield (sha, lineno) + in_metadata = True + + assert not in_metadata + + def diff_tree(self, tree1, tree2, path="", find_renames=False): + """calls `git diff-tree` and returns tuples of the kind + (mode1,mode2,obj1,obj2,action,path1,path2)""" + + # diff-tree returns records with the following structure: + # : NUL NUL [ NUL ] + + path = path.strip("/") + diff_tree_args = ["-z", "-r"] + if find_renames: + diff_tree_args.append("-M") + if tree1: + diff_tree_args.append(str(tree1)) + else: + diff_tree_args.append("--root") + diff_tree_args.extend([str(tree2), + "--", path]) + + lines = self.repo.diff_tree(*diff_tree_args).split('\0') + + assert lines[-1] == "" + del lines[-1] + + if tree1 is None and lines: + # if only one tree-sha is given on commandline, + # the first line is just the redundant tree-sha itself... + assert not lines[0].startswith(':') + del lines[0] + + chg = None + + def __chg_tuple(): + if len(chg) == 6: + chg.append(None) + assert len(chg) == 7 + return tuple(chg) + + for line in lines: + if line.startswith(':'): + if chg: + yield __chg_tuple() + + chg = line[1:].split() + assert len(chg) == 5 + else: + chg.append(line) + + if chg: + yield __chg_tuple() + +############################################################################ +############################################################################ +############################################################################ + +if __name__ == '__main__': + import sys, logging, timeit + + assert not GitCore.is_sha("123") + assert GitCore.is_sha("1a3f") + assert GitCore.is_sha("f"*40) + assert not GitCore.is_sha("x"+"f"*39) + assert not GitCore.is_sha("f"*41) + + print "git version [%s]" % str(Storage.git_version()) + + # custom linux hack reading `/proc//statm` + if sys.platform == "linux2": + __pagesize = os.sysconf('SC_PAGESIZE') + + def proc_statm(pid = os.getpid()): + __proc_statm = '/proc/%d/statm' % pid + try: + t = open(__proc_statm) + result = t.read().split() + t.close() + assert len(result) == 7 + return tuple([ __pagesize*int(p) for p in result ]) + except: + raise RuntimeError("failed to get memory stats") + + else: # not linux2 + print "WARNING - meminfo.proc_statm() not available" + def proc_statm(): + return (0,)*7 + + print "statm =", proc_statm() + __data_size = proc_statm()[5] + __data_size_last = __data_size + + def print_data_usage(): + global __data_size_last + __tmp = proc_statm()[5] + print "DATA: %6d %+6d" % (__tmp - __data_size, __tmp - __data_size_last) + __data_size_last = __tmp + + print_data_usage() + + g = Storage(sys.argv[1], logging) + + print_data_usage() + + print "[%s]" % g.head() + print g.ls_tree(g.head()) + print "--------------" + print_data_usage() + print g.read_commit(g.head()) + print "--------------" + print_data_usage() + p = g.parents(g.head()) + print list(p) + print "--------------" + print list(g.children(list(p)[0])) + print list(g.children(list(p)[0])) + print "--------------" + print g.get_commit_encoding() + print "--------------" + print g.get_branches() + print "--------------" + print g.hist_prev_revision(g.oldest_rev()), g.oldest_rev(), g.hist_next_revision(g.oldest_rev()) + print_data_usage() + print "--------------" + p = g.youngest_rev() + print g.hist_prev_revision(p), p, g.hist_next_revision(p) + print "--------------" + + p = g.head() + for i in range(-5,5): + print i, g.history_relative_rev(p, i) + + # check for loops + def check4loops(head): + print "check4loops", head + seen = set([head]) + for sha in g.children_recursive(head): + if sha in seen: + print "dupe detected :-/", sha, len(seen) + seen.add(sha) + return seen + + print len(check4loops(g.parents(g.head())[0])) + + #p = g.head() + #revs = [ g.history_relative_rev(p, i) for i in range(0,10) ] + print_data_usage() + revs = g.get_commits().keys() + print_data_usage() + + def shortrev_test(): + for i in revs: + i = str(i) + s = g.shortrev(i, min_len=4) + assert i.startswith(s) + assert g.fullrev(s) == i + + iters = 1 + print "timing %d*shortrev_test()..." % len(revs) + t = timeit.Timer("shortrev_test()", "from __main__ import shortrev_test") + print "%.2f usec/rev" % (1000000 * t.timeit(number=iters)/len(revs)) + + #print len(check4loops(g.oldest_rev())) + #print len(list(g.children_recursive(g.oldest_rev()))) + + print_data_usage() + + # perform typical trac operations: + + if 1: + print "--------------" + rev = g.head() + for mode,type,sha,_size,name in g.ls_tree(rev): + [last_rev] = g.history(rev, name, limit=1) + if type == "blob": + s = g.get_obj_size(sha) + else: + s = 0 + msg = g.read_commit(last_rev) + + print "%s %s %10d [%s]" % (type, last_rev, s, name) + + print "allocating 2nd instance" + print_data_usage() + g2 = Storage(sys.argv[1], logging) + g2.head() + print_data_usage() + print "allocating 3rd instance" + g3 = Storage(sys.argv[1], logging) + g3.head() + print_data_usage() diff -Nru trac-git-0.0.20090320/0.11-py2.4/tracext/git/__init__.py trac-git-0.0.20100513/0.11-py2.4/tracext/git/__init__.py --- trac-git-0.0.20090320/0.11-py2.4/tracext/git/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.11-py2.4/tracext/git/__init__.py 2008-02-11 12:22:56.000000000 +0100 @@ -0,0 +1 @@ +# empty diff -Nru trac-git-0.0.20090320/0.11-py2.4/tracext/git/git_fs.py trac-git-0.0.20100513/0.11-py2.4/tracext/git/git_fs.py --- trac-git-0.0.20090320/0.11-py2.4/tracext/git/git_fs.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.11-py2.4/tracext/git/git_fs.py 2010-03-07 14:00:01.000000000 +0100 @@ -0,0 +1,454 @@ +# -*- coding: iso-8859-1 -*- +# +# Copyright (C) 2006,2008 Herbert Valerio Riedel +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +from trac.core import * +from trac.util import TracError, shorten_line +from trac.util.datefmt import FixedOffset, to_timestamp, format_datetime +from trac.util.text import to_unicode +from trac.versioncontrol.api import \ + Changeset, Node, Repository, IRepositoryConnector, NoSuchChangeset, NoSuchNode +from trac.wiki import IWikiSyntaxProvider +from trac.versioncontrol.cache import CachedRepository +from trac.versioncontrol.web_ui import IPropertyRenderer +from trac.config import BoolOption, IntOption, PathOption, Option +from trac.web.chrome import Chrome + +# for some reason CachedRepository doesn't pass-through short_rev()s +class CachedRepository2(CachedRepository): + def short_rev(self, path): + return self.repos.short_rev(path) + +from genshi.builder import tag +from genshi.core import Markup, escape + +from datetime import datetime +import time, sys + +if not sys.version_info[:2] >= (2,4): + raise TracError("python >= 2.4 dependancy not met") + +import PyGIT + +def _last_iterable(iterable): + "helper for detecting last iteration in for-loop" + i = iter(iterable) + v = i.next() + for nextv in i: + yield False, v + v = nextv + yield True, v + +# helper +def _parse_user_time(s): + """parse author/committer attribute lines and return + (user,timestamp)""" + (user,time,tz_str) = s.rsplit(None, 2) + tz = FixedOffset((int(tz_str)*6)/10, tz_str) + time = datetime.fromtimestamp(float(time), tz) + return (user,time) + +class GitConnector(Component): + implements(IRepositoryConnector, IWikiSyntaxProvider, IPropertyRenderer) + + def __init__(self): + self._version = None + + try: + self._version = PyGIT.Storage.git_version(git_bin=self._git_bin) + except PyGIT.GitError, e: + self.log.error("GitError: "+e.message) + + if self._version: + self.log.info("detected GIT version %s" % self._version['v_str']) + self.env.systeminfo.append(('GIT', self._version['v_str'])) + if not self._version['v_compatible']: + self.log.error("GIT version %s installed not compatible (need >= %s)" % + (self._version['v_str'], self._version['v_min_str'])) + + def _format_sha_link(self, formatter, ns, sha, label, fullmatch=None): + try: + changeset = self.env.get_repository().get_changeset(sha) + return tag.a(label, class_="changeset", + title=shorten_line(changeset.message), + href=formatter.href.changeset(sha)) + except TracError, e: + return tag.a(label, class_="missing changeset", + href=formatter.href.changeset(sha), + title=unicode(e), rel="nofollow") + + ####################### + # IPropertyRenderer + + # relied upon by GitChangeset + + def match_property(self, name, mode): + if name in ('Parents','Children','git-committer','git-author') \ + and mode == 'revprop': + return 8 # default renderer has priority 1 + return 0 + + def render_property(self, name, mode, context, props): + def sha_link(sha): + return self._format_sha_link(context, 'sha', sha, sha) + + if name in ('Parents','Children'): + revs = props[name] + + return tag([tag(sha_link(rev), ', ') for rev in revs[:-1]], + sha_link(revs[-1])) + + if name in ('git-committer', 'git-author'): + user_, time_ = props[name] + _str = "%s (%s)" % (Chrome(self.env).format_author(context.req, user_), + format_datetime(time_, tzinfo=context.req.tz)) + return unicode(_str) + + raise TracError("internal error") + + ####################### + # IWikiSyntaxProvider + + def get_wiki_syntax(self): + yield (r'(?:\b|!)[0-9a-fA-F]{40,40}\b', + lambda fmt, sha, match: + self._format_sha_link(fmt, 'changeset', sha, sha)) + + def get_link_resolvers(self): + yield ('sha', self._format_sha_link) + + ####################### + # IRepositoryConnector + + _persistent_cache = BoolOption('git', 'persistent_cache', 'false', + "enable persistent caching of commit tree") + + _cached_repository = BoolOption('git', 'cached_repository', 'false', + "wrap `GitRepository` in `CachedRepository`") + + _shortrev_len = IntOption('git', 'shortrev_len', 7, + "length rev sha sums should be tried to be abbreviated to" + " (must be >= 4 and <= 40)") + + _git_bin = PathOption('git', 'git_bin', '/usr/bin/git', "path to git executable (relative to trac project folder!)") + + + def get_supported_types(self): + yield ("git", 8) + + def get_repository(self, type, dir, authname): + """GitRepository factory method""" + assert type == "git" + + if not self._version: + raise TracError("GIT backend not available") + elif not self._version['v_compatible']: + raise TracError("GIT version %s installed not compatible (need >= %s)" % + (self._version['v_str'], self._version['v_min_str'])) + + repos = GitRepository(dir, self.log, + persistent_cache=self._persistent_cache, + git_bin=self._git_bin, + shortrev_len=self._shortrev_len) + + if self._cached_repository: + repos = CachedRepository2(self.env.get_db_cnx(), repos, None, self.log) + self.log.info("enabled CachedRepository for '%s'" % dir) + else: + self.log.info("disabled CachedRepository for '%s'" % dir) + + return repos + +class GitRepository(Repository): + def __init__(self, path, log, persistent_cache=False, git_bin='git', shortrev_len=7): + self.logger = log + self.gitrepo = path + self._shortrev_len = max(4, min(shortrev_len, 40)) + + self.git = PyGIT.StorageFactory(path, log, not persistent_cache, + git_bin=git_bin).getInstance() + Repository.__init__(self, "git:"+path, None, log) + + def close(self): + self.git = None + + def clear(self, youngest_rev=None): + self.youngest = None + if youngest_rev is not None: + self.youngest = self.normalize_rev(youngest_rev) + self.oldest = None + + def get_youngest_rev(self): + return self.git.youngest_rev() + + def get_oldest_rev(self): + return self.git.oldest_rev() + + def normalize_path(self, path): + return path and path.strip('/') or '/' + + def normalize_rev(self, rev): + if not rev: + return self.get_youngest_rev() + normrev=self.git.verifyrev(rev) + if normrev is None: + raise NoSuchChangeset(rev) + return normrev + + def short_rev(self, rev): + return self.git.shortrev(self.normalize_rev(rev), min_len=self._shortrev_len) + + def get_node(self, path, rev=None): + return GitNode(self.git, path, rev, self.log) + + def get_quickjump_entries(self, rev): + for bname,bsha in self.git.get_branches(): + yield 'branches', bname, '/', bsha + for t in self.git.get_tags(): + yield 'tags', t, '/', t + + def get_changesets(self, start, stop): + for rev in self.git.history_timerange(to_timestamp(start), to_timestamp(stop)): + yield self.get_changeset(rev) + + def get_changeset(self, rev): + """GitChangeset factory method""" + return GitChangeset(self.git, rev) + + def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=0): + # TODO: handle renames/copies, ignore_ancestry + if old_path != new_path: + raise TracError("not supported in git_fs") + + for chg in self.git.diff_tree(old_rev, new_rev, self.normalize_path(new_path)): + (mode1,mode2,obj1,obj2,action,path,path2) = chg + + kind = Node.FILE + if mode2.startswith('04') or mode1.startswith('04'): + kind = Node.DIRECTORY + + change = GitChangeset.action_map[action] + + old_node = None + new_node = None + + if change != Changeset.ADD: + old_node = self.get_node(path, old_rev) + if change != Changeset.DELETE: + new_node = self.get_node(path, new_rev) + + yield (old_node, new_node, kind, change) + + def next_rev(self, rev, path=''): + return self.git.hist_next_revision(rev) + + def previous_rev(self, rev, path=''): + return self.git.hist_prev_revision(rev) + + def rev_older_than(self, rev1, rev2): + rc = self.git.rev_is_anchestor_of(rev1, rev2) + return rc + + def clear(self, youngest_rev=None): + self.sync() + + def sync(self, rev_callback=None): + if rev_callback: + revs = set(self.git.all_revs()) + + if not self.git.sync(): + return None # nothing expected to change + + if rev_callback: + revs = set(self.git.all_revs()) - revs + for rev in revs: + rev_callback(rev) + +class GitNode(Node): + def __init__(self, git, path, rev, log, ls_tree_info=None): + self.log = log + self.git = git + self.fs_sha = None # points to either tree or blobs + self.fs_perm = None + self.fs_size = None + rev = rev and str(rev) or 'HEAD' + + kind = Node.DIRECTORY + p = path.strip('/') + if p: # ie. not the root-tree + if not ls_tree_info: + ls_tree_info = git.ls_tree(rev, p) or None + if ls_tree_info: + [ls_tree_info] = ls_tree_info + + if not ls_tree_info: + raise NoSuchNode(path, rev) + + (self.fs_perm, k, self.fs_sha, self.fs_size, fn) = ls_tree_info + + # fix-up to the last commit-rev that touched this node + rev = self.git.last_change(rev, p) + + if k=='tree': + pass + elif k=='blob': + kind = Node.FILE + else: + raise TracError("internal error (got unexpected object kind '%s')" % k) + + self.created_path = path + self.created_rev = rev + + Node.__init__(self, path, rev, kind) + + def __git_path(self): + "return path as expected by PyGIT" + p = self.path.strip('/') + if self.isfile: + assert p + return p + if self.isdir: + return p and (p + '/') + + raise TracError("internal error") + + def get_content(self): + if not self.isfile: + return None + + return self.git.get_file(self.fs_sha) + + def get_properties(self): + return self.fs_perm and {'mode': self.fs_perm } or {} + + def get_annotations(self): + if not self.isfile: + return + + return [ rev for (rev,lineno) in self.git.blame(self.rev, self.__git_path()) ] + + def get_entries(self): + if not self.isdir: + return + + for ent in self.git.ls_tree(self.rev, self.__git_path()): + yield GitNode(self.git, ent[-1], self.rev, self.log, ent) + + def get_content_type(self): + if self.isdir: + return None + + return '' + + def get_content_length(self): + if not self.isfile: + return None + + if self.fs_size is None: + self.fs_size = self.git.get_obj_size(self.fs_sha) + + return self.fs_size + + def get_history(self, limit=None): + # TODO: find a way to follow renames/copies + for is_last,rev in _last_iterable(self.git.history(self.rev, self.__git_path(), limit)): + if is_last: + chg = Changeset.ADD + else: + chg = Changeset.EDIT + yield (self.path, rev, chg) + + def get_last_modified(self): + if not self.isfile: + return None + + try: + msg, props = self.git.read_commit(self.rev) + user,ts = _parse_user_time(props['committer'][0]) + except: + self.log.error("internal error (could not get timestamp from commit '%s')" % self.rev) + return None + + return ts + +class GitChangeset(Changeset): + + action_map = { # see also git-diff-tree(1) --diff-filter + 'A': Changeset.ADD, + 'M': Changeset.EDIT, # modified + 'T': Changeset.EDIT, # file type (mode) change + 'D': Changeset.DELETE, + 'R': Changeset.MOVE, # renamed + 'C': Changeset.COPY + } # TODO: U, X, B + + def __init__(self, git, sha): + self.git = git + try: + (msg, props) = git.read_commit(sha) + except PyGIT.GitErrorSha: + raise NoSuchChangeset(sha) + self.props = props + + assert 'children' not in props + _children = list(git.children(sha)) + if _children: + props['children'] = _children + + # use 1st committer as changeset owner/timestamp + (user_, time_) = _parse_user_time(props['committer'][0]) + + Changeset.__init__(self, sha, msg, user_, time_) + + def get_properties(self): + properties = {} + if 'parent' in self.props: + properties['Parents'] = self.props['parent'] + if 'children' in self.props: + properties['Children'] = self.props['children'] + if 'committer' in self.props: + properties['git-committer'] = \ + _parse_user_time(self.props['committer'][0]) + if 'author' in self.props: + git_author = _parse_user_time(self.props['author'][0]) + if not properties.get('git-committer') == git_author: + properties['git-author'] = git_author + + return properties + + def get_changes(self): + paths_seen = set() + for parent in self.props.get('parent', [None]): + for mode1,mode2,obj1,obj2,action,path1,path2 in \ + self.git.diff_tree(parent, self.rev, find_renames=True): + path = path2 or path1 + p_path, p_rev = path1, parent + + kind = Node.FILE + if mode2.startswith('04') or mode1.startswith('04'): + kind = Node.DIRECTORY + + action = GitChangeset.action_map[action[0]] + + if action == Changeset.ADD: + p_path = '' + p_rev = None + + # CachedRepository expects unique (rev, path, change_type) key + # this is only an issue in case of merges where files required editing + if path in paths_seen: + continue + + paths_seen.add(path) + + yield (to_unicode(path), kind, action, to_unicode(p_path), p_rev) diff -Nru trac-git-0.0.20090320/0.12/COPYING trac-git-0.0.20100513/0.12/COPYING --- trac-git-0.0.20090320/0.12/COPYING 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.12/COPYING 2006-09-29 12:24:18.000000000 +0200 @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff -Nru trac-git-0.0.20090320/0.12/README trac-git-0.0.20100513/0.12/README --- trac-git-0.0.20090320/0.12/README 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.12/README 2006-09-29 12:24:18.000000000 +0200 @@ -0,0 +1,6 @@ +========= +GitPlugin +========= + +see http://www.trac-hacks.org/wiki/GitPlugin for instructions + diff -Nru trac-git-0.0.20090320/0.12/setup.cfg trac-git-0.0.20100513/0.12/setup.cfg --- trac-git-0.0.20090320/0.12/setup.cfg 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.12/setup.cfg 2010-03-07 12:43:05.000000000 +0100 @@ -0,0 +1,3 @@ +[egg_info] +tag_build = dev +tag_svn_revision = true diff -Nru trac-git-0.0.20090320/0.12/setup.py trac-git-0.0.20100513/0.12/setup.py --- trac-git-0.0.20090320/0.12/setup.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.12/setup.py 2010-02-21 23:28:17.000000000 +0100 @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup( + name='TracGit', + install_requires=[],#'Trac ==0.12', + description='GIT version control plugin for Trac 0.12', + author='Herbert Valerio Riedel', + author_email='hvr@gnu.org', + keywords='trac scm plugin git', + url="http://trac-hacks.org/wiki/GitPlugin", + version='0.12.0.2', + license="GPL", + long_description=""" + This Trac 0.12 plugin provides support for the GIT SCM. + + See http://trac-hacks.org/wiki/GitPlugin for more details. + """, + packages=['tracext', 'tracext.git'], + namespace_packages=['tracext'], + entry_points = {'trac.plugins': 'git = tracext.git.git_fs'}, + data_files=['COPYING','README']) diff -Nru trac-git-0.0.20090320/0.12/tracext/__init__.py trac-git-0.0.20100513/0.12/tracext/__init__.py --- trac-git-0.0.20090320/0.12/tracext/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.12/tracext/__init__.py 2008-02-11 12:22:56.000000000 +0100 @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff -Nru trac-git-0.0.20090320/0.12/tracext/git/PyGIT.py trac-git-0.0.20100513/0.12/tracext/git/PyGIT.py --- trac-git-0.0.20090320/0.12/tracext/git/PyGIT.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.12/tracext/git/PyGIT.py 2010-03-07 17:13:38.000000000 +0100 @@ -0,0 +1,824 @@ +# -*- coding: iso-8859-1 -*- +# +# Copyright (C) 2006,2008 Herbert Valerio Riedel +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +from __future__ import with_statement + +import os, re, sys, time, weakref +from collections import deque +from functools import partial +from threading import Lock +from subprocess import Popen, PIPE +import cStringIO +import string + +__all__ = ["git_version", "GitError", "GitErrorSha", "Storage", "StorageFactory"] + +class GitError(Exception): + pass + +class GitErrorSha(GitError): + pass + +class GitCore: + def __init__(self, git_dir=None, git_bin="git"): + self.__git_bin = git_bin + self.__git_dir = git_dir + + def __build_git_cmd(self, gitcmd, *args): + "construct command tuple for git call suitable for Popen()" + + cmd = [self.__git_bin] + if self.__git_dir: + cmd.append('--git-dir=%s' % self.__git_dir) + cmd.append(gitcmd) + cmd.extend(args) + + return cmd + + def __execute(self, git_cmd, *cmd_args): + "execute git command and return file-like object of stdout" + + #print >>sys.stderr, "DEBUG:", git_cmd, cmd_args + + if sys.platform == "win32": + p = Popen(self.__build_git_cmd(git_cmd, *cmd_args), + stdin=None, stdout=PIPE, stderr=PIPE) + else: + p = Popen(self.__build_git_cmd(git_cmd, *cmd_args), + stdin=None, stdout=PIPE, stderr=PIPE, close_fds=True) + + stdout_data, stderr_data = p.communicate() + #TODO, do something with p.returncode, e.g. raise exception + + return stdout_data + + def __getattr__(self, name): + return partial(self.__execute, name.replace('_','-')) + + __is_sha_pat = re.compile(r'[0-9A-Fa-f]*$') + + @classmethod + def is_sha(cls,sha): + """returns whether sha is a potential sha id + (i.e. proper hexstring between 4 and 40 characters""" + if not (4 <= len(sha) <= 40): + return False + + return bool(cls.__is_sha_pat.match(sha)) + +# helper class for caching... +class SizedDict(dict): + def __init__(self, max_size=0): + dict.__init__(self) + self.__max_size = max_size + self.__key_fifo = deque() + self.__lock = Lock() + + def __setitem__(self, name, value): + with self.__lock: + assert len(self) == len(self.__key_fifo) # invariant + + if not self.__contains__(name): + self.__key_fifo.append(name) + + rc = dict.__setitem__(self, name, value) + + while len(self.__key_fifo) > self.__max_size: + self.__delitem__(self.__key_fifo.popleft()) + + assert len(self) == len(self.__key_fifo) # invariant + + return rc + + def setdefault(k,d=None): + # TODO + raise AttributeError("SizedDict has no setdefault() method") + +class StorageFactory: + __dict = weakref.WeakValueDictionary() + __dict_nonweak = dict() + __dict_lock = Lock() + + def __init__(self, repo, log, weak=True, git_bin='git'): + self.logger = log + + with StorageFactory.__dict_lock: + try: + i = StorageFactory.__dict[repo] + except KeyError: + i = Storage(repo, log, git_bin) + StorageFactory.__dict[repo] = i + + # create or remove additional reference depending on 'weak' argument + if weak: + try: + del StorageFactory.__dict_nonweak[repo] + except KeyError: + pass + else: + StorageFactory.__dict_nonweak[repo] = i + + self.__inst = i + self.__repo = repo + + def getInstance(self): + is_weak = self.__repo not in StorageFactory.__dict_nonweak + self.logger.debug("requested %sPyGIT.Storage instance %d for '%s'" + % (("","weak ")[is_weak], id(self.__inst), self.__repo)) + return self.__inst + +class Storage: + __SREV_MIN = 4 # minimum short-rev length + + @staticmethod + def __rev_key(rev): + assert len(rev) >= 4 + #assert GitCore.is_sha(rev) + srev_key = int(rev[:4], 16) + assert srev_key >= 0 and srev_key <= 0xffff + return srev_key + + @staticmethod + def git_version(git_bin="git"): + GIT_VERSION_MIN_REQUIRED = (1,5,6) + try: + g = GitCore(git_bin=git_bin) + [v] = g.version().splitlines() + _,_,version = v.strip().split() + # 'version' has usually at least 3 numeric version components, e.g.:: + # 1.5.4.2 + # 1.5.4.3.230.g2db511 + # 1.5.4.GIT + + def try_int(s): + try: + return int(s) + except ValueError: + return s + + split_version = tuple(map(try_int, version.split('.'))) + + result = {} + result['v_str'] = version + result['v_tuple'] = split_version + result['v_min_tuple'] = GIT_VERSION_MIN_REQUIRED + result['v_min_str'] = ".".join(map(str, GIT_VERSION_MIN_REQUIRED)) + result['v_compatible'] = split_version >= GIT_VERSION_MIN_REQUIRED + return result + except: + raise GitError("Could not retrieve GIT version") + + def __init__(self, git_dir, log, git_bin='git'): + self.logger = log + + # simple sanity checking + __git_file_path = partial(os.path.join, git_dir) + if not all(map(os.path.exists, + map(__git_file_path, + ['HEAD','objects','refs']))): + self.logger.error("GIT control files missing in '%s'" % git_dir) + if os.path.exists(__git_file_path('.git')): + self.logger.error("entry '.git' found in '%s'" + " -- maybe use that folder instead..." % git_dir) + raise GitError("GIT control files not found, maybe wrong directory?") + + self.logger.debug("PyGIT.Storage instance %d constructed" % id(self)) + + self.repo = GitCore(git_dir, git_bin=git_bin) + + self.commit_encoding = None + + # caches + self.__rev_cache = None + self.__rev_cache_lock = Lock() + + # cache the last 200 commit messages + self.__commit_msg_cache = SizedDict(200) + self.__commit_msg_lock = Lock() + + + + def __del__(self): + self.logger.debug("PyGIT.Storage instance %d destructed" % id(self)) + + # + # cache handling + # + + # called by Storage.sync() + def __rev_cache_sync(self, youngest_rev=None): + "invalidates revision db cache if necessary" + with self.__rev_cache_lock: + need_update = False + if self.__rev_cache: + last_youngest_rev = self.__rev_cache[0] + if last_youngest_rev != youngest_rev: + self.logger.debug("invalidated caches (%s != %s)" % (last_youngest_rev, youngest_rev)) + need_update = True + else: + need_update = True # almost NOOP + + if need_update: + self.__rev_cache = None + + return need_update + + def get_rev_cache(self): + with self.__rev_cache_lock: + if self.__rev_cache is None: # can be cleared by Storage.__rev_cache_sync() + self.logger.debug("triggered rebuild of commit tree db for %d" % id(self)) + new_db = {} + new_sdb = {} + new_tags = set([]) + youngest = None + oldest = None + for revs in self.repo.rev_parse("--tags").splitlines(): + new_tags.add(revs.strip()) + + # helper for reusing strings + __rev_seen = {} + def __rev_reuse(rev): + rev = str(rev) + return __rev_seen.setdefault(rev, rev) + + rev = ord_rev = 0 + for revs in self.repo.rev_list("--parents", "--all").splitlines(): + revs = revs.strip().split() + + revs = map(__rev_reuse, revs) + + rev = revs[0] + + # shortrev "hash" map + srev_key = self.__rev_key(rev) + new_sdb.setdefault(srev_key, []).append(rev) + + parents = tuple(revs[1:]) + + ord_rev += 1 + + # first rev seen is assumed to be the youngest one (and has ord_rev=1) + if not youngest: + youngest = rev + + # new_db[rev] = (children(rev), parents(rev), ordinal_id(rev)) + if new_db.has_key(rev): + _children,_parents,_ord_rev = new_db[rev] + assert _children + assert not _parents + assert _ord_rev == 0 + else: + _children = [] + + # create/update entry + new_db[rev] = _children, parents, ord_rev + + # update all parents(rev)'s children + for parent in parents: + # by default, a dummy ordinal_id is used for the mean-time + _children, _parents, _ord_rev = new_db.setdefault(parent, ([], [], 0)) + if rev not in _children: + _children.append(rev) + + # last rev seen is assumed to be the oldest one (with highest ord_rev) + oldest = rev + + __rev_seen = None + + assert len(new_db) == ord_rev + + # convert children lists to tuples + tmp = {} + try: + while True: + k,v = new_db.popitem() + assert v[2] > 0 + tmp[k] = tuple(v[0]),v[1],v[2] + except KeyError: + pass + + assert len(new_db) == 0 + new_db = tmp + + # convert sdb either to dict or array depending on size + tmp = [()]*(max(new_sdb.keys())+1) if len(new_sdb) > 5000 else {} + + try: + while True: + k,v = new_sdb.popitem() + tmp[k] = tuple(v) + except KeyError: + pass + + assert len(new_sdb) == 0 + new_sdb = tmp + + # atomically update self.__rev_cache + self.__rev_cache = youngest, oldest, new_db, new_tags, new_sdb + self.logger.debug("rebuilt commit tree db for %d with %d entries" % (id(self),len(new_db))) + + assert all(e is not None for e in self.__rev_cache) or not any(self.__rev_cache) + + return self.__rev_cache + # with self.__rev_cache_lock + + # tuple: youngest_rev, oldest_rev, rev_dict, tag_dict, short_rev_dict + rev_cache = property(get_rev_cache) + + def get_commits(self): + return self.rev_cache[2] + + def oldest_rev(self): + return self.rev_cache[1] + + def youngest_rev(self): + return self.rev_cache[0] + + def history_relative_rev(self, sha, rel_pos): + db = self.get_commits() + + if sha not in db: + raise GitErrorSha + + if rel_pos == 0: + return sha + + lin_rev = db[sha][2] + rel_pos + + if lin_rev < 1 or lin_rev > len(db): + return None + + for k,v in db.iteritems(): + if v[2] == lin_rev: + return k + + # should never be reached if db is consistent + raise GitError("internal inconsistency detected") + + def hist_next_revision(self, sha): + return self.history_relative_rev(sha, -1) + + def hist_prev_revision(self, sha): + return self.history_relative_rev(sha, +1) + + def get_commit_encoding(self): + if self.commit_encoding is None: + self.commit_encoding = \ + self.repo.repo_config("--get", "i18n.commitEncoding").strip() or 'utf-8' + + return self.commit_encoding + + def head(self): + "get current HEAD commit id" + return self.verifyrev("HEAD") + + def verifyrev(self, rev): + "verify/lookup given revision object and return a sha id or None if lookup failed" + rev = str(rev) + + db, tag_db = self.rev_cache[2:4] + + if GitCore.is_sha(rev): + # maybe it's a short or full rev + fullrev = self.fullrev(rev) + if fullrev: + return fullrev + + # fall back to external git calls + rc = self.repo.rev_parse("--verify", rev).strip() + if not rc: + return None + + if db.has_key(rc): + return rc + + if rc in tag_db: + sha=self.repo.cat_file("tag", rc).split(None, 2)[:2] + if sha[0] != 'object': + self.logger.debug("unexpected result from 'git-cat-file tag %s'" % rc) + return None + return sha[1] + + return None + + def shortrev(self, rev, min_len=7): + "try to shorten sha id" + #try to emulate the following: + #return self.repo.rev_parse("--short", str(rev)).strip() + rev = str(rev) + + if min_len < self.__SREV_MIN: + min_len = self.__SREV_MIN + + db, tag_db, sdb = self.rev_cache[2:5] + + if rev not in db: + return None + + srev = rev[:min_len] + srevs = set(sdb[self.__rev_key(rev)]) + + if len(srevs) == 1: + return srev # we already got a unique id + + # find a shortened id for which rev doesn't conflict with + # the other ones from srevs + crevs = srevs - set([rev]) + + for l in range(min_len+1, 40): + srev = rev[:l] + if srev not in [ r[:l] for r in crevs ]: + return srev + + return rev # worst-case, all except the last character match + + def fullrev(self, srev): + "try to reverse shortrev()" + srev = str(srev) + db, tag_db, sdb = self.rev_cache[2:5] + + # short-cut + if len(srev) == 40 and srev in db: + return srev + + if not GitCore.is_sha(srev): + return None + + try: + srevs = sdb[self.__rev_key(srev)] + except KeyError: + return None + + srevs = filter(lambda s: s.startswith(srev), srevs) + if len(srevs) == 1: + return srevs[0] + + return None + + def get_branches(self): + "returns list of (local) branches, with active (= HEAD) one being the first item" + result=[] + for e in self.repo.branch("-v", "--no-abbrev").splitlines(): + (bname,bsha)=e[1:].strip().split()[:2] + if e.startswith('*'): + result.insert(0,(bname,bsha)) + else: + result.append((bname,bsha)) + return result + + def get_tags(self): + return [e.strip() for e in self.repo.tag("-l").splitlines()] + + def ls_tree(self, rev, path=""): + rev = rev and str(rev) or 'HEAD' # paranoia + if path.startswith('/'): + path = path[1:] + + tree = self.repo.ls_tree("-z", "-l", rev, "--", path).split('\0') + + def split_ls_tree_line(l): + "split according to ' \t'" + meta,fname = l.split('\t') + _mode,_type,_sha,_size = meta.split() + + if _size == '-': + _size = None + else: + _size = int(_size) + + return _mode,_type,_sha,_size,fname + + return [ split_ls_tree_line(e) for e in tree if e ] + + def read_commit(self, commit_id): + if not commit_id: + raise GitError("read_commit called with empty commit_id") + + commit_id, commit_id_orig = self.fullrev(commit_id), commit_id + + db = self.get_commits() + if commit_id not in db: + self.logger.info("read_commit failed for '%s' ('%s')" % + (commit_id, commit_id_orig)) + raise GitErrorSha + + with self.__commit_msg_lock: + if self.__commit_msg_cache.has_key(commit_id): + # cache hit + result = self.__commit_msg_cache[commit_id] + return result[0], dict(result[1]) + + # cache miss + raw = self.repo.cat_file("commit", commit_id) + raw = unicode(raw, self.get_commit_encoding(), 'replace') + lines = raw.splitlines() + + if not lines: + raise GitErrorSha + + line = lines.pop(0) + props = {} + while line: + (key,value) = line.split(None, 1) + props.setdefault(key,[]).append(value.strip()) + line = lines.pop(0) + + result = ("\n".join(lines), props) + + self.__commit_msg_cache[commit_id] = result + + return result[0], dict(result[1]) + + def get_file(self, sha): + return cStringIO.StringIO(self.repo.cat_file("blob", str(sha))) + + def get_obj_size(self, sha): + sha = str(sha) + + try: + obj_size = int(self.repo.cat_file("-s", sha).strip()) + except ValueError: + raise GitErrorSha("object '%s' not found" % sha) + + return obj_size + + def children(self, sha): + db = self.get_commits() + + try: + return list(db[sha][0]) + except KeyError: + return [] + + def children_recursive(self, sha): + db = self.get_commits() + + work_list = deque() + seen = set() + + seen.update(db[sha][0]) + work_list.extend(db[sha][0]) + + while work_list: + p = work_list.popleft() + yield p + + _children = set(db[p][0]) - seen + + seen.update(_children) + work_list.extend(_children) + + assert len(work_list) == 0 + + def parents(self, sha): + db = self.get_commits() + + try: + return list(db[sha][1]) + except KeyError: + return [] + + def all_revs(self): + return self.get_commits().iterkeys() + + def sync(self): + rev = self.repo.rev_list("--max-count=1", "--all").strip() + return self.__rev_cache_sync(rev) + + def last_change(self, sha, path): + return self.repo.rev_list("--max-count=1", + sha, "--", path).strip() or None + + def history(self, sha, path, limit=None): + if limit is None: + limit = -1 + + tmp = self.repo.rev_list("--max-count=%d" % limit, str(sha), "--", path) + return [ rev.strip() for rev in tmp.splitlines() ] + + def history_timerange(self, start, stop): + return [ rev.strip() for rev in \ + self.repo.rev_list("--reverse", + "--max-age=%d" % start, + "--min-age=%d" % stop, + "--all").splitlines() ] + + def rev_is_anchestor_of(self, rev1, rev2): + """return True if rev2 is successor of rev1""" + rev1 = rev1.strip() + rev2 = rev2.strip() + return rev2 in self.children_recursive(rev1) + + def blame(self, commit_sha, path): + in_metadata = False + + for line in self.repo.blame("-p", "--", path, str(commit_sha)).splitlines(): + assert line + if in_metadata: + in_metadata = not line.startswith('\t') + else: + split_line = line.split() + if len(split_line) == 4: + (sha, orig_lineno, lineno, group_size) = split_line + else: + (sha, orig_lineno, lineno) = split_line + + assert len(sha) == 40 + yield (sha, lineno) + in_metadata = True + + assert not in_metadata + + def diff_tree(self, tree1, tree2, path="", find_renames=False): + """calls `git diff-tree` and returns tuples of the kind + (mode1,mode2,obj1,obj2,action,path1,path2)""" + + # diff-tree returns records with the following structure: + # : NUL NUL [ NUL ] + + path = path.strip("/") + diff_tree_args = ["-z", "-r"] + if find_renames: + diff_tree_args.append("-M") + diff_tree_args.extend([str(tree1) if tree1 else "--root", + str(tree2), + "--", path]) + + lines = self.repo.diff_tree(*diff_tree_args).split('\0') + + assert lines[-1] == "" + del lines[-1] + + if tree1 is None and lines: + # if only one tree-sha is given on commandline, + # the first line is just the redundant tree-sha itself... + assert not lines[0].startswith(':') + del lines[0] + + chg = None + + def __chg_tuple(): + if len(chg) == 6: + chg.append(None) + assert len(chg) == 7 + return tuple(chg) + + for line in lines: + if line.startswith(':'): + if chg: + yield __chg_tuple() + + chg = line[1:].split() + assert len(chg) == 5 + else: + chg.append(line) + + if chg: + yield __chg_tuple() + +############################################################################ +############################################################################ +############################################################################ + +if __name__ == '__main__': + import sys, logging, timeit + + assert not GitCore.is_sha("123") + assert GitCore.is_sha("1a3f") + assert GitCore.is_sha("f"*40) + assert not GitCore.is_sha("x"+"f"*39) + assert not GitCore.is_sha("f"*41) + + print "git version [%s]" % str(Storage.git_version()) + + # custom linux hack reading `/proc//statm` + if sys.platform == "linux2": + __pagesize = os.sysconf('SC_PAGESIZE') + + def proc_statm(pid = os.getpid()): + __proc_statm = '/proc/%d/statm' % pid + try: + t = open(__proc_statm) + result = t.read().split() + t.close() + assert len(result) == 7 + return tuple([ __pagesize*int(p) for p in result ]) + except: + raise RuntimeError("failed to get memory stats") + + else: # not linux2 + print "WARNING - meminfo.proc_statm() not available" + def proc_statm(): + return (0,)*7 + + print "statm =", proc_statm() + __data_size = proc_statm()[5] + __data_size_last = __data_size + + def print_data_usage(): + global __data_size_last + __tmp = proc_statm()[5] + print "DATA: %6d %+6d" % (__tmp - __data_size, __tmp - __data_size_last) + __data_size_last = __tmp + + print_data_usage() + + g = Storage(sys.argv[1], logging) + + print_data_usage() + + print "[%s]" % g.head() + print g.ls_tree(g.head()) + print "--------------" + print_data_usage() + print g.read_commit(g.head()) + print "--------------" + print_data_usage() + p = g.parents(g.head()) + print list(p) + print "--------------" + print list(g.children(list(p)[0])) + print list(g.children(list(p)[0])) + print "--------------" + print g.get_commit_encoding() + print "--------------" + print g.get_branches() + print "--------------" + print g.hist_prev_revision(g.oldest_rev()), g.oldest_rev(), g.hist_next_revision(g.oldest_rev()) + print_data_usage() + print "--------------" + p = g.youngest_rev() + print g.hist_prev_revision(p), p, g.hist_next_revision(p) + print "--------------" + + p = g.head() + for i in range(-5,5): + print i, g.history_relative_rev(p, i) + + # check for loops + def check4loops(head): + print "check4loops", head + seen = set([head]) + for sha in g.children_recursive(head): + if sha in seen: + print "dupe detected :-/", sha, len(seen) + seen.add(sha) + return seen + + print len(check4loops(g.parents(g.head())[0])) + + #p = g.head() + #revs = [ g.history_relative_rev(p, i) for i in range(0,10) ] + print_data_usage() + revs = g.get_commits().keys() + print_data_usage() + + def shortrev_test(): + for i in revs: + i = str(i) + s = g.shortrev(i, min_len=4) + assert i.startswith(s) + assert g.fullrev(s) == i + + iters = 1 + print "timing %d*shortrev_test()..." % len(revs) + t = timeit.Timer("shortrev_test()", "from __main__ import shortrev_test") + print "%.2f usec/rev" % (1000000 * t.timeit(number=iters)/len(revs)) + + #print len(check4loops(g.oldest_rev())) + #print len(list(g.children_recursive(g.oldest_rev()))) + + print_data_usage() + + # perform typical trac operations: + + if 1: + print "--------------" + rev = g.head() + for mode,type,sha,_size,name in g.ls_tree(rev): + [last_rev] = g.history(rev, name, limit=1) + s = g.get_obj_size(sha) if type == "blob" else 0 + msg = g.read_commit(last_rev) + + print "%s %s %10d [%s]" % (type, last_rev, s, name) + + print "allocating 2nd instance" + print_data_usage() + g2 = Storage(sys.argv[1], logging) + g2.head() + print_data_usage() + print "allocating 3rd instance" + g3 = Storage(sys.argv[1], logging) + g3.head() + print_data_usage() diff -Nru trac-git-0.0.20090320/0.12/tracext/git/__init__.py trac-git-0.0.20100513/0.12/tracext/git/__init__.py --- trac-git-0.0.20090320/0.12/tracext/git/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.12/tracext/git/__init__.py 2008-02-11 12:22:56.000000000 +0100 @@ -0,0 +1 @@ +# empty diff -Nru trac-git-0.0.20090320/0.12/tracext/git/git_fs.py trac-git-0.0.20100513/0.12/tracext/git/git_fs.py --- trac-git-0.0.20090320/0.12/tracext/git/git_fs.py 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/0.12/tracext/git/git_fs.py 2010-03-07 18:16:57.000000000 +0100 @@ -0,0 +1,476 @@ +# -*- coding: iso-8859-1 -*- +# +# Copyright (C) 2006,2008 Herbert Valerio Riedel +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +from trac.core import * +from trac.util import TracError, shorten_line +from trac.util.datefmt import FixedOffset, to_timestamp, format_datetime +from trac.util.text import to_unicode +from trac.versioncontrol.api import \ + Changeset, Node, Repository, IRepositoryConnector, NoSuchChangeset, NoSuchNode +from trac.wiki import IWikiSyntaxProvider +from trac.versioncontrol.cache import CachedRepository +from trac.versioncontrol.web_ui import IPropertyRenderer +from trac.config import BoolOption, IntOption, PathOption, Option +from trac.web.chrome import Chrome + +# for some reason CachedRepository doesn't pass-through short_rev()s +class CachedRepository2(CachedRepository): + def short_rev(self, path): + return self.repos.short_rev(path) + def normalize_rev(self, rev): + if not rev: + return self.repos.get_youngest_rev() + normrev=self.repos.git.verifyrev(rev) + if normrev is None: + raise NoSuchChangeset(rev) + return normrev + + +from genshi.builder import tag +from genshi.core import Markup, escape + +from datetime import datetime +import time, sys + +if not sys.version_info[:2] >= (2,5): + raise TracError("python >= 2.5 dependancy not met") + +import PyGIT + +def _last_iterable(iterable): + "helper for detecting last iteration in for-loop" + i = iter(iterable) + v = i.next() + for nextv in i: + yield False, v + v = nextv + yield True, v + +# helper +def _parse_user_time(s): + """parse author/committer attribute lines and return + (user,timestamp)""" + (user,time,tz_str) = s.rsplit(None, 2) + tz = FixedOffset((int(tz_str)*6)/10, tz_str) + time = datetime.fromtimestamp(float(time), tz) + return (user,time) + +class GitConnector(Component): + implements(IRepositoryConnector, IWikiSyntaxProvider, IPropertyRenderer) + + def __init__(self): + self._version = None + + try: + self._version = PyGIT.Storage.git_version(git_bin=self._git_bin) + except PyGIT.GitError, e: + self.log.error("GitError: "+e.message) + + if self._version: + self.log.info("detected GIT version %s" % self._version['v_str']) + self.env.systeminfo.append(('GIT', self._version['v_str'])) + if not self._version['v_compatible']: + self.log.error("GIT version %s installed not compatible (need >= %s)" % + (self._version['v_str'], self._version['v_min_str'])) + + def _format_sha_link(self, formatter, ns, sha, label, context=None): + reponame = '' + if context is None: + context = formatter.context + if formatter is None: + formatter = context # hack + while context: + if context.resource.realm in ('source', 'changeset'): + reponame = context.resource.parent.id + break + context = context.parent + repos = self.env.get_repository(reponame) + if repos: + try: + changeset = repos.get_changeset(sha) + return tag.a(label, class_="changeset", + title=shorten_line(changeset.message), + href=formatter.href.changeset(sha, reponame)) + except Exception, e: + errmsg = to_unicode(e) + else: + errmsg = "Repository '%s' not found" % reponame + + return tag.a(label, class_="missing changeset", + #href=formatter.href.changeset(sha, reponame), + title=to_unicode(errmsg), rel="nofollow") + + ####################### + # IPropertyRenderer + + # relied upon by GitChangeset + + def match_property(self, name, mode): + if name in ('Parents','Children','git-committer','git-author') \ + and mode == 'revprop': + return 8 # default renderer has priority 1 + return 0 + + def render_property(self, name, mode, context, props): + def sha_link(sha): + return self._format_sha_link(None, 'sha', sha, sha, context=context) + + if name in ('Parents','Children'): + revs = props[name] + + return tag([tag(sha_link(rev), ', ') for rev in revs[:-1]], + sha_link(revs[-1])) + + if name in ('git-committer', 'git-author'): + user_, time_ = props[name] + _str = "%s (%s)" % (Chrome(self.env).format_author(context.req, user_), + format_datetime(time_, tzinfo=context.req.tz)) + return unicode(_str) + + raise TracError("internal error") + + ####################### + # IWikiSyntaxProvider + + def get_wiki_syntax(self): + yield (r'(?:\b|!)[0-9a-fA-F]{40,40}\b', + lambda fmt, sha, match: + self._format_sha_link(fmt, 'changeset', sha, sha)) + + def get_link_resolvers(self): + yield ('sha', self._format_sha_link) + + ####################### + # IRepositoryConnector + + _persistent_cache = BoolOption('git', 'persistent_cache', 'false', + "enable persistent caching of commit tree") + + _cached_repository = BoolOption('git', 'cached_repository', 'false', + "wrap `GitRepository` in `CachedRepository`") + + _shortrev_len = IntOption('git', 'shortrev_len', 7, + "length rev sha sums should be tried to be abbreviated to" + " (must be >= 4 and <= 40)") + + _git_bin = PathOption('git', 'git_bin', '/usr/bin/git', "path to git executable (relative to trac project folder!)") + + + def get_supported_types(self): + yield ("git", 8) + + def get_repository(self, type, dir, params): + """GitRepository factory method""" + assert type == "git" + + if not self._version: + raise TracError("GIT backend not available") + elif not self._version['v_compatible']: + raise TracError("GIT version %s installed not compatible (need >= %s)" % + (self._version['v_str'], self._version['v_min_str'])) + + repos = GitRepository(dir, params, self.log, + persistent_cache=self._persistent_cache, + git_bin=self._git_bin, + shortrev_len=self._shortrev_len) + + if self._cached_repository: + repos = CachedRepository2(self.env, repos, self.log) + self.log.info("enabled CachedRepository for '%s'" % dir) + else: + self.log.info("disabled CachedRepository for '%s'" % dir) + + return repos + +class GitRepository(Repository): + def __init__(self, path, params, log, persistent_cache=False, + git_bin='git', shortrev_len=7): + self.logger = log + self.gitrepo = path + self.params = params + self._shortrev_len = max(4, min(shortrev_len, 40)) + + self.git = PyGIT.StorageFactory(path, log, not persistent_cache, + git_bin=git_bin).getInstance() + Repository.__init__(self, "git:"+path, self.params, log) + + def close(self): + self.git = None + + def clear(self, youngest_rev=None): + self.youngest = None + if youngest_rev is not None: + self.youngest = self.normalize_rev(youngest_rev) + self.oldest = None + + def get_youngest_rev(self): + return self.git.youngest_rev() + + def get_oldest_rev(self): + return self.git.oldest_rev() + + def normalize_path(self, path): + return path and path.strip('/') or '/' + + def normalize_rev(self, rev): + if not rev: + return self.get_youngest_rev() + normrev=self.git.verifyrev(rev) + if normrev is None: + raise NoSuchChangeset(rev) + return normrev + + def short_rev(self, rev): + return self.git.shortrev(self.normalize_rev(rev), min_len=self._shortrev_len) + + def get_node(self, path, rev=None): + return GitNode(self, path, rev, self.log) + + def get_quickjump_entries(self, rev): + for bname,bsha in self.git.get_branches(): + yield 'branches', bname, '/', bsha + for t in self.git.get_tags(): + yield 'tags', t, '/', t + + def get_changesets(self, start, stop): + for rev in self.git.history_timerange(to_timestamp(start), to_timestamp(stop)): + yield self.get_changeset(rev) + + def get_changeset(self, rev): + """GitChangeset factory method""" + return GitChangeset(self, rev) + + def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=0): + # TODO: handle renames/copies, ignore_ancestry + if old_path != new_path: + raise TracError("not supported in git_fs") + + for chg in self.git.diff_tree(old_rev, new_rev, self.normalize_path(new_path)): + (mode1,mode2,obj1,obj2,action,path,path2) = chg + + kind = Node.FILE + if mode2.startswith('04') or mode1.startswith('04'): + kind = Node.DIRECTORY + + change = GitChangeset.action_map[action] + + old_node = None + new_node = None + + if change != Changeset.ADD: + old_node = self.get_node(path, old_rev) + if change != Changeset.DELETE: + new_node = self.get_node(path, new_rev) + + yield (old_node, new_node, kind, change) + + def next_rev(self, rev, path=''): + return self.git.hist_next_revision(rev) + + def previous_rev(self, rev, path=''): + return self.git.hist_prev_revision(rev) + + def rev_older_than(self, rev1, rev2): + rc = self.git.rev_is_anchestor_of(rev1, rev2) + return rc + + def clear(self, youngest_rev=None): + self.sync() + + def sync(self, rev_callback=None, clean=None): + if rev_callback: + revs = set(self.git.all_revs()) + + if not self.git.sync(): + return None # nothing expected to change + + if rev_callback: + revs = set(self.git.all_revs()) - revs + for rev in revs: + rev_callback(rev) + +class GitNode(Node): + def __init__(self, repos, path, rev, log, ls_tree_info=None): + self.log = log + self.repos = repos + self.fs_sha = None # points to either tree or blobs + self.fs_perm = None + self.fs_size = None + rev = rev and str(rev) or 'HEAD' + + kind = Node.DIRECTORY + p = path.strip('/') + if p: # ie. not the root-tree + if not ls_tree_info: + ls_tree_info = repos.git.ls_tree(rev, p) or None + if ls_tree_info: + [ls_tree_info] = ls_tree_info + + if not ls_tree_info: + raise NoSuchNode(path, rev) + + (self.fs_perm, k, self.fs_sha, self.fs_size, fn) = ls_tree_info + + # fix-up to the last commit-rev that touched this node + rev = repos.git.last_change(rev, p) + + if k=='tree': + pass + elif k=='blob': + kind = Node.FILE + else: + raise TracError("internal error (got unexpected object kind '%s')" % k) + + self.created_path = path + self.created_rev = rev + + Node.__init__(self, repos, path, rev, kind) + + def __git_path(self): + "return path as expected by PyGIT" + p = self.path.strip('/') + if self.isfile: + assert p + return p + if self.isdir: + return p and (p + '/') + + raise TracError("internal error") + + def get_content(self): + if not self.isfile: + return None + + return self.repos.git.get_file(self.fs_sha) + + def get_properties(self): + return self.fs_perm and {'mode': self.fs_perm } or {} + + def get_annotations(self): + if not self.isfile: + return + + return [ rev for (rev,lineno) in self.repos.git.blame(self.rev, self.__git_path()) ] + + def get_entries(self): + if not self.isdir: + return + + for ent in self.repos.git.ls_tree(self.rev, self.__git_path()): + yield GitNode(self.repos, ent[-1], self.rev, self.log, ent) + + def get_content_type(self): + if self.isdir: + return None + + return '' + + def get_content_length(self): + if not self.isfile: + return None + + if self.fs_size is None: + self.fs_size = self.repos.git.get_obj_size(self.fs_sha) + + return self.fs_size + + def get_history(self, limit=None): + # TODO: find a way to follow renames/copies + for is_last,rev in _last_iterable(self.repos.git.history(self.rev, self.__git_path(), limit)): + yield (self.path, rev, Changeset.EDIT if not is_last else Changeset.ADD) + + def get_last_modified(self): + if not self.isfile: + return None + + try: + msg, props = self.repos.git.read_commit(self.rev) + user,ts = _parse_user_time(props['committer'][0]) + except: + self.log.error("internal error (could not get timestamp from commit '%s')" % self.rev) + return None + + return ts + +class GitChangeset(Changeset): + + action_map = { # see also git-diff-tree(1) --diff-filter + 'A': Changeset.ADD, + 'M': Changeset.EDIT, # modified + 'T': Changeset.EDIT, # file type (mode) change + 'D': Changeset.DELETE, + 'R': Changeset.MOVE, # renamed + 'C': Changeset.COPY + } # TODO: U, X, B + + def __init__(self, repos, sha): + self.repos = repos + try: + (msg, props) = repos.git.read_commit(sha) + except PyGIT.GitErrorSha: + raise NoSuchChangeset(sha) + self.props = props + + assert 'children' not in props + _children = list(repos.git.children(sha)) + if _children: + props['children'] = _children + + # use 1st committer as changeset owner/timestamp + (user_, time_) = _parse_user_time(props['committer'][0]) + + Changeset.__init__(self, repos, sha, msg, user_, time_) + + def get_properties(self): + properties = {} + if 'parent' in self.props: + properties['Parents'] = self.props['parent'] + if 'children' in self.props: + properties['Children'] = self.props['children'] + if 'committer' in self.props: + properties['git-committer'] = \ + _parse_user_time(self.props['committer'][0]) + if 'author' in self.props: + git_author = _parse_user_time(self.props['author'][0]) + if not properties.get('git-committer') == git_author: + properties['git-author'] = git_author + + return properties + + def get_changes(self): + paths_seen = set() + for parent in self.props.get('parent', [None]): + for mode1,mode2,obj1,obj2,action,path1,path2 in \ + self.repos.git.diff_tree(parent, self.rev, find_renames=True): + path = path2 or path1 + p_path, p_rev = path1, parent + + kind = Node.FILE + if mode2.startswith('04') or mode1.startswith('04'): + kind = Node.DIRECTORY + + action = GitChangeset.action_map[action[0]] + + if action == Changeset.ADD: + p_path = '' + p_rev = None + + # CachedRepository expects unique (rev, path, change_type) key + # this is only an issue in case of merges where files required editing + if path in paths_seen: + continue + + paths_seen.add(path) + + yield (to_unicode(path), kind, action, to_unicode(p_path), p_rev) diff -Nru trac-git-0.0.20090320/debian/changelog trac-git-0.0.20100513/debian/changelog --- trac-git-0.0.20090320/debian/changelog 2010-07-20 10:36:57.000000000 +0200 +++ trac-git-0.0.20100513/debian/changelog 2010-07-20 10:33:36.000000000 +0200 @@ -1,3 +1,25 @@ +trac-git (0.0.20100513-2ubuntu1) maverick; urgency=low + + * Merge from debian unstable (LP: #607625) + + -- David Sugar Tue, 20 Jun 2010 10:23:42 +0200 + +trac-git (0.0.20100513-2) unstable; urgency=low + + * debian/control: Make Python-Version >= 2.5, not = 2.5, to make it + importable in python 2.6 code. (Closes: #586193) + + -- Jonny Lamb Thu, 17 Jun 2010 13:07:52 +0100 + +trac-git (0.0.20100513-1) unstable; urgency=low + + * New upstream snapshot. (Closes: #570808) + * debian/copyright: Updated debian/* copyright. + * debian/control: Upped Standards-Version. (no changes) + * Changed source format to 3.0 (quilt). + + -- Jonny Lamb Thu, 13 May 2010 09:09:20 +0100 + trac-git (0.0.20090320-1ubuntu2) lucid; urgency=low * allow the package to work with python >= 2.5. LP: #388911 @@ -102,3 +124,4 @@ * Initial release. (Closes: #482334) -- Jonny Lamb Mon, 19 May 2008 14:33:23 +0100 + diff -Nru trac-git-0.0.20090320/debian/control trac-git-0.0.20100513/debian/control --- trac-git-0.0.20090320/debian/control 2010-07-20 10:36:57.000000000 +0200 +++ trac-git-0.0.20100513/debian/control 2010-07-20 10:12:44.000000000 +0200 @@ -3,9 +3,9 @@ XSBC-Original-Maintainer: Jonny Lamb Section: python Priority: optional -Build-Depends: debhelper (>= 5.0), quilt (>= 0.40) +Build-Depends: debhelper (>= 5.0) Build-Depends-Indep: python, python-central (>= 0.5), python-setuptools -Standards-Version: 3.8.1 +Standards-Version: 3.8.4 XS-Python-Version: >= 2.5 Homepage: http://trac-hacks.org/wiki/GitPlugin Vcs-Git: git://git.jonnylamb.com/git/packaging/trac-git.git diff -Nru trac-git-0.0.20090320/debian/copyright trac-git-0.0.20100513/debian/copyright --- trac-git-0.0.20090320/debian/copyright 2010-07-20 10:36:57.000000000 +0200 +++ trac-git-0.0.20100513/debian/copyright 2010-06-17 20:23:42.000000000 +0200 @@ -22,5 +22,5 @@ On Debian systems, the complete text of the GNU General Public License, version 2, can be found in /usr/share/common-licenses/GPL-2 -The Debian packaging is (C) 2008, Jonny Lamb and -is licensed under the GPL, see `/usr/share/common-licenses/GPL'. +The Debian packaging is copyright 2008-2009, Jonny Lamb and +is licensed under the GPL-2, see `/usr/share/common-licenses/GPL-2'. diff -Nru trac-git-0.0.20090320/debian/rules trac-git-0.0.20100513/debian/rules --- trac-git-0.0.20090320/debian/rules 2010-07-20 10:36:57.000000000 +0200 +++ trac-git-0.0.20100513/debian/rules 2010-06-17 20:23:42.000000000 +0200 @@ -5,9 +5,8 @@ include /usr/share/python/python.mk include $(CURDIR)/debian/update-patches.mk -include /usr/share/quilt/quilt.make -clean: unpatch +clean: dh_testdir dh_testroot rm -f build-stamp @@ -21,7 +20,7 @@ build: build-stamp -build-stamp: patch +build-stamp: dh_testdir cd 0.11 && python setup.py build && cd .. touch $@ diff -Nru trac-git-0.0.20090320/debian/source/format trac-git-0.0.20100513/debian/source/format --- trac-git-0.0.20090320/debian/source/format 1970-01-01 01:00:00.000000000 +0100 +++ trac-git-0.0.20100513/debian/source/format 2010-07-20 10:36:57.938171623 +0200 @@ -0,0 +1 @@ +3.0 (quilt)