mirror of
https://github.com/git/git
synced 2024-08-27 19:49:21 +00:00
![Ævar Arnfjörð Bjarmason](/assets/img/avatar_default.png)
Change the skeleton implementation of i18n in Git to one that can show localized strings to users for our C, Shell and Perl programs using either GNU libintl or the Solaris gettext implementation. This new internationalization support is enabled by default. If gettext isn't available, or if Git is compiled with NO_GETTEXT=YesPlease, Git falls back on its current behavior of showing interface messages in English. When using the autoconf script we'll auto-detect if the gettext libraries are installed and act appropriately. This change is somewhat large because as well as adding a C, Shell and Perl i18n interface we're adding a lot of tests for them, and for those tests to work we need a skeleton PO file to actually test translations. A minimal Icelandic translation is included for this purpose. Icelandic includes multi-byte characters which makes it easy to test various edge cases, and it's a language I happen to understand. The rest of the commit message goes into detail about various sub-parts of this commit. = Installation Gettext .mo files will be installed and looked for in the standard $(prefix)/share/locale path. GIT_TEXTDOMAINDIR can also be set to override that, but that's only intended to be used to test Git itself. = Perl Perl code that's to be localized should use the new Git::I18n module. It imports a __ function into the caller's package by default. Instead of using the high level Locale::TextDomain interface I've opted to use the low-level (equivalent to the C interface) Locale::Messages module, which Locale::TextDomain itself uses. Locale::TextDomain does a lot of redundant work we don't need, and some of it would potentially introduce bugs. It tries to set the $TEXTDOMAIN based on package of the caller, and has its own hardcoded paths where it'll search for messages. I found it easier just to completely avoid it rather than try to circumvent its behavior. In any case, this is an issue wholly internal Git::I18N. Its guts can be changed later if that's deemed necessary. See <AANLkTilYD_NyIZMyj9dHtVk-ylVBfvyxpCC7982LWnVd@mail.gmail.com> for a further elaboration on this topic. = Shell Shell code that's to be localized should use the git-sh-i18n library. It's basically just a wrapper for the system's gettext.sh. If gettext.sh isn't available we'll fall back on gettext(1) if it's available. The latter is available without the former on Solaris, which has its own non-GNU gettext implementation. We also need to emulate eval_gettext() there. If neither are present we'll use a dumb printf(1) fall-through wrapper. = About libcharset.h and langinfo.h We use libcharset to query the character set of the current locale if it's available. I.e. we'll use it instead of nl_langinfo if HAVE_LIBCHARSET_H is set. The GNU gettext manual recommends using langinfo.h's nl_langinfo(CODESET) to acquire the current character set, but on systems that have libcharset.h's locale_charset() using the latter is either saner, or the only option on those systems. GNU and Solaris have a nl_langinfo(CODESET), FreeBSD can use either, but MinGW and some others need to use libcharset.h's locale_charset() instead. =Credits This patch is based on work by Jeff Epler <jepler@unpythonic.net> who did the initial Makefile / C work, and a lot of comments from the Git mailing list, including Jonathan Nieder, Jakub Narebski, Johannes Sixt, Erik Faye-Lund, Peter Krefting, Junio C Hamano, Thomas Rast and others. [jc: squashed a small Makefile fix from Ramsay] Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>
221 lines
5.1 KiB
C
221 lines
5.1 KiB
C
#include "cache.h"
|
|
#include "quote.h"
|
|
#include "exec_cmd.h"
|
|
#include "strbuf.h"
|
|
#include "run-command.h"
|
|
|
|
#define COMMAND_DIR "git-shell-commands"
|
|
#define HELP_COMMAND COMMAND_DIR "/help"
|
|
|
|
static int do_generic_cmd(const char *me, char *arg)
|
|
{
|
|
const char *my_argv[4];
|
|
|
|
setup_path();
|
|
if (!arg || !(arg = sq_dequote(arg)))
|
|
die("bad argument");
|
|
if (prefixcmp(me, "git-"))
|
|
die("bad command");
|
|
|
|
my_argv[0] = me + 4;
|
|
my_argv[1] = arg;
|
|
my_argv[2] = NULL;
|
|
|
|
return execv_git_cmd(my_argv);
|
|
}
|
|
|
|
static int do_cvs_cmd(const char *me, char *arg)
|
|
{
|
|
const char *cvsserver_argv[3] = {
|
|
"cvsserver", "server", NULL
|
|
};
|
|
|
|
if (!arg || strcmp(arg, "server"))
|
|
die("git-cvsserver only handles server: %s", arg);
|
|
|
|
setup_path();
|
|
return execv_git_cmd(cvsserver_argv);
|
|
}
|
|
|
|
static int is_valid_cmd_name(const char *cmd)
|
|
{
|
|
/* Test command contains no . or / characters */
|
|
return cmd[strcspn(cmd, "./")] == '\0';
|
|
}
|
|
|
|
static char *make_cmd(const char *prog)
|
|
{
|
|
char *prefix = xmalloc((strlen(prog) + strlen(COMMAND_DIR) + 2));
|
|
strcpy(prefix, COMMAND_DIR);
|
|
strcat(prefix, "/");
|
|
strcat(prefix, prog);
|
|
return prefix;
|
|
}
|
|
|
|
static void cd_to_homedir(void)
|
|
{
|
|
const char *home = getenv("HOME");
|
|
if (!home)
|
|
die("could not determine user's home directory; HOME is unset");
|
|
if (chdir(home) == -1)
|
|
die("could not chdir to user's home directory");
|
|
}
|
|
|
|
static void run_shell(void)
|
|
{
|
|
int done = 0;
|
|
static const char *help_argv[] = { HELP_COMMAND, NULL };
|
|
/* Print help if enabled */
|
|
run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
|
|
|
|
do {
|
|
struct strbuf line = STRBUF_INIT;
|
|
const char *prog;
|
|
char *full_cmd;
|
|
char *rawargs;
|
|
char *split_args;
|
|
const char **argv;
|
|
int code;
|
|
int count;
|
|
|
|
fprintf(stderr, "git> ");
|
|
if (strbuf_getline(&line, stdin, '\n') == EOF) {
|
|
fprintf(stderr, "\n");
|
|
strbuf_release(&line);
|
|
break;
|
|
}
|
|
strbuf_trim(&line);
|
|
rawargs = strbuf_detach(&line, NULL);
|
|
split_args = xstrdup(rawargs);
|
|
count = split_cmdline(split_args, &argv);
|
|
if (count < 0) {
|
|
fprintf(stderr, "invalid command format '%s': %s\n", rawargs,
|
|
split_cmdline_strerror(count));
|
|
free(split_args);
|
|
free(rawargs);
|
|
continue;
|
|
}
|
|
|
|
prog = argv[0];
|
|
if (!strcmp(prog, "")) {
|
|
} else if (!strcmp(prog, "quit") || !strcmp(prog, "logout") ||
|
|
!strcmp(prog, "exit") || !strcmp(prog, "bye")) {
|
|
done = 1;
|
|
} else if (is_valid_cmd_name(prog)) {
|
|
full_cmd = make_cmd(prog);
|
|
argv[0] = full_cmd;
|
|
code = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
|
|
if (code == -1 && errno == ENOENT) {
|
|
fprintf(stderr, "unrecognized command '%s'\n", prog);
|
|
}
|
|
free(full_cmd);
|
|
} else {
|
|
fprintf(stderr, "invalid command format '%s'\n", prog);
|
|
}
|
|
|
|
free(argv);
|
|
free(rawargs);
|
|
} while (!done);
|
|
}
|
|
|
|
static struct commands {
|
|
const char *name;
|
|
int (*exec)(const char *me, char *arg);
|
|
} cmd_list[] = {
|
|
{ "git-receive-pack", do_generic_cmd },
|
|
{ "git-upload-pack", do_generic_cmd },
|
|
{ "git-upload-archive", do_generic_cmd },
|
|
{ "cvs", do_cvs_cmd },
|
|
{ NULL },
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char *prog;
|
|
const char **user_argv;
|
|
struct commands *cmd;
|
|
int devnull_fd;
|
|
int count;
|
|
|
|
git_setup_gettext();
|
|
|
|
git_extract_argv0_path(argv[0]);
|
|
|
|
/*
|
|
* Always open file descriptors 0/1/2 to avoid clobbering files
|
|
* in die(). It also avoids not messing up when the pipes are
|
|
* dup'ed onto stdin/stdout/stderr in the child processes we spawn.
|
|
*/
|
|
devnull_fd = open("/dev/null", O_RDWR);
|
|
while (devnull_fd >= 0 && devnull_fd <= 2)
|
|
devnull_fd = dup(devnull_fd);
|
|
if (devnull_fd == -1)
|
|
die_errno("opening /dev/null failed");
|
|
close (devnull_fd);
|
|
|
|
/*
|
|
* Special hack to pretend to be a CVS server
|
|
*/
|
|
if (argc == 2 && !strcmp(argv[1], "cvs server")) {
|
|
argv--;
|
|
} else if (argc == 1) {
|
|
/* Allow the user to run an interactive shell */
|
|
cd_to_homedir();
|
|
if (access(COMMAND_DIR, R_OK | X_OK) == -1) {
|
|
die("Interactive git shell is not enabled.\n"
|
|
"hint: ~/" COMMAND_DIR " should exist "
|
|
"and have read and execute access.");
|
|
}
|
|
run_shell();
|
|
exit(0);
|
|
} else if (argc != 3 || strcmp(argv[1], "-c")) {
|
|
/*
|
|
* We do not accept any other modes except "-c" followed by
|
|
* "cmd arg", where "cmd" is a very limited subset of git
|
|
* commands or a command in the COMMAND_DIR
|
|
*/
|
|
die("Run with no arguments or with -c cmd");
|
|
}
|
|
|
|
prog = xstrdup(argv[2]);
|
|
if (!strncmp(prog, "git", 3) && isspace(prog[3]))
|
|
/* Accept "git foo" as if the caller said "git-foo". */
|
|
prog[3] = '-';
|
|
|
|
for (cmd = cmd_list ; cmd->name ; cmd++) {
|
|
int len = strlen(cmd->name);
|
|
char *arg;
|
|
if (strncmp(cmd->name, prog, len))
|
|
continue;
|
|
arg = NULL;
|
|
switch (prog[len]) {
|
|
case '\0':
|
|
arg = NULL;
|
|
break;
|
|
case ' ':
|
|
arg = prog + len + 1;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
exit(cmd->exec(cmd->name, arg));
|
|
}
|
|
|
|
cd_to_homedir();
|
|
count = split_cmdline(prog, &user_argv);
|
|
if (count >= 0) {
|
|
if (is_valid_cmd_name(user_argv[0])) {
|
|
prog = make_cmd(user_argv[0]);
|
|
user_argv[0] = prog;
|
|
execv(user_argv[0], (char *const *) user_argv);
|
|
}
|
|
free(prog);
|
|
free(user_argv);
|
|
die("unrecognized command '%s'", argv[2]);
|
|
} else {
|
|
free(prog);
|
|
die("invalid command format '%s': %s", argv[2],
|
|
split_cmdline_strerror(count));
|
|
}
|
|
}
|