* Re: [PATCH] Changed timestamp behavior of options -c/-C/--amend
From: Erick Mattos @ 2009-10-31 1:42 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vocnoe83m.fsf@alter.siamese.dyndns.org>
You are completely right.
All your concern is relevant and the whole problem must be re-engineered.
The good news is that I have almost finished it and I will be starting
a new thread with the new solution in a few minutes.
Regards
2009/10/30 Junio C Hamano <gitster@pobox.com>:
> Erick Mattos <erick.mattos@gmail.com> writes:
>
>> I don't see a use for comparing the author and committer because I can
>> use as template my own commits or others'.
>
> You _can_ use whichever irrelevant commit as a template, but "you _can_"
> is different from what it means, and what is and what is not _sensible_.
>
> You may be rewriting somebody else's patch (e.g. fixing up a typo in the
> message, or changing the implementation, or both). If you are going to
> keep the authorship, you are saying that "it is still _his_ code, not
> mine". In such a case, it never makes sense to change the timestamp, if
> that author is somebody other than you. After all that other guy may not
> even be aware of what you are doing when you make this commit; he may be
> in bed sound asleep in a different timezone.
>
> In another scenario, if your fix-up is very significant, even if you
> started from somebody else's patch, you may want to say "now this is my
> patch, the original author may have given me some inspiration, but the
> changes in this commit, including all the bugs, are mine". The same
> applies if you looked at the problem description of somebody' patch, and
> did your own solution without using anything from his commit.
>
> At that point, you would want the resulting commit to say it was written
> by you at this moment. You do not want to see -c/-C/--amend to retain any
> part of the authorship (not just timestamp) from the original commit.
>
> Side note. You may be fixing your own patch, in which case you may or
> may not consider your change significant, but at the time of either
> old timestamp or current time, you were working on this change, so
> using the current timestamp instead of using the old one is not a big
> deal, and that is why I think committer==author may be a good
> heuristic when deciding to touch or not touch the timestamp.
>
> But in general I do not like such dwim that depends on who you are (it
> makes it harder to explain, even if the end result may be useful in
> practice), so I'd rather not to see such a code for this topic if we
> can avoid it.
>
> In short, I do not think it makes sense to change only the timestamp while
> keeping the author. The issue is not "timestamp behaviour" with "use new
> timestamp" option, but rather is an ability to declare "Now this is a
> commit made _by me_ and _now_; iow, I take authorship for this change",
> even when you reuse the commit log message from somewhere else.
>
> So what is needed is an option to tell -c/-C/--amend to reuse _only_ the
> message but no authorship information from the original commit, I think.
>
^ permalink raw reply
* [PATCH 8/8] Provide a build time default-pager setting
From: Jonathan Nieder @ 2009-10-31 1:45 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091031012050.GA5160@progeny.tock>
From: Junio C Hamano <gitster@pobox.com>
Provide a DEFAULT_PAGER knob so packagers can set the fallback
pager to something appropriate during the build.
Examples:
On (old) solaris systems, /usr/bin/less (typically the first less
found) doesn't understand the default arguments (FXRS), which
forces users to alter their environment (PATH, GIT_PAGER, LESS,
etc) or have a local or global gitconfig before paging works as
expected.
On Debian systems, by policy packages must fall back to the
'pager' command, so that changing the target of the
/usr/bin/pager symlink changes the default pager for all packages
at once.
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ben Walton <bwalton@artsci.utoronto.ca>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
Makefile | 11 +++++++++++
| 6 +++++-
2 files changed, 16 insertions(+), 1 deletions(-)
diff --git a/Makefile b/Makefile
index 625866c..18fc50a 100644
--- a/Makefile
+++ b/Makefile
@@ -201,6 +201,10 @@ all::
#
# 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:
@@ -1380,6 +1384,13 @@ 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_LDFLAGS += $(BASIC_LDFLAGS)
--git a/pager.c b/pager.c
index 0b63d99..92c03f6 100644
--- a/pager.c
+++ b/pager.c
@@ -2,6 +2,10 @@
#include "run-command.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
* something different on Windows.
@@ -60,7 +64,7 @@ const char *git_pager(void)
if (!pager)
pager = getenv("PAGER");
if (!pager)
- pager = "less";
+ pager = DEFAULT_PAGER;
else if (!*pager || !strcmp(pager, "cat"))
pager = NULL;
--
1.6.5.2
^ permalink raw reply related
* [PATCH 7/8] Provide a build time default-editor setting
From: Jonathan Nieder @ 2009-10-31 1:44 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091031012050.GA5160@progeny.tock>
Provide a DEFAULT_EDITOR knob to allow setting the fallback
editor to use instead of vi (when VISUAL, EDITOR, and GIT_EDITOR
are unset). The value can be set at build time according to a
system’s policy. For example, on Debian systems, the default
editor should be the 'editor' command.
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ben Walton <bwalton@artsci.utoronto.ca>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
Makefile | 17 +++++++++++++++++
editor.c | 6 +++++-
t/t7005-editor.sh | 31 ++++++++++++++++++++++++-------
3 files changed, 46 insertions(+), 8 deletions(-)
diff --git a/Makefile b/Makefile
index 268aede..625866c 100644
--- a/Makefile
+++ b/Makefile
@@ -200,6 +200,14 @@ all::
# 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 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
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -1363,6 +1371,15 @@ BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
$(COMPAT_CFLAGS)
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
+
ALL_CFLAGS += $(BASIC_CFLAGS)
ALL_LDFLAGS += $(BASIC_LDFLAGS)
diff --git a/editor.c b/editor.c
index 4f98b72..2aac807 100644
--- a/editor.c
+++ b/editor.c
@@ -2,6 +2,10 @@
#include "strbuf.h"
#include "run-command.h"
+#ifndef DEFAULT_EDITOR
+#define DEFAULT_EDITOR "vi"
+#endif
+
const char *git_editor(void)
{
const char *editor = getenv("GIT_EDITOR");
@@ -19,7 +23,7 @@ const char *git_editor(void)
return NULL;
if (!editor)
- editor = "vi";
+ editor = DEFAULT_EDITOR;
return editor;
}
diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh
index b647957..73ba44c 100755
--- a/t/t7005-editor.sh
+++ b/t/t7005-editor.sh
@@ -4,7 +4,26 @@ test_description='GIT_EDITOR, core.editor, and stuff'
. ./test-lib.sh
-for i in GIT_EDITOR core_editor EDITOR VISUAL vi
+unset EDITOR VISUAL GIT_EDITOR
+
+test_expect_success 'does editor have a simple name (no slashes, etc)?' '
+
+ editor=$(TERM=vt100 git var GIT_EDITOR) &&
+ test -n "$editor" &&
+ simple=t &&
+ case "$editor" in
+ */* | core_editor | [A-Z]*)
+ unset simple;;
+ esac
+
+'
+if test -z "${simple+set}"
+then
+ say 'skipping editor tests, default editor is not sought on PATH'
+ test_done
+fi
+
+for i in GIT_EDITOR core_editor EDITOR VISUAL "$editor"
do
cat >e-$i.sh <<-EOF
#!$SHELL_PATH
@@ -12,15 +31,13 @@ do
EOF
chmod +x e-$i.sh
done
-unset vi
-mv e-vi.sh vi
-unset EDITOR VISUAL GIT_EDITOR
+mv "e-$editor.sh" "$editor"
test_expect_success setup '
msg="Hand edited" &&
echo "$msg" >expect &&
- git add vi &&
+ git add "$editor" &&
test_tick &&
git commit -m "$msg" &&
git show -s --pretty=oneline |
@@ -44,7 +61,7 @@ test_expect_success 'dumb should error out when falling back on vi' '
TERM=vt100
export TERM
-for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+for i in "$editor" EDITOR VISUAL core_editor GIT_EDITOR
do
echo "Edited by $i" >expect
unset EDITOR VISUAL GIT_EDITOR
@@ -68,7 +85,7 @@ done
unset EDITOR VISUAL GIT_EDITOR
git config --unset-all core.editor
-for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+for i in "$editor" EDITOR VISUAL core_editor GIT_EDITOR
do
echo "Edited by $i" >expect
case "$i" in
--
1.6.5.2
^ permalink raw reply related
* [PATCH 6/8] am -i, git-svn: use "git var GIT_PAGER"
From: Jonathan Nieder @ 2009-10-31 1:43 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091031012050.GA5160@progeny.tock>
Use the new "git var GIT_PAGER" command to ask what pager to use.
Without this change, the core.pager configuration is ignored by
these commands.
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
git-am.sh | 5 ++++-
git-svn.perl | 6 ++----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/git-am.sh b/git-am.sh
index c132f50..2649487 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -649,7 +649,10 @@ do
[eE]*) git_editor "$dotest/final-commit"
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 ;;
esac
done
diff --git a/git-svn.perl b/git-svn.perl
index 42c9a72..c4ca548 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -5171,10 +5171,8 @@ sub git_svn_log_cmd {
# adapted from pager.c
sub config_pager {
- $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
- if (!defined $pager) {
- $pager = 'less';
- } elsif (length $pager == 0 || $pager eq 'cat') {
+ chomp(my $pager = command_oneline(qw(var GIT_PAGER)));
+ if ($pager eq 'cat') {
$pager = undef;
}
$ENV{GIT_PAGER_IN_USE} = defined($pager);
--
1.6.5.2
^ permalink raw reply related
* [PATCH 5/8] add -i, send-email, svn, p4, etc: use "git var GIT_EDITOR"
From: Jonathan Nieder @ 2009-10-31 1:42 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091031012050.GA5160@progeny.tock>
Use the new "git var GIT_EDITOR" feature to decide what editor to
use, instead of duplicating its logic elsewhere. This should make
the behavior of commands in edge cases (e.g., editor names with
spaces) a little more consistent.
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
Documentation/config.txt | 4 +---
Documentation/git-commit.txt | 2 +-
Documentation/git-send-email.txt | 4 ++--
contrib/fast-import/git-p4 | 5 +----
git-add--interactive.perl | 3 +--
git-send-email.perl | 3 ++-
git-sh-setup.sh | 19 ++++++-------------
git-svn.perl | 5 ++---
8 files changed, 16 insertions(+), 29 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d1e2120..5181b77 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -387,9 +387,7 @@ core.editor::
Commands such as `commit` and `tag` that lets you edit
messages by launching an editor uses the value of this
variable when it is set, and the environment variable
- `GIT_EDITOR` is not set. The order of preference is
- `GIT_EDITOR` environment, `core.editor`, `VISUAL` and
- `EDITOR` environment variables and then finally `vi`.
+ `GIT_EDITOR` is not set. See linkgit:git-var[1].
core.pager::
The command that git will use to paginate output. Can
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 0578a40..3ea80c8 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -323,7 +323,7 @@ ENVIRONMENT AND CONFIGURATION VARIABLES
The editor used to edit the commit log message will be chosen from the
GIT_EDITOR environment variable, the core.editor configuration variable, the
VISUAL environment variable, or the EDITOR environment variable (in that
-order).
+order). See linkgit:git-var[1] for details.
HOOKS
-----
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 767cf4d..c85d7f4 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -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.
--compose::
- Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
- introductory message for the patch series.
+ Invoke a text editor (see GIT_EDITOR in linkgit:git-var[1])
+ to edit an introductory message for the patch series.
+
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
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index e710219..48059d0 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -729,13 +729,10 @@ class P4Submit(Command):
tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
tmpFile.close()
mtime = os.stat(fileName).st_mtime
- defaultEditor = "vi"
- if platform.system() == "Windows":
- defaultEditor = "notepad"
if os.environ.has_key("P4EDITOR"):
editor = os.environ.get("P4EDITOR")
else:
- editor = os.environ.get("EDITOR", defaultEditor);
+ editor = read_pipe("git var GIT_EDITOR")
system(editor + " " + fileName)
response = "y"
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 69aeaf0..0c74e5c 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -987,8 +987,7 @@ sub edit_hunk_manually {
EOF
close $fh;
- my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor")
- || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+ chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
if ($? != 0) {
diff --git a/git-send-email.perl b/git-send-email.perl
index a0279de..4f5da4e 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -162,7 +162,8 @@ my $compose_filename;
# Handle interactive edition of files.
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 {
if (defined($multiedit) && !$multiedit) {
map {
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index c41c2f7..99cceeb 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -99,19 +99,12 @@ set_reflog_action() {
}
git_editor() {
- : "${GIT_EDITOR:=$(git config core.editor)}"
- : "${GIT_EDITOR:=${VISUAL:-${EDITOR}}}"
- case "$GIT_EDITOR,$TERM" in
- ,dumb)
- 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."
- 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}" '"$@"'
+ if test -z "${GIT_EDITOR:+set}"
+ then
+ GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
+ fi
+
+ eval "$GIT_EDITOR" '"$@"'
}
is_bare_repository () {
diff --git a/git-svn.perl b/git-svn.perl
index 6a3b501..42c9a72 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -1321,9 +1321,8 @@ sub get_commit_entry {
close $log_fh or croak $!;
if ($_edit || ($type eq 'tree')) {
- my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
- # TODO: strip out spaces, comments, like git-commit.sh
- system($editor, $commit_editmsg);
+ chomp(my $editor = command_oneline(qw(var GIT_EDITOR)));
+ system('sh', '-c', $editor.' "$@"', $editor, $commit_editmsg);
}
rename $commit_editmsg, $commit_msg or croak $!;
{
--
1.6.5.2
^ permalink raw reply related
* [PATCH 4/8] Teach git var about GIT_PAGER
From: Jonathan Nieder @ 2009-10-31 1:41 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091031012050.GA5160@progeny.tock>
Expose the command found by setup_pager() for scripts to use.
Scripts can use this to avoid repeating the logic to look for a
proper pager in each command.
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
No changes from the last version sent.
Documentation/git-var.txt | 6 ++++++
cache.h | 1 +
| 18 +++++++++++++++---
var.c | 10 ++++++++++
4 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
index 89e4b4f..ef6aa81 100644
--- a/Documentation/git-var.txt
+++ b/Documentation/git-var.txt
@@ -44,6 +44,12 @@ 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
-----------
You don't exist. Go away!::
diff --git a/cache.h b/cache.h
index 311cfe1..5aaa4ba 100644
--- a/cache.h
+++ b/cache.h
@@ -751,6 +751,7 @@ 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_name(const char *name, const char *email);
extern const char *git_editor(void);
+extern const char *git_pager(void);
struct checkout {
const char *base_dir;
--git a/pager.c b/pager.c
index 86facec..0b63d99 100644
--- a/pager.c
+++ b/pager.c
@@ -44,12 +44,14 @@ static void wait_for_pager_signal(int signo)
raise(signo);
}
-void setup_pager(void)
+const char *git_pager(void)
{
- const char *pager = getenv("GIT_PAGER");
+ const char *pager;
if (!isatty(1))
- return;
+ return NULL;
+
+ pager = getenv("GIT_PAGER");
if (!pager) {
if (!pager_program)
git_config(git_default_config, NULL);
@@ -60,6 +62,16 @@ void setup_pager(void)
if (!pager)
pager = "less";
else if (!*pager || !strcmp(pager, "cat"))
+ pager = NULL;
+
+ return pager;
+}
+
+void setup_pager(void)
+{
+ const char *pager = git_pager();
+
+ if (!pager)
return;
spawned_pager = 1; /* means we are emitting to terminal */
diff --git a/var.c b/var.c
index 399f409..facec11 100644
--- a/var.c
+++ b/var.c
@@ -23,6 +23,15 @@ static const char *editor(int flag)
return pgm;
}
+static const char *pager(int flag)
+{
+ const char *pgm = git_pager();
+
+ if (!pgm)
+ pgm = "cat";
+ return pgm;
+}
+
struct git_var {
const char *name;
const char *(*read)(int);
@@ -31,6 +40,7 @@ static struct git_var git_vars[] = {
{ "GIT_COMMITTER_IDENT", git_committer_info },
{ "GIT_AUTHOR_IDENT", git_author_info },
{ "GIT_EDITOR", editor },
+ { "GIT_PAGER", pager },
{ "", NULL },
};
--
1.6.5.2
^ permalink raw reply related
* [PATCH v2 3/8] Teach git var about GIT_EDITOR
From: Jonathan Nieder @ 2009-10-31 1:39 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091031012050.GA5160@progeny.tock>
From: Johannes Sixt <j6t@kdbg.org>
Expose the command used by launch_editor() for scripts to use.
This should allow one to avoid searching for a proper editor
separately in each command.
If no satisfactory GIT_EDITOR could be chosen, let "git var -l"
output a warning. This warning goes to stderr so as not to
confuse scripts. Example:
core.logallrefupdates=true
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
GIT_COMMITTER_IDENT=user <user@domain> 1256952739 -0500
GIT_AUTHOR_IDENT=user <user@domain> 1256952739 -0500
warning: GIT_EDITOR: terminal is dumb, but EDITOR unset
Signed-off-by: Johannes Sixt <j6t@kdbg.org>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
Documentation/git-var.txt | 8 ++++++++
cache.h | 1 +
editor.c | 14 ++++++++++++--
var.c | 21 ++++++++++++++++++++-
4 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
index e2f4c09..89e4b4f 100644
--- a/Documentation/git-var.txt
+++ b/Documentation/git-var.txt
@@ -36,6 +36,14 @@ GIT_AUTHOR_IDENT::
GIT_COMMITTER_IDENT::
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'.
+
Diagnostics
-----------
You don't exist. Go away!::
diff --git a/cache.h b/cache.h
index 96840c7..311cfe1 100644
--- a/cache.h
+++ b/cache.h
@@ -750,6 +750,7 @@ extern const char *git_author_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_name(const char *name, const char *email);
+extern const char *git_editor(void);
struct checkout {
const char *base_dir;
diff --git a/editor.c b/editor.c
index 3f13751..4f98b72 100644
--- a/editor.c
+++ b/editor.c
@@ -2,7 +2,7 @@
#include "strbuf.h"
#include "run-command.h"
-int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
+const char *git_editor(void)
{
const char *editor = getenv("GIT_EDITOR");
const char *terminal = getenv("TERM");
@@ -16,11 +16,21 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
editor = getenv("EDITOR");
if (!editor && terminal_is_dumb)
- return error("terminal is dumb, but EDITOR unset");
+ return NULL;
if (!editor)
editor = "vi";
+ 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, ":")) {
size_t len = strlen(editor);
int i = 0;
diff --git a/var.c b/var.c
index 125c0d1..399f409 100644
--- a/var.c
+++ b/var.c
@@ -8,6 +8,21 @@
static const char var_usage[] = "git var [-l | <variable>]";
+static const char *editor(int flag)
+{
+ const char *pgm = git_editor();
+
+ if (!pgm) {
+ if (flag & IDENT_ERROR_ON_NO_NAME)
+ die("terminal is dumb, but EDITOR unset");
+ if (flag & IDENT_WARN_ON_NO_NAME)
+ warning("GIT_EDITOR: terminal is dumb, "
+ "but EDITOR unset");
+ }
+
+ return pgm;
+}
+
struct git_var {
const char *name;
const char *(*read)(int);
@@ -15,14 +30,18 @@ struct git_var {
static struct git_var git_vars[] = {
{ "GIT_COMMITTER_IDENT", git_committer_info },
{ "GIT_AUTHOR_IDENT", git_author_info },
+ { "GIT_EDITOR", editor },
{ "", NULL },
};
static void list_vars(void)
{
struct git_var *ptr;
+ const char *val;
+
for (ptr = git_vars; ptr->read; ptr++)
- printf("%s=%s\n", ptr->name, ptr->read(IDENT_WARN_ON_NO_NAME));
+ if ((val = ptr->read(IDENT_WARN_ON_NO_NAME)))
+ printf("%s=%s\n", ptr->name, val);
}
static const char *read_var(const char *var)
--
1.6.5.2
^ permalink raw reply related
* [PATCH 2/8] Do not use VISUAL editor on dumb terminals
From: Jonathan Nieder @ 2009-10-31 1:30 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091031012050.GA5160@progeny.tock>
Refuse to use $VISUAL and fall back to $EDITOR if TERM is unset
or set to "dumb". Traditionally, VISUAL is set to a screen
editor and EDITOR to a line-based editor, which should be more
useful in that situation.
vim, for example, is happy to assume a terminal supports ANSI
sequences even if TERM is dumb (e.g., when running from a text
editor like Acme). git already refuses to fall back to vi on a
dumb terminal if GIT_EDITOR, core.editor, VISUAL, and EDITOR are
unset, but without this patch, that check is suppressed by
VISUAL=vi.
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
This patch eases my discomfort about the error message a little. It
is not actually needed to support any ways of working I engage in.
If stdout is redirected, this is probably still making the wrong
choice; isatty(STDOUT_FILENO) might be a more useful datum to use.
But it does not seem worth complicating the logic further.
editor.c | 12 ++++++------
1 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/editor.c b/editor.c
index 941c0b2..3f13751 100644
--- a/editor.c
+++ b/editor.c
@@ -4,19 +4,19 @@
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
{
- const char *editor, *terminal;
+ 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)
editor = editor_program;
- if (!editor)
+ if (!editor && !terminal_is_dumb)
editor = getenv("VISUAL");
if (!editor)
editor = getenv("EDITOR");
- terminal = getenv("TERM");
- if (!editor && (!terminal || !strcmp(terminal, "dumb")))
- return error("Terminal is dumb but no VISUAL nor EDITOR defined.");
+ if (!editor && terminal_is_dumb)
+ return error("terminal is dumb, but EDITOR unset");
if (!editor)
editor = "vi";
--
1.6.5.2
^ permalink raw reply related
* [PATCH 1/8] Handle more shell metacharacters in editor names
From: Jonathan Nieder @ 2009-10-31 1:24 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091031012050.GA5160@progeny.tock>
Pass the editor name to the shell if it contains any susv3 shell
special character (globs, redirections, variable substitutions,
escapes, etc). This way, the meaning of some characters will not
meaninglessly change when others are added, and git commands
implemented in C and in shell scripts will interpret editor names
in the same way.
This does not make the GIT_EDITOR setting any more expressive,
since one could always use single quotes to force the editor to
be passed to the shell.
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
Unchanged from v2.
editor.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/editor.c b/editor.c
index 4d469d0..941c0b2 100644
--- a/editor.c
+++ b/editor.c
@@ -28,7 +28,7 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
const char *args[6];
struct strbuf arg0 = STRBUF_INIT;
- if (strcspn(editor, "$ \t'") != len) {
+ if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) {
/* there are specials */
strbuf_addf(&arg0, "%s \"$@\"", editor);
args[i++] = "sh";
--
1.6.5.2
^ permalink raw reply related
* [PATCH v3 0/8] Default pager and editor
From: Jonathan Nieder @ 2009-10-31 1:20 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ben Walton, Johannes Sixt, David Roundy, GIT List
In-Reply-To: <20091030101634.GA1610@progeny.tock>
Junio C Hamano wrote:
> I'll queue these for now probably on 'pu', but with the comments we saw on
> the list expect them to be followed up with replacement patches.
Here’s a replacement series. It omits the longer error message when
TERM=dumb and the git var --run experiment because I was not happy
with where either of those were going.
Thanks for all the comments, everyone.
Johannes Sixt (1):
Teach git var about GIT_EDITOR
Jonathan Nieder (6):
Handle more shell metacharacters in editor names
Do not use VISUAL editor on dumb terminals
Teach git var about GIT_PAGER
add -i, send-email, svn, p4, etc: use "git var GIT_EDITOR"
am -i, git-svn: use "git var GIT_PAGER"
Provide a build time default-editor setting
Junio C Hamano (1):
Provide a build time default-pager setting
Documentation/config.txt | 4 +---
Documentation/git-commit.txt | 2 +-
Documentation/git-send-email.txt | 4 ++--
Documentation/git-var.txt | 14 ++++++++++++++
Makefile | 28 ++++++++++++++++++++++++++++
cache.h | 2 ++
contrib/fast-import/git-p4 | 5 +----
editor.c | 32 +++++++++++++++++++++++---------
git-add--interactive.perl | 3 +--
git-am.sh | 5 ++++-
git-send-email.perl | 3 ++-
git-sh-setup.sh | 19 ++++++-------------
git-svn.perl | 11 ++++-------
pager.c | 24 ++++++++++++++++++++----
t/t7005-editor.sh | 31 ++++++++++++++++++++++++-------
var.c | 31 ++++++++++++++++++++++++++++++-
16 files changed, 163 insertions(+), 55 deletions(-)
^ permalink raw reply
* [PATCH v5 27/28] http tests: use /dumb/ URL prefix
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Tay Ray Chuan
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
To clarify what part of the HTTP transprot is being tested we change
the URLs used by existing tests to include /dumb/ at the start,
indicating they use the non-Git aware code paths.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
CC: Tay Ray Chuan <rctay89@gmail.com>
---
t/lib-httpd/apache.conf | 7 ++++++-
t/t5540-http-push.sh | 10 +++++-----
t/t5550-http-fetch.sh | 4 ++--
3 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 21aa42f..47a438f 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -8,6 +8,11 @@ ErrorLog error.log
<IfModule !mod_log_config.c>
LoadModule log_config_module modules/mod_log_config.so
</IfModule>
+<IfModule !mod_alias.c>
+ LoadModule alias_module modules/mod_alias.so
+</IfModule>
+
+Alias /dumb/ www/
<IfDefine SSL>
LoadModule ssl_module modules/mod_ssl.so
@@ -26,7 +31,7 @@ SSLEngine On
LoadModule dav_fs_module modules/mod_dav_fs.so
DAVLockDB DAVLock
- <Location />
+ <Location /dumb/>
Dav on
</Location>
</IfDefine>
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index d1234f9..4a9450e 100755
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
@@ -42,7 +42,7 @@ test_expect_success 'setup remote repository' '
test_expect_success 'clone remote repository' '
cd "$ROOT_PATH" &&
- git clone $HTTPD_URL/test_repo.git test_repo_clone
+ git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone
'
test_expect_success 'push to remote repository with packed refs' '
@@ -75,7 +75,7 @@ test_expect_success 'http-push fetches unpacked objects' '
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
"$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_unpacked.git &&
- git clone $HTTPD_URL/test_repo_unpacked.git \
+ git clone $HTTPD_URL/dumb/test_repo_unpacked.git \
"$ROOT_PATH"/fetch_unpacked &&
# By reset, we force git to retrieve the object
@@ -84,14 +84,14 @@ test_expect_success 'http-push fetches unpacked objects' '
git remote rm origin &&
git reflog expire --expire=0 --all &&
git prune &&
- git push -f -v $HTTPD_URL/test_repo_unpacked.git master)
+ git push -f -v $HTTPD_URL/dumb/test_repo_unpacked.git master)
'
test_expect_success 'http-push fetches packed objects' '
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
"$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git &&
- git clone $HTTPD_URL/test_repo_packed.git \
+ git clone $HTTPD_URL/dumb/test_repo_packed.git \
"$ROOT_PATH"/test_repo_clone_packed &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git &&
@@ -104,7 +104,7 @@ test_expect_success 'http-push fetches packed objects' '
git remote rm origin &&
git reflog expire --expire=0 --all &&
git prune &&
- git push -f -v $HTTPD_URL/test_repo_packed.git master)
+ git push -f -v $HTTPD_URL/dumb/test_repo_packed.git master)
'
test_expect_success 'create and delete remote branch' '
diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
index 0e69324..776057c 100755
--- a/t/t5550-http-fetch.sh
+++ b/t/t5550-http-fetch.sh
@@ -30,7 +30,7 @@ test_expect_success 'create http-accessible bare repository' '
'
test_expect_success 'clone http repository' '
- git clone $HTTPD_URL/repo.git clone &&
+ git clone $HTTPD_URL/dumb/repo.git clone &&
test_cmp file clone/file
'
@@ -58,7 +58,7 @@ test_expect_success 'fetch packed objects' '
cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
git --bare repack &&
git --bare prune-packed &&
- git clone $HTTPD_URL/repo_pack.git
+ git clone $HTTPD_URL/dumb/repo_pack.git
'
stop_httpd
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 28/28] test smart http fetch and push
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
The top level directory "/smart/" of the test Apache server is mapped
through our git-http-backend CGI, but uses the same underlying
repository space as the server's document root. This is the most
simple installation possible.
Server logs are checked to verify the client has accessed only the
smart URLs during the test. During fetch testing the headers are
also logged from libcurl to ensure we are making a reasonably sane
HTTP request, and getting back reasonably sane response headers
from the CGI.
When validating the request headers used during smart fetch we munge
away the actual Content-Length and replace it with the placeholder
"xxx". This avoids unnecessary varability in the test caused by
an unrelated change in the requested capabilities in the first want
line of the request. However, we still want to look for and verify
that Content-Length was used, because smaller payloads should be
using Content-Length and not "Transfer-Encoding: chunked".
When validating the server response headers we must discard both
Content-Length and Transfer-Encoding, as Apache2 can use either
format to return our response.
During development of this test I observed Apache returning both
forms, depending on when the processes got CPU time. If our CGI
returned the pack data quickly, Apache just buffered the whole
thing and returned a Content-Length. If our CGI took just a bit
too long to complete, Apache flushed its buffer and instead used
"Transfer-Encoding: chunked".
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
t/lib-httpd/apache.conf | 17 ++++++++
t/t5540-http-push.sh | 2 +-
t/t5541-http-push.sh | 92 ++++++++++++++++++++++++++++++++++++++++++
t/t5550-http-fetch.sh | 8 +++-
t/t5551-http-fetch.sh | 102 +++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 219 insertions(+), 2 deletions(-)
create mode 100755 t/t5541-http-push.sh
create mode 100755 t/t5551-http-fetch.sh
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 47a438f..0fe3fd0 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -11,9 +11,26 @@ ErrorLog error.log
<IfModule !mod_alias.c>
LoadModule alias_module modules/mod_alias.so
</IfModule>
+<IfModule !mod_cgi.c>
+ LoadModule cgi_module modules/mod_cgi.so
+</IfModule>
+<IfModule !mod_env.c>
+ LoadModule env_module modules/mod_env.so
+</IfModule>
Alias /dumb/ www/
+<Location /smart/>
+ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+</Location>
+ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
+<Directory ${GIT_EXEC_PATH}>
+ Options None
+</Directory>
+<Files ${GIT_EXEC_PATH}/git-http-backend>
+ Options ExecCGI
+</Files>
+
<IfDefine SSL>
LoadModule ssl_module modules/mod_ssl.so
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index 4a9450e..bb18f8b 100755
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
@@ -3,7 +3,7 @@
# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
#
-test_description='test http-push
+test_description='test WebDAV http-push
This test runs various sanity checks on http-push.'
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
new file mode 100755
index 0000000..2a58d0c
--- /dev/null
+++ b/t/t5541-http-push.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
+#
+
+test_description='test smart pushing over http via http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+ say 'skipping test, git built without http support'
+ test_done
+fi
+
+ROOT_PATH="$PWD"
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'setup remote repository' '
+ cd "$ROOT_PATH" &&
+ mkdir test_repo &&
+ cd test_repo &&
+ git init &&
+ : >path1 &&
+ git add path1 &&
+ test_tick &&
+ git commit -m initial &&
+ cd - &&
+ git clone --bare test_repo test_repo.git &&
+ cd test_repo.git &&
+ git config http.receivepack true &&
+ ORIG_HEAD=$(git rev-parse --verify HEAD) &&
+ cd - &&
+ mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
+'
+
+test_expect_success 'clone remote repository' '
+ cd "$ROOT_PATH" &&
+ git clone $HTTPD_URL/smart/test_repo.git test_repo_clone
+'
+
+test_expect_success 'push to remote repository' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ : >path2 &&
+ git add path2 &&
+ test_tick &&
+ git commit -m path2 &&
+ HEAD=$(git rev-parse --verify HEAD) &&
+ git push &&
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+ test $HEAD = $(git rev-parse --verify HEAD))
+'
+
+test_expect_success 'push already up-to-date' '
+ git push
+'
+
+test_expect_success 'create and delete remote branch' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ git checkout -b dev &&
+ : >path3 &&
+ git add path3 &&
+ test_tick &&
+ git commit -m dev &&
+ git push origin dev &&
+ git push origin :dev &&
+ test_must_fail git show-ref --verify refs/remotes/origin/dev
+'
+
+cat >exp <<EOF
+GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+EOF
+test_expect_success 'used receive-pack service' '
+ sed -e "
+ s/^.* \"//
+ s/\"//
+ s/ [1-9][0-9]*\$//
+ s/^GET /GET /
+ " >act <"$HTTPD_ROOT_PATH"/access.log &&
+ test_cmp exp act
+'
+
+stop_httpd
+test_done
diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
index 776057c..8cfce96 100755
--- a/t/t5550-http-fetch.sh
+++ b/t/t5550-http-fetch.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='test fetching over http'
+test_description='test dumb fetching over http via static file'
. ./test-lib.sh
if test -n "$NO_CURL"; then
@@ -61,5 +61,11 @@ test_expect_success 'fetch packed objects' '
git clone $HTTPD_URL/dumb/repo_pack.git
'
+test_expect_success 'did not use upload-pack service' '
+ grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act
+ : >exp
+ test_cmp exp act
+'
+
stop_httpd
test_done
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
new file mode 100755
index 0000000..eb0c039
--- /dev/null
+++ b/t/t5551-http-fetch.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='test smart fetching over http via http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+ say 'skipping test, git built without http support'
+ test_done
+fi
+
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5551'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'setup repository' '
+ echo content >file &&
+ git add file &&
+ git commit -m one
+'
+
+test_expect_success 'create http-accessible bare repository' '
+ mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git --bare init
+ ) &&
+ git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git push public master:master
+'
+
+cat >exp <<EOF
+> GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1
+> Accept: */*
+> Pragma: no-cache
+
+< HTTP/1.1 200 OK
+< Pragma: no-cache
+< Cache-Control: no-cache, max-age=0, must-revalidate
+< Content-Type: application/x-git-upload-pack-advertisement
+<
+> POST /smart/repo.git/git-upload-pack HTTP/1.1
+> Accept-Encoding: deflate, gzip
+> Content-Type: application/x-git-upload-pack-request
+> Accept: application/x-git-upload-pack-response
+> Content-Length: xxx
+
+< HTTP/1.1 200 OK
+< Pragma: no-cache
+< Cache-Control: no-cache, max-age=0, must-revalidate
+< Content-Type: application/x-git-upload-pack-result
+<
+EOF
+test_expect_success 'clone http repository' '
+ GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err &&
+ test_cmp file clone/file &&
+ tr '\''\015'\'' Q <err |
+ sed -e "
+ s/Q\$//
+ /^[*] /d
+
+ /^[^><]/{
+ s/^/> /
+ }
+
+ /^> User-Agent: /d
+ /^> Host: /d
+ s/^> Content-Length: .*/> Content-Length: xxx/
+
+ /^< Server: /d
+ /^< Expires: /d
+ /^< Date: /d
+ /^< Content-Length: /d
+ /^< Transfer-Encoding: /d
+ " >act &&
+ test_cmp exp act
+'
+
+test_expect_success 'fetch changes via http' '
+ echo content >>file &&
+ git commit -a -m two &&
+ git push public
+ (cd clone && git pull) &&
+ test_cmp file clone/file
+'
+
+cat >exp <<EOF
+GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200
+GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200
+EOF
+test_expect_success 'used upload-pack service' '
+ sed -e "
+ s/^.* \"//
+ s/\"//
+ s/ [1-9][0-9]*\$//
+ s/^GET /GET /
+ " >act <"$HTTPD_ROOT_PATH"/access.log &&
+ test_cmp exp act
+'
+
+stop_httpd
+test_done
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 20/28] http-backend: more explict LocationMatch
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Mark Lodato
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
From: Mark Lodato <lodatom@gmail.com>
In the git-http-backend examples, only match git-receive-pack within
/git/.
Signed-off-by: Mark Lodato <lodatom@gmail.com>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
Documentation/git-http-backend.txt | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index 2989c9f..f17251a 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -69,7 +69,7 @@ To enable anonymous read access but authenticated write access,
require authorization with a LocationMatch directive:
+
----------------------------------------------------------------
-<LocationMatch ".*/git-receive-pack$">
+<LocationMatch "^/git/.*/git-receive-pack$">
AuthType Basic
AuthName "Git Access"
Require group committers
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 26/28] set httpd port before sourcing lib-httpd
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Clemens Buchacher
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
From: Clemens Buchacher <drizzd@aon.at>
If LIB_HTTPD_PORT is not set already, lib-httpd will set it to the
default 8111.
Signed-off-by: Clemens Buchacher <drizzd@aon.at>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
t/t5540-http-push.sh | 7 +++----
1 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index 28a746e..d1234f9 100755
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
@@ -9,17 +9,16 @@ This test runs various sanity checks on http-push.'
. ./test-lib.sh
-ROOT_PATH="$PWD"
-LIB_HTTPD_DAV=t
-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
-
if git http-push > /dev/null 2>&1 || [ $? -eq 128 ]
then
say "skipping test, USE_CURL_MULTI is not defined"
test_done
fi
+LIB_HTTPD_DAV=t
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
. "$TEST_DIRECTORY"/lib-httpd.sh
+ROOT_PATH="$PWD"
start_httpd
test_expect_success 'setup remote repository' '
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 25/28] t5540-http-push: remove redundant fetches
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Tay Ray Chuan
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
From: Tay Ray Chuan <rctay89@gmail.com>
Signed-off-by: Tay Ray Chuan <rctay89@gmail.com>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
t/t5540-http-push.sh | 2 --
1 files changed, 0 insertions(+), 2 deletions(-)
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index 2ece661..28a746e 100755
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
@@ -116,9 +116,7 @@ test_expect_success 'create and delete remote branch' '
test_tick &&
git commit -m dev &&
git push origin dev &&
- git fetch &&
git push origin :dev &&
- git fetch &&
test_must_fail git show-ref --verify refs/remotes/origin/dev
'
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 22/28] Smart push over HTTP: client side
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Daniel Barkalow
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
The git-remote-curl backend detects if the remote server supports
the git-receive-pack service, and if so, runs git-send-pack in a
pipe to dump the command and pack data as a single POST request.
The advertisements from the server that were obtained during the
discovery are passed into git-send-pack before the POST request
starts. This permits git-send-pack to operate largely unmodified.
For smaller packs (those under 1 MiB) a HTTP/1.0 POST with a
Content-Length is used, permitting interaction with any server.
The 1 MiB limit is arbitrary, but is sufficent to fit most deltas
created by human authors against text sources with the occasional
small binary file (e.g. few KiB icon image). The configuration
option http.postBuffer can be used to increase (or shink) this
buffer if the default is not sufficient.
For larger packs which cannot be spooled entirely into the helper's
memory space (due to http.postBuffer being too small), the POST
request requires HTTP/1.1 and sets "Transfer-Encoding: chunked".
This permits the client to upload an unknown amount of data in one
HTTP transaction without needing to pregenerate the entire pack
file locally.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
CC: Daniel Barkalow <barkalow@iabervon.org>
---
Documentation/config.txt | 8 ++
builtin-send-pack.c | 116 +++++++++++++++++++++-
http.c | 13 ++-
http.h | 2 +
remote-curl.c | 235 +++++++++++++++++++++++++++++++++++++++++++++-
send-pack.h | 3 +-
sideband.c | 11 ++-
transport.c | 1 +
8 files changed, 374 insertions(+), 15 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index cd17814..7130d07 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1089,6 +1089,14 @@ http.maxRequests::
How many HTTP requests to launch in parallel. Can be overridden
by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
+http.postBuffer::
+ Maximum size in bytes of the buffer used by smart HTTP
+ transports when POSTing data to the remote system.
+ For requests larger than this buffer size, HTTP/1.1 and
+ Transfer-Encoding: chunked is used to avoid creating a
+ massive pack file locally. Default is 1 MiB, which is
+ sufficient for most requests.
+
http.lowSpeedLimit, http.lowSpeedTime::
If the HTTP transfer speed is less than 'http.lowSpeedLimit'
for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 37e528e..a0fbad0 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -2,9 +2,11 @@
#include "commit.h"
#include "refs.h"
#include "pkt-line.h"
+#include "sideband.h"
#include "run-command.h"
#include "remote.h"
#include "send-pack.h"
+#include "quote.h"
static const char send_pack_usage[] =
"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
@@ -59,7 +61,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
memset(&po, 0, sizeof(po));
po.argv = argv;
po.in = -1;
- po.out = fd;
+ po.out = args->stateless_rpc ? -1 : fd;
po.git_cmd = 1;
if (start_command(&po))
die_errno("git pack-objects failed");
@@ -83,6 +85,20 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
}
close(po.in);
+
+ if (args->stateless_rpc) {
+ char *buf = xmalloc(LARGE_PACKET_MAX);
+ while (1) {
+ ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
+ if (n <= 0)
+ break;
+ send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
+ }
+ free(buf);
+ close(po.out);
+ po.out = -1;
+ }
+
if (finish_command(&po))
return error("pack-objects died with strange error");
return 0;
@@ -303,6 +319,59 @@ static int refs_pushed(struct ref *ref)
return 0;
}
+static void print_helper_status(struct ref *ref)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ for (; ref; ref = ref->next) {
+ const char *msg = NULL;
+ const char *res;
+
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ res = "error";
+ msg = "no match";
+ break;
+
+ case REF_STATUS_OK:
+ res = "ok";
+ break;
+
+ case REF_STATUS_UPTODATE:
+ res = "ok";
+ msg = "up to date";
+ break;
+
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ res = "error";
+ msg = "non-fast forward";
+ break;
+
+ case REF_STATUS_REJECT_NODELETE:
+ case REF_STATUS_REMOTE_REJECT:
+ res = "error";
+ break;
+
+ case REF_STATUS_EXPECTING_REPORT:
+ default:
+ continue;
+ }
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s %s", res, ref->name);
+ if (ref->remote_status)
+ msg = ref->remote_status;
+ if (msg) {
+ strbuf_addch(&buf, ' ');
+ quote_two_c_style(&buf, "", msg, 0);
+ }
+ strbuf_addch(&buf, '\n');
+
+ safe_write(1, buf.buf, buf.len);
+ }
+ strbuf_release(&buf);
+}
+
int send_pack(struct send_pack_args *args,
int fd[], struct child_process *conn,
struct ref *remote_refs,
@@ -310,6 +379,7 @@ int send_pack(struct send_pack_args *args,
{
int in = fd[0];
int out = fd[1];
+ struct strbuf req_buf = STRBUF_INIT;
struct ref *ref;
int new_refs;
int ask_for_status_report = 0;
@@ -391,14 +461,14 @@ int send_pack(struct send_pack_args *args,
char *new_hex = sha1_to_hex(ref->new_sha1);
if (ask_for_status_report) {
- packet_write(out, "%s %s %s%c%s",
+ packet_buf_write(&req_buf, "%s %s %s%c%s",
old_hex, new_hex, ref->name, 0,
"report-status");
ask_for_status_report = 0;
expect_status_report = 1;
}
else
- packet_write(out, "%s %s %s",
+ packet_buf_write(&req_buf, "%s %s %s",
old_hex, new_hex, ref->name);
}
ref->status = expect_status_report ?
@@ -406,7 +476,17 @@ int send_pack(struct send_pack_args *args,
REF_STATUS_OK;
}
- packet_flush(out);
+ if (args->stateless_rpc) {
+ if (!args->dry_run) {
+ packet_buf_flush(&req_buf);
+ send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
+ }
+ } else {
+ safe_write(out, req_buf.buf, req_buf.len);
+ packet_flush(out);
+ }
+ strbuf_release(&req_buf);
+
if (new_refs && !args->dry_run) {
if (pack_objects(out, remote_refs, extra_have, args) < 0) {
for (ref = remote_refs; ref; ref = ref->next)
@@ -414,11 +494,15 @@ int send_pack(struct send_pack_args *args,
return -1;
}
}
+ if (args->stateless_rpc && !args->dry_run)
+ packet_flush(out);
if (expect_status_report)
ret = receive_status(in, remote_refs);
else
ret = 0;
+ if (args->stateless_rpc)
+ packet_flush(out);
if (ret < 0)
return ret;
@@ -478,6 +562,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
struct extra_have_objects extra_have;
struct ref *remote_refs, *local_refs;
int ret;
+ int helper_status = 0;
int send_all = 0;
const char *receivepack = "git-receive-pack";
int flags;
@@ -523,6 +608,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.use_thin_pack = 1;
continue;
}
+ if (!strcmp(arg, "--stateless-rpc")) {
+ args.stateless_rpc = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--helper-status")) {
+ helper_status = 1;
+ continue;
+ }
usage(send_pack_usage);
}
if (!dest) {
@@ -551,7 +644,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
}
}
- conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0);
+ if (args.stateless_rpc) {
+ conn = NULL;
+ fd[0] = 0;
+ fd[1] = 1;
+ } else {
+ conn = git_connect(fd, dest, receivepack,
+ args.verbose ? CONNECT_VERBOSE : 0);
+ }
memset(&extra_have, 0, sizeof(extra_have));
@@ -575,12 +675,16 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
+ if (helper_status)
+ print_helper_status(remote_refs);
+
close(fd[1]);
close(fd[0]);
ret |= finish_connect(conn);
- print_push_status(dest, remote_refs);
+ if (!helper_status)
+ print_push_status(dest, remote_refs);
if (!args.dry_run && remote) {
struct ref *ref;
diff --git a/http.c b/http.c
index 23b2a19..ed6414a 100644
--- a/http.c
+++ b/http.c
@@ -1,9 +1,11 @@
#include "http.h"
#include "pack.h"
+#include "sideband.h"
int data_received;
int active_requests;
int http_is_verbose;
+size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
#ifdef USE_CURL_MULTI
static int max_requests = -1;
@@ -97,8 +99,6 @@ size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
return eltsize * nmemb;
}
-static void finish_active_slot(struct active_request_slot *slot);
-
#ifdef USE_CURL_MULTI
static void process_curl_messages(void)
{
@@ -174,6 +174,13 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.proxy", var))
return git_config_string(&curl_http_proxy, var, value);
+ if (!strcmp("http.postbuffer", var)) {
+ http_post_buffer = git_config_int(var, value);
+ if (http_post_buffer < LARGE_PACKET_MAX)
+ http_post_buffer = LARGE_PACKET_MAX;
+ return 0;
+ }
+
/* Fall back on the default ones */
return git_default_config(var, value, cb);
}
@@ -638,7 +645,7 @@ void release_active_slot(struct active_request_slot *slot)
#endif
}
-static void finish_active_slot(struct active_request_slot *slot)
+void finish_active_slot(struct active_request_slot *slot)
{
closedown_active_slot(slot);
curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
diff --git a/http.h b/http.h
index 4c4e99c..f828e1d 100644
--- a/http.h
+++ b/http.h
@@ -79,6 +79,7 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
extern struct active_request_slot *get_active_slot(void);
extern int start_active_slot(struct active_request_slot *slot);
extern void run_active_slot(struct active_request_slot *slot);
+extern void finish_active_slot(struct active_request_slot *slot);
extern void finish_all_active_slots(void);
extern void release_active_slot(struct active_request_slot *slot);
@@ -94,6 +95,7 @@ extern void http_cleanup(void);
extern int data_received;
extern int active_requests;
extern int http_is_verbose;
+extern size_t http_post_buffer;
extern char curl_errorstr[CURL_ERROR_SIZE];
diff --git a/remote-curl.c b/remote-curl.c
index 3917d45..f1206cb 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -6,6 +6,7 @@
#include "exec_cmd.h"
#include "run-command.h"
#include "pkt-line.h"
+#include "sideband.h"
static struct remote *remote;
static const char *url;
@@ -16,7 +17,8 @@ struct options {
unsigned long depth;
unsigned progress : 1,
followtags : 1,
- dry_run : 1;
+ dry_run : 1,
+ thin : 1;
};
static struct options options;
@@ -274,6 +276,188 @@ static void output_refs(struct ref *refs)
free_refs(refs);
}
+struct rpc_state {
+ const char *service_name;
+ const char **argv;
+ char *service_url;
+ char *hdr_content_type;
+ char *hdr_accept;
+ char *buf;
+ size_t alloc;
+ size_t len;
+ size_t pos;
+ int in;
+ int out;
+ struct strbuf result;
+};
+
+static size_t rpc_out(void *ptr, size_t eltsize,
+ size_t nmemb, void *buffer_)
+{
+ size_t max = eltsize * nmemb;
+ struct rpc_state *rpc = buffer_;
+ size_t avail = rpc->len - rpc->pos;
+
+ if (!avail) {
+ avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+ if (!avail)
+ return 0;
+ rpc->pos = 0;
+ rpc->len = avail;
+ }
+
+ if (max < avail);
+ avail = max;
+ memcpy(ptr, rpc->buf + rpc->pos, avail);
+ rpc->pos += avail;
+ return avail;
+}
+
+static size_t rpc_in(const void *ptr, size_t eltsize,
+ size_t nmemb, void *buffer_)
+{
+ size_t size = eltsize * nmemb;
+ struct rpc_state *rpc = buffer_;
+ write_or_die(rpc->in, ptr, size);
+ return size;
+}
+
+static int post_rpc(struct rpc_state *rpc)
+{
+ struct active_request_slot *slot;
+ struct slot_results results;
+ struct curl_slist *headers = NULL;
+ int err = 0, large_request = 0;
+
+ /* Try to load the entire request, if we can fit it into the
+ * allocated buffer space we can use HTTP/1.0 and avoid the
+ * chunked encoding mess.
+ */
+ while (1) {
+ size_t left = rpc->alloc - rpc->len;
+ char *buf = rpc->buf + rpc->len;
+ int n;
+
+ if (left < LARGE_PACKET_MAX) {
+ large_request = 1;
+ break;
+ }
+
+ n = packet_read_line(rpc->out, buf, left);
+ if (!n)
+ break;
+ rpc->len += n;
+ }
+
+ slot = get_active_slot();
+ slot->results = &results;
+
+ curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);
+
+ headers = curl_slist_append(headers, rpc->hdr_content_type);
+ headers = curl_slist_append(headers, rpc->hdr_accept);
+
+ if (large_request) {
+ /* The request body is large and the size cannot be predicted.
+ * We must use chunked encoding to send it.
+ */
+ headers = curl_slist_append(headers, "Expect: 100-continue");
+ headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
+ if (options.verbosity > 1) {
+ fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
+ fflush(stderr);
+ }
+
+ } else {
+ /* We know the complete request size in advance, use the
+ * more normal Content-Length approach.
+ */
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf);
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, rpc->len);
+ if (options.verbosity > 1) {
+ fprintf(stderr, "POST %s (%lu bytes)\n",
+ rpc->service_name, (unsigned long)rpc->len);
+ fflush(stderr);
+ }
+ }
+
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
+
+ slot->curl_result = curl_easy_perform(slot->curl);
+ finish_active_slot(slot);
+
+ if (results.curl_result != CURLE_OK) {
+ err |= error("RPC failed; result=%d, HTTP code = %ld",
+ results.curl_result, results.http_code);
+ }
+
+ curl_slist_free_all(headers);
+ return err;
+}
+
+static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
+{
+ const char *svc = rpc->service_name;
+ struct strbuf buf = STRBUF_INIT;
+ struct child_process client;
+ int err = 0;
+
+ init_walker();
+ memset(&client, 0, sizeof(client));
+ client.in = -1;
+ client.out = -1;
+ client.git_cmd = 1;
+ client.argv = rpc->argv;
+ if (start_command(&client))
+ exit(1);
+ if (heads)
+ write_or_die(client.in, heads->buf, heads->len);
+
+ rpc->alloc = http_post_buffer;
+ rpc->buf = xmalloc(rpc->alloc);
+ rpc->in = client.in;
+ rpc->out = client.out;
+ strbuf_init(&rpc->result, 0);
+
+ strbuf_addf(&buf, "%s/%s", url, svc);
+ rpc->service_url = strbuf_detach(&buf, NULL);
+
+ strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc);
+ rpc->hdr_content_type = strbuf_detach(&buf, NULL);
+
+ strbuf_addf(&buf, "Accept: application/x-%s-response", svc);
+ rpc->hdr_accept = strbuf_detach(&buf, NULL);
+
+ while (!err) {
+ int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+ if (!n)
+ break;
+ rpc->pos = 0;
+ rpc->len = n;
+ err |= post_rpc(rpc);
+ }
+ strbuf_read(&rpc->result, client.out, 0);
+
+ close(client.in);
+ close(client.out);
+ client.in = -1;
+ client.out = -1;
+
+ err |= finish_command(&client);
+ free(rpc->service_url);
+ free(rpc->hdr_content_type);
+ free(rpc->hdr_accept);
+ free(rpc->buf);
+ strbuf_release(&buf);
+ return err;
+}
+
static int fetch_dumb(int nr_heads, struct ref **to_fetch)
{
char **targets = xmalloc(nr_heads * sizeof(char*));
@@ -371,6 +555,52 @@ static int push_dav(int nr_spec, char **specs)
return 0;
}
+static int push_git(struct discovery *heads, int nr_spec, char **specs)
+{
+ struct rpc_state rpc;
+ const char **argv;
+ int argc = 0, i, err;
+
+ argv = xmalloc((10 + nr_spec) * sizeof(char*));
+ argv[argc++] = "send-pack";
+ argv[argc++] = "--stateless-rpc";
+ argv[argc++] = "--helper-status";
+ if (options.thin)
+ argv[argc++] = "--thin";
+ if (options.dry_run)
+ argv[argc++] = "--dry-run";
+ if (options.verbosity > 1)
+ argv[argc++] = "--verbose";
+ argv[argc++] = url;
+ for (i = 0; i < nr_spec; i++)
+ argv[argc++] = specs[i];
+ argv[argc++] = NULL;
+
+ memset(&rpc, 0, sizeof(rpc));
+ rpc.service_name = "git-receive-pack",
+ rpc.argv = argv;
+
+ err = rpc_service(&rpc, heads);
+ if (rpc.result.len)
+ safe_write(1, rpc.result.buf, rpc.result.len);
+ strbuf_release(&rpc.result);
+ free(argv);
+ return err;
+}
+
+static int push(int nr_spec, char **specs)
+{
+ struct discovery *heads = discover_refs("git-receive-pack");
+ int ret;
+
+ if (heads->proto_git)
+ ret = push_git(heads, nr_spec, specs);
+ else
+ ret = push_dav(nr_spec, specs);
+ free_discovery(heads);
+ return ret;
+}
+
static void parse_push(struct strbuf *buf)
{
char **specs = NULL;
@@ -391,7 +621,7 @@ static void parse_push(struct strbuf *buf)
break;
} while (1);
- if (push_dav(nr_spec, specs))
+ if (push(nr_spec, specs))
exit(128); /* error already reported */
for (i = 0; i < nr_spec; i++)
free(specs[i]);
@@ -414,6 +644,7 @@ int main(int argc, const char **argv)
options.verbosity = 1;
options.progress = !!isatty(2);
+ options.thin = 1;
remote = remote_get(argv[1]);
diff --git a/send-pack.h b/send-pack.h
index 8b3cf02..28141ac 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -8,7 +8,8 @@ struct send_pack_args {
force_update:1,
use_thin_pack:1,
use_ofs_delta:1,
- dry_run:1;
+ dry_run:1,
+ stateless_rpc:1;
};
int send_pack(struct send_pack_args *args,
diff --git a/sideband.c b/sideband.c
index 899b1ff..d5ffa1c 100644
--- a/sideband.c
+++ b/sideband.c
@@ -135,9 +135,14 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet
n = sz;
if (packet_max - 5 < n)
n = packet_max - 5;
- sprintf(hdr, "%04x", n + 5);
- hdr[4] = band;
- safe_write(fd, hdr, 5);
+ if (0 <= band) {
+ sprintf(hdr, "%04x", n + 5);
+ hdr[4] = band;
+ safe_write(fd, hdr, 5);
+ } else {
+ sprintf(hdr, "%04x", n + 4);
+ safe_write(fd, hdr, 4);
+ }
safe_write(fd, p, n);
p += n;
sz -= n;
diff --git a/transport.c b/transport.c
index 6d9652d..2ff1650 100644
--- a/transport.c
+++ b/transport.c
@@ -731,6 +731,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
NULL);
}
+ memset(&args, 0, sizeof(args));
args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
args.use_thin_pack = data->thin;
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 23/28] Smart fetch over HTTP: client side
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Daniel Barkalow
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
The git-remote-curl backend detects if the remote server supports
the git-upload-pack service, and if so, runs git-fetch-pack locally
in a pipe to generate the want/have commands.
The advertisements from the server that were obtained during the
discovery are passed into git-fetch-pack before the POST request
starts, permitting server capability discovery and enablement.
Common objects that are discovered are appended onto the request as
have lines and are sent again on the next request. This allows the
remote side to reinitialize its in-memory list of common objects
during the next request.
Because all requests are relatively short, below git-remote-curl's
1 MiB buffer limit, requests will use the standard Content-Length
header and be valid HTTP/1.0 POST requests. This makes the fetch
client more tolerant of proxy servers which don't support HTTP/1.1
or the chunked transfer encoding.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
CC: Daniel Barkalow <barkalow@iabervon.org>
---
builtin-fetch-pack.c | 110 ++++++++++++++++++++++++++++++++++++++++++--------
fetch-pack.h | 3 +-
remote-curl.c | 69 +++++++++++++++++++++++++++++--
3 files changed, 160 insertions(+), 22 deletions(-)
diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c
index 615f549..8ed4a6f 100644
--- a/builtin-fetch-pack.c
+++ b/builtin-fetch-pack.c
@@ -165,6 +165,24 @@ enum ack_type {
ACK_ready
};
+static void consume_shallow_list(int fd)
+{
+ if (args.stateless_rpc && args.depth > 0) {
+ /* If we sent a depth we will get back "duplicate"
+ * shallow and unshallow commands every time there
+ * is a block of have lines exchanged.
+ */
+ char line[1000];
+ while (packet_read_line(fd, line, sizeof(line))) {
+ if (!prefixcmp(line, "shallow "))
+ continue;
+ if (!prefixcmp(line, "unshallow "))
+ continue;
+ die("git fetch-pack: expected shallow list");
+ }
+ }
+}
+
static enum ack_type get_ack(int fd, unsigned char *result_sha1)
{
static char line[1000];
@@ -190,6 +208,15 @@ static enum ack_type get_ack(int fd, unsigned char *result_sha1)
die("git fetch_pack: expected ACK/NAK, got '%s'", line);
}
+static void send_request(int fd, struct strbuf *buf)
+{
+ if (args.stateless_rpc) {
+ send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
+ packet_flush(fd);
+ } else
+ safe_write(fd, buf->buf, buf->len);
+}
+
static int find_common(int fd[2], unsigned char *result_sha1,
struct ref *refs)
{
@@ -199,7 +226,10 @@ static int find_common(int fd[2], unsigned char *result_sha1,
unsigned in_vain = 0;
int got_continue = 0;
struct strbuf req_buf = STRBUF_INIT;
+ size_t state_len = 0;
+ if (args.stateless_rpc && multi_ack == 1)
+ die("--stateless-rpc requires multi_ack_detailed");
if (marked)
for_each_ref(clear_marks, NULL);
marked = 1;
@@ -256,13 +286,13 @@ static int find_common(int fd[2], unsigned char *result_sha1,
if (args.depth > 0)
packet_buf_write(&req_buf, "deepen %d", args.depth);
packet_buf_flush(&req_buf);
-
- safe_write(fd[1], req_buf.buf, req_buf.len);
+ state_len = req_buf.len;
if (args.depth > 0) {
char line[1024];
unsigned char sha1[20];
+ send_request(fd[1], &req_buf);
while (packet_read_line(fd[0], line, sizeof(line))) {
if (!prefixcmp(line, "shallow ")) {
if (get_sha1_hex(line + 8, sha1))
@@ -284,28 +314,40 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
die("expected shallow/unshallow, got %s", line);
}
+ } else if (!args.stateless_rpc)
+ send_request(fd[1], &req_buf);
+
+ if (!args.stateless_rpc) {
+ /* If we aren't using the stateless-rpc interface
+ * we don't need to retain the headers.
+ */
+ strbuf_setlen(&req_buf, 0);
+ state_len = 0;
}
flushes = 0;
retval = -1;
while ((sha1 = get_rev())) {
- packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+ packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1));
if (args.verbose)
fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
in_vain++;
if (!(31 & ++count)) {
int ack;
- packet_flush(fd[1]);
+ packet_buf_flush(&req_buf);
+ send_request(fd[1], &req_buf);
+ strbuf_setlen(&req_buf, state_len);
flushes++;
/*
* We keep one window "ahead" of the other side, and
* will wait for an ACK only on the next one
*/
- if (count == 32)
+ if (!args.stateless_rpc && count == 32)
continue;
+ consume_shallow_list(fd[0]);
do {
ack = get_ack(fd[0], result_sha1);
if (args.verbose && ack)
@@ -322,6 +364,17 @@ static int find_common(int fd[2], unsigned char *result_sha1,
case ACK_continue: {
struct commit *commit =
lookup_commit(result_sha1);
+ if (args.stateless_rpc
+ && ack == ACK_common
+ && !(commit->object.flags & COMMON)) {
+ /* We need to replay the have for this object
+ * on the next RPC request so the peer knows
+ * it is in common with us.
+ */
+ const char *hex = sha1_to_hex(result_sha1);
+ packet_buf_write(&req_buf, "have %s\n", hex);
+ state_len = req_buf.len;
+ }
mark_common(commit, 0, 1);
retval = 0;
in_vain = 0;
@@ -339,7 +392,8 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
}
done:
- packet_write(fd[1], "done\n");
+ packet_buf_write(&req_buf, "done\n");
+ send_request(fd[1], &req_buf);
if (args.verbose)
fprintf(stderr, "done\n");
if (retval != 0) {
@@ -348,6 +402,7 @@ done:
}
strbuf_release(&req_buf);
+ consume_shallow_list(fd[0]);
while (flushes || multi_ack) {
int ack = get_ack(fd[0], result_sha1);
if (ack) {
@@ -672,6 +727,8 @@ static struct ref *do_fetch_pack(int fd[2],
*/
warning("no common commits");
+ if (args.stateless_rpc)
+ packet_flush(fd[1]);
if (get_pack(fd, pack_lockfile))
die("git fetch-pack: fetch failed.");
@@ -742,6 +799,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
struct ref *ref = NULL;
char *dest = NULL, **heads;
int fd[2];
+ char *pack_lockfile = NULL;
+ char **pack_lockfile_ptr = NULL;
struct child_process *conn;
nr_heads = 0;
@@ -791,6 +850,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
args.no_progress = 1;
continue;
}
+ if (!strcmp("--stateless-rpc", arg)) {
+ args.stateless_rpc = 1;
+ continue;
+ }
+ if (!strcmp("--lock-pack", arg)) {
+ args.lock_pack = 1;
+ pack_lockfile_ptr = &pack_lockfile;
+ continue;
+ }
usage(fetch_pack_usage);
}
dest = (char *)arg;
@@ -801,19 +869,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
if (!dest)
usage(fetch_pack_usage);
- conn = git_connect(fd, (char *)dest, args.uploadpack,
- args.verbose ? CONNECT_VERBOSE : 0);
- if (conn) {
- get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
-
- ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL);
- close(fd[0]);
- close(fd[1]);
- if (finish_connect(conn))
- ref = NULL;
+ if (args.stateless_rpc) {
+ conn = NULL;
+ fd[0] = 0;
+ fd[1] = 1;
} else {
- ref = NULL;
+ conn = git_connect(fd, (char *)dest, args.uploadpack,
+ args.verbose ? CONNECT_VERBOSE : 0);
+ }
+
+ get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
+
+ ref = fetch_pack(&args, fd, conn, ref, dest,
+ nr_heads, heads, pack_lockfile_ptr);
+ if (pack_lockfile) {
+ printf("lock %s\n", pack_lockfile);
+ fflush(stdout);
}
+ close(fd[0]);
+ close(fd[1]);
+ if (finish_connect(conn))
+ ref = NULL;
ret = !ref;
if (!ret && nr_heads) {
diff --git a/fetch-pack.h b/fetch-pack.h
index 8bd9c32..fbe85ac 100644
--- a/fetch-pack.h
+++ b/fetch-pack.h
@@ -13,7 +13,8 @@ struct fetch_pack_args
fetch_all:1,
verbose:1,
no_progress:1,
- include_tag:1;
+ include_tag:1,
+ stateless_rpc:1;
};
struct ref *fetch_pack(struct fetch_pack_args *args,
diff --git a/remote-curl.c b/remote-curl.c
index f1206cb..0eb6fc4 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -45,7 +45,7 @@ static int set_option(const char *name, const char *value)
options.progress = 0;
else
return -1;
- return 1 /* TODO implement later */;
+ return 0;
}
else if (!strcmp(name, "depth")) {
char *end;
@@ -53,7 +53,7 @@ static int set_option(const char *name, const char *value)
if (value == end || *end)
return -1;
options.depth = v;
- return 1 /* TODO implement later */;
+ return 0;
}
else if (!strcmp(name, "followtags")) {
if (!strcmp(value, "true"))
@@ -62,7 +62,7 @@ static int set_option(const char *name, const char *value)
options.followtags = 0;
else
return -1;
- return 1 /* TODO implement later */;
+ return 0;
}
else if (!strcmp(name, "dry-run")) {
if (!strcmp(value, "true"))
@@ -463,6 +463,8 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch)
char **targets = xmalloc(nr_heads * sizeof(char*));
int ret, i;
+ if (options.depth)
+ die("dumb http transport does not support --depth");
for (i = 0; i < nr_heads; i++)
targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
@@ -481,6 +483,65 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch)
return ret ? error("Fetch failed.") : 0;
}
+static int fetch_git(struct discovery *heads,
+ int nr_heads, struct ref **to_fetch)
+{
+ struct rpc_state rpc;
+ char *depth_arg = NULL;
+ const char **argv;
+ int argc = 0, i, err;
+
+ argv = xmalloc((15 + nr_heads) * sizeof(char*));
+ argv[argc++] = "fetch-pack";
+ argv[argc++] = "--stateless-rpc";
+ argv[argc++] = "--lock-pack";
+ if (options.followtags)
+ argv[argc++] = "--include-tag";
+ if (options.thin)
+ argv[argc++] = "--thin";
+ if (options.verbosity >= 3) {
+ argv[argc++] = "-v";
+ argv[argc++] = "-v";
+ }
+ if (!options.progress)
+ argv[argc++] = "--no-progress";
+ if (options.depth) {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf, "--depth=%lu", options.depth);
+ depth_arg = strbuf_detach(&buf, NULL);
+ argv[argc++] = depth_arg;
+ }
+ argv[argc++] = url;
+ for (i = 0; i < nr_heads; i++) {
+ struct ref *ref = to_fetch[i];
+ if (!ref->name || !*ref->name)
+ die("cannot fetch by sha1 over smart http");
+ argv[argc++] = ref->name;
+ }
+ argv[argc++] = NULL;
+
+ memset(&rpc, 0, sizeof(rpc));
+ rpc.service_name = "git-upload-pack",
+ rpc.argv = argv;
+
+ err = rpc_service(&rpc, heads);
+ if (rpc.result.len)
+ safe_write(1, rpc.result.buf, rpc.result.len);
+ strbuf_release(&rpc.result);
+ free(argv);
+ free(depth_arg);
+ return err;
+}
+
+static int fetch(int nr_heads, struct ref **to_fetch)
+{
+ struct discovery *d = discover_refs("git-upload-pack");
+ if (d->proto_git)
+ return fetch_git(d, nr_heads, to_fetch);
+ else
+ return fetch_dumb(nr_heads, to_fetch);
+}
+
static void parse_fetch(struct strbuf *buf)
{
struct ref **to_fetch = NULL;
@@ -523,7 +584,7 @@ static void parse_fetch(struct strbuf *buf)
break;
} while (1);
- if (fetch_dumb(nr_heads, to_fetch))
+ if (fetch(nr_heads, to_fetch))
exit(128); /* error already reported */
free_refs(list_head);
free(to_fetch);
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 24/28] Smart HTTP fetch: gzip requests
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Daniel Barkalow
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
The upload-pack requests are mostly plain text and they compress
rather well. Deflating them with Content-Encoding: gzip can easily
drop the size of the request by 50%, reducing the amount of data
to transfer as we negotiate the common commits.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
CC: Daniel Barkalow <barkalow@iabervon.org>
---
remote-curl.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 50 insertions(+), 0 deletions(-)
diff --git a/remote-curl.c b/remote-curl.c
index 0eb6fc4..0d7cf16 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -289,6 +289,7 @@ struct rpc_state {
int in;
int out;
struct strbuf result;
+ unsigned gzip_request : 1;
};
static size_t rpc_out(void *ptr, size_t eltsize,
@@ -327,6 +328,8 @@ static int post_rpc(struct rpc_state *rpc)
struct active_request_slot *slot;
struct slot_results results;
struct curl_slist *headers = NULL;
+ int use_gzip = rpc->gzip_request;
+ char *gzip_body = NULL;
int err = 0, large_request = 0;
/* Try to load the entire request, if we can fit it into the
@@ -340,6 +343,7 @@ static int post_rpc(struct rpc_state *rpc)
if (left < LARGE_PACKET_MAX) {
large_request = 1;
+ use_gzip = 0;
break;
}
@@ -355,6 +359,7 @@ static int post_rpc(struct rpc_state *rpc)
curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);
+ curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
headers = curl_slist_append(headers, rpc->hdr_content_type);
headers = curl_slist_append(headers, rpc->hdr_accept);
@@ -372,6 +377,49 @@ static int post_rpc(struct rpc_state *rpc)
fflush(stderr);
}
+ } else if (use_gzip && 1024 < rpc->len) {
+ /* The client backend isn't giving us compressed data so
+ * we can try to deflate it ourselves, this may save on.
+ * the transfer time.
+ */
+ size_t size;
+ z_stream stream;
+ int ret;
+
+ memset(&stream, 0, sizeof(stream));
+ ret = deflateInit2(&stream, Z_BEST_COMPRESSION,
+ Z_DEFLATED, (15 + 16),
+ 8, Z_DEFAULT_STRATEGY);
+ if (ret != Z_OK)
+ die("cannot deflate request; zlib init error %d", ret);
+ size = deflateBound(&stream, rpc->len);
+ gzip_body = xmalloc(size);
+
+ stream.next_in = (unsigned char *)rpc->buf;
+ stream.avail_in = rpc->len;
+ stream.next_out = (unsigned char *)gzip_body;
+ stream.avail_out = size;
+
+ ret = deflate(&stream, Z_FINISH);
+ if (ret != Z_STREAM_END)
+ die("cannot deflate request; zlib deflate error %d", ret);
+
+ ret = deflateEnd(&stream);
+ if (ret != Z_OK)
+ die("cannot deflate request; zlib end error %d", ret);
+
+ size = stream.total_out;
+
+ headers = curl_slist_append(headers, "Content-Encoding: gzip");
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body);
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, size);
+
+ if (options.verbosity > 1) {
+ fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n",
+ rpc->service_name,
+ (unsigned long)rpc->len, (unsigned long)size);
+ fflush(stderr);
+ }
} else {
/* We know the complete request size in advance, use the
* more normal Content-Length approach.
@@ -398,6 +446,7 @@ static int post_rpc(struct rpc_state *rpc)
}
curl_slist_free_all(headers);
+ free(gzip_body);
return err;
}
@@ -523,6 +572,7 @@ static int fetch_git(struct discovery *heads,
memset(&rpc, 0, sizeof(rpc));
rpc.service_name = "git-upload-pack",
rpc.argv = argv;
+ rpc.gzip_request = 1;
err = rpc_service(&rpc, heads);
if (rpc.result.len)
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 21/28] Discover refs via smart HTTP server when available
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Daniel Barkalow
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
Instead of loading the cached info/refs, try to use the smart HTTP
version when the server supports it. Since the smart variant is
actually the pkt-line stream from the start of either upload-pack
or receive-pack we need to parse these through get_remote_heads,
which requires a background thread to feed its pipe.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
CC: Daniel Barkalow <barkalow@iabervon.org>
---
remote-curl.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 131 insertions(+), 17 deletions(-)
diff --git a/remote-curl.c b/remote-curl.c
index 5c9dd97..3917d45 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -5,6 +5,7 @@
#include "http.h"
#include "exec_cmd.h"
#include "run-command.h"
+#include "pkt-line.h"
static struct remote *remote;
static const char *url;
@@ -75,21 +76,46 @@ static int set_option(const char *name, const char *value)
}
}
-static struct ref *get_refs(void)
+struct discovery {
+ const char *service;
+ char *buf_alloc;
+ char *buf;
+ size_t len;
+ unsigned proto_git : 1;
+};
+static struct discovery *last_discovery;
+
+static void free_discovery(struct discovery *d)
+{
+ if (d) {
+ if (d == last_discovery)
+ last_discovery = NULL;
+ free(d->buf_alloc);
+ free(d);
+ }
+}
+
+static struct discovery* discover_refs(const char *service)
{
struct strbuf buffer = STRBUF_INIT;
- char *data, *start, *mid;
- char *ref_name;
+ struct discovery *last = last_discovery;
char *refs_url;
- int i = 0;
- int http_ret;
+ int http_ret, is_http = 0;
- struct ref *refs = NULL;
- struct ref *ref = NULL;
- struct ref *last_ref = NULL;
+ if (last && !strcmp(service, last->service))
+ return last;
+ free_discovery(last);
- refs_url = xmalloc(strlen(url) + 11);
- sprintf(refs_url, "%s/info/refs", url);
+ strbuf_addf(&buffer, "%s/info/refs", url);
+ if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) {
+ is_http = 1;
+ if (!strchr(url, '?'))
+ strbuf_addch(&buffer, '?');
+ else
+ strbuf_addch(&buffer, '&');
+ strbuf_addf(&buffer, "service=%s", service);
+ }
+ refs_url = strbuf_detach(&buffer, NULL);
init_walker();
http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
@@ -104,10 +130,86 @@ static struct ref *get_refs(void)
die("HTTP request failed");
}
- data = buffer.buf;
+ last= xcalloc(1, sizeof(*last_discovery));
+ last->service = service;
+ last->buf_alloc = strbuf_detach(&buffer, &last->len);
+ last->buf = last->buf_alloc;
+
+ if (is_http && 5 <= last->len && last->buf[4] == '#') {
+ /* smart HTTP response; validate that the service
+ * pkt-line matches our request.
+ */
+ struct strbuf exp = STRBUF_INIT;
+
+ if (packet_get_line(&buffer, &last->buf, &last->len) <= 0)
+ die("%s has invalid packet header", refs_url);
+ if (buffer.len && buffer.buf[buffer.len - 1] == '\n')
+ strbuf_setlen(&buffer, buffer.len - 1);
+
+ strbuf_addf(&exp, "# service=%s", service);
+ if (strbuf_cmp(&exp, &buffer))
+ die("invalid server response; got '%s'", buffer.buf);
+ strbuf_release(&exp);
+
+ /* The header can include additional metadata lines, up
+ * until a packet flush marker. Ignore these now, but
+ * in the future we might start to scan them.
+ */
+ strbuf_reset(&buffer);
+ while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
+ strbuf_reset(&buffer);
+
+ last->proto_git = 1;
+ }
+
+ free(refs_url);
+ strbuf_release(&buffer);
+ last_discovery = last;
+ return last;
+}
+
+static int write_discovery(int fd, void *data)
+{
+ struct discovery *heads = data;
+ int err = 0;
+ if (write_in_full(fd, heads->buf, heads->len) != heads->len)
+ err = 1;
+ close(fd);
+ return err;
+}
+
+static struct ref *parse_git_refs(struct discovery *heads)
+{
+ struct ref *list = NULL;
+ struct async async;
+
+ memset(&async, 0, sizeof(async));
+ async.proc = write_discovery;
+ async.data = heads;
+
+ if (start_async(&async))
+ die("cannot start thread to parse advertised refs");
+ get_remote_heads(async.out, &list, 0, NULL, 0, NULL);
+ close(async.out);
+ if (finish_async(&async))
+ die("ref parsing thread failed");
+ return list;
+}
+
+static struct ref *parse_info_refs(struct discovery *heads)
+{
+ char *data, *start, *mid;
+ char *ref_name;
+ int i = 0;
+
+ struct ref *refs = NULL;
+ struct ref *ref = NULL;
+ struct ref *last_ref = NULL;
+
+ data = heads->buf;
start = NULL;
mid = data;
- while (i < buffer.len) {
+ while (i < heads->len) {
if (!start) {
start = &data[i];
}
@@ -131,8 +233,7 @@ static struct ref *get_refs(void)
i++;
}
- strbuf_release(&buffer);
-
+ init_walker();
ref = alloc_ref("HEAD");
if (!walker->fetch_ref(walker, ref) &&
!resolve_remote_symref(ref, refs)) {
@@ -142,11 +243,23 @@ static struct ref *get_refs(void)
free(ref);
}
- strbuf_release(&buffer);
- free(refs_url);
return refs;
}
+static struct ref *get_refs(int for_push)
+{
+ struct discovery *heads;
+
+ if (for_push)
+ heads = discover_refs("git-receive-pack");
+ else
+ heads = discover_refs("git-upload-pack");
+
+ if (heads->proto_git)
+ return parse_git_refs(heads);
+ return parse_info_refs(heads);
+}
+
static void output_refs(struct ref *refs)
{
struct ref *posn;
@@ -317,7 +430,8 @@ int main(int argc, const char **argv)
parse_fetch(&buf);
} else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) {
- output_refs(get_refs());
+ int for_push = !!strstr(buf.buf + 4, "for-push");
+ output_refs(get_refs(for_push));
} else if (!prefixcmp(buf.buf, "push ")) {
parse_push(&buf);
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 18/28] http-backend: use mod_alias instead of mod_rewrite
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Mark Lodato
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
From: Mark Lodato <lodatom@gmail.com>
In the git-http-backend documentation, use mod_alias exlusively, instead
of using a combination of mod_alias and mod_rewrite. This makes the
example slightly shorted and a bit more clear.
Signed-off-by: Mark Lodato <lodatom@gmail.com>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
Documentation/git-http-backend.txt | 10 +++-------
1 files changed, 3 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index 0b5e951..e67519d 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -98,13 +98,9 @@ Accelerated static Apache 2.x::
----------------------------------------------------------------
SetEnv GIT_PROJECT_ROOT /var/www/git
-ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
-Alias /git_static/ /var/www/git/
-
-RewriteEngine on
-RewriteRule ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /git_static/$1 [PT]
-RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.pack)$ /git_static/$1 [PT]
-RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.idx)$ /git_static/$1 [PT]
+AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1
+AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
----------------------------------------------------------------
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 19/28] http-backend: add example for gitweb on same URL
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Mark Lodato
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
From: Mark Lodato <lodatom@gmail.com>
In the git-http-backend documentation, add an example of how to set up
gitweb and git-http-backend on the same URL by using a series of
mod_alias commands.
Signed-off-by: Mark Lodato <lodatom@gmail.com>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
Documentation/git-http-backend.txt | 33 +++++++++++++++++++++++++++++++++
1 files changed, 33 insertions(+), 0 deletions(-)
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index e67519d..2989c9f 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -88,6 +88,23 @@ directive around the repository, or one of its parent directories:
...
</Location>
----------------------------------------------------------------
++
+To serve gitweb at the same url, use a ScriptAliasMatch to only
+those URLs that 'git-http-backend' can handle, and forward the
+rest to gitweb:
++
+----------------------------------------------------------------
+ScriptAliasMatch \
+ "(?x)^/git/(.*/(HEAD | \
+ info/refs | \
+ objects/(info/[^/]+ | \
+ [0-9a-f]{2}/[0-9a-f]{38} | \
+ pack/pack-[0-9a-f]{40}\.(pack|idx)) | \
+ git-(upload|receive)-pack))$" \
+ /usr/libexec/git-core/git-http-backend/$1
+
+ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
+----------------------------------------------------------------
Accelerated static Apache 2.x::
Similar to the above, but Apache can be used to return static
@@ -102,6 +119,22 @@ AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1
AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1
ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
----------------------------------------------------------------
++
+This can be combined with the gitweb configuration:
++
+----------------------------------------------------------------
+SetEnv GIT_PROJECT_ROOT /var/www/git
+
+AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1
+AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1
+ScriptAliasMatch \
+ "(?x)^/git/(.*/(HEAD | \
+ info/refs | \
+ objects/info/[^/]+ | \
+ git-(upload|receive)-pack))$" \
+ /usr/libexec/git-core/git-http-backend/$1
+ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
+----------------------------------------------------------------
ENVIRONMENT
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 15/28] Smart fetch and push over HTTP: server side
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
Requests for $GIT_URL/git-receive-pack and $GIT_URL/git-upload-pack
are forwarded to the corresponding backend process by directly
executing it and leaving stdin and stdout connected to the invoking
web server. Prior to starting the backend process the HTTP response
headers are sent, thereby freeing the backend from needing to know
about the HTTP protocol.
Requests that are encoded with Content-Encoding: gzip are
automatically inflated before being streamed into the backend.
This is primarily useful for the git-upload-pack backend, which
receives highly repetitive text data from clients that easily
compresses to 50% of its original size.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
Documentation/git-http-backend.txt | 39 +++++-
http-backend.c | 324 +++++++++++++++++++++++++++++++++++-
2 files changed, 359 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index 867675f..022a243 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -22,6 +22,23 @@ By default, only the `upload-pack` service is enabled, which serves
This is ideally suited for read-only updates, i.e., pulling from
git repositories.
+SERVICES
+--------
+These services can be enabled/disabled using the per-repository
+configuration file:
+
+http.uploadpack::
+ This serves 'git-fetch-pack' and 'git-ls-remote' clients.
+ It is enabled by default, but a repository can disable it
+ by setting this configuration item to `false`.
+
+http.receivepack::
+ This serves 'git-send-pack' clients, allowing push. It is
+ disabled by default for anonymous users, and enabled by
+ default for users authenticated by the web server. It can be
+ disabled by setting this item to `false`, or enabled for all
+ users, including anonymous users, by setting it to `true`.
+
URL TRANSLATION
---------------
'git-http-backend' relies on the invoking web server to perform
@@ -49,7 +66,19 @@ ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/
</Files>
----------------------------------------------------------------
+
-To require authentication for reads, use a Directory
+To enable anonymous read access but authenticated write access,
+require authorization with a LocationMatch directive:
++
+----------------------------------------------------------------
+<LocationMatch ".*/git-receive-pack$">
+ AuthType Basic
+ AuthName "Git Access"
+ Require group committers
+ ...
+</LocationMatch>
+----------------------------------------------------------------
++
+To require authentication for both reads and writes, use a Directory
directive around the repository, or one of its parent directories:
+
----------------------------------------------------------------
@@ -92,6 +121,14 @@ by the invoking web server, including:
* QUERY_STRING
* REQUEST_METHOD
+The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
+GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
+ensuring that any reflogs created by 'git-receive-pack' contain some
+identifying information of the remote user who performed the push.
+
+All CGI environment variables are available to each of the hooks
+invoked by the 'git-receive-pack'.
+
Author
------
Written by Shawn O. Pearce <spearce@spearce.org>.
diff --git a/http-backend.c b/http-backend.c
index 374f60d..67030b5 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -4,11 +4,109 @@
#include "object.h"
#include "tag.h"
#include "exec_cmd.h"
+#include "run-command.h"
+#include "string-list.h"
static const char content_type[] = "Content-Type";
static const char content_length[] = "Content-Length";
static const char last_modified[] = "Last-Modified";
+static struct string_list *query_params;
+
+struct rpc_service {
+ const char *name;
+ const char *config_name;
+ signed enabled : 2;
+};
+
+static struct rpc_service rpc_service[] = {
+ { "upload-pack", "uploadpack", 1 },
+ { "receive-pack", "receivepack", -1 },
+};
+
+static int decode_char(const char *q)
+{
+ int i;
+ unsigned char val = 0;
+ for (i = 0; i < 2; i++) {
+ unsigned char c = *q++;
+ val <<= 4;
+ if (c >= '0' && c <= '9')
+ val += c - '0';
+ else if (c >= 'a' && c <= 'f')
+ val += c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ val += c - 'A' + 10;
+ else
+ return -1;
+ }
+ return val;
+}
+
+static char *decode_parameter(const char **query, int is_name)
+{
+ const char *q = *query;
+ struct strbuf out;
+
+ strbuf_init(&out, 16);
+ do {
+ unsigned char c = *q;
+
+ if (!c)
+ break;
+ if (c == '&' || (is_name && c == '=')) {
+ q++;
+ break;
+ }
+
+ if (c == '%') {
+ int val = decode_char(q + 1);
+ if (0 <= val) {
+ strbuf_addch(&out, val);
+ q += 3;
+ continue;
+ }
+ }
+
+ if (c == '+')
+ strbuf_addch(&out, ' ');
+ else
+ strbuf_addch(&out, c);
+ q++;
+ } while (1);
+ *query = q;
+ return strbuf_detach(&out, NULL);
+}
+
+static struct string_list *get_parameters(void)
+{
+ if (!query_params) {
+ const char *query = getenv("QUERY_STRING");
+
+ query_params = xcalloc(1, sizeof(*query_params));
+ while (query && *query) {
+ char *name = decode_parameter(&query, 1);
+ char *value = decode_parameter(&query, 0);
+ struct string_list_item *i;
+
+ i = string_list_lookup(name, query_params);
+ if (!i)
+ i = string_list_insert(name, query_params);
+ else
+ free(i->util);
+ i->util = value;
+ }
+ }
+ return query_params;
+}
+
+static const char *get_parameter(const char *name)
+{
+ struct string_list_item *i;
+ i = string_list_lookup(name, get_parameters());
+ return i ? i->util : NULL;
+}
+
static void format_write(int fd, const char *fmt, ...)
{
static char buffer[1024];
@@ -81,6 +179,21 @@ static NORETURN void not_found(const char *err, ...)
exit(0);
}
+static NORETURN void forbidden(const char *err, ...)
+{
+ va_list params;
+
+ http_status(403, "Forbidden");
+ hdr_nocache();
+ end_headers();
+
+ va_start(params, err);
+ if (err && *err)
+ vfprintf(stderr, err, params);
+ va_end(params);
+ exit(0);
+}
+
static void send_strbuf(const char *type, struct strbuf *buf)
{
hdr_int(content_length, buf->len);
@@ -147,6 +260,145 @@ static void get_idx_file(char *name)
send_file("application/x-git-packed-objects-toc", name);
}
+static int http_config(const char *var, const char *value, void *cb)
+{
+ struct rpc_service *svc = cb;
+
+ if (!prefixcmp(var, "http.") &&
+ !strcmp(var + 5, svc->config_name)) {
+ svc->enabled = git_config_bool(var, value);
+ return 0;
+ }
+
+ /* we are not interested in parsing any other configuration here */
+ return 0;
+}
+
+static struct rpc_service *select_service(const char *name)
+{
+ struct rpc_service *svc = NULL;
+ int i;
+
+ if (prefixcmp(name, "git-"))
+ forbidden("Unsupported service: '%s'", name);
+
+ for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
+ struct rpc_service *s = &rpc_service[i];
+ if (!strcmp(s->name, name + 4)) {
+ svc = s;
+ break;
+ }
+ }
+
+ if (!svc)
+ forbidden("Unsupported service: '%s'", name);
+
+ git_config(http_config, svc);
+ if (svc->enabled < 0) {
+ const char *user = getenv("REMOTE_USER");
+ svc->enabled = (user && *user) ? 1 : 0;
+ }
+ if (!svc->enabled)
+ forbidden("Service not enabled: '%s'", svc->name);
+ return svc;
+}
+
+static void inflate_request(const char *prog_name, int out)
+{
+ z_stream stream;
+ unsigned char in_buf[8192];
+ unsigned char out_buf[8192];
+ unsigned long cnt = 0;
+ int ret;
+
+ memset(&stream, 0, sizeof(stream));
+ ret = inflateInit2(&stream, (15 + 16));
+ if (ret != Z_OK)
+ die("cannot start zlib inflater, zlib err %d", ret);
+
+ while (1) {
+ ssize_t n = xread(0, in_buf, sizeof(in_buf));
+ if (n <= 0)
+ die("request ended in the middle of the gzip stream");
+
+ stream.next_in = in_buf;
+ stream.avail_in = n;
+
+ while (0 < stream.avail_in) {
+ int ret;
+
+ stream.next_out = out_buf;
+ stream.avail_out = sizeof(out_buf);
+
+ ret = inflate(&stream, Z_NO_FLUSH);
+ if (ret != Z_OK && ret != Z_STREAM_END)
+ die("zlib error inflating request, result %d", ret);
+
+ n = stream.total_out - cnt;
+ if (write_in_full(out, out_buf, n) != n)
+ die("%s aborted reading request", prog_name);
+ cnt += n;
+
+ if (ret == Z_STREAM_END)
+ goto done;
+ }
+ }
+
+done:
+ inflateEnd(&stream);
+ close(out);
+}
+
+static void run_service(const char **argv)
+{
+ const char *encoding = getenv("HTTP_CONTENT_ENCODING");
+ const char *user = getenv("REMOTE_USER");
+ const char *host = getenv("REMOTE_ADDR");
+ char *env[3];
+ struct strbuf buf = STRBUF_INIT;
+ int gzipped_request = 0;
+ struct child_process cld;
+
+ if (encoding && !strcmp(encoding, "gzip"))
+ gzipped_request = 1;
+ else if (encoding && !strcmp(encoding, "x-gzip"))
+ gzipped_request = 1;
+
+ if (!user || !*user)
+ user = "anonymous";
+ if (!host || !*host)
+ host = "(none)";
+
+ memset(&env, 0, sizeof(env));
+ strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user);
+ env[0] = strbuf_detach(&buf, NULL);
+
+ strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
+ env[1] = strbuf_detach(&buf, NULL);
+ env[2] = NULL;
+
+ memset(&cld, 0, sizeof(cld));
+ cld.argv = argv;
+ cld.env = (const char *const *)env;
+ if (gzipped_request)
+ cld.in = -1;
+ cld.git_cmd = 1;
+ if (start_command(&cld))
+ exit(1);
+
+ close(1);
+ if (gzipped_request)
+ inflate_request(argv[0], cld.in);
+ else
+ close(0);
+
+ if (finish_command(&cld))
+ exit(1);
+ free(env[0]);
+ free(env[1]);
+ strbuf_release(&buf);
+}
+
static int show_text_ref(const char *name, const unsigned char *sha1,
int flag, void *cb_data)
{
@@ -167,11 +419,32 @@ static int show_text_ref(const char *name, const unsigned char *sha1,
static void get_info_refs(char *arg)
{
+ const char *service_name = get_parameter("service");
struct strbuf buf = STRBUF_INIT;
- for_each_ref(show_text_ref, &buf);
hdr_nocache();
- send_strbuf("text/plain", &buf);
+
+ if (service_name) {
+ const char *argv[] = {NULL /* service name */,
+ "--stateless-rpc", "--advertise-refs",
+ ".", NULL};
+ struct rpc_service *svc = select_service(service_name);
+
+ strbuf_addf(&buf, "application/x-git-%s-advertisement",
+ svc->name);
+ hdr_str(content_type, buf.buf);
+ end_headers();
+
+ packet_write(1, "# service=git-%s\n", svc->name);
+ packet_flush(1);
+
+ argv[0] = svc->name;
+ run_service(argv);
+
+ } else {
+ for_each_ref(show_text_ref, &buf);
+ send_strbuf("text/plain", &buf);
+ }
strbuf_release(&buf);
}
@@ -200,6 +473,48 @@ static void get_info_packs(char *arg)
strbuf_release(&buf);
}
+static void check_content_type(const char *accepted_type)
+{
+ const char *actual_type = getenv("CONTENT_TYPE");
+
+ if (!actual_type)
+ actual_type = "";
+
+ if (strcmp(actual_type, accepted_type)) {
+ http_status(415, "Unsupported Media Type");
+ hdr_nocache();
+ end_headers();
+ format_write(1,
+ "Expected POST with Content-Type '%s',"
+ " but received '%s' instead.\n",
+ accepted_type, actual_type);
+ exit(0);
+ }
+}
+
+static void service_rpc(char *service_name)
+{
+ const char *argv[] = {NULL, "--stateless-rpc", ".", NULL};
+ struct rpc_service *svc = select_service(service_name);
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "application/x-git-%s-request", svc->name);
+ check_content_type(buf.buf);
+
+ hdr_nocache();
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "application/x-git-%s-result", svc->name);
+ hdr_str(content_type, buf.buf);
+
+ end_headers();
+
+ argv[0] = svc->name;
+ run_service(argv);
+ strbuf_release(&buf);
+}
+
static NORETURN void die_webcgi(const char *err, va_list params)
{
char buffer[1000];
@@ -226,7 +541,10 @@ static struct service_cmd {
{"GET", "/objects/info/[^/]*$", get_text_file},
{"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object},
{"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file},
- {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file}
+ {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file},
+
+ {"POST", "/git-upload-pack$", service_rpc},
+ {"POST", "/git-receive-pack$", service_rpc}
};
int main(int argc, char **argv)
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 10/28] remote-helpers: Support custom transport options
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Daniel Barkalow
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
Some transports, like the native pack transport implemented by
fetch-pack, support useful features like depth or include tags.
These should be exposed if the underlying helper knows how to
use them.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
CC: Daniel Barkalow <barkalow@iabervon.org>
---
Documentation/git-remote-helpers.txt | 38 +++++++++++++++
remote-curl.c | 74 ++++++++++++++++++++++++++++-
transport-helper.c | 88 +++++++++++++++++++++++++++++++++-
3 files changed, 198 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
index e44d821..1133f04 100644
--- a/Documentation/git-remote-helpers.txt
+++ b/Documentation/git-remote-helpers.txt
@@ -35,6 +35,16 @@ Commands are given by the caller on the helper's standard input, one per line.
the name; unrecognized attributes are ignored. After the
complete list, outputs a blank line.
+'option' <name> <value>::
+ Set the transport helper option <name> to <value>. Outputs a
+ single line containing one of 'ok' (option successfully set),
+ 'unsupported' (option not recognized) or 'error <msg>'
+ (option <name> is supported but <value> is not correct
+ for it). Options should be set before other commands,
+ and may how those commands behave.
++
+Supported if the helper has the "option" capability.
+
'fetch' <sha1> <name>::
Fetches the given object, writing the necessary objects
to the database. Fetch commands are sent in a batch, one
@@ -63,11 +73,39 @@ CAPABILITIES
'fetch'::
This helper supports the 'fetch' command.
+'option'::
+ This helper supports the option command.
+
REF LIST ATTRIBUTES
-------------------
None are defined yet, but the caller must accept any which are supplied.
+OPTIONS
+-------
+'option verbosity' <N>::
+ Change the level of messages displayed by the helper.
+ When N is 0 the end-user has asked the process to be
+ quiet, and the helper should produce only error output.
+ N of 1 is the default level of verbosity, higher values
+ of N correspond to the number of -v flags passed on the
+ command line.
+
+'option progress' \{'true'|'false'\}::
+ Enable (or disable) progress messages displayed by the
+ transport helper during a command.
+
+'option depth' <depth>::
+ Deepen the history of a shallow repository.
+
+'option followtags' \{'true'|'false'\}::
+ If enabled the helper should automatically fetch annotated
+ tag objects if the object the tag points at was transferred
+ during the fetch command. If the tag is not fetched by
+ the helper a second fetch command will usually be sent to
+ ask for the tag specifically. Some helpers may be able to
+ use this option to avoid a second network connection.
+
Documentation
-------------
Documentation by Daniel Barkalow.
diff --git a/remote-curl.c b/remote-curl.c
index 22cd5c5..0951f11 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -9,12 +9,61 @@ static struct remote *remote;
static const char *url;
static struct walker *walker;
+struct options {
+ int verbosity;
+ unsigned long depth;
+ unsigned progress : 1,
+ followtags : 1;
+};
+static struct options options;
+
static void init_walker(void)
{
if (!walker)
walker = get_http_walker(url, remote);
}
+static int set_option(const char *name, const char *value)
+{
+ if (!strcmp(name, "verbosity")) {
+ char *end;
+ int v = strtol(value, &end, 10);
+ if (value == end || *end)
+ return -1;
+ options.verbosity = v;
+ return 0;
+ }
+ else if (!strcmp(name, "progress")) {
+ if (!strcmp(value, "true"))
+ options.progress = 1;
+ else if (!strcmp(value, "false"))
+ options.progress = 0;
+ else
+ return -1;
+ return 1 /* TODO implement later */;
+ }
+ else if (!strcmp(name, "depth")) {
+ char *end;
+ unsigned long v = strtoul(value, &end, 10);
+ if (value == end || *end)
+ return -1;
+ options.depth = v;
+ return 1 /* TODO implement later */;
+ }
+ else if (!strcmp(name, "followtags")) {
+ if (!strcmp(value, "true"))
+ options.followtags = 1;
+ else if (!strcmp(value, "false"))
+ options.followtags = 0;
+ else
+ return -1;
+ return 1 /* TODO implement later */;
+ }
+ else {
+ return 1 /* unsupported */;
+ }
+}
+
static struct ref *get_refs(void)
{
struct strbuf buffer = STRBUF_INIT;
@@ -99,7 +148,7 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch)
walker->get_all = 1;
walker->get_tree = 1;
walker->get_history = 1;
- walker->get_verbosely = 0;
+ walker->get_verbosely = options.verbosity >= 3;
walker->get_recover = 0;
ret = walker_fetch(walker, nr_heads, targets, NULL, NULL);
@@ -173,6 +222,9 @@ int main(int argc, const char **argv)
return 1;
}
+ options.verbosity = 1;
+ options.progress = !!isatty(2);
+
remote = remote_get(argv[1]);
if (argc > 2) {
@@ -198,8 +250,28 @@ int main(int argc, const char **argv)
}
printf("\n");
fflush(stdout);
+ } else if (!prefixcmp(buf.buf, "option ")) {
+ char *name = buf.buf + strlen("option ");
+ char *value = strchr(name, ' ');
+ int result;
+
+ if (value)
+ *value++ = '\0';
+ else
+ value = "true";
+
+ result = set_option(name, value);
+ if (!result)
+ printf("ok\n");
+ else if (result < 0)
+ printf("error invalid value\n");
+ else
+ printf("unsupported\n");
+ fflush(stdout);
+
} else if (!strcmp(buf.buf, "capabilities")) {
printf("fetch\n");
+ printf("option\n");
printf("\n");
fflush(stdout);
} else {
diff --git a/transport-helper.c b/transport-helper.c
index 9de3408..577abc6 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -5,13 +5,15 @@
#include "commit.h"
#include "diff.h"
#include "revision.h"
+#include "quote.h"
struct helper_data
{
const char *name;
struct child_process *helper;
FILE *out;
- unsigned fetch : 1;
+ unsigned fetch : 1,
+ option : 1;
};
static struct child_process *get_helper(struct transport *transport)
@@ -48,6 +50,8 @@ static struct child_process *get_helper(struct transport *transport)
break;
if (!strcmp(buf.buf, "fetch"))
data->fetch = 1;
+ if (!strcmp(buf.buf, "option"))
+ data->option = 1;
}
return data->helper;
}
@@ -65,9 +69,88 @@ static int disconnect_helper(struct transport *transport)
free(data->helper);
data->helper = NULL;
}
+ free(data);
return 0;
}
+static const char *unsupported_options[] = {
+ TRANS_OPT_UPLOADPACK,
+ TRANS_OPT_RECEIVEPACK,
+ TRANS_OPT_THIN,
+ TRANS_OPT_KEEP
+ };
+static const char *boolean_options[] = {
+ TRANS_OPT_THIN,
+ TRANS_OPT_KEEP,
+ TRANS_OPT_FOLLOWTAGS
+ };
+
+static int set_helper_option(struct transport *transport,
+ const char *name, const char *value)
+{
+ struct helper_data *data = transport->data;
+ struct child_process *helper = get_helper(transport);
+ struct strbuf buf = STRBUF_INIT;
+ int i, ret, is_bool = 0;
+
+ if (!data->option)
+ return 1;
+
+ for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) {
+ if (!strcmp(name, unsupported_options[i]))
+ return 1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(boolean_options); i++) {
+ if (!strcmp(name, boolean_options[i])) {
+ is_bool = 1;
+ break;
+ }
+ }
+
+ strbuf_addf(&buf, "option %s ", name);
+ if (is_bool)
+ strbuf_addstr(&buf, value ? "true" : "false");
+ else
+ quote_c_style(value, &buf, NULL, 0);
+ strbuf_addch(&buf, '\n');
+
+ if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
+ die_errno("cannot send option to %s", data->name);
+
+ strbuf_reset(&buf);
+ if (strbuf_getline(&buf, data->out, '\n') == EOF)
+ exit(128); /* child died, message supplied already */
+
+ if (!strcmp(buf.buf, "ok"))
+ ret = 0;
+ else if (!prefixcmp(buf.buf, "error")) {
+ ret = -1;
+ } else if (!strcmp(buf.buf, "unsupported"))
+ ret = 1;
+ else {
+ warning("%s unexpectedly said: '%s'", data->name, buf.buf);
+ ret = 1;
+ }
+ strbuf_release(&buf);
+ return ret;
+}
+
+static void standard_options(struct transport *t)
+{
+ char buf[16];
+ int n;
+ int v = t->verbose;
+ int no_progress = v < 0 || (!t->progress && !isatty(1));
+
+ set_helper_option(t, "progress", !no_progress ? "true" : "false");
+
+ n = snprintf(buf, sizeof(buf), "%d", v + 1);
+ if (n >= sizeof(buf))
+ die("impossibly large verbosity value");
+ set_helper_option(t, "verbosity", buf);
+}
+
static int fetch_with_fetch(struct transport *transport,
int nr_heads, const struct ref **to_fetch)
{
@@ -75,6 +158,8 @@ static int fetch_with_fetch(struct transport *transport,
int i;
struct strbuf buf = STRBUF_INIT;
+ standard_options(transport);
+
for (i = 0; i < nr_heads; i++) {
const struct ref *posn = to_fetch[i];
if (posn->status & REF_STATUS_UPTODATE)
@@ -178,6 +263,7 @@ int transport_helper_init(struct transport *transport, const char *name)
data->name = name;
transport->data = data;
+ transport->set_option = set_helper_option;
transport->get_refs_list = get_refs_list;
transport->fetch = fetch;
transport->disconnect = disconnect_helper;
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 12/28] remote-helpers: return successfully if everything up-to-date
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Clemens Buchacher
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
From: Clemens Buchacher <drizzd@aon.at>
Signed-off-by: Clemens Buchacher <drizzd@aon.at>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
t/t5540-http-push.sh | 2 +-
transport-helper.c | 2 ++
2 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index 09edd23..2ece661 100755
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
@@ -58,7 +58,7 @@ test_expect_success 'push to remote repository with packed refs' '
test $HEAD = $(git rev-parse --verify HEAD))
'
-test_expect_failure 'push already up-to-date' '
+test_expect_success 'push already up-to-date' '
git push
'
diff --git a/transport-helper.c b/transport-helper.c
index 16c6641..5078c71 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -263,6 +263,8 @@ static int push_refs(struct transport *transport,
strbuf_addstr(&buf, ref->name);
strbuf_addch(&buf, '\n');
}
+ if (buf.len == 0)
+ return 0;
transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0;
standard_options(transport);
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
* [PATCH v5 16/28] http-backend: add GIT_PROJECT_ROOT environment var
From: Shawn O. Pearce @ 2009-10-31 0:47 UTC (permalink / raw)
To: git; +Cc: Mark Lodato
In-Reply-To: <1256950067-27938-1-git-send-email-spearce@spearce.org>
From: Mark Lodato <lodatom@gmail.com>
Add a new environment variable, GIT_PROJECT_ROOT, to override the
method of using PATH_TRANSLATED to find the git repository on disk.
This makes it much easier to configure the web server, especially when
the web server's DocumentRoot does not contain the git repositories,
which is the usual case.
Signed-off-by: Mark Lodato <lodatom@gmail.com>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
Documentation/git-http-backend.txt | 39 +++++++++++++++--------------------
http-backend.c | 25 ++++++++++++++++++++--
2 files changed, 39 insertions(+), 25 deletions(-)
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index 022a243..99dbbfb 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -41,29 +41,24 @@ http.receivepack::
URL TRANSLATION
---------------
-'git-http-backend' relies on the invoking web server to perform
-URL to path translation, and store the repository path into the
-PATH_TRANSLATED environment variable. Most web servers will do
-this translation automatically, resolving the suffix after the
-CGI name relative to the server's document root.
+To determine the location of the repository on disk, 'git-http-backend'
+concatenates the environment variables PATH_INFO, which is set
+automatically by the web server, and GIT_PROJECT_ROOT, which must be set
+manually in the web server configuration. If GIT_PROJECT_ROOT is not
+set, 'git-http-backend' reads PATH_TRANSLATED, which is also set
+automatically by the web server.
EXAMPLES
--------
Apache 2.x::
- To serve all Git repositories contained within the '/git/'
- subdirectory of the DocumentRoot, ensure mod_cgi and
- mod_alias are enabled, and create a ScriptAlias to the CGI:
+ Ensure mod_cgi, mod_alias, and mod_env are enabled, set
+ GIT_PROJECT_ROOT (or DocumentRoot) appropriately, and
+ create a ScriptAlias to the CGI:
+
----------------------------------------------------------------
-ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/
-
-<Directory /usr/libexec/git-core>
- Options None
-</Directory>
-<Files /usr/libexec/git-core/git-http-backend>
- Options ExecCGI
-</Files>
+SetEnv GIT_PROJECT_ROOT /var/www/git
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
----------------------------------------------------------------
+
To enable anonymous read access but authenticated write access,
@@ -78,16 +73,16 @@ require authorization with a LocationMatch directive:
</LocationMatch>
----------------------------------------------------------------
+
-To require authentication for both reads and writes, use a Directory
+To require authentication for both reads and writes, use a Location
directive around the repository, or one of its parent directories:
+
----------------------------------------------------------------
-<Directory /var/www/git/private>
+<Location /git/private>
AuthType Basic
AuthName "Private Git Access"
Require group committers
...
-</Directory>
+</Location>
----------------------------------------------------------------
Accelerated static Apache 2.x::
@@ -97,9 +92,9 @@ Accelerated static Apache 2.x::
file contents from the file system directly to the network:
+
----------------------------------------------------------------
-DocumentRoot /var/www
+SetEnv GIT_PROJECT_ROOT /var/www/git
-ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
Alias /git_static/ /var/www/git/
RewriteEngine on
@@ -114,7 +109,7 @@ ENVIRONMENT
'git-http-backend' relies upon the CGI environment variables set
by the invoking web server, including:
-* PATH_TRANSLATED
+* PATH_INFO (if GIT_PROJECT_ROOT is set, otherwise PATH_TRANSLATED)
* REMOTE_USER
* REMOTE_ADDR
* CONTENT_TYPE
diff --git a/http-backend.c b/http-backend.c
index 67030b5..8e5c0a2 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -528,6 +528,26 @@ static NORETURN void die_webcgi(const char *err, va_list params)
exit(0);
}
+static char* getdir(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ char *pathinfo = getenv("PATH_INFO");
+ char *root = getenv("GIT_PROJECT_ROOT");
+ char *path = getenv("PATH_TRANSLATED");
+
+ if (root && *root) {
+ if (!pathinfo || !*pathinfo)
+ die("GIT_PROJECT_ROOT is set but PATH_INFO is not");
+ strbuf_addstr(&buf, root);
+ strbuf_addstr(&buf, pathinfo);
+ return strbuf_detach(&buf, NULL);
+ } else if (path && *path) {
+ return xstrdup(path);
+ } else
+ die("No GIT_PROJECT_ROOT or PATH_TRANSLATED from server");
+ return NULL;
+}
+
static struct service_cmd {
const char *method;
const char *pattern;
@@ -550,7 +570,7 @@ static struct service_cmd {
int main(int argc, char **argv)
{
char *method = getenv("REQUEST_METHOD");
- char *dir = getenv("PATH_TRANSLATED");
+ char *dir;
struct service_cmd *cmd = NULL;
char *cmd_arg = NULL;
int i;
@@ -562,8 +582,7 @@ int main(int argc, char **argv)
die("No REQUEST_METHOD from server");
if (!strcmp(method, "HEAD"))
method = "GET";
- if (!dir)
- die("No PATH_TRANSLATED from server");
+ dir = getdir();
for (i = 0; i < ARRAY_SIZE(services); i++) {
struct service_cmd *c = &services[i];
--
1.6.5.2.181.gd6f41
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox