monotone

monotone Mtn Source Tree

Root/cvs_sync.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 "cvs_sync.hh"
7#include "keys.hh"
8#include "transforms.hh"
9#include <vector>
10#include <boost/lexical_cast.hpp>
11#include "botan/md5.h"
12#include <boost/date_time/posix_time/posix_time.hpp>
13#include <boost/date_time/gregorian/greg_date.hpp>
14#include <fstream>
15#include <sys/stat.h>
16#include "stringtok.hh"
17#include "piece_table.hh"
18#include "basic_io.hh"
19#include <sstream>
20typedef std::string symbol;
21
22#ifdef WIN32
23#define sleep(x) _sleep(x)
24#endif
25
26#undef MM
27#define MM(x)
28
29using namespace std;
30
31// since the piece methods in rcs_import depend on rcs_file I cannot reuse them
32// I rely on string handling reference counting (which is not that bad IIRC)
33// -> investigate under which conditions a string gets copied
34static std::string const cvs_cert_name="cvs-revisions";
35
36using namespace cvs_sync;
37
38bool file_state::operator<(const file_state &b) const
39{ return since_when<b.since_when
40 || (since_when==b.since_when
41 && cvs_revision_nr(cvs_version)<cvs_revision_nr(b.cvs_version));
42}
43
44// whether time is below span or (within span and lesser author,changelog)
45bool cvs_sync::operator<(const file_state &s,const cvs_edge &e)
46{ return s.since_when<e.time ||
47 (s.since_when<=e.time2 && (s.author<e.author ||
48 (s.author==e.author && s.log_msg<e.changelog)));
49}
50
51// whether time is below span or (within span and lesser/equal author,changelog)
52bool
53cvs_sync::operator<=(const file_state &s,const cvs_edge &e)
54{ return s.since_when<e.time ||
55 (s.since_when<=e.time2 && (s.author<=e.author ||
56 (s.author==e.author && s.log_msg<=e.changelog)));
57}
58
59/* supported by the woody version:
60Root Valid-responses valid-requests Repository Directory Max-dotdot
61Static-directory Sticky Checkin-prog Update-prog Entry Kopt Checkin-time
62Modified Is-modified UseUnchanged Unchanged Notify Questionable Case
63Argument Argumentx Global_option Gzip-stream wrapper-sendme-rcsOptions Set
64expand-modules ci co update diff log rlog add remove update-patches
65gzip-file-contents status rdiff tag rtag import admin export history release
66watch-on watch-off watch-add watch-remove watchers editors annotate
67rannotate noop version
68*/
69
70//--------------------- implementation -------------------------------
71
72size_t const cvs_edge::cvs_window;
73
74bool cvs_revision_nr::operator==(const cvs_revision_nr &b) const
75{ return parts==b.parts;
76}
77
78// is this strictly correct? feels ok for now (and this is last ressort)
79bool cvs_revision_nr::operator<(const cvs_revision_nr &b) const
80{ return parts<b.parts;
81}
82
83cvs_revision_nr::cvs_revision_nr(const std::string &x)
84{ std::string::size_type begin=0;
85 do
86 { std::string::size_type end=x.find(".",begin);
87 std::string::size_type len=end-begin;
88 if (end==std::string::npos) len=std::string::npos;
89 parts.push_back(atoi(x.substr(begin,len).c_str()));
90 begin=end;
91 if (begin!=std::string::npos) ++begin;
92 } while(begin!=std::string::npos);
93};
94
95// we cannot guess whether the revision following 1.3 is 1.3.2.1 or 1.4 :-(
96// so we can only hope, that this is the expected result
97void cvs_revision_nr::operator++()
98{ if (parts.empty()) return;
99 if (parts.size()==4 && get_string()=="1.1.1.1") *this=cvs_revision_nr("1.2");
100 else parts.back()++;
101}
102
103std::string cvs_revision_nr::get_string() const
104{ std::string result;
105 for (std::vector<int>::const_iterator i=parts.begin();i!=parts.end();++i)
106 { if (!result.empty()) result+=".";
107 result+=boost::lexical_cast<string>(*i);
108 }
109 return result;
110}
111
112bool cvs_revision_nr::is_parent_of(const cvs_revision_nr &child) const
113{ unsigned cps=child.parts.size();
114 unsigned ps=parts.size();
115 if (cps<ps)
116 { if (child==cvs_revision_nr("1.2") && *this==cvs_revision_nr("1.1.1.1"))
117 return true;
118 return false;
119 }
120 if (is_branch() || child.is_branch()) return false;
121 unsigned diff=0;
122 for (;diff<ps;++diff) if (child.parts[diff]!=parts[diff]) break;
123 if (cps==ps)
124 { if (diff+1!=cps) return false;
125 if (parts[diff]+1 != child.parts[diff]) return false;
126 }
127 else // ps < cps
128 { if (diff!=ps) return false;
129 if (ps+2!=cps) return false;
130 if (child.parts[diff]&1 || !child.parts[diff]) return false;
131 if (child.parts[diff+1]!=1) return false;
132 }
133 return true;
134}
135
136// impair number of numbers => branch tag
137bool cvs_revision_nr::is_branch() const
138{ return parts.size()&1;
139}
140
141// cvs_repository ----------------------
142
143// very short form to output in logs etc.
144std::string time_t2human(const time_t &t)
145{ struct tm *tm;
146 tm=gmtime(&t);
147 return (boost::format("%02d%02d%02dT%02d%02d%02d") % (tm->tm_year%100)
148 % (tm->tm_mon+1) % tm->tm_mday % tm->tm_hour % tm->tm_min
149 % tm->tm_sec).str();
150}
151
152std::string time_t2monotone(const time_t &t)
153{ struct tm *tm;
154 tm=gmtime(&t);
155 return (boost::format("%04d-%02d-%02dT%02d:%02d:%02d") % (tm->tm_year+1900)
156 % (tm->tm_mon+1) % tm->tm_mday % tm->tm_hour % tm->tm_min
157 % tm->tm_sec).str();
158}
159
160struct cvs_repository::get_all_files_log_cb : rlog_callbacks
161{ cvs_repository &repo;
162 get_all_files_log_cb(cvs_repository &r) : repo(r) {}
163 virtual void file(const std::string &file,const std::string &head_rev) const
164 { L(F("get_all_files_log_cb %s") % file);
165 repo.files[file];
166 }
167 virtual void tag(const std::string &file,const std::string &tag,
168 const std::string &revision) const {}
169 virtual void revision(const std::string &file,time_t t,
170 const std::string &rev,const std::string &author,
171 const std::string &state,const std::string &log) const {}
172};
173
174// get all available files and their newest revision
175void cvs_repository::get_all_files()
176{ if (edges.empty())
177 { // rlist seems to be more efficient but it's hard to guess the directory the
178 // server talks about
179 I(CommandValid("rlog"));
180 RLog(get_all_files_log_cb(*this),false,"-N","-h","--",module.c_str(),(void*)0);
181 }
182}
183
184std::string debug_manifest(const cvs_manifest &mf)
185{ std::string result;
186 for (cvs_manifest::const_iterator i=mf.begin(); i!=mf.end(); ++i)
187 { result+= i->first + " " + i->second->cvs_version;
188 if (!i->second->keyword_substitution.empty())
189 result+="/"+i->second->keyword_substitution;
190 result+=" " + std::string(i->second->dead?"dead ":"") + i->second->sha1sum() + "\n";
191 }
192 return result;
193}
194
195std::string cvs_repository::debug_file(std::string const& name)
196{ std::map<std::string,file_history>::const_iterator i=files.find(name);
197 E(i!=files.end(),F("file '%s' not found\n") % name);
198 std::string result;
199 for (std::set<file_state>::const_iterator j=i->second.known_states.begin();
200 j!=i->second.known_states.end();++j)
201 { result+="since "+time_t2human(j->since_when);
202 result+=" V"+j->cvs_version+" ";
203 if (j->dead) result+= "dead";
204 else if (j->size) result+= boost::lexical_cast<string>(j->size);
205 else if (j->patchsize) result+= "p" + boost::lexical_cast<string>(j->patchsize);
206 else if (!j->sha1sum().empty()) result+= j->sha1sum().substr(0,4) + j->keyword_substitution;
207 result+=" "+j->log_msg.substr(0,20)+"\n";
208 }
209 return result;
210}
211
212#if 0
213std::string debug_files(const std::map<std::string,file_history> &files)
214{ std::string result;
215 for (std::map<std::string,file_history>::const_iterator i=files.begin();
216 i!=files.end();++i)
217 { result += i->first;
218 result += " (";
219 for (std::set<file_state>::const_iterator j=i->second.known_states.begin();
220 j!=i->second.known_states.end();)
221 { result += boost::lexical_cast<string>(j->since_when%1000) + ":" + j->cvs_version + "=";
222 if (j->dead) result += "dead";
223 else if (j->size) result += boost::lexical_cast<string>(j->size);
224 else if (j->patchsize) result += 'p' + boost::lexical_cast<string>(j->patchsize);
225 else if (!j->sha1sum().empty()) result += j->sha1sum().substr(0,4) + j->keyword_substitution;
226 ++j;
227 if (j!=i->second.known_states.end()) result += ",";
228 }
229 result += ")\n";
230 }
231 return result;
232}
233#endif
234
235// returns the length of the first line (header) and fills in fields
236std::string::size_type cvs_repository::parse_cvs_cert_header(cert_value const& value,
237 std::string &repository, std::string& module, std::string& branch)
238{
239 MM(value());
240 std::string::size_type nlpos=value().find('\n');
241 E(nlpos!=std::string::npos, F("malformed cvs-revision cert %s") % value());
242 std::string repo=value().substr(0,nlpos);
243 std::string::size_type modulebegin=repo.find('\t');
244 E(modulebegin!=std::string::npos, F("malformed cvs-revision header %s") % repo);
245 std::string::size_type branchbegin=repo.find(modulebegin,'\t');
246 std::string::size_type modulelen=std::string::npos;
247
248 if (branchbegin!=std::string::npos)
249 { branch=repo.substr(branchbegin+1);
250 modulelen=branchbegin-modulebegin-1;
251 }
252 repository=repo.substr(0,modulebegin);
253 module=repo.substr(modulebegin+1,modulelen);
254 return nlpos;
255}
256
257void cvs_repository::parse_cvs_cert_header(revision<cert> const& c,
258 std::string &repository, std::string& module, std::string& branch)
259{
260 cert_value value;
261 decode_base64(c.inner().value, value);
262 parse_cvs_cert_header(value, repository, module, branch);
263}
264
265std::string cvs_repository::create_cvs_cert_header() const
266{
267 // I assume that at least TAB is uncommon in path names - even on Windows
268 std::string result=host+":"+root+"\t"+module;
269 if (!branch.empty()) result+="\t"+branch;
270 return result+"\n";
271}
272
273std::string cvs_repository::debug() const
274{ std::string result;
275
276 // edges set<cvs_edge>
277 result+= "Edges :\n";
278 for (std::set<cvs_edge>::const_iterator i=edges.begin();
279 i!=edges.end();++i)
280 { result+= "[" + time_t2human(i->time);
281 if (i->time!=i->time2) result+= "+" + boost::lexical_cast<string>(i->time2-i->time);
282 if (!i->revision().empty()) result+= "," + i->revision().substr(0,4);
283 if (!i->xfiles.empty())
284 result+= "," + boost::lexical_cast<string>(i->xfiles.size())
285 + (i->delta_base.inner()().empty()?"files":"deltas");
286 result+= "," + i->author + ",";
287 std::string::size_type nlpos=i->changelog.find_first_of("\n\r");
288 if (nlpos>50) nlpos=50;
289 result+= i->changelog.substr(0,nlpos) + "]\n";
290 }
291 result+= "Files :\n";
292 for (std::map<std::string,file_history>::const_iterator i=files.begin();
293 i!=files.end();++i)
294 { result+= i->first;
295 result+= " (";
296 for (std::set<file_state>::const_iterator j=i->second.known_states.begin();
297 j!=i->second.known_states.end();)
298 { if (j->dead) result+= "dead";
299 else if (j->size) result+= boost::lexical_cast<string>(j->size);
300 else if (j->patchsize) result+= "p" + boost::lexical_cast<string>(j->patchsize);
301 else if (!j->sha1sum().empty()) result+= j->sha1sum().substr(0,4) + j->keyword_substitution;
302 ++j;
303 if (j!=i->second.known_states.end()) result+= ",";
304 }
305 result+= ")\n";
306 }
307 result+= "Tags :\n";
308 for (std::map<std::string,std::map<std::string,std::string> >::const_iterator i=tags.begin();
309 i!=tags.end();++i)
310 { result+= i->first + "(" + boost::lexical_cast<string>(i->second.size()) + " files)\n";
311 }
312 return result;
313}
314
315struct cvs_repository::prime_log_cb : rlog_callbacks
316{ cvs_repository &repo;
317 std::map<std::string,struct cvs_sync::file_history>::iterator i;
318 time_t override_time;
319 prime_log_cb(cvs_repository &r,const std::map<std::string,struct cvs_sync::file_history>::iterator &_i
320 ,time_t overr_time=-1)
321 : repo(r), i(_i), override_time(overr_time) {}
322 virtual void tag(const std::string &file,const std::string &tag,
323 const std::string &revision) const;
324 virtual void revision(const std::string &file,time_t t,
325 const std::string &rev,const std::string &author,
326 const std::string &state,const std::string &log) const;
327 virtual void file(const std::string &file,const std::string &head_rev) const
328 { }
329};
330
331void cvs_repository::prime_log_cb::tag(const std::string &file,const std::string &tag,
332 const std::string &revision) const
333{ MM(file);
334 MM(tag);
335 I(i->first==file);
336 std::map<std::string,std::string> &tagslot=repo.tags[tag];
337 tagslot[file]=revision;
338}
339
340void cvs_repository::prime_log_cb::revision(const std::string &file,time_t checkin_time,
341 const std::string &revision,const std::string &_author,
342 const std::string &dead,const std::string &_message) const
343{ L(F("prime_log_cb %s:%s %s %s %d %s\n") % file % revision % time_t2human(checkin_time)
344 % _author % _message.size() % dead);
345 std::string author=_author;
346 std::string message=_message;
347 I(i->first==file);
348 if (override_time!=-1)
349 { checkin_time=override_time;
350 message="initial state for cvs_pull --since";
351 author=repo.app.signing_key();
352 }
353 std::pair<std::set<file_state>::iterator,bool> iter=
354 i->second.known_states.insert
355 (file_state(checkin_time,revision,dead=="dead"));
356 // I(iter.second==false);
357 // set iterators are read only to prevent you from destroying the order
358 file_state &fs=const_cast<file_state &>(*(iter.first));
359 fs.log_msg=message;
360 fs.author=author;
361 std::pair<std::set<cvs_edge>::iterator,bool> iter2=
362 repo.edges.insert(cvs_edge(message,checkin_time,author));
363 if (iter2.second && repo.cvs_edges_ticker.get()) ++(*repo.cvs_edges_ticker);
364}
365
366bool cvs_edge::similar_enough(cvs_edge const & other) const
367{
368 if (changelog != other.changelog)
369 return false;
370 if (author != other.author)
371 return false;
372 if (labs(time - other.time) > cvs_window
373 && labs(time2 - other.time) > cvs_window)
374 return false;
375 return true;
376}
377
378bool cvs_edge::operator<(cvs_edge const & other) const
379{
380 return time < other.time ||
381
382 (time == other.time
383 && author < other.author) ||
384
385 (time == other.time
386 && author == other.author
387 && changelog < other.changelog);
388}
389
390void cvs_repository::store_contents(const data &dat, hexenc<id> &sha1sum)
391{
392 calculate_ident(dat,sha1sum);
393 if (!app.db.file_version_exists(sha1sum))
394 { app.db.put_file(sha1sum, dat);
395 if (file_id_ticker.get()) ++(*file_id_ticker);
396 }
397}
398
399static void apply_delta(piece::piece_table &contents, const std::string &patch)
400{ piece::piece_table after;
401 piece::apply_diff(contents,after,patch);
402 std::swap(contents,after);
403}
404
405void cvs_repository::store_delta(const std::string &new_contents,
406 const std::string &old_contents,
407 // this argument is unused since we can no longer use the rcs patch
408 const std::string &dummy,
409 const hexenc<id> &from, hexenc<id> &to)
410{ if (old_contents.empty())
411 { store_contents(new_contents, to);
412 return;
413 }
414 data dat(new_contents);
415 calculate_ident(dat,to);
416 if (!app.db.file_version_exists(to))
417 { delta del;
418 diff(data(old_contents), data(new_contents), del);
419 if (dat().size()<=del().size())
420 // the data is smaller or of equal size to the patch
421 app.db.put_file(to, dat);
422 else
423 app.db.put_file_version(from,to,del);
424 if (file_id_ticker.get()) ++(*file_id_ticker);
425 }
426}
427
428static bool
429build_change_set(const cvs_client &c, manifest_map const& oldm, cvs_manifest &newm,
430 change_set & cs, cvs_file_state remove_state, unsigned cm_delta_depth)
431{
432 cs = change_set();
433 cvs_manifest cvs_delta;
434
435 L(F("build_change_set(%d,%d,)\n") % oldm.size() % newm.size());
436
437 for (manifest_map::const_iterator f = oldm.begin(); f != oldm.end(); ++f)
438 {
439 cvs_manifest::const_iterator fn = newm.find(f->first.as_internal());
440 if (fn==newm.end())
441 {
442 L(F("deleting file '%s'\n") % f->first);
443 cs.delete_file(f->first);
444 cvs_delta[f->first.as_internal()]=remove_state;
445 }
446 else
447 { if (f->second == fn->second->sha1sum)
448 {
449// L(F("skipping preserved entry state '%s' on '%s'\n")
450// % fn->second->sha1sum % fn->first);
451 }
452 else
453 {
454 L(F("applying state delta on '%s' : '%s' -> '%s'\n")
455 % fn->first % f->second % fn->second->sha1sum);
456 I(!fn->second->sha1sum().empty());
457 cs.apply_delta(file_path_internal(fn->first), f->second, fn->second->sha1sum);
458 cvs_delta[f->first.as_internal()]=fn->second;
459 }
460 }
461 }
462 for (cvs_manifest::const_iterator f = newm.begin(); f != newm.end(); ++f)
463 {
464 manifest_map::const_iterator fo = oldm.find(file_path_internal(f->first));
465 if (fo==oldm.end())
466 {
467 L(F("adding file '%s' as '%s'\n") % f->second->sha1sum % f->first);
468 I(!f->second->sha1sum().empty());
469 cs.add_file(file_path_internal(f->first), f->second->sha1sum);
470 cvs_delta[f->first]=f->second;
471 }
472 }
473 if (!oldm.empty() && cvs_delta.size()<newm.size()
474 && cm_delta_depth+1<cvs_edge::cm_max_delta_depth)
475 { newm=cvs_delta;
476 return true;
477 }
478 return false;
479}
480
481void cvs_repository::check_split(const cvs_file_state &s, const cvs_file_state &end,
482 const std::set<cvs_edge>::iterator &e)
483{ cvs_file_state s2=s;
484 ++s2;
485 if (s2==end) return;
486 MM(boost::lexical_cast<std::string>(s->since_when));
487 MM(boost::lexical_cast<std::string>(s2->since_when));
488 I(s->since_when!=s2->since_when);
489 // checkins must not overlap (next revision must lie beyond edge)
490 if ((*s2) <= (*e))
491 { W(F("splitting edge %s-%s at %s\n") % time_t2human(e->time)
492 % time_t2human(e->time2) % time_t2human(s2->since_when));
493 cvs_edge new_edge=*e;
494 MM(boost::lexical_cast<std::string>(e->time));
495 I(s2->since_when-1>=e->time);
496 e->time2=s2->since_when-1;
497 new_edge.time=s2->since_when;
498 edges.insert(new_edge);
499 }
500}
501
502void cvs_repository::join_edge_parts(std::set<cvs_edge>::iterator i)
503{ for (;i!=edges.end();)
504 { std::set<cvs_edge>::iterator j=i;
505 j++; // next one
506 if (j==edges.end()) break;
507
508 MM(boost::lexical_cast<std::string>(j->time2));
509 MM(boost::lexical_cast<std::string>(j->time));
510 MM(boost::lexical_cast<std::string>(i->time2));
511 MM(boost::lexical_cast<std::string>(i->time));
512 I(j->time2==j->time); // make sure we only do this once
513 I(i->time2<=j->time); // should be sorted ...
514 if (!i->similar_enough(*j))
515 { ++i; continue; }
516 I((j->time-i->time2)<=time_t(cvs_edge::cvs_window)); // just to be sure
517 I(i->author==j->author);
518 I(i->changelog==j->changelog);
519 I(i->time2<j->time); // should be non overlapping ...
520 L(F("joining %s-%s+%s\n") % time_t2human(i->time) % time_t2human(i->time2) % time_t2human(j->time));
521 i->time2=j->time;
522 edges.erase(j);
523 }
524}
525
526void cvs_repository::store_update(std::set<file_state>::const_iterator s,
527 std::set<file_state>::iterator s2,const cvs_client::update &u,
528 std::string &contents)
529{
530 if (u.removed)
531 { const_cast<bool&>(s2->dead)=true;
532 }
533 else if (!u.checksum.empty())
534 { // const_cast<std::string&>(s2->rcs_patch)=u.patch;
535 const_cast<std::string&>(s2->md5sum)=u.checksum;
536 const_cast<unsigned&>(s2->patchsize)=u.patch.size();
537 const_cast<std::string&>(s2->keyword_substitution)=u.keyword_substitution;
538 // I(s2->since_when==u.mod_time);
539 if (u.mod_time!=s2->since_when && u.mod_time!=-1)
540 { W(F("update time %s and log time %s disagree\n") % time_t2human(u.mod_time) % time_t2human(s2->since_when));
541 }
542 std::string old_contents=contents;
543 { piece::piece_table file_contents;
544 piece::index_deltatext(contents,file_contents);
545 apply_delta(file_contents, u.patch);
546 piece::build_string(file_contents, contents);
547 piece::reset();
548 }
549 // check md5
550 Botan::MD5 hash;
551 std::string md5sum=xform<Botan::Hex_Decoder>(u.checksum);
552 I(md5sum.size()==hash.OUTPUT_LENGTH);
553 Botan::SecureVector<Botan::byte> hashval=hash.process(contents);
554 I(hashval.size()==hash.OUTPUT_LENGTH);
555 unsigned hashidx=hash.OUTPUT_LENGTH;
556 for (;hashidx && hashval[hashidx-1]==Botan::byte(md5sum[hashidx-1]);--hashidx) ;
557 if (!hashidx)
558 { store_delta(contents, old_contents, u.patch, s->sha1sum, const_cast<hexenc<id>&>(s2->sha1sum));
559 }
560 else
561 { E(false, F("MD5 sum %s<>%s") % u.checksum
562 % xform<Botan::Hex_Encoder>(std::string(hashval.begin(),hashval.end())));
563 }
564 }
565 else
566 { if (!s->sha1sum().empty())
567 // we default to patch if it's at all possible
568 store_delta(u.contents, contents, std::string(), s->sha1sum, const_cast<hexenc<id>&>(s2->sha1sum));
569 else
570 store_contents(u.contents, const_cast<hexenc<id>&>(s2->sha1sum));
571 const_cast<unsigned&>(s2->size)=u.contents.size();
572 contents=u.contents;
573 const_cast<std::string&>(s2->keyword_substitution)=u.keyword_substitution;
574 }
575}
576
577// s2 gets changed
578void cvs_repository::update(std::set<file_state>::const_iterator s,
579 std::set<file_state>::iterator s2,const std::string &file,
580 std::string &contents)
581{
582 cvs_revision_nr srev(s->cvs_version);
583 MM(file);
584 MM(s->cvs_version);
585 MM(s2->cvs_version);
586 if (!srev.is_parent_of(s2->cvs_version))
587 std::cerr << "Inconsistency "<< file << ": " << s->cvs_version
588 << "->" << s2->cvs_version << "\n" << debug() << '\n';
589 I(srev.is_parent_of(s2->cvs_version));
590 if (s->dead)
591 {
592 // this might fail (?) because we issued an Entry somewhere above
593 // but ... we can specify the correct directory!
594 cvs_client::update c=Update(file,s2->cvs_version);
595 I(!c.removed); // dead->dead is no change, so shouldn't get a number
596 I(!s2->dead);
597 // I(s2->since_when==c.mod_time);
598 if (c.mod_time!=s2->since_when && c.mod_time!=-1 && s2->since_when!=sync_since)
599 { W(F("checkout time %s and log time %s disagree\n") % time_t2human(c.mod_time) % time_t2human(s2->since_when));
600 }
601 store_contents(c.contents, const_cast<hexenc<id>&>(s2->sha1sum));
602 const_cast<unsigned&>(s2->size)=c.contents.size();
603 contents=c.contents;
604 const_cast<std::string&>(s2->keyword_substitution)=c.keyword_substitution;
605 }
606 else if (s2->dead) // short circuit if we already know it's dead
607 { L(F("file %s: revision %s already known to be dead\n") % file % s2->cvs_version);
608 }
609 else
610 { cvs_client::update u=Update(file,s->cvs_version,s2->cvs_version,s->keyword_substitution);
611 try
612 { store_update(s,s2,u,contents);
613 } catch (informative_failure &e)
614 { W(F("Update: patching failed with %s\n") % e.what);
615 cvs_client::update c=Update(file,s2->cvs_version);
616 if (c.mod_time!=s2->since_when && c.mod_time!=-1 && s2->since_when!=sync_since)
617 { W(F("checkout time %s and log time %s disagree\n") % time_t2human(c.mod_time) % time_t2human(s2->since_when));
618 }
619 const_cast<std::string&>(s2->md5sum)="";
620 const_cast<unsigned&>(s2->patchsize)=0;
621 store_contents(c.contents, const_cast<hexenc<id>&>(s2->sha1sum));
622 const_cast<unsigned&>(s2->size)=c.contents.size();
623 contents=c.contents;
624 const_cast<std::string&>(s2->keyword_substitution)=c.keyword_substitution;
625 } catch (std::exception &e)
626 { W(F("Update: patching failed with %s\n") % e.what());
627 cvs_client::update c=Update(file,s2->cvs_version);
628 if (c.mod_time!=s2->since_when && c.mod_time!=-1 && s2->since_when!=sync_since)
629 { W(F("checkout time %s and log time %s disagree\n") % time_t2human(c.mod_time) % time_t2human(s2->since_when));
630 }
631 const_cast<std::string&>(s2->md5sum)="";
632 const_cast<unsigned&>(s2->patchsize)=0;
633 store_contents(c.contents, const_cast<hexenc<id>&>(s2->sha1sum));
634 const_cast<unsigned&>(s2->size)=c.contents.size();
635 contents=c.contents;
636 const_cast<std::string&>(s2->keyword_substitution)=c.keyword_substitution;
637 }
638 }
639}
640
641void cvs_repository::store_checkout(std::set<file_state>::iterator s2,
642 const cvs_client::checkout &c, std::string &file_contents)
643{ const_cast<bool&>(s2->dead)=c.dead;
644 if (!c.dead)
645 { // I(c.mod_time==s2->since_when);
646 if (c.mod_time!=s2->since_when && c.mod_time!=-1 && s2->since_when!=sync_since)
647 { W(F("checkout time %s and log time %s disagree\n") % time_t2human(c.mod_time) % time_t2human(s2->since_when));
648 }
649 store_contents(c.contents, const_cast<hexenc<id>&>(s2->sha1sum));
650 const_cast<unsigned&>(s2->size)=c.contents.size();
651 file_contents=c.contents;
652 const_cast<std::string&>(s2->keyword_substitution)=c.keyword_substitution;
653 }
654}
655
656void cvs_repository::store_checkout(std::set<file_state>::iterator s2,
657 const cvs_client::update &c, std::string &file_contents)
658{ const_cast<bool&>(s2->dead)=c.removed;
659 if (!c.removed)
660 { // I(c.mod_time==s2->since_when);
661 if (c.mod_time!=s2->since_when && c.mod_time!=-1 && s2->since_when!=sync_since)
662 { W(F("checkout time %s and log time %s disagree\n") % time_t2human(c.mod_time) % time_t2human(s2->since_when));
663 }
664 store_contents(c.contents, const_cast<hexenc<id>&>(s2->sha1sum));
665 const_cast<unsigned&>(s2->size)=c.contents.size();
666 file_contents=c.contents;
667 const_cast<std::string&>(s2->keyword_substitution)=c.keyword_substitution;
668 }
669}
670
671void cvs_repository::fill_manifests(std::set<cvs_edge>::iterator e)
672{ cvs_manifest current_manifest;
673 if (e!=edges.begin())
674 { std::set<cvs_edge>::const_iterator before=e;
675 --before;
676 current_manifest=get_files(*before);
677 }
678 for (;e!=edges.end();++e)
679 { std::set<cvs_edge>::iterator next_edge=e;
680 ++next_edge;
681 for (std::map<std::string,file_history>::const_iterator f=files.begin();f!=files.end();++f)
682 { I(!branch.empty() || !f->second.known_states.empty());
683 // this file does not belong to this branch
684 if (f->second.known_states.empty()) continue;
685 if (!(*(f->second.known_states.begin()) <= (*e)))
686 // the file does not exist yet (first is not below/equal current edge)
687 { L(F("%s before beginning %s/%s+%d\n") % f->first
688 % time_t2human(f->second.known_states.begin()->since_when)
689 % time_t2human(e->time) % (e->time2-e->time));
690 continue;
691 }
692 cvs_manifest::iterator mi=current_manifest.find(f->first);
693 if (mi==current_manifest.end()) // the file is currently dead
694 { cvs_file_state s=f->second.known_states.end();
695 // find last revision that fits but does not yet belong to next edge
696 // use until end, or above range, or belongs to next edge
697 for (cvs_file_state s2=f->second.known_states.begin();
698 s2!=f->second.known_states.end()
699 && (*s2)<=(*e)
700 && ( next_edge==edges.end() || ((*s2)<(*next_edge)) );
701 ++s2)
702 { L(F("%s matches %s/%s+%d\n") % f->first
703 % time_t2human(s2->since_when)
704 % time_t2human(e->time) % (e->time2-e->time));
705 s=s2;
706 }
707
708 if (s!=f->second.known_states.end() && !s->dead)
709 // a matching revision was found
710 { current_manifest[f->first]=s;
711 I(!s->sha1sum().empty());
712 check_split(s,f->second.known_states.end(),e);
713 }
714 }
715 else // file was present in last manifest, check whether next revision already fits
716 { cvs_file_state s=mi->second;
717 ++s;
718 if (s!=f->second.known_states.end()
719 && (*s)<=(*e)
720 && ( next_edge==edges.end() || ((*s)<(*next_edge)) ) )
721 { if (s->dead) current_manifest.erase(mi);
722 else
723 { mi->second=s;
724 I(!s->sha1sum().empty());
725 }
726 check_split(s,f->second.known_states.end(),e);
727 }
728 }
729 }
730 e->xfiles=current_manifest;
731 }
732}
733
734// commit CVS revisions to monotone (pull)
735void cvs_repository::commit_revisions(std::set<cvs_edge>::iterator e)
736{ // cvs_manifest parent_manifest;
737 revision_id parent_rid;
738 manifest_id parent_mid;
739 manifest_map parent_map;
740 manifest_map child_map=parent_map;
741 packet_db_writer dbw(app);
742 unsigned cm_delta_depth=0;
743
744 cvs_edges_ticker.reset(0);
745 L(F("commit_revisions(%s %s)\n") % time_t2human(e->time) % e->revision());
746 revision_ticker.reset(new ticker("revisions", "R", 3));
747// const cvs_manifest *oldmanifestp=&empty;
748 if (e!=edges.begin())
749 { std::set<cvs_edge>::const_iterator before=e;
750 --before;
751 L(F("found last committed %s %s\n") % time_t2human(before->time) % before->revision());
752 I(!before->revision().empty());
753 parent_rid=before->revision;
754 app.db.get_revision_manifest(parent_rid,parent_mid);
755 app.db.get_manifest(parent_mid,parent_map);
756 child_map=parent_map;
757// parent_manifest=get_files(*before);
758 cm_delta_depth=before->cm_delta_depth;
759 }
760 for (; e!=edges.end(); ++e)
761 { boost::shared_ptr<change_set> cs(new change_set());
762 I(e->delta_base.inner()().empty()); // no delta yet
763 cvs_manifest child_manifest=get_files(*e);
764 L(F("build_change_set(%s %s)\n") % time_t2human(e->time) % e->revision());
765 if (build_change_set(*this,parent_map,e->xfiles,*cs,remove_state,cm_delta_depth))
766 { e->delta_base=parent_rid;
767 e->cm_delta_depth=cm_delta_depth+1;
768 }
769 if (cs->empty())
770 { W(F("null edge (empty cs) @%s skipped\n") % time_t2human(e->time));
771 continue;
772 }
773 if (e->xfiles.empty())
774 { W(F("empty edge (no files) @%s skipped\n") % time_t2human(e->time));
775 continue;
776 }
777 apply_change_set(*cs, child_map);
778 if (child_map.empty())
779 { W(F("empty edge (no files in manifest) @%s skipped\n") % time_t2human(e->time));
780 // perhaps begin a new tree:
781 // parent_rid=revision_id();
782 // parent_mid=manifest_id();
783// parent_manifest=cvs_manifest();
784 continue;
785 }
786 manifest_id child_mid;
787 calculate_ident(child_map, child_mid);
788
789 revision_set rev;
790 rev.new_manifest = child_mid;
791 rev.edges.insert(std::make_pair(parent_rid, make_pair(parent_mid, cs)));
792 revision_id child_rid;
793 calculate_ident(rev, child_rid);
794 L(F("CVS Sync: Inserting revision %s (%s) into repository\n") % child_rid % child_mid);
795
796 if (app.db.manifest_version_exists(child_mid))
797 {
798 L(F("existing path to %s found, skipping\n") % child_mid);
799 }
800 else if (parent_mid.inner()().empty())
801 {
802 manifest_data mdat;
803 I(!child_map.empty());
804 write_manifest_map(child_map, mdat);
805 app.db.put_manifest(child_mid, mdat);
806 }
807 else
808 {
809 delta del;
810 I(!child_map.empty());
811 diff(parent_map, child_map, del);
812 app.db.put_manifest_version(parent_mid, child_mid, del);
813 }
814 e->revision=child_rid.inner();
815 if (! app.db.revision_exists(child_rid))
816 { app.db.put_revision(child_rid, rev);
817 if (revision_ticker.get()) ++(*revision_ticker);
818 }
819 cert_revision_in_branch(child_rid, app.branch_name(), app, dbw);
820 std::string author=e->author;
821 if (author.find('@')==std::string::npos) author+="@"+host;
822 cert_revision_author(child_rid, author, app, dbw);
823 cert_revision_changelog(child_rid, e->changelog, app, dbw);
824 cert_revision_date_time(child_rid, e->time, app, dbw);
825 cert_cvs(*e, dbw);
826
827 // now apply same change set to parent_map, making parent_map == child_map
828 apply_change_set(*cs, parent_map);
829 parent_mid = child_mid;
830 parent_rid = child_rid;
831// parent_manifest=child_manifest;
832 cm_delta_depth=e->cm_delta_depth;
833 }
834}
835
836void cvs_repository::prime()
837{ retrieve_modules();
838 get_all_files();
839 revision_ticker.reset(0);
840 cvs_edges_ticker.reset(new ticker("edges", "E", 10));
841 for (std::map<std::string,file_history>::iterator i=files.begin();i!=files.end();++i)
842 { std::vector<std::string> args;
843 MM(i->first);
844
845 if (!branch.empty())
846 args.push_back("-r"+branch);
847 else
848 args.push_back("-b");
849
850 if (sync_since!=-1)
851 { args.push_back("-d");
852 size_t date_index=args.size();
853 args.push_back(time_t2rfc822(sync_since));
854 // state _at_ this point in time
855 Log(prime_log_cb(*this,i,sync_since),i->first,args);
856 // -d Jun 20 09:38:29 1997<
857 args[date_index]+='<';
858 // state _since_ this point in time
859 Log(prime_log_cb(*this,i,sync_since),i->first,args);
860 }
861 else
862 Log(prime_log_cb(*this,i),i->first,args);
863 }
864 // remove duplicate states (because some edges were added by the
865 // get_all_files method)
866 for (std::set<cvs_edge>::iterator i=edges.begin();i!=edges.end();)
867 { if (i->changelog_valid || i->author.size()) { ++i; continue; }
868 std::set<cvs_edge>::iterator j=i;
869 j++;
870 MM(boost::lexical_cast<std::string>(i->time));
871 MM(boost::lexical_cast<std::string>(j->time));
872 I(j!=edges.end());
873 I(j->time==i->time);
874 I(i->xfiles.empty());
875 edges.erase(i);
876 if (cvs_edges_ticker.get()) --(*cvs_edges_ticker);
877 i=j;
878 }
879
880 // join adjacent check ins (same author, same changelog)
881 join_edge_parts(edges.begin());
882
883 // get the contents
884 for (std::map<std::string,file_history>::iterator i=files.begin();i!=files.end();++i)
885 { std::string file_contents;
886 MM(i->first);
887 I(!branch.empty() || !i->second.known_states.empty());
888 if (!i->second.known_states.empty())
889 { std::set<file_state>::iterator s2=i->second.known_states.begin();
890 cvs_client::update c=Update(i->first,s2->cvs_version);
891 store_checkout(s2,c,file_contents);
892 }
893 for (std::set<file_state>::iterator s=i->second.known_states.begin();
894 s!=i->second.known_states.end();++s)
895 { std::set<file_state>::iterator s2=s;
896 ++s2;
897 if (s2==i->second.known_states.end()) break;
898 update(s,s2,i->first,file_contents);
899 }
900 }
901 drop_connection();
902
903 // fill in file states at given point
904 fill_manifests(edges.begin());
905
906 // commit them all
907 commit_revisions(edges.begin());
908
909 store_modules();
910}
911
912void cvs_repository::cert_cvs(const cvs_edge &e, packet_consumer & pc)
913{
914 std::string content=create_cvs_cert_header();
915 if (!e.delta_base.inner()().empty())
916 { content+="+"+e.delta_base.inner()()+"\n";
917 }
918 for (cvs_manifest::const_iterator i=e.xfiles.begin(); i!=e.xfiles.end(); ++i)
919 { if (i->second->cvs_version.empty())
920 { W(F("blocking attempt to certify an empty CVS revision\n"
921 "(this is normal for a cvs_takeover of a locally modified tree)\n"));
922 return;
923 }
924 content+=i->second->cvs_version;
925 if (!i->second->keyword_substitution.empty())
926 content+="/"+i->second->keyword_substitution;
927 content+=" "+i->first+"\n";
928 }
929 cert t;
930 make_simple_cert(e.revision, cert_name(cvs_cert_name), content, app, t);
931 revision<cert> cc(t);
932 pc.consume_revision_cert(cc);
933}
934
935cvs_repository::cvs_repository(app_state &_app, const std::string &repository,
936 const std::string &module, const std::string &branch, bool connect)
937 : cvs_client(repository,module,branch,connect), app(_app),
938 file_id_ticker(), revision_ticker(), cvs_edges_ticker(),
939 remove_state(), sync_since(-1)
940{
941 file_id_ticker.reset(new ticker("file ids", "F", 10));
942 remove_state=remove_set.insert(file_state(0,"-",true)).first;
943 if (!app.sync_since().empty())
944 { sync_since=posix2time_t(app.sync_since());
945 N(sync_since<=time(0), F("Since lies in the future. Remember to specify time in UTC\n"));
946 }
947}
948
949static void test_key_availability(app_state &app)
950{
951 // early short-circuit to avoid failure after lots of work
952 rsa_keypair_id key;
953 get_user_key(key, app);
954 // Require the password early on, so that we don't do lots of work
955 // and then die.
956 app.signing_key = key;
957
958 N(app.lua.hook_persist_phrase_ok(),
959 F("need permission to store persistent passphrase (see hook persist_phrase_ok())"));
960 require_password(key, app);
961}
962
963std::set<cvs_edge>::iterator cvs_repository::last_known_revision()
964{ I(!edges.empty());
965 std::set<cvs_edge>::iterator now_iter=edges.end();
966 --now_iter;
967 return now_iter;
968}
969
970time_t cvs_repository::posix2time_t(std::string posix_format)
971{ std::string::size_type next_illegal=0;
972 MM(posix_format);
973 while ((next_illegal=posix_format.find_first_of("-:"))!=std::string::npos)
974 posix_format.erase(next_illegal,1);
975 boost::posix_time::ptime tmp= boost::posix_time::from_iso_string(posix_format);
976 boost::posix_time::time_duration dur= tmp
977 -boost::posix_time::ptime(boost::gregorian::date(1970,1,1),
978 boost::posix_time::time_duration(0,0,0,0));
979 return dur.total_seconds();
980}
981
982cvs_edge::cvs_edge(const revision_id &rid, app_state &app)
983 : changelog_valid(), time(), time2(), cm_delta_depth()
984{ revision=hexenc<id>(rid.inner());
985 // get author + date
986 std::vector< ::revision<cert> > edge_certs;
987 app.db.get_revision_certs(rid,edge_certs);
988 // erase_bogus_certs ?
989 for (std::vector< ::revision<cert> >::const_iterator c=edge_certs.begin();
990 c!=edge_certs.end();++c)
991 { cert_value value;
992 decode_base64(c->inner().value, value);
993 if (c->inner().name()==date_cert_name)
994 { L(F("date cert %s\n")%value());
995 time=time2=cvs_repository::posix2time_t(value());
996 }
997 else if (c->inner().name()==author_cert_name)
998 { author=value();
999 }
1000 else if (c->inner().name()==changelog_cert_name)
1001 { changelog=value();
1002 changelog_valid=true;
1003 }
1004 }
1005}
1006
1007std::set<cvs_edge>::iterator cvs_repository::commit(
1008 std::set<cvs_edge>::iterator parent, const revision_id &rid, bool &fail)
1009{ // check that it's the last one
1010 L(F("commit %s -> %s\n") % parent->revision % rid);
1011 { std::set<cvs_edge>::iterator test=parent;
1012 ++test;
1013 I(test==edges.end());
1014 }
1015 // a bit like process_certs
1016 cvs_edge e(rid,app);
1017
1018 revision_set rs;
1019 app.db.get_revision(rid, rs);
1020 std::vector<commit_arg> commits;
1021 unsigned cm_delta_depth=parent->cm_delta_depth;
1022
1023 for (edge_map::const_iterator j = rs.edges.begin();
1024 j != rs.edges.end();
1025 ++j)
1026 { if (!(edge_old_revision(j) == parent->revision))
1027 { L(F("%s != %s\n") % edge_old_revision(j) % parent->revision);
1028 continue;
1029 }
1030 const change_set &cs=edge_changes(j);
1031 N(cs.rearrangement.renamed_dirs.empty(),
1032 F("I can't commit directory renames yet\n"));
1033 N(cs.rearrangement.deleted_dirs.empty(),
1034 F("I can't commit directory deletions yet\n"));
1035 cvs_manifest parent_manifest=get_files(*parent);
1036
1037 for (std::set<file_path>::const_iterator i=cs.rearrangement.deleted_files.begin();
1038 i!=cs.rearrangement.deleted_files.end(); ++i)
1039 { commit_arg a;
1040 a.file=i->as_internal();
1041 cvs_manifest::const_iterator old=parent_manifest.find(a.file);
1042 I(old!=parent_manifest.end());
1043 a.removed=true;
1044 a.old_revision=old->second->cvs_version;
1045 a.keyword_substitution=old->second->keyword_substitution;
1046 commits.push_back(a);
1047 L(F("delete %s -%s %s\n") % a.file % a.old_revision % a.keyword_substitution);
1048 }
1049
1050 for (std::map<file_path,file_path>::const_iterator i
1051 =cs.rearrangement.renamed_files.begin();
1052 i!=cs.rearrangement.renamed_files.end(); ++i)
1053 { commit_arg a; // remove
1054 a.file=i->first.as_internal();
1055 cvs_manifest::const_iterator old=parent_manifest.find(a.file);
1056 I(old!=parent_manifest.end());
1057 a.removed=true;
1058 a.old_revision=old->second->cvs_version;
1059 a.keyword_substitution=old->second->keyword_substitution;
1060 commits.push_back(a);
1061 L(F("rename from %s -%s %s\n") % a.file % a.old_revision % a.keyword_substitution);
1062
1063 a=commit_arg(); // add
1064 a.file=i->second.as_internal();
1065 I(!old->second->sha1sum().empty());
1066 file_data dat;
1067 app.db.get_file_version(old->second->sha1sum,dat);
1068 a.new_content=dat.inner()();
1069 commits.push_back(a);
1070 L(F("rename to %s %d\n") % a.file % a.new_content.size());
1071 }
1072
1073 // added files also have a delta, so we can ignore this list
1074
1075 for (change_set::delta_map::const_iterator i=cs.deltas.begin();
1076 i!=cs.deltas.end(); ++i)
1077 {
1078 commit_arg a;
1079 a.file=i->first.as_internal();
1080 cvs_manifest::const_iterator old=parent_manifest.find(a.file);
1081 if (old!=parent_manifest.end())
1082 { a.old_revision=old->second->cvs_version;
1083 a.keyword_substitution=old->second->keyword_substitution;
1084 }
1085 file_data dat;
1086 app.db.get_file_version(i->second.second,dat);
1087 a.new_content=dat.inner()();
1088 commits.push_back(a);
1089 L(F("delta %s %s %s %d\n") % a.file % a.old_revision % a.keyword_substitution
1090 % a.new_content.size());
1091 }
1092
1093 if (commits.empty())
1094 { W(F("revision %s: nothing to commit") % e.revision());
1095 packet_db_writer dbw(app);
1096 e.cm_delta_depth=++cm_delta_depth;
1097 e.delta_base=parent->revision;
1098 cert_cvs(e, dbw);
1099 revision_lookup[e.revision]=edges.insert(e).first;
1100 fail=false;
1101 return --(edges.end());
1102 }
1103 std::string changelog;
1104 changelog=e.changelog+"\nmonotone "+e.author+" "
1105 +cvs_client::time_t2rfc822(e.time)+" "+e.revision()+"\n";
1106 // gather information CVS does not know about into the changelog
1107 changelog+=gather_merge_information(e.revision);
1108 std::map<std::string,std::pair<std::string,std::string> > result
1109 =Commit(changelog,e.time,commits);
1110 if (result.empty()) { fail=true; return edges.end(); }
1111
1112 e.delta_base=parent->revision;
1113
1114 for (std::map<std::string,std::pair<std::string,std::string> >::const_iterator
1115 i=result.begin(); i!=result.end(); ++i)
1116 { if (i->second.first.empty())
1117 { e.xfiles[i->first]=remove_state;
1118 }
1119 else
1120 { file_state fs(e.time,i->second.first);
1121 fs.log_msg=e.changelog;
1122 fs.author=e.author;
1123 fs.keyword_substitution=i->second.second;
1124 change_set::delta_map::const_iterator mydelta=cs.deltas.find(file_path_internal(i->first));
1125 I(mydelta!=cs.deltas.end());
1126 fs.sha1sum=mydelta->second.second.inner();
1127 std::pair<std::set<file_state>::iterator,bool> newelem=
1128 files[i->first].known_states.insert(fs);
1129 I(newelem.second);
1130 e.xfiles[i->first]=newelem.first;
1131 }
1132 }
1133 packet_db_writer dbw(app);
1134 if (cm_delta_depth+1>=cvs_edge::cm_max_delta_depth)
1135 { get_files(e);
1136 cm_delta_depth=0;
1137 }
1138 else
1139 e.cm_delta_depth=++cm_delta_depth;
1140 cert_cvs(e, dbw);
1141 revision_lookup[e.revision]=edges.insert(e).first;
1142 if (global_sanity.debug) L(F("%s") % debug());
1143 fail=false;
1144 return --(edges.end());
1145 }
1146 W(F("no matching parent found\n"));
1147 fail=true;
1148 return edges.end();
1149}
1150
1151std::string cvs_repository::gather_merge_information(revision_id const& id, int depth)
1152{ if (!depth) return "";
1153 std::set<revision_id> parents;
1154 app.db.get_revision_parents(id,parents);
1155 std::string result;
1156 for (std::set<revision_id>::const_iterator i=parents.begin();i!=parents.end();++i)
1157 { std::vector< revision<cert> > certs;
1158 if (*i==revision_id()) continue;
1159 app.db.get_revision_certs(*i,certs);
1160 std::vector< revision<cert> >::const_iterator j=certs.begin();
1161 std::string to_match=create_cvs_cert_header();
1162 for (;j!=certs.end();++j)
1163 { if (j->inner().name()!=cvs_cert_name) continue;
1164 cert_value value;
1165 decode_base64(j->inner().value, value);
1166 if (value().size()<to_match.size()) continue;
1167 if (value().substr(0,to_match.size())!=to_match) continue;
1168 break;
1169 }
1170 // this revision is already in _this_ repository
1171 if (j!=certs.end()) continue;
1172
1173 std::string author,changelog;
1174 time_t date=0;
1175 for (j=certs.begin();j!=certs.end();++j)
1176 { cert_value value;
1177 decode_base64(j->inner().value, value);
1178 if (j->inner().name()==date_cert_name)
1179 { date=cvs_repository::posix2time_t(value());
1180 }
1181 else if (j->inner().name()==author_cert_name)
1182 { author=value();
1183 }
1184 else if (j->inner().name()==changelog_cert_name)
1185 { changelog=value();
1186 }
1187 }
1188 result+="-------------------\n"
1189 +changelog+"\nmonotone "+author+" "
1190 +cvs_client::time_t2rfc822(date)+" "+i->inner()()+"\n";
1191 result+=gather_merge_information(*i,depth-1);
1192 }
1193 return result;
1194}
1195
1196void cvs_repository::commit()
1197{ retrieve_modules();
1198 std::set<cvs_edge>::iterator now_iter=last_known_revision();
1199 while (now_iter!=edges.end())
1200 { const cvs_edge &now=*now_iter;
1201 I(!now.revision().empty());
1202
1203 L(F("looking for children of revision %s\n") % now.revision);
1204 std::set<revision_id> children;
1205 app.db.get_revision_children(now.revision, children);
1206
1207 if (!app.branch_name().empty())
1208 { base64<cert_value> value;
1209 encode_base64(cert_value(app.branch_name()), value);
1210 // ignore revisions not belonging to the specified branch
1211 for (std::set<revision_id>::iterator i=children.begin();
1212 i!=children.end();)
1213 { std::vector< revision<cert> > certs;
1214 app.db.get_revision_certs(*i,branch_cert_name,value,certs);
1215 std::set<revision_id>::iterator help=i;
1216 ++help;
1217 if (certs.empty()) children.erase(i);
1218 i=help;
1219 }
1220 }
1221 if (children.empty()) return;
1222 revision_id next;
1223 if (children.size()>1) // && !ap.revision_selectors.size())
1224 { for (std::vector<utf8>::const_iterator i=app.revision_selectors.begin();
1225 i!=app.revision_selectors.end();++i)
1226 { for (std::set<revision_id>::const_iterator j=children.begin();
1227 j!=children.end();++j)
1228 { if (revision_id(hexenc<id>((*i)()))==*j)
1229 { next=*j;
1230 break;
1231 }
1232 }
1233 }
1234 if (next.inner()().empty())
1235 { W(F("several children found for %s:\n") % now.revision);
1236 for (std::set<revision_id>::const_iterator i=children.begin();
1237 i!=children.end();++i)
1238 { W(F("%s\n") % *i);
1239 }
1240 W(F("please specify direction using --revision\n"));
1241 return;
1242 }
1243 }
1244 else next=*children.begin();
1245 bool fail=bool();
1246 now_iter=commit(now_iter,next,fail);
1247
1248 if (!fail)
1249 P(F("checked %s into cvs repository") % now.revision);
1250 // we'd better seperate the commits so that ordering them is possible
1251 if (now_iter!=edges.end()) sleep(2);
1252 }
1253 store_modules();
1254}
1255
1256// look for _any_ cvs cert in the given monotone branch and assign
1257// its value to repository, module, branch
1258
1259// this is somewhat clumsy ... but works well enough
1260static void guess_repository(std::string &repository, std::string &module,
1261 std::string & branch,
1262 std::vector< revision<cert> > &certs, app_state &app)
1263{ I(!app.branch_name().empty());
1264 app.db.get_revision_certs(cvs_cert_name, certs);
1265 // erase_bogus_certs ?
1266 std::vector< revision<cert> > branch_certs;
1267 base64<cert_value> branch_value;
1268 encode_base64(cert_value(app.branch_name()), branch_value);
1269 app.db.get_revision_certs(branch_cert_name, branch_value, branch_certs);
1270 // use a set to gain speed? scan the smaller vector to gain speed?
1271 for (std::vector< revision<cert> >::const_iterator ci=certs.begin();
1272 ci!=certs.end();++ci)
1273 for (std::vector< revision<cert> >::const_iterator bi=branch_certs.begin();
1274 bi!=branch_certs.end();++bi)
1275 { // actually this finds an arbitrary element of the set intersection
1276 if (ci->inner().ident==bi->inner().ident)
1277 { try
1278 { cvs_repository::parse_cvs_cert_header(*ci,repository,module,branch);
1279 if (branch.empty())
1280 L(F("using module '%s' in repository '%s'\n") % module % repository);
1281 else
1282 L(F("using branch '%s' of module '%s' in repository '%s'\n")
1283 % branch % module % repository);
1284 goto break_outer;
1285 }
1286 catch (std::exception &e)
1287 { W(F("exception %s on revision %s\n") % e.what() % ci->inner().ident);
1288 }
1289 catch (informative_failure &e)
1290 { W(F("exception %s on revision %s\n") % e.what % ci->inner().ident);
1291 }
1292 }
1293 }
1294 break_outer: ;
1295 N(!module.empty(), F("No cvs cert in this branch, please specify repository and module"));
1296}
1297
1298void cvs_sync::push(const std::string &_repository, const std::string &_module,
1299 std::string const& _branch, app_state &app)
1300{ test_key_availability(app);
1301 // make the variables changeable
1302 std::string repository=_repository, module=_module, branch=_branch;
1303 std::vector< revision<cert> > certs;
1304 if (repository.empty() || module.empty())
1305 guess_repository(repository, module, branch, certs, app);
1306 cvs_sync::cvs_repository repo(app,repository,module,branch);
1307// turned off for DEBUGGING
1308 if (!getenv("CVS_CLIENT_LOG"))
1309 repo.GzipStream(3);
1310 transaction_guard guard(app.db);
1311
1312 if (certs.empty())
1313 app.db.get_revision_certs(cvs_cert_name, certs);
1314 repo.process_certs(certs);
1315
1316 N(!repo.empty(),
1317 F("no revision certs for this repository/module\n"));
1318
1319 repo.commit();
1320
1321 guard.commit();
1322}
1323
1324void cvs_sync::pull(const std::string &_repository, const std::string &_module,
1325 std::string const& _branch, app_state &app)
1326{ test_key_availability(app);
1327 // make the variables changeable
1328 std::string repository=_repository, module=_module, branch=_branch;
1329
1330 std::vector< revision<cert> > certs;
1331
1332 if (repository.empty() || module.empty())
1333 guess_repository(repository, module, branch, certs, app);
1334 cvs_sync::cvs_repository repo(app,repository,module,branch);
1335// turn compression on when not DEBUGGING
1336 if (!getenv("CVS_CLIENT_LOG"))
1337 repo.GzipStream(3);
1338 transaction_guard guard(app.db);
1339
1340 if (certs.empty()) app.db.get_revision_certs(cvs_cert_name, certs);
1341 if (!app.cvspull_full) repo.process_certs(certs);
1342
1343 // initial checkout
1344 if (repo.empty())
1345 repo.prime();
1346 else repo.update();
1347
1348 guard.commit();
1349}
1350
1351cvs_file_state cvs_repository::remember(std::set<file_state> &s,const file_state &fs, std::string const& filename)
1352{ for (std::set<file_state>::iterator i=s.begin();i!=s.end();++i)
1353 { if (i->cvs_version==fs.cvs_version)
1354 { if (i->since_when>fs.since_when) // i->since_when has to be the minimum
1355 const_cast<time_t&>(i->since_when)=fs.since_when;
1356 static file_id emptysha1sum;
1357 if (emptysha1sum.inner()().empty())
1358 calculate_ident(data(),const_cast<hexenc<id>&>(emptysha1sum.inner()));
1359 if (i->log_msg=="last cvs update (modified)"
1360 && i->sha1sum==emptysha1sum.inner()
1361 && i->author==("unknown@"+host))
1362 { W(F("replacing fake contents for %s V%s\n")
1363 % filename % i->cvs_version);
1364 const_cast<hexenc<id>&>(i->sha1sum)=fs.sha1sum;
1365 const_cast<std::string&>(i->log_msg)=fs.log_msg;
1366 }
1367 return i;
1368 }
1369 }
1370 std::pair<cvs_file_state,bool> iter=s.insert(fs);
1371 I(iter.second);
1372 return iter.first;
1373}
1374
1375void cvs_repository::process_certs(const std::vector< revision<cert> > &certs)
1376{
1377 std::auto_ptr<ticker> cert_ticker;
1378 cert_ticker.reset(new ticker("cvs certs", "C", 10));
1379
1380 std::string needed_cert=create_cvs_cert_header();
1381 for (vector<revision<cert> >::const_iterator i=certs.begin(); i!=certs.end(); ++i)
1382 { // populate data structure using these certs
1383 cert_value cvs_revisions;
1384 decode_base64(i->inner().value, cvs_revisions);
1385 if (cvs_revisions().size()>needed_cert.size()
1386 && (cvs_revisions().substr(0,needed_cert.size())==needed_cert))
1387 { // parse and add the cert
1388 ++(*cert_ticker);
1389 cvs_edge e(i->inner().ident,app);
1390
1391 piece::piece_table pieces;
1392 // in Zeilen aufteilen
1393 piece::index_deltatext(cvs_revisions(),pieces);
1394 I(!pieces.empty());
1395 manifest_id mid;
1396 app.db.get_revision_manifest(i->inner().ident,mid);
1397 manifest_map manifest;
1398 app.db.get_manifest(mid,manifest);
1399 // manifest;
1400 piece::piece_table::const_iterator p=pieces.begin()+1;
1401 if ((**p)[0]=='+') // this is a delta encoded manifest
1402 { hexenc<id> h=(**p).substr(1,40); // remember to omit the trailing \n
1403 e.delta_base=revision_id(h);
1404 ++p;
1405 }
1406 for (;p!=pieces.end();++p)
1407 { std::string line=**p;
1408 I(!line.empty());
1409 I(line[line.size()-1]=='\n');
1410 line.erase(line.size()-1,1);
1411 // the format is "<revsion>[/<keyword_substitution>] <path>\n"
1412 // e.g. "1.1 .cvsignore", "1.43/-kb test.png"
1413 std::string::size_type space=line.find(' ');
1414 I(space!=std::string::npos);
1415 std::string monotone_path=line.substr(space+1);
1416 std::string path=monotone_path;
1417 // look for the optional initial slash separating the keyword mode
1418 std::string::size_type slash=line.find('/');
1419 if (slash==std::string::npos || slash>space)
1420 slash=space;
1421
1422 file_state fs;
1423 fs.since_when=e.time;
1424 fs.cvs_version=line.substr(0,slash);
1425 if (space!=slash)
1426 fs.keyword_substitution=line.substr(slash+1,space-(slash+1));
1427 if (fs.cvs_version=="-") // delta encoded: remove
1428 { I(!e.delta_base.inner()().empty());
1429 fs.log_msg=e.changelog;
1430 fs.author=e.author;
1431 fs.dead=true;
1432 cvs_file_state cfs=remember(files[path].known_states,fs,path);
1433 e.xfiles.insert(std::make_pair(path,cfs)); // remove_state));
1434 }
1435 else
1436 { manifest_map::const_iterator iter_file_id=manifest.find(file_path_internal(monotone_path));
1437 I(iter_file_id!=manifest.end());
1438 fs.sha1sum=iter_file_id->second.inner();
1439 fs.log_msg=e.changelog;
1440 fs.author=e.author;
1441 cvs_file_state cfs=remember(files[path].known_states,fs,path);
1442 e.xfiles.insert(std::make_pair(path,cfs));
1443 }
1444 }
1445 piece::reset();
1446 revision_lookup[e.revision]=edges.insert(e).first;
1447 }
1448 else L(F("cvs cert %s ignored (!=%s)") % cvs_revisions % needed_cert);
1449 }
1450 // because some manifests might have been absolute (not delta encoded)
1451 // we possibly did not notice removes. check for them
1452 std::set<cvs_edge>::const_iterator last=edges.end();
1453 for (std::set<cvs_edge>::const_iterator i=edges.begin();
1454 i!=edges.end();++i)
1455 { if (last!=edges.end() && i->delta_base.inner()().empty())
1456 try
1457 { cvs_manifest old=get_files(*last),new_m=get_files(*i);
1458 for (cvs_manifest::iterator j=old.begin();j!=old.end();++j)
1459 { cvs_manifest::iterator new_iter=new_m.find(j->first);
1460 if (new_iter==new_m.end()) // this file get's removed here
1461 { file_state fs;
1462 fs.since_when=i->time;
1463 cvs_revision_nr rev=j->second->cvs_version;
1464 ++rev;
1465 fs.cvs_version=rev.get_string();
1466 fs.log_msg=i->changelog;
1467 fs.author=i->author;
1468 fs.dead=true;
1469 L(F("file %s gets removed at %s\n") % j->first % i->revision());
1470 remember(files[j->first].known_states,fs,j->first);
1471 }
1472 }
1473 }
1474 catch (informative_failure &e)
1475 { W(F("failed to reconstruct CVS revision structure:\n"
1476 "%s\n"
1477 "I can't figure out which files got removed on %s->%s\n")
1478 % e.what % last->revision() % i->revision());
1479 }
1480 last=i;
1481 }
1482 if (global_sanity.debug) L(F("%s") % debug());
1483}
1484
1485struct cvs_repository::update_cb : cvs_client::update_callbacks
1486{ cvs_repository &repo;
1487 std::vector<cvs_client::update> &results;
1488
1489 update_cb(cvs_repository &r, std::vector<cvs_client::update> &re)
1490 : repo(r), results(re) {}
1491 virtual void operator()(const cvs_client::update &u) const
1492 { results.push_back(u);
1493 // perhaps store the file contents into the db to save storage
1494 }
1495};
1496
1497void cvs_repository::update()
1498{ retrieve_modules();
1499 std::set<cvs_edge>::iterator now_iter=last_known_revision();
1500 const cvs_edge &now=*now_iter;
1501 I(!now.revision().empty());
1502 std::vector<update_args> file_revisions;
1503 std::vector<cvs_client::update> results;
1504 const cvs_manifest &m=get_files(now);
1505 file_revisions.reserve(m.size());
1506 for (cvs_manifest::const_iterator i=m.begin();i!=m.end();++i)
1507 file_revisions.push_back(update_args(i->first,i->second->cvs_version,
1508 std::string(),i->second->keyword_substitution));
1509 Update(file_revisions,update_cb(*this,results));
1510 for (std::vector<cvs_client::update>::const_iterator i=results.begin();i!=results.end();++i)
1511 { // 2do: use tags
1512 cvs_manifest::const_iterator now_file=m.find(i->file);
1513 std::string last_known_revision;
1514 std::map<std::string,file_history>::iterator f=files.find(i->file);
1515
1516 if (now_file!=m.end())
1517 { last_known_revision=now_file->second->cvs_version;
1518 I(f!=files.end());
1519 }
1520 else // the file is not present in our last import
1521 // e.g. the file is currently dead but we know an old revision
1522 { if (f!=files.end() // we know anything about this file
1523 && !f->second.known_states.empty()) // we have at least one known file revision
1524 { std::set<file_state>::const_iterator last=f->second.known_states.end();
1525 --last;
1526 last_known_revision=last->cvs_version;
1527 }
1528 else f=files.insert(std::make_pair(i->file,file_history())).first;
1529 }
1530 if (last_known_revision=="1.1.1.1")
1531 last_known_revision="1.1";
1532 std::set<file_state>::const_iterator last=f->second.known_states.end();
1533 if (last!=f->second.known_states.begin()) --last;
1534
1535 if (last_known_revision.empty())
1536 Log(prime_log_cb(*this,f),i->file.c_str(),"-b","-N",(void*)0);
1537 else
1538 // -b causes -r to get ignored on 0.12
1539 Log(prime_log_cb(*this,f),i->file.c_str(),/*"-b",*/"-N",
1540 ("-r"+last_known_revision+"::").c_str(),(void*)0);
1541
1542 std::string file_contents,initial_contents;
1543 if(last==f->second.known_states.end() || last->dead)
1544 { last=f->second.known_states.begin();
1545 I(last!=f->second.known_states.end());
1546 std::set<file_state>::iterator s2=last;
1547 cvs_client::update c=Update(i->file,s2->cvs_version);
1548 store_checkout(s2,c,file_contents);
1549 }
1550 else
1551 { I(!last->sha1sum().empty());
1552 file_data dat;
1553 app.db.get_file_version(last->sha1sum,dat);
1554 file_contents=dat.inner()();
1555 initial_contents=file_contents;
1556 }
1557 for (std::set<file_state>::const_iterator s=last;
1558 s!=f->second.known_states.end();++s)
1559 { std::set<file_state>::const_iterator s2=s;
1560 ++s2;
1561 if (s2==f->second.known_states.end()) break;
1562 if (s2->cvs_version==i->new_revision)
1563 { // we do not need to ask the host, we already did ...
1564 try
1565 { store_update(last,s2,*i,initial_contents);
1566 } catch (informative_failure &e)
1567 { W(F("error during update: %s\n") % e.what);
1568 // we _might_ try to use store delta ...
1569 cvs_client::update c=Update(i->file,s2->cvs_version);
1570 const_cast<std::string&>(s2->md5sum)="";
1571 const_cast<unsigned&>(s2->patchsize)=0;
1572 store_contents(c.contents, const_cast<hexenc<id>&>(s2->sha1sum));
1573 const_cast<unsigned&>(s2->size)=c.contents.size();
1574 const_cast<std::string&>(s2->keyword_substitution)=c.keyword_substitution;
1575 }
1576 break;
1577 }
1578 else
1579 { update(s,s2,i->file,file_contents);
1580 }
1581 }
1582 }
1583 drop_connection();
1584
1585 std::set<cvs_edge>::iterator dummy_iter=now_iter;
1586 ++dummy_iter;
1587 if (dummy_iter!=edges.end())
1588 {
1589 join_edge_parts(dummy_iter);
1590 fill_manifests(dummy_iter);
1591 if (global_sanity.debug) L(F("%s") % debug());
1592 commit_revisions(dummy_iter);
1593 }
1594
1595 store_modules();
1596}
1597
1598static void apply_manifest_delta(cvs_manifest &base,const cvs_manifest &delta)
1599{ L(F("apply_manifest_delta: base %d delta %d\n") % base.size() % delta.size());
1600 for (cvs_manifest::const_iterator i=delta.begin(); i!=delta.end(); ++i)
1601 { if (i->second->dead)
1602 { cvs_manifest::iterator to_remove=base.find(i->first);
1603 I(to_remove!=base.end());
1604 base.erase(to_remove);
1605 }
1606 else
1607 base[i->first]=i->second;
1608 }
1609 L(F("apply_manifest_delta: result %d\n") % base.size());
1610}
1611
1612const cvs_manifest &cvs_repository::get_files(const cvs_edge &e)
1613{ L(F("get_files(%s %s) %s %d\n") % time_t2human(e.time) % e.revision % e.delta_base % e.xfiles.size());
1614 if (!e.delta_base.inner()().empty())
1615 { cvs_manifest calculated_manifest;
1616 // this is non-recursive by reason ...
1617 const cvs_edge *current=&e;
1618 std::vector<const cvs_edge *> deltas;
1619 while (!current->delta_base.inner()().empty())
1620 { L(F("get_files: looking for base rev %s\n") % current->delta_base);
1621 ++e.cm_delta_depth;
1622 deltas.push_back(current);
1623 std::map<revision_id,std::set<cvs_edge>::iterator>::const_iterator
1624 cache_item=revision_lookup.find(current->delta_base);
1625 E(cache_item!=revision_lookup.end(),
1626 F("missing cvs cert on base revision %s\n") % current->delta_base);
1627 current=&*(cache_item->second);
1628 }
1629 I(current->delta_base.inner()().empty());
1630 calculated_manifest=current->xfiles;
1631 for (std::vector<const cvs_edge *>::const_reverse_iterator i=deltas.rbegin();
1632 i!=static_cast<std::vector<const cvs_edge *>::const_reverse_iterator>(deltas.rend());
1633 ++i)
1634 apply_manifest_delta(calculated_manifest,(*i)->xfiles);
1635 e.xfiles=calculated_manifest;
1636 e.delta_base=revision_id();
1637 }
1638 return e.xfiles;
1639}
1640
1641void cvs_sync::debug(const std::string &command, const std::string &arg,
1642 app_state &app)
1643{
1644 // we default to the first repository found (which might not be what you wanted)
1645 if (command=="manifest" && arg.size()==constants::idlen)
1646 { revision_id rid(arg);
1647 // easy but not very efficient way, since we parse all revisions to decode
1648 // the delta encoding
1649 // (perhaps?) it would be better to retrieve needed revisions recursively
1650 std::vector< revision<cert> > certs;
1651 app.db.get_revision_certs(rid,cvs_cert_name,certs);
1652 // erase_bogus_certs ?
1653 N(!certs.empty(),F("revision has no 'cvs-revisions' certificates\n"));
1654
1655 std::string repository,module,branch;
1656 cvs_repository::parse_cvs_cert_header(certs.front(), repository, module, branch);
1657 cvs_sync::cvs_repository repo(app,repository,module,branch,false);
1658 app.db.get_revision_certs(cvs_cert_name, certs);
1659 repo.process_certs(certs);
1660 std::cout << debug_manifest(repo.get_files(rid));
1661 }
1662 else if (command=="history") // filename or empty
1663 {
1664 std::string repository, module, branch;
1665 std::vector< revision<cert> > certs;
1666 guess_repository(repository, module, branch, certs, app);
1667 cvs_sync::cvs_repository repo(app,repository,module,branch,false);
1668 repo.process_certs(certs);
1669 if (arg.empty()) std::cout << repo.debug();
1670 else std::cout << repo.debug_file(arg);
1671 }
1672}
1673
1674const cvs_manifest &cvs_repository::get_files(const revision_id &rid)
1675{ std::map<revision_id,std::set<cvs_edge>::iterator>::const_iterator
1676 cache_item=revision_lookup.find(rid);
1677 I(cache_item!=revision_lookup.end());
1678 return get_files(*(cache_item->second));
1679}
1680
1681struct cvs_client::checkout cvs_repository::CheckOut2(const std::string &file, const std::string &revision)
1682{ try
1683 { return CheckOut(file,revision);
1684 } catch (oops &e)
1685 { W(F("trying to reconnect, perhaps the server is confused\n"));
1686 reconnect();
1687 return CheckOut(file,revision);
1688 }
1689}
1690
1691void cvs_client::validate_path(const std::string &local, const std::string &server)
1692{ for (std::map<std::string,std::string>::const_iterator i=server_dir.begin();
1693 i!=server_dir.end();++i)
1694 { if (local.substr(0,i->first.size())==i->first
1695 && server.substr(0,i->second.size())==i->second
1696 && local.substr(i->first.size())==server.substr(i->second.size()))
1697 return;
1698 }
1699 server_dir[local]=server;
1700}
1701
1702void cvs_repository::takeover_dir(const std::string &path)
1703{ // remember the server path for this subdirectory
1704 MM(path);
1705 { std::string repository;
1706 std::ifstream cvs_repository((path+"CVS/Repository").c_str());
1707 N(cvs_repository.good(), F("can't open %sCVS/Repository\n") % path);
1708 std::getline(cvs_repository,repository);
1709 I(!repository.empty());
1710 if (repository[0]!='/') repository=root+"/"+repository;
1711 validate_path(path,repository+"/");
1712 }
1713 std::ifstream cvs_Entries((path+"CVS/Entries").c_str());
1714 N(cvs_Entries.good(),
1715 F("can't open %s\n") % (path+"CVS/Entries"));
1716 L(F("takeover_dir %s\n") % path);
1717 static hexenc<id> empty_file;
1718 while (true)
1719 { std::string line;
1720 std::getline(cvs_Entries,line);
1721 if (!cvs_Entries.good()) break;
1722 if (!line.empty())
1723 { std::vector<std::string> parts;
1724 MM(line);
1725 stringtok(parts,line,"/");
1726 // empty last part will not get created
1727 if (parts.size()==5) parts.push_back(std::string());
1728 if (parts.size()!=6)
1729 { W(F("entry line with %d components '%s'\n") % parts.size() %line);
1730 continue;
1731 }
1732 if (parts[0]=="D")
1733 { takeover_dir(path+parts[1]+"/");
1734 }
1735 else // file
1736 { // remember permissions, store file contents
1737 I(parts[0].empty());
1738 std::string filename=path+parts[1];
1739 I(!access(filename.c_str(),R_OK));
1740 // parts[2]=version
1741 // parts[3]=date
1742 // parts[4]=keyword subst
1743 // parts[5]='T'tag
1744 time_t modtime=-1;
1745 try
1746 { modtime=cvs_client::Entries2time_t(parts[3]);
1747 } catch (std::exception &e) {}
1748 catch (informative_failure &e) {}
1749 I(files.find(filename)==files.end());
1750 std::map<std::string,file_history>::iterator f
1751 =files.insert(std::make_pair(filename,file_history())).first;
1752 file_state fs(modtime,parts[2]);
1753 fs.author="unknown";
1754 fs.keyword_substitution=parts[4];
1755 { struct stat sbuf;
1756 I(!stat(filename.c_str(), &sbuf));
1757 if (sbuf.st_mtime!=modtime)
1758 { L(F("modified %s %u %u\n") % filename % modtime % sbuf.st_mtime);
1759 fs.log_msg="partially overwritten content from last update";
1760 store_contents(std::string(), fs.sha1sum);
1761 f->second.known_states.insert(fs);
1762
1763 fs.since_when=time(NULL);
1764 fs.cvs_version=std::string();
1765 }
1766 }
1767 // @@ import the file and check whether it is (un-)changed
1768 fs.log_msg="initial cvs content";
1769 data new_data;
1770 read_localized_data(file_path_internal(filename), new_data, app.lua);
1771 store_contents(new_data, fs.sha1sum);
1772 f->second.known_states.insert(fs);
1773 }
1774 }
1775 }
1776}
1777
1778void cvs_repository::takeover()
1779{ takeover_dir("");
1780
1781 bool need_second=false;
1782 cvs_edge e1,e2;
1783 e1.time=0;
1784 e1.changelog="last cvs update (modified)";
1785 e1.changelog_valid=true;
1786 e1.author="unknown";
1787 e2.time=time(NULL);
1788 e2.changelog="cvs takeover";
1789 e2.changelog_valid=true;
1790 e2.author="unknown";
1791 for (std::map<std::string,file_history>::const_iterator i=files.begin();
1792 i!=files.end();++i)
1793 { cvs_file_state first,second;
1794 first=i->second.known_states.begin();
1795 I(first!=i->second.known_states.end());
1796 second=first;
1797 ++second;
1798 if (second==i->second.known_states.end()) second=first;
1799 else if (!need_second) need_second=true;
1800 if (e1.time<first->since_when) e1.time=first->since_when;
1801 e1.xfiles[i->first]=first;
1802 e2.xfiles[i->first]=second;
1803 // at most two states known !
1804 I((++second)==i->second.known_states.end());
1805 }
1806 if (!need_second) e1.changelog=e2.changelog;
1807 edges.insert(e1);
1808 if (need_second)
1809 { edges.insert(e2);
1810 }
1811 // commit them all
1812 commit_revisions(edges.begin());
1813 // create a MT directory
1814 app.create_working_copy(system_path("."));
1815
1816// like in commit ?
1817// update_any_attrs(app);
1818 put_revision_id((--edges.end())->revision);
1819// maybe_update_inodeprints(app);
1820
1821 store_modules();
1822}
1823
1824// read in directory put into db
1825void cvs_sync::takeover(app_state &app, const std::string &_module)
1826{ std::string root,module=_module,branch;
1827
1828 N(access("MT",F_OK),F("Found a MT file or directory, already under monotone's control?"));
1829 { fstream cvs_root("CVS/Root");
1830 N(cvs_root.good(),
1831 F("can't open ./CVS/Root, please change into the working directory\n"));
1832 std::getline(cvs_root,root);
1833 }
1834 { fstream cvs_branch("CVS/Tag");
1835 if (cvs_branch.good())
1836 { std::getline(cvs_branch,branch);
1837 MM(branch);
1838 I(!branch.empty());
1839 I(branch[0]=='T');
1840 branch.erase(0,1);
1841 }
1842 }
1843 if (module.empty())
1844 { fstream cvs_repository("CVS/Repository");
1845 N(cvs_repository.good(),
1846 F("can't open ./CVS/Repository\n"));
1847 std::getline(cvs_repository,module);
1848 W(F("Guessing '%s' as the module name\n") % module);
1849 }
1850 test_key_availability(app);
1851 cvs_sync::cvs_repository repo(app,root,module,branch,false);
1852 // FIXME? check that directory layout matches the module structure
1853 repo.takeover();
1854}
1855
1856void cvs_repository::store_modules()
1857{ const std::map<std::string,std::string> &sd=GetServerDir();
1858 std::string value;
1859 std::string name=create_cvs_cert_header();
1860 for (std::map<std::string,std::string>::const_iterator i=sd.begin();
1861 i!=sd.end();++i)
1862 { value+=i->first+"\t"+i->second+"\n";
1863 }
1864 std::pair<var_domain,var_name> key(var_domain("cvs-server-path"), var_name(name));
1865 var_value oldval;
1866 try
1867 { app.db.get_var(key,oldval);
1868 } catch (...) {}
1869 if (oldval()!=value) app.db.set_var(key, value);
1870}
1871
1872void cvs_repository::retrieve_modules()
1873{ if (!GetServerDir().empty()) return;
1874 std::string name=create_cvs_cert_header();
1875 std::pair<var_domain,var_name> key(var_domain("cvs-server-path"), var_name(name));
1876 var_value value;
1877 try {
1878 app.db.get_var(key,value);
1879 } catch (...) { return; }
1880 std::map<std::string,std::string> sd;
1881 piece::piece_table pieces;
1882 std::string value_s=value();
1883 piece::index_deltatext(value_s,pieces);
1884 for (piece::piece_table::const_iterator p=pieces.begin();p!=pieces.end();++p)
1885 { std::string line=**p;
1886 MM(line);
1887 I(!line.empty());
1888 std::string::size_type tab=line.find('\t');
1889 I(tab!=std::string::npos);
1890 I(line[line.size()-1]=='\n');
1891 line.erase(line.size()-1,1);
1892 sd[line.substr(0,tab)]=line.substr(tab+1);
1893 }
1894 piece::reset();
1895 SetServerDir(sd);
1896}
1897
1898static std::string escape(std::string const& a)
1899{ std::string result;
1900 for (std::string::const_iterator i=a.begin();i!=a.end();++i)
1901 { if (*i=='\'') result+="\\'";
1902 else if (*i=='/') result+="\\/";
1903 else if (*i=='\\') result+="\\\\";
1904 else result+=*i;
1905 }
1906 return result;
1907}
1908
1909namespace
1910{
1911 namespace syms
1912 {
1913 symbol const set("set");
1914 symbol const attr("attr");
1915 symbol const value("value");
1916 }
1917}
1918
1919static void print_attr(basic_io::printer & pr, std::string const& file,
1920 std::string const& name, std::string const& value)
1921{
1922 basic_io::stanza st;
1923 st.push_str_pair(syms::set, file);
1924 st.push_str_pair(syms::attr, name);
1925 st.push_str_pair(syms::value, value);
1926 pr.print_stanza(st);
1927}
1928
1929void cvs_repository::migrate()
1930{
1931 retrieve_modules();
1932 I(!edges.empty());
1933 std::set<cvs_edge>::const_iterator e=--edges.end();
1934 I(!e->revision().empty()); // it's already in monotone
1935 // output the revision descriptor
1936 std::string domain="cvs";
1937 std::cout << "#!/bin/sh\n"
1938 "DATABASE=mtn.db\n"
1939 "OLD_REVISION="<< e->revision() <<"\n"
1940 "MTN=\"mtn\"\n"
1941 "REVISION=`$MTN -d$DATABASE automate select 'b:" << escape(app.branch_name())
1942 << "/a:" << escape(e->author) << "/d:" << time_t2monotone(e->time) << "'`\n"
1943 "# remove newline character\n"
1944 "REVISION=`echo $REVISION`\n"
1945 "echo revision $REVISION\n"
1946 "(echo -n 'l13:put_sync_info40:'$REVISION'"<< domain.size() << ':'
1947 << domain << "' ; cat <<EOF) | $MTN -d$DATABASE automate stdio\n";
1948 // modules, root, repository, branch
1949 std::ostringstream str;
1950 basic_io::printer printer(str);
1951 print_attr(printer, "", domain+":root", host+":"+root);
1952 print_attr(printer, "", domain+":module", module);
1953 if (!branch.empty())
1954 print_attr(printer, "", domain+":branch", branch);
1955 const std::map<std::string,std::string> &sd=GetServerDir();
1956 for (std::map<std::string,std::string>::const_iterator i=sd.begin();
1957 i!=sd.end();++i)
1958 { if (i->first.empty() || i->second!=root+"/"+module+"/")
1959 {
1960 std::string local_path=i->first;
1961 if (!local_path.empty() && local_path[local_path.size()-1]=='/')
1962 local_path.erase(local_path.size()-1,1);
1963 print_attr(printer, local_path, domain+":path", i->second);
1964 }
1965 }
1966 cvs_manifest m=get_files(*e);
1967 for (cvs_manifest::const_iterator i=m.begin(); i!=m.end(); ++i)
1968 { print_attr(printer, i->first, domain+":revision", i->second->cvs_version);
1969 if (!i->second->keyword_substitution.empty())
1970 print_attr(printer, i->first, domain+":keyword", i->second->keyword_substitution);
1971 }
1972 std::cout << str.str().size() << ':' << str.str() << "e\n"
1973 "EOF\n";
1974}
1975
1976void cvs_sync::migrate(app_state &app)
1977{
1978 std::string repository, module, branch;
1979
1980 std::vector< revision<cert> > certs;
1981
1982 I(!app.branch_name().empty());
1983 guess_repository(repository, module, branch, certs, app);
1984 I(!repository.empty());
1985 I(!module.empty());
1986 cvs_sync::cvs_repository repo(app,repository,module,branch,false);
1987 repo.process_certs(certs);
1988 repo.migrate();
1989}

Archive Download this file

Branches

Tags

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