Git development
 help / color / mirror / Atom feed
* [RFC/PATCH] i18n: add infrastructure for translating Git with gettext
From: Ævar Arnfjörð Bjarmason @ 2011-11-11  0:09 UTC (permalink / raw)
  To: git; +Cc: Ævar Arnfjörð Bjarmason
In-Reply-To: <1320970164-31694-1-git-send-email-avarab@gmail.com>

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 libint 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 fall 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 wants 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.

I originally tried to detect if the system supported `echo -n' but
I found this to be a waste of time. My benchmarks on Linux, Solaris
and FreeBSD reveal that printf(1) is fast enough, especially since
we aren't calling gettext() from within any tight loops, and
unlikely to ever do so.

= 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.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 .gitignore                          |    1 +
 Documentation/CodingGuidelines      |    7 +
 INSTALL                             |   12 ++
 Makefile                            |   81 +++++++++++++-
 config.mak.in                       |    3 +
 configure.ac                        |   19 +++
 daemon.c                            |    2 +
 fast-import.c                       |    2 +
 gettext.c                           |  116 +++++++++++++++++++
 gettext.h                           |   25 ++++-
 git-sh-i18n.sh                      |  100 ++++++++++++-----
 git.c                               |    2 +
 http-backend.c                      |    2 +
 http-fetch.c                        |    2 +
 http-push.c                         |    2 +
 imap-send.c                         |    2 +
 perl/Git/I18N.pm                    |   89 +++++++++++++++
 perl/Makefile                       |    3 +-
 perl/Makefile.PL                    |   14 ++-
 po/README                           |  209 +++++++++++++++++++++++++++++++++++
 po/is.po                            |   93 ++++++++++++++++
 shell.c                             |    1 +
 show-index.c                        |    2 +
 t/lib-gettext.sh                    |   55 +++++++++
 t/t0200-gettext-basic.sh            |  108 ++++++++++++++++++
 t/t0200/test.c                      |   23 ++++
 t/t0200/test.perl                   |   14 +++
 t/t0200/test.sh                     |   14 +++
 t/t0201-gettext-fallbacks.sh        |   20 +++-
 t/t0202-gettext-perl.sh             |   27 +++++
 t/t0202/test.pl                     |  110 ++++++++++++++++++
 t/t0203-gettext-setlocale-sanity.sh |   26 +++++
 t/t0204-gettext-reencode-sanity.sh  |   78 +++++++++++++
 t/t0205-gettext-poison.sh           |   36 ++++++
 t/test-lib.sh                       |    3 +
 upload-pack.c                       |    2 +
 wrap-for-bin.sh                     |    3 +-
 37 files changed, 1269 insertions(+), 39 deletions(-)
 create mode 100644 perl/Git/I18N.pm
 create mode 100644 po/README
 create mode 100644 po/is.po
 create mode 100644 t/lib-gettext.sh
 create mode 100755 t/t0200-gettext-basic.sh
 create mode 100644 t/t0200/test.c
 create mode 100644 t/t0200/test.perl
 create mode 100644 t/t0200/test.sh
 create mode 100755 t/t0202-gettext-perl.sh
 create mode 100644 t/t0202/test.pl
 create mode 100755 t/t0203-gettext-setlocale-sanity.sh
 create mode 100755 t/t0204-gettext-reencode-sanity.sh
 create mode 100755 t/t0205-gettext-poison.sh

diff --git a/.gitignore b/.gitignore
index 8572c8c..c47f3a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -224,3 +224,4 @@
 *.pdb
 /Debug/
 /Release/
+/share/
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index fe1c1e5..4830086 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -81,6 +81,10 @@ For shell scripts specifically (not exhaustive):
      are ERE elements not BRE (note that \? and \+ are not even part
      of BRE -- making them accessible from BRE is a GNU extension).
 
+ - Use Git's gettext wrappers in git-sh-i18n to make the user
+   interface translatable. See "Marking strings for translation" in
+   po/README.
+
 For C programs:
 
  - We use tabs to indent, and interpret tabs as taking up to
@@ -144,6 +148,9 @@ For C programs:
  - When we pass <string, length> pair to functions, we should try to
    pass them in that order.
 
+ - Use Git's gettext wrappers to make the user interface
+   translatable. See "Marking strings for translation" in po/README.
+
 Writing Documentation:
 
  Every user-visible change should be reflected in the documentation.
diff --git a/INSTALL b/INSTALL
index bf0d97e..8120641 100644
--- a/INSTALL
+++ b/INSTALL
@@ -106,6 +106,18 @@ Issues of note:
 	  history graphically, and in git-gui.  If you don't want gitk or
 	  git-gui, you can use NO_TCLTK.
 
+	- A gettext library is used by default for localizing Git. The
+	  primary target is GNU libintl, but the Solaris gettext
+	  implementation also works.
+
+	  We need a gettext.h on the system for C code, gettext.sh (or
+	  Solaris gettext(1)) for shell scripts, and libintl-perl for Perl
+	  programs.
+
+	  Set NO_GETTEXT to disable localization support and make Git only
+	  use English. Under autoconf the configure script will do this
+	  automatically if it can't find libintl on the system.
+
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
diff --git a/Makefile b/Makefile
index ee34eab..28dde47 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,22 @@ all::
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation,
+# plus libintl-perl at runtime.
+#
+# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
+# trust the langinfo.h's nl_langinfo(CODESET) function to return the
+# current character set. 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.
+#
+# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
+# need -lintl when linking.
+#
+# Define NO_MSGFMT_CHECK if your implementation of msgfmt does not
+# support the --check GNU extension to msgfmt(1)
+#
 # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
 # it specifies.
 #
@@ -301,6 +317,7 @@ gitexecdir = libexec/git-core
 mergetoolsdir = $(gitexecdir)/mergetools
 sharedir = $(prefix)/share
 gitwebdir = $(sharedir)/gitweb
+localedir = $(sharedir)/locale
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ETC_GITCONFIG = $(sysconfdir)/gitconfig
@@ -309,7 +326,7 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
-export prefix bindir sharedir sysconfdir gitwebdir
+export prefix bindir sharedir sysconfdir gitwebdir localedir
 
 CC = gcc
 AR = ar
@@ -322,6 +339,7 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 XGETTEXT = xgettext
+MSGFMT = msgfmt
 PTHREAD_LIBS = -lpthread
 PTHREAD_CFLAGS =
 GCOV = gcov
@@ -621,6 +639,7 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+LIB_OBJS += gettext.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -817,12 +836,14 @@ ifeq ($(uname_S),Linux)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	LIBC_CONTAINS_LIBINTL = YesPlease
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
 	DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
+	LIBC_CONTAINS_LIBINTL = YesPlease
 endif
 ifeq ($(uname_S),UnixWare)
 	CC = cc
@@ -889,6 +910,7 @@ ifeq ($(uname_S),SunOS)
 	NO_MKSTEMPS = YesPlease
 	NO_REGEX = YesPlease
 	NO_FNMATCH_CASEFOLD = YesPlease
+	NO_MSGFMT_CHECK = YesPlease
 	ifeq ($(uname_R),5.6)
 		SOCKLEN_T = int
 		NO_HSTRERROR = YesPlease
@@ -1012,6 +1034,7 @@ ifeq ($(uname_S),GNU)
 	NO_STRLCPY=YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	LIBC_CONTAINS_LIBINTL = YesPlease
 endif
 ifeq ($(uname_S),IRIX)
 	NO_SETENV = YesPlease
@@ -1228,6 +1251,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
 	EXTLIBS += /mingw/lib/libz.a
 	NO_R_TO_GCC_LINKER = YesPlease
 	INTERNAL_QSORT = YesPlease
+	HAVE_LIBCHARSET_H = YesPlease
 else
 	NO_CURL = YesPlease
 endif
@@ -1405,6 +1429,11 @@ endif
 ifdef NEEDS_LIBGEN
 	EXTLIBS += -lgen
 endif
+ifndef NO_GETTEXT
+ifndef LIBC_CONTAINS_LIBINTL
+	EXTLIBS += -lintl
+endif
+endif
 ifdef NEEDS_SOCKET
 	EXTLIBS += -lsocket
 endif
@@ -1447,9 +1476,11 @@ ifdef NO_SYMLINK_HEAD
 	BASIC_CFLAGS += -DNO_SYMLINK_HEAD
 endif
 ifdef GETTEXT_POISON
-	LIB_OBJS += gettext.o
 	BASIC_CFLAGS += -DGETTEXT_POISON
 endif
+ifdef NO_GETTEXT
+	BASIC_CFLAGS += -DNO_GETTEXT
+endif
 ifdef NO_STRCASESTR
 	COMPAT_CFLAGS += -DNO_STRCASESTR
 	COMPAT_OBJS += compat/strcasestr.o
@@ -1612,6 +1643,10 @@ ifdef HAVE_PATHS_H
 	BASIC_CFLAGS += -DHAVE_PATHS_H
 endif
 
+ifdef HAVE_LIBCHARSET_H
+	BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
+endif
+
 ifdef DIR_HAS_BSD_GROUP_SEMANTICS
 	COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
 endif
@@ -1632,6 +1667,10 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
 	export GIT_TEST_CMP_USE_COPIED_CONTEXT
 endif
 
+ifndef NO_MSGFMT_CHECK
+	MSGFMT += --check
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1662,6 +1701,7 @@ ifndef V
 	QUIET_GEN      = @echo '   ' GEN $@;
 	QUIET_LNCP     = @echo '   ' LN/CP $@;
 	QUIET_XGETTEXT = @echo '   ' XGETTEXT $@;
+	QUIET_MSGFMT   = @echo '   ' MSGFMT $@;
 	QUIET_GCOV     = @echo '   ' GCOV $@;
 	QUIET_SP       = @echo '   ' SP $<;
 	QUIET_SUBDIR0  = +@subdir=
@@ -1688,6 +1728,7 @@ bindir_SQ = $(subst ','\'',$(bindir))
 bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
 mandir_SQ = $(subst ','\'',$(mandir))
 infodir_SQ = $(subst ','\'',$(infodir))
+localedir_SQ = $(subst ','\'',$(localedir))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
@@ -1743,7 +1784,7 @@ ifndef NO_TCLTK
 	$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
 ifndef NO_PERL
-	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
 endif
 ifndef NO_PYTHON
 	$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
@@ -1793,6 +1834,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
     -e 's|@@DIFF@@|$(DIFF_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+    -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
@@ -2045,6 +2087,9 @@ config.sp config.s config.o: EXTRA_CPPFLAGS = \
 attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \
 	-DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
 
+gettext.s gettext.o: EXTRA_CPPFLAGS = \
+	-DGIT_LOCALE_PATH='"$(localedir_SQ)"'
+
 http.sp http.s http.o: EXTRA_CPPFLAGS = \
 	-DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
 
@@ -2118,17 +2163,37 @@ XGETTEXT_FLAGS = \
 XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
 	--keyword=_ --keyword=N_ --keyword="Q_:1,2"
 XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
+XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
 LOCALIZED_C := $(C_OBJ:o=c)
 LOCALIZED_SH := $(SCRIPT_SH)
+LOCALIZED_PERL := $(SCRIPT_PERL)
+
+ifdef XGETTEXT_INCLUDE_TESTS
+LOCALIZED_C += t/t0200/test.c
+LOCALIZED_SH += t/t0200/test.sh
+LOCALIZED_PERL += t/t0200/test.perl
+endif
 
 po/git.pot: $(LOCALIZED_C)
 	$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C)
 	$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \
 		$(LOCALIZED_SH)
+	$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_PERL) \
+		$(LOCALIZED_PERL)
 	mv $@+ $@
 
 pot: po/git.pot
 
+POFILES := $(wildcard po/*.po)
+MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES))
+
+ifndef NO_GETTEXT
+all:: $(MOFILES)
+endif
+
+share/locale/%/LC_MESSAGES/git.mo: po/%.po
+	$(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
+
 FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
 			$(FIND) . \( -name .git -type d -prune \) \
 				-o \( -name '*.[hcS]' -type f -print \) )
@@ -2147,7 +2212,8 @@ cscope:
 
 ### Detect prefix changes
 TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
-             $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
+             $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
+             $(localedir_SQ)
 
 GIT-CFLAGS: FORCE
 	@FLAGS='$(TRACK_CFLAGS)'; \
@@ -2184,6 +2250,7 @@ endif
 ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
 	@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
 endif
+	@echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
 	@echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
@@ -2299,6 +2366,11 @@ install: all
 	$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
 	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
 	$(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
+ifndef NO_GETTEXT
+	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(localedir_SQ)'
+	(cd share/locale && $(TAR) cf - .) | \
+	(cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -)
+endif
 ifndef NO_PERL
 	$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
 	$(MAKE) -C gitweb install
@@ -2435,6 +2507,7 @@ clean:
 	$(RM) $(TEST_PROGRAMS)
 	$(RM) -r bin-wrappers
 	$(RM) -r $(dep_dirs)
+	$(RM) -r po/git.pot share/
 	$(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
 	$(RM) -r autom4te.cache
 	$(RM) config.log config.mak.autogen config.mak.append config.status config.cache
diff --git a/config.mak.in b/config.mak.in
index ab37101..10698c8 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -35,6 +35,9 @@ NO_CURL=@NO_CURL@
 NO_EXPAT=@NO_EXPAT@
 NO_LIBGEN_H=@NO_LIBGEN_H@
 HAVE_PATHS_H=@HAVE_PATHS_H@
+HAVE_LIBCHARSET_H=@HAVE_LIBCHARSET_H@
+NO_GETTEXT=@NO_GETTEXT@
+LIBC_CONTAINS_LIBINTL=@LIBC_CONTAINS_LIBINTL@
 NEEDS_LIBICONV=@NEEDS_LIBICONV@
 NEEDS_SOCKET=@NEEDS_SOCKET@
 NEEDS_RESOLV=@NEEDS_RESOLV@
diff --git a/configure.ac b/configure.ac
index 048a1d4..630dbdd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -636,6 +636,12 @@ AC_CHECK_LIB([c], [basename],
 AC_SUBST(NEEDS_LIBGEN)
 test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
 
+AC_CHECK_LIB([c], [gettext],
+[LIBC_CONTAINS_LIBINTL=YesPlease],
+[LIBC_CONTAINS_LIBINTL=])
+AC_SUBST(LIBC_CONTAINS_LIBINTL)
+test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
+
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
 #
@@ -818,6 +824,19 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+#
+# Define HAVE_LIBCHARSET_H if have libcharset.h
+AC_CHECK_HEADER([libcharset.h],
+[HAVE_LIBCHARSET_H=YesPlease],
+[HAVE_LIBCHARSET_H=])
+AC_SUBST(HAVE_LIBCHARSET_H)
+#
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
 [NO_STRCASESTR=],
diff --git a/daemon.c b/daemon.c
index fa28300..15ce918 100644
--- a/daemon.c
+++ b/daemon.c
@@ -1099,6 +1099,8 @@ int main(int argc, char **argv)
 	struct credentials *cred = NULL;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	for (i = 1; i < argc; i++) {
diff --git a/fast-import.c b/fast-import.c
index 8d8ea3c..b59e7db 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -3292,6 +3292,8 @@ int main(int argc, const char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage(fast_import_usage);
 
diff --git a/gettext.c b/gettext.c
index ae5394a..08bdb07 100644
--- a/gettext.c
+++ b/gettext.c
@@ -5,6 +5,18 @@
 #include "git-compat-util.h"
 #include "gettext.h"
 
+#ifndef NO_GETTEXT
+#	include <locale.h>
+#	include <libintl.h>
+#	ifdef HAVE_LIBCHARSET_H
+#		include <libcharset.h>
+#	else
+#		include <langinfo.h>
+#		define locale_charset() nl_langinfo(CODESET)
+#	endif
+#endif
+
+#ifdef GETTEXT_POISON
 int use_gettext_poison(void)
 {
 	static int poison_requested = -1;
@@ -12,3 +24,107 @@ int use_gettext_poison(void)
 		poison_requested = getenv("GIT_GETTEXT_POISON") ? 1 : 0;
 	return poison_requested;
 }
+#endif
+
+#ifndef NO_GETTEXT
+static void init_gettext_charset(const char *domain)
+{
+	const char *charset;
+
+	/* 
+	   This trick arranges for messages to be emitted in the user's
+	   requested encoding, but avoids setting LC_CTYPE from the
+	   environment for the whole program.
+
+	   This primarily done to avoid a bug in vsnprintf in the GNU C
+	   Library [1]. which triggered a "your vsnprintf is broken" error
+	   on Git's own repository when inspecting v0.99.6~1 under a UTF-8
+	   locale.
+
+	   That commit contains a ISO-8859-1 encoded author name, which
+	   the locale aware vsnprintf(3) won't interpolate in the format
+	   argument, due to mismatch between the data encoding and the
+	   locale.
+
+	   Even if it wasn't for that bug we wouldn't want to use LC_CTYPE at
+	   this point, because it'd require auditing all the code that uses C
+	   functions whose semantics are modified by LC_CTYPE.
+
+	   But only setting LC_MESSAGES as we do creates a problem, since
+	   we declare the encoding of our PO files[2] the gettext
+	   implementation will try to recode it to the user's locale, but
+	   without LC_CTYPE it'll emit something like this on 'git init'
+
+	       Bj? til t?ma Git lind ? /hl/agh/.git/
+
+	   Gettext knows about the encoding of our PO file, but we haven't
+	   told it about the user's encoding, so all the non-US-ASCII
+	   characters get encoded to question marks.
+
+	   But we're in luck! We can set LC_CTYPE from the environment
+	   only while we call nl_langinfo and
+	   bind_textdomain_codeset. That suffices to tell gettext what
+	   encoding it should emit in, so it'll now say:
+
+	       Bjó til tóma Git lind í /hl/agh/.git/
+
+	   And the equivalent ISO-8859-1 string will be emitted under a
+	   ISO-8859-1 locale.
+
+	   With this change way we get the advantages of setting LC_CTYPE
+	   (talk to the user in his language/encoding), without the major
+	   drawbacks (changed semantics for C functions we rely on).
+
+	   However foreign functions using other message catalogs that
+	   aren't using our neat trick will still have a problem, e.g. if
+	   we have to call perror(3):
+    
+    	   #include <stdio.h>
+    	   #include <locale.h>
+    	   #include <errno.h>
+    
+    	   int main(void)
+    	   {
+        	   setlocale(LC_MESSAGES, "");
+        	   setlocale(LC_CTYPE, "C");
+        	   errno = ENODEV;
+        	   perror("test");
+        	   return 0;
+    	   }
+    
+	   Running that will give you a message with question marks:
+
+    	   $ LANGUAGE= LANG=de_DE.utf8 ./test
+    	   test: Kein passendes Ger?t gefunden
+
+	   In the long term we should probably see about getting that
+	   vsnprintf bug in glibc fixed, and audit our code so it won't
+	   fall apart under a non-C locale.
+
+	   Then we could simply set LC_CTYPE from the environment, which would
+	   make things like the external perror(3) messages work.
+
+	   See t/t0203-gettext-setlocale-sanity.sh for a regression test
+	   that makes sure this code is functioning correctly.
+
+	   1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530
+	   2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po
+	*/
+	setlocale(LC_CTYPE, "");
+	charset = locale_charset();
+	bind_textdomain_codeset(domain, charset);
+	setlocale(LC_CTYPE, "C");
+}
+
+void git_setup_gettext(void)
+{
+	const char *podir = getenv("GIT_TEXTDOMAINDIR");
+
+	if (!podir)
+		podir = GIT_LOCALE_PATH;
+	bindtextdomain("git", podir);
+	setlocale(LC_MESSAGES, "");
+	init_gettext_charset("git");
+	textdomain("git");
+}
+#endif
diff --git a/gettext.h b/gettext.h
index 24d9182..57ba8bb 100644
--- a/gettext.h
+++ b/gettext.h
@@ -13,8 +13,29 @@
 #error "namespace conflict: '_' or 'Q_' is pre-defined?"
 #endif
 
+#ifndef NO_GETTEXT
+#	include <libintl.h>
+#else
+#	ifdef gettext
+#		undef gettext
+#	endif
+#	define gettext(s) (s)
+#	ifdef ngettext
+#		undef ngettext
+#	endif
+#	define ngettext(s, p, n) ((n == 1) ? (s) : (p))
+#endif
+
 #define FORMAT_PRESERVING(n) __attribute__((format_arg(n)))
 
+#ifndef NO_GETTEXT
+extern void git_setup_gettext(void);
+#else
+static inline void git_setup_gettext(void)
+{
+}
+#endif
+
 #ifdef GETTEXT_POISON
 extern int use_gettext_poison(void);
 #else
@@ -23,7 +44,7 @@ extern int use_gettext_poison(void);
 
 static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
 {
-	return use_gettext_poison() ? "# GETTEXT POISON #" : msgid;
+	return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid);
 }
 
 static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
@@ -31,7 +52,7 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n)
 {
 	if (use_gettext_poison())
 		return "# GETTEXT POISON #";
-	return n == 1 ? msgid : plu;
+	return ngettext(msgid, plu, n);
 }
 
 /* Mark msgid for translation but do not translate it. */
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
index e672366..b4575fb 100644
--- a/git-sh-i18n.sh
+++ b/git-sh-i18n.sh
@@ -2,47 +2,91 @@
 #
 # Copyright (c) 2010 Ævar Arnfjörð Bjarmason
 #
-# This is a skeleton no-op implementation of gettext for Git. It'll be
-# replaced by something that uses gettext.sh in a future patch series.
+# This is Git's interface to gettext.sh. See po/README for usage
+# instructions.
+
+# Export the TEXTDOMAIN* data that we need for Git
+TEXTDOMAIN=git
+export TEXTDOMAIN
+if test -z "$GIT_TEXTDOMAINDIR"
+then
+	TEXTDOMAINDIR="@@LOCALEDIR@@"
+else
+	TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+fi
+export TEXTDOMAINDIR
 
 if test -z "$GIT_GETTEXT_POISON"
 then
-	gettext () {
-		printf "%s" "$1"
-	}
+	if test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && type gettext.sh >/dev/null 2>&1
+	then
+		# This is GNU libintl's gettext.sh, we don't need to do anything
+		# else than setting up the environment and loading gettext.sh
+		GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
+		export GIT_INTERNAL_GETTEXT_SH_SCHEME
 
-	gettextln() {
-		printf "%s\n" "$1"
-	}
+		# Try to use libintl's gettext.sh, or fall back to English if we
+		# can't.
+		. gettext.sh
 
-	eval_gettext () {
-		printf "%s" "$1" | (
-			export PATH $(git sh-i18n--envsubst --variables "$1");
-			git sh-i18n--envsubst "$1"
-		)
-	}
+	elif test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && test "$(gettext -h 2>&1)" = "-h"
+	then
+		# We don't have gettext.sh, but there's a gettext binary in our
+		# path. This is probably Solaris or something like it which has a
+		# gettext implementation that isn't GNU libintl.
+		GIT_INTERNAL_GETTEXT_SH_SCHEME=solaris
+		export GIT_INTERNAL_GETTEXT_SH_SCHEME
 
-	eval_gettextln () {
-		printf "%s\n" "$1" | (
-			export PATH $(git sh-i18n--envsubst --variables "$1");
-			git sh-i18n--envsubst "$1"
-		)
-	}
+		# Solaris has a gettext(1) but no eval_gettext(1)
+		eval_gettext () {
+			gettext "$1" | (
+				export PATH $(git sh-i18n--envsubst --variables "$1");
+				git sh-i18n--envsubst "$1"
+			)
+		}
+
+	else
+		# Since gettext.sh isn't available we'll have to define our own
+		# dummy pass-through functions.
+
+		# Tell our tests that we don't have the real gettext.sh
+		GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough
+		export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+		gettext () {
+			printf "%s" "$1"
+		}
+
+		eval_gettext () {
+			printf "%s" "$1" | (
+				export PATH $(git sh-i18n--envsubst --variables "$1");
+				git sh-i18n--envsubst "$1"
+			)
+		}
+	fi
 else
+	# Emit garbage under GETTEXT_POISON=YesPlease. Unlike the C tests
+	# this relies on an environment variable
+
+	GIT_INTERNAL_GETTEXT_SH_SCHEME=poison
+	export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
 	gettext () {
 		printf "%s" "# GETTEXT POISON #"
 	}
 
-	gettextln () {
-		printf "%s\n" "# GETTEXT POISON #"
-	}
-
 	eval_gettext () {
 		printf "%s" "# GETTEXT POISON #"
 	}
-
-	eval_gettextln () {
-		printf "%s\n" "# GETTEXT POISON #"
-	}
 fi
 
+# Git-specific wrapper functions
+gettextln () {
+	gettext "$1"
+	echo
+}
+
+eval_gettextln () {
+	eval_gettext "$1"
+	echo
+}
diff --git a/git.c b/git.c
index 8e34903..fa918b5 100644
--- a/git.c
+++ b/git.c
@@ -537,6 +537,8 @@ int main(int argc, const char **argv)
 	if (!cmd)
 		cmd = "git-help";
 
+	git_setup_gettext();
+
 	/*
 	 * "git-xxxx" is the same as "git xxxx", but we obviously:
 	 *
diff --git a/http-backend.c b/http-backend.c
index 59ad7da..869d515 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -545,6 +545,8 @@ int main(int argc, char **argv)
 	char *cmd_arg = NULL;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	set_die_routine(die_webcgi);
 
diff --git a/http-fetch.c b/http-fetch.c
index 69299b7..9719d58 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -22,6 +22,8 @@ int main(int argc, const char **argv)
 	int get_verbosely = 0;
 	int get_recover = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	while (arg < argc && argv[arg][0] == '-') {
diff --git a/http-push.c b/http-push.c
index edd553b..f856299 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1748,6 +1748,8 @@ int main(int argc, char **argv)
 	int new_refs;
 	struct ref *ref, *local_refs;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	repo = xcalloc(sizeof(*repo), 1);
diff --git a/imap-send.c b/imap-send.c
index e1ad1a4..9fba422 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1539,6 +1539,8 @@ int main(int argc, char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(imap_send_usage);
 
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
new file mode 100644
index 0000000..07597dc
--- /dev/null
+++ b/perl/Git/I18N.pm
@@ -0,0 +1,89 @@
+package Git::I18N;
+use 5.008;
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw(__);
+our @EXPORT_OK = @EXPORT;
+
+sub __bootstrap_locale_messages {
+	our $TEXTDOMAIN = 'git';
+	our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';
+
+	require POSIX;
+	POSIX->import(qw(setlocale));
+	# Non-core prerequisite module
+	require Locale::Messages;
+	Locale::Messages->import(qw(:locale_h :libintl_h));
+
+	setlocale(LC_MESSAGES(), '');
+	setlocale(LC_CTYPE(), '');
+	textdomain($TEXTDOMAIN);
+	bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);
+
+	return;
+}
+
+BEGIN
+{
+	# Used by our test script to see if it should test fallbacks or
+	# not.
+	our $__HAS_LIBRARY = 1;
+
+	local $@;
+	eval {
+		__bootstrap_locale_messages();
+		*__ = \&Locale::Messages::gettext;
+		1;
+	} or do {
+		# Tell test.pl that we couldn't load the gettext library.
+		$Git::I18N::__HAS_LIBRARY = 0;
+
+		# Just a fall-through no-op
+		*__ = sub ($) { $_[0] };
+	};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Git::I18N - Perl interface to Git's Gettext localizations
+
+=head1 SYNOPSIS
+
+	use Git::I18N;
+
+	print __("Welcome to Git!\n");
+
+	printf __("The following error occured: %s\n"), $error;
+
+=head1 DESCRIPTION
+
+Git's internal Perl interface to gettext via L<Locale::Messages>. If
+L<Locale::Messages> can't be loaded (it's not a core module) we
+provide stub passthrough fallbacks.
+
+This is a distilled interface to gettext, see C<info '(gettext)Perl'>
+for the full interface. This module implements only a small part of
+it.
+
+=head1 FUNCTIONS
+
+=head2 __($)
+
+L<Locale::Messages>'s gettext function if all goes well, otherwise our
+passthrough fallback function.
+
+=head1 AUTHOR
+
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=cut
diff --git a/perl/Makefile b/perl/Makefile
index a2ffb64..b2977cd 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -5,6 +5,7 @@ makfile:=perl.mak
 
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 prefix_SQ = $(subst ','\'',$(prefix))
+localedir_SQ = $(subst ','\'',$(localedir))
 
 ifndef V
 	QUIET = @
@@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	echo '	echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-	$(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE=''
+	$(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE='' --localedir='$(localedir_SQ)'
 endif
 
 # this is just added comfort for calling make directly in perl dir
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index 0b9deca..456d45b 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -1,4 +1,12 @@
+use strict;
+use warnings;
 use ExtUtils::MakeMaker;
+use Getopt::Long;
+
+# Sanity: die at first unknown option
+Getopt::Long::Configure qw/ pass_through /;
+
+GetOptions("localedir=s" => \my $localedir);
 
 sub MY::postamble {
 	return <<'MAKE_FRAG';
@@ -16,7 +24,10 @@ endif
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+my %pm = (
+	'Git.pm' => '$(INST_LIBDIR)/Git.pm',
+	'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
+);
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
@@ -33,6 +44,7 @@ WriteMakefile(
 	NAME            => 'Git',
 	VERSION_FROM    => 'Git.pm',
 	PM		=> \%pm,
+	PM_FILTER	=> qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
 	MAKEFILE	=> 'perl.mak',
 	INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
 );
diff --git a/po/README b/po/README
new file mode 100644
index 0000000..eff16dd
--- /dev/null
+++ b/po/README
@@ -0,0 +1,209 @@
+Core GIT Translations
+=====================
+
+This directory holds the translations for the core of Git. This
+document describes how to add to and maintain these translations, and
+how to mark source strings for translation.
+
+
+Generating a .pot file
+----------------------
+
+The po/git.pot file contains a message catalog extracted from Git's
+sources. You need to generate it to add new translations with
+msginit(1), or update existing ones with msgmerge(1).
+
+Since the file can be automatically generated it's not checked into
+git.git. To generate it do, at the top-level:
+
+    make pot
+
+
+Initializing a .po file
+-----------------------
+
+To add a new translation first generate git.pot (see above) and then
+in the po/ directory do:
+
+    msginit --locale=XX
+
+Where XX is your locale, e.g. "is", "de" or "pt_BR".
+
+Then edit the automatically generated copyright info in your new XX.po
+to be correct, e.g. for Icelandic:
+    
+    @@ -1,6 +1,6 @@
+    -# Icelandic translations for PACKAGE package.
+    -# Copyright (C) 2010 THE PACKAGE'S COPYRIGHT HOLDER
+    -# This file is distributed under the same license as the PACKAGE package.
+    +# Icelandic translations for Git.
+    +# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+    +# This file is distributed under the same license as the Git package.
+     # Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
+
+And change references to PACKAGE VERSION in the PO Header Entry to
+just "Git":
+
+    perl -pi -e 's/(?<="Project-Id-Version: )PACKAGE VERSION/Git/' XX.po
+
+
+Updating a .po file
+-------------------
+
+If there's an existing *.po file for your language but you need to
+update the translation you first need to generate git.pot (see above)
+and then in the po/ directory do:
+
+    msgmerge --add-location --backup=off -U XX.po git.pot
+
+Where XX.po is the file you want to update.
+
+Testing your changes
+--------------------
+
+Before you submit your changes go back to the top-level and do:
+
+    make
+
+On systems with GNU gettext (i.e. not Solaris) this will compile your
+changed PO file with `msgfmt --check`, the --check option flags many
+common errors, e.g. missing printf format strings, or translated
+messages that deviate from the originals in whether they begin/end
+with a newline or not.
+
+
+Marking strings for translation
+-------------------------------
+
+Before strings can be translated they first have to be marked for
+translation.
+
+Git uses an internationalization interface that wraps the system's
+gettext library, so most of the advice in your gettext documentation
+(on GNU systems `info gettext` in a terminal) applies.
+
+General advice:
+
+ - Don't mark everything for translation, only strings which will be
+   read by humans (the porcelain interface) should be translated.
+
+   The output from Git's plumbing utilities will primarily be read by
+   programs and would break scripts under non-C locales if it was
+   translated. Plumbing strings should not be translated, since
+   they're part of Git's API.
+
+ - Adjust the strings so that they're easy to translate. Most of the
+   advice in `info '(gettext)Preparing Strings'` applies here.
+
+ - If something is unclear or ambiguous you can use a "TRANSLATORS"
+   comment to tell the translators what to make of it. These will be
+   extracted by xgettext(1) and put in the po/*.po files, e.g. from
+   git-am.sh:
+
+       # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+       # in your translation. The program will only accept English
+       # input at this point.
+       gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+
+   Or in C, from builtin/revert.c:
+
+       /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+       die(_("%s: Unable to write new index file"), me);
+
+We provide wrappers for C, Shell and Perl programs. Here's how they're
+used:
+
+C:
+
+ - Include builtin.h at the top, it'll pull in in gettext.h, which
+   defines the gettext interface. Consult with the list if you need to
+   use gettext.h directly.
+
+ - The C interface is a subset of the normal GNU gettext
+   interface. We currently export these functions:
+
+   - _()
+
+    Mark and translate a string. E.g.:
+
+        printf(_("HEAD is now at %s"), hex);
+
+   - N_()
+
+    A no-op pass-through macro for marking strings inside static
+    initializations, e.g.:
+
+        static const char *reset_type_names[] = {
+            N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
+        };
+
+    And then, later:
+
+        die(_("%s reset is not allowed in a bare repository"),
+               _(reset_type_names[reset_type]));
+
+    Here _() couldn't have statically determined what the translation
+    string will be, but since it was already marked for translation
+    with N_() the look-up in the message catalog will succeed.
+
+Shell:
+
+ - The Git gettext shell interface is just a wrapper for
+   gettext.sh. Import it right after git-sh-setup like this:
+
+       . git-sh-setup
+       . git-sh-i18n
+
+   And then use the gettext or eval_gettext functions:
+
+       # For constant interface messages:
+       gettext "A message for the user"; echo
+
+       # To interpolate variables:
+       details="oh noes"
+       eval_gettext "An error occured: \$details"; echo
+
+   More documentation about the interface is available in the GNU info
+   page: `info '(gettext)sh'`. Looking at git-am.sh (the first shell
+   command to be translated) for examples is also useful:
+
+       git log --reverse -p --grep=gettextize git-am.sh
+
+Perl:
+
+ - The Git::I18N module provides a limited subset of the
+   Locale::Messages functionality, e.g.:
+
+       use Git::I18N;
+       print __("Welcome to Git!\n");
+       printf __("The following error occured: %s\n"), $error;
+
+   Run `perldoc perl/Git/I18N.pm` for more info.
+
+
+Testing marked strings
+----------------------
+
+Even if you've correctly marked porcelain strings for translation
+something in the test suite might still depend on the US English
+version of the strings, e.g. to grep some error message or other
+output.
+
+To smoke out issues like these Git can be compiled with gettext poison
+support, at the top-level:
+
+    make GETTEXT_POISON=YesPlease
+
+That'll give you a git which emits gibberish on every call to
+gettext. It's obviously not meant to be installed, but you should run
+the test suite with it:
+
+    cd t && prove -j 9 ./t[0-9]*.sh
+
+If tests break with it you should inspect them manually and see if
+what you're translating is sane, i.e. that you're not translating
+plumbing output.
+
+If not you should add a C_LOCALE_OUTPUT prerequisite to the test (as
+documented in t/README). See existing test files with this
+prerequisite for examples.
diff --git a/po/is.po b/po/is.po
new file mode 100644
index 0000000..8692a8b
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,93 @@
+# Icelandic translations for Git.
+# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+# This file is distributed under the same license as the Git package.
+# Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2010-09-20 14:44+0000\n"
+"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"Language: is\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:5
+msgid "See 'git help COMMAND' for more information on a specific command."
+msgstr "Sjá 'git help SKIPUN' til að sjá hjálp fyrir tiltekna skipun."
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:10
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:13
+#, c-format
+msgid "TEST: A C test string %s"
+msgstr "TILRAUN: C tilraunastrengur %s"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:16
+#, c-format
+msgid "TEST: Hello World!"
+msgstr "TILRAUN: Halló Heimur!"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:19
+#, c-format
+msgid "TEST: Old English Runes"
+msgstr "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:22
+#, c-format
+msgid "TEST: ‘single’ and “double” quotes"
+msgstr "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:8
+msgid "TEST: A Shell test string"
+msgstr "TILRAUN: Skeljartilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:11
+#, sh-format
+msgid "TEST: A Shell test $variable"
+msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:8
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:11
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
+
+#. TRANSLATORS: The first '%s' is either "Reinitialized
+#. existing" or "Initialized empty", the second " shared" or
+#. "", and the last '%s%s' is the verbatim directory name.
+#: builtin/init-db.c:355
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s Git lind í %s%s\n"
+
+#: builtin/init-db.c:356
+msgid "Reinitialized existing"
+msgstr "Endurgerði"
+
+#: builtin/init-db.c:356
+msgid "Initialized empty"
+msgstr "Bjó til tóma"
+
+#: builtin/init-db.c:357
+msgid " shared"
+msgstr " sameiginlega"
diff --git a/shell.c b/shell.c
index abb8622..713def9 100644
--- a/shell.c
+++ b/shell.c
@@ -137,6 +137,7 @@ int main(int argc, char **argv)
 	int devnull_fd;
 	int count;
 
+	git_setup_gettext();
 	git_extract_argv0_path(argv[0]);
 
 	/*
diff --git a/show-index.c b/show-index.c
index 63f9da5..5a9eed7 100644
--- a/show-index.c
+++ b/show-index.c
@@ -11,6 +11,8 @@ int main(int argc, char **argv)
 	unsigned int version;
 	static unsigned int top_index[256];
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(show_index_usage);
 	if (fread(top_index, 2 * 4, 1, stdin) != 1)
diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh
new file mode 100644
index 0000000..a54a9f8
--- /dev/null
+++ b/t/lib-gettext.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/share/locale"
+GIT_PO_PATH="$GIT_BUILD_DIR/po"
+export GIT_TEXTDOMAINDIR GIT_PO_PATH
+
+. "$GIT_BUILD_DIR"/git-sh-i18n
+
+if test_have_prereq GETTEXT && ! test_have_prereq GETTEXT_POISON
+then
+	# is_IS.UTF-8 on Solaris and FreeBSD, is_IS.utf8 on Debian
+	is_IS_locale=$(locale -a | sed -n '/^is_IS\.[uU][tT][fF]-*8$/{
+		p
+		q
+	}')
+	# is_IS.ISO8859-1 on Solaris and FreeBSD, is_IS.iso88591 on Debian
+	is_IS_iso_locale=$(locale -a | sed -n '/^is_IS\.[iI][sS][oO]8859-*1$/{
+		p
+		q
+	}')
+
+	# Export them as an environment variable so the t0202/test.pl Perl
+	# test can use it too
+	export is_IS_locale is_IS_iso_locale
+
+	if test -n "$is_IS_locale" &&
+		test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
+	then
+		# Some of the tests need the reference Icelandic locale
+		test_set_prereq GETTEXT_LOCALE
+
+		# Exporting for t0202/test.pl
+		GETTEXT_LOCALE=1
+		export GETTEXT_LOCALE
+		say "# lib-gettext: Found '$is_IS_locale' as an is_IS UTF-8 locale"
+	else
+		say "# lib-gettext: No is_IS UTF-8 locale available"
+	fi
+
+	if test -n "$is_IS_iso_locale" &&
+		test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
+	then
+		# Some of the tests need the reference Icelandic locale
+		test_set_prereq GETTEXT_ISO_LOCALE
+
+		say "# lib-gettext: Found '$is_IS_iso_locale' as an is_IS ISO-8859-1 locale"
+	else
+		say "# lib-gettext: No is_IS ISO-8859-1 locale available"
+	fi
+fi
diff --git a/t/t0200-gettext-basic.sh b/t/t0200-gettext-basic.sh
new file mode 100755
index 0000000..8853d8a
--- /dev/null
+++ b/t/t0200-gettext-basic.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext support for Git'
+
+. ./lib-gettext.sh
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $TEXTDOMAIN is git' '
+    test $TEXTDOMAIN = "git"
+'
+
+test_expect_success 'xgettext sanity: Perl _() strings are not extracted' '
+    ! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '
+    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l >expect &&
+    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' '
+    ! grep "This is a phony" "$GIT_PO_PATH"/is.po &&
+    ! grep "the above comment" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success GETTEXT 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
+    test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+
+test_expect_success GETTEXT 'sanity: Icelandic locale was compiled' '
+    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
+'
+
+# TODO: When we have more locales, generalize this to test them
+# all. Maybe we'll need a dir->locale map for that.
+test_expect_success GETTEXT_LOCALE 'sanity: gettext("") metadata is OK' '
+    # Return value may be non-zero
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >zero-expect &&
+    grep "Project-Id-Version: Git" zero-expect &&
+    grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
+    grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
+    grep "Content-Transfer-Encoding: 8bit" zero-expect
+'
+
+test_expect_success GETTEXT_LOCALE 'sanity: gettext(unknown) is passed through' '
+    printf "This is not a translation string"  >expect &&
+    gettext "This is not a translation string" >actual &&
+    eval_gettext "This is not a translation string" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from C
+test_expect_success GETTEXT_LOCALE 'xgettext: C extraction of _() and N_() strings' '
+    printf "TILRAUN: C tilraunastrengur" >expect &&
+    printf "\n" >>expect &&
+    printf "Sjá '\''git help SKIPUN'\'' til að sjá hjálp fyrir tiltekna skipun." >>expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string" >actual &&
+    printf "\n" >>actual &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "See '\''git help COMMAND'\'' for more information on a specific command." >>actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: C extraction with %s' '
+    printf "TILRAUN: C tilraunastrengur %%s" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string %s" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from Shell
+test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction' '
+    printf "TILRAUN: Skeljartilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Shell test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction with $variable' '
+    printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" >x-expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" variable="a var i able" eval_gettext "TEST: A Shell test \$variable" >x-actual &&
+    test_cmp x-expect x-actual
+'
+
+# xgettext from Perl
+test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction' '
+    printf "TILRAUN: Perl tilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction with %s' '
+    printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test variable %s" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'sanity: Some gettext("") data for real locale' '
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >real-locale &&
+    test -s real-locale
+'
+
+test_done
diff --git a/t/t0200/test.c b/t/t0200/test.c
new file mode 100644
index 0000000..584d45c
--- /dev/null
+++ b/t/t0200/test.c
@@ -0,0 +1,23 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+const char help[] =
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	N_("See 'git help COMMAND' for more information on a specific command.");
+
+int main(void)
+{
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	puts(_("TEST: A C test string"));
+
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	printf(_("TEST: A C test string %s"), "variable");
+
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	printf(_("TEST: Hello World!"));
+
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	printf(_("TEST: Old English Runes"));
+
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	printf(_("TEST: ‘single’ and “double” quotes"));
+}
diff --git a/t/t0200/test.perl b/t/t0200/test.perl
new file mode 100644
index 0000000..36fba34
--- /dev/null
+++ b/t/t0200/test.perl
@@ -0,0 +1,14 @@
+# This is a phony Perl program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+1;
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+print __("TEST: A Perl test string");
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+printf __("TEST: A Perl test variable %s"), "moo";
+
+# TRANSLATORS: If you see this, Git has a bug
+print _"TEST: A Perl string xgettext will not get";
diff --git a/t/t0200/test.sh b/t/t0200/test.sh
new file mode 100644
index 0000000..022d607
--- /dev/null
+++ b/t/t0200/test.sh
@@ -0,0 +1,14 @@
+# This is a phony Shell program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+echo
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+gettext "TEST: A Shell test string"
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+eval_gettext "TEST: A Shell test \$variable"
+
+# TRANSLATORS: If you see this, Git has a bug
+_("TEST: A Shell string xgettext won't get")
diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh
index 54d98b9..52b1c27 100755
--- a/t/t0201-gettext-fallbacks.sh
+++ b/t/t0201-gettext-fallbacks.sh
@@ -5,8 +5,24 @@
 
 test_description='Gettext Shell fallbacks'
 
-. ./test-lib.sh
-. "$GIT_BUILD_DIR"/git-sh-i18n
+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease
+export GIT_INTERNAL_GETTEXT_TEST_FALLBACKS
+
+. ./lib-gettext.sh
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '
+    test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+'
+
+test_expect_success C_LOCALE_OUTPUT 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' '
+    echo fallthrough >expect &&
+    echo $GIT_INTERNAL_GETTEXT_SH_SCHEME >actual &&
+    test_cmp expect actual
+'
 
 test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
     printf "test" >expect &&
diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh
new file mode 100755
index 0000000..428ebb0
--- /dev/null
+++ b/t/t0202-gettext-perl.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Perl gettext interface (Git::I18N)'
+
+. ./lib-gettext.sh
+
+if ! test_have_prereq PERL; then
+	skip_all='skipping perl interface tests, perl not available'
+	test_done
+fi
+
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+	skip_all="Perl Test::More unavailable, skipping test"
+	test_done
+}
+
+# The external test will outputs its own plan
+test_external_has_tap=1
+
+test_external_without_stderr \
+    'Perl Git::I18N API' \
+    "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl
+
+test_done
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
new file mode 100644
index 0000000..a0e1501
--- /dev/null
+++ b/t/t0202/test.pl
@@ -0,0 +1,110 @@
+#!/usr/bin/perl
+use 5.008;
+use lib (split(/:/, $ENV{GITPERLLIB}));
+use warnings;
+use strict;
+use Test::More tests => 8;
+use Git::I18N;
+use POSIX qw(:locale_h);
+
+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
+
+ok(1, "Testing Git::I18N with " .
+	 ($has_gettext_library
+	  ? (defined $Locale::Messages::VERSION
+		 ? "Locale::Messages version $Locale::Messages::VERSION"
+		 # Versions of Locale::Messages before 1.17 didn't have a
+		 # $VERSION variable.
+		 : "Locale::Messages version <1.17")
+	  : "NO Perl gettext library"));
+ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
+
+{
+	my $exports = @Git::I18N::EXPORT;
+	ok($exports, "sanity: Git::I18N has $exports export(s)");
+}
+is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default");
+
+# prototypes
+{
+	# Add prototypes here when modifying the public interface to add
+	# more gettext wrapper functions.
+	my %prototypes = (qw(
+		__	$
+	));
+	while (my ($sub, $proto) = each %prototypes) {
+		is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
+	}
+}
+
+# Test basic passthrough in the C locale
+{
+	local $ENV{LANGUAGE} = 'C';
+	local $ENV{LC_ALL}   = 'C';
+	local $ENV{LANG}     = 'C';
+
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	is(__($got), $expect, "Passing a string through __() in the C locale works");
+}
+
+# Test a basic message on different locales
+SKIP: {
+	unless ($ENV{GETTEXT_LOCALE}) {
+		# Can't reliably test __() with a non-C locales because the
+		# required locales may not be installed on the system.
+		#
+		# We test for these anyway as part of the shell
+		# tests. Skipping these here will eliminate failures on odd
+		# platforms with incomplete locale data.
+
+		skip "GETTEXT_LOCALE must be set by lib-gettext.sh for exhaustive Git::I18N tests", 2;
+	}
+
+	# The is_IS UTF-8 locale passed from lib-gettext.sh
+	my $is_IS_locale = $ENV{is_IS_locale};
+
+	my $test = sub {
+		my ($got, $expect, $msg, $locale) = @_;
+		# Maybe this system doesn't have the locale we're trying to
+		# test.
+		my $locale_ok = setlocale(LC_ALL, $locale);
+		is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>");
+	};
+
+	my $env_C = sub {
+		$ENV{LANGUAGE} = 'C';
+		$ENV{LC_ALL}   = 'C';
+	};
+
+	my $env_is = sub {
+		$ENV{LANGUAGE} = 'is';
+		$ENV{LC_ALL}   = $is_IS_locale;
+	};
+
+	# Translation's the same as the original
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	if ($has_gettext_library) {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "With", 'C');
+		}
+
+		{
+			my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');
+			local %ENV; $env_is->();
+			$test->($got, $expect, "With", $is_IS_locale);
+		}
+	} else {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "Without", 'C');
+		}
+
+		{
+			local %ENV; $env_is->();
+			$test->($got, $expect, "Without", 'is');
+		}
+	}
+}
diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh
new file mode 100755
index 0000000..a212460
--- /dev/null
+++ b/t/t0203-gettext-setlocale-sanity.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description="The Git C functions aren't broken by setlocale(3)"
+
+. ./lib-gettext.sh
+
+test_expect_success 'git show a ISO-8859-1 commit under C locale' '
+	. "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+	test_commit "iso-c-commit" iso-under-c &&
+	git show >out 2>err &&
+	! test -s err &&
+	grep -q "iso-c-commit" out
+'
+
+test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' '
+	. "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+	test_commit "iso-utf8-commit" iso-under-utf8 &&
+	LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err &&
+	! test -s err &&
+	grep -q "iso-utf8-commit" out
+'
+
+test_done
diff --git a/t/t0204-gettext-reencode-sanity.sh b/t/t0204-gettext-reencode-sanity.sh
new file mode 100755
index 0000000..189af90
--- /dev/null
+++ b/t/t0204-gettext-reencode-sanity.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description="Gettext reencoding of our *.po/*.mo files works"
+
+. ./lib-gettext.sh
+
+
+test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Icelandic' '
+    printf "TILRAUN: Halló Heimur!" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Hello World!" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Runes' '
+    printf "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Old English Runes" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Icelandic' '
+    printf "TILRAUN: Halló Heimur!" | iconv -f UTF-8 -t ISO8859-1 >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Hello World!" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Runes' '
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Old English Runes" >runes &&
+
+	if grep "^TEST: Old English Runes$" runes
+	then
+		say "Your system can not handle this complexity and returns the string as-is"
+	else
+		# Both Solaris and GNU libintl will return this stream of
+		# question marks, so it is s probably portable enough
+		printf "TILRAUN: ?? ???? ??? ?? ???? ?? ??? ????? ??????????? ??? ?? ????" >runes-expect &&
+		test_cmp runes-expect runes
+	fi
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext: Fetching a UTF-8 msgid -> UTF-8' '
+    printf "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: ‘single’ and “double” quotes" >actual &&
+    test_cmp expect actual
+'
+
+# How these quotes get transliterated depends on the gettext implementation:
+#
+#   Debian:  ,einfaldar' og ,,tvöfaldar" [GNU libintl]
+#   FreeBSD: `einfaldar` og "tvöfaldar"  [GNU libintl]
+#   Solaris: ?einfaldar? og ?tvöfaldar?  [Solaris libintl]
+#
+# Just make sure the contents are transliterated, and don't use grep -q
+# so that these differences are emitted under --verbose for curious
+# eyes.
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Fetching a UTF-8 msgid -> ISO-8859-1' '
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: ‘single’ and “double” quotes" >actual &&
+    grep "einfaldar" actual &&
+    grep "$(echo tvöfaldar | iconv -f UTF-8 -t ISO8859-1)" actual
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext.c: git init UTF-8 -> UTF-8' '
+    printf "Bjó til tóma Git lind" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" git init repo >actual &&
+    test_when_finished "rm -rf repo" &&
+    grep "^$(cat expect) " actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext.c: git init UTF-8 -> ISO-8859-1' '
+    printf "Bjó til tóma Git lind" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" git init repo >actual &&
+    test_when_finished "rm -rf repo" &&
+    grep "^$(cat expect | iconv -f UTF-8 -t ISO8859-1) " actual
+'
+
+test_done
diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh
new file mode 100755
index 0000000..2361590
--- /dev/null
+++ b/t/t0205-gettext-poison.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext Shell poison'
+
+. ./lib-gettext.sh
+
+test_expect_success GETTEXT_POISON "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' '
+    test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison"
+'
+
+test_expect_success GETTEXT_POISON 'gettext: our gettext() fallback has poison semantics' '
+    printf "# GETTEXT POISON #" >expect &&
+    gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "# GETTEXT POISON #" >expect &&
+    gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_POISON 'eval_gettext: our eval_gettext() fallback has poison semantics' '
+    printf "# GETTEXT POISON #" >expect &&
+    eval_gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "# GETTEXT POISON #" >expect &&
+    eval_gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index bdd9513..9cfabe4 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -44,6 +44,7 @@ export LANG LC_ALL PAGER TERM TZ
 EDITOR=:
 unset VISUAL
 unset EMAIL
+unset LANGUAGE
 unset $(perl -e '
 	my @env = keys %ENV;
 	my $ok = join("|", qw(
@@ -1113,12 +1114,14 @@ esac
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
 test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
+test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # Can we rely on git's output in the C locale?
 if test -n "$GETTEXT_POISON"
 then
 	GIT_GETTEXT_POISON=YesPlease
 	export GIT_GETTEXT_POISON
+	test_set_prereq GETTEXT_POISON
 else
 	test_set_prereq C_LOCALE_OUTPUT
 fi
diff --git a/upload-pack.c b/upload-pack.c
index 470cffd..6f36f62 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -784,6 +784,8 @@ int main(int argc, char **argv)
 	int i;
 	int strict = 0;
 
+	git_setup_gettext();
+
 	packet_trace_identity("upload-pack");
 	git_extract_argv0_path(argv[0]);
 	read_replace_refs = 0;
diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh
index 09feb1f..d2d9dcc 100644
--- a/wrap-for-bin.sh
+++ b/wrap-for-bin.sh
@@ -15,7 +15,8 @@ else
 	export GIT_TEMPLATE_DIR
 fi
 GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
+GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/share/locale'
 PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
-export GIT_EXEC_PATH GITPERLLIB PATH
+export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR
 
 exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
-- 
1.7.6.3

^ permalink raw reply related

* Re: [git patches] libata updates, GPG signed (but see admin notes)
From: Johan Herland @ 2011-11-11  1:17 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Linus Torvalds, Ted Ts'o, Shawn Pearce, git, James Bottomley,
	Jeff Garzik, Andrew Morton, linux-ide, LKML
In-Reply-To: <7v39dvuca4.fsf@alter.siamese.dyndns.org>

On Thu, Nov 10, 2011 at 18:18, Junio C Hamano <junio@pobox.com> wrote:
> Johan Herland <johan@herland.net> writes:
>
>> What about having one notes ref per branch? If/when the branch is merged,
>> the associated notes ref containing the annotations for the commits on that
>> branch would be merged as well (using "git notes merge").
>
> That is a crude workaround that you could (with help from users) make it
> work, but it does not change the fact that the current mechanism to
> transfer and integrate notes across repositories is a bad match for what
> the "signed commit" type annotations wants to achieve. In fact, the need
> for such a workaround is an illustration of how bad a match the mechanism
> is.
>
> When you merge a history that has commit A into another history that did
> not have that commit, the act of creating a merge commit itself should be
> enough to make the resulting history to contain that commit. The commit
> DAG already expresses it, and if a parallel "notes" mechanism needs to be
> futzed with to match that DAG, and command like "merge" needs to be told
> to help that process, that is a shortcoming of the "notes" mechanism.

[ ...and from elsewhere in this thread: ]

> Note that in this thread, I am not saying that "git notes" mechanism is
> not good for anything. A tree whose node names encode an object name is a
> valid way to store the mapping from that object to a set of other objects,
> and we already agreed that as the "local" storage mechanism, "git notes"
> may be used as-is for the purpose of this thread.
>
> But the transfer and merge semantics "git notes" mechanism offers treats
> the entire "notes" that appear in _one_ repository and merging that set to
> the entire "notes" in another repository and it is not a good match for
> the purpose of this thread.

Ok. Point taken.

Given that we need an alternative way to transfer annotations between
repos (using auto-follow to select the relevant set of annotations, and
then transferring only those annotations): Can we leverage existing
functionality in "notes" where useful (e.g. using existing notes merge
strategies to deal with colliding annotations), while at the same time
extending the current "notes" feature with this alternative transfer
mechanism? FWIW, I expect there are other "notes" use cases that
would also prefer the auto-follow only-relevant transfer behavior.

So, how can we use "notes" to better support the transfer semantics you
suggest? The mapping from the object being annotated to the annotation
object is already contained in the notes tree, but the "timestamp" you
describe (needed to efficiently calculate the set of annotations to
auto-follow) is not [1]. However, we could easily enough add a sorted
list of (timestamp,  annotated object name) pairs, to allow fast lookup
of annotations created after a given timestamp. We could even store this
list in a blob or tree object referenced directly from the notes tree [2].


Have fun! :)

...Johan


[1]: Although I did at some point experiment with using timestamps in the
internal organization of the notes tree (see for example
http://article.gmane.org/gmane.comp.version-control.git/127966 ), I ended
up using only the annotated object name (with flexible fanout). I don't
think that reintroducing timestamps in the notes tree organization will
pay off, because we need both lookup by annotated SHA1 and lookup by
newer-than-given-timestamp to be fast, and there's AFAIK no way to get
both from a single notes tree organzation.

[2]: E.g. accessible with "git cat-file refs/notes/foo:timestamps". When
a notes tree contains an entry that is obviously not an object name (SHA1),
the notes code will leave it alone/untouched in the tree (see "struct
non_note" and associated code in notes.c for further details).

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply

* [PATCH] mktree: fix a memory leak in write_tree()
From: Liu Yuan @ 2011-11-11  3:51 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

From: Liu Yuan <tailai.ly@taobao.com>

We forget to call strbuf_release to release the buf memory.

Signed-off-by: Liu Yuan <tailai.ly@taobao.com>
---
 builtin/mktree.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/builtin/mktree.c b/builtin/mktree.c
index 098395f..4ae1c41 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -60,6 +60,7 @@ static void write_tree(unsigned char *sha1)
 	}
 
 	write_sha1_file(buf.buf, buf.len, tree_type, sha1);
+	strbuf_release(&buf);
 }
 
 static const char *mktree_usage[] = {
-- 
1.7.6.1

^ permalink raw reply related

* Re: [git patches] libata updates, GPG signed (but see admin notes)
From: Junio C Hamano @ 2011-11-11  5:26 UTC (permalink / raw)
  To: Johan Herland
  Cc: Linus Torvalds, Ted Ts'o, Shawn Pearce, git, James Bottomley,
	Jeff Garzik, Andrew Morton, linux-ide, LKML
In-Reply-To: <CALKQrgcJmPHZG0bbgwoH6_htQODYVJtXYyHgSb_7qRKkJkb2Yw@mail.gmail.com>

Johan Herland <johan@herland.net> writes:

> Given that we need an alternative way to transfer annotations between
> repos (using auto-follow to select the relevant set of annotations, and
> then transferring only those annotations): Can we leverage existing
> functionality in "notes" where useful (e.g. using existing notes merge
> strategies to deal with colliding annotations), while at the same time
> extending the current "notes" feature with this alternative transfer
> mechanism? FWIW, I expect there are other "notes" use cases that
> would also prefer the auto-follow only-relevant transfer behavior.
>
> So, how can we use "notes" to better support the transfer semantics you
> suggest? The mapping from the object being annotated to the annotation
> object is already contained in the notes tree, but the "timestamp" you
> describe (needed to efficiently calculate the set of annotations to
> auto-follow) is not [1].

Please do not take the "timestamp" part too seriously.

I am starting to think that what we want in this context actually is very
close to annotated tags. I said we want a mapping from an annotated object
to "a set of other objects" that annotate it, but it was an unnecessary
and premature generalization. There is no reason that these annotations
have to be structured "Git" objects such as blobs and trees.

A set of annotated tags that have the same value on their "object" field
is a perfect match for "a set of annotations attached to a given object".

We already know that using the real tags has its own problems coming from
having to give each and every one of them unique names somewhere in the
refs hierarchy (be it refs/tags/ or refs/audit/), but imagine if we
somehow had a way to:

 - keep these annotated tags in the object store;

 - keep them from getting pruned even if they are not referenced from
   anywhere in refs/ hierarchy;

 - given an object, efficiently enumerate such annotate tags that refer to
   the object.

And then imagine that we are pushing history leading to a commit from one
repository to another. Both repositories store these "anonymous" (that is
what they are---they do not have a name in the refs/ hierarchy) tags.

The two repositories can individually enumerate all these "anonymous" tags
that annotate commits in the history that is being exchanged, and run a
set reconciliation algorithm (e.g. [*1*]) to find out the anonymous tags
that are missing from the recipient repository.

Such an approach does not require any timestamp.

My point is _not_ that the alternative in this message is superiour to the
handwaving in my other message, but is that I think it may not be the best
approach to think what needs to be added to "notes" to make it applicable
for the problem we are solving.

Rather, I think we should design how the overall system should look like
(i.e. what property the resulting system should have) and then find out
what is necessary in each part of the resulting solution (i.e. the list of
"somehow had a way to..." above, plus "efficient set reconciliation").


[Footnote]

*1* What's the Difference? Efficient Set Reconciliation without Prior
Context http://cseweb.ucsd.edu/~fuyeda/papers/sigcomm2011.pdf

^ permalink raw reply

* Update Linux kernel branch source from GIT
From: Eric Kom @ 2011-11-11  5:48 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vsjlyw0y4.fsf@alter.siamese.dyndns.org>


[-- Attachment #1.1: Type: text/plain, Size: 652 bytes --]

Good day,

Am new on this list, and also compile the kernel linux from git after
clone. since I don't use the tar kernel version, am use to clone a new
kernel branch instead to update it via patch.

Please can you explain to me how to make a patch after clone it?

This is my clone command:
 git clone
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-3.2


-- 
Your Truly

Eric Kom

2 Hennie Van Till, White River, 1240
erickom@kom.za.net | erickom@namekom.co.za | erickom@erickom.co.za
www.kom.za.net | www.kom.za.org | www.erickom.co.za

Key fingerprint: 513E E91A C243 3020 8735 09BB 2DBC 5AD7 A9DA 1EF5

[-- Attachment #1.2: 0xA9DA1EF5.asc --]
[-- Type: application/pgp-keys, Size: 2211 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply

* [PATCH/RFC] Restore line limit option in post-receive-email
From: Cheng Leong @ 2011-11-11  6:35 UTC (permalink / raw)
  To: git; +Cc: kpfleming, Cheng Leong

The hooks.emailmaxlines config currently has no effect. Stop
prep_for_email from clobbering the already-initialized maxlines
variable in the contrib/hooks/post-receive-email example.

Signed-off-by: Cheng Leong <leongc@alumni.rice.edu>
---
 contrib/hooks/post-receive-email |    1 -
 1 files changed, 0 insertions(+), 1 deletions(-)

diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index ba077c1..ac2e0ed 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -85,7 +85,6 @@ prep_for_email()
 	oldrev=$(git rev-parse $1)
 	newrev=$(git rev-parse $2)
 	refname="$3"
-	maxlines=$4
 
 	# --- Interpret
 	# 0000->1234 (create)
-- 
1.7.7.1.msysgit.0

^ permalink raw reply related

* Re: [PATCH/RFC] Restore line limit option in post-receive-email
From: Junio C Hamano @ 2011-11-11  6:56 UTC (permalink / raw)
  To: Cheng Leong; +Cc: git, kpfleming
In-Reply-To: <1320993311-27112-1-git-send-email-leongc@alumni.rice.edu>

Cheng Leong <leongc@alumni.rice.edu> writes:

> The hooks.emailmaxlines config currently has no effect. Stop
> prep_for_email from clobbering the already-initialized maxlines
> variable in the contrib/hooks/post-receive-email example.
>
> Signed-off-by: Cheng Leong <leongc@alumni.rice.edu>
> ---
>  contrib/hooks/post-receive-email |    1 -
>  1 files changed, 0 insertions(+), 1 deletions(-)
>
> diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
> index ba077c1..ac2e0ed 100755
> --- a/contrib/hooks/post-receive-email
> +++ b/contrib/hooks/post-receive-email
> @@ -85,7 +85,6 @@ prep_for_email()
>  	oldrev=$(git rev-parse $1)
>  	newrev=$(git rev-parse $2)
>  	refname="$3"
> -	maxlines=$4
>  
>  	# --- Interpret
>  	# 0000->1234 (create)

Umm, there is another place where $maxlines is used without
merit. Shouldn't we do something like below as well?

 contrib/hooks/post-receive-email |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index ba077c1..e27ca3c 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -743,6 +743,6 @@ else
 	while read oldrev newrev refname
 	do
 		prep_for_email $oldrev $newrev $refname || continue
-		generate_email $maxlines | send_mail
+		generate_email | send_mail
 	done
 fi

^ permalink raw reply related

* Re: Disappearing change on pull rebase
From: Johannes Sixt @ 2011-11-11  6:56 UTC (permalink / raw)
  To: Pitucha, Stanislaw Izaak; +Cc: git@vger.kernel.org
In-Reply-To: <3FF1328CB05DB74898F769F1BA17812C3E49B74699@GVW1348EXA.americas.hpqcorp.net>

Am 11/10/2011 14:35, schrieb Pitucha, Stanislaw Izaak:
> As mentioned in the original mail - the merge commit did have changes.
> Here's the log of reproducing it. The line containing "2" in changelog
> is gone from master after pull --rebase.
> ...
> disappearing_commit$ git merge --no-ff --no-commit some-branch
> Automatic merge went well; stopped before committing as requested
> disappearing_commit$ echo 2 >> changelog 
> disappearing_commit$ git add changelog
> disappearing_commit$ git commit
> [master e41e4c9] Merge branch 'some-branch'

This is by design. Rebase does not rebase merge commits because it is
assumed that merge commits only do what their name implies - to merge
branches of a forked history. As such, they do not introduce their own
changes. Follow this rule, i.e., make your change in a separate non-merge
commit, and you are fine.

-- Hannes

^ permalink raw reply

* Re: [PATCH/RFC] Restore line limit option in post-receive-email
From: Cheng Leong @ 2011-11-11  7:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, kpfleming
In-Reply-To: <7v4nybrvug.fsf@alter.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> wrote:
> Umm, there is another place where $maxlines is used without
> merit. Shouldn't we do something like below as well?
...
> -               generate_email $maxlines | send_mail
> +               generate_email | send_mail

Agree. $maxlines is harmless, but extraneous here.
Would you like me to reroll a patch with both or is this a trivial fixup?

Cheng

^ permalink raw reply

* Re: Disappearing change on pull rebase
From: Philippe Vaucher @ 2011-11-11  9:50 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: Pitucha, Stanislaw Izaak, git@vger.kernel.org
In-Reply-To: <4EBCC71D.6000505@viscovery.net>

> This is by design. Rebase does not rebase merge commits because it is
> assumed that merge commits only do what their name implies - to merge
> branches of a forked history. As such, they do not introduce their own
> changes. Follow this rule, i.e., make your change in a separate non-merge
> commit, and you are fine.

Doesn't this create a problem if you pull, resolve a conflit but do NOT
push, then pull --rebase some more commits later on? As I understand
it, the conflict resolution commit will be a merge commit and will be
thrown away by the git pull --rebase.

Philippe

^ permalink raw reply

* Re: Disappearing change on pull rebase
From: Johannes Sixt @ 2011-11-11 10:04 UTC (permalink / raw)
  To: Philippe Vaucher; +Cc: Pitucha, Stanislaw Izaak, git@vger.kernel.org
In-Reply-To: <CAGK7Mr6D6-4aNceTDCYTHabA3vnxh+uvQ=GOeS_3nrL9rjmc9w@mail.gmail.com>

Am 11/11/2011 10:50, schrieb Philippe Vaucher:
>> This is by design. Rebase does not rebase merge commits because it is
>> assumed that merge commits only do what their name implies - to merge
>> branches of a forked history. As such, they do not introduce their own
>> changes. Follow this rule, i.e., make your change in a separate non-merge
>> commit, and you are fine.
> 
> Doesn't this create a problem if you pull, resolve a conflit but do NOT
> push, then pull --rebase some more commits later on? As I understand
> it, the conflict resolution commit will be a merge commit and will be
> thrown away by the git pull --rebase.

You are correct, but it is not a problem: During the rebase, the same
conflicts will arise as during the merge, and you will be forced to
resolve them before you can complete the rebase. Therefore, nothing will
be lost.

-- Hannes

^ permalink raw reply

* Re: Disappearing change on pull rebase
From: Philippe Vaucher @ 2011-11-11 10:08 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: Pitucha, Stanislaw Izaak, git@vger.kernel.org
In-Reply-To: <4EBCF34A.3090908@viscovery.net>

>> Doesn't this create a problem if you pull, resolve a conflit but do NOT
>> push, then pull --rebase some more commits later on? As I understand
>> it, the conflict resolution commit will be a merge commit and will be
>> thrown away by the git pull --rebase.
>
> You are correct, but it is not a problem: During the rebase, the same
> conflicts will arise as during the merge, and you will be forced to
> resolve them before you can complete the rebase. Therefore, nothing will
> be lost.

Doing the same conflict resolution twice is kinda irritating... but
ok, fair enough.
I guess when you resolve a conflict you're supposed to push to avoid this :)

Philippe

^ permalink raw reply

* Re: [RFC/PATCH] i18n: add infrastructure for translating Git with gettext
From: Jakub Narebski @ 2011-11-11 10:27 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git
In-Reply-To: <1320970164-31694-2-git-send-email-avarab@gmail.com>

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> = Perl
> 
> Perl code that wants 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.

Nice of you using libintl-perl instead of old Locale::MakeText
(with gettext compatibility layer).

> See <AANLkTilYD_NyIZMyj9dHtVk-ylVBfvyxpCC7982LWnVd@mail.gmail.com> for
> a further elaboration on this topic.

http://thread.gmane.org/gmane.comp.version-control.git/148446/focus=148478

> = 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.
> 
> I originally tried to detect if the system supported `echo -n' but
> I found this to be a waste of time. My benchmarks on Linux, Solaris
> and FreeBSD reveal that printf(1) is fast enough, especially since
> we aren't calling gettext() from within any tight loops, and
> unlikely to ever do so.

