objects.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. # objects.py -- Access to base git objects
  2. # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
  3. # Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; version 2
  8. # of the License or (at your option) a later version of the License.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  18. # MA 02110-1301, USA.
  19. """Access to base git objects."""
  20. import binascii
  21. from cStringIO import (
  22. StringIO,
  23. )
  24. import mmap
  25. import os
  26. import stat
  27. import zlib
  28. from dulwich.errors import (
  29. NotBlobError,
  30. NotCommitError,
  31. NotTagError,
  32. NotTreeError,
  33. ObjectFormatException,
  34. )
  35. from dulwich.file import GitFile
  36. from dulwich.misc import (
  37. make_sha,
  38. )
  39. # Header fields for commits
  40. _TREE_HEADER = "tree"
  41. _PARENT_HEADER = "parent"
  42. _AUTHOR_HEADER = "author"
  43. _COMMITTER_HEADER = "committer"
  44. _ENCODING_HEADER = "encoding"
  45. # Header fields for objects
  46. _OBJECT_HEADER = "object"
  47. _TYPE_HEADER = "type"
  48. _TAG_HEADER = "tag"
  49. _TAGGER_HEADER = "tagger"
  50. S_IFGITLINK = 0160000
  51. def S_ISGITLINK(m):
  52. return (stat.S_IFMT(m) == S_IFGITLINK)
  53. def _decompress(string):
  54. dcomp = zlib.decompressobj()
  55. dcomped = dcomp.decompress(string)
  56. dcomped += dcomp.flush()
  57. return dcomped
  58. def sha_to_hex(sha):
  59. """Takes a string and returns the hex of the sha within"""
  60. hexsha = binascii.hexlify(sha)
  61. assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % hexsha
  62. return hexsha
  63. def hex_to_sha(hex):
  64. """Takes a hex sha and returns a binary sha"""
  65. assert len(hex) == 40, "Incorrent length of hexsha: %s" % hex
  66. return binascii.unhexlify(hex)
  67. def serializable_property(name, docstring=None):
  68. def set(obj, value):
  69. obj._ensure_parsed()
  70. setattr(obj, "_"+name, value)
  71. obj._needs_serialization = True
  72. def get(obj):
  73. obj._ensure_parsed()
  74. return getattr(obj, "_"+name)
  75. return property(get, set, doc=docstring)
  76. def object_class(type):
  77. """Get the object class corresponding to the given type.
  78. :param type: Either a type name string or a numeric type.
  79. :return: The ShaFile subclass corresponding to the given type.
  80. """
  81. return _TYPE_MAP[type]
  82. def check_hexsha(hex, error_msg):
  83. try:
  84. hex_to_sha(hex)
  85. except (TypeError, AssertionError):
  86. raise ObjectFormatException("%s %s" % (error_msg, hex))
  87. def check_identity(identity, error_msg):
  88. email_start = identity.find("<")
  89. email_end = identity.find(">")
  90. if (email_start < 0 or email_end < 0 or email_end <= email_start
  91. or identity.find("<", email_start + 1) >= 0
  92. or identity.find(">", email_end + 1) >= 0
  93. or not identity.endswith(">")):
  94. raise ObjectFormatException(error_msg)
  95. class ShaFile(object):
  96. """A git SHA file."""
  97. @classmethod
  98. def _parse_legacy_object(cls, map):
  99. """Parse a legacy object, creating it and setting object._text"""
  100. text = _decompress(map)
  101. object = None
  102. for cls in OBJECT_CLASSES:
  103. if text.startswith(cls.type_name):
  104. object = cls()
  105. text = text[len(cls.type_name):]
  106. break
  107. assert object is not None, "%s is not a known object type" % text[:9]
  108. assert text[0] == ' ', "%s is not a space" % text[0]
  109. text = text[1:]
  110. size = 0
  111. i = 0
  112. while text[0] >= '0' and text[0] <= '9':
  113. if i > 0 and size == 0:
  114. raise AssertionError("Size is not in canonical format")
  115. size = (size * 10) + int(text[0])
  116. text = text[1:]
  117. i += 1
  118. object._size = size
  119. assert text[0] == "\0", "Size not followed by null"
  120. text = text[1:]
  121. object.set_raw_string(text)
  122. return object
  123. def as_legacy_object_chunks(self):
  124. compobj = zlib.compressobj()
  125. yield compobj.compress(self._header())
  126. for chunk in self.as_raw_chunks():
  127. yield compobj.compress(chunk)
  128. yield compobj.flush()
  129. def as_legacy_object(self):
  130. return "".join(self.as_legacy_object_chunks())
  131. def as_raw_chunks(self):
  132. if self._needs_serialization:
  133. self._chunked_text = self._serialize()
  134. self._needs_serialization = False
  135. return self._chunked_text
  136. def as_raw_string(self):
  137. return "".join(self.as_raw_chunks())
  138. def __str__(self):
  139. return self.as_raw_string()
  140. def __hash__(self):
  141. return hash(self.id)
  142. def as_pretty_string(self):
  143. return self.as_raw_string()
  144. def _ensure_parsed(self):
  145. if self._needs_parsing:
  146. self._deserialize(self._chunked_text)
  147. self._needs_parsing = False
  148. def set_raw_string(self, text):
  149. if type(text) != str:
  150. raise TypeError(text)
  151. self.set_raw_chunks([text])
  152. def set_raw_chunks(self, chunks):
  153. self._chunked_text = chunks
  154. self._sha = None
  155. self._needs_parsing = True
  156. self._needs_serialization = False
  157. @classmethod
  158. def _parse_object(cls, map):
  159. """Parse a new style object , creating it and setting object._text"""
  160. used = 0
  161. byte = ord(map[used])
  162. used += 1
  163. type_num = (byte >> 4) & 7
  164. try:
  165. object = object_class(type_num)()
  166. except KeyError:
  167. raise AssertionError("Not a known type: %d" % type_num)
  168. while (byte & 0x80) != 0:
  169. byte = ord(map[used])
  170. used += 1
  171. raw = map[used:]
  172. object.set_raw_string(_decompress(raw))
  173. return object
  174. @classmethod
  175. def _parse_file(cls, map):
  176. word = (ord(map[0]) << 8) + ord(map[1])
  177. if ord(map[0]) == 0x78 and (word % 31) == 0:
  178. return cls._parse_legacy_object(map)
  179. else:
  180. return cls._parse_object(map)
  181. def __init__(self):
  182. """Don't call this directly"""
  183. self._sha = None
  184. def _deserialize(self, chunks):
  185. raise NotImplementedError(self._deserialize)
  186. def _serialize(self):
  187. raise NotImplementedError(self._serialize)
  188. @classmethod
  189. def from_file(cls, filename):
  190. """Get the contents of a SHA file on disk"""
  191. size = os.path.getsize(filename)
  192. f = GitFile(filename, 'rb')
  193. try:
  194. map = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
  195. shafile = cls._parse_file(map)
  196. return shafile
  197. finally:
  198. f.close()
  199. @staticmethod
  200. def from_raw_string(type_num, string):
  201. """Creates an object of the indicated type from the raw string given.
  202. :param type_num: The numeric type of the object.
  203. :param string: The raw uncompressed contents.
  204. """
  205. obj = object_class(type_num)()
  206. obj.set_raw_string(string)
  207. return obj
  208. @staticmethod
  209. def from_raw_chunks(type_num, chunks):
  210. """Creates an object of the indicated type from the raw chunks given.
  211. :param type_num: The numeric type of the object.
  212. :param chunks: An iterable of the raw uncompressed contents.
  213. """
  214. obj = object_class(type_num)()
  215. obj.set_raw_chunks(chunks)
  216. return obj
  217. @classmethod
  218. def from_string(cls, string):
  219. """Create a blob from a string."""
  220. obj = cls()
  221. obj.set_raw_string(string)
  222. return obj
  223. def _check_has_member(self, member, error_msg):
  224. """Check that the object has a given member variable.
  225. :param member: the member variable to check for
  226. :param error_msg: the message for an error if the member is missing
  227. :raise ObjectFormatException: with the given error_msg if member is
  228. missing or is None
  229. """
  230. if getattr(self, member, None) is None:
  231. raise ObjectFormatException(error_msg)
  232. def check(self):
  233. """Check this object for internal consistency.
  234. :raise ObjectFormatException: if the object is malformed in some way
  235. """
  236. # TODO: if we find that error-checking during object parsing is a
  237. # performance bottleneck, those checks should be moved to the class's
  238. # check() method during optimization so we can still check the object
  239. # when necessary.
  240. try:
  241. self._deserialize(self.as_raw_chunks())
  242. except Exception, e:
  243. raise ObjectFormatException(e)
  244. def _header(self):
  245. return "%s %lu\0" % (self.type_name, self.raw_length())
  246. def raw_length(self):
  247. """Returns the length of the raw string of this object."""
  248. ret = 0
  249. for chunk in self.as_raw_chunks():
  250. ret += len(chunk)
  251. return ret
  252. def _make_sha(self):
  253. ret = make_sha()
  254. ret.update(self._header())
  255. for chunk in self.as_raw_chunks():
  256. ret.update(chunk)
  257. return ret
  258. def sha(self):
  259. """The SHA1 object that is the name of this object."""
  260. if self._needs_serialization or self._sha is None:
  261. self._sha = self._make_sha()
  262. return self._sha
  263. @property
  264. def id(self):
  265. return self.sha().hexdigest()
  266. def get_type(self):
  267. return self.type_num
  268. def set_type(self, type):
  269. self.type_num = type
  270. # DEPRECATED: use type_num or type_name as needed.
  271. type = property(get_type, set_type)
  272. def __repr__(self):
  273. return "<%s %s>" % (self.__class__.__name__, self.id)
  274. def __ne__(self, other):
  275. return self.id != other.id
  276. def __eq__(self, other):
  277. """Return true if the sha of the two objects match.
  278. The __le__ etc methods aren't overriden as they make no sense,
  279. certainly at this level.
  280. """
  281. return self.id == other.id
  282. class Blob(ShaFile):
  283. """A Git Blob object."""
  284. type_name = 'blob'
  285. type_num = 3
  286. def __init__(self):
  287. super(Blob, self).__init__()
  288. self._chunked_text = []
  289. self._needs_parsing = False
  290. self._needs_serialization = False
  291. def _get_data(self):
  292. return self.as_raw_string()
  293. def _set_data(self, data):
  294. self.set_raw_string(data)
  295. data = property(_get_data, _set_data,
  296. "The text contained within the blob object.")
  297. def _get_chunked(self):
  298. return self._chunked_text
  299. def _set_chunked(self, chunks):
  300. self._chunked_text = chunks
  301. chunked = property(_get_chunked, _set_chunked,
  302. "The text within the blob object, as chunks (not necessarily lines).")
  303. @classmethod
  304. def from_file(cls, filename):
  305. blob = ShaFile.from_file(filename)
  306. if not isinstance(blob, cls):
  307. raise NotBlobError(filename)
  308. return blob
  309. def check(self):
  310. """Check this object for internal consistency.
  311. :raise ObjectFormatException: if the object is malformed in some way
  312. """
  313. pass # it's impossible for raw data to be malformed
  314. class Tag(ShaFile):
  315. """A Git Tag object."""
  316. type_name = 'tag'
  317. type_num = 4
  318. def __init__(self):
  319. super(Tag, self).__init__()
  320. self._needs_parsing = False
  321. self._needs_serialization = True
  322. self._tag_timezone_neg_utc = False
  323. @classmethod
  324. def from_file(cls, filename):
  325. tag = ShaFile.from_file(filename)
  326. if not isinstance(tag, cls):
  327. raise NotTagError(filename)
  328. return tag
  329. @classmethod
  330. def from_string(cls, string):
  331. """Create a blob from a string."""
  332. shafile = cls()
  333. shafile.set_raw_string(string)
  334. return shafile
  335. def check(self):
  336. """Check this object for internal consistency.
  337. :raise ObjectFormatException: if the object is malformed in some way
  338. """
  339. super(Tag, self).check()
  340. # TODO(dborowitz): check header order
  341. self._check_has_member("_object_sha", "missing object sha")
  342. self._check_has_member("_object_class", "missing object type")
  343. self._check_has_member("_name", "missing tag name")
  344. if not self._name:
  345. raise ObjectFormatException("empty tag name")
  346. check_hexsha(self._object_sha, "invalid object sha")
  347. if getattr(self, "_tagger", None):
  348. check_identity(self._tagger, "invalid tagger")
  349. def _serialize(self):
  350. chunks = []
  351. chunks.append("%s %s\n" % (_OBJECT_HEADER, self._object_sha))
  352. chunks.append("%s %s\n" % (_TYPE_HEADER, self._object_class.type_name))
  353. chunks.append("%s %s\n" % (_TAG_HEADER, self._name))
  354. if self._tagger:
  355. if self._tag_time is None:
  356. chunks.append("%s %s\n" % (_TAGGER_HEADER, self._tagger))
  357. else:
  358. chunks.append("%s %s %d %s\n" % (
  359. _TAGGER_HEADER, self._tagger, self._tag_time,
  360. format_timezone(self._tag_timezone,
  361. self._tag_timezone_neg_utc)))
  362. chunks.append("\n") # To close headers
  363. chunks.append(self._message)
  364. return chunks
  365. def _deserialize(self, chunks):
  366. """Grab the metadata attached to the tag"""
  367. self._tagger = None
  368. f = StringIO("".join(chunks))
  369. for l in f:
  370. l = l.rstrip("\n")
  371. if l == "":
  372. break # empty line indicates end of headers
  373. (field, value) = l.split(" ", 1)
  374. if field == _OBJECT_HEADER:
  375. self._object_sha = value
  376. elif field == _TYPE_HEADER:
  377. self._object_class = object_class(value)
  378. elif field == _TAG_HEADER:
  379. self._name = value
  380. elif field == _TAGGER_HEADER:
  381. try:
  382. sep = value.index("> ")
  383. except ValueError:
  384. self._tagger = value
  385. self._tag_time = None
  386. self._tag_timezone = None
  387. self._tag_timezone_neg_utc = False
  388. else:
  389. self._tagger = value[0:sep+1]
  390. (timetext, timezonetext) = value[sep+2:].rsplit(" ", 1)
  391. self._tag_time = int(timetext)
  392. self._tag_timezone, self._tag_timezone_neg_utc = \
  393. parse_timezone(timezonetext)
  394. else:
  395. raise AssertionError("Unknown field %s" % field)
  396. self._message = f.read()
  397. def _get_object(self):
  398. """Get the object pointed to by this tag.
  399. :return: tuple of (object class, sha).
  400. """
  401. self._ensure_parsed()
  402. return (self._object_class, self._object_sha)
  403. def _set_object(self, value):
  404. self._ensure_parsed()
  405. (self._object_class, self._object_sha) = value
  406. self._needs_serialization = True
  407. object = property(_get_object, _set_object)
  408. name = serializable_property("name", "The name of this tag")
  409. tagger = serializable_property("tagger",
  410. "Returns the name of the person who created this tag")
  411. tag_time = serializable_property("tag_time",
  412. "The creation timestamp of the tag. As the number of seconds since the epoch")
  413. tag_timezone = serializable_property("tag_timezone",
  414. "The timezone that tag_time is in.")
  415. message = serializable_property("message", "The message attached to this tag")
  416. def parse_tree(text):
  417. """Parse a tree text.
  418. :param text: Serialized text to parse
  419. :yields: tuples of (name, mode, sha)
  420. """
  421. count = 0
  422. l = len(text)
  423. while count < l:
  424. mode_end = text.index(' ', count)
  425. mode = int(text[count:mode_end], 8)
  426. name_end = text.index('\0', mode_end)
  427. name = text[mode_end+1:name_end]
  428. count = name_end+21
  429. sha = text[name_end+1:count]
  430. yield (name, mode, sha_to_hex(sha))
  431. def serialize_tree(items):
  432. """Serialize the items in a tree to a text.
  433. :param items: Sorted iterable over (name, mode, sha) tuples
  434. :return: Serialized tree text as chunks
  435. """
  436. for name, mode, hexsha in items:
  437. yield "%04o %s\0%s" % (mode, name, hex_to_sha(hexsha))
  438. def sorted_tree_items(entries):
  439. """Iterate over a tree entries dictionary in the order in which
  440. the items would be serialized.
  441. :param entries: Dictionary mapping names to (mode, sha) tuples
  442. :return: Iterator over (name, mode, sha)
  443. """
  444. for name, entry in sorted(entries.iteritems(), cmp=cmp_entry):
  445. yield name, entry[0], entry[1]
  446. def cmp_entry((name1, value1), (name2, value2)):
  447. """Compare two tree entries."""
  448. if stat.S_ISDIR(value1[0]):
  449. name1 += "/"
  450. if stat.S_ISDIR(value2[0]):
  451. name2 += "/"
  452. return cmp(name1, name2)
  453. class Tree(ShaFile):
  454. """A Git tree object"""
  455. type_name = 'tree'
  456. type_num = 2
  457. def __init__(self):
  458. super(Tree, self).__init__()
  459. self._entries = {}
  460. self._needs_parsing = False
  461. self._needs_serialization = True
  462. @classmethod
  463. def from_file(cls, filename):
  464. tree = ShaFile.from_file(filename)
  465. if not isinstance(tree, cls):
  466. raise NotTreeError(filename)
  467. return tree
  468. def __contains__(self, name):
  469. self._ensure_parsed()
  470. return name in self._entries
  471. def __getitem__(self, name):
  472. self._ensure_parsed()
  473. return self._entries[name]
  474. def __setitem__(self, name, value):
  475. assert isinstance(value, tuple)
  476. assert len(value) == 2
  477. self._ensure_parsed()
  478. self._entries[name] = value
  479. self._needs_serialization = True
  480. def __delitem__(self, name):
  481. self._ensure_parsed()
  482. del self._entries[name]
  483. self._needs_serialization = True
  484. def __len__(self):
  485. self._ensure_parsed()
  486. return len(self._entries)
  487. def __iter__(self):
  488. self._ensure_parsed()
  489. return iter(self._entries)
  490. def add(self, mode, name, hexsha):
  491. assert type(mode) == int
  492. assert type(name) == str
  493. assert type(hexsha) == str
  494. self._ensure_parsed()
  495. self._entries[name] = mode, hexsha
  496. self._needs_serialization = True
  497. def entries(self):
  498. """Return a list of tuples describing the tree entries"""
  499. self._ensure_parsed()
  500. # The order of this is different from iteritems() for historical
  501. # reasons
  502. return [
  503. (mode, name, hexsha) for (name, mode, hexsha) in self.iteritems()]
  504. def iteritems(self):
  505. """Iterate over entries in the order in which they would be serialized.
  506. :return: Iterator over (name, mode, sha) tuples
  507. """
  508. self._ensure_parsed()
  509. return sorted_tree_items(self._entries)
  510. def _deserialize(self, chunks):
  511. """Grab the entries in the tree"""
  512. parsed_entries = parse_tree("".join(chunks))
  513. # TODO: list comprehension is for efficiency in the common (small) case;
  514. # if memory efficiency in the large case is a concern, use a genexp.
  515. self._entries = dict([(n, (m, s)) for n, m, s in parsed_entries])
  516. self._needs_parsing = False
  517. def check(self):
  518. """Check this object for internal consistency.
  519. :raise ObjectFormatException: if the object is malformed in some way
  520. """
  521. super(Tree, self).check()
  522. last = None
  523. allowed_modes = (stat.S_IFREG | 0755, stat.S_IFREG | 0644,
  524. stat.S_IFLNK, stat.S_IFDIR, S_IFGITLINK,
  525. # TODO: optionally exclude as in git fsck --strict
  526. stat.S_IFREG | 0664)
  527. for name, mode, sha in parse_tree("".join(self._chunked_text)):
  528. check_hexsha(sha, 'invalid sha %s' % sha)
  529. if '/' in name or name in ('', '.', '..'):
  530. raise ObjectFormatException('invalid name %s' % name)
  531. if mode not in allowed_modes:
  532. raise ObjectFormatException('invalid mode %06o' % mode)
  533. entry = (name, (mode, sha))
  534. if last:
  535. if cmp_entry(last, entry) > 0:
  536. raise ObjectFormatException('entries not sorted')
  537. if name == last[0]:
  538. raise ObjectFormatException('duplicate entry %s' % name)
  539. last = entry
  540. def _serialize(self):
  541. return list(serialize_tree(self.iteritems()))
  542. def as_pretty_string(self):
  543. text = []
  544. for name, mode, hexsha in self.iteritems():
  545. if mode & stat.S_IFDIR:
  546. kind = "tree"
  547. else:
  548. kind = "blob"
  549. text.append("%04o %s %s\t%s\n" % (mode, kind, hexsha, name))
  550. return "".join(text)
  551. def parse_timezone(text):
  552. offset = int(text)
  553. negative_utc = (offset == 0 and text[0] == '-')
  554. signum = (offset < 0) and -1 or 1
  555. offset = abs(offset)
  556. hours = int(offset / 100)
  557. minutes = (offset % 100)
  558. return signum * (hours * 3600 + minutes * 60), negative_utc
  559. def format_timezone(offset, negative_utc=False):
  560. if offset % 60 != 0:
  561. raise ValueError("Unable to handle non-minute offset.")
  562. if offset < 0 or (offset == 0 and negative_utc):
  563. sign = '-'
  564. else:
  565. sign = '+'
  566. offset = abs(offset)
  567. return '%c%02d%02d' % (sign, offset / 3600, (offset / 60) % 60)
  568. class Commit(ShaFile):
  569. """A git commit object"""
  570. type_name = 'commit'
  571. type_num = 1
  572. def __init__(self):
  573. super(Commit, self).__init__()
  574. self._parents = []
  575. self._encoding = None
  576. self._needs_parsing = False
  577. self._needs_serialization = True
  578. self._extra = {}
  579. self._author_timezone_neg_utc = False
  580. self._commit_timezone_neg_utc = False
  581. @classmethod
  582. def from_file(cls, filename):
  583. commit = ShaFile.from_file(filename)
  584. if not isinstance(commit, cls):
  585. raise NotCommitError(filename)
  586. return commit
  587. def _deserialize(self, chunks):
  588. self._parents = []
  589. self._extra = []
  590. self._author = None
  591. f = StringIO("".join(chunks))
  592. for l in f:
  593. l = l.rstrip("\n")
  594. if l == "":
  595. # Empty line indicates end of headers
  596. break
  597. (field, value) = l.split(" ", 1)
  598. if field == _TREE_HEADER:
  599. self._tree = value
  600. elif field == _PARENT_HEADER:
  601. self._parents.append(value)
  602. elif field == _AUTHOR_HEADER:
  603. self._author, timetext, timezonetext = value.rsplit(" ", 2)
  604. self._author_time = int(timetext)
  605. self._author_timezone, self._author_timezone_neg_utc =\
  606. parse_timezone(timezonetext)
  607. elif field == _COMMITTER_HEADER:
  608. self._committer, timetext, timezonetext = value.rsplit(" ", 2)
  609. self._commit_time = int(timetext)
  610. self._commit_timezone, self._commit_timezone_neg_utc =\
  611. parse_timezone(timezonetext)
  612. elif field == _ENCODING_HEADER:
  613. self._encoding = value
  614. else:
  615. self._extra.append((field, value))
  616. self._message = f.read()
  617. def check(self):
  618. """Check this object for internal consistency.
  619. :raise ObjectFormatException: if the object is malformed in some way
  620. """
  621. super(Commit, self).check()
  622. # TODO(dborowitz): check header order
  623. # TODO(dborowitz): check for duplicate headers
  624. self._check_has_member("_tree", "missing tree")
  625. self._check_has_member("_author", "missing author")
  626. self._check_has_member("_committer", "missing committer")
  627. # times are currently checked when set
  628. for parent in self._parents:
  629. check_hexsha(parent, "invalid parent sha")
  630. check_hexsha(self._tree, "invalid tree sha")
  631. check_identity(self._author, "invalid author")
  632. check_identity(self._committer, "invalid committer")
  633. def _serialize(self):
  634. chunks = []
  635. chunks.append("%s %s\n" % (_TREE_HEADER, self._tree))
  636. for p in self._parents:
  637. chunks.append("%s %s\n" % (_PARENT_HEADER, p))
  638. chunks.append("%s %s %s %s\n" % (
  639. _AUTHOR_HEADER, self._author, str(self._author_time),
  640. format_timezone(self._author_timezone,
  641. self._author_timezone_neg_utc)))
  642. chunks.append("%s %s %s %s\n" % (
  643. _COMMITTER_HEADER, self._committer, str(self._commit_time),
  644. format_timezone(self._commit_timezone,
  645. self._commit_timezone_neg_utc)))
  646. if self.encoding:
  647. chunks.append("%s %s\n" % (_ENCODING_HEADER, self.encoding))
  648. for k, v in self.extra:
  649. if "\n" in k or "\n" in v:
  650. raise AssertionError("newline in extra data: %r -> %r" % (k, v))
  651. chunks.append("%s %s\n" % (k, v))
  652. chunks.append("\n") # There must be a new line after the headers
  653. chunks.append(self._message)
  654. return chunks
  655. tree = serializable_property("tree", "Tree that is the state of this commit")
  656. def _get_parents(self):
  657. """Return a list of parents of this commit."""
  658. self._ensure_parsed()
  659. return self._parents
  660. def _set_parents(self, value):
  661. """Set a list of parents of this commit."""
  662. self._ensure_parsed()
  663. self._needs_serialization = True
  664. self._parents = value
  665. parents = property(_get_parents, _set_parents)
  666. def _get_extra(self):
  667. """Return extra settings of this commit."""
  668. self._ensure_parsed()
  669. return self._extra
  670. extra = property(_get_extra)
  671. author = serializable_property("author",
  672. "The name of the author of the commit")
  673. committer = serializable_property("committer",
  674. "The name of the committer of the commit")
  675. message = serializable_property("message",
  676. "The commit message")
  677. commit_time = serializable_property("commit_time",
  678. "The timestamp of the commit. As the number of seconds since the epoch.")
  679. commit_timezone = serializable_property("commit_timezone",
  680. "The zone the commit time is in")
  681. author_time = serializable_property("author_time",
  682. "The timestamp the commit was written. as the number of seconds since the epoch.")
  683. author_timezone = serializable_property("author_timezone",
  684. "Returns the zone the author time is in.")
  685. encoding = serializable_property("encoding",
  686. "Encoding of the commit message.")
  687. OBJECT_CLASSES = (
  688. Commit,
  689. Tree,
  690. Blob,
  691. Tag,
  692. )
  693. _TYPE_MAP = {}
  694. for cls in OBJECT_CLASSES:
  695. _TYPE_MAP[cls.type_name] = cls
  696. _TYPE_MAP[cls.type_num] = cls
  697. # Hold on to the pure-python implementations for testing
  698. _parse_tree_py = parse_tree
  699. _sorted_tree_items_py = sorted_tree_items
  700. try:
  701. # Try to import C versions
  702. from dulwich._objects import parse_tree, sorted_tree_items
  703. except ImportError:
  704. pass