Merge branch 'jn/editor-pager'

* jn/editor-pager:
  Provide a build time default-pager setting
  Provide a build time default-editor setting
  am -i, git-svn: use "git var GIT_PAGER"
  add -i, send-email, svn, p4, etc: use "git var GIT_EDITOR"
  Teach git var about GIT_PAGER
  Teach git var about GIT_EDITOR
  Suppress warnings from "git var -l"
  Do not use VISUAL editor on dumb terminals
  Handle more shell metacharacters in editor names
This commit is contained in:
Junio C Hamano 2009-11-20 23:48:52 -08:00
commit 376f39fbea
19 changed files with 179 additions and 70 deletions

View file

@ -387,9 +387,7 @@ core.editor::
Commands such as `commit` and `tag` that lets you edit Commands such as `commit` and `tag` that lets you edit
messages by launching an editor uses the value of this messages by launching an editor uses the value of this
variable when it is set, and the environment variable variable when it is set, and the environment variable
`GIT_EDITOR` is not set. The order of preference is `GIT_EDITOR` is not set. See linkgit:git-var[1].
`GIT_EDITOR` environment, `core.editor`, `VISUAL` and
`EDITOR` environment variables and then finally `vi`.
core.pager:: core.pager::
The command that git will use to paginate output. Can The command that git will use to paginate output. Can

View file

@ -323,7 +323,7 @@ ENVIRONMENT AND CONFIGURATION VARIABLES
The editor used to edit the commit log message will be chosen from the The editor used to edit the commit log message will be chosen from the
GIT_EDITOR environment variable, the core.editor configuration variable, the GIT_EDITOR environment variable, the core.editor configuration variable, the
VISUAL environment variable, or the EDITOR environment variable (in that VISUAL environment variable, or the EDITOR environment variable (in that
order). order). See linkgit:git-var[1] for details.
HOOKS HOOKS
----- -----

View file

@ -60,8 +60,8 @@ The --bcc option must be repeated for each user you want on the bcc list.
The --cc option must be repeated for each user you want on the cc list. The --cc option must be repeated for each user you want on the cc list.
--compose:: --compose::
Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an Invoke a text editor (see GIT_EDITOR in linkgit:git-var[1])
introductory message for the patch series. to edit an introductory message for the patch series.
+ +
When '--compose' is used, git send-email will use the From, Subject, and When '--compose' is used, git send-email will use the From, Subject, and
In-Reply-To headers specified in the message. If the body of the message In-Reply-To headers specified in the message. If the body of the message

View file

@ -36,6 +36,20 @@ GIT_AUTHOR_IDENT::
GIT_COMMITTER_IDENT:: GIT_COMMITTER_IDENT::
The person who put a piece of code into git. The person who put a piece of code into git.
GIT_EDITOR::
Text editor for use by git commands. The value is meant to be
interpreted by the shell when it is used. Examples: `~/bin/vi`,
`$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe"
--nofork`. The order of preference is the `$GIT_EDITOR`
environment variable, then `core.editor` configuration, then
`$VISUAL`, then `$EDITOR`, and then finally 'vi'.
GIT_PAGER::
Text viewer for use by git commands (e.g., 'less'). The value
is meant to be interpreted by the shell. The order of preference
is the `$GIT_PAGER` environment variable, then `core.pager`
configuration, then `$PAGER`, and then finally 'less'.
Diagnostics Diagnostics
----------- -----------
You don't exist. Go away!:: You don't exist. Go away!::

View file