Didn't we decide that the only sane way to handle eval_gettext
is to provide minimal implemetation in the form of external command?
 

Thanks for working on this.
-- 
Jakub Narębski

^ permalink raw reply

* Re: [RFC/PATCH] i18n: add infrastructure for translating Git with gettext
From: Ævar Arnfjörð Bjarmason @ 2011-11-11 10:34 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <m31utfnedi.fsf@localhost.localdomain>

On Fri, Nov 11, 2011 at 11:27, Jakub Narebski <jnareb@gmail.com> wrote:

>> I originally tried to detect if the system supported `echo -n' but
>> I found this to be a waste of time. My benchmarks on Linux, Solaris
>> and FreeBSD reveal that printf(1) is fast enough, especially since
>> we aren't calling gettext() from within any tight loops, and
>> unlikely to ever do so.
>
> Didn't we decide that the only sane way to handle eval_gettext
> is to provide minimal implemetation in the form of external command?

This comment is a bit out of date and probably shouldn't be in the
commit message. But it doesn't have to do with eval_gettext. I mean at
one point I tried to change the fall-through wrapper from:

    gettext () {
	    printf "%s" "$1"
	}

to:

    gettext () {
	    echo -n $1
	}

But found it to bee too troublesome with various echo implementations
and just not worth it for the complexity.

But yeah, for eval_gettext() we need an external program, which we
already have in git.git as git-sh-i18n--envsubst.

^ permalink raw reply

* Re: [RFC/PATCH] remote: add new sync command
From: Jakub Narebski @ 2011-11-11 10:35 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Felipe Contreras, Jeff King, git
In-Reply-To: <7vobwmvuei.fsf@alter.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> writes:
> Felipe Contreras <felipe.contreras@gmail.com> writes:
> 
> > Perhaps these 'git remote' commands should be removed in 1.8 then.
> 
> It is true that it was a long-term goal to deprecate many parts of the
> "git remote" script that started as a hack to scratch itches "git fetch"
> in the older days did not directly scratch for people, e.g. fetching from
> multiple remotes in one go.
> 
> I do not think 1.7.X series to 1.8 is a big enough jump to remove
> duplicated features, though.
 
I am using "git remote update" to fetch a _subset_ of remotes;
does "git fetch" offers such feature already?

-- 
Jakub Narębski

^ permalink raw reply

* Re: [RFC/PATCH] remote: add new sync command
From: Felipe Contreras @ 2011-11-11 12:30 UTC (permalink / raw)
  To: Jeff King; +Cc: git
In-Reply-To: <20111108181442.GA17317@sigill.intra.peff.net>

On Tue, Nov 8, 2011 at 8:14 PM, Jeff King <peff@peff.net> wrote:
> On Tue, Nov 08, 2011 at 07:31:09PM +0200, Felipe Contreras wrote:
>
>> >  1. git push --prune <remote> :
>> >
>> >     I.e., use the "matching" refspec to not push new things, but turn
>> >     on pruning.
>>
>> I guess so, but ":" seems a bit obscure.
>
> Yeah, it is. It's also the default, so you could just do:
>
>  git push --prune <remote>

That would work only if not configured otherwise; remote.<foo>.push,
push.default

> Although some people have argued for changing the default in future
> versions. I don't know what the status of that is.

IMO the default doesn't matter, it should be easy for everyone.

>> >  2. git push <remote> refs/heads/*
>> >
>> >     Turn off pruning, but use an explicit refspec, not just "matching",
>> >     which will push all local branches.
>>
>> Isn't refs/heads/* the same as --all? BTW. I think --all is confusing,
>> should be --branches, or something.
>
> Doesn't --all mean all refs, including tags and branches?

Nope, only branches, try it. I also found it strange. And what is
more, you can't use --all and --tags at the same time. Totally
strange.

Also, --all doesn't push other refs (say refs/foobar/test)

I think this area has been neglected.

> I thought that was the thing you were avoiding.

--all + --tags is not the same as --mirror (refs/foobar/* is pushed
only by --mirror).

And yes, in this particular use-case that's what I am trying to avoid,
but in other use-cases (like creating a new repo and pushing
*everything*), a *true* --all would be nice.

> We could add syntactic sugar to make
> --branches mean "refs/heads/*". But I do worry that pseudo-options like
> that just end up creating more confusion (I seem to recall there being a
> lot of confusion about "--tags", which is more or less the same thing).

But it's not, that could explain part of the confusion. I think these
would be totally intuitive.

 --branches
 --tags
 --other
 --all
 --update
 --prune

But what about 'git fetch'? You didn't comment anything. I think we
should try to be consistent in these imaginary future options, maybe
to devise a transition, or at least to identify good names for the new
options.

Cheers.

-- 
Felipe Contreras

^ permalink raw reply

* Re: Thoughts on gitk's memory footprint over linux-2.6.git
From: Felipe Contreras @ 2011-11-11 12:44 UTC (permalink / raw)
  To: Martin Langhoff; +Cc: Git Mailing List
In-Reply-To: <CACPiFC+T1EZ1CSakQxsYZhsnHc-ZsN1-=tpoi-NaQSdpU5Yxkg@mail.gmail.com>

On Mon, Sep 26, 2011 at 10:38 PM, Martin Langhoff
<martin.langhoff@gmail.com> wrote:
> However, I find it extremely annoying over the kernel tree, due to its
> memory footprint. It is not the only thing I am running, (Chrome
> Browser, Gnome3, Firefox, many gnome Terminal windows, emacs), and
> given that I am looking at "just a couple of commits" I don't feel
> opening a few gitk instances should be problematic... except that it
> is.

Sometimes I do this:
 % gitk master..branch_1 master..branch_2 ...

But as I visualize more branches, it becomes tedious.

It would be nice to have --base option, and show only the commits
<base>..<branch>.

Cheers.

-- 
Felipe Contreras

^ permalink raw reply

* feature request: git annotate -w like git blame -w
From: Raoul Bhatia [IPAX] @ 2011-11-11 13:02 UTC (permalink / raw)
  To: git

hi!

is it possible to add a "git annotate -w" option like git blame has?

thanks,
raoul
ps. please reply to me in cc as i'm not subscribed to this list.
-- 
____________________________________________________________________
DI (FH) Raoul Bhatia M.Sc.          email.          r.bhatia@ipax.at
Technischer Leiter

IPAX - Aloy Bhatia Hava OG          web.          http://www.ipax.at
Barawitzkagasse 10/2/2/11           email.            office@ipax.at
1190 Wien                           tel.               +43 1 3670030
FN 277995t HG Wien                  fax.            +43 1 3670030 15
____________________________________________________________________

^ permalink raw reply

* Re: [PATCH 0/14] resumable network bundles
From: David Michael Barr @ 2011-11-11 13:13 UTC (permalink / raw)
  To: Jeff King; +Cc: git
In-Reply-To: <20111110074330.GA27925@sigill.intra.peff.net>

On Thu, Nov 10, 2011 at 6:43 PM, Jeff King <peff@peff.net> wrote:
> One possible option for resumable clones that has been discussed is
> letting the server point the client by http to a static bundle
> containing most of history, followed by a fetch from the actual git repo
> (which should be much cheaper now that we have all of the bundled
> history).  This series implements "step 0" of this plan: just letting
> bundles be fetched across the network in the first place.
>
> Shawn raised some issues about using bundles for this (as opposed to
> accessing the packfiles themselves); specifically, this raises the I/O
> footprint of a repository that has to serve both the bundled version of
> the pack and the regular packfile.
>
> So it may be that we don't follow this plan all the way through.
> However, even if we don't, fetching bundles over http is still a useful
> thing to be able to do. Which makes this first step worth doing either
> way.
>
>  [01/14]: t/lib-httpd: check for NO_CURL
>  [02/14]: http: turn off curl signals
>  [03/14]: http: refactor http_request function
>  [04/14]: http: add a public function for arbitrary-callback request
>  [05/14]: remote-curl: use http callback for requesting refs
>  [06/14]: transport: factor out bundle to ref list conversion
>  [07/14]: bundle: add is_bundle_buf helper
>  [08/14]: remote-curl: free "discovery" object
>  [09/14]: remote-curl: auto-detect bundles when fetching refs
>  [10/14]: remote-curl: try base $URL after $URL/info/refs
>  [11/14]: progress: allow pure-throughput progress meters
>  [12/14]: remote-curl: show progress for bundle downloads
>  [13/14]: remote-curl: resume interrupted bundle transfers
>  [14/14]: clone: give advice on how to resume a failed clone
>
> -Peff
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

I just want to say thank you for doing this.--David Barr

^ permalink raw reply

* Re: feature request: git annotate -w like git blame -w
From: Jakub Narebski @ 2011-11-11 13:57 UTC (permalink / raw)
  To: Raoul Bhatia [IPAX]; +Cc: git
In-Reply-To: <4EBD1CF4.7040002@ipax.at>

"Raoul Bhatia [IPAX]" <r.bhatia@ipax.at> writes:

> is it possible to add a "git annotate -w" option like git blame has?

Why not use "git blame -c -w"?  `-c' turns on annotate-compatibile
output.

