]> git.ipfire.org Git - ipfire.org.git/blame - source/git/objects.py
Merge branch 'master' of ssh://git.ipfire.org/pub/git/ipfire.org
[ipfire.org.git] / source / git / objects.py
CommitLineData
727e2055
MT
1# -*- coding: utf-8 -*- ex:set ts=4 sw=4 et:
2
3# Copyright © 2008 - Steve Frécinaux
4# License: LGPL 2
5
6class 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
46class 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
146class 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
224class 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