]>
Commit | Line | Data |
---|---|---|
1 | # -*- coding: utf-8 -*- ex:set ts=4 sw=4 et: | |
2 | ||
3 | # Copyright © 2008 - Steve Frécinaux | |
4 | # License: LGPL 2 | |
5 | ||
6 | class Object(object): | |
7 | "An object, following Git's definition." | |
8 | ||
9 | def __init__(self, repo, objectname): | |
10 | self._repo = repo | |
11 | self._name = objectname | |
12 | ||
13 | def __str__(self): | |
14 | return self._name or '' | |
15 | ||
16 | def __repr__(self): | |
17 | return '<git.%s "%s">' % (self.__class__.__name__, self._name) | |
18 | ||
19 | def __eq__(self, other): | |
20 | # Objects with no name are never equal to any other object. | |
21 | return self._name is not None and self._name == other._name | |
22 | ||
23 | def _dirty(self): | |
24 | """ | |
25 | Mark an object as dirty. As a result, all its parent objects are | |
26 | marked as dirty too. | |
27 | """ | |
28 | # We are already dirty, so our parents should be too. | |
29 | if self._name is None: | |
30 | return | |
31 | ||
32 | self._name = None | |
33 | if hasattr(self, 'parent') and self.parent is not None: | |
34 | self.parent._dirty() | |
35 | ||
36 | @property | |
37 | def name(self): | |
38 | return self._name | |
39 | ||
40 | @property | |
41 | def shortname(self): | |
42 | if self._name is None: | |
43 | return None | |
44 | return self.name[:8] | |
45 | ||
46 | class Commit(Object): | |
47 | def __init__(self, repo, objectname=None, refname=None): | |
48 | Object.__init__(self, repo, objectname) | |
49 | if objectname is None: | |
50 | self._loaded = True | |
51 | self._tree = Tree(repo, parent=self) | |
52 | self._message = '' | |
53 | else: | |
54 | self._loaded = False | |
55 | self._tree = None | |
56 | self._parents = [] | |
57 | self._refname = refname | |
58 | ||
59 | def _load(self): | |
60 | if self._loaded: return | |
61 | self._message = ''; | |
62 | if self._name is None: | |
63 | return | |
64 | is_header = True | |
65 | for line in self._repo.run('cat-file', 'commit', self._name): | |
66 | if is_header: | |
67 | line = line.strip() | |
68 | if line == '': | |
69 | is_header = False | |
70 | continue | |
71 | key, value = line.split(' ', 1) | |
72 | if key == 'tree': | |
73 | self._tree = Tree(self._repo, value, parent=self) | |
74 | continue | |
75 | if key == 'parent': | |
76 | self._parents.append(value) | |
77 | continue | |
78 | if key == 'author': | |
79 | author, timestamp, offset = value.rsplit(' ', 2) | |
80 | self._author = author | |
81 | self._author_timestamp = timestamp | |
82 | self._author_offset = offset | |
83 | continue | |
84 | if key == 'committer': | |
85 | author, timestamp, offset = value.rsplit(' ', 2) | |
86 | self._committer = author | |
87 | self._committer_timestamp = timestamp | |
88 | self._committer_offset = offset | |
89 | continue | |
90 | continue | |
91 | self._message += line | |
92 | ||
93 | def _dirty(self): | |
94 | old_name = self._name | |
95 | if old_name is not None: | |
96 | Object._dirty(self) | |
97 | self._parents = [old_name] | |
98 | ||
99 | @property | |
100 | def parents(self): | |
101 | self._load() | |
102 | return [Commit(self._repo, c) for c in self._parents] | |
103 | ||
104 | @property | |
105 | def tree(self): | |
106 | self._load() | |
107 | return self._tree | |
108 | ||
109 | @property | |
110 | def author(self): | |
111 | self._load() | |
112 | return self._author | |
113 | ||
114 | @property | |
115 | def comitter(self): | |
116 | self._load() | |
117 | return self._committer | |
118 | ||
119 | @property | |
120 | def message(self): | |
121 | self._load() | |
122 | return self._message | |
123 | ||
124 | @property | |
125 | def refname(self): | |
126 | return self._refname | |
127 | ||
128 | def write(self): | |
129 | if self._name is not None: | |
130 | return self._name | |
131 | ||
132 | tree_name = self.tree.write() | |
133 | ||
134 | cmd = ['commit-tree', tree_name] | |
135 | for p in self._parents: | |
136 | cmd.extend(['-p', p]) | |
137 | ||
138 | self._name = self._repo.run(input=self.message, wait=True, *cmd).strip() | |
139 | return self._name | |
140 | ||
141 | def commit(self): | |
142 | self.write() | |
143 | if self.refname is not None: | |
144 | self._repo.run('update-ref', self.refname, self._name, output=False) | |
145 | ||
146 | class Tree(Object): | |
147 | def __init__(self, repo, objectname=None, mode='040000', parent=None): | |
148 | Object.__init__(self, repo, objectname) | |
149 | self._parent = parent | |
150 | self.mode = mode | |
151 | if objectname is None: | |
152 | self._loaded = True | |
153 | self._contents = {} | |
154 | else: | |
155 | self._loaded = False | |
156 | ||
157 | def _load(self): | |
158 | if self._loaded: | |
159 | return | |
160 | self._contents = {} | |
161 | if self._name is None: | |
162 | return | |
163 | for line in self._repo.run('cat-file', '-p', self._name): | |
164 | mode, objtype, objname, filename = line.split(None, 3) | |
165 | if objtype == 'tree': | |
166 | self._contents[filename] = Tree(self._repo, objname, mode=mode, parent=self) | |
167 | elif objtype == 'blob': | |
168 | self._contents[filename] = Blob(self._repo, objname, mode=mode, parent=self) | |
169 | else: | |
170 | raise Exception("Unknown object type: '%s'" % objtype) | |
171 | ||
172 | def __getitem__(self, filename): | |
173 | self._load() | |
174 | return self._contents[filename] | |
175 | ||
176 | def __setitem__(self, filename, obj): | |
177 | if not isinstance(filename, str): | |
178 | raise ValueError("filename must be a string.") | |
179 | if '/' in filename: | |
180 | raise ValueError("filename cannot contain the '/' symbol.") | |
181 | if not isinstance(obj, Blob) and not isinstance(obj, Tree): | |
182 | raise ValueError("value must be a Blob or Tree object.") | |
183 | ||
184 | self._load() | |
185 | self._contents[filename] = obj | |
186 | obj._parent = self | |
187 | self._dirty() | |
188 | ||
189 | def __iter__(self): | |
190 | self._load() | |
191 | return iter(self._contents) | |
192 | ||
193 | def keys(self): | |
194 | self._load() | |
195 | return self._contents.keys() | |
196 | ||
197 | @property | |
198 | def parent(self): | |
199 | "parent of this object" | |
200 | return self._parent | |
201 | ||
202 | @property | |
203 | def root(self): | |
204 | "root tree of this object" | |
205 | if isinstance(self._parent, Commit): | |
206 | return self | |
207 | else: | |
208 | return self._parent.root | |
209 | ||
210 | def write(self): | |
211 | if self._name is not None: | |
212 | return self._name | |
213 | ||
214 | data = [] | |
215 | for path in self._contents: | |
216 | obj = self._contents[path] | |
217 | obj.write() | |
218 | objtype = isinstance(obj, Tree) and 'tree' or 'blob' | |
219 | data.append("%s %s %s\t%s" % (obj.mode, objtype, obj.name, path)) | |
220 | ||
221 | self._name = self._repo.run('mktree', '-z', input='\0'.join(data), wait=True).strip() | |
222 | return self._name | |
223 | ||
224 | class Blob(Object): | |
225 | def __init__(self, repo, objectname=None, mode='100644', parent=None): | |
226 | Object.__init__(self, repo, objectname) | |
227 | self._parent = parent | |
228 | if objectname is None: | |
229 | self._contents = '' | |
230 | self._loaded = True | |
231 | else: | |
232 | self._loaded = False | |
233 | self.mode = mode | |
234 | ||
235 | def _load(self): | |
236 | if self._loaded: return | |
237 | self._contents = self._repo.run('cat-file', 'blob', self._name, wait=True) | |
238 | ||
239 | # Contents property | |
240 | def _get_contents(self): | |
241 | self._load() | |
242 | return self._contents | |
243 | ||
244 | def _set_contents(self, contents): | |
245 | self._loaded = True # No need to actually load the data here. | |
246 | self._contents = contents | |
247 | self._dirty() | |
248 | ||
249 | contents = property(_get_contents, _set_contents) | |
250 | del _get_contents | |
251 | del _set_contents | |
252 | ||
253 | @property | |
254 | def parent(self): | |
255 | "parent of this object" | |
256 | return self._parent | |
257 | ||
258 | @property | |
259 | def root(self): | |
260 | "root tree of this object" | |
261 | return self._parent.root | |
262 | ||
263 | def write(self): | |
264 | if self._name is None: | |
265 | self._name = self._repo.run('hash-object', '-w', '--stdin', input=self.contents, wait=True).strip() | |
266 | return self._name |