From git-annotate(1) manpage:

   The only difference between this command and git-blame(1) is that they  use
   slightly  different  output formats, and this command exists only for back-
   ward compatibility to support existing scripts, and provide a more familiar
   command name for people coming from other SCM systems.
 
> ps. please reply to me in cc as i'm not subscribed to this list.

This is usual behavior on this list.

-- 
Jakub Narębski

^ permalink raw reply

* Re: reset reports file as modified when it's in fact deleted
From: Carlos Martín Nieto @ 2011-11-11 14:08 UTC (permalink / raw)
  To: Jeff King; +Cc: git
In-Reply-To: <20111107162642.GA27055@sigill.intra.peff.net>

[-- Attachment #1: Type: text/plain, Size: 5184 bytes --]

On Mon, Nov 07, 2011 at 11:26:42AM -0500, Jeff King wrote:
> On Mon, Nov 07, 2011 at 10:43:30AM +0100, Carlos Martín Nieto wrote:
> 
> > When I delete a file (git rm) and then reset so it exists in the index
> > again, the message tells me 'M file.txt' even though the file doesn't
> > exist in the worktree anymore. Running git status afterwards does give
> > the correct output. So, here's the minimal steps to reproduce:
> > 
> >     $ git init
> >     Initialized empty Git repository in /home/carlos/test/reset-err/.git/
> >     $ touch file.txt
> >     $ git add file.txt
> >     $ git ci file.txt -m initial
> >     [master (root-commit) a536393] initial
> >      0 files changed, 0 insertions(+), 0 deletions(-)
> >      create mode 100644 file.txt
> >     $ git rm file.txt
> >     rm 'file.txt'
> >     $ git reset -- file.txt
> >     Unstaged changes after reset:
> >     M		 file.txt
> >     $ git status -b -s
> >     ## master
> >      D file.txt
> 
> You can simplify this even further by not touching the index at all:
> 
>   git init -q &&
>   >file && git add file && git commit -q -m initial &&
>   rm file &&
>   git reset
> 
> produces:
> 
>   Unstaged changes after reset:
>   M       file

Ah, I see. I got the previous sequence because that's what we have in
an instruction manual and where we saw it.

> 
> > I'd expect the output after "Unstaged changes after reset" to tell me
> > file.txt has been deleted instead of modified. This happens with
> > 1.7.8-rc0, 1.7.7 and 1.7.4.1 and I expect with many more that I don't
> > have here.
> > 
> > I thought the index diff code might have been checking the index at the
> > wrong time, but I can run 'git reset HEAD -- file.txt' as many times
> > as I want, and it will still say 'M', so now I'm not sure.
> 
> The index diff code isn't running at all. Those messages are produced by
> refresh_index, which outputs only two flags: modified or unmerged. I
> think the reason for this is somewhat historical. You would run
> "update-index --refresh", and it would helpfully say "by the way, when
> refreshing this entry, I noticed that it is in need of being updated in
> the index". The text was "file.txt: needs update".
> 
> Later, many porcelain commands started to refresh the index themselves,
> and the "needs update" message was very confusing. So it was switched to
> the more familiar "M file.txt" (though you can still see the original
> plumbing message if you run update-index yourself).
> 
> I think it is a little more friendly to distinguish deletion from just
> modification. And there's shouldn't be any compatibility questions, as
> these are explicitly porcelain output (plumbing will still say "needs
> update").
> 
> There are a few other cases users might expect to see. We'll never show
> copies or renames, of course, because we aren't actually doing a diff
> with copy detection. We won't see an "A"dded file, because such a file
> would be in the working tree but not the index, meaning it is not
> tracked.
> 
> We could see a "T"ypechange, but the distinction between that and a
> modified file is lost by the time we get to refresh_index. We could pass
> it up, but I wonder if it's really worth it.

That's probably overkill. The issue with reporting 'M' for a deleted
file is that it conflicts with what the status output would say, even
though it's in the same format.

> 
> The patch to do "D"eleted is pretty simple:
> 
> diff --git a/read-cache.c b/read-cache.c
> index dea7cd8..cc1ebdf 100644
> --- a/read-cache.c
> +++ b/read-cache.c
> @@ -1103,9 +1103,11 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
>  	int in_porcelain = (flags & REFRESH_IN_PORCELAIN);
>  	unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0;
>  	const char *needs_update_fmt;
> +	const char *needs_rm_fmt;
>  	const char *needs_merge_fmt;
>  
>  	needs_update_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
> +	needs_rm_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n");
>  	needs_merge_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");

While the name fits in with the rest of the variables, it's kind of
the wrong way around, isn't it? It doesn't need an 'rm', it /was/
rm'd. Other than that stupid thing, the patch works great, thanks. Can
we get it into git?

>  	for (i = 0; i < istate->cache_nr; i++) {
>  		struct cache_entry *ce, *new;
> @@ -1145,7 +1147,10 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
>  			}
>  			if (quiet)
>  				continue;
> -			show_file(needs_update_fmt, ce->name, in_porcelain, &first, header_msg);
> +			if (cache_errno == ENOENT)
> +				show_file(needs_rm_fmt, ce->name, in_porcelain, &first, header_msg);
> +			else
> +				show_file(needs_update_fmt, ce->name, in_porcelain, &first, header_msg);
>  			has_errors = 1;
>  			continue;
>  		}
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]

