Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9541c2a

Browse files
committed
last_change: Use git log --name-status to avoid repeated fork+exec
Signed-off-by: Anders Kaseorg <[email protected]>
1 parent 0283ffb commit 9541c2a

File tree

2 files changed

+73
-21
lines changed

2 files changed

+73
-21
lines changed

tracext/git/PyGIT.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from threading import Lock
1515
from subprocess import Popen, PIPE
1616
from operator import itemgetter
17+
from contextlib import contextmanager
1718
import cStringIO
1819
import codecs
1920

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

74+
def log_pipe(self, *cmd_args):
75+
return self.__pipe('log', *cmd_args, stdout=PIPE)
76+
7377
def __getattr__(self, name):
74-
if name[0] == '_' or name in ['cat_file_batch']:
78+
if name[0] == '_' or name in ['cat_file_batch', 'log_pipe']:
7579
raise AttributeError, name
7680
return partial(self.__execute, name.replace('_','-'))
7781

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

716-
def last_change(self, sha, path):
720+
@contextmanager
721+
def get_historian(self, sha, base_path):
722+
p = []
723+
change = {}
724+
next_path = []
725+
726+
def name_status_gen():
727+
p[:] = [self.repo.log_pipe('--pretty=format:%n%H', '--name-status',
728+
sha, '--', base_path)]
729+
f = p[0].stdout
730+
for l in f:
731+
if l == '\n': continue
732+
old_sha = l.rstrip('\n')
733+
for l in f:
734+
if l == '\n': break
735+
_, path = l.rstrip('\n').split('\t', 1)
736+
while path not in change:
737+
change[path] = old_sha
738+
if next_path == [path]: yield old_sha
739+
try:
740+
path, _ = path.rsplit('/', 1)
741+
except ValueError:
742+
break
743+
f.close()
744+
p[0].terminate()
745+
p[0].wait()
746+
p[:] = []
747+
while True: yield None
748+
gen = name_status_gen()
749+
750+
def historian(path):
751+
try:
752+
return change[path]
753+
except KeyError:
754+
next_path[:] = [path]
755+
return gen.next()
756+
yield historian
757+
758+
if p:
759+
p[0].stdout.close()
760+
p[0].terminate()
761+
p[0].wait()
762+
763+
def last_change(self, sha, path, historian=None):
764+
if historian is not None:
765+
return historian(path)
717766
return self.repo.rev_list("--max-count=1",
718767
sha, "--",
719768
self._fs_from_unicode(path)).strip() or None

tracext/git/git_fs.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,8 @@ def display_rev(self, rev):
387387
def short_rev(self, rev):
388388
return self.git.shortrev(self.normalize_rev(rev), min_len=self._shortrev_len)
389389

390-
def get_node(self, path, rev=None):
391-
return GitNode(self, path, rev, self.log)
390+
def get_node(self, path, rev=None, historian=None):
391+
return GitNode(self, path, rev, self.log, None, historian)
392392

393393
def get_quickjump_entries(self, rev):
394394
for bname, bsha in self.git.get_branches():
@@ -412,24 +412,26 @@ def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=0):
412412
if old_path != new_path:
413413
raise TracError("not supported in git_fs")
414414

415-
for chg in self.git.diff_tree(old_rev, new_rev, self.normalize_path(new_path)):
416-
mode1, mode2, obj1, obj2, action, path, path2 = chg
415+
with self.git.get_historian(old_rev, old_path.strip('/')) as old_historian:
416+
with self.git.get_historian(new_rev, new_path.strip('/')) as new_historian:
417+
for chg in self.git.diff_tree(old_rev, new_rev, self.normalize_path(new_path)):
418+
mode1, mode2, obj1, obj2, action, path, path2 = chg
417419

418-
kind = Node.FILE
419-
if mode2.startswith('04') or mode1.startswith('04'):
420-
kind = Node.DIRECTORY
420+
kind = Node.FILE
421+
if mode2.startswith('04') or mode1.startswith('04'):
422+
kind = Node.DIRECTORY
421423

422-
change = GitChangeset.action_map[action]
424+
change = GitChangeset.action_map[action]
423425

424-
old_node = None
425-
new_node = None
426+
old_node = None
427+
new_node = None
426428

427-
if change != Changeset.ADD:
428-
old_node = self.get_node(path, old_rev)
429-
if change != Changeset.DELETE:
430-
new_node = self.get_node(path, new_rev)
429+
if change != Changeset.ADD:
430+
old_node = self.get_node(path, old_rev, old_historian)
431+
if change != Changeset.DELETE:
432+
new_node = self.get_node(path, new_rev, new_historian)
431433

432-
yield old_node, new_node, kind, change
434+
yield old_node, new_node, kind, change
433435

434436
def next_rev(self, rev, path=''):
435437
return self.git.hist_next_revision(rev)
@@ -469,7 +471,7 @@ def sync(self, rev_callback=None, clean=None):
469471
rev_callback(rev)
470472

471473
class GitNode(Node):
472-
def __init__(self, repos, path, rev, log, ls_tree_info=None):
474+
def __init__(self, repos, path, rev, log, ls_tree_info=None, historian=None):
473475
self.log = log
474476
self.repos = repos
475477
self.fs_sha = None # points to either tree or blobs
@@ -491,7 +493,7 @@ def __init__(self, repos, path, rev, log, ls_tree_info=None):
491493
self.fs_perm, k, self.fs_sha, self.fs_size, _ = ls_tree_info
492494

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

496498
if k == 'tree':
497499
pass
@@ -537,8 +539,9 @@ def get_entries(self):
537539
if not self.isdir:
538540
return
539541

540-
for ent in self.repos.git.ls_tree(self.rev, self.__git_path()):
541-
yield GitNode(self.repos, ent[-1], self.rev, self.log, ent)
542+
with self.repos.git.get_historian(self.rev, self.path.strip('/')) as historian:
543+
for ent in self.repos.git.ls_tree(self.rev, self.__git_path()):
544+
yield GitNode(self.repos, ent[-1], self.rev, self.log, ent, historian)
542545

543546
def get_content_type(self):
544547
if self.isdir:

0 commit comments

Comments
 (0)