1 //
2 // aegis - project change supervisor
3 // Copyright (C) 1991-2009, 2011, 2012 Peter Miller
4 // Copyright (C) 2007-2009 Walter Franzini
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or (at
9 // your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19 
20 #include <common/ac/assert.h>
21 #include <common/ac/ctype.h>
22 #include <common/ac/stdio.h>
23 #include <common/ac/string.h>
24 #include <common/ac/stdlib.h>
25 
26 #include <common/error.h>
27 #include <common/fstrcmp.h>
28 #include <common/itab.h>
29 #include <common/mem.h>
30 #include <common/sizeof.h>
31 #include <common/skip_unlucky.h>
32 #include <common/str_list.h>
33 #include <common/symtab.h>
34 #include <common/trace.h>
35 #include <libaegis/arglex2.h>
36 #include <libaegis/change/branch.h>
37 #include <libaegis/change/file.h>
38 #include <libaegis/change.h>
39 #include <libaegis/commit.h>
40 #include <libaegis/gonzo.h>
41 #include <libaegis/lock.h>
42 #include <libaegis/option.h>
43 #include <libaegis/os.h>
44 #include <libaegis/project.h>
45 #include <libaegis/project/history.h>
46 #include <libaegis/pstate.fmtgen.h>
47 #include <libaegis/sub.h>
48 #include <libaegis/undo.h>
49 #include <libaegis/user.h>
50 
51 
52 void
convert_to_new_format()53 project::convert_to_new_format()
54 {
55     trace(("project::convert_to_new_format(this = %p)\n{\n", this));
56     // Don't worry about is not being NULL, it won't contain much,
57     // and it will only be a memory leak when the project is first
58     // converted, and will never happen again.
59     pcp = change_alloc(this, TRUNK_CHANGE_NUMBER);
60     change_bind_new(pcp);
61     change_branch_new(pcp);
62 
63     //
64     // The new change is in the 'being developed'
65     // state, and will be forever.
66     //
67     assert(up);
68     cstate_history_ty *h = change_history_new(pcp, up);
69     h->what = cstate_history_what_new_change;
70     h = change_history_new(pcp, up);
71     h->what = cstate_history_what_develop_begin;
72     pcp->cstate_data->state = cstate_state_being_developed;
73 
74     //
75     // set the development directory
76     //
77     change_development_directory_set(pcp, home_path_get());
78 
79     //
80     // Copy the information from the old format of project state
81     // into the newly created change.  Remember to clear the old
82     // stuff out, so that it isn't written back to the file.
83     //
84     pstate_get();
85 
86     change_branch_umask_set(pcp, pstate_data->umask);
87     pstate_data->umask = 0;
88 
89     if (pstate_data->owner_name)
90     {
91         // this field is ignored
92         str_free(pstate_data->owner_name);
93         pstate_data->owner_name = 0;
94     }
95 
96     if (pstate_data->group_name)
97     {
98         // this field is ignored
99         str_free(pstate_data->group_name);
100         pstate_data->group_name = 0;
101     }
102 
103     if (pstate_data->description)
104     {
105         project_description_set(this, pstate_data->description);
106         str_free(pstate_data->description);
107         pstate_data->description = 0;
108     }
109 
110     change_branch_developer_may_review_set
111     (
112         pcp,
113         pstate_data->developer_may_review
114     );
115     pstate_data->developer_may_review = false;
116 
117     change_branch_developer_may_integrate_set
118     (
119         pcp,
120         pstate_data->developer_may_integrate
121     );
122     pstate_data->developer_may_integrate = false;
123 
124     change_branch_reviewer_may_integrate_set
125     (
126         pcp,
127         pstate_data->reviewer_may_integrate
128     );
129     pstate_data->reviewer_may_integrate = false;
130 
131     change_branch_developers_may_create_changes_set
132     (
133         pcp,
134         pstate_data->developers_may_create_changes
135     );
136     pstate_data->developers_may_create_changes = false;
137 
138     //
139     // Old-style projects alawys had regression tests exempt by default.
140     //
141     change_branch_default_test_regression_exemption_set(pcp, true);
142 
143     change_branch_forced_develop_begin_notify_command_set
144     (
145         pcp,
146         pstate_data->forced_develop_begin_notify_command
147     );
148     pstate_data->forced_develop_begin_notify_command = 0;
149 
150     change_branch_develop_end_notify_command_set
151     (
152         pcp,
153         pstate_data->develop_end_notify_command
154     );
155     pstate_data->develop_end_notify_command = 0;
156 
157     change_branch_develop_end_undo_notify_command_set
158     (
159         pcp,
160         pstate_data->develop_end_undo_notify_command
161     );
162     pstate_data->develop_end_undo_notify_command = 0;
163 
164     change_branch_review_pass_notify_command_set
165     (
166         pcp,
167         pstate_data->review_pass_notify_command
168     );
169     pstate_data->review_pass_notify_command = 0;
170 
171     change_branch_review_pass_undo_notify_command_set
172     (
173         pcp,
174         pstate_data->review_pass_undo_notify_command
175     );
176     pstate_data->review_pass_undo_notify_command = 0;
177 
178     change_branch_review_fail_notify_command_set
179     (
180         pcp,
181         pstate_data->review_fail_notify_command
182     );
183     pstate_data->review_fail_notify_command = 0;
184 
185     change_branch_integrate_pass_notify_command_set
186     (
187         pcp,
188         pstate_data->integrate_pass_notify_command
189     );
190     pstate_data->integrate_pass_notify_command = 0;
191 
192     change_branch_integrate_fail_notify_command_set
193     (
194         pcp,
195         pstate_data->integrate_fail_notify_command
196     );
197     pstate_data->integrate_fail_notify_command = 0;
198 
199     change_branch_default_development_directory_set
200     (
201         pcp,
202         pstate_data->default_development_directory
203     );
204     pstate_data->default_development_directory = 0;
205 
206     change_branch_default_test_exemption_set
207     (
208         pcp,
209         pstate_data->default_test_exemption
210     );
211     pstate_data->default_test_exemption = false;
212 
213     if (!pstate_data->copyright_years)
214     {
215         pstate_data->copyright_years =
216             (pstate_copyright_years_list_ty *)
217             pstate_copyright_years_list_type.alloc();
218     }
219     for (size_t j = 0; j < pstate_data->copyright_years->length; ++j)
220     {
221         change_copyright_year_append
222         (
223             pcp,
224             pstate_data->copyright_years->list[j]
225         );
226     }
227     pstate_copyright_years_list_type.free(pstate_data->copyright_years);
228     pstate_data->copyright_years = 0;
229 
230     // no explict next change number in new format
231     pstate_data->next_change_number = 0;
232 
233     // no explict next delta number in new format
234     pstate_data->next_delta_number = 0;
235 
236     if (!pstate_data->src)
237     {
238         pstate_data->src = (pstate_src_list_ty *)pstate_src_list_type.alloc();
239     }
240     for (size_t j = 0; j < pstate_data->src->length; ++j)
241     {
242         pstate_src_ty *sp1 = pstate_data->src->list[j];
243         if (!sp1->file_name)
244         {
245             sub_context_ty  *scp;
246 
247             yuck:
248             scp = sub_context_new();
249             sub_var_set_string(scp, "File_Name", pstate_path);
250             sub_var_set_charstar(scp, "FieLD_Name", "src");
251             project_fatal
252             (
253                 this,
254                 scp,
255                 i18n("$filename: corrupted \"$field_name\" field")
256             );
257             // NOTREACHED
258             sub_context_delete(scp);
259         }
260         fstate_src_ty *sp2 = pcp->file_new(sp1->file_name);
261         if (!(sp1->mask & pstate_src_usage_mask))
262             goto yuck;
263         sp2->usage = sp1->usage;
264         sp2->mask |= fstate_src_usage_mask;
265         sp2->action = file_action_create;
266         sp2->mask |= fstate_src_action_mask;
267         if (sp1->edit_number)
268         {
269             sp2->edit = (history_version_ty *)history_version_type.alloc();
270             sp2->edit->revision = str_copy(sp1->edit_number);
271             sp2->edit_origin =
272                 (history_version_ty *)history_version_type.alloc();
273             sp2->edit_origin->revision = str_copy(sp1->edit_number);
274         }
275         sp2->locked_by = sp1->locked_by;
276         sp2->about_to_be_created_by = sp1->about_to_be_created_by;
277         sp2->deleted_by = sp1->deleted_by;
278 
279         if (sp2->deleted_by)
280             sp2->action = file_action_remove;
281 
282         //
283         // This code must agree with the corresponding code in
284         // libaegis/change/file/fstate.c
285         //
286         switch (sp2->action)
287         {
288         case file_action_remove:
289         case file_action_transparent:
290             break;
291 
292         case file_action_create:
293         case file_action_modify:
294         case file_action_insulate:
295 #ifndef DEBUG
296         default:
297 #endif
298             if (sp2->about_to_be_created_by || sp2->about_to_be_copied_by)
299                 sp2->action = file_action_transparent;
300             break;
301         }
302     }
303     pstate_src_list_type.free(pstate_data->src);
304     pstate_data->src = 0;
305 
306     if (!pstate_data->history)
307     {
308         pstate_data->history =
309             (pstate_history_list_ty *)pstate_history_list_type.alloc();
310     }
311     for (size_t j = 0; j < pstate_data->history->length; ++j)
312     {
313         pstate_history_ty *hp;
314 
315         hp = pstate_data->history->list[j];
316         change_branch_history_new
317         (
318             pcp,
319             hp->delta_number,
320             hp->change_number,
321             0,
322             TIME_NOT_SET,
323             false
324         );
325     }
326     pstate_history_list_type.free(pstate_data->history);
327     pstate_data->history = 0;
328 
329     if (!pstate_data->change)
330     {
331         pstate_data->change =
332             (pstate_change_list_ty *)pstate_change_list_type.alloc();
333     }
334     for (size_t j = 0; j < pstate_data->change->length; ++j)
335     {
336         change_branch_change_add(pcp, pstate_data->change->list[j], 0);
337     }
338     pstate_change_list_type.free(pstate_data->change);
339     pstate_data->change = 0;
340 
341     //
342     // copy the staff across
343     //
344     pstate_get();
345     if (!pstate_data->administrator)
346     {
347         pstate_data->administrator =
348             (pstate_administrator_list_ty *)
349             pstate_administrator_list_type.alloc();
350     }
351     for (size_t j = 0; j < pstate_data->administrator->length; ++j)
352     {
353         change_branch_administrator_add
354         (
355             pcp,
356             pstate_data->administrator->list[j]
357         );
358     }
359     pstate_administrator_list_type.free(pstate_data->administrator);
360     pstate_data->administrator = 0;
361 
362     if (!pstate_data->developer)
363     {
364         pstate_data->developer =
365             (pstate_developer_list_ty *)pstate_developer_list_type.alloc();
366     }
367     for (size_t j = 0; j < pstate_data->developer->length; ++j)
368     {
369         change_branch_developer_add(pcp, pstate_data->developer->list[j]);
370     }
371     pstate_developer_list_type.free(pstate_data->developer);
372     pstate_data->developer = 0;
373 
374     if (!pstate_data->reviewer)
375     {
376         pstate_data->reviewer =
377             (pstate_reviewer_list_ty *)pstate_reviewer_list_type.alloc();
378     }
379     for (size_t j = 0; j < pstate_data->reviewer->length; ++j)
380     {
381         change_branch_reviewer_add(pcp, pstate_data->reviewer->list[j]);
382     }
383     pstate_reviewer_list_type.free(pstate_data->reviewer);
384     pstate_data->reviewer = 0;
385 
386     if (!pstate_data->integrator)
387     {
388         pstate_data->integrator =
389             (pstate_integrator_list_ty *)pstate_integrator_list_type.alloc();
390     }
391     for (size_t j = 0; j < pstate_data->integrator->length; ++j)
392     {
393         change_branch_integrator_add(pcp, pstate_data->integrator->list[j]);
394     }
395     pstate_integrator_list_type.free(pstate_data->integrator);
396     pstate_data->integrator = 0;
397 
398     if (pstate_data->currently_integrating_change)
399     {
400         change_current_integration_set
401         (
402             pcp,
403             pstate_data->currently_integrating_change
404         );
405         pstate_data->currently_integrating_change = 0;
406     }
407 
408     //
409     // These should actually be acted on to create a change tree,
410     // but that will have to wait for a future aegis change, when
411     // branching is working properly.
412     //
413     // pstate_data->version_major = 0;
414     // pstate_data->version_minor = 0;
415     //
416 
417     if (pstate_data->version_previous)
418     {
419         pcp->cstate_data->version_previous = pstate_data->version_previous;
420         pstate_data->version_previous = 0;
421     }
422 
423     //
424     // By default we reuse change numbers.
425     //
426     change_branch_reuse_change_numbers_set(pcp, 1);
427 
428     //
429     // Phew!  Who would ever have guessed there was so much to do
430     // when it came to converting a project to use branching?
431     //
432     trace(("}\n"));
433 }
434 
435 
~project()436 project::~project()
437 {
438     trace(("project::~project(this = %p)\n{\n", this));
439     if (pcp)
440         change_free(pcp);
441     assert(name);
442     str_free(name);
443     if (home_path)
444         str_free(home_path);
445     if (baseline_path_unresolved)
446         str_free(baseline_path_unresolved);
447     if (baseline_path)
448         str_free(baseline_path);
449     if (change2time_stp)
450         itab_free(change2time_stp);
451     if (history_path)
452         str_free(history_path);
453     if (info_path)
454         str_free(info_path);
455     if (pstate_path)
456         str_free(pstate_path);
457     if (changes_path)
458         str_free(changes_path);
459     if (pstate_data)
460         pstate_type.free(pstate_data);
461     if (parent)
462         project_free(parent);
463     file_list_invalidate();
464     trace(("}\n"));
465 }
466 
467 
project(string_ty * s)468 project::project(string_ty *s) :
469     reference_count(1),
470     name(str_copy(s)),
471     home_path(0),
472     baseline_path_unresolved(0),
473     baseline_path(0),
474     history_path(0),
475     info_path(0),
476     pstate_path(0),
477     changes_path(0),
478     pstate_data(0),
479     is_a_new_file(false),
480     lock_magic(0),
481     pcp(0),
482     uid(-1),
483     gid(-1),
484     parent(0),
485     parent_bn(0),
486     off_limits(false)
487 {
488     change2time_stp = itab_alloc();
489     for (size_t j = 0; j < SIZEOF(file_list); ++j)
490         file_list[j] = 0;
491     for (size_t k = 0; k < SIZEOF(file_by_uuid); ++k)
492         file_by_uuid[k] = 0;
493 }
494 
495 
496 project *
project_alloc(string_ty * s)497 project_alloc(string_ty *s)
498 {
499     trace(("project_alloc(s = \"%s\")\n{\n", s->str_text));
500     project *pp = new project(s);
501     trace(("return %p;\n", pp));
502     trace(("}\n"));
503     return pp;
504 }
505 
506 
507 project *
project_copy(project * pp)508 project_copy(project *pp)
509 {
510     trace(("project_copy(pp = %p)\n{\n", pp));
511     assert(pp->reference_count >= 1);
512     pp->reference_count++;
513     trace(("return %p;\n", pp));
514     trace(("}\n"));
515     return pp;
516 }
517 
518 
519 void
project_free(project * pp)520 project_free(project *pp)
521 {
522     trace(("project_free(pp = %p)\n{\n", pp));
523     assert(pp->reference_count >= 1);
524     pp->reference_count--;
525 
526     //
527     // The root project's pcp references the root project (project
528     // data structure). Thus even as all other references go away, the
529     // reference count of the root project remains at 1 and its memory
530     // is not released. In the special case where reference_count is
531     // one in project_free() we should test whether we're the root
532     // project and the only thing holding on to us is the pcp. In that
533     // case we should untie the loop and do a free on the pcp (which
534     // will cascade to free us).
535     //
536     if (pp->reference_count == 1)
537     {
538         change::pointer tmp = pp->change_get_raw();
539 
540         if (tmp && tmp->pp == pp && pp->is_a_trunk())
541             pp->change_reset();
542     }
543     else if (pp->reference_count <= 0)
544     {
545         delete pp;
546     }
547     trace(("}\n"));
548 }
549 
550 
551 void
project_pstate_lock_prepare_top(project * pp)552 project_pstate_lock_prepare_top(project *pp)
553 {
554     trace(("project_pstate_lock_prepare_top(pp = %p)\n{\n", pp));
555     pp->trunk_get()->pstate_lock_prepare();
556     trace(("}\n"));
557 }
558 
559 
560 static void
waiting_for_baseline_read_lock(void * p)561 waiting_for_baseline_read_lock(void *p)
562 {
563     project         *pp;
564 
565     pp = (project *)p;
566     if (user_ty::create()->lock_wait())
567         project_error(pp, 0, i18n("waiting for baseline read lock"));
568     else
569         project_fatal(pp, 0, i18n("baseline read lock not available"));
570 }
571 
572 
573 void
project_baseline_read_lock_prepare(project * pp)574 project_baseline_read_lock_prepare(project *pp)
575 {
576     //
577     // A branch's baseline is "unioned" with its parent's
578     // baseline, so we need to lock them as well - all the
579     // way up the tree.
580     //
581     trace(("project_baseline_read_lock_prepare(pp = %p)\n{\n", pp));
582     assert(pp);
583     for (;;)
584     {
585         lock_prepare_baseline_read
586         (
587             pp->name_get(),
588             waiting_for_baseline_read_lock,
589             pp
590         );
591         if (pp->is_a_trunk())
592             break;
593         pp = pp->parent_get();
594     }
595     trace(("}\n"));
596 }
597 
598 
599 static void
waiting_for_baseline_write_lock(void * p)600 waiting_for_baseline_write_lock(void *p)
601 {
602     project         *pp;
603 
604     pp = (project *)p;
605     if (user_ty::create()->lock_wait())
606         project_error(pp, 0, i18n("waiting for baseline write lock"));
607     else
608         project_fatal(pp, 0, i18n("baseline write lock not available"));
609 }
610 
611 
612 void
project_baseline_write_lock_prepare(project * pp)613 project_baseline_write_lock_prepare(project *pp)
614 {
615     trace(("project_baseline_write_lock_prepare(pp = %p)\n{\n", pp));
616     lock_prepare_baseline_write
617     (
618         pp->name_get(),
619         waiting_for_baseline_write_lock,
620         pp
621     );
622     trace(("}\n"));
623 }
624 
625 
626 static void
waiting_for_history_lock(void * p)627 waiting_for_history_lock(void *p)
628 {
629     project         *pp;
630 
631     pp = (project *)p;
632     if (user_ty::create()->lock_wait())
633         project_error(pp, 0, i18n("waiting for history lock"));
634     else
635         project_fatal(pp, 0, i18n("history lock not available"));
636 }
637 
638 
639 void
project_history_lock_prepare(project * pp)640 project_history_lock_prepare(project *pp)
641 {
642     //
643     // The history tool may have no locking of its own, and it will
644     // be ignored if it does.  Therefore, take the history lock in
645     // the deepest project.  This prevents aeip on different
646     // branches from trashing each other's history files.
647     //
648     trace(("project_history_lock_prepare(pp = %p)\n{\n", pp));
649     pp = pp->trunk_get();
650     lock_prepare_history(pp->name_get(), waiting_for_history_lock, pp);
651     trace(("}\n"));
652 }
653 
654 
655 int
break_up_version_string(const char * sp,long * buf,int buflen_max,int * buflen_p,int leading_punct)656 break_up_version_string(const char *sp, long *buf, int buflen_max,
657     int *buflen_p, int leading_punct)
658 {
659     int             buflen;
660     long            n;
661 
662     if (*sp == 0)
663         return -1;
664     buflen = *buflen_p;
665     for (;;)
666     {
667         //
668         // one leading punctuation character
669         // but we don't care what it is (- and . are common)
670         // {C locale}
671         //
672         if (leading_punct)
673         {
674             if (!ispunct((unsigned char)*sp))
675                 return -1;
676             ++sp;
677         }
678         else
679             leading_punct = 1;
680 
681         //
682         // the next one must be a digit
683         //
684         if (!isdigit((unsigned char)*sp))
685             return -1;
686 
687         //
688         // The version string must not be too long.  (Even
689         // oracle, who use the most unbelievable 6 part version
690         // strings, will cope if you use a big enough buffer.)
691         //
692         if (buflen >= buflen_max)
693             return -1;
694 
695         //
696         // collect the number
697         //
698         n = 0;
699         for (;;)
700         {
701             n = n * 10 + *sp++ - '0';
702             if (!isdigit((unsigned char)*sp))
703                 break;
704         }
705         buf[buflen++] = magic_zero_encode(n);
706 
707         //
708         // stop at end of string
709         // (note: trailing punctuation is illegal)
710         //
711         if (!*sp)
712             break;
713     }
714     *buflen_p = buflen;
715     return 0;
716 }
717 
718 
719 void
extract_version_from_project_name(string_ty ** name_p,long * buf,int buflen_max,int * buflen_p)720 extract_version_from_project_name(string_ty **name_p, long *buf, int buflen_max,
721     int *buflen_p)
722 {
723     string_ty       *name;
724     char            *sp;
725     int             err;
726 
727     //
728     // if the project name does not end in a digit,
729     // it can't end in a version string
730     //
731     name = *name_p;
732     if
733     (
734         name->str_length < 3
735     ||
736         // C locale
737         !isdigit((unsigned char)name->str_text[name->str_length - 1])
738     )
739         return;
740     sp = name->str_text;
741 
742     //
743     // move down the name, looking for a trailing version number
744     //
745     for (; *sp; ++sp)
746     {
747         //
748         // skip over leading non-punctuation
749         // {C locale}
750         //
751         if (!ispunct((unsigned char)*sp))
752             continue;
753 
754         //
755         // attempt to break up the rest of the string
756         //
757         err = break_up_version_string(sp, buf, buflen_max, buflen_p, 1);
758 
759         //
760         // if there was an error, try again further down
761         //
762         if (err)
763             continue;
764 
765         //
766         // the version number is now in the buffer,
767         // so shorten the name to match
768         //
769         *name_p = str_n_from_c(name->str_text, sp - name->str_text);
770         str_free(name);
771         return;
772     }
773 }
774 
775 
776 project *
find_branch(const char * version_string)777 project::find_branch(const char *version_string)
778 {
779     long            version[20];
780     int             version_length;
781     int             j;
782 
783     if (parent)
784     {
785         //
786         // As a special case, the "grandparent" of a change is
787         // repesented by a branch name of ".." as this is expected to be
788         // the commonest variety of cross branch merge.
789         //
790         if (!strcmp(version_string, ".."))
791             return project_copy(parent);
792 
793         //
794         // the version is relative to the trunk of the project
795         //
796         return trunk_get()->find_branch(version_string);
797     }
798 
799     //
800     // break the version string
801     //
802     version_length = 0;
803     if
804     (
805         *version_string
806     &&
807         break_up_version_string
808         (
809             version_string,
810             version,
811             (int)sizeof(version),
812             &version_length,
813             0
814         )
815     )
816     {
817         sub_context_ty  *scp;
818 
819         scp = sub_context_new();
820         sub_var_set_charstar(scp, "Number", version_string);
821         fatal_intl(scp, i18n("bad version $number"));
822         // NOTREACHED
823         sub_context_delete(scp);
824     }
825     if (version_length == 0)
826         return project_copy(this);
827 
828     //
829     // follow the branches down
830     //
831     project *pp = this;
832     for (j = 0; j < version_length; ++j)
833     {
834         long            cn;
835         change::pointer cp;
836 
837         cn = version[j];
838         cp = change_alloc(pp, cn);
839         change_bind_existing(cp);
840         if (!cp->was_a_branch())
841         {
842             sub_context_ty  *scp;
843 
844             scp = sub_context_new();
845             sub_var_set_charstar(scp, "Number", version_string);
846             change_fatal(cp, scp, i18n("version $number not a branch"));
847             // NOTREACHED
848             sub_context_delete(scp);
849         }
850         pp = pp->bind_branch(cp);
851     }
852 
853     //
854     // return the derived project
855     //
856     return pp;
857 }
858 
859 
860 void
pstate_write()861 project::pstate_write()
862 {
863     string_ty       *filename;
864     string_ty       *filename_new;
865     string_ty       *filename_old;
866     static int      count;
867     int             compress;
868 
869     trace(("project::pstate_write(thid = %p)\n{\n", this));
870 
871     //
872     // write out the associated change, if it was read in
873     //
874     if (pcp)
875         pcp->cstate_write();
876 
877     //
878     // write it out
879     //
880     compress = project_compress_database_get(this);
881     if (pstate_data)
882     {
883         filename = pstate_path_get();
884         filename_new = str_format("%s,%d", filename->str_text, ++count);
885         filename_old = str_format("%s,%d", filename->str_text, ++count);
886         user_ty::become scoped(get_user());
887 
888         // enums with 0 value not to be printed
889         type_enum_option_set();
890 
891         if (is_a_new_file)
892         {
893             undo_unlink_errok(filename_new);
894             pstate_write_file(filename_new, pstate_data, compress);
895             commit_rename(filename_new, filename);
896         }
897         else
898         {
899             undo_unlink_errok(filename_new);
900             pstate_write_file(filename_new, pstate_data, compress);
901             commit_rename(filename, filename_old);
902             commit_rename(filename_new, filename);
903             commit_unlink_errok(filename_old);
904         }
905 
906         //
907         // Change so the project owns it.
908         // (Only needed for new files, but be paranoid.)
909         //
910         os_chmod(filename_new, 0644 & ~project_umask_get(this));
911         str_free(filename_new);
912         str_free(filename_old);
913     }
914     trace(("}\n"));
915 }
916 
917 
918 void
project_pstate_write_top(project * pp)919 project_pstate_write_top(project *pp)
920 {
921     trace(("project_pstate_write_top(pp = %p)\n{\n", pp));
922     pp->trunk_get()->pstate_write();
923     trace(("}\n"));
924 }
925 
926 
927 string_ty *
project_Home_path_get(project * pp)928 project_Home_path_get(project *pp)
929 {
930     return pp->trunk_get()->home_path_get();
931 }
932 
933 
934 string_ty *
project_top_path_get(project * pp,int resolve)935 project_top_path_get(project *pp, int resolve)
936 {
937     change::pointer cp = pp->change_get();
938     return change_top_path_get(cp, resolve);
939 }
940 
941 
942 nstring
project_rss_path_get(project * pp,bool resolve)943 project_rss_path_get(project *pp, bool resolve)
944 {
945     string_ty *project_top_path = project_top_path_get(pp, resolve);
946     return os_path_cat(nstring(project_top_path), "rss");
947 }
948 
949 
950 void
project_error(project * pp,sub_context_ty * scp,const char * s)951 project_error(project *pp, sub_context_ty *scp, const char *s)
952 {
953     string_ty       *msg;
954     int             need_to_delete;
955 
956     if (!scp)
957     {
958         scp = sub_context_new();
959         need_to_delete = 1;
960     }
961     else
962         need_to_delete = 0;
963 
964     //
965     // form the message
966     //
967     subst_intl_project(scp, pp);
968     msg = subst_intl(scp, s);
969 
970     //
971     // pass the message to the error function
972     //
973     // re-use substitution context
974     sub_var_set_string(scp, "MeSsaGe", msg);
975     str_free(msg);
976     subst_intl_project(scp, pp);
977     error_intl(scp, i18n("project \"$project\": $message"));
978 
979     if (need_to_delete)
980         sub_context_delete(scp);
981 }
982 
983 
984 void
project_fatal(project * pp,sub_context_ty * scp,const char * s)985 project_fatal(project *pp, sub_context_ty *scp, const char *s)
986 {
987     string_ty       *msg;
988     int             need_to_delete;
989 
990     if (!scp)
991     {
992         scp = sub_context_new();
993         need_to_delete = 1;
994     }
995     else
996         need_to_delete = 0;
997 
998     //
999     // form the message
1000     //
1001     subst_intl_project(scp, pp);
1002     msg = subst_intl(scp, s);
1003 
1004     //
1005     // pass the message to the error function
1006     //
1007     // re-use substitution context
1008     sub_var_set_string(scp, "MeSsaGe", msg);
1009     str_free(msg);
1010     subst_intl_project(scp, pp);
1011     fatal_intl(scp, i18n("project \"$project\": $message"));
1012     // NOTREACHED
1013     if (need_to_delete)
1014         sub_context_delete(scp);
1015 }
1016 
1017 
1018 void
project_verbose(project * pp,sub_context_ty * scp,const char * s)1019 project_verbose(project *pp, sub_context_ty *scp, const char *s)
1020 {
1021     string_ty       *msg;
1022     int             need_to_delete;
1023 
1024     if (!scp)
1025     {
1026         scp = sub_context_new();
1027         need_to_delete = 1;
1028     }
1029     else
1030         need_to_delete = 0;
1031 
1032     //
1033     // form the message
1034     //
1035     subst_intl_project(scp, pp);
1036     msg = subst_intl(scp, s);
1037 
1038     //
1039     // pass the message to the error function
1040     //
1041     // re-use the substitution context
1042     sub_var_set_string(scp, "MeSsaGe", msg);
1043     str_free(msg);
1044     subst_intl_project(scp, pp);
1045     verbose_intl(scp, i18n("project \"$project\": $message"));
1046 
1047     if (need_to_delete)
1048         sub_context_delete(scp);
1049 }
1050 
1051 
1052 void
project_change_append(project * pp,long cn,int is_a_branch)1053 project_change_append(project *pp, long cn, int is_a_branch)
1054 {
1055     change::pointer cp = pp->change_get();
1056     change_branch_change_add(cp, cn, is_a_branch);
1057 }
1058 
1059 
1060 void
project_change_delete(project * pp,long cn)1061 project_change_delete(project *pp, long cn)
1062 {
1063     change::pointer cp = pp->change_get();
1064     change_branch_change_remove(cp, cn);
1065 }
1066 
1067 
1068 int
project_change_number_in_use(project * pp,long cn)1069 project_change_number_in_use(project *pp, long cn)
1070 {
1071     change::pointer cp = pp->change_get();
1072     return change_branch_change_number_in_use(cp, cn);
1073 }
1074 
1075 
1076 nstring
project_version_short_get(project * pp)1077 project_version_short_get(project *pp)
1078 {
1079     nstring s;
1080 
1081     trace(("project_version_short_get(pp = %p)\n{\n", pp));
1082     if (!pp->is_a_trunk())
1083     {
1084         assert(pp->parent_branch_number_get() > 0
1085                ||
1086                pp->parent_branch_number_get() == MAGIC_ZERO);
1087         s = project_version_short_get(pp->parent_get());
1088         if (s.length())
1089         {
1090             // ...punctuation?
1091             s =
1092                 nstring::format
1093                 (
1094                     "%s.%ld",
1095                     s.c_str(),
1096                     magic_zero_decode(pp->parent_branch_number_get())
1097                 );
1098         }
1099         else
1100         {
1101             s =
1102                 nstring::format
1103                 (
1104                     "%ld",
1105                     magic_zero_decode(pp->parent_branch_number_get())
1106                 );
1107         }
1108     }
1109     else
1110     {
1111         pp->change_get(); // make sure is in memory
1112         pstate_ty *pstate_data = pp->pstate_get();
1113         if (pstate_data->version_major || pstate_data->version_minor)
1114         {
1115             //
1116             // old style project, not yet branching
1117             //
1118             s =
1119                 nstring::format
1120                 (
1121                     "%ld.%ld",
1122                     pstate_data->version_major,
1123                     pstate_data->version_minor
1124                 );
1125         }
1126         else
1127         {
1128             assert(s.empty());
1129         }
1130     }
1131     trace(("return \"%s\";\n", s.c_str()));
1132     trace(("}\n"));
1133     return s;
1134 }
1135 
1136 
1137 nstring
project_version_get(project * pp)1138 project_version_get(project *pp)
1139 {
1140     trace(("project_version_get(pp = %p)\n{\n", pp));
1141     change::pointer cp = pp->change_get();
1142     long dn = change_history_delta_latest(cp);
1143     nstring tmp = project_version_short_get(pp);
1144     nstring result = nstring::format("%s.D%3.3ld", tmp.c_str(), dn);
1145     trace(("return \"%s\";\n", result.c_str()));
1146     trace(("}\n"));
1147     return result;
1148 }
1149 
1150 
1151 user_ty::pointer
project_user(project * pp)1152 project_user(project *pp)
1153 {
1154     return pp->get_user();
1155 }
1156 
1157 
1158 user_ty::pointer
get_user() const1159 project::get_user()
1160     const
1161 {
1162     trace(("project::get_user(this = %p)\n{\n", this));
1163     if (!up)
1164     {
1165         fatal_raw
1166         (
1167             "%s: %d: project::up not set, you should have called "
1168             "project_bind_existing or project::bind_new() before now "
1169             "(bug)",
1170             __FILE__,
1171             __LINE__
1172         );
1173     }
1174     trace(("return %p;\n", up.get()));
1175     trace(("}\n"));
1176     return up;
1177 }
1178 
1179 
1180 void
project_become(project * pp)1181 project_become(project *pp)
1182 {
1183     trace(("project_become(pp = %p)\n{\n", pp));
1184     pp->get_user()->become_begin();
1185     trace(("}\n"));
1186 }
1187 
1188 
1189 void
project_become_undo(project * pp)1190 project_become_undo(project *pp)
1191 {
1192     trace(("project_become_undo(cp)\n{\n"));
1193     pp->get_user()->become_end();
1194     trace(("}\n"));
1195 }
1196 
1197 
1198 int
project_is_readable(project * pp)1199 project_is_readable(project *pp)
1200 {
1201     trace(("%s\n", __PRETTY_FUNCTION__));
1202     pp = pp->trunk_get();
1203     string_ty *s = pp->pstate_path_get();
1204     os_become_orig();
1205     int err = os_readable(s);
1206     os_become_undo();
1207     trace(("return %d\n", err));
1208     return err;
1209 }
1210 
1211 
1212 long
project_next_test_number_get(project * pp)1213 project_next_test_number_get(project *pp)
1214 {
1215     pstate_ty       *pstate_data;
1216     long            result;
1217     int             skip;
1218 
1219     trace(("project_next_test_number_get(pp = %p)\n{\n", pp));
1220     skip = project_skip_unlucky_get(pp);
1221     pp = pp->trunk_get();
1222     pstate_data = pp->pstate_get();
1223     result = pstate_data->next_test_number;
1224     if (skip)
1225         result = skip_unlucky(result);
1226     pstate_data->next_test_number = result + 1;
1227     trace(("return %ld;\n", result));
1228     trace(("}\n"));
1229     return result;
1230 }
1231 
1232 
1233 long
project_minimum_change_number_get(project * pp)1234 project_minimum_change_number_get(project *pp)
1235 {
1236     change::pointer cp = pp->change_get();
1237     return change_branch_minimum_change_number_get(cp);
1238 }
1239 
1240 
1241 void
project_minimum_change_number_set(project * pp,long n)1242 project_minimum_change_number_set(project *pp, long n)
1243 {
1244     change::pointer cp = pp->change_get();
1245     change_branch_minimum_change_number_set(cp, n);
1246 }
1247 
1248 
1249 bool
project_reuse_change_numbers_get(project * pp)1250 project_reuse_change_numbers_get(project *pp)
1251 {
1252     change::pointer cp = pp->change_get();
1253     return change_branch_reuse_change_numbers_get(cp);
1254 }
1255 
1256 
1257 void
project_reuse_change_numbers_set(project * pp,bool n)1258 project_reuse_change_numbers_set(project *pp, bool n)
1259 {
1260     change::pointer cp = pp->change_get();
1261     change_branch_reuse_change_numbers_set(cp, n);
1262 }
1263 
1264 
1265 long
project_minimum_branch_number_get(project * pp)1266 project_minimum_branch_number_get(project *pp)
1267 {
1268     change::pointer cp = pp->change_get();
1269     return change_branch_minimum_branch_number_get(cp);
1270 }
1271 
1272 
1273 void
project_minimum_branch_number_set(project * pp,long n)1274 project_minimum_branch_number_set(project *pp, long n)
1275 {
1276     change::pointer cp = pp->change_get();
1277     change_branch_minimum_branch_number_set(cp, n);
1278 }
1279 
1280 
1281 bool
project_skip_unlucky_get(project * pp)1282 project_skip_unlucky_get(project *pp)
1283 {
1284     change::pointer cp = pp->change_get();
1285     return change_branch_skip_unlucky_get(cp);
1286 }
1287 
1288 
1289 void
project_skip_unlucky_set(project * pp,bool n)1290 project_skip_unlucky_set(project *pp, bool n)
1291 {
1292     change::pointer cp = pp->change_get();
1293     change_branch_skip_unlucky_set(cp, n);
1294 }
1295 
1296 
1297 int
project_name_ok(string_ty * s)1298 project_name_ok(string_ty *s)
1299 {
1300     const char      *sp;
1301 
1302     //
1303     // The horrible characters are file separators in a variety of
1304     // operating systems (unix, dos, mac, primos).
1305     //
1306     static char     horrible[] =    "/\\:>";
1307 
1308     if (s->str_length == 0)
1309         return 0;
1310     for (sp = s->str_text; *sp; ++sp)
1311     {
1312         // C locale
1313         if
1314         (
1315             !isprint((unsigned char)*sp)
1316         ||
1317             isspace((unsigned char)*sp)
1318         ||
1319             strchr(horrible, *sp)
1320         )
1321             return 0;
1322     }
1323     return 1;
1324 }
1325 
1326 
1327 // vim: set ts=8 sw=4 et :
1328