^ permalink raw reply

* Re: Thoughts on gitk's memory footprint over linux-2.6.git
From: Johannes Sixt @ 2011-11-11 14:19 UTC (permalink / raw)
  To: Felipe Contreras; +Cc: Martin Langhoff, Git Mailing List
In-Reply-To: <CAMP44s1cZc5OZ0L0zG-Wu+QVpu7xv4-JtWTBtPvnjO7sUFeM9w@mail.gmail.com>

Am 11/11/2011 13:44, schrieb Felipe Contreras:
> On Mon, Sep 26, 2011 at 10:38 PM, Martin Langhoff
> <martin.langhoff@gmail.com> wrote:
>> However, I find it extremely annoying over the kernel tree, due to its
>> memory footprint. It is not the only thing I am running, (Chrome
>> Browser, Gnome3, Firefox, many gnome Terminal windows, emacs), and
>> given that I am looking at "just a couple of commits" I don't feel
>> opening a few gitk instances should be problematic... except that it
>> is.
> 
> Sometimes I do this:
>  % gitk master..branch_1 master..branch_2 ...
> 
> But as I visualize more branches, it becomes tedious.
> 
> It would be nice to have --base option, and show only the commits
> <base>..<branch>.

What's wrong with

     gitk master..branch_1 branch_2 branch_3 branch_4 branch_5
