1/*
2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3 *
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5 *                                  and others.
6 *
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
9 *
10 * You may distribute under the terms of the GNU General Public License as
11 * specified in the README file that comes with the CVS source distribution.
12 *
13 * Status Information
14 */
15
16#include "cvs.h"
17
18static Dtype status_dirproc PROTO ((void *callerdat, const char *dir,
19				    const char *repos, const char *update_dir,
20				    List *entries));
21static int status_fileproc PROTO ((void *callerdat, struct file_info *finfo));
22static int tag_list_proc PROTO((Node * p, void *closure));
23
24static int local = 0;
25static int long_format = 0;
26static RCSNode *xrcsnode;
27
28static const char *const status_usage[] =
29{
30    "Usage: %s %s [-vlR] [files...]\n",
31    "\t-v\tVerbose format; includes tag information for the file\n",
32    "\t-l\tProcess this directory only (not recursive).\n",
33    "\t-R\tProcess directories recursively.\n",
34    "(Specify the --help global option for a list of other help options)\n",
35    NULL
36};
37
38int
39cvsstatus (argc, argv)
40    int argc;
41    char **argv;
42{
43    int c;
44    int err = 0;
45
46    if (argc == -1)
47	usage (status_usage);
48
49    optind = 0;
50    while ((c = getopt (argc, argv, "+vlR")) != -1)
51    {
52	switch (c)
53	{
54	    case 'v':
55		long_format = 1;
56		break;
57	    case 'l':
58		local = 1;
59		break;
60	    case 'R':
61		local = 0;
62		break;
63	    case '?':
64	    default:
65		usage (status_usage);
66		break;
67	}
68    }
69    argc -= optind;
70    argv += optind;
71
72    wrap_setup ();
73
74#ifdef CLIENT_SUPPORT
75    if (current_parsed_root->isremote)
76    {
77	start_server ();
78
79	ign_setup ();
80
81	if (long_format)
82	    send_arg("-v");
83	if (local)
84	    send_arg("-l");
85	send_arg ("--");
86
87	/* For a while, we tried setting SEND_NO_CONTENTS here so this
88	   could be a fast operation.  That prevents the
89	   server from updating our timestamp if the timestamp is
90	   changed but the file is unmodified.  Worse, it is user-visible
91	   (shows "locally modified" instead of "up to date" if
92	   timestamp is changed but file is not).  And there is no good
93	   workaround (you might not want to run "cvs update"; "cvs -n
94	   update" doesn't update CVS/Entries; "cvs diff --brief" or
95	   something perhaps could be made to work but somehow that
96	   seems nonintuitive to me even if so).  Given that timestamps
97	   seem to have the potential to get munged for any number of
98	   reasons, it seems better to not rely too much on them.  */
99
100	send_files (argc, argv, local, 0, 0);
101
102	send_file_names (argc, argv, SEND_EXPAND_WILD);
103
104	send_to_server ("status\012", 0);
105	err = get_responses_and_close ();
106
107	return err;
108    }
109#endif
110
111    /* start the recursion processor */
112    err = start_recursion (status_fileproc, (FILESDONEPROC) NULL,
113			   status_dirproc, (DIRLEAVEPROC) NULL, NULL,
114			   argc, argv, local,
115			   W_LOCAL, 0, CVS_LOCK_READ, (char *) NULL, 1,
116			   (char *) NULL);
117
118    return (err);
119}
120
121/*
122 * display the status of a file
123 */
124/* ARGSUSED */
125static int
126status_fileproc (callerdat, finfo)
127    void *callerdat;
128    struct file_info *finfo;
129{
130    Ctype status;
131    char *sstat;
132    Vers_TS *vers;
133
134    status = Classify_File (finfo, (char *) NULL, (char *) NULL, (char *) NULL,
135			    1, 0, &vers, 0);
136    sstat = "Classify Error";
137    switch (status)
138    {
139	case T_UNKNOWN:
140	    sstat = "Unknown";
141	    break;
142	case T_CHECKOUT:
143	    sstat = "Needs Checkout";
144	    break;
145	case T_PATCH:
146	    sstat = "Needs Patch";
147	    break;
148	case T_CONFLICT:
149	    sstat = "Unresolved Conflict";
150	    break;
151	case T_ADDED:
152	    sstat = "Locally Added";
153	    break;
154	case T_REMOVED:
155	    sstat = "Locally Removed";
156	    break;
157	case T_MODIFIED:
158	    if (file_has_markers (finfo))
159		sstat = "File had conflicts on merge";
160	    else
161		/* Note that we do not re Register() the file when we spot
162		 * a resolved conflict like update_fileproc() does on the
163		 * premise that status should not alter the sandbox.
164		 */
165		sstat = "Locally Modified";
166	    break;
167	case T_REMOVE_ENTRY:
168	    sstat = "Entry Invalid";
169	    break;
170	case T_UPTODATE:
171	    sstat = "Up-to-date";
172	    break;
173	case T_NEEDS_MERGE:
174	    sstat = "Needs Merge";
175	    break;
176	case T_TITLE:
177	    /* I don't think this case can occur here.  Just print
178	       "Classify Error".  */
179	    break;
180    }
181
182    cvs_output ("\
183===================================================================\n", 0);
184    if (vers->ts_user == NULL)
185    {
186	cvs_output ("File: no file ", 0);
187	cvs_output (finfo->file, 0);
188	cvs_output ("\t\tStatus: ", 0);
189	cvs_output (sstat, 0);
190	cvs_output ("\n\n", 0);
191    }
192    else
193    {
194	char *buf;
195	buf = xmalloc (strlen (finfo->file) + strlen (sstat) + 80);
196	sprintf (buf, "File: %-17s\tStatus: %s\n\n", finfo->file, sstat);
197	cvs_output (buf, 0);
198	free (buf);
199    }
200
201    if (vers->vn_user == NULL)
202    {
203	cvs_output ("   Working revision:\tNo entry for ", 0);
204	cvs_output (finfo->file, 0);
205	cvs_output ("\n", 0);
206    }
207    else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
208	cvs_output ("   Working revision:\tNew file!\n", 0);
209    else
210    {
211	cvs_output ("   Working revision:\t", 0);
212	cvs_output (vers->vn_user, 0);
213	if (!server_active)
214	{
215	    cvs_output ("\t", 0);
216	    cvs_output (vers->ts_rcs, 0);
217	}
218	cvs_output ("\n", 0);
219    }
220
221    if (vers->vn_rcs == NULL)
222	cvs_output ("   Repository revision:\tNo revision control file\n", 0);
223    else
224    {
225	cvs_output ("   Repository revision:\t", 0);
226	cvs_output (vers->vn_rcs, 0);
227	cvs_output ("\t", 0);
228	cvs_output (vers->srcfile->path, 0);
229	cvs_output ("\n", 0);
230    }
231
232    if (vers->entdata)
233    {
234	Entnode *edata;
235
236	edata = vers->entdata;
237	if (edata->tag)
238	{
239	    if (vers->vn_rcs == NULL)
240	    {
241		cvs_output ("   Sticky Tag:\t\t", 0);
242		cvs_output (edata->tag, 0);
243		cvs_output (" - MISSING from RCS file!\n", 0);
244	    }
245	    else
246	    {
247		if (isdigit ((unsigned char) edata->tag[0]))
248		{
249		    cvs_output ("   Sticky Tag:\t\t", 0);
250		    cvs_output (edata->tag, 0);
251		    cvs_output ("\n", 0);
252		}
253		else
254		{
255		    char *branch = NULL;
256
257		    if (RCS_nodeisbranch (finfo->rcs, edata->tag))
258			branch = RCS_whatbranch(finfo->rcs, edata->tag);
259
260		    cvs_output ("   Sticky Tag:\t\t", 0);
261		    cvs_output (edata->tag, 0);
262		    cvs_output (" (", 0);
263		    cvs_output (branch ? "branch" : "revision", 0);
264		    cvs_output (": ", 0);
265		    cvs_output (branch ? branch : vers->vn_rcs, 0);
266		    cvs_output (")\n", 0);
267
268		    if (branch)
269			free (branch);
270		}
271	    }
272	}
273	else if (!really_quiet)
274	    cvs_output ("   Sticky Tag:\t\t(none)\n", 0);
275
276	if (edata->date)
277	{
278	    cvs_output ("   Sticky Date:\t\t", 0);
279	    cvs_output (edata->date, 0);
280	    cvs_output ("\n", 0);
281	}
282	else if (!really_quiet)
283	    cvs_output ("   Sticky Date:\t\t(none)\n", 0);
284
285	if (edata->options && edata->options[0])
286	{
287	    cvs_output ("   Sticky Options:\t", 0);
288	    cvs_output (edata->options, 0);
289	    cvs_output ("\n", 0);
290	}
291	else if (!really_quiet)
292	    cvs_output ("   Sticky Options:\t(none)\n", 0);
293    }
294
295    if (long_format && vers->srcfile)
296    {
297	List *symbols = RCS_symbols(vers->srcfile);
298
299	cvs_output ("\n   Existing Tags:\n", 0);
300	if (symbols)
301	{
302	    xrcsnode = finfo->rcs;
303	    (void) walklist (symbols, tag_list_proc, NULL);
304	}
305	else
306	    cvs_output ("\tNo Tags Exist\n", 0);
307    }
308
309    cvs_output ("\n", 0);
310    freevers_ts (&vers);
311    return (0);
312}
313
314/*
315 * Print a warm fuzzy message
316 */
317/* ARGSUSED */
318static Dtype
319status_dirproc (callerdat, dir, repos, update_dir, entries)
320    void *callerdat;
321    const char *dir;
322    const char *repos;
323    const char *update_dir;
324    List *entries;
325{
326    if (!quiet)
327	error (0, 0, "Examining %s", update_dir);
328    return (R_PROCESS);
329}
330
331/*
332 * Print out a tag and its type
333 */
334static int
335tag_list_proc (p, closure)
336    Node *p;
337    void *closure;
338{
339    char *branch = NULL;
340    char *buf;
341
342    if (RCS_nodeisbranch (xrcsnode, p->key))
343	branch = RCS_whatbranch(xrcsnode, p->key) ;
344
345    buf = xmalloc (80 + strlen (p->key)
346		   + (branch ? strlen (branch) : strlen (p->data)));
347    sprintf (buf, "\t%-25s\t(%s: %s)\n", p->key,
348	     branch ? "branch" : "revision",
349	     branch ? branch : (char *)p->data);
350    cvs_output (buf, 0);
351    free (buf);
352
353    if (branch)
354	free (branch);
355
356    return (0);
357}
358