1 |
|
|
/* $OpenBSD: ci.c,v 1.224 2016/07/04 01:39:12 millert Exp $ */ |
2 |
|
|
/* |
3 |
|
|
* Copyright (c) 2005, 2006 Niall O'Higgins <niallo@openbsd.org> |
4 |
|
|
* All rights reserved. |
5 |
|
|
* |
6 |
|
|
* Redistribution and use in source and binary forms, with or without |
7 |
|
|
* modification, are permitted provided that the following conditions |
8 |
|
|
* are met: |
9 |
|
|
* |
10 |
|
|
* 1. Redistributions of source code must retain the above copyright |
11 |
|
|
* notice, this list of conditions and the following disclaimer. |
12 |
|
|
* 2. The name of the author may not be used to endorse or promote products |
13 |
|
|
* derived from this software without specific prior written permission. |
14 |
|
|
* |
15 |
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, |
16 |
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
17 |
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
18 |
|
|
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 |
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 |
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
21 |
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
22 |
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
23 |
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
24 |
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 |
|
|
*/ |
26 |
|
|
|
27 |
|
|
#include <sys/stat.h> |
28 |
|
|
|
29 |
|
|
#include <ctype.h> |
30 |
|
|
#include <err.h> |
31 |
|
|
#include <fcntl.h> |
32 |
|
|
#include <stdio.h> |
33 |
|
|
#include <stdlib.h> |
34 |
|
|
#include <string.h> |
35 |
|
|
#include <unistd.h> |
36 |
|
|
|
37 |
|
|
#include "rcsprog.h" |
38 |
|
|
#include "diff.h" |
39 |
|
|
|
40 |
|
|
#define CI_OPTSTRING "d::f::I::i::j::k::l::M::m::N:n:qr::s:Tt::u::Vw:x::z::" |
41 |
|
|
#define DATE_NOW -1 |
42 |
|
|
#define DATE_MTIME -2 |
43 |
|
|
|
44 |
|
|
#define KW_ID "Id" |
45 |
|
|
#define KW_OPENBSD "OpenBSD" |
46 |
|
|
#define KW_AUTHOR "Author" |
47 |
|
|
#define KW_DATE "Date" |
48 |
|
|
#define KW_STATE "State" |
49 |
|
|
#define KW_REVISION "Revision" |
50 |
|
|
|
51 |
|
|
#define KW_TYPE_ID 1 |
52 |
|
|
#define KW_TYPE_AUTHOR 2 |
53 |
|
|
#define KW_TYPE_DATE 3 |
54 |
|
|
#define KW_TYPE_STATE 4 |
55 |
|
|
#define KW_TYPE_REVISION 5 |
56 |
|
|
|
57 |
|
|
/* Maximum number of tokens in a keyword. */ |
58 |
|
|
#define KW_NUMTOKS_MAX 10 |
59 |
|
|
|
60 |
|
|
#define RCSNUM_ZERO_ENDING(x) (x->rn_id[x->rn_len - 1] == 0) |
61 |
|
|
|
62 |
|
|
extern struct rcs_kw rcs_expkw[]; |
63 |
|
|
|
64 |
|
|
static int workfile_fd; |
65 |
|
|
|
66 |
|
|
struct checkin_params { |
67 |
|
|
int flags, openflags; |
68 |
|
|
mode_t fmode; |
69 |
|
|
time_t date; |
70 |
|
|
RCSFILE *file; |
71 |
|
|
RCSNUM *frev, *newrev; |
72 |
|
|
const char *description, *symbol; |
73 |
|
|
char fpath[PATH_MAX], *rcs_msg, *username, *filename; |
74 |
|
|
char *author, *state; |
75 |
|
|
BUF *deltatext; |
76 |
|
|
}; |
77 |
|
|
|
78 |
|
|
static int checkin_attach_symbol(struct checkin_params *); |
79 |
|
|
static int checkin_checklock(struct checkin_params *); |
80 |
|
|
static BUF *checkin_diff_file(struct checkin_params *); |
81 |
|
|
static char *checkin_getlogmsg(RCSNUM *, RCSNUM *, int); |
82 |
|
|
static int checkin_init(struct checkin_params *); |
83 |
|
|
static int checkin_keywordscan(BUF *, RCSNUM **, time_t *, char **, |
84 |
|
|
char **); |
85 |
|
|
static int checkin_keywordtype(char *); |
86 |
|
|
static void checkin_mtimedate(struct checkin_params *); |
87 |
|
|
static void checkin_parsekeyword(char *, RCSNUM **, time_t *, char **, |
88 |
|
|
char **); |
89 |
|
|
static int checkin_update(struct checkin_params *); |
90 |
|
|
static int checkin_revert(struct checkin_params *); |
91 |
|
|
|
92 |
|
|
__dead void |
93 |
|
|
checkin_usage(void) |
94 |
|
|
{ |
95 |
|
|
fprintf(stderr, |
96 |
|
|
"usage: ci [-qV] [-d[date]] [-f[rev]] [-I[rev]] [-i[rev]]\n" |
97 |
|
|
" [-j[rev]] [-k[rev]] [-l[rev]] [-M[rev]] [-mmsg]\n" |
98 |
|
|
" [-Nsymbol] [-nsymbol] [-r[rev]] [-sstate] [-t[str]]\n" |
99 |
|
|
" [-u[rev]] [-wusername] [-xsuffixes] [-ztz] file ...\n"); |
100 |
|
|
|
101 |
|
|
exit(1); |
102 |
|
|
} |
103 |
|
|
|
104 |
|
|
/* |
105 |
|
|
* checkin_main() |
106 |
|
|
* |
107 |
|
|
* Handler for the `ci' program. |
108 |
|
|
* Returns 0 on success, or >0 on error. |
109 |
|
|
*/ |
110 |
|
|
int |
111 |
|
|
checkin_main(int argc, char **argv) |
112 |
|
|
{ |
113 |
|
|
int fd; |
114 |
|
|
int i, ch, status; |
115 |
|
|
int base_flags, base_openflags; |
116 |
|
|
char *rev_str; |
117 |
|
|
struct checkin_params pb; |
118 |
|
|
|
119 |
|
|
pb.date = DATE_NOW; |
120 |
|
|
pb.file = NULL; |
121 |
|
|
pb.rcs_msg = pb.username = pb.author = pb.state = NULL; |
122 |
|
|
pb.description = pb.symbol = NULL; |
123 |
|
|
pb.deltatext = NULL; |
124 |
|
|
pb.newrev = NULL; |
125 |
|
|
pb.fmode = S_IRUSR|S_IRGRP|S_IROTH; |
126 |
|
|
status = 0; |
127 |
|
|
base_flags = INTERACTIVE; |
128 |
|
|
base_openflags = RCS_RDWR|RCS_CREATE|RCS_PARSE_FULLY; |
129 |
|
|
rev_str = NULL; |
130 |
|
|
|
131 |
|
|
while ((ch = rcs_getopt(argc, argv, CI_OPTSTRING)) != -1) { |
132 |
|
|
switch (ch) { |
133 |
|
|
case 'd': |
134 |
|
|
if (rcs_optarg == NULL) |
135 |
|
|
pb.date = DATE_MTIME; |
136 |
|
|
else if ((pb.date = date_parse(rcs_optarg)) == -1) |
137 |
|
|
errx(1, "invalid date"); |
138 |
|
|
break; |
139 |
|
|
case 'f': |
140 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
141 |
|
|
base_flags |= FORCE; |
142 |
|
|
break; |
143 |
|
|
case 'I': |
144 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
145 |
|
|
base_flags |= INTERACTIVE; |
146 |
|
|
break; |
147 |
|
|
case 'i': |
148 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
149 |
|
|
base_openflags |= RCS_CREATE; |
150 |
|
|
base_flags |= CI_INIT; |
151 |
|
|
break; |
152 |
|
|
case 'j': |
153 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
154 |
|
|
base_openflags &= ~RCS_CREATE; |
155 |
|
|
base_flags &= ~CI_INIT; |
156 |
|
|
break; |
157 |
|
|
case 'k': |
158 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
159 |
|
|
base_flags |= CI_KEYWORDSCAN; |
160 |
|
|
break; |
161 |
|
|
case 'l': |
162 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
163 |
|
|
base_flags |= CO_LOCK; |
164 |
|
|
break; |
165 |
|
|
case 'M': |
166 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
167 |
|
|
base_flags |= CO_REVDATE; |
168 |
|
|
break; |
169 |
|
|
case 'm': |
170 |
|
|
pb.rcs_msg = rcs_optarg; |
171 |
|
|
if (pb.rcs_msg == NULL) |
172 |
|
|
errx(1, "missing message for -m option"); |
173 |
|
|
base_flags &= ~INTERACTIVE; |
174 |
|
|
break; |
175 |
|
|
case 'N': |
176 |
|
|
base_flags |= CI_SYMFORCE; |
177 |
|
|
/* FALLTHROUGH */ |
178 |
|
|
case 'n': |
179 |
|
|
pb.symbol = rcs_optarg; |
180 |
|
|
if (rcs_sym_check(pb.symbol) != 1) |
181 |
|
|
errx(1, "invalid symbol `%s'", pb.symbol); |
182 |
|
|
break; |
183 |
|
|
case 'q': |
184 |
|
|
base_flags |= QUIET; |
185 |
|
|
break; |
186 |
|
|
case 'r': |
187 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
188 |
|
|
base_flags |= CI_DEFAULT; |
189 |
|
|
break; |
190 |
|
|
case 's': |
191 |
|
|
pb.state = rcs_optarg; |
192 |
|
|
if (rcs_state_check(pb.state) < 0) |
193 |
|
|
errx(1, "invalid state `%s'", pb.state); |
194 |
|
|
break; |
195 |
|
|
case 'T': |
196 |
|
|
base_flags |= PRESERVETIME; |
197 |
|
|
break; |
198 |
|
|
case 't': |
199 |
|
|
/* Ignore bare -t; kept for backwards compatibility. */ |
200 |
|
|
if (rcs_optarg == NULL) |
201 |
|
|
break; |
202 |
|
|
pb.description = rcs_optarg; |
203 |
|
|
base_flags |= DESCRIPTION; |
204 |
|
|
break; |
205 |
|
|
case 'u': |
206 |
|
|
rcs_setrevstr(&rev_str, rcs_optarg); |
207 |
|
|
base_flags |= CO_UNLOCK; |
208 |
|
|
break; |
209 |
|
|
case 'V': |
210 |
|
|
printf("%s\n", rcs_version); |
211 |
|
|
exit(0); |
212 |
|
|
case 'w': |
213 |
|
|
free(pb.author); |
214 |
|
|
pb.author = xstrdup(rcs_optarg); |
215 |
|
|
break; |
216 |
|
|
case 'x': |
217 |
|
|
/* Use blank extension if none given. */ |
218 |
|
|
rcs_suffixes = rcs_optarg ? rcs_optarg : ""; |
219 |
|
|
break; |
220 |
|
|
case 'z': |
221 |
|
|
timezone_flag = rcs_optarg; |
222 |
|
|
break; |
223 |
|
|
default: |
224 |
|
|
(usage)(); |
225 |
|
|
} |
226 |
|
|
} |
227 |
|
|
|
228 |
|
|
argc -= rcs_optind; |
229 |
|
|
argv += rcs_optind; |
230 |
|
|
|
231 |
|
|
if (argc == 0) { |
232 |
|
|
warnx("no input file"); |
233 |
|
|
(usage)(); |
234 |
|
|
} |
235 |
|
|
|
236 |
|
|
if ((pb.username = getlogin()) == NULL) |
237 |
|
|
err(1, "getlogin"); |
238 |
|
|
|
239 |
|
|
for (i = 0; i < argc; i++) { |
240 |
|
|
/* |
241 |
|
|
* The pb.flags and pb.openflags may change during |
242 |
|
|
* loop iteration so restore them for each file. |
243 |
|
|
*/ |
244 |
|
|
pb.flags = base_flags; |
245 |
|
|
pb.openflags = base_openflags; |
246 |
|
|
|
247 |
|
|
pb.filename = argv[i]; |
248 |
|
|
rcs_strip_suffix(pb.filename); |
249 |
|
|
|
250 |
|
|
if ((workfile_fd = open(pb.filename, O_RDONLY)) == -1) |
251 |
|
|
err(1, "%s", pb.filename); |
252 |
|
|
|
253 |
|
|
/* Find RCS file path. */ |
254 |
|
|
fd = rcs_choosefile(pb.filename, pb.fpath, sizeof(pb.fpath)); |
255 |
|
|
|
256 |
|
|
if (fd < 0) { |
257 |
|
|
if (pb.openflags & RCS_CREATE) |
258 |
|
|
pb.flags |= NEWFILE; |
259 |
|
|
else { |
260 |
|
|
/* XXX - Check if errno == ENOENT. */ |
261 |
|
|
warnx("No existing RCS file"); |
262 |
|
|
status = 1; |
263 |
|
|
(void)close(workfile_fd); |
264 |
|
|
continue; |
265 |
|
|
} |
266 |
|
|
} else { |
267 |
|
|
if (pb.flags & CI_INIT) { |
268 |
|
|
warnx("%s already exists", pb.fpath); |
269 |
|
|
status = 1; |
270 |
|
|
(void)close(fd); |
271 |
|
|
(void)close(workfile_fd); |
272 |
|
|
continue; |
273 |
|
|
} |
274 |
|
|
pb.openflags &= ~RCS_CREATE; |
275 |
|
|
} |
276 |
|
|
|
277 |
|
|
pb.file = rcs_open(pb.fpath, fd, pb.openflags, pb.fmode); |
278 |
|
|
if (pb.file == NULL) |
279 |
|
|
errx(1, "failed to open rcsfile `%s'", pb.fpath); |
280 |
|
|
|
281 |
|
|
if ((pb.flags & DESCRIPTION) && |
282 |
|
|
rcs_set_description(pb.file, pb.description, pb.flags) == -1) |
283 |
|
|
err(1, "%s", pb.filename); |
284 |
|
|
|
285 |
|
|
if (!(pb.flags & QUIET)) |
286 |
|
|
(void)fprintf(stderr, |
287 |
|
|
"%s <-- %s\n", pb.fpath, pb.filename); |
288 |
|
|
|
289 |
|
|
if (rev_str != NULL) |
290 |
|
|
if ((pb.newrev = rcs_getrevnum(rev_str, pb.file)) == |
291 |
|
|
NULL) |
292 |
|
|
errx(1, "invalid revision: %s", rev_str); |
293 |
|
|
|
294 |
|
|
if (!(pb.flags & NEWFILE)) |
295 |
|
|
pb.flags |= CI_SKIPDESC; |
296 |
|
|
|
297 |
|
|
/* XXX - support for committing to a file without revisions */ |
298 |
|
|
if (pb.file->rf_ndelta == 0) { |
299 |
|
|
pb.flags |= NEWFILE; |
300 |
|
|
pb.file->rf_flags |= RCS_CREATE; |
301 |
|
|
} |
302 |
|
|
|
303 |
|
|
/* |
304 |
|
|
* workfile_fd will be closed in checkin_init or |
305 |
|
|
* checkin_update |
306 |
|
|
*/ |
307 |
|
|
if (pb.flags & NEWFILE) { |
308 |
|
|
if (checkin_init(&pb) == -1) |
309 |
|
|
status = 1; |
310 |
|
|
} else { |
311 |
|
|
if (checkin_update(&pb) == -1) |
312 |
|
|
status = 1; |
313 |
|
|
} |
314 |
|
|
|
315 |
|
|
rcs_close(pb.file); |
316 |
|
|
if (rev_str != NULL) |
317 |
|
|
rcsnum_free(pb.newrev); |
318 |
|
|
pb.newrev = NULL; |
319 |
|
|
} |
320 |
|
|
|
321 |
|
|
if (!(base_flags & QUIET) && status == 0) |
322 |
|
|
(void)fprintf(stderr, "done\n"); |
323 |
|
|
|
324 |
|
|
return (status); |
325 |
|
|
} |
326 |
|
|
|
327 |
|
|
/* |
328 |
|
|
* checkin_diff_file() |
329 |
|
|
* |
330 |
|
|
* Generate the diff between the working file and a revision. |
331 |
|
|
* Returns pointer to a BUF on success, NULL on failure. |
332 |
|
|
*/ |
333 |
|
|
static BUF * |
334 |
|
|
checkin_diff_file(struct checkin_params *pb) |
335 |
|
|
{ |
336 |
|
|
char *path1, *path2; |
337 |
|
|
BUF *b1, *b2, *b3; |
338 |
|
|
|
339 |
|
|
b1 = b2 = b3 = NULL; |
340 |
|
|
path1 = path2 = NULL; |
341 |
|
|
|
342 |
|
|
if ((b1 = buf_load(pb->filename)) == NULL) { |
343 |
|
|
warnx("failed to load file: `%s'", pb->filename); |
344 |
|
|
goto out; |
345 |
|
|
} |
346 |
|
|
|
347 |
|
|
if ((b2 = rcs_getrev(pb->file, pb->frev)) == NULL) { |
348 |
|
|
warnx("failed to load revision"); |
349 |
|
|
goto out; |
350 |
|
|
} |
351 |
|
|
b2 = rcs_kwexp_buf(b2, pb->file, pb->frev); |
352 |
|
|
b3 = buf_alloc(128); |
353 |
|
|
|
354 |
|
|
(void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); |
355 |
|
|
buf_write_stmp(b1, path1); |
356 |
|
|
|
357 |
|
|
buf_free(b1); |
358 |
|
|
b1 = NULL; |
359 |
|
|
|
360 |
|
|
(void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); |
361 |
|
|
buf_write_stmp(b2, path2); |
362 |
|
|
|
363 |
|
|
buf_free(b2); |
364 |
|
|
b2 = NULL; |
365 |
|
|
|
366 |
|
|
diff_format = D_RCSDIFF; |
367 |
|
|
if (diffreg(path1, path2, b3, D_FORCEASCII) == D_ERROR) |
368 |
|
|
goto out; |
369 |
|
|
|
370 |
|
|
return (b3); |
371 |
|
|
out: |
372 |
|
|
buf_free(b1); |
373 |
|
|
buf_free(b2); |
374 |
|
|
buf_free(b3); |
375 |
|
|
free(path1); |
376 |
|
|
free(path2); |
377 |
|
|
|
378 |
|
|
return (NULL); |
379 |
|
|
} |
380 |
|
|
|
381 |
|
|
/* |
382 |
|
|
* checkin_getlogmsg() |
383 |
|
|
* |
384 |
|
|
* Get log message from user interactively. |
385 |
|
|
* Returns pointer to a char array on success, NULL on failure. |
386 |
|
|
*/ |
387 |
|
|
static char * |
388 |
|
|
checkin_getlogmsg(RCSNUM *rev, RCSNUM *rev2, int flags) |
389 |
|
|
{ |
390 |
|
|
char *rcs_msg, nrev[RCS_REV_BUFSZ], prev[RCS_REV_BUFSZ]; |
391 |
|
|
const char *prompt = |
392 |
|
|
"enter log message, terminated with a single '.' or end of file:\n"; |
393 |
|
|
RCSNUM *tmprev; |
394 |
|
|
|
395 |
|
|
rcs_msg = NULL; |
396 |
|
|
tmprev = rcsnum_alloc(); |
397 |
|
|
rcsnum_cpy(rev, tmprev, 16); |
398 |
|
|
rcsnum_tostr(tmprev, prev, sizeof(prev)); |
399 |
|
|
if (rev2 == NULL) |
400 |
|
|
rcsnum_tostr(rcsnum_inc(tmprev), nrev, sizeof(nrev)); |
401 |
|
|
else |
402 |
|
|
rcsnum_tostr(rev2, nrev, sizeof(nrev)); |
403 |
|
|
rcsnum_free(tmprev); |
404 |
|
|
|
405 |
|
|
if (!(flags & QUIET)) |
406 |
|
|
(void)fprintf(stderr, "new revision: %s; " |
407 |
|
|
"previous revision: %s\n", nrev, prev); |
408 |
|
|
|
409 |
|
|
rcs_msg = rcs_prompt(prompt, flags); |
410 |
|
|
|
411 |
|
|
return (rcs_msg); |
412 |
|
|
} |
413 |
|
|
|
414 |
|
|
/* |
415 |
|
|
* checkin_update() |
416 |
|
|
* |
417 |
|
|
* Do a checkin to an existing RCS file. |
418 |
|
|
* |
419 |
|
|
* On success, return 0. On error return -1. |
420 |
|
|
*/ |
421 |
|
|
static int |
422 |
|
|
checkin_update(struct checkin_params *pb) |
423 |
|
|
{ |
424 |
|
|
char numb1[RCS_REV_BUFSZ], numb2[RCS_REV_BUFSZ]; |
425 |
|
|
struct stat st; |
426 |
|
|
BUF *bp; |
427 |
|
|
|
428 |
|
|
/* |
429 |
|
|
* XXX this is wrong, we need to get the revision the user |
430 |
|
|
* has the lock for. So we can decide if we want to create a |
431 |
|
|
* branch or not. (if it's not current HEAD we need to branch). |
432 |
|
|
*/ |
433 |
|
|
pb->frev = pb->file->rf_head; |
434 |
|
|
|
435 |
|
|
/* Load file contents */ |
436 |
|
|
if ((bp = buf_load(pb->filename)) == NULL) |
437 |
|
|
return (-1); |
438 |
|
|
|
439 |
|
|
/* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */ |
440 |
|
|
if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev)) |
441 |
|
|
pb->newrev = rcsnum_inc(pb->newrev); |
442 |
|
|
|
443 |
|
|
if (checkin_checklock(pb) < 0) |
444 |
|
|
return (-1); |
445 |
|
|
|
446 |
|
|
/* If revision passed on command line is less than HEAD, bail. |
447 |
|
|
* XXX only applies to ci -r1.2 foo for example if HEAD is > 1.2 and |
448 |
|
|
* there is no lock set for the user. |
449 |
|
|
*/ |
450 |
|
|
if (pb->newrev != NULL && |
451 |
|
|
rcsnum_cmp(pb->newrev, pb->frev, 0) != -1) { |
452 |
|
|
warnx("%s: revision %s too low; must be higher than %s", |
453 |
|
|
pb->file->rf_path, |
454 |
|
|
rcsnum_tostr(pb->newrev, numb1, sizeof(numb1)), |
455 |
|
|
rcsnum_tostr(pb->frev, numb2, sizeof(numb2))); |
456 |
|
|
return (-1); |
457 |
|
|
} |
458 |
|
|
|
459 |
|
|
/* |
460 |
|
|
* Set the date of the revision to be the last modification |
461 |
|
|
* time of the working file if -d has no argument. |
462 |
|
|
*/ |
463 |
|
|
if (pb->date == DATE_MTIME) |
464 |
|
|
checkin_mtimedate(pb); |
465 |
|
|
|
466 |
|
|
/* Date from argv/mtime must be more recent than HEAD */ |
467 |
|
|
if (pb->date != DATE_NOW) { |
468 |
|
|
time_t head_date = rcs_rev_getdate(pb->file, pb->frev); |
469 |
|
|
if (pb->date <= head_date) { |
470 |
|
|
static const char fmt[] = "%Y/%m/%d %H:%M:%S"; |
471 |
|
|
char dbuf1[256], dbuf2[256]; |
472 |
|
|
struct tm *t, *t_head; |
473 |
|
|
|
474 |
|
|
t = gmtime(&pb->date); |
475 |
|
|
strftime(dbuf1, sizeof(dbuf1), fmt, t); |
476 |
|
|
t_head = gmtime(&head_date); |
477 |
|
|
strftime(dbuf2, sizeof(dbuf2), fmt, t_head); |
478 |
|
|
|
479 |
|
|
errx(1, "%s: Date %s precedes %s in revision %s.", |
480 |
|
|
pb->file->rf_path, dbuf1, dbuf2, |
481 |
|
|
rcsnum_tostr(pb->frev, numb2, sizeof(numb2))); |
482 |
|
|
} |
483 |
|
|
} |
484 |
|
|
|
485 |
|
|
/* Get RCS patch */ |
486 |
|
|
if ((pb->deltatext = checkin_diff_file(pb)) == NULL) { |
487 |
|
|
warnx("failed to get diff"); |
488 |
|
|
return (-1); |
489 |
|
|
} |
490 |
|
|
|
491 |
|
|
/* |
492 |
|
|
* If -f is not specified and there are no differences, tell |
493 |
|
|
* the user and revert to latest version. |
494 |
|
|
*/ |
495 |
|
|
if (!(pb->flags & FORCE) && (buf_len(pb->deltatext) < 1)) { |
496 |
|
|
if (checkin_revert(pb) == -1) |
497 |
|
|
return (-1); |
498 |
|
|
else |
499 |
|
|
return (0); |
500 |
|
|
} |
501 |
|
|
|
502 |
|
|
/* If no log message specified, get it interactively. */ |
503 |
|
|
if (pb->flags & INTERACTIVE) { |
504 |
|
|
if (pb->rcs_msg != NULL) { |
505 |
|
|
fprintf(stderr, |
506 |
|
|
"reuse log message of previous file? [yn](y): "); |
507 |
|
|
if (rcs_yesno('y') != 'y') { |
508 |
|
|
free(pb->rcs_msg); |
509 |
|
|
pb->rcs_msg = NULL; |
510 |
|
|
} |
511 |
|
|
} |
512 |
|
|
if (pb->rcs_msg == NULL) |
513 |
|
|
pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev, |
514 |
|
|
pb->flags); |
515 |
|
|
} |
516 |
|
|
|
517 |
|
|
if ((rcs_lock_remove(pb->file, pb->username, pb->frev) < 0) && |
518 |
|
|
(rcs_lock_getmode(pb->file) != RCS_LOCK_LOOSE)) { |
519 |
|
|
if (rcs_errno != RCS_ERR_NOENT) |
520 |
|
|
warnx("failed to remove lock"); |
521 |
|
|
else if (!(pb->flags & CO_LOCK)) |
522 |
|
|
warnx("previous revision was not locked; " |
523 |
|
|
"ignoring -l option"); |
524 |
|
|
} |
525 |
|
|
|
526 |
|
|
/* Current head revision gets the RCS patch as rd_text */ |
527 |
|
|
if (rcs_deltatext_set(pb->file, pb->frev, pb->deltatext) == -1) |
528 |
|
|
errx(1, "failed to set new rd_text for head rev"); |
529 |
|
|
|
530 |
|
|
/* Now add our new revision */ |
531 |
|
|
if (rcs_rev_add(pb->file, |
532 |
|
|
(pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev), |
533 |
|
|
pb->rcs_msg, pb->date, pb->author) != 0) { |
534 |
|
|
warnx("failed to add new revision"); |
535 |
|
|
return (-1); |
536 |
|
|
} |
537 |
|
|
|
538 |
|
|
/* |
539 |
|
|
* If we are checking in to a non-default (ie user-specified) |
540 |
|
|
* revision, set head to this revision. |
541 |
|
|
*/ |
542 |
|
|
if (pb->newrev != NULL) { |
543 |
|
|
if (rcs_head_set(pb->file, pb->newrev) < 0) |
544 |
|
|
errx(1, "rcs_head_set failed"); |
545 |
|
|
} else |
546 |
|
|
pb->newrev = pb->file->rf_head; |
547 |
|
|
|
548 |
|
|
/* New head revision has to contain entire file; */ |
549 |
|
|
if (rcs_deltatext_set(pb->file, pb->frev, bp) == -1) |
550 |
|
|
errx(1, "failed to set new head revision"); |
551 |
|
|
|
552 |
|
|
/* Attach a symbolic name to this revision if specified. */ |
553 |
|
|
if (pb->symbol != NULL && |
554 |
|
|
(checkin_attach_symbol(pb) < 0)) |
555 |
|
|
return (-1); |
556 |
|
|
|
557 |
|
|
/* Set the state of this revision if specified. */ |
558 |
|
|
if (pb->state != NULL) |
559 |
|
|
(void)rcs_state_set(pb->file, pb->newrev, pb->state); |
560 |
|
|
|
561 |
|
|
/* Maintain RCSFILE permissions */ |
562 |
|
|
if (fstat(workfile_fd, &st) == -1) |
563 |
|
|
err(1, "%s", pb->filename); |
564 |
|
|
|
565 |
|
|
/* Strip all the write bits */ |
566 |
|
|
pb->file->rf_mode = st.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH); |
567 |
|
|
|
568 |
|
|
(void)close(workfile_fd); |
569 |
|
|
(void)unlink(pb->filename); |
570 |
|
|
|
571 |
|
|
/* Write out RCSFILE before calling checkout_rev() */ |
572 |
|
|
rcs_write(pb->file); |
573 |
|
|
|
574 |
|
|
/* Do checkout if -u or -l are specified. */ |
575 |
|
|
if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) && |
576 |
|
|
!(pb->flags & CI_DEFAULT)) |
577 |
|
|
checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags, |
578 |
|
|
pb->username, pb->author, NULL, NULL); |
579 |
|
|
|
580 |
|
|
if ((pb->flags & INTERACTIVE) && (pb->rcs_msg[0] == '\0')) { |
581 |
|
|
free(pb->rcs_msg); /* free empty log message */ |
582 |
|
|
pb->rcs_msg = NULL; |
583 |
|
|
} |
584 |
|
|
|
585 |
|
|
return (0); |
586 |
|
|
} |
587 |
|
|
|
588 |
|
|
/* |
589 |
|
|
* checkin_init() |
590 |
|
|
* |
591 |
|
|
* Does an initial check in, just enough to create the new ,v file |
592 |
|
|
* On success, return 0. On error return -1. |
593 |
|
|
*/ |
594 |
|
|
static int |
595 |
|
|
checkin_init(struct checkin_params *pb) |
596 |
|
|
{ |
597 |
|
|
BUF *bp; |
598 |
|
|
char numb[RCS_REV_BUFSZ]; |
599 |
|
|
int fetchlog = 0; |
600 |
|
|
struct stat st; |
601 |
|
|
|
602 |
|
|
/* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */ |
603 |
|
|
if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev)) { |
604 |
|
|
pb->frev = rcsnum_alloc(); |
605 |
|
|
rcsnum_cpy(pb->newrev, pb->frev, 0); |
606 |
|
|
pb->newrev = rcsnum_inc(pb->newrev); |
607 |
|
|
fetchlog = 1; |
608 |
|
|
} |
609 |
|
|
|
610 |
|
|
/* Load file contents */ |
611 |
|
|
if ((bp = buf_load(pb->filename)) == NULL) |
612 |
|
|
return (-1); |
613 |
|
|
|
614 |
|
|
/* Get default values from working copy if -k specified */ |
615 |
|
|
if (pb->flags & CI_KEYWORDSCAN) |
616 |
|
|
checkin_keywordscan(bp, &pb->newrev, |
617 |
|
|
&pb->date, &pb->state, &pb->author); |
618 |
|
|
|
619 |
|
|
if (pb->flags & CI_SKIPDESC) |
620 |
|
|
goto skipdesc; |
621 |
|
|
|
622 |
|
|
/* Get description from user */ |
623 |
|
|
if (pb->description == NULL && |
624 |
|
|
rcs_set_description(pb->file, NULL, pb->flags) == -1) { |
625 |
|
|
warn("%s", pb->filename); |
626 |
|
|
return (-1); |
627 |
|
|
} |
628 |
|
|
|
629 |
|
|
skipdesc: |
630 |
|
|
|
631 |
|
|
/* |
632 |
|
|
* If the user had specified a zero-ending revision number e.g. 4.0 |
633 |
|
|
* emulate odd GNU behaviour and fetch log message. |
634 |
|
|
*/ |
635 |
|
|
if (fetchlog == 1) { |
636 |
|
|
pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev, |
637 |
|
|
pb->flags); |
638 |
|
|
rcsnum_free(pb->frev); |
639 |
|
|
} |
640 |
|
|
|
641 |
|
|
/* |
642 |
|
|
* Set the date of the revision to be the last modification |
643 |
|
|
* time of the working file if -d has no argument. |
644 |
|
|
*/ |
645 |
|
|
if (pb->date == DATE_MTIME) |
646 |
|
|
checkin_mtimedate(pb); |
647 |
|
|
|
648 |
|
|
/* Now add our new revision */ |
649 |
|
|
if (rcs_rev_add(pb->file, |
650 |
|
|
(pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev), |
651 |
|
|
(pb->rcs_msg == NULL ? "Initial revision" : pb->rcs_msg), |
652 |
|
|
pb->date, pb->author) != 0) { |
653 |
|
|
warnx("failed to add new revision"); |
654 |
|
|
return (-1); |
655 |
|
|
} |
656 |
|
|
|
657 |
|
|
/* |
658 |
|
|
* If we are checking in to a non-default (ie user-specified) |
659 |
|
|
* revision, set head to this revision. |
660 |
|
|
*/ |
661 |
|
|
if (pb->newrev != NULL) { |
662 |
|
|
if (rcs_head_set(pb->file, pb->newrev) < 0) |
663 |
|
|
errx(1, "rcs_head_set failed"); |
664 |
|
|
} else |
665 |
|
|
pb->newrev = pb->file->rf_head; |
666 |
|
|
|
667 |
|
|
/* New head revision has to contain entire file; */ |
668 |
|
|
if (rcs_deltatext_set(pb->file, pb->file->rf_head, bp) == -1) { |
669 |
|
|
warnx("failed to set new head revision"); |
670 |
|
|
return (-1); |
671 |
|
|
} |
672 |
|
|
|
673 |
|
|
/* Attach a symbolic name to this revision if specified. */ |
674 |
|
|
if (pb->symbol != NULL && checkin_attach_symbol(pb) < 0) |
675 |
|
|
return (-1); |
676 |
|
|
|
677 |
|
|
/* Set the state of this revision if specified. */ |
678 |
|
|
if (pb->state != NULL) |
679 |
|
|
(void)rcs_state_set(pb->file, pb->newrev, pb->state); |
680 |
|
|
|
681 |
|
|
/* Inherit RCSFILE permissions from file being checked in */ |
682 |
|
|
if (fstat(workfile_fd, &st) == -1) |
683 |
|
|
err(1, "%s", pb->filename); |
684 |
|
|
|
685 |
|
|
/* Strip all the write bits */ |
686 |
|
|
pb->file->rf_mode = st.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH); |
687 |
|
|
|
688 |
|
|
(void)close(workfile_fd); |
689 |
|
|
(void)unlink(pb->filename); |
690 |
|
|
|
691 |
|
|
/* Write out RCSFILE before calling checkout_rev() */ |
692 |
|
|
rcs_write(pb->file); |
693 |
|
|
|
694 |
|
|
/* Do checkout if -u or -l are specified. */ |
695 |
|
|
if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) && |
696 |
|
|
!(pb->flags & CI_DEFAULT)) { |
697 |
|
|
checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags, |
698 |
|
|
pb->username, pb->author, NULL, NULL); |
699 |
|
|
} |
700 |
|
|
|
701 |
|
|
if (!(pb->flags & QUIET)) { |
702 |
|
|
fprintf(stderr, "initial revision: %s\n", |
703 |
|
|
rcsnum_tostr(pb->newrev, numb, sizeof(numb))); |
704 |
|
|
} |
705 |
|
|
|
706 |
|
|
return (0); |
707 |
|
|
} |
708 |
|
|
|
709 |
|
|
/* |
710 |
|
|
* checkin_attach_symbol() |
711 |
|
|
* |
712 |
|
|
* Attempt to attach the specified symbol to the revision. |
713 |
|
|
* On success, return 0. On error return -1. |
714 |
|
|
*/ |
715 |
|
|
static int |
716 |
|
|
checkin_attach_symbol(struct checkin_params *pb) |
717 |
|
|
{ |
718 |
|
|
char rbuf[RCS_REV_BUFSZ]; |
719 |
|
|
int ret; |
720 |
|
|
if (!(pb->flags & QUIET)) |
721 |
|
|
printf("symbol: %s\n", pb->symbol); |
722 |
|
|
if (pb->flags & CI_SYMFORCE) { |
723 |
|
|
if (rcs_sym_remove(pb->file, pb->symbol) < 0) { |
724 |
|
|
if (rcs_errno != RCS_ERR_NOENT) { |
725 |
|
|
warnx("problem removing symbol: %s", |
726 |
|
|
pb->symbol); |
727 |
|
|
return (-1); |
728 |
|
|
} |
729 |
|
|
} |
730 |
|
|
} |
731 |
|
|
if ((ret = rcs_sym_add(pb->file, pb->symbol, pb->newrev)) == -1 && |
732 |
|
|
(rcs_errno == RCS_ERR_DUPENT)) { |
733 |
|
|
rcsnum_tostr(rcs_sym_getrev(pb->file, pb->symbol), |
734 |
|
|
rbuf, sizeof(rbuf)); |
735 |
|
|
warnx("symbolic name %s already bound to %s", pb->symbol, rbuf); |
736 |
|
|
return (-1); |
737 |
|
|
} else if (ret == -1) { |
738 |
|
|
warnx("problem adding symbol: %s", pb->symbol); |
739 |
|
|
return (-1); |
740 |
|
|
} |
741 |
|
|
return (0); |
742 |
|
|
} |
743 |
|
|
|
744 |
|
|
/* |
745 |
|
|
* checkin_revert() |
746 |
|
|
* |
747 |
|
|
* If there are no differences between the working file and the latest revision |
748 |
|
|
* and the -f flag is not specified, simply revert to the latest version and |
749 |
|
|
* warn the user. |
750 |
|
|
* |
751 |
|
|
*/ |
752 |
|
|
static int |
753 |
|
|
checkin_revert(struct checkin_params *pb) |
754 |
|
|
{ |
755 |
|
|
char rbuf[RCS_REV_BUFSZ]; |
756 |
|
|
|
757 |
|
|
rcsnum_tostr(pb->frev, rbuf, sizeof(rbuf)); |
758 |
|
|
|
759 |
|
|
if (!(pb->flags & QUIET)) |
760 |
|
|
(void)fprintf(stderr, "file is unchanged; reverting " |
761 |
|
|
"to previous revision %s\n", rbuf); |
762 |
|
|
|
763 |
|
|
/* Attach a symbolic name to this revision if specified. */ |
764 |
|
|
if (pb->symbol != NULL) { |
765 |
|
|
if (checkin_checklock(pb) == -1) |
766 |
|
|
return (-1); |
767 |
|
|
|
768 |
|
|
pb->newrev = pb->frev; |
769 |
|
|
if (checkin_attach_symbol(pb) == -1) |
770 |
|
|
return (-1); |
771 |
|
|
} |
772 |
|
|
|
773 |
|
|
pb->flags |= CO_REVERT; |
774 |
|
|
(void)close(workfile_fd); |
775 |
|
|
(void)unlink(pb->filename); |
776 |
|
|
|
777 |
|
|
/* If needed, write out RCSFILE before calling checkout_rev() */ |
778 |
|
|
if (pb->symbol != NULL) |
779 |
|
|
rcs_write(pb->file); |
780 |
|
|
|
781 |
|
|
if ((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) |
782 |
|
|
checkout_rev(pb->file, pb->frev, pb->filename, |
783 |
|
|
pb->flags, pb->username, pb->author, NULL, NULL); |
784 |
|
|
|
785 |
|
|
return (0); |
786 |
|
|
} |
787 |
|
|
|
788 |
|
|
/* |
789 |
|
|
* checkin_checklock() |
790 |
|
|
* |
791 |
|
|
* Check for the existence of a lock on the file. If there are no locks, or it |
792 |
|
|
* is not locked by the correct user, return -1. Otherwise, return 0. |
793 |
|
|
*/ |
794 |
|
|
static int |
795 |
|
|
checkin_checklock(struct checkin_params *pb) |
796 |
|
|
{ |
797 |
|
|
struct rcs_lock *lkp; |
798 |
|
|
|
799 |
|
|
if (rcs_lock_getmode(pb->file) == RCS_LOCK_LOOSE) |
800 |
|
|
return (0); |
801 |
|
|
|
802 |
|
|
TAILQ_FOREACH(lkp, &(pb->file->rf_locks), rl_list) { |
803 |
|
|
if (!strcmp(lkp->rl_name, pb->username) && |
804 |
|
|
!rcsnum_cmp(lkp->rl_num, pb->frev, 0)) |
805 |
|
|
return (0); |
806 |
|
|
} |
807 |
|
|
|
808 |
|
|
warnx("%s: no lock set by %s", pb->file->rf_path, pb->username); |
809 |
|
|
return (-1); |
810 |
|
|
} |
811 |
|
|
|
812 |
|
|
/* |
813 |
|
|
* checkin_mtimedate() |
814 |
|
|
* |
815 |
|
|
* Set the date of the revision to be the last modification |
816 |
|
|
* time of the working file. |
817 |
|
|
*/ |
818 |
|
|
static void |
819 |
|
|
checkin_mtimedate(struct checkin_params *pb) |
820 |
|
|
{ |
821 |
|
|
struct stat sb; |
822 |
|
|
|
823 |
|
|
if (fstat(workfile_fd, &sb) == -1) |
824 |
|
|
err(1, "%s", pb->filename); |
825 |
|
|
|
826 |
|
|
pb->date = sb.st_mtimespec.tv_sec; |
827 |
|
|
} |
828 |
|
|
|
829 |
|
|
/* |
830 |
|
|
* checkin_keywordscan() |
831 |
|
|
* |
832 |
|
|
* Searches working file for keyword values to determine its revision |
833 |
|
|
* number, creation date and author, and uses these values instead of |
834 |
|
|
* calculating them locally. |
835 |
|
|
* |
836 |
|
|
* Params: The data buffer to scan and pointers to pointers of variables in |
837 |
|
|
* which to store the outputs. |
838 |
|
|
* |
839 |
|
|
* On success, return 0. On error return -1. |
840 |
|
|
*/ |
841 |
|
|
static int |
842 |
|
|
checkin_keywordscan(BUF *data, RCSNUM **rev, time_t *date, char **author, |
843 |
|
|
char **state) |
844 |
|
|
{ |
845 |
|
|
BUF *buf; |
846 |
|
|
size_t left; |
847 |
|
|
u_int j; |
848 |
|
|
char *kwstr; |
849 |
|
|
unsigned char *c, *end, *start; |
850 |
|
|
|
851 |
|
|
end = buf_get(data) + buf_len(data) - 1; |
852 |
|
|
kwstr = NULL; |
853 |
|
|
|
854 |
|
|
left = buf_len(data); |
855 |
|
|
for (c = buf_get(data); |
856 |
|
|
c <= end && (c = memchr(c, '$', left)) != NULL; |
857 |
|
|
left = end - c + 1) { |
858 |
|
|
size_t len; |
859 |
|
|
|
860 |
|
|
start = c; |
861 |
|
|
c++; |
862 |
|
|
if (!isalpha(*c)) |
863 |
|
|
continue; |
864 |
|
|
|
865 |
|
|
/* look for any matching keywords */ |
866 |
|
|
for (j = 0; j < 10; j++) { |
867 |
|
|
len = strlen(rcs_expkw[j].kw_str); |
868 |
|
|
if (left < len) |
869 |
|
|
continue; |
870 |
|
|
if (memcmp(c, rcs_expkw[j].kw_str, len) != 0) { |
871 |
|
|
kwstr = rcs_expkw[j].kw_str; |
872 |
|
|
break; |
873 |
|
|
} |
874 |
|
|
} |
875 |
|
|
|
876 |
|
|
/* unknown keyword, continue looking */ |
877 |
|
|
if (kwstr == NULL) |
878 |
|
|
continue; |
879 |
|
|
|
880 |
|
|
c += len; |
881 |
|
|
if (c > end) { |
882 |
|
|
kwstr = NULL; |
883 |
|
|
break; |
884 |
|
|
} |
885 |
|
|
if (*c != ':') { |
886 |
|
|
kwstr = NULL; |
887 |
|
|
continue; |
888 |
|
|
} |
889 |
|
|
|
890 |
|
|
/* Find end of line or end of keyword. */ |
891 |
|
|
while (++c <= end) { |
892 |
|
|
if (*c == '\n') { |
893 |
|
|
/* Skip newline since it is definitely not `$'. */ |
894 |
|
|
++c; |
895 |
|
|
goto loopend; |
896 |
|
|
} |
897 |
|
|
if (*c == '$') |
898 |
|
|
break; |
899 |
|
|
} |
900 |
|
|
|
901 |
|
|
len = c - start + 1; |
902 |
|
|
buf = buf_alloc(len + 1); |
903 |
|
|
buf_append(buf, start, len); |
904 |
|
|
|
905 |
|
|
/* XXX - Not binary safe. */ |
906 |
|
|
buf_putc(buf, '\0'); |
907 |
|
|
checkin_parsekeyword(buf_get(buf), rev, date, author, state); |
908 |
|
|
buf_free(buf); |
909 |
|
|
loopend:; |
910 |
|
|
} |
911 |
|
|
if (kwstr == NULL) |
912 |
|
|
return (-1); |
913 |
|
|
else |
914 |
|
|
return (0); |
915 |
|
|
} |
916 |
|
|
|
917 |
|
|
/* |
918 |
|
|
* checkin_keywordtype() |
919 |
|
|
* |
920 |
|
|
* Given an RCS keyword string, determine what type of string it is. |
921 |
|
|
* This enables us to know what data should be in it. |
922 |
|
|
* |
923 |
|
|
* Returns type on success, or -1 on failure. |
924 |
|
|
*/ |
925 |
|
|
static int |
926 |
|
|
checkin_keywordtype(char *keystring) |
927 |
|
|
{ |
928 |
|
|
char *p; |
929 |
|
|
|
930 |
|
|
p = keystring; |
931 |
|
|
p++; |
932 |
|
|
if (strncmp(p, KW_ID, strlen(KW_ID)) == 0 || |
933 |
|
|
strncmp(p, KW_OPENBSD, strlen(KW_OPENBSD)) == 0) |
934 |
|
|
return (KW_TYPE_ID); |
935 |
|
|
else if (strncmp(p, KW_AUTHOR, strlen(KW_AUTHOR)) == 0) |
936 |
|
|
return (KW_TYPE_AUTHOR); |
937 |
|
|
else if (strncmp(p, KW_DATE, strlen(KW_DATE)) == 0) |
938 |
|
|
return (KW_TYPE_DATE); |
939 |
|
|
else if (strncmp(p, KW_STATE, strlen(KW_STATE)) == 0) |
940 |
|
|
return (KW_TYPE_STATE); |
941 |
|
|
else if (strncmp(p, KW_REVISION, strlen(KW_REVISION)) == 0) |
942 |
|
|
return (KW_TYPE_REVISION); |
943 |
|
|
else |
944 |
|
|
return (-1); |
945 |
|
|
} |
946 |
|
|
|
947 |
|
|
/* |
948 |
|
|
* checkin_parsekeyword() |
949 |
|
|
* |
950 |
|
|
* Do the actual parsing of an RCS keyword string, setting the values passed |
951 |
|
|
* to the function to whatever is found. |
952 |
|
|
* |
953 |
|
|
* XXX - Don't error out on malformed keywords. |
954 |
|
|
*/ |
955 |
|
|
static void |
956 |
|
|
checkin_parsekeyword(char *keystring, RCSNUM **rev, time_t *date, |
957 |
|
|
char **author, char **state) |
958 |
|
|
{ |
959 |
|
|
char *tokens[KW_NUMTOKS_MAX], *p, *datestring; |
960 |
|
|
int i = 0; |
961 |
|
|
|
962 |
|
|
for ((p = strtok(keystring, " ")); p; (p = strtok(NULL, " "))) { |
963 |
|
|
if (i < KW_NUMTOKS_MAX - 1) |
964 |
|
|
tokens[i++] = p; |
965 |
|
|
else |
966 |
|
|
break; |
967 |
|
|
} |
968 |
|
|
|
969 |
|
|
/* Parse data out of the expanded keyword */ |
970 |
|
|
switch (checkin_keywordtype(keystring)) { |
971 |
|
|
case KW_TYPE_ID: |
972 |
|
|
if (i < 3) |
973 |
|
|
break; |
974 |
|
|
/* only parse revision if one is not already set */ |
975 |
|
|
if (*rev == NULL) { |
976 |
|
|
if ((*rev = rcsnum_parse(tokens[2])) == NULL) |
977 |
|
|
errx(1, "could not parse rcsnum"); |
978 |
|
|
} |
979 |
|
|
|
980 |
|
|
if (i < 5) |
981 |
|
|
break; |
982 |
|
|
(void)xasprintf(&datestring, "%s %s", tokens[3], tokens[4]); |
983 |
|
|
if ((*date = date_parse(datestring)) == -1) |
984 |
|
|
errx(1, "could not parse date"); |
985 |
|
|
free(datestring); |
986 |
|
|
|
987 |
|
|
if (i < 6) |
988 |
|
|
break; |
989 |
|
|
free(*author); |
990 |
|
|
*author = xstrdup(tokens[5]); |
991 |
|
|
|
992 |
|
|
if (i < 7) |
993 |
|
|
break; |
994 |
|
|
free(*state); |
995 |
|
|
*state = xstrdup(tokens[6]); |
996 |
|
|
break; |
997 |
|
|
case KW_TYPE_AUTHOR: |
998 |
|
|
if (i < 2) |
999 |
|
|
break; |
1000 |
|
|
free(*author); |
1001 |
|
|
*author = xstrdup(tokens[1]); |
1002 |
|
|
break; |
1003 |
|
|
case KW_TYPE_DATE: |
1004 |
|
|
if (i < 3) |
1005 |
|
|
break; |
1006 |
|
|
(void)xasprintf(&datestring, "%s %s", tokens[1], tokens[2]); |
1007 |
|
|
if ((*date = date_parse(datestring)) == -1) |
1008 |
|
|
errx(1, "could not parse date"); |
1009 |
|
|
free(datestring); |
1010 |
|
|
break; |
1011 |
|
|
case KW_TYPE_STATE: |
1012 |
|
|
if (i < 2) |
1013 |
|
|
break; |
1014 |
|
|
free(*state); |
1015 |
|
|
*state = xstrdup(tokens[1]); |
1016 |
|
|
break; |
1017 |
|
|
case KW_TYPE_REVISION: |
1018 |
|
|
if (i < 2) |
1019 |
|
|
break; |
1020 |
|
|
/* only parse revision if one is not already set */ |
1021 |
|
|
if (*rev != NULL) |
1022 |
|
|
break; |
1023 |
|
|
if ((*rev = rcsnum_parse(tokens[1])) == NULL) |
1024 |
|
|
errx(1, "could not parse rcsnum"); |
1025 |
|
|
break; |
1026 |
|
|
} |
1027 |
|
|
} |