* Copyright (c) 1992, Brian Berliner and Jeff Polk
* Copyright (c) 1989-1992, Brian Berliner
* You may distribute under the terms of the GNU General Public License as
* specified in the README file that comes with the CVS 1.3 kit.
* "import" checks in the vendor release located in the current directory into
* the CVS source repository. The CVS vendor branch support is utilized.
* At least three arguments are expected to follow the options:
* repository Where the source belongs relative to the CVSROOT
* VendorTag Vendor's major tag
* VendorReleTag Tag for this particular release
* Additional arguments specify more Vendor Release Tags.
static char rcsid
[] = "@(#)import.c 1.52 92/03/31";
#define FILE_HOLDER ".#cvsxxx"
static char *get_comment (char *user
);
static int add_rcs_file (char *message
, char *rcs
, char *user
, char *vtag
,
int targc
, char *targv
[]);
static int expand_at_signs (char *buf
, off_t size
, FILE *fp
);
static int add_rev (char *message
, char *rcs
, char *vfile
, char *vers
);
static int add_tags (char *rcs
, char *vfile
, char *vtag
, int targc
,
static int import_descend (char *message
, char *vtag
, int targc
, char *targv
[]);
static int import_descend_dir (char *message
, char *dir
, char *vtag
,
int targc
, char *targv
[]);
static int process_import_file (char *message
, char *vfile
, char *vtag
,
int targc
, char *targv
[]);
static int update_rcs_file (char *message
, char *vfile
, char *vtag
, int targc
,
static void add_log (int ch
, char *fname
);
static int import_descend ();
static int process_import_file ();
static int update_rcs_file ();
static char *get_comment ();
static int add_rcs_file ();
static int expand_at_signs ();
static int import_descend_dir ();
static char repository
[PATH_MAX
];
static char *import_usage
[] =
"Usage: %s %s [-Qq] [-I ign] [-m msg] [-b branch]\n",
" repository vendor-tag release-tags...\n",
"\t-q\tSomewhat quiet.\n",
"\t-I ign\tMore files to ignore (! to reset).\n",
"\t-b bra\tVendor branch id.\n",
"\t-m msg\tLog message.\n",
char message
[MAXMESGLEN
];
char tmpfile
[L_tmpnam
+1];
(void) strcpy (vbranch
, CVSBRANCH
);
while ((c
= gnu_getopt (argc
, argv
, "Qqb:m:I:")) != -1)
(void) strcpy (vbranch
, optarg
);
if (strlen (optarg
) >= (sizeof (message
) - 1))
error (0, 0, "warning: message too long; truncated!");
(void) strncpy (message
, optarg
, sizeof (message
));
message
[sizeof (message
) - 2] = '\0';
(void) strcpy (message
, optarg
);
for (i
= 1; i
< argc
; i
++) /* check the tags for validity */
/* XXX - this should be a module, not just a pathname */
error (0, 0, "missing CVSROOT environment variable\n");
error (1, 0, "Set it or specify the '-d' option to %s.",
(void) sprintf (repository
, "%s/%s", CVSroot
, argv
[0]);
repos_len
= strlen (CVSroot
);
(void) strcpy (repository
, argv
[0]);
* Consistency checks on the specified vendor branch. It must be
* composed of only numbers and dots ('.'). Also, for now we only
* support branching to a single level, so the specified vendor branch
* must only have two dots in it (like "1.1.1").
for (cp
= vbranch
; *cp
!= '\0'; cp
++)
if (!isdigit (*cp
) && *cp
!= '.')
error (1, 0, "%s is not a numeric branch", vbranch
);
if (numdots (vbranch
) != 2)
error (1, 0, "Only branches with two dots are supported: %s", vbranch
);
(void) strcpy (vhead
, vbranch
);
cp
= rindex (vhead
, '.');
do_editor ((char *) NULL
, message
, repository
, (List
*) NULL
);
msglen
= strlen (message
);
if (msglen
== 0 || message
[msglen
- 1] != '\n')
message
[msglen
+ 1] = '\0';
* Make all newly created directories writable. Should really use a more
* sophisticated security mechanism here.
make_directories (repository
);
/* Create the logfile that will be logged upon completion */
if ((logfp
= fopen (tmpnam (tmpfile
), "w+")) == NULL
)
error (1, errno
, "cannot create temporary file `%s'", tmpfile
);
(void) unlink (tmpfile
); /* to be sure it goes away */
(void) fprintf (logfp
, "\nVendor Tag:\t%s\n", argv
[1]);
(void) fprintf (logfp
, "Release Tags:\t");
for (i
= 2; i
< argc
; i
++)
(void) fprintf (logfp
, "%s\n\t\t", argv
[i
]);
(void) fprintf (logfp
, "\n");
err
= import_descend (message
, argv
[1], argc
- 2, argv
+ 2);
(void) printf ("\n%d conflicts created by this import.\n",
(void) printf ("Use the following command to help the merge:\n\n");
(void) printf ("\t%s checkout -j%s:yesterday -j%s %s\n\n",
program_name
, argv
[1], argv
[1], argv
[0]);
(void) fprintf (logfp
, "\n%d conflicts created by this import.\n",
"Use the following command to help the merge:\n\n");
(void) fprintf (logfp
, "\t%s checkout -j%s:yesterday -j%s %s\n\n",
program_name
, argv
[1], argv
[1], argv
[0]);
(void) printf ("\nNo conflicts created by this import\n\n");
(void) fprintf (logfp
, "\nNo conflicts created by this import\n\n");
* Write out the logfile and clean up.
p
->delproc
= update_delproc
;
p
->key
= xstrdup ("- Imported sources");
p
->data
= (char *) T_TITLE
;
(void) addnode (ulist
, p
);
Update_Logfile (repository
, message
, vbranch
, logfp
, ulist
);
* process all the files in ".", then descend into other directories.
import_descend (message
, vtag
, targc
, targv
)
/* first, load up any per-directory ignore lists */
ign_add_file (CVSDOTIGNORE
, 1);
if ((dirp
= opendir (".")) == NULL
)
while ((dp
= readdir (dirp
)) != NULL
)
if (strcmp (dp
->d_name
, ".") == 0 || strcmp (dp
->d_name
, "..") == 0)
if (ign_name (dp
->d_name
))
add_log ('I', dp
->d_name
);
add_log ('L', dp
->d_name
);
if ((lln
= readlink(dp
->d_name
, lnbuf
, PATH_MAX
)) == -1) {
error(0, errno
, "Can't read contents of symlink %s",
sprintf(lnrep
, "%s/SymLinks", repository
);
links
= fopen(lnrep
, "a+");
"Can't open SymLinks file %s", lnrep
);
fputs(dp
->d_name
, links
);
add_log ('L', dp
->d_name
);
err
+= process_import_file (message
, dp
->d_name
,
if ((dirp
= opendir (".")) == NULL
)
while ((dp
= readdir (dirp
)) != NULL
)
if (ign_name (dp
->d_name
) || !isdir (dp
->d_name
))
err
+= import_descend_dir (message
, dp
->d_name
,
* Process the argument import file.
process_import_file (message
, vfile
, vtag
, targc
, targv
)
char attic_name
[PATH_MAX
];
(void) sprintf (rcs
, "%s/%s%s", repository
, vfile
, RCSEXT
);
(void) sprintf (attic_name
, "%s/%s/%s%s", repository
, CVSATTIC
,
if (!isfile (attic_name
))
* A new import source file; it doesn't exist as a ,v within the
* repository nor in the Attic -- create it anew.
return (add_rcs_file (message
, rcs
, vfile
, vtag
, targc
, targv
));
* an rcs file exists. have to do things the official, slow, way.
return (update_rcs_file (message
, vfile
, vtag
, targc
, targv
));
* The RCS file exists; update it by adding the new import file to the
* (possibly already existing) vendor branch.
update_rcs_file (message
, vfile
, vtag
, targc
, targv
)
vers
= Version_TS (repository
, (char *) NULL
, vbranch
, (char *) NULL
, vfile
,
1, 0, (List
*) NULL
, (List
*) NULL
);
if (vers
->vn_rcs
!= NULL
)
/* XXX - should be more unique */
(void) sprintf (xtmpfile
, "/tmp/%s", FILE_HOLDER
);
* The rcs file does have a revision on the vendor branch. Compare
* this revision with the import file; if they match exactly, there
* is no need to install the new import file as a new revision to the
* branch. Just tag the revision with the new import tags.
* This is to try to cut down the number of "C" conflict messages for
* locally modified import source files.
run_setup ("%s%s -q -f -r%s -p -ko", Rcsbin
, RCS_CO
, vers
->vn_rcs
);
run_setup ("%s%s -q -f -r%s -p", Rcsbin
, RCS_CO
, vers
->vn_rcs
);
run_arg (vers
->srcfile
->path
);
if ((retcode
= run_exec (RUN_TTY
, xtmpfile
, RUN_TTY
,
RUN_NORMAL
|RUN_REALLY
)) != 0)
fperror (logfp
, 0, retcode
== -1 ? ierrno
: 0,
"ERROR: cannot co revision %s of file %s", vers
->vn_rcs
,
error (0, retcode
== -1 ? ierrno
: 0,
"ERROR: cannot co revision %s of file %s", vers
->vn_rcs
,
(void) unlink_file (xtmpfile
);
different
= xcmp (xtmpfile
, vfile
);
(void) unlink_file (xtmpfile
);
* The two files are identical. Just update the tags, print the
* "U", signifying that the file has changed, but needs no
* attention, and we're done.
if (add_tags (vers
->srcfile
->path
, vfile
, vtag
, targc
, targv
))
/* We may have failed to parse the RCS file; check just in case */
if (vers
->srcfile
== NULL
|| add_rev (message
, vers
->srcfile
->path
,
add_tags (vers
->srcfile
->path
, vfile
, vtag
, targc
, targv
))
if (vers
->srcfile
->branch
== NULL
||
strcmp (vers
->srcfile
->branch
, vbranch
) != 0)
* Add the revision to the vendor branch
add_rev (message
, rcs
, vfile
, vers
)
int locked
, status
, ierrno
;
run_setup ("%s%s -q -l%s", Rcsbin
, RCS
, vbranch
);
if ((retcode
= run_exec (RUN_TTY
, DEVNULL
, DEVNULL
, RUN_NORMAL
)) == 0)
error (0, errno
, "fork failed");
if (link_file (vfile
, FILE_HOLDER
) < 0)
(void) unlink_file (FILE_HOLDER
);
(void) link_file (vfile
, FILE_HOLDER
);
fperror (logfp
, 0, ierrno
, "ERROR: cannot create link to %s", vfile
);
error (0, ierrno
, "ERROR: cannot create link to %s", vfile
);
run_setup ("%s%s -q -f -r%s", Rcsbin
, RCS_CI
, vbranch
);
run_args ("-m%s", message
);
status
= run_exec (RUN_TTY
, RUN_TTY
, RUN_TTY
, RUN_NORMAL
);
rename_file (FILE_HOLDER
, vfile
);
fperror (logfp
, 0, status
== -1 ? ierrno
: 0, "ERROR: Check-in of %s failed", rcs
);
error (0, status
== -1 ? ierrno
: 0, "ERROR: Check-in of %s failed", rcs
);
run_setup ("%s%s -q -u%s", Rcsbin
, RCS
, vbranch
);
(void) run_exec (RUN_TTY
, RUN_TTY
, RUN_TTY
, RUN_NORMAL
);
* Add the vendor branch tag and all the specified import release tags to the
* RCS file. The vendor branch tag goes on the branch root (1.1.1) while the
* vendor release tags go on the newly added leaf of the branch (1.1.1.1,
add_tags (rcs
, vfile
, vtag
, targc
, targv
)
run_setup ("%s%s -q -N%s:%s", Rcsbin
, RCS
, vtag
, vbranch
);
if ((retcode
= run_exec (RUN_TTY
, RUN_TTY
, RUN_TTY
, RUN_NORMAL
)) != 0)
fperror (logfp
, 0, retcode
== -1 ? ierrno
: 0,
"ERROR: Failed to set tag %s in %s", vtag
, rcs
);
error (0, retcode
== -1 ? ierrno
: 0,
"ERROR: Failed to set tag %s in %s", vtag
, rcs
);
vers
= Version_TS (repository
, (char *) NULL
, vtag
, (char *) NULL
, vfile
,
1, 0, (List
*) NULL
, (List
*) NULL
);
for (i
= 0; i
< targc
; i
++)
run_setup ("%s%s -q -N%s:%s", Rcsbin
, RCS
, targv
[i
], vers
->vn_rcs
);
if ((retcode
= run_exec (RUN_TTY
, RUN_TTY
, RUN_TTY
, RUN_NORMAL
)) != 0)
fperror (logfp
, 0, retcode
== -1 ? ierrno
: 0,
"WARNING: Couldn't add tag %s to %s", targv
[i
], rcs
);
error (0, retcode
== -1 ? ierrno
: 0,
"WARNING: Couldn't add tag %s to %s", targv
[i
], rcs
);
* Stolen from rcs/src/rcsfnms.c, and adapted/extended.
struct compair comtable
[] =
* comtable pairs each filename suffix with a comment leader. The comment
* leader is placed before each line generated by the $Log keyword. This
* table is used to guess the proper comment leader from the working file's
* suffix during initial ci (see InitAdmin()). Comment leaders are needed for
* languages without multiline comments; for others they are optional.
"asm", ";; ", /* assembler (MS-DOS) */
"bat", ":: ", /* batch (MS-DOS) */
"c++", "// ", /* C++ in all its infinite guises */
"cl", ";;; ", /* Common Lisp */
"cmd", ":: ", /* command (OS/2) */
"cmf", "c ", /* CM Fortran */
"el", "; ", /* Emacs Lisp */
"h", " * ", /* C-header */
"hh", "// ", /* C++ header */
"in", "# ", /* for Makefile.in */
"l", " * ", /* lex (conflict between lex and
"mac", ";; ", /* macro (DEC-10, MS-DOS, PDP-11,
"me", ".\\\" ", /* me-macros t/nroff */
"ml", "; ", /* mocklisp */
"mm", ".\\\" ", /* mm-macros t/nroff */
"ms", ".\\\" ", /* ms-macros t/nroff */
"man", ".\\\" ", /* man-macros t/nroff */
"1", ".\\\" ", /* feeble attempt at man pages... */
"pl", "# ", /* perl (conflict with Prolog) */
"ps", "% ", /* postscript */
"red", "% ", /* psl/rlisp */
"s", "! ", /* assembler */
"s", "| ", /* assembler */
"s", "/ ", /* assembler */
"s", "# ", /* assembler */
"s", "# ", /* assembler */
"S", "# ", /* Macro assembler */
"ye", " * ", /* yacc-efl */
"yr", " * ", /* yacc-ratfor */
"", "# ", /* default for empty suffix */
NULL
, "# " /* default for unknown suffix; */
/* must always be last */
char suffix_path
[PATH_MAX
];
* Convert to lower-case, since we are not concerned about the
* case-ness of the suffix.
(void) strcpy (suffix_path
, cp
);
for (cp
= suffix_path
; *cp
; cp
++)
suffix
= ""; /* will use the default */
if (comtable
[i
].suffix
== NULL
) /* default */
return (comtable
[i
].comlead
);
if (strcmp (suffix
, comtable
[i
].suffix
) == 0)
return (comtable
[i
].comlead
);
add_rcs_file (message
, rcs
, user
, vtag
, targc
, targv
)
char altdate1
[50], altdate2
[50];
int i
, mode
, ierrno
, err
= 0;
fprcs
= open_file (rcs
, "w+");
fpuser
= open_file (user
, "r");
if (fprintf (fprcs
, "head %s;\n", vhead
) == EOF
||
fprintf (fprcs
, "branch %s;\n", vbranch
) == EOF
||
fprintf (fprcs
, "access ;\n") == EOF
||
fprintf (fprcs
, "symbols ") == EOF
)
for (i
= targc
- 1; i
>= 0; i
--) /* RCS writes the symbols backwards */
if (fprintf (fprcs
, "%s:%s.1 ", targv
[i
], vbranch
) == EOF
)
if (fprintf (fprcs
, "%s:%s;\n", vtag
, vbranch
) == EOF
||
fprintf (fprcs
, "locks ; strict;\n") == EOF
||
/* XXX - make sure @@ processing works in the RCS file */
fprintf (fprcs
, "comment @%s@;\n\n", get_comment (user
)) == EOF
)
(void) sprintf (altdate1
, DATEFORM
,
ftm
->tm_year
+ (ftm
->tm_year
< 100 ? 0 : 1900),
ftm
->tm_mon
+ 1, ftm
->tm_mday
, ftm
->tm_hour
,
ftm
->tm_min
, ftm
->tm_sec
);
(void) sprintf (altdate2
, DATEFORM
,
ftm
->tm_year
+ (ftm
->tm_year
< 100 ? 0 : 1900),
ftm
->tm_mon
+ 1, ftm
->tm_mday
, ftm
->tm_hour
,
ftm
->tm_min
, ftm
->tm_sec
);
if (fprintf (fprcs
, "\n%s\n", vhead
) == EOF
||
fprintf (fprcs
, "date %s; author %s; state Exp;\n",
altdate1
, author
) == EOF
||
fprintf (fprcs
, "branches %s.1;\n", vbranch
) == EOF
||
fprintf (fprcs
, "next ;\n") == EOF
||
fprintf (fprcs
, "\n%s.1\n", vbranch
) == EOF
||
fprintf (fprcs
, "date %s; author %s; state Exp;\n",
altdate2
, author
) == EOF
||
fprintf (fprcs
, "branches ;\n") == EOF
||
fprintf (fprcs
, "next ;\n\n") == EOF
||
fprintf (fprcs
, "\ndesc\n") == EOF
||
fprintf (fprcs
, "@@\n\n\n") == EOF
||
fprintf (fprcs
, "\n%s\n", vhead
) == EOF
||
fprintf (fprcs
, "log\n") == EOF
||
fprintf (fprcs
, "@Initial revision\n@\n") == EOF
||
fprintf (fprcs
, "text\n@") == EOF
)
if (fstat (fileno (fpuser
), &sb
) < 0)
error (1, errno
, "cannot fstat %s", user
);
buf
= xmalloc ((int) size
);
if (fread (buf
, (int) size
, 1, fpuser
) != 1)
error (1, errno
, "cannot read file %s for copying", user
);
if (expand_at_signs (buf
, size
, fprcs
) == EOF
)
if (fprintf (fprcs
, "@\n\n") == EOF
||
fprintf (fprcs
, "\n%s.1\n", vbranch
) == EOF
||
fprintf (fprcs
, "log\n@") == EOF
||
expand_at_signs (message
, (off_t
) strlen (message
), fprcs
) == EOF
||
fprintf (fprcs
, "@\ntext\n") == EOF
||
fprintf (fprcs
, "@@\n") == EOF
)
if (fclose (fprcs
) == EOF
)
goto write_error_noclose
;
* Fix the modes on the RCS files. They must maintain the same modes as
* the original user file, except that all write permissions must be
mode
= sb
.st_mode
& ~(S_IWRITE
| S_IWGRP
| S_IWOTH
);
if (chmod (rcs
, mode
) < 0)
fperror (logfp
, 0, ierrno
,
"WARNING: cannot change mode of file %s", rcs
);
error (0, ierrno
, "WARNING: cannot change mode of file %s", rcs
);
fperror (logfp
, 0, ierrno
, "ERROR: cannot write file %s", rcs
);
error (0, ierrno
, "ERROR: cannot write file %s", rcs
);
fperror (logfp
, 0, 0, "ERROR: out of space - aborting");
error (1, 0, "ERROR: out of space - aborting");
* Sigh.. need to expand @ signs into double @ signs
expand_at_signs (buf
, size
, fp
)
for (cp
= buf
, end
= buf
+ size
; cp
< end
; cp
++)
if (putc (*cp
, fp
) == EOF
)
* Write an update message to (potentially) the screen and the log file.
if (!really_quiet
) /* write to terminal */
(void) printf ("%c %s/%s\n", ch
, repository
+ repos_len
+ 1, fname
);
(void) printf ("%c %s/%s\n", ch
, repository
, fname
);
(void) printf ("%c %s\n", ch
, fname
);
if (repos_len
) /* write to logfile */
(void) fprintf (logfp
, "%c %s/%s\n", ch
,
repository
+ repos_len
+ 1, fname
);
(void) fprintf (logfp
, "%c %s/%s\n", ch
, repository
, fname
);
(void) fprintf (logfp
, "%c %s\n", ch
, fname
);
* This is the recursive function that walks the argument directory looking
* for sub-directories that have CVS administration files in them and updates
* Note that we do not follow symbolic links here, which is a feature!
import_descend_dir (message
, dir
, vtag
, targc
, targv
)
fperror (logfp
, 0, 0, "ERROR: cannot get working directory: %s", cwd
);
error (0, 0, "ERROR: cannot get working directory: %s", cwd
);
if (repository
[0] == '\0')
(void) strcpy (repository
, dir
);
(void) strcat (repository
, "/");
(void) strcat (repository
, dir
);
error (0, 0, "Importing %s", repository
);
fperror (logfp
, 0, ierrno
, "ERROR: cannot chdir to %s", repository
);
error (0, ierrno
, "ERROR: cannot chdir to %s", repository
);
fperror (logfp
, 0, 0, "ERROR: %s is a file, should be a directory!",
error (0, 0, "ERROR: %s is a file, should be a directory!",
if (noexec
== 0 && mkdir (repository
, 0777) < 0)
fperror (logfp
, 0, ierrno
,
"ERROR: cannot mkdir %s -- not added", repository
);
"ERROR: cannot mkdir %s -- not added", repository
);
err
= import_descend (message
, vtag
, targc
, targv
);
if ((cp
= rindex (repository
, '/')) != NULL
)
error (1, errno
, "cannot chdir to %s", cwd
);