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