/* * 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. * * "update" updates the version in the present directory with respect to the RCS * repository. The present version must have been created by "checkout". The * user can keep up-to-date by calling "update" whenever he feels like it. * * The present version can be committed by "commit", but this keeps the version * in tact. * * Arguments following the options are taken to be file names to be updated, * rather than updating the entire directory. * * Modified or non-existent RCS files are checked out and reported as U * * * Modified user files are reported as M . If both the RCS file and * the user file have been modified, the user file is replaced by the result * of rcsmerge, and a backup file is written for the user in .#file.version. * If this throws up irreconcilable differences, the file is reported as C * , and as M otherwise. * * Files added but not yet committed are reported as A . Files * removed but not yet committed are reported as R . * * If the current directory contains subdirectories that hold concurrent * versions, these are updated too. If the -d option was specified, new * directories added to the repository are automatically created and updated * as well. */ #include "cvs.h" #ifndef lint static char rcsid[] = "@(#)update.c 1.83 92/04/10"; #endif #if __STDC__ static int checkout_file (char *file, char *repository, List *entries, List *srcfiles, Vers_TS *vers_ts, char *update_dir); static int isemptydir (char *dir); static int merge_file (char *file, char *repository, List *entries, Vers_TS *vers, char *update_dir); static int scratch_file (char *file, char *repository, List * entries, char *update_dir); static Dtype update_dirent_proc (char *dir, char *repository, char *update_dir); static int update_dirleave_proc (char *dir, int err, char *update_dir); static int update_file_proc (char *file, char *update_dir, char *repository, List * entries, List * srcfiles); static int update_filesdone_proc (int err, char *repository, char *update_dir); static int write_letter (char *file, int letter, char *update_dir); static void ignore_files (List * ilist, char *update_dir); static void join_file (char *file, List *srcfiles, Vers_TS *vers_ts, char *update_dir); #else static int update_file_proc (); static int update_filesdone_proc (); static Dtype update_dirent_proc (); static int update_dirleave_proc (); static int isemptydir (); static int scratch_file (); static int checkout_file (); static int write_letter (); static int merge_file (); static void ignore_files (); static void join_file (); #endif /* __STDC__ */ static char *options = NULL; static char *tag = NULL; static char *date = NULL; static char *join_rev1, *date_rev1; static char *join_rev2, *date_rev2; static int aflag = 0; static int force_tag_match = 1; static int update_build_dirs = 0; static int update_prune_dirs = 0; static int pipeout = 0; static List *ignlist = (List *) NULL; static char *update_usage[] = { "Usage:\n %s %s [-APQdflRpq] [-k kopt] [-r rev|-D date] [-j rev] [-I ign] [files...]\n", "\t-A\tReset any sticky tags/date/kopts.\n", "\t-P\tPrune empty directories.\n", "\t-Q\tReally quiet.\n", "\t-d\tBuild directories, like checkout does.\n", "\t-f\tForce a head revision match if tag/date not found.\n", "\t-l\tLocal directory only, no recursion.\n", "\t-R\tProcess directories recursively.\n", "\t-p\tSend updates to standard output.\n", "\t-q\tSomewhat quiet.\n", "\t-k kopt\tUse RCS kopt -k option on checkout.\n", "\t-r rev\tUpdate using specified revision/tag.\n", "\t-D date\tSet date to update from.\n", "\t-j rev\tMerge in changes made between current revision and rev.\n", "\t-I ign\tMore files to ignore (! to reset).\n", NULL }; /* * update is the argv,argc based front end for arg parsing */ int update (argc, argv) int argc; char *argv[]; { int c, err; int local = 0; /* recursive by default */ int which; /* where to look for files and dirs */ if (argc == -1) usage (update_usage); ign_setup (); /* parse the args */ optind = 1; while ((c = gnu_getopt (argc, argv, "ApPflRQqdk:r:D:j:I:")) != -1) { switch (c) { case 'A': aflag = 1; break; case 'I': ign_add (optarg, 0); break; case 'k': if (options) free (options); options = RCS_check_kflag (optarg); break; case 'l': local = 1; break; case 'R': local = 0; break; case 'Q': really_quiet = 1; /* FALL THROUGH */ case 'q': quiet = 1; break; case 'd': update_build_dirs = 1; break; case 'f': force_tag_match = 0; break; case 'r': tag = optarg; break; case 'D': date = Make_Date (optarg); break; case 'P': update_prune_dirs = 1; break; case 'p': pipeout = 1; noexec = 1; /* so no locks will be created */ break; case 'j': if (join_rev2) error (1, 0, "only two -j options can be specified"); if (join_rev1) join_rev2 = optarg; else join_rev1 = optarg; break; case '?': default: usage (update_usage); break; } } argc -= optind; argv += optind; /* * If we are updating the entire directory (for real) and building dirs * as we go, we make sure there is no static entries file and write the * tag file as appropriate */ if (argc <= 0 && !pipeout) { if (update_build_dirs) (void) unlink_file (CVSADM_ENTSTAT); /* keep the CVS/Tag file current with the specified arguments */ if (aflag || tag || date) WriteTag ((char *) NULL, tag, date); } /* look for files/dirs locally and in the repository */ which = W_LOCAL | W_REPOS; /* look in the attic too if a tag or date is specified */ if (tag != NULL || date != NULL) which |= W_ATTIC; /* call the command line interface */ err = do_update (argc, argv, options, tag, date, force_tag_match, local, update_build_dirs, aflag, update_prune_dirs, pipeout, which, join_rev1, join_rev2, (char *) NULL); /* free the space Make_Date allocated if necessary */ if (date != NULL) free (date); return (err); } /* * Command line interface to update (used by checkout) */ int do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag, xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir) int argc; char *argv[]; char *xoptions; char *xtag; char *xdate; int xforce; int local; int xbuild; int xaflag; int xprune; int xpipeout; int which; char *xjoin_rev1; char *xjoin_rev2; char *preload_update_dir; { int err = 0; char *cp; /* fill in the statics */ options = xoptions; tag = xtag; date = xdate; force_tag_match = xforce; update_build_dirs = xbuild; aflag = xaflag; update_prune_dirs = xprune; pipeout = xpipeout; /* setup the join support */ join_rev1 = xjoin_rev1; join_rev2 = xjoin_rev2; if (join_rev1 && (cp = index (join_rev1, ':')) != NULL) { *cp++ = '\0'; date_rev1 = Make_Date (cp); } else date_rev1 = (char *) NULL; if (join_rev2 && (cp = index (join_rev2, ':')) != NULL) { *cp++ = '\0'; date_rev2 = Make_Date (cp); } else date_rev2 = (char *) NULL; /* call the recursion processor */ err = start_recursion (update_file_proc, update_filesdone_proc, update_dirent_proc, update_dirleave_proc, argc, argv, local, which, aflag, 1, preload_update_dir, 1); return (err); } /* * This is the callback proc for update. It is called for each file in each * directory by the recursion code. The current directory is the local * instantiation. file is the file name we are to operate on. update_dir is * set to the path relative to where we started (for pretty printing). * repository is the repository. entries and srcfiles are the pre-parsed * entries and source control files. * * This routine decides what needs to be done for each file and does the * appropriate magic for checkout */ static int update_file_proc (file, update_dir, repository, entries, srcfiles) char *file; char *update_dir; char *repository; List *entries; List *srcfiles; { int retval; Ctype status; Vers_TS *vers; status = Classify_File (file, tag, date, options, force_tag_match, aflag, repository, entries, srcfiles, &vers); if (pipeout) { /* * We just return success without doing anything if any of the really * funky cases occur * * If there is still a valid RCS file, do a regular checkout type * operation */ switch (status) { case T_UNKNOWN: /* unknown file was explicitly asked * about */ case T_REMOVE_ENTRY: /* needs to be un-registered */ case T_ADDED: /* added but not committed */ retval = 0; break; case T_CONFLICT: /* old punt-type errors */ retval = 1; break; case T_UPTODATE: /* file was already up-to-date */ case T_NEEDS_MERGE: /* needs merging */ case T_MODIFIED: /* locally modified */ case T_REMOVED: /* removed but not committed */ case T_CHECKOUT: /* needs checkout */ retval = checkout_file (file, repository, entries, srcfiles, vers, update_dir); break; default: /* can't ever happen :-) */ error (0, 0, "unknown file status %d for file %s", status, file); retval = 0; break; } } else { switch (status) { case T_UNKNOWN: /* unknown file was explicitly asked * about */ case T_UPTODATE: /* file was already up-to-date */ retval = 0; break; case T_CONFLICT: /* old punt-type errors */ retval = 1; break; case T_NEEDS_MERGE: /* needs merging */ retval = merge_file (file, repository, entries, vers, update_dir); break; case T_MODIFIED: /* locally modified */ retval = write_letter (file, 'M', update_dir); break; case T_CHECKOUT: /* needs checkout */ retval = checkout_file (file, repository, entries, srcfiles, vers, update_dir); break; case T_ADDED: /* added but not committed */ retval = write_letter (file, 'A', update_dir); break; case T_REMOVED: /* removed but not committed */ retval = write_letter (file, 'R', update_dir); break; case T_REMOVE_ENTRY: /* needs to be un-registered */ retval = scratch_file (file, repository, entries, update_dir); break; default: /* can't ever happen :-) */ error (0, 0, "unknown file status %d for file %s", status, file); retval = 0; break; } } /* only try to join if things have gone well thus far */ if (retval == 0 && join_rev1) join_file (file, srcfiles, vers, update_dir); /* if this directory has an ignore list, add this file to it */ if (ignlist) { Node *p; p = getnode (); p->type = FILES; p->key = xstrdup (file); (void) addnode (ignlist, p); } freevers_ts (&vers); return (retval); } /* * update_filesdone_proc () is used */ /* ARGSUSED */ static int update_filesdone_proc (err, repository, update_dir) int err; char *repository; char *update_dir; { /* if this directory has an ignore list, process it then free it */ if (ignlist) { ignore_files (ignlist, update_dir); dellist (&ignlist); } /* Clean up CVS admin dirs if we are export */ if (strcmp (command_name, "export") == 0) { run_setup ("%s -fr", RM); run_arg (CVSADM); (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); } return (err); } /* * update_dirent_proc () is called back by the recursion processor before a * sub-directory is processed for update. In this case, update_dirent proc * will probably create the directory unless -d isn't specified and this is a * new directory. A return code of 0 indicates the directory should be * processed by the recursion code. A return of non-zero indicates the * recursion code should skip this directory. */ static Dtype update_dirent_proc (dir, repository, update_dir) char *dir; char *repository; char *update_dir; { if (!isdir (dir)) { /* if we aren't building dirs, blow it off */ if (!update_build_dirs) return (R_SKIP_ALL); if (noexec) { /* ignore the missing dir if -n is specified */ error (0, 0, "New directory `%s' -- ignored", dir); return (R_SKIP_ALL); } else { /* otherwise, create the dir and appropriate adm files */ make_directory (dir); Create_Admin (dir, repository, tag, date); } } /* * If we are building dirs and not going to stdout, we make sure there is * no static entries file and write the tag file as appropriate */ if (!pipeout) { if (update_build_dirs) { char tmp[PATH_MAX]; (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT); (void) unlink_file (tmp); } /* keep the CVS/Tag file current with the specified arguments */ if (aflag || tag || date) WriteTag (dir, tag, date); /* initialize the ignore list for this directory */ ignlist = getlist (); } /* print the warm fuzzy message */ if (!quiet) error (0, 0, "Updating %s", update_dir); return (R_PROCESS); } /* * update_dirleave_proc () is called back by the recursion code upon leaving * a directory. It will prune empty directories if needed and will execute * any appropriate update programs. */ /* ARGSUSED */ static int update_dirleave_proc (dir, err, update_dir) char *dir; int err; char *update_dir; { FILE *fp; /* run the update_prog if there is one */ if (err == 0 && !pipeout && !noexec && (fp = fopen (CVSADM_UPROG, "r")) != NULL) { char *cp; char *repository; char line[MAXLINELEN]; repository = Name_Repository ((char *) NULL, update_dir); if (fgets (line, sizeof (line), fp) != NULL) { if ((cp = rindex (line, '\n')) != NULL) *cp = '\0'; run_setup ("%s %s", line, repository); (void) printf ("%s %s: Executing '", program_name, command_name); run_print (stdout); (void) printf ("'\n"); (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); } (void) fclose (fp); free (repository); } /* Clean up CVS admin dirs if we are export */ if (strcmp (command_name, "export") == 0) { run_setup ("%s -fr", RM); run_arg (CVSADM); (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); } /* Prune empty dirs on the way out - if necessary */ (void) chdir (".."); if (update_prune_dirs && isemptydir (dir)) { run_setup ("%s -fr", RM); run_arg (dir); (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); } return (err); } /* * Returns 1 if the argument directory is completely empty, other than the * existence of the CVS directory entry. Zero otherwise. */ static int isemptydir (dir) char *dir; { DIR *dirp; struct direct *dp; if ((dirp = opendir (dir)) == NULL) { error (0, 0, "cannot open directory %s for empty check", dir); return (0); } while ((dp = readdir (dirp)) != NULL) { if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 && strcmp (dp->d_name, CVSADM) != 0 && strcmp (dp->d_name, OCVSADM) != 0) { (void) closedir (dirp); return (0); } } (void) closedir (dirp); return (1); } /* * scratch the Entries file entry associated with a file */ static int scratch_file (file, repository, entries, update_dir) char *file; char *repository; List *entries; char *update_dir; { history_write ('W', update_dir, "", file, repository); Scratch_Entry (entries, file); (void) unlink_file (file); return (0); } /* * check out a file - essentially returns the result of the fork on "co". */ static int checkout_file (file, repository, entries, srcfiles, vers_ts, update_dir) char *file; char *repository; List *entries; List *srcfiles; Vers_TS *vers_ts; char *update_dir; { char backup[PATH_MAX]; int set_time, retval = 0; int retcode = 0; /* don't screw with backup files if we're going to stdout */ if (!pipeout) { (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file); if (isfile (file)) rename_file (file, backup); else (void) unlink_file (backup); } run_setup ("%s%s -q -r%s %s", Rcsbin, RCS_CO, vers_ts->vn_rcs, vers_ts->options); /* * if we are checking out to stdout, print a nice message to stderr, and * add the -p flag to the command */ if (pipeout) { run_arg ("-p"); if (!quiet) { (void) fprintf (stderr, "===================================================================\n"); if (update_dir[0]) (void) fprintf (stderr, "Checking out %s/%s\n", update_dir, file); else (void) fprintf (stderr, "Checking out %s\n", file); (void) fprintf (stderr, "RCS: %s\n", vers_ts->srcfile->path); (void) fprintf (stderr, "VERS: %s\n", vers_ts->vn_rcs); (void) fprintf (stderr, "***************\n"); } } /* tack on the rcs and maybe the user file */ run_arg (vers_ts->srcfile->path); if (!pipeout) run_arg (file); if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, (pipeout ? (RUN_NORMAL|RUN_REALLY) : RUN_NORMAL))) == 0) { if (!pipeout) { Vers_TS *xvers_ts; if (cvswrite == TRUE) xchmod (file, 1); /* set the time from the RCS file iff it was unknown before */ if (vers_ts->vn_user == NULL || strncmp (vers_ts->ts_rcs, "Initial", 7) == 0) { set_time = 1; } else set_time = 0; xvers_ts = Version_TS (repository, options, tag, date, file, force_tag_match, set_time, entries, srcfiles); if (strcmp (xvers_ts->options, "-V4") == 0) xvers_ts->options[0] = '\0'; Register (entries, file, xvers_ts->vn_rcs, xvers_ts->ts_user, xvers_ts->options, xvers_ts->tag, xvers_ts->date); /* fix up the vers structure, in case it is used by join */ if (join_rev1) { if (vers_ts->vn_user != NULL) free (vers_ts->vn_user); if (vers_ts->vn_rcs != NULL) free (vers_ts->vn_rcs); vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs); vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs); } /* If this is really Update and not Checkout, recode history */ if (strcmp (command_name, "update") == 0) history_write ('U', update_dir, xvers_ts->vn_rcs, file, repository); freevers_ts (&xvers_ts); if (!really_quiet) { if (update_dir[0]) (void) printf ("U %s/%s\n", update_dir, file); else (void) printf ("U %s\n", file); } } } else { int old_errno = errno; /* save errno value over the rename */ if (!pipeout && isfile (backup)) rename_file (backup, file); error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0, "could not check out %s", file); retval = retcode; } if (!pipeout) (void) unlink_file (backup); return (retval); } /* * Several of the types we process only print a bit of information consisting * of a single letter and the name. */ static int write_letter (file, letter, update_dir) char *file; char letter; char *update_dir; { if (!really_quiet) { if (update_dir[0]) (void) printf ("%c %s/%s\n", letter, update_dir, file); else (void) printf ("%c %s\n", letter, file); } return (0); } /* * Do all the magic associated with a file which needs to be merged */ static int merge_file (file, repository, entries, vers, update_dir) char *file; char *repository; List *entries; Vers_TS *vers; char *update_dir; { char user[PATH_MAX]; char backup[PATH_MAX]; int status; int retcode = 0; /* * The users currently modified file is moved to a backup file name * ".#filename.version", so that it will stay around for a few days * before being automatically removed by some cron daemon. The "version" * is the version of the file that the user was most up-to-date with * before the merge. */ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user); if (update_dir[0]) (void) sprintf (user, "%s/%s", update_dir, file); else (void) strcpy (user, file); (void) unlink_file (backup); copy_file (file, backup); xchmod (file, 1); /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */ run_setup ("%s%s %s -r%s -r%s", Rcsbin, RCS_RCSMERGE, vers->options, vers->vn_user, vers->vn_rcs); run_arg (vers->srcfile->path); status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); if (status != 0 #ifdef HAVE_RCS5 && status != 1 #endif ) { error (0, status == -1 ? errno : 0, "could not merge revision %s of %s", vers->vn_user, user); error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s", user, backup); rename_file (backup, file); return (1); } /* XXX - Might want to make sure that rcsmerge changed the file */ if (strcmp (vers->options, "-V4") == 0) vers->options[0] = '\0'; Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options, vers->tag, vers->date); /* fix up the vers structure, in case it is used by join */ if (join_rev1) { if (vers->vn_user != NULL) free (vers->vn_user); vers->vn_user = xstrdup (vers->vn_rcs); } /* possibly run GREP to see if there appear to be conflicts in the file */ run_setup ("%s -s", GREP); run_arg (RCS_MERGE_PAT); run_arg (file); if (status == 1 || (retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) == 0) { if (!noexec) error (0, 0, "conflicts found in %s", user); if (!really_quiet) (void) printf ("C %s\n", user); history_write ('C', update_dir, vers->vn_rcs, file, repository); } else if (retcode == -1) { error (1, errno, "fork failed while examining update of %s", user); } else { if (!really_quiet) (void) printf ("M %s\n", user); history_write ('G', update_dir, vers->vn_rcs, file, repository); } return (0); } /* * Do all the magic associated with a file which needs to be joined * (-j option) */ static void join_file (file, srcfiles, vers, update_dir) char *file; List *srcfiles; Vers_TS *vers; char *update_dir; { char user[PATH_MAX]; char backup[PATH_MAX]; char *rev, *baserev; char *options; int status; /* determine if we need to do anything at all */ if (vers->vn_user == NULL || vers->srcfile == NULL || vers->srcfile->path == NULL) { return; } /* special handling when two revisions are specified */ if (join_rev1 && join_rev2) { rev = RCS_getversion (vers->srcfile, join_rev2, date_rev2, 1); if (rev == NULL) { if (!quiet && date_rev2 == NULL) error (0, 0, "cannot find revision %s in file %s", join_rev2, file); return; } baserev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1); if (baserev == NULL) { if (!quiet && date_rev1 == NULL) error (0, 0, "cannot find revision %s in file %s", join_rev1, file); free (rev); return; } /* * nothing to do if: * second revision matches our BASE revision (vn_user) && * both revisions are on the same branch */ if (strcmp (vers->vn_user, rev) == 0 && numdots (baserev) == numdots (rev)) { /* might be the same branch. take a real look */ char *dot = rindex (baserev, '.'); int len = (dot - baserev) + 1; if (strncmp (baserev, rev, len) == 0) return; } } else { rev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1); if (rev == NULL) return; if (strcmp (rev, vers->vn_user) == 0) /* no merge necessary */ { free (rev); return; } baserev = RCS_whatbranch (file, join_rev1, srcfiles); if (baserev) { char *cp; /* we get a branch -- turn it into a revision, or NULL if trunk */ if ((cp = rindex (baserev, '.')) == NULL) { free (baserev); baserev = (char *) NULL; } else *cp = '\0'; } } if (baserev && strcmp (baserev, rev) == 0) { /* they match -> nothing to do */ free (rev); free (baserev); return; } /* OK, so we have a revision and possibly a base revision; continue on */ /* * The users currently modified file is moved to a backup file name * ".#filename.version", so that it will stay around for a few days * before being automatically removed by some cron daemon. The "version" * is the version of the file that the user was most up-to-date with * before the merge. */ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user); if (update_dir[0]) (void) sprintf (user, "%s/%s", update_dir, file); else (void) strcpy (user, file); (void) unlink_file (backup); copy_file (file, backup); xchmod (file, 1); options = vers->options; #ifdef HAVE_RCS5 #if 0 /* This is temporarily disabled, since RCS loses $Log: update.c,v $ * Revision 1.2 1992/09/09 16:30:51 ceder * CVS Id 4: Summary: -kk / "update -j" problems with branches. * CVS Id 43: Mergeing from a branch loses $Log:$. * update.c (join_file): Don't send -kk flag to rcsmerge. This should fix * Id 43. Id 4 is because there is a bug in GNU diff. It might be cured * by this fix, it might not. * entries. */ if (*options == '\0') options = "-kk"; /* to ignore keyword expansions */ #endif #endif /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */ run_setup ("%s%s %s %s%s -r%s", Rcsbin, RCS_RCSMERGE, options, baserev ? "-r" : "", baserev ? baserev : "", rev); run_arg (vers->srcfile->path); status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); if (status != 0 #ifdef HAVE_RCS5 && status != 1 #endif ) { error (0, status == -1 ? errno : 0, "could not merge revision %s of %s", rev, user); error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s", user, backup); rename_file (backup, file); } free (rev); if (baserev) free (baserev); return; } /* * Process the current directory, looking for files not in ILIST and not on * the global ignore list for this directory. */ static void ignore_files (ilist, update_dir) List *ilist; char *update_dir; { DIR *dirp; struct direct *dp; struct stat sb; char *file; char *xdir; /* we get called with update_dir set to "." sometimes... strip it */ if (strcmp (update_dir, ".") == 0) xdir = ""; else xdir = update_dir; dirp = opendir ("."); if (dirp == NULL) return; ign_add_file (CVSDOTIGNORE, 1); while ((dp = readdir (dirp)) != NULL) { file = dp->d_name; if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0) continue; if (findnode (ilist, file) != NULL) continue; if (lstat (file, &sb) != -1) { if (S_ISDIR (sb.st_mode)) continue; #ifdef S_IFLNK if (S_ISLNK (sb.st_mode)) continue; #endif } if (ign_name (file)) continue; (void) write_letter (file, '?', xdir); } (void) closedir (dirp); }