Add test about issue with no commit on a branch
[cvsps-hv.git] / cache.c
blob5f67a7c032b74ca49718e6588dfc86f8903c4147
1 /*
2 * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
3 * See COPYING file for license information
4 */
6 #include <stdio.h>
7 #include <search.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <limits.h>
11 #include <unistd.h>
12 #include <ctype.h>
13 #include <time.h>
15 #include <cbtcommon/hash.h>
16 #include <cbtcommon/debug.h>
18 #include "cache.h"
19 #include "cvsps_types.h"
20 #include "cvsps.h"
21 #include "util.h"
23 #define CACHE_DESCR_BOUNDARY "-=-END CVSPS DESCR-=-\n"
25 /* change this when making the on-disk cache-format invalid */
26 static int cache_version = 1;
28 /* the tree walk API pretty much requries use of globals :-( */
29 static FILE * cache_fp;
30 static int ps_counter;
32 static void write_patch_set_to_cache(PatchSet *);
33 static void parse_cache_revision(PatchSetMember *, const char *);
34 static void dump_patch_set(FILE *, PatchSet *);
36 static FILE *cache_open(char const *mode)
38 char *prefix;
39 char fname[PATH_MAX];
40 char root[PATH_MAX];
41 char repository[PATH_MAX];
42 FILE * fp;
44 /* Get the prefix */
45 prefix = get_cvsps_dir();
46 if (!prefix)
47 return NULL;
49 /* Generate the full path */
50 strcpy(root, root_path);
51 strcpy(repository, repository_path);
53 strrep(root, '/', '#');
54 strrep(repository, '/', '#');
56 snprintf(fname, PATH_MAX, "%s/%s#%s", prefix, root, repository);
58 if (!(fp = fopen(fname, mode)) && *mode == 'r')
60 if ((fp = fopen("CVS/cvsps.cache", mode)))
62 fprintf(stderr, "\n");
63 fprintf(stderr, "****WARNING**** Obsolete CVS/cvsps.cache file found.\n");
64 fprintf(stderr, " New file will be re-written in ~/%s/\n", CVSPS_PREFIX);
65 fprintf(stderr, " Old file will be ignored.\n");
66 fprintf(stderr, " Please manually remove the old file.\n");
67 fprintf(stderr, " Continuing in 5 seconds.\n");
68 sleep(5);
69 fclose(fp);
70 fp = NULL;
74 return fp;
77 /* ************ Reading ************ */
79 enum
81 CACHE_NEED_FILE,
82 CACHE_NEED_BRANCHES,
83 CACHE_NEED_SYMBOLS,
84 CACHE_NEED_REV,
85 CACHE_NEED_PS,
86 CACHE_NEED_PS_DATE,
87 CACHE_NEED_PS_AUTHOR,
88 CACHE_NEED_PS_TAG,
89 CACHE_NEED_PS_TAG_FLAGS,
90 CACHE_NEED_PS_BRANCH,
91 CACHE_NEED_PS_BRANCH_ADD,
92 CACHE_NEED_PS_DESCR,
93 CACHE_NEED_PS_EOD,
94 CACHE_NEED_PS_MEMBERS,
95 CACHE_NEED_PS_EOM
98 time_t read_cache()
100 FILE * fp;
101 char buff[BUFSIZ];
102 int state = CACHE_NEED_FILE;
103 CvsFile * f = NULL;
104 PatchSet * ps = NULL;
105 char datebuff[20] = "";
106 char authbuff[AUTH_STR_MAX] = "";
107 char tagbuff[LOG_STR_MAX] = "";
108 int tag_flags = 0;
109 char branchbuff[LOG_STR_MAX] = "";
110 int branch_add = 0;
111 int logbufflen = LOG_STR_MAX + 1;
112 char * logbuff = malloc(logbufflen);
113 time_t cache_date = -1;
114 int read_version;
116 if (logbuff == NULL)
118 debug(DEBUG_SYSERROR, "could not malloc %d bytes for logbuff in read_cache", logbufflen);
119 exit(1);
122 logbuff[0] = 0;
124 if (!(fp = cache_open("r")))
125 goto out;
127 /* first line is cache version format "cache version: %d\n" */
128 if (!fgets(buff, BUFSIZ, fp) || strncmp(buff, "cache version:", 14))
130 debug(DEBUG_APPERROR, "bad cvsps.cache file");
131 goto out_close;
134 if ((read_version = atoi(buff + 15)) != cache_version)
136 debug(DEBUG_APPERROR, "bad cvsps.cache version %d, expecting %d. ignoring cache",
137 read_version, cache_version);
138 goto out_close;
141 /* second line is date cache was created, format "cache date: %d\n" */
142 if (!fgets(buff, BUFSIZ, fp) || strncmp(buff, "cache date:", 11))
144 debug(DEBUG_APPERROR, "bad cvsps.cache file");
145 goto out_close;
148 cache_date = atoi(buff + 12);
149 debug(DEBUG_STATUS, "read cache_date %d", (int)cache_date);
151 while (fgets(buff, BUFSIZ, fp))
153 int len = strlen(buff);
155 switch(state)
157 case CACHE_NEED_FILE:
158 if (strncmp(buff, "file:", 5) == 0)
160 len -= 6;
161 f = create_cvsfile();
162 f->filename = xstrdup(buff + 6);
163 f->filename[len-1] = 0; /* Remove the \n at the end of line */
164 debug(DEBUG_STATUS, "read cache filename '%s'", f->filename);
165 put_hash_object_ex(file_hash, f->filename, f, HT_NO_KEYCOPY, NULL, NULL);
166 state = CACHE_NEED_BRANCHES;
168 else
170 state = CACHE_NEED_PS;
172 break;
173 case CACHE_NEED_BRANCHES:
174 if (buff[0] != '\n')
176 char * tag;
178 tag = strchr(buff, ':');
179 if (tag)
181 *tag = 0;
182 tag += 2;
183 buff[len - 1] = 0;
184 cvs_file_add_branch(f, buff, tag);
187 else
189 f->have_branches = 1;
190 state = CACHE_NEED_SYMBOLS;
192 break;
193 case CACHE_NEED_SYMBOLS:
194 if (buff[0] != '\n')
196 char * rev;
198 rev = strchr(buff, ':');
199 if (rev)
201 *rev = 0;
202 rev += 2;
203 buff[len - 1] = 0;
204 cvs_file_add_symbol(f, rev, buff);
207 else
209 state = CACHE_NEED_REV;
211 break;
212 case CACHE_NEED_REV:
213 if (isdigit(buff[0]))
215 char * p = strchr(buff, ' ');
216 if (p)
218 CvsFileRevision * rev;
219 *p++ = 0;
220 buff[len-1] = 0;
221 rev = cvs_file_add_revision(f, buff);
222 if (strcmp(rev->branch, p) != 0)
224 debug(DEBUG_APPERROR, "branch mismatch for %s:%s %s != %s",
225 rev->file->filename, rev->rev, rev->branch, p);
229 else
231 state = CACHE_NEED_FILE;
233 break;
234 case CACHE_NEED_PS:
235 if (strncmp(buff, "patchset:", 9) == 0)
236 state = CACHE_NEED_PS_DATE;
237 break;
238 case CACHE_NEED_PS_DATE:
239 if (strncmp(buff, "date:", 5) == 0)
241 /* remove prefix "date: " and LF from len */
242 len -= 6;
243 strzncpy(datebuff, buff + 6, MIN(len, sizeof(datebuff)));
244 state = CACHE_NEED_PS_AUTHOR;
246 break;
247 case CACHE_NEED_PS_AUTHOR:
248 if (strncmp(buff, "author:", 7) == 0)
250 /* remove prefix "author: " and LF from len */
251 len -= 8;
252 strzncpy(authbuff, buff + 8, MIN(len, AUTH_STR_MAX));
253 state = CACHE_NEED_PS_TAG;
255 break;
256 case CACHE_NEED_PS_TAG:
257 if (strncmp(buff, "tag:", 4) == 0)
259 /* remove prefix "tag: " and LF from len */
260 len -= 5;
261 strzncpy(tagbuff, buff + 5, MIN(len, LOG_STR_MAX));
262 state = CACHE_NEED_PS_TAG_FLAGS;
264 break;
265 case CACHE_NEED_PS_TAG_FLAGS:
266 if (strncmp(buff, "tag_flags:", 10) == 0)
268 /* remove prefix "tag_flags: " and LF from len */
269 len -= 11;
270 tag_flags = atoi(buff + 11);
271 state = CACHE_NEED_PS_BRANCH;
273 break;
274 case CACHE_NEED_PS_BRANCH:
275 if (strncmp(buff, "branch:", 7) == 0)
277 /* remove prefix "branch: " and LF from len */
278 len -= 8;
279 strzncpy(branchbuff, buff + 8, MIN(len, LOG_STR_MAX));
280 state = CACHE_NEED_PS_BRANCH_ADD;
282 break;
283 case CACHE_NEED_PS_BRANCH_ADD:
284 if (strncmp(buff, "branch_add:", 11) == 0)
286 /* remove prefix "branch_add: " and LF from len */
287 len -= 12;
288 branch_add = atoi(buff + 12);
289 state = CACHE_NEED_PS_DESCR;
291 break;
292 case CACHE_NEED_PS_DESCR:
293 if (strncmp(buff, "descr:", 6) == 0)
294 state = CACHE_NEED_PS_EOD;
295 break;
296 case CACHE_NEED_PS_EOD:
297 if (strcmp(buff, CACHE_DESCR_BOUNDARY) == 0)
299 debug(DEBUG_STATUS, "patch set %s %s %s %s", datebuff, authbuff, logbuff, branchbuff);
300 ps = get_patch_set(datebuff, logbuff, authbuff, branchbuff, NULL);
301 /* the tag and tag_flags will be assigned by the resolve_global_symbols code
302 * ps->tag = (strlen(tagbuff)) ? get_string(tagbuff) : NULL;
303 * ps->tag_flags = tag_flags;
305 ps->branch_add = branch_add;
306 state = CACHE_NEED_PS_MEMBERS;
308 else
310 /* Make sure we have enough in the buffer */
311 int len = strlen(buff);
312 if (strlen(logbuff) + len >= LOG_STR_MAX)
314 logbufflen += (len >= LOG_STR_MAX ? (len+1) : LOG_STR_MAX);
315 char * newlogbuff = realloc(logbuff, logbufflen);
316 if (newlogbuff == NULL)
318 debug(DEBUG_SYSERROR, "could not realloc %d bytes for logbuff in read_cache", logbufflen);
319 exit(1);
321 logbuff = newlogbuff;
323 strcat(logbuff, buff);
325 break;
326 case CACHE_NEED_PS_MEMBERS:
327 if (strncmp(buff, "members:", 8) == 0)
328 state = CACHE_NEED_PS_EOM;
329 break;
330 case CACHE_NEED_PS_EOM:
331 if (buff[0] == '\n')
333 datebuff[0] = 0;
334 authbuff[0] = 0;
335 tagbuff[0] = 0;
336 tag_flags = 0;
337 branchbuff[0] = 0;
338 branch_add = 0;
339 logbuff[0] = 0;
340 state = CACHE_NEED_PS;
342 else
344 PatchSetMember * psm = create_patch_set_member();
345 parse_cache_revision(psm, buff);
346 patch_set_add_member(ps, psm);
348 break;
352 out_close:
353 fclose(fp);
354 out:
355 free(logbuff);
356 return cache_date;
359 enum
361 CR_FILENAME,
362 CR_PRE_REV,
363 CR_POST_REV,
364 CR_DEAD,
365 CR_BRANCH_POINT
368 static void parse_cache_revision(PatchSetMember * psm, const char * buff)
370 /* The format used to generate is:
371 * "file:%s; pre_rev:%s; post_rev:%s; dead:%d; branch_point:%d\n"
373 char filename[PATH_MAX];
374 char pre[REV_STR_MAX];
375 char post[REV_STR_MAX];
376 int dead = 0;
377 int bp = 0;
378 int state = CR_FILENAME;
379 const char *sep;
380 char * p;
381 char * c;
383 for (p = buff, sep = buff; /* just ensure sep is non-NULL */
384 (sep != NULL) && (c = strchr(p, ':'));
385 p = sep + 1)
387 size_t len;
388 sep = strchr(c, ';');
389 c++;
391 if (sep != NULL)
392 len = sep - c;
393 else /* last field in the cache line */
394 len = strlen(c);
396 switch(state)
398 case CR_FILENAME:
399 memcpy(filename, c, len);
400 filename[len] = '\0';
401 break;
402 case CR_PRE_REV:
403 memcpy(pre, c, len);
404 pre[len] = '\0';
405 break;
406 case CR_POST_REV:
407 memcpy(post, c, len);
408 post[len] = '\0';
409 break;
410 case CR_DEAD:
411 dead = atoi(c);
412 break;
413 case CR_BRANCH_POINT:
414 bp = atoi(c);
415 break;
417 state++;
420 psm->file = (CvsFile*)get_hash_object(file_hash, filename);
422 if (!psm->file)
424 debug(DEBUG_APPERROR, "file '%s' not found in hash", filename);
425 exit(1);
428 psm->pre_rev = file_get_revision(psm->file, pre);
429 psm->post_rev = file_get_revision(psm->file, post);
430 psm->post_rev->dead = dead;
431 psm->post_rev->post_psm = psm;
433 if (!bp)
435 if (psm->pre_rev)
436 psm->pre_rev->pre_psm = psm;
438 else
440 list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
444 /************ Writing ************/
446 void write_cache(time_t cache_date)
448 struct hash_entry * file_iter;
450 ps_counter = 0;
452 if ((cache_fp = cache_open("w")) == NULL)
454 debug(DEBUG_SYSERROR, "can't open cvsps.cache for write");
455 return;
458 fprintf(cache_fp, "cache version: %d\n", cache_version);
459 fprintf(cache_fp, "cache date: %d\n", (int)cache_date);
461 reset_hash_iterator(file_hash);
463 while ((file_iter = next_hash_entry(file_hash)))
465 CvsFile * file = (CvsFile*)file_iter->he_obj;
466 struct hash_entry * rev_iter;
468 fprintf(cache_fp, "file: %s\n", file->filename);
470 reset_hash_iterator(file->branches);
471 while ((rev_iter = next_hash_entry(file->branches)))
473 char * rev = (char *)rev_iter->he_key;
474 char * tag = (char *)rev_iter->he_obj;
475 fprintf(cache_fp, "%s: %s\n", rev, tag);
478 fprintf(cache_fp, "\n");
480 reset_hash_iterator(file->symbols);
481 while ((rev_iter = next_hash_entry(file->symbols)))
483 char * tag = (char *)rev_iter->he_key;
484 CvsFileRevision * rev = (CvsFileRevision*)rev_iter->he_obj;
486 if (rev->present)
487 fprintf(cache_fp, "%s: %s\n", tag, rev->rev);
490 fprintf(cache_fp, "\n");
492 reset_hash_iterator(file->revisions);
493 while ((rev_iter = next_hash_entry(file->revisions)))
495 CvsFileRevision * rev = (CvsFileRevision*)rev_iter->he_obj;
496 if (rev->present)
497 fprintf(cache_fp, "%s %s\n", rev->rev, rev->branch);
500 fprintf(cache_fp, "\n");
503 fprintf(cache_fp, "\n");
504 walk_all_patch_sets(write_patch_set_to_cache);
505 fclose(cache_fp);
506 cache_fp = NULL;
509 static void write_patch_set_to_cache(PatchSet * ps)
511 dump_patch_set(cache_fp, ps);
514 static void dump_patch_set(FILE * fp, PatchSet * ps)
516 struct list_head * next = ps->members.next;
518 ps_counter++;
519 fprintf(fp, "patchset: %d\n", ps_counter);
520 fprintf(fp, "date: %d\n", (int)ps->date);
521 fprintf(fp, "author: %s\n", ps->author);
522 fprintf(fp, "tag: %s\n", ps->tag ? ps->tag : "");
523 fprintf(fp, "tag_flags: %d\n", ps->tag_flags);
524 fprintf(fp, "branch: %s\n", ps->branch);
525 fprintf(fp, "branch_add: %d\n", ps->branch_add);
526 fprintf(fp, "descr:\n%s", ps->descr); /* descr is guaranteed to end with LF */
527 fprintf(fp, CACHE_DESCR_BOUNDARY);
528 fprintf(fp, "members:\n");
530 while (next != &ps->members)
532 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
533 int bp = 1;
535 /* this actually deduces if this revision is a branch point... */
536 if (!psm->pre_rev || (psm->pre_rev->pre_psm && psm->pre_rev->pre_psm == psm))
537 bp = 0;
539 fflush(fp);
541 fprintf(fp, "file:%s; pre_rev:%s; post_rev:%s; dead:%d; branch_point:%d\n",
542 psm->file->filename,
543 psm->pre_rev ? psm->pre_rev->rev : "INITIAL", psm->post_rev->rev,
544 psm->post_rev->dead, bp);
545 next = next->next;
548 fprintf(fp, "\n");
551 /* where's arithmetic?... */