xref: /netbsd-src/external/gpl2/xcvs/dist/src/status.c (revision 413d532bcc3f62d122e56d92e13ac64825a40baf)
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 
18 static Dtype status_dirproc (void *callerdat, const char *dir,
19                              const char *repos, const char *update_dir,
20                              List *entries);
21 static int status_fileproc (void *callerdat, struct file_info *finfo);
22 static int tag_list_proc (Node * p, void *closure);
23 
24 static int local = 0;
25 static int long_format = 0;
26 static RCSNode *xrcsnode;
27 
28 static 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 
38 int
39 cvsstatus (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 */
121 static int
122 status_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 
131 /* cvsacl patch */
132 #ifdef SERVER_SUPPORT
133     if (use_cvs_acl /* && server_active */)
134     {
135 	if (!access_allowed (finfo->file, finfo->repository, vers->tag, 5,
136 			     NULL, NULL, 1))
137 	{
138 	    if (stop_at_first_permission_denied)
139 		error (1, 0, "permission denied for %s",
140 		       Short_Repository (finfo->repository));
141 	    else
142 		error (0, 0, "permission denied for %s/%s",
143 		       Short_Repository (finfo->repository), finfo->file);
144 
145 	    return (0);
146 	}
147     }
148 #endif
149 
150     sstat = "Classify Error";
151     switch (status)
152     {
153 	case T_UNKNOWN:
154 	    sstat = "Unknown";
155 	    break;
156 	case T_CHECKOUT:
157 	    sstat = "Needs Checkout";
158 	    break;
159 	case T_PATCH:
160 	    sstat = "Needs Patch";
161 	    break;
162 	case T_CONFLICT:
163 	    /* FIXME - This message could be clearer.  It comes up
164 	     * when a file exists or has been added in the local sandbox
165 	     * and a file of the same name has been committed indepenently to
166 	     * the repository from a different sandbox, as well as when a
167 	     * timestamp hasn't changed since a merge resulted in conflicts.
168 	     * It also comes up whether an update has been attempted or not, so
169 	     * technically, I think the double-add case is not actually a
170 	     * conflict yet.
171 	     */
172 	    sstat = "Unresolved Conflict";
173 	    break;
174 	case T_ADDED:
175 	    sstat = "Locally Added";
176 	    break;
177 	case T_REMOVED:
178 	    sstat = "Locally Removed";
179 	    break;
180 	case T_MODIFIED:
181 	    if (file_has_markers (finfo))
182 		sstat = "File had conflicts on merge";
183 	    else
184 		/* Note that we do not re Register() the file when we spot
185 		 * a resolved conflict like update_fileproc() does on the
186 		 * premise that status should not alter the sandbox.
187 		 */
188 		sstat = "Locally Modified";
189 	    break;
190 	case T_REMOVE_ENTRY:
191 	    sstat = "Entry Invalid";
192 	    break;
193 	case T_UPTODATE:
194 	    sstat = "Up-to-date";
195 	    break;
196 	case T_NEEDS_MERGE:
197 	    sstat = "Needs Merge";
198 	    break;
199 	case T_TITLE:
200 	    /* I don't think this case can occur here.  Just print
201 	       "Classify Error".  */
202 	    break;
203     }
204 
205     cvs_output ("\
206 ===================================================================\n", 0);
207     if (vers->ts_user == NULL)
208     {
209 	cvs_output ("File: no file ", 0);
210 	cvs_output (finfo->file, 0);
211 	cvs_output ("\t\tStatus: ", 0);
212 	cvs_output (sstat, 0);
213 	cvs_output ("\n\n", 0);
214     }
215     else
216     {
217 	char *buf;
218 	buf = Xasprintf ("File: %-17s\tStatus: %s\n\n", finfo->file, sstat);
219 	cvs_output (buf, 0);
220 	free (buf);
221     }
222 
223     if (vers->vn_user == NULL)
224     {
225 	cvs_output ("   Working revision:\tNo entry for ", 0);
226 	cvs_output (finfo->file, 0);
227 	cvs_output ("\n", 0);
228     }
229     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
230 	cvs_output ("   Working revision:\tNew file!\n", 0);
231     else
232     {
233 	cvs_output ("   Working revision:\t", 0);
234 	cvs_output (vers->vn_user, 0);
235 
236 	/* Only add the UTC timezone if there is a time to use. */
237 	if (!server_active && strlen (vers->ts_rcs) > 0)
238 	{
239 	    /* Convert from the asctime() format to ISO 8601 */
240 	    char *buf;
241 
242 	    cvs_output ("\t", 0);
243 
244 	    /* Allow conversion from CVS/Entries asctime() to ISO 8601 */
245 	    buf = Xasprintf ("%s UTC", vers->ts_rcs);
246 	    cvs_output_tagged ("date", buf);
247 	    free (buf);
248 	}
249 	cvs_output ("\n", 0);
250     }
251 
252     if (vers->vn_rcs == NULL)
253 	cvs_output ("   Repository revision:\tNo revision control file\n", 0);
254     else
255     {
256 	cvs_output ("   Repository revision:\t", 0);
257 	cvs_output (vers->vn_rcs, 0);
258 	cvs_output ("\t", 0);
259 	cvs_output (vers->srcfile->print_path, 0);
260 	cvs_output ("\n", 0);
261 
262 	node = findnode(vers->srcfile->versions,vers->vn_rcs);
263 	if (node)
264 	{
265 	    RCSVers *v;
266 	    v=(RCSVers*)node->data;
267 	    node = findnode(v->other_delta,"commitid");
268 	    cvs_output("   Commit Identifier:\t", 0);
269 	    if(node && node->data)
270 	        cvs_output(node->data, 0);
271 	    else
272 	        cvs_output("(none)",0);
273 	    cvs_output("\n",0);
274 	}
275     }
276 
277     if (vers->entdata)
278     {
279 	Entnode *edata;
280 
281 	edata = vers->entdata;
282 	if (edata->tag)
283 	{
284 	    if (vers->vn_rcs == NULL)
285 	    {
286 		cvs_output ("   Sticky Tag:\t\t", 0);
287 		cvs_output (edata->tag, 0);
288 		cvs_output (" - MISSING from RCS file!\n", 0);
289 	    }
290 	    else
291 	    {
292 		if (isdigit ((unsigned char) edata->tag[0]))
293 		{
294 		    cvs_output ("   Sticky Tag:\t\t", 0);
295 		    cvs_output (edata->tag, 0);
296 		    cvs_output ("\n", 0);
297 		}
298 		else
299 		{
300 		    char *branch = NULL;
301 
302 		    if (RCS_nodeisbranch (finfo->rcs, edata->tag))
303 			branch = RCS_whatbranch(finfo->rcs, edata->tag);
304 
305 		    cvs_output ("   Sticky Tag:\t\t", 0);
306 		    cvs_output (edata->tag, 0);
307 		    cvs_output (" (", 0);
308 		    cvs_output (branch ? "branch" : "revision", 0);
309 		    cvs_output (": ", 0);
310 		    cvs_output (branch ? branch : vers->vn_rcs, 0);
311 		    cvs_output (")\n", 0);
312 
313 		    if (branch)
314 			free (branch);
315 		}
316 	    }
317 	}
318 	else if (!really_quiet)
319 	    cvs_output ("   Sticky Tag:\t\t(none)\n", 0);
320 
321 	if (edata->date)
322 	{
323 	    cvs_output ("   Sticky Date:\t\t", 0);
324 	    cvs_output (edata->date, 0);
325 	    cvs_output ("\n", 0);
326 	}
327 	else if (!really_quiet)
328 	    cvs_output ("   Sticky Date:\t\t(none)\n", 0);
329 
330 	if (edata->options && edata->options[0])
331 	{
332 	    cvs_output ("   Sticky Options:\t", 0);
333 	    cvs_output (edata->options, 0);
334 	    cvs_output ("\n", 0);
335 	}
336 	else if (!really_quiet)
337 	    cvs_output ("   Sticky Options:\t(none)\n", 0);
338     }
339 
340     if (long_format && vers->srcfile)
341     {
342 	List *symbols = RCS_symbols(vers->srcfile);
343 
344 	cvs_output ("\n   Existing Tags:\n", 0);
345 	if (symbols)
346 	{
347 	    xrcsnode = finfo->rcs;
348 	    (void) walklist (symbols, tag_list_proc, NULL);
349 	}
350 	else
351 	    cvs_output ("\tNo Tags Exist\n", 0);
352     }
353 
354     cvs_output ("\n", 0);
355     freevers_ts (&vers);
356     return (0);
357 }
358 
359 
360 
361 /*
362  * Print a warm fuzzy message
363  */
364 /* ARGSUSED */
365 static Dtype
366 status_dirproc (void *callerdat, const char *dir, const char *repos,
367                 const char *update_dir, List *entries)
368 {
369     if (!quiet)
370 	error (0, 0, "Examining %s", update_dir);
371     return (R_PROCESS);
372 }
373 
374 
375 
376 /*
377  * Print out a tag and its type
378  */
379 static int
380 tag_list_proc (Node *p, void *closure)
381 {
382     char *branch = NULL;
383     char *buf;
384 
385     if (RCS_nodeisbranch (xrcsnode, p->key))
386 	branch = RCS_whatbranch(xrcsnode, p->key) ;
387 
388     buf = Xasprintf ("\t%-25s\t(%s: %s)\n", p->key,
389 		     branch ? "branch" : "revision",
390 		     branch ? branch : (char *)p->data);
391     cvs_output (buf, 0);
392     free (buf);
393 
394     if (branch)
395 	free (branch);
396 
397     return (0);
398 }
399