55#include "http.h"
66#include "exec_cmd.h"
77#include "run-command.h"
8+ #include "pkt-line.h"
89
910static struct remote * remote ;
1011static const char * url ;
@@ -75,21 +76,46 @@ static int set_option(const char *name, const char *value)
7576 }
7677}
7778
78- static struct ref * get_refs (void )
79+ struct discovery {
80+ const char * service ;
81+ char * buf_alloc ;
82+ char * buf ;
83+ size_t len ;
84+ unsigned proto_git : 1 ;
85+ };
86+ static struct discovery * last_discovery ;
87+
88+ static void free_discovery (struct discovery * d )
89+ {
90+ if (d ) {
91+ if (d == last_discovery )
92+ last_discovery = NULL ;
93+ free (d -> buf_alloc );
94+ free (d );
95+ }
96+ }
97+
98+ static struct discovery * discover_refs (const char * service )
7999{
80100 struct strbuf buffer = STRBUF_INIT ;
81- char * data , * start , * mid ;
82- char * ref_name ;
101+ struct discovery * last = last_discovery ;
83102 char * refs_url ;
84- int i = 0 ;
85- int http_ret ;
103+ int http_ret , is_http = 0 ;
86104
87- struct ref * refs = NULL ;
88- struct ref * ref = NULL ;
89- struct ref * last_ref = NULL ;
105+ if ( last && ! strcmp ( service , last -> service ))
106+ return last ;
107+ free_discovery ( last ) ;
90108
91- refs_url = xmalloc (strlen (url ) + 11 );
92- sprintf (refs_url , "%s/info/refs" , url );
109+ strbuf_addf (& buffer , "%s/info/refs" , url );
110+ if (!prefixcmp (url , "http://" ) || !prefixcmp (url , "https://" )) {
111+ is_http = 1 ;
112+ if (!strchr (url , '?' ))
113+ strbuf_addch (& buffer , '?' );
114+ else
115+ strbuf_addch (& buffer , '&' );
116+ strbuf_addf (& buffer , "service=%s" , service );
117+ }
118+ refs_url = strbuf_detach (& buffer , NULL );
93119
94120 init_walker ();
95121 http_ret = http_get_strbuf (refs_url , & buffer , HTTP_NO_CACHE );
@@ -104,10 +130,86 @@ static struct ref *get_refs(void)
104130 die ("HTTP request failed" );
105131 }
106132
107- data = buffer .buf ;
133+ last = xcalloc (1 , sizeof (* last_discovery ));
134+ last -> service = service ;
135+ last -> buf_alloc = strbuf_detach (& buffer , & last -> len );
136+ last -> buf = last -> buf_alloc ;
137+
138+ if (is_http && 5 <= last -> len && last -> buf [4 ] == '#' ) {
139+ /* smart HTTP response; validate that the service
140+ * pkt-line matches our request.
141+ */
142+ struct strbuf exp = STRBUF_INIT ;
143+
144+ if (packet_get_line (& buffer , & last -> buf , & last -> len ) <= 0 )
145+ die ("%s has invalid packet header" , refs_url );
146+ if (buffer .len && buffer .buf [buffer .len - 1 ] == '\n' )
147+ strbuf_setlen (& buffer , buffer .len - 1 );
148+
149+ strbuf_addf (& exp , "# service=%s" , service );
150+ if (strbuf_cmp (& exp , & buffer ))
151+ die ("invalid server response; got '%s'" , buffer .buf );
152+ strbuf_release (& exp );
153+
154+ /* The header can include additional metadata lines, up
155+ * until a packet flush marker. Ignore these now, but
156+ * in the future we might start to scan them.
157+ */
158+ strbuf_reset (& buffer );
159+ while (packet_get_line (& buffer , & last -> buf , & last -> len ) > 0 )
160+ strbuf_reset (& buffer );
161+
162+ last -> proto_git = 1 ;
163+ }
164+
165+ free (refs_url );
166+ strbuf_release (& buffer );
167+ last_discovery = last ;
168+ return last ;
169+ }
170+
171+ static int write_discovery (int fd , void * data )
172+ {
173+ struct discovery * heads = data ;
174+ int err = 0 ;
175+ if (write_in_full (fd , heads -> buf , heads -> len ) != heads -> len )
176+ err = 1 ;
177+ close (fd );
178+ return err ;
179+ }
180+
181+ static struct ref * parse_git_refs (struct discovery * heads )
182+ {
183+ struct ref * list = NULL ;
184+ struct async async ;
185+
186+ memset (& async , 0 , sizeof (async ));
187+ async .proc = write_discovery ;
188+ async .data = heads ;
189+
190+ if (start_async (& async ))
191+ die ("cannot start thread to parse advertised refs" );
192+ get_remote_heads (async .out , & list , 0 , NULL , 0 , NULL );
193+ close (async .out );
194+ if (finish_async (& async ))
195+ die ("ref parsing thread failed" );
196+ return list ;
197+ }
198+
199+ static struct ref * parse_info_refs (struct discovery * heads )
200+ {
201+ char * data , * start , * mid ;
202+ char * ref_name ;
203+ int i = 0 ;
204+
205+ struct ref * refs = NULL ;
206+ struct ref * ref = NULL ;
207+ struct ref * last_ref = NULL ;
208+
209+ data = heads -> buf ;
108210 start = NULL ;
109211 mid = data ;
110- while (i < buffer . len ) {
212+ while (i < heads -> len ) {
111213 if (!start ) {
112214 start = & data [i ];
113215 }
@@ -131,8 +233,7 @@ static struct ref *get_refs(void)
131233 i ++ ;
132234 }
133235
134- strbuf_release (& buffer );
135-
236+ init_walker ();
136237 ref = alloc_ref ("HEAD" );
137238 if (!walker -> fetch_ref (walker , ref ) &&
138239 !resolve_remote_symref (ref , refs )) {
@@ -142,11 +243,23 @@ static struct ref *get_refs(void)
142243 free (ref );
143244 }
144245
145- strbuf_release (& buffer );
146- free (refs_url );
147246 return refs ;
148247}
149248
249+ static struct ref * get_refs (int for_push )
250+ {
251+ struct discovery * heads ;
252+
253+ if (for_push )
254+ heads = discover_refs ("git-receive-pack" );
255+ else
256+ heads = discover_refs ("git-upload-pack" );
257+
258+ if (heads -> proto_git )
259+ return parse_git_refs (heads );
260+ return parse_info_refs (heads );
261+ }
262+
150263static void output_refs (struct ref * refs )
151264{
152265 struct ref * posn ;
@@ -317,7 +430,8 @@ int main(int argc, const char **argv)
317430 parse_fetch (& buf );
318431
319432 } else if (!strcmp (buf .buf , "list" ) || !prefixcmp (buf .buf , "list " )) {
320- output_refs (get_refs ());
433+ int for_push = !!strstr (buf .buf + 4 , "for-push" );
434+ output_refs (get_refs (for_push ));
321435
322436 } else if (!prefixcmp (buf .buf , "push " )) {
323437 parse_push (& buf );
0 commit comments