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.
# client.py -- Implementation of the server side git protocols# Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org># Copyright (C) 2008 John Carr## 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; either version 2# 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."""Client side support for the Git protocol.The Dulwich client supports the following capabilities: * thin-pack * multi_ack_detailed * multi_ack * side-band-64k * ofs-delta * report-status * delete-refsKnown capabilities that are not supported: * shallow * no-progress * include-tag"""__docformat__='restructuredText'fromcStringIOimportStringIOimportselectimportsocketimportsubprocessimporturllib2importurlparsefromdulwich.errorsimport(GitProtocolError,NotGitRepository,SendPackError,UpdateRefsError,)fromdulwich.protocolimport(_RBUFSIZE,PktLineParser,Protocol,TCP_GIT_PORT,ZERO_SHA,extract_capabilities,)fromdulwich.packimport(write_pack_objects,)# Python 2.6.6 included these in urlparse.uses_netloc upstream. Do# monkeypatching to enable similar behaviour in earlier Pythons:forschemein('git','git+ssh'):ifschemenotinurlparse.uses_netloc:urlparse.uses_netloc.append(scheme)def_fileno_can_read(fileno):"""Check if a file descriptor is readable.""" return len(select.select([fileno], [], [], 0)[0]) > 0
COMMON_CAPABILITIES = ['ofs-delta', 'side-band-64k']
-FETCH_CAPABILITIES = ['multi_ack', 'multi_ack_detailed'] + COMMON_CAPABILITIES
+FETCH_CAPABILITIES = ['thin-pack', 'multi_ack', 'multi_ack_detailed'] + COMMON_CAPABILITIES
SEND_CAPABILITIES = ['report-status'] + COMMON_CAPABILITIES
classReportStatusParser(object):"""Handle status as reported by servers with the 'report-status' capability. """def__init__(self):self._done=Falseself._pack_status=Noneself._ref_status_ok=Trueself._ref_statuses=[]defcheck(self):"""Check if there were any errors and, if so, raise exceptions. :raise SendPackError: Raised when the server could not unpack :raise UpdateRefsError: Raised when refs could not be updated """ifself._pack_statusnotin('unpack ok',None):raiseSendPackError(self._pack_status)ifnotself._ref_status_ok:ref_status={}ok=set()forstatusinself._ref_statuses:if' 'notinstatus:# malformed response, move on to the next onecontinuestatus,ref=status.split(' ',1)ifstatus=='ng':if' 'inref:ref,status=ref.split(' ',1)else:ok.add(ref)ref_status[ref]=statusraiseUpdateRefsError('%s failed to update'%', '.join([refforrefinref_statusifrefnotinok]),ref_status=ref_status)defhandle_packet(self,pkt):"""Handle a packet. :raise GitProtocolError: Raised when packets are received after a flush packet. """ifself._done:raiseGitProtocolError("received more data after status report")ifpktisNone:self._done=Truereturnifself._pack_statusisNone:self._pack_status=pkt.strip()else:ref_status=pkt.strip()self._ref_statuses.append(ref_status)ifnotref_status.startswith('ok '):self._ref_status_ok=False# TODO(durin42): this doesn't correctly degrade if the server doesn't# support some capabilities. This should work properly with servers# that don't support multi_ack.classGitClient(object):"""Git smart server client. """def__init__(self,thin_packs=True,report_activity=None):"""Create a new GitClient instance. :param thin_packs: Whether or not thin packs should be retrieved :param report_activity: Optional callback for reporting transport activity. """ self._report_activity = report_activity
self._fetch_capabilities = set(FETCH_CAPABILITIES)
self._send_capabilities = set(SEND_CAPABILITIES)
- if thin_packs:
- self._fetch_capabilities.add('thin-pack')
+ if not thin_packs:
+ self._fetch_capabilities.remove('thin-pack')
def _read_refs(self, proto):
server_capabilities = None
refs={}# Receive refs from serverforpktinproto.read_pkt_seq():(sha,ref)=pkt.rstrip('\n').split(' ',1)ifsha=='ERR':raiseGitProtocolError(ref)ifserver_capabilitiesisNone:(ref,server_capabilities)=extract_capabilities(ref)refs[ref]=shareturnrefs,set(server_capabilities)defsend_pack(self,path,determine_wants,generate_pack_contents,progress=None):"""Upload a pack to a remote repository. :param path: Repository path :param generate_pack_contents: Function that can return a sequence of the shas of the objects to upload. :param progress: Optional progress function :raises SendPackError: if server rejects the pack data :raises UpdateRefsError: if the server supports report-status and rejects ref updates """raiseNotImplementedError(self.send_pack)deffetch(self,path,target,determine_wants=None,progress=None):"""Fetch into a target repository. :param path: Path to fetch from :param target: Target repository to fetch into :param determine_wants: Optional function to determine what refs to fetch :param progress: Optional progress function :return: remote refs as dictionary """ifdetermine_wantsisNone:determine_wants=target.object_store.determine_wants_allf,commit=target.object_store.add_pack()try:returnself.fetch_pack(path,determine_wants,target.get_graph_walker(),f.write,progress)finally:commit()deffetch_pack(self,path,determine_wants,graph_walker,pack_data,progress=None):"""Retrieve a pack from a git smart server. :param determine_wants: Callback that returns list of commits to fetch :param graph_walker: Object with next() and ack(). :param pack_data: Callback called for each bit of data in the pack :param progress: Callback for progress reports (strings) """raiseNotImplementedError(self.fetch_pack)def_parse_status_report(self,proto):unpack=proto.read_pkt_line().strip()ifunpack!='unpack ok':st=True# flush remaining error datawhilestisnotNone:st=proto.read_pkt_line()raiseSendPackError(unpack)statuses=[]errs=Falseref_status=proto.read_pkt_line()whileref_status:ref_status=ref_status.strip()statuses.append(ref_status)ifnotref_status.startswith('ok '):errs=Trueref_status=proto.read_pkt_line()iferrs:ref_status={}ok=set()forstatusinstatuses:if' 'notinstatus:# malformed response, move on to the next onecontinuestatus,ref=status.split(' ',1)ifstatus=='ng':if' 'inref:ref,status=ref.split(' ',1)else:ok.add(ref)ref_status[ref]=statusraiseUpdateRefsError('%s failed to update'%', '.join([refforrefinref_statusifrefnotinok]),ref_status=ref_status)def_read_side_band64k_data(self,proto,channel_callbacks):"""Read per-channel data. This requires the side-band-64k capability. :param proto: Protocol object to read from :param channel_callbacks: Dictionary mapping channels to packet handlers to use. None for a callback discards channel data. """forpktinproto.read_pkt_seq():channel=ord(pkt[0])pkt=pkt[1:]try:cb=channel_callbacks[channel]exceptKeyError:raiseAssertionError('Invalid sideband channel %d'%channel)else:ifcbisnotNone:cb(pkt)def_handle_receive_pack_head(self,proto,capabilities,old_refs,new_refs):"""Handle the head of a 'git-receive-pack' request. :param proto: Protocol object to read from :param capabilities: List of negotiated capabilities :param old_refs: Old refs, as received from the server :param new_refs: New refs :return: (have, want) tuple """want=[]have=[xforxinold_refs.values()ifnotx==ZERO_SHA]sent_capabilities=Falseforrefnameinset(new_refs.keys()+old_refs.keys()):old_sha1=old_refs.get(refname,ZERO_SHA)new_sha1=new_refs.get(refname,ZERO_SHA)ifold_sha1!=new_sha1:ifsent_capabilities:proto.write_pkt_line('%s%s%s'%(old_sha1,new_sha1,refname))else:proto.write_pkt_line('%s%s%s\0%s'%(old_sha1,new_sha1,refname,' '.join(capabilities)))sent_capabilities=Trueifnew_sha1notinhaveandnew_sha1!=ZERO_SHA:want.append(new_sha1)proto.write_pkt_line(None)return(have,want)def_handle_receive_pack_tail(self,proto,capabilities,progress=None):"""Handle the tail of a 'git-receive-pack' request. :param proto: Protocol object to read from :param capabilities: List of negotiated capabilities :param progress: Optional progress reporting function """if'report-status'incapabilities:report_status_parser=ReportStatusParser()else:report_status_parser=Noneif"side-band-64k"incapabilities:ifprogressisNone:progress=lambdax:Nonechannel_callbacks={2:progress}if'report-status'incapabilities:channel_callbacks[1]=PktLineParser(report_status_parser.handle_packet).parseself._read_side_band64k_data(proto,channel_callbacks)else:if'report-status'incapabilities:forpktinproto.read_pkt_seq():report_status_parser.handle_packet(pkt)ifreport_status_parserisnotNone:report_status_parser.check()# wait for EOF before returningdata=proto.read()ifdata:raiseSendPackError('Unexpected response %r'%data)def_handle_upload_pack_head(self,proto,capabilities,graph_walker,wants,can_read):"""Handle the head of a 'git-upload-pack' request. :param proto: Protocol object to read from :param capabilities: List of negotiated capabilities :param graph_walker: GraphWalker instance to call .ack() on :param wants: List of commits to fetch :param can_read: function that returns a boolean that indicates whether there is extra graph data to read on proto """assertisinstance(wants,list)andtype(wants[0])==strproto.write_pkt_line('want %s%s\n'%(wants[0],' '.join(capabilities)))forwantinwants[1:]:proto.write_pkt_line('want %s\n'%want)proto.write_pkt_line(None)have=graph_walker.next()whilehave:proto.write_pkt_line('have %s\n'%have)ifcan_read():pkt=proto.read_pkt_line()parts=pkt.rstrip('\n').split(' ')ifparts[0]=='ACK':graph_walker.ack(parts[1])ifparts[2]in('continue','common'):passelifparts[2]=='ready':breakelse:raiseAssertionError("%s not in ('continue', 'ready', 'common)"%parts[2])have=graph_walker.next()proto.write_pkt_line('done\n')def_handle_upload_pack_tail(self,proto,capabilities,graph_walker,pack_data,progress=None,rbufsize=_RBUFSIZE):"""Handle the tail of a 'git-upload-pack' request. :param proto: Protocol object to read from :param capabilities: List of negotiated capabilities :param graph_walker: GraphWalker instance to call .ack() on :param pack_data: Function to call with pack data :param progress: Optional progress reporting function :param rbufsize: Read buffer size """pkt=proto.read_pkt_line()whilepkt:parts=pkt.rstrip('\n').split(' ')ifparts[0]=='ACK':graph_walker.ack(pkt.split(' ')[1])iflen(parts)<3orparts[2]notin('ready','continue','common'):breakpkt=proto.read_pkt_line()if"side-band-64k"incapabilities:ifprogressisNone:# Just ignore progress dataprogress=lambdax:Noneself._read_side_band64k_data(proto,{1:pack_data,2:progress})# wait for EOF before returningdata=proto.read()ifdata:raiseException('Unexpected response %r'%data)else:whileTrue:data=self.read(rbufsize)ifdata=="":breakpack_data(data)classTraditionalGitClient(GitClient):"""Traditional Git client."""def_connect(self,cmd,path):"""Create a connection to the server. This method is abstract - concrete implementations should implement their own variant which connects to the server and returns an initialized Protocol object with the service ready for use and a can_read function which may be used to see if reads would block. :param cmd: The git service name to which we should connect. :param path: The path we should pass to the service. """raiseNotImplementedError()defsend_pack(self,path,determine_wants,generate_pack_contents,progress=None):"""Upload a pack to a remote repository. :param path: Repository path :param generate_pack_contents: Function that can return a sequence of the shas of the objects to upload. :param progress: Optional callback called with progress updates :raises SendPackError: if server rejects the pack data :raises UpdateRefsError: if the server supports report-status and rejects ref updates """proto,unused_can_read=self._connect('receive-pack',path)old_refs,server_capabilities=self._read_refs(proto)negotiated_capabilities=self._send_capabilities&server_capabilitiestry:new_refs=determine_wants(dict(old_refs))except:proto.write_pkt_line(None)raiseifnew_refsisNone:proto.write_pkt_line(None)returnold_refs(have,want)=self._handle_receive_pack_head(proto,negotiated_capabilities,old_refs,new_refs)ifnotwantandold_refs==new_refs:returnnew_refsobjects=generate_pack_contents(have,want)iflen(objects)>0:entries,sha=write_pack_objects(proto.write_file(),objects)self._handle_receive_pack_tail(proto,negotiated_capabilities,progress)returnnew_refsdeffetch_pack(self,path,determine_wants,graph_walker,pack_data,progress=None):"""Retrieve a pack from a git smart server. :param determine_wants: Callback that returns list of commits to fetch :param graph_walker: Object with next() and ack(). :param pack_data: Callback called for each bit of data in the pack :param progress: Callback for progress reports (strings) """proto,can_read=self._connect('upload-pack',path)refs,server_capabilities=self._read_refs(proto)negotiated_capabilities=self._fetch_capabilities&server_capabilitiestry:wants=determine_wants(refs)except:proto.write_pkt_line(None)raiseifwantsisnotNone:wants=[cidforcidinwantsifcid!=ZERO_SHA]ifnotwants:proto.write_pkt_line(None)returnrefsself._handle_upload_pack_head(proto,negotiated_capabilities,graph_walker,wants,can_read)self._handle_upload_pack_tail(proto,negotiated_capabilities,graph_walker,pack_data,progress)returnrefsdefarchive(self,path,committish,write_data,progress=None):proto,can_read=self._connect('upload-archive',path)proto.write_pkt_line("argument %s"%committish)proto.write_pkt_line(None)pkt=proto.read_pkt_line()ifpkt=="NACK\n":returnelifpkt=="ACK\n":passelifpkt.startswith("ERR "):raiseGitProtocolError(pkt[4:].rstrip("\n"))else:raiseAssertionError("invalid response %r"%pkt)ret=proto.read_pkt_line()ifretisnotNone:raiseAssertionError("expected pkt tail")self._read_side_band64k_data(proto,{1:write_data,2:progress})classTCPGitClient(TraditionalGitClient):"""A Git Client that works over TCP directly (i.e. git://)."""def__init__(self,host,port=None,*args,**kwargs):ifportisNone:port=TCP_GIT_PORTself._host=hostself._port=portTraditionalGitClient.__init__(self,*args,**kwargs)def_connect(self,cmd,path):sockaddrs=socket.getaddrinfo(self._host,self._port,socket.AF_UNSPEC,socket.SOCK_STREAM)s=Noneerr=socket.error("no address found for %s"%self._host)for(family,socktype,proto,canonname,sockaddr)insockaddrs:s=socket.socket(family,socktype,proto)s.setsockopt(socket.IPPROTO_TCP,socket.TCP_NODELAY,1)try:s.connect(sockaddr)breakexceptsocket.error,err:ifsisnotNone:s.close()s=NoneifsisNone:raiseerr# -1 means system default bufferingrfile=s.makefile('rb',-1)# 0 means unbufferedwfile=s.makefile('wb',0)proto=Protocol(rfile.read,wfile.write,report_activity=self._report_activity)ifpath.startswith("/~"):path=path[1:]proto.send_cmd('git-%s'%cmd,path,'host=%s'%self._host)returnproto,lambda:_fileno_can_read(s)classSubprocessWrapper(object):"""A socket-like object that talks to a subprocess via pipes."""def__init__(self,proc):self.proc=procself.read=proc.stdout.readself.write=proc.stdin.writedefcan_read(self):ifsubprocess.mswindows:frommsvcrtimportget_osfhandlefromwin32pipeimportPeekNamedPipehandle=get_osfhandle(self.proc.stdout.fileno())returnPeekNamedPipe(handle,0)[2]!=0else:return_fileno_can_read(self.proc.stdout.fileno())defclose(self):self.proc.stdin.close()self.proc.stdout.close()self.proc.wait()classSubprocessGitClient(TraditionalGitClient):"""Git client that talks to a server using a subprocess."""def__init__(self,*args,**kwargs):self._connection=Noneself._stderr=Noneself._stderr=kwargs.get('stderr')if'stderr'inkwargs:delkwargs['stderr']TraditionalGitClient.__init__(self,*args,**kwargs)def_connect(self,service,path):importsubprocessargv=['git',service,path]p=SubprocessWrapper(subprocess.Popen(argv,bufsize=0,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=self._stderr))returnProtocol(p.read,p.write,report_activity=self._report_activity),p.can_readclassSSHVendor(object):defconnect_ssh(self,host,command,username=None,port=None):importsubprocess#FIXME: This has no way to deal with passwords..args=['ssh','-x']ifportisnotNone:args.extend(['-p',str(port)])ifusernameisnotNone:host='%s@%s'%(username,host)args.append(host)proc=subprocess.Popen(args+command,stdin=subprocess.PIPE,stdout=subprocess.PIPE)returnSubprocessWrapper(proc)# Can be overridden by usersget_ssh_vendor=SSHVendorclassSSHGitClient(TraditionalGitClient):def__init__(self,host,port=None,username=None,*args,**kwargs):self.host=hostself.port=portself.username=usernameTraditionalGitClient.__init__(self,*args,**kwargs)self.alternative_paths={}def_get_cmd_path(self,cmd):returnself.alternative_paths.get(cmd,'git-%s'%cmd)def_connect(self,cmd,path):con=get_ssh_vendor().connect_ssh(self.host,["%s '%s'"%(self._get_cmd_path(cmd),path)],port=self.port,username=self.username)return(Protocol(con.read,con.write,report_activity=self._report_activity),con.can_read)classHttpGitClient(GitClient):def__init__(self,base_url,dumb=None,*args,**kwargs):self.base_url=base_url.rstrip("/")+"/"self.dumb=dumbGitClient.__init__(self,*args,**kwargs)def_get_url(self,path):returnurlparse.urljoin(self.base_url,path).rstrip("/")+"/"def_perform(self,req):"""Perform a HTTP request. This is provided so subclasses can provide their own version. :param req: urllib2.Request instance :return: matching response """returnurllib2.urlopen(req)def_discover_references(self,service,url):asserturl[-1]=="/"url=urlparse.urljoin(url,"info/refs")headers={}ifself.dumb!=False:url+="?service=%s"%serviceheaders["Content-Type"]="application/x-%s-request"%servicereq=urllib2.Request(url,headers=headers)resp=self._perform(req)ifresp.getcode()==404:raiseNotGitRepository()ifresp.getcode()!=200:raiseGitProtocolError("unexpected http response %d"%resp.getcode())self.dumb=(notresp.info().gettype().startswith("application/x-git-"))proto=Protocol(resp.read,None)ifnotself.dumb:# The first line should mention the servicepkts=list(proto.read_pkt_seq())ifpkts!=[('# service=%s\n'%service)]:raiseGitProtocolError("unexpected first line %r from smart server"%pkts)returnself._read_refs(proto)def_smart_request(self,service,url,data):asserturl[-1]=="/"url=urlparse.urljoin(url,service)req=urllib2.Request(url,headers={"Content-Type":"application/x-%s-request"%service},data=data)resp=self._perform(req)ifresp.getcode()==404:raiseNotGitRepository()ifresp.getcode()!=200:raiseGitProtocolError("Invalid HTTP response from server: %d"%resp.getcode())ifresp.info().gettype()!=("application/x-%s-result"%service):raiseGitProtocolError("Invalid content-type from server: %s"%resp.info().gettype())returnrespdefsend_pack(self,path,determine_wants,generate_pack_contents,progress=None):"""Upload a pack to a remote repository. :param path: Repository path :param generate_pack_contents: Function that can return a sequence of the shas of the objects to upload. :param progress: Optional progress function :raises SendPackError: if server rejects the pack data :raises UpdateRefsError: if the server supports report-status and rejects ref updates """url=self._get_url(path)old_refs,server_capabilities=self._discover_references("git-receive-pack",url)negotiated_capabilities=self._send_capabilities&server_capabilitiesnew_refs=determine_wants(dict(old_refs))ifnew_refsisNone:returnold_refsifself.dumb:raiseNotImplementedError(self.fetch_pack)req_data=StringIO()req_proto=Protocol(None,req_data.write)(have,want)=self._handle_receive_pack_head(req_proto,negotiated_capabilities,old_refs,new_refs)ifnotwantandold_refs==new_refs:returnnew_refsobjects=generate_pack_contents(have,want)iflen(objects)>0:entries,sha=write_pack_objects(req_proto.write_file(),objects)resp=self._smart_request("git-receive-pack",url,data=req_data.getvalue())resp_proto=Protocol(resp.read,None)self._handle_receive_pack_tail(resp_proto,negotiated_capabilities,progress)returnnew_refsdeffetch_pack(self,path,determine_wants,graph_walker,pack_data,progress=None):"""Retrieve a pack from a git smart server. :param determine_wants: Callback that returns list of commits to fetch :param graph_walker: Object with next() and ack(). :param pack_data: Callback called for each bit of data in the pack :param progress: Callback for progress reports (strings) :return: Dictionary with the refs of the remote repository """url=self._get_url(path)refs,server_capabilities=self._discover_references("git-upload-pack",url)negotiated_capabilities=server_capabilitieswants=determine_wants(refs)ifwantsisnotNone:wants=[cidforcidinwantsifcid!=ZERO_SHA]ifnotwants:returnrefsifself.dumb:raiseNotImplementedError(self.send_pack)req_data=StringIO()req_proto=Protocol(None,req_data.write)self._handle_upload_pack_head(req_proto,negotiated_capabilities,graph_walker,wants,lambda:False)resp=self._smart_request("git-upload-pack",url,data=req_data.getvalue())resp_proto=Protocol(resp.read,None)self._handle_upload_pack_tail(resp_proto,negotiated_capabilities,graph_walker,pack_data,progress)returnrefsdefget_transport_and_path(uri,**kwargs):"""Obtain a git client from a URI or path. :param uri: URI or path :param thin_packs: Whether or not thin packs should be retrieved :param report_activity: Optional callback for reporting transport activity. :return: Tuple with client instance and relative path. """parsed=urlparse.urlparse(uri)ifparsed.scheme=='git':return(TCPGitClient(parsed.hostname,port=parsed.port,**kwargs),parsed.path)elifparsed.scheme=='git+ssh':returnSSHGitClient(parsed.hostname,port=parsed.port,username=parsed.username,**kwargs),parsed.pathelifparsed.schemein('http','https'):returnHttpGitClient(urlparse.urlunparse(parsed),**kwargs),parsed.pathifparsed.schemeandnotparsed.netloc:# SSH with no user@, zero or one leading slash.returnSSHGitClient(parsed.scheme,**kwargs),parsed.pathelifparsed.scheme:raiseValueError('Unknown git protocol scheme: %s'%parsed.scheme)elif'@'inparsed.pathand':'inparsed.path:# SSH with user@host:foo.user_host,path=parsed.path.split(':')user,host=user_host.rsplit('@')returnSSHGitClient(host,username=user,**kwargs),path# Otherwise, assume it's a local path.returnSubprocessGitClient(**kwargs),uri
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.