| @@ -151,11 +151,11 @@ class GitIndexer(threading.Thread): | |||||
| :type repo_url: :class:`GitRepository` | :type repo_url: :class:`GitRepository` | ||||
| """ | """ | ||||
| commits_meta = self._get_commits_metadata(repo) | |||||
| if commits_meta is None: | |||||
| file_meta = self._get_file_metadata(repo) | |||||
| if file_meta is None: | |||||
| return | return | ||||
| for filename, data in commits_meta.iteritems(): | |||||
| for filename, data in file_meta.iteritems(): | |||||
| authors = [(author, None) for author in data["authors"]] | authors = [(author, None) for author in data["authors"]] | ||||
| encoded_source = data["blob"].data_stream.read() | encoded_source = data["blob"].data_stream.read() | ||||
| source = UnicodeDammit(encoded_source).unicode_markup | source = UnicodeDammit(encoded_source).unicode_markup | ||||
| @@ -199,13 +199,24 @@ class GitIndexer(threading.Thread): | |||||
| def _walk_history(self, files, head): | def _walk_history(self, files, head): | ||||
| """Walk a repository's history for metadata.""" | """Walk a repository's history for metadata.""" | ||||
| def update_entry(commit, entry, new_file): | def update_entry(commit, entry, new_file): | ||||
| entry["authors"].add(commit.author.name) | |||||
| if commit.author.name not in entry["authors"]: | |||||
| entry["authors"].append(commit.author.name) | |||||
| commit_ts = datetime.utcfromtimestamp(commit.committed_date) | commit_ts = datetime.utcfromtimestamp(commit.committed_date) | ||||
| if commit_ts > entry["time_last_modified"]: | if commit_ts > entry["time_last_modified"]: | ||||
| entry["time_last_modified"] = commit_ts | entry["time_last_modified"] = commit_ts | ||||
| if new_file: | if new_file: | ||||
| entry["time_created"] = commit_ts | entry["time_created"] = commit_ts | ||||
| def get_diffs(commit, parent): | |||||
| cache_key = parent.binsha + commit.binsha | |||||
| if cache_key in diff_cache: | |||||
| return diff_cache[cache_key] | |||||
| diffs = parent.diff(commit, create_patch=True) | |||||
| for diff in diffs: | |||||
| del diff.diff | |||||
| diff_cache[cache_key] = diffs | |||||
| return diffs | |||||
| def handle_commit(commit, paths): | def handle_commit(commit, paths): | ||||
| if not commit.parents: | if not commit.parents: | ||||
| for item in commit.tree.traverse(): | for item in commit.tree.traverse(): | ||||
| @@ -214,7 +225,9 @@ class GitIndexer(threading.Thread): | |||||
| return | return | ||||
| for parent in commit.parents: | for parent in commit.parents: | ||||
| for diff in parent.diff(commit, create_patch=True): | |||||
| for diff in get_diffs(commit, parent): | |||||
| if not diff.b_blob: # Happens when file modes are changed | |||||
| continue | |||||
| pth = diff.rename_to if diff.renamed else diff.b_blob.path | pth = diff.rename_to if diff.renamed else diff.b_blob.path | ||||
| if pth not in paths: | if pth not in paths: | ||||
| continue | continue | ||||
| @@ -224,14 +237,23 @@ class GitIndexer(threading.Thread): | |||||
| del paths[pth] | del paths[pth] | ||||
| pending = [(head, {path: path for path in files})] | pending = [(head, {path: path for path in files})] | ||||
| diff_cache = {} | |||||
| processed = {} | |||||
| while pending: | while pending: | ||||
| commit, paths = pending.pop() | commit, paths = pending.pop() | ||||
| handle_commit(commit, paths) | handle_commit(commit, paths) | ||||
| hash_key = hash(frozenset(paths.items())) | |||||
| for parent in commit.parents: | for parent in commit.parents: | ||||
| new_paths = paths.copy() if len(commit.parents) > 1 else paths | new_paths = paths.copy() if len(commit.parents) > 1 else paths | ||||
| pending.append((parent, new_paths)) | |||||
| def _get_commits_metadata(self, repo): | |||||
| if parent.binsha in processed: | |||||
| if hash_key not in processed[parent.binsha]: | |||||
| pending.append((parent, new_paths)) | |||||
| processed[parent.binsha].append(hash_key) | |||||
| else: | |||||
| pending.append((parent, new_paths)) | |||||
| processed[parent.binsha] = [hash_key] | |||||
| def _get_file_metadata(self, repo): | |||||
| """ | """ | ||||
| Return a dictionary containing every valuable tracked file's metadata. | Return a dictionary containing every valuable tracked file's metadata. | ||||
| @@ -241,7 +263,7 @@ class GitIndexer(threading.Thread): | |||||
| sample_returned_dict = { | sample_returned_dict = { | ||||
| "my_file" : { | "my_file" : { | ||||
| "blob": (GitPython Blob) <object>, | "blob": (GitPython Blob) <object>, | ||||
| "authors" : (str set) {"author1", "author2"}, | |||||
| "authors" : (str list) ["author1", "author2"], | |||||
| "time_created" : (`datetime.datetime`) <object>, | "time_created" : (`datetime.datetime`) <object>, | ||||
| "time_last_modified" : (`datetime.datetime`) <object> | "time_last_modified" : (`datetime.datetime`) <object> | ||||
| } | } | ||||
| @@ -258,11 +280,12 @@ class GitIndexer(threading.Thread): | |||||
| if item.type == "blob" and self._is_ascii(item.data_stream): | if item.type == "blob" and self._is_ascii(item.data_stream): | ||||
| files[item.path] = { | files[item.path] = { | ||||
| "blob": item, | "blob": item, | ||||
| "authors" : set(), | |||||
| "authors" : [], | |||||
| "time_last_modified": datetime.utcfromtimestamp(0), | "time_last_modified": datetime.utcfromtimestamp(0), | ||||
| "time_created": datetime.utcfromtimestamp(0) | "time_created": datetime.utcfromtimestamp(0) | ||||
| } | } | ||||
| self._logger.debug("Building file metadata") | |||||
| self._walk_history(files, repo.repo.head.commit) | self._walk_history(files, repo.repo.head.commit) | ||||
| return files | return files | ||||