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 (void *callerdat, const char *dir,
19                             const char *repos, const char *update_dir,
20                             List *entries);
21static int status_fileproc (void *callerdat, struct file_info *finfo);
22static int tag_list_proc (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 (int argc, char **argv)
40{
41    int c;
42    int err = 0;
43
44    if (argc == -1)
45	usage (status_usage);
46
47    getoptreset ();
48    while ((c = getopt (argc, argv, "+vlR")) != -1)
49    {
50	switch (c)
51	{
52	    case 'v':
53		long_format = 1;
54		break;
55	    case 'l':
56		local = 1;
57		break;
58	    case 'R':
59		local = 0;
60		break;
61	    case '?':
62	    default:
63		usage (status_usage);
64		break;
65	}
66    }
67    argc -= optind;
68    argv += optind;
69
70    wrap_setup ();
71
72#ifdef CLIENT_SUPPORT
73    if (current_parsed_root->isremote)
74    {
75	start_server ();
76
77	ign_setup ();
78
79	if (long_format)
80	    send_arg("-v");
81	if (local)
82	    send_arg("-l");
83	send_arg ("--");
84
85	/* For a while, we tried setting SEND_NO_CONTENTS here so this
86	   could be a fast operation.  That prevents the
87	   server from updating our timestamp if the timestamp is
88	   changed but the file is unmodified.  Worse, it is user-visible
89	   (shows "locally modified" instead of "up to date" if
90	   timestamp is changed but file is not).  And there is no good
91	   workaround (you might not want to run "cvs update"; "cvs -n
92	   update" doesn't update CVS/Entries; "cvs diff --brief" or
93	   something perhaps could be made to work but somehow that
94	   seems nonintuitive to me even if so).  Given that timestamps
95	   seem to have the potential to get munged for any number of
96	   reasons, it seems better to not rely too much on them.  */
97
98	send_files (argc, argv, local, 0, 0);
99
100	send_file_names (argc, argv, SEND_EXPAND_WILD);
101
102	send_to_server ("status\012", 0);
103	err = get_responses_and_close ();
104
105	return err;
106    }
107#endif
108
109    /* start the recursion processor */
110    err = start_recursion (status_fileproc, NULL, status_dirproc,
111			   NULL, NULL, argc, argv, local, W_LOCAL,
112			   0, CVS_LOCK_READ, NULL, 1, NULL);
113
114    return (err);
115}
116
117/*
118 * display the status of a file
119 */
120/* ARGSUSED */
121static int
122status_fileproc (void *callerdat, struct file_info *finfo)
123{
124    Ctype status;
125    char *sstat;
126    Vers_TS *vers;
127    Node *node;
128
129    status = Classify_File (finfo, NULL, NULL, NULL, 1, 0, &vers, 0);
130    sstat = "Classify Error";
131    switch (status)
132    {
133	case T_UNKNOWN:
134	    sstat = "Unknown";
135	    break;
136	case T_CHECKOUT:
137	    sstat = "Needs Checkout";
138	    break;
139	case T_PATCH:
140	    sstat = "Needs Patch";
141	    break;
142	case T_CONFLICT:
143	    /* FIXME - This message could be clearer.  It comes up
144	     * when a file exists or has been added in the local sandbox
145	     * and a file of the same name has been committed indepenently to
146	     * the repository from a different sandbox, as well as when a
147	     * timestamp hasn't changed since a merge resulted in conflicts.
148	     * It also comes up whether an update has been attempted or not, so
149	     * technically, I think the double-add case is not actually a
150	     * conflict yet.
151	     */
152	    sstat = "Unresolved Conflict";
153	    break;
154	case T_ADDED:
155	    sstat = "Locally Added";
156	    break;
157	case T_REMOVED:
158	    sstat = "Locally Removed";
159	    break;
160	case T_MODIFIED:
161	    if (file_has_markers (finfo))
162		sstat = "File had conflicts on merge";
163	    else
164		/* Note that we do not re Register() the file when we spot
165		 * a resolved conflict like update_fileproc() does on the
166		 * premise that status should not alter the sandbox.
167		 */
168		sstat = "Locally Modified";
169	    break;
170	case T_REMOVE_ENTRY:
171	    sstat = "Entry Invalid";
172	    break;
173	case T_UPTODATE:
174	    sstat = "Up-to-date";
175	    break;
176	case T_NEEDS_MERGE:
177	    sstat = "Needs Merge";
178	    break;
179	case T_TITLE:
180	    /* I don't think this case can occur here.  Just print
181	       "Classify Error".  */
182	    break;
183    }
184
185    cvs_output ("\
186===================================================================\n", 0);
187    if (vers->ts_user == NULL)
188    {
189	cvs_output ("File: no file ", 0);
190	cvs_output (finfo->file, 0);
191	cvs_output ("\t\tStatus: ", 0);
192	cvs_output (sstat, 0);
193	cvs_output ("\n\n", 0);
194    }
195    else
196    {
197	char *buf;
198	buf = Xasprintf ("File: %-17s\tStatus: %s\n\n", finfo->file, sstat);
199	cvs_output (buf, 0);
200	free (buf);
201    }
202
203    if (vers->vn_user == NULL)
204    {
205	cvs_output ("   Working revision:\tNo entry for ", 0);
206	cvs_output (finfo->file, 0);
207	cvs_output ("\n", 0);
208    }
209    else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
210	cvs_output ("   Working revision:\tNew file!\n", 0);
211    else
212    {
213	cvs_output ("   Working revision:\t", 0);
214	cvs_output (vers->vn_user, 0);
215
216	/* Only add the UTC timezone if there is a time to use. */
217	if (!server_active && strlen (vers->ts_rcs) > 0)
218	{
219	    /* Convert from the asctime() format to ISO 8601 */
220	    char *buf;
221
222	    cvs_output ("\t", 0);
223
224	    /* Allow conversion from CVS/Entries asctime() to ISO 8601 */
225	    buf = Xasprintf ("%s UTC", vers->ts_rcs);
226	    cvs_output_tagged ("date", buf);
227	    free (buf);
228	}
229	cvs_output ("\n", 0);
230    }
231
232    if (vers->vn_rcs == NULL)
233	cvs_output ("   Repository revision:\tNo revision control file\n", 0);
234    else
235    {
236	cvs_output ("   Repository revision:\t", 0);
237	cvs_output (vers->vn_rcs, 0);
238	cvs_output ("\t", 0);
239	cvs_output (vers->srcfile->print_path, 0);
240	cvs_output ("\n", 0);
241
242	node = findnode(vers->srcfile->versions,vers->vn_rcs);
243	if (node)
244	{
245	    RCSVers *v;
246	    v=(RCSVers*)node->data;
247	    node = findnode(v->other_delta,"commitid");
248	    cvs_output("   Commit Identifier:\t", 0);
249	    if(node && node->data)
250	        cvs_output(node->data, 0);
251	    else
252	        cvs_output("(none)",0);
253	    cvs_output("\n",0);
254	}
255    }
256
257    if (vers->entdata)
258    {
259	Entnode *edata;
260
261	edata = vers->entdata;
262	if (edata->tag)
263	{
264	    if (vers->vn_rcs == NULL)
265	    {
266		cvs_output ("   Sticky Tag:\t\t", 0);
267		cvs_output (edata->tag, 0);
268		cvs_output (" - MISSING from RCS file!\n", 0);
269	    }
270	    else
271	    {
272		if (isdigit ((unsigned char) edata->tag[0]))
273		{
274		    cvs_output ("   Sticky Tag:\t\t", 0);
275		    cvs_output (edata->tag, 0);
276		    cvs_output ("\n", 0);
277		}
278		else
279		{
280		    char *branch = NULL;
281
282		    if (RCS_nodeisbranch (finfo->rcs, edata->tag))
283			branch = RCS_whatbranch(finfo->rcs, edata->tag);
284
285		    cvs_output ("   Sticky Tag:\t\t", 0);
286		    cvs_output (edata->tag, 0);
287		    cvs_output (" (", 0);
288		    cvs_output (branch ? "branch" : "revision", 0);
289		    cvs_output (": ", 0);
290		    cvs_output (branch ? branch : vers->vn_rcs, 0);
291		    cvs_output (")\n", 0);
292
293		    if (branch)
294			free (branch);
295		}
296	    }
297	}
298	else if (!really_quiet)
299	    cvs_output ("   Sticky Tag:\t\t(none)\n", 0);
300
301	if (edata->date)
302	{
303	    cvs_output ("   Sticky Date:\t\t", 0);
304	    cvs_output (edata->date, 0);
305	    cvs_output ("\n", 0);
306	}
307	else if (!really_quiet)
308	    cvs_output ("   Sticky Date:\t\t(none)\n", 0);
309
310	if (edata->options && edata->options[0])
311	{
312	    cvs_output ("   Sticky Options:\t", 0);
313	    cvs_output (edata->options, 0);
314	    cvs_output ("\n", 0);
315	}
316	else if (!really_quiet)
317	    cvs_output ("   Sticky Options:\t(none)\n", 0);
318    }
319
320    if (long_format && vers->srcfile)
321    {
322	List *symbols = RCS_symbols(vers->srcfile);
323
324	cvs_output ("\n   Existing Tags:\n", 0);
325	if (symbols)
326	{
327	    xrcsnode = finfo->rcs;
328	    (void) walklist (symbols, tag_list_proc, NULL);
329	}
330	else
331	    cvs_output ("\tNo Tags Exist\n", 0);
332    }
333
334    cvs_output ("\n", 0);
335    freevers_ts (&vers);
336    return (0);
337}
338
339
340
341/*
342 * Print a warm fuzzy message
343 */
344/* ARGSUSED */
345static Dtype
346status_dirproc (void *callerdat, const char *dir, const char *repos,
347                const char *update_dir, List *entries)
348{
349    if (!quiet)
350	error (0, 0, "Examining %s", update_dir);
351    return (R_PROCESS);
352}
353
354
355
356/*
357 * Print out a tag and its type
358 */
359static int
360tag_list_proc (Node *p, void *closure)
361{
362    char *branch = NULL;
363    char *buf;
364
365    if (RCS_nodeisbranch (xrcsnode, p->key))
366	branch = RCS_whatbranch(xrcsnode, p->key) ;
367
368    buf = Xasprintf ("\t%-25s\t(%s: %s)\n", p->key,
369		     branch ? "branch" : "revision",
370		     branch ? branch : (char *)p->data);
371    cvs_output (buf, 0);
372    free (buf);
373
374    if (branch)
375	free (branch);
376
377    return (0);
378}
379