/* * C4 -- CVS like front end to the Perforce p4 SCM tool. * * Copyright (c) 1998 - 2000, Neil Russell. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Neil Russell. * 4. The name Neil Russell may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY NEIL RUSSELL ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NEIL RUSSELL BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Scanning logic. */ #include "defs.h" /**************************************************/ int CmdVerbose; /* '-v' verbose output */ int SupressIgnore; /* '-I' Supress ignore processing */ int Debug; /* '-D' debugging */ static int auto_add; /* '-a' automatic add */ static int no_add; /* '-x' supress missing files report */ static int auto_delete; /* '-d' delete missing files */ static int canonical; /* '-c' canonical update processing */ static int need_resolve; /* Need for resolve found in scan */ /* * Get command line options for Scan command. */ static char ** options(char ** argv) { for (; *argv && **argv == '-'; argv++) { char * p; p = *argv + 1; while (*p) { switch (*p++) { case 'a': auto_add = 1; break; case 'd': auto_delete = 1; break; case 'x': no_add = 1; break; case 'c': canonical = 1; auto_add = 1; auto_delete = 1; break; case 'n': CmdExec = 0; break; case 'v': CmdVerbose = 1; break; case 'I': SupressIgnore = 1; break; case 'D': Debug++; break; default: Error(0, "illegal option (%c)", p[-1]); break; } } } return argv; } /**************************************************/ /* * Recursively remove files; used in the canonical case, where a * user has replaced a directory with a file or symbolic link. */ static void remove_directory(File * fp) { File * xp; for (xp = fp->children; xp; xp = xp->next) { if (xp->children) remove_directory(xp); else if (xp->flag & F_DEPOT) xp->flag |= F_REMOVE; } } /* * Directory scan. */ static void scan_dirs(char * dir) { DIR * dp; struct dirent * ep; File * fpd; File * fp; char name[1024]; int namelen; strcpy(name, dir); namelen = strlen(name); name[namelen++] = '/'; if (CmdVerbose) printf("Scanning directory %s\n", dir); dp = opendir(dir); if (!dp) Error(1, "Can't open directory %s", dir); while (ep = readdir(dp)) { struct stat statb; if (Debug >= 2) printf("\tfile %s/%s\n", dir, ep->d_name); if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) continue; strcpy(&name[namelen], ep->d_name); if (lstat(name, &statb) == -1) continue; /* Ignore it */ fp = Lookup(name, 1); if (S_ISDIR(statb.st_mode)) { if (canonical && (fp->flag & F_DEPOT)) { /* * The user has replaced a file or * symbolic link with a directory. * So delete the file, and setup to * scan the directory. */ fp->flag |= (F_SDIR | F_REMOVE); } else if (fp->flag & F_DEPOT) { printf("Error:\tDepot "); if (fp->flag & F_SYMLINK) printf("symbolic link"); else printf("file"); printf(" has been replaced by a client " "directory (%s)\n", name); printf("\tDo a `c4 delete %s' before " "creating the directory.\n", name); fp->flag |= F_ERROR; } else fp->flag |= F_SDIR; } else if (S_ISREG(statb.st_mode)) { if (canonical && fp->children) { /* * User has replaced a directory with a * file. Setup to remove all files * in the directory and add the file. */ remove_directory(fp); fp->flag |= F_ADD; } else if (canonical && ((fp->flag & F_DEPOT) && (fp->flag & F_SYMLINK) )) { /* * User has replaced a symbolic link * with a file. Delete one and add * the other. */ fp->flag |= (F_ADD | F_REMOVE); } else if (fp->children) { printf("Error:\tDepot directory has been " "replaced by a client file " "(%s)\n", name); printf("\tDelete the depot directory tree " "before creating the file.\n"); fp->flag |= F_ERROR; } else if ((fp->flag & F_DEPOT) && (fp->flag & F_SYMLINK)) { printf("Error:\tDepot symlink has been " "replaced by a client " "file (%s)\n", name); printf("\tDelete the depot symlink " "before creating the file.\n"); fp->flag |= F_ERROR; } else { fp->flag |= F_EXISTS; if ((fp->flag & F_DEPOT) && statb.st_mtime != fp->modtime) { if (fp->flag & F_OPEN) fp->flag |= F_DIFF1; else fp->flag |= F_DIFF0; } } } else if (S_ISLNK(statb.st_mode)) { if (canonical && fp->children) { /* * User has replaced a directory with a * symbolic link. Setup to remove all * files in the directory and add the * file. */ remove_directory(fp); fp->flag |= F_ADD; } else if (canonical && ((fp->flag & F_DEPOT) && !(fp->flag & F_SYMLINK) )) { /* * User has replaced a file with a * symbolic link. Delete one and add * the other. */ fp->flag |= (F_ADD | F_REMOVE); } else if (fp->children) { printf("Error:\tDepot directory has been " "replaced by a client " "symlink (%s)\n", name); printf("\tDelete the depot directory tree " "before creating the symlink.\n"); fp->flag |= F_ERROR; } else if ((fp->flag & F_DEPOT) && !(fp->flag & F_SYMLINK)) { printf("Error:\tDepot file has been replaced " "by a client " "symlink (%s)\n", name); printf("\tDelete the depot file " "before creating the symlink.\n"); fp->flag |= F_ERROR; } else fp->flag |= F_EXISTS; } } if (closedir(dp) != 0) Error(1, "closedir failed"); fpd = Lookup(dir, 0); fp = fpd->children; for (; fp; fp = fp->next) { if (!(fp->flag & F_SDIR)) continue; if (!no_add && IsIgnored(dir, fpd, fp->name)) { /* * We found an ignore specification that matches * this directory. If `children' is non-nil, * then the "p4 fstat" found files inside this * directory. We should not ignore a directory * that contains files in the depot. */ if (fp->children) printf("Directory %s/%s can't be ignored;" " it contains active files.\n", dir, fp->name); else continue; } /* * Scan the directory recursively. */ strcpy(&name[namelen], fp->name); scan_dirs(name); } } /**************************************************/ #define REP_NEVER 0 /* Never report file name */ #define REP_NOEXEC 1 /* Report file name only if !CmdExec */ #define REP_ALWAYS 2 /* Always report file name */ static void recurser(char * dir, int bit, char * why, int rep) { File * fp; char name[1024]; int namelen; strcpy(name, dir); namelen = strlen(name); name[namelen++] = '/'; fp = Lookup(dir, 0); for (fp = fp->children; fp; fp = fp->next) { if (fp->flag & F_ERROR) continue; if (fp->flag & bit) { strcpy(&name[namelen], fp->name); CommandArg(name); if (why) switch (rep) { case REP_NOEXEC: if (CmdExec) break; case REP_ALWAYS: printf("%s %s\n", why, name); break; } } else if (fp->children) { strcpy(&name[namelen], fp->name); recurser(name, bit, why, rep); } } } /**************************************************/ static void diff_func(char * l) { File * fp; if (strncmp(l, CurDir, CurDirLen) == 0) { /* * Lookup the name using the relative path (strip the * current directory portion, including the '/'). */ fp = Lookup(&l[CurDirLen + 1], 0); if (!fp) Error(0, "diff entry not found (%s)", l); fp->flag &= ~(F_DIFF0 | F_DIFF1); fp->flag |= F_MODIFIED; } } static void do_diff(void) { if (UseDiffSC) { /* * If we have a 97.3 or later server, we have * a "p4 diff -sc" command available, that combines * a "p4 diff -sa" with a "p4 diff -se". We can * run one less command, speeding things a little. * * Check files to see if there are any different. */ Command("diff -sc", diff_func, 0); recurser(".", (F_DIFF0 | F_DIFF1), (char *)0, REP_NEVER); if (CmdArgs > 0) CommandDone(); } else { /* * Check opened files to see if they are still different. */ Command("diff -sa", diff_func, 0); recurser(".", F_DIFF1, (char *)0, REP_NEVER); if (CmdArgs > 0) CommandDone(); /* * Check unopened files to see if they are now different. */ Command("diff -se", diff_func, 0); recurser(".", F_DIFF0, (char *)0, REP_NEVER); if (CmdArgs > 0) CommandDone(); } } /**************************************************/ static void set_flags(char * dir) { File * dp; File * fp; char name[1024]; int namelen; strcpy(name, dir); namelen = strlen(name); name[namelen++] = '/'; dp = Lookup(dir, 0); for (fp = dp->children; fp; fp = fp->next) { if (fp->flag & F_ERROR) continue; if ((fp->flag & (F_MODIFIED | F_OPEN)) == F_MODIFIED) fp->flag |= F_EDIT; if ((fp->flag & (F_MODIFIED | F_OPEN)) == F_OPEN) fp->flag |= F_REVERT; if (!(fp->flag & F_DEPOT) && !(fp->flag & F_HAVE) && (fp->flag & F_EXISTS) && !IsIgnored(dir, dp, fp->name)) { /* * If the file is `open' and we get here, * then the file must have already been * added, and the F_REVERT flag will be * set (see above). Reset the revert * flag and don't set the add flag. */ if (fp->flag & F_OPEN) { /* * If the file is `open', then * a) the file has already been added, * and b) the F_REVERT flag must be * set. We reset the revert flag and * we don't set the F_ADD flag. */ fp->flag &= ~F_REVERT; } else if (auto_add) { /* * Auto add. A file exists here but * not in the depot; careful of the * case where the file has been * deleted in the depot, but this * client has not yet been updated. */ fp->flag |= F_ADD; } else if (!no_add) { /* * An unknown and not ignored file that * may be a candidate for auto add. * This flag causes it to be reported. */ fp->flag |= F_ADDMAYBE; } } else if ((fp->flag & F_DEPOT) && (fp->flag & F_HAVE) && !(fp->flag & F_EXISTS) && !(fp->flag & F_DELETE)) { if (auto_delete) fp->flag |= F_REMOVE; else fp->flag |= F_REFRESH; } else if ((fp->flag & F_DEPOT) && !(fp->flag & F_HAVE) && !(fp->flag & F_EXISTS) && !(fp->flag & F_DELETE)) { fp->flag |= F_GET; } if (fp->flag & F_GET) { if (fp->flag & (F_UNRESOLVED | F_MODIFIED)) { need_resolve = 1; fp->flag |= F_RESOLVE; } fp->flag &= ~F_REFRESH; } if (fp->flag & F_UNRESOLVED) { need_resolve = 1; fp->flag |= F_RESOLVE; } if (fp->children) { strcpy(&name[namelen], fp->name); set_flags(name); } } } static void report_file (char why, char *s0) { char *s = strdup (s0); char *filename = NULL; char *p = s; if (!strncmp ("...", s, 3)) return; while (*p) { if (strncmp (p, "//", 2) && (*p == '/' || (*p == '/' && *(p+1) == '/'))) filename = p; while (*p && *p != ' ') { p++; } if (*p == ' ') *(p++) = '\0'; } printf ("%c %s\n", why, (filename ? filename : s0)); free (s); } static void resolve_func (char *s) { static char *filename = NULL; int yours, theirs, both, conflicts; if (!strncmp (s, "//", 2)) return; if (4 == sscanf (s, "Diff chunks: %u yours + %u theirs + %u both + %u conflicting", &yours, &theirs, &both, &conflicts)) { char c; int do_free = !!filename; if (conflicts) c = 'C'; else c = 'P'; if (!filename) filename = "(unknown file)"; report_file (c, filename); if (do_free) free (filename); filename = NULL; return; } filename = strdup (s); } static void do_resolve(void) { if (!need_resolve) return; Command ("resolve -af", resolve_func, 1); recurser(".", F_RESOLVE, "resolve", REP_NOEXEC); if (CmdArgs > 0) CommandDone(); } /**************************************************/ static void add_func (char *s) { report_file ('A', s); } static void do_add(void) { recurser(".", F_ADDMAYBE, "?", REP_ALWAYS); Command("add", add_func, 1); recurser(".", F_ADD, "add", REP_NOEXEC); if (CmdArgs > 0) CommandDone(); } /**************************************************/ static void do_edit(void) { recurser (".", F_MODIFIED, "M", REP_ALWAYS); Command("edit", (void (*)(char*)) 0, 1); recurser(".", F_EDIT, "edit", REP_NOEXEC); if (CmdArgs > 0) CommandDone(); } /**************************************************/ static void get_func (char *s) { if (!strstr (s, " - is opened and not being changed")) report_file ('U', s); } static void do_refresh(void) { Command("refresh", get_func, 1); recurser(".", F_REFRESH, "refresh", REP_NOEXEC); if (CmdArgs > 0) CommandDone(); } static void do_get(void) { Command("get", get_func, 1); recurser(".", F_GET, "get", REP_NOEXEC); if (CmdArgs) CommandDone(); } /**************************************************/ static void do_revert(void) { Command("revert", (void (*)(char *))0, 1); recurser(".", F_REVERT, "revert", REP_NOEXEC); if (CmdArgs > 0) CommandDone(); } /**************************************************/ static void delete_func (char *s) { report_file ('R', s); } static void do_delete(void) { Command("delete", delete_func, 1); recurser(".", F_REMOVE, "delete", REP_NOEXEC); if (CmdArgs > 0) CommandDone(); } void Scan_Update (char ** argv, int Sync_Files) { /* * Search for a ".c4" file and glean information about * our environment. */ GetClientPath(); if (Debug >= 2) { printf("Client path = `%s'\n", ClientPath); printf("Current directory = `%s'\n", CurDir); } /* * Import information about depot files under this directory. */ DoFstat(); /* * Scan the files in the user's directory to see what * has happened. */ scan_dirs("."); PrintTree("scan_dirs"); /* * Compare files with the depot to see if they are different. */ do_diff(); PrintTree("do_diff"); /* * Decide what to do about it. */ set_flags("."); PrintTree("set_flags"); /* * Generate commands to do what we want. The order of these * matters - delete must happen before add incase a file changed * it's type (ie. from regular to symlink), and get must come * last so that we don't overwrite local changes. */ do_delete(); do_add(); do_edit(); do_revert(); do_refresh(); if (Sync_Files) { do_get(); do_resolve(); } } /**************************************************/ /* * Scan command. */ void Scan(char ** argv) { /* * Collect options. */ (void)options(argv); Scan_Update (argv, 0); exit (0); } /**************************************************/ /* * Update command. */ void Update (char **argv) { /* * Collect options. */ (void)options(argv); Scan_Update (argv, 1); exit (0); } /**************************************************/ /* * Commit command. */ void Commit (char **argv) { /* * Collect options. */ (void)options(argv); auto_add = 0; Scan_Update (argv, 0); if (need_resolve) { printf ("Up-to-date check failed! Please run c4 update.\n"); exit (1); } #if 0 Command("submit", (void (*)(char*))0, 1); recurser(".", (F_ADD | F_DELETE | F_EDIT | F_RESOLVE), (char *)0, REP_NEVER); if (CmdArgs > 0) CommandDone(); #else /* Cannot submit an explicit list of files */ Command("submit ...", (void (*)(char*))0, 1); CommandDone (); #endif exit (0); } /**************************************************/ /* * Import command / Canonical Update Processing. */ void Import(char ** argv) { /* * Collect options. */ canonical = 1; auto_add = 1; auto_delete = 1; SupressIgnore = 1; (void)options(argv); /* * Search for a ".c4" file and glean information about * our environment. */ GetClientPath(); if (Debug >= 2) { printf("Client path = `%s'\n", ClientPath); printf("Current directory = `%s'\n", CurDir); } /* * Import information about depot files under this directory. */ DoFstat(); /* * Scan the files in the user's directory to see what * has happened. */ scan_dirs("."); PrintTree("scan_dirs"); /* * Compare files with the depot to see if they are different. */ do_diff(); PrintTree("do_diff"); /* * Decide what to do about it. */ set_flags("."); PrintTree("set_flags"); /* * Generate commands to do what we want. The order of these * matters - delete must happen before add in case a file changed * it's type (ie. from regular to symlink). */ do_delete(); do_add(); do_edit(); do_resolve(); exit(0); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#3 | 338 | Thomas Quinot |
More CVS-like reporting. Automatic merge resolution. |
||
#2 | 337 | Thomas Quinot |
Automatically resolve when updating. New 'commit' command -- performs a scan and, if up-to-date, a submit. |
||
#1 | 323 | Thomas Quinot | Thomas' changes to C4. |