or
     gitk --branches --not master

? (I do that all the time.) Recall that 'master..branch_1' is short for
'^master branch_1'. It is sufficient to specify the negative ref, ^master,
only once.

-- Hannes

^ permalink raw reply

* Re: Thoughts on gitk's memory footprint over linux-2.6.git
From: Felipe Contreras @ 2011-11-11 16:04 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: Martin Langhoff, Git Mailing List
In-Reply-To: <4EBD2EFF.1010000@viscovery.net>

On Fri, Nov 11, 2011 at 4:19 PM, Johannes Sixt <j.sixt@viscovery.net> wrote:
> Am 11/11/2011 13:44, schrieb Felipe Contreras:
>> It would be nice to have --base option, and show only the commits
>> <base>..<branch>.
>
> What's wrong with
>
>     gitk master..branch_1 branch_2 branch_3 branch_4 branch_5
> or
>     gitk --branches --not master
>
> ? (I do that all the time.) Recall that 'master..branch_1' is short for
> '^master branch_1'. It is sufficient to specify the negative ref, ^master,
> only once.

Ah, nice! Thanks a lot :)

-- 
Felipe Contreras

^ permalink raw reply

* Re: Disappearing change on pull rebase
From: Junio C Hamano @ 2011-11-11 16:31 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: Pitucha, Stanislaw Izaak, git@vger.kernel.org
In-Reply-To: <4EBCC71D.6000505@viscovery.net>

