Skip to content
This repository has been archived by the owner on May 22, 2019. It is now read-only.

Commit

Permalink
last_change: Use git log --name-status to avoid repeated fork+exec
Browse files Browse the repository at this point in the history
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
  • Loading branch information
andersk committed Nov 19, 2011
1 parent 0283ffb commit 9541c2a
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 21 deletions.
53 changes: 51 additions & 2 deletions tracext/git/PyGIT.py
Expand Up @@ -14,6 +14,7 @@
from threading import Lock
from subprocess import Popen, PIPE
from operator import itemgetter
from contextlib import contextmanager
import cStringIO
import codecs

Expand Down Expand Up @@ -70,8 +71,11 @@ def __execute(self, git_cmd, *cmd_args):
def cat_file_batch(self):
return self.__pipe('cat-file', '--batch', stdin=PIPE, stdout=PIPE)

def log_pipe(self, *cmd_args):
return self.__pipe('log', *cmd_args, stdout=PIPE)

def __getattr__(self, name):
if name[0] == '_' or name in ['cat_file_batch']:
if name[0] == '_' or name in ['cat_file_batch', 'log_pipe']:
raise AttributeError, name
return partial(self.__execute, name.replace('_','-'))

Expand Down Expand Up @@ -713,7 +717,52 @@ def sync(self):
rev = self.repo.rev_list("--max-count=1", "--topo-order", "--all").strip()
return self.__rev_cache_sync(rev)

def last_change(self, sha, path):
@contextmanager
def get_historian(self, sha, base_path):
p = []
change = {}
next_path = []

def name_status_gen():
p[:] = [self.repo.log_pipe('--pretty=format:%n%H', '--name-status',
sha, '--', base_path)]
f = p[0].stdout
for l in f:
if l == '\n': continue
old_sha = l.rstrip('\n')
for l in f:
if l == '\n': break
_, path = l.rstrip('\n').split('\t', 1)
while path not in change:
change[path] = old_sha
if next_path == [path]: yield old_sha
try:
path, _ = path.rsplit('/', 1)
except ValueError:
break
f.close()
p[0].terminate()
p[0].wait()
p[:] = []
while True: yield None
gen = name_status_gen()

def historian(path):
try:
return change[path]
except KeyError:
next_path[:] = [path]
return gen.next()
yield historian

if p:
p[0].stdout.close()
p[0].terminate()
p[0].wait()

def last_change(self, sha, path, historian=None):
if historian is not None:
return historian(path)
return self.repo.rev_list("--max-count=1",
sha, "--",
self._fs_from_unicode(path)).strip() or None
Expand Down
41 changes: 22 additions & 19 deletions tracext/git/git_fs.py
Expand Up @@ -387,8 +387,8 @@ def display_rev(self, rev):
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_node(self, path, rev=None, historian=None):
return GitNode(self, path, rev, self.log, None, historian)

def get_quickjump_entries(self, rev):
for bname, bsha in self.git.get_branches():
Expand All @@ -412,24 +412,26 @@ def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=0):
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
with self.git.get_historian(old_rev, old_path.strip('/')) as old_historian:
with self.git.get_historian(new_rev, new_path.strip('/')) as new_historian:
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, old_historian)
if change != Changeset.DELETE:
new_node = self.get_node(path, new_rev, new_historian)

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)
Expand Down Expand Up @@ -469,7 +471,7 @@ def sync(self, rev_callback=None, clean=None):
rev_callback(rev)

class GitNode(Node):
def __init__(self, repos, path, rev, log, ls_tree_info=None):
def __init__(self, repos, path, rev, log, ls_tree_info=None, historian=None):
self.log = log
self.repos = repos
self.fs_sha = None # points to either tree or blobs
Expand All @@ -491,7 +493,7 @@ def __init__(self, repos, path, rev, log, ls_tree_info=None):
self.fs_perm, k, self.fs_sha, self.fs_size, _ = ls_tree_info

# fix-up to the last commit-rev that touched this node
rev = repos.git.last_change(rev, p)
rev = repos.git.last_change(rev, p, historian)

if k == 'tree':
pass
Expand Down Expand Up @@ -537,8 +539,9 @@ 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)
with self.repos.git.get_historian(self.rev, self.path.strip('/')) as historian:
for ent in self.repos.git.ls_tree(self.rev, self.__git_path()):
yield GitNode(self.repos, ent[-1], self.rev, self.log, ent, historian)

def get_content_type(self):
if self.isdir:
Expand Down

0 comments on commit 9541c2a

Please sign in to comment.