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.
Protocol does not have a recv attritbue. This causes dul-receive-pack to throw an error.
Fixes a bug where an AttributeError is thrown when dul-receive-pack is run. This is caused due to the fact that the default protocol used is the Protocol class, which does not have recv defined.
# server.py -- Implementation of the server side git protocols# Copyright (C) 2008 John Carr <john.carr@unrouted.co.uk>## 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# or (at your option) any 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."""Git smart network protocol server implementation.For more detailed implementation on the network protocol, see theDocumentation/technical directory in the cgit distribution, and in particular:* Documentation/technical/protocol-capabilities.txt* Documentation/technical/pack-protocol.txtCurrently supported capabilities: * include-tag * thin-pack * multi_ack_detailed * multi_ack * side-band-64k * ofs-delta * no-progress * report-status * delete-refsKnown capabilities that are not supported: * shallow (http://pad.lv/909524)"""importcollectionsimportosimportsocketimportSocketServerimportsysimportzlibfromdulwich.errorsimport(ApplyDeltaError,ChecksumMismatch,GitProtocolError,NotGitRepository,UnexpectedCommandError,ObjectFormatException,)fromdulwichimportlog_utilsfromdulwich.objectsimport(hex_to_sha,)fromdulwich.packimport(write_pack_objects,)fromdulwich.protocolimport(BufferedPktLineWriter,MULTI_ACK,MULTI_ACK_DETAILED,Protocol,ProtocolFile,ReceivableProtocol,SINGLE_ACK,TCP_GIT_PORT,ZERO_SHA,ack_type,extract_capabilities,extract_want_line_capabilities,)fromdulwich.repoimport(Repo,)logger=log_utils.getLogger(__name__)classBackend(object):"""A backend for the Git smart server implementation."""defopen_repository(self,path):"""Open the repository at a path. :param path: Path to the repository :raise NotGitRepository: no git repository was found at path :return: Instance of BackendRepo """raiseNotImplementedError(self.open_repository)classBackendRepo(object):"""Repository abstraction used by the Git server. Please note that the methods required here are a subset of those provided by dulwich.repo.Repo. """object_store=Nonerefs=Nonedefget_refs(self):""" Get all the refs in the repository :return: dict of name -> sha """raiseNotImplementedErrordefget_peeled(self,name):"""Return the cached peeled value of a ref, if available. :param name: Name of the ref to peel :return: The peeled value of the ref. If the ref is known not point to a tag, this will be the SHA the ref refers to. If no cached information about a tag is available, this method may return None, but it should attempt to peel the tag if possible. """returnNonedeffetch_objects(self,determine_wants,graph_walker,progress,get_tagged=None):""" Yield the objects required for a list of commits. :param progress: is a callback to send progress messages to the client :param get_tagged: Function that returns a dict of pointed-to sha -> tag sha for including tags. """raiseNotImplementedErrorclassDictBackend(Backend):"""Trivial backend that looks up Git repositories in a dictionary."""def__init__(self,repos):self.repos=reposdefopen_repository(self,path):logger.debug('Opening repository at %s',path)try:returnself.repos[path]exceptKeyError:raiseNotGitRepository("No git repository was found at %(path)s"%dict(path=path))classFileSystemBackend(Backend):"""Simple backend that looks up Git repositories in the local file system."""defopen_repository(self,path):logger.debug('opening repository at %s',path)returnRepo(path)classHandler(object):"""Smart protocol command handler base class."""def__init__(self,backend,proto,http_req=None):self.backend=backendself.proto=protoself.http_req=http_reqself._client_capabilities=None@classmethoddefcapability_line(cls):return" ".join(cls.capabilities())@classmethoddefcapabilities(cls):raiseNotImplementedError(cls.capabilities)@classmethoddefinnocuous_capabilities(cls):return("include-tag","thin-pack","no-progress","ofs-delta")@classmethoddefrequired_capabilities(cls):"""Return a list of capabilities that we require the client to have."""return[]defset_client_capabilities(self,caps):allowable_caps=set(self.innocuous_capabilities())allowable_caps.update(self.capabilities())forcapincaps:ifcapnotinallowable_caps:raiseGitProtocolError('Client asked for capability %s that ''was not advertised.'%cap)forcapinself.required_capabilities():ifcapnotincaps:raiseGitProtocolError('Client does not support required ''capability %s.'%cap)self._client_capabilities=set(caps)logger.info('Client capabilities: %s',caps)defhas_capability(self,cap):ifself._client_capabilitiesisNone:raiseGitProtocolError('Server attempted to access capability %s ''before asking client'%cap)returncapinself._client_capabilitiesclassUploadPackHandler(Handler):"""Protocol handler for uploading a pack to the server."""def__init__(self,backend,args,proto,http_req=None,advertise_refs=False):Handler.__init__(self,backend,proto,http_req=http_req)self.repo=backend.open_repository(args[0])self._graph_walker=Noneself.advertise_refs=advertise_refs@classmethoddefcapabilities(cls):return("multi_ack_detailed","multi_ack","side-band-64k","thin-pack","ofs-delta","no-progress","include-tag")@classmethoddefrequired_capabilities(cls):return("side-band-64k","thin-pack","ofs-delta")defprogress(self,message):ifself.has_capability("no-progress"):returnself.proto.write_sideband(2,message)defget_tagged(self,refs=None,repo=None):"""Get a dict of peeled values of tags to their original tag shas. :param refs: dict of refname -> sha of possible tags; defaults to all of the backend's refs. :param repo: optional Repo instance for getting peeled refs; defaults to the backend's repo, if available :return: dict of peeled_sha -> tag_sha, where tag_sha is the sha of a tag whose peeled value is peeled_sha. """ifnotself.has_capability("include-tag"):return{}ifrefsisNone:refs=self.repo.get_refs()ifrepoisNone:repo=getattr(self.repo,"repo",None)ifrepoisNone:# Bail if we don't have a Repo available; this is ok since# clients must be able to handle if the server doesn't include# all relevant tags.# TODO: fix behavior when missingreturn{}tagged={}forname,shainrefs.iteritems():peeled_sha=repo.get_peeled(name)ifpeeled_sha!=sha:tagged[peeled_sha]=shareturntaggeddefhandle(self):write=lambdax:self.proto.write_sideband(1,x)graph_walker=ProtocolGraphWalker(self,self.repo.object_store,self.repo.get_peeled)objects_iter=self.repo.fetch_objects(graph_walker.determine_wants,graph_walker,self.progress,get_tagged=self.get_tagged)# Did the process short-circuit (e.g. in a stateless RPC call)? Note# that the client still expects a 0-object pack in most cases.ifobjects_iterisNone:returnself.progress("dul-daemon says what\n")self.progress("counting objects: %d, done.\n"%len(objects_iter))write_pack_objects(ProtocolFile(None,write),objects_iter)self.progress("how was that, then?\n")# we are doneself.proto.write("0000")def_split_proto_line(line,allowed):"""Split a line read from the wire. :param line: The line read from the wire. :param allowed: An iterable of command names that should be allowed. Command names not listed below as possible return values will be ignored. If None, any commands from the possible return values are allowed. :return: a tuple having one of the following forms: ('want', obj_id) ('have', obj_id) ('done', None) (None, None) (for a flush-pkt) :raise UnexpectedCommandError: if the line cannot be parsed into one of the allowed return values. """ifnotline:fields=[None]else:fields=line.rstrip('\n').split(' ',1)command=fields[0]ifallowedisnotNoneandcommandnotinallowed:raiseUnexpectedCommandError(command)try:iflen(fields)==1andcommandin('done',None):return(command,None)eliflen(fields)==2andcommandin('want','have'):hex_to_sha(fields[1])returntuple(fields)except(TypeError,AssertionError),e:raiseGitProtocolError(e)raiseGitProtocolError('Received invalid line from client: %s'%line)classProtocolGraphWalker(object):"""A graph walker that knows the git protocol. As a graph walker, this class implements ack(), next(), and reset(). It also contains some base methods for interacting with the wire and walking the commit tree. The work of determining which acks to send is passed on to the implementation instance stored in _impl. The reason for this is that we do not know at object creation time what ack level the protocol requires. A call to set_ack_level() is required to set up the implementation, before any calls to next() or ack() are made. """def__init__(self,handler,object_store,get_peeled):self.handler=handlerself.store=object_storeself.get_peeled=get_peeledself.proto=handler.protoself.http_req=handler.http_reqself.advertise_refs=handler.advertise_refsself._wants=[]self._cached=Falseself._cache=[]self._cache_index=0self._impl=Nonedefdetermine_wants(self,heads):"""Determine the wants for a set of heads. The given heads are advertised to the client, who then specifies which refs he wants using 'want' lines. This portion of the protocol is the same regardless of ack type, and in fact is used to set the ack type of the ProtocolGraphWalker. :param heads: a dict of refname->SHA1 to advertise :return: a list of SHA1s requested by the client """ifnotheads:# The repo is empty, so short-circuit the whole process.self.proto.write_pkt_line(None)returnNonevalues=set(heads.itervalues())ifself.advertise_refsornotself.http_req:fori,(ref,sha)inenumerate(sorted(heads.iteritems())):line="%s%s"%(sha,ref)ifnoti:line="%s\x00%s"%(line,self.handler.capability_line())self.proto.write_pkt_line("%s\n"%line)peeled_sha=self.get_peeled(ref)ifpeeled_sha!=sha:self.proto.write_pkt_line('%s%s^{}\n'%(peeled_sha,ref))# i'm done..self.proto.write_pkt_line(None)ifself.advertise_refs:returnNone# Now client will sending want want want commandswant=self.proto.read_pkt_line()ifnotwant:return[]line,caps=extract_want_line_capabilities(want)self.handler.set_client_capabilities(caps)self.set_ack_type(ack_type(caps))allowed=('want',None)command,sha=_split_proto_line(line,allowed)want_revs=[]whilecommand!=None:ifshanotinvalues:raiseGitProtocolError('Client wants invalid object %s'%sha)want_revs.append(sha)command,sha=self.read_proto_line(allowed)self.set_wants(want_revs)ifself.http_reqandself.proto.eof():# The client may close the socket at this point, expecting a# flush-pkt from the server. We might be ready to send a packfile at# this point, so we need to explicitly short-circuit in this case.returnNonereturnwant_revsdefack(self,have_ref):returnself._impl.ack(have_ref)defreset(self):self._cached=Trueself._cache_index=0defnext(self):ifnotself._cached:ifnotself._implandself.http_req:returnNonereturnself._impl.next()self._cache_index+=1ifself._cache_index>len(self._cache):returnNonereturnself._cache[self._cache_index]defread_proto_line(self,allowed):"""Read a line from the wire. :param allowed: An iterable of command names that should be allowed. :return: A tuple of (command, value); see _split_proto_line. :raise GitProtocolError: If an error occurred reading the line. """return_split_proto_line(self.proto.read_pkt_line(),allowed)defsend_ack(self,sha,ack_type=''):ifack_type:ack_type=' %s'%ack_typeself.proto.write_pkt_line('ACK %s%s\n'%(sha,ack_type))defsend_nak(self):self.proto.write_pkt_line('NAK\n')defset_wants(self,wants):self._wants=wantsdef_is_satisfied(self,haves,want,earliest):"""Check whether a want is satisfied by a set of haves. A want, typically a branch tip, is "satisfied" only if there exists a path back from that want to one of the haves. :param haves: A set of commits we know the client has. :param want: The want to check satisfaction for. :param earliest: A timestamp beyond which the search for haves will be terminated, presumably because we're searching too far down the wrong branch. """o=self.store[want]pending=collections.deque([o])whilepending:commit=pending.popleft()ifcommit.idinhaves:returnTrueifcommit.type_name!="commit":# non-commit wants are assumed to be satisfiedcontinueforparentincommit.parents:parent_obj=self.store[parent]# TODO: handle parents with later commit times than childrenifparent_obj.commit_time>=earliest:pending.append(parent_obj)returnFalsedefall_wants_satisfied(self,haves):"""Check whether all the current wants are satisfied by a set of haves. :param haves: A set of commits we know the client has. :note: Wants are specified with set_wants rather than passed in since in the current interface they are determined outside this class. """haves=set(haves)earliest=min([self.store[h].commit_timeforhinhaves])forwantinself._wants:ifnotself._is_satisfied(haves,want,earliest):returnFalsereturnTruedefset_ack_type(self,ack_type):impl_classes={MULTI_ACK:MultiAckGraphWalkerImpl,MULTI_ACK_DETAILED:MultiAckDetailedGraphWalkerImpl,SINGLE_ACK:SingleAckGraphWalkerImpl,}self._impl=impl_classes[ack_type](self)_GRAPH_WALKER_COMMANDS=('have','done',None)classSingleAckGraphWalkerImpl(object):"""Graph walker implementation that speaks the single-ack protocol."""def__init__(self,walker):self.walker=walkerself._sent_ack=Falsedefack(self,have_ref):ifnotself._sent_ack:self.walker.send_ack(have_ref)self._sent_ack=Truedefnext(self):command,sha=self.walker.read_proto_line(_GRAPH_WALKER_COMMANDS)ifcommandin(None,'done'):ifnotself._sent_ack:self.walker.send_nak()returnNoneelifcommand=='have':returnshaclassMultiAckGraphWalkerImpl(object):"""Graph walker implementation that speaks the multi-ack protocol."""def__init__(self,walker):self.walker=walkerself._found_base=Falseself._common=[]defack(self,have_ref):self._common.append(have_ref)ifnotself._found_base:self.walker.send_ack(have_ref,'continue')ifself.walker.all_wants_satisfied(self._common):self._found_base=True# else we blind ack within nextdefnext(self):whileTrue:command,sha=self.walker.read_proto_line(_GRAPH_WALKER_COMMANDS)ifcommandisNone:self.walker.send_nak()# in multi-ack mode, a flush-pkt indicates the client wants to# flush but more have lines are still comingcontinueelifcommand=='done':# don't nak unless no common commits were found, even if not# everything is satisfiedifself._common:self.walker.send_ack(self._common[-1])else:self.walker.send_nak()returnNoneelifcommand=='have':ifself._found_base:# blind ackself.walker.send_ack(sha,'continue')returnshaclassMultiAckDetailedGraphWalkerImpl(object):"""Graph walker implementation speaking the multi-ack-detailed protocol."""def__init__(self,walker):self.walker=walkerself._found_base=Falseself._common=[]defack(self,have_ref):self._common.append(have_ref)ifnotself._found_base:self.walker.send_ack(have_ref,'common')ifself.walker.all_wants_satisfied(self._common):self._found_base=Trueself.walker.send_ack(have_ref,'ready')# else we blind ack within nextdefnext(self):whileTrue:command,sha=self.walker.read_proto_line(_GRAPH_WALKER_COMMANDS)ifcommandisNone:self.walker.send_nak()ifself.walker.http_req:returnNonecontinueelifcommand=='done':# don't nak unless no common commits were found, even if not# everything is satisfiedifself._common:self.walker.send_ack(self._common[-1])else:self.walker.send_nak()returnNoneelifcommand=='have':ifself._found_base:# blind ack; can happen if the client has more requests# inflightself.walker.send_ack(sha,'ready')returnshaclassReceivePackHandler(Handler):"""Protocol handler for downloading a pack from the client."""def__init__(self,backend,args,proto,http_req=None,advertise_refs=False):Handler.__init__(self,backend,proto,http_req=http_req)self.repo=backend.open_repository(args[0])self.advertise_refs=advertise_refs@classmethoddefcapabilities(cls):return("report-status","delete-refs","side-band-64k")def_apply_pack(self,refs):all_exceptions=(IOError,OSError,ChecksumMismatch,ApplyDeltaError,AssertionError,socket.error,zlib.error,ObjectFormatException) status = []
# TODO: more informative error messages than just the exception string
try:
- p = self.repo.object_store.add_thin_pack(self.proto.read,
- self.proto.recv)
+ recv = getattr(self.proto, "recv", None)+ p = self.repo.object_store.add_thin_pack(self.proto.read, recv)
status.append(('unpack', 'ok'))
except all_exceptions, e:
status.append(('unpack', str(e).replace('\n', '')))
# The pack may still have been moved in, but it may contain broken# objects. We trust a later GC to clean it up.foroldsha,sha,refinrefs:ref_status='ok'try:ifsha==ZERO_SHA:ifnot'delete-refs'inself.capabilities():raiseGitProtocolError('Attempted to delete refs without delete-refs ''capability.')try:delself.repo.refs[ref]exceptall_exceptions:ref_status='failed to delete'else:try:self.repo.refs[ref]=shaexceptall_exceptions:ref_status='failed to write'exceptKeyError,e:ref_status='bad ref'status.append((ref,ref_status))returnstatusdef_report_status(self,status):ifself.has_capability('side-band-64k'):writer=BufferedPktLineWriter(lambdad:self.proto.write_sideband(1,d))write=writer.writedefflush():writer.flush()self.proto.write_pkt_line(None)else:write=self.proto.write_pkt_lineflush=lambda:Noneforname,msginstatus:ifname=='unpack':write('unpack %s\n'%msg)elifmsg=='ok':write('ok %s\n'%name)else:write('ng %s%s\n'%(name,msg))write(None)flush()defhandle(self):refs=sorted(self.repo.get_refs().iteritems())ifself.advertise_refsornotself.http_req:ifrefs:self.proto.write_pkt_line("%s%s\x00%s\n"%(refs[0][1],refs[0][0],self.capability_line()))foriinrange(1,len(refs)):ref=refs[i]self.proto.write_pkt_line("%s%s\n"%(ref[1],ref[0]))else:self.proto.write_pkt_line("%s capabilities^{}\0%s"%(ZERO_SHA,self.capability_line()))self.proto.write("0000")ifself.advertise_refs:returnclient_refs=[]ref=self.proto.read_pkt_line()# if ref is none then client doesnt want to send us anything..ifrefisNone:returnref,caps=extract_capabilities(ref)self.set_client_capabilities(caps)# client will now send us a list of (oldsha, newsha, ref)whileref:client_refs.append(ref.split())ref=self.proto.read_pkt_line()# backend can now deal with this refs and read a pack using self.readstatus=self._apply_pack(client_refs)# when we have read all the pack from the client, send a status report# if the client asked for itifself.has_capability('report-status'):self._report_status(status)# Default handler classes for git services.DEFAULT_HANDLERS={'git-upload-pack':UploadPackHandler,'git-receive-pack':ReceivePackHandler,}classTCPGitRequestHandler(SocketServer.StreamRequestHandler):def__init__(self,handlers,*args,**kwargs):self.handlers=handlersSocketServer.StreamRequestHandler.__init__(self,*args,**kwargs)defhandle(self):proto=ReceivableProtocol(self.connection.recv,self.wfile.write)command,args=proto.read_cmd()logger.info('Handling %s request, args=%s',command,args)cls=self.handlers.get(command,None)ifnotcallable(cls):raiseGitProtocolError('Invalid service %s'%command)h=cls(self.server.backend,args,proto)h.handle()classTCPGitServer(SocketServer.TCPServer):allow_reuse_address=Trueserve=SocketServer.TCPServer.serve_foreverdef_make_handler(self,*args,**kwargs):returnTCPGitRequestHandler(self.handlers,*args,**kwargs)def__init__(self,backend,listen_addr,port=TCP_GIT_PORT,handlers=None):self.handlers=dict(DEFAULT_HANDLERS)ifhandlersisnotNone:self.handlers.update(handlers)self.backend=backendlogger.info('Listening for TCP connections on %s:%d',listen_addr,port)SocketServer.TCPServer.__init__(self,(listen_addr,port),self._make_handler)defverify_request(self,request,client_address):logger.info('Handling request from %s',client_address)returnTruedefhandle_error(self,request,client_address):logger.exception('Exception happened during processing of request ''from %s',client_address)defmain(argv=sys.argv):"""Entry point for starting a TCP git server."""iflen(argv)>1:gitdir=argv[1]else:gitdir='.'log_utils.default_logging_config()backend=DictBackend({'/':Repo(gitdir)})server=TCPGitServer(backend,'localhost')server.serve_forever()defserve_command(handler_cls,argv=sys.argv,backend=None,inf=sys.stdin,outf=sys.stdout):"""Serve a single command. This is mostly useful for the implementation of commands used by e.g. git+ssh. :param handler_cls: `Handler` class to use for the request :param argv: execv-style command-line arguments. Defaults to sys.argv. :param backend: `Backend` to use :param inf: File-like object to read from, defaults to standard input. :param outf: File-like object to write to, defaults to standard output. :return: Exit code for use with sys.exit. 0 on success, 1 on failure. """ifbackendisNone:backend=FileSystemBackend()defsend_fn(data):outf.write(data)outf.flush()proto=Protocol(inf.read,send_fn)handler=handler_cls(backend,argv[1:],proto)# FIXME: Catch exceptions and write a single-line summary to outf.handler.handle()return0defgenerate_info_refs(repo):"""Generate an info refs file."""refs=repo.get_refs()fornameinsorted(refs.iterkeys()):# get_refs() includes HEAD as a special case, but we don't want to# advertise itifname=='HEAD':continuesha=refs[name]o=repo.object_store[sha]ifnoto:continueyield'%s\t%s\n'%(sha,name)peeled_sha=repo.get_peeled(name)ifpeeled_sha!=sha:yield'%s\t%s^{}\n'%(peeled_sha,name)defgenerate_objects_info_packs(repo):"""Generate an index for for packs."""forpackinrepo.object_store.packs:yield'P pack-%s.pack\n'%pack.name()defupdate_server_info(repo):"""Generate server info for dumb file access. This generates info/refs and objects/info/packs, similar to "git update-server-info". """repo._put_named_file(os.path.join('info','refs'),"".join(generate_info_refs(repo)))repo._put_named_file(os.path.join('objects','info','packs'),"".join(generate_objects_info_packs(repo)))
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.