@ -204,6 +204,18 @@ all::
# memory allocators with the nedmalloc allocator written by Niall Douglas. # memory allocators with the nedmalloc allocator written by Niall Douglas.
# #
# Define NO_REGEX if you have no or inferior regex support in your C library. # Define NO_REGEX if you have no or inferior regex support in your C library.
#
# Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if
# you want to use something different. The value will be interpreted by the
# shell at runtime when it is used.
#
# Define DEFAULT_EDITOR to a sensible editor command (defaults to "vi") if you
# want to use something different. The value will be interpreted by the shell
# if necessary when it is used. Examples:
#
# DEFAULT_EDITOR='~/bin/vi',
# DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
# DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
@$(SHELL_PATH) ./GIT-VERSION-GEN @$(SHELL_PATH) ./GIT-VERSION-GEN
@ -1374,6 +1386,22 @@ BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
$(COMPAT_CFLAGS) $(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS) LIB_OBJS += $(COMPAT_OBJS)
# Quote for C
ifdef DEFAULT_EDITOR
DEFAULT_EDITOR_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_EDITOR)))"
DEFAULT_EDITOR_CQ_SQ = $(subst ','\'',$(DEFAULT_EDITOR_CQ))
BASIC_CFLAGS += -DDEFAULT_EDITOR='$(DEFAULT_EDITOR_CQ_SQ)'
endif
ifdef DEFAULT_PAGER
DEFAULT_PAGER_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_PAGER)))"
DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ))
BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
endif
ALL_CFLAGS += $(BASIC_CFLAGS) ALL_CFLAGS += $(BASIC_CFLAGS)
ALL_LDFLAGS += $(BASIC_LDFLAGS) ALL_LDFLAGS += $(BASIC_LDFLAGS)

View file

