Defining a custom hunk-header
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Each group of changes (called "hunk") in the textual diff output
+ Each group of changes (called a "hunk") in the textual diff output
is prefixed with a line of the form:
@@ -k,l +n,m @@ TEXT
- The text is called 'hunk header', and by default a line that
- begins with an alphabet, an underscore or a dollar sign is used,
- which matches what GNU 'diff -p' output uses. This default
- selection however is not suited for some contents, and you can
- use customized pattern to make a selection.
+ This is called a 'hunk header'. The "TEXT" portion is by default a line
+ that begins with an alphabet, an underscore or a dollar sign; this
+ matches what GNU 'diff -p' output uses. This default selection however
+ is not suited for some contents, and you can use a customized pattern
+ to make a selection.
- First in .gitattributes, you would assign the `diff` attribute
+ First, in .gitattributes, you would assign the `diff` attribute
for paths.
------------------------
*.tex diff=tex
------------------------
- Then, you would define "diff.tex.funcname" configuration to
+ Then, you would define a "diff.tex.funcname" configuration to
specify a regular expression that matches a line that you would
- want to appear as the hunk header, like this:
+ want to appear as the hunk header "TEXT", like this:
------------------------
[diff "tex"]
- `bibtex` suitable for files with BibTeX coded references.
+- `html` suitable for HTML/XHTML documents.
+
- `java` suitable for source code in the Java language.
- `pascal` suitable for source code in the Pascal/Delphi language.
+- `php` suitable for source code in the PHP language.
+
+- `python` suitable for source code in the Python language.
+
- `ruby` suitable for source code in the Ruby language.
- `tex` suitable for source code for LaTeX documents.
if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
die("'%s': not a regular file or symlink", path);
+ diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
+
if (blob[0].mode == S_IFINVALID)
blob[0].mode = canon_mode(st.st_mode);
argv++; argc--;
}
- if (revs->max_count == -1 &&
+ /*
+ * "diff --base" should not combine merges because it was not
+ * asked to. "diff -c" should not densify (if the user wants
+ * dense one, --cc can be explicitly asked for, or just rely
+ * on the default).
+ */
+ if (revs->max_count == -1 && !revs->combine_merges &&
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
revs->combine_merges = revs->dense_combined_merges = 1;
$head ||= 'HEAD';
my @refs;
my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
+ unless ($gs) {
+ die "Unable to determine upstream SVN information from ",
+ "$head history.\nPerhaps the repository is empty.";
+ }
$url = defined $_commit_url ? $_commit_url : $gs->full_url;
my $last_rev = $_revision if defined $_revision;
if ($url) {
print "Committing to $url ...\n";
}
- unless ($gs) {
- die "Unable to determine upstream SVN information from ",
- "$head history.\nPerhaps the repository is empty.";
- }
my ($linear_refs, $parents) = linearize_history($gs, \@refs);
if ($_no_rebase && scalar(@$linear_refs) > 1) {
warn "Attempting to commit more than one change while ",
}
}
+sub escape_uri_only {
+ my ($uri) = @_;
+ my @tmp;
+ foreach (split m{/}, $uri) {
+ s/([^\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
+ push @tmp, $_;
+ }
+ join('/', @tmp);
+}
+
+sub escape_url {
+ my ($url) = @_;
+ if ($url =~ m#^([^:]+)://([^/]*)(.*)$#) {
+ my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
+ $url = "$scheme://$domain$uri";
+ }
+ $url;
+}
+
sub cmd_info {
my $path = canonicalize_path(defined($_[0]) ? $_[0] : ".");
+ my $fullpath = canonicalize_path($cmd_dir_prefix . $path);
if (exists $_[1]) {
die "Too many arguments specified\n";
}
my ($file_type, $diff_status) = find_file_type_and_diff_status($path);
if (!$file_type && !$diff_status) {
- print STDERR "$path: (Not a versioned resource)\n\n";
- return;
+ print STDERR "svn: '$path' is not under version control\n";
+ exit 1;
}
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
# canonicalize_path() will return "" to make libsvn 1.5.x happy,
$path = "." if $path eq "";
- my $full_url = $url . ($path eq "." ? "" : "/$path");
+ my $full_url = $url . ($fullpath eq "" ? "" : "/$fullpath");
if ($_url) {
- print $full_url, "\n";
+ print escape_url($full_url), "\n";
return;
}
my $result = "Path: $path\n";
$result .= "Name: " . basename($path) . "\n" if $file_type ne "dir";
- $result .= "URL: " . $full_url . "\n";
+ $result .= "URL: " . escape_url($full_url) . "\n";
eval {
my $repos_root = $gs->repos_root;
Git::SVN::remove_username($repos_root);
- $result .= "Repository Root: $repos_root\n";
+ $result .= "Repository Root: " . escape_url($repos_root) . "\n";
};
if ($@) {
$result .= "Repository Root: (offline)\n";
}
my ($lc_author, $lc_rev, $lc_date_utc);
- my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $path);
+ my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $fullpath);
my $log = command_output_pipe(@args);
my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
while (<$log>) {
while (<$diff_fh>) {
chomp $_; # this gets rid of the trailing "\0"
if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
- $::sha1\s($::sha1)\s
+ ($::sha1)\s($::sha1)\s
([MTCRAD])\d*$/xo) {
push @mods, { mode_a => $1, mode_b => $2,
- sha1_b => $3, chg => $4 };
- if ($4 =~ /^(?:C|R)$/) {
+ sha1_a => $3, sha1_b => $4,
+ chg => $5 };
+ if ($5 =~ /^(?:C|R)$/) {
$state = 'file_a';
} else {
$state = 'file_b';
my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
$self->url_path($m->{file_a}), $self->{r});
print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
+ $self->apply_autoprops($file, $fbat);
$self->chg_file($fbat, $m);
$self->close_file($fbat,undef,$self->{pool});
$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
}
-sub chg_file {
- my ($self, $fbat, $m) = @_;
- if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
- $self->change_file_prop($fbat,'svn:executable','*');
- } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
- $self->change_file_prop($fbat,'svn:executable',undef);
- }
- my $fh = Git::temp_acquire('git_blob');
- if ($m->{mode_b} =~ /^120/) {
+sub _chg_file_get_blob ($$$$) {
+ my ($self, $fbat, $m, $which) = @_;
+ my $fh = Git::temp_acquire("git_blob_$which");
+ if ($m->{"mode_$which"} =~ /^120/) {
print $fh 'link ' or croak $!;
$self->change_file_prop($fbat,'svn:special','*');
- } elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
+ } elsif ($m->{mode_a} =~ /^120/ && $m->{"mode_$which"} !~ /^120/) {
$self->change_file_prop($fbat,'svn:special',undef);
}
- my $size = $::_repository->cat_blob($m->{sha1_b}, $fh);
- croak "Failed to read object $m->{sha1_b}" if ($size < 0);
+ my $blob = $m->{"sha1_$which"};
+ return ($fh,) if ($blob =~ /^0{40}$/);
+ my $size = $::_repository->cat_blob($blob, $fh);
+ croak "Failed to read object $blob" if ($size < 0);
$fh->flush == 0 or croak $!;
seek $fh, 0, 0 or croak $!;
my $exp = ::md5sum($fh);
seek $fh, 0, 0 or croak $!;
+ return ($fh, $exp);
+}
+sub chg_file {
+ my ($self, $fbat, $m) = @_;
+ if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
+ $self->change_file_prop($fbat,'svn:executable','*');
+ } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
+ $self->change_file_prop($fbat,'svn:executable',undef);
+ }
+ my ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';
+ my ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';
my $pool = SVN::Pool->new;
- my $atd = $self->apply_textdelta($fbat, undef, $pool);
- my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
- die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
- Git::temp_release($fh, 1);
+ my $atd = $self->apply_textdelta($fbat, $exp_a, $pool);
+ if (-s $fh_a) {
+ my $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);
+ my $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);
+ if (defined $res) {
+ die "Unexpected result from send_txstream: $res\n",
+ "(SVN::Core::VERSION: $SVN::Core::VERSION)\n";
+ }
+ } else {
+ my $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);
+ die "Checksum mismatch\nexpected: $exp_b\ngot: $got\n"
+ if ($got ne $exp_b);
+ }
+ Git::temp_release($fh_b, 1);
+ Git::temp_release($fh_a, 1);
$pool->clear;
}
my $old_url = $full_url;
$full_url .= '/' . escape_uri_only($path) if length $path;
my ($ra, $reparented);
- if ($old_url ne $full_url) {
- if ($old_url !~ m#^svn(\+ssh)?://#) {
- SVN::_Ra::svn_ra_reparent($self->{session}, $full_url,
- $pool);
- $self->{url} = $full_url;
- $reparented = 1;
- } else {
- $_[0] = undef;
- $self = undef;
- $RA = undef;
- $ra = Git::SVN::Ra->new($full_url);
- $ra_invalid = 1;
- }
+
+ if ($old_url =~ m#^svn(\+ssh)?://#) {
+ $_[0] = undef;
+ $self = undef;
+ $RA = undef;
+ $ra = Git::SVN::Ra->new($full_url);
+ $ra_invalid = 1;
+ } elsif ($old_url ne $full_url) {
+ SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);
+ $self->{url} = $full_url;
+ $reparented = 1;
}
+
$ra ||= $self;
$url_b = escape_url($url_b);
my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
}
# format marker of refs pointing to given object
+
+# the destination action is chosen based on object type and current context:
+# - for annotated tags, we choose the tag view unless it's the current view
+# already, in which case we go to shortlog view
+# - for other refs, we keep the current view if we're in history, shortlog or
+# log view, and select shortlog otherwise
sub format_ref_marker {
my ($refs, $id) = @_;
my $markers = '';
if (defined $refs->{$id}) {
foreach my $ref (@{$refs->{$id}}) {
+ # this code exploits the fact that non-lightweight tags are the
+ # only indirect objects, and that they are the only objects for which
+ # we want to use tag instead of shortlog as action
my ($type, $name) = qw();
+ my $indirect = ($ref =~ s/\^\{\}$//);
# e.g. tags/v2.6.11 or heads/next
if ($ref =~ m!^(.*?)s?/(.*)$!) {
$type = $1;
$name = $ref;
}
- $markers .= " <span class=\"$type\" title=\"$ref\">" .
- esc_html($name) . "</span>";
+ my $class = $type;
+ $class .= " indirect" if $indirect;
+
+ my $dest_action = "shortlog";
+
+ if ($indirect) {
+ $dest_action = "tag" unless $action eq "tag";
+ } elsif ($action =~ /^(history|(short)?log)$/) {
+ $dest_action = $action;
+ }
+
+ my $dest = "";
+ $dest .= "refs/" unless $ref =~ m!^refs/!;
+ $dest .= $ref;
+
+ my $link = $cgi->a({
+ -href => href(
+ action=>$dest_action,
+ hash=>$dest
+ )}, $name);
+
+ $markers .= " <span class=\"$class\" title=\"$ref\">" .
+ $link . "</span>";
}
}
while (my $line = <$fd>) {
chomp $line;
- if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
+ if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
if (defined $refs{$1}) {
push @{$refs{$1}}, $2;
} else {
last;
}
}
- if ($co{'title'} eq "") {
+ if (! defined $co{'title'} || $co{'title'} eq "") {
$co{'title'} = $co{'title_short'} = '(no commit message)';
}
# remove added spaces
*/
int move_temp_to_file(const char *tmpfile, const char *filename)
{
- int ret = link(tmpfile, filename);
+ int ret = 0;
+ if (link(tmpfile, filename))
+ ret = errno;
/*
* Coda hack - coda doesn't like cross-directory links,
return has_loose_object(sha1);
}
-int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
+static int index_mem(unsigned char *sha1, void *buf, size_t size,
+ int write_object, enum object_type type, const char *path)
{
- struct strbuf buf;
- int ret;
-
- strbuf_init(&buf, 0);
- if (strbuf_read(&buf, fd, 4096) < 0) {
- strbuf_release(&buf);
- return -1;
- }
-
- if (!type)
- type = blob_type;
- if (write_object)
- ret = write_sha1_file(buf.buf, buf.len, type, sha1);
- else
- ret = hash_sha1_file(buf.buf, buf.len, type, sha1);
- strbuf_release(&buf);
-
- return ret;
-}
-
-int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
- enum object_type type, const char *path)
-{
- size_t size = xsize_t(st->st_size);
- void *buf = NULL;
int ret, re_allocated = 0;
- if (size)
- buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
- close(fd);
-
if (!type)
type = OBJ_BLOB;
/*
* Convert blobs to git internal format
*/
- if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
+ if ((type == OBJ_BLOB) && path) {
struct strbuf nbuf;
strbuf_init(&nbuf, 0);
if (convert_to_git(path, buf, size, &nbuf,
write_object ? safe_crlf : 0)) {
- munmap(buf, size);
buf = strbuf_detach(&nbuf, &size);
re_allocated = 1;
}
ret = write_sha1_file(buf, size, typename(type), sha1);
else
ret = hash_sha1_file(buf, size, typename(type), sha1);
- if (re_allocated) {
+ if (re_allocated)
free(buf);
- return ret;
- }
- if (size)
+ return ret;
+}
+
+int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
+ enum object_type type, const char *path)
+{
+ int ret;
+ size_t size = xsize_t(st->st_size);
+
+ if (!S_ISREG(st->st_mode)) {
+ struct strbuf sbuf;
+ strbuf_init(&sbuf, 0);
+ if (strbuf_read(&sbuf, fd, 4096) >= 0)
+ ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
+ type, path);
+ else
+ ret = -1;
+ strbuf_release(&sbuf);
+ } else if (size) {
+ void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ ret = index_mem(sha1, buf, size, write_object, type, path);
munmap(buf, size);
+ } else
+ ret = index_mem(sha1, NULL, size, write_object, type, path);
+ close(fd);
return ret;
}
. ./test-lib.sh
+remotes_git_svn=remotes/git""-svn
+git_svn_id=git""-svn-id
+
if test -n "$NO_SVN_TESTS"
then
- test_expect_success 'skipping git-svn tests, NO_SVN_TESTS defined' :
+ test_expect_success 'skipping git svn tests, NO_SVN_TESTS defined' :
test_done
exit
fi
svn >/dev/null 2>&1
if test $? -ne 1
then
- test_expect_success 'skipping git-svn tests, svn not found' :
+ test_expect_success 'skipping git svn tests, svn not found' :
test_done
exit
fi
mkdir "$GIT_DIR"/logs
cat > "$GIT_DIR/httpd.conf" <<EOF
-ServerName "git-svn test"
+ServerName "git svn test"
ServerRoot "$GIT_DIR"
DocumentRoot "$GIT_DIR"
PidFile "$GIT_DIR/httpd.pid"
close $rd or die $!;
EOF
}
+
+ require_svnserve () {
+ if test -z "$SVNSERVE_PORT"
+ then
+ say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
+ test_done
+ exit
+ fi
+ }
+
+ start_svnserve () {
+ svnserve --listen-port $SVNSERVE_PORT \
+ --root "$rawsvnrepo" \
+ --listen-once \
+ --listen-host 127.0.0.1 &
+ }
+
# daemon running on a users system if the test fails.
# Not all git users will need to interact with SVN.
-test_description='git-svn dcommit new files over svn:// test'
+test_description='git svn dcommit new files over svn:// test'
. ./lib-git-svn.sh
- if test -z "$SVNSERVE_PORT"
- then
- say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
- test_done
- exit
- fi
-
- start_svnserve () {
- svnserve --listen-port $SVNSERVE_PORT \
- --root "$rawsvnrepo" \
- --listen-once \
- --listen-host 127.0.0.1 &
- }
+ require_svnserve
test_expect_success 'start tracking an empty repo' '
svn mkdir -m "empty dir" "$svnrepo"/empty-dir &&