##// END OF EJS Templates
chg: fallback to original hg if stdio fds are missing...
Jun Wu -
r46093:5eee6f4f default
parent child Browse files
Show More
@@ -1,470 +1,484
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 <errno.h>
12 12 #include <fcntl.h>
13 13 #include <signal.h>
14 14 #include <stdio.h>
15 15 #include <stdlib.h>
16 16 #include <string.h>
17 17 #include <sys/file.h>
18 18 #include <sys/stat.h>
19 19 #include <sys/types.h>
20 20 #include <sys/un.h>
21 21 #include <sys/wait.h>
22 22 #include <time.h>
23 23 #include <unistd.h>
24 24
25 25 #include "hgclient.h"
26 26 #include "procutil.h"
27 27 #include "util.h"
28 28
29 29 #ifndef PATH_MAX
30 30 #define PATH_MAX 4096
31 31 #endif
32 32
33 33 struct cmdserveropts {
34 34 char sockname[PATH_MAX];
35 35 char initsockname[PATH_MAX];
36 36 char redirectsockname[PATH_MAX];
37 37 size_t argsize;
38 38 const char **args;
39 39 };
40 40
41 41 static void initcmdserveropts(struct cmdserveropts *opts)
42 42 {
43 43 memset(opts, 0, sizeof(struct cmdserveropts));
44 44 }
45 45
46 46 static void freecmdserveropts(struct cmdserveropts *opts)
47 47 {
48 48 free(opts->args);
49 49 opts->args = NULL;
50 50 opts->argsize = 0;
51 51 }
52 52
53 53 /*
54 54 * Test if an argument is a sensitive flag that should be passed to the server.
55 55 * Return 0 if not, otherwise the number of arguments starting from the current
56 56 * one that should be passed to the server.
57 57 */
58 58 static size_t testsensitiveflag(const char *arg)
59 59 {
60 60 static const struct {
61 61 const char *name;
62 62 size_t narg;
63 63 } flags[] = {
64 64 {"--config", 1}, {"--cwd", 1}, {"--repo", 1},
65 65 {"--repository", 1}, {"--traceback", 0}, {"-R", 1},
66 66 };
67 67 size_t i;
68 68 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
69 69 size_t len = strlen(flags[i].name);
70 70 size_t narg = flags[i].narg;
71 71 if (memcmp(arg, flags[i].name, len) == 0) {
72 72 if (arg[len] == '\0') {
73 73 /* --flag (value) */
74 74 return narg + 1;
75 75 } else if (arg[len] == '=' && narg > 0) {
76 76 /* --flag=value */
77 77 return 1;
78 78 } else if (flags[i].name[1] != '-') {
79 79 /* short flag */
80 80 return 1;
81 81 }
82 82 }
83 83 }
84 84 return 0;
85 85 }
86 86
87 87 /*
88 88 * Parse argv[] and put sensitive flags to opts->args
89 89 */
90 90 static void setcmdserverargs(struct cmdserveropts *opts, int argc,
91 91 const char *argv[])
92 92 {
93 93 size_t i, step;
94 94 opts->argsize = 0;
95 95 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
96 96 if (!argv[i])
97 97 continue; /* pass clang-analyse */
98 98 if (strcmp(argv[i], "--") == 0)
99 99 break;
100 100 size_t n = testsensitiveflag(argv[i]);
101 101 if (n == 0 || i + n > (size_t)argc)
102 102 continue;
103 103 opts->args =
104 104 reallocx(opts->args, (n + opts->argsize) * sizeof(char *));
105 105 memcpy(opts->args + opts->argsize, argv + i,
106 106 sizeof(char *) * n);
107 107 opts->argsize += n;
108 108 step = n;
109 109 }
110 110 }
111 111
112 112 static void preparesockdir(const char *sockdir)
113 113 {
114 114 int r;
115 115 r = mkdir(sockdir, 0700);
116 116 if (r < 0 && errno != EEXIST)
117 117 abortmsgerrno("cannot create sockdir %s", sockdir);
118 118
119 119 struct stat st;
120 120 r = lstat(sockdir, &st);
121 121 if (r < 0)
122 122 abortmsgerrno("cannot stat %s", sockdir);
123 123 if (!S_ISDIR(st.st_mode))
124 124 abortmsg("cannot create sockdir %s (file exists)", sockdir);
125 125 if (st.st_uid != geteuid() || st.st_mode & 0077)
126 126 abortmsg("insecure sockdir %s", sockdir);
127 127 }
128 128
129 129 /*
130 130 * Check if a socket directory exists and is only owned by the current user.
131 131 * Return 1 if so, 0 if not. This is used to check if XDG_RUNTIME_DIR can be
132 132 * used or not. According to the specification [1], XDG_RUNTIME_DIR should be
133 133 * ignored if the directory is not owned by the user with mode 0700.
134 134 * [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
135 135 */
136 136 static int checkruntimedir(const char *sockdir)
137 137 {
138 138 struct stat st;
139 139 int r = lstat(sockdir, &st);
140 140 if (r < 0) /* ex. does not exist */
141 141 return 0;
142 142 if (!S_ISDIR(st.st_mode)) /* ex. is a file, not a directory */
143 143 return 0;
144 144 return st.st_uid == geteuid() && (st.st_mode & 0777) == 0700;
145 145 }
146 146
147 147 static void getdefaultsockdir(char sockdir[], size_t size)
148 148 {
149 149 /* by default, put socket file in secure directory
150 150 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
151 151 * (permission of socket file may be ignored on some Unices) */
152 152 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
153 153 int r;
154 154 if (runtimedir && checkruntimedir(runtimedir)) {
155 155 r = snprintf(sockdir, size, "%s/chg", runtimedir);
156 156 } else {
157 157 const char *tmpdir = getenv("TMPDIR");
158 158 if (!tmpdir)
159 159 tmpdir = "/tmp";
160 160 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
161 161 }
162 162 if (r < 0 || (size_t)r >= size)
163 163 abortmsg("too long TMPDIR (r = %d)", r);
164 164 }
165 165
166 166 static void setcmdserveropts(struct cmdserveropts *opts)
167 167 {
168 168 int r;
169 169 char sockdir[PATH_MAX];
170 170 const char *envsockname = getenv("CHGSOCKNAME");
171 171 if (!envsockname) {
172 172 getdefaultsockdir(sockdir, sizeof(sockdir));
173 173 preparesockdir(sockdir);
174 174 }
175 175
176 176 const char *basename = (envsockname) ? envsockname : sockdir;
177 177 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
178 178 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
179 179 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
180 180 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
181 181 r = snprintf(opts->initsockname, sizeof(opts->initsockname), "%s.%u",
182 182 opts->sockname, (unsigned)getpid());
183 183 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
184 184 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
185 185 }
186 186
187 187 static const char *gethgcmd(void)
188 188 {
189 189 static const char *hgcmd = NULL;
190 190 if (!hgcmd) {
191 191 hgcmd = getenv("CHGHG");
192 192 if (!hgcmd || hgcmd[0] == '\0')
193 193 hgcmd = getenv("HG");
194 194 if (!hgcmd || hgcmd[0] == '\0')
195 195 #ifdef HGPATH
196 196 hgcmd = (HGPATH);
197 197 #else
198 198 hgcmd = "hg";
199 199 #endif
200 200 }
201 201 return hgcmd;
202 202 }
203 203
204 204 static void execcmdserver(const struct cmdserveropts *opts)
205 205 {
206 206 const char *hgcmd = gethgcmd();
207 207
208 208 const char *baseargv[] = {
209 209 hgcmd,
210 210 "serve",
211 211 "--cmdserver",
212 212 "chgunix",
213 213 "--address",
214 214 opts->initsockname,
215 215 "--daemon-postexec",
216 216 "chdir:/",
217 217 };
218 218 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
219 219 size_t argsize = baseargvsize + opts->argsize + 1;
220 220
221 221 const char **argv = mallocx(sizeof(char *) * argsize);
222 222 memcpy(argv, baseargv, sizeof(baseargv));
223 223 if (opts->args) {
224 224 size_t size = sizeof(char *) * opts->argsize;
225 225 memcpy(argv + baseargvsize, opts->args, size);
226 226 }
227 227 argv[argsize - 1] = NULL;
228 228
229 229 const char *lc_ctype_env = getenv("LC_CTYPE");
230 230 if (lc_ctype_env == NULL) {
231 231 if (putenv("CHG_CLEAR_LC_CTYPE=") != 0)
232 232 abortmsgerrno("failed to putenv CHG_CLEAR_LC_CTYPE");
233 233 } else {
234 234 if (setenv("CHGORIG_LC_CTYPE", lc_ctype_env, 1) != 0) {
235 235 abortmsgerrno("failed to setenv CHGORIG_LC_CTYPE");
236 236 }
237 237 }
238 238
239 239 if (putenv("CHGINTERNALMARK=") != 0)
240 240 abortmsgerrno("failed to putenv");
241 241 if (execvp(hgcmd, (char **)argv) < 0)
242 242 abortmsgerrno("failed to exec cmdserver");
243 243 free(argv);
244 244 }
245 245
246 246 /* Retry until we can connect to the server. Give up after some time. */
247 247 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
248 248 {
249 249 static const struct timespec sleepreq = {0, 10 * 1000000};
250 250 int pst = 0;
251 251
252 252 debugmsg("try connect to %s repeatedly", opts->initsockname);
253 253
254 254 unsigned int timeoutsec = 60; /* default: 60 seconds */
255 255 const char *timeoutenv = getenv("CHGTIMEOUT");
256 256 if (timeoutenv)
257 257 sscanf(timeoutenv, "%u", &timeoutsec);
258 258
259 259 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
260 260 hgclient_t *hgc = hgc_open(opts->initsockname);
261 261 if (hgc) {
262 262 debugmsg("rename %s to %s", opts->initsockname,
263 263 opts->sockname);
264 264 int r = rename(opts->initsockname, opts->sockname);
265 265 if (r != 0)
266 266 abortmsgerrno("cannot rename");
267 267 return hgc;
268 268 }
269 269
270 270 if (pid > 0) {
271 271 /* collect zombie if child process fails to start */
272 272 int r = waitpid(pid, &pst, WNOHANG);
273 273 if (r != 0)
274 274 goto cleanup;
275 275 }
276 276
277 277 nanosleep(&sleepreq, NULL);
278 278 }
279 279
280 280 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
281 281 return NULL;
282 282
283 283 cleanup:
284 284 if (WIFEXITED(pst)) {
285 285 if (WEXITSTATUS(pst) == 0)
286 286 abortmsg("could not connect to cmdserver "
287 287 "(exited with status 0)");
288 288 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
289 289 exit(WEXITSTATUS(pst));
290 290 } else if (WIFSIGNALED(pst)) {
291 291 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
292 292 } else {
293 293 abortmsg("error while waiting for cmdserver");
294 294 }
295 295 return NULL;
296 296 }
297 297
298 298 /* Connect to a cmdserver. Will start a new server on demand. */
299 299 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
300 300 {
301 301 const char *sockname =
302 302 opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname;
303 303 debugmsg("try connect to %s", sockname);
304 304 hgclient_t *hgc = hgc_open(sockname);
305 305 if (hgc)
306 306 return hgc;
307 307
308 308 /* prevent us from being connected to an outdated server: we were
309 309 * told by a server to redirect to opts->redirectsockname and that
310 310 * address does not work. we do not want to connect to the server
311 311 * again because it will probably tell us the same thing. */
312 312 if (sockname == opts->redirectsockname)
313 313 unlink(opts->sockname);
314 314
315 315 debugmsg("start cmdserver at %s", opts->initsockname);
316 316
317 317 pid_t pid = fork();
318 318 if (pid < 0)
319 319 abortmsg("failed to fork cmdserver process");
320 320 if (pid == 0) {
321 321 execcmdserver(opts);
322 322 } else {
323 323 hgc = retryconnectcmdserver(opts, pid);
324 324 }
325 325
326 326 return hgc;
327 327 }
328 328
329 329 static void killcmdserver(const struct cmdserveropts *opts)
330 330 {
331 331 /* resolve config hash */
332 332 char *resolvedpath = realpath(opts->sockname, NULL);
333 333 if (resolvedpath) {
334 334 unlink(resolvedpath);
335 335 free(resolvedpath);
336 336 }
337 337 }
338 338
339 339 /* Run instructions sent from the server like unlink and set redirect path
340 340 * Return 1 if reconnect is needed, otherwise 0 */
341 341 static int runinstructions(struct cmdserveropts *opts, const char **insts)
342 342 {
343 343 int needreconnect = 0;
344 344 if (!insts)
345 345 return needreconnect;
346 346
347 347 assert(insts);
348 348 opts->redirectsockname[0] = '\0';
349 349 const char **pinst;
350 350 for (pinst = insts; *pinst; pinst++) {
351 351 debugmsg("instruction: %s", *pinst);
352 352 if (strncmp(*pinst, "unlink ", 7) == 0) {
353 353 unlink(*pinst + 7);
354 354 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
355 355 int r = snprintf(opts->redirectsockname,
356 356 sizeof(opts->redirectsockname), "%s",
357 357 *pinst + 9);
358 358 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
359 359 abortmsg("redirect path is too long (%d)", r);
360 360 needreconnect = 1;
361 361 } else if (strncmp(*pinst, "exit ", 5) == 0) {
362 362 int n = 0;
363 363 if (sscanf(*pinst + 5, "%d", &n) != 1)
364 364 abortmsg("cannot read the exit code");
365 365 exit(n);
366 366 } else if (strcmp(*pinst, "reconnect") == 0) {
367 367 needreconnect = 1;
368 368 } else {
369 369 abortmsg("unknown instruction: %s", *pinst);
370 370 }
371 371 }
372 372 return needreconnect;
373 373 }
374 374
375 375 /*
376 * Test whether the command is unsupported or not. This is not designed to
377 * cover all cases. But it's fast, does not depend on the server.
376 * Test whether the command and the environment is unsupported or not.
377 *
378 * If any of the stdio file descriptors are not present (rare, but some tools
379 * might spawn new processes without stdio instead of redirecting them to the
380 * null device), then mark it as not supported because attachio won't work
381 * correctly.
382 *
383 * The command list is not designed to cover all cases. But it's fast, and does
384 * not depend on the server.
378 385 */
379 386 static int isunsupported(int argc, const char *argv[])
380 387 {
381 388 enum { SERVE = 1,
382 389 DAEMON = 2,
383 390 SERVEDAEMON = SERVE | DAEMON,
384 391 };
385 392 unsigned int state = 0;
386 393 int i;
394 /* use fcntl to test missing stdio fds */
395 if (fcntl(STDIN_FILENO, F_GETFD) == -1 ||
396 fcntl(STDOUT_FILENO, F_GETFD) == -1 ||
397 fcntl(STDERR_FILENO, F_GETFD) == -1) {
398 debugmsg("stdio fds are missing");
399 return 1;
400 }
387 401 for (i = 0; i < argc; ++i) {
388 402 if (strcmp(argv[i], "--") == 0)
389 403 break;
390 404 /*
391 405 * there can be false positives but no false negative
392 406 * we cannot assume `serve` will always be first argument
393 407 * because global options can be passed before the command name
394 408 */
395 409 if (strcmp("serve", argv[i]) == 0)
396 410 state |= SERVE;
397 411 else if (strcmp("-d", argv[i]) == 0 ||
398 412 strcmp("--daemon", argv[i]) == 0)
399 413 state |= DAEMON;
400 414 }
401 415 return (state & SERVEDAEMON) == SERVEDAEMON;
402 416 }
403 417
404 418 static void execoriginalhg(const char *argv[])
405 419 {
406 420 debugmsg("execute original hg");
407 421 if (execvp(gethgcmd(), (char **)argv) < 0)
408 422 abortmsgerrno("failed to exec original hg");
409 423 }
410 424
411 425 int main(int argc, const char *argv[], const char *envp[])
412 426 {
413 427 if (getenv("CHGDEBUG"))
414 428 enabledebugmsg();
415 429
416 430 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
417 431 enablecolor();
418 432
419 433 if (getenv("CHGINTERNALMARK"))
420 434 abortmsg("chg started by chg detected.\n"
421 435 "Please make sure ${HG:-hg} is not a symlink or "
422 436 "wrapper to chg. Alternatively, set $CHGHG to the "
423 437 "path of real hg.");
424 438
425 439 if (isunsupported(argc - 1, argv + 1))
426 440 execoriginalhg(argv);
427 441
428 442 struct cmdserveropts opts;
429 443 initcmdserveropts(&opts);
430 444 setcmdserveropts(&opts);
431 445 setcmdserverargs(&opts, argc, argv);
432 446
433 447 if (argc == 2) {
434 448 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
435 449 killcmdserver(&opts);
436 450 return 0;
437 451 }
438 452 }
439 453
440 454 hgclient_t *hgc;
441 455 size_t retry = 0;
442 456 while (1) {
443 457 hgc = connectcmdserver(&opts);
444 458 if (!hgc)
445 459 abortmsg("cannot open hg client");
446 460 hgc_setenv(hgc, envp);
447 461 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
448 462 int needreconnect = runinstructions(&opts, insts);
449 463 free(insts);
450 464 if (!needreconnect)
451 465 break;
452 466 hgc_close(hgc);
453 467 if (++retry > 10)
454 468 abortmsg("too many redirections.\n"
455 469 "Please make sure %s is not a wrapper which "
456 470 "changes sensitive environment variables "
457 471 "before executing hg. If you have to use a "
458 472 "wrapper, wrap chg instead of hg.",
459 473 gethgcmd());
460 474 }
461 475
462 476 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
463 477 atexit(waitpager);
464 478 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
465 479 restoresignalhandler();
466 480 hgc_close(hgc);
467 481 freecmdserveropts(&opts);
468 482
469 483 return exitcode;
470 484 }
@@ -1,461 +1,469
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 hg: parse error at * (glob)
33 33 [255]
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 missing stdio
201 -------------
202
203 $ CHGDEBUG=1 chg version -q 0<&-
204 chg: debug: * stdio fds are missing (glob)
205 chg: debug: * execute original hg (glob)
206 Mercurial Distributed SCM * (glob)
207
200 208 server lifecycle
201 209 ----------------
202 210
203 211 chg server should be restarted on code change, and old server will shut down
204 212 automatically. In this test, we use the following time parameters:
205 213
206 214 - "sleep 1" to make mtime different
207 215 - "sleep 2" to notice mtime change (polling interval is 1 sec)
208 216
209 217 set up repository with an extension:
210 218
211 219 $ chg init extreload
212 220 $ cd extreload
213 221 $ touch dummyext.py
214 222 $ cat <<EOF >> .hg/hgrc
215 223 > [extensions]
216 224 > dummyext = dummyext.py
217 225 > EOF
218 226
219 227 isolate socket directory for stable result:
220 228
221 229 $ OLDCHGSOCKNAME=$CHGSOCKNAME
222 230 $ mkdir chgsock
223 231 $ CHGSOCKNAME=`pwd`/chgsock/server
224 232
225 233 warm up server:
226 234
227 235 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
228 236 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
229 237
230 238 new server should be started if extension modified:
231 239
232 240 $ sleep 1
233 241 $ touch dummyext.py
234 242 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
235 243 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
236 244 chg: debug: * instruction: reconnect (glob)
237 245 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
238 246
239 247 old server will shut down, while new server should still be reachable:
240 248
241 249 $ sleep 2
242 250 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
243 251
244 252 socket file should never be unlinked by old server:
245 253 (simulates unowned socket by updating mtime, which makes sure server exits
246 254 at polling cycle)
247 255
248 256 $ ls chgsock/server-*
249 257 chgsock/server-* (glob)
250 258 $ touch chgsock/server-*
251 259 $ sleep 2
252 260 $ ls chgsock/server-*
253 261 chgsock/server-* (glob)
254 262
255 263 since no server is reachable from socket file, new server should be started:
256 264 (this test makes sure that old server shut down automatically)
257 265
258 266 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
259 267 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
260 268
261 269 shut down servers and restore environment:
262 270
263 271 $ rm -R chgsock
264 272 $ sleep 2
265 273 $ CHGSOCKNAME=$OLDCHGSOCKNAME
266 274 $ cd ..
267 275
268 276 check that server events are recorded:
269 277
270 278 $ ls log
271 279 server.log
272 280 server.log.1
273 281
274 282 print only the last 10 lines, since we aren't sure how many records are
275 283 preserved (since setprocname isn't available on py3 and pure version,
276 284 the 10th-most-recent line is different when using py3):
277 285
278 286 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
279 287 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (no-setprocname !)
280 288 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
281 289 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (setprocname !)
282 290 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
283 291 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
284 292 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
285 293 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
286 294 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
287 295 YYYY/MM/DD HH:MM:SS (PID)> validate: []
288 296 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
289 297 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
290 298
291 299 global data mutated by schems
292 300 -----------------------------
293 301
294 302 $ hg init schemes
295 303 $ cd schemes
296 304
297 305 initial state
298 306
299 307 $ cat > .hg/hgrc <<'EOF'
300 308 > [extensions]
301 309 > schemes =
302 310 > [schemes]
303 311 > foo = https://foo.example.org/
304 312 > EOF
305 313 $ hg debugexpandscheme foo://expanded
306 314 https://foo.example.org/expanded
307 315 $ hg debugexpandscheme bar://unexpanded
308 316 bar://unexpanded
309 317
310 318 add bar
311 319
312 320 $ cat > .hg/hgrc <<'EOF'
313 321 > [extensions]
314 322 > schemes =
315 323 > [schemes]
316 324 > foo = https://foo.example.org/
317 325 > bar = https://bar.example.org/
318 326 > EOF
319 327 $ hg debugexpandscheme foo://expanded
320 328 https://foo.example.org/expanded
321 329 $ hg debugexpandscheme bar://expanded
322 330 https://bar.example.org/expanded
323 331
324 332 remove foo
325 333
326 334 $ cat > .hg/hgrc <<'EOF'
327 335 > [extensions]
328 336 > schemes =
329 337 > [schemes]
330 338 > bar = https://bar.example.org/
331 339 > EOF
332 340 $ hg debugexpandscheme foo://unexpanded
333 341 foo://unexpanded
334 342 $ hg debugexpandscheme bar://expanded
335 343 https://bar.example.org/expanded
336 344
337 345 $ cd ..
338 346
339 347 repository cache
340 348 ----------------
341 349
342 350 $ rm log/server.log*
343 351 $ cp $HGRCPATH.unconfigured $HGRCPATH
344 352 $ cat <<'EOF' >> $HGRCPATH
345 353 > [cmdserver]
346 354 > log = $TESTTMP/log/server.log
347 355 > max-repo-cache = 1
348 356 > track-log = command, repocache
349 357 > EOF
350 358
351 359 isolate socket directory for stable result:
352 360
353 361 $ OLDCHGSOCKNAME=$CHGSOCKNAME
354 362 $ mkdir chgsock
355 363 $ CHGSOCKNAME=`pwd`/chgsock/server
356 364
357 365 create empty repo and cache it:
358 366
359 367 $ hg init cached
360 368 $ hg id -R cached
361 369 000000000000 tip
362 370 $ sleep 1
363 371
364 372 modify repo (and cache will be invalidated):
365 373
366 374 $ touch cached/a
367 375 $ hg ci -R cached -Am 'add a'
368 376 adding a
369 377 $ sleep 1
370 378
371 379 read cached repo:
372 380
373 381 $ hg log -R cached
374 382 changeset: 0:ac82d8b1f7c4
375 383 tag: tip
376 384 user: test
377 385 date: Thu Jan 01 00:00:00 1970 +0000
378 386 summary: add a
379 387
380 388 $ sleep 1
381 389
382 390 discard cached from LRU cache:
383 391
384 392 $ hg clone cached cached2
385 393 updating to branch default
386 394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 395 $ hg id -R cached2
388 396 ac82d8b1f7c4 tip
389 397 $ sleep 1
390 398
391 399 read uncached repo:
392 400
393 401 $ hg log -R cached
394 402 changeset: 0:ac82d8b1f7c4
395 403 tag: tip
396 404 user: test
397 405 date: Thu Jan 01 00:00:00 1970 +0000
398 406 summary: add a
399 407
400 408 $ sleep 1
401 409
402 410 shut down servers and restore environment:
403 411
404 412 $ rm -R chgsock
405 413 $ sleep 2
406 414 $ CHGSOCKNAME=$OLDCHGSOCKNAME
407 415
408 416 check server log:
409 417
410 418 $ cat log/server.log | filterlog
411 419 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
412 420 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...) (?)
413 421 YYYY/MM/DD HH:MM:SS (PID)> init cached
414 422 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
415 423 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
416 424 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
417 425 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
418 426 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
419 427 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
420 428 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
421 429 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
422 430 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
423 431 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
424 432 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
425 433 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
426 434 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
427 435
428 436 Test that chg works (sets to the user's actual LC_CTYPE) even when python
429 437 "coerces" the locale (py3.7+)
430 438
431 439 $ cat > $TESTTMP/debugenv.py <<EOF
432 440 > from mercurial import encoding
433 441 > from mercurial import registrar
434 442 > cmdtable = {}
435 443 > command = registrar.command(cmdtable)
436 444 > @command(b'debugenv', [], b'', norepo=True)
437 445 > def debugenv(ui):
438 446 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
439 447 > v = encoding.environ.get(k)
440 448 > if v is not None:
441 449 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
442 450 > EOF
443 451 (hg keeps python's modified LC_CTYPE, chg doesn't)
444 452 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
445 453 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
446 454 LC_CTYPE=C.UTF-8 (py37 !)
447 455 LC_CTYPE= (no-py37 !)
448 456 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
449 457 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
450 458 LC_CTYPE=
451 459 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
452 460 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
453 461 LC_CTYPE=unsupported_value
454 462 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
455 463 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
456 464 LC_CTYPE=
457 465 $ LANG= LC_ALL= LC_CTYPE= chg \
458 466 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
459 467 LC_ALL=
460 468 LC_CTYPE=
461 469 LANG=
General Comments 0
You need to be logged in to leave comments. Login now