59 pnset_t *manifest;
60 char hgpath[MAXPATHLEN];
61 char root[MAXPATHLEN];
62 unsigned int rootlen;
63 boolean_t rootwarn;
64 } hgdata_t;
65
66 /*
67 * Hooks used to check if a given unreferenced file is known to an SCM
68 * (currently Mercurial and TeamWare).
69 */
70 typedef int checkscm_func_t(const char *, const struct FTW *);
71 typedef void chdirscm_func_t(const char *);
72
73 typedef struct {
74 const char *name;
75 checkscm_func_t *checkfunc;
76 chdirscm_func_t *chdirfunc;
77 } scm_t;
78
79 static checkscm_func_t check_tw, check_hg;
80 static chdirscm_func_t chdir_hg;
81 static int pnset_add(pnset_t *, const char *);
82 static int pnset_check(const pnset_t *, const char *);
83 static void pnset_empty(pnset_t *);
84 static void pnset_free(pnset_t *);
85 static int checkpath(const char *, const struct stat *, int, struct FTW *);
86 static pnset_t *make_exset(const char *);
87 static void warn(const char *, ...);
88 static void die(const char *, ...);
89
90 static const scm_t scms[] = {
91 { "tw", check_tw, NULL },
92 { "teamware", check_tw, NULL },
93 { "hg", check_hg, chdir_hg },
94 { "mercurial", check_hg, chdir_hg },
95 { NULL, NULL, NULL }
96 };
97
98 static const scm_t *scm;
99 static hgdata_t hgdata;
100 static time_t tstamp; /* timestamp to compare files to */
101 static pnset_t *exsetp; /* pathname globs to ignore */
102 static const char *progname;
103
104 int
105 main(int argc, char *argv[])
106 {
107 int c;
108 char path[MAXPATHLEN];
109 char subtree[MAXPATHLEN] = "./";
110 char *tstampfile = ".build.tstamp";
111 struct stat tsstat;
112
113 progname = strrchr(argv[0], '/');
114 if (progname == NULL)
115 progname = argv[0];
116 else
117 progname++;
118
119 while ((c = getopt(argc, argv, "as:t:S:")) != EOF) {
133 case 'S':
134 for (scm = scms; scm->name != NULL; scm++) {
135 if (strcmp(scm->name, optarg) == 0)
136 break;
137 }
138 if (scm->name == NULL)
139 die("unsupported SCM `%s'\n", optarg);
140 break;
141
142 default:
143 case '?':
144 goto usage;
145 }
146 }
147
148 argc -= optind;
149 argv += optind;
150
151 if (argc != 2) {
152 usage: (void) fprintf(stderr, "usage: %s [-s <subtree>] "
153 "[-t <tstampfile>] [-S hg|tw] <srcroot> <exceptfile>\n",
154 progname);
155 return (EXIT_FAILURE);
156 }
157
158 /*
159 * Interpret a relative timestamp path as relative to srcroot.
160 */
161 if (tstampfile[0] == '/')
162 (void) strlcpy(path, tstampfile, MAXPATHLEN);
163 else
164 (void) snprintf(path, MAXPATHLEN, "%s/%s", argv[0], tstampfile);
165
166 if (stat(path, &tsstat) == -1)
167 die("cannot stat timestamp file \"%s\"", path);
168 tstamp = tsstat.st_mtime;
169
170 /*
171 * Create the exception pathname set.
172 */
173 exsetp = make_exset(argv[1]);
184 die("cannot walk tree rooted at \"%s\"\n", argv[0]);
185
186 pnset_empty(exsetp);
187 return (EXIT_SUCCESS);
188 }
189
190 /*
191 * Load and return a pnset for the manifest for the Mercurial repo at `hgroot'.
192 */
193 static pnset_t *
194 load_manifest(const char *hgroot)
195 {
196 FILE *fp = NULL;
197 char *hgcmd = NULL;
198 char *newline;
199 pnset_t *pnsetp;
200 char path[MAXPATHLEN];
201
202 pnsetp = calloc(sizeof (pnset_t), 1);
203 if (pnsetp == NULL ||
204 asprintf(&hgcmd, "/usr/bin/hg manifest -R %s", hgroot) == -1)
205 goto fail;
206
207 fp = popen(hgcmd, "r");
208 if (fp == NULL)
209 goto fail;
210
211 while (fgets(path, sizeof (path), fp) != NULL) {
212 newline = strrchr(path, '\n');
213 if (newline != NULL)
214 *newline = '\0';
215
216 if (pnset_add(pnsetp, path) == 0)
217 goto fail;
218 }
219
220 (void) pclose(fp);
221 free(hgcmd);
222 return (pnsetp);
223 fail:
224 warn("cannot load hg manifest at %s", hgroot);
225 if (fp != NULL)
226 (void) pclose(fp);
227 free(hgcmd);
228 pnset_free(pnsetp);
229 return (NULL);
230 }
231
232 /*
233 * If necessary, change our active manifest to be appropriate for `path'.
234 */
235 static void
236 chdir_hg(const char *path)
237 {
238 char hgpath[MAXPATHLEN];
239 char basepath[MAXPATHLEN];
240 char *slash;
241
242 (void) snprintf(hgpath, MAXPATHLEN, "%s/.hg", path);
243
244 /*
245 * Change our active manifest if any one of the following is true:
246 *
247 * 1. No manifest is loaded. Find the nearest hgroot to load from.
248 *
249 * 2. A manifest is loaded, but we've moved into a directory with
250 * its own hgroot (e.g., usr/closed). Load from its hgroot.
251 *
291 if (hgdata.root[strlen(hgdata.root) - 1] != '/')
292 (void) strlcat(hgdata.root, "/", MAXPATHLEN);
293 hgdata.rootlen = strlen(hgdata.root);
294 }
295 }
296
297 /*
298 * Check if a file is under Mercurial control by checking against the manifest.
299 */
300 /* ARGSUSED */
301 static int
302 check_hg(const char *path, const struct FTW *ftwp)
303 {
304 /*
305 * The manifest paths are relative to the manifest root; skip past it.
306 */
307 path += hgdata.rootlen;
308
309 return (hgdata.manifest != NULL && pnset_check(hgdata.manifest, path));
310 }
311
312 /*
313 * Check if a file is under TeamWare control by checking for its corresponding
314 * SCCS "s-dot" file.
315 */
316 static int
317 check_tw(const char *path, const struct FTW *ftwp)
318 {
319 char sccspath[MAXPATHLEN];
320
321 (void) snprintf(sccspath, MAXPATHLEN, "%.*s/SCCS/s.%s", ftwp->base,
322 path, path + ftwp->base);
323
324 return (access(sccspath, F_OK) == 0);
325 }
326
327 /*
328 * Using `exceptfile' and a built-in list of exceptions, build and return a
329 * pnset_t consisting of all of the pathnames globs which are allowed to be
330 * unreferenced in the source tree.
|
59 pnset_t *manifest;
60 char hgpath[MAXPATHLEN];
61 char root[MAXPATHLEN];
62 unsigned int rootlen;
63 boolean_t rootwarn;
64 } hgdata_t;
65
66 /*
67 * Hooks used to check if a given unreferenced file is known to an SCM
68 * (currently Mercurial and TeamWare).
69 */
70 typedef int checkscm_func_t(const char *, const struct FTW *);
71 typedef void chdirscm_func_t(const char *);
72
73 typedef struct {
74 const char *name;
75 checkscm_func_t *checkfunc;
76 chdirscm_func_t *chdirfunc;
77 } scm_t;
78
79 static checkscm_func_t check_tw, check_hg, check_git;
80 static chdirscm_func_t chdir_hg, chdir_git;
81 static int pnset_add(pnset_t *, const char *);
82 static int pnset_check(const pnset_t *, const char *);
83 static void pnset_empty(pnset_t *);
84 static void pnset_free(pnset_t *);
85 static int checkpath(const char *, const struct stat *, int, struct FTW *);
86 static pnset_t *make_exset(const char *);
87 static void warn(const char *, ...);
88 static void die(const char *, ...);
89
90 static const scm_t scms[] = {
91 { "tw", check_tw, NULL },
92 { "teamware", check_tw, NULL },
93 { "hg", check_hg, chdir_hg },
94 { "mercurial", check_hg, chdir_hg },
95 { "git", check_git, chdir_git },
96 { NULL, NULL, NULL }
97 };
98
99 static const scm_t *scm;
100 static hgdata_t hgdata;
101 static pnset_t *gitmanifest = NULL;
102 static time_t tstamp; /* timestamp to compare files to */
103 static pnset_t *exsetp; /* pathname globs to ignore */
104 static const char *progname;
105
106 int
107 main(int argc, char *argv[])
108 {
109 int c;
110 char path[MAXPATHLEN];
111 char subtree[MAXPATHLEN] = "./";
112 char *tstampfile = ".build.tstamp";
113 struct stat tsstat;
114
115 progname = strrchr(argv[0], '/');
116 if (progname == NULL)
117 progname = argv[0];
118 else
119 progname++;
120
121 while ((c = getopt(argc, argv, "as:t:S:")) != EOF) {
135 case 'S':
136 for (scm = scms; scm->name != NULL; scm++) {
137 if (strcmp(scm->name, optarg) == 0)
138 break;
139 }
140 if (scm->name == NULL)
141 die("unsupported SCM `%s'\n", optarg);
142 break;
143
144 default:
145 case '?':
146 goto usage;
147 }
148 }
149
150 argc -= optind;
151 argv += optind;
152
153 if (argc != 2) {
154 usage: (void) fprintf(stderr, "usage: %s [-s <subtree>] "
155 "[-t <tstampfile>] [-S hg|tw|git] <srcroot> <exceptfile>\n",
156 progname);
157 return (EXIT_FAILURE);
158 }
159
160 /*
161 * Interpret a relative timestamp path as relative to srcroot.
162 */
163 if (tstampfile[0] == '/')
164 (void) strlcpy(path, tstampfile, MAXPATHLEN);
165 else
166 (void) snprintf(path, MAXPATHLEN, "%s/%s", argv[0], tstampfile);
167
168 if (stat(path, &tsstat) == -1)
169 die("cannot stat timestamp file \"%s\"", path);
170 tstamp = tsstat.st_mtime;
171
172 /*
173 * Create the exception pathname set.
174 */
175 exsetp = make_exset(argv[1]);
186 die("cannot walk tree rooted at \"%s\"\n", argv[0]);
187
188 pnset_empty(exsetp);
189 return (EXIT_SUCCESS);
190 }
191
192 /*
193 * Load and return a pnset for the manifest for the Mercurial repo at `hgroot'.
194 */
195 static pnset_t *
196 load_manifest(const char *hgroot)
197 {
198 FILE *fp = NULL;
199 char *hgcmd = NULL;
200 char *newline;
201 pnset_t *pnsetp;
202 char path[MAXPATHLEN];
203
204 pnsetp = calloc(sizeof (pnset_t), 1);
205 if (pnsetp == NULL ||
206 asprintf(&hgcmd, "hg manifest -R %s", hgroot) == -1)
207 goto fail;
208
209 fp = popen(hgcmd, "r");
210 if (fp == NULL)
211 goto fail;
212
213 while (fgets(path, sizeof (path), fp) != NULL) {
214 newline = strrchr(path, '\n');
215 if (newline != NULL)
216 *newline = '\0';
217
218 if (pnset_add(pnsetp, path) == 0)
219 goto fail;
220 }
221
222 (void) pclose(fp);
223 free(hgcmd);
224 return (pnsetp);
225 fail:
226 warn("cannot load hg manifest at %s", hgroot);
227 if (fp != NULL)
228 (void) pclose(fp);
229 free(hgcmd);
230 pnset_free(pnsetp);
231 return (NULL);
232 }
233
234 static void
235 chdir_git(const char *path)
236 {
237 FILE *fp = NULL;
238 char *gitcmd = NULL;
239 char *newline;
240 char fn[MAXPATHLEN];
241 pnset_t *pnsetp;
242
243 pnsetp = calloc(sizeof (pnset_t), 1);
244 if ((pnsetp == NULL) ||
245 (asprintf(&gitcmd, "git ls-files %s", path) == -1))
246 goto fail;
247
248 if ((fp = popen(gitcmd, "r")) == NULL)
249 goto fail;
250
251 while (fgets(fn, sizeof (fn), fp) != NULL) {
252 if ((newline = strrchr(fn, '\n')) != NULL)
253 *newline = '\0';
254
255 if (pnset_add(pnsetp, fn) == 0)
256 goto fail;
257 }
258
259 (void) pclose(fp);
260 free(gitcmd);
261 gitmanifest = pnsetp;
262 return;
263 fail:
264 warn("cannot load git manifest");
265 if (fp != NULL)
266 (void) pclose(fp);
267 if (pnsetp != NULL)
268 free(pnsetp);
269 if (gitcmd != NULL)
270 free(gitcmd);
271 }
272
273 /*
274 * If necessary, change our active manifest to be appropriate for `path'.
275 */
276 static void
277 chdir_hg(const char *path)
278 {
279 char hgpath[MAXPATHLEN];
280 char basepath[MAXPATHLEN];
281 char *slash;
282
283 (void) snprintf(hgpath, MAXPATHLEN, "%s/.hg", path);
284
285 /*
286 * Change our active manifest if any one of the following is true:
287 *
288 * 1. No manifest is loaded. Find the nearest hgroot to load from.
289 *
290 * 2. A manifest is loaded, but we've moved into a directory with
291 * its own hgroot (e.g., usr/closed). Load from its hgroot.
292 *
332 if (hgdata.root[strlen(hgdata.root) - 1] != '/')
333 (void) strlcat(hgdata.root, "/", MAXPATHLEN);
334 hgdata.rootlen = strlen(hgdata.root);
335 }
336 }
337
338 /*
339 * Check if a file is under Mercurial control by checking against the manifest.
340 */
341 /* ARGSUSED */
342 static int
343 check_hg(const char *path, const struct FTW *ftwp)
344 {
345 /*
346 * The manifest paths are relative to the manifest root; skip past it.
347 */
348 path += hgdata.rootlen;
349
350 return (hgdata.manifest != NULL && pnset_check(hgdata.manifest, path));
351 }
352 /* ARGSUSED */
353 static int
354 check_git(const char *path, const struct FTW *ftwp)
355 {
356 path += 2; /* Skip "./" */
357 return (gitmanifest != NULL && pnset_check(gitmanifest, path));
358 }
359
360 /*
361 * Check if a file is under TeamWare control by checking for its corresponding
362 * SCCS "s-dot" file.
363 */
364 static int
365 check_tw(const char *path, const struct FTW *ftwp)
366 {
367 char sccspath[MAXPATHLEN];
368
369 (void) snprintf(sccspath, MAXPATHLEN, "%.*s/SCCS/s.%s", ftwp->base,
370 path, path + ftwp->base);
371
372 return (access(sccspath, F_OK) == 0);
373 }
374
375 /*
376 * Using `exceptfile' and a built-in list of exceptions, build and return a
377 * pnset_t consisting of all of the pathnames globs which are allowed to be
378 * unreferenced in the source tree.
|