/*
* 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\t%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_EDIT)
need_resolve = 1;
fp->flag &= ~F_REFRESH;
}
if (fp->children)
{
strcpy(&name[namelen], fp->name);
set_flags(name);
}
}
}
void
check_resolve(void)
{
if (need_resolve)
printf("Conflicts found - run `c4 resolve'.\n");
}
/**************************************************/
static void
do_add(void)
{
recurser(".", F_ADDMAYBE, "???", REP_ALWAYS);
Command("add", (void (*)(char *))0, 1);
recurser(".", F_ADD, "add", REP_NOEXEC);
if (CmdArgs > 0)
CommandDone();
}
/**************************************************/
static void
do_edit(void)
{
Command("edit", (void (*)(char *))0, 1);
recurser(".", F_EDIT, "edit", REP_NOEXEC);
if (CmdArgs > 0)
CommandDone();
}
/**************************************************/
static void
do_revert(void)
{
Command("revert", (void (*)(char *))0, 1);
recurser(".", F_REVERT, "revert", REP_NOEXEC);
if (CmdArgs > 0)
CommandDone();
}
/**************************************************/
static void
do_refresh(void)
{
Command("refresh", (void (*)(char *))0, 1);
recurser(".", F_REFRESH, "refresh", REP_NOEXEC);
if (CmdArgs > 0)
CommandDone();
}
/**************************************************/
static void
do_delete(void)
{
Command("delete", (void (*)(char *))0, 1);
recurser(".", F_REMOVE, "delete", REP_NOEXEC);
if (CmdArgs > 0)
CommandDone();
}
/**************************************************/
static void
do_get(void)
{
Command("get", (void (*)(char *))0, 1);
recurser(".", F_GET, "get", REP_NOEXEC);
if (CmdArgs)
CommandDone();
}
/**************************************************/
/*
* Scan command.
*/
void
Scan(char ** argv)
{
/*
* Collect options.
*/
no_add = 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 incase a file changed
* it's type (ie. from regular to symlink).
*/
do_delete();
do_add();
do_edit();
do_revert();
do_refresh();
check_resolve();
exit(0);
}
/**************************************************/
/*
* Update command.
*/
void
Update(char ** argv)
{
/*
* Collect options.
*/
(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 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();
do_get();
check_resolve();
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();
check_resolve();
exit(0);
}