Johannes Sixt <j.sixt@viscovery.net> writes:

> Am 11/10/2011 14:35, schrieb Pitucha, Stanislaw Izaak:
>> As mentioned in the original mail - the merge commit did have changes.
>> Here's the log of reproducing it. The line containing "2" in changelog
>> is gone from master after pull --rebase.
>> ...
>> disappearing_commit$ git merge --no-ff --no-commit some-branch
>> Automatic merge went well; stopped before committing as requested
>> disappearing_commit$ echo 2 >> changelog 
>> disappearing_commit$ git add changelog
>> disappearing_commit$ git commit
>> [master e41e4c9] Merge branch 'some-branch'
>
> This is by design. Rebase does not rebase merge commits because it is
> assumed that merge commits only do what their name implies - to merge
> branches of a forked history. As such, they do not introduce their own
> changes. Follow this rule, i.e., make your change in a separate non-merge
> commit, and you are fine.

While that may be technically correct, I wonder if we can be a bit more
helpful to the users in such a case (upfront I admit that I have a strong
suspicion that this is a hard problem in general). One typical use of
"rebase" is to linearize a history that has unfortunate merges that did
not have to be there, so refusing "git rebase A..B" when there is a merge
in "git rev-list --merges A..B" is not a solution. But would it help if we
warned about the merges when we find that there is something _interesting_
going on in them, e.g. an evil merge that adds material that did not exist
in any of the parents [*1*]? The warning message may diagnose "you asked
me to linearize the history by picking commits on the non-merge segments
and replaying them, but there may be changes made in this merge commit,
and it does this interesting thing: $(git show -c $that_merge_commit)" and
may further suggest "if you do not want to linearize but just transplant
the history, perhaps you want to run the command with the '-m' option?".

