Mercurial and Git clients can push and pull from this alias URL to interact with this repository. You can change to which repository an alias points by going to the Aliases link on the project page.
0.9.4
objects.py: mergetag value parsing should not gobble up all remaining lines as it breaks "extra" key-value pairs with empty keys (see test_evil_git_extras)
# objects.py -- Access to base git objects# Copyright (C) 2007 James Westby <jw+debian@jameswestby.net># Copyright (C) 2008-2013 Jelmer Vernooij <jelmer@samba.org>## This program is free software; you can redistribute it and/or# modify it under the terms of the GNU General Public License# as published by the Free Software Foundation; version 2# of the License or (at your option) a later version of the License.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.## You should have received a copy of the GNU General Public License# along with this program; if not, write to the Free Software# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,# MA 02110-1301, USA."""Access to base git objects."""importbinasciifromcStringIOimport(StringIO,)importosimportposixpathimportstatimportwarningsimportzlibfromdulwich.errorsimport(ChecksumMismatch,NotBlobError,NotCommitError,NotTagError,NotTreeError,ObjectFormatException,)fromdulwich.fileimportGitFilefromdulwich._compatimport(make_sha,namedtuple,)ZERO_SHA="0"*40# Header fields for commits_TREE_HEADER="tree"_PARENT_HEADER="parent"_AUTHOR_HEADER="author"_COMMITTER_HEADER="committer"_ENCODING_HEADER="encoding"_MERGETAG_HEADER="mergetag"# Header fields for objects_OBJECT_HEADER="object"_TYPE_HEADER="type"_TAG_HEADER="tag"_TAGGER_HEADER="tagger"S_IFGITLINK=0160000defS_ISGITLINK(m):"""Check if a mode indicates a submodule. :param m: Mode to check :return: a ``boolean`` """return(stat.S_IFMT(m)==S_IFGITLINK)def_decompress(string):dcomp=zlib.decompressobj()dcomped=dcomp.decompress(string)dcomped+=dcomp.flush()returndcompeddefsha_to_hex(sha):"""Takes a string and returns the hex of the sha within"""hexsha=binascii.hexlify(sha)assertlen(hexsha)==40,"Incorrect length of sha1 string: %d"%hexshareturnhexshadefhex_to_sha(hex):"""Takes a hex sha and returns a binary sha"""assertlen(hex)==40,"Incorrent length of hexsha: %s"%hextry:returnbinascii.unhexlify(hex)exceptTypeError,exc:ifnotisinstance(hex,str):raiseraiseValueError(exc.message)defhex_to_filename(path,hex):"""Takes a hex sha and returns its filename relative to the given path."""dir=hex[:2]file=hex[2:]# Check from object dirreturnos.path.join(path,dir,file)deffilename_to_hex(filename):"""Takes an object filename and returns its corresponding hex sha."""# grab the last (up to) two path componentsnames=filename.rsplit(os.path.sep,2)[-2:]errmsg="Invalid object filename: %s"%filenameassertlen(names)==2,errmsgbase,rest=namesassertlen(base)==2andlen(rest)==38,errmsghex=base+resthex_to_sha(hex)returnhexdefobject_header(num_type,length):"""Return an object header for the given numeric type and text length."""return"%s%d\0"%(object_class(num_type).type_name,length)defserializable_property(name,docstring=None):"""A property that helps tracking whether serialization is necessary. """defset(obj,value):obj._ensure_parsed()setattr(obj,"_"+name,value)obj._needs_serialization=Truedefget(obj):obj._ensure_parsed()returngetattr(obj,"_"+name)returnproperty(get,set,doc=docstring)defobject_class(type):"""Get the object class corresponding to the given type. :param type: Either a type name string or a numeric type. :return: The ShaFile subclass corresponding to the given type, or None if type is not a valid type name/number. """return_TYPE_MAP.get(type,None)defcheck_hexsha(hex,error_msg):"""Check if a string is a valid hex sha string. :param hex: Hex string to check :param error_msg: Error message to use in exception :raise ObjectFormatException: Raised when the string is not valid """try:hex_to_sha(hex)except(TypeError,AssertionError,ValueError):raiseObjectFormatException("%s%s"%(error_msg,hex))defcheck_identity(identity,error_msg):"""Check if the specified identity is valid. This will raise an exception if the identity is not valid. :param identity: Identity string :param error_msg: Error message to use in exception """email_start=identity.find("<")email_end=identity.find(">")if(email_start<0oremail_end<0oremail_end<=email_startoridentity.find("<",email_start+1)>=0oridentity.find(">",email_end+1)>=0ornotidentity.endswith(">")):raiseObjectFormatException(error_msg)classFixedSha(object):"""SHA object that behaves like hashlib's but is given a fixed value."""__slots__=('_hexsha','_sha')def__init__(self,hexsha):self._hexsha=hexshaself._sha=hex_to_sha(hexsha)defdigest(self):"""Return the raw SHA digest."""returnself._shadefhexdigest(self):"""Return the hex SHA digest."""returnself._hexshaclassShaFile(object):"""A git SHA file."""__slots__=('_needs_parsing','_chunked_text','_file','_path','_sha','_needs_serialization','_magic')@staticmethoddef_parse_legacy_object_header(magic,f):"""Parse a legacy object, creating it but not reading the file."""bufsize=1024decomp=zlib.decompressobj()header=decomp.decompress(magic)start=0end=-1whileend<0:extra=f.read(bufsize)header+=decomp.decompress(extra)magic+=extraend=header.find("\0",start)start=len(header)header=header[:end]type_name,size=header.split(" ",1)size=int(size)# sanity checkobj_class=object_class(type_name)ifnotobj_class:raiseObjectFormatException("Not a known type: %s"%type_name)ret=obj_class()ret._magic=magicreturnretdef_parse_legacy_object(self,map):"""Parse a legacy object, setting the raw string."""text=_decompress(map)header_end=text.find('\0')ifheader_end<0:raiseObjectFormatException("Invalid object header, no \\0")self.set_raw_string(text[header_end+1:])defas_legacy_object_chunks(self):"""Return chunks representing the object in the experimental format. :return: List of strings """compobj=zlib.compressobj()yieldcompobj.compress(self._header())forchunkinself.as_raw_chunks():yieldcompobj.compress(chunk)yieldcompobj.flush()defas_legacy_object(self):"""Return string representing the object in the experimental format. """return"".join(self.as_legacy_object_chunks())defas_raw_chunks(self):"""Return chunks with serialization of the object. :return: List of strings, not necessarily one per line """ifself._needs_parsing:self._ensure_parsed()elifself._needs_serialization:self._chunked_text=self._serialize()returnself._chunked_textdefas_raw_string(self):"""Return raw string with serialization of the object. :return: String object """return"".join(self.as_raw_chunks())def__str__(self):"""Return raw string serialization of this object."""returnself.as_raw_string()def__hash__(self):"""Return unique hash for this object."""returnhash(self.id)defas_pretty_string(self):"""Return a string representing this object, fit for display."""returnself.as_raw_string()def_ensure_parsed(self):ifself._needs_parsing:ifnotself._chunked_text:ifself._fileisnotNone:self._parse_file(self._file)self._file=Noneelifself._pathisnotNone:self._parse_path()else:raiseAssertionError("ShaFile needs either text or filename")self._deserialize(self._chunked_text)self._needs_parsing=Falsedefset_raw_string(self,text,sha=None):"""Set the contents of this object from a serialized string."""iftype(text)!=str:raiseTypeError(text)self.set_raw_chunks([text],sha)defset_raw_chunks(self,chunks,sha=None):"""Set the contents of this object from a list of chunks."""self._chunked_text=chunksself._deserialize(chunks)ifshaisNone:self._sha=Noneelse:self._sha=FixedSha(sha)self._needs_parsing=Falseself._needs_serialization=False@staticmethoddef_parse_object_header(magic,f):"""Parse a new style object, creating it but not reading the file."""num_type=(ord(magic[0])>>4)&7obj_class=object_class(num_type)ifnotobj_class:raiseObjectFormatException("Not a known type %d"%num_type)ret=obj_class()ret._magic=magicreturnretdef_parse_object(self,map):"""Parse a new style object, setting self._text."""# skip type and size; type must have already been determined, and# we trust zlib to fail if it's otherwise corruptedbyte=ord(map[0])used=1while(byte&0x80)!=0:byte=ord(map[used])used+=1raw=map[used:]self.set_raw_string(_decompress(raw))@classmethoddef_is_legacy_object(cls,magic):b0,b1=map(ord,magic)word=(b0<<8)+b1return(b0&0x8F)==0x08and(word%31)==0@classmethoddef_parse_file_header(cls,f):magic=f.read(2)ifcls._is_legacy_object(magic):returncls._parse_legacy_object_header(magic,f)else:returncls._parse_object_header(magic,f)def__init__(self):"""Don't call this directly"""self._sha=Noneself._path=Noneself._file=Noneself._magic=Noneself._chunked_text=[]self._needs_parsing=Falseself._needs_serialization=Truedef_deserialize(self,chunks):raiseNotImplementedError(self._deserialize)def_serialize(self):raiseNotImplementedError(self._serialize)def_parse_path(self):f=GitFile(self._path,'rb')try:self._parse_file(f)finally:f.close()def_parse_file(self,f):magic=self._magicifmagicisNone:magic=f.read(2)map=magic+f.read()ifself._is_legacy_object(magic[:2]):self._parse_legacy_object(map)else:self._parse_object(map)@classmethoddeffrom_path(cls,path):"""Open a SHA file from disk."""f=GitFile(path,'rb')try:obj=cls.from_file(f)obj._path=pathobj._sha=FixedSha(filename_to_hex(path))obj._file=Noneobj._magic=Nonereturnobjfinally:f.close()@classmethoddeffrom_file(cls,f):"""Get the contents of a SHA file on disk."""try:obj=cls._parse_file_header(f)obj._sha=Noneobj._needs_parsing=Trueobj._needs_serialization=Trueobj._file=freturnobjexcept(IndexError,ValueError),e:raiseObjectFormatException("invalid object header")@staticmethoddeffrom_raw_string(type_num,string,sha=None):"""Creates an object of the indicated type from the raw string given. :param type_num: The numeric type of the object. :param string: The raw uncompressed contents. :param sha: Optional known sha for the object """obj=object_class(type_num)()obj.set_raw_string(string,sha)returnobj@staticmethoddeffrom_raw_chunks(type_num,chunks,sha=None):"""Creates an object of the indicated type from the raw chunks given. :param type_num: The numeric type of the object. :param chunks: An iterable of the raw uncompressed contents. :param sha: Optional known sha for the object """obj=object_class(type_num)()obj.set_raw_chunks(chunks,sha)returnobj@classmethoddeffrom_string(cls,string):"""Create a ShaFile from a string."""obj=cls()obj.set_raw_string(string)returnobjdef_check_has_member(self,member,error_msg):"""Check that the object has a given member variable. :param member: the member variable to check for :param error_msg: the message for an error if the member is missing :raise ObjectFormatException: with the given error_msg if member is missing or is None """ifgetattr(self,member,None)isNone:raiseObjectFormatException(error_msg)defcheck(self):"""Check this object for internal consistency. :raise ObjectFormatException: if the object is malformed in some way :raise ChecksumMismatch: if the object was created with a SHA that does not match its contents """# TODO: if we find that error-checking during object parsing is a# performance bottleneck, those checks should be moved to the class's# check() method during optimization so we can still check the object# when necessary.old_sha=self.idtry:self._deserialize(self.as_raw_chunks())self._sha=Nonenew_sha=self.idexceptException,e:raiseObjectFormatException(e)ifold_sha!=new_sha:raiseChecksumMismatch(new_sha,old_sha)def_header(self):returnobject_header(self.type,self.raw_length())defraw_length(self):"""Returns the length of the raw string of this object."""ret=0forchunkinself.as_raw_chunks():ret+=len(chunk)returnretdef_make_sha(self):ret=make_sha()ret.update(self._header())forchunkinself.as_raw_chunks():ret.update(chunk)returnretdefsha(self):"""The SHA1 object that is the name of this object."""ifself._shaisNoneorself._needs_serialization:# this is a local because as_raw_chunks() overwrites self._shanew_sha=make_sha()new_sha.update(self._header())forchunkinself.as_raw_chunks():new_sha.update(chunk)self._sha=new_shareturnself._sha@propertydefid(self):"""The hex SHA of this object."""returnself.sha().hexdigest()defget_type(self):"""Return the type number for this object class."""returnself.type_numdefset_type(self,type):"""Set the type number for this object class."""self.type_num=type# DEPRECATED: use type_num or type_name as needed.type=property(get_type,set_type)def__repr__(self):return"<%s%s>"%(self.__class__.__name__,self.id)def__ne__(self,other):returnnotisinstance(other,ShaFile)orself.id!=other.iddef__eq__(self,other):"""Return True if the SHAs of the two objects match. It doesn't make sense to talk about an order on ShaFiles, so we don't override the rich comparison methods (__le__, etc.). """returnisinstance(other,ShaFile)andself.id==other.idclassBlob(ShaFile):"""A Git Blob object."""__slots__=()type_name='blob'type_num=3def__init__(self):super(Blob,self).__init__()self._chunked_text=[]self._needs_parsing=Falseself._needs_serialization=Falsedef_get_data(self):returnself.as_raw_string()def_set_data(self,data):self.set_raw_string(data)data=property(_get_data,_set_data,"The text contained within the blob object.")def_get_chunked(self):self._ensure_parsed()returnself._chunked_textdef_set_chunked(self,chunks):self._chunked_text=chunksdef_serialize(self):ifnotself._chunked_text:self._ensure_parsed()self._needs_serialization=Falsereturnself._chunked_textdef_deserialize(self,chunks):self._chunked_text=chunkschunked=property(_get_chunked,_set_chunked,"The text within the blob object, as chunks (not necessarily lines).")@classmethoddeffrom_path(cls,path):blob=ShaFile.from_path(path)ifnotisinstance(blob,cls):raiseNotBlobError(path)returnblobdefcheck(self):"""Check this object for internal consistency. :raise ObjectFormatException: if the object is malformed in some way """super(Blob,self).check()def_parse_message(chunks):"""Parse a message with a list of fields and a body. :param chunks: the raw chunks of the tag or commit object. :return: iterator of tuples of (field, value), one per header line, in the order read from the text, possibly including duplicates. Includes a field named None for the freeform tag/commit text. """f=StringIO("".join(chunks)) k = None
v = ""
for l in f:
- if l.startswith(" "):
+ if l.startswith(" ") and k == 'mergetag':
v += l[1:]
else:
if k is not None:
yield(k,v.rstrip("\n"))ifl=="\n":# Empty line indicates end of headersbreak(k,v)=l.split(" ",1)yield(None,f.read())f.close()classTag(ShaFile):"""A Git Tag object."""type_name='tag'type_num=4__slots__=('_tag_timezone_raw','_name','_object_sha','_object_class','_tag_time','_tag_timezone','_tagger','_message')def__init__(self):super(Tag,self).__init__()self._tag_timezone_raw=None@classmethoddeffrom_path(cls,filename):tag=ShaFile.from_path(filename)ifnotisinstance(tag,cls):raiseNotTagError(filename)returntagdefcheck(self):"""Check this object for internal consistency. :raise ObjectFormatException: if the object is malformed in some way """super(Tag,self).check()self._check_has_member("_object_sha","missing object sha")self._check_has_member("_object_class","missing object type")self._check_has_member("_name","missing tag name")ifnotself._name:raiseObjectFormatException("empty tag name")check_hexsha(self._object_sha,"invalid object sha")ifgetattr(self,"_tagger",None):check_identity(self._tagger,"invalid tagger")last=Noneforfield,_in_parse_message(self._chunked_text):iffield==_OBJECT_HEADERandlastisnotNone:raiseObjectFormatException("unexpected object")eliffield==_TYPE_HEADERandlast!=_OBJECT_HEADER:raiseObjectFormatException("unexpected type")eliffield==_TAG_HEADERandlast!=_TYPE_HEADER:raiseObjectFormatException("unexpected tag name")eliffield==_TAGGER_HEADERandlast!=_TAG_HEADER:raiseObjectFormatException("unexpected tagger")last=fielddef_serialize(self):chunks=[]chunks.append("%s%s\n"%(_OBJECT_HEADER,self._object_sha))chunks.append("%s%s\n"%(_TYPE_HEADER,self._object_class.type_name))chunks.append("%s%s\n"%(_TAG_HEADER,self._name))ifself._tagger:ifself._tag_timeisNone:chunks.append("%s%s\n"%(_TAGGER_HEADER,self._tagger))else:chunks.append("%s%s%d%s\n"%(_TAGGER_HEADER,self._tagger,self._tag_time,self._tag_timezone_raworformat_timezone(self._tag_timezone)))chunks.append("\n")# To close headerschunks.append(self._message)returnchunksdef_deserialize(self,chunks):"""Grab the metadata attached to the tag"""self._tagger=Noneforfield,valuein_parse_message(chunks):iffield==_OBJECT_HEADER:self._object_sha=valueeliffield==_TYPE_HEADER:obj_class=object_class(value)ifnotobj_class:raiseObjectFormatException("Not a known type: %s"%value)self._object_class=obj_classeliffield==_TAG_HEADER:self._name=valueeliffield==_TAGGER_HEADER:try:sep=value.index("> ")exceptValueError:self._tagger=valueself._tag_time=Noneself._tag_timezone=Noneself._tag_timezone_raw=Noneelse:self._tagger=value[0:sep+1]try:(timetext,timezonetext)=value[sep+2:].rsplit(" ",1)self._tag_time=int(timetext)self._tag_timezone,self._tag_timezone_raw= \
parse_timezone(timezonetext)exceptValueError,e:raiseObjectFormatException(e)eliffieldisNone:self._message=valueelse:raiseObjectFormatException("Unknown field %s"%field)def_get_object(self):"""Get the object pointed to by this tag. :return: tuple of (object class, sha). """self._ensure_parsed()return(self._object_class,self._object_sha)def_set_object(self,value):self._ensure_parsed()(self._object_class,self._object_sha)=valueself._needs_serialization=Trueobject=property(_get_object,_set_object)name=serializable_property("name","The name of this tag")tagger=serializable_property("tagger","Returns the name of the person who created this tag")tag_time=serializable_property("tag_time","The creation timestamp of the tag. As the number of seconds since the epoch")tag_timezone=serializable_property("tag_timezone","The timezone that tag_time is in.")message=serializable_property("message","The message attached to this tag")classTreeEntry(namedtuple('TreeEntry',['path','mode','sha'])):"""Named tuple encapsulating a single tree entry."""defin_path(self,path):"""Return a copy of this entry with the given path prepended."""iftype(self.path)!=str:raiseTypeErrorreturnTreeEntry(posixpath.join(path,self.path),self.mode,self.sha)defparse_tree(text,strict=False):"""Parse a tree text. :param text: Serialized text to parse :return: iterator of tuples of (name, mode, sha) :raise ObjectFormatException: if the object was malformed in some way """count=0l=len(text)whilecount<l:mode_end=text.index(' ',count)mode_text=text[count:mode_end]ifstrictandmode_text.startswith('0'):raiseObjectFormatException("Invalid mode '%s'"%mode_text)try:mode=int(mode_text,8)exceptValueError:raiseObjectFormatException("Invalid mode '%s'"%mode_text)name_end=text.index('\0',mode_end)name=text[mode_end+1:name_end]count=name_end+21sha=text[name_end+1:count]iflen(sha)!=20:raiseObjectFormatException("Sha has invalid length")hexsha=sha_to_hex(sha)yield(name,mode,hexsha)defserialize_tree(items):"""Serialize the items in a tree to a text. :param items: Sorted iterable over (name, mode, sha) tuples :return: Serialized tree text as chunks """forname,mode,hexshainitems:yield"%04o%s\0%s"%(mode,name,hex_to_sha(hexsha))defsorted_tree_items(entries,name_order):"""Iterate over a tree entries dictionary. :param name_order: If True, iterate entries in order of their name. If False, iterate entries in tree order, that is, treat subtree entries as having '/' appended. :param entries: Dictionary mapping names to (mode, sha) tuples :return: Iterator over (name, mode, hexsha) """cmp_func=name_orderandcmp_entry_name_orderorcmp_entryforname,entryinsorted(entries.iteritems(),cmp=cmp_func):mode,hexsha=entry# Stricter type checks than normal to mirror checks in the C version.ifnotisinstance(mode,int)andnotisinstance(mode,long):raiseTypeError('Expected integer/long for mode, got %r'%mode)mode=int(mode)ifnotisinstance(hexsha,str):raiseTypeError('Expected a string for SHA, got %r'%hexsha)yieldTreeEntry(name,mode,hexsha)defcmp_entry((name1,value1),(name2,value2)):"""Compare two tree entries in tree order."""ifstat.S_ISDIR(value1[0]):name1+="/"ifstat.S_ISDIR(value2[0]):name2+="/"returncmp(name1,name2)defcmp_entry_name_order(entry1,entry2):"""Compare two tree entries in name order."""returncmp(entry1[0],entry2[0])classTree(ShaFile):"""A Git tree object"""type_name='tree'type_num=2__slots__=('_entries')def__init__(self):super(Tree,self).__init__()self._entries={}@classmethoddeffrom_path(cls,filename):tree=ShaFile.from_path(filename)ifnotisinstance(tree,cls):raiseNotTreeError(filename)returntreedef__contains__(self,name):self._ensure_parsed()returnnameinself._entriesdef__getitem__(self,name):self._ensure_parsed()returnself._entries[name]def__setitem__(self,name,value):"""Set a tree entry by name. :param name: The name of the entry, as a string. :param value: A tuple of (mode, hexsha), where mode is the mode of the entry as an integral type and hexsha is the hex SHA of the entry as a string. """mode,hexsha=valueself._ensure_parsed()self._entries[name]=(mode,hexsha)self._needs_serialization=Truedef__delitem__(self,name):self._ensure_parsed()delself._entries[name]self._needs_serialization=Truedef__len__(self):self._ensure_parsed()returnlen(self._entries)def__iter__(self):self._ensure_parsed()returniter(self._entries)defadd(self,name,mode,hexsha):"""Add an entry to the tree. :param mode: The mode of the entry as an integral type. Not all possible modes are supported by git; see check() for details. :param name: The name of the entry, as a string. :param hexsha: The hex SHA of the entry as a string. """iftype(name)isintandtype(mode)isstr:(name,mode)=(mode,name)warnings.warn("Please use Tree.add(name, mode, hexsha)",category=DeprecationWarning,stacklevel=2)self._ensure_parsed()self._entries[name]=mode,hexshaself._needs_serialization=Truedefentries(self):"""Return a list of tuples describing the tree entries. :note: The order of the tuples that are returned is different from that returned by the items and iteritems methods. This function will be deprecated in the future. """warnings.warn("Tree.entries() is deprecated. Use Tree.items() or"" Tree.iteritems() instead.",category=DeprecationWarning,stacklevel=2)self._ensure_parsed()# The order of this is different from iteritems() for historical# reasonsreturn[(mode,name,hexsha)for(name,mode,hexsha)inself.iteritems()]defiteritems(self,name_order=False):"""Iterate over entries. :param name_order: If True, iterate in name order instead of tree order. :return: Iterator over (name, mode, sha) tuples """self._ensure_parsed()returnsorted_tree_items(self._entries,name_order)defitems(self):"""Return the sorted entries in this tree. :return: List with (name, mode, sha) tuples """returnlist(self.iteritems())def_deserialize(self,chunks):"""Grab the entries in the tree"""try:parsed_entries=parse_tree("".join(chunks))exceptValueError,e:raiseObjectFormatException(e)# TODO: list comprehension is for efficiency in the common (small) case;# if memory efficiency in the large case is a concern, use a genexp.self._entries=dict([(n,(m,s))forn,m,sinparsed_entries])defcheck(self):"""Check this object for internal consistency. :raise ObjectFormatException: if the object is malformed in some way """super(Tree,self).check()last=Noneallowed_modes=(stat.S_IFREG|0755,stat.S_IFREG|0644,stat.S_IFLNK,stat.S_IFDIR,S_IFGITLINK,# TODO: optionally exclude as in git fsck --strictstat.S_IFREG|0664)forname,mode,shainparse_tree(''.join(self._chunked_text),True):check_hexsha(sha,'invalid sha %s'%sha)if'/'innameornamein('','.','..'):raiseObjectFormatException('invalid name %s'%name)ifmodenotinallowed_modes:raiseObjectFormatException('invalid mode %06o'%mode)entry=(name,(mode,sha))iflast:ifcmp_entry(last,entry)>0:raiseObjectFormatException('entries not sorted')ifname==last[0]:raiseObjectFormatException('duplicate entry %s'%name)last=entrydef_serialize(self):returnlist(serialize_tree(self.iteritems()))defas_pretty_string(self):text=[]forname,mode,hexshainself.iteritems():ifmode&stat.S_IFDIR:kind="tree"else:kind="blob"text.append("%04o%s%s\t%s\n"%(mode,kind,hexsha,name))return"".join(text)deflookup_path(self,lookup_obj,path):"""Look up an object in a Git tree. :param lookup_obj: Callback for retrieving object by SHA1 :param path: Path to lookup :return: A tuple of (mode, SHA) of the resulting path. """parts=path.split('/')sha=self.idmode=Noneforpinparts:ifnotp:continueobj=lookup_obj(sha)ifnotisinstance(obj,Tree):raiseNotTreeError(sha)mode,sha=obj[p]returnmode,shadefparse_timezone(text):"""Parse a timezone text fragment (e.g. '+0100'). :param text: Text to parse. :return: Tuple with timezone as seconds difference to UTC and a boolean indicating whether this was a UTC timezone prefixed with a negative sign (-0000). """# cgit parses the first character as the sign, and the rest# as an integer (using strtol), which could also be negative.# We do the same for compatibility. See #697828.ifnottext[0]in'+-':raiseValueError("Timezone must start with + or - (%(text)s)"%vars())sign=text[0]offset=int(text[1:])ifsign=='-':offset=-offsetunnecessary_negative_timezone=(offset>=0andsign=='-')signum=(offset<0)and-1or1offset=abs(offset)hours=int(offset/100)minutes=(offset%100)raw=Noneparsed=signum*(hours*3600+minutes*60)ifunnecessary_negative_timezoneortext!=format_timezone(parsed):raw=textreturn(parsed,raw)defformat_timezone(offset,unnecessary_negative_timezone=False):"""Format a timezone for Git serialization. :param offset: Timezone offset as seconds difference to UTC :param unnecessary_negative_timezone: Whether to use a minus sign for UTC or positive timezones (-0000 and --700 rather than +0000 / +0700). """ifoffset%60!=0:raiseValueError("Unable to handle non-minute offset.")ifoffset<0orunnecessary_negative_timezone:sign='-'offset=-offsetelse:sign='+'return'%c%02d%02d'%(sign,offset/3600,(offset/60)%60)defparse_commit(chunks):"""Parse a commit object from chunks. :param chunks: Chunks to parse :return: Tuple of (tree, parents, author_info, commit_info, encoding, mergetag, message, extra) """parents=[]extra=[]tree=Noneauthor_info=(None,None,(None,None))commit_info=(None,None,(None,None))encoding=Nonemergetag=[]message=Noneforfield,valuein_parse_message(chunks):iffield==_TREE_HEADER:tree=valueeliffield==_PARENT_HEADER:parents.append(value)eliffield==_AUTHOR_HEADER:author,timetext,timezonetext=value.rsplit(" ",2)author_time=int(timetext)author_info=(author,author_time,parse_timezone(timezonetext))eliffield==_COMMITTER_HEADER:committer,timetext,timezonetext=value.rsplit(" ",2)commit_time=int(timetext)commit_info=(committer,commit_time,parse_timezone(timezonetext))eliffield==_ENCODING_HEADER:encoding=valueeliffield==_MERGETAG_HEADER:mergetag.append(Tag.from_string(value+"\n"))eliffieldisNone:message=valueelse:extra.append((field,value))return(tree,parents,author_info,commit_info,encoding,mergetag,message,extra)classCommit(ShaFile):"""A git commit object"""type_name='commit'type_num=1__slots__=('_parents','_encoding','_extra','_author_timezone_raw','_commit_timezone_raw','_commit_time','_author_time','_author_timezone','_commit_timezone','_author','_committer','_parents','_extra','_encoding','_tree','_message','_mergetag')def__init__(self):super(Commit,self).__init__()self._parents=[]self._encoding=Noneself._mergetag=[]self._extra=[]self._author_timezone_raw=Noneself._commit_timezone_raw=None@classmethoddeffrom_path(cls,path):commit=ShaFile.from_path(path)ifnotisinstance(commit,cls):raiseNotCommitError(path)returncommitdef_deserialize(self,chunks):(self._tree,self._parents,author_info,commit_info,self._encoding,self._mergetag,self._message,self._extra)= \
parse_commit(chunks)(self._author,self._author_time,(self._author_timezone,self._author_timezone_raw))=author_info(self._committer,self._commit_time,(self._commit_timezone,self._commit_timezone_raw))=commit_infodefcheck(self):"""Check this object for internal consistency. :raise ObjectFormatException: if the object is malformed in some way """super(Commit,self).check()self._check_has_member("_tree","missing tree")self._check_has_member("_author","missing author")self._check_has_member("_committer","missing committer")# times are currently checked when setforparentinself._parents:check_hexsha(parent,"invalid parent sha")check_hexsha(self._tree,"invalid tree sha")check_identity(self._author,"invalid author")check_identity(self._committer,"invalid committer")last=Noneforfield,_in_parse_message(self._chunked_text):iffield==_TREE_HEADERandlastisnotNone:raiseObjectFormatException("unexpected tree")eliffield==_PARENT_HEADERandlastnotin(_PARENT_HEADER,_TREE_HEADER):raiseObjectFormatException("unexpected parent")eliffield==_AUTHOR_HEADERandlastnotin(_TREE_HEADER,_PARENT_HEADER):raiseObjectFormatException("unexpected author")eliffield==_COMMITTER_HEADERandlast!=_AUTHOR_HEADER:raiseObjectFormatException("unexpected committer")eliffield==_ENCODING_HEADERandlast!=_COMMITTER_HEADER:raiseObjectFormatException("unexpected encoding")last=field# TODO: optionally check for duplicate parentsdef_serialize(self):chunks=[]chunks.append("%s%s\n"%(_TREE_HEADER,self._tree))forpinself._parents:chunks.append("%s%s\n"%(_PARENT_HEADER,p))chunks.append("%s%s%s%s\n"%(_AUTHOR_HEADER,self._author,str(self._author_time),self._author_timezone_raworformat_timezone(self._author_timezone)))chunks.append("%s%s%s%s\n"%(_COMMITTER_HEADER,self._committer,str(self._commit_time),self._commit_timezone_raworformat_timezone(self._commit_timezone)))ifself.encoding:chunks.append("%s%s\n"%(_ENCODING_HEADER,self.encoding))formergetaginself.mergetag:mergetag_chunks=mergetag.as_raw_string().split("\n")chunks.append("%s%s\n"%(_MERGETAG_HEADER,mergetag_chunks[0]))# Embedded extra header needs leading spaceforchunkinmergetag_chunks[1:]:chunks.append(" %s\n"%chunk)# No trailing empty linechunks[-1]=chunks[-1].rstrip(" \n")fork,vinself.extra:if"\n"inkor"\n"inv:raiseAssertionError("newline in extra data: %r -> %r"%(k,v))chunks.append("%s%s\n"%(k,v))chunks.append("\n")# There must be a new line after the headerschunks.append(self._message)returnchunkstree=serializable_property("tree","Tree that is the state of this commit")def_get_parents(self):"""Return a list of parents of this commit."""self._ensure_parsed()returnself._parentsdef_set_parents(self,value):"""Set a list of parents of this commit."""self._ensure_parsed()self._needs_serialization=Trueself._parents=valueparents=property(_get_parents,_set_parents)def_get_extra(self):"""Return extra settings of this commit."""self._ensure_parsed()returnself._extraextra=property(_get_extra)author=serializable_property("author","The name of the author of the commit")committer=serializable_property("committer","The name of the committer of the commit")message=serializable_property("message","The commit message")commit_time=serializable_property("commit_time","The timestamp of the commit. As the number of seconds since the epoch.")commit_timezone=serializable_property("commit_timezone","The zone the commit time is in")commit_timezone_raw=serializable_property("commit_timezone_raw","The raw zone the commit time is in, if reserializing the parsed""offset would result in a different value; otherwise, None")author_time=serializable_property("author_time","The timestamp the commit was written. as the number of seconds since the epoch.")author_timezone=serializable_property("author_timezone","Returns the zone the author time is in.")author_timezone_raw=serializable_property("author_timezone_raw","The raw zone the author time is in, if reserializing the parsed""offset would result in a different value; otherwise, None")encoding=serializable_property("encoding","Encoding of the commit message.")mergetag=serializable_property("mergetag","Associated signed tag.")OBJECT_CLASSES=(Commit,Tree,Blob,Tag,)_TYPE_MAP={}forclsinOBJECT_CLASSES:_TYPE_MAP[cls.type_name]=cls_TYPE_MAP[cls.type_num]=cls# Hold on to the pure-python implementations for testing_parse_tree_py=parse_tree_sorted_tree_items_py=sorted_tree_itemstry:# Try to import C versionsfromdulwich._objectsimportparse_tree,sorted_tree_itemsexceptImportError:pass
Attach a Trello Card
Add a tag
Your session has expired
You are no longer logged in. Please log in and try your request again.
Filter RSS Feed
This RSS feed URL allows you to see the contents of your current filter using any feed reader.
This link includes a special authentication token. If you share the URL with anyone else, they can see this RSS feed's activity. You can disable these tokens when needed.
Your current filter is unsaved; changing it won't affect this RSS feed.