monotone

monotone Mtn Source Tree

Root/cvs_client.cc

1// copyright (C) 2005 Christof Petig <christof@petig-baender.de>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6#include <list>
7#include <iostream>
8#include <fstream>
9#include <unistd.h>
10#include <errno.h>
11#include <fcntl.h>
12#include "sanity.hh"
13#include "cvs_client.hh"
14#include <boost/lexical_cast.hpp>
15#include <netxx/stream.h>
16#include "stringtok.hh"
17
18#undef MM
19#define MM(x)
20
21void cvs_client::writestr(const std::string &s, bool flush)
22{ if (s.size()) L(F("writestr(%s") % s); // s mostly contains the \n char
23 if (!gzip_level)
24 { if (s.size() && byte_out_ticker.get())
25 (*byte_out_ticker)+=stream->write(s.c_str(),s.size());
26 return;
27 }
28 char outbuf[1024];
29 compress.next_in=(Bytef*)s.c_str();
30 compress.avail_in=s.size();
31 for (;;)
32 // the zlib headers say that avail_out is the only criterion for looping
33 { compress.next_out=(Bytef*)outbuf;
34 compress.avail_out=sizeof outbuf;
35 int err=deflate(&compress,flush?Z_SYNC_FLUSH:Z_NO_FLUSH);
36 E(err==Z_OK || err==Z_BUF_ERROR, F("deflate error %d") % err);
37 unsigned written=sizeof(outbuf)-compress.avail_out;
38 if (written && byte_out_ticker.get())
39 (*byte_out_ticker)+=stream->write(outbuf,written);
40 else break;
41 }
42}
43
44std::string cvs_client::readline()
45{ // flush
46 writestr(std::string(),true);
47
48 // read input
49 std::string result;
50 for (;;)
51 { if (inputbuffer.empty()) underflow();
52 E(!inputbuffer.empty(),F("no data avail"));
53 std::string::size_type eol=inputbuffer.find('\n');
54 if (eol==std::string::npos)
55 { result+=inputbuffer;
56 inputbuffer.clear();
57 }
58 else
59 { result+=inputbuffer.substr(0,eol);
60 inputbuffer.erase(0,eol+1);
61 L(F("readline result '%s'\n") % result);
62 return result;
63 }
64 }
65}
66
67std::string cvs_client::read_n(unsigned len)
68{ // no flush necessary
69 std::string result;
70 while (len)
71 { if (inputbuffer.empty()) underflow();
72 I(!inputbuffer.empty());
73 unsigned avail=inputbuffer.size();
74 if (len<avail) avail=len;
75 result+=inputbuffer.substr(0,avail);
76 inputbuffer.erase(0,avail);
77 len-=avail;
78 }
79 return result;
80}
81
82void cvs_client::underflow()
83{ char buf[1024],buf2[1024];
84 Netxx::PipeCompatibleProbe probe;
85 probe.add(*stream, Netxx::Probe::ready_read);
86try_again:
87 Netxx::Probe::result_type res = probe.ready(Netxx::Timeout(60L),
88 Netxx::Probe::ready_read); // 60 seconds
89 E((res.second&Netxx::Probe::ready_read),F("timeout reading from CVS server"));
90 ssize_t avail_in=stream->read(buf,sizeof buf);
91 E(avail_in>0, F("read error %s") % strerror(errno));
92 if (byte_in_ticker.get())
93 (*byte_in_ticker)+=avail_in;
94 if (!gzip_level)
95 { inputbuffer+=std::string(buf,buf+avail_in);
96 return;
97 }
98 decompress.next_in=(Bytef*)buf;
99 decompress.avail_in=avail_in;
100 for (;;)
101 { decompress.next_out=(Bytef*)buf2;
102 decompress.avail_out=sizeof(buf2);
103 int err=inflate(&decompress,Z_NO_FLUSH);
104 E(err==Z_OK || err==Z_BUF_ERROR, F("inflate error %d") % err);
105 unsigned bytes_in=sizeof(buf2)-decompress.avail_out;
106 if (bytes_in) inputbuffer+=std::string(buf2,buf2+bytes_in);
107 else break;
108 }
109 if (inputbuffer.empty()) goto try_again;
110}
111
112static std::string trim(const std::string &s)
113{ std::string::size_type start=s.find_first_not_of(" ");
114 if (start==std::string::npos) return std::string();
115 std::string::size_type end=s.find_last_not_of(" ");
116 if (end==std::string::npos) end=s.size();
117 else ++end;
118 // " AA " gives start=2 end=3, so substr(2,1) is correct
119 return s.substr(start,end-start);
120}
121
122void cvs_client::SendCommand(const char *cmd,...)
123{ va_list ap;
124 va_start(ap, cmd);
125 SendCommand(cmd,ap);
126 va_end(ap);
127}
128
129void cvs_client::SendCommand(const char *cmd,va_list ap)
130{ const char *arg;
131 while ((arg=va_arg(ap,const char *)))
132 { writestr("Argument "+std::string(arg)+"\n");
133 }
134 writestr(cmd+std::string("\n"));
135}
136
137void cvs_client::SendCommand(std::string const& cmd, std::vector<std::string> const&args)
138{ for (std::vector<std::string>::const_iterator i=args.begin();i!=args.end();++i)
139 writestr("Argument "+*i+"\n");
140 writestr(cmd+"\n");
141}
142
143bool cvs_client::begins_with(const std::string &s, const std::string &sub, unsigned &len)
144{ std::string::size_type sublen=sub.size();
145 if (s.size()<sublen) return false;
146 if (!s.compare(0,sublen,sub)) { len=sublen; return true; }
147 return false;
148}
149
150bool cvs_client::begins_with(const std::string &s, const std::string &sub)
151{ std::string::size_type len=sub.size();
152 if (s.size()<len) return false;
153 return !s.compare(0,len,sub);
154}
155
156cvs_client::cvs_client(const std::string &repository, const std::string &_module,
157 std::string const &_branch, bool do_connect)
158 : byte_in_ticker(), byte_out_ticker(),
159 gzip_level(), pserver(), module(_module), branch(_branch)
160{ // parse the arguments
161 { unsigned len;
162 std::string d_arg=repository;
163 if (begins_with(d_arg,":ext:",len)) d_arg.erase(0,len);
164 else if (begins_with(d_arg,":pserver:",len))
165 { pserver=true;
166 d_arg.erase(0,len);
167 }
168 std::string::size_type at=d_arg.find('@');
169 std::string::size_type host_start=at;
170 if (at!=std::string::npos)
171 { user=d_arg.substr(0,at);
172 ++host_start;
173 }
174 else host_start=0;
175 std::string::size_type colon=d_arg.find(':',host_start);
176 std::string::size_type root_start=colon;
177 if (colon!=std::string::npos)
178 { host=d_arg.substr(host_start,colon-host_start);
179 ++root_start;
180 }
181 else root_start=0;
182 root=d_arg.substr(root_start);
183 }
184
185 memset(&compress,0,sizeof compress);
186 memset(&decompress,0,sizeof decompress);
187
188 if (do_connect) connect();
189 else if (!pserver && host.empty()) host=localhost_name();
190}
191
192std::string cvs_client::localhost_name()
193{ // get localhost's name
194 char domainname[1024];
195 *domainname=0;
196#ifdef WIN32
197 strcpy(domainname,"localhost"); // gethostname does not work here ...
198#else
199 E(!gethostname(domainname,sizeof domainname),
200 F("gethostname %s\n") % strerror(errno));
201 domainname[sizeof(domainname)-1]=0;
202#endif
203#if !defined(__sun) && !defined(WIN32)
204 unsigned len=strlen(domainname);
205 if (len && len<sizeof(domainname)-2)
206 { domainname[len]='.';
207 domainname[++len]=0;
208 }
209 E(!getdomainname(domainname+len,sizeof(domainname)-len),
210 F("getdomainname %s\n") % strerror(errno));
211 domainname[sizeof(domainname)-1]=0;
212#endif
213 L(F("localhost's name %s\n") % domainname);
214 return domainname;
215}
216
217void cvs_client::connect()
218{ byte_in_ticker.reset(new ticker("bytes in", ">", 256));
219 byte_out_ticker.reset(new ticker("bytes out", "<", 256));
220
221 memset(&compress,0,sizeof compress);
222 memset(&decompress,0,sizeof decompress);
223
224 if (pserver)
225 { Netxx::Address addr(host.c_str(), 2401);
226 stream=boost::shared_ptr<Netxx::StreamBase>(new Netxx::Stream(addr, Netxx::Timeout(30)));
227
228 writestr("BEGIN AUTH REQUEST\n");
229 writestr(root+"\n");
230 writestr(user+"\n");
231 writestr(pserver_password(":pserver:"+user+"@"+host+":"+root)+"\n");
232 writestr("END AUTH REQUEST\n");
233 std::string answer=readline();
234 E(answer=="I LOVE YOU", F("pserver Authentification failed\n"));
235 }
236 else // rsh
237 { std::string local_name=localhost_name();
238
239 if (host==local_name) host="";
240
241 std::string cmd;
242 std::vector<std::string> args;
243 if (host.empty())
244 { const char *cvs_client_log=getenv("CVS_CLIENT_LOG");
245 if (!cvs_client_log)
246 { cmd="cvs";
247 args.push_back("server");
248 }
249 else // ugly hack :-(
250 { cmd="sh";
251 args.push_back("-c");
252 args.push_back(std::string("tee \"")+cvs_client_log+".in"+
253 "\" | cvs server | tee \""+cvs_client_log+".out\"");
254 }
255 }
256 else
257 { const char *rsh=getenv("CVS_RSH");
258 if (!rsh) rsh="rsh";
259 cmd=rsh;
260 if (!user.empty())
261 { args.push_back("-l");
262 args.push_back(user);
263 }
264 args.push_back(host);
265 args.push_back("cvs server");
266 }
267 if (host.empty()) host=local_name;
268 L(F("spawning pipe to '%s' ") % cmd);
269 for (std::vector<std::string>::const_iterator i=args.begin();i!=args.end();++i)
270 L(F("'%s' ") % *i);
271 L(F("\n"));
272 stream=boost::shared_ptr<Netxx::StreamBase>(new Netxx::PipeStream(cmd,args));
273 }
274
275 InitZipStream(0);
276 writestr("Root "+root+"\n");
277 writestr("Valid-responses ok error Valid-requests Checked-in "
278 "New-entry Checksum Copy-file Updated Created Update-existing "
279 "Merged Patched Rcs-diff Mode Mod-time Removed Remove-entry "
280 "Set-static-directory Clear-static-directory Set-sticky "
281 "Clear-sticky Template Clear-template Notified Module-expansion "
282 "Wrapper-rcsOption M Mbinary E F MT\n");
283
284 writestr("valid-requests\n");
285 std::string answer=readline();
286 MM(answer);
287 E(begins_with(answer,"Valid-requests "),
288 F("CVS server answered '%s' to Valid-requests\n") % answer);
289 // boost::tokenizer does not provide the needed functionality (e.g. preserve -)
290 push_back2insert<stringset_t> adaptor(Valid_requests);
291 stringtok(adaptor,answer.substr(15));
292 answer=readline();
293 E(answer=="ok",
294 F("CVS server did not answer ok to Valid-requests: %s\n") % answer);
295
296 I(CommandValid("UseUnchanged"));
297 writestr("UseUnchanged\n");
298
299 writestr("Global_option -q\n"); // -Q?
300}
301
302void cvs_client::drop_connection()
303{ byte_in_ticker.reset();
304 byte_out_ticker.reset();
305 deflateEnd(&compress);
306 inflateEnd(&decompress);
307 gzip_level=0;
308 stream.reset();
309}
310
311cvs_client::~cvs_client()
312{ drop_connection();
313}
314
315void cvs_client::reconnect()
316{ drop_connection();
317 connect();
318}
319
320void cvs_client::InitZipStream(int level)
321{ int error=deflateInit(&compress,level);
322 E(error==Z_OK,F("deflateInit %d\n") % error);
323 error=inflateInit(&decompress);
324 E(error==Z_OK,F("inflateInit %d\n") % error);
325}
326
327void cvs_client::GzipStream(int level)
328{ if (!CommandValid("Gzip-stream")) return;
329 std::string cmd="Gzip-stream ";
330 cmd+=char('0'+level);
331 cmd+='\n';
332 writestr(cmd);
333 int error=deflateParams(&compress,level,Z_DEFAULT_STRATEGY);
334 E(error==Z_OK,F("deflateParams %d\n") % error);
335 gzip_level=level;
336}
337
338bool cvs_client::fetch_result(std::string &result)
339{ std::vector<std::pair<std::string,std::string> > res;
340 if (!fetch_result(res) || res.empty()) return false;
341 result=combine_result(res);
342 return true;
343}
344
345std::string cvs_client::combine_result(const std::vector<std::pair<std::string,std::string> > &res)
346{ if (res.empty()) return std::string();
347 // optimized for the single entry case
348 std::vector<std::pair<std::string,std::string> >::const_iterator i=res.begin();
349 std::string result=i->second;
350 for (++i;i!=res.end();++i) result+=i->second;
351 return result;
352}
353
354bool cvs_client::fetch_result(std::vector<std::pair<std::string,std::string> > &result)
355{ result.clear();
356 std::list<std::string> active_tags;
357loop:
358 std::string x=readline();
359 MM(x);
360 unsigned len=0;
361 if (x=="F" || x=="F ")
362 { // flush stderr
363 goto loop;
364 }
365 if (x.size()<2) goto error;
366 if (begins_with(x,"E ",len))
367 { W(F("%s\n") % x.substr(len));
368 goto loop;
369 }
370 if (begins_with(x,"M ",len))
371 { result.push_back(std::make_pair(std::string(),x.substr(len)));
372 return true;
373 }
374 if (active_tags.empty() && x=="MT newline") return true;
375 if (begins_with(x,"MT ",len))
376 { if (x[len]=='+')
377 { active_tags.push_back(x.substr(len+1));
378 result.push_back(std::make_pair(std::string(),x.substr(len)));
379 goto loop;
380 }
381 if (x[len]=='-')
382 { I(!active_tags.empty());
383 I(active_tags.back()==x.substr(len+1));
384 active_tags.pop_back();
385 result.push_back(std::make_pair(std::string(),x.substr(len)));
386 if (active_tags.empty()) return true;
387 goto loop;
388 }
389 std::string::size_type sep=x.find_first_of(" ",len);
390 if (sep==std::string::npos)
391 result.push_back(std::make_pair(std::string(),x.substr(len)));
392 else
393 result.push_back(std::make_pair(x.substr(len,sep-len),x.substr(sep+1)));
394 goto loop;
395 }
396 if (x=="ok") return false;
397 if (!result.empty()) goto error;
398 // more complex results
399 if (begins_with(x,"Clear-sticky ",len)
400 || begins_with(x,"Set-static-directory ",len)
401 || begins_with(x,"Clear-static-directory ",len)
402 || begins_with(x,"Clear-template ",len)
403 || begins_with(x,"Removed ",len)
404 || begins_with(x,"Remove-entry ",len))
405 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
406 result.push_back(std::make_pair("dir",x.substr(len)));
407 result.push_back(std::make_pair("rcs",readline()));
408 return true;
409 }
410 if (begins_with(x,"Template ",len))
411 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
412 result.push_back(std::make_pair("dir",x.substr(len)));
413 result.push_back(std::make_pair("path",readline()));
414 std::string length=readline();
415 result.push_back(std::make_pair("length",length));
416 result.push_back(std::make_pair("data",read_n(boost::lexical_cast<long>(length.c_str()))));
417 return true;
418 }
419 if (begins_with(x,"Mod-time ",len))
420 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
421 result.push_back(std::make_pair("date",x.substr(len)));
422 return true;
423 }
424 if (begins_with(x,"Mode ",len))
425 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
426 result.push_back(std::make_pair("mode",x.substr(len)));
427 return true;
428 }
429 if (begins_with(x,"Copy-file ",len))
430 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
431 result.push_back(std::make_pair("dir",x.substr(len)));
432 result.push_back(std::make_pair("file",readline()));
433 result.push_back(std::make_pair("new-file",readline()));
434 return true;
435 }
436 if (begins_with(x,"Checksum ",len))
437 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
438 result.push_back(std::make_pair("data",x.substr(len)));
439 return true;
440 }
441 if (begins_with(x,"Module-expansion ",len))
442 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
443 result.push_back(std::make_pair("dir",x.substr(len)));
444 return true;
445 }
446 if (begins_with(x,"Checked-in ",len))
447 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
448 result.push_back(std::make_pair("dir",x.substr(len)));
449 result.push_back(std::make_pair("rcs",readline()));
450 result.push_back(std::make_pair("new entries line",readline()));
451 return true;
452 }
453 if (begins_with(x,"Set-sticky ",len))
454 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
455 result.push_back(std::make_pair("dir",x.substr(len)));
456 result.push_back(std::make_pair("rcs",readline()));
457 result.push_back(std::make_pair("tag",readline()));
458 return true;
459 }
460 if (begins_with(x,"Created ",len) || begins_with(x,"Update-existing ",len)
461 || begins_with(x,"Rcs-diff ",len) || begins_with(x,"Merged ",len))
462 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
463 result.push_back(std::make_pair("dir",x.substr(len)));
464 result.push_back(std::make_pair("rcs",readline()));
465 result.push_back(std::make_pair("new entries line",readline()));
466 result.push_back(std::make_pair("mode",readline()));
467 std::string length=readline();
468 result.push_back(std::make_pair("length",length));
469 result.push_back(std::make_pair("data",read_n(boost::lexical_cast<long>(length.c_str()))));
470 return true;
471 }
472 if (x=="Mbinary ")
473 { result.push_back(std::make_pair("CMD",x.substr(0,len-1)));
474 std::string length=readline();
475 result.push_back(std::make_pair("length",length));
476 result.push_back(std::make_pair("data",read_n(boost::lexical_cast<long>(length.c_str()))));
477 return true;
478 }
479 if (x=="error ")
480 { result.push_back(std::make_pair("CMD","error"));
481 return true;
482 }
483
484error:
485 W(F("unhandled server response \"%s\"\n") % x);
486 exit(1);
487}
488
489static time_t timezone2time_t(const struct tm &tm, int offset_min)
490{ I(!offset_min);
491 time_t result=-1;
492#if 0 // non portable
493 result=timegm(&tm);
494#else // ugly
495 const char *tz=getenv("TZ");
496#ifdef WIN32
497 putenv("TZ=UTC");
498#else
499 setenv("TZ","",true);
500#endif
501 tzset();
502 result=mktime(const_cast<struct tm*>(&tm));
503#ifndef WIN32
504 if (tz) setenv("TZ", tz, true);
505 else unsetenv("TZ");
506 tzset();
507#endif
508#endif
509// L(F("result %ld\n") % result);
510 return result;
511}
512
513static time_t cvs111date2time_t(const std::string &t)
514{ // 2000/11/10 14:43:25
515 MM(t);
516 E(t.size()==19, F("cvs111date2time_t unknown format '%s'\n") % t);
517 I(t[4]=='/' && t[7]=='/');
518 I(t[10]==' ' && t[13]==':');
519 I(t[16]==':');
520 struct tm tm;
521 memset(&tm,0,sizeof tm);
522 tm.tm_year=boost::lexical_cast<int>(t.substr(0,4).c_str())-1900;
523 tm.tm_mon=boost::lexical_cast<int>(t.substr(5,2).c_str())-1;
524 tm.tm_mday=boost::lexical_cast<int>(t.substr(8,2).c_str());
525 tm.tm_hour=boost::lexical_cast<int>(t.substr(11,2).c_str());
526 tm.tm_min=boost::lexical_cast<int>(t.substr(14,2).c_str());
527 tm.tm_sec=boost::lexical_cast<int>(t.substr(17,2).c_str());
528 // on my debian/woody server (1.11) this is UTC ...
529 return timezone2time_t(tm,0);
530}
531
532static time_t rls_l2time_t(const std::string &t)
533{ // 2003-11-26 09:20:57 +0000
534 MM(t);
535 E(t.size()==25, F("rls_l2time_t unknown format '%s'\n") % t);
536 I(t[4]=='-' && t[7]=='-');
537 I(t[10]==' ' && t[13]==':');
538 I(t[16]==':' && t[19]==' ');
539 I(t[20]=='+' || t[20]=='-');
540 struct tm tm;
541 memset(&tm,0,sizeof tm);
542 tm.tm_year=boost::lexical_cast<int>(t.substr(0,4).c_str())-1900;
543 tm.tm_mon=boost::lexical_cast<int>(t.substr(5,2).c_str())-1;
544 tm.tm_mday=boost::lexical_cast<int>(t.substr(8,2).c_str());
545 tm.tm_hour=boost::lexical_cast<int>(t.substr(11,2).c_str());
546 tm.tm_min=boost::lexical_cast<int>(t.substr(14,2).c_str());
547 tm.tm_sec=boost::lexical_cast<int>(t.substr(17,2).c_str());
548 int dst_offs=boost::lexical_cast<int>(t.substr(20,5).c_str());
549// L(F("%d-%d-%d %d:%02d:%02d %04d") % tm.tm_year % tm.tm_mon % tm.tm_mday
550// % tm.tm_hour % tm.tm_min % tm.tm_sec % dst_offs );
551 tm.tm_isdst=0;
552 return timezone2time_t(tm,dst_offs);
553}
554
555// third format:
556// 19 Nov 1996 11:22:50 -0000
557// 4 Jun 1999 12:00:41 -0000
558// 19 Jul 2002 07:33:26 -0000
559
560// Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
561// Apr,Aug,Dec,Feb,Jan,Jul,Jun,Mar,May,Nov,Oct,Sep
562static time_t monname2month(const std::string &x)
563{ MM(x);
564 I(x.size()==3);
565 // I hope this will never get internationalized
566 if (x[0]=='O') return 10;
567 if (x[0]=='S') return 9;
568 if (x[0]=='D') return 12;
569 if (x[0]=='F') return 2;
570 if (x[0]=='N') return 11;
571 if (x[0]=='A') return x[1]=='p'?4:8;
572 if (x[0]=='M') return x[2]=='r'?3:5;
573 I(x[0]=='J');
574 return x[1]=='a'?1:(x[2]=='n'?6:7);
575 return 0;
576}
577
578static time_t mod_time2time_t(const std::string &t)
579{ std::vector<std::string> parts;
580 MM(t);
581 stringtok(parts,t);
582 I(parts.size()==5);
583 struct tm tm;
584 memset(&tm,0,sizeof tm);
585 I(parts[3][2]==':' && parts[3][5]==':');
586 I(parts[4][0]=='+' || parts[4][0]=='-');
587 tm.tm_year=boost::lexical_cast<int>(parts[2].c_str())-1900;
588 tm.tm_mon=monname2month(parts[1])-1;
589 tm.tm_mday=boost::lexical_cast<int>(parts[0].c_str());
590 tm.tm_hour=boost::lexical_cast<int>(parts[3].substr(0,2).c_str());
591 tm.tm_min=boost::lexical_cast<int>(parts[3].substr(3,2).c_str());
592 tm.tm_sec=boost::lexical_cast<int>(parts[3].substr(6,2).c_str());
593 int dst_offs=boost::lexical_cast<int>(parts[4].c_str());
594 tm.tm_isdst=0;
595 return timezone2time_t(tm,dst_offs);
596}
597
598time_t cvs_client::Entries2time_t(const std::string &t)
599{ MM(t);
600 E(t.size()==24, F("Entries2time_t unknown format '%s'\n") % t);
601 I(t[3]==' ');
602 I(t[7]==' ');
603 std::vector<std::string> parts;
604 stringtok(parts,t);
605 // stringtok is not overly well suited for this task, every single whitespace
606 // separates parts
607 if (parts.size()==6 && t[8]==' ' && parts[2].empty())
608 parts.erase(parts.begin()+2);
609 I(parts.size()==5); // || parts.size()==6);
610 struct tm tm;
611 memset(&tm,0,sizeof tm);
612 I(parts[3][2]==':' && parts[3][5]==':');
613 tm.tm_year=boost::lexical_cast<int>(parts[4].c_str())-1900;
614 tm.tm_mon=monname2month(parts[1])-1;
615 tm.tm_mday=boost::lexical_cast<int>(parts[2].c_str());
616 tm.tm_hour=boost::lexical_cast<int>(parts[3].substr(0,2).c_str());
617 tm.tm_min=boost::lexical_cast<int>(parts[3].substr(3,2).c_str());
618 tm.tm_sec=boost::lexical_cast<int>(parts[3].substr(6,2).c_str());
619 tm.tm_isdst=0;
620 // at least for me it was UTC ...
621 return timezone2time_t(tm,0);
622}
623
624std::string cvs_client::time_t2rfc822(time_t t)
625{ static const char * const months[12] =
626 {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
627 struct tm *tm=gmtime(&t);
628 I(tm);
629 // do _not_ translate this into locale format (e.g. F() )
630 return (boost::format("%02d %s %d %02d:%02d:%02d +0000")
631 % tm->tm_mday % months[tm->tm_mon] % (tm->tm_year+1900)
632 % tm->tm_hour % tm->tm_min % tm->tm_sec).str();
633}
634
635void cvs_client::Directory(const std::string &path)
636{ MM(path);
637 if (path.empty() || path==".") // ???
638 { std::map<std::string,std::string>::const_iterator i=server_dir.find("");
639 I(i!=server_dir.end());
640 writestr("Directory .\n"+i->second+"\n");
641 }
642 else
643 { std::map<std::string,std::string>::reverse_iterator i;
644 std::string path_with_slash=path+"/";
645 unsigned len=0;
646 for (i=server_dir.rbegin();i!=server_dir.rend();++i)
647 { if (begins_with(path_with_slash,i->first,len)) break;
648 }
649 I(!server_dir.empty());
650// if (i==server_dir.rend()) { --i; len=0; } // take the last one
651 I(i!=server_dir.rend());
652// if (path[len]=='/') ++len;
653 I(!i->second.empty());
654 I(i->second[i->second.size()-1]=='/');
655 std::string rcspath=i->second;
656 if (len<path.size()) rcspath+=path_with_slash.substr(len);
657 writestr("Directory "+path+"\n"+rcspath+"\n");
658 }
659}
660
661void cvs_client::RList(const rlist_callbacks &cb,bool dummy,...)
662{ primeModules();
663 { va_list ap;
664 va_start(ap,dummy);
665 SendCommand("rlist",ap);
666 va_end(ap);
667 }
668 std::vector<std::pair<std::string,std::string> > lresult;
669 enum { st_dir, st_file } state=st_dir;
670 std::string directory;
671 while (fetch_result(lresult))
672 { L(F("result %s\n") % combine_result(lresult));
673 switch(state)
674 { case st_dir:
675 { std::string result=combine_result(lresult);
676 I(result.size()>=2);
677 I(result[result.size()-1]==':');
678 directory=result.substr(0,result.size()-1);
679 state=st_file;
680 break;
681 }
682 case st_file:
683 if (lresult.empty() || lresult[0].second.empty()) state=st_dir;
684 else
685 { I(lresult.size()==3);
686 I(lresult[0].first=="text");
687 I(lresult[1].first=="date");
688 I(lresult[2].first=="text");
689 std::string keyword=trim(lresult[0].second);
690 std::string date=trim(lresult[1].second);
691 std::string version=trim(lresult[2].second.substr(1,10));
692 std::string dead=trim(lresult[2].second.substr(12,4));
693 std::string name=lresult[2].second.substr(17);
694
695 I(keyword[0]=='-' || keyword[0]=='d');
696 I(dead.empty() || dead=="dead");
697 I(!name.empty());
698
699 if (keyword=="----") keyword.clear();
700 if (keyword!="d---")
701 { //std::cerr << (directory+"/"+name) << " V"
702 // << version << " from " << date << " " << dead
703 // << " " << keyword << '\n';
704 time_t t=rls_l2time_t(date);
705 cb.file(directory+"/"+name,t,version,!dead.empty());
706 }
707 // construct manifest
708 // search for a matching revision
709 // - do that later when all files are known ???
710 }
711 break;
712 }
713 }
714}
715
716static std::string basename(const std::string &s)
717{ std::string::size_type lastslash=s.rfind("/");
718 if (lastslash==std::string::npos) return s;
719 return s.substr(lastslash+1);
720}
721
722static std::string dirname(const std::string &s)
723{ std::string::size_type lastslash=s.rfind("/");
724 if (lastslash==std::string::npos) return ".";
725 if (!lastslash) return "/";
726 return s.substr(0,lastslash);
727}
728
729void cvs_client::Log_internal(const rlog_callbacks &cb,const std::string &file,va_list ap)
730{ Directory(dirname(std::string(file)));
731 std::string bname=basename(std::string(file));
732 writestr("Entry /"+bname+"/1.1.1.1//-kb/\n");
733 writestr("Unchanged "+bname+"\n");
734 { const char *arg;
735 while ((arg=va_arg(ap,const char *)))
736 { writestr("Argument "+std::string(arg)+"\n");
737 }
738 }
739 writestr("Argument --\n"
740 "Argument "+bname+"\n"
741 "log\n");
742 processLogOutput(cb);
743}
744
745void cvs_client::Log_internal(const rlog_callbacks &cb,const std::string &file,
746 std::vector<std::string> const &args)
747{ Directory(dirname(std::string(file)));
748 std::string bname=basename(std::string(file));
749 writestr("Entry /"+bname+"/1.1.1.1//-kb/\n");
750 writestr("Unchanged "+bname+"\n");
751 for (std::vector<std::string>::const_iterator i=args.begin();i!=args.end();++i)
752 writestr("Argument "+*i+"\n");
753 writestr("Argument --\n"
754 "Argument "+bname+"\n"
755 "log\n");
756 processLogOutput(cb);
757}
758
759void cvs_client::Log(const rlog_callbacks &cb,const char *file,...)
760{ primeModules();
761 va_list ap,ap2;
762 va_start(ap,file);
763 va_copy(ap2,ap);
764 try {
765 Log_internal(cb,file,ap);
766 } catch (...)
767 { W(F("trying to reconnect, perhaps the server is confused\n"));
768 reconnect();
769 Log_internal(cb,file,ap2);
770 }
771 va_end(ap);
772}
773
774void cvs_client::Log(const rlog_callbacks &cb,std::string const& file,
775 std::vector<std::string> const& args)
776{ primeModules();
777 try {
778 Log_internal(cb,file,args);
779 } catch (...)
780 { W(F("trying to reconnect, perhaps the server is confused\n"));
781 reconnect();
782 Log_internal(cb,file,args);
783 }
784}
785
786// dummy is needed to satisfy va_start (cannot pass objects of non-POD type)
787void cvs_client::RLog(const rlog_callbacks &cb,bool dummy,...)
788{ primeModules();
789 { va_list ap;
790 va_start(ap,dummy);
791 SendCommand("rlog",ap);
792 va_end(ap);
793 }
794 processLogOutput(cb);
795}
796
797void cvs_client::processLogOutput(const rlog_callbacks &cb)
798{
799 static const char * const fileend="=============================================================================";
800 static const char * const revisionend="----------------------------";
801 enum { st_head, st_tags, st_desc, st_rev, st_msg, st_date_author
802 } state=st_head;
803 std::vector<std::pair<std::string,std::string> > lresult;
804 std::string file;
805 std::string revision,head_rev;
806 std::string message;
807 std::string author;
808 std::string description;
809 std::string dead;
810 time_t checkin_time=0;
811 while (fetch_result(lresult))
812 {reswitch:
813 L(F("state %d\n") % int(state));
814 I(!lresult.empty());
815 MM(lresult[0].first);
816 MM(lresult[0].second);
817 E(lresult[0].first!="CMD" || lresult[0].second!="error", F("log failed"));
818 switch(state)
819 { case st_head:
820 { std::string result=combine_result(lresult);
821 unsigned len;
822 if (result.empty()) break; // accept a (first) empty line
823 if (result==fileend)
824 { cb.file(file,head_rev);
825 }
826 else if (begins_with(result,"RCS file: ",len))
827 { file=rcs_file2path(result.substr(len));
828 }
829 else if (begins_with(result,"head: ",len))
830 { head_rev=result.substr(len);
831 }
832 else if (begins_with(result,"branch:") ||
833 begins_with(result,"locks: ") ||
834 begins_with(result,"access list:") ||
835 begins_with(result,"keyword substitution: ") ||
836 begins_with(result,"Working file: ") ||
837 begins_with(result,"total revisions: "))
838 ;
839 else if (result=="description:")
840 { state=st_desc;
841 description.clear();
842 }
843 else if (result=="symbolic names:")
844 state=st_tags;
845 else
846 { W(F("unknown rcs head '%s'\n") % result);
847 }
848 break;
849 }
850 case st_tags:
851 { std::string result=combine_result(lresult);
852 I(!result.empty());
853 if (result[0]!='\t')
854 { L(F("result[0] %d %d\n") % result.size() % int(result[0])); state=st_head; goto reswitch; }
855 I(result.find_first_not_of("\t ")==1);
856 std::string::size_type colon=result.find(':');
857 I(colon!=std::string::npos);
858 cb.tag(file,result.substr(1,colon-1),result.substr(colon+2));
859 break;
860 }
861 case st_desc:
862 { std::string result=combine_result(lresult);
863 if (result==revisionend)
864 { state=st_rev;
865 // cb.file(file,description);
866 }
867 else
868 { if (!description.empty()) description+='\n';
869 description+=result;
870 }
871 break;
872 }
873 case st_rev:
874 { std::string result=combine_result(lresult);
875 unsigned len=0;
876 if (!begins_with(result,"revision ",len)) // do not delete the space here
877 // it restricts accepted lines further
878 // accept ---------------------------- lines in changelogs
879 { description+=std::string(revisionend)+"\n";
880 state=st_desc;
881 goto reswitch;
882 }
883 revision=result.substr(len);
884 state=st_date_author;
885 break;
886 }
887 case st_date_author:
888 { if (lresult.size()==1) // M ... (cvs 1.11.1p1)
889 { std::string result=combine_result(lresult);
890 unsigned len=0;
891 I(begins_with(result,"date: ",len));
892 std::string::size_type authorpos=result.find("; author: ",len);
893 I(authorpos!=std::string::npos);
894 std::string::size_type authorbegin=authorpos+11;
895 std::string::size_type statepos=result.find("; state: ",authorbegin);
896 I(statepos!=std::string::npos);
897 std::string::size_type statebegin=statepos+10;
898 std::string::size_type linespos=result.find(";",statebegin);
899 // "; lines: "
900 I(linespos!=std::string::npos);
901 checkin_time=cvs111date2time_t(result.substr(len,authorpos-len));
902 author=result.substr(authorbegin,statepos-authorbegin);
903 dead=result.substr(statebegin,linespos-statebegin);
904 }
905 else // MT ... (cvs 1.12.9)
906 { I(lresult.size()==11 || lresult.size()==7);
907 I(lresult[0].first=="text");
908 I(lresult[0].second=="date: ");
909 I(lresult[1].first=="date");
910 checkin_time=rls_l2time_t(lresult[1].second);
911 I(lresult[2].first=="text");
912 I(lresult[2].second=="; author: ");
913 I(lresult[3].first=="text");
914 author=lresult[3].second;
915 I(lresult[4].first=="text");
916 I(lresult[4].second=="; state: ");
917 I(lresult[5].first=="text");
918 dead=lresult[5].second;
919 }
920 state=st_msg;
921 message.clear();
922 break;
923 }
924 case st_msg:
925 { std::string result=combine_result(lresult);
926 // evtl ├╝berpr├╝fen, ob das nicht nur ein fake war ...
927 if (result==revisionend || result==fileend)
928 { cb.revision(file,checkin_time,revision,author,dead,message);
929 if (result==fileend)
930 { state=st_head;
931 goto reswitch; // emit file cb
932 }
933 state=st_rev;
934 }
935 else
936 { if (!message.empty()) message+='\n';
937 message+=result;
938 }
939 break;
940 }
941 }
942 }
943}
944
945cvs_client::checkout cvs_client::CheckOut(const std::string &_file, const std::string &revision)
946{ primeModules();
947 std::string file=_file;
948 struct checkout result;
949 MM(file);
950 MM(revision);
951 // Directory("");
952 std::string usemodule=module;
953 { std::map<std::string,std::string>::reverse_iterator i;
954 unsigned len=0;
955 for (i=server_dir.rbegin();i!=server_dir.rend();++i)
956 { if (begins_with(file,i->first,len)) break;
957 }
958 I(i!=server_dir.rend());
959 if (!i->first.empty())
960 { usemodule=i->first;
961 if (usemodule[usemodule.size()-1]=='/')
962 usemodule.erase(usemodule.size()-1,1);
963 usemodule=basename(usemodule);
964 file.erase(0,i->first.size());
965 L(F("usemodule %s @%s %s /%s\n") % _file % i->first % usemodule % file);
966 }
967 }
968 SendCommand("co",/*"-N","-P",*/"-r",revision.c_str(),"--",(usemodule+"/"+file).c_str(),(void*)0);
969 enum { st_co
970 } state=st_co;
971 std::vector<std::pair<std::string,std::string> > lresult;
972 std::string dir,dir2,rcsfile;
973 while (fetch_result(lresult))
974 { switch(state)
975 { case st_co:
976 { I(!lresult.empty());
977 if (lresult[0].first=="CMD")
978 { E(lresult[0].second!="error", F("failed to check out %s\n") % file);
979 if (lresult[0].second=="Clear-sticky")
980 { I(lresult.size()==3);
981 I(lresult[1].first=="dir");
982 dir=lresult[1].second;
983 }
984 else if (lresult[0].second=="Set-static-directory")
985 { I(lresult.size()==3);
986 I(lresult[1].first=="dir");
987 dir2=lresult[1].second;
988 }
989 else if (lresult[0].second=="Remove-entry"
990 || lresult[0].second=="Removed")
991 { I(lresult.size()==3);
992 result.dead=true;
993 }
994 else if (lresult[0].second=="Mod-time")
995 { I(lresult.size()==2);
996 I(lresult[1].first=="date");
997 // this is 18 Nov 1996 14:39:40 -0000 format - strange ...
998 result.mod_time=mod_time2time_t(lresult[1].second);
999 }
1000 else if (lresult[0].second=="Created"
1001 || lresult[0].second=="Update-existing")
1002 // Update-existing results after crossing a dead state
1003 { // std::cerr << combine_result(lresult) << '\n';
1004 I(lresult.size()==7);
1005 I(lresult[6].first=="data");
1006 I(lresult[3].first=="new entries line");
1007 std::string new_revision;
1008 parse_entry(lresult[3].second,new_revision,result.keyword_substitution);
1009 result.mode=lresult[4].second;
1010 result.contents=lresult[6].second;
1011 L(F("file %s revision %s: %d bytes\n") % file
1012 % revision % lresult[6].second.size());
1013 }
1014 else if (lresult[0].second=="Template")
1015 { I(lresult.size()==5);
1016 I(lresult[3].first=="length");
1017 long len = boost::lexical_cast<long>(lresult[3].second.c_str());
1018 I(len >= 0);
1019 I(lresult[4].second.size() == (size_t) len);
1020 L(F("found commit template %s:\n%s") % lresult[2].second % lresult[4].second);
1021 // FIX actually do something with the template?
1022 result.committemplate = lresult[4].second;
1023 }
1024 else
1025 { W(F("CheckOut: unrecognized CMD %s\n") % lresult[0].second);
1026 }
1027 }
1028 else if (lresult[0].second=="+updated")
1029 { // std::cerr << combine_result(lresult) << '\n';
1030 }
1031 else
1032 { W(F("CheckOut: unrecognized response %s\n") % lresult[0].second);
1033 }
1034 break;
1035 }
1036 }
1037 }
1038 return result;
1039}
1040
1041std::string cvs_client::pserver_password(const std::string &root)
1042{ const char *home=getenv("HOME");
1043 if (!home) home="";
1044 std::ifstream fs((std::string(home)+"/.cvspass").c_str());
1045 while (fs.good())
1046 { char buf[1024];
1047 if (fs.getline(buf,sizeof buf).good())
1048 { std::string line=buf;
1049 if (line.substr(0,3)=="/1 ") line.erase(0,3);
1050 if (line.size()>=root.size()+2 && begins_with(line,root)
1051 && line[root.size()]==' ')
1052 return line.substr(root.size()+1);
1053 }
1054 }
1055 return "A"; // empty password
1056}
1057
1058std::string cvs_client::shorten_path(const std::string &p) const
1059{ unsigned len=0;
1060 if (cvs_client::begins_with(p,module,len))
1061 { if (p[len]=='/') ++len;
1062 return p.substr(len);
1063 }
1064 return p;
1065}
1066
1067std::string cvs_client::rcs_file2path(std::string file) const
1068{ // try to guess a sane file name (e.g. on cvs.gnome.org)
1069 for (std::map<std::string,std::string>::const_reverse_iterator i=server_dir.rbegin();
1070 i!=server_dir.rend();++i)
1071 { if (begins_with(file,i->second))
1072 { file.replace(0,i->second.size(),i->first);
1073 // remove additional slashes (e.g. sourceforge gc-linux)
1074 while (file.size()>i->first.size() && file[i->first.size()]=='/')
1075 file.erase(i->first.size(),1);
1076 break;
1077 }
1078 }
1079 if (file.size()>2 && file.substr(file.size()-2)==",v") file.erase(file.size()-2,2);
1080 std::string::size_type lastslash=file.rfind('/');
1081 if (lastslash!=std::string::npos && lastslash>=5
1082 && file.substr(lastslash-5,6)=="Attic/")
1083 file.erase(lastslash-5,6);
1084 return file;
1085}
1086
1087namespace {
1088struct store_here : cvs_client::update_callbacks
1089{ cvs_client::update &store;
1090 store_here(cvs_client::update &s) : store(s) {}
1091 virtual void operator()(const cvs_client::update &u) const
1092 { const_cast<cvs_client::update&>(store)=u;
1093 }
1094};
1095}
1096
1097cvs_client::update cvs_client::Update(const std::string &file,
1098 const std::string &old_revision, const std::string &new_revision,
1099 const std::string &keyword_expansion)
1100{
1101 struct update result;
1102 std::vector<update_args> file_revision;
1103 file_revision.push_back(update_args(file,old_revision,new_revision,keyword_expansion));
1104 Update(file_revision,store_here(result));
1105 return result;
1106}
1107
1108cvs_client::update cvs_client::Update(const std::string &file, const std::string &new_revision)
1109{
1110 struct update result;
1111 std::vector<update_args> file_revision;
1112 file_revision.push_back(update_args(file,"",new_revision,""));
1113 Update(file_revision,store_here(result));
1114 return result;
1115}
1116
1117// we have to update, status will give us only strange strings (and uses too
1118// much bandwidth?) [is too verbose]
1119void cvs_client::Update(const std::vector<update_args> &file_revisions,
1120 const update_callbacks &cb)
1121{ primeModules();
1122 struct update result;
1123 I(!file_revisions.empty());
1124 std::string olddir;
1125 for (std::vector<update_args>::const_iterator i=file_revisions.begin();
1126 i!=file_revisions.end(); ++i)
1127 { if (dirname(i->file)!=olddir)
1128 { olddir=dirname(i->file);
1129 Directory(olddir);
1130 }
1131 if (!i->old_revision.empty())
1132 { std::string bname=basename(i->file);
1133 std::string branchpart;
1134 if (!branch.empty()) branchpart="T"+branch;
1135 writestr("Entry /"+bname+"/"+i->old_revision+"//"
1136 +i->keyword_substitution+"/"+branchpart+"\n");
1137 writestr("Unchanged "+bname+"\n");
1138 }
1139 }
1140 if (file_revisions.size()==1 && !file_revisions.begin()->new_revision.empty())
1141 { std::vector<std::string> args;
1142 args.push_back("-d"); // create new directories
1143 args.push_back("-C"); // ignore previous context
1144 if (file_revisions.begin()->old_revision.empty())
1145 {
1146 if (branch.empty())
1147 args.push_back("-A");
1148 }
1149 else
1150 args.push_back("-u"); // send back diff
1151 args.push_back("-r");
1152 args.push_back(file_revisions.begin()->new_revision);
1153 args.push_back(basename(file_revisions.begin()->file));
1154 SendCommand(std::string("update"),args);
1155 }
1156 else
1157 { std::vector<std::string> args;
1158 args.push_back("-d"); // create new directories
1159 args.push_back("-C"); // ignore previous context
1160 args.push_back("-u"); // send back diff
1161 if (!branch.empty())
1162 args.push_back("-r"+branch);
1163 Directory("."); // needed for 1.11
1164 SendCommand(std::string("update"),args);
1165 }
1166 std::vector<std::pair<std::string,std::string> > lresult;
1167 std::string dir,dir2,rcsfile;
1168 enum { st_normal, st_merge } state=st_normal;
1169
1170 while (fetch_result(lresult))
1171 { I(!lresult.empty());
1172 unsigned len=0;
1173 if (lresult[0].first=="CMD")
1174 { if (lresult[0].second=="Created" || lresult[0].second=="Update-existing")
1175 { I(lresult.size()==7);
1176 I(lresult[6].first=="data");
1177 dir=lresult[1].second;
1178 result.file=lresult[2].second;
1179 I(!result.file.empty());
1180 if (result.file[0]=='/') result.file=rcs_file2path(result.file);
1181 result.contents=lresult[6].second;
1182 parse_entry(lresult[3].second,result.new_revision,result.keyword_substitution);
1183 cb(result);
1184 result=update();
1185 state=st_normal;
1186 }
1187 else if (lresult[0].second=="Rcs-diff")
1188 { I(lresult.size()==7);
1189 I(lresult[6].first=="data");
1190 dir=lresult[1].second;
1191 result.file=lresult[2].second;
1192 I(!result.file.empty());
1193 if (result.file[0]=='/') result.file=rcs_file2path(result.file);
1194 result.patch=lresult[6].second;
1195 parse_entry(lresult[3].second,result.new_revision,result.keyword_substitution);
1196 cb(result);
1197 result=update();
1198 state=st_normal;
1199 }
1200 else if (lresult[0].second=="Checksum")
1201 { I(lresult.size()==2);
1202 I(lresult[1].first=="data");
1203 result.checksum=lresult[1].second;
1204 }
1205 else if (lresult[0].second=="Removed")
1206 { I(lresult.size()==3);
1207 result.file=lresult[2].second;
1208 I(!result.file.empty());
1209 if (result.file[0]=='/') result.file=rcs_file2path(result.file);
1210 result.removed=true;
1211 cb(result);
1212 result=update();
1213 state=st_normal;
1214 }
1215 else if (lresult[0].second=="Clear-static-directory"
1216 || lresult[0].second=="Clear-template"
1217 || lresult[0].second=="Clear-sticky")
1218 {
1219 }
1220 else if (lresult[0].second=="Copy-file")
1221 { I(state==st_merge);
1222 }
1223 else if (lresult[0].second=="Mod-time")
1224 { result.mod_time=mod_time2time_t(lresult[1].second);
1225 }
1226 else if (lresult[0].second=="Merged")
1227 { I(state==st_merge);
1228 I(lresult.size()==7);
1229 I(lresult[6].first=="data");
1230 dir=lresult[1].second;
1231 result.file=lresult[2].second;
1232 I(!result.file.empty());
1233 if (result.file[0]=='/') result.file=rcs_file2path(result.file);
1234 result.contents=lresult[6].second; // strictly this is unnecessary ...
1235 parse_entry(lresult[3].second,result.new_revision,result.keyword_substitution);
1236 E(false, F("Update ->%s of %s exposed CVS bug\n")
1237 % result.new_revision % result.file);
1238 }
1239 else if (lresult[0].second=="error")
1240 { I(state==st_merge);
1241 break;
1242 }
1243 else
1244 { W(F("Update: unrecognized CMD %s\n") % lresult[0].second);
1245 }
1246 }
1247 else if (lresult[0].second=="+updated")
1248 { // std::cerr << combine_result(lresult) << '\n';
1249 state=st_normal;
1250 }
1251 else if (lresult[0].second=="P ")
1252 { // std::cerr << combine_result(lresult) << '\n';
1253 I(lresult.size()==2);
1254 I(lresult[1].first=="fname");
1255 }
1256 else if (lresult[0].second=="M ")
1257 { I(lresult.size()==2);
1258 I(lresult[1].first=="fname");
1259 state=st_merge;
1260 }
1261 else if (lresult[0].second=="? ")
1262 { I(lresult.size()==2);
1263 I(lresult[1].first=="fname");
1264 W(F("cvs erraneously reports ? %s\n") % lresult[1].second);
1265 }
1266 else if (begins_with(lresult[0].second,"RCS file: ",len))
1267 { I(state==st_normal);
1268 state=st_merge;
1269 }
1270 else if (begins_with(lresult[0].second,"retrieving revision ",len))
1271 { I(state==st_merge);
1272 }
1273 else if (begins_with(lresult[0].second,"Merging ",len))
1274 { I(state==st_merge);
1275 }
1276 else if (begins_with(lresult[0].second,"C ",len))
1277 { state=st_merge;
1278 I(lresult.size()==2);
1279 I(lresult[1].first=="fname");
1280 }
1281 else
1282 { W(F("Update: unrecognized response %s\n") % lresult[0].second);
1283 }
1284 }
1285}
1286
1287void cvs_client::parse_entry(const std::string &line, std::string &new_revision,
1288 std::string &keyword_substitution)
1289{
1290 MM(line);
1291 std::vector<std::string> parts;
1292 stringtok(parts,line,"/");
1293 // empty last part will not get created
1294 if (parts.size()==5) parts.push_back(std::string());
1295 I(parts.size()==6);
1296 new_revision=parts[2];
1297 keyword_substitution=parts[4];
1298}
1299
1300std::map<std::string,std::pair<std::string,std::string> >
1301 cvs_client::Commit(const std::string &changelog, time_t when,
1302 const std::vector<commit_arg> &commits)
1303{ primeModules();
1304 std::string olddir;
1305 I(!commits.empty());
1306// undocumented ...
1307// if (CommandValid("Command-prep")) writestr("Command-prep commit\n");
1308 for (std::vector<commit_arg>::const_iterator i=commits.begin();
1309 i!=commits.end(); ++i)
1310 { if (dirname(i->file)!=olddir)
1311 { olddir=dirname(i->file);
1312 Directory(olddir);
1313 }
1314 std::string bname=basename(i->file);
1315 std::string branchpart;
1316 if (!branch.empty()) branchpart="T"+branch;
1317 writestr("Entry /"+bname+"/"+(i->removed?"-":"")
1318 +i->old_revision+"//"+i->keyword_substitution+"/"
1319 +branchpart+"\n");
1320 if (!i->removed)
1321 { writestr("Checkin-time "+time_t2rfc822(when)+"\n");
1322 writestr("Modified "+bname+"\n");
1323 writestr("u=rw,g=r,o=r\n"); // standard mode
1324 // do _not_ translate this into locale format (e.g. F() )
1325 writestr(boost::lexical_cast<std::string>(i->new_content.size())+"\n");
1326 writestr(i->new_content);
1327 }
1328 }
1329 Directory(".");
1330 writestr("Argument -m\n");
1331 SendArgument(changelog);
1332 writestr("Argument --\n");
1333 for (std::vector<commit_arg>::const_iterator i=commits.begin();
1334 i!=commits.end(); ++i)
1335 writestr("Argument "+i->file+"\n");
1336 writestr("ci\n");
1337 std::map<std::string,std::pair<std::string,std::string> > result;
1338 // process result
1339 std::vector<std::pair<std::string,std::string> > lresult;
1340
1341 while (fetch_result(lresult))
1342 { I(!lresult.empty());
1343 unsigned len=0;
1344 if (lresult[0].first=="CMD")
1345 { if (lresult[0].second=="Mode")
1346 ; // who cares
1347 else if (lresult[0].second=="Checked-in")
1348 { I(lresult.size()==4);
1349 I(lresult[2].first=="rcs");
1350 I(lresult[3].first=="new entries line");
1351 std::pair<std::string,std::string> p;
1352 std::string file=lresult[2].second;
1353 I(!file.empty());
1354 if (file[0]=='/') file=rcs_file2path(file);
1355 parse_entry(lresult[3].second,p.first,p.second);
1356 result[file]=p;
1357 }
1358 else if (lresult[0].second=="Remove-entry")
1359 { I(lresult.size()==3);
1360 I(lresult[2].first=="rcs");
1361 std::string file=lresult[2].second;
1362 I(!file.empty());
1363 if (file[0]=='/') file=rcs_file2path(file);
1364 result[file]=std::make_pair(std::string(),std::string());
1365 }
1366 else if (lresult[0].second=="Mod-time")
1367 { I(lresult.size()==2);
1368 I(lresult[1].first=="date");
1369 W(F("Commit: Mod-time %s\n") % lresult[1].second);
1370 }
1371 else if (lresult[0].second=="Update-existing")
1372 // perhaps a reaction to $something$ and -kk
1373 { I(lresult.size()==7);
1374 I(lresult[6].first=="data");
1375 I(lresult[2].first=="rcs");
1376 I(lresult[3].first=="new entries line");
1377// result.mode=lresult[4].second;
1378 std::pair<std::string,std::string> p;
1379 std::string file=lresult[2].second;
1380 I(!file.empty());
1381 if (file[0]=='/') file=rcs_file2path(file);
1382 parse_entry(lresult[3].second,p.first,p.second);
1383 result[file]=p;
1384 W(F("Commit: Update-existing %s rev.%s%s (%db)\n") % file
1385 % p.first % p.second % lresult[6].second.size());
1386 }
1387 else if (lresult[0].second=="error")
1388 return std::map<std::string,std::pair<std::string,std::string> >();
1389 else
1390 { W(F("Commit: unrecognized CMD %s\n") % lresult[0].second);
1391 }
1392 }
1393 else if (lresult[0].second.empty())
1394 { I(!lresult[0].second.empty());
1395 }
1396 else if (lresult[0].second[0]=='/')
1397 // /cvsroot/test/F,v <-- F
1398 { L(F("%s\n") % lresult[0].second);
1399 }
1400 else if (begins_with(lresult[0].second,"new revision:",len)
1401 || begins_with(lresult[0].second,"initial revision:",len)
1402 || begins_with(lresult[0].second,"RCS file:",len)
1403 || begins_with(lresult[0].second,"done",len)
1404 || begins_with(lresult[0].second,"Removing ",len)
1405 || begins_with(lresult[0].second,"Checking in ",len))
1406 { L(F("%s\n") % lresult[0].second);
1407 }
1408 else
1409 { W(F("Commit: unrecognized response %s\n") % lresult[0].second);
1410 }
1411 }
1412 return result;
1413}
1414
1415void cvs_client::SendArgument(const std::string &a)
1416{ // send each line separately (Argument,[Argumentx[,...]])
1417 std::string::size_type newline=0,start=0;
1418 std::string::size_type size_of_a=a.size();
1419 while ((newline=a.find('\n',start))!=std::string::npos)
1420 { writestr("Argument"+std::string(start?"x":"")+" "+a.substr(start,newline-start)+"\n");
1421 start=newline+1;
1422 if (start==size_of_a) break;
1423 }
1424 writestr("Argument"+std::string(start?"x":"")+" "+a.substr(start)+"\n");
1425}
1426
1427std::vector<std::string> cvs_client::ExpandModules()
1428{ SendCommand("expand-modules",module.c_str(),(void*)0);
1429 std::vector<std::string> result;
1430 std::vector<std::pair<std::string,std::string> > lresult;
1431 while (fetch_result(lresult))
1432 { if (lresult.size()==1 && lresult[0].first=="CMD" && lresult[0].second=="error")
1433 E(false, F("error accessing CVS module %s\n") % module);
1434 I(lresult.size()==2);
1435 I(lresult[0].second=="Module-expansion");
1436 result.push_back(lresult[1].second);
1437 }
1438 return result;
1439}
1440
1441// if you know a more efficient way to get this, feel free to replace it
1442std::map<std::string,std::string> cvs_client::RequestServerDir()
1443{ if (server_dir.size()<=1)
1444 SendCommand("co","-l","-r9999",module.c_str(),(void*)0);
1445 else SendCommand("co","-r9999",module.c_str(),(void*)0);
1446 std::string last_local,last_rcs;
1447 std::map<std::string,std::string> result;
1448 std::vector<std::pair<std::string,std::string> > lresult;
1449 while (fetch_result(lresult))
1450 { I(!lresult.empty());
1451 I(lresult[0].first=="CMD");
1452 if (lresult[0].second=="Set-sticky"
1453 || lresult[0].second=="Clear-template"
1454 || lresult[0].second=="Set-static-directory"
1455 || lresult[0].second=="Template") continue;
1456 if (lresult[0].second!="Clear-static-directory")
1457 L(F("cvs_client::RequestServerDir lresult[0].second is '%s', not 'Clear-static-directory'") % lresult[0].second);
1458 I(lresult[0].second=="Clear-static-directory");
1459 I(lresult.size()==3);
1460 if (!last_rcs.empty() && begins_with(lresult[2].second,last_rcs)
1461 && lresult[1].second.substr(0,last_local.size())==last_local)
1462 { I(lresult[2].second.substr(last_rcs.size())
1463 ==lresult[1].second.substr(last_local.size()));
1464 continue;
1465 }
1466 result[shorten_path(lresult[1].second)]=lresult[2].second;
1467 last_local=lresult[1].second;
1468 last_rcs=lresult[2].second;
1469 }
1470 return result;
1471}
1472
1473void cvs_client::SetServerDir(const std::map<std::string,std::string> &m)
1474{ server_dir=m;
1475}
1476
1477void cvs_client::primeModules()
1478{ if (!server_dir.empty()) return;
1479 std::vector<std::string> modules=ExpandModules();
1480 for (std::vector<std::string>::const_iterator i=modules.begin();
1481 i!=modules.end();++i)
1482 { server_dir[shorten_path(*i)];
1483 }
1484 server_dir=RequestServerDir();
1485 for (std::map<std::string,std::string>::const_iterator i=server_dir.begin();
1486 i!=server_dir.end();++i)
1487 L(F("server dir %s -> %s") % i->first % i->second);
1488}

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status