##// END OF EJS Templates
chg: pass --no-profile to disable profiling when starting hg serve...
Kyle Lippincott -
r47788:8138092f default draft
parent child Browse files
Show More
@@ -1,551 +1,552 b''
1 1 /*
2 2 * A fast client for Mercurial command server
3 3 *
4 4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 5 *
6 6 * This software may be used and distributed according to the terms of the
7 7 * GNU General Public License version 2 or any later version.
8 8 */
9 9
10 10 #include <assert.h>
11 11 #include <dirent.h>
12 12 #include <errno.h>
13 13 #include <fcntl.h>
14 14 #include <signal.h>
15 15 #include <stdio.h>
16 16 #include <stdlib.h>
17 17 #include <string.h>
18 18 #include <sys/file.h>
19 19 #include <sys/stat.h>
20 20 #include <sys/types.h>
21 21 #include <sys/un.h>
22 22 #include <sys/wait.h>
23 23 #include <time.h>
24 24 #include <unistd.h>
25 25
26 26 #include "hgclient.h"
27 27 #include "procutil.h"
28 28 #include "util.h"
29 29
30 30 #ifndef PATH_MAX
31 31 #define PATH_MAX 4096
32 32 #endif
33 33
34 34 struct cmdserveropts {
35 35 char sockname[PATH_MAX];
36 36 char initsockname[PATH_MAX];
37 37 char redirectsockname[PATH_MAX];
38 38 size_t argsize;
39 39 const char **args;
40 40 };
41 41
42 42 static void initcmdserveropts(struct cmdserveropts *opts)
43 43 {
44 44 memset(opts, 0, sizeof(struct cmdserveropts));
45 45 }
46 46
47 47 static void freecmdserveropts(struct cmdserveropts *opts)
48 48 {
49 49 free(opts->args);
50 50 opts->args = NULL;
51 51 opts->argsize = 0;
52 52 }
53 53
54 54 /*
55 55 * Test if an argument is a sensitive flag that should be passed to the server.
56 56 * Return 0 if not, otherwise the number of arguments starting from the current
57 57 * one that should be passed to the server.
58 58 */
59 59 static size_t testsensitiveflag(const char *arg)
60 60 {
61 61 static const struct {
62 62 const char *name;
63 63 size_t narg;
64 64 } flags[] = {
65 65 {"--config", 1}, {"--cwd", 1}, {"--repo", 1},
66 66 {"--repository", 1}, {"--traceback", 0}, {"-R", 1},
67 67 };
68 68 size_t i;
69 69 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
70 70 size_t len = strlen(flags[i].name);
71 71 size_t narg = flags[i].narg;
72 72 if (memcmp(arg, flags[i].name, len) == 0) {
73 73 if (arg[len] == '\0') {
74 74 /* --flag (value) */
75 75 return narg + 1;
76 76 } else if (arg[len] == '=' && narg > 0) {
77 77 /* --flag=value */
78 78 return 1;
79 79 } else if (flags[i].name[1] != '-') {
80 80 /* short flag */
81 81 return 1;
82 82 }
83 83 }
84 84 }
85 85 return 0;
86 86 }
87 87
88 88 /*
89 89 * Parse argv[] and put sensitive flags to opts->args
90 90 */
91 91 static void setcmdserverargs(struct cmdserveropts *opts, int argc,
92 92 const char *argv[])
93 93 {
94 94 size_t i, step;
95 95 opts->argsize = 0;
96 96 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
97 97 if (!argv[i])
98 98 continue; /* pass clang-analyse */
99 99 if (strcmp(argv[i], "--") == 0)
100 100 break;
101 101 size_t n = testsensitiveflag(argv[i]);
102 102 if (n == 0 || i + n > (size_t)argc)
103 103 continue;
104 104 opts->args =
105 105 reallocx(opts->args, (n + opts->argsize) * sizeof(char *));
106 106 memcpy(opts->args + opts->argsize, argv + i,
107 107 sizeof(char *) * n);
108 108 opts->argsize += n;
109 109 step = n;
110 110 }
111 111 }
112 112
113 113 static void preparesockdir(const char *sockdir)
114 114 {
115 115 int r;
116 116 r = mkdir(sockdir, 0700);
117 117 if (r < 0 && errno != EEXIST)
118 118 abortmsgerrno("cannot create sockdir %s", sockdir);
119 119
120 120 struct stat st;
121 121 r = lstat(sockdir, &st);
122 122 if (r < 0)
123 123 abortmsgerrno("cannot stat %s", sockdir);
124 124 if (!S_ISDIR(st.st_mode))
125 125 abortmsg("cannot create sockdir %s (file exists)", sockdir);
126 126 if (st.st_uid != geteuid() || st.st_mode & 0077)
127 127 abortmsg("insecure sockdir %s", sockdir);
128 128 }
129 129
130 130 /*
131 131 * Check if a socket directory exists and is only owned by the current user.
132 132 * Return 1 if so, 0 if not. This is used to check if XDG_RUNTIME_DIR can be
133 133 * used or not. According to the specification [1], XDG_RUNTIME_DIR should be
134 134 * ignored if the directory is not owned by the user with mode 0700.
135 135 * [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
136 136 */
137 137 static int checkruntimedir(const char *sockdir)
138 138 {
139 139 struct stat st;
140 140 int r = lstat(sockdir, &st);
141 141 if (r < 0) /* ex. does not exist */
142 142 return 0;
143 143 if (!S_ISDIR(st.st_mode)) /* ex. is a file, not a directory */
144 144 return 0;
145 145 return st.st_uid == geteuid() && (st.st_mode & 0777) == 0700;
146 146 }
147 147
148 148 static void getdefaultsockdir(char sockdir[], size_t size)
149 149 {
150 150 /* by default, put socket file in secure directory
151 151 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
152 152 * (permission of socket file may be ignored on some Unices) */
153 153 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
154 154 int r;
155 155 if (runtimedir && checkruntimedir(runtimedir)) {
156 156 r = snprintf(sockdir, size, "%s/chg", runtimedir);
157 157 } else {
158 158 const char *tmpdir = getenv("TMPDIR");
159 159 if (!tmpdir)
160 160 tmpdir = "/tmp";
161 161 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
162 162 }
163 163 if (r < 0 || (size_t)r >= size)
164 164 abortmsg("too long TMPDIR (r = %d)", r);
165 165 }
166 166
167 167 static void setcmdserveropts(struct cmdserveropts *opts)
168 168 {
169 169 int r;
170 170 char sockdir[PATH_MAX];
171 171 const char *envsockname = getenv("CHGSOCKNAME");
172 172 if (!envsockname) {
173 173 getdefaultsockdir(sockdir, sizeof(sockdir));
174 174 preparesockdir(sockdir);
175 175 }
176 176
177 177 const char *basename = (envsockname) ? envsockname : sockdir;
178 178 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
179 179 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
180 180 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
181 181 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
182 182 r = snprintf(opts->initsockname, sizeof(opts->initsockname), "%s.%u",
183 183 opts->sockname, (unsigned)getpid());
184 184 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
185 185 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
186 186 }
187 187
188 188 /* If the current program is, say, /a/b/c/chg, returns /a/b/c/hg. */
189 189 static char *getrelhgcmd(void)
190 190 {
191 191 ssize_t n;
192 192 char *res, *slash;
193 193 int maxsize = 4096;
194 194 res = malloc(maxsize);
195 195 if (res == NULL)
196 196 goto cleanup;
197 197 n = readlink("/proc/self/exe", res, maxsize);
198 198 if (n < 0 || n >= maxsize)
199 199 goto cleanup;
200 200 res[n] = '\0';
201 201 slash = strrchr(res, '/');
202 202 if (slash == NULL)
203 203 goto cleanup;
204 204 /* 4 is strlen("/hg") + nul byte */
205 205 if (slash + 4 >= res + maxsize)
206 206 goto cleanup;
207 207 memcpy(slash, "/hg", 4);
208 208 return res;
209 209 cleanup:
210 210 free(res);
211 211 return NULL;
212 212 }
213 213
214 214 static const char *gethgcmd(void)
215 215 {
216 216 static const char *hgcmd = NULL;
217 217 #ifdef HGPATHREL
218 218 int tryrelhgcmd = 1;
219 219 #else
220 220 int tryrelhgcmd = 0;
221 221 #endif
222 222 if (!hgcmd) {
223 223 hgcmd = getenv("CHGHG");
224 224 if (!hgcmd || hgcmd[0] == '\0')
225 225 hgcmd = getenv("HG");
226 226 if (tryrelhgcmd && (!hgcmd || hgcmd[0] == '\0'))
227 227 hgcmd = getrelhgcmd();
228 228 if (!hgcmd || hgcmd[0] == '\0')
229 229 #ifdef HGPATH
230 230 hgcmd = (HGPATH);
231 231 #else
232 232 hgcmd = "hg";
233 233 #endif
234 234 }
235 235 return hgcmd;
236 236 }
237 237
238 238 static void execcmdserver(const struct cmdserveropts *opts)
239 239 {
240 240 const char *hgcmd = gethgcmd();
241 241
242 242 const char *baseargv[] = {
243 243 hgcmd,
244 244 "serve",
245 "--no-profile",
245 246 "--cmdserver",
246 247 "chgunix",
247 248 "--address",
248 249 opts->initsockname,
249 250 "--daemon-postexec",
250 251 "chdir:/",
251 252 };
252 253 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
253 254 size_t argsize = baseargvsize + opts->argsize + 1;
254 255
255 256 const char **argv = mallocx(sizeof(char *) * argsize);
256 257 memcpy(argv, baseargv, sizeof(baseargv));
257 258 if (opts->args) {
258 259 size_t size = sizeof(char *) * opts->argsize;
259 260 memcpy(argv + baseargvsize, opts->args, size);
260 261 }
261 262 argv[argsize - 1] = NULL;
262 263
263 264 const char *lc_ctype_env = getenv("LC_CTYPE");
264 265 if (lc_ctype_env == NULL) {
265 266 if (putenv("CHG_CLEAR_LC_CTYPE=") != 0)
266 267 abortmsgerrno("failed to putenv CHG_CLEAR_LC_CTYPE");
267 268 } else {
268 269 if (setenv("CHGORIG_LC_CTYPE", lc_ctype_env, 1) != 0) {
269 270 abortmsgerrno("failed to setenv CHGORIG_LC_CTYPE");
270 271 }
271 272 }
272 273
273 274 /* close any open files to avoid hanging locks */
274 275 DIR *dp = opendir("/proc/self/fd");
275 276 if (dp != NULL) {
276 277 debugmsg("closing files based on /proc contents");
277 278 struct dirent *de;
278 279 while ((de = readdir(dp))) {
279 280 errno = 0;
280 281 char *end;
281 282 long fd_value = strtol(de->d_name, &end, 10);
282 283 if (end == de->d_name) {
283 284 /* unable to convert to int (. or ..) */
284 285 continue;
285 286 }
286 287 if (errno == ERANGE) {
287 288 debugmsg("tried to parse %s, but range error "
288 289 "occurred",
289 290 de->d_name);
290 291 continue;
291 292 }
292 293 if (fd_value > STDERR_FILENO && fd_value != dirfd(dp)) {
293 294 debugmsg("closing fd %ld", fd_value);
294 295 int res = close(fd_value);
295 296 if (res) {
296 297 debugmsg("tried to close fd %ld: %d "
297 298 "(errno: %d)",
298 299 fd_value, res, errno);
299 300 }
300 301 }
301 302 }
302 303 closedir(dp);
303 304 }
304 305
305 306 if (putenv("CHGINTERNALMARK=") != 0)
306 307 abortmsgerrno("failed to putenv");
307 308 if (execvp(hgcmd, (char **)argv) < 0)
308 309 abortmsgerrno("failed to exec cmdserver");
309 310 free(argv);
310 311 }
311 312
312 313 /* Retry until we can connect to the server. Give up after some time. */
313 314 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
314 315 {
315 316 static const struct timespec sleepreq = {0, 10 * 1000000};
316 317 int pst = 0;
317 318
318 319 debugmsg("try connect to %s repeatedly", opts->initsockname);
319 320
320 321 unsigned int timeoutsec = 60; /* default: 60 seconds */
321 322 const char *timeoutenv = getenv("CHGTIMEOUT");
322 323 if (timeoutenv)
323 324 sscanf(timeoutenv, "%u", &timeoutsec);
324 325
325 326 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
326 327 hgclient_t *hgc = hgc_open(opts->initsockname);
327 328 if (hgc) {
328 329 debugmsg("rename %s to %s", opts->initsockname,
329 330 opts->sockname);
330 331 int r = rename(opts->initsockname, opts->sockname);
331 332 if (r != 0)
332 333 abortmsgerrno("cannot rename");
333 334 return hgc;
334 335 }
335 336
336 337 if (pid > 0) {
337 338 /* collect zombie if child process fails to start */
338 339 int r = waitpid(pid, &pst, WNOHANG);
339 340 if (r != 0)
340 341 goto cleanup;
341 342 }
342 343
343 344 nanosleep(&sleepreq, NULL);
344 345 }
345 346
346 347 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
347 348 return NULL;
348 349
349 350 cleanup:
350 351 if (WIFEXITED(pst)) {
351 352 if (WEXITSTATUS(pst) == 0)
352 353 abortmsg("could not connect to cmdserver "
353 354 "(exited with status 0)");
354 355 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
355 356 exit(WEXITSTATUS(pst));
356 357 } else if (WIFSIGNALED(pst)) {
357 358 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
358 359 } else {
359 360 abortmsg("error while waiting for cmdserver");
360 361 }
361 362 return NULL;
362 363 }
363 364
364 365 /* Connect to a cmdserver. Will start a new server on demand. */
365 366 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
366 367 {
367 368 const char *sockname =
368 369 opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname;
369 370 debugmsg("try connect to %s", sockname);
370 371 hgclient_t *hgc = hgc_open(sockname);
371 372 if (hgc)
372 373 return hgc;
373 374
374 375 /* prevent us from being connected to an outdated server: we were
375 376 * told by a server to redirect to opts->redirectsockname and that
376 377 * address does not work. we do not want to connect to the server
377 378 * again because it will probably tell us the same thing. */
378 379 if (sockname == opts->redirectsockname)
379 380 unlink(opts->sockname);
380 381
381 382 debugmsg("start cmdserver at %s", opts->initsockname);
382 383
383 384 pid_t pid = fork();
384 385 if (pid < 0)
385 386 abortmsg("failed to fork cmdserver process");
386 387 if (pid == 0) {
387 388 execcmdserver(opts);
388 389 } else {
389 390 hgc = retryconnectcmdserver(opts, pid);
390 391 }
391 392
392 393 return hgc;
393 394 }
394 395
395 396 static void killcmdserver(const struct cmdserveropts *opts)
396 397 {
397 398 /* resolve config hash */
398 399 char *resolvedpath = realpath(opts->sockname, NULL);
399 400 if (resolvedpath) {
400 401 unlink(resolvedpath);
401 402 free(resolvedpath);
402 403 }
403 404 }
404 405
405 406 /* Run instructions sent from the server like unlink and set redirect path
406 407 * Return 1 if reconnect is needed, otherwise 0 */
407 408 static int runinstructions(struct cmdserveropts *opts, const char **insts)
408 409 {
409 410 int needreconnect = 0;
410 411 if (!insts)
411 412 return needreconnect;
412 413
413 414 assert(insts);
414 415 opts->redirectsockname[0] = '\0';
415 416 const char **pinst;
416 417 for (pinst = insts; *pinst; pinst++) {
417 418 debugmsg("instruction: %s", *pinst);
418 419 if (strncmp(*pinst, "unlink ", 7) == 0) {
419 420 unlink(*pinst + 7);
420 421 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
421 422 int r = snprintf(opts->redirectsockname,
422 423 sizeof(opts->redirectsockname), "%s",
423 424 *pinst + 9);
424 425 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
425 426 abortmsg("redirect path is too long (%d)", r);
426 427 needreconnect = 1;
427 428 } else if (strncmp(*pinst, "exit ", 5) == 0) {
428 429 int n = 0;
429 430 if (sscanf(*pinst + 5, "%d", &n) != 1)
430 431 abortmsg("cannot read the exit code");
431 432 exit(n);
432 433 } else if (strcmp(*pinst, "reconnect") == 0) {
433 434 needreconnect = 1;
434 435 } else {
435 436 abortmsg("unknown instruction: %s", *pinst);
436 437 }
437 438 }
438 439 return needreconnect;
439 440 }
440 441
441 442 /*
442 443 * Test whether the command and the environment is unsupported or not.
443 444 *
444 445 * If any of the stdio file descriptors are not present (rare, but some tools
445 446 * might spawn new processes without stdio instead of redirecting them to the
446 447 * null device), then mark it as not supported because attachio won't work
447 448 * correctly.
448 449 *
449 450 * The command list is not designed to cover all cases. But it's fast, and does
450 451 * not depend on the server.
451 452 */
452 453 static int isunsupported(int argc, const char *argv[])
453 454 {
454 455 enum {
455 456 SERVE = 1,
456 457 DAEMON = 2,
457 458 SERVEDAEMON = SERVE | DAEMON,
458 459 };
459 460 unsigned int state = 0;
460 461 int i;
461 462 /* use fcntl to test missing stdio fds */
462 463 if (fcntl(STDIN_FILENO, F_GETFD) == -1 ||
463 464 fcntl(STDOUT_FILENO, F_GETFD) == -1 ||
464 465 fcntl(STDERR_FILENO, F_GETFD) == -1) {
465 466 debugmsg("stdio fds are missing");
466 467 return 1;
467 468 }
468 469 for (i = 0; i < argc; ++i) {
469 470 if (strcmp(argv[i], "--") == 0)
470 471 break;
471 472 /*
472 473 * there can be false positives but no false negative
473 474 * we cannot assume `serve` will always be first argument
474 475 * because global options can be passed before the command name
475 476 */
476 477 if (strcmp("serve", argv[i]) == 0)
477 478 state |= SERVE;
478 479 else if (strcmp("-d", argv[i]) == 0 ||
479 480 strcmp("--daemon", argv[i]) == 0)
480 481 state |= DAEMON;
481 482 }
482 483 return (state & SERVEDAEMON) == SERVEDAEMON;
483 484 }
484 485
485 486 static void execoriginalhg(const char *argv[])
486 487 {
487 488 debugmsg("execute original hg");
488 489 if (execvp(gethgcmd(), (char **)argv) < 0)
489 490 abortmsgerrno("failed to exec original hg");
490 491 }
491 492
492 493 int main(int argc, const char *argv[], const char *envp[])
493 494 {
494 495 if (getenv("CHGDEBUG"))
495 496 enabledebugmsg();
496 497
497 498 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
498 499 enablecolor();
499 500
500 501 if (getenv("CHGINTERNALMARK"))
501 502 abortmsg("chg started by chg detected.\n"
502 503 "Please make sure ${HG:-hg} is not a symlink or "
503 504 "wrapper to chg. Alternatively, set $CHGHG to the "
504 505 "path of real hg.");
505 506
506 507 if (isunsupported(argc - 1, argv + 1))
507 508 execoriginalhg(argv);
508 509
509 510 struct cmdserveropts opts;
510 511 initcmdserveropts(&opts);
511 512 setcmdserveropts(&opts);
512 513 setcmdserverargs(&opts, argc, argv);
513 514
514 515 if (argc == 2) {
515 516 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
516 517 killcmdserver(&opts);
517 518 return 0;
518 519 }
519 520 }
520 521
521 522 hgclient_t *hgc;
522 523 size_t retry = 0;
523 524 while (1) {
524 525 hgc = connectcmdserver(&opts);
525 526 if (!hgc)
526 527 abortmsg("cannot open hg client");
527 528 hgc_setenv(hgc, envp);
528 529 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
529 530 int needreconnect = runinstructions(&opts, insts);
530 531 free(insts);
531 532 if (!needreconnect)
532 533 break;
533 534 hgc_close(hgc);
534 535 if (++retry > 10)
535 536 abortmsg("too many redirections.\n"
536 537 "Please make sure %s is not a wrapper which "
537 538 "changes sensitive environment variables "
538 539 "before executing hg. If you have to use a "
539 540 "wrapper, wrap chg instead of hg.",
540 541 gethgcmd());
541 542 }
542 543
543 544 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
544 545 atexit(waitpager);
545 546 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
546 547 restoresignalhandler();
547 548 hgc_close(hgc);
548 549 freecmdserveropts(&opts);
549 550
550 551 return exitcode;
551 552 }
@@ -1,539 +1,539 b''
1 1 #require chg
2 2
3 3 $ mkdir log
4 4 $ cp $HGRCPATH $HGRCPATH.unconfigured
5 5 $ cat <<'EOF' >> $HGRCPATH
6 6 > [cmdserver]
7 7 > log = $TESTTMP/log/server.log
8 8 > max-log-files = 1
9 9 > max-log-size = 10 kB
10 10 > EOF
11 11 $ cp $HGRCPATH $HGRCPATH.orig
12 12
13 13 $ filterlog () {
14 14 > sed -e 's!^[0-9/]* [0-9:]* ([0-9]*)>!YYYY/MM/DD HH:MM:SS (PID)>!' \
15 15 > -e 's!\(setprocname\|received fds\|setenv\): .*!\1: ...!' \
16 16 > -e 's!\(confighash\|mtimehash\) = [0-9a-f]*!\1 = ...!g' \
17 17 > -e 's!\(in \)[0-9.]*s\b!\1 ...s!g' \
18 18 > -e 's!\(pid\)=[0-9]*!\1=...!g' \
19 19 > -e 's!\(/server-\)[0-9a-f]*!\1...!g'
20 20 > }
21 21
22 22 init repo
23 23
24 24 $ chg init foo
25 25 $ cd foo
26 26
27 27 ill-formed config
28 28
29 29 $ chg status
30 30 $ echo '=brokenconfig' >> $HGRCPATH
31 31 $ chg status
32 32 config error at * =brokenconfig (glob)
33 33 [30]
34 34
35 35 $ cp $HGRCPATH.orig $HGRCPATH
36 36
37 37 long socket path
38 38
39 39 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
40 40 $ mkdir -p $sockpath
41 41 $ bakchgsockname=$CHGSOCKNAME
42 42 $ CHGSOCKNAME=$sockpath/server
43 43 $ export CHGSOCKNAME
44 44 $ chg root
45 45 $TESTTMP/foo
46 46 $ rm -rf $sockpath
47 47 $ CHGSOCKNAME=$bakchgsockname
48 48 $ export CHGSOCKNAME
49 49
50 50 $ cd ..
51 51
52 52 editor
53 53 ------
54 54
55 55 $ cat >> pushbuffer.py <<EOF
56 56 > def reposetup(ui, repo):
57 57 > repo.ui.pushbuffer(subproc=True)
58 58 > EOF
59 59
60 60 $ chg init editor
61 61 $ cd editor
62 62
63 63 by default, system() should be redirected to the client:
64 64
65 65 $ touch foo
66 66 $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
67 67 > | egrep "HG:|run 'cat"
68 68 chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
69 69 HG: Enter commit message. Lines beginning with 'HG:' are removed.
70 70 HG: Leave message empty to abort commit.
71 71 HG: --
72 72 HG: user: test
73 73 HG: branch 'default'
74 74 HG: added foo
75 75
76 76 but no redirection should be made if output is captured:
77 77
78 78 $ touch bar
79 79 $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
80 80 > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
81 81 > | egrep "HG:|run 'cat"
82 82 [1]
83 83
84 84 check that commit commands succeeded:
85 85
86 86 $ hg log -T '{rev}:{desc}\n'
87 87 1:bufferred
88 88 0:channeled
89 89
90 90 $ cd ..
91 91
92 92 pager
93 93 -----
94 94
95 95 $ cat >> fakepager.py <<EOF
96 96 > import sys
97 97 > for line in sys.stdin:
98 98 > sys.stdout.write('paged! %r\n' % line)
99 99 > EOF
100 100
101 101 enable pager extension globally, but spawns the master server with no tty:
102 102
103 103 $ chg init pager
104 104 $ cd pager
105 105 $ cat >> $HGRCPATH <<EOF
106 106 > [extensions]
107 107 > pager =
108 108 > [pager]
109 109 > pager = "$PYTHON" $TESTTMP/fakepager.py
110 110 > EOF
111 111 $ chg version > /dev/null
112 112 $ touch foo
113 113 $ chg ci -qAm foo
114 114
115 115 pager should be enabled if the attached client has a tty:
116 116
117 117 $ chg log -l1 -q --config ui.formatted=True
118 118 paged! '0:1f7b0de80e11\n'
119 119 $ chg log -l1 -q --config ui.formatted=False
120 120 0:1f7b0de80e11
121 121
122 122 chg waits for pager if runcommand raises
123 123
124 124 $ cat > $TESTTMP/crash.py <<EOF
125 125 > from mercurial import registrar
126 126 > cmdtable = {}
127 127 > command = registrar.command(cmdtable)
128 128 > @command(b'crash')
129 129 > def pagercrash(ui, repo, *pats, **opts):
130 130 > ui.write(b'going to crash\n')
131 131 > raise Exception('.')
132 132 > EOF
133 133
134 134 $ cat > $TESTTMP/fakepager.py <<EOF
135 135 > from __future__ import absolute_import
136 136 > import sys
137 137 > import time
138 138 > for line in iter(sys.stdin.readline, ''):
139 139 > if 'crash' in line: # only interested in lines containing 'crash'
140 140 > # if chg exits when pager is sleeping (incorrectly), the output
141 141 > # will be captured by the next test case
142 142 > time.sleep(1)
143 143 > sys.stdout.write('crash-pager: %s' % line)
144 144 > EOF
145 145
146 146 $ cat >> .hg/hgrc <<EOF
147 147 > [extensions]
148 148 > crash = $TESTTMP/crash.py
149 149 > EOF
150 150
151 151 $ chg crash --pager=on --config ui.formatted=True 2>/dev/null
152 152 crash-pager: going to crash
153 153 [255]
154 154
155 155 no stdout data should be printed after pager quits, and the buffered data
156 156 should never persist (issue6207)
157 157
158 158 "killed!" may be printed if terminated by SIGPIPE, which isn't important
159 159 in this test.
160 160
161 161 $ cat > $TESTTMP/bulkwrite.py <<'EOF'
162 162 > import time
163 163 > from mercurial import error, registrar
164 164 > cmdtable = {}
165 165 > command = registrar.command(cmdtable)
166 166 > @command(b'bulkwrite')
167 167 > def bulkwrite(ui, repo, *pats, **opts):
168 168 > ui.write(b'going to write massive data\n')
169 169 > ui.flush()
170 170 > t = time.time()
171 171 > while time.time() - t < 2:
172 172 > ui.write(b'x' * 1023 + b'\n') # will be interrupted by SIGPIPE
173 173 > raise error.Abort(b"write() doesn't block")
174 174 > EOF
175 175
176 176 $ cat > $TESTTMP/fakepager.py <<'EOF'
177 177 > import sys
178 178 > import time
179 179 > sys.stdout.write('paged! %r\n' % sys.stdin.readline())
180 180 > time.sleep(1) # new data will be written
181 181 > EOF
182 182
183 183 $ cat >> .hg/hgrc <<EOF
184 184 > [extensions]
185 185 > bulkwrite = $TESTTMP/bulkwrite.py
186 186 > EOF
187 187
188 188 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
189 189 paged! 'going to write massive data\n'
190 190 killed! (?)
191 191 [255]
192 192
193 193 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
194 194 paged! 'going to write massive data\n'
195 195 killed! (?)
196 196 [255]
197 197
198 198 $ cd ..
199 199
200 200 missing stdio
201 201 -------------
202 202
203 203 $ CHGDEBUG=1 chg version -q 0<&-
204 204 chg: debug: * stdio fds are missing (glob)
205 205 chg: debug: * execute original hg (glob)
206 206 Mercurial Distributed SCM * (glob)
207 207
208 208 server lifecycle
209 209 ----------------
210 210
211 211 chg server should be restarted on code change, and old server will shut down
212 212 automatically. In this test, we use the following time parameters:
213 213
214 214 - "sleep 1" to make mtime different
215 215 - "sleep 2" to notice mtime change (polling interval is 1 sec)
216 216
217 217 set up repository with an extension:
218 218
219 219 $ chg init extreload
220 220 $ cd extreload
221 221 $ touch dummyext.py
222 222 $ cat <<EOF >> .hg/hgrc
223 223 > [extensions]
224 224 > dummyext = dummyext.py
225 225 > EOF
226 226
227 227 isolate socket directory for stable result:
228 228
229 229 $ OLDCHGSOCKNAME=$CHGSOCKNAME
230 230 $ mkdir chgsock
231 231 $ CHGSOCKNAME=`pwd`/chgsock/server
232 232
233 233 warm up server:
234 234
235 235 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
236 236 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
237 237
238 238 new server should be started if extension modified:
239 239
240 240 $ sleep 1
241 241 $ touch dummyext.py
242 242 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
243 243 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
244 244 chg: debug: * instruction: reconnect (glob)
245 245 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
246 246
247 247 old server will shut down, while new server should still be reachable:
248 248
249 249 $ sleep 2
250 250 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
251 251
252 252 socket file should never be unlinked by old server:
253 253 (simulates unowned socket by updating mtime, which makes sure server exits
254 254 at polling cycle)
255 255
256 256 $ ls chgsock/server-*
257 257 chgsock/server-* (glob)
258 258 $ touch chgsock/server-*
259 259 $ sleep 2
260 260 $ ls chgsock/server-*
261 261 chgsock/server-* (glob)
262 262
263 263 since no server is reachable from socket file, new server should be started:
264 264 (this test makes sure that old server shut down automatically)
265 265
266 266 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
267 267 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
268 268
269 269 shut down servers and restore environment:
270 270
271 271 $ rm -R chgsock
272 272 $ sleep 2
273 273 $ CHGSOCKNAME=$OLDCHGSOCKNAME
274 274 $ cd ..
275 275
276 276 check that server events are recorded:
277 277
278 278 $ ls log
279 279 server.log
280 280 server.log.1
281 281
282 282 print only the last 10 lines, since we aren't sure how many records are
283 283 preserved (since setprocname isn't available on py3 and pure version,
284 284 the 10th-most-recent line is different when using py3):
285 285
286 286 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
287 287 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (no-setprocname !)
288 288 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
289 289 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (setprocname !)
290 290 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
291 291 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
292 292 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
293 293 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
294 294 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
295 295 YYYY/MM/DD HH:MM:SS (PID)> validate: []
296 296 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
297 297 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
298 298
299 299 global data mutated by schems
300 300 -----------------------------
301 301
302 302 $ hg init schemes
303 303 $ cd schemes
304 304
305 305 initial state
306 306
307 307 $ cat > .hg/hgrc <<'EOF'
308 308 > [extensions]
309 309 > schemes =
310 310 > [schemes]
311 311 > foo = https://foo.example.org/
312 312 > EOF
313 313 $ hg debugexpandscheme foo://expanded
314 314 https://foo.example.org/expanded
315 315 $ hg debugexpandscheme bar://unexpanded
316 316 bar://unexpanded
317 317
318 318 add bar
319 319
320 320 $ cat > .hg/hgrc <<'EOF'
321 321 > [extensions]
322 322 > schemes =
323 323 > [schemes]
324 324 > foo = https://foo.example.org/
325 325 > bar = https://bar.example.org/
326 326 > EOF
327 327 $ hg debugexpandscheme foo://expanded
328 328 https://foo.example.org/expanded
329 329 $ hg debugexpandscheme bar://expanded
330 330 https://bar.example.org/expanded
331 331
332 332 remove foo
333 333
334 334 $ cat > .hg/hgrc <<'EOF'
335 335 > [extensions]
336 336 > schemes =
337 337 > [schemes]
338 338 > bar = https://bar.example.org/
339 339 > EOF
340 340 $ hg debugexpandscheme foo://unexpanded
341 341 foo://unexpanded
342 342 $ hg debugexpandscheme bar://expanded
343 343 https://bar.example.org/expanded
344 344
345 345 $ cd ..
346 346
347 347 repository cache
348 348 ----------------
349 349
350 350 $ rm log/server.log*
351 351 $ cp $HGRCPATH.unconfigured $HGRCPATH
352 352 $ cat <<'EOF' >> $HGRCPATH
353 353 > [cmdserver]
354 354 > log = $TESTTMP/log/server.log
355 355 > max-repo-cache = 1
356 356 > track-log = command, repocache
357 357 > EOF
358 358
359 359 isolate socket directory for stable result:
360 360
361 361 $ OLDCHGSOCKNAME=$CHGSOCKNAME
362 362 $ mkdir chgsock
363 363 $ CHGSOCKNAME=`pwd`/chgsock/server
364 364
365 365 create empty repo and cache it:
366 366
367 367 $ hg init cached
368 368 $ hg id -R cached
369 369 000000000000 tip
370 370 $ sleep 1
371 371
372 372 modify repo (and cache will be invalidated):
373 373
374 374 $ touch cached/a
375 375 $ hg ci -R cached -Am 'add a'
376 376 adding a
377 377 $ sleep 1
378 378
379 379 read cached repo:
380 380
381 381 $ hg log -R cached
382 382 changeset: 0:ac82d8b1f7c4
383 383 tag: tip
384 384 user: test
385 385 date: Thu Jan 01 00:00:00 1970 +0000
386 386 summary: add a
387 387
388 388 $ sleep 1
389 389
390 390 discard cached from LRU cache:
391 391
392 392 $ hg clone cached cached2
393 393 updating to branch default
394 394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
395 395 $ hg id -R cached2
396 396 ac82d8b1f7c4 tip
397 397 $ sleep 1
398 398
399 399 read uncached repo:
400 400
401 401 $ hg log -R cached
402 402 changeset: 0:ac82d8b1f7c4
403 403 tag: tip
404 404 user: test
405 405 date: Thu Jan 01 00:00:00 1970 +0000
406 406 summary: add a
407 407
408 408 $ sleep 1
409 409
410 410 shut down servers and restore environment:
411 411
412 412 $ rm -R chgsock
413 413 $ sleep 2
414 414 $ CHGSOCKNAME=$OLDCHGSOCKNAME
415 415
416 416 check server log:
417 417
418 418 $ cat log/server.log | filterlog
419 419 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
420 420 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...) (?)
421 421 YYYY/MM/DD HH:MM:SS (PID)> init cached
422 422 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
423 423 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
424 424 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
425 425 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
426 426 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
427 427 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
428 428 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
429 429 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
430 430 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
431 431 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
432 432 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
433 433 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
434 434 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
435 435
436 436 Test that chg works (sets to the user's actual LC_CTYPE) even when python
437 437 "coerces" the locale (py3.7+)
438 438
439 439 $ cat > $TESTTMP/debugenv.py <<EOF
440 440 > from mercurial import encoding
441 441 > from mercurial import registrar
442 442 > cmdtable = {}
443 443 > command = registrar.command(cmdtable)
444 444 > @command(b'debugenv', [], b'', norepo=True)
445 445 > def debugenv(ui):
446 446 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
447 447 > v = encoding.environ.get(k)
448 448 > if v is not None:
449 449 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
450 450 > EOF
451 451 (hg keeps python's modified LC_CTYPE, chg doesn't)
452 452 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
453 453 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
454 454 LC_CTYPE=C.UTF-8 (py37 !)
455 455 LC_CTYPE= (no-py37 !)
456 456 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
457 457 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
458 458 LC_CTYPE=
459 459 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
460 460 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
461 461 *cannot change locale* (glob) (?)
462 462 LC_CTYPE=unsupported_value
463 463 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
464 464 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
465 465 LC_CTYPE=
466 466 $ LANG= LC_ALL= LC_CTYPE= chg \
467 467 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
468 468 LC_ALL=
469 469 LC_CTYPE=
470 470 LANG=
471 471
472 472 Profiling isn't permanently enabled or carried over between chg invocations that
473 473 share the same server
474 474 $ cp $HGRCPATH.orig $HGRCPATH
475 475 $ hg init $TESTTMP/profiling
476 476 $ cd $TESTTMP/profiling
477 477 $ filteredchg() {
478 478 > CHGDEBUG=1 chg "$@" 2>&1 | egrep 'Sample count|start cmdserver' || true
479 479 > }
480 480 $ newchg() {
481 481 > chg --kill-chg-daemon
482 482 > filteredchg "$@" | egrep -v 'start cmdserver' || true
483 483 > }
484 484 (--profile isn't permanently on just because it was specified when chg was
485 485 started)
486 486 $ newchg log -r . --profile
487 487 Sample count: * (glob)
488 488 $ filteredchg log -r .
489 489 (enabling profiling via config works, even on the first chg command that starts
490 490 a cmdserver)
491 491 $ cat >> $HGRCPATH <<EOF
492 492 > [profiling]
493 493 > type=stat
494 494 > enabled=1
495 495 > EOF
496 496 $ newchg log -r .
497 497 Sample count: * (glob)
498 498 $ filteredchg log -r .
499 499 Sample count: * (glob)
500 500 (test that we aren't accumulating more and more samples each run)
501 501 $ cat > $TESTTMP/debugsleep.py <<EOF
502 502 > import time
503 503 > from mercurial import registrar
504 504 > cmdtable = {}
505 505 > command = registrar.command(cmdtable)
506 506 > @command(b'debugsleep', [], b'', norepo=True)
507 507 > def debugsleep(ui):
508 508 > start = time.time()
509 509 > x = 0
510 510 > while time.time() < start + 0.5:
511 511 > time.sleep(.1)
512 512 > x += 1
513 513 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
514 514 > EOF
515 515 $ cat >> $HGRCPATH <<EOF
516 516 > [extensions]
517 517 > debugsleep = $TESTTMP/debugsleep.py
518 518 > EOF
519 519 $ newchg debugsleep > run_1
520 520 $ filteredchg debugsleep > run_2
521 521 $ filteredchg debugsleep > run_3
522 522 $ filteredchg debugsleep > run_4
523 523 FIXME: Run 4 should not be >3x Run 1's number of samples.
524 524 $ "$PYTHON" <<EOF
525 525 > r1 = int(open("run_1", "r").read().split()[-1])
526 526 > r4 = int(open("run_4", "r").read().split()[-1])
527 527 > print("Run 1: %d samples\nRun 4: %d samples\nRun 4 > 3 * Run 1: %s" %
528 528 > (r1, r4, r4 > (r1 * 3)))
529 529 > EOF
530 530 Run 1: * samples (glob)
531 531 Run 4: * samples (glob)
532 Run 4 > 3 * Run 1: True
532 Run 4 > 3 * Run 1: False
533 533 (Disabling with --no-profile on the commandline still works, but isn't permanent)
534 534 $ newchg log -r . --no-profile
535 535 $ filteredchg log -r .
536 536 Sample count: * (glob)
537 537 $ filteredchg log -r . --no-profile
538 538 $ filteredchg log -r .
539 539 Sample count: * (glob)
General Comments 0
You need to be logged in to leave comments. Login now