@ -751,6 +751,8 @@ extern const char *git_author_info(int);
extern const char *git_committer_info(int); extern const char *git_committer_info(int);
extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
extern const char *fmt_name(const char *name, const char *email); extern const char *fmt_name(const char *name, const char *email);
extern const char *git_editor(void);
extern const char *git_pager(void);
struct checkout { struct checkout {
const char *base_dir; const char *base_dir;

View file

@ -729,13 +729,10 @@ class P4Submit(Command):
tmpFile.write(submitTemplate + separatorLine + diff + newdiff) tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
tmpFile.close() tmpFile.close()
mtime = os.stat(fileName).st_mtime mtime = os.stat(fileName).st_mtime
defaultEditor = "vi"
if platform.system() == "Windows":
defaultEditor = "notepad"
if os.environ.has_key("P4EDITOR"): if os.environ.has_key("P4EDITOR"):
editor = os.environ.get("P4EDITOR") editor = os.environ.get("P4EDITOR")
else: else:
editor = os.environ.get("EDITOR", defaultEditor); editor = read_pipe("git var GIT_EDITOR")
system(editor + " " + fileName) system(editor + " " + fileName)
response = "y" response = "y"

View file

@ -2,24 +2,38 @@
#include "strbuf.h" #include "strbuf.h"
#include "run-command.h" #include "run-command.h"
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env) #ifndef DEFAULT_EDITOR
{ #define DEFAULT_EDITOR "vi"
const char *editor, *terminal; #endif
const char *git_editor(void)
{
const char *editor = getenv("GIT_EDITOR");
const char *terminal = getenv("TERM");
int terminal_is_dumb = !terminal || !strcmp(terminal, "dumb");
editor = getenv("GIT_EDITOR");
if (!editor && editor_program) if (!editor && editor_program)
editor = editor_program; editor = editor_program;
if (!editor) if (!editor && !terminal_is_dumb)
editor = getenv("VISUAL"); editor = getenv("VISUAL");
if (!editor) if (!editor)
editor = getenv("EDITOR"); editor = getenv("EDITOR");
terminal = getenv("TERM"); if (!editor && terminal_is_dumb)
if (!editor && (!terminal || !strcmp(terminal, "dumb"))) return NULL;
return error("Terminal is dumb but no VISUAL nor EDITOR defined.");
if (!editor) if (!editor)
editor = "vi"; editor = DEFAULT_EDITOR;
return editor;
}
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
{
const char *editor = git_editor();
if (!editor)
return error("Terminal is dumb, but EDITOR unset");
if (strcmp(editor, ":")) { if (strcmp(editor, ":")) {
size_t len = strlen(editor); size_t len = strlen(editor);
@ -28,7 +42,7 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
const char *args[6]; const char *args[6];
struct strbuf arg0 = STRBUF_INIT; struct strbuf arg0 = STRBUF_INIT;
if (strcspn(editor, "$ \t'") != len) { if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) {
/* there are specials */ /* there are specials */
strbuf_addf(&arg0, "%s \"$@\"", editor); strbuf_addf(&arg0, "%s \"$@\"", editor);
args[i++] = "sh"; args[i++] = "sh";

View file

@ -990,8 +990,7 @@ sub edit_hunk_manually {
EOF EOF
close $fh; close $fh;
my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor") chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
|| $ENV{VISUAL} || $ENV{EDITOR} || "vi";
system('sh', '-c', $editor.' "$@"', $editor, $hunkfile); system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
if ($? != 0) { if ($? != 0) {

View file

@ -649,7 +649,10 @@ do
[eE]*) git_editor "$dotest/final-commit" [eE]*) git_editor "$dotest/final-commit"
action=again ;; action=again ;;
[vV]*) action=again [vV]*) action=again
LESS=-S ${PAGER:-less} "$dotest/patch" ;; : ${GIT_PAGER=$(git var GIT_PAGER)}
: ${LESS=-FRSX}
export LESS
$GIT_PAGER "$dotest/patch" ;;
*) action=again ;; *) action=again ;;
esac esac
done done

View file

@ -162,7 +162,8 @@ sub format_2822_time {
# Handle interactive edition of files. # Handle interactive edition of files.
my $multiedit; my $multiedit;
my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi"; my $editor = Git::command_oneline('var', 'GIT_EDITOR');
sub do_edit { sub do_edit {
if (defined($multiedit) && !$multiedit) { if (defined($multiedit) && !$multiedit) {
map { map {

View file

@ -99,19 +99,12 @@ set_reflog_action() {
} }
git_editor() { git_editor() {
: "${GIT_EDITOR:=$(git config core.editor)}" if test -z "${GIT_EDITOR:+set}"
: "${GIT_EDITOR:=${VISUAL:-${EDITOR}}}" then
case "$GIT_EDITOR,$TERM" in GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
,dumb) fi
echo >&2 "No editor specified in GIT_EDITOR, core.editor, VISUAL,"
echo >&2 "or EDITOR. Tried to fall back to vi but terminal is dumb." eval "$GIT_EDITOR" '"$@"'
echo >&2 "Please set one of these variables to an appropriate"
echo >&2 "editor or run $0 with options that will not cause an"
echo >&2 "editor to be invoked (e.g., -m or -F for git-commit)."
exit 1
;;
esac
eval "${GIT_EDITOR:=vi}" '"$@"'
} }
is_bare_repository () { is_bare_repository () {

View file

@ -1332,9 +1332,8 @@ sub get_commit_entry {
close $log_fh or croak $!; close $log_fh or croak $!;
if ($_edit || ($type eq 'tree')) { if ($_edit || ($type eq 'tree')) {
my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi'; chomp(my $editor = command_oneline(qw(var GIT_EDITOR)));
# TODO: strip out spaces, comments, like git-commit.sh system('sh', '-c', $editor.' "$@"', $editor, $commit_editmsg);
system($editor, $commit_editmsg);
} }
rename $commit_editmsg, $commit_msg or croak $!; rename $commit_editmsg, $commit_msg or croak $!;
{ {
@ -5219,10 +5218,8 @@ sub git_svn_log_cmd {
# adapted from pager.c # adapted from pager.c
sub config_pager { sub config_pager {
$pager ||= $ENV{GIT_PAGER} || $ENV{PAGER}; chomp(my $pager = command_oneline(qw(var GIT_PAGER)));
if (!defined $pager) { if ($pager eq 'cat') {
$pager = 'less';
} elsif (length $pager == 0 || $pager eq 'cat') {
$pager = undef; $pager = undef;
} }
$ENV{GIT_PAGER_IN_USE} = defined($pager); $ENV{GIT_PAGER_IN_USE} = defined($pager);

View file

@ -205,7 +205,7 @@ const char *fmt_ident(const char *name, const char *email,
if ((warn_on_no_name || error_on_no_name) && if ((warn_on_no_name || error_on_no_name) &&
name == git_default_name && env_hint) { name == git_default_name && env_hint) {
fprintf(stderr, env_hint, au_env, co_env); fprintf(stderr, env_hint, au_env, co_env);
env_hint = NULL; /* warn only once, for "git var -l" */ env_hint = NULL; /* warn only once */
} }
if (error_on_no_name) if (error_on_no_name)
die("empty ident %s <%s> not allowed", name, email); die("empty ident %s <%s> not allowed", name, email);

24
pager.c
View file

@ -2,6 +2,10 @@
#include "run-command.h" #include "run-command.h"
#include "sigchain.h" #include "sigchain.h"
#ifndef DEFAULT_PAGER
#define DEFAULT_PAGER "less"
#endif
/* /*
* This is split up from the rest of git so that we can do * This is split up from the rest of git so that we can do
* something different on Windows. * something different on Windows.
@ -44,12 +48,14 @@ static void wait_for_pager_signal(int signo)
raise(signo); raise(signo);
} }
void setup_pager(void) const char *git_pager(void)
{ {
const char *pager = getenv("GIT_PAGER"); const char *pager;
if (!isatty(1)) if (!isatty(1))
return; return NULL;
pager = getenv("GIT_PAGER");
if (!pager) { if (!pager) {
if (!pager_program) if (!pager_program)
git_config(git_default_config, NULL); git_config(git_default_config, NULL);
@ -58,8 +64,18 @@ void setup_pager(void)
if (!pager) if (!pager)
pager = getenv("PAGER"); pager = getenv("PAGER");
if (!pager) if (!pager)
pager = "less"; pager = DEFAULT_PAGER;
else if (!*pager || !strcmp(pager, "cat")) else if (!*pager || !strcmp(pager, "cat"))
pager = NULL;
return pager;
}
void setup_pager(void)
{
const char *pager = git_pager();
if (!pager)
return; return;
spawned_pager = 1; /* means we are emitting to terminal */ spawned_pager = 1; /* means we are emitting to terminal */

View file

@ -4,7 +4,21 @@ test_description='GIT_EDITOR, core.editor, and stuff'
. ./test-lib.sh . ./test-lib.sh
for i in GIT_EDITOR core_editor EDITOR VISUAL vi unset EDITOR VISUAL GIT_EDITOR
test_expect_success 'determine default editor' '
vi=$(TERM=vt100 git var GIT_EDITOR) &&
test -n "$vi"
'
if ! expr "$vi" : '^[a-z]*$' >/dev/null
then
vi=
fi
for i in GIT_EDITOR core_editor EDITOR VISUAL $vi
do do
cat >e-$i.sh <<-EOF cat >e-$i.sh <<-EOF
#!$SHELL_PATH #!$SHELL_PATH
@ -12,19 +26,18 @@ do
EOF EOF
chmod +x e-$i.sh chmod +x e-$i.sh
done done
unset vi
mv e-vi.sh vi if ! test -z "$vi"
unset EDITOR VISUAL GIT_EDITOR then
mv e-$vi.sh $vi
fi
test_expect_success setup ' test_expect_success setup '
msg="Hand edited" && msg="Hand-edited" &&
test_commit "$msg" &&
echo "$msg" >expect && echo "$msg" >expect &&
git add vi && git show -s --format=%s > actual &&
test_tick &&
git commit -m "$msg" &&
git show -s --pretty=oneline |
sed -e "s/^[0-9a-f]* //" >actual &&
diff actual expect diff actual expect
' '
@ -42,9 +55,19 @@ test_expect_success 'dumb should error out when falling back on vi' '
fi fi
' '
test_expect_success 'dumb should prefer EDITOR to VISUAL' '
EDITOR=./e-EDITOR.sh &&
VISUAL=./e-VISUAL.sh &&
export EDITOR VISUAL &&
git commit --amend &&
test "$(git show -s --format=%s)" = "Edited by EDITOR"
'
TERM=vt100 TERM=vt100
export TERM export TERM
for i in vi EDITOR VISUAL core_editor GIT_EDITOR for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
do do
echo "Edited by $i" >expect echo "Edited by $i" >expect
unset EDITOR VISUAL GIT_EDITOR unset EDITOR VISUAL GIT_EDITOR
@ -68,7 +91,7 @@ done
unset EDITOR VISUAL GIT_EDITOR unset EDITOR VISUAL GIT_EDITOR
git config --unset-all core.editor git config --unset-all core.editor
for i in vi EDITOR VISUAL core_editor GIT_EDITOR for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
do do
echo "Edited by $i" >expect echo "Edited by $i" >expect
case "$i" in case "$i" in

View file

@ -86,7 +86,7 @@ chmod 755 editor
test_expect_success \ test_expect_success \
"amend commit" \ "amend commit" \
"VISUAL=./editor git commit --amend" "EDITOR=./editor git commit --amend"
test_expect_success \ test_expect_success \
"passing -m and -F" \ "passing -m and -F" \
@ -107,7 +107,7 @@ chmod 755 editor
test_expect_success \ test_expect_success \
"editing message from other commit" \ "editing message from other commit" \
"echo 'hula hula' >file && \ "echo 'hula hula' >file && \
VISUAL=./editor git commit -c HEAD^ -a" EDITOR=./editor git commit -c HEAD^ -a"
test_expect_success \ test_expect_success \
"message from stdin" \ "message from stdin" \
@ -141,10 +141,10 @@ EOF
test_expect_success \ test_expect_success \
'editor not invoked if -F is given' ' 'editor not invoked if -F is given' '
echo "moo" >file && echo "moo" >file &&
VISUAL=./editor git commit -a -F msg && EDITOR=./editor git commit -a -F msg &&
git show -s --pretty=format:"%s" | grep -q good && git show -s --pretty=format:"%s" | grep -q good &&
echo "quack" >file && echo "quack" >file &&
echo "Another good message." | VISUAL=./editor git commit -a -F - && echo "Another good message." | EDITOR=./editor git commit -a -F - &&
git show -s --pretty=format:"%s" | grep -q good git show -s --pretty=format:"%s" | grep -q good
' '
# We could just check the head sha1, but checking each commit makes it # We could just check the head sha1, but checking each commit makes it

View file

@ -30,7 +30,7 @@ TZ=UTC
TERM=dumb TERM=dumb
export LANG LC_ALL PAGER TERM TZ export LANG LC_ALL PAGER TERM TZ
EDITOR=: EDITOR=:
VISUAL=: unset VISUAL
unset GIT_EDITOR unset GIT_EDITOR
unset AUTHOR_DATE unset AUTHOR_DATE
unset AUTHOR_EMAIL unset AUTHOR_EMAIL
@ -58,7 +58,7 @@ GIT_MERGE_VERBOSITY=5
export GIT_MERGE_VERBOSITY export GIT_MERGE_VERBOSITY
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
export EDITOR VISUAL export EDITOR
GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u} GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
# Protect ourselves from common misconfiguration to export # Protect ourselves from common misconfiguration to export
@ -207,8 +207,8 @@ trap 'die' EXIT
test_set_editor () { test_set_editor () {
FAKE_EDITOR="$1" FAKE_EDITOR="$1"
export FAKE_EDITOR export FAKE_EDITOR
VISUAL='"$FAKE_EDITOR"' EDITOR='"$FAKE_EDITOR"'
export VISUAL export EDITOR
} }
test_tick () { test_tick () {

26
var.c
View file

@ -8,6 +8,25 @@
static const char var_usage[] = "git var [-l | <variable>]"; static const char var_usage[] = "git var [-l | <variable>]";
static const char *editor(int flag)
{
const char *pgm = git_editor();
if (!pgm && flag & IDENT_ERROR_ON_NO_NAME)
die("Terminal is dumb, but EDITOR unset");
return pgm;
}
static const char *pager(int flag)
{
const char *pgm = git_pager();
if (!pgm)
pgm = "cat";
return pgm;
}
struct git_var { struct git_var {
const char *name; const char *name;
const char *(*read)(int); const char *(*read)(int);
@ -15,14 +34,19 @@ struct git_var {
static struct git_var git_vars[] = { static struct git_var git_vars[] = {
{ "GIT_COMMITTER_IDENT", git_committer_info }, { "GIT_COMMITTER_IDENT", git_committer_info },
{ "GIT_AUTHOR_IDENT", git_author_info }, { "GIT_AUTHOR_IDENT", git_author_info },
{ "GIT_EDITOR", editor },
{ "GIT_PAGER", pager },
{ "", NULL }, { "", NULL },
}; };
static void list_vars(void) static void list_vars(void)
{ {
struct git_var *ptr; struct git_var *ptr;
const char *val;
for (ptr = git_vars; ptr->read; ptr++) for (ptr = git_vars; ptr->read; ptr++)
printf("%s=%s\n", ptr->name, ptr->read(IDENT_WARN_ON_NO_NAME)); if ((val = ptr->read(0)))
printf("%s=%s\n", ptr->name, val);
} }
static const char *read_var(const char *var) static const char *read_var(const char *var)