def blame(self, commit, path): """Return a 'git blame' list for the file at `path`: For each line in the file, the list contains the commit that last changed that line. """ # XXX see comment in `.history()` cmd = ['git', 'blame', '-ls', '--root', decode_from_git(commit.id), '--', path] output = subprocess.check_output(cmd, cwd=os.path.abspath(self.path)) sha1_sums = [line[:40] for line in output.strip().split(b'\n')] return [None if self[sha1] is None else decode_from_git(self[sha1].id) for sha1 in sha1_sums]
def listdir(self, commit, path): """Return a list of submodules, directories and files in given directory: Lists of (link name, target path) tuples. """ submodules, dirs, files = [], [], [] for entry_rel in self.get_blob_or_tree(commit, path).items(): # entry_rel: Entry('foo.txt', ...) # entry_abs: Entry('spam/eggs/foo.txt', ...) entry_abs = entry_rel.in_path(encode_for_git(path)) path_str = decode_from_git(entry_abs.path) item = (os.path.basename(path_str), path_str) if S_ISGITLINK(entry_abs.mode): submodules.append(item) elif stat.S_ISDIR(entry_abs.mode): dirs.append(item) else: files.append(item) keyfunc = lambda tpl: tpl[0].lower() submodules.sort(key=keyfunc) files.sort(key=keyfunc) dirs.sort(key=keyfunc) if path: dirs.insert(0, ('..', parent_directory(path))) return {'submodules': submodules, 'dirs' : dirs, 'files' : files}
def listdir(self, commit, path): """Return a list of submodules, directories and files in given directory: Lists of (link name, target path) tuples. """ submodules, dirs, files = [], [], [] for entry_rel in self.get_blob_or_tree(commit, path).items(): # entry_rel: Entry('foo.txt', ...) # entry_abs: Entry('spam/eggs/foo.txt', ...) entry_abs = entry_rel.in_path(encode_for_git(path)) path_str = decode_from_git(entry_abs.path) item = (os.path.basename(path_str), path_str) if S_ISGITLINK(entry_abs.mode): submodules.append(item) elif stat.S_ISDIR(entry_abs.mode): dirs.append(item) else: files.append(item) keyfunc = lambda tpl: tpl[0].lower() submodules.sort(key=keyfunc) files.sort(key=keyfunc) dirs.sort(key=keyfunc) if path: dirs.insert(0, ('..', parent_directory(path))) return {'submodules': submodules, 'dirs': dirs, 'files': files}
def history(self, commit, path=None, max_commits=None, skip=0): """Return a list of all commits that affected `path`, starting at branch or commit `commit`. `skip` can be used for pagination, `max_commits` to limit the number of commits returned. Similar to `git log [branch/commit] [--skip skip] [-n max_commits]`. """ # XXX The pure-Python/dulwich code is very slow compared to `git log` # at the time of this writing (mid-2012). # For instance, `git log .tx` in the Django root directory takes # about 0.15s on my machine whereas the history() method needs 5s. # Therefore we use `git log` here until dulwich gets faster. # For the pure-Python implementation, see the 'purepy-hist' branch. cmd = ['git', 'log', '--format=%H'] if skip: cmd.append('--skip=%d' % skip) if max_commits: cmd.append('--max-count=%d' % max_commits) cmd.append(decode_from_git(commit.id)) if path: cmd.extend(['--', path]) output = subprocess.check_output(cmd, cwd=os.path.abspath(self.path)) sha1_sums = output.strip().split(b'\n') return [self[sha1] for sha1 in sha1_sums]
def commit_diff(self, commit): """Return the list of changes introduced by `commit`.""" from klaus.utils import guess_is_binary if commit.parents: parent_tree = self[commit.parents[0]].tree else: parent_tree = None summary = {'nfiles': 0, 'nadditions': 0, 'ndeletions': 0} file_changes = [] # the changes in detail dulwich_changes = self.object_store.tree_changes( parent_tree, commit.tree) for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in dulwich_changes: summary['nfiles'] += 1 try: # Check for binary files -- can't show diffs for these if newsha and guess_is_binary(self[newsha]) or \ oldsha and guess_is_binary(self[oldsha]): file_changes.append({ 'is_binary': True, 'old_filename': oldpath or '/dev/null', 'new_filename': newpath or '/dev/null', 'chunks': None }) continue except KeyError: # newsha/oldsha are probably related to submodules. # Dulwich will handle that. pass bytesio = io.BytesIO() dulwich.patch.write_object_diff(bytesio, self.object_store, (oldpath, oldmode, oldsha), (newpath, newmode, newsha)) files = prepare_udiff(decode_from_git(bytesio.getvalue()), want_header=False) if not files: # the diff module doesn't handle deletions/additions # of empty files correctly. file_changes.append({ 'old_filename': oldpath or '/dev/null', 'new_filename': newpath or '/dev/null', 'chunks': [], 'additions': 0, 'deletions': 0, }) else: change = files[0] summary['nadditions'] += change['additions'] summary['ndeletions'] += change['deletions'] file_changes.append(change) return summary, file_changes
def blame(self, commit, path): """Return a 'git blame' list for the file at `path`: For each line in the file, the list contains the commit that last changed that line. """ # XXX see comment in `.history()` cmd = [ 'git', 'blame', '-ls', '--root', decode_from_git(commit.id), '--', path ] output = subprocess.check_output(cmd, cwd=os.path.abspath(self.path)) sha1_sums = [line[:40] for line in output.strip().split(b'\n')] lines = [] for sha1 in sha1_sums: obj = self.getref(sha1, None) if obj is not None: obj = decode_from_git(obj.id) lines.append(obj) return lines
def walk_tree(repo, tree, root=''): """Recursively walk a dulwich Tree, yielding tuples of (absolute path, TreeEntry) along the way. """ for entry in tree.iteritems(): entry_abspath = os.path.join(root, decode_from_git(entry.path)) if stat.S_ISDIR(entry.mode): for _ in walk_tree(repo, repo[entry.sha], entry_abspath): yield _ else: yield (entry_abspath, entry)
def commit_diff(self, commit): """Return the list of changes introduced by `commit`.""" from klaus.utils import guess_is_binary if commit.parents: parent_tree = self[commit.parents[0]].tree else: parent_tree = None summary = {'nfiles': 0, 'nadditions': 0, 'ndeletions': 0} file_changes = [] # the changes in detail dulwich_changes = self.object_store.tree_changes(parent_tree, commit.tree) for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in dulwich_changes: summary['nfiles'] += 1 try: # Check for binary files -- can't show diffs for these if newsha and guess_is_binary(self[newsha]) or \ oldsha and guess_is_binary(self[oldsha]): file_changes.append({ 'is_binary': True, 'old_filename': oldpath or '/dev/null', 'new_filename': newpath or '/dev/null', 'chunks': None }) continue except KeyError: # newsha/oldsha are probably related to submodules. # Dulwich will handle that. pass bytesio = io.BytesIO() dulwich.patch.write_object_diff(bytesio, self.object_store, (oldpath, oldmode, oldsha), (newpath, newmode, newsha)) files = prepare_udiff(decode_from_git(bytesio.getvalue()), want_header=False) if not files: # the diff module doesn't handle deletions/additions # of empty files correctly. file_changes.append({ 'old_filename': oldpath or '/dev/null', 'new_filename': newpath or '/dev/null', 'chunks': [], 'additions': 0, 'deletions': 0, }) else: change = files[0] summary['nadditions'] += change['additions'] summary['ndeletions'] += change['deletions'] file_changes.append(change) return summary, file_changes
def get_sorted_ref_names(self, prefix, exclude=None): refs = self.refs.as_dict(encode_for_git(prefix)) if exclude: refs.pop(prefix + exclude, None) def get_commit_time(refname): obj = self[refs[refname]] if isinstance(obj, dulwich.objects.Tag): return obj.tag_time return obj.commit_time return [decode_from_git(ref) for ref in sorted(refs.keys(), key=get_commit_time, reverse=True)]
def get_ref_names_ordered_by_last_commit(self, prefix, exclude=None): """Return a list of ref names that begin with `prefix`, ordered by the time they have been committed to last. """ def get_commit_time(refname): obj = self[refs[refname]] if isinstance(obj, dulwich.objects.Tag): return obj.tag_time return obj.commit_time refs = self.refs.as_dict(encode_for_git(prefix)) if exclude: refs.pop(prefix + exclude, None) sorted_names = sorted(refs.keys(), key=get_commit_time, reverse=True) return [decode_from_git(ref) for ref in sorted_names]
def listdir(self, commit, path): """Return a list of directories and files in given directory.""" submodules, dirs, files = [], [], [] for entry in self.get_blob_or_tree(commit, path).items(): entry_path = decode_from_git(entry.path) name, entry = entry_path, entry.in_path(encode_for_git(path)) if S_ISGITLINK(entry.mode): submodules.append((name.lower(), name, entry_path, entry.sha)) elif stat.S_ISDIR(entry.mode): dirs.append((name.lower(), name, entry_path)) else: files.append((name.lower(), name, entry_path)) files.sort() dirs.sort() if path: dirs.insert(0, (None, '..', parent_directory(path))) return {'submodules': submodules, 'dirs': dirs, 'files': files}
def get_ref_names_ordered_by_last_commit(self, prefix, exclude=None): """Return a list of ref names that begin with `prefix`, ordered by the time they have been committed to last. """ def get_commit_time(obj): if obj is None: # Put refs that point to non-existent objects last. return 0 elif isinstance(obj, dulwich.objects.Tag): return obj.tag_time else: return obj.commit_time refs = self.get_resolved_refs_as_dict(encode_for_git(prefix), resolve_default=None) if exclude: refs.pop(prefix + exclude, None) sorted_refs = sorted(refs.items(), key=lambda item: get_commit_time(item[1]), reverse=True) return [decode_from_git(name) for name, _ in sorted_refs]