[Footnote]

*1* This is a hard problem and not just the matter of looking at "show -c"
output. A "-s ours" merge would appear empty in "show -c" but it _is_ an
interesting event that makes the result of linearizing non-merge segments
vastly different from the original. Also material that did not exist in
any of the parents is not necessarily evil (e.g. the side branch may have
added one parameter to a function and updated its call sites, while our
branch may have added a different parameter to the same function. The
update to the call sites in the merge result should pass two more parameters
from the common ancestor, and different from either of the parents).

^ permalink raw reply

* Re: [RFC/PATCH] remote: add new sync command
From: Junio C Hamano @ 2011-11-11 16:38 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: Felipe Contreras, Jeff King, git
In-Reply-To: <m3wrb7lzg8.fsf@localhost.localdomain>

Jakub Narebski <jnareb@gmail.com> writes:

> Junio C Hamano <gitster@pobox.com> writes:
>> Felipe Contreras <felipe.contreras@gmail.com> writes:
>> 
>> > Perhaps these 'git remote' commands should be removed in 1.8 then.
>> 
>> It is true that it was a long-term goal to deprecate many parts of the
>> "git remote" script that started as a hack to scratch itches "git fetch"
>> in the older days did not directly scratch for people, e.g. fetching from
>> multiple remotes in one go.
>> 
>> I do not think 1.7.X series to 1.8 is a big enough jump to remove
>> duplicated features, though.
>  
> I am using "git remote update" to fetch a _subset_ of remotes;
> does "git fetch" offers such feature already?

Heh, look at builtin/remote.c::update() and report what you see.  It just
calls into "git fetch" and let the command fetch either from a single
repository or from a remote group. "git remote update" is not even aware
of the remote groups; the expansion is done by "git fetch".

Whoever added "multiple repositories" feature to "git fetch" in order to
support "remote update <group>" apparently under-documented it.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox