2 * Copyright © 2006 Sean Estabrooks <seanlkml@sympatico.ca>
3 * Copyright © 2006 Keith Packard <keithp@keithp.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or (at
8 * your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 * Large portions of code contained in this file were obtained from
20 * the original RCS application under GPLv2 or later license, it retains
21 * the copyright of the original authors listed below:
23 * Copyright 1982, 1988, 1989 Walter Tichy
24 * Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
25 * Distributed under license by the Free Software Foundation, Inc.
31 typedef unsigned char uchar
;
34 struct alloclist
*nextalloc
;
37 struct out_buffer_type
{
38 char *text
, *ptr
, *end_of_text
;
42 struct in_buffer_type
{
49 long line1
, nlines
, adprev
, dafter
;
52 const int initial_out_buffer_size
= 1024;
53 char const ciklog
[] = "checked in with -k by ";
55 #define KEYLENGTH 8 /* max length of any of the above keywords */
56 #define KDELIM '$' /* keyword delimiter */
57 #define VDELIM ':' /* separates keywords from values */
58 #define SDELIM '@' /* string delimiter */
59 #define min(a,b) ((a) < (b) ? (a) : (b))
60 #define max(a,b) ((a) > (b) ? (a) : (b))
62 char const *const Keyword
[] = {
63 0, "Author", "Date", "Header", "Id", "Locker", "Log",
64 "Name", "RCSfile", "Revision", "Source", "State"
66 enum markers
{ Nomatch
, Author
, Date
, Header
, Id
, Locker
, Log
,
67 Name
, RCSfile
, Revision
, Source
, State
};
68 enum stringwork
{ENTER
, EDIT
};
70 enum expand_mode
{EXPANDKKV
, EXPANDKKVL
, EXPANDKK
, EXPANDKV
, EXPANDKO
, EXPANDKB
};
71 enum expand_mode Gexpand
;
75 char const *Gfilename
;
77 cvs_version
*Gversion
;
78 char Gversion_number
[CVS_MAX_REV_LEN
];
79 struct out_buffer_type
*Goutbuf
;
80 struct in_buffer_type in_buffer_store
;
81 struct in_buffer_type
*Ginbuf
= &in_buffer_store
;
84 * Gline contains pointers to the lines in the currently edit buffer
85 * It is a 0-origin array that represents Glinemax-Ggapsize lines.
86 * Gline[0 .. Ggap-1] and Gline[Ggap+Ggapsize .. Glinemax-1] hold
87 * pointers to lines. Gline[Ggap .. Ggap+Ggapsize-1] contains garbage.
88 * Any @s in lines are duplicated.
89 * Lines are terminated by \n, or (for a last partial line only) by single @.
96 size_t gap
, gapsize
, linemax
;
97 } stack
[CVS_MAX_DEPTH
/2];
98 #define Gline stack[depth].line
99 #define Ggap stack[depth].gap
100 #define Ggapsize stack[depth].gapsize
101 #define Glinemax stack[depth].linemax
103 static void fatal_system_error(char const *s
)
109 static void fatal_error(char const *format
,...)
113 fprintf(stderr
, "rcsco2git fatal: ");
114 va_start(args
, format
);
115 vfprintf(stderr
, format
, args
);
117 fprintf(stderr
, "\n");
121 static inline void* xmalloc(size_t size
)
123 void *ret
= malloc(size
);
127 fatal_system_error("Out of memory, malloc failed");
130 static inline void* xrealloc(void *ptr
, size_t size
)
132 void *ret
= realloc(ptr
, size
);
134 ret
= realloc(ptr
, 1);
136 fatal_system_error("Out of memory, realloc failed");
140 /* backup one position in the input buffer, unless at start of buffer
141 * return character at new position, or EOF if we could not back up
143 static int in_buffer_ungetc(void)
146 if (Ginbuf
->read_count
== 0)
148 --Ginbuf
->read_count
;
158 static int in_buffer_getc(void)
161 c
= *(Ginbuf
->ptr
++);
162 ++Ginbuf
->read_count
;
164 c
= *(Ginbuf
->ptr
++);
167 --Ginbuf
->read_count
;
174 static uchar
* in_get_line(void)
177 uchar
*ptr
= Ginbuf
->ptr
;
181 while (c
!= EOF
&& c
!= '\n')
182 c
= in_buffer_getc();
186 static uchar
* in_buffer_loc(void)
191 static void in_buffer_init(uchar
*text
, int bypass_initial
)
193 Ginbuf
->ptr
= Ginbuf
->buffer
= text
;
194 Ginbuf
->read_count
=0;
195 if (bypass_initial
&& *Ginbuf
->ptr
++ != SDELIM
)
196 fatal_error("Illegal buffer, missing @ %s", text
);
199 static void out_buffer_init(void)
202 Goutbuf
= xmalloc(sizeof(struct out_buffer_type
));
203 memset(Goutbuf
, 0, sizeof(struct out_buffer_type
));
204 Goutbuf
->size
= initial_out_buffer_size
;
205 t
= xmalloc(Goutbuf
->size
);
208 Goutbuf
->end_of_text
= t
+ Goutbuf
->size
;
211 static void out_buffer_enlarge(void)
213 int ptroffset
= Goutbuf
->ptr
- Goutbuf
->text
;
215 Goutbuf
->text
= xrealloc(Goutbuf
->text
, Goutbuf
->size
);
216 Goutbuf
->end_of_text
= Goutbuf
->text
+ Goutbuf
->size
;
217 Goutbuf
->ptr
= Goutbuf
->text
+ ptroffset
;
220 static unsigned long out_buffer_count(void)
222 return (unsigned long) (Goutbuf
->ptr
- Goutbuf
->text
);
225 static char *out_buffer_text(void)
227 return Goutbuf
->text
;
230 static void out_buffer_cleanup(void)
236 inline static void out_putc(int c
)
239 if (Goutbuf
->ptr
>= Goutbuf
->end_of_text
)
240 out_buffer_enlarge();
243 static void out_printf(const char *fmt
, ...)
248 room
= Goutbuf
->end_of_text
- Goutbuf
->ptr
;
250 ret
= vsnprintf(Goutbuf
->ptr
, room
, fmt
, ap
);
252 if (ret
> -1 && ret
< room
) {
256 out_buffer_enlarge();
260 static int out_fputs(const char *s
)
267 static void out_awrite(char const *s
, size_t len
)
273 static int latin1_alpha(int c
)
275 if (c
>= 192 && c
!= 215 && c
!= 247 ) return 1;
276 if ((c
>= 97 && c
< 123) || (c
>= 65 && c
< 91)) return 1;
280 static int latin1_whitespace(uchar c
)
282 if (c
== 32 || (c
>= 8 && c
<= 13 && c
!= 10)) return 1;
286 static enum expand_mode
expand_override(char const *s
)
288 char * const expand_names
[] = {"kv", "kvl", "k", "v", "o", "b"};
290 for (i
=0; i
< 6; ++i
)
291 if (strcmp(s
,expand_names
[i
]) == 0)
292 return (enum expand_mode
) i
;
296 static char const * basefilename(char const *p
)
298 char const *b
= p
, *q
= p
;
301 case '/': b
= q
; break;
306 /* Convert relative RCS filename to absolute path */
307 static char const * getfullRCSname(void)
317 if (Gfilename
[0] == '/')
320 /* If we've already calculated the absolute path, return it */
324 /* Get working directory and strip any trailing slashes */
325 wdbuflen
= _POSIX_PATH_MAX
+ 1;
326 wdbuf
= xmalloc(wdbuflen
);
327 while (!getcwd(wdbuf
, wdbuflen
)) {
329 xrealloc(wdbuf
, wdbuflen
<<1);
330 else fatal_system_error("getcwd");
333 /* Trim off trailing slashes */
334 dlen
= strlen(wdbuf
);
335 while (dlen
&& wdbuf
[dlen
-1] == '/')
339 /* Ignore leading `./'s in Gfilename. */
340 for (r
= Gfilename
; r
[0]=='.' && r
[1] == '/'; r
+= 2)
344 /* Build full pathname. */
345 Gabspath
= d
= xmalloc(dlen
+ strlen(r
) + 2);
346 memcpy(d
, wdbuf
, dlen
);
355 /* Check if string starts with a keyword followed by a KDELIM or VDELIM */
356 static enum markers
trymatch(char const *string
)
360 for (j
= sizeof(Keyword
)/sizeof(*Keyword
); (--j
); ) {
363 while (*p
++ == *s
++) {
368 return (enum markers
)j
;
378 /* Before line N, insert line L. N is 0-origin. */
379 static void insertline(unsigned long n
, uchar
* l
)
381 if (n
> Glinemax
- Ggapsize
)
382 fatal_error("edit script tried to insert beyond eof");
385 Ggap
= Ggapsize
= Glinemax
; Glinemax
<<= 1;
386 Gline
= xrealloc(Gline
, sizeof(uchar
*) * Glinemax
);
388 Glinemax
= Ggapsize
= 1024;
389 Gline
= xmalloc(sizeof(uchar
*) * Glinemax
);
393 memmove(Gline
+n
+Ggapsize
, Gline
+n
, (Ggap
-n
) * sizeof(uchar
*));
395 memmove(Gline
+Ggap
, Gline
+Ggap
+Ggapsize
, (n
-Ggap
) * sizeof(uchar
*));
401 /* Delete lines N through N+NLINES-1. N is 0-origin. */
402 static void deletelines(unsigned long n
, unsigned long nlines
)
404 unsigned long l
= n
+ nlines
;
405 if (Glinemax
-Ggapsize
< l
|| l
< n
)
406 fatal_error("edit script tried to delete beyond eof");
408 memmove(Gline
+l
+Ggapsize
, Gline
+l
, (Ggap
-l
) * sizeof(uchar
*));
410 memmove(Gline
+Ggap
, Gline
+Ggap
+Ggapsize
, (n
-Ggap
) * sizeof(uchar
*));
415 static long parsenum(void)
419 for(c
=in_buffer_getc(); isdigit(c
); c
=in_buffer_getc())
420 ret
= (ret
* 10) + (c
- '0');
425 static int parse_next_delta_command(struct diffcmd
*dc
)
430 cmd
= in_buffer_getc();
436 while (in_buffer_getc() == ' ')
442 while (in_buffer_getc() != '\n')
445 if (!nlines
|| (cmd
!= 'a' && cmd
!= 'd') || line1
+nlines
< line1
)
446 fatal_error("Corrupt delta");
449 if (line1
< dc
->adprev
)
450 fatal_error("backward insertion in delta");
451 dc
->adprev
= line1
+ 1;
452 } else if (cmd
== 'd') {
453 if (line1
< dc
->adprev
|| line1
< dc
->dafter
)
454 fatal_error("backward deletion in delta");
456 dc
->dafter
= line1
+ nlines
;
464 static void escape_string(register char const *s
)
468 switch ((c
= *s
++)) {
470 case '\t': out_fputs("\\t"); break;
471 case '\n': out_fputs("\\n"); break;
472 case ' ': out_fputs("\\040"); break;
473 case KDELIM
: out_fputs("\\044"); break;
474 case '\\': out_fputs("\\\\"); break;
475 default: out_putc(c
); break;
480 /* output the appropriate keyword value(s) */
481 static void keyreplace(enum markers marker
)
483 const char *target_lockedby
= NULL
; // Not wired in yet
489 char date_string
[25];
490 uchar
*kdelim_ptr
= NULL
;
491 enum expand_mode exp
= Gexpand
;
492 char const *sp
= Keyword
[(int)marker
];
494 strftime(date_string
, 25,
495 "%Y/%m/%d %H:%M:%S", localtime(&Gversion
->date
));
498 out_printf("%c%s", KDELIM
, sp
);
500 if (exp
!= EXPANDKK
) {
502 out_printf("%c%c", VDELIM
, ' ');
506 out_fputs(Gversion
->author
);
509 out_fputs(date_string
);
514 escape_string(basefilename(Gfilename
));
515 else escape_string(getfullRCSname());
516 out_printf(" %s %s %s %s",
517 Gversion_number
, date_string
,
518 Gversion
->author
, Gversion
->state
);
519 if (target_lockedby
&& exp
== EXPANDKKVL
)
520 out_printf(" %s", target_lockedby
);
523 if (target_lockedby
&& exp
== EXPANDKKVL
)
524 out_fputs(target_lockedby
);
528 escape_string(basefilename(Gfilename
));
531 out_fputs(Gversion_number
);
534 escape_string(getfullRCSname());
537 out_fputs(Gversion
->state
);
548 /* Closing delimiter is processed again in expandline */
555 * "Closing delimiter is processed again in explandline"
556 * does not apply here, since we consume the input.
563 if (sizeof(ciklog
)-1<=ls
&& !memcmp(sp
,ciklog
,sizeof(ciklog
)-1))
566 /* Back up to the start of the current input line */
569 c
= in_buffer_ungetc();
578 /* It is possible to have multiple keywords
579 on one line. Make sure we don't backtrack
580 into some other keyword! */
581 if (num_kdelims
> 2) {
585 kdelim_ptr
= in_buffer_loc();
589 /* Copy characters before `$Log' into LEADER. */
590 xxp
= leader
= xmalloc(kdelim_ptr
- in_buffer_loc());
591 for (cs
= 0; ; cs
++) {
592 c
= in_buffer_getc();
598 /* Convert traditional C or Pascal leader to ` *'. */
599 for (cw
= 0; cw
< cs
; cw
++)
600 if (!latin1_whitespace(xxp
[cw
]))
602 if (cw
+1 < cs
&& xxp
[cw
+1] == '*' &&
603 (xxp
[cw
] == '/' || xxp
[cw
] == '(')) {
609 } else if (!latin1_whitespace(xxp
[i
]))
614 /* Skip `$Log ... $' string. */
616 c
= in_buffer_getc();
617 } while (c
!= KDELIM
);
621 out_printf("Revision %s %s %s",
626 /* Do not include state: it may change and is not updated. */
628 for (; cw
&& (xxp
[cw
-1]==' ' || xxp
[cw
-1]=='\t'); --cw
)
638 out_awrite(xxp
+cw
, cs
-cw
);
652 static int expandline(void)
658 enum markers matchresult
;
661 if (Gkvlen
< KEYLENGTH
+3) {
662 Gkvlen
= KEYLENGTH
+ 3;
663 Gkeyval
= xrealloc(Gkeyval
, Gkvlen
);
669 c
= in_buffer_getc();
684 /* check for keyword */
685 /* first, copy a long enough string into keystring */
689 c
= in_buffer_getc();
690 if (tp
<= &Gkeyval
[KEYLENGTH
] && latin1_alpha(c
))
694 *tp
++ = c
; *tp
= '\0';
695 matchresult
= trymatch(Gkeyval
+1);
696 if (matchresult
==Nomatch
) {
699 continue; /* last c handled properly */
702 /* Now we have a keyword terminated with a K/VDELIM */
704 /* try to find closing KDELIM, and replace value */
705 tlim
= Gkeyval
+ Gkvlen
;
707 c
= in_buffer_getc();
708 if (c
=='\n' || c
==KDELIM
)
714 Gkeyval
= xrealloc(Gkeyval
, Gkvlen
);
715 tlim
= Gkeyval
+ Gkvlen
;
716 tp
= Gkeyval
+ orig_size
;
723 /* couldn't find closing KDELIM -- give up */
726 continue; /* last c handled properly */
730 * CVS will expand keywords that have
731 * overlapping delimiters, eg "$Name$Id$". To
732 * support that (mis)feature, push the closing
733 * delimiter back on the input so that the
734 * loop will resume processing starting with
740 /* now put out the new keyword value */
741 keyreplace(matchresult
);
756 static void process_delta(Node
*node
, enum stringwork func
)
758 long editline
= 0, linecnt
= 0, adjust
= 0;
764 in_buffer_init((uchar
*)node
->p
->text
, 1);
766 cvs_number_string(&Gversion
->number
, Gversion_number
);
770 while( (ptr
=in_get_line()) )
771 insertline(editline
++, ptr
);
773 dc
.dafter
= dc
.adprev
= 0;
774 while ((editor_command
= parse_next_delta_command(&dc
)) >= 0) {
775 if (editor_command
) {
776 editline
= dc
.line1
+ adjust
;
779 insertline(editline
++, in_get_line());
782 deletelines(dc
.line1
- 1 + adjust
, dc
.nlines
);
790 static void finishedit(void)
792 uchar
**p
, **lim
, **l
= Gline
;
793 for (p
=l
, lim
=l
+Ggap
; p
<lim
; ) {
794 in_buffer_init(*p
++, 0);
797 for (p
+=Ggapsize
, lim
=l
+Glinemax
; p
<lim
; ) {
798 in_buffer_init(*p
++, 0);
803 static void snapshotline(register uchar
* l
)
807 if ((c
= *l
++) == SDELIM
&& *l
++ != SDELIM
)
814 static void snapshotedit(void)
816 uchar
**p
, **lim
, **l
=Gline
;
817 for (p
=l
, lim
=l
+Ggap
; p
<lim
; )
819 for (p
+=Ggapsize
, lim
=l
+Glinemax
; p
<lim
; )
823 extern int write_sha1_file( void *buf
, unsigned long len
,
824 const char *type
, uchar
*return_sha1
);
825 extern char *sha1_to_hex(const uchar
*sha1
);
827 static void enter_branch(Node
*node
)
829 uchar
**p
= xmalloc(sizeof(uchar
*) * stack
[depth
].linemax
);
830 memcpy(p
, stack
[depth
].line
, sizeof(uchar
*) * stack
[depth
].linemax
);
831 stack
[depth
+ 1] = stack
[depth
];
832 stack
[depth
+ 1].next_branch
= node
->sib
;
833 stack
[depth
+ 1].line
= p
;
837 void generate_files(cvs_file
*cvs
)
839 int expand_override_enabled
= 1;
840 int expandflag
= Gexpand
< EXPANDKO
;
841 Node
*node
= head_node
;
843 Gfilename
= cvs
->name
;
844 if (cvs
->expand
&& expand_override_enabled
)
845 Gexpand
= expand_override(cvs
->expand
);
846 else Gexpand
= EXPANDKK
;
848 Gline
= NULL
; Ggap
= Ggapsize
= Glinemax
= 0;
849 stack
[0].node
= node
;
850 process_delta(node
, ENTER
);
860 write_sha1_file(out_buffer_text(),
863 out_buffer_cleanup();
864 strncpy(sha1_ascii
, sha1_to_hex(sha1
), 41);
865 node
->file
->sha1
= atom(sha1_ascii
);
872 while ((node
= stack
[depth
].node
->to
) == NULL
) {
873 free(stack
[depth
].line
);
876 node
= stack
[depth
--].next_branch
;
883 stack
[depth
].node
= node
;
884 process_delta(node
, EDIT
);