Merge branch 'master' into jc/globfetch

This is to pick up the fix made on master:

  git-fetch: exit with non-zero status when fast-forward check fails
This commit is contained in:
Junio C Hamano 2006-11-25 01:04:54 -08:00
commit cadd8a7d4d
18 changed files with 1015 additions and 312 deletions

View File

@ -219,6 +219,12 @@ i18n.commitEncoding::
browser (and possibly at other places in the future or in other browser (and possibly at other places in the future or in other
porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'. porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'.
log.showroot::
If true, the initial commit will be shown as a big creation event.
This is equivalent to a diff against an empty tree.
Tools like gitlink:git-log[1] or gitlink:git-whatchanged[1], which
normally hide the root commit will now show it. True by default.
merge.summary:: merge.summary::
Whether to include summaries of merged commits in newly created Whether to include summaries of merged commits in newly created
merge commit messages. False by default. merge commit messages. False by default.

View File

@ -8,14 +8,16 @@ git-branch - List, create, or delete branches.
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git-branch' [-r] 'git-branch' [-r] [-a] [-v] [--abbrev=<length>]
'git-branch' [-l] [-f] <branchname> [<start-point>] 'git-branch' [-l] [-f] <branchname> [<start-point>]
'git-branch' (-d | -D) <branchname>... 'git-branch' (-d | -D) <branchname>...
DESCRIPTION DESCRIPTION
----------- -----------
With no arguments given (or just `-r`) a list of available branches With no arguments given a list of existing branches
will be shown, the current branch will be highlighted with an asterisk. will be shown, the current branch will be highlighted with an asterisk.
Option `-r` causes the remote-tracking branches to be listed,
and option `-a` shows both.
In its second form, a new branch named <branchname> will be created. In its second form, a new branch named <branchname> will be created.
It will start out with a head equal to the one given as <start-point>. It will start out with a head equal to the one given as <start-point>.
@ -45,7 +47,17 @@ OPTIONS
a branch that already exists with the same name. a branch that already exists with the same name.
-r:: -r::
List only the "remote" branches. List the remote-tracking branches.
-a::
List both remote-tracking branches and local branches.
-v::
Show sha1 and subject message for each head.
--abbrev=<length>::
Alter minimum display length for sha1 in output listing,
default value is 7.
<branchname>:: <branchname>::
The name of the branch to create or delete. The name of the branch to create or delete.

View File

@ -11,7 +11,8 @@ SYNOPSIS
[verse] [verse]
'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare] 'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
[-o <name>] [-u <upload-pack>] [--reference <repository>] [-o <name>] [-u <upload-pack>] [--reference <repository>]
[--use-separate-remote] <repository> [<directory>] [--use-separate-remote | --use-immingled-remote] <repository>
[<directory>]
DESCRIPTION DESCRIPTION
----------- -----------
@ -71,9 +72,13 @@ OPTIONS
Make a 'bare' GIT repository. That is, instead of Make a 'bare' GIT repository. That is, instead of
creating `<directory>` and placing the administrative creating `<directory>` and placing the administrative
files in `<directory>/.git`, make the `<directory>` files in `<directory>/.git`, make the `<directory>`
itself the `$GIT_DIR`. This implies `-n` option. When itself the `$GIT_DIR`. This obviously implies the `-n`
this option is used, neither the `origin` branch nor the because there is nowhere to check out the working tree.
default `remotes/origin` file is created. Also the branch heads at the remote are copied directly
to corresponding local branch heads, without mapping
them to `refs/remotes/origin/`. When this option is
used, neither the `origin` branch nor the default
`remotes/origin` file is created.
--origin <name>:: --origin <name>::
-o <name>:: -o <name>::
@ -97,8 +102,15 @@ OPTIONS
--use-separate-remote:: --use-separate-remote::
Save remotes heads under `$GIT_DIR/remotes/origin/` instead Save remotes heads under `$GIT_DIR/remotes/origin/` instead
of `$GIT_DIR/refs/heads/`. Only the master branch is saved of `$GIT_DIR/refs/heads/`. Only the local master branch is
in the latter. saved in the latter. This is the default.
--use-immingled-remote::
Save remotes heads in the same namespace as the local
heads, `$GIT_DIR/refs/heads/'. In regular repositories,
this is a legacy setup git-clone created by default in
older Git versions, and will be removed before the next
major release.
<repository>:: <repository>::
The (possibly remote) repository to clone from. It can The (possibly remote) repository to clone from. It can

View File

@ -2119,7 +2119,11 @@ static void numstat_patch_list(struct patch *patch)
for ( ; patch; patch = patch->next) { for ( ; patch; patch = patch->next) {
const char *name; const char *name;
name = patch->new_name ? patch->new_name : patch->old_name; name = patch->new_name ? patch->new_name : patch->old_name;
printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); if (patch->is_binary)
printf("-\t-\t");
else
printf("%d\t%d\t",
patch->lines_added, patch->lines_deleted);
if (line_termination && quote_c_style(name, NULL, NULL, 0)) if (line_termination && quote_c_style(name, NULL, NULL, 0))
quote_c_style(name, NULL, stdout, 0); quote_c_style(name, NULL, stdout, 0);
else else

View File

@ -11,7 +11,7 @@
#include "builtin.h" #include "builtin.h"
static const char builtin_branch_usage[] = static const char builtin_branch_usage[] =
"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]"; "git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r | -a] [-v] [--abbrev=<length>] ";
static const char *head; static const char *head;
@ -38,12 +38,16 @@ static int in_merge_bases(const unsigned char *sha1,
static void delete_branches(int argc, const char **argv, int force) static void delete_branches(int argc, const char **argv, int force)
{ {
struct commit *rev, *head_rev; struct commit *rev, *head_rev = head_rev;
unsigned char sha1[20]; unsigned char sha1[20];
char *name; char *name;
int i; int i;
if (!force) {
head_rev = lookup_commit_reference(head_sha1); head_rev = lookup_commit_reference(head_sha1);
if (!head_rev)
die("Couldn't look up commit object for HEAD");
}
for (i = 0; i < argc; i++) { for (i = 0; i < argc; i++) {
if (!strcmp(head, argv[i])) if (!strcmp(head, argv[i]))
die("Cannot delete the branch you are currently on."); die("Cannot delete the branch you are currently on.");
@ -53,8 +57,8 @@ static void delete_branches(int argc, const char **argv, int force)
die("Branch '%s' not found.", argv[i]); die("Branch '%s' not found.", argv[i]);
rev = lookup_commit_reference(sha1); rev = lookup_commit_reference(sha1);
if (!rev || !head_rev) if (!rev)
die("Couldn't look up commit objects."); die("Couldn't look up commit object for '%s'", name);
/* This checks whether the merge bases of branch and /* This checks whether the merge bases of branch and
* HEAD contains branch -- which means that the HEAD * HEAD contains branch -- which means that the HEAD
@ -79,46 +83,129 @@ static void delete_branches(int argc, const char **argv, int force)
} }
} }
static int ref_index, ref_alloc; #define REF_UNKNOWN_TYPE 0x00
static char **ref_list; #define REF_LOCAL_BRANCH 0x01
#define REF_REMOTE_BRANCH 0x02
#define REF_TAG 0x04
static int append_ref(const char *refname, const unsigned char *sha1, int flags, struct ref_item {
void *cb_data) char *name;
unsigned int kind;
unsigned char sha1[20];
};
struct ref_list {
int index, alloc, maxwidth;
struct ref_item *list;
int kinds;
};
static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
{ {
if (ref_index >= ref_alloc) { struct ref_list *ref_list = (struct ref_list*)(cb_data);
ref_alloc = alloc_nr(ref_alloc); struct ref_item *newitem;
ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *)); int kind = REF_UNKNOWN_TYPE;
int len;
/* Detect kind */
if (!strncmp(refname, "refs/heads/", 11)) {
kind = REF_LOCAL_BRANCH;
refname += 11;
} else if (!strncmp(refname, "refs/remotes/", 13)) {
kind = REF_REMOTE_BRANCH;
refname += 13;
} else if (!strncmp(refname, "refs/tags/", 10)) {
kind = REF_TAG;
refname += 10;
} }
ref_list[ref_index++] = xstrdup(refname); /* Don't add types the caller doesn't want */
if ((kind & ref_list->kinds) == 0)
return 0;
/* Resize buffer */
if (ref_list->index >= ref_list->alloc) {
ref_list->alloc = alloc_nr(ref_list->alloc);
ref_list->list = xrealloc(ref_list->list,
ref_list->alloc * sizeof(struct ref_item));
}
/* Record the new item */
newitem = &(ref_list->list[ref_list->index++]);
newitem->name = xstrdup(refname);
newitem->kind = kind;
hashcpy(newitem->sha1, sha1);
len = strlen(newitem->name);
if (len > ref_list->maxwidth)
ref_list->maxwidth = len;
return 0; return 0;
} }
static int ref_cmp(const void *r1, const void *r2) static void free_ref_list(struct ref_list *ref_list)
{ {
return strcmp(*(char **)r1, *(char **)r2); int i;
for (i = 0; i < ref_list->index; i++)
free(ref_list->list[i].name);
free(ref_list->list);
} }
static void print_ref_list(int remote_only) static int ref_cmp(const void *r1, const void *r2)
{
struct ref_item *c1 = (struct ref_item *)(r1);
struct ref_item *c2 = (struct ref_item *)(r2);
if (c1->kind != c2->kind)
return c1->kind - c2->kind;
return strcmp(c1->name, c2->name);
}
static void print_ref_info(const unsigned char *sha1, int abbrev)
{
struct commit *commit;
char subject[256];
commit = lookup_commit(sha1);
if (commit && !parse_commit(commit))
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
subject, sizeof(subject), 0,
NULL, NULL, 0);
else
strcpy(subject, " **** invalid ref ****");
printf(" %s %s\n", find_unique_abbrev(sha1, abbrev), subject);
}
static void print_ref_list(int kinds, int verbose, int abbrev)
{ {
int i; int i;
char c; char c;
struct ref_list ref_list;
if (remote_only) memset(&ref_list, 0, sizeof(ref_list));
for_each_remote_ref(append_ref, NULL); ref_list.kinds = kinds;
else for_each_ref(append_ref, &ref_list);
for_each_branch_ref(append_ref, NULL);
qsort(ref_list, ref_index, sizeof(char *), ref_cmp); qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
for (i = 0; i < ref_index; i++) { for (i = 0; i < ref_list.index; i++) {
c = ' '; c = ' ';
if (!strcmp(ref_list[i], head)) if (ref_list.list[i].kind == REF_LOCAL_BRANCH &&
!strcmp(ref_list.list[i].name, head))
c = '*'; c = '*';
printf("%c %s\n", c, ref_list[i]); if (verbose) {
printf("%c %-*s", c, ref_list.maxwidth,
ref_list.list[i].name);
print_ref_info(ref_list.list[i].sha1, abbrev);
} }
else
printf("%c %s\n", c, ref_list.list[i].name);
}
free_ref_list(&ref_list);
} }
static void create_branch(const char *name, const char *start, static void create_branch(const char *name, const char *start,
@ -160,8 +247,10 @@ static void create_branch(const char *name, const char *start,
int cmd_branch(int argc, const char **argv, const char *prefix) int cmd_branch(int argc, const char **argv, const char *prefix)
{ {
int delete = 0, force_delete = 0, force_create = 0, remote_only = 0; int delete = 0, force_delete = 0, force_create = 0;
int verbose = 0, abbrev = DEFAULT_ABBREV;
int reflog = 0; int reflog = 0;
int kinds = REF_LOCAL_BRANCH;
int i; int i;
git_config(git_default_config); git_config(git_default_config);
@ -189,13 +278,25 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
continue; continue;
} }
if (!strcmp(arg, "-r")) { if (!strcmp(arg, "-r")) {
remote_only = 1; kinds = REF_REMOTE_BRANCH;
continue;
}
if (!strcmp(arg, "-a")) {
kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH;
continue; continue;
} }
if (!strcmp(arg, "-l")) { if (!strcmp(arg, "-l")) {
reflog = 1; reflog = 1;
continue; continue;
} }
if (!strncmp(arg, "--abbrev=", 9)) {
abbrev = atoi(arg+9);
continue;
}
if (!strcmp(arg, "-v")) {
verbose = 1;
continue;
}
usage(builtin_branch_usage); usage(builtin_branch_usage);
} }
@ -209,7 +310,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (delete) if (delete)
delete_branches(argc - i, argv + i, force_delete); delete_branches(argc - i, argv + i, force_delete);
else if (i == argc) else if (i == argc)
print_ref_list(remote_only); print_ref_list(kinds, verbose, abbrev);
else if (i == argc - 1) else if (i == argc - 1)
create_branch(argv[i], head, force_create, reflog); create_branch(argv[i], head, force_create, reflog);
else if (i == argc - 2) else if (i == argc - 2)

View File

@ -13,6 +13,8 @@
#include <time.h> #include <time.h>
#include <sys/time.h> #include <sys/time.h>
static int default_show_root = 1;
/* this is in builtin-diff.c */ /* this is in builtin-diff.c */
void add_head(struct rev_info *revs); void add_head(struct rev_info *revs);
@ -22,6 +24,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
rev->abbrev = DEFAULT_ABBREV; rev->abbrev = DEFAULT_ABBREV;
rev->commit_format = CMIT_FMT_DEFAULT; rev->commit_format = CMIT_FMT_DEFAULT;
rev->verbose_header = 1; rev->verbose_header = 1;
rev->show_root_diff = default_show_root;
argc = setup_revisions(argc, argv, rev, "HEAD"); argc = setup_revisions(argc, argv, rev, "HEAD");
if (rev->diffopt.pickaxe || rev->diffopt.filter) if (rev->diffopt.pickaxe || rev->diffopt.filter)
rev->always_show_header = 0; rev->always_show_header = 0;
@ -44,11 +47,20 @@ static int cmd_log_walk(struct rev_info *rev)
return 0; return 0;
} }
static int git_log_config(const char *var, const char *value)
{
if (!strcmp(var, "log.showroot")) {
default_show_root = git_config_bool(var, value);
return 0;
}
return git_diff_ui_config(var, value);
}
int cmd_whatchanged(int argc, const char **argv, const char *prefix) int cmd_whatchanged(int argc, const char **argv, const char *prefix)
{ {
struct rev_info rev; struct rev_info rev;
git_config(git_diff_ui_config); git_config(git_log_config);
init_revisions(&rev, prefix); init_revisions(&rev, prefix);
rev.diff = 1; rev.diff = 1;
rev.diffopt.recursive = 1; rev.diffopt.recursive = 1;
@ -63,7 +75,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
{ {
struct rev_info rev; struct rev_info rev;
git_config(git_diff_ui_config); git_config(git_log_config);
init_revisions(&rev, prefix); init_revisions(&rev, prefix);
rev.diff = 1; rev.diff = 1;
rev.diffopt.recursive = 1; rev.diffopt.recursive = 1;
@ -80,7 +92,7 @@ int cmd_log(int argc, const char **argv, const char *prefix)
{ {
struct rev_info rev; struct rev_info rev;
git_config(git_diff_ui_config); git_config(git_log_config);
init_revisions(&rev, prefix); init_revisions(&rev, prefix);
rev.always_show_header = 1; rev.always_show_header = 1;
cmd_log_init(argc, argv, prefix, &rev); cmd_log_init(argc, argv, prefix, &rev);
@ -109,7 +121,7 @@ static int git_format_config(const char *var, const char *value)
if (!strcmp(var, "diff.color")) { if (!strcmp(var, "diff.color")) {
return 0; return 0;
} }
return git_diff_ui_config(var, value); return git_log_config(var, value);
} }

View File

@ -1176,7 +1176,9 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
* on an earlier try, but only when reusing delta data. * on an earlier try, but only when reusing delta data.
*/ */
if (!no_reuse_delta && trg_entry->in_pack && if (!no_reuse_delta && trg_entry->in_pack &&
trg_entry->in_pack == src_entry->in_pack) trg_entry->in_pack == src_entry->in_pack &&
trg_entry->in_pack_type != OBJ_REF_DELTA &&
trg_entry->in_pack_type != OBJ_OFS_DELTA)
return 0; return 0;
/* /*

View File

@ -16,8 +16,15 @@ static struct rev_info revs;
static int prune_object(char *path, const char *filename, const unsigned char *sha1) static int prune_object(char *path, const char *filename, const unsigned char *sha1)
{ {
char buf[20];
const char *type;
if (show_only) { if (show_only) {
printf("would prune %s/%s\n", path, filename); if (sha1_object_info(sha1, buf, NULL))
type = "unknown";
else
type = buf;
printf("%s %s\n", sha1_to_hex(sha1), type);
return 0; return 0;
} }
unlink(mkpath("%s/%s", path, filename)); unlink(mkpath("%s/%s", path, filename));

View File

@ -174,22 +174,59 @@ static int count_refspec_match(const char *pattern,
struct ref *refs, struct ref *refs,
struct ref **matched_ref) struct ref **matched_ref)
{ {
int match;
int patlen = strlen(pattern); int patlen = strlen(pattern);
struct ref *matched_weak = NULL;
struct ref *matched = NULL;
int weak_match = 0;
int match = 0;
for (match = 0; refs; refs = refs->next) { for (weak_match = match = 0; refs; refs = refs->next) {
char *name = refs->name; char *name = refs->name;
int namelen = strlen(name); int namelen = strlen(name);
int weak_match;
if (namelen < patlen || if (namelen < patlen ||
memcmp(name + namelen - patlen, pattern, patlen)) memcmp(name + namelen - patlen, pattern, patlen))
continue; continue;
if (namelen != patlen && name[namelen - patlen - 1] != '/') if (namelen != patlen && name[namelen - patlen - 1] != '/')
continue; continue;
match++;
*matched_ref = refs; /* A match is "weak" if it is with refs outside
* heads or tags, and did not specify the pattern
* in full (e.g. "refs/remotes/origin/master") or at
* least from the toplevel (e.g. "remotes/origin/master");
* otherwise "git push $URL master" would result in
* ambiguity between remotes/origin/master and heads/master
* at the remote site.
*/
if (namelen != patlen &&
patlen != namelen - 5 &&
strncmp(name, "refs/heads/", 11) &&
strncmp(name, "refs/tags/", 10)) {
/* We want to catch the case where only weak
* matches are found and there are multiple
* matches, and where more than one strong
* matches are found, as ambiguous. One
* strong match with zero or more weak matches
* are acceptable as a unique match.
*/
matched_weak = refs;
weak_match++;
} }
else {
matched = refs;
match++;
}
}
if (!matched) {
*matched_ref = matched_weak;
return weak_match;
}
else {
*matched_ref = matched;
return match; return match;
} }
}
static void link_dst_tail(struct ref *ref, struct ref ***tail) static void link_dst_tail(struct ref *ref, struct ref ***tail)
{ {

View File

@ -14,7 +14,7 @@ die() {
} }
usage() { usage() {
die "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]" die "Usage: $0 [--template=<template_directory>] [--use-immingled-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
} }
get_repo_base() { get_repo_base() {
@ -48,6 +48,10 @@ Perhaps git-update-server-info needs to be run there?"
case "$name" in case "$name" in
*^*) continue;; *^*) continue;;
esac esac
case "$bare,$name" in
yes,* | ,heads/* | ,tags/*) ;;
*) continue ;;
esac
if test -n "$use_separate_remote" && if test -n "$use_separate_remote" &&
branch_name=`expr "z$name" : 'zheads/\(.*\)'` branch_name=`expr "z$name" : 'zheads/\(.*\)'`
then then
@ -115,7 +119,7 @@ bare=
reference= reference=
origin= origin=
origin_override= origin_override=
use_separate_remote= use_separate_remote=t
while while
case "$#,$1" in case "$#,$1" in
0,*) break ;; 0,*) break ;;
@ -134,7 +138,10 @@ while
template="$1" ;; template="$1" ;;
*,-q|*,--quiet) quiet=-q ;; *,-q|*,--quiet) quiet=-q ;;
*,--use-separate-remote) *,--use-separate-remote)
# default
use_separate_remote=t ;; use_separate_remote=t ;;
*,--use-immingled-remote)
use_separate_remote= ;;
1,--reference) usage ;; 1,--reference) usage ;;
*,--reference) *,--reference)
shift; reference="$1" ;; shift; reference="$1" ;;
@ -169,18 +176,15 @@ repo="$1"
test -n "$repo" || test -n "$repo" ||
die 'you must specify a repository to clone.' die 'you must specify a repository to clone.'
# --bare implies --no-checkout # --bare implies --no-checkout and --use-immingled-remote
if test yes = "$bare" if test yes = "$bare"
then then
if test yes = "$origin_override" if test yes = "$origin_override"
then then
die '--bare and --origin $origin options are incompatible.' die '--bare and --origin $origin options are incompatible.'
fi fi
if test t = "$use_separate_remote"
then
die '--bare and --use-separate-remote options are incompatible.'
fi
no_checkout=yes no_checkout=yes
use_separate_remote=
fi fi
if test -z "$origin" if test -z "$origin"

View File

@ -161,8 +161,22 @@ sub new {
sub conn { sub conn {
my $self = shift; my $self = shift;
my $repo = $self->{'fullrep'}; my $repo = $self->{'fullrep'};
if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) { if($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
my($user,$pass,$serv,$port) = ($1,$2,$3,$4); my($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
my($proxyhost,$proxyport);
if($param && ($param =~ m/proxy=([^;]+)/)) {
$proxyhost = $1;
# Default proxyport, if not specified, is 8080.
$proxyport = 8080;
if($ENV{"CVS_PROXY_PORT"}) {
$proxyport = $ENV{"CVS_PROXY_PORT"};
}
if($param =~ m/proxyport=([^;]+)/){
$proxyport = $1;
}
}
$user="anonymous" unless defined $user; $user="anonymous" unless defined $user;
my $rr2 = "-"; my $rr2 = "-";
unless($port) { unless($port) {
@ -187,13 +201,43 @@ sub conn {
} }
$pass="A" unless $pass; $pass="A" unless $pass;
my $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port); my ($s, $rep);
if($proxyhost) {
# Use a HTTP Proxy. Only works for HTTP proxies that
# don't require user authentication
#
# See: http://www.ietf.org/rfc/rfc2817.txt
$s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
die "Socket to $proxyhost: $!\n" unless defined $s;
$s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
or die "Write to $proxyhost: $!\n";
$s->flush();
$rep = <$s>;
# The answer should look like 'HTTP/1.x 2yy ....'
if(!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
die "Proxy connect: $rep\n";
}
# Skip up to the empty line of the proxy server output
# including the response headers.
while ($rep = <$s>) {
last if (!defined $rep ||
$rep eq "\n" ||
$rep eq "\r\n");
}
} else {
$s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
die "Socket to $serv: $!\n" unless defined $s; die "Socket to $serv: $!\n" unless defined $s;
}
$s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n") $s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
or die "Write to $serv: $!\n"; or die "Write to $serv: $!\n";
$s->flush(); $s->flush();
my $rep = <$s>; $rep = <$s>;
if($rep ne "I LOVE YOU\n") { if($rep ne "I LOVE YOU\n") {
$rep="<unknown>" unless $rep; $rep="<unknown>" unless $rep;

View File

@ -360,7 +360,7 @@ fetch_main () {
esac esac
append_fetch_head "$head" "$remote" \ append_fetch_head "$head" "$remote" \
"$remote_name" "$remote_nick" "$local_name" "$not_for_merge" "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit
done done
@ -414,15 +414,16 @@ fetch_main () {
done done
local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') local_name=$(expr "z$found" : 'z[^:]*:\(.*\)')
append_fetch_head "$sha1" "$remote" \ append_fetch_head "$sha1" "$remote" \
"$remote_name" "$remote_nick" "$local_name" "$not_for_merge" "$remote_name" "$remote_nick" "$local_name" \
done "$not_for_merge" || exit
done &&
if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi
) || exit ;; ) || exit ;;
esac esac
} }
fetch_main "$reflist" fetch_main "$reflist" || exit
# automated tag following # automated tag following
case "$no_tags$tags" in case "$no_tags$tags" in
@ -450,7 +451,7 @@ case "$no_tags$tags" in
case "$taglist" in case "$taglist" in
'') ;; '') ;;
?*) ?*)
fetch_main "$taglist" ;; fetch_main "$taglist" || exit ;;
esac esac
esac esac

View File

@ -21,6 +21,7 @@ $ENV{TZ} = 'UTC';
$ENV{LC_ALL} = 'C'; $ENV{LC_ALL} = 'C';
$| = 1; # unbuffer STDOUT $| = 1; # unbuffer STDOUT
sub fatal (@) { print STDERR $@; exit 1 }
# If SVN:: library support is added, please make the dependencies # If SVN:: library support is added, please make the dependencies
# optional and preserve the capability to use the command-line client. # optional and preserve the capability to use the command-line client.
# use eval { require SVN::... } to make it lazy load # use eval { require SVN::... } to make it lazy load
@ -39,7 +40,7 @@ memoize('revisions_eq');
memoize('cmt_metadata'); memoize('cmt_metadata');
memoize('get_commit_time'); memoize('get_commit_time');
my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib); my ($SVN, $_use_lib);
sub nag_lib { sub nag_lib {
print STDERR <<EOF; print STDERR <<EOF;
@ -66,7 +67,8 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
$_template, $_shared, $_no_default_regex, $_no_graft_copy, $_template, $_shared, $_no_default_regex, $_no_graft_copy,
$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
$_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive); $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
$_username, $_config_dir, $_no_auth_cache);
my (@_branch_from, %tree_map, %users, %rusers, %equiv); my (@_branch_from, %tree_map, %users, %rusers, %equiv);
my ($_svn_co_url_revs, $_svn_pg_peg_revs); my ($_svn_co_url_revs, $_svn_pg_peg_revs);
my @repo_path_split_cache; my @repo_path_split_cache;
@ -79,6 +81,9 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
'repack:i' => \$_repack, 'repack:i' => \$_repack,
'no-metadata' => \$_no_metadata, 'no-metadata' => \$_no_metadata,
'quiet|q' => \$_q, 'quiet|q' => \$_q,
'username=s' => \$_username,
'config-dir=s' => \$_config_dir,
'no-auth-cache' => \$_no_auth_cache,
'ignore-nodate' => \$_ignore_nodate, 'ignore-nodate' => \$_ignore_nodate,
'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
@ -377,10 +382,7 @@ sub fetch_cmd {
sub fetch_lib { sub fetch_lib {
my (@parents) = @_; my (@parents) = @_;
$SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
my $repo; $SVN ||= libsvn_connect($SVN_URL);
($repo, $SVN_PATH) = repo_path_split($SVN_URL);
$SVN_LOG ||= libsvn_connect($repo);
$SVN ||= libsvn_connect($repo);
my ($last_rev, $last_commit) = svn_grab_base_rev(); my ($last_rev, $last_commit) = svn_grab_base_rev();
my ($base, $head) = libsvn_parse_revision($last_rev); my ($base, $head) = libsvn_parse_revision($last_rev);
if ($base > $head) { if ($base > $head) {
@ -422,7 +424,7 @@ sub fetch_lib {
# performance sucks with it enabled, so it's much # performance sucks with it enabled, so it's much
# faster to fetch revision ranges instead of relying # faster to fetch revision ranges instead of relying
# on the limiter. # on the limiter.
libsvn_get_log($SVN_LOG, '/'.$SVN_PATH, libsvn_get_log(libsvn_dup_ra($SVN), [''],
$min, $max, 0, 1, 1, $min, $max, 0, 1, 1,
sub { sub {
my $log_msg; my $log_msg;
@ -524,7 +526,6 @@ sub commit_lib {
my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
my $repo; my $repo;
($repo, $SVN_PATH) = repo_path_split($SVN_URL);
set_svn_commit_env(); set_svn_commit_env();
foreach my $c (@revs) { foreach my $c (@revs) {
my $log_msg = get_commit_message($c, $commit_msg); my $log_msg = get_commit_message($c, $commit_msg);
@ -533,13 +534,11 @@ sub commit_lib {
# can't track down... (it's probably in the SVN code) # can't track down... (it's probably in the SVN code)
defined(my $pid = open my $fh, '-|') or croak $!; defined(my $pid = open my $fh, '-|') or croak $!;
if (!$pid) { if (!$pid) {
$SVN_LOG = libsvn_connect($repo);
$SVN = libsvn_connect($repo);
my $ed = SVN::Git::Editor->new( my $ed = SVN::Git::Editor->new(
{ r => $r_last, { r => $r_last,
ra => $SVN_LOG, ra => libsvn_dup_ra($SVN),
c => $c, c => $c,
svn_path => $SVN_PATH svn_path => $SVN->{svn_path},
}, },
$SVN->get_commit_editor( $SVN->get_commit_editor(
$log_msg->{msg}, $log_msg->{msg},
@ -571,7 +570,7 @@ sub commit_lib {
$no = 1; $no = 1;
} }
} }
close $fh or croak $?; close $fh or exit 1;
if (! defined $r_new && ! defined $cmt_new) { if (! defined $r_new && ! defined $cmt_new) {
unless ($no) { unless ($no) {
die "Failed to parse revision information\n"; die "Failed to parse revision information\n";
@ -657,10 +656,9 @@ sub show_ignore_cmd {
sub show_ignore_lib { sub show_ignore_lib {
my $repo; my $repo;
($repo, $SVN_PATH) = repo_path_split($SVN_URL); $SVN ||= libsvn_connect($SVN_URL);
$SVN ||= libsvn_connect($repo);
my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum; my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r); libsvn_traverse_ignore(\*STDOUT, $SVN->{svn_path}, $r);
} }
sub graft_branches { sub graft_branches {
@ -786,7 +784,7 @@ sub show_log {
} elsif (/^:\d{6} \d{6} $sha1_short/o) { } elsif (/^:\d{6} \d{6} $sha1_short/o) {
push @{$c->{raw}}, $_; push @{$c->{raw}}, $_;
} elsif (/^[ACRMDT]\t/) { } elsif (/^[ACRMDT]\t/) {
# we could add $SVN_PATH here, but that requires # we could add $SVN->{svn_path} here, but that requires
# remote access at the moment (repo_path_split)... # remote access at the moment (repo_path_split)...
s#^([ACRMDT])\t# $1 #; s#^([ACRMDT])\t# $1 #;
push @{$c->{changed}}, $_; push @{$c->{changed}}, $_;
@ -852,10 +850,7 @@ sub commit_diff {
$_message ||= get_commit_message($tb, $_message ||= get_commit_message($tb,
"$GIT_DIR/.svn-commit.tmp.$$")->{msg}; "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
} }
my $repo; $SVN ||= libsvn_connect($SVN_URL);
($repo, $SVN_PATH) = repo_path_split($SVN_URL);
$SVN_LOG ||= libsvn_connect($repo);
$SVN ||= libsvn_connect($repo);
if ($r eq 'HEAD') { if ($r eq 'HEAD') {
$r = $SVN->get_latest_revnum; $r = $SVN->get_latest_revnum;
} elsif ($r !~ /^\d+$/) { } elsif ($r !~ /^\d+$/) {
@ -864,8 +859,9 @@ sub commit_diff {
my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
my $rev_committed; my $rev_committed;
my $ed = SVN::Git::Editor->new({ r => $r, my $ed = SVN::Git::Editor->new({ r => $r,
ra => $SVN_LOG, c => $tb, ra => libsvn_dup_ra($SVN),
svn_path => $SVN_PATH c => $tb,
svn_path => $SVN->{svn_path}
}, },
$SVN->get_commit_editor($_message, $SVN->get_commit_editor($_message,
sub { sub {
@ -873,6 +869,7 @@ sub commit_diff {
print "Committed $_[0]\n"; print "Committed $_[0]\n";
}, @lock) }, @lock)
); );
eval {
my $mods = libsvn_checkout_tree($ta, $tb, $ed); my $mods = libsvn_checkout_tree($ta, $tb, $ed);
if (@$mods == 0) { if (@$mods == 0) {
print "No changes\n$ta == $tb\n"; print "No changes\n$ta == $tb\n";
@ -880,6 +877,8 @@ sub commit_diff {
} else { } else {
$ed->close_edit; $ed->close_edit;
} }
};
fatal "$@\n" if $@;
$_message = $_file = undef; $_message = $_file = undef;
return $rev_committed; return $rev_committed;
} }
@ -1143,8 +1142,7 @@ sub graft_file_copy_lib {
my $tree_paths = $l_map->{$u}; my $tree_paths = $l_map->{$u};
my $pfx = common_prefix([keys %$tree_paths]); my $pfx = common_prefix([keys %$tree_paths]);
my ($repo, $path) = repo_path_split($u.$pfx); my ($repo, $path) = repo_path_split($u.$pfx);
$SVN_LOG ||= libsvn_connect($repo); $SVN = libsvn_connect($repo);
$SVN ||= libsvn_connect($repo);
my ($base, $head) = libsvn_parse_revision(); my ($base, $head) = libsvn_parse_revision();
my $inc = 1000; my $inc = 1000;
@ -1153,7 +1151,8 @@ sub graft_file_copy_lib {
$SVN::Error::handler = \&libsvn_skip_unknown_revs; $SVN::Error::handler = \&libsvn_skip_unknown_revs;
while (1) { while (1) {
my $pool = SVN::Pool->new; my $pool = SVN::Pool->new;
libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1, libsvn_get_log(libsvn_dup_ra($SVN), [$path],
$min, $max, 0, 1, 1,
sub { sub {
libsvn_graft_file_copies($grafts, $tree_paths, libsvn_graft_file_copies($grafts, $tree_paths,
$path, @_); $path, @_);
@ -1263,13 +1262,9 @@ sub repo_path_split {
return ($u, $full_url); return ($u, $full_url);
} }
} }
if ($_use_lib) { if ($_use_lib) {
my $tmp = libsvn_connect($full_url); my $tmp = libsvn_connect($full_url);
my $url = $tmp->get_repos_root; return ($tmp->{repos_root}, $tmp->{svn_path});
$full_url =~ s#^\Q$url\E/*##;
push @repo_path_split_cache, qr/^(\Q$url\E)/;
return ($url, $full_url);
} else { } else {
my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
$path =~ s#^/+##; $path =~ s#^/+##;
@ -2683,26 +2678,169 @@ sub libsvn_load {
my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
$SVN::Node::dir.$SVN::Node::unknown. $SVN::Node::dir.$SVN::Node::unknown.
$SVN::Node::none.$SVN::Node::file. $SVN::Node::none.$SVN::Node::file.
$SVN::Node::dir.$SVN::Node::unknown; $SVN::Node::dir.$SVN::Node::unknown.
$SVN::Auth::SSL::CNMISMATCH.
$SVN::Auth::SSL::NOTYETVALID.
$SVN::Auth::SSL::EXPIRED.
$SVN::Auth::SSL::UNKNOWNCA.
$SVN::Auth::SSL::OTHER;
1; 1;
}; };
} }
sub _simple_prompt {
my ($cred, $realm, $default_username, $may_save, $pool) = @_;
$may_save = undef if $_no_auth_cache;
$default_username = $_username if defined $_username;
if (defined $default_username && length $default_username) {
if (defined $realm && length $realm) {
print "Authentication realm: $realm\n";
}
$cred->username($default_username);
} else {
_username_prompt($cred, $realm, $may_save, $pool);
}
$cred->password(_read_password("Password for '" .
$cred->username . "': ", $realm));
$cred->may_save($may_save);
$SVN::_Core::SVN_NO_ERROR;
}
sub _ssl_server_trust_prompt {
my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
$may_save = undef if $_no_auth_cache;
print "Error validating server certificate for '$realm':\n";
if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
print " - The certificate is not issued by a trusted ",
"authority. Use the\n",
" fingerprint to validate the certificate manually!\n";
}
if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
print " - The certificate hostname does not match.\n";
}
if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
print " - The certificate is not yet valid.\n";
}
if ($failures & $SVN::Auth::SSL::EXPIRED) {
print " - The certificate has expired.\n";
}
if ($failures & $SVN::Auth::SSL::OTHER) {
print " - The certificate has an unknown error.\n";
}
printf( "Certificate information:\n".
" - Hostname: %s\n".
" - Valid: from %s until %s\n".
" - Issuer: %s\n".
" - Fingerprint: %s\n",
map $cert_info->$_, qw(hostname valid_from valid_until
issuer_dname fingerprint) );
my $choice;
prompt:
print $may_save ?
"(R)eject, accept (t)emporarily or accept (p)ermanently? " :
"(R)eject or accept (t)emporarily? ";
$choice = lc(substr(<STDIN> || 'R', 0, 1));
if ($choice =~ /^t$/i) {
$cred->may_save(undef);
} elsif ($choice =~ /^r$/i) {
return -1;
} elsif ($may_save && $choice =~ /^p$/i) {
$cred->may_save($may_save);
} else {
goto prompt;
}
$cred->accepted_failures($failures);
$SVN::_Core::SVN_NO_ERROR;
}
sub _ssl_client_cert_prompt {
my ($cred, $realm, $may_save, $pool) = @_;
$may_save = undef if $_no_auth_cache;
print "Client certificate filename: ";
chomp(my $filename = <STDIN>);
$cred->cert_file($filename);
$cred->may_save($may_save);
$SVN::_Core::SVN_NO_ERROR;
}
sub _ssl_client_cert_pw_prompt {
my ($cred, $realm, $may_save, $pool) = @_;
$may_save = undef if $_no_auth_cache;
$cred->password(_read_password("Password: ", $realm));
$cred->may_save($may_save);
$SVN::_Core::SVN_NO_ERROR;
}
sub _username_prompt {
my ($cred, $realm, $may_save, $pool) = @_;
$may_save = undef if $_no_auth_cache;
if (defined $realm && length $realm) {
print "Authentication realm: $realm\n";
}
my $username;
if (defined $_username) {
$username = $_username;
} else {
print "Username: ";
chomp($username = <STDIN>);
}
$cred->username($username);
$cred->may_save($may_save);
$SVN::_Core::SVN_NO_ERROR;
}
sub _read_password {
my ($prompt, $realm) = @_;
print $prompt;
require Term::ReadKey;
Term::ReadKey::ReadMode('noecho');
my $password = '';
while (defined(my $key = Term::ReadKey::ReadKey(0))) {
last if $key =~ /[\012\015]/; # \n\r
$password .= $key;
}
Term::ReadKey::ReadMode('restore');
print "\n";
$password;
}
sub libsvn_connect { sub libsvn_connect {
my ($url) = @_; my ($url) = @_;
my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(), SVN::_Core::svn_config_ensure($_config_dir, undef);
my ($baton, $callbacks) = SVN::Core::auth_open_helper([
SVN::Client::get_simple_provider(),
SVN::Client::get_ssl_server_trust_file_provider(), SVN::Client::get_ssl_server_trust_file_provider(),
SVN::Client::get_username_provider()]); SVN::Client::get_simple_prompt_provider(
my $s = eval { SVN::Ra->new(url => $url, auth => $auth) }; \&_simple_prompt, 2),
return $s; SVN::Client::get_ssl_client_cert_prompt_provider(
\&_ssl_client_cert_prompt, 2),
SVN::Client::get_ssl_client_cert_pw_prompt_provider(
\&_ssl_client_cert_pw_prompt, 2),
SVN::Client::get_username_provider(),
SVN::Client::get_ssl_server_trust_prompt_provider(
\&_ssl_server_trust_prompt),
SVN::Client::get_username_prompt_provider(
\&_username_prompt, 2),
]);
my $ra = SVN::Ra->new(url => $url, auth => $baton,
pool => SVN::Pool->new,
auth_provider_callbacks => $callbacks);
$ra->{svn_path} = $url;
$ra->{repos_root} = $ra->get_repos_root;
$ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##;
push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/;
return $ra;
}
sub libsvn_dup_ra {
my ($ra) = @_;
SVN::Ra->new(map { $_ => $ra->{$_} }
qw/url auth auth_provider_callbacks repos_root svn_path/);
} }
sub libsvn_get_file { sub libsvn_get_file {
my ($gui, $f, $rev, $chg) = @_; my ($gui, $f, $rev, $chg) = @_;
my $p = $f; $f =~ s#^/##;
if (length $SVN_PATH > 0) {
return unless ($p =~ s#^\Q$SVN_PATH\E/##);
}
print "\t$chg\t$f\n" unless $_q; print "\t$chg\t$f\n" unless $_q;
my ($hash, $pid, $in, $out); my ($hash, $pid, $in, $out);
@ -2739,7 +2877,7 @@ sub libsvn_get_file {
waitpid $pid, 0; waitpid $pid, 0;
$hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
} }
print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!; print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!;
} }
sub libsvn_log_entry { sub libsvn_log_entry {
@ -2757,7 +2895,6 @@ sub libsvn_log_entry {
sub process_rm { sub process_rm {
my ($gui, $last_commit, $f) = @_; my ($gui, $last_commit, $f) = @_;
$f =~ s#^\Q$SVN_PATH\E/?## or return;
# remove entire directories. # remove entire directories.
if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) { if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
defined(my $pid = open my $ls, '-|') or croak $!; defined(my $pid = open my $ls, '-|') or croak $!;
@ -2779,9 +2916,11 @@ sub libsvn_fetch {
my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
open my $gui, '| git-update-index -z --index-info' or croak $!; open my $gui, '| git-update-index -z --index-info' or croak $!;
my @amr; my @amr;
my $p = $SVN->{svn_path};
foreach my $f (keys %$paths) { foreach my $f (keys %$paths) {
my $m = $paths->{$f}->action(); my $m = $paths->{$f}->action();
$f =~ s#^/+##; $f =~ s#^/\Q$p\E/##;
next if $f =~ m#^/#;
if ($m =~ /^[DR]$/) { if ($m =~ /^[DR]$/) {
print "\t$m\t$f\n" unless $_q; print "\t$m\t$f\n" unless $_q;
process_rm($gui, $last_commit, $f); process_rm($gui, $last_commit, $f);
@ -2871,9 +3010,9 @@ sub libsvn_parse_revision {
sub libsvn_traverse { sub libsvn_traverse {
my ($gui, $pfx, $path, $rev, $files) = @_; my ($gui, $pfx, $path, $rev, $files) = @_;
my $cwd = "$pfx/$path"; my $cwd = length $pfx ? "$pfx/$path" : $path;
my $pool = SVN::Pool->new; my $pool = SVN::Pool->new;
$cwd =~ s#^/+##g; $cwd =~ s#^\Q$SVN->{svn_path}\E##;
my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool); my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);
foreach my $d (keys %$dirent) { foreach my $d (keys %$dirent) {
my $t = $dirent->{$d}->kind; my $t = $dirent->{$d}->kind;
@ -2897,7 +3036,7 @@ sub libsvn_traverse_ignore {
my $pool = SVN::Pool->new; my $pool = SVN::Pool->new;
my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool); my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
my $p = $path; my $p = $path;
$p =~ s#^\Q$SVN_PATH\E/?##; $p =~ s#^\Q$SVN->{svn_path}\E/##;
print $fh length $p ? "\n# $p\n" : "\n# /\n"; print $fh length $p ? "\n# $p\n" : "\n# /\n";
if (my $s = $props->{'svn:ignore'}) { if (my $s = $props->{'svn:ignore'}) {
$s =~ s/[\r\n]+/\n/g; $s =~ s/[\r\n]+/\n/g;
@ -2924,7 +3063,7 @@ sub revisions_eq {
if ($_use_lib) { if ($_use_lib) {
# should be OK to use Pool here (r1 - r0) should be small # should be OK to use Pool here (r1 - r0) should be small
my $pool = SVN::Pool->new; my $pool = SVN::Pool->new;
libsvn_get_log($SVN, "/$path", $r0, $r1, libsvn_get_log($SVN, [$path], $r0, $r1,
0, 1, 1, sub {$nr++}, $pool); 0, 1, 1, sub {$nr++}, $pool);
$pool->clear; $pool->clear;
} else { } else {
@ -2939,7 +3078,7 @@ sub revisions_eq {
sub libsvn_find_parent_branch { sub libsvn_find_parent_branch {
my ($paths, $rev, $author, $date, $msg) = @_; my ($paths, $rev, $author, $date, $msg) = @_;
my $svn_path = '/'.$SVN_PATH; my $svn_path = '/'.$SVN->{svn_path};
# look for a parent from another branch: # look for a parent from another branch:
my $i = $paths->{$svn_path} or return; my $i = $paths->{$svn_path} or return;
@ -2950,7 +3089,7 @@ sub libsvn_find_parent_branch {
$branch_from =~ s#^/##; $branch_from =~ s#^/##;
my $l_map = {}; my $l_map = {};
read_url_paths_all($l_map, '', "$GIT_DIR/svn"); read_url_paths_all($l_map, '', "$GIT_DIR/svn");
my $url = $SVN->{url}; my $url = $SVN->{repos_root};
defined $l_map->{$url} or return; defined $l_map->{$url} or return;
my $id = $l_map->{$url}->{$branch_from}; my $id = $l_map->{$url}->{$branch_from};
if (!defined $id && $_follow_parent) { if (!defined $id && $_follow_parent) {
@ -2972,7 +3111,7 @@ sub libsvn_find_parent_branch {
$GIT_SVN = $ENV{GIT_SVN_ID} = $id; $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
init_vars(); init_vars();
$SVN_URL = "$url/$branch_from"; $SVN_URL = "$url/$branch_from";
$SVN_LOG = $SVN = undef; $SVN = undef;
setup_git_svn(); setup_git_svn();
# we can't assume SVN_URL exists at r+1: # we can't assume SVN_URL exists at r+1:
$_revision = "0:$r"; $_revision = "0:$r";
@ -3009,7 +3148,7 @@ sub libsvn_new_tree {
} }
my ($paths, $rev, $author, $date, $msg) = @_; my ($paths, $rev, $author, $date, $msg) = @_;
open my $gui, '| git-update-index -z --index-info' or croak $!; open my $gui, '| git-update-index -z --index-info' or croak $!;
libsvn_traverse($gui, '', $SVN_PATH, $rev); libsvn_traverse($gui, '', $SVN->{svn_path}, $rev);
close $gui or croak $?; close $gui or croak $?;
return libsvn_log_entry($rev, $author, $date, $msg); return libsvn_log_entry($rev, $author, $date, $msg);
} }
@ -3094,11 +3233,10 @@ sub libsvn_commit_cb {
sub libsvn_ls_fullurl { sub libsvn_ls_fullurl {
my $fullurl = shift; my $fullurl = shift;
my ($repo, $path) = repo_path_split($fullurl); $SVN ||= libsvn_connect($fullurl);
$SVN ||= libsvn_connect($repo);
my @ret; my @ret;
my $pool = SVN::Pool->new; my $pool = SVN::Pool->new;
my ($dirent, undef, undef) = $SVN->get_dir($path, my ($dirent, undef, undef) = $SVN->get_dir($SVN->{svn_path},
$SVN->get_latest_revnum, $pool); $SVN->get_latest_revnum, $pool);
foreach my $d (keys %$dirent) { foreach my $d (keys %$dirent) {
if ($dirent->{$d}->kind == $SVN::Node::dir) { if ($dirent->{$d}->kind == $SVN::Node::dir) {
@ -3120,8 +3258,9 @@ sub libsvn_skip_unknown_revs {
# Wonderfully consistent library, eh? # Wonderfully consistent library, eh?
# 160013 - svn:// and file:// # 160013 - svn:// and file://
# 175002 - http(s):// # 175002 - http(s)://
# 175007 - http(s):// (this repo required authorization, too...)
# More codes may be discovered later... # More codes may be discovered later...
if ($errno == 175002 || $errno == 160013) { if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
return; return;
} }
croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
@ -3209,8 +3348,7 @@ sub split_path {
} }
sub repo_path { sub repo_path {
(defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]" (defined $_[1] && length $_[1]) ? $_[1] : ''
: $_[0]->{svn_path}
} }
sub url_path { sub url_path {
@ -3242,10 +3380,9 @@ sub rmdirs {
exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!; exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;
} }
local $/ = "\0"; local $/ = "\0";
my @svn_path = split m#/#, $self->{svn_path};
while (<$fh>) { while (<$fh>) {
chomp; chomp;
my @dn = (@svn_path, (split m#/#, $_)); my @dn = split m#/#, $_;
while (pop @dn) { while (pop @dn) {
delete $rm->{join '/', @dn}; delete $rm->{join '/', @dn};
} }

View File

@ -334,11 +334,13 @@ div.diff.extended_header {
padding: 2px 0px 2px 0px; padding: 2px 0px 2px 0px;
} }
div.diff a.list,
div.diff a.path, div.diff a.path,
div.diff a.hash { div.diff a.hash {
text-decoration: none; text-decoration: none;
} }
div.diff a.list:hover,
div.diff a.path:hover, div.diff a.path:hover,
div.diff a.hash:hover { div.diff a.hash:hover {
text-decoration: underline; text-decoration: underline;
@ -362,14 +364,25 @@ div.diff.rem {
color: #cc0000; color: #cc0000;
} }
div.diff.chunk_header a,
div.diff.chunk_header { div.diff.chunk_header {
color: #990099; color: #990099;
}
div.diff.chunk_header {
border: dotted #ffe0ff; border: dotted #ffe0ff;
border-width: 1px 0px 0px 0px; border-width: 1px 0px 0px 0px;
margin-top: 2px; margin-top: 2px;
} }
div.diff.chunk_header span.chunk_info {
background-color: #ffeeff;
}
div.diff.chunk_header span.section {
color: #aa22aa;
}
div.diff.incomplete { div.diff.incomplete {
color: #cccccc; color: #cccccc;
} }

View File

@ -425,6 +425,7 @@ my %actions = (
"history" => \&git_history, "history" => \&git_history,
"log" => \&git_log, "log" => \&git_log,
"rss" => \&git_rss, "rss" => \&git_rss,
"atom" => \&git_atom,
"search" => \&git_search, "search" => \&git_search,
"search_help" => \&git_search_help, "search_help" => \&git_search_help,
"shortlog" => \&git_shortlog, "shortlog" => \&git_shortlog,
@ -459,7 +460,8 @@ exit;
sub href(%) { sub href(%) {
my %params = @_; my %params = @_;
my $href = $my_uri; # default is to use -absolute url() i.e. $my_uri
my $href = $params{-full} ? $my_url : $my_uri;
# XXX: Warning: If you touch this, check the search form for updating, # XXX: Warning: If you touch this, check the search form for updating,
# too. # too.
@ -874,8 +876,10 @@ sub format_subject_html {
} }
} }
# format patch (diff) line (rather not to be used for diff headers)
sub format_diff_line { sub format_diff_line {
my $line = shift; my $line = shift;
my ($from, $to) = @_;
my $char = substr($line, 0, 1); my $char = substr($line, 0, 1);
my $diff_class = ""; my $diff_class = "";
@ -891,6 +895,25 @@ sub format_diff_line {
$diff_class = " incomplete"; $diff_class = " incomplete";
} }
$line = untabify($line); $line = untabify($line);
if ($from && $to && $line =~ m/^\@{2} /) {
my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
$line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
$from_lines = 0 unless defined $from_lines;
$to_lines = 0 unless defined $to_lines;
if ($from->{'href'}) {
$from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
-class=>"list"}, $from_text);
}
if ($to->{'href'}) {
$to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
-class=>"list"}, $to_text);
}
$line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
"<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
return "<div class=\"diff$diff_class\">$line</div>\n";
}
return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n"; return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
} }
@ -1180,6 +1203,8 @@ sub parse_date {
$days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
$date{'mday-time'} = sprintf "%d %s %02d:%02d", $date{'mday-time'} = sprintf "%d %s %02d:%02d",
$mday, $months[$mon], $hour ,$min; $mday, $months[$mon], $hour ,$min;
$date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
1900+$year, $mon, $mday, $hour ,$min, $sec;
$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
my $local = $epoch + ((int $1 + ($2/60)) * 3600); my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@ -1650,14 +1675,17 @@ EOF
} }
} }
if (defined $project) { if (defined $project) {
printf('<link rel="alternate" title="%s log" '. printf('<link rel="alternate" title="%s log RSS feed" '.
'href="%s" type="application/rss+xml" />'."\n", 'href="%s" type="application/rss+xml" />'."\n",
esc_param($project), href(action=>"rss")); esc_param($project), href(action=>"rss"));
printf('<link rel="alternate" title="%s log Atom feed" '.
'href="%s" type="application/atom+xml" />'."\n",
esc_param($project), href(action=>"atom"));
} else { } else {
printf('<link rel="alternate" title="%s projects list" '. printf('<link rel="alternate" title="%s projects list" '.
'href="%s" type="text/plain; charset=utf-8"/>'."\n", 'href="%s" type="text/plain; charset=utf-8"/>'."\n",
$site_name, href(project=>undef, action=>"project_index")); $site_name, href(project=>undef, action=>"project_index"));
printf('<link rel="alternate" title="%s projects logs" '. printf('<link rel="alternate" title="%s projects feeds" '.
'href="%s" type="text/x-opml"/>'."\n", 'href="%s" type="text/x-opml"/>'."\n",
$site_name, href(project=>undef, action=>"opml")); $site_name, href(project=>undef, action=>"opml"));
} }
@ -1723,7 +1751,9 @@ sub git_footer_html {
print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
} }
print $cgi->a({-href => href(action=>"rss"), print $cgi->a({-href => href(action=>"rss"),
-class => "rss_logo"}, "RSS") . "\n"; -class => "rss_logo"}, "RSS") . " ";
print $cgi->a({-href => href(action=>"atom"),
-class => "rss_logo"}, "Atom") . "\n";
} else { } else {
print $cgi->a({-href => href(project=>undef, action=>"opml"), print $cgi->a({-href => href(project=>undef, action=>"opml"),
-class => "rss_logo"}, "OPML") . " "; -class => "rss_logo"}, "OPML") . " ";
@ -2062,7 +2092,11 @@ sub git_difftree_body {
# link to patch # link to patch
$patchno++; $patchno++;
print $cgi->a({-href => "#patch$patchno"}, "patch"); print $cgi->a({-href => "#patch$patchno"}, "patch");
print " | ";
} }
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'})},
"blob") . " | ";
print "</td>\n"; print "</td>\n";
} elsif ($diff{'status'} eq "D") { # deleted } elsif ($diff{'status'} eq "D") { # deleted
@ -2084,9 +2118,7 @@ sub git_difftree_body {
hash_base=>$parent, file_name=>$diff{'file'})}, hash_base=>$parent, file_name=>$diff{'file'})},
"blob") . " | "; "blob") . " | ";
if ($have_blame) { if ($have_blame) {
print $cgi->a({-href => print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
href(action=>"blame",
hash_base=>$parent,
file_name=>$diff{'file'})}, file_name=>$diff{'file'})},
"blame") . " | "; "blame") . " | ";
} }
@ -2136,8 +2168,7 @@ sub git_difftree_body {
hash_base=>$hash, file_name=>$diff{'file'})}, hash_base=>$hash, file_name=>$diff{'file'})},
"blob") . " | "; "blob") . " | ";
if ($have_blame) { if ($have_blame) {
print $cgi->a({-href => href(action=>"blame", print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
hash_base=>$hash,
file_name=>$diff{'file'})}, file_name=>$diff{'file'})},
"blame") . " | "; "blame") . " | ";
} }
@ -2178,17 +2209,16 @@ sub git_difftree_body {
"diff") . "diff") .
" | "; " | ";
} }
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$parent, file_name=>$diff{'from_file'})}, hash_base=>$parent, file_name=>$diff{'to_file'})},
"blob") . " | "; "blob") . " | ";
if ($have_blame) { if ($have_blame) {
print $cgi->a({-href => href(action=>"blame", print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
hash_base=>$hash,
file_name=>$diff{'to_file'})}, file_name=>$diff{'to_file'})},
"blame") . " | "; "blame") . " | ";
} }
print $cgi->a({-href => href(action=>"history", hash_base=>$parent, print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
file_name=>$diff{'from_file'})}, file_name=>$diff{'to_file'})},
"history"); "history");
print "</td>\n"; print "</td>\n";
@ -2202,31 +2232,56 @@ sub git_patchset_body {
my ($fd, $difftree, $hash, $hash_parent) = @_; my ($fd, $difftree, $hash, $hash_parent) = @_;
my $patch_idx = 0; my $patch_idx = 0;
my $in_header = 0; my $patch_line;
my $patch_found = 0;
my $diffinfo; my $diffinfo;
my (%from, %to); my (%from, %to);
my ($from_id, $to_id);
print "<div class=\"patchset\">\n"; print "<div class=\"patchset\">\n";
LINE: # skip to first patch
while (my $patch_line = <$fd>) { while ($patch_line = <$fd>) {
chomp $patch_line; chomp $patch_line;
if ($patch_line =~ m/^diff /) { # "git diff" header last if ($patch_line =~ m/^diff /);
# beginning of patch (in patchset)
if ($patch_found) {
# close extended header for previous empty patch
if ($in_header) {
print "</div>\n" # class="diff extended_header"
} }
# close previous patch
print "</div>\n"; # class="patch" PATCH:
while ($patch_line) {
my @diff_header;
# git diff header
#assert($patch_line =~ m/^diff /) if DEBUG;
#assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
push @diff_header, $patch_line;
# extended diff header
EXTENDED_HEADER:
while ($patch_line = <$fd>) {
chomp $patch_line;
last EXTENDED_HEADER if ($patch_line =~ m/^--- /);
if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
$from_id = $1;
$to_id = $2;
}
push @diff_header, $patch_line;
}
#last PATCH unless $patch_line;
my $last_patch_line = $patch_line;
# check if current patch belong to current raw line
# and parse raw git-diff line if needed
if (defined $diffinfo &&
$diffinfo->{'from_id'} eq $from_id &&
$diffinfo->{'to_id'} eq $to_id) {
# this is split patch
print "<div class=\"patch cont\">\n";
} else { } else {
# first patch in patchset # advance raw git-diff output if needed
$patch_found = 1; $patch_idx++ if defined $diffinfo;
}
print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
# read and prepare patch information # read and prepare patch information
if (ref($difftree->[$patch_idx]) eq "HASH") { if (ref($difftree->[$patch_idx]) eq "HASH") {
@ -2247,9 +2302,13 @@ sub git_patchset_body {
hash=>$diffinfo->{'to_id'}, hash=>$diffinfo->{'to_id'},
file_name=>$to{'file'}); file_name=>$to{'file'});
} }
$patch_idx++; # this is first patch for raw difftree line with $patch_idx index
# we index @$difftree array from 0, but number patches from 1
print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
}
# print "git diff" header # print "git diff" header
$patch_line = shift @diff_header;
$patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
if ($from{'href'}) { if ($from{'href'}) {
$patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
@ -2264,15 +2323,12 @@ sub git_patchset_body {
} else { # file was deleted } else { # file was deleted
$patch_line .= 'b/' . esc_path($to{'file'}); $patch_line .= 'b/' . esc_path($to{'file'});
} }
print "<div class=\"diff header\">$patch_line</div>\n"; print "<div class=\"diff header\">$patch_line</div>\n";
print "<div class=\"diff extended_header\">\n";
$in_header = 1;
next LINE;
}
if ($in_header) { # print extended diff header
if ($patch_line !~ m/^---/) { print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
EXTENDED_HEADER:
foreach $patch_line (@diff_header) {
# match <path> # match <path>
if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
$patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
@ -2303,16 +2359,23 @@ sub git_patchset_body {
} else { } else {
$to_link = '0' x 7; $to_link = '0' x 7;
} }
#affirm {
# my ($from_hash, $to_hash) =
# ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/);
# my ($from_id, $to_id) =
# ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
# ($from_hash eq $from_id) && ($to_hash eq $to_id);
#} if DEBUG;
my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'}); my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
$patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!; $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
} }
print $patch_line . "<br/>\n"; print $patch_line . "<br/>\n";
}
print "</div>\n" if (@diff_header > 0); # class="diff extended_header"
} else { # from-file/to-file diff header
#$in_header && $patch_line =~ m/^---/; $patch_line = $last_patch_line;
print "</div>\n"; # class="diff extended_header" #assert($patch_line =~ m/^---/) if DEBUG;
$in_header = 0;
if ($from{'href'}) { if ($from{'href'}) {
$patch_line = '--- a/' . $patch_line = '--- a/' .
$cgi->a({-href=>$from{'href'}, -class=>"path"}, $cgi->a({-href=>$from{'href'}, -class=>"path"},
@ -2321,9 +2384,10 @@ sub git_patchset_body {
print "<div class=\"diff from_file\">$patch_line</div>\n"; print "<div class=\"diff from_file\">$patch_line</div>\n";
$patch_line = <$fd>; $patch_line = <$fd>;
#last PATCH unless $patch_line;
chomp $patch_line; chomp $patch_line;
#$patch_line =~ m/^+++/; #assert($patch_line =~ m/^+++/) if DEBUG;
if ($to{'href'}) { if ($to{'href'}) {
$patch_line = '+++ b/' . $patch_line = '+++ b/' .
$cgi->a({-href=>$to{'href'}, -class=>"path"}, $cgi->a({-href=>$to{'href'}, -class=>"path"},
@ -2331,17 +2395,20 @@ sub git_patchset_body {
} }
print "<div class=\"diff to_file\">$patch_line</div>\n"; print "<div class=\"diff to_file\">$patch_line</div>\n";
# the patch itself
LINE:
while ($patch_line = <$fd>) {
chomp $patch_line;
next PATCH if ($patch_line =~ m/^diff /);
print format_diff_line($patch_line, \%from, \%to);
} }
next LINE; } continue {
print "</div>\n"; # class="patch"
} }
print format_diff_line($patch_line);
}
print "</div>\n" if $in_header; # extended header
print "</div>\n" if $patch_found; # class="patch"
print "</div>\n"; # class="patchset" print "</div>\n"; # class="patchset"
} }
@ -2851,8 +2918,8 @@ sub git_tag {
print "<div class=\"page_body\">"; print "<div class=\"page_body\">";
my $comment = $tag{'comment'}; my $comment = $tag{'comment'};
foreach my $line (@$comment) { foreach my $line (@$comment) {
chomp($line); chomp $line;
print esc_html($line) . "<br/>\n"; print esc_html($line, -nbsp=>1) . "<br/>\n";
} }
print "</div>\n"; print "</div>\n";
git_footer_html(); git_footer_html();
@ -2921,7 +2988,7 @@ HTML
} }
} }
my $data = $_; my $data = $_;
chomp($data); chomp $data;
my $rev = substr($full_rev, 0, 8); my $rev = substr($full_rev, 0, 8);
my $author = $meta->{'author'}; my $author = $meta->{'author'};
my %date = parse_date($meta->{'author-time'}, my %date = parse_date($meta->{'author-time'},
@ -3392,6 +3459,7 @@ sub git_log {
} }
sub git_commit { sub git_commit {
$hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash); my %co = parse_commit($hash);
if (!%co) { if (!%co) {
die_error(undef, "Unknown commit object"); die_error(undef, "Unknown commit object");
@ -3669,6 +3737,7 @@ sub git_blobdiff_plain {
sub git_commitdiff { sub git_commitdiff {
my $format = shift || 'html'; my $format = shift || 'html';
$hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash); my %co = parse_commit($hash);
if (!%co) { if (!%co) {
die_error(undef, "Unknown commit object"); die_error(undef, "Unknown commit object");
@ -3731,7 +3800,8 @@ sub git_commitdiff {
$hash_parent, $hash, "--" $hash_parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed"); or die_error(undef, "Open git-diff-tree failed");
while (chomp(my $line = <$fd>)) { while (my $line = <$fd>) {
chomp $line;
# empty line ends raw part of diff-tree output # empty line ends raw part of diff-tree output
last unless $line; last unless $line;
push @difftree, $line; push @difftree, $line;
@ -4088,26 +4158,125 @@ sub git_shortlog {
} }
## ...................................................................... ## ......................................................................
## feeds (RSS, OPML) ## feeds (RSS, Atom; OPML)
sub git_rss { sub git_feed {
# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ my $format = shift || 'atom';
my ($have_blame) = gitweb_check_feature('blame');
# Atom: http://www.atomenabled.org/developers/syndication/
# RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
if ($format ne 'rss' && $format ne 'atom') {
die_error(undef, "Unknown web feed format");
}
# log/feed of current (HEAD) branch, log of given branch, history of file/directory
my $head = $hash || 'HEAD';
open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
git_get_head_hash($project), "--" $head, "--", (defined $file_name ? $file_name : ())
or die_error(undef, "Open git-rev-list failed"); or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>; my @revlist = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-rev-list failed"); close $fd or die_error(undef, "Reading git-rev-list failed");
print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
my %latest_commit;
my %latest_date;
my $content_type = "application/$format+xml";
if (defined $cgi->http('HTTP_ACCEPT') &&
$cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
# browser (feed reader) prefers text/xml
$content_type = 'text/xml';
}
if (defined($revlist[0])) {
%latest_commit = parse_commit($revlist[0]);
%latest_date = parse_date($latest_commit{'committer_epoch'});
print $cgi->header(
-type => $content_type,
-charset => 'utf-8',
-last_modified => $latest_date{'rfc2822'});
} else {
print $cgi->header(
-type => $content_type,
-charset => 'utf-8');
}
# Optimization: skip generating the body if client asks only
# for Last-Modified date.
return if ($cgi->request_method() eq 'HEAD');
# header variables
my $title = "$site_name - $project/$action";
my $feed_type = 'log';
if (defined $hash) {
$title .= " - '$hash'";
$feed_type = 'branch log';
if (defined $file_name) {
$title .= " :: $file_name";
$feed_type = 'history';
}
} elsif (defined $file_name) {
$title .= " - $file_name";
$feed_type = 'history';
}
$title .= " $feed_type";
my $descr = git_get_project_description($project);
if (defined $descr) {
$descr = esc_html($descr);
} else {
$descr = "$project " .
($format eq 'rss' ? 'RSS' : 'Atom') .
" feed";
}
my $owner = git_get_project_owner($project);
$owner = esc_html($owner);
#header
my $alt_url;
if (defined $file_name) {
$alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
} elsif (defined $hash) {
$alt_url = href(-full=>1, action=>"log", hash=>$hash);
} else {
$alt_url = href(-full=>1, action=>"summary");
}
print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
if ($format eq 'rss') {
print <<XML; print <<XML;
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"> <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel> <channel>
<title>$project $my_uri $my_url</title>
<link>${\esc_html("$my_url?p=$project;a=summary")}</link>
<description>$project log</description>
<language>en</language>
XML XML
print "<title>$title</title>\n" .
"<link>$alt_url</link>\n" .
"<description>$descr</description>\n" .
"<language>en</language>\n";
} elsif ($format eq 'atom') {
print <<XML;
<feed xmlns="http://www.w3.org/2005/Atom">
XML
print "<title>$title</title>\n" .
"<subtitle>$descr</subtitle>\n" .
'<link rel="alternate" type="text/html" href="' .
$alt_url . '" />' . "\n" .
'<link rel="self" type="' . $content_type . '" href="' .
$cgi->self_url() . '" />' . "\n" .
"<id>" . href(-full=>1) . "</id>\n" .
# use project owner for feed author
"<author><name>$owner</name></author>\n";
if (defined $favicon) {
print "<icon>" . esc_url($favicon) . "</icon>\n";
}
if (defined $logo_url) {
# not twice as wide as tall: 72 x 27 pixels
print "<logo>" . esc_url($logo_url) . "</logo>\n";
}
if (! %latest_date) {
# dummy date to keep the feed valid until commits trickle in:
print "<updated>1970-01-01T00:00:00Z</updated>\n";
} else {
print "<updated>$latest_date{'iso-8601'}</updated>\n";
}
}
# contents
for (my $i = 0; $i <= $#revlist; $i++) { for (my $i = 0; $i <= $#revlist; $i++) {
my $commit = $revlist[$i]; my $commit = $revlist[$i];
my %co = parse_commit($commit); my %co = parse_commit($commit);
@ -4116,42 +4285,100 @@ XML
last; last;
} }
my %cd = parse_date($co{'committer_epoch'}); my %cd = parse_date($co{'committer_epoch'});
# get list of changed files
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
$co{'parent'}, $co{'id'}, "--" $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
or next; or next;
my @difftree = map { chomp; $_ } <$fd>; my @difftree = map { chomp; $_ } <$fd>;
close $fd close $fd
or next; or next;
# print element (entry, item)
my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
if ($format eq 'rss') {
print "<item>\n" . print "<item>\n" .
"<title>" . "<title>" . esc_html($co{'title'}) . "</title>\n" .
sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
"</title>\n" .
"<author>" . esc_html($co{'author'}) . "</author>\n" . "<author>" . esc_html($co{'author'}) . "</author>\n" .
"<pubDate>$cd{'rfc2822'}</pubDate>\n" . "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
"<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . "<guid isPermaLink=\"true\">$co_url</guid>\n" .
"<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . "<link>$co_url</link>\n" .
"<description>" . esc_html($co{'title'}) . "</description>\n" . "<description>" . esc_html($co{'title'}) . "</description>\n" .
"<content:encoded>" . "<content:encoded>" .
"<![CDATA[\n"; "<![CDATA[\n";
} elsif ($format eq 'atom') {
print "<entry>\n" .
"<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
"<updated>$cd{'iso-8601'}</updated>\n" .
"<author><name>" . esc_html($co{'author_name'}) . "</name></author>\n" .
# use committer for contributor
"<contributor><name>" . esc_html($co{'committer_name'}) . "</name></contributor>\n" .
"<published>$cd{'iso-8601'}</published>\n" .
"<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
"<id>$co_url</id>\n" .
"<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
"<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
}
my $comment = $co{'comment'}; my $comment = $co{'comment'};
print "<pre>\n";
foreach my $line (@$comment) { foreach my $line (@$comment) {
$line = to_utf8($line); $line = esc_html($line);
print "$line<br/>\n"; print "$line\n";
} }
print "<br/>\n"; print "</pre><ul>\n";
foreach my $line (@difftree) { foreach my $difftree_line (@difftree) {
if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { my %difftree = parse_difftree_raw_line($difftree_line);
next; next if !$difftree{'from_id'};
my $file = $difftree{'file'} || $difftree{'to_file'};
print "<li>" .
"[" .
$cgi->a({-href => href(-full=>1, action=>"blobdiff",
hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
file_name=>$file, file_parent=>$difftree{'from_file'}),
-title => "diff"}, 'D');
if ($have_blame) {
print $cgi->a({-href => href(-full=>1, action=>"blame",
file_name=>$file, hash_base=>$commit),
-title => "blame"}, 'B');
} }
my $file = esc_path(unquote($7)); # if this is not a feed of a file history
$file = to_utf8($file); if (!defined $file_name || $file_name ne $file) {
print "$file<br/>\n"; print $cgi->a({-href => href(-full=>1, action=>"history",
file_name=>$file, hash=>$commit),
-title => "history"}, 'H');
} }
print "]]>\n" . $file = esc_path($file);
print "] ".
"$file</li>\n";
}
if ($format eq 'rss') {
print "</ul>]]>\n" .
"</content:encoded>\n" . "</content:encoded>\n" .
"</item>\n"; "</item>\n";
} elsif ($format eq 'atom') {
print "</ul>\n</div>\n" .
"</content>\n" .
"</entry>\n";
} }
print "</channel></rss>"; }
# end of feed
if ($format eq 'rss') {
print "</channel>\n</rss>\n";
} elsif ($format eq 'atom') {
print "</feed>\n";
}
}
sub git_rss {
git_feed('rss');
}
sub git_atom {
git_feed('atom');
} }
sub git_opml { sub git_opml {

View File

@ -73,6 +73,7 @@ test_expect_success setup '
for i in 1 2; do echo $i; done >>dir/sub && for i in 1 2; do echo $i; done >>dir/sub &&
git update-index file0 dir/sub && git update-index file0 dir/sub &&
git repo-config log.showroot false &&
git commit --amend && git commit --amend &&
git show-branch git show-branch
' '

View File

@ -12,9 +12,15 @@
static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>"; static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
#define THEY_HAVE (1U << 0) /* bits #0..7 in revision.h, #8..10 in commit.c */
#define OUR_REF (1U << 1) #define THEY_HAVE (1u << 11)
#define WANTED (1U << 2) #define OUR_REF (1u << 12)
#define WANTED (1u << 13)
#define COMMON_KNOWN (1u << 14)
#define REACHABLE (1u << 15)
static unsigned long oldest_have;
static int multi_ack, nr_our_refs; static int multi_ack, nr_our_refs;
static int use_thin_pack, use_ofs_delta; static int use_thin_pack, use_ofs_delta;
static struct object_array have_obj; static struct object_array have_obj;
@ -303,11 +309,12 @@ static void create_pack_file(void)
static int got_sha1(char *hex, unsigned char *sha1) static int got_sha1(char *hex, unsigned char *sha1)
{ {
struct object *o; struct object *o;
int we_knew_they_have = 0;
if (get_sha1_hex(hex, sha1)) if (get_sha1_hex(hex, sha1))
die("git-upload-pack: expected SHA1 object, got '%s'", hex); die("git-upload-pack: expected SHA1 object, got '%s'", hex);
if (!has_sha1_file(sha1)) if (!has_sha1_file(sha1))
return 0; return -1;
o = lookup_object(sha1); o = lookup_object(sha1);
if (!(o && o->parsed)) if (!(o && o->parsed))
@ -316,17 +323,86 @@ static int got_sha1(char *hex, unsigned char *sha1)
die("oops (%s)", sha1_to_hex(sha1)); die("oops (%s)", sha1_to_hex(sha1));
if (o->type == OBJ_COMMIT) { if (o->type == OBJ_COMMIT) {
struct commit_list *parents; struct commit_list *parents;
struct commit *commit = (struct commit *)o;
if (o->flags & THEY_HAVE) if (o->flags & THEY_HAVE)
return 0; we_knew_they_have = 1;
else
o->flags |= THEY_HAVE; o->flags |= THEY_HAVE;
for (parents = ((struct commit*)o)->parents; if (!oldest_have || (commit->date < oldest_have))
oldest_have = commit->date;
for (parents = commit->parents;
parents; parents;
parents = parents->next) parents = parents->next)
parents->item->object.flags |= THEY_HAVE; parents->item->object.flags |= THEY_HAVE;
} }
if (!we_knew_they_have) {
add_object_array(o, NULL, &have_obj); add_object_array(o, NULL, &have_obj);
return 1; return 1;
} }
return 0;
}
static int reachable(struct commit *want)
{
struct commit_list *work = NULL;
insert_by_date(want, &work);
while (work) {
struct commit_list *list = work->next;
struct commit *commit = work->item;
free(work);
work = list;
if (commit->object.flags & THEY_HAVE) {
want->object.flags |= COMMON_KNOWN;
break;
}
if (!commit->object.parsed)
parse_object(commit->object.sha1);
if (commit->object.flags & REACHABLE)
continue;
commit->object.flags |= REACHABLE;
if (commit->date < oldest_have)
continue;
for (list = commit->parents; list; list = list->next) {
struct commit *parent = list->item;
if (!(parent->object.flags & REACHABLE))
insert_by_date(parent, &work);
}
}
want->object.flags |= REACHABLE;
clear_commit_marks(want, REACHABLE);
free_commit_list(work);
return (want->object.flags & COMMON_KNOWN);
}
static int ok_to_give_up(void)
{
int i;
if (!have_obj.nr)
return 0;
for (i = 0; i < want_obj.nr; i++) {
struct object *want = want_obj.objects[i].item;
if (want->flags & COMMON_KNOWN)
continue;
want = deref_tag(want, "a want line", 0);
if (!want || want->type != OBJ_COMMIT) {
/* no way to tell if this is reachable by
* looking at the ancestry chain alone, so
* leave a note to ourselves not to worry about
* this object anymore.
*/
want_obj.objects[i].item->flags |= COMMON_KNOWN;
continue;
}
if (!reachable((struct commit *)want))
return 0;
}
return 1;
}
static int get_common_commits(void) static int get_common_commits(void)
{ {
@ -349,7 +425,13 @@ static int get_common_commits(void)
} }
len = strip(line, len); len = strip(line, len);
if (!strncmp(line, "have ", 5)) { if (!strncmp(line, "have ", 5)) {
if (got_sha1(line+5, sha1)) { switch (got_sha1(line+5, sha1)) {
case -1: /* they have what we do not */
if (multi_ack && ok_to_give_up())
packet_write(1, "ACK %s continue\n",
sha1_to_hex(sha1));
break;
default:
memcpy(hex, sha1_to_hex(sha1), 41); memcpy(hex, sha1_to_hex(sha1), 41);
if (multi_ack) { if (multi_ack) {
const char *msg = "ACK %s continue\n"; const char *msg = "ACK %s continue\n";
@ -358,6 +440,7 @@ static int get_common_commits(void)
} }
else if (have_obj.nr == 1) else if (have_obj.nr == 1)
packet_write(1, "ACK %s\n", hex); packet_write(1, "ACK %s\n", hex);
break;
} }
continue; continue;
} }

View File

@ -118,7 +118,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg) { xdemitconf_t const *xecfg) {
long s1, s2, e1, e2, lctx; long s1, s2, e1, e2, lctx;
xdchange_t *xch, *xche; xdchange_t *xch, *xche;
char funcbuf[40]; char funcbuf[80];
long funclen = 0; long funclen = 0;
if (xecfg->flags & XDL_EMIT_COMMON) if (xecfg->flags & XDL_EMIT_COMMON)