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