/*
 *	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);
}
