1 /* $OpenBSD: tag.c,v 1.88 2021/01/27 07:18:17 deraadt Exp $ */
2 /*
3 * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <errno.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22
23 #include "cvs.h"
24 #include "remote.h"
25
26 #define T_CHECK_UPTODATE 0x01
27 #define T_DELETE 0x02
28 #define T_FORCE_MOVE 0x04
29 #define T_BRANCH 0x08
30
31 void cvs_tag_check_files(struct cvs_file *);
32 void cvs_tag_local(struct cvs_file *);
33
34 static int tag_del(struct cvs_file *);
35 static int tag_add(struct cvs_file *);
36
37 struct file_info_list tag_files_info;
38
39 static int runflags = 0;
40 static int tag_errors = 0;
41 static char *tag = NULL;
42 static char *tag_date = NULL;
43 static char *tag_name = NULL;
44 static char *tag_oldname = NULL;
45
46 struct cvs_cmd cvs_cmd_rtag = {
47 CVS_OP_RTAG, CVS_LOCK_REPO, "rtag",
48 { "rt", "rfreeze" },
49 "Add a symbolic tag to a module",
50 "[-bcdFflR] [-D date | -r rev] symbolic_tag module ...",
51 "bcD:dFflRr:",
52 NULL,
53 cvs_tag
54 };
55
56 struct cvs_cmd cvs_cmd_tag = {
57 CVS_OP_TAG, CVS_USE_WDIR | CVS_LOCK_REPO, "tag",
58 { "ta", "freeze" },
59 "Add a symbolic tag to checked out version of files",
60 "[-bcdFflR] [-D date | -r rev] symbolic_tag [file ...]",
61 "bcD:dFflRr:",
62 NULL,
63 cvs_tag
64 };
65
66 int
cvs_tag(int argc,char ** argv)67 cvs_tag(int argc, char **argv)
68 {
69 int ch, flags, i;
70 char repo[PATH_MAX];
71 char *arg = ".";
72 struct cvs_recursion cr;
73 struct trigger_list *line_list;
74
75 flags = CR_RECURSE_DIRS;
76
77 while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_TAG ?
78 cvs_cmd_tag.cmd_opts : cvs_cmd_rtag.cmd_opts)) != -1) {
79 switch (ch) {
80 case 'b':
81 runflags |= T_BRANCH;
82 break;
83 case 'c':
84 runflags |= T_CHECK_UPTODATE;
85 break;
86 case 'D':
87 tag_date = optarg;
88 break;
89 case 'd':
90 runflags |= T_DELETE;
91 break;
92 case 'F':
93 runflags |= T_FORCE_MOVE;
94 break;
95 case 'l':
96 flags &= ~CR_RECURSE_DIRS;
97 break;
98 case 'R':
99 flags |= CR_RECURSE_DIRS;
100 break;
101 case 'r':
102 tag_oldname = optarg;
103 break;
104 default:
105 fatal("%s", cvs_cmdop == CVS_OP_TAG ?
106 cvs_cmd_tag.cmd_synopsis :
107 cvs_cmd_rtag.cmd_synopsis);
108 }
109 }
110
111 argc -= optind;
112 argv += optind;
113
114 if (cvs_cmdop == CVS_OP_RTAG) {
115 flags |= CR_REPO;
116
117 if (argc < 2)
118 fatal("%s", cvs_cmd_rtag.cmd_synopsis);
119
120 for (i = 1; i < argc; i++) {
121 if (argv[i][0] == '/') {
122 fatal("Absolute path name is invalid: %s",
123 argv[i]);
124 }
125 }
126 } else if (cvs_cmdop == CVS_OP_TAG && argc == 0) {
127 fatal("%s", cvs_cmd_tag.cmd_synopsis);
128 }
129
130 tag_name = argv[0];
131 argc--;
132 argv++;
133
134 if (!rcs_sym_check(tag_name)) {
135 fatal("tag `%s' must not contain the characters `%s'",
136 tag_name, RCS_SYM_INVALCHAR);
137 }
138
139 if (tag_oldname != NULL) {
140 if (runflags & T_DELETE)
141 tag_oldname = NULL;
142 else
143 tag = tag_oldname;
144 }
145
146 if (tag_date != NULL) {
147 if (runflags & T_DELETE)
148 tag_date = NULL;
149 else
150 tag = tag_date;
151 }
152
153 if (tag_oldname != NULL && tag_date != NULL)
154 fatal("-r and -D options are mutually exclusive");
155
156 cr.enterdir = NULL;
157 cr.leavedir = NULL;
158
159 if (cvsroot_is_remote()) {
160 cvs_client_connect_to_server();
161 cr.fileproc = cvs_client_sendfile;
162
163 if (argc > 0)
164 cvs_file_run(argc, argv, &cr);
165 else
166 cvs_file_run(1, &arg, &cr);
167
168 if (runflags & T_BRANCH)
169 cvs_client_send_request("Argument -b");
170
171 if (runflags & T_CHECK_UPTODATE)
172 cvs_client_send_request("Argument -c");
173
174 if (runflags & T_DELETE)
175 cvs_client_send_request("Argument -d");
176
177 if (runflags & T_FORCE_MOVE)
178 cvs_client_send_request("Argument -F");
179
180 if (!(flags & CR_RECURSE_DIRS))
181 cvs_client_send_request("Argument -l");
182
183 if (tag_date != NULL)
184 cvs_client_send_request("Argument -D%s", tag_date);
185
186 if (tag_oldname != NULL)
187 cvs_client_send_request("Argument -r%s", tag_oldname);
188
189 cvs_client_send_request("Argument %s", tag_name);
190 cvs_client_send_files(argv, argc);
191 cvs_client_senddir(".");
192 cvs_client_send_request((cvs_cmdop == CVS_OP_RTAG) ?
193 "rtag" : "tag");
194 cvs_client_get_responses();
195
196 return (0);
197 }
198
199 if (cvs_cmdop == CVS_OP_RTAG && chdir(current_cvsroot->cr_dir) == -1)
200 fatal("cvs_tag: %s", strerror(errno));
201
202 TAILQ_INIT(&tag_files_info);
203 cvs_get_repository_name(".", repo, PATH_MAX);
204 line_list = cvs_trigger_getlines(CVS_PATH_TAGINFO, repo);
205
206 cr.flags = flags;
207 cr.fileproc = cvs_tag_check_files;
208
209 if (argc > 0)
210 cvs_file_run(argc, argv, &cr);
211 else
212 cvs_file_run(1, &arg, &cr);
213
214 if (tag_errors)
215 fatal("correct the above errors first!");
216
217 if (line_list != NULL) {
218 if (cvs_trigger_handle(CVS_TRIGGER_TAGINFO, repo, NULL,
219 line_list, &tag_files_info))
220 fatal("Pre-tag check failed");
221 cvs_trigger_freelist(line_list);
222 }
223
224 cr.fileproc = cvs_tag_local;
225
226 if (argc > 0)
227 cvs_file_run(argc, argv, &cr);
228 else
229 cvs_file_run(1, &arg, &cr);
230
231 if (line_list != NULL)
232 cvs_trigger_freeinfo(&tag_files_info);
233
234 return (0);
235 }
236
237 void
cvs_tag_check_files(struct cvs_file * cf)238 cvs_tag_check_files(struct cvs_file *cf)
239 {
240 RCSNUM *srev = NULL, *rev = NULL;
241 char rbuf[CVS_REV_BUFSZ];
242 struct file_info *fi;
243
244 cvs_log(LP_TRACE, "cvs_tag_check_files(%s)", cf->file_path);
245
246 cvs_file_classify(cf, tag);
247
248 if (cf->file_type == CVS_DIR || cf->file_status == FILE_UNKNOWN)
249 return;
250
251 if (runflags & T_CHECK_UPTODATE) {
252 if (cf->file_status != FILE_UPTODATE &&
253 cf->file_status != FILE_CHECKOUT &&
254 cf->file_status != FILE_PATCH) {
255 tag_errors++;
256 cvs_log(LP_NOTICE,
257 "%s is locally modified", cf->file_path);
258 return;
259 }
260 }
261
262 switch (cf->file_status) {
263 case FILE_ADDED:
264 case FILE_REMOVED:
265 return;
266 default:
267 break;
268 }
269
270 if (cvs_cmdop == CVS_OP_TAG) {
271 if (cf->file_ent == NULL)
272 return;
273 srev = cf->file_ent->ce_rev;
274 } else
275 srev = cf->file_rcsrev;
276
277 rcsnum_tostr(srev, rbuf, sizeof(rbuf));
278 fi = xcalloc(1, sizeof(*fi));
279 fi->nrevstr = xstrdup(rbuf);
280 fi->file_path = xstrdup(cf->file_path);
281
282 if (tag_oldname != NULL)
283 fi->tag_old = xstrdup(tag_oldname);
284 else if (tag_date != NULL)
285 fi->tag_old = xstrdup(tag_date);
286
287 if ((rev = rcs_sym_getrev(cf->file_rcs, tag_name)) != NULL) {
288 if (!rcsnum_differ(srev, rev))
289 goto bad;
290 rcsnum_tostr(rev, rbuf, sizeof(rbuf));
291 fi->crevstr = xstrdup(rbuf);
292 free(rev);
293 } else if (runflags & T_DELETE)
294 goto bad;
295
296 fi->tag_new = xstrdup(tag_name);
297
298 if (runflags & T_BRANCH)
299 fi->tag_type = 'T';
300 else if (runflags & T_DELETE)
301 fi->tag_type = '?';
302 else
303 fi->tag_type = 'N';
304
305 if (runflags & T_FORCE_MOVE)
306 fi->tag_op = "mov";
307 else if (runflags & T_DELETE)
308 fi->tag_op = "del";
309 else
310 fi->tag_op = "add";
311
312 TAILQ_INSERT_TAIL(&tag_files_info, fi, flist);
313 return;
314
315 bad:
316 free(fi->file_path);
317 free(fi->crevstr);
318 free(fi->nrevstr);
319 free(fi->tag_new);
320 free(fi->tag_old);
321 free(rev);
322 free(fi);
323 }
324
325 void
cvs_tag_local(struct cvs_file * cf)326 cvs_tag_local(struct cvs_file *cf)
327 {
328 cvs_log(LP_TRACE, "cvs_tag_local(%s)", cf->file_path);
329
330 cvs_file_classify(cf, tag);
331
332 if (cf->file_type == CVS_DIR) {
333 if (verbosity > 1) {
334 cvs_log(LP_NOTICE, "%s %s",
335 (runflags & T_DELETE) ? "Untagging" : "Tagging",
336 cf->file_path);
337 }
338 return;
339 }
340
341 if (runflags & T_DELETE) {
342 if (tag_del(cf) == 0) {
343 if (verbosity > 0)
344 cvs_printf("D %s\n", cf->file_path);
345 }
346 return;
347 }
348
349 switch (cf->file_status) {
350 case FILE_ADDED:
351 if (verbosity > 1) {
352 cvs_log(LP_NOTICE,
353 "couldn't tag added but un-committed file `%s'",
354 cf->file_path);
355 }
356 break;
357 case FILE_REMOVED:
358 if (verbosity > 1) {
359 cvs_log(LP_NOTICE,
360 "skipping removed but un-committed file `%s'",
361 cf->file_path);
362 }
363 break;
364 case FILE_CHECKOUT:
365 case FILE_MODIFIED:
366 case FILE_PATCH:
367 case FILE_UPTODATE:
368 if (tag_add(cf) == 0) {
369 if (verbosity > 0)
370 cvs_printf("T %s\n", cf->file_path);
371 cvs_history_add(CVS_HISTORY_TAG, cf, tag_name);
372 }
373 break;
374 default:
375 break;
376 }
377 }
378
379 static int
tag_del(struct cvs_file * cf)380 tag_del(struct cvs_file *cf)
381 {
382 if (cf->file_rcs == NULL)
383 return (-1);
384
385 if (cvs_noexec == 1)
386 return (0);
387
388 return (rcs_sym_remove(cf->file_rcs, tag_name));
389 }
390
391 static int
tag_add(struct cvs_file * cf)392 tag_add(struct cvs_file *cf)
393 {
394 int ret;
395 char revbuf[CVS_REV_BUFSZ], trevbuf[CVS_REV_BUFSZ];
396 RCSNUM *srev, *trev;
397 struct rcs_sym *sym;
398
399 if (cf->file_rcs == NULL) {
400 if (verbosity > 1)
401 cvs_log(LP_NOTICE, "cannot find revision "
402 "control file for `%s'", cf->file_name);
403 return (-1);
404 }
405
406 if (cvs_cmdop == CVS_OP_TAG) {
407 if (cf->file_ent == NULL)
408 return (-1);
409 srev = cf->file_ent->ce_rev;
410 } else
411 srev = cf->file_rcsrev;
412
413 if (cvs_noexec == 1)
414 return (0);
415
416 (void)rcsnum_tostr(srev, revbuf, sizeof(revbuf));
417
418 trev = rcs_sym_getrev(cf->file_rcs, tag_name);
419 if (trev != NULL) {
420 if (rcsnum_cmp(srev, trev, 0) == 0) {
421 free(trev);
422 return (-1);
423 }
424 (void)rcsnum_tostr(trev, trevbuf, sizeof(trevbuf));
425 free(trev);
426
427 if (!(runflags & T_FORCE_MOVE)) {
428 cvs_printf("W %s : %s ", cf->file_path, tag_name);
429 cvs_printf("already exists on version %s", trevbuf);
430 cvs_printf(" : NOT MOVING tag to version %s\n", revbuf);
431
432 return (-1);
433 } else {
434 sym = rcs_sym_get(cf->file_rcs, tag_name);
435 rcsnum_cpy(srev, sym->rs_num, 0);
436 cf->file_rcs->rf_flags &= ~RCS_SYNCED;
437
438 return (0);
439 }
440 }
441
442 if (runflags & T_BRANCH) {
443 if ((trev = rcs_branch_new(cf->file_rcs, srev)) == NULL)
444 fatal("Cannot create a new branch");
445 } else {
446 trev = rcsnum_alloc();
447 rcsnum_cpy(srev, trev, 0);
448 }
449
450 if ((ret = rcs_sym_add(cf->file_rcs, tag_name, trev)) != 0) {
451 if (ret != 1) {
452 cvs_log(LP_NOTICE,
453 "failed to set tag %s to revision %s in %s",
454 tag_name, revbuf, cf->file_rcs->rf_path);
455 }
456 free(trev);
457 return (-1);
458 }
459
460 free(trev);
461 return (0);
462 }
463