File: | src/gnu/usr.bin/cvs/src/commit.c |
Warning: | line 1639, column 5 Value stored to 'retcode' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* |
2 | * Copyright (c) 1992, Brian Berliner and Jeff Polk |
3 | * Copyright (c) 1989-1992, Brian Berliner |
4 | * |
5 | * You may distribute under the terms of the GNU General Public License as |
6 | * specified in the README file that comes with the CVS source distribution. |
7 | * |
8 | * Commit Files |
9 | * |
10 | * "commit" commits the present version to the RCS repository, AFTER |
11 | * having done a test on conflicts. |
12 | * |
13 | * The call is: cvs commit [options] files... |
14 | * |
15 | */ |
16 | |
17 | #include <assert.h> |
18 | #include "cvs.h" |
19 | #include "getline.h" |
20 | #include "edit.h" |
21 | #include "fileattr.h" |
22 | #include "hardlink.h" |
23 | |
24 | static Dtype check_direntproc PROTO ((void *callerdat, char *dir,(void *callerdat, char *dir, char *repos, char *update_dir, List *entries) |
25 | char *repos, char *update_dir,(void *callerdat, char *dir, char *repos, char *update_dir, List *entries) |
26 | List *entries))(void *callerdat, char *dir, char *repos, char *update_dir, List *entries); |
27 | static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo); |
28 | static int check_filesdoneproc PROTO ((void *callerdat, int err,(void *callerdat, int err, char *repos, char *update_dir, List *entries) |
29 | char *repos, char *update_dir,(void *callerdat, int err, char *repos, char *update_dir, List *entries) |
30 | List *entries))(void *callerdat, int err, char *repos, char *update_dir, List *entries); |
31 | static int checkaddfile PROTO((char *file, char *repository, char *tag,(char *file, char *repository, char *tag, char *options, RCSNode **rcsnode) |
32 | char *options, RCSNode **rcsnode))(char *file, char *repository, char *tag, char *options, RCSNode **rcsnode); |
33 | static Dtype commit_direntproc PROTO ((void *callerdat, char *dir,(void *callerdat, char *dir, char *repos, char *update_dir, List *entries) |
34 | char *repos, char *update_dir,(void *callerdat, char *dir, char *repos, char *update_dir, List *entries) |
35 | List *entries))(void *callerdat, char *dir, char *repos, char *update_dir, List *entries); |
36 | static int commit_dirleaveproc PROTO ((void *callerdat, char *dir,(void *callerdat, char *dir, int err, char *update_dir, List * entries) |
37 | int err, char *update_dir,(void *callerdat, char *dir, int err, char *update_dir, List * entries) |
38 | List *entries))(void *callerdat, char *dir, int err, char *update_dir, List * entries); |
39 | static int commit_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo); |
40 | static int commit_filesdoneproc PROTO ((void *callerdat, int err,(void *callerdat, int err, char *repository, char *update_dir , List *entries) |
41 | char *repository, char *update_dir,(void *callerdat, int err, char *repository, char *update_dir , List *entries) |
42 | List *entries))(void *callerdat, int err, char *repository, char *update_dir , List *entries); |
43 | static int finaladd PROTO((struct file_info *finfo, char *revision, char *tag,(struct file_info *finfo, char *revision, char *tag, char *options ) |
44 | char *options))(struct file_info *finfo, char *revision, char *tag, char *options ); |
45 | static int findmaxrev PROTO((Node * p, void *closure))(Node * p, void *closure); |
46 | static int lock_RCS PROTO((char *user, RCSNode *rcs, char *rev,(char *user, RCSNode *rcs, char *rev, char *repository) |
47 | char *repository))(char *user, RCSNode *rcs, char *rev, char *repository); |
48 | static int precommit_list_proc PROTO((Node * p, void *closure))(Node * p, void *closure); |
49 | static int precommit_proc PROTO((char *repository, char *filter))(char *repository, char *filter); |
50 | static int remove_file PROTO ((struct file_info *finfo, char *tag,(struct file_info *finfo, char *tag, char *message) |
51 | char *message))(struct file_info *finfo, char *tag, char *message); |
52 | static void fixaddfile PROTO((char *file, char *repository))(char *file, char *repository); |
53 | static void fixbranch PROTO((RCSNode *, char *branch))(RCSNode *, char *branch); |
54 | static void unlockrcs PROTO((RCSNode *rcs))(RCSNode *rcs); |
55 | static void ci_delproc PROTO((Node *p))(Node *p); |
56 | static void masterlist_delproc PROTO((Node *p))(Node *p); |
57 | static char *locate_rcs PROTO((char *file, char *repository))(char *file, char *repository); |
58 | |
59 | struct commit_info |
60 | { |
61 | Ctype status; /* as returned from Classify_File() */ |
62 | char *rev; /* a numeric rev, if we know it */ |
63 | char *tag; /* any sticky tag, or -r option */ |
64 | char *options; /* Any sticky -k option */ |
65 | }; |
66 | struct master_lists |
67 | { |
68 | List *ulist; /* list for Update_Logfile */ |
69 | List *cilist; /* list with commit_info structs */ |
70 | }; |
71 | |
72 | static int force_ci = 0; |
73 | static int got_message; |
74 | static int run_module_prog = 1; |
75 | static int aflag; |
76 | static char *saved_tag; |
77 | static char *write_dirtag; |
78 | static int write_dirnonbranch; |
79 | static char *logfile; |
80 | static List *mulist; |
81 | static List *saved_ulist; |
82 | static char *saved_message; |
83 | static time_t last_register_time; |
84 | |
85 | static const char *const commit_usage[] = |
86 | { |
87 | "Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n", |
88 | " -n Do not run the module program (if any).\n", |
89 | " -R Process directories recursively.\n", |
90 | " -l Local directory only (not recursive).\n", |
91 | " -f Force the file to be committed; disables recursion.\n", |
92 | " -F logfile Read the log message from file.\n", |
93 | " -m msg Log message.\n", |
94 | " -r rev Commit to this branch or trunk revision.\n", |
95 | "(Specify the --help global option for a list of other help options)\n", |
96 | NULL((void*)0) |
97 | }; |
98 | |
99 | #ifdef CLIENT_SUPPORT1 |
100 | /* Identify a file which needs "? foo" or a Questionable request. */ |
101 | struct question { |
102 | /* The two fields for the Directory request. */ |
103 | char *dir; |
104 | char *repos; |
105 | |
106 | /* The file name. */ |
107 | char *file; |
108 | |
109 | struct question *next; |
110 | }; |
111 | |
112 | struct find_data { |
113 | List *ulist; |
114 | int argc; |
115 | char **argv; |
116 | |
117 | /* This is used from dirent to filesdone time, for each directory, |
118 | to make a list of files we have already seen. */ |
119 | List *ignlist; |
120 | |
121 | /* Linked list of files which need "? foo" or a Questionable request. */ |
122 | struct question *questionables; |
123 | |
124 | /* Only good within functions called from the filesdoneproc. Stores |
125 | the repository (pointer into storage managed by the recursion |
126 | processor. */ |
127 | char *repository; |
128 | |
129 | /* Non-zero if we should force the commit. This is enabled by |
130 | either -f or -r options, unlike force_ci which is just -f. */ |
131 | int force; |
132 | }; |
133 | |
134 | static Dtype find_dirent_proc PROTO ((void *callerdat, char *dir,(void *callerdat, char *dir, char *repository, char *update_dir , List *entries) |
135 | char *repository, char *update_dir,(void *callerdat, char *dir, char *repository, char *update_dir , List *entries) |
136 | List *entries))(void *callerdat, char *dir, char *repository, char *update_dir , List *entries); |
137 | |
138 | static Dtype |
139 | find_dirent_proc (callerdat, dir, repository, update_dir, entries) |
140 | void *callerdat; |
141 | char *dir; |
142 | char *repository; |
143 | char *update_dir; |
144 | List *entries; |
145 | { |
146 | struct find_data *find_data = (struct find_data *)callerdat; |
147 | |
148 | /* This check seems to slowly be creeping throughout CVS (update |
149 | and send_dirent_proc by CVS 1.5, diff in 31 Oct 1995. My guess |
150 | is that it (or some variant thereof) should go in all the |
151 | dirent procs. Unless someone has some better idea... */ |
152 | if (!isdir (dir)) |
153 | return (R_SKIP_ALL); |
154 | |
155 | /* initialize the ignore list for this directory */ |
156 | find_data->ignlist = getlist (); |
157 | |
158 | /* Print the same warm fuzzy as in check_direntproc, since that |
159 | code will never be run during client/server operation and we |
160 | want the messages to match. */ |
161 | if (!quiet) |
162 | error (0, 0, "Examining %s", update_dir); |
163 | |
164 | return R_PROCESS; |
165 | } |
166 | |
167 | /* Here as a static until we get around to fixing ignore_files to pass |
168 | it along as an argument. */ |
169 | static struct find_data *find_data_static; |
170 | |
171 | static void find_ignproc PROTO ((char *, char *))(char *, char *); |
172 | |
173 | static void |
174 | find_ignproc (file, dir) |
175 | char *file; |
176 | char *dir; |
177 | { |
178 | struct question *p; |
179 | |
180 | p = (struct question *) xmalloc (sizeof (struct question)); |
181 | p->dir = xstrdup (dir); |
182 | p->repos = xstrdup (find_data_static->repository); |
183 | p->file = xstrdup (file); |
184 | p->next = find_data_static->questionables; |
185 | find_data_static->questionables = p; |
186 | } |
187 | |
188 | static int find_filesdoneproc PROTO ((void *callerdat, int err,(void *callerdat, int err, char *repository, char *update_dir , List *entries) |
189 | char *repository, char *update_dir,(void *callerdat, int err, char *repository, char *update_dir , List *entries) |
190 | List *entries))(void *callerdat, int err, char *repository, char *update_dir , List *entries); |
191 | |
192 | static int |
193 | find_filesdoneproc (callerdat, err, repository, update_dir, entries) |
194 | void *callerdat; |
195 | int err; |
196 | char *repository; |
197 | char *update_dir; |
198 | List *entries; |
199 | { |
200 | struct find_data *find_data = (struct find_data *)callerdat; |
201 | find_data->repository = repository; |
202 | |
203 | /* if this directory has an ignore list, process it then free it */ |
204 | if (find_data->ignlist) |
205 | { |
206 | find_data_static = find_data; |
207 | ignore_files (find_data->ignlist, entries, update_dir, find_ignproc); |
208 | dellist (&find_data->ignlist); |
209 | } |
210 | |
211 | find_data->repository = NULL((void*)0); |
212 | |
213 | return err; |
214 | } |
215 | |
216 | static int find_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo); |
217 | |
218 | /* Machinery to find out what is modified, added, and removed. It is |
219 | possible this should be broken out into a new client_classify function; |
220 | merging it with classify_file is almost sure to be a mess, though, |
221 | because classify_file has all kinds of repository processing. */ |
222 | static int |
223 | find_fileproc (callerdat, finfo) |
224 | void *callerdat; |
225 | struct file_info *finfo; |
226 | { |
227 | Vers_TS *vers; |
228 | enum classify_type status; |
229 | Node *node; |
230 | struct find_data *args = (struct find_data *)callerdat; |
231 | struct logfile_info *data; |
232 | struct file_info xfinfo; |
233 | |
234 | /* if this directory has an ignore list, add this file to it */ |
235 | if (args->ignlist) |
236 | { |
237 | Node *p; |
238 | |
239 | p = getnode (); |
240 | p->type = FILES; |
241 | p->key = xstrdup (finfo->file); |
242 | if (addnode (args->ignlist, p) != 0) |
243 | freenode (p); |
244 | } |
245 | |
246 | xfinfo = *finfo; |
247 | xfinfo.repository = NULL((void*)0); |
248 | xfinfo.rcs = NULL((void*)0); |
249 | |
250 | vers = Version_TS (&xfinfo, NULL((void*)0), saved_tag, NULL((void*)0), 0, 0); |
251 | if (vers->ts_user == NULL((void*)0) |
252 | && vers->vn_user != NULL((void*)0) |
253 | && vers->vn_user[0] == '-') |
254 | /* FIXME: If vn_user is starts with "-" but ts_user is |
255 | non-NULL, what classify_file does is print "%s should be |
256 | removed and is still there". I'm not sure what it does |
257 | then. We probably should do the same. */ |
258 | status = T_REMOVED; |
259 | else if (vers->vn_user == NULL((void*)0)) |
260 | { |
261 | if (vers->ts_user == NULL((void*)0)) |
262 | error (0, 0, "nothing known about `%s'", finfo->fullname); |
263 | else |
264 | error (0, 0, "use `%s add' to create an entry for %s", |
265 | program_name, finfo->fullname); |
266 | freevers_ts (&vers); |
267 | return 1; |
268 | } |
269 | else if (vers->ts_user != NULL((void*)0) |
270 | && vers->vn_user != NULL((void*)0) |
271 | && vers->vn_user[0] == '0') |
272 | /* FIXME: If vn_user is "0" but ts_user is NULL, what classify_file |
273 | does is print "new-born %s has disappeared" and removes the entry. |
274 | We probably should do the same. */ |
275 | status = T_ADDED; |
276 | else if (vers->ts_user != NULL((void*)0) |
277 | && vers->ts_rcs != NULL((void*)0) |
278 | && (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0)) |
279 | /* If we are forcing commits, pretend that the file is |
280 | modified. */ |
281 | status = T_MODIFIED; |
282 | else |
283 | { |
284 | /* This covers unmodified files, as well as a variety of other |
285 | cases. FIXME: we probably should be printing a message and |
286 | returning 1 for many of those cases (but I'm not sure |
287 | exactly which ones). */ |
288 | freevers_ts (&vers); |
289 | return 0; |
290 | } |
291 | |
292 | node = getnode (); |
293 | node->key = xstrdup (finfo->fullname); |
294 | |
295 | data = (struct logfile_info *) xmalloc (sizeof (struct logfile_info)); |
296 | data->type = status; |
297 | data->tag = xstrdup (vers->tag); |
298 | data->rev_old = data->rev_new = NULL((void*)0); |
299 | |
300 | node->type = UPDATE; |
301 | node->delproc = update_delproc; |
302 | node->data = (char *) data; |
303 | (void)addnode (args->ulist, node); |
304 | |
305 | ++args->argc; |
306 | |
307 | freevers_ts (&vers); |
308 | return 0; |
309 | } |
310 | |
311 | static int copy_ulist PROTO ((Node *, void *))(Node *, void *); |
312 | |
313 | static int |
314 | copy_ulist (node, data) |
315 | Node *node; |
316 | void *data; |
317 | { |
318 | struct find_data *args = (struct find_data *)data; |
319 | args->argv[args->argc++] = node->key; |
320 | return 0; |
321 | } |
322 | #endif /* CLIENT_SUPPORT */ |
323 | |
324 | int |
325 | commit (argc, argv) |
326 | int argc; |
327 | char **argv; |
328 | { |
329 | int c; |
330 | int err = 0; |
331 | int local = 0; |
332 | |
333 | if (argc == -1) |
334 | usage (commit_usage); |
335 | |
336 | #ifdef CVS_BADROOT |
337 | /* |
338 | * For log purposes, do not allow "root" to commit files. If you look |
339 | * like root, but are really logged in as a non-root user, it's OK. |
340 | */ |
341 | /* FIXME: Shouldn't this check be much more closely related to the |
342 | readonly user stuff (CVSROOT/readers, &c). That is, why should |
343 | root be able to "cvs init", "cvs import", &c, but not "cvs ci"? */ |
344 | if (geteuid () == (uid_t) 0 |
345 | # ifdef CLIENT_SUPPORT1 |
346 | /* Who we are on the client side doesn't affect logging. */ |
347 | && !current_parsed_root->isremote |
348 | # endif |
349 | ) |
350 | { |
351 | struct passwd *pw; |
352 | |
353 | if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL((void*)0)) |
354 | error (1, 0, "you are unknown to this system"); |
355 | if (pw->pw_uid == (uid_t) 0) |
356 | error (1, 0, "cannot commit files as 'root'"); |
357 | } |
358 | #endif /* CVS_BADROOT */ |
359 | |
360 | optind = 0; |
361 | while ((c = getopt (argc, argv, "+nlRm:fF:r:")) != -1) |
362 | { |
363 | switch (c) |
364 | { |
365 | case 'n': |
366 | run_module_prog = 0; |
367 | break; |
368 | case 'm': |
369 | #ifdef FORCE_USE_EDITOR |
370 | use_editor = 1; |
371 | #else |
372 | use_editor = 0; |
373 | #endif |
374 | if (saved_message) |
375 | { |
376 | free (saved_message); |
377 | saved_message = NULL((void*)0); |
378 | } |
379 | |
380 | saved_message = xstrdup(optarg); |
381 | break; |
382 | case 'r': |
383 | if (saved_tag) |
384 | free (saved_tag); |
385 | saved_tag = xstrdup (optarg); |
386 | break; |
387 | case 'l': |
388 | local = 1; |
389 | break; |
390 | case 'R': |
391 | local = 0; |
392 | break; |
393 | case 'f': |
394 | force_ci = 1; |
395 | local = 1; /* also disable recursion */ |
396 | break; |
397 | case 'F': |
398 | #ifdef FORCE_USE_EDITOR |
399 | use_editor = 1; |
400 | #else |
401 | use_editor = 0; |
402 | #endif |
403 | logfile = optarg; |
404 | break; |
405 | case '?': |
406 | default: |
407 | usage (commit_usage); |
408 | break; |
409 | } |
410 | } |
411 | argc -= optind; |
412 | argv += optind; |
413 | |
414 | /* numeric specified revision means we ignore sticky tags... */ |
415 | if (saved_tag && isdigit ((unsigned char) *saved_tag)) |
416 | { |
417 | aflag = 1; |
418 | /* strip trailing dots */ |
419 | while (saved_tag[strlen (saved_tag) - 1] == '.') |
420 | saved_tag[strlen (saved_tag) - 1] = '\0'; |
421 | } |
422 | |
423 | /* some checks related to the "-F logfile" option */ |
424 | if (logfile) |
425 | { |
426 | size_t size = 0, len; |
427 | |
428 | if (saved_message) |
429 | error (1, 0, "cannot specify both a message and a log file"); |
430 | |
431 | get_file (logfile, logfile, "r", &saved_message, &size, &len); |
432 | } |
433 | |
434 | #ifdef CLIENT_SUPPORT1 |
435 | if (current_parsed_root->isremote) |
436 | { |
437 | struct find_data find_args; |
438 | |
439 | ign_setup (); |
440 | |
441 | find_args.ulist = getlist (); |
442 | find_args.argc = 0; |
443 | find_args.questionables = NULL((void*)0); |
444 | find_args.ignlist = NULL((void*)0); |
445 | find_args.repository = NULL((void*)0); |
446 | |
447 | /* It is possible that only a numeric tag should set this. |
448 | I haven't really thought about it much. |
449 | Anyway, I suspect that setting it unnecessarily only causes |
450 | a little unneeded network traffic. */ |
451 | find_args.force = force_ci || saved_tag != NULL((void*)0); |
452 | |
453 | err = start_recursion (find_fileproc, find_filesdoneproc, |
454 | find_dirent_proc, (DIRLEAVEPROC) NULL((void*)0), |
455 | (void *)&find_args, |
456 | argc, argv, local, W_LOCAL0x01, 0, 0, |
457 | (char *)NULL((void*)0), 0); |
458 | if (err) |
459 | error (1, 0, "correct above errors first!"); |
460 | |
461 | if (find_args.argc == 0) |
462 | { |
463 | /* Nothing to commit. Exit now without contacting the |
464 | server (note that this means that we won't print "? |
465 | foo" for files which merit it, because we don't know |
466 | what is in the CVSROOT/cvsignore file). */ |
467 | dellist (&find_args.ulist); |
468 | return 0; |
469 | } |
470 | |
471 | /* Now we keep track of which files we actually are going to |
472 | operate on, and only work with those files in the future. |
473 | This saves time--we don't want to search the file system |
474 | of the working directory twice. */ |
475 | if (size_overflow_p (xtimes (find_args.argc, sizeof (char **)))((((find_args.argc) <= 0xffffffffffffffffUL / (sizeof (char **)) ? (size_t) (find_args.argc) * (sizeof (char **)) : 0xffffffffffffffffUL )) == 0xffffffffffffffffUL)) |
476 | { |
477 | find_args.argc = 0; |
478 | return 0; |
479 | } |
480 | find_args.argv = xmalloc (xtimes (find_args.argc, sizeof (char **))((find_args.argc) <= 0xffffffffffffffffUL / (sizeof (char * *)) ? (size_t) (find_args.argc) * (sizeof (char **)) : 0xffffffffffffffffUL )); |
481 | find_args.argc = 0; |
482 | walklist (find_args.ulist, copy_ulist, &find_args); |
483 | |
484 | /* Do this before calling do_editor; don't ask for a log |
485 | message if we can't talk to the server. But do it after we |
486 | have made the checks that we can locally (to more quickly |
487 | catch syntax errors, the case where no files are modified, |
488 | added or removed, etc.). |
489 | |
490 | On the other hand, calling start_server before do_editor |
491 | means that we chew up server resources the whole time that |
492 | the user has the editor open (hours or days if the user |
493 | forgets about it), which seems dubious. */ |
494 | start_server (); |
495 | |
496 | /* |
497 | * We do this once, not once for each directory as in normal CVS. |
498 | * The protocol is designed this way. This is a feature. |
499 | */ |
500 | if (use_editor) |
501 | do_editor (".", &saved_message, (char *)NULL((void*)0), find_args.ulist); |
502 | |
503 | /* Run the user-defined script to verify/check information in |
504 | *the log message |
505 | */ |
506 | do_verify (saved_message, (char *)NULL((void*)0)); |
507 | |
508 | /* We always send some sort of message, even if empty. */ |
509 | /* FIXME: is that true? There seems to be some code in do_editor |
510 | which can leave the message NULL. */ |
511 | option_with_arg ("-m", saved_message); |
512 | |
513 | /* OK, now process all the questionable files we have been saving |
514 | up. */ |
515 | { |
516 | struct question *p; |
517 | struct question *q; |
518 | |
519 | p = find_args.questionables; |
520 | while (p != NULL((void*)0)) |
521 | { |
522 | if (ign_inhibit_server || !supported_request ("Questionable")) |
523 | { |
524 | cvs_output ("? ", 2); |
525 | if (p->dir[0] != '\0') |
526 | { |
527 | cvs_output (p->dir, 0); |
528 | cvs_output ("/", 1); |
529 | } |
530 | cvs_output (p->file, 0); |
531 | cvs_output ("\n", 1); |
532 | } |
533 | else |
534 | { |
535 | send_to_server ("Directory ", 0); |
536 | send_to_server (p->dir[0] == '\0' ? "." : p->dir, 0); |
537 | send_to_server ("\012", 1); |
538 | send_to_server (p->repos, 0); |
539 | send_to_server ("\012", 1); |
540 | |
541 | send_to_server ("Questionable ", 0); |
542 | send_to_server (p->file, 0); |
543 | send_to_server ("\012", 1); |
544 | } |
545 | free (p->dir); |
546 | free (p->repos); |
547 | free (p->file); |
548 | q = p->next; |
549 | free (p); |
550 | p = q; |
551 | } |
552 | } |
553 | |
554 | if (local) |
555 | send_arg("-l"); |
556 | if (force_ci) |
557 | send_arg("-f"); |
558 | if (!run_module_prog) |
559 | send_arg("-n"); |
560 | option_with_arg ("-r", saved_tag); |
561 | |
562 | /* FIXME: This whole find_args.force/SEND_FORCE business is a |
563 | kludge. It would seem to be a server bug that we have to |
564 | say that files are modified when they are not. This makes |
565 | "cvs commit -r 2" across a whole bunch of files a very slow |
566 | operation (and it isn't documented in cvsclient.texi). I |
567 | haven't looked at the server code carefully enough to be |
568 | _sure_ why this is needed, but if it is because the "ci" |
569 | program, which we used to call, wanted the file to exist, |
570 | then it would be relatively simple to fix in the server. */ |
571 | send_files (find_args.argc, find_args.argv, local, 0, |
572 | find_args.force ? SEND_FORCE2 : 0); |
573 | |
574 | /* Sending only the names of the files which were modified, added, |
575 | or removed means that the server will only do an up-to-date |
576 | check on those files. This is different from local CVS and |
577 | previous versions of client/server CVS, but it probably is a Good |
578 | Thing, or at least Not Such A Bad Thing. */ |
579 | send_file_names (find_args.argc, find_args.argv, 0); |
580 | free (find_args.argv); |
581 | dellist (&find_args.ulist); |
582 | |
583 | send_to_server ("ci\012", 0); |
584 | err = get_responses_and_close (); |
585 | if (err != 0 && use_editor && saved_message != NULL((void*)0)) |
586 | { |
587 | /* If there was an error, don't nuke the user's carefully |
588 | constructed prose. This is something of a kludge; a better |
589 | solution is probably more along the lines of #150 in TODO |
590 | (doing a second up-to-date check before accepting the |
591 | log message has also been suggested, but that seems kind of |
592 | iffy because the real up-to-date check could still fail, |
593 | another error could occur, &c. Also, a second check would |
594 | slow things down). */ |
595 | |
596 | char *fname; |
597 | FILE *fp; |
598 | |
599 | fp = cvs_temp_file (&fname); |
600 | if (fp == NULL((void*)0)) |
601 | error (1, 0, "cannot create temporary file %s", fname); |
602 | if (fwrite (saved_message, 1, strlen (saved_message), fp) |
603 | != strlen (saved_message)) |
604 | error (1, errno(*__errno()), "cannot write temporary file %s", fname); |
605 | if (fclose (fp) < 0) |
606 | error (0, errno(*__errno()), "cannot close temporary file %s", fname); |
607 | error (0, 0, "saving log message in %s", fname); |
608 | free (fname); |
609 | } |
610 | return err; |
611 | } |
612 | #endif |
613 | |
614 | if (saved_tag != NULL((void*)0)) |
615 | tag_check_valid (saved_tag, argc, argv, local, aflag, ""); |
616 | |
617 | /* XXX - this is not the perfect check for this */ |
618 | if (argc <= 0) |
619 | write_dirtag = saved_tag; |
620 | |
621 | wrap_setup (); |
622 | |
623 | lock_tree_for_write (argc, argv, local, W_LOCAL0x01, aflag); |
624 | |
625 | /* |
626 | * Set up the master update list and hard link list |
627 | */ |
628 | mulist = getlist (); |
629 | |
630 | #ifdef PRESERVE_PERMISSIONS_SUPPORT |
631 | if (preserve_perms) |
632 | { |
633 | hardlist = getlist (); |
634 | |
635 | /* |
636 | * We need to save the working directory so that |
637 | * check_fileproc can construct a full pathname for each file. |
638 | */ |
639 | working_dir = xgetwd(); |
640 | } |
641 | #endif |
642 | |
643 | /* |
644 | * Run the recursion processor to verify the files are all up-to-date |
645 | */ |
646 | err = start_recursion (check_fileproc, check_filesdoneproc, |
647 | check_direntproc, (DIRLEAVEPROC) NULL((void*)0), NULL((void*)0), argc, |
648 | argv, local, W_LOCAL0x01, aflag, 0, (char *) NULL((void*)0), 1); |
649 | if (err) |
650 | { |
651 | Lock_Cleanup (); |
652 | error (1, 0, "correct above errors first!"); |
653 | } |
654 | |
655 | /* |
656 | * Run the recursion processor to commit the files |
657 | */ |
658 | write_dirnonbranch = 0; |
659 | if (noexec == 0) |
660 | err = start_recursion (commit_fileproc, commit_filesdoneproc, |
661 | commit_direntproc, commit_dirleaveproc, NULL((void*)0), |
662 | argc, argv, local, W_LOCAL0x01, aflag, 0, |
663 | (char *) NULL((void*)0), 1); |
664 | |
665 | /* |
666 | * Unlock all the dirs and clean up |
667 | */ |
668 | Lock_Cleanup (); |
669 | dellist (&mulist); |
670 | |
671 | #ifdef SERVER_SUPPORT1 |
672 | if (server_active) |
673 | return err; |
674 | #endif |
675 | |
676 | /* see if we need to sleep before returning to avoid time-stamp races */ |
677 | if (last_register_time) |
678 | { |
679 | sleep_past (last_register_time); |
680 | } |
681 | |
682 | return (err); |
683 | } |
684 | |
685 | /* This routine determines the status of a given file and retrieves |
686 | the version information that is associated with that file. */ |
687 | |
688 | static |
689 | Ctype |
690 | classify_file_internal (finfo, vers) |
691 | struct file_info *finfo; |
692 | Vers_TS **vers; |
693 | { |
694 | int save_noexec, save_quiet, save_really_quiet; |
695 | Ctype status; |
696 | |
697 | /* FIXME: Do we need to save quiet as well as really_quiet? Last |
698 | time I glanced at Classify_File I only saw it looking at really_quiet |
699 | not quiet. */ |
700 | save_noexec = noexec; |
701 | save_quiet = quiet; |
702 | save_really_quiet = really_quiet; |
703 | noexec = quiet = really_quiet = 1; |
704 | |
705 | /* handle specified numeric revision specially */ |
706 | if (saved_tag && isdigit ((unsigned char) *saved_tag)) |
707 | { |
708 | /* If the tag is for the trunk, make sure we're at the head */ |
709 | if (numdots (saved_tag) < 2) |
710 | { |
711 | status = Classify_File (finfo, (char *) NULL((void*)0), (char *) NULL((void*)0), |
712 | (char *) NULL((void*)0), 1, aflag, vers, 0); |
713 | if (status == T_UPTODATE || status == T_MODIFIED || |
714 | status == T_ADDED) |
715 | { |
716 | Ctype xstatus; |
717 | |
718 | freevers_ts (vers); |
719 | xstatus = Classify_File (finfo, saved_tag, (char *) NULL((void*)0), |
720 | (char *) NULL((void*)0), 1, aflag, vers, 0); |
721 | if (xstatus == T_REMOVE_ENTRY) |
722 | status = T_MODIFIED; |
723 | else if (status == T_MODIFIED && xstatus == T_CONFLICT) |
724 | status = T_MODIFIED; |
725 | else |
726 | status = xstatus; |
727 | } |
728 | } |
729 | else |
730 | { |
731 | char *xtag, *cp; |
732 | |
733 | /* |
734 | * The revision is off the main trunk; make sure we're |
735 | * up-to-date with the head of the specified branch. |
736 | */ |
737 | xtag = xstrdup (saved_tag); |
738 | if ((numdots (xtag) & 1) != 0) |
739 | { |
740 | cp = strrchr (xtag, '.'); |
741 | *cp = '\0'; |
742 | } |
743 | status = Classify_File (finfo, xtag, (char *) NULL((void*)0), |
744 | (char *) NULL((void*)0), 1, aflag, vers, 0); |
745 | if ((status == T_REMOVE_ENTRY || status == T_CONFLICT) |
746 | && (cp = strrchr (xtag, '.')) != NULL((void*)0)) |
747 | { |
748 | /* pluck one more dot off the revision */ |
749 | *cp = '\0'; |
750 | freevers_ts (vers); |
751 | status = Classify_File (finfo, xtag, (char *) NULL((void*)0), |
752 | (char *) NULL((void*)0), 1, aflag, vers, 0); |
753 | if (status == T_UPTODATE || status == T_REMOVE_ENTRY) |
754 | status = T_MODIFIED; |
755 | } |
756 | /* now, muck with vers to make the tag correct */ |
757 | free ((*vers)->tag); |
758 | (*vers)->tag = xstrdup (saved_tag); |
759 | free (xtag); |
760 | } |
761 | } |
762 | else |
763 | status = Classify_File (finfo, saved_tag, (char *) NULL((void*)0), (char *) NULL((void*)0), |
764 | 1, 0, vers, 0); |
765 | noexec = save_noexec; |
766 | quiet = save_quiet; |
767 | really_quiet = save_really_quiet; |
768 | |
769 | return status; |
770 | } |
771 | |
772 | /* |
773 | * Check to see if a file is ok to commit and make sure all files are |
774 | * up-to-date |
775 | */ |
776 | /* ARGSUSED */ |
777 | static int |
778 | check_fileproc (callerdat, finfo) |
779 | void *callerdat; |
780 | struct file_info *finfo; |
781 | { |
782 | Ctype status; |
783 | char *xdir; |
784 | Node *p; |
785 | List *ulist, *cilist; |
786 | Vers_TS *vers; |
787 | struct commit_info *ci; |
788 | struct logfile_info *li; |
789 | |
790 | size_t cvsroot_len = strlen (current_parsed_root->directory); |
791 | |
792 | if (!finfo->repository) |
793 | { |
794 | error (0, 0, "nothing known about `%s'", finfo->fullname); |
795 | return (1); |
796 | } |
797 | |
798 | if (strncmp (finfo->repository, current_parsed_root->directory, cvsroot_len) == 0 |
799 | && ISDIRSEP (finfo->repository[cvsroot_len])((finfo->repository[cvsroot_len]) == '/') |
800 | && strncmp (finfo->repository + cvsroot_len + 1, |
801 | CVSROOTADM"CVSROOT", |
802 | sizeof (CVSROOTADM"CVSROOT") - 1) == 0 |
803 | && ISDIRSEP (finfo->repository[cvsroot_len + sizeof (CVSROOTADM)])((finfo->repository[cvsroot_len + sizeof ("CVSROOT")]) == '/' ) |
804 | && strcmp (finfo->repository + cvsroot_len + sizeof (CVSROOTADM"CVSROOT") + 1, |
805 | CVSNULLREPOS"Emptydir") == 0 |
806 | ) |
807 | error (1, 0, "cannot check in to %s", finfo->repository); |
808 | |
809 | status = classify_file_internal (finfo, &vers); |
810 | |
811 | /* |
812 | * If the force-commit option is enabled, and the file in question |
813 | * appears to be up-to-date, just make it look modified so that |
814 | * it will be committed. |
815 | */ |
816 | if (force_ci && status == T_UPTODATE) |
817 | status = T_MODIFIED; |
818 | |
819 | switch (status) |
820 | { |
821 | case T_CHECKOUT: |
822 | case T_PATCH: |
823 | case T_NEEDS_MERGE: |
824 | case T_CONFLICT: |
825 | case T_REMOVE_ENTRY: |
826 | error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname); |
827 | freevers_ts (&vers); |
828 | return (1); |
829 | case T_MODIFIED: |
830 | case T_ADDED: |
831 | case T_REMOVED: |
832 | /* |
833 | * some quick sanity checks; if no numeric -r option specified: |
834 | * - can't have a sticky date |
835 | * - can't have a sticky tag that is not a branch |
836 | * Also, |
837 | * - if status is T_REMOVED, can't have a numeric tag |
838 | * - if status is T_ADDED, rcs file must not exist unless on |
839 | * a branch or head is dead |
840 | * - if status is T_ADDED, can't have a non-trunk numeric rev |
841 | * - if status is T_MODIFIED and a Conflict marker exists, don't |
842 | * allow the commit if timestamp is identical or if we find |
843 | * an RCS_MERGE_PAT in the file. |
844 | */ |
845 | if (!saved_tag || !isdigit ((unsigned char) *saved_tag)) |
846 | { |
847 | if (vers->date) |
848 | { |
849 | error (0, 0, |
850 | "cannot commit with sticky date for file `%s'", |
851 | finfo->fullname); |
852 | freevers_ts (&vers); |
853 | return (1); |
854 | } |
855 | if (status == T_MODIFIED && vers->tag && |
856 | !RCS_isbranch (finfo->rcs, vers->tag)) |
857 | { |
858 | error (0, 0, |
859 | "sticky tag `%s' for file `%s' is not a branch", |
860 | vers->tag, finfo->fullname); |
861 | freevers_ts (&vers); |
862 | return (1); |
863 | } |
864 | } |
865 | if (status == T_MODIFIED && !force_ci && vers->ts_conflict) |
866 | { |
867 | char *filestamp; |
868 | int retcode; |
869 | |
870 | /* |
871 | * We found a "conflict" marker. |
872 | * |
873 | * If the timestamp on the file is the same as the |
874 | * timestamp stored in the Entries file, we block the commit. |
875 | */ |
876 | #ifdef SERVER_SUPPORT1 |
877 | if (server_active) |
878 | retcode = vers->ts_conflict[0] != '='; |
879 | else { |
880 | filestamp = time_stamp (finfo->file); |
881 | retcode = strcmp (vers->ts_conflict, filestamp); |
882 | free (filestamp); |
883 | } |
884 | #else |
885 | filestamp = time_stamp (finfo->file); |
886 | retcode = strcmp (vers->ts_conflict, filestamp); |
887 | free (filestamp); |
888 | #endif |
889 | if (retcode == 0) |
890 | { |
891 | error (0, 0, |
892 | "file `%s' had a conflict and has not been modified", |
893 | finfo->fullname); |
894 | freevers_ts (&vers); |
895 | return (1); |
896 | } |
897 | |
898 | if (file_has_markers (finfo)) |
899 | { |
900 | /* Make this a warning, not an error, because we have |
901 | no way of knowing whether the "conflict indicators" |
902 | are really from a conflict or whether they are part |
903 | of the document itself (cvs.texinfo and sanity.sh in |
904 | CVS itself, for example, tend to want to have strings |
905 | like ">>>>>>>" at the start of a line). Making people |
906 | kludge this the way they need to kludge keyword |
907 | expansion seems undesirable. And it is worse than |
908 | keyword expansion, because there is no -ko |
909 | analogue. */ |
910 | error (0, 0, |
911 | "\ |
912 | warning: file `%s' seems to still contain conflict indicators", |
913 | finfo->fullname); |
914 | } |
915 | } |
916 | |
917 | if (status == T_REMOVED |
918 | && vers->tag |
919 | && isdigit ((unsigned char) *vers->tag)) |
920 | { |
921 | /* Remove also tries to forbid this, but we should check |
922 | here. I'm only _sure_ about somewhat obscure cases |
923 | (hacking the Entries file, using an old version of |
924 | CVS for the remove and a new one for the commit), but |
925 | there might be other cases. */ |
926 | error (0, 0, |
927 | "cannot remove file `%s' which has a numeric sticky tag of `%s'", |
928 | finfo->fullname, vers->tag); |
929 | freevers_ts (&vers); |
930 | return (1); |
931 | } |
932 | if (status == T_ADDED) |
933 | { |
934 | if (vers->tag == NULL((void*)0)) |
935 | { |
936 | if (finfo->rcs != NULL((void*)0) && |
937 | !RCS_isdead (finfo->rcs, finfo->rcs->head)) |
938 | { |
939 | error (0, 0, |
940 | "cannot add file `%s' when RCS file `%s' already exists", |
941 | finfo->fullname, finfo->rcs->path); |
942 | freevers_ts (&vers); |
943 | return (1); |
944 | } |
945 | } |
946 | else if (isdigit ((unsigned char) *vers->tag) && |
947 | numdots (vers->tag) > 1) |
948 | { |
949 | error (0, 0, |
950 | "cannot add file `%s' with revision `%s'; must be on trunk", |
951 | finfo->fullname, vers->tag); |
952 | freevers_ts (&vers); |
953 | return (1); |
954 | } |
955 | } |
956 | |
957 | /* done with consistency checks; now, to get on with the commit */ |
958 | if (finfo->update_dir[0] == '\0') |
959 | xdir = "."; |
960 | else |
961 | xdir = finfo->update_dir; |
962 | if ((p = findnode (mulist, xdir)) != NULL((void*)0)) |
963 | { |
964 | ulist = ((struct master_lists *) p->data)->ulist; |
965 | cilist = ((struct master_lists *) p->data)->cilist; |
966 | } |
967 | else |
968 | { |
969 | struct master_lists *ml; |
970 | |
971 | ulist = getlist (); |
972 | cilist = getlist (); |
973 | p = getnode (); |
974 | p->key = xstrdup (xdir); |
975 | p->type = UPDATE; |
976 | ml = (struct master_lists *) |
977 | xmalloc (sizeof (struct master_lists)); |
978 | ml->ulist = ulist; |
979 | ml->cilist = cilist; |
980 | p->data = (char *) ml; |
981 | p->delproc = masterlist_delproc; |
982 | (void) addnode (mulist, p); |
983 | } |
984 | |
985 | /* first do ulist, then cilist */ |
986 | p = getnode (); |
987 | p->key = xstrdup (finfo->file); |
988 | p->type = UPDATE; |
989 | p->delproc = update_delproc; |
990 | li = ((struct logfile_info *) |
991 | xmalloc (sizeof (struct logfile_info))); |
992 | li->type = status; |
993 | li->tag = xstrdup (vers->tag); |
994 | li->rev_old = xstrdup (vers->vn_rcs); |
995 | li->rev_new = NULL((void*)0); |
996 | p->data = (char *) li; |
997 | (void) addnode (ulist, p); |
998 | |
999 | p = getnode (); |
1000 | p->key = xstrdup (finfo->file); |
1001 | p->type = UPDATE; |
1002 | p->delproc = ci_delproc; |
1003 | ci = (struct commit_info *) xmalloc (sizeof (struct commit_info)); |
1004 | ci->status = status; |
1005 | if (vers->tag) |
1006 | if (isdigit ((unsigned char) *vers->tag)) |
1007 | ci->rev = xstrdup (vers->tag); |
1008 | else |
1009 | ci->rev = RCS_whatbranch (finfo->rcs, vers->tag); |
1010 | else |
1011 | ci->rev = (char *) NULL((void*)0); |
1012 | ci->tag = xstrdup (vers->tag); |
1013 | ci->options = xstrdup(vers->options); |
1014 | p->data = (char *) ci; |
1015 | (void) addnode (cilist, p); |
1016 | |
1017 | #ifdef PRESERVE_PERMISSIONS_SUPPORT |
1018 | if (preserve_perms) |
1019 | { |
1020 | /* Add this file to hardlist, indexed on its inode. When |
1021 | we are done, we can find out what files are hardlinked |
1022 | to a given file by looking up its inode in hardlist. */ |
1023 | char *fullpath; |
1024 | Node *linkp; |
1025 | struct hardlink_info *hlinfo; |
1026 | |
1027 | /* Get the full pathname of the current file. */ |
1028 | fullpath = xmalloc (strlen(working_dir) + |
1029 | strlen(finfo->fullname) + 2); |
1030 | sprintf (fullpath, "%s/%s", working_dir, finfo->fullname); |
1031 | |
1032 | /* To permit following links in subdirectories, files |
1033 | are keyed on finfo->fullname, not on finfo->name. */ |
1034 | linkp = lookup_file_by_inode (fullpath); |
1035 | |
1036 | /* If linkp is NULL, the file doesn't exist... maybe |
1037 | we're doing a remove operation? */ |
1038 | if (linkp != NULL((void*)0)) |
1039 | { |
1040 | /* Create a new hardlink_info node, which will record |
1041 | the current file's status and the links listed in its |
1042 | `hardlinks' delta field. We will append this |
1043 | hardlink_info node to the appropriate hardlist entry. */ |
1044 | hlinfo = (struct hardlink_info *) |
1045 | xmalloc (sizeof (struct hardlink_info)); |
1046 | hlinfo->status = status; |
1047 | linkp->data = (char *) hlinfo; |
1048 | } |
1049 | } |
1050 | #endif |
1051 | |
1052 | break; |
1053 | case T_UNKNOWN: |
1054 | error (0, 0, "nothing known about `%s'", finfo->fullname); |
1055 | freevers_ts (&vers); |
1056 | return (1); |
1057 | case T_UPTODATE: |
1058 | break; |
1059 | default: |
1060 | error (0, 0, "CVS internal error: unknown status %d", status); |
1061 | break; |
1062 | } |
1063 | |
1064 | freevers_ts (&vers); |
1065 | return (0); |
1066 | } |
1067 | |
1068 | /* |
1069 | * By default, return the code that tells do_recursion to examine all |
1070 | * directories |
1071 | */ |
1072 | /* ARGSUSED */ |
1073 | static Dtype |
1074 | check_direntproc (callerdat, dir, repos, update_dir, entries) |
1075 | void *callerdat; |
1076 | char *dir; |
1077 | char *repos; |
1078 | char *update_dir; |
1079 | List *entries; |
1080 | { |
1081 | if (!isdir (dir)) |
1082 | return (R_SKIP_ALL); |
1083 | |
1084 | if (!quiet) |
1085 | error (0, 0, "Examining %s", update_dir); |
1086 | |
1087 | return (R_PROCESS); |
1088 | } |
1089 | |
1090 | /* |
1091 | * Walklist proc to run pre-commit checks |
1092 | */ |
1093 | static int |
1094 | precommit_list_proc (p, closure) |
1095 | Node *p; |
1096 | void *closure; |
1097 | { |
1098 | struct logfile_info *li; |
1099 | |
1100 | li = (struct logfile_info *) p->data; |
1101 | if (li->type == T_ADDED |
1102 | || li->type == T_MODIFIED |
1103 | || li->type == T_REMOVED) |
1104 | { |
1105 | run_arg (p->key); |
1106 | } |
1107 | return (0); |
1108 | } |
1109 | |
1110 | /* |
1111 | * Callback proc for pre-commit checking |
1112 | */ |
1113 | static int |
1114 | precommit_proc (repository, filter) |
1115 | char *repository; |
1116 | char *filter; |
1117 | { |
1118 | /* see if the filter is there, only if it's a full path */ |
1119 | if (isabsolute (filter)) |
1120 | { |
1121 | char *s, *cp; |
1122 | |
1123 | s = xstrdup (filter); |
1124 | for (cp = s; *cp; cp++) |
1125 | if (isspace ((unsigned char) *cp)) |
1126 | { |
1127 | *cp = '\0'; |
1128 | break; |
1129 | } |
1130 | if (!isfile (s)) |
1131 | { |
1132 | error (0, errno(*__errno()), "cannot find pre-commit filter `%s'", s); |
1133 | free (s); |
1134 | return (1); /* so it fails! */ |
1135 | } |
1136 | free (s); |
1137 | } |
1138 | |
1139 | run_setup (filter); |
1140 | run_arg (repository); |
1141 | (void) walklist (saved_ulist, precommit_list_proc, NULL((void*)0)); |
1142 | return (run_exec (RUN_TTY(char *)0, RUN_TTY(char *)0, RUN_TTY(char *)0, RUN_NORMAL0x0000|RUN_REALLY0x0002)); |
1143 | } |
1144 | |
1145 | /* |
1146 | * Run the pre-commit checks for the dir |
1147 | */ |
1148 | /* ARGSUSED */ |
1149 | static int |
1150 | check_filesdoneproc (callerdat, err, repos, update_dir, entries) |
1151 | void *callerdat; |
1152 | int err; |
1153 | char *repos; |
1154 | char *update_dir; |
1155 | List *entries; |
1156 | { |
1157 | int n; |
1158 | Node *p; |
1159 | |
1160 | /* find the update list for this dir */ |
1161 | p = findnode (mulist, update_dir); |
1162 | if (p != NULL((void*)0)) |
1163 | saved_ulist = ((struct master_lists *) p->data)->ulist; |
1164 | else |
1165 | saved_ulist = (List *) NULL((void*)0); |
1166 | |
1167 | /* skip the checks if there's nothing to do */ |
1168 | if (saved_ulist == NULL((void*)0) || saved_ulist->list->next == saved_ulist->list) |
1169 | return (err); |
1170 | |
1171 | /* run any pre-commit checks */ |
1172 | if ((n = Parse_Info (CVSROOTADM_COMMITINFO"commitinfo", repos, precommit_proc, 1)) > 0) |
1173 | { |
1174 | error (0, 0, "Pre-commit check failed"); |
1175 | err += n; |
1176 | } |
1177 | |
1178 | return (err); |
1179 | } |
1180 | |
1181 | /* |
1182 | * Do the work of committing a file |
1183 | */ |
1184 | static int maxrev; |
1185 | static char *sbranch; |
1186 | |
1187 | /* ARGSUSED */ |
1188 | static int |
1189 | commit_fileproc (callerdat, finfo) |
1190 | void *callerdat; |
1191 | struct file_info *finfo; |
1192 | { |
1193 | Node *p; |
1194 | int err = 0; |
1195 | List *ulist, *cilist; |
1196 | struct commit_info *ci; |
1197 | |
1198 | /* Keep track of whether write_dirtag is a branch tag. |
1199 | Note that if it is a branch tag in some files and a nonbranch tag |
1200 | in others, treat it as a nonbranch tag. It is possible that case |
1201 | should elicit a warning or an error. */ |
1202 | if (write_dirtag != NULL((void*)0) |
1203 | && finfo->rcs != NULL((void*)0)) |
1204 | { |
1205 | char *rev = RCS_getversion (finfo->rcs, write_dirtag, NULL((void*)0), 1, NULL((void*)0)); |
1206 | if (rev != NULL((void*)0) |
1207 | && !RCS_nodeisbranch (finfo->rcs, write_dirtag)) |
1208 | write_dirnonbranch = 1; |
1209 | if (rev != NULL((void*)0)) |
1210 | free (rev); |
1211 | } |
1212 | |
1213 | if (finfo->update_dir[0] == '\0') |
1214 | p = findnode (mulist, "."); |
1215 | else |
1216 | p = findnode (mulist, finfo->update_dir); |
1217 | |
1218 | /* |
1219 | * if p is null, there were file type command line args which were |
1220 | * all up-to-date so nothing really needs to be done |
1221 | */ |
1222 | if (p == NULL((void*)0)) |
1223 | return (0); |
1224 | ulist = ((struct master_lists *) p->data)->ulist; |
1225 | cilist = ((struct master_lists *) p->data)->cilist; |
1226 | |
1227 | /* |
1228 | * At this point, we should have the commit message unless we were called |
1229 | * with files as args from the command line. In that latter case, we |
1230 | * need to get the commit message ourselves |
1231 | */ |
1232 | if (!(got_message)) |
1233 | { |
1234 | got_message = 1; |
1235 | if (use_editor) |
1236 | do_editor (finfo->update_dir, &saved_message, |
1237 | finfo->repository, ulist); |
1238 | do_verify (saved_message, finfo->repository); |
1239 | } |
1240 | |
1241 | p = findnode (cilist, finfo->file); |
1242 | if (p == NULL((void*)0)) |
1243 | return (0); |
1244 | |
1245 | ci = (struct commit_info *) p->data; |
1246 | if (ci->status == T_MODIFIED) |
1247 | { |
1248 | if (finfo->rcs == NULL((void*)0)) |
1249 | error (1, 0, "internal error: no parsed RCS file"); |
1250 | if (lock_RCS (finfo->file, finfo->rcs, ci->rev, |
1251 | finfo->repository) != 0) |
1252 | { |
1253 | unlockrcs (finfo->rcs); |
1254 | err = 1; |
1255 | goto out; |
1256 | } |
1257 | } |
1258 | else if (ci->status == T_ADDED) |
1259 | { |
1260 | if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options, |
1261 | &finfo->rcs) != 0) |
1262 | { |
1263 | fixaddfile (finfo->file, finfo->repository); |
1264 | err = 1; |
1265 | goto out; |
1266 | } |
1267 | |
1268 | /* adding files with a tag, now means adding them on a branch. |
1269 | Since the branch test was done in check_fileproc for |
1270 | modified files, we need to stub it in again here. */ |
1271 | |
1272 | if (ci->tag |
1273 | |
1274 | /* If numeric, it is on the trunk; check_fileproc enforced |
1275 | this. */ |
1276 | && !isdigit ((unsigned char) ci->tag[0])) |
1277 | { |
1278 | if (finfo->rcs == NULL((void*)0)) |
1279 | error (1, 0, "internal error: no parsed RCS file"); |
1280 | if (ci->rev) |
1281 | free (ci->rev); |
1282 | ci->rev = RCS_whatbranch (finfo->rcs, ci->tag); |
1283 | err = Checkin ('A', finfo, finfo->rcs->path, ci->rev, |
1284 | ci->tag, ci->options, saved_message); |
1285 | if (err != 0) |
1286 | { |
1287 | unlockrcs (finfo->rcs); |
1288 | fixbranch (finfo->rcs, sbranch); |
1289 | } |
1290 | |
1291 | (void) time (&last_register_time); |
1292 | |
1293 | ci->status = T_UPTODATE; |
1294 | } |
1295 | } |
1296 | |
1297 | /* |
1298 | * Add the file for real |
1299 | */ |
1300 | if (ci->status == T_ADDED) |
1301 | { |
1302 | char *xrev = (char *) NULL((void*)0); |
1303 | |
1304 | if (ci->rev == NULL((void*)0)) |
1305 | { |
1306 | /* find the max major rev number in this directory */ |
1307 | maxrev = 0; |
1308 | (void) walklist (finfo->entries, findmaxrev, NULL((void*)0)); |
1309 | if (finfo->rcs->head) { |
1310 | /* resurrecting: include dead revision */ |
1311 | int thisrev = atoi (finfo->rcs->head); |
1312 | if (thisrev > maxrev) |
1313 | maxrev = thisrev; |
1314 | } |
1315 | if (maxrev == 0) |
1316 | maxrev = 1; |
1317 | xrev = xmalloc (20); |
1318 | (void) sprintf (xrev, "%d", maxrev); |
1319 | } |
1320 | |
1321 | /* XXX - an added file with symbolic -r should add tag as well */ |
1322 | err = finaladd (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options); |
1323 | if (xrev) |
1324 | free (xrev); |
1325 | } |
1326 | else if (ci->status == T_MODIFIED) |
1327 | { |
1328 | err = Checkin ('M', finfo, |
1329 | finfo->rcs->path, ci->rev, ci->tag, |
1330 | ci->options, saved_message); |
1331 | |
1332 | (void) time (&last_register_time); |
1333 | |
1334 | if (err != 0) |
1335 | { |
1336 | unlockrcs (finfo->rcs); |
1337 | fixbranch (finfo->rcs, sbranch); |
1338 | } |
1339 | } |
1340 | else if (ci->status == T_REMOVED) |
1341 | { |
1342 | err = remove_file (finfo, ci->tag, saved_message); |
1343 | #ifdef SERVER_SUPPORT1 |
1344 | if (server_active) { |
1345 | server_scratch_entry_only (); |
1346 | server_updated (finfo, |
1347 | NULL((void*)0), |
1348 | |
1349 | /* Doesn't matter, it won't get checked. */ |
1350 | SERVER_UPDATED, |
1351 | |
1352 | (mode_t) -1, |
1353 | (unsigned char *) NULL((void*)0), |
1354 | (struct buffer *) NULL((void*)0)); |
1355 | } |
1356 | #endif |
1357 | } |
1358 | |
1359 | /* Clearly this is right for T_MODIFIED. I haven't thought so much |
1360 | about T_ADDED or T_REMOVED. */ |
1361 | notify_do ('C', finfo->file, getcaller (), NULL((void*)0), NULL((void*)0), finfo->repository); |
1362 | |
1363 | out: |
1364 | if (err != 0) |
1365 | { |
1366 | /* on failure, remove the file from ulist */ |
1367 | p = findnode (ulist, finfo->file); |
1368 | if (p) |
1369 | delnode (p); |
1370 | } |
1371 | else |
1372 | { |
1373 | /* On success, retrieve the new version number of the file and |
1374 | copy it into the log information (see logmsg.c |
1375 | (logfile_write) for more details). We should only update |
1376 | the version number for files that have been added or |
1377 | modified but not removed. Why? classify_file_internal |
1378 | will return the version number of a file even after it has |
1379 | been removed from the archive, which is not the behavior we |
1380 | want for our commitlog messages; we want the old version |
1381 | number and then "NONE." */ |
1382 | |
1383 | if (ci->status != T_REMOVED) |
1384 | { |
1385 | p = findnode (ulist, finfo->file); |
1386 | if (p) |
1387 | { |
1388 | Vers_TS *vers; |
1389 | struct logfile_info *li; |
1390 | |
1391 | (void) classify_file_internal (finfo, &vers); |
1392 | li = (struct logfile_info *) p->data; |
1393 | li->rev_new = xstrdup (vers->vn_rcs); |
1394 | freevers_ts (&vers); |
1395 | } |
1396 | } |
1397 | } |
1398 | if (SIG_inCrSect ()) |
1399 | SIG_endCrSect (); |
1400 | |
1401 | return (err); |
1402 | } |
1403 | |
1404 | /* |
1405 | * Log the commit and clean up the update list |
1406 | */ |
1407 | /* ARGSUSED */ |
1408 | static int |
1409 | commit_filesdoneproc (callerdat, err, repository, update_dir, entries) |
1410 | void *callerdat; |
1411 | int err; |
1412 | char *repository; |
1413 | char *update_dir; |
1414 | List *entries; |
1415 | { |
1416 | Node *p; |
1417 | List *ulist; |
1418 | |
1419 | p = findnode (mulist, update_dir); |
1420 | if (p == NULL((void*)0)) |
1421 | return (err); |
1422 | |
1423 | ulist = ((struct master_lists *) p->data)->ulist; |
1424 | |
1425 | got_message = 0; |
1426 | |
1427 | |
1428 | Update_Logfile (repository, saved_message, (FILE *) 0, ulist); |
1429 | |
1430 | /* Build the administrative files if necessary. */ |
1431 | { |
1432 | char *p; |
1433 | |
1434 | if (strncmp (current_parsed_root->directory, repository, |
1435 | strlen (current_parsed_root->directory)) != 0) |
1436 | error (0, 0, |
1437 | "internal error: repository (%s) doesn't begin with root (%s)", |
1438 | repository, current_parsed_root->directory); |
1439 | p = repository + strlen (current_parsed_root->directory); |
1440 | if (*p == '/') |
1441 | ++p; |
1442 | if (strcmp ("CVSROOT", p) == 0 |
1443 | /* Check for subdirectories because people may want to create |
1444 | subdirectories and list files therein in checkoutlist. */ |
1445 | || strncmp ("CVSROOT/", p, strlen ("CVSROOT/")) == 0 |
1446 | ) |
1447 | { |
1448 | /* "Database" might a little bit grandiose and/or vague, |
1449 | but "checked-out copies of administrative files, unless |
1450 | in the case of modules and you are using ndbm in which |
1451 | case modules.{pag,dir,db}" is verbose and excessively |
1452 | focused on how the database is implemented. */ |
1453 | |
1454 | /* mkmodules requires the absolute name of the CVSROOT directory. |
1455 | Remove anything after the `CVSROOT' component -- this is |
1456 | necessary when committing in a subdirectory of CVSROOT. */ |
1457 | char *admin_dir = xstrdup (repository); |
1458 | int cvsrootlen = strlen ("CVSROOT"); |
1459 | assert (admin_dir[p - repository + cvsrootlen] == '\0'((admin_dir[p - repository + cvsrootlen] == '\0' || admin_dir [p - repository + cvsrootlen] == '/') ? (void)0 : __assert2("/usr/src/gnu/usr.bin/cvs/src/commit.c" , 1460, __func__, "admin_dir[p - repository + cvsrootlen] == '\\0' || admin_dir[p - repository + cvsrootlen] == '/'" )) |
1460 | || admin_dir[p - repository + cvsrootlen] == '/')((admin_dir[p - repository + cvsrootlen] == '\0' || admin_dir [p - repository + cvsrootlen] == '/') ? (void)0 : __assert2("/usr/src/gnu/usr.bin/cvs/src/commit.c" , 1460, __func__, "admin_dir[p - repository + cvsrootlen] == '\\0' || admin_dir[p - repository + cvsrootlen] == '/'" )); |
1461 | admin_dir[p - repository + cvsrootlen] = '\0'; |
1462 | |
1463 | cvs_output (program_name, 0); |
1464 | cvs_output (" ", 1); |
1465 | cvs_output (command_name, 0); |
1466 | cvs_output (": Rebuilding administrative file database\n", 0); |
1467 | mkmodules (admin_dir); |
1468 | free (admin_dir); |
1469 | } |
1470 | } |
1471 | |
1472 | if (err == 0 && run_module_prog) |
1473 | { |
1474 | FILE *fp; |
1475 | |
1476 | if ((fp = CVS_FOPENfopen (CVSADM_CIPROG"CVS/Checkin.prog", "r")) != NULL((void*)0)) |
1477 | { |
1478 | char *line; |
1479 | int line_length; |
1480 | size_t line_chars_allocated; |
1481 | char *repos; |
1482 | |
1483 | line = NULL((void*)0); |
1484 | line_chars_allocated = 0; |
1485 | line_length = get_line (&line, &line_chars_allocated, fp); |
1486 | if (line_length > 0) |
1487 | { |
1488 | /* Remove any trailing newline. */ |
1489 | if (line[line_length - 1] == '\n') |
1490 | line[--line_length] = '\0'; |
1491 | repos = Name_Repository ((char *) NULL((void*)0), update_dir); |
1492 | run_setup (line); |
1493 | run_arg (repos); |
1494 | cvs_output (program_name, 0); |
1495 | cvs_output (" ", 1); |
1496 | cvs_output (command_name, 0); |
1497 | cvs_output (": Executing '", 0); |
1498 | run_print (stdout(&__sF[1])); |
1499 | cvs_output ("'\n", 0); |
1500 | cvs_flushout (); |
1501 | (void) run_exec (RUN_TTY(char *)0, RUN_TTY(char *)0, RUN_TTY(char *)0, RUN_NORMAL0x0000); |
1502 | free (repos); |
1503 | } |
1504 | else |
1505 | { |
1506 | if (ferror (fp)(!__isthreaded ? (((fp)->_flags & 0x0040) != 0) : (ferror )(fp))) |
1507 | error (0, errno(*__errno()), "warning: error reading %s", |
1508 | CVSADM_CIPROG"CVS/Checkin.prog"); |
1509 | } |
1510 | if (line != NULL((void*)0)) |
1511 | free (line); |
1512 | if (fclose (fp) < 0) |
1513 | error (0, errno(*__errno()), "warning: cannot close %s", CVSADM_CIPROG"CVS/Checkin.prog"); |
1514 | } |
1515 | else |
1516 | { |
1517 | if (! existence_error (errno)(((*__errno())) == 2)) |
1518 | error (0, errno(*__errno()), "warning: cannot open %s", CVSADM_CIPROG"CVS/Checkin.prog"); |
1519 | } |
1520 | } |
1521 | |
1522 | return (err); |
1523 | } |
1524 | |
1525 | /* |
1526 | * Get the log message for a dir |
1527 | */ |
1528 | /* ARGSUSED */ |
1529 | static Dtype |
1530 | commit_direntproc (callerdat, dir, repos, update_dir, entries) |
1531 | void *callerdat; |
1532 | char *dir; |
1533 | char *repos; |
1534 | char *update_dir; |
1535 | List *entries; |
1536 | { |
1537 | Node *p; |
1538 | List *ulist; |
1539 | char *real_repos; |
1540 | |
1541 | if (!isdir (dir)) |
1542 | return (R_SKIP_ALL); |
1543 | |
1544 | /* find the update list for this dir */ |
1545 | p = findnode (mulist, update_dir); |
1546 | if (p != NULL((void*)0)) |
1547 | ulist = ((struct master_lists *) p->data)->ulist; |
1548 | else |
1549 | ulist = (List *) NULL((void*)0); |
1550 | |
1551 | /* skip the files as an optimization */ |
1552 | if (ulist == NULL((void*)0) || ulist->list->next == ulist->list) |
1553 | return (R_SKIP_FILES); |
1554 | |
1555 | /* get commit message */ |
1556 | real_repos = Name_Repository (dir, update_dir); |
1557 | got_message = 1; |
1558 | if (use_editor) |
1559 | do_editor (update_dir, &saved_message, real_repos, ulist); |
1560 | do_verify (saved_message, real_repos); |
1561 | free (real_repos); |
1562 | return (R_PROCESS); |
1563 | } |
1564 | |
1565 | /* |
1566 | * Process the post-commit proc if necessary |
1567 | */ |
1568 | /* ARGSUSED */ |
1569 | static int |
1570 | commit_dirleaveproc (callerdat, dir, err, update_dir, entries) |
1571 | void *callerdat; |
1572 | char *dir; |
1573 | int err; |
1574 | char *update_dir; |
1575 | List *entries; |
1576 | { |
1577 | /* update the per-directory tag info */ |
1578 | /* FIXME? Why? The "commit examples" node of cvs.texinfo briefly |
1579 | mentions commit -r being sticky, but apparently in the context of |
1580 | this being a confusing feature! */ |
1581 | if (err == 0 && write_dirtag != NULL((void*)0)) |
1582 | { |
1583 | char *repos = Name_Repository (dir, update_dir); |
1584 | WriteTag (NULL((void*)0), write_dirtag, NULL((void*)0), write_dirnonbranch, |
1585 | update_dir, repos); |
1586 | free (repos); |
1587 | } |
1588 | |
1589 | return (err); |
1590 | } |
1591 | |
1592 | /* |
1593 | * find the maximum major rev number in an entries file |
1594 | */ |
1595 | static int |
1596 | findmaxrev (p, closure) |
1597 | Node *p; |
1598 | void *closure; |
1599 | { |
1600 | int thisrev; |
1601 | Entnode *entdata; |
1602 | |
1603 | entdata = (Entnode *) p->data; |
1604 | if (entdata->type != ENT_FILE) |
1605 | return (0); |
1606 | thisrev = atoi (entdata->version); |
1607 | if (thisrev > maxrev) |
1608 | maxrev = thisrev; |
1609 | return (0); |
1610 | } |
1611 | |
1612 | /* |
1613 | * Actually remove a file by moving it to the attic |
1614 | * XXX - if removing a ,v file that is a relative symbolic link to |
1615 | * another ,v file, we probably should add a ".." component to the |
1616 | * link to keep it relative after we move it into the attic. |
1617 | |
1618 | Return value is 0 on success, or >0 on error (in which case we have |
1619 | printed an error message). */ |
1620 | static int |
1621 | remove_file (finfo, tag, message) |
1622 | struct file_info *finfo; |
1623 | char *tag; |
1624 | char *message; |
1625 | { |
1626 | int retcode; |
1627 | |
1628 | int branch; |
1629 | int lockflag; |
1630 | char *corev; |
1631 | char *rev; |
1632 | char *prev_rev; |
1633 | char *old_path; |
1634 | |
1635 | corev = NULL((void*)0); |
1636 | rev = NULL((void*)0); |
1637 | prev_rev = NULL((void*)0); |
1638 | |
1639 | retcode = 0; |
Value stored to 'retcode' is never read | |
1640 | |
1641 | if (finfo->rcs == NULL((void*)0)) |
1642 | error (1, 0, "internal error: no parsed RCS file"); |
1643 | |
1644 | branch = 0; |
1645 | if (tag && !(branch = RCS_nodeisbranch (finfo->rcs, tag))) |
1646 | { |
1647 | /* a symbolic tag is specified; just remove the tag from the file */ |
1648 | if ((retcode = RCS_deltag (finfo->rcs, tag)) != 0) |
1649 | { |
1650 | if (!quiet) |
1651 | error (0, retcode == -1 ? errno(*__errno()) : 0, |
1652 | "failed to remove tag `%s' from `%s'", tag, |
1653 | finfo->fullname); |
1654 | return (1); |
1655 | } |
1656 | RCS_rewrite (finfo->rcs, NULL((void*)0), NULL((void*)0)); |
1657 | Scratch_Entry (finfo->entries, finfo->file); |
1658 | return (0); |
1659 | } |
1660 | |
1661 | /* we are removing the file from either the head or a branch */ |
1662 | /* commit a new, dead revision. */ |
1663 | |
1664 | /* Print message indicating that file is going to be removed. */ |
1665 | cvs_output ("Removing ", 0); |
1666 | cvs_output (finfo->fullname, 0); |
1667 | cvs_output (";\n", 0); |
1668 | |
1669 | rev = NULL((void*)0); |
1670 | lockflag = 1; |
1671 | if (branch) |
1672 | { |
1673 | char *branchname; |
1674 | |
1675 | rev = RCS_whatbranch (finfo->rcs, tag); |
1676 | if (rev == NULL((void*)0)) |
1677 | { |
1678 | error (0, 0, "cannot find branch \"%s\".", tag); |
1679 | return (1); |
1680 | } |
1681 | |
1682 | branchname = RCS_getbranch (finfo->rcs, rev, 1); |
1683 | if (branchname == NULL((void*)0)) |
1684 | { |
1685 | /* no revision exists on this branch. use the previous |
1686 | revision but do not lock. */ |
1687 | corev = RCS_gettag (finfo->rcs, tag, 1, (int *) NULL((void*)0)); |
1688 | prev_rev = xstrdup(rev); |
1689 | lockflag = 0; |
1690 | } else |
1691 | { |
1692 | corev = xstrdup (rev); |
1693 | prev_rev = xstrdup(branchname); |
1694 | free (branchname); |
1695 | } |
1696 | |
1697 | } else /* Not a branch */ |
1698 | { |
1699 | /* Get current head revision of file. */ |
1700 | prev_rev = RCS_head (finfo->rcs); |
1701 | } |
1702 | |
1703 | /* if removing without a tag or a branch, then make sure the default |
1704 | branch is the trunk. */ |
1705 | if (!tag && !branch) |
1706 | { |
1707 | if (RCS_setbranch (finfo->rcs, NULL((void*)0)) != 0) |
1708 | { |
1709 | error (0, 0, "cannot change branch to default for %s", |
1710 | finfo->fullname); |
1711 | return (1); |
1712 | } |
1713 | RCS_rewrite (finfo->rcs, NULL((void*)0), NULL((void*)0)); |
1714 | } |
1715 | |
1716 | /* check something out. Generally this is the head. If we have a |
1717 | particular rev, then name it. */ |
1718 | retcode = RCS_checkout (finfo->rcs, finfo->file, rev ? corev : NULL((void*)0), |
1719 | (char *) NULL((void*)0), (char *) NULL((void*)0), RUN_TTY(char *)0, |
1720 | (RCSCHECKOUTPROC) NULL((void*)0), (void *) NULL((void*)0)); |
1721 | if (retcode != 0) |
1722 | { |
1723 | error (0, 0, |
1724 | "failed to check out `%s'", finfo->fullname); |
1725 | return (1); |
1726 | } |
1727 | |
1728 | /* Except when we are creating a branch, lock the revision so that |
1729 | we can check in the new revision. */ |
1730 | if (lockflag) |
1731 | { |
1732 | if (RCS_lock (finfo->rcs, rev ? corev : NULL((void*)0), 1) == 0) |
1733 | RCS_rewrite (finfo->rcs, NULL((void*)0), NULL((void*)0)); |
1734 | } |
1735 | |
1736 | if (corev != NULL((void*)0)) |
1737 | free (corev); |
1738 | |
1739 | retcode = RCS_checkin (finfo->rcs, finfo->file, message, rev, |
1740 | RCS_FLAGS_DEAD2 | RCS_FLAGS_QUIET4); |
1741 | if (retcode != 0) |
1742 | { |
1743 | if (!quiet) |
1744 | error (0, retcode == -1 ? errno(*__errno()) : 0, |
1745 | "failed to commit dead revision for `%s'", finfo->fullname); |
1746 | return (1); |
1747 | } |
1748 | /* At this point, the file has been committed as removed. We should |
1749 | probably tell the history file about it */ |
1750 | history_write ('R', NULL((void*)0), finfo->rcs->head, finfo->file, finfo->repository); |
1751 | |
1752 | if (rev != NULL((void*)0)) |
1753 | free (rev); |
1754 | |
1755 | old_path = xstrdup (finfo->rcs->path); |
1756 | if (!branch) |
1757 | RCS_setattic (finfo->rcs, 1); |
1758 | |
1759 | /* Print message that file was removed. */ |
1760 | cvs_output (old_path, 0); |
1761 | cvs_output (" <-- ", 0); |
1762 | cvs_output (finfo->file, 0); |
1763 | cvs_output ("\nnew revision: delete; previous revision: ", 0); |
1764 | cvs_output (prev_rev, 0); |
1765 | cvs_output ("\ndone\n", 0); |
1766 | free(prev_rev); |
1767 | |
1768 | free (old_path); |
1769 | |
1770 | Scratch_Entry (finfo->entries, finfo->file); |
1771 | return (0); |
1772 | } |
1773 | |
1774 | /* |
1775 | * Do the actual checkin for added files |
1776 | */ |
1777 | static int |
1778 | finaladd (finfo, rev, tag, options) |
1779 | struct file_info *finfo; |
1780 | char *rev; |
1781 | char *tag; |
1782 | char *options; |
1783 | { |
1784 | int ret; |
1785 | char *rcs; |
1786 | |
1787 | rcs = locate_rcs (finfo->file, finfo->repository); |
1788 | ret = Checkin ('A', finfo, rcs, rev, tag, options, saved_message); |
1789 | if (ret == 0) |
1790 | { |
1791 | char *tmp = xmalloc (strlen (finfo->file) + sizeof (CVSADM"CVS") |
1792 | + sizeof (CVSEXT_LOG",t") + 10); |
1793 | (void) sprintf (tmp, "%s/%s%s", CVSADM"CVS", finfo->file, CVSEXT_LOG",t"); |
1794 | if (unlink_file (tmp) < 0 |
1795 | && !existence_error (errno)(((*__errno())) == 2)) |
1796 | error (0, errno(*__errno()), "cannot remove %s", tmp); |
1797 | free (tmp); |
1798 | } |
1799 | else |
1800 | fixaddfile (finfo->file, finfo->repository); |
1801 | |
1802 | (void) time (&last_register_time); |
1803 | free (rcs); |
1804 | |
1805 | return (ret); |
1806 | } |
1807 | |
1808 | /* |
1809 | * Unlock an rcs file |
1810 | */ |
1811 | static void |
1812 | unlockrcs (rcs) |
1813 | RCSNode *rcs; |
1814 | { |
1815 | int retcode; |
1816 | |
1817 | if ((retcode = RCS_unlock (rcs, NULL((void*)0), 0)) != 0) |
1818 | error (retcode == -1 ? 1 : 0, retcode == -1 ? errno(*__errno()) : 0, |
1819 | "could not unlock %s", rcs->path); |
1820 | else |
1821 | RCS_rewrite (rcs, NULL((void*)0), NULL((void*)0)); |
1822 | } |
1823 | |
1824 | /* |
1825 | * remove a partially added file. if we can parse it, leave it alone. |
1826 | */ |
1827 | static void |
1828 | fixaddfile (file, repository) |
1829 | char *file; |
1830 | char *repository; |
1831 | { |
1832 | RCSNode *rcsfile; |
1833 | char *rcs; |
1834 | int save_really_quiet; |
1835 | |
1836 | rcs = locate_rcs (file, repository); |
1837 | save_really_quiet = really_quiet; |
1838 | really_quiet = 1; |
1839 | if ((rcsfile = RCS_parsercsfile (rcs)) == NULL((void*)0)) |
1840 | { |
1841 | if (unlink_file (rcs) < 0) |
1842 | error (0, errno(*__errno()), "cannot remove %s", rcs); |
1843 | } |
1844 | else |
1845 | freercsnode (&rcsfile); |
1846 | really_quiet = save_really_quiet; |
1847 | free (rcs); |
1848 | } |
1849 | |
1850 | /* |
1851 | * put the branch back on an rcs file |
1852 | */ |
1853 | static void |
1854 | fixbranch (rcs, branch) |
1855 | RCSNode *rcs; |
1856 | char *branch; |
1857 | { |
1858 | int retcode; |
1859 | |
1860 | if (branch != NULL((void*)0)) |
1861 | { |
1862 | if ((retcode = RCS_setbranch (rcs, branch)) != 0) |
1863 | error (retcode == -1 ? 1 : 0, retcode == -1 ? errno(*__errno()) : 0, |
1864 | "cannot restore branch to %s for %s", branch, rcs->path); |
1865 | RCS_rewrite (rcs, NULL((void*)0), NULL((void*)0)); |
1866 | } |
1867 | } |
1868 | |
1869 | /* |
1870 | * do the initial part of a file add for the named file. if adding |
1871 | * with a tag, put the file in the Attic and point the symbolic tag |
1872 | * at the committed revision. |
1873 | */ |
1874 | |
1875 | static int |
1876 | checkaddfile (file, repository, tag, options, rcsnode) |
1877 | char *file; |
1878 | char *repository; |
1879 | char *tag; |
1880 | char *options; |
1881 | RCSNode **rcsnode; |
1882 | { |
1883 | char *rcs; |
1884 | char *fname; |
1885 | mode_t omask; |
1886 | int retcode = 0; |
1887 | int newfile = 0; |
1888 | RCSNode *rcsfile = NULL((void*)0); |
1889 | int retval; |
1890 | int adding_on_branch; |
1891 | |
1892 | /* Callers expect to be able to use either "" or NULL to mean the |
1893 | default keyword expansion. */ |
1894 | if (options != NULL((void*)0) && options[0] == '\0') |
1895 | options = NULL((void*)0); |
1896 | if (options != NULL((void*)0)) |
1897 | assert (options[0] == '-' && options[1] == 'k')((options[0] == '-' && options[1] == 'k') ? (void)0 : __assert2("/usr/src/gnu/usr.bin/cvs/src/commit.c", 1897, __func__ , "options[0] == '-' && options[1] == 'k'")); |
1898 | |
1899 | /* If numeric, it is on the trunk; check_fileproc enforced |
1900 | this. */ |
1901 | adding_on_branch = tag != NULL((void*)0) && !isdigit ((unsigned char) tag[0]); |
1902 | |
1903 | if (adding_on_branch) |
1904 | { |
1905 | rcs = xmalloc (strlen (repository) + strlen (file) |
1906 | + sizeof (RCSEXT",v") + sizeof (CVSATTIC"Attic") + 10); |
1907 | (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT",v"); |
1908 | if (! isreadable (rcs)) |
1909 | { |
1910 | (void) sprintf(rcs, "%s/%s", repository, CVSATTIC"Attic"); |
1911 | omask = umask (cvsumask); |
1912 | if (CVS_MKDIRmkdir (rcs, 0777) != 0 && errno(*__errno()) != EEXIST17) |
1913 | error (1, errno(*__errno()), "cannot make directory `%s'", rcs);; |
1914 | (void) umask (omask); |
1915 | (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC"Attic", file, |
1916 | RCSEXT",v"); |
1917 | } |
1918 | } |
1919 | else |
1920 | rcs = locate_rcs (file, repository); |
1921 | |
1922 | if (isreadable (rcs)) |
1923 | { |
1924 | /* file has existed in the past. Prepare to resurrect. */ |
1925 | char *rev; |
1926 | char *oldexpand; |
1927 | |
1928 | if ((rcsfile = *rcsnode) == NULL((void*)0)) |
1929 | { |
1930 | error (0, 0, "could not find parsed rcsfile %s", file); |
1931 | retval = 1; |
1932 | goto out; |
1933 | } |
1934 | |
1935 | oldexpand = RCS_getexpand (rcsfile); |
1936 | if ((oldexpand != NULL((void*)0) |
1937 | && options != NULL((void*)0) |
1938 | && strcmp (options + 2, oldexpand) != 0) |
1939 | || (oldexpand == NULL((void*)0) && options != NULL((void*)0))) |
1940 | { |
1941 | /* We tell the user about this, because it means that the |
1942 | old revisions will no longer retrieve the way that they |
1943 | used to. */ |
1944 | error (0, 0, "changing keyword expansion mode to %s", options); |
1945 | RCS_setexpand (rcsfile, options + 2); |
1946 | } |
1947 | |
1948 | if (!adding_on_branch) |
1949 | { |
1950 | /* We are adding on the trunk, so move the file out of the |
1951 | Attic. */ |
1952 | if (!(rcsfile->flags & INATTIC0x2)) |
1953 | { |
1954 | error (0, 0, "warning: expected %s to be in Attic", |
1955 | rcsfile->path); |
1956 | } |
1957 | |
1958 | sprintf (rcs, "%s/%s%s", repository, file, RCSEXT",v"); |
1959 | |
1960 | /* Begin a critical section around the code that spans the |
1961 | first commit on the trunk of a file that's already been |
1962 | committed on a branch. */ |
1963 | SIG_beginCrSect (); |
1964 | |
1965 | if (RCS_setattic (rcsfile, 0)) |
1966 | { |
1967 | retval = 1; |
1968 | goto out; |
1969 | } |
1970 | } |
1971 | |
1972 | rev = RCS_getversion (rcsfile, tag, NULL((void*)0), 1, (int *) NULL((void*)0)); |
1973 | /* and lock it */ |
1974 | if (lock_RCS (file, rcsfile, rev, repository)) |
1975 | { |
1976 | error (0, 0, "cannot lock `%s'.", rcs); |
1977 | if (rev != NULL((void*)0)) |
1978 | free (rev); |
1979 | retval = 1; |
1980 | goto out; |
1981 | } |
1982 | |
1983 | if (rev != NULL((void*)0)) |
1984 | free (rev); |
1985 | } |
1986 | else |
1987 | { |
1988 | /* this is the first time we have ever seen this file; create |
1989 | an rcs file. */ |
1990 | |
1991 | char *desc; |
1992 | size_t descalloc; |
1993 | size_t desclen; |
1994 | |
1995 | char *opt; |
1996 | |
1997 | desc = NULL((void*)0); |
1998 | descalloc = 0; |
1999 | desclen = 0; |
2000 | fname = xmalloc (strlen (file) + sizeof (CVSADM"CVS") |
2001 | + sizeof (CVSEXT_LOG",t") + 10); |
2002 | (void) sprintf (fname, "%s/%s%s", CVSADM"CVS", file, CVSEXT_LOG",t"); |
2003 | /* If the file does not exist, no big deal. In particular, the |
2004 | server does not (yet at least) create CVSEXT_LOG files. */ |
2005 | if (isfile (fname)) |
2006 | /* FIXME: Should be including update_dir in the appropriate |
2007 | place here. */ |
2008 | get_file (fname, fname, "r", &desc, &descalloc, &desclen); |
2009 | free (fname); |
2010 | |
2011 | /* From reading the RCS 5.7 source, "rcs -i" adds a newline to the |
2012 | end of the log message if the message is nonempty. |
2013 | Do it. RCS also deletes certain whitespace, in cleanlogmsg, |
2014 | which we don't try to do here. */ |
2015 | if (desclen > 0) |
2016 | { |
2017 | expand_string (&desc, &descalloc, desclen + 1); |
2018 | desc[desclen++] = '\012'; |
2019 | } |
2020 | |
2021 | /* Set RCS keyword expansion options. */ |
2022 | if (options != NULL((void*)0)) |
2023 | opt = options + 2; |
2024 | else |
2025 | opt = NULL((void*)0); |
2026 | |
2027 | /* This message is an artifact of the time when this |
2028 | was implemented via "rcs -i". It should be revised at |
2029 | some point (does the "initial revision" in the message from |
2030 | RCS_checkin indicate that this is a new file? Or does the |
2031 | "RCS file" message serve some function?). */ |
2032 | cvs_output ("RCS file: ", 0); |
2033 | cvs_output (rcs, 0); |
2034 | cvs_output ("\ndone\n", 0); |
2035 | |
2036 | if (add_rcs_file (NULL((void*)0), rcs, file, NULL((void*)0), opt, |
2037 | NULL((void*)0), NULL((void*)0), 0, NULL((void*)0), |
2038 | desc, desclen, NULL((void*)0)) != 0) |
2039 | { |
2040 | retval = 1; |
2041 | goto out; |
2042 | } |
2043 | rcsfile = RCS_parsercsfile (rcs); |
2044 | newfile = 1; |
2045 | if (desc != NULL((void*)0)) |
2046 | free (desc); |
2047 | if (rcsnode != NULL((void*)0)) |
2048 | { |
2049 | assert (*rcsnode == NULL)((*rcsnode == ((void*)0)) ? (void)0 : __assert2("/usr/src/gnu/usr.bin/cvs/src/commit.c" , 2049, __func__, "*rcsnode == NULL")); |
2050 | *rcsnode = rcsfile; |
2051 | } |
2052 | } |
2053 | |
2054 | /* when adding a file for the first time, and using a tag, we need |
2055 | to create a dead revision on the trunk. */ |
2056 | if (adding_on_branch) |
2057 | { |
2058 | if (newfile) |
2059 | { |
2060 | char *tmp; |
2061 | FILE *fp; |
2062 | |
2063 | /* move the new file out of the way. */ |
2064 | fname = xmalloc (strlen (file) + sizeof (CVSADM"CVS") |
2065 | + sizeof (CVSPREFIX",,") + 10); |
2066 | (void) sprintf (fname, "%s/%s%s", CVSADM"CVS", CVSPREFIX",,", file); |
2067 | rename_file (file, fname); |
2068 | |
2069 | /* Create empty FILE. Can't use copy_file with a DEVNULL |
2070 | argument -- copy_file now ignores device files. */ |
2071 | fp = fopen (file, "w"); |
2072 | if (fp == NULL((void*)0)) |
2073 | error (1, errno(*__errno()), "cannot open %s for writing", file); |
2074 | if (fclose (fp) < 0) |
2075 | error (0, errno(*__errno()), "cannot close %s", file); |
2076 | |
2077 | tmp = xmalloc (strlen (file) + strlen (tag) + 80); |
2078 | /* commit a dead revision. */ |
2079 | (void) sprintf (tmp, "file %s was initially added on branch %s.", |
2080 | file, tag); |
2081 | retcode = RCS_checkin (rcsfile, NULL((void*)0), tmp, NULL((void*)0), |
2082 | RCS_FLAGS_DEAD2 | RCS_FLAGS_QUIET4); |
2083 | free (tmp); |
2084 | if (retcode != 0) |
2085 | { |
2086 | error (retcode == -1 ? 1 : 0, retcode == -1 ? errno(*__errno()) : 0, |
2087 | "could not create initial dead revision %s", rcs); |
2088 | retval = 1; |
2089 | goto out; |
2090 | } |
2091 | |
2092 | /* put the new file back where it was */ |
2093 | rename_file (fname, file); |
2094 | free (fname); |
2095 | |
2096 | /* double-check that the file was written correctly */ |
2097 | freercsnode (&rcsfile); |
2098 | rcsfile = RCS_parse (file, repository); |
2099 | if (rcsfile == NULL((void*)0)) |
2100 | { |
2101 | error (0, 0, "could not read %s", rcs); |
2102 | retval = 1; |
2103 | goto out; |
2104 | } |
2105 | if (rcsnode != NULL((void*)0)) |
2106 | *rcsnode = rcsfile; |
2107 | |
2108 | /* and lock it once again. */ |
2109 | if (lock_RCS (file, rcsfile, NULL((void*)0), repository)) |
2110 | { |
2111 | error (0, 0, "cannot lock `%s'.", rcs); |
2112 | retval = 1; |
2113 | goto out; |
2114 | } |
2115 | } |
2116 | |
2117 | /* when adding with a tag, we need to stub a branch, if it |
2118 | doesn't already exist. */ |
2119 | |
2120 | if (rcsfile == NULL((void*)0)) |
2121 | { |
2122 | if (rcsnode != NULL((void*)0) && *rcsnode != NULL((void*)0)) |
2123 | rcsfile = *rcsnode; |
2124 | else |
2125 | { |
2126 | rcsfile = RCS_parse (file, repository); |
2127 | if (rcsfile == NULL((void*)0)) |
2128 | { |
2129 | error (0, 0, "could not read %s", rcs); |
2130 | retval = 1; |
2131 | goto out; |
2132 | } |
2133 | } |
2134 | } |
2135 | |
2136 | if (!RCS_nodeisbranch (rcsfile, tag)) |
2137 | { |
2138 | /* branch does not exist. Stub it. */ |
2139 | char *head; |
2140 | char *magicrev; |
2141 | |
2142 | head = RCS_getversion (rcsfile, NULL((void*)0), NULL((void*)0), 0, (int *) NULL((void*)0)); |
2143 | magicrev = RCS_magicrev (rcsfile, head); |
2144 | |
2145 | retcode = RCS_settag (rcsfile, tag, magicrev); |
2146 | RCS_rewrite (rcsfile, NULL((void*)0), NULL((void*)0)); |
2147 | |
2148 | free (head); |
2149 | free (magicrev); |
2150 | |
2151 | if (retcode != 0) |
2152 | { |
2153 | error (retcode == -1 ? 1 : 0, retcode == -1 ? errno(*__errno()) : 0, |
2154 | "could not stub branch %s for %s", tag, rcs); |
2155 | retval = 1; |
2156 | goto out; |
2157 | } |
2158 | } |
2159 | else |
2160 | { |
2161 | /* lock the branch. (stubbed branches need not be locked.) */ |
2162 | if (lock_RCS (file, rcsfile, NULL((void*)0), repository)) |
2163 | { |
2164 | error (0, 0, "cannot lock `%s'.", rcs); |
2165 | retval = 1; |
2166 | goto out; |
2167 | } |
2168 | } |
2169 | |
2170 | if (rcsnode && *rcsnode != rcsfile) |
2171 | { |
2172 | freercsnode(rcsnode); |
2173 | *rcsnode = rcsfile; |
2174 | } |
2175 | } |
2176 | |
2177 | fileattr_newfile (file); |
2178 | |
2179 | /* At this point, we used to set the file mode of the RCS file |
2180 | based on the mode of the file in the working directory. If we |
2181 | are creating the RCS file for the first time, add_rcs_file does |
2182 | this already. If we are re-adding the file, then perhaps it is |
2183 | consistent to preserve the old file mode, just as we preserve |
2184 | the old keyword expansion mode. |
2185 | |
2186 | If we decide that we should change the modes, then we can't do |
2187 | it here anyhow. At this point, the RCS file may be owned by |
2188 | somebody else, so a chmod will fail. We need to instead do the |
2189 | chmod after rewriting it. |
2190 | |
2191 | FIXME: In general, I think the file mode (and the keyword |
2192 | expansion mode) should be associated with a particular revision |
2193 | of the file, so that it is possible to have different revisions |
2194 | of a file have different modes. */ |
2195 | |
2196 | retval = 0; |
2197 | |
2198 | out: |
2199 | if (retval != 0 && SIG_inCrSect ()) |
2200 | SIG_endCrSect (); |
2201 | free (rcs); |
2202 | return retval; |
2203 | } |
2204 | |
2205 | /* |
2206 | * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it |
2207 | * couldn't. If the RCS file currently has a branch as the head, we must |
2208 | * move the head back to the trunk before locking the file, and be sure to |
2209 | * put the branch back as the head if there are any errors. |
2210 | */ |
2211 | static int |
2212 | lock_RCS (user, rcs, rev, repository) |
2213 | char *user; |
2214 | RCSNode *rcs; |
2215 | char *rev; |
2216 | char *repository; |
2217 | { |
2218 | char *branch = NULL((void*)0); |
2219 | int err = 0; |
2220 | |
2221 | /* |
2222 | * For a specified, numeric revision of the form "1" or "1.1", (or when |
2223 | * no revision is specified ""), definitely move the branch to the trunk |
2224 | * before locking the RCS file. |
2225 | * |
2226 | * The assumption is that if there is more than one revision on the trunk, |
2227 | * the head points to the trunk, not a branch... and as such, it's not |
2228 | * necessary to move the head in this case. |
2229 | */ |
2230 | if (rev == NULL((void*)0) |
2231 | || (rev && isdigit ((unsigned char) *rev) && numdots (rev) < 2)) |
2232 | { |
2233 | branch = xstrdup (rcs->branch); |
2234 | if (branch != NULL((void*)0)) |
2235 | { |
2236 | if (RCS_setbranch (rcs, NULL((void*)0)) != 0) |
2237 | { |
2238 | error (0, 0, "cannot change branch to default for %s", |
2239 | rcs->path); |
2240 | if (branch) |
2241 | free (branch); |
2242 | return (1); |
2243 | } |
2244 | } |
2245 | err = RCS_lock(rcs, NULL((void*)0), 1); |
2246 | } |
2247 | else |
2248 | { |
2249 | (void) RCS_lock(rcs, rev, 1); |
2250 | } |
2251 | |
2252 | /* We used to call RCS_rewrite here, and that might seem |
2253 | appropriate in order to write out the locked revision |
2254 | information. However, such a call would actually serve no |
2255 | purpose. CVS locks will prevent any interference from other |
2256 | CVS processes. The comment above rcs_internal_lockfile |
2257 | explains that it is already unsafe to use RCS and CVS |
2258 | simultaneously. It follows that writing out the locked |
2259 | revision information here would add no additional security. |
2260 | |
2261 | If we ever do care about it, the proper fix is to create the |
2262 | RCS lock file before calling this function, and maintain it |
2263 | until the checkin is complete. |
2264 | |
2265 | The call to RCS_lock is still required at present, since in |
2266 | some cases RCS_checkin will determine which revision to check |
2267 | in by looking for a lock. FIXME: This is rather roundabout, |
2268 | and a more straightforward approach would probably be easier to |
2269 | understand. */ |
2270 | |
2271 | if (err == 0) |
2272 | { |
2273 | if (sbranch != NULL((void*)0)) |
2274 | free (sbranch); |
2275 | sbranch = branch; |
2276 | return (0); |
2277 | } |
2278 | |
2279 | /* try to restore the branch if we can on error */ |
2280 | if (branch != NULL((void*)0)) |
2281 | fixbranch (rcs, branch); |
2282 | |
2283 | if (branch) |
2284 | free (branch); |
2285 | return (1); |
2286 | } |
2287 | |
2288 | /* |
2289 | * free an UPDATE node's data |
2290 | */ |
2291 | void |
2292 | update_delproc (p) |
2293 | Node *p; |
2294 | { |
2295 | struct logfile_info *li; |
2296 | |
2297 | li = (struct logfile_info *) p->data; |
2298 | if (li->tag) |
2299 | free (li->tag); |
2300 | if (li->rev_old) |
2301 | free (li->rev_old); |
2302 | if (li->rev_new) |
2303 | free (li->rev_new); |
2304 | free (li); |
2305 | } |
2306 | |
2307 | /* |
2308 | * Free the commit_info structure in p. |
2309 | */ |
2310 | static void |
2311 | ci_delproc (p) |
2312 | Node *p; |
2313 | { |
2314 | struct commit_info *ci; |
2315 | |
2316 | ci = (struct commit_info *) p->data; |
2317 | if (ci->rev) |
2318 | free (ci->rev); |
2319 | if (ci->tag) |
2320 | free (ci->tag); |
2321 | if (ci->options) |
2322 | free (ci->options); |
2323 | free (ci); |
2324 | } |
2325 | |
2326 | /* |
2327 | * Free the commit_info structure in p. |
2328 | */ |
2329 | static void |
2330 | masterlist_delproc (p) |
2331 | Node *p; |
2332 | { |
2333 | struct master_lists *ml; |
2334 | |
2335 | ml = (struct master_lists *) p->data; |
2336 | dellist (&ml->ulist); |
2337 | dellist (&ml->cilist); |
2338 | free (ml); |
2339 | } |
2340 | |
2341 | /* Find an RCS file in the repository. Most parts of CVS will want to |
2342 | rely instead on RCS_parse which performs a similar operation and is |
2343 | called by recurse.c which then puts the result in useful places |
2344 | like the rcs field of struct file_info. |
2345 | |
2346 | REPOSITORY is the repository (including the directory) and FILE is |
2347 | the filename within that directory (without RCSEXT). Returns a |
2348 | newly-malloc'd array containing the absolute pathname of the RCS |
2349 | file that was found. */ |
2350 | static char * |
2351 | locate_rcs (file, repository) |
2352 | char *file; |
2353 | char *repository; |
2354 | { |
2355 | char *rcs; |
2356 | |
2357 | rcs = xmalloc (strlen (repository) + strlen (file) + sizeof (RCSEXT",v") + 10); |
2358 | (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT",v"); |
2359 | if (!isreadable (rcs)) |
2360 | { |
2361 | (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC"Attic", file, RCSEXT",v"); |
2362 | if (!isreadable (rcs)) |
2363 | (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT",v"); |
2364 | } |
2365 | return rcs; |
2366 | } |