-
Notifications
You must be signed in to change notification settings - Fork 10
/
git.py
162 lines (143 loc) · 6.16 KB
/
git.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
from datetime import datetime, timedelta
from operator import itemgetter, attrgetter
import os
from dulwich.repo import Repo
from dulwich import objects
from dulwich.errors import NotCommitError
from pyvcs.commit import Commit
from pyvcs.exceptions import CommitDoesNotExist, FileDoesNotExist, FolderDoesNotExist
from pyvcs.repository import BaseRepository
from pyvcs.utils import generate_unified_diff
def traverse_tree(repo, tree):
for mode, name, sha in tree.entries():
if isinstance(repo.get_object(sha), objects.Tree):
for item in traverse_tree(repo, repo.get_object(sha)):
yield os.path.join(name, item)
else:
yield name
def get_differing_files(repo, past, current):
past_files = {}
current_files = {}
if past is not None:
past_files = dict([(name, sha) for mode, name, sha in past.entries()])
if current is not None:
current_files = dict([(name, sha) for mode, name, sha in current.entries()])
added = set(current_files) - set(past_files)
removed = set(past_files) - set(current_files)
changed = [o for o in past_files if o in current_files and past_files[o] != current_files[o]]
for name in added:
sha = current_files[name]
yield name
if isinstance(repo.get_object(sha), objects.Tree):
for item in get_differing_files(repo, None, repo.get_object(sha)):
yield os.path.join(name, item)
for name in removed:
sha = past_files[name]
yield name
if isinstance(repo.get_object(sha), objects.Tree):
for item in get_differing_files(repo, repo.get_object(sha), None):
yield os.path.join(name, item)
for name in changed:
past_sha = past_files[name]
current_sha = current_files[name]
if isinstance(repo.get_object(past_sha), objects.Tree):
for item in get_differing_files(repo, repo.get_object(past_sha), repo.get_object(current_sha)):
yield os.path.join(name, item)
else:
yield name
class Repository(BaseRepository):
def __init__(self, *args, **kwargs):
super(Repository, self).__init__(*args, **kwargs)
self._repo = Repo(self.path)
def _get_commit(self, commit_id):
try:
return self._repo[commit_id]
except Exception, e:
raise CommitDoesNotExist("%s is not a commit" % commit_id)
def _get_obj(self, sha):
return self._repo.get_object(sha)
def _diff_files(self, commit_id1, commit_id2):
if commit_id1 == 'NULL':
commit_id1 = None
if commit_id2 == 'NULL':
commit_id2 = None
tree1 = self._get_obj(self._get_obj(commit_id1).tree) if commit_id1 else None
tree2 = self._get_obj(self._get_obj(commit_id2).tree) if commit_id2 else None
return sorted(get_differing_files(
self._repo,
tree1,
tree2,
))
def get_commit_by_id(self, commit_id):
commit = self._get_commit(commit_id)
parent = commit.parents[0] if len(commit.parents) else 'NULL'
files = self._diff_files(commit.id, parent)
return Commit(commit.id, commit.committer,
datetime.fromtimestamp(commit.commit_time), commit.message, files,
lambda: generate_unified_diff(self, files, parent, commit.id))
def get_recent_commits(self, since=None):
if since is None:
#since = datetime.fromtimestamp(self._repo.commit(self._repo.head()).commit_time) - timedelta(days=5)
since = datetime.fromtimestamp(self._repo[self._repo.head()].commit_time) - timedelta(days=5)
pending_commits = self._repo.get_refs().values()#[self._repo.head()]
history = {}
while pending_commits:
head = pending_commits.pop(0)
try:
commit = self._repo[head]
except KeyError:
raise CommitDoesNotExist
if not isinstance(commit, objects.Commit) or commit.id in history or\
datetime.fromtimestamp(commit.commit_time) <= since:
continue
history[commit.id] = commit
pending_commits.extend(commit.parents)
commits = filter(lambda o: datetime.fromtimestamp(o.commit_time) >= since, history.values())
commits = map(lambda o: self.get_commit_by_id(o.id), commits)
return sorted(commits, key=attrgetter('time'), reverse=True)
def list_directory(self, path, revision=None):
if revision is None:
commit = self._get_commit(self._repo.head())
elif revision is 'NULL':
return ([],[])
else:
commit = self._get_commit(revision)
tree = self._repo[commit.tree]
path = filter(bool, path.split(os.path.sep))
while path:
part = path.pop(0)
found = False
for mode, name, hexsha in self._repo[tree.id].entries():
if part == name:
found = True
tree = self._repo[hexsha]
break
if not found:
raise FolderDoesNotExist
files, folders = [], []
for mode, name, hexsha in tree.entries():
if isinstance(self._repo.get_object(hexsha), objects.Tree):
folders.append(name)
elif isinstance(self._repo.get_object(hexsha), objects.Blob):
files.append(name)
return files, folders
def file_contents(self, path, revision=None):
if revision is None:
commit = self._get_commit(self._repo.head())
elif revision is 'NULL':
return ''
else:
commit = self._get_commit(revision)
tree = self._repo[commit.tree]
path = path.split(os.path.sep)
path, filename = path[:-1], path[-1]
while path:
part = path.pop(0)
for mode, name, hexsha in self._repo[tree.id].entries():
if part == name:
tree = self._repo[hexsha]
break
for mode, name, hexsha in tree.entries():
if name == filename:
return self._repo[hexsha].as_pretty_string()
raise FileDoesNotExist