Changeset 2591 for trunk/src/kmk/job.c
- Timestamp:
- Jun 17, 2012, 10:45:31 PM (13 years ago)
- Location:
- trunk/src/kmk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/kmk
- Property svn:ignore
-
old new 13 13 stamp-* 14 14 makebook* 15 15 16 .*gdbinit 17 .gdb_history 18 16 19 *.dep 17 20 *.dvi … … 31 34 *.pg 32 35 *.pgs 36 33 37 README 34 38 README.DOS 35 39 README.W32 40 README.OS2 36 41 aclocal.m4 37 42 autom4te.cache … … 52 57 config.h.W32 53 58 config.h-vms 59 54 60 loadavg 55 61 loadavg.c 56 62 make 63 57 64 .deps 58 65 .dep_segment 66 ID 67 TAGS 68 59 69 _* 60 70 sun4 … … 72 82 sol2 73 83 i486-linux 84 74 85 customs 86 75 87 install-sh 76 88 mkinstalldirs 89 90 .directive.asc
-
- Property svn:ignore
-
trunk/src/kmk/job.c
r2564 r2591 1 1 /* Job execution and handling for GNU Make. 2 2 Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 3 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software4 Foundation, Inc.3 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 4 2010 Free Software Foundation, Inc. 5 5 This file is part of GNU Make. 6 6 … … 182 182 #endif /* Don't have `union wait'. */ 183 183 184 #if ndef HAVE_UNISTD_H184 #if !defined(HAVE_UNISTD_H) && !defined(WINDOWS32) 185 185 # ifndef _MSC_VER /* bird */ 186 186 int dup2 (); … … 196 196 #endif 197 197 198 /* Different systems have different requirements for pid_t. 199 Plus we have to support gettext string translation... Argh. */ 200 static const char * 201 pid2str (pid_t pid) 202 { 203 static char pidstring[100]; 204 #if defined(WINDOWS32) && (__GNUC__ > 3 || _MSC_VER > 1300) 205 /* %Id is only needed for 64-builds, which were not supported by 206 older versions of Windows compilers. */ 207 sprintf (pidstring, "%Id", pid); 208 #else 209 sprintf (pidstring, "%lu", (unsigned long) pid); 210 #endif 211 return pidstring; 212 } 213 198 214 int getloadavg (double loadavg[], int nelem); 199 215 int start_remote_job (char **argv, char **envp, int stdin_fd, int *is_remote, … … 248 264 */ 249 265 int 250 w32_kill( int pid, int sig)266 w32_kill(pid_t pid, int sig) 251 267 { 252 268 return ((process_kill((HANDLE)pid, sig) == TRUE) ? 0 : -1); … … 261 277 { 262 278 const char *const ext = unixy ? "sh" : "bat"; 263 const char *error = NULL;279 const char *error_string = NULL; 264 280 char temp_path[MAXPATHLEN]; /* need to know its length */ 265 281 unsigned path_size = GetTempPath(sizeof temp_path, temp_path); … … 307 323 else 308 324 { 309 error = map_windows32_error_to_string (er);325 error_string = map_windows32_error_to_string (er); 310 326 break; 311 327 } … … 316 332 char *const path = xmalloc (final_size); 317 333 memcpy (path, temp_path, final_size); 318 *fd = _open_osfhandle (( long)h, 0);334 *fd = _open_osfhandle ((intptr_t)h, 0); 319 335 if (unixy) 320 336 { … … 330 346 331 347 *fd = -1; 332 if (error == NULL)333 error = _("Cannot create a temporary file\n");334 fatal (NILF, error );348 if (error_string == NULL) 349 error_string = _("Cannot create a temporary file\n"); 350 fatal (NILF, error_string); 335 351 336 352 /* not reached */ … … 383 399 #endif /* __EMX__ */ 384 400 401 /* determines whether path looks to be a Bourne-like shell. */ 402 int 403 is_bourne_compatible_shell (const char *path) 404 { 405 /* list of known unix (Bourne-like) shells */ 406 const char *unix_shells[] = { 407 "sh", 408 "bash", 409 "ksh", 410 "rksh", 411 "zsh", 412 "ash", 413 "dash", 414 NULL 415 }; 416 unsigned i, len; 417 418 /* find the rightmost '/' or '\\' */ 419 const char *name = strrchr (path, '/'); 420 char *p = strrchr (path, '\\'); 421 422 if (name && p) /* take the max */ 423 name = (name > p) ? name : p; 424 else if (p) /* name must be 0 */ 425 name = p; 426 else if (!name) /* name and p must be 0 */ 427 name = path; 428 429 if (*name == '/' || *name == '\\') name++; 430 431 /* this should be able to deal with extensions on Windows-like systems */ 432 for (i = 0; unix_shells[i] != NULL; i++) { 433 len = strlen(unix_shells[i]); 434 #if defined(WINDOWS32) || defined(__MSDOS__) 435 if ((strncasecmp (name, unix_shells[i], len) == 0) && 436 (strlen(name) >= len && (name[len] == '\0' || name[len] == '.'))) 437 #else 438 if ((strncmp (name, unix_shells[i], len) == 0) && 439 (strlen(name) >= len && name[len] == '\0')) 440 #endif 441 return 1; /* a known unix-style shell */ 442 } 443 444 /* if not on the list, assume it's not a Bourne-like shell */ 445 return 0; 446 } 447 385 448 386 449 … … 537 600 { 538 601 completed_child = c; 539 DB (DB_JOBS, (_("builtin child 0x%08lx (%s) PID %ld%s Status %ld\n"),540 ( unsigned long int)c, c->file->name,541 (long) c->pid, c->remote ? _(" (remote)") : "",602 DB (DB_JOBS, (_("builtin child %p (%s) PID %s %s Status %ld\n"), 603 (void *)c, c->file->name, 604 pid2str (c->pid), c->remote ? _(" (remote)") : "", 542 605 (long) c->status)); 543 606 } 544 607 else 545 608 #endif 546 DB (DB_JOBS, (_("Live child 0x%08lx (%s) PID %ld%s\n"),547 ( unsigned long int) c, c->file->name,548 (long) c->pid,c->remote ? _(" (remote)") : ""));609 DB (DB_JOBS, (_("Live child %p (%s) PID %s %s\n"), 610 (void *)c, c->file->name, pid2str (c->pid), 611 c->remote ? _(" (remote)") : "")); 549 612 #ifdef VMS 550 613 break; … … 681 744 } 682 745 else 683 DB (DB_VERBOSE, ("Main thread handle = 0x%08lx\n", 684 (unsigned long)main_thread)); 746 DB (DB_VERBOSE, ("Main thread handle = %p\n", main_thread)); 685 747 } 686 748 … … 745 807 746 808 DB (DB_JOBS, (child_failed 747 ? _("Reaping losing child 0x%08lx PID %ld %s\n") 748 : _("Reaping winning child 0x%08lx PID %ld %s\n"), 749 (unsigned long int) c, (long) c->pid, 750 c->remote ? _(" (remote)") : "")); 809 ? _("Reaping losing child %p PID %s %s\n") 810 : _("Reaping winning child %p PID %s %s\n"), 811 (void *)c, pid2str (c->pid), c->remote ? _(" (remote)") : "")); 751 812 752 813 if (c->sh_batch_file) { … … 859 920 notice_finished_file (c->file); 860 921 861 DB (DB_JOBS, (_("Removing child 0x%08lx PID %ld%s from chain.\n"), 862 (unsigned long int) c, (long) c->pid, 863 c->remote ? _(" (remote)") : "")); 922 DB (DB_JOBS, (_("Removing child %p PID %s%s from chain.\n"), 923 (void *)c, pid2str (c->pid), c->remote ? _(" (remote)") : "")); 864 924 865 925 /* Block fatal signals while frobnicating the list, so that … … 908 968 #endif 909 969 if (!jobserver_tokens) 910 fatal (NILF, "INTERNAL: Freeing child 0x%08lx(%s) but no tokens left!\n",911 ( unsigned long int)child, child->file->name);970 fatal (NILF, "INTERNAL: Freeing child %p (%s) but no tokens left!\n", 971 (void *)child, child->file->name); 912 972 913 973 /* If we're using the jobserver and this child is not the only outstanding … … 925 985 pfatal_with_name (_("write jobserver")); 926 986 927 DB (DB_JOBS, (_("Released token for child 0x%08lx(%s).\n"),928 ( unsigned long int)child, child->file->name));987 DB (DB_JOBS, (_("Released token for child %p (%s).\n"), 988 (void *)child, child->file->name)); 929 989 } 930 990 … … 1031 1091 rval = sigaction (SIGCLD, &sa, NULL); 1032 1092 #endif 1033 if (rval != 0) 1093 if (rval != 0) 1034 1094 fprintf (stderr, "sigaction: %s (%d)\n", strerror (errno), errno); 1035 1095 #if defined SIGALRM … … 1062 1122 static int bad_stdin = -1; 1063 1123 #endif 1064 register char *p; 1065 int flags; 1124 char *p; 1125 /* Must be volatile to silence bogus GCC warning about longjmp/vfork. */ 1126 /*volatile*/ int flags; 1066 1127 #ifdef VMS 1067 1128 char *argv; 1068 1129 #else 1069 1130 char **argv; 1131 char ** volatile volatile_argv; 1132 int volatile volatile_flags; 1070 1133 #endif 1071 1134 … … 1229 1292 unixy_shell /* the test is complicated and we already did it */ 1230 1293 #else 1231 (argv[0] && !strcmp (argv[0], "/bin/sh")) 1232 #endif 1233 && (argv[1] 1234 && argv[1][0] == '-' && argv[1][1] == 'c' && argv[1][2] == '\0') 1294 (argv[0] && is_bourne_compatible_shell(argv[0])) 1295 #endif 1296 && (argv[1] && argv[1][0] == '-' 1297 && 1298 ((argv[1][1] == 'c' && argv[1][2] == '\0') 1299 || 1300 (argv[1][1] == 'e' && argv[1][2] == 'c' && argv[1][3] == '\0'))) 1235 1301 && (argv[2] && argv[2][0] == ':' && argv[2][1] == '\0') 1236 1302 && argv[3] == NULL) … … 1435 1501 1436 1502 #else /* !__EMX__ */ 1503 volatile_argv = argv; /* shut up gcc */ 1504 volatile_flags = flags; /* ditto */ 1437 1505 1438 1506 child->pid = vfork (); 1439 1507 environ = parent_environ; /* Restore value child may have clobbered. */ 1508 argv = volatile_argv; /* shut up gcc */ 1440 1509 if (child->pid == 0) 1441 1510 { … … 1445 1514 /* If we aren't running a recursive command and we have a jobserver 1446 1515 pipe, close it before exec'ing. */ 1447 if (!( flags & COMMANDS_RECURSE) && job_fds[0] >= 0)1516 if (!(volatile_flags & COMMANDS_RECURSE) && job_fds[0] >= 0) 1448 1517 { 1449 1518 close (job_fds[0]); … … 1452 1521 if (job_rfd >= 0) 1453 1522 close (job_rfd); 1523 1524 #ifdef SET_STACK_SIZE 1525 /* Reset limits, if necessary. */ 1526 if (stack_limit.rlim_cur) 1527 setrlimit (RLIMIT_STACK, &stack_limit); 1528 #endif 1454 1529 1455 1530 child_execute_job (child->good_stdin ? 0 : bad_stdin, 1, … … 1553 1628 1554 1629 if (hPID != INVALID_HANDLE_VALUE) 1555 child->pid = ( int) hPID;1630 child->pid = (pid_t) hPID; 1556 1631 else { 1557 1632 int i; … … 1675 1750 case cs_running: 1676 1751 c->next = children; 1677 DB (DB_JOBS, (_("Putting child 0x%08lx (%s) PID %ld%s on the chain.\n"),1678 ( unsigned long int) c, c->file->name,1679 (long) c->pid,c->remote ? _(" (remote)") : ""));1752 DB (DB_JOBS, (_("Putting child %p (%s) PID %s%s on the chain.\n"), 1753 (void *)c, c->file->name, pid2str (c->pid), 1754 c->remote ? _(" (remote)") : "")); 1680 1755 children = c; 1681 1756 /* One more job slot is in use. */ … … 1820 1895 Copy the remaining uninteresting text to the output. */ 1821 1896 if (out != in) 1822 strcpy (out, in);1897 memmove (out, in, strlen (in) + 1); 1823 1898 1824 1899 /* Finally, expand the line. */ … … 1830 1905 `struct child', and add that to the chain. */ 1831 1906 1832 c = xmalloc (sizeof (struct child)); 1833 memset (c, '\0', sizeof (struct child)); 1907 c = xcalloc (sizeof (struct child)); 1834 1908 c->file = file; 1835 1909 c->command_lines = lines; … … 1935 2009 if (got_token == 1) 1936 2010 { 1937 DB (DB_JOBS, (_("Obtained token for child 0x%08lx(%s).\n"),1938 ( unsigned long int)c, c->file->name));2011 DB (DB_JOBS, (_("Obtained token for child %p (%s).\n"), 2012 (void *)c, c->file->name)); 1939 2013 break; 1940 2014 } … … 2288 2362 int i; 2289 2363 fprintf(stderr, 2290 _("process_easy() failed failedto launch process (e=%ld)\n"),2364 _("process_easy() failed to launch process (e=%ld)\n"), 2291 2365 process_last_err(hPID)); 2292 2366 for (i = 0; argv[i]; i++) … … 2317 2391 break; 2318 2392 else 2393 { 2394 char *pidstr = xstrdup (pid2str ((pid_t)hWaitPID)); 2395 2319 2396 fprintf(stderr, 2320 _("make reaped child pid %ld, still waiting for pid %ld\n"), 2321 (DWORD)hWaitPID, (DWORD)hPID); 2397 _("make reaped child pid %s, still waiting for pid %s\n"), 2398 pidstr, pid2str ((pid_t)hPID)); 2399 free (pidstr); 2400 } 2322 2401 } 2323 2402 … … 2478 2557 static char ** 2479 2558 construct_command_argv_internal (char *line, char **restp, char *shell, 2480 char * ifs, int flags,2559 char *shellflags, char *ifs, int flags, 2481 2560 char **batch_filename_ptr) 2482 2561 { … … 2519 2598 "continue", "export", "read", "readonly", 2520 2599 "shift", "times", "trap", "switch", "unset", 2521 0 };2600 "ulimit", 0 }; 2522 2601 2523 2602 char *sh_chars; … … 2561 2640 #elif defined (WINDOWS32) 2562 2641 static char sh_chars_dos[] = "\"|&<>"; 2563 static char *sh_cmds_dos[] = { "break", "call", "cd", "chcp", "chdir", "cls", 2564 "copy", "ctty", "date", "del", "dir", "echo", 2565 "erase", "exit", "for", "goto", "if", "if", "md", 2566 "mkdir", "path", "pause", "prompt", "rd", "rem", 2567 "ren", "rename", "rmdir", "set", "shift", "time", 2568 "type", "ver", "verify", "vol", ":", 0 }; 2642 static char *sh_cmds_dos[] = { "assoc", "break", "call", "cd", "chcp", 2643 "chdir", "cls", "color", "copy", "ctty", 2644 "date", "del", "dir", "echo", "echo.", 2645 "endlocal", "erase", "exit", "for", "ftype", 2646 "goto", "if", "if", "md", "mkdir", "path", 2647 "pause", "prompt", "rd", "rem", "ren", 2648 "rename", "rmdir", "set", "setlocal", 2649 "shift", "time", "title", "type", "ver", 2650 "verify", "vol", ":", 0 }; 2569 2651 static char sh_chars_sh[] = "#;\"*?[]&|<>(){}$`^"; 2570 2652 static char *sh_cmds_sh[] = { "cd", "eval", "exec", "exit", "login", … … 2588 2670 "login", "logout", "read", "readonly", "set", 2589 2671 "shift", "switch", "test", "times", "trap", 2590 "u mask", "wait", "while", 0 };2672 "ulimit", "umask", "unset", "wait", "while", 0 }; 2591 2673 # ifdef HAVE_DOS_PATHS 2592 2674 /* This is required if the MSYS/Cygwin ports (which do not define … … 2727 2809 if (*ap != ' ' && *ap != '\t' && *ap != '\n') 2728 2810 goto slow; 2811 2812 if (shellflags != 0) 2813 if (shellflags[0] != '-' 2814 || ((shellflags[1] != 'c' || shellflags[2] != '\0') 2815 && (shellflags[1] != 'e' || shellflags[2] != 'c' || shellflags[3] != '\0'))) 2816 goto slow; 2729 2817 2730 2818 i = strlen (line) + 1; … … 2810 2898 goto slow; 2811 2899 #endif /* !KMK */ 2900 else if (one_shell && *p == '\n') 2901 /* In .ONESHELL mode \n is a separator like ; or && */ 2902 goto slow; 2812 2903 #ifdef __MSDOS__ 2813 2904 else if (*p == '.' && p[1] == '.' && p[2] == '.' && p[3] != '.') … … 3032 3123 return 0; 3033 3124 #endif /* WINDOWS32 */ 3125 3034 3126 { 3035 3127 /* SHELL may be a multi-word command. Construct a command line 3036 " SHELL -cLINE", with all special chars in LINE escaped.3128 "$(SHELL) $(.SHELLFLAGS) LINE", with all special chars in LINE escaped. 3037 3129 Then recurse, expanding this command line to get the final 3038 3130 argument list. */ 3039 3131 3040 3132 unsigned int shell_len = strlen (shell); 3041 #ifndef VMS3042 static char minus_c[] = " -c ";3043 #else3044 static char minus_c[] = "";3045 #endif3046 3133 unsigned int line_len = strlen (line); 3047 3048 char *new_line = alloca (shell_len + (sizeof (minus_c)-1) 3049 + (line_len*2) + 1); 3134 unsigned int sflags_len = strlen (shellflags); 3050 3135 char *command_ptr = NULL; /* used for batch_mode_shell mode */ 3136 char *new_line; 3051 3137 3052 3138 # ifdef __EMX__ /* is this necessary? */ 3053 3139 if (!unixy_shell) 3054 minus_c[1] = '/'; /* " /c" */3140 shellflags[0] = '/'; /* "/c" */ 3055 3141 # endif 3056 3142 3143 /* In .ONESHELL mode we are allowed to throw the entire current 3144 recipe string at a single shell and trust that the user 3145 has configured the shell and shell flags, and formatted 3146 the string, appropriately. */ 3147 if (one_shell) 3148 { 3149 /* If the shell is Bourne compatible, we must remove and ignore 3150 interior special chars [@+-] because they're meaningless to 3151 the shell itself. If, however, we're in .ONESHELL mode and 3152 have changed SHELL to something non-standard, we should 3153 leave those alone because they could be part of the 3154 script. In this case we must also leave in place 3155 any leading [@+-] for the same reason. */ 3156 3157 /* Remove and ignore interior prefix chars [@+-] because they're 3158 meaningless given a single shell. */ 3159 #if defined __MSDOS__ || defined (__EMX__) 3160 if (unixy_shell) /* the test is complicated and we already did it */ 3161 #else 3162 if (is_bourne_compatible_shell(shell)) 3163 #endif 3164 { 3165 const char *f = line; 3166 char *t = line; 3167 3168 /* Copy the recipe, removing and ignoring interior prefix chars 3169 [@+-]: they're meaningless in .ONESHELL mode. */ 3170 while (f[0] != '\0') 3171 { 3172 int esc = 0; 3173 3174 /* This is the start of a new recipe line. 3175 Skip whitespace and prefix characters. */ 3176 while (isblank (*f) || *f == '-' || *f == '@' || *f == '+') 3177 ++f; 3178 3179 /* Copy until we get to the next logical recipe line. */ 3180 while (*f != '\0') 3181 { 3182 *(t++) = *(f++); 3183 if (f[-1] == '\\') 3184 esc = !esc; 3185 else 3186 { 3187 /* On unescaped newline, we're done with this line. */ 3188 if (f[-1] == '\n' && ! esc) 3189 break; 3190 3191 /* Something else: reset the escape sequence. */ 3192 esc = 0; 3193 } 3194 } 3195 } 3196 *t = '\0'; 3197 } 3198 3199 new_argv = xmalloc (4 * sizeof (char *)); 3200 new_argv[0] = xstrdup(shell); 3201 new_argv[1] = xstrdup(shellflags); 3202 new_argv[2] = line; 3203 new_argv[3] = NULL; 3204 return new_argv; 3205 } 3206 3207 new_line = alloca (shell_len + 1 + sflags_len + 1 3208 + (line_len*2) + 1); 3057 3209 ap = new_line; 3058 3210 memcpy (ap, shell, shell_len); 3059 3211 ap += shell_len; 3060 memcpy (ap, minus_c, sizeof (minus_c) - 1); 3061 ap += sizeof (minus_c) - 1; 3212 *(ap++) = ' '; 3213 memcpy (ap, shellflags, sflags_len); 3214 ap += sflags_len; 3215 *(ap++) = ' '; 3062 3216 command_ptr = ap; 3063 3217 for (p = line; *p != '\0'; ++p) … … 3109 3263 *ap++ = *p; 3110 3264 } 3111 if (ap == new_line + shell_len + s izeof (minus_c) - 1)3265 if (ap == new_line + shell_len + sflags_len + 2) 3112 3266 /* Line was empty. */ 3113 3267 return 0; … … 3161 3315 } else 3162 3316 #endif /* WINDOWS32 */ 3317 3163 3318 if (unixy_shell) 3164 new_argv = construct_command_argv_internal (new_line, 0, 0, 0, flags, 0); 3319 new_argv = construct_command_argv_internal (new_line, 0, 0, 0, 0, flags, 0); 3320 3165 3321 #ifdef __EMX__ 3166 3322 else if (!unixy_shell) … … 3173 3329 char *q = new_line; 3174 3330 memcpy (new_line, line, line_len + 1); 3175 /* replace all backslash-newline combination and also following tabs */ 3176 while (*q != '\0') 3331 /* Replace all backslash-newline combination and also following tabs. 3332 Important: stop at the first '\n' because that's what the loop above 3333 did. The next line starting at restp[0] will be executed during the 3334 next call of this function. */ 3335 while (*q != '\0' && *q != '\n') 3177 3336 { 3178 3337 if (q[0] == '\\' && q[1] == '\n') … … 3235 3394 cannot backslash-escape the special characters (see above). */ 3236 3395 new_argv = xmalloc (sizeof (char *)); 3237 line_len = strlen (new_line) - shell_len - s izeof (minus_c) + 1;3396 line_len = strlen (new_line) - shell_len - sflags_len - 2; 3238 3397 new_argv[0] = xmalloc (line_len + 1); 3239 3398 strncpy (new_argv[0], 3240 new_line + shell_len + s izeof (minus_c) - 1, line_len);3399 new_line + shell_len + sflags_len + 2, line_len); 3241 3400 new_argv[0][line_len] = '\0'; 3242 3401 } … … 3270 3429 int cmd_flags, char **batch_filename_ptr) 3271 3430 { 3272 char *shell, *ifs ;3431 char *shell, *ifs, *shellflags; 3273 3432 char **argv; 3274 3433 … … 3375 3534 #endif /* __EMX__ */ 3376 3535 3536 shellflags = allocated_variable_expand_for_file ("$(.SHELLFLAGS)", file); 3377 3537 ifs = allocated_variable_expand_for_file ("$(IFS)", file); 3378 3538 … … 3400 3560 unixy_shell = 1; 3401 3561 batch_mode_shell = 0; 3402 argv = construct_command_argv_internal (line, restp, shell, ifs,3562 argv = construct_command_argv_internal (line, restp, shell, shellflags, ifs, 3403 3563 cmd_flags, batch_filename_ptr); 3404 3564 batch_mode_shell = saved_batch_mode_shell; … … 3410 3570 else 3411 3571 #endif /* CONFIG_WITH_KMK_BUILTIN */ 3412 argv = construct_command_argv_internal (line, restp, shell, ifs,3572 argv = construct_command_argv_internal (line, restp, shell, shellflags, ifs, 3413 3573 cmd_flags, batch_filename_ptr); 3414 3574 3415 3575 free (shell); 3576 free (shellflags); 3416 3577 free (ifs); 3417 3578 #endif /* !VMS */ … … 3437 3598 return fd; 3438 3599 } 3439 #endif /* !HA PE_DUP2 && !_AMIGA */3600 #endif /* !HAVE_DUP2 && !_AMIGA */ 3440 3601 3441 3602 #ifdef CONFIG_WITH_PRINT_TIME_SWITCH
Note:
See TracChangeset
for help on using the changeset viewer.