From b1edc53d062c4f6adae08a15be08d6e7bccd242e Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:29 +0200 Subject: Introduce Git.pm (v4) This patch introduces a very basic and barebone Git.pm module with a sketch of how the generic interface would look like; most functions are missing, but this should give some good base. I will continue expanding it. Most desirable now is more careful error reporting, generic_in() for feeding input to Git commands and the repository() constructor doing some poking with git-rev-parse to get the git directory and subdirectory prefix. Those three are basically the prerequisities for converting git-mv. I will send them as follow-ups to this patch. Currently Git.pm just wraps up exec()s of Git commands, but even that is not trivial to get right and various Git perl scripts do it in various inconsistent ways. In addition to Git.pm, there is now also Git.xs which provides barebone Git.xs for directly interfacing with libgit.a, and as an example providing the hash_object() function using libgit. This adds the Git module, integrates it to the build system and as an example converts the git-fmt-merge-msg.perl script to it (the result is not very impressive since its advantage is not quite apparent in this one, but I just picked up the simplest Git user around). Compared to v3, only very minor things were fixed in this patch (some whitespaces, a missing export, tiny bug in git-fmt-merge-msg.perl); at first I wanted to post them as a separate patch but since this is still only in pu, I decided that it will be cleaner to just resend the patch. My current working state is available all the time at http://pasky.or.cz/~xpasky/git-perl/Git.pm and an irregularily updated API documentation is at http://pasky.or.cz/~xpasky/git-perl/Git.html Many thanks to Jakub Narebski, Junio and others for their feedback. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index cde619c..730b38a 100644 --- a/Makefile +++ b/Makefile @@ -490,7 +490,8 @@ export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk -all: +all: perl/Makefile + $(MAKE) -C perl $(MAKE) -C templates strip: $(PROGRAMS) git$X @@ -522,7 +523,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl rm -f $@ $@+ - sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + sed -e '1s|#!.*perl\(.*\)|#!$(PERL_PATH_SQ)\1 -I'"$$(make -s -C perl instlibdir)"'|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ $@.perl >$@+ chmod +x $@+ @@ -608,6 +609,9 @@ $(XDIFF_LIB): $(XDIFF_OBJS) rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) +perl/Makefile: perl/Git.pm perl/Makefile.PL + (cd perl && $(PERL_PATH) Makefile.PL PREFIX="$(prefix)" DEFINE="$(ALL_CFLAGS)" LIBS="$(LIBS)") + doc: $(MAKE) -C Documentation all @@ -663,6 +667,7 @@ install: all $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates install + $(MAKE) -C perl install $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ @@ -730,7 +735,8 @@ clean: rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f $(htmldocs).tar.gz $(manpages).tar.gz $(MAKE) -C Documentation/ clean - $(MAKE) -C templates clean + [ ! -e perl/Makefile ] || $(MAKE) -C perl/ clean + $(MAKE) -C templates/ clean $(MAKE) -C t/ clean rm -f GIT-VERSION-FILE GIT-CFLAGS diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl index 5986e54..be2a48c 100755 --- a/git-fmt-merge-msg.perl +++ b/git-fmt-merge-msg.perl @@ -6,6 +6,9 @@ # by grouping branches and tags together to form a single line. use strict; +use Git; + +my $repo = Git->repository(); my @src; my %src; @@ -28,13 +31,12 @@ sub andjoin { } sub repoconfig { - my ($val) = qx{git-repo-config --get merge.summary}; + my ($val) = $repo->command_oneline('repo-config', '--get', 'merge.summary'); return $val; } sub current_branch { - my ($bra) = qx{git-symbolic-ref HEAD}; - chomp($bra); + my ($bra) = $repo->command_oneline('symbolic-ref', 'HEAD'); $bra =~ s|^refs/heads/||; if ($bra ne 'master') { $bra = " into $bra"; @@ -47,11 +49,10 @@ sub current_branch { sub shortlog { my ($tip) = @_; my @result; - foreach ( qx{git-log --no-merges --topo-order --pretty=oneline $tip ^HEAD} ) { + foreach ($repo->command('log', '--no-merges', '--topo-order', '--pretty=oneline', $tip, '^HEAD')) { s/^[0-9a-f]{40}\s+//; push @result, $_; } - die "git-log failed\n" if $?; return @result; } @@ -168,6 +169,6 @@ for (@origin) { print " ...\n"; last; } - print " $log"; + print " $log\n"; } } diff --git a/perl/.gitignore b/perl/.gitignore new file mode 100644 index 0000000..6d778f3 --- /dev/null +++ b/perl/.gitignore @@ -0,0 +1,7 @@ +Git.bs +Git.c +Makefile +blib +blibdirs +pm_to_blib +ppport.h diff --git a/perl/Git.pm b/perl/Git.pm new file mode 100644 index 0000000..8fff785 --- /dev/null +++ b/perl/Git.pm @@ -0,0 +1,408 @@ +=head1 NAME + +Git - Perl interface to the Git version control system + +=cut + + +package Git; + +use strict; + + +BEGIN { + +our ($VERSION, @ISA, @EXPORT, @EXPORT_OK); + +# Totally unstable API. +$VERSION = '0.01'; + + +=head1 SYNOPSIS + + use Git; + + my $version = Git::command_oneline('version'); + + Git::command_noisy('update-server-info'); + + my $repo = Git->repository (Directory => '/srv/git/cogito.git'); + + + my @revs = $repo->command('rev-list', '--since=last monday', '--all'); + + my $fh = $repo->command_pipe('rev-list', '--since=last monday', '--all'); + my $lastrev = <$fh>; chomp $lastrev; + close $fh; # You may want to test rev-list exit status here + + my $lastrev = $repo->command_oneline('rev-list', '--all'); + +=cut + + +require Exporter; + +@ISA = qw(Exporter); + +@EXPORT = qw(); + +# Methods which can be called as standalone functions as well: +@EXPORT_OK = qw(command command_oneline command_pipe command_noisy + hash_object); + + +=head1 DESCRIPTION + +This module provides Perl scripts easy way to interface the Git version control +system. The modules have an easy and well-tested way to call arbitrary Git +commands; in the future, the interface will also provide specialized methods +for doing easily operations which are not totally trivial to do over +the generic command interface. + +While some commands can be executed outside of any context (e.g. 'version' +or 'init-db'), most operations require a repository context, which in practice +means getting an instance of the Git object using the repository() constructor. +(In the future, we will also get a new_repository() constructor.) All commands +called as methods of the object are then executed in the context of the +repository. + +TODO: In the future, we might also do + + my $subdir = $repo->subdir('Documentation'); + # Gets called in the subdirectory context: + $subdir->command('status'); + + my $remoterepo = $repo->remote_repository (Name => 'cogito', Branch => 'master'); + $remoterepo ||= Git->remote_repository ('http://git.or.cz/cogito.git/'); + my @refs = $remoterepo->refs(); + +So far, all functions just die if anything goes wrong. If you don't want that, +make appropriate provisions to catch the possible deaths. Better error recovery +mechanisms will be provided in the future. + +Currently, the module merely wraps calls to external Git tools. In the future, +it will provide a much faster way to interact with Git by linking directly +to libgit. This should be completely opaque to the user, though (performance +increate nonwithstanding). + +=cut + + +use Carp qw(carp croak); + +require XSLoader; +XSLoader::load('Git', $VERSION); + +} + + +=head1 CONSTRUCTORS + +=over 4 + +=item repository ( OPTIONS ) + +=item repository ( DIRECTORY ) + +=item repository () + +Construct a new repository object. +C are passed in a hash like fashion, using key and value pairs. +Possible options are: + +B - Path to the Git repository. + +B - Path to the associated working copy; not strictly required +as many commands will happily crunch on a bare repository. + +B - Path to the Git working directory in its usual setup. This +is just for convenient setting of both C and C +at once: If the directory as a C<.git> subdirectory, C is pointed +to the subdirectory and the directory is assumed to be the working copy. +If the directory does not have the subdirectory, C is left +undefined and C is pointed to the directory itself. + +B - Path to the C binary executable. By default the C<$PATH> +is searched for it. + +You should not use both C and either of C and +C - the results of that are undefined. + +Alternatively, a directory path may be passed as a single scalar argument +to the constructor; it is equivalent to setting only the C option +field. + +Calling the constructor with no options whatsoever is equivalent to +calling it with C<< Directory => '.' >>. + +=cut + +sub repository { + my $class = shift; + my @args = @_; + my %opts = (); + my $self; + + if (defined $args[0]) { + if ($#args % 2 != 1) { + # Not a hash. + $#args == 0 or croak "bad usage"; + %opts = (Directory => $args[0]); + } else { + %opts = @args; + } + + if ($opts{Directory}) { + -d $opts{Directory} or croak "Directory not found: $!"; + if (-d $opts{Directory}."/.git") { + # TODO: Might make this more clever + $opts{WorkingCopy} = $opts{Directory}; + $opts{Repository} = $opts{Directory}."/.git"; + } else { + $opts{Repository} = $opts{Directory}; + } + delete $opts{Directory}; + } + } + + $self = { opts => \%opts }; + bless $self, $class; +} + + +=back + +=head1 METHODS + +=over 4 + +=item command ( COMMAND [, ARGUMENTS... ] ) + +Execute the given Git C (specify it without the 'git-' +prefix), optionally with the specified extra C. + +The method can be called without any instance or on a specified Git repository +(in that case the command will be run in the repository context). + +In scalar context, it returns all the command output in a single string +(verbatim). + +In array context, it returns an array containing lines printed to the +command's stdout (without trailing newlines). + +In both cases, the command's stdin and stderr are the same as the caller's. + +=cut + +sub command { + my $fh = command_pipe(@_); + + if (not defined wantarray) { + _cmd_close($fh); + + } elsif (not wantarray) { + local $/; + my $text = <$fh>; + _cmd_close($fh); + return $text; + + } else { + my @lines = <$fh>; + _cmd_close($fh); + chomp @lines; + return @lines; + } +} + + +=item command_oneline ( COMMAND [, ARGUMENTS... ] ) + +Execute the given C in the same way as command() +does but always return a scalar string containing the first line +of the command's standard output. + +=cut + +sub command_oneline { + my $fh = command_pipe(@_); + + my $line = <$fh>; + _cmd_close($fh); + + chomp $line; + return $line; +} + + +=item command_pipe ( COMMAND [, ARGUMENTS... ] ) + +Execute the given C in the same way as command() +does but return a pipe filehandle from which the command output can be +read. + +=cut + +sub command_pipe { + my ($self, $cmd, @args) = _maybe_self(@_); + + $cmd =~ /^[a-z0-9A-Z_-]+$/ or croak "bad command: $cmd"; + + my $pid = open(my $fh, "-|"); + if (not defined $pid) { + croak "open failed: $!"; + } elsif ($pid == 0) { + _cmd_exec($self, $cmd, @args); + } + return $fh; +} + + +=item command_noisy ( COMMAND [, ARGUMENTS... ] ) + +Execute the given C in the same way as command() does but do not +capture the command output - the standard output is not redirected and goes +to the standard output of the caller application. + +While the method is called command_noisy(), you might want to as well use +it for the most silent Git commands which you know will never pollute your +stdout but you want to avoid the overhead of the pipe setup when calling them. + +The function returns only after the command has finished running. + +=cut + +sub command_noisy { + my ($self, $cmd, @args) = _maybe_self(@_); + + $cmd =~ /^[a-z0-9A-Z_-]+$/ or croak "bad command: $cmd"; + + my $pid = fork; + if (not defined $pid) { + croak "fork failed: $!"; + } elsif ($pid == 0) { + _cmd_exec($self, $cmd, @args); + } + if (waitpid($pid, 0) > 0 and $? != 0) { + croak "exit status: $?"; + } +} + + +=item hash_object ( FILENAME [, TYPE ] ) + +=item hash_object ( FILEHANDLE [, TYPE ] ) + +Compute the SHA1 object id of the given C (or data waiting in +C) considering it is of the C object type (C +(default), C, C). + +In case of C passed instead of file name, all the data +available are read and hashed, and the filehandle is automatically +closed. The file handle should be freshly opened - if you have already +read anything from the file handle, the results are undefined (since +this function works directly with the file descriptor and internal +PerlIO buffering might have messed things up). + +The method can be called without any instance or on a specified Git repository, +it makes zero difference. + +The function returns the SHA1 hash. + +Implementation of this function is very fast; no external command calls +are involved. + +=cut + +# Implemented in Git.xs. + + +=back + +=head1 TODO + +This is still fairly crude. +We need some good way to report errors back except just dying. + +=head1 COPYRIGHT + +Copyright 2006 by Petr Baudis Epasky@suse.czE. + +This module is free software; it may be used, copied, modified +and distributed under the terms of the GNU General Public Licence, +either version 2, or (at your option) any later version. + +=cut + + +# Take raw method argument list and return ($obj, @args) in case +# the method was called upon an instance and (undef, @args) if +# it was called directly. +sub _maybe_self { + # This breaks inheritance. Oh well. + ref $_[0] eq 'Git' ? @_ : (undef, @_); +} + +# When already in the subprocess, set up the appropriate state +# for the given repository and execute the git command. +sub _cmd_exec { + my ($self, @args) = @_; + if ($self) { + $self->{opts}->{Repository} and $ENV{'GIT_DIR'} = $self->{opts}->{Repository}; + $self->{opts}->{WorkingCopy} and chdir($self->{opts}->{WorkingCopy}); + } + my $git = $self->{opts}->{GitPath}; + $git ||= 'git'; + exec ($git, @args) or croak "exec failed: $!"; +} + +# Close pipe to a subprocess. +sub _cmd_close { + my ($fh) = @_; + if (not close $fh) { + if ($!) { + # It's just close, no point in fatalities + carp "error closing pipe: $!"; + } elsif ($? >> 8) { + croak "exit status: ".($? >> 8); + } + # else we might e.g. closed a live stream; the command + # dying of SIGPIPE would drive us here. + } +} + + +# Trickery for .xs routines: In order to avoid having some horrid +# C code trying to do stuff with undefs and hashes, we gate all +# xs calls through the following and in case we are being ran upon +# an instance call a C part of the gate which will set up the +# environment properly. +sub _call_gate { + my $xsfunc = shift; + my ($self, @args) = _maybe_self(@_); + + if (defined $self) { + # XXX: We ignore the WorkingCopy! To properly support + # that will require heavy changes in libgit. + + # XXX: And we ignore everything else as well. libgit + # at least needs to be extended to let us specify + # the $GIT_DIR instead of looking it up in environment. + #xs_call_gate($self->{opts}->{Repository}); + } + + &$xsfunc(@args); +} + +sub AUTOLOAD { + my $xsname; + our $AUTOLOAD; + ($xsname = $AUTOLOAD) =~ s/.*:://; + croak "&Git::$xsname not defined" if $xsname =~ /^xs_/; + $xsname = 'xs_'.$xsname; + _call_gate(\&$xsname, @_); +} + +sub DESTROY { } + + +1; # Famous last words diff --git a/perl/Git.xs b/perl/Git.xs new file mode 100644 index 0000000..1b81ce2 --- /dev/null +++ b/perl/Git.xs @@ -0,0 +1,64 @@ +/* By carefully stacking #includes here (even if WE don't really need them) + * we strive to make the thing actually compile. Git header files aren't very + * nice. Perl headers are one of the signs of the coming apocalypse. */ +#include +/* Ok, it hasn't been so bad so far. */ + +/* libgit interface */ +#include "../cache.h" + +/* XS and Perl interface */ +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +#include "ppport.h" + + +MODULE = Git PACKAGE = Git + +PROTOTYPES: DISABLE + +# /* TODO: xs_call_gate(). See Git.pm. */ + +char * +xs_hash_object(file, type = "blob") + SV *file; + char *type; +CODE: +{ + unsigned char sha1[20]; + + if (SvTYPE(file) == SVt_RV) + file = SvRV(file); + + if (SvTYPE(file) == SVt_PVGV) { + /* Filehandle */ + PerlIO *pio; + + pio = IoIFP(sv_2io(file)); + if (!pio) + croak("You passed me something weird - a dir glob?"); + /* XXX: I just hope PerlIO didn't read anything from it yet. + * --pasky */ + if (index_pipe(sha1, PerlIO_fileno(pio), type, 0)) + croak("Unable to hash given filehandle"); + /* Avoid any nasty surprises. */ + PerlIO_close(pio); + + } else { + /* String */ + char *path = SvPV_nolen(file); + int fd = open(path, O_RDONLY); + struct stat st; + + if (fd < 0 || + fstat(fd, &st) < 0 || + index_fd(sha1, fd, &st, 0, type)) + croak("Unable to hash %s", path); + close(fd); + } + RETVAL = sha1_to_hex(sha1); +} +OUTPUT: + RETVAL diff --git a/perl/Makefile.PL b/perl/Makefile.PL new file mode 100644 index 0000000..dd61056 --- /dev/null +++ b/perl/Makefile.PL @@ -0,0 +1,21 @@ +use ExtUtils::MakeMaker; + +sub MY::postamble { + return <<'MAKE_FRAG'; +instlibdir: + @echo $(INSTALLSITELIB) + +MAKE_FRAG +} + +WriteMakefile( + NAME => 'Git', + VERSION_FROM => 'Git.pm', + MYEXTLIB => '../libgit.a', + INC => '-I. -I..', +); + + +use Devel::PPPort; + +-s 'ppport.h' or Devel::PPPort::WriteFile(); -- cgit v0.10.2-6-g49f6 From eca1f6fdb862e6ca07288ac385725c95fd96490e Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:31 +0200 Subject: Git.pm: Implement Git::exec_path() This patch implements Git::exec_path() (as a direct XS call). Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 8fff785..5c5ae12 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -48,7 +48,7 @@ require Exporter; # Methods which can be called as standalone functions as well: @EXPORT_OK = qw(command command_oneline command_pipe command_noisy - hash_object); + exec_path hash_object); =head1 DESCRIPTION @@ -288,6 +288,19 @@ sub command_noisy { } +=item exec_path () + +Return path to the git sub-command executables (the same as +C). Useful mostly only internally. + +Implementation of this function is very fast; no external command calls +are involved. + +=cut + +# Implemented in Git.xs. + + =item hash_object ( FILENAME [, TYPE ] ) =item hash_object ( FILEHANDLE [, TYPE ] ) diff --git a/perl/Git.xs b/perl/Git.xs index 1b81ce2..9e754d2 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -6,6 +6,7 @@ /* libgit interface */ #include "../cache.h" +#include "../exec_cmd.h" /* XS and Perl interface */ #include "EXTERN.h" @@ -21,6 +22,17 @@ PROTOTYPES: DISABLE # /* TODO: xs_call_gate(). See Git.pm. */ + +const char * +xs_exec_path() +CODE: +{ + RETVAL = git_exec_path(); +} +OUTPUT: + RETVAL + + char * xs_hash_object(file, type = "blob") SV *file; -- cgit v0.10.2-6-g49f6 From 8062f81c2d9df5e6552bf267b258ffcc5f647f93 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:34 +0200 Subject: Git.pm: Call external commands using execv_git_cmd() Instead of explicitly using the git wrapper to call external commands, use the execv_git_cmd() function which will directly call whatever needs to be called. GitBin option becomes useless so drop it. This actually means the exec_path() thing I planned to use worthless internally, but Jakub wants it in anyway and I don't mind, so... Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 5c5ae12..212337e 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -122,9 +122,6 @@ to the subdirectory and the directory is assumed to be the working copy. If the directory does not have the subdirectory, C is left undefined and C is pointed to the directory itself. -B - Path to the C binary executable. By default the C<$PATH> -is searched for it. - You should not use both C and either of C and C - the results of that are undefined. @@ -363,11 +360,14 @@ sub _cmd_exec { $self->{opts}->{Repository} and $ENV{'GIT_DIR'} = $self->{opts}->{Repository}; $self->{opts}->{WorkingCopy} and chdir($self->{opts}->{WorkingCopy}); } - my $git = $self->{opts}->{GitPath}; - $git ||= 'git'; - exec ($git, @args) or croak "exec failed: $!"; + xs__execv_git_cmd(@args); + croak "exec failed: $!"; } +# Execute the given Git command ($_[0]) with arguments ($_[1..]) +# by searching for it at proper places. +# _execv_git_cmd(), implemented in Git.xs. + # Close pipe to a subprocess. sub _cmd_close { my ($fh) = @_; diff --git a/perl/Git.xs b/perl/Git.xs index 9e754d2..6478f9c 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -33,6 +33,28 @@ OUTPUT: RETVAL +void +xs__execv_git_cmd(...) +CODE: +{ + const char **argv; + int i; + + argv = malloc(sizeof(const char *) * (items + 1)); + if (!argv) + croak("malloc failed"); + for (i = 0; i < items; i++) + argv[i] = strdup(SvPV_nolen(ST(i))); + argv[i] = NULL; + + execv_git_cmd(argv); + + for (i = 0; i < items; i++) + if (argv[i]) + free((char *) argv[i]); + free((char **) argv); +} + char * xs_hash_object(file, type = "blob") SV *file; -- cgit v0.10.2-6-g49f6 From 63df97ae7baeedc3ce04995139fa0f6bc5eea76c Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:36 +0200 Subject: Git.pm: Implement Git::version() Git::version() returns the Git version string. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 730b38a..dda9b9d 100644 --- a/Makefile +++ b/Makefile @@ -610,7 +610,10 @@ $(XDIFF_LIB): $(XDIFF_OBJS) perl/Makefile: perl/Git.pm perl/Makefile.PL - (cd perl && $(PERL_PATH) Makefile.PL PREFIX="$(prefix)" DEFINE="$(ALL_CFLAGS)" LIBS="$(LIBS)") + (cd perl && $(PERL_PATH) Makefile.PL \ + PREFIX="$(prefix)" \ + DEFINE="$(ALL_CFLAGS) -DGIT_VERSION=\\\"$(GIT_VERSION)\\\"" \ + LIBS="$(LIBS)") doc: $(MAKE) -C Documentation all diff --git a/perl/Git.pm b/perl/Git.pm index 212337e..dcd769b 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -48,7 +48,7 @@ require Exporter; # Methods which can be called as standalone functions as well: @EXPORT_OK = qw(command command_oneline command_pipe command_noisy - exec_path hash_object); + version exec_path hash_object); =head1 DESCRIPTION @@ -285,6 +285,18 @@ sub command_noisy { } +=item version () + +Return the Git version in use. + +Implementation of this function is very fast; no external command calls +are involved. + +=cut + +# Implemented in Git.xs. + + =item exec_path () Return path to the git sub-command executables (the same as diff --git a/perl/Git.xs b/perl/Git.xs index 6478f9c..d4608eb 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -24,6 +24,16 @@ PROTOTYPES: DISABLE const char * +xs_version() +CODE: +{ + RETVAL = GIT_VERSION; +} +OUTPUT: + RETVAL + + +const char * xs_exec_path() CODE: { -- cgit v0.10.2-6-g49f6 From 5c4082fd687bd0784d3a4d96550e8afab332b63a Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:40 +0200 Subject: Add Error.pm to the distribution I have been thinking about how to do the error reporting the best way and after scraping various overcomplicated concepts, I have decided that by far the most elegant way is to throw Error exceptions; the closest sane alternative is to catch the dies in Git.pm by enclosing the calls in eval{}s and that's really _quite_ ugly. The only "small" trouble is that Error.pm turns out sadly not to be part of the standard distribution, and installation from CPAN is a bother, especially if you can't install it system-wide. But since it is very small, I've decided to just bundle it. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Error.pm b/perl/Error.pm new file mode 100644 index 0000000..ebd0749 --- /dev/null +++ b/perl/Error.pm @@ -0,0 +1,821 @@ +# Error.pm +# +# Copyright (c) 1997-8 Graham Barr . All rights reserved. +# This program is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +# +# Based on my original Error.pm, and Exceptions.pm by Peter Seibel +# and adapted by Jesse Glick . +# +# but modified ***significantly*** + +package Error; + +use strict; +use vars qw($VERSION); +use 5.004; + +$VERSION = "0.15009"; + +use overload ( + '""' => 'stringify', + '0+' => 'value', + 'bool' => sub { return 1; }, + 'fallback' => 1 +); + +$Error::Depth = 0; # Depth to pass to caller() +$Error::Debug = 0; # Generate verbose stack traces +@Error::STACK = (); # Clause stack for try +$Error::THROWN = undef; # last error thrown, a workaround until die $ref works + +my $LAST; # Last error created +my %ERROR; # Last error associated with package + +sub throw_Error_Simple +{ + my $args = shift; + return Error::Simple->new($args->{'text'}); +} + +$Error::ObjectifyCallback = \&throw_Error_Simple; + + +# Exported subs are defined in Error::subs + +use Scalar::Util (); + +sub import { + shift; + local $Exporter::ExportLevel = $Exporter::ExportLevel + 1; + Error::subs->import(@_); +} + +# I really want to use last for the name of this method, but it is a keyword +# which prevent the syntax last Error + +sub prior { + shift; # ignore + + return $LAST unless @_; + + my $pkg = shift; + return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef + unless ref($pkg); + + my $obj = $pkg; + my $err = undef; + if($obj->isa('HASH')) { + $err = $obj->{'__Error__'} + if exists $obj->{'__Error__'}; + } + elsif($obj->isa('GLOB')) { + $err = ${*$obj}{'__Error__'} + if exists ${*$obj}{'__Error__'}; + } + + $err; +} + +sub flush { + shift; #ignore + + unless (@_) { + $LAST = undef; + return; + } + + my $pkg = shift; + return unless ref($pkg); + + undef $ERROR{$pkg} if defined $ERROR{$pkg}; +} + +# Return as much information as possible about where the error +# happened. The -stacktrace element only exists if $Error::DEBUG +# was set when the error was created + +sub stacktrace { + my $self = shift; + + return $self->{'-stacktrace'} + if exists $self->{'-stacktrace'}; + + my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died"; + + $text .= sprintf(" at %s line %d.\n", $self->file, $self->line) + unless($text =~ /\n$/s); + + $text; +} + +# Allow error propagation, ie +# +# $ber->encode(...) or +# return Error->prior($ber)->associate($ldap); + +sub associate { + my $err = shift; + my $obj = shift; + + return unless ref($obj); + + if($obj->isa('HASH')) { + $obj->{'__Error__'} = $err; + } + elsif($obj->isa('GLOB')) { + ${*$obj}{'__Error__'} = $err; + } + $obj = ref($obj); + $ERROR{ ref($obj) } = $err; + + return; +} + +sub new { + my $self = shift; + my($pkg,$file,$line) = caller($Error::Depth); + + my $err = bless { + '-package' => $pkg, + '-file' => $file, + '-line' => $line, + @_ + }, $self; + + $err->associate($err->{'-object'}) + if(exists $err->{'-object'}); + + # To always create a stacktrace would be very inefficient, so + # we only do it if $Error::Debug is set + + if($Error::Debug) { + require Carp; + local $Carp::CarpLevel = $Error::Depth; + my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error"; + my $trace = Carp::longmess($text); + # Remove try calls from the trace + $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog; + $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog; + $err->{'-stacktrace'} = $trace + } + + $@ = $LAST = $ERROR{$pkg} = $err; +} + +# Throw an error. this contains some very gory code. + +sub throw { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + # if we are not rethrow-ing then create the object to throw + $self = $self->new(@_) unless ref($self); + + die $Error::THROWN = $self; +} + +# syntactic sugar for +# +# die with Error( ... ); + +sub with { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + $self->new(@_); +} + +# syntactic sugar for +# +# record Error( ... ) and return; + +sub record { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + $self->new(@_); +} + +# catch clause for +# +# try { ... } catch CLASS with { ... } + +sub catch { + my $pkg = shift; + my $code = shift; + my $clauses = shift || {}; + my $catch = $clauses->{'catch'} ||= []; + + unshift @$catch, $pkg, $code; + + $clauses; +} + +# Object query methods + +sub object { + my $self = shift; + exists $self->{'-object'} ? $self->{'-object'} : undef; +} + +sub file { + my $self = shift; + exists $self->{'-file'} ? $self->{'-file'} : undef; +} + +sub line { + my $self = shift; + exists $self->{'-line'} ? $self->{'-line'} : undef; +} + +sub text { + my $self = shift; + exists $self->{'-text'} ? $self->{'-text'} : undef; +} + +# overload methods + +sub stringify { + my $self = shift; + defined $self->{'-text'} ? $self->{'-text'} : "Died"; +} + +sub value { + my $self = shift; + exists $self->{'-value'} ? $self->{'-value'} : undef; +} + +package Error::Simple; + +@Error::Simple::ISA = qw(Error); + +sub new { + my $self = shift; + my $text = "" . shift; + my $value = shift; + my(@args) = (); + + local $Error::Depth = $Error::Depth + 1; + + @args = ( -file => $1, -line => $2) + if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s); + push(@args, '-value', 0 + $value) + if defined($value); + + $self->SUPER::new(-text => $text, @args); +} + +sub stringify { + my $self = shift; + my $text = $self->SUPER::stringify; + $text .= sprintf(" at %s line %d.\n", $self->file, $self->line) + unless($text =~ /\n$/s); + $text; +} + +########################################################################## +########################################################################## + +# Inspired by code from Jesse Glick and +# Peter Seibel + +package Error::subs; + +use Exporter (); +use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS); + +@EXPORT_OK = qw(try with finally except otherwise); +%EXPORT_TAGS = (try => \@EXPORT_OK); + +@ISA = qw(Exporter); + +sub run_clauses ($$$\@) { + my($clauses,$err,$wantarray,$result) = @_; + my $code = undef; + + $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err); + + CATCH: { + + # catch + my $catch; + if(defined($catch = $clauses->{'catch'})) { + my $i = 0; + + CATCHLOOP: + for( ; $i < @$catch ; $i += 2) { + my $pkg = $catch->[$i]; + unless(defined $pkg) { + #except + splice(@$catch,$i,2,$catch->[$i+1]->()); + $i -= 2; + next CATCHLOOP; + } + elsif(Scalar::Util::blessed($err) && $err->isa($pkg)) { + $code = $catch->[$i+1]; + while(1) { + my $more = 0; + local($Error::THROWN); + my $ok = eval { + if($wantarray) { + @{$result} = $code->($err,\$more); + } + elsif(defined($wantarray)) { + @{$result} = (); + $result->[0] = $code->($err,\$more); + } + else { + $code->($err,\$more); + } + 1; + }; + if( $ok ) { + next CATCHLOOP if $more; + undef $err; + } + else { + $err = defined($Error::THROWN) + ? $Error::THROWN : $@; + $err = $Error::ObjectifyCallback->({'text' =>$err}) + unless ref($err); + } + last CATCH; + }; + } + } + } + + # otherwise + my $owise; + if(defined($owise = $clauses->{'otherwise'})) { + my $code = $clauses->{'otherwise'}; + my $more = 0; + my $ok = eval { + if($wantarray) { + @{$result} = $code->($err,\$more); + } + elsif(defined($wantarray)) { + @{$result} = (); + $result->[0] = $code->($err,\$more); + } + else { + $code->($err,\$more); + } + 1; + }; + if( $ok ) { + undef $err; + } + else { + $err = defined($Error::THROWN) + ? $Error::THROWN : $@; + + $err = $Error::ObjectifyCallback->({'text' =>$err}) + unless ref($err); + } + } + } + $err; +} + +sub try (&;$) { + my $try = shift; + my $clauses = @_ ? shift : {}; + my $ok = 0; + my $err = undef; + my @result = (); + + unshift @Error::STACK, $clauses; + + my $wantarray = wantarray(); + + do { + local $Error::THROWN = undef; + local $@ = undef; + + $ok = eval { + if($wantarray) { + @result = $try->(); + } + elsif(defined $wantarray) { + $result[0] = $try->(); + } + else { + $try->(); + } + 1; + }; + + $err = defined($Error::THROWN) ? $Error::THROWN : $@ + unless $ok; + }; + + shift @Error::STACK; + + $err = run_clauses($clauses,$err,wantarray,@result) + unless($ok); + + $clauses->{'finally'}->() + if(defined($clauses->{'finally'})); + + if (defined($err)) + { + if (Scalar::Util::blessed($err) && $err->can('throw')) + { + throw $err; + } + else + { + die $err; + } + } + + wantarray ? @result : $result[0]; +} + +# Each clause adds a sub to the list of clauses. The finally clause is +# always the last, and the otherwise clause is always added just before +# the finally clause. +# +# All clauses, except the finally clause, add a sub which takes one argument +# this argument will be the error being thrown. The sub will return a code ref +# if that clause can handle that error, otherwise undef is returned. +# +# The otherwise clause adds a sub which unconditionally returns the users +# code reference, this is why it is forced to be last. +# +# The catch clause is defined in Error.pm, as the syntax causes it to +# be called as a method + +sub with (&;$) { + @_ +} + +sub finally (&) { + my $code = shift; + my $clauses = { 'finally' => $code }; + $clauses; +} + +# The except clause is a block which returns a hashref or a list of +# key-value pairs, where the keys are the classes and the values are subs. + +sub except (&;$) { + my $code = shift; + my $clauses = shift || {}; + my $catch = $clauses->{'catch'} ||= []; + + my $sub = sub { + my $ref; + my(@array) = $code->($_[0]); + if(@array == 1 && ref($array[0])) { + $ref = $array[0]; + $ref = [ %$ref ] + if(UNIVERSAL::isa($ref,'HASH')); + } + else { + $ref = \@array; + } + @$ref + }; + + unshift @{$catch}, undef, $sub; + + $clauses; +} + +sub otherwise (&;$) { + my $code = shift; + my $clauses = shift || {}; + + if(exists $clauses->{'otherwise'}) { + require Carp; + Carp::croak("Multiple otherwise clauses"); + } + + $clauses->{'otherwise'} = $code; + + $clauses; +} + +1; +__END__ + +=head1 NAME + +Error - Error/exception handling in an OO-ish way + +=head1 SYNOPSIS + + use Error qw(:try); + + throw Error::Simple( "A simple error"); + + sub xyz { + ... + record Error::Simple("A simple error") + and return; + } + + unlink($file) or throw Error::Simple("$file: $!",$!); + + try { + do_some_stuff(); + die "error!" if $condition; + throw Error::Simple -text => "Oops!" if $other_condition; + } + catch Error::IO with { + my $E = shift; + print STDERR "File ", $E->{'-file'}, " had a problem\n"; + } + except { + my $E = shift; + my $general_handler=sub {send_message $E->{-description}}; + return { + UserException1 => $general_handler, + UserException2 => $general_handler + }; + } + otherwise { + print STDERR "Well I don't know what to say\n"; + } + finally { + close_the_garage_door_already(); # Should be reliable + }; # Don't forget the trailing ; or you might be surprised + +=head1 DESCRIPTION + +The C package provides two interfaces. Firstly C provides +a procedural interface to exception handling. Secondly C is a +base class for errors/exceptions that can either be thrown, for +subsequent catch, or can simply be recorded. + +Errors in the class C should not be thrown directly, but the +user should throw errors from a sub-class of C. + +=head1 PROCEDURAL INTERFACE + +C exports subroutines to perform exception handling. These will +be exported if the C<:try> tag is used in the C line. + +=over 4 + +=item try BLOCK CLAUSES + +C is the main subroutine called by the user. All other subroutines +exported are clauses to the try subroutine. + +The BLOCK will be evaluated and, if no error is throw, try will return +the result of the block. + +C are the subroutines below, which describe what to do in the +event of an error being thrown within BLOCK. + +=item catch CLASS with BLOCK + +This clauses will cause all errors that satisfy C<$err-Eisa(CLASS)> +to be caught and handled by evaluating C. + +C will be passed two arguments. The first will be the error +being thrown. The second is a reference to a scalar variable. If this +variable is set by the catch block then, on return from the catch +block, try will continue processing as if the catch block was never +found. + +To propagate the error the catch block may call C<$err-Ethrow> + +If the scalar reference by the second argument is not set, and the +error is not thrown. Then the current try block will return with the +result from the catch block. + +=item except BLOCK + +When C is looking for a handler, if an except clause is found +C is evaluated. The return value from this block should be a +HASHREF or a list of key-value pairs, where the keys are class names +and the values are CODE references for the handler of errors of that +type. + +=item otherwise BLOCK + +Catch any error by executing the code in C + +When evaluated C will be passed one argument, which will be the +error being processed. + +Only one otherwise block may be specified per try block + +=item finally BLOCK + +Execute the code in C either after the code in the try block has +successfully completed, or if the try block throws an error then +C will be executed after the handler has completed. + +If the handler throws an error then the error will be caught, the +finally block will be executed and the error will be re-thrown. + +Only one finally block may be specified per try block + +=back + +=head1 CLASS INTERFACE + +=head2 CONSTRUCTORS + +The C object is implemented as a HASH. This HASH is initialized +with the arguments that are passed to it's constructor. The elements +that are used by, or are retrievable by the C class are listed +below, other classes may add to these. + + -file + -line + -text + -value + -object + +If C<-file> or C<-line> are not specified in the constructor arguments +then these will be initialized with the file name and line number where +the constructor was called from. + +If the error is associated with an object then the object should be +passed as the C<-object> argument. This will allow the C package +to associate the error with the object. + +The C package remembers the last error created, and also the +last error associated with a package. This could either be the last +error created by a sub in that package, or the last error which passed +an object blessed into that package as the C<-object> argument. + +=over 4 + +=item throw ( [ ARGS ] ) + +Create a new C object and throw an error, which will be caught +by a surrounding C block, if there is one. Otherwise it will cause +the program to exit. + +C may also be called on an existing error to re-throw it. + +=item with ( [ ARGS ] ) + +Create a new C object and returns it. This is defined for +syntactic sugar, eg + + die with Some::Error ( ... ); + +=item record ( [ ARGS ] ) + +Create a new C object and returns it. This is defined for +syntactic sugar, eg + + record Some::Error ( ... ) + and return; + +=back + +=head2 STATIC METHODS + +=over 4 + +=item prior ( [ PACKAGE ] ) + +Return the last error created, or the last error associated with +C + +=item flush ( [ PACKAGE ] ) + +Flush the last error created, or the last error associated with +C.It is necessary to clear the error stack before exiting the +package or uncaught errors generated using C will be reported. + + $Error->flush; + +=cut + +=back + +=head2 OBJECT METHODS + +=over 4 + +=item stacktrace + +If the variable C<$Error::Debug> was non-zero when the error was +created, then C returns a string created by calling +C. If the variable was zero the C returns +the text of the error appended with the filename and line number of +where the error was created, providing the text does not end with a +newline. + +=item object + +The object this error was associated with + +=item file + +The file where the constructor of this error was called from + +=item line + +The line where the constructor of this error was called from + +=item text + +The text of the error + +=back + +=head2 OVERLOAD METHODS + +=over 4 + +=item stringify + +A method that converts the object into a string. This method may simply +return the same as the C method, or it may append more +information. For example the file name and line number. + +By default this method returns the C<-text> argument that was passed to +the constructor, or the string C<"Died"> if none was given. + +=item value + +A method that will return a value that can be associated with the +error. For example if an error was created due to the failure of a +system call, then this may return the numeric value of C<$!> at the +time. + +By default this method returns the C<-value> argument that was passed +to the constructor. + +=back + +=head1 PRE-DEFINED ERROR CLASSES + +=over 4 + +=item Error::Simple + +This class can be used to hold simple error strings and values. It's +constructor takes two arguments. The first is a text value, the second +is a numeric value. These values are what will be returned by the +overload methods. + +If the text value ends with C as $@ strings do, then +this infomation will be used to set the C<-file> and C<-line> arguments +of the error object. + +This class is used internally if an eval'd block die's with an error +that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified) + +=back + +=head1 $Error::ObjectifyCallback + +This variable holds a reference to a subroutine that converts errors that +are plain strings to objects. It is used by Error.pm to convert textual +errors to objects, and can be overrided by the user. + +It accepts a single argument which is a hash reference to named parameters. +Currently the only named parameter passed is C<'text'> which is the text +of the error, but others may be available in the future. + +For example the following code will cause Error.pm to throw objects of the +class MyError::Bar by default: + + sub throw_MyError_Bar + { + my $args = shift; + my $err = MyError::Bar->new(); + $err->{'MyBarText'} = $args->{'text'}; + return $err; + } + + { + local $Error::ObjectifyCallback = \&throw_MyError_Bar; + + # Error handling here. + } + +=head1 KNOWN BUGS + +None, but that does not mean there are not any. + +=head1 AUTHORS + +Graham Barr + +The code that inspired me to write this was originally written by +Peter Seibel and adapted by Jesse Glick +. + +=head1 MAINTAINER + +Shlomi Fish + +=head1 PAST MAINTAINERS + +Arun Kumar U + +=cut diff --git a/perl/Makefile.PL b/perl/Makefile.PL index dd61056..54e8b20 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -8,9 +8,19 @@ instlibdir: MAKE_FRAG } +my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm'); + +# We come with our own bundled Error.pm. It's not in the set of default +# Perl modules so install it if it's not available on the system yet. +eval { require 'Error' }; +if ($@) { + $pm{'Error.pm'} = '$(INST_LIBDIR)/Error.pm'; +} + WriteMakefile( NAME => 'Git', VERSION_FROM => 'Git.pm', + PM => \%pm, MYEXTLIB => '../libgit.a', INC => '-I. -I..', ); -- cgit v0.10.2-6-g49f6 From 97b16c067492506287a6f474e79ef6cbe0a30e49 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:42 +0200 Subject: Git.pm: Better error handling So far, errors just killed the whole program and in case of an error inside of libgit it would be totally uncatchable. This patch makes Git.pm throw standard Perl exceptions instead. In the future we might subclass Error to Git::Error or something but for now Error::Simple is more than enough. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index dcd769b..733fec9 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -88,7 +88,8 @@ increate nonwithstanding). =cut -use Carp qw(carp croak); +use Carp qw(carp); # croak is bad - throw instead +use Error qw(:try); require XSLoader; XSLoader::load('Git', $VERSION); @@ -143,14 +144,14 @@ sub repository { if (defined $args[0]) { if ($#args % 2 != 1) { # Not a hash. - $#args == 0 or croak "bad usage"; - %opts = (Directory => $args[0]); + $#args == 0 or throw Error::Simple("bad usage"); + %opts = ( Directory => $args[0] ); } else { %opts = @args; } if ($opts{Directory}) { - -d $opts{Directory} or croak "Directory not found: $!"; + -d $opts{Directory} or throw Error::Simple("Directory not found: $!"); if (-d $opts{Directory}."/.git") { # TODO: Might make this more clever $opts{WorkingCopy} = $opts{Directory}; @@ -242,11 +243,11 @@ read. sub command_pipe { my ($self, $cmd, @args) = _maybe_self(@_); - $cmd =~ /^[a-z0-9A-Z_-]+$/ or croak "bad command: $cmd"; + $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd"); my $pid = open(my $fh, "-|"); if (not defined $pid) { - croak "open failed: $!"; + throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { _cmd_exec($self, $cmd, @args); } @@ -271,16 +272,17 @@ The function returns only after the command has finished running. sub command_noisy { my ($self, $cmd, @args) = _maybe_self(@_); - $cmd =~ /^[a-z0-9A-Z_-]+$/ or croak "bad command: $cmd"; + $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd"); my $pid = fork; if (not defined $pid) { - croak "fork failed: $!"; + throw Error::Simple("fork failed: $!"); } elsif ($pid == 0) { _cmd_exec($self, $cmd, @args); } if (waitpid($pid, 0) > 0 and $? != 0) { - croak "exit status: $?"; + # This is the best candidate for a custom exception class. + throw Error::Simple("exit status: $?"); } } @@ -340,10 +342,10 @@ are involved. =back -=head1 TODO +=head1 ERROR HANDLING -This is still fairly crude. -We need some good way to report errors back except just dying. +All functions are supposed to throw Perl exceptions in case of errors. +See L. =head1 COPYRIGHT @@ -372,8 +374,8 @@ sub _cmd_exec { $self->{opts}->{Repository} and $ENV{'GIT_DIR'} = $self->{opts}->{Repository}; $self->{opts}->{WorkingCopy} and chdir($self->{opts}->{WorkingCopy}); } - xs__execv_git_cmd(@args); - croak "exec failed: $!"; + _execv_git_cmd(@args); + die "exec failed: $!"; } # Execute the given Git command ($_[0]) with arguments ($_[1..]) @@ -388,7 +390,8 @@ sub _cmd_close { # It's just close, no point in fatalities carp "error closing pipe: $!"; } elsif ($? >> 8) { - croak "exit status: ".($? >> 8); + # This is the best candidate for a custom exception class. + throw Error::Simple("exit status: ".($? >> 8)); } # else we might e.g. closed a live stream; the command # dying of SIGPIPE would drive us here. @@ -415,6 +418,8 @@ sub _call_gate { #xs_call_gate($self->{opts}->{Repository}); } + # Having to call throw from the C code is a sure path to insanity. + local $SIG{__DIE__} = sub { throw Error::Simple("@_"); }; &$xsfunc(@args); } @@ -422,7 +427,7 @@ sub AUTOLOAD { my $xsname; our $AUTOLOAD; ($xsname = $AUTOLOAD) =~ s/.*:://; - croak "&Git::$xsname not defined" if $xsname =~ /^xs_/; + throw Error::Simple("&Git::$xsname not defined") if $xsname =~ /^xs_/; $xsname = 'xs_'.$xsname; _call_gate(\&$xsname, @_); } diff --git a/perl/Git.xs b/perl/Git.xs index d4608eb..9d247b7 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -8,6 +8,8 @@ #include "../cache.h" #include "../exec_cmd.h" +#define die perlyshadow_die__ + /* XS and Perl interface */ #include "EXTERN.h" #include "perl.h" @@ -15,11 +17,48 @@ #include "ppport.h" +#undef die + + +static char * +report_xs(const char *prefix, const char *err, va_list params) +{ + static char buf[4096]; + strcpy(buf, prefix); + vsnprintf(buf + strlen(prefix), 4096 - strlen(prefix), err, params); + return buf; +} + +void +die_xs(const char *err, va_list params) +{ + char *str; + str = report_xs("fatal: ", err, params); + croak(str); +} + +int +error_xs(const char *err, va_list params) +{ + char *str; + str = report_xs("error: ", err, params); + warn(str); + return -1; +} + MODULE = Git PACKAGE = Git PROTOTYPES: DISABLE + +BOOT: +{ + set_error_routine(error_xs); + set_die_routine(die_xs); +} + + # /* TODO: xs_call_gate(). See Git.pm. */ -- cgit v0.10.2-6-g49f6 From 8b9150e3e3cc6bf78b21b2e01dcc5e3ed45597a4 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:44 +0200 Subject: Git.pm: Handle failed commands' output Currently if an external command returns error exit code, a generic exception is thrown and there is no chance for the caller to retrieve the command's output. This patch introduces a Git::Error::Command exception class which is thrown in this case and contains both the error code and the captured command output. You can use the new git_cmd_try statement to fatally catch the exception while producing a user-friendly message. It also adds command_close_pipe() for easier checking of exit status of a command we have just a pipe handle of. It has partial forward dependency on the next patch, but basically only in the area of documentation. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl index be2a48c..f86231e 100755 --- a/git-fmt-merge-msg.perl +++ b/git-fmt-merge-msg.perl @@ -7,6 +7,7 @@ use strict; use Git; +use Error qw(:try); my $repo = Git->repository(); @@ -31,7 +32,17 @@ sub andjoin { } sub repoconfig { - my ($val) = $repo->command_oneline('repo-config', '--get', 'merge.summary'); + my $val; + try { + $val = $repo->command_oneline('repo-config', '--get', 'merge.summary'); + } catch Git::Error::Command with { + my ($E) = shift; + if ($E->value() == 1) { + return undef; + } else { + throw $E; + } + }; return $val; } diff --git a/perl/Git.pm b/perl/Git.pm index 733fec9..4205ac5 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -24,16 +24,17 @@ $VERSION = '0.01'; my $version = Git::command_oneline('version'); - Git::command_noisy('update-server-info'); + git_cmd_try { Git::command_noisy('update-server-info') } + '%s failed w/ code %d'; my $repo = Git->repository (Directory => '/srv/git/cogito.git'); my @revs = $repo->command('rev-list', '--since=last monday', '--all'); - my $fh = $repo->command_pipe('rev-list', '--since=last monday', '--all'); + my ($fh, $c) = $repo->command_pipe('rev-list', '--since=last monday', '--all'); my $lastrev = <$fh>; chomp $lastrev; - close $fh; # You may want to test rev-list exit status here + $repo->command_close_pipe($fh, $c); my $lastrev = $repo->command_oneline('rev-list', '--all'); @@ -44,11 +45,11 @@ require Exporter; @ISA = qw(Exporter); -@EXPORT = qw(); +@EXPORT = qw(git_cmd_try); # Methods which can be called as standalone functions as well: @EXPORT_OK = qw(command command_oneline command_pipe command_noisy - version exec_path hash_object); + version exec_path hash_object git_cmd_try); =head1 DESCRIPTION @@ -88,7 +89,7 @@ increate nonwithstanding). =cut -use Carp qw(carp); # croak is bad - throw instead +use Carp qw(carp croak); # but croak is bad - throw instead use Error qw(:try); require XSLoader; @@ -193,21 +194,35 @@ In both cases, the command's stdin and stderr are the same as the caller's. =cut sub command { - my $fh = command_pipe(@_); + my ($fh, $ctx) = command_pipe(@_); if (not defined wantarray) { - _cmd_close($fh); + # Nothing to pepper the possible exception with. + _cmd_close($fh, $ctx); } elsif (not wantarray) { local $/; my $text = <$fh>; - _cmd_close($fh); + try { + _cmd_close($fh, $ctx); + } catch Git::Error::Command with { + # Pepper with the output: + my $E = shift; + $E->{'-outputref'} = \$text; + throw $E; + }; return $text; } else { my @lines = <$fh>; - _cmd_close($fh); chomp @lines; + try { + _cmd_close($fh, $ctx); + } catch Git::Error::Command with { + my $E = shift; + $E->{'-outputref'} = \@lines; + throw $E; + }; return @lines; } } @@ -222,12 +237,18 @@ of the command's standard output. =cut sub command_oneline { - my $fh = command_pipe(@_); + my ($fh, $ctx) = command_pipe(@_); my $line = <$fh>; - _cmd_close($fh); - chomp $line; + try { + _cmd_close($fh, $ctx); + } catch Git::Error::Command with { + # Pepper with the output: + my $E = shift; + $E->{'-outputref'} = \$line; + throw $E; + }; return $line; } @@ -251,7 +272,32 @@ sub command_pipe { } elsif ($pid == 0) { _cmd_exec($self, $cmd, @args); } - return $fh; + return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh; +} + + +=item command_close_pipe ( PIPE [, CTX ] ) + +Close the C as returned from C, checking +whether the command finished successfuly. The optional C argument +is required if you want to see the command name in the error message, +and it is the second value returned by C when +called in array context. The call idiom is: + + my ($fh, $ctx) = $r->command_pipe('status'); + while (<$fh>) { ... } + $r->command_close_pipe($fh, $ctx); + +Note that you should not rely on whatever actually is in C; +currently it is simply the command name but in future the context might +have more complicated structure. + +=cut + +sub command_close_pipe { + my ($self, $fh, $ctx) = _maybe_self(@_); + $ctx ||= ''; + _cmd_close($fh, $ctx); } @@ -280,9 +326,8 @@ sub command_noisy { } elsif ($pid == 0) { _cmd_exec($self, $cmd, @args); } - if (waitpid($pid, 0) > 0 and $? != 0) { - # This is the best candidate for a custom exception class. - throw Error::Simple("exit status: $?"); + if (waitpid($pid, 0) > 0 and $?>>8 != 0) { + throw Git::Error::Command(join(' ', $cmd, @args), $? >> 8); } } @@ -340,12 +385,117 @@ are involved. # Implemented in Git.xs. + =back =head1 ERROR HANDLING All functions are supposed to throw Perl exceptions in case of errors. -See L. +See the L module on how to catch those. Most exceptions are mere +L instances. + +However, the C, C and C +functions suite can throw C exceptions as well: those are +thrown when the external command returns an error code and contain the error +code as well as access to the captured command's output. The exception class +provides the usual C and C (command's exit code) methods and +in addition also a C method that returns either an array or a +string with the captured command output (depending on the original function +call context; C returns C) and $ which +returns the command and its arguments (but without proper quoting). + +Note that the C function cannot throw this exception since +it has no idea whether the command failed or not. You will only find out +at the time you C the pipe; if you want to have that automated, +use C, which can throw the exception. + +=cut + +{ + package Git::Error::Command; + + @Git::Error::Command::ISA = qw(Error); + + sub new { + my $self = shift; + my $cmdline = '' . shift; + my $value = 0 + shift; + my $outputref = shift; + my(@args) = (); + + local $Error::Depth = $Error::Depth + 1; + + push(@args, '-cmdline', $cmdline); + push(@args, '-value', $value); + push(@args, '-outputref', $outputref); + + $self->SUPER::new(-text => 'command returned error', @args); + } + + sub stringify { + my $self = shift; + my $text = $self->SUPER::stringify; + $self->cmdline() . ': ' . $text . ': ' . $self->value() . "\n"; + } + + sub cmdline { + my $self = shift; + $self->{'-cmdline'}; + } + + sub cmd_output { + my $self = shift; + my $ref = $self->{'-outputref'}; + defined $ref or undef; + if (ref $ref eq 'ARRAY') { + return @$ref; + } else { # SCALAR + return $$ref; + } + } +} + +=over 4 + +=item git_cmd_try { CODE } ERRMSG + +This magical statement will automatically catch any C +exceptions thrown by C and make your program die with C +on its lips; the message will have %s substituted for the command line +and %d for the exit status. This statement is useful mostly for producing +more user-friendly error messages. + +In case of no exception caught the statement returns C's return value. + +Note that this is the only auto-exported function. + +=cut + +sub git_cmd_try(&$) { + my ($code, $errmsg) = @_; + my @result; + my $err; + my $array = wantarray; + try { + if ($array) { + @result = &$code; + } else { + $result[0] = &$code; + } + } catch Git::Error::Command with { + my $E = shift; + $err = $errmsg; + $err =~ s/\%s/$E->cmdline()/ge; + $err =~ s/\%d/$E->value()/ge; + # We can't croak here since Error.pm would mangle + # that to Error::Simple. + }; + $err and croak $err; + return $array ? @result : $result[0]; +} + + +=back =head1 COPYRIGHT @@ -384,14 +534,14 @@ sub _cmd_exec { # Close pipe to a subprocess. sub _cmd_close { - my ($fh) = @_; + my ($fh, $ctx) = @_; if (not close $fh) { if ($!) { # It's just close, no point in fatalities carp "error closing pipe: $!"; } elsif ($? >> 8) { - # This is the best candidate for a custom exception class. - throw Error::Simple("exit status: ".($? >> 8)); + # The caller should pepper this. + throw Git::Error::Command($ctx, $? >> 8); } # else we might e.g. closed a live stream; the command # dying of SIGPIPE would drive us here. -- cgit v0.10.2-6-g49f6 From d79850e1fd22abd44e447214a64e5a774ada311e Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:47 +0200 Subject: Git.pm: Enhance the command_pipe() mechanism Rename command_pipe() to command_output_pipe(), outsource the functionality to _command_common_pipe(). Add command_input_pipe(). Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 4205ac5..11ec62d 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -32,7 +32,7 @@ $VERSION = '0.01'; my @revs = $repo->command('rev-list', '--since=last monday', '--all'); - my ($fh, $c) = $repo->command_pipe('rev-list', '--since=last monday', '--all'); + my ($fh, $c) = $repo->command_output_pipe('rev-list', '--since=last monday', '--all'); my $lastrev = <$fh>; chomp $lastrev; $repo->command_close_pipe($fh, $c); @@ -48,7 +48,8 @@ require Exporter; @EXPORT = qw(git_cmd_try); # Methods which can be called as standalone functions as well: -@EXPORT_OK = qw(command command_oneline command_pipe command_noisy +@EXPORT_OK = qw(command command_oneline command_noisy + command_output_pipe command_input_pipe command_close_pipe version exec_path hash_object git_cmd_try); @@ -194,7 +195,7 @@ In both cases, the command's stdin and stderr are the same as the caller's. =cut sub command { - my ($fh, $ctx) = command_pipe(@_); + my ($fh, $ctx) = command_output_pipe(@_); if (not defined wantarray) { # Nothing to pepper the possible exception with. @@ -237,7 +238,7 @@ of the command's standard output. =cut sub command_oneline { - my ($fh, $ctx) = command_pipe(@_); + my ($fh, $ctx) = command_output_pipe(@_); my $line = <$fh>; chomp $line; @@ -253,40 +254,49 @@ sub command_oneline { } -=item command_pipe ( COMMAND [, ARGUMENTS... ] ) +=item command_output_pipe ( COMMAND [, ARGUMENTS... ] ) Execute the given C in the same way as command() does but return a pipe filehandle from which the command output can be read. +The function can return C<($pipe, $ctx)> in array context. +See C for details. + =cut -sub command_pipe { - my ($self, $cmd, @args) = _maybe_self(@_); +sub command_output_pipe { + _command_common_pipe('-|', @_); +} - $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd"); - my $pid = open(my $fh, "-|"); - if (not defined $pid) { - throw Error::Simple("open failed: $!"); - } elsif ($pid == 0) { - _cmd_exec($self, $cmd, @args); - } - return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh; +=item command_input_pipe ( COMMAND [, ARGUMENTS... ] ) + +Execute the given C in the same way as command_output_pipe() +does but return an input pipe filehandle instead; the command output +is not captured. + +The function can return C<($pipe, $ctx)> in array context. +See C for details. + +=cut + +sub command_input_pipe { + _command_common_pipe('|-', @_); } =item command_close_pipe ( PIPE [, CTX ] ) -Close the C as returned from C, checking +Close the C as returned from C, checking whether the command finished successfuly. The optional C argument is required if you want to see the command name in the error message, -and it is the second value returned by C when +and it is the second value returned by C when called in array context. The call idiom is: - my ($fh, $ctx) = $r->command_pipe('status'); - while (<$fh>) { ... } - $r->command_close_pipe($fh, $ctx); + my ($fh, $ctx) = $r->command_output_pipe('status'); + while (<$fh>) { ... } + $r->command_close_pipe($fh, $ctx); Note that you should not rely on whatever actually is in C; currently it is simply the command name but in future the context might @@ -317,8 +327,7 @@ The function returns only after the command has finished running. sub command_noisy { my ($self, $cmd, @args) = _maybe_self(@_); - - $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd"); + _check_valid_cmd($cmd); my $pid = fork; if (not defined $pid) { @@ -404,7 +413,7 @@ string with the captured command output (depending on the original function call context; C returns C) and $ which returns the command and its arguments (but without proper quoting). -Note that the C function cannot throw this exception since +Note that the C functions cannot throw this exception since it has no idea whether the command failed or not. You will only find out at the time you C the pipe; if you want to have that automated, use C, which can throw the exception. @@ -516,6 +525,27 @@ sub _maybe_self { ref $_[0] eq 'Git' ? @_ : (undef, @_); } +# Check if the command id is something reasonable. +sub _check_valid_cmd { + my ($cmd) = @_; + $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd"); +} + +# Common backend for the pipe creators. +sub _command_common_pipe { + my $direction = shift; + my ($self, $cmd, @args) = _maybe_self(@_); + _check_valid_cmd($cmd); + + my $pid = open(my $fh, $direction); + if (not defined $pid) { + throw Error::Simple("open failed: $!"); + } elsif ($pid == 0) { + _cmd_exec($self, $cmd, @args); + } + return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh; +} + # When already in the subprocess, set up the appropriate state # for the given repository and execute the git command. sub _cmd_exec { -- cgit v0.10.2-6-g49f6 From d43ba4680754c150124b6ac3cd9c6e52765c6881 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:49 +0200 Subject: Git.pm: Implement options for the command interface This gives the user a way to easily pass options to the command routines. Currently only the STDERR option is implemented and can be used to adjust what shall be done with error output of the called command (most usefully, it can be used to silence it). Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 11ec62d..e2b66c4 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -36,7 +36,8 @@ $VERSION = '0.01'; my $lastrev = <$fh>; chomp $lastrev; $repo->command_close_pipe($fh, $c); - my $lastrev = $repo->command_oneline('rev-list', '--all'); + my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ], + STDERR => 0 ); =cut @@ -178,9 +179,21 @@ sub repository { =item command ( COMMAND [, ARGUMENTS... ] ) +=item command ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } ) + Execute the given Git C (specify it without the 'git-' prefix), optionally with the specified extra C. +The second more elaborate form can be used if you want to further adjust +the command execution. Currently, only one option is supported: + +B - How to deal with the command's error output. By default (C) +it is delivered to the caller's C. A false value (0 or '') will cause +it to be thrown away. If you want to process it, you can get it in a filehandle +you specify, but you must be extremely careful; if the error output is not +very short and you want to read it in the same process as where you called +C, you are set up for a nice deadlock! + The method can be called without any instance or on a specified Git repository (in that case the command will be run in the repository context). @@ -231,6 +244,8 @@ sub command { =item command_oneline ( COMMAND [, ARGUMENTS... ] ) +=item command_oneline ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } ) + Execute the given C in the same way as command() does but always return a scalar string containing the first line of the command's standard output. @@ -256,6 +271,8 @@ sub command_oneline { =item command_output_pipe ( COMMAND [, ARGUMENTS... ] ) +=item command_output_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } ) + Execute the given C in the same way as command() does but return a pipe filehandle from which the command output can be read. @@ -272,6 +289,8 @@ sub command_output_pipe { =item command_input_pipe ( COMMAND [, ARGUMENTS... ] ) +=item command_input_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } ) + Execute the given C in the same way as command_output_pipe() does but return an input pipe filehandle instead; the command output is not captured. @@ -534,13 +553,27 @@ sub _check_valid_cmd { # Common backend for the pipe creators. sub _command_common_pipe { my $direction = shift; - my ($self, $cmd, @args) = _maybe_self(@_); + my ($self, @p) = _maybe_self(@_); + my (%opts, $cmd, @args); + if (ref $p[0]) { + ($cmd, @args) = @{shift @p}; + %opts = ref $p[0] ? %{$p[0]} : @p; + } else { + ($cmd, @args) = @p; + } _check_valid_cmd($cmd); my $pid = open(my $fh, $direction); if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { + if (defined $opts{STDERR}) { + close STDERR; + } + if ($opts{STDERR}) { + open (STDERR, '>&', $opts{STDERR}) + or die "dup failed: $!"; + } _cmd_exec($self, $cmd, @args); } return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh; -- cgit v0.10.2-6-g49f6 From d5c7721d586225c46c675b893b7693220e28cfd5 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:51 +0200 Subject: Git.pm: Add support for subdirectories inside of working copies This patch adds support for subdirectories inside of working copies; you can specify them in the constructor either as the Directory option (it will just get autodetected using rev-parse) or explicitly using the WorkingSubdir option. This makes Git->repository() do the exact same path setup and repository lookup as the Git porcelain does. This patch also introduces repo_path(), wc_path() and wc_subdir() accessor methods and wc_chdir() mutator. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index e2b66c4..7bbb5be 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -69,20 +69,18 @@ means getting an instance of the Git object using the repository() constructor. called as methods of the object are then executed in the context of the repository. -TODO: In the future, we might also do +Part of the "repository state" is also information about path to the attached +working copy (unless you work with a bare repository). You can also navigate +inside of the working copy using the C method. (Note that +the repository object is self-contained and will not change working directory +of your process.) - my $subdir = $repo->subdir('Documentation'); - # Gets called in the subdirectory context: - $subdir->command('status'); +TODO: In the future, we might also do my $remoterepo = $repo->remote_repository (Name => 'cogito', Branch => 'master'); $remoterepo ||= Git->remote_repository ('http://git.or.cz/cogito.git/'); my @refs = $remoterepo->refs(); -So far, all functions just die if anything goes wrong. If you don't want that, -make appropriate provisions to catch the possible deaths. Better error recovery -mechanisms will be provided in the future. - Currently, the module merely wraps calls to external Git tools. In the future, it will provide a much faster way to interact with Git by linking directly to libgit. This should be completely opaque to the user, though (performance @@ -93,6 +91,7 @@ increate nonwithstanding). use Carp qw(carp croak); # but croak is bad - throw instead use Error qw(:try); +use Cwd qw(abs_path); require XSLoader; XSLoader::load('Git', $VERSION); @@ -119,12 +118,17 @@ B - Path to the Git repository. B - Path to the associated working copy; not strictly required as many commands will happily crunch on a bare repository. -B - Path to the Git working directory in its usual setup. This -is just for convenient setting of both C and C -at once: If the directory as a C<.git> subdirectory, C is pointed -to the subdirectory and the directory is assumed to be the working copy. -If the directory does not have the subdirectory, C is left -undefined and C is pointed to the directory itself. +B - Subdirectory in the working copy to work inside. +Just left undefined if you do not want to limit the scope of operations. + +B - Path to the Git working directory in its usual setup. +The C<.git> directory is searched in the directory and all the parent +directories; if found, C is set to the directory containing +it and C to the C<.git> directory itself. If no C<.git> +directory was found, the C is assumed to be a bare repository, +C is set to point at it and C is left undefined. +If the C<$GIT_DIR> environment variable is set, things behave as expected +as well. You should not use both C and either of C and C - the results of that are undefined. @@ -134,7 +138,10 @@ to the constructor; it is equivalent to setting only the C option field. Calling the constructor with no options whatsoever is equivalent to -calling it with C<< Directory => '.' >>. +calling it with C<< Directory => '.' >>. In general, if you are building +a standard porcelain command, simply doing C<< Git->repository() >> should +do the right thing and setup the object to reflect exactly where the user +is right now. =cut @@ -152,18 +159,59 @@ sub repository { } else { %opts = @args; } + } + + if (not defined $opts{Repository} and not defined $opts{WorkingCopy}) { + $opts{Directory} ||= '.'; + } + + if ($opts{Directory}) { + -d $opts{Directory} or throw Error::Simple("Directory not found: $!"); + + my $search = Git->repository(WorkingCopy => $opts{Directory}); + my $dir; + try { + $dir = $search->command_oneline(['rev-parse', '--git-dir'], + STDERR => 0); + } catch Git::Error::Command with { + $dir = undef; + }; - if ($opts{Directory}) { - -d $opts{Directory} or throw Error::Simple("Directory not found: $!"); - if (-d $opts{Directory}."/.git") { - # TODO: Might make this more clever - $opts{WorkingCopy} = $opts{Directory}; - $opts{Repository} = $opts{Directory}."/.git"; - } else { - $opts{Repository} = $opts{Directory}; + if ($dir) { + $opts{Repository} = abs_path($dir); + + # If --git-dir went ok, this shouldn't die either. + my $prefix = $search->command_oneline('rev-parse', '--show-prefix'); + $dir = abs_path($opts{Directory}) . '/'; + if ($prefix) { + if (substr($dir, -length($prefix)) ne $prefix) { + throw Error::Simple("rev-parse confused me - $dir does not have trailing $prefix"); + } + substr($dir, -length($prefix)) = ''; } - delete $opts{Directory}; + $opts{WorkingCopy} = $dir; + $opts{WorkingSubdir} = $prefix; + + } else { + # A bare repository? Let's see... + $dir = $opts{Directory}; + + unless (-d "$dir/refs" and -d "$dir/objects" and -e "$dir/HEAD") { + # Mimick git-rev-parse --git-dir error message: + throw Error::Simple('fatal: Not a git repository'); + } + my $search = Git->repository(Repository => $dir); + try { + $search->command('symbolic-ref', 'HEAD'); + } catch Git::Error::Command with { + # Mimick git-rev-parse --git-dir error message: + throw Error::Simple('fatal: Not a git repository'); + } + + $opts{Repository} = abs_path($dir); } + + delete $opts{Directory}; } $self = { opts => \%opts }; @@ -256,7 +304,7 @@ sub command_oneline { my ($fh, $ctx) = command_output_pipe(@_); my $line = <$fh>; - chomp $line; + defined $line and chomp $line; try { _cmd_close($fh, $ctx); } catch Git::Error::Command with { @@ -374,7 +422,7 @@ are involved. =item exec_path () -Return path to the git sub-command executables (the same as +Return path to the Git sub-command executables (the same as C). Useful mostly only internally. Implementation of this function is very fast; no external command calls @@ -385,6 +433,58 @@ are involved. # Implemented in Git.xs. +=item repo_path () + +Return path to the git repository. Must be called on a repository instance. + +=cut + +sub repo_path { $_[0]->{opts}->{Repository} } + + +=item wc_path () + +Return path to the working copy. Must be called on a repository instance. + +=cut + +sub wc_path { $_[0]->{opts}->{WorkingCopy} } + + +=item wc_subdir () + +Return path to the subdirectory inside of a working copy. Must be called +on a repository instance. + +=cut + +sub wc_subdir { $_[0]->{opts}->{WorkingSubdir} ||= '' } + + +=item wc_chdir ( SUBDIR ) + +Change the working copy subdirectory to work within. The C is +relative to the working copy root directory (not the current subdirectory). +Must be called on a repository instance attached to a working copy +and the directory must exist. + +=cut + +sub wc_chdir { + my ($self, $subdir) = @_; + + $self->wc_path() + or throw Error::Simple("bare repository"); + + -d $self->wc_path().'/'.$subdir + or throw Error::Simple("subdir not found: $!"); + # Of course we will not "hold" the subdirectory so anyone + # can delete it now and we will never know. But at least we tried. + + $self->{opts}->{WorkingSubdir} = $subdir; +} + + =item hash_object ( FILENAME [, TYPE ] ) =item hash_object ( FILEHANDLE [, TYPE ] ) @@ -584,8 +684,9 @@ sub _command_common_pipe { sub _cmd_exec { my ($self, @args) = @_; if ($self) { - $self->{opts}->{Repository} and $ENV{'GIT_DIR'} = $self->{opts}->{Repository}; - $self->{opts}->{WorkingCopy} and chdir($self->{opts}->{WorkingCopy}); + $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); + $self->wc_path() and chdir($self->wc_path()); + $self->wc_subdir() and chdir($self->wc_subdir()); } _execv_git_cmd(@args); die "exec failed: $!"; -- cgit v0.10.2-6-g49f6 From 8f00660fc13df9ed22f059f032a65c60168a2057 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 04:34:53 +0200 Subject: Convert git-mv to use Git.pm Fairly straightforward. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/git-mv.perl b/git-mv.perl index 75aa8fe..f1bde43 100755 --- a/git-mv.perl +++ b/git-mv.perl @@ -10,6 +10,7 @@ use warnings; use strict; use Getopt::Std; +use Git; sub usage() { print <= 1 or usage; -my $GIT_DIR = `git rev-parse --git-dir`; -exit 1 if $?; # rev-parse would have given "not a git dir" message. -chomp($GIT_DIR); +my $repo = Git->repository(); my (@srcArgs, @dstArgs, @srcs, @dsts); my ($src, $dst, $base, $dstDir); @@ -62,11 +61,11 @@ else { $dstDir = ""; } -my $subdir_prefix = `git rev-parse --show-prefix`; -chomp($subdir_prefix); +my $subdir_prefix = $repo->wc_subdir(); # run in git base directory, so that git-ls-files lists all revisioned files -chdir "$GIT_DIR/.."; +chdir $repo->wc_path(); +$repo->wc_chdir(''); # normalize paths, needed to compare against versioned files and update-index # also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c" @@ -84,12 +83,10 @@ my (@allfiles,@srcfiles,@dstfiles); my $safesrc; my (%overwritten, %srcForDst); -$/ = "\0"; -open(F, 'git-ls-files -z |') - or die "Failed to open pipe from git-ls-files: " . $!; - -@allfiles = map { chomp; $_; } ; -close(F); +{ + local $/ = "\0"; + @allfiles = $repo->command('ls-files', '-z'); +} my ($i, $bad); @@ -219,28 +216,28 @@ if ($opt_n) { } else { if (@changedfiles) { - open(H, "| git-update-index -z --stdin") - or die "git-update-index failed to update changed files with code $!\n"; + my ($fd, $ctx) = $repo->command_input_pipe('update-index', '-z', '--stdin'); foreach my $fileName (@changedfiles) { - print H "$fileName\0"; + print $fd "$fileName\0"; } - close(H); + git_cmd_try { $repo->command_close_pipe($fd, $ctx); } + 'git-update-index failed to update changed files with code %d'; } if (@addedfiles) { - open(H, "| git-update-index --add -z --stdin") - or die "git-update-index failed to add new names with code $!\n"; + my ($fd, $ctx) = $repo->command_input_pipe('update-index', '--add', '-z', '--stdin'); foreach my $fileName (@addedfiles) { - print H "$fileName\0"; + print $fd "$fileName\0"; } - close(H); + git_cmd_try { $repo->command_close_pipe($fd, $ctx); } + 'git-update-index failed to add new files with code %d'; } if (@deletedfiles) { - open(H, "| git-update-index --remove -z --stdin") - or die "git-update-index failed to remove old names with code $!\n"; + my ($fd, $ctx) = $repo->command_input_pipe('update-index', '--remove', '-z', '--stdin'); foreach my $fileName (@deletedfiles) { - print H "$fileName\0"; + print $fd "$fileName\0"; } - close(H); + git_cmd_try { $repo->command_close_pipe($fd, $ctx); } + 'git-update-index failed to remove old files with code %d'; } } -- cgit v0.10.2-6-g49f6 From f6af75d29c7e01e1d538dc3458c743e1a34defb6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Jun 2006 17:57:48 -0700 Subject: Perl interface: add build-time configuration to allow building with -fPIC On x86-64 it seems that Git.xs does not link without compiling the main git objects with -fPIC. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index dda9b9d..aa0618e 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,6 @@ # The default target of this Makefile is... all: -# Define MOZILLA_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast -# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default -# choice) has very fast version optimized for i586. -# # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies MOZILLA_SHA1. # @@ -39,6 +34,14 @@ all: # Define ARM_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for ARM. # +# Define MOZILLA_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast +# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default +# choice) has very fast version optimized for i586. +# +# Define USE_PIC if you need the main git objects to be built with -fPIC +# in order to build and link perl/Git.so. x86-64 seems to need this. +# # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). @@ -65,13 +68,13 @@ all: # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you # sufficient guarantee that no collisions between objects will ever happen. - +# # Define USE_NSEC below if you want git to care about sub-second file mtimes # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely # randomly break unless your underlying filesystem supports those sub-second # times (my ext3 doesn't). - +# # Define USE_STDEV below if you want git to care about the underlying device # change being considered an inode change from the update-cache perspective. @@ -464,6 +467,9 @@ else endif endif endif +ifdef USE_PIC + ALL_CFLAGS += -fPIC +endif ifdef NO_ACCURATE_DIFF ALL_CFLAGS += -DNO_ACCURATE_DIFF endif -- cgit v0.10.2-6-g49f6 From 5e6ab8607e4ae53c0abb5b3027904f1e3f539969 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Jun 2006 17:56:11 -0700 Subject: Perl interface: make testsuite work again. Signed-off-by: Junio C Hamano diff --git a/t/test-lib.sh b/t/test-lib.sh index 05f6e79..fba0c51 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -206,6 +206,8 @@ PYTHON=`sed -e '1{ PYTHONPATH=$(pwd)/../compat export PYTHONPATH } +PERL5LIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git +export PERL5LIB test -d ../templates/blt || { error "You haven't built things yet, have you?" } -- cgit v0.10.2-6-g49f6 From 523bbaa4580a103b0994b5e3c71efcb1a8c6a7db Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 24 Jun 2006 05:16:17 -0700 Subject: perl: fix make clean When perl/Makefile is stale with respect to perl/Makefile.PL, it prevents "make clean" from completing which is quite irritating. Fix it by calling subdirectory make clean twice as needed. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index aa0618e..a76526a 100644 --- a/Makefile +++ b/Makefile @@ -744,7 +744,7 @@ clean: rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f $(htmldocs).tar.gz $(manpages).tar.gz $(MAKE) -C Documentation/ clean - [ ! -e perl/Makefile ] || $(MAKE) -C perl/ clean + [ ! -e perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean $(MAKE) -C templates/ clean $(MAKE) -C t/ clean rm -f GIT-VERSION-FILE GIT-CFLAGS -- cgit v0.10.2-6-g49f6 From d595a473ee628d0f88989d06871d9752caafa7e9 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 24 Jun 2006 18:35:12 -0700 Subject: Git.pm: assorted build related fixes. - We passed our own *.a archives as LIBS to the submake that runs in perl/; separate LIBS and EXTLIBS and pass the latter which tells what the system libraries are used. - The quoting of preprocesor symbol definitions passed down to perl/ submake was loose and we lost double quotes around include directives. Use *_SQ to quote them properly. - The installation location of perl/ submake is not architecture neutral anymore, so use SITEARCH instead of SITELIB. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index a76526a..1371e79 100644 --- a/Makefile +++ b/Makefile @@ -235,7 +235,7 @@ BUILTIN_OBJS = \ builtin-update-ref.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) -LIBS = $(GITLIBS) -lz +EXTLIBS = -lz # # Platform specific tweaks @@ -393,14 +393,14 @@ ifdef NEEDS_LIBICONV else ICONV_LINK = endif - LIBS += $(ICONV_LINK) -liconv + EXTLIBS += $(ICONV_LINK) -liconv endif ifdef NEEDS_SOCKET - LIBS += -lsocket + EXTLIBS += -lsocket SIMPLE_LIB += -lsocket endif ifdef NEEDS_NSL - LIBS += -lnsl + EXTLIBS += -lnsl SIMPLE_LIB += -lnsl endif ifdef NO_D_TYPE_IN_DIRENT @@ -463,7 +463,7 @@ ifdef MOZILLA_SHA1 LIB_OBJS += mozilla-sha1/sha1.o else SHA1_HEADER = - LIBS += $(LIB_4_CRYPTO) + EXTLIBS += $(LIB_4_CRYPTO) endif endif endif @@ -489,9 +489,13 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR)) +LIBS = $(GITLIBS) $(EXTLIBS) + ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir + + ### Build rules all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk @@ -615,11 +619,15 @@ $(XDIFF_LIB): $(XDIFF_OBJS) rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) -perl/Makefile: perl/Git.pm perl/Makefile.PL +PERL_DEFINE = $(ALL_CFLAGS) -DGIT_VERSION='"$(GIT_VERSION)"' +PERL_DEFINE_SQ = $(subst ','\'',$(PERL_DEFINE)) +PERL_LIBS = $(EXTLIBS) +PERL_LIBS_SQ = $(subst ','\'',$(PERL_LIBS)) +perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS (cd perl && $(PERL_PATH) Makefile.PL \ - PREFIX="$(prefix)" \ - DEFINE="$(ALL_CFLAGS) -DGIT_VERSION=\\\"$(GIT_VERSION)\\\"" \ - LIBS="$(LIBS)") + PREFIX='$(prefix_SQ)' \ + DEFINE='$(PERL_DEFINE_SQ)' \ + LIBS='$(PERL_LIBS_SQ)') doc: $(MAKE) -C Documentation all diff --git a/perl/Git.xs b/perl/Git.xs index 9d247b7..8b06ebf 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -29,7 +29,7 @@ report_xs(const char *prefix, const char *err, va_list params) return buf; } -void +static void NORETURN die_xs(const char *err, va_list params) { char *str; @@ -37,13 +37,12 @@ die_xs(const char *err, va_list params) croak(str); } -int +static void error_xs(const char *err, va_list params) { char *str; str = report_xs("error: ", err, params); warn(str); - return -1; } diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 54e8b20..92c140d 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -3,7 +3,7 @@ use ExtUtils::MakeMaker; sub MY::postamble { return <<'MAKE_FRAG'; instlibdir: - @echo $(INSTALLSITELIB) + @echo $(INSTALLSITEARCH) MAKE_FRAG } -- cgit v0.10.2-6-g49f6 From f6276fe159fe985af2d5831f4629ceefb33d082e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 24 Jun 2006 19:41:03 -0700 Subject: Git.pm: tentative fix to test the freshly built Git.pm Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 1371e79..9b9be59 100644 --- a/Makefile +++ b/Makefile @@ -531,9 +531,12 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh chmod +x $@+ mv $@+ $@ -$(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl +$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile +$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl rm -f $@ $@+ - sed -e '1s|#!.*perl\(.*\)|#!$(PERL_PATH_SQ)\1 -I'"$$(make -s -C perl instlibdir)"'|' \ + INSTLIBDIR=$$(make -s -C perl instlibdir) && \ + sed -e '1s|#!.*perl\(.*\)|#!$(PERL_PATH_SQ)\1|' \ + -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ $@.perl >$@+ chmod +x $@+ diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl index f86231e..e8fad02 100755 --- a/git-fmt-merge-msg.perl +++ b/git-fmt-merge-msg.perl @@ -5,6 +5,7 @@ # Read .git/FETCH_HEAD and make a human readable merge message # by grouping branches and tags together to form a single line. +unshift @INC, '@@INSTLIBDIR@@'; use strict; use Git; use Error qw(:try); -- cgit v0.10.2-6-g49f6 From a6065b548fc74ce4d8a655e17bfb1dba39540464 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 25 Jun 2006 03:54:23 +0200 Subject: Git.pm: Try to support ActiveState output pipe The code is stolen from git-annotate and completely untested since I don't have access to any Microsoft operating system now. Someone ActiveState-savvy should look at it anyway and try to implement the input pipe as well, if it is possible at all; also, the implementation seems to be horribly whitespace-unsafe. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 7bbb5be..6173043 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -663,18 +663,29 @@ sub _command_common_pipe { } _check_valid_cmd($cmd); - my $pid = open(my $fh, $direction); - if (not defined $pid) { - throw Error::Simple("open failed: $!"); - } elsif ($pid == 0) { - if (defined $opts{STDERR}) { - close STDERR; - } - if ($opts{STDERR}) { - open (STDERR, '>&', $opts{STDERR}) - or die "dup failed: $!"; + my $fh; + if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') { + # ActiveState Perl + #defined $opts{STDERR} and + # warn 'ignoring STDERR option - running w/ ActiveState'; + $direction eq '-|' or + die 'input pipe for ActiveState not implemented'; + tie ($fh, 'Git::activestate_pipe', $cmd, @args); + + } else { + my $pid = open($fh, $direction); + if (not defined $pid) { + throw Error::Simple("open failed: $!"); + } elsif ($pid == 0) { + if (defined $opts{STDERR}) { + close STDERR; + } + if ($opts{STDERR}) { + open (STDERR, '>&', $opts{STDERR}) + or die "dup failed: $!"; + } + _cmd_exec($self, $cmd, @args); } - _cmd_exec($self, $cmd, @args); } return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh; } @@ -749,4 +760,39 @@ sub AUTOLOAD { sub DESTROY { } +# Pipe implementation for ActiveState Perl. + +package Git::activestate_pipe; +use strict; + +sub TIEHANDLE { + my ($class, @params) = @_; + # FIXME: This is probably horrible idea and the thing will explode + # at the moment you give it arguments that require some quoting, + # but I have no ActiveState clue... --pasky + my $cmdline = join " ", @params; + my @data = qx{$cmdline}; + bless { i => 0, data => \@data }, $class; +} + +sub READLINE { + my $self = shift; + if ($self->{i} >= scalar @{$self->{data}}) { + return undef; + } + return $self->{'data'}->[ $self->{i}++ ]; +} + +sub CLOSE { + my $self = shift; + delete $self->{data}; + delete $self->{i}; +} + +sub EOF { + my $self = shift; + return ($self->{i} >= scalar @{$self->{data}}); +} + + 1; # Famous last words -- cgit v0.10.2-6-g49f6 From 24c4b7143639cc821b6d06f9e125429e65dad8cd Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 25 Jun 2006 03:54:26 +0200 Subject: Git.pm: Swap hash_object() parameters I'm about to introduce get_object() and it will be better for consistency if the object type always goes first. And writing 'blob' there explicitly is not much bother. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 6173043..5ec7ef8 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -485,13 +485,13 @@ sub wc_chdir { } -=item hash_object ( FILENAME [, TYPE ] ) +=item hash_object ( TYPE, FILENAME ) -=item hash_object ( FILEHANDLE [, TYPE ] ) +=item hash_object ( TYPE, FILEHANDLE ) Compute the SHA1 object id of the given C (or data waiting in -C) considering it is of the C object type (C -(default), C, C). +C) considering it is of the C object type (C, +C, C). In case of C passed instead of file name, all the data available are read and hashed, and the filehandle is automatically diff --git a/perl/Git.xs b/perl/Git.xs index 8b06ebf..3030ba9 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -104,9 +104,9 @@ CODE: } char * -xs_hash_object(file, type = "blob") - SV *file; +xs_hash_object(type, file) char *type; + SV *file; CODE: { unsigned char sha1[20]; -- cgit v0.10.2-6-g49f6 From 71efe0ca3c9c8bc8e7863e583cd2a808769c3bab Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 25 Jun 2006 03:54:28 +0200 Subject: Git.pm: Fix Git->repository("/somewhere/totally/elsewhere") Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 5ec7ef8..0581447 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -178,7 +178,8 @@ sub repository { }; if ($dir) { - $opts{Repository} = abs_path($dir); + $dir =~ m#^/# or $dir = $opts{Directory} . '/' . $dir; + $opts{Repository} = $dir; # If --git-dir went ok, this shouldn't die either. my $prefix = $search->command_oneline('rev-parse', '--show-prefix'); -- cgit v0.10.2-6-g49f6 From c2eeb4dcfece31a90f7de168092f4dfeaab96f95 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Mon, 26 Jun 2006 10:27:54 +0200 Subject: "test" in Solaris' /bin/sh does not support -e Running "make clean" currently fails: [ ! -e perl/Makefile ] || make -C perl/ clean /bin/sh: test: argument expected make: *** [clean] Error 1 Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 9b9be59..13411ea 100644 --- a/Makefile +++ b/Makefile @@ -755,7 +755,7 @@ clean: rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f $(htmldocs).tar.gz $(manpages).tar.gz $(MAKE) -C Documentation/ clean - [ ! -e perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean + [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean $(MAKE) -C templates/ clean $(MAKE) -C t/ clean rm -f GIT-VERSION-FILE GIT-CFLAGS -- cgit v0.10.2-6-g49f6 From de86e131b538a021c14d53c6cc98bd7f0330dc92 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Tue, 27 Jun 2006 00:21:07 +0200 Subject: Makefile fix for Solaris Solaris' /bin/sh does not support $( )-style command substitution Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 13411ea..1121d3e 100644 --- a/Makefile +++ b/Makefile @@ -534,7 +534,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl rm -f $@ $@+ - INSTLIBDIR=$$(make -s -C perl instlibdir) && \ + INSTLIBDIR=`make -s -C perl instlibdir` && \ sed -e '1s|#!.*perl\(.*\)|#!$(PERL_PATH_SQ)\1|' \ -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -- cgit v0.10.2-6-g49f6 From 8d7f586f13f5aac31dca22b1d726e1583e180cb5 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 25 Jun 2006 03:47:03 +0200 Subject: Git.pm: Support for perl/ being built by a different compiler dst_ on #git reported that on Solaris 9, Perl was built by Sun CC and perl/ is therefore being built with it as well, while the rest of Git is built with gcc. The problem (the first one visible, anyway) is that we passed perl/ even various gcc-specific options. This separates those to a special variable. This is not really meant for an application yet since it's not clear if it will alone help anything. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 1121d3e..ee9508e 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,11 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ ### --- END CONFIGURATION SECTION --- +# Those must not be GNU-specific; they are shared with perl/ which may +# be built by a different compiler. +BASIC_CFLAGS = +BASIC_LDFLAGS = + SCRIPT_SH = \ git-bisect.sh git-branch.sh git-checkout.sh \ git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ @@ -254,13 +259,13 @@ ifeq ($(uname_S),Darwin) NO_STRLCPY = YesPlease ## fink ifeq ($(shell test -d /sw/lib && echo y),y) - ALL_CFLAGS += -I/sw/include - ALL_LDFLAGS += -L/sw/lib + BASIC_CFLAGS += -I/sw/include + BASIC_LDFLAGS += -L/sw/lib endif ## darwinports ifeq ($(shell test -d /opt/local/lib && echo y),y) - ALL_CFLAGS += -I/opt/local/include - ALL_LDFLAGS += -L/opt/local/lib + BASIC_CFLAGS += -I/opt/local/include + BASIC_LDFLAGS += -L/opt/local/lib endif endif ifeq ($(uname_S),SunOS) @@ -280,7 +285,7 @@ ifeq ($(uname_S),SunOS) endif INSTALL = ginstall TAR = gtar - ALL_CFLAGS += -D__EXTENSIONS__ + BASIC_CFLAGS += -D__EXTENSIONS__ endif ifeq ($(uname_O),Cygwin) NO_D_TYPE_IN_DIRENT = YesPlease @@ -298,21 +303,22 @@ ifeq ($(uname_O),Cygwin) endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease - ALL_CFLAGS += -I/usr/local/include - ALL_LDFLAGS += -L/usr/local/lib + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease NEEDS_LIBICONV = YesPlease - ALL_CFLAGS += -I/usr/local/include - ALL_LDFLAGS += -L/usr/local/lib + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) NEEDS_LIBICONV = YesPlease endif - ALL_CFLAGS += -I/usr/pkg/include - ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib + BASIC_CFLAGS += -I/usr/pkg/include + BASIC_LDFLAGS += -L/usr/pkg/lib + ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib endif ifeq ($(uname_S),AIX) NO_STRCASESTR=YesPlease @@ -326,9 +332,9 @@ ifeq ($(uname_S),IRIX64) NO_STRLCPY = YesPlease NO_SOCKADDR_STORAGE=YesPlease SHELL_PATH=/usr/gnu/bin/bash - ALL_CFLAGS += -DPATH_MAX=1024 + BASIC_CFLAGS += -DPATH_MAX=1024 # for now, build 32-bit version - ALL_LDFLAGS += -L/usr/lib32 + BASIC_LDFLAGS += -L/usr/lib32 endif ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease @@ -349,7 +355,7 @@ endif ifndef NO_CURL ifdef CURLDIR # This is still problematic -- gcc does not always want -R. - ALL_CFLAGS += -I$(CURLDIR)/include + BASIC_CFLAGS += -I$(CURLDIR)/include CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl else CURL_LIBCURL = -lcurl @@ -370,13 +376,13 @@ ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR # Again this may be problematic -- gcc does not always want -R. - ALL_CFLAGS += -I$(OPENSSLDIR)/include + BASIC_CFLAGS += -I$(OPENSSLDIR)/include OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib else OPENSSL_LINK = endif else - ALL_CFLAGS += -DNO_OPENSSL + BASIC_CFLAGS += -DNO_OPENSSL MOZILLA_SHA1 = 1 OPENSSL_LIBSSL = endif @@ -388,7 +394,7 @@ endif ifdef NEEDS_LIBICONV ifdef ICONVDIR # Again this may be problematic -- gcc does not always want -R. - ALL_CFLAGS += -I$(ICONVDIR)/include + BASIC_CFLAGS += -I$(ICONVDIR)/include ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib else ICONV_LINK = @@ -404,13 +410,13 @@ ifdef NEEDS_NSL SIMPLE_LIB += -lnsl endif ifdef NO_D_TYPE_IN_DIRENT - ALL_CFLAGS += -DNO_D_TYPE_IN_DIRENT + BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT endif ifdef NO_D_INO_IN_DIRENT - ALL_CFLAGS += -DNO_D_INO_IN_DIRENT + BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT endif ifdef NO_SYMLINK_HEAD - ALL_CFLAGS += -DNO_SYMLINK_HEAD + BASIC_CFLAGS += -DNO_SYMLINK_HEAD endif ifdef NO_STRCASESTR COMPAT_CFLAGS += -DNO_STRCASESTR @@ -433,13 +439,13 @@ ifdef NO_MMAP COMPAT_OBJS += compat/mmap.o endif ifdef NO_IPV6 - ALL_CFLAGS += -DNO_IPV6 + BASIC_CFLAGS += -DNO_IPV6 endif ifdef NO_SOCKADDR_STORAGE ifdef NO_IPV6 - ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in + BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in else - ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in6 + BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6 endif endif ifdef NO_INET_NTOP @@ -447,7 +453,7 @@ ifdef NO_INET_NTOP endif ifdef NO_ICONV - ALL_CFLAGS += -DNO_ICONV + BASIC_CFLAGS += -DNO_ICONV endif ifdef PPC_SHA1 @@ -471,7 +477,7 @@ ifdef USE_PIC ALL_CFLAGS += -fPIC endif ifdef NO_ACCURATE_DIFF - ALL_CFLAGS += -DNO_ACCURATE_DIFF + BASIC_CFLAGS += -DNO_ACCURATE_DIFF endif # Shell quote (do not use $(call) to accomodate ancient setups); @@ -491,8 +497,12 @@ GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR)) LIBS = $(GITLIBS) $(EXTLIBS) -ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS) +BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) + +ALL_CFLAGS += $(BASIC_CFLAGS) +ALL_LDFLAGS += $(BASIC_LDFLAGS) + export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir @@ -622,9 +632,9 @@ $(XDIFF_LIB): $(XDIFF_OBJS) rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) -PERL_DEFINE = $(ALL_CFLAGS) -DGIT_VERSION='"$(GIT_VERSION)"' +PERL_DEFINE = $(BASIC_CFLAGS) -DGIT_VERSION='"$(GIT_VERSION)"' PERL_DEFINE_SQ = $(subst ','\'',$(PERL_DEFINE)) -PERL_LIBS = $(EXTLIBS) +PERL_LIBS = $(BASIC_LDFLAGS) $(EXTLIBS) PERL_LIBS_SQ = $(subst ','\'',$(PERL_LIBS)) perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS (cd perl && $(PERL_PATH) Makefile.PL \ -- cgit v0.10.2-6-g49f6 From c9093fb38b48dcf09dbf1fb5cbf72e2b1f2c1258 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Tue, 27 Jun 2006 00:23:08 +0200 Subject: Add possibility to pass CFLAGS and LDFLAGS specific to the perl subdir Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index ee9508e..2f66ab1 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,8 @@ CFLAGS = -g -O2 -Wall LDFLAGS = ALL_CFLAGS = $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) +PERL_CFLAGS = +PERL_LDFLAGS = STRIP ?= strip prefix = $(HOME) @@ -119,8 +121,8 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ # Those must not be GNU-specific; they are shared with perl/ which may # be built by a different compiler. -BASIC_CFLAGS = -BASIC_LDFLAGS = +BASIC_CFLAGS = $(PERL_CFLAGS) +BASIC_LDFLAGS = $(PERL_LDFLAGS) SCRIPT_SH = \ git-bisect.sh git-branch.sh git-checkout.sh \ -- cgit v0.10.2-6-g49f6 From f1b8fd4abae7910d9227ae019220944e8fac6884 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 28 Jun 2006 03:17:07 -0700 Subject: Perly Git: arrange include path settings properly. Before "use Git" takes effect, we would need to set up the Perl library path to point at the local installation location. So that instruction needs to be in BEGIN{} block. Pointed out and fixed by Pavel Roskin. Signed-off-by: Junio C Hamano diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl index e8fad02..1b23fa1 100755 --- a/git-fmt-merge-msg.perl +++ b/git-fmt-merge-msg.perl @@ -5,7 +5,7 @@ # Read .git/FETCH_HEAD and make a human readable merge message # by grouping branches and tags together to form a single line. -unshift @INC, '@@INSTLIBDIR@@'; +BEGIN { unshift @INC, '@@INSTLIBDIR@@'; } use strict; use Git; use Error qw(:try); diff --git a/git-mv.perl b/git-mv.perl index f1bde43..a604896 100755 --- a/git-mv.perl +++ b/git-mv.perl @@ -6,7 +6,7 @@ # This file is licensed under the GPL v2, or a later version # at the discretion of Linus Torvalds. - +BEGIN { unshift @INC, '@@INSTLIBDIR@@'; } use warnings; use strict; use Getopt::Std; -- cgit v0.10.2-6-g49f6 From c35ebc902fd5c48c978d0d4cfab52ccdb4b11f54 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 28 Jun 2006 22:08:54 -0700 Subject: Makefile: Set USE_PIC on x86-64 On some platforms, Git.xs refuses to link with the rest of git unless the latter is compiled with -fPIC, and we have USE_PIC control in the Makefile for the user to set it. At least we know x86-64 is such, so set it in the Makefile. The original suggestion by Marco Roeland conservatively did this only for Linux x86-64, but let's keep the Makefile simple and if it breaks somebody let them holler. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 2f66ab1..3c25fb3 100644 --- a/Makefile +++ b/Makefile @@ -341,6 +341,9 @@ endif ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease endif +ifeq ($(uname_M),x86_64) + USE_PIC = YesPlease +endif -include config.mak -- cgit v0.10.2-6-g49f6 From 893973a6f271429fbe1973d61dc8e1d76753327e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 29 Jun 2006 17:02:21 -0700 Subject: Perly git: work around buggy make implementations. FC4 uses gnumake 3.80 whose annoying "Entering directory..." messages are not silenced with -s alone. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 3c25fb3..3810514 100644 --- a/Makefile +++ b/Makefile @@ -549,7 +549,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl rm -f $@ $@+ - INSTLIBDIR=`make -s -C perl instlibdir` && \ + INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \ sed -e '1s|#!.*perl\(.*\)|#!$(PERL_PATH_SQ)\1|' \ -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 92c140d..d401a66 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -3,7 +3,7 @@ use ExtUtils::MakeMaker; sub MY::postamble { return <<'MAKE_FRAG'; instlibdir: - @echo $(INSTALLSITEARCH) + @echo '$(INSTALLSITEARCH)' MAKE_FRAG } -- cgit v0.10.2-6-g49f6 From 3553309f5ba7f9fed61ac2767d53677c309826b2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Jun 2006 00:43:43 -0700 Subject: Git.pm: clean generated files. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 3810514..7030167 100644 --- a/Makefile +++ b/Makefile @@ -771,6 +771,7 @@ clean: rm -f $(htmldocs).tar.gz $(manpages).tar.gz $(MAKE) -C Documentation/ clean [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean + rm -f perl/ppport.h perl/Makefile.old $(MAKE) -C templates/ clean $(MAKE) -C t/ clean rm -f GIT-VERSION-FILE GIT-CFLAGS -- cgit v0.10.2-6-g49f6 From 1d8c9dc47de0cbf3955ccc9408564cccbda8e348 Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Fri, 30 Jun 2006 01:09:23 -0400 Subject: Fix probing for already installed Error.pm The syntax for 'require' was wrong, and it was always failing, which resulted in installing our own version of Error.pm anyways. Now we used to ship our own Error.pm in the same directory, so after fixing the syntax, 'require' always succeeds, but it does not test if the platform has Error.pm module installed anymore. So rename the source we ship to private-Error.pm, and install that as Error.pm when the platform does not have one already. Signed-off-by: Pavel Roskin Signed-off-by: Junio C Hamano diff --git a/perl/Error.pm b/perl/Error.pm deleted file mode 100644 index ebd0749..0000000 --- a/perl/Error.pm +++ /dev/null @@ -1,821 +0,0 @@ -# Error.pm -# -# Copyright (c) 1997-8 Graham Barr . All rights reserved. -# This program is free software; you can redistribute it and/or -# modify it under the same terms as Perl itself. -# -# Based on my original Error.pm, and Exceptions.pm by Peter Seibel -# and adapted by Jesse Glick . -# -# but modified ***significantly*** - -package Error; - -use strict; -use vars qw($VERSION); -use 5.004; - -$VERSION = "0.15009"; - -use overload ( - '""' => 'stringify', - '0+' => 'value', - 'bool' => sub { return 1; }, - 'fallback' => 1 -); - -$Error::Depth = 0; # Depth to pass to caller() -$Error::Debug = 0; # Generate verbose stack traces -@Error::STACK = (); # Clause stack for try -$Error::THROWN = undef; # last error thrown, a workaround until die $ref works - -my $LAST; # Last error created -my %ERROR; # Last error associated with package - -sub throw_Error_Simple -{ - my $args = shift; - return Error::Simple->new($args->{'text'}); -} - -$Error::ObjectifyCallback = \&throw_Error_Simple; - - -# Exported subs are defined in Error::subs - -use Scalar::Util (); - -sub import { - shift; - local $Exporter::ExportLevel = $Exporter::ExportLevel + 1; - Error::subs->import(@_); -} - -# I really want to use last for the name of this method, but it is a keyword -# which prevent the syntax last Error - -sub prior { - shift; # ignore - - return $LAST unless @_; - - my $pkg = shift; - return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef - unless ref($pkg); - - my $obj = $pkg; - my $err = undef; - if($obj->isa('HASH')) { - $err = $obj->{'__Error__'} - if exists $obj->{'__Error__'}; - } - elsif($obj->isa('GLOB')) { - $err = ${*$obj}{'__Error__'} - if exists ${*$obj}{'__Error__'}; - } - - $err; -} - -sub flush { - shift; #ignore - - unless (@_) { - $LAST = undef; - return; - } - - my $pkg = shift; - return unless ref($pkg); - - undef $ERROR{$pkg} if defined $ERROR{$pkg}; -} - -# Return as much information as possible about where the error -# happened. The -stacktrace element only exists if $Error::DEBUG -# was set when the error was created - -sub stacktrace { - my $self = shift; - - return $self->{'-stacktrace'} - if exists $self->{'-stacktrace'}; - - my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died"; - - $text .= sprintf(" at %s line %d.\n", $self->file, $self->line) - unless($text =~ /\n$/s); - - $text; -} - -# Allow error propagation, ie -# -# $ber->encode(...) or -# return Error->prior($ber)->associate($ldap); - -sub associate { - my $err = shift; - my $obj = shift; - - return unless ref($obj); - - if($obj->isa('HASH')) { - $obj->{'__Error__'} = $err; - } - elsif($obj->isa('GLOB')) { - ${*$obj}{'__Error__'} = $err; - } - $obj = ref($obj); - $ERROR{ ref($obj) } = $err; - - return; -} - -sub new { - my $self = shift; - my($pkg,$file,$line) = caller($Error::Depth); - - my $err = bless { - '-package' => $pkg, - '-file' => $file, - '-line' => $line, - @_ - }, $self; - - $err->associate($err->{'-object'}) - if(exists $err->{'-object'}); - - # To always create a stacktrace would be very inefficient, so - # we only do it if $Error::Debug is set - - if($Error::Debug) { - require Carp; - local $Carp::CarpLevel = $Error::Depth; - my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error"; - my $trace = Carp::longmess($text); - # Remove try calls from the trace - $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog; - $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog; - $err->{'-stacktrace'} = $trace - } - - $@ = $LAST = $ERROR{$pkg} = $err; -} - -# Throw an error. this contains some very gory code. - -sub throw { - my $self = shift; - local $Error::Depth = $Error::Depth + 1; - - # if we are not rethrow-ing then create the object to throw - $self = $self->new(@_) unless ref($self); - - die $Error::THROWN = $self; -} - -# syntactic sugar for -# -# die with Error( ... ); - -sub with { - my $self = shift; - local $Error::Depth = $Error::Depth + 1; - - $self->new(@_); -} - -# syntactic sugar for -# -# record Error( ... ) and return; - -sub record { - my $self = shift; - local $Error::Depth = $Error::Depth + 1; - - $self->new(@_); -} - -# catch clause for -# -# try { ... } catch CLASS with { ... } - -sub catch { - my $pkg = shift; - my $code = shift; - my $clauses = shift || {}; - my $catch = $clauses->{'catch'} ||= []; - - unshift @$catch, $pkg, $code; - - $clauses; -} - -# Object query methods - -sub object { - my $self = shift; - exists $self->{'-object'} ? $self->{'-object'} : undef; -} - -sub file { - my $self = shift; - exists $self->{'-file'} ? $self->{'-file'} : undef; -} - -sub line { - my $self = shift; - exists $self->{'-line'} ? $self->{'-line'} : undef; -} - -sub text { - my $self = shift; - exists $self->{'-text'} ? $self->{'-text'} : undef; -} - -# overload methods - -sub stringify { - my $self = shift; - defined $self->{'-text'} ? $self->{'-text'} : "Died"; -} - -sub value { - my $self = shift; - exists $self->{'-value'} ? $self->{'-value'} : undef; -} - -package Error::Simple; - -@Error::Simple::ISA = qw(Error); - -sub new { - my $self = shift; - my $text = "" . shift; - my $value = shift; - my(@args) = (); - - local $Error::Depth = $Error::Depth + 1; - - @args = ( -file => $1, -line => $2) - if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s); - push(@args, '-value', 0 + $value) - if defined($value); - - $self->SUPER::new(-text => $text, @args); -} - -sub stringify { - my $self = shift; - my $text = $self->SUPER::stringify; - $text .= sprintf(" at %s line %d.\n", $self->file, $self->line) - unless($text =~ /\n$/s); - $text; -} - -########################################################################## -########################################################################## - -# Inspired by code from Jesse Glick and -# Peter Seibel - -package Error::subs; - -use Exporter (); -use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS); - -@EXPORT_OK = qw(try with finally except otherwise); -%EXPORT_TAGS = (try => \@EXPORT_OK); - -@ISA = qw(Exporter); - -sub run_clauses ($$$\@) { - my($clauses,$err,$wantarray,$result) = @_; - my $code = undef; - - $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err); - - CATCH: { - - # catch - my $catch; - if(defined($catch = $clauses->{'catch'})) { - my $i = 0; - - CATCHLOOP: - for( ; $i < @$catch ; $i += 2) { - my $pkg = $catch->[$i]; - unless(defined $pkg) { - #except - splice(@$catch,$i,2,$catch->[$i+1]->()); - $i -= 2; - next CATCHLOOP; - } - elsif(Scalar::Util::blessed($err) && $err->isa($pkg)) { - $code = $catch->[$i+1]; - while(1) { - my $more = 0; - local($Error::THROWN); - my $ok = eval { - if($wantarray) { - @{$result} = $code->($err,\$more); - } - elsif(defined($wantarray)) { - @{$result} = (); - $result->[0] = $code->($err,\$more); - } - else { - $code->($err,\$more); - } - 1; - }; - if( $ok ) { - next CATCHLOOP if $more; - undef $err; - } - else { - $err = defined($Error::THROWN) - ? $Error::THROWN : $@; - $err = $Error::ObjectifyCallback->({'text' =>$err}) - unless ref($err); - } - last CATCH; - }; - } - } - } - - # otherwise - my $owise; - if(defined($owise = $clauses->{'otherwise'})) { - my $code = $clauses->{'otherwise'}; - my $more = 0; - my $ok = eval { - if($wantarray) { - @{$result} = $code->($err,\$more); - } - elsif(defined($wantarray)) { - @{$result} = (); - $result->[0] = $code->($err,\$more); - } - else { - $code->($err,\$more); - } - 1; - }; - if( $ok ) { - undef $err; - } - else { - $err = defined($Error::THROWN) - ? $Error::THROWN : $@; - - $err = $Error::ObjectifyCallback->({'text' =>$err}) - unless ref($err); - } - } - } - $err; -} - -sub try (&;$) { - my $try = shift; - my $clauses = @_ ? shift : {}; - my $ok = 0; - my $err = undef; - my @result = (); - - unshift @Error::STACK, $clauses; - - my $wantarray = wantarray(); - - do { - local $Error::THROWN = undef; - local $@ = undef; - - $ok = eval { - if($wantarray) { - @result = $try->(); - } - elsif(defined $wantarray) { - $result[0] = $try->(); - } - else { - $try->(); - } - 1; - }; - - $err = defined($Error::THROWN) ? $Error::THROWN : $@ - unless $ok; - }; - - shift @Error::STACK; - - $err = run_clauses($clauses,$err,wantarray,@result) - unless($ok); - - $clauses->{'finally'}->() - if(defined($clauses->{'finally'})); - - if (defined($err)) - { - if (Scalar::Util::blessed($err) && $err->can('throw')) - { - throw $err; - } - else - { - die $err; - } - } - - wantarray ? @result : $result[0]; -} - -# Each clause adds a sub to the list of clauses. The finally clause is -# always the last, and the otherwise clause is always added just before -# the finally clause. -# -# All clauses, except the finally clause, add a sub which takes one argument -# this argument will be the error being thrown. The sub will return a code ref -# if that clause can handle that error, otherwise undef is returned. -# -# The otherwise clause adds a sub which unconditionally returns the users -# code reference, this is why it is forced to be last. -# -# The catch clause is defined in Error.pm, as the syntax causes it to -# be called as a method - -sub with (&;$) { - @_ -} - -sub finally (&) { - my $code = shift; - my $clauses = { 'finally' => $code }; - $clauses; -} - -# The except clause is a block which returns a hashref or a list of -# key-value pairs, where the keys are the classes and the values are subs. - -sub except (&;$) { - my $code = shift; - my $clauses = shift || {}; - my $catch = $clauses->{'catch'} ||= []; - - my $sub = sub { - my $ref; - my(@array) = $code->($_[0]); - if(@array == 1 && ref($array[0])) { - $ref = $array[0]; - $ref = [ %$ref ] - if(UNIVERSAL::isa($ref,'HASH')); - } - else { - $ref = \@array; - } - @$ref - }; - - unshift @{$catch}, undef, $sub; - - $clauses; -} - -sub otherwise (&;$) { - my $code = shift; - my $clauses = shift || {}; - - if(exists $clauses->{'otherwise'}) { - require Carp; - Carp::croak("Multiple otherwise clauses"); - } - - $clauses->{'otherwise'} = $code; - - $clauses; -} - -1; -__END__ - -=head1 NAME - -Error - Error/exception handling in an OO-ish way - -=head1 SYNOPSIS - - use Error qw(:try); - - throw Error::Simple( "A simple error"); - - sub xyz { - ... - record Error::Simple("A simple error") - and return; - } - - unlink($file) or throw Error::Simple("$file: $!",$!); - - try { - do_some_stuff(); - die "error!" if $condition; - throw Error::Simple -text => "Oops!" if $other_condition; - } - catch Error::IO with { - my $E = shift; - print STDERR "File ", $E->{'-file'}, " had a problem\n"; - } - except { - my $E = shift; - my $general_handler=sub {send_message $E->{-description}}; - return { - UserException1 => $general_handler, - UserException2 => $general_handler - }; - } - otherwise { - print STDERR "Well I don't know what to say\n"; - } - finally { - close_the_garage_door_already(); # Should be reliable - }; # Don't forget the trailing ; or you might be surprised - -=head1 DESCRIPTION - -The C package provides two interfaces. Firstly C provides -a procedural interface to exception handling. Secondly C is a -base class for errors/exceptions that can either be thrown, for -subsequent catch, or can simply be recorded. - -Errors in the class C should not be thrown directly, but the -user should throw errors from a sub-class of C. - -=head1 PROCEDURAL INTERFACE - -C exports subroutines to perform exception handling. These will -be exported if the C<:try> tag is used in the C line. - -=over 4 - -=item try BLOCK CLAUSES - -C is the main subroutine called by the user. All other subroutines -exported are clauses to the try subroutine. - -The BLOCK will be evaluated and, if no error is throw, try will return -the result of the block. - -C are the subroutines below, which describe what to do in the -event of an error being thrown within BLOCK. - -=item catch CLASS with BLOCK - -This clauses will cause all errors that satisfy C<$err-Eisa(CLASS)> -to be caught and handled by evaluating C. - -C will be passed two arguments. The first will be the error -being thrown. The second is a reference to a scalar variable. If this -variable is set by the catch block then, on return from the catch -block, try will continue processing as if the catch block was never -found. - -To propagate the error the catch block may call C<$err-Ethrow> - -If the scalar reference by the second argument is not set, and the -error is not thrown. Then the current try block will return with the -result from the catch block. - -=item except BLOCK - -When C is looking for a handler, if an except clause is found -C is evaluated. The return value from this block should be a -HASHREF or a list of key-value pairs, where the keys are class names -and the values are CODE references for the handler of errors of that -type. - -=item otherwise BLOCK - -Catch any error by executing the code in C - -When evaluated C will be passed one argument, which will be the -error being processed. - -Only one otherwise block may be specified per try block - -=item finally BLOCK - -Execute the code in C either after the code in the try block has -successfully completed, or if the try block throws an error then -C will be executed after the handler has completed. - -If the handler throws an error then the error will be caught, the -finally block will be executed and the error will be re-thrown. - -Only one finally block may be specified per try block - -=back - -=head1 CLASS INTERFACE - -=head2 CONSTRUCTORS - -The C object is implemented as a HASH. This HASH is initialized -with the arguments that are passed to it's constructor. The elements -that are used by, or are retrievable by the C class are listed -below, other classes may add to these. - - -file - -line - -text - -value - -object - -If C<-file> or C<-line> are not specified in the constructor arguments -then these will be initialized with the file name and line number where -the constructor was called from. - -If the error is associated with an object then the object should be -passed as the C<-object> argument. This will allow the C package -to associate the error with the object. - -The C package remembers the last error created, and also the -last error associated with a package. This could either be the last -error created by a sub in that package, or the last error which passed -an object blessed into that package as the C<-object> argument. - -=over 4 - -=item throw ( [ ARGS ] ) - -Create a new C object and throw an error, which will be caught -by a surrounding C block, if there is one. Otherwise it will cause -the program to exit. - -C may also be called on an existing error to re-throw it. - -=item with ( [ ARGS ] ) - -Create a new C object and returns it. This is defined for -syntactic sugar, eg - - die with Some::Error ( ... ); - -=item record ( [ ARGS ] ) - -Create a new C object and returns it. This is defined for -syntactic sugar, eg - - record Some::Error ( ... ) - and return; - -=back - -=head2 STATIC METHODS - -=over 4 - -=item prior ( [ PACKAGE ] ) - -Return the last error created, or the last error associated with -C - -=item flush ( [ PACKAGE ] ) - -Flush the last error created, or the last error associated with -C.It is necessary to clear the error stack before exiting the -package or uncaught errors generated using C will be reported. - - $Error->flush; - -=cut - -=back - -=head2 OBJECT METHODS - -=over 4 - -=item stacktrace - -If the variable C<$Error::Debug> was non-zero when the error was -created, then C returns a string created by calling -C. If the variable was zero the C returns -the text of the error appended with the filename and line number of -where the error was created, providing the text does not end with a -newline. - -=item object - -The object this error was associated with - -=item file - -The file where the constructor of this error was called from - -=item line - -The line where the constructor of this error was called from - -=item text - -The text of the error - -=back - -=head2 OVERLOAD METHODS - -=over 4 - -=item stringify - -A method that converts the object into a string. This method may simply -return the same as the C method, or it may append more -information. For example the file name and line number. - -By default this method returns the C<-text> argument that was passed to -the constructor, or the string C<"Died"> if none was given. - -=item value - -A method that will return a value that can be associated with the -error. For example if an error was created due to the failure of a -system call, then this may return the numeric value of C<$!> at the -time. - -By default this method returns the C<-value> argument that was passed -to the constructor. - -=back - -=head1 PRE-DEFINED ERROR CLASSES - -=over 4 - -=item Error::Simple - -This class can be used to hold simple error strings and values. It's -constructor takes two arguments. The first is a text value, the second -is a numeric value. These values are what will be returned by the -overload methods. - -If the text value ends with C as $@ strings do, then -this infomation will be used to set the C<-file> and C<-line> arguments -of the error object. - -This class is used internally if an eval'd block die's with an error -that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified) - -=back - -=head1 $Error::ObjectifyCallback - -This variable holds a reference to a subroutine that converts errors that -are plain strings to objects. It is used by Error.pm to convert textual -errors to objects, and can be overrided by the user. - -It accepts a single argument which is a hash reference to named parameters. -Currently the only named parameter passed is C<'text'> which is the text -of the error, but others may be available in the future. - -For example the following code will cause Error.pm to throw objects of the -class MyError::Bar by default: - - sub throw_MyError_Bar - { - my $args = shift; - my $err = MyError::Bar->new(); - $err->{'MyBarText'} = $args->{'text'}; - return $err; - } - - { - local $Error::ObjectifyCallback = \&throw_MyError_Bar; - - # Error handling here. - } - -=head1 KNOWN BUGS - -None, but that does not mean there are not any. - -=head1 AUTHORS - -Graham Barr - -The code that inspired me to write this was originally written by -Peter Seibel and adapted by Jesse Glick -. - -=head1 MAINTAINER - -Shlomi Fish - -=head1 PAST MAINTAINERS - -Arun Kumar U - -=cut diff --git a/perl/Makefile.PL b/perl/Makefile.PL index d401a66..25ae54a 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -12,9 +12,9 @@ my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm'); # We come with our own bundled Error.pm. It's not in the set of default # Perl modules so install it if it's not available on the system yet. -eval { require 'Error' }; +eval { require Error }; if ($@) { - $pm{'Error.pm'} = '$(INST_LIBDIR)/Error.pm'; + $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm'; } WriteMakefile( diff --git a/perl/private-Error.pm b/perl/private-Error.pm new file mode 100644 index 0000000..ebd0749 --- /dev/null +++ b/perl/private-Error.pm @@ -0,0 +1,821 @@ +# Error.pm +# +# Copyright (c) 1997-8 Graham Barr . All rights reserved. +# This program is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +# +# Based on my original Error.pm, and Exceptions.pm by Peter Seibel +# and adapted by Jesse Glick . +# +# but modified ***significantly*** + +package Error; + +use strict; +use vars qw($VERSION); +use 5.004; + +$VERSION = "0.15009"; + +use overload ( + '""' => 'stringify', + '0+' => 'value', + 'bool' => sub { return 1; }, + 'fallback' => 1 +); + +$Error::Depth = 0; # Depth to pass to caller() +$Error::Debug = 0; # Generate verbose stack traces +@Error::STACK = (); # Clause stack for try +$Error::THROWN = undef; # last error thrown, a workaround until die $ref works + +my $LAST; # Last error created +my %ERROR; # Last error associated with package + +sub throw_Error_Simple +{ + my $args = shift; + return Error::Simple->new($args->{'text'}); +} + +$Error::ObjectifyCallback = \&throw_Error_Simple; + + +# Exported subs are defined in Error::subs + +use Scalar::Util (); + +sub import { + shift; + local $Exporter::ExportLevel = $Exporter::ExportLevel + 1; + Error::subs->import(@_); +} + +# I really want to use last for the name of this method, but it is a keyword +# which prevent the syntax last Error + +sub prior { + shift; # ignore + + return $LAST unless @_; + + my $pkg = shift; + return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef + unless ref($pkg); + + my $obj = $pkg; + my $err = undef; + if($obj->isa('HASH')) { + $err = $obj->{'__Error__'} + if exists $obj->{'__Error__'}; + } + elsif($obj->isa('GLOB')) { + $err = ${*$obj}{'__Error__'} + if exists ${*$obj}{'__Error__'}; + } + + $err; +} + +sub flush { + shift; #ignore + + unless (@_) { + $LAST = undef; + return; + } + + my $pkg = shift; + return unless ref($pkg); + + undef $ERROR{$pkg} if defined $ERROR{$pkg}; +} + +# Return as much information as possible about where the error +# happened. The -stacktrace element only exists if $Error::DEBUG +# was set when the error was created + +sub stacktrace { + my $self = shift; + + return $self->{'-stacktrace'} + if exists $self->{'-stacktrace'}; + + my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died"; + + $text .= sprintf(" at %s line %d.\n", $self->file, $self->line) + unless($text =~ /\n$/s); + + $text; +} + +# Allow error propagation, ie +# +# $ber->encode(...) or +# return Error->prior($ber)->associate($ldap); + +sub associate { + my $err = shift; + my $obj = shift; + + return unless ref($obj); + + if($obj->isa('HASH')) { + $obj->{'__Error__'} = $err; + } + elsif($obj->isa('GLOB')) { + ${*$obj}{'__Error__'} = $err; + } + $obj = ref($obj); + $ERROR{ ref($obj) } = $err; + + return; +} + +sub new { + my $self = shift; + my($pkg,$file,$line) = caller($Error::Depth); + + my $err = bless { + '-package' => $pkg, + '-file' => $file, + '-line' => $line, + @_ + }, $self; + + $err->associate($err->{'-object'}) + if(exists $err->{'-object'}); + + # To always create a stacktrace would be very inefficient, so + # we only do it if $Error::Debug is set + + if($Error::Debug) { + require Carp; + local $Carp::CarpLevel = $Error::Depth; + my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error"; + my $trace = Carp::longmess($text); + # Remove try calls from the trace + $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog; + $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog; + $err->{'-stacktrace'} = $trace + } + + $@ = $LAST = $ERROR{$pkg} = $err; +} + +# Throw an error. this contains some very gory code. + +sub throw { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + # if we are not rethrow-ing then create the object to throw + $self = $self->new(@_) unless ref($self); + + die $Error::THROWN = $self; +} + +# syntactic sugar for +# +# die with Error( ... ); + +sub with { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + $self->new(@_); +} + +# syntactic sugar for +# +# record Error( ... ) and return; + +sub record { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + $self->new(@_); +} + +# catch clause for +# +# try { ... } catch CLASS with { ... } + +sub catch { + my $pkg = shift; + my $code = shift; + my $clauses = shift || {}; + my $catch = $clauses->{'catch'} ||= []; + + unshift @$catch, $pkg, $code; + + $clauses; +} + +# Object query methods + +sub object { + my $self = shift; + exists $self->{'-object'} ? $self->{'-object'} : undef; +} + +sub file { + my $self = shift; + exists $self->{'-file'} ? $self->{'-file'} : undef; +} + +sub line { + my $self = shift; + exists $self->{'-line'} ? $self->{'-line'} : undef; +} + +sub text { + my $self = shift; + exists $self->{'-text'} ? $self->{'-text'} : undef; +} + +# overload methods + +sub stringify { + my $self = shift; + defined $self->{'-text'} ? $self->{'-text'} : "Died"; +} + +sub value { + my $self = shift; + exists $self->{'-value'} ? $self->{'-value'} : undef; +} + +package Error::Simple; + +@Error::Simple::ISA = qw(Error); + +sub new { + my $self = shift; + my $text = "" . shift; + my $value = shift; + my(@args) = (); + + local $Error::Depth = $Error::Depth + 1; + + @args = ( -file => $1, -line => $2) + if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s); + push(@args, '-value', 0 + $value) + if defined($value); + + $self->SUPER::new(-text => $text, @args); +} + +sub stringify { + my $self = shift; + my $text = $self->SUPER::stringify; + $text .= sprintf(" at %s line %d.\n", $self->file, $self->line) + unless($text =~ /\n$/s); + $text; +} + +########################################################################## +########################################################################## + +# Inspired by code from Jesse Glick and +# Peter Seibel + +package Error::subs; + +use Exporter (); +use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS); + +@EXPORT_OK = qw(try with finally except otherwise); +%EXPORT_TAGS = (try => \@EXPORT_OK); + +@ISA = qw(Exporter); + +sub run_clauses ($$$\@) { + my($clauses,$err,$wantarray,$result) = @_; + my $code = undef; + + $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err); + + CATCH: { + + # catch + my $catch; + if(defined($catch = $clauses->{'catch'})) { + my $i = 0; + + CATCHLOOP: + for( ; $i < @$catch ; $i += 2) { + my $pkg = $catch->[$i]; + unless(defined $pkg) { + #except + splice(@$catch,$i,2,$catch->[$i+1]->()); + $i -= 2; + next CATCHLOOP; + } + elsif(Scalar::Util::blessed($err) && $err->isa($pkg)) { + $code = $catch->[$i+1]; + while(1) { + my $more = 0; + local($Error::THROWN); + my $ok = eval { + if($wantarray) { + @{$result} = $code->($err,\$more); + } + elsif(defined($wantarray)) { + @{$result} = (); + $result->[0] = $code->($err,\$more); + } + else { + $code->($err,\$more); + } + 1; + }; + if( $ok ) { + next CATCHLOOP if $more; + undef $err; + } + else { + $err = defined($Error::THROWN) + ? $Error::THROWN : $@; + $err = $Error::ObjectifyCallback->({'text' =>$err}) + unless ref($err); + } + last CATCH; + }; + } + } + } + + # otherwise + my $owise; + if(defined($owise = $clauses->{'otherwise'})) { + my $code = $clauses->{'otherwise'}; + my $more = 0; + my $ok = eval { + if($wantarray) { + @{$result} = $code->($err,\$more); + } + elsif(defined($wantarray)) { + @{$result} = (); + $result->[0] = $code->($err,\$more); + } + else { + $code->($err,\$more); + } + 1; + }; + if( $ok ) { + undef $err; + } + else { + $err = defined($Error::THROWN) + ? $Error::THROWN : $@; + + $err = $Error::ObjectifyCallback->({'text' =>$err}) + unless ref($err); + } + } + } + $err; +} + +sub try (&;$) { + my $try = shift; + my $clauses = @_ ? shift : {}; + my $ok = 0; + my $err = undef; + my @result = (); + + unshift @Error::STACK, $clauses; + + my $wantarray = wantarray(); + + do { + local $Error::THROWN = undef; + local $@ = undef; + + $ok = eval { + if($wantarray) { + @result = $try->(); + } + elsif(defined $wantarray) { + $result[0] = $try->(); + } + else { + $try->(); + } + 1; + }; + + $err = defined($Error::THROWN) ? $Error::THROWN : $@ + unless $ok; + }; + + shift @Error::STACK; + + $err = run_clauses($clauses,$err,wantarray,@result) + unless($ok); + + $clauses->{'finally'}->() + if(defined($clauses->{'finally'})); + + if (defined($err)) + { + if (Scalar::Util::blessed($err) && $err->can('throw')) + { + throw $err; + } + else + { + die $err; + } + } + + wantarray ? @result : $result[0]; +} + +# Each clause adds a sub to the list of clauses. The finally clause is +# always the last, and the otherwise clause is always added just before +# the finally clause. +# +# All clauses, except the finally clause, add a sub which takes one argument +# this argument will be the error being thrown. The sub will return a code ref +# if that clause can handle that error, otherwise undef is returned. +# +# The otherwise clause adds a sub which unconditionally returns the users +# code reference, this is why it is forced to be last. +# +# The catch clause is defined in Error.pm, as the syntax causes it to +# be called as a method + +sub with (&;$) { + @_ +} + +sub finally (&) { + my $code = shift; + my $clauses = { 'finally' => $code }; + $clauses; +} + +# The except clause is a block which returns a hashref or a list of +# key-value pairs, where the keys are the classes and the values are subs. + +sub except (&;$) { + my $code = shift; + my $clauses = shift || {}; + my $catch = $clauses->{'catch'} ||= []; + + my $sub = sub { + my $ref; + my(@array) = $code->($_[0]); + if(@array == 1 && ref($array[0])) { + $ref = $array[0]; + $ref = [ %$ref ] + if(UNIVERSAL::isa($ref,'HASH')); + } + else { + $ref = \@array; + } + @$ref + }; + + unshift @{$catch}, undef, $sub; + + $clauses; +} + +sub otherwise (&;$) { + my $code = shift; + my $clauses = shift || {}; + + if(exists $clauses->{'otherwise'}) { + require Carp; + Carp::croak("Multiple otherwise clauses"); + } + + $clauses->{'otherwise'} = $code; + + $clauses; +} + +1; +__END__ + +=head1 NAME + +Error - Error/exception handling in an OO-ish way + +=head1 SYNOPSIS + + use Error qw(:try); + + throw Error::Simple( "A simple error"); + + sub xyz { + ... + record Error::Simple("A simple error") + and return; + } + + unlink($file) or throw Error::Simple("$file: $!",$!); + + try { + do_some_stuff(); + die "error!" if $condition; + throw Error::Simple -text => "Oops!" if $other_condition; + } + catch Error::IO with { + my $E = shift; + print STDERR "File ", $E->{'-file'}, " had a problem\n"; + } + except { + my $E = shift; + my $general_handler=sub {send_message $E->{-description}}; + return { + UserException1 => $general_handler, + UserException2 => $general_handler + }; + } + otherwise { + print STDERR "Well I don't know what to say\n"; + } + finally { + close_the_garage_door_already(); # Should be reliable + }; # Don't forget the trailing ; or you might be surprised + +=head1 DESCRIPTION + +The C package provides two interfaces. Firstly C provides +a procedural interface to exception handling. Secondly C is a +base class for errors/exceptions that can either be thrown, for +subsequent catch, or can simply be recorded. + +Errors in the class C should not be thrown directly, but the +user should throw errors from a sub-class of C. + +=head1 PROCEDURAL INTERFACE + +C exports subroutines to perform exception handling. These will +be exported if the C<:try> tag is used in the C line. + +=over 4 + +=item try BLOCK CLAUSES + +C is the main subroutine called by the user. All other subroutines +exported are clauses to the try subroutine. + +The BLOCK will be evaluated and, if no error is throw, try will return +the result of the block. + +C are the subroutines below, which describe what to do in the +event of an error being thrown within BLOCK. + +=item catch CLASS with BLOCK + +This clauses will cause all errors that satisfy C<$err-Eisa(CLASS)> +to be caught and handled by evaluating C. + +C will be passed two arguments. The first will be the error +being thrown. The second is a reference to a scalar variable. If this +variable is set by the catch block then, on return from the catch +block, try will continue processing as if the catch block was never +found. + +To propagate the error the catch block may call C<$err-Ethrow> + +If the scalar reference by the second argument is not set, and the +error is not thrown. Then the current try block will return with the +result from the catch block. + +=item except BLOCK + +When C is looking for a handler, if an except clause is found +C is evaluated. The return value from this block should be a +HASHREF or a list of key-value pairs, where the keys are class names +and the values are CODE references for the handler of errors of that +type. + +=item otherwise BLOCK + +Catch any error by executing the code in C + +When evaluated C will be passed one argument, which will be the +error being processed. + +Only one otherwise block may be specified per try block + +=item finally BLOCK + +Execute the code in C either after the code in the try block has +successfully completed, or if the try block throws an error then +C will be executed after the handler has completed. + +If the handler throws an error then the error will be caught, the +finally block will be executed and the error will be re-thrown. + +Only one finally block may be specified per try block + +=back + +=head1 CLASS INTERFACE + +=head2 CONSTRUCTORS + +The C object is implemented as a HASH. This HASH is initialized +with the arguments that are passed to it's constructor. The elements +that are used by, or are retrievable by the C class are listed +below, other classes may add to these. + + -file + -line + -text + -value + -object + +If C<-file> or C<-line> are not specified in the constructor arguments +then these will be initialized with the file name and line number where +the constructor was called from. + +If the error is associated with an object then the object should be +passed as the C<-object> argument. This will allow the C package +to associate the error with the object. + +The C package remembers the last error created, and also the +last error associated with a package. This could either be the last +error created by a sub in that package, or the last error which passed +an object blessed into that package as the C<-object> argument. + +=over 4 + +=item throw ( [ ARGS ] ) + +Create a new C object and throw an error, which will be caught +by a surrounding C block, if there is one. Otherwise it will cause +the program to exit. + +C may also be called on an existing error to re-throw it. + +=item with ( [ ARGS ] ) + +Create a new C object and returns it. This is defined for +syntactic sugar, eg + + die with Some::Error ( ... ); + +=item record ( [ ARGS ] ) + +Create a new C object and returns it. This is defined for +syntactic sugar, eg + + record Some::Error ( ... ) + and return; + +=back + +=head2 STATIC METHODS + +=over 4 + +=item prior ( [ PACKAGE ] ) + +Return the last error created, or the last error associated with +C + +=item flush ( [ PACKAGE ] ) + +Flush the last error created, or the last error associated with +C.It is necessary to clear the error stack before exiting the +package or uncaught errors generated using C will be reported. + + $Error->flush; + +=cut + +=back + +=head2 OBJECT METHODS + +=over 4 + +=item stacktrace + +If the variable C<$Error::Debug> was non-zero when the error was +created, then C returns a string created by calling +C. If the variable was zero the C returns +the text of the error appended with the filename and line number of +where the error was created, providing the text does not end with a +newline. + +=item object + +The object this error was associated with + +=item file + +The file where the constructor of this error was called from + +=item line + +The line where the constructor of this error was called from + +=item text + +The text of the error + +=back + +=head2 OVERLOAD METHODS + +=over 4 + +=item stringify + +A method that converts the object into a string. This method may simply +return the same as the C method, or it may append more +information. For example the file name and line number. + +By default this method returns the C<-text> argument that was passed to +the constructor, or the string C<"Died"> if none was given. + +=item value + +A method that will return a value that can be associated with the +error. For example if an error was created due to the failure of a +system call, then this may return the numeric value of C<$!> at the +time. + +By default this method returns the C<-value> argument that was passed +to the constructor. + +=back + +=head1 PRE-DEFINED ERROR CLASSES + +=over 4 + +=item Error::Simple + +This class can be used to hold simple error strings and values. It's +constructor takes two arguments. The first is a text value, the second +is a numeric value. These values are what will be returned by the +overload methods. + +If the text value ends with C as $@ strings do, then +this infomation will be used to set the C<-file> and C<-line> arguments +of the error object. + +This class is used internally if an eval'd block die's with an error +that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified) + +=back + +=head1 $Error::ObjectifyCallback + +This variable holds a reference to a subroutine that converts errors that +are plain strings to objects. It is used by Error.pm to convert textual +errors to objects, and can be overrided by the user. + +It accepts a single argument which is a hash reference to named parameters. +Currently the only named parameter passed is C<'text'> which is the text +of the error, but others may be available in the future. + +For example the following code will cause Error.pm to throw objects of the +class MyError::Bar by default: + + sub throw_MyError_Bar + { + my $args = shift; + my $err = MyError::Bar->new(); + $err->{'MyBarText'} = $args->{'text'}; + return $err; + } + + { + local $Error::ObjectifyCallback = \&throw_MyError_Bar; + + # Error handling here. + } + +=head1 KNOWN BUGS + +None, but that does not mean there are not any. + +=head1 AUTHORS + +Graham Barr + +The code that inspired me to write this was originally written by +Peter Seibel and adapted by Jesse Glick +. + +=head1 MAINTAINER + +Shlomi Fish + +=head1 PAST MAINTAINERS + +Arun Kumar U + +=cut -- cgit v0.10.2-6-g49f6 From 1434dbce02aaa96ae5d0a1f4f000cce5727498fa Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Fri, 30 Jun 2006 01:09:26 -0400 Subject: Delete manuals if compiling without docs Otherwise, rpm would complain about unpacked files. Signed-off-by: Pavel Roskin Signed-off-by: Junio C Hamano diff --git a/git.spec.in b/git.spec.in index 8ccd256..b8feda3 100644 --- a/git.spec.in +++ b/git.spec.in @@ -86,6 +86,8 @@ make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \ (find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files %if %{!?_without_docs:1}0 (find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files +%else +rm -rf $RPM_BUILD_ROOT%{_mandir} %endif %clean -- cgit v0.10.2-6-g49f6 From b9795608c4d82ba119d78980b479d78bdfe753b6 Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Fri, 30 Jun 2006 01:09:28 -0400 Subject: Make perl interface a separate package Install it as a vendor package. Remove .packlist, perllocal.pod, Git.bs. Require perl(Error) for building so that our Error.pm is not installed. Signed-off-by: Pavel Roskin Signed-off-by: Junio C Hamano diff --git a/git.spec.in b/git.spec.in index b8feda3..6d90034 100644 --- a/git.spec.in +++ b/git.spec.in @@ -9,7 +9,7 @@ URL: http://kernel.org/pub/software/scm/git/ Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk +Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, perl-Git %description This is a stupid (but extremely fast) directory content manager. It @@ -70,6 +70,16 @@ Requires: git-core = %{version}-%{release}, tk >= 8.4 %description -n gitk Git revision tree visualiser ('gitk') +%package -n perl-Git +Summary: Perl interface to Git +Group: Development/Libraries +Requires: git-core = %{version}-%{release} +Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) +BuildRequires: perl(Error) + +%description -n perl-Git +Perl interface to Git + %prep %setup -q @@ -80,10 +90,14 @@ make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \ %install rm -rf $RPM_BUILD_ROOT make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \ - prefix=%{_prefix} mandir=%{_mandir} \ + prefix=%{_prefix} mandir=%{_mandir} INSTALLDIRS=vendor \ install %{!?_without_docs: install-doc} +find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';' (find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files +(find $RPM_BUILD_ROOT%{perl_vendorarch} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files %if %{!?_without_docs:1}0 (find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files %else @@ -131,6 +145,9 @@ rm -rf $RPM_BUILD_ROOT %{!?_without_docs: %{_mandir}/man1/*gitk*.1*} %{!?_without_docs: %doc Documentation/*gitk*.html } +%files -n perl-Git -f perl-files +%defattr(-,root,root) + %files core -f bin-man-doc-files %defattr(-,root,root) %{_datadir}/git-core/ -- cgit v0.10.2-6-g49f6 From e6634ac9841f8df3ce1c0c461c677faf2d59af3e Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 2 Jul 2006 01:38:56 +0200 Subject: Git.pm: Remove PerlIO usage from Git.xs PerlIO_*() is not portable before 5.7.3, according to ppport.h, and it's more clear what is going on when we do it in the Perl part of the Git module anyway. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 0581447..b4ee88b 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -511,7 +511,19 @@ are involved. =cut -# Implemented in Git.xs. +sub hash_object { + my ($self, $type, $file) = _maybe_self(@_); + + # hash_object_* implemented in Git.xs. + + if (ref($file) eq 'GLOB') { + my $hash = hash_object_pipe($type, fileno($file)); + close $file; + return $hash; + } else { + hash_object_file($type, $file); + } +} diff --git a/perl/Git.xs b/perl/Git.xs index 3030ba9..cb23261 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -104,42 +104,36 @@ CODE: } char * -xs_hash_object(type, file) +xs_hash_object_pipe(type, fd) char *type; - SV *file; + int fd; CODE: { unsigned char sha1[20]; - if (SvTYPE(file) == SVt_RV) - file = SvRV(file); - - if (SvTYPE(file) == SVt_PVGV) { - /* Filehandle */ - PerlIO *pio; - - pio = IoIFP(sv_2io(file)); - if (!pio) - croak("You passed me something weird - a dir glob?"); - /* XXX: I just hope PerlIO didn't read anything from it yet. - * --pasky */ - if (index_pipe(sha1, PerlIO_fileno(pio), type, 0)) - croak("Unable to hash given filehandle"); - /* Avoid any nasty surprises. */ - PerlIO_close(pio); - - } else { - /* String */ - char *path = SvPV_nolen(file); - int fd = open(path, O_RDONLY); - struct stat st; - - if (fd < 0 || - fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, 0, type)) - croak("Unable to hash %s", path); - close(fd); - } + if (index_pipe(sha1, fd, type, 0)) + croak("Unable to hash given filehandle"); + RETVAL = sha1_to_hex(sha1); +} +OUTPUT: + RETVAL + +char * +xs_hash_object_file(type, path) + char *type; + char *path; +CODE: +{ + unsigned char sha1[20]; + int fd = open(path, O_RDONLY); + struct stat st; + + if (fd < 0 || + fstat(fd, &st) < 0 || + index_fd(sha1, fd, &st, 0, type)) + croak("Unable to hash %s", path); + close(fd); + RETVAL = sha1_to_hex(sha1); } OUTPUT: -- cgit v0.10.2-6-g49f6 From e2a38710941775761298d0bd7e6be2e7039fcd3d Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 2 Jul 2006 01:48:32 +0200 Subject: Git.pm: Avoid ppport.h This makes us not include ppport.h which seems not to give us anything real anyway; it is useful for checking for portability warts but since Devel::PPPort is a portability wart itself, we shouldn't require it for build. You can check for portability problems by calling make check in perl/. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.xs b/perl/Git.xs index cb23261..51bfac3 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -15,8 +15,6 @@ #include "perl.h" #include "XSUB.h" -#include "ppport.h" - #undef die diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 25ae54a..97ee9af 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -5,6 +5,11 @@ sub MY::postamble { instlibdir: @echo '$(INSTALLSITEARCH)' +check: + perl -MDevel::PPPort -le 'Devel::PPPort::WriteFile(".ppport.h")' && \ + perl .ppport.h --compat-version=5.6.0 Git.xs && \ + rm .ppport.h + MAKE_FRAG } @@ -24,8 +29,3 @@ WriteMakefile( MYEXTLIB => '../libgit.a', INC => '-I. -I..', ); - - -use Devel::PPPort; - --s 'ppport.h' or Devel::PPPort::WriteFile(); -- cgit v0.10.2-6-g49f6 From d78f099d8956947576cd5ccc1c5c13f94075b476 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 2 Jul 2006 11:53:03 +0200 Subject: Git.xs: older perl do not know const char * Both of these casts _should_ be safe, since you do not want to muck around with the version or the path anyway. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/perl/Git.xs b/perl/Git.xs index 51bfac3..c824210 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -59,7 +59,7 @@ BOOT: # /* TODO: xs_call_gate(). See Git.pm. */ -const char * +char * xs_version() CODE: { @@ -69,11 +69,11 @@ OUTPUT: RETVAL -const char * +char * xs_exec_path() CODE: { - RETVAL = git_exec_path(); + RETVAL = (char *)git_exec_path(); } OUTPUT: RETVAL -- cgit v0.10.2-6-g49f6 From 65a4e98a22eab9317a05d1485c7c5a9c5befd589 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 2 Jul 2006 22:57:17 +0200 Subject: Git.pm: Don't #define around die Back in the old days, we called Git's die() from the .xs code, but we had to hijack Perl's die() for that. Now we don't call Git's die() so no need to do the hijacking and it silences a compiler warning. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.xs b/perl/Git.xs index c824210..2bbec43 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -8,15 +8,11 @@ #include "../cache.h" #include "../exec_cmd.h" -#define die perlyshadow_die__ - /* XS and Perl interface */ #include "EXTERN.h" #include "perl.h" #include "XSUB.h" -#undef die - static char * report_xs(const char *prefix, const char *err, va_list params) -- cgit v0.10.2-6-g49f6 From d3140f5c2a6b42361bca960f627b00264d5c7372 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Jul 2006 16:49:12 -0700 Subject: Perly Git: make sure we do test the freshly built one. We could BEGIN { push @INC, '@@INSTLIBDIR@@'; } but that is not a good idea for normal execution. The would prevent a workaround for a user who is trying to override an old, faulty Git.pm installed on the system path with a newer version installed under $HOME/. Signed-off-by: Junio C Hamano diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl index 1b23fa1..a9805dd 100755 --- a/git-fmt-merge-msg.perl +++ b/git-fmt-merge-msg.perl @@ -5,7 +5,11 @@ # Read .git/FETCH_HEAD and make a human readable merge message # by grouping branches and tags together to form a single line. -BEGIN { unshift @INC, '@@INSTLIBDIR@@'; } +BEGIN { + unless (exists $ENV{'RUNNING_GIT_TESTS'}) { + unshift @INC, '@@INSTLIBDIR@@'; + } +} use strict; use Git; use Error qw(:try); diff --git a/git-mv.perl b/git-mv.perl index a604896..5134b80 100755 --- a/git-mv.perl +++ b/git-mv.perl @@ -6,7 +6,11 @@ # This file is licensed under the GPL v2, or a later version # at the discretion of Linus Torvalds. -BEGIN { unshift @INC, '@@INSTLIBDIR@@'; } +BEGIN { + unless (exists $ENV{'RUNNING_GIT_TESTS'}) { + unshift @INC, '@@INSTLIBDIR@@'; + } +} use warnings; use strict; use Getopt::Std; diff --git a/t/test-lib.sh b/t/test-lib.sh index fba0c51..298c6ca 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -206,8 +206,9 @@ PYTHON=`sed -e '1{ PYTHONPATH=$(pwd)/../compat export PYTHONPATH } +RUNNING_GIT_TESTS=YesWeAre PERL5LIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git -export PERL5LIB +export PERL5LIB RUNNING_GIT_TESTS test -d ../templates/blt || { error "You haven't built things yet, have you?" } -- cgit v0.10.2-6-g49f6 From 3c767a08243244b18d48315f8433ba07e435f654 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Jul 2006 23:54:47 -0700 Subject: INSTALL: a tip for running after building but without installing. Signed-off-by: Junio C Hamano diff --git a/INSTALL b/INSTALL index f8337e2..ed502de 100644 --- a/INSTALL +++ b/INSTALL @@ -29,6 +29,19 @@ Issues of note: has been actively developed since 1997, and people have moved over to graphical file managers. + - You can use git after building but without installing if you + wanted to. Various git commands need to find other git + commands and scripts to do their work, so you would need to + arrange a few environment variables to tell them that their + friends will be found in your built source area instead of at + their standard installation area. Something like this works + for me: + + GIT_EXEC_PATH=`pwd` + PATH=`pwd`:$PATH + PERL5LIB=`pwd`/perl/blib/lib:`pwd`/perl/blib/arch/auto/Git + export GIT_EXEC_PATH PATH PERL5LIB + - Git is reasonably self-sufficient, but does depend on a few external programs and libraries: -- cgit v0.10.2-6-g49f6 From 6fcca938b05c33bcd8b502d6b6f178e377609fa3 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 3 Jul 2006 23:16:32 +0200 Subject: Use $GITPERLLIB instead of $RUNNING_GIT_TESTS and centralize @INC munging This makes the Git perl scripts check $GITPERLLIB instead of $RUNNING_GIT_TESTS, which makes more sense if you are setting up your shell environment to use a non-installed Git instance. It also weeds out the @INC munging from the individual scripts and makes Makefile add it during the .perl files processing, so that we can change just a single place when we modify this shared logic. It looks ugly in the scripts, too. ;-) And instead of doing arcane things with the @INC array, we just do 'use lib' instead, which is essentialy the same thing anyway. I first want to do three separate patches but it turned out that it's quite a lot neater when bundled together, so I hope it's ok. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/INSTALL b/INSTALL index ed502de..4e8f883 100644 --- a/INSTALL +++ b/INSTALL @@ -39,8 +39,8 @@ Issues of note: GIT_EXEC_PATH=`pwd` PATH=`pwd`:$PATH - PERL5LIB=`pwd`/perl/blib/lib:`pwd`/perl/blib/arch/auto/Git - export GIT_EXEC_PATH PATH PERL5LIB + GITPERLLIB=`pwd`/perl/blib/lib:`pwd`/perl/blib/arch/auto/Git + export GIT_EXEC_PATH PATH GITPERLLIB - Git is reasonably self-sufficient, but does depend on a few external programs and libraries: diff --git a/Makefile b/Makefile index 7030167..71657ec 100644 --- a/Makefile +++ b/Makefile @@ -550,7 +550,9 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl rm -f $@ $@+ INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \ - sed -e '1s|#!.*perl\(.*\)|#!$(PERL_PATH_SQ)\1|' \ + sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|1' \ + -e '2i\ + use lib (split(/:/, $$ENV{GITPERLLIB} || '\'"$$INSTLIBDIR"\''));' \ -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ $@.perl >$@+ diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl index a9805dd..f86231e 100755 --- a/git-fmt-merge-msg.perl +++ b/git-fmt-merge-msg.perl @@ -5,11 +5,6 @@ # Read .git/FETCH_HEAD and make a human readable merge message # by grouping branches and tags together to form a single line. -BEGIN { - unless (exists $ENV{'RUNNING_GIT_TESTS'}) { - unshift @INC, '@@INSTLIBDIR@@'; - } -} use strict; use Git; use Error qw(:try); diff --git a/git-mv.perl b/git-mv.perl index 5134b80..322b9fd 100755 --- a/git-mv.perl +++ b/git-mv.perl @@ -6,11 +6,6 @@ # This file is licensed under the GPL v2, or a later version # at the discretion of Linus Torvalds. -BEGIN { - unless (exists $ENV{'RUNNING_GIT_TESTS'}) { - unshift @INC, '@@INSTLIBDIR@@'; - } -} use warnings; use strict; use Getopt::Std; diff --git a/t/test-lib.sh b/t/test-lib.sh index 298c6ca..ad9796e 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -206,9 +206,8 @@ PYTHON=`sed -e '1{ PYTHONPATH=$(pwd)/../compat export PYTHONPATH } -RUNNING_GIT_TESTS=YesWeAre -PERL5LIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git -export PERL5LIB RUNNING_GIT_TESTS +GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git +export GITPERLLIB test -d ../templates/blt || { error "You haven't built things yet, have you?" } -- cgit v0.10.2-6-g49f6 From dc2613de8633cecb1c0759657eadf6a637cebfa5 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 3 Jul 2006 22:47:55 +0200 Subject: Git.pm: Add config() method This accessor will retrieve value(s) of the given configuration variable. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 803c0d5..cc72fa9 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -54,7 +54,8 @@ OPTIONS --get:: Get the value for a given key (optionally filtered by a regex - matching the value). + matching the value). Returns error code 1 if the key was not + found and error code 2 if multiple key values were found. --get-all:: Like get, but does not fail if the number of values for the key diff --git a/perl/Git.pm b/perl/Git.pm index b4ee88b..24fd7ce 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -473,7 +473,6 @@ and the directory must exist. sub wc_chdir { my ($self, $subdir) = @_; - $self->wc_path() or throw Error::Simple("bare repository"); @@ -486,6 +485,42 @@ sub wc_chdir { } +=item config ( VARIABLE ) + +Retrieve the configuration C in the same manner as C +does. In scalar context requires the variable to be set only one time +(exception is thrown otherwise), in array context returns allows the +variable to be set multiple times and returns all the values. + +Must be called on a repository instance. + +This currently wraps command('repo-config') so it is not so fast. + +=cut + +sub config { + my ($self, $var) = @_; + $self->repo_path() + or throw Error::Simple("not a repository"); + + try { + if (wantarray) { + return $self->command('repo-config', '--get-all', $var); + } else { + return $self->command_oneline('repo-config', '--get', $var); + } + } catch Git::Error::Command with { + my $E = shift; + if ($E->value() == 1) { + # Key not found. + return undef; + } else { + throw $E; + } + }; +} + + =item hash_object ( TYPE, FILENAME ) =item hash_object ( TYPE, FILEHANDLE ) diff --git a/repo-config.c b/repo-config.c index 743f02b..c7ed0ac 100644 --- a/repo-config.c +++ b/repo-config.c @@ -118,7 +118,7 @@ static int get_value(const char* key_, const char* regex_) if (do_all) ret = !seen; else - ret = (seen == 1) ? 0 : 1; + ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1; free_strings: if (repo_config) -- cgit v0.10.2-6-g49f6 From 3cb8caf7294bf8909b924ab63ca7d8f90917e677 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 3 Jul 2006 22:47:58 +0200 Subject: Convert git-send-email to use Git.pm Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index c5d9e73..e794e44 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -21,6 +21,7 @@ use warnings; use Term::ReadLine; use Getopt::Long; use Data::Dumper; +use Git; # most mail servers generate the Date: header, but not all... $ENV{LC_ALL} = 'C'; @@ -46,6 +47,8 @@ my $smtp_server; # Example reply to: #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>'; +my $repo = Git->repository(); + my $term = new Term::ReadLine 'git-send-email'; # Begin by accumulating all the variables (defined above), that we will end up @@ -81,23 +84,9 @@ foreach my $entry (@bcclist) { # Now, let's fill any that aren't set in with defaults: -sub gitvar { - my ($var) = @_; - my $fh; - my $pid = open($fh, '-|'); - die "$!" unless defined $pid; - if (!$pid) { - exec('git-var', $var) or die "$!"; - } - my ($val) = <$fh>; - close $fh or die "$!"; - chomp($val); - return $val; -} - sub gitvar_ident { my ($name) = @_; - my $val = gitvar($name); + my $val = $repo->command('var', $name); my @field = split(/\s+/, $val); return join(' ', @field[0...(@field-3)]); } @@ -106,8 +95,8 @@ my ($author) = gitvar_ident('GIT_AUTHOR_IDENT'); my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT'); my %aliases; -chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`); -chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`); +my @alias_files = $repo->config('sendemail.aliasesfile'); +my $aliasfiletype = $repo->config('sendemail.aliasfiletype'); my %parse_alias = ( # multiline formats can be supported in the future mutt => sub { my $fh = shift; while (<$fh>) { @@ -132,7 +121,7 @@ my %parse_alias = ( }}} ); -if (@alias_files && defined $parse_alias{$aliasfiletype}) { +if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { foreach my $file (@alias_files) { open my $fh, '<', $file or die "opening $file: $!\n"; $parse_alias{$aliasfiletype}->($fh); @@ -374,10 +363,7 @@ sub send_message my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++)); my $gitversion = '@@GIT_VERSION@@'; if ($gitversion =~ m/..GIT_VERSION../) { - $gitversion = `git --version`; - chomp $gitversion; - # keep only what's after the last space - $gitversion =~ s/^.* //; + $gitversion = Git::version(); } my $header = "From: $from -- cgit v0.10.2-6-g49f6 From c7a30e56840b089c1d110b312475f692b5c1b6a4 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 3 Jul 2006 22:48:01 +0200 Subject: Git.pm: Introduce ident() and ident_person() methods These methods can retrieve/parse the author/committer ident. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index e794e44..79e82f5 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -84,15 +84,8 @@ foreach my $entry (@bcclist) { # Now, let's fill any that aren't set in with defaults: -sub gitvar_ident { - my ($name) = @_; - my $val = $repo->command('var', $name); - my @field = split(/\s+/, $val); - return join(' ', @field[0...(@field-3)]); -} - -my ($author) = gitvar_ident('GIT_AUTHOR_IDENT'); -my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT'); +my ($author) = $repo->ident_person('author'); +my ($committer) = $repo->ident_person('committer'); my %aliases; my @alias_files = $repo->config('sendemail.aliasesfile'); diff --git a/perl/Git.pm b/perl/Git.pm index 24fd7ce..9ce9fcd 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -521,6 +521,55 @@ sub config { } +=item ident ( TYPE | IDENTSTR ) + +=item ident_person ( TYPE | IDENTSTR | IDENTARRAY ) + +This suite of functions retrieves and parses ident information, as stored +in the commit and tag objects or produced by C (thus +C can be either I or I; case is insignificant). + +The C method retrieves the ident information from C +and either returns it as a scalar string or as an array with the fields parsed. +Alternatively, it can take a prepared ident string (e.g. from the commit +object) and just parse it. + +C returns the person part of the ident - name and email; +it can take the same arguments as C or the array returned by C. + +The synopsis is like: + + my ($name, $email, $time_tz) = ident('author'); + "$name <$email>" eq ident_person('author'); + "$name <$email>" eq ident_person($name); + $time_tz =~ /^\d+ [+-]\d{4}$/; + +Both methods must be called on a repository instance. + +=cut + +sub ident { + my ($self, $type) = @_; + my $identstr; + if (lc $type eq lc 'committer' or lc $type eq lc 'author') { + $identstr = $self->command_oneline('var', 'GIT_'.uc($type).'_IDENT'); + } else { + $identstr = $type; + } + if (wantarray) { + return $identstr =~ /^(.*) <(.*)> (\d+ [+-]\d{4})$/; + } else { + return $identstr; + } +} + +sub ident_person { + my ($self, @ident) = @_; + $#ident == 0 and @ident = $self->ident($ident[0]); + return "$ident[0] <$ident[1]>"; +} + + =item hash_object ( TYPE, FILENAME ) =item hash_object ( TYPE, FILEHANDLE ) -- cgit v0.10.2-6-g49f6 From 998c4daaf4a921fb03d478b50d6e06223326d7ef Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 7 Jul 2006 13:04:35 -0700 Subject: Work around sed and make interactions on the backslash at the end of line. Traditionally 'i' and 'a' commands to sed have been unfriendly with make, primarily because different make implementations did unexpected things to backslashes at the end of lines. So work it around by not using 'i' command. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 71657ec..01b9a94 100644 --- a/Makefile +++ b/Makefile @@ -550,9 +550,13 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl rm -f $@ $@+ INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \ - sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|1' \ - -e '2i\ - use lib (split(/:/, $$ENV{GITPERLLIB} || '\'"$$INSTLIBDIR"\''));' \ + sed -e '1{' \ + -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + -e ' h' \ + -e ' s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \ + -e ' H' \ + -e ' x' \ + -e '}' \ -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ $@.perl >$@+ -- cgit v0.10.2-6-g49f6 From 0270083ded143fd49841e3d3d0cac5eb06081d2a Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 3 Jul 2006 22:48:03 +0200 Subject: Make it possible to set up libgit directly (instead of from the environment) This introduces a setup_git() function which is essentialy a (public) backend for setup_git_env() which lets anyone specify custom sources for the various paths instead of environment variables. Since the repositories may get switched on the fly, this also updates code that caches paths to invalidate them properly; I hope neither of those is a sweet spot. It is used by Git.xs' xs__call_gate() to set up per-repository data for libgit's consumption. No code actually takes advantage of it yet but get_object() will in the next patches. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 8719939..962f2fc 100644 --- a/cache.h +++ b/cache.h @@ -116,6 +116,9 @@ extern struct cache_entry **active_cache; extern unsigned int active_nr, active_alloc, active_cache_changed; extern struct cache_tree *active_cache_tree; +extern void setup_git(char *new_git_dir, char *new_git_object_dir, + char *new_git_index_file, char *new_git_graft_file); + #define GIT_DIR_ENVIRONMENT "GIT_DIR" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" diff --git a/commit.c b/commit.c index e51ffa1..17f51c2 100644 --- a/commit.c +++ b/commit.c @@ -163,6 +163,14 @@ int register_commit_graft(struct commit_graft *graft, int ignore_dups) return 0; } +void free_commit_grafts(void) +{ + int pos = commit_graft_nr; + while (pos >= 0) + free(commit_graft[pos--]); + commit_graft_nr = 0; +} + struct commit_graft *read_graft_line(char *buf, int len) { /* The format is just "Commit Parent1 Parent2 ...\n" */ @@ -215,11 +223,18 @@ int read_graft_file(const char *graft_file) static void prepare_commit_graft(void) { static int commit_graft_prepared; - char *graft_file; + static char *last_graft_file; + char *graft_file = get_graft_file(); + + if (last_graft_file) { + if (!strcmp(graft_file, last_graft_file)) + return; + free_commit_grafts(); + } + if (last_graft_file) + free(last_graft_file); + last_graft_file = strdup(graft_file); - if (commit_graft_prepared) - return; - graft_file = get_graft_file(); read_graft_file(graft_file); commit_graft_prepared = 1; } diff --git a/environment.c b/environment.c index 3de8eb3..6b64d11 100644 --- a/environment.c +++ b/environment.c @@ -21,28 +21,61 @@ char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; const char *apply_default_whitespace = NULL; +static int dyn_git_object_dir, dyn_git_index_file, dyn_git_graft_file; static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; -static void setup_git_env(void) + +void setup_git(char *new_git_dir, char *new_git_object_dir, + char *new_git_index_file, char *new_git_graft_file) { - git_dir = getenv(GIT_DIR_ENVIRONMENT); + git_dir = new_git_dir; if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; - git_object_dir = getenv(DB_ENVIRONMENT); + + if (dyn_git_object_dir) + free(git_object_dir); + git_object_dir = new_git_object_dir; if (!git_object_dir) { git_object_dir = xmalloc(strlen(git_dir) + 9); sprintf(git_object_dir, "%s/objects", git_dir); + dyn_git_object_dir = 1; + } else { + dyn_git_object_dir = 0; } + + if (git_refs_dir) + free(git_refs_dir); git_refs_dir = xmalloc(strlen(git_dir) + 6); sprintf(git_refs_dir, "%s/refs", git_dir); - git_index_file = getenv(INDEX_ENVIRONMENT); + + if (dyn_git_index_file) + free(git_index_file); + git_index_file = new_git_index_file; if (!git_index_file) { git_index_file = xmalloc(strlen(git_dir) + 7); sprintf(git_index_file, "%s/index", git_dir); + dyn_git_index_file = 1; + } else { + dyn_git_index_file = 0; } - git_graft_file = getenv(GRAFT_ENVIRONMENT); - if (!git_graft_file) + + if (dyn_git_graft_file) + free(git_graft_file); + git_graft_file = new_git_graft_file; + if (!git_graft_file) { git_graft_file = strdup(git_path("info/grafts")); + dyn_git_graft_file = 1; + } else { + dyn_git_graft_file = 0; + } +} + +static void setup_git_env(void) +{ + setup_git(getenv(GIT_DIR_ENVIRONMENT), + getenv(DB_ENVIRONMENT), + getenv(INDEX_ENVIRONMENT), + getenv(GRAFT_ENVIRONMENT)); } char *get_git_dir(void) diff --git a/perl/Git.pm b/perl/Git.pm index 9ce9fcd..9da15e9 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -98,6 +98,8 @@ XSLoader::load('Git', $VERSION); } +my $instance_id = 0; + =head1 CONSTRUCTORS @@ -215,7 +217,7 @@ sub repository { delete $opts{Directory}; } - $self = { opts => \%opts }; + $self = { opts => \%opts, id => $instance_id++ }; bless $self, $class; } @@ -833,11 +835,10 @@ sub _call_gate { if (defined $self) { # XXX: We ignore the WorkingCopy! To properly support # that will require heavy changes in libgit. + # For now, when we will need to do it we could temporarily + # chdir() there and then chdir() back after the call is done. - # XXX: And we ignore everything else as well. libgit - # at least needs to be extended to let us specify - # the $GIT_DIR instead of looking it up in environment. - #xs_call_gate($self->{opts}->{Repository}); + xs__call_gate($self->{id}, $self->repo_path()); } # Having to call throw from the C code is a sure path to insanity. diff --git a/perl/Git.xs b/perl/Git.xs index 2bbec43..6ed26a2 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -52,7 +52,21 @@ BOOT: } -# /* TODO: xs_call_gate(). See Git.pm. */ +void +xs__call_gate(repoid, git_dir) + long repoid; + char *git_dir; +CODE: +{ + static long last_repoid; + if (repoid != last_repoid) { + setup_git(git_dir, + getenv(DB_ENVIRONMENT), + getenv(INDEX_ENVIRONMENT), + getenv(GRAFT_ENVIRONMENT)); + last_repoid = repoid; + } +} char * diff --git a/sha1_file.c b/sha1_file.c index 8179630..ab64543 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -126,16 +126,22 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) char *sha1_file_name(const unsigned char *sha1) { static char *name, *base; + static const char *last_objdir; + const char *sha1_file_directory = get_object_directory(); - if (!base) { - const char *sha1_file_directory = get_object_directory(); + if (!last_objdir || strcmp(last_objdir, sha1_file_directory)) { int len = strlen(sha1_file_directory); + if (base) + free(base); base = xmalloc(len + 60); memcpy(base, sha1_file_directory, len); memset(base+len, 0, 60); base[len] = '/'; base[len+3] = '/'; name = base + len + 1; + if (last_objdir) + free((char *) last_objdir); + last_objdir = strdup(sha1_file_directory); } fill_sha1_path(name, sha1); return base; @@ -145,14 +151,20 @@ char *sha1_pack_name(const unsigned char *sha1) { static const char hex[] = "0123456789abcdef"; static char *name, *base, *buf; + static const char *last_objdir; + const char *sha1_file_directory = get_object_directory(); int i; - if (!base) { - const char *sha1_file_directory = get_object_directory(); + if (!last_objdir || strcmp(last_objdir, sha1_file_directory)) { int len = strlen(sha1_file_directory); + if (base) + free(base); base = xmalloc(len + 60); sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory); name = base + len + 11; + if (last_objdir) + free((char *) last_objdir); + last_objdir = strdup(sha1_file_directory); } buf = name; @@ -170,14 +182,20 @@ char *sha1_pack_index_name(const unsigned char *sha1) { static const char hex[] = "0123456789abcdef"; static char *name, *base, *buf; + static const char *last_objdir; + const char *sha1_file_directory = get_object_directory(); int i; - if (!base) { - const char *sha1_file_directory = get_object_directory(); + if (!last_objdir || strcmp(last_objdir, sha1_file_directory)) { int len = strlen(sha1_file_directory); + if (base) + free(base); base = xmalloc(len + 60); sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory); name = base + len + 11; + if (last_objdir) + free((char *) last_objdir); + last_objdir = strdup(sha1_file_directory); } buf = name; diff --git a/sha1_name.c b/sha1_name.c index f2cbafa..c698c1b 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -12,15 +12,21 @@ static int find_short_object_filename(int len, const char *name, unsigned char * char hex[40]; int found = 0; static struct alternate_object_database *fakeent; + static const char *last_objdir; + const char *objdir = get_object_directory(); - if (!fakeent) { - const char *objdir = get_object_directory(); + if (!last_objdir || strcmp(last_objdir, objdir)) { int objdir_len = strlen(objdir); int entlen = objdir_len + 43; + if (fakeent) + free(fakeent); fakeent = xmalloc(sizeof(*fakeent) + entlen); memcpy(fakeent->base, objdir, objdir_len); fakeent->name = fakeent->base + objdir_len + 1; fakeent->name[-1] = '/'; + if (last_objdir) + free((char *) last_objdir); + last_objdir = strdup(objdir); } fakeent->next = alt_odb_list; -- cgit v0.10.2-6-g49f6 From 3c479c37f8651d09e1d08b8d6ea9757164ee1235 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 3 Jul 2006 22:48:05 +0200 Subject: Git.pm: Introduce fast get_object() method Direct .xs routine. Note that it does not work 100% correctly when you juggle multiple repository objects, but it is not that bad either. The trouble is that we might reuse packs information for another Git project; that is not an issue since Git depends on uniqueness of SHA1 ids so if we have found the object somewhere else, it is nevertheless going to be the same object. It merely makes object existence detection through this method unreliable; it is duly noted in the documentation. At least that's how I see it, I hope I didn't overlook any other potential problem. I tested it for memory leaks and it appears to be doing ok. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index 9da15e9..f2467bd 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -572,6 +572,24 @@ sub ident_person { } +=item get_object ( TYPE, SHA1 ) + +Return contents of the given object in a scalar string. If the object has +not been found, undef is returned; however, do not rely on this! Currently, +if you use multiple repositories at once, get_object() on one repository +_might_ return the object even though it exists only in another repository. +(But do not rely on this behaviour either.) + +The method must be called on a repository instance. + +Implementation of this method is very fast; no external command calls +are involved. That's why it is broken, too. ;-) + +=cut + +# Implemented in Git.xs. + + =item hash_object ( TYPE, FILENAME ) =item hash_object ( TYPE, FILEHANDLE ) diff --git a/perl/Git.xs b/perl/Git.xs index 6ed26a2..226dd4f 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -111,6 +111,30 @@ CODE: free((char **) argv); } + +SV * +xs_get_object(type, id) + char *type; + char *id; +CODE: +{ + unsigned char sha1[20]; + unsigned long size; + void *buf; + + if (strlen(id) != 40 || get_sha1_hex(id, sha1) < 0) + XSRETURN_UNDEF; + + buf = read_sha1_file(sha1, type, &size); + if (!buf) + XSRETURN_UNDEF; + RETVAL = newSVpvn(buf, size); + free(buf); +} +OUTPUT: + RETVAL + + char * xs_hash_object_pipe(type, fd) char *type; -- cgit v0.10.2-6-g49f6 From 7fb39d5f58efd05e982fe148630edc97ded753b6 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 3 Jul 2006 22:48:07 +0200 Subject: Convert git-annotate to use Git.pm Together with the other converted scripts, this is probably still pu material; it appears to work fine for me, though. The speed gain from get_object() is about 10% (I expected more...). Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/git-annotate.perl b/git-annotate.perl index a6a7a48..d924e87 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -11,6 +11,7 @@ use strict; use Getopt::Long; use POSIX qw(strftime gmtime); use File::Basename qw(basename dirname); +use Git; sub usage() { print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] @@ -29,7 +30,7 @@ sub usage() { exit(1); } -our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1); +our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file, $repo) = (0, 0, 1); my $rc = GetOptions( "long|l" => \$longrev, "time|t" => \$rawtime, @@ -52,6 +53,8 @@ my @stack = ( }, ); +$repo = Git->repository(); + our @filelines = (); if (defined $starting_rev) { @@ -102,15 +105,11 @@ while (my $bound = pop @stack) { push @revqueue, $head; init_claim( defined $starting_rev ? $head : 'dirty'); unless (defined $starting_rev) { - my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename) - or die "Failed to call git diff to check for dirty state: $!"; - - _git_diff_parse($diff, $head, "dirty", ( - 'author' => gitvar_name("GIT_AUTHOR_IDENT"), - 'author_date' => sprintf("%s +0000",time()), - ) - ); - close($diff); + my %ident; + @ident{'author', 'author_email', 'author_date'} = $repo->ident('author'); + my $diff = $repo->command_output_pipe('diff', '-R', 'HEAD', '--', $filename); + _git_diff_parse($diff, $head, "dirty", %ident); + $repo->command_close_pipe($diff); } handle_rev(); @@ -181,8 +180,7 @@ sub git_rev_list { open($revlist, '<' . $rev_file) or die "Failed to open $rev_file : $!"; } else { - $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file) - or die "Failed to exec git-rev-list: $!"; + $revlist = $repo->command_output_pipe('rev-list', '--parents', '--remove-empty', $rev, '--', $file); } my @revs; @@ -191,7 +189,7 @@ sub git_rev_list { my ($rev, @parents) = split /\s+/, $line; push @revs, [ $rev, @parents ]; } - close($revlist); + $repo->command_close_pipe($revlist); printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0); return @revs; @@ -200,8 +198,7 @@ sub git_rev_list { sub find_parent_renames { my ($rev, $file) = @_; - my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev") - or die "Failed to exec git-diff: $!"; + my $patch = $repo->command_output_pipe('diff-tree', '-M50', '-r', '--name-status', '-z', $rev); local $/ = "\0"; my %bound; @@ -227,7 +224,7 @@ sub find_parent_renames { } } } - close($patch); + $repo->command_close_pipe($patch); return \%bound; } @@ -236,14 +233,9 @@ sub find_parent_renames { sub git_find_parent { my ($rev, $filename) = @_; - my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename) - or die "Failed to open git-rev-list to find a single parent: $!"; - - my $parentline = <$revparent>; - chomp $parentline; - my ($revfound,$parent) = split m/\s+/, $parentline; - - close($revparent); + my $parentline = $repo->command_oneline('rev-list', '--remove-empty', + '--parents', '--max-count=1', $rev, '--', $filename); + my ($revfound, $parent) = split m/\s+/, $parentline; return $parent; } @@ -254,13 +246,13 @@ sub git_find_parent { sub git_diff_parse { my ($parent, $rev, %revinfo) = @_; - my $diff = open_pipe("git-diff-tree","-M","-p",$rev,$parent,"--", - $revs{$rev}{'filename'}, $revs{$parent}{'filename'}) - or die "Failed to call git-diff for annotation: $!"; + my $diff = $repo->command_output_pipe('diff-tree', '-M', '-p', + $rev, $parent, '--', + $revs{$rev}{'filename'}, $revs{$parent}{'filename'}); _git_diff_parse($diff, $parent, $rev, %revinfo); - close($diff); + $repo->command_close_pipe($diff); } sub _git_diff_parse { @@ -351,36 +343,25 @@ sub git_cat_file { my $blob = git_ls_tree($rev, $filename); die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob; - my $catfile = open_pipe("git","cat-file", "blob", $blob) - or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!; - - my @lines; - while(<$catfile>) { - chomp; - push @lines, $_; - } - close($catfile); - + my @lines = split(/\n/, $repo->get_object('blob', $blob)); + pop @lines unless $lines[$#lines]; # Trailing newline return @lines; } sub git_ls_tree { my ($rev, $filename) = @_; - my $lstree = open_pipe("git","ls-tree",$rev,$filename) - or die "Failed to call git ls-tree: $!"; - + my $lstree = $repo->command_output_pipe('ls-tree', $rev, $filename); my ($mode, $type, $blob, $tfilename); while(<$lstree>) { chomp; ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4); last if ($tfilename eq $filename); } - close($lstree); + $repo->command_close_pipe($lstree); return $blob if ($tfilename eq $filename); die "git-ls-tree failed to find blob for $filename"; - } @@ -396,25 +377,17 @@ sub claim_line { sub git_commit_info { my ($rev) = @_; - my $commit = open_pipe("git-cat-file", "commit", $rev) - or die "Failed to call git-cat-file: $!"; + my $commit = $repo->get_object('commit', $rev); my %info; - while(<$commit>) { - chomp; - last if (length $_ == 0); - - if (m/^author (.*) <(.*)> (.*)$/) { - $info{'author'} = $1; - $info{'author_email'} = $2; - $info{'author_date'} = $3; - } elsif (m/^committer (.*) <(.*)> (.*)$/) { - $info{'committer'} = $1; - $info{'committer_email'} = $2; - $info{'committer_date'} = $3; + while ($commit =~ /(.*?)\n/g) { + my $line = $1; + if ($line =~ s/^author //) { + @info{'author', 'author_email', 'author_date'} = $repo->ident($line); + } elsif ($line =~ s/^committer//) { + @info{'committer', 'committer_email', 'committer_date'} = $repo->ident($line); } } - close($commit); return %info; } @@ -432,81 +405,3 @@ sub format_date { my $t = $timestamp + $minutes * 60; return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t)); } - -# Copied from git-send-email.perl - We need a Git.pm module.. -sub gitvar { - my ($var) = @_; - my $fh; - my $pid = open($fh, '-|'); - die "$!" unless defined $pid; - if (!$pid) { - exec('git-var', $var) or die "$!"; - } - my ($val) = <$fh>; - close $fh or die "$!"; - chomp($val); - return $val; -} - -sub gitvar_name { - my ($name) = @_; - my $val = gitvar($name); - my @field = split(/\s+/, $val); - return join(' ', @field[0...(@field-4)]); -} - -sub open_pipe { - if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') { - return open_pipe_activestate(@_); - } else { - return open_pipe_normal(@_); - } -} - -sub open_pipe_activestate { - tie *fh, "Git::ActiveStatePipe", @_; - return *fh; -} - -sub open_pipe_normal { - my (@execlist) = @_; - - my $pid = open my $kid, "-|"; - defined $pid or die "Cannot fork: $!"; - - unless ($pid) { - exec @execlist; - die "Cannot exec @execlist: $!"; - } - - return $kid; -} - -package Git::ActiveStatePipe; -use strict; - -sub TIEHANDLE { - my ($class, @params) = @_; - my $cmdline = join " ", @params; - my @data = qx{$cmdline}; - bless { i => 0, data => \@data }, $class; -} - -sub READLINE { - my $self = shift; - if ($self->{i} >= scalar @{$self->{data}}) { - return undef; - } - return $self->{'data'}->[ $self->{i}++ ]; -} - -sub CLOSE { - my $self = shift; - delete $self->{data}; - delete $self->{i}; -} - -sub EOF { - my $self = shift; - return ($self->{i} >= scalar @{$self->{data}}); -} -- cgit v0.10.2-6-g49f6 From 6d297f81373e19d86b8f02cb68120201d1b0ab1d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 8 Jul 2006 18:42:41 +0200 Subject: Status update on merge-recursive in C This is just an update for people being interested. Alex and me were busy with that project for a few days now. While it has progressed nicely, there are quite a couple TODOs in merge-recursive.c, just search for "TODO". For impatient people: yes, it passes all the tests, and yes, according to the evil test Alex did, it is faster than the Python script. But no, it is not yet finished. Biggest points are: - there are still three external calls - in the end, it should not be necessary to write the index more than once (just before exiting) - a lot of things can be refactored to make the code easier and shorter BTW we cannot just plug in git-merge-tree yet, because git-merge-tree does not handle renames at all. This patch is meant for testing, and as such, - it compile the program to git-merge-recur - it adjusts the scripts and tests to use git-merge-recur instead of git-merge-recursive - it provides "TEST", a script to execute the tests regarding -recursive - it inlines the changes to read-cache.c (read_cache_from(), discard_cache() and refresh_cache_entry()) Brought to you by Alex Riesen and Dscho Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 01fb9cf..a749aa4 100644 --- a/Makefile +++ b/Makefile @@ -167,7 +167,8 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X \ git-symbolic-ref$X \ git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \ - git-describe$X git-merge-tree$X git-blame$X git-imap-send$X + git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ + git-merge-recur$X BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ git-count-objects$X git-diff$X git-push$X git-mailsplit$X \ @@ -615,6 +616,11 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) +merge-recursive.o path-list.o: path-list.h +git-merge-recur$X: merge-recursive.o path-list.o $(LIB_FILE) + $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) + $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) $(DIFF_OBJS): diffcore.h diff --git a/TEST b/TEST new file mode 100755 index 0000000..d530983 --- /dev/null +++ b/TEST @@ -0,0 +1,10 @@ +#!/bin/sh -x +cd t || exit +./t3400-rebase.sh "$@" && \ +./t6020-merge-df.sh "$@" && \ +./t3401-rebase-partial.sh "$@" && \ +./t6021-merge-criss-cross.sh "$@" && \ +./t3402-rebase-merge.sh "$@" && \ +./t6022-merge-rename.sh "$@" && \ +./t6010-merge-base.sh "$@" && \ +: diff --git a/cache.h b/cache.h index d433d46..8cc0ccb 100644 --- a/cache.h +++ b/cache.h @@ -115,6 +115,7 @@ static inline unsigned int create_ce_mode(unsigned int mode) extern struct cache_entry **active_cache; extern unsigned int active_nr, active_alloc, active_cache_changed; extern struct cache_tree *active_cache_tree; +extern int cache_errno; #define GIT_DIR_ENVIRONMENT "GIT_DIR" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" @@ -142,13 +143,16 @@ extern void verify_non_filename(const char *prefix, const char *name); /* Initialize and use the cache information */ extern int read_cache(void); +extern int read_cache_from(const char *path); extern int write_cache(int newfd, struct cache_entry **cache, int entries); +extern int discard_cache(void); extern int verify_path(const char *path); extern int cache_name_pos(const char *name, int namelen); #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ extern int add_cache_entry(struct cache_entry *ce, int option); +extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); extern int remove_cache_entry_at(int pos); extern int remove_file_from_cache(const char *path); extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); diff --git a/git-merge.sh b/git-merge.sh index 24e3b50..b26ca14 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,15 +9,15 @@ USAGE='[-n] [--no-commit] [--squash] [-s ]... < LF=' ' -all_strategies='recursive octopus resolve stupid ours' -default_twohead_strategies='recursive' +all_strategies='recur recur octopus resolve stupid ours' +default_twohead_strategies='recur' default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t if test "@@NO_PYTHON@@"; then - all_strategies='resolve octopus stupid ours' + all_strategies='recur resolve octopus stupid ours' default_twohead_strategies='resolve' fi diff --git a/git-rebase.sh b/git-rebase.sh index 1b9e986..2a4c8c8 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -35,7 +35,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase -strategy=recursive +strategy=recur do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 @@ -292,7 +292,7 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recursive" +if test "@@NO_PYTHON@@" && test "$strategy" = "recur" then die 'The recursive merge strategy currently relies on Python, which this installation of git was not configured with. Please consider diff --git a/merge-recursive.c b/merge-recursive.c new file mode 100644 index 0000000..cf81768 --- /dev/null +++ b/merge-recursive.c @@ -0,0 +1,1560 @@ +/* + * Recursive Merge algorithm stolen from git-merge-recursive.py by + * Fredrik Kuivinen. + * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 + */ +#include +#include +#include +#include +#include +#include +#include +#include "cache.h" +#include "cache-tree.h" +#include "commit.h" +#include "blob.h" +#include "tree-walk.h" +#include "diff.h" +#include "diffcore.h" +#include "run-command.h" +#include "tag.h" + +#include "path-list.h" + +/*#define DEBUG*/ + +#ifdef DEBUG +#define debug(args, ...) fprintf(stderr, args, ## __VA_ARGS__) +#else +#define debug(args, ...) +#endif + +#ifdef DEBUG +#include "quote.h" +static void show_ce_entry(const char *tag, struct cache_entry *ce) +{ + if (tag && *tag && + (ce->ce_flags & htons(CE_VALID))) { + static char alttag[4]; + memcpy(alttag, tag, 3); + if (isalpha(tag[0])) + alttag[0] = tolower(tag[0]); + else if (tag[0] == '?') + alttag[0] = '!'; + else { + alttag[0] = 'v'; + alttag[1] = tag[0]; + alttag[2] = ' '; + alttag[3] = 0; + } + tag = alttag; + } + + fprintf(stderr,"%s%06o %s %d\t", + tag, + ntohl(ce->ce_mode), + sha1_to_hex(ce->sha1), + ce_stage(ce)); + write_name_quoted("", 0, ce->name, + '\n', stderr); + fputc('\n', stderr); +} + +static void ls_files() { + int i; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + show_ce_entry("", ce); + } + fprintf(stderr, "---\n"); +} +#endif + +/* + * A virtual commit has + * - (const char *)commit->util set to the name, and + * - *(int *)commit->object.sha1 set to the virtual id. + */ +static const char *commit_title(struct commit *commit, int *len) +{ + const char *s = "(null commit)"; + *len = strlen(s); + + if ( commit->util ) { + s = commit->util; + *len = strlen(s); + } else { + if ( parse_commit(commit) != 0 ) { + s = "(bad commit)"; + *len = strlen(s); + } else { + s = commit->buffer; + char prev = '\0'; + while ( *s ) { + if ( '\n' == prev && '\n' == *s ) { + ++s; + break; + } + prev = *s++; + } + *len = 0; + while ( s[*len] && '\n' != s[*len] ) + ++(*len); + } + } + return s; +} + +static const char *commit_hex_sha1(const struct commit *commit) +{ + return commit->util ? "virtual" : commit ? + sha1_to_hex(commit->object.sha1) : "undefined"; +} + +static unsigned commit_list_count(const struct commit_list *l) +{ + unsigned c = 0; + for (; l; l = l->next ) + c++; + return c; +} + +static struct commit *make_virtual_commit(struct tree *tree, const char *comment) +{ + struct commit *commit = xcalloc(1, sizeof(struct commit)); + static unsigned virtual_id = 1; + commit->tree = tree; + commit->util = (void*)comment; + *(int*)commit->object.sha1 = virtual_id++; + return commit; +} + +/* + * TODO: we should not have to copy the SHA1s around, but rather reference + * them. That way, sha_eq() is just sha1 == sha2. + */ +static int sha_eq(const unsigned char *a, const unsigned char *b) +{ + if ( !a && !b ) + return 2; + return a && b && memcmp(a, b, 20) == 0; +} + +static void memswp(void *p1, void *p2, unsigned n) +{ + unsigned char *a = p1, *b = p2; + while ( n-- ) { + *a ^= *b; + *b ^= *a; + *a ^= *b; + ++a; + ++b; + } +} + +/* + * TODO: we should convert the merge_result users to + * int blabla(..., struct commit **result) + * like everywhere else in git. + * Same goes for merge_tree_result and merge_file_info. + */ +struct merge_result +{ + struct commit *commit; + unsigned clean:1; +}; + +struct merge_tree_result +{ + struct tree *tree; + unsigned clean:1; +}; + +/* + * TODO: check if we can just reuse the active_cache structure: it is already + * sorted (by name, stage). + * Only problem: do not write it when flushing the cache. + */ +struct stage_data +{ + struct + { + unsigned mode; + unsigned char sha[20]; + } stages[4]; + unsigned processed:1; +}; + +static struct path_list currentFileSet = {NULL, 0, 0, 1}; +static struct path_list currentDirectorySet = {NULL, 0, 0, 1}; + +static int output_indent = 0; + +static void output(const char *fmt, ...) +{ + va_list args; + int i; + for ( i = output_indent; i--; ) + fputs(" ", stdout); + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); + fputc('\n', stdout); +} + +static const char *original_index_file; +static const char *temporary_index_file; +static int cache_dirty = 0; + +static int flush_cache() +{ + /* flush temporary index */ + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int fd = hold_lock_file_for_update(lock, getenv("GIT_INDEX_FILE")); + if (fd < 0) + die("could not lock %s", temporary_index_file); + if (write_cache(fd, active_cache, active_nr) || + close(fd) || commit_lock_file(lock)) + die ("unable to write %s", getenv("GIT_INDEX_FILE")); + discard_cache(); + cache_dirty = 0; + return 0; +} + +static void setup_index(int temp) +{ + const char *idx = temp ? temporary_index_file: original_index_file; + if (cache_dirty) + die("fatal: cache changed flush_cache();"); + unlink(temporary_index_file); + setenv("GIT_INDEX_FILE", idx, 1); + discard_cache(); +} + +static struct cache_entry *make_cache_entry(unsigned int mode, + const unsigned char *sha1, const char *path, int stage, int refresh) +{ + int size, len; + struct cache_entry *ce; + + if (!verify_path(path)) + return NULL; + + len = strlen(path); + size = cache_entry_size(len); + ce = xcalloc(1, size); + + memcpy(ce->sha1, sha1, 20); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(len, stage); + ce->ce_mode = create_ce_mode(mode); + + if (refresh) + return refresh_cache_entry(ce, 0); + + return ce; +} + +static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, + const char *path, int stage, int refresh, int options) +{ + struct cache_entry *ce; + if (!cache_dirty) + read_cache_from(getenv("GIT_INDEX_FILE")); + cache_dirty++; + ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); + if (!ce) + return error("cache_addinfo failed: %s", strerror(cache_errno)); + return add_cache_entry(ce, options); +} + +/* + * This is a global variable which is used in a number of places but + * only written to in the 'merge' function. + * + * index_only == 1 => Don't leave any non-stage 0 entries in the cache and + * don't update the working directory. + * 0 => Leave unmerged entries in the cache and update + * the working directory. + */ +static int index_only = 0; + +/* + * TODO: this can be streamlined by refactoring builtin-read-tree.c + */ +static int git_read_tree(const struct tree *tree) +{ +#if 0 + fprintf(stderr, "GIT_INDEX_FILE='%s' git-read-tree %s\n", + getenv("GIT_INDEX_FILE"), + sha1_to_hex(tree->object.sha1)); +#endif + const char *argv[] = { "git-read-tree", NULL, NULL, }; + if (cache_dirty) + die("read-tree with dirty cache"); + argv[1] = sha1_to_hex(tree->object.sha1); + int rc = run_command_v(2, argv); + return rc < 0 ? -1: rc; +} + +/* + * TODO: this can be streamlined by refactoring builtin-read-tree.c + */ +static int git_merge_trees(const char *update_arg, + struct tree *common, + struct tree *head, + struct tree *merge) +{ +#if 0 + fprintf(stderr, "GIT_INDEX_FILE='%s' git-read-tree %s -m %s %s %s\n", + getenv("GIT_INDEX_FILE"), + update_arg, + sha1_to_hex(common->object.sha1), + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); +#endif + const char *argv[] = { + "git-read-tree", NULL, "-m", NULL, NULL, NULL, + NULL, + }; + if (cache_dirty) + flush_cache(); + argv[1] = update_arg; + argv[3] = sha1_to_hex(common->object.sha1); + argv[4] = sha1_to_hex(head->object.sha1); + argv[5] = sha1_to_hex(merge->object.sha1); + int rc = run_command_v(6, argv); + return rc < 0 ? -1: rc; +} + +/* + * TODO: this can be streamlined by refactoring builtin-write-tree.c + */ +static struct tree *git_write_tree() +{ +#if 0 + fprintf(stderr, "GIT_INDEX_FILE='%s' git-write-tree\n", + getenv("GIT_INDEX_FILE")); +#endif + if (cache_dirty) + flush_cache(); + FILE *fp = popen("git-write-tree 2>/dev/null", "r"); + char buf[41]; + unsigned char sha1[20]; + int ch; + unsigned i = 0; + while ( (ch = fgetc(fp)) != EOF ) + if ( i < sizeof(buf)-1 && ch >= '0' && ch <= 'f' ) + buf[i++] = ch; + else + break; + int rc = pclose(fp); + if ( rc == -1 || WEXITSTATUS(rc) ) + return NULL; + buf[i] = '\0'; + if ( get_sha1(buf, sha1) != 0 ) + return NULL; + return lookup_tree(sha1); +} + +/* + * TODO: get rid of files_and_dirs; we do not use it except for + * current_file_set and current_dir_set, which are global already. + */ +static struct +{ + struct path_list *files; + struct path_list *dirs; +} files_and_dirs; + +static int save_files_dirs(const unsigned char *sha1, + const char *base, int baselen, const char *path, + unsigned int mode, int stage) +{ + int len = strlen(path); + char *newpath = malloc(baselen + len + 1); + memcpy(newpath, base, baselen); + memcpy(newpath + baselen, path, len); + newpath[baselen + len] = '\0'; + + if (S_ISDIR(mode)) + path_list_insert(newpath, files_and_dirs.dirs); + else + path_list_insert(newpath, files_and_dirs.files); + free(newpath); + + return READ_TREE_RECURSIVE; +} + +static int get_files_dirs(struct tree *tree, + struct path_list *files, + struct path_list *dirs) +{ + int n; + files_and_dirs.files = files; + files_and_dirs.dirs = dirs; + debug("get_files_dirs ...\n"); + if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0) { + debug(" get_files_dirs done (0)\n"); + return 0; + } + n = files->nr + dirs->nr; + debug(" get_files_dirs done (%d)\n", n); + return n; +} + +/* + * TODO: this wrapper is so small, we can use path_list_lookup directly. + * Same goes for index_entry_get(), free_index_entries(), find_rename_bysrc(), + * free_rename_entries(). + */ +static struct stage_data *index_entry_find(struct path_list *ents, + const char *path) +{ + struct path_list_item *item = path_list_lookup(path, ents); + if (item) + return item->util; + return NULL; +} + +static struct stage_data *index_entry_get(struct path_list *ents, + const char *path) +{ + struct path_list_item *item = path_list_lookup(path, ents); + + if (item == NULL) { + item = path_list_insert(path, ents); + item->util = xcalloc(1, sizeof(struct stage_data)); + } + return item->util; +} + +/* + * TODO: since the result of index_entry_from_db() is tucked into a + * path_list anyway, this helper can do that already. + */ +/* + * Returns a index_entry instance which doesn't have to correspond to + * a real cache entry in Git's index. + */ +static struct stage_data *index_entry_from_db(const char *path, + struct tree *o, + struct tree *a, + struct tree *b) +{ + struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); + get_tree_entry(o->object.sha1, path, + e->stages[1].sha, &e->stages[1].mode); + get_tree_entry(a->object.sha1, path, + e->stages[2].sha, &e->stages[2].mode); + get_tree_entry(b->object.sha1, path, + e->stages[3].sha, &e->stages[3].mode); + return e; +} + +static void free_index_entries(struct path_list **ents) +{ + if (!*ents) + return; + + path_list_clear(*ents, 1); + free(*ents); + *ents = NULL; +} + +/* + * Create a dictionary mapping file names to CacheEntry objects. The + * dictionary contains one entry for every path with a non-zero stage entry. + */ +static struct path_list *get_unmerged() +{ + struct path_list *unmerged = xcalloc(1, sizeof(struct path_list)); + int i; + + unmerged->strdup_paths = 1; + if (!cache_dirty) { + read_cache_from(getenv("GIT_INDEX_FILE")); + cache_dirty++; + } + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + + struct stage_data *e = index_entry_get(unmerged, ce->name); + e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode); + memcpy(e->stages[ce_stage(ce)].sha, ce->sha1, 20); + } + + debug(" get_unmerged done\n"); + return unmerged; +} + +struct rename +{ + struct diff_filepair *pair; + struct stage_data *src_entry; + struct stage_data *dst_entry; + unsigned processed:1; +}; + +static struct rename *find_rename_bysrc(struct path_list *e, + const char *name) +{ + struct path_list_item *item = path_list_lookup(name, e); + if (item) + return item->util; + return NULL; +} + +static void free_rename_entries(struct path_list **list) +{ + if (!*list) + return; + + path_list_clear(*list, 0); + free(*list); + *list = NULL; +} + +/* + * Get information of all renames which occured between 'oTree' and + * 'tree'. We need the three trees in the merge ('oTree', 'aTree' and + * 'bTree') to be able to associate the correct cache entries with + * the rename information. 'tree' is always equal to either aTree or bTree. + */ +static struct path_list *get_renames(struct tree *tree, + struct tree *oTree, + struct tree *aTree, + struct tree *bTree, + struct path_list *entries) +{ +#ifdef DEBUG + time_t t = time(0); + debug("getRenames ...\n"); +#endif + int i; + struct path_list *renames = xcalloc(1, sizeof(struct path_list)); + struct diff_options opts; + diff_setup(&opts); + opts.recursive = 1; + opts.detect_rename = DIFF_DETECT_RENAME; + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + if (diff_setup_done(&opts) < 0) + die("diff setup failed"); + diff_tree_sha1(oTree->object.sha1, tree->object.sha1, "", &opts); + diffcore_std(&opts); + for (i = 0; i < diff_queued_diff.nr; ++i) { + struct rename *re; + struct diff_filepair *pair = diff_queued_diff.queue[i]; + if (pair->status != 'R') { + diff_free_filepair(pair); + continue; + } + re = xmalloc(sizeof(*re)); + re->processed = 0; + re->pair = pair; + re->src_entry = index_entry_find(entries, re->pair->one->path); + /* TODO: should it not be an error, if src_entry was found? */ + if ( !re->src_entry ) { + re->src_entry = index_entry_from_db(re->pair->one->path, + oTree, aTree, bTree); + struct path_list_item *item = + path_list_insert(re->pair->one->path, entries); + item->util = re->src_entry; + } + re->dst_entry = index_entry_find(entries, re->pair->two->path); + if ( !re->dst_entry ) { + re->dst_entry = index_entry_from_db(re->pair->two->path, + oTree, aTree, bTree); + struct path_list_item *item = + path_list_insert(re->pair->two->path, entries); + item->util = re->dst_entry; + } + struct path_list_item *item = path_list_insert(pair->one->path, renames); + item->util = re; + } + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_queued_diff.nr = 0; + diff_flush(&opts); + debug(" getRenames done in %ld\n", time(0)-t); + return renames; +} + +/* + * TODO: the code would be way nicer, if we had a struct containing just sha1 and mode. + * In this particular case, we might get away reusing stage_data, no? + */ +int update_stages(const char *path, + unsigned char *osha, unsigned omode, + unsigned char *asha, unsigned amode, + unsigned char *bsha, unsigned bmode, + int clear /* =True */) +{ + int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; + if ( clear ) + if (add_cacheinfo(0, null_sha1, path, 0, 0, options)) + return -1; + if ( omode ) + if (add_cacheinfo(omode, osha, path, 1, 0, options)) + return -1; + if ( amode ) + if (add_cacheinfo(omode, osha, path, 2, 0, options)) + return -1; + if ( bmode ) + if (add_cacheinfo(omode, osha, path, 3, 0, options)) + return -1; + return 0; +} + +/* + * TODO: there has to be a function in libgit doing this exact thing. + */ +static int remove_path(const char *name) +{ + int ret; + char *slash; + + ret = unlink(name); + if ( ret ) + return ret; + int len = strlen(name); + char *dirs = malloc(len+1); + memcpy(dirs, name, len); + dirs[len] = '\0'; + while ( (slash = strrchr(name, '/')) ) { + *slash = '\0'; + len = slash - name; + if ( rmdir(name) != 0 ) + break; + } + free(dirs); + return ret; +} + +/* General TODO: unC99ify the code: no declaration after code */ +/* General TODO: no javaIfiCation: rename updateCache to update_cache */ +/* + * TODO: once we no longer call external programs, we'd probably be better of + * not setting / getting the environment variable GIT_INDEX_FILE all the time. + */ +int remove_file(int clean, const char *path) +{ + int updateCache = index_only || clean; + int updateWd = !index_only; + + if ( updateCache ) { + if (!cache_dirty) + read_cache_from(getenv("GIT_INDEX_FILE")); + cache_dirty++; + if (remove_file_from_cache(path)) + return -1; + } + if ( updateWd ) + { + unlink(path); + if ( errno != ENOENT || errno != EISDIR ) + return -1; + remove_path(path); + } + return 0; +} + +static char *unique_path(const char *path, const char *branch) +{ + char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); + strcpy(newpath, path); + strcat(newpath, "~"); + char *p = newpath + strlen(newpath); + strcpy(p, branch); + for ( ; *p; ++p ) + if ( '/' == *p ) + *p = '_'; + int suffix = 0; + struct stat st; + while ( path_list_has_path(¤tFileSet, newpath) || + path_list_has_path(¤tDirectorySet, newpath) || + lstat(newpath, &st) == 0 ) { + sprintf(p, "_%d", suffix++); + } + path_list_insert(newpath, ¤tFileSet); + return newpath; +} + +/* + * TODO: except for create_last, this so looks like + * safe_create_leading_directories(). + */ +static int mkdir_p(const char *path, unsigned long mode, int create_last) +{ + char *buf = strdup(path); + char *p; + + for ( p = buf; *p; ++p ) { + if ( *p != '/' ) + continue; + *p = '\0'; + if (mkdir(buf, mode)) { + int e = errno; + if ( e == EEXIST ) { + struct stat st; + if ( !stat(buf, &st) && S_ISDIR(st.st_mode) ) + goto next; /* ok */ + errno = e; + } + free(buf); + return -1; + } + next: + *p = '/'; + } + free(buf); + if ( create_last && mkdir(path, mode) ) + return -1; + return 0; +} + +static void flush_buffer(int fd, const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(fd, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("merge-recursive: %s", strerror(errno)); + } else if (!ret) { + die("merge-recursive: disk full?"); + } + size -= ret; + buf += ret; + } +} + +/* General TODO: reindent according to guide lines (no if ( blabla )) */ +void update_file_flags(const unsigned char *sha, + unsigned mode, + const char *path, + int updateCache, + int updateWd) +{ + if ( index_only ) + updateWd = 0; + + if ( updateWd ) { + char type[20]; + void *buf; + unsigned long size; + + buf = read_sha1_file(sha, type, &size); + if (!buf) + die("cannot read object %s '%s'", sha1_to_hex(sha), path); + if ( strcmp(type, blob_type) != 0 ) + die("blob expected for %s '%s'", sha1_to_hex(sha), path); + + if ( S_ISREG(mode) ) { + if ( mkdir_p(path, 0777, 0 /* don't create last element */) ) + die("failed to create path %s: %s", path, strerror(errno)); + unlink(path); + if ( mode & 0100 ) + mode = 0777; + else + mode = 0666; + int fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); + if ( fd < 0 ) + die("failed to open %s: %s", path, strerror(errno)); + flush_buffer(fd, buf, size); + close(fd); + } else if ( S_ISLNK(mode) ) { + char *linkTarget = malloc(size + 1); + memcpy(linkTarget, buf, size); + linkTarget[size] = '\0'; + mkdir_p(path, 0777, 0); + symlink(linkTarget, path); + } else + die("do not know what to do with %06o %s '%s'", + mode, sha1_to_hex(sha), path); + } + if ( updateCache ) + add_cacheinfo(mode, sha, path, 0, updateWd, ADD_CACHE_OK_TO_ADD); +} + +/* TODO: is this often used? if not, do direct call */ +void update_file(int clean, + const unsigned char *sha, + unsigned mode, + const char *path) +{ + update_file_flags(sha, mode, path, index_only || clean, !index_only); +} + +/* Low level file merging, update and removal */ + +struct merge_file_info +{ + unsigned char sha[20]; + unsigned mode; + unsigned clean:1, + merge:1; +}; + +static char *git_unpack_file(const unsigned char *sha1, char *path) +{ + void *buf; + char type[20]; + unsigned long size; + int fd; + + buf = read_sha1_file(sha1, type, &size); + if (!buf || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + + strcpy(path, ".merge_file_XXXXXX"); + fd = mkstemp(path); + if (fd < 0) + die("unable to create temp-file"); + flush_buffer(fd, buf, size); + close(fd); + return path; +} + +/* + * TODO: the signature would be much more efficient using stage_data + */ +static struct merge_file_info merge_file(const char *oPath, + const unsigned char *oSha, + unsigned oMode, + const char *aPath, + const unsigned char *aSha, + unsigned aMode, + const char *bPath, + const unsigned char *bSha, + unsigned bMode, + const char *branch1Name, + const char *branch2Name) +{ + struct merge_file_info result; + result.merge = 0; + result.clean = 1; + + if ( (S_IFMT & aMode) != (S_IFMT & bMode) ) { + result.clean = 0; + if ( S_ISREG(aMode) ) { + result.mode = aMode; + memcpy(result.sha, aSha, 20); + } else { + result.mode = bMode; + memcpy(result.sha, bSha, 20); + } + } else { + if ( memcmp(aSha, oSha, 20) != 0 && memcmp(bSha, oSha, 20) != 0 ) + result.merge = 1; + + result.mode = aMode == oMode ? bMode: aMode; + + if ( memcmp(aSha, oSha, 20) == 0 ) + memcpy(result.sha, bSha, 20); + else if ( memcmp(bSha, oSha, 20) == 0 ) + memcpy(result.sha, aSha, 20); + else if ( S_ISREG(aMode) ) { + + int code = 1; + char orig[PATH_MAX]; + char src1[PATH_MAX]; + char src2[PATH_MAX]; + + git_unpack_file(oSha, orig); + git_unpack_file(aSha, src1); + git_unpack_file(bSha, src2); + + const char *argv[] = { + "merge", "-L", NULL, "-L", NULL, "-L", NULL, + src1, orig, src2, + NULL + }; + char *la, *lb, *lo; + argv[2] = la = strdup(mkpath("%s/%s", branch1Name, aPath)); + argv[6] = lb = strdup(mkpath("%s/%s", branch2Name, bPath)); + argv[4] = lo = strdup(mkpath("orig/%s", oPath)); + +#if 0 + printf("%s %s %s %s %s %s %s %s %s %s\n", + argv[0], argv[1], argv[2], argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8], argv[9]); +#endif + code = run_command_v(10, argv); + + free(la); + free(lb); + free(lo); + if ( code && code < -256 ) { + die("Failed to execute 'merge'. merge(1) is used as the " + "file-level merge tool. Is 'merge' in your path?"); + } + struct stat st; + int fd = open(src1, O_RDONLY); + if (fd < 0 || fstat(fd, &st) < 0 || + index_fd(result.sha, fd, &st, 1, + "blob")) + die("Unable to add %s to database", src1); + close(fd); + + unlink(orig); + unlink(src1); + unlink(src2); + + result.clean = WEXITSTATUS(code) == 0; + } else { + if ( !(S_ISLNK(aMode) || S_ISLNK(bMode)) ) + die("cannot merge modes?"); + + memcpy(result.sha, aSha, 20); + + if ( memcmp(aSha, bSha, 20) != 0 ) + result.clean = 0; + } + } + + return result; +} + +static void conflict_rename_rename(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *del[2]; + int delp = 0; + const char *ren1_dst = ren1->pair->two->path; + const char *ren2_dst = ren2->pair->two->path; + const char *dstName1 = ren1_dst; + const char *dstName2 = ren2_dst; + if (path_list_has_path(¤tDirectorySet, ren1_dst)) { + dstName1 = del[delp++] = unique_path(ren1_dst, branch1); + output("%s is a directory in %s adding as %s instead", + ren1_dst, branch2, dstName1); + remove_file(0, ren1_dst); + } + if (path_list_has_path(¤tDirectorySet, ren2_dst)) { + dstName2 = del[delp++] = unique_path(ren2_dst, branch2); + output("%s is a directory in %s adding as %s instead", + ren2_dst, branch1, dstName2); + remove_file(0, ren2_dst); + } + update_stages(dstName1, + NULL, 0, + ren1->pair->two->sha1, ren1->pair->two->mode, + NULL, 0, + 1 /* clear */); + update_stages(dstName2, + NULL, 0, + NULL, 0, + ren2->pair->two->sha1, ren2->pair->two->mode, + 1 /* clear */); + while ( delp-- ) + free(del[delp]); +} + +static void conflict_rename_dir(struct rename *ren1, + const char *branch1) +{ + char *newPath = unique_path(ren1->pair->two->path, branch1); + output("Renaming %s to %s instead", ren1->pair->one->path, newPath); + remove_file(0, ren1->pair->two->path); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, newPath); + free(newPath); +} + +static void conflict_rename_rename_2(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *newPath1 = unique_path(ren1->pair->two->path, branch1); + char *newPath2 = unique_path(ren2->pair->two->path, branch2); + output("Renaming %s to %s and %s to %s instead", + ren1->pair->one->path, newPath1, + ren2->pair->one->path, newPath2); + remove_file(0, ren1->pair->two->path); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, newPath1); + update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, newPath2); + free(newPath2); + free(newPath1); +} + +/* General TODO: get rid of all the debug messages */ +static int process_renames(struct path_list *renamesA, + struct path_list *renamesB, + const char *branchNameA, + const char *branchNameB) +{ + int cleanMerge = 1, i; + struct path_list srcNames = {NULL, 0, 0, 0}, byDstA = {NULL, 0, 0, 0}, byDstB = {NULL, 0, 0, 0}; + const struct rename *sre; + + /* + * TODO: think about a saner way to do this. + * Since both renamesA and renamesB are sorted, it should + * be much more efficient to traverse both simultaneously, + * only byDstA and byDstB should be needed. + */ + debug("processRenames...\n"); + for (i = 0; i < renamesA->nr; i++) { + sre = renamesA->items[i].util; + path_list_insert(sre->pair->one->path, &srcNames); + path_list_insert(sre->pair->two->path, &byDstA)->util + = sre->dst_entry; + } + for (i = 0; i < renamesB->nr; i++) { + sre = renamesB->items[i].util; + path_list_insert(sre->pair->one->path, &srcNames); + path_list_insert(sre->pair->two->path, &byDstB)->util + = sre->dst_entry; + } + + for (i = 0; i < srcNames.nr; i++) { + char *src = srcNames.items[i].path; + struct path_list *renames1, *renames2, *renames2Dst; + struct rename *ren1, *ren2; + const char *branchName1, *branchName2; + ren1 = find_rename_bysrc(renamesA, src); + ren2 = find_rename_bysrc(renamesB, src); + /* TODO: refactor, so that 1/2 are not needed */ + if ( ren1 ) { + renames1 = renamesA; + renames2 = renamesB; + renames2Dst = &byDstB; + branchName1 = branchNameA; + branchName2 = branchNameB; + } else { + renames1 = renamesB; + renames2 = renamesA; + renames2Dst = &byDstA; + branchName1 = branchNameB; + branchName2 = branchNameA; + struct rename *tmp = ren2; + ren2 = ren1; + ren1 = tmp; + } + + ren1->dst_entry->processed = 1; + ren1->src_entry->processed = 1; + + if ( ren1->processed ) + continue; + ren1->processed = 1; + + const char *ren1_src = ren1->pair->one->path; + const char *ren1_dst = ren1->pair->two->path; + + if ( ren2 ) { + const char *ren2_src = ren2->pair->one->path; + const char *ren2_dst = ren2->pair->two->path; + /* Renamed in 1 and renamed in 2 */ + if (strcmp(ren1_src, ren2_src) != 0) + die("ren1.src != ren2.src"); + ren2->dst_entry->processed = 1; + ren2->processed = 1; + if (strcmp(ren1_dst, ren2_dst) != 0) { + cleanMerge = 0; + output("CONFLICT (rename/rename): " + "Rename %s->%s in branch %s " + "rename %s->%s in %s", + src, ren1_dst, branchName1, + src, ren2_dst, branchName2); + conflict_rename_rename(ren1, branchName1, ren2, branchName2); + } else { + remove_file(1, ren1_src); + struct merge_file_info mfi; + mfi = merge_file(ren1_src, + ren1->pair->one->sha1, + ren1->pair->one->mode, + ren1_dst, + ren1->pair->two->sha1, + ren1->pair->two->mode, + ren2_dst, + ren2->pair->two->sha1, + ren2->pair->two->mode, + branchName1, + branchName2); + if ( mfi.merge || !mfi.clean ) + output("Renaming %s->%s", src, ren1_dst); + + if ( mfi.merge ) + output("Auto-merging %s", ren1_dst); + + if ( !mfi.clean ) { + output("CONFLICT (content): merge conflict in %s", + ren1_dst); + cleanMerge = 0; + + if ( !index_only ) + update_stages(ren1_dst, + ren1->pair->one->sha1, + ren1->pair->one->mode, + ren1->pair->two->sha1, + ren1->pair->two->mode, + ren2->pair->two->sha1, + ren2->pair->two->mode, + 1 /* clear */); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } else { + /* Renamed in 1, maybe changed in 2 */ + remove_file(1, ren1_src); + + unsigned char srcShaOtherBranch[20], dstShaOtherBranch[20]; + unsigned srcModeOtherBranch, dstModeOtherBranch; + + int stage = renamesA == renames1 ? 3: 2; + + memcpy(srcShaOtherBranch, ren1->src_entry->stages[stage].sha, 20); + srcModeOtherBranch = ren1->src_entry->stages[stage].mode; + + memcpy(dstShaOtherBranch, ren1->dst_entry->stages[stage].sha, 20); + dstModeOtherBranch = ren1->dst_entry->stages[stage].mode; + + int tryMerge = 0; + char *newPath; + + if (path_list_has_path(¤tDirectorySet, ren1_dst)) { + cleanMerge = 0; + output("CONFLICT (rename/directory): Rename %s->%s in %s " + " directory %s added in %s", + ren1_src, ren1_dst, branchName1, + ren1_dst, branchName2); + conflict_rename_dir(ren1, branchName1); + } else if ( memcmp(srcShaOtherBranch, null_sha1, 20) == 0 ) { + cleanMerge = 0; + output("CONFLICT (rename/delete): Rename %s->%s in %s " + "and deleted in %s", + ren1_src, ren1_dst, branchName1, + branchName2); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + } else if ( memcmp(dstShaOtherBranch, null_sha1, 20) != 0 ) { + cleanMerge = 0; + tryMerge = 1; + output("CONFLICT (rename/add): Rename %s->%s in %s. " + "%s added in %s", + ren1_src, ren1_dst, branchName1, + ren1_dst, branchName2); + newPath = unique_path(ren1_dst, branchName2); + output("Adding as %s instead", newPath); + update_file(0, dstShaOtherBranch, dstModeOtherBranch, newPath); + } else if ( (ren2 = find_rename_bysrc(renames2Dst, ren1_dst)) ) { + cleanMerge = 0; + ren2->processed = 1; + output("CONFLICT (rename/rename): Rename %s->%s in %s. " + "Rename %s->%s in %s", + ren1_src, ren1_dst, branchName1, + ren2->pair->one->path, ren2->pair->two->path, branchName2); + conflict_rename_rename_2(ren1, branchName1, ren2, branchName2); + } else + tryMerge = 1; + + if ( tryMerge ) { + const char *oname = ren1_src; + const char *aname = ren1_dst; + const char *bname = ren1_src; + unsigned char osha[20], asha[20], bsha[20]; + unsigned omode = ren1->pair->one->mode; + unsigned amode = ren1->pair->two->mode; + unsigned bmode = srcModeOtherBranch; + memcpy(osha, ren1->pair->one->sha1, 20); + memcpy(asha, ren1->pair->two->sha1, 20); + memcpy(bsha, srcShaOtherBranch, 20); + const char *aBranch = branchName1; + const char *bBranch = branchName2; + + if ( renamesA != renames1 ) { + memswp(&aname, &bname, sizeof(aname)); + memswp(asha, bsha, 20); + memswp(&aBranch, &bBranch, sizeof(aBranch)); + } + struct merge_file_info mfi; + mfi = merge_file(oname, osha, omode, + aname, asha, amode, + bname, bsha, bmode, + aBranch, bBranch); + + if ( mfi.merge || !mfi.clean ) + output("Renaming %s => %s", ren1_src, ren1_dst); + if ( mfi.merge ) + output("Auto-merging %s", ren1_dst); + if ( !mfi.clean ) { + output("CONFLICT (rename/modify): Merge conflict in %s", + ren1_dst); + cleanMerge = 0; + + if ( !index_only ) + update_stages(ren1_dst, + osha, omode, + asha, amode, + bsha, bmode, + 1 /* clear */); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } + } + path_list_clear(&srcNames, 0); + debug(" processRenames done\n"); + + if (cache_dirty) + flush_cache(); + return cleanMerge; +} + +static unsigned char *has_sha(const unsigned char *sha) +{ + return memcmp(sha, null_sha1, 20) == 0 ? NULL: (unsigned char *)sha; +} + +/* Per entry merge function */ +static int process_entry(const char *path, struct stage_data *entry, + const char *branch1Name, + const char *branch2Name) +{ + /* + printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); + print_index_entry("\tpath: ", entry); + */ + int cleanMerge = 1; + unsigned char *oSha = has_sha(entry->stages[1].sha); + unsigned char *aSha = has_sha(entry->stages[2].sha); + unsigned char *bSha = has_sha(entry->stages[3].sha); + unsigned oMode = entry->stages[1].mode; + unsigned aMode = entry->stages[2].mode; + unsigned bMode = entry->stages[3].mode; + + if ( oSha && (!aSha || !bSha) ) { + /* Case A: Deleted in one */ + if ( (!aSha && !bSha) || + (sha_eq(aSha, oSha) && !bSha) || + (!aSha && sha_eq(bSha, oSha)) ) { + /* Deleted in both or deleted in one and + * unchanged in the other */ + if ( aSha ) + output("Removing %s", path); + remove_file(1, path); + } else { + /* Deleted in one and changed in the other */ + cleanMerge = 0; + if ( !aSha ) { + output("CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch1Name, + branch2Name, branch2Name, path); + update_file(0, bSha, bMode, path); + } else { + output("CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch2Name, + branch1Name, branch1Name, path); + update_file(0, aSha, aMode, path); + } + } + + } else if ( (!oSha && aSha && !bSha) || + (!oSha && !aSha && bSha) ) { + /* Case B: Added in one. */ + const char *addBranch; + const char *otherBranch; + unsigned mode; + const unsigned char *sha; + const char *conf; + + if ( aSha ) { + addBranch = branch1Name; + otherBranch = branch2Name; + mode = aMode; + sha = aSha; + conf = "file/directory"; + } else { + addBranch = branch2Name; + otherBranch = branch1Name; + mode = bMode; + sha = bSha; + conf = "directory/file"; + } + if ( path_list_has_path(¤tDirectorySet, path) ) { + cleanMerge = 0; + const char *newPath = unique_path(path, addBranch); + output("CONFLICT (%s): There is a directory with name %s in %s. " + "Adding %s as %s", + conf, path, otherBranch, path, newPath); + remove_file(0, path); + update_file(0, sha, mode, newPath); + } else { + output("Adding %s", path); + update_file(1, sha, mode, path); + } + } else if ( !oSha && aSha && bSha ) { + /* Case C: Added in both (check for same permissions). */ + if ( sha_eq(aSha, bSha) ) { + if ( aMode != bMode ) { + cleanMerge = 0; + output("CONFLICT: File %s added identically in both branches, " + "but permissions conflict %06o->%06o", + path, aMode, bMode); + output("CONFLICT: adding with permission: %06o", aMode); + update_file(0, aSha, aMode, path); + } else { + /* This case is handled by git-read-tree */ + assert(0 && "This case must be handled by git-read-tree"); + } + } else { + cleanMerge = 0; + const char *newPath1 = unique_path(path, branch1Name); + const char *newPath2 = unique_path(path, branch2Name); + output("CONFLICT (add/add): File %s added non-identically " + "in both branches. Adding as %s and %s instead.", + path, newPath1, newPath2); + remove_file(0, path); + update_file(0, aSha, aMode, newPath1); + update_file(0, bSha, bMode, newPath2); + } + + } else if ( oSha && aSha && bSha ) { + /* case D: Modified in both, but differently. */ + output("Auto-merging %s", path); + struct merge_file_info mfi; + mfi = merge_file(path, oSha, oMode, + path, aSha, aMode, + path, bSha, bMode, + branch1Name, branch2Name); + + if ( mfi.clean ) + update_file(1, mfi.sha, mfi.mode, path); + else { + cleanMerge = 0; + output("CONFLICT (content): Merge conflict in %s", path); + + if ( index_only ) + update_file(0, mfi.sha, mfi.mode, path); + else + update_file_flags(mfi.sha, mfi.mode, path, + 0 /* updateCache */, 1 /* updateWd */); + } + } else + die("Fatal merge failure, shouldn't happen."); + + if (cache_dirty) + flush_cache(); + + return cleanMerge; +} + +static struct merge_tree_result merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1Name, + const char *branch2Name) +{ + int code; + struct merge_tree_result result = { NULL, 0 }; + if ( !memcmp(common->object.sha1, merge->object.sha1, 20) ) { + output("Already uptodate!"); + result.tree = head; + result.clean = 1; + return result; + } + + debug("merge_trees ...\n"); + code = git_merge_trees(index_only ? "-i": "-u", common, head, merge); + + if ( code != 0 ) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + + result.tree = git_write_tree(); + + if ( !result.tree ) { + path_list_clear(¤tFileSet, 1); + path_list_clear(¤tDirectorySet, 1); + get_files_dirs(head, ¤tFileSet, ¤tDirectorySet); + get_files_dirs(merge, ¤tFileSet, ¤tDirectorySet); + + struct path_list *entries = get_unmerged(); + struct path_list *re_head, *re_merge; + re_head = get_renames(head, common, head, merge, entries); + re_merge = get_renames(merge, common, head, merge, entries); + result.clean = process_renames(re_head, re_merge, + branch1Name, branch2Name); + debug("\tprocessing entries...\n"); + int i; + for (i = 0; i < entries->nr; i++) { + const char *path = entries->items[i].path; + struct stage_data *e = entries->items[i].util; + if (e->processed) + continue; + if (!process_entry(path, e, branch1Name, branch2Name)) + result.clean = 0; + } + + free_rename_entries(&re_merge); + free_rename_entries(&re_head); + free_index_entries(&entries); + + if (result.clean || index_only) + result.tree = git_write_tree(); + else + result.tree = NULL; + debug("\t processing entries done\n"); + } else { + result.clean = 1; + printf("merging of trees %s and %s resulted in %s\n", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1), + sha1_to_hex(result.tree->object.sha1)); + } + + debug(" merge_trees done\n"); + return result; +} + +/* + * Merge the commits h1 and h2, return the resulting virtual + * commit object and a flag indicating the cleaness of the merge. + */ +static +struct merge_result merge(struct commit *h1, + struct commit *h2, + const char *branch1Name, + const char *branch2Name, + int callDepth /* =0 */, + struct commit *ancestor /* =None */) +{ + struct merge_result result = { NULL, 0 }; + const char *msg; + int msglen; + struct commit_list *ca = NULL, *iter; + struct commit *mergedCA; + struct merge_tree_result mtr; + + output("Merging:"); + msg = commit_title(h1, &msglen); + /* TODO: refactor. we always show the sha1 with the title */ + output("%s %.*s", commit_hex_sha1(h1), msglen, msg); + msg = commit_title(h2, &msglen); + output("%s %.*s", commit_hex_sha1(h2), msglen, msg); + + if ( ancestor ) + commit_list_insert(ancestor, &ca); + else + ca = get_merge_bases(h1, h2, 1); + + output("found %u common ancestor(s):", commit_list_count(ca)); + for (iter = ca; iter; iter = iter->next) { + msg = commit_title(iter->item, &msglen); + output("%s %.*s", commit_hex_sha1(iter->item), msglen, msg); + } + + mergedCA = pop_commit(&ca); + + /* TODO: what happens when merge with virtual commits fails? */ + for (iter = ca; iter; iter = iter->next) { + output_indent = callDepth + 1; + result = merge(mergedCA, iter->item, + "Temporary merge branch 1", + "Temporary merge branch 2", + callDepth + 1, + NULL); + mergedCA = result.commit; + output_indent = callDepth; + + if ( !mergedCA ) + die("merge returned no commit"); + } + + if ( callDepth == 0 ) { + setup_index(0); + index_only = 0; + } else { + setup_index(1); + git_read_tree(h1->tree); + index_only = 1; + } + + mtr = merge_trees(h1->tree, h2->tree, + mergedCA->tree, branch1Name, branch2Name); + + if ( !ancestor && (mtr.clean || index_only) ) { + result.commit = make_virtual_commit(mtr.tree, "merged tree"); + commit_list_insert(h1, &result.commit->parents); + commit_list_insert(h2, &result.commit->parents->next); + } else + result.commit = NULL; + + result.clean = mtr.clean; + return result; +} + +static struct commit *get_ref(const char *ref) +{ + unsigned char sha1[20]; + struct object *object; + + if (get_sha1(ref, sha1)) + die("Could not resolve ref '%s'", ref); + object = deref_tag(parse_object(sha1), ref, strlen(ref)); + if (object->type != TYPE_COMMIT) + return NULL; + if (parse_commit((struct commit *)object)) + die("Could not parse commit '%s'", sha1_to_hex(object->sha1)); + return (struct commit *)object; +} + +int main(int argc, char *argv[]) +{ + static const char *bases[2]; + static unsigned bases_count = 0; + + original_index_file = getenv("GIT_INDEX_FILE"); + + if (!original_index_file) + original_index_file = strdup(git_path("index")); + + temporary_index_file = strdup(git_path("mrg-rcrsv-tmp-idx")); + + if (argc < 4) + die("Usage: %s ... -- ...\n", argv[0]); + + int i; + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--")) + break; + if (bases_count < sizeof(bases)/sizeof(*bases)) + bases[bases_count++] = argv[i]; + } + if (argc - i != 3) /* "--" "" "" */ + die("Not handling anything other than two heads merge."); + + const char *branch1, *branch2; + + branch1 = argv[++i]; + branch2 = argv[++i]; + printf("Merging %s with %s\n", branch1, branch2); + + struct merge_result result; + struct commit *h1 = get_ref(branch1); + struct commit *h2 = get_ref(branch2); + + if (bases_count == 1) { + struct commit *ancestor = get_ref(bases[0]); + result = merge(h1, h2, branch1, branch2, 0, ancestor); + } else + result = merge(h1, h2, branch1, branch2, 0, NULL); + + if (cache_dirty) + flush_cache(); + + return result.clean ? 0: 1; +} + +/* +vim: sw=8 noet +*/ diff --git a/path-list.c b/path-list.c new file mode 100644 index 0000000..f15a10d --- /dev/null +++ b/path-list.c @@ -0,0 +1,105 @@ +#include +#include "cache.h" +#include "path-list.h" + +/* if there is no exact match, point to the index where the entry could be + * inserted */ +static int get_entry_index(const struct path_list *list, const char *path, + int *exact_match) +{ + int left = -1, right = list->nr; + + while (left + 1 < right) { + int middle = (left + right) / 2; + int compare = strcmp(path, list->items[middle].path); + if (compare < 0) + right = middle; + else if (compare > 0) + left = middle; + else { + *exact_match = 1; + return middle; + } + } + + *exact_match = 0; + return right; +} + +/* returns -1-index if already exists */ +static int add_entry(struct path_list *list, const char *path) +{ + int exact_match; + int index = get_entry_index(list, path, &exact_match); + + if (exact_match) + return -1 - index; + + if (list->nr + 1 >= list->alloc) { + list->alloc += 32; + list->items = xrealloc(list->items, list->alloc + * sizeof(struct path_list_item)); + } + if (index < list->nr) + memmove(list->items + index + 1, list->items + index, + (list->nr - index) + * sizeof(struct path_list_item)); + list->items[index].path = list->strdup_paths ? + strdup(path) : (char *)path; + list->items[index].util = NULL; + list->nr++; + + return index; +} + +struct path_list_item *path_list_insert(const char *path, struct path_list *list) +{ + int index = add_entry(list, path); + + if (index < 0) + index = 1 - index; + + return list->items + index; +} + +int path_list_has_path(const struct path_list *list, const char *path) +{ + int exact_match; + get_entry_index(list, path, &exact_match); + return exact_match; +} + +struct path_list_item *path_list_lookup(const char *path, struct path_list *list) +{ + int exact_match, i = get_entry_index(list, path, &exact_match); + if (!exact_match) + return NULL; + return list->items + i; +} + +void path_list_clear(struct path_list *list, int free_items) +{ + if (list->items) { + int i; + if (free_items) + for (i = 0; i < list->nr; i++) { + if (list->strdup_paths) + free(list->items[i].path); + if (list->items[i].util) + free(list->items[i].util); + } + free(list->items); + } + list->items = NULL; + list->nr = list->alloc = 0; +} + +void print_path_list(const char *text, const struct path_list *p) +{ + int i; + if ( text ) + printf("%s\n", text); + for (i = 0; i < p->nr; i++) + printf("%s:%p\n", p->items[i].path, p->items[i].util); +} + diff --git a/path-list.h b/path-list.h new file mode 100644 index 0000000..d6401ea --- /dev/null +++ b/path-list.h @@ -0,0 +1,22 @@ +#ifndef _PATH_LIST_H_ +#define _PATH_LIST_H_ + +struct path_list_item { + char *path; + void *util; +}; +struct path_list +{ + struct path_list_item *items; + unsigned int nr, alloc; + unsigned int strdup_paths:1; +}; + +void print_path_list(const char *text, const struct path_list *p); + +int path_list_has_path(const struct path_list *list, const char *path); +void path_list_clear(struct path_list *list, int free_items); +struct path_list_item *path_list_insert(const char *path, struct path_list *list); +struct path_list_item *path_list_lookup(const char *path, struct path_list *list); + +#endif /* _PATH_LIST_H_ */ diff --git a/read-cache.c b/read-cache.c index a50d361..9c0a9fc 100644 --- a/read-cache.c +++ b/read-cache.c @@ -24,6 +24,11 @@ unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0; struct cache_tree *active_cache_tree = NULL; +int cache_errno = 0; + +static void *cache_mmap = NULL; +static size_t cache_mmap_size = 0; + /* * This only updates the "non-critical" parts of the directory * cache, ie the parts that aren't tracked by GIT, and only used @@ -577,22 +582,6 @@ int add_cache_entry(struct cache_entry *ce, int option) return 0; } -/* Three functions to allow overloaded pointer return; see linux/err.h */ -static inline void *ERR_PTR(long error) -{ - return (void *) error; -} - -static inline long PTR_ERR(const void *ptr) -{ - return (long) ptr; -} - -static inline long IS_ERR(const void *ptr) -{ - return (unsigned long)ptr > (unsigned long)-1000L; -} - /* * "refresh" does not calculate a new sha1 file or bring the * cache up-to-date for mode/content changes. But what it @@ -604,14 +593,16 @@ static inline long IS_ERR(const void *ptr) * For example, you'd want to do this after doing a "git-read-tree", * to link up the stat cache details with the proper files. */ -static struct cache_entry *refresh_entry(struct cache_entry *ce, int really) +struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) { struct stat st; struct cache_entry *updated; int changed, size; - if (lstat(ce->name, &st) < 0) - return ERR_PTR(-errno); + if (lstat(ce->name, &st) < 0) { + cache_errno = errno; + return NULL; + } changed = ce_match_stat(ce, &st, really); if (!changed) { @@ -619,11 +610,13 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce, int really) !(ce->ce_flags & htons(CE_VALID))) ; /* mark this one VALID again */ else - return NULL; + return ce; } - if (ce_modified(ce, &st, really)) - return ERR_PTR(-EINVAL); + if (ce_modified(ce, &st, really)) { + cache_errno = EINVAL; + return NULL; + } size = ce_size(ce); updated = xmalloc(size); @@ -666,13 +659,13 @@ int refresh_cache(unsigned int flags) continue; } - new = refresh_entry(ce, really); - if (!new) + new = refresh_cache_entry(ce, really); + if (new == ce) continue; - if (IS_ERR(new)) { - if (not_new && PTR_ERR(new) == -ENOENT) + if (!new) { + if (not_new && cache_errno == ENOENT) continue; - if (really && PTR_ERR(new) == -EINVAL) { + if (really && cache_errno == EINVAL) { /* If we are doing --really-refresh that * means the index is not valid anymore. */ @@ -729,39 +722,43 @@ static int read_index_extension(const char *ext, void *data, unsigned long sz) int read_cache(void) { + return read_cache_from(get_index_file()); +} + +/* remember to discard_cache() before reading a different cache! */ +int read_cache_from(const char *path) +{ int fd, i; struct stat st; - unsigned long size, offset; - void *map; + unsigned long offset; struct cache_header *hdr; errno = EBUSY; - if (active_cache) + if (cache_mmap) return active_nr; errno = ENOENT; index_file_timestamp = 0; - fd = open(get_index_file(), O_RDONLY); + fd = open(path, O_RDONLY); if (fd < 0) { if (errno == ENOENT) return 0; die("index file open failed (%s)", strerror(errno)); } - size = 0; /* avoid gcc warning */ - map = MAP_FAILED; + cache_mmap = MAP_FAILED; if (!fstat(fd, &st)) { - size = st.st_size; + cache_mmap_size = st.st_size; errno = EINVAL; - if (size >= sizeof(struct cache_header) + 20) - map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (cache_mmap_size >= sizeof(struct cache_header) + 20) + cache_mmap = mmap(NULL, cache_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); } close(fd); - if (map == MAP_FAILED) + if (cache_mmap == MAP_FAILED) die("index file mmap failed (%s)", strerror(errno)); - hdr = map; - if (verify_hdr(hdr, size) < 0) + hdr = cache_mmap; + if (verify_hdr(hdr, cache_mmap_size) < 0) goto unmap; active_nr = ntohl(hdr->hdr_entries); @@ -770,12 +767,12 @@ int read_cache(void) offset = sizeof(*hdr); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = (struct cache_entry *) ((char *) map + offset); + struct cache_entry *ce = (struct cache_entry *) ((char *) cache_mmap + offset); offset = offset + ce_size(ce); active_cache[i] = ce; } index_file_timestamp = st.st_mtime; - while (offset <= size - 20 - 8) { + while (offset <= cache_mmap_size - 20 - 8) { /* After an array of active_nr index entries, * there can be arbitrary number of extended * sections, each of which is prefixed with @@ -783,10 +780,10 @@ int read_cache(void) * in 4-byte network byte order. */ unsigned long extsize; - memcpy(&extsize, (char *) map + offset + 4, 4); + memcpy(&extsize, (char *) cache_mmap + offset + 4, 4); extsize = ntohl(extsize); - if (read_index_extension(((const char *) map) + offset, - (char *) map + offset + 8, + if (read_index_extension(((const char *) cache_mmap) + offset, + (char *) cache_mmap + offset + 8, extsize) < 0) goto unmap; offset += 8; @@ -795,11 +792,28 @@ int read_cache(void) return active_nr; unmap: - munmap(map, size); + munmap(cache_mmap, cache_mmap_size); errno = EINVAL; die("index file corrupt"); } +int discard_cache() +{ + int ret; + + if (cache_mmap == NULL) + return 0; + ret = munmap(cache_mmap, cache_mmap_size); + cache_mmap = NULL; + cache_mmap_size = 0; + active_nr = active_cache_changed = 0; + index_file_timestamp = 0; + cache_tree_free(&active_cache_tree); + + /* no need to throw away allocated active_cache */ + return ret; +} + #define WRITE_BUFFER_SIZE 8192 static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned long write_buffer_len; diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index d34c6cf..b70e177 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -51,7 +51,7 @@ test_expect_success setup ' ' test_expect_success 'reference merge' ' - git merge -s recursive "reference merge" HEAD master + git merge -s recur "reference merge" HEAD master ' test_expect_success rebase ' -- cgit v0.10.2-6-g49f6 From 06d30f4f3eea71bce4cf48db3ea384976b3983b7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 9 Jul 2006 00:42:26 -0700 Subject: recur vs recursive: help testing without touching too many stuff. During git-merge-recur development, you could set an environment variable GIT_USE_RECUR_FOR_RECURSIVE to use WIP recur in place of the recursive strategy. Signed-off-by: Junio C Hamano diff --git a/TEST b/TEST index d530983..7286e2a 100755 --- a/TEST +++ b/TEST @@ -1,10 +1,14 @@ #!/bin/sh -x + cd t || exit -./t3400-rebase.sh "$@" && \ -./t6020-merge-df.sh "$@" && \ -./t3401-rebase-partial.sh "$@" && \ -./t6021-merge-criss-cross.sh "$@" && \ -./t3402-rebase-merge.sh "$@" && \ -./t6022-merge-rename.sh "$@" && \ -./t6010-merge-base.sh "$@" && \ +GIT_USE_RECUR_FOR_RECURSIVE=LetsTryIt +export GIT_USE_RECUR_FOR_RECURSIVE + +./t3400-rebase.sh "$@" && +./t6020-merge-df.sh "$@" && +./t3401-rebase-partial.sh "$@" && +./t6021-merge-criss-cross.sh "$@" && +./t3402-rebase-merge.sh "$@" && +./t6022-merge-rename.sh "$@" && +./t6010-merge-base.sh "$@" && : diff --git a/git-merge.sh b/git-merge.sh index b26ca14..9b68115 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,8 +9,13 @@ USAGE='[-n] [--no-commit] [--squash] [-s ]... < LF=' ' -all_strategies='recur recur octopus resolve stupid ours' -default_twohead_strategies='recur' +all_strategies='recursive recur octopus resolve stupid ours' +case "${GIT_USE_RECUR_FOR_RECURSIVE}" in +'') + default_twohead_strategies=recursive ;; +?*) + default_twohead_strategies=recur ;; +esac default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= @@ -110,6 +115,10 @@ do strategy="$2" shift ;; esac + case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in + recursive,?*) + strategy=recur ;; + esac case " $all_strategies " in *" $strategy "*) use_strategies="$use_strategies$strategy " ;; diff --git a/git-rebase.sh b/git-rebase.sh index 2a4c8c8..8c5da72 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -35,7 +35,13 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase -strategy=recur +case "${GIT_USE_RECUR_FOR_RECURSIVE}" in +'') + strategy=recursive ;; +?*) + strategy=recur ;; +esac + do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 @@ -198,6 +204,11 @@ do shift done +case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in +recursive,?*) + strategy=recur ;; +esac + # Make sure we do not have .dotest if test -z "$do_merge" then @@ -292,7 +303,7 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recur" +if test "@@NO_PYTHON@@" && test "$strategy" = "recursive" then die 'The recursive merge strategy currently relies on Python, which this installation of git was not configured with. Please consider diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index b70e177..d34c6cf 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -51,7 +51,7 @@ test_expect_success setup ' ' test_expect_success 'reference merge' ' - git merge -s recur "reference merge" HEAD master + git merge -s recursive "reference merge" HEAD master ' test_expect_success rebase ' -- cgit v0.10.2-6-g49f6 From e1a7c81f6a8c5a96a3d07632514f85d9470c3a82 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Tue, 18 Jul 2006 01:52:14 +1000 Subject: gitk: Minor cleanups Removed some unnecessary quotes and globals, updated copyright notice. Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index 7d540c1..7b86c19 100755 --- a/gitk +++ b/gitk @@ -2,7 +2,7 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright (C) 2005 Paul Mackerras. All rights reserved. +# Copyright (C) 2005-2006 Paul Mackerras. All rights reserved. # This program is free software; it may be used, copied, modified # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. @@ -341,13 +341,13 @@ proc readrefs {} { set tag {} catch { set commit [exec git rev-parse "$id^0"] - if {"$commit" != "$id"} { + if {$commit != $id} { set tagids($name) $commit lappend idtags($commit) $name } } catch { - set tagcontents($name) [exec git cat-file tag "$id"] + set tagcontents($name) [exec git cat-file tag $id] } } elseif { $type == "heads" } { set headids($name) $id @@ -3263,8 +3263,7 @@ proc show_status {msg} { proc finishcommits {} { global commitidx phase curview - global canv mainfont ctext maincursor textcursor - global findinprogress pending_select + global pending_select if {$commitidx($curview) > 0} { drawrest @@ -3307,9 +3306,7 @@ proc notbusy {what} { } proc drawrest {} { - global numcommits global startmsecs - global canvy0 numcommits linespc global rowlaidout commitidx curview global pending_select @@ -3323,6 +3320,7 @@ proc drawrest {} { } set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}] + #global numcommits #puts "overall $drawmsecs ms for $numcommits commits" } -- cgit v0.10.2-6-g49f6 From 96bc4de85cf810db5c7cd94bf0688a98a64a0bc7 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 26 Jul 2006 03:03:58 +0200 Subject: Eliminate Scalar::Util usage from private-Error.pm We used just the blessed() routine so steal it from Scalar/Util.pm. Unfortunately, Scalar::Util is not bundled with older Perl versions. This is a newer much saner blessed() version by Randal L. Schwarz. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/perl/private-Error.pm b/perl/private-Error.pm index ebd0749..8fff866 100644 --- a/perl/private-Error.pm +++ b/perl/private-Error.pm @@ -43,8 +43,6 @@ $Error::ObjectifyCallback = \&throw_Error_Simple; # Exported subs are defined in Error::subs -use Scalar::Util (); - sub import { shift; local $Exporter::ExportLevel = $Exporter::ExportLevel + 1; @@ -290,6 +288,14 @@ use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS); @ISA = qw(Exporter); + +sub blessed { + my $item = shift; + local $@; # don't kill an outer $@ + ref $item and eval { $item->can('can') }; +} + + sub run_clauses ($$$\@) { my($clauses,$err,$wantarray,$result) = @_; my $code = undef; @@ -312,7 +318,7 @@ sub run_clauses ($$$\@) { $i -= 2; next CATCHLOOP; } - elsif(Scalar::Util::blessed($err) && $err->isa($pkg)) { + elsif(blessed($err) && $err->isa($pkg)) { $code = $catch->[$i+1]; while(1) { my $more = 0; @@ -421,7 +427,7 @@ sub try (&;$) { if (defined($err)) { - if (Scalar::Util::blessed($err) && $err->can('throw')) + if (blessed($err) && $err->can('throw')) { throw $err; } -- cgit v0.10.2-6-g49f6 From 3af244caa8297793f29d7422bb19d9da1bf07b5e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 27 Jul 2006 13:17:07 +0200 Subject: Cumulative update of merge-recursive in C This contains mainly three sorts of fixes: - get rid of small wrapper functions - reuse the diff_filespec structure when sha1, mode & path are needed - Junio's pedantic updates Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 8d30519..d78f58d 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -25,9 +25,9 @@ /*#define DEBUG*/ #ifdef DEBUG -#define debug(args, ...) fprintf(stderr, args, ## __VA_ARGS__) +#define debug(...) fprintf(stderr, __VA_ARGS__) #else -#define debug(args, ...) +#define debug(...) do { ; /* nothing */ } while (0) #endif #ifdef DEBUG @@ -61,13 +61,14 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) fputc('\n', stderr); } -static void ls_files() { +static void ls_files(void) { int i; for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; show_ce_entry("", ce); } fprintf(stderr, "---\n"); + if (0) ls_files(); /* avoid "unused" warning */ } #endif @@ -76,41 +77,6 @@ static void ls_files() { * - (const char *)commit->util set to the name, and * - *(int *)commit->object.sha1 set to the virtual id. */ -static const char *commit_title(struct commit *commit, int *len) -{ - const char *s = "(null commit)"; - *len = strlen(s); - - if ( commit->util ) { - s = commit->util; - *len = strlen(s); - } else { - if ( parse_commit(commit) != 0 ) { - s = "(bad commit)"; - *len = strlen(s); - } else { - s = commit->buffer; - char prev = '\0'; - while ( *s ) { - if ( '\n' == prev && '\n' == *s ) { - ++s; - break; - } - prev = *s++; - } - *len = 0; - while ( s[*len] && '\n' != s[*len] ) - ++(*len); - } - } - return s; -} - -static const char *commit_hex_sha1(const struct commit *commit) -{ - return commit->util ? "virtual" : commit ? - sha1_to_hex(commit->object.sha1) : "undefined"; -} static unsigned commit_list_count(const struct commit_list *l) { @@ -136,41 +102,11 @@ static struct commit *make_virtual_commit(struct tree *tree, const char *comment */ static int sha_eq(const unsigned char *a, const unsigned char *b) { - if ( !a && !b ) + if (!a && !b) return 2; return a && b && memcmp(a, b, 20) == 0; } -static void memswp(void *p1, void *p2, unsigned n) -{ - unsigned char *a = p1, *b = p2; - while ( n-- ) { - *a ^= *b; - *b ^= *a; - *a ^= *b; - ++a; - ++b; - } -} - -/* - * TODO: we should convert the merge_result users to - * int blabla(..., struct commit **result) - * like everywhere else in git. - * Same goes for merge_tree_result and merge_file_info. - */ -struct merge_result -{ - struct commit *commit; - unsigned clean:1; -}; - -struct merge_tree_result -{ - struct tree *tree; - unsigned clean:1; -}; - /* * TODO: check if we can just reuse the active_cache structure: it is already * sorted (by name, stage). @@ -195,7 +131,7 @@ static void output(const char *fmt, ...) { va_list args; int i; - for ( i = output_indent; i--; ) + for (i = output_indent; i--;) fputs(" ", stdout); va_start(args, fmt); vfprintf(stdout, fmt, args); @@ -203,11 +139,37 @@ static void output(const char *fmt, ...) fputc('\n', stdout); } +static void output_commit_title(struct commit *commit) +{ + int i; + for (i = output_indent; i--;) + fputs(" ", stdout); + if (commit->util) + printf("virtual %s\n", (char *)commit->util); + else { + printf("%s ", sha1_to_hex(commit->object.sha1)); + if (parse_commit(commit) != 0) + printf("(bad commit)\n"); + else { + const char *s; + int len; + for (s = commit->buffer; *s; s++) + if (*s == '\n' && s[1] == '\n') { + s += 2; + break; + } + for (len = 0; s[len] && '\n' != s[len]; len++) + ; /* do nothing */ + printf("%.*s\n", len, s); + } + } +} + static const char *original_index_file; static const char *temporary_index_file; static int cache_dirty = 0; -static int flush_cache() +static int flush_cache(void) { /* flush temporary index */ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); @@ -290,11 +252,12 @@ static int git_read_tree(const struct tree *tree) getenv("GIT_INDEX_FILE"), sha1_to_hex(tree->object.sha1)); #endif + int rc; const char *argv[] = { "git-read-tree", NULL, NULL, }; if (cache_dirty) die("read-tree with dirty cache"); argv[1] = sha1_to_hex(tree->object.sha1); - int rc = run_command_v(2, argv); + rc = run_command_v(2, argv); return rc < 0 ? -1: rc; } @@ -314,6 +277,7 @@ static int git_merge_trees(const char *update_arg, sha1_to_hex(head->object.sha1), sha1_to_hex(merge->object.sha1)); #endif + int rc; const char *argv[] = { "git-read-tree", NULL, "-m", NULL, NULL, NULL, NULL, @@ -324,50 +288,42 @@ static int git_merge_trees(const char *update_arg, argv[3] = sha1_to_hex(common->object.sha1); argv[4] = sha1_to_hex(head->object.sha1); argv[5] = sha1_to_hex(merge->object.sha1); - int rc = run_command_v(6, argv); + rc = run_command_v(6, argv); return rc < 0 ? -1: rc; } /* * TODO: this can be streamlined by refactoring builtin-write-tree.c */ -static struct tree *git_write_tree() +static struct tree *git_write_tree(void) { #if 0 fprintf(stderr, "GIT_INDEX_FILE='%s' git-write-tree\n", getenv("GIT_INDEX_FILE")); #endif - if (cache_dirty) - flush_cache(); - FILE *fp = popen("git-write-tree 2>/dev/null", "r"); + FILE *fp; + int rc; char buf[41]; unsigned char sha1[20]; int ch; unsigned i = 0; - while ( (ch = fgetc(fp)) != EOF ) - if ( i < sizeof(buf)-1 && ch >= '0' && ch <= 'f' ) + if (cache_dirty) + flush_cache(); + fp = popen("git-write-tree 2>/dev/null", "r"); + while ((ch = fgetc(fp)) != EOF) + if (i < sizeof(buf)-1 && ch >= '0' && ch <= 'f') buf[i++] = ch; else break; - int rc = pclose(fp); - if ( rc == -1 || WEXITSTATUS(rc) ) + rc = pclose(fp); + if (rc == -1 || WEXITSTATUS(rc)) return NULL; buf[i] = '\0'; - if ( get_sha1(buf, sha1) != 0 ) + if (get_sha1(buf, sha1) != 0) return NULL; return lookup_tree(sha1); } -/* - * TODO: get rid of files_and_dirs; we do not use it except for - * current_file_set and current_dir_set, which are global already. - */ -static struct -{ - struct path_list *files; - struct path_list *dirs; -} files_and_dirs; - static int save_files_dirs(const unsigned char *sha1, const char *base, int baselen, const char *path, unsigned int mode, int stage) @@ -379,70 +335,36 @@ static int save_files_dirs(const unsigned char *sha1, newpath[baselen + len] = '\0'; if (S_ISDIR(mode)) - path_list_insert(newpath, files_and_dirs.dirs); + path_list_insert(newpath, ¤tDirectorySet); else - path_list_insert(newpath, files_and_dirs.files); + path_list_insert(newpath, ¤tFileSet); free(newpath); return READ_TREE_RECURSIVE; } -static int get_files_dirs(struct tree *tree, - struct path_list *files, - struct path_list *dirs) +static int get_files_dirs(struct tree *tree) { int n; - files_and_dirs.files = files; - files_and_dirs.dirs = dirs; debug("get_files_dirs ...\n"); if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0) { debug(" get_files_dirs done (0)\n"); return 0; } - n = files->nr + dirs->nr; + n = currentFileSet.nr + currentDirectorySet.nr; debug(" get_files_dirs done (%d)\n", n); return n; } /* - * TODO: this wrapper is so small, we can use path_list_lookup directly. - * Same goes for index_entry_get(), free_index_entries(), find_rename_bysrc(), - * free_rename_entries(). - */ -static struct stage_data *index_entry_find(struct path_list *ents, - const char *path) -{ - struct path_list_item *item = path_list_lookup(path, ents); - if (item) - return item->util; - return NULL; -} - -static struct stage_data *index_entry_get(struct path_list *ents, - const char *path) -{ - struct path_list_item *item = path_list_lookup(path, ents); - - if (item == NULL) { - item = path_list_insert(path, ents); - item->util = xcalloc(1, sizeof(struct stage_data)); - } - return item->util; -} - -/* - * TODO: since the result of index_entry_from_db() is tucked into a - * path_list anyway, this helper can do that already. - */ -/* * Returns a index_entry instance which doesn't have to correspond to * a real cache entry in Git's index. */ -static struct stage_data *index_entry_from_db(const char *path, - struct tree *o, - struct tree *a, - struct tree *b) +static struct stage_data *insert_stage_data(const char *path, + struct tree *o, struct tree *a, struct tree *b, + struct path_list *entries) { + struct path_list_item *item; struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); get_tree_entry(o->object.sha1, path, e->stages[1].sha, &e->stages[1].mode); @@ -450,24 +372,16 @@ static struct stage_data *index_entry_from_db(const char *path, e->stages[2].sha, &e->stages[2].mode); get_tree_entry(b->object.sha1, path, e->stages[3].sha, &e->stages[3].mode); + item = path_list_insert(path, entries); + item->util = e; return e; } -static void free_index_entries(struct path_list **ents) -{ - if (!*ents) - return; - - path_list_clear(*ents, 1); - free(*ents); - *ents = NULL; -} - /* * Create a dictionary mapping file names to CacheEntry objects. The * dictionary contains one entry for every path with a non-zero stage entry. */ -static struct path_list *get_unmerged() +static struct path_list *get_unmerged(void) { struct path_list *unmerged = xcalloc(1, sizeof(struct path_list)); int i; @@ -478,16 +392,22 @@ static struct path_list *get_unmerged() cache_dirty++; } for (i = 0; i < active_nr; i++) { + struct path_list_item *item; + struct stage_data *e; struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; - struct stage_data *e = index_entry_get(unmerged, ce->name); + item = path_list_lookup(ce->name, unmerged); + if (!item) { + item = path_list_insert(ce->name, unmerged); + item->util = xcalloc(1, sizeof(struct stage_data)); + } + e = item->util; e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode); memcpy(e->stages[ce_stage(ce)].sha, ce->sha1, 20); } - debug(" get_unmerged done\n"); return unmerged; } @@ -499,25 +419,6 @@ struct rename unsigned processed:1; }; -static struct rename *find_rename_bysrc(struct path_list *e, - const char *name) -{ - struct path_list_item *item = path_list_lookup(name, e); - if (item) - return item->util; - return NULL; -} - -static void free_rename_entries(struct path_list **list) -{ - if (!*list) - return; - - path_list_clear(*list, 0); - free(*list); - *list = NULL; -} - /* * Get information of all renames which occured between 'oTree' and * 'tree'. We need the three trees in the merge ('oTree', 'aTree' and @@ -530,13 +431,16 @@ static struct path_list *get_renames(struct tree *tree, struct tree *bTree, struct path_list *entries) { + int i; + struct path_list *renames; + struct diff_options opts; #ifdef DEBUG time_t t = time(0); + debug("getRenames ...\n"); #endif - int i; - struct path_list *renames = xcalloc(1, sizeof(struct path_list)); - struct diff_options opts; + + renames = xcalloc(1, sizeof(struct path_list)); diff_setup(&opts); opts.recursive = 1; opts.detect_rename = DIFF_DETECT_RENAME; @@ -546,6 +450,7 @@ static struct path_list *get_renames(struct tree *tree, diff_tree_sha1(oTree->object.sha1, tree->object.sha1, "", &opts); diffcore_std(&opts); for (i = 0; i < diff_queued_diff.nr; ++i) { + struct path_list_item *item; struct rename *re; struct diff_filepair *pair = diff_queued_diff.queue[i]; if (pair->status != 'R') { @@ -555,78 +460,66 @@ static struct path_list *get_renames(struct tree *tree, re = xmalloc(sizeof(*re)); re->processed = 0; re->pair = pair; - re->src_entry = index_entry_find(entries, re->pair->one->path); - /* TODO: should it not be an error, if src_entry was found? */ - if ( !re->src_entry ) { - re->src_entry = index_entry_from_db(re->pair->one->path, - oTree, aTree, bTree); - struct path_list_item *item = - path_list_insert(re->pair->one->path, entries); - item->util = re->src_entry; - } - re->dst_entry = index_entry_find(entries, re->pair->two->path); - if ( !re->dst_entry ) { - re->dst_entry = index_entry_from_db(re->pair->two->path, - oTree, aTree, bTree); - struct path_list_item *item = - path_list_insert(re->pair->two->path, entries); - item->util = re->dst_entry; - } - struct path_list_item *item = path_list_insert(pair->one->path, renames); + item = path_list_lookup(re->pair->one->path, entries); + if (!item) + re->src_entry = insert_stage_data(re->pair->one->path, + oTree, aTree, bTree, entries); + else + re->src_entry = item->util; + + item = path_list_lookup(re->pair->two->path, entries); + if (!item) + re->dst_entry = insert_stage_data(re->pair->two->path, + oTree, aTree, bTree, entries); + else + re->dst_entry = item->util; + item = path_list_insert(pair->one->path, renames); item->util = re; } opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_queued_diff.nr = 0; diff_flush(&opts); +#ifdef DEBUG debug(" getRenames done in %ld\n", time(0)-t); +#endif return renames; } -/* - * TODO: the code would be way nicer, if we had a struct containing just sha1 and mode. - * In this particular case, we might get away reusing stage_data, no? - */ -int update_stages(const char *path, - unsigned char *osha, unsigned omode, - unsigned char *asha, unsigned amode, - unsigned char *bsha, unsigned bmode, - int clear /* =True */) +int update_stages(const char *path, struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, int clear) { int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; - if ( clear ) - if (add_cacheinfo(0, null_sha1, path, 0, 0, options)) + if (clear) + if (remove_file_from_cache(path)) return -1; - if ( omode ) - if (add_cacheinfo(omode, osha, path, 1, 0, options)) + if (o) + if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) return -1; - if ( amode ) - if (add_cacheinfo(omode, osha, path, 2, 0, options)) + if (a) + if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) return -1; - if ( bmode ) - if (add_cacheinfo(omode, osha, path, 3, 0, options)) + if (b) + if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) return -1; return 0; } -/* - * TODO: there has to be a function in libgit doing this exact thing. - */ static int remove_path(const char *name) { - int ret; - char *slash; + int ret, len; + char *slash, *dirs; ret = unlink(name); - if ( ret ) + if (ret) return ret; - int len = strlen(name); - char *dirs = malloc(len+1); + len = strlen(name); + dirs = malloc(len+1); memcpy(dirs, name, len); dirs[len] = '\0'; - while ( (slash = strrchr(name, '/')) ) { + while ((slash = strrchr(name, '/'))) { *slash = '\0'; len = slash - name; - if ( rmdir(name) != 0 ) + if (rmdir(name) != 0) break; } free(dirs); @@ -636,7 +529,7 @@ static int remove_path(const char *name) /* General TODO: unC99ify the code: no declaration after code */ /* General TODO: no javaIfiCation: rename updateCache to update_cache */ /* - * TODO: once we no longer call external programs, we'd probably be better of + * TODO: once we no longer call external programs, we'd probably be better off * not setting / getting the environment variable GIT_INDEX_FILE all the time. */ int remove_file(int clean, const char *path) @@ -644,17 +537,17 @@ int remove_file(int clean, const char *path) int updateCache = index_only || clean; int updateWd = !index_only; - if ( updateCache ) { + if (updateCache) { if (!cache_dirty) read_cache_from(getenv("GIT_INDEX_FILE")); cache_dirty++; if (remove_file_from_cache(path)) return -1; } - if ( updateWd ) + if (updateWd) { unlink(path); - if ( errno != ENOENT || errno != EISDIR ) + if (errno != ENOENT || errno != EISDIR) return -1; remove_path(path); } @@ -664,55 +557,31 @@ int remove_file(int clean, const char *path) static char *unique_path(const char *path, const char *branch) { char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); + int suffix = 0; + struct stat st; + char *p = newpath + strlen(newpath); strcpy(newpath, path); strcat(newpath, "~"); - char *p = newpath + strlen(newpath); strcpy(p, branch); - for ( ; *p; ++p ) - if ( '/' == *p ) + for (; *p; ++p) + if ('/' == *p) *p = '_'; - int suffix = 0; - struct stat st; - while ( path_list_has_path(¤tFileSet, newpath) || - path_list_has_path(¤tDirectorySet, newpath) || - lstat(newpath, &st) == 0 ) { + while (path_list_has_path(¤tFileSet, newpath) || + path_list_has_path(¤tDirectorySet, newpath) || + lstat(newpath, &st) == 0) sprintf(p, "_%d", suffix++); - } + path_list_insert(newpath, ¤tFileSet); return newpath; } -/* - * TODO: except for create_last, this so looks like - * safe_create_leading_directories(). - */ -static int mkdir_p(const char *path, unsigned long mode, int create_last) +static int mkdir_p(const char *path, unsigned long mode) { + /* path points to cache entries, so strdup before messing with it */ char *buf = strdup(path); - char *p; - - for ( p = buf; *p; ++p ) { - if ( *p != '/' ) - continue; - *p = '\0'; - if (mkdir(buf, mode)) { - int e = errno; - if ( e == EEXIST ) { - struct stat st; - if ( !stat(buf, &st) && S_ISDIR(st.st_mode) ) - goto next; /* ok */ - errno = e; - } - free(buf); - return -1; - } - next: - *p = '/'; - } + int result = safe_create_leading_directories(buf); free(buf); - if ( create_last && mkdir(path, mode) ) - return -1; - return 0; + return result; } static void flush_buffer(int fd, const char *buf, unsigned long size) @@ -732,17 +601,16 @@ static void flush_buffer(int fd, const char *buf, unsigned long size) } } -/* General TODO: reindent according to guide lines (no if ( blabla )) */ void update_file_flags(const unsigned char *sha, - unsigned mode, - const char *path, - int updateCache, - int updateWd) + unsigned mode, + const char *path, + int update_cache, + int update_wd) { - if ( index_only ) - updateWd = 0; + if (index_only) + update_wd = 0; - if ( updateWd ) { + if (update_wd) { char type[20]; void *buf; unsigned long size; @@ -750,37 +618,38 @@ void update_file_flags(const unsigned char *sha, buf = read_sha1_file(sha, type, &size); if (!buf) die("cannot read object %s '%s'", sha1_to_hex(sha), path); - if ( strcmp(type, blob_type) != 0 ) + if (strcmp(type, blob_type) != 0) die("blob expected for %s '%s'", sha1_to_hex(sha), path); - if ( S_ISREG(mode) ) { - if ( mkdir_p(path, 0777, 0 /* don't create last element */) ) + if (S_ISREG(mode)) { + int fd; + if (mkdir_p(path, 0777)) die("failed to create path %s: %s", path, strerror(errno)); unlink(path); - if ( mode & 0100 ) + if (mode & 0100) mode = 0777; else mode = 0666; - int fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); - if ( fd < 0 ) + fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); + if (fd < 0) die("failed to open %s: %s", path, strerror(errno)); flush_buffer(fd, buf, size); close(fd); - } else if ( S_ISLNK(mode) ) { - char *linkTarget = malloc(size + 1); - memcpy(linkTarget, buf, size); - linkTarget[size] = '\0'; - mkdir_p(path, 0777, 0); - symlink(linkTarget, path); + } else if (S_ISLNK(mode)) { + char *lnk = malloc(size + 1); + memcpy(lnk, buf, size); + lnk[size] = '\0'; + mkdir_p(path, 0777); + unlink(lnk); + symlink(lnk, path); } else die("do not know what to do with %06o %s '%s'", mode, sha1_to_hex(sha), path); } - if ( updateCache ) - add_cacheinfo(mode, sha, path, 0, updateWd, ADD_CACHE_OK_TO_ADD); + if (update_cache) + add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); } -/* TODO: is this often used? if not, do direct call */ void update_file(int clean, const unsigned char *sha, unsigned mode, @@ -819,64 +688,53 @@ static char *git_unpack_file(const unsigned char *sha1, char *path) return path; } -/* - * TODO: the signature would be much more efficient using stage_data - */ -static struct merge_file_info merge_file(const char *oPath, - const unsigned char *oSha, - unsigned oMode, - const char *aPath, - const unsigned char *aSha, - unsigned aMode, - const char *bPath, - const unsigned char *bSha, - unsigned bMode, - const char *branch1Name, - const char *branch2Name) +static struct merge_file_info merge_file(struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + const char *branch1Name, const char *branch2Name) { struct merge_file_info result; result.merge = 0; result.clean = 1; - if ( (S_IFMT & aMode) != (S_IFMT & bMode) ) { + if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { result.clean = 0; - if ( S_ISREG(aMode) ) { - result.mode = aMode; - memcpy(result.sha, aSha, 20); + if (S_ISREG(a->mode)) { + result.mode = a->mode; + memcpy(result.sha, a->sha1, 20); } else { - result.mode = bMode; - memcpy(result.sha, bSha, 20); + result.mode = b->mode; + memcpy(result.sha, b->sha1, 20); } } else { - if ( memcmp(aSha, oSha, 20) != 0 && memcmp(bSha, oSha, 20) != 0 ) + if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) result.merge = 1; - result.mode = aMode == oMode ? bMode: aMode; + result.mode = a->mode == o->mode ? b->mode: a->mode; - if ( memcmp(aSha, oSha, 20) == 0 ) - memcpy(result.sha, bSha, 20); - else if ( memcmp(bSha, oSha, 20) == 0 ) - memcpy(result.sha, aSha, 20); - else if ( S_ISREG(aMode) ) { - - int code = 1; + if (sha_eq(a->sha1, o->sha1)) + memcpy(result.sha, b->sha1, 20); + else if (sha_eq(b->sha1, o->sha1)) + memcpy(result.sha, a->sha1, 20); + else if (S_ISREG(a->mode)) { + int code = 1, fd; + struct stat st; char orig[PATH_MAX]; char src1[PATH_MAX]; char src2[PATH_MAX]; - - git_unpack_file(oSha, orig); - git_unpack_file(aSha, src1); - git_unpack_file(bSha, src2); - const char *argv[] = { "merge", "-L", NULL, "-L", NULL, "-L", NULL, src1, orig, src2, NULL }; char *la, *lb, *lo; - argv[2] = la = strdup(mkpath("%s/%s", branch1Name, aPath)); - argv[6] = lb = strdup(mkpath("%s/%s", branch2Name, bPath)); - argv[4] = lo = strdup(mkpath("orig/%s", oPath)); + + git_unpack_file(o->sha1, orig); + git_unpack_file(a->sha1, src1); + git_unpack_file(b->sha1, src2); + + argv[2] = la = strdup(mkpath("%s/%s", branch1Name, a->path)); + argv[6] = lb = strdup(mkpath("%s/%s", branch2Name, b->path)); + argv[4] = lo = strdup(mkpath("orig/%s", o->path)); #if 0 printf("%s %s %s %s %s %s %s %s %s %s\n", @@ -888,17 +746,15 @@ static struct merge_file_info merge_file(const char *oPath, free(la); free(lb); free(lo); - if ( code && code < -256 ) { + if (code && code < -256) { die("Failed to execute 'merge'. merge(1) is used as the " "file-level merge tool. Is 'merge' in your path?"); } - struct stat st; - int fd = open(src1, O_RDONLY); + fd = open(src1, O_RDONLY); if (fd < 0 || fstat(fd, &st) < 0 || index_fd(result.sha, fd, &st, 1, "blob")) die("Unable to add %s to database", src1); - close(fd); unlink(orig); unlink(src1); @@ -906,12 +762,12 @@ static struct merge_file_info merge_file(const char *oPath, result.clean = WEXITSTATUS(code) == 0; } else { - if ( !(S_ISLNK(aMode) || S_ISLNK(bMode)) ) + if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) die("cannot merge modes?"); - memcpy(result.sha, aSha, 20); + memcpy(result.sha, a->sha1, 20); - if ( memcmp(aSha, bSha, 20) != 0 ) + if (!sha_eq(a->sha1, b->sha1)) result.clean = 0; } } @@ -942,17 +798,9 @@ static void conflict_rename_rename(struct rename *ren1, ren2_dst, branch1, dstName2); remove_file(0, ren2_dst); } - update_stages(dstName1, - NULL, 0, - ren1->pair->two->sha1, ren1->pair->two->mode, - NULL, 0, - 1 /* clear */); - update_stages(dstName2, - NULL, 0, - NULL, 0, - ren2->pair->two->sha1, ren2->pair->two->mode, - 1 /* clear */); - while ( delp-- ) + update_stages(dstName1, NULL, ren1->pair->two, NULL, 1); + update_stages(dstName2, NULL, NULL, ren2->pair->two, 1); + while (delp--) free(del[delp]); } @@ -989,66 +837,73 @@ static int process_renames(struct path_list *renamesA, const char *branchNameA, const char *branchNameB) { - int cleanMerge = 1, i; - struct path_list srcNames = {NULL, 0, 0, 0}, byDstA = {NULL, 0, 0, 0}, byDstB = {NULL, 0, 0, 0}; + int cleanMerge = 1, i, j; + struct path_list byDstA = {NULL, 0, 0, 0}, byDstB = {NULL, 0, 0, 0}; const struct rename *sre; - /* - * TODO: think about a saner way to do this. - * Since both renamesA and renamesB are sorted, it should - * be much more efficient to traverse both simultaneously, - * only byDstA and byDstB should be needed. - */ - debug("processRenames...\n"); for (i = 0; i < renamesA->nr; i++) { sre = renamesA->items[i].util; - path_list_insert(sre->pair->one->path, &srcNames); path_list_insert(sre->pair->two->path, &byDstA)->util = sre->dst_entry; } for (i = 0; i < renamesB->nr; i++) { sre = renamesB->items[i].util; - path_list_insert(sre->pair->one->path, &srcNames); path_list_insert(sre->pair->two->path, &byDstB)->util = sre->dst_entry; } - for (i = 0; i < srcNames.nr; i++) { - char *src = srcNames.items[i].path; + for (i = 0, j = 0; i < renamesA->nr || j < renamesB->nr;) { + int compare; + char *src; struct path_list *renames1, *renames2, *renames2Dst; - struct rename *ren1, *ren2; + struct rename *ren1 = NULL, *ren2 = NULL; const char *branchName1, *branchName2; - ren1 = find_rename_bysrc(renamesA, src); - ren2 = find_rename_bysrc(renamesB, src); + const char *ren1_src, *ren1_dst; + + if (i >= renamesA->nr) { + compare = 1; + ren2 = renamesB->items[j++].util; + } else if (j >= renamesB->nr) { + compare = -1; + ren1 = renamesA->items[i++].util; + } else { + compare = strcmp(renamesA->items[i].path, + renamesB->items[j].path); + ren1 = renamesA->items[i++].util; + ren2 = renamesB->items[j++].util; + } + /* TODO: refactor, so that 1/2 are not needed */ - if ( ren1 ) { + if (ren1) { renames1 = renamesA; renames2 = renamesB; renames2Dst = &byDstB; branchName1 = branchNameA; branchName2 = branchNameB; } else { + struct rename *tmp; renames1 = renamesB; renames2 = renamesA; renames2Dst = &byDstA; branchName1 = branchNameB; branchName2 = branchNameA; - struct rename *tmp = ren2; + tmp = ren2; ren2 = ren1; ren1 = tmp; } + src = ren1->pair->one->path; ren1->dst_entry->processed = 1; ren1->src_entry->processed = 1; - if ( ren1->processed ) + if (ren1->processed) continue; ren1->processed = 1; - const char *ren1_src = ren1->pair->one->path; - const char *ren1_dst = ren1->pair->two->path; + ren1_src = ren1->pair->one->path; + ren1_dst = ren1->pair->two->path; - if ( ren2 ) { + if (ren2) { const char *ren2_src = ren2->pair->one->path; const char *ren2_dst = ren2->pair->two->path; /* Renamed in 1 and renamed in 2 */ @@ -1067,57 +922,48 @@ static int process_renames(struct path_list *renamesA, } else { remove_file(1, ren1_src); struct merge_file_info mfi; - mfi = merge_file(ren1_src, - ren1->pair->one->sha1, - ren1->pair->one->mode, - ren1_dst, - ren1->pair->two->sha1, - ren1->pair->two->mode, - ren2_dst, - ren2->pair->two->sha1, - ren2->pair->two->mode, + mfi = merge_file(ren1->pair->one, + ren1->pair->two, + ren2->pair->two, branchName1, branchName2); - if ( mfi.merge || !mfi.clean ) + if (mfi.merge || !mfi.clean) output("Renaming %s->%s", src, ren1_dst); - if ( mfi.merge ) + if (mfi.merge) output("Auto-merging %s", ren1_dst); - if ( !mfi.clean ) { + if (!mfi.clean) { output("CONFLICT (content): merge conflict in %s", ren1_dst); cleanMerge = 0; - if ( !index_only ) + if (!index_only) update_stages(ren1_dst, - ren1->pair->one->sha1, - ren1->pair->one->mode, - ren1->pair->two->sha1, - ren1->pair->two->mode, - ren2->pair->two->sha1, - ren2->pair->two->mode, + ren1->pair->one, + ren1->pair->two, + ren2->pair->two, 1 /* clear */); } update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); } } else { /* Renamed in 1, maybe changed in 2 */ - remove_file(1, ren1_src); - - unsigned char srcShaOtherBranch[20], dstShaOtherBranch[20]; - unsigned srcModeOtherBranch, dstModeOtherBranch; + struct path_list_item *item; + /* we only use sha1 and mode of these */ + struct diff_filespec src_other, dst_other; + int tryMerge, stage = renamesA == renames1 ? 3: 2; - int stage = renamesA == renames1 ? 3: 2; - - memcpy(srcShaOtherBranch, ren1->src_entry->stages[stage].sha, 20); - srcModeOtherBranch = ren1->src_entry->stages[stage].mode; + remove_file(1, ren1_src); - memcpy(dstShaOtherBranch, ren1->dst_entry->stages[stage].sha, 20); - dstModeOtherBranch = ren1->dst_entry->stages[stage].mode; + memcpy(src_other.sha1, + ren1->src_entry->stages[stage].sha, 20); + src_other.mode = ren1->src_entry->stages[stage].mode; + memcpy(dst_other.sha1, + ren1->dst_entry->stages[stage].sha, 20); + dst_other.mode = ren1->dst_entry->stages[stage].mode; - int tryMerge = 0; - char *newPath; + tryMerge = 0; if (path_list_has_path(¤tDirectorySet, ren1_dst)) { cleanMerge = 0; @@ -1126,14 +972,15 @@ static int process_renames(struct path_list *renamesA, ren1_src, ren1_dst, branchName1, ren1_dst, branchName2); conflict_rename_dir(ren1, branchName1); - } else if ( memcmp(srcShaOtherBranch, null_sha1, 20) == 0 ) { + } else if (sha_eq(src_other.sha1, null_sha1)) { cleanMerge = 0; output("CONFLICT (rename/delete): Rename %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branchName1, branchName2); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); - } else if ( memcmp(dstShaOtherBranch, null_sha1, 20) != 0 ) { + } else if (!sha_eq(dst_other.sha1, null_sha1)) { + const char *newPath; cleanMerge = 0; tryMerge = 1; output("CONFLICT (rename/add): Rename %s->%s in %s. " @@ -1142,8 +989,9 @@ static int process_renames(struct path_list *renamesA, ren1_dst, branchName2); newPath = unique_path(ren1_dst, branchName2); output("Adding as %s instead", newPath); - update_file(0, dstShaOtherBranch, dstModeOtherBranch, newPath); - } else if ( (ren2 = find_rename_bysrc(renames2Dst, ren1_dst)) ) { + update_file(0, dst_other.sha1, dst_other.mode, newPath); + } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) { + ren2 = item->util; cleanMerge = 0; ren2->processed = 1; output("CONFLICT (rename/rename): Rename %s->%s in %s. " @@ -1154,53 +1002,41 @@ static int process_renames(struct path_list *renamesA, } else tryMerge = 1; - if ( tryMerge ) { - const char *oname = ren1_src; - const char *aname = ren1_dst; - const char *bname = ren1_src; - unsigned char osha[20], asha[20], bsha[20]; - unsigned omode = ren1->pair->one->mode; - unsigned amode = ren1->pair->two->mode; - unsigned bmode = srcModeOtherBranch; - memcpy(osha, ren1->pair->one->sha1, 20); - memcpy(asha, ren1->pair->two->sha1, 20); - memcpy(bsha, srcShaOtherBranch, 20); - const char *aBranch = branchName1; - const char *bBranch = branchName2; - - if ( renamesA != renames1 ) { - memswp(&aname, &bname, sizeof(aname)); - memswp(asha, bsha, 20); - memswp(&aBranch, &bBranch, sizeof(aBranch)); - } + if (tryMerge) { + struct diff_filespec *o, *a, *b; struct merge_file_info mfi; - mfi = merge_file(oname, osha, omode, - aname, asha, amode, - bname, bsha, bmode, - aBranch, bBranch); + src_other.path = (char *)ren1_src; + + o = ren1->pair->one; + if (renamesA == renames1) { + a = ren1->pair->two; + b = &src_other; + } else { + b = ren1->pair->two; + a = &src_other; + } + mfi = merge_file(o, a, b, + branchNameA, branchNameB); - if ( mfi.merge || !mfi.clean ) + if (mfi.merge || !mfi.clean) output("Renaming %s => %s", ren1_src, ren1_dst); - if ( mfi.merge ) + if (mfi.merge) output("Auto-merging %s", ren1_dst); - if ( !mfi.clean ) { + if (!mfi.clean) { output("CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); cleanMerge = 0; - if ( !index_only ) + if (!index_only) update_stages(ren1_dst, - osha, omode, - asha, amode, - bsha, bmode, - 1 /* clear */); + o, a, b, 1); } update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); } } } - path_list_clear(&srcNames, 0); - debug(" processRenames done\n"); + path_list_clear(&byDstA, 0); + path_list_clear(&byDstB, 0); if (cache_dirty) flush_cache(); @@ -1229,20 +1065,20 @@ static int process_entry(const char *path, struct stage_data *entry, unsigned aMode = entry->stages[2].mode; unsigned bMode = entry->stages[3].mode; - if ( oSha && (!aSha || !bSha) ) { + if (oSha && (!aSha || !bSha)) { /* Case A: Deleted in one */ - if ( (!aSha && !bSha) || - (sha_eq(aSha, oSha) && !bSha) || - (!aSha && sha_eq(bSha, oSha)) ) { + if ((!aSha && !bSha) || + (sha_eq(aSha, oSha) && !bSha) || + (!aSha && sha_eq(bSha, oSha))) { /* Deleted in both or deleted in one and * unchanged in the other */ - if ( aSha ) + if (aSha) output("Removing %s", path); remove_file(1, path); } else { /* Deleted in one and changed in the other */ cleanMerge = 0; - if ( !aSha ) { + if (!aSha) { output("CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", path, branch1Name, @@ -1257,8 +1093,8 @@ static int process_entry(const char *path, struct stage_data *entry, } } - } else if ( (!oSha && aSha && !bSha) || - (!oSha && !aSha && bSha) ) { + } else if ((!oSha && aSha && !bSha) || + (!oSha && !aSha && bSha)) { /* Case B: Added in one. */ const char *addBranch; const char *otherBranch; @@ -1266,7 +1102,7 @@ static int process_entry(const char *path, struct stage_data *entry, const unsigned char *sha; const char *conf; - if ( aSha ) { + if (aSha) { addBranch = branch1Name; otherBranch = branch2Name; mode = aMode; @@ -1279,9 +1115,9 @@ static int process_entry(const char *path, struct stage_data *entry, sha = bSha; conf = "directory/file"; } - if ( path_list_has_path(¤tDirectorySet, path) ) { - cleanMerge = 0; + if (path_list_has_path(¤tDirectorySet, path)) { const char *newPath = unique_path(path, addBranch); + cleanMerge = 0; output("CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", conf, path, otherBranch, path, newPath); @@ -1291,10 +1127,10 @@ static int process_entry(const char *path, struct stage_data *entry, output("Adding %s", path); update_file(1, sha, mode, path); } - } else if ( !oSha && aSha && bSha ) { + } else if (!oSha && aSha && bSha) { /* Case C: Added in both (check for same permissions). */ - if ( sha_eq(aSha, bSha) ) { - if ( aMode != bMode ) { + if (sha_eq(aSha, bSha)) { + if (aMode != bMode) { cleanMerge = 0; output("CONFLICT: File %s added identically in both branches, " "but permissions conflict %06o->%06o", @@ -1306,9 +1142,10 @@ static int process_entry(const char *path, struct stage_data *entry, assert(0 && "This case must be handled by git-read-tree"); } } else { + const char *newPath1, *newPath2; cleanMerge = 0; - const char *newPath1 = unique_path(path, branch1Name); - const char *newPath2 = unique_path(path, branch2Name); + newPath1 = unique_path(path, branch1Name); + newPath2 = unique_path(path, branch2Name); output("CONFLICT (add/add): File %s added non-identically " "in both branches. Adding as %s and %s instead.", path, newPath1, newPath2); @@ -1317,22 +1154,30 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(0, bSha, bMode, newPath2); } - } else if ( oSha && aSha && bSha ) { + } else if (oSha && aSha && bSha) { /* case D: Modified in both, but differently. */ - output("Auto-merging %s", path); struct merge_file_info mfi; - mfi = merge_file(path, oSha, oMode, - path, aSha, aMode, - path, bSha, bMode, + struct diff_filespec o, a, b; + + output("Auto-merging %s", path); + o.path = a.path = b.path = (char *)path; + memcpy(o.sha1, oSha, 20); + o.mode = oMode; + memcpy(a.sha1, aSha, 20); + a.mode = aMode; + memcpy(b.sha1, bSha, 20); + b.mode = bMode; + + mfi = merge_file(&o, &a, &b, branch1Name, branch2Name); - if ( mfi.clean ) + if (mfi.clean) update_file(1, mfi.sha, mfi.mode, path); else { cleanMerge = 0; output("CONFLICT (content): Merge conflict in %s", path); - if ( index_only ) + if (index_only) update_file(0, mfi.sha, mfi.mode, path); else update_file_flags(mfi.sha, mfi.mode, path, @@ -1347,73 +1192,68 @@ static int process_entry(const char *path, struct stage_data *entry, return cleanMerge; } -static struct merge_tree_result merge_trees(struct tree *head, - struct tree *merge, - struct tree *common, - const char *branch1Name, - const char *branch2Name) +static int merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1Name, + const char *branch2Name, + struct tree **result) { - int code; - struct merge_tree_result result = { NULL, 0 }; - if ( !memcmp(common->object.sha1, merge->object.sha1, 20) ) { + int code, clean; + if (sha_eq(common->object.sha1, merge->object.sha1)) { output("Already uptodate!"); - result.tree = head; - result.clean = 1; - return result; + *result = head; + return 1; } - debug("merge_trees ...\n"); code = git_merge_trees(index_only ? "-i": "-u", common, head, merge); - if ( code != 0 ) + if (code != 0) die("merging of trees %s and %s failed", sha1_to_hex(head->object.sha1), sha1_to_hex(merge->object.sha1)); - result.tree = git_write_tree(); + *result = git_write_tree(); - if ( !result.tree ) { + if (!*result) { + struct path_list *entries, *re_head, *re_merge; + int i; path_list_clear(¤tFileSet, 1); path_list_clear(¤tDirectorySet, 1); - get_files_dirs(head, ¤tFileSet, ¤tDirectorySet); - get_files_dirs(merge, ¤tFileSet, ¤tDirectorySet); + get_files_dirs(head); + get_files_dirs(merge); - struct path_list *entries = get_unmerged(); - struct path_list *re_head, *re_merge; + entries = get_unmerged(); re_head = get_renames(head, common, head, merge, entries); re_merge = get_renames(merge, common, head, merge, entries); - result.clean = process_renames(re_head, re_merge, - branch1Name, branch2Name); - debug("\tprocessing entries...\n"); - int i; + clean = process_renames(re_head, re_merge, + branch1Name, branch2Name); for (i = 0; i < entries->nr; i++) { const char *path = entries->items[i].path; struct stage_data *e = entries->items[i].util; if (e->processed) continue; if (!process_entry(path, e, branch1Name, branch2Name)) - result.clean = 0; + clean = 0; } - free_rename_entries(&re_merge); - free_rename_entries(&re_head); - free_index_entries(&entries); + path_list_clear(re_merge, 0); + path_list_clear(re_head, 0); + path_list_clear(entries, 1); - if (result.clean || index_only) - result.tree = git_write_tree(); + if (clean || index_only) + *result = git_write_tree(); else - result.tree = NULL; - debug("\t processing entries done\n"); + *result = NULL; } else { - result.clean = 1; + clean = 1; printf("merging of trees %s and %s resulted in %s\n", sha1_to_hex(head->object.sha1), sha1_to_hex(merge->object.sha1), - sha1_to_hex(result.tree->object.sha1)); + sha1_to_hex((*result)->object.sha1)); } - debug(" merge_trees done\n"); - return result; + return clean; } /* @@ -1421,76 +1261,75 @@ static struct merge_tree_result merge_trees(struct tree *head, * commit object and a flag indicating the cleaness of the merge. */ static -struct merge_result merge(struct commit *h1, +int merge(struct commit *h1, struct commit *h2, const char *branch1Name, const char *branch2Name, int callDepth /* =0 */, - struct commit *ancestor /* =None */) + struct commit *ancestor /* =None */, + struct commit **result) { - struct merge_result result = { NULL, 0 }; - const char *msg; - int msglen; struct commit_list *ca = NULL, *iter; struct commit *mergedCA; - struct merge_tree_result mtr; + struct tree *mrtree; + int clean; output("Merging:"); - msg = commit_title(h1, &msglen); - /* TODO: refactor. we always show the sha1 with the title */ - output("%s %.*s", commit_hex_sha1(h1), msglen, msg); - msg = commit_title(h2, &msglen); - output("%s %.*s", commit_hex_sha1(h2), msglen, msg); + output_commit_title(h1); + output_commit_title(h2); - if ( ancestor ) + if (ancestor) commit_list_insert(ancestor, &ca); else ca = get_merge_bases(h1, h2, 1); output("found %u common ancestor(s):", commit_list_count(ca)); - for (iter = ca; iter; iter = iter->next) { - msg = commit_title(iter->item, &msglen); - output("%s %.*s", commit_hex_sha1(iter->item), msglen, msg); - } + for (iter = ca; iter; iter = iter->next) + output_commit_title(iter->item); mergedCA = pop_commit(&ca); - /* TODO: what happens when merge with virtual commits fails? */ for (iter = ca; iter; iter = iter->next) { output_indent = callDepth + 1; - result = merge(mergedCA, iter->item, - "Temporary merge branch 1", - "Temporary merge branch 2", - callDepth + 1, - NULL); - mergedCA = result.commit; + /* + * When the merge fails, the result contains files + * with conflict markers. The cleanness flag is + * ignored, it was never acutally used, as result of + * merge_trees has always overwritten it: the commited + * "conflicts" were already resolved. + */ + merge(mergedCA, iter->item, + "Temporary merge branch 1", + "Temporary merge branch 2", + callDepth + 1, + NULL, + &mergedCA); output_indent = callDepth; - if ( !mergedCA ) + if (!mergedCA) die("merge returned no commit"); } - if ( callDepth == 0 ) { - setup_index(0); + if (callDepth == 0) { + setup_index(0 /* $GIT_DIR/index */); index_only = 0; } else { - setup_index(1); + setup_index(1 /* temporary index */); git_read_tree(h1->tree); index_only = 1; } - mtr = merge_trees(h1->tree, h2->tree, - mergedCA->tree, branch1Name, branch2Name); + clean = merge_trees(h1->tree, h2->tree, mergedCA->tree, + branch1Name, branch2Name, &mrtree); - if ( !ancestor && (mtr.clean || index_only) ) { - result.commit = make_virtual_commit(mtr.tree, "merged tree"); - commit_list_insert(h1, &result.commit->parents); - commit_list_insert(h2, &result.commit->parents->next); + if (!ancestor && (clean || index_only)) { + *result = make_virtual_commit(mrtree, "merged tree"); + commit_list_insert(h1, &(*result)->parents); + commit_list_insert(h2, &(*result)->parents->next); } else - result.commit = NULL; + *result = NULL; - result.clean = mtr.clean; - return result; + return clean; } static struct commit *get_ref(const char *ref) @@ -1512,6 +1351,9 @@ int main(int argc, char *argv[]) { static const char *bases[2]; static unsigned bases_count = 0; + int i, clean; + const char *branch1, *branch2; + struct commit *result, *h1, *h2; original_index_file = getenv("GIT_INDEX_FILE"); @@ -1523,7 +1365,6 @@ int main(int argc, char *argv[]) if (argc < 4) die("Usage: %s ... -- ...\n", argv[0]); - int i; for (i = 1; i < argc; ++i) { if (!strcmp(argv[i], "--")) break; @@ -1533,26 +1374,23 @@ int main(int argc, char *argv[]) if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); - const char *branch1, *branch2; - branch1 = argv[++i]; branch2 = argv[++i]; printf("Merging %s with %s\n", branch1, branch2); - struct merge_result result; - struct commit *h1 = get_ref(branch1); - struct commit *h2 = get_ref(branch2); + h1 = get_ref(branch1); + h2 = get_ref(branch2); if (bases_count == 1) { struct commit *ancestor = get_ref(bases[0]); - result = merge(h1, h2, branch1, branch2, 0, ancestor); + clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result); } else - result = merge(h1, h2, branch1, branch2, 0, NULL); + clean = merge(h1, h2, branch1, branch2, 0, NULL, &result); if (cache_dirty) flush_cache(); - return result.clean ? 0: 1; + return clean ? 0: 1; } /* -- cgit v0.10.2-6-g49f6 From 5a753613406286962df514db21094afc99525989 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 27 Jul 2006 19:12:29 +0200 Subject: merge-recur: Convert variable names to lower_case Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index d78f58d..5d20f9e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -122,8 +122,8 @@ struct stage_data unsigned processed:1; }; -static struct path_list currentFileSet = {NULL, 0, 0, 1}; -static struct path_list currentDirectorySet = {NULL, 0, 0, 1}; +static struct path_list current_file_set = {NULL, 0, 0, 1}; +static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int output_indent = 0; @@ -335,9 +335,9 @@ static int save_files_dirs(const unsigned char *sha1, newpath[baselen + len] = '\0'; if (S_ISDIR(mode)) - path_list_insert(newpath, ¤tDirectorySet); + path_list_insert(newpath, ¤t_directory_set); else - path_list_insert(newpath, ¤tFileSet); + path_list_insert(newpath, ¤t_file_set); free(newpath); return READ_TREE_RECURSIVE; @@ -351,7 +351,7 @@ static int get_files_dirs(struct tree *tree) debug(" get_files_dirs done (0)\n"); return 0; } - n = currentFileSet.nr + currentDirectorySet.nr; + n = current_file_set.nr + current_directory_set.nr; debug(" get_files_dirs done (%d)\n", n); return n; } @@ -378,7 +378,7 @@ static struct stage_data *insert_stage_data(const char *path, } /* - * Create a dictionary mapping file names to CacheEntry objects. The + * Create a dictionary mapping file names to stage_data objects. The * dictionary contains one entry for every path with a non-zero stage entry. */ static struct path_list *get_unmerged(void) @@ -420,15 +420,15 @@ struct rename }; /* - * Get information of all renames which occured between 'oTree' and - * 'tree'. We need the three trees in the merge ('oTree', 'aTree' and - * 'bTree') to be able to associate the correct cache entries with - * the rename information. 'tree' is always equal to either aTree or bTree. + * Get information of all renames which occured between 'o_tree' and + * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and + * 'b_tree') to be able to associate the correct cache entries with + * the rename information. 'tree' is always equal to either a_tree or b_tree. */ static struct path_list *get_renames(struct tree *tree, - struct tree *oTree, - struct tree *aTree, - struct tree *bTree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, struct path_list *entries) { int i; @@ -437,7 +437,7 @@ static struct path_list *get_renames(struct tree *tree, #ifdef DEBUG time_t t = time(0); - debug("getRenames ...\n"); + debug("get_renames ...\n"); #endif renames = xcalloc(1, sizeof(struct path_list)); @@ -447,7 +447,7 @@ static struct path_list *get_renames(struct tree *tree, opts.output_format = DIFF_FORMAT_NO_OUTPUT; if (diff_setup_done(&opts) < 0) die("diff setup failed"); - diff_tree_sha1(oTree->object.sha1, tree->object.sha1, "", &opts); + diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); diffcore_std(&opts); for (i = 0; i < diff_queued_diff.nr; ++i) { struct path_list_item *item; @@ -463,14 +463,14 @@ static struct path_list *get_renames(struct tree *tree, item = path_list_lookup(re->pair->one->path, entries); if (!item) re->src_entry = insert_stage_data(re->pair->one->path, - oTree, aTree, bTree, entries); + o_tree, a_tree, b_tree, entries); else re->src_entry = item->util; item = path_list_lookup(re->pair->two->path, entries); if (!item) re->dst_entry = insert_stage_data(re->pair->two->path, - oTree, aTree, bTree, entries); + o_tree, a_tree, b_tree, entries); else re->dst_entry = item->util; item = path_list_insert(pair->one->path, renames); @@ -480,7 +480,7 @@ static struct path_list *get_renames(struct tree *tree, diff_queued_diff.nr = 0; diff_flush(&opts); #ifdef DEBUG - debug(" getRenames done in %ld\n", time(0)-t); + debug(" get_renames done in %ld\n", time(0)-t); #endif return renames; } @@ -526,25 +526,23 @@ static int remove_path(const char *name) return ret; } -/* General TODO: unC99ify the code: no declaration after code */ -/* General TODO: no javaIfiCation: rename updateCache to update_cache */ /* * TODO: once we no longer call external programs, we'd probably be better off * not setting / getting the environment variable GIT_INDEX_FILE all the time. */ int remove_file(int clean, const char *path) { - int updateCache = index_only || clean; - int updateWd = !index_only; + int update_cache = index_only || clean; + int update_working_directory = !index_only; - if (updateCache) { + if (update_cache) { if (!cache_dirty) read_cache_from(getenv("GIT_INDEX_FILE")); cache_dirty++; if (remove_file_from_cache(path)) return -1; } - if (updateWd) + if (update_working_directory) { unlink(path); if (errno != ENOENT || errno != EISDIR) @@ -566,12 +564,12 @@ static char *unique_path(const char *path, const char *branch) for (; *p; ++p) if ('/' == *p) *p = '_'; - while (path_list_has_path(¤tFileSet, newpath) || - path_list_has_path(¤tDirectorySet, newpath) || + while (path_list_has_path(¤t_file_set, newpath) || + path_list_has_path(¤t_directory_set, newpath) || lstat(newpath, &st) == 0) sprintf(p, "_%d", suffix++); - path_list_insert(newpath, ¤tFileSet); + path_list_insert(newpath, ¤t_file_set); return newpath; } @@ -784,22 +782,22 @@ static void conflict_rename_rename(struct rename *ren1, int delp = 0; const char *ren1_dst = ren1->pair->two->path; const char *ren2_dst = ren2->pair->two->path; - const char *dstName1 = ren1_dst; - const char *dstName2 = ren2_dst; - if (path_list_has_path(¤tDirectorySet, ren1_dst)) { - dstName1 = del[delp++] = unique_path(ren1_dst, branch1); + const char *dst_name1 = ren1_dst; + const char *dst_name2 = ren2_dst; + if (path_list_has_path(¤t_directory_set, ren1_dst)) { + dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); output("%s is a directory in %s adding as %s instead", - ren1_dst, branch2, dstName1); + ren1_dst, branch2, dst_name1); remove_file(0, ren1_dst); } - if (path_list_has_path(¤tDirectorySet, ren2_dst)) { - dstName2 = del[delp++] = unique_path(ren2_dst, branch2); + if (path_list_has_path(¤t_directory_set, ren2_dst)) { + dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); output("%s is a directory in %s adding as %s instead", - ren2_dst, branch1, dstName2); + ren2_dst, branch1, dst_name2); remove_file(0, ren2_dst); } - update_stages(dstName1, NULL, ren1->pair->two, NULL, 1); - update_stages(dstName2, NULL, NULL, ren2->pair->two, 1); + update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); + update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); while (delp--) free(del[delp]); } @@ -807,11 +805,11 @@ static void conflict_rename_rename(struct rename *ren1, static void conflict_rename_dir(struct rename *ren1, const char *branch1) { - char *newPath = unique_path(ren1->pair->two->path, branch1); - output("Renaming %s to %s instead", ren1->pair->one->path, newPath); + char *new_path = unique_path(ren1->pair->two->path, branch1); + output("Renaming %s to %s instead", ren1->pair->one->path, new_path); remove_file(0, ren1->pair->two->path); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, newPath); - free(newPath); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); + free(new_path); } static void conflict_rename_rename_2(struct rename *ren1, @@ -819,74 +817,74 @@ static void conflict_rename_rename_2(struct rename *ren1, struct rename *ren2, const char *branch2) { - char *newPath1 = unique_path(ren1->pair->two->path, branch1); - char *newPath2 = unique_path(ren2->pair->two->path, branch2); + char *new_path1 = unique_path(ren1->pair->two->path, branch1); + char *new_path2 = unique_path(ren2->pair->two->path, branch2); output("Renaming %s to %s and %s to %s instead", - ren1->pair->one->path, newPath1, - ren2->pair->one->path, newPath2); + ren1->pair->one->path, new_path1, + ren2->pair->one->path, new_path2); remove_file(0, ren1->pair->two->path); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, newPath1); - update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, newPath2); - free(newPath2); - free(newPath1); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); + update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); + free(new_path2); + free(new_path1); } /* General TODO: get rid of all the debug messages */ -static int process_renames(struct path_list *renamesA, - struct path_list *renamesB, - const char *branchNameA, - const char *branchNameB) +static int process_renames(struct path_list *a_renames, + struct path_list *b_renames, + const char *a_branch, + const char *b_branch) { - int cleanMerge = 1, i, j; - struct path_list byDstA = {NULL, 0, 0, 0}, byDstB = {NULL, 0, 0, 0}; + int clean_merge = 1, i, j; + struct path_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; const struct rename *sre; - for (i = 0; i < renamesA->nr; i++) { - sre = renamesA->items[i].util; - path_list_insert(sre->pair->two->path, &byDstA)->util + for (i = 0; i < a_renames->nr; i++) { + sre = a_renames->items[i].util; + path_list_insert(sre->pair->two->path, &a_by_dst)->util = sre->dst_entry; } - for (i = 0; i < renamesB->nr; i++) { - sre = renamesB->items[i].util; - path_list_insert(sre->pair->two->path, &byDstB)->util + for (i = 0; i < b_renames->nr; i++) { + sre = b_renames->items[i].util; + path_list_insert(sre->pair->two->path, &b_by_dst)->util = sre->dst_entry; } - for (i = 0, j = 0; i < renamesA->nr || j < renamesB->nr;) { + for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { int compare; char *src; struct path_list *renames1, *renames2, *renames2Dst; struct rename *ren1 = NULL, *ren2 = NULL; - const char *branchName1, *branchName2; + const char *branch1, *branch2; const char *ren1_src, *ren1_dst; - if (i >= renamesA->nr) { + if (i >= a_renames->nr) { compare = 1; - ren2 = renamesB->items[j++].util; - } else if (j >= renamesB->nr) { + ren2 = b_renames->items[j++].util; + } else if (j >= b_renames->nr) { compare = -1; - ren1 = renamesA->items[i++].util; + ren1 = a_renames->items[i++].util; } else { - compare = strcmp(renamesA->items[i].path, - renamesB->items[j].path); - ren1 = renamesA->items[i++].util; - ren2 = renamesB->items[j++].util; + compare = strcmp(a_renames->items[i].path, + b_renames->items[j].path); + ren1 = a_renames->items[i++].util; + ren2 = b_renames->items[j++].util; } /* TODO: refactor, so that 1/2 are not needed */ if (ren1) { - renames1 = renamesA; - renames2 = renamesB; - renames2Dst = &byDstB; - branchName1 = branchNameA; - branchName2 = branchNameB; + renames1 = a_renames; + renames2 = b_renames; + renames2Dst = &b_by_dst; + branch1 = a_branch; + branch2 = b_branch; } else { struct rename *tmp; - renames1 = renamesB; - renames2 = renamesA; - renames2Dst = &byDstA; - branchName1 = branchNameB; - branchName2 = branchNameA; + renames1 = b_renames; + renames2 = a_renames; + renames2Dst = &a_by_dst; + branch1 = b_branch; + branch2 = a_branch; tmp = ren2; ren2 = ren1; ren1 = tmp; @@ -912,21 +910,21 @@ static int process_renames(struct path_list *renamesA, ren2->dst_entry->processed = 1; ren2->processed = 1; if (strcmp(ren1_dst, ren2_dst) != 0) { - cleanMerge = 0; + clean_merge = 0; output("CONFLICT (rename/rename): " "Rename %s->%s in branch %s " "rename %s->%s in %s", - src, ren1_dst, branchName1, - src, ren2_dst, branchName2); - conflict_rename_rename(ren1, branchName1, ren2, branchName2); + src, ren1_dst, branch1, + src, ren2_dst, branch2); + conflict_rename_rename(ren1, branch1, ren2, branch2); } else { - remove_file(1, ren1_src); struct merge_file_info mfi; + remove_file(1, ren1_src); mfi = merge_file(ren1->pair->one, ren1->pair->two, ren2->pair->two, - branchName1, - branchName2); + branch1, + branch2); if (mfi.merge || !mfi.clean) output("Renaming %s->%s", src, ren1_dst); @@ -936,7 +934,7 @@ static int process_renames(struct path_list *renamesA, if (!mfi.clean) { output("CONFLICT (content): merge conflict in %s", ren1_dst); - cleanMerge = 0; + clean_merge = 0; if (!index_only) update_stages(ren1_dst, @@ -952,7 +950,7 @@ static int process_renames(struct path_list *renamesA, struct path_list_item *item; /* we only use sha1 and mode of these */ struct diff_filespec src_other, dst_other; - int tryMerge, stage = renamesA == renames1 ? 3: 2; + int try_merge, stage = a_renames == renames1 ? 3: 2; remove_file(1, ren1_src); @@ -963,52 +961,52 @@ static int process_renames(struct path_list *renamesA, ren1->dst_entry->stages[stage].sha, 20); dst_other.mode = ren1->dst_entry->stages[stage].mode; - tryMerge = 0; + try_merge = 0; - if (path_list_has_path(¤tDirectorySet, ren1_dst)) { - cleanMerge = 0; + if (path_list_has_path(¤t_directory_set, ren1_dst)) { + clean_merge = 0; output("CONFLICT (rename/directory): Rename %s->%s in %s " " directory %s added in %s", - ren1_src, ren1_dst, branchName1, - ren1_dst, branchName2); - conflict_rename_dir(ren1, branchName1); + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + conflict_rename_dir(ren1, branch1); } else if (sha_eq(src_other.sha1, null_sha1)) { - cleanMerge = 0; + clean_merge = 0; output("CONFLICT (rename/delete): Rename %s->%s in %s " "and deleted in %s", - ren1_src, ren1_dst, branchName1, - branchName2); + ren1_src, ren1_dst, branch1, + branch2); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); } else if (!sha_eq(dst_other.sha1, null_sha1)) { - const char *newPath; - cleanMerge = 0; - tryMerge = 1; + const char *new_path; + clean_merge = 0; + try_merge = 1; output("CONFLICT (rename/add): Rename %s->%s in %s. " "%s added in %s", - ren1_src, ren1_dst, branchName1, - ren1_dst, branchName2); - newPath = unique_path(ren1_dst, branchName2); - output("Adding as %s instead", newPath); - update_file(0, dst_other.sha1, dst_other.mode, newPath); + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + new_path = unique_path(ren1_dst, branch2); + output("Adding as %s instead", new_path); + update_file(0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; - cleanMerge = 0; + clean_merge = 0; ren2->processed = 1; output("CONFLICT (rename/rename): Rename %s->%s in %s. " "Rename %s->%s in %s", - ren1_src, ren1_dst, branchName1, - ren2->pair->one->path, ren2->pair->two->path, branchName2); - conflict_rename_rename_2(ren1, branchName1, ren2, branchName2); + ren1_src, ren1_dst, branch1, + ren2->pair->one->path, ren2->pair->two->path, branch2); + conflict_rename_rename_2(ren1, branch1, ren2, branch2); } else - tryMerge = 1; + try_merge = 1; - if (tryMerge) { + if (try_merge) { struct diff_filespec *o, *a, *b; struct merge_file_info mfi; src_other.path = (char *)ren1_src; o = ren1->pair->one; - if (renamesA == renames1) { + if (a_renames == renames1) { a = ren1->pair->two; b = &src_other; } else { @@ -1016,7 +1014,7 @@ static int process_renames(struct path_list *renamesA, a = &src_other; } mfi = merge_file(o, a, b, - branchNameA, branchNameB); + a_branch, b_branch); if (mfi.merge || !mfi.clean) output("Renaming %s => %s", ren1_src, ren1_dst); @@ -1025,7 +1023,7 @@ static int process_renames(struct path_list *renamesA, if (!mfi.clean) { output("CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); - cleanMerge = 0; + clean_merge = 0; if (!index_only) update_stages(ren1_dst, @@ -1035,12 +1033,12 @@ static int process_renames(struct path_list *renamesA, } } } - path_list_clear(&byDstA, 0); - path_list_clear(&byDstB, 0); + path_list_clear(&a_by_dst, 0); + path_list_clear(&b_by_dst, 0); if (cache_dirty) flush_cache(); - return cleanMerge; + return clean_merge; } static unsigned char *has_sha(const unsigned char *sha) @@ -1057,116 +1055,116 @@ static int process_entry(const char *path, struct stage_data *entry, printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); print_index_entry("\tpath: ", entry); */ - int cleanMerge = 1; - unsigned char *oSha = has_sha(entry->stages[1].sha); - unsigned char *aSha = has_sha(entry->stages[2].sha); - unsigned char *bSha = has_sha(entry->stages[3].sha); - unsigned oMode = entry->stages[1].mode; - unsigned aMode = entry->stages[2].mode; - unsigned bMode = entry->stages[3].mode; - - if (oSha && (!aSha || !bSha)) { + int clean_merge = 1; + unsigned char *o_sha = has_sha(entry->stages[1].sha); + unsigned char *a_sha = has_sha(entry->stages[2].sha); + unsigned char *b_sha = has_sha(entry->stages[3].sha); + unsigned o_mode = entry->stages[1].mode; + unsigned a_mode = entry->stages[2].mode; + unsigned b_mode = entry->stages[3].mode; + + if (o_sha && (!a_sha || !b_sha)) { /* Case A: Deleted in one */ - if ((!aSha && !bSha) || - (sha_eq(aSha, oSha) && !bSha) || - (!aSha && sha_eq(bSha, oSha))) { + if ((!a_sha && !b_sha) || + (sha_eq(a_sha, o_sha) && !b_sha) || + (!a_sha && sha_eq(b_sha, o_sha))) { /* Deleted in both or deleted in one and * unchanged in the other */ - if (aSha) + if (a_sha) output("Removing %s", path); remove_file(1, path); } else { /* Deleted in one and changed in the other */ - cleanMerge = 0; - if (!aSha) { + clean_merge = 0; + if (!a_sha) { output("CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", path, branch1Name, branch2Name, branch2Name, path); - update_file(0, bSha, bMode, path); + update_file(0, b_sha, b_mode, path); } else { output("CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", path, branch2Name, branch1Name, branch1Name, path); - update_file(0, aSha, aMode, path); + update_file(0, a_sha, a_mode, path); } } - } else if ((!oSha && aSha && !bSha) || - (!oSha && !aSha && bSha)) { + } else if ((!o_sha && a_sha && !b_sha) || + (!o_sha && !a_sha && b_sha)) { /* Case B: Added in one. */ - const char *addBranch; - const char *otherBranch; + const char *add_branch; + const char *other_branch; unsigned mode; const unsigned char *sha; const char *conf; - if (aSha) { - addBranch = branch1Name; - otherBranch = branch2Name; - mode = aMode; - sha = aSha; + if (a_sha) { + add_branch = branch1Name; + other_branch = branch2Name; + mode = a_mode; + sha = a_sha; conf = "file/directory"; } else { - addBranch = branch2Name; - otherBranch = branch1Name; - mode = bMode; - sha = bSha; + add_branch = branch2Name; + other_branch = branch1Name; + mode = b_mode; + sha = b_sha; conf = "directory/file"; } - if (path_list_has_path(¤tDirectorySet, path)) { - const char *newPath = unique_path(path, addBranch); - cleanMerge = 0; + if (path_list_has_path(¤t_directory_set, path)) { + const char *new_path = unique_path(path, add_branch); + clean_merge = 0; output("CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", - conf, path, otherBranch, path, newPath); + conf, path, other_branch, path, new_path); remove_file(0, path); - update_file(0, sha, mode, newPath); + update_file(0, sha, mode, new_path); } else { output("Adding %s", path); update_file(1, sha, mode, path); } - } else if (!oSha && aSha && bSha) { + } else if (!o_sha && a_sha && b_sha) { /* Case C: Added in both (check for same permissions). */ - if (sha_eq(aSha, bSha)) { - if (aMode != bMode) { - cleanMerge = 0; + if (sha_eq(a_sha, b_sha)) { + if (a_mode != b_mode) { + clean_merge = 0; output("CONFLICT: File %s added identically in both branches, " "but permissions conflict %06o->%06o", - path, aMode, bMode); - output("CONFLICT: adding with permission: %06o", aMode); - update_file(0, aSha, aMode, path); + path, a_mode, b_mode); + output("CONFLICT: adding with permission: %06o", a_mode); + update_file(0, a_sha, a_mode, path); } else { /* This case is handled by git-read-tree */ assert(0 && "This case must be handled by git-read-tree"); } } else { - const char *newPath1, *newPath2; - cleanMerge = 0; - newPath1 = unique_path(path, branch1Name); - newPath2 = unique_path(path, branch2Name); + const char *new_path1, *new_path2; + clean_merge = 0; + new_path1 = unique_path(path, branch1Name); + new_path2 = unique_path(path, branch2Name); output("CONFLICT (add/add): File %s added non-identically " "in both branches. Adding as %s and %s instead.", - path, newPath1, newPath2); + path, new_path1, new_path2); remove_file(0, path); - update_file(0, aSha, aMode, newPath1); - update_file(0, bSha, bMode, newPath2); + update_file(0, a_sha, a_mode, new_path1); + update_file(0, b_sha, b_mode, new_path2); } - } else if (oSha && aSha && bSha) { + } else if (o_sha && a_sha && b_sha) { /* case D: Modified in both, but differently. */ struct merge_file_info mfi; struct diff_filespec o, a, b; output("Auto-merging %s", path); o.path = a.path = b.path = (char *)path; - memcpy(o.sha1, oSha, 20); - o.mode = oMode; - memcpy(a.sha1, aSha, 20); - a.mode = aMode; - memcpy(b.sha1, bSha, 20); - b.mode = bMode; + memcpy(o.sha1, o_sha, 20); + o.mode = o_mode; + memcpy(a.sha1, a_sha, 20); + a.mode = a_mode; + memcpy(b.sha1, b_sha, 20); + b.mode = b_mode; mfi = merge_file(&o, &a, &b, branch1Name, branch2Name); @@ -1174,14 +1172,14 @@ static int process_entry(const char *path, struct stage_data *entry, if (mfi.clean) update_file(1, mfi.sha, mfi.mode, path); else { - cleanMerge = 0; + clean_merge = 0; output("CONFLICT (content): Merge conflict in %s", path); if (index_only) update_file(0, mfi.sha, mfi.mode, path); else update_file_flags(mfi.sha, mfi.mode, path, - 0 /* updateCache */, 1 /* updateWd */); + 0 /* update_cache */, 1 /* update_working_directory */); } } else die("Fatal merge failure, shouldn't happen."); @@ -1189,7 +1187,7 @@ static int process_entry(const char *path, struct stage_data *entry, if (cache_dirty) flush_cache(); - return cleanMerge; + return clean_merge; } static int merge_trees(struct tree *head, @@ -1218,8 +1216,8 @@ static int merge_trees(struct tree *head, if (!*result) { struct path_list *entries, *re_head, *re_merge; int i; - path_list_clear(¤tFileSet, 1); - path_list_clear(¤tDirectorySet, 1); + path_list_clear(¤t_file_set, 1); + path_list_clear(¤t_directory_set, 1); get_files_dirs(head); get_files_dirs(merge); @@ -1265,12 +1263,12 @@ int merge(struct commit *h1, struct commit *h2, const char *branch1Name, const char *branch2Name, - int callDepth /* =0 */, + int call_depth /* =0 */, struct commit *ancestor /* =None */, struct commit **result) { struct commit_list *ca = NULL, *iter; - struct commit *mergedCA; + struct commit *merged_common_ancestors; struct tree *mrtree; int clean; @@ -1287,10 +1285,10 @@ int merge(struct commit *h1, for (iter = ca; iter; iter = iter->next) output_commit_title(iter->item); - mergedCA = pop_commit(&ca); + merged_common_ancestors = pop_commit(&ca); for (iter = ca; iter; iter = iter->next) { - output_indent = callDepth + 1; + output_indent = call_depth + 1; /* * When the merge fails, the result contains files * with conflict markers. The cleanness flag is @@ -1298,19 +1296,19 @@ int merge(struct commit *h1, * merge_trees has always overwritten it: the commited * "conflicts" were already resolved. */ - merge(mergedCA, iter->item, + merge(merged_common_ancestors, iter->item, "Temporary merge branch 1", "Temporary merge branch 2", - callDepth + 1, + call_depth + 1, NULL, - &mergedCA); - output_indent = callDepth; + &merged_common_ancestors); + output_indent = call_depth; - if (!mergedCA) + if (!merged_common_ancestors) die("merge returned no commit"); } - if (callDepth == 0) { + if (call_depth == 0) { setup_index(0 /* $GIT_DIR/index */); index_only = 0; } else { @@ -1319,7 +1317,7 @@ int merge(struct commit *h1, index_only = 1; } - clean = merge_trees(h1->tree, h2->tree, mergedCA->tree, + clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, branch1Name, branch2Name, &mrtree); if (!ancestor && (clean || index_only)) { -- cgit v0.10.2-6-g49f6 From bd669986f7ecfb0215e5d5fa29ec8449a6225bc1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 27 Jul 2006 19:12:51 +0200 Subject: merge-recur: Get rid of debug code Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 5d20f9e..6f109f1 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -22,56 +22,6 @@ #include "path-list.h" -/*#define DEBUG*/ - -#ifdef DEBUG -#define debug(...) fprintf(stderr, __VA_ARGS__) -#else -#define debug(...) do { ; /* nothing */ } while (0) -#endif - -#ifdef DEBUG -#include "quote.h" -static void show_ce_entry(const char *tag, struct cache_entry *ce) -{ - if (tag && *tag && - (ce->ce_flags & htons(CE_VALID))) { - static char alttag[4]; - memcpy(alttag, tag, 3); - if (isalpha(tag[0])) - alttag[0] = tolower(tag[0]); - else if (tag[0] == '?') - alttag[0] = '!'; - else { - alttag[0] = 'v'; - alttag[1] = tag[0]; - alttag[2] = ' '; - alttag[3] = 0; - } - tag = alttag; - } - - fprintf(stderr,"%s%06o %s %d\t", - tag, - ntohl(ce->ce_mode), - sha1_to_hex(ce->sha1), - ce_stage(ce)); - write_name_quoted("", 0, ce->name, - '\n', stderr); - fputc('\n', stderr); -} - -static void ls_files(void) { - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - show_ce_entry("", ce); - } - fprintf(stderr, "---\n"); - if (0) ls_files(); /* avoid "unused" warning */ -} -#endif - /* * A virtual commit has * - (const char *)commit->util set to the name, and @@ -346,13 +296,9 @@ static int save_files_dirs(const unsigned char *sha1, static int get_files_dirs(struct tree *tree) { int n; - debug("get_files_dirs ...\n"); - if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0) { - debug(" get_files_dirs done (0)\n"); + if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0) return 0; - } n = current_file_set.nr + current_directory_set.nr; - debug(" get_files_dirs done (%d)\n", n); return n; } @@ -434,11 +380,6 @@ static struct path_list *get_renames(struct tree *tree, int i; struct path_list *renames; struct diff_options opts; -#ifdef DEBUG - time_t t = time(0); - - debug("get_renames ...\n"); -#endif renames = xcalloc(1, sizeof(struct path_list)); diff_setup(&opts); @@ -479,9 +420,6 @@ static struct path_list *get_renames(struct tree *tree, opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_queued_diff.nr = 0; diff_flush(&opts); -#ifdef DEBUG - debug(" get_renames done in %ld\n", time(0)-t); -#endif return renames; } @@ -829,7 +767,6 @@ static void conflict_rename_rename_2(struct rename *ren1, free(new_path1); } -/* General TODO: get rid of all the debug messages */ static int process_renames(struct path_list *a_renames, struct path_list *b_renames, const char *a_branch, -- cgit v0.10.2-6-g49f6 From 5d3afe05d9466a8472be315793dfc08594b1e9c4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 27 Jul 2006 19:13:08 +0200 Subject: merge-recur: Remove dead code Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 6f109f1..79c022e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -197,11 +197,6 @@ static int index_only = 0; */ static int git_read_tree(const struct tree *tree) { -#if 0 - fprintf(stderr, "GIT_INDEX_FILE='%s' git-read-tree %s\n", - getenv("GIT_INDEX_FILE"), - sha1_to_hex(tree->object.sha1)); -#endif int rc; const char *argv[] = { "git-read-tree", NULL, NULL, }; if (cache_dirty) @@ -219,14 +214,6 @@ static int git_merge_trees(const char *update_arg, struct tree *head, struct tree *merge) { -#if 0 - fprintf(stderr, "GIT_INDEX_FILE='%s' git-read-tree %s -m %s %s %s\n", - getenv("GIT_INDEX_FILE"), - update_arg, - sha1_to_hex(common->object.sha1), - sha1_to_hex(head->object.sha1), - sha1_to_hex(merge->object.sha1)); -#endif int rc; const char *argv[] = { "git-read-tree", NULL, "-m", NULL, NULL, NULL, @@ -247,10 +234,6 @@ static int git_merge_trees(const char *update_arg, */ static struct tree *git_write_tree(void) { -#if 0 - fprintf(stderr, "GIT_INDEX_FILE='%s' git-write-tree\n", - getenv("GIT_INDEX_FILE")); -#endif FILE *fp; int rc; char buf[41]; @@ -672,11 +655,6 @@ static struct merge_file_info merge_file(struct diff_filespec *o, argv[6] = lb = strdup(mkpath("%s/%s", branch2Name, b->path)); argv[4] = lo = strdup(mkpath("orig/%s", o->path)); -#if 0 - printf("%s %s %s %s %s %s %s %s %s %s\n", - argv[0], argv[1], argv[2], argv[3], argv[4], - argv[5], argv[6], argv[7], argv[8], argv[9]); -#endif code = run_command_v(10, argv); free(la); -- cgit v0.10.2-6-g49f6 From 7b3f5daabc9fcfffc632a6a963cf5466745b7dbd Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 27 Jul 2006 19:13:24 +0200 Subject: merge-recur: Fix compiler warning with -pedantic Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 79c022e..1a21ff3 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -642,7 +642,7 @@ static struct merge_file_info merge_file(struct diff_filespec *o, char src2[PATH_MAX]; const char *argv[] = { "merge", "-L", NULL, "-L", NULL, "-L", NULL, - src1, orig, src2, + NULL, NULL, NULL, NULL }; char *la, *lb, *lo; @@ -654,6 +654,9 @@ static struct merge_file_info merge_file(struct diff_filespec *o, argv[2] = la = strdup(mkpath("%s/%s", branch1Name, a->path)); argv[6] = lb = strdup(mkpath("%s/%s", branch2Name, b->path)); argv[4] = lo = strdup(mkpath("orig/%s", o->path)); + argv[7] = src1; + argv[8] = orig; + argv[9] = src2, code = run_command_v(10, argv); -- cgit v0.10.2-6-g49f6 From c1d20846f4a7112c44653dfe25411ccd19297790 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 27 Jul 2006 19:13:47 +0200 Subject: merge-recur: Cleanup last mixedCase variables... Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 1a21ff3..eda4fee 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -609,7 +609,7 @@ static char *git_unpack_file(const unsigned char *sha1, char *path) static struct merge_file_info merge_file(struct diff_filespec *o, struct diff_filespec *a, struct diff_filespec *b, - const char *branch1Name, const char *branch2Name) + const char *branch1, const char *branch2) { struct merge_file_info result; result.merge = 0; @@ -651,8 +651,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o, git_unpack_file(a->sha1, src1); git_unpack_file(b->sha1, src2); - argv[2] = la = strdup(mkpath("%s/%s", branch1Name, a->path)); - argv[6] = lb = strdup(mkpath("%s/%s", branch2Name, b->path)); + argv[2] = la = strdup(mkpath("%s/%s", branch1, a->path)); + argv[6] = lb = strdup(mkpath("%s/%s", branch2, b->path)); argv[4] = lo = strdup(mkpath("orig/%s", o->path)); argv[7] = src1; argv[8] = orig; @@ -966,8 +966,8 @@ static unsigned char *has_sha(const unsigned char *sha) /* Per entry merge function */ static int process_entry(const char *path, struct stage_data *entry, - const char *branch1Name, - const char *branch2Name) + const char *branch1, + const char *branch2) { /* printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); @@ -997,14 +997,14 @@ static int process_entry(const char *path, struct stage_data *entry, if (!a_sha) { output("CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", - path, branch1Name, - branch2Name, branch2Name, path); + path, branch1, + branch2, branch2, path); update_file(0, b_sha, b_mode, path); } else { output("CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", - path, branch2Name, - branch1Name, branch1Name, path); + path, branch2, + branch1, branch1, path); update_file(0, a_sha, a_mode, path); } } @@ -1019,14 +1019,14 @@ static int process_entry(const char *path, struct stage_data *entry, const char *conf; if (a_sha) { - add_branch = branch1Name; - other_branch = branch2Name; + add_branch = branch1; + other_branch = branch2; mode = a_mode; sha = a_sha; conf = "file/directory"; } else { - add_branch = branch2Name; - other_branch = branch1Name; + add_branch = branch2; + other_branch = branch1; mode = b_mode; sha = b_sha; conf = "directory/file"; @@ -1060,8 +1060,8 @@ static int process_entry(const char *path, struct stage_data *entry, } else { const char *new_path1, *new_path2; clean_merge = 0; - new_path1 = unique_path(path, branch1Name); - new_path2 = unique_path(path, branch2Name); + new_path1 = unique_path(path, branch1); + new_path2 = unique_path(path, branch2); output("CONFLICT (add/add): File %s added non-identically " "in both branches. Adding as %s and %s instead.", path, new_path1, new_path2); @@ -1085,7 +1085,7 @@ static int process_entry(const char *path, struct stage_data *entry, b.mode = b_mode; mfi = merge_file(&o, &a, &b, - branch1Name, branch2Name); + branch1, branch2); if (mfi.clean) update_file(1, mfi.sha, mfi.mode, path); @@ -1111,8 +1111,8 @@ static int process_entry(const char *path, struct stage_data *entry, static int merge_trees(struct tree *head, struct tree *merge, struct tree *common, - const char *branch1Name, - const char *branch2Name, + const char *branch1, + const char *branch2, struct tree **result) { int code, clean; @@ -1143,13 +1143,13 @@ static int merge_trees(struct tree *head, re_head = get_renames(head, common, head, merge, entries); re_merge = get_renames(merge, common, head, merge, entries); clean = process_renames(re_head, re_merge, - branch1Name, branch2Name); + branch1, branch2); for (i = 0; i < entries->nr; i++) { const char *path = entries->items[i].path; struct stage_data *e = entries->items[i].util; if (e->processed) continue; - if (!process_entry(path, e, branch1Name, branch2Name)) + if (!process_entry(path, e, branch1, branch2)) clean = 0; } @@ -1179,8 +1179,8 @@ static int merge_trees(struct tree *head, static int merge(struct commit *h1, struct commit *h2, - const char *branch1Name, - const char *branch2Name, + const char *branch1, + const char *branch2, int call_depth /* =0 */, struct commit *ancestor /* =None */, struct commit **result) @@ -1236,7 +1236,7 @@ int merge(struct commit *h1, } clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, - branch1Name, branch2Name, &mrtree); + branch1, branch2, &mrtree); if (!ancestor && (clean || index_only)) { *result = make_virtual_commit(mrtree, "merged tree"); -- cgit v0.10.2-6-g49f6 From 3058e9339f05379462b7e14d832d091cc3d9b2b9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 27 Jul 2006 19:14:17 +0200 Subject: merge-recur: Explain why sha_eq() and struct stage_data cannot go There were two TODOs to remove sha_eq() and to convert users of struct stage_data to active_cache users, but this is not possible. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index eda4fee..6a796f2 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -47,8 +47,8 @@ static struct commit *make_virtual_commit(struct tree *tree, const char *comment } /* - * TODO: we should not have to copy the SHA1s around, but rather reference - * them. That way, sha_eq() is just sha1 == sha2. + * Since we use get_tree_entry(), which does not put the read object into + * the object pool, we cannot rely on a == b. */ static int sha_eq(const unsigned char *a, const unsigned char *b) { @@ -58,9 +58,8 @@ static int sha_eq(const unsigned char *a, const unsigned char *b) } /* - * TODO: check if we can just reuse the active_cache structure: it is already - * sorted (by name, stage). - * Only problem: do not write it when flushing the cache. + * Since we want to write the index eventually, we cannot reuse the index + * for these (temporary) data. */ struct stage_data { -- cgit v0.10.2-6-g49f6 From a060b803b49c04cd6e3b0d859f131349dab6b26f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 27 Jul 2006 22:02:45 -0700 Subject: Makefile: git-merge-recur depends on xdiff libraries. Tighten dependencies to allow parallel build. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index a749aa4..12ffd45 100644 --- a/Makefile +++ b/Makefile @@ -617,7 +617,7 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) merge-recursive.o path-list.o: path-list.h -git-merge-recur$X: merge-recursive.o path-list.o $(LIB_FILE) +git-merge-recur$X: merge-recursive.o path-list.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) -- cgit v0.10.2-6-g49f6 From f59aac47f3839367d0da04019b0fc2bd61345225 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 30 Jul 2006 18:35:21 +0200 Subject: merge-recur: fix thinko in unique_path() This could result in a nasty infinite loop, or in bogus names (it used the strlen() of the newly allocated buffer instead of the original buffer). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 6a796f2..5375a1b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -477,9 +477,9 @@ static char *unique_path(const char *path, const char *branch) char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); int suffix = 0; struct stat st; - char *p = newpath + strlen(newpath); + char *p = newpath + strlen(path); strcpy(newpath, path); - strcat(newpath, "~"); + *(p++) = '~'; strcpy(p, branch); for (; *p; ++p) if ('/' == *p) -- cgit v0.10.2-6-g49f6 From 7a85b848ad77c72c539c5887ead99a89140541da Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 30 Jul 2006 20:27:10 +0200 Subject: merge-recur: use the unpack_trees() interface instead of exec()ing read-tree Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 5375a1b..10bce70 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -19,7 +19,7 @@ #include "diffcore.h" #include "run-command.h" #include "tag.h" - +#include "unpack-trees.h" #include "path-list.h" /* @@ -124,7 +124,7 @@ static int flush_cache(void) struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int fd = hold_lock_file_for_update(lock, getenv("GIT_INDEX_FILE")); if (fd < 0) - die("could not lock %s", temporary_index_file); + die("could not lock %s", lock->filename); if (write_cache(fd, active_cache, active_nr) || close(fd) || commit_lock_file(lock)) die ("unable to write %s", getenv("GIT_INDEX_FILE")); @@ -191,41 +191,59 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, */ static int index_only = 0; -/* - * TODO: this can be streamlined by refactoring builtin-read-tree.c - */ -static int git_read_tree(const struct tree *tree) +static int git_read_tree(struct tree *tree) { int rc; - const char *argv[] = { "git-read-tree", NULL, NULL, }; + struct object_list *trees = NULL; + struct unpack_trees_options opts; + if (cache_dirty) die("read-tree with dirty cache"); - argv[1] = sha1_to_hex(tree->object.sha1); - rc = run_command_v(2, argv); - return rc < 0 ? -1: rc; + + memset(&opts, 0, sizeof(opts)); + object_list_append(&tree->object, &trees); + rc = unpack_trees(trees, &opts); + cache_tree_free(&active_cache_tree); + + if (rc == 0) + cache_dirty = 1; + + return rc; } -/* - * TODO: this can be streamlined by refactoring builtin-read-tree.c - */ -static int git_merge_trees(const char *update_arg, +static int git_merge_trees(int index_only, struct tree *common, struct tree *head, struct tree *merge) { int rc; - const char *argv[] = { - "git-read-tree", NULL, "-m", NULL, NULL, NULL, - NULL, - }; - if (cache_dirty) - flush_cache(); - argv[1] = update_arg; - argv[3] = sha1_to_hex(common->object.sha1); - argv[4] = sha1_to_hex(head->object.sha1); - argv[5] = sha1_to_hex(merge->object.sha1); - rc = run_command_v(6, argv); - return rc < 0 ? -1: rc; + struct object_list *trees = NULL; + struct unpack_trees_options opts; + + if (!cache_dirty) { + read_cache_from(getenv("GIT_INDEX_FILE")); + cache_dirty = 1; + } + + memset(&opts, 0, sizeof(opts)); + if (index_only) + opts.index_only = 1; + else + opts.update = 1; + opts.merge = 1; + opts.head_idx = 2; + opts.fn = threeway_merge; + + object_list_append(&common->object, &trees); + object_list_append(&head->object, &trees); + object_list_append(&merge->object, &trees); + + rc = unpack_trees(trees, &opts); + cache_tree_free(&active_cache_tree); + + cache_dirty = 1; + + return rc; } /* @@ -239,8 +257,14 @@ static struct tree *git_write_tree(void) unsigned char sha1[20]; int ch; unsigned i = 0; - if (cache_dirty) + if (cache_dirty) { + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) + return NULL; + } flush_cache(); + } fp = popen("git-write-tree 2>/dev/null", "r"); while ((ch = fgetc(fp)) != EOF) if (i < sizeof(buf)-1 && ch >= '0' && ch <= 'f') @@ -1121,7 +1145,7 @@ static int merge_trees(struct tree *head, return 1; } - code = git_merge_trees(index_only ? "-i": "-u", common, head, merge); + code = git_merge_trees(index_only, common, head, merge); if (code != 0) die("merging of trees %s and %s failed", -- cgit v0.10.2-6-g49f6 From c1f3089e4bb5a3e8e8f92915800f2d5a2d4dd246 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 31 Jul 2006 12:42:35 +0200 Subject: merge-recur: virtual commits shall never be parsed It would not make sense to parse a virtual commit, therefore set the "parsed" flag to 1. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 10bce70..74a329f 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -43,6 +43,8 @@ static struct commit *make_virtual_commit(struct tree *tree, const char *comment commit->tree = tree; commit->util = (void*)comment; *(int*)commit->object.sha1 = virtual_id++; + /* avoid warnings */ + commit->object.parsed = 1; return commit; } -- cgit v0.10.2-6-g49f6 From cec7bece83a42918457819b526c49c7d55f795c5 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Wed, 2 Aug 2006 09:38:10 +1000 Subject: gitk: Recompute ancestor/descendent heads/tags when rereading refs We weren't updating the desc_heads, desc_tags and anc_tags arrays when rereading the set of heads/tags/etc. The tricky thing to get right here is restarting the computation correctly when we are only half-way through it. Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index 7b86c19..1f0a2e6 100755 --- a/gitk +++ b/gitk @@ -4932,7 +4932,7 @@ proc domktag {} { proc redrawtags {id} { global canv linehtag commitrow idpos selectedline curview - global mainfont + global mainfont canvxmax if {![info exists commitrow($curview,$id)]} return drawcmitrow $commitrow($curview,$id) @@ -5020,8 +5020,9 @@ proc wrcomcan {} { # Stuff for finding nearby tags proc getallcommits {} { - global allcstart allcommits allcfd + global allcstart allcommits allcfd allids + set allids {} set fd [open [concat | git rev-list --all --topo-order --parents] r] set allcfd $fd fconfigure $fd -blocking 0 @@ -5105,10 +5106,52 @@ proc combine_atags {l1 l2} { return $res } +proc forward_pass {id children} { + global idtags desc_tags idheads desc_heads alldtags tagisdesc + + set dtags {} + set dheads {} + foreach child $children { + if {[info exists idtags($child)]} { + set ctags [list $child] + } else { + set ctags $desc_tags($child) + } + if {$dtags eq {}} { + set dtags $ctags + } elseif {$ctags ne $dtags} { + set dtags [combine_dtags $dtags $ctags] + } + set cheads $desc_heads($child) + if {$dheads eq {}} { + set dheads $cheads + } elseif {$cheads ne $dheads} { + set dheads [lsort -unique [concat $dheads $cheads]] + } + } + set desc_tags($id) $dtags + if {[info exists idtags($id)]} { + set adt $dtags + foreach tag $dtags { + set adt [concat $adt $alldtags($tag)] + } + set adt [lsort -unique $adt] + set alldtags($id) $adt + foreach tag $adt { + set tagisdesc($id,$tag) -1 + set tagisdesc($tag,$id) 1 + } + } + if {[info exists idheads($id)]} { + lappend dheads $id + } + set desc_heads($id) $dheads +} + proc getallclines {fd} { global allparents allchildren allcommits allcstart - global desc_tags anc_tags idtags alldtags tagisdesc allids - global desc_heads idheads + global desc_tags anc_tags idtags tagisdesc allids + global desc_heads idheads travindex while {[gets $fd line] >= 0} { set id [lindex $line 0] @@ -5123,43 +5166,7 @@ proc getallclines {fd} { } # compute nearest tagged descendents as we go # also compute descendent heads - set dtags {} - set dheads {} - foreach child $allchildren($id) { - if {[info exists idtags($child)]} { - set ctags [list $child] - } else { - set ctags $desc_tags($child) - } - if {$dtags eq {}} { - set dtags $ctags - } elseif {$ctags ne $dtags} { - set dtags [combine_dtags $dtags $ctags] - } - set cheads $desc_heads($child) - if {$dheads eq {}} { - set dheads $cheads - } elseif {$cheads ne $dheads} { - set dheads [lsort -unique [concat $dheads $cheads]] - } - } - set desc_tags($id) $dtags - if {[info exists idtags($id)]} { - set adt $dtags - foreach tag $dtags { - set adt [concat $adt $alldtags($tag)] - } - set adt [lsort -unique $adt] - set alldtags($id) $adt - foreach tag $adt { - set tagisdesc($id,$tag) -1 - set tagisdesc($tag,$id) 1 - } - } - if {[info exists idheads($id)]} { - lappend dheads $id - } - set desc_heads($id) $dheads + forward_pass $id $allchildren($id) if {[clock clicks -milliseconds] - $allcstart >= 50} { fileevent $fd readable {} after idle restartgetall $fd @@ -5167,7 +5174,9 @@ proc getallclines {fd} { } } if {[eof $fd]} { - after idle restartatags [llength $allids] + set travindex [llength $allids] + set allcommits "traversing" + after idle restartatags if {[catch {close $fd} err]} { error_popup "Error reading full commit graph: $err.\n\ Results may be incomplete." @@ -5176,10 +5185,11 @@ proc getallclines {fd} { } # walk backward through the tree and compute nearest tagged ancestors -proc restartatags {i} { - global allids allparents idtags anc_tags t0 +proc restartatags {} { + global allids allparents idtags anc_tags travindex set t0 [clock clicks -milliseconds] + set i $travindex while {[incr i -1] >= 0} { set id [lindex $allids $i] set atags {} @@ -5197,17 +5207,41 @@ proc restartatags {i} { } set anc_tags($id) $atags if {[clock clicks -milliseconds] - $t0 >= 50} { - after idle restartatags $i + set travindex $i + after idle restartatags return } } set allcommits "done" + set travindex 0 notbusy allcommits dispneartags } +proc changedrefs {} { + global desc_heads desc_tags anc_tags allcommits allids + global allchildren allparents idtags travindex + + if {![info exists allcommits]} return + catch {unset desc_heads} + catch {unset desc_tags} + catch {unset anc_tags} + catch {unset alldtags} + catch {unset tagisdesc} + foreach id $allids { + forward_pass $id $allchildren($id) + } + if {$allcommits ne "reading"} { + set travindex [llength $allids] + if {$allcommits ne "traversing"} { + set allcommits "traversing" + after idle restartatags + } + } +} + proc rereadrefs {} { - global idtags idheads idotherrefs + global idtags idheads idotherrefs mainhead set refids [concat [array names idtags] \ [array names idheads] [array names idotherrefs]] @@ -5216,12 +5250,16 @@ proc rereadrefs {} { set ref($id) [listrefs $id] } } + set oldmainhead $mainhead readrefs + changedrefs set refids [lsort -unique [concat $refids [array names idtags] \ [array names idheads] [array names idotherrefs]]] foreach id $refids { set v [listrefs $id] - if {![info exists ref($id)] || $ref($id) != $v} { + if {![info exists ref($id)] || $ref($id) != $v || + ($id eq $oldmainhead && $id ne $mainhead) || + ($id eq $mainhead && $id ne $oldmainhead)} { redrawtags $id } } -- cgit v0.10.2-6-g49f6 From d6ac1a86e932fdb48705b9b1b6e2849aaea9ad24 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Wed, 2 Aug 2006 09:41:04 +1000 Subject: gitk: Add a row context-menu item for creating a new branch Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index 1f0a2e6..61106f2 100755 --- a/gitk +++ b/gitk @@ -711,6 +711,7 @@ proc makewindow {} { $rowctxmenu add command -label "Make patch" -command mkpatch $rowctxmenu add command -label "Create tag" -command mktag $rowctxmenu add command -label "Write commit to file" -command writecommit + $rowctxmenu add command -label "Create new branch" -command mkbranch } # mouse-2 makes all windows scan vertically, but only the one @@ -5018,6 +5019,61 @@ proc wrcomcan {} { unset wrcomtop } +proc mkbranch {} { + global rowmenuid mkbrtop + + set top .makebranch + catch {destroy $top} + toplevel $top + label $top.title -text "Create new branch" + grid $top.title - -pady 10 + label $top.id -text "ID:" + entry $top.sha1 -width 40 -relief flat + $top.sha1 insert 0 $rowmenuid + $top.sha1 conf -state readonly + grid $top.id $top.sha1 -sticky w + label $top.nlab -text "Name:" + entry $top.name -width 40 + grid $top.nlab $top.name -sticky w + frame $top.buts + button $top.buts.go -text "Create" -command [list mkbrgo $top] + button $top.buts.can -text "Cancel" -command "catch {destroy $top}" + grid $top.buts.go $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.name +} + +proc mkbrgo {top} { + global headids idheads + + set name [$top.name get] + set id [$top.sha1 get] + if {$name eq {}} { + error_popup "Please specify a name for the new branch" + return + } + catch {destroy $top} + nowbusy newbranch + update + if {[catch { + exec git branch $name $id + } err]} { + notbusy newbranch + error_popup $err + } else { + set headids($name) $id + if {![info exists idheads($id)]} { + addedhead $id + } + lappend idheads($id) $name + # XXX should update list of heads displayed for selected commit + notbusy newbranch + redrawtags $id + } +} + # Stuff for finding nearby tags proc getallcommits {} { global allcstart allcommits allcfd allids @@ -5218,6 +5274,30 @@ proc restartatags {} { dispneartags } +# update the desc_heads array for a new head just added +proc addedhead {hid} { + global desc_heads allparents + + set todo [list $hid] + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_heads($do)] || + [lsearch -exact $desc_heads($do) $hid] >= 0} continue + set oldheads $desc_heads($do) + lappend desc_heads($do) $hid + set heads $desc_heads($do) + while {1} { + set p $allparents($do) + if {[llength $p] != 1 || ![info exists desc_heads($p)] || + $desc_heads($p) ne $oldheads} break + set do $p + set desc_heads($do) $heads + } + set todo [concat $todo $p] + } +} + proc changedrefs {} { global desc_heads desc_tags anc_tags allcommits allids global allchildren allparents idtags travindex -- cgit v0.10.2-6-g49f6 From 10299152ca10107a4570764286e129572ed2f3c3 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Wed, 2 Aug 2006 09:52:01 +1000 Subject: gitk: Add a context menu for heads This menu allows you to check out a branch and to delete a branch. If you ask to delete a branch that has commits that aren't on any other branch, gitk will prompt for confirmation before doing the deletion. Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index 61106f2..fc65cc0 100755 --- a/gitk +++ b/gitk @@ -384,6 +384,23 @@ proc error_popup msg { show_error $w $w $msg } +proc confirm_popup msg { + global confirm_ok + set confirm_ok 0 + set w .confirm + toplevel $w + wm transient $w . + message $w.m -text $msg -justify center -aspect 400 + pack $w.m -side top -fill x -padx 20 -pady 20 + button $w.ok -text OK -command "set confirm_ok 1; destroy $w" + pack $w.ok -side left -fill x + button $w.cancel -text Cancel -command "destroy $w" + pack $w.cancel -side right -fill x + bind $w "grab $w; focus $w" + tkwait window $w + return $confirm_ok +} + proc makewindow {} { global canv canv2 canv3 linespc charspc ctext cflist global textfont mainfont uifont @@ -394,6 +411,7 @@ proc makewindow {} { global highlight_files gdttype global searchstring sstring global bgcolor fgcolor bglist fglist diffcolors + global headctxmenu menu .bar .bar add cascade -label "File" -menu .bar.file @@ -712,6 +730,13 @@ proc makewindow {} { $rowctxmenu add command -label "Create tag" -command mktag $rowctxmenu add command -label "Write commit to file" -command writecommit $rowctxmenu add command -label "Create new branch" -command mkbranch + + set headctxmenu .headctxmenu + menu $headctxmenu -tearoff 0 + $headctxmenu add command -label "Check out this branch" \ + -command cobranch + $headctxmenu add command -label "Remove this branch" \ + -command rmbranch } # mouse-2 makes all windows scan vertically, but only the one @@ -3237,6 +3262,8 @@ proc drawtags {id x xt y1} { -font $font -tags [list tag.$id text]] if {$ntags >= 0} { $canv bind $t <1> [list showtag $tag 1] + } elseif {$nheads >= 0} { + $canv bind $t [list headmenu %X %Y $id $tag] } } return $xt @@ -5074,6 +5101,73 @@ proc mkbrgo {top} { } } +# context menu for a head +proc headmenu {x y id head} { + global headmenuid headmenuhead headctxmenu + + set headmenuid $id + set headmenuhead $head + tk_popup $headctxmenu $x $y +} + +proc cobranch {} { + global headmenuid headmenuhead mainhead headids + + # check the tree is clean first?? + set oldmainhead $mainhead + nowbusy checkout + update + if {[catch { + exec git checkout $headmenuhead + } err]} { + notbusy checkout + error_popup $err + } else { + notbusy checkout + set maainhead $headmenuhead + if {[info exists headids($oldmainhead)]} { + redrawtags $headids($oldmainhead) + } + redrawtags $headmenuid + } +} + +proc rmbranch {} { + global desc_heads headmenuid headmenuhead mainhead + global headids idheads + + set head $headmenuhead + set id $headmenuid + if {$head eq $mainhead} { + error_popup "Cannot delete the currently checked-out branch" + return + } + if {$desc_heads($id) eq $id} { + # the stuff on this branch isn't on any other branch + if {![confirm_popup "The commits on branch $head aren't on any other\ + branch.\nReally delete branch $head?"]} return + } + nowbusy rmbranch + update + if {[catch {exec git branch -D $head} err]} { + notbusy rmbranch + error_popup $err + return + } + unset headids($head) + if {$idheads($id) eq $head} { + unset idheads($id) + removedhead $id + } else { + set i [lsearch -exact $idheads($id) $head] + if {$i >= 0} { + set idheads($id) [lreplace $idheads($id) $i $i] + } + } + redrawtags $id + notbusy rmbranch +} + # Stuff for finding nearby tags proc getallcommits {} { global allcstart allcommits allcfd allids @@ -5298,6 +5392,30 @@ proc addedhead {hid} { } } +# update the desc_heads array for a head just removed +proc removedhead {hid} { + global desc_heads allparents + + set todo [list $hid] + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_heads($do)]} continue + set i [lsearch -exact $desc_heads($do) $hid] + if {$i < 0} continue + set oldheads $desc_heads($do) + set heads [lreplace $desc_heads($do) $i $i] + while {1} { + set desc_heads($do) $heads + set p $allparents($do) + if {[llength $p] != 1 || ![info exists desc_heads($p)] || + $desc_heads($p) ne $oldheads} break + set do $p + } + set todo [concat $todo $p] + } +} + proc changedrefs {} { global desc_heads desc_tags anc_tags allcommits allids global allchildren allparents idtags travindex -- cgit v0.10.2-6-g49f6 From 53cda8d97e6ee53cc8defa8c7226f0b3093eb12a Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Wed, 2 Aug 2006 19:43:34 +1000 Subject: gitk: Fix a couple of buglets in the branch head menu items This fixes a silly typo (an extra a) and fixes the condition for asking for confirmation of removing a branch. Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index fc65cc0..596f605 100755 --- a/gitk +++ b/gitk @@ -5124,7 +5124,7 @@ proc cobranch {} { error_popup $err } else { notbusy checkout - set maainhead $headmenuhead + set mainhead $headmenuhead if {[info exists headids($oldmainhead)]} { redrawtags $headids($oldmainhead) } @@ -5142,7 +5142,7 @@ proc rmbranch {} { error_popup "Cannot delete the currently checked-out branch" return } - if {$desc_heads($id) eq $id} { + if {$desc_heads($id) eq $id && $idheads($id) eq [list $head]} { # the stuff on this branch isn't on any other branch if {![confirm_popup "The commits on branch $head aren't on any other\ branch.\nReally delete branch $head?"]} return -- cgit v0.10.2-6-g49f6 From c8b87ef8b0da6b08a19ed4f98eb58f46748af1e9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Aug 2006 16:17:51 -0700 Subject: .gitignore: git-merge-recur is a built file. Also it is OK to omit the documentation for it at least for now. Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index fb0fa3f..2299e36 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ git-merge-tree git-merge-octopus git-merge-one-file git-merge-ours +git-merge-recur git-merge-recursive git-merge-resolve git-merge-stupid diff --git a/Makefile b/Makefile index b7a095f..81d26a0 100644 --- a/Makefile +++ b/Makefile @@ -802,7 +802,7 @@ check-docs:: do \ case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ - git-merge-resolve | git-merge-stupid | \ + git-merge-resolve | git-merge-stupid | git-merge-recur | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ -- cgit v0.10.2-6-g49f6 From 3d234d0afacd355eb30b330a56bd2efa73bd44d5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Aug 2006 18:21:41 +0200 Subject: merge-recursive: fix rename handling To handle renames properly, we iterate through all file names of both heads, the current one, and the one to be merged. Only that there was a bug, where it was checked if the file name was present in both heads, but the result of the check was never used. Instead, the merge proceeded as if both heads contained that file. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 74a329f..f5c0080 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -810,8 +810,10 @@ static int process_renames(struct path_list *a_renames, } else { compare = strcmp(a_renames->items[i].path, b_renames->items[j].path); - ren1 = a_renames->items[i++].util; - ren2 = b_renames->items[j++].util; + if (compare <= 0) + ren1 = a_renames->items[i++].util; + if (compare >= 0) + ren2 = b_renames->items[j++].util; } /* TODO: refactor, so that 1/2 are not needed */ -- cgit v0.10.2-6-g49f6 From ca6d8f58a15b9db621dd2b905a04d06bdff44bf8 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Sun, 6 Aug 2006 21:08:05 +1000 Subject: gitk: Add a menu item for cherry-picking commits This does a git-cherry-pick -r to cherry-pick the commit that was right-clicked on to the head of the current branch. This would work better with some minor changes to the git-cherry-pick script. Along the way, this changes desc_heads to record the names of the descendent heads rather than their IDs. Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index 596f605..750a081 100755 --- a/gitk +++ b/gitk @@ -730,6 +730,8 @@ proc makewindow {} { $rowctxmenu add command -label "Create tag" -command mktag $rowctxmenu add command -label "Write commit to file" -command writecommit $rowctxmenu add command -label "Create new branch" -command mkbranch + $rowctxmenu add command -label "Cherry-pick this commit" \ + -command cherrypick set headctxmenu .headctxmenu menu $headctxmenu -tearoff 0 @@ -3302,6 +3304,104 @@ proc finishcommits {} { catch {unset pending_select} } +# Inserting a new commit as the child of the commit on row $row. +# The new commit will be displayed on row $row and the commits +# on that row and below will move down one row. +proc insertrow {row newcmit} { + global displayorder parentlist childlist commitlisted + global commitrow curview rowidlist rowoffsets numcommits + global rowrangelist idrowranges rowlaidout rowoptim numcommits + global linesegends + + if {$row >= $numcommits} { + puts "oops, inserting new row $row but only have $numcommits rows" + return + } + set p [lindex $displayorder $row] + set displayorder [linsert $displayorder $row $newcmit] + set parentlist [linsert $parentlist $row $p] + set kids [lindex $childlist $row] + lappend kids $newcmit + lset childlist $row $kids + set childlist [linsert $childlist $row {}] + set l [llength $displayorder] + for {set r $row} {$r < $l} {incr r} { + set id [lindex $displayorder $r] + set commitrow($curview,$id) $r + } + + set idlist [lindex $rowidlist $row] + set offs [lindex $rowoffsets $row] + set newoffs {} + foreach x $idlist { + if {$x eq {} || ($x eq $p && [llength $kids] == 1)} { + lappend newoffs {} + } else { + lappend newoffs 0 + } + } + if {[llength $kids] == 1} { + set col [lsearch -exact $idlist $p] + lset idlist $col $newcmit + } else { + set col [llength $idlist] + lappend idlist $newcmit + lappend offs {} + lset rowoffsets $row $offs + } + set rowidlist [linsert $rowidlist $row $idlist] + set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs] + + set rowrangelist [linsert $rowrangelist $row {}] + set l [llength $rowrangelist] + for {set r 0} {$r < $l} {incr r} { + set ranges [lindex $rowrangelist $r] + if {$ranges ne {} && [lindex $ranges end] >= $row} { + set newranges {} + foreach x $ranges { + if {$x >= $row} { + lappend newranges [expr {$x + 1}] + } else { + lappend newranges $x + } + } + lset rowrangelist $r $newranges + } + } + if {[llength $kids] > 1} { + set rp1 [expr {$row + 1}] + set ranges [lindex $rowrangelist $rp1] + if {$ranges eq {}} { + set ranges [list $row $rp1] + } elseif {[lindex $ranges end-1] == $rp1} { + lset ranges end-1 $row + } + lset rowrangelist $rp1 $ranges + } + foreach id [array names idrowranges] { + set ranges $idrowranges($id) + if {$ranges ne {} && [lindex $ranges end] >= $row} { + set newranges {} + foreach x $ranges { + if {$x >= $row} { + lappend newranges [expr {$x + 1}] + } else { + lappend newranges $x + } + } + set idrowranges($id) $newranges + } + } + + set linesegends [linsert $linesegends $row {}] + + incr rowlaidout + incr rowoptim + incr numcommits + + redisplay +} + # Don't change the text pane cursor if it is currently the hand cursor, # showing that we are over a sha1 ID link. proc settextcursor {c} { @@ -3629,27 +3729,20 @@ proc viewnextline {dir} { # add a list of tag or branch names at position pos # returns the number of names inserted -proc appendrefs {pos l var} { - global ctext commitrow linknum curview idtags $var +proc appendrefs {pos tags var} { + global ctext commitrow linknum curview $var if {[catch {$ctext index $pos}]} { return 0 } - set tags {} - foreach id $l { - foreach tag [set $var\($id\)] { - lappend tags [concat $tag $id] - } - } - set tags [lsort -index 1 $tags] + set tags [lsort $tags] set sep {} foreach tag $tags { - set name [lindex $tag 0] - set id [lindex $tag 1] + set id [set $var\($tag\)] set lk link$linknum incr linknum $ctext insert $pos $sep - $ctext insert $pos $name $lk + $ctext insert $pos $tag $lk $ctext tag conf $lk -foreground blue if {[info exists commitrow($curview,$id)]} { $ctext tag bind $lk <1> \ @@ -3663,6 +3756,18 @@ proc appendrefs {pos l var} { return [llength $tags] } +proc taglist {ids} { + global idtags + + set tags {} + foreach id $ids { + foreach tag $idtags($id) { + lappend tags $tag + } + } + return $tags +} + # called when we have finished computing the nearby tags proc dispneartags {} { global selectedline currentid ctext anc_tags desc_tags showneartags @@ -3672,15 +3777,15 @@ proc dispneartags {} { set id $currentid $ctext conf -state normal if {[info exists desc_heads($id)]} { - if {[appendrefs branch $desc_heads($id) idheads] > 1} { + if {[appendrefs branch $desc_heads($id) headids] > 1} { $ctext insert "branch -2c" "es" } } if {[info exists anc_tags($id)]} { - appendrefs follows $anc_tags($id) idtags + appendrefs follows [taglist $anc_tags($id)] tagids } if {[info exists desc_tags($id)]} { - appendrefs precedes $desc_tags($id) idtags + appendrefs precedes [taglist $desc_tags($id)] tagids } $ctext conf -state disabled } @@ -3813,7 +3918,7 @@ proc selectline {l isnew} { $ctext mark set branch "end -1c" $ctext mark gravity branch left if {[info exists desc_heads($id)]} { - if {[appendrefs branch $desc_heads($id) idheads] > 1} { + if {[appendrefs branch $desc_heads($id) headids] > 1} { # turn "Branch" into "Branches" $ctext insert "branch -2c" "es" } @@ -3822,13 +3927,13 @@ proc selectline {l isnew} { $ctext mark set follows "end -1c" $ctext mark gravity follows left if {[info exists anc_tags($id)]} { - appendrefs follows $anc_tags($id) idtags + appendrefs follows [taglist $anc_tags($id)] tagids } $ctext insert end "\nPrecedes: " $ctext mark set precedes "end -1c" $ctext mark gravity precedes left if {[info exists desc_tags($id)]} { - appendrefs precedes $desc_tags($id) idtags + appendrefs precedes [taglist $desc_tags($id)] tagids } $ctext insert end "\n" } @@ -4489,6 +4594,7 @@ proc redisplay {} { drawvisible if {[info exists selectedline]} { selectline $selectedline 0 + allcanvs yview moveto [lindex $span 0] } } @@ -5090,17 +5196,57 @@ proc mkbrgo {top} { notbusy newbranch error_popup $err } else { - set headids($name) $id - if {![info exists idheads($id)]} { - addedhead $id - } - lappend idheads($id) $name + addedhead $id $name # XXX should update list of heads displayed for selected commit notbusy newbranch redrawtags $id } } +proc cherrypick {} { + global rowmenuid curview commitrow + global mainhead desc_heads anc_tags desc_tags allparents allchildren + + if {[info exists desc_heads($rowmenuid)] + && [lsearch -exact $desc_heads($rowmenuid) $mainhead] >= 0} { + set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\ + included in branch $mainhead -- really re-apply it?"] + if {!$ok} return + } + nowbusy cherrypick + update + set oldhead [exec git rev-parse HEAD] + # Unfortunately git-cherry-pick writes stuff to stderr even when + # no error occurs, and exec takes that as an indication of error... + if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} { + notbusy cherrypick + error_popup $err + return + } + set newhead [exec git rev-parse HEAD] + if {$newhead eq $oldhead} { + notbusy cherrypick + error_popup "No changes committed" + return + } + set allparents($newhead) $oldhead + lappend allchildren($oldhead) $newhead + set desc_heads($newhead) $mainhead + if {[info exists anc_tags($oldhead)]} { + set anc_tags($newhead) $anc_tags($oldhead) + } + set desc_tags($newhead) {} + if {[info exists commitrow($curview,$oldhead)]} { + insertrow $commitrow($curview,$oldhead) $newhead + if {$mainhead ne {}} { + movedhead $newhead $mainhead + } + redrawtags $oldhead + redrawtags $newhead + } + notbusy cherrypick +} + # context menu for a head proc headmenu {x y id head} { global headmenuid headmenuhead headctxmenu @@ -5142,7 +5288,7 @@ proc rmbranch {} { error_popup "Cannot delete the currently checked-out branch" return } - if {$desc_heads($id) eq $id && $idheads($id) eq [list $head]} { + if {$desc_heads($id) eq $head} { # the stuff on this branch isn't on any other branch if {![confirm_popup "The commits on branch $head aren't on any other\ branch.\nReally delete branch $head?"]} return @@ -5154,16 +5300,7 @@ proc rmbranch {} { error_popup $err return } - unset headids($head) - if {$idheads($id) eq $head} { - unset idheads($id) - removedhead $id - } else { - set i [lsearch -exact $idheads($id) $head] - if {$i >= 0} { - set idheads($id) [lreplace $idheads($id) $i $i] - } - } + removedhead $id $head redrawtags $id notbusy rmbranch } @@ -5293,7 +5430,7 @@ proc forward_pass {id children} { } } if {[info exists idheads($id)]} { - lappend dheads $id + set dheads [concat $dheads $idheads($id)] } set desc_heads($id) $dheads } @@ -5301,7 +5438,7 @@ proc forward_pass {id children} { proc getallclines {fd} { global allparents allchildren allcommits allcstart global desc_tags anc_tags idtags tagisdesc allids - global desc_heads idheads travindex + global idheads travindex while {[gets $fd line] >= 0} { set id [lindex $line 0] @@ -5369,17 +5506,20 @@ proc restartatags {} { } # update the desc_heads array for a new head just added -proc addedhead {hid} { - global desc_heads allparents +proc addedhead {hid head} { + global desc_heads allparents headids idheads + + set headids($head) $hid + lappend idheads($hid) $head set todo [list $hid] while {$todo ne {}} { set do [lindex $todo 0] set todo [lrange $todo 1 end] if {![info exists desc_heads($do)] || - [lsearch -exact $desc_heads($do) $hid] >= 0} continue + [lsearch -exact $desc_heads($do) $head] >= 0} continue set oldheads $desc_heads($do) - lappend desc_heads($do) $hid + lappend desc_heads($do) $head set heads $desc_heads($do) while {1} { set p $allparents($do) @@ -5393,15 +5533,25 @@ proc addedhead {hid} { } # update the desc_heads array for a head just removed -proc removedhead {hid} { - global desc_heads allparents +proc removedhead {hid head} { + global desc_heads allparents headids idheads + + unset headids($head) + if {$idheads($hid) eq $head} { + unset idheads($hid) + } else { + set i [lsearch -exact $idheads($hid) $head] + if {$i >= 0} { + set idheads($hid) [lreplace $idheads($hid) $i $i] + } + } set todo [list $hid] while {$todo ne {}} { set do [lindex $todo 0] set todo [lrange $todo 1 end] if {![info exists desc_heads($do)]} continue - set i [lsearch -exact $desc_heads($do) $hid] + set i [lsearch -exact $desc_heads($do) $head] if {$i < 0} continue set oldheads $desc_heads($do) set heads [lreplace $desc_heads($do) $i $i] @@ -5416,6 +5566,23 @@ proc removedhead {hid} { } } +# update things for a head moved to a child of its previous location +proc movedhead {id name} { + global headids idheads + + set oldid $headids($name) + set headids($name) $id + if {$idheads($oldid) eq $name} { + unset idheads($oldid) + } else { + set i [lsearch -exact $idheads($oldid) $name] + if {$i >= 0} { + set idheads($oldid) [lreplace $idheads($oldid) $i $i] + } + } + lappend idheads($id) $name +} + proc changedrefs {} { global desc_heads desc_tags anc_tags allcommits allids global allchildren allparents idtags travindex -- cgit v0.10.2-6-g49f6 From ceadfe90c64b293a653bcbbce83283d2665d7cf9 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Tue, 8 Aug 2006 20:55:36 +1000 Subject: gitk: Update preceding/following tag info when creating a tag Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index 750a081..0f35227 100755 --- a/gitk +++ b/gitk @@ -5062,6 +5062,7 @@ proc domktag {} { set tagids($tag) $id lappend idtags($id) $tag redrawtags $id + addedtag $id } proc redrawtags {id} { @@ -5505,6 +5506,82 @@ proc restartatags {} { dispneartags } +# update the desc_tags and anc_tags arrays for a new tag just added +proc addedtag {id} { + global desc_tags anc_tags allparents allchildren allcommits + global idtags tagisdesc alldtags + + if {![info exists desc_tags($id)]} return + set adt $desc_tags($id) + foreach t $desc_tags($id) { + set adt [concat $adt $alldtags($t)] + } + set adt [lsort -unique $adt] + set alldtags($id) $adt + foreach t $adt { + set tagisdesc($id,$t) -1 + set tagisdesc($t,$id) 1 + } + if {[info exists anc_tags($id)]} { + set todo $anc_tags($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {[info exists tagisdesc($id,$do)]} continue + set tagisdesc($do,$id) -1 + set tagisdesc($id,$do) 1 + if {[info exists anc_tags($do)]} { + set todo [concat $todo $anc_tags($do)] + } + } + } + + set lastold $desc_tags($id) + set lastnew [list $id] + set nup 0 + set nch 0 + set todo $allparents($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_tags($do)]} continue + if {$desc_tags($do) ne $lastold} { + set lastold $desc_tags($do) + set lastnew [combine_dtags $lastold [list $id]] + incr nch + } + if {$lastold eq $lastnew} continue + set desc_tags($do) $lastnew + incr nup + if {![info exists idtags($do)]} { + set todo [concat $todo $allparents($do)] + } + } + + if {![info exists anc_tags($id)]} return + set lastold $anc_tags($id) + set lastnew [list $id] + set nup 0 + set nch 0 + set todo $allchildren($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists anc_tags($do)]} continue + if {$anc_tags($do) ne $lastold} { + set lastold $anc_tags($do) + set lastnew [combine_atags $lastold [list $id]] + incr nch + } + if {$lastold eq $lastnew} continue + set anc_tags($do) $lastnew + incr nup + if {![info exists idtags($do)]} { + set todo [concat $todo $allchildren($do)] + } + } +} + # update the desc_heads array for a new head just added proc addedhead {hid head} { global desc_heads allparents headids idheads -- cgit v0.10.2-6-g49f6 From 5b982f84ee6eb3027e5bdf2e917a0efa48aded6a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Aug 2006 15:04:16 +0200 Subject: merge-recur: do not call git-write-tree Since merge-recur is in C, and uses libgit, it can call the relevant functions directly, without writing the index file. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index f5c0080..b8b0951 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -248,38 +248,34 @@ static int git_merge_trees(int index_only, return rc; } -/* - * TODO: this can be streamlined by refactoring builtin-write-tree.c - */ static struct tree *git_write_tree(void) { - FILE *fp; - int rc; - char buf[41]; - unsigned char sha1[20]; - int ch; - unsigned i = 0; + struct tree *result = NULL; + if (cache_dirty) { + unsigned i; for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) return NULL; } - flush_cache(); - } - fp = popen("git-write-tree 2>/dev/null", "r"); - while ((ch = fgetc(fp)) != EOF) - if (i < sizeof(buf)-1 && ch >= '0' && ch <= 'f') - buf[i++] = ch; - else - break; - rc = pclose(fp); - if (rc == -1 || WEXITSTATUS(rc)) - return NULL; - buf[i] = '\0'; - if (get_sha1(buf, sha1) != 0) - return NULL; - return lookup_tree(sha1); + } else + read_cache_from(getenv("GIT_INDEX_FILE")); + + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree) && + cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) + die("error building trees"); + + result = lookup_tree(active_cache_tree->sha1); + + flush_cache(); + cache_dirty = 0; + + return result; } static int save_files_dirs(const unsigned char *sha1, -- cgit v0.10.2-6-g49f6 From c1964a006f9035cbdc6de8e55768fc6ad00d4825 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Aug 2006 15:07:31 +0200 Subject: merge-recur: do not setenv("GIT_INDEX_FILE") Since there are no external calls left in merge-recur, we do not need to set the environment variable GIT_INDEX_FILE all the time. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index b8b0951..7a93dd9 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -116,6 +116,7 @@ static void output_commit_title(struct commit *commit) } } +static const char *current_index_file = NULL; static const char *original_index_file; static const char *temporary_index_file; static int cache_dirty = 0; @@ -124,12 +125,12 @@ static int flush_cache(void) { /* flush temporary index */ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int fd = hold_lock_file_for_update(lock, getenv("GIT_INDEX_FILE")); + int fd = hold_lock_file_for_update(lock, current_index_file); if (fd < 0) die("could not lock %s", lock->filename); if (write_cache(fd, active_cache, active_nr) || close(fd) || commit_lock_file(lock)) - die ("unable to write %s", getenv("GIT_INDEX_FILE")); + die ("unable to write %s", current_index_file); discard_cache(); cache_dirty = 0; return 0; @@ -137,11 +138,10 @@ static int flush_cache(void) static void setup_index(int temp) { - const char *idx = temp ? temporary_index_file: original_index_file; + current_index_file = temp ? temporary_index_file: original_index_file; if (cache_dirty) die("fatal: cache changed flush_cache();"); unlink(temporary_index_file); - setenv("GIT_INDEX_FILE", idx, 1); discard_cache(); } @@ -174,7 +174,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, { struct cache_entry *ce; if (!cache_dirty) - read_cache_from(getenv("GIT_INDEX_FILE")); + read_cache_from(current_index_file); cache_dirty++; ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); if (!ce) @@ -223,7 +223,7 @@ static int git_merge_trees(int index_only, struct unpack_trees_options opts; if (!cache_dirty) { - read_cache_from(getenv("GIT_INDEX_FILE")); + read_cache_from(current_index_file); cache_dirty = 1; } @@ -260,7 +260,7 @@ static struct tree *git_write_tree(void) return NULL; } } else - read_cache_from(getenv("GIT_INDEX_FILE")); + read_cache_from(current_index_file); if (!active_cache_tree) active_cache_tree = cache_tree(); @@ -338,7 +338,7 @@ static struct path_list *get_unmerged(void) unmerged->strdup_paths = 1; if (!cache_dirty) { - read_cache_from(getenv("GIT_INDEX_FILE")); + read_cache_from(current_index_file); cache_dirty++; } for (i = 0; i < active_nr; i++) { @@ -468,10 +468,6 @@ static int remove_path(const char *name) return ret; } -/* - * TODO: once we no longer call external programs, we'd probably be better off - * not setting / getting the environment variable GIT_INDEX_FILE all the time. - */ int remove_file(int clean, const char *path) { int update_cache = index_only || clean; @@ -479,7 +475,7 @@ int remove_file(int clean, const char *path) if (update_cache) { if (!cache_dirty) - read_cache_from(getenv("GIT_INDEX_FILE")); + read_cache_from(current_index_file); cache_dirty++; if (remove_file_from_cache(path)) return -1; -- cgit v0.10.2-6-g49f6 From 934d9a24078e65111e9946ad3449c3fa9c06475e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Aug 2006 18:43:03 +0200 Subject: merge-recur: if there is no common ancestor, fake empty one This fixes the coolest merge ever. [jc: with two "Oops that's not it" fixes from Johannes and Alex, and an obvious type mismatch fix.] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 7a93dd9..d4de1ad 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1223,6 +1223,18 @@ int merge(struct commit *h1, output_commit_title(iter->item); merged_common_ancestors = pop_commit(&ca); + if (merged_common_ancestors == NULL) { + /* if there is no common ancestor, make an empty tree */ + struct tree *tree = xcalloc(1, sizeof(struct tree)); + unsigned char hdr[40]; + int hdrlen; + + tree->object.parsed = 1; + tree->object.type = OBJ_TREE; + write_sha1_file_prepare(NULL, 0, tree_type, tree->object.sha1, + hdr, &hdrlen); + merged_common_ancestors = make_virtual_commit(tree, "ancestor"); + } for (iter = ca; iter; iter = iter->next) { output_indent = call_depth + 1; -- cgit v0.10.2-6-g49f6 From 8918b0c9c2667c5a69461955135c709b09561f72 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Aug 2006 22:30:58 +0200 Subject: merge-recur: try to merge older merge bases first It seems to be the only sane way to do it: when a two-head merge is done, and the merge-base and one of the two branches agree, the merge assumes that the other branch has something new. If we start creating virtual commits from newer merge-bases, and go back to older merge-bases, and then merge with newer commits again, chances are that a patch is lost, _because_ the merge-base and the head agree on it. Unlikely, yes, but it happened to me. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index d4de1ad..9281cd1 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1191,6 +1191,17 @@ static int merge_trees(struct tree *head, return clean; } +static struct commit_list *reverse_commit_list(struct commit_list *list) +{ + struct commit_list *next = NULL, *current, *backup; + for (current = list; current; current = backup) { + backup = current->next; + current->next = next; + next = current; + } + return next; +} + /* * Merge the commits h1 and h2, return the resulting virtual * commit object and a flag indicating the cleaness of the merge. @@ -1216,7 +1227,7 @@ int merge(struct commit *h1, if (ancestor) commit_list_insert(ancestor, &ca); else - ca = get_merge_bases(h1, h2, 1); + ca = reverse_commit_list(get_merge_bases(h1, h2, 1)); output("found %u common ancestor(s):", commit_list_count(ca)); for (iter = ca; iter; iter = iter->next) -- cgit v0.10.2-6-g49f6 From 984b65707e25c426a32feb9b9d46f077b605cb31 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Aug 2006 22:31:49 +0200 Subject: merge-recur: do not die unnecessarily When the cache is dirty, and we switch the index file from temporary to final, we want to discard the cache without complaint. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 9281cd1..454e293 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -139,8 +139,10 @@ static int flush_cache(void) static void setup_index(int temp) { current_index_file = temp ? temporary_index_file: original_index_file; - if (cache_dirty) - die("fatal: cache changed flush_cache();"); + if (cache_dirty) { + discard_cache(); + cache_dirty = 0; + } unlink(temporary_index_file); discard_cache(); } -- cgit v0.10.2-6-g49f6 From 4147d801db66df9b127ffe315601f467aa9d1c48 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Aug 2006 16:47:21 +0200 Subject: discard_cache(): discard index, even if no file was mmap()ed Since add_cacheinfo() can be called without a mapped index file, discard_cache() _has_ to discard the entries, even when cache_mmap == NULL. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/read-cache.c b/read-cache.c index c375e91..fc2af2c 100644 --- a/read-cache.c +++ b/read-cache.c @@ -840,14 +840,14 @@ int discard_cache() { int ret; + active_nr = active_cache_changed = 0; + index_file_timestamp = 0; + cache_tree_free(&active_cache_tree); if (cache_mmap == NULL) return 0; ret = munmap(cache_mmap, cache_mmap_size); cache_mmap = NULL; cache_mmap_size = 0; - active_nr = active_cache_changed = 0; - index_file_timestamp = 0; - cache_tree_free(&active_cache_tree); /* no need to throw away allocated active_cache */ return ret; -- cgit v0.10.2-6-g49f6 From c04c4e5708c9ad3dc534ff50ad8f28de5a7cfbff Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Jul 2006 18:12:12 -0700 Subject: upload-pack: minor clean-up in multi-ack logic No changes to what it does, but separating the codepath clearly with if ... else if ... chain makes it easier to follow. Signed-off-by: Junio C Hamano diff --git a/upload-pack.c b/upload-pack.c index bbd6bd6..35c7ecb 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -351,7 +351,8 @@ static int got_sha1(char *hex, unsigned char *sha1) static int get_common_commits(void) { static char line[1000]; - unsigned char sha1[20], last_sha1[20]; + unsigned char sha1[20]; + char hex[41], last_hex[41]; int len; track_object_refs = 0; @@ -368,21 +369,22 @@ static int get_common_commits(void) } len = strip(line, len); if (!strncmp(line, "have ", 5)) { - if (got_sha1(line+5, sha1) && - (multi_ack || have_obj.nr == 1)) { - packet_write(1, "ACK %s%s\n", - sha1_to_hex(sha1), - multi_ack ? " continue" : ""); - if (multi_ack) - memcpy(last_sha1, sha1, 20); + if (got_sha1(line+5, sha1)) { + memcpy(hex, sha1_to_hex(sha1), 41); + if (multi_ack) { + const char *msg = "ACK %s continue\n"; + packet_write(1, msg, hex); + memcpy(last_hex, hex, 41); + } + else if (have_obj.nr == 1) + packet_write(1, "ACK %s\n", hex); } continue; } if (!strcmp(line, "done")) { if (have_obj.nr > 0) { if (multi_ack) - packet_write(1, "ACK %s\n", - sha1_to_hex(last_sha1)); + packet_write(1, "ACK %s\n", last_hex); return 0; } packet_write(1, "NAK\n"); -- cgit v0.10.2-6-g49f6 From 937a515a15f776aa84430574f71367292a52b978 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Jul 2006 21:28:20 -0700 Subject: upload-pack: stop the other side when they have more roots than we do. When the downloader has more roots than we do, we will see many "have" that leads to the root we do not have and let the other side walk all the way to that root. Tell them to stop sending the branch'es ancestors by sending a fake "ACK" when we already have common ancestor for the wanted refs. Signed-off-by: Junio C Hamano diff --git a/upload-pack.c b/upload-pack.c index 35c7ecb..e8f4be3 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -11,9 +11,15 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] "; -#define THEY_HAVE (1U << 0) -#define OUR_REF (1U << 1) -#define WANTED (1U << 2) +/* bits #0..7 in revision.h, #8..10 in commit.c */ +#define THEY_HAVE (1u << 11) +#define OUR_REF (1u << 12) +#define WANTED (1u << 13) +#define COMMON_KNOWN (1u << 14) +#define REACHABLE (1u << 15) + +static unsigned long oldest_have = 0; + static int multi_ack = 0, nr_our_refs = 0; static int use_thin_pack = 0; static struct object_array have_obj; @@ -323,11 +329,12 @@ static void create_pack_file(void) static int got_sha1(char *hex, unsigned char *sha1) { struct object *o; + int we_knew_they_have = 0; if (get_sha1_hex(hex, sha1)) die("git-upload-pack: expected SHA1 object, got '%s'", hex); if (!has_sha1_file(sha1)) - return 0; + return -1; o = lookup_object(sha1); if (!(o && o->parsed)) @@ -336,15 +343,84 @@ static int got_sha1(char *hex, unsigned char *sha1) die("oops (%s)", sha1_to_hex(sha1)); if (o->type == OBJ_COMMIT) { struct commit_list *parents; + struct commit *commit = (struct commit *)o; if (o->flags & THEY_HAVE) - return 0; - o->flags |= THEY_HAVE; - for (parents = ((struct commit*)o)->parents; + we_knew_they_have = 1; + else + o->flags |= THEY_HAVE; + if (!oldest_have || (commit->date < oldest_have)) + oldest_have = commit->date; + for (parents = commit->parents; parents; parents = parents->next) parents->item->object.flags |= THEY_HAVE; } - add_object_array(o, NULL, &have_obj); + if (!we_knew_they_have) { + add_object_array(o, NULL, &have_obj); + 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; } @@ -369,7 +445,13 @@ static int get_common_commits(void) } len = strip(line, len); 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); if (multi_ack) { const char *msg = "ACK %s continue\n"; @@ -378,6 +460,7 @@ static int get_common_commits(void) } else if (have_obj.nr == 1) packet_write(1, "ACK %s\n", hex); + break; } continue; } -- cgit v0.10.2-6-g49f6 From 4c5cf8c44ce06a79da5bafd4a92e6d6f598cea2e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 13 Aug 2006 04:13:25 -0700 Subject: pass DESTDIR to the generated perl/Makefile Makes life for binary packagers easier, as the Perl modules will be installed inside DESTDIR. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 97ee9af..ef9d82d 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -22,10 +22,14 @@ if ($@) { $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm'; } +my %extra; +$extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR}; + WriteMakefile( NAME => 'Git', VERSION_FROM => 'Git.pm', PM => \%pm, MYEXTLIB => '../libgit.a', INC => '-I. -I..', + %extra ); -- cgit v0.10.2-6-g49f6 From 60a144f28047b4fa0e4a795972c483fa85a7d3c8 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Tue, 15 Aug 2006 11:01:31 +0200 Subject: Fix compilation with Sun CC - Add the CFLAGS variable to config.mak.in to override the Makefile's default, which is gcc-specific and won't work with Sun CC. - Prefer "cc" over "gcc", because Pasky's Git.pm will not compile with gcc on Solaris at all. On Linux and the free BSDs "cc" is linked to "gcc" anyway. - Set correct flag to generate position-independent code. - Add "-xO3" (= use default optimization level) to CFLAGS. Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index ac42ae3..4e7a37a 100644 --- a/Makefile +++ b/Makefile @@ -112,6 +112,7 @@ uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') # CFLAGS and LDFLAGS are for the users to override from the command line. CFLAGS = -g -O2 -Wall +PIC_FLAG = -fPIC LDFLAGS = ALL_CFLAGS = $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) @@ -402,6 +403,9 @@ endif ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease endif +ifeq ($(uname_M),sun4u) + USE_PIC = YesPlease +endif ifeq ($(uname_M),x86_64) USE_PIC = YesPlease endif @@ -544,7 +548,7 @@ endif endif endif ifdef USE_PIC - ALL_CFLAGS += -fPIC + ALL_CFLAGS += $(PIC_FLAG) endif ifdef NO_ACCURATE_DIFF BASIC_CFLAGS += -DNO_ACCURATE_DIFF diff --git a/config.mak.in b/config.mak.in index 369e611..addda4f 100644 --- a/config.mak.in +++ b/config.mak.in @@ -2,6 +2,8 @@ # @configure_input@ CC = @CC@ +CFLAGS = @CFLAGS@ +PIC_FLAG = @PIC_FLAG@ AR = @AR@ TAR = @TAR@ #INSTALL = @INSTALL@ # needs install-sh or install.sh in sources diff --git a/configure.ac b/configure.ac index 36f9cd9..0f93f6f 100644 --- a/configure.ac +++ b/configure.ac @@ -95,7 +95,14 @@ AC_SUBST(PYTHON_PATH) ## Checks for programs. AC_MSG_NOTICE([CHECKS for programs]) # -AC_PROG_CC +AC_PROG_CC([cc gcc]) +if test -n "$GCC"; then + PIC_FLAG="-fPIC" +else + AC_CHECK_DECL(__SUNPRO_C, [CFLAGS="$CFLAGS -xO3"; PIC_FLAG="-KPIC"]) +fi +AC_SUBST(PIC_FLAG) + #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) -- cgit v0.10.2-6-g49f6 From d1e46756d312f65613863a41c256d852fcde330c Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Wed, 16 Aug 2006 20:02:32 +1000 Subject: gitk: Improve responsiveness while reading and layout out the graph This restructures layoutmore so that it can take a time limit and do limited amounts of graph layout and graph optimization, and return 1 if it exceeded the time limit before finishing everything it could do. Also getcommitlines reads at most half a megabyte each time, to limit the time it spends parsing the commits to about a tenth of a second. Also got rid of the unused ncmupdate variable while I was at it. Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index 0f35227..b66ccca 100755 --- a/gitk +++ b/gitk @@ -17,13 +17,12 @@ proc gitdir {} { } proc start_rev_list {view} { - global startmsecs nextupdate ncmupdate + global startmsecs nextupdate global commfd leftover tclencoding datemode global viewargs viewfiles commitidx set startmsecs [clock clicks -milliseconds] set nextupdate [expr {$startmsecs + 100}] - set ncmupdate 1 set commitidx($view) 0 set args $viewargs($view) if {$viewfiles($view) ne {}} { @@ -79,7 +78,7 @@ proc getcommitlines {fd view} { global parentlist childlist children curview hlview global vparentlist vchildlist vdisporder vcmitlisted - set stuff [read $fd] + set stuff [read $fd 500000] if {$stuff == {}} { if {![eof $fd]} return global viewname @@ -185,7 +184,7 @@ proc getcommitlines {fd view} { } if {$gotsome} { if {$view == $curview} { - layoutmore + while {[layoutmore $nextupdate]} doupdate } elseif {[info exists hlview] && $view == $hlview} { vhighlightmore } @@ -196,20 +195,13 @@ proc getcommitlines {fd view} { } proc doupdate {} { - global commfd nextupdate numcommits ncmupdate + global commfd nextupdate numcommits foreach v [array names commfd] { fileevent $commfd($v) readable {} } update set nextupdate [expr {[clock clicks -milliseconds] + 100}] - if {$numcommits < 100} { - set ncmupdate [expr {$numcommits + 1}] - } elseif {$numcommits < 10000} { - set ncmupdate [expr {$numcommits + 10}] - } else { - set ncmupdate [expr {$numcommits + 100}] - } foreach v [array names commfd] { set fd $commfd($v) fileevent $fd readable [list getcommitlines $fd $v] @@ -1697,7 +1689,7 @@ proc showview {n} { show_status "Reading commits..." } if {[info exists commfd($n)]} { - layoutmore + layoutmore {} } else { finishcommits } @@ -2378,20 +2370,38 @@ proc visiblerows {} { return [list $r0 $r1] } -proc layoutmore {} { +proc layoutmore {tmax} { global rowlaidout rowoptim commitidx numcommits optim_delay global uparrowlen curview - set row $rowlaidout - set rowlaidout [layoutrows $row $commitidx($curview) 0] - set orow [expr {$rowlaidout - $uparrowlen - 1}] - if {$orow > $rowoptim} { - optimize_rows $rowoptim 0 $orow - set rowoptim $orow - } - set canshow [expr {$rowoptim - $optim_delay}] - if {$canshow > $numcommits} { - showstuff $canshow + while {1} { + if {$rowoptim - $optim_delay > $numcommits} { + showstuff [expr {$rowoptim - $optim_delay}] + } elseif {$rowlaidout - $uparrowlen - 1 > $rowoptim} { + set nr [expr {$rowlaidout - $uparrowlen - 1 - $rowoptim}] + if {$nr > 100} { + set nr 100 + } + optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}] + incr rowoptim $nr + } elseif {$commitidx($curview) > $rowlaidout} { + set nr [expr {$commitidx($curview) - $rowlaidout}] + # may need to increase this threshold if uparrowlen or + # mingaplen are increased... + if {$nr > 150} { + set nr 150 + } + set row $rowlaidout + set rowlaidout [layoutrows $row [expr {$row + $nr}] 0] + if {$rowlaidout == $row} { + return 0 + } + } else { + return 0 + } + if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} { + return 1 + } } } -- cgit v0.10.2-6-g49f6 From 03eb8f8aeb8a483b11b797cca012fdded818a5c1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 16 Aug 2006 16:07:20 -0700 Subject: builtin-apply --reverse: two bugfixes. Parsing of a binary hunk did not consume the terminating blank line. When applying in reverse, it did not use the second, reverse binary hunk. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 4f0eef0..be6e94d 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1063,8 +1063,12 @@ static struct fragment *parse_binary_hunk(char **buf_p, llen = linelen(buffer, size); used += llen; linenr++; - if (llen == 1) + if (llen == 1) { + /* consume the blank line */ + buffer++; + size--; break; + } /* Minimum line is "A00000\n" which is 7-byte long, * and the line length must be multiple of 5 plus 2. */ @@ -1618,7 +1622,7 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) "without the reverse hunk to '%s'", patch->new_name ? patch->new_name : patch->old_name); - fragment = fragment; + fragment = fragment->next; } data = (void*) fragment->patch; switch (fragment->binary_patch_method) { @@ -1717,7 +1721,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) write_sha1_file_prepare(desc->buffer, desc->size, blob_type, sha1, hdr, &hdrlen); if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error("binary patch to '%s' creates incorrect result", name); + return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1)); } return 0; -- cgit v0.10.2-6-g49f6 From d4c452f03b49072ebb46fc524e6d85056a35ef13 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 16 Aug 2006 16:08:14 -0700 Subject: diff.c: make binary patch reversible. This matches the format previous "git-apply --reverse" update expects. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 7a238d0..b816196 100644 --- a/diff.c +++ b/diff.c @@ -838,7 +838,7 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -849,7 +849,6 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) unsigned long deflate_size; unsigned long data_size; - printf("GIT binary patch\n"); /* We could do deflated delta, or we could do just deflated two, * whichever is smaller. */ @@ -898,6 +897,13 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) free(data); } +static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +{ + printf("GIT binary patch\n"); + emit_binary_diff_body(one, two); + emit_binary_diff_body(two, one); +} + #define FIRST_FEW_BYTES 8000 static int mmfile_is_binary(mmfile_t *mf) { -- cgit v0.10.2-6-g49f6 From 2cda1a214e9d2e362242027b4b622ecb3d9260de Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 16 Aug 2006 16:09:25 -0700 Subject: apply --reverse: tie it all together. Add a few tests, usage string, and documentation. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 2ff7494..f1ab1f9 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] - [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] + [--no-add] [--index-info] [--allow-binary-replacement] + [--reverse] [-z] [-pNUM] [-CNUM] [--whitespace=] [...] @@ -62,6 +63,9 @@ OPTIONS the original version of the blob is available locally, outputs information about them to the standard output. +--reverse:: + Apply the patch in reverse. + -z:: When showing the index information, do not munge paths, but use NUL terminated machine readable format. Without diff --git a/builtin-apply.c b/builtin-apply.c index be6e94d..4737b64 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -43,7 +43,7 @@ static int show_index_info; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; static enum whitespace_eol { nowarn_whitespace, diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh index 69aebe6..74f5c2a 100755 --- a/t/t4116-apply-reverse.sh +++ b/t/t4116-apply-reverse.sh @@ -22,25 +22,64 @@ test_expect_success setup ' tr "[mon]" '\''[\0\1\2]'\'' file2 && git commit -a -m second && + git tag second && - git diff --binary -R initial >patch + git diff --binary initial second >patch ' test_expect_success 'apply in forward' ' + T0=`git rev-parse "second^{tree}"` && + git reset --hard initial && git apply --index --binary patch && - git diff initial >diff && - diff -u /dev/null diff - + T1=`git write-tree` && + test "$T0" = "$T1" ' test_expect_success 'apply in reverse' ' + git reset --hard second && git apply --reverse --binary --index patch && git diff >diff && diff -u /dev/null diff ' +test_expect_success 'setup separate repository lacking postimage' ' + + git tar-tree initial initial | tar xf - && + ( + cd initial && git init-db && git add . + ) && + + git tar-tree second second | tar xf - && + ( + cd second && git init-db && git add . + ) + +' + +test_expect_success 'apply in forward without postimage' ' + + T0=`git rev-parse "second^{tree}"` && + ( + cd initial && + git apply --index --binary ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + +test_expect_success 'apply in reverse without postimage' ' + + T0=`git rev-parse "initial^{tree}"` && + ( + cd second && + git apply --index --binary --reverse ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + test_done -- cgit v0.10.2-6-g49f6 From 57dc397cff09bfabd79ddbc38b704cdd0c2bc6e3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 16 Aug 2006 17:55:29 -0700 Subject: git-apply --reject With the new flag "--reject", hunks that do not apply are sent to the standard output, and the usable hunks are applied. The command itself exits with non-zero status when this happens, so that the user or wrapper can take notice and sort the remaining mess out. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index f1ab1f9..11641a9 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] - [--reverse] [-z] [-pNUM] + [--reverse] [--reject] [-z] [-pNUM] [-CNUM] [--whitespace=] [...] @@ -66,6 +66,13 @@ OPTIONS --reverse:: Apply the patch in reverse. +--reject:: + For atomicity, `git apply` fails the whole patch and + does not touch the working tree when some of the hunks + do not apply by default. This option makes it apply + parts of the patch that are applicable, and send the + rejected hunks to the standard output of the command. + -z:: When showing the index information, do not munge paths, but use NUL terminated machine readable format. Without diff --git a/builtin-apply.c b/builtin-apply.c index 4737b64..7dea913 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -38,12 +38,13 @@ static int summary; static int check; static int apply = 1; static int apply_in_reverse; +static int apply_with_reject; static int no_add; static int show_index_info; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; static enum whitespace_eol { nowarn_whitespace, @@ -122,6 +123,7 @@ struct fragment { unsigned long newpos, newlines; const char *patch; int size; + int rejected; struct fragment *next; }; @@ -138,6 +140,7 @@ struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; int is_rename, is_copy, is_new, is_delete, is_binary; + int rejected; unsigned long deflate_origlen; int lines_added, lines_deleted; int score; @@ -1548,7 +1551,8 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i lines = 0; pos = frag->newpos; for (;;) { - offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); + offset = find_offset(buf, desc->size, + oldlines, oldsize, pos, &lines); if (match_end && offset + oldsize != desc->size) offset = -1; if (match_beginning && offset) @@ -1561,8 +1565,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i /* Warn if it was necessary to reduce the number * of context lines. */ - if ((leading != frag->leading) || (trailing != frag->trailing)) - fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n", + if ((leading != frag->leading) || + (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld)" + " to apply fragment at %d\n", leading, trailing, pos + lines); if (size > alloc) { @@ -1572,7 +1578,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i desc->buffer = buf; } desc->size = size; - memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); + memmove(buf + offset + newsize, + buf + offset + oldsize, + size - offset - newsize); memcpy(buf + offset, newlines, newsize); offset = 0; @@ -1736,9 +1744,12 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return apply_binary(desc, patch); while (frag) { - if (apply_one_fragment(desc, frag, patch->inaccurate_eof) < 0) - return error("patch failed: %s:%ld", - name, frag->oldpos); + if (apply_one_fragment(desc, frag, patch->inaccurate_eof)) { + error("patch failed: %s:%ld", name, frag->oldpos); + if (!apply_with_reject) + return -1; + frag->rejected = 1; + } frag = frag->next; } return 0; @@ -1774,8 +1785,9 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * desc.size = size; desc.alloc = alloc; desc.buffer = buf; + if (apply_fragments(&desc, patch) < 0) - return -1; + return -1; /* note with --reject this succeeds. */ /* NUL terminate the result */ if (desc.alloc <= desc.size) @@ -1800,6 +1812,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) struct cache_entry *ce = NULL; int ok_if_exists; + patch->rejected = 1; /* we will drop this after we succeed */ if (old_name) { int changed = 0; int stat_ret = 0; @@ -1905,6 +1918,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) if (apply_data(patch, &st, ce) < 0) return error("%s: patch does not apply", name); + patch->rejected = 0; return 0; } @@ -2223,23 +2237,73 @@ static void write_out_one_result(struct patch *patch, int phase) if (phase == 0) remove_file(patch); if (phase == 1) - create_file(patch); + create_file(patch); } -static void write_out_results(struct patch *list, int skipped_patch) +static int write_out_one_reject(struct patch *patch) +{ + struct fragment *frag; + int rejects = 0; + + for (rejects = 0, frag = patch->fragments; frag; frag = frag->next) { + if (!frag->rejected) + continue; + if (rejects == 0) { + rejects = 1; + printf("** Rejected hunk(s) for "); + if (patch->old_name && patch->new_name && + strcmp(patch->old_name, patch->new_name)) { + write_name_quoted(NULL, 0, + patch->old_name, 1, stdout); + fputs(" => ", stdout); + write_name_quoted(NULL, 0, + patch->new_name, 1, stdout); + } + else { + const char *n = patch->new_name; + if (!n) + n = patch->old_name; + write_name_quoted(NULL, 0, n, 1, stdout); + } + printf(" **\n"); + } + printf("%.*s", frag->size, frag->patch); + if (frag->patch[frag->size-1] != '\n') + putchar('\n'); + } + return rejects; +} + +static int write_out_results(struct patch *list, int skipped_patch) { int phase; + int errs = 0; + struct patch *l; if (!list && !skipped_patch) - die("No changes"); + return error("No changes"); for (phase = 0; phase < 2; phase++) { - struct patch *l = list; + l = list; + while (l) { + if (l->rejected) + errs = 1; + else + write_out_one_result(l, phase); + l = l->next; + } + } + if (apply_with_reject) { + l = list; while (l) { - write_out_one_result(l, phase); + if (!l->rejected) { + if (write_out_one_reject(l)) + errs = 1; + } l = l->next; } } + return errs; } static struct lock_file lock_file; @@ -2314,11 +2378,13 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) die("unable to read index file"); } - if ((check || apply) && check_patch_list(list) < 0) + if ((check || apply) && + check_patch_list(list) < 0 && + !apply_with_reject) exit(1); - if (apply) - write_out_results(list, skipped_patch); + if (apply && write_out_results(list, skipped_patch)) + exit(1); if (show_index_info) show_index_list(list); @@ -2351,6 +2417,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int i; int read_stdin = 1; int inaccurate_eof = 0; + int errs = 0; const char *whitespace_option = NULL; @@ -2360,7 +2427,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int fd; if (!strcmp(arg, "-")) { - apply_patch(0, "", inaccurate_eof); + errs |= apply_patch(0, "", inaccurate_eof); read_stdin = 0; continue; } @@ -2441,6 +2508,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix) apply_in_reverse = 1; continue; } + if (!strcmp(arg, "--reject")) { + apply = apply_with_reject = 1; + continue; + } if (!strcmp(arg, "--inaccurate-eof")) { inaccurate_eof = 1; continue; @@ -2461,18 +2532,19 @@ int cmd_apply(int argc, const char **argv, const char *prefix) usage(apply_usage); read_stdin = 0; set_default_whitespace_mode(whitespace_option); - apply_patch(fd, arg, inaccurate_eof); + errs |= apply_patch(fd, arg, inaccurate_eof); close(fd); } set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0, "", inaccurate_eof); + errs |= apply_patch(0, "", inaccurate_eof); if (whitespace_error) { if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) { int squelched = whitespace_error - squelch_whitespace_errors; - fprintf(stderr, "warning: squelched %d whitespace error%s\n", + fprintf(stderr, "warning: squelched %d " + "whitespace error%s\n", squelched, squelched == 1 ? "" : "s"); } @@ -2500,5 +2572,5 @@ int cmd_apply(int argc, const char **argv, const char *prefix) die("Unable to write new index file"); } - return 0; + return !!errs; } diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh new file mode 100755 index 0000000..3362819 --- /dev/null +++ b/t/t4117-apply-reject.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply with rejects + +' + +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + do + echo $i + done >file1 && + cat file1 >saved.file1 && + git update-index --add file1 && + git commit -m initial && + + for i in 1 2 A B 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 D 21 + do + echo $i + done >file1 && + git diff >patch.1 && + + mv file1 file2 && + git update-index --add --remove file1 file2 && + git diff -M HEAD >patch.2 && + + rm -f file1 file2 && + mv saved.file1 file1 && + git update-index --add --remove file1 file2 && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >file1 && + + cat file1 >saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 + + if git apply --reject patch.1 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + cat rejects + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >expected.file1 && + + diff -u file1 expected.file1 +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 + + if git apply --reject patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + cat rejects + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >expected.file2 && + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected.file2 +' + +test_done -- cgit v0.10.2-6-g49f6 From 756d2f064b2419fcdf9cd9c851f352e2a4f75103 Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Thu, 17 Aug 2006 00:28:36 +0200 Subject: gitweb: continue consolidation of URL generation. Further use href() instead of URL generation by string concatenation. Almost all functions are converted now. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 04282fa..4bffbf2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -204,7 +204,9 @@ sub href(%) { my $href = "$my_uri?"; $href .= esc_param( join(";", - map { "$mapping{$_}=$params{$_}" } keys %params + map { + "$mapping{$_}=$params{$_}" if defined $params{$_} + } keys %params ) ); return $href; @@ -1058,7 +1060,7 @@ sub git_footer_html { } print $cgi->a({-href => href(action=>"rss"), -class => "rss_logo"}, "RSS") . "\n"; } else { - print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; + print $cgi->a({-href => href(action=>"opml"), -class => "rss_logo"}, "OPML") . "\n"; } print "\n" . "\n" . @@ -1263,7 +1265,7 @@ sub git_difftree_body { "" . $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, "blob"); if ($to_id ne $from_id) { # modified - print $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$file)}, "diff"); + print " | " . $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$file)}, "diff"); } print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash, file_name=>$file)}, "history") . "\n"; print "\n"; @@ -1674,16 +1676,16 @@ sub git_project_list { print "\n"; } $alternate ^= 1; - print "" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), + print "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list"}, esc_html($pr->{'path'})) . "\n" . "" . esc_html($pr->{'descr'}) . "\n" . "" . chop_str($pr->{'owner'}, 15) . "\n"; print "{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . "\n" . "\n"; } @@ -2051,11 +2053,11 @@ sub git_tree { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $hash_base); git_header_html(); - my $base_key = ""; + my %base_key = (); my $base = ""; my $have_blame = git_get_project_config_bool ('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { - $base_key = ";hb=$hash_base"; + $base_key{hash_base} = $hash_base; git_print_page_nav('tree','', $hash_base); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { @@ -2086,23 +2088,23 @@ sub git_tree { print "" . mode_str($t_mode) . "\n"; if ($t_type eq "blob") { print "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name"), -class => "list"}, esc_html($t_name)) . + $cgi->a({-href => href(action=>"blob", hash=>$t_hash, file_name=>"$base$t_name", %base_key), -class => "list"}, esc_html($t_name)) . "\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob"); + $cgi->a({-href => href(action=>"blob", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, "blob"); if ($have_blame) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame"); + print " | " . $cgi->a({-href => href(action=>"blame", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, "blame"); } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") . + print " | " . $cgi->a({-href => href(action=>"history", hash=>$t_hash, hash_base=>$hash_base, file_name=>"$base$t_name")}, "history") . + " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$t_hash, file_name=>"$base$t_name")}, "raw") . "\n"; } elsif ($t_type eq "tree") { print "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, esc_html($t_name)) . + $cgi->a({-href => href(action=>"tree", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, esc_html($t_name)) . "\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") . + $cgi->a({-href => href(action=>"tree", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, "tree") . + " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, file_name=>"$base$t_name")}, "history") . "\n"; } print "\n"; @@ -2291,7 +2293,7 @@ sub git_blobdiff { git_header_html(); if (defined $hash_base && (my %co = parse_commit($hash_base))) { my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + $cgi->a({-href => href(action=>"blobdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, "plain"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { @@ -2302,9 +2304,9 @@ sub git_blobdiff { git_print_page_path($file_name, "blob"); print "
\n" . "
blob:" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name")}, $hash_parent) . + $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, hash_base=>$hash_base, file_name=>$file_name)}, $hash_parent) . " -> blob:" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) . + $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, $hash) . "
\n"; git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash); print "
"; @@ -2339,7 +2341,7 @@ sub git_commitdiff { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + $cgi->a({-href => href(action=>"commitdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, "plain"); git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); @@ -2382,22 +2384,22 @@ sub git_commitdiff { my $file = validate_input(unquote($6)); if ($status eq "A") { print "
" . file_type($to_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, $to_id) . "(new)" . "
\n"; git_diff_print(undef, "/dev/null", $to_id, "b/$file"); } elsif ($status eq "D") { print "
" . file_type($from_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . "(deleted)" . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$hash_parent, file_name=>$file)}, $from_id) . "(deleted)" . "
\n"; git_diff_print($from_id, "a/$file", undef, "/dev/null"); } elsif ($status eq "M") { if ($from_id ne $to_id) { print "
" . file_type($from_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$hash_parent, file_name=>$file)}, $from_id) . " -> " . file_type($to_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, $to_id) . print "
\n"; git_diff_print($from_id, "a/$file", $to_id, "b/$file"); } @@ -2559,7 +2561,7 @@ sub git_search { print "$co{'age_string_date'}\n" . "" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "" . esc_html(chop_str($co{'title'}, 50)) . "
"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}) -class => "list"}, "" . esc_html(chop_str($co{'title'}, 50)) . "
"); my $comment = $co{'comment'}; foreach my $line (@$comment) { if ($line =~ m/^(.*)($searchtext)(.*)$/i) { @@ -2574,8 +2576,8 @@ sub git_search { } print "\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . + " | " . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); print "\n" . "\n"; } @@ -2612,18 +2614,18 @@ sub git_search { print "$co{'age_string_date'}\n" . "" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list"}, "" . esc_html(chop_str($co{'title'}, 50)) . "
"); while (my $setref = shift @files) { my %set = %$setref; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"}, + print $cgi->a({-href => href(action=>"blob", hash=>$set{'id'}, hash_base=>$co{'id'}, file_name=>$set{'file'}), class => "list"}, "" . esc_html($set{'file'}) . "") . "
\n"; } print "\n" . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . + " | " . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); print "\n" . "\n"; } @@ -2656,7 +2658,7 @@ sub git_shortlog { my $next_link = ''; if ($#revlist >= (100 * ($page+1)-1)) { $next_link = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), + $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1), -title => "Alt-n"}, "next"); } -- cgit v0.10.2-6-g49f6 From 5c95fab017681f6ab21bf82c195bf0a3826fe014 Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Thu, 17 Aug 2006 00:28:38 +0200 Subject: gitweb: support for "fp" parameter. The "fp" (file name parent) parameter was previously generated for blob diffs of renamed files. However, it was not used in any code. Now href() can generate "fp" parameters and they are used by the blobdiff code to show the correct file name. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 4bffbf2..4328579 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -119,6 +119,13 @@ if (defined $file_name) { } } +our $file_parent = $cgi->param('fp'); +if (defined $file_parent) { + if (!validate_input($file_parent)) { + die_error(undef, "Invalid file parent parameter"); + } +} + our $hash = $cgi->param('h'); if (defined $hash) { if (!validate_input($hash)) { @@ -192,6 +199,7 @@ sub href(%) { action => "a", project => "p", file_name => "f", + file_parent => "fp", hash => "h", hash_parent => "hp", hash_base => "hb", @@ -1287,8 +1295,7 @@ sub git_difftree_body { $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file)}, "blob"); if ($to_id ne $from_id) { print " | " . - $cgi->a({-href => "$my_uri?" . - esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file;fp=$from_file")}, "diff"); + $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$to_file, file_parent=>$from_file)}, "diff"); } print "\n"; @@ -1309,8 +1316,7 @@ sub git_difftree_body { $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file)}, "blob"); if ($to_id ne $from_id) { print " | " . - $cgi->a({-href => "$my_uri?" . - esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file;fp=$from_file")}, "diff"); + $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$to_file, file_parent=>$from_file)}, "diff"); } print "\n"; } # we should not encounter Unmerged (U) or Unknown (X) status @@ -2304,7 +2310,7 @@ sub git_blobdiff { git_print_page_path($file_name, "blob"); print "
\n" . "
blob:" . - $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, hash_base=>$hash_base, file_name=>$file_name)}, $hash_parent) . + $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, hash_base=>$hash_base, file_name=>($file_parent || $file_name))}, $hash_parent) . " -> blob:" . $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, $hash) . "
\n"; -- cgit v0.10.2-6-g49f6 From 6132b7e4bbdbca14aff72d87784909810edefb82 Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Thu, 17 Aug 2006 00:28:39 +0200 Subject: gitweb: support for / as home_link. If the webserver is configured to use gitweb even for the root directory of the site, then my_uri is empty which leads to a non-functional home link. Fix that by defaulting to "/" in this case. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 4328579..0dd2467 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -34,7 +34,7 @@ our $projectroot = "++GITWEB_PROJECTROOT++"; our $git_temp = "/tmp/gitweb"; # target of the home link on top of all pages -our $home_link = $my_uri; +our $home_link = $my_uri || "/"; # string of the home link on top of all pages our $home_link_str = "++GITWEB_HOME_LINK_STR++"; -- cgit v0.10.2-6-g49f6 From 13d02165042c6139905dad38ee8324780d771d99 Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Thu, 17 Aug 2006 00:28:40 +0200 Subject: gitweb: fix project list if PATH_INFO=="/". The project list now uses several common header / footer generation functions. These functions only check for "defined $project", but when PATH_INFO just contains a "/" (which is often generated by web servers), then this test fails. Now explicitly undef $project if there is none so that the tests in other gitweb parts work again. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0dd2467..cd9395d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -95,8 +95,9 @@ our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); if (defined $project) { $project =~ s|^/||; $project =~ s|/$||; + $project = undef unless $project; } -if (defined $project && $project) { +if (defined $project) { if (!validate_input($project)) { die_error(undef, "Invalid project parameter"); } -- cgit v0.10.2-6-g49f6 From d16d093c2dcdd31b71b278591db18a9b0ccdbe90 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 17 Aug 2006 11:21:23 +0200 Subject: gitweb: Refactor printing commit message Separate pretty-printing commit message (comment) into git_print_log and git_print_simplified_log subroutines. As of now the former is used in git_commit, the latter in git_log and git_commitdiff. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index cd9395d..36d3082 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1186,6 +1186,66 @@ sub git_print_page_path { } } +sub git_print_log { + my $log = shift; + + # remove leading empty lines + while (defined $log->[0] && $log->[0] eq "") { + shift @$log; + } + + # print log + my $signoff = 0; + my $empty = 0; + foreach my $line (@$log) { + # print only one empty line + # do not print empty line after signoff + if ($line eq "") { + next if ($empty || $signoff); + $empty = 1; + } else { + $empty = 0; + } + if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { + $signoff = 1; + print "" . esc_html($line) . "
\n"; + } else { + $signoff = 0; + print format_log_line_html($line) . "
\n"; + } + } +} + +sub git_print_simplified_log { + my $log = shift; + my $remove_title = shift; + + shift @$log if $remove_title; + # remove leading empty lines + while (defined $log->[0] && $log->[0] eq "") { + shift @$log; + } + + # simplify and print log + my $empty = 0; + foreach my $line (@$log) { + # remove signoff lines + if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { + next; + } + # print only one empty line + if ($line eq "") { + next if $empty; + $empty = 1; + } else { + $empty = 0; + } + print format_log_line_html($line) . "
\n"; + } + # end with single empty line + print "
\n" unless $empty; +} + ## ...................................................................... ## functions printing large fragments of HTML @@ -2165,27 +2225,10 @@ sub git_log { "
\n" . "
\n" . "" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]
\n" . - "\n" . - "
\n"; - my $comment = $co{'comment'}; - my $empty = 0; - foreach my $line (@$comment) { - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - next; - } - if ($line eq "") { - if ($empty) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - print format_log_line_html($line) . "
\n"; - } - if (!$empty) { - print "
\n"; - } + "
\n"; + + print "
\n"; + git_print_simplified_log($co{'comment'}); print "
\n"; } git_footer_html(); @@ -2266,28 +2309,9 @@ sub git_commit { } print "". "\n"; + print "
\n"; - my $comment = $co{'comment'}; - my $empty = 0; - my $signed = 0; - foreach my $line (@$comment) { - # print only one empty line - if ($line eq "") { - if ($empty || $signed) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - $signed = 1; - print "" . esc_html($line) . "
\n"; - } else { - $signed = 0; - print format_log_line_html($line) . "
\n"; - } - } + git_print_log($co{'comment'}); print "
\n"; git_difftree_body(\@difftree, $parent); @@ -2353,29 +2377,7 @@ sub git_commitdiff { git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); print "
\n"; - my $comment = $co{'comment'}; - my $empty = 0; - my $signed = 0; - my @log = @$comment; - # remove first and empty lines after that - shift @log; - while (defined $log[0] && $log[0] eq "") { - shift @log; - } - foreach my $line (@log) { - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - next; - } - if ($line eq "") { - if ($empty) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - print format_log_line_html($line) . "
\n"; - } + git_print_simplified_log($co{'comment'}, 1); # skip title print "
\n"; foreach my $line (@difftree) { # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' -- cgit v0.10.2-6-g49f6 From cb9c6e5b0a3772a979aff10f58a2b7fcd0fbe7d9 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Thu, 17 Aug 2006 20:59:46 +0530 Subject: gitweb: Support for snapshot This adds snapshort support in gitweb. To enable one need to set gitweb.snapshot = true in the config file. Signed-off-by: Aneesh Kumar K.V Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 36d3082..941dce0 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -15,6 +15,7 @@ use CGI::Carp qw(fatalsToBrowser); use Encode; use Fcntl ':mode'; use File::Find qw(); +use File::Basename qw(basename); binmode STDOUT, ':utf8'; our $cgi = new CGI; @@ -183,6 +184,7 @@ my %actions = ( "tag" => \&git_tag, "tags" => \&git_tags, "tree" => \&git_tree, + "snapshot" => \&git_snapshot, ); $action = 'summary' if (!defined($action)); @@ -1389,6 +1391,7 @@ sub git_difftree_body { sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; + my $have_snapshot = git_get_project_config_bool('snapshot'); $from = 0 unless defined $from; $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); @@ -1413,8 +1416,11 @@ sub git_shortlog_body { print "\n" . "" . $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . - $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . - "\n" . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); + if ($have_snapshot) { + print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + } + print "\n" . "\n"; } if (defined $extra) { @@ -2181,6 +2187,29 @@ sub git_tree { git_footer_html(); } +sub git_snapshot { + + if (!defined $hash) { + $hash = git_get_head_hash($project); + } + + my $filename = basename($project) . "-$hash.tar.gz"; + + print $cgi->header(-type => 'application/x-tar', + -content-encoding => 'gzip', + '-content-disposition' => "inline; filename=\"$filename\"", + -status => '200 OK'); + + open my $fd, "-|", "$GIT tar-tree $hash \'$project\' | gzip" or + die_error(undef, "Execute git-tar-tree failed."); + binmode STDOUT, ':raw'; + print <$fd>; + binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi + close $fd; + + +} + sub git_log { my $head = git_get_head_hash($project); if (!defined $hash) { @@ -2258,6 +2287,7 @@ sub git_commit { } my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); + my $have_snapshot = git_get_project_config_bool('snapshot'); my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; @@ -2293,8 +2323,11 @@ sub git_commit { "" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash), class => "list"}, $co{'tree'}) . "" . - "" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, "tree") . - "" . + "" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, "tree"); + if ($have_snapshot) { + print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot"); + } + print "" . "\n"; my $parents = $co{'parents'}; foreach my $par (@$parents) { -- cgit v0.10.2-6-g49f6 From 3899e7a329aabfc22eca9beb82599e1bb214b3d2 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Thu, 17 Aug 2006 13:52:09 -0700 Subject: gitweb: bugfix: commitdiff regression Fix regression in git_commitdiff() introduced by commit 756d2f064b2419fcdf9cd9c851f352e2a4f75103. Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 941dce0..18ba4b0 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2441,7 +2441,7 @@ sub git_commitdiff { $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$hash_parent, file_name=>$file)}, $from_id) . " -> " . file_type($to_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, $to_id) . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, $to_id); print "
\n"; git_diff_print($from_id, "a/$file", $to_id, "b/$file"); } -- cgit v0.10.2-6-g49f6 From 59fb1c944551083bbd4bccd1b5783488745f6bc4 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Thu, 17 Aug 2006 10:39:29 -0700 Subject: gitweb: bugfix: git_print_page_path() needs the hash base If a file F exists in branch B, but doesn't exist in master branch, then blob_plain needs the hash base in order to properly get the file. The hash base is passed on symbolically so we still preserve the "latest" quality of the link presented by git_print_page_path(). Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 18ba4b0..f7c0418 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1177,12 +1177,18 @@ sub git_print_header_div { sub git_print_page_path { my $name = shift; my $type = shift; + my $hb = shift; if (!defined $name) { print "
/
\n"; } elsif (defined $type && $type eq 'blob') { - print "
" . - $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name)}, esc_html($name)) . "
\n"; + print "
"; + if (defined $hb) { + print $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hb, file_name=>$file_name)}, esc_html($name)); + } else { + print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name)}, esc_html($name)); + } + print "
\n"; } else { print "
" . esc_html($name) . "
\n"; } @@ -1874,7 +1880,7 @@ sub git_blame2 { " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name, $ftype); + git_print_page_path($file_name, $ftype, $hash_base); my @rev_color = (qw(light2 dark2)); my $num_colors = scalar(@rev_color); my $current_color = 0; @@ -1928,7 +1934,7 @@ sub git_blame { " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name, 'blob'); + git_print_page_path($file_name, 'blob', $hash_base); print "
\n"; print < @@ -2091,7 +2097,7 @@ sub git_blob { "

\n" . "
$hash
\n"; } - git_print_page_path($file_name, "blob"); + git_print_page_path($file_name, "blob", $hash_base); print "
\n"; my $nr; while (my $line = <$fd>) { @@ -2141,7 +2147,7 @@ sub git_tree { if (defined $file_name) { $base = esc_html("$file_name/"); } - git_print_page_path($file_name, 'tree'); + git_print_page_path($file_name, 'tree', $hash_base); print "
\n"; print "\n"; my $alternate = 0; @@ -2365,7 +2371,7 @@ sub git_blobdiff { "

\n" . "
$hash vs $hash_parent
\n"; } - git_print_page_path($file_name, "blob"); + git_print_page_path($file_name, "blob", $hash_base); print "
\n" . "
blob:" . $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, hash_base=>$hash_base, file_name=>($file_parent || $file_name))}, $hash_parent) . @@ -2535,7 +2541,7 @@ sub git_history { if (defined $hash) { $ftype = git_get_type($hash); } - git_print_page_path($file_name, $ftype); + git_print_page_path($file_name, $ftype, $hash_base); open my $fd, "-|", $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; -- cgit v0.10.2-6-g49f6 From f0321866be971bf7dda2464d143e5a156182eba8 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Thu, 17 Aug 2006 22:56:23 -0700 Subject: gitweb: fix snapshot support [jc: when I applied the patch I misread RFC 2616 which mildly recommended against using the name "gzip", which was there only for a historical reason. This fixes the mistake up and uses the content-encoding "x-gzip" again.] Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index f7c0418..f8d1036 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2202,7 +2202,7 @@ sub git_snapshot { my $filename = basename($project) . "-$hash.tar.gz"; print $cgi->header(-type => 'application/x-tar', - -content-encoding => 'gzip', + -content-encoding => 'x-gzip', '-content-disposition' => "inline; filename=\"$filename\"", -status => '200 OK'); -- cgit v0.10.2-6-g49f6 From 82e2765f59126f96da6e5dc30adf7c6189e9697d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 18 Aug 2006 03:10:19 -0700 Subject: git-apply --reject: send rejects to .rej files. ... just like everybody else does, instead of sending it to the standard output, which was just silly. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 7dea913..668be9c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2242,36 +2242,61 @@ static void write_out_one_result(struct patch *patch, int phase) static int write_out_one_reject(struct patch *patch) { + FILE *rej; + char namebuf[PATH_MAX]; struct fragment *frag; - int rejects = 0; + int cnt = 0; - for (rejects = 0, frag = patch->fragments; frag; frag = frag->next) { + for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { if (!frag->rejected) continue; - if (rejects == 0) { - rejects = 1; - printf("** Rejected hunk(s) for "); - if (patch->old_name && patch->new_name && - strcmp(patch->old_name, patch->new_name)) { - write_name_quoted(NULL, 0, - patch->old_name, 1, stdout); - fputs(" => ", stdout); - write_name_quoted(NULL, 0, - patch->new_name, 1, stdout); - } - else { - const char *n = patch->new_name; - if (!n) - n = patch->old_name; - write_name_quoted(NULL, 0, n, 1, stdout); - } - printf(" **\n"); + cnt++; + } + + if (!cnt) + return 0; + + /* This should not happen, because a removal patch that leaves + * contents are marked "rejected" at the patch level. + */ + if (!patch->new_name) + die("internal error"); + + cnt = strlen(patch->new_name); + if (ARRAY_SIZE(namebuf) <= cnt + 5) { + cnt = ARRAY_SIZE(namebuf) - 5; + fprintf(stderr, + "warning: truncating .rej filename to %.*s.rej", + cnt - 1, patch->new_name); + } + memcpy(namebuf, patch->new_name, cnt); + memcpy(namebuf + cnt, ".rej", 5); + + rej = fopen(namebuf, "w"); + if (!rej) + return error("cannot open %s: %s", namebuf, strerror(errno)); + + /* Normal git tools never deal with .rej, so do not pretend + * this is a git patch by saying --git nor give extended + * headers. While at it, maybe please "kompare" that wants + * the trailing TAB and some garbage at the end of line ;-). + */ + fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", + patch->new_name, patch->new_name); + for (cnt = 0, frag = patch->fragments; + frag; + cnt++, frag = frag->next) { + if (!frag->rejected) { + fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt); + continue; } - printf("%.*s", frag->size, frag->patch); + fprintf(stderr, "Rejected hunk #%d.\n", cnt); + fprintf(rej, "%.*s", frag->size, frag->patch); if (frag->patch[frag->size-1] != '\n') - putchar('\n'); + fputc('\n', rej); } - return rejects; + fclose(rej); + return -1; } static int write_out_results(struct patch *list, int skipped_patch) @@ -2288,16 +2313,9 @@ static int write_out_results(struct patch *list, int skipped_patch) while (l) { if (l->rejected) errs = 1; - else + else { write_out_one_result(l, phase); - l = l->next; - } - } - if (apply_with_reject) { - l = list; - while (l) { - if (!l->rejected) { - if (write_out_one_reject(l)) + if (phase == 1 && write_out_one_reject(l)) errs = 1; } l = l->next; diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh index 3362819..1cf9a2e 100755 --- a/t/t4117-apply-reject.sh +++ b/t/t4117-apply-reject.sh @@ -23,6 +23,12 @@ test_expect_success setup ' echo $i done >file1 && git diff >patch.1 && + cat file1 >clean && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >expected && mv file1 file2 && git update-index --add --remove file1 file2 && @@ -53,25 +59,30 @@ test_expect_success 'apply without --reject should fail' ' test_expect_success 'apply with --reject should fail but update the file' ' - cat saved.file1 >file1 + cat saved.file1 >file1 && + rm -f file1.rej file2.rej && - if git apply --reject patch.1 >rejects + if git apply --reject patch.1 then echo "succeeds with --reject?" exit 1 fi - cat rejects - for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 - do - echo $i - done >expected.file1 && - diff -u file1 expected.file1 + diff -u file1 expected && + + cat file1.rej && + + if test -f file2.rej + then + echo "file2 should not have been touched" + exit 1 + fi ' test_expect_success 'apply with --reject should fail but update the file' ' - cat saved.file1 >file1 + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && if git apply --reject patch.2 >rejects then @@ -79,18 +90,20 @@ test_expect_success 'apply with --reject should fail but update the file' ' exit 1 fi - cat rejects - - for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 - do - echo $i - done >expected.file2 && - test -f file1 && { echo "file1 still exists?" exit 1 } - diff -u file2 expected.file2 + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + ' test_done -- cgit v0.10.2-6-g49f6 From a2bf404e280b8d56d0efa15bd9700464cf8f0d4d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 18 Aug 2006 03:14:48 -0700 Subject: git-apply --verbose Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 668be9c..42253e6 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -39,12 +39,13 @@ static int check; static int apply = 1; static int apply_in_reverse; static int apply_with_reject; +static int apply_verbosely; static int no_add; static int show_index_info; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; static enum whitespace_eol { nowarn_whitespace, @@ -153,6 +154,24 @@ struct patch { struct patch *next; }; +static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post) +{ + fputs(pre, output); + if (patch->old_name && patch->new_name && + strcmp(patch->old_name, patch->new_name)) { + write_name_quoted(NULL, 0, patch->old_name, 1, output); + fputs(" => ", output); + write_name_quoted(NULL, 0, patch->new_name, 1, output); + } + else { + const char *n = patch->new_name; + if (!n) + n = patch->old_name; + write_name_quoted(NULL, 0, n, 1, output); + } + fputs(post, output); +} + #define CHUNKSIZE (8192) #define SLOP (16) @@ -1928,6 +1947,9 @@ static int check_patch_list(struct patch *patch) int error = 0; for (prev_patch = NULL; patch ; patch = patch->next) { + if (apply_verbosely) + say_patch_name(stderr, + "Checking patch ", patch, "...\n"); error |= check_patch(patch, prev_patch); prev_patch = patch; } @@ -2253,8 +2275,12 @@ static int write_out_one_reject(struct patch *patch) cnt++; } - if (!cnt) + if (!cnt) { + if (apply_verbosely) + say_patch_name(stderr, + "Applied patch ", patch, " cleanly.\n"); return 0; + } /* This should not happen, because a removal patch that leaves * contents are marked "rejected" at the patch level. @@ -2262,6 +2288,10 @@ static int write_out_one_reject(struct patch *patch) if (!patch->new_name) die("internal error"); + /* Say this even without --verbose */ + say_patch_name(stderr, "Applying patch ", patch, " with"); + fprintf(stderr, " %d rejects...\n", cnt); + cnt = strlen(patch->new_name); if (ARRAY_SIZE(namebuf) <= cnt + 5) { cnt = ARRAY_SIZE(namebuf) - 5; @@ -2530,6 +2560,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix) apply = apply_with_reject = 1; continue; } + if (!strcmp(arg, "--verbose")) { + apply_verbosely = 1; + continue; + } if (!strcmp(arg, "--inaccurate-eof")) { inaccurate_eof = 1; continue; diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh index 1cf9a2e..b4de075 100755 --- a/t/t4117-apply-reject.sh +++ b/t/t4117-apply-reject.sh @@ -57,6 +57,17 @@ test_expect_success 'apply without --reject should fail' ' diff -u file1 saved.file1 ' +test_expect_success 'apply without --reject should fail' ' + + if git apply --verbose patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + test_expect_success 'apply with --reject should fail but update the file' ' cat saved.file1 >file1 && @@ -106,4 +117,41 @@ test_expect_success 'apply with --reject should fail but update the file' ' ' +test_expect_success 'the same test with --verbose' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject --verbose patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'apply cleanly with --verbose' ' + + git cat-file -p HEAD:file1 >file1 && + rm -f file?.rej file2 && + + git apply --verbose patch.1 && + + diff -u file1 clean +' + test_done -- cgit v0.10.2-6-g49f6 From ddb8d900489216a0bf821a26901a21f89571a1c3 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Sun, 20 Aug 2006 11:53:04 +0530 Subject: gitweb: Make blame and snapshot a feature. This adds blame and snapshot to the feature associative array. This also helps in enabling or disabling these features via GITWEB_CONFIG and overriding if allowed via project specfic config. Signed-off-by: Aneesh Kumar K.V Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index f8d1036..063735d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -67,6 +67,68 @@ our $default_text_plain_charset = undef; # (relative to the current git repository) our $mimetypes_file = undef; +# You define site-wide feature defaults here; override them with +# $GITWEB_CONFIG as necessary. +our %feature = +( + +# feature => {'sub' => feature-sub, 'override' => allow-override, 'default' => [ default options...] + +'blame' => {'sub' => \&feature_blame, 'override' => 0, 'default' => [0]}, +'snapshot' => {'sub' => \&feature_snapshot, 'override' => 0, 'default' => ['x-gzip', 'gz', 'gzip']}, + +); + +sub gitweb_check_feature { + my ($name) = @_; + return undef unless exists $feature{$name}; + my ($sub, $override, @defaults) = ($feature{$name}{'sub'}, + $feature{$name}{'override'}, + @{$feature{$name}{'default'}}); + if (!$override) { return @defaults; } + return $sub->(@defaults); +} + +# To enable system wide have in $GITWEB_CONFIG +# $feature{'blame'}{'default'} = [1]; +# To have project specific config enable override in $GITWEB_CONFIG +# $feature{'blame'}{'override'} = 1; +# and in project config gitweb.blame = 0|1; + +sub feature_blame { + my ($val) = git_get_project_config('blame', '--bool'); + + if ($val eq 'true') { + return 1; + } elsif ($val eq 'false') { + return 0; + } + + return $_[0]; +} + +# To disable system wide have in $GITWEB_CONFIG +# $feature{'snapshot'}{'default'} = [undef]; +# To have project specific config enable override in $GITWEB_CONFIG +# $feature{'blame'}{'override'} = 1; +# and in project config gitweb.snapshot = none|gzip|bzip2 + +sub feature_snapshot { + my ($ctype, $suffix, $command) = @_; + + my ($val) = git_get_project_config('snapshot'); + + if ($val eq 'gzip') { + return ('x-gzip', 'gz', 'gzip'); + } elsif ($val eq 'bzip2') { + return ('x-bzip2', 'bz2', 'bzip2'); + } elsif ($val eq 'none') { + return (); + } + + return ($ctype, $suffix, $command); +} + our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; require $GITWEB_CONFIG if -e $GITWEB_CONFIG; @@ -485,24 +547,21 @@ sub git_get_type { } sub git_get_project_config { - my $key = shift; + my ($key, $type) = @_; return unless ($key); $key =~ s/^gitweb\.//; return if ($key =~ m/\W/); - my $val = qx($GIT repo-config --get gitweb.$key); + my @x = ($GIT, 'repo-config'); + if (defined $type) { push @x, $type; } + push @x, "--get"; + push @x, "gitweb.$key"; + my $val = qx(@x); + chomp $val; return ($val); } -sub git_get_project_config_bool { - my $val = git_get_project_config (@_); - if ($val and $val =~ m/true|yes|on/) { - return (1); - } - return; # implicit false -} - # get hash of given path at given ref sub git_get_hash_by_path { my $base = shift; @@ -1397,7 +1456,10 @@ sub git_difftree_body { sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; - my $have_snapshot = git_get_project_config_bool('snapshot'); + + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + $from = 0 unless defined $from; $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); @@ -1858,7 +1920,10 @@ sub git_tag { sub git_blame2 { my $fd; my $ftype; - die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame')); + + if (!gitweb_check_feature('blame')) { + die_error('403 Permission denied', "Permission denied"); + } die_error('404 Not Found', "File name not defined") if (!$file_name); $hash_base ||= git_get_head_hash($project); die_error(undef, "Couldn't find base commit") unless ($hash_base); @@ -1916,7 +1981,10 @@ sub git_blame2 { sub git_blame { my $fd; - die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame')); + + if (!gitweb_check_feature('blame')) { + die_error('403 Permission denied', "Permission denied"); + } die_error('404 Not Found', "File name not defined") if (!$file_name); $hash_base ||= git_get_head_hash($project); die_error(undef, "Couldn't find base commit") unless ($hash_base); @@ -2069,7 +2137,7 @@ sub git_blob { die_error(undef, "No file name defined"); } } - my $have_blame = git_get_project_config_bool ('blame'); + my $have_blame = gitweb_check_feature('blame'); open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); my $mimetype = blob_mimetype($fd, $file_name); @@ -2134,7 +2202,7 @@ sub git_tree { git_header_html(); my %base_key = (); my $base = ""; - my $have_blame = git_get_project_config_bool ('blame'); + my $have_blame = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { $base_key{hash_base} = $hash_base; git_print_page_nav('tree','', $hash_base); @@ -2195,25 +2263,31 @@ sub git_tree { sub git_snapshot { + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + if (!$have_snapshot) { + die_error('403 Permission denied', "Permission denied"); + } + if (!defined $hash) { $hash = git_get_head_hash($project); } - my $filename = basename($project) . "-$hash.tar.gz"; + my $filename = basename($project) . "-$hash.tar.$suffix"; print $cgi->header(-type => 'application/x-tar', - -content-encoding => 'x-gzip', - '-content-disposition' => "inline; filename=\"$filename\"", - -status => '200 OK'); + -content-encoding => $ctype, + '-content-disposition' => + "inline; filename=\"$filename\"", + -status => '200 OK'); - open my $fd, "-|", "$GIT tar-tree $hash \'$project\' | gzip" or - die_error(undef, "Execute git-tar-tree failed."); + open my $fd, "-|", "$GIT tar-tree $hash \'$project\' | $command" or + die_error(undef, "Execute git-tar-tree failed."); binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi close $fd; - } sub git_log { @@ -2293,7 +2367,10 @@ sub git_commit { } my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); - my $have_snapshot = git_get_project_config_bool('snapshot'); + + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; -- cgit v0.10.2-6-g49f6 From 740e67f903ce738b25876f6d60faaff21f70fb4a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 21 Aug 2006 23:07:00 +0200 Subject: gitweb: Added parse_difftree_raw_line function for later use Adds parse_difftree_raw_line function which parses one line of "raw" format diff-tree output into a hash. For later use in git_difftree_body, git_commitdiff and git_commitdiff_plain, git_search. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 063735d..824fc53 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -918,6 +918,33 @@ sub parse_ref { return %ref_item; } +sub parse_difftree_raw_line { + my $line = shift; + my %res; + + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { + $res{'from_mode'} = $1; + $res{'to_mode'} = $2; + $res{'from_id'} = $3; + $res{'to_id'} = $4; + $res{'status'} = $5; + $res{'similarity'} = $6; + if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied + ($res{'from_file'}, $res{'to_file'}) = map(unquote, split("\t", $7)); + } else { + $res{'file'} = unquote($7); + } + } + # 'c512b523472485aef4fff9e57b229d9d243c967f' + #elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { + # $res{'commit'} = $1; + #} + + return wantarray ? %res : \%res; +} + ## ...................................................................... ## parse to array of hashes functions -- cgit v0.10.2-6-g49f6 From e8e41a9383ff8e1fb22a7abb899b16486ad25fe1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 21 Aug 2006 23:08:52 +0200 Subject: gitweb: Use parse_difftree_raw_line in git_difftree_body Use newly introduced parse_difftree_raw_line function in the git_difftree_body subroutine. While at it correct error in parse_difftree_raw_line (unquote is unprototyped function), and add comment explaining this function. It also refactors git_difftree_body somewhat, and tries to fit it in 80 columns. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 824fc53..31a1824 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -918,6 +918,7 @@ sub parse_ref { return %ref_item; } +# parse line of git-diff-tree "raw" output sub parse_difftree_raw_line { my $line = shift; my %res; @@ -932,7 +933,7 @@ sub parse_difftree_raw_line { $res{'status'} = $5; $res{'similarity'} = $6; if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied - ($res{'from_file'}, $res{'to_file'}) = map(unquote, split("\t", $7)); + ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7); } else { $res{'file'} = unquote($7); } @@ -1355,18 +1356,7 @@ sub git_difftree_body { print "
\n"; my $alternate = 0; foreach my $line (@{$difftree}) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { - next; - } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $similarity = $6; # score - my $file = validate_input(unquote($7)); + my %diff = parse_difftree_raw_line($line); if ($alternate) { print "\n"; @@ -1375,105 +1365,131 @@ sub git_difftree_body { } $alternate ^= 1; - if ($status eq "A") { # created - my $mode_chng = ""; - if (S_ISREG(oct $to_mode)) { - $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); + my ($to_mode_oct, $to_mode_str, $to_file_type); + my ($from_mode_oct, $from_mode_str, $from_file_type); + if ($diff{'to_mode'} ne ('0' x 6)) { + $to_mode_oct = oct $diff{'to_mode'}; + if (S_ISREG($to_mode_oct)) { # only for regular file + $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits } + $to_file_type = file_type($diff{'to_mode'}); + } + if ($diff{'from_mode'} ne ('0' x 6)) { + $from_mode_oct = oct $diff{'from_mode'}; + if (S_ISREG($to_mode_oct)) { # only for regular file + $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits + } + $from_file_type = file_type($diff{'from_mode'}); + } + + if ($diff{'status'} eq "A") { # created + my $mode_chng = "[new $to_file_type"; + $mode_chng .= " with mode: $to_mode_str" if $to_mode_str; + $mode_chng .= "]"; print "\n" . - "\n" . + "\n" . "\n"; - } elsif ($status eq "D") { # deleted + } elsif ($diff{'status'} eq "D") { # deleted + my $mode_chng = "[deleted $from_file_type]"; print "\n" . - "\n" . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + hash_base=>$parent, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})) . + "\n" . + "\n" . "\n" + $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + hash_base=>$parent, file_name=>$diff{'file'})}, + "blob") . + " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$parent, + file_name=>$diff{'file'})},\ + "history") . + "\n"; - } elsif ($status eq "M" || $status eq "T") { # modified, or type changed + } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed my $mode_chnge = ""; - if ($from_mode != $to_mode) { - $mode_chnge = " [changed"; - if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { - $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); + if ($diff{'from_mode'} != $diff{'to_mode'}) { + $mode_chnge = "[changed"; + if ($from_file_type != $to_file_type) { + $mode_chnge .= " from $from_file_type to $to_file_type"; } - if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { - if (S_ISREG($from_mode) && S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); - } elsif (S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); + if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) { + if ($from_mode_str && $to_mode_str) { + $mode_chnge .= " mode: $from_mode_str->$to_mode_str"; + } elsif ($to_mode_str) { + $mode_chnge .= " mode: $to_mode_str"; } } $mode_chnge .= "]\n"; } print "\n" . "\n" . "\n"; - - } elsif ($status eq "R") { # renamed - my ($from_file, $to_file) = split "\t", $file; - my $mode_chng = ""; - if ($from_mode != $to_mode) { - $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); - } - print "\n" . - "\n" . - "\n"; - } elsif ($status eq "C") { # copied - my ($from_file, $to_file) = split "\t", $file; + } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied + my %status_name = ('R' => 'moved', 'C' => 'copied'); + my $nstatus = $status_name{$diff{'status'}}; my $mode_chng = ""; - if ($from_mode != $to_mode) { - $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); + if ($diff{'from_mode'} != $diff{'to_mode'}) { + # mode also for directories, so we cannot use $to_mode_str + $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777); } print "\n" . - "\n" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}), + -class => "list"}, esc_html($diff{'to_file'})) . "\n" . + "\n" . "\n"; + } # we should not encounter Unmerged (U) or Unknown (X) status print "\n"; } -- cgit v0.10.2-6-g49f6 From 0e9ee32358413feaa4e1cb664cb0a1e6d52f434f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 22 Aug 2006 15:49:28 -0700 Subject: apply --reject: count hunks starting from 1, not 0 Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 42253e6..a874375 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2313,7 +2313,7 @@ static int write_out_one_reject(struct patch *patch) */ fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", patch->new_name, patch->new_name); - for (cnt = 0, frag = patch->fragments; + for (cnt = 1, frag = patch->fragments; frag; cnt++, frag = frag->next) { if (!frag->rejected) { -- cgit v0.10.2-6-g49f6 From 7c278014730dc15bf03b8640b1d406c657447092 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 22 Aug 2006 12:02:48 +0200 Subject: gitweb: bugfix: a.list formatting regression Fix regression introduced by commit 17d07443188909ef5f8b8c24043cb6d9fef51bca. "a.list" being "bold", makes a myriad of things shown by gitweb in bold font-weight, which is a regression from pre-17d07443188909ef5f8b8c24043cb6d9fef51bca behavior. The fix is to add "subject" class and use this class to replace pre-format_subject_html formatting of subject (comment) via using (or not) ... element. This should go back to the pre-17d0744318... style. Regression noticed by Luben Tuikov. Signed-off-by: Jakub Narebski Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 9013895..6c13d9e 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -117,11 +117,14 @@ div.list_head { a.list { text-decoration: none; - font-weight: bold; color: #000000; } -table.tags a.list { +a.subject { + font-weight: bold; +} + +table.tags a.subject { font-weight: normal; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 31a1824..0e46977 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -504,11 +504,11 @@ sub format_subject_html { $extra = '' unless defined($extra); if (length($short) < length($long)) { - return $cgi->a({-href => $href, -class => "list", + return $cgi->a({-href => $href, -class => "list subject", -title => $long}, esc_html($short) . $extra); } else { - return $cgi->a({-href => $href, -class => "list"}, + return $cgi->a({-href => $href, -class => "list subject"}, esc_html($long) . $extra); } } -- cgit v0.10.2-6-g49f6 From 63e4220b75d4fd7c8820209193641f3207d24238 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 22 Aug 2006 12:38:59 +0200 Subject: gitweb: Replace some presentational HTML by CSS Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 6c13d9e..4821022 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -42,6 +42,7 @@ div.page_nav a:visited { div.page_path { padding: 8px; + font-weight: bold; border: solid #d9d8d1; border-width: 0px 0px 1px; } @@ -120,7 +121,7 @@ a.list { color: #000000; } -a.subject { +a.subject, a.name { font-weight: bold; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0e46977..39f719c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1267,17 +1267,17 @@ sub git_print_page_path { my $hb = shift; if (!defined $name) { - print "
/
\n"; + print "
/
\n"; } elsif (defined $type && $type eq 'blob') { - print "
"; + print "
"; if (defined $hb) { print $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hb, file_name=>$file_name)}, esc_html($name)); } else { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name)}, esc_html($name)); } - print "
\n"; + print "
\n"; } else { - print "
" . esc_html($name) . "
\n"; + print "
" . esc_html($name) . "
\n"; } } @@ -1626,7 +1626,7 @@ sub git_tags_body { print "
\n" . "\n" . "\n" . ($tag{'id'} eq $head ? "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n"; print "\n"; - print "\n"; + $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + esc_html($rev)) . "\n"; + print "\n"; print "\n"; print "\n"; } print "
" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file), - -class => "list"}, esc_html($file)) . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})) . "[new " . file_type($to_mode) . "$mode_chng]$mode_chng" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, "blob") . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob") . "" . - $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$file), - -class => "list"}, esc_html($file)) . "[deleted " . file_type($from_mode). "]$mode_chng" . - $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$file)}, "blob") . " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$file)}, "history") . - ""; - if ($to_id ne $from_id) { # modified - print $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$file), - -class => "list"}, esc_html($file)); - } else { # mode changed - print $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file), - -class => "list"}, esc_html($file)); + if ($diff{'to_id'} ne $diff{'from_id'}) { # modified + print $cgi->a({-href => href(action=>"blobdiff", hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); + } else { # only mode changed + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); } print "$mode_chnge" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, "blob"); - if ($to_id ne $from_id) { # modified - print " | " . $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$file)}, "diff"); - } - print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash, file_name=>$file)}, "history") . "\n"; - print "" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file), - -class => "list"}, esc_html($to_file)) . "[moved from " . - $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$from_file), - -class => "list"}, esc_html($from_file)) . - " with " . (int $similarity) . "% similarity$mode_chng]" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file)}, "blob"); - if ($to_id ne $from_id) { + $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob"); + if ($diff{'to_id'} ne $diff{'from_id'}) { # modified print " | " . - $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$to_file, file_parent=>$from_file)}, "diff"); + $cgi->a({-href => href(action=>"blobdiff", hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "diff"); } + print " | " . + $cgi->a({-href => href(action=>"history", + hash_base=>$hash, file_name=>$diff{'file'})}, + "history"); print "" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file), - -class => "list"}, esc_html($to_file)) . "[copied from " . - $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$from_file), - -class => "list"}, esc_html($from_file)) . - " with " . (int $similarity) . "% similarity$mode_chng][$nstatus from " . + $cgi->a({-href => href(action=>"blob", hash_base=>$parent, + hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}), + -class => "list"}, esc_html($diff{'from_file'})) . + " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file)}, "blob"); - if ($to_id ne $from_id) { + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})}, + "blob"); + if ($diff{'to_id'} ne $diff{'from_id'}) { print " | " . - $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$to_file, file_parent=>$from_file)}, "diff"); + $cgi->a({-href => href(action=>"blobdiff", hash_base=>$hash, + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff"); } print "
$tag{'age'}" . $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}), - -class => "list"}, "" . esc_html($tag{'name'}) . "") . + -class => "list name"}, esc_html($tag{'name'})) . ""; if (defined $comment) { @@ -1680,7 +1680,7 @@ sub git_heads_body { print "$tag{'age'}" : "") . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}), - -class => "list"}, "" . esc_html($tag{'name'}) . "") . + -class => "list name"},esc_html($tag{'name'})) . "" . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . @@ -2729,7 +2729,8 @@ sub git_search { print "$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}) -class => "list"}, "" . esc_html(chop_str($co{'title'}, 50)) . "
"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, + esc_html(chop_str($co{'title'}, 50)) . "
"); my $comment = $co{'comment'}; foreach my $line (@$comment) { if ($line =~ m/^(.*)($searchtext)(.*)$/i) { @@ -2782,8 +2783,8 @@ sub git_search { print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list"}, "" . - esc_html(chop_str($co{'title'}, 50)) . "
"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, + esc_html(chop_str($co{'title'}, 50)) . "
"); while (my $setref = shift @files) { my %set = %$setref; print $cgi->a({-href => href(action=>"blob", hash=>$set{'id'}, hash_base=>$co{'id'}, file_name=>$set{'file'}), class => "list"}, -- cgit v0.10.2-6-g49f6 From 952c65fc0201c3d506f7c4d7377ec2d8bb346d4b Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 22 Aug 2006 16:52:50 +0200 Subject: gitweb: Whitespace cleanup: realign, reindent This patch tries (but no too hard) to fit gitweb source in 80 columns, for 2 columns wide tabs, and indent and align source for better readibility. While at it added comment to 'snapshot' entry defaults for %feature hash, corrected "blobl" action in git_blame2 and git_blame to "blob", key of argument to $cgi->a from 'class' to '-class'. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 39f719c..63dc477 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -69,22 +69,30 @@ our $mimetypes_file = undef; # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. -our %feature = -( - -# feature => {'sub' => feature-sub, 'override' => allow-override, 'default' => [ default options...] - -'blame' => {'sub' => \&feature_blame, 'override' => 0, 'default' => [0]}, -'snapshot' => {'sub' => \&feature_snapshot, 'override' => 0, 'default' => ['x-gzip', 'gz', 'gzip']}, - +our %feature = ( + # feature => {'sub' => feature-sub, 'override' => allow-override, 'default' => [ default options...] + # if feature is overridable, feature-sub will be called with default options; + # return value indicates if to enable specified feature + + 'blame' => { + 'sub' => \&feature_blame, + 'override' => 0, + 'default' => [0]}, + + 'snapshot' => { + 'sub' => \&feature_snapshot, + 'override' => 0, + # => [content-encoding, suffix, program] + 'default' => ['x-gzip', 'gz', 'gzip']}, ); sub gitweb_check_feature { my ($name) = @_; return undef unless exists $feature{$name}; - my ($sub, $override, @defaults) = ($feature{$name}{'sub'}, - $feature{$name}{'override'}, - @{$feature{$name}{'default'}}); + my ($sub, $override, @defaults) = ( + $feature{$name}{'sub'}, + $feature{$name}{'override'}, + @{$feature{$name}{'default'}}); if (!$override) { return @defaults; } return $sub->(@defaults); } @@ -463,7 +471,9 @@ sub format_log_line_html { if ($line =~ m/([0-9a-fA-F]{40})/) { my $hash_text = $1; if (git_get_type($hash_text) eq "commit") { - my $link = $cgi->a({-class => "text", -href => href(action=>"commit", hash=>$hash_text)}, $hash_text); + my $link = + $cgi->a({-href => href(action=>"commit", hash=>$hash_text), + -class => "text"}, $hash_text); $line =~ s/$hash_text/$link/; } } @@ -734,8 +744,10 @@ sub parse_date { $date{'mday'} = $mday; $date{'day'} = $days[$wday]; $date{'month'} = $months[$mon]; - $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; - $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; + $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", + $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; + $date{'mday-time'} = sprintf "%d %s %02d:%02d", + $mday, $months[$mon], $hour ,$min; $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; my $local = $epoch + ((int $1 + ($2/60)) * 3600); @@ -792,7 +804,8 @@ sub parse_commit { @commit_lines = @$commit_text; } else { $/ = "\0"; - open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id + or return; @commit_lines = split '\n', <$fd>; close $fd or return; $/ = "\n"; @@ -1086,12 +1099,15 @@ sub git_header_html { # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. # we have to do this because MSIE sometimes globs '*/*', pretending to # support xhtml+xml but choking when it gets what it asked for. - if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { + if (defined $cgi->http('HTTP_ACCEPT') && + $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && + $cgi->Accept('application/xhtml+xml') != 0) { $content_type = 'application/xhtml+xml'; } else { $content_type = 'text/html'; } - print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + print $cgi->header(-type=>$content_type, -charset => 'utf-8', + -status=> $status, -expires => $expires); print < @@ -1271,9 +1287,12 @@ sub git_print_page_path { } elsif (defined $type && $type eq 'blob') { print "
"; if (defined $hb) { - print $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hb, file_name=>$file_name)}, esc_html($name)); + print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, + hash_base=>$hb)}, + esc_html($name)); } else { - print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name)}, esc_html($name)); + print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name)}, + esc_html($name)); } print "
\n"; } else { @@ -1523,7 +1542,8 @@ sub git_shortlog_body { print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 10)) . ""; - print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); + print format_subject_html($co{'title'}, $co{'title_short'}, + href(action=>"commit", hash=>$commit), $ref); print "" . $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . @@ -1572,7 +1592,8 @@ sub git_history_body { "" . esc_html(chop_str($co{'author_name'}, 15, 3)) . ""; # originally git_history used chop_str($co{'title'}, 50) - print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); + print format_subject_html($co{'title'}, $co{'title_short'}, + href(action=>"commit", hash=>$commit), $ref); print "" . $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . @@ -1585,7 +1606,8 @@ sub git_history_body { if (defined $blob_current && defined $blob_parent && $blob_current ne $blob_parent) { print " | " . - $cgi->a({-href => href(action=>"blobdiff", hash=>$blob_current, hash_parent=>$blob_parent, hash_base=>$commit, file_name=>$file_name)}, + $cgi->a({-href => href(action=>"blobdiff", hash=>$blob_current, hash_parent=>$blob_parent, + hash_base=>$commit, file_name=>$file_name)}, "diff to current"); } } @@ -1630,7 +1652,8 @@ sub git_tags_body { ""; if (defined $comment) { - print format_subject_html($comment, $comment_short, href(action=>"tag", hash=>$tag{'id'})); + print format_subject_html($comment, $comment_short, + href(action=>"tag", hash=>$tag{'id'})); } print ""; @@ -1941,13 +1964,17 @@ sub git_tag { "\n" . "\n" . "\n" . - "\n" . - "\n" . + "\n" . + "\n" . "\n"; if (defined($tag{'author'})) { my %ad = parse_date($tag{'epoch'}, $tag{'tz'}); print "\n"; - print "\n"; + print "\n"; } print "
object" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, $tag{'object'}) . "" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, $tag{'type'}) . "" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, + $tag{'object'}) . "" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, + $tag{'type'}) . "
author" . esc_html($tag{'author'}) . "
" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "
" . $ad{'rfc2822'} . + sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . + "
\n\n" . "\n"; @@ -1984,8 +2011,11 @@ sub git_blame2 { or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = - $cgi->a({-href => href(action=>"blobl", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . - " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); + $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "blob") . + " | " . + $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + "head"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); @@ -2011,14 +2041,17 @@ sub git_blame2 { } print "
" . - $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, esc_html($rev)) . "" . esc_html($lineno) . "" . + esc_html($lineno) . "" . esc_html($data) . "
\n"; print "
"; - close $fd or print "Reading blob failed\n"; + close $fd + or print "Reading blob failed\n"; git_footer_html(); } @@ -2041,8 +2074,11 @@ sub git_blame { or die_error(undef, "Open git-annotate failed"); git_header_html(); my $formats_nav = - $cgi->a({-href => href(action=>"blobl", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . - " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); + $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "blob") . + " | " . + $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + "head"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, 'blob', $hash_base); @@ -2106,7 +2142,8 @@ HTML HTML } # while (my $line = <$fd>) print "\n\n"; - close $fd or print "Reading blob failed.\n"; + close $fd + or print "Reading blob failed.\n"; print "
"; git_footer_html(); } @@ -2193,13 +2230,23 @@ sub git_blob { if (defined $hash_base && (my %co = parse_commit($hash_base))) { if (defined $file_name) { if ($have_blame) { - $formats_nav .= $cgi->a({-href => href(action=>"blame", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blame") . " | "; + $formats_nav .= + $cgi->a({-href => href(action=>"blame", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "blame") . + " | "; } $formats_nav .= - $cgi->a({-href => href(action=>"blob_plain", hash=>$hash, file_name=>$file_name)}, "plain") . - " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$file_name)}, "head"); + $cgi->a({-href => href(action=>"blob_plain", + hash=>$hash, file_name=>$file_name)}, + "plain") . + " | " . + $cgi->a({-href => href(action=>"blob", + hash_base=>"HEAD", file_name=>$file_name)}, + "head"); } else { - $formats_nav .= $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "plain"); + $formats_nav .= + $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "plain"); } git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); @@ -2215,9 +2262,11 @@ sub git_blob { chomp $line; $nr++; $line = untabify($line); - printf "
%4i %s
\n", $nr, $nr, $nr, esc_html($line); + printf "
%4i %s
\n", + $nr, $nr, $nr, esc_html($line); } - close $fd or print "Reading blob failed.\n"; + close $fd + or print "Reading blob failed.\n"; print ""; git_footer_html(); } @@ -2278,23 +2327,37 @@ sub git_tree { print "" . mode_str($t_mode) . "\n"; if ($t_type eq "blob") { print "" . - $cgi->a({-href => href(action=>"blob", hash=>$t_hash, file_name=>"$base$t_name", %base_key), -class => "list"}, esc_html($t_name)) . + $cgi->a({-href => href(action=>"blob", hash=>$t_hash, file_name=>"$base$t_name", %base_key), + -class => "list"}, esc_html($t_name)) . "\n" . "" . - $cgi->a({-href => href(action=>"blob", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, "blob"); + $cgi->a({-href => href(action=>"blob", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, + "blob"); if ($have_blame) { - print " | " . $cgi->a({-href => href(action=>"blame", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, "blame"); + print " | " . + $cgi->a({-href => href(action=>"blame", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, + "blame"); } - print " | " . $cgi->a({-href => href(action=>"history", hash=>$t_hash, hash_base=>$hash_base, file_name=>"$base$t_name")}, "history") . - " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$t_hash, file_name=>"$base$t_name")}, "raw") . + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$t_hash, file_name=>"$base$t_name")}, + "history") . + " | " . + $cgi->a({-href => href(action=>"blob_plain", + hash=>$t_hash, file_name=>"$base$t_name")}, + "raw") . "\n"; } elsif ($t_type eq "tree") { print "" . - $cgi->a({-href => href(action=>"tree", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, esc_html($t_name)) . + $cgi->a({-href => href(action=>"tree", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, + esc_html($t_name)) . "\n" . "" . - $cgi->a({-href => href(action=>"tree", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, "tree") . - " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, file_name=>"$base$t_name")}, "history") . + $cgi->a({-href => href(action=>"tree", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, + "tree") . + " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, file_name=>"$base$t_name")}, + "history") . "\n"; } print "\n"; @@ -2319,10 +2382,9 @@ sub git_snapshot { my $filename = basename($project) . "-$hash.tar.$suffix"; print $cgi->header(-type => 'application/x-tar', - -content-encoding => $ctype, - '-content-disposition' => - "inline; filename=\"$filename\"", - -status => '200 OK'); + -content-encoding => $ctype, + '-content-disposition' => "inline; filename=\"$filename\"", + -status => '200 OK'); open my $fd, "-|", "$GIT tar-tree $hash \'$project\' | $command" or die_error(undef, "Execute git-tar-tree failed."); @@ -2373,7 +2435,8 @@ sub git_log { print "
\n" . "
\n" . $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . - " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . + " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . "
\n" . "
\n" . "" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]
\n" . @@ -2417,12 +2480,14 @@ sub git_commit { my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; - $formats_nav .= $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); + $formats_nav .= + $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, + "blame"); } git_header_html(undef, $expires); git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', - $hash, $co{'tree'}, $hash, - $formats_nav); + $hash, $co{'tree'}, $hash, + $formats_nav); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); @@ -2435,23 +2500,31 @@ sub git_commit { "" . " $ad{'rfc2822'}"; if ($ad{'hour_local'} < 6) { - printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + printf(" (%02d:%02d %s)", + $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); } else { - printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + printf(" (%02d:%02d %s)", + $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); } print "" . "\n"; print "committer" . esc_html($co{'committer'}) . "\n"; - print " $cd{'rfc2822'}" . sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "\n"; + print " $cd{'rfc2822'}" . + sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . + "\n"; print "commit$co{'id'}\n"; print "" . "tree" . "" . - $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash), class => "list"}, $co{'tree'}) . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash), + class => "list"}, $co{'tree'}) . "" . - "" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, "tree"); + "" . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, + "tree"); if ($have_snapshot) { - print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot"); + print " | " . + $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot"); } print "" . "\n"; @@ -2459,10 +2532,14 @@ sub git_commit { foreach my $par (@$parents) { print "" . "parent" . - "" . $cgi->a({-href => href(action=>"commit", hash=>$par), class => "list"}, $par) . "" . + "" . + $cgi->a({-href => href(action=>"commit", hash=>$par), + class => "list"}, $par) . + "" . "" . $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") . - " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "commitdiff") . + " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "commitdiff") . "" . "\n"; } @@ -2483,7 +2560,9 @@ sub git_blobdiff { git_header_html(); if (defined $hash_base && (my %co = parse_commit($hash_base))) { my $formats_nav = - $cgi->a({-href => href(action=>"blobdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, "plain"); + $cgi->a({-href => href(action=>"blobdiff_plain", + hash=>$hash, hash_parent=>$hash_parent)}, + "plain"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { @@ -2494,12 +2573,16 @@ sub git_blobdiff { git_print_page_path($file_name, "blob", $hash_base); print "
\n" . "
blob:" . - $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, hash_base=>$hash_base, file_name=>($file_parent || $file_name))}, $hash_parent) . + $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, + hash_base=>$hash_base, file_name=>($file_parent || $file_name))}, + $hash_parent) . " -> blob:" . - $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, $hash) . + $cgi->a({-href => href(action=>"blob", hash=>$hash, + hash_base=>$hash_base, file_name=>$file_name)}, + $hash) . "
\n"; git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash); - print "
"; + print "
"; # page_body git_footer_html(); } @@ -2531,7 +2614,8 @@ sub git_commitdiff { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); my $formats_nav = - $cgi->a({-href => href(action=>"commitdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, "plain"); + $cgi->a({-href => href(action=>"commitdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, + "plain"); git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); @@ -2552,22 +2636,30 @@ sub git_commitdiff { my $file = validate_input(unquote($6)); if ($status eq "A") { print "
" . file_type($to_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, $to_id) . "(new)" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$to_id, file_name=>$file)}, + $to_id) . "(new)" . "
\n"; git_diff_print(undef, "/dev/null", $to_id, "b/$file"); } elsif ($status eq "D") { print "
" . file_type($from_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$hash_parent, file_name=>$file)}, $from_id) . "(deleted)" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$from_id, file_name=>$file)}, + $from_id) . "(deleted)" . "
\n"; git_diff_print($from_id, "a/$file", undef, "/dev/null"); } elsif ($status eq "M") { if ($from_id ne $to_id) { print "
" . file_type($from_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$hash_parent, file_name=>$file)}, $from_id) . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$from_id, file_name=>$file)}, + $from_id) . " -> " . file_type($to_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, $to_id); + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$to_id, file_name=>$file)}, + $to_id); print "
\n"; git_diff_print($from_id, "a/$file", $to_id, "b/$file"); } @@ -2607,7 +2699,9 @@ sub git_commitdiff_plain { } } - print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); + print $cgi->header(-type => "text/plain", + -charset => 'utf-8', + '-content-disposition' => "inline; filename=\"git-$hash.patch\""); my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my $comment = $co{'comment'}; print "From: $co{'author'}\n" . @@ -2746,7 +2840,8 @@ sub git_search { print "\n" . "" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . - " | " . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); print "\n" . "\n"; } @@ -2783,18 +2878,22 @@ sub git_search { print "$co{'age_string_date'}\n" . "" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "\n" . "" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), + -class => "list subject"}, esc_html(chop_str($co{'title'}, 50)) . "
"); while (my $setref = shift @files) { my %set = %$setref; - print $cgi->a({-href => href(action=>"blob", hash=>$set{'id'}, hash_base=>$co{'id'}, file_name=>$set{'file'}), class => "list"}, - "" . esc_html($set{'file'}) . "") . + print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, + hash=>$set{'id'}, file_name=>$set{'file'}), + -class => "list"}, + "" . esc_html($set{'file'}) . "") . "
\n"; } print "\n" . "" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . - " | " . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); print "\n" . "\n"; } -- cgit v0.10.2-6-g49f6 From 134a6941ec7f44b9deb2cbb51429ff6a47e0d08b Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 22 Aug 2006 16:55:34 +0200 Subject: gitweb: Use underscore instead of hyphen to separate words in HTTP headers names Use underscore (which will be turned into hyphen) to separate words in HTTP header names, in keys to CGI header() method, consistently. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 63dc477..c4a4f7f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2198,7 +2198,8 @@ sub git_blob_plain { $save_as .= '.txt'; } - print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\""); + print $cgi->header(-type => "$type", + -content_disposition => "inline; filename=\"$save_as\""); undef $/; binmode STDOUT, ':raw'; print <$fd>; @@ -2382,8 +2383,8 @@ sub git_snapshot { my $filename = basename($project) . "-$hash.tar.$suffix"; print $cgi->header(-type => 'application/x-tar', - -content-encoding => $ctype, - '-content-disposition' => "inline; filename=\"$filename\"", + -content_encoding => $ctype, + -content_disposition => "inline; filename=\"$filename\"", -status => '200 OK'); open my $fd, "-|", "$GIT tar-tree $hash \'$project\' | $command" or @@ -2701,7 +2702,7 @@ sub git_commitdiff_plain { print $cgi->header(-type => "text/plain", -charset => 'utf-8', - '-content-disposition' => "inline; filename=\"git-$hash.patch\""); + -content_disposition => "inline; filename=\"git-$hash.patch\""); my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my $comment = $co{'comment'}; print "From: $co{'author'}\n" . -- cgit v0.10.2-6-g49f6 From 77a153fd92ae6e4e40e0ccb15c9f808d63e198a0 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 22 Aug 2006 16:59:20 +0200 Subject: gitweb: Route rest of action subroutines through %actions Route rest of action subroutines, namely git_project_list and git_opml (both of which doesn't need $project) through %actions hash. This has disadvantage that all parameters are read and validated; earlier git_opml was called as soon as $action was parsed and validated, git_project_list was called as soon as $project was parsed and validated. This has advantage that all action dispatch is grouped in one place. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c4a4f7f..ae38eca 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -155,11 +155,6 @@ if (defined $action) { if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { die_error(undef, "Invalid action parameter"); } - # action which does not check rest of parameters - if ($action eq "opml") { - git_opml(); - exit; - } } our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); @@ -179,9 +174,6 @@ if (defined $project) { die_error(undef, "No such project"); } $ENV{'GIT_DIR'} = "$projectroot/$project"; -} else { - git_project_list(); - exit; } our $file_name = $cgi->param('f'); @@ -255,9 +247,16 @@ my %actions = ( "tags" => \&git_tags, "tree" => \&git_tree, "snapshot" => \&git_snapshot, + # those below don't need $project + "opml" => \&git_opml, + "project_list" => \&git_project_list, ); -$action = 'summary' if (!defined($action)); +if (defined $project) { + $action ||= 'summary'; +} else { + $action ||= 'project_list'; +} if (!defined($actions{$action})) { die_error(undef, "Unknown action"); } -- cgit v0.10.2-6-g49f6 From 59b9f61a3f76762dc975e99cc05335a3b97ad1f9 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 22 Aug 2006 23:42:53 +0200 Subject: gitweb: Use here-doc Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ae38eca..1c8a2eb 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1185,11 +1185,13 @@ sub die_error { my $error = shift || "Malformed query, file missing or permission denied"; git_header_html($status); - print "
\n" . - "

\n" . - "$status - $error\n" . - "
\n" . - "
\n"; + print < +

+$status - $error +
+ +EOF git_footer_html(); exit; } @@ -2022,9 +2024,11 @@ sub git_blame2 { my $num_colors = scalar(@rev_color); my $current_color = 0; my $last_rev; - print "
\n"; - print "\n"; - print "\n"; + print < +
CommitLineData
+ +HTML while (<$fd>) { /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; my $full_rev = $1; @@ -2566,9 +2570,10 @@ sub git_blobdiff { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { - print "
\n" . - "

\n" . - "
$hash vs $hash_parent
\n"; + print <

+
$hash vs $hash_parent
+HTML } git_print_page_path($file_name, "blob", $hash_base); print "
\n" . @@ -2704,9 +2709,11 @@ sub git_commitdiff_plain { -content_disposition => "inline; filename=\"git-$hash.patch\""); my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my $comment = $co{'comment'}; - print "From: $co{'author'}\n" . - "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". - "Subject: $co{'title'}\n"; + print <; close $fd or die_error(undef, "Reading git-rev-list failed"); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "\n". - "\n"; - print "\n"; - print "$project\n". - "" . esc_html("$my_url?p=$project;a=summary") . "\n". - "$project log\n". - "en\n"; + print < + + +$project $my_uri $my_url +${\esc_html("$my_url?p=$project;a=summary")} +$project log +en +XML for (my $i = 0; $i <= $#revlist; $i++) { my $commit = $revlist[$i]; @@ -3005,13 +3014,15 @@ sub git_opml { my @list = git_get_projects_list(); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "\n". - "\n". - "". - " $site_name Git OPML Export\n". - "\n". - "\n". - "\n"; + print < + + + $site_name Git OPML Export + + + +XML foreach my $pr (@list) { my %proj = %$pr; @@ -3030,7 +3041,9 @@ sub git_opml { my $html = "$my_url?p=$proj{'path'};a=summary"; print "\n"; } - print "\n". - "\n". - "\n"; + print < + + +XML } -- cgit v0.10.2-6-g49f6 From 1149fecfc270a2effc344897989f40afe449a72c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 22 Aug 2006 19:05:24 +0200 Subject: gitweb: Drop the href() params which keys are not in %mapping If someone would enter parameter name incorrectly, and some key of %params is not found in %mapping hash, the parameter is now ignored. Change introduced by Martin Waitz in commit 756d2f064b2419fcdf9cd9c851f352e2a4f75103 tried to do that, but it left empty value and there was doubled ";;" in returned string. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1c8a2eb..43b1600 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -285,7 +285,7 @@ sub href(%) { my $href = "$my_uri?"; $href .= esc_param( join(";", map { - "$mapping{$_}=$params{$_}" if defined $params{$_} + defined $params{$_} ? "$mapping{$_}=$params{$_}" : () } keys %params ) ); -- cgit v0.10.2-6-g49f6 From 498fe00201401b766a200cf423a8ec42b5d5643e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 22 Aug 2006 19:05:25 +0200 Subject: gitweb: Sort CGI parameters returned by href() Restore pre-1c2a4f5addce479c619057c6cdc841802139982f ordering of CGI parameters. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 43b1600..50083e3 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -267,7 +267,9 @@ exit; ## action links sub href(%) { - my %mapping = ( + my %params = @_; + + my @mapping = ( action => "a", project => "p", file_name => "f", @@ -278,18 +280,18 @@ sub href(%) { page => "pg", searchtext => "s", ); + my %mapping = @mapping; - my %params = @_; $params{"project"} ||= $project; - my $href = "$my_uri?"; - $href .= esc_param( join(";", - map { - defined $params{$_} ? "$mapping{$_}=$params{$_}" : () - } keys %params - ) ); - - return $href; + my @result = (); + for (my $i = 0; $i < @mapping; $i += 2) { + my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]); + if (defined $params{$name}) { + push @result, $symbol . "=" . esc_param($params{$name}); + } + } + return "$my_uri?" . join(';', @result); } -- cgit v0.10.2-6-g49f6 From 678dac6b45437d65fa958c03ca47f4a0337efad4 Mon Sep 17 00:00:00 2001 From: Tilman Sauerbeck Date: Tue, 22 Aug 2006 19:37:41 +0200 Subject: Added support for dropping privileges to git-daemon. Signed-off-by: Tilman Sauerbeck Signed-off-by: Junio C Hamano diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 0f7d274..17619a3 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,7 +11,8 @@ SYNOPSIS 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] - [--reuseaddr] [--detach] [--pid-file=file] [directory...] + [--reuseaddr] [--detach] [--pid-file=file] + [--user=user [--group=group]] [directory...] DESCRIPTION ----------- @@ -93,6 +94,17 @@ OPTIONS --pid-file=file:: Save the process id in 'file'. +--user=user, --group=group:: + Change daemon's uid and gid before entering the service loop. + When only `--user` is given without `--group`, the + primary group ID for the user is used. The values of + the option are given to `getpwnam(3)` and `getgrnam(3)` + and numeric IDs are not supported. ++ +Giving these options is an error when used with `--inetd`; use +the facility of inet daemon to achieve the same before spawning +`git-daemon` if needed. + :: A directory to add to the whitelist of allowed directories. Unless --strict-paths is specified this will also include subdirectories diff --git a/daemon.c b/daemon.c index 012936f..dd3915a 100644 --- a/daemon.c +++ b/daemon.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" @@ -19,7 +21,8 @@ static const char daemon_usage[] = "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n" " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" -" [--reuseaddr] [--detach] [--pid-file=file] [directory...]"; +" [--reuseaddr] [--detach] [--pid-file=file]\n" +" [--user=user [[--group=group]] [directory...]"; /* List of acceptable pathname prefixes */ static char **ok_paths; @@ -701,7 +704,7 @@ static void store_pid(const char *path) fclose(f); } -static int serve(int port) +static int serve(int port, struct passwd *pass, gid_t gid) { int socknum, *socklist; @@ -709,6 +712,11 @@ static int serve(int port) if (socknum == 0) die("unable to allocate any listen sockets on port %u", port); + if (pass && gid && + (initgroups(pass->pw_name, gid) || setgid (gid) || + setuid(pass->pw_uid))) + die("cannot drop privileges"); + return service_loop(socknum, socklist); } @@ -716,8 +724,11 @@ int main(int argc, char **argv) { int port = DEFAULT_GIT_PORT; int inetd_mode = 0; - const char *pid_file = NULL; + const char *pid_file = NULL, *user_name = NULL, *group_name = NULL; int detach = 0; + struct passwd *pass = NULL; + struct group *group; + gid_t gid = 0; int i; /* Without this we cannot rely on waitpid() to tell @@ -791,6 +802,14 @@ int main(int argc, char **argv) log_syslog = 1; continue; } + if (!strncmp(arg, "--user=", 7)) { + user_name = arg + 7; + continue; + } + if (!strncmp(arg, "--group=", 8)) { + group_name = arg + 8; + continue; + } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; break; @@ -802,6 +821,28 @@ int main(int argc, char **argv) usage(daemon_usage); } + if (inetd_mode && (group_name || user_name)) + die("--user and --group are incompatible with --inetd"); + + if (group_name && !user_name) + die("--group supplied without --user"); + + if (user_name) { + pass = getpwnam(user_name); + if (!pass) + die("user not found - %s", user_name); + + if (!group_name) + gid = pass->pw_gid; + else { + group = getgrnam(group_name); + if (!group) + die("group not found - %s", group_name); + + gid = group->gr_gid; + } + } + if (log_syslog) { openlog("git-daemon", 0, LOG_DAEMON); set_die_routine(daemon_die); @@ -831,5 +872,5 @@ int main(int argc, char **argv) if (pid_file) store_pid(pid_file); - return serve(port); + return serve(port, pass, gid); } -- cgit v0.10.2-6-g49f6 From 8da714939458fed99ff750ca09d4169fe68b034e Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 23 Aug 2006 02:49:00 -0400 Subject: Convert memcpy(a,b,20) to hashcpy(a,b). This abstracts away the size of the hash values when copying them from memory location to memory location, much as the introduction of hashcmp abstracted away hash value comparsion. A few call sites were using char* rather than unsigned char* so I added the cast rather than open hashcpy to be void*. This is a reasonable tradeoff as most call sites already use unsigned char* and the existing hashcmp is also declared to be unsigned char*. [jc: this is a follow-up patch for merge-recursive.c which is not in "master" yet. The original was sent-in for "next" so I splitted it out. ] Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 048cca1..8a2f697 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -158,7 +158,7 @@ static struct cache_entry *make_cache_entry(unsigned int mode, size = cache_entry_size(len); ce = xcalloc(1, size); - memcpy(ce->sha1, sha1, 20); + hashcpy(ce->sha1, sha1); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(len, stage); ce->ce_mode = create_ce_mode(mode); @@ -355,7 +355,7 @@ static struct path_list *get_unmerged(void) } e = item->util; e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode); - memcpy(e->stages[ce_stage(ce)].sha, ce->sha1, 20); + hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); } return unmerged; @@ -636,10 +636,10 @@ static struct merge_file_info merge_file(struct diff_filespec *o, result.clean = 0; if (S_ISREG(a->mode)) { result.mode = a->mode; - memcpy(result.sha, a->sha1, 20); + hashcpy(result.sha, a->sha1); } else { result.mode = b->mode; - memcpy(result.sha, b->sha1, 20); + hashcpy(result.sha, b->sha1); } } else { if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) @@ -648,9 +648,9 @@ static struct merge_file_info merge_file(struct diff_filespec *o, result.mode = a->mode == o->mode ? b->mode: a->mode; if (sha_eq(a->sha1, o->sha1)) - memcpy(result.sha, b->sha1, 20); + hashcpy(result.sha, b->sha1); else if (sha_eq(b->sha1, o->sha1)) - memcpy(result.sha, a->sha1, 20); + hashcpy(result.sha, a->sha1); else if (S_ISREG(a->mode)) { int code = 1, fd; struct stat st; @@ -699,7 +699,7 @@ static struct merge_file_info merge_file(struct diff_filespec *o, if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) die("cannot merge modes?"); - memcpy(result.sha, a->sha1, 20); + hashcpy(result.sha, a->sha1); if (!sha_eq(a->sha1, b->sha1)) result.clean = 0; @@ -1096,11 +1096,11 @@ static int process_entry(const char *path, struct stage_data *entry, output("Auto-merging %s", path); o.path = a.path = b.path = (char *)path; - memcpy(o.sha1, o_sha, 20); + hashcpy(o.sha1, o_sha); o.mode = o_mode; - memcpy(a.sha1, a_sha, 20); + hashcpy(a.sha1, a_sha); a.mode = a_mode; - memcpy(b.sha1, b_sha, 20); + hashcpy(b.sha1, b_sha); b.mode = b_mode; mfi = merge_file(&o, &a, &b, -- cgit v0.10.2-6-g49f6 From 87cb004e4251722ca7b888f8b6c77335acb71a85 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 23 Aug 2006 14:31:20 -0700 Subject: hashcpy/hashcmp remaining bits. This fixes up merge-recursive.c for hashcpy/hashcmp changes. Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 8a2f697..39a1eae 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -56,7 +56,7 @@ static int sha_eq(const unsigned char *a, const unsigned char *b) { if (!a && !b) return 2; - return a && b && memcmp(a, b, 20) == 0; + return a && b && hashcmp(a, b) == 0; } /* @@ -891,11 +891,9 @@ static int process_renames(struct path_list *a_renames, remove_file(1, ren1_src); - memcpy(src_other.sha1, - ren1->src_entry->stages[stage].sha, 20); + hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; - memcpy(dst_other.sha1, - ren1->dst_entry->stages[stage].sha, 20); + hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha); dst_other.mode = ren1->dst_entry->stages[stage].mode; try_merge = 0; @@ -980,7 +978,7 @@ static int process_renames(struct path_list *a_renames, static unsigned char *has_sha(const unsigned char *sha) { - return memcmp(sha, null_sha1, 20) == 0 ? NULL: (unsigned char *)sha; + return is_null_sha1(sha) ? NULL: (unsigned char *)sha; } /* Per entry merge function */ -- cgit v0.10.2-6-g49f6 From 60b7f38e0e08867b72022de5c20715d8eb72de24 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Wed, 23 Aug 2006 12:39:10 +0200 Subject: avoid to use error that shadows the function name, use err instead. builtin-apply.c and builtin-push.c uses a local variable called 'error' which shadows the error() function. Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 4f0eef0..5991737 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1907,13 +1907,13 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) static int check_patch_list(struct patch *patch) { struct patch *prev_patch = NULL; - int error = 0; + int err = 0; for (prev_patch = NULL; patch ; patch = patch->next) { - error |= check_patch(patch, prev_patch); + err |= check_patch(patch, prev_patch); prev_patch = patch; } - return error; + return err; } static void show_index_list(struct patch *list) diff --git a/builtin-push.c b/builtin-push.c index 2b5e6fa..ada8338 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -232,7 +232,7 @@ static int do_push(const char *repo) common_argc = argc; for (i = 0; i < n; i++) { - int error; + int err; int dest_argc = common_argc; int dest_refspec_nr = refspec_nr; const char **dest_refspec = refspec; @@ -248,10 +248,10 @@ static int do_push(const char *repo) while (dest_refspec_nr--) argv[dest_argc++] = *dest_refspec++; argv[dest_argc] = NULL; - error = run_command_v(argc, argv); - if (!error) + err = run_command_v(argc, argv); + if (!err) continue; - switch (error) { + switch (err) { case -ERR_RUN_COMMAND_FORK: die("unable to fork for %s", sender); case -ERR_RUN_COMMAND_EXEC: @@ -262,7 +262,7 @@ static int do_push(const char *repo) case -ERR_RUN_COMMAND_WAITPID_NOEXIT: die("%s died with strange error", sender); default: - return -error; + return -err; } } return 0; -- cgit v0.10.2-6-g49f6 From c5fba16c500ad5847842876df0418664cddf6e50 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Wed, 23 Aug 2006 12:39:11 +0200 Subject: git_dir holds pointers to local strings, hence MUST be const. Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 08d6a91..3044794 100644 --- a/cache.h +++ b/cache.h @@ -123,7 +123,7 @@ extern int cache_errno; #define INDEX_ENVIRONMENT "GIT_INDEX_FILE" #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE" -extern char *get_git_dir(void); +extern const char *get_git_dir(void); extern char *get_object_directory(void); extern char *get_refs_directory(void); extern char *get_index_file(void); diff --git a/environment.c b/environment.c index e6bd003..5fae9ac 100644 --- a/environment.c +++ b/environment.c @@ -25,8 +25,9 @@ int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; int pager_use_color = 1; -static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, - *git_graft_file; +static const char *git_dir; +static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; + static void setup_git_env(void) { git_dir = getenv(GIT_DIR_ENVIRONMENT); @@ -49,7 +50,7 @@ static void setup_git_env(void) git_graft_file = strdup(git_path("info/grafts")); } -char *get_git_dir(void) +const char *get_git_dir(void) { if (!git_dir) setup_git_env(); -- cgit v0.10.2-6-g49f6 From b5bf7cd6b7dbcbef9181e21bfae21612c75738c4 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Wed, 23 Aug 2006 12:39:12 +0200 Subject: missing 'static' keywords builtin-tar-tree.c::git_tar_config() and http-push.c::add_one_object() are not used outside their own files. Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index e0bcb0a..61a4135 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -275,7 +275,7 @@ static void traverse_tree(struct tree_desc *tree, struct strbuf *path) } } -int git_tar_config(const char *var, const char *value) +static int git_tar_config(const char *var, const char *value) { if (!strcmp(var, "tar.umask")) { if (!strcmp(value, "user")) { diff --git a/http-push.c b/http-push.c index 4849779..7d12f69 100644 --- a/http-push.c +++ b/http-push.c @@ -1700,7 +1700,7 @@ static int locking_available(void) return lock_flags; } -struct object_list **add_one_object(struct object *obj, struct object_list **p) +static struct object_list **add_one_object(struct object *obj, struct object_list **p) { struct object_list *entry = xmalloc(sizeof(struct object_list)); entry->item = obj; -- cgit v0.10.2-6-g49f6 From d828f6ddf8bc33f848688655b94c82791edfe0d7 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Wed, 23 Aug 2006 12:39:13 +0200 Subject: remove ugly shadowing of loop indexes in subloops. builtin-mv.c and git.c has a nested loop that is governed by a variable 'i', but they shadow it with another instance of 'i'. Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano diff --git a/builtin-mv.c b/builtin-mv.c index ff882be..fd1e520 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -262,10 +262,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } else { for (i = 0; i < changed.nr; i++) { const char *path = changed.items[i].path; - int i = cache_name_pos(path, strlen(path)); - struct cache_entry *ce = active_cache[i]; + int j = cache_name_pos(path, strlen(path)); + struct cache_entry *ce = active_cache[j]; - if (i < 0) + if (j < 0) die ("Huh? Cache entry for %s unknown?", path); refresh_cache_entry(ce, 0); } diff --git a/git.c b/git.c index 930998b..a01d195 100644 --- a/git.c +++ b/git.c @@ -292,11 +292,11 @@ static void handle_internal_command(int argc, const char **argv, char **envp) if (p->option & USE_PAGER) setup_pager(); if (getenv("GIT_TRACE")) { - int i; + int j; fprintf(stderr, "trace: built-in: git"); - for (i = 0; i < argc; ++i) { + for (j = 0; j < argc; ++j) { fputc(' ', stderr); - sq_quote_print(stderr, argv[i]); + sq_quote_print(stderr, argv[j]); } putc('\n', stderr); fflush(stderr); -- cgit v0.10.2-6-g49f6 From 599f8d63140f3626604d4fc83a48cd00c67b804a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 23 Aug 2006 18:39:49 -0700 Subject: builtin-grep.c: remove unused debugging piece. Signed-off-by: Junio C Hamano diff --git a/builtin-grep.c b/builtin-grep.c index 0bd517b..8213ce2 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -293,9 +293,6 @@ static void compile_patterns(struct grep_opt *opt) */ p = opt->pattern_list; opt->pattern_expression = compile_pattern_expr(&p); -#if DEBUG - dump_pattern_exp(opt->pattern_expression, 0); -#endif if (p) die("incomplete pattern expression: %s", p->pattern); } -- cgit v0.10.2-6-g49f6 From dd305c846231e2fddf61c1e1314029b53af88a77 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Wed, 23 Aug 2006 12:39:15 +0200 Subject: use name[len] in switch directly, instead of creating a shadowed variable. builtin-apply.c defines a local variable 'c' which is used only once and then later gets shadowed by another instance of 'c'. Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 5991737..f8f5eeb 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -606,9 +606,7 @@ static char *git_header_name(char *line, int llen) * form. */ for (len = 0 ; ; len++) { - char c = name[len]; - - switch (c) { + switch (name[len]) { default: continue; case '\n': -- cgit v0.10.2-6-g49f6 From 5df7dbbae4a51e20afc00acc5c87ea4536d1302c Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Wed, 23 Aug 2006 12:39:16 +0200 Subject: n is in fact unused, and is later shadowed. date.c::approxidate_alpha() counts the number of alphabets while moving the pointer but does not use the count. Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano diff --git a/date.c b/date.c index 66be23a..d780846 100644 --- a/date.c +++ b/date.c @@ -584,10 +584,10 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) const struct typelen *tl; const struct special *s; const char *end = date; - int n = 1, i; + int i; - while (isalpha(*++end)) - n++; + while (isalpha(*++end)); + ; for (i = 0; i < 12; i++) { int match = match_string(date, month_names[i]); -- cgit v0.10.2-6-g49f6 From 7099c9c7c9c14ca3f66b8a04219162e6f259ff37 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 23 Aug 2006 21:24:47 -0700 Subject: update-index -g I often find myself typing this but the common abbreviation "g" for "again" has not been supported so far for some unknown reason. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 3ae6e74..41bb7e1 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -15,7 +15,7 @@ SYNOPSIS [--cacheinfo ]\* [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] - [--really-refresh] [--unresolve] [--again] + [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] [-z] [--stdin] [--verbose] @@ -80,7 +80,7 @@ OPTIONS filesystem that has very slow lstat(2) system call (e.g. cifs). ---again:: +--again, -g:: Runs `git-update-index` itself on the paths whose index entries are different from those from the `HEAD` commit. diff --git a/builtin-update-index.c b/builtin-update-index.c index 5dd91af..75c0abb 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -306,7 +306,7 @@ static void read_index_info(int line_termination) } static const char update_index_usage[] = -"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again] [--ignore-missing] [-z] [--verbose] [--] ..."; +"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] ..."; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -595,7 +595,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) active_cache_changed = 0; goto finish; } - if (!strcmp(path, "--again")) { + if (!strcmp(path, "--again") || !strcmp(path, "-g")) { has_errors = do_reupdate(argc - i, argv + i, prefix, prefix_length); if (has_errors) -- cgit v0.10.2-6-g49f6 From 5684ed6d32fdb953e3b318333ed84ed245f2f6b0 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Fri, 25 Aug 2006 02:56:55 +0200 Subject: git-apply(1): document missing options and improve existing ones Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 2ff7494..20e12ce 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -10,9 +10,10 @@ SYNOPSIS -------- [verse] 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] - [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] - [-CNUM] [--whitespace=] - [...] + [--no-add] [--index-info] [--allow-binary-replacement | --binary] + [-R | --reverse] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof] + [--whitespace=] [--exclude=PATH] + [--cached] [--verbose] [...] DESCRIPTION ----------- @@ -55,6 +56,11 @@ OPTIONS up-to-date, it is flagged as an error. This flag also causes the index file to be updated. +--cached:: + Apply a patch without touching the working tree. Instead, take the + cached data, apply the patch, and store the result in the index, + without using the working tree. This implies '--index'. + --index-info:: Newer git-diff output has embedded 'index information' for each blob to help identify the original version that @@ -62,6 +68,16 @@ OPTIONS the original version of the blob is available locally, outputs information about them to the standard output. +-R, --reverse:: + Apply the patch in reverse. + +--reject:: + For atomicity, gitlink:git-apply[1] by default fails the whole patch and + does not touch the working tree when some of the hunks + do not apply. This option makes it apply + the parts of the patch that are applicable, and send the + rejected hunks to the standard output of the command. + -z:: When showing the index information, do not munge paths, but use NUL terminated machine readable format. Without @@ -80,8 +96,8 @@ OPTIONS ever ignored. --apply:: - If you use any of the options marked ``Turns off - "apply"'' above, git-apply reads and outputs the + If you use any of the options marked "Turns off + 'apply'" above, gitlink:git-apply[1] reads and outputs the information you asked without actually applying the patch. Give this flag after those flags to also apply the patch. @@ -93,7 +109,7 @@ OPTIONS the result with this option, which would apply the deletion part but not addition part. ---allow-binary-replacement:: +--allow-binary-replacement, --binary:: When applying a patch, which is a git-enhanced patch that was prepared to record the pre- and post-image object name in full, and the path being patched exactly matches @@ -104,13 +120,18 @@ OPTIONS result. This allows binary files to be patched in a very limited way. +--exclude=:: + Don't apply changes to files matching the given path pattern. This can + be useful when importing patchsets, where you want to exclude certain + files or directories. + --whitespace=
CommitLineData
\n"; } +sub git_patchset_body { + my ($patchset, $difftree, $hash, $hash_parent) = @_; + + my $patch_idx = 0; + my $in_header = 0; + my $patch_found = 0; + my %diffinfo; + + print "
\n"; + + LINE: foreach my $patch_line (@$patchset) { + + if ($patch_line =~ m/^diff /) { # "git diff" header + # beginning of patch (in patchset) + if ($patch_found) { + # close previous patch + print "
\n"; # class="patch" + } else { + # first patch in patchset + $patch_found = 1; + } + print "
\n"; + + %diffinfo = parse_difftree_raw_line($difftree->[$patch_idx++]); + + # for now, no extended header, hence we skip empty patches + # companion to next LINE if $in_header; + if ($diffinfo{'from_id'} eq $diffinfo{'to_id'}) { # no change + $in_header = 1; + next LINE; + } + + if ($diffinfo{'status'} eq "A") { # added + print "
" . file_type($diffinfo{'to_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'file'})}, + $diffinfo{'to_id'}) . "(new)" . + "
\n"; # class="diff_info" + + } elsif ($diffinfo{'status'} eq "D") { # deleted + print "
" . file_type($diffinfo{'from_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'file'})}, + $diffinfo{'from_id'}) . "(deleted)" . + "
\n"; # class="diff_info" + + } elsif ($diffinfo{'status'} eq "R" || # renamed + $diffinfo{'status'} eq "C") { # copied + print "
" . + file_type($diffinfo{'from_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'from_file'})}, + $diffinfo{'from_id'}) . + " -> " . + file_type($diffinfo{'to_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'to_file'})}, + $diffinfo{'to_id'}); + print "
\n"; # class="diff_info" + + } else { # modified, mode changed, ... + print "
" . + file_type($diffinfo{'from_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'file'})}, + $diffinfo{'from_id'}) . + " -> " . + file_type($diffinfo{'to_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'file'})}, + $diffinfo{'to_id'}); + print "
\n"; # class="diff_info" + } + + #print "
\n"; + $in_header = 1; + next LINE; + } # start of patch in patchset + + + if ($in_header && $patch_line =~ m/^---/) { + #print "
\n" + $in_header = 0; + } + next LINE if $in_header; + + print format_diff_line($patch_line); + } + print "
\n" if $patch_found; # class="patch" + + print "
\n"; # class="patchset" +} + +# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; @@ -2556,7 +2672,7 @@ sub git_commit { git_print_log($co{'comment'}); print "\n"; - git_difftree_body(\@difftree, $parent); + git_difftree_body(\@difftree, $hash, $parent); git_footer_html(); } @@ -2600,7 +2716,7 @@ sub git_blobdiff_plain { } sub git_commitdiff { - mkdir($git_temp, 0700); + my $format = shift || 'html'; my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); @@ -2608,143 +2724,106 @@ sub git_commitdiff { if (!defined $hash_parent) { $hash_parent = $co{'parent'} || '--root'; } - open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open git-diff-tree failed"); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed"); + + # read commitdiff + my $fd; + my @difftree; + my @patchset; + if ($format eq 'html') { + open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', + "--patch-with-raw", "--full-index", $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + + while (chomp(my $line = <$fd>)) { + # empty line ends raw part of diff-tree output + last unless $line; + push @difftree, $line; + } + @patchset = map { chomp; $_ } <$fd>; + + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + } elsif ($format eq 'plain') { + open $fd, "-|", $GIT, "diff-tree", '-r', '-p', '-B', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + } else { + die_error(undef, "Unknown commitdiff format"); + } # non-textual hash id's can be cached my $expires; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = git_get_references(); - my $ref = format_ref_marker($refs, $co{'id'}); - my $formats_nav = - $cgi->a({-href => href(action=>"commitdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, - "plain"); - git_header_html(undef, $expires); - git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); - git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); - print "
\n"; - git_print_simplified_log($co{'comment'}, 1); # skip title - print "
\n"; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { - next; - } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $file = validate_input(unquote($6)); - if ($status eq "A") { - print "
" . file_type($to_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$to_id, file_name=>$file)}, - $to_id) . "(new)" . - "
\n"; - git_diff_print(undef, "/dev/null", $to_id, "b/$file"); - } elsif ($status eq "D") { - print "
" . file_type($from_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$from_id, file_name=>$file)}, - $from_id) . "(deleted)" . - "
\n"; - git_diff_print($from_id, "a/$file", undef, "/dev/null"); - } elsif ($status eq "M") { - if ($from_id ne $to_id) { - print "
" . - file_type($from_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$from_id, file_name=>$file)}, - $from_id) . - " -> " . - file_type($to_mode) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$to_id, file_name=>$file)}, - $to_id); - print "
\n"; - git_diff_print($from_id, "a/$file", $to_id, "b/$file"); - } - } - } - print "
\n" . - "
"; - git_footer_html(); -} -sub git_commitdiff_plain { - mkdir($git_temp, 0700); - my %co = parse_commit($hash); - if (!%co) { - die_error(undef, "Unknown commit object"); - } - if (!defined $hash_parent) { - $hash_parent = $co{'parent'} || '--root'; - } - open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open git-diff-tree failed"); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed"); - - # try to figure out the next tag after this commit - my $tagname; - my $refs = git_get_references("tags"); - open $fd, "-|", $GIT, "rev-list", "HEAD"; - my @commits = map { chomp; $_ } <$fd>; - close $fd; - foreach my $commit (@commits) { - if (defined $refs->{$commit}) { - $tagname = $refs->{$commit} - } - if ($commit eq $hash) { - last; - } - } + # write commit message + if ($format eq 'html') { + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); + my $formats_nav = + $cgi->a({-href => href(action=>"commitdiff_plain", + hash=>$hash, hash_parent=>$hash_parent)}, + "plain"); - print $cgi->header(-type => "text/plain", - -charset => 'utf-8', - -content_disposition => "inline; filename=\"git-$hash.patch\""); - my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); - my $comment = $co{'comment'}; - print <\n"; + print "
\n"; + git_print_simplified_log($co{'comment'}, 1); # skip title + print "
\n"; # class="log" + + } elsif ($format eq 'plain') { + my $refs = git_get_references("tags"); + my @tagnames; + if (exists $refs->{$hash}) { + @tagnames = map { s|^tags/|| } $refs->{$hash}; + } + my $filename = basename($project) . "-$hash.patch"; + + print $cgi->header( + -type => 'text/plain', + -charset => 'utf-8', + -expires => $expires, + -content_disposition => qq(inline; filename="$filename")); + my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); + print <self_url() . "\n\n"; + foreach my $line (@{$co{'comment'}}) { + print "$line\n"; + } + print "---\n\n"; } - print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . - "\n"; - foreach my $line (@$comment) {; - print "$line\n"; - } - print "---\n\n"; + # write patch + if ($format eq 'html') { + #git_difftree_body(\@difftree, $hash, $hash_parent); + #print "
\n"; - foreach my $line (@difftree) { - if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { - next; - } - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $file = $6; - if ($status eq "A") { - git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain"); - } elsif ($status eq "D") { - git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain"); - } elsif ($status eq "M") { - git_diff_print($from_id, "a/$file", $to_id, "b/$file", "plain"); - } + git_patchset_body(\@patchset, \@difftree, $hash, $hash_parent); + + print "\n"; # class="page_body" + git_footer_html(); + + } elsif ($format eq 'plain') { + local $/ = undef; + print <$fd>; + close $fd + or print "Reading git-diff-tree failed\n"; } } +sub git_commitdiff_plain { + git_commitdiff('plain'); +} + sub git_history { if (!defined $hash_base) { $hash_base = git_get_head_hash($project); -- cgit v0.10.2-6-g49f6 From af33ef21bfb0171973393e21deac7621efd2ec7a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 24 Aug 2006 01:58:49 +0200 Subject: gitweb: Show information about incomplete lines in commitdiff In format_diff_line, instead of skipping errors/incomplete lines, for example "\ No newline at end of file" in HTML pretty-printing of diff, use "incomplete" class for div. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 4821022..5eaa24f 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -285,6 +285,10 @@ div.diff.chunk_header { color: #990099; } +div.diff.incomplete { + color: #cccccc; +} + div.diff_info { font-family: monospace; color: #000099; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9367685..fe9b9ee 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -539,7 +539,7 @@ sub format_diff_line { $diff_class = " chunk_header"; } elsif ($char eq "\\") { # skip errors (incomplete lines) - return ""; + $diff_class = " incomplete"; } $line = untabify($line); return "
" . esc_html($line) . "
\n"; -- cgit v0.10.2-6-g49f6 From 1613b79faa63e3d0c1add4facc286c18cf1204db Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 24 Aug 2006 19:32:13 +0200 Subject: gitweb: Remove invalid comment in format_diff_line Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fe9b9ee..1d3d9df 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -538,7 +538,6 @@ sub format_diff_line { } elsif ($char eq "@") { $diff_class = " chunk_header"; } elsif ($char eq "\\") { - # skip errors (incomplete lines) $diff_class = " incomplete"; } $line = untabify($line); -- cgit v0.10.2-6-g49f6 From 157e43b4b0dd5a08eb7a6838192ac58bca62fa5b Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 24 Aug 2006 19:34:36 +0200 Subject: gitweb: Streamify patch output in git_commitdiff Change output of patch(set) in git_commitdiff from slurping whole diff in @patchset array before processing, to passing file descriptor to git_patchset_body. Advantages: faster, incremental output, smaller memory footprint. Disadvantages: cannot react when there is error during closing file descriptor. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1d3d9df..08e0472 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1539,7 +1539,7 @@ sub git_difftree_body { } sub git_patchset_body { - my ($patchset, $difftree, $hash, $hash_parent) = @_; + my ($fd, $difftree, $hash, $hash_parent) = @_; my $patch_idx = 0; my $in_header = 0; @@ -1548,7 +1548,9 @@ sub git_patchset_body { print "
\n"; - LINE: foreach my $patch_line (@$patchset) { + LINE: + while (my $patch_line @$fd>) { + chomp $patch_line; if ($patch_line =~ m/^diff /) { # "git diff" header # beginning of patch (in patchset) @@ -2727,7 +2729,6 @@ sub git_commitdiff { # read commitdiff my $fd; my @difftree; - my @patchset; if ($format eq 'html') { open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', "--patch-with-raw", "--full-index", $hash_parent, $hash @@ -2738,13 +2739,11 @@ sub git_commitdiff { last unless $line; push @difftree, $line; } - @patchset = map { chomp; $_ } <$fd>; - close $fd - or die_error(undef, "Reading git-diff-tree failed"); } elsif ($format eq 'plain') { open $fd, "-|", $GIT, "diff-tree", '-r', '-p', '-B', $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed"); + } else { die_error(undef, "Unknown commitdiff format"); } @@ -2806,8 +2805,8 @@ TEXT #git_difftree_body(\@difftree, $hash, $hash_parent); #print "
\n"; - git_patchset_body(\@patchset, \@difftree, $hash, $hash_parent); - + git_patchset_body($fd, \@difftree, $hash, $hash_parent); + close $fd; print "
\n"; # class="page_body" git_footer_html(); -- cgit v0.10.2-6-g49f6 From 470b96d4837c5b019f15c8a5b013501724236de4 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 24 Aug 2006 19:37:04 +0200 Subject: gitweb: Add git_get_{following,preceding}_references functions Adds git_get_following_references function, based on code which was used in git_commitdiff_plain to generate X-Git-Tag: header, and companion git_get_preceding_references function. Both functions return array of all references of given type (as returned by git_get_references) following/preceding given commit in array (list) context, and last following/first preceding ref in scalar context. Stripping ref (list of refs) of "$type/" (e.g. "tags/") is left to caller. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 08e0472..b964302 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -749,6 +749,58 @@ sub git_get_references { return \%refs; } +sub git_get_following_references { + my $hash = shift || return undef; + my $type = shift; + my $base = shift || $hash_base || "HEAD"; + + my $refs = git_get_references($type); + open my $fd, "-|", $GIT, "rev-list", $base + or return undef; + my @commits = map { chomp; $_ } <$fd>; + close $fd + or return undef; + + my @reflist; + my $lastref; + + foreach my $commit (@commits) { + foreach my $ref (@{$refs->{$commit}}) { + $lastref = $ref; + push @reflist, $lastref; + } + if ($commit eq $hash) { + last; + } + } + + return wantarray ? @reflist : $lastref; +} + +sub git_get_preceding_references { + my $hash = shift || return undef; + my $type = shift; + + my $refs = git_get_references($type); + open my $fd, "-|", $GIT, "rev-list", $hash + or return undef; + my @commits = map { chomp; $_ } <$fd>; + close $fd + or return undef; + + my @reflist; + my $firstref; + + foreach my $commit (@commits) { + foreach my $ref (@{$refs->{$commit}}) { + $firstref = $ref unless $firstref; + push @reflist, $ref; + } + } + + return wantarray ? @reflist : $firstref; +} + ## ---------------------------------------------------------------------- ## parse to hash functions -- cgit v0.10.2-6-g49f6 From 3066c359c63d1dc32db5147ebf015fe9bba4c5bb Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 24 Aug 2006 19:39:32 +0200 Subject: gitweb: Faster return from git_get_preceding_references if possible Return on first ref found when git_get_preceding_references is called in scalar context Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b964302..01452d2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -789,16 +789,15 @@ sub git_get_preceding_references { or return undef; my @reflist; - my $firstref; foreach my $commit (@commits) { foreach my $ref (@{$refs->{$commit}}) { - $firstref = $ref unless $firstref; + return $ref unless wantarray; push @reflist, $ref; } } - return wantarray ? @reflist : $firstref; + return @reflist; } ## ---------------------------------------------------------------------- -- cgit v0.10.2-6-g49f6 From 56a322f16113119bcc3770ef9565297ab59c29d2 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 24 Aug 2006 19:41:23 +0200 Subject: gitweb: Add git_get_rev_name_tags function Add git_get_rev_name_tags function, for later use in git_commitdiff('plain') for X-Git-Tag: header. This function, contrary to the call to git_get_following_references($hash, "tags"); _does_ strip "tags/" and returns bare tag name. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 01452d2..7aa6838 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -800,6 +800,22 @@ sub git_get_preceding_references { return @reflist; } +sub git_get_rev_name_tags { + my $hash = shift || return undef; + + open my $fd, "-|", $GIT, "name-rev", "--tags", $hash + or return; + my $name_rev = <$fd>; + close $fd; + + if ($name_rev =~ m|^$hash tags/(.*)$|) { + return $1; + } else { + # catches also '$hash undefined' output + return undef; + } +} + ## ---------------------------------------------------------------------- ## parse to hash functions -- cgit v0.10.2-6-g49f6 From edf735abfad53a07ae91ca60576386f239bf7482 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 24 Aug 2006 19:45:30 +0200 Subject: gitweb: Use git_get_name_rev_tags for commitdiff_plain X-Git-Tag: header Use git_get_rev_name_tags function for X-Git-Tag: header in git_commitdiff('plain'), i.e. for commitdiff_plain action. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7aa6838..50e405f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2840,10 +2840,7 @@ sub git_commitdiff { } elsif ($format eq 'plain') { my $refs = git_get_references("tags"); - my @tagnames; - if (exists $refs->{$hash}) { - @tagnames = map { s|^tags/|| } $refs->{$hash}; - } + my $tagname = git_get_rev_name_tags($hash); my $filename = basename($project) . "-$hash.patch"; print $cgi->header( @@ -2857,10 +2854,9 @@ From: $co{'author'} Date: $ad{'rfc2822'} ($ad{'tz_local'}) Subject: $co{'title'} TEXT - foreach my $tag (@tagnames) { - print "X-Git-Tag: $tag\n"; - } + print "X-Git-Tag: $tagname\n" if $tagname; print "X-Git-Url: " . $cgi->self_url() . "\n\n"; + foreach my $line (@{$co{'comment'}}) { print "$line\n"; } -- cgit v0.10.2-6-g49f6 From 420e92f255982564a782b6ea8cd3c522f48c7d12 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 24 Aug 2006 23:53:54 +0200 Subject: gitweb: Add support for hash_parent_base parameter for blobdiffs Add support for hash_parent_base in input validation part and in href() function. Add proper hash_parent_base to all calls to blobdiff and blobdiff_plain action URLs. Use hash_parent_base as hash_base for blobs of hash_parent. To be used in future rewrite of git_blobdiff and git_blobdiff_plain. While at it, move project before action in ordering CGI parameters in href(). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 50e405f..80de7b6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -211,6 +211,13 @@ if (defined $hash_base) { } } +our $hash_parent_base = $cgi->param('hpb'); +if (defined $hash_parent_base) { + if (!validate_input($hash_parent_base)) { + die_error(undef, "Invalid hash parent base parameter"); + } +} + our $page = $cgi->param('pg'); if (defined $page) { if ($page =~ m/[^0-9]$/) { @@ -270,13 +277,14 @@ sub href(%) { my %params = @_; my @mapping = ( - action => "a", project => "p", + action => "a", file_name => "f", file_parent => "fp", hash => "h", hash_parent => "hp", hash_base => "hb", + hash_parent_base => "hpb", page => "pg", searchtext => "s", ); @@ -1543,8 +1551,10 @@ sub git_difftree_body { } print ""; if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - print $cgi->a({-href => href(action=>"blobdiff", hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, file_name=>$diff{'file'}), + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'}), -class => "list"}, esc_html($diff{'file'})); } else { # only mode changed print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, @@ -1559,8 +1569,10 @@ sub git_difftree_body { "blob"); if ($diff{'to_id'} ne $diff{'from_id'}) { # modified print " | " . - $cgi->a({-href => href(action=>"blobdiff", hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, + $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, "diff"); } print " | " . @@ -1592,8 +1604,9 @@ sub git_difftree_body { "blob"); if ($diff{'to_id'} ne $diff{'from_id'}) { print " | " . - $cgi->a({-href => href(action=>"blobdiff", hash_base=>$hash, + $cgi->a({-href => href(action=>"blobdiff", hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, "diff"); } @@ -1793,8 +1806,10 @@ sub git_history_body { if (defined $blob_current && defined $blob_parent && $blob_current ne $blob_parent) { print " | " . - $cgi->a({-href => href(action=>"blobdiff", hash=>$blob_current, hash_parent=>$blob_parent, - hash_base=>$commit, file_name=>$file_name)}, + $cgi->a({-href => href(action=>"blobdiff", + hash=>$blob_current, hash_parent=>$blob_parent, + hash_base=>$hash_base, hash_parent_base=>$commit, + file_name=>$file_name)}, "diff to current"); } } @@ -2751,7 +2766,9 @@ sub git_blobdiff { if (defined $hash_base && (my %co = parse_commit($hash_base))) { my $formats_nav = $cgi->a({-href => href(action=>"blobdiff_plain", - hash=>$hash, hash_parent=>$hash_parent)}, + hash=>$hash, hash_parent=>$hash_parent, + hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, + file_name=>$file_name, file_parent=>$file_parent)}, "plain"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); @@ -2765,7 +2782,7 @@ HTML print "
\n" . "
blob:" . $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, - hash_base=>$hash_base, file_name=>($file_parent || $file_name))}, + hash_base=>$hash_parent_base, file_name=>($file_parent || $file_name))}, $hash_parent) . " -> blob:" . $cgi->a({-href => href(action=>"blob", hash=>$hash, -- cgit v0.10.2-6-g49f6 From fe87585e53860f1c89a088e91c08dd89b6702a74 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 20:59:39 +0200 Subject: gitweb: Allow for pre-parsed difftree info in git_patchset_body Preparation for converting git_blobdiff and git_blobdiff_plain to use git-diff-tree patch format to generate patches. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 80de7b6..56a47ab 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1624,7 +1624,7 @@ sub git_patchset_body { my $patch_idx = 0; my $in_header = 0; my $patch_found = 0; - my %diffinfo; + my $diffinfo; print "
\n"; @@ -1643,54 +1643,59 @@ sub git_patchset_body { } print "
\n"; - %diffinfo = parse_difftree_raw_line($difftree->[$patch_idx++]); + if (ref($difftree->[$patch_idx]) eq "HASH") { + $diffinfo = $difftree->[$patch_idx]; + } else { + $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); + } + $patch_idx++; # for now, no extended header, hence we skip empty patches # companion to next LINE if $in_header; - if ($diffinfo{'from_id'} eq $diffinfo{'to_id'}) { # no change + if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change $in_header = 1; next LINE; } - if ($diffinfo{'status'} eq "A") { # added - print "
" . file_type($diffinfo{'to_mode'}) . ":" . + if ($diffinfo->{'status'} eq "A") { # added + print "
" . file_type($diffinfo->{'to_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'file'})}, - $diffinfo{'to_id'}) . "(new)" . + hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, + $diffinfo->{'to_id'}) . "(new)" . "
\n"; # class="diff_info" - } elsif ($diffinfo{'status'} eq "D") { # deleted - print "
" . file_type($diffinfo{'from_mode'}) . ":" . + } elsif ($diffinfo->{'status'} eq "D") { # deleted + print "
" . file_type($diffinfo->{'from_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'file'})}, - $diffinfo{'from_id'}) . "(deleted)" . + hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, + $diffinfo->{'from_id'}) . "(deleted)" . "
\n"; # class="diff_info" - } elsif ($diffinfo{'status'} eq "R" || # renamed - $diffinfo{'status'} eq "C") { # copied + } elsif ($diffinfo->{'status'} eq "R" || # renamed + $diffinfo->{'status'} eq "C") { # copied print "
" . - file_type($diffinfo{'from_mode'}) . ":" . + file_type($diffinfo->{'from_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'from_file'})}, - $diffinfo{'from_id'}) . + hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'from_file'})}, + $diffinfo->{'from_id'}) . " -> " . - file_type($diffinfo{'to_mode'}) . ":" . + file_type($diffinfo->{'to_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'to_file'})}, - $diffinfo{'to_id'}); + hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'to_file'})}, + $diffinfo->{'to_id'}); print "
\n"; # class="diff_info" } else { # modified, mode changed, ... print "
" . - file_type($diffinfo{'from_mode'}) . ":" . + file_type($diffinfo->{'from_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'file'})}, - $diffinfo{'from_id'}) . + hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, + $diffinfo->{'from_id'}) . " -> " . - file_type($diffinfo{'to_mode'}) . ":" . + file_type($diffinfo->{'to_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'file'})}, - $diffinfo{'to_id'}); + hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, + $diffinfo->{'to_id'}); print "
\n"; # class="diff_info" } -- cgit v0.10.2-6-g49f6 From e4e4f825455f2903e4d015e51c09ec0527a0be86 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:04:13 +0200 Subject: gitweb: Parse two-line from-file/to-file diff header in git_patchset_body Parse two-line from-file/to-file unified diff header in git_patchset_body directly, instead of leaving pretty-printing to format_diff_line function. Hashes as from-file/to-file are replaced by proper from-file and to-file names (from $diffinfo); in the future we can put hyperlinks there. This makes possible to do blobdiff with only blobs hashes. The lines in two-line unified diff header have now class "from_file" and "to_file"; the style is chosen to match previous output (classes "rem" and "add" because of '-' and '+' as first character of patch line). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 5eaa24f..0912361 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -273,10 +273,12 @@ td.mode { font-family: monospace; } +div.diff.to_file, div.diff.add { color: #008800; } +div.diff.from_file, div.diff.rem { color: #cc0000; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 56a47ab..b2159bb 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1706,8 +1706,24 @@ sub git_patchset_body { if ($in_header && $patch_line =~ m/^---/) { - #print "
\n" + #print "
\n"; # class="diff extended_header" $in_header = 0; + + my $file = $diffinfo->{'from_file'}; + $file ||= $diffinfo->{'file'}; + $patch_line =~ s|a/[0-9a-fA-F]{40}|a/$file|g; + print "
" . esc_html($patch_line) . "
\n"; + + $patch_line = <$fd>; + chomp $patch_line; + + #$patch_line =~ m/^+++/; + $file = $diffinfo->{'to_file'}; + $file ||= $diffinfo->{'file'}; + $patch_line =~ s|b/[0-9a-fA-F]{40}|b/$file|g; + print "
" . esc_html($patch_line) . "
\n"; + + next LINE; } next LINE if $in_header; -- cgit v0.10.2-6-g49f6 From ef10ee877f6d4db4c9bc65d0e36b3670f0fe114e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:05:07 +0200 Subject: gitweb: Add invisible hyperlink to from-file/to-file diff header Change replacing hashes as from-file/to-file with filenames from difftree to adding invisible (except underlining on hover/mouseover) hyperlink to from-file/to-file blob. /dev/null as from-file or to-file is not changed (is not hyperlinked). This makes two-file from-file/to-file unified diff header parsing in git_patchset_body more generic, and not only for legacy blobdiffs. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 0912361..afd9e8a 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -273,11 +273,21 @@ td.mode { font-family: monospace; } +div.diff a.list { + text-decoration: none; +} + +div.diff a.list:hover { + text-decoration: underline; +} + +div.diff.to_file a.list, div.diff.to_file, div.diff.add { color: #008800; } +div.diff.from_file a.list, div.diff.from_file, div.diff.rem { color: #cc0000; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b2159bb..2995342 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1711,8 +1711,11 @@ sub git_patchset_body { my $file = $diffinfo->{'from_file'}; $file ||= $diffinfo->{'file'}; - $patch_line =~ s|a/[0-9a-fA-F]{40}|a/$file|g; - print "
" . esc_html($patch_line) . "
\n"; + $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, file_name=>$file), + -class => "list"}, esc_html($file)); + $patch_line =~ s|a/.*$|a/$file|g; + print "
$patch_line
\n"; $patch_line = <$fd>; chomp $patch_line; @@ -1720,8 +1723,11 @@ sub git_patchset_body { #$patch_line =~ m/^+++/; $file = $diffinfo->{'to_file'}; $file ||= $diffinfo->{'file'}; - $patch_line =~ s|b/[0-9a-fA-F]{40}|b/$file|g; - print "
" . esc_html($patch_line) . "
\n"; + $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, file_name=>$file), + -class => "list"}, esc_html($file)); + $patch_line =~ s|b/.*|b/$file|g; + print "
$patch_line
\n"; next LINE; } -- cgit v0.10.2-6-g49f6 From c2c8ff24380efe0149815fc014445e31f81c3403 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:05:45 +0200 Subject: gitweb: Always display link to blobdiff_plain in git_blobdiff Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2995342..e631188 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2790,18 +2790,18 @@ sub git_commit { sub git_blobdiff { mkdir($git_temp, 0700); git_header_html(); + my $formats_nav = + $cgi->a({-href => href(action=>"blobdiff_plain", + hash=>$hash, hash_parent=>$hash_parent, + hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, + file_name=>$file_name, file_parent=>$file_parent)}, + "plain"); if (defined $hash_base && (my %co = parse_commit($hash_base))) { - my $formats_nav = - $cgi->a({-href => href(action=>"blobdiff_plain", - hash=>$hash, hash_parent=>$hash_parent, - hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, - file_name=>$file_name, file_parent=>$file_parent)}, - "plain"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print <

+
$hash vs $hash_parent
HTML } -- cgit v0.10.2-6-g49f6 From 990dd0de51530a47f4c8fad961268d4b8545a7be Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:06:49 +0200 Subject: gitweb: Change here-doc back for style consistency in git_blobdiff Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e631188..40f65be 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2800,10 +2800,8 @@ sub git_blobdiff { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { - print <
$formats_nav
-
$hash vs $hash_parent
-HTML + print "

$formats_nav
\n"; + print "
$hash vs $hash_parent
\n"; } git_print_page_path($file_name, "blob", $hash_base); print "
\n" . -- cgit v0.10.2-6-g49f6 From 7c5e2ebb5d8ca3d8428e19593c69a4f329d64855 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:13:34 +0200 Subject: gitweb: Use git-diff-tree or git-diff patch output for blobdiff This is second part of removing gitweb dependency on external diff (used in git_diff_print). Get rid of git_diff_print invocation in git_blobdiff, and use either git-diff-tree (when both hash_base and hash_parent_base are provided) patch format or git-diff patch format (when only hash and hash_parent are provided) for output. Supported URI schemes, and output formats: * New URI scheme: both hash_base and hash_parent_base (trees-ish containing blobs versions we want to compare) are provided. Also either filename is provided, or hash (of blob) is provided (we try to find filename then). For this scheme we have copying and renames detection, mode changes, file types etc., and information extended diff header is correct. * Old URI scheme: hash_parent_base is not provided, we use hash and hash_parent to directly compare blobs using git-diff. If no filename is given, blobs hashes are used in place of filenames. This scheme has always "blob" as file type, it cannot detect mode changes, and we rely on CGI parameters to provide name of the file. Added git_to_hash subroutine, which transforms symbolic name or list of symbolic name to hash or list of hashes using git-rev-parse. To have "blob" instead of "unknown" (or "file" regardless of the type) in "gitweb diff header" for legacy scheme, file_type function now returns its argument if it is not octal string. Added support for fake "2" status code in git_patchset_body. Such code is generated by git_blobdiff in legacy scheme case. ATTENTION: The order of arguments (operands) to git-diff is reversed (sic!) to have correct diff in the legacy (no hash_parent_base) case. $hash_parent, $hash ordering is commented out, as it gives reversed patch (at least for git version 1.4.1.1) as compared to output in new scheme and output of older gitweb version. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 40f65be..0fa35f3 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -454,7 +454,13 @@ sub mode_str { # convert file mode in octal to file type string sub file_type { - my $mode = oct shift; + my $mode = shift; + + if ($mode !~ m/^[0-7]+$/) { + return $mode; + } else { + $mode = oct $mode; + } if (S_ISDIR($mode & S_IFMT)) { return "directory"; @@ -618,6 +624,26 @@ sub git_get_hash_by_path { return $3; } +# converts symbolic name to hash +sub git_to_hash { + my @params = @_; + return undef unless @params; + + open my $fd, "-|", $GIT, "rev-parse", @params + or return undef; + my @hashes = map { chomp; $_ } <$fd>; + close $fd; + + if (wantarray) { + return @hashes; + } elsif (scalar(@hashes) == 1) { + # single hash + return $hashes[0]; + } else { + return \@hashes; + } +} + ## ...................................................................... ## git utility functions, directly accessing git repository @@ -1672,7 +1698,8 @@ sub git_patchset_body { "
\n"; # class="diff_info" } elsif ($diffinfo->{'status'} eq "R" || # renamed - $diffinfo->{'status'} eq "C") { # copied + $diffinfo->{'status'} eq "C" || # copied + $diffinfo->{'status'} eq "2") { # with two filenames (from git_blobdiff) print "
" . file_type($diffinfo->{'from_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, @@ -2788,14 +2815,102 @@ sub git_commit { } sub git_blobdiff { - mkdir($git_temp, 0700); - git_header_html(); + my $fd; + my @difftree; + my %diffinfo; + + # preparing $fd and %diffinfo for git_patchset_body + # new style URI + if (defined $hash_base && defined $hash_parent_base) { + if (defined $file_name) { + # read raw output + open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', $hash_parent_base, $hash_base, + "--", $file_name + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + @difftree + or die_error('404 Not Found', "Blob diff not found"); + + } elsif (defined $hash) { # try to find filename from $hash + if ($hash !~ /[0-9a-fA-F]{40}/) { + $hash = git_to_hash($hash); + } + + # read filtered raw output + open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', $hash_parent_base, $hash_base + or die_error(undef, "Open git-diff-tree failed"); + @difftree = + # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' + # $hash == to_id + grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ } + map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + @difftree + or die_error('404 Not Found', "Blob diff not found"); + + } else { + die_error('404 Not Found', "Missing one of the blob diff parameters"); + } + + if (@difftree > 1) { + die_error('404 Not Found', "Ambiguous blob diff specification"); + } + + %diffinfo = parse_difftree_raw_line($difftree[0]); + $file_parent ||= $diffinfo{'from_file'} || $file_name || $diffinfo{'file'}; + $file_name ||= $diffinfo{'to_file'} || $diffinfo{'file'}; + + $hash_parent ||= $diffinfo{'from_id'}; + $hash ||= $diffinfo{'to_id'}; + + # open patch output + open $fd, "-|", $GIT, "diff-tree", '-r', '-p', '-M', '-C', $hash_parent_base, $hash_base, + "--", $file_name + or die_error(undef, "Open git-diff-tree failed"); + } + + # old/legacy style URI + if (!%diffinfo && # if new style URI failed + defined $hash && defined $hash_parent) { + # fake git-diff-tree raw output + $diffinfo{'from_mode'} = $diffinfo{'to_mode'} = "blob"; + $diffinfo{'from_id'} = $hash_parent; + $diffinfo{'to_id'} = $hash; + if (defined $file_name) { + if (defined $file_parent) { + $diffinfo{'status'} = '2'; + $diffinfo{'from_file'} = $file_parent; + $diffinfo{'to_file'} = $file_name; + } else { # assume not renamed + $diffinfo{'status'} = '1'; + $diffinfo{'from_file'} = $file_name; + $diffinfo{'to_file'} = $file_name; + } + } else { # no filename given + $diffinfo{'status'} = '2'; + $diffinfo{'from_file'} = $hash_parent; + $diffinfo{'to_file'} = $hash; + } + + #open $fd, "-|", $GIT, "diff", '-p', $hash_parent, $hash + open $fd, "-|", $GIT, "diff", '-p', $hash, $hash_parent + or die_error(undef, "Open git-diff failed"); + } else { + die_error('404 Not Found', "Missing one of the blob diff parameters") + unless %diffinfo; + } + + # header my $formats_nav = $cgi->a({-href => href(action=>"blobdiff_plain", hash=>$hash, hash_parent=>$hash_parent, hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, file_name=>$file_name, file_parent=>$file_parent)}, "plain"); + git_header_html(); if (defined $hash_base && (my %co = parse_commit($hash_base))) { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); @@ -2803,19 +2918,19 @@ sub git_blobdiff { print "

$formats_nav
\n"; print "
$hash vs $hash_parent
\n"; } - git_print_page_path($file_name, "blob", $hash_base); - print "
\n" . - "
blob:" . - $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, - hash_base=>$hash_parent_base, file_name=>($file_parent || $file_name))}, - $hash_parent) . - " -> blob:" . - $cgi->a({-href => href(action=>"blob", hash=>$hash, - hash_base=>$hash_base, file_name=>$file_name)}, - $hash) . - "
\n"; - git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash); - print "
"; # page_body + if (defined $file_name) { + git_print_page_path($file_name, "blob", $hash_base); + } else { + print "
\n"; + } + + # patch + print "
\n"; + + git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base); + close $fd; + + print "
\n"; # class="page_body" git_footer_html(); } -- cgit v0.10.2-6-g49f6 From 9b71b1f6b3b57b3771151d252a5e22886524a154 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:14:49 +0200 Subject: gitweb: git_blobdiff_plain is git_blobdiff('plain') git_blobdiff and git_blobdiff_plain are now collapsed into one subroutine git_blobdiff, with format (currently 'html' which is default format corresponding to git_blobdiff, and 'plain' corresponding to git_blobdiff_plain) specified in argument. blobdiff_plain format is now generated either by git-diff-tree or by git-diff. Added X-Git-Url: header. From-file and to-file name in header are corrected. Note that for now commitdiff_plain does not detect renames and copying, while blobdiff_plain does. While at it, set expires to "+1d" for non-textual hash ids. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0fa35f3..11b3fdc 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2815,9 +2815,12 @@ sub git_commit { } sub git_blobdiff { + my $format = shift || 'html'; + my $fd; my @difftree; my %diffinfo; + my $expires; # preparing $fd and %diffinfo for git_patchset_body # new style URI @@ -2866,6 +2869,12 @@ sub git_blobdiff { $hash_parent ||= $diffinfo{'from_id'}; $hash ||= $diffinfo{'to_id'}; + # non-textual hash id's can be cached + if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ && + $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) { + $expires = '+1d'; + } + # open patch output open $fd, "-|", $GIT, "diff-tree", '-r', '-p', '-M', '-C', $hash_parent_base, $hash_base, "--", $file_name @@ -2895,6 +2904,13 @@ sub git_blobdiff { $diffinfo{'to_file'} = $hash; } + # non-textual hash id's can be cached + if ($hash =~ m/^[0-9a-fA-F]{40}$/ && + $hash_parent =~ m/^[0-9a-fA-F]{40}$/) { + $expires = '+1d'; + } + + # open patch output #open $fd, "-|", $GIT, "diff", '-p', $hash_parent, $hash open $fd, "-|", $GIT, "diff", '-p', $hash, $hash_parent or die_error(undef, "Open git-diff failed"); @@ -2904,40 +2920,67 @@ sub git_blobdiff { } # header - my $formats_nav = - $cgi->a({-href => href(action=>"blobdiff_plain", - hash=>$hash, hash_parent=>$hash_parent, - hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, - file_name=>$file_name, file_parent=>$file_parent)}, - "plain"); - git_header_html(); - if (defined $hash_base && (my %co = parse_commit($hash_base))) { - git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_print_header_div('commit', esc_html($co{'title'}), $hash_base); - } else { - print "

$formats_nav
\n"; - print "
$hash vs $hash_parent
\n"; - } - if (defined $file_name) { - git_print_page_path($file_name, "blob", $hash_base); + if ($format eq 'html') { + my $formats_nav = + $cgi->a({-href => href(action=>"blobdiff_plain", + hash=>$hash, hash_parent=>$hash_parent, + hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, + file_name=>$file_name, file_parent=>$file_parent)}, + "plain"); + git_header_html(undef, $expires); + if (defined $hash_base && (my %co = parse_commit($hash_base))) { + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + } else { + print "

$formats_nav
\n"; + print "
$hash vs $hash_parent
\n"; + } + if (defined $file_name) { + git_print_page_path($file_name, "blob", $hash_base); + } else { + print "
\n"; + } + + } elsif ($format eq 'plain') { + print $cgi->header( + -type => 'text/plain', + -charset => 'utf-8', + -expires => $expires, + -content_disposition => qq(inline; filename="${file_name}.patch")); + + print "X-Git-Url: " . $cgi->self_url() . "\n\n"; + } else { - print "
\n"; + die_error(undef, "Unknown blobdiff format"); } # patch - print "
\n"; + if ($format eq 'html') { + print "
\n"; - git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base); - close $fd; + git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base); + close $fd; - print "
\n"; # class="page_body" - git_footer_html(); + print "
\n"; # class="page_body" + git_footer_html(); + + } else { + while (my $line = <$fd>) { + $line =~ s!a/($hash|$hash_parent)!a/$diffinfo{'from_file'}!g; + $line =~ s!b/($hash|$hash_parent)!b/$diffinfo{'to_file'}!g; + + print $line; + + last if $line =~ m!^\+\+\+!; + } + local $/ = undef; + print <$fd>; + close $fd; + } } sub git_blobdiff_plain { - mkdir($git_temp, 0700); - print $cgi->header(-type => "text/plain", -charset => 'utf-8'); - git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash, "plain"); + git_blobdiff('plain'); } sub git_commitdiff { -- cgit v0.10.2-6-g49f6 From 8cce8e3ddb2c2f7e0ab028012ae4dc729c8bf3f9 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:15:27 +0200 Subject: gitweb: Remove git_diff_print subroutine Remove git_diff_print subroutine, used to print diff in previous versions of "diff" actions, namely git_commitdiff, git_commitdiff_plain, git_blobdiff, git_blobdiff_plain. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 11b3fdc..35f0da1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1975,77 +1975,6 @@ sub git_heads_body { print "\n"; } -## ---------------------------------------------------------------------- -## functions printing large fragments, format as one of arguments - -sub git_diff_print { - my $from = shift; - my $from_name = shift; - my $to = shift; - my $to_name = shift; - my $format = shift || "html"; - - my $from_tmp = "/dev/null"; - my $to_tmp = "/dev/null"; - my $pid = $$; - - # create tmp from-file - if (defined $from) { - $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; - open my $fd2, "> $from_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $from; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } - - # create tmp to-file - if (defined $to) { - $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; - open my $fd2, "> $to_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $to; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } - - open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp"; - if ($format eq "plain") { - undef $/; - print <$fd>; - $/ = "\n"; - } else { - while (my $line = <$fd>) { - chomp $line; - my $char = substr($line, 0, 1); - my $diff_class = ""; - if ($char eq '+') { - $diff_class = " add"; - } elsif ($char eq "-") { - $diff_class = " rem"; - } elsif ($char eq "@") { - $diff_class = " chunk_header"; - } elsif ($char eq "\\") { - # skip errors - next; - } - $line = untabify($line); - print "
" . esc_html($line) . "
\n"; - } - } - close $fd; - - if (defined $from) { - unlink($from_tmp); - } - if (defined $to) { - unlink($to_tmp); - } -} - - ## ====================================================================== ## ====================================================================== ## actions -- cgit v0.10.2-6-g49f6 From 903acca75329841996bbf0c3a32a7cfeb3ae0d0f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:35:27 +0200 Subject: gitweb: Remove creating directory for temporary files Remove $git_temp variable which held location for temporary files needed by git_diff_print, and removed creating $git_temp directory. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 35f0da1..0ae1810 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -31,9 +31,6 @@ our $GIT = "++GIT_BINDIR++/git"; #our $projectroot = "/pub/scm"; our $projectroot = "++GITWEB_PROJECTROOT++"; -# location for temporary files needed for diffs -our $git_temp = "/tmp/gitweb"; - # target of the home link on top of all pages our $home_link = $my_uri || "/"; @@ -144,9 +141,6 @@ require $GITWEB_CONFIG if -e $GITWEB_CONFIG; our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; $projects_list ||= $projectroot; -if (! -d $git_temp) { - mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp"); -} # ====================================================================== # input validation and dispatch -- cgit v0.10.2-6-g49f6 From f7122265fcb821208c290ad9817d296dec19d523 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Fri, 25 Aug 2006 02:48:04 +0200 Subject: describe: fix off-by-one error in --abbrev=40 handling Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano diff --git a/describe.c b/describe.c index 2b9301f..5dd8b2e 100644 --- a/describe.c +++ b/describe.c @@ -42,7 +42,7 @@ static void add_to_known_names(const char *path, struct commit_name *name = xmalloc(sizeof(struct commit_name) + len); name->commit = commit; - name->prio = prio; + name->prio = prio; memcpy(name->path, path, len); idx = names; if (idx >= allocs) { @@ -154,7 +154,7 @@ int main(int argc, char **argv) tags = 1; else if (!strncmp(arg, "--abbrev=", 9)) { abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev < MINIMUM_ABBREV || 40 <= abbrev) + if (abbrev < MINIMUM_ABBREV || 40 < abbrev) abbrev = DEFAULT_ABBREV; } else -- cgit v0.10.2-6-g49f6 From 5a990e45f920b50aa6b81823120223fb50f56d97 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 25 Aug 2006 12:28:18 -0700 Subject: git-svn: establish new connections on commit after fork SVN seems to have a problem with https:// repositories from time-to-time when doing multiple, sequential commits. This problem is not consistently reproducible without the patch, but it should go away entirely with this patch... Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 0d58bb9..b311c3d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -500,6 +500,8 @@ sub commit_lib { my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; + my $repo; + ($repo, $SVN_PATH) = repo_path_split($SVN_URL); set_svn_commit_env(); foreach my $c (@revs) { my $log_msg = get_commit_message($c, $commit_msg); @@ -508,6 +510,8 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { + $SVN_LOG = libsvn_connect($repo); + $SVN = libsvn_connect($repo); my $ed = SVN::Git::Editor->new( { r => $r_last, ra => $SVN, -- cgit v0.10.2-6-g49f6 From 2e93115ed84901fb8ee8447a1baf37cd8c343d8e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 25 Aug 2006 12:48:23 -0700 Subject: git-svn: recommend rebase for syncing against an SVN repo Does this make sense to other git-svn users out there? pull can give funky history unless you understand how git-svn works internally, which users should not be expected to do. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 14bdef2..2fa5e94 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -235,12 +235,26 @@ Tracking and contributing to an Subversion managed-project: git-svn commit [ ...] # Commit all the git commits from my-branch that don't exist in SVN: git-svn commit remotes/git-svn..my-branch -# Something is committed to SVN, pull the latest into your branch: - git-svn fetch && git pull . remotes/git-svn +# Something is committed to SVN, rebase the latest into your branch: + git-svn fetch && git rebase remotes/git-svn # Append svn:ignore settings to the default git exclude file: git-svn show-ignore >> .git/info/exclude ------------------------------------------------------------------------ +REBASE VS. PULL +--------------- + +Originally, git-svn recommended that the remotes/git-svn branch be +pulled from. This is because the author favored 'git-svn commit B' +to commit a single head rather than the 'git-svn commit A..B' notation +to commit multiple commits. + +If you use 'git-svn commit A..B' to commit several diffs and you do not +have the latest remotes/git-svn merged into my-branch, you should use +'git rebase' to update your work branch instead of 'git pull'. 'pull' +can cause non-linear history to be flattened when committing into SVN, +which can lead to merge commits reversing previous commits in SVN. + DESIGN PHILOSOPHY ----------------- Merge tracking in Subversion is lacking and doing branched development @@ -339,6 +353,10 @@ the possible corner cases (git doesn't do it, either). Renamed and copied files are fully supported if they're similar enough for git to detect them. +SEE ALSO +-------- +gitlink:git-rebase[1] + Author ------ Written by Eric Wong . -- cgit v0.10.2-6-g49f6 From b22d449721b22f6ec090f22c418ae6b0a560f78d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 26 Aug 2006 00:01:23 -0700 Subject: git-svn: add the 'dcommit' command This is a high-level wrapper around the 'commit-diff' command and used to produce cleaner history against the mirrored repository through rebase/reset usage. It's basically a more polished version of this: for i in `git rev-list --no-merges remotes/git-svn..HEAD | tac`; do git-svn commit-diff $i~1 $i done git reset --hard remotes/git-svn Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 2fa5e94..b7b63f7 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -54,6 +54,15 @@ remotes/git-svn. See '<>' if you are interested in manually joining branches on commit. +'dcommit':: + Commit all diffs from the current HEAD directly to the SVN + repository, and then rebase or reset (depending on whether or + not there is a diff between SVN and HEAD). It is recommended + that you run git-svn fetch and rebase (not pull) your commits + against the latest changes in the SVN repository. + This is advantageous over 'commit' (below) because it produces + cleaner, more linear history. + 'commit':: Commit specified commit or tree objects to SVN. This relies on your imported fetch data being up-to-date. This makes @@ -157,6 +166,24 @@ after the authors-file is modified should continue operation. repo-config key: svn.authors-file +-m:: +--merge:: +-s:: +--strategy=:: + +These are only used with the 'dcommit' command. + +Passed directly to git-rebase when using 'dcommit' if a +'git-reset' cannot be used (see dcommit). + +-n:: +--dry-run:: + +This is only used with the 'dcommit' command. + +Print out the series of git arguments that would show +which diffs would be committed to SVN. + -- ADVANCED OPTIONS diff --git a/git-svn.perl b/git-svn.perl index b311c3d..9382a15 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -51,7 +51,8 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_message, $_file, $_follow_parent, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_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); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -118,6 +119,11 @@ my %cmd = ( { 'message|m=s' => \$_message, 'file|F=s' => \$_file, %cmt_opts } ], + dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', + { 'merge|m|M' => \$_merge, + 'strategy|s=s' => \$_strategy, + 'dry-run|n' => \$_dry_run, + %cmt_opts } ], ); my $cmd; @@ -561,6 +567,33 @@ sub commit_lib { unlink $commit_msg; } +sub dcommit { + my $gs = "refs/remotes/$GIT_SVN"; + chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD")); + foreach my $d (reverse @refs) { + if ($_dry_run) { + print "diff-tree $d~1 $d\n"; + } else { + commit_diff("$d~1", $d); + } + } + return if $_dry_run; + fetch(); + my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs); + my @finish; + if (@diff) { + @finish = qw/rebase/; + push @finish, qw/--merge/ if $_merge; + push @finish, "--strategy=$_strategy" if $_strategy; + print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff; + } else { + print "No changes between current HEAD and $gs\n", + "Hard resetting to the latest $gs\n"; + @finish = qw/reset --hard/; + } + sys('git', @finish, $gs); +} + sub show_ignore { $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); $_use_lib ? show_ignore_lib() : show_ignore_cmd(); -- cgit v0.10.2-6-g49f6 From 030b52087f0a4b7b1178d34839868ce438eb2f0e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 26 Aug 2006 02:13:05 +0200 Subject: gitweb: git_annotate didn't expect negative numeric timezone Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 04282fa..966c54a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1882,7 +1882,7 @@ HTML chomp $line; $line_class_num = ($line_class_num + 1) % $line_class_len; - if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) { + if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) [+-]\d\d\d\d\t(\d+)\)(.*)$/) { $long_rev = $1; $author = $2; $time = $3; -- cgit v0.10.2-6-g49f6 From 73c9083f52ac918b530c51887bbf5fd0a1119b4c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 26 Aug 2006 12:33:17 +0200 Subject: gitweb: Remove workaround for git-diff bug fixed in f82cd3c Remove workaround in git_blobdiff for error in git-diff (showing reversed diff for diff of blobs), corrected in commit f82cd3c Fix "git diff blob1 blob2" showing the diff in reverse. which is post 1.4.2-rc2 commit. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e00a6ed..5d321e9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2834,8 +2834,7 @@ sub git_blobdiff { } # open patch output - #open $fd, "-|", $GIT, "diff", '-p', $hash_parent, $hash - open $fd, "-|", $GIT, "diff", '-p', $hash, $hash_parent + open $fd, "-|", $GIT, "diff", '-p', $hash_parent, $hash or die_error(undef, "Open git-diff failed"); } else { die_error('404 Not Found', "Missing one of the blob diff parameters") -- cgit v0.10.2-6-g49f6 From 17848fc67cf466b805f35608230f4df898943ec1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 26 Aug 2006 19:14:22 +0200 Subject: gitweb: Improve comments about gitweb features configuration Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5d321e9..2db99c3 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -67,9 +67,16 @@ our $mimetypes_file = undef; # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( - # feature => {'sub' => feature-sub, 'override' => allow-override, 'default' => [ default options...] - # if feature is overridable, feature-sub will be called with default options; - # return value indicates if to enable specified feature + # feature => { + # 'sub' => feature-sub (subroutine), + # 'override' => allow-override (boolean), + # 'default' => [ default options...] (array reference)} + # + # if feature is overridable (it means that allow-override has true value, + # then feature-sub will be called with default options as parameters; + # return value of feature-sub indicates if to enable specified feature + # + # use gitweb_check_feature() to check if is enabled 'blame' => { 'sub' => \&feature_blame, @@ -95,9 +102,9 @@ sub gitweb_check_feature { } # To enable system wide have in $GITWEB_CONFIG -# $feature{'blame'}{'default'} = [1]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'blame'}{'override'} = 1; +# $feature{'blame'}{'default'} = [1]; +# To have project specific config enable override in $GITWEB_CONFIG +# $feature{'blame'}{'override'} = 1; # and in project config gitweb.blame = 0|1; sub feature_blame { @@ -113,9 +120,9 @@ sub feature_blame { } # To disable system wide have in $GITWEB_CONFIG -# $feature{'snapshot'}{'default'} = [undef]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'blame'}{'override'} = 1; +# $feature{'snapshot'}{'default'} = [undef]; +# To have project specific config enable override in $GITWEB_CONFIG +# $feature{'blame'}{'override'} = 1; # and in project config gitweb.snapshot = none|gzip|bzip2 sub feature_snapshot { -- cgit v0.10.2-6-g49f6 From f2e73302994b6072e556df1af998c0afd643e833 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 26 Aug 2006 19:14:25 +0200 Subject: gitweb: blobs defined by non-textual hash ids can be cached Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2db99c3..0df59af 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2363,6 +2363,12 @@ sub git_heads { } sub git_blob_plain { + # blobs defined by non-textual hash id's can be cached + my $expires; + if ($hash =~ m/^[0-9a-fA-F]{40}$/) { + $expires = "+1d"; + } + if (!defined $hash) { if (defined $file_name) { my $base = $hash_base || git_get_head_hash($project); @@ -2386,8 +2392,10 @@ sub git_blob_plain { $save_as .= '.txt'; } - print $cgi->header(-type => "$type", - -content_disposition => "inline; filename=\"$save_as\""); + print $cgi->header( + -type => "$type", + -expires=>$expires, + -content_disposition => "inline; filename=\"$save_as\""); undef $/; binmode STDOUT, ':raw'; print <$fd>; @@ -2397,6 +2405,12 @@ sub git_blob_plain { } sub git_blob { + # blobs defined by non-textual hash id's can be cached + my $expires; + if ($hash =~ m/^[0-9a-fA-F]{40}$/) { + $expires = "+1d"; + } + if (!defined $hash) { if (defined $file_name) { my $base = $hash_base || git_get_head_hash($project); @@ -2414,7 +2428,7 @@ sub git_blob { close $fd; return git_blob_plain($mimetype); } - git_header_html(); + git_header_html(undef, $expires); my $formats_nav = ''; if (defined $hash_base && (my %co = parse_commit($hash_base))) { if (defined $file_name) { -- cgit v0.10.2-6-g49f6 From de530aaa4b57b1edd9b973d814d7df8354752ccc Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 26 Aug 2006 04:10:43 -0400 Subject: Reorganize/rename unpack_non_delta_entry to unpack_compressed_entry. This function was moved above unpack_delta_entry so we can call it from within unpack_delta_entry without a forward declaration. This change looks worse than it is. Its really just a relocation of unpack_non_delta_entry to earlier in the file and renaming the function to unpack_compressed_entry. No other changes were made. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 769a809..53beb91 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1057,6 +1057,33 @@ static int packed_object_info(struct pack_entry *entry, return 0; } +static void *unpack_compressed_entry(unsigned char *data, + unsigned long size, + unsigned long left) +{ + int st; + z_stream stream; + unsigned char *buffer; + + buffer = xmalloc(size + 1); + buffer[size] = 0; + memset(&stream, 0, sizeof(stream)); + stream.next_in = data; + stream.avail_in = left; + stream.next_out = buffer; + stream.avail_out = size; + + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if ((st != Z_STREAM_END) || stream.total_out != size) { + free(buffer); + return NULL; + } + + return buffer; +} + static void *unpack_delta_entry(unsigned char *base_sha1, unsigned long delta_size, unsigned long left, @@ -1110,33 +1137,6 @@ static void *unpack_delta_entry(unsigned char *base_sha1, return result; } -static void *unpack_non_delta_entry(unsigned char *data, - unsigned long size, - unsigned long left) -{ - int st; - z_stream stream; - unsigned char *buffer; - - buffer = xmalloc(size + 1); - buffer[size] = 0; - memset(&stream, 0, sizeof(stream)); - stream.next_in = data; - stream.avail_in = left; - stream.next_out = buffer; - stream.avail_out = size; - - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - inflateEnd(&stream); - if ((st != Z_STREAM_END) || stream.total_out != size) { - free(buffer); - return NULL; - } - - return buffer; -} - static void *unpack_entry(struct pack_entry *entry, char *type, unsigned long *sizep) { @@ -1185,7 +1185,7 @@ void *unpack_entry_gently(struct pack_entry *entry, return NULL; } *sizep = size; - retval = unpack_non_delta_entry(pack, size, left); + retval = unpack_compressed_entry(pack, size, left); return retval; } -- cgit v0.10.2-6-g49f6 From 7c3e8be30718f9866f330044fe2a0a7c5b2c3461 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 26 Aug 2006 04:11:02 -0400 Subject: Reuse compression code in unpack_compressed_entry. [PATCH 2/5] Reuse compression code in unpack_compressed_entry. This cleans up the code by reusing a perfectly good decompression implementation at the expense of 1 extra byte of memory allocated in temporary memory while the delta is being decompressed and applied to the base. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 53beb91..3d7358f 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1092,10 +1092,8 @@ static void *unpack_delta_entry(unsigned char *base_sha1, struct packed_git *p) { struct pack_entry base_ent; - void *data, *delta_data, *result, *base; - unsigned long data_size, result_size, base_size; - z_stream stream; - int st; + void *delta_data, *result, *base; + unsigned long result_size, base_size; if (left < 20) die("truncated pack file"); @@ -1109,23 +1107,8 @@ static void *unpack_delta_entry(unsigned char *base_sha1, die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); - data = base_sha1 + 20; - data_size = left - 20; - delta_data = xmalloc(delta_size); - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = data; - stream.avail_in = data_size; - stream.next_out = delta_data; - stream.avail_out = delta_size; - - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - inflateEnd(&stream); - if ((st != Z_STREAM_END) || stream.total_out != delta_size) - die("delta data unpack failed"); - + delta_data = unpack_compressed_entry(base_sha1 + 20, + delta_size, left - 20); result = patch_delta(base, base_size, delta_data, delta_size, &result_size); -- cgit v0.10.2-6-g49f6 From 5a18f540a5f8f8ba8b4f8027ed7742bfec85433c Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 26 Aug 2006 04:11:36 -0400 Subject: Cleanup unpack_entry_gently and friends to use type_name array. [PATCH 3/5] Cleanup unpack_entry_gently and friends to use type_name array. This change allows combining all of the non-delta entries into a single case, as well as to remove an unnecessary local variable in unpack_entry_gently. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 3d7358f..461768c 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -996,16 +996,10 @@ void packed_object_info_detail(struct pack_entry *e, } switch (kind) { case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); + strcpy(type, type_names[kind]); break; default: die("corrupted pack file %s containing object of kind %d", @@ -1036,16 +1030,10 @@ static int packed_object_info(struct pack_entry *entry, unuse_packed_git(p); return retval; case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); + strcpy(type, type_names[kind]); break; default: die("corrupted pack file %s containing object of kind %d", @@ -1143,33 +1131,23 @@ void *unpack_entry_gently(struct pack_entry *entry, unsigned long offset, size, left; unsigned char *pack; enum object_type kind; - void *retval; offset = unpack_object_header(p, entry->offset, &kind, &size); pack = (unsigned char *) p->pack_base + offset; left = p->pack_size - offset; switch (kind) { case OBJ_DELTA: - retval = unpack_delta_entry(pack, size, left, type, sizep, p); - return retval; + return unpack_delta_entry(pack, size, left, type, sizep, p); case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); - break; + strcpy(type, type_names[kind]); + *sizep = size; + return unpack_compressed_entry(pack, size, left); default: return NULL; } - *sizep = size; - retval = unpack_compressed_entry(pack, size, left); - return retval; } int num_packed_objects(const struct packed_git *p) -- cgit v0.10.2-6-g49f6 From 465b26eeef79e3cb4cb6276e405e59d986069926 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 26 Aug 2006 04:12:04 -0400 Subject: Cleanup unpack_object_header to use only offsets. If we're always incrementing both the offset and the pointer we aren't gaining anything by keeping both. Instead just use the offset since that's what we were given and what we are expected to return. Also using offset is likely to make it easier to remap the pack in the future should partial mapping of very large packs get implemented. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 461768c..7f4145c 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -917,23 +917,19 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of enum object_type *type, unsigned long *sizep) { unsigned shift; - unsigned char *pack, c; + unsigned char c; unsigned long size; if (offset >= p->pack_size) die("object offset outside of pack file"); - - pack = (unsigned char *) p->pack_base + offset; - c = *pack++; - offset++; + c = *((unsigned char *)p->pack_base + offset++); *type = (c >> 4) & 7; size = c & 15; shift = 4; while (c & 0x80) { if (offset >= p->pack_size) die("object offset outside of pack file"); - c = *pack++; - offset++; + c = *((unsigned char *)p->pack_base + offset++); size += (c & 0x7f) << shift; shift += 7; } -- cgit v0.10.2-6-g49f6 From eb950c192a0e0dfc069e16e857cbe1ae16d8ad82 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 26 Aug 2006 04:12:27 -0400 Subject: Convert unpack_entry_gently and friends to use offsets. Change unpack_entry_gently and its helper functions to use offsets rather than addresses and left counts to supply pack position information. In most cases this makes the code easier to follow, and it reduces the number of local variables in a few functions. It also better prepares this code for mapping partial segments of packs and altering what regions of a pack are mapped while unpacking an entry. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 7f4145c..dd9bcaa 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1041,9 +1041,9 @@ static int packed_object_info(struct pack_entry *entry, return 0; } -static void *unpack_compressed_entry(unsigned char *data, - unsigned long size, - unsigned long left) +static void *unpack_compressed_entry(struct packed_git *p, + unsigned long offset, + unsigned long size) { int st; z_stream stream; @@ -1052,8 +1052,8 @@ static void *unpack_compressed_entry(unsigned char *data, buffer = xmalloc(size + 1); buffer[size] = 0; memset(&stream, 0, sizeof(stream)); - stream.next_in = data; - stream.avail_in = left; + stream.next_in = (unsigned char*)p->pack_base + offset; + stream.avail_in = p->pack_size - offset; stream.next_out = buffer; stream.avail_out = size; @@ -1068,21 +1068,22 @@ static void *unpack_compressed_entry(unsigned char *data, return buffer; } -static void *unpack_delta_entry(unsigned char *base_sha1, +static void *unpack_delta_entry(struct packed_git *p, + unsigned long offset, unsigned long delta_size, - unsigned long left, char *type, - unsigned long *sizep, - struct packed_git *p) + unsigned long *sizep) { struct pack_entry base_ent; void *delta_data, *result, *base; unsigned long result_size, base_size; + unsigned char* base_sha1; - if (left < 20) + if ((offset + 20) >= p->pack_size) die("truncated pack file"); /* The base entry _must_ be in the same pack */ + base_sha1 = (unsigned char*)p->pack_base + offset; if (!find_pack_entry_one(base_sha1, &base_ent, p)) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); @@ -1091,8 +1092,7 @@ static void *unpack_delta_entry(unsigned char *base_sha1, die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); - delta_data = unpack_compressed_entry(base_sha1 + 20, - delta_size, left - 20); + delta_data = unpack_compressed_entry(p, offset + 20, delta_size); result = patch_delta(base, base_size, delta_data, delta_size, &result_size); @@ -1124,23 +1124,20 @@ void *unpack_entry_gently(struct pack_entry *entry, char *type, unsigned long *sizep) { struct packed_git *p = entry->p; - unsigned long offset, size, left; - unsigned char *pack; + unsigned long offset, size; enum object_type kind; offset = unpack_object_header(p, entry->offset, &kind, &size); - pack = (unsigned char *) p->pack_base + offset; - left = p->pack_size - offset; switch (kind) { case OBJ_DELTA: - return unpack_delta_entry(pack, size, left, type, sizep, p); + return unpack_delta_entry(p, offset, size, type, sizep); case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: strcpy(type, type_names[kind]); *sizep = size; - return unpack_compressed_entry(pack, size, left); + return unpack_compressed_entry(p, offset, size); default: return NULL; } -- cgit v0.10.2-6-g49f6 From 095c424d08d96a5f9ee3ca53ae952e92c5cff99b Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Sat, 26 Aug 2006 16:09:17 +0200 Subject: Use PATH_MAX instead of MAXPATHLEN According to sys/paramh.h it's a "BSD name" for values defined in . Besides PATH_MAX seems to be more commonly used. Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index 6b55f93..b097c88 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -45,7 +45,7 @@ static int line_termination = '\n'; static int checkout_stage; /* default to checkout stage0 */ static int to_tempfile; -static char topath[4][MAXPATHLEN+1]; +static char topath[4][PATH_MAX + 1]; static struct checkout state; diff --git a/dir.c b/dir.c index 092d077..a686de6 100644 --- a/dir.c +++ b/dir.c @@ -293,7 +293,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (fdir) { int exclude_stk; struct dirent *de; - char fullname[MAXPATHLEN + 1]; + char fullname[PATH_MAX + 1]; memcpy(fullname, base, baselen); exclude_stk = push_exclude_per_directory(dir, base, baselen); diff --git a/entry.c b/entry.c index 793724f..b2ea0ef 100644 --- a/entry.c +++ b/entry.c @@ -135,7 +135,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) { - static char path[MAXPATHLEN+1]; + static char path[PATH_MAX + 1]; struct stat st; int len = state->base_dir_len; @@ -172,5 +172,3 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) create_directories(path, state); return write_entry(ce, path, state, 0); } - - diff --git a/git-compat-util.h b/git-compat-util.h index b2e18954..91f2b0d 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -172,7 +172,4 @@ static inline int sane_case(int x, int high) return x; } -#ifndef MAXPATHLEN -#define MAXPATHLEN 256 -#endif #endif -- cgit v0.10.2-6-g49f6 From 83572c1a914d3f7a8dd66d954c11bbc665b7b923 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Sat, 26 Aug 2006 16:16:18 +0200 Subject: Use xrealloc instead of realloc Change places that use realloc, without a proper error path, to instead use xrealloc. Drop an erroneous error path in the daemon code that used errno in the die message in favour of the simpler xrealloc. Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 28b5dfd..a5ed8db 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -27,8 +27,8 @@ static void append_to_list(struct list *list, char *value, void *payload) { if (list->nr == list->alloc) { list->alloc += 32; - list->list = realloc(list->list, sizeof(char *) * list->alloc); - list->payload = realloc(list->payload, + list->list = xrealloc(list->list, sizeof(char *) * list->alloc); + list->payload = xrealloc(list->payload, sizeof(char *) * list->alloc); } list->payload[list->nr] = payload; diff --git a/builtin-log.c b/builtin-log.c index 691cf3a..fbc58bb 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -101,7 +101,7 @@ static int git_format_config(const char *var, const char *value) if (!strcmp(var, "format.headers")) { int len = strlen(value); extra_headers_size += len + 1; - extra_headers = realloc(extra_headers, extra_headers_size); + extra_headers = xrealloc(extra_headers, extra_headers_size); extra_headers[extra_headers_size - len - 1] = 0; strcat(extra_headers, value); return 0; @@ -381,7 +381,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; nr++; - list = realloc(list, nr * sizeof(list[0])); + list = xrealloc(list, nr * sizeof(list[0])); list[nr - 1] = commit; } total = nr; diff --git a/builtin-mv.c b/builtin-mv.c index fd1e520..4d21d88 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -168,13 +168,13 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int j, dst_len; if (last - first > 0) { - source = realloc(source, + source = xrealloc(source, (count + last - first) * sizeof(char *)); - destination = realloc(destination, + destination = xrealloc(destination, (count + last - first) * sizeof(char *)); - modes = realloc(modes, + modes = xrealloc(modes, (count + last - first) * sizeof(enum update_mode)); } diff --git a/daemon.c b/daemon.c index 012936f..5bf5c82 100644 --- a/daemon.c +++ b/daemon.c @@ -526,7 +526,6 @@ static int socksetup(int port, int **socklist_p) for (ai = ai0; ai; ai = ai->ai_next) { int sockfd; - int *newlist; sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sockfd < 0) @@ -560,11 +559,7 @@ static int socksetup(int port, int **socklist_p) continue; /* not fatal */ } - newlist = realloc(socklist, sizeof(int) * (socknum + 1)); - if (!newlist) - die("memory allocation failed: %s", strerror(errno)); - - socklist = newlist; + socklist = xrealloc(socklist, sizeof(int) * (socknum + 1)); socklist[socknum++] = sockfd; if (maxfd < sockfd) diff --git a/diff-delta.c b/diff-delta.c index 51e2e56..fa16d06 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -392,7 +392,7 @@ create_delta(const struct delta_index *index, outsize = max_size + MAX_OP_SIZE + 1; if (max_size && outpos > max_size) break; - out = realloc(out, outsize); + out = xrealloc(out, outsize); if (!out) { free(tmp); return NULL; diff --git a/dir.c b/dir.c index a686de6..d53d48f 100644 --- a/dir.c +++ b/dir.c @@ -101,8 +101,8 @@ void add_exclude(const char *string, const char *base, x->baselen = baselen; if (which->nr == which->alloc) { which->alloc = alloc_nr(which->alloc); - which->excludes = realloc(which->excludes, - which->alloc * sizeof(x)); + which->excludes = xrealloc(which->excludes, + which->alloc * sizeof(x)); } which->excludes[which->nr++] = x; } diff --git a/git.c b/git.c index a01d195..3adf262 100644 --- a/git.c +++ b/git.c @@ -120,7 +120,7 @@ static int split_cmdline(char *cmdline, const char ***argv) ; /* skip */ if (count >= size) { size += 16; - *argv = realloc(*argv, sizeof(char*) * size); + *argv = xrealloc(*argv, sizeof(char*) * size); } (*argv)[count++] = cmdline + dst; } else if(!quoted && (c == '\'' || c == '"')) { @@ -191,8 +191,8 @@ static int handle_alias(int *argcp, const char ***argv) fflush(stderr); } - new_argv = realloc(new_argv, sizeof(char*) * - (count + *argcp + 1)); + new_argv = xrealloc(new_argv, sizeof(char*) * + (count + *argcp + 1)); /* insert after command name */ memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); new_argv[count+*argcp] = NULL; diff --git a/sha1_file.c b/sha1_file.c index dd9bcaa..46272b5 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1740,7 +1740,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size) off += iret; if (off == size) { size *= 2; - buf = realloc(buf, size); + buf = xrealloc(buf, size); } } } while (iret > 0); diff --git a/xdiff-interface.c b/xdiff-interface.c index 6a82da7..08602f5 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -69,9 +69,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) for (i = 0; i < nbuf; i++) { if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); priv->remainder_size += mb[i].size; @@ -83,9 +83,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) consume_one(priv, mb[i].ptr, mb[i].size); continue; } - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); consume_one(priv, priv->remainder, -- cgit v0.10.2-6-g49f6 From 5f641ccc69da1e0f2e3953f615cf592e83ec721b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 26 Aug 2006 09:52:25 -0700 Subject: git-svn: stop repeatedly reusing the first commit message with dcommit Excessive use of global variables got me into trouble. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 9382a15..0290850 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -819,6 +819,7 @@ sub commit_diff { } else { $ed->close_edit; } + $_message = $_file = undef; } ########################### utility functions ######################### -- cgit v0.10.2-6-g49f6 From 090525541ffd3f52867d5d26de5e02ce4998460b Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 26 Aug 2006 23:33:58 +0200 Subject: gitweb: Fix typo in git_difftree_body Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0df59af..ba5024a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1556,7 +1556,7 @@ sub git_difftree_body { "blob") . " | " . $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'file'})},\ + file_name=>$diff{'file'})}, "history") . "\n"; -- cgit v0.10.2-6-g49f6 From e4fbbfe9eccd37c0f9c060eac181ce05988db76c Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 26 Aug 2006 23:19:21 +0200 Subject: Add git-zip-tree In the Windows world ZIP files are better supported than tar files. Windows even includes built-in support for ZIP files nowadays. git-zip-tree is similar to git-tar-tree; it creates ZIP files out of git trees. It stores the commit ID (if available) in a ZIP file comment which can be extracted by unzip. There's still quite some room for improvement: this initial version supports no symlinks, calls write() way too often (three times per file) and there is no unit test. [jc: with a minor typefix to avoid void* arithmetic] Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/Documentation/git-zip-tree.txt b/Documentation/git-zip-tree.txt new file mode 100644 index 0000000..2e9d981 --- /dev/null +++ b/Documentation/git-zip-tree.txt @@ -0,0 +1,67 @@ +git-zip-tree(1) +=============== + +NAME +---- +git-zip-tree - Creates a ZIP archive of the files in the named tree + + +SYNOPSIS +-------- +'git-zip-tree' [-0|...|-9] [ ] + +DESCRIPTION +----------- +Creates a ZIP archive containing the tree structure for the named tree. +When is specified it is added as a leading path to the files in the +generated ZIP archive. + +git-zip-tree behaves differently when given a tree ID versus when given +a commit ID or tag ID. In the first case the current time is used as +modification time of each file in the archive. In the latter case the +commit time as recorded in the referenced commit object is used instead. +Additionally the commit ID is stored as an archive comment. + +Currently git-zip-tree can handle only files and directories, symbolic +links are not supported. + +OPTIONS +------- + +-0:: + Store the files instead of deflating them. + +-9:: + Highest and slowest compression level. You can specify any + number from 1 to 9 to adjust compression speed and ratio. + +:: + The tree or commit to produce ZIP archive for. If it is + the object name of a commit object. + +:: + Leading path to the files in the resulting ZIP archive. + +EXAMPLES +-------- +git zip-tree v1.4.0 git-1.4.0 >git-1.4.0.zip:: + + Create a ZIP file for v1.4.0 release. + +git zip-tree HEAD:Documentation/ git-docs >docs.zip:: + + Put everything in the current head's Documentation/ directory + into 'docs.zip', with the prefix 'git-docs/'. + +Author +------ +Written by Rene Scharfe. + +Documentation +-------------- +Documentation by David Greaves, Junio C Hamano and the git-list . + +GIT +--- +Part of the gitlink:git[7] suite + diff --git a/Makefile b/Makefile index b15b420..d9741f9 100644 --- a/Makefile +++ b/Makefile @@ -292,7 +292,8 @@ BUILTIN_OBJS = \ builtin-update-ref.o \ builtin-upload-tar.o \ builtin-verify-pack.o \ - builtin-write-tree.o + builtin-write-tree.o \ + builtin-zip-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c new file mode 100644 index 0000000..a5b834d --- /dev/null +++ b/builtin-zip-tree.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2006 Rene Scharfe + */ +#include +#include "cache.h" +#include "commit.h" +#include "blob.h" +#include "tree.h" +#include "quote.h" +#include "builtin.h" + +static const char zip_tree_usage[] = +"git-zip-tree [-0|...|-9] [ ]"; + +static int zip_date; +static int zip_time; + +static unsigned char *zip_dir; +static unsigned int zip_dir_size; + +static unsigned int zip_offset; +static unsigned int zip_dir_offset; +static unsigned int zip_dir_entries; + +#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) + +struct zip_local_header { + unsigned char magic[4]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; +}; + +struct zip_dir_header { + unsigned char magic[4]; + unsigned char creator_version[2]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; + unsigned char comment_length[2]; + unsigned char disk[2]; + unsigned char attr1[2]; + unsigned char attr2[4]; + unsigned char offset[4]; +}; + +struct zip_dir_trailer { + unsigned char magic[4]; + unsigned char disk[2]; + unsigned char directory_start_disk[2]; + unsigned char entries_on_this_disk[2]; + unsigned char entries[2]; + unsigned char size[4]; + unsigned char offset[4]; + unsigned char comment_length[2]; +}; + +static void copy_le16(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); +} + +static void copy_le32(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); + dest[2] = 0xff & (n >> 020); + dest[3] = 0xff & (n >> 030); +} + +static void *zlib_deflate(void *data, unsigned long size, + unsigned long *compressed_size) +{ + z_stream stream; + unsigned long maxsize; + void *buffer; + int result; + + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, zlib_compression_level); + maxsize = deflateBound(&stream, size); + buffer = xmalloc(maxsize); + + stream.next_in = data; + stream.avail_in = size; + stream.next_out = buffer; + stream.avail_out = maxsize; + + do { + result = deflate(&stream, Z_FINISH); + } while (result == Z_OK); + + if (result != Z_STREAM_END) { + free(buffer); + return NULL; + } + + deflateEnd(&stream); + *compressed_size = stream.total_out; + + return buffer; +} + +static char *construct_path(const char *base, int baselen, + const char *filename, int isdir, int *pathlen) +{ + int filenamelen = strlen(filename); + int len = baselen + filenamelen; + char *path, *p; + + if (isdir) + len++; + p = path = xmalloc(len + 1); + + memcpy(p, base, baselen); + p += baselen; + memcpy(p, filename, filenamelen); + p += filenamelen; + if (isdir) + *p++ = '/'; + *p = '\0'; + + *pathlen = len; + + return path; +} + +static int write_zip_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + struct zip_local_header header; + struct zip_dir_header dirent; + unsigned long compressed_size; + unsigned long uncompressed_size; + unsigned long crc; + unsigned long direntsize; + unsigned long size; + int method; + int result = -1; + int pathlen; + unsigned char *out; + char *path; + char type[20]; + void *buffer = NULL; + void *deflated = NULL; + + crc = crc32(0, Z_NULL, 0); + + path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); + if (pathlen > 0xffff) { + error("path too long (%d chars, SHA1: %s): %s", pathlen, + sha1_to_hex(sha1), path); + goto out; + } + + if (S_ISDIR(mode)) { + method = 0; + result = READ_TREE_RECURSIVE; + out = NULL; + uncompressed_size = 0; + compressed_size = 0; + } else if (S_ISREG(mode)) { + method = zlib_compression_level == 0 ? 0 : 8; + result = 0; + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + crc = crc32(crc, buffer, size); + out = buffer; + uncompressed_size = size; + compressed_size = size; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", mode, + sha1_to_hex(sha1)); + goto out; + } + + if (method == 8) { + deflated = zlib_deflate(buffer, size, &compressed_size); + if (deflated && compressed_size - 6 < size) { + /* ZLIB --> raw compressed data (see RFC 1950) */ + /* CMF and FLG ... */ + out = (unsigned char *)deflated + 2; + compressed_size -= 6; /* ... and ADLER32 */ + } else { + method = 0; + compressed_size = size; + } + } + + /* make sure we have enough free space in the dictionary */ + direntsize = sizeof(struct zip_dir_header) + pathlen; + while (zip_dir_size < zip_dir_offset + direntsize) { + zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; + zip_dir = xrealloc(zip_dir, zip_dir_size); + } + + copy_le32(dirent.magic, 0x02014b50); + copy_le16(dirent.creator_version, 0); + copy_le16(dirent.version, 20); + copy_le16(dirent.flags, 0); + copy_le16(dirent.compression_method, method); + copy_le16(dirent.mtime, zip_time); + copy_le16(dirent.mdate, zip_date); + copy_le32(dirent.crc32, crc); + copy_le32(dirent.compressed_size, compressed_size); + copy_le32(dirent.size, uncompressed_size); + copy_le16(dirent.filename_length, pathlen); + copy_le16(dirent.extra_length, 0); + copy_le16(dirent.comment_length, 0); + copy_le16(dirent.disk, 0); + copy_le16(dirent.attr1, 0); + copy_le32(dirent.attr2, 0); + copy_le32(dirent.offset, zip_offset); + memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); + zip_dir_offset += sizeof(struct zip_dir_header); + memcpy(zip_dir + zip_dir_offset, path, pathlen); + zip_dir_offset += pathlen; + zip_dir_entries++; + + copy_le32(header.magic, 0x04034b50); + copy_le16(header.version, 20); + copy_le16(header.flags, 0); + copy_le16(header.compression_method, method); + copy_le16(header.mtime, zip_time); + copy_le16(header.mdate, zip_date); + copy_le32(header.crc32, crc); + copy_le32(header.compressed_size, compressed_size); + copy_le32(header.size, uncompressed_size); + copy_le16(header.filename_length, pathlen); + copy_le16(header.extra_length, 0); + write_or_die(1, &header, sizeof(struct zip_local_header)); + zip_offset += sizeof(struct zip_local_header); + write_or_die(1, path, pathlen); + zip_offset += pathlen; + if (compressed_size > 0) { + write_or_die(1, out, compressed_size); + zip_offset += compressed_size; + } + +out: + free(buffer); + free(deflated); + free(path); + + return result; +} + +static void write_zip_trailer(const unsigned char *sha1) +{ + struct zip_dir_trailer trailer; + + copy_le32(trailer.magic, 0x06054b50); + copy_le16(trailer.disk, 0); + copy_le16(trailer.directory_start_disk, 0); + copy_le16(trailer.entries_on_this_disk, zip_dir_entries); + copy_le16(trailer.entries, zip_dir_entries); + copy_le32(trailer.size, zip_dir_offset); + copy_le32(trailer.offset, zip_offset); + copy_le16(trailer.comment_length, sha1 ? 40 : 0); + + write_or_die(1, zip_dir, zip_dir_offset); + write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); + if (sha1) + write_or_die(1, sha1_to_hex(sha1), 40); +} + +static void dos_time(time_t *time, int *dos_date, int *dos_time) +{ + struct tm *t = localtime(time); + + *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + + (t->tm_year + 1900 - 1980) * 512; + *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; +} + +int cmd_zip_tree(int argc, const char **argv, const char *prefix) +{ + unsigned char sha1[20]; + struct tree *tree; + struct commit *commit; + time_t archive_time; + char *base; + int baselen; + + git_config(git_default_config); + + if (argc > 1 && argv[1][0] == '-') { + if (isdigit(argv[1][1]) && argv[1][2] == '\0') { + zlib_compression_level = argv[1][1] - '0'; + argc--; + argv++; + } + } + + switch (argc) { + case 3: + base = strdup(argv[2]); + baselen = strlen(base); + break; + case 2: + base = strdup(""); + baselen = 0; + break; + default: + usage(zip_tree_usage); + } + + if (get_sha1(argv[1], sha1)) + die("Not a valid object name %s", argv[1]); + + commit = lookup_commit_reference_gently(sha1, 1); + archive_time = commit ? commit->date : time(NULL); + dos_time(&archive_time, &zip_date, &zip_time); + + zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); + zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; + + tree = parse_tree_indirect(sha1); + if (!tree) + die("not a tree object"); + + if (baselen > 0) { + write_zip_entry(tree->object.sha1, "", 0, base, 040777, 0); + base = xrealloc(base, baselen + 1); + base[baselen] = '/'; + baselen++; + base[baselen] = '\0'; + } + read_tree_recursive(tree, base, baselen, 0, NULL, write_zip_entry); + write_zip_trailer(commit ? commit->object.sha1 : NULL); + + free(zip_dir); + free(base); + + return 0; +} diff --git a/builtin.h b/builtin.h index ade58c4..25431d7 100644 --- a/builtin.h +++ b/builtin.h @@ -52,6 +52,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); +extern int cmd_zip_tree(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 3adf262..bd07289 100644 --- a/git.c +++ b/git.c @@ -263,6 +263,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tar-tree", cmd_tar_tree, RUN_SETUP }, + { "zip-tree", cmd_zip_tree, RUN_SETUP }, { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From 9a8e35e98793af086f05d1ca9643052df9b44a74 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 26 Aug 2006 15:45:26 -0700 Subject: Relative timestamps in git log I noticed that I was looking at the kernel gitweb output at some point rather than just do "git log", simply because I liked seeing the simplified date-format, ie the "5 days ago" rather than a full date. This adds infrastructure to do that for "git log" too. It does NOT add the actual flag to enable it, though, so right now this patch is a no-op, but it should now be easy to add a command line flag (and possibly a config file option) to just turn on the "relative" date format. The exact cut-off points when it switches from days to weeks etc are totally arbitrary, but are picked somewhat to avoid the "1 weeks ago" thing (by making it show "10 days ago" rather than "1 week", or "70 minutes ago" rather than "1 hour ago"). [jc: with minor fix and tweak around "month" and "week" area.] Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 7a6fa56..6c16bfa 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -54,7 +54,7 @@ static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long write_or_die(1, tagger, sp - tagger); date = strtoul(sp, &ep, 10); tz = strtol(ep, NULL, 10); - sp = show_date(date, tz); + sp = show_date(date, tz, 0); write_or_die(1, sp, strlen(sp)); xwrite(1, "\n", 1); break; diff --git a/cache.h b/cache.h index 1f212d7..ccb83a1 100644 --- a/cache.h +++ b/cache.h @@ -286,7 +286,7 @@ extern void *read_object_with_reference(const unsigned char *sha1, unsigned long *size, unsigned char *sha1_ret); -const char *show_date(unsigned long time, int timezone); +const char *show_date(unsigned long time, int timezone, int relative); const char *show_rfc2822_date(unsigned long time, int timezone); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); diff --git a/commit.c b/commit.c index 00bc3de..c3ff9b4 100644 --- a/commit.c +++ b/commit.c @@ -507,14 +507,14 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c } switch (fmt) { case CMIT_FMT_MEDIUM: - ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); + ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz, 0)); break; case CMIT_FMT_EMAIL: ret += sprintf(buf + ret, "Date: %s\n", show_rfc2822_date(time, tz)); break; case CMIT_FMT_FULLER: - ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); + ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz, 0)); break; default: /* notin' */ diff --git a/date.c b/date.c index d780846..e387dcd 100644 --- a/date.c +++ b/date.c @@ -37,6 +37,16 @@ static const char *weekday_names[] = { "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" }; +static time_t gm_time_t(unsigned long time, int tz) +{ + int minutes; + + minutes = tz < 0 ? -tz : tz; + minutes = (minutes / 100)*60 + (minutes % 100); + minutes = tz < 0 ? -minutes : minutes; + return time + minutes * 60; +} + /* * The "tz" thing is passed in as this strange "decimal parse of tz" * thing, which means that tz -0100 is passed in as the integer -100, @@ -44,21 +54,58 @@ static const char *weekday_names[] = { */ static struct tm *time_to_tm(unsigned long time, int tz) { - time_t t; - int minutes; - - minutes = tz < 0 ? -tz : tz; - minutes = (minutes / 100)*60 + (minutes % 100); - minutes = tz < 0 ? -minutes : minutes; - t = time + minutes * 60; + time_t t = gm_time_t(time, tz); return gmtime(&t); } -const char *show_date(unsigned long time, int tz) +const char *show_date(unsigned long time, int tz, int relative) { struct tm *tm; static char timebuf[200]; + if (relative) { + unsigned long diff; + time_t t = gm_time_t(time, tz); + struct timeval now; + gettimeofday(&now, NULL); + if (now.tv_sec < t) + return "in the future"; + diff = now.tv_sec - t; + if (diff < 90) { + snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff); + return timebuf; + } + /* Turn it into minutes */ + diff = (diff + 30) / 60; + if (diff < 90) { + snprintf(timebuf, sizeof(timebuf), "%lu minutes ago", diff); + return timebuf; + } + /* Turn it into hours */ + diff = (diff + 30) / 60; + if (diff < 36) { + snprintf(timebuf, sizeof(timebuf), "%lu hours ago", diff); + return timebuf; + } + /* We deal with number of days from here on */ + diff = (diff + 12) / 24; + if (diff < 14) { + snprintf(timebuf, sizeof(timebuf), "%lu days ago", diff); + return timebuf; + } + /* Say weeks for the past 10 weeks or so */ + if (diff < 70) { + snprintf(timebuf, sizeof(timebuf), "%lu weeks ago", (diff + 3) / 7); + return timebuf; + } + /* Say months for the past 12 months or so */ + if (diff < 360) { + snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30); + return timebuf; + } + /* Else fall back on absolute format.. */ + } + tm = time_to_tm(time, tz); if (!tm) return NULL; -- cgit v0.10.2-6-g49f6 From c8a99d76749aeaa6b7fd8555724a4921eb677a37 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 26 Aug 2006 19:14:23 +0200 Subject: gitweb: Fix typo in git_patchset_body Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ba5024a..7b458bc 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1656,7 +1656,7 @@ sub git_patchset_body { print "
\n"; LINE: - while (my $patch_line @$fd>) { + while (my $patch_line = <$fd>) { chomp $patch_line; if ($patch_line =~ m/^diff /) { # "git diff" header -- cgit v0.10.2-6-g49f6 From 023782bd4df765b0555b8abc1830d9a90cdccca2 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 27 Aug 2006 23:44:38 +0200 Subject: gitweb: Remove unused git_get_{preceding,following}_references Remove unused (and with errors in implementation) git_get_{preceding,following}_references subroutines. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7b458bc..9aa7e4d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -784,57 +784,6 @@ sub git_get_references { return \%refs; } -sub git_get_following_references { - my $hash = shift || return undef; - my $type = shift; - my $base = shift || $hash_base || "HEAD"; - - my $refs = git_get_references($type); - open my $fd, "-|", $GIT, "rev-list", $base - or return undef; - my @commits = map { chomp; $_ } <$fd>; - close $fd - or return undef; - - my @reflist; - my $lastref; - - foreach my $commit (@commits) { - foreach my $ref (@{$refs->{$commit}}) { - $lastref = $ref; - push @reflist, $lastref; - } - if ($commit eq $hash) { - last; - } - } - - return wantarray ? @reflist : $lastref; -} - -sub git_get_preceding_references { - my $hash = shift || return undef; - my $type = shift; - - my $refs = git_get_references($type); - open my $fd, "-|", $GIT, "rev-list", $hash - or return undef; - my @commits = map { chomp; $_ } <$fd>; - close $fd - or return undef; - - my @reflist; - - foreach my $commit (@commits) { - foreach my $ref (@{$refs->{$commit}}) { - return $ref unless wantarray; - push @reflist, $ref; - } - } - - return @reflist; -} - sub git_get_rev_name_tags { my $hash = shift || return undef; -- cgit v0.10.2-6-g49f6 From 0aea33762b1262d11fb43eda9f3fc152b5622cca Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 27 Aug 2006 23:45:26 +0200 Subject: gitweb: Remove git_to_hash function Remove git_to_hash function, which was to translate symbolic reference to hash, and it's use in git_blobdiff. We don't try so hard to guess filename if it was not provided. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9aa7e4d..8d28207 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -625,26 +625,6 @@ sub git_get_hash_by_path { return $3; } -# converts symbolic name to hash -sub git_to_hash { - my @params = @_; - return undef unless @params; - - open my $fd, "-|", $GIT, "rev-parse", @params - or return undef; - my @hashes = map { chomp; $_ } <$fd>; - close $fd; - - if (wantarray) { - return @hashes; - } elsif (scalar(@hashes) == 1) { - # single hash - return $hashes[0]; - } else { - return \@hashes; - } -} - ## ...................................................................... ## git utility functions, directly accessing git repository @@ -2733,6 +2713,9 @@ sub git_blobdiff { if ($hash !~ /[0-9a-fA-F]{40}/) { $hash = git_to_hash($hash); } + } elsif (defined $hash && + $hash =~ /[0-9a-fA-F]{40}/) { + # try to find filename from $hash # read filtered raw output open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', $hash_parent_base, $hash_base -- cgit v0.10.2-6-g49f6 From bee597cb7fa40bd24ed8da8f3c581215ad38e15a Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sun, 27 Aug 2006 13:19:45 +0200 Subject: git-cherry: remove unused variable Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/git-cherry.sh b/git-cherry.sh index f0e8831..8832573 100755 --- a/git-cherry.sh +++ b/git-cherry.sh @@ -51,9 +51,6 @@ patch=$tmp-patch mkdir $patch trap "rm -rf $tmp-*" 0 1 2 3 15 -_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" - for c in $inup do git-diff-tree -p $c -- cgit v0.10.2-6-g49f6 From 2e6183840e5c8f1a478975346549be7440405175 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sun, 27 Aug 2006 13:19:58 +0200 Subject: git-reset: remove unused variable Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/git-reset.sh b/git-reset.sh index 36fc8ce..3133b5b 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -3,9 +3,6 @@ USAGE='[--mixed | --soft | --hard] []' . git-sh-setup -tmp=${GIT_DIR}/reset.$$ -trap 'rm -f $tmp-*' 0 1 2 3 15 - update= reset_type=--mixed case "$1" in -- cgit v0.10.2-6-g49f6 From 6bcf4b46c9a4ba7a6e5c78201c619fa41ac76a99 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 27 Aug 2006 23:49:36 +0200 Subject: gitweb: Use @diff_opts, default ('M'), as git-diff and git-diff-tree paramete Added new global configuration variable @diff_opts, which holds additional options (parameters) to git-diff and git-diff-tree, usually dealing rename/copying detection. Default value is '-M', taken from git_commit subroutine. Description of options and their approximate cost by Junio C Hamano. Changes: * git_commitdiff, git_blobdiff and git_blobdiff_plain now use '-M' instead of '-M', '-C' * git-diff now uses the same options as git-diff-tree * git_comittdiff_plain now uses '-M' instead of '-B' and is now rename-aware * git_rss uses now '-M' instead of () Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8d28207..1430a7a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -141,6 +141,16 @@ sub feature_snapshot { return ($ctype, $suffix, $command); } +# rename detection options for git-diff and git-diff-tree +# - default is '-M', with the cost proportional to +# (number of removed files) * (number of new files). +# - more costly is '-C' (or '-C', '-M'), with the cost proportional to +# (number of changed files + number of removed files) * (number of new files) +# - even more costly is '-C', '--find-copies-harder' with cost +# (number of files in the original tree) * (number of new files) +# - one might want to include '-B' option, e.g. '-B', '-M' +our @diff_opts = ('-M'); # taken from git_commit + our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; require $GITWEB_CONFIG if -e $GITWEB_CONFIG; @@ -2593,7 +2603,7 @@ sub git_commit { if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash + open my $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, $parent, $hash or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed"); @@ -2700,7 +2710,7 @@ sub git_blobdiff { if (defined $hash_base && defined $hash_parent_base) { if (defined $file_name) { # read raw output - open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', $hash_parent_base, $hash_base, + open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, "--", $file_name or die_error(undef, "Open git-diff-tree failed"); @difftree = map { chomp; $_ } <$fd>; @@ -2718,7 +2728,7 @@ sub git_blobdiff { # try to find filename from $hash # read filtered raw output - open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', $hash_parent_base, $hash_base + open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base or die_error(undef, "Open git-diff-tree failed"); @difftree = # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' @@ -2752,7 +2762,8 @@ sub git_blobdiff { } # open patch output - open $fd, "-|", $GIT, "diff-tree", '-r', '-p', '-M', '-C', $hash_parent_base, $hash_base, + open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, + '-p', $hash_parent_base, $hash_base, "--", $file_name or die_error(undef, "Open git-diff-tree failed"); } @@ -2787,7 +2798,7 @@ sub git_blobdiff { } # open patch output - open $fd, "-|", $GIT, "diff", '-p', $hash_parent, $hash + open $fd, "-|", $GIT, "diff", '-p', @diff_opts, $hash_parent, $hash or die_error(undef, "Open git-diff failed"); } else { die_error('404 Not Found', "Missing one of the blob diff parameters") @@ -2872,7 +2883,7 @@ sub git_commitdiff { my $fd; my @difftree; if ($format eq 'html') { - open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', + open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, "--patch-with-raw", "--full-index", $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed"); @@ -2883,7 +2894,8 @@ sub git_commitdiff { } } elsif ($format eq 'plain') { - open $fd, "-|", $GIT, "diff-tree", '-r', '-p', '-B', $hash_parent, $hash + open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, + '-p', $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed"); } else { @@ -3192,9 +3204,12 @@ XML last; } my %cd = parse_date($co{'committer_epoch'}); - open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; + open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, + $co{'parent'}, $co{'id'} + or next; my @difftree = map { chomp; $_ } <$fd>; - close $fd or next; + close $fd + or next; print "\n" . "" . sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . -- cgit v0.10.2-6-g49f6 From 8938045a4eae7a9c39631508a3c3d34c50c6257a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 27 Aug 2006 15:53:20 -0700 Subject: git-apply --reject: finishing touches. After a failed "git am" attempt: git apply --reject --verbose .dotest/patch applies hunks that are applicable and leaves *.rej files the rejected hunks, and it reports what it is doing. With --index, files with a rejected hunk do not get their index entries updated at all, so "git diff" will show the hunks that successfully got applied. Without --verbose to remind the user that the patch updated some other paths cleanly, it is very easy to lose track of the status of the working tree, so --reject implies --verbose. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 11641a9..2e2acd7 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -70,8 +70,8 @@ OPTIONS For atomicity, `git apply` fails the whole patch and does not touch the working tree when some of the hunks do not apply by default. This option makes it apply - parts of the patch that are applicable, and send the - rejected hunks to the standard output of the command. + parts of the patch that are applicable, and leave the + rejected hunks in corresponding *.rej files. -z:: When showing the index information, do not munge paths, diff --git a/builtin-apply.c b/builtin-apply.c index a874375..0b00a98 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2557,7 +2557,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--reject")) { - apply = apply_with_reject = 1; + apply = apply_with_reject = apply_verbosely = 1; continue; } if (!strcmp(arg, "--verbose")) { -- cgit v0.10.2-6-g49f6 From c470701a98700533024b1864b789d4fc17e5e823 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Mon, 28 Aug 2006 01:55:46 +0200 Subject: Use fstat instead of fseek Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/dir.c b/dir.c index d53d48f..5a40d8f 100644 --- a/dir.c +++ b/dir.c @@ -112,17 +112,15 @@ static int add_excludes_from_file_1(const char *fname, int baselen, struct exclude_list *which) { + struct stat st; int fd, i; long size; char *buf, *entry; fd = open(fname, O_RDONLY); - if (fd < 0) + if (fd < 0 || fstat(fd, &st) < 0) goto err; - size = lseek(fd, 0, SEEK_END); - if (size < 0) - goto err; - lseek(fd, 0, SEEK_SET); + size = st.st_size; if (size == 0) { close(fd); return 0; -- cgit v0.10.2-6-g49f6 From b3c952f8386cebe12fc95227866683bb1cec99a9 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Mon, 28 Aug 2006 02:26:07 +0200 Subject: Use xcalloc instead of calloc Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/object-refs.c b/object-refs.c index b1b8065..b0034e4 100644 --- a/object-refs.c +++ b/object-refs.c @@ -30,7 +30,7 @@ static void grow_refs_hash(void) int new_hash_size = (refs_hash_size + 1000) * 3 / 2; struct object_refs **new_hash; - new_hash = calloc(new_hash_size, sizeof(struct object_refs *)); + new_hash = xcalloc(new_hash_size, sizeof(struct object_refs *)); for (i = 0; i < refs_hash_size; i++) { struct object_refs *ref = refs_hash[i]; if (!ref) diff --git a/object.c b/object.c index 60bf16b..9281300 100644 --- a/object.c +++ b/object.c @@ -73,7 +73,7 @@ static void grow_object_hash(void) int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size; struct object **new_hash; - new_hash = calloc(new_hash_size, sizeof(struct object *)); + new_hash = xcalloc(new_hash_size, sizeof(struct object *)); for (i = 0; i < obj_hash_size; i++) { struct object *obj = obj_hash[i]; if (!obj) -- cgit v0.10.2-6-g49f6 From 4cac42b1324951579036a9d3ac403f5c2c3eeed8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 27 Aug 2006 21:19:39 -0700 Subject: free(NULL) is perfectly valid. Jonas noticed some places say "if (X) free(X)" which is totally unnecessary. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-apply.c b/builtin-apply.c index b47ccac..1a1deaf 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1131,8 +1131,7 @@ static struct fragment *parse_binary_hunk(char **buf_p, return frag; corrupt: - if (data) - free(data); + free(data); *status_p = -1; error("corrupt binary patch at line %d: %.*s", linenr-1, llen-1, buffer); @@ -1329,8 +1328,7 @@ static void show_stats(struct patch *patch) printf(" %s%-*s |%5d %.*s%.*s\n", prefix, len, name, patch->lines_added + patch->lines_deleted, add, pluses, del, minuses); - if (qname) - free(qname); + free(qname); } static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size) diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index a5ed8db..76d22b4 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -55,8 +55,7 @@ static void free_list(struct list *list) for (i = 0; i < list->nr; i++) { free(list->list[i]); - if (list->payload[i]) - free(list->payload[i]); + free(list->payload[i]); } free(list->list); free(list->payload); diff --git a/builtin-repo-config.c b/builtin-repo-config.c index c416480..6560cf1 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -122,10 +122,8 @@ static int get_value(const char* key_, const char* regex_) ret = (seen == 1) ? 0 : 1; free_strings: - if (repo_config) - free(repo_config); - if (global) - free(global); + free(repo_config); + free(global); return ret; } diff --git a/builtin-rev-list.c b/builtin-rev-list.c index bc48a3e..7f3e1fc 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -93,10 +93,8 @@ static void show_commit(struct commit *commit) free_commit_list(commit->parents); commit->parents = NULL; } - if (commit->buffer) { - free(commit->buffer); - commit->buffer = NULL; - } + free(commit->buffer); + commit->buffer = NULL; } static void process_blob(struct blob *blob, diff --git a/config.c b/config.c index 82b3562..d9f2b78 100644 --- a/config.c +++ b/config.c @@ -361,8 +361,7 @@ int git_config(config_fn_t fn) } ret += git_config_from_file(fn, filename); - if (repo_config) - free(repo_config); + free(repo_config); return ret; } @@ -734,8 +733,7 @@ int git_config_set_multivar(const char* key, const char* value, out_free: if (0 <= fd) close(fd); - if (config_filename) - free(config_filename); + free(config_filename); if (lock_file) { unlink(lock_file); free(lock_file); diff --git a/fetch.c b/fetch.c index ef60b04..7d3812c 100644 --- a/fetch.c +++ b/fetch.c @@ -302,8 +302,7 @@ int pull(int targets, char **target, const char **write_ref, if (ret) goto unlock_and_fail; } - if (msg) - free(msg); + free(msg); return 0; diff --git a/http-fetch.c b/http-fetch.c index 7619b33..6806f36 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -696,10 +696,8 @@ xml_start_tag(void *userData, const char *name, const char **atts) strcat(ctx->name, "."); strcat(ctx->name, c); - if (ctx->cdata) { - free(ctx->cdata); - ctx->cdata = NULL; - } + free(ctx->cdata); + ctx->cdata = NULL; ctx->userFunc(ctx, 0); } @@ -726,8 +724,7 @@ static void xml_cdata(void *userData, const XML_Char *s, int len) { struct xml_ctx *ctx = (struct xml_ctx *)userData; - if (ctx->cdata) - free(ctx->cdata); + free(ctx->cdata); ctx->cdata = xmalloc(len + 1); strlcpy(ctx->cdata, s, len + 1); } @@ -765,9 +762,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->dentry_flags |= IS_DIR; } } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - if (ls->dentry_name) { - free(ls->dentry_name); - } + free(ls->dentry_name); ls->dentry_name = NULL; ls->dentry_flags = 0; } diff --git a/http-push.c b/http-push.c index 04cb238..7814666 100644 --- a/http-push.c +++ b/http-push.c @@ -1238,10 +1238,8 @@ xml_start_tag(void *userData, const char *name, const char **atts) strcat(ctx->name, "."); strcat(ctx->name, c); - if (ctx->cdata) { - free(ctx->cdata); - ctx->cdata = NULL; - } + free(ctx->cdata); + ctx->cdata = NULL; ctx->userFunc(ctx, 0); } @@ -1268,8 +1266,7 @@ static void xml_cdata(void *userData, const XML_Char *s, int len) { struct xml_ctx *ctx = (struct xml_ctx *)userData; - if (ctx->cdata) - free(ctx->cdata); + free(ctx->cdata); ctx->cdata = xmalloc(len + 1); strlcpy(ctx->cdata, s, len + 1); } @@ -1518,9 +1515,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->dentry_flags |= IS_DIR; } } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - if (ls->dentry_name) { - free(ls->dentry_name); - } + free(ls->dentry_name); ls->dentry_name = NULL; ls->dentry_flags = 0; } diff --git a/path-list.c b/path-list.c index f15a10d..b1ee72d 100644 --- a/path-list.c +++ b/path-list.c @@ -85,8 +85,7 @@ void path_list_clear(struct path_list *list, int free_items) for (i = 0; i < list->nr; i++) { if (list->strdup_paths) free(list->items[i].path); - if (list->items[i].util) - free(list->items[i].util); + free(list->items[i].util); } free(list->items); } diff --git a/refs.c b/refs.c index e70ef0a..aab14fc 100644 --- a/refs.c +++ b/refs.c @@ -348,10 +348,8 @@ void unlock_ref(struct ref_lock *lock) if (lock->lk) rollback_lock_file(lock->lk); } - if (lock->ref_file) - free(lock->ref_file); - if (lock->log_file) - free(lock->log_file); + free(lock->ref_file); + free(lock->log_file); free(lock); } -- cgit v0.10.2-6-g49f6 From 370e0966ef4abff81f08c9ea5c7d167eb0b0d354 Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Sun, 27 Aug 2006 13:19:49 +0200 Subject: Add git-zip-tree to .gitignore Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index 3da0e5e..78cb671 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ git-verify-pack git-verify-tag git-whatchanged git-write-tree +git-zip-tree git-core-*/?* gitweb/gitweb.cgi test-date -- cgit v0.10.2-6-g49f6 From d819e4e682d68ca41378e4352b9e2bba084971dc Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 20 Aug 2006 19:03:13 -0700 Subject: daemon: prepare for multiple services. This adds an infrastructure to selectively enable and disable more than one services in git-daemon. Currently upload-pack service, which serves the git-fetch-pack and git-peek-remote clients, is the only service that is defined. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/daemon.c b/daemon.c index 66ec830..e430cfb 100644 --- a/daemon.c +++ b/daemon.c @@ -232,13 +232,42 @@ static char *path_ok(char *dir) return NULL; /* Fallthrough. Deny by default */ } -static int upload(char *dir) +typedef int (*daemon_service_fn)(void); +struct daemon_service { + const char *name; + const char *config_name; + daemon_service_fn fn; + int enabled; + int overridable; +}; + +static struct daemon_service *service_looking_at; +static int service_enabled; + +static int git_daemon_config(const char *var, const char *value) +{ + if (!strncmp(var, "daemon.", 7) && + !strcmp(var + 7, service_looking_at->config_name)) { + service_enabled = git_config_bool(var, value); + return 0; + } + + /* we are not interested in parsing any other configuration here */ + return 0; +} + +static int run_service(char *dir, struct daemon_service *service) { - /* Timeout as string */ - char timeout_buf[64]; const char *path; + int enabled = service->enabled; + + loginfo("Request %s for '%s'", service->name, dir); - loginfo("Request for '%s'", dir); + if (!enabled && !service->overridable) { + logerror("'%s': service not enabled.", service->name); + errno = EACCES; + return -1; + } if (!(path = path_ok(dir))) return -1; @@ -260,12 +289,34 @@ static int upload(char *dir) return -1; } + if (service->overridable) { + service_looking_at = service; + service_enabled = -1; + git_config(git_daemon_config); + if (0 <= service_enabled) + enabled = service_enabled; + } + if (!enabled) { + logerror("'%s': service not enabled for '%s'", + service->name, path); + errno = EACCES; + return -1; + } + /* * We'll ignore SIGTERM from now on, we have a * good client. */ signal(SIGTERM, SIG_IGN); + return service->fn(); +} + +static int upload_pack(void) +{ + /* Timeout as string */ + char timeout_buf[64]; + snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout); /* git-upload-pack only ever reads stuff, so this is safe */ @@ -273,10 +324,36 @@ static int upload(char *dir) return -1; } +static struct daemon_service daemon_service[] = { + { "upload-pack", "uploadpack", upload_pack, 1, 1 }, +}; + +static void enable_service(const char *name, int ena) { + int i; + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + if (!strcmp(daemon_service[i].name, name)) { + daemon_service[i].enabled = ena; + return; + } + } + die("No such service %s", name); +} + +static void make_service_overridable(const char *name, int ena) { + int i; + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + if (!strcmp(daemon_service[i].name, name)) { + daemon_service[i].overridable = ena; + return; + } + } + die("No such service %s", name); +} + static int execute(struct sockaddr *addr) { static char line[1000]; - int pktlen, len; + int pktlen, len, i; if (addr) { char addrbuf[256] = ""; @@ -313,8 +390,14 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; - if (!strncmp("git-upload-pack ", line, 16)) - return upload(line+16); + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + struct daemon_service *s = &(daemon_service[i]); + int namelen = strlen(s->name); + if (!strncmp("git-", line, 4) && + !strncmp(s->name, line + 4, namelen) && + line[namelen + 4] == ' ') + return run_service(line + namelen + 5, s); + } logerror("Protocol error: '%s'", line); return -1; @@ -805,6 +888,22 @@ int main(int argc, char **argv) group_name = arg + 8; continue; } + if (!strncmp(arg, "--enable=", 9)) { + enable_service(arg + 9, 1); + continue; + } + if (!strncmp(arg, "--disable=", 10)) { + enable_service(arg + 10, 0); + continue; + } + if (!strncmp(arg, "--enable-override=", 18)) { + make_service_overridable(arg + 18, 1); + continue; + } + if (!strncmp(arg, "--disable-override=", 19)) { + make_service_overridable(arg + 19, 0); + continue; + } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; break; -- cgit v0.10.2-6-g49f6 From 74c0cc21a57a15bbce46ee02bc882064ee9bcf6b Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 20 Aug 2006 19:03:50 -0700 Subject: daemon: add upload-tar service. This allows clients to ask for tarballs with: git tar-tree --remote=git://server/repo refname By default, the upload-tar service is not enabled. To enable it server-wide, the server can be started with: git-daemon --enable=upload-tar This service is by default overridable per repostiory, so alternatively, a repository can define "daemon.uploadtar = true" to enable it. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/daemon.c b/daemon.c index e430cfb..a4a08f3 100644 --- a/daemon.c +++ b/daemon.c @@ -324,8 +324,15 @@ static int upload_pack(void) return -1; } +static int upload_tar(void) +{ + execl_git_cmd("upload-tar", ".", NULL); + return -1; +} + static struct daemon_service daemon_service[] = { { "upload-pack", "uploadpack", upload_pack, 1, 1 }, + { "upload-tar", "uploadtar", upload_tar, 0, 1 }, }; static void enable_service(const char *name, int ena) { @@ -896,12 +903,12 @@ int main(int argc, char **argv) enable_service(arg + 10, 0); continue; } - if (!strncmp(arg, "--enable-override=", 18)) { - make_service_overridable(arg + 18, 1); + if (!strncmp(arg, "--allow-override=", 17)) { + make_service_overridable(arg + 17, 1); continue; } - if (!strncmp(arg, "--disable-override=", 19)) { - make_service_overridable(arg + 19, 0); + if (!strncmp(arg, "--forbid-override=", 18)) { + make_service_overridable(arg + 18, 0); continue; } if (!strcmp(arg, "--")) { -- cgit v0.10.2-6-g49f6 From 355f541249633487aa2685e7e7e29963f596b308 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 20 Aug 2006 19:32:43 -0700 Subject: multi-service daemon: documentation Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 17619a3..35c3c4b 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,17 +11,16 @@ SYNOPSIS 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] - [--reuseaddr] [--detach] [--pid-file=file] - [--user=user [--group=group]] [directory...] + [--enable=service] [--disable=service] + [--allow-override=service] [--forbid-override=service] + [--reuseaddr] [--detach] [--pid-file=file] + [--user=user [--group=group]] [directory...] DESCRIPTION ----------- A really simple TCP git daemon that normally listens on port "DEFAULT_GIT_PORT" -aka 9418. It waits for a connection, and will just execute "git-upload-pack" -when it gets one. - -It's careful in that there's a magic request-line that gives the command and -what directory to upload, and it verifies that the directory is OK. +aka 9418. It waits for a connection asking for a service, and will serve +that service if it is enabled. It verifies that the directory has the magic file "git-daemon-export-ok", and it will refuse to export any git directory that hasn't explicitly been marked @@ -29,7 +28,12 @@ for export this way (unless the '--export-all' parameter is specified). If you pass some directory paths as 'git-daemon' arguments, you can further restrict the offers to a whitelist comprising of those. -This is ideally suited for read-only updates, i.e., pulling from git repositories. +By default, only `upload-pack` service is enabled, which serves +`git-fetch-pack` and `git-peek-remote` clients that are invoked +from `git-fetch`, `git-ls-remote`, and `git-clone`. + +This is ideally suited for read-only updates, i.e., pulling from +git repositories. OPTIONS ------- @@ -105,11 +109,38 @@ Giving these options is an error when used with `--inetd`; use the facility of inet daemon to achieve the same before spawning `git-daemon` if needed. +--enable-service, --disable-service:: + Enable/disable the service site-wide per default. Note + that a service disabled site-wide can still be enabled + per repository if it is marked overridable and the + repository enables the service with an configuration + item. + +--allow-override, --forbid-override:: + Allow/forbid overriding the site-wide default with per + repository configuration. By default, all the services + are overridable. + <directory>:: A directory to add to the whitelist of allowed directories. Unless --strict-paths is specified this will also include subdirectories of each named directory. +SERVICES +-------- + +upload-pack:: + This serves `git-fetch-pack` and `git-peek-remote` + clients. It is enabled by default, but a repository can + disable it by setting `daemon.uploadpack` configuration + item to `false`. + +upload-tar:: + This serves `git-tar-tree --remote=repository` client. + It is not enabled by default, but a repository can + enable it by setting `daemon.uploadtar` configuration + item to `true`. + Author ------ Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki -- cgit v0.10.2-6-g49f6 From 561d038ab8adb2bab5ba368b88e620be18c4811b Mon Sep 17 00:00:00 2001 From: Paul Mackerras <paulus@samba.org> Date: Mon, 28 Aug 2006 22:41:09 +1000 Subject: gitk: Fix some bugs in the new cherry-picking code When inserting the new commit row for the cherry-picked commit, we weren't advancing the selected line (if there is one), and we weren't updating commitlisted properly. diff --git a/gitk b/gitk index b66ccca..ebbeac6 100755 --- a/gitk +++ b/gitk @@ -3314,14 +3314,14 @@ proc finishcommits {} { catch {unset pending_select} } -# Inserting a new commit as the child of the commit on row $row. +# Insert a new commit as the child of the commit on row $row. # The new commit will be displayed on row $row and the commits # on that row and below will move down one row. proc insertrow {row newcmit} { global displayorder parentlist childlist commitlisted global commitrow curview rowidlist rowoffsets numcommits global rowrangelist idrowranges rowlaidout rowoptim numcommits - global linesegends + global linesegends selectedline if {$row >= $numcommits} { puts "oops, inserting new row $row but only have $numcommits rows" @@ -3334,6 +3334,7 @@ proc insertrow {row newcmit} { lappend kids $newcmit lset childlist $row $kids set childlist [linsert $childlist $row {}] + set commitlisted [linsert $commitlisted $row 1] set l [llength $displayorder] for {set r $row} {$r < $l} {incr r} { set id [lindex $displayorder $r] @@ -3409,6 +3410,9 @@ proc insertrow {row newcmit} { incr rowoptim incr numcommits + if {[info exists selectedline] && $selectedline >= $row} { + incr selectedline + } redisplay } -- cgit v0.10.2-6-g49f6 From 3dfb9278dff6d81fcc062e9a56edab9ece38ea7d Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Mon, 28 Aug 2006 15:52:13 +0200 Subject: Add --relative-date option to the revision interface Exposes the infrastructure from 9a8e35e98793af086f05d1ca9643052df9b44a74. Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index dd9fff1..a446a6b 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -128,6 +128,11 @@ OPTIONS After a failed merge, show refs that touch files having a conflict and don't exist on all heads to merge. +--relative-date:: + Show dates relative to the current time, e.g. "2 hours ago". + Only takes effect for dates shown in human-readable format, + such as when using "--pretty". + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 7f3e1fc..402af8e 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -85,7 +85,7 @@ static void show_commit(struct commit *commit) static char pretty_header[16384]; pretty_print_commit(revs.commit_format, commit, ~0, pretty_header, sizeof(pretty_header), - revs.abbrev, NULL, NULL); + revs.abbrev, NULL, NULL, revs.relative_date); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 18786f8..d7de18e 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -261,7 +261,7 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->util; if (commit->object.parsed) pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - pretty, sizeof(pretty), 0, NULL, NULL); + pretty, sizeof(pretty), 0, NULL, NULL, 0); else strcpy(pretty, "(unavailable)"); if (!strncmp(pretty, "[PATCH] ", 8)) diff --git a/commit.c b/commit.c index c3ff9b4..5b6e082 100644 --- a/commit.c +++ b/commit.c @@ -467,7 +467,8 @@ static int add_rfc2047(char *buf, const char *line, int len) return bp - buf; } -static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line) +static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, + const char *line, int relative_date) { char *date; int namelen; @@ -507,14 +508,16 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c } switch (fmt) { case CMIT_FMT_MEDIUM: - ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz, 0)); + ret += sprintf(buf + ret, "Date: %s\n", + show_date(time, tz, relative_date)); break; case CMIT_FMT_EMAIL: ret += sprintf(buf + ret, "Date: %s\n", show_rfc2822_date(time, tz)); break; case CMIT_FMT_FULLER: - ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz, 0)); + ret += sprintf(buf + ret, "%sDate: %s\n", what, + show_date(time, tz, relative_date)); break; default: /* notin' */ @@ -557,7 +560,10 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject) +unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, + unsigned long len, char *buf, unsigned long space, + int abbrev, const char *subject, + const char *after_subject, int relative_date) { int hdr = 1, body = 0; unsigned long offset = 0; @@ -646,12 +652,14 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (!memcmp(line, "author ", 7)) offset += add_user_info("Author", fmt, buf + offset, - line + 7); + line + 7, + relative_date); if (!memcmp(line, "committer ", 10) && (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) offset += add_user_info("Commit", fmt, buf + offset, - line + 10); + line + 10, + relative_date); continue; } diff --git a/commit.h b/commit.h index 779ed82..fc13de9 100644 --- a/commit.h +++ b/commit.h @@ -52,7 +52,7 @@ enum cmit_fmt { }; extern enum cmit_fmt get_commit_format(const char *arg); -extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject); +extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, int relative_date); /** Removes the first commit from a list sorted by date, and adds all * of its parents. diff --git a/log-tree.c b/log-tree.c index 031af88..54cdaa4 100644 --- a/log-tree.c +++ b/log-tree.c @@ -194,7 +194,9 @@ void show_log(struct rev_info *opt, const char *sep) /* * And then the pretty-printed message itself */ - len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, extra_headers); + len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, + sizeof(this_header), abbrev, subject, + extra_headers, opt->relative_date); if (opt->add_signoff) len = append_signoff(this_header, sizeof(this_header), len, diff --git a/revision.c b/revision.c index 1d89d72..b588f74 100644 --- a/revision.c +++ b/revision.c @@ -816,6 +816,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->simplify_history = 0; continue; } + if (!strcmp(arg, "--relative-date")) { + revs->relative_date = 1; + continue; + } opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { revs->diff = 1; diff --git a/revision.h b/revision.h index 0c3b8d9..d289781 100644 --- a/revision.h +++ b/revision.h @@ -55,7 +55,8 @@ struct rev_info { /* Format info */ unsigned int shown_one:1, - abbrev_commit:1; + abbrev_commit:1, + relative_date:1; unsigned int abbrev; enum cmit_fmt commit_format; struct log_info *loginfo; -- cgit v0.10.2-6-g49f6 From 25691fbe6d02135d55dfc3a5180e29890dce1521 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg <dennis@stosberg.net> Date: Mon, 28 Aug 2006 17:49:58 +0200 Subject: gitweb: Use --git-dir parameter instead of setting $ENV{'GIT_DIR'} This makes it possible to run gitweb under mod_perl's Apache::Registry. It needs a fairly new git version, with --git-dir=<path> parameter to git wrapper, i.e. post v1.4.2-rc2-g6acbcb9 version. Signed-off-by: Dennis Stosberg <dennis@stosberg.net> Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1430a7a..352236b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -157,6 +157,9 @@ require $GITWEB_CONFIG if -e $GITWEB_CONFIG; # version of the core git binary our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; +# path to the current git repository +our $git_dir; + $projects_list ||= $projectroot; # ====================================================================== @@ -184,7 +187,7 @@ if (defined $project) { if (!(-e "$projectroot/$project/HEAD")) { die_error(undef, "No such project"); } - $ENV{'GIT_DIR'} = "$projectroot/$project"; + $git_dir = "$projectroot/$project"; } our $file_name = $cgi->param('f'); @@ -572,21 +575,31 @@ sub format_diff_line { ## ---------------------------------------------------------------------- ## git utility subroutines, invoking git commands +# returns path to the core git executable and the --git-dir parameter as list +sub git_cmd { + return $GIT, '--git-dir='.$git_dir; +} + +# returns path to the core git executable and the --git-dir parameter as string +sub git_cmd_str { + return join(' ', git_cmd()); +} + # get HEAD ref of given project as hash sub git_get_head_hash { my $project = shift; - my $oENV = $ENV{'GIT_DIR'}; + my $o_git_dir = $git_dir; my $retval = undef; - $ENV{'GIT_DIR'} = "$projectroot/$project"; - if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") { + $git_dir = "$projectroot/$project"; + if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") { my $head = <$fd>; close $fd; if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { $retval = $1; } } - if (defined $oENV) { - $ENV{'GIT_DIR'} = $oENV; + if (defined $o_git_dir) { + $git_dir = $o_git_dir; } return $retval; } @@ -595,7 +608,7 @@ sub git_get_head_hash { sub git_get_type { my $hash = shift; - open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; + open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return; my $type = <$fd>; close $fd or return; chomp $type; @@ -609,7 +622,7 @@ sub git_get_project_config { $key =~ s/^gitweb\.//; return if ($key =~ m/\W/); - my @x = ($GIT, 'repo-config'); + my @x = (git_cmd(), 'repo-config'); if (defined $type) { push @x, $type; } push @x, "--get"; push @x, "gitweb.$key"; @@ -625,7 +638,7 @@ sub git_get_hash_by_path { my $tree = $base; - open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path + open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path or die_error(undef, "Open git-ls-tree failed"); my $line = <$fd>; close $fd or return undef; @@ -756,7 +769,7 @@ sub git_get_references { open $fd, "$projectroot/$project/info/refs" or return; } else { - open $fd, "-|", $GIT, "ls-remote", "." + open $fd, "-|", git_cmd(), "ls-remote", "." or return; } @@ -777,7 +790,7 @@ sub git_get_references { sub git_get_rev_name_tags { my $hash = shift || return undef; - open my $fd, "-|", $GIT, "name-rev", "--tags", $hash + open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash or return; my $name_rev = <$fd>; close $fd; @@ -825,7 +838,7 @@ sub parse_tag { my %tag; my @comment; - open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; + open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return; $tag{'id'} = $tag_id; while (my $line = <$fd>) { chomp $line; @@ -866,7 +879,7 @@ sub parse_commit { @commit_lines = @$commit_text; } else { $/ = "\0"; - open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id + open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; @commit_lines = split '\n', <$fd>; close $fd or return; @@ -1935,7 +1948,7 @@ sub git_project_list { if (!defined $head) { next; } - $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; + $git_dir = "$projectroot/$pr->{'path'}"; my %co = parse_commit($head); if (!%co) { next; @@ -2054,7 +2067,8 @@ sub git_summary { } print "</table>\n"; - open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_get_head_hash($project) + open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", + git_get_head_hash($project) or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -2132,7 +2146,7 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '-l', $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = @@ -2197,7 +2211,7 @@ sub git_blame { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") or die_error(undef, "Error lookup file"); } - open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "annotate", '-l', '-t', '-r', $file_name, $hash_base) or die_error(undef, "Open git-annotate failed"); git_header_html(); my $formats_nav = @@ -2318,7 +2332,7 @@ sub git_blob_plain { } } my $type = shift; - open my $fd, "-|", $GIT, "cat-file", "blob", $hash + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); $type ||= blob_mimetype($fd, $file_name); @@ -2360,7 +2374,7 @@ sub git_blob { } } my $have_blame = gitweb_check_feature('blame'); - open my $fd, "-|", $GIT, "cat-file", "blob", $hash + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); my $mimetype = blob_mimetype($fd, $file_name); if ($mimetype !~ m/^text\//) { @@ -2425,7 +2439,7 @@ sub git_tree { } } $/ = "\0"; - open my $fd, "-|", $GIT, "ls-tree", '-z', $hash + open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash or die_error(undef, "Open git-ls-tree failed"); my @entries = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading tree failed"); @@ -2528,7 +2542,8 @@ sub git_snapshot { -content_disposition => "inline; filename=\"$filename\"", -status => '200 OK'); - open my $fd, "-|", "$GIT tar-tree $hash \'$project\' | $command" or + my $git_command = git_cmd_str(); + open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or die_error(undef, "Execute git-tar-tree failed."); binmode STDOUT, ':raw'; print <$fd>; @@ -2548,7 +2563,7 @@ sub git_log { my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", $GIT, "rev-list", $limit, $hash + open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -2603,7 +2618,7 @@ sub git_commit { if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, $parent, $hash + open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $parent, $hash or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed"); @@ -2710,7 +2725,7 @@ sub git_blobdiff { if (defined $hash_base && defined $hash_parent_base) { if (defined $file_name) { # read raw output - open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, "--", $file_name or die_error(undef, "Open git-diff-tree failed"); @difftree = map { chomp; $_ } <$fd>; @@ -2728,7 +2743,7 @@ sub git_blobdiff { # try to find filename from $hash # read filtered raw output - open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base or die_error(undef, "Open git-diff-tree failed"); @difftree = # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' @@ -2762,7 +2777,7 @@ sub git_blobdiff { } # open patch output - open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, '-p', $hash_parent_base, $hash_base, "--", $file_name or die_error(undef, "Open git-diff-tree failed"); @@ -2798,7 +2813,7 @@ sub git_blobdiff { } # open patch output - open $fd, "-|", $GIT, "diff", '-p', @diff_opts, $hash_parent, $hash + open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts, $hash_parent, $hash or die_error(undef, "Open git-diff failed"); } else { die_error('404 Not Found', "Missing one of the blob diff parameters") @@ -2883,7 +2898,7 @@ sub git_commitdiff { my $fd; my @difftree; if ($format eq 'html') { - open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, "--patch-with-raw", "--full-index", $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed"); @@ -2894,7 +2909,7 @@ sub git_commitdiff { } } elsif ($format eq 'plain') { - open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, '-p', $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed"); @@ -2994,7 +3009,8 @@ sub git_history { git_print_page_path($file_name, $ftype, $hash_base); open my $fd, "-|", - $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; + git_cmd(), "rev-list", "--full-history", $hash_base, "--", $file_name; + git_history_body($fd, $refs, $hash_base, $ftype); close $fd; @@ -3034,7 +3050,7 @@ sub git_search { my $alternate = 0; if ($commit_search) { $/ = "\0"; - open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next; + open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next; while (my $commit_text = <$fd>) { if (!grep m/$searchtext/i, $commit_text) { next; @@ -3086,7 +3102,9 @@ sub git_search { if ($pickaxe_search) { $/ = "\n"; - open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'"; + my $git_command = git_cmd_str(); + open my $fd, "-|", "$git_command rev-list $hash | " . + "$git_command diff-tree -r --stdin -S\'$searchtext\'"; undef %co; my @files; while (my $line = <$fd>) { @@ -3153,7 +3171,7 @@ sub git_shortlog { my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", $GIT, "rev-list", $limit, $hash + open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -3181,7 +3199,7 @@ sub git_shortlog { sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_get_head_hash($project) + open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", git_get_head_hash($project) or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-rev-list failed"); @@ -3204,7 +3222,7 @@ XML last; } my %cd = parse_date($co{'committer_epoch'}); - open $fd, "-|", $GIT, "diff-tree", '-r', @diff_opts, + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $co{'parent'}, $co{'id'} or next; my @difftree = map { chomp; $_ } <$fd>; @@ -3262,7 +3280,7 @@ XML if (!defined $head) { next; } - $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; + $git_dir = "$projectroot/$proj{'path'}"; my %co = parse_commit($head); if (!%co) { next; -- cgit v0.10.2-6-g49f6 From b7f9253df9bf351527f319d67bf67a95a4b8d79a Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 28 Aug 2006 14:48:10 +0200 Subject: gitweb: Make git_print_log generic; git_print_simplified_log uses it Collapse git_print_log and git_print_simplified_log into one subroutine git_print_log. git_print_simplified_log now simply calls git_print_log with proper options. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 352236b..ba21a47 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1377,9 +1377,15 @@ sub git_print_page_path { } } -sub git_print_log { +# sub git_print_log (\@;%) { +sub git_print_log ($;%) { my $log = shift; + my %opts = @_; + if ($opts{'-remove_title'}) { + # remove title, i.e. first line of log + shift @$log; + } # remove leading empty lines while (defined $log->[0] && $log->[0] eq "") { shift @$log; @@ -1389,6 +1395,19 @@ sub git_print_log { my $signoff = 0; my $empty = 0; foreach my $line (@$log) { + if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { + $signoff = 1; + if (! $opts{'-remove_signoff'}) { + print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n"; + next; + } else { + # remove signoff lines + next; + } + } else { + $signoff = 0; + } + # print only one empty line # do not print empty line after signoff if ($line eq "") { @@ -1397,13 +1416,13 @@ sub git_print_log { } else { $empty = 0; } - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - $signoff = 1; - print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n"; - } else { - $signoff = 0; - print format_log_line_html($line) . "<br/>\n"; - } + + print format_log_line_html($line) . "<br/>\n"; + } + + if ($opts{'-final_empty_line'}) { + # end with single empty line + print "<br/>\n" unless $empty; } } @@ -1411,30 +1430,10 @@ sub git_print_simplified_log { my $log = shift; my $remove_title = shift; - shift @$log if $remove_title; - # remove leading empty lines - while (defined $log->[0] && $log->[0] eq "") { - shift @$log; - } - - # simplify and print log - my $empty = 0; - foreach my $line (@$log) { - # remove signoff lines - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - next; - } - # print only one empty line - if ($line eq "") { - next if $empty; - $empty = 1; - } else { - $empty = 0; - } - print format_log_line_html($line) . "<br/>\n"; - } - # end with single empty line - print "<br/>\n" unless $empty; + git_print_log($log, + -final_empty_line=> 1, + -remove_signoff => 1, + -remove_title => $remove_title); } ## ...................................................................... -- cgit v0.10.2-6-g49f6 From d50a9398fe9428f94ec7f2081626cc49c938e704 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 28 Aug 2006 14:48:11 +0200 Subject: gitweb: Do not remove signoff lines in git_print_simplified_log Remove '-remove_signoff => 1' option to git_print_log call in the git_print_simplified_log subroutine. This means that in "log" and "commitdiff" views (git_log and git_commitdiff subroutines) signoff lines will be shown. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ba21a47..d191ef1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1432,7 +1432,6 @@ sub git_print_simplified_log { git_print_log($log, -final_empty_line=> 1, - -remove_signoff => 1, -remove_title => $remove_title); } -- cgit v0.10.2-6-g49f6 From 6fd92a28ebfcbf5d0fc5b51653e223d4e2e727a6 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 28 Aug 2006 14:48:12 +0200 Subject: gitweb: Add author information to commitdiff view Add subroutine git_print_authorship to print author and date of commit, div.author_date style to CSS, and use them in git_commitdiff. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index afd9e8a..eb9fc38 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -116,6 +116,13 @@ div.list_head { font-style: italic; } +div.author_date { + padding: 8px; + border: solid #d9d8d1; + border-width: 0px 0px 1px 0px; + font-style: italic; +} + a.list { text-decoration: none; color: #000000; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d191ef1..6d53c8b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1354,6 +1354,16 @@ sub git_print_header_div { "\n</div>\n"; } +#sub git_print_authorship (\%) { +sub git_print_authorship { + my $co = shift; + + my %ad = parse_date($co->{'author_epoch'}); + print "<div class=\"author_date\">" . + esc_html($co->{'author_name'}) . + " [$ad{'rfc2822'}]</div>\n"; +} + sub git_print_page_path { my $name = shift; my $type = shift; @@ -2933,6 +2943,7 @@ sub git_commitdiff { git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); + git_print_authorship(\%co); print "<div class=\"page_body\">\n"; print "<div class=\"log\">\n"; git_print_simplified_log($co{'comment'}, 1); # skip title -- cgit v0.10.2-6-g49f6 From fba20b429a45a682796214140a7e92fac52a3dbe Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 28 Aug 2006 14:48:13 +0200 Subject: gitweb: git_print_log: signoff line is non-empty line This correct minor error in git_print_log that didn't add final empty line when requested, if commit log ended with signoff. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6d53c8b..24c2fe2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1407,6 +1407,7 @@ sub git_print_log ($;%) { foreach my $line (@$log) { if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { $signoff = 1; + $empty = 0; if (! $opts{'-remove_signoff'}) { print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n"; next; -- cgit v0.10.2-6-g49f6 From b4657e7759fa5f3b1ef09d147c2fd0254f8fcb2d Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 28 Aug 2006 14:48:14 +0200 Subject: gitweb: Add diff tree, with links to patches, to commitdiff view Added/uncommented git_difftree_body invocation in git_commitdiff. Added anchors (via 'id' attribute) to patches in patchset. git_difftree_body is modified to link to patch anchor when called from git_commitdiff, instead of link to blobdiff. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24c2fe2..1b352cb 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1460,6 +1460,7 @@ sub git_difftree_body { print "<table class=\"diff_tree\">\n"; my $alternate = 0; + my $patchno = 0; foreach my $line (@{$difftree}) { my %diff = parse_difftree_raw_line($line); @@ -1500,8 +1501,14 @@ sub git_difftree_body { "<td class=\"link\">" . $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'})}, - "blob") . - "</td>\n"; + "blob"); + if ($action == "commitdiff") { + # link to patch + $patchno++; + print " | " . + $cgi->a({-href => "#patch$patchno"}, "patch"); + } + print "</td>\n"; } elsif ($diff{'status'} eq "D") { # deleted my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>"; @@ -1515,8 +1522,14 @@ sub git_difftree_body { $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'})}, "blob") . - " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$parent, + " | "; + if ($action == "commitdiff") { + # link to patch + $patchno++; + print " | " . + $cgi->a({-href => "#patch$patchno"}, "patch"); + } + print $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$diff{'file'})}, "history") . "</td>\n"; @@ -1552,16 +1565,23 @@ sub git_difftree_body { print "</td>\n" . "<td>$mode_chnge</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, - "blob"); + $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob"); if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - print " | " . - $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, - "diff"); + if ($action == "commitdiff") { + # link to patch + $patchno++; + print " | " . + $cgi->a({-href => "#patch$patchno"}, "patch"); + } else { + print " | " . + $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, + "diff"); + } } print " | " . $cgi->a({-href => href(action=>"history", @@ -1591,12 +1611,19 @@ sub git_difftree_body { hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})}, "blob"); if ($diff{'to_id'} ne $diff{'from_id'}) { - print " | " . - $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, - "diff"); + if ($action == "commitdiff") { + # link to patch + $patchno++; + print " | " . + $cgi->a({-href => "#patch$patchno"}, "patch"); + } else { + print " | " . + $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff"); + } } print "</td>\n"; @@ -1629,7 +1656,7 @@ sub git_patchset_body { # first patch in patchset $patch_found = 1; } - print "<div class=\"patch\">\n"; + print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n"; if (ref($difftree->[$patch_idx]) eq "HASH") { $diffinfo = $difftree->[$patch_idx]; @@ -2977,8 +3004,8 @@ TEXT # write patch if ($format eq 'html') { - #git_difftree_body(\@difftree, $hash, $hash_parent); - #print "<br/>\n"; + git_difftree_body(\@difftree, $hash, $hash_parent); + print "<br/>\n"; git_patchset_body($fd, \@difftree, $hash, $hash_parent); close $fd; -- cgit v0.10.2-6-g49f6 From a44465ccd998bcb6fbfb49cb63d5206dce26051a Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 28 Aug 2006 23:17:31 +0200 Subject: gitweb: Add local time and timezone to git_print_authorship Add local time (hours and minutes) and local timezone to the output of git_print_authorship command, used by git_commitdiff. The code was taken from git_commit subroutine. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1b352cb..9324d71 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1358,10 +1358,18 @@ sub git_print_header_div { sub git_print_authorship { my $co = shift; - my %ad = parse_date($co->{'author_epoch'}); + my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<div class=\"author_date\">" . esc_html($co->{'author_name'}) . - " [$ad{'rfc2822'}]</div>\n"; + " [$ad{'rfc2822'}"; + if ($ad{'hour_local'} < 6) { + printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", + $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + } else { + printf(" (%02d:%02d %s)", + $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + } + print "]</div>\n"; } sub git_print_page_path { -- cgit v0.10.2-6-g49f6 From 9e848013968959bd4de5d407c2ee91cb960c53bb Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz <matthias@spinlock.ch> Date: Tue, 29 Aug 2006 11:12:14 +0200 Subject: Check if pack directory exists prior to descending into it This fixes the following warning: git-repack: line 42: cd: .git/objects/pack: No such file or directory This happens only, when git-repack -a is run without any packs in the repository. Signed-off-by: Matthias Kestenholz <matthias@spinlock.ch> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-repack.sh b/git-repack.sh index 9da92fb..584a732 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -38,7 +38,7 @@ case ",$all_into_one," in pack_objects= # Redundancy check in all-into-one case is trivial. - existing=`cd "$PACKDIR" && \ + existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \ find . -type f \( -name '*.pack' -o -name '*.idx' \) -print` ;; esac -- cgit v0.10.2-6-g49f6 From 071fa89e25855a746728b835359eb263c7c7ee7f Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Tue, 29 Aug 2006 11:47:30 -0700 Subject: git-fsck-objects: lacking default references should not be fatal The comment added says it all: if we have lost all references in a git archive, git-fsck-objects should still work, so instead of dying it should just notify the user about that condition. This change was triggered by me just doing a "git-init-db" and then populating that empty git archive with a pack/index file to look at it. Having git-fsck-objects not work just because I didn't have any references handy was rather irritating, since part of the reason for running git-fsck-objects in the first place was to _find_ the missing references. However, "--unreachable" really doesn't make sense in that situation, and we want to turn it off to protect anybody who uses the old "git prune" shell-script (rather than the modern built-in). The old pruning script used to remove all objects that were reported as unreachable, and without any refs, that obviously means everything - not worth it. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/fsck-objects.c b/fsck-objects.c index ae0ec8d..24286de 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -425,8 +425,23 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1) static void get_default_heads(void) { for_each_ref(fsck_handle_ref); - if (!default_refs) - die("No default references"); + + /* + * Not having any default heads isn't really fatal, but + * it does mean that "--unreachable" no longer makes any + * sense (since in this case everything will obviously + * be unreachable by definition. + * + * Showing dangling objects is valid, though (as those + * dangling objects are likely lost heads). + * + * So we just print a warning about it, and clear the + * "show_unreachable" flag. + */ + if (!default_refs) { + error("No default references"); + show_unreachable = 0; + } } static void fsck_object_dir(const char *path) -- cgit v0.10.2-6-g49f6 From c9b0597d3d3168e29f68b0e82fa755ee165f6b72 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Tue, 29 Aug 2006 12:50:29 +0200 Subject: unpack-objects: remove unused variable "eof" Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index ca0ebc2..0c180b5 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -15,7 +15,7 @@ static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file"; /* We always read in 4kB chunks. */ static unsigned char buffer[4096]; -static unsigned long offset, len, eof; +static unsigned long offset, len; static SHA_CTX ctx; /* @@ -26,8 +26,6 @@ static void * fill(int min) { if (min <= len) return buffer + offset; - if (eof) - die("unable to fill input"); if (min > sizeof(buffer)) die("cannot fill %d bytes", min); if (offset) { -- cgit v0.10.2-6-g49f6 From 104ff34a742672b360b1da2b7d7668a14da551c2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Tue, 29 Aug 2006 12:51:14 +0200 Subject: Makefile: fix typo We checked NO_SETENV instead of NO_UNSETENV to decide if unsetenv is available. Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index a608476..05bd77f 100644 --- a/Makefile +++ b/Makefile @@ -496,7 +496,7 @@ ifdef NO_SETENV COMPAT_CFLAGS += -DNO_SETENV COMPAT_OBJS += compat/setenv.o endif -ifdef NO_SETENV +ifdef NO_UNSETENV COMPAT_CFLAGS += -DNO_UNSETENV COMPAT_OBJS += compat/unsetenv.o endif -- cgit v0.10.2-6-g49f6 From 28f5c70b859e02e6b688bf35605678680c08045b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Tue, 29 Aug 2006 13:02:35 +0200 Subject: Remove uneeded #include Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/peek-remote.c b/peek-remote.c index 2b30980..87f1543 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -1,7 +1,6 @@ #include "cache.h" #include "refs.h" #include "pkt-line.h" -#include <sys/wait.h> static const char peek_remote_usage[] = "git-peek-remote [--exec=upload-pack] [host:]directory"; diff --git a/receive-pack.c b/receive-pack.c index 2015316..78f75da 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -2,7 +2,6 @@ #include "refs.h" #include "pkt-line.h" #include "run-command.h" -#include <sys/wait.h> static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; -- cgit v0.10.2-6-g49f6 From fc1c75ec74c372670f01f22c68193156a445728a Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Tue, 29 Aug 2006 13:37:06 +0200 Subject: log-tree.c: cleanup a bit append_signoff() This patch clean up append_signoff() by moving specific code that looks up for "^[-A-Za-z]+: [^@]+@" pattern into a function. It also stops the primary search when the cursor oversteps 'buf + at' limit. This patch changes slightly append_signoff() behaviour too. If we detect any Signed-off-by pattern during the primary search, we needn't to do a pattern research after. Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/log-tree.c b/log-tree.c index 54cdaa4..fbe1399 100644 --- a/log-tree.c +++ b/log-tree.c @@ -12,10 +12,58 @@ static void show_parents(struct commit *commit, int abbrev) } } +/* + * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches + * Signed-off-by: and Acked-by: lines. + */ +static int detect_any_signoff(char *letter, int size) +{ + char ch, *cp; + int seen_colon = 0; + int seen_at = 0; + int seen_name = 0; + int seen_head = 0; + + cp = letter + size; + while (letter <= --cp && (ch = *cp) == '\n') + continue; + + while (letter <= cp) { + ch = *cp--; + if (ch == '\n') + break; + + if (!seen_at) { + if (ch == '@') + seen_at = 1; + continue; + } + if (!seen_colon) { + if (ch == '@') + return 0; + else if (ch == ':') + seen_colon = 1; + else + seen_name = 1; + continue; + } + if (('A' <= ch && ch <= 'Z') || + ('a' <= ch && ch <= 'z') || + ch == '-') { + seen_head = 1; + continue; + } + /* no empty last line doesn't match */ + return 0; + } + return seen_head && seen_name; +} + static int append_signoff(char *buf, int buf_sz, int at, const char *signoff) { - int signoff_len = strlen(signoff); static const char signed_off_by[] = "Signed-off-by: "; + int signoff_len = strlen(signoff); + int has_signoff = 0; char *cp = buf; /* Do we have enough space to add it? */ @@ -23,58 +71,26 @@ static int append_signoff(char *buf, int buf_sz, int at, const char *signoff) return at; /* First see if we already have the sign-off by the signer */ - while (1) { - cp = strstr(cp, signed_off_by); - if (!cp) - break; + while ((cp = strstr(cp, signed_off_by))) { + + has_signoff = 1; + cp += strlen(signed_off_by); - if ((cp + signoff_len < buf + at) && - !strncmp(cp, signoff, signoff_len) && - isspace(cp[signoff_len])) - return at; /* we already have him */ + if (cp + signoff_len >= buf + at) + break; + if (strncmp(cp, signoff, signoff_len)) + continue; + if (!isspace(cp[signoff_len])) + continue; + /* we already have him */ + return at; } - /* Does the last line already end with "^[-A-Za-z]+: [^@]+@"? - * If not, add a blank line to separate the message from - * the run of Signed-off-by: and Acked-by: lines. - */ - { - char ch; - int seen_colon, seen_at, seen_name, seen_head, not_signoff; - seen_colon = 0; - seen_at = 0; - seen_name = 0; - seen_head = 0; - not_signoff = 0; - cp = buf + at; - while (buf <= --cp && (ch = *cp) == '\n') - ; - while (!not_signoff && buf <= cp && (ch = *cp--) != '\n') { - if (!seen_at) { - if (ch == '@') - seen_at = 1; - continue; - } - if (!seen_colon) { - if (ch == '@') - not_signoff = 1; - else if (ch == ':') - seen_colon = 1; - else - seen_name = 1; - continue; - } - if (('A' <= ch && ch <= 'Z') || - ('a' <= ch && ch <= 'z') || - ch == '-') { - seen_head = 1; - continue; - } - not_signoff = 1; - } - if (not_signoff || !seen_head || !seen_name) - buf[at++] = '\n'; - } + if (!has_signoff) + has_signoff = detect_any_signoff(buf, at); + + if (!has_signoff) + buf[at++] = '\n'; strcpy(buf + at, signed_off_by); at += strlen(signed_off_by); -- cgit v0.10.2-6-g49f6 From 3a36bc846949df8a7fb0ad9b2fc8ffe294cdca7c Mon Sep 17 00:00:00 2001 From: Dennis Stosberg <dennis@stosberg.net> Date: Thu, 31 Aug 2006 21:32:45 +0200 Subject: gitweb: Remove forgotten call to git_to_hash On Aug 27th, Jakub Narebski sent a patch which removed the git_to_hash() function and this call to it. The patch did not apply cleanly and had to be applied manually. Removing the last chunk has obviously been forgotten. See: commit 0aea33762b1262d11fb43eda9f3fc152b5622cca and message <200608272345.26722.jnareb@gmail.com> Signed-off-by: Dennis Stosberg <dennis@stosberg.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9324d71..68f40bd 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2778,10 +2778,6 @@ sub git_blobdiff { @difftree or die_error('404 Not Found', "Blob diff not found"); - } elsif (defined $hash) { # try to find filename from $hash - if ($hash !~ /[0-9a-fA-F]{40}/) { - $hash = git_to_hash($hash); - } } elsif (defined $hash && $hash =~ /[0-9a-fA-F]{40}/) { # try to find filename from $hash -- cgit v0.10.2-6-g49f6 From 4b5dc988c0db1e7ce5ba41c09a1e75cbc71c3f35 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg <dennis@stosberg.net> Date: Tue, 29 Aug 2006 09:19:02 +0200 Subject: use do() instead of require() to include configuration When run under mod_perl, require() will read and execute the configuration file on the first invocation only. On every subsequent invocation, all configuration variables will be reset to their default values. do() reads and executes the configuration file unconditionally. Signed-off-by: Dennis Stosberg <dennis@stosberg.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 68f40bd..7922c3c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -152,7 +152,7 @@ sub feature_snapshot { our @diff_opts = ('-M'); # taken from git_commit our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; -require $GITWEB_CONFIG if -e $GITWEB_CONFIG; +do $GITWEB_CONFIG if -e $GITWEB_CONFIG; # version of the core git binary our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; -- cgit v0.10.2-6-g49f6 From cb849b46ac68feea716a45fd1de99cfdd71123d0 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Thu, 31 Aug 2006 00:32:15 +0200 Subject: gitweb: Move git-ls-tree output parsing to parse_ls_tree_line Add new subroutine parse_ls_tree_line and use it in git_tree. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7922c3c..758032a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1034,6 +1034,27 @@ sub parse_difftree_raw_line { return wantarray ? %res : \%res; } +# parse line of git-ls-tree output +sub parse_ls_tree_line ($;%) { + my $line = shift; + my %opts = @_; + my %res; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + if ($opts{'-z'}) { + $res{'name'} = $4; + } else { + $res{'name'} = unquote($4); + } + + return wantarray ? %res : \%res; +} + ## ...................................................................... ## parse to array of hashes functions @@ -2512,51 +2533,54 @@ sub git_tree { print "<table cellspacing=\"0\">\n"; my $alternate = 0; foreach my $line (@entries) { - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - my $t_mode = $1; - my $t_type = $2; - my $t_hash = $3; - my $t_name = validate_input($4); + my %t = parse_ls_tree_line($line, -z => 1); + if ($alternate) { print "<tr class=\"dark\">\n"; } else { print "<tr class=\"light\">\n"; } $alternate ^= 1; - print "<td class=\"mode\">" . mode_str($t_mode) . "</td>\n"; - if ($t_type eq "blob") { + + print "<td class=\"mode\">" . mode_str($t{'mode'}) . "</td>\n"; + if ($t{'type'} eq "blob") { print "<td class=\"list\">" . - $cgi->a({-href => href(action=>"blob", hash=>$t_hash, file_name=>"$base$t_name", %base_key), - -class => "list"}, esc_html($t_name)) . + $cgi->a({-href => href(action=>"blob", hash=>$t{'hash'}, + file_name=>"$base$t{'name'}", %base_key), + -class => "list"}, esc_html($t{'name'})) . "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => href(action=>"blob", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, + $cgi->a({-href => href(action=>"blob", hash=>$t{'hash'}, + file_name=>"$base$t{'name'}", %base_key)}, "blob"); if ($have_blame) { print " | " . - $cgi->a({-href => href(action=>"blame", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, + $cgi->a({-href => href(action=>"blame", hash=>$t{'hash'}, + file_name=>"$base$t{'name'}", %base_key)}, "blame"); } print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, - hash=>$t_hash, file_name=>"$base$t_name")}, + hash=>$t{'hash'}, file_name=>"$base$t{'name'}")}, "history") . " | " . $cgi->a({-href => href(action=>"blob_plain", - hash=>$t_hash, file_name=>"$base$t_name")}, + hash=>$t{'hash'}, file_name=>"$base$t{'name'}")}, "raw") . "</td>\n"; - } elsif ($t_type eq "tree") { + } elsif ($t{'type'} eq "tree") { print "<td class=\"list\">" . - $cgi->a({-href => href(action=>"tree", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, - esc_html($t_name)) . + $cgi->a({-href => href(action=>"tree", hash=>$t{'hash'}, + file_name=>"$base$t{'name'}", %base_key)}, + esc_html($t{'name'})) . "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => href(action=>"tree", hash=>$t_hash, file_name=>"$base$t_name", %base_key)}, + $cgi->a({-href => href(action=>"tree", hash=>$t{'hash'}, + file_name=>"$base$t{'name'}", %base_key)}, "tree") . " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, file_name=>"$base$t_name")}, + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + file_name=>"$base$t{'name'}")}, "history") . "</td>\n"; } -- cgit v0.10.2-6-g49f6 From fa702003e4f63ecdc71d16b51efc02f33fe7931f Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Thu, 31 Aug 2006 00:35:07 +0200 Subject: gitweb: Separate printing of git_tree row into git_print_tree_entry This is preparation for "tree blame" (similar to what ViewVC shows) output, i.e. for each entry give commit where it was changed. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 758032a..7f6bdaa 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1475,6 +1475,62 @@ sub git_print_simplified_log { -remove_title => $remove_title); } +# print tree entry (row of git_tree), but without encompassing <tr> element +sub git_print_tree_entry { + my ($t, $basedir, $hash_base, $have_blame) = @_; + + my %base_key = (); + $base_key{hash_base} = $hash_base if defined $hash_base; + + print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n"; + if ($t->{'type'} eq "blob") { + print "<td class=\"list\">" . + $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key), + -class => "list"}, esc_html($t->{'name'})) . + "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blob"); + if ($have_blame) { + print " | " . + $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); + } + if (defined $hash_base) { + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, + "history"); + } + print " | " . + $cgi->a({-href => href(action=>"blob_plain", + hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, + "raw") . + "</td>\n"; + + } elsif ($t->{'type'} eq "tree") { + print "<td class=\"list\">" . + $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + esc_html($t->{'name'})) . + "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "tree"); + if (defined $hash_base) { + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + file_name=>"$basedir$t->{'name'}")}, + "history"); + } + print "</td>\n"; + } +} + ## ...................................................................... ## functions printing large fragments of HTML @@ -2513,14 +2569,13 @@ sub git_tree { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $hash_base); git_header_html(); - my %base_key = (); my $base = ""; my $have_blame = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { - $base_key{hash_base} = $hash_base; git_print_page_nav('tree','', $hash_base); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { + undef $hash_base; print "<div class=\"page_nav\">\n"; print "<br/><br/></div>\n"; print "<div class=\"title\">$hash</div>\n"; @@ -2542,48 +2597,8 @@ sub git_tree { } $alternate ^= 1; - print "<td class=\"mode\">" . mode_str($t{'mode'}) . "</td>\n"; - if ($t{'type'} eq "blob") { - print "<td class=\"list\">" . - $cgi->a({-href => href(action=>"blob", hash=>$t{'hash'}, - file_name=>"$base$t{'name'}", %base_key), - -class => "list"}, esc_html($t{'name'})) . - "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"blob", hash=>$t{'hash'}, - file_name=>"$base$t{'name'}", %base_key)}, - "blob"); - if ($have_blame) { - print " | " . - $cgi->a({-href => href(action=>"blame", hash=>$t{'hash'}, - file_name=>"$base$t{'name'}", %base_key)}, - "blame"); - } - print " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, - hash=>$t{'hash'}, file_name=>"$base$t{'name'}")}, - "history") . - " | " . - $cgi->a({-href => href(action=>"blob_plain", - hash=>$t{'hash'}, file_name=>"$base$t{'name'}")}, - "raw") . - "</td>\n"; - } elsif ($t{'type'} eq "tree") { - print "<td class=\"list\">" . - $cgi->a({-href => href(action=>"tree", hash=>$t{'hash'}, - file_name=>"$base$t{'name'}", %base_key)}, - esc_html($t{'name'})) . - "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"tree", hash=>$t{'hash'}, - file_name=>"$base$t{'name'}", %base_key)}, - "tree") . - " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, - file_name=>"$base$t{'name'}")}, - "history") . - "</td>\n"; - } + git_print_tree_entry(\%t, $base, $hash_base, $have_blame); + print "</tr>\n"; } print "</table>\n" . -- cgit v0.10.2-6-g49f6 From 0edcb37d6707c8c9b566bcbb51a2d70275bd30da Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Thu, 31 Aug 2006 00:36:04 +0200 Subject: gitweb: Extend parse_difftree_raw_line to save commit info Extend parse_difftree_raw_line to save commit info from when git-diff-tree is given only one <tree-ish>, for example when fed from git-rev-list using --stdin option. git-diff-tree outputs a line with the commit ID when applicable. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7f6bdaa..0984e85 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1027,9 +1027,9 @@ sub parse_difftree_raw_line { } } # 'c512b523472485aef4fff9e57b229d9d243c967f' - #elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { - # $res{'commit'} = $1; - #} + elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { + $res{'commit'} = $1; + } return wantarray ? %res : \%res; } -- cgit v0.10.2-6-g49f6 From 2c6d22df9f8a975c88fc9a93c4db8bb0bd116b74 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Thu, 31 Aug 2006 14:14:20 -0700 Subject: t5710: fix two thinkos. The intention of the test seems to be to build a long chain of clones that locally borrow objects from their parents and see the system give up dereferencing long chains. There were two problems: (1) it did not test the right repository; (2) it did not build a chain long enough to trigger the limitation. I do not think it is a good test to make sure the limitation the current implementation happens to have still exists, but that is a topic at a totally different level. At least this fixes the broken test. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh index 2e1b48a..b9f6d96 100755 --- a/t/t5710-info-alternate.sh +++ b/t/t5710-info-alternate.sh @@ -58,6 +58,8 @@ test_expect_failure 'creating too deep nesting' \ git clone -l -s D E && git clone -l -s E F && git clone -l -s F G && +git clone -l -s G H && +cd H && test_valid_repo' cd "$base_dir" -- cgit v0.10.2-6-g49f6 From 7cf67205ca68a157c6ffdb4e5a4ff231217c0871 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Thu, 31 Aug 2006 08:42:11 +0200 Subject: Trace into open fd and refactor tracing code. Now if GIT_TRACE is set to an integer value greater than 1 and lower than 10, we interpret this as an open fd value and we trace into it. Note that this behavior is not compatible with the previous one. We also trace whole messages using one write(2) call to make sure messages from processes do net get mixed up in the middle. It's now possible to run the tests like this: GIT_TRACE=9 make test 9>/var/tmp/trace.log Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 05bd77f..a639cdf 100644 --- a/Makefile +++ b/Makefile @@ -249,7 +249,7 @@ LIB_OBJS = \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - write_or_die.o \ + write_or_die.o trace.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) BUILTIN_OBJS = \ diff --git a/cache.h b/cache.h index 03d9dd0..f532b46 100644 --- a/cache.h +++ b/cache.h @@ -398,6 +398,7 @@ extern char git_commit_encoding[MAX_ENCODING_LENGTH]; extern int copy_fd(int ifd, int ofd); extern void write_or_die(int fd, const void *buf, size_t count); +extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); /* Finish off pack transfer receiving end */ extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int); @@ -423,4 +424,9 @@ extern struct commit *alloc_commit_node(void); extern struct tag *alloc_tag_node(void); extern void alloc_report(void); +/* trace.c */ +extern int nfvasprintf(char **str, const char *fmt, va_list va); +extern void trace_printf(const char *format, ...); +extern void trace_argv_printf(const char **argv, int count, const char *format, ...); + #endif /* CACHE_H */ diff --git a/exec_cmd.c b/exec_cmd.c index e30936d..5d6a124 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -97,26 +97,12 @@ int execv_git_cmd(const char **argv) tmp = argv[0]; argv[0] = git_command; - if (getenv("GIT_TRACE")) { - const char **p = argv; - fputs("trace: exec:", stderr); - while (*p) { - fputc(' ', stderr); - sq_quote_print(stderr, *p); - ++p; - } - putc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(argv, -1, "trace: exec:"); /* execve() can only ever return if it fails */ execve(git_command, (char **)argv, environ); - if (getenv("GIT_TRACE")) { - fprintf(stderr, "trace: exec failed: %s\n", - strerror(errno)); - fflush(stderr); - } + trace_printf("trace: exec failed: %s\n", strerror(errno)); argv[0] = tmp; } diff --git a/git.c b/git.c index bd07289..0c8cfa8 100644 --- a/git.c +++ b/git.c @@ -179,17 +179,9 @@ static int handle_alias(int *argcp, const char ***argv) if (!strcmp(alias_command, new_argv[0])) die("recursive alias: %s", alias_command); - if (getenv("GIT_TRACE")) { - int i; - fprintf(stderr, "trace: alias expansion: %s =>", - alias_command); - for (i = 0; i < count; ++i) { - fputc(' ', stderr); - sq_quote_print(stderr, new_argv[i]); - } - fputc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(new_argv, count, + "trace: alias expansion: %s =>", + alias_command); new_argv = xrealloc(new_argv, sizeof(char*) * (count + *argcp + 1)); @@ -292,16 +284,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); - if (getenv("GIT_TRACE")) { - int j; - fprintf(stderr, "trace: built-in: git"); - for (j = 0; j < argc; ++j) { - fputc(' ', stderr); - sq_quote_print(stderr, argv[j]); - } - putc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(argv, argc, "trace: built-in: git"); exit(p->fn(argc, argv, prefix)); } diff --git a/imap-send.c b/imap-send.c index 65c71c6..6a52dbd 100644 --- a/imap-send.c +++ b/imap-send.c @@ -110,7 +110,6 @@ static char *next_arg( char ** ); static void free_generic_messages( message_t * ); -static int nfvasprintf( char **str, const char *fmt, va_list va ); static int nfsnprintf( char *buf, int blen, const char *fmt, ... ); @@ -372,21 +371,6 @@ free_generic_messages( message_t *msgs ) } static int -git_vasprintf( char **strp, const char *fmt, va_list ap ) -{ - int len; - char tmp[1024]; - - if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = xmalloc( len + 1 ))) - return -1; - if (len >= (int)sizeof(tmp)) - vsprintf( *strp, fmt, ap ); - else - memcpy( *strp, tmp, len + 1 ); - return len; -} - -static int nfsnprintf( char *buf, int blen, const char *fmt, ... ) { int ret; @@ -399,15 +383,6 @@ nfsnprintf( char *buf, int blen, const char *fmt, ... ) return ret; } -static int -nfvasprintf( char **str, const char *fmt, va_list va ) -{ - int ret = git_vasprintf( str, fmt, va ); - if (ret < 0) - die( "Fatal: Out of memory\n"); - return ret; -} - static struct { unsigned char i, j, s[256]; } rs; diff --git a/quote.c b/quote.c index e220dcc..a38786c 100644 --- a/quote.c +++ b/quote.c @@ -74,6 +74,38 @@ char *sq_quote(const char *src) return buf; } +char *sq_quote_argv(const char** argv, int count) +{ + char *buf, *to; + int i; + size_t len = 0; + + /* Count argv if needed. */ + if (count < 0) { + for (count = 0; argv[count]; count++) + ; /* just counting */ + } + + /* Special case: no argv. */ + if (!count) + return xcalloc(1,1); + + /* Get destination buffer length. */ + for (i = 0; i < count; i++) + len += sq_quote_buf(NULL, 0, argv[i]) + 1; + + /* Alloc destination buffer. */ + to = buf = xmalloc(len + 1); + + /* Copy into destination buffer. */ + for (i = 0; i < count; ++i) { + *to++ = ' '; + to += sq_quote_buf(to, len, argv[i]); + } + + return buf; +} + char *sq_dequote(char *arg) { char *dst = arg; diff --git a/quote.h b/quote.h index fc5481e..a6c4611 100644 --- a/quote.h +++ b/quote.h @@ -31,6 +31,7 @@ extern char *sq_quote(const char *src); extern void sq_quote_print(FILE *stream, const char *src); extern size_t sq_quote_buf(char *dst, size_t n, const char *src); +extern char *sq_quote_argv(const char** argv, int count); /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have diff --git a/t/test-lib.sh b/t/test-lib.sh index 470a909..b0d7990 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -28,7 +28,6 @@ unset GIT_DIR unset GIT_EXTERNAL_DIFF unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY -unset GIT_TRACE unset SHA1_FILE_DIRECTORIES unset SHA1_FILE_DIRECTORY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME diff --git a/trace.c b/trace.c new file mode 100644 index 0000000..90847c3 --- /dev/null +++ b/trace.c @@ -0,0 +1,125 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org> + * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net> + * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu> + * Copyright (C) 2006 Mike McCormack + * Copyright (C) 2006 Christian Couder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "cache.h" +#include "quote.h" + +/* Stolen from "imap-send.c". */ +static int git_vasprintf(char **strp, const char *fmt, va_list ap) +{ + int len; + char tmp[1024]; + + if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 || + !(*strp = xmalloc(len + 1))) + return -1; + if (len >= (int)sizeof(tmp)) + vsprintf(*strp, fmt, ap); + else + memcpy(*strp, tmp, len + 1); + return len; +} + +/* Stolen from "imap-send.c". */ +int nfvasprintf(char **str, const char *fmt, va_list va) +{ + int ret = git_vasprintf(str, fmt, va); + if (ret < 0) + die("Fatal: Out of memory\n"); + return ret; +} + +/* Get a trace file descriptor from GIT_TRACE env variable. */ +static int get_trace_fd() +{ + char *trace = getenv("GIT_TRACE"); + + if (!trace || !strcmp(trace, "0") || !strcasecmp(trace," false")) + return 0; + if (!strcmp(trace, "1") || !strcasecmp(trace, "true")) + return STDERR_FILENO; + if (strlen(trace) == 1 && isdigit(*trace)) + return atoi(trace); + + fprintf(stderr, "What does '%s' for GIT_TRACE means ?\n", trace); + fprintf(stderr, "Defaulting to tracing on stderr...\n"); + return STDERR_FILENO; +} + +static const char err_msg[] = "Could not trace into fd given by " + "GIT_TRACE environment variable"; + +void trace_printf(const char *format, ...) +{ + char *trace_str; + va_list rest; + int fd = get_trace_fd(); + + if (!fd) + return; + + va_start(rest, format); + nfvasprintf(&trace_str, format, rest); + va_end(rest); + + write_or_whine(fd, trace_str, strlen(trace_str), err_msg); + + free(trace_str); +} + +void trace_argv_printf(const char **argv, int count, const char *format, ...) +{ + char *argv_str, *format_str, *trace_str; + size_t argv_len, format_len, trace_len; + va_list rest; + int fd = get_trace_fd(); + + if (!fd) + return; + + /* Get the argv string. */ + argv_str = sq_quote_argv(argv, count); + argv_len = strlen(argv_str); + + /* Get the formated string. */ + va_start(rest, format); + nfvasprintf(&format_str, format, rest); + va_end(rest); + + /* Allocate buffer for trace string. */ + format_len = strlen(format_str); + trace_len = argv_len + format_len + 1; /* + 1 for \n */ + trace_str = xmalloc(trace_len + 1); + + /* Copy everything into the trace string. */ + strncpy(trace_str, format_str, format_len); + strncpy(trace_str + format_len, argv_str, argv_len); + strcpy(trace_str + trace_len - 1, "\n"); + + write_or_whine(fd, trace_str, trace_len, err_msg); + + free(argv_str); + free(format_str); + free(trace_str); +} diff --git a/write_or_die.c b/write_or_die.c index ab4cb8a..bfe4eeb 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -18,3 +18,28 @@ void write_or_die(int fd, const void *buf, size_t count) p += written; } } + +int write_or_whine(int fd, const void *buf, size_t count, const char *msg) +{ + const char *p = buf; + ssize_t written; + + while (count > 0) { + written = xwrite(fd, p, count); + if (written == 0) { + fprintf(stderr, "%s: disk full?\n", msg); + return 0; + } + else if (written < 0) { + if (errno == EPIPE) + exit(0); + fprintf(stderr, "%s: write error (%s)\n", + msg, strerror(errno)); + return 0; + } + count -= written; + p += written; + } + + return 1; +} -- cgit v0.10.2-6-g49f6 From ef1186228d39af29c109785fa752e3866c79a6b3 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Fri, 1 Sep 2006 01:15:38 +0200 Subject: git(7): move gitk(1) to the list of porcelain commands Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git.txt b/Documentation/git.txt index a9c87e3..76b41c8 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -303,6 +303,9 @@ gitlink:git-format-patch[1]:: gitlink:git-grep[1]:: Print lines matching a pattern. +gitlink:gitk[1]:: + The git repository browser. + gitlink:git-log[1]:: Shows commit logs. @@ -483,13 +486,6 @@ gitlink:git-stripspace[1]:: Filter out empty lines. -Commands not yet documented ---------------------------- - -gitlink:gitk[1]:: - The gitk repository browser. - - Configuration Mechanism ----------------------- -- cgit v0.10.2-6-g49f6 From 2d7320d0b09d7a9aab4e5dbc5458f37bfb6ce9f5 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Fri, 1 Sep 2006 00:32:39 +0200 Subject: Use xmalloc instead of malloc Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 76d22b4..ed59e77 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -140,14 +140,14 @@ static int handle_line(char *line) if (!strcmp(".", src) || !strcmp(src, origin)) { int len = strlen(origin); if (origin[0] == '\'' && origin[len - 1] == '\'') { - char *new_origin = malloc(len - 1); + char *new_origin = xmalloc(len - 1); memcpy(new_origin, origin + 1, len - 2); new_origin[len - 1] = 0; origin = new_origin; } else origin = strdup(origin); } else { - char *new_origin = malloc(strlen(origin) + strlen(src) + 5); + char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); sprintf(new_origin, "%s of %s", origin, src); origin = new_origin; } @@ -214,7 +214,7 @@ static void shortlog(const char *name, unsigned char *sha1, if (eol) { int len = eol - bol; - oneline = malloc(len + 1); + oneline = xmalloc(len + 1); memcpy(oneline, bol, len); oneline[len] = 0; } else diff --git a/builtin-repo-config.c b/builtin-repo-config.c index 6560cf1..a1756c8 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -84,7 +84,7 @@ static int get_value(const char* key_, const char* regex_) *tl = tolower(*tl); if (use_key_regexp) { - key_regexp = (regex_t*)malloc(sizeof(regex_t)); + key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { fprintf(stderr, "Invalid key pattern: %s\n", key_); goto free_strings; @@ -97,7 +97,7 @@ static int get_value(const char* key_, const char* regex_) regex_++; } - regexp = (regex_t*)malloc(sizeof(regex_t)); + regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { fprintf(stderr, "Invalid pattern: %s\n", regex_); goto free_strings; diff --git a/config.c b/config.c index d9f2b78..c0897cc 100644 --- a/config.c +++ b/config.c @@ -565,7 +565,7 @@ int git_config_set_multivar(const char* key, const char* value, /* * Validate the key and while at it, lower case it for matching. */ - store.key = (char*)malloc(strlen(key)+1); + store.key = xmalloc(strlen(key) + 1); dot = 0; for (i = 0; key[i]; i++) { unsigned char c = key[i]; @@ -633,7 +633,7 @@ int git_config_set_multivar(const char* key, const char* value, } else store.do_not_match = 0; - store.value_regex = (regex_t*)malloc(sizeof(regex_t)); + store.value_regex = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(store.value_regex, value_regex, REG_EXTENDED)) { fprintf(stderr, "Invalid pattern: %s\n", diff --git a/git.c b/git.c index bd07289..05871ad 100644 --- a/git.c +++ b/git.c @@ -29,7 +29,7 @@ static void prepend_to_path(const char *dir, int len) path_len = len + strlen(old_path) + 1; - path = malloc(path_len + 1); + path = xmalloc(path_len + 1); memcpy(path, dir, len); path[len] = ':'; diff --git a/help.c b/help.c index 9ecdefd..0824c25 100644 --- a/help.c +++ b/help.c @@ -184,7 +184,7 @@ static void show_man_page(const char *git_cmd) page = git_cmd; else { int page_len = strlen(git_cmd) + 4; - char *p = malloc(page_len + 1); + char *p = xmalloc(page_len + 1); strcpy(p, "git-"); strcpy(p + 4, git_cmd); p[page_len] = 0; diff --git a/merge-recursive.c b/merge-recursive.c index 39a1eae..48b2763 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -283,7 +283,7 @@ static int save_files_dirs(const unsigned char *sha1, unsigned int mode, int stage) { int len = strlen(path); - char *newpath = malloc(baselen + len + 1); + char *newpath = xmalloc(baselen + len + 1); memcpy(newpath, base, baselen); memcpy(newpath + baselen, path, len); newpath[baselen + len] = '\0'; @@ -455,7 +455,7 @@ static int remove_path(const char *name) if (ret) return ret; len = strlen(name); - dirs = malloc(len+1); + dirs = xmalloc(len+1); memcpy(dirs, name, len); dirs[len] = '\0'; while ((slash = strrchr(name, '/'))) { @@ -572,7 +572,7 @@ void update_file_flags(const unsigned char *sha, flush_buffer(fd, buf, size); close(fd); } else if (S_ISLNK(mode)) { - char *lnk = malloc(size + 1); + char *lnk = xmalloc(size + 1); memcpy(lnk, buf, size); lnk[size] = '\0'; mkdir_p(path, 0777); diff --git a/mktag.c b/mktag.c index be23e58..3448a5d 100644 --- a/mktag.c +++ b/mktag.c @@ -119,7 +119,7 @@ static int verify_tag(char *buffer, unsigned long size) int main(int argc, char **argv) { unsigned long size = 4096; - char *buffer = malloc(size); + char *buffer = xmalloc(size); unsigned char result_sha1[20]; if (argc != 1) diff --git a/send-pack.c b/send-pack.c index fd79a61..ac4501d 100644 --- a/send-pack.c +++ b/send-pack.c @@ -53,7 +53,7 @@ static void exec_rev_list(struct ref *refs) if (900 < i) die("git-rev-list environment overflow"); if (!is_zero_sha1(ref->new_sha1)) { - char *buf = malloc(100); + char *buf = xmalloc(100); args[i++] = buf; snprintf(buf, 50, "%s", sha1_to_hex(ref->new_sha1)); buf += 50; @@ -75,7 +75,7 @@ static void exec_rev_list(struct ref *refs) if (is_zero_sha1(ref->new_sha1) && !is_zero_sha1(ref->old_sha1) && has_sha1_file(ref->old_sha1)) { - char *buf = malloc(42); + char *buf = xmalloc(42); args[i++] = buf; snprintf(buf, 42, "^%s", sha1_to_hex(ref->old_sha1)); } diff --git a/sha1_file.c b/sha1_file.c index 46272b5..af2bf72 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1756,7 +1756,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size) int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) { unsigned long size = 4096; - char *buf = malloc(size); + char *buf = xmalloc(size); int ret; unsigned char hdr[50]; int hdrlen; -- cgit v0.10.2-6-g49f6 From 95676853b2dc5d610a6f917aac37cbb12c57fcd2 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Fri, 1 Sep 2006 00:31:08 +0200 Subject: Include config.mak.autogen in the doc Makefile ... to install documentation relative to the path set with configure's --prefix option. Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/Makefile b/Documentation/Makefile index 0d9ffb4..ed8b886 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -33,6 +33,8 @@ man7dir=$(mandir)/man7 INSTALL?=install +-include ../config.mak.autogen + # # Please note that there is a minor bug in asciidoc. # The version after 6.0.3 _will_ include the patch found here: -- cgit v0.10.2-6-g49f6 From 1d3fc68ae711b3f46aea02f8d819423cf8780b7d Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" <aneesh.kumar@gmail.com> Date: Fri, 1 Sep 2006 09:13:32 +0530 Subject: gitweb: Fix git_blame Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0984e85..57ffa25 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2251,7 +2251,8 @@ sub git_blame2 { my $fd; my $ftype; - if (!gitweb_check_feature('blame')) { + my ($have_blame) = gitweb_check_feature('blame'); + if (!$have_blame) { die_error('403 Permission denied', "Permission denied"); } die_error('404 Not Found', "File name not defined") if (!$file_name); @@ -2320,7 +2321,8 @@ HTML sub git_blame { my $fd; - if (!gitweb_check_feature('blame')) { + my ($have_blame) = gitweb_check_feature('blame'); + if (!$have_blame) { die_error('403 Permission denied', "Permission denied"); } die_error('404 Not Found', "File name not defined") if (!$file_name); @@ -2494,7 +2496,7 @@ sub git_blob { die_error(undef, "No file name defined"); } } - my $have_blame = gitweb_check_feature('blame'); + my ($have_blame) = gitweb_check_feature('blame'); open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); my $mimetype = blob_mimetype($fd, $file_name); @@ -2570,7 +2572,7 @@ sub git_tree { my $ref = format_ref_marker($refs, $hash_base); git_header_html(); my $base = ""; - my $have_blame = gitweb_check_feature('blame'); + my ($have_blame) = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { git_print_page_nav('tree','', $hash_base); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); -- cgit v0.10.2-6-g49f6 From 839837b953c613c1649b9e36ec2f01da759d87e3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 1 Sep 2006 00:17:47 -0700 Subject: Constness tightening for move/link_temp_to_file() Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/cache.h b/cache.h index 03d9dd0..7257c4c 100644 --- a/cache.h +++ b/cache.h @@ -257,7 +257,7 @@ extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned l extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, size_t bufsize, size_t *bufposn); extern int write_sha1_to_fd(int fd, const unsigned char *sha1); -extern int move_temp_to_file(const char *tmpfile, char *filename); +extern int move_temp_to_file(const char *tmpfile, const char *filename); extern int has_sha1_pack(const unsigned char *sha1); extern int has_sha1_file(const unsigned char *sha1); diff --git a/sha1_file.c b/sha1_file.c index af2bf72..ce90e20 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1348,7 +1348,7 @@ char *write_sha1_file_prepare(void *buf, * * Returns the errno on failure, 0 on success. */ -static int link_temp_to_file(const char *tmpfile, char *filename) +static int link_temp_to_file(const char *tmpfile, const char *filename) { int ret; char *dir; @@ -1381,7 +1381,7 @@ static int link_temp_to_file(const char *tmpfile, char *filename) /* * Move the just written object into its final resting place */ -int move_temp_to_file(const char *tmpfile, char *filename) +int move_temp_to_file(const char *tmpfile, const char *filename) { int ret = link_temp_to_file(tmpfile, filename); -- cgit v0.10.2-6-g49f6 From 8c02eee29e116355474250a5e9270db6034c60de Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Fri, 1 Sep 2006 00:37:15 +0200 Subject: git-rev-list(1): group options; reformat; document more options Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index a446a6b..3c4c2fb 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -27,111 +27,233 @@ SYNOPSIS DESCRIPTION ----------- + Lists commit objects in reverse chronological order starting at the given commit(s), taking ancestry relationship into account. This is useful to produce human-readable log output. -Commits which are stated with a preceding '{caret}' cause listing to stop at -that point. Their parents are implied. "git-rev-list foo bar {caret}baz" thus +Commits which are stated with a preceding '{caret}' cause listing to +stop at that point. Their parents are implied. Thus the following +command: + +----------------------------------------------------------------------- + $ git-rev-list foo bar ^baz +----------------------------------------------------------------------- + means "list all the commits which are included in 'foo' and 'bar', but not in 'baz'". -A special notation <commit1>..<commit2> can be used as a -short-hand for {caret}<commit1> <commit2>. +A special notation "'<commit1>'..'<commit2>'" can be used as a +short-hand for "{caret}'<commit1>' '<commit2>'". For example, either of +the following may be used interchangeably: -Another special notation is <commit1>...<commit2> which is useful for -merges. The resulting set of commits is the symmetric difference +----------------------------------------------------------------------- + $ git-rev-list origin..HEAD + $ git-rev-list HEAD ^origin +----------------------------------------------------------------------- + +Another special notation is "'<commit1>'...'<commit2>'" which is useful +for merges. The resulting set of commits is the symmetric difference between the two operands. The following two commands are equivalent: ------------- -$ git-rev-list A B --not $(git-merge-base --all A B) -$ git-rev-list A...B ------------- +----------------------------------------------------------------------- + $ git-rev-list A B --not $(git-merge-base --all A B) + $ git-rev-list A...B +----------------------------------------------------------------------- + +gitlink:git-rev-list[1] is a very essential git program, since it +provides the ability to build and traverse commit ancestry graphs. For +this reason, it has a lot of different options that enables it to be +used by commands as different as gitlink:git-bisect[1] and +gitlink:git-repack[1]. OPTIONS ------- ---pretty:: - Print the contents of the commit changesets in human-readable form. + +Commit Formatting +~~~~~~~~~~~~~~~~~ + +Using these options, gitlink:git-rev-list[1] will act similar to the +more specialized family of commit log tools: gitlink:git-log[1], +gitlink:git-show[1], and gitlink:git-whatchanged[1] + +--pretty[='<format>']:: + + Pretty print the contents of the commit logs in a given format, + where '<format>' can be one of 'raw', 'medium', 'short', 'full', + and 'oneline'. When left out the format default to 'medium'. + +--relative-date:: + + Show dates relative to the current time, e.g. "2 hours ago". + Only takes effect for dates shown in human-readable format, such + as when using "--pretty". --header:: - Print the contents of the commit in raw-format; each - record is separated with a NUL character. + + Print the contents of the commit in raw-format; each record is + separated with a NUL character. --parents:: + Print the parents of the commit. ---objects:: - Print the object IDs of any object referenced by the listed commits. - 'git-rev-list --objects foo ^bar' thus means "send me all object IDs - which I need to download if I have the commit object 'bar', but - not 'foo'". +Diff Formatting +~~~~~~~~~~~~~~~ ---objects-edge:: - Similar to `--objects`, but also print the IDs of - excluded commits prefixed with a `-` character. This is - used by `git-pack-objects` to build 'thin' pack, which - records objects in deltified form based on objects - contained in these excluded commits to reduce network - traffic. +Below are listed options that control the formatting of diff output. +Some of them are specific to gitlink:git-rev-list[1], however other diff +options may be given. See gitlink:git-diff-files[1] for more options. ---unpacked:: - Only useful with `--objects`; print the object IDs that - are not in packs. +-c:: + + This flag changes the way a merge commit is displayed. It shows + the differences from each of the parents to the merge result + simultaneously instead of showing pairwise diff between a parent + and the result one at a time. Furthermore, it lists only files + which were modified from all parents. + +--cc:: + + This flag implies the '-c' options and further compresses the + patch output by omitting hunks that show differences from only + one parent, or show the same change from all but one parent for + an Octopus merge. + +-r:: + + Show recursive diffs. + +-t:: + + Show the tree objects in the diff output. This implies '-r'. + +Commit Limiting +~~~~~~~~~~~~~~~ + +Besides specifying a range of commits that should be listed using the +special notations explained in the description, additional commit +limiting may be applied. + +-- + +-n 'number', --max-count='number':: ---bisect:: - Limit output to the one commit object which is roughly halfway - between the included and excluded commits. Thus, if 'git-rev-list - --bisect foo {caret}bar {caret}baz' outputs 'midpoint', the output - of 'git-rev-list foo {caret}midpoint' and 'git-rev-list midpoint - {caret}bar {caret}baz' would be of roughly the same length. - Finding the change - which introduces a regression is thus reduced to a binary search: - repeatedly generate and test new 'midpoint's until the commit chain - is of length one. - ---max-count:: Limit the number of commits output. ---max-age=timestamp, --min-age=timestamp:: - Limit the commits output to specified time range. +--since='date', --after='date':: + + Show commits more recent than a specific date. + +--until='date', --before='date':: ---sparse:: - When optional paths are given, the command outputs only - the commits that changes at least one of them, and also - ignores merges that do not touch the given paths. This - flag makes the command output all eligible commits - (still subject to count and age limitation), but apply - merge simplification nevertheless. + Show commits older than a specific date. + +--max-age='timestamp', --min-age='timestamp':: + + Limit the commits output to specified time range. --remove-empty:: + Stop when a given path disappears from the tree. --no-merges:: + Do not print commits with more than one parent. --not:: - Reverses the meaning of the '{caret}' prefix (or lack - thereof) for all following revision specifiers, up to - the next `--not`. + + Reverses the meaning of the '{caret}' prefix (or lack thereof) + for all following revision specifiers, up to the next '--not'. --all:: - Pretend as if all the refs in `$GIT_DIR/refs/` are - listed on the command line as <commit>. ---topo-order:: - By default, the commits are shown in reverse - chronological order. This option makes them appear in - topological order (i.e. descendant commits are shown - before their parents). + Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the + command line as '<commit>'. --merge:: + After a failed merge, show refs that touch files having a conflict and don't exist on all heads to merge. ---relative-date:: - Show dates relative to the current time, e.g. "2 hours ago". - Only takes effect for dates shown in human-readable format, - such as when using "--pretty". +--boundary:: + + Output uninteresting commits at the boundary, which are usually + not shown. + +--dense, --sparse:: + +When optional paths are given, the default behaviour ('--dense') is to +only output commits that changes at least one of them, and also ignore +merges that do not touch the given paths. + +Use the '--sparse' flag to makes the command output all eligible commits +(still subject to count and age limitation), but apply merge +simplification nevertheless. + +--bisect:: + +Limit output to the one commit object which is roughly halfway between +the included and excluded commits. Thus, if + +----------------------------------------------------------------------- + $ git-rev-list --bisect foo ^bar ^baz +----------------------------------------------------------------------- + +outputs 'midpoint', the output of the two commands + +----------------------------------------------------------------------- + $ git-rev-list foo ^midpoint + $ git-rev-list midpoint ^bar ^baz +----------------------------------------------------------------------- + +would be of roughly the same length. Finding the change which +introduces a regression is thus reduced to a binary search: repeatedly +generate and test new 'midpoint's until the commit chain is of length +one. + +-- + +Commit Ordering +~~~~~~~~~~~~~~~ + +By default, the commits are shown in reverse chronological order. + +--topo-order:: + + This option makes them appear in topological order (i.e. + descendant commits are shown before their parents). + +--date-order:: + + This option is similar to '--topo-order' in the sense that no + parent comes before all of its children, but otherwise things + are still ordered in the commit timestamp order. + +Object Traversal +~~~~~~~~~~~~~~~~ + +These options are mostly targeted for packing of git repositories. + +--objects:: + + Print the object IDs of any object referenced by the listed + commits. 'git-rev-list --objects foo ^bar' thus means "send me + all object IDs which I need to download if I have the commit + object 'bar', but not 'foo'". + +--objects-edge:: + + Similar to '--objects', but also print the IDs of excluded + commits prefixed with a "-" character. This is used by + gitlink:git-pack-objects[1] to build "thin" pack, which records + objects in deltified form based on objects contained in these + excluded commits to reduce network traffic. + +--unpacked:: + + Only useful with '--objects'; print the object IDs that are not + in packs. Author ------ @@ -139,9 +261,9 @@ Written by Linus Torvalds <torvalds@osdl.org> Documentation -------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. +Documentation by David Greaves, Junio C Hamano, Jonas Fonseca +and the git-list <git@vger.kernel.org>. GIT --- Part of the gitlink:git[7] suite - -- cgit v0.10.2-6-g49f6 From af04b1271090801b277938836dcb7a39fc059721 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Fri, 1 Sep 2006 10:49:29 +0200 Subject: fmt-merge-msg: fix off-by-one bug Thanks to the recent malloc()->xmalloc() change, and XMALLOC_POISON, this bug was found. Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index ed59e77..432963d 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -142,7 +142,7 @@ static int handle_line(char *line) if (origin[0] == '\'' && origin[len - 1] == '\'') { char *new_origin = xmalloc(len - 1); memcpy(new_origin, origin + 1, len - 2); - new_origin[len - 1] = 0; + new_origin[len - 2] = 0; origin = new_origin; } else origin = strdup(origin); -- cgit v0.10.2-6-g49f6 From 501524e938aee0b9691fe7fb1abf5eb17a23132f Mon Sep 17 00:00:00 2001 From: Sergey Vlasov <vsu@altlinux.ru> Date: Fri, 1 Sep 2006 22:42:59 +0400 Subject: Documentation: Fix howto/revert-branch-rebase.html generation The rule for howto/*.html used "$?", which expands to the list of all newer prerequisites, including asciidoc.conf added by another rule. "$<" should be used instead. Signed-off-by: Sergey Vlasov <vsu@altlinux.ru> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/Makefile b/Documentation/Makefile index ed8b886..c00f5f6 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -107,7 +107,7 @@ WEBDOC_DEST = /pub/software/scm/git/docs $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt rm -f $@+ $@ - sed -e '1,/^$$/d' $? | asciidoc -b xhtml11 - >$@+ + sed -e '1,/^$$/d' $< | asciidoc -b xhtml11 - >$@+ mv $@+ $@ install-webdoc : html -- cgit v0.10.2-6-g49f6 From ad1ed5ee896ba5d7f89bc04c7441b1532efb9853 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 1 Sep 2006 15:17:01 -0700 Subject: consolidate two copies of new style object header parsing code. Also while we are at it, remove redundant typename[] array from unpack_sha1_header. The only reason it is different from the type_names[] array in object.c module is that this code cares about the subset of object types that are valid in a loose object, so prepare a separate array of boolean that tells us which types are valid, and share the name translation with the others. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/sha1_file.c b/sha1_file.c index ce90e20..76f66e6 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -711,17 +711,39 @@ int legacy_loose_object(unsigned char *map) return 0; } -static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) +static unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) { + unsigned shift; unsigned char c; - unsigned int bits; unsigned long size; - static const char *typename[8] = { - NULL, /* OBJ_EXT */ - "commit", "tree", "blob", "tag", - NULL, NULL, NULL + unsigned long used = 0; + + c = buf[used++]; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) + return 0; + if (sizeof(long) * 8 <= shift) + return 0; + c = buf[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + *sizep = size; + return used; +} + +static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) +{ + unsigned long size, used; + static const char valid_loose_object_type[8] = { + 0, /* OBJ_EXT */ + 1, 1, 1, 1, /* "commit", "tree", "blob", "tag" */ + 0, /* "delta" and others are invalid in a loose object */ }; - const char *type; + enum object_type type; /* Get the data stream */ memset(stream, 0, sizeof(*stream)); @@ -735,22 +757,11 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon return inflate(stream, 0); } - c = *map++; - mapsize--; - type = typename[(c >> 4) & 7]; - if (!type) + used = unpack_object_header_gently(map, mapsize, &type, &size); + if (!used || !valid_loose_object_type[type]) return -1; - - bits = 4; - size = c & 0xf; - while ((c & 0x80)) { - if (bits >= 8*sizeof(long)) - return -1; - c = *map++; - size += (c & 0x7f) << bits; - bits += 7; - mapsize--; - } + map += used; + mapsize -= used; /* Set up the stream for the rest.. */ stream->next_in = map; @@ -758,7 +769,8 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon inflateInit(stream); /* And generate the fake traditional header */ - stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", type, size); + stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", + type_names[type], size); return 0; } @@ -916,25 +928,18 @@ static int packed_delta_info(unsigned char *base_sha1, static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset, enum object_type *type, unsigned long *sizep) { - unsigned shift; - unsigned char c; - unsigned long size; + unsigned long used; - if (offset >= p->pack_size) + if (p->pack_size <= offset) die("object offset outside of pack file"); - c = *((unsigned char *)p->pack_base + offset++); - *type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while (c & 0x80) { - if (offset >= p->pack_size) - die("object offset outside of pack file"); - c = *((unsigned char *)p->pack_base + offset++); - size += (c & 0x7f) << shift; - shift += 7; - } - *sizep = size; - return offset; + + used = unpack_object_header_gently((unsigned char *)p->pack_base + + offset, + p->pack_size - offset, type, sizep); + if (!used) + die("object offset outside of pack file"); + + return offset + used; } int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, -- cgit v0.10.2-6-g49f6 From 9befac470b4cfad529032dbcffcb71242ec71f91 Mon Sep 17 00:00:00 2001 From: Shawn Pearce <spearce@spearce.org> Date: Sat, 2 Sep 2006 00:16:31 -0400 Subject: Replace uses of strdup with xstrdup. Like xmalloc and xrealloc xstrdup dies with a useful message if the native strdup() implementation returns NULL rather than a valid pointer. I just tried to use xstrdup in new code and found it to be missing. However I expected it to be present as xmalloc and xrealloc are already commonly used throughout the code. [jc: removed the part that deals with last_XXX, which I am finding more and more dubious these days.] Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/blame.c b/blame.c index 8968046..8cfd5d9 100644 --- a/blame.c +++ b/blame.c @@ -617,7 +617,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) if (new_name) { struct util_info* putil = get_util(p); if (!putil->pathname) - putil->pathname = strdup(new_name); + putil->pathname = xstrdup(new_name); } else { *pp = parent->next; continue; diff --git a/builtin-apply.c b/builtin-apply.c index 1a1deaf..872c800 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2449,7 +2449,7 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) static int git_apply_config(const char *var, const char *value) { if (!strcmp(var, "apply.whitespace")) { - apply_default_whitespace = strdup(value); + apply_default_whitespace = xstrdup(value); return 0; } return git_default_config(var, value); diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 432963d..c407c03 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -111,29 +111,29 @@ static int handle_line(char *line) i = find_in_list(&srcs, src); if (i < 0) { i = srcs.nr; - append_to_list(&srcs, strdup(src), + append_to_list(&srcs, xstrdup(src), xcalloc(1, sizeof(struct src_data))); } src_data = srcs.payload[i]; if (pulling_head) { - origin = strdup(src); + origin = xstrdup(src); src_data->head_status |= 1; } else if (!strncmp(line, "branch ", 7)) { - origin = strdup(line + 7); + origin = xstrdup(line + 7); append_to_list(&src_data->branch, origin, NULL); src_data->head_status |= 2; } else if (!strncmp(line, "tag ", 4)) { origin = line; - append_to_list(&src_data->tag, strdup(origin + 4), NULL); + append_to_list(&src_data->tag, xstrdup(origin + 4), NULL); src_data->head_status |= 2; } else if (!strncmp(line, "remote branch ", 14)) { - origin = strdup(line + 14); + origin = xstrdup(line + 14); append_to_list(&src_data->r_branch, origin, NULL); src_data->head_status |= 2; } else { - origin = strdup(src); - append_to_list(&src_data->generic, strdup(line), NULL); + origin = xstrdup(src); + append_to_list(&src_data->generic, xstrdup(line), NULL); src_data->head_status |= 2; } @@ -145,7 +145,7 @@ static int handle_line(char *line) new_origin[len - 2] = 0; origin = new_origin; } else - origin = strdup(origin); + origin = xstrdup(origin); } else { char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); sprintf(new_origin, "%s of %s", origin, src); @@ -203,7 +203,7 @@ static void shortlog(const char *name, unsigned char *sha1, bol = strstr(commit->buffer, "\n\n"); if (!bol) { - append_to_list(&subjects, strdup(sha1_to_hex( + append_to_list(&subjects, xstrdup(sha1_to_hex( commit->object.sha1)), NULL); continue; @@ -218,7 +218,7 @@ static void shortlog(const char *name, unsigned char *sha1, memcpy(oneline, bol, len); oneline[len] = 0; } else - oneline = strdup(bol); + oneline = xstrdup(bol); append_to_list(&subjects, oneline, NULL); } @@ -277,7 +277,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) usage(fmt_merge_msg_usage); /* get current branch */ - head = strdup(git_path("HEAD")); + head = xstrdup(git_path("HEAD")); current_branch = resolve_ref(head, head_sha1, 1); current_branch += strlen(head) - 4; free((char *)head); diff --git a/builtin-grep.c b/builtin-grep.c index 8213ce2..6430f6d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -1048,7 +1048,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* ignore empty line like grep does */ if (!buf[0]) continue; - add_pattern(&opt, strdup(buf), argv[1], ++lno, + add_pattern(&opt, xstrdup(buf), argv[1], ++lno, GREP_PATTERN); } fclose(patterns); diff --git a/builtin-name-rev.c b/builtin-name-rev.c index d44e782..52886b6 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -100,7 +100,7 @@ static int name_ref(const char *path, const unsigned char *sha1) else if (!strncmp(path, "refs/", 5)) path = path + 5; - name_rev(commit, strdup(path), 0, 0, deref); + name_rev(commit, xstrdup(path), 0, 0, deref); } return 0; } diff --git a/builtin-prune.c b/builtin-prune.c index fc885ce..6228c79 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -106,7 +106,7 @@ static void process_tree(struct tree *tree, obj->flags |= SEEN; if (parse_tree(tree) < 0) die("bad tree object %s", sha1_to_hex(obj->sha1)); - name = strdup(name); + name = xstrdup(name); add_object(obj, p, path, name); me.up = path; me.elem = name; diff --git a/builtin-push.c b/builtin-push.c index ada8338..c43f256 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -33,7 +33,7 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1) ref += 5; if (!strncmp(ref, "tags/", 5)) - add_refspec(strdup(ref)); + add_refspec(xstrdup(ref)); return 0; } @@ -100,12 +100,12 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI]) if (!is_refspec) { if (n < MAX_URI) - uri[n++] = strdup(s); + uri[n++] = xstrdup(s); else error("more than %d URL's specified, ignoring the rest", MAX_URI); } else if (is_refspec && !has_explicit_refspec) - add_refspec(strdup(s)); + add_refspec(xstrdup(s)); } fclose(f); if (!n) @@ -125,13 +125,13 @@ static int get_remote_config(const char* key, const char* value) !strncmp(key + 7, config_repo, config_repo_len)) { if (!strcmp(key + 7 + config_repo_len, ".url")) { if (config_current_uri < MAX_URI) - config_uri[config_current_uri++] = strdup(value); + config_uri[config_current_uri++] = xstrdup(value); else error("more than %d URL's specified, ignoring the rest", MAX_URI); } else if (config_get_refspecs && !strcmp(key + 7 + config_repo_len, ".push")) - add_refspec(strdup(value)); + add_refspec(xstrdup(value)); } return 0; } diff --git a/builtin-repo-config.c b/builtin-repo-config.c index a1756c8..9cf12d3 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -72,12 +72,12 @@ static int get_value(const char* key_, const char* regex_) const char *home = getenv("HOME"); local = getenv("GIT_CONFIG_LOCAL"); if (!local) - local = repo_config = strdup(git_path("config")); + local = repo_config = xstrdup(git_path("config")); if (home) - global = strdup(mkpath("%s/.gitconfig", home)); + global = xstrdup(mkpath("%s/.gitconfig", home)); } - key = strdup(key_); + key = xstrdup(key_); for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl) *tl = tolower(*tl); for (tl=key; *tl && *tl != '.'; ++tl) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 402af8e..8437454 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -109,7 +109,7 @@ static void process_blob(struct blob *blob, if (obj->flags & (UNINTERESTING | SEEN)) return; obj->flags |= SEEN; - name = strdup(name); + name = xstrdup(name); add_object(obj, p, path, name); } @@ -130,7 +130,7 @@ static void process_tree(struct tree *tree, if (parse_tree(tree) < 0) die("bad tree object %s", sha1_to_hex(obj->sha1)); obj->flags |= SEEN; - name = strdup(name); + name = xstrdup(name); add_object(obj, p, path, name); me.up = path; me.elem = name; diff --git a/builtin-rm.c b/builtin-rm.c index 593d867..33d04bd 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -32,7 +32,7 @@ static int remove_file(const char *name) ret = unlink(name); if (!ret && (slash = strrchr(name, '/'))) { - char *n = strdup(name); + char *n = xstrdup(name); do { n[slash - name] = 0; name = n; diff --git a/builtin-show-branch.c b/builtin-show-branch.c index d7de18e..578c9fa 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -163,7 +163,7 @@ static void name_commits(struct commit_list *list, en += sprintf(en, "^"); else en += sprintf(en, "^%d", nth); - name_commit(p, strdup(newname), 0); + name_commit(p, xstrdup(newname), 0); i++; name_first_parent_chain(p); } @@ -364,7 +364,7 @@ static int append_ref(const char *refname, const unsigned char *sha1) refname, MAX_REVS); return 0; } - ref_name[ref_name_cnt++] = strdup(refname); + ref_name[ref_name_cnt++] = xstrdup(refname); ref_name[ref_name_cnt] = NULL; return 0; } @@ -521,7 +521,7 @@ static int git_show_branch_config(const char *var, const char *value) default_alloc = default_alloc * 3 / 2 + 20; default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc); } - default_arg[default_num++] = strdup(value); + default_arg[default_num++] = xstrdup(value); default_arg[default_num] = NULL; return 0; } diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index b4ec6f2..1d3a5e2 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -7,7 +7,7 @@ static const char git_symbolic_ref_usage[] = static void check_symref(const char *HEAD) { unsigned char sha1[20]; - const char *git_HEAD = strdup(git_path("%s", HEAD)); + const char *git_HEAD = xstrdup(git_path("%s", HEAD)); const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0); if (git_refs_heads_master) { /* we want to strip the .git/ part */ @@ -26,7 +26,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) check_symref(argv[1]); break; case 3: - create_symref(strdup(git_path("%s", argv[1])), argv[2]); + create_symref(xstrdup(git_path("%s", argv[1])), argv[2]); break; default: usage(git_symbolic_ref_usage); diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 61a4135..fa666f7 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -351,7 +351,7 @@ static int remote_tar(int argc, const char **argv) usage(tar_tree_usage); /* --remote=<repo> */ - url = strdup(argv[1]+9); + url = xstrdup(argv[1]+9); pid = git_connect(fd, url, exec); if (pid < 0) return 1; diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c index 7b401bb..06a945a 100644 --- a/builtin-upload-tar.c +++ b/builtin-upload-tar.c @@ -53,7 +53,7 @@ int cmd_upload_tar(int argc, const char **argv, const char *prefix) return nak("expected (optional) base"); if (buf[len-1] == '\n') buf[--len] = 0; - base = strdup(buf + 5); + base = xstrdup(buf + 5); len = packet_read_line(0, buf, sizeof(buf)); } if (len) diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c index a5b834d..1c1f683 100644 --- a/builtin-zip-tree.c +++ b/builtin-zip-tree.c @@ -311,11 +311,11 @@ int cmd_zip_tree(int argc, const char **argv, const char *prefix) switch (argc) { case 3: - base = strdup(argv[2]); + base = xstrdup(argv[2]); baselen = strlen(base); break; case 2: - base = strdup(""); + base = xstrdup(""); baselen = 0; break; default: diff --git a/config.c b/config.c index c0897cc..e8f0caf 100644 --- a/config.c +++ b/config.c @@ -350,11 +350,11 @@ int git_config(config_fn_t fn) home = getenv("HOME"); filename = getenv("GIT_CONFIG_LOCAL"); if (!filename) - filename = repo_config = strdup(git_path("config")); + filename = repo_config = xstrdup(git_path("config")); } if (home) { - char *user_config = strdup(mkpath("%s/.gitconfig", home)); + char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); if (!access(user_config, R_OK)) ret = git_config_from_file(fn, user_config); free(user_config); @@ -545,8 +545,8 @@ int git_config_set_multivar(const char* key, const char* value, if (!config_filename) config_filename = git_path("config"); } - config_filename = strdup(config_filename); - lock_file = strdup(mkpath("%s.lock", config_filename)); + config_filename = xstrdup(config_filename); + lock_file = xstrdup(mkpath("%s.lock", config_filename)); /* * Since "key" actually contains the section name and the real diff --git a/connect.c b/connect.c index e501ccc..06ef387 100644 --- a/connect.c +++ b/connect.c @@ -69,7 +69,7 @@ struct ref **get_remote_heads(int in, struct ref **list, if (len != name_len + 41) { if (server_capabilities) free(server_capabilities); - server_capabilities = strdup(name + name_len + 1); + server_capabilities = xstrdup(name + name_len + 1); } if (!check_ref(name, name_len, flags)) @@ -661,7 +661,7 @@ int git_connect(int fd[2], char *url, const char *prog) if (path[1] == '~') path++; else { - path = strdup(ptr); + path = xstrdup(ptr); free_path = 1; } @@ -672,7 +672,7 @@ int git_connect(int fd[2], char *url, const char *prog) /* These underlying connection commands die() if they * cannot connect. */ - char *target_host = strdup(host); + char *target_host = xstrdup(host); if (git_use_proxy(host)) git_proxy_connect(fd, host); else diff --git a/diff.c b/diff.c index 70699fd..9dcbda3 100644 --- a/diff.c +++ b/diff.c @@ -216,7 +216,7 @@ static char *quote_one(const char *str) return NULL; needlen = quote_c_style(str, NULL, NULL, 0); if (!needlen) - return strdup(str); + return xstrdup(str); xp = xmalloc(needlen + 1); quote_c_style(str, xp, NULL, 0); return xp; @@ -658,7 +658,7 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, x->is_renamed = 1; } else - x->name = strdup(name_a); + x->name = xstrdup(name_a); return x; } diff --git a/environment.c b/environment.c index 5fae9ac..84d870c 100644 --- a/environment.c +++ b/environment.c @@ -47,7 +47,7 @@ static void setup_git_env(void) } git_graft_file = getenv(GRAFT_ENVIRONMENT); if (!git_graft_file) - git_graft_file = strdup(git_path("info/grafts")); + git_graft_file = xstrdup(git_path("info/grafts")); } const char *get_git_dir(void) diff --git a/fetch.c b/fetch.c index 7d3812c..34df8d3 100644 --- a/fetch.c +++ b/fetch.c @@ -234,8 +234,8 @@ int pull_targets_stdin(char ***target, const char ***write_ref) *target = xrealloc(*target, targets_alloc * sizeof(**target)); *write_ref = xrealloc(*write_ref, targets_alloc * sizeof(**write_ref)); } - (*target)[targets] = strdup(tg_one); - (*write_ref)[targets] = rf_one ? strdup(rf_one) : NULL; + (*target)[targets] = xstrdup(tg_one); + (*write_ref)[targets] = rf_one ? xstrdup(rf_one) : NULL; targets++; } return targets; diff --git a/fsck-objects.c b/fsck-objects.c index 24286de..4d994f3 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -458,7 +458,7 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { unsigned char sha1[20]; - const char *git_HEAD = strdup(git_path("HEAD")); + const char *git_HEAD = xstrdup(git_path("HEAD")); const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1); int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */ diff --git a/git-compat-util.h b/git-compat-util.h index 91f2b0d..552b8ec 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -84,6 +84,14 @@ extern char *gitstrcasestr(const char *haystack, const char *needle); extern size_t gitstrlcpy(char *, const char *, size_t); #endif +static inline char* xstrdup(const char *str) +{ + char *ret = strdup(str); + if (!ret) + die("Out of memory, strdup failed"); + return ret; +} + static inline void *xmalloc(size_t size) { void *ret = malloc(size); diff --git a/git.c b/git.c index 05871ad..9bb21ed 100644 --- a/git.c +++ b/git.c @@ -97,7 +97,7 @@ static char *alias_string; static int git_alias_config(const char *var, const char *value) { if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) { - alias_string = strdup(value); + alias_string = xstrdup(value); } return 0; } diff --git a/http-fetch.c b/http-fetch.c index 6806f36..fac1760 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -787,7 +787,7 @@ static int remote_ls(struct alt_base *repo, const char *path, int flags, ls.flags = flags; ls.repo = repo; - ls.path = strdup(path); + ls.path = xstrdup(path); ls.dentry_name = NULL; ls.dentry_flags = 0; ls.userData = userData; diff --git a/http-push.c b/http-push.c index 7814666..670ff00 100644 --- a/http-push.c +++ b/http-push.c @@ -1539,7 +1539,7 @@ static void remote_ls(const char *path, int flags, struct remote_ls_ctx ls; ls.flags = flags; - ls.path = strdup(path); + ls.path = xstrdup(path); ls.dentry_name = NULL; ls.dentry_flags = 0; ls.userData = userData; @@ -1738,7 +1738,7 @@ static struct object_list **process_tree(struct tree *tree, die("bad tree object %s", sha1_to_hex(obj->sha1)); obj->flags |= SEEN; - name = strdup(name); + name = xstrdup(name); p = add_one_object(obj, p); me.up = path; me.elem = name; @@ -2467,7 +2467,7 @@ int main(int argc, char **argv) /* Set up revision info for this refspec */ commit_argc = 3; - new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1)); + new_sha1_hex = xstrdup(sha1_to_hex(ref->new_sha1)); old_sha1_hex = NULL; commit_argv[1] = "--objects"; commit_argv[2] = new_sha1_hex; diff --git a/imap-send.c b/imap-send.c index 65c71c6..8ed0f0a 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1032,7 +1032,7 @@ imap_open_store( imap_server_conf_t *srvc ) * getpass() returns a pointer to a static buffer. make a copy * for long term storage. */ - srvc->pass = strdup( arg ); + srvc->pass = xstrdup( arg ); } if (CAP(NOLOGIN)) { fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); @@ -1288,7 +1288,7 @@ git_imap_config(const char *key, const char *val) key += sizeof imap_key - 1; if (!strcmp( "folder", key )) { - imap_folder = strdup( val ); + imap_folder = xstrdup( val ); } else if (!strcmp( "host", key )) { { if (!strncmp( "imap:", val, 5 )) @@ -1298,16 +1298,16 @@ git_imap_config(const char *key, const char *val) } if (!strncmp( "//", val, 2 )) val += 2; - server.host = strdup( val ); + server.host = xstrdup( val ); } else if (!strcmp( "user", key )) - server.user = strdup( val ); + server.user = xstrdup( val ); else if (!strcmp( "pass", key )) - server.pass = strdup( val ); + server.pass = xstrdup( val ); else if (!strcmp( "port", key )) server.port = git_config_int( key, val ); else if (!strcmp( "tunnel", key )) - server.tunnel = strdup( val ); + server.tunnel = xstrdup( val ); return 0; } diff --git a/merge-file.c b/merge-file.c index f32c653..fc9b148 100644 --- a/merge-file.c +++ b/merge-file.c @@ -21,7 +21,7 @@ static const char *write_temp_file(mmfile_t *f) fd = mkstemp(filename); if (fd < 0) return NULL; - filename = strdup(filename); + filename = xstrdup(filename); if (f->size != xwrite(fd, f->ptr, f->size)) { rm_temp_file(filename); return NULL; diff --git a/merge-recursive.c b/merge-recursive.c index 48b2763..611cd95 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -513,8 +513,8 @@ static char *unique_path(const char *path, const char *branch) static int mkdir_p(const char *path, unsigned long mode) { - /* path points to cache entries, so strdup before messing with it */ - char *buf = strdup(path); + /* path points to cache entries, so xstrdup before messing with it */ + char *buf = xstrdup(path); int result = safe_create_leading_directories(buf); free(buf); return result; @@ -668,9 +668,9 @@ static struct merge_file_info merge_file(struct diff_filespec *o, git_unpack_file(a->sha1, src1); git_unpack_file(b->sha1, src2); - argv[2] = la = strdup(mkpath("%s/%s", branch1, a->path)); - argv[6] = lb = strdup(mkpath("%s/%s", branch2, b->path)); - argv[4] = lo = strdup(mkpath("orig/%s", o->path)); + argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); + argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); + argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); argv[7] = src1; argv[8] = orig; argv[9] = src2, @@ -1314,9 +1314,9 @@ int main(int argc, char *argv[]) original_index_file = getenv("GIT_INDEX_FILE"); if (!original_index_file) - original_index_file = strdup(git_path("index")); + original_index_file = xstrdup(git_path("index")); - temporary_index_file = strdup(git_path("mrg-rcrsv-tmp-idx")); + temporary_index_file = xstrdup(git_path("mrg-rcrsv-tmp-idx")); if (argc < 4) die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]); diff --git a/merge-tree.c b/merge-tree.c index c2e9a86..60df758 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -177,7 +177,7 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en if (!branch1) return; - path = strdup(mkpath("%s%s", base, result->path)); + path = xstrdup(mkpath("%s%s", base, result->path)); orig = create_entry(2, branch1->mode, branch1->sha1, path); final = create_entry(0, result->mode, result->sha1, path); @@ -233,7 +233,7 @@ static struct merge_list *link_entry(unsigned stage, const char *base, struct na if (entry) path = entry->path; else - path = strdup(mkpath("%s%s", base, n->path)); + path = xstrdup(mkpath("%s%s", base, n->path)); link = create_entry(stage, n->mode, n->sha1, path); link->link = entry; return link; diff --git a/path-list.c b/path-list.c index b1ee72d..0c332dc 100644 --- a/path-list.c +++ b/path-list.c @@ -45,7 +45,7 @@ static int add_entry(struct path_list *list, const char *path) (list->nr - index) * sizeof(struct path_list_item)); list->items[index].path = list->strdup_paths ? - strdup(path) : (char *)path; + xstrdup(path) : (char *)path; list->items[index].util = NULL; list->nr++; diff --git a/refs.c b/refs.c index aab14fc..5e65314 100644 --- a/refs.c +++ b/refs.c @@ -313,8 +313,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *path, } lock->lk = xcalloc(1, sizeof(struct lock_file)); - lock->ref_file = strdup(path); - lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen)); + lock->ref_file = xstrdup(path); + lock->log_file = xstrdup(git_path("logs/%s", lock->ref_file + plen)); lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT; if (safe_create_leading_directories(lock->ref_file)) diff --git a/server-info.c b/server-info.c index 7df628f..2fb8f57 100644 --- a/server-info.c +++ b/server-info.c @@ -23,7 +23,7 @@ static int add_info_ref(const char *path, const unsigned char *sha1) static int update_info_refs(int force) { - char *path0 = strdup(git_path("info/refs")); + char *path0 = xstrdup(git_path("info/refs")); int len = strlen(path0); char *path1 = xmalloc(len + 2); diff --git a/sha1_file.c b/sha1_file.c index 76f66e6..4ef9805 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -115,7 +115,7 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) /* * NOTE! This returns a statically allocated buffer, so you have to be - * careful about using it. Do a "strdup()" if you need to save the + * careful about using it. Do a "xstrdup()" if you need to save the * filename. * * Also note that this returns the location for creating. Reading diff --git a/sha1_name.c b/sha1_name.c index 3f6b77c..1fbc443 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -279,7 +279,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) pathname = resolve_ref(git_path(*p, len, str), this_result, 1); if (pathname) { if (!refs_found++) - real_path = strdup(pathname); + real_path = xstrdup(pathname); if (!warn_ambiguous_refs) break; } -- cgit v0.10.2-6-g49f6 From 6ce4e61f1be690681f6494eb5ca26540c2316f81 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Sat, 2 Sep 2006 18:23:48 +0200 Subject: Trace into a file or an open fd and refactor tracing code. If GIT_TRACE is set to an absolute path (starting with a '/' character), we interpret this as a file path and we trace into it. Also if GIT_TRACE is set to an integer value greater than 1 and lower than 10, we interpret this as an open fd value and we trace into it. Note that this behavior is not compatible with the previous one. We also trace whole messages using one write(2) call to make sure messages from processes do net get mixed up in the middle. This patch makes it possible to get trace information when running "make test". Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 05bd77f..a639cdf 100644 --- a/Makefile +++ b/Makefile @@ -249,7 +249,7 @@ LIB_OBJS = \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - write_or_die.o \ + write_or_die.o trace.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) BUILTIN_OBJS = \ diff --git a/cache.h b/cache.h index 7257c4c..195908f 100644 --- a/cache.h +++ b/cache.h @@ -398,6 +398,7 @@ extern char git_commit_encoding[MAX_ENCODING_LENGTH]; extern int copy_fd(int ifd, int ofd); extern void write_or_die(int fd, const void *buf, size_t count); +extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); /* Finish off pack transfer receiving end */ extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int); @@ -423,4 +424,9 @@ extern struct commit *alloc_commit_node(void); extern struct tag *alloc_tag_node(void); extern void alloc_report(void); +/* trace.c */ +extern int nfvasprintf(char **str, const char *fmt, va_list va); +extern void trace_printf(const char *format, ...); +extern void trace_argv_printf(const char **argv, int count, const char *format, ...); + #endif /* CACHE_H */ diff --git a/exec_cmd.c b/exec_cmd.c index e30936d..5d6a124 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -97,26 +97,12 @@ int execv_git_cmd(const char **argv) tmp = argv[0]; argv[0] = git_command; - if (getenv("GIT_TRACE")) { - const char **p = argv; - fputs("trace: exec:", stderr); - while (*p) { - fputc(' ', stderr); - sq_quote_print(stderr, *p); - ++p; - } - putc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(argv, -1, "trace: exec:"); /* execve() can only ever return if it fails */ execve(git_command, (char **)argv, environ); - if (getenv("GIT_TRACE")) { - fprintf(stderr, "trace: exec failed: %s\n", - strerror(errno)); - fflush(stderr); - } + trace_printf("trace: exec failed: %s\n", strerror(errno)); argv[0] = tmp; } diff --git a/git.c b/git.c index 9bb21ed..1d00111 100644 --- a/git.c +++ b/git.c @@ -179,17 +179,9 @@ static int handle_alias(int *argcp, const char ***argv) if (!strcmp(alias_command, new_argv[0])) die("recursive alias: %s", alias_command); - if (getenv("GIT_TRACE")) { - int i; - fprintf(stderr, "trace: alias expansion: %s =>", - alias_command); - for (i = 0; i < count; ++i) { - fputc(' ', stderr); - sq_quote_print(stderr, new_argv[i]); - } - fputc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(new_argv, count, + "trace: alias expansion: %s =>", + alias_command); new_argv = xrealloc(new_argv, sizeof(char*) * (count + *argcp + 1)); @@ -292,16 +284,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); - if (getenv("GIT_TRACE")) { - int j; - fprintf(stderr, "trace: built-in: git"); - for (j = 0; j < argc; ++j) { - fputc(' ', stderr); - sq_quote_print(stderr, argv[j]); - } - putc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(argv, argc, "trace: built-in: git"); exit(p->fn(argc, argv, prefix)); } diff --git a/imap-send.c b/imap-send.c index 8ed0f0a..362e474 100644 --- a/imap-send.c +++ b/imap-send.c @@ -110,7 +110,6 @@ static char *next_arg( char ** ); static void free_generic_messages( message_t * ); -static int nfvasprintf( char **str, const char *fmt, va_list va ); static int nfsnprintf( char *buf, int blen, const char *fmt, ... ); @@ -372,21 +371,6 @@ free_generic_messages( message_t *msgs ) } static int -git_vasprintf( char **strp, const char *fmt, va_list ap ) -{ - int len; - char tmp[1024]; - - if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = xmalloc( len + 1 ))) - return -1; - if (len >= (int)sizeof(tmp)) - vsprintf( *strp, fmt, ap ); - else - memcpy( *strp, tmp, len + 1 ); - return len; -} - -static int nfsnprintf( char *buf, int blen, const char *fmt, ... ) { int ret; @@ -399,15 +383,6 @@ nfsnprintf( char *buf, int blen, const char *fmt, ... ) return ret; } -static int -nfvasprintf( char **str, const char *fmt, va_list va ) -{ - int ret = git_vasprintf( str, fmt, va ); - if (ret < 0) - die( "Fatal: Out of memory\n"); - return ret; -} - static struct { unsigned char i, j, s[256]; } rs; diff --git a/quote.c b/quote.c index e220dcc..a38786c 100644 --- a/quote.c +++ b/quote.c @@ -74,6 +74,38 @@ char *sq_quote(const char *src) return buf; } +char *sq_quote_argv(const char** argv, int count) +{ + char *buf, *to; + int i; + size_t len = 0; + + /* Count argv if needed. */ + if (count < 0) { + for (count = 0; argv[count]; count++) + ; /* just counting */ + } + + /* Special case: no argv. */ + if (!count) + return xcalloc(1,1); + + /* Get destination buffer length. */ + for (i = 0; i < count; i++) + len += sq_quote_buf(NULL, 0, argv[i]) + 1; + + /* Alloc destination buffer. */ + to = buf = xmalloc(len + 1); + + /* Copy into destination buffer. */ + for (i = 0; i < count; ++i) { + *to++ = ' '; + to += sq_quote_buf(to, len, argv[i]); + } + + return buf; +} + char *sq_dequote(char *arg) { char *dst = arg; diff --git a/quote.h b/quote.h index fc5481e..a6c4611 100644 --- a/quote.h +++ b/quote.h @@ -31,6 +31,7 @@ extern char *sq_quote(const char *src); extern void sq_quote_print(FILE *stream, const char *src); extern size_t sq_quote_buf(char *dst, size_t n, const char *src); +extern char *sq_quote_argv(const char** argv, int count); /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have diff --git a/t/test-lib.sh b/t/test-lib.sh index 470a909..e262933 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -28,13 +28,21 @@ unset GIT_DIR unset GIT_EXTERNAL_DIFF unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY -unset GIT_TRACE unset SHA1_FILE_DIRECTORIES unset SHA1_FILE_DIRECTORY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR VISUAL +case $(echo $GIT_TRACE |tr [A-Z] [a-z]) in + 1|2|true) + echo "* warning: Some tests will not work if GIT_TRACE" \ + "is set as to trace on STDERR ! *" + echo "* warning: Please set GIT_TRACE to something" \ + "other than 1, 2 or true ! *" + ;; +esac + # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... diff --git a/trace.c b/trace.c new file mode 100644 index 0000000..ce01c34 --- /dev/null +++ b/trace.c @@ -0,0 +1,149 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org> + * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net> + * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu> + * Copyright (C) 2006 Mike McCormack + * Copyright (C) 2006 Christian Couder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "cache.h" +#include "quote.h" + +/* Stolen from "imap-send.c". */ +static int git_vasprintf(char **strp, const char *fmt, va_list ap) +{ + int len; + char tmp[1024]; + + if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 || + !(*strp = xmalloc(len + 1))) + return -1; + if (len >= (int)sizeof(tmp)) + vsprintf(*strp, fmt, ap); + else + memcpy(*strp, tmp, len + 1); + return len; +} + +/* Stolen from "imap-send.c". */ +int nfvasprintf(char **str, const char *fmt, va_list va) +{ + int ret = git_vasprintf(str, fmt, va); + if (ret < 0) + die("Fatal: Out of memory\n"); + return ret; +} + +/* Get a trace file descriptor from GIT_TRACE env variable. */ +static int get_trace_fd(int *need_close) +{ + char *trace = getenv("GIT_TRACE"); + + if (!trace || !strcmp(trace, "0") || !strcasecmp(trace," false")) + return 0; + if (!strcmp(trace, "1") || !strcasecmp(trace, "true")) + return STDERR_FILENO; + if (strlen(trace) == 1 && isdigit(*trace)) + return atoi(trace); + if (*trace == '/') { + int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666); + if (fd == -1) { + fprintf(stderr, + "Could not open '%s' for tracing: %s\n" + "Defaulting to tracing on stderr...\n", + trace, strerror(errno)); + return STDERR_FILENO; + } + *need_close = 1; + return fd; + } + + fprintf(stderr, "What does '%s' for GIT_TRACE means ?\n", trace); + fprintf(stderr, "If you want to trace into a file, " + "then please set GIT_TRACE to an absolute pathname " + "(starting with /).\n"); + fprintf(stderr, "Defaulting to tracing on stderr...\n"); + + return STDERR_FILENO; +} + +static const char err_msg[] = "Could not trace into fd given by " + "GIT_TRACE environment variable"; + +void trace_printf(const char *format, ...) +{ + char *trace_str; + va_list rest; + int need_close = 0; + int fd = get_trace_fd(&need_close); + + if (!fd) + return; + + va_start(rest, format); + nfvasprintf(&trace_str, format, rest); + va_end(rest); + + write_or_whine(fd, trace_str, strlen(trace_str), err_msg); + + free(trace_str); + + if (need_close) + close(fd); +} + +void trace_argv_printf(const char **argv, int count, const char *format, ...) +{ + char *argv_str, *format_str, *trace_str; + size_t argv_len, format_len, trace_len; + va_list rest; + int need_close = 0; + int fd = get_trace_fd(&need_close); + + if (!fd) + return; + + /* Get the argv string. */ + argv_str = sq_quote_argv(argv, count); + argv_len = strlen(argv_str); + + /* Get the formated string. */ + va_start(rest, format); + nfvasprintf(&format_str, format, rest); + va_end(rest); + + /* Allocate buffer for trace string. */ + format_len = strlen(format_str); + trace_len = argv_len + format_len + 1; /* + 1 for \n */ + trace_str = xmalloc(trace_len + 1); + + /* Copy everything into the trace string. */ + strncpy(trace_str, format_str, format_len); + strncpy(trace_str + format_len, argv_str, argv_len); + strcpy(trace_str + trace_len - 1, "\n"); + + write_or_whine(fd, trace_str, trace_len, err_msg); + + free(argv_str); + free(format_str); + free(trace_str); + + if (need_close) + close(fd); +} diff --git a/write_or_die.c b/write_or_die.c index ab4cb8a..bfe4eeb 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -18,3 +18,28 @@ void write_or_die(int fd, const void *buf, size_t count) p += written; } } + +int write_or_whine(int fd, const void *buf, size_t count, const char *msg) +{ + const char *p = buf; + ssize_t written; + + while (count > 0) { + written = xwrite(fd, p, count); + if (written == 0) { + fprintf(stderr, "%s: disk full?\n", msg); + return 0; + } + else if (written < 0) { + if (errno == EPIPE) + exit(0); + fprintf(stderr, "%s: write error (%s)\n", + msg, strerror(errno)); + return 0; + } + count -= written; + p += written; + } + + return 1; +} -- cgit v0.10.2-6-g49f6 From df6d61017a17efe67e4709028fea8e820b5efc5e Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 1 Sep 2006 15:05:12 -0700 Subject: pack-objects: re-validate data we copy from elsewhere. When reusing data from an existing pack and from a new style loose objects, we used to just copy it staight into the resulting pack. Instead make sure they are not corrupt, but do so only when we are not streaming to stdout, in which case the receiving end will do the validation either by unpacking the stream or by constructing the .idx file. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 46f524d..11cc3c8 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -65,6 +65,7 @@ static unsigned char pack_file_sha1[20]; static int progress = 1; static volatile sig_atomic_t progress_update; static int window = 10; +static int pack_to_stdout; /* * The object names in objects array are hashed with this hashtable, @@ -242,6 +243,58 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha return n; } +static int revalidate_one(struct object_entry *entry, + void *data, char *type, unsigned long size) +{ + int err; + if (!data) + return -1; + if (size != entry->size) + return -1; + err = check_sha1_signature(entry->sha1, data, size, + type_names[entry->type]); + free(data); + return err; +} + +/* + * we are going to reuse the existing pack entry data. make + * sure it is not corrupt. + */ +static int revalidate_pack_entry(struct object_entry *entry) +{ + void *data; + char type[20]; + unsigned long size; + struct pack_entry e; + + if (pack_to_stdout) + return 0; + + e.p = entry->in_pack; + e.offset = entry->in_pack_offset; + + /* the caller has already called use_packed_git() for us */ + data = unpack_entry_gently(&e, type, &size); + return revalidate_one(entry, data, type, size); +} + +static int revalidate_loose_object(struct object_entry *entry, + unsigned char *map, + unsigned long mapsize) +{ + /* we already know this is a loose object with new type header. */ + void *data; + char type[20]; + unsigned long size; + + if (pack_to_stdout) + return 0; + + data = unpack_sha1_file(map, mapsize, type, &size); + return revalidate_one(entry, data, type, size); +} + static unsigned long write_object(struct sha1file *f, struct object_entry *entry) { @@ -276,6 +329,9 @@ static unsigned long write_object(struct sha1file *f, map = map_sha1_file(entry->sha1, &mapsize); if (map && !legacy_loose_object(map)) { /* We can copy straight into the pack file */ + if (revalidate_loose_object(entry, map, mapsize)) + die("corrupt loose object %s", + sha1_to_hex(entry->sha1)); sha1write(f, map, mapsize); munmap(map, mapsize); written++; @@ -286,7 +342,7 @@ static unsigned long write_object(struct sha1file *f, munmap(map, mapsize); } - if (! to_reuse) { + if (!to_reuse) { buf = read_sha1_file(entry->sha1, type, &size); if (!buf) die("unable to read %s", sha1_to_hex(entry->sha1)); @@ -319,6 +375,9 @@ static unsigned long write_object(struct sha1file *f, datalen = find_packed_object_size(p, entry->in_pack_offset); buf = (char *) p->pack_base + entry->in_pack_offset; + + if (revalidate_pack_entry(entry)) + die("corrupt delta in pack %s", sha1_to_hex(entry->sha1)); sha1write(f, buf, datalen); unuse_packed_git(p); hdrlen = 0; /* not really */ @@ -1163,7 +1222,7 @@ static void prepare_pack(int window, int depth) find_deltas(sorted_by_type, window+1, depth); } -static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout) +static int reuse_cached_pack(unsigned char *sha1) { static const char cache[] = "pack-cache/pack-%s.%s"; char *cached_pack, *cached_idx; @@ -1247,7 +1306,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) { SHA_CTX ctx; char line[40 + 1 + PATH_MAX + 2]; - int depth = 10, pack_to_stdout = 0; + int depth = 10; struct object_entry **list; int num_preferred_base = 0; int i; @@ -1367,7 +1426,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (progress && (nr_objects != nr_result)) fprintf(stderr, "Result has %d objects.\n", nr_result); - if (reuse_cached_pack(object_list_sha1, pack_to_stdout)) + if (reuse_cached_pack(object_list_sha1)) ; else { if (nr_result) -- cgit v0.10.2-6-g49f6 From 3c2f5886c78454276a045c1312eca652c11d98d6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 2 Sep 2006 22:57:42 -0700 Subject: Revert "Convert git-annotate to use Git.pm" This reverts commit 7fb39d5f58efd05e982fe148630edc97ded753b6. diff --git a/git-annotate.perl b/git-annotate.perl index 742a51c..215ed26 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -11,7 +11,6 @@ use strict; use Getopt::Long; use POSIX qw(strftime gmtime); use File::Basename qw(basename dirname); -use Git; sub usage() { print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] @@ -30,7 +29,7 @@ sub usage() { exit(1); } -our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file, $repo) = (0, 0, 1); +our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1); my $rc = GetOptions( "long|l" => \$longrev, "time|t" => \$rawtime, @@ -53,8 +52,6 @@ my @stack = ( }, ); -$repo = Git->repository(); - our @filelines = (); if (defined $starting_rev) { @@ -105,11 +102,15 @@ while (my $bound = pop @stack) { push @revqueue, $head; init_claim( defined $starting_rev ? $head : 'dirty'); unless (defined $starting_rev) { - my %ident; - @ident{'author', 'author_email', 'author_date'} = $repo->ident('author'); - my $diff = $repo->command_output_pipe('diff', '-R', 'HEAD', '--', $filename); - _git_diff_parse($diff, [$head], "dirty", %ident); - $repo->command_close_pipe($diff); + my $diff = open_pipe("git","diff","HEAD", "--",$filename) + or die "Failed to call git diff to check for dirty state: $!"; + + _git_diff_parse($diff, [$head], "dirty", ( + 'author' => gitvar_name("GIT_AUTHOR_IDENT"), + 'author_date' => sprintf("%s +0000",time()), + ) + ); + close($diff); } handle_rev(); @@ -179,7 +180,8 @@ sub git_rev_list { open($revlist, '<' . $rev_file) or die "Failed to open $rev_file : $!"; } else { - $revlist = $repo->command_output_pipe('rev-list', '--parents', '--remove-empty', $rev, '--', $file); + $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file) + or die "Failed to exec git-rev-list: $!"; } my @revs; @@ -188,7 +190,7 @@ sub git_rev_list { my ($rev, @parents) = split /\s+/, $line; push @revs, [ $rev, @parents ]; } - $repo->command_close_pipe($revlist); + close($revlist); printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0); return @revs; @@ -197,7 +199,8 @@ sub git_rev_list { sub find_parent_renames { my ($rev, $file) = @_; - my $patch = $repo->command_output_pipe('diff-tree', '-M50', '-r', '--name-status', '-z', $rev); + my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev") + or die "Failed to exec git-diff: $!"; local $/ = "\0"; my %bound; @@ -223,7 +226,7 @@ sub find_parent_renames { } } } - $repo->command_close_pipe($patch); + close($patch); return \%bound; } @@ -232,9 +235,14 @@ sub find_parent_renames { sub git_find_parent { my ($rev, $filename) = @_; - my $parentline = $repo->command_oneline('rev-list', '--remove-empty', - '--parents', '--max-count=1', $rev, '--', $filename); - my ($revfound, $parent) = split m/\s+/, $parentline; + my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename) + or die "Failed to open git-rev-list to find a single parent: $!"; + + my $parentline = <$revparent>; + chomp $parentline; + my ($revfound,$parent) = split m/\s+/, $parentline; + + close($revparent); return $parent; } @@ -242,16 +250,29 @@ sub git_find_parent { sub git_find_all_parents { my ($rev) = @_; - my $parentline = $repo->command_oneline("rev-list","--remove-empty", "--parents","--max-count=1","$rev"); + my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev") + or die "Failed to open git-rev-list to find a single parent: $!"; + + my $parentline = <$revparent>; + chomp $parentline; my ($origrev, @parents) = split m/\s+/, $parentline; + close($revparent); + return @parents; } sub git_merge_base { my ($rev1, $rev2) = @_; - my $base = $repo->command_oneline("merge-base", $rev1, $rev2); + my $mb = open_pipe("git-merge-base", $rev1, $rev2) + or die "Failed to open git-merge-base: $!"; + + my $base = <$mb>; + chomp $base; + + close($mb); + return $base; } @@ -316,7 +337,7 @@ sub git_diff_parse { my ($parents, $rev, %revinfo) = @_; my @pseudo_parents; - my @command = ("diff-tree"); + my @command = ("git-diff-tree"); my $revision_spec; if (scalar @$parents == 1) { @@ -345,11 +366,12 @@ sub git_diff_parse { push @command, "-p", "-M", $revision_spec, "--", @filenames; - my $diff = $repo->command_output_pipe(@command); + my $diff = open_pipe( @command ) + or die "Failed to call git-diff for annotation: $!"; _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo); - $repo->command_close_pipe($diff); + close($diff); } sub _git_diff_parse { @@ -525,25 +547,36 @@ sub git_cat_file { my $blob = git_ls_tree($rev, $filename); die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob; - my @lines = split(/\n/, $repo->get_object('blob', $blob)); - pop @lines unless $lines[$#lines]; # Trailing newline + my $catfile = open_pipe("git","cat-file", "blob", $blob) + or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!; + + my @lines; + while(<$catfile>) { + chomp; + push @lines, $_; + } + close($catfile); + return @lines; } sub git_ls_tree { my ($rev, $filename) = @_; - my $lstree = $repo->command_output_pipe('ls-tree', $rev, $filename); + my $lstree = open_pipe("git","ls-tree",$rev,$filename) + or die "Failed to call git ls-tree: $!"; + my ($mode, $type, $blob, $tfilename); while(<$lstree>) { chomp; ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4); last if ($tfilename eq $filename); } - $repo->command_close_pipe($lstree); + close($lstree); return $blob if ($tfilename eq $filename); die "git-ls-tree failed to find blob for $filename"; + } @@ -559,17 +592,25 @@ sub claim_line { sub git_commit_info { my ($rev) = @_; - my $commit = $repo->get_object('commit', $rev); + my $commit = open_pipe("git-cat-file", "commit", $rev) + or die "Failed to call git-cat-file: $!"; my %info; - while ($commit =~ /(.*?)\n/g) { - my $line = $1; - if ($line =~ s/^author //) { - @info{'author', 'author_email', 'author_date'} = $repo->ident($line); - } elsif ($line =~ s/^committer//) { - @info{'committer', 'committer_email', 'committer_date'} = $repo->ident($line); + while(<$commit>) { + chomp; + last if (length $_ == 0); + + if (m/^author (.*) <(.*)> (.*)$/) { + $info{'author'} = $1; + $info{'author_email'} = $2; + $info{'author_date'} = $3; + } elsif (m/^committer (.*) <(.*)> (.*)$/) { + $info{'committer'} = $1; + $info{'committer_email'} = $2; + $info{'committer_date'} = $3; } } + close($commit); return %info; } @@ -587,3 +628,81 @@ sub format_date { my $t = $timestamp + $minutes * 60; return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t)); } + +# Copied from git-send-email.perl - We need a Git.pm module.. +sub gitvar { + my ($var) = @_; + my $fh; + my $pid = open($fh, '-|'); + die "$!" unless defined $pid; + if (!$pid) { + exec('git-var', $var) or die "$!"; + } + my ($val) = <$fh>; + close $fh or die "$!"; + chomp($val); + return $val; +} + +sub gitvar_name { + my ($name) = @_; + my $val = gitvar($name); + my @field = split(/\s+/, $val); + return join(' ', @field[0...(@field-4)]); +} + +sub open_pipe { + if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') { + return open_pipe_activestate(@_); + } else { + return open_pipe_normal(@_); + } +} + +sub open_pipe_activestate { + tie *fh, "Git::ActiveStatePipe", @_; + return *fh; +} + +sub open_pipe_normal { + my (@execlist) = @_; + + my $pid = open my $kid, "-|"; + defined $pid or die "Cannot fork: $!"; + + unless ($pid) { + exec @execlist; + die "Cannot exec @execlist: $!"; + } + + return $kid; +} + +package Git::ActiveStatePipe; +use strict; + +sub TIEHANDLE { + my ($class, @params) = @_; + my $cmdline = join " ", @params; + my @data = qx{$cmdline}; + bless { i => 0, data => \@data }, $class; +} + +sub READLINE { + my $self = shift; + if ($self->{i} >= scalar @{$self->{data}}) { + return undef; + } + return $self->{'data'}->[ $self->{i}++ ]; +} + +sub CLOSE { + my $self = shift; + delete $self->{data}; + delete $self->{i}; +} + +sub EOF { + my $self = shift; + return ($self->{i} >= scalar @{$self->{data}}); +} -- cgit v0.10.2-6-g49f6 From 9594b326dcd6b879807fe6614f55ba50fa3d4551 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 2 Sep 2006 22:58:32 -0700 Subject: Revert "Git.pm: Introduce fast get_object() method" This reverts commit 3c479c37f8651d09e1d08b8d6ea9757164ee1235. diff --git a/perl/Git.pm b/perl/Git.pm index f2467bd..9da15e9 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -572,24 +572,6 @@ sub ident_person { } -=item get_object ( TYPE, SHA1 ) - -Return contents of the given object in a scalar string. If the object has -not been found, undef is returned; however, do not rely on this! Currently, -if you use multiple repositories at once, get_object() on one repository -_might_ return the object even though it exists only in another repository. -(But do not rely on this behaviour either.) - -The method must be called on a repository instance. - -Implementation of this method is very fast; no external command calls -are involved. That's why it is broken, too. ;-) - -=cut - -# Implemented in Git.xs. - - =item hash_object ( TYPE, FILENAME ) =item hash_object ( TYPE, FILEHANDLE ) diff --git a/perl/Git.xs b/perl/Git.xs index 226dd4f..6ed26a2 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -111,30 +111,6 @@ CODE: free((char **) argv); } - -SV * -xs_get_object(type, id) - char *type; - char *id; -CODE: -{ - unsigned char sha1[20]; - unsigned long size; - void *buf; - - if (strlen(id) != 40 || get_sha1_hex(id, sha1) < 0) - XSRETURN_UNDEF; - - buf = read_sha1_file(sha1, type, &size); - if (!buf) - XSRETURN_UNDEF; - RETVAL = newSVpvn(buf, size); - free(buf); -} -OUTPUT: - RETVAL - - char * xs_hash_object_pipe(type, fd) char *type; -- cgit v0.10.2-6-g49f6 From 81a71734bb73c2def1e86de88fb8de9fb6379cc5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 2 Sep 2006 22:58:48 -0700 Subject: Revert "Make it possible to set up libgit directly (instead of from the environment)" This reverts commit 0270083ded143fd49841e3d3d0cac5eb06081d2a. diff --git a/cache.h b/cache.h index 2b8fafb..af77402 100644 --- a/cache.h +++ b/cache.h @@ -117,9 +117,6 @@ extern unsigned int active_nr, active_alloc, active_cache_changed; extern struct cache_tree *active_cache_tree; extern int cache_errno; -extern void setup_git(char *new_git_dir, char *new_git_object_dir, - char *new_git_index_file, char *new_git_graft_file); - #define GIT_DIR_ENVIRONMENT "GIT_DIR" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" diff --git a/commit.c b/commit.c index 4d5c0c2..77f0ca1 100644 --- a/commit.c +++ b/commit.c @@ -163,14 +163,6 @@ int register_commit_graft(struct commit_graft *graft, int ignore_dups) return 0; } -void free_commit_grafts(void) -{ - int pos = commit_graft_nr; - while (pos >= 0) - free(commit_graft[pos--]); - commit_graft_nr = 0; -} - struct commit_graft *read_graft_line(char *buf, int len) { /* The format is just "Commit Parent1 Parent2 ...\n" */ @@ -223,18 +215,11 @@ int read_graft_file(const char *graft_file) static void prepare_commit_graft(void) { static int commit_graft_prepared; - static char *last_graft_file; - char *graft_file = get_graft_file(); - - if (last_graft_file) { - if (!strcmp(graft_file, last_graft_file)) - return; - free_commit_grafts(); - } - if (last_graft_file) - free(last_graft_file); - last_graft_file = strdup(graft_file); + char *graft_file; + if (commit_graft_prepared) + return; + graft_file = get_graft_file(); read_graft_file(graft_file); commit_graft_prepared = 1; } diff --git a/environment.c b/environment.c index 1ce3411..87162b2 100644 --- a/environment.c +++ b/environment.c @@ -25,61 +25,28 @@ int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; int pager_use_color = 1; -static int dyn_git_object_dir, dyn_git_index_file, dyn_git_graft_file; static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; - -void setup_git(char *new_git_dir, char *new_git_object_dir, - char *new_git_index_file, char *new_git_graft_file) +static void setup_git_env(void) { - git_dir = new_git_dir; + git_dir = getenv(GIT_DIR_ENVIRONMENT); if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; - - if (dyn_git_object_dir) - free(git_object_dir); - git_object_dir = new_git_object_dir; + git_object_dir = getenv(DB_ENVIRONMENT); if (!git_object_dir) { git_object_dir = xmalloc(strlen(git_dir) + 9); sprintf(git_object_dir, "%s/objects", git_dir); - dyn_git_object_dir = 1; - } else { - dyn_git_object_dir = 0; } - - if (git_refs_dir) - free(git_refs_dir); git_refs_dir = xmalloc(strlen(git_dir) + 6); sprintf(git_refs_dir, "%s/refs", git_dir); - - if (dyn_git_index_file) - free(git_index_file); - git_index_file = new_git_index_file; + git_index_file = getenv(INDEX_ENVIRONMENT); if (!git_index_file) { git_index_file = xmalloc(strlen(git_dir) + 7); sprintf(git_index_file, "%s/index", git_dir); - dyn_git_index_file = 1; - } else { - dyn_git_index_file = 0; } - - if (dyn_git_graft_file) - free(git_graft_file); - git_graft_file = new_git_graft_file; - if (!git_graft_file) { + git_graft_file = getenv(GRAFT_ENVIRONMENT); + if (!git_graft_file) git_graft_file = strdup(git_path("info/grafts")); - dyn_git_graft_file = 1; - } else { - dyn_git_graft_file = 0; - } -} - -static void setup_git_env(void) -{ - setup_git(getenv(GIT_DIR_ENVIRONMENT), - getenv(DB_ENVIRONMENT), - getenv(INDEX_ENVIRONMENT), - getenv(GRAFT_ENVIRONMENT)); } char *get_git_dir(void) diff --git a/perl/Git.pm b/perl/Git.pm index 9da15e9..9ce9fcd 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -98,8 +98,6 @@ XSLoader::load('Git', $VERSION); } -my $instance_id = 0; - =head1 CONSTRUCTORS @@ -217,7 +215,7 @@ sub repository { delete $opts{Directory}; } - $self = { opts => \%opts, id => $instance_id++ }; + $self = { opts => \%opts }; bless $self, $class; } @@ -835,10 +833,11 @@ sub _call_gate { if (defined $self) { # XXX: We ignore the WorkingCopy! To properly support # that will require heavy changes in libgit. - # For now, when we will need to do it we could temporarily - # chdir() there and then chdir() back after the call is done. - xs__call_gate($self->{id}, $self->repo_path()); + # XXX: And we ignore everything else as well. libgit + # at least needs to be extended to let us specify + # the $GIT_DIR instead of looking it up in environment. + #xs_call_gate($self->{opts}->{Repository}); } # Having to call throw from the C code is a sure path to insanity. diff --git a/perl/Git.xs b/perl/Git.xs index 6ed26a2..2bbec43 100644 --- a/perl/Git.xs +++ b/perl/Git.xs @@ -52,21 +52,7 @@ BOOT: } -void -xs__call_gate(repoid, git_dir) - long repoid; - char *git_dir; -CODE: -{ - static long last_repoid; - if (repoid != last_repoid) { - setup_git(git_dir, - getenv(DB_ENVIRONMENT), - getenv(INDEX_ENVIRONMENT), - getenv(GRAFT_ENVIRONMENT)); - last_repoid = repoid; - } -} +# /* TODO: xs_call_gate(). See Git.pm. */ char * diff --git a/sha1_file.c b/sha1_file.c index ed52d71..842a6f3 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -126,22 +126,16 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) char *sha1_file_name(const unsigned char *sha1) { static char *name, *base; - static const char *last_objdir; - const char *sha1_file_directory = get_object_directory(); - if (!last_objdir || strcmp(last_objdir, sha1_file_directory)) { + if (!base) { + const char *sha1_file_directory = get_object_directory(); int len = strlen(sha1_file_directory); - if (base) - free(base); base = xmalloc(len + 60); memcpy(base, sha1_file_directory, len); memset(base+len, 0, 60); base[len] = '/'; base[len+3] = '/'; name = base + len + 1; - if (last_objdir) - free((char *) last_objdir); - last_objdir = strdup(sha1_file_directory); } fill_sha1_path(name, sha1); return base; @@ -151,20 +145,14 @@ char *sha1_pack_name(const unsigned char *sha1) { static const char hex[] = "0123456789abcdef"; static char *name, *base, *buf; - static const char *last_objdir; - const char *sha1_file_directory = get_object_directory(); int i; - if (!last_objdir || strcmp(last_objdir, sha1_file_directory)) { + if (!base) { + const char *sha1_file_directory = get_object_directory(); int len = strlen(sha1_file_directory); - if (base) - free(base); base = xmalloc(len + 60); sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory); name = base + len + 11; - if (last_objdir) - free((char *) last_objdir); - last_objdir = strdup(sha1_file_directory); } buf = name; @@ -182,20 +170,14 @@ char *sha1_pack_index_name(const unsigned char *sha1) { static const char hex[] = "0123456789abcdef"; static char *name, *base, *buf; - static const char *last_objdir; - const char *sha1_file_directory = get_object_directory(); int i; - if (!last_objdir || strcmp(last_objdir, sha1_file_directory)) { + if (!base) { + const char *sha1_file_directory = get_object_directory(); int len = strlen(sha1_file_directory); - if (base) - free(base); base = xmalloc(len + 60); sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory); name = base + len + 11; - if (last_objdir) - free((char *) last_objdir); - last_objdir = strdup(sha1_file_directory); } buf = name; diff --git a/sha1_name.c b/sha1_name.c index ddabb04..c5a05fa 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -12,21 +12,15 @@ static int find_short_object_filename(int len, const char *name, unsigned char * char hex[40]; int found = 0; static struct alternate_object_database *fakeent; - static const char *last_objdir; - const char *objdir = get_object_directory(); - if (!last_objdir || strcmp(last_objdir, objdir)) { + if (!fakeent) { + const char *objdir = get_object_directory(); int objdir_len = strlen(objdir); int entlen = objdir_len + 43; - if (fakeent) - free(fakeent); fakeent = xmalloc(sizeof(*fakeent) + entlen); memcpy(fakeent->base, objdir, objdir_len); fakeent->name = fakeent->base + objdir_len + 1; fakeent->name[-1] = '/'; - if (last_objdir) - free((char *) last_objdir); - last_objdir = strdup(objdir); } fakeent->next = alt_odb_list; -- cgit v0.10.2-6-g49f6 From 2886bdb118699fbc343e3c0b4f93f06fbeae1061 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Sun, 3 Sep 2006 17:32:24 +0200 Subject: Update GIT_TRACE documentation. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git.txt b/Documentation/git.txt index 76b41c8..744c38d 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -634,9 +634,18 @@ other This environment variable overrides `$PAGER`. 'GIT_TRACE':: - If this variable is set git will print `trace:` messages on + If this variable is set to "1", "2" or "true" (comparison + is case insensitive), git will print `trace:` messages on stderr telling about alias expansion, built-in command execution and external command execution. + If this variable is set to an integer value greater than 1 + and lower than 10 (strictly) then git will interpret this + value as an open file descriptor and will try to write the + trace messages into this file descriptor. + Alternatively, if this variable is set to an absolute path + (starting with a '/' character), git will interpret this + as a file path and will try to write the trace messages + into it. Discussion[[Discussion]] ------------------------ -- cgit v0.10.2-6-g49f6 From 7042dbf7a1e9137eb856b3b086a062561c50b8a3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 3 Sep 2006 14:44:46 -0700 Subject: pack-objects: fix thinko in revalidate code When revalidating an entry from an existing pack entry->size and entry->type are not necessarily the size of the final object when the entry is deltified, but for base objects they must match. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 11cc3c8..5e42387 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -247,12 +247,13 @@ static int revalidate_one(struct object_entry *entry, void *data, char *type, unsigned long size) { int err; - if (!data) - return -1; - if (size != entry->size) - return -1; - err = check_sha1_signature(entry->sha1, data, size, - type_names[entry->type]); + if ((!data) || + ((entry->type != OBJ_DELTA) && + ( (size != entry->size) || + strcmp(type_names[entry->type], type)))) + err = -1; + else + err = check_sha1_signature(entry->sha1, data, size, type); free(data); return err; } -- cgit v0.10.2-6-g49f6 From f2e609473cb4aeb3884b7dd57a3a652df4e5edcf Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Sun, 3 Sep 2006 23:43:03 +0200 Subject: gitweb: Change the name of diff to parent link in "commit" view to "diff Change the name of diff to parent (current commit to one of parents) link in "commit" view (git_commit subroutine) from "commitdiff" to "diff". Let's leave "commitdiff" for equivalent of git-show, or git-diff-tree with one revision, i.e. diff for a given commit to its parent (parents). Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 57ffa25..2b40aa1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2781,7 +2781,7 @@ sub git_commit { "<td class=\"link\">" . $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") . " | " . - $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "commitdiff") . + $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") . "</td>" . "</tr>\n"; } -- cgit v0.10.2-6-g49f6 From 72518e9c2623af0b5de864a7b66208ea94aacadb Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 3 Sep 2006 21:09:18 -0700 Subject: more lightweight revalidation while reusing deflated stream in packing When copying from an existing pack and when copying from a loose object with new style header, the code makes sure that the piece we are going to copy out inflates well and inflate() consumes the data in full while doing so. The check to see if the xdelta really apply is quite expensive as you described, because you would need to have the image of the base object which can be represented as a delta against something else. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 5e42387..149fa28 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -243,41 +243,61 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha return n; } -static int revalidate_one(struct object_entry *entry, - void *data, char *type, unsigned long size) +static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect) { - int err; - if ((!data) || - ((entry->type != OBJ_DELTA) && - ( (size != entry->size) || - strcmp(type_names[entry->type], type)))) - err = -1; - else - err = check_sha1_signature(entry->sha1, data, size, type); - free(data); - return err; + z_stream stream; + unsigned char fakebuf[4096]; + int st; + + memset(&stream, 0, sizeof(stream)); + stream.next_in = data; + stream.avail_in = len; + stream.next_out = fakebuf; + stream.avail_out = sizeof(fakebuf); + inflateInit(&stream); + + while (1) { + st = inflate(&stream, Z_FINISH); + if (st == Z_STREAM_END || st == Z_OK) { + st = (stream.total_out == expect && + stream.total_in == len) ? 0 : -1; + break; + } + if (st != Z_BUF_ERROR) { + st = -1; + break; + } + stream.next_out = fakebuf; + stream.avail_out = sizeof(fakebuf); + } + inflateEnd(&stream); + return st; } /* * we are going to reuse the existing pack entry data. make * sure it is not corrupt. */ -static int revalidate_pack_entry(struct object_entry *entry) +static int revalidate_pack_entry(struct object_entry *entry, unsigned char *data, unsigned long len) { - void *data; - char type[20]; - unsigned long size; - struct pack_entry e; + enum object_type type; + unsigned long size, used; if (pack_to_stdout) return 0; - e.p = entry->in_pack; - e.offset = entry->in_pack_offset; - - /* the caller has already called use_packed_git() for us */ - data = unpack_entry_gently(&e, type, &size); - return revalidate_one(entry, data, type, size); + /* the caller has already called use_packed_git() for us, + * so it is safe to access the pack data from mmapped location. + * make sure the entry inflates correctly. + */ + used = unpack_object_header_gently(data, len, &type, &size); + if (!used) + return -1; + if (type == OBJ_DELTA) + used += 20; /* skip base object name */ + data += used; + len -= used; + return check_inflate(data, len, entry->size); } static int revalidate_loose_object(struct object_entry *entry, @@ -285,15 +305,18 @@ static int revalidate_loose_object(struct object_entry *entry, unsigned long mapsize) { /* we already know this is a loose object with new type header. */ - void *data; - char type[20]; - unsigned long size; + enum object_type type; + unsigned long size, used; if (pack_to_stdout) return 0; - data = unpack_sha1_file(map, mapsize, type, &size); - return revalidate_one(entry, data, type, size); + used = unpack_object_header_gently(map, mapsize, &type, &size); + if (!used) + return -1; + map += used; + mapsize -= used; + return check_inflate(map, mapsize, size); } static unsigned long write_object(struct sha1file *f, @@ -377,7 +400,7 @@ static unsigned long write_object(struct sha1file *f, datalen = find_packed_object_size(p, entry->in_pack_offset); buf = (char *) p->pack_base + entry->in_pack_offset; - if (revalidate_pack_entry(entry)) + if (revalidate_pack_entry(entry, buf, datalen)) die("corrupt delta in pack %s", sha1_to_hex(entry->sha1)); sha1write(f, buf, datalen); unuse_packed_git(p); diff --git a/cache.h b/cache.h index 195908f..a53204f 100644 --- a/cache.h +++ b/cache.h @@ -267,6 +267,17 @@ extern int legacy_loose_object(unsigned char *); extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); +enum object_type { + OBJ_NONE = 0, + OBJ_COMMIT = 1, + OBJ_TREE = 2, + OBJ_BLOB = 3, + OBJ_TAG = 4, + /* 5/6 for future expansion */ + OBJ_DELTA = 7, + OBJ_BAD, +}; + /* Convert to/from hex/sha1 representation */ #define MINIMUM_ABBREV 4 #define DEFAULT_ABBREV 7 @@ -374,6 +385,7 @@ extern int num_packed_objects(const struct packed_git *p); extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*); extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *); extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *); +extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *); /* Dumb servers support */ diff --git a/object.h b/object.h index 733faac..3d4ff46 100644 --- a/object.h +++ b/object.h @@ -27,17 +27,6 @@ struct object_array { /* * The object type is stored in 3 bits. */ -enum object_type { - OBJ_NONE = 0, - OBJ_COMMIT = 1, - OBJ_TREE = 2, - OBJ_BLOB = 3, - OBJ_TAG = 4, - /* 5/6 for future expansion */ - OBJ_DELTA = 7, - OBJ_BAD, -}; - struct object { unsigned parsed : 1; unsigned used : 1; diff --git a/sha1_file.c b/sha1_file.c index 4ef9805..428d791 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -711,7 +711,7 @@ int legacy_loose_object(unsigned char *map) return 0; } -static unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) +unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) { unsigned shift; unsigned char c; -- cgit v0.10.2-6-g49f6 From f986f2c830b24a0f56f2bf4b3f30353ca1e372a9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 3 Sep 2006 22:55:54 -0700 Subject: unpack-objects desperately salvages objects from a corrupt pack The command unpack-objects dies upon the first error. This is probably considered a feature -- if a pack is corrupt, instead of trying to extract from it and possibly risking to contaminate a good repository with objects whose validity is dubious, we should seek a good copy of the pack and retry. However, we may not have any good copy anywhere. This implements the last resort effort to extract what are salvageable from such a corrupt pack. This flag might have helped Sergio when recovering from a corrupt pack. In my test, it managed to salvage 247 objects out of a pack that had 251 objects but without it the command stopped after extracting 73 objects. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index c20b38b..415c09b 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -8,7 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive SYNOPSIS -------- -'git-unpack-objects' [-n] [-q] <pack-file +'git-unpack-objects' [-n] [-q] [-r] <pack-file DESCRIPTION @@ -34,6 +34,12 @@ OPTIONS The command usually shows percentage progress. This flag suppresses it. +-r:: + When unpacking a corrupt packfile, the command dies at + the first corruption. This flag tells it to keep going + and make the best effort to salvage as many objects as + possible. + Author ------ diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 0c180b5..5f9c0d2 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -10,7 +10,7 @@ #include <sys/time.h> -static int dry_run, quiet; +static int dry_run, quiet, desperate, has_errors; static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file"; /* We always read in 4kB chunks. */ @@ -71,8 +71,15 @@ static void *get_data(unsigned long size) use(len - stream.avail_in); if (stream.total_out == size && ret == Z_STREAM_END) break; - if (ret != Z_OK) - die("inflate returned %d\n", ret); + if (ret != Z_OK) { + error("inflate returned %d\n", ret); + free(buf); + buf = NULL; + if (!desperate) + exit(1); + has_errors = 1; + break; + } stream.next_in = fill(1); stream.avail_in = len; } @@ -110,9 +117,9 @@ static void write_object(void *buf, unsigned long size, const char *type) added_object(sha1, type, buf, size); } -static int resolve_delta(const char *type, - void *base, unsigned long base_size, - void *delta, unsigned long delta_size) +static void resolve_delta(const char *type, + void *base, unsigned long base_size, + void *delta, unsigned long delta_size) { void *result; unsigned long result_size; @@ -125,7 +132,6 @@ static int resolve_delta(const char *type, free(delta); write_object(result, result_size, type); free(result); - return 0; } static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size) @@ -145,7 +151,7 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi } } -static int unpack_non_delta_entry(enum object_type kind, unsigned long size) +static void unpack_non_delta_entry(enum object_type kind, unsigned long size) { void *buf = get_data(size); const char *type; @@ -157,39 +163,42 @@ static int unpack_non_delta_entry(enum object_type kind, unsigned long size) case OBJ_TAG: type = tag_type; break; default: die("bad type %d", kind); } - if (!dry_run) + if (!dry_run && buf) write_object(buf, size, type); free(buf); - return 0; } -static int unpack_delta_entry(unsigned long delta_size) +static void unpack_delta_entry(unsigned long delta_size) { void *delta_data, *base; unsigned long base_size; char type[20]; unsigned char base_sha1[20]; - int result; hashcpy(base_sha1, fill(20)); use(20); delta_data = get_data(delta_size); - if (dry_run) { + if (dry_run || !delta_data) { free(delta_data); - return 0; + return; } if (!has_sha1_file(base_sha1)) { add_delta_to_list(base_sha1, delta_data, delta_size); - return 0; + return; } base = read_sha1_file(base_sha1, type, &base_size); - if (!base) - die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); - result = resolve_delta(type, base, base_size, delta_data, delta_size); + if (!base) { + error("failed to read delta-pack base object %s", + sha1_to_hex(base_sha1)); + if (!desperate) + exit(1); + has_errors = 1; + return; + } + resolve_delta(type, base, base_size, delta_data, delta_size); free(base); - return result; } static void unpack_one(unsigned nr, unsigned total) @@ -236,7 +245,11 @@ static void unpack_one(unsigned nr, unsigned total) unpack_delta_entry(size); return; default: - die("bad object type %d", type); + error("bad object type %d", type); + has_errors = 1; + if (desperate) + return; + exit(1); } } @@ -280,6 +293,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) quiet = 1; continue; } + if (!strcmp(arg, "-r")) { + desperate = 1; + continue; + } usage(unpack_usage); } @@ -306,5 +323,5 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) /* All done */ if (!quiet) fprintf(stderr, "\n"); - return 0; + return has_errors; } -- cgit v0.10.2-6-g49f6 From 5d44cd1c8b27fb677fedc886303d38b19fbb07f7 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Mon, 4 Sep 2006 08:34:12 -0700 Subject: Fix git-fsck-objects SIGSEGV/divide-by-zero If you try to fsck a repository that isn't entirely empty, but that has no inter-object references (ie all the objects are blobs, and don't refer to anything else), git-fsck-objects currently fails. This probably cannot happen in practice, but can be tested with something like git init-db touch dummy git add dummy git fsck-objects where the fsck will die by a divide-by-zero when it tries to look up the references from the one object it found (hash_obj() will do a modulus by refs_hash_size). On some other archiectures (ppc, sparc) the divide-by-zero will go unnoticed, and we'll instead SIGSEGV when we hit the "refs_hash[j]" access. So move the test that should protect against this from mark_reachable() into lookup_object_refs(), which incidentally in the process also fixes mark_reachable() itself (it used to not mark the one object that _was_ reachable, because it decided that it had no refs too early). Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/object-refs.c b/object-refs.c index b0034e4..98ea100 100644 --- a/object-refs.c +++ b/object-refs.c @@ -55,9 +55,13 @@ static void add_object_refs(struct object *obj, struct object_refs *ref) struct object_refs *lookup_object_refs(struct object *obj) { - int j = hash_obj(obj, refs_hash_size); struct object_refs *ref; + int j; + /* nothing to lookup */ + if (!refs_hash_size) + return NULL; + j = hash_obj(obj, refs_hash_size); while ((ref = refs_hash[j]) != NULL) { if (ref->base == obj) break; @@ -125,9 +129,6 @@ void mark_reachable(struct object *obj, unsigned int mask) if (!track_object_refs) die("cannot do reachability with object refs turned off"); - /* nothing to lookup */ - if (!refs_hash_size) - return; /* If we've been here already, don't bother */ if (obj->flags & mask) return; -- cgit v0.10.2-6-g49f6 From 0b5deba13268a196a8b302255453a8c2194c876d Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 4 Sep 2006 20:32:13 +0200 Subject: gitweb: Add GIT favicon, assuming image/png type Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index a639cdf..7b3114f 100644 --- a/Makefile +++ b/Makefile @@ -131,6 +131,7 @@ GITWEB_LIST = GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png +GITWEB_FAVICON = git-favicon.png export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR @@ -635,6 +636,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ + -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ $< >$@+ chmod +x $@+ mv $@+ $@ diff --git a/gitweb/git-favicon.png b/gitweb/git-favicon.png new file mode 100644 index 0000000..de637c0 Binary files /dev/null and b/gitweb/git-favicon.png differ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2b40aa1..313e842 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -48,6 +48,8 @@ our $home_text = "++GITWEB_HOMETEXT++"; our $stylesheet = "++GITWEB_CSS++"; # URI of GIT logo our $logo = "++GITWEB_LOGO++"; +# URI of GIT favicon, assumed to be image/png type +our $favicon = "++GITWEB_FAVICON++"; # source of projects list our $projects_list = "++GITWEB_LIST++"; @@ -1222,6 +1224,9 @@ EOF 'href="%s" type="application/rss+xml"/>'."\n", esc_param($project), href(action=>"rss")); } + if (defined $favicon) { + print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n); + } print "</head>\n" . "<body>\n" . -- cgit v0.10.2-6-g49f6 From 72dbafa1e6696b6acec59c22aa7e3d956cf9992f Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 4 Sep 2006 18:19:58 +0200 Subject: gitweb: Correct typo: '==' instead of 'eq' in git_difftree_body Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 313e842..6acbb5e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1592,7 +1592,7 @@ sub git_difftree_body { $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'})}, "blob"); - if ($action == "commitdiff") { + if ($action eq 'commitdiff') { # link to patch $patchno++; print " | " . @@ -1613,7 +1613,7 @@ sub git_difftree_body { hash_base=>$parent, file_name=>$diff{'file'})}, "blob") . " | "; - if ($action == "commitdiff") { + if ($action eq 'commitdiff') { # link to patch $patchno++; print " | " . @@ -1659,7 +1659,7 @@ sub git_difftree_body { hash_base=>$hash, file_name=>$diff{'file'})}, "blob"); if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - if ($action == "commitdiff") { + if ($action eq 'commitdiff') { # link to patch $patchno++; print " | " . @@ -1701,7 +1701,7 @@ sub git_difftree_body { hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})}, "blob"); if ($diff{'to_id'} ne $diff{'from_id'}) { - if ($action == "commitdiff") { + if ($action eq 'commitdiff') { # link to patch $patchno++; print " | " . -- cgit v0.10.2-6-g49f6 From 762c7205f6700070fa3c18c8544a769461b2e567 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 4 Sep 2006 18:17:58 +0200 Subject: gitweb: Divide page path into directories -- path's "breadcrumbs" Divide page path into directories, so that each part of path links to the "tree" view of the $hash_base (or HEAD, if $hash_base is not set) version of the directory. If the entity is blob, final part (basename) links to $hash_base or HEAD revision of the "raw" blob ("blob_plain" view). If the entity is tree, link to the "tree" view. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6acbb5e..d89f709 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1405,19 +1405,32 @@ sub git_print_page_path { if (!defined $name) { print "<div class=\"page_path\">/</div>\n"; - } elsif (defined $type && $type eq 'blob') { + } else { + my @dirname = split '/', $name; + my $basename = pop @dirname; + my $fullname = ''; + print "<div class=\"page_path\">"; - if (defined $hb) { + foreach my $dir (@dirname) { + $fullname .= $dir . '/'; + print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, + hash_base=>$hb), + -title => $fullname}, esc_html($dir)); + print "/"; + } + if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, - hash_base=>$hb)}, - esc_html($name)); + hash_base=>$hb), + -title => $name}, esc_html($basename)); + } elsif (defined $type && $type eq 'tree') { + print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, + hash_base=>$hb), + -title => $name}, esc_html($basename)); + print "/"; } else { - print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name)}, - esc_html($name)); + print esc_html($basename); } print "<br/></div>\n"; - } else { - print "<div class=\"page_path\">" . esc_html($name) . "<br/></div>\n"; } } -- cgit v0.10.2-6-g49f6 From c23cca17202f6b04cb2f8f850dac6afc289bcd3d Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 5 Sep 2006 00:55:52 +0200 Subject: autoconf: Add -liconv to LIBS when NEEDS_LIBICONV Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/configure.ac b/configure.ac index 36f9cd9..fc5b813 100644 --- a/configure.ac +++ b/configure.ac @@ -147,6 +147,7 @@ AC_CHECK_LIB([c], [iconv], [NEEDS_LIBICONV=], [NEEDS_LIBICONV=YesPlease]) AC_SUBST(NEEDS_LIBICONV) +test -n "$NEEDS_SOCKET" && LIBS="$LIBS -liconv" # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). -- cgit v0.10.2-6-g49f6 From f8affe317dbfa3d5eb3cc2705568efc184f844db Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 5 Sep 2006 00:57:45 +0200 Subject: autoconf: Check for subprocess.py Add custom test for checking if Python comes with subprocess.py, or should we use our own subprocess.py by defining WITH_OWN_SUBPROCESS_PY. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/configure.ac b/configure.ac index fc5b813..6fc554e 100644 --- a/configure.ac +++ b/configure.ac @@ -261,6 +261,16 @@ AC_SUBST(NO_SETENV) # Enable it on Windows. By default, symrefs are still used. # # Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. +AC_CACHE_CHECK(for subprocess.py, + ac_cv_python_has_subprocess_py, +[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then + ac_cv_python_has_subprocess_py=yes +else + ac_cv_python_has_subprocess_py=no +fi]) +if test $ac_cv_python_has_subprocess_py != yes; then + GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease]) +fi # # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. -- cgit v0.10.2-6-g49f6 From f685d07de045423a69045e42e72d2efc22a541ca Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 5 Sep 2006 00:58:25 +0200 Subject: autoconf: Quote AC_CACHE_CHECK arguments Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/configure.ac b/configure.ac index 6fc554e..64c8345 100644 --- a/configure.ac +++ b/configure.ac @@ -205,8 +205,8 @@ AC_SUBST(NO_IPV6) # do not support the 'size specifiers' introduced by C99, namely ll, hh, # j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). # some C compilers supported these specifiers prior to C99 as an extension. -AC_CACHE_CHECK(whether formatted IO functions support C99 size specifiers, - ac_cv_c_c99_format, +AC_CACHE_CHECK([whether formatted IO functions support C99 size specifiers], + [ac_cv_c_c99_format], [# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c AC_RUN_IFELSE( [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], @@ -261,8 +261,8 @@ AC_SUBST(NO_SETENV) # Enable it on Windows. By default, symrefs are still used. # # Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. -AC_CACHE_CHECK(for subprocess.py, - ac_cv_python_has_subprocess_py, +AC_CACHE_CHECK([for subprocess.py], + [ac_cv_python_has_subprocess_py], [if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then ac_cv_python_has_subprocess_py=yes else -- cgit v0.10.2-6-g49f6 From cb2b9f5ee15547e878c668504140c66f3a42d81c Mon Sep 17 00:00:00 2001 From: Paul Mackerras <paulus@samba.org> Date: Mon, 4 Sep 2006 21:38:40 +1000 Subject: diff-index --cc shows a 3-way diff between HEAD, index and working tree. This implements a 3-way diff between the HEAD commit, the state in the index, and the working directory. This is like the n-way diff for a merge, and uses much of the same code. It is invoked with the -c flag to git-diff-index, which it already accepted and did nothing with. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff-lib.c b/diff-lib.c index 9edfa92..fc69fb9 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -213,6 +213,31 @@ static int show_modified(struct rev_info *revs, return -1; } + if (revs->combine_merges && !cached && + (hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) { + struct combine_diff_path *p; + int pathlen = ce_namelen(new); + + p = xmalloc(combine_diff_path_size(2, pathlen)); + p->path = (char *) &p->parent[2]; + p->next = NULL; + p->len = pathlen; + memcpy(p->path, new->name, pathlen); + p->path[pathlen] = 0; + p->mode = ntohl(mode); + hashclr(p->sha1); + memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent)); + p->parent[0].status = DIFF_STATUS_MODIFIED; + p->parent[0].mode = ntohl(new->ce_mode); + hashcpy(p->parent[0].sha1, new->sha1); + p->parent[1].status = DIFF_STATUS_MODIFIED; + p->parent[1].mode = ntohl(old->ce_mode); + hashcpy(p->parent[1].sha1, old->sha1); + show_combined_diff(p, 2, revs->dense_combined_merges, revs); + free(p); + return 0; + } + oldmode = old->ce_mode; if (mode == oldmode && !hashcmp(sha1, old->sha1) && !revs->diffopt.find_copies_harder) -- cgit v0.10.2-6-g49f6 From 825b045f52daa2c8f28370f30c759adbdec51cbb Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 5 Sep 2006 22:03:48 +0200 Subject: autoconf: Fix copy'n'paste error Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/configure.ac b/configure.ac index 64c8345..67c1ae0 100644 --- a/configure.ac +++ b/configure.ac @@ -147,7 +147,7 @@ AC_CHECK_LIB([c], [iconv], [NEEDS_LIBICONV=], [NEEDS_LIBICONV=YesPlease]) AC_SUBST(NEEDS_LIBICONV) -test -n "$NEEDS_SOCKET" && LIBS="$LIBS -liconv" +test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv" # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). -- cgit v0.10.2-6-g49f6 From c41e20b30b2bd4b8167917336676cde6f6aadd06 Mon Sep 17 00:00:00 2001 From: Andy Whitcroft <apw@shadowen.org> Date: Tue, 5 Sep 2006 20:00:17 +0100 Subject: send-pack: remove remote reference limit When build a pack for a push we query the remote copy for existant heads. These are used to prune unnecessary objects from the pack. As we receive the remote references in get_remote_heads() we validate the reference names via check_ref() which includes a length check; rejecting those >45 characters in size. This is a miss converted change, it was originally designed to reject messages which were less than 45 characters in length (a 40 character sha1 and refs/) to prevent comparing unitialised memory. check_ref() now gets the raw length so check for at least 5 characters. Signed-off-by: Andy Whitcroft <apw@shadowen.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/connect.c b/connect.c index 06ef387..1c6429b 100644 --- a/connect.c +++ b/connect.c @@ -17,7 +17,7 @@ static int check_ref(const char *name, int len, unsigned int flags) if (!flags) return 1; - if (len > 45 || memcmp(name, "refs/", 5)) + if (len < 5 || memcmp(name, "refs/", 5)) return 0; /* Skip the "refs/" part */ -- cgit v0.10.2-6-g49f6 From 0caea90be0e565a923b69df7b32c13cbcf892d6d Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Tue, 5 Sep 2006 08:22:16 +0200 Subject: Fix memory leak in prepend_to_path (git.c). Some memory was allocated for a new path but not freed after the path was used. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git.c b/git.c index 1d00111..335f405 100644 --- a/git.c +++ b/git.c @@ -36,6 +36,8 @@ static void prepend_to_path(const char *dir, int len) memcpy(path + len + 1, old_path, path_len - len); setenv("PATH", path, 1); + + free(path); } static int handle_options(const char*** argv, int* argc) -- cgit v0.10.2-6-g49f6 From 5d6f0935e6df017fcc446e348aebf4da2d210a27 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 5 Sep 2006 21:28:36 -0700 Subject: revision.c: allow injecting revision parameters after setup_revisions(). setup_revisions() wants to get all the parameters at once and then postprocesses the resulting revs structure after it is done with them. This code structure is a bit cumbersome to deal with efficiently when we want to inject revision parameters from the side (e.g. read from standard input). Fortunately, the nature of this postprocessing is not affected by revision parameters; they are affected only by flags. So it is Ok to do add_object() after the it returns. This splits out the code that deals with the revision parameter out of the main loop of setup_revisions(), so that we can later call it from elsewhere after it returns. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/revision.c b/revision.c index b588f74..db01682 100644 --- a/revision.c +++ b/revision.c @@ -592,6 +592,85 @@ static void prepare_show_merge(struct rev_info *revs) revs->prune_data = prune; } +int handle_revision_arg(const char *arg, struct rev_info *revs, + int flags, + int cant_be_filename) +{ + char *dotdot; + struct object *object; + unsigned char sha1[20]; + int local_flags; + + dotdot = strstr(arg, ".."); + if (dotdot) { + unsigned char from_sha1[20]; + const char *next = dotdot + 2; + const char *this = arg; + int symmetric = *next == '.'; + unsigned int flags_exclude = flags ^ UNINTERESTING; + + *dotdot = 0; + next += symmetric; + + if (!*next) + next = "HEAD"; + if (dotdot == arg) + this = "HEAD"; + if (!get_sha1(this, from_sha1) && + !get_sha1(next, sha1)) { + struct commit *a, *b; + struct commit_list *exclude; + + a = lookup_commit_reference(from_sha1); + b = lookup_commit_reference(sha1); + if (!a || !b) { + die(symmetric ? + "Invalid symmetric difference expression %s...%s" : + "Invalid revision range %s..%s", + arg, next); + } + + if (!cant_be_filename) { + *dotdot = '.'; + verify_non_filename(revs->prefix, arg); + } + + if (symmetric) { + exclude = get_merge_bases(a, b, 1); + add_pending_commit_list(revs, exclude, + flags_exclude); + free_commit_list(exclude); + a->object.flags |= flags; + } else + a->object.flags |= flags_exclude; + b->object.flags |= flags; + add_pending_object(revs, &a->object, this); + add_pending_object(revs, &b->object, next); + return 0; + } + *dotdot = '.'; + } + dotdot = strstr(arg, "^@"); + if (dotdot && !dotdot[2]) { + *dotdot = 0; + if (add_parents_only(revs, arg, flags)) + return 0; + *dotdot = '^'; + } + local_flags = 0; + if (*arg == '^') { + local_flags = UNINTERESTING; + arg++; + } + if (get_sha1(arg, sha1)) + return -1; + if (!cant_be_filename) + verify_non_filename(revs->prefix, arg); + object = get_reference(revs, arg, sha1, flags ^ local_flags); + add_pending_object(revs, object, arg); + return 0; +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -620,12 +699,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch flags = show_merge = 0; for (i = 1; i < argc; i++) { - struct object *object; const char *arg = argv[i]; - unsigned char sha1[20]; - char *dotdot; - int local_flags; - if (*arg == '-') { int opts; if (!strncmp(arg, "--max-count=", 12)) { @@ -830,71 +904,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch left++; continue; } - dotdot = strstr(arg, ".."); - if (dotdot) { - unsigned char from_sha1[20]; - const char *next = dotdot + 2; - const char *this = arg; - int symmetric = *next == '.'; - unsigned int flags_exclude = flags ^ UNINTERESTING; - - *dotdot = 0; - next += symmetric; - - if (!*next) - next = "HEAD"; - if (dotdot == arg) - this = "HEAD"; - if (!get_sha1(this, from_sha1) && - !get_sha1(next, sha1)) { - struct commit *a, *b; - struct commit_list *exclude; - - a = lookup_commit_reference(from_sha1); - b = lookup_commit_reference(sha1); - if (!a || !b) { - die(symmetric ? - "Invalid symmetric difference expression %s...%s" : - "Invalid revision range %s..%s", - arg, next); - } - - if (!seen_dashdash) { - *dotdot = '.'; - verify_non_filename(revs->prefix, arg); - } - - if (symmetric) { - exclude = get_merge_bases(a, b, 1); - add_pending_commit_list(revs, exclude, - flags_exclude); - free_commit_list(exclude); - a->object.flags |= flags; - } else - a->object.flags |= flags_exclude; - b->object.flags |= flags; - add_pending_object(revs, &a->object, this); - add_pending_object(revs, &b->object, next); - continue; - } - *dotdot = '.'; - } - dotdot = strstr(arg, "^@"); - if (dotdot && !dotdot[2]) { - *dotdot = 0; - if (add_parents_only(revs, arg, flags)) - continue; - *dotdot = '^'; - } - local_flags = 0; - if (*arg == '^') { - local_flags = UNINTERESTING; - arg++; - } - if (get_sha1(arg, sha1)) { - int j; - if (seen_dashdash || local_flags) + if (handle_revision_arg(arg, revs, flags, seen_dashdash)) { + int j; + if (seen_dashdash || *arg == '^') die("bad revision '%s'", arg); /* If we didn't have a "--": @@ -906,14 +919,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch for (j = i; j < argc; j++) verify_filename(revs->prefix, argv[j]); - revs->prune_data = get_pathspec(revs->prefix, argv + i); + revs->prune_data = get_pathspec(revs->prefix, + argv + i); break; } - if (!seen_dashdash) - verify_non_filename(revs->prefix, arg); - object = get_reference(revs, arg, sha1, flags ^ local_flags); - add_pending_object(revs, object, arg); } + if (show_merge) prepare_show_merge(revs); if (def && !revs->pending.nr) { diff --git a/revision.h b/revision.h index d289781..c1f71af 100644 --- a/revision.h +++ b/revision.h @@ -90,6 +90,8 @@ extern int rev_compare_tree(struct rev_info *, struct tree *t1, struct tree *t2) extern void init_revisions(struct rev_info *revs, const char *prefix); extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); +extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename); + extern void prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); -- cgit v0.10.2-6-g49f6 From 42cabc341c4e6da3c9873db7af1003bd1a72a8fd Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 5 Sep 2006 21:39:02 -0700 Subject: Teach rev-list an option to read revs from the standard input. When --stdin option is given, in addition to the <rev>s listed on the command line, the command can read one rev parameter per line from the standard input. The list of revs ends at the first empty line or EOF. Note that you still have to give all the flags from the command line; only rev arguments (including A..B, A...B, and A^@ notations) can be give from the standard input. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 3c4c2fb..28966ad 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -17,6 +17,7 @@ SYNOPSIS [ \--remove-empty ] [ \--not ] [ \--all ] + [ \--stdin ] [ \--topo-order ] [ \--parents ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] @@ -171,6 +172,11 @@ limiting may be applied. Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the command line as '<commit>'. +--stdin:: + + In addition to the '<commit>' listed on the command + line, read them from the standard input. + --merge:: After a failed merge, show refs that touch files having a diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 8437454..8fe8afb 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -23,6 +23,7 @@ static const char rev_list_usage[] = " --no-merges\n" " --remove-empty\n" " --all\n" +" --stdin\n" " ordering output:\n" " --topo-order\n" " --date-order\n" @@ -304,10 +305,28 @@ static void mark_edges_uninteresting(struct commit_list *list) } } +static void read_revisions_from_stdin(struct rev_info *revs) +{ + char line[1000]; + + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = strlen(line); + if (line[len - 1] == '\n') + line[--len] = 0; + if (!len) + break; + if (line[0] == '-') + die("options not supported in --stdin mode"); + if (handle_revision_arg(line, revs, 0, 1)) + die("bad revision '%s'", line); + } +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct commit_list *list; int i; + int read_from_stdin = 0; init_revisions(&revs, prefix); revs.abbrev = 0; @@ -329,6 +348,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_list = 1; continue; } + if (!strcmp(arg, "--stdin")) { + if (read_from_stdin++) + die("--stdin given twice?"); + read_revisions_from_stdin(&revs); + continue; + } usage(rev_list_usage); } -- cgit v0.10.2-6-g49f6 From 0ea2582d1cbbacd91eb0f040c4994ecd2bb6cdb9 Mon Sep 17 00:00:00 2001 From: Martin Langhoff <martin@catalyst.net.nz> Date: Mon, 4 Sep 2006 17:42:32 +1200 Subject: git-repack: create new packs inside $GIT_DIR, not cwd Avoid failing when cwd is !writable by writing the packfiles in $GIT_DIR, which is more in line with other commands. Without this, git-repack was failing when run from crontab by non-root user accounts. For large repositories, this also makes the mv operation a lot cheaper, and avoids leaving temp packfiles around the fs upon failure. Signed-off-by: Martin Langhoff <martin@catalyst.net.nz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-repack.sh b/git-repack.sh index 584a732..b525fc5 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -24,8 +24,10 @@ do shift done -rm -f .tmp-pack-* PACKDIR="$GIT_OBJECT_DIRECTORY/pack" +PACKTMP="$GIT_DIR/.tmp-$$-pack" +rm -f "$PACKTMP"-* +trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15 # There will be more repacking strategies to come... case ",$all_into_one," in @@ -42,11 +44,12 @@ case ",$all_into_one," in find . -type f \( -name '*.pack' -o -name '*.idx' \) -print` ;; esac + pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra" name=$( { git-rev-list --objects --all $rev_list || echo "git-rev-list died with exit code $?" } | - git-pack-objects --non-empty $pack_objects .tmp-pack) || + git-pack-objects --non-empty $pack_objects "$PACKTMP") || exit 1 if [ -z "$name" ]; then echo Nothing new to pack. @@ -64,8 +67,8 @@ else "$PACKDIR/old-pack-$name.$sfx" fi done && - mv -f .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" && - mv -f .tmp-pack-$name.idx "$PACKDIR/pack-$name.idx" && + mv -f "$PACKTMP-$name.pack" "$PACKDIR/pack-$name.pack" && + mv -f "$PACKTMP-$name.idx" "$PACKDIR/pack-$name.idx" && test -f "$PACKDIR/pack-$name.pack" && test -f "$PACKDIR/pack-$name.idx" || { echo >&2 "Couldn't replace the existing pack with updated one." -- cgit v0.10.2-6-g49f6 From d9edcbd6061a392c1315ab6f3aedb9992a3c01b1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Thu, 7 Sep 2006 01:40:04 -0700 Subject: Revert "daemon: add upload-tar service." This reverts parts of commit 74c0cc2 and part of commit 355f541. Franck and Rene are working on a unified upload-archive which would supersede this when done, so better not to get in their way. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 35c3c4b..741f2c6 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -135,12 +135,6 @@ upload-pack:: disable it by setting `daemon.uploadpack` configuration item to `false`. -upload-tar:: - This serves `git-tar-tree --remote=repository` client. - It is not enabled by default, but a repository can - enable it by setting `daemon.uploadtar` configuration - item to `true`. - Author ------ Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki diff --git a/daemon.c b/daemon.c index a4a08f3..b14d808 100644 --- a/daemon.c +++ b/daemon.c @@ -22,6 +22,7 @@ static const char daemon_usage[] = " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" " [--reuseaddr] [--detach] [--pid-file=file]\n" +" [--[enable|disable|allow-override|forbid-override]=service]\n" " [--user=user [[--group=group]] [directory...]"; /* List of acceptable pathname prefixes */ @@ -324,15 +325,8 @@ static int upload_pack(void) return -1; } -static int upload_tar(void) -{ - execl_git_cmd("upload-tar", ".", NULL); - return -1; -} - static struct daemon_service daemon_service[] = { { "upload-pack", "uploadpack", upload_pack, 1, 1 }, - { "upload-tar", "uploadtar", upload_tar, 0, 1 }, }; static void enable_service(const char *name, int ena) { -- cgit v0.10.2-6-g49f6 From c727fe2afcb86195d72136b4b3ba4b5fea5514d5 Mon Sep 17 00:00:00 2001 From: Andy Whitcroft <apw@shadowen.org> Date: Tue, 5 Sep 2006 22:52:12 +0100 Subject: send-pack: switch to using git-rev-list --stdin When we are generating packs to update remote repositories we want to supply as much information as possible about the revisions that already exist to rev-list in order optimise the pack as much as possible. We need to pass two revisions for each branch we are updating in the remote repository and one for each additional branch. Where the remote repository has numerous branches we can run out of command line space to pass them. Utilise the git-rev-list --stdin mode to allow unlimited numbers of revision constraints. This allows us to move back to the much simpler unordered revision selection code. [jc: added some comments in the code to describe the pipe flow a bit.] Signed-off-by: Andy Whitcroft <apw@shadowen.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/send-pack.c b/send-pack.c index ac4501d..49be764 100644 --- a/send-pack.c +++ b/send-pack.c @@ -38,9 +38,8 @@ static void exec_pack_objects(void) static void exec_rev_list(struct ref *refs) { - struct ref *ref; - static const char *args[1000]; - int i = 0, j; + static const char *args[4]; + int i = 0; args[i++] = "rev-list"; /* 0 */ if (use_thin_pack) /* 1 */ @@ -48,43 +47,16 @@ static void exec_rev_list(struct ref *refs) else args[i++] = "--objects"; - /* First send the ones we care about most */ - for (ref = refs; ref; ref = ref->next) { - if (900 < i) - die("git-rev-list environment overflow"); - if (!is_zero_sha1(ref->new_sha1)) { - char *buf = xmalloc(100); - args[i++] = buf; - snprintf(buf, 50, "%s", sha1_to_hex(ref->new_sha1)); - buf += 50; - if (!is_zero_sha1(ref->old_sha1) && - has_sha1_file(ref->old_sha1)) { - args[i++] = buf; - snprintf(buf, 50, "^%s", - sha1_to_hex(ref->old_sha1)); - } - } - } + args[i++] = "--stdin"; - /* Then a handful of the remainder - * NEEDSWORK: we would be better off if used the newer ones first. - */ - for (ref = refs, j = i + 16; - i < 900 && i < j && ref; - ref = ref->next) { - if (is_zero_sha1(ref->new_sha1) && - !is_zero_sha1(ref->old_sha1) && - has_sha1_file(ref->old_sha1)) { - char *buf = xmalloc(42); - args[i++] = buf; - snprintf(buf, 42, "^%s", sha1_to_hex(ref->old_sha1)); - } - } args[i] = NULL; execv_git_cmd(args); die("git-rev-list exec failed (%s)", strerror(errno)); } +/* + * Run "rev-list --stdin | pack-objects" pipe. + */ static void rev_list(int fd, struct ref *refs) { int pipe_fd[2]; @@ -94,6 +66,9 @@ static void rev_list(int fd, struct ref *refs) die("rev-list setup: pipe failed"); pack_objects_pid = fork(); if (!pack_objects_pid) { + /* The child becomes pack-objects; reads from pipe + * and writes to the original fd + */ dup2(pipe_fd[0], 0); dup2(fd, 1); close(pipe_fd[0]); @@ -104,6 +79,8 @@ static void rev_list(int fd, struct ref *refs) } if (pack_objects_pid < 0) die("pack-objects fork failed"); + + /* We become rev-list --stdin; output goes to pipe. */ dup2(pipe_fd[1], 1); close(pipe_fd[0]); close(pipe_fd[1]); @@ -111,13 +88,71 @@ static void rev_list(int fd, struct ref *refs) exec_rev_list(refs); } +/* + * Create "rev-list --stdin | pack-objects" pipe and feed + * the refs into the pipeline. + */ +static void rev_list_generate(int fd, struct ref *refs) +{ + int pipe_fd[2]; + pid_t rev_list_generate_pid; + + if (pipe(pipe_fd) < 0) + die("rev-list-generate setup: pipe failed"); + rev_list_generate_pid = fork(); + if (!rev_list_generate_pid) { + /* The child becomes the "rev-list | pack-objects" + * pipeline. It takes input from us, and its output + * goes to fd. + */ + dup2(pipe_fd[0], 0); + dup2(fd, 1); + close(pipe_fd[0]); + close(pipe_fd[1]); + close(fd); + rev_list(fd, refs); + die("rev-list setup failed"); + } + if (rev_list_generate_pid < 0) + die("rev-list-generate fork failed"); + + /* We feed the rev parameters to them. We do not write into + * fd nor read from the pipe. + */ + close(pipe_fd[0]); + close(fd); + while (refs) { + char buf[42]; + + if (!is_null_sha1(refs->old_sha1) && + has_sha1_file(refs->old_sha1)) { + memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40); + buf[0] = '^'; + buf[41] = '\n'; + write(pipe_fd[1], buf, 42); + } + if (!is_null_sha1(refs->new_sha1)) { + memcpy(buf, sha1_to_hex(refs->new_sha1), 40); + buf[40] = '\n'; + write(pipe_fd[1], buf, 41); + } + refs = refs->next; + } + close(pipe_fd[1]); + // waitpid(rev_list_generate_pid); + exit(0); +} + +/* + * Make a pack stream and spit it out into file descriptor fd + */ static void pack_objects(int fd, struct ref *refs) { pid_t rev_list_pid; rev_list_pid = fork(); if (!rev_list_pid) { - rev_list(fd, refs); + rev_list_generate(fd, refs); die("rev-list setup failed"); } if (rev_list_pid < 0) -- cgit v0.10.2-6-g49f6 From 2b6eef943ff81df63a809857b7b400ee175eb29b Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 6 Sep 2006 22:45:21 -0700 Subject: Make apply --binary a no-op. Historically we did not allow binary patch applied without an explicit permission from the user, and this flag was the way to do so. This makes the flag a no-op by always allowing binary patch application. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index c76cfff..0a6f7b3 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -110,15 +110,10 @@ OPTIONS deletion part but not addition part. --allow-binary-replacement, --binary:: - When applying a patch, which is a git-enhanced patch - that was prepared to record the pre- and post-image object - name in full, and the path being patched exactly matches - the object the patch applies to (i.e. "index" line's - pre-image object name is what is in the working tree), - and the post-image object is available in the object - database, use the post-image object as the patch - result. This allows binary files to be patched in a - very limited way. + Historically we did not allow binary patch applied + without an explicit permission from the user, and this + flag was the way to do so. Currently we always allow binary + patch application, so this is a no-op. --exclude=<path-pattern>:: Don't apply changes to files matching the given path pattern. This can diff --git a/builtin-apply.c b/builtin-apply.c index 872c800..6e0864c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -28,7 +28,6 @@ static int prefix_length = -1; static int newfd = -1; static int p_value = 1; -static int allow_binary_replacement; static int check_index; static int write_index; static int cached; @@ -1228,14 +1227,12 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) } } - /* Empty patch cannot be applied if: - * - it is a binary patch and we do not do binary_replace, or - * - text patch without metadata change + /* Empty patch cannot be applied if it is a text patch + * without metadata change. A binary patch appears + * empty to us here. */ if ((apply || check) && - (patch->is_binary - ? !allow_binary_replacement - : !metadata_changes(patch))) + (!patch->is_binary && !metadata_changes(patch))) die("patch with only garbage at line %d", linenr); } @@ -1676,11 +1673,6 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) unsigned char hdr[50]; int hdrlen; - if (!allow_binary_replacement) - return error("cannot apply binary patch to '%s' " - "without --allow-binary-replacement", - name); - /* For safety, we require patch index line to contain * full 40-byte textual SHA1 for old and new, at least for now. */ @@ -2497,8 +2489,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--allow-binary-replacement") || !strcmp(arg, "--binary")) { - allow_binary_replacement = 1; - continue; + continue; /* now no-op */ } if (!strcmp(arg, "--numstat")) { apply = 0; diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index ff05269..e2b1124 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -94,11 +94,11 @@ test_expect_failure 'apply binary diff (copy) -- should fail.' \ 'do_reset git-apply --index C.diff' -test_expect_failure 'apply binary diff without replacement -- should fail.' \ +test_expect_success 'apply binary diff without replacement.' \ 'do_reset git-apply BF.diff' -test_expect_failure 'apply binary diff without replacement (copy) -- should fail.' \ +test_expect_success 'apply binary diff without replacement (copy).' \ 'do_reset git-apply CF.diff' -- cgit v0.10.2-6-g49f6 From 82793c55e45ea1c7306ff396d8ce316241e584da Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Thu, 7 Sep 2006 00:54:22 -0700 Subject: diff --binary generates full index on binary files. ... without --full-index. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff.c b/diff.c index 9dcbda3..6638865 100644 --- a/diff.c +++ b/diff.c @@ -1588,6 +1588,12 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) if (hashcmp(one->sha1, two->sha1)) { int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; + if (o->binary) { + mmfile_t mf; + if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) || + (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf))) + abbrev = 40; + } len += snprintf(msg + len, sizeof(msg) - len, "index %.*s..%.*s", abbrev, sha1_to_hex(one->sha1), @@ -1818,7 +1824,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->full_index = 1; else if (!strcmp(arg, "--binary")) { options->output_format |= DIFF_FORMAT_PATCH; - options->full_index = options->binary = 1; + options->binary = 1; } else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) { options->text = 1; -- cgit v0.10.2-6-g49f6 From c64ed70d2557101f2a2c3f76315049d027fe645b Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Mon, 4 Sep 2006 21:50:12 -0700 Subject: Separate object listing routines out of rev-list Create a separate file, list-objects.c, and move object listing routines from rev-list to it. The next round will use it in pack-objects directly. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 7b3114f..18cd79e 100644 --- a/Makefile +++ b/Makefile @@ -233,7 +233,7 @@ XDIFF_LIB=xdiff/lib.a LIB_H = \ blob.h cache.h commit.h csum-file.h delta.h \ - diff.h object.h pack.h pkt-line.h quote.h refs.h \ + diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h @@ -250,7 +250,7 @@ LIB_OBJS = \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - write_or_die.o trace.o \ + write_or_die.o trace.o list-objects.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) BUILTIN_OBJS = \ diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 8fe8afb..0900737 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -7,6 +7,7 @@ #include "tree-walk.h" #include "diff.h" #include "revision.h" +#include "list-objects.h" #include "builtin.h" /* bits #0-15 in revision.h */ @@ -98,104 +99,19 @@ static void show_commit(struct commit *commit) commit->buffer = NULL; } -static void process_blob(struct blob *blob, - struct object_array *p, - struct name_path *path, - const char *name) +static void show_object(struct object_array_entry *p) { - struct object *obj = &blob->object; - - if (!revs.blob_objects) - return; - if (obj->flags & (UNINTERESTING | SEEN)) - return; - obj->flags |= SEEN; - name = xstrdup(name); - add_object(obj, p, path, name); -} - -static void process_tree(struct tree *tree, - struct object_array *p, - struct name_path *path, - const char *name) -{ - struct object *obj = &tree->object; - struct tree_desc desc; - struct name_entry entry; - struct name_path me; - - if (!revs.tree_objects) - return; - if (obj->flags & (UNINTERESTING | SEEN)) - return; - if (parse_tree(tree) < 0) - die("bad tree object %s", sha1_to_hex(obj->sha1)); - obj->flags |= SEEN; - name = xstrdup(name); - add_object(obj, p, path, name); - me.up = path; - me.elem = name; - me.elem_len = strlen(name); - - desc.buf = tree->buffer; - desc.size = tree->size; - - while (tree_entry(&desc, &entry)) { - if (S_ISDIR(entry.mode)) - process_tree(lookup_tree(entry.sha1), p, &me, entry.path); - else - process_blob(lookup_blob(entry.sha1), p, &me, entry.path); - } - free(tree->buffer); - tree->buffer = NULL; -} - -static void show_commit_list(struct rev_info *revs) -{ - int i; - struct commit *commit; - struct object_array objects = { 0, 0, NULL }; - - while ((commit = get_revision(revs)) != NULL) { - process_tree(commit->tree, &objects, NULL, ""); - show_commit(commit); - } - for (i = 0; i < revs->pending.nr; i++) { - struct object_array_entry *pending = revs->pending.objects + i; - struct object *obj = pending->item; - const char *name = pending->name; - if (obj->flags & (UNINTERESTING | SEEN)) - continue; - if (obj->type == OBJ_TAG) { - obj->flags |= SEEN; - add_object_array(obj, name, &objects); - continue; - } - if (obj->type == OBJ_TREE) { - process_tree((struct tree *)obj, &objects, NULL, name); - continue; - } - if (obj->type == OBJ_BLOB) { - process_blob((struct blob *)obj, &objects, NULL, name); - continue; - } - die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); - } - for (i = 0; i < objects.nr; i++) { - struct object_array_entry *p = objects.objects + i; - - /* An object with name "foo\n0000000..." can be used to - * confuse downstream git-pack-objects very badly. - */ - const char *ep = strchr(p->name, '\n'); - if (ep) { - printf("%s %.*s\n", sha1_to_hex(p->item->sha1), - (int) (ep - p->name), - p->name); - } - else - printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); + /* An object with name "foo\n0000000..." can be used to + * confuse downstream git-pack-objects very badly. + */ + const char *ep = strchr(p->name, '\n'); + if (ep) { + printf("%s %.*s\n", sha1_to_hex(p->item->sha1), + (int) (ep - p->name), + p->name); } + else + printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); } /* @@ -389,7 +305,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) revs.commits = find_bisection(revs.commits); - show_commit_list(&revs); + traverse_commit_list(&revs, show_commit, show_object); return 0; } diff --git a/list-objects.c b/list-objects.c new file mode 100644 index 0000000..adaf997 --- /dev/null +++ b/list-objects.c @@ -0,0 +1,107 @@ +#include "cache.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "diff.h" +#include "tree-walk.h" +#include "revision.h" +#include "list-objects.h" + +static void process_blob(struct rev_info *revs, + struct blob *blob, + struct object_array *p, + struct name_path *path, + const char *name) +{ + struct object *obj = &blob->object; + + if (!revs->blob_objects) + return; + if (obj->flags & (UNINTERESTING | SEEN)) + return; + obj->flags |= SEEN; + name = xstrdup(name); + add_object(obj, p, path, name); +} + +static void process_tree(struct rev_info *revs, + struct tree *tree, + struct object_array *p, + struct name_path *path, + const char *name) +{ + struct object *obj = &tree->object; + struct tree_desc desc; + struct name_entry entry; + struct name_path me; + + if (!revs->tree_objects) + return; + if (obj->flags & (UNINTERESTING | SEEN)) + return; + if (parse_tree(tree) < 0) + die("bad tree object %s", sha1_to_hex(obj->sha1)); + obj->flags |= SEEN; + name = xstrdup(name); + add_object(obj, p, path, name); + me.up = path; + me.elem = name; + me.elem_len = strlen(name); + + desc.buf = tree->buffer; + desc.size = tree->size; + + while (tree_entry(&desc, &entry)) { + if (S_ISDIR(entry.mode)) + process_tree(revs, + lookup_tree(entry.sha1), + p, &me, entry.path); + else + process_blob(revs, + lookup_blob(entry.sha1), + p, &me, entry.path); + } + free(tree->buffer); + tree->buffer = NULL; +} + +void traverse_commit_list(struct rev_info *revs, + void (*show_commit)(struct commit *), + void (*show_object)(struct object_array_entry *)) +{ + int i; + struct commit *commit; + struct object_array objects = { 0, 0, NULL }; + + while ((commit = get_revision(revs)) != NULL) { + process_tree(revs, commit->tree, &objects, NULL, ""); + show_commit(commit); + } + for (i = 0; i < revs->pending.nr; i++) { + struct object_array_entry *pending = revs->pending.objects + i; + struct object *obj = pending->item; + const char *name = pending->name; + if (obj->flags & (UNINTERESTING | SEEN)) + continue; + if (obj->type == OBJ_TAG) { + obj->flags |= SEEN; + add_object_array(obj, name, &objects); + continue; + } + if (obj->type == OBJ_TREE) { + process_tree(revs, (struct tree *)obj, &objects, + NULL, name); + continue; + } + if (obj->type == OBJ_BLOB) { + process_blob(revs, (struct blob *)obj, &objects, + NULL, name); + continue; + } + die("unknown pending object %s (%s)", + sha1_to_hex(obj->sha1), name); + } + for (i = 0; i < objects.nr; i++) + show_object(&objects.objects[i]); +} diff --git a/list-objects.h b/list-objects.h new file mode 100644 index 0000000..8a5fae6 --- /dev/null +++ b/list-objects.h @@ -0,0 +1,8 @@ +#ifndef LIST_OBJECTS_H +#define LIST_OBJECTS_H + +void traverse_commit_list(struct rev_info *revs, + void (*show_commit)(struct commit *), + void (*show_object)(struct object_array_entry *)); + +#endif -- cgit v0.10.2-6-g49f6 From b5d97e6b0a044b11b409250189c61d40209065f2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Mon, 4 Sep 2006 23:47:39 -0700 Subject: pack-objects: run rev-list equivalent internally. Instead of piping the rev-list output from its standard input, you can say: pack-objects --all --unpacked --revs pack and feed the rev parameters you would otherwise give the rev-list on its command line from the standard input. In other words: echo 'master..next' | pack-objects --revs pack and rev-list --objects master..next | pack-objects pack are equivalent. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 149fa28..b6e5960 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -9,10 +9,13 @@ #include "pack.h" #include "csum-file.h" #include "tree-walk.h" +#include "diff.h" +#include "revision.h" +#include "list-objects.h" #include <sys/time.h> #include <signal.h> -static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list"; +static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} [--revs [--unpacked | --all]* <ref-list | <object-list]"; struct object_entry { unsigned char sha1[20]; @@ -1326,89 +1329,14 @@ static int git_pack_config(const char *k, const char *v) return git_default_config(k, v); } -int cmd_pack_objects(int argc, const char **argv, const char *prefix) +static void read_object_list_from_stdin(void) { - SHA_CTX ctx; - char line[40 + 1 + PATH_MAX + 2]; - int depth = 10; - struct object_entry **list; int num_preferred_base = 0; - int i; - - git_config(git_pack_config); - - progress = isatty(2); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!strcmp("--non-empty", arg)) { - non_empty = 1; - continue; - } - if (!strcmp("--local", arg)) { - local = 1; - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("--incremental", arg)) { - incremental = 1; - continue; - } - if (!strncmp("--window=", arg, 9)) { - char *end; - window = strtoul(arg+9, &end, 0); - if (!arg[9] || *end) - usage(pack_usage); - continue; - } - if (!strncmp("--depth=", arg, 8)) { - char *end; - depth = strtoul(arg+8, &end, 0); - if (!arg[8] || *end) - usage(pack_usage); - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("-q", arg)) { - progress = 0; - continue; - } - if (!strcmp("--no-reuse-delta", arg)) { - no_reuse_delta = 1; - continue; - } - if (!strcmp("--stdout", arg)) { - pack_to_stdout = 1; - continue; - } - usage(pack_usage); - } - if (base_name) - usage(pack_usage); - base_name = arg; - } - - if (pack_to_stdout != !base_name) - usage(pack_usage); - - prepare_packed_git(); - - if (progress) { - fprintf(stderr, "Generating pack...\n"); - setup_progress_signal(); - } + char line[40 + 1 + PATH_MAX + 2]; + unsigned char sha1[20]; + unsigned hash; for (;;) { - unsigned char sha1[20]; - unsigned hash; - if (!fgets(line, sizeof(line), stdin)) { if (feof(stdin)) break; @@ -1419,21 +1347,226 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) clearerr(stdin); continue; } - if (line[0] == '-') { if (get_sha1_hex(line+1, sha1)) die("expected edge sha1, got garbage:\n %s", - line+1); + line); if (num_preferred_base++ < window) add_preferred_base(sha1); continue; } if (get_sha1_hex(line, sha1)) die("expected sha1, got garbage:\n %s", line); + hash = name_hash(line+41); add_preferred_base_object(line+41, hash); add_object_entry(sha1, hash, 0); } +} + +/* copied from rev-list but needs to do things slightly differently */ +static void mark_edge_parents_uninteresting(struct commit *commit) +{ + struct commit_list *parents; + + for (parents = commit->parents; parents; parents = parents->next) { + struct commit *parent = parents->item; + if (!(parent->object.flags & UNINTERESTING)) + continue; + mark_tree_uninteresting(parent->tree); + } +} + +static void mark_edges_uninteresting(struct commit_list *list) +{ + for ( ; list; list = list->next) { + struct commit *commit = list->item; + + if (commit->object.flags & UNINTERESTING) { + mark_tree_uninteresting(commit->tree); + continue; + } + mark_edge_parents_uninteresting(commit); + } +} + +static void show_commit(struct commit *commit) +{ + unsigned hash = name_hash(""); + add_object_entry(commit->object.sha1, hash, 0); +} + +static void show_object(struct object_array_entry *p) +{ + unsigned hash = name_hash(p->name); + add_object_entry(p->item->sha1, hash, 0); +} + +static void get_object_list(int unpacked, int all) +{ + struct rev_info revs; + char line[1000]; + const char *av[6]; + int ac; + int flags = 0; + + av[0] = "pack-objects"; + av[1] = "--objects"; + ac = 2; + if (unpacked) + av[ac++] = "--unpacked"; + if (all) + av[ac++] = "--all"; + av[ac++] = "--stdin"; + av[ac] = NULL; + + init_revisions(&revs, NULL); + save_commit_buffer = 0; + track_object_refs = 0; + setup_revisions(ac, av, &revs, NULL); + + /* make sure we did not get pathspecs */ + if (revs.prune_data) + die("pathspec given"); + + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = strlen(line); + if (line[len - 1] == '\n') + line[--len] = 0; + if (!len) + break; + if (*line == '-') { + if (!strcmp(line, "--not")) { + flags ^= UNINTERESTING; + continue; + } + die("not a rev '%s'", line); + } + if (handle_revision_arg(line, &revs, flags, 1)) + die("bad revision '%s'", line); + } + + prepare_revision_walk(&revs); + mark_edges_uninteresting(revs.commits); + + traverse_commit_list(&revs, show_commit, show_object); +} + +int cmd_pack_objects(int argc, const char **argv, const char *prefix) +{ + SHA_CTX ctx; + int depth = 10; + struct object_entry **list; + int use_internal_rev_list = 0; + int unpacked = 0; + int all = 0; + int i; + + git_config(git_pack_config); + + progress = isatty(2); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (*arg != '-') + break; + + if (!strcmp("--non-empty", arg)) { + non_empty = 1; + continue; + } + if (!strcmp("--local", arg)) { + local = 1; + continue; + } + if (!strcmp("--progress", arg)) { + progress = 1; + continue; + } + if (!strcmp("--incremental", arg)) { + incremental = 1; + continue; + } + if (!strncmp("--window=", arg, 9)) { + char *end; + window = strtoul(arg+9, &end, 0); + if (!arg[9] || *end) + usage(pack_usage); + continue; + } + if (!strncmp("--depth=", arg, 8)) { + char *end; + depth = strtoul(arg+8, &end, 0); + if (!arg[8] || *end) + usage(pack_usage); + continue; + } + if (!strcmp("--progress", arg)) { + progress = 1; + continue; + } + if (!strcmp("-q", arg)) { + progress = 0; + continue; + } + if (!strcmp("--no-reuse-delta", arg)) { + no_reuse_delta = 1; + continue; + } + if (!strcmp("--stdout", arg)) { + pack_to_stdout = 1; + continue; + } + if (!strcmp("--revs", arg)) { + use_internal_rev_list = 1; + continue; + } + if (!strcmp("--unpacked", arg)) { + unpacked = 1; + continue; + } + if (!strcmp("--all", arg)) { + all = 1; + continue; + } + usage(pack_usage); + } + + /* Traditionally "pack-objects [options] base extra" failed; + * we would however want to take refs parameter that would + * have been given to upstream rev-list ourselves, which means + * we somehow want to say what the base name is. So the + * syntax would be: + * + * pack-objects [options] base <refs...> + * + * in other words, we would treat the first non-option as the + * base_name and send everything else to the internal revision + * walker. + */ + + if (!pack_to_stdout) + base_name = argv[i++]; + + if (pack_to_stdout != !base_name) + usage(pack_usage); + + /* --unpacked and --all makes sense only with --revs */ + if (!use_internal_rev_list && (unpacked || all)) + usage(pack_usage); + + prepare_packed_git(); + + if (progress) { + fprintf(stderr, "Generating pack...\n"); + setup_progress_signal(); + } + + if (!use_internal_rev_list) + read_object_list_from_stdin(); + else + get_object_list(unpacked, all); + if (progress) fprintf(stderr, "Done counting %d objects.\n", nr_objects); sorted_by_sha = create_final_object_list(); -- cgit v0.10.2-6-g49f6 From 8d1d8f83b5d918c6071b606e321de9c31fed9e68 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 6 Sep 2006 01:42:23 -0700 Subject: pack-objects: further work on internal rev-list logic. This teaches the internal rev-list logic to understand options that are needed for pack handling: --all, --unpacked, and --thin. It also moves two functions from builtin-rev-list to list-objects so that the two programs can share more code. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index b6e5960..753dd9a 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -69,6 +69,7 @@ static int progress = 1; static volatile sig_atomic_t progress_update; static int window = 10; static int pack_to_stdout; +static int num_preferred_base; /* * The object names in objects array are hashed with this hashtable, @@ -841,7 +842,7 @@ static int check_pbase_path(unsigned hash) return 0; } -static void add_preferred_base_object(char *name, unsigned hash) +static void add_preferred_base_object(const char *name, unsigned hash) { struct pbase_tree *it; int cmplen = name_cmp_len(name); @@ -870,6 +871,9 @@ static void add_preferred_base(unsigned char *sha1) unsigned long size; unsigned char tree_sha1[20]; + if (window <= num_preferred_base++) + return; + data = read_object_with_reference(sha1, tree_type, &size, tree_sha1); if (!data) return; @@ -1331,7 +1335,6 @@ static int git_pack_config(const char *k, const char *v) static void read_object_list_from_stdin(void) { - int num_preferred_base = 0; char line[40 + 1 + PATH_MAX + 2]; unsigned char sha1[20]; unsigned hash; @@ -1351,8 +1354,7 @@ static void read_object_list_from_stdin(void) if (get_sha1_hex(line+1, sha1)) die("expected edge sha1, got garbage:\n %s", line); - if (num_preferred_base++ < window) - add_preferred_base(sha1); + add_preferred_base(sha1); continue; } if (get_sha1_hex(line, sha1)) @@ -1364,71 +1366,36 @@ static void read_object_list_from_stdin(void) } } -/* copied from rev-list but needs to do things slightly differently */ -static void mark_edge_parents_uninteresting(struct commit *commit) -{ - struct commit_list *parents; - - for (parents = commit->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - if (!(parent->object.flags & UNINTERESTING)) - continue; - mark_tree_uninteresting(parent->tree); - } -} - -static void mark_edges_uninteresting(struct commit_list *list) -{ - for ( ; list; list = list->next) { - struct commit *commit = list->item; - - if (commit->object.flags & UNINTERESTING) { - mark_tree_uninteresting(commit->tree); - continue; - } - mark_edge_parents_uninteresting(commit); - } -} - static void show_commit(struct commit *commit) { unsigned hash = name_hash(""); + add_preferred_base_object("", hash); add_object_entry(commit->object.sha1, hash, 0); } static void show_object(struct object_array_entry *p) { unsigned hash = name_hash(p->name); + add_preferred_base_object(p->name, hash); add_object_entry(p->item->sha1, hash, 0); } -static void get_object_list(int unpacked, int all) +static void show_edge(struct commit *commit) +{ + add_preferred_base(commit->object.sha1); +} + +static void get_object_list(int ac, const char **av) { struct rev_info revs; char line[1000]; - const char *av[6]; - int ac; int flags = 0; - av[0] = "pack-objects"; - av[1] = "--objects"; - ac = 2; - if (unpacked) - av[ac++] = "--unpacked"; - if (all) - av[ac++] = "--all"; - av[ac++] = "--stdin"; - av[ac] = NULL; - init_revisions(&revs, NULL); save_commit_buffer = 0; track_object_refs = 0; setup_revisions(ac, av, &revs, NULL); - /* make sure we did not get pathspecs */ - if (revs.prune_data) - die("pathspec given"); - while (fgets(line, sizeof(line), stdin) != NULL) { int len = strlen(line); if (line[len - 1] == '\n') @@ -1447,8 +1414,7 @@ static void get_object_list(int unpacked, int all) } prepare_revision_walk(&revs); - mark_edges_uninteresting(revs.commits); - + mark_edges_uninteresting(revs.commits, &revs, show_edge); traverse_commit_list(&revs, show_commit, show_object); } @@ -1458,9 +1424,14 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) int depth = 10; struct object_entry **list; int use_internal_rev_list = 0; - int unpacked = 0; - int all = 0; + int thin = 0; int i; + const char *rp_av[64]; + int rp_ac; + + rp_av[0] = "pack-objects"; + rp_av[1] = "--objects"; /* --thin will make it --objects-edge */ + rp_ac = 2; git_config(git_pack_config); @@ -1521,12 +1492,19 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) use_internal_rev_list = 1; continue; } - if (!strcmp("--unpacked", arg)) { - unpacked = 1; + if (!strcmp("--unpacked", arg) || + !strncmp("--unpacked=", arg, 11) || + !strcmp("--all", arg)) { + use_internal_rev_list = 1; + if (ARRAY_SIZE(rp_av) - 1 <= rp_ac) + die("too many internal rev-list options"); + rp_av[rp_ac++] = arg; continue; } - if (!strcmp("--all", arg)) { - all = 1; + if (!strcmp("--thin", arg)) { + use_internal_rev_list = 1; + thin = 1; + rp_av[1] = "--objects-edge"; continue; } usage(pack_usage); @@ -1551,9 +1529,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (pack_to_stdout != !base_name) usage(pack_usage); - /* --unpacked and --all makes sense only with --revs */ - if (!use_internal_rev_list && (unpacked || all)) - usage(pack_usage); + if (!pack_to_stdout && thin) + die("--thin cannot be used to build an indexable pack."); prepare_packed_git(); @@ -1564,8 +1541,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!use_internal_rev_list) read_object_list_from_stdin(); - else - get_object_list(unpacked, all); + else { + rp_av[rp_ac] = NULL; + get_object_list(rp_ac, rp_av); + } if (progress) fprintf(stderr, "Done counting %d objects.\n", nr_objects); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 0900737..1f3333d 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -114,6 +114,11 @@ static void show_object(struct object_array_entry *p) printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); } +static void show_edge(struct commit *commit) +{ + printf("-%s\n", sha1_to_hex(commit->object.sha1)); +} + /* * This is a truly stupid algorithm, but it's only * used for bisection, and we just don't care enough. @@ -192,35 +197,6 @@ static struct commit_list *find_bisection(struct commit_list *list) return best; } -static void mark_edge_parents_uninteresting(struct commit *commit) -{ - struct commit_list *parents; - - for (parents = commit->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - if (!(parent->object.flags & UNINTERESTING)) - continue; - mark_tree_uninteresting(parent->tree); - if (revs.edge_hint && !(parent->object.flags & SHOWN)) { - parent->object.flags |= SHOWN; - printf("-%s\n", sha1_to_hex(parent->object.sha1)); - } - } -} - -static void mark_edges_uninteresting(struct commit_list *list) -{ - for ( ; list; list = list->next) { - struct commit *commit = list->item; - - if (commit->object.flags & UNINTERESTING) { - mark_tree_uninteresting(commit->tree); - continue; - } - mark_edge_parents_uninteresting(commit); - } -} - static void read_revisions_from_stdin(struct rev_info *revs) { char line[1000]; @@ -300,7 +276,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) prepare_revision_walk(&revs); if (revs.tree_objects) - mark_edges_uninteresting(revs.commits); + mark_edges_uninteresting(revs.commits, &revs, show_edge); if (bisect_list) revs.commits = find_bisection(revs.commits); diff --git a/list-objects.c b/list-objects.c index adaf997..f1fa21c 100644 --- a/list-objects.c +++ b/list-objects.c @@ -66,6 +66,39 @@ static void process_tree(struct rev_info *revs, tree->buffer = NULL; } +static void mark_edge_parents_uninteresting(struct commit *commit, + struct rev_info *revs, + show_edge_fn show_edge) +{ + struct commit_list *parents; + + for (parents = commit->parents; parents; parents = parents->next) { + struct commit *parent = parents->item; + if (!(parent->object.flags & UNINTERESTING)) + continue; + mark_tree_uninteresting(parent->tree); + if (revs->edge_hint && !(parent->object.flags & SHOWN)) { + parent->object.flags |= SHOWN; + show_edge(parent); + } + } +} + +void mark_edges_uninteresting(struct commit_list *list, + struct rev_info *revs, + show_edge_fn show_edge) +{ + for ( ; list; list = list->next) { + struct commit *commit = list->item; + + if (commit->object.flags & UNINTERESTING) { + mark_tree_uninteresting(commit->tree); + continue; + } + mark_edge_parents_uninteresting(commit, revs, show_edge); + } +} + void traverse_commit_list(struct rev_info *revs, void (*show_commit)(struct commit *), void (*show_object)(struct object_array_entry *)) diff --git a/list-objects.h b/list-objects.h index 8a5fae6..0f41391 100644 --- a/list-objects.h +++ b/list-objects.h @@ -1,8 +1,12 @@ #ifndef LIST_OBJECTS_H #define LIST_OBJECTS_H -void traverse_commit_list(struct rev_info *revs, - void (*show_commit)(struct commit *), - void (*show_object)(struct object_array_entry *)); +typedef void (*show_commit_fn)(struct commit *); +typedef void (*show_object_fn)(struct object_array_entry *); +typedef void (*show_edge_fn)(struct commit *); + +void traverse_commit_list(struct rev_info *revs, show_commit_fn, show_object_fn); + +void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn); #endif -- cgit v0.10.2-6-g49f6 From 106d710bc13f34aec1a15c4cff80f062f384edf6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 6 Sep 2006 02:12:09 -0700 Subject: pack-objects --unpacked=<existing pack> option. Incremental repack without -a essentially boils down to: rev-list --objects --unpacked --all | pack-objects $new_pack which picks up all loose objects that are still live and creates a new pack. This implements --unpacked=<existing pack> option to tell the revision walking machinery to pretend as if objects in such a pack are unpacked for the purpose of object listing. With this, we could say: rev-list --objects --unpacked=$active_pack --all | pack-objects $new_pack instead, to mean "all live loose objects but pretend as if objects that are in this pack are also unpacked". The newly created pack would be perfect for updating $active_pack by replacing it. Since pack-objects now knows how to do the rev-list's work itself internally, you can also write the above example by: pack-objects --unpacked=$active_pack --all $new_pack </dev/null Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-count-objects.c b/builtin-count-objects.c index 1d3729a..73c5982 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -62,7 +62,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose, hex[40] = 0; if (get_sha1_hex(hex, sha1)) die("internal error"); - if (has_sha1_pack(sha1)) + if (has_sha1_pack(sha1, NULL)) (*packed_loose)++; } } diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c index d3dd94d..960db49 100644 --- a/builtin-prune-packed.c +++ b/builtin-prune-packed.c @@ -19,7 +19,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len) memcpy(hex+2, de->d_name, 38); if (get_sha1_hex(hex, sha1)) continue; - if (!has_sha1_pack(sha1)) + if (!has_sha1_pack(sha1, NULL)) continue; memcpy(pathname + len, de->d_name, 38); if (dryrun) diff --git a/cache.h b/cache.h index a53204f..ac51ed1 100644 --- a/cache.h +++ b/cache.h @@ -259,7 +259,7 @@ extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, extern int write_sha1_to_fd(int fd, const unsigned char *sha1); extern int move_temp_to_file(const char *tmpfile, const char *filename); -extern int has_sha1_pack(const unsigned char *sha1); +extern int has_sha1_pack(const unsigned char *sha1, const char **ignore); extern int has_sha1_file(const unsigned char *sha1); extern void *map_sha1_file(const unsigned char *sha1, unsigned long *); extern int legacy_loose_object(unsigned char *); diff --git a/revision.c b/revision.c index db01682..6a2539b 100644 --- a/revision.c +++ b/revision.c @@ -416,7 +416,8 @@ static void limit_list(struct rev_info *revs) if (revs->max_age != -1 && (commit->date < revs->max_age)) obj->flags |= UNINTERESTING; - if (revs->unpacked && has_sha1_pack(obj->sha1)) + if (revs->unpacked && + has_sha1_pack(obj->sha1, revs->ignore_packed)) obj->flags |= UNINTERESTING; add_parents_to_list(revs, commit, &list); if (obj->flags & UNINTERESTING) { @@ -671,6 +672,16 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; } +static void add_ignore_packed(struct rev_info *revs, const char *name) +{ + int num = ++revs->num_ignore_packed; + + revs->ignore_packed = xrealloc(revs->ignore_packed, + sizeof(const char **) * (num + 1)); + revs->ignore_packed[num-1] = name; + revs->ignore_packed[num] = NULL; +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -811,6 +822,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch } if (!strcmp(arg, "--unpacked")) { revs->unpacked = 1; + free(revs->ignore_packed); + revs->ignore_packed = NULL; + revs->num_ignore_packed = 0; + continue; + } + if (!strncmp(arg, "--unpacked=", 11)) { + revs->unpacked = 1; + add_ignore_packed(revs, arg+11); continue; } if (!strcmp(arg, "-r")) { @@ -1057,7 +1076,8 @@ struct commit *get_revision(struct rev_info *revs) */ if (!revs->limited) { if ((revs->unpacked && - has_sha1_pack(commit->object.sha1)) || + has_sha1_pack(commit->object.sha1, + revs->ignore_packed)) || (revs->max_age != -1 && (commit->date < revs->max_age))) continue; diff --git a/revision.h b/revision.h index c1f71af..a5c35d0 100644 --- a/revision.h +++ b/revision.h @@ -38,7 +38,7 @@ struct rev_info { blob_objects:1, edge_hint:1, limited:1, - unpacked:1, + unpacked:1, /* see also ignore_packed below */ boundary:1, parents:1; @@ -57,6 +57,10 @@ struct rev_info { unsigned int shown_one:1, abbrev_commit:1, relative_date:1; + + const char **ignore_packed; /* pretend objects in these are unpacked */ + int num_ignore_packed; + unsigned int abbrev; enum cmit_fmt commit_format; struct log_info *loginfo; diff --git a/sha1_file.c b/sha1_file.c index 428d791..d5d048a 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1189,12 +1189,20 @@ int find_pack_entry_one(const unsigned char *sha1, return 0; } -static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) +static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) { struct packed_git *p; prepare_packed_git(); for (p = packed_git; p; p = p->next) { + if (ignore_packed) { + const char **ig; + for (ig = ignore_packed; *ig; ig++) + if (!strcmp(p->pack_name, *ig)) + break; + if (*ig) + continue; + } if (find_pack_entry_one(sha1, e, p)) return 1; } @@ -1227,10 +1235,10 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep if (!map) { struct pack_entry e; - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return packed_object_info(&e, type, sizep); reprepare_packed_git(); - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return packed_object_info(&e, type, sizep); return error("unable to find %s", sha1_to_hex(sha1)); } @@ -1253,7 +1261,7 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo { struct pack_entry e; - if (!find_pack_entry(sha1, &e)) { + if (!find_pack_entry(sha1, &e, NULL)) { error("cannot read sha1_file for %s", sha1_to_hex(sha1)); return NULL; } @@ -1266,7 +1274,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size void *map, *buf; struct pack_entry e; - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return read_packed_sha1(sha1, type, size); map = map_sha1_file(sha1, &mapsize); if (map) { @@ -1275,7 +1283,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size return buf; } reprepare_packed_git(); - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return read_packed_sha1(sha1, type, size); return NULL; } @@ -1707,10 +1715,10 @@ int has_pack_file(const unsigned char *sha1) return 1; } -int has_sha1_pack(const unsigned char *sha1) +int has_sha1_pack(const unsigned char *sha1, const char **ignore_packed) { struct pack_entry e; - return find_pack_entry(sha1, &e); + return find_pack_entry(sha1, &e, ignore_packed); } int has_sha1_file(const unsigned char *sha1) @@ -1718,7 +1726,7 @@ int has_sha1_file(const unsigned char *sha1) struct stat st; struct pack_entry e; - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return 1; return find_sha1_file(sha1, &st) ? 1 : 0; } -- cgit v0.10.2-6-g49f6 From 6ff88de7f7affba3e9899cfdab4dab46b554e93f Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Thu, 7 Sep 2006 13:48:08 +0200 Subject: autoconf: Set NO_ICONV if iconv is found neither in libc, nor in libiconv Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/configure.ac b/configure.ac index 67c1ae0..9bbc7da 100644 --- a/configure.ac +++ b/configure.ac @@ -143,9 +143,12 @@ AC_CHECK_LIB([expat], [XML_ParserCreate], AC_SUBST(NO_EXPAT) # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). +# Define NO_ICONV if neither libc nor libiconv support iconv. AC_CHECK_LIB([c], [iconv], -[NEEDS_LIBICONV=], -[NEEDS_LIBICONV=YesPlease]) + [NEEDS_LIBICONV=], + AC_CHECK_LIB([iconv], [iconv], + [NEEDS_LIBICONV=YesPlease], + [GIT_CONF_APPEND_LINE([NO_ICONV=YesPlease])])) AC_SUBST(NEEDS_LIBICONV) test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv" # -- cgit v0.10.2-6-g49f6 From baf1219acbda6ba361c388363ba2e771d95bbb2d Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Thu, 7 Sep 2006 13:48:49 +0200 Subject: autoconf: Add support for setting NO_ICONV and ICONVDIR Add support for ./configure options --without-iconv (if neither libc nor libiconv properly support iconv), and for --with-iconv=PATH (to set prefix to libiconv library and headers, used only when NEED_LIBICONV is set). While at it, make ./configure set or unset NO_ICONV always (it is not autodetected in Makefile). Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/config.mak.in b/config.mak.in index 369e611..6d20673 100644 --- a/config.mak.in +++ b/config.mak.in @@ -37,4 +37,5 @@ NO_C99_FORMAT=@NO_C99_FORMAT@ NO_STRCASESTR=@NO_STRCASESTR@ NO_STRLCPY=@NO_STRLCPY@ NO_SETENV=@NO_SETENV@ +NO_ICONV=@NO_ICONV@ diff --git a/configure.ac b/configure.ac index 9bbc7da..511cac9 100644 --- a/configure.ac +++ b/configure.ac @@ -148,8 +148,9 @@ AC_CHECK_LIB([c], [iconv], [NEEDS_LIBICONV=], AC_CHECK_LIB([iconv], [iconv], [NEEDS_LIBICONV=YesPlease], - [GIT_CONF_APPEND_LINE([NO_ICONV=YesPlease])])) + [NO_ICONV=YesPlease])) AC_SUBST(NEEDS_LIBICONV) +AC_SUBST(NO_ICONV) test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv" # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, @@ -343,6 +344,16 @@ GIT_PARSE_WITH(expat)) # library directories by defining CFLAGS and LDFLAGS appropriately. # # Define NO_MMAP if you want to avoid mmap. +# +# Define NO_ICONV if your libc does not properly support iconv. +AC_ARG_WITH(iconv, +AS_HELP_STRING([--without-iconv], +[if your architecture doesn't properly support iconv]) +AS_HELP_STRING([--with-iconv=PATH], +[PATH is prefix for libiconv library and headers]) +AS_HELP_STRING([], +[used only if you need linking with libiconv]), +GIT_PARSE_WITH(iconv)) ## --enable-FEATURE[=ARG] and --disable-FEATURE # -- cgit v0.10.2-6-g49f6 From 2878836c5074be92b913c95446b94bbf1c9a9e1d Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Thu, 7 Sep 2006 14:30:06 +0200 Subject: autoconf: Add config.cache to .gitignore Add generated file config.cache (default cache file, when running ./configure with -C, --config-cache option) to the list of ignored files. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index 78cb671..0d608fe 100644 --- a/.gitignore +++ b/.gitignore @@ -141,6 +141,7 @@ git-core.spec *.py[co] config.mak autom4te.cache +config.cache config.log config.status config.mak.autogen -- cgit v0.10.2-6-g49f6 From 0424558190fb85e6702e7c64cd2b11bc31d7be85 Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Thu, 7 Sep 2006 02:35:42 -0400 Subject: diff: support custom callbacks for output Users can request the DIFF_FORMAT_CALLBACK output format to get a callback consisting of the whole diff_queue_struct. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff.c b/diff.c index 9dcbda3..a3ebc62 100644 --- a/diff.c +++ b/diff.c @@ -2587,6 +2587,9 @@ void diff_flush(struct diff_options *options) } } + if (output_format & DIFF_FORMAT_CALLBACK) + options->format_callback(q, options, options->format_callback_data); + for (i = 0; i < q->nr; i++) diff_free_filepair(q->queue[i]); free_queue: diff --git a/diff.h b/diff.h index b007240..b60a02e 100644 --- a/diff.h +++ b/diff.h @@ -8,6 +8,7 @@ struct rev_info; struct diff_options; +struct diff_queue_struct; typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, @@ -20,6 +21,9 @@ typedef void (*add_remove_fn_t)(struct diff_options *options, const unsigned char *sha1, const char *base, const char *path); +typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, + struct diff_options *options, void *data); + #define DIFF_FORMAT_RAW 0x0001 #define DIFF_FORMAT_DIFFSTAT 0x0002 #define DIFF_FORMAT_SUMMARY 0x0004 @@ -35,6 +39,8 @@ typedef void (*add_remove_fn_t)(struct diff_options *options, */ #define DIFF_FORMAT_NO_OUTPUT 0x0080 +#define DIFF_FORMAT_CALLBACK 0x0100 + struct diff_options { const char *filter; const char *orderfile; @@ -68,6 +74,8 @@ struct diff_options { int *pathlens; change_fn_t change; add_remove_fn_t add_remove; + diff_format_fn_t format_callback; + void *format_callback_data; }; enum color_diff { -- cgit v0.10.2-6-g49f6 From 7c92fe0eaa4fb89e27fa3617b9ae52f20b511573 Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Fri, 8 Sep 2006 04:03:18 -0400 Subject: Move color option parsing out of diff.c and into color.[ch] The intent is to lib-ify colorizing code so it can be reused. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 7b3114f..78748cb 100644 --- a/Makefile +++ b/Makefile @@ -251,7 +251,8 @@ LIB_OBJS = \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o \ - alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) + alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ + color.o BUILTIN_OBJS = \ builtin-add.o \ diff --git a/color.c b/color.c new file mode 100644 index 0000000..d8c8399 --- /dev/null +++ b/color.c @@ -0,0 +1,176 @@ +#include "color.h" +#include "cache.h" +#include "git-compat-util.h" + +#include <stdarg.h> + +#define COLOR_RESET "\033[m" + +static int parse_color(const char *name, int len) +{ + static const char * const color_names[] = { + "normal", "black", "red", "green", "yellow", + "blue", "magenta", "cyan", "white" + }; + char *end; + int i; + for (i = 0; i < ARRAY_SIZE(color_names); i++) { + const char *str = color_names[i]; + if (!strncasecmp(name, str, len) && !str[len]) + return i - 1; + } + i = strtol(name, &end, 10); + if (*name && !*end && i >= -1 && i <= 255) + return i; + return -2; +} + +static int parse_attr(const char *name, int len) +{ + static const int attr_values[] = { 1, 2, 4, 5, 7 }; + static const char * const attr_names[] = { + "bold", "dim", "ul", "blink", "reverse" + }; + int i; + for (i = 0; i < ARRAY_SIZE(attr_names); i++) { + const char *str = attr_names[i]; + if (!strncasecmp(name, str, len) && !str[len]) + return attr_values[i]; + } + return -1; +} + +void color_parse(const char *value, const char *var, char *dst) +{ + const char *ptr = value; + int attr = -1; + int fg = -2; + int bg = -2; + + if (!strcasecmp(value, "reset")) { + strcpy(dst, "\033[m"); + return; + } + + /* [fg [bg]] [attr] */ + while (*ptr) { + const char *word = ptr; + int val, len = 0; + + while (word[len] && !isspace(word[len])) + len++; + + ptr = word + len; + while (*ptr && isspace(*ptr)) + ptr++; + + val = parse_color(word, len); + if (val >= -1) { + if (fg == -2) { + fg = val; + continue; + } + if (bg == -2) { + bg = val; + continue; + } + goto bad; + } + val = parse_attr(word, len); + if (val < 0 || attr != -1) + goto bad; + attr = val; + } + + if (attr >= 0 || fg >= 0 || bg >= 0) { + int sep = 0; + + *dst++ = '\033'; + *dst++ = '['; + if (attr >= 0) { + *dst++ = '0' + attr; + sep++; + } + if (fg >= 0) { + if (sep++) + *dst++ = ';'; + if (fg < 8) { + *dst++ = '3'; + *dst++ = '0' + fg; + } else { + dst += sprintf(dst, "38;5;%d", fg); + } + } + if (bg >= 0) { + if (sep++) + *dst++ = ';'; + if (bg < 8) { + *dst++ = '4'; + *dst++ = '0' + bg; + } else { + dst += sprintf(dst, "48;5;%d", bg); + } + } + *dst++ = 'm'; + } + *dst = 0; + return; +bad: + die("bad config value '%s' for variable '%s'", value, var); +} + +int git_config_colorbool(const char *var, const char *value) +{ + if (!value) + return 1; + if (!strcasecmp(value, "auto")) { + if (isatty(1) || (pager_in_use && pager_use_color)) { + char *term = getenv("TERM"); + if (term && strcmp(term, "dumb")) + return 1; + } + return 0; + } + if (!strcasecmp(value, "never")) + return 0; + if (!strcasecmp(value, "always")) + return 1; + return git_config_bool(var, value); +} + +static int color_vprintf(const char *color, const char *fmt, + va_list args, const char *trail) +{ + int r = 0; + + if (*color) + r += printf("%s", color); + r += vprintf(fmt, args); + if (*color) + r += printf("%s", COLOR_RESET); + if (trail) + r += printf("%s", trail); + return r; +} + + + +int color_printf(const char *color, const char *fmt, ...) +{ + va_list args; + int r; + va_start(args, fmt); + r = color_vprintf(color, fmt, args, NULL); + va_end(args); + return r; +} + +int color_printf_ln(const char *color, const char *fmt, ...) +{ + va_list args; + int r; + va_start(args, fmt); + r = color_vprintf(color, fmt, args, "\n"); + va_end(args); + return r; +} diff --git a/color.h b/color.h new file mode 100644 index 0000000..88bb8ff --- /dev/null +++ b/color.h @@ -0,0 +1,12 @@ +#ifndef COLOR_H +#define COLOR_H + +/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ +#define COLOR_MAXLEN 24 + +int git_config_colorbool(const char *var, const char *value); +void color_parse(const char *var, const char *value, char *dst); +int color_printf(const char *color, const char *fmt, ...); +int color_printf_ln(const char *color, const char *fmt, ...); + +#endif /* COLOR_H */ diff --git a/diff.c b/diff.c index a3ebc62..8178c11 100644 --- a/diff.c +++ b/diff.c @@ -10,6 +10,7 @@ #include "diffcore.h" #include "delta.h" #include "xdiff-interface.h" +#include "color.h" static int use_size_cache; @@ -17,8 +18,7 @@ static int diff_detect_rename_default; static int diff_rename_limit_default = -1; static int diff_use_color_default; -/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ -static char diff_colors[][24] = { +static char diff_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ "", /* normal */ "\033[1m", /* bold */ @@ -45,119 +45,6 @@ static int parse_diff_color_slot(const char *var, int ofs) die("bad config variable '%s'", var); } -static int parse_color(const char *name, int len) -{ - static const char * const color_names[] = { - "normal", "black", "red", "green", "yellow", - "blue", "magenta", "cyan", "white" - }; - char *end; - int i; - for (i = 0; i < ARRAY_SIZE(color_names); i++) { - const char *str = color_names[i]; - if (!strncasecmp(name, str, len) && !str[len]) - return i - 1; - } - i = strtol(name, &end, 10); - if (*name && !*end && i >= -1 && i <= 255) - return i; - return -2; -} - -static int parse_attr(const char *name, int len) -{ - static const int attr_values[] = { 1, 2, 4, 5, 7 }; - static const char * const attr_names[] = { - "bold", "dim", "ul", "blink", "reverse" - }; - int i; - for (i = 0; i < ARRAY_SIZE(attr_names); i++) { - const char *str = attr_names[i]; - if (!strncasecmp(name, str, len) && !str[len]) - return attr_values[i]; - } - return -1; -} - -static void parse_diff_color_value(const char *value, const char *var, char *dst) -{ - const char *ptr = value; - int attr = -1; - int fg = -2; - int bg = -2; - - if (!strcasecmp(value, "reset")) { - strcpy(dst, "\033[m"); - return; - } - - /* [fg [bg]] [attr] */ - while (*ptr) { - const char *word = ptr; - int val, len = 0; - - while (word[len] && !isspace(word[len])) - len++; - - ptr = word + len; - while (*ptr && isspace(*ptr)) - ptr++; - - val = parse_color(word, len); - if (val >= -1) { - if (fg == -2) { - fg = val; - continue; - } - if (bg == -2) { - bg = val; - continue; - } - goto bad; - } - val = parse_attr(word, len); - if (val < 0 || attr != -1) - goto bad; - attr = val; - } - - if (attr >= 0 || fg >= 0 || bg >= 0) { - int sep = 0; - - *dst++ = '\033'; - *dst++ = '['; - if (attr >= 0) { - *dst++ = '0' + attr; - sep++; - } - if (fg >= 0) { - if (sep++) - *dst++ = ';'; - if (fg < 8) { - *dst++ = '3'; - *dst++ = '0' + fg; - } else { - dst += sprintf(dst, "38;5;%d", fg); - } - } - if (bg >= 0) { - if (sep++) - *dst++ = ';'; - if (bg < 8) { - *dst++ = '4'; - *dst++ = '0' + bg; - } else { - dst += sprintf(dst, "48;5;%d", bg); - } - } - *dst++ = 'm'; - } - *dst = 0; - return; -bad: - die("bad config value '%s' for variable '%s'", value, var); -} - /* * These are to give UI layer defaults. * The core-level commands such as git-diff-files should @@ -171,22 +58,7 @@ int git_diff_ui_config(const char *var, const char *value) return 0; } if (!strcmp(var, "diff.color")) { - if (!value) - diff_use_color_default = 1; /* bool */ - else if (!strcasecmp(value, "auto")) { - diff_use_color_default = 0; - if (isatty(1) || (pager_in_use && pager_use_color)) { - char *term = getenv("TERM"); - if (term && strcmp(term, "dumb")) - diff_use_color_default = 1; - } - } - else if (!strcasecmp(value, "never")) - diff_use_color_default = 0; - else if (!strcasecmp(value, "always")) - diff_use_color_default = 1; - else - diff_use_color_default = git_config_bool(var, value); + diff_use_color_default = git_config_colorbool(var, value); return 0; } if (!strcmp(var, "diff.renames")) { @@ -201,7 +73,7 @@ int git_diff_ui_config(const char *var, const char *value) } if (!strncmp(var, "diff.color.", 11)) { int slot = parse_diff_color_slot(var, 11); - parse_diff_color_value(value, var, diff_colors[slot]); + color_parse(value, var, diff_colors[slot]); return 0; } return git_default_config(var, value); -- cgit v0.10.2-6-g49f6 From c91f0d92efb36d7b349f586cafafaf0e6ac3f5b2 Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Fri, 8 Sep 2006 04:05:34 -0400 Subject: git-commit.sh: convert run_status to a C builtin This creates a new git-runstatus which should do roughly the same thing as the run_status function from git-commit.sh. Except for color support, the main focus has been to keep the output identical, so that it can be verified as correct and then used as a C platform for other improvements to the status printing code. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index 0d608fe..97592ab 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ git-rev-list git-rev-parse git-revert git-rm +git-runstatus git-send-email git-send-pack git-sh-setup diff --git a/Makefile b/Makefile index 78748cb..a9314ac 100644 --- a/Makefile +++ b/Makefile @@ -252,7 +252,7 @@ LIB_OBJS = \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ - color.o + color.o wt-status.o BUILTIN_OBJS = \ builtin-add.o \ @@ -286,6 +286,7 @@ BUILTIN_OBJS = \ builtin-rev-list.o \ builtin-rev-parse.o \ builtin-rm.o \ + builtin-runstatus.o \ builtin-show-branch.o \ builtin-stripspace.o \ builtin-symbolic-ref.o \ diff --git a/builtin-runstatus.c b/builtin-runstatus.c new file mode 100644 index 0000000..7979d61 --- /dev/null +++ b/builtin-runstatus.c @@ -0,0 +1,34 @@ +#include "wt-status.h" +#include "cache.h" + +extern int wt_status_use_color; + +static const char runstatus_usage[] = +"git-runstatus [--color|--nocolor] [--amend] [--verbose]"; + +int cmd_runstatus(int argc, const char **argv, const char *prefix) +{ + struct wt_status s; + int i; + + git_config(git_status_config); + wt_status_prepare(&s); + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--color")) + wt_status_use_color = 1; + else if (!strcmp(argv[i], "--nocolor")) + wt_status_use_color = 0; + else if (!strcmp(argv[i], "--amend")) { + s.amend = 1; + s.reference = "HEAD^1"; + } + else if (!strcmp(argv[i], "--verbose")) + s.verbose = 1; + else + usage(runstatus_usage); + } + + wt_status_print(&s); + return s.commitable ? 0 : 1; +} diff --git a/builtin.h b/builtin.h index 25431d7..53a896c 100644 --- a/builtin.h +++ b/builtin.h @@ -47,6 +47,7 @@ extern int cmd_repo_config(int argc, const char **argv, const char *prefix); extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); +extern int cmd_runstatus(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); diff --git a/dir.c b/dir.c index 5a40d8f..e2f472b 100644 --- a/dir.c +++ b/dir.c @@ -397,3 +397,10 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; } + +int +file_exists(const char *f) +{ + struct stat sb; + return stat(f, &sb) == 0; +} diff --git a/dir.h b/dir.h index 56a1b7f..313f8ab 100644 --- a/dir.h +++ b/dir.h @@ -47,5 +47,6 @@ extern int excluded(struct dir_struct *, const char *); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); +extern int file_exists(const char *); #endif diff --git a/git-commit.sh b/git-commit.sh index 4cf3fab..10c269a 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -60,26 +60,6 @@ report () { } run_status () { - ( - # We always show status for the whole tree. - cd "$TOP" - - IS_INITIAL="$initial_commit" - REFERENCE=HEAD - case "$amend" in - t) - # If we are amending the initial commit, there - # is no HEAD^1. - if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1 - then - REFERENCE="HEAD^1" - IS_INITIAL= - else - IS_INITIAL=t - fi - ;; - esac - # If TMP_INDEX is defined, that means we are doing # "--only" partial commit, and that index file is used # to build the tree for the commit. Otherwise, if @@ -96,85 +76,13 @@ run_status () { export GIT_INDEX_FILE fi - case "$branch" in - refs/heads/master) ;; - *) echo "# On branch $branch" ;; - esac - - if test -z "$IS_INITIAL" - then - git-diff-index -M --cached --name-status \ - --diff-filter=MDTCRA $REFERENCE | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - ' | - report "Updated but not checked in" "will commit" - committable="$?" - else - echo '# -# Initial commit -#' - git-ls-files | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - s/^/A / - ' | - report "Updated but not checked in" "will commit" - - committable="$?" - fi - - git-diff-files --name-status | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - ' | - report "Changed but not updated" \ - "use git-update-index to mark for commit" - - option="" - if test -z "$untracked_files"; then - option="--directory --no-empty-directory" - fi - hdr_shown= - if test -f "$GIT_DIR/info/exclude" - then - git-ls-files --others $option \ - --exclude-from="$GIT_DIR/info/exclude" \ - --exclude-per-directory=.gitignore - else - git-ls-files --others $option \ - --exclude-per-directory=.gitignore - fi | - while read line; do - if [ -z "$hdr_shown" ]; then - echo '#' - echo '# Untracked files:' - echo '# (use "git add" to add to commit)' - echo '#' - hdr_shown=1 - fi - echo "# $line" - done - - if test -n "$verbose" -a -z "$IS_INITIAL" - then - git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE - fi - case "$committable" in - 0) - case "$amend" in - t) - echo "# No changes" ;; - *) - echo "nothing to commit" ;; - esac - exit 1 ;; - esac - exit 0 - ) + case "$status_only" in + t) color= ;; + *) color=--nocolor ;; + esac + git-runstatus ${color} \ + ${verbose:+--verbose} \ + ${amend:+--amend} } trap ' diff --git a/git.c b/git.c index 335f405..495b39a 100644 --- a/git.c +++ b/git.c @@ -252,6 +252,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "rev-list", cmd_rev_list, RUN_SETUP }, { "rev-parse", cmd_rev_parse, RUN_SETUP }, { "rm", cmd_rm, RUN_SETUP }, + { "runstatus", cmd_runstatus, RUN_SETUP }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, diff --git a/wt-status.c b/wt-status.c new file mode 100644 index 0000000..ec2c728 --- /dev/null +++ b/wt-status.c @@ -0,0 +1,271 @@ +#include "wt-status.h" +#include "color.h" +#include "cache.h" +#include "object.h" +#include "dir.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "diffcore.h" + +int wt_status_use_color = 0; +static char wt_status_colors[][COLOR_MAXLEN] = { + "", /* WT_STATUS_HEADER: normal */ + "\033[32m", /* WT_STATUS_UPDATED: green */ + "\033[31m", /* WT_STATUS_CHANGED: red */ + "\033[31m", /* WT_STATUS_UNTRACKED: red */ +}; + +static int parse_status_slot(const char *var, int offset) +{ + if (!strcasecmp(var+offset, "header")) + return WT_STATUS_HEADER; + if (!strcasecmp(var+offset, "updated")) + return WT_STATUS_UPDATED; + if (!strcasecmp(var+offset, "changed")) + return WT_STATUS_CHANGED; + if (!strcasecmp(var+offset, "untracked")) + return WT_STATUS_UNTRACKED; + die("bad config variable '%s'", var); +} + +static const char* color(int slot) +{ + return wt_status_use_color ? wt_status_colors[slot] : ""; +} + +void wt_status_prepare(struct wt_status *s) +{ + unsigned char sha1[20]; + const char *head; + + s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0; + + head = resolve_ref(git_path("HEAD"), sha1, 0); + s->branch = head ? + strdup(head + strlen(get_git_dir()) + 1) : + NULL; + + s->reference = "HEAD"; + s->amend = 0; + s->verbose = 0; + s->commitable = 0; +} + +static void wt_status_print_header(const char *main, const char *sub) +{ + const char *c = color(WT_STATUS_HEADER); + color_printf_ln(c, "# %s:", main); + color_printf_ln(c, "# (%s)", sub); + color_printf_ln(c, "#"); +} + +static void wt_status_print_trailer(void) +{ + color_printf_ln(color(WT_STATUS_HEADER), "#"); +} + +static void wt_status_print_filepair(int t, struct diff_filepair *p) +{ + const char *c = color(t); + color_printf(color(WT_STATUS_HEADER), "#\t"); + switch (p->status) { + case DIFF_STATUS_ADDED: + color_printf(c, "new file: %s", p->one->path); break; + case DIFF_STATUS_COPIED: + color_printf(c, "copied: %s -> %s", + p->one->path, p->two->path); + break; + case DIFF_STATUS_DELETED: + color_printf_ln(c, "deleted: %s", p->one->path); break; + case DIFF_STATUS_MODIFIED: + color_printf(c, "modified: %s", p->one->path); break; + case DIFF_STATUS_RENAMED: + color_printf(c, "renamed: %s -> %s", + p->one->path, p->two->path); + break; + case DIFF_STATUS_TYPE_CHANGED: + color_printf(c, "typechange: %s", p->one->path); break; + case DIFF_STATUS_UNKNOWN: + color_printf(c, "unknown: %s", p->one->path); break; + case DIFF_STATUS_UNMERGED: + color_printf(c, "unmerged: %s", p->one->path); break; + default: + die("bug: unhandled diff status %c", p->status); + } + printf("\n"); +} + +static void wt_status_print_updated_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + struct wt_status *s = data; + int shown_header = 0; + int i; + if (q->nr) { + } + for (i = 0; i < q->nr; i++) { + if (q->queue[i]->status == 'U') + continue; + if (!shown_header) { + wt_status_print_header("Updated but not checked in", + "will commit"); + s->commitable = 1; + shown_header = 1; + } + wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]); + } + if (shown_header) + wt_status_print_trailer(); +} + +static void wt_status_print_changed_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + if (q->nr) + wt_status_print_header("Changed but not updated", + "use git-update-index to mark for commit"); + for (i = 0; i < q->nr; i++) + wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); + if (q->nr) + wt_status_print_trailer(); +} + +void wt_status_print_initial(struct wt_status *s) +{ + int i; + read_cache(); + if (active_nr) { + s->commitable = 1; + wt_status_print_header("Updated but not checked in", + "will commit"); + } + for (i = 0; i < active_nr; i++) { + color_printf(color(WT_STATUS_HEADER), "#\t"); + color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s", + active_cache[i]->name); + } + if (active_nr) + wt_status_print_trailer(); +} + +static void wt_status_print_updated(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL, NULL }; + argv[1] = s->reference; + init_revisions(&rev, NULL); + setup_revisions(2, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = wt_status_print_updated_cb; + rev.diffopt.format_callback_data = s; + rev.diffopt.detect_rename = 1; + run_diff_index(&rev, 1); +} + +static void wt_status_print_changed(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL }; + init_revisions(&rev, ""); + setup_revisions(1, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = wt_status_print_changed_cb; + rev.diffopt.format_callback_data = s; + run_diff_files(&rev, 0); +} + +static void wt_status_print_untracked(const struct wt_status *s) +{ + struct dir_struct dir; + const char *x; + int i; + int shown_header = 0; + + memset(&dir, 0, sizeof(dir)); + + dir.exclude_per_dir = ".gitignore"; + x = git_path("info/exclude"); + if (file_exists(x)) + add_excludes_from_file(&dir, x); + + read_directory(&dir, ".", "", 0); + for(i = 0; i < dir.nr; i++) { + /* check for matching entry, which is unmerged; lifted from + * builtin-ls-files:show_other_files */ + struct dir_entry *ent = dir.entries[i]; + int pos = cache_name_pos(ent->name, ent->len); + struct cache_entry *ce; + if (0 <= pos) + die("bug in wt_status_print_untracked"); + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == ent->len && + !memcmp(ce->name, ent->name, ent->len)) + continue; + } + if (!shown_header) { + wt_status_print_header("Untracked files", + "use \"git add\" to add to commit"); + shown_header = 1; + } + color_printf(color(WT_STATUS_HEADER), "#\t"); + color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s", + ent->len, ent->name); + } +} + +static void wt_status_print_verbose(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL, NULL }; + argv[1] = s->reference; + init_revisions(&rev, NULL); + setup_revisions(2, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + rev.diffopt.detect_rename = 1; + run_diff_index(&rev, 1); +} + +void wt_status_print(struct wt_status *s) +{ + if (s->branch && strcmp(s->branch, "refs/heads/master")) + color_printf_ln(color(WT_STATUS_HEADER), + "# On branch %s", s->branch); + + if (s->is_initial) { + color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit"); + color_printf_ln(color(WT_STATUS_HEADER), "#"); + wt_status_print_initial(s); + } + else { + wt_status_print_updated(s); + discard_cache(); + } + + wt_status_print_changed(s); + wt_status_print_untracked(s); + + if (s->verbose && !s->is_initial) + wt_status_print_verbose(s); + if (!s->commitable) + printf("%s\n", s->amend ? "# No changes" : "nothing to commit"); +} + +int git_status_config(const char *k, const char *v) +{ + if (!strcmp(k, "status.color")) { + wt_status_use_color = git_config_colorbool(k, v); + return 0; + } + if (!strncmp(k, "status.color.", 13)) { + int slot = parse_status_slot(k, 13); + color_parse(v, k, wt_status_colors[slot]); + } + return git_default_config(k, v); +} diff --git a/wt-status.h b/wt-status.h new file mode 100644 index 0000000..75d3cfe --- /dev/null +++ b/wt-status.h @@ -0,0 +1,24 @@ +#ifndef STATUS_H +#define STATUS_H + +enum color_wt_status { + WT_STATUS_HEADER, + WT_STATUS_UPDATED, + WT_STATUS_CHANGED, + WT_STATUS_UNTRACKED, +}; + +struct wt_status { + int is_initial; + char *branch; + const char *reference; + int commitable; + int verbose; + int amend; +}; + +int git_status_config(const char *var, const char *value); +void wt_status_prepare(struct wt_status *s); +void wt_status_print(struct wt_status *s); + +#endif /* STATUS_H */ -- cgit v0.10.2-6-g49f6 From 4df096a5ca24f2f39042c51cf51b8a2bec66a2b5 Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Thu, 7 Sep 2006 15:12:02 +0200 Subject: Add git-archive git-archive is a command to make TAR and ZIP archives of a git tree. It helps prevent a proliferation of git-{format}-tree commands. Instead of directly calling git-{tar,zip}-tree command, it defines a very simple API, that archiver should implement and register in "git-archive.c". This API is made up by 2 functions whose prototype is defined in "archive.h" file. - The first one is used to parse 'extra' parameters which have signification only for the specific archiver. That would allow different archive backends to have different kind of options. - The second one is used to ask to an archive backend to build the archive given some already resolved parameters. The main reason for making this API is to avoid using git-{tar,zip}-tree commands, hence making them useless. Maybe it's time for them to die ? It also implements remote operations by defining a very simple protocol: it first sends the name of the specific uploader followed the repository name (git-upload-tar git://example.org/repo.git). Then it sends options. It's done by sending a sequence of one argument per packet, with prefix "argument ", followed by a flush. The remote protocol is implemented in "git-archive.c" for client side and is triggered by "--remote=<repo>" option. For example, to fetch a TAR archive in a remote repo, you can issue: $ git archive --format=tar --remote=git://xxx/yyy/zzz.git HEAD We choose to not make a new command "git-fetch-archive" for example, avoind one more GIT command which should be nice for users (less commands to remember, keeps existing --remote option). Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Acked-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index 0d608fe..a3f33d4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ git-apply git-applymbox git-applypatch git-archimport +git-archive git-bisect git-branch git-cat-file diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt new file mode 100644 index 0000000..913528d --- /dev/null +++ b/Documentation/git-archive.txt @@ -0,0 +1,100 @@ +git-archive(1) +============== + +NAME +---- +git-archive - Creates a archive of the files in the named tree + + +SYNOPSIS +-------- +'git-archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>] + [--remote=<repo>] <tree-ish> [path...] + +DESCRIPTION +----------- +Creates an archive of the specified format containing the tree +structure for the named tree. If <prefix> is specified it is +prepended to the filenames in the archive. + +'git-archive' behaves differently when given a tree ID versus when +given a commit ID or tag ID. In the first case the current time is +used as modification time of each file in the archive. In the latter +case the commit time as recorded in the referenced commit object is +used instead. Additionally the commit ID is stored in a global +extended pax header if the tar format is used; it can be extracted +using 'git-get-tar-commit-id'. In ZIP files it is stored as a file +comment. + +OPTIONS +------- + +--format=<fmt>:: + Format of the resulting archive: 'tar', 'zip'... + +--list:: + Show all available formats. + +--prefix=<prefix>/:: + Prepend <prefix>/ to each filename in the archive. + +<extra>:: + This can be any options that the archiver backend understand. + +--remote=<repo>:: + Instead of making a tar archive from local repository, + retrieve a tar archive from a remote repository. + +<tree-ish>:: + The tree or commit to produce an archive for. + +path:: + If one or more paths are specified, include only these in the + archive, otherwise include all files and subdirectories. + +CONFIGURATION +------------- +By default, file and directories modes are set to 0666 or 0777 in tar +archives. It is possible to change this by setting the "umask" variable +in the repository configuration as follows : + +[tar] + umask = 002 ;# group friendly + +The special umask value "user" indicates that the user's current umask +will be used instead. The default value remains 0, which means world +readable/writable files and directories. + +EXAMPLES +-------- +git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -):: + + Create a tar archive that contains the contents of the + latest commit on the current branch, and extracts it in + `/var/tmp/junk` directory. + +git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz:: + + Create a compressed tarball for v1.4.0 release. + +git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz:: + + Create a compressed tarball for v1.4.0 release, but without a + global extended pax header. + +git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip:: + + Put everything in the current head's Documentation/ directory + into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'. + +Author +------ +Written by Franck Bui-Huu and Rene Scharfe. + +Documentation +-------------- +Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Makefile b/Makefile index 7b3114f..8f62cef 100644 --- a/Makefile +++ b/Makefile @@ -232,7 +232,7 @@ LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a LIB_H = \ - blob.h cache.h commit.h csum-file.h delta.h \ + archive.h blob.h cache.h commit.h csum-file.h delta.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h @@ -256,6 +256,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-add.o \ builtin-apply.o \ + builtin-archive.o \ builtin-cat-file.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ diff --git a/archive.h b/archive.h new file mode 100644 index 0000000..24b016f --- /dev/null +++ b/archive.h @@ -0,0 +1,41 @@ +#ifndef ARCHIVE_H +#define ARCHIVE_H + +#define MAX_EXTRA_ARGS 32 +#define MAX_ARGS (MAX_EXTRA_ARGS + 32) + +struct archiver_args { + const char *base; + struct tree *tree; + const unsigned char *commit_sha1; + time_t time; + const char **pathspec; + void *extra; +}; + +typedef int (*write_archive_fn_t)(struct archiver_args *); + +typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv); + +struct archiver { + const char *name; + const char *remote; + struct archiver_args args; + write_archive_fn_t write_archive; + parse_extra_args_fn_t parse_extra; +}; + +extern struct archiver archivers[]; + +extern int parse_archive_args(int argc, + const char **argv, + struct archiver *ar); + +extern void parse_treeish_arg(const char **treeish, + struct archiver_args *ar_args, + const char *prefix); + +extern void parse_pathspec_arg(const char **pathspec, + struct archiver_args *args); + +#endif /* ARCHIVE_H */ diff --git a/builtin-archive.c b/builtin-archive.c new file mode 100644 index 0000000..f6bc269 --- /dev/null +++ b/builtin-archive.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2006 Franck Bui-Huu + * Copyright (c) 2006 Rene Scharfe + */ +#include <time.h> +#include "cache.h" +#include "builtin.h" +#include "archive.h" +#include "commit.h" +#include "tree-walk.h" +#include "exec_cmd.h" +#include "pkt-line.h" + +static const char archive_usage[] = \ +"git-archive --format=<fmt> [--prefix=<prefix>/] [<extra>] <tree-ish> [path...]"; + +struct archiver archivers[] = { + { "" /* dummy */ }, +}; + +static int run_remote_archiver(struct archiver *ar, int argc, + const char **argv) +{ + char *url, buf[1024]; + int fd[2], i, len, rv; + pid_t pid; + + sprintf(buf, "git-upload-archive"); + + url = xstrdup(ar->remote); + pid = git_connect(fd, url, buf); + if (pid < 0) + return pid; + + for (i = 1; i < argc; i++) { + if (!strncmp(argv[i], "--remote=", 9)) + continue; + packet_write(fd[1], "argument %s\n", argv[i]); + } + packet_flush(fd[1]); + + len = packet_read_line(fd[0], buf, sizeof(buf)); + if (!len) + die("git-archive: expected ACK/NAK, got EOF"); + if (buf[len-1] == '\n') + buf[--len] = 0; + if (strcmp(buf, "ACK")) { + if (len > 5 && !strncmp(buf, "NACK ", 5)) + die("git-archive: NACK %s", buf + 5); + die("git-archive: protocol error"); + } + + len = packet_read_line(fd[0], buf, sizeof(buf)); + if (len) + die("git-archive: expected a flush"); + + /* Now, start reading from fd[0] and spit it out to stdout */ + rv = copy_fd(fd[0], 1); + + close(fd[0]); + rv |= finish_connect(pid); + + return !!rv; +} + +static int init_archiver(const char *name, struct archiver *ar) +{ + int rv = -1, i; + + for (i = 0; i < ARRAY_SIZE(archivers); i++) { + if (!strcmp(name, archivers[i].name)) { + memcpy(ar, &archivers[i], sizeof(struct archiver)); + rv = 0; + break; + } + } + return rv; +} + +void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args) +{ + ar_args->pathspec = get_pathspec(ar_args->base, pathspec); +} + +void parse_treeish_arg(const char **argv, struct archiver_args *ar_args, + const char *prefix) +{ + const char *name = argv[0]; + const unsigned char *commit_sha1; + time_t archive_time; + struct tree *tree; + struct commit *commit; + unsigned char sha1[20]; + + if (get_sha1(name, sha1)) + die("Not a valid object name"); + + commit = lookup_commit_reference_gently(sha1, 1); + if (commit) { + commit_sha1 = commit->object.sha1; + archive_time = commit->date; + } else { + commit_sha1 = NULL; + archive_time = time(NULL); + } + + tree = parse_tree_indirect(sha1); + if (tree == NULL) + die("not a tree object"); + + if (prefix) { + unsigned char tree_sha1[20]; + unsigned int mode; + int err; + + err = get_tree_entry(tree->object.sha1, prefix, + tree_sha1, &mode); + if (err || !S_ISDIR(mode)) + die("current working directory is untracked"); + + free(tree); + tree = parse_tree_indirect(tree_sha1); + } + ar_args->tree = tree; + ar_args->commit_sha1 = commit_sha1; + ar_args->time = archive_time; +} + +static const char *default_parse_extra(struct archiver *ar, + const char **argv) +{ + static char msg[64]; + + snprintf(msg, sizeof(msg) - 4, "'%s' format does not handle %s", + ar->name, *argv); + + return strcat(msg, "..."); +} + +int parse_archive_args(int argc, const char **argv, struct archiver *ar) +{ + const char *extra_argv[MAX_EXTRA_ARGS]; + int extra_argc = 0; + const char *format = NULL; /* might want to default to "tar" */ + const char *remote = NULL; + const char *base = ""; + int list = 0; + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "--list") || !strcmp(arg, "-l")) { + list = 1; + continue; + } + if (!strncmp(arg, "--format=", 9)) { + format = arg + 9; + continue; + } + if (!strncmp(arg, "--prefix=", 9)) { + base = arg + 9; + continue; + } + if (!strncmp(arg, "--remote=", 9)) { + remote = arg + 9; + continue; + } + if (!strcmp(arg, "--")) { + i++; + break; + } + if (arg[0] == '-') { + if (extra_argc > MAX_EXTRA_ARGS - 1) + die("Too many extra options"); + extra_argv[extra_argc++] = arg; + continue; + } + break; + } + + if (list) { + if (!remote) { + for (i = 0; i < ARRAY_SIZE(archivers); i++) + printf("%s\n", archivers[i].name); + exit(0); + } + die("--list and --remote are mutually exclusive"); + } + + if (argc - i < 1) + usage(archive_usage); + if (!format) + die("You must specify an archive format"); + if (init_archiver(format, ar) < 0) + die("Unknown archive format '%s'", format); + + if (extra_argc && !remote) { + if (!ar->parse_extra) { + die("%s", default_parse_extra(ar, extra_argv)); + } + ar->args.extra = ar->parse_extra(extra_argc, extra_argv); + } + ar->remote = remote; + ar->args.base = base; + + return i; +} + +int cmd_archive(int argc, const char **argv, const char *prefix) +{ + struct archiver ar; + int tree_idx; + + tree_idx = parse_archive_args(argc, argv, &ar); + + if (ar.remote) + return run_remote_archiver(&ar, argc, argv); + + if (prefix == NULL) + prefix = setup_git_directory(); + + argv += tree_idx; + parse_treeish_arg(argv, &ar.args, prefix); + parse_pathspec_arg(argv + 1, &ar.args); + + return ar.write_archive(&ar.args); +} diff --git a/builtin.h b/builtin.h index 25431d7..50852cd 100644 --- a/builtin.h +++ b/builtin.h @@ -15,6 +15,7 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); +extern int cmd_archive(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index ec1eda2..5450918 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -12,6 +12,7 @@ struct cmdname_help common_cmds[] = {" sort <<\EOF | add apply +archive bisect branch checkout diff --git a/git.c b/git.c index 335f405..8c64b27 100644 --- a/git.c +++ b/git.c @@ -220,6 +220,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) } commands[] = { { "add", cmd_add, RUN_SETUP }, { "apply", cmd_apply }, + { "archive", cmd_archive }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, -- cgit v0.10.2-6-g49f6 From efd8696cd7ccfa5042ef710e89fe0d10efdcd085 Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Thu, 7 Sep 2006 15:12:03 +0200 Subject: git-archive: wire up TAR format. This is based on Rene Scharfe's earlier patch, but uses the archiver support introduced by the previous patch. Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Acked-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/archive.h b/archive.h index 24b016f..5c3f29b 100644 --- a/archive.h +++ b/archive.h @@ -37,5 +37,9 @@ extern void parse_treeish_arg(const char **treeish, extern void parse_pathspec_arg(const char **pathspec, struct archiver_args *args); +/* + * Archive-format specific backends. + */ +extern int write_tar_archive(struct archiver_args *); #endif /* ARCHIVE_H */ diff --git a/builtin-archive.c b/builtin-archive.c index f6bc269..c6423b9 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -15,7 +15,7 @@ static const char archive_usage[] = \ "git-archive --format=<fmt> [--prefix=<prefix>/] [<extra>] <tree-ish> [path...]"; struct archiver archivers[] = { - { "" /* dummy */ }, + { .name = "tar", .write_archive = write_tar_archive }, }; static int run_remote_archiver(struct archiver *ar, int argc, diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index fa666f7..c20eb0e 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -9,6 +9,7 @@ #include "tar.h" #include "builtin.h" #include "pkt-line.h" +#include "archive.h" #define RECORDSIZE (512) #define BLOCKSIZE (RECORDSIZE * 20) @@ -338,6 +339,72 @@ static int generate_tar(int argc, const char **argv, const char *prefix) return 0; } +static int write_tar_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + static struct strbuf path; + int filenamelen = strlen(filename); + void *buffer; + char type[20]; + unsigned long size; + + if (!path.alloc) { + path.buf = xmalloc(PATH_MAX); + path.alloc = PATH_MAX; + path.len = path.eof = 0; + } + if (path.alloc < baselen + filenamelen) { + free(path.buf); + path.buf = xmalloc(baselen + filenamelen); + path.alloc = baselen + filenamelen; + } + memcpy(path.buf, base, baselen); + memcpy(path.buf + baselen, filename, filenamelen); + path.len = baselen + filenamelen; + if (S_ISDIR(mode)) { + strbuf_append_string(&path, "/"); + buffer = NULL; + size = 0; + } else { + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + } + + write_entry(sha1, &path, mode, buffer, size); + free(buffer); + + return READ_TREE_RECURSIVE; +} + +int write_tar_archive(struct archiver_args *args) +{ + int plen = strlen(args->base); + + git_config(git_tar_config); + + archive_time = args->time; + + if (args->commit_sha1) + write_global_extended_header(args->commit_sha1); + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = strdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_tar_entry); + write_trailer(); + + return 0; +} + static const char *exec = "git-upload-tar"; static int remote_tar(int argc, const char **argv) -- cgit v0.10.2-6-g49f6 From ec06bff5e6f76b46c22a3b2c97452568f088fa3c Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Thu, 7 Sep 2006 15:12:04 +0200 Subject: git-archive: wire up ZIP format. Again, this is based on Rene Scharfe's earlier patch, but uses the archiver support introduced by the previous patch. Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Acked-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/archive.h b/archive.h index 5c3f29b..f3d344b 100644 --- a/archive.h +++ b/archive.h @@ -41,5 +41,6 @@ extern void parse_pathspec_arg(const char **pathspec, * Archive-format specific backends. */ extern int write_tar_archive(struct archiver_args *); +extern int write_zip_archive(struct archiver_args *); #endif /* ARCHIVE_H */ diff --git a/builtin-archive.c b/builtin-archive.c index c6423b9..651d1bf 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -16,6 +16,7 @@ static const char archive_usage[] = \ struct archiver archivers[] = { { .name = "tar", .write_archive = write_tar_archive }, + { .name = "zip", .write_archive = write_zip_archive }, }; static int run_remote_archiver(struct archiver *ar, int argc, diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c index 1c1f683..3afb7bd 100644 --- a/builtin-zip-tree.c +++ b/builtin-zip-tree.c @@ -8,6 +8,7 @@ #include "tree.h" #include "quote.h" #include "builtin.h" +#include "archive.h" static const char zip_tree_usage[] = "git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]"; @@ -351,3 +352,30 @@ int cmd_zip_tree(int argc, const char **argv, const char *prefix) return 0; } + +int write_zip_archive(struct archiver_args *args) +{ + int plen = strlen(args->base); + + dos_time(&args->time, &zip_date, &zip_time); + + zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); + zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = strdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_zip_entry); + write_zip_trailer(args->commit_sha1); + + free(zip_dir); + + return 0; +} -- cgit v0.10.2-6-g49f6 From 39345a216ff37bda9fb7cec85f6de44069f5205d Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Thu, 7 Sep 2006 15:12:05 +0200 Subject: Add git-upload-archive This command implements the git archive protocol on the server side. This command is not intended to be used by the end user. Underlying git-archive command line options are sent over the protocol from "git-archive --remote=...", just like upload-tar currently does with "git-tar-tree=...". As for "git-archive" command implementation, this new command does not execute any existing "git-{tar,zip}-tree" but rely on the archive API defined by "git-archive" patch. Hence we get 2 good points: - "git-archive" and "git-upload-archive" share all option parsing code. - All kind of git-upload-{tar,zip} can be deprecated. Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index a3f33d4..90d6d7c 100644 --- a/.gitignore +++ b/.gitignore @@ -119,6 +119,7 @@ git-unpack-objects git-update-index git-update-ref git-update-server-info +git-upload-archive git-upload-pack git-upload-tar git-var diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt new file mode 100644 index 0000000..388bb53 --- /dev/null +++ b/Documentation/git-upload-archive.txt @@ -0,0 +1,37 @@ +git-upload-archive(1) +==================== + +NAME +---- +git-upload-archive - Send archive + + +SYNOPSIS +-------- +'git-upload-archive' <directory> + +DESCRIPTION +----------- +Invoked by 'git-archive --remote' and sends a generated archive to the +other end over the git protocol. + +This command is usually not invoked directly by the end user. The UI +for the protocol is on the 'git-archive' side, and the program pair +is meant to be used to get an archive from a remote repository. + +OPTIONS +------- +<directory>:: + The repository to get a tar archive from. + +Author +------ +Written by Franck Bui-Huu. + +Documentation +-------------- +Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Makefile b/Makefile index 8f62cef..4ac85fd 100644 --- a/Makefile +++ b/Makefile @@ -293,6 +293,7 @@ BUILTIN_OBJS = \ builtin-unpack-objects.o \ builtin-update-index.o \ builtin-update-ref.o \ + builtin-upload-archive.o \ builtin-upload-tar.o \ builtin-verify-pack.o \ builtin-write-tree.o \ diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c new file mode 100644 index 0000000..3bdb607 --- /dev/null +++ b/builtin-upload-archive.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2006 Franck Bui-Huu + */ +#include <time.h> +#include "cache.h" +#include "builtin.h" +#include "archive.h" +#include "pkt-line.h" + +static const char upload_archive_usage[] = + "git-upload-archive <repo>"; + + +int cmd_upload_archive(int argc, const char **argv, const char *prefix) +{ + struct archiver ar; + const char *sent_argv[MAX_ARGS]; + const char *arg_cmd = "argument "; + char *p, buf[4096]; + int treeish_idx; + int sent_argc; + int len; + + if (argc != 2) + usage(upload_archive_usage); + + if (strlen(argv[1]) > sizeof(buf)) + die("insanely long repository name"); + + strcpy(buf, argv[1]); /* enter-repo smudges its argument */ + + if (!enter_repo(buf, 0)) + die("not a git archive"); + + /* put received options in sent_argv[] */ + sent_argc = 1; + sent_argv[0] = "git-upload-archive"; + for (p = buf;;) { + /* This will die if not enough free space in buf */ + len = packet_read_line(0, p, (buf + sizeof buf) - p); + if (len == 0) + break; /* got a flush */ + if (sent_argc > MAX_ARGS - 2) + die("Too many options (>29)"); + + if (p[len-1] == '\n') { + p[--len] = 0; + } + if (len < strlen(arg_cmd) || + strncmp(arg_cmd, p, strlen(arg_cmd))) + die("'argument' token or flush expected"); + + len -= strlen(arg_cmd); + memmove(p, p + strlen(arg_cmd), len); + sent_argv[sent_argc++] = p; + p += len; + *p++ = 0; + } + sent_argv[sent_argc] = NULL; + + /* parse all options sent by the client */ + treeish_idx = parse_archive_args(sent_argc, sent_argv, &ar); + + parse_treeish_arg(sent_argv + treeish_idx, &ar.args, prefix); + parse_pathspec_arg(sent_argv + treeish_idx + 1, &ar.args); + + packet_write(1, "ACK\n"); + packet_flush(1); + + return ar.write_archive(&ar.args); +} + diff --git a/builtin.h b/builtin.h index 50852cd..6bfd2e7 100644 --- a/builtin.h +++ b/builtin.h @@ -57,6 +57,7 @@ extern int cmd_zip_tree(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); +extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); diff --git a/daemon.c b/daemon.c index b14d808..a2954a0 100644 --- a/daemon.c +++ b/daemon.c @@ -325,7 +325,14 @@ static int upload_pack(void) return -1; } +static int upload_archive(void) +{ + execl_git_cmd("upload-archive", ".", NULL); + return -1; +} + static struct daemon_service daemon_service[] = { + { "upload-archive", "uploadarch", upload_archive, 0, 1 }, { "upload-pack", "uploadpack", upload_pack, 1, 1 }, }; diff --git a/git.c b/git.c index 8c64b27..bcf3fc8 100644 --- a/git.c +++ b/git.c @@ -262,6 +262,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, + { "upload-archive", cmd_upload_archive }, { "upload-tar", cmd_upload_tar }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, -- cgit v0.10.2-6-g49f6 From 854c4168e77a692dc198311f04bf31939355f2a3 Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Sat, 9 Sep 2006 17:02:38 +0200 Subject: git-archive: make compression level of ZIP archives configurable Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/archive.h b/archive.h index f3d344b..d8cca73 100644 --- a/archive.h +++ b/archive.h @@ -42,5 +42,6 @@ extern void parse_pathspec_arg(const char **pathspec, */ extern int write_tar_archive(struct archiver_args *); extern int write_zip_archive(struct archiver_args *); +extern void *parse_extra_zip_args(int argc, const char **argv); #endif /* ARCHIVE_H */ diff --git a/builtin-archive.c b/builtin-archive.c index 651d1bf..b944737 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -15,8 +15,15 @@ static const char archive_usage[] = \ "git-archive --format=<fmt> [--prefix=<prefix>/] [<extra>] <tree-ish> [path...]"; struct archiver archivers[] = { - { .name = "tar", .write_archive = write_tar_archive }, - { .name = "zip", .write_archive = write_zip_archive }, + { + .name = "tar", + .write_archive = write_tar_archive, + }, + { + .name = "zip", + .write_archive = write_zip_archive, + .parse_extra = parse_extra_zip_args, + }, }; static int run_remote_archiver(struct archiver *ar, int argc, diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c index 3afb7bd..4e79633 100644 --- a/builtin-zip-tree.c +++ b/builtin-zip-tree.c @@ -379,3 +379,16 @@ int write_zip_archive(struct archiver_args *args) return 0; } + +void *parse_extra_zip_args(int argc, const char **argv) +{ + for (; argc > 0; argc--, argv++) { + const char *arg = argv[0]; + + if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') + zlib_compression_level = arg[1] - '0'; + else + die("Unknown argument for zip format: %s", arg); + } + return NULL; +} -- cgit v0.10.2-6-g49f6 From a41fae9c46a4cb5e59cc1a17d19f6a3a6cbfbb2f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 9 Sep 2006 22:21:27 -0700 Subject: get_sha1_hex() micro-optimization The function appeared high on a gprof output for a rev-list run of a non-trivial size, and it was an obvious low-hanging fruit. The code is from Linus. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/sha1_file.c b/sha1_file.c index 428d791..b64b92d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -26,15 +26,43 @@ const unsigned char null_sha1[20]; static unsigned int sha1_file_open_flag = O_NOATIME; -static unsigned hexval(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return ~0; +static inline unsigned int hexval(unsigned int c) +{ + static signed char val[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ + }; + return val[c]; } int get_sha1_hex(const char *hex, unsigned char *sha1) -- cgit v0.10.2-6-g49f6 From 37f944363d5b5fb5bcbf2d184865534739713c01 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 9 Sep 2006 23:48:03 -0700 Subject: archive: allow remote to have more formats than we understand. This fixes git-archive --remote not to parse archiver arguments; otherwise if the remote end implements formats other than the one known locally we will not be able to access that format. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/archive.h b/archive.h index d8cca73..e0782b9 100644 --- a/archive.h +++ b/archive.h @@ -19,7 +19,6 @@ typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv); struct archiver { const char *name; - const char *remote; struct archiver_args args; write_archive_fn_t write_archive; parse_extra_args_fn_t parse_extra; diff --git a/builtin-archive.c b/builtin-archive.c index b944737..c70488c 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -26,7 +26,7 @@ struct archiver archivers[] = { }, }; -static int run_remote_archiver(struct archiver *ar, int argc, +static int run_remote_archiver(const char *remote, int argc, const char **argv) { char *url, buf[1024]; @@ -35,16 +35,13 @@ static int run_remote_archiver(struct archiver *ar, int argc, sprintf(buf, "git-upload-archive"); - url = xstrdup(ar->remote); + url = xstrdup(remote); pid = git_connect(fd, url, buf); if (pid < 0) return pid; - for (i = 1; i < argc; i++) { - if (!strncmp(argv[i], "--remote=", 9)) - continue; + for (i = 1; i < argc; i++) packet_write(fd[1], "argument %s\n", argv[i]); - } packet_flush(fd[1]); len = packet_read_line(fd[0], buf, sizeof(buf)); @@ -150,17 +147,16 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) const char *extra_argv[MAX_EXTRA_ARGS]; int extra_argc = 0; const char *format = NULL; /* might want to default to "tar" */ - const char *remote = NULL; const char *base = ""; - int list = 0; int i; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--list") || !strcmp(arg, "-l")) { - list = 1; - continue; + for (i = 0; i < ARRAY_SIZE(archivers); i++) + printf("%s\n", archivers[i].name); + exit(0); } if (!strncmp(arg, "--format=", 9)) { format = arg + 9; @@ -170,10 +166,6 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) base = arg + 9; continue; } - if (!strncmp(arg, "--remote=", 9)) { - remote = arg + 9; - continue; - } if (!strcmp(arg, "--")) { i++; break; @@ -187,44 +179,67 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) break; } - if (list) { - if (!remote) { - for (i = 0; i < ARRAY_SIZE(archivers); i++) - printf("%s\n", archivers[i].name); - exit(0); - } - die("--list and --remote are mutually exclusive"); - } - - if (argc - i < 1) + /* We need at least one parameter -- tree-ish */ + if (argc - 1 < i) usage(archive_usage); if (!format) die("You must specify an archive format"); if (init_archiver(format, ar) < 0) die("Unknown archive format '%s'", format); - if (extra_argc && !remote) { - if (!ar->parse_extra) { + if (extra_argc) { + if (!ar->parse_extra) die("%s", default_parse_extra(ar, extra_argv)); - } ar->args.extra = ar->parse_extra(extra_argc, extra_argv); } - ar->remote = remote; ar->args.base = base; return i; } +static const char *remote_request(int *ac, const char **av) +{ + int ix, iy, cnt = *ac; + int no_more_options = 0; + const char *remote = NULL; + + for (ix = iy = 1; ix < cnt; ix++) { + const char *arg = av[ix]; + if (!strcmp(arg, "--")) + no_more_options = 1; + if (!no_more_options) { + if (!strncmp(arg, "--remote=", 9)) { + if (remote) + die("Multiple --remote specified"); + remote = arg + 9; + continue; + } + if (arg[0] != '-') + no_more_options = 1; + } + if (ix != iy) + av[iy] = arg; + iy++; + } + if (remote) { + av[--cnt] = NULL; + *ac = cnt; + } + return remote; +} + int cmd_archive(int argc, const char **argv, const char *prefix) { struct archiver ar; int tree_idx; + const char *remote = NULL; - tree_idx = parse_archive_args(argc, argv, &ar); - - if (ar.remote) - return run_remote_archiver(&ar, argc, argv); + remote = remote_request(&argc, argv); + if (remote) + return run_remote_archiver(remote, argc, argv); + memset(&ar, 0, sizeof(ar)); + tree_idx = parse_archive_args(argc, argv, &ar); if (prefix == NULL) prefix = setup_git_directory(); -- cgit v0.10.2-6-g49f6 From 49a52b1d1fb120f52de3a67f1e4e5ae81512ab81 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 10 Sep 2006 01:06:33 -0700 Subject: Move sideband client side support into reusable form. This moves the receiver side of the sideband support from fetch-clone.c to sideband.c and its header file, so that archiver protocol can use it. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 7b3114f..a46cd52 100644 --- a/Makefile +++ b/Makefile @@ -233,7 +233,7 @@ XDIFF_LIB=xdiff/lib.a LIB_H = \ blob.h cache.h commit.h csum-file.h delta.h \ - diff.h object.h pack.h pkt-line.h quote.h refs.h \ + diff.h object.h pack.h pkt-line.h quote.h refs.h sideband.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h @@ -245,7 +245,7 @@ DIFF_OBJS = \ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \ - object.o pack-check.o patch-delta.o path.o pkt-line.o \ + object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ diff --git a/fetch-clone.c b/fetch-clone.c index c5cf477..b62feac 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -1,6 +1,7 @@ #include "cache.h" #include "exec_cmd.h" #include "pkt-line.h" +#include "sideband.h" #include <sys/wait.h> #include <sys/time.h> @@ -114,36 +115,13 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) die("%s: unable to fork off sideband demultiplexer", me); if (!side_pid) { /* subprocess */ + char buf[DEFAULT_PACKET_MAX]; + close(fd[0]); if (xd[0] != xd[1]) close(xd[1]); - while (1) { - char buf[1024]; - int len = packet_read_line(xd[0], buf, sizeof(buf)); - if (len == 0) - break; - if (len < 1) - die("%s: protocol error: no band designator", - me); - len--; - switch (buf[0] & 0xFF) { - case 3: - safe_write(2, "remote: ", 8); - safe_write(2, buf+1, len); - safe_write(2, "\n", 1); - exit(1); - case 2: - safe_write(2, "remote: ", 8); - safe_write(2, buf+1, len); - continue; - case 1: - safe_write(fd[1], buf+1, len); - continue; - default: - die("%s: protocol error: bad band #%d", - me, (buf[0] & 0xFF)); - } - } + if (recv_sideband(me, xd[0], fd[1], 2, buf, sizeof(buf))) + exit(1); exit(0); } close(xd[0]); diff --git a/sideband.c b/sideband.c new file mode 100644 index 0000000..861f621 --- /dev/null +++ b/sideband.c @@ -0,0 +1,48 @@ +#include "pkt-line.h" +#include "sideband.h" + +/* + * Receive multiplexed output stream over git native protocol. + * in_stream is the input stream from the remote, which carries data + * in pkt_line format with band designator. Demultiplex it into out + * and err and return error appropriately. Band #1 carries the + * primary payload. Things coming over band #2 is not necessarily + * error; they are usually informative message on the standard error + * stream, aka "verbose"). A message over band #3 is a signal that + * the remote died unexpectedly. A flush() concludes the stream. + */ +int recv_sideband(const char *me, int in_stream, int out, int err, char *buf, int bufsz) +{ + while (1) { + int len = packet_read_line(in_stream, buf, bufsz); + if (len == 0) + break; + if (len < 1) { + len = sprintf(buf, "%s: protocol error: no band designator\n", me); + safe_write(err, buf, len); + return SIDEBAND_PROTOCOL_ERROR; + } + len--; + switch (buf[0] & 0xFF) { + case 3: + safe_write(err, "remote: ", 8); + safe_write(err, buf+1, len); + safe_write(err, "\n", 1); + return SIDEBAND_REMOTE_ERROR; + case 2: + safe_write(err, "remote: ", 8); + safe_write(err, buf+1, len); + continue; + case 1: + safe_write(out, buf+1, len); + continue; + default: + len = sprintf(buf + 1, + "%s: protocol error: bad band #%d\n", + me, buf[0] & 0xFF); + safe_write(err, buf+1, len); + return SIDEBAND_PROTOCOL_ERROR; + } + } + return 0; +} diff --git a/sideband.h b/sideband.h new file mode 100644 index 0000000..90b3855 --- /dev/null +++ b/sideband.h @@ -0,0 +1,11 @@ +#ifndef SIDEBAND_H +#define SIDEBAND_H + +#define SIDEBAND_PROTOCOL_ERROR -2 +#define SIDEBAND_REMOTE_ERROR -1 + +#define DEFAULT_PACKET_MAX 1000 + +int recv_sideband(const char *me, int in_stream, int out, int err, char *, int); + +#endif -- cgit v0.10.2-6-g49f6 From 958c24b1b8f463bca857f45c41a2f8198e345c2f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 10 Sep 2006 03:20:24 -0700 Subject: Move sideband server side support into reusable form. The server side support; this is just the very low level, and the caller needs to know which band it wants to send things out. Signed-off-by: Junio C Hamano <junkio@cox.net> (cherry picked from b786552b67878c7780c50def4c069d46dc54efbe commit) diff --git a/sideband.c b/sideband.c index 861f621..1b14ff8 100644 --- a/sideband.c +++ b/sideband.c @@ -46,3 +46,29 @@ int recv_sideband(const char *me, int in_stream, int out, int err, char *buf, in } return 0; } + +/* + * fd is connected to the remote side; send the sideband data + * over multiplexed packet stream. + */ +ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max) +{ + ssize_t ssz = sz; + const char *p = data; + + while (sz) { + unsigned n; + char hdr[5]; + + n = sz; + if (packet_max - 5 < n) + n = packet_max - 5; + sprintf(hdr, "%04x", n + 5); + hdr[4] = band; + safe_write(fd, hdr, 5); + safe_write(fd, p, n); + p += n; + sz -= n; + } + return ssz; +} diff --git a/sideband.h b/sideband.h index 90b3855..c645cf2 100644 --- a/sideband.h +++ b/sideband.h @@ -7,5 +7,6 @@ #define DEFAULT_PACKET_MAX 1000 int recv_sideband(const char *me, int in_stream, int out, int err, char *, int); +ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); #endif diff --git a/upload-pack.c b/upload-pack.c index 51ce936..1f2f7f7 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -4,6 +4,7 @@ #include "cache.h" #include "refs.h" #include "pkt-line.h" +#include "sideband.h" #include "tag.h" #include "object.h" #include "commit.h" @@ -33,45 +34,19 @@ static int strip(char *line, int len) return len; } -#define PACKET_MAX 1000 static ssize_t send_client_data(int fd, const char *data, ssize_t sz) { - ssize_t ssz; - const char *p; - - if (!data) { - if (!use_sideband) - return 0; - packet_flush(1); - } - - if (!use_sideband) { - if (fd == 3) - /* emergency quit */ - fd = 2; - if (fd == 2) { - xwrite(fd, data, sz); - return sz; - } - return safe_write(fd, data, sz); - } - p = data; - ssz = sz; - while (sz) { - unsigned n; - char hdr[5]; - - n = sz; - if (PACKET_MAX - 5 < n) - n = PACKET_MAX - 5; - sprintf(hdr, "%04x", n + 5); - hdr[4] = fd; - safe_write(1, hdr, 5); - safe_write(1, p, n); - p += n; - sz -= n; + if (use_sideband) + return send_sideband(1, fd, data, sz, DEFAULT_PACKET_MAX); + + if (fd == 3) + /* emergency quit */ + fd = 2; + if (fd == 2) { + xwrite(fd, data, sz); + return sz; } - return ssz; + return safe_write(fd, data, sz); } static void create_pack_file(void) @@ -308,7 +283,8 @@ static void create_pack_file(void) goto fail; fprintf(stderr, "flushed.\n"); } - send_client_data(1, NULL, 0); + if (use_sideband) + packet_flush(1); return; } fail: -- cgit v0.10.2-6-g49f6 From 326711c16879792da8d7159bf29d080569c3a1e0 Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Sun, 10 Sep 2006 18:10:01 +0200 Subject: Use xstrdup instead of strdup in builtin-{tar,zip}-tree.c Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> (cherry picked from 5d2aea4cb383a43e40d47ab69d8ad7a495df6ea2 commit) diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index c20eb0e..e8e492f 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -390,7 +390,7 @@ int write_tar_archive(struct archiver_args *args) write_global_extended_header(args->commit_sha1); if (args->base && plen > 0 && args->base[plen - 1] == '/') { - char *base = strdup(args->base); + char *base = xstrdup(args->base); int baselen = strlen(base); while (baselen > 0 && base[baselen - 1] == '/') diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c index 4e79633..fdac2bd 100644 --- a/builtin-zip-tree.c +++ b/builtin-zip-tree.c @@ -363,7 +363,7 @@ int write_zip_archive(struct archiver_args *args) zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; if (args->base && plen > 0 && args->base[plen - 1] == '/') { - char *base = strdup(args->base); + char *base = xstrdup(args->base); int baselen = strlen(base); while (baselen > 0 && base[baselen - 1] == '/') -- cgit v0.10.2-6-g49f6 From 8142f603b9955648228549d2e83ace7fbe834114 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 10 Sep 2006 04:16:39 -0700 Subject: archive: force line buffered output to stderr Otherwise the remote notification that comes with -v option can get clumped together. Signed-off-by: Junio C Hamano <junkio@cox.net> (cherry picked from a675cda60ead41f439b04bc69e0f19ace04e59d3 commit) diff --git a/builtin-archive.c b/builtin-archive.c index c70488c..3a8be57 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -238,6 +238,8 @@ int cmd_archive(int argc, const char **argv, const char *prefix) if (remote) return run_remote_archiver(remote, argc, argv); + setlinebuf(stderr); + memset(&ar, 0, sizeof(ar)); tree_idx = parse_archive_args(argc, argv, &ar); if (prefix == NULL) -- cgit v0.10.2-6-g49f6 From e0ffb24877d4530208905512f7c91dd8d71e2c95 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 9 Sep 2006 22:42:02 -0700 Subject: Add --verbose to git-archive And teach backends about it. Signed-off-by: Junio C Hamano <junkio@cox.net> (cherry picked from 9e2c44a2893ae90944a0b7c9f40a9d22b759b5c0 commit) diff --git a/archive.h b/archive.h index e0782b9..16dcdb8 100644 --- a/archive.h +++ b/archive.h @@ -10,6 +10,7 @@ struct archiver_args { const unsigned char *commit_sha1; time_t time; const char **pathspec; + unsigned int verbose : 1; void *extra; }; diff --git a/builtin-archive.c b/builtin-archive.c index 3a8be57..7544ad3 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -12,7 +12,7 @@ #include "pkt-line.h" static const char archive_usage[] = \ -"git-archive --format=<fmt> [--prefix=<prefix>/] [<extra>] <tree-ish> [path...]"; +"git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]"; struct archiver archivers[] = { { @@ -148,6 +148,7 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) int extra_argc = 0; const char *format = NULL; /* might want to default to "tar" */ const char *base = ""; + int verbose = 0; int i; for (i = 1; i < argc; i++) { @@ -158,6 +159,10 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) printf("%s\n", archivers[i].name); exit(0); } + if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) { + verbose = 1; + continue; + } if (!strncmp(arg, "--format=", 9)) { format = arg + 9; continue; @@ -192,6 +197,7 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) die("%s", default_parse_extra(ar, extra_argv)); ar->args.extra = ar->parse_extra(extra_argc, extra_argv); } + ar->args.verbose = verbose; ar->args.base = base; return i; diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index e8e492f..f2679a8 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -22,6 +22,7 @@ static unsigned long offset; static time_t archive_time; static int tar_umask; +static int verbose; /* writes out the whole block, but only if it is full */ static void write_if_needed(void) @@ -169,6 +170,8 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path, mode = 0100666; sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); } else { + if (verbose) + fprintf(stderr, "%.*s\n", path->len, path->buf); if (S_ISDIR(mode)) { *header.typeflag = TYPEFLAG_DIR; mode = (mode | 0777) & ~tar_umask; @@ -385,6 +388,7 @@ int write_tar_archive(struct archiver_args *args) git_config(git_tar_config); archive_time = args->time; + verbose = args->verbose; if (args->commit_sha1) write_global_extended_header(args->commit_sha1); diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c index fdac2bd..52d4b7a 100644 --- a/builtin-zip-tree.c +++ b/builtin-zip-tree.c @@ -13,6 +13,7 @@ static const char zip_tree_usage[] = "git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]"; +static int verbose; static int zip_date; static int zip_time; @@ -164,6 +165,8 @@ static int write_zip_entry(const unsigned char *sha1, crc = crc32(0, Z_NULL, 0); path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); + if (verbose) + fprintf(stderr, "%s\n", path); if (pathlen > 0xffff) { error("path too long (%d chars, SHA1: %s): %s", pathlen, sha1_to_hex(sha1), path); @@ -361,6 +364,7 @@ int write_zip_archive(struct archiver_args *args) zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; + verbose = args->verbose; if (args->base && plen > 0 && args->base[plen - 1] == '/') { char *base = xstrdup(args->base); -- cgit v0.10.2-6-g49f6 From fe5ab763f848cfcda22001f9280625f06c4c3760 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 10 Sep 2006 04:02:57 -0700 Subject: Teach --exec to git-archive --remote Some people needed --exec to specify the location of the upload-pack executable, because their default SSH log-in does not include the directory they have their own private copy of git on the $PATH. These people need to be able to say --exec to git-archive --remote for the same reason. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-archive.c b/builtin-archive.c index 7544ad3..dd7ffc0 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -32,16 +32,30 @@ static int run_remote_archiver(const char *remote, int argc, char *url, buf[1024]; int fd[2], i, len, rv; pid_t pid; + const char *exec = "git-upload-archive"; + int exec_at = 0; - sprintf(buf, "git-upload-archive"); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strncmp("--exec=", arg, 7)) { + if (exec_at) + die("multiple --exec specified"); + exec = arg + 7; + exec_at = i; + break; + } + } url = xstrdup(remote); - pid = git_connect(fd, url, buf); + pid = git_connect(fd, url, exec); if (pid < 0) return pid; - for (i = 1; i < argc; i++) + for (i = 1; i < argc; i++) { + if (i == exec_at) + continue; packet_write(fd[1], "argument %s\n", argv[i]); + } packet_flush(fd[1]); len = packet_read_line(fd[0], buf, sizeof(buf)); -- cgit v0.10.2-6-g49f6 From d47f3db75c58139cdcbca5cc63b17bf5db293b6a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 10 Sep 2006 16:27:08 -0700 Subject: Prepare larger packet buffer for upload-pack protocol. The original side-band support added to the upload-pack protocol used the default 1000-byte packet length. The pkt-line format allows up to 64k, so prepare the receiver for the maximum size, and have the uploader and downloader negotiate if larger packet length is allowed. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/fetch-clone.c b/fetch-clone.c index b62feac..b632ca0 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -115,7 +115,7 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) die("%s: unable to fork off sideband demultiplexer", me); if (!side_pid) { /* subprocess */ - char buf[DEFAULT_PACKET_MAX]; + char buf[LARGE_PACKET_MAX]; close(fd[0]); if (xd[0] != xd[1]) diff --git a/fetch-pack.c b/fetch-pack.c index 377fede..1b2d6ee 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -166,10 +166,11 @@ static int find_common(int fd[2], unsigned char *result_sha1, } if (!fetching) - packet_write(fd[1], "want %s%s%s%s\n", + packet_write(fd[1], "want %s%s%s%s%s\n", sha1_to_hex(remote), (multi_ack ? " multi_ack" : ""), - (use_sideband ? " side-band" : ""), + (use_sideband == 2 ? " side-band-64k" : ""), + (use_sideband == 1 ? " side-band" : ""), (use_thin_pack ? " thin-pack" : "")); else packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); @@ -426,7 +427,12 @@ static int fetch_pack(int fd[2], int nr_match, char **match) fprintf(stderr, "Server supports multi_ack\n"); multi_ack = 1; } - if (server_supports("side-band")) { + if (server_supports("side-band-64k")) { + if (verbose) + fprintf(stderr, "Server supports side-band-64k\n"); + use_sideband = 2; + } + else if (server_supports("side-band")) { if (verbose) fprintf(stderr, "Server supports side-band\n"); use_sideband = 1; diff --git a/sideband.h b/sideband.h index c645cf2..4872106 100644 --- a/sideband.h +++ b/sideband.h @@ -5,6 +5,7 @@ #define SIDEBAND_REMOTE_ERROR -1 #define DEFAULT_PACKET_MAX 1000 +#define LARGE_PACKET_MAX 65520 int recv_sideband(const char *me, int in_stream, int out, int err, char *, int); ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); diff --git a/upload-pack.c b/upload-pack.c index 1f2f7f7..b673d8c 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -20,6 +20,9 @@ static int use_thin_pack; static struct object_array have_obj; static struct object_array want_obj; static unsigned int timeout; +/* 0 for no sideband, + * otherwise maximum packet size (up to 65520 bytes). + */ static int use_sideband; static void reset_timeout(void) @@ -37,8 +40,7 @@ static int strip(char *line, int len) static ssize_t send_client_data(int fd, const char *data, ssize_t sz) { if (use_sideband) - return send_sideband(1, fd, data, sz, DEFAULT_PACKET_MAX); - + return send_sideband(1, fd, data, sz, use_sideband); if (fd == 3) /* emergency quit */ fd = 2; @@ -389,8 +391,10 @@ static void receive_needs(void) multi_ack = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; - if (strstr(line+45, "side-band")) - use_sideband = 1; + if (strstr(line+45, "side-band-64k")) + use_sideband = LARGE_PACKET_MAX; + else if (strstr(line+45, "side-band")) + use_sideband = DEFAULT_PACKET_MAX; /* We have sent all our refs already, and the other end * should have chosen out of them; otherwise they are @@ -412,7 +416,7 @@ static void receive_needs(void) static int send_ref(const char *refname, const unsigned char *sha1) { - static const char *capabilities = "multi_ack thin-pack side-band"; + static const char *capabilities = "multi_ack thin-pack side-band side-band-64k"; struct object *o = parse_object(sha1); if (!o) -- cgit v0.10.2-6-g49f6 From 23d6d112c004d4242f9dbd8161f79ccdeb47bde8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 10 Sep 2006 03:33:34 -0700 Subject: Add sideband status report to git-archive protocol Using the refactored sideband code from existing upload-pack protocol, this lets the error condition and status output sent from the remote process to be shown locally. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-archive.c b/builtin-archive.c index dd7ffc0..cb883df 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -10,6 +10,7 @@ #include "tree-walk.h" #include "exec_cmd.h" #include "pkt-line.h" +#include "sideband.h" static const char archive_usage[] = \ "git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]"; @@ -29,7 +30,7 @@ struct archiver archivers[] = { static int run_remote_archiver(const char *remote, int argc, const char **argv) { - char *url, buf[1024]; + char *url, buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; pid_t pid; const char *exec = "git-upload-archive"; @@ -74,8 +75,7 @@ static int run_remote_archiver(const char *remote, int argc, die("git-archive: expected a flush"); /* Now, start reading from fd[0] and spit it out to stdout */ - rv = copy_fd(fd[0], 1); - + rv = recv_sideband("archive", fd[0], 1, 2, buf, sizeof(buf)); close(fd[0]); rv |= finish_connect(pid); diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index 3bdb607..42cb9f8 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -6,12 +6,18 @@ #include "builtin.h" #include "archive.h" #include "pkt-line.h" +#include "sideband.h" +#include <sys/wait.h> +#include <sys/poll.h> static const char upload_archive_usage[] = "git-upload-archive <repo>"; +static const char deadchild[] = +"git-upload-archive: archiver died with error"; -int cmd_upload_archive(int argc, const char **argv, const char *prefix) + +static int run_upload_archive(int argc, const char **argv, const char *prefix) { struct archiver ar; const char *sent_argv[MAX_ARGS]; @@ -64,9 +70,89 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) parse_treeish_arg(sent_argv + treeish_idx, &ar.args, prefix); parse_pathspec_arg(sent_argv + treeish_idx + 1, &ar.args); + return ar.write_archive(&ar.args); +} + +int cmd_upload_archive(int argc, const char **argv, const char *prefix) +{ + pid_t writer; + int fd1[2], fd2[2]; + /* + * Set up sideband subprocess. + * + * We (parent) monitor and read from child, sending its fd#1 and fd#2 + * multiplexed out to our fd#1. If the child dies, we tell the other + * end over channel #3. + */ + if (pipe(fd1) < 0 || pipe(fd2) < 0) { + int err = errno; + packet_write(1, "NACK pipe failed on the remote side\n"); + die("upload-archive: %s", strerror(err)); + } + writer = fork(); + if (writer < 0) { + int err = errno; + packet_write(1, "NACK fork failed on the remote side\n"); + die("upload-archive: %s", strerror(err)); + } + if (!writer) { + /* child - connect fd#1 and fd#2 to the pipe */ + dup2(fd1[1], 1); + dup2(fd2[1], 2); + close(fd1[1]); close(fd2[1]); + close(fd1[0]); close(fd2[0]); /* we do not read from pipe */ + + exit(run_upload_archive(argc, argv, prefix)); + } + + /* parent - read from child, multiplex and send out to fd#1 */ + close(fd1[1]); close(fd2[1]); /* we do not write to pipe */ packet_write(1, "ACK\n"); packet_flush(1); - return ar.write_archive(&ar.args); -} + while (1) { + struct pollfd pfd[2]; + char buf[16384]; + ssize_t sz; + pid_t pid; + int status; + + pfd[0].fd = fd1[0]; + pfd[0].events = POLLIN; + pfd[1].fd = fd2[0]; + pfd[1].events = POLLIN; + if (poll(pfd, 2, -1) < 0) { + if (errno != EINTR) { + error("poll failed resuming: %s", + strerror(errno)); + sleep(1); + } + continue; + } + if (pfd[0].revents & (POLLIN|POLLHUP)) { + /* Data stream ready */ + sz = read(pfd[0].fd, buf, sizeof(buf)); + send_sideband(1, 1, buf, sz, LARGE_PACKET_MAX); + } + if (pfd[1].revents & (POLLIN|POLLHUP)) { + /* Status stream ready */ + sz = read(pfd[1].fd, buf, sizeof(buf)); + send_sideband(1, 2, buf, sz, LARGE_PACKET_MAX); + } + if (((pfd[0].revents | pfd[1].revents) & POLLHUP) == 0) + continue; + /* did it die? */ + pid = waitpid(writer, &status, WNOHANG); + if (!pid) { + fprintf(stderr, "Hmph, HUP?\n"); + continue; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + send_sideband(1, 3, deadchild, strlen(deadchild), + LARGE_PACKET_MAX); + packet_flush(1); + break; + } + return 0; +} -- cgit v0.10.2-6-g49f6 From 04f7a94f65f40d24607ba9af77756e121b7c8e4f Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 11 Sep 2006 00:29:27 +0200 Subject: gitweb: Make pickaxe search a feature As pickaxe search (selected using undocumented 'pickaxe:' operator in search query) is resource consuming, allow to turn it on/off using feature meachanism. Turned on by default, for historical reasons. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d89f709..53e3478 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -90,6 +90,11 @@ our %feature = ( 'override' => 0, # => [content-encoding, suffix, program] 'default' => ['x-gzip', 'gz', 'gzip']}, + + 'pickaxe' => { + 'sub' => \&feature_pickaxe, + 'override' => 0, + 'default' => [1]}, ); sub gitweb_check_feature { @@ -143,6 +148,24 @@ sub feature_snapshot { return ($ctype, $suffix, $command); } +# To enable system wide have in $GITWEB_CONFIG +# $feature{'pickaxe'}{'default'} = [1]; +# To have project specific config enable override in $GITWEB_CONFIG +# $feature{'pickaxe'}{'override'} = 1; +# and in project config gitweb.pickaxe = 0|1; + +sub feature_pickaxe { + my ($val) = git_get_project_config('pickaxe', '--bool'); + + if ($val eq 'true') { + return (1); + } elsif ($val eq 'false') { + return (0); + } + + return ($_[0]); +} + # rename detection options for git-diff and git-diff-tree # - default is '-M', with the cost proportional to # (number of removed files) * (number of new files). @@ -3128,8 +3151,7 @@ sub git_search { if (!%co) { die_error(undef, "Unknown commit object"); } - # pickaxe may take all resources of your box and run for several minutes - # with every query - so decide by yourself how public you make this feature :) + my $commit_search = 1; my $author_search = 0; my $committer_search = 0; @@ -3141,6 +3163,13 @@ sub git_search { } elsif ($searchtext =~ s/^pickaxe\\://i) { $commit_search = 0; $pickaxe_search = 1; + + # pickaxe may take all resources of your box and run for several minutes + # with every query - so decide by yourself how public you make this feature + my ($have_pickaxe) = gitweb_check_feature('pickaxe'); + if (!$have_pickaxe) { + die_error('403 Permission denied', "Permission denied"); + } } git_header_html(); git_print_page_nav('','', $hash,$co{'tree'},$hash); -- cgit v0.10.2-6-g49f6 From 8be683520e5a00cb7bd67acfd71d9346c33305b2 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Mon, 11 Sep 2006 00:36:04 +0200 Subject: gitweb: Paginate history output git_history output is now divided into pages, like git_shortlog, git_tags and git_heads output. As whole git-rev-list output is now read into array before writing anything, it allows for better signaling of errors. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 53e3478..c3544dd 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1933,12 +1933,15 @@ sub git_shortlog_body { sub git_history_body { # Warning: assumes constant type (blob or tree) during history - my ($fd, $refs, $hash_base, $ftype, $extra) = @_; + my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_; + + $from = 0 unless defined $from; + $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist}); print "<table class=\"history\" cellspacing=\"0\">\n"; my $alternate = 0; - while (my $line = <$fd>) { - if ($line !~ m/^([0-9a-fA-F]{40})/) { + for (my $i = $from; $i <= $to; $i++) { + if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) { next; } @@ -3114,29 +3117,70 @@ sub git_history { if (!defined $hash_base) { $hash_base = git_get_head_hash($project); } + if (!defined $page) { + $page = 0; + } my $ftype; my %co = parse_commit($hash_base); if (!%co) { die_error(undef, "Unknown commit object"); } + my $refs = git_get_references(); - git_header_html(); - git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base); - git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + my $limit = sprintf("--max-count=%i", (100 * ($page+1))); + if (!defined $hash && defined $file_name) { $hash = git_get_hash_by_path($hash_base, $file_name); } if (defined $hash) { $ftype = git_get_type($hash); } - git_print_page_path($file_name, $ftype, $hash_base); open my $fd, "-|", - git_cmd(), "rev-list", "--full-history", $hash_base, "--", $file_name; + git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name + or die_error(undef, "Open git-rev-list-failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-rev-list failed"); + + my $paging_nav = ''; + if ($page > 0) { + $paging_nav .= + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name)}, + "first"); + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name, page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= "first"; + $paging_nav .= " ⋅ prev"; + } + if ($#revlist >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + my $next_link = ''; + if ($#revlist >= (100 * ($page+1)-1)) { + $next_link = + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name, page=>$page+1), + -title => "Alt-n"}, "next"); + } - git_history_body($fd, $refs, $hash_base, $ftype); + git_header_html(); + git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, $ftype, $hash_base); + + git_history_body(\@revlist, ($page * 100), $#revlist, + $refs, $hash_base, $ftype, $next_link); - close $fd; git_footer_html(); } -- cgit v0.10.2-6-g49f6 From 86257aa324d04694408ad806989690979456f29a Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Mon, 11 Sep 2006 06:59:22 +0200 Subject: Move add_to_string to "quote.c" and make it extern. So that this function may be used in places other than "rsh.c". Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/quote.c b/quote.c index a38786c..e3a4d4a 100644 --- a/quote.c +++ b/quote.c @@ -106,6 +106,35 @@ char *sq_quote_argv(const char** argv, int count) return buf; } +/* + * Append a string to a string buffer, with or without shell quoting. + * Return true if the buffer overflowed. + */ +int add_to_string(char **ptrp, int *sizep, const char *str, int quote) +{ + char *p = *ptrp; + int size = *sizep; + int oc; + int err = 0; + + if (quote) + oc = sq_quote_buf(p, size, str); + else { + oc = strlen(str); + memcpy(p, str, (size <= oc) ? size - 1 : oc); + } + + if (size <= oc) { + err = 1; + oc = size - 1; + } + + *ptrp += oc; + **ptrp = '\0'; + *sizep -= oc; + return err; +} + char *sq_dequote(char *arg) { char *dst = arg; diff --git a/quote.h b/quote.h index a6c4611..1a29e79 100644 --- a/quote.h +++ b/quote.h @@ -33,6 +33,12 @@ extern void sq_quote_print(FILE *stream, const char *src); extern size_t sq_quote_buf(char *dst, size_t n, const char *src); extern char *sq_quote_argv(const char** argv, int count); +/* + * Append a string to a string buffer, with or without shell quoting. + * Return true if the buffer overflowed. + */ +extern int add_to_string(char **ptrp, int *sizep, const char *str, int quote); + /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have * produced. diff --git a/rsh.c b/rsh.c index 07166ad..f34409e 100644 --- a/rsh.c +++ b/rsh.c @@ -8,36 +8,7 @@ #define COMMAND_SIZE 4096 -/* - * Append a string to a string buffer, with or without shell quoting. - * Return true if the buffer overflowed. - */ -static int add_to_string(char **ptrp, int *sizep, const char *str, int quote) -{ - char *p = *ptrp; - int size = *sizep; - int oc; - int err = 0; - - if ( quote ) { - oc = sq_quote_buf(p, size, str); - } else { - oc = strlen(str); - memcpy(p, str, (oc >= size) ? size-1 : oc); - } - - if ( oc >= size ) { - err = 1; - oc = size-1; - } - - *ptrp += oc; - **ptrp = '\0'; - *sizep -= oc; - return err; -} - -int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, +int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, char *url, int rmt_argc, char **rmt_argv) { char *host; -- cgit v0.10.2-6-g49f6 From 0f503d77ac9032fbfbd5f3bacafeccbcf408b31f Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Mon, 11 Sep 2006 07:04:50 +0200 Subject: Fix a memory leak in "connect.c" and die if command too long. Use "add_to_string" instead of "sq_quote" and "snprintf", so that there is no memory allocation and no memory leak. Also check if the command is too long to fit into the buffer and die if this is the case, instead of truncating it to the buffer size. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/connect.c b/connect.c index 1c6429b..49251f9 100644 --- a/connect.c +++ b/connect.c @@ -599,12 +599,13 @@ static void git_proxy_connect(int fd[2], char *host) close(pipefd[1][0]); } +#define MAX_CMD_LEN 1024 + /* * Yeah, yeah, fixme. Need to pass in the heads etc. */ int git_connect(int fd[2], char *url, const char *prog) { - char command[1024]; char *host, *path = url; char *end; int c; @@ -697,8 +698,18 @@ int git_connect(int fd[2], char *url, const char *prog) if (pid < 0) die("unable to fork"); if (!pid) { - snprintf(command, sizeof(command), "%s %s", prog, - sq_quote(path)); + char command[MAX_CMD_LEN]; + char *posn = command; + int size = MAX_CMD_LEN; + int of = 0; + + of |= add_to_string(&posn, &size, prog, 0); + of |= add_to_string(&posn, &size, " ", 0); + of |= add_to_string(&posn, &size, path, 1); + + if (of) + die("command line too long"); + dup2(pipefd[1][0], 0); dup2(pipefd[0][1], 1); close(pipefd[0][0]); -- cgit v0.10.2-6-g49f6 From f42a5c4eb0453cd33276e078cd7541b1ef25b2c4 Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Tue, 12 Sep 2006 11:00:13 +0200 Subject: connect.c: finish_connect(): allow null pid parameter git_connect() can return 0 if we use git protocol for example. Users of this function don't know and don't care if a process had been created or not, and to avoid them to check it before calling finish_connect() this patch allows finish_connect() to take a null pid. And in that case return 0. [jc: updated function signature of git_connect() with a comment on its return value. ] Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/cache.h b/cache.h index a53204f..8d09997 100644 --- a/cache.h +++ b/cache.h @@ -359,7 +359,7 @@ struct ref { #define REF_HEADS (1u << 1) #define REF_TAGS (1u << 2) -extern int git_connect(int fd[2], char *url, const char *prog); +extern pid_t git_connect(int fd[2], char *url, const char *prog); extern int finish_connect(pid_t pid); extern int path_match(const char *path, int nr, char **match); extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, diff --git a/connect.c b/connect.c index 49251f9..c55a20a 100644 --- a/connect.c +++ b/connect.c @@ -602,9 +602,15 @@ static void git_proxy_connect(int fd[2], char *host) #define MAX_CMD_LEN 1024 /* - * Yeah, yeah, fixme. Need to pass in the heads etc. + * This returns 0 if the transport protocol does not need fork(2), + * or a process id if it does. Once done, finish the connection + * with finish_connect() with the value returned from this function + * (it is safe to call finish_connect() with 0 to support the former + * case). + * + * Does not return a negative value on error; it just dies. */ -int git_connect(int fd[2], char *url, const char *prog) +pid_t git_connect(int fd[2], char *url, const char *prog) { char *host, *path = url; char *end; @@ -748,6 +754,9 @@ int git_connect(int fd[2], char *url, const char *prog) int finish_connect(pid_t pid) { + if (pid == 0) + return 0; + while (waitpid(pid, NULL, 0) < 0) { if (errno != EINTR) return -1; -- cgit v0.10.2-6-g49f6 From 6d2489235f5430071b8df2281c5f9ce00097bb31 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Tue, 12 Sep 2006 06:43:08 +0200 Subject: Fix space in string " false" problem in "trace.c". Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/trace.c b/trace.c index ce01c34..f9efc91 100644 --- a/trace.c +++ b/trace.c @@ -55,7 +55,7 @@ static int get_trace_fd(int *need_close) { char *trace = getenv("GIT_TRACE"); - if (!trace || !strcmp(trace, "0") || !strcasecmp(trace," false")) + if (!trace || !strcmp(trace, "0") || !strcasecmp(trace, "false")) return 0; if (!strcmp(trace, "1") || !strcasecmp(trace, "true")) return STDERR_FILENO; -- cgit v0.10.2-6-g49f6 From d3788e19e20cd14aeac99d1f294d9a368437284f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 12 Sep 2006 00:26:57 -0700 Subject: upload-archive: monitor child communication more carefully. Franck noticed that the code around polling and relaying messages from the child process was quite bogus. Here is an attempt to clean it up a bit, based on his patch: - When POLLHUP is set, it goes ahead and reads the file descriptor. Worse yet, it does not check the return value of read() for errors when it does. - When we processed one POLLIN, we should just go back and see if any more data is available. We can check if the child is still there when poll gave control back at us but without any actual input. [jc: with simplification suggested by Franck. ] Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index 42cb9f8..115a12d 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -16,6 +16,9 @@ static const char upload_archive_usage[] = static const char deadchild[] = "git-upload-archive: archiver died with error"; +static const char lostchild[] = +"git-upload-archive: archiver process was lost"; + static int run_upload_archive(int argc, const char **argv, const char *prefix) { @@ -73,6 +76,31 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix) return ar.write_archive(&ar.args); } +static void error_clnt(const char *fmt, ...) +{ + char buf[1024]; + va_list params; + int len; + + va_start(params, fmt); + len = vsprintf(buf, fmt, params); + va_end(params); + send_sideband(1, 3, buf, len, LARGE_PACKET_MAX); + die("sent error to the client: %s", buf); +} + +static void process_input(int child_fd, int band) +{ + char buf[16384]; + ssize_t sz = read(child_fd, buf, sizeof(buf)); + if (sz < 0) { + if (errno != EINTR) + error_clnt("read error: %s\n", strerror(errno)); + return; + } + send_sideband(1, band, buf, sz, LARGE_PACKET_MAX); +} + int cmd_upload_archive(int argc, const char **argv, const char *prefix) { pid_t writer; @@ -112,9 +140,6 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) while (1) { struct pollfd pfd[2]; - char buf[16384]; - ssize_t sz; - pid_t pid; int status; pfd[0].fd = fd1[0]; @@ -129,28 +154,19 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) } continue; } - if (pfd[0].revents & (POLLIN|POLLHUP)) { + if (pfd[0].revents & POLLIN) /* Data stream ready */ - sz = read(pfd[0].fd, buf, sizeof(buf)); - send_sideband(1, 1, buf, sz, LARGE_PACKET_MAX); - } - if (pfd[1].revents & (POLLIN|POLLHUP)) { + process_input(pfd[0].fd, 1); + if (pfd[1].revents & POLLIN) /* Status stream ready */ - sz = read(pfd[1].fd, buf, sizeof(buf)); - send_sideband(1, 2, buf, sz, LARGE_PACKET_MAX); - } - - if (((pfd[0].revents | pfd[1].revents) & POLLHUP) == 0) - continue; - /* did it die? */ - pid = waitpid(writer, &status, WNOHANG); - if (!pid) { - fprintf(stderr, "Hmph, HUP?\n"); + process_input(pfd[1].fd, 2); + if ((pfd[0].revents | pfd[1].revents) == POLLIN) continue; - } - if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) - send_sideband(1, 3, deadchild, strlen(deadchild), - LARGE_PACKET_MAX); + + if (waitpid(writer, &status, 0) < 0) + error_clnt("%s", lostchild); + else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + error_clnt("%s", deadchild); packet_flush(1); break; } -- cgit v0.10.2-6-g49f6 From d751864cf7f5253da1ce749cf71215466590037f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 12 Sep 2006 22:42:31 -0700 Subject: builtin-archive.c: rename remote_request() to extract_remote_arg() Suggested by Franck, and I think it makes sense. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-archive.c b/builtin-archive.c index cb883df..da3f714 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -217,7 +217,7 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) return i; } -static const char *remote_request(int *ac, const char **av) +static const char *extract_remote_arg(int *ac, const char **av) { int ix, iy, cnt = *ac; int no_more_options = 0; @@ -254,7 +254,7 @@ int cmd_archive(int argc, const char **argv, const char *prefix) int tree_idx; const char *remote = NULL; - remote = remote_request(&argc, argv); + remote = extract_remote_arg(&argc, argv); if (remote) return run_remote_archiver(remote, argc, argv); -- cgit v0.10.2-6-g49f6 From 4321134cd85eabc5d4d07a7ce00d4cf6a02c38fb Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 12 Sep 2006 22:59:15 -0700 Subject: pack-objects: document --revs, --unpacked and --all. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 4991f88..d4661dd 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] - {--stdout | base-name} < object-list + [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list DESCRIPTION @@ -56,6 +56,24 @@ base-name:: Write the pack contents (what would have been written to .pack file) out to the standard output. +--revs:: + Read the revision arguments from the standard input, instead of + individual object names. The revision arguments are processed + the same way as gitlink:git-rev-list[1] with `--objects` flag + uses its `commit` arguments to build the list of objects it + outputs. The objects on the resulting list are packed. + +--unpacked:: + This implies `--revs`. When processing the list of + revision arguments read from the standard input, limit + the objects packed to those that are not already packed. + +--all:: + This implies `--revs`. In addition to the list of + revision arguments read from the standard input, pretend + as if all refs under `$GIT_DIR/refs` are specifed to be + included. + --window and --depth:: These two options affects how the objects contained in the pack are stored using delta compression. The @@ -103,6 +121,7 @@ Documentation by Junio C Hamano See Also -------- +gitlink:git-rev-list[1] gitlink:git-repack[1] gitlink:git-prune-packed[1] diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 753dd9a..8d7a120 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -15,7 +15,7 @@ #include <sys/time.h> #include <signal.h> -static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} [--revs [--unpacked | --all]* <ref-list | <object-list]"; +static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]"; struct object_entry { unsigned char sha1[20]; -- cgit v0.10.2-6-g49f6 From 2074cb0af339f586cab6e0cdc20c4eadf3ba93e8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Tue, 12 Sep 2006 22:45:12 +0200 Subject: Teach runstatus about --untracked Actually, teach runstatus what to do if it is not passed; it should not list the contents of completely untracked directories, but only the name of that directory (plus a trailing '/'). [jc: with comments by Jeff King to match hide-empty-directories behaviour of the original.] Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-runstatus.c b/builtin-runstatus.c index 7979d61..303c556 100644 --- a/builtin-runstatus.c +++ b/builtin-runstatus.c @@ -25,6 +25,8 @@ int cmd_runstatus(int argc, const char **argv, const char *prefix) } else if (!strcmp(argv[i], "--verbose")) s.verbose = 1; + else if (!strcmp(argv[i], "--untracked")) + s.untracked = 1; else usage(runstatus_usage); } diff --git a/git-commit.sh b/git-commit.sh index 10c269a..5a4c659 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -82,7 +82,8 @@ run_status () { esac git-runstatus ${color} \ ${verbose:+--verbose} \ - ${amend:+--amend} + ${amend:+--amend} \ + ${untracked_files:+--untracked} } trap ' diff --git a/wt-status.c b/wt-status.c index ec2c728..c644331 100644 --- a/wt-status.c +++ b/wt-status.c @@ -50,6 +50,7 @@ void wt_status_prepare(struct wt_status *s) s->amend = 0; s->verbose = 0; s->commitable = 0; + s->untracked = 0; } static void wt_status_print_header(const char *main, const char *sub) @@ -188,6 +189,10 @@ static void wt_status_print_untracked(const struct wt_status *s) memset(&dir, 0, sizeof(dir)); dir.exclude_per_dir = ".gitignore"; + if (!s->untracked) { + dir.show_other_directories = 1; + dir.hide_empty_directories = 1; + } x = git_path("info/exclude"); if (file_exists(x)) add_excludes_from_file(&dir, x); diff --git a/wt-status.h b/wt-status.h index 75d3cfe..0a5a5b7 100644 --- a/wt-status.h +++ b/wt-status.h @@ -15,6 +15,7 @@ struct wt_status { int commitable; int verbose; int amend; + int untracked; }; int git_status_config(const char *var, const char *value); -- cgit v0.10.2-6-g49f6 From b982592d6677aebff646a0e68b1075804ca3955f Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Mon, 11 Sep 2006 19:21:17 -0400 Subject: git-status: document colorization config options Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/config.txt b/Documentation/config.txt index ce722a2..844cae4 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -225,6 +225,20 @@ showbranch.default:: The default set of branches for gitlink:git-show-branch[1]. See gitlink:git-show-branch[1]. +status.color:: + A boolean to enable/disable color in the output of + gitlink:git-status[1]. May be set to `true` (or `always`), + `false` (or `never`) or `auto`, in which case colors are used + only when the output is to a terminal. Defaults to false. + +status.color.<slot>:: + Use customized color for status colorization. `<slot>` is + one of `header` (the header text of the status message), + `updated` (files which are updated but not committed), + `changed` (files which are changed but not updated in the index), + or `untracked` (files which are not tracked by git). The values of + these variables may be specified as in diff.color.<slot>. + tar.umask:: By default, gitlink:git-tar-tree[1] sets file and directories modes to 0666 or 0777. While this is both useful and acceptable for projects -- cgit v0.10.2-6-g49f6 From cdad8bbe92a447233133be8f9f3015c1bd7e10eb Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Mon, 11 Sep 2006 19:22:49 -0400 Subject: contrib/vim: add syntax highlighting file for commits Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/contrib/vim/README b/contrib/vim/README new file mode 100644 index 0000000..9e7881f --- /dev/null +++ b/contrib/vim/README @@ -0,0 +1,8 @@ +To syntax highlight git's commit messages, you need to: + 1. Copy syntax/gitcommit.vim to vim's syntax directory: + $ mkdir -p $HOME/.vim/syntax + $ cp syntax/gitcommit.vim $HOME/.vim/syntax + 2. Auto-detect the editing of git commit files: + $ cat >>$HOME/.vimrc <<'EOF' + autocmd BufNewFile,BufRead COMMIT_EDITMSG set filetype=gitcommit + EOF diff --git a/contrib/vim/syntax/gitcommit.vim b/contrib/vim/syntax/gitcommit.vim new file mode 100644 index 0000000..a9de09f --- /dev/null +++ b/contrib/vim/syntax/gitcommit.vim @@ -0,0 +1,18 @@ +syn region gitLine start=/^#/ end=/$/ +syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile +syn region gitHead contained start=/^# (.*)/ end=/^#$/ +syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile +syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile + +syn match gitCommitFile contained /^#\t.*/hs=s+2 +syn match gitChangedFile contained /^#\t.*/hs=s+2 +syn match gitUntrackedFile contained /^#\t.*/hs=s+2 + +hi def link gitLine Comment +hi def link gitCommit Comment +hi def link gitChanged Comment +hi def link gitHead Comment +hi def link gitUntracked Comment +hi def link gitCommitFile Type +hi def link gitChangedFile Constant +hi def link gitUntrackedFile Constant -- cgit v0.10.2-6-g49f6 From 5df1e0d05cc42aeef9bf2fce243375fb6dac2121 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 12 Sep 2006 23:53:27 -0700 Subject: http-fetch: fix alternates handling. Fetch over http from a repository that uses alternates to borrow from neighbouring repositories were quite broken, apparently for some time now. We parse input and count bytes to allocate the new buffer, and when we copy into that buffer we know exactly how many bytes we want to copy from where. Using strlcpy for it was simply stupid, and the code forgot to take it into account that strlcpy terminated the string with NUL. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/http-fetch.c b/http-fetch.c index fac1760..a113bb8 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -559,9 +559,36 @@ static void process_alternates_response(void *callback_data) char *target = NULL; char *path; if (data[i] == '/') { - serverlen = strchr(base + 8, '/') - base; - okay = 1; + /* This counts + * http://git.host/pub/scm/linux.git/ + * -----------here^ + * so memcpy(dst, base, serverlen) will + * copy up to "...git.host". + */ + const char *colon_ss = strstr(base,"://"); + if (colon_ss) { + serverlen = (strchr(colon_ss + 3, '/') + - base); + okay = 1; + } } else if (!memcmp(data + i, "../", 3)) { + /* Relative URL; chop the corresponding + * number of subpath from base (and ../ + * from data), and concatenate the result. + * + * The code first drops ../ from data, and + * then drops one ../ from data and one path + * from base. IOW, one extra ../ is dropped + * from data than path is dropped from base. + * + * This is not wrong. The alternate in + * http://git.host/pub/scm/linux.git/ + * to borrow from + * http://git.host/pub/scm/linus.git/ + * is ../../linus.git/objects/. You need + * two ../../ to borrow from your direct + * neighbour. + */ i += 3; serverlen = strlen(base); while (i + 2 < posn && @@ -583,11 +610,13 @@ static void process_alternates_response(void *callback_data) okay = 1; } } - /* skip 'objects' at end */ + /* skip "objects\n" at end */ if (okay) { target = xmalloc(serverlen + posn - i - 6); - strlcpy(target, base, serverlen); - strlcpy(target + serverlen, data + i, posn - i - 6); + memcpy(target, base, serverlen); + memcpy(target + serverlen, data + i, + posn - i - 7); + target[serverlen + posn - i - 7] = 0; if (get_verbosely) fprintf(stderr, "Also look at %s\n", target); -- cgit v0.10.2-6-g49f6 From 883653babd8ee7ea23e6a5c392bb739348b1eb61 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 12 Sep 2006 23:53:27 -0700 Subject: http-fetch: fix alternates handling. Fetch over http from a repository that uses alternates to borrow from neighbouring repositories were quite broken, apparently for some time now. We parse input and count bytes to allocate the new buffer, and when we copy into that buffer we know exactly how many bytes we want to copy from where. Using strlcpy for it was simply stupid, and the code forgot to take it into account that strlcpy terminated the string with NUL. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/http-fetch.c b/http-fetch.c index de5fc44..259292d 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -559,9 +559,36 @@ static void process_alternates_response(void *callback_data) char *target = NULL; char *path; if (data[i] == '/') { - serverlen = strchr(base + 8, '/') - base; - okay = 1; + /* This counts + * http://git.host/pub/scm/linux.git/ + * -----------here^ + * so memcpy(dst, base, serverlen) will + * copy up to "...git.host". + */ + const char *colon_ss = strstr(base,"://"); + if (colon_ss) { + serverlen = (strchr(colon_ss + 3, '/') + - base); + okay = 1; + } } else if (!memcmp(data + i, "../", 3)) { + /* Relative URL; chop the corresponding + * number of subpath from base (and ../ + * from data), and concatenate the result. + * + * The code first drops ../ from data, and + * then drops one ../ from data and one path + * from base. IOW, one extra ../ is dropped + * from data than path is dropped from base. + * + * This is not wrong. The alternate in + * http://git.host/pub/scm/linux.git/ + * to borrow from + * http://git.host/pub/scm/linus.git/ + * is ../../linus.git/objects/. You need + * two ../../ to borrow from your direct + * neighbour. + */ i += 3; serverlen = strlen(base); while (i + 2 < posn && @@ -583,11 +610,13 @@ static void process_alternates_response(void *callback_data) okay = 1; } } - /* skip 'objects' at end */ + /* skip "objects\n" at end */ if (okay) { target = xmalloc(serverlen + posn - i - 6); - strlcpy(target, base, serverlen); - strlcpy(target + serverlen, data + i, posn - i - 6); + memcpy(target, base, serverlen); + memcpy(target + serverlen, data + i, + posn - i - 7); + target[serverlen + posn - i - 7] = 0; if (get_verbosely) fprintf(stderr, "Also look at %s\n", target); -- cgit v0.10.2-6-g49f6 From 8a5dbef8ac24bc5a28409d646cf3ff6db0cccb3f Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Wed, 13 Sep 2006 10:26:47 +0200 Subject: Test return value of finish_connect() Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/fetch-pack.c b/fetch-pack.c index 377fede..1b4d827 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -519,7 +519,7 @@ int main(int argc, char **argv) ret = fetch_pack(fd, nr_heads, heads); close(fd[0]); close(fd[1]); - finish_connect(pid); + ret |= finish_connect(pid); if (!ret && nr_heads) { /* If the heads to pull were given, we should have @@ -534,5 +534,5 @@ int main(int argc, char **argv) } } - return ret; + return !!ret; } diff --git a/peek-remote.c b/peek-remote.c index 87f1543..353da00 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -66,6 +66,6 @@ int main(int argc, char **argv) ret = peek_remote(fd, flags); close(fd[0]); close(fd[1]); - finish_connect(pid); - return ret; + ret |= finish_connect(pid); + return !!ret; } diff --git a/send-pack.c b/send-pack.c index ac4501d..5172ef8 100644 --- a/send-pack.c +++ b/send-pack.c @@ -408,6 +408,6 @@ int main(int argc, char **argv) ret = send_pack(fd[0], fd[1], nr_heads, heads); close(fd[0]); close(fd[1]); - finish_connect(pid); - return ret; + ret |= finish_connect(pid); + return !!ret; } -- cgit v0.10.2-6-g49f6 From 3b67d2917a0e93aa583c4069f5a00655e05dbd84 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 13 Sep 2006 12:59:20 -0700 Subject: unpack-objects -r: call it "recover". The code called this operation "desperate" but the option flag is -r and the word "recover" describes what it does better. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index 415c09b..ff6184b 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -37,7 +37,7 @@ OPTIONS -r:: When unpacking a corrupt packfile, the command dies at the first corruption. This flag tells it to keep going - and make the best effort to salvage as many objects as + and make the best effort to recover as many objects as possible. diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 5f9c0d2..4f96bca 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -10,8 +10,8 @@ #include <sys/time.h> -static int dry_run, quiet, desperate, has_errors; -static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file"; +static int dry_run, quiet, recover, has_errors; +static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file"; /* We always read in 4kB chunks. */ static unsigned char buffer[4096]; @@ -75,7 +75,7 @@ static void *get_data(unsigned long size) error("inflate returned %d\n", ret); free(buf); buf = NULL; - if (!desperate) + if (!recover) exit(1); has_errors = 1; break; @@ -192,7 +192,7 @@ static void unpack_delta_entry(unsigned long delta_size) if (!base) { error("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); - if (!desperate) + if (!recover) exit(1); has_errors = 1; return; @@ -247,7 +247,7 @@ static void unpack_one(unsigned nr, unsigned total) default: error("bad object type %d", type); has_errors = 1; - if (desperate) + if (recover) return; exit(1); } @@ -294,7 +294,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "-r")) { - desperate = 1; + recover = 1; continue; } usage(unpack_usage); -- cgit v0.10.2-6-g49f6 From 2232c0c69ff114a23011a0698b119aeea0272e8b Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Wed, 13 Sep 2006 22:55:04 +0200 Subject: git-archive: inline default_parse_extra() Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-archive.c b/builtin-archive.c index da3f714..6dabdee 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -145,17 +145,6 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args, ar_args->time = archive_time; } -static const char *default_parse_extra(struct archiver *ar, - const char **argv) -{ - static char msg[64]; - - snprintf(msg, sizeof(msg) - 4, "'%s' format does not handle %s", - ar->name, *argv); - - return strcat(msg, "..."); -} - int parse_archive_args(int argc, const char **argv, struct archiver *ar) { const char *extra_argv[MAX_EXTRA_ARGS]; @@ -208,7 +197,8 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) if (extra_argc) { if (!ar->parse_extra) - die("%s", default_parse_extra(ar, extra_argv)); + die("'%s' format does not handle %s", + ar->name, extra_argv[0]); ar->args.extra = ar->parse_extra(extra_argc, extra_argv); } ar->args.verbose = verbose; -- cgit v0.10.2-6-g49f6 From db830b4f233bd45e28592fbf59395bc98d79dec5 Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Wed, 13 Sep 2006 18:37:14 -0400 Subject: wt-status: remove extraneous newline from 'deleted:' output This was accidentally introduced during the fixes to avoid putting newlines inside of colorized output. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/wt-status.c b/wt-status.c index c644331..4b74e68 100644 --- a/wt-status.c +++ b/wt-status.c @@ -78,7 +78,7 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p) p->one->path, p->two->path); break; case DIFF_STATUS_DELETED: - color_printf_ln(c, "deleted: %s", p->one->path); break; + color_printf(c, "deleted: %s", p->one->path); break; case DIFF_STATUS_MODIFIED: color_printf(c, "modified: %s", p->one->path); break; case DIFF_STATUS_RENAMED: -- cgit v0.10.2-6-g49f6 From 8112894d82637c199294702942cac477b756b678 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" <ldv@altlinux.org> Date: Thu, 14 Sep 2006 05:03:59 +0400 Subject: Make count-objects, describe and merge-tree work in subdirectory Call setup_git_directory() to make these commands work in subdirectory. Signed-off-by: Dmitry V. Levin <ldv@altlinux.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/describe.c b/describe.c index 5dd8b2e..5ed052d 100644 --- a/describe.c +++ b/describe.c @@ -161,6 +161,8 @@ int main(int argc, char **argv) usage(describe_usage); } + setup_git_directory(); + if (i == argc) describe("HEAD", 1); else diff --git a/git.c b/git.c index 335f405..47c85e1 100644 --- a/git.c +++ b/git.c @@ -224,7 +224,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, - { "count-objects", cmd_count_objects }, + { "count-objects", cmd_count_objects, RUN_SETUP }, { "diff", cmd_diff, RUN_SETUP }, { "diff-files", cmd_diff_files, RUN_SETUP }, { "diff-index", cmd_diff_index, RUN_SETUP }, diff --git a/merge-tree.c b/merge-tree.c index 60df758..c154dcf 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -340,6 +340,8 @@ int main(int argc, char **argv) if (argc < 4) usage(merge_tree_usage); + setup_git_directory(); + buf1 = get_tree_descriptor(t+0, argv[1]); buf2 = get_tree_descriptor(t+1, argv[2]); buf3 = get_tree_descriptor(t+2, argv[3]); -- cgit v0.10.2-6-g49f6 From b85c4bbbd7d16c084f0b2f0eb9678149d08d09e8 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" <ldv@altlinux.org> Date: Thu, 14 Sep 2006 05:04:33 +0400 Subject: Documentation: Fix broken links core-tutorial.txt, cvs-migration.txt, tutorial-2.txt: Fix broken links. Signed-off-by: Dmitry V. Levin <ldv@altlinux.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 1185897..47505aa 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -1620,7 +1620,7 @@ suggested in the previous section may be new to you. You do not have to worry. git supports "shared public repository" style of cooperation you are probably more familiar with as well. -See link:cvs-migration.txt[git for CVS users] for the details. +See link:cvs-migration.html[git for CVS users] for the details. Bundling your work together --------------------------- diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index d2b0bd3..6812683 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -172,7 +172,7 @@ Advanced Shared Repository Management Git allows you to specify scripts called "hooks" to be run at certain points. You can use these, for example, to send all commits to the shared -repository to a mailing list. See link:hooks.txt[Hooks used by git]. +repository to a mailing list. See link:hooks.html[Hooks used by git]. You can enforce finer grained permissions using update hooks. See link:howto/update-hook-example.txt[Controlling access to branches using diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 2f4fe12..42b6e7d 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -368,7 +368,7 @@ in the index file is identical to the one in the working directory. In addition to being the staging area for new commits, the index file is also populated from the object database when checking out a branch, and is used to hold the trees involved in a merge operation. -See the link:core-tutorial.txt[core tutorial] and the relevant man +See the link:core-tutorial.html[core tutorial] and the relevant man pages for details. What next? -- cgit v0.10.2-6-g49f6 From 38529e28a4f465ad5d5f2fa249ca17da680bac5f Mon Sep 17 00:00:00 2001 From: Sasha Khapyorsky <sashak@voltaire.com> Date: Thu, 14 Sep 2006 05:24:04 +0300 Subject: Trivial support for cloning and fetching via ftp://. This adds trivial support for cloning and fetching via ftp://. Signed-off-by: Sasha Khapyorsky <sashak@voltaire.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-clone.sh b/git-clone.sh index 7060bda..e1b3bf3 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -298,7 +298,7 @@ yes,yes) fi git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1 ;; - https://*|http://*) + https://*|http://*|ftp://*) if test -z "@@NO_CURL@@" then clone_dumb_http "$repo" "$D" diff --git a/git-fetch.sh b/git-fetch.sh index c2eebee..09a5d6c 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -286,7 +286,7 @@ fetch_main () { # There are transports that can fetch only one head at a time... case "$remote" in - http://* | https://*) + http://* | https://* | ftp://*) if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi @@ -350,7 +350,7 @@ fetch_main () { done case "$remote" in - http://* | https://* | rsync://* ) + http://* | https://* | ftp://* | rsync://* ) ;; # we are already done. *) ( : subshell because we muck with IFS diff --git a/git-ls-remote.sh b/git-ls-remote.sh index 2fdcaf7..2c0b521 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -49,7 +49,7 @@ trap "rm -fr $tmp-*" 0 1 2 3 15 tmpdir=$tmp-d case "$peek_repo" in -http://* | https://* ) +http://* | https://* | ftp://* ) if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi -- cgit v0.10.2-6-g49f6 From 7977f0ea53fad7103c83e5bd61777da27451f1d6 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Thu, 14 Sep 2006 10:45:12 -0700 Subject: Add "-h/-H" parsing to "git grep" It turns out that I actually wanted to avoid the filenames (because I didn't care - I just wanted to see the context in which something was used) when doing a grep. But since "git grep" didn't take the "-h" parameter, I ended up having to do "grep -5 -h *.c" instead. So here's a trivial patch that adds "-h" (and thus has to enable -H too) to "git grep" parsing. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-grep.c b/builtin-grep.c index 6430f6d..ed87a55 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -138,6 +138,7 @@ struct grep_opt { unsigned binary:2; unsigned extended:1; unsigned relative:1; + unsigned pathname:1; int regflags; unsigned pre_context; unsigned post_context; @@ -316,7 +317,8 @@ static int word_char(char ch) static void show_line(struct grep_opt *opt, const char *bol, const char *eol, const char *name, unsigned lno, char sign) { - printf("%s%c", name, sign); + if (opt->pathname) + printf("%s%c", name, sign); if (opt->linenum) printf("%d%c", lno, sign); printf("%.*s\n", (int)(eol-bol), bol); @@ -691,6 +693,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) push_arg("-F"); if (opt->linenum) push_arg("-n"); + if (!opt->pathname) + push_arg("-h"); if (opt->regflags & REG_EXTENDED) push_arg("-E"); if (opt->regflags & REG_ICASE) @@ -911,6 +915,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; opt.relative = 1; + opt.pathname = 1; opt.pattern_tail = &opt.pattern_list; opt.regflags = REG_NEWLINE; @@ -970,10 +975,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.linenum = 1; continue; } + if (!strcmp("-h", arg)) { + opt.pathname = 0; + continue; + } if (!strcmp("-H", arg)) { - /* We always show the pathname, so this - * is a noop. - */ + opt.pathname = 1; continue; } if (!strcmp("-l", arg) || -- cgit v0.10.2-6-g49f6 From 5b6df8e45f31c7eb26c30536448cffe1b715e1ce Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" <ldv@altlinux.org> Date: Thu, 14 Sep 2006 05:04:09 +0400 Subject: Handle invalid argc gently describe, git: Handle argc==0 case the same way as argc==1. merge-tree: Refuse excessive arguments. Signed-off-by: Dmitry V. Levin <ldv@altlinux.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/describe.c b/describe.c index 5ed052d..ab192f8 100644 --- a/describe.c +++ b/describe.c @@ -163,7 +163,7 @@ int main(int argc, char **argv) setup_git_directory(); - if (i == argc) + if (argc <= i) describe("HEAD", 1); else while (i < argc) { diff --git a/git.c b/git.c index 47c85e1..8c182a5 100644 --- a/git.c +++ b/git.c @@ -294,7 +294,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int main(int argc, const char **argv, char **envp) { - const char *cmd = argv[0]; + const char *cmd = argv[0] ? argv[0] : "git-help"; char *slash = strrchr(cmd, '/'); const char *exec_path = NULL; int done_alias = 0; diff --git a/merge-tree.c b/merge-tree.c index c154dcf..692ede0 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -337,7 +337,7 @@ int main(int argc, char **argv) struct tree_desc t[3]; void *buf1, *buf2, *buf3; - if (argc < 4) + if (argc != 4) usage(merge_tree_usage); setup_git_directory(); -- cgit v0.10.2-6-g49f6 From c0011ff8c83f200139267492589575970fb72afc Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Thu, 14 Sep 2006 22:18:59 +0200 Subject: gitweb: Use File::Find::find in git_get_projects_list Earlier code to get list of projects when $projects_list is a directory (e.g. when it is equal to $projectroot) had a hardcoded flat (one level) list of directories. Allow for projects to be in subdirectories also for $projects_list being a directory by using File::Find. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c3544dd..1d3a4c1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -715,16 +715,26 @@ sub git_get_projects_list { if (-d $projects_list) { # search in directory my $dir = $projects_list; - opendir my ($dh), $dir or return undef; - while (my $dir = readdir($dh)) { - if (-e "$projectroot/$dir/HEAD") { - my $pr = { - path => $dir, - }; - push @list, $pr - } - } - closedir($dh); + my $pfxlen = length("$dir"); + + File::Find::find({ + follow_fast => 1, # follow symbolic links + dangling_symlinks => 0, # ignore dangling symlinks, silently + wanted => sub { + # skip project-list toplevel, if we get it. + return if (m!^[/.]$!); + # only directories can be git repositories + return unless (-d $_); + + my $subdir = substr($File::Find::name, $pfxlen + 1); + # we check related file in $projectroot + if (-e "$projectroot/$subdir/HEAD") { + push @list, { path => $subdir }; + $File::Find::prune = 1; + } + }, + }, "$dir"); + } elsif (-f $projects_list) { # read from file(url-encoded): # 'git%2Fgit.git Linus+Torvalds' -- cgit v0.10.2-6-g49f6 From c83a77e4e139303a8a830b2ba7802bed0b8fe9d9 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Fri, 15 Sep 2006 03:43:28 +0200 Subject: gitweb: Do not parse refs by hand, use git-peek-remote instead This is in response to Linus's work on packed refs. Additionally it makes gitweb work with symrefs, too. Do not parse refs by hand, using File::Find and reading individual heads to get hash of reference, but use git-peek-remote output instead. Assume that the hash for deref (with ^{}) always follows hash for ref, and that we have derefs only for tag objects; this removes call to git_get_type (and git-cat-file -t invocation) for tags, which speeds "summary" and "tags" views generation, but might slow generation of "heads" view a bit. For now, we do not save and use the deref hash. Remove git_get_hash_by_ref while at it, as git_get_refs_list was the only place it was used. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1d3a4c1..b28da2c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -676,19 +676,6 @@ sub git_get_hash_by_path { ## ...................................................................... ## git utility functions, directly accessing git repository -# assumes that PATH is not symref -sub git_get_hash_by_ref { - my $path = shift; - - open my $fd, "$projectroot/$path" or return undef; - my $head = <$fd>; - close $fd; - chomp $head; - if ($head =~ m/^[0-9a-fA-F]{40}$/) { - return $head; - } -} - sub git_get_project_description { my $path = shift; @@ -1098,17 +1085,27 @@ sub git_get_refs_list { my @reflist; my @refs; - my $pfxlen = length("$projectroot/$project/$ref_dir"); - File::Find::find(sub { - return if (/^\./); - if (-f $_) { - push @refs, substr($File::Find::name, $pfxlen + 1); + open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" + or return; + while (my $line = <$fd>) { + chomp $line; + if ($line =~ m/^([0-9a-fA-F]{40})\t$ref_dir\/?([^\^]+)$/) { + push @refs, { hash => $1, name => $2 }; + } elsif ($line =~ m/^[0-9a-fA-F]{40}\t$ref_dir\/?(.*)\^\{\}$/ && + $1 eq $refs[-1]{'name'}) { + # most likely a tag is followed by its peeled + # (deref) one, and when that happens we know the + # previous one was of type 'tag'. + $refs[-1]{'type'} = "tag"; } - }, "$projectroot/$project/$ref_dir"); + } + close $fd; + + foreach my $ref (@refs) { + my $ref_file = $ref->{'name'}; + my $ref_id = $ref->{'hash'}; - foreach my $ref_file (@refs) { - my $ref_id = git_get_hash_by_ref("$project/$ref_dir/$ref_file"); - my $type = git_get_type($ref_id) || next; + my $type = $ref->{'type'} || git_get_type($ref_id) || next; my %ref_item = parse_ref($ref_file, $ref_id, $type); push @reflist, \%ref_item; -- cgit v0.10.2-6-g49f6 From fc2b2be031f44aef0106cf7f872b750cd90b2253 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Fri, 15 Sep 2006 04:56:03 +0200 Subject: gitweb: Add git_project_index for generating index.aux Add git_project_index, which generates index.aux file that can be used as a source of projects list, instead of generating projects list from a directory. Using file as a source of projects list allows for some projects to be not present in gitweb main (project_list) page, and/or correct project owner info. And is probably faster. Additionally it can be used to get the list of all available repositories for scripts (in easily parseable form). Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b28da2c..c43f4fe 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -296,6 +296,7 @@ my %actions = ( # those below don't need $project "opml" => \&git_opml, "project_list" => \&git_project_list, + "project_index" => \&git_project_index, ); if (defined $project) { @@ -2210,6 +2211,30 @@ sub git_project_list { git_footer_html(); } +sub git_project_index { + my @projects = git_get_projects_list(); + + print $cgi->header( + -type => 'text/plain', + -charset => 'utf-8', + -content_disposition => qq(inline; filename="index.aux")); + + foreach my $pr (@projects) { + if (!exists $pr->{'owner'}) { + $pr->{'owner'} = get_file_owner("$projectroot/$project"); + } + + my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'}); + # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' ' + $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg; + $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg; + $path =~ s/ /\+/g; + $owner =~ s/ /\+/g; + + print "$path $owner\n"; + } +} + sub git_summary { my $descr = git_get_project_description($project) || "none"; my $head = git_get_head_hash($project); -- cgit v0.10.2-6-g49f6 From d48ffdbb0b2580c6ff58da7cedbf78785ba6121b Mon Sep 17 00:00:00 2001 From: Liu Yubao <yubao.liu@gmail.com> Date: Fri, 15 Sep 2006 13:46:07 -0700 Subject: Fix duplicate xmalloc in builtin-add [jc: patch came without sign-off but it was too obvious and trivial.] Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-add.c b/builtin-add.c index 0cb9c81..febb75e 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -70,7 +70,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) base = ""; if (baselen) { char *common = xmalloc(baselen + 1); - common = xmalloc(baselen + 1); memcpy(common, *pathspec, baselen); common[baselen] = 0; path = base = common; -- cgit v0.10.2-6-g49f6 From 17fd965d215060a10911db99c2dacf9c3554787a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 15 Sep 2006 18:37:01 -0700 Subject: Document git-grep -[Hh] Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 7545dd9..d8af4d9 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-grep' [--cached] [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp] - [-v | --invert-match] [--full-name] + [-v | --invert-match] [-h|-H] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] [-c | --count] @@ -47,6 +47,13 @@ OPTIONS -v | --invert-match:: Select non-matching lines. +-h | -H:: + By default, the command shows the filename for each + match. `-h` option is used to suppress this output. + `-H` is there for completeness and does not do anything + except it overrides `-h` given earlier on the command + line. + --full-name:: When run from a subdirectory, the command usually outputs paths relative to the current directory. This -- cgit v0.10.2-6-g49f6 From d0c2449f7805ee35b97c5dcb22845f157da1cea4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 15 Sep 2006 22:47:21 -0700 Subject: Define fallback PATH_MAX on systems that do not define one in <limits.h> Notably on GNU/Hurd, as reported by Gerrit Pape. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin.h b/builtin.h index 25431d7..398eafb 100644 --- a/builtin.h +++ b/builtin.h @@ -1,8 +1,7 @@ #ifndef BUILTIN_H #define BUILTIN_H -#include <stdio.h> -#include <limits.h> +#include "git-compat-util.h" extern const char git_version_string[]; extern const char git_usage_string[]; diff --git a/git-compat-util.h b/git-compat-util.h index 552b8ec..0272d04 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -26,6 +26,13 @@ #include <sys/types.h> #include <dirent.h> +/* On most systems <limits.h> would have given us this, but + * not on some systems (e.g. GNU/Hurd). + */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + #ifdef __GNUC__ #define NORETURN __attribute__((__noreturn__)) #else -- cgit v0.10.2-6-g49f6 From a1565c447d441234e227fa6881d26eaf227367e3 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Fri, 15 Sep 2006 19:30:34 +0200 Subject: gitweb: Allow for href() to be used for links without project param Make it possible to use href() subroutine to generate link with query string which does not include project ('p') parameter. href() used to add project=$project to its parameters, if it was not set (to be more exact if $params{'project'} was false). Now you can pass "project => undef" if you don't want for href() to add project parameter to query string in the generated link. Links to "project_list", "project_index" and "opml" (all related to list of all projects/all git repositories) doesn't need project parameter. Moreover "project_list" is default view (action) if project ('p') parameter is not set, just like "summary" is default view (action) if project is set; project list served as a kind of "home" page for gitweb instalation, and links to "project_list" view were done without specyfying it as an action. Convert remaining links (except $home_link and anchor links) to use href(); this required adding 'order => "o"' to @mapping in href(). This finishes consolidation of URL generation. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c43f4fe..f75fea5 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -326,11 +326,12 @@ sub href(%) { hash_base => "hb", hash_parent_base => "hpb", page => "pg", + order => "o", searchtext => "s", ); my %mapping = @mapping; - $params{"project"} ||= $project; + $params{'project'} = $project unless exists $params{'project'}; my @result = (); for (my $i = 0; $i < @mapping; $i += 2) { @@ -1304,9 +1305,11 @@ sub git_footer_html { if (defined $descr) { print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; } - print $cgi->a({-href => href(action=>"rss"), -class => "rss_logo"}, "RSS") . "\n"; + print $cgi->a({-href => href(action=>"rss"), + -class => "rss_logo"}, "RSS") . "\n"; } else { - print $cgi->a({-href => href(action=>"opml"), -class => "rss_logo"}, "OPML") . "\n"; + print $cgi->a({-href => href(project=>undef, action=>"opml"), + -class => "rss_logo"}, "OPML") . "\n"; } print "</div>\n" . "</body>\n" . @@ -2153,7 +2156,7 @@ sub git_project_list { print "<th>Project</th>\n"; } else { print "<th>" . - $cgi->a({-href => "$my_uri?" . esc_param("o=project"), + $cgi->a({-href => href(project=>undef, order=>'project'), -class => "header"}, "Project") . "</th>\n"; } @@ -2162,7 +2165,7 @@ sub git_project_list { print "<th>Description</th>\n"; } else { print "<th>" . - $cgi->a({-href => "$my_uri?" . esc_param("o=descr"), + $cgi->a({-href => href(project=>undef, order=>'descr'), -class => "header"}, "Description") . "</th>\n"; } @@ -2171,7 +2174,7 @@ sub git_project_list { print "<th>Owner</th>\n"; } else { print "<th>" . - $cgi->a({-href => "$my_uri?" . esc_param("o=owner"), + $cgi->a({-href => href(project=>undef, order=>'owner'), -class => "header"}, "Owner") . "</th>\n"; } @@ -2180,7 +2183,7 @@ sub git_project_list { print "<th>Last Change</th>\n"; } else { print "<th>" . - $cgi->a({-href => "$my_uri?" . esc_param("o=age"), + $cgi->a({-href => href(project=>undef, order=>'age'), -class => "header"}, "Last Change") . "</th>\n"; } -- cgit v0.10.2-6-g49f6 From 9d0734ae49c8c0a5a2c6b6bf85011d095182e357 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Fri, 15 Sep 2006 11:11:33 +0200 Subject: gitweb: Add link to "project_index" view to "project_list" page Add link to "project_index" view as [TXT] beside link to "opml" view, (which is marked by [OPML]) to "project_list" page. While at it add alternate links for "opml" and "project_list" to HTML header for "project_list" view. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index f75fea5..a81c8d4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1255,6 +1255,13 @@ EOF printf('<link rel="alternate" title="%s log" '. 'href="%s" type="application/rss+xml"/>'."\n", esc_param($project), href(action=>"rss")); + } else { + printf('<link rel="alternate" title="%s projects list" '. + 'href="%s" type="text/plain; charset=utf-8"/>'."\n", + $site_name, href(project=>undef, action=>"project_index")); + printf('<link rel="alternate" title="%s projects logs" '. + 'href="%s" type="text/x-opml"/>'."\n", + $site_name, href(project=>undef, action=>"opml")); } if (defined $favicon) { print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n); @@ -1309,7 +1316,9 @@ sub git_footer_html { -class => "rss_logo"}, "RSS") . "\n"; } else { print $cgi->a({-href => href(project=>undef, action=>"opml"), - -class => "rss_logo"}, "OPML") . "\n"; + -class => "rss_logo"}, "OPML") . " "; + print $cgi->a({-href => href(project=>undef, action=>"project_index"), + -class => "rss_logo"}, "TXT") . "\n"; } print "</div>\n" . "</body>\n" . -- cgit v0.10.2-6-g49f6 From e7676d2f6454c9c99e600ee2ce3c7205a9fcfb5f Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Mon, 11 Sep 2006 12:03:15 -0700 Subject: Allow multiple "git_path()" uses This allows you to maintain a few filesystem pathnames concurrently, by simply replacing the single static "pathname" buffer with a LRU of four buffers. We did exactly the same thing with sha1_to_hex(), for pretty much exactly the same reason. Sometimes you want to use two pathnames, and while it's easy enough to xstrdup() them, why not just do the LU buffer thing. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/path.c b/path.c index db8905f..bb89fb0 100644 --- a/path.c +++ b/path.c @@ -13,9 +13,15 @@ #include "cache.h" #include <pwd.h> -static char pathname[PATH_MAX]; static char bad_path[] = "/bad-path/"; +static char *get_pathname(void) +{ + static char pathname_array[4][PATH_MAX]; + static int index; + return pathname_array[3 & ++index]; +} + static char *cleanup_path(char *path) { /* Clean it up */ @@ -31,6 +37,7 @@ char *mkpath(const char *fmt, ...) { va_list args; unsigned len; + char *pathname = get_pathname(); va_start(args, fmt); len = vsnprintf(pathname, PATH_MAX, fmt, args); @@ -43,6 +50,7 @@ char *mkpath(const char *fmt, ...) char *git_path(const char *fmt, ...) { const char *git_dir = get_git_dir(); + char *pathname = get_pathname(); va_list args; unsigned len; -- cgit v0.10.2-6-g49f6 From c95b138985186992b222321f332cf92edbbd4141 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 15 Sep 2006 23:19:02 -0700 Subject: Fix git-am safety checks An earlier commit cbd64af added a check that prevents "git-am" to run without its standard input connected to a terminal while resuming operation. This was to catch a user error to try feeding a new patch from its standard input while recovery. The assumption of the check was that it is an indication that a new patch is being fed if the standard input is not connected to a terminal. It is however not quite correct (the standard input can be /dev/null if the user knows the operation does not need any input, for example). This broke t3403 when the test was run with its standard input connected to /dev/null. When git-am is given an explicit command such as --skip, there is no reason to insist that the standard input is a terminal; we are not going to read a new patch anyway. Credit goes to Gerrit Pape for noticing and reporting the problem with t3403-rebase-skip test. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-am.sh b/git-am.sh index d0af786..afe322b 100755 --- a/git-am.sh +++ b/git-am.sh @@ -166,10 +166,25 @@ fi if test -d "$dotest" then - if test ",$#," != ",0," || ! tty -s - then - die "previous dotest directory $dotest still exists but mbox given." - fi + case "$#,$skip$resolved" in + 0,*t*) + # Explicit resume command and we do not have file, so + # we are happy. + : ;; + 0,) + # No file input but without resume parameters; catch + # user error to feed us a patch from standard input + # when there is already .dotest. This is somewhat + # unreliable -- stdin could be /dev/null for example + # and the caller did not intend to feed us a patch but + # wanted to continue unattended. + tty -s + ;; + *) + false + ;; + esac || + die "previous dotest directory $dotest still exists but mbox given." resume=yes else # Make sure we are not given --skip nor --resolved -- cgit v0.10.2-6-g49f6 From 358ddb62cfd03bba1ca2f1ae8e81b9510f42ea9a Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Fri, 15 Sep 2006 11:19:32 -0700 Subject: Add "git show-ref" builtin command It's kind of like "git peek-remote", but works only locally (and thus avoids the whole overhead of git_connect()) and has some extra verification features. For example, it allows you to filter the results, and to choose whether you want the tag dereferencing or not. You can also use it to just test whether a particular ref exists. For example: git show-ref master will show all references called "master", whether tags or heads or anything else, and regardless of how deep in the reference naming hierarchy they are (so it would show "refs/heads/master" but also "refs/remote/other-repo/master"). When using the "--verify" flag, the command requires an exact ref path: git show-ref --verify refs/heads/master will only match the exact branch called "master". If nothing matches, show-ref will return an error code of 1, and in the case of verification, it will show an error message. For scripting, you can ask it to be quiet with the "--quiet" flag, which allows you to do things like git-show-ref --quiet --verify -- "refs/heads/$headname" || echo "$headname is not a valid branch" to check whether a particular branch exists or not (notice how we don't actually want to show any results, and we want to use the full refname for it in order to not trigger the problem with ambiguous partial matches). To show only tags, or only proper branch heads, use "--tags" and/or "--heads" respectively (using both means that it shows tags _and_ heads, but not other random references under the refs/ subdirectory). To do automatic tag object dereferencing, use the "-d" or "--dereference" flag, so you can do git show-ref --tags --dereference to get a listing of all tags together with what they dereference. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 7b3114f..c365138 100644 --- a/Makefile +++ b/Makefile @@ -295,7 +295,8 @@ BUILTIN_OBJS = \ builtin-upload-tar.o \ builtin-verify-pack.o \ builtin-write-tree.o \ - builtin-zip-tree.o + builtin-zip-tree.o \ + builtin-show-ref.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-show-ref.c b/builtin-show-ref.c new file mode 100644 index 0000000..161b236 --- /dev/null +++ b/builtin-show-ref.c @@ -0,0 +1,112 @@ +#include "cache.h" +#include "refs.h" +#include "object.h" +#include "tag.h" + +static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--deref] [--tags] [--heads] [--] [pattern*]"; + +static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, found_match = 0, verify = 0, quiet = 0; +static const char **pattern; + +static int show_ref(const char *refname, const unsigned char *sha1) +{ + struct object *obj; + + if (tags_only || heads_only) { + int match; + + match = heads_only && !strncmp(refname, "refs/heads/", 11); + match |= tags_only && !strncmp(refname, "refs/tags/", 10); + if (!match) + return 0; + } + if (pattern) { + int reflen = strlen(refname); + const char **p = pattern, *m; + while ((m = *p++) != NULL) { + int len = strlen(m); + if (len > reflen) + continue; + if (memcmp(m, refname + reflen - len, len)) + continue; + if (len == reflen) + goto match; + /* "--verify" requires an exact match */ + if (verify) + continue; + if (refname[reflen - len - 1] == '/') + goto match; + } + return 0; + } + +match: + found_match++; + obj = parse_object(sha1); + if (!obj) { + if (quiet) + return 0; + die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1)); + } + if (quiet) + return 0; + printf("%s %s\n", sha1_to_hex(sha1), refname); + if (deref_tags && obj->type == OBJ_TAG) { + obj = deref_tag(obj, refname, 0); + printf("%s %s^{}\n", sha1_to_hex(obj->sha1), refname); + } + return 0; +} + +int cmd_show_ref(int argc, const char **argv, const char *prefix) +{ + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (*arg != '-') { + pattern = argv + i; + break; + } + if (!strcmp(arg, "--")) { + pattern = argv + i + 1; + if (!*pattern) + pattern = NULL; + break; + } + if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) { + quiet = 1; + continue; + } + if (!strcmp(arg, "-h") || !strcmp(arg, "--head")) { + show_head = 1; + continue; + } + if (!strcmp(arg, "-d") || !strcmp(arg, "--dereference")) { + deref_tags = 1; + continue; + } + if (!strcmp(arg, "--verify")) { + verify = 1; + continue; + } + if (!strcmp(arg, "--tags")) { + tags_only = 1; + continue; + } + if (!strcmp(arg, "--heads")) { + heads_only = 1; + continue; + } + usage(show_ref_usage); + } + if (show_head) + head_ref(show_ref); + for_each_ref(show_ref); + if (!found_match) { + if (verify && !quiet) + die("No match"); + return 1; + } + return 0; +} diff --git a/builtin.h b/builtin.h index 398eafb..a7242be 100644 --- a/builtin.h +++ b/builtin.h @@ -60,5 +60,6 @@ extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); +extern int cmd_show_ref(int argc, const char **argv, const char *prefix); #endif diff --git a/git.c b/git.c index 8c182a5..fedd536 100644 --- a/git.c +++ b/git.c @@ -266,6 +266,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, { "verify-pack", cmd_verify_pack }, + { "show-ref", cmd_show_ref, RUN_SETUP }, }; int i; -- cgit v0.10.2-6-g49f6 From 305e22c36e674924de6decb29e8a4c22292b5054 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Fri, 15 Sep 2006 14:56:55 -0700 Subject: Teach "git checkout" to use git-show-ref That way, it doesn't care how the refs are stored any more Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-checkout.sh b/git-checkout.sh index 580a9e8..f03620b 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do shift [ -z "$newbranch" ] && die "git checkout: -b needs a branch name" - [ -e "$GIT_DIR/refs/heads/$newbranch" ] && + git-show-ref --verify --quiet -- "refs/heads/$newbranch" && die "git checkout: branch $newbranch already exists" git-check-ref-format "heads/$newbranch" || die "git checkout: we do not like '$newbranch' as a branch name." @@ -51,7 +51,8 @@ while [ "$#" != "0" ]; do fi new="$rev" new_name="$arg^0" - if [ -f "$GIT_DIR/refs/heads/$arg" ]; then + if git-show-ref --verify --quiet -- "refs/heads/$arg" + then branch="$arg" fi elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null) -- cgit v0.10.2-6-g49f6 From 9f613ddd21cbd05bfc139d9b1551b5780aa171f6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 15 Sep 2006 13:30:02 -0700 Subject: Add git-for-each-ref: helper for language bindings This adds a new command, git-for-each-ref. You can have it iterate over refs and have it output various aspects of the objects they refer to. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index 0d608fe..0b08f37 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ git-fetch git-fetch-pack git-findtags git-fmt-merge-msg +git-for-each-ref git-format-patch git-fsck-objects git-get-tar-commit-id diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt new file mode 100644 index 0000000..6649f79 --- /dev/null +++ b/Documentation/git-for-each-ref.txt @@ -0,0 +1,164 @@ +git-for-each-ref(1) +=================== + +NAME +---- +git-for-each-ref - Output information on each ref + +SYNOPSIS +-------- +'git-for-each-ref' [--count=<count>]* [--shell|--perl|--python] [--sort=<key>]* [--format=<format>] [<pattern>] + +DESCRIPTION +----------- + +Iterate over all refs that match `<pattern>` and show them +according to the given `<format>`, after sorting them according +to the given set of `<key>`s. If `<max>` is given, stop after +showing that many refs. The interporated values in `<format>` +can optionally be quoted as string literals in the specified +host language. + +OPTIONS +------- +<count>:: + By default the command shows all refs that match + `<pattern>`. This option makes it stop after showing + that many refs. + +<key>:: + A field name to sort on. Prefix `-` to sort in + descending order of the value. When unspecified, + `refname` is used. More than one sort keys can be + given. + +<format>:: + A string that interpolates `%(fieldname)` from the + object pointed at by a ref being shown. If `fieldname` + is prefixed with an asterisk (`*`) and the ref points + at a tag object, the value for the field in the object + tag refers is used. When unspecified, defaults to + `%(refname)`. + +<pattern>:: + If given, the name of the ref is matched against this + using fnmatch(3). Refs that do not match the pattern + are not shown. + +--shell, --perl, --python:: + If given, strings that substitute `%(fieldname)` + placeholders are quoted as string literals suitable for + the specified host language. This is meant to produce + a scriptlet that can directly be `eval`ed. + + +FIELD NAMES +----------- + +Various values from structured fields in referenced objects can +be used to interpolate into the resulting output, or as sort +keys. + +For all objects, the following names can be used: + +refname:: + The name of the ref (the part after $GIT_DIR/refs/). + +objecttype:: + The type of the object (`blob`, `tree`, `commit`, `tag`). + +objectsize:: + The size of the object (the same as `git-cat-file -s` reports). + +objectname:: + The object name (aka SHA-1). + +In addition to the above, for commit and tag objects, the header +field names (`tree`, `parent`, `object`, `type`, and `tag`) can +be used to specify the value in the header field. + +Fields that have name-email-date tuple as its value (`author`, +`committer`, and `tagger`) can be suffixed with `name`, `email`, +and `date` to extract the named component. + +The first line of the message in a commit and tag object is +`subject`, the remaining lines are `body`. The whole message +is `contents`. + +For sorting purposes, fields with numeric values sort in numeric +order (`objectsize`, `authordate`, `committerdate`, `taggerdate`). +All other fields are used to sort in their byte-value order. + +In any case, a field name that refers to a field inapplicable to +the object referred by the ref does not cause an error. It +returns an empty string instead. + + +EXAMPLES +-------- + +Show the most recent 3 tagged commits:: + +------------ +#!/bin/sh + +git-for-each-ref --count=3 --sort='-*authordate' \ +--format='From: %(*authorname) %(*authoremail) +Subject: %(*subject) +Date: %(*authordate) +Ref: %(*refname) + +%(*body) +' 'refs/tags' +------------ + +A bit more elaborate report on tags:: +------------ +#!/bin/sh + +fmt=' + r=%(refname) + t=%(*objecttype) + T=${r#refs/tags/} + + o=%(*objectname) + n=%(*authorname) + e=%(*authoremail) + s=%(*subject) + d=%(*authordate) + b=%(*body) + + kind=Tag + if test "z$t" = z + then + # could be a lightweight tag + t=%(objecttype) + kind="Lightweight tag" + o=%(objectname) + n=%(authorname) + e=%(authoremail) + s=%(subject) + d=%(authordate) + b=%(body) + fi + echo "$kind $T points at a $t object $o" + if test "z$t" = zcommit + then + echo "The commit was authored by $n $e +at $d, and titled + + $s + +Its message reads as: +" + echo "$b" | sed -e "s/^/ /" + echo + fi +' + +eval=`git-for-each-ref -s --format="$fmt" \ + --sort='*objecttype' \ + --sort=-taggerdate \ + refs/tags` +eval "$eval" +------------ diff --git a/Makefile b/Makefile index 7b3114f..f0e2e51 100644 --- a/Makefile +++ b/Makefile @@ -267,6 +267,7 @@ BUILTIN_OBJS = \ builtin-diff-stages.o \ builtin-diff-tree.o \ builtin-fmt-merge-msg.o \ + builtin-for-each-ref.o \ builtin-grep.o \ builtin-init-db.o \ builtin-log.o \ diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c new file mode 100644 index 0000000..698618b --- /dev/null +++ b/builtin-for-each-ref.c @@ -0,0 +1,874 @@ +#include "cache.h" +#include "refs.h" +#include "object.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "quote.h" +#include <fnmatch.h> + +/* Quoting styles */ +#define QUOTE_NONE 0 +#define QUOTE_SHELL 1 +#define QUOTE_PERL 2 +#define QUOTE_PYTHON 3 + +typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; + +struct atom_value { + const char *s; + unsigned long ul; /* used for sorting when not FIELD_STR */ +}; + +struct ref_sort { + struct ref_sort *next; + int atom; /* index into used_atom array */ + unsigned reverse : 1; +}; + +struct refinfo { + char *refname; + unsigned char objectname[20]; + struct atom_value *value; +}; + +static struct { + const char *name; + cmp_type cmp_type; +} valid_atom[] = { + { "refname" }, + { "objecttype" }, + { "objectsize", FIELD_ULONG }, + { "objectname" }, + { "tree" }, + { "parent" }, /* NEEDSWORK: how to address 2nd and later parents? */ + { "numparent", FIELD_ULONG }, + { "object" }, + { "type" }, + { "tag" }, + { "author" }, + { "authorname" }, + { "authoremail" }, + { "authordate", FIELD_TIME }, + { "committer" }, + { "committername" }, + { "committeremail" }, + { "committerdate", FIELD_TIME }, + { "tagger" }, + { "taggername" }, + { "taggeremail" }, + { "taggerdate", FIELD_TIME }, + { "subject" }, + { "body" }, + { "contents" }, +}; + +/* + * An atom is a valid field atom listed above, possibly prefixed with + * a "*" to denote deref_tag(). + * + * We parse given format string and sort specifiers, and make a list + * of properties that we need to extract out of objects. refinfo + * structure will hold an array of values extracted that can be + * indexed with the "atom number", which is an index into this + * array. + */ +static const char **used_atom; +static cmp_type *used_atom_type; +static int used_atom_cnt, sort_atom_limit, need_tagged; + +/* + * Used to parse format string and sort specifiers + */ +static int parse_atom(const char *atom, const char *ep) +{ + const char *sp; + char *n; + int i, at; + + sp = atom; + if (*sp == '*' && sp < ep) + sp++; /* deref */ + if (ep <= sp) + die("malformed field name: %.*s", (int)(ep-atom), atom); + + /* Do we have the atom already used elsewhere? */ + for (i = 0; i < used_atom_cnt; i++) { + int len = strlen(used_atom[i]); + if (len == ep - atom && !memcmp(used_atom[i], atom, len)) + return i; + } + + /* Is the atom a valid one? */ + for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { + int len = strlen(valid_atom[i].name); + if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len)) + break; + } + + if (ARRAY_SIZE(valid_atom) <= i) + die("unknown field name: %.*s", (int)(ep-atom), atom); + + /* Add it in, including the deref prefix */ + at = used_atom_cnt; + used_atom_cnt++; + used_atom = xrealloc(used_atom, + (sizeof *used_atom) * used_atom_cnt); + used_atom_type = xrealloc(used_atom_type, + (sizeof(*used_atom_type) * used_atom_cnt)); + n = xmalloc(ep - atom + 1); + memcpy(n, atom, ep - atom); + n[ep-atom] = 0; + used_atom[at] = n; + used_atom_type[at] = valid_atom[i].cmp_type; + return at; +} + +/* + * In a format string, find the next occurrence of %(atom). + */ +static const char *find_next(const char *cp) +{ + while (*cp) { + if (*cp == '%') { + /* %( is the start of an atom; + * %% is a quoteed per-cent. + */ + if (cp[1] == '(') + return cp; + else if (cp[1] == '%') + cp++; /* skip over two % */ + /* otherwise this is a singleton, literal % */ + } + cp++; + } + return NULL; +} + +/* + * Make sure the format string is well formed, and parse out + * the used atoms. + */ +static void verify_format(const char *format) +{ + const char *cp, *sp; + for (cp = format; *cp && (sp = find_next(cp)); ) { + const char *ep = strchr(sp, ')'); + if (!ep) + die("malformatted format string %s", sp); + /* sp points at "%(" and ep points at the closing ")" */ + parse_atom(sp + 2, ep); + cp = ep + 1; + } +} + +/* + * Given an object name, read the object data and size, and return a + * "struct object". If the object data we are returning is also borrowed + * by the "struct object" representation, set *eaten as well---it is a + * signal from parse_object_buffer to us not to free the buffer. + */ +static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten) +{ + char type[20]; + void *buf = read_sha1_file(sha1, type, sz); + + if (buf) + *obj = parse_object_buffer(sha1, type, *sz, buf, eaten); + else + *obj = NULL; + return buf; +} + +/* See grab_values */ +static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (!strcmp(name, "objecttype")) + v->s = type_names[obj->type]; + else if (!strcmp(name, "objectsize")) { + char *s = xmalloc(40); + sprintf(s, "%lu", sz); + v->ul = sz; + v->s = s; + } + else if (!strcmp(name, "objectname")) { + char *s = xmalloc(41); + strcpy(s, sha1_to_hex(obj->sha1)); + v->s = s; + } + } +} + +/* See grab_values */ +static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + struct tag *tag = (struct tag *) obj; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (!strcmp(name, "tag")) + v->s = tag->tag; + } +} + +static int num_parents(struct commit *commit) +{ + struct commit_list *parents; + int i; + + for (i = 0, parents = commit->parents; + parents; + parents = parents->next) + i++; + return i; +} + +/* See grab_values */ +static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + struct commit *commit = (struct commit *) obj; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (!strcmp(name, "tree")) { + char *s = xmalloc(41); + strcpy(s, sha1_to_hex(commit->tree->object.sha1)); + v->s = s; + } + if (!strcmp(name, "numparent")) { + char *s = xmalloc(40); + sprintf(s, "%lu", v->ul); + v->s = s; + v->ul = num_parents(commit); + } + else if (!strcmp(name, "parent")) { + int num = num_parents(commit); + int i; + struct commit_list *parents; + char *s = xmalloc(42 * num); + v->s = s; + for (i = 0, parents = commit->parents; + parents; + parents = parents->next, i = i + 42) { + struct commit *parent = parents->item; + strcpy(s+i, sha1_to_hex(parent->object.sha1)); + if (parents->next) + s[i+40] = ' '; + } + } + } +} + +static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz) +{ + const char *eol; + while (*buf) { + if (!strncmp(buf, who, wholen) && + buf[wholen] == ' ') + return buf + wholen + 1; + eol = strchr(buf, '\n'); + if (!eol) + return ""; + eol++; + if (eol[1] == '\n') + return ""; /* end of header */ + buf = eol; + } + return ""; +} + +static char *copy_line(const char *buf) +{ + const char *eol = strchr(buf, '\n'); + char *line; + int len; + if (!eol) + return ""; + len = eol - buf; + line = xmalloc(len + 1); + memcpy(line, buf, len); + line[len] = 0; + return line; +} + +static char *copy_name(const char *buf) +{ + const char *eol = strchr(buf, '\n'); + const char *eoname = strstr(buf, " <"); + char *line; + int len; + if (!(eoname && eol && eoname < eol)) + return ""; + len = eoname - buf; + line = xmalloc(len + 1); + memcpy(line, buf, len); + line[len] = 0; + return line; +} + +static char *copy_email(const char *buf) +{ + const char *email = strchr(buf, '<'); + const char *eoemail = strchr(email, '>'); + char *line; + int len; + if (!email || !eoemail) + return ""; + eoemail++; + len = eoemail - email; + line = xmalloc(len + 1); + memcpy(line, email, len); + line[len] = 0; + return line; +} + +static void grab_date(const char *buf, struct atom_value *v) +{ + const char *eoemail = strstr(buf, "> "); + char *zone; + unsigned long timestamp; + long tz; + + if (!eoemail) + goto bad; + timestamp = strtoul(eoemail + 2, &zone, 10); + if (timestamp == ULONG_MAX) + goto bad; + tz = strtol(zone, NULL, 10); + if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) + goto bad; + v->s = xstrdup(show_date(timestamp, tz, 0)); + v->ul = timestamp; + return; + bad: + v->s = ""; + v->ul = 0; +} + +/* See grab_values */ +static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + int wholen = strlen(who); + const char *wholine = NULL; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (strncmp(who, name, wholen)) + continue; + if (name[wholen] != 0 && + strcmp(name + wholen, "name") && + strcmp(name + wholen, "email") && + strcmp(name + wholen, "date")) + continue; + if (!wholine) + wholine = find_wholine(who, wholen, buf, sz); + if (!wholine) + return; /* no point looking for it */ + if (name[wholen] == 0) + v->s = copy_line(wholine); + else if (!strcmp(name + wholen, "name")) + v->s = copy_name(wholine); + else if (!strcmp(name + wholen, "email")) + v->s = copy_email(wholine); + else if (!strcmp(name + wholen, "date")) + grab_date(wholine, v); + } +} + +static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) +{ + while (*buf) { + const char *eol = strchr(buf, '\n'); + if (!eol) + return; + if (eol[1] == '\n') { + buf = eol + 1; + break; /* found end of header */ + } + buf = eol + 1; + } + while (*buf == '\n') + buf++; + if (!*buf) + return; + *sub = buf; /* first non-empty line */ + buf = strchr(buf, '\n'); + if (!buf) + return; /* no body */ + while (*buf == '\n') + buf++; /* skip blank between subject and body */ + *body = buf; +} + +/* See grab_values */ +static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + const char *subpos = NULL, *bodypos = NULL; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (strcmp(name, "subject") && + strcmp(name, "body") && + strcmp(name, "contents")) + continue; + if (!subpos) + find_subpos(buf, sz, &subpos, &bodypos); + if (!subpos) + return; + + if (!strcmp(name, "subject")) + v->s = copy_line(subpos); + else if (!strcmp(name, "body")) + v->s = bodypos; + else if (!strcmp(name, "contents")) + v->s = subpos; + } +} + +/* We want to have empty print-string for field requests + * that do not apply (e.g. "authordate" for a tag object) + */ +static void fill_missing_values(struct atom_value *val) +{ + int i; + for (i = 0; i < used_atom_cnt; i++) { + struct atom_value *v = &val[i]; + if (v->s == NULL) + v->s = ""; + } +} + +/* + * val is a list of atom_value to hold returned values. Extract + * the values for atoms in used_atom array out of (obj, buf, sz). + * when deref is false, (obj, buf, sz) is the object that is + * pointed at by the ref itself; otherwise it is the object the + * ref (which is a tag) refers to. + */ +static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + grab_common_values(val, deref, obj, buf, sz); + switch (obj->type) { + case OBJ_TAG: + grab_tag_values(val, deref, obj, buf, sz); + grab_sub_body_contents(val, deref, obj, buf, sz); + grab_person("tagger", val, deref, obj, buf, sz); + break; + case OBJ_COMMIT: + grab_commit_values(val, deref, obj, buf, sz); + grab_sub_body_contents(val, deref, obj, buf, sz); + grab_person("author", val, deref, obj, buf, sz); + grab_person("committer", val, deref, obj, buf, sz); + break; + case OBJ_TREE: + // grab_tree_values(val, deref, obj, buf, sz); + break; + case OBJ_BLOB: + // grab_blob_values(val, deref, obj, buf, sz); + break; + default: + die("Eh? Object of type %d?", obj->type); + } +} + +/* + * Parse the object referred by ref, and grab needed value. + */ +static void populate_value(struct refinfo *ref) +{ + void *buf; + struct object *obj; + int eaten, i; + unsigned long size; + const unsigned char *tagged; + + ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt); + + buf = get_obj(ref->objectname, &obj, &size, &eaten); + if (!buf) + die("missing object %s for %s", + sha1_to_hex(ref->objectname), ref->refname); + if (!obj) + die("parse_object_buffer failed on %s for %s", + sha1_to_hex(ref->objectname), ref->refname); + + /* Fill in specials first */ + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &ref->value[i]; + if (!strcmp(name, "refname")) + v->s = ref->refname; + else if (!strcmp(name, "*refname")) { + int len = strlen(ref->refname); + char *s = xmalloc(len + 4); + sprintf(s, "%s^{}", ref->refname); + v->s = s; + } + } + + grab_values(ref->value, 0, obj, buf, size); + if (!eaten) + free(buf); + + /* If there is no atom that wants to know about tagged + * object, we are done. + */ + if (!need_tagged || (obj->type != OBJ_TAG)) + return; + + /* If it is a tag object, see if we use a value that derefs + * the object, and if we do grab the object it refers to. + */ + tagged = ((struct tag *)obj)->tagged->sha1; + + /* NEEDSWORK: This derefs tag only once, which + * is good to deal with chains of trust, but + * is not consistent with what deref_tag() does + * which peels the onion to the core. + */ + buf = get_obj(tagged, &obj, &size, &eaten); + if (!buf) + die("missing object %s for %s", + sha1_to_hex(tagged), ref->refname); + if (!obj) + die("parse_object_buffer failed on %s for %s", + sha1_to_hex(tagged), ref->refname); + grab_values(ref->value, 1, obj, buf, size); + if (!eaten) + free(buf); +} + +/* + * Given a ref, return the value for the atom. This lazily gets value + * out of the object by calling populate value. + */ +static void get_value(struct refinfo *ref, int atom, struct atom_value **v) +{ + if (!ref->value) { + populate_value(ref); + fill_missing_values(ref->value); + } + *v = &ref->value[atom]; +} + +static struct refinfo **grab_array; +static const char **grab_pattern; +static int *grab_cnt; + +/* + * A call-back given to for_each_ref(). It is unfortunate that we + * need to use global variables to pass extra information to this + * function. + */ +static int grab_single_ref(const char *refname, const unsigned char *sha1) +{ + struct refinfo *ref; + int cnt; + + if (*grab_pattern) { + const char **pattern; + int namelen = strlen(refname); + for (pattern = grab_pattern; *pattern; pattern++) { + const char *p = *pattern; + int plen = strlen(p); + + if ((plen <= namelen) && + !strncmp(refname, p, plen) && + (refname[plen] == '\0' || + refname[plen] == '/')) + break; + if (!fnmatch(p, refname, FNM_PATHNAME)) + break; + } + if (!*pattern) + return 0; + } + + /* We do not open the object yet; sort may only need refname + * to do its job and the resulting list may yet to be pruned + * by maxcount logic. + */ + ref = xcalloc(1, sizeof(*ref)); + ref->refname = xstrdup(refname); + hashcpy(ref->objectname, sha1); + + cnt = *grab_cnt; + grab_array = xrealloc(grab_array, sizeof(*grab_array) * (cnt + 1)); + grab_array[cnt++] = ref; + *grab_cnt = cnt; + return 0; +} + +static struct refinfo **grab_refs(const char **pattern, int *cnt) +{ + /* Sheesh, we really should make for-each-ref to take + * callback data. + */ + *cnt = 0; + grab_pattern = pattern; + grab_cnt = cnt; + for_each_ref(grab_single_ref); + return grab_array; +} + +static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b) +{ + struct atom_value *va, *vb; + int cmp; + cmp_type cmp_type = used_atom_type[s->atom]; + + get_value(a, s->atom, &va); + get_value(b, s->atom, &vb); + switch (cmp_type) { + case FIELD_STR: + cmp = strcmp(va->s, vb->s); + break; + default: + if (va->ul < vb->ul) + cmp = -1; + else if (va->ul == vb->ul) + cmp = 0; + else + cmp = 1; + break; + } + return (s->reverse) ? -cmp : cmp; +} + +static struct ref_sort *ref_sort; +static int compare_refs(const void *a_, const void *b_) +{ + struct refinfo *a = *((struct refinfo **)a_); + struct refinfo *b = *((struct refinfo **)b_); + struct ref_sort *s; + + for (s = ref_sort; s; s = s->next) { + int cmp = cmp_ref_sort(s, a, b); + if (cmp) + return cmp; + } + return 0; +} + +static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs) +{ + ref_sort = sort; + qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs); +} + +static void print_value(struct refinfo *ref, int atom, int quote_style) +{ + struct atom_value *v; + get_value(ref, atom, &v); + switch (quote_style) { + case QUOTE_NONE: + fputs(v->s, stdout); + break; + case QUOTE_SHELL: + sq_quote_print(stdout, v->s); + break; + case QUOTE_PERL: + perl_quote_print(stdout, v->s); + break; + case QUOTE_PYTHON: + python_quote_print(stdout, v->s); + break; + } +} + +static int hex1(char ch) +{ + if ('0' <= ch && ch <= '9') + return ch - '0'; + else if ('a' <= ch && ch <= 'f') + return ch - 'a' + 10; + else if ('A' <= ch && ch <= 'F') + return ch - 'A' + 10; + return -1; +} +static int hex2(const char *cp) +{ + if (cp[0] && cp[1]) + return (hex1(cp[0]) << 4) | hex1(cp[1]); + else + return -1; +} + +static void emit(const char *cp, const char *ep) +{ + while (*cp && (!ep || cp < ep)) { + if (*cp == '%') { + if (cp[1] == '%') + cp++; + else { + int ch = hex2(cp + 1); + if (0 <= ch) { + putchar(ch); + cp += 3; + continue; + } + } + } + putchar(*cp); + cp++; + } +} + +static void show_ref(struct refinfo *info, const char *format, int quote_style) +{ + const char *cp, *sp, *ep; + + for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { + ep = strchr(sp, ')'); + if (cp < sp) + emit(cp, sp); + print_value(info, parse_atom(sp + 2, ep), quote_style); + } + if (*cp) { + sp = cp + strlen(cp); + emit(cp, sp); + } + putchar('\n'); +} + +static struct ref_sort *default_sort(void) +{ + static const char cstr_name[] = "refname"; + + struct ref_sort *sort = xcalloc(1, sizeof(*sort)); + + sort->next = NULL; + sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name)); + return sort; +} + +int cmd_for_each_ref(int ac, const char **av, char *prefix) +{ + int i, num_refs; + const char *format = NULL; + struct ref_sort *sort = NULL, **sort_tail = &sort; + int maxcount = 0; + int quote_style = -1; /* unspecified yet */ + struct refinfo **refs; + + for (i = 1; i < ac; i++) { + const char *arg = av[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strncmp(arg, "--format=", 9)) { + if (format) + die("more than one --format?"); + format = arg + 9; + continue; + } + if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) { + if (0 <= quote_style) + die("more than one quoting style?"); + quote_style = QUOTE_SHELL; + continue; + } + if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) { + if (0 <= quote_style) + die("more than one quoting style?"); + quote_style = QUOTE_PERL; + continue; + } + if (!strcmp(arg, "--python") ) { + if (0 <= quote_style) + die("more than one quoting style?"); + quote_style = QUOTE_PYTHON; + continue; + } + if (!strncmp(arg, "--count=", 8)) { + if (maxcount) + die("more than one --count?"); + maxcount = atoi(arg + 8); + if (maxcount <= 0) + die("The number %s did not parse", arg); + continue; + } + if (!strncmp(arg, "--sort=", 7)) { + struct ref_sort *s = xcalloc(1, sizeof(*s)); + int len; + + s->next = NULL; + *sort_tail = s; + sort_tail = &s->next; + + arg += 7; + if (*arg == '-') { + s->reverse = 1; + arg++; + } + len = strlen(arg); + sort->atom = parse_atom(arg, arg+len); + continue; + } + break; + } + if (quote_style < 0) + quote_style = QUOTE_NONE; + + if (!sort) + sort = default_sort(); + sort_atom_limit = used_atom_cnt; + if (!format) + format = "%(objectname) %(objecttype)\t%(refname)"; + + verify_format(format); + + refs = grab_refs(av + i, &num_refs); + + for (i = 0; i < used_atom_cnt; i++) { + if (used_atom[i][0] == '*') { + need_tagged = 1; + break; + } + } + + sort_refs(sort, refs, num_refs); + + if (!maxcount || num_refs < maxcount) + maxcount = num_refs; + for (i = 0; i < maxcount; i++) + show_ref(refs[i], format, quote_style); + return 0; +} diff --git a/builtin.h b/builtin.h index 398eafb..37915bc 100644 --- a/builtin.h +++ b/builtin.h @@ -25,6 +25,7 @@ extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_stages(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); +extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix); extern int cmd_format_patch(int argc, const char **argv, const char *prefix); extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); extern int cmd_grep(int argc, const char **argv, const char *prefix); @@ -46,8 +47,8 @@ extern int cmd_repo_config(int argc, const char **argv, const char *prefix); extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); -extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); +extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 8c182a5..10db27b 100644 --- a/git.c +++ b/git.c @@ -231,6 +231,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "diff-stages", cmd_diff_stages, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, + { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, { "format-patch", cmd_format_patch, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, { "grep", cmd_grep, RUN_SETUP }, diff --git a/object.c b/object.c index 9281300..de244e2 100644 --- a/object.c +++ b/object.c @@ -138,42 +138,56 @@ struct object *lookup_unknown_object(const unsigned char *sha1) return obj; } +struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p) +{ + struct object *obj; + int eaten = 0; + + if (!strcmp(type, blob_type)) { + struct blob *blob = lookup_blob(sha1); + parse_blob_buffer(blob, buffer, size); + obj = &blob->object; + } else if (!strcmp(type, tree_type)) { + struct tree *tree = lookup_tree(sha1); + obj = &tree->object; + if (!tree->object.parsed) { + parse_tree_buffer(tree, buffer, size); + eaten = 1; + } + } else if (!strcmp(type, commit_type)) { + struct commit *commit = lookup_commit(sha1); + parse_commit_buffer(commit, buffer, size); + if (!commit->buffer) { + commit->buffer = buffer; + eaten = 1; + } + obj = &commit->object; + } else if (!strcmp(type, tag_type)) { + struct tag *tag = lookup_tag(sha1); + parse_tag_buffer(tag, buffer, size); + obj = &tag->object; + } else { + obj = NULL; + } + *eaten_p = eaten; + return obj; +} + struct object *parse_object(const unsigned char *sha1) { unsigned long size; char type[20]; + int eaten; void *buffer = read_sha1_file(sha1, type, &size); + if (buffer) { struct object *obj; if (check_sha1_signature(sha1, buffer, size, type) < 0) printf("sha1 mismatch %s\n", sha1_to_hex(sha1)); - if (!strcmp(type, blob_type)) { - struct blob *blob = lookup_blob(sha1); - parse_blob_buffer(blob, buffer, size); - obj = &blob->object; - } else if (!strcmp(type, tree_type)) { - struct tree *tree = lookup_tree(sha1); - obj = &tree->object; - if (!tree->object.parsed) { - parse_tree_buffer(tree, buffer, size); - buffer = NULL; - } - } else if (!strcmp(type, commit_type)) { - struct commit *commit = lookup_commit(sha1); - parse_commit_buffer(commit, buffer, size); - if (!commit->buffer) { - commit->buffer = buffer; - buffer = NULL; - } - obj = &commit->object; - } else if (!strcmp(type, tag_type)) { - struct tag *tag = lookup_tag(sha1); - parse_tag_buffer(tag, buffer, size); - obj = &tag->object; - } else { - obj = NULL; - } - free(buffer); + + obj = parse_object_buffer(sha1, type, size, buffer, &eaten); + if (!eaten) + free(buffer); return obj; } return NULL; diff --git a/object.h b/object.h index 3d4ff46..caee733 100644 --- a/object.h +++ b/object.h @@ -59,6 +59,12 @@ void created_object(const unsigned char *sha1, struct object *obj); /** Returns the object, having parsed it to find out what it is. **/ struct object *parse_object(const unsigned char *sha1); +/* Given the result of read_sha1_file(), returns the object after + * parsing it. eaten_p indicates if the object has a borrowed copy + * of buffer and the caller should not free() it. + */ +struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p); + /** Returns the object, with potentially excess memory allocated. **/ struct object *lookup_unknown_object(const unsigned char *sha1); diff --git a/quote.c b/quote.c index e3a4d4a..ee7d62c 100644 --- a/quote.c +++ b/quote.c @@ -349,3 +349,41 @@ void write_name_quoted(const char *prefix, int prefix_len, else goto no_quote; } + +/* quoting as a string literal for other languages */ + +void perl_quote_print(FILE *stream, const char *src) +{ + const char sq = '\''; + const char bq = '\\'; + char c; + + fputc(sq, stream); + while ((c = *src++)) { + if (c == sq || c == bq) + fputc(bq, stream); + fputc(c, stream); + } + fputc(sq, stream); +} + +void python_quote_print(FILE *stream, const char *src) +{ + const char sq = '\''; + const char bq = '\\'; + const char nl = '\n'; + char c; + + fputc(sq, stream); + while ((c = *src++)) { + if (c == nl) { + fputc(bq, stream); + fputc('n', stream); + continue; + } + if (c == sq || c == bq) + fputc(bq, stream); + fputc(c, stream); + } + fputc(sq, stream); +} diff --git a/quote.h b/quote.h index 1a29e79..b55e699 100644 --- a/quote.h +++ b/quote.h @@ -52,4 +52,8 @@ extern char *unquote_c_style(const char *quoted, const char **endp); extern void write_name_quoted(const char *prefix, int prefix_len, const char *name, int quote, FILE *out); +/* quoting as a string literal for other languages */ +extern void perl_quote_print(FILE *stream, const char *src); +extern void python_quote_print(FILE *stream, const char *src); + #endif -- cgit v0.10.2-6-g49f6 From be4a015b0fed2bd96e509f45b80b63db056b9ca9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 16 Sep 2006 10:58:20 -0700 Subject: http-fetch.c: consolidate code to detect missing fetch target At a handful places we check two error codes from curl library to see if the file we asked was missing from the remote (e.g. we asked for a loose object when it is in a pack) to decide what to do next. This consolidates the check into a single function. NOTE: the original did not check for HTTP_RETURNED_ERROR when error code is 404, but this version does to make sure 404 is from HTTP and not some other protcol. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/http-fetch.c b/http-fetch.c index a113bb8..98cf52d 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -144,6 +144,17 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, return size; } +static int missing__target(int code, int result) +{ + return /* file:// URL -- do we ever use one??? */ + (result == CURLE_FILE_COULDNT_READ_FILE) || + /* http:// and https:// URL */ + (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) + ; +} + +#define missing_target(a) missing__target((a)->http_code, (a)->curl_result) + static void fetch_alternates(const char *base); static void process_object_response(void *callback_data); @@ -323,8 +334,7 @@ static void process_object_response(void *callback_data) obj_req->state = COMPLETE; /* Use alternates if necessary */ - if (obj_req->http_code == 404 || - obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE) { + if (missing_target(obj_req)) { fetch_alternates(alt->base); if (obj_req->repo->next != NULL) { obj_req->repo = @@ -537,8 +547,7 @@ static void process_alternates_response(void *callback_data) return; } } else if (slot->curl_result != CURLE_OK) { - if (slot->http_code != 404 && - slot->curl_result != CURLE_FILE_COULDNT_READ_FILE) { + if (!missing_target(slot)) { got_alternates = -1; return; } @@ -941,8 +950,7 @@ static int fetch_indices(struct alt_base *repo) if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result != CURLE_OK) { - if (results.http_code == 404 || - results.curl_result == CURLE_FILE_COULDNT_READ_FILE) { + if (missing_target(&results)) { repo->got_indices = 1; free(buffer.buffer); return 0; @@ -1123,8 +1131,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) ret = error("Request for %s aborted", hex); } else if (obj_req->curl_result != CURLE_OK && obj_req->http_code != 416) { - if (obj_req->http_code == 404 || - obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE) + if (missing_target(obj_req)) ret = -1; /* Be silent, it is probably in a pack. */ else ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)", -- cgit v0.10.2-6-g49f6 From 4adffc7b54e8c5d8fc7510007dd9b8235a67430f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 16 Sep 2006 11:06:02 -0700 Subject: Add ftp:// protocol support for git-http-fetch Based on Sasha Khapyorsky's patch but adjusted to the refactored "missing target" detection code. It might have been better if the program were called git-url-fetch but it is too late now ;-). Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/http-fetch.c b/http-fetch.c index 98cf52d..bc74f30 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -149,7 +149,9 @@ static int missing__target(int code, int result) return /* file:// URL -- do we ever use one??? */ (result == CURLE_FILE_COULDNT_READ_FILE) || /* http:// and https:// URL */ - (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) + (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) || + /* ftp:// URL */ + (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE) ; } -- cgit v0.10.2-6-g49f6 From 8aac4b45f39a7c845848a75ac971717a1933d99f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 16 Sep 2006 13:41:14 -0700 Subject: t1400: make test debuggable. I had a hard time figuring out why this test was failing with the packed-refs update without running it under "sh -x". This makes output from "sh t1400-update-ref.sh -v" more descriptive. Updating other tests would be a good janitorial task. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index ddc80bb..b3b920e 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -19,51 +19,51 @@ n=$n_dir/fixes test_expect_success \ "create $m" \ - 'git-update-ref $m $A && - test $A = $(cat .git/$m)' + "git-update-ref $m $A && + test $A"' = $(cat .git/'"$m"')' test_expect_success \ "create $m" \ - 'git-update-ref $m $B $A && - test $B = $(cat .git/$m)' + "git-update-ref $m $B $A && + test $B"' = $(cat .git/'"$m"')' rm -f .git/$m test_expect_success \ "fail to create $n" \ - 'touch .git/$n_dir + "touch .git/$n_dir git-update-ref $n $A >out 2>err - test $? = 1 && + test "'$? = 1 && test "" = "$(cat out)" && grep "error: unable to resolve reference" err && - grep $n err' + grep '"$n err" rm -f .git/$n_dir out err test_expect_success \ "create $m (by HEAD)" \ - 'git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + "git-update-ref HEAD $A && + test $A"' = $(cat .git/'"$m"')' test_expect_success \ "create $m (by HEAD)" \ - 'git-update-ref HEAD $B $A && - test $B = $(cat .git/$m)' + "git-update-ref HEAD $B $A && + test $B"' = $(cat .git/'"$m"')' rm -f .git/$m test_expect_failure \ '(not) create HEAD with old sha1' \ - 'git-update-ref HEAD $A $B' + "git-update-ref HEAD $A $B" test_expect_failure \ "(not) prior created .git/$m" \ - 'test -f .git/$m' + "test -f .git/$m" rm -f .git/$m test_expect_success \ "create HEAD" \ - 'git-update-ref HEAD $A' + "git-update-ref HEAD $A" test_expect_failure \ '(not) change HEAD with wrong SHA1' \ - 'git-update-ref HEAD $B $Z' + "git-update-ref HEAD $B $Z" test_expect_failure \ "(not) changed .git/$m" \ - 'test $B = $(cat .git/$m)' + "test $B"' = $(cat .git/'"$m"')' rm -f .git/$m mkdir -p .git/logs/refs/heads @@ -71,18 +71,18 @@ touch .git/logs/refs/heads/master test_expect_success \ "create $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git-update-ref HEAD $A -m "Initial Creation" && - test $A = $(cat .git/$m)' + git-update-ref HEAD '"$A"' -m "Initial Creation" && + test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:31" \ - git-update-ref HEAD $B $A -m "Switch" && - test $B = $(cat .git/$m)' + git-update-ref HEAD'" $B $A "'-m "Switch" && + test '"$B"' = $(cat .git/'"$m"')' test_expect_success \ "set $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:41" \ - git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + git-update-ref HEAD'" $A && + test $A"' = $(cat .git/'"$m"')' cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation @@ -91,7 +91,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 EOF test_expect_success \ "verifying $m's log" \ - 'diff expect .git/logs/$m' + "diff expect .git/logs/$m" rm -rf .git/$m .git/logs expect test_expect_success \ @@ -102,18 +102,18 @@ test_expect_success \ test_expect_success \ "create $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:32" \ - git-update-ref HEAD $A -m "Initial Creation" && - test $A = $(cat .git/$m)' + git-update-ref HEAD'" $A "'-m "Initial Creation" && + test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:33" \ - git-update-ref HEAD $B $A -m "Switch" && - test $B = $(cat .git/$m)' + git-update-ref HEAD'" $B $A "'-m "Switch" && + test '"$B"' = $(cat .git/'"$m"')' test_expect_success \ "set $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:43" \ - git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + git-update-ref HEAD '"$A && + test $A"' = $(cat .git/'"$m"')' cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation @@ -140,50 +140,50 @@ test_expect_success \ 'Query "master@{May 25 2005}" (before history)' \ 'rm -f o e git-rev-parse --verify "master@{May 25 2005}" >o 2>e && - test $C = $(cat o) && - test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ "Query master@{2005-05-25} (before history)" \ 'rm -f o e git-rev-parse --verify master@{2005-05-25} >o 2>e && - test $C = $(cat o) && - echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + echo test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \ 'rm -f o e git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e && - test $C = $(cat o) && - test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \ 'rm -f o e git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e && - test $A = $(cat o) && + test '"$A"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e && - test $B = $(cat o) && - test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"' + test '"$B"' = $(cat o) && + test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:38:00}" (middle of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e && - test $Z = $(cat o) && + test '"$Z"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e && - test $E = $(cat o) && + test '"$E"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-28}" (past end of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-28}" >o 2>e && - test $D = $(cat o) && - test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"' + test '"$D"' = $(cat o) && + test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"' rm -f .git/$m .git/logs/$m expect @@ -221,7 +221,7 @@ $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 c EOF test_expect_success \ 'git-commit logged updates' \ - 'diff expect .git/logs/$m' + "diff expect .git/logs/$m" unset h_TEST h_OTHER h_FIXED h_MERGED test_expect_success \ -- cgit v0.10.2-6-g49f6 From 4be609625e48e908f2b76d35bfeb61a8ba3a83a0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 17 Sep 2006 01:04:24 -0700 Subject: apply --unidiff-zero: loosen sanity checks for --unidiff=0 patches In "git-apply", we have a few sanity checks and heuristics that expects that the patch fed to us is a unified diff with at least one line of context. * When there is no leading context line in a hunk, the hunk must apply at the beginning of the preimage. Similarly, no trailing context means that the hunk is anchored at the end. * We learn a patch deletes the file from a hunk that has no resulting line (i.e. all lines are prefixed with '-') if it has not otherwise been known if the patch deletes the file. Similarly, no old line means the file is being created. And we declare an error condition when the file created by a creation patch already exists, and/or when a deletion patch still leaves content in the file. These sanity checks are good safety measures, but breaks down when people feed a diff generated with --unified=0. This was recently noticed first by Matthew Wilcox and Gerrit Pape. This adds a new flag, --unified-zero, to allow bypassing these checks. If you are in control of the patch generation process, you should not use --unified=0 patch and fix it up with this flag; rather you should try work with a patch with context. But if all you have to work with is a patch without context, this flag may come handy as the last resort. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-apply.c b/builtin-apply.c index 6e0864c..25e90d8 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -27,6 +27,7 @@ static const char *prefix; static int prefix_length = -1; static int newfd = -1; +static int unidiff_zero; static int p_value = 1; static int check_index; static int write_index; @@ -854,11 +855,10 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc } /* - * Parse a unified diff. Note that this really needs - * to parse each fragment separately, since the only - * way to know the difference between a "---" that is - * part of a patch, and a "---" that starts the next - * patch is to look at the line counts.. + * Parse a unified diff. Note that this really needs to parse each + * fragment separately, since the only way to know the difference + * between a "---" that is part of a patch, and a "---" that starts + * the next patch is to look at the line counts.. */ static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment) { @@ -875,31 +875,14 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s leading = 0; trailing = 0; - if (patch->is_new < 0) { - patch->is_new = !oldlines; - if (!oldlines) - patch->old_name = NULL; - } - if (patch->is_delete < 0) { - patch->is_delete = !newlines; - if (!newlines) - patch->new_name = NULL; - } - - if (patch->is_new && oldlines) - return error("new file depends on old contents"); - if (patch->is_delete != !newlines) { - if (newlines) - return error("deleted file still has contents"); - fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name); - } - /* Parse the thing.. */ line += len; size -= len; linenr++; added = deleted = 0; - for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) { + for (offset = len; + 0 < size; + offset += len, size -= len, line += len, linenr++) { if (!oldlines && !newlines) break; len = linelen(line, size); @@ -972,12 +955,18 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s patch->lines_added += added; patch->lines_deleted += deleted; + + if (0 < patch->is_new && oldlines) + return error("new file depends on old contents"); + if (0 < patch->is_delete && newlines) + return error("deleted file still has contents"); return offset; } static int parse_single_patch(char *line, unsigned long size, struct patch *patch) { unsigned long offset = 0; + unsigned long oldlines = 0, newlines = 0, context = 0; struct fragment **fragp = &patch->fragments; while (size > 4 && !memcmp(line, "@@ -", 4)) { @@ -988,9 +977,11 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc len = parse_fragment(line, size, patch, fragment); if (len <= 0) die("corrupt patch at line %d", linenr); - fragment->patch = line; fragment->size = len; + oldlines += fragment->oldlines; + newlines += fragment->newlines; + context += fragment->leading + fragment->trailing; *fragp = fragment; fragp = &fragment->next; @@ -999,6 +990,46 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc line += len; size -= len; } + + /* + * If something was removed (i.e. we have old-lines) it cannot + * be creation, and if something was added it cannot be + * deletion. However, the reverse is not true; --unified=0 + * patches that only add are not necessarily creation even + * though they do not have any old lines, and ones that only + * delete are not necessarily deletion. + * + * Unfortunately, a real creation/deletion patch do _not_ have + * any context line by definition, so we cannot safely tell it + * apart with --unified=0 insanity. At least if the patch has + * more than one hunk it is not creation or deletion. + */ + if (patch->is_new < 0 && + (oldlines || (patch->fragments && patch->fragments->next))) + patch->is_new = 0; + if (patch->is_delete < 0 && + (newlines || (patch->fragments && patch->fragments->next))) + patch->is_delete = 0; + if (!unidiff_zero || context) { + /* If the user says the patch is not generated with + * --unified=0, or if we have seen context lines, + * then not having oldlines means the patch is creation, + * and not having newlines means the patch is deletion. + */ + if (patch->is_new < 0 && !oldlines) + patch->is_new = 1; + if (patch->is_delete < 0 && !newlines) + patch->is_delete = 1; + } + + if (0 < patch->is_new && oldlines) + die("new file %s depends on old contents", patch->new_name); + if (0 < patch->is_delete && newlines) + die("deleted file %s still has contents", patch->old_name); + if (!patch->is_delete && !newlines && context) + fprintf(stderr, "** warning: file %s becomes empty but " + "is not deleted\n", patch->new_name); + return offset; } @@ -1556,9 +1587,19 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i /* * If we don't have any leading/trailing data in the patch, * we want it to match at the beginning/end of the file. + * + * But that would break if the patch is generated with + * --unified=0; sane people wouldn't do that to cause us + * trouble, but we try to please not so sane ones as well. */ - match_beginning = !leading && (frag->oldpos == 1); - match_end = !trailing; + if (unidiff_zero) { + match_beginning = (!leading && !frag->oldpos); + match_end = 0; + } + else { + match_beginning = !leading && (frag->oldpos == 1); + match_end = !trailing; + } lines = 0; pos = frag->newpos; @@ -1804,7 +1845,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * patch->result = desc.buffer; patch->resultsize = desc.size; - if (patch->is_delete && patch->resultsize) + if (0 < patch->is_delete && patch->resultsize) return error("removal patch leaves file contents"); return 0; @@ -1876,7 +1917,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) old_name, st_mode, patch->old_mode); } - if (new_name && prev_patch && prev_patch->is_delete && + if (new_name && prev_patch && 0 < prev_patch->is_delete && !strcmp(prev_patch->old_name, new_name)) /* A type-change diff is always split into a patch to * delete old, immediately followed by a patch to @@ -1889,7 +1930,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) else ok_if_exists = 0; - if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) { + if (new_name && + ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) { if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0 && !ok_if_exists) @@ -1906,7 +1948,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) return error("%s: %s", new_name, strerror(errno)); } if (!patch->new_mode) { - if (patch->is_new) + if (0 < patch->is_new) patch->new_mode = S_IFREG | 0644; else patch->new_mode = patch->old_mode; @@ -1957,7 +1999,7 @@ static void show_index_list(struct patch *list) const char *name; name = patch->old_name ? patch->old_name : patch->new_name; - if (patch->is_new) + if (0 < patch->is_new) sha1_ptr = null_sha1; else if (get_sha1(patch->old_sha1_prefix, sha1)) die("sha1 information is lacking or useless (%s).", @@ -2543,6 +2585,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix) apply_in_reverse = 1; continue; } + if (!strcmp(arg, "--unidiff-zero")) { + unidiff_zero = 1; + continue; + } if (!strcmp(arg, "--reject")) { apply = apply_with_reject = apply_verbosely = 1; continue; diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 8ab63c5..bb25315 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -37,7 +37,9 @@ test_expect_success setup ' git branch skip-merge skip-reference ' -test_expect_failure 'rebase with git am -3 (default)' 'git rebase master' +test_expect_failure 'rebase with git am -3 (default)' ' + git rebase master +' test_expect_success 'rebase --skip with am -3' ' git reset --hard HEAD && diff --git a/t/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh new file mode 100755 index 0000000..2ff800c --- /dev/null +++ b/t/t4104-apply-boundary.sh @@ -0,0 +1,115 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply boundary tests + +' +. ./test-lib.sh + +L="c d e f g h i j k l m n o p q r s t u v w x" + +test_expect_success setup ' + for i in b '"$L"' y + do + echo $i + done >victim && + cat victim >original && + git update-index --add victim && + + : add to the head + for i in a b '"$L"' y + do + echo $i + done >victim && + cat victim >add-a-expect && + git diff victim >add-a-patch.with && + git diff --unified=0 >add-a-patch.without && + + : modify at the head + for i in a '"$L"' y + do + echo $i + done >victim && + cat victim >mod-a-expect && + git diff victim >mod-a-patch.with && + git diff --unified=0 >mod-a-patch.without && + + : remove from the head + for i in '"$L"' y + do + echo $i + done >victim && + cat victim >del-a-expect && + git diff victim >del-a-patch.with + git diff --unified=0 >del-a-patch.without && + + : add to the tail + for i in b '"$L"' y z + do + echo $i + done >victim && + cat victim >add-z-expect && + git diff victim >add-z-patch.with && + git diff --unified=0 >add-z-patch.without && + + : modify at the tail + for i in a '"$L"' y + do + echo $i + done >victim && + cat victim >mod-z-expect && + git diff victim >mod-z-patch.with && + git diff --unified=0 >mod-z-patch.without && + + : remove from the tail + for i in b '"$L"' + do + echo $i + done >victim && + cat victim >del-z-expect && + git diff victim >del-z-patch.with + git diff --unified=0 >del-z-patch.without && + + : done +' + +for with in with without +do + case "$with" in + with) u= ;; + without) u='--unidiff-zero ' ;; + esac + for kind in add-a add-z mod-a mod-z del-a del-z + do + test_expect_success "apply $kind-patch $with context" ' + cat original >victim && + git update-index victim && + git apply --index '"$u$kind-patch.$with"' || { + cat '"$kind-patch.$with"' + (exit 1) + } && + diff -u '"$kind"'-expect victim + ' + done +done + +for kind in add-a add-z mod-a mod-z del-a del-z +do + rm -f $kind-ng.without + sed -e "s/^diff --git /diff /" \ + -e '/^index /d' \ + <$kind-patch.without >$kind-ng.without + test_expect_success "apply non-git $kind-patch without context" ' + cat original >victim && + git update-index victim && + git apply --unidiff-zero --index '"$kind-ng.without"' || { + cat '"$kind-ng.without"' + (exit 1) + } && + diff -u '"$kind"'-expect victim + ' +done + +test_done -- cgit v0.10.2-6-g49f6 From dd70235f5a81e52725365365a554cf7c8cfa37e6 Mon Sep 17 00:00:00 2001 From: Martin Waitz <tali@admingilde.org> Date: Sat, 16 Sep 2006 23:08:32 +0200 Subject: gitweb: more support for PATH_INFO based URLs Now three types of path based URLs are supported: gitweb.cgi/project.git gitweb.cgi/project.git/branch gitweb.cgi/project.git/branch/filename The first one (show project summary) was already supported for a long time now. The other two are new: they show the shortlog of a branch or the plain file contents of some file contained in the repository. This is especially useful to support project web pages for small projects: just create an html branch and then use an URL like gitweb.cgi/project.git/html/index.html. Signed-off-by: Martin Waitz <tali@admingilde.org> Acked-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a81c8d4..645ae79 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -196,12 +196,7 @@ if (defined $action) { } } -our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); -if (defined $project) { - $project =~ s|^/||; - $project =~ s|/$||; - $project = undef unless $project; -} +our $project = $cgi->param('p'); if (defined $project) { if (!validate_input($project)) { die_error(undef, "Invalid project parameter"); @@ -212,7 +207,6 @@ if (defined $project) { if (!(-e "$projectroot/$project/HEAD")) { die_error(undef, "No such project"); } - $git_dir = "$projectroot/$project"; } our $file_name = $cgi->param('f'); @@ -272,6 +266,32 @@ if (defined $searchtext) { $searchtext = quotemeta $searchtext; } +# now read PATH_INFO and use it as alternative to parameters +our $path_info = $ENV{"PATH_INFO"}; +$path_info =~ s|^/||; +$path_info =~ s|/$||; +if (validate_input($path_info) && !defined $project) { + $project = $path_info; + while ($project && !-e "$projectroot/$project/HEAD") { + $project =~ s,/*[^/]*$,,; + } + if (defined $project) { + $project = undef unless $project; + } + if ($path_info =~ m,^$project/([^/]+)/(.+)$,) { + # we got "project.git/branch/filename" + $action ||= "blob_plain"; + $hash_base ||= $1; + $file_name ||= $2; + } elsif ($path_info =~ m,^$project/([^/]+)$,) { + # we got "project.git/branch" + $action ||= "shortlog"; + $hash ||= $1; + } +} + +$git_dir = "$projectroot/$project"; + # dispatch my %actions = ( "blame" => \&git_blame2, -- cgit v0.10.2-6-g49f6 From 800764cf335cb4de289269c919e865c536635885 Mon Sep 17 00:00:00 2001 From: Martin Waitz <tali@admingilde.org> Date: Sat, 16 Sep 2006 23:09:02 +0200 Subject: gitweb: fix uninitialized variable warning. Perl spit out a varning when "blob" or "blob_plain" actions were used without a $hash parameter. Signed-off-by: Martin Waitz <tali@admingilde.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 645ae79..2501385 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2547,11 +2547,7 @@ sub git_heads { } sub git_blob_plain { - # blobs defined by non-textual hash id's can be cached my $expires; - if ($hash =~ m/^[0-9a-fA-F]{40}$/) { - $expires = "+1d"; - } if (!defined $hash) { if (defined $file_name) { @@ -2561,7 +2557,11 @@ sub git_blob_plain { } else { die_error(undef, "No file name defined"); } + } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) { + # blobs defined by non-textual hash id's can be cached + $expires = "+1d"; } + my $type = shift; open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); @@ -2589,11 +2589,7 @@ sub git_blob_plain { } sub git_blob { - # blobs defined by non-textual hash id's can be cached my $expires; - if ($hash =~ m/^[0-9a-fA-F]{40}$/) { - $expires = "+1d"; - } if (!defined $hash) { if (defined $file_name) { @@ -2603,7 +2599,11 @@ sub git_blob { } else { die_error(undef, "No file name defined"); } + } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) { + # blobs defined by non-textual hash id's can be cached + $expires = "+1d"; } + my ($have_blame) = gitweb_check_feature('blame'); open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); -- cgit v0.10.2-6-g49f6 From 87af29f09f119c4a4fa7fdf308483ae1abb74b49 Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Sat, 16 Sep 2006 21:20:36 +0200 Subject: git-tar-tree: devolve git-tar-tree into a wrapper for git-archive This patch removes the custom tree walker tree_traverse(), and makes generate_tar() use write_tar_archive() and the infrastructure provided by git-archive instead. As a kind of side effect, make write_tar_archive() able to handle NULL as base directory, as this is what the new and simple generate_tar() uses to indicate the absence of a base directory. This was simpler and cleaner than playing tricks with empty strings. The behaviour of git-tar-tree should be unchanged (quick tests didn't indicate otherwise) except for the text of some error messages. Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index f2679a8..437eb72 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -3,7 +3,6 @@ */ #include <time.h> #include "cache.h" -#include "tree-walk.h" #include "commit.h" #include "strbuf.h" #include "tar.h" @@ -248,37 +247,6 @@ static void write_global_extended_header(const unsigned char *sha1) free(ext_header.buf); } -static void traverse_tree(struct tree_desc *tree, struct strbuf *path) -{ - int pathlen = path->len; - struct name_entry entry; - - while (tree_entry(tree, &entry)) { - void *eltbuf; - char elttype[20]; - unsigned long eltsize; - - eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize); - if (!eltbuf) - die("cannot read %s", sha1_to_hex(entry.sha1)); - - path->len = pathlen; - strbuf_append_string(path, entry.path); - if (S_ISDIR(entry.mode)) - strbuf_append_string(path, "/"); - - write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize); - - if (S_ISDIR(entry.mode)) { - struct tree_desc subtree; - subtree.buf = eltbuf; - subtree.size = eltsize; - traverse_tree(&subtree, path); - } - free(eltbuf); - } -} - static int git_tar_config(const char *var, const char *value) { if (!strcmp(var, "tar.umask")) { @@ -295,51 +263,29 @@ static int git_tar_config(const char *var, const char *value) static int generate_tar(int argc, const char **argv, const char *prefix) { - unsigned char sha1[20], tree_sha1[20]; - struct commit *commit; - struct tree_desc tree; - struct strbuf current_path; - void *buffer; - - current_path.buf = xmalloc(PATH_MAX); - current_path.alloc = PATH_MAX; - current_path.len = current_path.eof = 0; + struct archiver_args args; + int result; + char *base = NULL; git_config(git_tar_config); - switch (argc) { - case 3: - strbuf_append_string(¤t_path, argv[2]); - strbuf_append_string(¤t_path, "/"); - /* FALLTHROUGH */ - case 2: - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - break; - default: + memset(&args, 0, sizeof(args)); + if (argc != 2 && argc != 3) usage(tar_tree_usage); + if (argc == 3) { + int baselen = strlen(argv[2]); + base = xmalloc(baselen + 2); + memcpy(base, argv[2], baselen); + base[baselen] = '/'; + base[baselen + 1] = '\0'; } + args.base = base; + parse_treeish_arg(argv + 1, &args, NULL); - commit = lookup_commit_reference_gently(sha1, 1); - if (commit) { - write_global_extended_header(commit->object.sha1); - archive_time = commit->date; - } else - archive_time = time(NULL); - - tree.buf = buffer = read_object_with_reference(sha1, tree_type, - &tree.size, tree_sha1); - if (!tree.buf) - die("not a reference to a tag, commit or tree object: %s", - sha1_to_hex(sha1)); - - if (current_path.len > 0) - write_entry(tree_sha1, ¤t_path, 040777, NULL, 0); - traverse_tree(&tree, ¤t_path); - write_trailer(); - free(buffer); - free(current_path.buf); - return 0; + result = write_tar_archive(&args); + free(base); + + return result; } static int write_tar_entry(const unsigned char *sha1, @@ -383,7 +329,7 @@ static int write_tar_entry(const unsigned char *sha1, int write_tar_archive(struct archiver_args *args) { - int plen = strlen(args->base); + int plen = args->base ? strlen(args->base) : 0; git_config(git_tar_config); -- cgit v0.10.2-6-g49f6 From 7939fe44b8abedb4d8bb5090aec7a92eb1aa7a4b Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer <matled@gmx.net> Date: Sun, 17 Sep 2006 00:30:27 +0200 Subject: gitweb: do not use 'No such directory' error message undef $project; to prevent a file named description to be read. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2501385..fa65757 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -198,13 +198,10 @@ if (defined $action) { our $project = $cgi->param('p'); if (defined $project) { - if (!validate_input($project)) { - die_error(undef, "Invalid project parameter"); - } - if (!(-d "$projectroot/$project")) { - die_error(undef, "No such directory"); - } - if (!(-e "$projectroot/$project/HEAD")) { + if (!validate_input($project) || + !(-d "$projectroot/$project") || + !(-e "$projectroot/$project/HEAD")) { + undef $project; die_error(undef, "No such project"); } } -- cgit v0.10.2-6-g49f6 From 32f4aaccaa45fcd79bacd424c7d69051156bb766 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer <matled@gmx.net> Date: Sun, 17 Sep 2006 00:31:01 +0200 Subject: gitweb: export options $export_ok: If this variable evaluates to true it is checked if a file with this name exists in the repository. If it does not exist the repository cannot be viewed from gitweb. (Similar to git-daemon-export-ok for git-daemon). $strict_export: If this variable evaluates to true only repositories listed on the project-list-page of gitweb can be accessed. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 7b3114f..b9938ac 100644 --- a/Makefile +++ b/Makefile @@ -126,6 +126,8 @@ GITWEB_CONFIG = gitweb_config.perl GITWEB_HOME_LINK_STR = projects GITWEB_SITENAME = GITWEB_PROJECTROOT = /pub/git +GITWEB_EXPORT_OK = +GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = GITWEB_LIST = GITWEB_HOMETEXT = indextext.html @@ -631,6 +633,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ + -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fa65757..497129a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -54,6 +54,13 @@ our $favicon = "++GITWEB_FAVICON++"; # source of projects list our $projects_list = "++GITWEB_LIST++"; +# show repository only if this file exists +# (only effective if this variable evaluates to true) +our $export_ok = "++GITWEB_EXPORT_OK++"; + +# only allow viewing of repositories also shown on the overview page +our $strict_export = "++GITWEB_STRICT_EXPORT++"; + # list of git base URLs used for URL to where fetch project from, # i.e. full URL is "$git_base_url/$project" our @git_base_url_list = ("++GITWEB_BASE_URL++"); @@ -200,7 +207,9 @@ our $project = $cgi->param('p'); if (defined $project) { if (!validate_input($project) || !(-d "$projectroot/$project") || - !(-e "$projectroot/$project/HEAD")) { + !(-e "$projectroot/$project/HEAD") || + ($export_ok && !(-e "$projectroot/$project/$export_ok")) || + ($strict_export && !project_in_list($project))) { undef $project; die_error(undef, "No such project"); } @@ -422,6 +431,12 @@ sub untabify { return $line; } +sub project_in_list { + my $project = shift; + my @list = git_get_projects_list(); + return @list && scalar(grep { $_->{'path'} eq $project } @list); +} + ## ---------------------------------------------------------------------- ## HTML aware string manipulation @@ -734,7 +749,8 @@ sub git_get_projects_list { my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot - if (-e "$projectroot/$subdir/HEAD") { + if (-e "$projectroot/$subdir/HEAD" && (!$export_ok || + -e "$projectroot/$subdir/$export_ok")) { push @list, { path => $subdir }; $File::Find::prune = 1; } @@ -755,7 +771,8 @@ sub git_get_projects_list { if (!defined $path) { next; } - if (-e "$projectroot/$path/HEAD") { + if (-e "$projectroot/$path/HEAD" && (!$export_ok || + -e "$projectroot/$path/$export_ok")) { my $pr = { path => $path, owner => decode("utf8", $owner, Encode::FB_DEFAULT), -- cgit v0.10.2-6-g49f6 From c40abef89f746ef7b9b7815b12b740e2f22905c8 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Sun, 17 Sep 2006 06:20:24 +0200 Subject: Add [-s|--hash] option to Linus' show-ref. With this option only the sha1 hash of the ref should be printed. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 161b236..577d934 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -3,9 +3,10 @@ #include "object.h" #include "tag.h" -static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--deref] [--tags] [--heads] [--] [pattern*]"; +static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--deref] [-s|--hash] [--tags] [--heads] [--] [pattern*]"; -static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, found_match = 0, verify = 0, quiet = 0; +static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, + found_match = 0, verify = 0, quiet = 0, hash_only = 0; static const char **pattern; static int show_ref(const char *refname, const unsigned char *sha1) @@ -50,7 +51,10 @@ match: } if (quiet) return 0; - printf("%s %s\n", sha1_to_hex(sha1), refname); + if (hash_only) + printf("%s\n", sha1_to_hex(sha1)); + else + printf("%s %s\n", sha1_to_hex(sha1), refname); if (deref_tags && obj->type == OBJ_TAG) { obj = deref_tag(obj, refname, 0); printf("%s %s^{}\n", sha1_to_hex(obj->sha1), refname); @@ -86,6 +90,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) deref_tags = 1; continue; } + if (!strcmp(arg, "-s") || !strcmp(arg, "--hash")) { + hash_only = 1; + continue; + } if (!strcmp(arg, "--verify")) { verify = 1; continue; -- cgit v0.10.2-6-g49f6 From ee1a9b2f1824100bbac8d23e7dea1a6e9e85fb21 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Sun, 17 Sep 2006 06:32:24 +0200 Subject: Use Linus' show ref in "git-branch.sh". Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-branch.sh b/git-branch.sh index e0501ec..2600e9c 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -21,7 +21,7 @@ delete_branch () { ,,) die "What branch are you on anyway?" ;; esac - branch=$(cat "$GIT_DIR/refs/heads/$branch_name") && + branch=$(git-show-ref --verify --hash -- "refs/heads/$branch_name") && branch=$(git-rev-parse --verify "$branch^0") || die "Seriously, what branch are you talking about?" case "$option" in @@ -112,7 +112,7 @@ rev=$(git-rev-parse --verify "$head") || exit git-check-ref-format "heads/$branchname" || die "we do not like '$branchname' as a branch name." -if [ -e "$GIT_DIR/refs/heads/$branchname" ] +if git-show-ref --verify --quiet -- "refs/heads/$branchname" then if test '' = "$force" then -- cgit v0.10.2-6-g49f6 From 9c95fbf94f9a801d04dbe38898909def7a99793f Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Sun, 17 Sep 2006 16:09:18 +0200 Subject: upload-archive: monitor child communication even more carefully. The current code works like this: if others flags than POLLIN is raised we assume that (a) something bad happened and the child died or (b) the child has closed the pipe because it had no more data to send. For the latter case, we assume wrongly that one call to process_input() will empty the pipe. Indeed it reads only 16Ko of data by call and the the pipe capacity can be larger than that (on current Linux kernel, it is 65536 bytes). Therefore the child can write 32ko of data, for example, and close the pipe. After that poll will return POLLIN _and_ POLLHUP and the parent will read only 16ko of data. This patch forces the parent to empty the pipe as soon as POLLIN is raised and even if POLLHUP or something else is raised too. Moreover, some implementations of poll might return POLLRDNORM flag even if it is non standard. Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index 115a12d..0596865 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -160,7 +160,8 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) if (pfd[1].revents & POLLIN) /* Status stream ready */ process_input(pfd[1].fd, 2); - if ((pfd[0].revents | pfd[1].revents) == POLLIN) + /* Always finish to read data when available */ + if ((pfd[0].revents | pfd[1].revents) & POLLIN) continue; if (waitpid(writer, &status, 0) < 0) -- cgit v0.10.2-6-g49f6 From 645927cec82253a3592ca738c4a405427a71eb31 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer <matled@gmx.net> Date: Sun, 17 Sep 2006 15:29:48 +0200 Subject: gitweb: fix warnings in PATH_INFO code and add export_ok/strict_export Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 497129a..0fb8638 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -189,9 +189,6 @@ do $GITWEB_CONFIG if -e $GITWEB_CONFIG; # version of the core git binary our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; -# path to the current git repository -our $git_dir; - $projects_list ||= $projectroot; # ====================================================================== @@ -273,30 +270,41 @@ if (defined $searchtext) { } # now read PATH_INFO and use it as alternative to parameters -our $path_info = $ENV{"PATH_INFO"}; -$path_info =~ s|^/||; -$path_info =~ s|/$||; -if (validate_input($path_info) && !defined $project) { +sub evaluate_path_info { + return if defined $project; + my $path_info = $ENV{"PATH_INFO"}; + return if !$path_info; + $path_info =~ s,(^/|/$),,gs; + $path_info = validate_input($path_info); + return if !$path_info; $project = $path_info; while ($project && !-e "$projectroot/$project/HEAD") { $project =~ s,/*[^/]*$,,; } - if (defined $project) { - $project = undef unless $project; + if (!$project || + ($export_ok && !-e "$projectroot/$project/$export_ok") || + ($strict_export && !project_in_list($project))) { + undef $project; + return; } + # do not change any parameters if an action is given using the query string + return if $action; if ($path_info =~ m,^$project/([^/]+)/(.+)$,) { # we got "project.git/branch/filename" $action ||= "blob_plain"; - $hash_base ||= $1; - $file_name ||= $2; + $hash_base ||= validate_input($1); + $file_name ||= validate_input($2); } elsif ($path_info =~ m,^$project/([^/]+)$,) { # we got "project.git/branch" $action ||= "shortlog"; - $hash ||= $1; + $hash ||= validate_input($1); } } +evaluate_path_info(); -$git_dir = "$projectroot/$project"; +# path to the current git repository +our $git_dir; +$git_dir = "$projectroot/$project" if $project; # dispatch my %actions = ( -- cgit v0.10.2-6-g49f6 From f58bb6fb41a24723ef59febe344f36956a9a8d41 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Sun, 17 Sep 2006 12:55:38 +0200 Subject: git-apply(1): document --unidiff-zero Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 0a6f7b3..d9137c7 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -95,6 +95,16 @@ OPTIONS context exist they all must match. By default no context is ever ignored. +--unidiff-zero:: + By default, gitlink:git-apply[1] expects that the patch being + applied is a unified diff with at least one line of context. + This provides good safety measures, but breaks down when + applying a diff generated with --unified=0. To bypass these + checks use '--unidiff-zero'. ++ +Note, for the reasons stated above usage of context-free patches are +discouraged. + --apply:: If you use any of the options marked "Turns off 'apply'" above, gitlink:git-apply[1] reads and outputs the -- cgit v0.10.2-6-g49f6 From 02ac04fc9feacc0174fcbddba6dc8c098c315b13 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Sun, 17 Sep 2006 13:02:59 +0200 Subject: git-repack(1): document --window and --depth Copy and pasted from git-pack-objects(1). Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 9516227..49f7e0a 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -9,7 +9,7 @@ objects into pack files. SYNOPSIS -------- -'git-repack' [-a] [-d] [-f] [-l] [-n] [-q] +'git-repack' [-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N] DESCRIPTION ----------- @@ -56,6 +56,16 @@ OPTIONS Do not update the server information with `git update-server-info`. +--window=[N], --depth=[N]:: + These two options affects how the objects contained in the pack are + stored using delta compression. The objects are first internally + sorted by type, size and optionally names and compared against the + other objects within `--window` to see if using delta compression saves + space. `--depth` limits the maximum delta depth; making it too deep + affects the performance on the unpacker side, because delta data needs + to be applied that many times to get to the necessary object. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> -- cgit v0.10.2-6-g49f6 From ac8e3f2bb8a790ca7810df18c3282d07e84ae345 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer <matled@gmx.net> Date: Sun, 17 Sep 2006 13:52:45 +0200 Subject: gitweb fix validating pg (page) parameter Currently it is possible to give any string ending with a number as page. -1 for example is quite bad (error log shows probably 100 warnings). Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0fb8638..c77270c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -256,7 +256,7 @@ if (defined $hash_parent_base) { our $page = $cgi->param('pg'); if (defined $page) { - if ($page =~ m/[^0-9]$/) { + if ($page =~ m/[^0-9]/) { die_error(undef, "Invalid page parameter"); } } -- cgit v0.10.2-6-g49f6 From e1e22e37f47e3f4d741d28920e1d27e3775c31ad Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Mon, 11 Sep 2006 16:37:32 -0700 Subject: Start handling references internally as a sorted in-memory list This also adds some very rudimentary support for the notion of packed refs. HOWEVER! At this point it isn't used to actually look up a ref yet, only for listing them (ie "for_each_ref()" and friends see the packed refs, but none of the other single-ref lookup routines). Note how we keep two separate lists: one for the loose refs, and one for the packed refs we read. That's so that we can easily keep the two apart, and read only one set or the other (and still always make sure that the loose refs take precedence). [ From this, it's not actually obvious why we'd keep the two separate lists, but it's important to have the packed refs on their own list later on, when I add support for looking up a single loose one. For that case, we will want to read _just_ the packed refs in case the single-ref lookup fails, yet we may end up needing the other list at some point in the future, so keeping them separated is important ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 8467447..cdbb566 100644 --- a/Makefile +++ b/Makefile @@ -301,7 +301,8 @@ BUILTIN_OBJS = \ builtin-upload-tar.o \ builtin-verify-pack.o \ builtin-write-tree.o \ - builtin-zip-tree.o + builtin-zip-tree.o \ + builtin-pack-refs.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c new file mode 100644 index 0000000..0f5d827 --- /dev/null +++ b/builtin-pack-refs.c @@ -0,0 +1,41 @@ +#include "cache.h" +#include "refs.h" + +static FILE *refs_file; +static const char *result_path, *lock_path; + +static void remove_lock_file(void) +{ + if (lock_path) + unlink(lock_path); +} + +static int handle_one_ref(const char *path, const unsigned char *sha1) +{ + fprintf(refs_file, "%s %s\n", sha1_to_hex(sha1), path); + return 0; +} + +int cmd_pack_refs(int argc, const char **argv, const char *prefix) +{ + int fd; + + result_path = xstrdup(git_path("packed-refs")); + lock_path = xstrdup(mkpath("%s.lock", result_path)); + + fd = open(lock_path, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd < 0) + die("unable to create new ref-pack file (%s)", strerror(errno)); + atexit(remove_lock_file); + + refs_file = fdopen(fd, "w"); + if (!refs_file) + die("unable to create ref-pack file structure (%s)", strerror(errno)); + for_each_ref(handle_one_ref); + fsync(fd); + fclose(refs_file); + if (rename(lock_path, result_path) < 0) + die("unable to overwrite old ref-pack file (%s)", strerror(errno)); + lock_path = NULL; + return 0; +} diff --git a/builtin.h b/builtin.h index ccade94..4b11f52 100644 --- a/builtin.h +++ b/builtin.h @@ -63,5 +63,6 @@ extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); +extern int cmd_pack_refs(int argc, const char **argv, const char *prefix); #endif diff --git a/git.c b/git.c index 44ab0de..de2a06b 100644 --- a/git.c +++ b/git.c @@ -269,6 +269,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, { "verify-pack", cmd_verify_pack }, + { "pack-refs", cmd_pack_refs, RUN_SETUP }, }; int i; diff --git a/refs.c b/refs.c index 5e65314..5f80a68 100644 --- a/refs.c +++ b/refs.c @@ -3,6 +3,145 @@ #include <errno.h> +struct ref_list { + struct ref_list *next; + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +static const char *parse_ref_line(char *line, unsigned char *sha1) +{ + /* + * 42: the answer to everything. + * + * In this case, it happens to be the answer to + * 40 (length of sha1 hex representation) + * +1 (space in between hex and name) + * +1 (newline at the end of the line) + */ + int len = strlen(line) - 42; + + if (len <= 0) + return NULL; + if (get_sha1_hex(line, sha1) < 0) + return NULL; + if (!isspace(line[40])) + return NULL; + line += 41; + if (line[len] != '\n') + return NULL; + line[len] = 0; + return line; +} + +static struct ref_list *add_ref(const char *name, const unsigned char *sha1, struct ref_list *list) +{ + int len; + struct ref_list **p = &list, *entry; + + /* Find the place to insert the ref into.. */ + while ((entry = *p) != NULL) { + int cmp = strcmp(entry->name, name); + if (cmp > 0) + break; + + /* Same as existing entry? */ + if (!cmp) + return list; + p = &entry->next; + } + + /* Allocate it and add it in.. */ + len = strlen(name) + 1; + entry = xmalloc(sizeof(struct ref_list) + len); + hashcpy(entry->sha1, sha1); + memcpy(entry->name, name, len); + entry->next = *p; + *p = entry; + return list; +} + +static struct ref_list *get_packed_refs(void) +{ + static int did_refs = 0; + static struct ref_list *refs = NULL; + + if (!did_refs) { + FILE *f = fopen(git_path("packed-refs"), "r"); + if (f) { + struct ref_list *list = NULL; + char refline[PATH_MAX]; + while (fgets(refline, sizeof(refline), f)) { + unsigned char sha1[20]; + const char *name = parse_ref_line(refline, sha1); + if (!name) + continue; + list = add_ref(name, sha1, list); + } + fclose(f); + refs = list; + } + did_refs = 1; + } + return refs; +} + +static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) +{ + DIR *dir = opendir(git_path("%s", base)); + + if (dir) { + struct dirent *de; + int baselen = strlen(base); + char *path = xmalloc(baselen + 257); + + memcpy(path, base, baselen); + if (baselen && base[baselen-1] != '/') + path[baselen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + unsigned char sha1[20]; + struct stat st; + int namelen; + + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if (namelen > 255) + continue; + if (has_extension(de->d_name, ".lock")) + continue; + memcpy(path + baselen, de->d_name, namelen+1); + if (stat(git_path("%s", path), &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) { + list = get_ref_dir(path, list); + continue; + } + if (read_ref(git_path("%s", path), sha1) < 0) { + error("%s points nowhere!", path); + continue; + } + list = add_ref(path, sha1, list); + } + free(path); + closedir(dir); + } + return list; +} + +static struct ref_list *get_loose_refs(void) +{ + static int did_refs = 0; + static struct ref_list *refs = NULL; + + if (!did_refs) { + refs = get_ref_dir("refs", NULL); + did_refs = 1; + } + return refs; +} + /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 @@ -121,60 +260,41 @@ int read_ref(const char *filename, unsigned char *sha1) static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim) { - int retval = 0; - DIR *dir = opendir(git_path("%s", base)); - - if (dir) { - struct dirent *de; - int baselen = strlen(base); - char *path = xmalloc(baselen + 257); - - if (!strncmp(base, "./", 2)) { - base += 2; - baselen -= 2; + int retval; + struct ref_list *packed = get_packed_refs(); + struct ref_list *loose = get_loose_refs(); + + while (packed && loose) { + struct ref_list *entry; + int cmp = strcmp(packed->name, loose->name); + if (!cmp) { + packed = packed->next; + continue; } - memcpy(path, base, baselen); - if (baselen && base[baselen-1] != '/') - path[baselen++] = '/'; - - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; - struct stat st; - int namelen; + if (cmp > 0) { + entry = loose; + loose = loose->next; + } else { + entry = packed; + packed = packed->next; + } + if (strncmp(base, entry->name, trim)) + continue; + retval = fn(entry->name + trim, entry->sha1); + if (retval) + return retval; + } - if (de->d_name[0] == '.') - continue; - namelen = strlen(de->d_name); - if (namelen > 255) - continue; - if (has_extension(de->d_name, ".lock")) - continue; - memcpy(path + baselen, de->d_name, namelen+1); - if (stat(git_path("%s", path), &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) { - retval = do_for_each_ref(path, fn, trim); - if (retval) - break; - continue; - } - if (read_ref(git_path("%s", path), sha1) < 0) { - error("%s points nowhere!", path); - continue; - } - if (!has_sha1_file(sha1)) { - error("%s does not point to a valid " - "commit object!", path); - continue; - } - retval = fn(path + trim, sha1); + packed = packed ? packed : loose; + while (packed) { + if (!strncmp(base, packed->name, trim)) { + retval = fn(packed->name + trim, packed->sha1); if (retval) - break; + return retval; } - free(path); - closedir(dir); + packed = packed->next; } - return retval; + return 0; } int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) @@ -187,22 +307,22 @@ int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)) { - return do_for_each_ref("refs", fn, 0); + return do_for_each_ref("refs/", fn, 0); } int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)) { - return do_for_each_ref("refs/tags", fn, 10); + return do_for_each_ref("refs/tags/", fn, 10); } int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)) { - return do_for_each_ref("refs/heads", fn, 11); + return do_for_each_ref("refs/heads/", fn, 11); } int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)) { - return do_for_each_ref("refs/remotes", fn, 13); + return do_for_each_ref("refs/remotes/", fn, 13); } int get_ref_sha1(const char *ref, unsigned char *sha1) -- cgit v0.10.2-6-g49f6 From b37a562a1097af7403c649a5f903a93acaf279e8 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Mon, 11 Sep 2006 20:10:15 -0700 Subject: Add support for negative refs You can remove a ref that is packed two different ways: either simply repack all the refs without that one, or create a loose ref that has the magic all-zero SHA1. This also adds back the test that a ref actually has the object it points to. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 5f80a68..72e2283 100644 --- a/refs.c +++ b/refs.c @@ -280,6 +280,12 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u } if (strncmp(base, entry->name, trim)) continue; + if (is_null_sha1(entry->sha1)) + continue; + if (!has_sha1_file(entry->sha1)) { + error("%s does not point to a valid object!", entry->name); + continue; + } retval = fn(entry->name + trim, entry->sha1); if (retval) return retval; -- cgit v0.10.2-6-g49f6 From ed378ec7e85fd2c5cfcc7bd64b454236357fdd97 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Mon, 11 Sep 2006 20:17:35 -0700 Subject: Make ref resolution saner The old code used to totally mix up the notion of a ref-name and the path that that ref was associated with. That was not only horribly ugly (a number of users got the path, and then wanted to try to turn it back into a ref-name again), but it fundamnetally doesn't work at all once we do any setup where a ref doesn't have a 1:1 relationship with a particular pathname. This fixes things up so that we use the ref-name throughout, and only turn it into a pathname once we actually look it up in the filesystem. That makes a lot of things much clearer and more straightforward. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index c407c03..b93c17c 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -249,7 +249,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) FILE *in = stdin; const char *sep = ""; unsigned char head_sha1[20]; - const char *head, *current_branch; + const char *current_branch; git_config(fmt_merge_msg_config); @@ -277,10 +277,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) usage(fmt_merge_msg_usage); /* get current branch */ - head = xstrdup(git_path("HEAD")); - current_branch = resolve_ref(head, head_sha1, 1); - current_branch += strlen(head) - 4; - free((char *)head); + current_branch = resolve_ref("HEAD", head_sha1, 1); if (!strncmp(current_branch, "refs/heads/", 11)) current_branch += 11; diff --git a/builtin-init-db.c b/builtin-init-db.c index 5085018..23b7714 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -218,8 +218,8 @@ static void create_default_files(const char *git_dir, const char *template_path) * branch, if it does not exist yet. */ strcpy(path + len, "HEAD"); - if (read_ref(path, sha1) < 0) { - if (create_symref(path, "refs/heads/master") < 0) + if (read_ref("HEAD", sha1) < 0) { + if (create_symref("HEAD", "refs/heads/master") < 0) exit(1); } diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 578c9fa..4d8db0c 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -437,21 +437,13 @@ static void snarf_refs(int head, int tag) } } -static int rev_is_head(char *head_path, int headlen, char *name, +static int rev_is_head(char *head, int headlen, char *name, unsigned char *head_sha1, unsigned char *sha1) { - int namelen; - if ((!head_path[0]) || + if ((!head[0]) || (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; - namelen = strlen(name); - if ((headlen < namelen) || - memcmp(head_path + headlen - namelen, name, namelen)) - return 0; - if (headlen == namelen || - head_path[headlen - namelen - 1] == '/') - return 1; - return 0; + return !strcmp(head, name); } static int show_merge_base(struct commit_list *seen, int num_rev) @@ -559,9 +551,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int all_heads = 0, all_tags = 0; int all_mask, all_revs; int lifo = 1; - char head_path[128]; - const char *head_path_p; - int head_path_len; + char head[128]; + const char *head_p; + int head_len; unsigned char head_sha1[20]; int merge_base = 0; int independent = 0; @@ -638,31 +630,31 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) ac--; av++; } - head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1); - if (head_path_p) { - head_path_len = strlen(head_path_p); - memcpy(head_path, head_path_p, head_path_len + 1); + head_p = resolve_ref("HEAD", head_sha1, 1); + if (head_p) { + head_len = strlen(head_p); + memcpy(head, head_p, head_len + 1); } else { - head_path_len = 0; - head_path[0] = 0; + head_len = 0; + head[0] = 0; } - if (with_current_branch && head_path_p) { + if (with_current_branch && head_p) { int has_head = 0; for (i = 0; !has_head && i < ref_name_cnt; i++) { /* We are only interested in adding the branch * HEAD points at. */ - if (rev_is_head(head_path, - head_path_len, + if (rev_is_head(head, + head_len, ref_name[i], head_sha1, NULL)) has_head++; } if (!has_head) { - int pfxlen = strlen(git_path("refs/heads/")); - append_one_rev(head_path + pfxlen); + int pfxlen = strlen("refs/heads/"); + append_one_rev(head + pfxlen); } } @@ -713,8 +705,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (1 < num_rev || extra < 0) { for (i = 0; i < num_rev; i++) { int j; - int is_head = rev_is_head(head_path, - head_path_len, + int is_head = rev_is_head(head, + head_len, ref_name[i], head_sha1, rev[i]->object.sha1); diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 1d3a5e2..6f18db8 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -7,15 +7,11 @@ static const char git_symbolic_ref_usage[] = static void check_symref(const char *HEAD) { unsigned char sha1[20]; - const char *git_HEAD = xstrdup(git_path("%s", HEAD)); - const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0); - if (git_refs_heads_master) { - /* we want to strip the .git/ part */ - int pfxlen = strlen(git_HEAD) - strlen(HEAD); - puts(git_refs_heads_master + pfxlen); - } - else + const char *refs_heads_master = resolve_ref("HEAD", sha1, 0); + + if (!refs_heads_master) die("No such ref: %s", HEAD); + puts(refs_heads_master); } int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) @@ -26,7 +22,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) check_symref(argv[1]); break; case 3: - create_symref(xstrdup(git_path("%s", argv[1])), argv[2]); + create_symref(argv[1], argv[2]); break; default: usage(git_symbolic_ref_usage); diff --git a/cache.h b/cache.h index 57db7c9..282eed6 100644 --- a/cache.h +++ b/cache.h @@ -287,8 +287,8 @@ extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); extern const char *resolve_ref(const char *path, unsigned char *sha1, int); -extern int create_symref(const char *git_HEAD, const char *refs_heads_master); -extern int validate_symref(const char *git_HEAD); +extern int create_symref(const char *ref, const char *refs_heads_master); +extern int validate_symref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); diff --git a/refs.c b/refs.c index 72e2283..50c25d3 100644 --- a/refs.c +++ b/refs.c @@ -93,11 +93,11 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) if (dir) { struct dirent *de; int baselen = strlen(base); - char *path = xmalloc(baselen + 257); + char *ref = xmalloc(baselen + 257); - memcpy(path, base, baselen); + memcpy(ref, base, baselen); if (baselen && base[baselen-1] != '/') - path[baselen++] = '/'; + ref[baselen++] = '/'; while ((de = readdir(dir)) != NULL) { unsigned char sha1[20]; @@ -111,20 +111,20 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) continue; if (has_extension(de->d_name, ".lock")) continue; - memcpy(path + baselen, de->d_name, namelen+1); - if (stat(git_path("%s", path), &st) < 0) + memcpy(ref + baselen, de->d_name, namelen+1); + if (stat(git_path("%s", ref), &st) < 0) continue; if (S_ISDIR(st.st_mode)) { - list = get_ref_dir(path, list); + list = get_ref_dir(ref, list); continue; } - if (read_ref(git_path("%s", path), sha1) < 0) { - error("%s points nowhere!", path); + if (read_ref(ref, sha1) < 0) { + error("%s points nowhere!", ref); continue; } - list = add_ref(path, sha1, list); + list = add_ref(ref, sha1, list); } - free(path); + free(ref); closedir(dir); } return list; @@ -145,12 +145,14 @@ static struct ref_list *get_loose_refs(void) /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -const char *resolve_ref(const char *path, unsigned char *sha1, int reading) +const char *resolve_ref(const char *ref, unsigned char *sha1, int reading) { int depth = MAXDEPTH, len; char buffer[256]; + static char ref_buffer[256]; for (;;) { + const char *path = git_path("%s", ref); struct stat st; char *buf; int fd; @@ -169,14 +171,16 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) if (reading || errno != ENOENT) return NULL; hashclr(sha1); - return path; + return ref; } /* Follow "normalized" - ie "refs/.." symlinks by hand */ if (S_ISLNK(st.st_mode)) { len = readlink(path, buffer, sizeof(buffer)-1); if (len >= 5 && !memcmp("refs/", buffer, 5)) { - path = git_path("%.*s", len, buffer); + buffer[len] = 0; + strcpy(ref_buffer, buffer); + ref = ref_buffer; continue; } } @@ -201,19 +205,22 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) while (len && isspace(*buf)) buf++, len--; while (len && isspace(buf[len-1])) - buf[--len] = 0; - path = git_path("%.*s", len, buf); + len--; + buf[len] = 0; + memcpy(ref_buffer, buf, len + 1); + ref = ref_buffer; } if (len < 40 || get_sha1_hex(buffer, sha1)) return NULL; - return path; + return ref; } -int create_symref(const char *git_HEAD, const char *refs_heads_master) +int create_symref(const char *ref_target, const char *refs_heads_master) { const char *lockpath; char ref[1000]; int fd, len, written; + const char *git_HEAD = git_path("%s", ref_target); #ifndef NO_SYMLINK_HEAD if (prefer_symlink_refs) { @@ -251,9 +258,9 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master) return 0; } -int read_ref(const char *filename, unsigned char *sha1) +int read_ref(const char *ref, unsigned char *sha1) { - if (resolve_ref(filename, sha1, 1)) + if (resolve_ref(ref, sha1, 1)) return 0; return -1; } @@ -306,7 +313,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) { unsigned char sha1[20]; - if (!read_ref(git_path("HEAD"), sha1)) + if (!read_ref("HEAD", sha1)) return fn("HEAD", sha1); return 0; } @@ -335,7 +342,7 @@ int get_ref_sha1(const char *ref, unsigned char *sha1) { if (check_ref_format(ref)) return -1; - return read_ref(git_path("refs/%s", ref), sha1); + return read_ref(mkpath("refs/%s", ref), sha1); } /* @@ -416,31 +423,30 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } -static struct ref_lock *lock_ref_sha1_basic(const char *path, +static struct ref_lock *lock_ref_sha1_basic(const char *ref, int plen, const unsigned char *old_sha1, int mustexist) { - const char *orig_path = path; + const char *orig_ref = ref; struct ref_lock *lock; struct stat st; lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - plen = strlen(path) - plen; - path = resolve_ref(path, lock->old_sha1, mustexist); - if (!path) { + ref = resolve_ref(ref, lock->old_sha1, mustexist); + if (!ref) { int last_errno = errno; error("unable to resolve reference %s: %s", - orig_path, strerror(errno)); + orig_ref, strerror(errno)); unlock_ref(lock); errno = last_errno; return NULL; } lock->lk = xcalloc(1, sizeof(struct lock_file)); - lock->ref_file = xstrdup(path); - lock->log_file = xstrdup(git_path("logs/%s", lock->ref_file + plen)); + lock->ref_file = xstrdup(git_path("%s", ref)); + lock->log_file = xstrdup(git_path("logs/%s", ref)); lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT; if (safe_create_leading_directories(lock->ref_file)) @@ -455,15 +461,14 @@ struct ref_lock *lock_ref_sha1(const char *ref, { if (check_ref_format(ref)) return NULL; - return lock_ref_sha1_basic(git_path("refs/%s", ref), + return lock_ref_sha1_basic(mkpath("refs/%s", ref), 5 + strlen(ref), old_sha1, mustexist); } struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist) { - return lock_ref_sha1_basic(git_path("%s", ref), - strlen(ref), old_sha1, mustexist); + return lock_ref_sha1_basic(ref, strlen(ref), old_sha1, mustexist); } void unlock_ref(struct ref_lock *lock) diff --git a/sha1_name.c b/sha1_name.c index 1fbc443..b497528 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -247,8 +247,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) NULL }; static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; - const char **p, *pathname; - char *real_path = NULL; + const char **p, *ref; + char *real_ref = NULL; int refs_found = 0, am; unsigned long at_time = (unsigned long)-1; unsigned char *this_result; @@ -276,10 +276,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) for (p = fmt; *p; p++) { this_result = refs_found ? sha1_from_ref : sha1; - pathname = resolve_ref(git_path(*p, len, str), this_result, 1); - if (pathname) { + ref = resolve_ref(mkpath(*p, len, str), this_result, 1); + if (ref) { if (!refs_found++) - real_path = xstrdup(pathname); + real_ref = xstrdup(ref); if (!warn_ambiguous_refs) break; } @@ -293,12 +293,12 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (at_time != (unsigned long)-1) { read_ref_at( - real_path + strlen(git_path(".")) - 1, + real_ref, at_time, sha1); } - free(real_path); + free(real_ref); return 0; } -- cgit v0.10.2-6-g49f6 From 434cd0cd30d17bc703397a76480fdd129cecc064 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Thu, 14 Sep 2006 10:14:47 -0700 Subject: Enable the packed refs file format This actually "turns on" the packed ref file format, now that the infrastructure to do so sanely exists (ie notably the change to make the reference reading logic take refnames rather than pathnames to the loose objects that no longer necessarily even exist). In particular, when the ref lookup hits a refname that has no loose file associated with it, it falls back on the packed-ref information. Also, the ref-locking code, while still using a loose file for the locking itself (and _creating_ a loose file for the new ref) no longer requires that the old ref be in such an unpacked state. Finally, this does a minimal hack to git-checkout.sh to rather than check the ref-file directly, do a "git-rev-parse" on the "heads/$refname". That's not really wonderful - we should rather really have a special routine to verify the names as proper branch head names, but it is a workable solution for now. With this, I can literally do something like git pack-refs find .git/refs -type f -print0 | xargs -0 rm -f -- and the end result is a largely working repository (ie I've done two commits - which creates _one_ unpacked ref file - done things like run "gitk" and "git log" etc, and it all looks ok). There are probably things missing, but I'm hoping that the missing things are now of the "small and obvious" kind, and that somebody else might want to start looking at this too. Hint hint ;) Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-checkout.sh b/git-checkout.sh index 580a9e8..c60e029 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do shift [ -z "$newbranch" ] && die "git checkout: -b needs a branch name" - [ -e "$GIT_DIR/refs/heads/$newbranch" ] && + git-rev-parse --symbolic "heads/$newbranch" >&/dev/null && die "git checkout: branch $newbranch already exists" git-check-ref-format "heads/$newbranch" || die "git checkout: we do not like '$newbranch' as a branch name." @@ -51,7 +51,7 @@ while [ "$#" != "0" ]; do fi new="$rev" new_name="$arg^0" - if [ -f "$GIT_DIR/refs/heads/$arg" ]; then + if git-rev-parse "heads/$arg^0" >&/dev/null; then branch="$arg" fi elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null) diff --git a/refs.c b/refs.c index 50c25d3..134c0fc 100644 --- a/refs.c +++ b/refs.c @@ -28,6 +28,8 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) if (!isspace(line[40])) return NULL; line += 41; + if (isspace(*line)) + return NULL; if (line[len] != '\n') return NULL; line[len] = 0; @@ -168,6 +170,14 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading) * reading. */ if (lstat(path, &st) < 0) { + struct ref_list *list = get_packed_refs(); + while (list) { + if (!strcmp(ref, list->name)) { + hashcpy(sha1, list->sha1); + return ref; + } + list = list->next; + } if (reading || errno != ENOENT) return NULL; hashclr(sha1); @@ -400,22 +410,13 @@ int check_ref_format(const char *ref) static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { - char buf[40]; - int nr, fd = open(lock->ref_file, O_RDONLY); - if (fd < 0 && (mustexist || errno != ENOENT)) { - error("Can't verify ref %s", lock->ref_file); - unlock_ref(lock); - return NULL; - } - nr = read(fd, buf, 40); - close(fd); - if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) { - error("Can't verify ref %s", lock->ref_file); + if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist)) { + error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); return NULL; } if (hashcmp(lock->old_sha1, old_sha1)) { - error("Ref %s is at %s but expected %s", lock->ref_file, + error("Ref %s is at %s but expected %s", lock->ref_name, sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); unlock_ref(lock); return NULL; @@ -427,6 +428,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, int plen, const unsigned char *old_sha1, int mustexist) { + char *ref_file; const char *orig_ref = ref; struct ref_lock *lock; struct stat st; @@ -445,13 +447,14 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, } lock->lk = xcalloc(1, sizeof(struct lock_file)); - lock->ref_file = xstrdup(git_path("%s", ref)); + lock->ref_name = xstrdup(ref); lock->log_file = xstrdup(git_path("logs/%s", ref)); - lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT; + ref_file = git_path(ref); + lock->force_write = lstat(ref_file, &st) && errno == ENOENT; - if (safe_create_leading_directories(lock->ref_file)) - die("unable to create directory for %s", lock->ref_file); - lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1); + if (safe_create_leading_directories(ref_file)) + die("unable to create directory for %s", ref_file); + lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1); return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; } @@ -479,7 +482,7 @@ void unlock_ref(struct ref_lock *lock) if (lock->lk) rollback_lock_file(lock->lk); } - free(lock->ref_file); + free(lock->ref_name); free(lock->log_file); free(lock); } @@ -556,7 +559,7 @@ int write_ref_sha1(struct ref_lock *lock, return -1; } if (commit_lock_file(lock->lk)) { - error("Couldn't set %s", lock->ref_file); + error("Couldn't set %s", lock->ref_name); unlock_ref(lock); return -1; } diff --git a/refs.h b/refs.h index 553155c..af347e6 100644 --- a/refs.h +++ b/refs.h @@ -2,7 +2,7 @@ #define REFS_H struct ref_lock { - char *ref_file; + char *ref_name; char *log_file; struct lock_file *lk; unsigned char old_sha1[20]; -- cgit v0.10.2-6-g49f6 From ef176ad06afde424b2167da290a0fdd7fb8ca3d4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 16 Sep 2006 13:46:00 -0700 Subject: Fix t1400-update-ref test minimally It depended on specific error messages to detect failure but the implementation changed and broke the test. This fixes the breakage minimally. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b3b920e..6a917f2 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -30,11 +30,8 @@ rm -f .git/$m test_expect_success \ "fail to create $n" \ "touch .git/$n_dir - git-update-ref $n $A >out 2>err - test "'$? = 1 && - test "" = "$(cat out)" && - grep "error: unable to resolve reference" err && - grep '"$n err" + git-update-ref $n $A >out 2>err"' + test $? != 0' rm -f .git/$n_dir out err test_expect_success \ -- cgit v0.10.2-6-g49f6 From f62363fb02865967e5efd17de1a09de6e02f3e3f Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Sun, 17 Sep 2006 19:13:56 -0700 Subject: wt-status: use simplified resolve_ref to find current branch Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/wt-status.c b/wt-status.c index 4b74e68..050922d 100644 --- a/wt-status.c +++ b/wt-status.c @@ -41,10 +41,8 @@ void wt_status_prepare(struct wt_status *s) s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0; - head = resolve_ref(git_path("HEAD"), sha1, 0); - s->branch = head ? - strdup(head + strlen(get_git_dir()) + 1) : - NULL; + head = resolve_ref("HEAD", sha1, 0); + s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; s->amend = 0; -- cgit v0.10.2-6-g49f6 From 0b7c5a5450951dfed9017ff4dec5682219228639 Mon Sep 17 00:00:00 2001 From: Liu Yubao <yubao.liu@gmail.com> Date: Fri, 15 Sep 2006 13:46:07 -0700 Subject: Fix duplicate xmalloc in builtin-add [jc: patch came without sign-off but it was too obvious and trivial.] Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-add.c b/builtin-add.c index 0cb9c81..febb75e 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -70,7 +70,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) base = ""; if (baselen) { char *common = xmalloc(baselen + 1); - common = xmalloc(baselen + 1); memcpy(common, *pathspec, baselen); common[baselen] = 0; path = base = common; -- cgit v0.10.2-6-g49f6 From f2b5792564754d5ba812a0d3eaec2440dfc0652a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 15 Sep 2006 23:19:02 -0700 Subject: Fix git-am safety checks An earlier commit cbd64af added a check that prevents "git-am" to run without its standard input connected to a terminal while resuming operation. This was to catch a user error to try feeding a new patch from its standard input while recovery. The assumption of the check was that it is an indication that a new patch is being fed if the standard input is not connected to a terminal. It is however not quite correct (the standard input can be /dev/null if the user knows the operation does not need any input, for example). This broke t3403 when the test was run with its standard input connected to /dev/null. When git-am is given an explicit command such as --skip, there is no reason to insist that the standard input is a terminal; we are not going to read a new patch anyway. Credit goes to Gerrit Pape for noticing and reporting the problem with t3403-rebase-skip test. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-am.sh b/git-am.sh index d0af786..afe322b 100755 --- a/git-am.sh +++ b/git-am.sh @@ -166,10 +166,25 @@ fi if test -d "$dotest" then - if test ",$#," != ",0," || ! tty -s - then - die "previous dotest directory $dotest still exists but mbox given." - fi + case "$#,$skip$resolved" in + 0,*t*) + # Explicit resume command and we do not have file, so + # we are happy. + : ;; + 0,) + # No file input but without resume parameters; catch + # user error to feed us a patch from standard input + # when there is already .dotest. This is somewhat + # unreliable -- stdin could be /dev/null for example + # and the caller did not intend to feed us a patch but + # wanted to continue unattended. + tty -s + ;; + *) + false + ;; + esac || + die "previous dotest directory $dotest still exists but mbox given." resume=yes else # Make sure we are not given --skip nor --resolved -- cgit v0.10.2-6-g49f6 From b3dc864c6d5ffb96513328f976c076c3a90331b0 Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Mon, 18 Sep 2006 00:34:38 -0700 Subject: gitignore: git-pack-refs is a generated file. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index a3d9c7a..1ff28c7 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,7 @@ git-name-rev git-mv git-pack-redundant git-pack-objects +git-pack-refs git-parse-remote git-patch-id git-peek-remote -- cgit v0.10.2-6-g49f6 From 582c5b09be6336e74a366ad900ca7ec3a5d1572d Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Mon, 18 Sep 2006 00:35:07 -0700 Subject: gitignore: git-show-ref is a generated file. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index 0d608fe..f53e0b2 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,7 @@ git-shortlog git-show git-show-branch git-show-index +git-show-ref git-ssh-fetch git-ssh-pull git-ssh-push -- cgit v0.10.2-6-g49f6 From 5b10b091139ea5ef87376998c89f3a20dd7c7793 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Mon, 18 Sep 2006 01:08:00 -0700 Subject: fsck-objects: adjust to resolve_ref() clean-up. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/fsck-objects.c b/fsck-objects.c index 4d994f3..456c17e 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -458,15 +458,13 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { unsigned char sha1[20]; - const char *git_HEAD = xstrdup(git_path("HEAD")); - const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1); - int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */ + const char *head_points_at = resolve_ref("HEAD", sha1, 1); - if (!git_refs_heads_master) + if (!head_points_at) return error("HEAD is not a symbolic ref"); - if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11)) + if (strncmp(head_points_at, "refs/heads/", 11)) return error("HEAD points to something strange (%s)", - git_refs_heads_master + pfxlen); + head_points_at); if (is_null_sha1(sha1)) return error("HEAD: not a valid git pointer"); return 0; -- cgit v0.10.2-6-g49f6 From c0990ff36f0b9b8e806c8f649a0888d05bb22c37 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Mon, 18 Sep 2006 14:32:41 +0200 Subject: Add man page for git-show-ref Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt new file mode 100644 index 0000000..529ea17 --- /dev/null +++ b/Documentation/git-show-ref.txt @@ -0,0 +1,136 @@ +git-show-ref(1) +=============== + +NAME +---- +git-show-ref - List references in a local repository + +SYNOPSIS +-------- +[verse] +'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] + [--tags] [--heads] [--] <pattern>... + +DESCRIPTION +----------- + +Displays references available in a local repository along with the associated +commit IDs. Results can be filtered using a pattern and tags can be +dereferenced into object IDs. Additionally, it can be used to test whether a +particular ref exists. + +Use of this utility is encouraged in favor of directly accessing files under +in the `.git` directory. + +OPTIONS +------- + +-h, --head:: + + Show the HEAD reference. + +--tags, --heads:: + + Limit to only "refs/heads" and "refs/tags", respectively. These + options are not mutually exclusive; when given both, references stored + in "refs/heads" and "refs/tags" are displayed. + +-d, --dereference:: + + Dereference tags into object IDs. They will be shown with "^{}" + appended. + +--verify:: + + Enable stricter reference checking by requiring an exact ref path. + Aside from returning an error code of 1, it will also print an error + message if '--quiet' was not specified. + +-q, --quiet:: + + Do not print any results to stdout. When combined with '--verify' this + can be used to silently check if a reference exists. + +<pattern>:: + + Show references matching one or more patterns. + +OUTPUT +------ + +The output is in the format: '<SHA-1 ID>' '<space>' '<reference name>'. + +----------------------------------------------------------------------------- +$ git show-ref --head --dereference +832e76a9899f560a90ffd62ae2ce83bbeff58f54 HEAD +832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/master +832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/origin +3521017556c5de4159da4615a39fa4d5d2c279b5 refs/tags/v0.99.9c +6ddc0964034342519a87fe013781abf31c6db6ad refs/tags/v0.99.9c^{} +055e4ae3ae6eb344cbabf2a5256a49ea66040131 refs/tags/v1.0rc4 +423325a2d24638ddcc82ce47be5e40be550f4507 refs/tags/v1.0rc4^{} +... +----------------------------------------------------------------------------- + +EXAMPLE +------- + +To show all references called "master", whether tags or heads or anything +else, and regardless of how deep in the reference naming hierarchy they are, +use: + +----------------------------------------------------------------------------- + git show-ref master +----------------------------------------------------------------------------- + +This will show "refs/heads/master" but also "refs/remote/other-repo/master", +if such references exists. + +When using the '--verify' flag, the command requires an exact path: + +----------------------------------------------------------------------------- + git show-ref --verify refs/heads/master +----------------------------------------------------------------------------- + +will only match the exact branch called "master". + +If nothing matches, gitlink:git-show-ref[1] will return an error code of 1, +and in the case of verification, it will show an error message. + +For scripting, you can ask it to be quiet with the "--quiet" flag, which +allows you to do things like + +----------------------------------------------------------------------------- + git-show-ref --quiet --verify -- "refs/heads/$headname" || + echo "$headname is not a valid branch" +----------------------------------------------------------------------------- + +to check whether a particular branch exists or not (notice how we don't +actually want to show any results, and we want to use the full refname for it +in order to not trigger the problem with ambiguous partial matches). + +To show only tags, or only proper branch heads, use "--tags" and/or "--heads" +respectively (using both means that it shows tags and heads, but not other +random references under the refs/ subdirectory). + +To do automatic tag object dereferencing, use the "-d" or "--dereference" +flag, so you can do + +----------------------------------------------------------------------------- + git show-ref --tags --dereference +----------------------------------------------------------------------------- + +to get a listing of all tags together with what they dereference. + +SEE ALSO +-------- +gitlink:git-ls-remote[1], gitlink:git-peek-remote[1] + +AUTHORS +------- +Written by Linus Torvalds <torvalds@osdl.org>. +Man page by Jonas Fonseca <fonseca@diku.dk>. + +GIT +--- +Part of the gitlink:git[7] suite -- cgit v0.10.2-6-g49f6 From c774b2dcf6c16a408757e9da1bf7c006528fc6a6 Mon Sep 17 00:00:00 2001 From: Art Haas <ahaas@airmail.net> Date: Tue, 19 Sep 2006 07:20:19 -0500 Subject: Patch for http-fetch.c and older curl releases Older curl releases do not define CURLE_HTTP_RETURNED_ERROR, they use CURLE_HTTP_NOT_FOUND instead. Newer curl releases keep the CURLE_HTTP_NOT_FOUND definition but using a -DCURL_NO_OLDIES preprocessor flag the old name will not be present in the 'curl.h' header. This patch makes our code written for newer releases of the curl library but allow compiling against an older curl (older than 0x070a03) by defining the missing CURLE_HTTP_RETURNED_ERROR as a synonym for CURLE_HTTP_NOT_FOUND. Signed-off-by: Art Haas <ahaas@airmail.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/http.h b/http.h index 9ca16ac..6e12e41 100644 --- a/http.h +++ b/http.h @@ -22,6 +22,10 @@ #define NO_CURL_EASY_DUPHANDLE #endif +#if LIBCURL_VERSION_NUM < 0x070a03 +#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND +#endif + struct slot_results { CURLcode curl_result; -- cgit v0.10.2-6-g49f6 From 8059319acc3638c8398d1bd34f647a2b28f48d5c Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 19 Sep 2006 13:57:03 +0200 Subject: gitweb: Fix mimetype_guess_file for files with multiple extensions Fix getting correct mimetype for "blob_plain" view for files which have multiple extensions, e.g. foo.1.html; now only the last extension is used to find mimetype. Noticed by Martin Waitz. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c77270c..969c2de 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1197,7 +1197,7 @@ sub mimetype_guess_file { } close(MIME); - $filename =~ /\.(.*?)$/; + $filename =~ /\.([^.]*)$/; return $mimemap{$1}; } -- cgit v0.10.2-6-g49f6 From 53cce84c05d3cba237ab45540b1e882be9c97215 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Tue, 19 Sep 2006 22:58:23 +0200 Subject: Fix broken sha1 locking Current git#next is totally broken wrt. cloning over HTTP, generating refs at random directories. Of course it's caused by the static get_pathname() buffer. lock_ref_sha1() stores return value of mkpath()'s get_pathname() call, then calls lock_ref_sha1_basic() which calls git_path(ref) which calls get_pathname() at that point returning pointer to the same buffer. So now you are sprintf()ing a format string into itself, wow! The resulting pathnames are really cute. (If you've been paying attention, yes, the mere fact that a format string _could_ write over itself is very wrong and probably exploitable here. See the other mail I've just sent.) I've never liked how we use return values of those functions so liberally, the "allow some random number of get_pathname() return values to work concurrently" is absolutely horrible pit and we've already fallen in this before IIRC. I consider it an awful coding practice, you add a call somewhere and at some other point some distant caller of that breaks since it reuses the same return values. Not to mention this takes quite some time to debug. My gut feeling tells me that there might be more of this. I don't have time to review the rest of the users of the refs.c functions though. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 134c0fc..7bd36e4 100644 --- a/refs.c +++ b/refs.c @@ -462,10 +462,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist) { + char refpath[PATH_MAX]; if (check_ref_format(ref)) return NULL; - return lock_ref_sha1_basic(mkpath("refs/%s", ref), - 5 + strlen(ref), old_sha1, mustexist); + strcpy(refpath, mkpath("refs/%s", ref)); + return lock_ref_sha1_basic(refpath, strlen(refpath), + old_sha1, mustexist); } struct ref_lock *lock_any_ref_for_update(const char *ref, -- cgit v0.10.2-6-g49f6 From 9581e0fca225927dbcbdf03fe70a1d2711ddc6b9 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Wed, 20 Sep 2006 06:14:54 +0200 Subject: Document git-show-ref [-s|--hash] option. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index 529ea17..b724d83 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] - [--tags] [--heads] [--] <pattern>... + [-s|--hash] [--tags] [--heads] [--] <pattern>... DESCRIPTION ----------- @@ -40,6 +40,12 @@ OPTIONS Dereference tags into object IDs. They will be shown with "^{}" appended. +-s, --hash:: + + Only show the SHA1 hash, not the reference name. When also using + --dereference the dereferenced tag will still be shown after the SHA1, + this maybe a bug. + --verify:: Enable stricter reference checking by requiring an exact ref path. @@ -72,6 +78,16 @@ $ git show-ref --head --dereference ... ----------------------------------------------------------------------------- +When using --hash (and not --dereference) the output format is: '<SHA-1 ID>' + +----------------------------------------------------------------------------- +$ git show-ref --heads --hash +2e3ba0114a1f52b47df29743d6915d056be13278 +185008ae97960c8d551adcd9e23565194651b5d1 +03adf42c988195b50e1a1935ba5fcbc39b2b029b +... +----------------------------------------------------------------------------- + EXAMPLE ------- -- cgit v0.10.2-6-g49f6 From 9c13359aaf3176428603cc9dfbdf30da889ab3d3 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Wed, 20 Sep 2006 06:21:25 +0200 Subject: Fix show-ref usage for --dereference. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 577d934..fab359b 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -3,7 +3,7 @@ #include "object.h" #include "tag.h" -static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--deref] [-s|--hash] [--tags] [--heads] [--] [pattern*]"; +static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash] [--tags] [--heads] [--] [pattern*]"; static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, found_match = 0, verify = 0, quiet = 0, hash_only = 0; -- cgit v0.10.2-6-g49f6 From 45ad9b5096b5b823f8cec562500dc8830d5961b5 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Wed, 20 Sep 2006 12:15:39 +0200 Subject: Fix trivial typos and inconsistencies in hooks documentation Pointed out by Alan Chandler. Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index 898b4aa..517f49b 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -5,8 +5,7 @@ Hooks are little scripts you can place in `$GIT_DIR/hooks` directory to trigger action at certain points. When `git-init-db` is run, a handful example hooks are copied in the `hooks` directory of the new repository, but by default they are -all disabled. To enable a hook, make it executable with `chmod -+x`. +all disabled. To enable a hook, make it executable with `chmod +x`. This document describes the currently defined hooks. @@ -16,16 +15,16 @@ applypatch-msg This hook is invoked by `git-applypatch` script, which is typically invoked by `git-applymbox`. It takes a single parameter, the name of the file that holds the proposed commit -log message. Exiting with non-zero status causes the -'git-applypatch' to abort before applying the patch. +log message. Exiting with non-zero status causes +`git-applypatch` to abort before applying the patch. The hook is allowed to edit the message file in place, and can be used to normalize the message into some project standard format (if the project has one). It can also be used to refuse the commit after inspecting the message file. -The default applypatch-msg hook, when enabled, runs the -commit-msg hook, if the latter is enabled. +The default 'applypatch-msg' hook, when enabled, runs the +'commit-msg' hook, if the latter is enabled. pre-applypatch -------------- @@ -39,8 +38,8 @@ after application of the patch not committed. It can be used to inspect the current working tree and refuse to make a commit if it does not pass certain test. -The default pre-applypatch hook, when enabled, runs the -pre-commit hook, if the latter is enabled. +The default 'pre-applypatch' hook, when enabled, runs the +'pre-commit' hook, if the latter is enabled. post-applypatch --------------- @@ -61,9 +60,9 @@ invoked before obtaining the proposed commit log message and making a commit. Exiting with non-zero status from this script causes the `git-commit` to abort. -The default pre-commit hook, when enabled, catches introduction +The default 'pre-commit' hook, when enabled, catches introduction of lines with trailing whitespaces and aborts the commit when -a such line is found. +such a line is found. commit-msg ---------- @@ -79,8 +78,8 @@ be used to normalize the message into some project standard format (if the project has one). It can also be used to refuse the commit after inspecting the message file. -The default commit-msg hook, when enabled, detects duplicate -Signed-off-by: lines, and aborts the commit when one is found. +The default 'commit-msg' hook, when enabled, detects duplicate +"Signed-off-by" lines, and aborts the commit if one is found. post-commit ----------- @@ -91,23 +90,24 @@ parameter, and is invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of `git-commit`. -The default post-commit hook, when enabled, demonstrates how to +The default 'post-commit' hook, when enabled, demonstrates how to send out a commit notification e-mail. update ------ This hook is invoked by `git-receive-pack` on the remote repository, -which is happens when a `git push` is done on a local repository. +which happens when a `git push` is done on a local repository. Just before updating the ref on the remote repository, the update hook is invoked. Its exit status determines the success or failure of the ref update. The hook executes once for each ref to be updated, and takes three parameters: - - the name of the ref being updated, - - the old object name stored in the ref, - - and the new objectname to be stored in the ref. + + - the name of the ref being updated, + - the old object name stored in the ref, + - and the new objectname to be stored in the ref. A zero exit from the update hook allows the ref to be updated. Exiting with a non-zero status prevents `git-receive-pack` @@ -126,16 +126,16 @@ Another use suggested on the mailing list is to use this hook to implement access control which is finer grained than the one based on filesystem group. -The standard output of this hook is sent to /dev/null; if you -want to report something to the git-send-pack on the other end, -you can redirect your output to your stderr. +The standard output of this hook is sent to `/dev/null`; if you +want to report something to the `git-send-pack` on the other end, +you can redirect your output to your `stderr`. post-update ----------- This hook is invoked by `git-receive-pack` on the remote repository, -which is happens when a `git push` is done on a local repository. +which happens when a `git push` is done on a local repository. It executes on the remote repository once after all the refs have been updated. @@ -145,16 +145,16 @@ name of ref that was actually updated. This hook is meant primarily for notification, and cannot affect the outcome of `git-receive-pack`. -The post-update hook can tell what are the heads that were pushed, +The 'post-update' hook can tell what are the heads that were pushed, but it does not know what their original and updated values are, so it is a poor place to do log old..new. -The default post-update hook, when enabled, runs +When enabled, the default 'post-update' hook runs `git-update-server-info` to keep the information used by dumb -transports (e.g., http) up-to-date. If you are publishing -a git repository that is accessible via http, you should +transports (e.g., HTTP) up-to-date. If you are publishing +a git repository that is accessible via HTTP, you should probably enable this hook. -The standard output of this hook is sent to /dev/null; if you -want to report something to the git-send-pack on the other end, -you can redirect your output to your stderr. +The standard output of this hook is sent to `/dev/null`; if you +want to report something to the `git-send-pack` on the other end, +you can redirect your output to your `stderr`. -- cgit v0.10.2-6-g49f6 From 44d2775a98c14daa77baa66b039080acad5efa0c Mon Sep 17 00:00:00 2001 From: Jonas Fonseca <fonseca@diku.dk> Date: Wed, 20 Sep 2006 12:23:41 +0200 Subject: gitk(1): mention --all Signed-off-by: Jonas Fonseca <fonseca@diku.dk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 23be005..f1aeb07 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -37,6 +37,10 @@ frequently used options. Show commits older than a specific date. +--all:: + + Show all branches. + <revs>:: Limit the revisions to show. This can be either a single revision @@ -63,6 +67,11 @@ gitk --since="2 weeks ago" \-- gitk:: The "--" is necessary to avoid confusion with the *branch* named 'gitk' +gitk --max-count=100 --all -- Makefile:: + + Show at most 100 changes made to the file 'Makefile'. Instead of only + looking for changes in the current branch look in all branches. + See Also -------- 'qgit(1)':: -- cgit v0.10.2-6-g49f6 From cd90e75ff4a9b01a9cf59505d8d10d79fd1071ca Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Wed, 20 Sep 2006 00:49:51 +0200 Subject: gitweb: Even more support for PATH_INFO based URLs Now the following types of path based URLs are supported: * project overview (summary) page of project * project/branch shortlog of branch * project/branch:file file in branch, blob_plain view * project/branch:dir/ directory listing of dir in branch, tree view The following shortcuts works (see explanation below): * project/branch: directory listing of branch, main tree view * project/:file file in HEAD (raw) * project/:dir/ directory listing of dir in HEAD * project/: directory listing of project's HEAD We use ':' as separator between branch (ref) name and file name (pathname) because valid branch (ref) name cannot have ':' inside. This limit applies to branch name only. This allow for hierarchical branches e.g. topic branch 'topic/subtopic', separate remotes tracking branches e.g. 'refs/remotes/origin/HEAD', and discriminate between head (branch) and tag with the same name. Empty branch should be interpreted as HEAD. If pathname (the part after ':') ends with '/', we assume that pathname is name of directory, and we want to show contents of said directory using "tree" view. If pathname is empty, it is equivalent to '/' (top directory). If pathname (the part after ':') does not end with '/', we assume that pathname is name of file, and we show contents of said file using "blob_plain" view. Pathname is stripped of leading '/', so we can use ':/' to separate branch from pathname. The rationale behind support for PATH_INFO based URLs was to support project web pages for small projects: just create an html branch and then use an URL like http://nowhere.com/gitweb.cgi/project.git/html:/index.html The ':/' syntax allow for working links between .html files served in such way, e.g. <a href="main.html"> link inside "index.html" would get http://nowhere.com/gitweb.cgi/project.git/html:/main.html. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 969c2de..5f597f7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -274,13 +274,16 @@ sub evaluate_path_info { return if defined $project; my $path_info = $ENV{"PATH_INFO"}; return if !$path_info; - $path_info =~ s,(^/|/$),,gs; - $path_info = validate_input($path_info); + $path_info =~ s,^/+,,; return if !$path_info; + # find which part of PATH_INFO is project $project = $path_info; + $project =~ s,/+$,,; while ($project && !-e "$projectroot/$project/HEAD") { $project =~ s,/*[^/]*$,,; } + # validate project + $project = validate_input($project); if (!$project || ($export_ok && !-e "$projectroot/$project/$export_ok") || ($strict_export && !project_in_list($project))) { @@ -289,15 +292,23 @@ sub evaluate_path_info { } # do not change any parameters if an action is given using the query string return if $action; - if ($path_info =~ m,^$project/([^/]+)/(.+)$,) { - # we got "project.git/branch/filename" - $action ||= "blob_plain"; - $hash_base ||= validate_input($1); - $file_name ||= validate_input($2); - } elsif ($path_info =~ m,^$project/([^/]+)$,) { + $path_info =~ s,^$project/*,,; + my ($refname, $pathname) = split(/:/, $path_info, 2); + if (defined $pathname) { + # we got "project.git/branch:filename" or "project.git/branch:dir/" + # we could use git_get_type(branch:pathname), but it needs $git_dir + $pathname =~ s,^/+,,; + if (!$pathname || substr($pathname, -1) eq "/") { + $action ||= "tree"; + } else { + $action ||= "blob_plain"; + } + $hash_base ||= validate_input($refname); + $file_name ||= validate_input($pathname); + } elsif (defined $refname) { # we got "project.git/branch" $action ||= "shortlog"; - $hash ||= validate_input($1); + $hash ||= validate_input($refname); } } evaluate_path_info(); -- cgit v0.10.2-6-g49f6 From d04d3d424b913332f5c400162f0d87faac1ad3ea Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 19 Sep 2006 21:53:22 +0200 Subject: gitweb: Require project for almost all actions Require that project (repository) is given for all actions except project_list, project_index and opml. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5f597f7..7fd2e19 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -352,6 +352,10 @@ if (defined $project) { if (!defined($actions{$action})) { die_error(undef, "Unknown action"); } +if ($action !~ m/^(opml|project_list|project_index)$/ && + !$project) { + die_error(undef, "Project needed"); +} $actions{$action}->(); exit; -- cgit v0.10.2-6-g49f6 From 1f24c58724a64e7b100ae8d8e0318c9e564df88b Mon Sep 17 00:00:00 2001 From: Andy Whitcroft <apw@shadowen.org> Date: Wed, 20 Sep 2006 17:37:04 +0100 Subject: cvsimport: move over to using git-for-each-ref to read refs. cvsimport opens all of the files in $GIT_DIR/refs/heads and reads out the sha1's in order to work out what time the last commit on that branch was made (in CVS) thus allowing incremental updates. However, this takes no account of hierachical refs naming producing the following error for each directory in $GIT_DIR/refs: Use of uninitialized value in chomp at /usr/bin/git-cvsimport line 503. Use of uninitialized value in concatenation (.) or string at /usr/bin/git-cvsimport line 505. usage: git-cat-file [-t|-s|-e|-p|<type>] <sha1> Take advantage of the new packed refs work to use the new for-each-ref iterator to get this information. Signed-off-by: Andy Whitcroft <apw@shadowen.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-cvsimport.perl b/git-cvsimport.perl index e5a00a1..14e2c61 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -495,22 +495,17 @@ unless(-d $git_dir) { $tip_at_start = `git-rev-parse --verify HEAD`; # Get the last import timestamps - opendir(D,"$git_dir/refs/heads"); - while(defined(my $head = readdir(D))) { - next if $head =~ /^\./; - open(F,"$git_dir/refs/heads/$head") - or die "Bad head branch: $head: $!\n"; - chomp(my $ftag = <F>); - close(F); - open(F,"git-cat-file commit $ftag |"); - while(<F>) { - next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/; - $branch_date{$head} = $1; - last; - } - close(F); + my $fmt = '($ref, $author) = (%(refname), %(author));'; + open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or + die "Cannot run git-for-each-ref: $!\n"; + while(defined(my $entry = <H>)) { + my ($ref, $author); + eval($entry) || die "cannot eval refs list: $@"; + my ($head) = ($ref =~ m|^refs/heads/(.*)|); + $author =~ /^.*\s(\d+)\s[-+]\d{4}$/; + $branch_date{$head} = $1; } - closedir(D); + close(H); } -d $git_dir -- cgit v0.10.2-6-g49f6 From 9704d75ddc3e38f4945e23f5afffb849fb51b09f Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 19 Sep 2006 14:31:49 +0200 Subject: gitweb: Always use git-peek-remote in git_get_references Instead of trying to read info/refs file, which might not be present (we did fallback to git-ls-remote), always use git-peek-remote in git_get_references. It is preparation for git_get_refs_info to also return references info. We should not use info/refs for git_get_refs_info as the repository is not served for http-fetch clients. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7fd2e19..532bd00 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -843,16 +843,10 @@ sub git_get_project_owner { sub git_get_references { my $type = shift || ""; my %refs; - my $fd; # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - if (-f "$projectroot/$project/info/refs") { - open $fd, "$projectroot/$project/info/refs" - or return; - } else { - open $fd, "-|", git_cmd(), "ls-remote", "." - or return; - } + open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" + or return; while (my $line = <$fd>) { chomp $line; -- cgit v0.10.2-6-g49f6 From 120ddde2a843e923944abd5d6e61f8625e820e92 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 19 Sep 2006 14:33:22 +0200 Subject: gitweb: Make git_get_refs_list do work of git_get_references Make git_get_refs_list do also work of git_get_references, to avoid calling git-peek-remote twice. Change meaning of git_get_refs_list meaning: it is now type, and not a full path, e.g. we now use git_get_refs_list("heads") instead of former git_get_refs_list("refs/heads"). Modify git_summary to use only one call to git_get_refs_list instead of one call to git_get_references and two to git_get_refs_list. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 532bd00..0d13b33 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1134,7 +1134,8 @@ sub parse_ls_tree_line ($;%) { ## parse to array of hashes functions sub git_get_refs_list { - my $ref_dir = shift; + my $type = shift || ""; + my %refs; my @reflist; my @refs; @@ -1142,14 +1143,21 @@ sub git_get_refs_list { or return; while (my $line = <$fd>) { chomp $line; - if ($line =~ m/^([0-9a-fA-F]{40})\t$ref_dir\/?([^\^]+)$/) { - push @refs, { hash => $1, name => $2 }; - } elsif ($line =~ m/^[0-9a-fA-F]{40}\t$ref_dir\/?(.*)\^\{\}$/ && - $1 eq $refs[-1]{'name'}) { - # most likely a tag is followed by its peeled - # (deref) one, and when that happens we know the - # previous one was of type 'tag'. - $refs[-1]{'type'} = "tag"; + if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) { + if (defined $refs{$1}) { + push @{$refs{$1}}, $2; + } else { + $refs{$1} = [ $2 ]; + } + + if (! $4) { # unpeeled, direct reference + push @refs, { hash => $1, name => $3 }; # without type + } elsif ($3 eq $refs[-1]{'name'}) { + # most likely a tag is followed by its peeled + # (deref) one, and when that happens we know the + # previous one was of type 'tag'. + $refs[-1]{'type'} = "tag"; + } } } close $fd; @@ -1165,7 +1173,7 @@ sub git_get_refs_list { } # sort refs by age @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return \@reflist; + return (\@reflist, \%refs); } ## ---------------------------------------------------------------------- @@ -2129,14 +2137,14 @@ sub git_tags_body { sub git_heads_body { # uses global variable $project - my ($taglist, $head, $from, $to, $extra) = @_; + my ($headlist, $head, $from, $to, $extra) = @_; $from = 0 unless defined $from; - $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to); print "<table class=\"heads\" cellspacing=\"0\">\n"; my $alternate = 0; for (my $i = $from; $i <= $to; $i++) { - my $entry = $taglist->[$i]; + my $entry = $headlist->[$i]; my %tag = %$entry; my $curr = $tag{'id'} eq $head; if ($alternate) { @@ -2306,7 +2314,19 @@ sub git_summary { my $owner = git_get_project_owner($project); - my $refs = git_get_references(); + my ($reflist, $refs) = git_get_refs_list(); + + my @taglist; + my @headlist; + foreach my $ref (@$reflist) { + if ($ref->{'name'} =~ s!^heads/!!) { + push @headlist, $ref; + } else { + $ref->{'name'} =~ s!^tags/!!; + push @taglist, $ref; + } + } + git_header_html(); git_print_page_nav('summary','', $head); @@ -2336,17 +2356,15 @@ sub git_summary { git_shortlog_body(\@revlist, 0, 15, $refs, $cgi->a({-href => href(action=>"shortlog")}, "...")); - my $taglist = git_get_refs_list("refs/tags"); - if (defined @$taglist) { + if (@taglist) { git_print_header_div('tags'); - git_tags_body($taglist, 0, 15, + git_tags_body(\@taglist, 0, 15, $cgi->a({-href => href(action=>"tags")}, "...")); } - my $headlist = git_get_refs_list("refs/heads"); - if (defined @$headlist) { + if (@headlist) { git_print_header_div('heads'); - git_heads_body($headlist, $head, 0, 15, + git_heads_body(\@headlist, $head, 0, 15, $cgi->a({-href => href(action=>"heads")}, "...")); } @@ -2557,7 +2575,7 @@ sub git_tags { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my $taglist = git_get_refs_list("refs/tags"); + my ($taglist) = git_get_refs_list("tags"); if (defined @$taglist) { git_tags_body($taglist); } @@ -2570,9 +2588,9 @@ sub git_heads { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my $taglist = git_get_refs_list("refs/heads"); - if (defined @$taglist) { - git_heads_body($taglist, $head); + my ($headlist) = git_get_refs_list("heads"); + if (defined @$headlist) { + git_heads_body($headlist, $head); } git_footer_html(); } -- cgit v0.10.2-6-g49f6 From 62e27f273d66afa996cb7aee6cdb25fbedc053f6 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 19 Sep 2006 20:47:27 +0200 Subject: gitweb: Fix thinko in git_tags and git_heads git_get_refs_list always return reference to list (and reference to hash which we ignore), so $taglist (in git_tags) and $headlist (in git_heads) are always defined, but @$taglist / @$headlist might be empty. Replaced incorrect "if (defined @$taglist)" with "if (@$taglist)" in git_tags and respectively in git_heads. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0d13b33..baadbe7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2576,7 +2576,7 @@ sub git_tags { git_print_header_div('summary', $project); my ($taglist) = git_get_refs_list("tags"); - if (defined @$taglist) { + if (@$taglist) { git_tags_body($taglist); } git_footer_html(); @@ -2589,7 +2589,7 @@ sub git_heads { git_print_header_div('summary', $project); my ($headlist) = git_get_refs_list("heads"); - if (defined @$headlist) { + if (@$headlist) { git_heads_body($headlist, $head); } git_footer_html(); -- cgit v0.10.2-6-g49f6 From 83b5d2f5b0c95fe102bc3d1cc2947abbdf5e5c5b Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 17 Sep 2006 16:02:52 -0700 Subject: builtin-grep: make pieces of it available as library. This makes three functions and associated option structures from builtin-grep available from other parts of the system. * options to drive built-in grep engine is stored in struct grep_opt; * pattern strings and extended grep expressions are added to struct grep_opt with append_grep_pattern(); * when finished calling append_grep_pattern(), call compile_grep_patterns() to prepare for execution; * call grep_buffer() to find matches in the in-core buffer. This also adds an internal option "status_only" to grep_opt, which suppresses any output from grep_buffer(). Callers of the function as library can use it to check if there is a match without producing any output. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 8467447..b15aa9d 100644 --- a/Makefile +++ b/Makefile @@ -234,7 +234,7 @@ LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a LIB_H = \ - archive.h blob.h cache.h commit.h csum-file.h delta.h \ + archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h @@ -252,7 +252,7 @@ LIB_OBJS = \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - write_or_die.o trace.o list-objects.o \ + write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o diff --git a/builtin-grep.c b/builtin-grep.c index ed87a55..6718788 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -11,6 +11,7 @@ #include "tree-walk.h" #include "builtin.h" #include <regex.h> +#include "grep.h" #include <fnmatch.h> #include <sys/wait.h> @@ -82,498 +83,6 @@ static int pathspec_matches(const char **paths, const char *name) return 0; } -enum grep_pat_token { - GREP_PATTERN, - GREP_AND, - GREP_OPEN_PAREN, - GREP_CLOSE_PAREN, - GREP_NOT, - GREP_OR, -}; - -struct grep_pat { - struct grep_pat *next; - const char *origin; - int no; - enum grep_pat_token token; - const char *pattern; - regex_t regexp; -}; - -enum grep_expr_node { - GREP_NODE_ATOM, - GREP_NODE_NOT, - GREP_NODE_AND, - GREP_NODE_OR, -}; - -struct grep_expr { - enum grep_expr_node node; - union { - struct grep_pat *atom; - struct grep_expr *unary; - struct { - struct grep_expr *left; - struct grep_expr *right; - } binary; - } u; -}; - -struct grep_opt { - struct grep_pat *pattern_list; - struct grep_pat **pattern_tail; - struct grep_expr *pattern_expression; - int prefix_length; - regex_t regexp; - unsigned linenum:1; - unsigned invert:1; - unsigned name_only:1; - unsigned unmatch_name_only:1; - unsigned count:1; - unsigned word_regexp:1; - unsigned fixed:1; -#define GREP_BINARY_DEFAULT 0 -#define GREP_BINARY_NOMATCH 1 -#define GREP_BINARY_TEXT 2 - unsigned binary:2; - unsigned extended:1; - unsigned relative:1; - unsigned pathname:1; - int regflags; - unsigned pre_context; - unsigned post_context; -}; - -static void add_pattern(struct grep_opt *opt, const char *pat, - const char *origin, int no, enum grep_pat_token t) -{ - struct grep_pat *p = xcalloc(1, sizeof(*p)); - p->pattern = pat; - p->origin = origin; - p->no = no; - p->token = t; - *opt->pattern_tail = p; - opt->pattern_tail = &p->next; - p->next = NULL; -} - -static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) -{ - int err = regcomp(&p->regexp, p->pattern, opt->regflags); - if (err) { - char errbuf[1024]; - char where[1024]; - if (p->no) - sprintf(where, "In '%s' at %d, ", - p->origin, p->no); - else if (p->origin) - sprintf(where, "%s, ", p->origin); - else - where[0] = 0; - regerror(err, &p->regexp, errbuf, 1024); - regfree(&p->regexp); - die("%s'%s': %s", where, p->pattern, errbuf); - } -} - -static struct grep_expr *compile_pattern_expr(struct grep_pat **); -static struct grep_expr *compile_pattern_atom(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x; - - p = *list; - switch (p->token) { - case GREP_PATTERN: /* atom */ - x = xcalloc(1, sizeof (struct grep_expr)); - x->node = GREP_NODE_ATOM; - x->u.atom = p; - *list = p->next; - return x; - case GREP_OPEN_PAREN: - *list = p->next; - x = compile_pattern_expr(list); - if (!x) - return NULL; - if (!*list || (*list)->token != GREP_CLOSE_PAREN) - die("unmatched parenthesis"); - *list = (*list)->next; - return x; - default: - return NULL; - } -} - -static struct grep_expr *compile_pattern_not(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x; - - p = *list; - switch (p->token) { - case GREP_NOT: - if (!p->next) - die("--not not followed by pattern expression"); - *list = p->next; - x = xcalloc(1, sizeof (struct grep_expr)); - x->node = GREP_NODE_NOT; - x->u.unary = compile_pattern_not(list); - if (!x->u.unary) - die("--not followed by non pattern expression"); - return x; - default: - return compile_pattern_atom(list); - } -} - -static struct grep_expr *compile_pattern_and(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x, *y, *z; - - x = compile_pattern_not(list); - p = *list; - if (p && p->token == GREP_AND) { - if (!p->next) - die("--and not followed by pattern expression"); - *list = p->next; - y = compile_pattern_and(list); - if (!y) - die("--and not followed by pattern expression"); - z = xcalloc(1, sizeof (struct grep_expr)); - z->node = GREP_NODE_AND; - z->u.binary.left = x; - z->u.binary.right = y; - return z; - } - return x; -} - -static struct grep_expr *compile_pattern_or(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x, *y, *z; - - x = compile_pattern_and(list); - p = *list; - if (x && p && p->token != GREP_CLOSE_PAREN) { - y = compile_pattern_or(list); - if (!y) - die("not a pattern expression %s", p->pattern); - z = xcalloc(1, sizeof (struct grep_expr)); - z->node = GREP_NODE_OR; - z->u.binary.left = x; - z->u.binary.right = y; - return z; - } - return x; -} - -static struct grep_expr *compile_pattern_expr(struct grep_pat **list) -{ - return compile_pattern_or(list); -} - -static void compile_patterns(struct grep_opt *opt) -{ - struct grep_pat *p; - - /* First compile regexps */ - for (p = opt->pattern_list; p; p = p->next) { - if (p->token == GREP_PATTERN) - compile_regexp(p, opt); - else - opt->extended = 1; - } - - if (!opt->extended) - return; - - /* Then bundle them up in an expression. - * A classic recursive descent parser would do. - */ - p = opt->pattern_list; - opt->pattern_expression = compile_pattern_expr(&p); - if (p) - die("incomplete pattern expression: %s", p->pattern); -} - -static char *end_of_line(char *cp, unsigned long *left) -{ - unsigned long l = *left; - while (l && *cp != '\n') { - l--; - cp++; - } - *left = l; - return cp; -} - -static int word_char(char ch) -{ - return isalnum(ch) || ch == '_'; -} - -static void show_line(struct grep_opt *opt, const char *bol, const char *eol, - const char *name, unsigned lno, char sign) -{ - if (opt->pathname) - printf("%s%c", name, sign); - if (opt->linenum) - printf("%d%c", lno, sign); - printf("%.*s\n", (int)(eol-bol), bol); -} - -/* - * NEEDSWORK: share code with diff.c - */ -#define FIRST_FEW_BYTES 8000 -static int buffer_is_binary(const char *ptr, unsigned long size) -{ - if (FIRST_FEW_BYTES < size) - size = FIRST_FEW_BYTES; - return !!memchr(ptr, 0, size); -} - -static int fixmatch(const char *pattern, char *line, regmatch_t *match) -{ - char *hit = strstr(line, pattern); - if (!hit) { - match->rm_so = match->rm_eo = -1; - return REG_NOMATCH; - } - else { - match->rm_so = hit - line; - match->rm_eo = match->rm_so + strlen(pattern); - return 0; - } -} - -static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) -{ - int hit = 0; - int at_true_bol = 1; - regmatch_t pmatch[10]; - - again: - if (!opt->fixed) { - regex_t *exp = &p->regexp; - hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), - pmatch, 0); - } - else { - hit = !fixmatch(p->pattern, bol, pmatch); - } - - if (hit && opt->word_regexp) { - if ((pmatch[0].rm_so < 0) || - (eol - bol) <= pmatch[0].rm_so || - (pmatch[0].rm_eo < 0) || - (eol - bol) < pmatch[0].rm_eo) - die("regexp returned nonsense"); - - /* Match beginning must be either beginning of the - * line, or at word boundary (i.e. the last char must - * not be a word char). Similarly, match end must be - * either end of the line, or at word boundary - * (i.e. the next char must not be a word char). - */ - if ( ((pmatch[0].rm_so == 0 && at_true_bol) || - !word_char(bol[pmatch[0].rm_so-1])) && - ((pmatch[0].rm_eo == (eol-bol)) || - !word_char(bol[pmatch[0].rm_eo])) ) - ; - else - hit = 0; - - if (!hit && pmatch[0].rm_so + bol + 1 < eol) { - /* There could be more than one match on the - * line, and the first match might not be - * strict word match. But later ones could be! - */ - bol = pmatch[0].rm_so + bol + 1; - at_true_bol = 0; - goto again; - } - } - return hit; -} - -static int match_expr_eval(struct grep_opt *opt, - struct grep_expr *x, - char *bol, char *eol) -{ - switch (x->node) { - case GREP_NODE_ATOM: - return match_one_pattern(opt, x->u.atom, bol, eol); - break; - case GREP_NODE_NOT: - return !match_expr_eval(opt, x->u.unary, bol, eol); - case GREP_NODE_AND: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) && - match_expr_eval(opt, x->u.binary.right, bol, eol)); - case GREP_NODE_OR: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) || - match_expr_eval(opt, x->u.binary.right, bol, eol)); - } - die("Unexpected node type (internal error) %d\n", x->node); -} - -static int match_expr(struct grep_opt *opt, char *bol, char *eol) -{ - struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol); -} - -static int match_line(struct grep_opt *opt, char *bol, char *eol) -{ - struct grep_pat *p; - if (opt->extended) - return match_expr(opt, bol, eol); - for (p = opt->pattern_list; p; p = p->next) { - if (match_one_pattern(opt, p, bol, eol)) - return 1; - } - return 0; -} - -static int grep_buffer(struct grep_opt *opt, const char *name, - char *buf, unsigned long size) -{ - char *bol = buf; - unsigned long left = size; - unsigned lno = 1; - struct pre_context_line { - char *bol; - char *eol; - } *prev = NULL, *pcl; - unsigned last_hit = 0; - unsigned last_shown = 0; - int binary_match_only = 0; - const char *hunk_mark = ""; - unsigned count = 0; - - if (buffer_is_binary(buf, size)) { - switch (opt->binary) { - case GREP_BINARY_DEFAULT: - binary_match_only = 1; - break; - case GREP_BINARY_NOMATCH: - return 0; /* Assume unmatch */ - break; - default: - break; - } - } - - if (opt->pre_context) - prev = xcalloc(opt->pre_context, sizeof(*prev)); - if (opt->pre_context || opt->post_context) - hunk_mark = "--\n"; - - while (left) { - char *eol, ch; - int hit = 0; - - eol = end_of_line(bol, &left); - ch = *eol; - *eol = 0; - - hit = match_line(opt, bol, eol); - - /* "grep -v -e foo -e bla" should list lines - * that do not have either, so inversion should - * be done outside. - */ - if (opt->invert) - hit = !hit; - if (opt->unmatch_name_only) { - if (hit) - return 0; - goto next_line; - } - if (hit) { - count++; - if (binary_match_only) { - printf("Binary file %s matches\n", name); - return 1; - } - if (opt->name_only) { - printf("%s\n", name); - return 1; - } - /* Hit at this line. If we haven't shown the - * pre-context lines, we would need to show them. - * When asked to do "count", this still show - * the context which is nonsense, but the user - * deserves to get that ;-). - */ - if (opt->pre_context) { - unsigned from; - if (opt->pre_context < lno) - from = lno - opt->pre_context; - else - from = 1; - if (from <= last_shown) - from = last_shown + 1; - if (last_shown && from != last_shown + 1) - printf(hunk_mark); - while (from < lno) { - pcl = &prev[lno-from-1]; - show_line(opt, pcl->bol, pcl->eol, - name, from, '-'); - from++; - } - last_shown = lno-1; - } - if (last_shown && lno != last_shown + 1) - printf(hunk_mark); - if (!opt->count) - show_line(opt, bol, eol, name, lno, ':'); - last_shown = last_hit = lno; - } - else if (last_hit && - lno <= last_hit + opt->post_context) { - /* If the last hit is within the post context, - * we need to show this line. - */ - if (last_shown && lno != last_shown + 1) - printf(hunk_mark); - show_line(opt, bol, eol, name, lno, '-'); - last_shown = lno; - } - if (opt->pre_context) { - memmove(prev+1, prev, - (opt->pre_context-1) * sizeof(*prev)); - prev->bol = bol; - prev->eol = eol; - } - - next_line: - *eol = ch; - bol = eol + 1; - if (!left) - break; - left--; - lno++; - } - - if (opt->unmatch_name_only) { - /* We did not see any hit, so we want to show this */ - printf("%s\n", name); - return 1; - } - - /* NEEDSWORK: - * The real "grep -c foo *.c" gives many "bar.c:0" lines, - * which feels mostly useless but sometimes useful. Maybe - * make it another option? For now suppress them. - */ - if (opt->count && count) - printf("%s:%u\n", name, count); - return !!last_hit; -} - static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len) { unsigned long size; @@ -1055,8 +564,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* ignore empty line like grep does */ if (!buf[0]) continue; - add_pattern(&opt, xstrdup(buf), argv[1], ++lno, - GREP_PATTERN); + append_grep_pattern(&opt, xstrdup(buf), + argv[1], ++lno, + GREP_PATTERN); } fclose(patterns); argv++; @@ -1064,27 +574,32 @@ int cmd_grep(int argc, const char **argv, const char *prefix) continue; } if (!strcmp("--not", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_NOT); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_NOT); continue; } if (!strcmp("--and", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_AND); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_AND); continue; } if (!strcmp("--or", arg)) continue; /* no-op */ if (!strcmp("(", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_OPEN_PAREN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_OPEN_PAREN); continue; } if (!strcmp(")", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_CLOSE_PAREN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_CLOSE_PAREN); continue; } if (!strcmp("-e", arg)) { if (1 < argc) { - add_pattern(&opt, argv[1], "-e option", 0, - GREP_PATTERN); + append_grep_pattern(&opt, argv[1], + "-e option", 0, + GREP_PATTERN); argv++; argc--; continue; @@ -1106,8 +621,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* First unrecognized non-option token */ if (!opt.pattern_list) { - add_pattern(&opt, arg, "command line", 0, - GREP_PATTERN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_PATTERN); break; } else { @@ -1124,8 +639,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) die("no pattern given."); if ((opt.regflags != REG_NEWLINE) && opt.fixed) die("cannot mix --fixed-strings and regexp"); - if (!opt.fixed) - compile_patterns(&opt); + compile_grep_patterns(&opt); /* Check revs and then paths */ for (i = 1; i < argc; i++) { diff --git a/grep.c b/grep.c new file mode 100644 index 0000000..61db6e1 --- /dev/null +++ b/grep.c @@ -0,0 +1,440 @@ +#include "cache.h" +#include <regex.h> +#include "grep.h" + +void append_grep_pattern(struct grep_opt *opt, const char *pat, + const char *origin, int no, enum grep_pat_token t) +{ + struct grep_pat *p = xcalloc(1, sizeof(*p)); + p->pattern = pat; + p->origin = origin; + p->no = no; + p->token = t; + *opt->pattern_tail = p; + opt->pattern_tail = &p->next; + p->next = NULL; +} + +static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + int err = regcomp(&p->regexp, p->pattern, opt->regflags); + if (err) { + char errbuf[1024]; + char where[1024]; + if (p->no) + sprintf(where, "In '%s' at %d, ", + p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + regerror(err, &p->regexp, errbuf, 1024); + regfree(&p->regexp); + die("%s'%s': %s", where, p->pattern, errbuf); + } +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **); +static struct grep_expr *compile_pattern_atom(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + p = *list; + switch (p->token) { + case GREP_PATTERN: /* atom */ + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_ATOM; + x->u.atom = p; + *list = p->next; + return x; + case GREP_OPEN_PAREN: + *list = p->next; + x = compile_pattern_expr(list); + if (!x) + return NULL; + if (!*list || (*list)->token != GREP_CLOSE_PAREN) + die("unmatched parenthesis"); + *list = (*list)->next; + return x; + default: + return NULL; + } +} + +static struct grep_expr *compile_pattern_not(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + p = *list; + switch (p->token) { + case GREP_NOT: + if (!p->next) + die("--not not followed by pattern expression"); + *list = p->next; + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_NOT; + x->u.unary = compile_pattern_not(list); + if (!x->u.unary) + die("--not followed by non pattern expression"); + return x; + default: + return compile_pattern_atom(list); + } +} + +static struct grep_expr *compile_pattern_and(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + x = compile_pattern_not(list); + p = *list; + if (p && p->token == GREP_AND) { + if (!p->next) + die("--and not followed by pattern expression"); + *list = p->next; + y = compile_pattern_and(list); + if (!y) + die("--and not followed by pattern expression"); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_AND; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_or(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + x = compile_pattern_and(list); + p = *list; + if (x && p && p->token != GREP_CLOSE_PAREN) { + y = compile_pattern_or(list); + if (!y) + die("not a pattern expression %s", p->pattern); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_OR; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **list) +{ + return compile_pattern_or(list); +} + +void compile_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p; + + if (opt->fixed) + return; + + /* First compile regexps */ + for (p = opt->pattern_list; p; p = p->next) { + if (p->token == GREP_PATTERN) + compile_regexp(p, opt); + else + opt->extended = 1; + } + + if (!opt->extended) + return; + + /* Then bundle them up in an expression. + * A classic recursive descent parser would do. + */ + p = opt->pattern_list; + opt->pattern_expression = compile_pattern_expr(&p); + if (p) + die("incomplete pattern expression: %s", p->pattern); +} + +static char *end_of_line(char *cp, unsigned long *left) +{ + unsigned long l = *left; + while (l && *cp != '\n') { + l--; + cp++; + } + *left = l; + return cp; +} + +static int word_char(char ch) +{ + return isalnum(ch) || ch == '_'; +} + +static void show_line(struct grep_opt *opt, const char *bol, const char *eol, + const char *name, unsigned lno, char sign) +{ + if (opt->pathname) + printf("%s%c", name, sign); + if (opt->linenum) + printf("%d%c", lno, sign); + printf("%.*s\n", (int)(eol-bol), bol); +} + +/* + * NEEDSWORK: share code with diff.c + */ +#define FIRST_FEW_BYTES 8000 +static int buffer_is_binary(const char *ptr, unsigned long size) +{ + if (FIRST_FEW_BYTES < size) + size = FIRST_FEW_BYTES; + return !!memchr(ptr, 0, size); +} + +static int fixmatch(const char *pattern, char *line, regmatch_t *match) +{ + char *hit = strstr(line, pattern); + if (!hit) { + match->rm_so = match->rm_eo = -1; + return REG_NOMATCH; + } + else { + match->rm_so = hit - line; + match->rm_eo = match->rm_so + strlen(pattern); + return 0; + } +} + +static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) +{ + int hit = 0; + int at_true_bol = 1; + regmatch_t pmatch[10]; + + again: + if (!opt->fixed) { + regex_t *exp = &p->regexp; + hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), + pmatch, 0); + } + else { + hit = !fixmatch(p->pattern, bol, pmatch); + } + + if (hit && opt->word_regexp) { + if ((pmatch[0].rm_so < 0) || + (eol - bol) <= pmatch[0].rm_so || + (pmatch[0].rm_eo < 0) || + (eol - bol) < pmatch[0].rm_eo) + die("regexp returned nonsense"); + + /* Match beginning must be either beginning of the + * line, or at word boundary (i.e. the last char must + * not be a word char). Similarly, match end must be + * either end of the line, or at word boundary + * (i.e. the next char must not be a word char). + */ + if ( ((pmatch[0].rm_so == 0 && at_true_bol) || + !word_char(bol[pmatch[0].rm_so-1])) && + ((pmatch[0].rm_eo == (eol-bol)) || + !word_char(bol[pmatch[0].rm_eo])) ) + ; + else + hit = 0; + + if (!hit && pmatch[0].rm_so + bol + 1 < eol) { + /* There could be more than one match on the + * line, and the first match might not be + * strict word match. But later ones could be! + */ + bol = pmatch[0].rm_so + bol + 1; + at_true_bol = 0; + goto again; + } + } + return hit; +} + +static int match_expr_eval(struct grep_opt *opt, + struct grep_expr *x, + char *bol, char *eol) +{ + switch (x->node) { + case GREP_NODE_ATOM: + return match_one_pattern(opt, x->u.atom, bol, eol); + break; + case GREP_NODE_NOT: + return !match_expr_eval(opt, x->u.unary, bol, eol); + case GREP_NODE_AND: + return (match_expr_eval(opt, x->u.binary.left, bol, eol) && + match_expr_eval(opt, x->u.binary.right, bol, eol)); + case GREP_NODE_OR: + return (match_expr_eval(opt, x->u.binary.left, bol, eol) || + match_expr_eval(opt, x->u.binary.right, bol, eol)); + } + die("Unexpected node type (internal error) %d\n", x->node); +} + +static int match_expr(struct grep_opt *opt, char *bol, char *eol) +{ + struct grep_expr *x = opt->pattern_expression; + return match_expr_eval(opt, x, bol, eol); +} + +static int match_line(struct grep_opt *opt, char *bol, char *eol) +{ + struct grep_pat *p; + if (opt->extended) + return match_expr(opt, bol, eol); + for (p = opt->pattern_list; p; p = p->next) { + if (match_one_pattern(opt, p, bol, eol)) + return 1; + } + return 0; +} + +int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) +{ + char *bol = buf; + unsigned long left = size; + unsigned lno = 1; + struct pre_context_line { + char *bol; + char *eol; + } *prev = NULL, *pcl; + unsigned last_hit = 0; + unsigned last_shown = 0; + int binary_match_only = 0; + const char *hunk_mark = ""; + unsigned count = 0; + + if (buffer_is_binary(buf, size)) { + switch (opt->binary) { + case GREP_BINARY_DEFAULT: + binary_match_only = 1; + break; + case GREP_BINARY_NOMATCH: + return 0; /* Assume unmatch */ + break; + default: + break; + } + } + + if (opt->pre_context) + prev = xcalloc(opt->pre_context, sizeof(*prev)); + if (opt->pre_context || opt->post_context) + hunk_mark = "--\n"; + + while (left) { + char *eol, ch; + int hit = 0; + + eol = end_of_line(bol, &left); + ch = *eol; + *eol = 0; + + hit = match_line(opt, bol, eol); + *eol = ch; + + /* "grep -v -e foo -e bla" should list lines + * that do not have either, so inversion should + * be done outside. + */ + if (opt->invert) + hit = !hit; + if (opt->unmatch_name_only) { + if (hit) + return 0; + goto next_line; + } + if (hit) { + count++; + if (opt->status_only) + return 1; + if (binary_match_only) { + printf("Binary file %s matches\n", name); + return 1; + } + if (opt->name_only) { + printf("%s\n", name); + return 1; + } + /* Hit at this line. If we haven't shown the + * pre-context lines, we would need to show them. + * When asked to do "count", this still show + * the context which is nonsense, but the user + * deserves to get that ;-). + */ + if (opt->pre_context) { + unsigned from; + if (opt->pre_context < lno) + from = lno - opt->pre_context; + else + from = 1; + if (from <= last_shown) + from = last_shown + 1; + if (last_shown && from != last_shown + 1) + printf(hunk_mark); + while (from < lno) { + pcl = &prev[lno-from-1]; + show_line(opt, pcl->bol, pcl->eol, + name, from, '-'); + from++; + } + last_shown = lno-1; + } + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + if (!opt->count) + show_line(opt, bol, eol, name, lno, ':'); + last_shown = last_hit = lno; + } + else if (last_hit && + lno <= last_hit + opt->post_context) { + /* If the last hit is within the post context, + * we need to show this line. + */ + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + show_line(opt, bol, eol, name, lno, '-'); + last_shown = lno; + } + if (opt->pre_context) { + memmove(prev+1, prev, + (opt->pre_context-1) * sizeof(*prev)); + prev->bol = bol; + prev->eol = eol; + } + + next_line: + bol = eol + 1; + if (!left) + break; + left--; + lno++; + } + + if (opt->status_only) + return 0; + if (opt->unmatch_name_only) { + /* We did not see any hit, so we want to show this */ + printf("%s\n", name); + return 1; + } + + /* NEEDSWORK: + * The real "grep -c foo *.c" gives many "bar.c:0" lines, + * which feels mostly useless but sometimes useful. Maybe + * make it another option? For now suppress them. + */ + if (opt->count && count) + printf("%s:%u\n", name, count); + return !!last_hit; +} + diff --git a/grep.h b/grep.h new file mode 100644 index 0000000..80122b0 --- /dev/null +++ b/grep.h @@ -0,0 +1,71 @@ +#ifndef GREP_H +#define GREP_H + +enum grep_pat_token { + GREP_PATTERN, + GREP_AND, + GREP_OPEN_PAREN, + GREP_CLOSE_PAREN, + GREP_NOT, + GREP_OR, +}; + +struct grep_pat { + struct grep_pat *next; + const char *origin; + int no; + enum grep_pat_token token; + const char *pattern; + regex_t regexp; +}; + +enum grep_expr_node { + GREP_NODE_ATOM, + GREP_NODE_NOT, + GREP_NODE_AND, + GREP_NODE_OR, +}; + +struct grep_expr { + enum grep_expr_node node; + union { + struct grep_pat *atom; + struct grep_expr *unary; + struct { + struct grep_expr *left; + struct grep_expr *right; + } binary; + } u; +}; + +struct grep_opt { + struct grep_pat *pattern_list; + struct grep_pat **pattern_tail; + struct grep_expr *pattern_expression; + int prefix_length; + regex_t regexp; + unsigned linenum:1; + unsigned invert:1; + unsigned status_only:1; + unsigned name_only:1; + unsigned unmatch_name_only:1; + unsigned count:1; + unsigned word_regexp:1; + unsigned fixed:1; +#define GREP_BINARY_DEFAULT 0 +#define GREP_BINARY_NOMATCH 1 +#define GREP_BINARY_TEXT 2 + unsigned binary:2; + unsigned extended:1; + unsigned relative:1; + unsigned pathname:1; + int regflags; + unsigned pre_context; + unsigned post_context; +}; + +extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); +extern void compile_grep_patterns(struct grep_opt *opt); +extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size); + +#endif -- cgit v0.10.2-6-g49f6 From 8ecae9b032cd0427079d557a3bb6c39116420d4b Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 17 Sep 2006 15:43:40 -0700 Subject: revision traversal: prepare for commit log match. This is from a suggestion by Linus, just to mark the locations where we need to modify to actually implement the filtering. We do not have any actual filtering code yet. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/revision.c b/revision.c index 6a2539b..a14457a 100644 --- a/revision.c +++ b/revision.c @@ -6,6 +6,8 @@ #include "diff.h" #include "refs.h" #include "revision.h" +#include <regex.h> +#include "grep.h" static char *path_name(struct name_path *path, const char *name) { @@ -1045,6 +1047,15 @@ static void mark_boundary_to_show(struct commit *commit) } } +static int commit_match(struct commit *commit, struct rev_info *opt) +{ + if (!opt->header_filter && !opt->message_filter) + return 1; + + /* match it here */ + return 1; +} + struct commit *get_revision(struct rev_info *revs) { struct commit_list *list = revs->commits; @@ -1105,6 +1116,8 @@ struct commit *get_revision(struct rev_info *revs) if (revs->no_merges && commit->parents && commit->parents->next) continue; + if (!commit_match(commit, revs)) + continue; if (revs->prune_fn && revs->dense) { /* Commit without changes? */ if (!(commit->object.flags & TREECHANGE)) { diff --git a/revision.h b/revision.h index a5c35d0..60030e5 100644 --- a/revision.h +++ b/revision.h @@ -71,6 +71,10 @@ struct rev_info { const char *add_signoff; const char *extra_headers; + /* Filter by commit log message */ + struct grep_opt *header_filter; + struct grep_opt *message_filter; + /* special limits */ int max_count; unsigned long max_age; -- cgit v0.10.2-6-g49f6 From bd95fcd34543d7d98bff033c00054341165bc9ce Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 17 Sep 2006 17:23:20 -0700 Subject: revision traversal: --author, --committer, and --grep. This adds three options to setup_revisions(), which lets you filter resulting commits by the author name, the committer name and the log message with regexp. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 28966ad..00a95e2 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -20,6 +20,7 @@ SYNOPSIS [ \--stdin ] [ \--topo-order ] [ \--parents ] + [ \--(author|committer|grep)=<pattern> ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] [ \--bisect ] @@ -154,6 +155,16 @@ limiting may be applied. Limit the commits output to specified time range. +--author='pattern', --committer='pattern':: + + Limit the commits output to ones with author/committer + header lines that match the specified pattern. + +--grep='pattern':: + + Limit the commits output to ones with log message that + matches the specified pattern. + --remove-empty:: Stop when a given path disappears from the tree. diff --git a/revision.c b/revision.c index a14457a..26dd418 100644 --- a/revision.c +++ b/revision.c @@ -674,6 +674,40 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; } +static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) +{ + char *pat; + int patlen, fldlen; + + if (!revs->header_filter) { + struct grep_opt *opt = xcalloc(1, sizeof(*opt)); + opt->status_only = 1; + opt->pattern_tail = &(opt->pattern_list); + opt->regflags = REG_NEWLINE; + revs->header_filter = opt; + } + + fldlen = strlen(field); + patlen = strlen(pattern); + pat = xmalloc(patlen + fldlen + 3); + sprintf(pat, "^%s %s", field, pattern); + append_grep_pattern(revs->header_filter, pat, + "command line", 0, GREP_PATTERN); +} + +static void add_message_grep(struct rev_info *revs, const char *pattern) +{ + if (!revs->message_filter) { + struct grep_opt *opt = xcalloc(1, sizeof(*opt)); + opt->status_only = 1; + opt->pattern_tail = &(opt->pattern_list); + opt->regflags = REG_NEWLINE; + revs->message_filter = opt; + } + append_grep_pattern(revs->message_filter, pattern, + "command line", 0, GREP_PATTERN); +} + static void add_ignore_packed(struct rev_info *revs, const char *name) { int num = ++revs->num_ignore_packed; @@ -915,6 +949,18 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->relative_date = 1; continue; } + if (!strncmp(arg, "--author=", 9)) { + add_header_grep(revs, "author", arg+9); + continue; + } + if (!strncmp(arg, "--committer=", 12)) { + add_header_grep(revs, "committer", arg+12); + continue; + } + if (!strncmp(arg, "--grep=", 7)) { + add_message_grep(revs, arg+7); + continue; + } opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { revs->diff = 1; @@ -975,6 +1021,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); + if (revs->header_filter) + compile_grep_patterns(revs->header_filter); + if (revs->message_filter) + compile_grep_patterns(revs->message_filter); + return left; } @@ -1049,10 +1100,33 @@ static void mark_boundary_to_show(struct commit *commit) static int commit_match(struct commit *commit, struct rev_info *opt) { + char *header, *message; + unsigned long header_len, message_len; + if (!opt->header_filter && !opt->message_filter) return 1; - /* match it here */ + header = commit->buffer; + message = strstr(header, "\n\n"); + if (message) { + message += 2; + header_len = message - header - 1; + message_len = strlen(message); + } + else { + header_len = strlen(header); + message = header; + message_len = 0; + } + + if (opt->header_filter && + !grep_buffer(opt->header_filter, NULL, header, header_len)) + return 0; + + if (opt->message_filter && + !grep_buffer(opt->message_filter, NULL, message, message_len)) + return 0; + return 1; } -- cgit v0.10.2-6-g49f6 From f69895fb0c5921f5b399f35a71caa9a023776ddf Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Mon, 18 Sep 2006 02:52:42 -0400 Subject: rev-list: fix segfault with --{author,committer,grep} We need to save the commit buffer if we're going to match against it. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 1f3333d..dbfee75 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -269,7 +269,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); - save_commit_buffer = revs.verbose_header; + save_commit_buffer = revs.verbose_header || + revs.header_filter || + revs.message_filter; track_object_refs = 0; if (bisect_list) revs.limited = 1; -- cgit v0.10.2-6-g49f6 From a2ed6ae402582a3ee76e9b4639848eba261a12de Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Mon, 18 Sep 2006 10:07:51 -0700 Subject: git-log --author and --committer are not left-anchored by default I know that I'd prefer a rule where "--author=^Junio" would result in the grep-pattern being "^author Junio", but without the initial '^' it would be "^author .*Junio". Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/revision.c b/revision.c index 26dd418..bca1229 100644 --- a/revision.c +++ b/revision.c @@ -677,6 +677,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) { char *pat; + const char *prefix; int patlen, fldlen; if (!revs->header_filter) { @@ -689,8 +690,13 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char fldlen = strlen(field); patlen = strlen(pattern); - pat = xmalloc(patlen + fldlen + 3); - sprintf(pat, "^%s %s", field, pattern); + pat = xmalloc(patlen + fldlen + 10); + prefix = ".*"; + if (*pattern == '^') { + prefix = ""; + pattern++; + } + sprintf(pat, "^%s %s%s", field, prefix, pattern); append_grep_pattern(revs->header_filter, pat, "command line", 0, GREP_PATTERN); } -- cgit v0.10.2-6-g49f6 From cd0d74d2f9c7578b36e705dda55f79731dbe9696 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Mon, 18 Sep 2006 02:29:01 -0700 Subject: repack: use only pack-objects, not rev-list. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-repack.sh b/git-repack.sh index b525fc5..9ae5092 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -32,12 +32,10 @@ trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15 # There will be more repacking strategies to come... case ",$all_into_one," in ,,) - rev_list='--unpacked' - pack_objects='--incremental' + args='--unpacked --incremental' ;; ,t,) - rev_list= - pack_objects= + args= # Redundancy check in all-into-one case is trivial. existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \ @@ -45,11 +43,8 @@ case ",$all_into_one," in ;; esac -pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra" -name=$( { git-rev-list --objects --all $rev_list || - echo "git-rev-list died with exit code $?" - } | - git-pack-objects --non-empty $pack_objects "$PACKTMP") || +args="$args $local $quiet $no_reuse_delta$extra" +name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") || exit 1 if [ -z "$name" ]; then echo Nothing new to pack. -- cgit v0.10.2-6-g49f6 From 49ba83fb67d9e447b86953965ce5f949c6a93b81 Mon Sep 17 00:00:00 2001 From: Jon Loeliger <jdl@jdl.com> Date: Tue, 19 Sep 2006 20:31:51 -0500 Subject: Add virtualization support to git-daemon Signed-off-by: Jon Loeliger Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 741f2c6..51d7c94 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] + [--interpolated-path=pathtemplate] [--enable=service] [--disable=service] [--allow-override=service] [--forbid-override=service] [--reuseaddr] [--detach] [--pid-file=file] @@ -50,6 +51,12 @@ OPTIONS 'git://example.com/hello.git', `git-daemon` will interpret the path as '/srv/git/hello.git'. +--interpolated-path=pathtemplate:: + To support virtual hosting, an interpolated path template can be + used to dynamically construct alternate paths. The template + supports %H for the target hostname as supplied by the client, + and %D for the absolute path of the named repository. + --export-all:: Allow pulling from all directories that look like GIT repositories (have the 'objects' and 'refs' subdirectories), even if they @@ -135,6 +142,46 @@ upload-pack:: disable it by setting `daemon.uploadpack` configuration item to `false`. +EXAMPLES +-------- +git-daemon as inetd server:: + To set up `git-daemon` as an inetd service that handles any + repository under the whitelisted set of directories, /pub/foo + and /pub/bar, place an entry like the following into + /etc/inetd all on one line: ++ +------------------------------------------------ + git stream tcp nowait nobody /usr/bin/git-daemon + git-daemon --inetd --verbose + --syslog --export-all + /pub/foo /pub/bar +------------------------------------------------ + + +git-daemon as inetd server for virtual hosts:: + To set up `git-daemon` as an inetd service that handles + repositories for different virtual hosts, `www.example.com` + and `www.example.org`, place an entry like the following into + `/etc/inetd` all on one line: ++ +------------------------------------------------ + git stream tcp nowait nobody /usr/bin/git-daemon + git-daemon --inetd --verbose + --syslog --export-all + --interpolated-path=/pub/%H%D + /pub/www.example.org/software + /pub/www.example.com/software + /software +------------------------------------------------ ++ +In this example, the root-level directory `/pub` will contain +a subdirectory for each virtual host name supported. +Further, both hosts advertise repositories simply as +`git://www.example.com/software/repo.git`. For pre-1.4.0 +clients, a symlink from `/software` into the appropriate +default repository could be made as well. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki diff --git a/Makefile b/Makefile index 8467447..fb2ade5 100644 --- a/Makefile +++ b/Makefile @@ -246,7 +246,9 @@ DIFF_OBJS = \ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ - date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \ + date.o diff-delta.o entry.o exec_cmd.o ident.o \ + interpolate.o \ + lockfile.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ diff --git a/daemon.c b/daemon.c index a2954a0..eb4f3f1 100644 --- a/daemon.c +++ b/daemon.c @@ -12,6 +12,7 @@ #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" +#include "interpolate.h" static int log_syslog; static int verbose; @@ -21,6 +22,7 @@ static const char daemon_usage[] = "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n" " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" +" [--interpolated-path=path]\n" " [--reuseaddr] [--detach] [--pid-file=file]\n" " [--[enable|disable|allow-override|forbid-override]=service]\n" " [--user=user [[--group=group]] [directory...]"; @@ -34,6 +36,10 @@ static int export_all_trees; /* Take all paths relative to this one if non-NULL */ static char *base_path; +static char *interpolated_path; + +/* Flag indicating client sent extra args. */ +static int saw_extended_args; /* If defined, ~user notation is allowed and the string is inserted * after ~user/. E.g. a request to git://host/~alice/frotz would @@ -45,6 +51,21 @@ static const char *user_path; static unsigned int timeout; static unsigned int init_timeout; +/* + * Static table for now. Ugh. + * Feel free to make dynamic as needed. + */ +#define INTERP_SLOT_HOST (0) +#define INTERP_SLOT_DIR (1) +#define INTERP_SLOT_PERCENT (2) + +static struct interp interp_table[] = { + { "%H", 0}, + { "%D", 0}, + { "%%", "%"}, +}; + + static void logreport(int priority, const char *err, va_list params) { /* We should do a single write so that it is atomic and output @@ -152,10 +173,14 @@ static int avoid_alias(char *p) } } -static char *path_ok(char *dir) +static char *path_ok(struct interp *itable) { static char rpath[PATH_MAX]; + static char interp_path[PATH_MAX]; char *path; + char *dir; + + dir = itable[INTERP_SLOT_DIR].value; if (avoid_alias(dir)) { logerror("'%s': aliased", dir); @@ -184,16 +209,27 @@ static char *path_ok(char *dir) dir = rpath; } } + else if (interpolated_path && saw_extended_args) { + if (*dir != '/') { + /* Allow only absolute */ + logerror("'%s': Non-absolute path denied (interpolated-path active)", dir); + return NULL; + } + + interpolate(interp_path, PATH_MAX, interpolated_path, + interp_table, ARRAY_SIZE(interp_table)); + loginfo("Interpolated dir '%s'", interp_path); + + dir = interp_path; + } else if (base_path) { if (*dir != '/') { /* Allow only absolute */ logerror("'%s': Non-absolute path denied (base-path active)", dir); return NULL; } - else { - snprintf(rpath, PATH_MAX, "%s%s", base_path, dir); - dir = rpath; - } + snprintf(rpath, PATH_MAX, "%s%s", base_path, dir); + dir = rpath; } path = enter_repo(dir, strict_paths); @@ -257,12 +293,14 @@ static int git_daemon_config(const char *var, const char *value) return 0; } -static int run_service(char *dir, struct daemon_service *service) +static int run_service(struct interp *itable, struct daemon_service *service) { const char *path; int enabled = service->enabled; - loginfo("Request %s for '%s'", service->name, dir); + loginfo("Request %s for '%s'", + service->name, + itable[INTERP_SLOT_DIR].value); if (!enabled && !service->overridable) { logerror("'%s': service not enabled.", service->name); @@ -270,7 +308,7 @@ static int run_service(char *dir, struct daemon_service *service) return -1; } - if (!(path = path_ok(dir))) + if (!(path = path_ok(itable))) return -1; /* @@ -358,6 +396,28 @@ static void make_service_overridable(const char *name, int ena) { die("No such service %s", name); } +static void parse_extra_args(char *extra_args, int buflen) +{ + char *val; + int vallen; + char *end = extra_args + buflen; + + while (extra_args < end && *extra_args) { + saw_extended_args = 1; + if (strncasecmp("host=", extra_args, 5) == 0) { + val = extra_args + 5; + vallen = strlen(val) + 1; + if (*val) { + char *save = xmalloc(vallen); + interp_table[INTERP_SLOT_HOST].value = save; + strlcpy(save, val, vallen); + } + /* On to the next one */ + extra_args = val + vallen; + } + } +} + static int execute(struct sockaddr *addr) { static char line[1000]; @@ -398,13 +458,18 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; + if (len != pktlen) + parse_extra_args(line + len + 1, pktlen - len - 1); + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); int namelen = strlen(s->name); if (!strncmp("git-", line, 4) && !strncmp(s->name, line + 4, namelen) && - line[namelen + 4] == ' ') - return run_service(line + namelen + 5, s); + line[namelen + 4] == ' ') { + interp_table[INTERP_SLOT_DIR].value = line+namelen+5; + return run_service(interp_table, s); + } } logerror("Protocol error: '%s'", line); @@ -867,6 +932,10 @@ int main(int argc, char **argv) base_path = arg+12; continue; } + if (!strncmp(arg, "--interpolated-path=", 20)) { + interpolated_path = arg+20; + continue; + } if (!strcmp(arg, "--reuseaddr")) { reuseaddr = 1; continue; diff --git a/interpolate.c b/interpolate.c new file mode 100644 index 0000000..d82f1b5 --- /dev/null +++ b/interpolate.c @@ -0,0 +1,82 @@ +/* + * Copyright 2006 Jon Loeliger + */ + +#include <string.h> + +#include "interpolate.h" + + +/* + * Convert a NUL-terminated string in buffer orig + * into the supplied buffer, result, whose length is reslen, + * performing substitutions on %-named sub-strings from + * the table, interps, with ninterps entries. + * + * Example interps: + * { + * { "%H", "example.org"}, + * { "%port", "123"}, + * { "%%", "%"}, + * } + * + * Returns 1 on a successful substitution pass that fits in result, + * Returns 0 on a failed or overflowing substitution pass. + */ + +int interpolate(char *result, int reslen, + char *orig, + struct interp *interps, int ninterps) +{ + char *src = orig; + char *dest = result; + int newlen = 0; + char *name, *value; + int namelen, valuelen; + int i; + char c; + + memset(result, 0, reslen); + + while ((c = *src) && newlen < reslen - 1) { + if (c == '%') { + /* Try to match an interpolation string. */ + for (i = 0; i < ninterps; i++) { + name = interps[i].name; + namelen = strlen(name); + if (strncmp(src, name, namelen) == 0) { + break; + } + } + + /* Check for valid interpolation. */ + if (i < ninterps) { + value = interps[i].value; + valuelen = strlen(value); + + if (newlen + valuelen < reslen - 1) { + /* Substitute. */ + strncpy(dest, value, valuelen); + newlen += valuelen; + dest += valuelen; + src += namelen; + } else { + /* Something's not fitting. */ + return 0; + } + + } else { + /* Skip bogus interpolation. */ + *dest++ = *src++; + newlen++; + } + + } else { + /* Straight copy one non-interpolation character. */ + *dest++ = *src++; + newlen++; + } + } + + return newlen < reslen - 1; +} diff --git a/interpolate.h b/interpolate.h new file mode 100644 index 0000000..00c63a5 --- /dev/null +++ b/interpolate.h @@ -0,0 +1,18 @@ +/* + * Copyright 2006 Jon Loeliger + */ + +#ifndef INTERPOLATE_H +#define INTERPOLATE_H + + +struct interp { + char *name; + char *value; +}; + +extern int interpolate(char *result, int reslen, + char *orig, + struct interp *interps, int ninterps); + +#endif /* INTERPOLATE_H */ -- cgit v0.10.2-6-g49f6 From 480c1ca6fd8df58a783e231648b489ed2bfd17f1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 20 Sep 2006 12:39:46 -0700 Subject: Update grep internal for grepping only in head/body This further updates the built-in grep engine so that we can say something like "this pattern should match only in head". This can be used to simplify grepping in the log messages. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/grep.c b/grep.c index 61db6e1..cc8d684 100644 --- a/grep.c +++ b/grep.c @@ -43,6 +43,8 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list) p = *list; switch (p->token) { case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: x = xcalloc(1, sizeof (struct grep_expr)); x->node = GREP_NODE_ATOM; x->u.atom = p; @@ -141,10 +143,16 @@ void compile_grep_patterns(struct grep_opt *opt) /* First compile regexps */ for (p = opt->pattern_list; p; p = p->next) { - if (p->token == GREP_PATTERN) + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: compile_regexp(p, opt); - else + break; + default: opt->extended = 1; + break; + } } if (!opt->extended) @@ -210,12 +218,16 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match) } } -static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) +static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx) { int hit = 0; int at_true_bol = 1; regmatch_t pmatch[10]; + if ((p->token != GREP_PATTERN) && + ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD))) + return 0; + again: if (!opt->fixed) { regex_t *exp = &p->regexp; @@ -262,37 +274,40 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, - char *bol, char *eol) + char *bol, char *eol, + enum grep_context ctx) { switch (x->node) { case GREP_NODE_ATOM: - return match_one_pattern(opt, x->u.atom, bol, eol); + return match_one_pattern(opt, x->u.atom, bol, eol, ctx); break; case GREP_NODE_NOT: - return !match_expr_eval(opt, x->u.unary, bol, eol); + return !match_expr_eval(opt, x->u.unary, bol, eol, ctx); case GREP_NODE_AND: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) && - match_expr_eval(opt, x->u.binary.right, bol, eol)); + return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) && + match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); case GREP_NODE_OR: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) || - match_expr_eval(opt, x->u.binary.right, bol, eol)); + return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) || + match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); } die("Unexpected node type (internal error) %d\n", x->node); } -static int match_expr(struct grep_opt *opt, char *bol, char *eol) +static int match_expr(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx) { struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol); + return match_expr_eval(opt, x, bol, eol, ctx); } -static int match_line(struct grep_opt *opt, char *bol, char *eol) +static int match_line(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx) { struct grep_pat *p; if (opt->extended) - return match_expr(opt, bol, eol); + return match_expr(opt, bol, eol, ctx); for (p = opt->pattern_list; p; p = p->next) { - if (match_one_pattern(opt, p, bol, eol)) + if (match_one_pattern(opt, p, bol, eol, ctx)) return 1; } return 0; @@ -312,6 +327,7 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long int binary_match_only = 0; const char *hunk_mark = ""; unsigned count = 0; + enum grep_context ctx = GREP_CONTEXT_HEAD; if (buffer_is_binary(buf, size)) { switch (opt->binary) { @@ -339,7 +355,10 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long ch = *eol; *eol = 0; - hit = match_line(opt, bol, eol); + if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol)) + ctx = GREP_CONTEXT_BODY; + + hit = match_line(opt, bol, eol, ctx); *eol = ch; /* "grep -v -e foo -e bla" should list lines diff --git a/grep.h b/grep.h index 80122b0..0b503ea 100644 --- a/grep.h +++ b/grep.h @@ -3,6 +3,8 @@ enum grep_pat_token { GREP_PATTERN, + GREP_PATTERN_HEAD, + GREP_PATTERN_BODY, GREP_AND, GREP_OPEN_PAREN, GREP_CLOSE_PAREN, @@ -10,6 +12,11 @@ enum grep_pat_token { GREP_OR, }; +enum grep_context { + GREP_CONTEXT_HEAD, + GREP_CONTEXT_BODY, +}; + struct grep_pat { struct grep_pat *next; const char *origin; -- cgit v0.10.2-6-g49f6 From 2d10c555374df257e32848ba6f77fd73d608645f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 20 Sep 2006 13:21:56 -0700 Subject: git log: Unify header_filter and message_filter into one. Now we can tell the built-in grep to grep only in head or in body, use that to update --author, --committer, and --grep. Unfortunately, to make --and, --not and other grep boolean expressions useful, as in: # Things written by Junio committed and by Linus and log # does not talk about diff. git log --author=Junio --and --committer=Linus \ --grep-not --grep=diff we will need to do another round of built-in grep core enhancement, because grep boolean expressions are designed to work on one line at a time. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-rev-list.c b/builtin-rev-list.c index dbfee75..fb7fc92 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -269,9 +269,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); - save_commit_buffer = revs.verbose_header || - revs.header_filter || - revs.message_filter; + save_commit_buffer = revs.verbose_header || revs.grep_filter; track_object_refs = 0; if (bisect_list) revs.limited = 1; diff --git a/revision.c b/revision.c index bca1229..93f2513 100644 --- a/revision.c +++ b/revision.c @@ -674,19 +674,24 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; } -static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) +static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what) { - char *pat; - const char *prefix; - int patlen, fldlen; - - if (!revs->header_filter) { + if (!revs->grep_filter) { struct grep_opt *opt = xcalloc(1, sizeof(*opt)); opt->status_only = 1; opt->pattern_tail = &(opt->pattern_list); opt->regflags = REG_NEWLINE; - revs->header_filter = opt; + revs->grep_filter = opt; } + append_grep_pattern(revs->grep_filter, ptn, + "command line", 0, what); +} + +static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) +{ + char *pat; + const char *prefix; + int patlen, fldlen; fldlen = strlen(field); patlen = strlen(pattern); @@ -697,21 +702,12 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char pattern++; } sprintf(pat, "^%s %s%s", field, prefix, pattern); - append_grep_pattern(revs->header_filter, pat, - "command line", 0, GREP_PATTERN); + add_grep(revs, pat, GREP_PATTERN_HEAD); } static void add_message_grep(struct rev_info *revs, const char *pattern) { - if (!revs->message_filter) { - struct grep_opt *opt = xcalloc(1, sizeof(*opt)); - opt->status_only = 1; - opt->pattern_tail = &(opt->pattern_list); - opt->regflags = REG_NEWLINE; - revs->message_filter = opt; - } - append_grep_pattern(revs->message_filter, pattern, - "command line", 0, GREP_PATTERN); + add_grep(revs, pattern, GREP_PATTERN_BODY); } static void add_ignore_packed(struct rev_info *revs, const char *name) @@ -955,6 +951,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->relative_date = 1; continue; } + + /* + * Grepping the commit log + */ if (!strncmp(arg, "--author=", 9)) { add_header_grep(revs, "author", arg+9); continue; @@ -967,6 +967,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch add_message_grep(revs, arg+7); continue; } + opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { revs->diff = 1; @@ -1027,10 +1028,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); - if (revs->header_filter) - compile_grep_patterns(revs->header_filter); - if (revs->message_filter) - compile_grep_patterns(revs->message_filter); + if (revs->grep_filter) + compile_grep_patterns(revs->grep_filter); return left; } @@ -1106,34 +1105,11 @@ static void mark_boundary_to_show(struct commit *commit) static int commit_match(struct commit *commit, struct rev_info *opt) { - char *header, *message; - unsigned long header_len, message_len; - - if (!opt->header_filter && !opt->message_filter) + if (!opt->grep_filter) return 1; - - header = commit->buffer; - message = strstr(header, "\n\n"); - if (message) { - message += 2; - header_len = message - header - 1; - message_len = strlen(message); - } - else { - header_len = strlen(header); - message = header; - message_len = 0; - } - - if (opt->header_filter && - !grep_buffer(opt->header_filter, NULL, header, header_len)) - return 0; - - if (opt->message_filter && - !grep_buffer(opt->message_filter, NULL, message, message_len)) - return 0; - - return 1; + return grep_buffer(opt->grep_filter, + NULL, /* we say nothing, not even filename */ + commit->buffer, strlen(commit->buffer)); } struct commit *get_revision(struct rev_info *revs) diff --git a/revision.h b/revision.h index 60030e5..3adab95 100644 --- a/revision.h +++ b/revision.h @@ -72,8 +72,7 @@ struct rev_info { const char *extra_headers; /* Filter by commit log message */ - struct grep_opt *header_filter; - struct grep_opt *message_filter; + struct grep_opt *grep_filter; /* special limits */ int max_count; -- cgit v0.10.2-6-g49f6 From e49521b56d8715f46b93ee6bc95f7de9c6858365 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 20 Sep 2006 16:04:46 -0700 Subject: Make hexval() available to others. builtin-mailinfo.c has its own hexval implementaiton but it can share the table-lookup one recently implemented in sha1_file.c Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 0c65f93..b8d7dbc 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -451,17 +451,6 @@ static int read_one_header_line(char *line, int sz, FILE *in) return ofs; } -static unsigned hexval(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return ~0; -} - static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047) { int c; diff --git a/cache.h b/cache.h index 57db7c9..d557e75 100644 --- a/cache.h +++ b/cache.h @@ -278,6 +278,12 @@ enum object_type { OBJ_BAD, }; +extern signed char hexval_table[256]; +static inline unsigned int hexval(unsigned int c) +{ + return hexval_table[c]; +} + /* Convert to/from hex/sha1 representation */ #define MINIMUM_ABBREV 4 #define DEFAULT_ABBREV 7 diff --git a/sha1_file.c b/sha1_file.c index b89edb9..0f9c2b6 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -26,44 +26,40 @@ const unsigned char null_sha1[20]; static unsigned int sha1_file_open_flag = O_NOATIME; -static inline unsigned int hexval(unsigned int c) -{ - static signed char val[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ - 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ - 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ - }; - return val[c]; -} +signed char hexval_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +}; int get_sha1_hex(const char *hex, unsigned char *sha1) { -- cgit v0.10.2-6-g49f6 From 7dd45e15c259e44b1c8b5ffdfc0c3d002c7f642c Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 20 Sep 2006 16:11:08 -0700 Subject: sha1_name.c: understand "describe" output as a valid object name Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/sha1_name.c b/sha1_name.c index 1fbc443..9b226e3 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -431,6 +431,26 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) return 0; } +static int get_describe_name(const char *name, int len, unsigned char *sha1) +{ + const char *cp; + + for (cp = name + len - 1; name + 2 <= cp; cp--) { + char ch = *cp; + if (hexval(ch) & ~0377) { + /* We must be looking at g in "SOMETHING-g" + * for it to be describe output. + */ + if (ch == 'g' && cp[-1] == '-') { + cp++; + len -= cp - name; + return get_short_sha1(cp, len, sha1, 1); + } + } + } + return -1; +} + static int get_sha1_1(const char *name, int len, unsigned char *sha1) { int ret, has_suffix; @@ -472,6 +492,12 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) ret = get_sha1_basic(name, len, sha1); if (!ret) return 0; + + /* It could be describe output that is "SOMETHING-gXXXX" */ + ret = get_describe_name(name, len, sha1); + if (!ret) + return 0; + return get_short_sha1(name, len, sha1, 0); } -- cgit v0.10.2-6-g49f6 From 11031d7e9f34f6a20ff4a4bd4fa3e5e3c0024a57 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Thu, 21 Sep 2006 01:07:54 +0200 Subject: add receive.denyNonFastforwards config variable If receive.denyNonFastforwards is set to true, git-receive-pack will deny non fast-forwards, i.e. forced updates. Most notably, a push to a repository which has that flag set will fail. As a first user, 'git-init-db --shared' sets this flag, since in a shared setup, you are most unlikely to want forced pushes to succeed. Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-init-db.c b/builtin-init-db.c index 5085018..c3ed1ce 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -311,6 +311,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) */ sprintf(buf, "%d", shared_repository); git_config_set("core.sharedrepository", buf); + git_config_set("receive.denyNonFastforwards", "true"); } return 0; diff --git a/cache.h b/cache.h index d557e75..ef2e581 100644 --- a/cache.h +++ b/cache.h @@ -188,6 +188,7 @@ extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int shared_repository; +extern int deny_non_fast_forwards; extern const char *apply_default_whitespace; extern int zlib_compression_level; diff --git a/environment.c b/environment.c index 84d870c..63b1d15 100644 --- a/environment.c +++ b/environment.c @@ -20,6 +20,7 @@ int warn_ambiguous_refs = 1; int repository_format_version; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; +int deny_non_fast_forwards = 0; const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; diff --git a/receive-pack.c b/receive-pack.c index 78f75da..a6ec9f9 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -2,6 +2,8 @@ #include "refs.h" #include "pkt-line.h" #include "run-command.h" +#include "commit.h" +#include "object.h" static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; @@ -127,6 +129,20 @@ static int update(struct command *cmd) return error("unpack should have generated %s, " "but I can't find it!", new_hex); } + if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { + struct commit *old_commit, *new_commit; + struct commit_list *bases; + + old_commit = (struct commit *)parse_object(old_sha1); + new_commit = (struct commit *)parse_object(new_sha1); + for (bases = get_merge_bases(old_commit, new_commit, 1); + bases; bases = bases->next) + if (!hashcmp(old_sha1, bases->item->object.sha1)) + break; + if (!bases) + return error("denying non-fast forward;" + " you should pull first"); + } safe_create_leading_directories(lock_name); newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); diff --git a/setup.c b/setup.c index 2afdba4..9a46a58 100644 --- a/setup.c +++ b/setup.c @@ -244,6 +244,8 @@ int check_repository_format_version(const char *var, const char *value) repository_format_version = git_config_int(var, value); else if (strcmp(var, "core.sharedrepository") == 0) shared_repository = git_config_perm(var, value); + else if (strcmp(var, "receive.denynonfastforwards") == 0) + deny_non_fast_forwards = git_config_bool(var, value); return 0; } -- cgit v0.10.2-6-g49f6 From cc4c4f0ce25cc3803197bc19c275fd6f59a2a511 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 20 Sep 2006 21:10:17 -0700 Subject: symbolit-ref: fix resolve_ref conversion. An earlier conversion accidentally hardcoded "HEAD" to be passed to resolve_ref(), thereby causing git-symbolic-ref command to always report where the HEAD points at, ignoring the command line parameter. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 6f18db8..13163ba 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -7,7 +7,7 @@ static const char git_symbolic_ref_usage[] = static void check_symref(const char *HEAD) { unsigned char sha1[20]; - const char *refs_heads_master = resolve_ref("HEAD", sha1, 0); + const char *refs_heads_master = resolve_ref(HEAD, sha1, 0); if (!refs_heads_master) die("No such ref: %s", HEAD); -- cgit v0.10.2-6-g49f6 From cb5d709ff8a4bae19d57a470ba2b137c25938a44 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 20 Sep 2006 21:47:42 -0700 Subject: Add callback data to for_each_ref() family. This is a long overdue fix to the API for for_each_ref() family of functions. It allows the callers to specify a callback data pointer, so that the caller does not have to use static variables to communicate with the callback funciton. The updated for_each_ref() family takes a function of type int (*fn)(const char *, const unsigned char *, void *) and a void pointer as parameters, and calls the function with the name of the ref and its SHA-1 with the caller-supplied void pointer as parameters. The commit updates two callers, builtin-name-rev.c and builtin-pack-refs.c as an example. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 52886b6..9e3e537 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -75,11 +75,10 @@ copy_data: } } -static int tags_only; - -static int name_ref(const char *path, const unsigned char *sha1) +static int name_ref(const char *path, const unsigned char *sha1, void *cb_data) { struct object *o = parse_object(sha1); + int tags_only = *(int*)cb_data; int deref = 0; if (tags_only && strncmp(path, "refs/tags/", 10)) @@ -131,6 +130,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = { 0, 0, NULL }; int as_is = 0, all = 0, transform_stdin = 0; + int tags_only = 0; git_config(git_default_config); @@ -186,7 +186,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) add_object_array((struct object *)commit, *argv, &revs); } - for_each_ref(name_ref); + for_each_ref(name_ref, &tags_only); if (transform_stdin) { char buffer[2048]; diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 0f5d827..b3d5470 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -1,7 +1,6 @@ #include "cache.h" #include "refs.h" -static FILE *refs_file; static const char *result_path, *lock_path; static void remove_lock_file(void) @@ -10,8 +9,10 @@ static void remove_lock_file(void) unlink(lock_path); } -static int handle_one_ref(const char *path, const unsigned char *sha1) +static int handle_one_ref(const char *path, const unsigned char *sha1, void *cb_data) { + FILE *refs_file = cb_data; + fprintf(refs_file, "%s %s\n", sha1_to_hex(sha1), path); return 0; } @@ -19,6 +20,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1) int cmd_pack_refs(int argc, const char **argv, const char *prefix) { int fd; + FILE *refs_file; result_path = xstrdup(git_path("packed-refs")); lock_path = xstrdup(mkpath("%s.lock", result_path)); @@ -31,7 +33,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) refs_file = fdopen(fd, "w"); if (!refs_file) die("unable to create ref-pack file structure (%s)", strerror(errno)); - for_each_ref(handle_one_ref); + for_each_ref(handle_one_ref, refs_file); fsync(fd); fclose(refs_file); if (rename(lock_path, result_path) < 0) diff --git a/builtin-prune.c b/builtin-prune.c index 6228c79..e21c29b 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -174,7 +174,7 @@ static void walk_commit_list(struct rev_info *revs) } } -static int add_one_ref(const char *path, const unsigned char *sha1) +static int add_one_ref(const char *path, const unsigned char *sha1, void *cb_data) { struct object *object = parse_object(sha1); if (!object) @@ -240,7 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) revs.tree_objects = 1; /* Add all external refs */ - for_each_ref(add_one_ref); + for_each_ref(add_one_ref, NULL); /* Add all refs from the index file */ add_cache_refs(); diff --git a/builtin-push.c b/builtin-push.c index c43f256..88fc8e2 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -27,7 +27,7 @@ static void add_refspec(const char *ref) refspec_nr = nr; } -static int expand_one_ref(const char *ref, const unsigned char *sha1) +static int expand_one_ref(const char *ref, const unsigned char *sha1, void *cb_data) { /* Ignore the "refs/" at the beginning of the refname */ ref += 5; @@ -51,7 +51,7 @@ static void expand_refspecs(void) } if (!tags) return; - for_each_ref(expand_one_ref); + for_each_ref(expand_one_ref, NULL); } static void set_refspecs(const char **refs, int nr) diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index fd3ccc8..c771274 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -137,7 +137,7 @@ static void show_default(void) } } -static int show_reference(const char *refname, const unsigned char *sha1) +static int show_reference(const char *refname, const unsigned char *sha1, void *cb_data) { show_rev(NORMAL, sha1, refname); return 0; @@ -299,19 +299,19 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--all")) { - for_each_ref(show_reference); + for_each_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--branches")) { - for_each_branch_ref(show_reference); + for_each_branch_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--tags")) { - for_each_tag_ref(show_reference); + for_each_tag_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--remotes")) { - for_each_remote_ref(show_reference); + for_each_remote_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--show-prefix")) { diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 4d8db0c..b3548ae 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -346,7 +346,7 @@ static void sort_ref_range(int bottom, int top) compare_ref_name); } -static int append_ref(const char *refname, const unsigned char *sha1) +static int append_ref(const char *refname, const unsigned char *sha1, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); int i; @@ -369,7 +369,7 @@ static int append_ref(const char *refname, const unsigned char *sha1) return 0; } -static int append_head_ref(const char *refname, const unsigned char *sha1) +static int append_head_ref(const char *refname, const unsigned char *sha1, void *cb_data) { unsigned char tmp[20]; int ofs = 11; @@ -380,14 +380,14 @@ static int append_head_ref(const char *refname, const unsigned char *sha1) */ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) ofs = 5; - return append_ref(refname + ofs, sha1); + return append_ref(refname + ofs, sha1, cb_data); } -static int append_tag_ref(const char *refname, const unsigned char *sha1) +static int append_tag_ref(const char *refname, const unsigned char *sha1, void *cb_data) { if (strncmp(refname, "refs/tags/", 10)) return 0; - return append_ref(refname + 5, sha1); + return append_ref(refname + 5, sha1, cb_data); } static const char *match_ref_pattern = NULL; @@ -401,7 +401,7 @@ static int count_slash(const char *s) return cnt; } -static int append_matching_ref(const char *refname, const unsigned char *sha1) +static int append_matching_ref(const char *refname, const unsigned char *sha1, void *cb_data) { /* we want to allow pattern hold/<asterisk> to show all * branches under refs/heads/hold/, and v0.99.9? to show @@ -417,22 +417,22 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1) if (fnmatch(match_ref_pattern, tail, 0)) return 0; if (!strncmp("refs/heads/", refname, 11)) - return append_head_ref(refname, sha1); + return append_head_ref(refname, sha1, cb_data); if (!strncmp("refs/tags/", refname, 10)) - return append_tag_ref(refname, sha1); - return append_ref(refname, sha1); + return append_tag_ref(refname, sha1, cb_data); + return append_ref(refname, sha1, cb_data); } static void snarf_refs(int head, int tag) { if (head) { int orig_cnt = ref_name_cnt; - for_each_ref(append_head_ref); + for_each_ref(append_head_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } if (tag) { int orig_cnt = ref_name_cnt; - for_each_ref(append_tag_ref); + for_each_ref(append_tag_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } } @@ -487,7 +487,7 @@ static void append_one_rev(const char *av) { unsigned char revkey[20]; if (!get_sha1(av, revkey)) { - append_ref(av, revkey); + append_ref(av, revkey, NULL); return; } if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { @@ -495,7 +495,7 @@ static void append_one_rev(const char *av) int saved_matches = ref_name_cnt; match_ref_pattern = av; match_ref_slash = count_slash(av); - for_each_ref(append_matching_ref); + for_each_ref(append_matching_ref, NULL); if (saved_matches == ref_name_cnt && ref_name_cnt < MAX_REVS) error("no matching refs with %s", av); diff --git a/describe.c b/describe.c index ab192f8..ea0f2ce 100644 --- a/describe.c +++ b/describe.c @@ -53,7 +53,7 @@ static void add_to_known_names(const char *path, names = ++idx; } -static int get_name(const char *path, const unsigned char *sha1) +static int get_name(const char *path, const unsigned char *sha1, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); struct object *object; @@ -113,7 +113,7 @@ static void describe(const char *arg, int last_one) if (!initialized) { initialized = 1; - for_each_ref(get_name); + for_each_ref(get_name, NULL); qsort(name_array, names, sizeof(*name_array), compare_names); } diff --git a/fetch-pack.c b/fetch-pack.c index e8708aa..6264ea1 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -42,7 +42,7 @@ static void rev_list_push(struct commit *commit, int mark) } } -static int rev_list_insert_ref(const char *path, const unsigned char *sha1) +static int rev_list_insert_ref(const char *path, const unsigned char *sha1, void *cb_data) { struct object *o = deref_tag(parse_object(sha1), path, 0); @@ -143,7 +143,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, unsigned in_vain = 0; int got_continue = 0; - for_each_ref(rev_list_insert_ref); + for_each_ref(rev_list_insert_ref, NULL); fetching = 0; for ( ; refs ; refs = refs->next) { @@ -253,7 +253,7 @@ done: static struct commit_list *complete; -static int mark_complete(const char *path, const unsigned char *sha1) +static int mark_complete(const char *path, const unsigned char *sha1, void *cb_data) { struct object *o = parse_object(sha1); @@ -365,7 +365,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) } } - for_each_ref(mark_complete); + for_each_ref(mark_complete, NULL); if (cutoff) mark_recent_complete_commits(cutoff); diff --git a/fetch.c b/fetch.c index 34df8d3..36d1e76 100644 --- a/fetch.c +++ b/fetch.c @@ -201,7 +201,7 @@ static int interpret_target(char *target, unsigned char *sha1) return -1; } -static int mark_complete(const char *path, const unsigned char *sha1) +static int mark_complete(const char *path, const unsigned char *sha1, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); if (commit) { @@ -274,7 +274,7 @@ int pull(int targets, char **target, const char **write_ref, } if (!get_recover) - for_each_ref(mark_complete); + for_each_ref(mark_complete, NULL); for (i = 0; i < targets; i++) { if (interpret_target(target[i], &sha1[20 * i])) { diff --git a/fsck-objects.c b/fsck-objects.c index 456c17e..bb0c94e 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -402,7 +402,7 @@ static void fsck_dir(int i, char *path) static int default_refs; -static int fsck_handle_ref(const char *refname, const unsigned char *sha1) +static int fsck_handle_ref(const char *refname, const unsigned char *sha1, void *cb_data) { struct object *obj; @@ -424,7 +424,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1) static void get_default_heads(void) { - for_each_ref(fsck_handle_ref); + for_each_ref(fsck_handle_ref, NULL); /* * Not having any default heads isn't really fatal, but diff --git a/http-push.c b/http-push.c index 670ff00..460c9be 100644 --- a/http-push.c +++ b/http-push.c @@ -1864,7 +1864,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) static struct ref *local_refs, **local_tail; static struct ref *remote_refs, **remote_tail; -static int one_local_ref(const char *refname, const unsigned char *sha1) +static int one_local_ref(const char *refname, const unsigned char *sha1, void *cb_data) { struct ref *ref; int len = strlen(refname) + 1; @@ -1913,7 +1913,7 @@ static void one_remote_ref(char *refname) static void get_local_heads(void) { local_tail = &local_refs; - for_each_ref(one_local_ref); + for_each_ref(one_local_ref, NULL); } static void get_dav_remote_heads(void) diff --git a/receive-pack.c b/receive-pack.c index 78f75da..7abc921 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -12,7 +12,7 @@ static int report_status; static char capabilities[] = "report-status"; static int capabilities_sent; -static int show_ref(const char *path, const unsigned char *sha1) +static int show_ref(const char *path, const unsigned char *sha1, void *cb_data) { if (capabilities_sent) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); @@ -25,9 +25,9 @@ static int show_ref(const char *path, const unsigned char *sha1) static void write_head_info(void) { - for_each_ref(show_ref); + for_each_ref(show_ref, NULL); if (!capabilities_sent) - show_ref("capabilities^{}", null_sha1); + show_ref("capabilities^{}", null_sha1, NULL); } diff --git a/refs.c b/refs.c index 7bd36e4..85564f0 100644 --- a/refs.c +++ b/refs.c @@ -275,7 +275,7 @@ int read_ref(const char *ref, unsigned char *sha1) return -1; } -static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim) +static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_data) { int retval; struct ref_list *packed = get_packed_refs(); @@ -303,7 +303,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u error("%s does not point to a valid object!", entry->name); continue; } - retval = fn(entry->name + trim, entry->sha1); + retval = fn(entry->name + trim, entry->sha1, cb_data); if (retval) return retval; } @@ -311,7 +311,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u packed = packed ? packed : loose; while (packed) { if (!strncmp(base, packed->name, trim)) { - retval = fn(packed->name + trim, packed->sha1); + retval = fn(packed->name + trim, packed->sha1, cb_data); if (retval) return retval; } @@ -320,34 +320,39 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u return 0; } -int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int head_ref(each_ref_fn fn, void *cb_data) { unsigned char sha1[20]; if (!read_ref("HEAD", sha1)) - return fn("HEAD", sha1); + return fn("HEAD", sha1, cb_data); return 0; } -int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/", fn, 0); + return do_for_each_ref("refs/", fn, 0, cb_data); } -int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_tag_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/tags/", fn, 10); + return do_for_each_ref("refs/tags/", fn, 10, cb_data); } -int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_branch_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/heads/", fn, 11); + return do_for_each_ref("refs/heads/", fn, 11, cb_data); } -int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_remote_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/remotes/", fn, 13); + return do_for_each_ref("refs/remotes/", fn, 13, cb_data); } +/* NEEDSWORK: This is only used by ssh-upload and it should go; the + * caller should do resolve_ref or read_ref like everybody else. Or + * maybe everybody else should use get_ref_sha1() instead of doing + * read_ref(). + */ int get_ref_sha1(const char *ref, unsigned char *sha1) { if (check_ref_format(ref)) diff --git a/refs.h b/refs.h index af347e6..886c857 100644 --- a/refs.h +++ b/refs.h @@ -14,11 +14,12 @@ struct ref_lock { * Calls the specified function for each ref file until it returns nonzero, * and returns the value */ -extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)); +typedef int each_ref_fn(const char *refname, const unsigned char *sha1, void *cb_data); +extern int head_ref(each_ref_fn, void *); +extern int for_each_ref(each_ref_fn, void *); +extern int for_each_tag_ref(each_ref_fn, void *); +extern int for_each_branch_ref(each_ref_fn, void *); +extern int for_each_remote_ref(each_ref_fn, void *); /** Reads the refs file specified into sha1 **/ extern int get_ref_sha1(const char *ref, unsigned char *sha1); diff --git a/revision.c b/revision.c index 6a2539b..0e84b8a 100644 --- a/revision.c +++ b/revision.c @@ -466,7 +466,7 @@ static void limit_list(struct rev_info *revs) static int all_flags; static struct rev_info *all_revs; -static int handle_one_ref(const char *path, const unsigned char *sha1) +static int handle_one_ref(const char *path, const unsigned char *sha1, void *cb_data) { struct object *object = get_reference(all_revs, path, sha1, all_flags); add_pending_object(all_revs, object, ""); @@ -477,7 +477,7 @@ static void handle_all(struct rev_info *revs, unsigned flags) { all_revs = revs; all_flags = flags; - for_each_ref(handle_one_ref); + for_each_ref(handle_one_ref, NULL); } static int add_parents_only(struct rev_info *revs, const char *arg, int flags) diff --git a/send-pack.c b/send-pack.c index 5bb123a..ee13093 100644 --- a/send-pack.c +++ b/send-pack.c @@ -215,7 +215,7 @@ static int ref_newer(const unsigned char *new_sha1, static struct ref *local_refs, **local_tail; static struct ref *remote_refs, **remote_tail; -static int one_local_ref(const char *refname, const unsigned char *sha1) +static int one_local_ref(const char *refname, const unsigned char *sha1, void *cb_data) { struct ref *ref; int len = strlen(refname) + 1; @@ -230,7 +230,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) static void get_local_heads(void) { local_tail = &local_refs; - for_each_ref(one_local_ref); + for_each_ref(one_local_ref, NULL); } static int receive_status(int in) diff --git a/server-info.c b/server-info.c index 2fb8f57..7667b41 100644 --- a/server-info.c +++ b/server-info.c @@ -7,7 +7,7 @@ /* refs */ static FILE *info_ref_fp; -static int add_info_ref(const char *path, const unsigned char *sha1) +static int add_info_ref(const char *path, const unsigned char *sha1, void *cb_data) { struct object *o = parse_object(sha1); @@ -34,7 +34,7 @@ static int update_info_refs(int force) info_ref_fp = fopen(path1, "w"); if (!info_ref_fp) return error("unable to update %s", path0); - for_each_ref(add_info_ref); + for_each_ref(add_info_ref, NULL); fclose(info_ref_fp); rename(path1, path0); free(path0); diff --git a/upload-pack.c b/upload-pack.c index 189b239..10237eb 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -416,7 +416,7 @@ static void receive_needs(void) } } -static int send_ref(const char *refname, const unsigned char *sha1) +static int send_ref(const char *refname, const unsigned char *sha1, void *cb_data) { static const char *capabilities = "multi_ack thin-pack side-band side-band-64k"; struct object *o = parse_object(sha1); @@ -444,8 +444,8 @@ static int send_ref(const char *refname, const unsigned char *sha1) static void upload_pack(void) { reset_timeout(); - head_ref(send_ref); - for_each_ref(send_ref); + head_ref(send_ref, NULL); + for_each_ref(send_ref, NULL); packet_flush(1); receive_needs(); if (want_obj.nr) { -- cgit v0.10.2-6-g49f6 From 8da197755450d4f16018bd4b5486dc8ed88b0f2a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 20 Sep 2006 22:02:01 -0700 Subject: Tell between packed, unpacked and symbolic refs. This adds a "int *flag" parameter to resolve_ref() and makes for_each_ref() family to call callback function with an extra "int flag" parameter. They are used to give two bits of information (REF_ISSYMREF and REF_ISPACKED) about the ref. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index b93c17c..3d3097d 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -277,7 +277,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) usage(fmt_merge_msg_usage); /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1); + current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); if (!strncmp(current_branch, "refs/heads/", 11)) current_branch += 11; diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 9e3e537..618aa31 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -75,7 +75,7 @@ copy_data: } } -static int name_ref(const char *path, const unsigned char *sha1, void *cb_data) +static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct object *o = parse_object(sha1); int tags_only = *(int*)cb_data; diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index b3d5470..9871089 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -9,7 +9,8 @@ static void remove_lock_file(void) unlink(lock_path); } -static int handle_one_ref(const char *path, const unsigned char *sha1, void *cb_data) +static int handle_one_ref(const char *path, const unsigned char *sha1, + int flags, void *cb_data) { FILE *refs_file = cb_data; diff --git a/builtin-prune.c b/builtin-prune.c index e21c29b..e79b515 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -174,7 +174,7 @@ static void walk_commit_list(struct rev_info *revs) } } -static int add_one_ref(const char *path, const unsigned char *sha1, void *cb_data) +static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *object = parse_object(sha1); if (!object) diff --git a/builtin-push.c b/builtin-push.c index 88fc8e2..581c44b 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -27,7 +27,7 @@ static void add_refspec(const char *ref) refspec_nr = nr; } -static int expand_one_ref(const char *ref, const unsigned char *sha1, void *cb_data) +static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data) { /* Ignore the "refs/" at the beginning of the refname */ ref += 5; diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index c771274..3b716fb 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -137,7 +137,7 @@ static void show_default(void) } } -static int show_reference(const char *refname, const unsigned char *sha1, void *cb_data) +static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { show_rev(NORMAL, sha1, refname); return 0; diff --git a/builtin-show-branch.c b/builtin-show-branch.c index b3548ae..5d6ce56 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -346,7 +346,7 @@ static void sort_ref_range(int bottom, int top) compare_ref_name); } -static int append_ref(const char *refname, const unsigned char *sha1, void *cb_data) +static int append_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); int i; @@ -369,7 +369,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, void *cb_d return 0; } -static int append_head_ref(const char *refname, const unsigned char *sha1, void *cb_data) +static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { unsigned char tmp[20]; int ofs = 11; @@ -380,14 +380,14 @@ static int append_head_ref(const char *refname, const unsigned char *sha1, void */ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) ofs = 5; - return append_ref(refname + ofs, sha1, cb_data); + return append_ref(refname + ofs, sha1, flag, cb_data); } -static int append_tag_ref(const char *refname, const unsigned char *sha1, void *cb_data) +static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { if (strncmp(refname, "refs/tags/", 10)) return 0; - return append_ref(refname + 5, sha1, cb_data); + return append_ref(refname + 5, sha1, flag, cb_data); } static const char *match_ref_pattern = NULL; @@ -401,7 +401,7 @@ static int count_slash(const char *s) return cnt; } -static int append_matching_ref(const char *refname, const unsigned char *sha1, void *cb_data) +static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { /* we want to allow pattern hold/<asterisk> to show all * branches under refs/heads/hold/, and v0.99.9? to show @@ -417,10 +417,10 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, v if (fnmatch(match_ref_pattern, tail, 0)) return 0; if (!strncmp("refs/heads/", refname, 11)) - return append_head_ref(refname, sha1, cb_data); + return append_head_ref(refname, sha1, flag, cb_data); if (!strncmp("refs/tags/", refname, 10)) - return append_tag_ref(refname, sha1, cb_data); - return append_ref(refname, sha1, cb_data); + return append_tag_ref(refname, sha1, flag, cb_data); + return append_ref(refname, sha1, flag, cb_data); } static void snarf_refs(int head, int tag) @@ -487,7 +487,7 @@ static void append_one_rev(const char *av) { unsigned char revkey[20]; if (!get_sha1(av, revkey)) { - append_ref(av, revkey, NULL); + append_ref(av, revkey, 0, NULL); return; } if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { @@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) ac--; av++; } - head_p = resolve_ref("HEAD", head_sha1, 1); + head_p = resolve_ref("HEAD", head_sha1, 1, NULL); if (head_p) { head_len = strlen(head_p); memcpy(head, head_p, head_len + 1); diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 13163ba..d8be052 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "refs.h" static const char git_symbolic_ref_usage[] = "git-symbolic-ref name [ref]"; @@ -7,10 +8,13 @@ static const char git_symbolic_ref_usage[] = static void check_symref(const char *HEAD) { unsigned char sha1[20]; - const char *refs_heads_master = resolve_ref(HEAD, sha1, 0); + int flag; + const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag); if (!refs_heads_master) die("No such ref: %s", HEAD); + else if (!(flag & REF_ISSYMREF)) + die("ref %s is not a symbolic ref", HEAD); puts(refs_heads_master); } diff --git a/cache.h b/cache.h index 282eed6..6def155 100644 --- a/cache.h +++ b/cache.h @@ -286,7 +286,7 @@ extern int get_sha1(const char *str, unsigned char *sha1); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); -extern const char *resolve_ref(const char *path, unsigned char *sha1, int); +extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); extern int create_symref(const char *ref, const char *refs_heads_master); extern int validate_symref(const char *ref); diff --git a/describe.c b/describe.c index ea0f2ce..f4029ee 100644 --- a/describe.c +++ b/describe.c @@ -53,7 +53,7 @@ static void add_to_known_names(const char *path, names = ++idx; } -static int get_name(const char *path, const unsigned char *sha1, void *cb_data) +static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); struct object *object; diff --git a/fetch-pack.c b/fetch-pack.c index 6264ea1..99ac08b 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -42,7 +42,7 @@ static void rev_list_push(struct commit *commit, int mark) } } -static int rev_list_insert_ref(const char *path, const unsigned char *sha1, void *cb_data) +static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = deref_tag(parse_object(sha1), path, 0); @@ -253,7 +253,7 @@ done: static struct commit_list *complete; -static int mark_complete(const char *path, const unsigned char *sha1, void *cb_data) +static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = parse_object(sha1); diff --git a/fetch.c b/fetch.c index 36d1e76..a2cbdfb 100644 --- a/fetch.c +++ b/fetch.c @@ -201,7 +201,7 @@ static int interpret_target(char *target, unsigned char *sha1) return -1; } -static int mark_complete(const char *path, const unsigned char *sha1, void *cb_data) +static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); if (commit) { diff --git a/fsck-objects.c b/fsck-objects.c index bb0c94e..46b628c 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -402,7 +402,7 @@ static void fsck_dir(int i, char *path) static int default_refs; -static int fsck_handle_ref(const char *refname, const unsigned char *sha1, void *cb_data) +static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; @@ -458,9 +458,10 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { unsigned char sha1[20]; - const char *head_points_at = resolve_ref("HEAD", sha1, 1); + int flag; + const char *head_points_at = resolve_ref("HEAD", sha1, 1, &flag); - if (!head_points_at) + if (!head_points_at || !(flag & REF_ISSYMREF)) return error("HEAD is not a symbolic ref"); if (strncmp(head_points_at, "refs/heads/", 11)) return error("HEAD points to something strange (%s)", diff --git a/http-push.c b/http-push.c index 460c9be..ecefdfd 100644 --- a/http-push.c +++ b/http-push.c @@ -1864,7 +1864,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) static struct ref *local_refs, **local_tail; static struct ref *remote_refs, **remote_tail; -static int one_local_ref(const char *refname, const unsigned char *sha1, void *cb_data) +static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct ref *ref; int len = strlen(refname) + 1; diff --git a/receive-pack.c b/receive-pack.c index 7abc921..abbcb6a 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -12,7 +12,7 @@ static int report_status; static char capabilities[] = "report-status"; static int capabilities_sent; -static int show_ref(const char *path, const unsigned char *sha1, void *cb_data) +static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { if (capabilities_sent) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); @@ -27,7 +27,7 @@ static void write_head_info(void) { for_each_ref(show_ref, NULL); if (!capabilities_sent) - show_ref("capabilities^{}", null_sha1, NULL); + show_ref("capabilities^{}", null_sha1, 0, NULL); } diff --git a/refs.c b/refs.c index 85564f0..40f16af 100644 --- a/refs.c +++ b/refs.c @@ -5,6 +5,7 @@ struct ref_list { struct ref_list *next; + unsigned char flag; /* ISSYMREF? ISPACKED? */ unsigned char sha1[20]; char name[FLEX_ARRAY]; }; @@ -36,7 +37,8 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) return line; } -static struct ref_list *add_ref(const char *name, const unsigned char *sha1, struct ref_list *list) +static struct ref_list *add_ref(const char *name, const unsigned char *sha1, + int flag, struct ref_list *list) { int len; struct ref_list **p = &list, *entry; @@ -58,6 +60,7 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, str entry = xmalloc(sizeof(struct ref_list) + len); hashcpy(entry->sha1, sha1); memcpy(entry->name, name, len); + entry->flag = flag; entry->next = *p; *p = entry; return list; @@ -78,7 +81,7 @@ static struct ref_list *get_packed_refs(void) const char *name = parse_ref_line(refline, sha1); if (!name) continue; - list = add_ref(name, sha1, list); + list = add_ref(name, sha1, REF_ISPACKED, list); } fclose(f); refs = list; @@ -104,6 +107,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) while ((de = readdir(dir)) != NULL) { unsigned char sha1[20]; struct stat st; + int flag; int namelen; if (de->d_name[0] == '.') @@ -120,11 +124,11 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) list = get_ref_dir(ref, list); continue; } - if (read_ref(ref, sha1) < 0) { + if (!resolve_ref(ref, sha1, 1, &flag)) { error("%s points nowhere!", ref); continue; } - list = add_ref(ref, sha1, list); + list = add_ref(ref, sha1, flag, list); } free(ref); closedir(dir); @@ -147,12 +151,15 @@ static struct ref_list *get_loose_refs(void) /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -const char *resolve_ref(const char *ref, unsigned char *sha1, int reading) +const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag) { int depth = MAXDEPTH, len; char buffer[256]; static char ref_buffer[256]; + if (flag) + *flag = 0; + for (;;) { const char *path = git_path("%s", ref); struct stat st; @@ -174,6 +181,8 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading) while (list) { if (!strcmp(ref, list->name)) { hashcpy(sha1, list->sha1); + if (flag) + *flag |= REF_ISPACKED; return ref; } list = list->next; @@ -191,6 +200,8 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading) buffer[len] = 0; strcpy(ref_buffer, buffer); ref = ref_buffer; + if (flag) + *flag |= REF_ISSYMREF; continue; } } @@ -219,6 +230,8 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading) buf[len] = 0; memcpy(ref_buffer, buf, len + 1); ref = ref_buffer; + if (flag) + *flag |= REF_ISSYMREF; } if (len < 40 || get_sha1_hex(buffer, sha1)) return NULL; @@ -270,12 +283,13 @@ int create_symref(const char *ref_target, const char *refs_heads_master) int read_ref(const char *ref, unsigned char *sha1) { - if (resolve_ref(ref, sha1, 1)) + if (resolve_ref(ref, sha1, 1, NULL)) return 0; return -1; } -static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_data) +static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, + void *cb_data) { int retval; struct ref_list *packed = get_packed_refs(); @@ -303,7 +317,8 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_ error("%s does not point to a valid object!", entry->name); continue; } - retval = fn(entry->name + trim, entry->sha1, cb_data); + retval = fn(entry->name + trim, entry->sha1, + entry->flag, cb_data); if (retval) return retval; } @@ -311,7 +326,8 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_ packed = packed ? packed : loose; while (packed) { if (!strncmp(base, packed->name, trim)) { - retval = fn(packed->name + trim, packed->sha1, cb_data); + retval = fn(packed->name + trim, packed->sha1, + packed->flag, cb_data); if (retval) return retval; } @@ -323,8 +339,10 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_ int head_ref(each_ref_fn fn, void *cb_data) { unsigned char sha1[20]; - if (!read_ref("HEAD", sha1)) - return fn("HEAD", sha1, cb_data); + int flag; + + if (resolve_ref("HEAD", sha1, 1, &flag)) + return fn("HEAD", sha1, flag, cb_data); return 0; } @@ -415,7 +433,7 @@ int check_ref_format(const char *ref) static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { - if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist)) { + if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) { error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); return NULL; @@ -441,7 +459,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - ref = resolve_ref(ref, lock->old_sha1, mustexist); + ref = resolve_ref(ref, lock->old_sha1, mustexist, NULL); if (!ref) { int last_errno = errno; error("unable to resolve reference %s: %s", diff --git a/refs.h b/refs.h index 886c857..305d408 100644 --- a/refs.h +++ b/refs.h @@ -14,7 +14,9 @@ struct ref_lock { * Calls the specified function for each ref file until it returns nonzero, * and returns the value */ -typedef int each_ref_fn(const char *refname, const unsigned char *sha1, void *cb_data); +#define REF_ISSYMREF 01 +#define REF_ISPACKED 02 +typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); extern int for_each_tag_ref(each_ref_fn, void *); diff --git a/revision.c b/revision.c index 0e84b8a..cb13b90 100644 --- a/revision.c +++ b/revision.c @@ -466,7 +466,7 @@ static void limit_list(struct rev_info *revs) static int all_flags; static struct rev_info *all_revs; -static int handle_one_ref(const char *path, const unsigned char *sha1, void *cb_data) +static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *object = get_reference(all_revs, path, sha1, all_flags); add_pending_object(all_revs, object, ""); diff --git a/send-pack.c b/send-pack.c index ee13093..fbd792c 100644 --- a/send-pack.c +++ b/send-pack.c @@ -215,7 +215,7 @@ static int ref_newer(const unsigned char *new_sha1, static struct ref *local_refs, **local_tail; static struct ref *remote_refs, **remote_tail; -static int one_local_ref(const char *refname, const unsigned char *sha1, void *cb_data) +static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct ref *ref; int len = strlen(refname) + 1; diff --git a/server-info.c b/server-info.c index 7667b41..6cd38be 100644 --- a/server-info.c +++ b/server-info.c @@ -7,7 +7,7 @@ /* refs */ static FILE *info_ref_fp; -static int add_info_ref(const char *path, const unsigned char *sha1, void *cb_data) +static int add_info_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = parse_object(sha1); diff --git a/sha1_name.c b/sha1_name.c index b497528..84d24c6 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -276,7 +276,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) for (p = fmt; *p; p++) { this_result = refs_found ? sha1_from_ref : sha1; - ref = resolve_ref(mkpath(*p, len, str), this_result, 1); + ref = resolve_ref(mkpath(*p, len, str), this_result, 1, NULL); if (ref) { if (!refs_found++) real_ref = xstrdup(ref); diff --git a/upload-pack.c b/upload-pack.c index 10237eb..9412a9b 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -416,7 +416,7 @@ static void receive_needs(void) } } -static int send_ref(const char *refname, const unsigned char *sha1, void *cb_data) +static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { static const char *capabilities = "multi_ack thin-pack side-band side-band-64k"; struct object *o = parse_object(sha1); diff --git a/wt-status.c b/wt-status.c index 050922d..d8e284c 100644 --- a/wt-status.c +++ b/wt-status.c @@ -41,7 +41,7 @@ void wt_status_prepare(struct wt_status *s) s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0; - head = resolve_ref("HEAD", sha1, 0); + head = resolve_ref("HEAD", sha1, 0, NULL); s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; -- cgit v0.10.2-6-g49f6 From 9edd7e4652e080a1a3b1ef614d22eba75b39ef87 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Thu, 21 Sep 2006 02:07:19 +0200 Subject: receive-pack: plug memory leak in fast-forward checking code. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/receive-pack.c b/receive-pack.c index a6ec9f9..ea2dbd4 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -131,17 +131,18 @@ static int update(struct command *cmd) } if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { struct commit *old_commit, *new_commit; - struct commit_list *bases; + struct commit_list *bases, *ent; old_commit = (struct commit *)parse_object(old_sha1); new_commit = (struct commit *)parse_object(new_sha1); - for (bases = get_merge_bases(old_commit, new_commit, 1); - bases; bases = bases->next) - if (!hashcmp(old_sha1, bases->item->object.sha1)) + bases = get_merge_bases(old_commit, new_commit, 1); + for (ent = bases; ent; ent = ent->next) + if (!hashcmp(old_sha1, ent->item->object.sha1)) break; - if (!bases) + free_commit_list(bases); + if (!ent) return error("denying non-fast forward;" - " you should pull first"); + " you should pull first"); } safe_create_leading_directories(lock_name); -- cgit v0.10.2-6-g49f6 From 199a92186b6721b23a2400c91f8bd44e7ffa349a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Thu, 21 Sep 2006 02:10:30 +0200 Subject: Document receive.denyNonFastforwards [jc: with a fix to config handling in t5400 test, which took annoyingly long to diagnose.] Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/config.txt b/Documentation/config.txt index 844cae4..bb2fbc3 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -267,3 +267,10 @@ whatchanged.difftree:: imap:: The configuration variables in the 'imap' section are described in gitlink:git-imap-send[1]. + +receive.denyNonFastforwads:: + If set to true, git-receive-pack will deny a ref update which is + not a fast forward. Use this to prevent such an update via a push, + even if that push is forced. This configuration variable is + set when initializing a shared repository. + diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 63cd5da..ca7d09d 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -48,6 +48,10 @@ is given: - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository readable by all users. +By default, the configuration flag receive.denyNonFastforward is enabled +in shared repositories, so that you cannot force a non fast-forwarding push +into it. + -- diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index f9457d4..0dfadc2 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -73,6 +73,8 @@ packed and is served via a dumb transport. There are other real-world examples of using update and post-update hooks found in the Documentation/howto directory. +git-receive-pack honours the receive.denyNonFastforwards flag, which +tells it if updates to a ref should be denied if they are not fast-forwards. OPTIONS ------- diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index f3694ac..8afb899 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -64,4 +64,18 @@ test_expect_success \ cmp victim/.git/refs/heads/master .git/refs/heads/master ' +unset GIT_CONFIG GIT_CONFIG_LOCAL +HOME=`pwd`/no-such-directory +export HOME ;# this way we force the victim/.git/config to be used. + +test_expect_success \ + 'pushing with --force should be denied with denyNonFastforwards' ' + cd victim && + git-repo-config receive.denyNonFastforwards true && + cd .. && + git-update-ref refs/heads/master master^ && + git-send-pack --force ./victim/.git/ master && + ! diff -u .git/refs/heads/master victim/.git/refs/heads/master +' + test_done -- cgit v0.10.2-6-g49f6 From 13e4aa90acad5738f54385c8a336f89fb6aacdd0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Thu, 21 Sep 2006 00:06:05 -0700 Subject: pack-refs: do not pack symbolic refs. Now we can tell which one is symbolic and which one is not, it is easy to do so. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 9871089..0fc8a55 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -14,7 +14,9 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, { FILE *refs_file = cb_data; - fprintf(refs_file, "%s %s\n", sha1_to_hex(sha1), path); + /* Do not pack the symbolic refs */ + if (!(flags & REF_ISSYMREF)) + fprintf(refs_file, "%s %s\n", sha1_to_hex(sha1), path); return 0; } -- cgit v0.10.2-6-g49f6 From 968846015229fe0fec28e3c85db722e131c15f93 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Thu, 21 Sep 2006 00:06:06 -0700 Subject: git-pack-refs --prune "git pack-refs --prune", after successfully packing the existing refs, removes the loose ref files. It tries to protect against race by doing the usual lock_ref_sha1() which makes sure the contents of the ref has not changed since we last looked at. Also we do not bother trying to prune what was already packed, and we do not try pruning symbolic refs. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 0fc8a55..246dd63 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -2,6 +2,20 @@ #include "refs.h" static const char *result_path, *lock_path; +static const char builtin_pack_refs_usage[] = +"git-pack-refs [--prune]"; + +struct ref_to_prune { + struct ref_to_prune *next; + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +struct pack_refs_cb_data { + int prune; + struct ref_to_prune *ref_to_prune; + FILE *refs_file; +}; static void remove_lock_file(void) { @@ -9,21 +23,70 @@ static void remove_lock_file(void) unlink(lock_path); } +static int do_not_prune(int flags) +{ + /* If it is already packed or if it is a symref, + * do not prune it. + */ + return (flags & (REF_ISSYMREF|REF_ISPACKED)); +} + static int handle_one_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { - FILE *refs_file = cb_data; + struct pack_refs_cb_data *cb = cb_data; /* Do not pack the symbolic refs */ if (!(flags & REF_ISSYMREF)) - fprintf(refs_file, "%s %s\n", sha1_to_hex(sha1), path); + fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if (cb->prune && !do_not_prune(flags)) { + int namelen = strlen(path) + 1; + struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); + hashcpy(n->sha1, sha1); + strcpy(n->name, path); + n->next = cb->ref_to_prune; + cb->ref_to_prune = n; + } return 0; } +/* make sure nobody touched the ref, and unlink */ +static void prune_ref(struct ref_to_prune *r) +{ + struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1, 1); + + if (lock) { + unlink(git_path(r->name)); + unlock_ref(lock); + } +} + +static void prune_refs(struct ref_to_prune *r) +{ + while (r) { + prune_ref(r); + r = r->next; + } +} + int cmd_pack_refs(int argc, const char **argv, const char *prefix) { - int fd; - FILE *refs_file; + int fd, i; + struct pack_refs_cb_data cbdata; + + memset(&cbdata, 0, sizeof(cbdata)); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--prune")) { + cbdata.prune = 1; + continue; + } + /* perhaps other parameters later... */ + break; + } + if (i != argc) + usage(builtin_pack_refs_usage); result_path = xstrdup(git_path("packed-refs")); lock_path = xstrdup(mkpath("%s.lock", result_path)); @@ -33,14 +96,17 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) die("unable to create new ref-pack file (%s)", strerror(errno)); atexit(remove_lock_file); - refs_file = fdopen(fd, "w"); - if (!refs_file) - die("unable to create ref-pack file structure (%s)", strerror(errno)); - for_each_ref(handle_one_ref, refs_file); + cbdata.refs_file = fdopen(fd, "w"); + if (!cbdata.refs_file) + die("unable to create ref-pack file structure (%s)", + strerror(errno)); + for_each_ref(handle_one_ref, &cbdata); fsync(fd); - fclose(refs_file); + fclose(cbdata.refs_file); if (rename(lock_path, result_path) < 0) die("unable to overwrite old ref-pack file (%s)", strerror(errno)); lock_path = NULL; + if (cbdata.prune) + prune_refs(cbdata.ref_to_prune); return 0; } -- cgit v0.10.2-6-g49f6 From 053d62bb5bd523f492c6ef2e202da837b7f56905 Mon Sep 17 00:00:00 2001 From: Martin Waitz <tali@admingilde.org> Date: Thu, 21 Sep 2006 09:48:21 +0200 Subject: gitweb: fix display of trees via PATH_INFO. When adding a / to the URL, git should display the corresponding tree object, but it has to remove the / first. Signed-off-by: Martin Waitz <tali@admingilde.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index baadbe7..ea57717 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -300,6 +300,7 @@ sub evaluate_path_info { $pathname =~ s,^/+,,; if (!$pathname || substr($pathname, -1) eq "/") { $action ||= "tree"; + $pathname =~ s,/$,,; } else { $action ||= "blob_plain"; } -- cgit v0.10.2-6-g49f6 From 16fdb4882e3f7b5b60907a2729df494aaa1410a3 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@ucw.cz> Date: Thu, 21 Sep 2006 02:05:50 +0200 Subject: Fix showing of path in tree view This patch fixes two things - links to all path elements except the last one were broken since gitweb does not like the trailing slash in them, and the root tree was not reachable from the subdirectory view. To compensate for the one more slash in the front, the trailing slash is not there anymore. ;-) I don't care if it stays there though. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ea57717..fb8d37e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1514,12 +1514,15 @@ sub git_print_page_path { my $fullname = ''; print "<div class=\"page_path\">"; + print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), + -title => '/'}, '/'); + print " "; foreach my $dir (@dirname) { - $fullname .= $dir . '/'; + $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => $fullname}, esc_html($dir)); - print "/"; + -title => $fullname}, esc_html($dir . '/')); + print " "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, @@ -1528,8 +1531,7 @@ sub git_print_page_path { } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename)); - print "/"; + -title => $name}, esc_html($basename . '/')); } else { print esc_html($basename); } -- cgit v0.10.2-6-g49f6 From 1729fa9878ed8c99ae0bb2aecced557618d0c894 Mon Sep 17 00:00:00 2001 From: Andy Whitcroft <apw@shadowen.org> Date: Thu, 21 Sep 2006 10:19:17 +0100 Subject: git-for-each-ref: improve the documentation on scripting modes When reading the synopsis for git-for-each-ref it is easy to miss the obvious power of --shell and family. Call this feature out in the primary paragragh. Also add more description to the examples to indicate which features we are demonstrating. Finally add a very simple eval based example in addition to the very complex one to give a gentler introduction. Signed-off-by: Andy Whitcroft <apw@shadowen.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 6649f79..d5fdcef 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -17,7 +17,7 @@ according to the given `<format>`, after sorting them according to the given set of `<key>`s. If `<max>` is given, stop after showing that many refs. The interporated values in `<format>` can optionally be quoted as string literals in the specified -host language. +host language allowing their direct evaluation in that language. OPTIONS ------- @@ -97,7 +97,8 @@ returns an empty string instead. EXAMPLES -------- -Show the most recent 3 tagged commits:: +An example directly producing formatted text. Show the most recent +3 tagged commits:: ------------ #!/bin/sh @@ -112,7 +113,23 @@ Ref: %(*refname) ' 'refs/tags' ------------ -A bit more elaborate report on tags:: + +A simple example showing the use of shell eval on the output, +demonstrating the use of --shell. List the prefixes of all heads:: +------------ +#!/bin/sh + +git-for-each-ref --shell --format="ref=%(refname)" refs/heads | \ +while read entry +do + eval "$entry" + echo `dirname $ref` +done +------------ + + +A bit more elaborate report on tags, demonstrating that the format +may be an entire script:: ------------ #!/bin/sh @@ -156,7 +173,7 @@ Its message reads as: fi ' -eval=`git-for-each-ref -s --format="$fmt" \ +eval=`git-for-each-ref --shell --format="$fmt" \ --sort='*objecttype' \ --sort=-taggerdate \ refs/tags` -- cgit v0.10.2-6-g49f6 From 1d782b03b078c1a525cbb5728f6dc5eb06f06924 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Thu, 21 Sep 2006 18:09:12 +0200 Subject: gitweb: Make git_get_hash_by_path check type if provided Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fb8d37e..d3757f4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -718,6 +718,7 @@ sub git_get_project_config { sub git_get_hash_by_path { my $base = shift; my $path = shift || return undef; + my $type = shift; my $tree = $base; @@ -728,6 +729,10 @@ sub git_get_hash_by_path { #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + if (defined $type && $type ne $2) { + # type doesn't match + return undef; + } return $3; } -- cgit v0.10.2-6-g49f6 From 913c983e0481b477ccc11fa72ebeb0d6ec8d06aa Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Thu, 21 Sep 2006 23:29:59 +0200 Subject: Fix git-update-index --again It called read_ref(git_path(..)..), where read_ref does the git_path() stuff itself. Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-update-index.c b/builtin-update-index.c index 0620e77..09214c8 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -404,9 +404,9 @@ static int unresolve_one(const char *path) static void read_head_pointers(void) { - if (read_ref(git_path("HEAD"), head_sha1)) + if (read_ref("HEAD", head_sha1)) die("No HEAD -- no initial commit yet?\n"); - if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) { + if (read_ref("MERGE_HEAD", merge_head_sha1)) { fprintf(stderr, "Not in the middle of a merge.\n"); exit(0); } @@ -443,7 +443,7 @@ static int do_reupdate(int ac, const char **av, int has_head = 1; const char **pathspec = get_pathspec(prefix, av + 1); - if (read_ref(git_path("HEAD"), head_sha1)) + if (read_ref("HEAD", head_sha1)) /* If there is no HEAD, that means it is an initial * commit. Update everything in the index. */ -- cgit v0.10.2-6-g49f6 From afdcec73660737d0ac28a808829ab76587a9befa Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Fri, 22 Sep 2006 00:07:01 +0200 Subject: show-branch: mark active branch with a '*' again This was lost in the packed-ref updates. The original test was a bit dubious, so I cleaned that up, too. It fixes the case when the current HEAD is refs/heads/bla/master: the original test was true for both bla/master _and_ master. However, it shares a hard-to-fix bug with the original test: if the current HEAD is refs/heads/master, and there is a branch refs/heads/heads/master, then both are marked active. Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 5d6ce56..fb1a400 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -443,6 +443,12 @@ static int rev_is_head(char *head, int headlen, char *name, if ((!head[0]) || (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; + if (!strncmp(head, "refs/heads/", 11)) + head += 11; + if (!strncmp(name, "refs/heads/", 11)) + name += 11; + else if (!strncmp(name, "heads/", 6)) + name += 6; return !strcmp(head, name); } -- cgit v0.10.2-6-g49f6 From 609ff267fb03fb10dcefd15fc1f0ef3d7a1ba5ce Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Fri, 22 Sep 2006 01:58:40 +0200 Subject: gitweb: Link (HEAD) tree for each project from projects list Current projects list is oriented on easily getting "what's new" information. But when already using gitweb as an interface to something, I personally find myself to _much_ more frequently wanting to rather see "what's in" (or "what's new in") and it's quite annoying to have to go through the summary page (which is also rather expensive to generate) just to get there. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d3757f4..8fd7f66 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2282,7 +2282,8 @@ sub git_project_list { "<td class=\"link\">" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . "</td>\n" . "</tr>\n"; } -- cgit v0.10.2-6-g49f6 From cae1862a3b55b487731e9857f2213ac59d5646d1 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Fri, 22 Sep 2006 03:19:41 +0200 Subject: gitweb: More per-view navigation bar links Navigation bars in various views were empty or missed important items that should have been there, e.g. getting a snapshot in tree view or log of ancestry in commit view... This feeble patch attempts to consolidate that. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8fd7f66..8ce77f6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2441,6 +2441,9 @@ sub git_blame2 { $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . " | " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); @@ -2507,6 +2510,9 @@ sub git_blame { $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . " | " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); @@ -2682,6 +2688,10 @@ sub git_blob { " | "; } $formats_nav .= + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$hash, file_name=>$file_name)}, "plain") . @@ -2717,6 +2727,9 @@ sub git_blob { } sub git_tree { + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + if (!defined $hash) { $hash = git_get_head_hash($project); if (defined $file_name) { @@ -2740,7 +2753,23 @@ sub git_tree { my $base = ""; my ($have_blame) = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { - git_print_page_nav('tree','', $hash_base); + my @views_nav = (); + if (defined $file_name) { + push @views_nav, + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "history"), + $cgi->a({-href => href(action=>"tree", + hash_base=>"HEAD", file_name=>$file_name)}, + "head"); + } + if ($have_snapshot) { + # FIXME: Should be available when we have no hash base as well. + push @views_nav, + $cgi->a({-href => href(action=>"snapshot")}, + "snapshot"); + } + git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { undef $hash_base; @@ -2885,17 +2914,22 @@ sub git_commit { my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); my $have_snapshot = (defined $ctype && defined $suffix); - my $formats_nav = ''; + my @views_nav = (); if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; - $formats_nav .= + push @views_nav, $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); } + if (defined $co{'parent'}) { + push @views_nav, + $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"), + $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log"); + } git_header_html(undef, $expires); git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', $hash, $co{'tree'}, $hash, - $formats_nav); + join (' | ', @views_nav)); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); -- cgit v0.10.2-6-g49f6 From 35749ae566b15d1860cbfba5bc5ac227eb785715 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Fri, 22 Sep 2006 03:19:44 +0200 Subject: gitweb: Link to tree instead of snapshot in shortlog Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8ce77f6..cbbd75c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1975,9 +1975,6 @@ sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); - $from = 0 unless defined $from; $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); @@ -2003,10 +2000,8 @@ sub git_shortlog_body { print "</td>\n" . "<td class=\"link\">" . $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . - $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); - if ($have_snapshot) { - print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); - } + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); print "</td>\n" . "</tr>\n"; } -- cgit v0.10.2-6-g49f6 From 1d62be25ed931f1892fad8639037c99677db5d1d Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Fri, 22 Sep 2006 03:19:46 +0200 Subject: gitweb: Link to latest tree from the head line in heads list Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index cbbd75c..0091e18 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2163,7 +2163,8 @@ sub git_heads_body { "</td>\n" . "<td class=\"link\">" . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . - $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . + $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") . "</td>\n" . "</tr>"; } -- cgit v0.10.2-6-g49f6 From 6ef4cb2e8dd791612044f5e71f61a4788e87c4ac Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Fri, 22 Sep 2006 03:19:48 +0200 Subject: gitweb: Link to associated tree from a particular log item in full log view Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0091e18..34ef3fc 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2870,6 +2870,8 @@ sub git_log { $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$commit), hash_base=>$commit}, "tree") . "<br/>\n" . "</div>\n" . "<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" . -- cgit v0.10.2-6-g49f6 From 35329cc1ccd8c720628a72276402d5c3788b48e7 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Fri, 22 Sep 2006 03:19:50 +0200 Subject: gitweb: Rename "plain" labels to "raw" I don't have much preference either way and as far as I'm concerned, it may go the other way as well. Consistency is what is important. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 34ef3fc..8b4d34f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2690,14 +2690,14 @@ sub git_blob { " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$hash, file_name=>$file_name)}, - "plain") . + "raw") . " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$file_name)}, "head"); } else { $formats_nav .= - $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "plain"); + $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw"); } git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); @@ -3106,7 +3106,7 @@ sub git_blobdiff { hash=>$hash, hash_parent=>$hash_parent, hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, file_name=>$file_name, file_parent=>$file_parent)}, - "plain"); + "raw"); git_header_html(undef, $expires); if (defined $hash_base && (my %co = parse_commit($hash_base))) { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); @@ -3209,7 +3209,7 @@ sub git_commitdiff { my $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, - "plain"); + "raw"); git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); -- cgit v0.10.2-6-g49f6 From f35274dad8e617c1ea2c55c2b7b0fbbcb1abd8ae Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Fri, 22 Sep 2006 03:19:53 +0200 Subject: gitweb: Relabel "head" as "HEAD" "head" is a reference in refs/heads/, while those labels mean HEAD, the latest revision of the default branch. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8b4d34f..1ce4973 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2441,7 +2441,7 @@ sub git_blame2 { "history") . " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, - "head"); + "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); @@ -2510,7 +2510,7 @@ sub git_blame { "history") . " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, - "head"); + "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, 'blob', $hash_base); @@ -2694,7 +2694,7 @@ sub git_blob { " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$file_name)}, - "head"); + "HEAD"); } else { $formats_nav .= $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw"); @@ -2757,7 +2757,7 @@ sub git_tree { "history"), $cgi->a({-href => href(action=>"tree", hash_base=>"HEAD", file_name=>$file_name)}, - "head"); + "HEAD"), } if ($have_snapshot) { # FIXME: Should be available when we have no hash base as well. -- cgit v0.10.2-6-g49f6 From 26d0a976e88cf2e1ccb0b4d8172e9ce24d144d1f Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Sat, 23 Sep 2006 01:00:12 +0200 Subject: Make path in tree view look nicer Based on talk on the IRC with Junio some evenings ago, I've updated the path showing in tree view to look better and sent updated patches privately, but it seems the old version ended up being used, so here's the new one again. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1ce4973..b9df3cc 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1520,14 +1520,14 @@ sub git_print_page_path { print "<div class=\"page_path\">"; print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => '/'}, '/'); - print " "; + -title => 'tree root'}, "[$project]"); + print " / "; foreach my $dir (@dirname) { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => $fullname}, esc_html($dir . '/')); - print " "; + -title => $fullname}, esc_html($dir)); + print " / "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, @@ -1536,7 +1536,7 @@ sub git_print_page_path { } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename . '/')); + -title => $name}, esc_html($basename)); } else { print esc_html($basename); } -- cgit v0.10.2-6-g49f6 From 7c1a278d99a18445e12bd55e308cd69080963198 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Sat, 23 Sep 2006 01:08:45 +0200 Subject: Fix buggy ref recording There is a format string vulnerability introduced with the packed refs file format. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 40f16af..5fdf9c4 100644 --- a/refs.c +++ b/refs.c @@ -472,7 +472,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, lock->ref_name = xstrdup(ref); lock->log_file = xstrdup(git_path("logs/%s", ref)); - ref_file = git_path(ref); + ref_file = git_path("%s", ref); lock->force_write = lstat(ref_file, &st) && errno == ENOENT; if (safe_create_leading_directories(ref_file)) -- cgit v0.10.2-6-g49f6 From d72672074361a738afbf3139d8921ddf8bd782f8 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Fri, 22 Sep 2006 16:56:43 -0700 Subject: gitweb: Fix tree link associated with each commit log entry. The link forgot to have hb parameter and the resulting tree view failed to show the navbar for that commit. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b9df3cc..30d7d76 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2871,7 +2871,7 @@ sub git_log { " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$commit), hash_base=>$commit}, "tree") . + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . "<br/>\n" . "</div>\n" . "<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" . -- cgit v0.10.2-6-g49f6 From 74d6166751ddcf08029ffc90a14158a86f80cd40 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Sat, 23 Sep 2006 01:15:18 +0200 Subject: gitweb: Fix @git_base_url_list usage As it is now, that array was never used because the customurl accessor was broken and ''unless @url_list'' never happenned. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 30d7d76..7ff5c04 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -752,7 +752,7 @@ sub git_get_project_description { sub git_get_project_url_list { my $path = shift; - open my $fd, "$projectroot/$path/cloneurl" or return undef; + open my $fd, "$projectroot/$path/cloneurl" or return; my @git_project_url_list = map { chomp; $_ } <$fd>; close $fd; -- cgit v0.10.2-6-g49f6 From 5c7d2cf3d6a059038d8d0bda6a76fa7818a9caa0 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Sat, 23 Sep 2006 01:21:20 +0200 Subject: Fix snapshot link in tree view It would just give HEAD snapshot instead of one of the particular tree. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7ff5c04..3d06181 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2762,7 +2762,7 @@ sub git_tree { if ($have_snapshot) { # FIXME: Should be available when we have no hash base as well. push @views_nav, - $cgi->a({-href => href(action=>"snapshot")}, + $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot"); } git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); -- cgit v0.10.2-6-g49f6 From 3d5c0cc9387b35df47c988fbc0e4379e413d783e Mon Sep 17 00:00:00 2001 From: Robin Rosenberg <robin.rosenberg@dewire.com> Date: Sat, 23 Sep 2006 00:35:20 +0200 Subject: Quote arguments to tr in test-lib When there are single-character filenames in the test directory, the shell tries to expand regexps meant for tr. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/test-lib.sh b/t/test-lib.sh index e262933..e75ad5f 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -34,7 +34,7 @@ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR VISUAL -case $(echo $GIT_TRACE |tr [A-Z] [a-z]) in +case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in 1|2|true) echo "* warning: Some tests will not work if GIT_TRACE" \ "is set as to trace on STDERR ! *" -- cgit v0.10.2-6-g49f6 From ae35b30433f5b732bd21f9577711584e3f9bba06 Mon Sep 17 00:00:00 2001 From: Sasha Khapyorsky <sashak@voltaire.com> Date: Tue, 5 Sep 2006 21:46:11 +0300 Subject: git-svnimport: Parse log message for Signed-off-by: lines This add '-S' option. When specified svn-import will try to parse commit message for 'Signed-off-by: ...' line, and if found will use the name and email address extracted at first occurrence as this commit author name and author email address. Committer name and email are extracted in usual way. Signed-off-by: Sasha Khapyorsky <sashak@voltaire.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-svnimport.perl b/git-svnimport.perl index 26dc454..ed62897 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -31,7 +31,7 @@ $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, - $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D); + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S); sub usage() { print STDERR <<END; @@ -39,12 +39,12 @@ Usage: ${\basename $0} # fetch/update GIT from SVN [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname] [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg] - [-m] [-M regex] [-A author_file] [SVN_URL] + [-m] [-M regex] [-A author_file] [-S] [SVN_URL] END exit(1); } -getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage(); +getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:Suv") or usage(); usage if $opt_h; my $tag_name = $opt_t || "tags"; @@ -531,21 +531,30 @@ sub copy_path($$$$$$$$) { sub commit { my($branch, $changed_paths, $revision, $author, $date, $message) = @_; - my($author_name,$author_email,$dest); + my($committer_name,$committer_email,$dest); + my($author_name,$author_email); my(@old,@new,@parents); if (not defined $author or $author eq "") { - $author_name = $author_email = "unknown"; + $committer_name = $committer_email = "unknown"; } elsif (defined $users_file) { die "User $author is not listed in $users_file\n" unless exists $users{$author}; - ($author_name,$author_email) = @{$users{$author}}; + ($committer_name,$committer_email) = @{$users{$author}}; } elsif ($author =~ /^(.*?)\s+<(.*)>$/) { - ($author_name, $author_email) = ($1, $2); + ($committer_name, $committer_email) = ($1, $2); } else { $author =~ s/^<(.*)>$/$1/; - $author_name = $author_email = $author; + $committer_name = $committer_email = $author; + } + + if ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + ($author_name, $author_email) = ($1, $2); + } else { + $author_name = $committer_name; + $author_email = $committer_email; } + $date = pdate($date); my $tag; @@ -772,8 +781,8 @@ sub commit { "GIT_AUTHOR_NAME=$author_name", "GIT_AUTHOR_EMAIL=$author_email", "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "GIT_COMMITTER_NAME=$author_name", - "GIT_COMMITTER_EMAIL=$author_email", + "GIT_COMMITTER_NAME=$committer_name", + "GIT_COMMITTER_EMAIL=$committer_email", "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), "git-commit-tree", $tree,@par); die "Cannot exec git-commit-tree: $!\n"; @@ -825,7 +834,7 @@ sub commit { print $out ("object $cid\n". "type commit\n". "tag $dest\n". - "tagger $author_name <$author_email>\n") and + "tagger $committer_name <$committer_email>\n") and close($out) or die "Cannot create tag object $dest: $!\n"; -- cgit v0.10.2-6-g49f6 From 16854571aae6302f457c5fbee41ac64669b09595 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Thu, 21 Sep 2006 00:11:59 -0400 Subject: move pack creation to version 3 It's been quite a while now that GIT is able to read version 3 packs. Let's create them at last. Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff-delta.c b/diff-delta.c index fa16d06..51df460 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -308,8 +308,8 @@ create_delta(const struct delta_index *index, continue; if (ref_size > top - src) ref_size = top - src; - if (ref_size > 0x10000) - ref_size = 0x10000; + if (ref_size > 0xffffff) + ref_size = 0xffffff; if (ref_size <= msize) break; while (ref_size-- && *src++ == *ref) @@ -318,6 +318,8 @@ create_delta(const struct delta_index *index, /* this is our best match so far */ msize = ref - entry->ptr; moff = entry->ptr - ref_data; + if (msize >= 0x10000) + break; /* this is good enough */ } } @@ -381,6 +383,8 @@ create_delta(const struct delta_index *index, if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; } msize >>= 8; if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; } + msize >>= 8; + if (msize & 0xff) { out[outpos++] = msize; i |= 0x40; } *op = i; } diff --git a/pack.h b/pack.h index eb07b03..05557da 100644 --- a/pack.h +++ b/pack.h @@ -7,7 +7,7 @@ * Packed object header */ #define PACK_SIGNATURE 0x5041434b /* "PACK" */ -#define PACK_VERSION 2 +#define PACK_VERSION 3 #define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) struct pack_header { unsigned int hdr_signature; -- cgit v0.10.2-6-g49f6 From 6d15987c1e06dab2c0470b86cf9f3df747891f5a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 22 Sep 2006 21:31:40 -0700 Subject: pack-refs: fix git_path() usage. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 246dd63..db57fee 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -56,7 +56,7 @@ static void prune_ref(struct ref_to_prune *r) struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1, 1); if (lock) { - unlink(git_path(r->name)); + unlink(git_path("%s", r->name)); unlock_ref(lock); } } -- cgit v0.10.2-6-g49f6 From 5fd6f5cffc7b8bc679c922b8ff0b1cd35bb1a30c Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 22 Sep 2006 21:41:49 -0700 Subject: lock_ref_sha1_basic: remove unused parameter "plen". Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 5fdf9c4..2cef2b4 100644 --- a/refs.c +++ b/refs.c @@ -447,9 +447,7 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } -static struct ref_lock *lock_ref_sha1_basic(const char *ref, - int plen, - const unsigned char *old_sha1, int mustexist) +static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int mustexist) { char *ref_file; const char *orig_ref = ref; @@ -489,14 +487,13 @@ struct ref_lock *lock_ref_sha1(const char *ref, if (check_ref_format(ref)) return NULL; strcpy(refpath, mkpath("refs/%s", ref)); - return lock_ref_sha1_basic(refpath, strlen(refpath), - old_sha1, mustexist); + return lock_ref_sha1_basic(refpath, old_sha1, mustexist); } struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist) { - return lock_ref_sha1_basic(ref, strlen(ref), old_sha1, mustexist); + return lock_ref_sha1_basic(ref, old_sha1, mustexist); } void unlock_ref(struct ref_lock *lock) -- cgit v0.10.2-6-g49f6 From 43057304c0bbaf7bc7511daaf81df08c7909a90b Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Thu, 21 Sep 2006 00:05:37 -0400 Subject: many cleanups to sha1_file.c Those cleanups are mainly to set the table for the support of deltas with base objects referenced by offsets instead of sha1. This means that many pack lookup functions are converted to take a pack/offset tuple instead of a sha1. This eliminates many struct pack_entry usages since this structure carried redundent information in many cases, and it increased stack footprint needlessly for a couple recursively called functions that used to declare a local copy of it for every recursion loop. In the process, packed_object_info_detail() has been reorganized as well so to look much saner and more amenable to deltas with offset support. Finally the appropriate adjustments have been made to functions that depend on the above changes. But there is no functionality changes yet simply some code refactoring at this point. Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 8d7a120..96c069a 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -597,15 +597,15 @@ static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclud if (!exclude) { for (p = packed_git; p; p = p->next) { - struct pack_entry e; - if (find_pack_entry_one(sha1, &e, p)) { + unsigned long offset = find_pack_entry_one(sha1, p); + if (offset) { if (incremental) return 0; if (local && !p->pack_local) return 0; if (!found_pack) { - found_offset = e.offset; - found_pack = e.p; + found_offset = offset; + found_pack = p; } } } diff --git a/cache.h b/cache.h index ef2e581..97debd0 100644 --- a/cache.h +++ b/cache.h @@ -390,10 +390,10 @@ extern void unuse_packed_git(struct packed_git *); extern struct packed_git *add_packed_git(char *, int, int); extern int num_packed_objects(const struct packed_git *p); extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*); -extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *); -extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *); +extern unsigned long find_pack_entry_one(const unsigned char *, struct packed_git *); +extern void *unpack_entry_gently(struct packed_git *, unsigned long, char *, unsigned long *); extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); -extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *); +extern void packed_object_info_detail(struct packed_git *, unsigned long, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *); /* Dumb servers support */ extern int update_server_info(int); diff --git a/pack-check.c b/pack-check.c index 04c6c00..c0caaee 100644 --- a/pack-check.c +++ b/pack-check.c @@ -42,16 +42,16 @@ static int verify_packfile(struct packed_git *p) */ for (i = err = 0; i < nr_objects; i++) { unsigned char sha1[20]; - struct pack_entry e; void *data; char type[20]; - unsigned long size; + unsigned long size, offset; if (nth_packed_object_sha1(p, i, sha1)) die("internal error pack-check nth-packed-object"); - if (!find_pack_entry_one(sha1, &e, p)) + offset = find_pack_entry_one(sha1, p); + if (!offset) die("internal error pack-check find-pack-entry-one"); - data = unpack_entry_gently(&e, type, &size); + data = unpack_entry_gently(p, offset, type, &size); if (!data) { err = error("cannot unpack %s from %s", sha1_to_hex(sha1), p->pack_name); @@ -84,25 +84,26 @@ static void show_pack_info(struct packed_git *p) for (i = 0; i < nr_objects; i++) { unsigned char sha1[20], base_sha1[20]; - struct pack_entry e; char type[20]; unsigned long size; unsigned long store_size; + unsigned long offset; unsigned int delta_chain_length; if (nth_packed_object_sha1(p, i, sha1)) die("internal error pack-check nth-packed-object"); - if (!find_pack_entry_one(sha1, &e, p)) + offset = find_pack_entry_one(sha1, p); + if (!offset) die("internal error pack-check find-pack-entry-one"); - packed_object_info_detail(&e, type, &size, &store_size, + packed_object_info_detail(p, offset, type, &size, &store_size, &delta_chain_length, base_sha1); printf("%s ", sha1_to_hex(sha1)); if (!delta_chain_length) - printf("%-6s %lu %u\n", type, size, e.offset); + printf("%-6s %lu %lu\n", type, size, offset); else { - printf("%-6s %lu %u %u %s\n", type, size, e.offset, + printf("%-6s %lu %lu %u %s\n", type, size, offset, delta_chain_length, sha1_to_hex(base_sha1)); if (delta_chain_length < MAX_CHAIN) chain_histogram[delta_chain_length]++; diff --git a/sha1_file.c b/sha1_file.c index 0f9c2b6..27b1ebb 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -884,33 +884,32 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l } /* forward declaration for a mutually recursive function */ -static int packed_object_info(struct pack_entry *entry, +static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep); -static int packed_delta_info(unsigned char *base_sha1, - unsigned long delta_size, - unsigned long left, +static int packed_delta_info(struct packed_git *p, + unsigned long offset, char *type, - unsigned long *sizep, - struct packed_git *p) + unsigned long *sizep) { - struct pack_entry base_ent; + unsigned long base_offset; + unsigned char *base_sha1 = (unsigned char *) p->pack_base + offset; - if (left < 20) + if (p->pack_size < offset + 20) die("truncated pack file"); - /* The base entry _must_ be in the same pack */ - if (!find_pack_entry_one(base_sha1, &base_ent, p)) + base_offset = find_pack_entry_one(base_sha1, p); + if (!base_offset) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); + offset += 20; /* We choose to only get the type of the base object and * ignore potentially corrupt pack file that expects the delta * based on a base with a wrong size. This saves tons of * inflate() calls. */ - - if (packed_object_info(&base_ent, type, NULL)) + if (packed_object_info(p, base_offset, type, NULL)) die("cannot get info for delta-pack base"); if (sizep) { @@ -922,8 +921,8 @@ static int packed_delta_info(unsigned char *base_sha1, memset(&stream, 0, sizeof(stream)); - data = stream.next_in = base_sha1 + 20; - stream.avail_in = left - 20; + stream.next_in = (unsigned char *) p->pack_base + offset; + stream.avail_in = p->pack_size - offset; stream.next_out = delta_head; stream.avail_out = sizeof(delta_head); @@ -985,75 +984,60 @@ int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, return status; } -void packed_object_info_detail(struct pack_entry *e, +void packed_object_info_detail(struct packed_git *p, + unsigned long offset, char *type, unsigned long *size, unsigned long *store_size, unsigned int *delta_chain_length, unsigned char *base_sha1) { - struct packed_git *p = e->p; - unsigned long offset; - unsigned char *pack; + unsigned long val; + unsigned char *next_sha1; enum object_type kind; - offset = unpack_object_header(p, e->offset, &kind, size); - pack = (unsigned char *) p->pack_base + offset; - if (kind != OBJ_DELTA) - *delta_chain_length = 0; - else { - unsigned int chain_length = 0; - if (p->pack_size <= offset + 20) - die("pack file %s records an incomplete delta base", - p->pack_name); - hashcpy(base_sha1, pack); - do { - struct pack_entry base_ent; - unsigned long junk; - - find_pack_entry_one(pack, &base_ent, p); - offset = unpack_object_header(p, base_ent.offset, - &kind, &junk); - pack = (unsigned char *) p->pack_base + offset; - chain_length++; - } while (kind == OBJ_DELTA); - *delta_chain_length = chain_length; - } - switch (kind) { - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - strcpy(type, type_names[kind]); - break; - default: - die("corrupted pack file %s containing object of kind %d", - p->pack_name, kind); + *delta_chain_length = 0; + offset = unpack_object_header(p, offset, &kind, size); + + for (;;) { + switch (kind) { + default: + die("corrupted pack file %s containing object of kind %d", + p->pack_name, kind); + case OBJ_COMMIT: + case OBJ_TREE: + case OBJ_BLOB: + case OBJ_TAG: + strcpy(type, type_names[kind]); + *store_size = 0; /* notyet */ + return; + case OBJ_DELTA: + if (p->pack_size <= offset + 20) + die("pack file %s records an incomplete delta base", + p->pack_name); + next_sha1 = (unsigned char *) p->pack_base + offset; + if (*delta_chain_length == 0) + hashcpy(base_sha1, next_sha1); + offset = find_pack_entry_one(next_sha1, p); + break; + } + offset = unpack_object_header(p, offset, &kind, &val); + (*delta_chain_length)++; } - *store_size = 0; /* notyet */ } -static int packed_object_info(struct pack_entry *entry, +static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - struct packed_git *p = entry->p; - unsigned long offset, size, left; - unsigned char *pack; + unsigned long size; enum object_type kind; - int retval; - if (use_packed_git(p)) - die("cannot map packed file"); + offset = unpack_object_header(p, offset, &kind, &size); - offset = unpack_object_header(p, entry->offset, &kind, &size); - pack = (unsigned char *) p->pack_base + offset; - left = p->pack_size - offset; + if (kind == OBJ_DELTA) + return packed_delta_info(p, offset, type, sizep); switch (kind) { - case OBJ_DELTA: - retval = packed_delta_info(pack, size, left, type, sizep, p); - unuse_packed_git(p); - return retval; case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: @@ -1066,7 +1050,6 @@ static int packed_object_info(struct pack_entry *entry, } if (sizep) *sizep = size; - unuse_packed_git(p); return 0; } @@ -1103,25 +1086,26 @@ static void *unpack_delta_entry(struct packed_git *p, char *type, unsigned long *sizep) { - struct pack_entry base_ent; void *delta_data, *result, *base; - unsigned long result_size, base_size; - unsigned char* base_sha1; + unsigned long result_size, base_size, base_offset; + unsigned char *base_sha1; - if ((offset + 20) >= p->pack_size) + if (p->pack_size < offset + 20) die("truncated pack file"); - /* The base entry _must_ be in the same pack */ base_sha1 = (unsigned char*)p->pack_base + offset; - if (!find_pack_entry_one(base_sha1, &base_ent, p)) + base_offset = find_pack_entry_one(base_sha1, p); + if (!base_offset) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); - base = unpack_entry_gently(&base_ent, type, &base_size); + offset += 20; + + base = unpack_entry_gently(p, base_offset, type, &base_size); if (!base) - die("failed to read delta-pack base object %s", - sha1_to_hex(base_sha1)); + die("failed to read delta base object at %lu from %s", + base_offset, p->pack_name); - delta_data = unpack_compressed_entry(p, offset + 20, delta_size); + delta_data = unpack_compressed_entry(p, offset, delta_size); result = patch_delta(base, base_size, delta_data, delta_size, &result_size); @@ -1141,7 +1125,7 @@ static void *unpack_entry(struct pack_entry *entry, if (use_packed_git(p)) die("cannot map packed file"); - retval = unpack_entry_gently(entry, type, sizep); + retval = unpack_entry_gently(p, entry->offset, type, sizep); unuse_packed_git(p); if (!retval) die("corrupted pack file %s", p->pack_name); @@ -1149,14 +1133,13 @@ static void *unpack_entry(struct pack_entry *entry, } /* The caller is responsible for use_packed_git()/unuse_packed_git() pair */ -void *unpack_entry_gently(struct pack_entry *entry, +void *unpack_entry_gently(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - struct packed_git *p = entry->p; - unsigned long offset, size; + unsigned long size; enum object_type kind; - offset = unpack_object_header(p, entry->offset, &kind, &size); + offset = unpack_object_header(p, offset, &kind, &size); switch (kind) { case OBJ_DELTA: return unpack_delta_entry(p, offset, size, type, sizep); @@ -1188,8 +1171,8 @@ int nth_packed_object_sha1(const struct packed_git *p, int n, return 0; } -int find_pack_entry_one(const unsigned char *sha1, - struct pack_entry *e, struct packed_git *p) +unsigned long find_pack_entry_one(const unsigned char *sha1, + struct packed_git *p) { unsigned int *level1_ofs = p->index_base; int hi = ntohl(level1_ofs[*sha1]); @@ -1199,12 +1182,8 @@ int find_pack_entry_one(const unsigned char *sha1, do { int mi = (lo + hi) / 2; int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1); - if (!cmp) { - e->offset = ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); - hashcpy(e->sha1, sha1); - e->p = p; - return 1; - } + if (!cmp) + return ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); if (cmp > 0) hi = mi; else @@ -1216,6 +1195,8 @@ int find_pack_entry_one(const unsigned char *sha1, static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) { struct packed_git *p; + unsigned long offset; + prepare_packed_git(); for (p = packed_git; p; p = p->next) { @@ -1227,8 +1208,13 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons if (*ig) continue; } - if (find_pack_entry_one(sha1, e, p)) + offset = find_pack_entry_one(sha1, p); + if (offset) { + e->offset = offset; + e->p = p; + hashcpy(e->sha1, sha1); return 1; + } } return 0; } @@ -1237,10 +1223,9 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1, struct packed_git *packs) { struct packed_git *p; - struct pack_entry e; for (p = packs; p; p = p->next) { - if (find_pack_entry_one(sha1, &e, p)) + if (find_pack_entry_one(sha1, p)) return p; } return NULL; @@ -1259,12 +1244,16 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep if (!map) { struct pack_entry e; - if (find_pack_entry(sha1, &e, NULL)) - return packed_object_info(&e, type, sizep); - reprepare_packed_git(); - if (find_pack_entry(sha1, &e, NULL)) - return packed_object_info(&e, type, sizep); - return error("unable to find %s", sha1_to_hex(sha1)); + if (!find_pack_entry(sha1, &e, NULL)) { + reprepare_packed_git(); + if (!find_pack_entry(sha1, &e, NULL)) + return error("unable to find %s", sha1_to_hex(sha1)); + } + if (use_packed_git(e.p)) + die("cannot map packed file"); + status = packed_object_info(e.p, e.offset, type, sizep); + unuse_packed_git(e.p); + return status; } if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) status = error("unable to unpack %s header", -- cgit v0.10.2-6-g49f6 From 31756c5da8198eb09f2792230e1bed4d107f5135 Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Sat, 23 Sep 2006 17:06:11 +0200 Subject: Remove git-zip-tree git-zip-tree can be safely removed because it was never part of a formal release. This patch makes 'git-archive --format=zip' the one and only git ZIP file creation command. Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index a3d9c7a..3ca66e4 100644 --- a/.gitignore +++ b/.gitignore @@ -128,7 +128,6 @@ git-verify-pack git-verify-tag git-whatchanged git-write-tree -git-zip-tree git-core-*/?* gitweb/gitweb.cgi test-date diff --git a/Documentation/git-zip-tree.txt b/Documentation/git-zip-tree.txt deleted file mode 100644 index 2e9d981..0000000 --- a/Documentation/git-zip-tree.txt +++ /dev/null @@ -1,67 +0,0 @@ -git-zip-tree(1) -=============== - -NAME ----- -git-zip-tree - Creates a ZIP archive of the files in the named tree - - -SYNOPSIS --------- -'git-zip-tree' [-0|...|-9] <tree-ish> [ <base> ] - -DESCRIPTION ------------ -Creates a ZIP archive containing the tree structure for the named tree. -When <base> is specified it is added as a leading path to the files in the -generated ZIP archive. - -git-zip-tree behaves differently when given a tree ID versus when given -a commit ID or tag ID. In the first case the current time is used as -modification time of each file in the archive. In the latter case the -commit time as recorded in the referenced commit object is used instead. -Additionally the commit ID is stored as an archive comment. - -Currently git-zip-tree can handle only files and directories, symbolic -links are not supported. - -OPTIONS -------- - --0:: - Store the files instead of deflating them. - --9:: - Highest and slowest compression level. You can specify any - number from 1 to 9 to adjust compression speed and ratio. - -<tree-ish>:: - The tree or commit to produce ZIP archive for. If it is - the object name of a commit object. - -<base>:: - Leading path to the files in the resulting ZIP archive. - -EXAMPLES --------- -git zip-tree v1.4.0 git-1.4.0 >git-1.4.0.zip:: - - Create a ZIP file for v1.4.0 release. - -git zip-tree HEAD:Documentation/ git-docs >docs.zip:: - - Put everything in the current head's Documentation/ directory - into 'docs.zip', with the prefix 'git-docs/'. - -Author ------- -Written by Rene Scharfe. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - -GIT ---- -Part of the gitlink:git[7] suite - diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c index 52d4b7a..3ffdad6 100644 --- a/builtin-zip-tree.c +++ b/builtin-zip-tree.c @@ -10,9 +10,6 @@ #include "builtin.h" #include "archive.h" -static const char zip_tree_usage[] = -"git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]"; - static int verbose; static int zip_date; static int zip_time; @@ -294,68 +291,6 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time) *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; } -int cmd_zip_tree(int argc, const char **argv, const char *prefix) -{ - unsigned char sha1[20]; - struct tree *tree; - struct commit *commit; - time_t archive_time; - char *base; - int baselen; - - git_config(git_default_config); - - if (argc > 1 && argv[1][0] == '-') { - if (isdigit(argv[1][1]) && argv[1][2] == '\0') { - zlib_compression_level = argv[1][1] - '0'; - argc--; - argv++; - } - } - - switch (argc) { - case 3: - base = xstrdup(argv[2]); - baselen = strlen(base); - break; - case 2: - base = xstrdup(""); - baselen = 0; - break; - default: - usage(zip_tree_usage); - } - - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - - commit = lookup_commit_reference_gently(sha1, 1); - archive_time = commit ? commit->date : time(NULL); - dos_time(&archive_time, &zip_date, &zip_time); - - zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); - zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; - - tree = parse_tree_indirect(sha1); - if (!tree) - die("not a tree object"); - - if (baselen > 0) { - write_zip_entry(tree->object.sha1, "", 0, base, 040777, 0); - base = xrealloc(base, baselen + 1); - base[baselen] = '/'; - baselen++; - base[baselen] = '\0'; - } - read_tree_recursive(tree, base, baselen, 0, NULL, write_zip_entry); - write_zip_trailer(commit ? commit->object.sha1 : NULL); - - free(zip_dir); - free(base); - - return 0; -} - int write_zip_archive(struct archiver_args *args) { int plen = strlen(args->base); diff --git a/builtin.h b/builtin.h index ccade94..f9fa9ff 100644 --- a/builtin.h +++ b/builtin.h @@ -53,7 +53,6 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); -extern int cmd_zip_tree(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 44ab0de..1686220 100644 --- a/git.c +++ b/git.c @@ -259,7 +259,6 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tar-tree", cmd_tar_tree, RUN_SETUP }, - { "zip-tree", cmd_zip_tree, RUN_SETUP }, { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From 3fc8284e2114624f2657142b3fecdc6f514b2090 Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Sat, 23 Sep 2006 17:06:35 +0200 Subject: Rename builtin-zip-tree.c to archive-zip.c Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 8467447..739d7e3 100644 --- a/Makefile +++ b/Makefile @@ -254,7 +254,7 @@ LIB_OBJS = \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ - color.o wt-status.o + color.o wt-status.o archive-zip.o BUILTIN_OBJS = \ builtin-add.o \ @@ -300,8 +300,7 @@ BUILTIN_OBJS = \ builtin-upload-archive.o \ builtin-upload-tar.o \ builtin-verify-pack.o \ - builtin-write-tree.o \ - builtin-zip-tree.o + builtin-write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/archive-zip.c b/archive-zip.c new file mode 100644 index 0000000..3ffdad6 --- /dev/null +++ b/archive-zip.c @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2006 Rene Scharfe + */ +#include <time.h> +#include "cache.h" +#include "commit.h" +#include "blob.h" +#include "tree.h" +#include "quote.h" +#include "builtin.h" +#include "archive.h" + +static int verbose; +static int zip_date; +static int zip_time; + +static unsigned char *zip_dir; +static unsigned int zip_dir_size; + +static unsigned int zip_offset; +static unsigned int zip_dir_offset; +static unsigned int zip_dir_entries; + +#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) + +struct zip_local_header { + unsigned char magic[4]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; +}; + +struct zip_dir_header { + unsigned char magic[4]; + unsigned char creator_version[2]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; + unsigned char comment_length[2]; + unsigned char disk[2]; + unsigned char attr1[2]; + unsigned char attr2[4]; + unsigned char offset[4]; +}; + +struct zip_dir_trailer { + unsigned char magic[4]; + unsigned char disk[2]; + unsigned char directory_start_disk[2]; + unsigned char entries_on_this_disk[2]; + unsigned char entries[2]; + unsigned char size[4]; + unsigned char offset[4]; + unsigned char comment_length[2]; +}; + +static void copy_le16(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); +} + +static void copy_le32(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); + dest[2] = 0xff & (n >> 020); + dest[3] = 0xff & (n >> 030); +} + +static void *zlib_deflate(void *data, unsigned long size, + unsigned long *compressed_size) +{ + z_stream stream; + unsigned long maxsize; + void *buffer; + int result; + + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, zlib_compression_level); + maxsize = deflateBound(&stream, size); + buffer = xmalloc(maxsize); + + stream.next_in = data; + stream.avail_in = size; + stream.next_out = buffer; + stream.avail_out = maxsize; + + do { + result = deflate(&stream, Z_FINISH); + } while (result == Z_OK); + + if (result != Z_STREAM_END) { + free(buffer); + return NULL; + } + + deflateEnd(&stream); + *compressed_size = stream.total_out; + + return buffer; +} + +static char *construct_path(const char *base, int baselen, + const char *filename, int isdir, int *pathlen) +{ + int filenamelen = strlen(filename); + int len = baselen + filenamelen; + char *path, *p; + + if (isdir) + len++; + p = path = xmalloc(len + 1); + + memcpy(p, base, baselen); + p += baselen; + memcpy(p, filename, filenamelen); + p += filenamelen; + if (isdir) + *p++ = '/'; + *p = '\0'; + + *pathlen = len; + + return path; +} + +static int write_zip_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + struct zip_local_header header; + struct zip_dir_header dirent; + unsigned long compressed_size; + unsigned long uncompressed_size; + unsigned long crc; + unsigned long direntsize; + unsigned long size; + int method; + int result = -1; + int pathlen; + unsigned char *out; + char *path; + char type[20]; + void *buffer = NULL; + void *deflated = NULL; + + crc = crc32(0, Z_NULL, 0); + + path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); + if (verbose) + fprintf(stderr, "%s\n", path); + if (pathlen > 0xffff) { + error("path too long (%d chars, SHA1: %s): %s", pathlen, + sha1_to_hex(sha1), path); + goto out; + } + + if (S_ISDIR(mode)) { + method = 0; + result = READ_TREE_RECURSIVE; + out = NULL; + uncompressed_size = 0; + compressed_size = 0; + } else if (S_ISREG(mode)) { + method = zlib_compression_level == 0 ? 0 : 8; + result = 0; + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + crc = crc32(crc, buffer, size); + out = buffer; + uncompressed_size = size; + compressed_size = size; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", mode, + sha1_to_hex(sha1)); + goto out; + } + + if (method == 8) { + deflated = zlib_deflate(buffer, size, &compressed_size); + if (deflated && compressed_size - 6 < size) { + /* ZLIB --> raw compressed data (see RFC 1950) */ + /* CMF and FLG ... */ + out = (unsigned char *)deflated + 2; + compressed_size -= 6; /* ... and ADLER32 */ + } else { + method = 0; + compressed_size = size; + } + } + + /* make sure we have enough free space in the dictionary */ + direntsize = sizeof(struct zip_dir_header) + pathlen; + while (zip_dir_size < zip_dir_offset + direntsize) { + zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; + zip_dir = xrealloc(zip_dir, zip_dir_size); + } + + copy_le32(dirent.magic, 0x02014b50); + copy_le16(dirent.creator_version, 0); + copy_le16(dirent.version, 20); + copy_le16(dirent.flags, 0); + copy_le16(dirent.compression_method, method); + copy_le16(dirent.mtime, zip_time); + copy_le16(dirent.mdate, zip_date); + copy_le32(dirent.crc32, crc); + copy_le32(dirent.compressed_size, compressed_size); + copy_le32(dirent.size, uncompressed_size); + copy_le16(dirent.filename_length, pathlen); + copy_le16(dirent.extra_length, 0); + copy_le16(dirent.comment_length, 0); + copy_le16(dirent.disk, 0); + copy_le16(dirent.attr1, 0); + copy_le32(dirent.attr2, 0); + copy_le32(dirent.offset, zip_offset); + memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); + zip_dir_offset += sizeof(struct zip_dir_header); + memcpy(zip_dir + zip_dir_offset, path, pathlen); + zip_dir_offset += pathlen; + zip_dir_entries++; + + copy_le32(header.magic, 0x04034b50); + copy_le16(header.version, 20); + copy_le16(header.flags, 0); + copy_le16(header.compression_method, method); + copy_le16(header.mtime, zip_time); + copy_le16(header.mdate, zip_date); + copy_le32(header.crc32, crc); + copy_le32(header.compressed_size, compressed_size); + copy_le32(header.size, uncompressed_size); + copy_le16(header.filename_length, pathlen); + copy_le16(header.extra_length, 0); + write_or_die(1, &header, sizeof(struct zip_local_header)); + zip_offset += sizeof(struct zip_local_header); + write_or_die(1, path, pathlen); + zip_offset += pathlen; + if (compressed_size > 0) { + write_or_die(1, out, compressed_size); + zip_offset += compressed_size; + } + +out: + free(buffer); + free(deflated); + free(path); + + return result; +} + +static void write_zip_trailer(const unsigned char *sha1) +{ + struct zip_dir_trailer trailer; + + copy_le32(trailer.magic, 0x06054b50); + copy_le16(trailer.disk, 0); + copy_le16(trailer.directory_start_disk, 0); + copy_le16(trailer.entries_on_this_disk, zip_dir_entries); + copy_le16(trailer.entries, zip_dir_entries); + copy_le32(trailer.size, zip_dir_offset); + copy_le32(trailer.offset, zip_offset); + copy_le16(trailer.comment_length, sha1 ? 40 : 0); + + write_or_die(1, zip_dir, zip_dir_offset); + write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); + if (sha1) + write_or_die(1, sha1_to_hex(sha1), 40); +} + +static void dos_time(time_t *time, int *dos_date, int *dos_time) +{ + struct tm *t = localtime(time); + + *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + + (t->tm_year + 1900 - 1980) * 512; + *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; +} + +int write_zip_archive(struct archiver_args *args) +{ + int plen = strlen(args->base); + + dos_time(&args->time, &zip_date, &zip_time); + + zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); + zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; + verbose = args->verbose; + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_zip_entry); + write_zip_trailer(args->commit_sha1); + + free(zip_dir); + + return 0; +} + +void *parse_extra_zip_args(int argc, const char **argv) +{ + for (; argc > 0; argc--, argv++) { + const char *arg = argv[0]; + + if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') + zlib_compression_level = arg[1] - '0'; + else + die("Unknown argument for zip format: %s", arg); + } + return NULL; +} diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c deleted file mode 100644 index 3ffdad6..0000000 --- a/builtin-zip-tree.c +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (c) 2006 Rene Scharfe - */ -#include <time.h> -#include "cache.h" -#include "commit.h" -#include "blob.h" -#include "tree.h" -#include "quote.h" -#include "builtin.h" -#include "archive.h" - -static int verbose; -static int zip_date; -static int zip_time; - -static unsigned char *zip_dir; -static unsigned int zip_dir_size; - -static unsigned int zip_offset; -static unsigned int zip_dir_offset; -static unsigned int zip_dir_entries; - -#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) - -struct zip_local_header { - unsigned char magic[4]; - unsigned char version[2]; - unsigned char flags[2]; - unsigned char compression_method[2]; - unsigned char mtime[2]; - unsigned char mdate[2]; - unsigned char crc32[4]; - unsigned char compressed_size[4]; - unsigned char size[4]; - unsigned char filename_length[2]; - unsigned char extra_length[2]; -}; - -struct zip_dir_header { - unsigned char magic[4]; - unsigned char creator_version[2]; - unsigned char version[2]; - unsigned char flags[2]; - unsigned char compression_method[2]; - unsigned char mtime[2]; - unsigned char mdate[2]; - unsigned char crc32[4]; - unsigned char compressed_size[4]; - unsigned char size[4]; - unsigned char filename_length[2]; - unsigned char extra_length[2]; - unsigned char comment_length[2]; - unsigned char disk[2]; - unsigned char attr1[2]; - unsigned char attr2[4]; - unsigned char offset[4]; -}; - -struct zip_dir_trailer { - unsigned char magic[4]; - unsigned char disk[2]; - unsigned char directory_start_disk[2]; - unsigned char entries_on_this_disk[2]; - unsigned char entries[2]; - unsigned char size[4]; - unsigned char offset[4]; - unsigned char comment_length[2]; -}; - -static void copy_le16(unsigned char *dest, unsigned int n) -{ - dest[0] = 0xff & n; - dest[1] = 0xff & (n >> 010); -} - -static void copy_le32(unsigned char *dest, unsigned int n) -{ - dest[0] = 0xff & n; - dest[1] = 0xff & (n >> 010); - dest[2] = 0xff & (n >> 020); - dest[3] = 0xff & (n >> 030); -} - -static void *zlib_deflate(void *data, unsigned long size, - unsigned long *compressed_size) -{ - z_stream stream; - unsigned long maxsize; - void *buffer; - int result; - - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - maxsize = deflateBound(&stream, size); - buffer = xmalloc(maxsize); - - stream.next_in = data; - stream.avail_in = size; - stream.next_out = buffer; - stream.avail_out = maxsize; - - do { - result = deflate(&stream, Z_FINISH); - } while (result == Z_OK); - - if (result != Z_STREAM_END) { - free(buffer); - return NULL; - } - - deflateEnd(&stream); - *compressed_size = stream.total_out; - - return buffer; -} - -static char *construct_path(const char *base, int baselen, - const char *filename, int isdir, int *pathlen) -{ - int filenamelen = strlen(filename); - int len = baselen + filenamelen; - char *path, *p; - - if (isdir) - len++; - p = path = xmalloc(len + 1); - - memcpy(p, base, baselen); - p += baselen; - memcpy(p, filename, filenamelen); - p += filenamelen; - if (isdir) - *p++ = '/'; - *p = '\0'; - - *pathlen = len; - - return path; -} - -static int write_zip_entry(const unsigned char *sha1, - const char *base, int baselen, - const char *filename, unsigned mode, int stage) -{ - struct zip_local_header header; - struct zip_dir_header dirent; - unsigned long compressed_size; - unsigned long uncompressed_size; - unsigned long crc; - unsigned long direntsize; - unsigned long size; - int method; - int result = -1; - int pathlen; - unsigned char *out; - char *path; - char type[20]; - void *buffer = NULL; - void *deflated = NULL; - - crc = crc32(0, Z_NULL, 0); - - path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); - if (verbose) - fprintf(stderr, "%s\n", path); - if (pathlen > 0xffff) { - error("path too long (%d chars, SHA1: %s): %s", pathlen, - sha1_to_hex(sha1), path); - goto out; - } - - if (S_ISDIR(mode)) { - method = 0; - result = READ_TREE_RECURSIVE; - out = NULL; - uncompressed_size = 0; - compressed_size = 0; - } else if (S_ISREG(mode)) { - method = zlib_compression_level == 0 ? 0 : 8; - result = 0; - buffer = read_sha1_file(sha1, type, &size); - if (!buffer) - die("cannot read %s", sha1_to_hex(sha1)); - crc = crc32(crc, buffer, size); - out = buffer; - uncompressed_size = size; - compressed_size = size; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", mode, - sha1_to_hex(sha1)); - goto out; - } - - if (method == 8) { - deflated = zlib_deflate(buffer, size, &compressed_size); - if (deflated && compressed_size - 6 < size) { - /* ZLIB --> raw compressed data (see RFC 1950) */ - /* CMF and FLG ... */ - out = (unsigned char *)deflated + 2; - compressed_size -= 6; /* ... and ADLER32 */ - } else { - method = 0; - compressed_size = size; - } - } - - /* make sure we have enough free space in the dictionary */ - direntsize = sizeof(struct zip_dir_header) + pathlen; - while (zip_dir_size < zip_dir_offset + direntsize) { - zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; - zip_dir = xrealloc(zip_dir, zip_dir_size); - } - - copy_le32(dirent.magic, 0x02014b50); - copy_le16(dirent.creator_version, 0); - copy_le16(dirent.version, 20); - copy_le16(dirent.flags, 0); - copy_le16(dirent.compression_method, method); - copy_le16(dirent.mtime, zip_time); - copy_le16(dirent.mdate, zip_date); - copy_le32(dirent.crc32, crc); - copy_le32(dirent.compressed_size, compressed_size); - copy_le32(dirent.size, uncompressed_size); - copy_le16(dirent.filename_length, pathlen); - copy_le16(dirent.extra_length, 0); - copy_le16(dirent.comment_length, 0); - copy_le16(dirent.disk, 0); - copy_le16(dirent.attr1, 0); - copy_le32(dirent.attr2, 0); - copy_le32(dirent.offset, zip_offset); - memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); - zip_dir_offset += sizeof(struct zip_dir_header); - memcpy(zip_dir + zip_dir_offset, path, pathlen); - zip_dir_offset += pathlen; - zip_dir_entries++; - - copy_le32(header.magic, 0x04034b50); - copy_le16(header.version, 20); - copy_le16(header.flags, 0); - copy_le16(header.compression_method, method); - copy_le16(header.mtime, zip_time); - copy_le16(header.mdate, zip_date); - copy_le32(header.crc32, crc); - copy_le32(header.compressed_size, compressed_size); - copy_le32(header.size, uncompressed_size); - copy_le16(header.filename_length, pathlen); - copy_le16(header.extra_length, 0); - write_or_die(1, &header, sizeof(struct zip_local_header)); - zip_offset += sizeof(struct zip_local_header); - write_or_die(1, path, pathlen); - zip_offset += pathlen; - if (compressed_size > 0) { - write_or_die(1, out, compressed_size); - zip_offset += compressed_size; - } - -out: - free(buffer); - free(deflated); - free(path); - - return result; -} - -static void write_zip_trailer(const unsigned char *sha1) -{ - struct zip_dir_trailer trailer; - - copy_le32(trailer.magic, 0x06054b50); - copy_le16(trailer.disk, 0); - copy_le16(trailer.directory_start_disk, 0); - copy_le16(trailer.entries_on_this_disk, zip_dir_entries); - copy_le16(trailer.entries, zip_dir_entries); - copy_le32(trailer.size, zip_dir_offset); - copy_le32(trailer.offset, zip_offset); - copy_le16(trailer.comment_length, sha1 ? 40 : 0); - - write_or_die(1, zip_dir, zip_dir_offset); - write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); - if (sha1) - write_or_die(1, sha1_to_hex(sha1), 40); -} - -static void dos_time(time_t *time, int *dos_date, int *dos_time) -{ - struct tm *t = localtime(time); - - *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + - (t->tm_year + 1900 - 1980) * 512; - *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; -} - -int write_zip_archive(struct archiver_args *args) -{ - int plen = strlen(args->base); - - dos_time(&args->time, &zip_date, &zip_time); - - zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); - zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; - verbose = args->verbose; - - if (args->base && plen > 0 && args->base[plen - 1] == '/') { - char *base = xstrdup(args->base); - int baselen = strlen(base); - - while (baselen > 0 && base[baselen - 1] == '/') - base[--baselen] = '\0'; - write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); - free(base); - } - read_tree_recursive(args->tree, args->base, plen, 0, - args->pathspec, write_zip_entry); - write_zip_trailer(args->commit_sha1); - - free(zip_dir); - - return 0; -} - -void *parse_extra_zip_args(int argc, const char **argv) -{ - for (; argc > 0; argc--, argv++) { - const char *arg = argv[0]; - - if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') - zlib_compression_level = arg[1] - '0'; - else - die("Unknown argument for zip format: %s", arg); - } - return NULL; -} -- cgit v0.10.2-6-g49f6 From 18b0fc1ce1ef92716d4c5d5c7acd5d5a61a0a556 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Sat, 23 Sep 2006 20:20:47 +0200 Subject: Git.pm: Kill Git.xs for now This patch removes Git.xs from the repository for the time being. This should hopefully enable Git.pm to finally make its way to master. Git.xs is not going away forever. When the Git libification makes some progress, it will hopefully return (but most likely as an optional component, due to the portability woes) since the performance boosts are really important for applications like Gitweb or Cogito. It needs to go away now since it is not really reliable in case you use it for several repositories in the scope of a single process, and that is not possible to fix without some either very ugly or very intrusive core changes. Rest in peace. (While you can.) Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 4e7a37a..8a7f29b 100644 --- a/Makefile +++ b/Makefile @@ -116,8 +116,6 @@ PIC_FLAG = -fPIC LDFLAGS = ALL_CFLAGS = $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) -PERL_CFLAGS = -PERL_LDFLAGS = STRIP ?= strip prefix = $(HOME) @@ -154,9 +152,10 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ ### --- END CONFIGURATION SECTION --- # Those must not be GNU-specific; they are shared with perl/ which may -# be built by a different compiler. -BASIC_CFLAGS = $(PERL_CFLAGS) -BASIC_LDFLAGS = $(PERL_LDFLAGS) +# be built by a different compiler. (Note that this is an artifact now +# but it still might be nice to keep that distinction.) +BASIC_CFLAGS = +BASIC_LDFLAGS = SCRIPT_SH = \ git-bisect.sh git-branch.sh git-checkout.sh \ @@ -753,15 +752,9 @@ $(XDIFF_LIB): $(XDIFF_OBJS) rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) -PERL_DEFINE = $(BASIC_CFLAGS) -DGIT_VERSION='"$(GIT_VERSION)"' -PERL_DEFINE_SQ = $(subst ','\'',$(PERL_DEFINE)) -PERL_LIBS = $(BASIC_LDFLAGS) $(EXTLIBS) -PERL_LIBS_SQ = $(subst ','\'',$(PERL_LIBS)) perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS (cd perl && $(PERL_PATH) Makefile.PL \ - PREFIX='$(prefix_SQ)' \ - DEFINE='$(PERL_DEFINE_SQ)' \ - LIBS='$(PERL_LIBS_SQ)') + PREFIX='$(prefix_SQ)') doc: $(MAKE) -C Documentation all diff --git a/perl/.gitignore b/perl/.gitignore index 6d778f3..e990cae 100644 --- a/perl/.gitignore +++ b/perl/.gitignore @@ -1,7 +1,4 @@ -Git.bs -Git.c Makefile blib blibdirs pm_to_blib -ppport.h diff --git a/perl/Git.pm b/perl/Git.pm index 9ce9fcd..2b26b65b 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -93,9 +93,6 @@ use Carp qw(carp croak); # but croak is bad - throw instead use Error qw(:try); use Cwd qw(abs_path); -require XSLoader; -XSLoader::load('Git', $VERSION); - } @@ -413,12 +410,13 @@ sub command_noisy { Return the Git version in use. -Implementation of this function is very fast; no external command calls -are involved. - =cut -# Implemented in Git.xs. +sub version { + my $verstr = command_oneline('--version'); + $verstr =~ s/^git version //; + $verstr; +} =item exec_path () @@ -426,12 +424,9 @@ are involved. Return path to the Git sub-command executables (the same as C<git --exec-path>). Useful mostly only internally. -Implementation of this function is very fast; no external command calls -are involved. - =cut -# Implemented in Git.xs. +sub exec_path { command_oneline('--exec-path') } =item repo_path () @@ -572,41 +567,21 @@ sub ident_person { =item hash_object ( TYPE, FILENAME ) -=item hash_object ( TYPE, FILEHANDLE ) - Compute the SHA1 object id of the given C<FILENAME> (or data waiting in C<FILEHANDLE>) considering it is of the C<TYPE> object type (C<blob>, C<commit>, C<tree>). -In case of C<FILEHANDLE> passed instead of file name, all the data -available are read and hashed, and the filehandle is automatically -closed. The file handle should be freshly opened - if you have already -read anything from the file handle, the results are undefined (since -this function works directly with the file descriptor and internal -PerlIO buffering might have messed things up). - The method can be called without any instance or on a specified Git repository, it makes zero difference. The function returns the SHA1 hash. -Implementation of this function is very fast; no external command calls -are involved. - =cut +# TODO: Support for passing FILEHANDLE instead of FILENAME sub hash_object { my ($self, $type, $file) = _maybe_self(@_); - - # hash_object_* implemented in Git.xs. - - if (ref($file) eq 'GLOB') { - my $hash = hash_object_pipe($type, fileno($file)); - close $file; - return $hash; - } else { - hash_object_file($type, $file); - } + command_oneline('hash-object', '-t', $type, $file); } @@ -802,7 +777,7 @@ sub _cmd_exec { # Execute the given Git command ($_[0]) with arguments ($_[1..]) # by searching for it at proper places. -# _execv_git_cmd(), implemented in Git.xs. +sub _execv_git_cmd { exec('git', @_); } # Close pipe to a subprocess. sub _cmd_close { @@ -821,39 +796,6 @@ sub _cmd_close { } -# Trickery for .xs routines: In order to avoid having some horrid -# C code trying to do stuff with undefs and hashes, we gate all -# xs calls through the following and in case we are being ran upon -# an instance call a C part of the gate which will set up the -# environment properly. -sub _call_gate { - my $xsfunc = shift; - my ($self, @args) = _maybe_self(@_); - - if (defined $self) { - # XXX: We ignore the WorkingCopy! To properly support - # that will require heavy changes in libgit. - - # XXX: And we ignore everything else as well. libgit - # at least needs to be extended to let us specify - # the $GIT_DIR instead of looking it up in environment. - #xs_call_gate($self->{opts}->{Repository}); - } - - # Having to call throw from the C code is a sure path to insanity. - local $SIG{__DIE__} = sub { throw Error::Simple("@_"); }; - &$xsfunc(@args); -} - -sub AUTOLOAD { - my $xsname; - our $AUTOLOAD; - ($xsname = $AUTOLOAD) =~ s/.*:://; - throw Error::Simple("&Git::$xsname not defined") if $xsname =~ /^xs_/; - $xsname = 'xs_'.$xsname; - _call_gate(\&$xsname, @_); -} - sub DESTROY { } diff --git a/perl/Git.xs b/perl/Git.xs deleted file mode 100644 index 2bbec43..0000000 --- a/perl/Git.xs +++ /dev/null @@ -1,134 +0,0 @@ -/* By carefully stacking #includes here (even if WE don't really need them) - * we strive to make the thing actually compile. Git header files aren't very - * nice. Perl headers are one of the signs of the coming apocalypse. */ -#include <ctype.h> -/* Ok, it hasn't been so bad so far. */ - -/* libgit interface */ -#include "../cache.h" -#include "../exec_cmd.h" - -/* XS and Perl interface */ -#include "EXTERN.h" -#include "perl.h" -#include "XSUB.h" - - -static char * -report_xs(const char *prefix, const char *err, va_list params) -{ - static char buf[4096]; - strcpy(buf, prefix); - vsnprintf(buf + strlen(prefix), 4096 - strlen(prefix), err, params); - return buf; -} - -static void NORETURN -die_xs(const char *err, va_list params) -{ - char *str; - str = report_xs("fatal: ", err, params); - croak(str); -} - -static void -error_xs(const char *err, va_list params) -{ - char *str; - str = report_xs("error: ", err, params); - warn(str); -} - - -MODULE = Git PACKAGE = Git - -PROTOTYPES: DISABLE - - -BOOT: -{ - set_error_routine(error_xs); - set_die_routine(die_xs); -} - - -# /* TODO: xs_call_gate(). See Git.pm. */ - - -char * -xs_version() -CODE: -{ - RETVAL = GIT_VERSION; -} -OUTPUT: - RETVAL - - -char * -xs_exec_path() -CODE: -{ - RETVAL = (char *)git_exec_path(); -} -OUTPUT: - RETVAL - - -void -xs__execv_git_cmd(...) -CODE: -{ - const char **argv; - int i; - - argv = malloc(sizeof(const char *) * (items + 1)); - if (!argv) - croak("malloc failed"); - for (i = 0; i < items; i++) - argv[i] = strdup(SvPV_nolen(ST(i))); - argv[i] = NULL; - - execv_git_cmd(argv); - - for (i = 0; i < items; i++) - if (argv[i]) - free((char *) argv[i]); - free((char **) argv); -} - -char * -xs_hash_object_pipe(type, fd) - char *type; - int fd; -CODE: -{ - unsigned char sha1[20]; - - if (index_pipe(sha1, fd, type, 0)) - croak("Unable to hash given filehandle"); - RETVAL = sha1_to_hex(sha1); -} -OUTPUT: - RETVAL - -char * -xs_hash_object_file(type, path) - char *type; - char *path; -CODE: -{ - unsigned char sha1[20]; - int fd = open(path, O_RDONLY); - struct stat st; - - if (fd < 0 || - fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, 0, type)) - croak("Unable to hash %s", path); - close(fd); - - RETVAL = sha1_to_hex(sha1); -} -OUTPUT: - RETVAL diff --git a/perl/Makefile.PL b/perl/Makefile.PL index ef9d82d..de73235 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -3,12 +3,7 @@ use ExtUtils::MakeMaker; sub MY::postamble { return <<'MAKE_FRAG'; instlibdir: - @echo '$(INSTALLSITEARCH)' - -check: - perl -MDevel::PPPort -le 'Devel::PPPort::WriteFile(".ppport.h")' && \ - perl .ppport.h --compat-version=5.6.0 Git.xs && \ - rm .ppport.h + @echo '$(INSTALLSITELIB)' MAKE_FRAG } @@ -29,7 +24,5 @@ WriteMakefile( NAME => 'Git', VERSION_FROM => 'Git.pm', PM => \%pm, - MYEXTLIB => '../libgit.a', - INC => '-I. -I..', %extra ); -- cgit v0.10.2-6-g49f6 From 8f41db8c370d535ed0132ef33d73e47edcc5af03 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Sun, 24 Sep 2006 00:25:19 +0200 Subject: Deprecate git-resolve.sh Seriously, is anyone still using this thing? It's collecting dust and blocking the name for something potentially useful like a tool for user-friendly marking of resolved conflicts or resolving index conflicts. We've loved you when Git was young, now thank you and please go away. ;-) This makes git-resolve.sh print a big deprecation warning and sleep a bit for extra annoyance. It should be removed completely after the next release. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-resolve.sh b/git-resolve.sh index a7bc680..729ec65 100755 --- a/git-resolve.sh +++ b/git-resolve.sh @@ -5,6 +5,10 @@ # Resolve two trees. # +echo 'WARNING: This command is DEPRECATED and will be removed very soon.' >&2 +echo 'WARNING: Please use git-merge or git-pull instead.' >&2 +sleep 2 + USAGE='<head> <remote> <merge-message>' . git-sh-setup -- cgit v0.10.2-6-g49f6 From a2f3db2f5de2a3667b0e038aa65e3e097e642e7d Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Sun, 24 Sep 2006 00:18:41 +0200 Subject: gitweb: Consolidate escaping/validation of query string Consider: http://repo.or.cz/?p=glibc-cvs.git;a=tree;h=2609cb0411389325f4ee2854cc7159756eb0671e;hb=2609cb0411389325f4ee2854cc7159756eb0671e (click on the funny =__ify file) We ought to handle anything in filenames and I actually see no reason why we don't, modulo very little missing escaping that this patch hopefully also fixes. I have also made esc_param() escape [?=&;]. Not escaping [&;] was downright buggy and [?=] just feels better escaped. ;-) YMMV. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3d06181..0693a83 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -212,19 +212,9 @@ if (defined $project) { } } +# We have to handle those containing any characters: our $file_name = $cgi->param('f'); -if (defined $file_name) { - if (!validate_input($file_name)) { - die_error(undef, "Invalid file parameter"); - } -} - our $file_parent = $cgi->param('fp'); -if (defined $file_parent) { - if (!validate_input($file_parent)) { - die_error(undef, "Invalid file parent parameter"); - } -} our $hash = $cgi->param('h'); if (defined $hash) { @@ -305,7 +295,7 @@ sub evaluate_path_info { $action ||= "blob_plain"; } $hash_base ||= validate_input($refname); - $file_name ||= validate_input($pathname); + $file_name ||= $pathname; } elsif (defined $refname) { # we got "project.git/branch" $action ||= "shortlog"; @@ -416,7 +406,7 @@ sub validate_input { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; - $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; + $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg; $str =~ s/\+/%2B/g; $str =~ s/ /\+/g; return $str; @@ -1282,7 +1272,7 @@ sub git_header_html { if (defined $action) { $title .= "/$action"; if (defined $file_name) { - $title .= " - $file_name"; + $title .= " - " . esc_html($file_name); if ($action eq "tree" && $file_name !~ m|/$|) { $title .= "/"; } @@ -2430,7 +2420,7 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '-l', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = @@ -3072,12 +3062,12 @@ sub git_blobdiff { if (defined $file_name) { if (defined $file_parent) { $diffinfo{'status'} = '2'; - $diffinfo{'from_file'} = $file_parent; - $diffinfo{'to_file'} = $file_name; + $diffinfo{'from_file'} = esc_html($file_parent); + $diffinfo{'to_file'} = esc_html($file_name); } else { # assume not renamed $diffinfo{'status'} = '1'; - $diffinfo{'from_file'} = $file_name; - $diffinfo{'to_file'} = $file_name; + $diffinfo{'from_file'} = esc_html($file_name); + $diffinfo{'to_file'} = esc_html($file_name); } } else { # no filename given $diffinfo{'status'} = '2'; @@ -3126,7 +3116,7 @@ sub git_blobdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => qq(inline; filename="${file_name}.patch")); + -content_disposition => qq(inline; filename=") . quotemeta($file_name) . qq(.patch")); print "X-Git-Url: " . $cgi->self_url() . "\n\n"; @@ -3576,7 +3566,7 @@ XML if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { next; } - my $file = validate_input(unquote($7)); + my $file = esc_html(unquote($7)); $file = decode("utf8", $file, Encode::FB_DEFAULT); print "$file<br/>\n"; } -- cgit v0.10.2-6-g49f6 From ed1795fcc5f2aa3f105630429bcbed49c50053fa Mon Sep 17 00:00:00 2001 From: "Randal L. Schwartz" <merlyn@stonehenge.com> Date: Sat, 23 Sep 2006 17:20:45 -0700 Subject: builtin-upload-archive.c broken on openbsd Looks like ctype again. Gotta be careful with that on BSD releases: $ gmake prefix=/opt/git all GIT_VERSION = 1.4.2.GIT gcc -o builtin-upload-archive.o -c -g -O2 -Wall -I/usr/local/include -DSHA1_HEADER='<openssl/sha.h>' -DNO_STRCASESTR builtin-upload-archive.c In file included from /usr/include/sys/poll.h:54, from builtin-upload-archive.c:11: /usr/include/ctype.h:68: error: syntax error before ']' token /usr/include/ctype.h:69: error: syntax error before ']' token ... /usr/include/sys/poll.h:53:1: unterminated #ifndef /usr/include/sys/poll.h:28:1: unterminated #ifndef gmake: *** [builtin-upload-archive.o] Error 1 This fixes it. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index 0596865..45c92e1 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -2,13 +2,13 @@ * Copyright (c) 2006 Franck Bui-Huu */ #include <time.h> +#include <sys/wait.h> +#include <sys/poll.h> #include "cache.h" #include "builtin.h" #include "archive.h" #include "pkt-line.h" #include "sideband.h" -#include <sys/wait.h> -#include <sys/poll.h> static const char upload_archive_usage[] = "git-upload-archive <repo>"; -- cgit v0.10.2-6-g49f6 From 448c3ef144b2f528bb6dc50153ac13cc2abbc3c4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 22 Sep 2006 22:48:39 -0700 Subject: diff.c: second war on whitespace. This adds DIFF_WHITESPACE color class (default = reverse red) to colored diff output to let you catch common whitespace errors. - trailing whitespaces at the end of line - a space followed by a tab in the indent Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff.c b/diff.c index 443e248..2464238 100644 --- a/diff.c +++ b/diff.c @@ -20,12 +20,13 @@ static int diff_use_color_default; static char diff_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ - "", /* normal */ - "\033[1m", /* bold */ - "\033[36m", /* cyan */ - "\033[31m", /* red */ - "\033[32m", /* green */ - "\033[33m" /* yellow */ + "", /* PLAIN (normal) */ + "\033[1m", /* METAINFO (bold) */ + "\033[36m", /* FRAGINFO (cyan) */ + "\033[31m", /* OLD (red) */ + "\033[32m", /* NEW (green) */ + "\033[33m", /* COMMIT (yellow) */ + "\033[41m", /* WHITESPACE (red background) */ }; static int parse_diff_color_slot(const char *var, int ofs) @@ -42,6 +43,8 @@ static int parse_diff_color_slot(const char *var, int ofs) return DIFF_FILE_NEW; if (!strcasecmp(var+ofs, "commit")) return DIFF_COMMIT; + if (!strcasecmp(var+ofs, "whitespace")) + return DIFF_WHITESPACE; die("bad config variable '%s'", var); } @@ -383,9 +386,89 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } +static void emit_line(const char *set, const char *reset, const char *line, int len) +{ + if (len > 0 && line[len-1] == '\n') + len--; + fputs(set, stdout); + fwrite(line, len, 1, stdout); + puts(reset); +} + +static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +{ + int col0 = ecbdata->nparents; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int i; + int tail = len; + int need_highlight_leading_space = 0; + const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + if (!*ws) { + emit_line(set, reset, line, len); + return; + } + + /* The line is a newly added line. Does it have funny leading + * whitespaces? In indent, SP should never precede a TAB. + */ + for (i = col0; i < len; i++) { + if (line[i] == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_highlight_leading_space = 1; + } + else if (line[i] == ' ') + last_space_in_indent = i; + else + break; + } + fputs(set, stdout); + fwrite(line, col0, 1, stdout); + fputs(reset, stdout); + if (((i == len) || line[i] == '\n') && i != col0) { + /* The whole line was indent */ + emit_line(ws, reset, line + col0, len - col0); + return; + } + i = col0; + if (need_highlight_leading_space) { + while (i < last_tab_in_indent) { + if (line[i] == ' ') { + fputs(ws, stdout); + putchar(' '); + fputs(reset, stdout); + } + else + putchar(line[i]); + i++; + } + } + tail = len - 1; + if (line[tail] == '\n' && i < tail) + tail--; + while (i < tail) { + if (!isspace(line[tail])) + break; + tail--; + } + if ((i < tail && line[tail + 1] != '\n')) { + /* This has whitespace between tail+1..len */ + fputs(set, stdout); + fwrite(line + i, tail - i + 1, 1, stdout); + fputs(reset, stdout); + emit_line(ws, reset, line + tail + 1, len - tail - 1); + } + else + emit_line(set, reset, line + i, len - i); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { int i; + int color; struct emit_callback *ecbdata = priv; const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); @@ -403,45 +486,52 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ; if (2 <= i && i < len && line[i] == ' ') { ecbdata->nparents = i - 1; - set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO); + emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), + reset, line, len); + return; } - else if (len < ecbdata->nparents) + + if (len < ecbdata->nparents) { set = reset; - else { - int nparents = ecbdata->nparents; - int color = DIFF_PLAIN; - if (ecbdata->diff_words && nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); - if (ecbdata->diff_words) { - if (line[0] == '-') { - diff_words_append(line, len, - &ecbdata->diff_words->minus); - return; - } else if (line[0] == '+') { - diff_words_append(line, len, - &ecbdata->diff_words->plus); - return; - } - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); - line++; - len--; - } else - for (i = 0; i < nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - set = diff_get_color(ecbdata->color_diff, color); + emit_line(reset, reset, line, len); + return; } - if (len > 0 && line[len-1] == '\n') + + color = DIFF_PLAIN; + if (ecbdata->diff_words && ecbdata->nparents != 1) + /* fall back to normal diff */ + free_diff_words_data(ecbdata); + if (ecbdata->diff_words) { + if (line[0] == '-') { + diff_words_append(line, len, + &ecbdata->diff_words->minus); + return; + } else if (line[0] == '+') { + diff_words_append(line, len, + &ecbdata->diff_words->plus); + return; + } + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); + line++; len--; - fputs (set, stdout); - fwrite (line, len, 1, stdout); - puts (reset); + emit_line(set, reset, line, len); + return; + } + for (i = 0; i < ecbdata->nparents && len; i++) { + if (line[i] == '-') + color = DIFF_FILE_OLD; + else if (line[i] == '+') + color = DIFF_FILE_NEW; + } + + if (color != DIFF_FILE_NEW) { + emit_line(diff_get_color(ecbdata->color_diff, color), + reset, line, len); + return; + } + emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) diff --git a/diff.h b/diff.h index b60a02e..3435fe7 100644 --- a/diff.h +++ b/diff.h @@ -86,6 +86,7 @@ enum color_diff { DIFF_FILE_OLD = 4, DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, + DIFF_WHITESPACE = 7, }; const char *diff_get_color(int diff_use_color, enum color_diff ix); -- cgit v0.10.2-6-g49f6 From d0c25035df4897bb58422b4d64f00b54cf11f07e Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 23 Sep 2006 00:37:19 -0700 Subject: git-apply: second war on whitespace. This makes --whitespace={warn,error,strip} option to also notice the leading whitespace errors in addition to the trailing whitespace errors. Spaces that are followed by a tab in indent are detected as errors, and --whitespace=strip option fixes them. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-apply.c b/builtin-apply.c index 25e90d8..de5f855 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -854,6 +854,49 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } +static void check_whitespace(const char *line, int len) +{ + const char *err = "Adds trailing whitespace"; + int seen_space = 0; + int i; + + /* + * We know len is at least two, since we have a '+' and we + * checked that the last character was a '\n' before calling + * this function. That is, an addition of an empty line would + * check the '+' here. Sneaky... + */ + if (isspace(line[len-2])) + goto error; + + /* + * Make sure that there is no space followed by a tab in + * indentation. + */ + err = "Space in indent is followed by a tab"; + for (i = 1; i < len; i++) { + if (line[i] == '\t') { + if (seen_space) + goto error; + } + else if (line[i] == ' ') + seen_space = 1; + else + break; + } + return; + + error: + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < whitespace_error) + ; + else + fprintf(stderr, "%s.\n%s:%d:%.*s\n", + err, patch_input_file, linenr, len-2, line+1); +} + + /* * Parse a unified diff. Note that this really needs to parse each * fragment separately, since the only way to know the difference @@ -904,25 +947,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s trailing = 0; break; case '+': - /* - * We know len is at least two, since we have a '+' and - * we checked that the last character was a '\n' above. - * That is, an addition of an empty line would check - * the '+' here. Sneaky... - */ - if ((new_whitespace != nowarn_whitespace) && - isspace(line[len-2])) { - whitespace_error++; - if (squelch_whitespace_errors && - squelch_whitespace_errors < - whitespace_error) - ; - else { - fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", - patch_input_file, - linenr, len-2, line+1); - } - } + if (new_whitespace != nowarn_whitespace) + check_whitespace(line, len); added++; newlines--; trailing = 0; @@ -1494,22 +1520,68 @@ static int apply_line(char *output, const char *patch, int plen) { /* plen is number of bytes to be copied from patch, * starting at patch+1 (patch[0] is '+'). Typically - * patch[plen] is '\n'. + * patch[plen] is '\n', unless this is the incomplete + * last line. */ + int i; int add_nl_to_tail = 0; - if ((new_whitespace == strip_whitespace) && - 1 < plen && isspace(patch[plen-1])) { + int fixed = 0; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int need_fix_leading_space = 0; + char *buf; + + if ((new_whitespace != strip_whitespace) || !whitespace_error) { + memcpy(output, patch + 1, plen); + return plen; + } + + if (1 < plen && isspace(patch[plen-1])) { if (patch[plen] == '\n') add_nl_to_tail = 1; plen--; while (0 < plen && isspace(patch[plen])) plen--; - applied_after_stripping++; + fixed = 1; } - memcpy(output, patch + 1, plen); + + for (i = 1; i < plen; i++) { + char ch = patch[i]; + if (ch == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_fix_leading_space = 1; + } + else if (ch == ' ') + last_space_in_indent = i; + else + break; + } + + buf = output; + if (need_fix_leading_space) { + /* between patch[1..last_tab_in_indent] strip the + * funny spaces, updating them to tab as needed. + */ + for (i = 1; i < last_tab_in_indent; i++, plen--) { + char ch = patch[i]; + if (ch != ' ') + *output++ = ch; + else if ((i % 8) == 0) + *output++ = '\t'; + } + fixed = 1; + i = last_tab_in_indent; + } + else + i = 1; + + memcpy(output, patch + i, plen); if (add_nl_to_tail) output[plen++] = '\n'; - return plen; + if (fixed) + applied_after_stripping++; + return output + plen - buf; } static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof) -- cgit v0.10.2-6-g49f6 From 648ad18f5077295c4317f0784b29e09d2c0bac93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= <sbejar@gmail.com> Date: Sat, 23 Sep 2006 12:05:43 +0200 Subject: Fetch: default remote repository from branch properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If in branch "foo" and this in config: [branch "foo"] remote=bar "git fetch" = "git fetch bar" "git pull" = "git pull bar" Signed-off-by: Santi Béjar <sbejar@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/config.txt b/Documentation/config.txt index bb2fbc3..04c5094 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -119,6 +119,9 @@ apply.whitespace:: Tells `git-apply` how to handle whitespaces, in the same way as the '--whitespace' option. See gitlink:git-apply[1]. +branch.<name>.remote:: + When in branch <name>, it tells `git fetch` which remote to fetch. + pager.color:: A boolean to enable/disable colored output when the pager is in use (default is true). diff --git a/git-fetch.sh b/git-fetch.sh index 09a5d6c..50ad101 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -68,11 +68,10 @@ done case "$#" in 0) - test -f "$GIT_DIR/branches/origin" || - test -f "$GIT_DIR/remotes/origin" || - git-repo-config --get remote.origin.url >/dev/null || - die "Where do you want to fetch from today?" - set origin ;; + origin=$(get_default_remote) + test -n "$(get_remote_url ${origin})" || + die "Where do you want to fetch from today?" + set x $origin ; shift ;; esac remote_nick="$1" diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 187f088..6999816 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -68,6 +68,12 @@ get_remote_url () { esac } +get_default_remote () { + curr_branch=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||') + origin=$(git-repo-config --get "branch.$curr_branch.remote") + echo ${origin:-origin} +} + get_remote_default_refs_for_push () { data_source=$(get_data_source "$1") case "$data_source" in -- cgit v0.10.2-6-g49f6 From 7be1d62c6f6b322d826d145762db0050057aeb69 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 23 Sep 2006 03:40:17 -0700 Subject: Add t5510 to test per branch configuration affecting git-fetch. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh new file mode 100755 index 0000000..e71581a --- /dev/null +++ b/t/t5510-fetch.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# Copyright (c) 2006, Junio C Hamano. + +test_description='Per branch config variables affects "git fetch". + +' + +. ./test-lib.sh + +D=`pwd` + +test_expect_success setup ' + echo >file original && + git add file && + git commit -a -m original' + +test_expect_success "clone and setup child repos" ' + git clone . one && + cd one && + echo >file updated by one && + git commit -a -m "updated by one" && + cd .. && + git clone . two && + cd two && + git repo-config branch.master.remote one && + { + echo "URL: ../one/.git/" + echo "Pull: refs/heads/master:refs/heads/one" + } >.git/remotes/one +' + +test_expect_success "fetch test" ' + cd "$D" && + echo >file updated by origin && + git commit -a -m "updated by origin" && + cd two && + git fetch && + test -f .git/refs/heads/one && + mine=`git rev-parse refs/heads/one` && + his=`cd ../one && git rev-parse refs/heads/master` && + test "z$mine" = "z$his" +' + +test_done -- cgit v0.10.2-6-g49f6 From 5372806a849cf117596b1f7c8c7d512c519f8092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= <sbejar@gmail.com> Date: Sat, 23 Sep 2006 22:53:04 +0200 Subject: fetch: get the remote branches to merge from the branch properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If in branch "foo" and this in config: [branch "foo"] merge=bar "git fetch": fetch from the default repository and program the "bar" branch to be merged with pull. Signed-off-by: Santi Béjar <sbejar@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/config.txt b/Documentation/config.txt index 04c5094..98c1f3e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -122,6 +122,10 @@ apply.whitespace:: branch.<name>.remote:: When in branch <name>, it tells `git fetch` which remote to fetch. +branch.<name>.merge:: + When in branch <name>, it tells `git fetch` the default remote branch + to be merged. + pager.color:: A boolean to enable/disable colored output when the pager is in use (default is true). diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 6999816..c325ef7 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -92,9 +92,22 @@ get_remote_default_refs_for_push () { # Subroutine to canonicalize remote:local notation. canon_refs_list_for_fetch () { - # Leave only the first one alone; add prefix . to the rest + # If called from get_remote_default_refs_for_fetch + # leave the branches in branch.${curr_branch}.merge alone, + # or the first one otherwise; add prefix . to the rest # to prevent the secondary branches to be merged by default. - dot_prefix= + merge_branches= + if test "$1" = "-d" + then + shift ; remote="$1" ; shift + if test "$remote" = "$(get_default_remote)" + then + curr_branch=$(git-symbolic-ref HEAD | \ + sed -e 's|^refs/heads/||') + merge_branches=$(git-repo-config \ + --get-all "branch.${curr_branch}.merge") + fi + fi for ref do force= @@ -107,6 +120,18 @@ canon_refs_list_for_fetch () { expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:" remote=$(expr "z$ref" : 'z\([^:]*\):') local=$(expr "z$ref" : 'z[^:]*:\(.*\)') + dot_prefix=. + if test -z "$merge_branches" + then + merge_branches=$remote + dot_prefix= + else + for merge_branch in $merge_branches + do + [ "$remote" = "$merge_branch" ] && + dot_prefix= && break + done + fi case "$remote" in '') remote=HEAD ;; refs/heads/* | refs/tags/* | refs/remotes/*) ;; @@ -126,7 +151,6 @@ canon_refs_list_for_fetch () { die "* refusing to create funny ref '$local_ref_name' locally" fi echo "${dot_prefix}${force}${remote}:${local}" - dot_prefix=. done } @@ -137,7 +161,7 @@ get_remote_default_refs_for_fetch () { '' | config-partial | branches-partial) echo "HEAD:" ;; config) - canon_refs_list_for_fetch \ + canon_refs_list_for_fetch -d "$1" \ $(git-repo-config --get-all "remote.$1.fetch") ;; branches) remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1") @@ -145,10 +169,7 @@ get_remote_default_refs_for_fetch () { echo "refs/heads/${remote_branch}:refs/heads/$1" ;; remotes) - # This prefixes the second and later default refspecs - # with a '.', to signal git-fetch to mark them - # not-for-merge. - canon_refs_list_for_fetch $(sed -ne '/^Pull: */{ + canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{ s///p }' "$GIT_DIR/remotes/$1") ;; -- cgit v0.10.2-6-g49f6 From 6cc7c36d5e2fd89be596a164bcc2afede9d855d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= <sbejar@gmail.com> Date: Sat, 23 Sep 2006 22:55:35 +0200 Subject: Add test for the default merges in fetch. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [jc: with minor fix-ups] Signed-off-by: Santi Béjar <sbejar@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index e71581a..df0ae48 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -27,6 +27,16 @@ test_expect_success "clone and setup child repos" ' echo "URL: ../one/.git/" echo "Pull: refs/heads/master:refs/heads/one" } >.git/remotes/one + cd .. && + git clone . three && + cd three && + git repo-config branch.master.remote two && + git repo-config branch.master.merge refs/heads/one && + { + echo "URL: ../two/.git/" + echo "Pull: refs/heads/master:refs/heads/two" + echo "Pull: refs/heads/one:refs/heads/one" + } >.git/remotes/two ' test_expect_success "fetch test" ' @@ -41,4 +51,19 @@ test_expect_success "fetch test" ' test "z$mine" = "z$his" ' +test_expect_success "fetch test for-merge" ' + cd "$D" && + cd three && + git fetch && + test -f .git/refs/heads/two && + test -f .git/refs/heads/one && + master_in_two=`cd ../two && git rev-parse master` && + one_in_two=`cd ../two && git rev-parse one` && + { + echo "$master_in_two not-for-merge" + echo "$one_in_two " + } >expected && + cut -f -2 .git/FETCH_HEAD >actual && + diff expected actual' + test_done -- cgit v0.10.2-6-g49f6 From 81b84c42d645dd9c30b77f804abc07e128dc490b Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Sun, 24 Sep 2006 17:30:44 +0200 Subject: git-tar-tree: Remove duplicate git_config() call generate_tar() eventually calls write_tar_archive() which does all the "real" work and which also calls git_config(git_tar_config). We only need to do this once. Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 437eb72..82b4951 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -267,8 +267,6 @@ static int generate_tar(int argc, const char **argv, const char *prefix) int result; char *base = NULL; - git_config(git_tar_config); - memset(&args, 0, sizeof(args)); if (argc != 2 && argc != 3) usage(tar_tree_usage); -- cgit v0.10.2-6-g49f6 From 3d74982f0b1f8895d27937aa6ed62c1ddd50a020 Mon Sep 17 00:00:00 2001 From: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Date: Sun, 24 Sep 2006 17:31:10 +0200 Subject: git-tar-tree: Move code for git-archive --format=tar to archive-tar.c This patch doesn't change any functionality, it only moves code around. It makes seeing the few remaining lines of git-tar-tree code easier. ;-) Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 51fbe6a..59164b8 100644 --- a/Makefile +++ b/Makefile @@ -256,7 +256,7 @@ LIB_OBJS = \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ - color.o wt-status.o archive-zip.o + color.o wt-status.o archive-zip.o archive-tar.o BUILTIN_OBJS = \ builtin-add.o \ diff --git a/archive-tar.c b/archive-tar.c new file mode 100644 index 0000000..ff0f6e2 --- /dev/null +++ b/archive-tar.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2005, 2006 Rene Scharfe + */ +#include <time.h> +#include "cache.h" +#include "commit.h" +#include "strbuf.h" +#include "tar.h" +#include "builtin.h" +#include "archive.h" + +#define RECORDSIZE (512) +#define BLOCKSIZE (RECORDSIZE * 20) + +static char block[BLOCKSIZE]; +static unsigned long offset; + +static time_t archive_time; +static int tar_umask; +static int verbose; + +/* writes out the whole block, but only if it is full */ +static void write_if_needed(void) +{ + if (offset == BLOCKSIZE) { + write_or_die(1, block, BLOCKSIZE); + offset = 0; + } +} + +/* + * queues up writes, so that all our write(2) calls write exactly one + * full block; pads writes to RECORDSIZE + */ +static void write_blocked(const void *data, unsigned long size) +{ + const char *buf = data; + unsigned long tail; + + if (offset) { + unsigned long chunk = BLOCKSIZE - offset; + if (size < chunk) + chunk = size; + memcpy(block + offset, buf, chunk); + size -= chunk; + offset += chunk; + buf += chunk; + write_if_needed(); + } + while (size >= BLOCKSIZE) { + write_or_die(1, buf, BLOCKSIZE); + size -= BLOCKSIZE; + buf += BLOCKSIZE; + } + if (size) { + memcpy(block + offset, buf, size); + offset += size; + } + tail = offset % RECORDSIZE; + if (tail) { + memset(block + offset, 0, RECORDSIZE - tail); + offset += RECORDSIZE - tail; + } + write_if_needed(); +} + +/* + * The end of tar archives is marked by 2*512 nul bytes and after that + * follows the rest of the block (if any). + */ +static void write_trailer(void) +{ + int tail = BLOCKSIZE - offset; + memset(block + offset, 0, tail); + write_or_die(1, block, BLOCKSIZE); + if (tail < 2 * RECORDSIZE) { + memset(block, 0, offset); + write_or_die(1, block, BLOCKSIZE); + } +} + +static void strbuf_append_string(struct strbuf *sb, const char *s) +{ + int slen = strlen(s); + int total = sb->len + slen; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + memcpy(sb->buf + sb->len, s, slen); + sb->len = total; +} + +/* + * pax extended header records have the format "%u %s=%s\n". %u contains + * the size of the whole string (including the %u), the first %s is the + * keyword, the second one is the value. This function constructs such a + * string and appends it to a struct strbuf. + */ +static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, + const char *value, unsigned int valuelen) +{ + char *p; + int len, total, tmp; + + /* "%u %s=%s\n" */ + len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; + for (tmp = len; tmp > 9; tmp /= 10) + len++; + + total = sb->len + len; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + + p = sb->buf; + p += sprintf(p, "%u %s=", len, keyword); + memcpy(p, value, valuelen); + p += valuelen; + *p = '\n'; + sb->len = total; +} + +static unsigned int ustar_header_chksum(const struct ustar_header *header) +{ + char *p = (char *)header; + unsigned int chksum = 0; + while (p < header->chksum) + chksum += *p++; + chksum += sizeof(header->chksum) * ' '; + p += sizeof(header->chksum); + while (p < (char *)header + sizeof(struct ustar_header)) + chksum += *p++; + return chksum; +} + +static int get_path_prefix(const struct strbuf *path, int maxlen) +{ + int i = path->len; + if (i > maxlen) + i = maxlen; + do { + i--; + } while (i > 0 && path->buf[i] != '/'); + return i; +} + +static void write_entry(const unsigned char *sha1, struct strbuf *path, + unsigned int mode, void *buffer, unsigned long size) +{ + struct ustar_header header; + struct strbuf ext_header; + + memset(&header, 0, sizeof(header)); + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + + if (!sha1) { + *header.typeflag = TYPEFLAG_GLOBAL_HEADER; + mode = 0100666; + strcpy(header.name, "pax_global_header"); + } else if (!path) { + *header.typeflag = TYPEFLAG_EXT_HEADER; + mode = 0100666; + sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + } else { + if (verbose) + fprintf(stderr, "%.*s\n", path->len, path->buf); + if (S_ISDIR(mode)) { + *header.typeflag = TYPEFLAG_DIR; + mode = (mode | 0777) & ~tar_umask; + } else if (S_ISLNK(mode)) { + *header.typeflag = TYPEFLAG_LNK; + mode |= 0777; + } else if (S_ISREG(mode)) { + *header.typeflag = TYPEFLAG_REG; + mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", + mode, sha1_to_hex(sha1)); + return; + } + if (path->len > sizeof(header.name)) { + int plen = get_path_prefix(path, sizeof(header.prefix)); + int rest = path->len - plen - 1; + if (plen > 0 && rest <= sizeof(header.name)) { + memcpy(header.prefix, path->buf, plen); + memcpy(header.name, path->buf + plen + 1, rest); + } else { + sprintf(header.name, "%s.data", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "path", + path->buf, path->len); + } + } else + memcpy(header.name, path->buf, path->len); + } + + if (S_ISLNK(mode) && buffer) { + if (size > sizeof(header.linkname)) { + sprintf(header.linkname, "see %s.paxheader", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "linkpath", + buffer, size); + } else + memcpy(header.linkname, buffer, size); + } + + sprintf(header.mode, "%07o", mode & 07777); + sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); + sprintf(header.mtime, "%011lo", archive_time); + + /* XXX: should we provide more meaningful info here? */ + sprintf(header.uid, "%07o", 0); + sprintf(header.gid, "%07o", 0); + strlcpy(header.uname, "git", sizeof(header.uname)); + strlcpy(header.gname, "git", sizeof(header.gname)); + sprintf(header.devmajor, "%07o", 0); + sprintf(header.devminor, "%07o", 0); + + memcpy(header.magic, "ustar", 6); + memcpy(header.version, "00", 2); + + sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); + + if (ext_header.len > 0) { + write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); + } + write_blocked(&header, sizeof(header)); + if (S_ISREG(mode) && buffer && size > 0) + write_blocked(buffer, size); +} + +static void write_global_extended_header(const unsigned char *sha1) +{ + struct strbuf ext_header; + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); + write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); +} + +static int git_tar_config(const char *var, const char *value) +{ + if (!strcmp(var, "tar.umask")) { + if (!strcmp(value, "user")) { + tar_umask = umask(0); + umask(tar_umask); + } else { + tar_umask = git_config_int(var, value); + } + return 0; + } + return git_default_config(var, value); +} + +static int write_tar_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + static struct strbuf path; + int filenamelen = strlen(filename); + void *buffer; + char type[20]; + unsigned long size; + + if (!path.alloc) { + path.buf = xmalloc(PATH_MAX); + path.alloc = PATH_MAX; + path.len = path.eof = 0; + } + if (path.alloc < baselen + filenamelen) { + free(path.buf); + path.buf = xmalloc(baselen + filenamelen); + path.alloc = baselen + filenamelen; + } + memcpy(path.buf, base, baselen); + memcpy(path.buf + baselen, filename, filenamelen); + path.len = baselen + filenamelen; + if (S_ISDIR(mode)) { + strbuf_append_string(&path, "/"); + buffer = NULL; + size = 0; + } else { + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + } + + write_entry(sha1, &path, mode, buffer, size); + free(buffer); + + return READ_TREE_RECURSIVE; +} + +int write_tar_archive(struct archiver_args *args) +{ + int plen = args->base ? strlen(args->base) : 0; + + git_config(git_tar_config); + + archive_time = args->time; + verbose = args->verbose; + + if (args->commit_sha1) + write_global_extended_header(args->commit_sha1); + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_tar_entry); + write_trailer(); + + return 0; +} diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 82b4951..aa370e3 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -4,7 +4,6 @@ #include <time.h> #include "cache.h" #include "commit.h" -#include "strbuf.h" #include "tar.h" #include "builtin.h" #include "pkt-line.h" @@ -16,251 +15,6 @@ static const char tar_tree_usage[] = "git-tar-tree [--remote=<repo>] <tree-ish> [basedir]"; -static char block[BLOCKSIZE]; -static unsigned long offset; - -static time_t archive_time; -static int tar_umask; -static int verbose; - -/* writes out the whole block, but only if it is full */ -static void write_if_needed(void) -{ - if (offset == BLOCKSIZE) { - write_or_die(1, block, BLOCKSIZE); - offset = 0; - } -} - -/* - * queues up writes, so that all our write(2) calls write exactly one - * full block; pads writes to RECORDSIZE - */ -static void write_blocked(const void *data, unsigned long size) -{ - const char *buf = data; - unsigned long tail; - - if (offset) { - unsigned long chunk = BLOCKSIZE - offset; - if (size < chunk) - chunk = size; - memcpy(block + offset, buf, chunk); - size -= chunk; - offset += chunk; - buf += chunk; - write_if_needed(); - } - while (size >= BLOCKSIZE) { - write_or_die(1, buf, BLOCKSIZE); - size -= BLOCKSIZE; - buf += BLOCKSIZE; - } - if (size) { - memcpy(block + offset, buf, size); - offset += size; - } - tail = offset % RECORDSIZE; - if (tail) { - memset(block + offset, 0, RECORDSIZE - tail); - offset += RECORDSIZE - tail; - } - write_if_needed(); -} - -/* - * The end of tar archives is marked by 2*512 nul bytes and after that - * follows the rest of the block (if any). - */ -static void write_trailer(void) -{ - int tail = BLOCKSIZE - offset; - memset(block + offset, 0, tail); - write_or_die(1, block, BLOCKSIZE); - if (tail < 2 * RECORDSIZE) { - memset(block, 0, offset); - write_or_die(1, block, BLOCKSIZE); - } -} - -static void strbuf_append_string(struct strbuf *sb, const char *s) -{ - int slen = strlen(s); - int total = sb->len + slen; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - memcpy(sb->buf + sb->len, s, slen); - sb->len = total; -} - -/* - * pax extended header records have the format "%u %s=%s\n". %u contains - * the size of the whole string (including the %u), the first %s is the - * keyword, the second one is the value. This function constructs such a - * string and appends it to a struct strbuf. - */ -static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, - const char *value, unsigned int valuelen) -{ - char *p; - int len, total, tmp; - - /* "%u %s=%s\n" */ - len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; - for (tmp = len; tmp > 9; tmp /= 10) - len++; - - total = sb->len + len; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - - p = sb->buf; - p += sprintf(p, "%u %s=", len, keyword); - memcpy(p, value, valuelen); - p += valuelen; - *p = '\n'; - sb->len = total; -} - -static unsigned int ustar_header_chksum(const struct ustar_header *header) -{ - char *p = (char *)header; - unsigned int chksum = 0; - while (p < header->chksum) - chksum += *p++; - chksum += sizeof(header->chksum) * ' '; - p += sizeof(header->chksum); - while (p < (char *)header + sizeof(struct ustar_header)) - chksum += *p++; - return chksum; -} - -static int get_path_prefix(const struct strbuf *path, int maxlen) -{ - int i = path->len; - if (i > maxlen) - i = maxlen; - do { - i--; - } while (i > 0 && path->buf[i] != '/'); - return i; -} - -static void write_entry(const unsigned char *sha1, struct strbuf *path, - unsigned int mode, void *buffer, unsigned long size) -{ - struct ustar_header header; - struct strbuf ext_header; - - memset(&header, 0, sizeof(header)); - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - - if (!sha1) { - *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666; - strcpy(header.name, "pax_global_header"); - } else if (!path) { - *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666; - sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); - } else { - if (verbose) - fprintf(stderr, "%.*s\n", path->len, path->buf); - if (S_ISDIR(mode)) { - *header.typeflag = TYPEFLAG_DIR; - mode = (mode | 0777) & ~tar_umask; - } else if (S_ISLNK(mode)) { - *header.typeflag = TYPEFLAG_LNK; - mode |= 0777; - } else if (S_ISREG(mode)) { - *header.typeflag = TYPEFLAG_REG; - mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", - mode, sha1_to_hex(sha1)); - return; - } - if (path->len > sizeof(header.name)) { - int plen = get_path_prefix(path, sizeof(header.prefix)); - int rest = path->len - plen - 1; - if (plen > 0 && rest <= sizeof(header.name)) { - memcpy(header.prefix, path->buf, plen); - memcpy(header.name, path->buf + plen + 1, rest); - } else { - sprintf(header.name, "%s.data", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "path", - path->buf, path->len); - } - } else - memcpy(header.name, path->buf, path->len); - } - - if (S_ISLNK(mode) && buffer) { - if (size > sizeof(header.linkname)) { - sprintf(header.linkname, "see %s.paxheader", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "linkpath", - buffer, size); - } else - memcpy(header.linkname, buffer, size); - } - - sprintf(header.mode, "%07o", mode & 07777); - sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header.mtime, "%011lo", archive_time); - - /* XXX: should we provide more meaningful info here? */ - sprintf(header.uid, "%07o", 0); - sprintf(header.gid, "%07o", 0); - strlcpy(header.uname, "git", sizeof(header.uname)); - strlcpy(header.gname, "git", sizeof(header.gname)); - sprintf(header.devmajor, "%07o", 0); - sprintf(header.devminor, "%07o", 0); - - memcpy(header.magic, "ustar", 6); - memcpy(header.version, "00", 2); - - sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); - - if (ext_header.len > 0) { - write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); - } - write_blocked(&header, sizeof(header)); - if (S_ISREG(mode) && buffer && size > 0) - write_blocked(buffer, size); -} - -static void write_global_extended_header(const unsigned char *sha1) -{ - struct strbuf ext_header; - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); - write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); -} - -static int git_tar_config(const char *var, const char *value) -{ - if (!strcmp(var, "tar.umask")) { - if (!strcmp(value, "user")) { - tar_umask = umask(0); - umask(tar_umask); - } else { - tar_umask = git_config_int(var, value); - } - return 0; - } - return git_default_config(var, value); -} - static int generate_tar(int argc, const char **argv, const char *prefix) { struct archiver_args args; @@ -286,73 +40,6 @@ static int generate_tar(int argc, const char **argv, const char *prefix) return result; } -static int write_tar_entry(const unsigned char *sha1, - const char *base, int baselen, - const char *filename, unsigned mode, int stage) -{ - static struct strbuf path; - int filenamelen = strlen(filename); - void *buffer; - char type[20]; - unsigned long size; - - if (!path.alloc) { - path.buf = xmalloc(PATH_MAX); - path.alloc = PATH_MAX; - path.len = path.eof = 0; - } - if (path.alloc < baselen + filenamelen) { - free(path.buf); - path.buf = xmalloc(baselen + filenamelen); - path.alloc = baselen + filenamelen; - } - memcpy(path.buf, base, baselen); - memcpy(path.buf + baselen, filename, filenamelen); - path.len = baselen + filenamelen; - if (S_ISDIR(mode)) { - strbuf_append_string(&path, "/"); - buffer = NULL; - size = 0; - } else { - buffer = read_sha1_file(sha1, type, &size); - if (!buffer) - die("cannot read %s", sha1_to_hex(sha1)); - } - - write_entry(sha1, &path, mode, buffer, size); - free(buffer); - - return READ_TREE_RECURSIVE; -} - -int write_tar_archive(struct archiver_args *args) -{ - int plen = args->base ? strlen(args->base) : 0; - - git_config(git_tar_config); - - archive_time = args->time; - verbose = args->verbose; - - if (args->commit_sha1) - write_global_extended_header(args->commit_sha1); - - if (args->base && plen > 0 && args->base[plen - 1] == '/') { - char *base = xstrdup(args->base); - int baselen = strlen(base); - - while (baselen > 0 && base[baselen - 1] == '/') - base[--baselen] = '\0'; - write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); - free(base); - } - read_tree_recursive(args->tree, args->base, plen, 0, - args->pathspec, write_tar_entry); - write_trailer(); - - return 0; -} - static const char *exec = "git-upload-tar"; static int remote_tar(int argc, const char **argv) -- cgit v0.10.2-6-g49f6 From fd88d9c81ecdefd656b01f288ed9127b7abef53e Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 24 Sep 2006 14:42:01 -0700 Subject: Remove upload-tar and make git-tar-tree a thin wrapper to git-archive The command now issues a big deprecation warning message and runs git-archive command with appropriate arguments. git-tar-tree $tree_ish $base always forces $base to be the leading directory name, so the --prefix parameter passed internally to git-archive is a slash appended to it, i.e. "--prefix=$base/". Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index 3ca66e4..284db5d 100644 --- a/.gitignore +++ b/.gitignore @@ -122,7 +122,6 @@ git-update-ref git-update-server-info git-upload-archive git-upload-pack -git-upload-tar git-var git-verify-pack git-verify-tag diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt index 1e1c7fa..74a6fdd 100644 --- a/Documentation/git-tar-tree.txt +++ b/Documentation/git-tar-tree.txt @@ -12,6 +12,9 @@ SYNOPSIS DESCRIPTION ----------- +THIS COMMAND IS DEPRECATED. Use `git-archive` with `--format=tar` +option instead. + Creates a tar archive containing the tree structure for the named tree. When <base> is specified it is added as a leading path to the files in the generated tar archive. diff --git a/Documentation/git-upload-tar.txt b/Documentation/git-upload-tar.txt deleted file mode 100644 index 394af62..0000000 --- a/Documentation/git-upload-tar.txt +++ /dev/null @@ -1,39 +0,0 @@ -git-upload-tar(1) -================= - -NAME ----- -git-upload-tar - Send tar archive - - -SYNOPSIS --------- -'git-upload-tar' <directory> - -DESCRIPTION ------------ -Invoked by 'git-tar-tree --remote' and sends a generated tar archive -to the other end over the git protocol. - -This command is usually not invoked directly by the end user. -The UI for the protocol is on the 'git-tar-tree' side, and the -program pair is meant to be used to get a tar archive from a -remote repository. - - -OPTIONS -------- -<directory>:: - The repository to get a tar archive from. - -Author ------- -Written by Junio C Hamano <junio@kernel.org> - -Documentation --------------- -Documentation by Junio C Hamano. - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index 744c38d..1bf5ef5 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -247,10 +247,6 @@ gitlink:git-upload-pack[1]:: Invoked by 'git-fetch-pack' to push what are asked for. -gitlink:git-upload-tar[1]:: - Invoked by 'git-tar-tree --remote' to return the tar - archive the other end asked for. - High-level commands (porcelain) ------------------------------- diff --git a/Makefile b/Makefile index 59164b8..28091d6 100644 --- a/Makefile +++ b/Makefile @@ -300,7 +300,6 @@ BUILTIN_OBJS = \ builtin-update-index.o \ builtin-update-ref.o \ builtin-upload-archive.o \ - builtin-upload-tar.o \ builtin-verify-pack.o \ builtin-write-tree.o diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index aa370e3..4d4cfec 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -6,96 +6,66 @@ #include "commit.h" #include "tar.h" #include "builtin.h" -#include "pkt-line.h" -#include "archive.h" - -#define RECORDSIZE (512) -#define BLOCKSIZE (RECORDSIZE * 20) +#include "quote.h" static const char tar_tree_usage[] = -"git-tar-tree [--remote=<repo>] <tree-ish> [basedir]"; +"git-tar-tree [--remote=<repo>] <tree-ish> [basedir]\n" +"*** Note that this command is now deprecated; use git-archive instead."; -static int generate_tar(int argc, const char **argv, const char *prefix) +int cmd_tar_tree(int argc, const char **argv, const char *prefix) { - struct archiver_args args; - int result; - char *base = NULL; - - memset(&args, 0, sizeof(args)); - if (argc != 2 && argc != 3) - usage(tar_tree_usage); - if (argc == 3) { - int baselen = strlen(argv[2]); - base = xmalloc(baselen + 2); - memcpy(base, argv[2], baselen); - base[baselen] = '/'; - base[baselen + 1] = '\0'; + /* + * git-tar-tree is now a wrapper around git-archive --format=tar + * + * $0 --remote=<repo> arg... ==> + * git-archive --format=tar --remote=<repo> arg... + * $0 tree-ish ==> + * git-archive --format=tar tree-ish + * $0 tree-ish basedir ==> + * git-archive --format-tar --prefix=basedir tree-ish + */ + int i; + const char **nargv = xcalloc(sizeof(*nargv), argc + 2); + char *basedir_arg; + int nargc = 0; + + nargv[nargc++] = "git-archive"; + nargv[nargc++] = "--format=tar"; + + if (2 <= argc && !strncmp("--remote=", argv[1], 9)) { + nargv[nargc++] = argv[1]; + argv++; + argc--; } - args.base = base; - parse_treeish_arg(argv + 1, &args, NULL); - - result = write_tar_archive(&args); - free(base); - - return result; -} - -static const char *exec = "git-upload-tar"; - -static int remote_tar(int argc, const char **argv) -{ - int fd[2], ret, len; - pid_t pid; - char buf[1024]; - char *url; - - if (argc < 3 || 4 < argc) + switch (argc) { + default: usage(tar_tree_usage); - - /* --remote=<repo> */ - url = xstrdup(argv[1]+9); - pid = git_connect(fd, url, exec); - if (pid < 0) - return 1; - - packet_write(fd[1], "want %s\n", argv[2]); - if (argv[3]) - packet_write(fd[1], "base %s\n", argv[3]); - packet_flush(fd[1]); - - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (!len) - die("git-tar-tree: expected ACK/NAK, got EOF"); - if (buf[len-1] == '\n') - buf[--len] = 0; - if (strcmp(buf, "ACK")) { - if (5 < len && !strncmp(buf, "NACK ", 5)) - die("git-tar-tree: NACK %s", buf + 5); - die("git-tar-tree: protocol error"); + break; + case 3: + /* base-path */ + basedir_arg = xmalloc(strlen(argv[2]) + 11); + sprintf(basedir_arg, "--prefix=%s/", argv[2]); + nargv[nargc++] = basedir_arg; + /* fallthru */ + case 2: + /* tree-ish */ + nargv[nargc++] = argv[1]; } - /* expect a flush */ - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (len) - die("git-tar-tree: expected a flush"); - - /* Now, start reading from fd[0] and spit it out to stdout */ - ret = copy_fd(fd[0], 1); - close(fd[0]); - - ret |= finish_connect(pid); - return !!ret; -} - -int cmd_tar_tree(int argc, const char **argv, const char *prefix) -{ - if (argc < 2) - usage(tar_tree_usage); - if (!strncmp("--remote=", argv[1], 9)) - return remote_tar(argc, argv); - return generate_tar(argc, argv, prefix); + nargv[nargc] = NULL; + + fprintf(stderr, + "*** git-tar-tree is now deprecated.\n" + "*** Running git-archive instead.\n***"); + for (i = 0; i < nargc; i++) { + fputc(' ', stderr); + sq_quote_print(stderr, nargv[i]); + } + fputc('\n', stderr); + return cmd_archive(nargc, nargv, prefix); } /* ustar header + extended global header content */ +#define RECORDSIZE (512) #define HEADERSIZE (2 * RECORDSIZE) int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c deleted file mode 100644 index 06a945a..0000000 --- a/builtin-upload-tar.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2006 Junio C Hamano - */ -#include "cache.h" -#include "pkt-line.h" -#include "exec_cmd.h" -#include "builtin.h" - -static const char upload_tar_usage[] = "git-upload-tar <repo>"; - -static int nak(const char *reason) -{ - packet_write(1, "NACK %s\n", reason); - packet_flush(1); - return 1; -} - -int cmd_upload_tar(int argc, const char **argv, const char *prefix) -{ - int len; - const char *dir = argv[1]; - char buf[8192]; - unsigned char sha1[20]; - char *base = NULL; - char hex[41]; - int ac; - const char *av[4]; - - if (argc != 2) - usage(upload_tar_usage); - if (strlen(dir) < sizeof(buf)-1) - strcpy(buf, dir); /* enter-repo smudges its argument */ - else - packet_write(1, "NACK insanely long repository name %s\n", dir); - if (!enter_repo(buf, 0)) { - packet_write(1, "NACK not a git archive %s\n", dir); - packet_flush(1); - return 1; - } - - len = packet_read_line(0, buf, sizeof(buf)); - if (len < 5 || strncmp("want ", buf, 5)) - return nak("expected want"); - if (buf[len-1] == '\n') - buf[--len] = 0; - if (get_sha1(buf + 5, sha1)) - return nak("expected sha1"); - strcpy(hex, sha1_to_hex(sha1)); - - len = packet_read_line(0, buf, sizeof(buf)); - if (len) { - if (len < 5 || strncmp("base ", buf, 5)) - return nak("expected (optional) base"); - if (buf[len-1] == '\n') - buf[--len] = 0; - base = xstrdup(buf + 5); - len = packet_read_line(0, buf, sizeof(buf)); - } - if (len) - return nak("expected flush"); - - packet_write(1, "ACK\n"); - packet_flush(1); - - ac = 0; - av[ac++] = "tar-tree"; - av[ac++] = hex; - if (base) - av[ac++] = base; - av[ac++] = NULL; - execv_git_cmd(av); - /* should it return that is an error */ - return 1; -} diff --git a/git.c b/git.c index 1686220..ae80e78 100644 --- a/git.c +++ b/git.c @@ -263,7 +263,6 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, - { "upload-tar", cmd_upload_tar }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From d0b353b1a7a2f5168a8e2c992403f2a7d1e323f8 Mon Sep 17 00:00:00 2001 From: Jeff King <peff@peff.net> Date: Sun, 24 Sep 2006 22:31:11 -0400 Subject: git-repack: allow git-repack to run in subdirectory Now that we explicitly create all tmpfiles below $GIT_DIR, there's no reason to care about which directory we're in. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-repack.sh b/git-repack.sh index 9ae5092..f2c9071 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -4,6 +4,7 @@ # USAGE='[-a] [-d] [-f] [-l] [-n] [-q]' +SUBDIRECTORY_OK='Yes' . git-sh-setup no_update_info= all_into_one= remove_redundant= -- cgit v0.10.2-6-g49f6 From f7bae37f9ab2fdc567780d23495733c9af526e02 Mon Sep 17 00:00:00 2001 From: Shawn Pearce <spearce@spearce.org> Date: Sun, 24 Sep 2006 22:50:15 -0400 Subject: Allow 'svn fetch' on '(no date)' revisions in Subversion. Added --ignore-nodate to allow 'git svn fetch' to import revisions from Subversion which have '(no date)' listed as the date of the revision. By default 'git svn fetch' will crash with an error when encountering such a revision. The user may restart the fetch operation by adding --ignore-nodate if they want to continue tracking that repository. I'm not entirely sure why a centralized version control system such as Subversion permits revisions to be created with absolutely no date/time associated with it but it apparently is possible as one of the Subversion repositories that I'm tracking with 'git svn' created such a revision on '(no date)' and by '(no user)'. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index b7b63f7..1cfa3e3 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -244,6 +244,18 @@ doing. repo-config key: svn.noignoreexternals +--ignore-nodate:: +Only used with the 'fetch' command. + +By default git-svn will crash if it tries to import a revision +from SVN which has '(no date)' listed as the date of the revision. +This is repository corruption on SVN's part, plain and simple. +But sometimes you really need those revisions anyway. + +If supplied git-svn will convert '(no date)' entries to the UNIX +epoch (midnight on Jan. 1, 1970). Yes, that's probably very wrong. +SVN was very wrong. + -- Basic Examples diff --git a/git-svn.perl b/git-svn.perl index 0290850..8a2ef99 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -52,7 +52,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, - $_merge, $_strategy, $_dry_run); + $_merge, $_strategy, $_dry_run, $_ignore_nodate); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -65,6 +65,7 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, + 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); my ($_trunk, $_tags, $_branches); @@ -1734,6 +1735,8 @@ sub next_log_entry { my $rev = $1; my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3); ($lines) = ($lines =~ /(\d+)/); + $date = '1970-01-01 00:00:00 +0000' + if ($_ignore_nodate && $date eq '(no date)'); my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~ /(\d{4})\-(\d\d)\-(\d\d)\s (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x) -- cgit v0.10.2-6-g49f6 From 8815788e93c0a5a2e47e067dfa0764b17b8d1ddd Mon Sep 17 00:00:00 2001 From: Shawn Pearce <spearce@spearce.org> Date: Sun, 24 Sep 2006 23:04:55 -0400 Subject: Allow '(no author)' in git-svn's authors file. When trying to import an SVN revision which has no author the Git user may desire to relabel '(no author)' to another name and email address with their svn.authorsfile. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-svn.perl b/git-svn.perl index 8a2ef99..017f45a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2171,7 +2171,7 @@ sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; while (<$authors>) { chomp; - next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; + next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/; my ($user, $name, $email) = ($1, $2, $3); $users{$user} = [$name, $email]; } -- cgit v0.10.2-6-g49f6 From 8391548e5e78677eb81f97334d998418802ea194 Mon Sep 17 00:00:00 2001 From: Petr Baudis <pasky@suse.cz> Date: Sun, 24 Sep 2006 14:57:40 -0700 Subject: gitweb: fix over-eager application of esc_html(). Contents of %diffinfo hash should be quoted upon output but kept unquoted internally. Later users of this hash expect filenames to be filenames, not HTML gibberish. Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0693a83..66be619 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3062,12 +3062,12 @@ sub git_blobdiff { if (defined $file_name) { if (defined $file_parent) { $diffinfo{'status'} = '2'; - $diffinfo{'from_file'} = esc_html($file_parent); - $diffinfo{'to_file'} = esc_html($file_name); + $diffinfo{'from_file'} = $file_parent; + $diffinfo{'to_file'} = $file_name; } else { # assume not renamed $diffinfo{'status'} = '1'; - $diffinfo{'from_file'} = esc_html($file_name); - $diffinfo{'to_file'} = esc_html($file_name); + $diffinfo{'from_file'} = $file_name; + $diffinfo{'to_file'} = $file_name; } } else { # no filename given $diffinfo{'status'} = '2'; @@ -3136,8 +3136,8 @@ sub git_blobdiff { } else { while (my $line = <$fd>) { - $line =~ s!a/($hash|$hash_parent)!a/$diffinfo{'from_file'}!g; - $line =~ s!b/($hash|$hash_parent)!b/$diffinfo{'to_file'}!g; + $line =~ s!a/($hash|$hash_parent)!'a/'.esc_html($diffinfo{'from_file'})!eg; + $line =~ s!b/($hash|$hash_parent)!'b/'.esc_html($diffinfo{'to_file'})!eg; print $line; -- cgit v0.10.2-6-g49f6 From a06f678eb998862ea83b73e46ece32f99132935b Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 24 Sep 2006 19:49:47 -0700 Subject: Deprecate merge-recursive.py This renames merge-recursive written in Python to merge-recursive-old, and makes merge-recur as a synonym to merge-recursive. We do not remove merge-recur yet, but we will remove merge-recur and merge-recursive-old in a few releases down the road. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/.gitignore b/.gitignore index 284db5d..25eb463 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ git-merge-one-file git-merge-ours git-merge-recur git-merge-recursive +git-merge-recursive-old git-merge-resolve git-merge-stupid git-mktag diff --git a/Makefile b/Makefile index 28091d6..c888c81 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,6 @@ all: # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. -# # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you # sufficient guarantee that no collisions between objects will ever happen. @@ -174,7 +172,7 @@ SCRIPT_PERL = \ git-send-email.perl git-svn.perl SCRIPT_PYTHON = \ - git-merge-recursive.py + git-merge-recursive-old.py SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ @@ -199,7 +197,7 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X \ git-pack-redundant$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ - git-merge-recur$X \ + git-merge-recursive$X \ $(EXTRA_PROGRAMS) # Empty... @@ -570,7 +568,8 @@ LIB_OBJS += $(COMPAT_OBJS) export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi +all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \ + git-merge-recur$X all: $(MAKE) -C templates @@ -585,6 +584,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS help.o: common-cmds.h +git-merge-recur$X: git-merge-recursive$X + rm -f $@ && ln git-merge-recursive$X $@ + $(BUILT_INS): git$X rm -f $@ && ln git$X $@ @@ -722,11 +724,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -merge-recursive.o path-list.o: path-list.h -git-merge-recur$X: merge-recursive.o path-list.o $(GITLIBS) - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) - $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) $(DIFF_OBJS): diffcore.h @@ -887,6 +884,7 @@ check-docs:: case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ git-merge-resolve | git-merge-stupid | git-merge-recur | \ + git-merge-recursive-old | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/configure.ac b/configure.ac index 511cac9..b1a5833 100644 --- a/configure.ac +++ b/configure.ac @@ -75,7 +75,6 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) AS_HELP_STRING([--without-python], [don't use python scripts])], @@ -100,7 +99,6 @@ AC_PROG_CC AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. if test -z "$NO_PYTHON"; then if test -z "$PYTHON_PATH"; then diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py new file mode 100755 index 0000000..4039435 --- /dev/null +++ b/git-merge-recursive-old.py @@ -0,0 +1,944 @@ +#!/usr/bin/python +# +# Copyright (C) 2005 Fredrik Kuivinen +# + +import sys +sys.path.append('''@@GIT_PYTHON_PATH@@''') + +import math, random, os, re, signal, tempfile, stat, errno, traceback +from heapq import heappush, heappop +from sets import Set + +from gitMergeCommon import * + +outputIndent = 0 +def output(*args): + sys.stdout.write(' '*outputIndent) + printList(args) + +originalIndexFile = os.environ.get('GIT_INDEX_FILE', + os.environ.get('GIT_DIR', '.git') + '/index') +temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ + '/merge-recursive-tmp-index' +def setupIndex(temporary): + try: + os.unlink(temporaryIndexFile) + except OSError: + pass + if temporary: + newIndex = temporaryIndexFile + else: + newIndex = originalIndexFile + os.environ['GIT_INDEX_FILE'] = newIndex + +# This is a global variable which is used in a number of places but +# only written to in the 'merge' function. + +# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and +# don't update the working directory. +# False => Leave unmerged entries in the cache and update +# the working directory. + +cacheOnly = False + +# The entry point to the merge code +# --------------------------------- + +def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): + '''Merge the commits h1 and h2, return the resulting virtual + commit object and a flag indicating the cleanness of the merge.''' + assert(isinstance(h1, Commit) and isinstance(h2, Commit)) + + global outputIndent + + output('Merging:') + output(h1) + output(h2) + sys.stdout.flush() + + if ancestor: + ca = [ancestor] + else: + assert(isinstance(graph, Graph)) + ca = getCommonAncestors(graph, h1, h2) + output('found', len(ca), 'common ancestor(s):') + for x in ca: + output(x) + sys.stdout.flush() + + mergedCA = ca[0] + for h in ca[1:]: + outputIndent = callDepth+1 + [mergedCA, dummy] = merge(mergedCA, h, + 'Temporary merge branch 1', + 'Temporary merge branch 2', + graph, callDepth+1) + outputIndent = callDepth + assert(isinstance(mergedCA, Commit)) + + global cacheOnly + if callDepth == 0: + setupIndex(False) + cacheOnly = False + else: + setupIndex(True) + runProgram(['git-read-tree', h1.tree()]) + cacheOnly = True + + [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), + branch1Name, branch2Name) + + if graph and (clean or cacheOnly): + res = Commit(None, [h1, h2], tree=shaRes) + graph.addNode(res) + else: + res = None + + return [res, clean] + +getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) +def getFilesAndDirs(tree): + files = Set() + dirs = Set() + out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) + for l in out.split('\0'): + m = getFilesRE.match(l) + if m: + if m.group(2) == 'tree': + dirs.add(m.group(4)) + elif m.group(2) == 'blob': + files.add(m.group(4)) + + return [files, dirs] + +# Those two global variables are used in a number of places but only +# written to in 'mergeTrees' and 'uniquePath'. They keep track of +# every file and directory in the two branches that are about to be +# merged. +currentFileSet = None +currentDirectorySet = None + +def mergeTrees(head, merge, common, branch1Name, branch2Name): + '''Merge the trees 'head' and 'merge' with the common ancestor + 'common'. The name of the head branch is 'branch1Name' and the name of + the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) + where tree is the resulting tree and cleanMerge is True iff the + merge was clean.''' + + assert(isSha(head) and isSha(merge) and isSha(common)) + + if common == merge: + output('Already uptodate!') + return [head, True] + + if cacheOnly: + updateArg = '-i' + else: + updateArg = '-u' + + [out, code] = runProgram(['git-read-tree', updateArg, '-m', + common, head, merge], returnCode = True) + if code != 0: + die('git-read-tree:', out) + + [tree, code] = runProgram('git-write-tree', returnCode=True) + tree = tree.rstrip() + if code != 0: + global currentFileSet, currentDirectorySet + [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) + [filesM, dirsM] = getFilesAndDirs(merge) + currentFileSet.union_update(filesM) + currentDirectorySet.union_update(dirsM) + + entries = unmergedCacheEntries() + renamesHead = getRenames(head, common, head, merge, entries) + renamesMerge = getRenames(merge, common, head, merge, entries) + + cleanMerge = processRenames(renamesHead, renamesMerge, + branch1Name, branch2Name) + for entry in entries: + if entry.processed: + continue + if not processEntry(entry, branch1Name, branch2Name): + cleanMerge = False + + if cleanMerge or cacheOnly: + tree = runProgram('git-write-tree').rstrip() + else: + tree = None + else: + cleanMerge = True + + return [tree, cleanMerge] + +# Low level file merging, update and removal +# ------------------------------------------ + +def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, + branch1Name, branch2Name): + + merge = False + clean = True + + if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): + clean = False + if stat.S_ISREG(aMode): + mode = aMode + sha = aSha + else: + mode = bMode + sha = bSha + else: + if aSha != oSha and bSha != oSha: + merge = True + + if aMode == oMode: + mode = bMode + else: + mode = aMode + + if aSha == oSha: + sha = bSha + elif bSha == oSha: + sha = aSha + elif stat.S_ISREG(aMode): + assert(stat.S_ISREG(bMode)) + + orig = runProgram(['git-unpack-file', oSha]).rstrip() + src1 = runProgram(['git-unpack-file', aSha]).rstrip() + src2 = runProgram(['git-unpack-file', bSha]).rstrip() + try: + [out, code] = runProgram(['merge', + '-L', branch1Name + '/' + aPath, + '-L', 'orig/' + oPath, + '-L', branch2Name + '/' + bPath, + src1, orig, src2], returnCode=True) + except ProgramError, e: + print >>sys.stderr, e + die("Failed to execute 'merge'. merge(1) is used as the " + "file-level merge tool. Is 'merge' in your path?") + + sha = runProgram(['git-hash-object', '-t', 'blob', '-w', + src1]).rstrip() + + os.unlink(orig) + os.unlink(src1) + os.unlink(src2) + + clean = (code == 0) + else: + assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) + sha = aSha + + if aSha != bSha: + clean = False + + return [sha, mode, clean, merge] + +def updateFile(clean, sha, mode, path): + updateCache = cacheOnly or clean + updateWd = not cacheOnly + + return updateFileExt(sha, mode, path, updateCache, updateWd) + +def updateFileExt(sha, mode, path, updateCache, updateWd): + if cacheOnly: + updateWd = False + + if updateWd: + pathComponents = path.split('/') + for x in xrange(1, len(pathComponents)): + p = '/'.join(pathComponents[0:x]) + + try: + createDir = not stat.S_ISDIR(os.lstat(p).st_mode) + except OSError: + createDir = True + + if createDir: + try: + os.mkdir(p) + except OSError, e: + die("Couldn't create directory", p, e.strerror) + + prog = ['git-cat-file', 'blob', sha] + if stat.S_ISREG(mode): + try: + os.unlink(path) + except OSError: + pass + if mode & 0100: + mode = 0777 + else: + mode = 0666 + fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) + proc = subprocess.Popen(prog, stdout=fd) + proc.wait() + os.close(fd) + elif stat.S_ISLNK(mode): + linkTarget = runProgram(prog) + os.symlink(linkTarget, path) + else: + assert(False) + + if updateWd and updateCache: + runProgram(['git-update-index', '--add', '--', path]) + elif updateCache: + runProgram(['git-update-index', '--add', '--cacheinfo', + '0%o' % mode, sha, path]) + +def setIndexStages(path, + oSHA1, oMode, + aSHA1, aMode, + bSHA1, bMode, + clear=True): + istring = [] + if clear: + istring.append("0 " + ("0" * 40) + "\t" + path + "\0") + if oMode: + istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) + if aMode: + istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) + if bMode: + istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) + + runProgram(['git-update-index', '-z', '--index-info'], + input="".join(istring)) + +def removeFile(clean, path): + updateCache = cacheOnly or clean + updateWd = not cacheOnly + + if updateCache: + runProgram(['git-update-index', '--force-remove', '--', path]) + + if updateWd: + try: + os.unlink(path) + except OSError, e: + if e.errno != errno.ENOENT and e.errno != errno.EISDIR: + raise + try: + os.removedirs(os.path.dirname(path)) + except OSError: + pass + +def uniquePath(path, branch): + def fileExists(path): + try: + os.lstat(path) + return True + except OSError, e: + if e.errno == errno.ENOENT: + return False + else: + raise + + branch = branch.replace('/', '_') + newPath = path + '~' + branch + suffix = 0 + while newPath in currentFileSet or \ + newPath in currentDirectorySet or \ + fileExists(newPath): + suffix += 1 + newPath = path + '~' + branch + '_' + str(suffix) + currentFileSet.add(newPath) + return newPath + +# Cache entry management +# ---------------------- + +class CacheEntry: + def __init__(self, path): + class Stage: + def __init__(self): + self.sha1 = None + self.mode = None + + # Used for debugging only + def __str__(self): + if self.mode != None: + m = '0%o' % self.mode + else: + m = 'None' + + if self.sha1: + sha1 = self.sha1 + else: + sha1 = 'None' + return 'sha1: ' + sha1 + ' mode: ' + m + + self.stages = [Stage(), Stage(), Stage(), Stage()] + self.path = path + self.processed = False + + def __str__(self): + return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) + +class CacheEntryContainer: + def __init__(self): + self.entries = {} + + def add(self, entry): + self.entries[entry.path] = entry + + def get(self, path): + return self.entries.get(path) + + def __iter__(self): + return self.entries.itervalues() + +unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) +def unmergedCacheEntries(): + '''Create a dictionary mapping file names to CacheEntry + objects. The dictionary contains one entry for every path with a + non-zero stage entry.''' + + lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') + lines.pop() + + res = CacheEntryContainer() + for l in lines: + m = unmergedRE.match(l) + if m: + mode = int(m.group(1), 8) + sha1 = m.group(2) + stage = int(m.group(3)) + path = m.group(4) + + e = res.get(path) + if not e: + e = CacheEntry(path) + res.add(e) + + e.stages[stage].mode = mode + e.stages[stage].sha1 = sha1 + else: + die('Error: Merge program failed: Unexpected output from', + 'git-ls-files:', l) + return res + +lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) +def getCacheEntry(path, origTree, aTree, bTree): + '''Returns a CacheEntry object which doesn't have to correspond to + a real cache entry in Git's index.''' + + def parse(out): + if out == '': + return [None, None] + else: + m = lsTreeRE.match(out) + if not m: + die('Unexpected output from git-ls-tree:', out) + elif m.group(2) == 'blob': + return [m.group(3), int(m.group(1), 8)] + else: + return [None, None] + + res = CacheEntry(path) + + [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) + [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) + [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) + + res.stages[1].sha1 = oSha + res.stages[1].mode = oMode + res.stages[2].sha1 = aSha + res.stages[2].mode = aMode + res.stages[3].sha1 = bSha + res.stages[3].mode = bMode + + return res + +# Rename detection and handling +# ----------------------------- + +class RenameEntry: + def __init__(self, + src, srcSha, srcMode, srcCacheEntry, + dst, dstSha, dstMode, dstCacheEntry, + score): + self.srcName = src + self.srcSha = srcSha + self.srcMode = srcMode + self.srcCacheEntry = srcCacheEntry + self.dstName = dst + self.dstSha = dstSha + self.dstMode = dstMode + self.dstCacheEntry = dstCacheEntry + self.score = score + + self.processed = False + +class RenameEntryContainer: + def __init__(self): + self.entriesSrc = {} + self.entriesDst = {} + + def add(self, entry): + self.entriesSrc[entry.srcName] = entry + self.entriesDst[entry.dstName] = entry + + def getSrc(self, path): + return self.entriesSrc.get(path) + + def getDst(self, path): + return self.entriesDst.get(path) + + def __iter__(self): + return self.entriesSrc.itervalues() + +parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') +def getRenames(tree, oTree, aTree, bTree, cacheEntries): + '''Get information of all renames which occured between 'oTree' and + 'tree'. We need the three trees in the merge ('oTree', 'aTree' and + 'bTree') to be able to associate the correct cache entries with + the rename information. 'tree' is always equal to either aTree or bTree.''' + + assert(tree == aTree or tree == bTree) + inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', + '-z', oTree, tree]) + + ret = RenameEntryContainer() + try: + recs = inp.split("\0") + recs.pop() # remove last entry (which is '') + it = recs.__iter__() + while True: + rec = it.next() + m = parseDiffRenamesRE.match(rec) + + if not m: + die('Unexpected output from git-diff-tree:', rec) + + srcMode = int(m.group(1), 8) + dstMode = int(m.group(2), 8) + srcSha = m.group(3) + dstSha = m.group(4) + score = m.group(5) + src = it.next() + dst = it.next() + + srcCacheEntry = cacheEntries.get(src) + if not srcCacheEntry: + srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) + cacheEntries.add(srcCacheEntry) + + dstCacheEntry = cacheEntries.get(dst) + if not dstCacheEntry: + dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) + cacheEntries.add(dstCacheEntry) + + ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, + dst, dstSha, dstMode, dstCacheEntry, + score)) + except StopIteration: + pass + return ret + +def fmtRename(src, dst): + srcPath = src.split('/') + dstPath = dst.split('/') + path = [] + endIndex = min(len(srcPath), len(dstPath)) - 1 + for x in range(0, endIndex): + if srcPath[x] == dstPath[x]: + path.append(srcPath[x]) + else: + endIndex = x + break + + if len(path) > 0: + return '/'.join(path) + \ + '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ + '/'.join(dstPath[endIndex:]) + '}' + else: + return src + ' => ' + dst + +def processRenames(renamesA, renamesB, branchNameA, branchNameB): + srcNames = Set() + for x in renamesA: + srcNames.add(x.srcName) + for x in renamesB: + srcNames.add(x.srcName) + + cleanMerge = True + for path in srcNames: + if renamesA.getSrc(path): + renames1 = renamesA + renames2 = renamesB + branchName1 = branchNameA + branchName2 = branchNameB + else: + renames1 = renamesB + renames2 = renamesA + branchName1 = branchNameB + branchName2 = branchNameA + + ren1 = renames1.getSrc(path) + ren2 = renames2.getSrc(path) + + ren1.dstCacheEntry.processed = True + ren1.srcCacheEntry.processed = True + + if ren1.processed: + continue + + ren1.processed = True + + if ren2: + # Renamed in 1 and renamed in 2 + assert(ren1.srcName == ren2.srcName) + ren2.dstCacheEntry.processed = True + ren2.processed = True + + if ren1.dstName != ren2.dstName: + output('CONFLICT (rename/rename): Rename', + fmtRename(path, ren1.dstName), 'in branch', branchName1, + 'rename', fmtRename(path, ren2.dstName), 'in', + branchName2) + cleanMerge = False + + if ren1.dstName in currentDirectorySet: + dstName1 = uniquePath(ren1.dstName, branchName1) + output(ren1.dstName, 'is a directory in', branchName2, + 'adding as', dstName1, 'instead.') + removeFile(False, ren1.dstName) + else: + dstName1 = ren1.dstName + + if ren2.dstName in currentDirectorySet: + dstName2 = uniquePath(ren2.dstName, branchName2) + output(ren2.dstName, 'is a directory in', branchName1, + 'adding as', dstName2, 'instead.') + removeFile(False, ren2.dstName) + else: + dstName2 = ren2.dstName + setIndexStages(dstName1, + None, None, + ren1.dstSha, ren1.dstMode, + None, None) + setIndexStages(dstName2, + None, None, + None, None, + ren2.dstSha, ren2.dstMode) + + else: + removeFile(True, ren1.srcName) + + [resSha, resMode, clean, merge] = \ + mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, + ren1.dstName, ren1.dstSha, ren1.dstMode, + ren2.dstName, ren2.dstSha, ren2.dstMode, + branchName1, branchName2) + + if merge or not clean: + output('Renaming', fmtRename(path, ren1.dstName)) + + if merge: + output('Auto-merging', ren1.dstName) + + if not clean: + output('CONFLICT (content): merge conflict in', + ren1.dstName) + cleanMerge = False + + if not cacheOnly: + setIndexStages(ren1.dstName, + ren1.srcSha, ren1.srcMode, + ren1.dstSha, ren1.dstMode, + ren2.dstSha, ren2.dstMode) + + updateFile(clean, resSha, resMode, ren1.dstName) + else: + removeFile(True, ren1.srcName) + + # Renamed in 1, maybe changed in 2 + if renamesA == renames1: + stage = 3 + else: + stage = 2 + + srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 + srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode + + dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 + dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode + + tryMerge = False + + if ren1.dstName in currentDirectorySet: + newPath = uniquePath(ren1.dstName, branchName1) + output('CONFLICT (rename/directory): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, + 'directory', ren1.dstName, 'added in', branchName2) + output('Renaming', ren1.srcName, 'to', newPath, 'instead') + cleanMerge = False + removeFile(False, ren1.dstName) + updateFile(False, ren1.dstSha, ren1.dstMode, newPath) + elif srcShaOtherBranch == None: + output('CONFLICT (rename/delete): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1, 'and deleted in', branchName2) + cleanMerge = False + updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) + elif dstShaOtherBranch: + newPath = uniquePath(ren1.dstName, branchName2) + output('CONFLICT (rename/add): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1 + '.', ren1.dstName, 'added in', branchName2) + output('Adding as', newPath, 'instead') + updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) + cleanMerge = False + tryMerge = True + elif renames2.getDst(ren1.dstName): + dst2 = renames2.getDst(ren1.dstName) + newPath1 = uniquePath(ren1.dstName, branchName1) + newPath2 = uniquePath(dst2.dstName, branchName2) + output('CONFLICT (rename/rename): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1+'. Rename', + fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) + output('Renaming', ren1.srcName, 'to', newPath1, 'and', + dst2.srcName, 'to', newPath2, 'instead') + removeFile(False, ren1.dstName) + updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) + updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) + dst2.processed = True + cleanMerge = False + else: + tryMerge = True + + if tryMerge: + + oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode + aName, bName = ren1.dstName, ren1.srcName + aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch + aMode, bMode = ren1.dstMode, srcModeOtherBranch + aBranch, bBranch = branchName1, branchName2 + + if renamesA != renames1: + aName, bName = bName, aName + aSHA1, bSHA1 = bSHA1, aSHA1 + aMode, bMode = bMode, aMode + aBranch, bBranch = bBranch, aBranch + + [resSha, resMode, clean, merge] = \ + mergeFile(oName, oSHA1, oMode, + aName, aSHA1, aMode, + bName, bSHA1, bMode, + aBranch, bBranch); + + if merge or not clean: + output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) + + if merge: + output('Auto-merging', ren1.dstName) + + if not clean: + output('CONFLICT (rename/modify): Merge conflict in', + ren1.dstName) + cleanMerge = False + + if not cacheOnly: + setIndexStages(ren1.dstName, + oSHA1, oMode, + aSHA1, aMode, + bSHA1, bMode) + + updateFile(clean, resSha, resMode, ren1.dstName) + + return cleanMerge + +# Per entry merge function +# ------------------------ + +def processEntry(entry, branch1Name, branch2Name): + '''Merge one cache entry.''' + + debug('processing', entry.path, 'clean cache:', cacheOnly) + + cleanMerge = True + + path = entry.path + oSha = entry.stages[1].sha1 + oMode = entry.stages[1].mode + aSha = entry.stages[2].sha1 + aMode = entry.stages[2].mode + bSha = entry.stages[3].sha1 + bMode = entry.stages[3].mode + + assert(oSha == None or isSha(oSha)) + assert(aSha == None or isSha(aSha)) + assert(bSha == None or isSha(bSha)) + + assert(oMode == None or type(oMode) is int) + assert(aMode == None or type(aMode) is int) + assert(bMode == None or type(bMode) is int) + + if (oSha and (not aSha or not bSha)): + # + # Case A: Deleted in one + # + if (not aSha and not bSha) or \ + (aSha == oSha and not bSha) or \ + (not aSha and bSha == oSha): + # Deleted in both or deleted in one and unchanged in the other + if aSha: + output('Removing', path) + removeFile(True, path) + else: + # Deleted in one and changed in the other + cleanMerge = False + if not aSha: + output('CONFLICT (delete/modify):', path, 'deleted in', + branch1Name, 'and modified in', branch2Name + '.', + 'Version', branch2Name, 'of', path, 'left in tree.') + mode = bMode + sha = bSha + else: + output('CONFLICT (modify/delete):', path, 'deleted in', + branch2Name, 'and modified in', branch1Name + '.', + 'Version', branch1Name, 'of', path, 'left in tree.') + mode = aMode + sha = aSha + + updateFile(False, sha, mode, path) + + elif (not oSha and aSha and not bSha) or \ + (not oSha and not aSha and bSha): + # + # Case B: Added in one. + # + if aSha: + addBranch = branch1Name + otherBranch = branch2Name + mode = aMode + sha = aSha + conf = 'file/directory' + else: + addBranch = branch2Name + otherBranch = branch1Name + mode = bMode + sha = bSha + conf = 'directory/file' + + if path in currentDirectorySet: + cleanMerge = False + newPath = uniquePath(path, addBranch) + output('CONFLICT (' + conf + '):', + 'There is a directory with name', path, 'in', + otherBranch + '. Adding', path, 'as', newPath) + + removeFile(False, path) + updateFile(False, sha, mode, newPath) + else: + output('Adding', path) + updateFile(True, sha, mode, path) + + elif not oSha and aSha and bSha: + # + # Case C: Added in both (check for same permissions). + # + if aSha == bSha: + if aMode != bMode: + cleanMerge = False + output('CONFLICT: File', path, + 'added identically in both branches, but permissions', + 'conflict', '0%o' % aMode, '->', '0%o' % bMode) + output('CONFLICT: adding with permission:', '0%o' % aMode) + + updateFile(False, aSha, aMode, path) + else: + # This case is handled by git-read-tree + assert(False) + else: + cleanMerge = False + newPath1 = uniquePath(path, branch1Name) + newPath2 = uniquePath(path, branch2Name) + output('CONFLICT (add/add): File', path, + 'added non-identically in both branches. Adding as', + newPath1, 'and', newPath2, 'instead.') + removeFile(False, path) + updateFile(False, aSha, aMode, newPath1) + updateFile(False, bSha, bMode, newPath2) + + elif oSha and aSha and bSha: + # + # case D: Modified in both, but differently. + # + output('Auto-merging', path) + [sha, mode, clean, dummy] = \ + mergeFile(path, oSha, oMode, + path, aSha, aMode, + path, bSha, bMode, + branch1Name, branch2Name) + if clean: + updateFile(True, sha, mode, path) + else: + cleanMerge = False + output('CONFLICT (content): Merge conflict in', path) + + if cacheOnly: + updateFile(False, sha, mode, path) + else: + updateFileExt(sha, mode, path, updateCache=False, updateWd=True) + else: + die("ERROR: Fatal merge failure, shouldn't happen.") + + return cleanMerge + +def usage(): + die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..') + +# main entry point as merge strategy module +# The first parameters up to -- are merge bases, and the rest are heads. + +if len(sys.argv) < 4: + usage() + +bases = [] +for nextArg in xrange(1, len(sys.argv)): + if sys.argv[nextArg] == '--': + if len(sys.argv) != nextArg + 3: + die('Not handling anything other than two heads merge.') + try: + h1 = firstBranch = sys.argv[nextArg + 1] + h2 = secondBranch = sys.argv[nextArg + 2] + except IndexError: + usage() + break + else: + bases.append(sys.argv[nextArg]) + +print 'Merging', h1, 'with', h2 + +try: + h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() + h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() + + if len(bases) == 1: + base = runProgram(['git-rev-parse', '--verify', + bases[0] + '^0']).rstrip() + ancestor = Commit(base, None) + [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), + firstBranch, secondBranch, None, 0, + ancestor) + else: + graph = buildGraph([h1, h2]) + [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], + firstBranch, secondBranch, graph) + + print '' +except: + if isinstance(sys.exc_info()[1], SystemExit): + raise + else: + traceback.print_exc(None, sys.stderr) + sys.exit(2) + +if clean: + sys.exit(0) +else: + sys.exit(1) diff --git a/git-merge-recursive.py b/git-merge-recursive.py deleted file mode 100755 index 4039435..0000000 --- a/git-merge-recursive.py +++ /dev/null @@ -1,944 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys -sys.path.append('''@@GIT_PYTHON_PATH@@''') - -import math, random, os, re, signal, tempfile, stat, errno, traceback -from heapq import heappush, heappop -from sets import Set - -from gitMergeCommon import * - -outputIndent = 0 -def output(*args): - sys.stdout.write(' '*outputIndent) - printList(args) - -originalIndexFile = os.environ.get('GIT_INDEX_FILE', - os.environ.get('GIT_DIR', '.git') + '/index') -temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ - '/merge-recursive-tmp-index' -def setupIndex(temporary): - try: - os.unlink(temporaryIndexFile) - except OSError: - pass - if temporary: - newIndex = temporaryIndexFile - else: - newIndex = originalIndexFile - os.environ['GIT_INDEX_FILE'] = newIndex - -# This is a global variable which is used in a number of places but -# only written to in the 'merge' function. - -# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and -# don't update the working directory. -# False => Leave unmerged entries in the cache and update -# the working directory. - -cacheOnly = False - -# The entry point to the merge code -# --------------------------------- - -def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): - '''Merge the commits h1 and h2, return the resulting virtual - commit object and a flag indicating the cleanness of the merge.''' - assert(isinstance(h1, Commit) and isinstance(h2, Commit)) - - global outputIndent - - output('Merging:') - output(h1) - output(h2) - sys.stdout.flush() - - if ancestor: - ca = [ancestor] - else: - assert(isinstance(graph, Graph)) - ca = getCommonAncestors(graph, h1, h2) - output('found', len(ca), 'common ancestor(s):') - for x in ca: - output(x) - sys.stdout.flush() - - mergedCA = ca[0] - for h in ca[1:]: - outputIndent = callDepth+1 - [mergedCA, dummy] = merge(mergedCA, h, - 'Temporary merge branch 1', - 'Temporary merge branch 2', - graph, callDepth+1) - outputIndent = callDepth - assert(isinstance(mergedCA, Commit)) - - global cacheOnly - if callDepth == 0: - setupIndex(False) - cacheOnly = False - else: - setupIndex(True) - runProgram(['git-read-tree', h1.tree()]) - cacheOnly = True - - [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), - branch1Name, branch2Name) - - if graph and (clean or cacheOnly): - res = Commit(None, [h1, h2], tree=shaRes) - graph.addNode(res) - else: - res = None - - return [res, clean] - -getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) -def getFilesAndDirs(tree): - files = Set() - dirs = Set() - out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) - for l in out.split('\0'): - m = getFilesRE.match(l) - if m: - if m.group(2) == 'tree': - dirs.add(m.group(4)) - elif m.group(2) == 'blob': - files.add(m.group(4)) - - return [files, dirs] - -# Those two global variables are used in a number of places but only -# written to in 'mergeTrees' and 'uniquePath'. They keep track of -# every file and directory in the two branches that are about to be -# merged. -currentFileSet = None -currentDirectorySet = None - -def mergeTrees(head, merge, common, branch1Name, branch2Name): - '''Merge the trees 'head' and 'merge' with the common ancestor - 'common'. The name of the head branch is 'branch1Name' and the name of - the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) - where tree is the resulting tree and cleanMerge is True iff the - merge was clean.''' - - assert(isSha(head) and isSha(merge) and isSha(common)) - - if common == merge: - output('Already uptodate!') - return [head, True] - - if cacheOnly: - updateArg = '-i' - else: - updateArg = '-u' - - [out, code] = runProgram(['git-read-tree', updateArg, '-m', - common, head, merge], returnCode = True) - if code != 0: - die('git-read-tree:', out) - - [tree, code] = runProgram('git-write-tree', returnCode=True) - tree = tree.rstrip() - if code != 0: - global currentFileSet, currentDirectorySet - [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) - [filesM, dirsM] = getFilesAndDirs(merge) - currentFileSet.union_update(filesM) - currentDirectorySet.union_update(dirsM) - - entries = unmergedCacheEntries() - renamesHead = getRenames(head, common, head, merge, entries) - renamesMerge = getRenames(merge, common, head, merge, entries) - - cleanMerge = processRenames(renamesHead, renamesMerge, - branch1Name, branch2Name) - for entry in entries: - if entry.processed: - continue - if not processEntry(entry, branch1Name, branch2Name): - cleanMerge = False - - if cleanMerge or cacheOnly: - tree = runProgram('git-write-tree').rstrip() - else: - tree = None - else: - cleanMerge = True - - return [tree, cleanMerge] - -# Low level file merging, update and removal -# ------------------------------------------ - -def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, - branch1Name, branch2Name): - - merge = False - clean = True - - if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): - clean = False - if stat.S_ISREG(aMode): - mode = aMode - sha = aSha - else: - mode = bMode - sha = bSha - else: - if aSha != oSha and bSha != oSha: - merge = True - - if aMode == oMode: - mode = bMode - else: - mode = aMode - - if aSha == oSha: - sha = bSha - elif bSha == oSha: - sha = aSha - elif stat.S_ISREG(aMode): - assert(stat.S_ISREG(bMode)) - - orig = runProgram(['git-unpack-file', oSha]).rstrip() - src1 = runProgram(['git-unpack-file', aSha]).rstrip() - src2 = runProgram(['git-unpack-file', bSha]).rstrip() - try: - [out, code] = runProgram(['merge', - '-L', branch1Name + '/' + aPath, - '-L', 'orig/' + oPath, - '-L', branch2Name + '/' + bPath, - src1, orig, src2], returnCode=True) - except ProgramError, e: - print >>sys.stderr, e - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?") - - sha = runProgram(['git-hash-object', '-t', 'blob', '-w', - src1]).rstrip() - - os.unlink(orig) - os.unlink(src1) - os.unlink(src2) - - clean = (code == 0) - else: - assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) - sha = aSha - - if aSha != bSha: - clean = False - - return [sha, mode, clean, merge] - -def updateFile(clean, sha, mode, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - return updateFileExt(sha, mode, path, updateCache, updateWd) - -def updateFileExt(sha, mode, path, updateCache, updateWd): - if cacheOnly: - updateWd = False - - if updateWd: - pathComponents = path.split('/') - for x in xrange(1, len(pathComponents)): - p = '/'.join(pathComponents[0:x]) - - try: - createDir = not stat.S_ISDIR(os.lstat(p).st_mode) - except OSError: - createDir = True - - if createDir: - try: - os.mkdir(p) - except OSError, e: - die("Couldn't create directory", p, e.strerror) - - prog = ['git-cat-file', 'blob', sha] - if stat.S_ISREG(mode): - try: - os.unlink(path) - except OSError: - pass - if mode & 0100: - mode = 0777 - else: - mode = 0666 - fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) - proc = subprocess.Popen(prog, stdout=fd) - proc.wait() - os.close(fd) - elif stat.S_ISLNK(mode): - linkTarget = runProgram(prog) - os.symlink(linkTarget, path) - else: - assert(False) - - if updateWd and updateCache: - runProgram(['git-update-index', '--add', '--', path]) - elif updateCache: - runProgram(['git-update-index', '--add', '--cacheinfo', - '0%o' % mode, sha, path]) - -def setIndexStages(path, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode, - clear=True): - istring = [] - if clear: - istring.append("0 " + ("0" * 40) + "\t" + path + "\0") - if oMode: - istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) - if aMode: - istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) - if bMode: - istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) - - runProgram(['git-update-index', '-z', '--index-info'], - input="".join(istring)) - -def removeFile(clean, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - if updateCache: - runProgram(['git-update-index', '--force-remove', '--', path]) - - if updateWd: - try: - os.unlink(path) - except OSError, e: - if e.errno != errno.ENOENT and e.errno != errno.EISDIR: - raise - try: - os.removedirs(os.path.dirname(path)) - except OSError: - pass - -def uniquePath(path, branch): - def fileExists(path): - try: - os.lstat(path) - return True - except OSError, e: - if e.errno == errno.ENOENT: - return False - else: - raise - - branch = branch.replace('/', '_') - newPath = path + '~' + branch - suffix = 0 - while newPath in currentFileSet or \ - newPath in currentDirectorySet or \ - fileExists(newPath): - suffix += 1 - newPath = path + '~' + branch + '_' + str(suffix) - currentFileSet.add(newPath) - return newPath - -# Cache entry management -# ---------------------- - -class CacheEntry: - def __init__(self, path): - class Stage: - def __init__(self): - self.sha1 = None - self.mode = None - - # Used for debugging only - def __str__(self): - if self.mode != None: - m = '0%o' % self.mode - else: - m = 'None' - - if self.sha1: - sha1 = self.sha1 - else: - sha1 = 'None' - return 'sha1: ' + sha1 + ' mode: ' + m - - self.stages = [Stage(), Stage(), Stage(), Stage()] - self.path = path - self.processed = False - - def __str__(self): - return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) - -class CacheEntryContainer: - def __init__(self): - self.entries = {} - - def add(self, entry): - self.entries[entry.path] = entry - - def get(self, path): - return self.entries.get(path) - - def __iter__(self): - return self.entries.itervalues() - -unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) -def unmergedCacheEntries(): - '''Create a dictionary mapping file names to CacheEntry - objects. The dictionary contains one entry for every path with a - non-zero stage entry.''' - - lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') - lines.pop() - - res = CacheEntryContainer() - for l in lines: - m = unmergedRE.match(l) - if m: - mode = int(m.group(1), 8) - sha1 = m.group(2) - stage = int(m.group(3)) - path = m.group(4) - - e = res.get(path) - if not e: - e = CacheEntry(path) - res.add(e) - - e.stages[stage].mode = mode - e.stages[stage].sha1 = sha1 - else: - die('Error: Merge program failed: Unexpected output from', - 'git-ls-files:', l) - return res - -lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) -def getCacheEntry(path, origTree, aTree, bTree): - '''Returns a CacheEntry object which doesn't have to correspond to - a real cache entry in Git's index.''' - - def parse(out): - if out == '': - return [None, None] - else: - m = lsTreeRE.match(out) - if not m: - die('Unexpected output from git-ls-tree:', out) - elif m.group(2) == 'blob': - return [m.group(3), int(m.group(1), 8)] - else: - return [None, None] - - res = CacheEntry(path) - - [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) - [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) - [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) - - res.stages[1].sha1 = oSha - res.stages[1].mode = oMode - res.stages[2].sha1 = aSha - res.stages[2].mode = aMode - res.stages[3].sha1 = bSha - res.stages[3].mode = bMode - - return res - -# Rename detection and handling -# ----------------------------- - -class RenameEntry: - def __init__(self, - src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score): - self.srcName = src - self.srcSha = srcSha - self.srcMode = srcMode - self.srcCacheEntry = srcCacheEntry - self.dstName = dst - self.dstSha = dstSha - self.dstMode = dstMode - self.dstCacheEntry = dstCacheEntry - self.score = score - - self.processed = False - -class RenameEntryContainer: - def __init__(self): - self.entriesSrc = {} - self.entriesDst = {} - - def add(self, entry): - self.entriesSrc[entry.srcName] = entry - self.entriesDst[entry.dstName] = entry - - def getSrc(self, path): - return self.entriesSrc.get(path) - - def getDst(self, path): - return self.entriesDst.get(path) - - def __iter__(self): - return self.entriesSrc.itervalues() - -parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') -def getRenames(tree, oTree, aTree, bTree, cacheEntries): - '''Get information of all renames which occured between 'oTree' and - 'tree'. We need the three trees in the merge ('oTree', 'aTree' and - 'bTree') to be able to associate the correct cache entries with - the rename information. 'tree' is always equal to either aTree or bTree.''' - - assert(tree == aTree or tree == bTree) - inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', - '-z', oTree, tree]) - - ret = RenameEntryContainer() - try: - recs = inp.split("\0") - recs.pop() # remove last entry (which is '') - it = recs.__iter__() - while True: - rec = it.next() - m = parseDiffRenamesRE.match(rec) - - if not m: - die('Unexpected output from git-diff-tree:', rec) - - srcMode = int(m.group(1), 8) - dstMode = int(m.group(2), 8) - srcSha = m.group(3) - dstSha = m.group(4) - score = m.group(5) - src = it.next() - dst = it.next() - - srcCacheEntry = cacheEntries.get(src) - if not srcCacheEntry: - srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) - cacheEntries.add(srcCacheEntry) - - dstCacheEntry = cacheEntries.get(dst) - if not dstCacheEntry: - dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) - cacheEntries.add(dstCacheEntry) - - ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score)) - except StopIteration: - pass - return ret - -def fmtRename(src, dst): - srcPath = src.split('/') - dstPath = dst.split('/') - path = [] - endIndex = min(len(srcPath), len(dstPath)) - 1 - for x in range(0, endIndex): - if srcPath[x] == dstPath[x]: - path.append(srcPath[x]) - else: - endIndex = x - break - - if len(path) > 0: - return '/'.join(path) + \ - '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ - '/'.join(dstPath[endIndex:]) + '}' - else: - return src + ' => ' + dst - -def processRenames(renamesA, renamesB, branchNameA, branchNameB): - srcNames = Set() - for x in renamesA: - srcNames.add(x.srcName) - for x in renamesB: - srcNames.add(x.srcName) - - cleanMerge = True - for path in srcNames: - if renamesA.getSrc(path): - renames1 = renamesA - renames2 = renamesB - branchName1 = branchNameA - branchName2 = branchNameB - else: - renames1 = renamesB - renames2 = renamesA - branchName1 = branchNameB - branchName2 = branchNameA - - ren1 = renames1.getSrc(path) - ren2 = renames2.getSrc(path) - - ren1.dstCacheEntry.processed = True - ren1.srcCacheEntry.processed = True - - if ren1.processed: - continue - - ren1.processed = True - - if ren2: - # Renamed in 1 and renamed in 2 - assert(ren1.srcName == ren2.srcName) - ren2.dstCacheEntry.processed = True - ren2.processed = True - - if ren1.dstName != ren2.dstName: - output('CONFLICT (rename/rename): Rename', - fmtRename(path, ren1.dstName), 'in branch', branchName1, - 'rename', fmtRename(path, ren2.dstName), 'in', - branchName2) - cleanMerge = False - - if ren1.dstName in currentDirectorySet: - dstName1 = uniquePath(ren1.dstName, branchName1) - output(ren1.dstName, 'is a directory in', branchName2, - 'adding as', dstName1, 'instead.') - removeFile(False, ren1.dstName) - else: - dstName1 = ren1.dstName - - if ren2.dstName in currentDirectorySet: - dstName2 = uniquePath(ren2.dstName, branchName2) - output(ren2.dstName, 'is a directory in', branchName1, - 'adding as', dstName2, 'instead.') - removeFile(False, ren2.dstName) - else: - dstName2 = ren2.dstName - setIndexStages(dstName1, - None, None, - ren1.dstSha, ren1.dstMode, - None, None) - setIndexStages(dstName2, - None, None, - None, None, - ren2.dstSha, ren2.dstMode) - - else: - removeFile(True, ren1.srcName) - - [resSha, resMode, clean, merge] = \ - mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, - ren1.dstName, ren1.dstSha, ren1.dstMode, - ren2.dstName, ren2.dstSha, ren2.dstMode, - branchName1, branchName2) - - if merge or not clean: - output('Renaming', fmtRename(path, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (content): merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - ren1.srcSha, ren1.srcMode, - ren1.dstSha, ren1.dstMode, - ren2.dstSha, ren2.dstMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - else: - removeFile(True, ren1.srcName) - - # Renamed in 1, maybe changed in 2 - if renamesA == renames1: - stage = 3 - else: - stage = 2 - - srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 - srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode - - dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 - dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode - - tryMerge = False - - if ren1.dstName in currentDirectorySet: - newPath = uniquePath(ren1.dstName, branchName1) - output('CONFLICT (rename/directory): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, - 'directory', ren1.dstName, 'added in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath, 'instead') - cleanMerge = False - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath) - elif srcShaOtherBranch == None: - output('CONFLICT (rename/delete): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1, 'and deleted in', branchName2) - cleanMerge = False - updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) - elif dstShaOtherBranch: - newPath = uniquePath(ren1.dstName, branchName2) - output('CONFLICT (rename/add): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1 + '.', ren1.dstName, 'added in', branchName2) - output('Adding as', newPath, 'instead') - updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) - cleanMerge = False - tryMerge = True - elif renames2.getDst(ren1.dstName): - dst2 = renames2.getDst(ren1.dstName) - newPath1 = uniquePath(ren1.dstName, branchName1) - newPath2 = uniquePath(dst2.dstName, branchName2) - output('CONFLICT (rename/rename): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1+'. Rename', - fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath1, 'and', - dst2.srcName, 'to', newPath2, 'instead') - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) - updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) - dst2.processed = True - cleanMerge = False - else: - tryMerge = True - - if tryMerge: - - oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode - aName, bName = ren1.dstName, ren1.srcName - aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch - aMode, bMode = ren1.dstMode, srcModeOtherBranch - aBranch, bBranch = branchName1, branchName2 - - if renamesA != renames1: - aName, bName = bName, aName - aSHA1, bSHA1 = bSHA1, aSHA1 - aMode, bMode = bMode, aMode - aBranch, bBranch = bBranch, aBranch - - [resSha, resMode, clean, merge] = \ - mergeFile(oName, oSHA1, oMode, - aName, aSHA1, aMode, - bName, bSHA1, bMode, - aBranch, bBranch); - - if merge or not clean: - output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (rename/modify): Merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - - return cleanMerge - -# Per entry merge function -# ------------------------ - -def processEntry(entry, branch1Name, branch2Name): - '''Merge one cache entry.''' - - debug('processing', entry.path, 'clean cache:', cacheOnly) - - cleanMerge = True - - path = entry.path - oSha = entry.stages[1].sha1 - oMode = entry.stages[1].mode - aSha = entry.stages[2].sha1 - aMode = entry.stages[2].mode - bSha = entry.stages[3].sha1 - bMode = entry.stages[3].mode - - assert(oSha == None or isSha(oSha)) - assert(aSha == None or isSha(aSha)) - assert(bSha == None or isSha(bSha)) - - assert(oMode == None or type(oMode) is int) - assert(aMode == None or type(aMode) is int) - assert(bMode == None or type(bMode) is int) - - if (oSha and (not aSha or not bSha)): - # - # Case A: Deleted in one - # - if (not aSha and not bSha) or \ - (aSha == oSha and not bSha) or \ - (not aSha and bSha == oSha): - # Deleted in both or deleted in one and unchanged in the other - if aSha: - output('Removing', path) - removeFile(True, path) - else: - # Deleted in one and changed in the other - cleanMerge = False - if not aSha: - output('CONFLICT (delete/modify):', path, 'deleted in', - branch1Name, 'and modified in', branch2Name + '.', - 'Version', branch2Name, 'of', path, 'left in tree.') - mode = bMode - sha = bSha - else: - output('CONFLICT (modify/delete):', path, 'deleted in', - branch2Name, 'and modified in', branch1Name + '.', - 'Version', branch1Name, 'of', path, 'left in tree.') - mode = aMode - sha = aSha - - updateFile(False, sha, mode, path) - - elif (not oSha and aSha and not bSha) or \ - (not oSha and not aSha and bSha): - # - # Case B: Added in one. - # - if aSha: - addBranch = branch1Name - otherBranch = branch2Name - mode = aMode - sha = aSha - conf = 'file/directory' - else: - addBranch = branch2Name - otherBranch = branch1Name - mode = bMode - sha = bSha - conf = 'directory/file' - - if path in currentDirectorySet: - cleanMerge = False - newPath = uniquePath(path, addBranch) - output('CONFLICT (' + conf + '):', - 'There is a directory with name', path, 'in', - otherBranch + '. Adding', path, 'as', newPath) - - removeFile(False, path) - updateFile(False, sha, mode, newPath) - else: - output('Adding', path) - updateFile(True, sha, mode, path) - - elif not oSha and aSha and bSha: - # - # Case C: Added in both (check for same permissions). - # - if aSha == bSha: - if aMode != bMode: - cleanMerge = False - output('CONFLICT: File', path, - 'added identically in both branches, but permissions', - 'conflict', '0%o' % aMode, '->', '0%o' % bMode) - output('CONFLICT: adding with permission:', '0%o' % aMode) - - updateFile(False, aSha, aMode, path) - else: - # This case is handled by git-read-tree - assert(False) - else: - cleanMerge = False - newPath1 = uniquePath(path, branch1Name) - newPath2 = uniquePath(path, branch2Name) - output('CONFLICT (add/add): File', path, - 'added non-identically in both branches. Adding as', - newPath1, 'and', newPath2, 'instead.') - removeFile(False, path) - updateFile(False, aSha, aMode, newPath1) - updateFile(False, bSha, bMode, newPath2) - - elif oSha and aSha and bSha: - # - # case D: Modified in both, but differently. - # - output('Auto-merging', path) - [sha, mode, clean, dummy] = \ - mergeFile(path, oSha, oMode, - path, aSha, aMode, - path, bSha, bMode, - branch1Name, branch2Name) - if clean: - updateFile(True, sha, mode, path) - else: - cleanMerge = False - output('CONFLICT (content): Merge conflict in', path) - - if cacheOnly: - updateFile(False, sha, mode, path) - else: - updateFileExt(sha, mode, path, updateCache=False, updateWd=True) - else: - die("ERROR: Fatal merge failure, shouldn't happen.") - - return cleanMerge - -def usage(): - die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..') - -# main entry point as merge strategy module -# The first parameters up to -- are merge bases, and the rest are heads. - -if len(sys.argv) < 4: - usage() - -bases = [] -for nextArg in xrange(1, len(sys.argv)): - if sys.argv[nextArg] == '--': - if len(sys.argv) != nextArg + 3: - die('Not handling anything other than two heads merge.') - try: - h1 = firstBranch = sys.argv[nextArg + 1] - h2 = secondBranch = sys.argv[nextArg + 2] - except IndexError: - usage() - break - else: - bases.append(sys.argv[nextArg]) - -print 'Merging', h1, 'with', h2 - -try: - h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() - h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() - - if len(bases) == 1: - base = runProgram(['git-rev-parse', '--verify', - bases[0] + '^0']).rstrip() - ancestor = Commit(base, None) - [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), - firstBranch, secondBranch, None, 0, - ancestor) - else: - graph = buildGraph([h1, h2]) - [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], - firstBranch, secondBranch, graph) - - print '' -except: - if isinstance(sys.exc_info()[1], SystemExit): - raise - else: - traceback.print_exc(None, sys.stderr) - sys.exit(2) - -if clean: - sys.exit(0) -else: - sys.exit(1) diff --git a/git-merge.sh b/git-merge.sh index d049e16..5b34b4d 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,21 +9,15 @@ USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> < LF=' ' -all_strategies='recursive recur octopus resolve stupid ours' -case "${GIT_USE_RECUR_FOR_RECURSIVE}" in -'') - default_twohead_strategies=recursive ;; -?*) - default_twohead_strategies=recur ;; -esac +all_strategies='recur recursive recursive-old octopus resolve stupid ours' +default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t if test "@@NO_PYTHON@@"; then - all_strategies='recur resolve octopus stupid ours' - default_twohead_strategies='resolve' + all_strategies='recur recursive resolve octopus stupid ours' fi dropsave() { @@ -122,10 +116,6 @@ do strategy="$2" shift ;; esac - case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in - recursive,?*) - strategy=recur ;; - esac case " $all_strategies " in *" $strategy "*) use_strategies="$use_strategies$strategy " ;; diff --git a/git-rebase.sh b/git-rebase.sh index 20f74d4..a7373c0 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -35,13 +35,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase -case "${GIT_USE_RECUR_FOR_RECURSIVE}" in -'') - strategy=recursive ;; -?*) - strategy=recur ;; -esac - +strategy=recursive do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 @@ -206,11 +200,6 @@ do shift done -case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in -recursive,?*) - strategy=recur ;; -esac - # Make sure we do not have .dotest if test -z "$do_merge" then @@ -303,11 +292,11 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recursive" +if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old" then - die 'The recursive merge strategy currently relies on Python, + die 'The recursive-old merge strategy is written in Python, which this installation of git was not configured with. Please consider -a different merge strategy (e.g. octopus, resolve, stupid, ours) +a different merge strategy (e.g. recursive, resolve, or stupid) or install Python and git with Python support.' fi diff --git a/t/test-lib.sh b/t/test-lib.sh index e75ad5f..0fe2718 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -211,7 +211,7 @@ export PATH GIT_EXEC_PATH PYTHON=`sed -e '1{ s/^#!// q -}' ../git-merge-recursive` || { +}' ../git-merge-recursive-old` || { error "You haven't built things yet, have you?" } "$PYTHON" -c 'import subprocess' 2>/dev/null || { -- cgit v0.10.2-6-g49f6 From e8daf78a00fc618f4b8b5b6253580226560f7dec Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu <vagabon.xyz@gmail.com> Date: Mon, 25 Sep 2006 12:31:52 +0200 Subject: git-archive: update documentation This patch documents zip backend options. It also adds git-archive command into the main git manual page. Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 913528d..031fcd5 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -40,6 +40,7 @@ OPTIONS <extra>:: This can be any options that the archiver backend understand. + See next section. --remote=<repo>:: Instead of making a tar archive from local repository, @@ -52,6 +53,18 @@ path:: If one or more paths are specified, include only these in the archive, otherwise include all files and subdirectories. +BACKEND EXTRA OPTIONS +--------------------- + +zip +~~~ +-0:: + Store the files instead of deflating them. +-9:: + Highest and slowest compression level. You can specify any + number from 1 to 9 to adjust compression speed and ratio. + + CONFIGURATION ------------- By default, file and directories modes are set to 0666 or 0777 in tar diff --git a/Documentation/git.txt b/Documentation/git.txt index 1bf5ef5..2135b65 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -266,6 +266,9 @@ gitlink:git-am[1]:: gitlink:git-applymbox[1]:: Apply patches from a mailbox, original version by Linus. +gitlink:git-archive[1]:: + Creates an archive of files from a named tree. + gitlink:git-bisect[1]:: Find the change that introduced a bug by binary search. -- cgit v0.10.2-6-g49f6 From 4dafd7d2444051fe0e1eb17e9ded503ad7d9dd25 Mon Sep 17 00:00:00 2001 From: Alex Riesen <fork0@t-online.de> Date: Mon, 25 Sep 2006 23:19:00 +0200 Subject: Use const for interpolate arguments Signed-off-by: Alex Riesen <raa.lkml@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/interpolate.c b/interpolate.c index d82f1b5..4570c12 100644 --- a/interpolate.c +++ b/interpolate.c @@ -25,10 +25,10 @@ */ int interpolate(char *result, int reslen, - char *orig, - struct interp *interps, int ninterps) + const char *orig, + const struct interp *interps, int ninterps) { - char *src = orig; + const char *src = orig; char *dest = result; int newlen = 0; char *name, *value; diff --git a/interpolate.h b/interpolate.h index 00c63a5..d16f924 100644 --- a/interpolate.h +++ b/interpolate.h @@ -5,6 +5,11 @@ #ifndef INTERPOLATE_H #define INTERPOLATE_H +/* + * Convert a NUL-terminated string in buffer orig, + * performing substitutions on %-named sub-strings from + * the interpretation table. + */ struct interp { char *name; @@ -12,7 +17,7 @@ struct interp { }; extern int interpolate(char *result, int reslen, - char *orig, - struct interp *interps, int ninterps); + const char *orig, + const struct interp *interps, int ninterps); #endif /* INTERPOLATE_H */ -- cgit v0.10.2-6-g49f6 From 4de741b3e1be564233f0de2c6915a859264fe9b0 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Mon, 25 Sep 2006 22:38:16 -0700 Subject: gitweb: tree view: eliminate redundant "blob" Binary and non-binary blobs: The "list" table element of tree view is identical to the "blob" link part of the link table element. I.e. clicking on "blob" is identical to clicking on the entry itself. Thus, eliminate "blob" from being shown -- the user can get identical result by simply clicking on the entry itself. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 66be619..c7ab3b6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1600,34 +1600,35 @@ sub git_print_tree_entry { my %base_key = (); $base_key{hash_base} = $hash_base if defined $hash_base; + # The format of a table row is: mode list link. Where mode is + # the mode of the entry, list is the name of the entry, an href, + # and link is the action links of the entry. + print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n"; if ($t->{'type'} eq "blob") { print "<td class=\"list\">" . - $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key), - -class => "list"}, esc_html($t->{'name'})) . - "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blob"); + $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key), + -class => "list"}, esc_html($t->{'name'})) . "</td>\n"; + print "<td class=\"link\">"; if ($have_blame) { - print " | " . - $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blame"); + print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); } if (defined $hash_base) { - print " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + if ($have_blame) { + print " | "; + } + print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, "history"); } print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, - "raw") . - "</td>\n"; + "raw"); + print "</td>\n"; } elsif ($t->{'type'} eq "tree") { print "<td class=\"list\">" . -- cgit v0.10.2-6-g49f6 From 9c7b0b3fc46e552bde1a65cd1950c3634854332b Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Tue, 26 Sep 2006 07:23:37 +0200 Subject: Remove empty ref directories that prevent creating a ref. This patch also adds test cases from Linus and Junio. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-branch.sh b/git-branch.sh index e0501ec..4f31903 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -112,6 +112,16 @@ rev=$(git-rev-parse --verify "$head") || exit git-check-ref-format "heads/$branchname" || die "we do not like '$branchname' as a branch name." +if [ -d "$GIT_DIR/refs/heads/$branchname" ] +then + for refdir in `cd "$GIT_DIR" && \ + find "refs/heads/$branchname" -type d | sort -r` + do + rmdir "$GIT_DIR/$refdir" || \ + die "Could not delete '$refdir', there may still be a ref there." + done +fi + if [ -e "$GIT_DIR/refs/heads/$branchname" ] then if test '' = "$force" diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 5b04efc..6907cbc 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -61,4 +61,16 @@ test_expect_success \ test -f .git/logs/refs/heads/g/h/i && diff expect .git/logs/refs/heads/g/h/i' +test_expect_success \ + 'git branch j/k should work after branch j has been deleted' \ + 'git-branch j && + git-branch -d j && + git-branch j/k' + +test_expect_success \ + 'git branch l should work after branch l/m has been deleted' \ + 'git-branch l/m && + git-branch -d l/m && + git-branch l' + test_done -- cgit v0.10.2-6-g49f6 From fd28b34afd9bbd58297a25edced3f504c9a5487a Mon Sep 17 00:00:00 2001 From: Shawn Pearce <spearce@spearce.org> Date: Wed, 27 Sep 2006 01:21:19 -0400 Subject: Ignore executable bit when adding files if filemode=0. If the user has configured core.filemode=0 then we shouldn't set the execute bit in the index when adding a new file as the user has indicated that the local filesystem can't be trusted. This means that when adding files that should be marked executable in a repository with core.filemode=0 the user must perform a 'git update-index --chmod=+x' on the file before committing the addition. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-update-index.c b/builtin-update-index.c index 0620e77..a3c0a45 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -112,11 +112,13 @@ static int add_file_to_cache(const char *path) ce->ce_mode = create_ce_mode(st.st_mode); if (!trust_executable_bit) { /* If there is an existing entry, pick the mode bits - * from it. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (0 <= pos) ce->ce_mode = active_cache[pos]->ce_mode; + else + ce->ce_mode = create_ce_mode(S_IFREG | 0644); } if (index_path(ce->sha1, path, &st, !info_only)) diff --git a/read-cache.c b/read-cache.c index 20c9d49..97c3867 100644 --- a/read-cache.c +++ b/read-cache.c @@ -347,11 +347,13 @@ int add_file_to_index(const char *path, int verbose) ce->ce_mode = create_ce_mode(st.st_mode); if (!trust_executable_bit) { /* If there is an existing entry, pick the mode bits - * from it. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (pos >= 0) ce->ce_mode = active_cache[pos]->ce_mode; + else + ce->ce_mode = create_ce_mode(S_IFREG | 0644); } if (index_path(ce->sha1, path, &st, 1)) diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 6cd05c3..d36f22d 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -19,4 +19,26 @@ test_expect_success \ 'Test that "git-add -- -q" works' \ 'touch -- -q && git-add -- -q' +test_expect_success \ + 'git-add: Test that executable bit is not used if core.filemode=0' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo1 && + chmod 755 xfoo1 && + git-add xfoo1 && + case "`git-ls-files --stage xfoo1`" in + 100644" "*xfoo1) echo ok;; + *) echo fail; git-ls-files --stage xfoo1; exit 1;; + esac' + +test_expect_success \ + 'git-update-index --add: Test that executable bit is not used...' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo2 && + chmod 755 xfoo2 && + git-add xfoo2 && + case "`git-ls-files --stage xfoo2`" in + 100644" "*xfoo2) echo ok;; + *) echo fail; git-ls-files --stage xfoo2; exit 1;; + esac' + test_done -- cgit v0.10.2-6-g49f6 From 191414c025c3f0032d9b4843d629e4877126a748 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov <vsu@altlinux.ru> Date: Tue, 26 Sep 2006 13:42:55 +0400 Subject: git-svn: Fix fetch --no-ignore-externals with GIT_SVN_NO_LIB=1 When using Subversion 1.3.1 without Perl bindings (GIT_SVN_NO_LIB=1), "git-svn fetch --no-ignore-externals" fails with errors like: Tree (.../.git/svn/git-svn/tree) is not clean: X directory_with_external In this case the 'X' lines in the "svn status" output are not a sign of unclean tree, and therefore should be ignored. Signed-off-by: Sergey Vlasov <vsu@altlinux.ru> Acked-by: Eric Wong <normalperson@yhbt.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-svn.perl b/git-svn.perl index 017f45a..f5c7d46 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1247,6 +1247,7 @@ sub assert_svn_wc_clean { } my @status = grep(!/^Performing status on external/,(`svn status`)); @status = grep(!/^\s*$/,@status); + @status = grep(!/^X/,@status) if $_no_ignore_ext; if (scalar @status) { print STDERR "Tree ($SVN_WC) is not clean:\n"; print STDERR $_ foreach @status; -- cgit v0.10.2-6-g49f6 From 0fa105e7f140fc381ea3cbb776465aacefa22265 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Tue, 26 Sep 2006 12:45:37 -0700 Subject: gitweb: Remove redundant "tree" link In "tree" view, remove redundant "tree" link in the tree listing. It is identical to simply clicking on the tree entry itself. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c7ab3b6..fa8a65a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1631,18 +1631,14 @@ sub git_print_tree_entry { print "</td>\n"; } elsif ($t->{'type'} eq "tree") { - print "<td class=\"list\">" . - $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - esc_html($t->{'name'})) . - "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, + print "<td class=\"list\">"; + print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key)}, - "tree"); + esc_html($t->{'name'})); + print "</td>\n"; + print "<td class=\"link\">"; if (defined $hash_base) { - print " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, file_name=>"$basedir$t->{'name'}")}, "history"); } -- cgit v0.10.2-6-g49f6 From 4a0641b7cf833644b286b56bb57d66b5538e4418 Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI <yashi@atmark-techno.com> Date: Wed, 27 Sep 2006 12:04:10 +0900 Subject: gitweb: Decode long title for link tooltips This is a simple one liner to decode long title string in perl's internal form to utf-8 for link tooltips. This is not crucial if the commit message is all in ASCII, however, if you decide to use other encoding, such as UTF-8, tooltips ain't readable any more. Signed-off-by: Yasushi SHOJI <yashi@atmark-techno.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 66be619..597d29f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -617,7 +617,7 @@ sub format_subject_html { if (length($short) < length($long)) { return $cgi->a({-href => $href, -class => "list subject", - -title => $long}, + -title => decode("utf8", $long, Encode::FB_DEFAULT)}, esc_html($short) . $extra); } else { return $cgi->a({-href => $href, -class => "list subject"}, -- cgit v0.10.2-6-g49f6 From 65910395c08e3dc4be685a9a9f60adfa61c89aa5 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Tue, 26 Sep 2006 16:45:31 -0700 Subject: gitweb: extend blame to show links to diff and previous git_blame2() now has two more columns, "Prev" and "Diff", before the "Commit" column, as follows: Prev Diff Commit Line Data SHA Diff SHA N ... ... The "Prev" column shows the SHA of the parent commit, between which this line changed. Clicking on it shows the blame of the file as of the parent commit, for that line. So clicking repeatedly on "Prev" would show you the blame of that file, from the point of view of the changes of that particular line whose "Prev" you're clicking on. The "Diff" column shows "Diff" which is a link to blobdiff between "Prev" and "Commit" commits _for that line_. So clicking on "Diff" would show you the blobdiff (HTML) between the parent commit and this commit which changed that particular line. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fa8a65a..e769c8e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2439,7 +2439,7 @@ sub git_blame2 { print <<HTML; <div class="page_body"> <table class="blame"> -<tr><th>Commit</th><th>Line</th><th>Data</th></tr> +<tr><th>Prev</th><th>Diff</th><th>Commit</th><th>Line</th><th>Data</th></tr> HTML while (<$fd>) { /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; @@ -2447,6 +2447,8 @@ HTML my $rev = substr($full_rev, 0, 8); my $lineno = $2; my $data = $3; + my %pco = parse_commit($full_rev); + my $parent = $pco{'parent'}; if (!defined $last_rev) { $last_rev = $full_rev; @@ -2455,11 +2457,25 @@ HTML $current_color = ++$current_color % $num_colors; } print "<tr class=\"$rev_color[$current_color]\">\n"; + # Print the Prev link + print "<td class=\"sha1\">"; + print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, file_name=>$file_name)}, + esc_html(substr($parent, 0, 8))); + print "</td>\n"; + # Print the Diff (blobdiff) link + print "<td>"; + print $cgi->a({-href => href(action=>"blobdiff", file_name=>$file_name, hash_parent_base=>$parent, + hash_base=>$full_rev)}, + esc_html("Diff")); + print "</td>\n"; + # Print the Commit link print "<td class=\"sha1\">" . $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, esc_html($rev)) . "</td>\n"; + # Print the Line number print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n"; + # Print the Data print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; print "</tr>\n"; } -- cgit v0.10.2-6-g49f6 From eb32d236df0c16b936b04f0c5402addb61cdb311 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Thu, 21 Sep 2006 00:06:49 -0400 Subject: introduce delta objects with offset to base This adds a new object, namely OBJ_OFS_DELTA, renames OBJ_DELTA to OBJ_REF_DELTA to better make the distinction between those two delta objects, and adds support for the handling of those new delta objects in sha1_file.c only. The OBJ_OFS_DELTA contains a relative offset from the delta object's position in a pack instead of the 20-byte SHA1 reference to identify the base object. Since the base is likely to be not so far away, the relative offset is more likely to have a smaller encoding on average than an absolute offset. And for those delta objects the base must always be stored first because there is no way to know the distance of later objects when streaming a pack. Hence this relative offset is always meant to be negative. The offset encoding is slightly denser than the one used for object size -- credits to <linux@horizon.com> (whoever this is) for bringing it to my attention. This allows for pack size reduction between 3.2% (Linux-2.6) to over 5% (linux-historic). Runtime pack access should be faster too since delta replay does skip a search in the pack index for each delta in a chain. Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 96c069a..c62734a 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -232,7 +232,7 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha int n = 1; unsigned char c; - if (type < OBJ_COMMIT || type > OBJ_DELTA) + if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) die("bad type %d", type); c = (type << 4) | (size & 15); @@ -297,7 +297,7 @@ static int revalidate_pack_entry(struct object_entry *entry, unsigned char *data used = unpack_object_header_gently(data, len, &type, &size); if (!used) return -1; - if (type == OBJ_DELTA) + if (type == OBJ_REF_DELTA) used += 20; /* skip base object name */ data += used; len -= used; @@ -340,7 +340,7 @@ static unsigned long write_object(struct sha1file *f, obj_type = entry->type; if (! entry->in_pack) to_reuse = 0; /* can't reuse what we don't have */ - else if (obj_type == OBJ_DELTA) + else if (obj_type == OBJ_REF_DELTA) to_reuse = 1; /* check_object() decided it for us */ else if (obj_type != entry->in_pack_type) to_reuse = 0; /* pack has delta which is unusable */ @@ -380,7 +380,7 @@ static unsigned long write_object(struct sha1file *f, if (entry->delta) { buf = delta_against(buf, size, entry); size = entry->delta_size; - obj_type = OBJ_DELTA; + obj_type = OBJ_REF_DELTA; } /* * The object header is a byte of 'type' followed by zero or @@ -409,11 +409,11 @@ static unsigned long write_object(struct sha1file *f, sha1write(f, buf, datalen); unuse_packed_git(p); hdrlen = 0; /* not really */ - if (obj_type == OBJ_DELTA) + if (obj_type == OBJ_REF_DELTA) reused_delta++; reused++; } - if (obj_type == OBJ_DELTA) + if (obj_type == OBJ_REF_DELTA) written_delta++; written++; return hdrlen + datalen; @@ -916,7 +916,7 @@ static void check_object(struct object_entry *entry) * delta. */ if (!no_reuse_delta && - entry->in_pack_type == OBJ_DELTA && + entry->in_pack_type == OBJ_REF_DELTA && (base_entry = locate_object_entry(base)) && (!base_entry->preferred_base)) { @@ -929,7 +929,7 @@ static void check_object(struct object_entry *entry) /* uncompressed size of the delta data */ entry->size = entry->delta_size = size; entry->delta = base_entry; - entry->type = OBJ_DELTA; + entry->type = OBJ_REF_DELTA; entry->delta_sibling = base_entry->delta_child; base_entry->delta_child = entry; diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 4f96bca..c6c6368 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -241,7 +241,7 @@ static void unpack_one(unsigned nr, unsigned total) case OBJ_TAG: unpack_non_delta_entry(type, size); return; - case OBJ_DELTA: + case OBJ_REF_DELTA: unpack_delta_entry(size); return; default: diff --git a/cache.h b/cache.h index 97debd0..3c5415e 100644 --- a/cache.h +++ b/cache.h @@ -274,8 +274,9 @@ enum object_type { OBJ_TREE = 2, OBJ_BLOB = 3, OBJ_TAG = 4, - /* 5/6 for future expansion */ - OBJ_DELTA = 7, + /* 5 for future expansion */ + OBJ_OFS_DELTA = 6, + OBJ_REF_DELTA = 7, OBJ_BAD, }; diff --git a/index-pack.c b/index-pack.c index 80bc6cb..aef7f0a 100644 --- a/index-pack.c +++ b/index-pack.c @@ -158,7 +158,7 @@ static void *unpack_raw_entry(unsigned long offset, } switch (type) { - case OBJ_DELTA: + case OBJ_REF_DELTA: if (pos + 20 >= pack_limit) bad_object(offset, "object extends past end of pack"); hashcpy(delta_base, pack_base + pos); @@ -301,7 +301,7 @@ static void parse_pack_objects(void) data = unpack_raw_entry(offset, &obj->type, &data_size, base_sha1, &offset); obj->real_type = obj->type; - if (obj->type == OBJ_DELTA) { + if (obj->type == OBJ_REF_DELTA) { struct delta_entry *delta = &deltas[nr_deltas++]; delta->obj = obj; hashcpy(delta->base_sha1, base_sha1); @@ -328,7 +328,7 @@ static void parse_pack_objects(void) struct object_entry *obj = &objects[i]; int j, first, last; - if (obj->type == OBJ_DELTA) + if (obj->type == OBJ_REF_DELTA) continue; if (find_deltas_based_on_sha1(obj->sha1, &first, &last)) continue; @@ -341,7 +341,7 @@ static void parse_pack_objects(void) /* Check for unresolved deltas */ for (i = 0; i < nr_deltas; i++) { - if (deltas[i].obj->real_type == OBJ_DELTA) + if (deltas[i].obj->real_type == OBJ_REF_DELTA) die("packfile '%s' has unresolved deltas", pack_name); } } diff --git a/sha1_file.c b/sha1_file.c index 27b1ebb..fdb4588 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -883,26 +883,61 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l return unpack_sha1_rest(&stream, hdr, *size); } +static unsigned long get_delta_base(struct packed_git *p, + unsigned long offset, + enum object_type kind, + unsigned long delta_obj_offset, + unsigned long *base_obj_offset) +{ + unsigned char *base_info = (unsigned char *) p->pack_base + offset; + unsigned long base_offset; + + /* there must be at least 20 bytes left regardless of delta type */ + if (p->pack_size <= offset + 20) + die("truncated pack file"); + + if (kind == OBJ_OFS_DELTA) { + unsigned used = 0; + unsigned char c = base_info[used++]; + base_offset = c & 127; + while (c & 128) { + base_offset += 1; + if (!base_offset || base_offset & ~(~0UL >> 7)) + die("offset value overflow for delta base object"); + c = base_info[used++]; + base_offset = (base_offset << 7) + (c & 127); + } + base_offset = delta_obj_offset - base_offset; + if (base_offset >= delta_obj_offset) + die("delta base offset out of bound"); + offset += used; + } else if (kind == OBJ_REF_DELTA) { + /* The base entry _must_ be in the same pack */ + base_offset = find_pack_entry_one(base_info, p); + if (!base_offset) + die("failed to find delta-pack base object %s", + sha1_to_hex(base_info)); + offset += 20; + } else + die("I am totally screwed"); + *base_obj_offset = base_offset; + return offset; +} + /* forward declaration for a mutually recursive function */ static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep); static int packed_delta_info(struct packed_git *p, unsigned long offset, + enum object_type kind, + unsigned long obj_offset, char *type, unsigned long *sizep) { unsigned long base_offset; - unsigned char *base_sha1 = (unsigned char *) p->pack_base + offset; - if (p->pack_size < offset + 20) - die("truncated pack file"); - /* The base entry _must_ be in the same pack */ - base_offset = find_pack_entry_one(base_sha1, p); - if (!base_offset) - die("failed to find delta-pack base object %s", - sha1_to_hex(base_sha1)); - offset += 20; + offset = get_delta_base(p, offset, kind, obj_offset, &base_offset); /* We choose to only get the type of the base object and * ignore potentially corrupt pack file that expects the delta @@ -975,7 +1010,7 @@ int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, use_packed_git(p); ptr = offset; ptr = unpack_object_header(p, ptr, kindp, sizep); - if (*kindp != OBJ_DELTA) + if (*kindp != OBJ_REF_DELTA) goto done; hashcpy(base, (unsigned char *) p->pack_base + ptr); status = 0; @@ -992,11 +1027,12 @@ void packed_object_info_detail(struct packed_git *p, unsigned int *delta_chain_length, unsigned char *base_sha1) { - unsigned long val; + unsigned long obj_offset, val; unsigned char *next_sha1; enum object_type kind; *delta_chain_length = 0; + obj_offset = offset; offset = unpack_object_header(p, offset, &kind, size); for (;;) { @@ -1011,7 +1047,13 @@ void packed_object_info_detail(struct packed_git *p, strcpy(type, type_names[kind]); *store_size = 0; /* notyet */ return; - case OBJ_DELTA: + case OBJ_OFS_DELTA: + get_delta_base(p, offset, kind, obj_offset, &offset); + if (*delta_chain_length == 0) { + /* TODO: find base_sha1 as pointed by offset */ + } + break; + case OBJ_REF_DELTA: if (p->pack_size <= offset + 20) die("pack file %s records an incomplete delta base", p->pack_name); @@ -1021,6 +1063,7 @@ void packed_object_info_detail(struct packed_git *p, offset = find_pack_entry_one(next_sha1, p); break; } + obj_offset = offset; offset = unpack_object_header(p, offset, &kind, &val); (*delta_chain_length)++; } @@ -1029,15 +1072,15 @@ void packed_object_info_detail(struct packed_git *p, static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - unsigned long size; + unsigned long size, obj_offset = offset; enum object_type kind; offset = unpack_object_header(p, offset, &kind, &size); - if (kind == OBJ_DELTA) - return packed_delta_info(p, offset, type, sizep); - switch (kind) { + case OBJ_OFS_DELTA: + case OBJ_REF_DELTA: + return packed_delta_info(p, offset, kind, obj_offset, type, sizep); case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: @@ -1083,23 +1126,15 @@ static void *unpack_compressed_entry(struct packed_git *p, static void *unpack_delta_entry(struct packed_git *p, unsigned long offset, unsigned long delta_size, + enum object_type kind, + unsigned long obj_offset, char *type, unsigned long *sizep) { void *delta_data, *result, *base; unsigned long result_size, base_size, base_offset; - unsigned char *base_sha1; - - if (p->pack_size < offset + 20) - die("truncated pack file"); - /* The base entry _must_ be in the same pack */ - base_sha1 = (unsigned char*)p->pack_base + offset; - base_offset = find_pack_entry_one(base_sha1, p); - if (!base_offset) - die("failed to find delta-pack base object %s", - sha1_to_hex(base_sha1)); - offset += 20; + offset = get_delta_base(p, offset, kind, obj_offset, &base_offset); base = unpack_entry_gently(p, base_offset, type, &base_size); if (!base) die("failed to read delta base object at %lu from %s", @@ -1136,13 +1171,14 @@ static void *unpack_entry(struct pack_entry *entry, void *unpack_entry_gently(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - unsigned long size; + unsigned long size, obj_offset = offset; enum object_type kind; offset = unpack_object_header(p, offset, &kind, &size); switch (kind) { - case OBJ_DELTA: - return unpack_delta_entry(p, offset, size, type, sizep); + case OBJ_OFS_DELTA: + case OBJ_REF_DELTA: + return unpack_delta_entry(p, offset, size, kind, obj_offset, type, sizep); case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: -- cgit v0.10.2-6-g49f6 From 209c554ab45701fb78de77fdcec803fef2c8fd39 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Thu, 21 Sep 2006 00:07:39 -0400 Subject: teach git-unpack-objects about deltas with offset to base For delta resolution to be possible, a list of sha1/offset tuple must be constructed in memory in order to load the appropriate base object. Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index c6c6368..e70a711 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -15,7 +15,7 @@ static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-fil /* We always read in 4kB chunks. */ static unsigned char buffer[4096]; -static unsigned long offset, len; +static unsigned long offset, len, consumed_bytes; static SHA_CTX ctx; /* @@ -51,6 +51,7 @@ static void use(int bytes) die("used more bytes than were available"); len -= bytes; offset += bytes; + consumed_bytes += bytes; } static void *get_data(unsigned long size) @@ -89,35 +90,49 @@ static void *get_data(unsigned long size) struct delta_info { unsigned char base_sha1[20]; + unsigned long base_offset; unsigned long size; void *delta; + unsigned nr; struct delta_info *next; }; static struct delta_info *delta_list; -static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size) +static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1, + unsigned long base_offset, + void *delta, unsigned long size) { struct delta_info *info = xmalloc(sizeof(*info)); hashcpy(info->base_sha1, base_sha1); + info->base_offset = base_offset; info->size = size; info->delta = delta; + info->nr = nr; info->next = delta_list; delta_list = info; } -static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size); +struct obj_info { + unsigned long offset; + unsigned char sha1[20]; +}; + +static struct obj_info *obj_list; -static void write_object(void *buf, unsigned long size, const char *type) +static void added_object(unsigned nr, const char *type, void *data, + unsigned long size); + +static void write_object(unsigned nr, void *buf, unsigned long size, + const char *type) { - unsigned char sha1[20]; - if (write_sha1_file(buf, size, type, sha1) < 0) + if (write_sha1_file(buf, size, type, obj_list[nr].sha1) < 0) die("failed to write object"); - added_object(sha1, type, buf, size); + added_object(nr, type, buf, size); } -static void resolve_delta(const char *type, +static void resolve_delta(unsigned nr, const char *type, void *base, unsigned long base_size, void *delta, unsigned long delta_size) { @@ -130,20 +145,23 @@ static void resolve_delta(const char *type, if (!result) die("failed to apply delta"); free(delta); - write_object(result, result_size, type); + write_object(nr, result, result_size, type); free(result); } -static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size) +static void added_object(unsigned nr, const char *type, void *data, + unsigned long size) { struct delta_info **p = &delta_list; struct delta_info *info; while ((info = *p) != NULL) { - if (!hashcmp(info->base_sha1, sha1)) { + if (!hashcmp(info->base_sha1, obj_list[nr].sha1) || + info->base_offset == obj_list[nr].offset) { *p = info->next; p = &delta_list; - resolve_delta(type, data, size, info->delta, info->size); + resolve_delta(info->nr, type, data, size, + info->delta, info->size); free(info); continue; } @@ -151,7 +169,8 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi } } -static void unpack_non_delta_entry(enum object_type kind, unsigned long size) +static void unpack_non_delta_entry(enum object_type kind, unsigned long size, + unsigned nr) { void *buf = get_data(size); const char *type; @@ -164,30 +183,80 @@ static void unpack_non_delta_entry(enum object_type kind, unsigned long size) default: die("bad type %d", kind); } if (!dry_run && buf) - write_object(buf, size, type); + write_object(nr, buf, size, type); free(buf); } -static void unpack_delta_entry(unsigned long delta_size) +static void unpack_delta_entry(enum object_type kind, unsigned long delta_size, + unsigned nr) { void *delta_data, *base; unsigned long base_size; char type[20]; unsigned char base_sha1[20]; - hashcpy(base_sha1, fill(20)); - use(20); + if (kind == OBJ_REF_DELTA) { + hashcpy(base_sha1, fill(20)); + use(20); + delta_data = get_data(delta_size); + if (dry_run || !delta_data) { + free(delta_data); + return; + } + if (!has_sha1_file(base_sha1)) { + hashcpy(obj_list[nr].sha1, null_sha1); + add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size); + return; + } + } else { + unsigned base_found = 0; + unsigned char *pack, c; + unsigned long base_offset; + unsigned lo, mid, hi; - delta_data = get_data(delta_size); - if (dry_run || !delta_data) { - free(delta_data); - return; - } + pack = fill(1); + c = *pack; + use(1); + base_offset = c & 127; + while (c & 128) { + base_offset += 1; + if (!base_offset || base_offset & ~(~0UL >> 7)) + die("offset value overflow for delta base object"); + pack = fill(1); + c = *pack; + use(1); + base_offset = (base_offset << 7) + (c & 127); + } + base_offset = obj_list[nr].offset - base_offset; - if (!has_sha1_file(base_sha1)) { - add_delta_to_list(base_sha1, delta_data, delta_size); - return; + delta_data = get_data(delta_size); + if (dry_run || !delta_data) { + free(delta_data); + return; + } + lo = 0; + hi = nr; + while (lo < hi) { + mid = (lo + hi)/2; + if (base_offset < obj_list[mid].offset) { + hi = mid; + } else if (base_offset > obj_list[mid].offset) { + lo = mid + 1; + } else { + hashcpy(base_sha1, obj_list[mid].sha1); + base_found = !is_null_sha1(base_sha1); + break; + } + } + if (!base_found) { + /* The delta base object is itself a delta that + has not been resolved yet. */ + hashcpy(obj_list[nr].sha1, null_sha1); + add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size); + return; + } } + base = read_sha1_file(base_sha1, type, &base_size); if (!base) { error("failed to read delta-pack base object %s", @@ -197,7 +266,7 @@ static void unpack_delta_entry(unsigned long delta_size) has_errors = 1; return; } - resolve_delta(type, base, base_size, delta_data, delta_size); + resolve_delta(nr, type, base, base_size, delta_data, delta_size); free(base); } @@ -208,6 +277,8 @@ static void unpack_one(unsigned nr, unsigned total) unsigned long size; enum object_type type; + obj_list[nr].offset = consumed_bytes; + pack = fill(1); c = *pack; use(1); @@ -216,7 +287,7 @@ static void unpack_one(unsigned nr, unsigned total) shift = 4; while (c & 0x80) { pack = fill(1); - c = *pack++; + c = *pack; use(1); size += (c & 0x7f) << shift; shift += 7; @@ -225,13 +296,14 @@ static void unpack_one(unsigned nr, unsigned total) static unsigned long last_sec; static unsigned last_percent; struct timeval now; - unsigned percentage = (nr * 100) / total; + unsigned percentage = ((nr+1) * 100) / total; gettimeofday(&now, NULL); if (percentage != last_percent || now.tv_sec != last_sec) { last_sec = now.tv_sec; last_percent = percentage; - fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total); + fprintf(stderr, "%4u%% (%u/%u) done\r", + percentage, (nr+1), total); } } switch (type) { @@ -239,10 +311,11 @@ static void unpack_one(unsigned nr, unsigned total) case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: - unpack_non_delta_entry(type, size); + unpack_non_delta_entry(type, size, nr); return; case OBJ_REF_DELTA: - unpack_delta_entry(size); + case OBJ_OFS_DELTA: + unpack_delta_entry(type, size, nr); return; default: error("bad object type %d", type); @@ -265,9 +338,10 @@ static void unpack_all(void) die("unknown pack file version %d", ntohl(hdr->hdr_version)); fprintf(stderr, "Unpacking %d objects\n", nr_objects); + obj_list = xmalloc(nr_objects * sizeof(*obj_list)); use(sizeof(struct pack_header)); for (i = 0; i < nr_objects; i++) - unpack_one(i+1, nr_objects); + unpack_one(i, nr_objects); if (delta_list) die("unresolved deltas left after unpacking"); } -- cgit v0.10.2-6-g49f6 From 53dda6ff6263a3f350514d9edae600468c946ed4 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Thu, 21 Sep 2006 00:08:33 -0400 Subject: teach git-index-pack about deltas with offset to base Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/index-pack.c b/index-pack.c index aef7f0a..fffddd2 100644 --- a/index-pack.c +++ b/index-pack.c @@ -18,10 +18,15 @@ struct object_entry unsigned char sha1[20]; }; +union delta_base { + unsigned char sha1[20]; + unsigned long offset; +}; + struct delta_entry { struct object_entry *obj; - unsigned char base_sha1[20]; + union delta_base base; }; static const char *pack_name; @@ -134,13 +139,13 @@ static void *unpack_entry_data(unsigned long offset, static void *unpack_raw_entry(unsigned long offset, enum object_type *obj_type, unsigned long *obj_size, - unsigned char *delta_base, + union delta_base *delta_base, unsigned long *next_obj_offset) { unsigned long pack_limit = pack_size - 20; unsigned long pos = offset; unsigned char c; - unsigned long size; + unsigned long size, base_offset; unsigned shift; enum object_type type; void *data; @@ -161,26 +166,43 @@ static void *unpack_raw_entry(unsigned long offset, case OBJ_REF_DELTA: if (pos + 20 >= pack_limit) bad_object(offset, "object extends past end of pack"); - hashcpy(delta_base, pack_base + pos); + hashcpy(delta_base->sha1, pack_base + pos); pos += 20; - /* fallthru */ + break; + case OBJ_OFS_DELTA: + memset(delta_base, 0, sizeof(*delta_base)); + c = pack_base[pos++]; + base_offset = c & 127; + while (c & 128) { + base_offset += 1; + if (!base_offset || base_offset & ~(~0UL >> 7)) + bad_object(offset, "offset value overflow for delta base object"); + if (pos >= pack_limit) + bad_object(offset, "object extends past end of pack"); + c = pack_base[pos++]; + base_offset = (base_offset << 7) + (c & 127); + } + delta_base->offset = offset - base_offset; + if (delta_base->offset >= offset) + bad_object(offset, "delta base offset is out of bound"); + break; case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: - data = unpack_entry_data(offset, &pos, size); break; default: bad_object(offset, "bad object type %d", type); } + data = unpack_entry_data(offset, &pos, size); *obj_type = type; *obj_size = size; *next_obj_offset = pos; return data; } -static int find_delta(const unsigned char *base_sha1) +static int find_delta(const union delta_base *base) { int first = 0, last = nr_deltas; @@ -189,7 +211,7 @@ static int find_delta(const unsigned char *base_sha1) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = hashcmp(base_sha1, delta->base_sha1); + cmp = memcmp(base, &delta->base, sizeof(*base)); if (!cmp) return next; if (cmp < 0) { @@ -201,18 +223,18 @@ static int find_delta(const unsigned char *base_sha1) return -first-1; } -static int find_deltas_based_on_sha1(const unsigned char *base_sha1, - int *first_index, int *last_index) +static int find_delta_childs(const union delta_base *base, + int *first_index, int *last_index) { - int first = find_delta(base_sha1); + int first = find_delta(base); int last = first; int end = nr_deltas - 1; if (first < 0) return -1; - while (first > 0 && !hashcmp(deltas[first - 1].base_sha1, base_sha1)) + while (first > 0 && !memcmp(&deltas[first - 1].base, base, sizeof(*base))) --first; - while (last < end && !hashcmp(deltas[last + 1].base_sha1, base_sha1)) + while (last < end && !memcmp(&deltas[last + 1].base, base, sizeof(*base))) ++last; *first_index = first; *last_index = last; @@ -253,13 +275,13 @@ static void resolve_delta(struct delta_entry *delta, void *base_data, void *result; unsigned long result_size; enum object_type delta_type; - unsigned char base_sha1[20]; + union delta_base delta_base; unsigned long next_obj_offset; int j, first, last; obj->real_type = type; delta_data = unpack_raw_entry(obj->offset, &delta_type, - &delta_size, base_sha1, + &delta_size, &delta_base, &next_obj_offset); result = patch_delta(base_data, base_size, delta_data, delta_size, &result_size); @@ -267,10 +289,22 @@ static void resolve_delta(struct delta_entry *delta, void *base_data, if (!result) bad_object(obj->offset, "failed to apply delta"); sha1_object(result, result_size, type, obj->sha1); - if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) { + + hashcpy(delta_base.sha1, obj->sha1); + if (!find_delta_childs(&delta_base, &first, &last)) { for (j = first; j <= last; j++) - resolve_delta(&deltas[j], result, result_size, type); + if (deltas[j].obj->type == OBJ_REF_DELTA) + resolve_delta(&deltas[j], result, result_size, type); } + + memset(&delta_base, 0, sizeof(delta_base)); + delta_base.offset = obj->offset; + if (!find_delta_childs(&delta_base, &first, &last)) { + for (j = first; j <= last; j++) + if (deltas[j].obj->type == OBJ_OFS_DELTA) + resolve_delta(&deltas[j], result, result_size, type); + } + free(result); } @@ -278,14 +312,14 @@ static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return hashcmp(delta_a->base_sha1, delta_b->base_sha1); + return memcmp(&delta_a->base, &delta_b->base, sizeof(union delta_base)); } static void parse_pack_objects(void) { int i; unsigned long offset = sizeof(struct pack_header); - unsigned char base_sha1[20]; + struct delta_entry *delta = deltas; void *data; unsigned long data_size; @@ -299,12 +333,12 @@ static void parse_pack_objects(void) struct object_entry *obj = &objects[i]; obj->offset = offset; data = unpack_raw_entry(offset, &obj->type, &data_size, - base_sha1, &offset); + &delta->base, &offset); obj->real_type = obj->type; - if (obj->type == OBJ_REF_DELTA) { - struct delta_entry *delta = &deltas[nr_deltas++]; + if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { + nr_deltas++; delta->obj = obj; - hashcpy(delta->base_sha1, base_sha1); + delta++; } else sha1_object(data, data_size, obj->type, obj->sha1); free(data); @@ -312,7 +346,7 @@ static void parse_pack_objects(void) if (offset != pack_size - 20) die("packfile '%s' has junk at the end", pack_name); - /* Sort deltas by base SHA1 for fast searching */ + /* Sort deltas by base SHA1/offset for fast searching */ qsort(deltas, nr_deltas, sizeof(struct delta_entry), compare_delta_entry); @@ -326,22 +360,37 @@ static void parse_pack_objects(void) */ for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - int j, first, last; + union delta_base base; + int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last; - if (obj->type == OBJ_REF_DELTA) + if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) continue; - if (find_deltas_based_on_sha1(obj->sha1, &first, &last)) + hashcpy(base.sha1, obj->sha1); + ref = !find_delta_childs(&base, &ref_first, &ref_last); + memset(&base, 0, sizeof(base)); + base.offset = obj->offset; + ofs = !find_delta_childs(&base, &ofs_first, &ofs_last); + if (!ref && !ofs) continue; data = unpack_raw_entry(obj->offset, &obj->type, &data_size, - base_sha1, &offset); - for (j = first; j <= last; j++) - resolve_delta(&deltas[j], data, data_size, obj->type); + &base, &offset); + if (ref) + for (j = ref_first; j <= ref_last; j++) + if (deltas[j].obj->type == OBJ_REF_DELTA) + resolve_delta(&deltas[j], data, + data_size, obj->type); + if (ofs) + for (j = ofs_first; j <= ofs_last; j++) + if (deltas[j].obj->type == OBJ_OFS_DELTA) + resolve_delta(&deltas[j], data, + data_size, obj->type); free(data); } /* Check for unresolved deltas */ for (i = 0; i < nr_deltas; i++) { - if (deltas[i].obj->real_type == OBJ_REF_DELTA) + if (deltas[i].obj->real_type == OBJ_REF_DELTA || + deltas[i].obj->real_type == OBJ_OFS_DELTA) die("packfile '%s' has unresolved deltas", pack_name); } } -- cgit v0.10.2-6-g49f6 From be6b19145f64f62790049c06320c35011f7312a7 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Thu, 21 Sep 2006 00:09:44 -0400 Subject: make git-pack-objects able to create deltas with offset to base This is enabled with --delta-base-offset only, and doesn't work with pack data reuse yet. The idea is to allow for the fetch protocol to use an extension flag to notify the remote end that --delta-base-offset can be used with git-pack-objects. Eventually git-repack will always provide this flag. With this, all delta base objects are now pushed before deltas that depend on them. This is a requirements for OBJ_OFS_DELTA. This is not a requirement for OBJ_REF_DELTA but always doing so makes the code simpler. Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index c62734a..2212649 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -60,6 +60,8 @@ static int non_empty; static int no_reuse_delta; static int local; static int incremental; +static int allow_ofs_delta; + static struct object_entry **sorted_by_sha, **sorted_by_type; static struct object_entry *objects; static int nr_objects, nr_alloc, nr_result; @@ -334,9 +336,6 @@ static unsigned long write_object(struct sha1file *f, enum object_type obj_type; int to_reuse = 0; - if (entry->preferred_base) - return 0; - obj_type = entry->type; if (! entry->in_pack) to_reuse = 0; /* can't reuse what we don't have */ @@ -380,18 +379,35 @@ static unsigned long write_object(struct sha1file *f, if (entry->delta) { buf = delta_against(buf, size, entry); size = entry->delta_size; - obj_type = OBJ_REF_DELTA; + obj_type = (allow_ofs_delta && entry->delta->offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; } /* * The object header is a byte of 'type' followed by zero or - * more bytes of length. For deltas, the 20 bytes of delta - * sha1 follows that. + * more bytes of length. */ hdrlen = encode_header(obj_type, size, header); sha1write(f, header, hdrlen); - if (entry->delta) { - sha1write(f, entry->delta, 20); + if (obj_type == OBJ_OFS_DELTA) { + /* + * Deltas with relative base contain an additional + * encoding of the relative offset for the delta + * base from this object's position in the pack. + */ + unsigned long ofs = entry->offset - entry->delta->offset; + unsigned pos = sizeof(header) - 1; + header[pos] = ofs & 127; + while (ofs >>= 7) + header[--pos] = 128 | (--ofs & 127); + sha1write(f, header + pos, sizeof(header) - pos); + hdrlen += sizeof(header) - pos; + } else if (obj_type == OBJ_REF_DELTA) { + /* + * Deltas with a base reference contain + * an additional 20 bytes for the base sha1. + */ + sha1write(f, entry->delta->sha1, 20); hdrlen += 20; } datalen = sha1write_compressed(f, buf, size); @@ -413,7 +429,7 @@ static unsigned long write_object(struct sha1file *f, reused_delta++; reused++; } - if (obj_type == OBJ_REF_DELTA) + if (entry->delta) written_delta++; written++; return hdrlen + datalen; @@ -423,17 +439,16 @@ static unsigned long write_one(struct sha1file *f, struct object_entry *e, unsigned long offset) { - if (e->offset) + if (e->offset || e->preferred_base) /* offset starts from header size and cannot be zero * if it is written already. */ return offset; - e->offset = offset; - offset += write_object(f, e); - /* if we are deltified, write out its base object. */ + /* if we are deltified, write out its base object first. */ if (e->delta) offset = write_one(f, e->delta, offset); - return offset; + e->offset = offset; + return offset + write_object(f, e); } static void write_pack_file(void) @@ -1484,6 +1499,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) no_reuse_delta = 1; continue; } + if (!strcmp("--delta-base-offset", arg)) { + allow_ofs_delta = no_reuse_delta = 1; + continue; + } if (!strcmp("--stdout", arg)) { pack_to_stdout = 1; continue; -- cgit v0.10.2-6-g49f6 From 780e6e735be189097dad4b223d8edeb18cce1928 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Fri, 22 Sep 2006 21:25:04 -0400 Subject: make pack data reuse compatible with both delta types This is the missing part to git-pack-objects allowing it to reuse delta data to/from any of the two delta types. It can reuse delta from any type, and it outputs base offsets when --allow-delta-base-offset is provided and the base is also included in the pack. Otherwise it outputs base sha1 references just like it always did. Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 2212649..6db97b6 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -29,6 +29,7 @@ struct object_entry { enum object_type type; enum object_type in_pack_type; /* could be delta */ unsigned long delta_size; /* delta data size (uncompressed) */ +#define in_pack_header_size delta_size /* only when reusing pack data */ struct object_entry *delta; /* delta base object */ struct packed_git *in_pack; /* already in pack */ unsigned int in_pack_offset; @@ -86,17 +87,25 @@ static int object_ix_hashsz; * Pack index for existing packs give us easy access to the offsets into * corresponding pack file where each object's data starts, but the entries * do not store the size of the compressed representation (uncompressed - * size is easily available by examining the pack entry header). We build - * a hashtable of existing packs (pack_revindex), and keep reverse index - * here -- pack index file is sorted by object name mapping to offset; this - * pack_revindex[].revindex array is an ordered list of offsets, so if you - * know the offset of an object, next offset is where its packed - * representation ends. + * size is easily available by examining the pack entry header). It is + * also rather expensive to find the sha1 for an object given its offset. + * + * We build a hashtable of existing packs (pack_revindex), and keep reverse + * index here -- pack index file is sorted by object name mapping to offset; + * this pack_revindex[].revindex array is a list of offset/index_nr pairs + * ordered by offset, so if you know the offset of an object, next offset + * is where its packed representation ends and the index_nr can be used to + * get the object sha1 from the main index. */ +struct revindex_entry { + unsigned int offset; + unsigned int nr; +}; struct pack_revindex { struct packed_git *p; - unsigned long *revindex; -} *pack_revindex = NULL; + struct revindex_entry *revindex; +}; +static struct pack_revindex *pack_revindex; static int pack_revindex_hashsz; /* @@ -143,14 +152,9 @@ static void prepare_pack_ix(void) static int cmp_offset(const void *a_, const void *b_) { - unsigned long a = *(unsigned long *) a_; - unsigned long b = *(unsigned long *) b_; - if (a < b) - return -1; - else if (a == b) - return 0; - else - return 1; + const struct revindex_entry *a = a_; + const struct revindex_entry *b = b_; + return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0; } /* @@ -163,25 +167,27 @@ static void prepare_pack_revindex(struct pack_revindex *rix) int i; void *index = p->index_base + 256; - rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1)); + rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1)); for (i = 0; i < num_ent; i++) { unsigned int hl = *((unsigned int *)((char *) index + 24*i)); - rix->revindex[i] = ntohl(hl); + rix->revindex[i].offset = ntohl(hl); + rix->revindex[i].nr = i; } /* This knows the pack format -- the 20-byte trailer * follows immediately after the last object data. */ - rix->revindex[num_ent] = p->pack_size - 20; - qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset); + rix->revindex[num_ent].offset = p->pack_size - 20; + rix->revindex[num_ent].nr = -1; + qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset); } -static unsigned long find_packed_object_size(struct packed_git *p, - unsigned long ofs) +static struct revindex_entry * find_packed_object(struct packed_git *p, + unsigned int ofs) { int num; int lo, hi; struct pack_revindex *rix; - unsigned long *revindex; + struct revindex_entry *revindex; num = pack_revindex_ix(p); if (num < 0) die("internal error: pack revindex uninitialized"); @@ -193,10 +199,10 @@ static unsigned long find_packed_object_size(struct packed_git *p, hi = num_packed_objects(p) + 1; do { int mi = (lo + hi) / 2; - if (revindex[mi] == ofs) { - return revindex[mi+1] - ofs; + if (revindex[mi].offset == ofs) { + return revindex + mi; } - else if (ofs < revindex[mi]) + else if (ofs < revindex[mi].offset) hi = mi; else lo = mi + 1; @@ -204,6 +210,20 @@ static unsigned long find_packed_object_size(struct packed_git *p, die("internal error: pack revindex corrupt"); } +static unsigned long find_packed_object_size(struct packed_git *p, + unsigned long ofs) +{ + struct revindex_entry *entry = find_packed_object(p, ofs); + return entry[1].offset - ofs; +} + +static unsigned char *find_packed_object_name(struct packed_git *p, + unsigned long ofs) +{ + struct revindex_entry *entry = find_packed_object(p, ofs); + return (unsigned char *)(p->index_base + 256) + 24 * entry->nr + 4; +} + static void *delta_against(void *buf, unsigned long size, struct object_entry *entry) { unsigned long othersize, delta_size; @@ -249,6 +269,10 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha return n; } +/* + * we are going to reuse the existing object data as is. make + * sure it is not corrupt. + */ static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect) { z_stream stream; @@ -280,32 +304,6 @@ static int check_inflate(unsigned char *data, unsigned long len, unsigned long e return st; } -/* - * we are going to reuse the existing pack entry data. make - * sure it is not corrupt. - */ -static int revalidate_pack_entry(struct object_entry *entry, unsigned char *data, unsigned long len) -{ - enum object_type type; - unsigned long size, used; - - if (pack_to_stdout) - return 0; - - /* the caller has already called use_packed_git() for us, - * so it is safe to access the pack data from mmapped location. - * make sure the entry inflates correctly. - */ - used = unpack_object_header_gently(data, len, &type, &size); - if (!used) - return -1; - if (type == OBJ_REF_DELTA) - used += 20; /* skip base object name */ - data += used; - len -= used; - return check_inflate(data, len, entry->size); -} - static int revalidate_loose_object(struct object_entry *entry, unsigned char *map, unsigned long mapsize) @@ -339,7 +337,7 @@ static unsigned long write_object(struct sha1file *f, obj_type = entry->type; if (! entry->in_pack) to_reuse = 0; /* can't reuse what we don't have */ - else if (obj_type == OBJ_REF_DELTA) + else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA) to_reuse = 1; /* check_object() decided it for us */ else if (obj_type != entry->in_pack_type) to_reuse = 0; /* pack has delta which is unusable */ @@ -415,18 +413,38 @@ static unsigned long write_object(struct sha1file *f, } else { struct packed_git *p = entry->in_pack; - use_packed_git(p); - datalen = find_packed_object_size(p, entry->in_pack_offset); - buf = (char *) p->pack_base + entry->in_pack_offset; + if (entry->delta) { + obj_type = (allow_ofs_delta && entry->delta->offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; + reused_delta++; + } + hdrlen = encode_header(obj_type, entry->size, header); + sha1write(f, header, hdrlen); + if (obj_type == OBJ_OFS_DELTA) { + unsigned long ofs = entry->offset - entry->delta->offset; + unsigned pos = sizeof(header) - 1; + header[pos] = ofs & 127; + while (ofs >>= 7) + header[--pos] = 128 | (--ofs & 127); + sha1write(f, header + pos, sizeof(header) - pos); + hdrlen += sizeof(header) - pos; + } else if (obj_type == OBJ_REF_DELTA) { + sha1write(f, entry->delta->sha1, 20); + hdrlen += 20; + } - if (revalidate_pack_entry(entry, buf, datalen)) + use_packed_git(p); + buf = (char *) p->pack_base + + entry->in_pack_offset + + entry->in_pack_header_size; + datalen = find_packed_object_size(p, entry->in_pack_offset) + - entry->in_pack_header_size; +//fprintf(stderr, "reusing %d at %d header %d size %d\n", obj_type, entry->in_pack_offset, entry->in_pack_header_size, datalen); + if (!pack_to_stdout && check_inflate(buf, datalen, entry->size)) die("corrupt delta in pack %s", sha1_to_hex(entry->sha1)); sha1write(f, buf, datalen); unuse_packed_git(p); - hdrlen = 0; /* not really */ - if (obj_type == OBJ_REF_DELTA) - reused_delta++; reused++; } if (entry->delta) @@ -914,26 +932,64 @@ static void check_object(struct object_entry *entry) char type[20]; if (entry->in_pack && !entry->preferred_base) { - unsigned char base[20]; - unsigned long size; - struct object_entry *base_entry; + struct packed_git *p = entry->in_pack; + unsigned long left = p->pack_size - entry->in_pack_offset; + unsigned long size, used; + unsigned char *buf; + struct object_entry *base_entry = NULL; + + use_packed_git(p); + buf = p->pack_base; + buf += entry->in_pack_offset; /* We want in_pack_type even if we do not reuse delta. * There is no point not reusing non-delta representations. */ - check_reuse_pack_delta(entry->in_pack, - entry->in_pack_offset, - base, &size, - &entry->in_pack_type); + used = unpack_object_header_gently(buf, left, + &entry->in_pack_type, &size); + if (!used || left - used <= 20) + die("corrupt pack for %s", sha1_to_hex(entry->sha1)); /* Check if it is delta, and the base is also an object * we are going to pack. If so we will reuse the existing * delta. */ - if (!no_reuse_delta && - entry->in_pack_type == OBJ_REF_DELTA && - (base_entry = locate_object_entry(base)) && - (!base_entry->preferred_base)) { + if (!no_reuse_delta) { + unsigned char c, *base_name; + unsigned long ofs; + /* there is at least 20 bytes left in the pack */ + switch (entry->in_pack_type) { + case OBJ_REF_DELTA: + base_name = buf + used; + used += 20; + break; + case OBJ_OFS_DELTA: + c = buf[used++]; + ofs = c & 127; + while (c & 128) { + ofs += 1; + if (!ofs || ofs & ~(~0UL >> 7)) + die("delta base offset overflow in pack for %s", + sha1_to_hex(entry->sha1)); + c = buf[used++]; + ofs = (ofs << 7) + (c & 127); + } + if (ofs >= entry->in_pack_offset) + die("delta base offset out of bound for %s", + sha1_to_hex(entry->sha1)); + ofs = entry->in_pack_offset - ofs; + base_name = find_packed_object_name(p, ofs); + break; + default: + base_name = NULL; + } + if (base_name) + base_entry = locate_object_entry(base_name); + } + unuse_packed_git(p); + entry->in_pack_header_size = used; + + if (base_entry && !base_entry->preferred_base) { /* Depth value does not matter - find_deltas() * will never consider reused delta as the @@ -942,9 +998,9 @@ static void check_object(struct object_entry *entry) */ /* uncompressed size of the delta data */ - entry->size = entry->delta_size = size; + entry->size = size; entry->delta = base_entry; - entry->type = OBJ_REF_DELTA; + entry->type = entry->in_pack_type; entry->delta_sibling = base_entry->delta_child; base_entry->delta_child = entry; @@ -1500,7 +1556,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) continue; } if (!strcmp("--delta-base-offset", arg)) { - allow_ofs_delta = no_reuse_delta = 1; + allow_ofs_delta = 1; continue; } if (!strcmp("--stdout", arg)) { diff --git a/pack.h b/pack.h index 05557da..4c9bddd 100644 --- a/pack.h +++ b/pack.h @@ -16,7 +16,4 @@ struct pack_header { }; extern int verify_pack(struct packed_git *, int); -extern int check_reuse_pack_delta(struct packed_git *, unsigned long, - unsigned char *, unsigned long *, - enum object_type *); #endif diff --git a/sha1_file.c b/sha1_file.c index fdb4588..18c2f88 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1000,25 +1000,6 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of return offset + used; } -int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, - unsigned char *base, unsigned long *sizep, - enum object_type *kindp) -{ - unsigned long ptr; - int status = -1; - - use_packed_git(p); - ptr = offset; - ptr = unpack_object_header(p, ptr, kindp, sizep); - if (*kindp != OBJ_REF_DELTA) - goto done; - hashcpy(base, (unsigned char *) p->pack_base + ptr); - status = 0; - done: - unuse_packed_git(p); - return status; -} - void packed_object_info_detail(struct packed_git *p, unsigned long offset, char *type, -- cgit v0.10.2-6-g49f6 From e4fe4b8ef7cdde842a9e5e2594d0fba1367d9dd3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Tue, 26 Sep 2006 11:27:39 -0400 Subject: let the GIT native protocol use offsets to delta base when possible There is no reason not to always do this when both ends agree. Therefore a client that can accept offsets to delta base always sends the "ofs-delta" flag. The server will stream a pack with or without offset to delta base depending on whether that flag is provided or not with no additional cost. Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/fetch-pack.c b/fetch-pack.c index e8708aa..474d545 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -166,12 +166,13 @@ static int find_common(int fd[2], unsigned char *result_sha1, } if (!fetching) - packet_write(fd[1], "want %s%s%s%s%s\n", + packet_write(fd[1], "want %s%s%s%s%s%s\n", sha1_to_hex(remote), (multi_ack ? " multi_ack" : ""), (use_sideband == 2 ? " side-band-64k" : ""), (use_sideband == 1 ? " side-band" : ""), - (use_thin_pack ? " thin-pack" : "")); + (use_thin_pack ? " thin-pack" : ""), + " ofs-delta"); else packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); fetching++; diff --git a/upload-pack.c b/upload-pack.c index 189b239..9ec3775 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -16,7 +16,7 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define OUR_REF (1U << 1) #define WANTED (1U << 2) static int multi_ack, nr_our_refs; -static int use_thin_pack; +static int use_thin_pack, use_ofs_delta; static struct object_array have_obj; static struct object_array want_obj; static unsigned int timeout; @@ -137,7 +137,9 @@ static void create_pack_file(void) close(pu_pipe[1]); close(pe_pipe[0]); close(pe_pipe[1]); - execl_git_cmd("pack-objects", "--stdout", "--progress", NULL); + execl_git_cmd("pack-objects", "--stdout", "--progress", + use_ofs_delta ? "--delta-base-offset" : NULL, + NULL); kill(pid_rev_list, SIGKILL); die("git-upload-pack: unable to exec git-pack-objects"); } @@ -393,6 +395,8 @@ static void receive_needs(void) multi_ack = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; + if (strstr(line+45, "ofs-delta")) + use_ofs_delta = 1; if (strstr(line+45, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; else if (strstr(line+45, "side-band")) @@ -418,7 +422,7 @@ static void receive_needs(void) static int send_ref(const char *refname, const unsigned char *sha1) { - static const char *capabilities = "multi_ack thin-pack side-band side-band-64k"; + static const char *capabilities = "multi_ack thin-pack side-band side-band-64k ofs-delta"; struct object *o = parse_object(sha1); if (!o) -- cgit v0.10.2-6-g49f6 From 4b02f48372c72b38d2be7484355222f987f3af97 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 26 Sep 2006 01:54:24 +0200 Subject: gitweb: Strip trailing slashes from $path in git_get_hash_by_path It also removes unused local variable $tree Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e769c8e..4686d93 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -710,7 +710,7 @@ sub git_get_hash_by_path { my $path = shift || return undef; my $type = shift; - my $tree = $base; + $path =~ s,/+$,,; open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path or die_error(undef, "Open git-ls-tree failed"); -- cgit v0.10.2-6-g49f6 From dd1ad5f16708e49722ad455dea8076816054391d Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 26 Sep 2006 01:56:17 +0200 Subject: gitweb: Use "return" instead of "return undef" for some subs Use "return" instead of "return undef" when subroutine can return, or always return, non-scalar (list) value. Other places are left as is. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 4686d93..eaa42b9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -106,7 +106,7 @@ our %feature = ( sub gitweb_check_feature { my ($name) = @_; - return undef unless exists $feature{$name}; + return unless exists $feature{$name}; my ($sub, $override, @defaults) = ( $feature{$name}{'sub'}, $feature{$name}{'override'}, @@ -781,7 +781,7 @@ sub git_get_projects_list { # 'git%2Fgit.git Linus+Torvalds' # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' - open my ($fd), $projects_list or return undef; + open my ($fd), $projects_list or return; while (my $line = <$fd>) { chomp $line; my ($path, $owner) = split ' ', $line; -- cgit v0.10.2-6-g49f6 From 24d0693a68008baacd827b1345c957e871488596 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 26 Sep 2006 01:57:02 +0200 Subject: gitweb: Split validate_input into validate_pathname and validate_refname Split validate_input subroutine into validate_pathname which is used for $project, $file_name and $file_parent parameters, and validate_refname which is used for $hash, $hash_base, $hash_parent and $hash_parent_base parameters. Reintroduce validation of $file_name and $file_parent parameters, removed in a2f3db2f validate_pathname in addition to what validate_input did checks also for doubled slashes and NUL character. It does not check if input is textual hash, and does not check if all characters are from the following set: [a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]. validate_refname first check if the input is textual hash, then checks if it is valid pathname, then checks for invalid characters (according to git-check-ref-format manpage). It does not check if all charactes are from the [a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%] set. We do not have to validate pathnames we got from git. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index eaa42b9..c513135 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -200,9 +200,10 @@ if (defined $action) { } } +# parameters which are pathnames our $project = $cgi->param('p'); if (defined $project) { - if (!validate_input($project) || + if (!validate_pathname($project) || !(-d "$projectroot/$project") || !(-e "$projectroot/$project/HEAD") || ($export_ok && !(-e "$projectroot/$project/$export_ok")) || @@ -212,38 +213,50 @@ if (defined $project) { } } -# We have to handle those containing any characters: our $file_name = $cgi->param('f'); +if (defined $file_name) { + if (!validate_pathname($file_name)) { + die_error(undef, "Invalid file parameter"); + } +} + our $file_parent = $cgi->param('fp'); +if (defined $file_parent) { + if (!validate_pathname($file_parent)) { + die_error(undef, "Invalid file parent parameter"); + } +} +# parameters which are refnames our $hash = $cgi->param('h'); if (defined $hash) { - if (!validate_input($hash)) { + if (!validate_refname($hash)) { die_error(undef, "Invalid hash parameter"); } } our $hash_parent = $cgi->param('hp'); if (defined $hash_parent) { - if (!validate_input($hash_parent)) { + if (!validate_refname($hash_parent)) { die_error(undef, "Invalid hash parent parameter"); } } our $hash_base = $cgi->param('hb'); if (defined $hash_base) { - if (!validate_input($hash_base)) { + if (!validate_refname($hash_base)) { die_error(undef, "Invalid hash base parameter"); } } our $hash_parent_base = $cgi->param('hpb'); if (defined $hash_parent_base) { - if (!validate_input($hash_parent_base)) { + if (!validate_refname($hash_parent_base)) { die_error(undef, "Invalid hash parent base parameter"); } } +# other parameters our $page = $cgi->param('pg'); if (defined $page) { if ($page =~ m/[^0-9]/) { @@ -273,7 +286,7 @@ sub evaluate_path_info { $project =~ s,/*[^/]*$,,; } # validate project - $project = validate_input($project); + $project = validate_pathname($project); if (!$project || ($export_ok && !-e "$projectroot/$project/$export_ok") || ($strict_export && !project_in_list($project))) { @@ -294,12 +307,12 @@ sub evaluate_path_info { } else { $action ||= "blob_plain"; } - $hash_base ||= validate_input($refname); - $file_name ||= $pathname; + $hash_base ||= validate_refname($refname); + $file_name ||= validate_pathname($pathname); } elsif (defined $refname) { # we got "project.git/branch" $action ||= "shortlog"; - $hash ||= validate_input($refname); + $hash ||= validate_refname($refname); } } evaluate_path_info(); @@ -387,16 +400,34 @@ sub href(%) { ## ====================================================================== ## validation, quoting/unquoting and escaping -sub validate_input { - my $input = shift; +sub validate_pathname { + my $input = shift || return undef; - if ($input =~ m/^[0-9a-fA-F]{40}$/) { - return $input; + # no '.' or '..' as elements of path, i.e. no '.' nor '..' + # at the beginning, at the end, and between slashes. + # also this catches doubled slashes + if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) { + return undef; } - if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { + # no null characters + if ($input =~ m!\0!) { return undef; } - if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { + return $input; +} + +sub validate_refname { + my $input = shift || return undef; + + # textual hashes are O.K. + if ($input =~ m/^[0-9a-fA-F]{40}$/) { + return $input; + } + # it must be correct pathname + $input = validate_pathname($input) + or return undef; + # restrictions on ref name according to git-check-ref-format + if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) { return undef; } return $input; -- cgit v0.10.2-6-g49f6 From f93bff8d4531d19938a9afbdc28b8d8f4dc97b32 Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 26 Sep 2006 01:58:41 +0200 Subject: gitweb: Add git_url subroutine, and use it to quote full URLs Add git_url subroutine, which does what git_param did before commit a2f3db2f5de2a3667b0e038aa65e3e097e642e7d, and is used to quote full URLs, currently only $home_link. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c513135..093ee60 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -443,6 +443,15 @@ sub esc_param { return $str; } +# quote unsafe chars in whole URL, so some charactrs cannot be quoted +sub esc_url { + my $str = shift; + $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; + $str =~ s/\+/%2B/g; + $str =~ s/ /\+/g; + return $str; +} + # replace invalid utf8 character with SUBSTITUTION sequence sub esc_html { my $str = shift; @@ -1359,7 +1368,7 @@ EOF "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" . "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" . "</a>\n"; - print $cgi->a({-href => esc_param($home_link)}, $home_link_str) . " / "; + print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; if (defined $project) { print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); if (defined $action) { -- cgit v0.10.2-6-g49f6 From ab41dfbfd4f3f9fedac71550027e9813b11abe3d Mon Sep 17 00:00:00 2001 From: Jakub Narebski <jnareb@gmail.com> Date: Tue, 26 Sep 2006 01:59:43 +0200 Subject: gitweb: Quote filename in HTTP Content-Disposition: header Finish work started by commit a2f3db2 (although not documented in commit message) of quoting using quotemeta the filename in HTTP -content_disposition header. Just in case filename contains end of line character. Also use consistent coding style to compute -content_disposition parameter. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 093ee60..9349fa1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2320,7 +2320,7 @@ sub git_project_index { print $cgi->header( -type => 'text/plain', -charset => 'utf-8', - -content_disposition => qq(inline; filename="index.aux")); + -content_disposition => 'inline; filename="index.aux"'); foreach my $pr (@projects) { if (!exists $pr->{'owner'}) { @@ -2682,7 +2682,7 @@ sub git_blob_plain { print $cgi->header( -type => "$type", -expires=>$expires, - -content_disposition => "inline; filename=\"$save_as\""); + -content_disposition => 'inline; filename="' . quotemeta($save_as) . '"'); undef $/; binmode STDOUT, ':raw'; print <$fd>; @@ -2856,10 +2856,11 @@ sub git_snapshot { my $filename = basename($project) . "-$hash.tar.$suffix"; - print $cgi->header(-type => 'application/x-tar', - -content_encoding => $ctype, - -content_disposition => "inline; filename=\"$filename\"", - -status => '200 OK'); + print $cgi->header( + -type => 'application/x-tar', + -content_encoding => $ctype, + -content_disposition => 'inline; filename="' . quotemeta($filename) . '"', + -status => '200 OK'); my $git_command = git_cmd_str(); open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or @@ -3169,7 +3170,7 @@ sub git_blobdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => qq(inline; filename=") . quotemeta($file_name) . qq(.patch")); + -content_disposition => 'inline; filename="' . quotemeta($file_name) . '.patch"'); print "X-Git-Url: " . $cgi->self_url() . "\n\n"; @@ -3272,7 +3273,7 @@ sub git_commitdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => qq(inline; filename="$filename")); + -content_disposition => 'inline; filename="' . quotemeta($filename) . '"'); my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); print <<TEXT; From: $co{'author'} -- cgit v0.10.2-6-g49f6 From 5a03e7f25334a6bf1dbbfdb9830d41de5b8f0d7f Mon Sep 17 00:00:00 2001 From: Shawn Pearce <spearce@spearce.org> Date: Mon, 25 Sep 2006 01:24:38 -0400 Subject: Allow git-checkout when on a non-existant branch. I've seen some users get into situtations where their HEAD symbolic-ref is pointing at a non-existant ref. (Sometimes this happens during clone when the remote repository lacks a 'master' branch.) If this happens the user is unable to use git-checkout to switch branches as there is no prior commit to merge from. So instead of giving the user low-level errors about how HEAD can't be resolved and how not a single revision was given change the type of checkout to be a force and go through with the user's request anyway. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-checkout.sh b/git-checkout.sh index 580a9e8..dd47724 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -4,8 +4,8 @@ USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]' SUBDIRECTORY_OK=Sometimes . git-sh-setup -old=$(git-rev-parse HEAD) old_name=HEAD +old=$(git-rev-parse --verify $old_name 2>/dev/null) new= new_name= force= @@ -139,6 +139,13 @@ fi die "git checkout: to checkout the requested commit you need to specify a name for a new branch which is created and switched to" +if [ "X$old" = X ] +then + echo "warning: You do not appear to currently be on a branch." >&2 + echo "warning: Forcing checkout of $new_name." >&2 + force=1 +fi + if [ "$force" ] then git-read-tree --reset -u $new diff --git a/t/t7201-co.sh b/t/t7201-co.sh index b64e8b7..085d4a0 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -31,6 +31,15 @@ test_expect_success setup ' git checkout master ' +test_expect_success "checkout from non-existing branch" ' + + git checkout -b delete-me master && + rm .git/refs/heads/delete-me && + test refs/heads/delete-me = "$(git symbolic-ref HEAD)" && + git checkout master && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + test_expect_success "checkout with dirty tree without -m" ' fill 0 1 2 3 4 5 >one && -- cgit v0.10.2-6-g49f6 From 4431fcc4b134ae501e3e57dc568ae4f031e57898 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 01:09:18 -0700 Subject: Clean-up lock-ref implementation This drops "mustexist" parameter lock_ref_sha1() and lock_any_ref_forupdate() functions take. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index db57fee..4093973 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -53,7 +53,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, /* make sure nobody touched the ref, and unlink */ static void prune_ref(struct ref_to_prune *r) { - struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1, 1); + struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1); if (lock) { unlink(git_path("%s", r->name)); diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 90a3da5..ab52833 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -48,7 +48,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); - lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0); + lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL); if (!lock) return 1; if (write_ref_sha1(lock, sha1, msg) < 0) diff --git a/fetch.c b/fetch.c index a2cbdfb..c426c04 100644 --- a/fetch.c +++ b/fetch.c @@ -266,7 +266,7 @@ int pull(int targets, char **target, const char **write_ref, if (!write_ref || !write_ref[i]) continue; - lock[i] = lock_ref_sha1(write_ref[i], NULL, 0); + lock[i] = lock_ref_sha1(write_ref[i], NULL); if (!lock[i]) { error("Can't lock ref %s", write_ref[i]); goto unlock_and_fail; diff --git a/refs.c b/refs.c index 2cef2b4..9a1bc0d 100644 --- a/refs.c +++ b/refs.c @@ -447,12 +447,13 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } -static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int mustexist) +static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1) { char *ref_file; const char *orig_ref = ref; struct ref_lock *lock; struct stat st; + int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; @@ -480,20 +481,18 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; } -struct ref_lock *lock_ref_sha1(const char *ref, - const unsigned char *old_sha1, int mustexist) +struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) { char refpath[PATH_MAX]; if (check_ref_format(ref)) return NULL; strcpy(refpath, mkpath("refs/%s", ref)); - return lock_ref_sha1_basic(refpath, old_sha1, mustexist); + return lock_ref_sha1_basic(refpath, old_sha1); } -struct ref_lock *lock_any_ref_for_update(const char *ref, - const unsigned char *old_sha1, int mustexist) +struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1) { - return lock_ref_sha1_basic(ref, old_sha1, mustexist); + return lock_ref_sha1_basic(ref, old_sha1); } void unlock_ref(struct ref_lock *lock) diff --git a/refs.h b/refs.h index 305d408..0d4d79e 100644 --- a/refs.h +++ b/refs.h @@ -27,10 +27,10 @@ extern int for_each_remote_ref(each_ref_fn, void *); extern int get_ref_sha1(const char *ref, unsigned char *sha1); /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ -extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist); +extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1); /** Locks any ref (for 'HEAD' type refs). */ -extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist); +extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1); /** Release any lock taken but not written. **/ extern void unlock_ref(struct ref_lock *lock); -- cgit v0.10.2-6-g49f6 From ac5409e420e5fdd7c4a381f873ffcedfb83d7117 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 01:58:57 -0700 Subject: update-ref: -d flag and ref creation safety. This adds -d flag to update-ref to allow safe deletion of ref. Before deleting it, the command checks if the given <oldvalue> still matches the value the caller thought the ref contained. Similarly, it also accepts 0{40} or an empty string as <oldvalue> to allow safe creation of a new ref. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index e062030..71bcb79 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely SYNOPSIS -------- -'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>] +'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>]) DESCRIPTION ----------- @@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that the current value of the <ref> matches <oldvalue>. E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>` updates the master branch head to <newvalue> only if its current -value is <oldvalue>. +value is <oldvalue>. You can specify 40 "0" or an empty string +as <oldvalue> to make sure that the ref you are creating does +not exist. It also allows a "ref" file to be a symbolic pointer to another ref file by starting with the four-byte header sequence of @@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a ref symlink to some other tree, if you have copied a whole archive by creating a symlink tree). +With `-d` flag, it deletes the named <ref> after verifying it +still contains <oldvalue>. + + Logging Updates --------------- If config parameter "core.logAllRefUpdates" is true or the file diff --git a/builtin-update-ref.c b/builtin-update-ref.c index ab52833..b34e598 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -3,15 +3,16 @@ #include "builtin.h" static const char git_update_ref_usage[] = -"git-update-ref <refname> <value> [<oldval>] [-m <reason>]"; +"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])"; int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL; struct ref_lock *lock; unsigned char sha1[20], oldsha1[20]; - int i; + int i, delete; + delete = 0; setup_ident(); git_config(git_default_config); @@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("Refusing to perform update with \\n in message."); continue; } + if (!strcmp("-d", argv[i])) { + delete = 1; + continue; + } if (!refname) { refname = argv[i]; continue; @@ -44,8 +49,15 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (get_sha1(value, sha1)) die("%s: not a valid SHA1", value); + + if (delete) { + if (oldval) + usage(git_update_ref_usage); + return delete_ref(refname, sha1); + } + hashclr(oldsha1); - if (oldval && get_sha1(oldval, oldsha1)) + if (oldval && *oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL); diff --git a/cache.h b/cache.h index 6def155..6e00450 100644 --- a/cache.h +++ b/cache.h @@ -179,6 +179,7 @@ struct lock_file { extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); extern void rollback_lock_file(struct lock_file *); +extern int delete_ref(const char *, unsigned char *sha1); /* Environment bits from configuration mechanism */ extern int use_legacy_headers; diff --git a/refs.c b/refs.c index 9a1bc0d..3d4cdd1 100644 --- a/refs.c +++ b/refs.c @@ -378,6 +378,32 @@ int get_ref_sha1(const char *ref, unsigned char *sha1) return read_ref(mkpath("refs/%s", ref), sha1); } +int delete_ref(const char *refname, unsigned char *sha1) +{ + struct ref_lock *lock; + int err, i, ret = 0; + + lock = lock_any_ref_for_update(refname, sha1); + if (!lock) + return 1; + i = strlen(lock->lk->filename) - 5; /* .lock */ + lock->lk->filename[i] = 0; + err = unlink(lock->lk->filename); + if (err) { + ret = 1; + error("unlink(%s) failed: %s", + lock->lk->filename, strerror(errno)); + } + lock->lk->filename[i] = '.'; + + err = unlink(lock->log_file); + if (err && errno != ENOENT) + fprintf(stderr, "warning: unlink(%s) failed: %s", + lock->log_file, strerror(errno)); + + return ret; +} + /* * Make sure "ref" is something reasonable to have under ".git/refs/"; * We do not like it if: -- cgit v0.10.2-6-g49f6 From cede7526534c47436de17977eb39e55aa8c1e646 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 02:06:31 -0700 Subject: update a few Porcelain-ish for ref lock safety. This updates the use of git-update-ref in git-branch, git-tag and git-commit to make them safer in a few corner cases as demonstration. - git-tag makes sure that the named tag does not exist, allows you to edit tag message and then creates the tag. If a tag with the same name was created by somebody else in the meantime, it used to happily overwrote it. Now it notices the situation. - git-branch -d and git-commit (for the initial commit) had the same issue but with smaller race window, which is plugged with this. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-branch.sh b/git-branch.sh index 2600e9c..4379a07 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -42,8 +42,7 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'." esac ;; esac - rm -f "$GIT_DIR/logs/refs/heads/$branch_name" - rm -f "$GIT_DIR/refs/heads/$branch_name" + git update-ref -d "refs/heads/$branch_name" "$branch" echo "Deleted branch $branch_name." done exit 0 @@ -112,6 +111,7 @@ rev=$(git-rev-parse --verify "$head") || exit git-check-ref-format "heads/$branchname" || die "we do not like '$branchname' as a branch name." +prev='' if git-show-ref --verify --quiet -- "refs/heads/$branchname" then if test '' = "$force" @@ -121,10 +121,11 @@ then then die "cannot force-update the current branch." fi + prev=`git rev-parse --verify "refs/heads/$branchname"` fi if test "$create_log" = 'yes' then mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname") touch "$GIT_DIR/logs/refs/heads/$branchname" fi -git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev +git update-ref -m "branch: Created from $head" "refs/heads/$branchname" "$rev" "$prev" diff --git a/git-commit.sh b/git-commit.sh index 5a4c659..ee5a165 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -547,15 +547,15 @@ then PARENTS=$(git-cat-file commit HEAD | sed -n -e '/^$/q' -e 's/^parent /-p /p') fi - current=$(git-rev-parse --verify HEAD) + current="$(git-rev-parse --verify HEAD)" else if [ -z "$(git-ls-files)" ]; then echo >&2 Nothing to commit exit 1 fi PARENTS="" - current= rloga='commit (initial)' + current='' fi if test -z "$no_edit" @@ -631,7 +631,7 @@ then fi && commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) && rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) && - git-update-ref -m "$rloga: $rlogm" HEAD $commit $current && + git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" && rm -f -- "$GIT_DIR/MERGE_HEAD" && if test -f "$NEXT_INDEX" then diff --git a/git-tag.sh b/git-tag.sh index a0afa25..2bde3c0 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -63,8 +63,11 @@ done name="$1" [ "$name" ] || usage -if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then - die "tag '$name' already exists" +prev=0000000000000000000000000000000000000000 +if test -e "$GIT_DIR/refs/tags/$name" +then + test -n "$force" || die "tag '$name' already exists" + prev=`git rev-parse "refs/tags/$name"` fi shift git-check-ref-format "tags/$name" || @@ -109,4 +112,4 @@ fi leading=`expr "refs/tags/$name" : '\(.*\)/'` && mkdir -p "$GIT_DIR/$leading" && -echo $object > "$GIT_DIR/refs/tags/$name" +GIT_DIR="$GIT_DIR" git update-ref "refs/tags/$name" "$object" "$prev" -- cgit v0.10.2-6-g49f6 From 3159c8dc2da4c88dcecabb95404fe88edc5441e4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 02:40:06 -0700 Subject: Teach receive-pack about ref-log This converts receive-pack to use the standard ref locking code instead of its own. As a side effect, it automatically records the "push" event to ref-log if enabled. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/receive-pack.c b/receive-pack.c index abbcb6a..f0b4cb4 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -41,34 +41,6 @@ struct command { static struct command *commands; -static int is_all_zeroes(const char *hex) -{ - int i; - for (i = 0; i < 40; i++) - if (*hex++ != '0') - return 0; - return 1; -} - -static int verify_old_ref(const char *name, char *hex_contents) -{ - int fd, ret; - char buffer[60]; - - if (is_all_zeroes(hex_contents)) - return 0; - fd = open(name, O_RDONLY); - if (fd < 0) - return -1; - ret = read(fd, buffer, 40); - close(fd); - if (ret != 40) - return -1; - if (memcmp(buffer, hex_contents, 40)) - return -1; - return 0; -} - static char update_hook[] = "hooks/update"; static int run_update_hook(const char *refname, @@ -105,8 +77,8 @@ static int update(struct command *cmd) const char *name = cmd->ref_name; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; - char new_hex[60], *old_hex, *lock_name; - int newfd, namelen, written; + char new_hex[41], old_hex[41]; + struct ref_lock *lock; cmd->error_string = NULL; if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) { @@ -115,59 +87,27 @@ static int update(struct command *cmd) name); } - namelen = strlen(name); - lock_name = xmalloc(namelen + 10); - memcpy(lock_name, name, namelen); - memcpy(lock_name + namelen, ".lock", 6); - strcpy(new_hex, sha1_to_hex(new_sha1)); - old_hex = sha1_to_hex(old_sha1); + strcpy(old_hex, sha1_to_hex(old_sha1)); if (!has_sha1_file(new_sha1)) { cmd->error_string = "bad pack"; return error("unpack should have generated %s, " "but I can't find it!", new_hex); } - safe_create_leading_directories(lock_name); - - newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (newfd < 0) { - cmd->error_string = "can't lock"; - return error("unable to create %s (%s)", - lock_name, strerror(errno)); - } - - /* Write the ref with an ending '\n' */ - new_hex[40] = '\n'; - new_hex[41] = 0; - written = write(newfd, new_hex, 41); - /* Remove the '\n' again */ - new_hex[40] = 0; - - close(newfd); - if (written != 41) { - unlink(lock_name); - cmd->error_string = "can't write"; - return error("unable to write %s", lock_name); - } - if (verify_old_ref(name, old_hex) < 0) { - unlink(lock_name); - cmd->error_string = "raced"; - return error("%s changed during push", name); - } if (run_update_hook(name, old_hex, new_hex)) { - unlink(lock_name); cmd->error_string = "hook declined"; return error("hook declined to update %s", name); } - else if (rename(lock_name, name) < 0) { - unlink(lock_name); - cmd->error_string = "can't rename"; - return error("unable to replace %s", name); - } - else { - fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); - return 0; + + lock = lock_any_ref_for_update(name, old_sha1); + if (!lock) { + cmd->error_string = "failed to lock"; + return error("failed to lock %s", name); } + write_ref_sha1(lock, new_sha1, "push"); + + fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); + return 0; } static char update_post_hook[] = "hooks/post-update"; @@ -318,9 +258,11 @@ int main(int argc, char **argv) if (!dir) usage(receive_pack_usage); - if(!enter_repo(dir, 0)) + if (!enter_repo(dir, 0)) die("'%s': unable to chdir or not a git archive", dir); + git_config(git_default_config); + write_head_info(); /* EOF */ -- cgit v0.10.2-6-g49f6 From a2540023dcf833ca02d87c28052e8b7135d56b60 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 26 Sep 2006 18:53:02 -0700 Subject: diff --stat: allow custom diffstat output width. This adds two parameters to "diff --stat". . --stat-width=72 tells that the page should fit on 72-column output. . --stat-name-width=30 tells that the filename part is limited to 30 columns. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff.c b/diff.c index 443e248..8d299f4 100644 --- a/diff.c +++ b/diff.c @@ -545,21 +545,64 @@ static void diffstat_consume(void *priv, char *line, unsigned long len) x->deleted++; } -static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= "----------------------------------------------------------------------"; const char mime_boundary_leader[] = "------------"; -static void show_stats(struct diffstat_t* data) +static int scale_linear(int it, int width, int max_change) +{ + /* + * round(width * it / max_change); + */ + return (it * width * 2 + max_change) / (max_change * 2); +} + +static void show_name(const char *prefix, const char *name, int len) +{ + printf(" %s%-*s |", prefix, len, name); +} + +static void show_graph(char ch, int cnt) +{ + if (cnt <= 0) + return; + while (cnt--) + putchar(ch); +} + +static void show_stats(struct diffstat_t* data, struct diff_options *options) { int i, len, add, del, total, adds = 0, dels = 0; - int max, max_change = 0, max_len = 0; + int max_change = 0, max_len = 0; int total_files = data->nr; + int width, name_width; if (data->nr == 0) return; + width = options->stat_width ? options->stat_width : 80; + name_width = options->stat_name_width ? options->stat_name_width : 50; + + /* Sanity: give at least 5 columns to the graph, + * but leave at least 10 columns for the name. + */ + if (width < name_width + 15) { + if (name_width <= 25) + width = name_width + 15; + else + name_width = width - 15; + } + + /* Find the longest filename and max number of changes */ for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; + int change = file->added + file->deleted; + + len = quote_c_style(file->name, NULL, NULL, 0); + if (len) { + char *qname = xmalloc(len + 1); + quote_c_style(file->name, qname, NULL, 0); + free(file->name); + file->name = qname; + } len = strlen(file->name); if (max_len < len) @@ -567,54 +610,53 @@ static void show_stats(struct diffstat_t* data) if (file->is_binary || file->is_unmerged) continue; - if (max_change < file->added + file->deleted) - max_change = file->added + file->deleted; + if (max_change < change) + max_change = change; } + /* Compute the width of the graph part; + * 10 is for one blank at the beginning of the line plus + * " | count " between the name and the graph. + * + * From here on, name_width is the width of the name area, + * and width is the width of the graph area. + */ + name_width = (name_width < max_len) ? name_width : max_len; + if (width < (name_width + 10) + max_change) + width = width - (name_width + 10); + else + width = max_change; + for (i = 0; i < data->nr; i++) { const char *prefix = ""; char *name = data->files[i]->name; int added = data->files[i]->added; int deleted = data->files[i]->deleted; - - if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { - char *qname = xmalloc(len + 1); - quote_c_style(name, qname, NULL, 0); - free(name); - data->files[i]->name = name = qname; - } + int name_len; /* * "scale" the filename */ - len = strlen(name); - max = max_len; - if (max > 50) - max = 50; - if (len > max) { + len = name_width; + name_len = strlen(name); + if (name_width < name_len) { char *slash; prefix = "..."; - max -= 3; - name += len - max; + len -= 3; + name += name_len - len; slash = strchr(name, '/'); if (slash) name = slash; } - len = max; - - /* - * scale the add/delete - */ - max = max_change; - if (max + len > 70) - max = 70 - len; if (data->files[i]->is_binary) { - printf(" %s%-*s | Bin\n", prefix, len, name); + show_name(prefix, name, len); + printf(" Bin\n"); goto free_diffstat_file; } else if (data->files[i]->is_unmerged) { - printf(" %s%-*s | Unmerged\n", prefix, len, name); + show_name(prefix, name, len); + printf(" Unmerged\n"); goto free_diffstat_file; } else if (!data->files[i]->is_renamed && @@ -623,27 +665,32 @@ static void show_stats(struct diffstat_t* data) goto free_diffstat_file; } + /* + * scale the add/delete + */ add = added; del = deleted; total = add + del; adds += add; dels += del; - if (max_change > 0) { - total = (total * max + max_change / 2) / max_change; - add = (add * max + max_change / 2) / max_change; + if (width <= max_change) { + total = scale_linear(total, width, max_change); + add = scale_linear(add, width, max_change); del = total - add; } - printf(" %s%-*s |%5d %.*s%.*s\n", prefix, - len, name, added + deleted, - add, pluses, del, minuses); + show_name(prefix, name, len); + printf("%5d ", added + deleted); + show_graph('+', add); + show_graph('-', del); + putchar('\n'); free_diffstat_file: free(data->files[i]->name); free(data->files[i]); } free(data->files); printf(" %d files changed, %d insertions(+), %d deletions(-)\n", - total_files, adds, dels); + total_files, adds, dels); } struct checkdiff_t { @@ -1681,6 +1728,14 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if (!strcmp(arg, "--stat")) options->output_format |= DIFF_FORMAT_DIFFSTAT; + else if (!strncmp(arg, "--stat-width=", 13)) { + options->stat_width = strtoul(arg + 13, NULL, 10); + options->output_format |= DIFF_FORMAT_DIFFSTAT; + } + else if (!strncmp(arg, "--stat-name-width=", 18)) { + options->stat_name_width = strtoul(arg + 18, NULL, 10); + options->output_format |= DIFF_FORMAT_DIFFSTAT; + } else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; else if (!strcmp(arg, "--summary")) @@ -2438,7 +2493,7 @@ void diff_flush(struct diff_options *options) if (check_pair_status(p)) diff_flush_stat(p, options, &diffstat); } - show_stats(&diffstat); + show_stats(&diffstat, options); separator++; } diff --git a/diff.h b/diff.h index b60a02e..e06d0f4 100644 --- a/diff.h +++ b/diff.h @@ -69,6 +69,9 @@ struct diff_options { const char *stat_sep; long xdl_opts; + int stat_width; + int stat_name_width; + int nr_paths; const char **paths; int *pathlens; -- cgit v0.10.2-6-g49f6 From 785f743276991567a36420f069c228e4dc9d0df3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 26 Sep 2006 18:59:41 -0700 Subject: diff --stat: color output. Under --color option, diffstat shows '+' and '-' in the graph the same color as added and deleted lines. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff.c b/diff.c index 8d299f4..3fd7a52 100644 --- a/diff.c +++ b/diff.c @@ -555,17 +555,20 @@ static int scale_linear(int it, int width, int max_change) return (it * width * 2 + max_change) / (max_change * 2); } -static void show_name(const char *prefix, const char *name, int len) +static void show_name(const char *prefix, const char *name, int len, + const char *reset, const char *set) { - printf(" %s%-*s |", prefix, len, name); + printf(" %s%s%-*s%s |", set, prefix, len, name, reset); } -static void show_graph(char ch, int cnt) +static void show_graph(char ch, int cnt, const char *set, const char *reset) { if (cnt <= 0) return; + printf("%s", set); while (cnt--) putchar(ch); + printf("%s", reset); } static void show_stats(struct diffstat_t* data, struct diff_options *options) @@ -574,6 +577,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) int max_change = 0, max_len = 0; int total_files = data->nr; int width, name_width; + const char *reset, *set, *add_c, *del_c; if (data->nr == 0) return; @@ -592,6 +596,11 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) } /* Find the longest filename and max number of changes */ + reset = diff_get_color(options->color_diff, DIFF_RESET); + set = diff_get_color(options->color_diff, DIFF_PLAIN); + add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW); + del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD); + for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; int change = file->added + file->deleted; @@ -650,12 +659,12 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) } if (data->files[i]->is_binary) { - show_name(prefix, name, len); + show_name(prefix, name, len, reset, set); printf(" Bin\n"); goto free_diffstat_file; } else if (data->files[i]->is_unmerged) { - show_name(prefix, name, len); + show_name(prefix, name, len, reset, set); printf(" Unmerged\n"); goto free_diffstat_file; } @@ -679,18 +688,18 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) add = scale_linear(add, width, max_change); del = total - add; } - show_name(prefix, name, len); + show_name(prefix, name, len, reset, set); printf("%5d ", added + deleted); - show_graph('+', add); - show_graph('-', del); + show_graph('+', add, add_c, reset); + show_graph('-', del, del_c, reset); putchar('\n'); free_diffstat_file: free(data->files[i]->name); free(data->files[i]); } free(data->files); - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", - total_files, adds, dels); + printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n", + set, total_files, adds, dels, reset); } struct checkdiff_t { -- cgit v0.10.2-6-g49f6 From 16652170bf80542fd77de75fb88da2f7761f65c4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 12:34:37 -0700 Subject: An illustration of rev-list --parents --pretty=raw This script creates two separate histories, A and B, each of which does: (A0, B0): create fileA and subdir/fileB (A1, B1): modify fileA (A2, B2): modify subdir/fileB and then grafts them together to make B0 a child of A2. So the final history looks like (time flows from top to bottom): true parent touches subdir? A0 none yes (creates it) A1 A0 no A2 A1 yes B0 none yes (different from what's in A2) B1 B0 no B2 B1 yes "git rev-list --parents --pretty=raw B2" would give "fake" parents on the "commit " header lines while "parent " header lines show the parent as recorded in the commit object (i.e. B0 appears to have A2 as its parent on "commit " header but there is no "parent A2" header line in it). When you have path limiters, we simplify history to omit commits that do not affect the specified paths. So "git rev-list --parents --pretty=raw B2 subdir" would return "B2 B0 A2 A0" (because B1 and A1 do not touch the path). When it does so, the "commit " header lines have "fake" parents (i.e. B2 appears to have B0 as its parent on "commit " header), but you can still get the true parents by looking at "parent " header. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh new file mode 100755 index 0000000..b2131cd --- /dev/null +++ b/t/t6001-rev-list-graft.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='Revision traversal vs grafts and path limiter' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir subdir && + echo >fileA fileA && + echo >subdir/fileB fileB && + git add fileA subdir/fileB && + git commit -a -m "Initial in one history." && + A0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified && + git commit -a -m "Second in one history." && + A1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified && + git commit -a -m "Third in one history." && + A2=`git rev-parse --verify HEAD` && + + rm -f .git/refs/heads/master .git/index && + + echo >fileA fileA again && + echo >subdir/fileB fileB again && + git add fileA subdir/fileB && + git commit -a -m "Initial in alternate history." && + B0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified in alternate history && + git commit -a -m "Second in alternate history." && + B1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified in alternate history && + git commit -a -m "Third in alternate history." && + B2=`git rev-parse --verify HEAD` && + : done +' + +check () { + type=$1 + shift + + arg= + which=arg + rm -f test.expect + for a + do + if test "z$a" = z-- + then + which=expect + child= + continue + fi + if test "$which" = arg + then + arg="$arg$a " + continue + fi + if test "$type" = basic + then + echo "$a" + else + if test "z$child" != z + then + echo "$child $a" + fi + child="$a" + fi + done >test.expect + if test "$type" != basic && test "z$child" != z + then + echo >>test.expect $child + fi + if test $type = basic + then + git rev-list $arg >test.actual + elif test $type = parents + then + git rev-list --parents $arg >test.actual + elif test $type = parents-raw + then + git rev-list --parents --pretty=raw $arg | + sed -n -e 's/^commit //p' >test.actual + fi + diff test.expect test.actual +} + +for type in basic parents parents-raw +do + test_expect_success 'without grafts' " + rm -f .git/info/grafts + check $type $B2 -- $B2 $B1 $B0 + " + + test_expect_success 'with grafts' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0 + " + + test_expect_success 'without grafts, with pathlimit' " + rm -f .git/info/grafts + check $type $B2 subdir -- $B2 $B0 + " + + test_expect_success 'with grafts, with pathlimit' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 subdir -- $B2 $B0 $A2 $A0 + " + +done +test_done -- cgit v0.10.2-6-g49f6 From f130446920b550a69716346fb9a9947c04fc7f90 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Wed, 27 Sep 2006 15:30:21 -0400 Subject: zap a debug remnant Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 6db97b6..16e98f3 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -440,7 +440,6 @@ static unsigned long write_object(struct sha1file *f, + entry->in_pack_header_size; datalen = find_packed_object_size(p, entry->in_pack_offset) - entry->in_pack_header_size; -//fprintf(stderr, "reusing %d at %d header %d size %d\n", obj_type, entry->in_pack_offset, entry->in_pack_header_size, datalen); if (!pack_to_stdout && check_inflate(buf, datalen, entry->size)) die("corrupt delta in pack %s", sha1_to_hex(entry->sha1)); sha1write(f, buf, datalen); -- cgit v0.10.2-6-g49f6 From 209e7569313aa045da6d55e333c884e49e7d8fb2 Mon Sep 17 00:00:00 2001 From: Shawn Pearce <spearce@spearce.org> Date: Wed, 27 Sep 2006 11:18:49 -0400 Subject: Corrected copy-and-paste thinko in ignore executable bit test case. This test should be testing update-index --add, not git-add as the latter is implemented in terms of the former. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t3700-add.sh b/t/t3700-add.sh index d36f22d..c20e4c2 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -35,7 +35,7 @@ test_expect_success \ 'git repo-config core.filemode 0 && echo foo >xfoo2 && chmod 755 xfoo2 && - git-add xfoo2 && + git-update-index --add xfoo2 && case "`git-ls-files --stage xfoo2`" in 100644" "*xfoo2) echo ok;; *) echo fail; git-ls-files --stage xfoo2; exit 1;; -- cgit v0.10.2-6-g49f6 From a2700696995651322796e04092bf4a4bfed31b88 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre <nico@cam.org> Date: Wed, 27 Sep 2006 15:42:16 -0400 Subject: allow delta data reuse even if base object is a preferred base Signed-off-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 16e98f3..ee5f031 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -988,7 +988,7 @@ static void check_object(struct object_entry *entry) unuse_packed_git(p); entry->in_pack_header_size = used; - if (base_entry && !base_entry->preferred_base) { + if (base_entry) { /* Depth value does not matter - find_deltas() * will never consider reused delta as the -- cgit v0.10.2-6-g49f6 From 94d8213f2c98c4a5fd50484fcb11b4b24b403294 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 13:47:21 -0700 Subject: receive-pack: call setup_ident before git_config Otherwise we would end up getting values from Gecos which is often not what people would want. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/receive-pack.c b/receive-pack.c index f0b4cb4..c8aacbb 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -261,6 +261,7 @@ int main(int argc, char **argv) if (!enter_repo(dir, 0)) die("'%s': unable to chdir or not a git archive", dir); + setup_ident(); git_config(git_default_config); write_head_info(); -- cgit v0.10.2-6-g49f6 From b48fb5b6a950a6757b790e9160967065a3e03978 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 16:27:10 -0700 Subject: grep: free expressions and patterns when done. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-grep.c b/builtin-grep.c index 6718788..4205e5d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -325,6 +325,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) else hit |= grep_file(opt, ce->name); } + free_grep_patterns(opt); return hit; } @@ -694,5 +695,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (grep_object(&opt, paths, real_obj, list.objects[i].name)) hit = 1; } + free_grep_patterns(&opt); return !hit; } diff --git a/grep.c b/grep.c index cc8d684..2c740bd 100644 --- a/grep.c +++ b/grep.c @@ -167,6 +167,46 @@ void compile_grep_patterns(struct grep_opt *opt) die("incomplete pattern expression: %s", p->pattern); } +static void free_pattern_expr(struct grep_expr *x) +{ + switch (x->node) { + case GREP_NODE_ATOM: + break; + case GREP_NODE_NOT: + free_pattern_expr(x->u.unary); + break; + case GREP_NODE_AND: + case GREP_NODE_OR: + free_pattern_expr(x->u.binary.left); + free_pattern_expr(x->u.binary.right); + break; + } + free(x); +} + +void free_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p, *n; + + for (p = opt->pattern_list; p; p = n) { + n = p->next; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + regfree(&p->regexp); + break; + default: + break; + } + free(p); + } + + if (!opt->extended) + return; + free_pattern_expr(opt->pattern_expression); +} + static char *end_of_line(char *cp, unsigned long *left) { unsigned long l = *left; @@ -439,6 +479,8 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long lno++; } + free(prev); + if (opt->status_only) return 0; if (opt->unmatch_name_only) { diff --git a/grep.h b/grep.h index 0b503ea..af9098c 100644 --- a/grep.h +++ b/grep.h @@ -73,6 +73,7 @@ struct grep_opt { extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); extern void compile_grep_patterns(struct grep_opt *opt); +extern void free_grep_patterns(struct grep_opt *opt); extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size); #endif -- cgit v0.10.2-6-g49f6 From a3f5d02edb2c1a037ed3ed8d2ebd3f3e5da9d198 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 16:42:53 -0700 Subject: grep: fix --fixed-strings combined with expression. "git grep --fixed-strings -e GIT --and -e VERSION .gitignore" misbehaved because we did not notice this needs to grab lines that have the given two fixed strings at the same time. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/grep.c b/grep.c index 2c740bd..c411ddd 100644 --- a/grep.c +++ b/grep.c @@ -138,16 +138,13 @@ void compile_grep_patterns(struct grep_opt *opt) { struct grep_pat *p; - if (opt->fixed) - return; - - /* First compile regexps */ for (p = opt->pattern_list; p; p = p->next) { switch (p->token) { case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - compile_regexp(p, opt); + if (!opt->fixed) + compile_regexp(p, opt); break; default: opt->extended = 1; -- cgit v0.10.2-6-g49f6 From dd4676299dde0a4c6f8a471e6353170f86a78c8a Mon Sep 17 00:00:00 2001 From: Jon Loeliger <jdl@jdl.com> Date: Tue, 26 Sep 2006 09:47:43 -0500 Subject: Cleaned up git-daemon virtual hosting support. Standardized on lowercase hostnames from client. Added interpolation values for the IP address, port and canonical hostname of the server as it is contacted and named by the client and passed in via the extended args. Added --listen=host_or_ipaddr option suport. Renamed port variable as "listen_port" correspondingly as well. Documented mutual exclusivity of --inetd option with --user, --group, --listen and --port options. Added compat/inet_pton.c from Paul Vixie as needed. Small memory leaks need to be cleaned up still. Signed-off-by: Jon Loeliger <jdl@jdl.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 51d7c94..d562232 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -8,14 +8,15 @@ git-daemon - A really simple server for git repositories SYNOPSIS -------- [verse] -'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] +'git-daemon' [--verbose] [--syslog] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] [--interpolated-path=pathtemplate] + [--reuseaddr] [--detach] [--pid-file=file] [--enable=service] [--disable=service] [--allow-override=service] [--forbid-override=service] - [--reuseaddr] [--detach] [--pid-file=file] - [--user=user [--group=group]] [directory...] + [--inetd | [--listen=host_or_ipaddr] [--port=n] [--user=user [--group=group]] + [directory...] DESCRIPTION ----------- @@ -54,8 +55,12 @@ OPTIONS --interpolated-path=pathtemplate:: To support virtual hosting, an interpolated path template can be used to dynamically construct alternate paths. The template - supports %H for the target hostname as supplied by the client, + supports %H for the target hostname as supplied by the client but + converted to all lowercase, %CH for the canonical hostname, + %IP for the server's IP address, %P for the port number, and %D for the absolute path of the named repository. + After interpolation, the path is validated against the directory + whitelist. --export-all:: Allow pulling from all directories that look like GIT repositories @@ -64,9 +69,17 @@ OPTIONS --inetd:: Have the server run as an inetd service. Implies --syslog. + Incompatible with --port, --listen, --user and --group options. + +--listen=host_or_ipaddr:: + Listen on an a specific IP address or hostname. IP addresses can + be either an IPv4 address or an IPV6 address if supported. If IPv6 + is not supported, then --listen=hostname is also not supported and + --listen must be given an IPv4 address. + Incompatible with '--inetd' option. ---port:: - Listen on an alternative port. +--port=n:: + Listen on an alternative port. Incompatible with '--inetd' option. --init-timeout:: Timeout between the moment the connection is established and the @@ -182,6 +195,24 @@ clients, a symlink from `/software` into the appropriate default repository could be made as well. +git-daemon as regular daemon for virtual hosts:: + To set up `git-daemon` as a regular, non-inetd service that + handles repositories for multiple virtual hosts based on + their IP addresses, start the daemon like this: ++ +------------------------------------------------ + git-daemon --verbose --export-all + --interpolated-path=/pub/%IP/%D + /pub/192.168.1.200/software + /pub/10.10.220.23/software +------------------------------------------------ ++ +In this example, the root-level directory `/pub` will contain +a subdirectory for each virtual host IP address supported. +Repositories can still be accessed by hostname though, assuming +they correspond to these IP addresses. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki diff --git a/Makefile b/Makefile index 28091d6..e68b4c0 100644 --- a/Makefile +++ b/Makefile @@ -524,6 +524,9 @@ endif ifdef NO_INET_NTOP LIB_OBJS += compat/inet_ntop.o endif +ifdef NO_INET_PTON + LIB_OBJS += compat/inet_pton.o +endif ifdef NO_ICONV ALL_CFLAGS += -DNO_ICONV diff --git a/compat/inet_pton.c b/compat/inet_pton.c new file mode 100644 index 0000000..5704e0d --- /dev/null +++ b/compat/inet_pton.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 1996-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <string.h> + +#ifndef NS_INT16SZ +#define NS_INT16SZ 2 +#endif + +#ifndef NS_INADDRSZ +#define NS_INADDRSZ 4 +#endif + +#ifndef NS_IN6ADDRSZ +#define NS_IN6ADDRSZ 16 +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4(const char *src, unsigned char *dst); +static int inet_pton6(const char *src, unsigned char *dst); + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[NS_INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr(digits, ch)) != NULL) { + unsigned int new = *tp * 10 + (pch - digits); + + if (new > 255) + return (0); + *tp = new; + if (! saw_digit) { + if (++octets > 4) + return (0); + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } else + return (0); + } + if (octets < 4) + return (0); + memcpy(dst, tmp, NS_INADDRSZ); + return (1); +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ + +#ifndef NO_IPV6 +static int +inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + unsigned int val; + + memset((tp = tmp), '\0', NS_IN6ADDRSZ); + endp = tp + NS_IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + memcpy(dst, tmp, NS_IN6ADDRSZ); + return (1); +} +#endif + +/* int + * isc_net_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * author: + * Paul Vixie, 1996. + */ +int +inet_pton(int af, const char *src, void *dst) +{ + switch (af) { + case AF_INET: + return (inet_pton4(src, dst)); +#ifndef NO_IPV6 + case AF_INET6: + return (inet_pton6(src, dst)); +#endif + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} diff --git a/daemon.c b/daemon.c index eb4f3f1..69ea35c 100644 --- a/daemon.c +++ b/daemon.c @@ -9,6 +9,7 @@ #include <syslog.h> #include <pwd.h> #include <grp.h> +#include <limits.h> #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" @@ -19,13 +20,15 @@ static int verbose; static int reuseaddr; static const char daemon_usage[] = -"git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n" +"git-daemon [--verbose] [--syslog] [--export-all]\n" " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" " [--interpolated-path=path]\n" " [--reuseaddr] [--detach] [--pid-file=file]\n" " [--[enable|disable|allow-override|forbid-override]=service]\n" -" [--user=user [[--group=group]] [directory...]"; +" [--inetd | [--listen=host_or_ipaddr] [--port=n]\n" +" [--user=user [--group=group]]\n" +" [directory...]"; /* List of acceptable pathname prefixes */ static char **ok_paths; @@ -56,11 +59,17 @@ static unsigned int init_timeout; * Feel free to make dynamic as needed. */ #define INTERP_SLOT_HOST (0) -#define INTERP_SLOT_DIR (1) -#define INTERP_SLOT_PERCENT (2) +#define INTERP_SLOT_CANON_HOST (1) +#define INTERP_SLOT_IP (2) +#define INTERP_SLOT_PORT (3) +#define INTERP_SLOT_DIR (4) +#define INTERP_SLOT_PERCENT (5) static struct interp interp_table[] = { { "%H", 0}, + { "%CH", 0}, + { "%IP", 0}, + { "%P", 0}, { "%D", 0}, { "%%", "%"}, }; @@ -408,9 +417,17 @@ static void parse_extra_args(char *extra_args, int buflen) val = extra_args + 5; vallen = strlen(val) + 1; if (*val) { - char *save = xmalloc(vallen); + char *port; + char *save = xmalloc(vallen); /* FIXME: Leak */ + interp_table[INTERP_SLOT_HOST].value = save; strlcpy(save, val, vallen); + port = strrchr(save, ':'); + if (port) { + *port = 0; + port++; + interp_table[INTERP_SLOT_PORT].value = port; + } } /* On to the next one */ extra_args = val + vallen; @@ -418,6 +435,73 @@ static void parse_extra_args(char *extra_args, int buflen) } } +void fill_in_extra_table_entries(struct interp *itable) +{ + char *hp; + char *canon_host = NULL; + char *ipaddr = NULL; + + /* + * Replace literal host with lowercase-ized hostname. + */ + hp = interp_table[INTERP_SLOT_HOST].value; + for ( ; *hp; hp++) + *hp = tolower(*hp); + + /* + * Locate canonical hostname and its IP address. + */ +#ifndef NO_IPV6 + { + struct addrinfo hints; + struct addrinfo *ai, *ai0; + int gai; + static char addrbuf[HOST_NAME_MAX + 1]; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + + gai = getaddrinfo(interp_table[INTERP_SLOT_HOST].value, 0, &hints, &ai0); + if (!gai) { + for (ai = ai0; ai; ai = ai->ai_next) { + struct sockaddr_in *sin_addr = (void *)ai->ai_addr; + + canon_host = xstrdup(ai->ai_canonname); + inet_ntop(AF_INET, &sin_addr->sin_addr, + addrbuf, sizeof(addrbuf)); + ipaddr = addrbuf; + break; + } + freeaddrinfo(ai0); + } + } +#else + { + struct hostent *hent; + struct sockaddr_in sa; + char **ap; + static char addrbuf[HOST_NAME_MAX + 1]; + + hent = gethostbyname(interp_table[INTERP_SLOT_HOST].value); + canon_host = xstrdup(hent->h_name); + + ap = hent->h_addr_list; + memset(&sa, 0, sizeof sa); + sa.sin_family = hent->h_addrtype; + sa.sin_port = htons(0); + memcpy(&sa.sin_addr, *ap, hent->h_length); + + inet_ntop(hent->h_addrtype, &sa.sin_addr, + addrbuf, sizeof(addrbuf)); + ipaddr = addrbuf; + } +#endif + + interp_table[INTERP_SLOT_CANON_HOST].value = canon_host; /* FIXME: Leak */ + interp_table[INTERP_SLOT_IP].value = xstrdup(ipaddr); /* FIXME: Leak */ +} + + static int execute(struct sockaddr *addr) { static char line[1000]; @@ -458,8 +542,10 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; - if (len != pktlen) + if (len != pktlen) { parse_extra_args(line + len + 1, pktlen - len - 1); + fill_in_extra_table_entries(interp_table); + } for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); @@ -663,23 +749,22 @@ static int set_reuse_addr(int sockfd) #ifndef NO_IPV6 -static int socksetup(int port, int **socklist_p) +static int socksetup(char *listen_addr, int listen_port, int **socklist_p) { int socknum = 0, *socklist = NULL; int maxfd = -1; char pbuf[NI_MAXSERV]; - struct addrinfo hints, *ai0, *ai; int gai; - sprintf(pbuf, "%d", port); + sprintf(pbuf, "%d", listen_port); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; - gai = getaddrinfo(NULL, pbuf, &hints, &ai0); + gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0); if (gai) die("getaddrinfo() failed: %s\n", gai_strerror(gai)); @@ -733,20 +818,27 @@ static int socksetup(int port, int **socklist_p) #else /* NO_IPV6 */ -static int socksetup(int port, int **socklist_p) +static int socksetup(char *lisen_addr, int listen_port, int **socklist_p) { struct sockaddr_in sin; int sockfd; + memset(&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_port = htons(listen_port); + + if (listen_addr) { + /* Well, host better be an IP address here. */ + if (inet_pton(AF_INET, listen_addr, &sin.sin_addr.s_addr) <= 0) + return 0; + } else { + sin.sin_addr.s_addr = htonl(INADDR_ANY); + } + sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) return 0; - memset(&sin, 0, sizeof sin); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(INADDR_ANY); - sin.sin_port = htons(port); - if (set_reuse_addr(sockfd)) { close(sockfd); return 0; @@ -855,13 +947,14 @@ static void store_pid(const char *path) fclose(f); } -static int serve(int port, struct passwd *pass, gid_t gid) +static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid) { int socknum, *socklist; - socknum = socksetup(port, &socklist); + socknum = socksetup(listen_addr, listen_port, &socklist); if (socknum == 0) - die("unable to allocate any listen sockets on port %u", port); + die("unable to allocate any listen sockets on host %s port %u", + listen_addr, listen_port); if (pass && gid && (initgroups(pass->pw_name, gid) || setgid (gid) || @@ -873,7 +966,8 @@ static int serve(int port, struct passwd *pass, gid_t gid) int main(int argc, char **argv) { - int port = DEFAULT_GIT_PORT; + int listen_port = 0; + char *listen_addr = NULL; int inetd_mode = 0; const char *pid_file = NULL, *user_name = NULL, *group_name = NULL; int detach = 0; @@ -890,12 +984,20 @@ int main(int argc, char **argv) for (i = 1; i < argc; i++) { char *arg = argv[i]; + if (!strncmp(arg, "--listen=", 9)) { + char *p = arg + 9; + char *ph = listen_addr = xmalloc(strlen(arg + 9) + 1); + while (*p) + *ph++ = tolower(*p++); + *ph = 0; + continue; + } if (!strncmp(arg, "--port=", 7)) { char *end; unsigned long n; n = strtoul(arg+7, &end, 0); if (arg[7] && !*end) { - port = n; + listen_port = n; continue; } } @@ -995,6 +1097,11 @@ int main(int argc, char **argv) if (inetd_mode && (group_name || user_name)) die("--user and --group are incompatible with --inetd"); + if (inetd_mode && (listen_port || listen_addr)) + die("--listen= and --port= are incompatible with --inetd"); + else if (listen_port == 0) + listen_port = DEFAULT_GIT_PORT; + if (group_name && !user_name) die("--group supplied without --user"); @@ -1043,5 +1150,5 @@ int main(int argc, char **argv) if (pid_file) store_pid(pid_file); - return serve(port, pass, gid); + return serve(listen_addr, listen_port, pass, gid); } -- cgit v0.10.2-6-g49f6 From eb30aed7c69190fd648947d54bbb9ebe53c67715 Mon Sep 17 00:00:00 2001 From: Jon Loeliger <jdl@jdl.com> Date: Wed, 27 Sep 2006 11:16:10 -0500 Subject: Removed memory leaks from interpolation table uses. Clarified that parse_extra_args()s results in interpolation table entries. Removed a few trailing whitespace occurrences. Signed-off-by: Jon Loeliger <jdl@jdl.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/daemon.c b/daemon.c index 69ea35c..5335d21 100644 --- a/daemon.c +++ b/daemon.c @@ -71,7 +71,7 @@ static struct interp interp_table[] = { { "%IP", 0}, { "%P", 0}, { "%D", 0}, - { "%%", "%"}, + { "%%", 0}, }; @@ -405,7 +405,11 @@ static void make_service_overridable(const char *name, int ena) { die("No such service %s", name); } -static void parse_extra_args(char *extra_args, int buflen) +/* + * Separate the "extra args" information as supplied by the client connection. + * Any resulting data is squirrelled away in the given interpolation table. + */ +static void parse_extra_args(struct interp *table, char *extra_args, int buflen) { char *val; int vallen; @@ -417,18 +421,17 @@ static void parse_extra_args(char *extra_args, int buflen) val = extra_args + 5; vallen = strlen(val) + 1; if (*val) { - char *port; - char *save = xmalloc(vallen); /* FIXME: Leak */ - - interp_table[INTERP_SLOT_HOST].value = save; - strlcpy(save, val, vallen); - port = strrchr(save, ':'); + /* Split <host>:<port> at colon. */ + char *host = val; + char *port = strrchr(host, ':'); if (port) { *port = 0; port++; - interp_table[INTERP_SLOT_PORT].value = port; + interp_set_entry(table, INTERP_SLOT_PORT, port); } + interp_set_entry(table, INTERP_SLOT_HOST, host); } + /* On to the next one */ extra_args = val + vallen; } @@ -438,8 +441,6 @@ static void parse_extra_args(char *extra_args, int buflen) void fill_in_extra_table_entries(struct interp *itable) { char *hp; - char *canon_host = NULL; - char *ipaddr = NULL; /* * Replace literal host with lowercase-ized hostname. @@ -466,10 +467,12 @@ void fill_in_extra_table_entries(struct interp *itable) for (ai = ai0; ai; ai = ai->ai_next) { struct sockaddr_in *sin_addr = (void *)ai->ai_addr; - canon_host = xstrdup(ai->ai_canonname); inet_ntop(AF_INET, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf)); - ipaddr = addrbuf; + interp_set_entry(interp_table, + INTERP_SLOT_CANON_HOST, ai->ai_canonname); + interp_set_entry(interp_table, + INTERP_SLOT_IP, addrbuf); break; } freeaddrinfo(ai0); @@ -483,7 +486,6 @@ void fill_in_extra_table_entries(struct interp *itable) static char addrbuf[HOST_NAME_MAX + 1]; hent = gethostbyname(interp_table[INTERP_SLOT_HOST].value); - canon_host = xstrdup(hent->h_name); ap = hent->h_addr_list; memset(&sa, 0, sizeof sa); @@ -493,12 +495,11 @@ void fill_in_extra_table_entries(struct interp *itable) inet_ntop(hent->h_addrtype, &sa.sin_addr, addrbuf, sizeof(addrbuf)); - ipaddr = addrbuf; + + interp_set_entry(interp_table, INTERP_SLOT_CANON_HOST, hent->h_name); + interp_set_entry(interp_table, INTERP_SLOT_IP, addrbuf); } #endif - - interp_table[INTERP_SLOT_CANON_HOST].value = canon_host; /* FIXME: Leak */ - interp_table[INTERP_SLOT_IP].value = xstrdup(ipaddr); /* FIXME: Leak */ } @@ -542,8 +543,14 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; + /* + * Initialize the path interpolation table for this connection. + */ + interp_clear_table(interp_table, ARRAY_SIZE(interp_table)); + interp_set_entry(interp_table, INTERP_SLOT_PERCENT, "%"); + if (len != pktlen) { - parse_extra_args(line + len + 1, pktlen - len - 1); + parse_extra_args(interp_table, line + len + 1, pktlen - len - 1); fill_in_extra_table_entries(interp_table); } @@ -553,7 +560,12 @@ static int execute(struct sockaddr *addr) if (!strncmp("git-", line, 4) && !strncmp(s->name, line + 4, namelen) && line[namelen + 4] == ' ') { - interp_table[INTERP_SLOT_DIR].value = line+namelen+5; + /* + * Note: The directory here is probably context sensitive, + * and might depend on the actual service being performed. + */ + interp_set_entry(interp_table, + INTERP_SLOT_DIR, line + namelen + 5); return run_service(interp_table, s); } } diff --git a/interpolate.c b/interpolate.c index 4570c12..62701d8 100644 --- a/interpolate.c +++ b/interpolate.c @@ -4,9 +4,35 @@ #include <string.h> +#include "git-compat-util.h" #include "interpolate.h" +void interp_set_entry(struct interp *table, int slot, char *value) +{ + char *oldval = table[slot].value; + char *newval = value; + + if (oldval) + free(oldval); + + if (value) + newval = xstrdup(value); + + table[slot].value = newval; +} + + +void interp_clear_table(struct interp *table, int ninterps) +{ + int i; + + for (i = 0; i < ninterps; i++) { + interp_set_entry(table, i, NULL); + } +} + + /* * Convert a NUL-terminated string in buffer orig * into the supplied buffer, result, whose length is reslen, diff --git a/interpolate.h b/interpolate.h index d16f924..a55fb8e 100644 --- a/interpolate.h +++ b/interpolate.h @@ -16,6 +16,9 @@ struct interp { char *value; }; +extern void interp_set_entry(struct interp *table, int slot, char *value); +extern void interp_clear_table(struct interp *table, int ninterps); + extern int interpolate(char *result, int reslen, const char *orig, const struct interp *interps, int ninterps); -- cgit v0.10.2-6-g49f6 From 709f898dae46b9b035e98cc634fa3ac2492b6cee Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Wed, 27 Sep 2006 17:22:03 -0700 Subject: Revert "gitweb: extend blame to show links to diff and previous" This concept is very fine, but it makes blame slow across renames and across branches, so revert it. There is a better way to do this. This reverts commit 03d06a8e26f4fbd37800d1e1125c6ecf4c104466. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9349fa1..0a62784 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2479,7 +2479,7 @@ sub git_blame2 { print <<HTML; <div class="page_body"> <table class="blame"> -<tr><th>Prev</th><th>Diff</th><th>Commit</th><th>Line</th><th>Data</th></tr> +<tr><th>Commit</th><th>Line</th><th>Data</th></tr> HTML while (<$fd>) { /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; @@ -2487,8 +2487,6 @@ HTML my $rev = substr($full_rev, 0, 8); my $lineno = $2; my $data = $3; - my %pco = parse_commit($full_rev); - my $parent = $pco{'parent'}; if (!defined $last_rev) { $last_rev = $full_rev; @@ -2497,25 +2495,11 @@ HTML $current_color = ++$current_color % $num_colors; } print "<tr class=\"$rev_color[$current_color]\">\n"; - # Print the Prev link - print "<td class=\"sha1\">"; - print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, file_name=>$file_name)}, - esc_html(substr($parent, 0, 8))); - print "</td>\n"; - # Print the Diff (blobdiff) link - print "<td>"; - print $cgi->a({-href => href(action=>"blobdiff", file_name=>$file_name, hash_parent_base=>$parent, - hash_base=>$full_rev)}, - esc_html("Diff")); - print "</td>\n"; - # Print the Commit link print "<td class=\"sha1\">" . $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, esc_html($rev)) . "</td>\n"; - # Print the Line number print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n"; - # Print the Data print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; print "</tr>\n"; } -- cgit v0.10.2-6-g49f6 From 499faeda1bd131f7c3b216c16eebbe30049728d3 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Wed, 27 Sep 2006 17:23:25 -0700 Subject: gitweb: Remove excessively redundant entries from git_difftree_body 1) All entries on the left are blobs and clicking on them leads to blobs. No more diff or blob depending on what happened (modified or mode changed) to the file -- this goes to the right, in the "link" column. 2) Remove redundant "blob" from the link column on the right. This can now be had by clicking on the entry itself. This reduces and simplifies the code. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0a62784..88a8bcd 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1732,47 +1732,39 @@ sub git_difftree_body { my $mode_chng = "<span class=\"file_status new\">[new $to_file_type"; $mode_chng .= " with mode: $to_mode_str" if $to_mode_str; $mode_chng .= "]</span>"; - print "<td>" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + print "<td>"; + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})) . - "</td>\n" . - "<td>$mode_chng</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, - "blob"); + -class => "list"}, esc_html($diff{'file'})); + print "</td>\n"; + print "<td>$mode_chng</td>\n"; + print "<td class=\"link\">"; if ($action eq 'commitdiff') { # link to patch $patchno++; - print " | " . - $cgi->a({-href => "#patch$patchno"}, "patch"); + print $cgi->a({-href => "#patch$patchno"}, "patch"); } print "</td>\n"; } elsif ($diff{'status'} eq "D") { # deleted my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>"; - print "<td>" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + print "<td>"; + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})) . - "</td>\n" . - "<td>$mode_chng</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, - hash_base=>$parent, file_name=>$diff{'file'})}, - "blob") . - " | "; + -class => "list"}, esc_html($diff{'file'})); + print "</td>\n"; + print "<td>$mode_chng</td>\n"; + print "<td class=\"link\">"; if ($action eq 'commitdiff') { # link to patch $patchno++; - print " | " . - $cgi->a({-href => "#patch$patchno"}, "patch"); + print $cgi->a({-href => "#patch$patchno"}, "patch"); + print " | "; } print $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$diff{'file'})}, - "history") . - "</td>\n"; + "history"); + print "</td>\n"; } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed my $mode_chnge = ""; @@ -1791,42 +1783,29 @@ sub git_difftree_body { $mode_chnge .= "]</span>\n"; } print "<td>"; - if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); - } else { # only mode changed - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); - } - print "</td>\n" . - "<td>$mode_chnge</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, - "blob"); + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); + print "</td>\n"; + print "<td>$mode_chnge</td>\n"; + print "<td class=\"link\">"; if ($diff{'to_id'} ne $diff{'from_id'}) { # modified if ($action eq 'commitdiff') { # link to patch $patchno++; - print " | " . - $cgi->a({-href => "#patch$patchno"}, "patch"); + print $cgi->a({-href => "#patch$patchno"}, "patch"); } else { - print " | " . - $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, - "diff"); + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, + "diff"); } + print " | "; } - print " | " . - $cgi->a({-href => href(action=>"history", - hash_base=>$hash, file_name=>$diff{'file'})}, - "history"); + print $cgi->a({-href => href(action=>"history", + hash_base=>$hash, file_name=>$diff{'file'})}, + "history"); print "</td>\n"; } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied @@ -1846,10 +1825,7 @@ sub git_difftree_body { hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}), -class => "list"}, esc_html($diff{'from_file'})) . " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})}, - "blob"); + "<td class=\"link\">"; if ($diff{'to_id'} ne $diff{'from_id'}) { if ($action eq 'commitdiff') { # link to patch -- cgit v0.10.2-6-g49f6 From 07ccbff89b6736a5253e6bba62c9a8e1f2da0ffd Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Thu, 28 Sep 2006 02:44:30 +0200 Subject: runstatus: do not recurse into subdirectories if not needed This speeds up the case when you run git-status, having an untracked subdirectory containing huge amounts of files. Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/dir.c b/dir.c index e2f472b..96389b3 100644 --- a/dir.c +++ b/dir.c @@ -283,7 +283,7 @@ static int dir_exists(const char *dirname, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen) +static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only) { DIR *fdir = opendir(path); int contents = 0; @@ -314,7 +314,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co switch (DTYPE(de)) { struct stat st; - int subdir, rewind_base; default: continue; case DT_UNKNOWN: @@ -328,26 +327,30 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co case DT_DIR: memcpy(fullname + baselen + len, "/", 2); len++; - rewind_base = dir->nr; - subdir = read_directory_recursive(dir, fullname, fullname, - baselen + len); if (dir->show_other_directories && - (subdir || !dir->hide_empty_directories) && !dir_exists(fullname, baselen + len)) { - /* Rewind the read subdirectory */ - while (dir->nr > rewind_base) - free(dir->entries[--dir->nr]); + if (dir->hide_empty_directories && + !read_directory_recursive(dir, + fullname, fullname, + baselen + len, 1)) + continue; break; } - contents += subdir; + + contents += read_directory_recursive(dir, + fullname, fullname, baselen + len, 0); continue; case DT_REG: case DT_LNK: break; } - add_name(dir, fullname, baselen + len); contents++; + if (check_only) + goto exit_early; + else + add_name(dir, fullname, baselen + len); } +exit_early: closedir(fdir); pop_exclude_per_directory(dir, exclude_stk); @@ -393,7 +396,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i } } - read_directory_recursive(dir, path, base, baselen); + read_directory_recursive(dir, path, base, baselen, 0); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; } -- cgit v0.10.2-6-g49f6 From eb51ec9c05e6f4b29567f58102e33c453e1a9a57 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Wed, 27 Sep 2006 17:24:49 -0700 Subject: gitweb: Add history and blame to git_difftree_body() Add blame and history to Deleted files. Add blame and history to Modified or Type changed files. Add blame and history to Renamed or Copied files. This allows us to do blame->commit->blame->commit->blame->... instead of blame->commit->file->blame->commit->file->blame->... which is longer and easier to get wrong. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 88a8bcd..c86ac1d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1761,9 +1761,12 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); print " | "; } + print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, + file_name=>$diff{'file'})}, + "blame") . " | "; print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'file'})}, - "history"); + file_name=>$diff{'file'})}, + "history"); print "</td>\n"; } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed @@ -1803,8 +1806,11 @@ sub git_difftree_body { } print " | "; } - print $cgi->a({-href => href(action=>"history", - hash_base=>$hash, file_name=>$diff{'file'})}, + print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, + file_name=>$diff{'file'})}, + "blame") . " | "; + print $cgi->a({-href => href(action=>"history", hash_base=>$hash, + file_name=>$diff{'file'})}, "history"); print "</td>\n"; @@ -1830,17 +1836,22 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print " | " . - $cgi->a({-href => "#patch$patchno"}, "patch"); + print $cgi->a({-href => "#patch$patchno"}, "patch"); } else { - print " | " . - $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, - "diff"); + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff"); } + print " | "; } + print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, + file_name=>$diff{'from_file'})}, + "blame") . " | "; + print $cgi->a({-href => href(action=>"history", hash_base=>$parent, + file_name=>$diff{'from_file'})}, + "history"); print "</td>\n"; } # we should not encounter Unmerged (U) or Unknown (X) status -- cgit v0.10.2-6-g49f6 From 919a3c981323b6f919747bf6756aa8f5af09361f Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Thu, 28 Sep 2006 06:58:03 +0200 Subject: Add pack-refs and show-ref test cases. Some of these test cases are from Junio. One test case is commented out because it doesn't work right now. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh new file mode 100755 index 0000000..2cc03e6 --- /dev/null +++ b/t/t3210-pack-refs.sh @@ -0,0 +1,70 @@ +#!/bin/sh +# +# Copyright (c) 2005 Amos Waterland +# Copyright (c) 2006 Christian Couder +# + +test_description='git pack-refs should not change the branch semantic + +This test runs git pack-refs and git show-ref and checks that the branch +semantic is still the same. +' +. ./test-lib.sh + +test_expect_success \ + 'prepare a trivial repository' \ + 'echo Hello > A && + git-update-index --add A && + git-commit -m "Initial commit." && + HEAD=$(git-rev-parse --verify HEAD)' + +SHA1= + +test_expect_success \ + 'see if git show-ref works as expected' \ + 'git-branch a && + SHA1=$(< .git/refs/heads/a) && + echo "$SHA1 refs/heads/a" >expect && + git-show-ref a >result && + diff expect result' + +test_expect_success \ + 'see if a branch still exists when packed' \ + 'git-branch b && + git-pack-refs && + rm .git/refs/heads/b && + echo "$SHA1 refs/heads/b" >expect && + git-show-ref b >result && + diff expect result' + +# test_expect_failure \ +# 'git branch c/d should barf if branch c exists' \ +# 'git-branch c && +# git-pack-refs && +# rm .git/refs/heads/c && +# git-branch c/d' + +test_expect_success \ + 'see if a branch still exists after git pack-refs --prune' \ + 'git-branch e && + git-pack-refs --prune && + echo "$SHA1 refs/heads/e" >expect && + git-show-ref e >result && + diff expect result' + +test_expect_failure \ + 'see if git pack-refs --prune remove ref files' \ + 'git-branch f && + git-pack-refs --prune && + ls .git/refs/heads/f' + +test_expect_success \ + 'git branch g should work when git branch g/h has been deleted' \ + 'git-branch g/h && + git-pack-refs --prune && + git-branch -d g/h && + git-branch g && + git-pack-refs && + git-branch -d g' + +test_done -- cgit v0.10.2-6-g49f6 From 5be7649131379e49f27d89cb6dd5bd8d0912a3d6 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Thu, 28 Sep 2006 07:00:38 +0200 Subject: When creating branch c/d check that branch c does not already exists. With packed refs, there may not be a ".git/refs/heads/c" file when branch c exists. And currently in this case, there is no check to prevent creation of branch c/d. This should probably be rewritten in C and done after the ref lock has been taken to make sure no race exists though. This is mainly to make all test cases in "t3210-pack-refs.sh" work. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-branch.sh b/git-branch.sh index bf84b30..c616830 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -121,6 +121,16 @@ then done fi +branchdir=$(dirname $branchname) +while test "$branchdir" != "." +do + if git-show-ref --verify --quiet -- "refs/heads/$branchdir" + then + die "$branchdir already exists." + fi + branchdir=$(dirname $branchdir) +done + prev='' if git-show-ref --verify --quiet -- "refs/heads/$branchname" then -- cgit v0.10.2-6-g49f6 From fc12f0829d027a6f7a86b38ae56b1424f2378470 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Thu, 28 Sep 2006 07:02:00 +0200 Subject: Uncomment test case: git branch c/d should barf if branch c exists. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 2cc03e6..193fe1f 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -37,12 +37,12 @@ test_expect_success \ git-show-ref b >result && diff expect result' -# test_expect_failure \ -# 'git branch c/d should barf if branch c exists' \ -# 'git-branch c && -# git-pack-refs && -# rm .git/refs/heads/c && -# git-branch c/d' +test_expect_failure \ + 'git branch c/d should barf if branch c exists' \ + 'git-branch c && + git-pack-refs && + rm .git/refs/heads/c && + git-branch c/d' test_expect_success \ 'see if a branch still exists after git pack-refs --prune' \ -- cgit v0.10.2-6-g49f6 From 0ab7befa31d07fe3ffb51a6cc626d4c09ded1c92 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 17:50:52 -0700 Subject: grep --all-match This lets you say: git grep --all-match -e A -e B -e C to find lines that match A or B or C but limit the matches from the files that have all of A, B and C. This is different from git grep -e A --and -e B --and -e C in that the latter looks for a single line that has all of these at the same time. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index d8af4d9..bfbece9 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -14,7 +14,7 @@ SYNOPSIS [-v | --invert-match] [-h|-H] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] - [-c | --count] + [-c | --count] [--all-match] [-A <post-context>] [-B <pre-context>] [-C <context>] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] [<tree>...] @@ -96,6 +96,11 @@ OPTIONS higher precedence than `--or`. `-e` has to be used for all patterns. +--all-match:: + When giving multiple pattern expressions combined with `--or`, + this flag is specified to limit the match to files that + have lines to match all of them. + `<tree>...`:: Search blobs in the trees for specified patterns. @@ -111,6 +116,10 @@ git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \):: Looks for a line that has `#define` and either `MAX_PATH` or `PATH_MAX`. +git grep --all-match -e NODE -e Unexpected:: + Looks for a line that has `NODE` or `Unexpected` in + files that have lines that match both. + Author ------ Originally written by Linus Torvalds <torvalds@osdl.org>, later diff --git a/builtin-grep.c b/builtin-grep.c index 4205e5d..ad7dc00 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -596,6 +596,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) GREP_CLOSE_PAREN); continue; } + if (!strcmp("--all-match", arg)) { + opt.all_match = 1; + continue; + } if (!strcmp("-e", arg)) { if (1 < argc) { append_grep_pattern(&opt, argv[1], diff --git a/grep.c b/grep.c index c411ddd..0fc078e 100644 --- a/grep.c +++ b/grep.c @@ -34,7 +34,7 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) } } -static struct grep_expr *compile_pattern_expr(struct grep_pat **); +static struct grep_expr *compile_pattern_or(struct grep_pat **); static struct grep_expr *compile_pattern_atom(struct grep_pat **list) { struct grep_pat *p; @@ -52,7 +52,7 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list) return x; case GREP_OPEN_PAREN: *list = p->next; - x = compile_pattern_expr(list); + x = compile_pattern_or(list); if (!x) return NULL; if (!*list || (*list)->token != GREP_CLOSE_PAREN) @@ -138,6 +138,9 @@ void compile_grep_patterns(struct grep_opt *opt) { struct grep_pat *p; + if (opt->all_match) + opt->extended = 1; + for (p = opt->pattern_list; p; p = p->next) { switch (p->token) { case GREP_PATTERN: /* atom */ @@ -309,40 +312,63 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol return hit; } -static int match_expr_eval(struct grep_opt *opt, +static int match_expr_eval(struct grep_opt *o, struct grep_expr *x, char *bol, char *eol, - enum grep_context ctx) + enum grep_context ctx, + int collect_hits) { + int h = 0; + switch (x->node) { case GREP_NODE_ATOM: - return match_one_pattern(opt, x->u.atom, bol, eol, ctx); + h = match_one_pattern(o, x->u.atom, bol, eol, ctx); break; case GREP_NODE_NOT: - return !match_expr_eval(opt, x->u.unary, bol, eol, ctx); + h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0); + break; case GREP_NODE_AND: - return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) && - match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); + if (!collect_hits) + return (match_expr_eval(o, x->u.binary.left, + bol, eol, ctx, 0) && + match_expr_eval(o, x->u.binary.right, + bol, eol, ctx, 0)); + h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0); + h &= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 0); + break; case GREP_NODE_OR: - return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) || - match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); + if (!collect_hits) + return (match_expr_eval(o, x->u.binary.left, + bol, eol, ctx, 0) || + match_expr_eval(o, x->u.binary.right, + bol, eol, ctx, 0)); + h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0); + x->u.binary.left->hit |= h; + h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1); + break; + default: + die("Unexpected node type (internal error) %d\n", x->node); } - die("Unexpected node type (internal error) %d\n", x->node); + if (collect_hits) + x->hit |= h; + return h; } static int match_expr(struct grep_opt *opt, char *bol, char *eol, - enum grep_context ctx) + enum grep_context ctx, int collect_hits) { struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol, ctx); + return match_expr_eval(opt, x, bol, eol, ctx, collect_hits); } static int match_line(struct grep_opt *opt, char *bol, char *eol, - enum grep_context ctx) + enum grep_context ctx, int collect_hits) { struct grep_pat *p; if (opt->extended) - return match_expr(opt, bol, eol, ctx); + return match_expr(opt, bol, eol, ctx, collect_hits); + + /* we do not call with collect_hits without being extended */ for (p = opt->pattern_list; p; p = p->next) { if (match_one_pattern(opt, p, bol, eol, ctx)) return 1; @@ -350,7 +376,8 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol, return 0; } -int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) +static int grep_buffer_1(struct grep_opt *opt, const char *name, + char *buf, unsigned long size, int collect_hits) { char *bol = buf; unsigned long left = size; @@ -386,7 +413,7 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long while (left) { char *eol, ch; - int hit = 0; + int hit; eol = end_of_line(bol, &left); ch = *eol; @@ -395,9 +422,12 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol)) ctx = GREP_CONTEXT_BODY; - hit = match_line(opt, bol, eol, ctx); + hit = match_line(opt, bol, eol, ctx, collect_hits); *eol = ch; + if (collect_hits) + goto next_line; + /* "grep -v -e foo -e bla" should list lines * that do not have either, so inversion should * be done outside. @@ -477,6 +507,8 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long } free(prev); + if (collect_hits) + return 0; if (opt->status_only) return 0; @@ -496,3 +528,49 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long return !!last_hit; } +static void clr_hit_marker(struct grep_expr *x) +{ + /* All-hit markers are meaningful only at the very top level + * OR node. + */ + while (1) { + x->hit = 0; + if (x->node != GREP_NODE_OR) + return; + x->u.binary.left->hit = 0; + x = x->u.binary.right; + } +} + +static int chk_hit_marker(struct grep_expr *x) +{ + /* Top level nodes have hit markers. See if they all are hits */ + while (1) { + if (x->node != GREP_NODE_OR) + return x->hit; + if (!x->u.binary.left->hit) + return 0; + x = x->u.binary.right; + } +} + +int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) +{ + /* + * we do not have to do the two-pass grep when we do not check + * buffer-wide "all-match". + */ + if (!opt->all_match) + return grep_buffer_1(opt, name, buf, size, 0); + + /* Otherwise the toplevel "or" terms hit a bit differently. + * We first clear hit markers from them. + */ + clr_hit_marker(opt->pattern_expression); + grep_buffer_1(opt, name, buf, size, 1); + + if (!chk_hit_marker(opt->pattern_expression)) + return 0; + + return grep_buffer_1(opt, name, buf, size, 0); +} diff --git a/grep.h b/grep.h index af9098c..d252dd2 100644 --- a/grep.h +++ b/grep.h @@ -35,6 +35,7 @@ enum grep_expr_node { struct grep_expr { enum grep_expr_node node; + unsigned hit; union { struct grep_pat *atom; struct grep_expr *unary; @@ -59,6 +60,7 @@ struct grep_opt { unsigned count:1; unsigned word_regexp:1; unsigned fixed:1; + unsigned all_match:1; #define GREP_BINARY_DEFAULT 0 #define GREP_BINARY_NOMATCH 1 #define GREP_BINARY_TEXT 2 -- cgit v0.10.2-6-g49f6 From 70d0afba914ddcf8c8a0144e5c6519d43d641dd9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 27 Sep 2006 17:55:58 -0700 Subject: teach revision walker about --all-match. This lets you say: git log --all-match --author=Linus --committer=Junio --grep=rev-list to limit commits that was written by Linus, committed by me and the log message contains word "rev-list". Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/revision.c b/revision.c index 93f2513..f1e0caa 100644 --- a/revision.c +++ b/revision.c @@ -732,6 +732,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch int i, flags, seen_dashdash, show_merge; const char **unrecognized = argv + 1; int left = 1; + int all_match = 0; /* First, search for "--" */ seen_dashdash = 0; @@ -967,6 +968,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch add_message_grep(revs, arg+7); continue; } + if (!strcmp(arg, "--all-match")) { + all_match = 1; + continue; + } opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { @@ -1028,8 +1033,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); - if (revs->grep_filter) + if (revs->grep_filter) { + revs->grep_filter->all_match = all_match; compile_grep_patterns(revs->grep_filter); + } return left; } -- cgit v0.10.2-6-g49f6 From 690d8824c852b6a59deb03f65e330297c587752e Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Thu, 28 Sep 2006 02:31:25 -0700 Subject: Contributed bash completion support for core Git tools. This is a set of bash completion routines for many of the popular core Git tools. I wrote these routines from scratch after reading the git-compl and git-compl-lib routines available from the gitcompletion package at http://gitweb.hawaga.org.uk/ and found those to be lacking in functionality for some commands. Consequently there may be some similarities but many differences. Since these are completion routines only for tools shipped with core Git and since bash is a popular shell on many of the native core Git platforms (Linux, Mac OS X, Solaris, BSD) including these routines as part of the stock package would probably be convienent for many users. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash new file mode 100755 index 0000000..d9cb17d --- /dev/null +++ b/contrib/completion/git-completion.bash @@ -0,0 +1,324 @@ +# +# bash completion support for core Git. +# +# Copyright (C) 2006 Shawn Pearce +# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/). +# +# The contained completion routines provide support for completing: +# +# *) local and remote branch names +# *) local and remote tag names +# *) .git/remotes file names +# *) git 'subcommands' +# *) tree paths within 'ref:path/to/file' expressions +# +# To use these routines: +# +# 1) Copy this file to somewhere (e.g. ~/.git-completion.sh). +# 2) Added the following line to your .bashrc: +# source ~/.git-completion.sh +# + +__git_refs () +{ + local cmd i is_hash=y + if [ -d "$1" ]; then + cmd=git-peek-remote + else + cmd=git-ls-remote + fi + for i in $($cmd "$1" 2>/dev/null); do + case "$is_hash,$i" in + y,*) is_hash=n ;; + n,*^{}) is_hash=y ;; + n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; + n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; + n,*) is_hash=y; echo "$i" ;; + esac + done +} + +__git_refs2 () +{ + local cmd i is_hash=y + if [ -d "$1" ]; then + cmd=git-peek-remote + else + cmd=git-ls-remote + fi + for i in $($cmd "$1" 2>/dev/null); do + case "$is_hash,$i" in + y,*) is_hash=n ;; + n,*^{}) is_hash=y ;; + n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;; + n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;; + n,*) is_hash=y; echo "$i:$i" ;; + esac + done +} + +__git_remotes () +{ + local i REVERTGLOB=$(shopt -p nullglob) + shopt -s nullglob + for i in .git/remotes/*; do + echo ${i#.git/remotes/} + done + $REVERTGLOB +} + +__git_complete_file () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + ?*:*) + local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')" + cur="$(echo "$cur" | sed 's,^.*:,,')" + case "$cur" in + ?*/*) + pfx="$(echo "$cur" | sed 's,/[^/]*$,,')" + cur="$(echo "$cur" | sed 's,^.*/,,')" + ls="$ref:$pfx" + pfx="$pfx/" + ;; + *) + ls="$ref" + ;; + esac + COMPREPLY=($(compgen -P "$pfx" \ + -W "$(git-ls-tree "$ls" \ + | sed '/^100... blob /s,^.* ,, + /^040000 tree /{ + s,^.* ,, + s,$,/, + } + s/^.* //')" \ + -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + ;; + esac +} + +_git_branch () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur")) +} + +_git_cat_file () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${COMP_WORDS[0]},$COMP_CWORD" in + git-cat-file*,1) + COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur")) + ;; + git,2) + COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur")) + ;; + *) + __git_complete_file + ;; + esac +} + +_git_checkout () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur")) +} + +_git_diff () +{ + __git_complete_file +} + +_git_diff_tree () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur")) +} + +_git_fetch () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + + case "${COMP_WORDS[0]},$COMP_CWORD" in + git-fetch*,1) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + git,2) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + *) + case "$cur" in + *:*) + cur=$(echo "$cur" | sed 's/^.*://') + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + ;; + *) + local remote + case "${COMP_WORDS[0]}" in + git-fetch) remote="${COMP_WORDS[1]}" ;; + git) remote="${COMP_WORDS[2]}" ;; + esac + COMPREPLY=($(compgen -W "$(__git_refs2 "$remote")" -- "$cur")) + ;; + esac + ;; + esac +} + +_git_ls_remote () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) +} + +_git_ls_tree () +{ + __git_complete_file +} + +_git_log () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + *..*) + local pfx=$(echo "$cur" | sed 's/\.\..*$/../') + cur=$(echo "$cur" | sed 's/^.*\.\.//') + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + ;; + esac +} + +_git_merge_base () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) +} + +_git_pull () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + + case "${COMP_WORDS[0]},$COMP_CWORD" in + git-pull*,1) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + git,2) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + *) + local remote + case "${COMP_WORDS[0]}" in + git-pull) remote="${COMP_WORDS[1]}" ;; + git) remote="${COMP_WORDS[2]}" ;; + esac + COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) + ;; + esac +} + +_git_push () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + + case "${COMP_WORDS[0]},$COMP_CWORD" in + git-push*,1) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + git,2) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + *) + case "$cur" in + *:*) + local remote + case "${COMP_WORDS[0]}" in + git-push) remote="${COMP_WORDS[1]}" ;; + git) remote="${COMP_WORDS[2]}" ;; + esac + cur=$(echo "$cur" | sed 's/^.*://') + COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur")) + ;; + esac + ;; + esac +} + +_git_show () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) +} + +_git () +{ + if [ $COMP_CWORD = 1 ]; then + COMPREPLY=($(compgen \ + -W "--version $(git help -a|egrep '^ ')" \ + -- "${COMP_WORDS[COMP_CWORD]}")) + else + case "${COMP_WORDS[1]}" in + branch) _git_branch ;; + cat-file) _git_cat_file ;; + checkout) _git_checkout ;; + diff) _git_diff ;; + diff-tree) _git_diff_tree ;; + fetch) _git_fetch ;; + log) _git_log ;; + ls-remote) _git_ls_remote ;; + ls-tree) _git_ls_tree ;; + pull) _git_pull ;; + push) _git_push ;; + show) _git_show ;; + show-branch) _git_log ;; + whatchanged) _git_log ;; + *) COMPREPLY=() ;; + esac + fi +} + +_gitk () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur")) +} + +complete -o default -o nospace -F _git git +complete -o default -F _gitk gitk +complete -o default -F _git_branch git-branch +complete -o default -o nospace -F _git_cat_file git-cat-file +complete -o default -F _git_checkout git-checkout +complete -o default -o nospace -F _git_diff git-diff +complete -o default -F _git_diff_tree git-diff-tree +complete -o default -o nospace -F _git_fetch git-fetch +complete -o default -o nospace -F _git_log git-log +complete -o default -F _git_ls_remote git-ls-remote +complete -o default -o nospace -F _git_ls_tree git-ls-tree +complete -o default -F _git_merge_base git-merge-base +complete -o default -o nospace -F _git_pull git-pull +complete -o default -o nospace -F _git_push git-push +complete -o default -F _git_show git-show +complete -o default -o nospace -F _git_log git-whatchanged + +# The following are necessary only for Cygwin, and only are needed +# when the user has tab-completed the executable name and consequently +# included the '.exe' suffix. +# +complete -o default -o nospace -F _git_cat_file git-cat-file.exe +complete -o default -o nospace -F _git_diff git-diff.exe +complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe +complete -o default -o nospace -F _git_log git-log.exe +complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe +complete -o default -F _git_merge_base git-merge-base.exe +complete -o default -o nospace -F _git_push git-push.exe +complete -o default -o nospace -F _git_log git-whatchanged.exe -- cgit v0.10.2-6-g49f6 From 7b40e7d1ab20aebaac86b4586d48b6b645da57d1 Mon Sep 17 00:00:00 2001 From: Andy Whitcroft <apw@shadowen.org> Date: Mon, 25 Sep 2006 12:08:13 +0100 Subject: svnimport: add support for parsing From: lines for author When commiting a non-signed off contribution you cannot just add a Signed-off-by: from the author as they did not sign it off. But if you then commit it, and necessarily sign it off yourself, the change appears to be yours. In this case it is common to use the following form: Commentry From: originator <email> Signed-of-by: me <my email> Now that we have support for parsing Signed-off-by: for author information it makes sense to handle From: as well. This patch adds a new -F which will handle From: lines in the comments. It may be used in combination with -S. Signed-off-by: Andy Whitcroft <apw@shadowen.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-svnimport.perl b/git-svnimport.perl index ed62897..988514e 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -31,7 +31,7 @@ $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, - $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S); + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F); sub usage() { print STDERR <<END; @@ -39,12 +39,12 @@ Usage: ${\basename $0} # fetch/update GIT from SVN [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname] [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg] - [-m] [-M regex] [-A author_file] [-S] [SVN_URL] + [-m] [-M regex] [-A author_file] [-S] [-F] [SVN_URL] END exit(1); } -getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:Suv") or usage(); +getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:Suv") or usage(); usage if $opt_h; my $tag_name = $opt_t || "tags"; @@ -548,8 +548,12 @@ sub commit { $committer_name = $committer_email = $author; } - if ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) { ($author_name, $author_email) = ($1, $2); + print "Author from From: $1 <$2>\n" if ($opt_v);; + } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + ($author_name, $author_email) = ($1, $2); + print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);; } else { $author_name = $committer_name; $author_email = $committer_email; -- cgit v0.10.2-6-g49f6 From c08e52486a7b5e38741c8264979a11f0103ec8c4 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer <matled@gmx.net> Date: Fri, 22 Sep 2006 13:12:01 +0200 Subject: format-patch: use cwd as default output directory Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-log.c b/builtin-log.c index fbc58bb..130b53a 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -270,6 +270,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.extra_headers = extra_headers; + output_directory = prefix; + /* * Parse the arguments before setup_revisions(), or something * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is -- cgit v0.10.2-6-g49f6 From 695dffe2efa53b9628e7811dbe33447a8014fd77 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Thu, 28 Sep 2006 12:00:35 +0200 Subject: daemon: default to 256 for HOST_NAME_MAX if it is not defined Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/daemon.c b/daemon.c index 5335d21..fc3951c 100644 --- a/daemon.c +++ b/daemon.c @@ -15,6 +15,10 @@ #include "exec_cmd.h" #include "interpolate.h" +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 256 +#endif + static int log_syslog; static int verbose; static int reuseaddr; -- cgit v0.10.2-6-g49f6 From 100690b6e8e9cc3cfe2c1d170192b5505d7a2ea8 Mon Sep 17 00:00:00 2001 From: Alex Riesen <fork0@t-online.de> Date: Thu, 28 Sep 2006 20:48:14 +0200 Subject: fix daemon.c compilation for NO_IPV6=1 Signed-off-by: Alex Riesen <raa.lkml@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/daemon.c b/daemon.c index fc3951c..ad84928 100644 --- a/daemon.c +++ b/daemon.c @@ -834,7 +834,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) #else /* NO_IPV6 */ -static int socksetup(char *lisen_addr, int listen_port, int **socklist_p) +static int socksetup(char *listen_addr, int listen_port, int **socklist_p) { struct sockaddr_in sin; int sockfd; -- cgit v0.10.2-6-g49f6 From e92a54d99cb36eab6e29069fe3b135e6e6b24f12 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Thu, 28 Sep 2006 12:12:28 -0700 Subject: Clean up approxidate() in preparation for fixes Our approxidate cannot handle simple times like "5 PM yesterday", and to fix that, we will need to add some logic for number handling. This just splits that out into a function of its own (the same way the _real_ date parsing works). Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/date.c b/date.c index e387dcd..4ff6604 100644 --- a/date.c +++ b/date.c @@ -712,6 +712,15 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) return end; } +static const char *approxidate_digit(const char *date, struct tm *tm, int *num) +{ + char *end; + unsigned long number = strtoul(date, &end, 10); + + *num = number; + return end; +} + unsigned long approxidate(const char *date) { int number = 0; @@ -731,9 +740,7 @@ unsigned long approxidate(const char *date) break; date++; if (isdigit(c)) { - char *end; - number = strtoul(date-1, &end, 10); - date = end; + date = approxidate_digit(date-1, &tm, &number); continue; } if (isalpha(c)) -- cgit v0.10.2-6-g49f6 From 393d340e4f4ae571cd48387c29c85e9ab098b098 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Thu, 28 Sep 2006 12:14:27 -0700 Subject: Fix approxidate() to understand more extended numbers You can now say "5:35 PM yesterday", and approxidate() gets the right answer. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/date.c b/date.c index 4ff6604..db4c185 100644 --- a/date.c +++ b/date.c @@ -598,6 +598,32 @@ static void date_tea(struct tm *tm, int *num) date_time(tm, 17); } +static void date_pm(struct tm *tm, int *num) +{ + int hour = *num; + *num = 0; + + if (hour > 0 && hour < 12) { + tm->tm_hour = hour; + tm->tm_min = 0; + tm->tm_sec = 0; + } + if (tm->tm_hour > 0 && tm->tm_hour < 12) + tm->tm_hour += 12; +} + +static void date_am(struct tm *tm, int *num) +{ + int hour = *num; + *num = 0; + + if (hour > 0 && hour < 12) { + tm->tm_hour = hour; + tm->tm_min = 0; + tm->tm_sec = 0; + } +} + static const struct special { const char *name; void (*fn)(struct tm *, int *); @@ -606,6 +632,8 @@ static const struct special { { "noon", date_noon }, { "midnight", date_midnight }, { "tea", date_tea }, + { "PM", date_pm }, + { "AM", date_am }, { NULL } }; @@ -717,6 +745,18 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) char *end; unsigned long number = strtoul(date, &end, 10); + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + int match = match_multi_number(number, *end, date, end, tm); + if (match) + return date + match; + } + } + *num = number; return end; } -- cgit v0.10.2-6-g49f6 From a28383770ec44357bfce4af834dc09bf14d9410e Mon Sep 17 00:00:00 2001 From: Alex Riesen <fork0@t-online.de> Date: Thu, 28 Sep 2006 21:12:55 +0200 Subject: do not discard constness in interp_set_entry value argument Signed-off-by: Alex Riesen <raa.lkml@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/interpolate.c b/interpolate.c index 62701d8..5d9d188 100644 --- a/interpolate.c +++ b/interpolate.c @@ -8,10 +8,10 @@ #include "interpolate.h" -void interp_set_entry(struct interp *table, int slot, char *value) +void interp_set_entry(struct interp *table, int slot, const char *value) { char *oldval = table[slot].value; - char *newval = value; + char *newval = NULL; if (oldval) free(oldval); diff --git a/interpolate.h b/interpolate.h index a55fb8e..190a180 100644 --- a/interpolate.h +++ b/interpolate.h @@ -16,7 +16,7 @@ struct interp { char *value; }; -extern void interp_set_entry(struct interp *table, int slot, char *value); +extern void interp_set_entry(struct interp *table, int slot, const char *value); extern void interp_clear_table(struct interp *table, int ninterps); extern int interpolate(char *result, int reslen, -- cgit v0.10.2-6-g49f6 From 77e565d8f76357781eb6236e031e8e0581de83a9 Mon Sep 17 00:00:00 2001 From: Matthias Lederhofer <matled@gmx.net> Date: Thu, 28 Sep 2006 21:55:35 +0200 Subject: git-format-patch: fix bug using -o in subdirectories This was introduced by me in commit v1.4.2.1-gc08e524. Signed-off-by: Matthias Lederhofer <matled@gmx.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-log.c b/builtin-log.c index 130b53a..9d1ceae 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -270,8 +270,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.extra_headers = extra_headers; - output_directory = prefix; - /* * Parse the arguments before setup_revisions(), or something * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is @@ -350,6 +348,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH; + if (!output_directory) + output_directory = prefix; + if (output_directory) { if (use_stdout) die("standard output, or directory, which one?"); -- cgit v0.10.2-6-g49f6 From 6dd36acd32476a474a5b7d2ad309a82c84513abe Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Thu, 28 Sep 2006 16:47:50 -0700 Subject: gitweb: "alternate" starts with shade (i.e. 1) When displaying a list of rows (difftree, shortlog, etc), the first entry is now printed shaded, i.e. alternate is initialized to 1, as opposed to non-shaded (alternate initialized to 0). This solves the problem when there is only one row to display -- it is displayed shaded to visually indicate that it is "active", part of a "list", etc. (Compare this to the trivial case of more than one entry, where the rows have alternating shade, thus suggesting being part of a "list" of "active" entries, etc.) Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c86ac1d..9550bd7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1699,7 +1699,7 @@ sub git_difftree_body { print "</div>\n"; print "<table class=\"diff_tree\">\n"; - my $alternate = 0; + my $alternate = 1; my $patchno = 0; foreach my $line (@{$difftree}) { my %diff = parse_difftree_raw_line($line); @@ -1993,7 +1993,7 @@ sub git_shortlog_body { $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); print "<table class=\"shortlog\" cellspacing=\"0\">\n"; - my $alternate = 0; + my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $commit = $revlist->[$i]; #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; @@ -2035,7 +2035,7 @@ sub git_history_body { $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist}); print "<table class=\"history\" cellspacing=\"0\">\n"; - my $alternate = 0; + my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) { next; @@ -2099,7 +2099,7 @@ sub git_tags_body { $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); print "<table class=\"tags\" cellspacing=\"0\">\n"; - my $alternate = 0; + my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $taglist->[$i]; my %tag = %$entry; @@ -2159,7 +2159,7 @@ sub git_heads_body { $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to); print "<table class=\"heads\" cellspacing=\"0\">\n"; - my $alternate = 0; + my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $headlist->[$i]; my %tag = %$entry; @@ -2275,7 +2275,7 @@ sub git_project_list { } print "<th></th>\n" . "</tr>\n"; - my $alternate = 0; + my $alternate = 1; foreach my $pr (@projects) { if ($alternate) { print "<tr class=\"dark\">\n"; @@ -2793,7 +2793,7 @@ sub git_tree { git_print_page_path($file_name, 'tree', $hash_base); print "<div class=\"page_body\">\n"; print "<table cellspacing=\"0\">\n"; - my $alternate = 0; + my $alternate = 1; foreach my $line (@entries) { my %t = parse_ls_tree_line($line, -z => 1); @@ -3389,7 +3389,7 @@ sub git_search { git_print_header_div('commit', esc_html($co{'title'}), $hash); print "<table cellspacing=\"0\">\n"; - my $alternate = 0; + my $alternate = 1; if ($commit_search) { $/ = "\0"; open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next; -- cgit v0.10.2-6-g49f6 From d1d866e9b8d5a6f0dbe490ba3467440bbf87feaf Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Thu, 28 Sep 2006 16:48:40 -0700 Subject: gitweb: Remove redundant "commit" link from shortlog Remove the redundant "commit" link from shortlog. It can be had by simply clicking on the entry title of the row. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9550bd7..5ef4d07 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2013,7 +2013,6 @@ sub git_shortlog_body { href(action=>"commit", hash=>$commit), $ref); print "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); print "</td>\n" . -- cgit v0.10.2-6-g49f6 From de9272f4bd029d0f331d154b59e64d160e6d1d14 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Thu, 28 Sep 2006 16:49:21 -0700 Subject: gitweb: Factor out gitweb_have_snapshot() Create gitweb_have_snapshot() which returns true of snapshot is available and enabled, else false. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5ef4d07..97b30aa 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -155,6 +155,13 @@ sub feature_snapshot { return ($ctype, $suffix, $command); } +sub gitweb_have_snapshot { + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + + return $have_snapshot; +} + # To enable system wide have in $GITWEB_CONFIG # $feature{'pickaxe'}{'default'} = [1]; # To have project specific config enable override in $GITWEB_CONFIG @@ -2736,8 +2743,7 @@ sub git_blob { } sub git_tree { - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); + my $have_snapshot = gitweb_have_snapshot(); if (!defined $hash) { $hash = git_get_head_hash($project); @@ -2813,7 +2819,6 @@ sub git_tree { } sub git_snapshot { - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); my $have_snapshot = (defined $ctype && defined $suffix); if (!$have_snapshot) { @@ -2923,8 +2928,7 @@ sub git_commit { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); + my $have_snapshot = gitweb_have_snapshot(); my @views_nav = (); if (defined $file_name && defined $co{'parent'}) { -- cgit v0.10.2-6-g49f6 From ba6ef81017a84c2fce822b7ceba74f67dea6919e Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Thu, 28 Sep 2006 16:50:09 -0700 Subject: gitweb: Add snapshot to shortlog Add snapshot to each commit-row of shortlog. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 97b30aa..8ad0457 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2021,7 +2021,8 @@ sub git_shortlog_body { print "</td>\n" . "<td class=\"link\">" . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . " | " . + $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); print "</td>\n" . "</tr>\n"; } -- cgit v0.10.2-6-g49f6 From a2a3bf7b2baf0ff64c5b5ffc78d54be82d9967f1 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Thu, 28 Sep 2006 16:51:43 -0700 Subject: gitweb: Don't use quotemeta on internally generated strings Do not use quotemeta on internally generated strings such as filenames of snapshot, blobs, etc. quotemeta quotes any characters not matching /A-Za-z_0-9/. Which means that we get strings like this: before: linux\-2\.6\.git\-5c2d97cb31fb77981797fec46230ca005b865799\.tar\.gz after: linux-2.6.git-5c2d97cb31fb77981797fec46230ca005b865799.tar.gz This patch fixes this. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8ad0457..a99e116 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2660,7 +2660,7 @@ sub git_blob_plain { print $cgi->header( -type => "$type", -expires=>$expires, - -content_disposition => 'inline; filename="' . quotemeta($save_as) . '"'); + -content_disposition => 'inline; filename="' . "$save_as" . '"'); undef $/; binmode STDOUT, ':raw'; print <$fd>; @@ -2835,7 +2835,7 @@ sub git_snapshot { print $cgi->header( -type => 'application/x-tar', -content_encoding => $ctype, - -content_disposition => 'inline; filename="' . quotemeta($filename) . '"', + -content_disposition => 'inline; filename="' . "$filename" . '"', -status => '200 OK'); my $git_command = git_cmd_str(); @@ -2933,7 +2933,6 @@ sub git_commit { my @views_nav = (); if (defined $file_name && defined $co{'parent'}) { - my $parent = $co{'parent'}; push @views_nav, $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); @@ -3145,7 +3144,7 @@ sub git_blobdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => 'inline; filename="' . quotemeta($file_name) . '.patch"'); + -content_disposition => 'inline; filename="' . "$file_name" . '.patch"'); print "X-Git-Url: " . $cgi->self_url() . "\n\n"; @@ -3248,7 +3247,7 @@ sub git_commitdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => 'inline; filename="' . quotemeta($filename) . '"'); + -content_disposition => 'inline; filename="' . "$filename" . '"'); my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); print <<TEXT; From: $co{'author'} -- cgit v0.10.2-6-g49f6 From 3ea099d48b15f69889f4efe71599c9dfde6bb26a Mon Sep 17 00:00:00 2001 From: Sasha Khapyorsky <sashak@voltaire.com> Date: Fri, 29 Sep 2006 03:10:44 +0300 Subject: http/ftp: optionally ask curl to not use EPSV command If http.noEPSV config variable is defined and true, or if GIT_CURL_FTP_NO_EPSV environment variable is defined, disable using of EPSV ftp command (PASV will be used instead). This is helpful with some "poor" ftp servers which does not support EPSV mode. Signed-off-by: Sasha Khapyorsky <sashak@voltaire.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/config.txt b/Documentation/config.txt index 98c1f3e..84e3891 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -202,6 +202,12 @@ http.lowSpeedLimit, http.lowSpeedTime:: Can be overridden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and 'GIT_HTTP_LOW_SPEED_TIME' environment variables. +http.noEPSV:: + A boolean which disables using of EPSV ftp command by curl. + This can helpful with some "poor" ftp servers which doesn't + support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV' + environment variable. Default is false (curl will use EPSV). + i18n.commitEncoding:: Character encoding the commit messages are stored in; git itself does not care per se, but this information is necessary e.g. when diff --git a/git-clone.sh b/git-clone.sh index e1b3bf3..3998c55 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -31,6 +31,10 @@ clone_dumb_http () { cd "$2" && clone_tmp="$GIT_DIR/clone-tmp" && mkdir -p "$clone_tmp" || exit 1 + if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \ + "`git-repo-config --bool http.noEPSV`" = true ]; then + curl_extra_args="${curl_extra_args} --disable-epsv" + fi http_fetch "$1/info/refs" "$clone_tmp/refs" || { echo >&2 "Cannot get remote repository information. Perhaps git-update-server-info needs to be run there?" diff --git a/git-fetch.sh b/git-fetch.sh index 50ad101..bcc67ab 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -289,6 +289,10 @@ fetch_main () { if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi + if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \ + "`git-repo-config --bool http.noEPSV`" = true ]; then + noepsv_opt="--disable-epsv" + fi max_depth=5 depth=0 head="ref: $remote_name" @@ -300,7 +304,7 @@ fetch_main () { $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg; print "$u"; ' "$head") - head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") + head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted") depth=$( expr \( $depth + 1 \) ) done expr "z$head" : "z$_x40\$" >/dev/null || diff --git a/git-ls-remote.sh b/git-ls-remote.sh index 2c0b521..0f88953 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -53,6 +53,10 @@ http://* | https://* | ftp://* ) if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi + if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \ + "`git-repo-config --bool http.noEPSV`" = true ]; then + curl_extra_args="${curl_extra_args} --disable-epsv" + fi curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" || echo "failed slurping" ;; diff --git a/http.c b/http.c index 6c1937b..576740f 100644 --- a/http.c +++ b/http.c @@ -23,6 +23,7 @@ char *ssl_capath = NULL; char *ssl_cainfo = NULL; long curl_low_speed_limit = -1; long curl_low_speed_time = -1; +int curl_ftp_no_epsv = 0; struct curl_slist *pragma_header; @@ -155,6 +156,11 @@ static int http_options(const char *var, const char *value) return 0; } + if (!strcmp("http.noepsv", var)) { + curl_ftp_no_epsv = git_config_bool(var, value); + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value); } @@ -196,6 +202,9 @@ static CURL* get_curl_handle(void) curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT); + if (curl_ftp_no_epsv) + curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); + return result; } @@ -251,6 +260,9 @@ void http_init(void) max_requests = DEFAULT_MAX_REQUESTS; #endif + if (getenv("GIT_CURL_FTP_NO_EPSV")) + curl_ftp_no_epsv = 1; + #ifndef NO_CURL_EASY_DUPHANDLE curl_default = get_curl_handle(); #endif -- cgit v0.10.2-6-g49f6 From e46b3c027e7953bccbfe26910ff8c2d43225a17c Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Thu, 28 Sep 2006 17:20:23 -0700 Subject: gitweb: Remove redundant "commit" from history Remove redundant "commit" from history -- it can be had by clicking on the title of the commit. This commit makes visualization consistent with shortlog, log, etc. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a99e116..caaa371 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2071,7 +2071,6 @@ sub git_history_body { href(action=>"commit", hash=>$commit), $ref); print "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype); -- cgit v0.10.2-6-g49f6 From 6d81c5a2ea6e0b11fdee87d61d073850c17ce6d8 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Thu, 28 Sep 2006 17:21:07 -0700 Subject: gitweb: History: blob and tree are first, then commitdiff, etc Reorder link display in history to be consistent with other list displays: log, shortlog, etc. We now display: blob | commitdiff blob | commitdiff | diff_to_current and tree | commitdiff Instead of the old history format where "blob" and "tree" are between "commitdiff" and "diff_to_current" if present/ applicable. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index caaa371..c8557c8 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2071,8 +2071,8 @@ sub git_history_body { href(action=>"commit", hash=>$commit), $ref); print "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . - $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype); + $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); if ($ftype eq 'blob') { my $blob_current = git_get_hash_by_path($hash_base, $file_name); -- cgit v0.10.2-6-g49f6 From 5c5b2ea9ab95f71ac155f12d75d1432b5c93c2bb Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Thu, 28 Sep 2006 15:07:16 -0700 Subject: diff --stat=width[,name-width]: allow custom diffstat output width. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index b5d9763..7b7b9e8 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -10,8 +10,11 @@ --patch-with-raw:: Synonym for "-p --raw". ---stat:: - Generate a diffstat. +--stat[=width[,name-width]]:: + Generate a diffstat. You can override the default + output width for 80-column terminal by "--stat=width". + The width of the filename part can be controlled by + giving another width to it separated by a comma. --summary:: Output a condensed summary of extended header information diff --git a/diff.c b/diff.c index 3fd7a52..90e0844 100644 --- a/diff.c +++ b/diff.c @@ -1735,15 +1735,32 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--patch-with-raw")) { options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW; } - else if (!strcmp(arg, "--stat")) - options->output_format |= DIFF_FORMAT_DIFFSTAT; - else if (!strncmp(arg, "--stat-width=", 13)) { - options->stat_width = strtoul(arg + 13, NULL, 10); - options->output_format |= DIFF_FORMAT_DIFFSTAT; - } - else if (!strncmp(arg, "--stat-name-width=", 18)) { - options->stat_name_width = strtoul(arg + 18, NULL, 10); + else if (!strncmp(arg, "--stat", 6)) { + char *end; + int width = options->stat_width; + int name_width = options->stat_name_width; + arg += 6; + end = (char *)arg; + + switch (*arg) { + case '-': + if (!strncmp(arg, "-width=", 7)) + width = strtoul(arg + 7, &end, 10); + else if (!strncmp(arg, "-name-width=", 12)) + name_width = strtoul(arg + 12, &end, 10); + break; + case '=': + width = strtoul(arg+1, &end, 10); + if (*end == ',') + name_width = strtoul(end+1, &end, 10); + } + + /* Important! This checks all the error cases! */ + if (*end) + return 0; options->output_format |= DIFF_FORMAT_DIFFSTAT; + options->stat_name_width = name_width; + options->stat_width = width; } else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; -- cgit v0.10.2-6-g49f6 From 3ed74e608a69ce0f10bfad2e4ef6cf99eec04613 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin <Johannes.Schindelin@gmx.de> Date: Thu, 28 Sep 2006 17:37:39 +0200 Subject: diff --stat: ensure at least one '-' for deletions, and one '+' for additions The number of '-' and '+' is still linear. The idea is that scaled-length := floor(a * length + b) with the following constraints: if length == 1, scaled-length == 1, and the combined length of plusses and minusses should not be larger than the width by a small margin. Thus, a + b == 1 and a * max_plusses + b + a * max_minusses + b = width + 1 The solution is a * x + b = ((width - 1) * (x - 1) + max_change - 1) / (max_change - 1) Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff.c b/diff.c index 90e0844..2df085f 100644 --- a/diff.c +++ b/diff.c @@ -550,9 +550,12 @@ const char mime_boundary_leader[] = "------------"; static int scale_linear(int it, int width, int max_change) { /* - * round(width * it / max_change); + * make sure that at least one '-' is printed if there were deletions, + * and likewise for '+'. */ - return (it * width * 2 + max_change) / (max_change * 2); + if (max_change < 2) + return it; + return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1); } static void show_name(const char *prefix, const char *name, int len, @@ -684,9 +687,9 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) dels += del; if (width <= max_change) { - total = scale_linear(total, width, max_change); add = scale_linear(add, width, max_change); - del = total - add; + del = scale_linear(del, width, max_change); + total = add + del; } show_name(prefix, name, len, reset, set); printf("%5d ", added + deleted); -- cgit v0.10.2-6-g49f6 From 21ff2bdb887c836aab3346bbfa6a3e0251ff6104 Mon Sep 17 00:00:00 2001 From: Robin Rosenberg <robin.rosenberg@dewire.com> Date: Fri, 29 Sep 2006 01:28:55 +0200 Subject: Make cvsexportcommit remove files. Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 99b3dc3..5e23851 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -135,7 +135,7 @@ foreach my $f (@files) { if ($fields[4] eq 'M') { push @mfiles, $fields[5]; } - if ($fields[4] eq 'R') { + if ($fields[4] eq 'D') { push @dfiles, $fields[5]; } } -- cgit v0.10.2-6-g49f6 From bc1a5807575b2f34538d4158834da6524a4fc1f7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 29 Sep 2006 02:06:24 -0700 Subject: git-diff -B output fix. Geert noticed that complete rewrite diff missed the usual a/ and b/ leading paths. Pickaxe says it never worked, ever. Embarrassing. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/diff.c b/diff.c index 2464238..17f5a91 100644 --- a/diff.c +++ b/diff.c @@ -208,7 +208,7 @@ static void emit_rewrite_diff(const char *name_a, diff_populate_filespec(two, 0); lc_a = count_lines(one->data, one->size); lc_b = count_lines(two->data, two->size); - printf("--- %s\n+++ %s\n@@ -", name_a, name_b); + printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b); print_line_count(lc_a); printf(" +"); print_line_count(lc_b); -- cgit v0.10.2-6-g49f6 From 18b633cafcd43b83d752738fea62f185d4de1b91 Mon Sep 17 00:00:00 2001 From: Linus Torvalds <torvalds@osdl.org> Date: Fri, 29 Sep 2006 12:36:13 -0700 Subject: Fix approxidate() to understand 12:34 AM/PM are 00:34 and 12:34 It just simplifies the whole thing to say "hour = (hour % 12) + X" where X is 12 for PM and 0 for AM. It also fixes the "exact date" parsing, which didn't parse AM at all, and as such would do the same "12:30 AM" means "12:30 24-hour-format" bug. Of course, I hope that no exact dates use AM/PM anyway, but since we support the PM format, let's just get it right. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/date.c b/date.c index db4c185..1825922 100644 --- a/date.c +++ b/date.c @@ -256,8 +256,12 @@ static int match_alpha(const char *date, struct tm *tm, int *offset) } if (match_string(date, "PM") == 2) { - if (tm->tm_hour > 0 && tm->tm_hour < 12) - tm->tm_hour += 12; + tm->tm_hour = (tm->tm_hour % 12) + 12; + return 2; + } + + if (match_string(date, "AM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 0; return 2; } @@ -600,28 +604,30 @@ static void date_tea(struct tm *tm, int *num) static void date_pm(struct tm *tm, int *num) { - int hour = *num; + int hour, n = *num; *num = 0; - if (hour > 0 && hour < 12) { - tm->tm_hour = hour; + hour = tm->tm_hour; + if (n) { + hour = n; tm->tm_min = 0; tm->tm_sec = 0; } - if (tm->tm_hour > 0 && tm->tm_hour < 12) - tm->tm_hour += 12; + tm->tm_hour = (hour % 12) + 12; } static void date_am(struct tm *tm, int *num) { - int hour = *num; + int hour, n = *num; *num = 0; - if (hour > 0 && hour < 12) { - tm->tm_hour = hour; + hour = tm->tm_hour; + if (n) { + hour = n; tm->tm_min = 0; tm->tm_sec = 0; } + tm->tm_hour = (hour % 12); } static const struct special { -- cgit v0.10.2-6-g49f6 From f7661ce0b8ee068e53d57249625199dda2829e30 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 29 Sep 2006 00:34:51 -0700 Subject: Remove -fPIC which was only needed for Git.xs The distinction between BASIC_ vs ALL_ is still kept, since it is not Git.xs specific -- we could face the same issue when we do other language bindings (e.g. Python). Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/INSTALL b/INSTALL index 0d432d7..fce6bc3 100644 --- a/INSTALL +++ b/INSTALL @@ -48,7 +48,7 @@ Issues of note: GIT_EXEC_PATH=`pwd` PATH=`pwd`:$PATH - GITPERLLIB=`pwd`/perl/blib/lib:`pwd`/perl/blib/arch/auto/Git + GITPERLLIB=`pwd`/perl/blib/lib export GIT_EXEC_PATH PATH GITPERLLIB - Git is reasonably self-sufficient, but does depend on a few external diff --git a/Makefile b/Makefile index 8a7f29b..1875965 100644 --- a/Makefile +++ b/Makefile @@ -60,9 +60,6 @@ all: # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default # choice) has very fast version optimized for i586. # -# Define USE_PIC if you need the main git objects to be built with -fPIC -# in order to build and link perl/Git.so. x86-64 seems to need this. -# # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). @@ -112,7 +109,6 @@ uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') # CFLAGS and LDFLAGS are for the users to override from the command line. CFLAGS = -g -O2 -Wall -PIC_FLAG = -fPIC LDFLAGS = ALL_CFLAGS = $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) @@ -402,12 +398,6 @@ endif ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease endif -ifeq ($(uname_M),sun4u) - USE_PIC = YesPlease -endif -ifeq ($(uname_M),x86_64) - USE_PIC = YesPlease -endif -include config.mak.autogen -include config.mak @@ -546,9 +536,6 @@ else endif endif endif -ifdef USE_PIC - ALL_CFLAGS += $(PIC_FLAG) -endif ifdef NO_ACCURATE_DIFF BASIC_CFLAGS += -DNO_ACCURATE_DIFF endif diff --git a/config.mak.in b/config.mak.in index addda4f..fecae80 100644 --- a/config.mak.in +++ b/config.mak.in @@ -3,7 +3,6 @@ CC = @CC@ CFLAGS = @CFLAGS@ -PIC_FLAG = @PIC_FLAG@ AR = @AR@ TAR = @TAR@ #INSTALL = @INSTALL@ # needs install-sh or install.sh in sources diff --git a/configure.ac b/configure.ac index 0f93f6f..8192826 100644 --- a/configure.ac +++ b/configure.ac @@ -96,13 +96,6 @@ AC_SUBST(PYTHON_PATH) AC_MSG_NOTICE([CHECKS for programs]) # AC_PROG_CC([cc gcc]) -if test -n "$GCC"; then - PIC_FLAG="-fPIC" -else - AC_CHECK_DECL(__SUNPRO_C, [CFLAGS="$CFLAGS -xO3"; PIC_FLAG="-KPIC"]) -fi -AC_SUBST(PIC_FLAG) - #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) -- cgit v0.10.2-6-g49f6 From 4839bd8a6605e0c9c5c68ddec40d2a1a5ddd57d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= <sbejar@gmail.com> Date: Fri, 29 Sep 2006 20:05:40 +0200 Subject: fetch: Reset remote refs list each time fetch_main is called MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This prevents the fetch of the heads again in the second call of fetch_main. Signed-off-by: Santi Béjar <sbejar@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-fetch.sh b/git-fetch.sh index bcc67ab..f1522bd 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -257,6 +257,7 @@ fi fetch_main () { reflist="$1" refs= + rref= for ref in $reflist do -- cgit v0.10.2-6-g49f6 From ce74618d95088d99de53fb691f361da05932f705 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 22 Sep 2006 16:17:58 -0700 Subject: git-diff/git-apply: make diff output a bit friendlier to GNU patch (part 1) Somebody was wondering on #git channel why a git generated diff does not apply with GNU patch when the filename contains a SP. It is because GNU patch expects to find TAB (and trailing timestamp) on ---/+++ (old_name and new_name) lines after the filenames. The "diff --git" output format was carefully designed to be compatible with GNU patch where it can, but whitespace characters were always a pain. We can make our output a bit more GNU patch friendly by adding an extra TAB (but not trailing timestamp) to old/new name lines when the filename as a SP in it. This updates git-apply to prepare ourselves to accept such a patch, but we still do not generate output that is patch friendly yet. That change needs to wait until everybody has this change. When a filename contains a real tab, "diff --git" format always c-quotes it as discussed on the list with GNU patch maintainer previously: http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 so there should be no downside. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-apply.c b/builtin-apply.c index de5f855..a7317d7 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -360,7 +360,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch) static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) { if (!orig_name && !isnull) - return find_name(line, NULL, 1, 0); + return find_name(line, NULL, 1, TERM_TAB); if (orig_name) { int len; @@ -370,7 +370,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, len = strlen(name); if (isnull) die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); - another = find_name(line, NULL, 1, 0); + another = find_name(line, NULL, 1, TERM_TAB); if (!another || memcmp(another, name, len)) die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); free(another); -- cgit v0.10.2-6-g49f6 From 82ca50556471eadfc3212cb478161c3da5c4d02a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Fri, 29 Sep 2006 02:06:24 -0700 Subject: git-diff -B output fix. Geert noticed that complete rewrite diff missed the usual a/ and b/ leading paths. Pickaxe says it never worked, ever. Embarrassing. Signed-off-by: Junio C Hamano <junkio@cox.net> (cherry picked from bc1a5807575b2f34538d4158834da6524a4fc1f7 commit) diff --git a/diff.c b/diff.c index b3b1781..5dbc913 100644 --- a/diff.c +++ b/diff.c @@ -333,7 +333,7 @@ static void emit_rewrite_diff(const char *name_a, diff_populate_filespec(two, 0); lc_a = count_lines(one->data, one->size); lc_b = count_lines(two->data, two->size); - printf("--- %s\n+++ %s\n@@ -", name_a, name_b); + printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b); print_line_count(lc_a); printf(" +"); print_line_count(lc_b); -- cgit v0.10.2-6-g49f6 From 6f7ea5fb333554887656f7f6ec683544ec6e3c22 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Fri, 29 Sep 2006 09:57:43 -0700 Subject: gitweb: tree view: hash_base and hash are now context sensitive In tree view, by default, hash_base is HEAD and hash is the entry equivalent. Else the user had selected a hash_base or hash, say by clicking on a revision or commit, in which case those values are used. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c8557c8..a3c3e74 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1672,9 +1672,9 @@ sub git_print_tree_entry { "history"); } print " | " . - $cgi->a({-href => href(action=>"blob_plain", - hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, - "raw"); + $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base, + file_name=>"$basedir$t->{'name'}")}, + "raw"); print "</td>\n"; } elsif ($t->{'type'} eq "tree") { @@ -2745,14 +2745,14 @@ sub git_blob { sub git_tree { my $have_snapshot = gitweb_have_snapshot(); + if (!defined $hash_base) { + $hash_base = "HEAD"; + } if (!defined $hash) { - $hash = git_get_head_hash($project); if (defined $file_name) { - my $base = $hash_base || $hash; - $hash = git_get_hash_by_path($base, $file_name, "tree"); - } - if (!defined $hash_base) { - $hash_base = $hash; + $hash = git_get_hash_by_path($hash_base, $file_name, "tree"); + } else { + $hash = $hash_base; } } $/ = "\0"; -- cgit v0.10.2-6-g49f6 From bc7127ef0f81d996e9691c6c898b926867aae89f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 30 Sep 2006 02:25:30 -0700 Subject: ref locking: allow 'foo' when 'foo/bar' used to exist but not anymore. It is normal to have .git/refs/heads/foo directory which is empty after the last branch whose name starts with foo/ is removed. Make sure we notice this case and allow creation of branch foo by removing the empty directory. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 3d4cdd1..b433c0c 100644 --- a/refs.c +++ b/refs.c @@ -473,6 +473,59 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } +static int remove_empty_dir_recursive(char *path, int len) +{ + DIR *dir = opendir(path); + struct dirent *e; + int ret = 0; + + if (!dir) + return -1; + if (path[len-1] != '/') + path[len++] = '/'; + while ((e = readdir(dir)) != NULL) { + struct stat st; + int namlen; + if ((e->d_name[0] == '.') && + ((e->d_name[1] == 0) || + ((e->d_name[1] == '.') && e->d_name[2] == 0))) + continue; /* "." and ".." */ + + namlen = strlen(e->d_name); + if ((len + namlen < PATH_MAX) && + strcpy(path + len, e->d_name) && + !lstat(path, &st) && + S_ISDIR(st.st_mode) && + remove_empty_dir_recursive(path, len + namlen)) + continue; /* happy */ + + /* path too long, stat fails, or non-directory still exists */ + ret = -1; + break; + } + closedir(dir); + if (!ret) { + path[len] = 0; + ret = rmdir(path); + } + return ret; +} + +static int remove_empty_directories(char *file) +{ + /* we want to create a file but there is a directory there; + * if that is an empty directory (or a directory that contains + * only empty directories), remove them. + */ + char path[PATH_MAX]; + int len = strlen(file); + + if (len >= PATH_MAX) /* path too long ;-) */ + return -1; + strcpy(path, file); + return remove_empty_dir_recursive(path, len); +} + static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1) { char *ref_file; @@ -485,6 +538,17 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock->lock_fd = -1; ref = resolve_ref(ref, lock->old_sha1, mustexist, NULL); + if (!ref && errno == EISDIR) { + /* we are trying to lock foo but we used to + * have foo/bar which now does not exist; + * it is normal for the empty directory 'foo' + * to remain. + */ + ref_file = git_path("%s", orig_ref); + if (remove_empty_directories(ref_file)) + die("there are still refs under '%s'", orig_ref); + ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, NULL); + } if (!ref) { int last_errno = errno; error("unable to resolve reference %s: %s", -- cgit v0.10.2-6-g49f6 From 5e290ff75a0e6996f297dc438aceb8e1f29a20a5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 30 Sep 2006 12:37:37 -0700 Subject: refs: minor restructuring of cached refs data. Once we read packed and loose refs, for_each_ref() and friends kept using them even after write_ref_sha1() and delete_ref() changed the refs. This adds invalidate_cached_refs() as a way to flush the cached information. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index b433c0c..6ee5f96 100644 --- a/refs.c +++ b/refs.c @@ -66,12 +66,42 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, return list; } -static struct ref_list *get_packed_refs(void) +/* + * Future: need to be in "struct repository" + * when doing a full libification. + */ +struct cached_refs { + char did_loose; + char did_packed; + struct ref_list *loose; + struct ref_list *packed; +} cached_refs; + +static void free_ref_list(struct ref_list *list) +{ + struct ref_list *next; + for ( ; list; list = next) { + next = list->next; + free(list); + } +} + +static void invalidate_cached_refs(void) { - static int did_refs = 0; - static struct ref_list *refs = NULL; + struct cached_refs *ca = &cached_refs; + + if (ca->did_loose && ca->loose) + free_ref_list(ca->loose); + if (ca->did_packed && ca->packed) + free_ref_list(ca->packed); + ca->loose = ca->packed = NULL; + ca->did_loose = ca->did_packed = 0; +} - if (!did_refs) { +static struct ref_list *get_packed_refs(void) +{ + if (!cached_refs.did_packed) { + struct ref_list *refs = NULL; FILE *f = fopen(git_path("packed-refs"), "r"); if (f) { struct ref_list *list = NULL; @@ -86,9 +116,10 @@ static struct ref_list *get_packed_refs(void) fclose(f); refs = list; } - did_refs = 1; + cached_refs.packed = refs; + cached_refs.did_packed = 1; } - return refs; + return cached_refs.packed; } static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) @@ -138,14 +169,11 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) static struct ref_list *get_loose_refs(void) { - static int did_refs = 0; - static struct ref_list *refs = NULL; - - if (!did_refs) { - refs = get_ref_dir("refs", NULL); - did_refs = 1; + if (!cached_refs.did_loose) { + cached_refs.loose = get_ref_dir("refs", NULL); + cached_refs.did_loose = 1; } - return refs; + return cached_refs.loose; } /* We allow "recursive" symbolic refs. Only within reason, though */ @@ -401,6 +429,7 @@ int delete_ref(const char *refname, unsigned char *sha1) fprintf(stderr, "warning: unlink(%s) failed: %s", lock->log_file, strerror(errno)); + invalidate_cached_refs(); return ret; } @@ -665,6 +694,7 @@ int write_ref_sha1(struct ref_lock *lock, unlock_ref(lock); return -1; } + invalidate_cached_refs(); if (log_ref_write(lock, sha1, logmsg) < 0) { unlock_ref(lock); return -1; -- cgit v0.10.2-6-g49f6 From 5cc3cef997503e7543d927dbe23daca891131168 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 30 Sep 2006 14:14:31 -0700 Subject: lock_ref_sha1(): do not sometimes error() and sometimes die(). This cleans up the error path in the function so it does not die() itself sometimes while signalling an error with NULL some other times which was inconsistent and confusing. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 6ee5f96..157de43 100644 --- a/refs.c +++ b/refs.c @@ -561,6 +561,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char const char *orig_ref = ref; struct ref_lock *lock; struct stat st; + int last_errno = 0; int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); lock = xcalloc(1, sizeof(struct ref_lock)); @@ -574,17 +575,18 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char * to remain. */ ref_file = git_path("%s", orig_ref); - if (remove_empty_directories(ref_file)) - die("there are still refs under '%s'", orig_ref); + if (remove_empty_directories(ref_file)) { + last_errno = errno; + error("there are still refs under '%s'", orig_ref); + goto error_return; + } ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, NULL); } if (!ref) { - int last_errno = errno; + last_errno = errno; error("unable to resolve reference %s: %s", orig_ref, strerror(errno)); - unlock_ref(lock); - errno = last_errno; - return NULL; + goto error_return; } lock->lk = xcalloc(1, sizeof(struct lock_file)); @@ -593,11 +595,19 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char ref_file = git_path("%s", ref); lock->force_write = lstat(ref_file, &st) && errno == ENOENT; - if (safe_create_leading_directories(ref_file)) - die("unable to create directory for %s", ref_file); + if (safe_create_leading_directories(ref_file)) { + last_errno = errno; + error("unable to create directory for %s", ref_file); + goto error_return; + } lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1); return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; + + error_return: + unlock_ref(lock); + errno = last_errno; + return NULL; } struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) -- cgit v0.10.2-6-g49f6 From 22a3844ebada55c207e2b85fb68509a7c9e2b5f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 30 Sep 2006 14:19:25 -0700 Subject: lock_ref_sha1(): check D/F conflict with packed ref when creating. This makes the ref locking codepath to notice if an existing ref overlaps with the ref we are creating. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 157de43..2bfa92a 100644 --- a/refs.c +++ b/refs.c @@ -588,6 +588,30 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char orig_ref, strerror(errno)); goto error_return; } + if (is_null_sha1(lock->old_sha1)) { + /* The ref did not exist and we are creating it. + * Make sure there is no existing ref that is packed + * whose name begins with our refname, nor a ref whose + * name is a proper prefix of our refname. + */ + int namlen = strlen(ref); /* e.g. 'foo/bar' */ + struct ref_list *list = get_packed_refs(); + while (list) { + /* list->name could be 'foo' or 'foo/bar/baz' */ + int len = strlen(list->name); + int cmplen = (namlen < len) ? namlen : len; + const char *lead = (namlen < len) ? list->name : ref; + + if (!strncmp(ref, list->name, cmplen) && + lead[cmplen] == '/') { + error("'%s' exists; cannot create '%s'", + list->name, ref); + goto error_return; + } + list = list->next; + } + } + lock->lk = xcalloc(1, sizeof(struct lock_file)); lock->ref_name = xstrdup(ref); -- cgit v0.10.2-6-g49f6 From c0277d15effb9efcaaaa0cd84decf71a327ac07b Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 30 Sep 2006 15:02:00 -0700 Subject: delete_ref(): delete packed ref This implements deletion of a packed ref. Since it is a very rare event to delete a ref compared to looking up, creating and updating, this opts to remove the ref from the packed-ref file instead of doing any of the filesystem based "negative ref" trick to optimize the deletion path. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 2bfa92a..858c534 100644 --- a/refs.c +++ b/refs.c @@ -406,33 +406,6 @@ int get_ref_sha1(const char *ref, unsigned char *sha1) return read_ref(mkpath("refs/%s", ref), sha1); } -int delete_ref(const char *refname, unsigned char *sha1) -{ - struct ref_lock *lock; - int err, i, ret = 0; - - lock = lock_any_ref_for_update(refname, sha1); - if (!lock) - return 1; - i = strlen(lock->lk->filename) - 5; /* .lock */ - lock->lk->filename[i] = 0; - err = unlink(lock->lk->filename); - if (err) { - ret = 1; - error("unlink(%s) failed: %s", - lock->lk->filename, strerror(errno)); - } - lock->lk->filename[i] = '.'; - - err = unlink(lock->log_file); - if (err && errno != ENOENT) - fprintf(stderr, "warning: unlink(%s) failed: %s", - lock->log_file, strerror(errno)); - - invalidate_cached_refs(); - return ret; -} - /* * Make sure "ref" is something reasonable to have under ".git/refs/"; * We do not like it if: @@ -555,7 +528,7 @@ static int remove_empty_directories(char *file) return remove_empty_dir_recursive(path, len); } -static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1) +static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag) { char *ref_file; const char *orig_ref = ref; @@ -567,7 +540,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - ref = resolve_ref(ref, lock->old_sha1, mustexist, NULL); + ref = resolve_ref(ref, lock->old_sha1, mustexist, flag); if (!ref && errno == EISDIR) { /* we are trying to lock foo but we used to * have foo/bar which now does not exist; @@ -580,7 +553,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char error("there are still refs under '%s'", orig_ref); goto error_return; } - ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, NULL); + ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag); } if (!ref) { last_errno = errno; @@ -640,12 +613,84 @@ struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) if (check_ref_format(ref)) return NULL; strcpy(refpath, mkpath("refs/%s", ref)); - return lock_ref_sha1_basic(refpath, old_sha1); + return lock_ref_sha1_basic(refpath, old_sha1, NULL); } struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1) { - return lock_ref_sha1_basic(ref, old_sha1); + return lock_ref_sha1_basic(ref, old_sha1, NULL); +} + +static int repack_without_ref(const char *refname) +{ + struct ref_list *list, *packed_ref_list; + int fd; + int found = 0; + struct lock_file packlock; + + packed_ref_list = get_packed_refs(); + for (list = packed_ref_list; list; list = list->next) { + if (!strcmp(refname, list->name)) { + found = 1; + break; + } + } + if (!found) + return 0; + memset(&packlock, 0, sizeof(packlock)); + fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0); + if (fd < 0) + return error("cannot delete '%s' from packed refs", refname); + + for (list = packed_ref_list; list; list = list->next) { + char line[PATH_MAX + 100]; + int len; + + if (!strcmp(refname, list->name)) + continue; + len = snprintf(line, sizeof(line), "%s %s\n", + sha1_to_hex(list->sha1), list->name); + /* this should not happen but just being defensive */ + if (len > sizeof(line)) + die("too long a refname '%s'", list->name); + write_or_die(fd, line, len); + } + return commit_lock_file(&packlock); +} + +int delete_ref(const char *refname, unsigned char *sha1) +{ + struct ref_lock *lock; + int err, i, ret = 0, flag = 0; + + lock = lock_ref_sha1_basic(refname, sha1, &flag); + if (!lock) + return 1; + if (!(flag & REF_ISPACKED)) { + /* loose */ + i = strlen(lock->lk->filename) - 5; /* .lock */ + lock->lk->filename[i] = 0; + err = unlink(lock->lk->filename); + if (err) { + ret = 1; + error("unlink(%s) failed: %s", + lock->lk->filename, strerror(errno)); + } + lock->lk->filename[i] = '.'; + } + /* removing the loose one could have resurrected an earlier + * packed one. Also, if it was not loose we need to repack + * without it. + */ + ret |= repack_without_ref(refname); + + err = unlink(lock->log_file); + if (err && errno != ENOENT) + fprintf(stderr, "warning: unlink(%s) failed: %s", + lock->log_file, strerror(errno)); + invalidate_cached_refs(); + unlock_ref(lock); + return ret; } void unlock_ref(struct ref_lock *lock) -- cgit v0.10.2-6-g49f6 From 936a9508cc0d5369b00c76ee63cdb81556e7be39 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sat, 30 Sep 2006 15:21:28 -0700 Subject: git-branch: remove D/F check done by hand. Now ref creation codepath in lock_ref_sha1() and friends notices the directory/file conflict situation, we do not do this by hand in git-branch anymore. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-branch.sh b/git-branch.sh index c616830..bf84b30 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -121,16 +121,6 @@ then done fi -branchdir=$(dirname $branchname) -while test "$branchdir" != "." -do - if git-show-ref --verify --quiet -- "refs/heads/$branchdir" - then - die "$branchdir already exists." - fi - branchdir=$(dirname $branchdir) -done - prev='' if git-show-ref --verify --quiet -- "refs/heads/$branchname" then -- cgit v0.10.2-6-g49f6 From 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=1B=2CAi=1B=28Bjar?= <sbejar@gmail.com> Date: Sun, 1 Oct 2006 05:33:05 +0200 Subject: fetch: Misc output cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In particular it removes duplicate information, uses short hashes (as git-log and company) and uses .. for fast forwarding commits and ... for not-fast-forwarding commits (shorter, easier to copy&paste). It also reformat the output as: 1. the ones we store in our local ref (either branches or tags): 1a) fast-forward * refs/heads/origin: fast forward to branch 'master' of ../git/ old..new: 1ad7a06..bc1a580 1b) same (only shown under -v) * refs/heads/next: same as branch 'origin/next' of ../git/ commit: ce47b9f 1c) non-fast-forward, forced * refs/heads/pu: forcing update to non-fast forward branch 'pu' of ../git/ old...new: 7c733a8...5faa935 1d) non-fast-forward, did not update because not forced * refs/heads/po: not updating to non-fast forward branch 'po' of ../git/ old...new: 7c733a8...5faa935 1e) creating a new local ref to store * refs/tags/v1.4.2-rc4: storing tag 'v1.4.2-rc4' of ../git/ tag: 8c7a107 * refs/heads/next: storing branch 'next' of ../git/ commit: f8a20ae 2. the ones we do not store in our local ref (only shown under -v): * fetched branch 'master' of ../git commit: 695dffe * fetched tag 'v1.4.2-rc4' of ../git tag: 8c7a107 Signed-off-by: Santi B.ANijar <sbejar@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-fetch.sh b/git-fetch.sh index f1522bd..85e96a1 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -129,22 +129,25 @@ append_fetch_head () { then headc_=$(git-rev-parse --verify "$head_^0") || exit echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD" - [ "$verbose" ] && echo >&2 "* committish: $head_" - [ "$verbose" ] && echo >&2 " $note_" else echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD" - [ "$verbose" ] && echo >&2 "* non-commit: $head_" - [ "$verbose" ] && echo >&2 " $note_" - fi - if test "$local_name_" != "" - then - # We are storing the head locally. Make sure that it is - # a fast forward (aka "reverse push"). - fast_forward_local "$local_name_" "$head_" "$note_" fi + + update_local_ref "$local_name_" "$head_" "$note_" } -fast_forward_local () { +update_local_ref () { + # If we are storing the head locally make sure that it is + # a fast forward (aka "reverse push"). + + label_=$(git-cat-file -t $2) + newshort_=$(git-rev-parse --short $2) + if test -z "$1" ; then + [ "$verbose" ] && echo >&2 "* fetched $3" + [ "$verbose" ] && echo >&2 " $label_: $newshort_" + return 0 + fi + oldshort_=$(git-rev-parse --short "$1" 2>/dev/null) mkdir -p "$(dirname "$GIT_DIR/$1")" case "$1" in refs/tags/*) @@ -154,13 +157,16 @@ fast_forward_local () { then if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2" then - [ "$verbose" ] && echo >&2 "* $1: same as $3" ||: + [ "$verbose" ] && echo >&2 "* $1: same as $3" + [ "$verbose" ] && echo >&2 " $label_: $newshort_" ||: else echo >&2 "* $1: updating with $3" + echo >&2 " $label_: $newshort_" git-update-ref -m "$rloga: updating tag" "$1" "$2" fi else echo >&2 "* $1: storing $3" + echo >&2 " $label_: $newshort_" git-update-ref -m "$rloga: storing tag" "$1" "$2" fi ;; @@ -178,31 +184,34 @@ fast_forward_local () { if test -n "$verbose" then echo >&2 "* $1: same as $3" + echo >&2 " $label_: $newshort_" fi ;; *,$local) echo >&2 "* $1: fast forward to $3" - echo >&2 " from $local to $2" + echo >&2 " old..new: $oldshort_..$newshort_" git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local" ;; *) false ;; esac || { - echo >&2 "* $1: does not fast forward to $3;" case ",$force,$single_force," in *,t,*) - echo >&2 " forcing update." + echo >&2 "* $1: forcing update to non-fast forward $3" + echo >&2 " old...new: $oldshort_...$newshort_" git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local" ;; *) - echo >&2 " not updating." + echo >&2 "* $1: not updating to non-fast forward $3" + echo >&2 " old...new: $oldshort_...$newshort_" exit 1 ;; esac } else echo >&2 "* $1: storing $3" + echo >&2 " $label_: $newshort_" git-update-ref -m "$rloga: storing head" "$1" "$2" fi ;; -- cgit v0.10.2-6-g49f6 From ba0ac36ec5708820e670731001f7ab35351c6c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= <sbejar@gmail.com> Date: Sun, 1 Oct 2006 05:34:17 +0200 Subject: merge and resolve: Output short hashes and .. in "Updating ..." MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Santi Béjar <sbejar@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-merge.sh b/git-merge.sh index 5b34b4d..49c46d5 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -197,7 +197,7 @@ f,*) ;; ?,1,"$head",*) # Again the most common case of merging one remote. - echo "Updating from $head to $1" + echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)" git-update-index --refresh 2>/dev/null new_head=$(git-rev-parse --verify "$1^0") && git-read-tree -u -v -m $head "$new_head" && diff --git a/git-resolve.sh b/git-resolve.sh index 729ec65..36b90e3 100755 --- a/git-resolve.sh +++ b/git-resolve.sh @@ -46,7 +46,7 @@ case "$common" in exit 0 ;; "$head") - echo "Updating from $head to $merge" + echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $merge)" git-read-tree -u -m $head $merge || exit 1 git-update-ref -m "resolve $merge_name: Fast forward" \ HEAD "$merge" "$head" -- cgit v0.10.2-6-g49f6 From 2eaf22242f61b13c38c87cbb0e84c84974c52d66 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 1 Oct 2006 00:27:27 -0700 Subject: show-ref --hash=len, --abbrev=len, and --abbrev This teaches show-ref to abbreviate the object name. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index b724d83..5973a82 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] - [-s|--hash] [--tags] [--heads] [--] <pattern>... + [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>... DESCRIPTION ----------- @@ -37,14 +37,13 @@ OPTIONS -d, --dereference:: - Dereference tags into object IDs. They will be shown with "^{}" + Dereference tags into object IDs as well. They will be shown with "^{}" appended. -s, --hash:: Only show the SHA1 hash, not the reference name. When also using - --dereference the dereferenced tag will still be shown after the SHA1, - this maybe a bug. + --dereference the dereferenced tag will still be shown after the SHA1. --verify:: @@ -52,6 +51,11 @@ OPTIONS Aside from returning an error code of 1, it will also print an error message if '--quiet' was not specified. +--abbrev, --abbrev=len:: + + Abbreviate the object name. When using `--hash`, you do + not have to say `--hash --abbrev`; `--hash=len` would do. + -q, --quiet:: Do not print any results to stdout. When combined with '--verify' this diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 12c457c..f2912e8 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -6,12 +6,13 @@ static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash] [--tags] [--heads] [--] [pattern*]"; static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, - found_match = 0, verify = 0, quiet = 0, hash_only = 0; + found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0; static const char **pattern; static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { struct object *obj; + const char *hex; if (tags_only || heads_only) { int match; @@ -51,13 +52,16 @@ match: } if (quiet) return 0; + + hex = find_unique_abbrev(sha1, abbrev); if (hash_only) - printf("%s\n", sha1_to_hex(sha1)); + printf("%s\n", hex); else - printf("%s %s\n", sha1_to_hex(sha1), refname); + printf("%s %s\n", hex, refname); if (deref_tags && obj->type == OBJ_TAG) { obj = deref_tag(obj, refname, 0); - printf("%s %s^{}\n", sha1_to_hex(obj->sha1), refname); + hex = find_unique_abbrev(obj->sha1, abbrev); + printf("%s %s^{}\n", hex, refname); } return 0; } @@ -94,6 +98,29 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) hash_only = 1; continue; } + if (!strncmp(arg, "--hash=", 7) || + (!strncmp(arg, "--abbrev", 8) && + (arg[8] == '=' || arg[8] == '\0'))) { + if (arg[3] != 'h' && !arg[8]) + /* --abbrev only */ + abbrev = DEFAULT_ABBREV; + else { + /* --hash= or --abbrev= */ + char *end; + if (arg[3] == 'h') { + hash_only = 1; + arg += 7; + } + else + arg += 9; + abbrev = strtoul(arg, &end, 10); + if (*end || abbrev > 40) + usage(show_ref_usage); + if (abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + } + continue; + } if (!strcmp(arg, "--verify")) { verify = 1; continue; -- cgit v0.10.2-6-g49f6 From fbc72799a82bb9a25657a6e30bfcd7b8fe3e5a45 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 1 Oct 2006 00:42:40 -0700 Subject: git-fetch: adjust to packed-refs. The command checked the presence of a ref by directly looking into $GIT_DIR/refs directory. Update it to use show-ref. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-fetch.sh b/git-fetch.sh index 85e96a1..216be0c 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -147,15 +147,15 @@ update_local_ref () { [ "$verbose" ] && echo >&2 " $label_: $newshort_" return 0 fi - oldshort_=$(git-rev-parse --short "$1" 2>/dev/null) - mkdir -p "$(dirname "$GIT_DIR/$1")" + oldshort_=$(git show-ref --hash --abbrev "$1" 2>/dev/null) + case "$1" in refs/tags/*) # Tags need not be pointing at commits so there # is no way to guarantee "fast-forward" anyway. - if test -f "$GIT_DIR/$1" + if test -n "$oldshort_" then - if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2" + if now_=$(git show-ref --hash "$1") && test "$now_" = "$2" then [ "$verbose" ] && echo >&2 "* $1: same as $3" [ "$verbose" ] && echo >&2 " $label_: $newshort_" ||: -- cgit v0.10.2-6-g49f6 From 1965efb1599f59b8e3380335d1fa395e2008a30b Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 1 Oct 2006 03:08:55 -0700 Subject: GIT 1.4.3-rc1 Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 14923c9..0cacac3 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.4.2.GIT +DEF_VER=v1.4.3.GIT LF=' ' -- cgit v0.10.2-6-g49f6 From 28bed6ea2198f6589ad43e48666906a879839442 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Sun, 1 Oct 2006 14:36:49 +0200 Subject: Fix a remove_empty_dir_recursive problem. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 858c534..221eb38 100644 --- a/refs.c +++ b/refs.c @@ -498,7 +498,7 @@ static int remove_empty_dir_recursive(char *path, int len) strcpy(path + len, e->d_name) && !lstat(path, &st) && S_ISDIR(st.st_mode) && - remove_empty_dir_recursive(path, len + namlen)) + !remove_empty_dir_recursive(path, len + namlen)) continue; /* happy */ /* path too long, stat fails, or non-directory still exists */ -- cgit v0.10.2-6-g49f6 From 14c8a681f751c425f47be38a5e98b514f000d499 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Sun, 1 Oct 2006 14:38:18 +0200 Subject: Clean up "git-branch.sh" and add remove recursive dir test cases. Now that directory recursive remove works in the core C code, we don't need to do it in "git-branch.sh". Also add test cases to check that directory recursive remove will continue to work. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-branch.sh b/git-branch.sh index bf84b30..4379a07 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -111,16 +111,6 @@ rev=$(git-rev-parse --verify "$head") || exit git-check-ref-format "heads/$branchname" || die "we do not like '$branchname' as a branch name." -if [ -d "$GIT_DIR/refs/heads/$branchname" ] -then - for refdir in `cd "$GIT_DIR" && \ - find "refs/heads/$branchname" -type d | sort -r` - do - rmdir "$GIT_DIR/$refdir" || \ - die "Could not delete '$refdir', there may still be a ref there." - done -fi - prev='' if git-show-ref --verify --quiet -- "refs/heads/$branchname" then diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 193fe1f..f31e79c 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -67,4 +67,31 @@ test_expect_success \ git-pack-refs && git-branch -d g' +test_expect_failure \ + 'git branch i/j/k should barf if branch i exists' \ + 'git-branch i && + git-pack-refs --prune && + git-branch i/j/k' + +test_expect_success \ + 'test git branch k after branch k/l/m and k/lm have been deleted' \ + 'git-branch k/l && + git-branch k/lm && + git-branch -d k/l && + git-branch k/l/m && + git-branch -d k/l/m && + git-branch -d k/lm && + git-branch k' + +test_expect_success \ + 'test git branch n after some branch deletion and pruning' \ + 'git-branch n/o && + git-branch n/op && + git-branch -d n/o && + git-branch n/o/p && + git-branch -d n/op && + git-pack-refs --prune && + git-branch -d n/o/p && + git-branch n' + test_done -- cgit v0.10.2-6-g49f6 From 26a063a10bca57f65d8fed6c4550a70d44a70b81 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 1 Oct 2006 11:41:00 -0700 Subject: Fix refs.c;:repack_without_ref() clean-up path The function repack_without_ref() passes a lock-file structure on the stack to hold_lock_file_for_update(), which in turn registers it to be cleaned up via atexit(). This is a big no-no. This is the same bug James Bottomley fixed with commit 31f584c242e7af28018ff920b6c8d1952beadbd4. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 221eb38..aa4c4e0 100644 --- a/refs.c +++ b/refs.c @@ -621,12 +621,13 @@ struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *o return lock_ref_sha1_basic(ref, old_sha1, NULL); } +static struct lock_file packlock; + static int repack_without_ref(const char *refname) { struct ref_list *list, *packed_ref_list; int fd; int found = 0; - struct lock_file packlock; packed_ref_list = get_packed_refs(); for (list = packed_ref_list; list; list = list->next) { -- cgit v0.10.2-6-g49f6 From d3d0013c59ed840520b86a65697137cb2c62819c Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Sun, 1 Oct 2006 22:16:22 +0200 Subject: Use git-update-ref to delete a tag instead of rm()ing the ref file. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-tag.sh b/git-tag.sh index 2bde3c0..6463b31 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -47,8 +47,10 @@ do -d) shift tag_name="$1" - rm "$GIT_DIR/refs/tags/$tag_name" && \ - echo "Deleted tag $tag_name." + tag=$(git-show-ref --verify --hash -- "refs/tags/$tag_name") || + die "Seriously, what tag are you talking about?" + git-update-ref -m 'tag: delete' -d "refs/tags/$tag_name" "$tag" && + echo "Deleted tag $tag_name." exit $? ;; -*) -- cgit v0.10.2-6-g49f6 From b431b2822f361efcb940adbc1f2097e122e90ed9 Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Sun, 1 Oct 2006 22:33:04 +0200 Subject: Check that a tag exists using show-ref instead of looking for the ref file. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-tag.sh b/git-tag.sh index 6463b31..a3f1819 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -66,7 +66,7 @@ done name="$1" [ "$name" ] || usage prev=0000000000000000000000000000000000000000 -if test -e "$GIT_DIR/refs/tags/$name" +if git-show-ref --verify --quiet -- "refs/tags/$name" then test -n "$force" || die "tag '$name' already exists" prev=`git rev-parse "refs/tags/$name"` -- cgit v0.10.2-6-g49f6 From 7c2738cefb07e6549cafa42d1d383a3bb54ed0f3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 1 Oct 2006 21:41:46 -0700 Subject: Makefile: install and clean merge-recur, still. We advertised git-merge-recur for some time, and we planned to support it for one release after we made it the 'recursive'. However we forgot to install it nor have "make clean" clean it. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index dd33158..401b893 100644 --- a/Makefile +++ b/Makefile @@ -215,7 +215,8 @@ BUILT_INS = \ $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir -ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) +ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) \ + git-merge-recur$X # Backward compatibility -- to be removed after 1.0 PROGRAMS += git-ssh-pull$X git-ssh-push$X @@ -586,8 +587,7 @@ export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \ - git-merge-recur$X +all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi all: perl/Makefile $(MAKE) -C perl -- cgit v0.10.2-6-g49f6 From 367337040d8d39294bf676672dfefc542717195b Mon Sep 17 00:00:00 2001 From: Christian Couder <chriscool@tuxfamily.org> Date: Mon, 2 Oct 2006 06:36:15 +0200 Subject: Do not create tag leading directories since git update-ref does it. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-tag.sh b/git-tag.sh index a3f1819..ac269e3 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -112,6 +112,5 @@ if [ "$annotate" ]; then object=$(git-mktag < "$GIT_DIR"/TAG_TMP) fi -leading=`expr "refs/tags/$name" : '\(.*\)/'` && -mkdir -p "$GIT_DIR/$leading" && -GIT_DIR="$GIT_DIR" git update-ref "refs/tags/$name" "$object" "$prev" +git update-ref "refs/tags/$name" "$object" "$prev" + -- cgit v0.10.2-6-g49f6 From 4fddf5798d8a0eb112c60a05272a2d9407eafc8f Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 1 Oct 2006 22:22:07 -0700 Subject: git-mv: invalidate the removed path properly in cache-tree The command updated the cache without invalidating the cache tree entries while removing an existing entry. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-mv.c b/builtin-mv.c index ff882be..6b0ab8a 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -278,6 +278,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) for (i = 0; i < deleted.nr; i++) { const char *path = deleted.items[i].path; remove_file_from_cache(path); + cache_tree_invalidate_path(active_cache_tree, path); } if (active_cache_changed) { diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index b7fcdb3..23a1eff 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -86,4 +86,23 @@ test_expect_success \ 'move into "."' \ 'git-mv path1/path2/ .' +test_expect_success "Michael Cassar's test case" ' + rm -fr .git papers partA && + git init-db && + mkdir -p papers/unsorted papers/all-papers partA && + echo a > papers/unsorted/Thesis.pdf && + echo b > partA/outline.txt && + echo c > papers/unsorted/_another && + git add papers partA && + T1=`git write-tree` && + + git mv papers/unsorted/Thesis.pdf papers/all-papers/moo-blah.pdf && + + T=`git write-tree` && + git ls-tree -r $T | grep partA/outline.txt || { + git ls-tree -r $T + (exit 1) + } +' + test_done -- cgit v0.10.2-6-g49f6 From 6fe5b7ff6cafcc94415deba2f3d611770d8e6b1e Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Mon, 2 Oct 2006 00:43:52 -0700 Subject: git-push: .git/remotes/ file does not require SP after colon Although most people would have one after colon if only for readability, we never required it in git-parse-remote, so let's not require one only in git-push. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-push.c b/builtin-push.c index 53bc378..273b27d 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -80,12 +80,12 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI]) int is_refspec; char *s, *p; - if (!strncmp("URL: ", buffer, 5)) { + if (!strncmp("URL:", buffer, 4)) { is_refspec = 0; - s = buffer + 5; - } else if (!strncmp("Push: ", buffer, 6)) { + s = buffer + 4; + } else if (!strncmp("Push:", buffer, 5)) { is_refspec = 1; - s = buffer + 6; + s = buffer + 5; } else continue; -- cgit v0.10.2-6-g49f6 From 9e756904d05a53f1f519492ea1addc3e3163c678 Mon Sep 17 00:00:00 2001 From: Martin Waitz <tali@admingilde.org> Date: Sun, 1 Oct 2006 23:57:48 +0200 Subject: gitweb: start to generate PATH_INFO URLs. Instead of providing the project as a ?p= parameter it is simply appended to the base URI. All other parameters are appended to that, except for ?a=summary which is the default and can be omitted. The this can be enabled with the "pathinfo" feature in gitweb_config.perl. [jc: let's introduce new features disabled by default not to upset too many existing installations.] Signed-off-by: Martin Waitz <tali@admingilde.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 44991b1..10e803a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -102,6 +102,10 @@ our %feature = ( 'sub' => \&feature_pickaxe, 'override' => 0, 'default' => [1]}, + + 'pathinfo' => { + 'override' => 0, + 'default' => [0]}, ); sub gitweb_check_feature { @@ -375,6 +379,7 @@ exit; sub href(%) { my %params = @_; + my $href = $my_uri; my @mapping = ( project => "p", @@ -393,6 +398,19 @@ sub href(%) { $params{'project'} = $project unless exists $params{'project'}; + my ($use_pathinfo) = gitweb_check_feature('pathinfo'); + if ($use_pathinfo) { + # use PATH_INFO for project name + $href .= "/$params{'project'}" if defined $params{'project'}; + delete $params{'project'}; + + # Summary just uses the project path URL + if (defined $params{'action'} && $params{'action'} eq 'summary') { + delete $params{'action'}; + } + } + + # now encode the parameters explicitly my @result = (); for (my $i = 0; $i < @mapping; $i += 2) { my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]); @@ -400,7 +418,9 @@ sub href(%) { push @result, $symbol . "=" . esc_param($params{$name}); } } - return "$my_uri?" . join(';', @result); + $href .= "?" . join(';', @result) if scalar @result; + + return $href; } -- cgit v0.10.2-6-g49f6 From 7a21632fa346df58d94d32f09625025931ef13ec Mon Sep 17 00:00:00 2001 From: Dennis Stosberg <dennis@stosberg.net> Date: Mon, 2 Oct 2006 19:23:53 +0200 Subject: lock_ref_sha1_basic does not remove empty directories on BSD lock_ref_sha1_basic relies on errno beeing set to EISDIR by the call to read() in resolve_ref() to detect directories. But calling read() on a directory under NetBSD returns EPERM, and even succeeds for local filesystems on FreeBSD. Signed-off-by: Dennis Stosberg <dennis@stosberg.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 5e65314..98327d7 100644 --- a/refs.c +++ b/refs.c @@ -42,6 +42,12 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) } } + /* Is it a directory? */ + if (S_ISDIR(st.st_mode)) { + errno = EISDIR; + return NULL; + } + /* * Anything else, just open it and try to use it as * a ref -- cgit v0.10.2-6-g49f6 From cb626bc6300c988d066aacc53a3f5893ee5dba53 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg <dennis@stosberg.net> Date: Mon, 2 Oct 2006 19:23:53 +0200 Subject: lock_ref_sha1_basic does not remove empty directories on BSD lock_ref_sha1_basic relies on errno beeing set to EISDIR by the call to read() in resolve_ref() to detect directories. But calling read() on a directory under NetBSD returns EPERM, and even succeeds for local filesystems on FreeBSD. Signed-off-by: Dennis Stosberg <dennis@stosberg.net> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/refs.c b/refs.c index 5e65314..98327d7 100644 --- a/refs.c +++ b/refs.c @@ -42,6 +42,12 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) } } + /* Is it a directory? */ + if (S_ISDIR(st.st_mode)) { + errno = EISDIR; + return NULL; + } + /* * Anything else, just open it and try to use it as * a ref -- cgit v0.10.2-6-g49f6 From b599deec18719379167c639613379abb9205e065 Mon Sep 17 00:00:00 2001 From: Robin Rosenberg <robin.rosenberg@dewire.com> Date: Tue, 3 Oct 2006 02:02:15 +0200 Subject: Error in test description of t1200-tutorial Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index c7db20e..0272dd4 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Johannes Schindelin # -test_description='Test git-rev-parse with different parent options' +test_description='A simple turial in the form of a test case' . ./test-lib.sh -- cgit v0.10.2-6-g49f6 From 54bd25580e227c15a5f1a2b01be794b65e434665 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Mon, 2 Oct 2006 22:52:57 -0700 Subject: escape tilde in Documentation/git-rev-parse.txt Fixes a failure to build the git-rev-parse manpage, seen with asciidoc 8.0.0 We would love to use nicer quoting $$~$$ but alas asciidoc 7 does not know about it. So use asciidoc.conf and define {tilde} to be ~. Signed-off-by: Junio C Hamano <junkio@cox.net> Acked-by: Stefan Richter <stefanr@s5r6.in-berlin.de> diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf index 8196d78..44b1ce4 100644 --- a/Documentation/asciidoc.conf +++ b/Documentation/asciidoc.conf @@ -11,6 +11,7 @@ caret=^ startsb=[ endsb=] +tilde=~ ifdef::backend-docbook[] [gitlink-inlinemacro] diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index b761b4b..2f1306c 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -138,7 +138,7 @@ syntax. 'rev{caret}0' means the commit itself and is used when 'rev' is the object name of a tag object that refers to a commit object. -* A suffix '~<n>' to a revision parameter means the commit +* A suffix '{tilde}<n>' to a revision parameter means the commit object that is the <n>th generation grand-parent of the named commit object, following only the first parent. I.e. rev~3 is equivalent to rev{caret}{caret}{caret} which is equivalent to\ -- cgit v0.10.2-6-g49f6 From e70866f53a8d31cde6cfff6396ba0d1f64029afb Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Mon, 2 Oct 2006 14:54:53 -0700 Subject: gitweb: Escape ESCAPE (\e) character Take a look at commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1 using gitweb before this patch. This patch fixes this. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 44991b1..3e9d4a0 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -465,6 +465,7 @@ sub esc_html { $str = decode("utf8", $str, Encode::FB_DEFAULT); $str = escapeHTML($str); $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) + $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) return $str; } -- cgit v0.10.2-6-g49f6 From 128eead19821ba18e2912ebf30e5621d08735e79 Mon Sep 17 00:00:00 2001 From: Martin Waitz <tali@admingilde.org> Date: Tue, 3 Oct 2006 10:03:28 +0200 Subject: gitweb: document webserver configuration for common gitweb/repo URLs. Add a small apache configuration which shows how to use apache to put gitweb and GIT repositories at the same URL. Signed-off-by: Martin Waitz <tali@admingilde.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/README b/gitweb/README index 27c6dac..61c7ab5 100644 --- a/gitweb/README +++ b/gitweb/README @@ -1,4 +1,5 @@ GIT web Interface +================= The one working on: http://www.kernel.org/git/ @@ -6,7 +7,8 @@ The one working on: From the git version 1.4.0 gitweb is bundled with git. -How to configure gitweb for your local system: +How to configure gitweb for your local system +--------------------------------------------- You can specify the following configuration variables when building GIT: * GITWEB_SITENAME @@ -29,6 +31,28 @@ You can specify the following configuration variables when building GIT: environment variable will be loaded instead of the file specified when gitweb.cgi was created. + +Webserver configuration +----------------------- + +If you want to have one URL for both gitweb and your http:// +repositories, you can configure apache like this: + +<VirtualHost www:80> + ServerName git.domain.org + DocumentRoot /pub/git + RewriteEngine on + RewriteRule ^/(.*\.git/(?!/?(info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] +</VirtualHost> + +The above configuration expects your public repositories to live under +/pub/git and will serve them as http://git.domain.org/dir-under-pub-git, +both as cloneable GIT URL and as browseable gitweb interface. +If you then start your git-daemon with --base-path=/pub/git --export-all +then you can even use the git:// URL with exactly the same path. + + + Originally written by: Kay Sievers <kay.sievers@vrfy.org> -- cgit v0.10.2-6-g49f6 From 954a6183756a073723a7c9fd8d2feb13132876b0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Sun, 1 Oct 2006 02:16:11 -0700 Subject: gitweb: make leftmost column of blame less cluttered. Instead of labelling each and every line with clickable commit object name, this makes the blame output to show them only on the first line of each group of lines from the same revision. Placing too many lines in one group would make the commit object name to appear too widely separated and also makes it consume more memory, the number of lines in one group is capped to 20 lines or so. Also it makes mouse-over to show the minimum authorship and authordate information for extra cuteness ;-). Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl old mode 100755 new mode 100644 index 3e9d4a0..55d1b2c --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2430,9 +2430,64 @@ sub git_tag { git_footer_html(); } +sub git_blame_flush_chunk { + my ($name, $revdata, $color, $rev, @line) = @_; + my $label = substr($rev, 0, 8); + my $line = scalar(@line); + my $cnt = 0; + my $pop = ''; + + if ($revdata->{$rev} ne '') { + $pop = ' title="' . esc_html($revdata->{$rev}) . '"'; + } + + for (@line) { + my ($lineno, $data) = @$_; + $cnt++; + print "<tr class=\"$color\">\n"; + if ($cnt == 1) { + print "<td class=\"sha1\"$pop"; + if ($line > 1) { + print " rowspan=\"$line\""; + } + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$rev, + file_name=>$name)}, + $label); + print "</td>\n"; + } + print "<td class=\"linenr\">". + "<a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . + esc_html($lineno) . "</a></td>\n"; + print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; + print "</tr>\n"; + } +} + +# We can have up to N*2 lines. If it is more than N lines, split it +# into two to avoid orphans. +sub git_blame_flush_chunk_1 { + my ($chunk_cap, $name, $revdata, $color, $rev, @chunk) = @_; + if ($chunk_cap < @chunk) { + my @first = splice(@chunk, 0, @chunk/2); + git_blame_flush_chunk($name, + $revdata, + $color, + $rev, + @first); + } + git_blame_flush_chunk($name, + $revdata, + $color, + $rev, + @chunk); +} + sub git_blame2 { my $fd; my $ftype; + my $chunk_cap = 20; my ($have_blame) = gitweb_check_feature('blame'); if (!$have_blame) { @@ -2475,27 +2530,45 @@ sub git_blame2 { <table class="blame"> <tr><th>Commit</th><th>Line</th><th>Data</th></tr> HTML + my @chunk = (); + my %revdata = (); while (<$fd>) { /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my $full_rev = $1; - my $rev = substr($full_rev, 0, 8); - my $lineno = $2; - my $data = $3; - + my ($full_rev, $author, $date, $lineno, $data) = + /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; + if (!exists $revdata{$full_rev}) { + $revdata{$full_rev} = "$author, $date"; + } if (!defined $last_rev) { $last_rev = $full_rev; } elsif ($last_rev ne $full_rev) { + git_blame_flush_chunk_1($chunk_cap, + $file_name, + \%revdata, + $rev_color[$current_color], + $last_rev, @chunk); + @chunk = (); $last_rev = $full_rev; $current_color = ++$current_color % $num_colors; } - print "<tr class=\"$rev_color[$current_color]\">\n"; - print "<td class=\"sha1\">" . - $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, - esc_html($rev)) . "</td>\n"; - print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . - esc_html($lineno) . "</a></td>\n"; - print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; - print "</tr>\n"; + elsif ($chunk_cap * 2 < @chunk) { + # We have more than N*2 lines from the same + # revision. Flush N lines and leave N lines + # in @chunk to avoid orphaned lines. + my @first = splice(@chunk, 0, $chunk_cap); + git_blame_flush_chunk($file_name, + \%revdata, + $rev_color[$current_color], + $last_rev, @first); + } + push @chunk, [$lineno, $data]; + } + if (@chunk) { + git_blame_flush_chunk_1($chunk_cap, + $file_name, + \%revdata, + $rev_color[$current_color], + $last_rev, @chunk); } print "</table>\n"; print "</div>"; -- cgit v0.10.2-6-g49f6 From 47292d65dec754bbe37d82369faabeaf8f0cdb7a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 3 Oct 2006 02:08:19 -0700 Subject: git-fetch: do not look into $GIT_DIR/refs to see if a tag exists. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-fetch.sh b/git-fetch.sh index f1522bd..e8a7668 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -417,7 +417,7 @@ case "$no_tags$tags" in sed -ne 's|^\([0-9a-f]*\)[ ]\(refs/tags/.*\)^{}$|\1 \2|p' | while read sha1 name do - test -f "$GIT_DIR/$name" && continue + git-show-ref --verify --quiet -- $name && continue git-check-ref-format "$name" || { echo >&2 "warning: tag ${name} ignored" continue -- cgit v0.10.2-6-g49f6 From 03a182107fdb36170a72b8a3d94de2b52e3f6668 Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 3 Oct 2006 02:15:18 -0700 Subject: pack-refs: use lockfile as everybody else does. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 4093973..ede4743 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -1,7 +1,6 @@ #include "cache.h" #include "refs.h" -static const char *result_path, *lock_path; static const char builtin_pack_refs_usage[] = "git-pack-refs [--prune]"; @@ -17,12 +16,6 @@ struct pack_refs_cb_data { FILE *refs_file; }; -static void remove_lock_file(void) -{ - if (lock_path) - unlink(lock_path); -} - static int do_not_prune(int flags) { /* If it is already packed or if it is a symref, @@ -69,6 +62,8 @@ static void prune_refs(struct ref_to_prune *r) } } +static struct lock_file packed; + int cmd_pack_refs(int argc, const char **argv, const char *prefix) { int fd, i; @@ -88,14 +83,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) if (i != argc) usage(builtin_pack_refs_usage); - result_path = xstrdup(git_path("packed-refs")); - lock_path = xstrdup(mkpath("%s.lock", result_path)); - - fd = open(lock_path, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd < 0) - die("unable to create new ref-pack file (%s)", strerror(errno)); - atexit(remove_lock_file); - + fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1); cbdata.refs_file = fdopen(fd, "w"); if (!cbdata.refs_file) die("unable to create ref-pack file structure (%s)", @@ -103,9 +91,8 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) for_each_ref(handle_one_ref, &cbdata); fsync(fd); fclose(cbdata.refs_file); - if (rename(lock_path, result_path) < 0) + if (commit_lock_file(&packed) < 0) die("unable to overwrite old ref-pack file (%s)", strerror(errno)); - lock_path = NULL; if (cbdata.prune) prune_refs(cbdata.ref_to_prune); return 0; -- cgit v0.10.2-6-g49f6 From 2172ce4b01c862e66e3d581282dc92223cbd28fa Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Tue, 3 Oct 2006 02:30:47 -0700 Subject: gitweb: prepare for repositories with packed refs. When a repository is initialized long time ago with symbolic HEAD, and "git-pack-refs --prune" is run, HEAD will be a dangling symlink to refs/heads/ somewhere. Running -e "$dir/HEAD" to guess if $dir is a git repository does not give us the right answer anymore in such a case. Also factor out two places that checked if the repository can be exported with similar code into a call to a new function, check_export_ok. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 55d1b2c..671a4e6 100644 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -180,6 +180,22 @@ sub feature_pickaxe { return ($_[0]); } +# checking HEAD file with -e is fragile if the repository was +# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed +# and then pruned. +sub check_head_link { + my ($dir) = @_; + my $headfile = "$dir/HEAD"; + return ((-e $headfile) || + (-l $headfile && readlink($headfile) =~ /^refs\/heads\//)); +} + +sub check_export_ok { + my ($dir) = @_; + return (check_head_link($dir) && + (!$export_ok || -e "$dir/$export_ok")); +} + # rename detection options for git-diff and git-diff-tree # - default is '-M', with the cost proportional to # (number of removed files) * (number of new files). @@ -212,7 +228,7 @@ our $project = $cgi->param('p'); if (defined $project) { if (!validate_pathname($project) || !(-d "$projectroot/$project") || - !(-e "$projectroot/$project/HEAD") || + !check_head_link("$projectroot/$project") || ($export_ok && !(-e "$projectroot/$project/$export_ok")) || ($strict_export && !project_in_list($project))) { undef $project; @@ -289,7 +305,7 @@ sub evaluate_path_info { # find which part of PATH_INFO is project $project = $path_info; $project =~ s,/+$,,; - while ($project && !-e "$projectroot/$project/HEAD") { + while ($project && !check_head_link("$projectroot/$project")) { $project =~ s,/*[^/]*$,,; } # validate project @@ -816,8 +832,7 @@ sub git_get_projects_list { my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot - if (-e "$projectroot/$subdir/HEAD" && (!$export_ok || - -e "$projectroot/$subdir/$export_ok")) { + if (check_export_ok("$projectroot/$subdir")) { push @list, { path => $subdir }; $File::Find::prune = 1; } @@ -838,8 +853,7 @@ sub git_get_projects_list { if (!defined $path) { next; } - if (-e "$projectroot/$path/HEAD" && (!$export_ok || - -e "$projectroot/$path/$export_ok")) { + if (check_export_ok("$projectroot/$path")) { my $pr = { path => $path, owner => decode("utf8", $owner, Encode::FB_DEFAULT), -- cgit v0.10.2-6-g49f6 From 604cb211a9b5401b265d1c1ca3d57dad243e7260 Mon Sep 17 00:00:00 2001 From: Alan Chandler <alan@chandlerfamily.org.uk> Date: Tue, 3 Oct 2006 22:48:46 +0100 Subject: Update the gitweb/README file to include setting the GITWEB_CONFIG environment Signed-off-by: Alan Chandler <alan@chandlerfamily.org.uk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/README b/gitweb/README index 61c7ab5..78e6fc0 100644 --- a/gitweb/README +++ b/gitweb/README @@ -43,6 +43,7 @@ repositories, you can configure apache like this: DocumentRoot /pub/git RewriteEngine on RewriteRule ^/(.*\.git/(?!/?(info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] + SetEnv GITWEB_CONFIG /etc/gitweb.conf </VirtualHost> The above configuration expects your public repositories to live under @@ -51,6 +52,12 @@ both as cloneable GIT URL and as browseable gitweb interface. If you then start your git-daemon with --base-path=/pub/git --export-all then you can even use the git:// URL with exactly the same path. +Setting the environment variable GITWEB_CONFIG will tell gitweb to use +the named file (i.e. in this example /etc/gitweb.conf) as a +configuration for gitweb. Perl variables defined in here will +override the defaults given at the head of the gitweb.perl (or +gitweb.cgi). Look at the comments in that file for information on +which variables and what they mean. Originally written by: -- cgit v0.10.2-6-g49f6 From 281e67d6fa8e523147792c17fbe6db03f13f72e1 Mon Sep 17 00:00:00 2001 From: Alan Chandler <alan@chandlerfamily.org.uk> Date: Tue, 3 Oct 2006 21:11:25 +0100 Subject: Fix usage string to match that given in the man page Still not managed to understand git-send-mail sufficiently well to not accidently miss of this list when I sending it to Junio Signed-off-by: Alan Chandler <alan@chandlerfamily.org.uk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git.c b/git.c index ae80e78..b8e8622 100644 --- a/git.c +++ b/git.c @@ -16,7 +16,7 @@ #include "builtin.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]"; static void prepend_to_path(const char *dir, int len) { -- cgit v0.10.2-6-g49f6 From c065b6e4293f856012ec53fcff7bf4d47ac50927 Mon Sep 17 00:00:00 2001 From: Martin Waitz <tali@admingilde.org> Date: Tue, 3 Oct 2006 18:38:25 +0200 Subject: git-commit: cleanup unused function. The report() function is not used anymore. Kill it. Signed-off-by: Martin Waitz <tali@admingilde.org> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/git-commit.sh b/git-commit.sh index 5a4c659..6f6cbda 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -32,33 +32,6 @@ save_index () { cp -p "$THIS_INDEX" "$NEXT_INDEX" } -report () { - header="# -# $1: -# ($2) -# -" - trailer="" - while read status name newname - do - printf '%s' "$header" - header="" - trailer="# -" - case "$status" in - M ) echo "# modified: $name";; - D*) echo "# deleted: $name";; - T ) echo "# typechange: $name";; - C*) echo "# copied: $name -> $newname";; - R*) echo "# renamed: $name -> $newname";; - A*) echo "# new file: $name";; - U ) echo "# unmerged: $name";; - esac - done - printf '%s' "$trailer" - [ "$header" ] -} - run_status () { # If TMP_INDEX is defined, that means we are doing # "--only" partial commit, and that index file is used -- cgit v0.10.2-6-g49f6 From 9074484a2ba28cd41a78e0b10074a8882653bf3a Mon Sep 17 00:00:00 2001 From: Junio C Hamano <junkio@cox.net> Date: Wed, 4 Oct 2006 14:54:32 -0700 Subject: Revert 954a6183756a073723a7c9fd8d2feb13132876b0 Luben makes a good argument against it, and I agree with him in general. The clickable handle that appear at seemingly random places makes them look as if they are separating groups when it is not. This also restores the executable bit I lost by mistake. Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl old mode 100644 new mode 100755 index 671a4e6..32bd7b6 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2444,64 +2444,9 @@ sub git_tag { git_footer_html(); } -sub git_blame_flush_chunk { - my ($name, $revdata, $color, $rev, @line) = @_; - my $label = substr($rev, 0, 8); - my $line = scalar(@line); - my $cnt = 0; - my $pop = ''; - - if ($revdata->{$rev} ne '') { - $pop = ' title="' . esc_html($revdata->{$rev}) . '"'; - } - - for (@line) { - my ($lineno, $data) = @$_; - $cnt++; - print "<tr class=\"$color\">\n"; - if ($cnt == 1) { - print "<td class=\"sha1\"$pop"; - if ($line > 1) { - print " rowspan=\"$line\""; - } - print ">"; - print $cgi->a({-href => href(action=>"commit", - hash=>$rev, - file_name=>$name)}, - $label); - print "</td>\n"; - } - print "<td class=\"linenr\">". - "<a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . - esc_html($lineno) . "</a></td>\n"; - print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; - print "</tr>\n"; - } -} - -# We can have up to N*2 lines. If it is more than N lines, split it -# into two to avoid orphans. -sub git_blame_flush_chunk_1 { - my ($chunk_cap, $name, $revdata, $color, $rev, @chunk) = @_; - if ($chunk_cap < @chunk) { - my @first = splice(@chunk, 0, @chunk/2); - git_blame_flush_chunk($name, - $revdata, - $color, - $rev, - @first); - } - git_blame_flush_chunk($name, - $revdata, - $color, - $rev, - @chunk); -} - sub git_blame2 { my $fd; my $ftype; - my $chunk_cap = 20; my ($have_blame) = gitweb_check_feature('blame'); if (!$have_blame) { @@ -2544,45 +2489,27 @@ sub git_blame2 { <table class="blame"> <tr><th>Commit</th><th>Line</th><th>Data</th></tr> HTML - my @chunk = (); - my %revdata = (); while (<$fd>) { /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my ($full_rev, $author, $date, $lineno, $data) = - /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; - if (!exists $revdata{$full_rev}) { - $revdata{$full_rev} = "$author, $date"; - } + my $full_rev = $1; + my $rev = substr($full_rev, 0, 8); + my $lineno = $2; + my $data = $3; + if (!defined $last_rev) { $last_rev = $full_rev; } elsif ($last_rev ne $full_rev) { - git_blame_flush_chunk_1($chunk_cap, - $file_name, - \%revdata, - $rev_color[$current_color], - $last_rev, @chunk); - @chunk = (); $last_rev = $full_rev; $current_color = ++$current_color % $num_colors; } - elsif ($chunk_cap * 2 < @chunk) { - # We have more than N*2 lines from the same - # revision. Flush N lines and leave N lines - # in @chunk to avoid orphaned lines. - my @first = splice(@chunk, 0, $chunk_cap); - git_blame_flush_chunk($file_name, - \%revdata, - $rev_color[$current_color], - $last_rev, @first); - } - push @chunk, [$lineno, $data]; - } - if (@chunk) { - git_blame_flush_chunk_1($chunk_cap, - $file_name, - \%revdata, - $rev_color[$current_color], - $last_rev, @chunk); + print "<tr class=\"$rev_color[$current_color]\">\n"; + print "<td class=\"sha1\">" . + $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + esc_html($rev)) . "</td>\n"; + print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . + esc_html($lineno) . "</a></td>\n"; + print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; + print "</tr>\n"; } print "</table>\n"; print "</div>"; -- cgit v0.10.2-6-g49f6 From 9dc5f8c9c2a10f77ecfa448c93da6ceec759df73 Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Wed, 4 Oct 2006 00:12:17 -0700 Subject: gitweb: blame: print commit-8 on the leading row of a commit-block Print commit-8 only on the first, leading row of a commit block, to complement the per-commit block coloring. Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 32bd7b6..dc21cd6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2495,17 +2495,23 @@ HTML my $rev = substr($full_rev, 0, 8); my $lineno = $2; my $data = $3; + my $print_c8 = 0; if (!defined $last_rev) { $last_rev = $full_rev; + $print_c8 = 1; } elsif ($last_rev ne $full_rev) { $last_rev = $full_rev; $current_color = ++$current_color % $num_colors; + $print_c8 = 1; } print "<tr class=\"$rev_color[$current_color]\">\n"; - print "<td class=\"sha1\">" . - $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, - esc_html($rev)) . "</td>\n"; + print "<td class=\"sha1\">"; + if ($print_c8 == 1) { + print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + esc_html($rev)); + } + print "</td>\n"; print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n"; print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; -- cgit v0.10.2-6-g49f6 From c8aeaaf7fc7dd93a284218e6a2b6e96b90e3100c Mon Sep 17 00:00:00 2001 From: Luben Tuikov <ltuikov@yahoo.com> Date: Wed, 4 Oct 2006 00:13:38 -0700 Subject: gitweb: blame: Mouse-over commit-8 shows author and date Signed-off-by: Luben Tuikov <ltuikov@yahoo.com> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index dc21cd6..a99fabf 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2490,11 +2490,9 @@ sub git_blame2 { <tr><th>Commit</th><th>Line</th><th>Data</th></tr> HTML while (<$fd>) { - /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my $full_rev = $1; + my ($full_rev, $author, $date, $lineno, $data) = + /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; my $rev = substr($full_rev, 0, 8); - my $lineno = $2; - my $data = $3; my $print_c8 = 0; if (!defined $last_rev) { @@ -2506,7 +2504,11 @@ HTML $print_c8 = 1; } print "<tr class=\"$rev_color[$current_color]\">\n"; - print "<td class=\"sha1\">"; + print "<td class=\"sha1\""; + if ($print_c8 == 1) { + print " title=\"$author, $date\""; + } + print ">"; if ($print_c8 == 1) { print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, esc_html($rev)); -- cgit v0.10.2-6-g49f6 From b2d3476e15cefdbd94366d4cf46fd05c1623034f Mon Sep 17 00:00:00 2001 From: Alan Chandler <alan@chandlerfamily.org.uk> Date: Tue, 3 Oct 2006 13:49:03 +0100 Subject: Gitweb - provide site headers and footers This allows web sites with a header and footer standard for each page to add them to the pages produced by gitweb. Two new variables $site_header and $site_footer are defined (default to null) each of which can specify a file containing the header and footer html. In addition, if the $stylesheet variable is undefined, a new array @stylesheets (which defaults to a single element of gitweb.css) can be used to specify more than one style sheet. This allows the clasical gitweb.css styles to be retained, but a site wide style sheet used within the header and footer areas. Signed-off-by: Alan Chandler <alan@chandlerfamily.org.uk> Signed-off-by: Junio C Hamano <junkio@cox.net> diff --git a/Makefile b/Makefile index 401b893..09f60bb 100644 --- a/Makefile +++ b/Makefile @@ -132,6 +132,8 @@ GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png +GITWEB_SITE_HEADER = +GITWEB_SITE_FOOTER = export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR @@ -675,6 +677,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ + -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ + -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ $< >$@+ chmod +x $@+ mv $@+ $@ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a99fabf..e4ebce6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -41,11 +41,19 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++"; # replace this with something more descriptive for clearer bookmarks our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; +# filename of html text to include at top of each page +our $site_header = "++GITWEB_SITE_HEADER++"; # html text to include at home page our $home_text = "++GITWEB_HOMETEXT++"; +# filename of html text to include at bottom of each page +our $site_footer = "++GITWEB_SITE_FOOTER++"; + +# URI of stylesheets +our @stylesheets = ("++GITWEB_CSS++"); +our $stylesheet; +# default is not to define style sheet, but it can be overwritten later +undef $stylesheet; -# URI of default stylesheet -our $stylesheet = "++GITWEB_CSS++"; # URI of GIT logo our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type @@ -1366,8 +1374,17 @@ sub git_header_html { <meta name="generator" content="gitweb/$version git/$git_version"/> <meta name="robots" content="index, nofollow"/> <title>$title - EOF +# print out each stylesheet that exist + if (defined $stylesheet) { +#provides backwards capability for those people who define style sheet in a config file + print ''."\n"; + } else { + foreach my $stylesheet (@stylesheets) { + next unless $stylesheet; + print ''."\n"; + } + } if (defined $project) { printf(''."\n", @@ -1385,8 +1402,15 @@ EOF } print "\n" . - "\n" . - "
\n" . + "\n"; + + if (-f $site_header) { + open (my $fd, $site_header); + print <$fd>; + close $fd; + } + + print "
\n" . "" . "\"git\"" . "\n"; @@ -1437,8 +1461,15 @@ sub git_footer_html { print $cgi->a({-href => href(project=>undef, action=>"project_index"), -class => "rss_logo"}, "TXT") . "\n"; } - print "
\n" . - "\n" . + print "
\n" ; + + if (-f $site_footer) { + open (my $fd, $site_footer); + print <$fd>; + close $fd; + } + + print "\n" . ""; } -- cgit v0.10.2-6-g49f6 From 91b489776c25866568c081e8cf4ae83fa41f8707 Mon Sep 17 00:00:00 2001 From: Robert Shearman Date: Tue, 3 Oct 2006 17:29:26 +0100 Subject: git-rebase: Use --ignore-if-in-upstream option when executing git-format-patch. This reduces the number of conflicts when rebasing after a series of patches to the same piece of code is committed upstream. Signed-off-by: Robert Shearman Signed-off-by: Junio C Hamano diff --git a/git-rebase.sh b/git-rebase.sh index a7373c0..413636e 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -286,7 +286,7 @@ fi if test -z "$do_merge" then - git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD | + git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD | git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \ --reflog-action=rebase exit $? -- cgit v0.10.2-6-g49f6 From b758789c202dc491555b698149afe4d4941f7cd5 Mon Sep 17 00:00:00 2001 From: Robert Shearman Date: Tue, 3 Oct 2006 17:29:31 +0100 Subject: git-rebase: Add a -v option to show a diffstat of the changes upstream at the start of a rebase. Signed-off-by: Robert Shearman Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 9d7bcaa..10f2924 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -7,7 +7,7 @@ git-rebase - Rebase local commits to a new head SYNOPSIS -------- -'git-rebase' [--merge] [--onto ] [] +'git-rebase' [-v] [--merge] [--onto ] [] 'git-rebase' --continue | --skip | --abort @@ -121,6 +121,9 @@ OPTIONS is used instead (`git-merge-recursive` when merging a single head, `git-merge-octopus` otherwise). This implies --merge. +-v, \--verbose:: + Display a diffstat of what changed upstream since the last rebase. + include::merge-strategies.txt[] NOTES diff --git a/git-rebase.sh b/git-rebase.sh index 413636e..546fa44 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--onto ] []' +USAGE='[-v] [--onto ] []' LONG_USAGE='git-rebase replaces with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to , otherwise it is equal to @@ -39,6 +39,7 @@ strategy=recursive do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 +verbose= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -190,6 +191,9 @@ do esac do_merge=t ;; + -v|--verbose) + verbose=t + ;; -*) usage ;; @@ -273,6 +277,12 @@ then exit 0 fi +if test -n "$verbose" +then + echo "Changes from $mb to $onto:" + git-diff-tree --stat --summary "$mb" "$onto" +fi + # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD. git-reset --hard "$onto" -- cgit v0.10.2-6-g49f6 From 422b4a0e03658d0933a7abc149f175735ea9c4b5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 4 Oct 2006 21:37:15 -0700 Subject: pack-refs: call fflush before fsync. Signed-off-by: Junio C Hamano diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index ede4743..23d0d07 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -89,6 +89,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) die("unable to create ref-pack file structure (%s)", strerror(errno)); for_each_ref(handle_one_ref, &cbdata); + fflush(cbdata.refs_file); fsync(fd); fclose(cbdata.refs_file); if (commit_lock_file(&packed) < 0) -- cgit v0.10.2-6-g49f6 From a9455919064ab316b7852d0006c1bd771b727f2e Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Tue, 3 Oct 2006 20:07:43 +0200 Subject: gitweb: warn if feature cannot be overridden. If the administrator configures pathinfo to be overrideable by the local repository a warning is shown. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 10e803a..0ff6f7c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -116,6 +116,10 @@ sub gitweb_check_feature { $feature{$name}{'override'}, @{$feature{$name}{'default'}}); if (!$override) { return @defaults; } + if (!defined $sub) { + warn "feature $name is not overrideable"; + return @defaults; + } return $sub->(@defaults); } -- cgit v0.10.2-6-g49f6 From 6030649591256a8bc1b504ec1e4fce86ab0ad064 Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Thu, 5 Oct 2006 10:42:16 +0200 Subject: Add git-upload-archive to the main git man page Signed-off-by: Franck Bui-Huu Signed-off-by: Junio C Hamano diff --git a/Documentation/git.txt b/Documentation/git.txt index 2135b65..3af6fc6 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -243,6 +243,9 @@ gitlink:git-update-server-info[1]:: Updates auxiliary information on a dumb server to help clients discover references and packs on it. +gitlink:git-upload-archive[1]:: + Invoked by 'git-archive' to send a generated archive. + gitlink:git-upload-pack[1]:: Invoked by 'git-fetch-pack' to push what are asked for. -- cgit v0.10.2-6-g49f6 From 9ccb64c8e0791a865ad520bcfff4b02cc7c50097 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 02:26:12 -0700 Subject: tar-tree deprecation: we eat our own dog food. It is silly to keep using git-tar-tree in dist target when the command gives a big deprecation warning when called. Instead, use "git-archive --format=tar" which we recommend to our users. Update gitweb's snapshot feature to use git-archive for the same reason. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 401b893..2c7c338 100644 --- a/Makefile +++ b/Makefile @@ -856,8 +856,9 @@ git.spec: git.spec.in mv $@+ $@ GIT_TARNAME=git-$(GIT_VERSION) -dist: git.spec git-tar-tree - ./git-tar-tree HEAD^{tree} $(GIT_TARNAME) > $(GIT_TARNAME).tar +dist: git.spec git-archive + ./git-archive --format=tar \ + --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar @mkdir -p $(GIT_TARNAME) @cp git.spec $(GIT_TARNAME) @echo $(GIT_VERSION) > $(GIT_TARNAME)/version diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3e9d4a0..6e1496d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2838,9 +2838,12 @@ sub git_snapshot { -content_disposition => 'inline; filename="' . "$filename" . '"', -status => '200 OK'); - my $git_command = git_cmd_str(); - open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or - die_error(undef, "Execute git-tar-tree failed."); + my $git = git_cmd_str(); + my $name = $project; + $name =~ s/\047/\047\\\047\047/g; + open my $fd, "-|", + "$git archive --format=tar --prefix=\'$name\'/ $hash | $command" + or die_error(undef, "Execute git-tar-tree failed."); binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi -- cgit v0.10.2-6-g49f6 From c530c5aa31f44adafd1f4ecb05223024162e689c Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Thu, 5 Oct 2006 11:29:57 +0200 Subject: git.el: Fixed inverted "renamed from/to" message. The deleted file should be labeled "renamed to" and the added file "renamed from", not the other way around (duh!) Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 68de9be..5354cd6 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -422,8 +422,8 @@ and returns the process output as a string." (propertize (concat " (" (if (eq state 'copy) "copied from " - (if (eq (git-fileinfo->state info) 'added) "renamed to " - "renamed from ")) + (if (eq (git-fileinfo->state info) 'added) "renamed from " + "renamed to ")) (git-escape-file-name (git-fileinfo->orig-name info)) ")") 'face 'git-status-face) ""))) -- cgit v0.10.2-6-g49f6 From 13f8e0b24b546c0da942ab8ebe334d6e55fe1ea6 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Thu, 5 Oct 2006 11:30:44 +0200 Subject: vc-git.el: Switch to using git-blame instead of git-annotate. Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 4a8f790..4189c4c 100644 --- a/contrib/emacs/vc-git.el +++ b/contrib/emacs/vc-git.el @@ -119,10 +119,10 @@ (defun vc-git-annotate-command (file buf &optional rev) ; FIXME: rev is ignored (let ((name (file-relative-name file))) - (call-process "git" nil buf nil "annotate" name))) + (call-process "git" nil buf nil "blame" name))) (defun vc-git-annotate-time () - (and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t) + (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t) (vc-annotate-convert-time (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7)))))) -- cgit v0.10.2-6-g49f6 From 6e0e92fda893311ff5af91836e5007bf6bbd4a21 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Thu, 5 Oct 2006 12:22:57 -0700 Subject: gitweb: Do not print "log" and "shortlog" redundantly in commit view Do not print "log" and "shortlog" redundantly in commit view. This is passed into the $extra argument of git_print_page_nav from git_commit, but git_print_page_nav prints "log" and "shortlog" already with the same head. Noticed by Junio. Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6e1496d..8bf7bf4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2940,11 +2940,6 @@ sub git_commit { $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); } - if (defined $co{'parent'}) { - push @views_nav, - $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"), - $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log"); - } git_header_html(undef, $expires); git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', $hash, $co{'tree'}, $hash, -- cgit v0.10.2-6-g49f6 From db94b41aee0dfeae06e9c6f03d38a2aa30f8fd10 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Thu, 5 Oct 2006 13:30:31 -0700 Subject: gitweb: blame: Minimize vertical table row padding Minimize vertical table row padding for blame only. I discovered this while having the browser's blame output right next to my editor's window, only to notice how much vertically stretched the blame output was. Blame most likely shows source code and is in this way more "spartan" than the rest of the tables gitweb shows. This patch makes the blame table more vertically compact, thus being closer to what you'd see in your editor's window, as well as reusing more window estate to show more information (which in turn minimizes scrolling). Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index eb9fc38..668e69a 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -173,6 +173,12 @@ table.blame { border-collapse: collapse; } +table.blame td { + padding: 0px 5px; + font-size: 12px; + vertical-align: top; +} + th { padding: 2px 5px; font-size: 12px; -- cgit v0.10.2-6-g49f6 From 51a7c66a73ce74fad3b7b05109ed6ce013202fa5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Sep 2006 12:36:01 -0700 Subject: gitweb: Make the Git logo link target to point to the homepage It provides more useful information for causual Git users than the Git docs (especially about where to get Git and such). People can override with GITWEB_CONFIG if they want to. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano Acked-by: Petr Baudis diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8bf7bf4..3320069 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -51,6 +51,9 @@ our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type our $favicon = "++GITWEB_FAVICON++"; +our $githelp_url = "http://git.or.cz/"; +our $githelp_label = "git homepage"; + # source of projects list our $projects_list = "++GITWEB_LIST++"; @@ -1373,7 +1376,9 @@ EOF print "\n" . "\n" . "
\n" . - "" . + "" . "\"git\"" . "\n"; print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; -- cgit v0.10.2-6-g49f6 From 506e49ff9ff2b5be34b2bdf15c88038b00d3ef66 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:00:55 -0700 Subject: blame.c: whitespace and formatting clean-up. Signed-off-by: Junio C Hamano diff --git a/blame.c b/blame.c index 8cfd5d9..394b5c3 100644 --- a/blame.c +++ b/blame.c @@ -20,16 +20,17 @@ #define DEBUG 0 -static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S ] [--] file [commit]\n" - " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" - " -l, --long Show long commit SHA1 (Default: off)\n" - " -t, --time Show raw timestamp (Default: off)\n" - " -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n" - " -h, --help This message"; +static const char blame_usage[] = +"git-blame [-c] [-l] [-t] [-S ] [--] file [commit]\n" +" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" +" -l, --long Show long commit SHA1 (Default: off)\n" +" -t, --time Show raw timestamp (Default: off)\n" +" -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n" +" -h, --help This message"; static struct commit **blame_lines; static int num_blame_lines; -static char* blame_contents; +static char *blame_contents; static int blame_len; struct util_info { @@ -38,9 +39,9 @@ struct util_info { char *buf; unsigned long size; int num_lines; - const char* pathname; + const char *pathname; - void* topo_data; + void *topo_data; }; struct chunk { @@ -156,11 +157,10 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, unsigned mode, int stage); static unsigned char blob_sha1[20]; -static const char* blame_file; +static const char *blame_file; static int get_blob_sha1(struct tree *t, const char *pathname, unsigned char *sha1) { - int i; const char *pathspec[2]; blame_file = pathname; pathspec[0] = pathname; @@ -168,12 +168,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname, hashclr(blob_sha1); read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); - for (i = 0; i < 20; i++) { - if (blob_sha1[i] != 0) - break; - } - - if (i == 20) + if (is_null_sha1(blob_sha1)) return -1; hashcpy(sha1, blob_sha1); @@ -239,7 +234,8 @@ static void print_map(struct commit *cmit, struct commit *other) if (i < util->num_lines) { num = util->line_map[i]; printf("%d\t", num); - } else + } + else printf("\t"); if (i < util2->num_lines) { @@ -247,7 +243,8 @@ static void print_map(struct commit *cmit, struct commit *other) printf("%d\t", num2); if (num != -1 && num2 != num) printf("---"); - } else + } + else printf("\t"); printf("\n"); @@ -266,12 +263,12 @@ static void fill_line_map(struct commit *commit, struct commit *other, int cur_chunk = 0; int i1, i2; - if (p->num && DEBUG) - print_patch(p); - - if (DEBUG) + if (DEBUG) { + if (p->num) + print_patch(p); printf("num lines 1: %d num lines 2: %d\n", util->num_lines, util2->num_lines); + } for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) { struct chunk *chunk = NULL; @@ -293,7 +290,8 @@ static void fill_line_map(struct commit *commit, struct commit *other, i2 += chunk->len2; cur_chunk++; - } else { + } + else { if (i2 >= util2->num_lines) break; @@ -327,7 +325,7 @@ static int map_line(struct commit *commit, int line) return info->line_map[line]; } -static struct util_info* get_util(struct commit *commit) +static struct util_info *get_util(struct commit *commit) { struct util_info *util = commit->util; @@ -369,7 +367,7 @@ static void alloc_line_map(struct commit *commit) if (util->buf[i] == '\n') util->num_lines++; } - if(util->buf[util->size - 1] != '\n') + if (util->buf[util->size - 1] != '\n') util->num_lines++; util->line_map = xmalloc(sizeof(int) * util->num_lines); @@ -378,9 +376,9 @@ static void alloc_line_map(struct commit *commit) util->line_map[i] = -1; } -static void init_first_commit(struct commit* commit, const char* filename) +static void init_first_commit(struct commit *commit, const char *filename) { - struct util_info* util = commit->util; + struct util_info *util = commit->util; int i; util->pathname = filename; @@ -395,18 +393,17 @@ static void init_first_commit(struct commit* commit, const char* filename) util->line_map[i] = i; } - static void process_commits(struct rev_info *rev, const char *path, - struct commit** initial) + struct commit **initial) { int i; - struct util_info* util; + struct util_info *util; int lines_left; int *blame_p; int *new_lines; int new_lines_len; - struct commit* commit = get_revision(rev); + struct commit *commit = get_revision(rev); assert(commit); init_first_commit(commit, path); @@ -442,7 +439,7 @@ static void process_commits(struct rev_info *rev, const char *path, parents != NULL; parents = parents->next) num_parents++; - if(num_parents == 0) + if (num_parents == 0) *initial = commit; if (fill_util_info(commit)) @@ -503,13 +500,12 @@ static void process_commits(struct rev_info *rev, const char *path, } while ((commit = get_revision(rev)) != NULL); } - -static int compare_tree_path(struct rev_info* revs, - struct commit* c1, struct commit* c2) +static int compare_tree_path(struct rev_info *revs, + struct commit *c1, struct commit *c2) { int ret; - const char* paths[2]; - struct util_info* util = c2->util; + const char *paths[2]; + struct util_info *util = c2->util; paths[0] = util->pathname; paths[1] = NULL; @@ -520,12 +516,11 @@ static int compare_tree_path(struct rev_info* revs, return ret; } - -static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, - const char* path) +static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1, + const char *path) { int ret; - const char* paths[2]; + const char *paths[2]; paths[0] = path; paths[1] = NULL; @@ -536,9 +531,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, return ret; } -static const char* find_rename(struct commit* commit, struct commit* parent) +static const char *find_rename(struct commit *commit, struct commit *parent) { - struct util_info* cutil = commit->util; + struct util_info *cutil = commit->util; struct diff_options diff_opts; const char *paths[1]; int i; @@ -564,9 +559,11 @@ static const char* find_rename(struct commit* commit, struct commit* parent) for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; - if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) { + if (p->status == 'R' && + !strcmp(p->one->path, cutil->pathname)) { if (DEBUG) - printf("rename %s -> %s\n", p->one->path, p->two->path); + printf("rename %s -> %s\n", + p->one->path, p->two->path); return p->two->path; } } @@ -582,7 +579,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) return; if (!commit->parents) { - struct util_info* util = commit->util; + struct util_info *util = commit->util; if (!same_tree_as_empty_path(revs, commit->tree, util->pathname)) commit->object.flags |= TREECHANGE; @@ -608,17 +605,17 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) case REV_TREE_NEW: { - - struct util_info* util = commit->util; + struct util_info *util = commit->util; if (revs->remove_empty_trees && same_tree_as_empty_path(revs, p->tree, util->pathname)) { - const char* new_name = find_rename(commit, p); + const char *new_name = find_rename(commit, p); if (new_name) { - struct util_info* putil = get_util(p); + struct util_info *putil = get_util(p); if (!putil->pathname) putil->pathname = xstrdup(new_name); - } else { + } + else { *pp = parent->next; continue; } @@ -639,19 +636,18 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) commit->object.flags |= TREECHANGE; } - struct commit_info { - char* author; - char* author_mail; + char *author; + char *author_mail; unsigned long author_time; - char* author_tz; + char *author_tz; }; -static void get_commit_info(struct commit* commit, struct commit_info* ret) +static void get_commit_info(struct commit *commit, struct commit_info *ret) { int len; - char* tmp; + char *tmp; static char author_buf[1024]; tmp = strstr(commit->buffer, "\nauthor ") + 8; @@ -662,24 +658,24 @@ static void get_commit_info(struct commit* commit, struct commit_info* ret) tmp = ret->author; tmp += len; *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; ret->author_tz = tmp+1; *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; ret->author_time = strtoul(tmp, NULL, 10); *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; ret->author_mail = tmp + 1; *tmp = 0; } -static const char* format_time(unsigned long time, const char* tz_str, +static const char *format_time(unsigned long time, const char *tz_str, int show_raw_time) { static char time_buf[128]; @@ -704,15 +700,15 @@ static const char* format_time(unsigned long time, const char* tz_str, return time_buf; } -static void topo_setter(struct commit* c, void* data) +static void topo_setter(struct commit *c, void *data) { - struct util_info* util = c->util; + struct util_info *util = c->util; util->topo_data = data; } -static void* topo_getter(struct commit* c) +static void *topo_getter(struct commit *c) { - struct util_info* util = c->util; + struct util_info *util = c->util; return util->topo_data; } @@ -747,9 +743,9 @@ int main(int argc, const char **argv) int compatibility = 0; int show_raw_time = 0; int options = 1; - struct commit* start_commit; + struct commit *start_commit; - const char* args[10]; + const char *args[10]; struct rev_info rev; struct commit_info ci; @@ -758,27 +754,30 @@ int main(int argc, const char **argv) int longest_file, longest_author; int found_rename; - const char* prefix = setup_git_directory(); + const char *prefix = setup_git_directory(); git_config(git_default_config); - for(i = 1; i < argc; i++) { - if(options) { - if(!strcmp(argv[i], "-h") || + for (i = 1; i < argc; i++) { + if (options) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) usage(blame_usage); - else if(!strcmp(argv[i], "-l") || - !strcmp(argv[i], "--long")) { + if (!strcmp(argv[i], "-l") || + !strcmp(argv[i], "--long")) { sha1_len = 40; continue; - } else if(!strcmp(argv[i], "-c") || - !strcmp(argv[i], "--compatibility")) { + } + if (!strcmp(argv[i], "-c") || + !strcmp(argv[i], "--compatibility")) { compatibility = 1; continue; - } else if(!strcmp(argv[i], "-t") || - !strcmp(argv[i], "--time")) { + } + if (!strcmp(argv[i], "-t") || + !strcmp(argv[i], "--time")) { show_raw_time = 1; continue; - } else if(!strcmp(argv[i], "-S")) { + } + if (!strcmp(argv[i], "-S")) { if (i + 1 < argc && !read_ancestry(argv[i + 1], &sha1_p)) { compatibility = 1; @@ -786,33 +785,34 @@ int main(int argc, const char **argv) continue; } usage(blame_usage); - } else if(!strcmp(argv[i], "--")) { + } + if (!strcmp(argv[i], "--")) { options = 0; continue; - } else if(argv[i][0] == '-') + } + if (argv[i][0] == '-') usage(blame_usage); - else - options = 0; + options = 0; } - if(!options) { - if(!filename) + if (!options) { + if (!filename) filename = argv[i]; - else if(!commit) + else if (!commit) commit = argv[i]; else usage(blame_usage); } } - if(!filename) + if (!filename) usage(blame_usage); if (commit && sha1_p) usage(blame_usage); - else if(!commit) + else if (!commit) commit = "HEAD"; - if(prefix) + if (prefix) sprintf(filename_buf, "%s%s", prefix, filename); else strcpy(filename_buf, filename); @@ -830,7 +830,6 @@ int main(int argc, const char **argv) return 1; } - init_revisions(&rev, setup_git_directory()); rev.remove_empty_trees = 1; rev.topo_order = 1; @@ -857,7 +856,7 @@ int main(int argc, const char **argv) found_rename = 0; for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; - struct util_info* u; + struct util_info *u; if (!c) c = initial; u = c->util; @@ -873,20 +872,20 @@ int main(int argc, const char **argv) for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; - struct util_info* u; - + struct util_info *u; if (!c) c = initial; - u = c->util; + get_commit_info(c, &ci); fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); - if(compatibility) { + if (compatibility) { printf("\t(%10s\t%10s\t%d)", ci.author, format_time(ci.author_time, ci.author_tz, show_raw_time), i+1); - } else { + } + else { if (found_rename) printf(" %-*.*s", longest_file, longest_file, u->pathname); @@ -897,13 +896,14 @@ int main(int argc, const char **argv) max_digits, i+1); } - if(i == num_blame_lines - 1) { + if (i == num_blame_lines - 1) { fwrite(buf, blame_len - (buf - blame_contents), 1, stdout); - if(blame_contents[blame_len-1] != '\n') + if (blame_contents[blame_len-1] != '\n') putc('\n', stdout); - } else { - char* next_buf = strchr(buf, '\n') + 1; + } + else { + char *next_buf = strchr(buf, '\n') + 1; fwrite(buf, next_buf - buf, 1, stdout); buf = next_buf; } -- cgit v0.10.2-6-g49f6 From eb93b7240665e35ecc0ed72098e1a5b3352bdc17 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:06:22 -0700 Subject: git-blame: --show-name (and -f) The new option makes the command's native output format show the filename even when there were no renames in its history, to make it simpler for Porcelains to parse its output. Signed-off-by: Junio C Hamano diff --git a/blame.c b/blame.c index 394b5c3..d830b29 100644 --- a/blame.c +++ b/blame.c @@ -752,7 +752,7 @@ int main(int argc, const char **argv) const char *buf; int max_digits; int longest_file, longest_author; - int found_rename; + int show_name = 0; const char *prefix = setup_git_directory(); git_config(git_default_config); @@ -786,6 +786,11 @@ int main(int argc, const char **argv) } usage(blame_usage); } + if (!strcmp(argv[i], "-f") || + !strcmp(argv[i], "--show-name")) { + show_name = 1; + continue; + } if (!strcmp(argv[i], "--")) { options = 0; continue; @@ -853,7 +858,6 @@ int main(int argc, const char **argv) longest_file = 0; longest_author = 0; - found_rename = 0; for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; struct util_info *u; @@ -861,8 +865,8 @@ int main(int argc, const char **argv) c = initial; u = c->util; - if (!found_rename && strcmp(filename, u->pathname)) - found_rename = 1; + if (!show_name && strcmp(filename, u->pathname)) + show_name = 1; if (longest_file < strlen(u->pathname)) longest_file = strlen(u->pathname); get_commit_info(c, &ci); @@ -886,7 +890,7 @@ int main(int argc, const char **argv) i+1); } else { - if (found_rename) + if (show_name) printf(" %-*.*s", longest_file, longest_file, u->pathname); printf(" (%-*.*s %10s %*d) ", -- cgit v0.10.2-6-g49f6 From cf54a029ff82ce9b565e075bfa5d97ec82841283 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:06:26 -0700 Subject: git-blame: --show-number (and -n) The new option makes the command's native output format show the original line number in the blamed revision. Note: the current implementation of find_orig_linenum involves linear search through the line_map array every time. It should probably build a reverse map upfront and do a simple look-up to speed things up, but I'll leave it to more clever and beautiful people ;-). Signed-off-by: Junio C Hamano diff --git a/blame.c b/blame.c index d830b29..bf4a1a1 100644 --- a/blame.c +++ b/blame.c @@ -731,6 +731,25 @@ static int read_ancestry(const char *graft_file, return 0; } +static int lineno_width(int lines) +{ + int i, width; + + for (width = 1, i = 10; i <= lines + 1; width++) + i *= 10; + return width; +} + +static int find_orig_linenum(struct util_info *u, int lineno) +{ + int i; + + for (i = 0; i < u->num_lines; i++) + if (lineno == u->line_map[i]) + return i + 1; + return 0; +} + int main(int argc, const char **argv) { int i; @@ -750,9 +769,10 @@ int main(int argc, const char **argv) struct commit_info ci; const char *buf; - int max_digits; - int longest_file, longest_author; + int max_digits, max_orig_digits; + int longest_file, longest_author, longest_file_lines; int show_name = 0; + int show_number = 0; const char *prefix = setup_git_directory(); git_config(git_default_config); @@ -791,6 +811,11 @@ int main(int argc, const char **argv) show_name = 1; continue; } + if (!strcmp(argv[i], "-n") || + !strcmp(argv[i], "--show-number")) { + show_number = 1; + continue; + } if (!strcmp(argv[i], "--")) { options = 0; continue; @@ -853,11 +878,11 @@ int main(int argc, const char **argv) process_commits(&rev, filename, &initial); buf = blame_contents; - for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++) - i *= 10; + max_digits = lineno_width(num_blame_lines); longest_file = 0; longest_author = 0; + longest_file_lines = 0; for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; struct util_info *u; @@ -869,17 +894,23 @@ int main(int argc, const char **argv) show_name = 1; if (longest_file < strlen(u->pathname)) longest_file = strlen(u->pathname); + if (longest_file_lines < u->num_lines) + longest_file_lines = u->num_lines; get_commit_info(c, &ci); if (longest_author < strlen(ci.author)) longest_author = strlen(ci.author); } + max_orig_digits = lineno_width(longest_file_lines); + for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; struct util_info *u; + int lineno; if (!c) c = initial; u = c->util; + lineno = find_orig_linenum(u, i); get_commit_info(c, &ci); fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); @@ -893,6 +924,9 @@ int main(int argc, const char **argv) if (show_name) printf(" %-*.*s", longest_file, longest_file, u->pathname); + if (show_number) + printf(" %*d", max_orig_digits, + lineno); printf(" (%-*.*s %10s %*d) ", longest_author, longest_author, ci.author, format_time(ci.author_time, ci.author_tz, -- cgit v0.10.2-6-g49f6 From c137f40f8a9dfe05ee002cf5f23bf562c1f13100 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:06:42 -0700 Subject: blame.c: move code to output metainfo into a separate function. This does not change any behaviour, but just separates out the code to emit the initial part of the output of each line into a separate function, since I'll be mucking with it further. Signed-off-by: Junio C Hamano diff --git a/blame.c b/blame.c index bf4a1a1..47471e8 100644 --- a/blame.c +++ b/blame.c @@ -750,6 +750,42 @@ static int find_orig_linenum(struct util_info *u, int lineno) return 0; } +static void emit_meta(struct commit *c, int lno, + int sha1_len, int compatibility, + int show_name, int show_number, int show_raw_time, + int longest_file, int longest_author, + int max_digits, int max_orig_digits) +{ + struct util_info *u; + int lineno; + struct commit_info ci; + + u = c->util; + lineno = find_orig_linenum(u, lno); + + get_commit_info(c, &ci); + fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); + if (compatibility) { + printf("\t(%10s\t%10s\t%d)", ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + lno + 1); + } + else { + if (show_name) + printf(" %-*.*s", longest_file, longest_file, + u->pathname); + if (show_number) + printf(" %*d", max_orig_digits, + lineno); + printf(" (%-*.*s %10s %*d) ", + longest_author, longest_author, ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + max_digits, lno + 1); + } +} + int main(int argc, const char **argv) { int i; @@ -877,6 +913,10 @@ int main(int argc, const char **argv) prepare_revision_walk(&rev); process_commits(&rev, filename, &initial); + for (i = 0; i < num_blame_lines; i++) + if (!blame_lines[i]) + blame_lines[i] = initial; + buf = blame_contents; max_digits = lineno_width(num_blame_lines); @@ -886,8 +926,6 @@ int main(int argc, const char **argv) for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; struct util_info *u; - if (!c) - c = initial; u = c->util; if (!show_name && strcmp(filename, u->pathname)) @@ -904,35 +942,11 @@ int main(int argc, const char **argv) max_orig_digits = lineno_width(longest_file_lines); for (i = 0; i < num_blame_lines; i++) { - struct commit *c = blame_lines[i]; - struct util_info *u; - int lineno; - if (!c) - c = initial; - u = c->util; - lineno = find_orig_linenum(u, i); - - get_commit_info(c, &ci); - fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); - if (compatibility) { - printf("\t(%10s\t%10s\t%d)", ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - i+1); - } - else { - if (show_name) - printf(" %-*.*s", longest_file, longest_file, - u->pathname); - if (show_number) - printf(" %*d", max_orig_digits, - lineno); - printf(" (%-*.*s %10s %*d) ", - longest_author, longest_author, ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - max_digits, i+1); - } + emit_meta(blame_lines[i], i, + sha1_len, compatibility, + show_name, show_number, show_raw_time, + longest_file, longest_author, + max_digits, max_orig_digits); if (i == num_blame_lines - 1) { fwrite(buf, blame_len - (buf - blame_contents), -- cgit v0.10.2-6-g49f6 From d7014dc0811eb24f418830a30dacc203c1a580f4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 3 Oct 2006 23:09:56 +0200 Subject: Turn on recursive with --summary This makes "git log/diff --summary" imply recursive behaviour, whose effect is summarized in one test output: --- a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial +++ b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial @@ -5,7 +5,7 @@ Date: Mon Jun 26 00:00:00 2006 +0000 Initial - create mode 040000 dir + create mode 100644 dir/sub create mode 100644 file0 create mode 100644 file2 $ When a file is created in a subdirectory, we used to say just the directory name only when that directory also was created, which did not make sense from two reasons. It is not any more significant to create a new file in a new directory than to create a new file in an existing directory, and even if it were, reportinging the new directory name without saying the actual filename is not useful. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index fb82432..9dbbed7 100644 --- a/diff.c +++ b/diff.c @@ -1741,6 +1741,7 @@ int diff_setup_done(struct diff_options *options) */ if (options->output_format & (DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT | + DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) options->recursive = 1; /* diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial index ea48205..58e5f74 100644 --- a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial +++ b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial @@ -5,7 +5,7 @@ Date: Mon Jun 26 00:00:00 2006 +0000 Initial - create mode 040000 dir + create mode 100644 dir/sub create mode 100644 file0 create mode 100644 file2 $ -- cgit v0.10.2-6-g49f6 From bc108f63dad7a5f6d95418cb78a587f5f570eae6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 16:36:15 -0700 Subject: git-send-email: avoid uninitialized variable warning. The code took length of $reply_to when it was not even defined, causing -w to warn. Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index 4a20310..3f50aba 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -538,7 +538,7 @@ foreach my $t (@files) { send_message(); # set up for the next message - if ($chain_reply_to || length($reply_to) == 0) { + if ($chain_reply_to || !defined $reply_to || length($reply_to) == 0) { $reply_to = $message_id; if (length $references > 0) { $references .= " $message_id"; -- cgit v0.10.2-6-g49f6 From abd6970acad5d758f48c13f7420367ae8216038e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 17:54:14 -0700 Subject: cherry-pick: make -r the default And introduce -x to expose (possibly) private commit object name for people who cherry-pick between public branches. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index bfa950c..875edb6 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit SYNOPSIS -------- -'git-cherry-pick' [--edit] [-n] [-r] +'git-cherry-pick' [--edit] [-n] [-x] DESCRIPTION ----------- @@ -24,13 +24,22 @@ OPTIONS With this option, `git-cherry-pick` will let you edit the commit message prior committing. --r|--replay:: - Usually the command appends which commit was +-x:: + Cause the command to append which commit was cherry-picked after the original commit message when - making a commit. This option, '--replay', causes it to - use the original commit message intact. This is useful - when you are reordering the patches in your private tree - before publishing. + making a commit. Do not use this option if you are + cherry-picking from your private branch because the + information is useless to the recipient. If on the + other hand you are cherry-picking between two publicly + visible branches (e.g. backporting a fix to a + maintenance branch for an older release from a + development branch), adding this information can be + useful. + +-r|--replay:: + It used to be that the command defaulted to do `-x` + described above, and `-r` was to disable it. Now the + default is not to do `-x` so this option is a no-op. -n|--no-commit:: Usually the command automatically creates a commit with diff --git a/git-revert.sh b/git-revert.sh index 2bf35d1..0784f74 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -12,13 +12,13 @@ case "$0" in *-cherry-pick* ) edit= me=cherry-pick - USAGE='[--edit] [-n] [-r] ' ;; + USAGE='[--edit] [-n] [-r] [-x] ' ;; * ) die "What are you talking about?" ;; esac . git-sh-setup -no_commit= replay= +no_commit= replay=t while case "$#" in 0) break ;; esac do case "$1" in @@ -32,8 +32,10 @@ do --n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit) edit= ;; - -r|--r|--re|--rep|--repl|--repla|--replay) - replay=t + -r) + : no-op ;; + -x|--i-really-want-to-expose-my-private-commit-object-name) + replay= ;; -*) usage @@ -121,7 +123,7 @@ cherry-pick) git-cat-file commit $commit | sed -e '1,/^$/d' case "$replay" in '') - echo "(cherry picked from $commit commit)" + echo "(cherry picked from commit $commit)" test "$rev" = "$commit" || echo "(original 'git cherry-pick' arguments: $@)" ;; -- cgit v0.10.2-6-g49f6 From ce91c2f6538fc4a905882f2a7a57d814d82b13e8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 16:36:49 -0700 Subject: git-send-email: do not drop custom headers the user prepared The command picked up only Subject, CC, and From headers in the incoming mbox text. Sending out patches prepared by git-format-patch with user's custom headers was impossible with that. Just keep the ones it does not need to look at and add them to the header of the message when sending it out. Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index 3f50aba..2fd5e87 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -83,7 +83,7 @@ sub cleanup_compose_files(); my $compose_filename = ".msg.$$"; # Variables we fill in automatically, or via prompting: -my (@to,@cc,@initial_cc,@bcclist, +my (@to,@cc,@initial_cc,@bcclist,@xh, $initial_reply_to,$initial_subject,@files,$from,$compose,$time); # Behavior modification variables @@ -422,6 +422,9 @@ X-Mailer: git-send-email $gitversion $header .= "In-Reply-To: $reply_to\n"; $header .= "References: $references\n"; } + if (@xh) { + $header .= join("\n", @xh) . "\n"; + } if ($smtp_server =~ m#^/#) { my $pid = open my $sm, '|-'; @@ -472,6 +475,7 @@ foreach my $t (@files) { my $author_not_sender = undef; @cc = @initial_cc; + @xh = (); my $found_mbox = 0; my $header_done = 0; $message = ""; @@ -495,6 +499,9 @@ foreach my $t (@files) { $2, $_) unless $quiet; push @cc, $2; } + elsif (/^[-A-Za-z]+:\s+\S/) { + push @xh, $_; + } } else { # In the traditional -- cgit v0.10.2-6-g49f6 From ab2a1a32ffa5a39aaf4204bd717562bce49e0a36 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 23:16:15 -0700 Subject: ref-log: allow ref@{count} syntax. Often I find myself wanting to say 'tip of "next" before I merged the last three topics'. Now I can say that with: git log next@{3}..next Since small integers alone are invalid input strings to approxidate, there is no fear of confusion. Signed-off-by: Junio C Hamano diff --git a/refs.c b/refs.c index 305c1a9..d7f4aa5 100644 --- a/refs.c +++ b/refs.c @@ -795,7 +795,7 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } -int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) +int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1) { const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; char *tz_c; @@ -828,7 +828,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) if (!lastgt) die("Log %s is corrupt.", logfile); date = strtoul(lastgt + 1, &tz_c, 10); - if (date <= at_time) { + if (date <= at_time || cnt == 0) { if (lastrec) { if (get_sha1_hex(lastrec, logged_sha1)) die("Log %s is corrupt.", logfile); @@ -859,6 +859,8 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) return 0; } lastrec = rec; + if (cnt > 0) + cnt--; } rec = logdata; diff --git a/refs.h b/refs.h index 0d4d79e..a57d437 100644 --- a/refs.h +++ b/refs.h @@ -39,7 +39,7 @@ extern void unlock_ref(struct ref_lock *lock); extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); /** Reads log for the value of ref during at_time. **/ -extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1); +extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1); /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); diff --git a/sha1_name.c b/sha1_name.c index ed711f2..e517033 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -249,24 +249,23 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; const char **p, *ref; char *real_ref = NULL; - int refs_found = 0, am; - unsigned long at_time = (unsigned long)-1; + int refs_found = 0; + int at, reflog_len; unsigned char *this_result; unsigned char sha1_from_ref[20]; if (len == 40 && !get_sha1_hex(str, sha1)) return 0; - /* At a given period of time? "@{2 hours ago}" */ - for (am = 1; am < len - 1; am++) { - if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') { - int date_len = len - am - 3; - char *date_spec = xmalloc(date_len + 1); - strlcpy(date_spec, str + am + 2, date_len + 1); - at_time = approxidate(date_spec); - free(date_spec); - len = am; - break; + /* basic@{time or number} format to query ref-log */ + reflog_len = 0; + if (str[len-1] == '}') { + for (at = 1; at < len - 1; at++) { + if (str[at] == '@' && str[at+1] == '{') { + reflog_len = (len-1) - (at+2); + len = at; + break; + } } } @@ -291,11 +290,22 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (warn_ambiguous_refs && refs_found > 1) fprintf(stderr, warning, len, str); - if (at_time != (unsigned long)-1) { - read_ref_at( - real_ref, - at_time, - sha1); + if (reflog_len) { + /* Is it asking for N-th entry, or approxidate? */ + int nth, i; + unsigned long at_time; + for (i = nth = 0; 0 <= nth && i < reflog_len; i++) { + char ch = str[at+2+i]; + if ('0' <= ch && ch <= '9') + nth = nth * 10 + ch - '0'; + else + nth = -1; + } + if (0 <= nth) + at_time = 0; + else + at_time = approxidate(str + at + 2); + read_ref_at(real_ref, at_time, nth, sha1); } free(real_ref); -- cgit v0.10.2-6-g49f6 From 7a2a0d214169a5d6f22fc62c4ae6a7ca5b27fb3e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 23:40:15 -0700 Subject: git-send-email: real name with period need to be dq-quoted on From: line An author name like 'A. U. Thor " is not a valid RFC 2822 address; when placing it on From: line, we would need to quote it, like this: Signed-off-by: "Junio C. Hamano" diff --git a/git-send-email.perl b/git-send-email.perl index 2fd5e87..21b3686 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -409,6 +409,11 @@ sub send_message $gitversion = Git::version(); } + my ($author_name) = ($from =~ /^(.*?)\s+ Date: Thu, 5 Oct 2006 14:07:42 -0700 Subject: git-blame --porcelain The new option makes the command's native output format to emit output that is easier to handle by Porcelain. Each line is output after a header. The header at the minimum has the first line which has: - 40-byte SHA-1 of the commit the line is attributed to; - the line number of the line in the original file; - the line number of the line in the final file; - on a line that starts a group of line from a different commit than the previous one, the number of lines in this group. On subsequent lines this field is absent. This header line is followed by the following information once for each commit: - author name ("author"), email ("author-mail"), time ("author-time"), and timezone ("author-tz"); similarly for committer. - filename in the commit the line is attributed to. - the first line of the commit log message ("summary"). The contents of the actual line is output after the above header, prefixed by a TAB. This is to allow adding more header elements later. Signed-off-by: Junio C Hamano diff --git a/blame.c b/blame.c index 47471e8..314f3e2 100644 --- a/blame.c +++ b/blame.c @@ -17,6 +17,7 @@ #include "diffcore.h" #include "revision.h" #include "xdiff-interface.h" +#include "quote.h" #define DEBUG 0 @@ -40,6 +41,7 @@ struct util_info { unsigned long size; int num_lines; const char *pathname; + unsigned meta_given:1; void *topo_data; }; @@ -332,12 +334,8 @@ static struct util_info *get_util(struct commit *commit) if (util) return util; - util = xmalloc(sizeof(struct util_info)); - util->buf = NULL; - util->size = 0; - util->line_map = NULL; + util = xcalloc(1, sizeof(struct util_info)); util->num_lines = -1; - util->pathname = NULL; commit->util = util; return util; } @@ -642,39 +640,99 @@ struct commit_info char *author_mail; unsigned long author_time; char *author_tz; + + /* filled only when asked for details */ + char *committer; + char *committer_mail; + unsigned long committer_time; + char *committer_tz; + + char *summary; }; -static void get_commit_info(struct commit *commit, struct commit_info *ret) +static void get_ac_line(const char *inbuf, const char *what, + int bufsz, char *person, char **mail, + unsigned long *time, char **tz) { int len; - char *tmp; - static char author_buf[1024]; - - tmp = strstr(commit->buffer, "\nauthor ") + 8; - len = strchr(tmp, '\n') - tmp; - ret->author = author_buf; - memcpy(ret->author, tmp, len); + char *tmp, *endp; + + tmp = strstr(inbuf, what); + if (!tmp) + goto error_out; + tmp += strlen(what); + endp = strchr(tmp, '\n'); + if (!endp) + len = strlen(tmp); + else + len = endp - tmp; + if (bufsz <= len) { + error_out: + /* Ugh */ + person = *mail = *tz = "(unknown)"; + *time = 0; + return; + } + memcpy(person, tmp, len); - tmp = ret->author; + tmp = person; tmp += len; *tmp = 0; while (*tmp != ' ') tmp--; - ret->author_tz = tmp+1; + *tz = tmp+1; *tmp = 0; while (*tmp != ' ') tmp--; - ret->author_time = strtoul(tmp, NULL, 10); + *time = strtoul(tmp, NULL, 10); *tmp = 0; while (*tmp != ' ') tmp--; - ret->author_mail = tmp + 1; - + *mail = tmp + 1; *tmp = 0; } +static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed) +{ + int len; + char *tmp, *endp; + static char author_buf[1024]; + static char committer_buf[1024]; + static char summary_buf[1024]; + + ret->author = author_buf; + get_ac_line(commit->buffer, "\nauthor ", + sizeof(author_buf), author_buf, &ret->author_mail, + &ret->author_time, &ret->author_tz); + + if (!detailed) + return; + + ret->committer = committer_buf; + get_ac_line(commit->buffer, "\ncommitter ", + sizeof(committer_buf), committer_buf, &ret->committer_mail, + &ret->committer_time, &ret->committer_tz); + + ret->summary = summary_buf; + tmp = strstr(commit->buffer, "\n\n"); + if (!tmp) { + error_out: + sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); + return; + } + tmp += 2; + endp = strchr(tmp, '\n'); + if (!endp) + goto error_out; + len = endp - tmp; + if (len >= sizeof(summary_buf)) + goto error_out; + memcpy(summary_buf, tmp, len); + summary_buf[len] = 0; +} + static const char *format_time(unsigned long time, const char *tz_str, int show_raw_time) { @@ -751,7 +809,7 @@ static int find_orig_linenum(struct util_info *u, int lineno) } static void emit_meta(struct commit *c, int lno, - int sha1_len, int compatibility, + int sha1_len, int compatibility, int porcelain, int show_name, int show_number, int show_raw_time, int longest_file, int longest_author, int max_digits, int max_orig_digits) @@ -763,7 +821,47 @@ static void emit_meta(struct commit *c, int lno, u = c->util; lineno = find_orig_linenum(u, lno); - get_commit_info(c, &ci); + if (porcelain) { + int group_size = -1; + struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1]; + if (cc != c) { + /* This is the beginning of this group */ + int i; + for (i = lno + 1; i < num_blame_lines; i++) + if (blame_lines[i] != c) + break; + group_size = i - lno; + } + if (0 < group_size) + printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1), + lineno, lno + 1, group_size); + else + printf("%s %d %d\n", sha1_to_hex(c->object.sha1), + lineno, lno + 1); + if (!u->meta_given) { + get_commit_info(c, &ci, 1); + printf("author %s\n", ci.author); + printf("author-mail %s\n", ci.author_mail); + printf("author-time %lu\n", ci.author_time); + printf("author-tz %s\n", ci.author_tz); + printf("committer %s\n", ci.committer); + printf("committer-mail %s\n", ci.committer_mail); + printf("committer-time %lu\n", ci.committer_time); + printf("committer-tz %s\n", ci.committer_tz); + printf("filename "); + if (quote_c_style(u->pathname, NULL, NULL, 0)) + quote_c_style(u->pathname, NULL, stdout, 0); + else + fputs(u->pathname, stdout); + printf("\nsummary %s\n", ci.summary); + + u->meta_given = 1; + } + putchar('\t'); + return; + } + + get_commit_info(c, &ci, 0); fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); if (compatibility) { printf("\t(%10s\t%10s\t%d)", ci.author, @@ -809,6 +907,7 @@ int main(int argc, const char **argv) int longest_file, longest_author, longest_file_lines; int show_name = 0; int show_number = 0; + int porcelain = 0; const char *prefix = setup_git_directory(); git_config(git_default_config); @@ -852,6 +951,12 @@ int main(int argc, const char **argv) show_number = 1; continue; } + if (!strcmp(argv[i], "--porcelain")) { + porcelain = 1; + sha1_len = 40; + show_raw_time = 1; + continue; + } if (!strcmp(argv[i], "--")) { options = 0; continue; @@ -934,7 +1039,7 @@ int main(int argc, const char **argv) longest_file = strlen(u->pathname); if (longest_file_lines < u->num_lines) longest_file_lines = u->num_lines; - get_commit_info(c, &ci); + get_commit_info(c, &ci, 0); if (longest_author < strlen(ci.author)) longest_author = strlen(ci.author); } @@ -943,7 +1048,7 @@ int main(int argc, const char **argv) for (i = 0; i < num_blame_lines; i++) { emit_meta(blame_lines[i], i, - sha1_len, compatibility, + sha1_len, compatibility, porcelain, show_name, show_number, show_raw_time, longest_file, longest_author, max_digits, max_orig_digits); -- cgit v0.10.2-6-g49f6 From eeef88cd20f2965325cde66e97a616bce4a757f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 13:55:58 -0700 Subject: gitweb: use blame --porcelain This makes gitweb (git_blame2) use "blame --porcelain", which lets the caller to figure out which line in the original version each line comes from. Using this information, change the behaviour of clicking the line number to go to the line of the blame output for the original commit. Before, clicking the line number meant "scoll up to show this line at the beginning of the page", which was not all that useful. The new behaviour lets you click on the line you are interested in to view the line in the context it was introduced, and keep digging deeper as you examine it. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0ac05cc..68347ac 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -974,6 +974,9 @@ sub parse_date { $date{'hour_local'} = $hour; $date{'minute_local'} = $min; $date{'tz_local'} = $tz; + $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s", + 1900+$year, $mon+1, $mday, + $hour, $min, $sec, $tz); return %date; } @@ -2501,7 +2504,8 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '--porcelain', '--', + $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = @@ -2525,33 +2529,52 @@ sub git_blame2 { HTML - while (<$fd>) { - my ($full_rev, $author, $date, $lineno, $data) = - /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; + my %metainfo = (); + while (1) { + $_ = <$fd>; + last unless defined $_; + my ($full_rev, $lineno, $orig_lineno, $group_size) = + /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/; + if (!exists $metainfo{$full_rev}) { + $metainfo{$full_rev} = {}; + } + my $meta = $metainfo{$full_rev}; + while (<$fd>) { + last if (s/^\t//); + if (/^(\S+) (.*)$/) { + $meta->{$1} = $2; + } + } + my $data = $_; my $rev = substr($full_rev, 0, 8); - my $print_c8 = 0; - - if (!defined $last_rev) { - $last_rev = $full_rev; - $print_c8 = 1; - } elsif ($last_rev ne $full_rev) { - $last_rev = $full_rev; + my $author = $meta->{'author'}; + my %date = parse_date($meta->{'author-time'}, + $meta->{'author-tz'}); + my $date = $date{'iso-tz'}; + if ($group_size) { $current_color = ++$current_color % $num_colors; - $print_c8 = 1; } print "\n"; - print "\n"; } - print "\n"; - print "\n"; + my $blamed = href(action => 'blame', + file_name => $meta->{'filename'}, + hash_base => $full_rev); + print ""; print "\n"; print "\n"; } -- cgit v0.10.2-6-g49f6 From 26e5fc3415a294546b4009c7280fed4d367d4e62 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Fri, 6 Oct 2006 11:10:54 +0200 Subject: Remove bashism from t3210-pack-refs.sh This bashism makes the test fail if /bin/sh is not bash. Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index f31e79c..ca5bd49 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -23,7 +23,7 @@ SHA1= test_expect_success \ 'see if git show-ref works as expected' \ 'git-branch a && - SHA1=$(< .git/refs/heads/a) && + SHA1=`cat .git/refs/heads/a` && echo "$SHA1 refs/heads/a" >expect && git-show-ref a >result && diff expect result' -- cgit v0.10.2-6-g49f6 From 3df196716eb89e752c96cf252d5c712387a7c112 Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Fri, 6 Oct 2006 11:15:03 +0200 Subject: Add default values for --window and --depth to the docs Currently, you actually have to read the source to find out the default values. While at it, fix two typos and suggest that these options actually take a parameter in git-pack-objects.txt. Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index d4661dd..f52e8fa 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -71,11 +71,11 @@ base-name:: --all:: This implies `--revs`. In addition to the list of revision arguments read from the standard input, pretend - as if all refs under `$GIT_DIR/refs` are specifed to be + as if all refs under `$GIT_DIR/refs` are specified to be included. ---window and --depth:: - These two options affects how the objects contained in +--window=[N], --depth=[N]:: + These two options affect how the objects contained in the pack are stored using delta compression. The objects are first internally sorted by type, size and optionally names and compared against the other objects @@ -84,6 +84,7 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. + The default value for both --window and --depth is 10. --incremental:: This flag causes an object already in a pack ignored diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 49f7e0a..d2eaa09 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -57,13 +57,14 @@ OPTIONS `git update-server-info`. --window=[N], --depth=[N]:: - These two options affects how the objects contained in the pack are + These two options affect how the objects contained in the pack are stored using delta compression. The objects are first internally sorted by type, size and optionally names and compared against the other objects within `--window` to see if using delta compression saves space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. + The default value for both --window and --depth is 10. Author -- cgit v0.10.2-6-g49f6 From 847abc0f2a8d57bf4c1f13e948995491b6bc6f58 Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Mon, 2 Oct 2006 06:21:33 +0900 Subject: gitweb: refactor decode() for utf8 conversion we already had a few place using decode() to convert perl internal encode to utf8. added a new thin wrapper to do just that. Signed-off-by: Yasushi SHOJI Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3320069..3cc0e96 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -443,6 +443,12 @@ sub validate_refname { return $input; } +# very thin wrapper for decode("utf8", $str, Encode::FB_DEFAULT); +sub to_utf8 { + my $str = shift; + return decode("utf8", $str, Encode::FB_DEFAULT); +} + # quote unsafe chars, but keep the slash, even when it's not # correct, but quoted slashes look too horrible in bookmarks sub esc_param { @@ -465,7 +471,7 @@ sub esc_url { # replace invalid utf8 character with SUBSTITUTION sequence sub esc_html { my $str = shift; - $str = decode("utf8", $str, Encode::FB_DEFAULT); + $str = to_utf8($str); $str = escapeHTML($str); $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) @@ -668,7 +674,7 @@ sub format_subject_html { if (length($short) < length($long)) { return $cgi->a({-href => $href, -class => "list subject", - -title => decode("utf8", $long, Encode::FB_DEFAULT)}, + -title => to_utf8($long)}, esc_html($short) . $extra); } else { return $cgi->a({-href => $href, -class => "list subject"}, @@ -845,7 +851,7 @@ sub git_get_projects_list { -e "$projectroot/$path/$export_ok")) { my $pr = { path => $path, - owner => decode("utf8", $owner, Encode::FB_DEFAULT), + owner => to_utf8($owner), }; push @list, $pr } @@ -874,7 +880,7 @@ sub git_get_project_owner { $pr = unescape($pr); $ow = unescape($ow); if ($pr eq $project) { - $owner = decode("utf8", $ow, Encode::FB_DEFAULT); + $owner = to_utf8($ow); last; } } @@ -1236,7 +1242,7 @@ sub get_file_owner { } my $owner = $gcos; $owner =~ s/[,;].*$//; - return decode("utf8", $owner, Encode::FB_DEFAULT); + return to_utf8($owner); } ## ...................................................................... @@ -3589,7 +3595,7 @@ XML "\n"; } print "
\n"; @@ -3598,7 +3604,7 @@ XML next; } my $file = esc_html(unquote($7)); - $file = decode("utf8", $file, Encode::FB_DEFAULT); + $file = to_utf8($file); print "$file
\n"; } print "]]>\n" . -- cgit v0.10.2-6-g49f6 From 55ff35cb649e77f7633f9a30e9988ecad1371fe5 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 6 Oct 2006 15:57:52 +0200 Subject: Show snapshot link in shortlog only if have_snapsho Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3cc0e96..276a842 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2033,8 +2033,10 @@ sub git_shortlog_body { print "\n" . "
\n" . "\n"; } -- cgit v0.10.2-6-g49f6 From 689b7f5ccbbb71f5a1a165cfb1e24abd42c7d009 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 6 Oct 2006 18:00:17 +0200 Subject: gitweb: Separate (new) and (deleted) in commitdiff by a space Currently it's pasted to the sha1 of the blob and looks ugly. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 276a842..3d677a9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1922,14 +1922,14 @@ sub git_patchset_body { print "
" . file_type($diffinfo->{'to_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'to_id'}) . "(new)" . + $diffinfo->{'to_id'}) . " (new)" . "
\n"; # class="diff_info" } elsif ($diffinfo->{'status'} eq "D") { # deleted print "
" . file_type($diffinfo->{'from_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'from_id'}) . "(deleted)" . + $diffinfo->{'from_id'}) . " (deleted)" . "
\n"; # class="diff_info" } elsif ($diffinfo->{'status'} eq "R" || # renamed -- cgit v0.10.2-6-g49f6 From 7e0fe5c939bdd5cc2885d21799e95304a46bf706 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 6 Oct 2006 18:55:04 +0200 Subject: gitweb: Handle commits with empty commit messages more reasonably Currently those look very weird, you can't get easily at the commit view etc. This patch makes their title '(no commit message)'. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3d677a9..c7a245a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1071,6 +1071,9 @@ sub parse_commit { last; } } + if ($co{'title'} eq "") { + $co{'title'} = $co{'title_short'} = '(no commit message)'; + } # remove added spaces foreach my $line (@commit_lines) { $line =~ s/^ //; -- cgit v0.10.2-6-g49f6 From a144154f85fe1d61da2a515e79ea1d08b5137f20 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 6 Oct 2006 18:59:33 +0200 Subject: gitweb: [commit view] Do not suppress commitdiff link in root commit There's no reason for that, the commitdiff view is meaningful for the root commit as well and we link to it everywhere else. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c7a245a..cdb09c4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2957,7 +2957,7 @@ sub git_commit { "blame"); } git_header_html(undef, $expires); - git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', + git_print_page_nav('commit', '', $hash, $co{'tree'}, $hash, join (' | ', @views_nav)); -- cgit v0.10.2-6-g49f6 From 3de63c3f9f82cd0a43f3f69dd1e3662bc101d629 Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Sat, 7 Oct 2006 21:07:40 +0200 Subject: git-commit: fix coding style. git-commit.sh was using a mixture of spaces and tabs for indentation. This is changed to one tab per indentation level. No code changes. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano diff --git a/git-commit.sh b/git-commit.sh index 6f6cbda..5b1cf85 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -41,21 +41,21 @@ run_status () { # so the regular index file is what we use to compare. if test '' != "$TMP_INDEX" then - GIT_INDEX_FILE="$TMP_INDEX" - export GIT_INDEX_FILE + GIT_INDEX_FILE="$TMP_INDEX" + export GIT_INDEX_FILE elif test -f "$NEXT_INDEX" then - GIT_INDEX_FILE="$NEXT_INDEX" - export GIT_INDEX_FILE + GIT_INDEX_FILE="$NEXT_INDEX" + export GIT_INDEX_FILE fi - case "$status_only" in - t) color= ;; - *) color=--nocolor ;; - esac - git-runstatus ${color} \ - ${verbose:+--verbose} \ - ${amend:+--amend} \ + case "$status_only" in + t) color= ;; + *) color=--nocolor ;; + esac + git-runstatus ${color} \ + ${verbose:+--verbose} \ + ${amend:+--amend} \ ${untracked_files:+--untracked} } @@ -87,179 +87,181 @@ only_include_assumed= untracked_files= while case "$#" in 0) break;; esac do - case "$1" in - -F|--F|-f|--f|--fi|--fil|--file) - case "$#" in 1) usage ;; esac - shift - no_edit=t - log_given=t$log_given - logfile="$1" - shift - ;; - -F*|-f*) - no_edit=t - log_given=t$log_given - logfile=`expr "z$1" : 'z-[Ff]\(.*\)'` - shift - ;; - --F=*|--f=*|--fi=*|--fil=*|--file=*) - no_edit=t - log_given=t$log_given - logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'` - shift - ;; - -a|--a|--al|--all) - all=t - shift - ;; - --au=*|--aut=*|--auth=*|--autho=*|--author=*) - force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'` - shift - ;; - --au|--aut|--auth|--autho|--author) - case "$#" in 1) usage ;; esac - shift - force_author="$1" - shift - ;; - -e|--e|--ed|--edi|--edit) - edit_flag=t - shift - ;; - -i|--i|--in|--inc|--incl|--inclu|--includ|--include) - also=t - shift - ;; - -o|--o|--on|--onl|--only) - only=t - shift - ;; - -m|--m|--me|--mes|--mess|--messa|--messag|--message) - case "$#" in 1) usage ;; esac - shift - log_given=m$log_given - if test "$log_message" = '' - then - log_message="$1" - else - log_message="$log_message + case "$1" in + -F|--F|-f|--f|--fi|--fil|--file) + case "$#" in 1) usage ;; esac + shift + no_edit=t + log_given=t$log_given + logfile="$1" + shift + ;; + -F*|-f*) + no_edit=t + log_given=t$log_given + logfile=`expr "z$1" : 'z-[Ff]\(.*\)'` + shift + ;; + --F=*|--f=*|--fi=*|--fil=*|--file=*) + no_edit=t + log_given=t$log_given + logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'` + shift + ;; + -a|--a|--al|--all) + all=t + shift + ;; + --au=*|--aut=*|--auth=*|--autho=*|--author=*) + force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'` + shift + ;; + --au|--aut|--auth|--autho|--author) + case "$#" in 1) usage ;; esac + shift + force_author="$1" + shift + ;; + -e|--e|--ed|--edi|--edit) + edit_flag=t + shift + ;; + -i|--i|--in|--inc|--incl|--inclu|--includ|--include) + also=t + shift + ;; + -o|--o|--on|--onl|--only) + only=t + shift + ;; + -m|--m|--me|--mes|--mess|--messa|--messag|--message) + case "$#" in 1) usage ;; esac + shift + log_given=m$log_given + if test "$log_message" = '' + then + log_message="$1" + else + log_message="$log_message $1" - fi - no_edit=t - shift - ;; - -m*) - log_given=m$log_given - if test "$log_message" = '' - then - log_message=`expr "z$1" : 'z-m\(.*\)'` - else - log_message="$log_message + fi + no_edit=t + shift + ;; + -m*) + log_given=m$log_given + if test "$log_message" = '' + then + log_message=`expr "z$1" : 'z-m\(.*\)'` + else + log_message="$log_message `expr "z$1" : 'z-m\(.*\)'`" - fi - no_edit=t - shift - ;; - --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) - log_given=m$log_given - if test "$log_message" = '' - then - log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'` - else - log_message="$log_message + fi + no_edit=t + shift + ;; + --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) + log_given=m$log_given + if test "$log_message" = '' + then + log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'` + else + log_message="$log_message `expr "z$1" : 'zq-[^=]*=\(.*\)'`" - fi - no_edit=t - shift - ;; - -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|--no-verify) - verify= - shift - ;; - --a|--am|--ame|--amen|--amend) - amend=t - log_given=t$log_given - use_commit=HEAD - shift - ;; - -c) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit= - shift - ;; - --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\ - --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ - --reedit-messag=*|--reedit-message=*) - log_given=t$log_given - use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` - no_edit= - shift - ;; - --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\ - --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|--reedit-message) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit= - shift - ;; - -C) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit=t - shift - ;; - --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\ - --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ - --reuse-message=*) - log_given=t$log_given - use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` - no_edit=t - shift - ;; - --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\ - --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit=t - shift - ;; - -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) - signoff=t - shift - ;; - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t - shift - ;; - -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|--untracked|\ - --untracked-|--untracked-f|--untracked-fi|--untracked-fil|--untracked-file|\ - --untracked-files) - untracked_files=t - shift - ;; - --) - shift - break - ;; - -*) - usage - ;; - *) - break - ;; - esac + fi + no_edit=t + shift + ;; + -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\ + --no-verify) + verify= + shift + ;; + --a|--am|--ame|--amen|--amend) + amend=t + log_given=t$log_given + use_commit=HEAD + shift + ;; + -c) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit= + shift + ;; + --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\ + --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ + --reedit-messag=*|--reedit-message=*) + log_given=t$log_given + use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` + no_edit= + shift + ;; + --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\ + --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\ + --reedit-message) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit= + shift + ;; + -C) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit=t + shift + ;; + --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\ + --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ + --reuse-message=*) + log_given=t$log_given + use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` + no_edit=t + shift + ;; + --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\ + --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit=t + shift + ;; + -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) + signoff=t + shift + ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t + shift + ;; + -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\ + --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\ + --untracked-file|--untracked-files) + untracked_files=t + shift + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac done case "$edit_flag" in t) no_edit= ;; esac @@ -268,33 +270,33 @@ case "$edit_flag" in t) no_edit= ;; esac case "$amend,$initial_commit" in t,t) - die "You do not have anything to amend." ;; + die "You do not have anything to amend." ;; t,) - if [ -f "$GIT_DIR/MERGE_HEAD" ]; then - die "You are in the middle of a merge -- cannot amend." - fi ;; + if [ -f "$GIT_DIR/MERGE_HEAD" ]; then + die "You are in the middle of a merge -- cannot amend." + fi ;; esac case "$log_given" in tt*) - die "Only one of -c/-C/-F can be used." ;; + die "Only one of -c/-C/-F can be used." ;; *tm*|*mt*) - die "Option -m cannot be combined with -c/-C/-F." ;; + die "Option -m cannot be combined with -c/-C/-F." ;; esac case "$#,$also,$only,$amend" in *,t,t,*) - die "Only one of --include/--only can be used." ;; + die "Only one of --include/--only can be used." ;; 0,t,,* | 0,,t,) - die "No paths with --include/--only does not make sense." ;; + die "No paths with --include/--only does not make sense." ;; 0,,t,t) - only_include_assumed="# Clever... amending the last one with dirty index." ;; + only_include_assumed="# Clever... amending the last one with dirty index." ;; 0,,,*) - ;; + ;; *,,,*) - only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..." - also= - ;; + only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..." + also= + ;; esac unset only case "$all,$also,$#" in @@ -341,47 +343,47 @@ t,) ,) case "$#" in 0) - ;; # commit as-is + ;; # commit as-is *) - if test -f "$GIT_DIR/MERGE_HEAD" - then - refuse_partial "Cannot do a partial commit during a merge." - fi - TMP_INDEX="$GIT_DIR/tmp-index$$" - if test -z "$initial_commit" - then - # make sure index is clean at the specified paths, or - # they are additions. - dirty_in_index=`git-diff-index --cached --name-status \ - --diff-filter=DMTU HEAD -- "$@"` - test -z "$dirty_in_index" || - refuse_partial "Different in index and the last commit: + if test -f "$GIT_DIR/MERGE_HEAD" + then + refuse_partial "Cannot do a partial commit during a merge." + fi + TMP_INDEX="$GIT_DIR/tmp-index$$" + if test -z "$initial_commit" + then + # make sure index is clean at the specified paths, or + # they are additions. + dirty_in_index=`git-diff-index --cached --name-status \ + --diff-filter=DMTU HEAD -- "$@"` + test -z "$dirty_in_index" || + refuse_partial "Different in index and the last commit: $dirty_in_index" - fi - commit_only=`git-ls-files --error-unmatch -- "$@"` || exit - - # Build the temporary index and update the real index - # the same way. - if test -z "$initial_commit" - then - cp "$THIS_INDEX" "$TMP_INDEX" - GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD - else - rm -f "$TMP_INDEX" - fi || exit - - echo "$commit_only" | - GIT_INDEX_FILE="$TMP_INDEX" \ - git-update-index --add --remove --stdin && - - save_index && - echo "$commit_only" | - ( - GIT_INDEX_FILE="$NEXT_INDEX" - export GIT_INDEX_FILE - git-update-index --remove --stdin - ) || exit - ;; + fi + commit_only=`git-ls-files --error-unmatch -- "$@"` || exit + + # Build the temporary index and update the real index + # the same way. + if test -z "$initial_commit" + then + cp "$THIS_INDEX" "$TMP_INDEX" + GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD + else + rm -f "$TMP_INDEX" + fi || exit + + echo "$commit_only" | + GIT_INDEX_FILE="$TMP_INDEX" \ + git-update-index --add --remove --stdin && + + save_index && + echo "$commit_only" | + ( + GIT_INDEX_FILE="$NEXT_INDEX" + export GIT_INDEX_FILE + git-update-index --remove --stdin + ) || exit + ;; esac ;; esac @@ -399,7 +401,7 @@ else fi GIT_INDEX_FILE="$USE_INDEX" \ - git-update-index -q $unmerged_ok_if_status --refresh || exit + git-update-index -q $unmerged_ok_if_status --refresh || exit ################################################################ # If the request is status, just show it and exit. -- cgit v0.10.2-6-g49f6 From 7a0cf2d0138cf3abd3f2c3c9a1aa4dc55bf0700f Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Sat, 7 Oct 2006 21:27:46 +0200 Subject: test-lib: separate individual test better in verbose mode. When running tests with --verbose it is difficult to see where one test starts and where it ends because everything is printed in one big lump. Fix that by printing one single newline between each test. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano diff --git a/t/test-lib.sh b/t/test-lib.sh index b523fef..2488e6e 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -135,6 +135,7 @@ test_expect_failure () { else test_failure_ "$@" fi + echo >&3 "" } test_expect_success () { @@ -148,6 +149,7 @@ test_expect_success () { else test_failure_ "$@" fi + echo >&3 "" } test_expect_code () { @@ -161,6 +163,7 @@ test_expect_code () { else test_failure_ "$@" fi + echo >&3 "" } # Most tests can use the created repository, but some amy need to create more. -- cgit v0.10.2-6-g49f6 From 45a3b12cfd3eaa05bbb0954790d5be5b8240a7b5 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 7 Oct 2006 15:17:47 +0200 Subject: gitweb: Document features better This expands gitweb/README to talk some more about GITWEB_CONFIG, moves feature-specific documentation in gitweb.cgi to the inside of the %features array, and adds some short description of all the features. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/README b/gitweb/README index 27c6dac..abbaf6b 100644 --- a/gitweb/README +++ b/gitweb/README @@ -24,11 +24,25 @@ You can specify the following configuration variables when building GIT: * GITWEB_LOGO Points to the location where you put git-logo.png on your web server. * GITWEB_CONFIG - This file will be loaded using 'require'. If the environment + This file will be loaded using 'require' and can be used to override any + of the options above as well as some other options - see the top of + 'gitweb.cgi' for their full list and description. If the environment $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the environment variable will be loaded instead of the file specified when gitweb.cgi was created. +Runtime gitweb configuration +---------------------------- + +You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG` +(defaults to 'gitweb_config.perl' in the same directory as the CGI). +See the top of 'gitweb.cgi' for the list of variables and some description. +The most notable thing that is not configurable at compile time are the +optional features, stored in the '%features' variable. You can find further +description on how to reconfigure the default features setting in your +`GITWEB_CONFIG` or per-project in `project.git/config` inside 'gitweb.cgi'. + + Originally written by: Kay Sievers diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0ff6f7c..1d5217e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -87,22 +87,63 @@ our %feature = ( # # use gitweb_check_feature() to check if is enabled + # Enable the 'blame' blob view, showing the last commit that modified + # each line in the file. This can be very CPU-intensive. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'blame'}{'default'} = [1]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'blame'}{'override'} = 1; + # and in project config gitweb.blame = 0|1; 'blame' => { 'sub' => \&feature_blame, 'override' => 0, 'default' => [0]}, + # Enable the 'snapshot' link, providing a compressed tarball of any + # tree. This can potentially generate high traffic if you have large + # project. + + # To disable system wide have in $GITWEB_CONFIG + # $feature{'snapshot'}{'default'} = [undef]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'blame'}{'override'} = 1; + # and in project config gitweb.snapshot = none|gzip|bzip2; 'snapshot' => { 'sub' => \&feature_snapshot, 'override' => 0, # => [content-encoding, suffix, program] 'default' => ['x-gzip', 'gz', 'gzip']}, + # Enable the pickaxe search, which will list the commits that modified + # a given string in a file. This can be practical and quite faster + # alternative to 'blame', but still potentially CPU-intensive. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'pickaxe'}{'default'} = [1]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'pickaxe'}{'override'} = 1; + # and in project config gitweb.pickaxe = 0|1; 'pickaxe' => { 'sub' => \&feature_pickaxe, 'override' => 0, 'default' => [1]}, + # Make gitweb use an alternative format of the URLs which can be + # more readable and natural-looking: project name is embedded + # directly in the path and the query string contains other + # auxiliary information. All gitweb installations recognize + # URL in either format; this configures in which formats gitweb + # generates links. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'pathinfo'}{'default'} = [1]; + # Project specific override is not supported. + + # Note that you will need to change the default location of CSS, + # favicon, logo and possibly other files to an absolute URL. Also, + # if gitweb.cgi serves as your indexfile, you will need to force + # $my_uri to contain the script name in your $GITWEB_CONFIG. 'pathinfo' => { 'override' => 0, 'default' => [0]}, @@ -123,12 +164,6 @@ sub gitweb_check_feature { return $sub->(@defaults); } -# To enable system wide have in $GITWEB_CONFIG -# $feature{'blame'}{'default'} = [1]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'blame'}{'override'} = 1; -# and in project config gitweb.blame = 0|1; - sub feature_blame { my ($val) = git_get_project_config('blame', '--bool'); @@ -141,12 +176,6 @@ sub feature_blame { return $_[0]; } -# To disable system wide have in $GITWEB_CONFIG -# $feature{'snapshot'}{'default'} = [undef]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'blame'}{'override'} = 1; -# and in project config gitweb.snapshot = none|gzip|bzip2 - sub feature_snapshot { my ($ctype, $suffix, $command) = @_; @@ -170,12 +199,6 @@ sub gitweb_have_snapshot { return $have_snapshot; } -# To enable system wide have in $GITWEB_CONFIG -# $feature{'pickaxe'}{'default'} = [1]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'pickaxe'}{'override'} = 1; -# and in project config gitweb.pickaxe = 0|1; - sub feature_pickaxe { my ($val) = git_get_project_config('pickaxe', '--bool'); -- cgit v0.10.2-6-g49f6 From cf72fb07b77c73b4777b6a2e0836e3029c5f0f3c Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 7 Oct 2006 01:47:24 +0200 Subject: git-archive --format=zip: use default version ID Use 10 for the "version needed to extract" field. This is the default value, and we want to use it because we don't do anything special. Info-ZIP's zip uses it, too. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/archive-zip.c b/archive-zip.c index 3ffdad6..ae74623 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -214,7 +214,7 @@ static int write_zip_entry(const unsigned char *sha1, copy_le32(dirent.magic, 0x02014b50); copy_le16(dirent.creator_version, 0); - copy_le16(dirent.version, 20); + copy_le16(dirent.version, 10); copy_le16(dirent.flags, 0); copy_le16(dirent.compression_method, method); copy_le16(dirent.mtime, zip_time); @@ -236,7 +236,7 @@ static int write_zip_entry(const unsigned char *sha1, zip_dir_entries++; copy_le32(header.magic, 0x04034b50); - copy_le16(header.version, 20); + copy_le16(header.version, 10); copy_le16(header.flags, 0); copy_le16(header.compression_method, method); copy_le16(header.mtime, zip_time); -- cgit v0.10.2-6-g49f6 From 62cdce17c57a28240048c5064fab57edae19657f Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 7 Oct 2006 01:47:35 +0200 Subject: git-archive --format=zip: add symlink support Add symlink support to ZIP file creation, and a few tests. This implementation sets the "version made by" field (creator_version) to Unix for symlinks, only; regular files and directories are still marked as originating from FAT/VFAT/NTFS. Also set "external file attributes" (attr2) to 0 for regular files and 16 for directories (FAT attribute), and to the file mode for symlinks. We could always set the creator_version to Unix and include the mode, but then Info-ZIP unzip would set the mode of the extracted files to *exactly* the value stored in attr2. The FAT trick makes it apply the umask instead. Note: FAT has no executable bit, so this information is not stored in the ZIP file. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/archive-zip.c b/archive-zip.c index ae74623..28e7352 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -145,6 +145,7 @@ static int write_zip_entry(const unsigned char *sha1, { struct zip_local_header header; struct zip_dir_header dirent; + unsigned long attr2; unsigned long compressed_size; unsigned long uncompressed_size; unsigned long crc; @@ -172,12 +173,16 @@ static int write_zip_entry(const unsigned char *sha1, if (S_ISDIR(mode)) { method = 0; + attr2 = 16; result = READ_TREE_RECURSIVE; out = NULL; uncompressed_size = 0; compressed_size = 0; - } else if (S_ISREG(mode)) { - method = zlib_compression_level == 0 ? 0 : 8; + } else if (S_ISREG(mode) || S_ISLNK(mode)) { + method = 0; + attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0; + if (S_ISREG(mode) && zlib_compression_level != 0) + method = 8; result = 0; buffer = read_sha1_file(sha1, type, &size); if (!buffer) @@ -213,7 +218,7 @@ static int write_zip_entry(const unsigned char *sha1, } copy_le32(dirent.magic, 0x02014b50); - copy_le16(dirent.creator_version, 0); + copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0); copy_le16(dirent.version, 10); copy_le16(dirent.flags, 0); copy_le16(dirent.compression_method, method); @@ -227,7 +232,7 @@ static int write_zip_entry(const unsigned char *sha1, copy_le16(dirent.comment_length, 0); copy_le16(dirent.disk, 0); copy_le16(dirent.attr1, 0); - copy_le32(dirent.attr2, 0); + copy_le32(dirent.attr2, attr2); copy_le32(dirent.offset, zip_offset); memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); zip_dir_offset += sizeof(struct zip_dir_header); diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 278eb66..cf08e92 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -26,6 +26,7 @@ commit id embedding: . ./test-lib.sh TAR=${TAR:-tar} +UNZIP=${UNZIP:-unzip} test_expect_success \ 'populate workdir' \ @@ -95,4 +96,38 @@ test_expect_success \ 'validate file contents with prefix' \ 'diff -r a c/prefix/a' +test_expect_success \ + 'git-archive --format=zip' \ + 'git-archive --format=zip HEAD >d.zip' + +test_expect_success \ + 'extract ZIP archive' \ + '(mkdir d && cd d && $UNZIP ../d.zip)' + +test_expect_success \ + 'validate filenames' \ + '(cd d/a && find .) | sort >d.lst && + diff a.lst d.lst' + +test_expect_success \ + 'validate file contents' \ + 'diff -r a d/a' + +test_expect_success \ + 'git-archive --format=zip with prefix' \ + 'git-archive --format=zip --prefix=prefix/ HEAD >e.zip' + +test_expect_success \ + 'extract ZIP archive with prefix' \ + '(mkdir e && cd e && $UNZIP ../e.zip)' + +test_expect_success \ + 'validate filenames with prefix' \ + '(cd e/prefix/a && find .) | sort >e.lst && + diff a.lst e.lst' + +test_expect_success \ + 'validate file contents with prefix' \ + 'diff -r a e/prefix/a' + test_done -- cgit v0.10.2-6-g49f6 From e6b0964af595bbc4dab2a63c17bc4c8064a28073 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 7 Oct 2006 03:09:05 -0700 Subject: Make git-send-email detect mbox-style patches more readily Earlier we insisted that mbox file to begin with "From ". That is fine as long as you feed format-patch output, but if you handcraft the input file, this is unnecessary burden. We should detect lines that look like e-mail headers and say that is also a mbox file. The other input file format is traditional "send lots of email", whose first line would never look like e-mail headers, so this is a safe change. The original patch was done by Matthew Wilcox, which checked explicitly for headers the script pays attention to. Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index 21b3686..eb91270 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -481,15 +481,21 @@ foreach my $t (@files) { my $author_not_sender = undef; @cc = @initial_cc; @xh = (); - my $found_mbox = 0; + my $input_format = undef; my $header_done = 0; $message = ""; while() { if (!$header_done) { - $found_mbox = 1, next if (/^From /); + if (/^From /) { + $input_format = 'mbox'; + next; + } chomp; + if (!defined $input_format && /^[-A-Za-z]+:\s/) { + $input_format = 'mbox'; + } - if ($found_mbox) { + if (defined $input_format && $input_format eq 'mbox') { if (/^Subject:\s+(.*)$/) { $subject = $1; @@ -514,6 +520,7 @@ foreach my $t (@files) { # line 1 = cc # line 2 = subject # So let's support that, too. + $input_format = 'lots'; if (@cc == 0) { printf("(non-mbox) Adding cc: %s from line '%s'\n", $_, $_) unless $quiet; -- cgit v0.10.2-6-g49f6 From 4057deb5de110176ac19519177654108607b685c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 8 Oct 2006 01:35:18 -0700 Subject: core.logallrefupdates create new log file only for branch heads. It used to mean "create log file for any ref that is updated", but now it creates new log files only for branch heads. The old behaviour made this configuration less useful than otherwise it would be; automatically creating log file for tags is almost always not useful. Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 84e3891..232e2a9 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -71,12 +71,16 @@ core.preferSymlinkRefs:: expect HEAD to be a symbolic link. core.logAllRefUpdates:: - If true, `git-update-ref` will append a line to - "$GIT_DIR/logs/" listing the new SHA1 and the date/time - of the update. If the file does not exist it will be - created automatically. This information can be used to - determine what commit was the tip of a branch "2 days ago". - This value is false by default (no logging). + Updates to a ref is logged to the file + "$GIT_DIR/logs/", by appending the new and old + SHA1, the date/time and the reason of the update, but + only when the file exists. If this configuration + variable is set to true, missing "$GIT_DIR/logs/" + file is automatically created for branch heads. + + This information can be used to determine what commit + was the tip of a branch "2 days ago". This value is + false by default (no automated creation of log files). core.repositoryFormatVersion:: Internal variable identifying the repository format and layout diff --git a/refs.c b/refs.c index 305c1a9..75a0d7b 100644 --- a/refs.c +++ b/refs.c @@ -721,7 +721,8 @@ static int log_ref_write(struct ref_lock *lock, char *logrec; const char *committer; - if (log_all_ref_updates) { + if (log_all_ref_updates && + !strncmp(lock->ref_name, "refs/heads/", 11)) { if (safe_create_leading_directories(lock->log_file) < 0) return error("unable to create directory for %s", lock->log_file); -- cgit v0.10.2-6-g49f6 From b3d4204fc49959ddcd54c329be94189f98714d73 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 8 Oct 2006 01:36:08 -0700 Subject: git-pack-refs --all This changes 'git-pack-refs' to pack only tags by default. Branches are meant to be updated, either by committing onto it yourself or tracking remote branches, and packed entries can become stale easily, but tags are usually "create once and live forever" and benefit more from packing. Signed-off-by: Junio C Hamano diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 23d0d07..1087657 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -2,7 +2,7 @@ #include "refs.h" static const char builtin_pack_refs_usage[] = -"git-pack-refs [--prune]"; +"git-pack-refs [--all] [--prune]"; struct ref_to_prune { struct ref_to_prune *next; @@ -68,6 +68,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) { int fd, i; struct pack_refs_cb_data cbdata; + int (*iterate_ref)(each_ref_fn, void *) = for_each_tag_ref; memset(&cbdata, 0, sizeof(cbdata)); @@ -77,6 +78,10 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) cbdata.prune = 1; continue; } + if (!strcmp(arg, "--all")) { + iterate_ref = for_each_ref; + continue; + } /* perhaps other parameters later... */ break; } @@ -88,7 +93,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) if (!cbdata.refs_file) die("unable to create ref-pack file structure (%s)", strerror(errno)); - for_each_ref(handle_one_ref, &cbdata); + iterate_ref(handle_one_ref, &cbdata); fflush(cbdata.refs_file); fsync(fd); fclose(cbdata.refs_file); diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index ca5bd49..a4fbfda 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -31,7 +31,7 @@ test_expect_success \ test_expect_success \ 'see if a branch still exists when packed' \ 'git-branch b && - git-pack-refs && + git-pack-refs --all && rm .git/refs/heads/b && echo "$SHA1 refs/heads/b" >expect && git-show-ref b >result && @@ -40,14 +40,14 @@ test_expect_success \ test_expect_failure \ 'git branch c/d should barf if branch c exists' \ 'git-branch c && - git-pack-refs && + git-pack-refs --all && rm .git/refs/heads/c && git-branch c/d' test_expect_success \ 'see if a branch still exists after git pack-refs --prune' \ 'git-branch e && - git-pack-refs --prune && + git-pack-refs --all --prune && echo "$SHA1 refs/heads/e" >expect && git-show-ref e >result && diff expect result' @@ -55,22 +55,22 @@ test_expect_success \ test_expect_failure \ 'see if git pack-refs --prune remove ref files' \ 'git-branch f && - git-pack-refs --prune && + git-pack-refs --all --prune && ls .git/refs/heads/f' test_expect_success \ 'git branch g should work when git branch g/h has been deleted' \ 'git-branch g/h && - git-pack-refs --prune && + git-pack-refs --all --prune && git-branch -d g/h && git-branch g && - git-pack-refs && + git-pack-refs --all && git-branch -d g' test_expect_failure \ 'git branch i/j/k should barf if branch i exists' \ 'git-branch i && - git-pack-refs --prune && + git-pack-refs --all --prune && git-branch i/j/k' test_expect_success \ @@ -90,7 +90,7 @@ test_expect_success \ git-branch -d n/o && git-branch n/o/p && git-branch -d n/op && - git-pack-refs --prune && + git-pack-refs --all --prune && git-branch -d n/o/p && git-branch n' -- cgit v0.10.2-6-g49f6 From 9cb90b80fc1ec09d8e51451b18a7c8ef7eac8908 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sun, 8 Oct 2006 15:44:50 +0200 Subject: git-tar-tree: don't RUN_SETUP Noted by Jiri Slaby, git-tar-tree --remote doesn't need to be run from inside of a git archive. Since git-tar-tree is now only a wrapper for git-archive, which calls setup_git_directory() as needed, we should drop the flag RUN_SETUP. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/git.c b/git.c index b8e8622..d7103a4 100644 --- a/git.c +++ b/git.c @@ -258,7 +258,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, - { "tar-tree", cmd_tar_tree, RUN_SETUP }, + { "tar-tree", cmd_tar_tree }, { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From 9a7a62ff71c436fb5e024ec409ec28fca460a168 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 6 Oct 2006 12:31:05 +0200 Subject: gitweb: Cleanup Git logo and Git logo target generation Rename $githelp_url and $githelp_label to $logo_url and $logo_label to be more obvious what they refer to; while at it add commented out previous contents (git documentation at kernel.org). Add comment about logo size. Use $cgi->a(...) to generate Git logo link; it automatically escapes attribute values when it is needed. Escape href attribute using esc_url instead of (incorrect!) esc_html. Move styling of git logo element from "style" attribute to CSS via setting class to "logo". Perhaps we should set it by id rather than by class. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 668e69a..3f62b6d 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -16,6 +16,11 @@ a:hover, a:visited, a:active { color: #880000; } +img.logo { + float: right; + border-width: 0px; +} + div.page_header { height: 25px; padding: 8px; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index cdb09c4..0ec1eef 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -46,13 +46,16 @@ our $home_text = "++GITWEB_HOMETEXT++"; # URI of default stylesheet our $stylesheet = "++GITWEB_CSS++"; -# URI of GIT logo +# URI of GIT logo (72x27 size) our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type our $favicon = "++GITWEB_FAVICON++"; -our $githelp_url = "http://git.or.cz/"; -our $githelp_label = "git homepage"; +# URI and label (title) of GIT logo link +#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/"; +#our $logo_label = "git documentation"; +our $logo_url = "http://git.or.cz/"; +our $logo_label = "git homepage"; # source of projects list our $projects_list = "++GITWEB_LIST++"; @@ -1385,11 +1388,9 @@ EOF print "\n" . "\n" . "
\n" . - "" . - "\"git\"" . - "\n"; + $cgi->a({-href => esc_url($logo_url), + -title => $logo_label}, + qq()); print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; if (defined $project) { print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); -- cgit v0.10.2-6-g49f6 From adc446fe5d5f6dc1fb5edeaa9aa016ef94e70da1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 8 Oct 2006 12:56:19 -0700 Subject: Add WEBDAV timeout to http-fetch. Sean writes: > On Sat, 07 Oct 2006 21:52:02 -0700 > Junio C Hamano wrote: > >> Using DAV, if it works with the server, has the advantage of not >> having to keep objects/info/packs up-to-date from repository >> owner's point of view. But the repository owner ends up keeping >> up-to-date as a side effect of keeping info/refs up-to-date >> anyway (as I do not see a code to read that information over >> DAV), so there is no point doing this over DAV in practice. >> >> Perhaps we should remove call to remote_ls() from >> fetch_indices() unconditionally, not just protected with >> NO_EXPAT and be done with it? > > That makes a lot of sense. A server really has to always provide > a objects/info/packs anyway, just to be fetchable today by clients > that are compiled with NO_EXPAT. And even for an isolated group where everybody knows that everybody else runs DAV-enabled clients, they need info/refs prepared for ls-remote and git-fetch script, which means you will run update-server-info to keep objects/info/packs up to date. Nick, do you see holes in my logic? -- >8 -- http-fetch.c: drop remote_ls() While doing remote_ls() over DAV potentially allows the server side not to keep objects/info/pack up-to-date, misconfigured or buggy servers can silently ignore or not to respond to DAV requests and makes the client hang. The server side (unfortunately) needs to run git-update-server-info even if remote_ls() removes the need to keep objects/info/pack file up-to-date, because the caller of git-http-fetch (git-fetch) and other clients that interact with the repository (e.g. git-ls-remote) need to read from info/refs file (there is no code to make that unnecessary by using DAV yet). Perhaps the right solution in the longer-term is to make info/refs also unnecessary by using DAV, and we would want to resurrect the code this patch removes when we do so, but let's drop remote_ls() implementation for now. It is causing problems without really helping anything yet. git will keep it for us until we need it next time. Signed-off-by: Junio C Hamano diff --git a/http-fetch.c b/http-fetch.c index bc74f30..396552d 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -4,35 +4,6 @@ #include "fetch.h" #include "http.h" -#ifndef NO_EXPAT -#include - -/* Definitions for DAV requests */ -#define DAV_PROPFIND "PROPFIND" -#define DAV_PROPFIND_RESP ".multistatus.response" -#define DAV_PROPFIND_NAME ".multistatus.response.href" -#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection" -#define PROPFIND_ALL_REQUEST "\n\n\n" - -/* Definitions for processing XML DAV responses */ -#ifndef XML_STATUS_OK -enum XML_Status { - XML_STATUS_OK = 1, - XML_STATUS_ERROR = 0 -}; -#define XML_STATUS_OK 1 -#define XML_STATUS_ERROR 0 -#endif - -/* Flags that control remote_ls processing */ -#define PROCESS_FILES (1u << 0) -#define PROCESS_DIRS (1u << 1) -#define RECURSIVE (1u << 2) - -/* Flags that remote_ls passes to callback functions */ -#define IS_DIR (1u << 0) -#endif - #define PREV_BUF_SIZE 4096 #define RANGE_HEADER_SIZE 30 @@ -90,30 +61,6 @@ struct alternates_request { int http_specific; }; -#ifndef NO_EXPAT -struct xml_ctx -{ - char *name; - int len; - char *cdata; - void (*userFunc)(struct xml_ctx *ctx, int tag_closed); - void *userData; -}; - -struct remote_ls_ctx -{ - struct alt_base *repo; - char *path; - void (*userFunc)(struct remote_ls_ctx *ls); - void *userData; - int flags; - char *dentry_name; - int dentry_flags; - int rc; - struct remote_ls_ctx *parent; -}; -#endif - static struct object_request *object_queue_head; static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, @@ -714,204 +661,6 @@ static void fetch_alternates(const char *base) free(url); } -#ifndef NO_EXPAT -static void -xml_start_tag(void *userData, const char *name, const char **atts) -{ - struct xml_ctx *ctx = (struct xml_ctx *)userData; - const char *c = strchr(name, ':'); - int new_len; - - if (c == NULL) - c = name; - else - c++; - - new_len = strlen(ctx->name) + strlen(c) + 2; - - if (new_len > ctx->len) { - ctx->name = xrealloc(ctx->name, new_len); - ctx->len = new_len; - } - strcat(ctx->name, "."); - strcat(ctx->name, c); - - free(ctx->cdata); - ctx->cdata = NULL; - - ctx->userFunc(ctx, 0); -} - -static void -xml_end_tag(void *userData, const char *name) -{ - struct xml_ctx *ctx = (struct xml_ctx *)userData; - const char *c = strchr(name, ':'); - char *ep; - - ctx->userFunc(ctx, 1); - - if (c == NULL) - c = name; - else - c++; - - ep = ctx->name + strlen(ctx->name) - strlen(c) - 1; - *ep = 0; -} - -static void -xml_cdata(void *userData, const XML_Char *s, int len) -{ - struct xml_ctx *ctx = (struct xml_ctx *)userData; - free(ctx->cdata); - ctx->cdata = xmalloc(len + 1); - strlcpy(ctx->cdata, s, len + 1); -} - -static int remote_ls(struct alt_base *repo, const char *path, int flags, - void (*userFunc)(struct remote_ls_ctx *ls), - void *userData); - -static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) -{ - struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData; - - if (tag_closed) { - if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) { - if (ls->dentry_flags & IS_DIR) { - if (ls->flags & PROCESS_DIRS) { - ls->userFunc(ls); - } - if (strcmp(ls->dentry_name, ls->path) && - ls->flags & RECURSIVE) { - ls->rc = remote_ls(ls->repo, - ls->dentry_name, - ls->flags, - ls->userFunc, - ls->userData); - } - } else if (ls->flags & PROCESS_FILES) { - ls->userFunc(ls); - } - } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) { - ls->dentry_name = xmalloc(strlen(ctx->cdata) - - ls->repo->path_len + 1); - strcpy(ls->dentry_name, ctx->cdata + ls->repo->path_len); - } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) { - ls->dentry_flags |= IS_DIR; - } - } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - free(ls->dentry_name); - ls->dentry_name = NULL; - ls->dentry_flags = 0; - } -} - -static int remote_ls(struct alt_base *repo, const char *path, int flags, - void (*userFunc)(struct remote_ls_ctx *ls), - void *userData) -{ - char *url = xmalloc(strlen(repo->base) + strlen(path) + 1); - struct active_request_slot *slot; - struct slot_results results; - struct buffer in_buffer; - struct buffer out_buffer; - char *in_data; - char *out_data; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; - struct curl_slist *dav_headers = NULL; - struct xml_ctx ctx; - struct remote_ls_ctx ls; - - ls.flags = flags; - ls.repo = repo; - ls.path = xstrdup(path); - ls.dentry_name = NULL; - ls.dentry_flags = 0; - ls.userData = userData; - ls.userFunc = userFunc; - ls.rc = 0; - - sprintf(url, "%s%s", repo->base, path); - - out_buffer.size = strlen(PROPFIND_ALL_REQUEST); - out_data = xmalloc(out_buffer.size + 1); - snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST); - out_buffer.posn = 0; - out_buffer.buffer = out_data; - - in_buffer.size = 4096; - in_data = xmalloc(in_buffer.size); - in_buffer.posn = 0; - in_buffer.buffer = in_data; - - dav_headers = curl_slist_append(dav_headers, "Depth: 1"); - dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); - - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result == CURLE_OK) { - ctx.name = xcalloc(10, 1); - ctx.len = 0; - ctx.cdata = NULL; - ctx.userFunc = handle_remote_ls_ctx; - ctx.userData = &ls; - XML_SetUserData(parser, &ctx); - XML_SetElementHandler(parser, xml_start_tag, - xml_end_tag); - XML_SetCharacterDataHandler(parser, xml_cdata); - result = XML_Parse(parser, in_buffer.buffer, - in_buffer.posn, 1); - free(ctx.name); - - if (result != XML_STATUS_OK) { - ls.rc = error("XML error: %s", - XML_ErrorString( - XML_GetErrorCode(parser))); - } - } else { - ls.rc = -1; - } - } else { - ls.rc = error("Unable to start PROPFIND request"); - } - - free(ls.path); - free(url); - free(out_data); - free(in_buffer.buffer); - curl_slist_free_all(dav_headers); - - return ls.rc; -} - -static void process_ls_pack(struct remote_ls_ctx *ls) -{ - unsigned char sha1[20]; - - if (strlen(ls->dentry_name) == 63 && - !strncmp(ls->dentry_name, "objects/pack/pack-", 18) && - has_extension(ls->dentry_name, ".pack")) { - get_sha1_hex(ls->dentry_name + 18, sha1); - setup_index(ls->repo, sha1); - } -} -#endif - static int fetch_indices(struct alt_base *repo) { unsigned char sha1[20]; @@ -934,12 +683,6 @@ static int fetch_indices(struct alt_base *repo) if (get_verbosely) fprintf(stderr, "Getting pack list for %s\n", repo->base); -#ifndef NO_EXPAT - if (remote_ls(repo, "objects/pack/", PROCESS_FILES, - process_ls_pack, NULL) == 0) - return 0; -#endif - url = xmalloc(strlen(repo->base) + 21); sprintf(url, "%s/objects/info/packs", repo->base); -- cgit v0.10.2-6-g49f6 From 83936a29e275bc0c04f60d3333e4951a9e16b1fc Mon Sep 17 00:00:00 2001 From: Sasha Khapyorsky Date: Sun, 8 Oct 2006 23:31:18 +0200 Subject: git-svnimport.perl: copying directory from original SVN place When copying whole directory, if source directory is not in already imported tree, try to get it from original SVN location. This happens when source directory is not matched by provided 'trunk' and/or 'tags/branches' templates or when it is not part of specified SVN sub-project. Signed-off-by: Sasha Khapyorsky Signed-off-by: Junio C Hamano diff --git a/git-svnimport.perl b/git-svnimport.perl index 988514e..4ae0eec 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -193,6 +193,13 @@ sub ignore { } } +sub dir_list { + my($self,$path,$rev) = @_; + my ($dirents,undef,$properties) + = $self->{'svn'}->get_dir($path,$rev,undef); + return $dirents; +} + package main; use URI; @@ -342,35 +349,16 @@ if ($opt_A) { open BRANCHES,">>", "$git_dir/svn2git"; -sub node_kind($$$) { - my ($branch, $path, $revision) = @_; +sub node_kind($$) { + my ($svnpath, $revision) = @_; my $pool=SVN::Pool->new; - my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool); + my $kind = $svn->{'svn'}->check_path($svnpath,$revision,$pool); $pool->clear; return $kind; } -sub revert_split_path($$) { - my($branch,$path) = @_; - - my $svnpath; - $path = "" if $path eq "/"; # this should not happen, but ... - if($branch eq "/") { - $svnpath = "$trunk_name/$path"; - } elsif($branch =~ m#^/#) { - $svnpath = "$tag_name$branch/$path"; - } else { - $svnpath = "$branch_name/$branch/$path"; - } - - $svnpath =~ s#/+$##; - return $svnpath; -} - sub get_file($$$) { - my($rev,$branch,$path) = @_; - - my $svnpath = revert_split_path($branch,$path); + my($svnpath,$rev,$path) = @_; # now get it my ($name,$mode); @@ -413,10 +401,9 @@ sub get_file($$$) { } sub get_ignore($$$$$) { - my($new,$old,$rev,$branch,$path) = @_; + my($new,$old,$rev,$path,$svnpath) = @_; return unless $opt_I; - my $svnpath = revert_split_path($branch,$path); my $name = $svn->ignore("$svnpath",$rev); if ($path eq '/') { $path = $opt_I; @@ -435,7 +422,7 @@ sub get_ignore($$$$$) { close $F; unlink $name; push(@$new,['0644',$sha,$path]); - } else { + } elsif (defined $old) { push(@$old,$path); } } @@ -480,6 +467,27 @@ sub branch_rev($$) { return $therev; } +sub expand_svndir($$$); + +sub expand_svndir($$$) +{ + my ($svnpath, $rev, $path) = @_; + my @list; + get_ignore(\@list, undef, $rev, $path, $svnpath); + my $dirents = $svn->dir_list($svnpath, $rev); + foreach my $p(keys %$dirents) { + my $kind = node_kind($svnpath.'/'.$p, $rev); + if ($kind eq $SVN::Node::file) { + my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p); + push(@list, $f) if $f; + } elsif ($kind eq $SVN::Node::dir) { + push(@list, + expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p)); + } + } + return @list; +} + sub copy_path($$$$$$$$) { # Somebody copied a whole subdirectory. # We need to find the index entries from the old version which the @@ -488,8 +496,11 @@ sub copy_path($$$$$$$$) { my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_; my($srcbranch,$srcpath) = split_path($rev,$oldpath); - unless(defined $srcbranch) { - print "Path not found when copying from $oldpath @ $rev\n"; + unless(defined $srcbranch && defined $srcpath) { + print "Path not found when copying from $oldpath @ $rev.\n". + "Will try to copy from original SVN location...\n" + if $opt_v; + push (@$new, expand_svndir($oldpath, $rev, $path)); return; } my $therev = branch_rev($srcbranch, $rev); @@ -503,7 +514,7 @@ sub copy_path($$$$$$$$) { } print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v; if ($node_kind eq $SVN::Node::dir) { - $srcpath =~ s#/*$#/#; + $srcpath =~ s#/*$#/#; } my $pid = open my $f,'-|'; @@ -582,10 +593,12 @@ sub commit { if(defined $oldpath) { my $p; ($parent,$p) = split_path($revision,$oldpath); - if($parent eq "/") { - $parent = $opt_o; - } else { - $parent =~ s#^/##; # if it's a tag + if(defined $parent) { + if($parent eq "/") { + $parent = $opt_o; + } else { + $parent =~ s#^/##; # if it's a tag + } } } else { $parent = undef; @@ -651,9 +664,10 @@ sub commit { push(@old,$path); # remove any old stuff } if(($action->[0] eq "A") || ($action->[0] eq "R")) { - my $node_kind = node_kind($branch,$path,$revision); + my $node_kind = node_kind($action->[3], $revision); if ($node_kind eq $SVN::Node::file) { - my $f = get_file($revision,$branch,$path); + my $f = get_file($action->[3], + $revision, $path); if ($f) { push(@new,$f) if $f; } else { @@ -668,19 +682,20 @@ sub commit { \@new, \@parents); } else { get_ignore(\@new, \@old, $revision, - $branch, $path); + $path, $action->[3]); } } } elsif ($action->[0] eq "D") { push(@old,$path); } elsif ($action->[0] eq "M") { - my $node_kind = node_kind($branch,$path,$revision); + my $node_kind = node_kind($action->[3], $revision); if ($node_kind eq $SVN::Node::file) { - my $f = get_file($revision,$branch,$path); + my $f = get_file($action->[3], + $revision, $path); push(@new,$f) if $f; } elsif ($node_kind eq $SVN::Node::dir) { get_ignore(\@new, \@old, $revision, - $branch,$path); + $path, $action->[3]); } } else { die "$revision: unknown action '".$action->[0]."' for $path\n"; -- cgit v0.10.2-6-g49f6 From 96779be48a8e5d3a50a07e0fcab942e7066235a9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 9 Oct 2006 19:19:45 -0700 Subject: Fix git-revert Defaulting to $replay for the sake of fixing cherry-pick was not done conditionally, which broke git-revert. Noticed by Luben. Signed-off-by: Junio C Hamano diff --git a/git-revert.sh b/git-revert.sh index 0784f74..4fd81b6 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -7,9 +7,11 @@ case "$0" in *-revert* ) test -t 0 && edit=-e + replay= me=revert USAGE='[--edit | --no-edit] [-n] ' ;; *-cherry-pick* ) + replay=t edit= me=cherry-pick USAGE='[--edit] [-n] [-r] [-x] ' ;; @@ -18,7 +20,7 @@ case "$0" in esac . git-sh-setup -no_commit= replay=t +no_commit= while case "$#" in 0) break ;; esac do case "$1" in -- cgit v0.10.2-6-g49f6 From 4e27fb06f0fe4206606172f94a18e08d7d87c070 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 6 Oct 2006 15:39:09 -0400 Subject: add commit count options to git-shortlog This patch does 3 things: 1) Output the number of commits along with the name for each author (nice to know for long lists spending more than a screen worth of commit lines). 2) Provide a switch (-n) to sort authors according to their number of commits instead of author alphabetic order. 3) Provide a switch (-s) to supress commit lines and only keep a summary of authors and the number of commits for each of them. And for good measure a short usage is displayed with -h. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 7486ebe..1601d22 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -7,16 +7,29 @@ git-shortlog - Summarize 'git log' output SYNOPSIS -------- -git-log --pretty=short | 'git-shortlog' +git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] DESCRIPTION ----------- Summarizes 'git log' output in a format suitable for inclusion -in release announcements. Each commit will be grouped by author +in release announcements. Each commit will be grouped by author and the first line of the commit message will be shown. Additionally, "[PATCH]" will be stripped from the commit description. +OPTIONS +------- + +-h:: + Print a short usage message and exit. + +-n:: + Sort output according to the number of commits per author instead + of author alphabetic order. + +-s: + Supress commit description and Provide a commit count summary only. + FILES ----- '.mailmap':: diff --git a/git-shortlog.perl b/git-shortlog.perl index 0b14f83..334fec7 100755 --- a/git-shortlog.perl +++ b/git-shortlog.perl @@ -1,6 +1,18 @@ #!/usr/bin/perl -w use strict; +use Getopt::Std; +use File::Basename qw(basename dirname); + +our ($opt_h, $opt_n, $opt_s); +getopts('hns'); + +$opt_h && usage(); + +sub usage { + print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < \n"; + exit(1); +} my (%mailmap); my (%email); @@ -38,16 +50,38 @@ sub by_name($$) { uc($a) cmp uc($b); } +sub by_nbentries($$) { + my ($a, $b) = @_; + my $a_entries = $map{$a}; + my $b_entries = $map{$b}; + + @$b_entries - @$a_entries || by_name $a, $b; +} + +my $sort_method = $opt_n ? \&by_nbentries : \&by_name; + +sub summary_output { + my ($obj, $num, $key); + + foreach $key (sort $sort_method keys %map) { + $obj = $map{$key}; + $num = @$obj; + printf "%s: %u\n", $key, $num; + $n_output += $num; + } +} sub shortlog_output { - my ($obj, $key, $desc); + my ($obj, $num, $key, $desc); + + foreach $key (sort $sort_method keys %map) { + $obj = $map{$key}; + $num = @$obj; - foreach $key (sort by_name keys %map) { # output author - printf "%s:\n", $key; + printf "%s (%u):\n", $key, $num; # output author's 1-line summaries - $obj = $map{$key}; foreach $desc (reverse @$obj) { print " $desc\n"; $n_output++; @@ -152,7 +186,7 @@ sub finalize { &setup_mailmap; &changelog_input; -&shortlog_output; +$opt_s ? &summary_output : &shortlog_output; &finalize; exit(0); -- cgit v0.10.2-6-g49f6 From f789e347465dded7fcec3a605473fa3f549792d8 Mon Sep 17 00:00:00 2001 From: Ryan Anderson Date: Mon, 9 Oct 2006 03:32:05 -0700 Subject: Remove git-annotate.perl and create a builtin-alias for git-blame Signed-off-by: Ryan Anderson Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 2c7c338..7e62e76 100644 --- a/Makefile +++ b/Makefile @@ -173,7 +173,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ git-shortlog.perl git-rerere.perl \ - git-annotate.perl git-cvsserver.perl \ + git-cvsserver.perl \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl @@ -265,6 +265,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-add.o \ + builtin-annotate.o \ builtin-apply.o \ builtin-archive.o \ builtin-cat-file.o \ diff --git a/builtin-annotate.c b/builtin-annotate.c new file mode 100644 index 0000000..25ad473 --- /dev/null +++ b/builtin-annotate.c @@ -0,0 +1,25 @@ +/* + * "git annotate" builtin alias + * + * Copyright (C) 2006 Ryan Anderson + */ +#include "git-compat-util.h" +#include "exec_cmd.h" + +int cmd_annotate(int argc, const char **argv, const char *prefix) +{ + const char **nargv; + int i; + nargv = xmalloc(sizeof(char *) * (argc + 2)); + + nargv[0] = "blame"; + nargv[1] = "-c"; + + for (i = 1; i < argc; i++) { + nargv[i+1] = argv[i]; + } + nargv[argc + 1] = NULL; + + return execv_git_cmd(nargv); +} + diff --git a/builtin.h b/builtin.h index f9fa9ff..2c5d900 100644 --- a/builtin.h +++ b/builtin.h @@ -13,6 +13,7 @@ extern void stripspace(FILE *in, FILE *out); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); +extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); extern int cmd_archive(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); diff --git a/git-annotate.perl b/git-annotate.perl deleted file mode 100755 index 215ed26..0000000 --- a/git-annotate.perl +++ /dev/null @@ -1,708 +0,0 @@ -#!/usr/bin/perl -# Copyright 2006, Ryan Anderson -# -# GPL v2 (See COPYING) -# -# This file is licensed under the GPL v2, or a later version -# at the discretion of Linus Torvalds. - -use warnings; -use strict; -use Getopt::Long; -use POSIX qw(strftime gmtime); -use File::Basename qw(basename dirname); - -sub usage() { - print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] - -l, --long - Show long rev (Defaults off) - -t, --time - Show raw timestamp (Defaults off) - -r, --rename - Follow renames (Defaults on). - -S, --rev-file revs-file - Use revs from revs-file instead of calling git-rev-list - -h, --help - This message. -"; - - exit(1); -} - -our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1); - -my $rc = GetOptions( "long|l" => \$longrev, - "time|t" => \$rawtime, - "help|h" => \$help, - "rename|r" => \$rename, - "rev-file|S=s" => \$rev_file); -if (!$rc or $help or !@ARGV) { - usage(); -} - -my $filename = shift @ARGV; -if (@ARGV) { - $starting_rev = shift @ARGV; -} - -my @stack = ( - { - 'rev' => defined $starting_rev ? $starting_rev : "HEAD", - 'filename' => $filename, - }, -); - -our @filelines = (); - -if (defined $starting_rev) { - @filelines = git_cat_file($starting_rev, $filename); -} else { - open(F,"<",$filename) - or die "Failed to open filename: $!"; - - while() { - chomp; - push @filelines, $_; - } - close(F); - -} - -our %revs; -our @revqueue; -our $head; - -my $revsprocessed = 0; -while (my $bound = pop @stack) { - my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'}); - foreach my $revinst (@revisions) { - my ($rev, @parents) = @$revinst; - $head ||= $rev; - - if (!defined($rev)) { - $rev = ""; - } - $revs{$rev}{'filename'} = $bound->{'filename'}; - if (scalar @parents > 0) { - $revs{$rev}{'parents'} = \@parents; - next; - } - - if (!$rename) { - next; - } - - my $newbound = find_parent_renames($rev, $bound->{'filename'}); - if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) { - push @stack, $newbound; - $revs{$rev}{'parents'} = [$newbound->{'rev'}]; - } - } -} -push @revqueue, $head; -init_claim( defined $starting_rev ? $head : 'dirty'); -unless (defined $starting_rev) { - my $diff = open_pipe("git","diff","HEAD", "--",$filename) - or die "Failed to call git diff to check for dirty state: $!"; - - _git_diff_parse($diff, [$head], "dirty", ( - 'author' => gitvar_name("GIT_AUTHOR_IDENT"), - 'author_date' => sprintf("%s +0000",time()), - ) - ); - close($diff); -} -handle_rev(); - - -my $i = 0; -foreach my $l (@filelines) { - my ($output, $rev, $committer, $date); - if (ref $l eq 'ARRAY') { - ($output, $rev, $committer, $date) = @$l; - if (!$longrev && length($rev) > 8) { - $rev = substr($rev,0,8); - } - } else { - $output = $l; - ($rev, $committer, $date) = ('unknown', 'unknown', 'unknown'); - } - - printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer, - format_date($date), ++$i, $output); -} - -sub init_claim { - my ($rev) = @_; - for (my $i = 0; $i < @filelines; $i++) { - $filelines[$i] = [ $filelines[$i], '', '', '', 1]; - # line, - # rev, - # author, - # date, - # 1 <-- belongs to the original file. - } - $revs{$rev}{'lines'} = \@filelines; -} - - -sub handle_rev { - my $revseen = 0; - my %seen; - while (my $rev = shift @revqueue) { - next if $seen{$rev}++; - - my %revinfo = git_commit_info($rev); - - if (exists $revs{$rev}{parents} && - scalar @{$revs{$rev}{parents}} != 0) { - - git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo); - push @revqueue, @{$revs{$rev}{'parents'}}; - - } else { - # We must be at the initial rev here, so claim everything that is left. - for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) { - if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') { - claim_line($i, $rev, $revs{$rev}{lines}, %revinfo); - } - } - } - } -} - - -sub git_rev_list { - my ($rev, $file) = @_; - - my $revlist; - if ($rev_file) { - open($revlist, '<' . $rev_file) - or die "Failed to open $rev_file : $!"; - } else { - $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file) - or die "Failed to exec git-rev-list: $!"; - } - - my @revs; - while(my $line = <$revlist>) { - chomp $line; - my ($rev, @parents) = split /\s+/, $line; - push @revs, [ $rev, @parents ]; - } - close($revlist); - - printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0); - return @revs; -} - -sub find_parent_renames { - my ($rev, $file) = @_; - - my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev") - or die "Failed to exec git-diff: $!"; - - local $/ = "\0"; - my %bound; - my $junk = <$patch>; - while (my $change = <$patch>) { - chomp $change; - my $filename = <$patch>; - if (!defined $filename) { - next; - } - chomp $filename; - - if ($change =~ m/^[AMD]$/ ) { - next; - } elsif ($change =~ m/^R/ ) { - my $oldfilename = $filename; - $filename = <$patch>; - chomp $filename; - if ( $file eq $filename ) { - my $parent = git_find_parent($rev, $oldfilename); - @bound{'rev','filename'} = ($parent, $oldfilename); - last; - } - } - } - close($patch); - - return \%bound; -} - - -sub git_find_parent { - my ($rev, $filename) = @_; - - my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename) - or die "Failed to open git-rev-list to find a single parent: $!"; - - my $parentline = <$revparent>; - chomp $parentline; - my ($revfound,$parent) = split m/\s+/, $parentline; - - close($revparent); - - return $parent; -} - -sub git_find_all_parents { - my ($rev) = @_; - - my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev") - or die "Failed to open git-rev-list to find a single parent: $!"; - - my $parentline = <$revparent>; - chomp $parentline; - my ($origrev, @parents) = split m/\s+/, $parentline; - - close($revparent); - - return @parents; -} - -sub git_merge_base { - my ($rev1, $rev2) = @_; - - my $mb = open_pipe("git-merge-base", $rev1, $rev2) - or die "Failed to open git-merge-base: $!"; - - my $base = <$mb>; - chomp $base; - - close($mb); - - return $base; -} - -# Construct a set of pseudo parents that are in the same order, -# and the same quantity as the real parents, -# but whose SHA1s are as similar to the logical parents -# as possible. -sub get_pseudo_parents { - my ($all, $fake) = @_; - - my @all = @$all; - my @fake = @$fake; - - my @pseudo; - - my %fake = map {$_ => 1} @fake; - my %seenfake; - - my $fakeidx = 0; - foreach my $p (@all) { - if (exists $fake{$p}) { - if ($fake[$fakeidx] ne $p) { - die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n", - $fake[$fakeidx], $p, - join(", ", @all), - join(", ", @fake), - ); - } - - push @pseudo, $p; - $fakeidx++; - $seenfake{$p}++; - - } else { - my $base = git_merge_base($fake[$fakeidx], $p); - if ($base ne $fake[$fakeidx]) { - die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n", - $fake[$fakeidx], $p, $base); - } - - # The details of how we parse the diffs - # mean that we cannot have a duplicate - # revision in the list, so if we've already - # seen the revision we would normally add, just use - # the actual revision. - if ($seenfake{$base}) { - push @pseudo, $p; - } else { - push @pseudo, $base; - $seenfake{$base}++; - } - } - } - - return @pseudo; -} - - -# Get a diff between the current revision and a parent. -# Record the commit information that results. -sub git_diff_parse { - my ($parents, $rev, %revinfo) = @_; - - my @pseudo_parents; - my @command = ("git-diff-tree"); - my $revision_spec; - - if (scalar @$parents == 1) { - - $revision_spec = join("..", $parents->[0], $rev); - @pseudo_parents = @$parents; - } else { - my @all_parents = git_find_all_parents($rev); - - if (@all_parents != @$parents) { - @pseudo_parents = get_pseudo_parents(\@all_parents, $parents); - } else { - @pseudo_parents = @$parents; - } - - $revision_spec = $rev; - push @command, "-c"; - } - - my @filenames = ( $revs{$rev}{'filename'} ); - - foreach my $parent (@$parents) { - push @filenames, $revs{$parent}{'filename'}; - } - - push @command, "-p", "-M", $revision_spec, "--", @filenames; - - - my $diff = open_pipe( @command ) - or die "Failed to call git-diff for annotation: $!"; - - _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo); - - close($diff); -} - -sub _git_diff_parse { - my ($diff, $parents, $rev, %revinfo) = @_; - - my $ri = 0; - - my $slines = $revs{$rev}{'lines'}; - my (%plines, %pi); - - my $gotheader = 0; - my ($remstart); - my $parent_count = @$parents; - - my $diff_header_regexp = "^@"; - $diff_header_regexp .= "@" x @$parents; - $diff_header_regexp .= ' -\d+,\d+' x @$parents; - $diff_header_regexp .= ' \+(\d+),\d+'; - $diff_header_regexp .= " " . ("@" x @$parents); - - my %claim_regexps; - my $allparentplus = '^' . '\\+' x @$parents . '(.*)$'; - - { - my $i = 0; - foreach my $parent (@$parents) { - - $pi{$parent} = 0; - my $r = '^' . '.' x @$parents . '(.*)$'; - my $p = $r; - substr($p,$i+1, 1) = '\\+'; - - my $m = $r; - substr($m,$i+1, 1) = '-'; - - $claim_regexps{$parent}{plus} = $p; - $claim_regexps{$parent}{minus} = $m; - - $plines{$parent} = []; - - $i++; - } - } - - DIFF: - while(<$diff>) { - chomp; - #printf("%d:%s:\n", $gotheader, $_); - if (m/$diff_header_regexp/) { - $remstart = $1 - 1; - # (0-based arrays) - - $gotheader = 1; - - foreach my $parent (@$parents) { - for (my $i = $ri; $i < $remstart; $i++) { - $plines{$parent}[$pi{$parent}++] = $slines->[$i]; - } - } - $ri = $remstart; - - next DIFF; - - } elsif (!$gotheader) { - # Skip over the leadin. - next DIFF; - } - - if (m/^\\/) { - ; - # Skip \No newline at end of file. - # But this can be internationalized, so only look - # for an initial \ - - } else { - my %claims = (); - my $negclaim = 0; - my $allclaimed = 0; - my $line; - - if (m/$allparentplus/) { - claim_line($ri, $rev, $slines, %revinfo); - $allclaimed = 1; - - } - - PARENT: - foreach my $parent (keys %claim_regexps) { - my $m = $claim_regexps{$parent}{minus}; - my $p = $claim_regexps{$parent}{plus}; - - if (m/$m/) { - $line = $1; - $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ]; - $negclaim++; - - } elsif (m/$p/) { - $line = $1; - if (get_line($slines, $ri) eq $line) { - # Found a match, claim - $claims{$parent}++; - - } else { - die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n", - $ri, $line, - get_line($slines, $ri), - $rev, $parent); - } - } - } - - if (%claims) { - foreach my $parent (@$parents) { - next if $claims{$parent} || $allclaimed; - $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; - #[ $line, '', '', '', 0 ]; - } - $ri++; - - } elsif ($negclaim) { - next DIFF; - - } else { - if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) { - foreach my $parent (@$parents) { - printf("parent %s is on line %d\n", $parent, $pi{$parent}); - } - - my @context; - for (my $i = -2; $i < 2; $i++) { - push @context, get_line($slines, $ri + $i); - } - my $context = join("\n", @context); - - my $justline = substr($_, scalar @$parents); - die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n", - $ri, - $justline, - $context); - } - foreach my $parent (@$parents) { - $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; - } - $ri++; - } - } - } - - for (my $i = $ri; $i < @{$slines} ; $i++) { - foreach my $parent (@$parents) { - push @{$plines{$parent}}, $slines->[$ri]; - } - $ri++; - } - - foreach my $parent (@$parents) { - $revs{$parent}{lines} = $plines{$parent}; - } - - return; -} - -sub get_line { - my ($lines, $index) = @_; - - return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index]; -} - -sub git_cat_file { - my ($rev, $filename) = @_; - return () unless defined $rev && defined $filename; - - my $blob = git_ls_tree($rev, $filename); - die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob; - - my $catfile = open_pipe("git","cat-file", "blob", $blob) - or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!; - - my @lines; - while(<$catfile>) { - chomp; - push @lines, $_; - } - close($catfile); - - return @lines; -} - -sub git_ls_tree { - my ($rev, $filename) = @_; - - my $lstree = open_pipe("git","ls-tree",$rev,$filename) - or die "Failed to call git ls-tree: $!"; - - my ($mode, $type, $blob, $tfilename); - while(<$lstree>) { - chomp; - ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4); - last if ($tfilename eq $filename); - } - close($lstree); - - return $blob if ($tfilename eq $filename); - die "git-ls-tree failed to find blob for $filename"; - -} - - - -sub claim_line { - my ($floffset, $rev, $lines, %revinfo) = @_; - my $oline = get_line($lines, $floffset); - @{$lines->[$floffset]} = ( $oline, $rev, - $revinfo{'author'}, $revinfo{'author_date'} ); - #printf("Claiming line %d with rev %s: '%s'\n", - # $floffset, $rev, $oline) if 1; -} - -sub git_commit_info { - my ($rev) = @_; - my $commit = open_pipe("git-cat-file", "commit", $rev) - or die "Failed to call git-cat-file: $!"; - - my %info; - while(<$commit>) { - chomp; - last if (length $_ == 0); - - if (m/^author (.*) <(.*)> (.*)$/) { - $info{'author'} = $1; - $info{'author_email'} = $2; - $info{'author_date'} = $3; - } elsif (m/^committer (.*) <(.*)> (.*)$/) { - $info{'committer'} = $1; - $info{'committer_email'} = $2; - $info{'committer_date'} = $3; - } - } - close($commit); - - return %info; -} - -sub format_date { - if ($rawtime) { - return $_[0]; - } - my ($timestamp, $timezone) = split(' ', $_[0]); - my $minutes = abs($timezone); - $minutes = int($minutes / 100) * 60 + ($minutes % 100); - if ($timezone < 0) { - $minutes = -$minutes; - } - my $t = $timestamp + $minutes * 60; - return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t)); -} - -# Copied from git-send-email.perl - We need a Git.pm module.. -sub gitvar { - my ($var) = @_; - my $fh; - my $pid = open($fh, '-|'); - die "$!" unless defined $pid; - if (!$pid) { - exec('git-var', $var) or die "$!"; - } - my ($val) = <$fh>; - close $fh or die "$!"; - chomp($val); - return $val; -} - -sub gitvar_name { - my ($name) = @_; - my $val = gitvar($name); - my @field = split(/\s+/, $val); - return join(' ', @field[0...(@field-4)]); -} - -sub open_pipe { - if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') { - return open_pipe_activestate(@_); - } else { - return open_pipe_normal(@_); - } -} - -sub open_pipe_activestate { - tie *fh, "Git::ActiveStatePipe", @_; - return *fh; -} - -sub open_pipe_normal { - my (@execlist) = @_; - - my $pid = open my $kid, "-|"; - defined $pid or die "Cannot fork: $!"; - - unless ($pid) { - exec @execlist; - die "Cannot exec @execlist: $!"; - } - - return $kid; -} - -package Git::ActiveStatePipe; -use strict; - -sub TIEHANDLE { - my ($class, @params) = @_; - my $cmdline = join " ", @params; - my @data = qx{$cmdline}; - bless { i => 0, data => \@data }, $class; -} - -sub READLINE { - my $self = shift; - if ($self->{i} >= scalar @{$self->{data}}) { - return undef; - } - return $self->{'data'}->[ $self->{i}++ ]; -} - -sub CLOSE { - my $self = shift; - delete $self->{data}; - delete $self->{i}; -} - -sub EOF { - my $self = shift; - return ($self->{i} >= scalar @{$self->{data}}); -} diff --git a/git.c b/git.c index b8e8622..32c356e 100644 --- a/git.c +++ b/git.c @@ -219,6 +219,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int option; } commands[] = { { "add", cmd_add, RUN_SETUP }, + { "annotate", cmd_annotate, }, { "apply", cmd_apply }, { "archive", cmd_archive }, { "cat-file", cmd_cat_file, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From 1974bf620b436b014bfe86179ff76485610a4887 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 9 Oct 2006 21:15:59 -0700 Subject: core.logallrefupdates thinko-fix diff --git a/refs.c b/refs.c index 75a0d7b..3d100df 100644 --- a/refs.c +++ b/refs.c @@ -731,7 +731,7 @@ static int log_ref_write(struct ref_lock *lock, logfd = open(lock->log_file, oflags, 0666); if (logfd < 0) { - if (!log_all_ref_updates && errno == ENOENT) + if (!(oflags & O_CREAT) && errno == ENOENT) return 0; return error("Unable to append to %s: %s", lock->log_file, strerror(errno)); -- cgit v0.10.2-6-g49f6 From 63fba759bc1d9405f362e73918096815bf8e2a15 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 10 Oct 2006 01:06:20 -0700 Subject: pack-objects: document --delta-base-offset option Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index d4661dd..5788709 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -9,7 +9,7 @@ git-pack-objects - Create a packed archive of objects SYNOPSIS -------- [verse] -'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty] +'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list @@ -110,6 +110,17 @@ base-name:: This flag tells the command not to reuse existing deltas but compute them from scratch. +--delta-base-offset:: + A packed archive can express base object of a delta as + either 20-byte object name or as an offset in the + stream, but older version of git does not understand the + latter. By default, git-pack-objects only uses the + former format for better compatibility. This option + allows the command to use the latter format for + compactness. Depending on the average delta chain + length, this option typically shrinks the resulting + packfile by 3-5 per-cent. + Author ------ diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index ee5f031..41e1e74 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -15,7 +15,7 @@ #include #include -static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] Date: Tue, 10 Oct 2006 21:16:25 +0200 Subject: paginate git-diff by default diff --git a/git.c b/git.c index d7103a4..e089b53 100644 --- a/git.c +++ b/git.c @@ -226,7 +226,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "check-ref-format", cmd_check_ref_format }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "count-objects", cmd_count_objects, RUN_SETUP }, - { "diff", cmd_diff, RUN_SETUP }, + { "diff", cmd_diff, RUN_SETUP | USE_PAGER }, { "diff-files", cmd_diff_files, RUN_SETUP }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-stages", cmd_diff_stages, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From 9861718b305a672f3d17936ea6a6cbd9d9a70059 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 10 Oct 2006 22:29:02 -0700 Subject: git-fetch --update-head-ok typofix Martin Waitz noticed that one of the case arms had an impossible choice. It turns out that what it was checking was redundant and the typo did not have any effect. Signed-off-by: Junio C Hamano diff --git a/git-fetch.sh b/git-fetch.sh index f1522bd..79222fb 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -436,10 +436,10 @@ esac # If the original head was empty (i.e. no "master" yet), or # if we were told not to worry, we do not have to check. -case ",$update_head_ok,$orig_head," in -*,, | t,* ) +case "$orig_head" in +'') ;; -*) +?*) curr_head=$(git-rev-parse --verify HEAD 2>/dev/null) if test "$curr_head" != "$orig_head" then -- cgit v0.10.2-6-g49f6 From a057f806674f691f1640d33df14de8de8a3d4c87 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 10 Oct 2006 23:00:29 -0700 Subject: git-pull: we say commit X, not X commit. Signed-off-by: Junio C Hamano diff --git a/git-pull.sh b/git-pull.sh index f380437..ed04e7d 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -58,7 +58,7 @@ then echo >&2 "Warning: fetch updated the current branch head." echo >&2 "Warning: fast forwarding your working tree from" - echo >&2 "Warning: $orig_head commit." + echo >&2 "Warning: commit $orig_head." git-update-index --refresh 2>/dev/null git-read-tree -u -m "$orig_head" "$curr_head" || die 'Cannot fast-forward your working tree. -- cgit v0.10.2-6-g49f6 From 0503f9c178c36a19e1f8e8930b367db0f58ce5ca Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 11 Oct 2006 07:57:17 +0000 Subject: git.spec.in: perl subpackage is installed in perl_vendorlib not vendorarch Signed-off-by: Junio C Hamano diff --git a/git.spec.in b/git.spec.in index 6d90034..9b1217a 100644 --- a/git.spec.in +++ b/git.spec.in @@ -97,7 +97,7 @@ find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';' find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';' (find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files -(find $RPM_BUILD_ROOT%{perl_vendorarch} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files +(find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files %if %{!?_without_docs:1}0 (find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files %else -- cgit v0.10.2-6-g49f6 From d15c55aa052434aee75c73da4ed771af3e3b6f61 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Wed, 11 Oct 2006 00:30:05 -0700 Subject: gitweb: blame porcelain: lineno and orig lineno swapped Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 68347ac..41d0477 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2533,7 +2533,7 @@ HTML while (1) { $_ = <$fd>; last unless defined $_; - my ($full_rev, $lineno, $orig_lineno, $group_size) = + my ($full_rev, $orig_lineno, $lineno, $group_size) = /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/; if (!exists $metainfo{$full_rev}) { $metainfo{$full_rev} = {}; -- cgit v0.10.2-6-g49f6 From 6130259c30695d8a6ceda414a7d0d14183491e82 Mon Sep 17 00:00:00 2001 From: Matthew Wilcox Date: Tue, 10 Oct 2006 08:58:23 -0600 Subject: Add --dry-run option to git-send-email Add a --dry-run option to git-send-email due to having made too many mistakes with it in the past week. I like having a safety catch on my machine gun. Signed-off-by: Matthew @ilcox Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index 3f50aba..04c8942 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -87,7 +87,8 @@ my (@to,@cc,@initial_cc,@bcclist, $initial_reply_to,$initial_subject,@files,$from,$compose,$time); # Behavior modification variables -my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0); +my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc, + $dry_run) = (1, 0, 0, 0, 0); my $smtp_server; # Example reply to: @@ -116,6 +117,7 @@ my $rc = GetOptions("from=s" => \$from, "quiet" => \$quiet, "suppress-from" => \$suppress_from, "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc, + "dry-run" => \$dry_run, ); # Verify the user input @@ -423,7 +425,9 @@ X-Mailer: git-send-email $gitversion $header .= "References: $references\n"; } - if ($smtp_server =~ m#^/#) { + if ($dry_run) { + # We don't want to send the email. + } elsif ($smtp_server =~ m#^/#) { my $pid = open my $sm, '|-'; defined $pid or die $!; if (!$pid) { -- cgit v0.10.2-6-g49f6 From 9ac13ec941933c32849c2284b5d79ef608023a56 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 11 Oct 2006 11:49:15 -0400 Subject: atomic write for sideband remote messages It has been a few times that I ended up with such a confusing display: |remote: Generating pack... |remote: Done counting 17 objects. |remote: Result has 9 objects. |remote: Deltifying 9 objects. |remote: 100% (9/9) done |remote: Unpacking 9 objects |Total 9, written 9 (delta 8), reused 0 (delta 0) | 100% (9/9) done The confusion can be avoided in most cases by writing the remote message in one go to prevent interleacing with local messages. The buffer declaration has been moved inside recv_sideband() to avoid extra string copies. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/builtin-archive.c b/builtin-archive.c index 6dabdee..9177379 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -75,7 +75,7 @@ static int run_remote_archiver(const char *remote, int argc, die("git-archive: expected a flush"); /* Now, start reading from fd[0] and spit it out to stdout */ - rv = recv_sideband("archive", fd[0], 1, 2, buf, sizeof(buf)); + rv = recv_sideband("archive", fd[0], 1, 2); close(fd[0]); rv |= finish_connect(pid); diff --git a/fetch-clone.c b/fetch-clone.c index b632ca0..76b99af 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -115,12 +115,10 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) die("%s: unable to fork off sideband demultiplexer", me); if (!side_pid) { /* subprocess */ - char buf[LARGE_PACKET_MAX]; - close(fd[0]); if (xd[0] != xd[1]) close(xd[1]); - if (recv_sideband(me, xd[0], fd[1], 2, buf, sizeof(buf))) + if (recv_sideband(me, xd[0], fd[1], 2)) exit(1); exit(0); } diff --git a/sideband.c b/sideband.c index 1b14ff8..277fa3c 100644 --- a/sideband.c +++ b/sideband.c @@ -11,10 +11,13 @@ * stream, aka "verbose"). A message over band #3 is a signal that * the remote died unexpectedly. A flush() concludes the stream. */ -int recv_sideband(const char *me, int in_stream, int out, int err, char *buf, int bufsz) +int recv_sideband(const char *me, int in_stream, int out, int err) { + char buf[7 + LARGE_PACKET_MAX + 1]; + strcpy(buf, "remote:"); while (1) { - int len = packet_read_line(in_stream, buf, bufsz); + int band, len; + len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX); if (len == 0) break; if (len < 1) { @@ -22,25 +25,26 @@ int recv_sideband(const char *me, int in_stream, int out, int err, char *buf, in safe_write(err, buf, len); return SIDEBAND_PROTOCOL_ERROR; } + band = buf[7] & 0xff; len--; - switch (buf[0] & 0xFF) { + switch (band) { case 3: - safe_write(err, "remote: ", 8); - safe_write(err, buf+1, len); - safe_write(err, "\n", 1); + buf[7] = ' '; + buf[8+len] = '\n'; + safe_write(err, buf, 8+len+1); return SIDEBAND_REMOTE_ERROR; case 2: - safe_write(err, "remote: ", 8); - safe_write(err, buf+1, len); + buf[7] = ' '; + safe_write(err, buf, 8+len); continue; case 1: - safe_write(out, buf+1, len); + safe_write(out, buf+8, len); continue; default: - len = sprintf(buf + 1, + len = sprintf(buf, "%s: protocol error: bad band #%d\n", - me, buf[0] & 0xFF); - safe_write(err, buf+1, len); + me, band); + safe_write(err, buf, len); return SIDEBAND_PROTOCOL_ERROR; } } diff --git a/sideband.h b/sideband.h index 4872106..a84b691 100644 --- a/sideband.h +++ b/sideband.h @@ -7,7 +7,7 @@ #define DEFAULT_PACKET_MAX 1000 #define LARGE_PACKET_MAX 65520 -int recv_sideband(const char *me, int in_stream, int out, int err, char *, int); +int recv_sideband(const char *me, int in_stream, int out, int err); ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); #endif -- cgit v0.10.2-6-g49f6 From c35b96e7851e417a24c1e255e353c67b31955466 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 11 Oct 2006 11:53:21 -0700 Subject: git-svn: multi-init saves and reuses --tags and --branches arguments This should make it much easier to track newly added tags and branches. Re-running multi-init without command-line arguments should now detect new-tags and branches. --trunk shouldn't change often, but running multi-init on it is now idempotent. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index f5c7d46..5a6c87e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -682,12 +682,17 @@ sub multi_init { } $_trunk = $url . $_trunk; } + my $ch_id; if ($GIT_SVN eq 'git-svn') { - print "GIT_SVN_ID set to 'trunk' for $_trunk\n"; + $ch_id = 1; $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk'; } init_vars(); - init($_trunk); + unless (-d $GIT_SVN_DIR) { + print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id; + init($_trunk); + sys('git-repo-config', 'svn.trunk', $_trunk); + } complete_url_ls_init($url, $_branches, '--branches/-b', ''); complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/'); } @@ -937,16 +942,21 @@ sub complete_url_ls_init { print STDERR "W: Unrecognized URL: $u\n"; die "This should never happen\n"; } + # don't try to init already existing refs my $id = $pfx.$1; - print "init $u => $id\n"; $GIT_SVN = $ENV{GIT_SVN_ID} = $id; init_vars(); - init($u); + unless (-d $GIT_SVN_DIR) { + print "init $u => $id\n"; + init($u); + } } exit 0; } waitpid $pid, 0; croak $? if $?; + my ($n) = ($switch =~ /^--(\w+)/); + sys('git-repo-config', "svn.$n", $var); } sub common_prefix { -- cgit v0.10.2-6-g49f6 From 74a31a100ab96c37ebec5c04e1822bb2681bc345 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 11 Oct 2006 11:53:22 -0700 Subject: git-svn: log command fixes Change the --verbose flag to more closely match svn. I was somehow under the impression that --summary included --raw diff output, but I was wrong. We now pass -r --raw --name-status as arguments if passed -v/--verbose. -r (recursive) is passed by default, since users usually want it, and accepting it causes difficulty with the -r option used by svn users. A --non-recursive switch has been added to disable this. Of course, --summary, --raw, -p and any other git-log options can still be passed directly (without --name-status). Also, several warnings about referencing undefined variables have been fixed. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 5a6c87e..4f6e6a3 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -52,7 +52,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, - $_merge, $_strategy, $_dry_run, $_ignore_nodate); + $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -114,6 +114,7 @@ my %cmd = ( 'incremental' => \$_incremental, 'oneline' => \$_oneline, 'show-commit' => \$_show_commit, + 'non-recursive' => \$_non_recursive, 'authors-file|A=s' => \$_authors, } ], 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', @@ -752,13 +753,18 @@ sub show_log { # ignore } elsif (/^:\d{6} \d{6} $sha1_short/o) { push @{$c->{raw}}, $_; + } elsif (/^[ACRMDT]\t/) { + # we could add $SVN_PATH here, but that requires + # remote access at the moment (repo_path_split)... + s#^([ACRMDT])\t# $1 #; + push @{$c->{changed}}, $_; } elsif (/^diff /) { $d = 1; push @{$c->{diff}}, $_; } elsif ($d) { push @{$c->{diff}}, $_; } elsif (/^ (git-svn-id:.+)$/) { - (undef, $c->{r}, undef) = extract_metadata($1); + ($c->{url}, $c->{r}, undef) = extract_metadata($1); } elsif (s/^ //) { push @{$c->{l}}, $_; } @@ -850,7 +856,8 @@ sub git_svn_log_cmd { my ($r_min, $r_max) = @_; my @cmd = (qw/git-log --abbrev-commit --pretty=raw --default/, "refs/remotes/$GIT_SVN"); - push @cmd, '--summary' if $_verbose; + push @cmd, '-r' unless $_non_recursive; + push @cmd, qw/--raw --name-status/ if $_verbose; return @cmd unless defined $r_max; if ($r_max == $r_min) { push @cmd, '--max-count=1'; @@ -861,7 +868,7 @@ sub git_svn_log_cmd { my ($c_min, $c_max); $c_max = revdb_get($REVDB, $r_max); $c_min = revdb_get($REVDB, $r_min); - if ($c_min && $c_max) { + if (defined $c_min && defined $c_max) { if ($r_max > $r_max) { push @cmd, "$c_min..$c_max"; } else { @@ -2561,6 +2568,12 @@ sub show_commit { } } +sub show_commit_changed_paths { + my ($c) = @_; + return unless $c->{changed}; + print "Changed paths:\n", @{$c->{changed}}; +} + sub show_commit_normal { my ($c) = @_; print '-' x72, "\nr$c->{r} | "; @@ -2570,7 +2583,8 @@ sub show_commit_normal { my $nr_line = 0; if (my $l = $c->{l}) { - while ($l->[$#$l] eq "\n" && $l->[($#$l - 1)] eq "\n") { + while ($l->[$#$l] eq "\n" && $#$l > 0 + && $l->[($#$l - 1)] eq "\n") { pop @$l; } $nr_line = scalar @$l; @@ -2582,11 +2596,15 @@ sub show_commit_normal { } else { $nr_line .= ' lines'; } - print $nr_line, "\n\n"; + print $nr_line, "\n"; + show_commit_changed_paths($c); + print "\n"; print $_ foreach @$l; } } else { - print "1 line\n\n"; + print "1 line\n"; + show_commit_changed_paths($c); + print "\n"; } foreach my $x (qw/raw diff/) { -- cgit v0.10.2-6-g49f6 From 34c06118ede69b37b76fcf71564f0f24234c5752 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 11 Oct 2006 22:31:15 +0200 Subject: gitweb: Fix search form when PATH_INFO is enabled Currently that was broken. Ideal fix would make the search form use PATH_INFO too, but it's just one insignificant place so it's no big deal if we don't for now... This at least makes it work. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1d5217e..0a7acfd 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -408,6 +408,9 @@ sub href(%) { my %params = @_; my $href = $my_uri; + # XXX: Warning: If you touch this, check the search form for updating, + # too. + my @mapping = ( project => "p", action => "a", @@ -1442,6 +1445,7 @@ EOF } $cgi->param("a", "search"); $cgi->param("h", $search_hash); + $cgi->param("p", $project); print $cgi->startform(-method => "get", -action => $my_uri) . "
\n" . $cgi->hidden(-name => "p") . "\n" . -- cgit v0.10.2-6-g49f6 From e8f5d9081cecff026913702dff8254d03e8f08b8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 11 Oct 2006 14:53:35 -0700 Subject: Documentation/git-svn: document some of the newer features I've forgotten to document many of the features added along the way in the manpages. This fills in some holes in the documentation and adds updates some outdated information. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 1cfa3e3..450ff1f 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -37,7 +37,9 @@ COMMANDS 'init':: Creates an empty git repository with additional metadata directories for git-svn. The Subversion URL must be specified - as a command-line argument. + as a command-line argument. Optionally, the target directory + to operate on can be specified as a second argument. Normally + this command initializes the current directory. 'fetch':: @@ -63,7 +65,30 @@ manually joining branches on commit. This is advantageous over 'commit' (below) because it produces cleaner, more linear history. +'log':: + This should make it easy to look up svn log messages when svn + users refer to -r/--revision numbers. + + The following features from `svn log' are supported: + + --revision=[:] - is supported, non-numeric args are not: + HEAD, NEXT, BASE, PREV, etc ... + -v/--verbose - it's not completely compatible with + the --verbose output in svn log, but + reasonably close. + --limit= - is NOT the same as --max-count, + doesn't count merged/excluded commits + --incremental - supported + + New features: + + --show-commit - shows the git commit sha1, as well + --oneline - our version of --pretty=oneline + + Any other arguments are passed directly to `git log' + 'commit':: + You should consider using 'dcommit' instead of this command. Commit specified commit or tree objects to SVN. This relies on your imported fetch data being up-to-date. This makes absolutely no attempts to do patching when committing to SVN, it @@ -86,12 +111,49 @@ manually joining branches on commit. directories. The output is suitable for appending to the $GIT_DIR/info/exclude file. +'commit-diff':: + Commits the diff of two tree-ish arguments from the + command-line. This command is intended for interopability with + git-svnimport and does not rely on being inside an git-svn + init-ed repository. This command takes three arguments, (a) the + original tree to diff against, (b) the new tree result, (c) the + URL of the target Subversion repository. The final argument + (URL) may be omitted if you are working from a git-svn-aware + repository (that has been init-ed with git-svn). + +'graft-branches':: + This command attempts to detect merges/branches from already + imported history. Techniques used currently include regexes, + file copies, and tree-matches). This command generates (or + modifies) the $GIT_DIR/info/grafts file. This command is + considered experimental, and inherently flawed because + merge-tracking in SVN is inherently flawed and inconsistent + across different repositories. + +'multi-init':: + This command supports git-svnimport-like command-line syntax for + importing repositories that are layed out as recommended by the + SVN folks. This is a bit more tolerant than the git-svnimport + command-line syntax and doesn't require the user to figure out + where the repository URL ends and where the repository path + begins. + +'multi-fetch':: + This runs fetch on all known SVN branches we're tracking. This + will NOT discover new branches (unlike git-svnimport), so + multi-init will need to be re-run (it's idempotent). + -- OPTIONS ------- -- +--shared:: +--template=:: + Only used with the 'init' command. + These are passed directly to gitlink:git-init-db[1]. + -r :: --revision :: @@ -115,7 +177,7 @@ git-rev-list --pretty=oneline output can be used. --rmdir:: -Only used with the 'commit' command. +Only used with the 'dcommit', 'commit' and 'commit-diff' commands. Remove directories from the SVN tree if there are no files left behind. SVN can version empty directories, and they are not @@ -128,7 +190,7 @@ repo-config key: svn.rmdir -e:: --edit:: -Only used with the 'commit' command. +Only used with the 'dcommit', 'commit' and 'commit-diff' commands. Edit the commit message before committing to SVN. This is off by default for objects that are commits, and forced on when committing @@ -139,7 +201,7 @@ repo-config key: svn.edit -l:: --find-copies-harder:: -Both of these are only used with the 'commit' command. +Only used with the 'dcommit', 'commit' and 'commit-diff' commands. They are both passed directly to git-diff-tree see gitlink:git-diff-tree[1] for more information. @@ -164,7 +226,26 @@ will abort operation. The user will then have to add the appropriate entry. Re-running the previous git-svn command after the authors-file is modified should continue operation. -repo-config key: svn.authors-file +repo-config key: svn.authorsfile + +-q:: +--quiet:: + Make git-svn less verbose. This only affects git-svn if you + have the SVN::* libraries installed and are using them. + +--repack[=]:: +--repack-flags= + These should help keep disk usage sane for large fetches + with many revisions. + + --repack takes an optional argument for the number of revisions + to fetch before repacking. This defaults to repacking every + 1000 commits fetched if no argument is specified. + + --repack-flags are passed directly to gitlink:git-repack[1]. + +repo-config key: svn.repack +repo-config key: svn.repackflags -m:: --merge:: @@ -215,6 +296,28 @@ section on '<>' for more information on using GIT_SVN_ID. +--follow-parent:: + This is especially helpful when we're tracking a directory + that has been moved around within the repository, or if we + started tracking a branch and never tracked the trunk it was + descended from. + + This relies on the SVN::* libraries to work. + +repo-config key: svn.followparent + +--no-metadata:: + This gets rid of the git-svn-id: lines at the end of every commit. + + With this, you lose the ability to use the rebuild command. If + you ever lose your .git/svn/git-svn/.rev_db file, you won't be + able to fetch again, either. This is fine for one-shot imports. + + The 'git-svn log' command will not work on repositories using this, + either. + +repo-config key: svn.nometadata + -- COMPATIBILITY OPTIONS @@ -231,6 +334,9 @@ for tracking the remote. --no-ignore-externals:: Only used with the 'fetch' and 'rebuild' command. +This command has no effect when you are using the SVN::* +libraries with git, svn:externals are always avoided. + By default, git-svn passes --ignore-externals to svn to avoid fetching svn:external trees into git. Pass this flag to enable externals tracking directly via git. @@ -264,7 +370,7 @@ Basic Examples Tracking and contributing to an Subversion managed-project: ------------------------------------------------------------------------ -# Initialize a tree (like git init-db): +# Initialize a repo (like git init-db): git-svn init http://svn.foo.org/project/trunk # Fetch remote revisions: git-svn fetch @@ -312,8 +418,8 @@ branches or directories in a Subversion repository, git-svn has a simple hack to allow it to track an arbitrary number of related _or_ unrelated SVN repositories via one git repository. Simply set the GIT_SVN_ID environment variable to a name other other than "git-svn" (the default) -and git-svn will ignore the contents of the $GIT_DIR/git-svn directory -and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that +and git-svn will ignore the contents of the $GIT_DIR/svn/git-svn directory +and instead do all of its work in $GIT_DIR/svn/$GIT_SVN_ID for that invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified by the user outside of git-svn commands. @@ -341,6 +447,9 @@ This allows you to tie unfetched SVN revision 375 to your current HEAD: Advanced Example: Tracking a Reorganized Repository ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Note: this example is now obsolete if you have SVN::* libraries +installed. Simply use --follow-parent when fetching. + If you're tracking a directory that has moved, or otherwise been branched or tagged off of another directory in the repository and you care about the full history of the project, then you can read this @@ -371,20 +480,18 @@ he needed to continue tracking /ufoai/trunk where /trunk left off. BUGS ---- -If somebody commits a conflicting changeset to SVN at a bad moment -(right before you commit) causing a conflict and your commit to fail, -your svn working tree ($GIT_DIR/git-svn/tree) may be dirtied. The -easiest thing to do is probably just to rm -rf $GIT_DIR/git-svn/tree and -run 'rebuild'. + +If you are not using the SVN::* Perl libraries and somebody commits a +conflicting changeset to SVN at a bad moment (right before you commit) +causing a conflict and your commit to fail, your svn working tree +($GIT_DIR/git-svn/tree) may be dirtied. The easiest thing to do is +probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'. We ignore all SVN properties except svn:executable. Too difficult to map them since we rely heavily on git write-tree being _exactly_ the same on both the SVN and git working trees and I prefer not to clutter working trees with metadata files. -svn:keywords can't be ignored in Subversion (at least I don't know of -a way to ignore them). - Renamed and copied directories are not detected by git and hence not tracked when committing to SVN. I do not plan on adding support for this as it's quite difficult and time-consuming to get working for all -- cgit v0.10.2-6-g49f6 From b203b769f2f382307f21b4a021ce08f9a4cfa94c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 11 Oct 2006 14:53:36 -0700 Subject: git-svn: -h(elp) message formatting fixes 'graft-branches' is slightly longer than the rest of the commands, so the text was squished together in the formatted output. This patch just adds some more whitespace to make the text look more pleasant. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 4f6e6a3..84d2c58 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -169,11 +169,11 @@ Usage: $0 [options] [arguments]\n foreach (sort keys %cmd) { next if $cmd && $cmd ne $_; - print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n"; + print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n"; foreach (keys %{$cmd{$_}->[2]}) { # prints out arguments as they should be passed: my $x = s#[:=]s$## ? '' : s#[:=]i$## ? '' : ''; - print $fd ' ' x 17, join(', ', map { length $_ > 1 ? + print $fd ' ' x 21, join(', ', map { length $_ > 1 ? "--$_" : "-$_" } split /\|/,$_)," $x\n"; } -- cgit v0.10.2-6-g49f6 From 14763e7bda06a502455ef4420067205797b9a907 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 11 Oct 2006 16:16:02 -0700 Subject: commit: fix a segfault when displaying a commit with unreachable parents I was running git show on various commits found by fsck-objects when I found this bug. Since find_unique_abbrev() cannot find an abbreviation for an object not in the database, it will return NULL, which is bad to run strlen() on. So instead, we'll just display the unabbreviated sha1 that we referenced in the commit. I'm not sure that this is the best 'fix' for it because the commit I was trying to show was broken, but I don't think a program should segfault even if the user tries to do something stupid. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/commit.c b/commit.c index 5b6e082..a6d543e 100644 --- a/commit.c +++ b/commit.c @@ -548,10 +548,13 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com while (parent) { struct commit *p = parent->item; - const char *hex = abbrev - ? find_unique_abbrev(p->object.sha1, abbrev) - : sha1_to_hex(p->object.sha1); - const char *dots = (abbrev && strlen(hex) != 40) ? "..." : ""; + const char *hex = NULL; + const char *dots; + if (abbrev) + hex = find_unique_abbrev(p->object.sha1, abbrev); + if (!hex) + hex = sha1_to_hex(p->object.sha1); + dots = (abbrev && strlen(hex) != 40) ? "..." : ""; parent = parent->next; offset += sprintf(buf + offset, " %s%s", hex, dots); -- cgit v0.10.2-6-g49f6 From 83e9940a5ef79cedc36a24a4a89d097abf1b28c7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 11 Oct 2006 18:19:55 -0700 Subject: git-svn: add a message encouraging use of SVN::* libraries I'm using svn 1.4.0-4 in Debian unstable and apparently there's a regression on the SVN side that prevents a symlink from becoming a regular file (which git supports, of course). It's not a noticeable regression for most people, but this broke the full-svn-tests target in t/Makefile for me. The SVN::* Perl libraries seem to have matured and improved over the past year, and git-svn has supported them for several months now, so with that I encourage all users to start using the SVN::* Perl libraries with git-svn. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 84d2c58..a128d90 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -40,8 +40,22 @@ memoize('cmt_metadata'); memoize('get_commit_time'); my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib); + +sub nag_lib { + print STDERR < Date: Thu, 12 Oct 2006 00:44:27 -0700 Subject: blame: Document and add help text for -f, -n, and -p New options --show-name, --show-number and --porcelain were not documented. Also add -p as a short-hand for --porcelain for consistency. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index e1f8944..9891c1d 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -7,7 +7,7 @@ git-blame - Show what revision and author last modified each line of a file SYNOPSIS -------- -'git-blame' [-c] [-l] [-t] [-S ] [--] [] +'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-S ] [--] [] DESCRIPTION ----------- @@ -45,10 +45,47 @@ OPTIONS -S, --rev-file :: Use revs from revs-file instead of calling gitlink:git-rev-list[1]. +-f, --show-name:: + Show filename in the original commit. By default + filename is shown if there is any line that came from a + file with different name, due to rename detection. + +-n, --show-number:: + Show line number in the original commit (Default: off). + +-p, --porcelain:: + Show in a format designed for machine consumption. + -h, --help:: Show help message. +THE PORCELAIN FORMAT +-------------------- + +In this format, each line is output after a header; the +header at the minumum has the first line which has: + +- 40-byte SHA-1 of the commit the line is attributed to; +- the line number of the line in the original file; +- the line number of the line in the final file; +- on a line that starts a group of line from a different + commit than the previous one, the number of lines in this + group. On subsequent lines this field is absent. + +This header line is followed by the following information +at least once for each commit: + +- author name ("author"), email ("author-mail"), time + ("author-time"), and timezone ("author-tz"); similarly + for committer. +- filename in the commit the line is attributed to. +- the first line of the commit log message ("summary"). + +The contents of the actual line is output after the above +header, prefixed by a TAB. This is to allow adding more +header elements later. + SEE ALSO -------- gitlink:git-annotate[1] diff --git a/blame.c b/blame.c index 314f3e2..e664813 100644 --- a/blame.c +++ b/blame.c @@ -22,11 +22,14 @@ #define DEBUG 0 static const char blame_usage[] = -"git-blame [-c] [-l] [-t] [-S ] [--] file [commit]\n" +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S ] [--] file [commit]\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" " -t, --time Show raw timestamp (Default: off)\n" -" -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n" +" -f, --show-name Show original filename (Default: auto)\n" +" -n, --show-number Show original linenumber (Default: off)\n" +" -p, --porcelain Show in a format designed for machine consumption\n" +" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n" " -h, --help This message"; static struct commit **blame_lines; @@ -951,7 +954,8 @@ int main(int argc, const char **argv) show_number = 1; continue; } - if (!strcmp(argv[i], "--porcelain")) { + if (!strcmp(argv[i], "-p") || + !strcmp(argv[i], "--porcelain")) { porcelain = 1; sha1_len = 40; show_raw_time = 1; -- cgit v0.10.2-6-g49f6 From 48fd688ab0ba7d41ff4286aee20f6f0b86e4c41c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 12 Oct 2006 00:47:03 -0700 Subject: gitweb: spell "blame --porcelain" with -p Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 41d0477..34b88ce 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2504,7 +2504,7 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '--porcelain', '--', + open ($fd, "-|", git_cmd(), "blame", '-p', '--', $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); -- cgit v0.10.2-6-g49f6 From 854de5a5341be4183c401157b9e5593d9a925f4f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 12 Oct 2006 02:57:39 -0700 Subject: apply --numstat -z: line termination fix. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index de5f855..e3ef044 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2112,7 +2112,7 @@ static void numstat_patch_list(struct patch *patch) quote_c_style(name, NULL, stdout, 0); else fputs(name, stdout); - putchar('\n'); + putchar(line_termination); } } -- cgit v0.10.2-6-g49f6 From 2344d47fba45bdddb0801ebf0789935a96e44352 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 12 Oct 2006 14:22:14 +0200 Subject: diff: fix 2 whitespace issues When whitespace or whitespace change was ignored, the function xdl_recmatch() returned memcmp() style differences, which is wrong, since it should return 0 on non-match. Also, there were three horrible off-by-one bugs, even leading to wrong hashes in the whitespace special handling. The issue was noticed by Ray Lehtiniemi. For good measure, this commit adds a test. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh new file mode 100755 index 0000000..c945085 --- /dev/null +++ b/t/t4015-diff-whitespace.sh @@ -0,0 +1,122 @@ +#!/bin/sh +# +# Copyright (c) 2006 Johannes E. Schindelin +# + +test_description='Test special whitespace in diff engine. + +' +. ./test-lib.sh +. ../diff-lib.sh + +# Ray Lehtiniemi's example + +cat << EOF > x +do { + nothing; +} while (0); +EOF + +git-update-index --add x + +cat << EOF > x +do +{ + nothing; +} +while (0); +EOF + +cat << EOF > expect +diff --git a/x b/x +index adf3937..6edc172 100644 +--- a/x ++++ b/x +@@ -1,3 +1,5 @@ +-do { ++do ++{ + nothing; +-} while (0); ++} ++while (0); +EOF + +git-diff > out +test_expect_success "Ray's example without options" 'diff -u expect out' + +git-diff -w > out +test_expect_success "Ray's example with -w" 'diff -u expect out' + +git-diff -b > out +test_expect_success "Ray's example with -b" 'diff -u expect out' + +cat << EOF > x +whitespace at beginning +whitespace change +whitespace in the middle +whitespace at end +unchanged line +CR at end +EOF + +git-update-index x + +cat << EOF > x + whitespace at beginning +whitespace change +white space in the middle +whitespace at end +unchanged line +CR at end +EOF + +cat << EOF > expect +diff --git a/x b/x +index d99af23..8b32fb5 100644 +--- a/x ++++ b/x +@@ -1,6 +1,6 @@ +-whitespace at beginning +-whitespace change +-whitespace in the middle +-whitespace at end ++ whitespace at beginning ++whitespace change ++white space in the middle ++whitespace at end + unchanged line +-CR at end ++CR at end +EOF +git-diff > out +test_expect_success 'another test, without options' 'diff -u expect out' + +cat << EOF > expect +diff --git a/x b/x +index d99af23..8b32fb5 100644 +EOF +git-diff -w > out +test_expect_success 'another test, with -w' 'diff -u expect out' + +cat << EOF > expect +diff --git a/x b/x +index d99af23..8b32fb5 100644 +--- a/x ++++ b/x +@@ -1,6 +1,6 @@ +-whitespace at beginning ++ whitespace at beginning + whitespace change +-whitespace in the middle +-whitespace at end ++white space in the middle ++whitespace at end + unchanged line +-CR at end ++CR at end +EOF +git-diff -b > out +test_expect_success 'another test, with -b' 'diff -u expect out' + +test_done diff --git a/xdiff/xutils.c b/xdiff/xutils.c index f7bdd39..9e4bb47 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -191,36 +191,30 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) int i1, i2; if (flags & XDF_IGNORE_WHITESPACE) { - for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) { + for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) { if (isspace(l1[i1])) while (isspace(l1[i1]) && i1 < s1) i1++; - else if (isspace(l2[i2])) + if (isspace(l2[i2])) while (isspace(l2[i2]) && i2 < s2) i2++; - else if (l1[i1] != l2[i2]) - return l2[i2] - l1[i1]; + if (i1 < s1 && i2 < s2 && l1[i1++] != l2[i2++]) + return 0; } - if (i1 >= s1) - return 1; - else if (i2 >= s2) - return -1; + return (i1 >= s1 && i2 >= s2); } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { - for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) { + for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) { if (isspace(l1[i1])) { if (!isspace(l2[i2])) - return -1; + return 0; while (isspace(l1[i1]) && i1 < s1) i1++; while (isspace(l2[i2]) && i2 < s2) i2++; - } else if (l1[i1] != l2[i2]) - return l2[i2] - l1[i1]; + } else if (l1[i1++] != l2[i2++]) + return 0; } - if (i1 >= s1) - return 1; - else if (i2 >= s2) - return -1; + return (i1 >= s1 && i2 >= s2); } else return s1 == s2 && !memcmp(l1, l2, s1); @@ -233,7 +227,8 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) { for (; ptr < top && *ptr != '\n'; ptr++) { if (isspace(*ptr) && (flags & XDF_WHITESPACE_FLAGS)) { - while (ptr < top && isspace(*ptr) && ptr[1] != '\n') + while (ptr + 1 < top && isspace(ptr[1]) + && ptr[1] != '\n') ptr++; if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { ha += (ha << 5); -- cgit v0.10.2-6-g49f6 From 23bed43d0c5190b6da89f3f34d8badcc1b3c304b Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 12 Oct 2006 18:26:34 +0200 Subject: Documentation: add missing second colons and remove a typo It takes two colons to mark text as item label. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index 7e1f894..c2485c6 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -34,7 +34,7 @@ OPTIONS Report the list of objects being walked locally and the list of objects successfully sent to the remote repository. -...: +...:: The remote refs to update. diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 9e67f17..5376f68 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -43,7 +43,7 @@ OPTIONS :: The repository to update. -...: +...:: The remote refs to update. diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 1601d22..d54fc3e 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -27,8 +27,8 @@ OPTIONS Sort output according to the number of commits per author instead of author alphabetic order. --s: - Supress commit description and Provide a commit count summary only. +-s:: + Supress commit description and provide a commit count summary only. FILES ----- diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt index 14449ca..7e560b0 100644 --- a/Documentation/glossary.txt +++ b/Documentation/glossary.txt @@ -179,7 +179,7 @@ object name:: character hexadecimal encoding of the hash of the object (possibly followed by a white space). -object type: +object type:: One of the identifiers "commit","tree","tag" and "blob" describing the type of an object. @@ -324,7 +324,7 @@ tag:: A tag is most typically used to mark a particular point in the commit ancestry chain. -unmerged index: +unmerged index:: An index which contains unmerged index entries. working tree:: -- cgit v0.10.2-6-g49f6 From 4035b46e12ee6e5c9339e263cd16c2e2422b8928 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 13 Oct 2006 14:20:27 -0700 Subject: t4015: work-around here document problem on Cygwin. Signed-off-by: Junio C Hamano diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index c945085..1bc5b7a 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -51,13 +51,13 @@ test_expect_success "Ray's example with -w" 'diff -u expect out' git-diff -b > out test_expect_success "Ray's example with -b" 'diff -u expect out' -cat << EOF > x +tr 'Q' '\015' << EOF > x whitespace at beginning whitespace change whitespace in the middle whitespace at end unchanged line -CR at end +CR at endQ EOF git-update-index x @@ -71,7 +71,7 @@ unchanged line CR at end EOF -cat << EOF > expect +tr 'Q' '\015' << EOF > expect diff --git a/x b/x index d99af23..8b32fb5 100644 --- a/x @@ -86,7 +86,7 @@ index d99af23..8b32fb5 100644 +white space in the middle +whitespace at end unchanged line --CR at end +-CR at endQ +CR at end EOF git-diff > out @@ -99,7 +99,7 @@ EOF git-diff -w > out test_expect_success 'another test, with -w' 'diff -u expect out' -cat << EOF > expect +tr 'Q' '\015' << EOF > expect diff --git a/x b/x index d99af23..8b32fb5 100644 --- a/x @@ -113,7 +113,7 @@ index d99af23..8b32fb5 100644 +white space in the middle +whitespace at end unchanged line --CR at end +-CR at endQ +CR at end EOF git-diff -b > out -- cgit v0.10.2-6-g49f6 From b6945f570acef7603c58d2aec4a84dce006fbd65 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 13 Oct 2006 21:28:58 -0700 Subject: git-repack: repo.usedeltabaseoffset When configuration variable `repack.UseDeltaBaseOffset` is set for the repository, the command passes `--delta-base-offset` option to `git-pack-objects`; this typically results in slightly smaller packs, but the generated packs are incompatible with versions of git older than (and including) v1.4.3. We will make it default to true sometime in the future, but not for a while. Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 98c1f3e..88e0bf0 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -224,6 +224,10 @@ pull.octopus:: pull.twohead:: The default merge strategy to use when pulling a single branch. +repack.usedeltabaseoffset:: + Allow gitlink:git-repack[1] to create packs that uses + delta-base offset. Defaults to false. + show.difftree:: The default gitlink:git-diff-tree[1] arguments to be used for gitlink:git-show[1]. diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 49f7e0a..4e6631a 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -66,6 +66,20 @@ OPTIONS to be applied that many times to get to the necessary object. +Configuration +------------- + +When configuration variable `repack.UseDeltaBaseOffset` is set +for the repository, the command passes `--delta-base-offset` +option to `git-pack-objects`; this typically results in slightly +smaller packs, but the generated packs are incompatible with +versions of git older than (and including) v1.4.3; do not set +the variable in a repository that older version of git needs to +be able to read (this includes repositories from which packs can +be copied out over http or rsync, and people who obtained packs +that way can try to use older git with it). + + Author ------ Written by Linus Torvalds diff --git a/git-repack.sh b/git-repack.sh index b525fc5..2a21489 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Linus Torvalds # -USAGE='[-a] [-d] [-f] [-l] [-n] [-q]' +USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]' . git-sh-setup no_update_info= all_into_one= remove_redundant= @@ -24,6 +24,15 @@ do shift done +# Later we will default repack.UseDeltaBaseOffset to true +default_dbo=false + +case "`git repo-config --bool repack.usedeltabaseoffset || + echo $default_dbo`" in +true) + extra="$extra --delta-base-offset" ;; +esac + PACKDIR="$GIT_OBJECT_DIRECTORY/pack" PACKTMP="$GIT_DIR/.tmp-$$-pack" rm -f "$PACKTMP"-* -- cgit v0.10.2-6-g49f6 From 74e2abe5b70aadf06984472ba9aa9a29900040e6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 12 Oct 2006 03:01:00 -0700 Subject: diff --numstat [jc: with documentation from Jakub] Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 7b7b9e8..e112172 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -16,6 +16,11 @@ The width of the filename part can be controlled by giving another width to it separated by a comma. +--numstat:: + Similar to \--stat, but shows number of added and + deleted lines in decimal notation and pathname without + abbreviation, to make it more machine friendly. + --summary:: Output a condensed summary of extended header information such as creations, renames and mode changes. diff --git a/combine-diff.c b/combine-diff.c index 46d9121..65c7868 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -856,8 +856,10 @@ void diff_tree_combined(const unsigned char *sha1, /* show stat against the first parent even * when doing combined diff. */ - if (i == 0 && opt->output_format & DIFF_FORMAT_DIFFSTAT) - diffopts.output_format = DIFF_FORMAT_DIFFSTAT; + int stat_opt = (opt->output_format & + (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT)); + if (i == 0 && stat_opt) + diffopts.output_format = stat_opt; else diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_tree_sha1(parent[i], sha1, "", &diffopts); @@ -887,7 +889,8 @@ void diff_tree_combined(const unsigned char *sha1, } needsep = 1; } - else if (opt->output_format & DIFF_FORMAT_DIFFSTAT) + else if (opt->output_format & + (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT)) needsep = 1; if (opt->output_format & DIFF_FORMAT_PATCH) { if (needsep) diff --git a/diff.c b/diff.c index fb82432..2dcad19 100644 --- a/diff.c +++ b/diff.c @@ -795,6 +795,23 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) set, total_files, adds, dels, reset); } +static void show_numstat(struct diffstat_t* data, struct diff_options *options) +{ + int i; + + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + + printf("%d\t%d\t", file->added, file->deleted); + if (options->line_termination && + quote_c_style(file->name, NULL, NULL, 0)) + quote_c_style(file->name, NULL, stdout, 0); + else + fputs(file->name, stdout); + putchar(options->line_termination); + } +} + struct checkdiff_t { struct xdiff_emit_state xm; const char *filename; @@ -1731,6 +1748,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_CHECKDIFF | DIFF_FORMAT_NO_OUTPUT)) options->output_format &= ~(DIFF_FORMAT_RAW | + DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH); @@ -1740,6 +1758,7 @@ int diff_setup_done(struct diff_options *options) * recursive bits for other formats here. */ if (options->output_format & (DIFF_FORMAT_PATCH | + DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_CHECKDIFF)) options->recursive = 1; @@ -1828,6 +1847,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--patch-with-raw")) { options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW; } + else if (!strcmp(arg, "--numstat")) { + options->output_format |= DIFF_FORMAT_NUMSTAT; + } else if (!strncmp(arg, "--stat", 6)) { char *end; int width = options->stat_width; @@ -2602,7 +2624,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & DIFF_FORMAT_DIFFSTAT) { + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -2612,7 +2634,10 @@ void diff_flush(struct diff_options *options) if (check_pair_status(p)) diff_flush_stat(p, options, &diffstat); } - show_stats(&diffstat, options); + if (output_format & DIFF_FORMAT_NUMSTAT) + show_numstat(&diffstat, options); + if (output_format & DIFF_FORMAT_DIFFSTAT) + show_stats(&diffstat, options); separator++; } diff --git a/diff.h b/diff.h index b48c991..ce3058e 100644 --- a/diff.h +++ b/diff.h @@ -26,20 +26,21 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_FORMAT_RAW 0x0001 #define DIFF_FORMAT_DIFFSTAT 0x0002 -#define DIFF_FORMAT_SUMMARY 0x0004 -#define DIFF_FORMAT_PATCH 0x0008 +#define DIFF_FORMAT_NUMSTAT 0x0004 +#define DIFF_FORMAT_SUMMARY 0x0008 +#define DIFF_FORMAT_PATCH 0x0010 /* These override all above */ -#define DIFF_FORMAT_NAME 0x0010 -#define DIFF_FORMAT_NAME_STATUS 0x0020 -#define DIFF_FORMAT_CHECKDIFF 0x0040 +#define DIFF_FORMAT_NAME 0x0100 +#define DIFF_FORMAT_NAME_STATUS 0x0200 +#define DIFF_FORMAT_CHECKDIFF 0x0400 /* Same as output_format = 0 but we know that -s flag was given * and we should not give default value to output_format. */ -#define DIFF_FORMAT_NO_OUTPUT 0x0080 +#define DIFF_FORMAT_NO_OUTPUT 0x0800 -#define DIFF_FORMAT_CALLBACK 0x0100 +#define DIFF_FORMAT_CALLBACK 0x1000 struct diff_options { const char *filter; @@ -170,6 +171,7 @@ extern void diffcore_std_no_resolve(struct diff_options *); " --patch-with-raw\n" \ " output both a patch and the diff-raw format.\n" \ " --stat show diffstat instead of patch.\n" \ +" --numstat show numeric diffstat instead of patch.\n" \ " --patch-with-stat\n" \ " output a patch and prepend its diffstat.\n" \ " --name-only show only names of changed files.\n" \ -- cgit v0.10.2-6-g49f6 From ce91fc6eb9c4aefc73f845fc7104f332b2b308d2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 14 Oct 2006 02:02:37 -0700 Subject: git-svn: fix commits over svn+ssh:// Once a get_commit_editor has been called from an SVN session, RA layer operations are not allowed (well, unless you're using file:// or http(s)://). So we'll pass an alternate SVN::Ra object to our editor object for running 'check-path'. This should fix commits over svnserve (svn:// without ssh, too). Closes Debian bug #392702, thanks to Pierre Habouzit for reporting the bug. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index a128d90..0f968c8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -536,7 +536,7 @@ sub commit_lib { $SVN = libsvn_connect($repo); my $ed = SVN::Git::Editor->new( { r => $r_last, - ra => $SVN, + ra => $SVN_LOG, c => $c, svn_path => $SVN_PATH }, @@ -832,7 +832,7 @@ sub commit_diff { $SVN ||= libsvn_connect($repo); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $ed = SVN::Git::Editor->new({ r => $SVN->get_latest_revnum, - ra => $SVN, c => $tb, + ra => $SVN_LOG, c => $tb, svn_path => $SVN_PATH }, $SVN->get_commit_editor($_message, -- cgit v0.10.2-6-g49f6 From abdc3fc84216a9334f40f3b7ce13e20a825697d0 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 14 Oct 2006 12:45:36 +0200 Subject: Add hash_sha1_file() Most callers of write_sha1_file_prepare() are only interested in the resulting hash but don't care about the returned file name or the header. This patch adds a simple wrapper named hash_sha1_file() which does just that, and converts potential callers. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index e3ef044..cbe5977 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1783,8 +1783,6 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) { const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned char sha1[20]; - unsigned char hdr[50]; - int hdrlen; /* For safety, we require patch index line to contain * full 40-byte textual SHA1 for old and new, at least for now. @@ -1800,8 +1798,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) /* See if the old one matches what the patch * applies to. */ - write_sha1_file_prepare(desc->buffer, desc->size, - blob_type, sha1, hdr, &hdrlen); + hash_sha1_file(desc->buffer, desc->size, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) return error("the patch applies to '%s' (%s), " "which does not match the " @@ -1846,8 +1843,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) name); /* verify that the result matches */ - write_sha1_file_prepare(desc->buffer, desc->size, blob_type, - sha1, hdr, &hdrlen); + hash_sha1_file(desc->buffer, desc->size, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1)); } diff --git a/cache-tree.c b/cache-tree.c index 323c68a..d388848 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -344,12 +344,8 @@ static int update_one(struct cache_tree *it, #endif } - if (dryrun) { - unsigned char hdr[200]; - int hdrlen; - write_sha1_file_prepare(buffer, offset, tree_type, it->sha1, - hdr, &hdrlen); - } + if (dryrun) + hash_sha1_file(buffer, offset, tree_type, it->sha1); else write_sha1_file(buffer, offset, tree_type, it->sha1); free(buffer); diff --git a/cache.h b/cache.h index 97debd0..aa3a562 100644 --- a/cache.h +++ b/cache.h @@ -245,6 +245,7 @@ char *enter_repo(char *path, int strict); extern int sha1_object_info(const unsigned char *, char *, unsigned long *); extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size); extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); +extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); extern char *write_sha1_file_prepare(void *buf, unsigned long len, diff --git a/merge-recursive.c b/merge-recursive.c index 611cd95..2ba43ae 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1235,13 +1235,10 @@ int merge(struct commit *h1, if (merged_common_ancestors == NULL) { /* if there is no common ancestor, make an empty tree */ struct tree *tree = xcalloc(1, sizeof(struct tree)); - unsigned char hdr[40]; - int hdrlen; tree->object.parsed = 1; tree->object.type = OBJ_TREE; - write_sha1_file_prepare(NULL, 0, tree_type, tree->object.sha1, - hdr, &hdrlen); + hash_sha1_file(NULL, 0, tree_type, tree->object.sha1); merged_common_ancestors = make_virtual_commit(tree, "ancestor"); } diff --git a/sha1_file.c b/sha1_file.c index 27b1ebb..6c64ec4 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1501,6 +1501,15 @@ static void setup_object_header(z_stream *stream, const char *type, unsigned lon stream->avail_out -= hdr; } +int hash_sha1_file(void *buf, unsigned long len, const char *type, + unsigned char *sha1) +{ + unsigned char hdr[50]; + int hdrlen; + write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); + return 0; +} + int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { int size; @@ -1784,8 +1793,6 @@ int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) unsigned long size = 4096; char *buf = xmalloc(size); int ret; - unsigned char hdr[50]; - int hdrlen; if (read_pipe(fd, &buf, &size)) { free(buf); @@ -1796,10 +1803,8 @@ int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) type = blob_type; if (write_object) ret = write_sha1_file(buf, size, type, sha1); - else { - write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen); - ret = 0; - } + else + ret = hash_sha1_file(buf, size, type, sha1); free(buf); return ret; } @@ -1809,8 +1814,6 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con unsigned long size = st->st_size; void *buf; int ret; - unsigned char hdr[50]; - int hdrlen; buf = ""; if (size) @@ -1823,10 +1826,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con type = blob_type; if (write_object) ret = write_sha1_file(buf, size, type, sha1); - else { - write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen); - ret = 0; - } + else + ret = hash_sha1_file(buf, size, type, sha1); if (size) munmap(buf, size); return ret; @@ -1855,12 +1856,9 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write return error("readlink(\"%s\"): %s", path, errstr); } - if (!write_object) { - unsigned char hdr[50]; - int hdrlen; - write_sha1_file_prepare(target, st->st_size, blob_type, - sha1, hdr, &hdrlen); - } else if (write_sha1_file(target, st->st_size, blob_type, sha1)) + if (!write_object) + hash_sha1_file(target, st->st_size, blob_type, sha1); + else if (write_sha1_file(target, st->st_size, blob_type, sha1)) return error("%s: failed to insert into database", path); free(target); -- cgit v0.10.2-6-g49f6 From 8f9777801d09b195e1820061914ec27fcd5975c8 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 14 Oct 2006 12:45:45 +0200 Subject: Make write_sha1_file_prepare() static There are no callers of write_sha1_file_prepare() left outside of sha1_file.c, so make it static. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index aa3a562..c354701 100644 --- a/cache.h +++ b/cache.h @@ -247,12 +247,6 @@ extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, uns extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); -extern char *write_sha1_file_prepare(void *buf, - unsigned long len, - const char *type, - unsigned char *sha1, - unsigned char *hdr, - int *hdrlen); extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); diff --git a/sha1_file.c b/sha1_file.c index 6c64ec4..d111be7 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1347,12 +1347,9 @@ void *read_object_with_reference(const unsigned char *sha1, } } -char *write_sha1_file_prepare(void *buf, - unsigned long len, - const char *type, - unsigned char *sha1, - unsigned char *hdr, - int *hdrlen) +static char *write_sha1_file_prepare(void *buf, unsigned long len, + const char *type, unsigned char *sha1, + unsigned char *hdr, int *hdrlen) { SHA_CTX c; -- cgit v0.10.2-6-g49f6 From 6844fc806ace7d7c31ad788a8886cfd0498ceec5 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sat, 14 Oct 2006 16:05:25 +0200 Subject: Fix tracing when GIT_TRACE is set to an empty string. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/trace.c b/trace.c index f9efc91..495e5ed 100644 --- a/trace.c +++ b/trace.c @@ -55,7 +55,8 @@ static int get_trace_fd(int *need_close) { char *trace = getenv("GIT_TRACE"); - if (!trace || !strcmp(trace, "0") || !strcasecmp(trace, "false")) + if (!trace || !strcmp(trace, "") || + !strcmp(trace, "0") || !strcasecmp(trace, "false")) return 0; if (!strcmp(trace, "1") || !strcasecmp(trace, "true")) return STDERR_FILENO; -- cgit v0.10.2-6-g49f6 From f7197dff15a3ab12ce59626865c5a388a3bb0976 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 14 Oct 2006 15:48:35 -0700 Subject: git-svn: reduce memory usage for large commits apply_textdelta and send_stream can use a separate pool from the rest of the editor interface, so we'll use a separate SVN::Pool for them and clear the pool after each file is sent to SVN. This drastically reduces memory usage per-changeset committed, and makes large commits (and initial imports) of several thousand files possible. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 0f968c8..54d2356 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3354,9 +3354,11 @@ sub chg_file { seek $fh, 0, 0 or croak $!; my $exp = $md5->hexdigest; - my $atd = $self->apply_textdelta($fbat, undef, $self->{pool}); - my $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool}); + 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); + $pool->clear; close $fh or croak $!; } -- cgit v0.10.2-6-g49f6 From 0a7a9a12d65ca75601f841c93222652f41de09c0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 11 Oct 2006 00:20:43 +0200 Subject: cvsserver: Show correct letters for modified, removed and added files Earlier, cvsserver showed always an 'U', sometimes even without a space between the 'U' and the name. Now, the correct letter is shown, with a space. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 2130d57..4de50d0 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -805,7 +805,14 @@ sub req_update $meta = $updater->getmeta($filename); } - next unless ( $meta->{revision} ); + if ( ! defined $meta ) + { + $meta = { + name => $filename, + revision => 0, + filehash => 'added' + }; + } my $oldmeta = $meta; @@ -835,7 +842,7 @@ sub req_update and not exists ( $state->{opt}{C} ) ) { $log->info("Tell the client the file is modified"); - print "MT text U\n"; + print "MT text M \n"; print "MT fname $filename\n"; print "MT newline\n"; next; @@ -855,15 +862,36 @@ sub req_update } } elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) - or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) + or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} + or $meta->{filehash} eq 'added' ) { - $log->info("Updating '$filename'"); - # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove) - print "MT +updated\n"; - print "MT text U\n"; - print "MT fname $filename\n"; - print "MT newline\n"; - print "MT -updated\n"; + # normal update, just send the new revision (either U=Update, + # or A=Add, or R=Remove) + if ( defined($wrev) && $wrev < 0 ) + { + $log->info("Tell the client the file is scheduled for removal"); + print "MT text R \n"; + print "MT fname $filename\n"; + print "MT newline\n"; + next; + } + elsif ( !defined($wrev) || $wrev == 0 ) + { + $log->info("Tell the client the file will be added"); + print "MT text A \n"; + print "MT fname $filename\n"; + print "MT newline\n"; + next; + + } + else { + $log->info("Updating '$filename' $wrev"); + print "MT +updated\n"; + print "MT text U \n"; + print "MT fname $filename\n"; + print "MT newline\n"; + print "MT -updated\n"; + } my ( $filepart, $dirpart ) = filenamesplit($filename,1); @@ -1709,6 +1737,17 @@ sub argsfromdir return if ( scalar ( @{$state->{args}} ) > 1 ); + my @gethead = @{$updater->gethead}; + + # push added files + foreach my $file (keys %{$state->{entries}}) { + if ( exists $state->{entries}{$file}{revision} && + $state->{entries}{$file}{revision} == 0 ) + { + push @gethead, { name => $file, filehash => 'added' }; + } + } + if ( scalar(@{$state->{args}}) == 1 ) { my $arg = $state->{args}[0]; @@ -1716,7 +1755,7 @@ sub argsfromdir $log->info("Only one arg specified, checking for directory expansion on '$arg'"); - foreach my $file ( @{$updater->gethead} ) + foreach my $file ( @gethead ) { next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) ); next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg ); @@ -1729,7 +1768,7 @@ sub argsfromdir $state->{args} = []; - foreach my $file ( @{$updater->gethead} ) + foreach my $file ( @gethead ) { next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) ); next unless ( $file->{name} =~ s/^$state->{prependdir}// ); -- cgit v0.10.2-6-g49f6 From d988b82232bb8f5826a1619fd4dcba1a5a330f27 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 11 Oct 2006 00:33:28 +0200 Subject: cvsserver: fix "cvs diff" in a subdirectory Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 4de50d0..08ad831 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -275,7 +275,7 @@ sub req_Directory $state->{directory} = "" if ( $state->{directory} eq "." ); $state->{directory} .= "/" if ( $state->{directory} =~ /\S/ ); - if ( not defined($state->{prependdir}) and $state->{localdir} eq "." and $state->{path} =~ /\S/ ) + if ( (not defined($state->{prependdir}) or $state->{prependdir} eq '') and $state->{localdir} eq "." and $state->{path} =~ /\S/ ) { $log->info("Setting prepend to '$state->{path}'"); $state->{prependdir} = $state->{path}; -- cgit v0.10.2-6-g49f6 From ced78b3907dd60d8289ad5385ffcfd3339149957 Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Sat, 14 Oct 2006 21:02:51 +0900 Subject: clone: the given repository dir should be relative to $PWD the repository argument for git-clone should be relative to $PWD instead of the given target directory. The old behavior gave us surprising success and you need a few minute to know why it worked. GIT_DIR is already exported so no need to cd into $D. And this makes $PWD for git-fetch-pack, which is the actual command to take the given repository dir, the same as git-clone. Signed-off-by: Yasushi SHOJI Signed-off-by: Junio C Hamano diff --git a/git-clone.sh b/git-clone.sh index 3998c55..bf54a11 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -312,7 +312,7 @@ yes,yes) fi ;; *) - cd "$D" && case "$upload_pack" in + case "$upload_pack" in '') git-fetch-pack --all -k $quiet "$repo" ;; *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;; esac >"$GIT_DIR/CLONE_HEAD" || { diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh index 0c6a363..041be04 100755 --- a/t/t5600-clone-fail-cleanup.sh +++ b/t/t5600-clone-fail-cleanup.sh @@ -25,6 +25,12 @@ test_create_repo foo # clone doesn't like it if there is no HEAD. Is that a bug? (cd foo && touch file && git add file && git commit -m 'add file' >/dev/null 2>&1) +# source repository given to git-clone should be relative to the +# current path not to the target dir +test_expect_failure \ + 'clone of non-existent (relative to $PWD) source should fail' \ + 'git-clone ../foo baz' + test_expect_success \ 'clone should work now that source exists' \ 'git-clone foo bar' -- cgit v0.10.2-6-g49f6 From 29f049a0c277be72637f74f1f90a89dccd3475bc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 14 Oct 2006 23:37:41 -0700 Subject: Revert "move pack creation to version 3" This reverts commit 16854571aae6302f457c5fbee41ac64669b09595. Git as recent as v1.1.6 do not understand version 3 delta. v1.2.0 is Ok and I personally would say it is old enough, but the improvement between version 2 and version 3 delta is not bit enough to justify breaking older clients. We should resurrect this later, but when we do so we shold make it conditional. Signed-off-by: Junio C Hamano diff --git a/diff-delta.c b/diff-delta.c index 51df460..fa16d06 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -308,8 +308,8 @@ create_delta(const struct delta_index *index, continue; if (ref_size > top - src) ref_size = top - src; - if (ref_size > 0xffffff) - ref_size = 0xffffff; + if (ref_size > 0x10000) + ref_size = 0x10000; if (ref_size <= msize) break; while (ref_size-- && *src++ == *ref) @@ -318,8 +318,6 @@ create_delta(const struct delta_index *index, /* this is our best match so far */ msize = ref - entry->ptr; moff = entry->ptr - ref_data; - if (msize >= 0x10000) - break; /* this is good enough */ } } @@ -383,8 +381,6 @@ create_delta(const struct delta_index *index, if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; } msize >>= 8; if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; } - msize >>= 8; - if (msize & 0xff) { out[outpos++] = msize; i |= 0x40; } *op = i; } diff --git a/pack.h b/pack.h index 05557da..eb07b03 100644 --- a/pack.h +++ b/pack.h @@ -7,7 +7,7 @@ * Packed object header */ #define PACK_SIGNATURE 0x5041434b /* "PACK" */ -#define PACK_VERSION 3 +#define PACK_VERSION 2 #define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) struct pack_header { unsigned int hdr_signature; -- cgit v0.10.2-6-g49f6 From 63e02a1be308aa57f3cabd7e951a94ac3b7aeb51 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 15 Oct 2006 03:29:09 -0700 Subject: gitweb: use for-each-ref to show the latest activity across branches The project list page shows last change from the HEAD branch but often people would want to view activity on any branch. Unfortunately that is fairly expensive without the core-side support. for-each-ref was invented exactly for that. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e4ebce6..e119e33 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1009,6 +1009,24 @@ sub parse_tag { return %tag } +sub git_get_last_activity { + my ($path) = @_; + my $fd; + + $git_dir = "$projectroot/$path"; + open($fd, "-|", git_cmd(), 'for-each-ref', + '--format=%(refname) %(committer)', + '--sort=-committerdate', + 'refs/heads') or return; + my $most_recent = <$fd>; + close $fd or return; + if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) { + my $timestamp = $1; + my $age = time - $timestamp; + return ($age, age_string($age)); + } +} + sub parse_commit { my $commit_id = shift; my $commit_text = shift; @@ -2258,16 +2276,11 @@ sub git_project_list { die_error(undef, "No projects found"); } foreach my $pr (@list) { - my $head = git_get_head_hash($pr->{'path'}); - if (!defined $head) { - next; - } - $git_dir = "$projectroot/$pr->{'path'}"; - my %co = parse_commit($head); - if (!%co) { + my (@aa) = git_get_last_activity($pr->{'path'}); + unless (@aa) { next; } - $pr->{'commit'} = \%co; + ($pr->{'age'}, $pr->{'age_string'}) = @aa; if (!defined $pr->{'descr'}) { my $descr = git_get_project_description($pr->{'path'}) || ""; $pr->{'descr'} = chop_str($descr, 25, 5); @@ -2317,7 +2330,7 @@ sub git_project_list { "\n"; } if ($order eq "age") { - @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; + @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects; print "
\n"; } else { print "\n" . "\n"; - print "\n" . + print "\n" . "\n"; + file_name=>"$basedir$t->{'name'}", %base_key), + -class => "list"}, esc_html($t->{'name'})) . "\n"; print "\n"; } elsif ($t->{'type'} eq "tree") { @@ -1758,7 +1758,7 @@ sub git_difftree_body { print "\n"; print "\n"; print "\n"; } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed @@ -1810,8 +1810,8 @@ sub git_difftree_body { } print "\n"; print "\n"; print "\n"; } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied @@ -1862,19 +1862,19 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); } else { print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, - "diff"); + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff"); } print " | "; } print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'from_file'})}, - "blame") . " | "; + file_name=>$diff{'from_file'})}, + "blame") . " | "; print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'from_file'})}, - "history"); + file_name=>$diff{'from_file'})}, + "history"); print "\n"; } # we should not encounter Unmerged (U) or Unknown (X) status @@ -2800,7 +2800,7 @@ sub git_tree { # FIXME: Should be available when we have no hash base as well. push @views_nav, $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, - "snapshot"); + "snapshot"); } git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); -- cgit v0.10.2-6-g49f6 From 300454feaba846b6bf95625c607d7096d81d26eb Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 21 Oct 2006 17:53:09 +0200 Subject: gitweb: Do not esc_html $basedir argument to git_print_tree_entry In git_tree, rename $base variable (which is passed as $basedir argument to git_print_tree_entry) to $basedir. Do not esc_html $basedir, as it is part of file_name ('f') argument in link and not printed. Add '/' at the end only if $basedir is not empty (it is empty for top directory) and doesn't end in '/' already. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d01ac94..23b26a2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2783,7 +2783,7 @@ sub git_tree { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $hash_base); git_header_html(); - my $base = ""; + my $basedir = ''; my ($have_blame) = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { my @views_nav = (); @@ -2811,7 +2811,10 @@ sub git_tree { print "
$hash
\n"; } if (defined $file_name) { - $base = esc_html("$file_name/"); + $basedir = $file_name; + if ($basedir ne '' && substr($basedir, -1) ne '/') { + $basedir .= '/'; + } } git_print_page_path($file_name, 'tree', $hash_base); print "
\n"; @@ -2827,7 +2830,7 @@ sub git_tree { } $alternate ^= 1; - git_print_tree_entry(\%t, $base, $hash_base, $have_blame); + git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame); print "
\n"; } -- cgit v0.10.2-6-g49f6 From 474a90fef9ebcdedee041b2def4b9a98b94cd146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Hasselstr=C3=B6m?= Date: Sun, 22 Oct 2006 20:46:36 +0200 Subject: git-vc: better installation instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provide some more detailed installation instructions, for the elisp-challenged among us. Signed-off-by: Karl Hasselström Signed-off-by: Junio C Hamano diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 4189c4c..80e7675 100644 --- a/contrib/emacs/vc-git.el +++ b/contrib/emacs/vc-git.el @@ -23,7 +23,10 @@ ;; system. ;; ;; To install: put this file on the load-path and add GIT to the list -;; of supported backends in `vc-handled-backends'. +;; of supported backends in `vc-handled-backends'; the following line, +;; placed in your ~/.emacs, will accomplish this: +;; +;; (add-to-list 'vc-handled-backends 'GIT) ;; ;; TODO ;; - changelog generation -- cgit v0.10.2-6-g49f6 From 2eb53e65bd9cdd3b76a6447a1a51dee6e5de96a3 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Sun, 22 Oct 2006 19:01:23 -0400 Subject: Make prune also run prune-packed Both the git-prune manpage and everday.txt say that git-prune should also prune unpacked objects that are also found in packs, by running git prune-packed. Junio thought this was "a regression when prune was rewritten as a built-in." So modify prune to call prune-packed again. Signed-off-by: J. Bruce Fields diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c index 960db49..e12b6cf 100644 --- a/builtin-prune-packed.c +++ b/builtin-prune-packed.c @@ -4,9 +4,7 @@ static const char prune_packed_usage[] = "git-prune-packed [-n]"; -static int dryrun; - -static void prune_dir(int i, DIR *dir, char *pathname, int len) +static void prune_dir(int i, DIR *dir, char *pathname, int len, int dryrun) { struct dirent *de; char hex[40]; @@ -31,7 +29,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len) rmdir(pathname); } -static void prune_packed_objects(void) +void prune_packed_objects(int dryrun) { int i; static char pathname[PATH_MAX]; @@ -50,7 +48,7 @@ static void prune_packed_objects(void) d = opendir(pathname); if (!d) continue; - prune_dir(i, d, pathname, len + 3); + prune_dir(i, d, pathname, len + 3, dryrun); closedir(d); } } @@ -58,6 +56,7 @@ static void prune_packed_objects(void) int cmd_prune_packed(int argc, const char **argv, const char *prefix) { int i; + int dryrun; for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -73,6 +72,6 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix) usage(prune_packed_usage); } sync(); - prune_packed_objects(); + prune_packed_objects(dryrun); return 0; } diff --git a/builtin-prune.c b/builtin-prune.c index 6228c79..7290e6d 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -255,5 +255,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) prune_object_dir(get_object_directory()); + sync(); + prune_packed_objects(show_only); return 0; } diff --git a/builtin.h b/builtin.h index f9fa9ff..f71b962 100644 --- a/builtin.h +++ b/builtin.h @@ -11,6 +11,7 @@ extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const cha extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); extern void stripspace(FILE *in, FILE *out); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); +extern void prune_packed_objects(int); extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); -- cgit v0.10.2-6-g49f6 From 0abc0260fa3419de649fcc1444e3d256a17ca6c7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 22 Oct 2006 20:28:10 -0700 Subject: pager: default to LESS=FRSX not LESS=FRS Signed-off-by: Junio C Hamano diff --git a/pager.c b/pager.c index 8bd33a1..4587fbb 100644 --- a/pager.c +++ b/pager.c @@ -50,7 +50,7 @@ void setup_pager(void) close(fd[0]); close(fd[1]); - setenv("LESS", "FRS", 0); + setenv("LESS", "FRSX", 0); run_pager(pager); die("unable to execute pager '%s'", pager); exit(255); -- cgit v0.10.2-6-g49f6 From 4df118ed6041dc0f126d55231a6621be05882b5f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 21 Oct 2006 17:53:55 +0200 Subject: gitweb: Improve git_print_page_path Add link to "tree root" (root directory) also for not defined name, for example for "tree" action without defined "file_name" which means "tree root". Add " / " at the end of path when $type eq "tree". Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 209b318..126cf3c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1615,17 +1615,16 @@ sub git_print_page_path { my $type = shift; my $hb = shift; - if (!defined $name) { - print "
/
\n"; - } else { + + print "
"; + print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), + -title => 'tree root'}, "[$project]"); + print " / "; + if (defined $name) { my @dirname = split '/', $name; my $basename = pop @dirname; my $fullname = ''; - print "
"; - print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => 'tree root'}, "[$project]"); - print " / "; foreach my $dir (@dirname) { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, @@ -1641,11 +1640,12 @@ sub git_print_page_path { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), -title => $name}, esc_html($basename)); + print " / "; } else { print esc_html($basename); } - print "
\n"; } + print "
\n"; } # sub git_print_log (\@;%) { -- cgit v0.10.2-6-g49f6 From b6b7fc7283bd091822541c0286340e78b0c497a2 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 21 Oct 2006 17:54:44 +0200 Subject: gitweb: Add '..' (up directory) to tree view if applicable Adds '..' (up directory) link at the top of "tree" view listing, if both $hash_base and $file_name are provided, and $file_name is not empty string. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 126cf3c..c9e57f0 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2871,6 +2871,30 @@ sub git_tree { print "
\n"; print "
CommitLineData
"; - if ($print_c8 == 1) { - print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + print " rowspan=\"$group_size\"" if ($group_size > 1); + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$full_rev, + file_name=>$file_name)}, esc_html($rev)); + print "" . - esc_html($lineno) . ""; + print $cgi->a({ -href => "$blamed#l$orig_lineno", + -id => "l$lineno", + -class => "linenr" }, + esc_html($lineno)); + print "" . esc_html($data) . "
" . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . " | " . - $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); + if (gitweb_have_snapshot()) { + print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + } print "
Last Change" . @@ -2339,8 +2352,8 @@ sub git_project_list { -class => "list"}, esc_html($pr->{'path'})) . "\n" . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . - $pr->{'commit'}{'age_string'} . "{'age'}) . "\">" . + $pr->{'age_string'} . "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . -- cgit v0.10.2-6-g49f6 From 972a9155832165ea38febba2303e7c760050b5d8 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sun, 15 Oct 2006 14:02:03 +0200 Subject: Make write_sha1_file_prepare() void Move file name generation from write_sha1_file_prepare() to the one caller that cares and make it a void function. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index d111be7..66cc767 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1347,9 +1347,9 @@ void *read_object_with_reference(const unsigned char *sha1, } } -static char *write_sha1_file_prepare(void *buf, unsigned long len, - const char *type, unsigned char *sha1, - unsigned char *hdr, int *hdrlen) +static void write_sha1_file_prepare(void *buf, unsigned long len, + const char *type, unsigned char *sha1, + unsigned char *hdr, int *hdrlen) { SHA_CTX c; @@ -1361,8 +1361,6 @@ static char *write_sha1_file_prepare(void *buf, unsigned long len, SHA1_Update(&c, hdr, *hdrlen); SHA1_Update(&c, buf, len); SHA1_Final(sha1, &c); - - return sha1_file_name(sha1); } /* @@ -1521,7 +1519,8 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha /* Normally if we have it in the pack then we do not bother writing * it out into .git/objects/??/?{38} file. */ - filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); + write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); + filename = sha1_file_name(sha1); if (returnsha1) hashcpy(returnsha1, sha1); if (has_sha1_file(sha1)) -- cgit v0.10.2-6-g49f6 From 7cfb5f367e50402c0bc75d89542d7ada4c5b8da4 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sun, 15 Oct 2006 14:02:18 +0200 Subject: Replace open-coded version of hash_sha1_file() Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 66cc767..716aef3 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -671,14 +671,8 @@ static void reprepare_packed_git(void) int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type) { - char header[100]; unsigned char real_sha1[20]; - SHA_CTX c; - - SHA1_Init(&c); - SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size)); - SHA1_Update(&c, map, size); - SHA1_Final(real_sha1, &c); + hash_sha1_file(map, size, type, real_sha1); return hashcmp(sha1, real_sha1) ? -1 : 0; } -- cgit v0.10.2-6-g49f6 From b32db4d0faec35ad8e6af35c54c37036b3d6c02f Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 16 Oct 2006 03:00:37 +0200 Subject: svnimport: Fix broken tags being generated Currently git-svnimport generates broken tags missing the timespec in the 'tagger' line. This is a random stab at a minimal fix. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/git-svnimport.perl b/git-svnimport.perl index 988514e..aca0e4f 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -838,7 +838,7 @@ sub commit { print $out ("object $cid\n". "type commit\n". "tag $dest\n". - "tagger $committer_name <$committer_email>\n") and + "tagger $committer_name <$committer_email> 0 +0000\n") and close($out) or die "Cannot create tag object $dest: $!\n"; -- cgit v0.10.2-6-g49f6 From a9cb3c6ecb97c4734423045f47899e03f135d3bd Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Thu, 12 Oct 2006 14:52:42 -0700 Subject: git-revert with conflicts to behave as git-merge with conflicts In a busy project, reverting a commit almost always results in a conflict between one or more files (depending on the commit being reverted). It is useful to record this conflict in the commit-to-be message of the resulting commit (after the resolve). The process now becomes: git-revert git-update-index git-commit -s And the commit message is now a merge of the revert commit message and the conflict commit message, giving the user a chance to edit it or add more information: Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/git-commit.sh b/git-commit.sh index ee5a165..8ac8dcc 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -466,7 +466,7 @@ then elif test "$use_commit" != "" then git-cat-file commit "$use_commit" | sed -e '1,/^$/d' -elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG" +elif test -f "$GIT_DIR/MERGE_MSG" then cat "$GIT_DIR/MERGE_MSG" elif test -f "$GIT_DIR/SQUASH_MSG" @@ -632,7 +632,7 @@ then commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) && rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) && git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" && - rm -f -- "$GIT_DIR/MERGE_HEAD" && + rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" && if test -f "$NEXT_INDEX" then mv "$NEXT_INDEX" "$THIS_INDEX" diff --git a/git-revert.sh b/git-revert.sh index 2bf35d1..066e677 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -141,9 +141,18 @@ git-read-tree -m -u --aggressive $base $head $next && result=$(git-write-tree 2>/dev/null) || { echo >&2 "Simple $me fails; trying Automatic $me." git-merge-index -o git-merge-one-file -a || { + mv -f .msg "$GIT_DIR/MERGE_MSG" + { + echo ' +Conflicts: +' + git ls-files --unmerged | + sed -e 's/^[^ ]* / /' | + uniq + } >>"$GIT_DIR/MERGE_MSG" echo >&2 "Automatic $me failed. After resolving the conflicts," echo >&2 "mark the corrected paths with 'git-update-index '" - echo >&2 "and commit with 'git commit -F .msg'" + echo >&2 "and commit the result." case "$me" in cherry-pick) echo >&2 "You may choose to use the following when making" -- cgit v0.10.2-6-g49f6 From 9de0834663f28bee9e6b2c4647ed6128241ed26f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 16 Oct 2006 19:58:54 -0700 Subject: Fix hash function in xdiff library Jim Mayering noticed that xdiff library took insanely long time when comparing files with many identical lines. This was because the hash function used in the library is broken on 64-bit architectures and caused too many collisions. http://thread.gmane.org/gmane.comp.version-control.git/28962/focus=28994 Acked-by: Davide Libenzi Signed-off-by: Junio C Hamano diff --git a/xdiff/xmacros.h b/xdiff/xmacros.h index 4c2fde8..e2cd202 100644 --- a/xdiff/xmacros.h +++ b/xdiff/xmacros.h @@ -24,14 +24,15 @@ #define XMACROS_H -#define GR_PRIME 0x9e370001UL #define XDL_MIN(a, b) ((a) < (b) ? (a): (b)) #define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) #define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) #define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') -#define XDL_HASHLONG(v, b) (((unsigned long)(v) * GR_PRIME) >> ((CHAR_BIT * sizeof(unsigned long)) - (b))) +#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) +#define XDL_MASKBITS(b) ((1UL << (b)) - 1) +#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) #define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0) #define XDL_LE32_PUT(p, v) \ do { \ -- cgit v0.10.2-6-g49f6 From 9b709e47ae46079efe1d7858580dcf5b8cf6ea33 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Mon, 16 Oct 2006 02:59:25 +0200 Subject: bisect reset: Leave the tree in usable state if git-checkout failed I had local modifications in the tree and doing bisect reset required me to manually edit .git/HEAD. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/git-bisect.sh b/git-bisect.sh index 06a8d26..6da31e8 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -179,11 +179,12 @@ bisect_reset() { *) usage ;; esac - git checkout "$branch" && - rm -fr "$GIT_DIR/refs/bisect" - rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name" - rm -f "$GIT_DIR/BISECT_LOG" - rm -f "$GIT_DIR/BISECT_NAMES" + if git checkout "$branch"; then + rm -fr "$GIT_DIR/refs/bisect" + rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name" + rm -f "$GIT_DIR/BISECT_LOG" + rm -f "$GIT_DIR/BISECT_NAMES" + fi } bisect_replay () { -- cgit v0.10.2-6-g49f6 From 17b96be29afd71577374e9deee663120b18eb8f1 Mon Sep 17 00:00:00 2001 From: Andy Whitcroft Date: Tue, 17 Oct 2006 19:08:08 +0100 Subject: add proper dependancies on the xdiff source We are not rebuilding the xdiff library when its header files change. Add dependancies for those to the main Makefile. Signed-off-by: Andy Whitcroft Acked-by: Ryan Anderson Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 2c7c338..66c8b4b 100644 --- a/Makefile +++ b/Makefile @@ -760,6 +760,8 @@ $(LIB_FILE): $(LIB_OBJS) rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o +$(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ + xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h $(XDIFF_LIB): $(XDIFF_OBJS) rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) -- cgit v0.10.2-6-g49f6 From e0b0830726286287744cc9e1a629a534bbe75452 Mon Sep 17 00:00:00 2001 From: Markus Amsler Date: Fri, 13 Oct 2006 00:19:35 +0200 Subject: git-imap-send: Strip smtp From_ header from imap message. Cyrus imap refuses messages with a 'From ' Header. [jc: Mike McCormack says this is fine with Courier as well.] Signed-off-by: Markus Amsler Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 362e474..16804ab 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1226,6 +1226,14 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs ) if (msg->len < 5 || strncmp( data, "From ", 5 )) return 0; + p = strchr( data, '\n' ); + if (p) { + p = &p[1]; + msg->len -= p-data; + *ofs += p-data; + data = p; + } + p = strstr( data, "\nFrom " ); if (p) msg->len = &p[1] - data; -- cgit v0.10.2-6-g49f6 From 3c552873c698117689af4e5159c7e491fe3a89a3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 17 Oct 2006 16:23:26 -0400 Subject: index-pack: compare only the first 20-bytes of the key. The "union delta_base" is a strange beast. It is a 20-byte binary blob key to search a binary searchable deltas[] array, each element of which uses it to represent its base object with either a full 20-byte SHA-1 or an offset in the pack. Which representation is used is determined by another field of the deltas[] array element, obj->type, so there is no room for confusion, as long as we make sure we compare the keys for the same type only with appropriate length. The code compared the full union with memcmp(). When storing the in-pack offset, the union was first cleared before storing an unsigned long, so comparison worked fine. On 64-bit architectures, however, the union typically is 24-byte long; the code did not clear the remaining 4-byte alignment padding when storing a full 20-byte SHA-1 representation. Using memcmp() to compare the whole union was wrong. This fixes the comparison to look at the first 20-bytes of the union, regardless of the architecture. As long as ulong is smaller than 20-bytes this works fine. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/index-pack.c b/index-pack.c index fffddd2..56c590e 100644 --- a/index-pack.c +++ b/index-pack.c @@ -23,6 +23,12 @@ union delta_base { unsigned long offset; }; +/* + * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want + * to memcmp() only the first 20 bytes. + */ +#define UNION_BASE_SZ 20 + struct delta_entry { struct object_entry *obj; @@ -211,7 +217,7 @@ static int find_delta(const union delta_base *base) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = memcmp(base, &delta->base, sizeof(*base)); + cmp = memcmp(base, &delta->base, UNION_BASE_SZ); if (!cmp) return next; if (cmp < 0) { @@ -232,9 +238,9 @@ static int find_delta_childs(const union delta_base *base, if (first < 0) return -1; - while (first > 0 && !memcmp(&deltas[first - 1].base, base, sizeof(*base))) + while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ)) --first; - while (last < end && !memcmp(&deltas[last + 1].base, base, sizeof(*base))) + while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ)) ++last; *first_index = first; *last_index = last; @@ -312,7 +318,7 @@ static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return memcmp(&delta_a->base, &delta_b->base, sizeof(union delta_base)); + return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ); } static void parse_pack_objects(void) -- cgit v0.10.2-6-g49f6 From 67c22874cf82edae9b5c5f0a47c432d08d7ad426 Mon Sep 17 00:00:00 2001 From: OGAWA Hirofumi Date: Wed, 27 Sep 2006 12:32:19 +0900 Subject: [PATCH] gitk: Fix nextfile() and add prevfile() The current nextfile() jumps to last hunk, but I think this is not intention, probably, it's forgetting to add "break;". And this patch also adds prevfile(), it jumps to previous hunk. Signed-off-by: OGAWA Hirofumi Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index ebbeac6..ab383b3 100755 --- a/gitk +++ b/gitk @@ -4440,12 +4440,27 @@ proc getblobdiffline {bdf ids} { } } +proc prevfile {} { + global difffilestart ctext + set prev [lindex $difffilestart 0] + set here [$ctext index @0,0] + foreach loc $difffilestart { + if {[$ctext compare $loc >= $here]} { + $ctext yview $prev + return + } + set prev $loc + } + $ctext yview $prev +} + proc nextfile {} { global difffilestart ctext set here [$ctext index @0,0] foreach loc $difffilestart { if {[$ctext compare $loc > $here]} { $ctext yview $loc + return } } } -- cgit v0.10.2-6-g49f6 From 1a3b55c6b424904835ebfd74c992a5bffbaa7e7e Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 18 Oct 2006 15:56:22 -0400 Subject: reduce delta head inflated size Supposing that both the base and result sizes were both full size 64-bit values, their encoding would occupy only 9.2 bytes each. Therefore inflating 64 bytes is way overkill. Limit it to 20 bytes instead which should be plenty enough for a couple years to come. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 716aef3..47e2a29 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -908,7 +908,7 @@ static int packed_delta_info(struct packed_git *p, if (sizep) { const unsigned char *data; - unsigned char delta_head[64]; + unsigned char delta_head[20]; unsigned long result_size; z_stream stream; int st; -- cgit v0.10.2-6-g49f6 From 8a83157e04e8f9654b3573cf04276895b1cbd68a Mon Sep 17 00:00:00 2001 From: "pclouds@gmail.com" Date: Thu, 19 Oct 2006 08:34:41 +0700 Subject: Reject hexstring longer than 40-bytes in get_short_sha1() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Such a string can never be a valid object name. Signed-off-by: Nguyá»…n Thái Ngá»c Duy Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index 9b226e3..6ffee22 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -157,7 +157,7 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, char canonical[40]; unsigned char res[20]; - if (len < MINIMUM_ABBREV) + if (len < MINIMUM_ABBREV || len > 40) return -1; hashclr(res); memset(canonical, 'x', 40); -- cgit v0.10.2-6-g49f6 From 6b09c7883f50044a68d93ef6872486bad2e93a9d Mon Sep 17 00:00:00 2001 From: "pclouds@gmail.com" Date: Thu, 19 Oct 2006 10:04:55 +0700 Subject: Add revspec documentation for ':path', ':[0-3]:path' and git-describe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyá»…n Thái Ngá»c Duy Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 2f1306c..5d42570 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -111,7 +111,9 @@ SPECIFYING REVISIONS A revision parameter typically, but not necessarily, names a commit object. They use what is called an 'extended SHA1' -syntax. +syntax. Here are various ways to spell object names. The +ones listed near the end of this list are to name trees and +blobs contained in a commit. * The full SHA1 object name (40-byte hexadecimal string), or a substring of such that is unique within the repository. @@ -119,6 +121,9 @@ syntax. name the same commit object if there are no other object in your repository whose object name starts with dae86e. +* An output from `git-describe`; i.e. a closest tag, followed by a + dash, a 'g', and an abbreviated object name. + * A symbolic ref name. E.g. 'master' typically means the commit object referenced by $GIT_DIR/refs/heads/master. If you happen to have both heads/master and tags/master, you can @@ -156,6 +161,15 @@ syntax. and dereference the tag recursively until a non-tag object is found. +* A suffix ':' followed by a path; this names the blob or tree + at the given path in the tree-ish object named by the part + before the colon. + +* A colon, optionally followed by a stage number (0 to 3) and a + colon, followed by a path; this names a blob object in the + index at the given path. Missing stage number (and the colon + that follows it) names an stage 0 entry. + Here is an illustration, by Jon Loeliger. Both node B and C are a commit parents of commit node A. Parent commits are ordered left-to-right. -- cgit v0.10.2-6-g49f6 From 3b463c3f02f83ef0bce2d5daa193459418e5258f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 19 Oct 2006 01:28:47 -0700 Subject: ref-log: fix D/F conflict coming from deleted refs. After deleting a branch l/k, you should be able to create a branch l. Earlier we added remove_empty_directories() on the ref creation side to remove leftover .git/refs/l directory but we also need a matching code to remove .git/logs/refs/l directory. Signed-off-by: Junio C Hamano diff --git a/refs.c b/refs.c index 3d100df..ed2e3b1 100644 --- a/refs.c +++ b/refs.c @@ -733,8 +733,18 @@ static int log_ref_write(struct ref_lock *lock, if (logfd < 0) { if (!(oflags & O_CREAT) && errno == ENOENT) return 0; - return error("Unable to append to %s: %s", - lock->log_file, strerror(errno)); + + if ((oflags & O_CREAT) && errno == EISDIR) { + if (remove_empty_directories(lock->log_file)) { + return error("There are still logs under '%s'", + lock->log_file); + } + logfd = open(lock->log_file, oflags, 0666); + } + + if (logfd < 0) + return error("Unable to append to %s: %s", + lock->log_file, strerror(errno)); } committer = git_committer_info(1); diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index a4fbfda..b1e9f2e 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -11,6 +11,8 @@ semantic is still the same. ' . ./test-lib.sh +echo '[core] logallrefupdates = true' >>.git/config + test_expect_success \ 'prepare a trivial repository' \ 'echo Hello > A && -- cgit v0.10.2-6-g49f6 From 7768e27e1d3f3d5e253e795433033b5de1d1c157 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 19 Oct 2006 10:33:01 +0200 Subject: Don't use $author_name undefined when $from contains no /\serr new-file OK. Log says: Date: Thu, 19 Oct 2006 10:26:24 +0200 Sendmail: /usr/sbin/sendmail From: j Subject: Cc: To: k Result: OK $ cat err Use of uninitialized value in pattern match (m//) at /p/bin/git-send-email line 416. Use of uninitialized value in concatenation (.) or string at /p/bin/git-send-email line 420. Use of uninitialized value in concatenation (.) or string at /p/bin/git-send-email line 468. There's a patch for the $author_name part below. The example above shows that $subject may also be used uninitialized. That should be easy to fix, too. Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index b17d261..1c6d2cc 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -412,7 +412,7 @@ sub send_message } my ($author_name) = ($from =~ /^(.*?)\s+ Date: Thu, 19 Oct 2006 19:26:08 -0700 Subject: git-apply: prepare for upcoming GNU diff -u format change. The latest GNU diff from CVS emits an empty line to express an empty context line, instead of more traditional "single white space followed by a newline". Do not get broken by it. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index cbe5977..11a5277 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -934,6 +934,7 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s switch (*line) { default: return -1; + case '\n': /* newer GNU diff, an empty context line */ case ' ': oldlines--; newlines--; @@ -1623,6 +1624,14 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i first = '-'; } switch (first) { + case '\n': + /* Newer GNU diff, empty context line */ + if (plen < 0) + /* ... followed by '\No newline'; nothing */ + break; + old[oldsize++] = '\n'; + new[newsize++] = '\n'; + break; case ' ': case '-': memcpy(old + oldsize, patch + 1, plen); diff --git a/t/t4118-apply-empty-context.sh b/t/t4118-apply-empty-context.sh new file mode 100755 index 0000000..7309422 --- /dev/null +++ b/t/t4118-apply-empty-context.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='git-apply with new style GNU diff with empty context + +' + +. ./test-lib.sh + +test_expect_success setup ' + { + echo; echo; + echo A; echo B; echo C; + echo; + } >file1 && + cat file1 >file1.orig && + { + cat file1 && + echo Q | tr -d "\\012" + } >file2 && + cat file2 >file2.orig + git add file1 file2 && + sed -e "/^B/d" file1 && + sed -e "/^B/d" file2 && + cat file1 >file1.mods && + cat file2 >file2.mods && + git diff | + sed -e "s/^ \$//" >diff.output +' + +test_expect_success 'apply --numstat' ' + + git apply --numstat diff.output >actual && + { + echo "0 1 file1" && + echo "0 1 file2" + } >expect && + diff -u expect actual + +' + +test_expect_success 'apply --apply' ' + + cat file1.orig >file1 && + cat file2.orig >file2 && + git update-index file1 file2 && + git apply --index diff.output && + diff -u file1.mods file1 && + diff -u file2.mods file2 +' + +test_done + -- cgit v0.10.2-6-g49f6 From cee7f245dcaef6dade28464f59420095a9949aac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 19 Oct 2006 16:00:04 -0700 Subject: git-pickaxe: blame rewritten. Currently it does what git-blame does, but only faster. More importantly, its internal structure is designed to support content movement (aka cut-and-paste) more easily by allowing more than one paths to be taken from the same commit. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt new file mode 100644 index 0000000..7685bd0 --- /dev/null +++ b/Documentation/git-pickaxe.txt @@ -0,0 +1,104 @@ +git-pickaxe(1) +============== + +NAME +---- +git-pickaxe - Show what revision and author last modified each line of a file + +SYNOPSIS +-------- +'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [] [--] + +DESCRIPTION +----------- + +Annotates each line in the given file with information from the revision which +last modified the line. Optionally, start annotating from the given revision. + +Also it can limit the range of lines annotated. + +This report doesn't tell you anything about lines which have been deleted or +replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe" +interface briefly mentioned in the following paragraph. + +Apart from supporting file annotation, git also supports searching the +development history for when a code snippet occured in a change. This makes it +possible to track when a code snippet was added to a file, moved or copied +between files, and eventually deleted or replaced. It works by searching for +a text string in the diff. A small example: + +----------------------------------------------------------------------------- +$ git log --pretty=oneline -S'blame_usage' +5040f17eba15504bad66b14a645bddd9b015ebb7 blame -S +ea4c7f9bf69e781dd0cd88d2bccb2bf5cc15c9a7 git-blame: Make the output +----------------------------------------------------------------------------- + +OPTIONS +------- +-c, --compatibility:: + Use the same output mode as gitlink:git-annotate[1] (Default: off). + +-L n,m:: + Annotate only the specified line range (lines count from 1). + +-l, --long:: + Show long rev (Default: off). + +-t, --time:: + Show raw timestamp (Default: off). + +-S, --rev-file :: + Use revs from revs-file instead of calling gitlink:git-rev-list[1]. + +-f, --show-name:: + Show filename in the original commit. By default + filename is shown if there is any line that came from a + file with different name, due to rename detection. + +-n, --show-number:: + Show line number in the original commit (Default: off). + +-p, --porcelain:: + Show in a format designed for machine consumption. + +-h, --help:: + Show help message. + + +THE PORCELAIN FORMAT +-------------------- + +In this format, each line is output after a header; the +header at the minumum has the first line which has: + +- 40-byte SHA-1 of the commit the line is attributed to; +- the line number of the line in the original file; +- the line number of the line in the final file; +- on a line that starts a group of line from a different + commit than the previous one, the number of lines in this + group. On subsequent lines this field is absent. + +This header line is followed by the following information +at least once for each commit: + +- author name ("author"), email ("author-mail"), time + ("author-time"), and timezone ("author-tz"); similarly + for committer. +- filename in the commit the line is attributed to. +- the first line of the commit log message ("summary"). + +The contents of the actual line is output after the above +header, prefixed by a TAB. This is to allow adding more +header elements later. + +SEE ALSO +-------- +gitlink:git-blame[1] + +AUTHOR +------ +Written by Junio C Hamano + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index 3af6fc6..7074e32 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -430,6 +430,9 @@ gitlink:git-annotate[1]:: gitlink:git-blame[1]:: Blame file lines on commits. +gitlink:git-pickaxe[1]:: + Find out where each line in a file came from. + gitlink:git-check-ref-format[1]:: Make sure ref name is well formed. diff --git a/Makefile b/Makefile index 66c8b4b..461fef6 100644 --- a/Makefile +++ b/Makefile @@ -288,6 +288,7 @@ BUILTIN_OBJS = \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ + builtin-pickaxe.o \ builtin-prune.o \ builtin-prune-packed.o \ builtin-push.o \ diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c new file mode 100644 index 0000000..cb69fcc --- /dev/null +++ b/builtin-pickaxe.c @@ -0,0 +1,1194 @@ +/* + * Pickaxe + * + * Copyright (c) 2006, Junio C Hamano + */ + +#include "cache.h" +#include "builtin.h" +#include "blob.h" +#include "commit.h" +#include "tag.h" +#include "tree-walk.h" +#include "diff.h" +#include "diffcore.h" +#include "revision.h" +#include "xdiff-interface.h" + +#include +#include + +static char pickaxe_usage[] = +"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [commit] [--] file\n" +" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" +" -l, --long Show long commit SHA1 (Default: off)\n" +" -t, --time Show raw timestamp (Default: off)\n" +" -f, --show-name Show original filename (Default: auto)\n" +" -n, --show-number Show original linenumber (Default: off)\n" +" -p, --porcelain Show in a format designed for machine consumption\n" +" -L n,m Process only line range n,m, counting from 1\n" +" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; + +static int longest_file; +static int longest_author; +static int max_orig_digits; +static int max_digits; + +#define DEBUG 0 + +/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */ +#define METAINFO_SHOWN (1u<<12) +#define MORE_THAN_ONE_PATH (1u<<13) + +/* + * One blob in a commit + */ +struct origin { + struct commit *commit; + unsigned char blob_sha1[20]; + char path[FLEX_ARRAY]; +}; + +struct blame_entry { + struct blame_entry *prev; + struct blame_entry *next; + + /* the first line of this group in the final image; + * internally all line numbers are 0 based. + */ + int lno; + + /* how many lines this group has */ + int num_lines; + + /* the commit that introduced this group into the final image */ + struct origin *suspect; + + /* true if the suspect is truly guilty; false while we have not + * checked if the group came from one of its parents. + */ + char guilty; + + /* the line number of the first line of this group in the + * suspect's file; internally all line numbers are 0 based. + */ + int s_lno; +}; + +struct scoreboard { + /* the final commit (i.e. where we started digging from) */ + struct commit *final; + + const char *path; + + /* the contents in the final; pointed into by buf pointers of + * blame_entries + */ + const char *final_buf; + unsigned long final_buf_size; + + /* linked list of blames */ + struct blame_entry *ent; + + int num_lines; + int *lineno; +}; + +static void coalesce(struct scoreboard *sb) +{ + struct blame_entry *ent, *next; + + for (ent = sb->ent; ent && (next = ent->next); ent = next) { + if (ent->suspect == next->suspect && + ent->guilty == next->guilty && + ent->s_lno + ent->num_lines == next->s_lno) { + ent->num_lines += next->num_lines; + ent->next = next->next; + if (ent->next) + ent->next->prev = ent; + free(next); + next = ent; /* again */ + } + } +} + +static void free_origin(struct origin *o) +{ + free(o); +} + +static struct origin *find_origin(struct scoreboard *sb, + struct commit *commit, + const char *path) +{ + struct blame_entry *ent; + struct origin *o; + unsigned mode; + char type[10]; + + for (ent = sb->ent; ent; ent = ent->next) { + if (ent->suspect->commit == commit && + !strcmp(ent->suspect->path, path)) + return ent->suspect; + } + + o = xcalloc(1, sizeof(*o) + strlen(path) + 1); + o->commit = commit; + strcpy(o->path, path); + if (get_tree_entry(commit->object.sha1, path, o->blob_sha1, &mode)) + goto err_out; + if (sha1_object_info(o->blob_sha1, type, NULL) || + strcmp(type, blob_type)) + goto err_out; + return o; + err_out: + free_origin(o); + return NULL; +} + +static struct origin *find_rename(struct scoreboard *sb, + struct commit *parent, + struct origin *origin) +{ + struct origin *porigin = NULL; + struct diff_options diff_opts; + int i; + const char *paths[1]; + + diff_setup(&diff_opts); + diff_opts.recursive = 1; + diff_opts.detect_rename = DIFF_DETECT_RENAME; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + paths[0] = NULL; + diff_tree_setup_paths(paths, &diff_opts); + if (diff_setup_done(&diff_opts) < 0) + die("diff-setup"); + diff_tree_sha1(origin->commit->tree->object.sha1, + parent->tree->object.sha1, + "", &diff_opts); + diffcore_std(&diff_opts); + + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + if (p->status == 'R' && !strcmp(p->one->path, origin->path)) { + porigin = find_origin(sb, parent, p->two->path); + break; + } + } + diff_flush(&diff_opts); + return porigin; +} + +struct chunk { + /* line number in postimage; up to but not including this + * line is the same as preimage + */ + int same; + + /* preimage line number after this chunk */ + int p_next; + + /* postimage line number after this chunk */ + int t_next; +}; + +struct patch { + struct chunk *chunks; + int num; +}; + +struct blame_diff_state { + struct xdiff_emit_state xm; + struct patch *ret; + unsigned hunk_post_context; + unsigned hunk_in_pre_context : 1; +}; + +static void process_u_diff(void *state_, char *line, unsigned long len) +{ + struct blame_diff_state *state = state_; + struct chunk *chunk; + int off1, off2, len1, len2, num; + + if (DEBUG) + fprintf(stderr, "%.*s", (int) len, line); + + num = state->ret->num; + if (len < 4 || line[0] != '@' || line[1] != '@') { + if (state->hunk_in_pre_context && line[0] == ' ') + state->ret->chunks[num - 1].same++; + else { + state->hunk_in_pre_context = 0; + if (line[0] == ' ') + state->hunk_post_context++; + else + state->hunk_post_context = 0; + } + return; + } + + if (num && state->hunk_post_context) { + chunk = &state->ret->chunks[num - 1]; + chunk->p_next -= state->hunk_post_context; + chunk->t_next -= state->hunk_post_context; + } + state->ret->num = ++num; + state->ret->chunks = xrealloc(state->ret->chunks, + sizeof(struct chunk) * num); + chunk = &state->ret->chunks[num - 1]; + if (parse_hunk_header(line, len, &off1, &len1, &off2, &len2)) { + state->ret->num--; + return; + } + + /* Line numbers in patch output are one based. */ + off1--; + off2--; + + chunk->same = len2 ? off2 : (off2 + 1); + + chunk->p_next = off1 + (len1 ? len1 : 1); + chunk->t_next = chunk->same + len2; + state->hunk_in_pre_context = 1; + state->hunk_post_context = 0; +} + +static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o, + int context) +{ + struct blame_diff_state state; + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = context; + xecfg.flags = 0; + ecb.outf = xdiff_outf; + ecb.priv = &state; + memset(&state, 0, sizeof(state)); + state.xm.consume = process_u_diff; + state.ret = xmalloc(sizeof(struct patch)); + state.ret->chunks = NULL; + state.ret->num = 0; + + xdl_diff(file_p, file_o, &xpp, &xecfg, &ecb); + + if (state.ret->num) { + struct chunk *chunk; + chunk = &state.ret->chunks[state.ret->num - 1]; + chunk->p_next -= state.hunk_post_context; + chunk->t_next -= state.hunk_post_context; + } + return state.ret; +} + +static struct patch *get_patch(struct origin *parent, struct origin *origin) +{ + mmfile_t file_p, file_o; + char type[10]; + char *blob_p, *blob_o; + struct patch *patch; + + if (DEBUG) fprintf(stderr, "get patch %.8s %.8s\n", + sha1_to_hex(parent->commit->object.sha1), + sha1_to_hex(origin->commit->object.sha1)); + + blob_p = read_sha1_file(parent->blob_sha1, type, + (unsigned long *) &file_p.size); + blob_o = read_sha1_file(origin->blob_sha1, type, + (unsigned long *) &file_o.size); + file_p.ptr = blob_p; + file_o.ptr = blob_o; + if (!file_p.ptr || !file_o.ptr) { + free(blob_p); + free(blob_o); + return NULL; + } + + patch = compare_buffer(&file_p, &file_o, 0); + free(blob_p); + free(blob_o); + return patch; +} + +static void free_patch(struct patch *p) +{ + free(p->chunks); + free(p); +} + +static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) +{ + struct blame_entry *ent, *prev = NULL; + + for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next) + prev = ent; + + /* prev, if not NULL, is the last one that is below e */ + e->prev = prev; + if (prev) { + e->next = prev->next; + prev->next = e; + } + else { + e->next = sb->ent; + sb->ent = e; + } + if (e->next) + e->next->prev = e; +} + +static void dup_entry(struct blame_entry *dst, struct blame_entry *src) +{ + struct blame_entry *p, *n; + p = dst->prev; + n = dst->next; + memcpy(dst, src, sizeof(*src)); + dst->prev = p; + dst->next = n; +} + +static const char *nth_line(struct scoreboard *sb, int lno) +{ + return sb->final_buf + sb->lineno[lno]; +} + +static void split_overlap(struct blame_entry split[3], + struct blame_entry *e, + int tlno, int plno, int same, + struct origin *parent) +{ + /* it is known that lines between tlno to same came from + * parent, and e has an overlap with that range. it also is + * known that parent's line plno corresponds to e's line tlno. + * + * <---- e -----> + * <------> + * <------------> + * <------------> + * <------------------> + * + * Potentially we need to split e into three parts; before + * this chunk, the chunk to be blamed for parent, and after + * that portion. + */ + int chunk_end_lno; + memset(split, 0, sizeof(struct blame_entry [3])); + + if (e->s_lno < tlno) { + /* there is a pre-chunk part not blamed on parent */ + split[0].suspect = e->suspect; + split[0].lno = e->lno; + split[0].s_lno = e->s_lno; + split[0].num_lines = tlno - e->s_lno; + split[1].lno = e->lno + tlno - e->s_lno; + split[1].s_lno = plno; + } + else { + split[1].lno = e->lno; + split[1].s_lno = plno + (e->s_lno - tlno); + } + + if (same < e->s_lno + e->num_lines) { + /* there is a post-chunk part not blamed on parent */ + split[2].suspect = e->suspect; + split[2].lno = e->lno + (same - e->s_lno); + split[2].s_lno = e->s_lno + (same - e->s_lno); + split[2].num_lines = e->s_lno + e->num_lines - same; + chunk_end_lno = split[2].lno; + } + else + chunk_end_lno = e->lno + e->num_lines; + split[1].num_lines = chunk_end_lno - split[1].lno; + + if (split[1].num_lines < 1) + return; + split[1].suspect = parent; +} + +static void split_blame(struct scoreboard *sb, + struct blame_entry split[3], + struct blame_entry *e) +{ + struct blame_entry *new_entry; + + if (split[0].suspect && split[2].suspect) { + /* we need to split e into two and add another for parent */ + dup_entry(e, &split[0]); + + new_entry = xmalloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); + add_blame_entry(sb, new_entry); + + new_entry = xmalloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); + add_blame_entry(sb, new_entry); + } + else if (!split[0].suspect && !split[2].suspect) + /* parent covers the entire area */ + dup_entry(e, &split[1]); + else if (split[0].suspect) { + dup_entry(e, &split[0]); + + new_entry = xmalloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); + add_blame_entry(sb, new_entry); + } + else { + dup_entry(e, &split[1]); + + new_entry = xmalloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); + add_blame_entry(sb, new_entry); + } + + if (DEBUG) { + struct blame_entry *ent; + int lno = 0, corrupt = 0; + + for (ent = sb->ent; ent; ent = ent->next) { + if (lno != ent->lno) + corrupt = 1; + if (ent->s_lno < 0) + corrupt = 1; + lno += ent->num_lines; + } + if (corrupt) { + lno = 0; + for (ent = sb->ent; ent; ent = ent->next) { + printf("L %8d l %8d n %8d\n", + lno, ent->lno, ent->num_lines); + lno = ent->lno + ent->num_lines; + } + die("oops"); + } + } +} + +static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, + int tlno, int plno, int same, + struct origin *parent) +{ + struct blame_entry split[3]; + + split_overlap(split, e, tlno, plno, same, parent); + if (!split[1].suspect) + return; + split_blame(sb, split, e); +} + +static int find_last_in_target(struct scoreboard *sb, struct origin *target) +{ + struct blame_entry *e; + int last_in_target = -1; + + for (e = sb->ent; e; e = e->next) { + if (e->guilty || e->suspect != target) + continue; + if (last_in_target < e->s_lno + e->num_lines) + last_in_target = e->s_lno + e->num_lines; + } + return last_in_target; +} + +static void blame_chunk(struct scoreboard *sb, + int tlno, int plno, int same, + struct origin *target, struct origin *parent) +{ + struct blame_entry *e, *n; + + for (e = sb->ent; e; e = n) { + n = e->next; + if (e->guilty || e->suspect != target) + continue; + if (same <= e->s_lno) + continue; + if (tlno < e->s_lno + e->num_lines) + blame_overlap(sb, e, tlno, plno, same, parent); + } +} + +static int pass_blame_to_parent(struct scoreboard *sb, + struct origin *target, + struct origin *parent) +{ + int i, last_in_target, plno, tlno; + struct patch *patch; + + last_in_target = find_last_in_target(sb, target); + if (last_in_target < 0) + return 1; /* nothing remains for this target */ + + patch = get_patch(parent, target); + plno = tlno = 0; + for (i = 0; i < patch->num; i++) { + struct chunk *chunk = &patch->chunks[i]; + + if (DEBUG) + fprintf(stderr, + "plno = %d, tlno = %d, " + "same as parent up to %d, resync %d and %d\n", + plno, tlno, + chunk->same, chunk->p_next, chunk->t_next); + blame_chunk(sb, tlno, plno, chunk->same, target, parent); + plno = chunk->p_next; + tlno = chunk->t_next; + } + /* rest (i.e. anything above tlno) are the same as parent */ + blame_chunk(sb, tlno, plno, last_in_target, target, parent); + + free_patch(patch); + return 0; +} + +#define MAXPARENT 16 + +static void pass_blame(struct scoreboard *sb, struct origin *origin) +{ + int i; + struct commit *commit = origin->commit; + struct commit_list *parent; + struct origin *parent_origin[MAXPARENT], *porigin; + + memset(parent_origin, 0, sizeof(parent_origin)); + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct commit *p = parent->item; + + if (parse_commit(p)) + continue; + porigin = find_origin(sb, parent->item, origin->path); + if (!porigin) + porigin = find_rename(sb, parent->item, origin); + if (!porigin) + continue; + if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { + struct blame_entry *e; + for (e = sb->ent; e; e = e->next) + if (e->suspect == origin) + e->suspect = porigin; + /* now everything blamed for origin is blamed for + * porigin, we do not need to keep it anymore. + * Do not free porigin (or the ones we got from + * earlier round); they may still be used elsewhere. + */ + free_origin(origin); + return; + } + parent_origin[i] = porigin; + } + + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct origin *porigin = parent_origin[i]; + if (!porigin) + continue; + if (pass_blame_to_parent(sb, origin, porigin)) + return; + } +} + +static void assign_blame(struct scoreboard *sb, struct rev_info *revs) +{ + while (1) { + struct blame_entry *ent; + struct commit *commit; + struct origin *suspect = NULL; + + /* find one suspect to break down */ + for (ent = sb->ent; !suspect && ent; ent = ent->next) + if (!ent->guilty) + suspect = ent->suspect; + if (!suspect) + return; /* all done */ + + commit = suspect->commit; + parse_commit(commit); + if (!(commit->object.flags & UNINTERESTING) && + !(revs->max_age != -1 && commit->date < revs->max_age)) + pass_blame(sb, suspect); + + /* Take responsibility for the remaining entries */ + for (ent = sb->ent; ent; ent = ent->next) + if (ent->suspect == suspect) + ent->guilty = 1; + } +} + +static const char *format_time(unsigned long time, const char *tz_str, + int show_raw_time) +{ + static char time_buf[128]; + time_t t = time; + int minutes, tz; + struct tm *tm; + + if (show_raw_time) { + sprintf(time_buf, "%lu %s", time, tz_str); + return time_buf; + } + + tz = atoi(tz_str); + minutes = tz < 0 ? -tz : tz; + minutes = (minutes / 100)*60 + (minutes % 100); + minutes = tz < 0 ? -minutes : minutes; + t = time + minutes * 60; + tm = gmtime(&t); + + strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); + strcat(time_buf, tz_str); + return time_buf; +} + +struct commit_info +{ + char *author; + char *author_mail; + unsigned long author_time; + char *author_tz; + + /* filled only when asked for details */ + char *committer; + char *committer_mail; + unsigned long committer_time; + char *committer_tz; + + char *summary; +}; + +static void get_ac_line(const char *inbuf, const char *what, + int bufsz, char *person, char **mail, + unsigned long *time, char **tz) +{ + int len; + char *tmp, *endp; + + tmp = strstr(inbuf, what); + if (!tmp) + goto error_out; + tmp += strlen(what); + endp = strchr(tmp, '\n'); + if (!endp) + len = strlen(tmp); + else + len = endp - tmp; + if (bufsz <= len) { + error_out: + /* Ugh */ + person = *mail = *tz = "(unknown)"; + *time = 0; + return; + } + memcpy(person, tmp, len); + + tmp = person; + tmp += len; + *tmp = 0; + while (*tmp != ' ') + tmp--; + *tz = tmp+1; + + *tmp = 0; + while (*tmp != ' ') + tmp--; + *time = strtoul(tmp, NULL, 10); + + *tmp = 0; + while (*tmp != ' ') + tmp--; + *mail = tmp + 1; + *tmp = 0; +} + +static void get_commit_info(struct commit *commit, + struct commit_info *ret, + int detailed) +{ + int len; + char *tmp, *endp; + static char author_buf[1024]; + static char committer_buf[1024]; + static char summary_buf[1024]; + + ret->author = author_buf; + get_ac_line(commit->buffer, "\nauthor ", + sizeof(author_buf), author_buf, &ret->author_mail, + &ret->author_time, &ret->author_tz); + + if (!detailed) + return; + + ret->committer = committer_buf; + get_ac_line(commit->buffer, "\ncommitter ", + sizeof(committer_buf), committer_buf, &ret->committer_mail, + &ret->committer_time, &ret->committer_tz); + + ret->summary = summary_buf; + tmp = strstr(commit->buffer, "\n\n"); + if (!tmp) { + error_out: + sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); + return; + } + tmp += 2; + endp = strchr(tmp, '\n'); + if (!endp) + goto error_out; + len = endp - tmp; + if (len >= sizeof(summary_buf)) + goto error_out; + memcpy(summary_buf, tmp, len); + summary_buf[len] = 0; +} + +#define OUTPUT_ANNOTATE_COMPAT 001 +#define OUTPUT_LONG_OBJECT_NAME 002 +#define OUTPUT_RAW_TIMESTAMP 004 +#define OUTPUT_PORCELAIN 010 +#define OUTPUT_SHOW_NAME 020 +#define OUTPUT_SHOW_NUMBER 040 + +static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) +{ + int cnt; + const char *cp; + struct origin *suspect = ent->suspect; + char hex[41]; + + strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); + printf("%s%c%d %d %d\n", + hex, + ent->guilty ? ' ' : '*', // purely for debugging + ent->s_lno + 1, + ent->lno + 1, + ent->num_lines); + if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { + struct commit_info ci; + suspect->commit->object.flags |= METAINFO_SHOWN; + get_commit_info(suspect->commit, &ci, 1); + printf("author %s\n", ci.author); + printf("author-mail %s\n", ci.author_mail); + printf("author-time %lu\n", ci.author_time); + printf("author-tz %s\n", ci.author_tz); + printf("committer %s\n", ci.committer); + printf("committer-mail %s\n", ci.committer_mail); + printf("committer-time %lu\n", ci.committer_time); + printf("committer-tz %s\n", ci.committer_tz); + printf("filename %s\n", suspect->path); + printf("summary %s\n", ci.summary); + } + else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH) + printf("filename %s\n", suspect->path); + + cp = nth_line(sb, ent->lno); + for (cnt = 0; cnt < ent->num_lines; cnt++) { + char ch; + if (cnt) + printf("%s %d %d\n", hex, + ent->s_lno + 1 + cnt, + ent->lno + 1 + cnt); + putchar('\t'); + do { + ch = *cp++; + putchar(ch); + } while (ch != '\n' && + cp < sb->final_buf + sb->final_buf_size); + } +} + +static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) +{ + int cnt; + const char *cp; + struct origin *suspect = ent->suspect; + struct commit_info ci; + char hex[41]; + int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); + + get_commit_info(suspect->commit, &ci, 1); + strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); + + cp = nth_line(sb, ent->lno); + for (cnt = 0; cnt < ent->num_lines; cnt++) { + char ch; + + printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex); + if (opt & OUTPUT_ANNOTATE_COMPAT) + printf("\t(%10s\t%10s\t%d)", ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + ent->lno + 1 + cnt); + else { + if (opt & OUTPUT_SHOW_NAME) + printf(" %-*.*s", longest_file, longest_file, + suspect->path); + if (opt & OUTPUT_SHOW_NUMBER) + printf(" %*d", max_orig_digits, + ent->s_lno + 1 + cnt); + printf(" (%-*.*s %10s %*d) ", + longest_author, longest_author, ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + max_digits, ent->lno + 1 + cnt); + } + do { + ch = *cp++; + putchar(ch); + } while (ch != '\n' && + cp < sb->final_buf + sb->final_buf_size); + } +} + +static void output(struct scoreboard *sb, int option) +{ + struct blame_entry *ent; + + if (option & OUTPUT_PORCELAIN) { + for (ent = sb->ent; ent; ent = ent->next) { + struct blame_entry *oth; + struct origin *suspect = ent->suspect; + struct commit *commit = suspect->commit; + if (commit->object.flags & MORE_THAN_ONE_PATH) + continue; + for (oth = ent->next; oth; oth = oth->next) { + if ((oth->suspect->commit != commit) || + !strcmp(oth->suspect->path, suspect->path)) + continue; + commit->object.flags |= MORE_THAN_ONE_PATH; + break; + } + } + } + + for (ent = sb->ent; ent; ent = ent->next) { + if (option & OUTPUT_PORCELAIN) + emit_porcelain(sb, ent); + else + emit_other(sb, ent, option); + } +} + +static int prepare_lines(struct scoreboard *sb) +{ + const char *buf = sb->final_buf; + unsigned long len = sb->final_buf_size; + int num = 0, incomplete = 0, bol = 1; + + if (len && buf[len-1] != '\n') + incomplete++; /* incomplete line at the end */ + while (len--) { + if (bol) { + sb->lineno = xrealloc(sb->lineno, + sizeof(int* ) * (num + 1)); + sb->lineno[num] = buf - sb->final_buf; + bol = 0; + } + if (*buf++ == '\n') { + num++; + bol = 1; + } + } + sb->num_lines = num + incomplete; + return sb->num_lines; +} + +static int read_ancestry(const char *graft_file) +{ + FILE *fp = fopen(graft_file, "r"); + char buf[1024]; + if (!fp) + return -1; + while (fgets(buf, sizeof(buf), fp)) { + /* The format is just "Commit Parent1 Parent2 ...\n" */ + int len = strlen(buf); + struct commit_graft *graft = read_graft_line(buf, len); + register_commit_graft(graft, 0); + } + fclose(fp); + return 0; +} + +static int lineno_width(int lines) +{ + int i, width; + + for (width = 1, i = 10; i <= lines + 1; width++) + i *= 10; + return width; +} + +static void find_alignment(struct scoreboard *sb, int *option) +{ + int longest_src_lines = 0; + int longest_dst_lines = 0; + struct blame_entry *e; + + for (e = sb->ent; e; e = e->next) { + struct origin *suspect = e->suspect; + struct commit_info ci; + int num; + + if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { + suspect->commit->object.flags |= METAINFO_SHOWN; + get_commit_info(suspect->commit, &ci, 1); + if (strcmp(suspect->path, sb->path)) + *option |= OUTPUT_SHOW_NAME; + num = strlen(suspect->path); + if (longest_file < num) + longest_file = num; + num = strlen(ci.author); + if (longest_author < num) + longest_author = num; + } + num = e->s_lno + e->num_lines; + if (longest_src_lines < num) + longest_src_lines = num; + num = e->lno + e->num_lines; + if (longest_dst_lines < num) + longest_dst_lines = num; + } + max_orig_digits = lineno_width(longest_src_lines); + max_digits = lineno_width(longest_dst_lines); +} + +static int has_path_in_work_tree(const char *path) +{ + struct stat st; + return !lstat(path, &st); +} + +int cmd_pickaxe(int argc, const char **argv, const char *prefix) +{ + struct rev_info revs; + const char *path; + struct scoreboard sb; + struct origin *o; + struct blame_entry *ent; + int i, seen_dashdash, unk; + long bottom, top, lno; + int output_option = 0; + const char *revs_file = NULL; + const char *final_commit_name = NULL; + char type[10]; + + bottom = top = 0; + seen_dashdash = 0; + for (unk = i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (*arg != '-') + break; + else if (!strcmp("-c", arg)) + output_option |= OUTPUT_ANNOTATE_COMPAT; + else if (!strcmp("-t", arg)) + output_option |= OUTPUT_RAW_TIMESTAMP; + else if (!strcmp("-l", arg)) + output_option |= OUTPUT_LONG_OBJECT_NAME; + else if (!strcmp("-S", arg) && ++i < argc) + revs_file = argv[i]; + else if (!strcmp("-L", arg) && ++i < argc) { + char *term; + arg = argv[i]; + if (bottom || top) + die("More than one '-L n,m' option given"); + bottom = strtol(arg, &term, 10); + if (*term == ',') { + top = strtol(term + 1, &term, 10); + if (*term) + usage(pickaxe_usage); + } + if (bottom && top && top < bottom) { + unsigned long tmp; + tmp = top; top = bottom; bottom = tmp; + } + } + else if (!strcmp("-f", arg) || + !strcmp("--show-name", arg)) + output_option |= OUTPUT_SHOW_NAME; + else if (!strcmp("-n", arg) || + !strcmp("--show-number", arg)) + output_option |= OUTPUT_SHOW_NUMBER; + else if (!strcmp("-p", arg) || + !strcmp("--porcelain", arg)) + output_option |= OUTPUT_PORCELAIN; + else if (!strcmp("--", arg)) { + seen_dashdash = 1; + i++; + break; + } + else + argv[unk++] = arg; + } + + /* We have collected options unknown to us in argv[1..unk] + * which are to be passed to revision machinery if we are + * going to do the "bottom" procesing. + * + * The remaining are: + * + * (1) if seen_dashdash, its either + * "-options -- " or + * "-options -- ". + * but the latter is allowed only if there is no + * options that we passed to revision machinery. + * + * (2) otherwise, we may have "--" somewhere later and + * might be looking at the first one of multiple 'rev' + * parameters (e.g. " master ^next ^maint -- path"). + * See if there is a dashdash first, and give the + * arguments before that to revision machinery. + * After that there must be one 'path'. + * + * (3) otherwise, its one of the three: + * "-options " + * "-options " + * "-options " + * but again the first one is allowed only if + * there is no options that we passed to revision + * machinery. + */ + + if (seen_dashdash) { + /* (1) */ + if (argc <= i) + usage(pickaxe_usage); + path = argv[i]; + if (i + 1 == argc - 1) { + if (unk != 1) + usage(pickaxe_usage); + argv[unk++] = argv[i + 1]; + } + else if (i + 1 != argc) + /* garbage at end */ + usage(pickaxe_usage); + } + else { + int j; + for (j = i; !seen_dashdash && j < argc; j++) + if (!strcmp(argv[j], "--")) + seen_dashdash = j; + if (seen_dashdash) { + if (seen_dashdash + 1 != argc - 1) + usage(pickaxe_usage); + path = argv[seen_dashdash + 1]; + for (j = i; j < seen_dashdash; j++) + argv[unk++] = argv[j]; + } + else { + /* (3) */ + path = argv[i]; + if (i + 1 == argc - 1) { + final_commit_name = argv[i + 1]; + + /* if (unk == 1) we could be getting + * old-style + */ + if (unk == 1 && !has_path_in_work_tree(path)) { + path = argv[i + 1]; + final_commit_name = argv[i]; + } + } + else if (i != argc - 1) + usage(pickaxe_usage); /* garbage at end */ + + if (!has_path_in_work_tree(path)) + die("cannot stat path %s: %s", + path, strerror(errno)); + } + } + + if (final_commit_name) + argv[unk++] = final_commit_name; + + /* Now we got rev and path. We do not want the path pruning + * but we may want "bottom" processing. + */ + argv[unk] = NULL; + + init_revisions(&revs, NULL); + setup_revisions(unk, argv, &revs, "HEAD"); + memset(&sb, 0, sizeof(sb)); + + /* There must be one and only one positive commit in the + * revs->pending array. + */ + for (i = 0; i < revs.pending.nr; i++) { + struct object *obj = revs.pending.objects[i].item; + if (obj->flags & UNINTERESTING) + continue; + while (obj->type == OBJ_TAG) + obj = deref_tag(obj, NULL, 0); + if (obj->type != OBJ_COMMIT) + die("Non commit %s?", + revs.pending.objects[i].name); + if (sb.final) + die("More than one commit to dig from %s and %s?", + revs.pending.objects[i].name, + final_commit_name); + sb.final = (struct commit *) obj; + final_commit_name = revs.pending.objects[i].name; + } + + if (!sb.final) { + /* "--not A B -- path" without anything positive */ + unsigned char head_sha1[20]; + + final_commit_name = "HEAD"; + if (get_sha1(final_commit_name, head_sha1)) + die("No such ref: HEAD"); + sb.final = lookup_commit_reference(head_sha1); + add_pending_object(&revs, &(sb.final->object), "HEAD"); + } + + /* If we have bottom, this will mark the ancestors of the + * bottom commits we would reach while traversing as + * uninteresting. + */ + prepare_revision_walk(&revs); + + o = find_origin(&sb, sb.final, path); + if (!o) + die("no such path %s in %s", path, final_commit_name); + + sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); + lno = prepare_lines(&sb); + + if (bottom < 1) + bottom = 1; + if (top < 1) + top = lno; + bottom--; + if (lno < top) + die("file %s has only %lu lines", path, lno); + + ent = xcalloc(1, sizeof(*ent)); + ent->lno = bottom; + ent->num_lines = top - bottom; + ent->suspect = o; + ent->s_lno = bottom; + + sb.ent = ent; + sb.path = path; + + if (revs_file && read_ancestry(revs_file)) + die("reading graft file %s failed: %s", + revs_file, strerror(errno)); + + assign_blame(&sb, &revs); + + coalesce(&sb); + + if (!(output_option & OUTPUT_PORCELAIN)) + find_alignment(&sb, &output_option); + + output(&sb, output_option); + free((void *)sb.final_buf); + for (ent = sb.ent; ent; ) { + struct blame_entry *e = ent->next; + free(ent); + ent = e; + } + return 0; +} diff --git a/builtin.h b/builtin.h index f9fa9ff..7451ce6 100644 --- a/builtin.h +++ b/builtin.h @@ -39,6 +39,7 @@ extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_pickaxe(int argc, const char **argv, const char *prefix); extern int cmd_prune(int argc, const char **argv, const char *prefix); extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index e089b53..6164380 100644 --- a/git.c +++ b/git.c @@ -245,6 +245,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "mv", cmd_mv, RUN_SETUP }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "pickaxe", cmd_pickaxe, RUN_SETUP }, { "prune", cmd_prune, RUN_SETUP }, { "prune-packed", cmd_prune_packed, RUN_SETUP }, { "push", cmd_push, RUN_SETUP }, diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index 8baf2fe..b5ceba4 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -4,6 +4,7 @@ check_count () { head= case "$1" in -h) head="$2"; shift; shift ;; esac + echo "$PROG file $head" >&4 $PROG file $head >.result || return 1 cat .result | perl -e ' my %expect = (@ARGV); diff --git a/t/t8003-pickaxe.sh b/t/t8003-pickaxe.sh new file mode 100755 index 0000000..d09d1c9 --- /dev/null +++ b/t/t8003-pickaxe.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +test_description='git-pickaxe' +. ./test-lib.sh + +PROG='git pickaxe -c' +. ../annotate-tests.sh + +test_done -- cgit v0.10.2-6-g49f6 From d24bba8008d8e537cb48e9760f7621cbe7ae9e38 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 19 Oct 2006 18:49:30 -0700 Subject: git-pickaxe -M: blame line movements within a file. This makes pickaxe more intelligent than the classic blame. A typical example is a change that moves one static C function from lower part of the file to upper part of the same file, because you added a new caller in the middle. The versions in the parent and the child would look like this: parent child A static foo() { B ... C } D A E B F C G D static foo() { ... call foo(); ... E } F H G H With the classic blame algorithm, we can blame lines A B C D E F G and H to the parent. The child is guilty of introducing the line "... call foo();", and the blame is placed on the child. However, the classic blame algorithm fails to notice that the implementation of foo() at the top of the file is not new, and moved from the lower part of the parent. This commit introduces detection of such line movements, and correctly blames the lines that were simply moved in the file to the parent. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt index 7685bd0..ebae20f 100644 --- a/Documentation/git-pickaxe.txt +++ b/Documentation/git-pickaxe.txt @@ -7,7 +7,9 @@ git-pickaxe - Show what revision and author last modified each line of a file SYNOPSIS -------- -'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [] [--] +[verse] +'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] + [-M] [--since=] [] [--] DESCRIPTION ----------- @@ -61,6 +63,16 @@ OPTIONS -p, --porcelain:: Show in a format designed for machine consumption. +-M:: + Detect moving lines in the file as well. When a commit + moves a block of lines in a file (e.g. the original file + has A and then B, and the commit changes it to B and + then A), traditional 'blame' algorithm typically blames + the lines that were moved up (i.e. B) to the parent and + assigns blame to the lines that were moved down (i.e. A) + to the child commit. With this option, both groups of + lines are blamed on the parent. + -h, --help:: Show help message. diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index cb69fcc..e6ce655 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -19,7 +19,7 @@ #include static char pickaxe_usage[] = -"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [commit] [--] file\n" +"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" " -t, --time Show raw timestamp (Default: off)\n" @@ -27,6 +27,7 @@ static char pickaxe_usage[] = " -n, --show-number Show original linenumber (Default: off)\n" " -p, --porcelain Show in a format designed for machine consumption\n" " -L n,m Process only line range n,m, counting from 1\n" +" -M Find line movements within the file\n" " -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; static int longest_file; @@ -36,6 +37,8 @@ static int max_digits; #define DEBUG 0 +#define PICKAXE_BLAME_MOVE 01 + /* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */ #define METAINFO_SHOWN (1u<<12) #define MORE_THAN_ONE_PATH (1u<<13) @@ -542,9 +545,99 @@ static int pass_blame_to_parent(struct scoreboard *sb, return 0; } +static void copy_split_if_better(struct blame_entry best_so_far[3], + struct blame_entry this[3]) +{ + if (!this[1].suspect) + return; + if (best_so_far[1].suspect && + (this[1].num_lines < best_so_far[1].num_lines)) + return; + memcpy(best_so_far, this, sizeof(struct blame_entry [3])); +} + +static void find_copy_in_blob(struct scoreboard *sb, + struct blame_entry *ent, + struct origin *parent, + struct blame_entry split[3], + mmfile_t *file_p) +{ + const char *cp; + int cnt; + mmfile_t file_o; + struct patch *patch; + int i, plno, tlno; + + cp = nth_line(sb, ent->lno); + file_o.ptr = (char*) cp; + cnt = ent->num_lines; + + while (cnt && cp < sb->final_buf + sb->final_buf_size) { + if (*cp++ == '\n') + cnt--; + } + file_o.size = cp - file_o.ptr; + + patch = compare_buffer(file_p, &file_o, 1); + + memset(split, 0, sizeof(struct blame_entry [3])); + plno = tlno = 0; + for (i = 0; i < patch->num; i++) { + struct chunk *chunk = &patch->chunks[i]; + + /* tlno to chunk->same are the same as ent */ + if (ent->num_lines <= tlno) + break; + if (tlno < chunk->same) { + struct blame_entry this[3]; + split_overlap(this, ent, + tlno + ent->s_lno, plno, + chunk->same + ent->s_lno, + parent); + copy_split_if_better(split, this); + } + plno = chunk->p_next; + tlno = chunk->t_next; + } + free_patch(patch); +} + +static int find_move_in_parent(struct scoreboard *sb, + struct origin *target, + struct origin *parent) +{ + int last_in_target; + struct blame_entry *ent, split[3]; + mmfile_t file_p; + char type[10]; + char *blob_p; + + last_in_target = find_last_in_target(sb, target); + if (last_in_target < 0) + return 1; /* nothing remains for this target */ + + blob_p = read_sha1_file(parent->blob_sha1, type, + (unsigned long *) &file_p.size); + file_p.ptr = blob_p; + if (!file_p.ptr) { + free(blob_p); + return 0; + } + + for (ent = sb->ent; ent; ent = ent->next) { + if (ent->suspect != target || ent->guilty) + continue; + find_copy_in_blob(sb, ent, parent, split, &file_p); + if (split[1].suspect) + split_blame(sb, split, ent); + } + free(blob_p); + return 0; +} + #define MAXPARENT 16 -static void pass_blame(struct scoreboard *sb, struct origin *origin) +static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) { int i; struct commit *commit = origin->commit; @@ -589,9 +682,24 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin) if (pass_blame_to_parent(sb, origin, porigin)) return; } + + /* + * Optionally run "miff" to find moves in parents' files here. + */ + if (opt & PICKAXE_BLAME_MOVE) + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct origin *porigin = parent_origin[i]; + if (!porigin) + continue; + if (find_move_in_parent(sb, origin, porigin)) + return; + } + } -static void assign_blame(struct scoreboard *sb, struct rev_info *revs) +static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) { while (1) { struct blame_entry *ent; @@ -609,7 +717,7 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs) parse_commit(commit); if (!(commit->object.flags & UNINTERESTING) && !(revs->max_age != -1 && commit->date < revs->max_age)) - pass_blame(sb, suspect); + pass_blame(sb, suspect, opt); /* Take responsibility for the remaining entries */ for (ent = sb->ent; ent; ent = ent->next) @@ -967,13 +1075,14 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) struct scoreboard sb; struct origin *o; struct blame_entry *ent; - int i, seen_dashdash, unk; + int i, seen_dashdash, unk, opt; long bottom, top, lno; int output_option = 0; const char *revs_file = NULL; const char *final_commit_name = NULL; char type[10]; + opt = 0; bottom = top = 0; seen_dashdash = 0; for (unk = i = 1; i < argc; i++) { @@ -988,6 +1097,8 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) output_option |= OUTPUT_LONG_OBJECT_NAME; else if (!strcmp("-S", arg) && ++i < argc) revs_file = argv[i]; + else if (!strcmp("-M", arg)) + opt |= PICKAXE_BLAME_MOVE; else if (!strcmp("-L", arg) && ++i < argc) { char *term; arg = argv[i]; @@ -1176,7 +1287,7 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) die("reading graft file %s failed: %s", revs_file, strerror(errno)); - assign_blame(&sb, &revs); + assign_blame(&sb, &revs, opt); coalesce(&sb); -- cgit v0.10.2-6-g49f6 From 18abd745a05197f498219f5ba88ce238a3d51580 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 19 Oct 2006 18:50:17 -0700 Subject: git-pickaxe -C: blame cut-and-pasted lines. This completes the initial round of git-pickaxe. In addition to the detection of line movements we already have, this finds new lines that were created by moving or cutting-and-pasting lines from different files in the parent. With this, git pickaxe -f -n -C v1.4.0 -- revision.c finds that a major part of that file actually came from rev-list.c when Linus split the latter at commit ae563642 and blames them to earlier commits that touch rev-list.c. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt index ebae20f..6d22fd9 100644 --- a/Documentation/git-pickaxe.txt +++ b/Documentation/git-pickaxe.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] - [-M] [--since=] [] [--] + [-M] [-C] [-C] [--since=] [] [--] DESCRIPTION ----------- @@ -73,6 +73,14 @@ OPTIONS to the child commit. With this option, both groups of lines are blamed on the parent. +-C:: + In addition to `-M`, detect lines copied from other + files that were modified in the same commit. This is + useful when you reorganize your program and move code + around across files. When this option is given twice, + the command looks for copies from all other files in the + parent for the commit that creates the file in addition. + -h, --help:: Show help message. diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index e6ce655..74c7c9a 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -19,7 +19,7 @@ #include static char pickaxe_usage[] = -"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [commit] [--] file\n" +"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" " -t, --time Show raw timestamp (Default: off)\n" @@ -27,7 +27,7 @@ static char pickaxe_usage[] = " -n, --show-number Show original linenumber (Default: off)\n" " -p, --porcelain Show in a format designed for machine consumption\n" " -L n,m Process only line range n,m, counting from 1\n" -" -M Find line movements within the file\n" +" -M, -C Find line movements within and across files\n" " -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; static int longest_file; @@ -38,6 +38,8 @@ static int max_digits; #define DEBUG 0 #define PICKAXE_BLAME_MOVE 01 +#define PICKAXE_BLAME_COPY 02 +#define PICKAXE_BLAME_COPY_HARDER 04 /* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */ #define METAINFO_SHOWN (1u<<12) @@ -635,6 +637,78 @@ static int find_move_in_parent(struct scoreboard *sb, return 0; } +static int find_copy_in_parent(struct scoreboard *sb, + struct origin *target, + struct commit *parent, + struct origin *porigin, + int opt) +{ + struct diff_options diff_opts; + const char *paths[1]; + struct blame_entry *ent; + int i; + + if (find_last_in_target(sb, target) < 0) + return 1; /* nothing remains for this target */ + + diff_setup(&diff_opts); + diff_opts.recursive = 1; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + + /* Try "find copies harder" on new path */ + if ((opt & PICKAXE_BLAME_COPY_HARDER) && + (!porigin || strcmp(target->path, porigin->path))) { + diff_opts.detect_rename = DIFF_DETECT_COPY; + diff_opts.find_copies_harder = 1; + } + paths[0] = NULL; + diff_tree_setup_paths(paths, &diff_opts); + if (diff_setup_done(&diff_opts) < 0) + die("diff-setup"); + diff_tree_sha1(parent->tree->object.sha1, + target->commit->tree->object.sha1, + "", &diff_opts); + diffcore_std(&diff_opts); + + for (ent = sb->ent; ent; ent = ent->next) { + struct blame_entry split[3]; + if (ent->suspect != target || ent->guilty) + continue; + + memset(split, 0, sizeof(split)); + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + struct origin *norigin; + mmfile_t file_p; + char type[10]; + char *blob; + struct blame_entry this[3]; + + if (!DIFF_FILE_VALID(p->one)) + continue; /* does not exist in parent */ + if (porigin && !strcmp(p->one->path, porigin->path)) + /* find_move already dealt with this path */ + continue; + norigin = find_origin(sb, parent, p->one->path); + + blob = read_sha1_file(norigin->blob_sha1, type, + (unsigned long *) &file_p.size); + file_p.ptr = blob; + if (!file_p.ptr) { + free(blob); + continue; + } + find_copy_in_blob(sb, ent, norigin, this, &file_p); + copy_split_if_better(split, this); + } + if (split[1].suspect) + split_blame(sb, split, ent); + } + diff_flush(&diff_opts); + + return 0; +} + #define MAXPARENT 16 static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) @@ -697,6 +771,18 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) return; } + /* + * Optionally run "ciff" to find copies from parents' files here. + */ + if (opt & PICKAXE_BLAME_COPY) + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct origin *porigin = parent_origin[i]; + if (find_copy_in_parent(sb, origin, parent->item, + porigin, opt)) + return; + } } static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) @@ -1099,6 +1185,11 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) revs_file = argv[i]; else if (!strcmp("-M", arg)) opt |= PICKAXE_BLAME_MOVE; + else if (!strcmp("-C", arg)) { + if (opt & PICKAXE_BLAME_COPY) + opt |= PICKAXE_BLAME_COPY_HARDER; + opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; + } else if (!strcmp("-L", arg) && ++i < argc) { char *term; arg = argv[i]; -- cgit v0.10.2-6-g49f6 From 96a035d1db9082d244867033020d0ceb571cf94e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 20 Oct 2006 16:37:49 -0700 Subject: pager: default to LESS=FRS Recent change to paginate "git diff" by default is often irritating when you do not have any change (or very small change) in your working tree. Signed-off-by: Junio C Hamano diff --git a/pager.c b/pager.c index dcb398d..8bd33a1 100644 --- a/pager.c +++ b/pager.c @@ -50,7 +50,7 @@ void setup_pager(void) close(fd[0]); close(fd[1]); - setenv("LESS", "-RS", 0); + setenv("LESS", "FRS", 0); run_pager(pager); die("unable to execute pager '%s'", pager); exit(255); -- cgit v0.10.2-6-g49f6 From 0b92f1a9d213644d2cd6c1870091c090a6b7eca9 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Fri, 20 Oct 2006 23:24:32 +0200 Subject: Fix typo in show-index.c Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano diff --git a/show-index.c b/show-index.c index c21d660..a30a2de 100644 --- a/show-index.c +++ b/show-index.c @@ -8,7 +8,7 @@ int main(int argc, char **argv) static unsigned int top_index[256]; if (fread(top_index, sizeof(top_index), 1, stdin) != 1) - die("unable to read idex"); + die("unable to read index"); nr = 0; for (i = 0; i < 256; i++) { unsigned n = ntohl(top_index[i]); -- cgit v0.10.2-6-g49f6 From 87b787ac77e0d8f81468dcba2a39c9d0287870c3 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Fri, 20 Oct 2006 23:38:31 +0400 Subject: git-clone: define die() and use it. Signed-off-by: Dmitry V. Levin Signed-off-by: Junio C Hamano diff --git a/git-clone.sh b/git-clone.sh index bf54a11..786d65a 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -8,11 +8,15 @@ # See git-sh-setup why. unset CDPATH -usage() { - echo >&2 "Usage: $0 [--template=] [--use-separate-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [--origin ] [-n] []" +die() { + echo >&2 "$@" exit 1 } +usage() { + die "Usage: $0 [--template=] [--use-separate-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [--origin ] [-n] []" +} + get_repo_base() { (cd "$1" && (cd .git ; pwd)) 2> /dev/null } @@ -35,11 +39,9 @@ clone_dumb_http () { "`git-repo-config --bool http.noEPSV`" = true ]; then curl_extra_args="${curl_extra_args} --disable-epsv" fi - http_fetch "$1/info/refs" "$clone_tmp/refs" || { - echo >&2 "Cannot get remote repository information. + http_fetch "$1/info/refs" "$clone_tmp/refs" || + die "Cannot get remote repository information. Perhaps git-update-server-info needs to be run there?" - exit 1; - } while read sha1 refname do name=`expr "z$refname" : 'zrefs/\(.*\)'` && @@ -143,17 +145,12 @@ while '') usage ;; */*) - echo >&2 "'$2' is not suitable for an origin name" - exit 1 + die "'$2' is not suitable for an origin name" esac - git-check-ref-format "heads/$2" || { - echo >&2 "'$2' is not suitable for a branch name" - exit 1 - } - test -z "$origin_override" || { - echo >&2 "Do not give more than one --origin options." - exit 1 - } + git-check-ref-format "heads/$2" || + die "'$2' is not suitable for a branch name" + test -z "$origin_override" || + die "Do not give more than one --origin options." origin_override=yes origin="$2"; shift ;; @@ -169,24 +166,19 @@ do done repo="$1" -if test -z "$repo" -then - echo >&2 'you must specify a repository to clone.' - exit 1 -fi +test -n "$repo" || + die 'you must specify a repository to clone.' # --bare implies --no-checkout if test yes = "$bare" then if test yes = "$origin_override" then - echo >&2 '--bare and --origin $origin options are incompatible.' - exit 1 + die '--bare and --origin $origin options are incompatible.' fi if test t = "$use_separate_remote" then - echo >&2 '--bare and --use-separate-remote options are incompatible.' - exit 1 + die '--bare and --use-separate-remote options are incompatible.' fi no_checkout=yes fi @@ -206,7 +198,7 @@ fi dir="$2" # Try using "humanish" part of source repo if user didn't specify one [ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') -[ -e "$dir" ] && echo "$dir already exists." && usage +[ -e "$dir" ] && die "destination directory '$dir' already exists." mkdir -p "$dir" && D=$(cd "$dir" && pwd) && trap 'err=$?; cd ..; rm -rf "$D"; exit $err' 0 @@ -233,7 +225,7 @@ then cd reference-tmp && tar xf -) else - echo >&2 "$reference: not a local directory." && usage + die "reference repository '$reference' is not a local directory." fi fi @@ -242,10 +234,8 @@ rm -f "$GIT_DIR/CLONE_HEAD" # We do local magic only when the user tells us to. case "$local,$use_local" in yes,yes) - ( cd "$repo/objects" ) || { - echo >&2 "-l flag seen but $repo is not local." - exit 1 - } + ( cd "$repo/objects" ) || + die "-l flag seen but repository '$repo' is not local." case "$local_shared" in no) @@ -307,18 +297,15 @@ yes,yes) then clone_dumb_http "$repo" "$D" else - echo >&2 "http transport not supported, rebuild Git with curl support" - exit 1 + die "http transport not supported, rebuild Git with curl support" fi ;; *) case "$upload_pack" in '') git-fetch-pack --all -k $quiet "$repo" ;; *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;; - esac >"$GIT_DIR/CLONE_HEAD" || { - echo >&2 "fetch-pack from '$repo' failed." - exit 1 - } + esac >"$GIT_DIR/CLONE_HEAD" || + die "fetch-pack from '$repo' failed." ;; esac ;; -- cgit v0.10.2-6-g49f6 From 2d477051ef260aad352d63fc7d9c07e4ebb4359b Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 20 Oct 2006 14:45:21 -0400 Subject: add the capability for index-pack to read from a stream This patch only adds the streaming capability to index-pack. Although the code is different it has the exact same functionality as before to make sure nothing broke. This is in preparation for receiving packs over the net, parse them on the fly, fix them up if they are "thin" packs, and keep the resulting pack instead of exploding it into loose objects. But such functionality should come separately. One immediate advantage of this patch is that index-pack can now deal with packs up to 4GB in size even on 32-bit architectures since the pack is not entirely mmap()'d all at once anymore. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/index-pack.c b/index-pack.c index 56c590e..e33f605 100644 --- a/index-pack.c +++ b/index-pack.c @@ -13,6 +13,8 @@ static const char index_pack_usage[] = struct object_entry { unsigned long offset; + unsigned long size; + unsigned int hdr_size; enum object_type type; enum object_type real_type; unsigned char sha1[20]; @@ -36,51 +38,68 @@ struct delta_entry }; static const char *pack_name; -static unsigned char *pack_base; -static unsigned long pack_size; static struct object_entry *objects; static struct delta_entry *deltas; static int nr_objects; static int nr_deltas; -static void open_pack_file(void) +/* We always read in 4kB chunks. */ +static unsigned char input_buffer[4096]; +static unsigned long input_offset, input_len, consumed_bytes; +static SHA_CTX input_ctx; +static int input_fd; + +/* + * Make sure at least "min" bytes are available in the buffer, and + * return the pointer to the buffer. + */ +static void * fill(int min) { - int fd; - struct stat st; + if (min <= input_len) + return input_buffer + input_offset; + if (min > sizeof(input_buffer)) + die("cannot fill %d bytes", min); + if (input_offset) { + SHA1_Update(&input_ctx, input_buffer, input_offset); + memcpy(input_buffer, input_buffer + input_offset, input_len); + input_offset = 0; + } + do { + int ret = xread(input_fd, input_buffer + input_len, + sizeof(input_buffer) - input_len); + if (ret <= 0) { + if (!ret) + die("early EOF"); + die("read error on input: %s", strerror(errno)); + } + input_len += ret; + } while (input_len < min); + return input_buffer; +} + +static void use(int bytes) +{ + if (bytes > input_len) + die("used more bytes than were available"); + input_len -= bytes; + input_offset += bytes; + consumed_bytes += bytes; +} - fd = open(pack_name, O_RDONLY); - if (fd < 0) +static void open_pack_file(void) +{ + input_fd = open(pack_name, O_RDONLY); + if (input_fd < 0) die("cannot open packfile '%s': %s", pack_name, strerror(errno)); - if (fstat(fd, &st)) { - int err = errno; - close(fd); - die("cannot fstat packfile '%s': %s", pack_name, - strerror(err)); - } - pack_size = st.st_size; - pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (pack_base == MAP_FAILED) { - int err = errno; - close(fd); - die("cannot mmap packfile '%s': %s", pack_name, - strerror(err)); - } - close(fd); + SHA1_Init(&input_ctx); } static void parse_pack_header(void) { - const struct pack_header *hdr; - unsigned char sha1[20]; - SHA_CTX ctx; - - /* Ensure there are enough bytes for the header and final SHA1 */ - if (pack_size < sizeof(struct pack_header) + 20) - die("packfile '%s' is too small", pack_name); + struct pack_header *hdr = fill(sizeof(struct pack_header)); /* Header consistency check */ - hdr = (void *)pack_base; if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) die("packfile '%s' signature mismatch", pack_name); if (!pack_version_ok(hdr->hdr_version)) @@ -88,13 +107,8 @@ static void parse_pack_header(void) pack_name, ntohl(hdr->hdr_version)); nr_objects = ntohl(hdr->hdr_entries); - - /* Check packfile integrity */ - SHA1_Init(&ctx); - SHA1_Update(&ctx, pack_base, pack_size - 20); - SHA1_Final(sha1, &ctx); - if (hashcmp(sha1, pack_base + pack_size - 20)) - die("packfile '%s' SHA1 mismatch", pack_name); + use(sizeof(struct pack_header)); + /*fprintf(stderr, "Indexing %d objects\n", nr_objects);*/ } static void bad_object(unsigned long offset, const char *format, @@ -112,85 +126,78 @@ static void bad_object(unsigned long offset, const char *format, ...) pack_name, offset, buf); } -static void *unpack_entry_data(unsigned long offset, - unsigned long *current_pos, unsigned long size) +static void *unpack_entry_data(unsigned long offset, unsigned long size) { - unsigned long pack_limit = pack_size - 20; - unsigned long pos = *current_pos; z_stream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); stream.next_out = buf; stream.avail_out = size; - stream.next_in = pack_base + pos; - stream.avail_in = pack_limit - pos; + stream.next_in = fill(1); + stream.avail_in = input_len; inflateInit(&stream); for (;;) { int ret = inflate(&stream, 0); - if (ret == Z_STREAM_END) + use(input_len - stream.avail_in); + if (stream.total_out == size && ret == Z_STREAM_END) break; if (ret != Z_OK) bad_object(offset, "inflate returned %d", ret); + stream.next_in = fill(1); + stream.avail_in = input_len; } inflateEnd(&stream); - if (stream.total_out != size) - bad_object(offset, "size mismatch (expected %lu, got %lu)", - size, stream.total_out); - *current_pos = pack_limit - stream.avail_in; return buf; } -static void *unpack_raw_entry(unsigned long offset, - enum object_type *obj_type, - unsigned long *obj_size, - union delta_base *delta_base, - unsigned long *next_obj_offset) +static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base) { - unsigned long pack_limit = pack_size - 20; - unsigned long pos = offset; - unsigned char c; + unsigned char *p, c; unsigned long size, base_offset; unsigned shift; - enum object_type type; - void *data; - c = pack_base[pos++]; - type = (c >> 4) & 7; + obj->offset = consumed_bytes; + + p = fill(1); + c = *p; + use(1); + obj->type = (c >> 4) & 7; size = (c & 15); shift = 4; while (c & 0x80) { - if (pos >= pack_limit) - bad_object(offset, "object extends past end of pack"); - c = pack_base[pos++]; + p = fill(1); + c = *p; + use(1); size += (c & 0x7fUL) << shift; shift += 7; } + obj->size = size; - switch (type) { + switch (obj->type) { case OBJ_REF_DELTA: - if (pos + 20 >= pack_limit) - bad_object(offset, "object extends past end of pack"); - hashcpy(delta_base->sha1, pack_base + pos); - pos += 20; + hashcpy(delta_base->sha1, fill(20)); + use(20); break; case OBJ_OFS_DELTA: memset(delta_base, 0, sizeof(*delta_base)); - c = pack_base[pos++]; + p = fill(1); + c = *p; + use(1); base_offset = c & 127; while (c & 128) { base_offset += 1; if (!base_offset || base_offset & ~(~0UL >> 7)) - bad_object(offset, "offset value overflow for delta base object"); - if (pos >= pack_limit) - bad_object(offset, "object extends past end of pack"); - c = pack_base[pos++]; + bad_object(obj->offset, "offset value overflow for delta base object"); + p = fill(1); + c = *p; + use(1); base_offset = (base_offset << 7) + (c & 127); } - delta_base->offset = offset - base_offset; - if (delta_base->offset >= offset) - bad_object(offset, "delta base offset is out of bound"); + delta_base->offset = obj->offset - base_offset; + if (delta_base->offset >= obj->offset) + bad_object(obj->offset, "delta base offset is out of bound"); break; case OBJ_COMMIT: case OBJ_TREE: @@ -198,13 +205,38 @@ static void *unpack_raw_entry(unsigned long offset, case OBJ_TAG: break; default: - bad_object(offset, "bad object type %d", type); + bad_object(obj->offset, "bad object type %d", obj->type); } + obj->hdr_size = consumed_bytes - obj->offset; + + return unpack_entry_data(obj->offset, obj->size); +} + +static void * get_data_from_pack(struct object_entry *obj) +{ + unsigned long from = obj[0].offset + obj[0].hdr_size; + unsigned long len = obj[1].offset - from; + unsigned pg_offset = from % getpagesize(); + unsigned char *map, *data; + z_stream stream; + int st; - data = unpack_entry_data(offset, &pos, size); - *obj_type = type; - *obj_size = size; - *next_obj_offset = pos; + map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE, + input_fd, from - pg_offset); + if (map == MAP_FAILED) + die("cannot mmap packfile '%s': %s", pack_name, strerror(errno)); + data = xmalloc(obj->size); + memset(&stream, 0, sizeof(stream)); + stream.next_out = data; + stream.avail_out = obj->size; + stream.next_in = map + pg_offset; + stream.avail_in = len; + inflateInit(&stream); + while ((st = inflate(&stream, Z_FINISH)) == Z_OK); + inflateEnd(&stream); + if (st != Z_STREAM_END || stream.total_out != obj->size) + die("serious inflate inconsistency"); + munmap(map, len + pg_offset); return data; } @@ -280,15 +312,12 @@ static void resolve_delta(struct delta_entry *delta, void *base_data, unsigned long delta_size; void *result; unsigned long result_size; - enum object_type delta_type; union delta_base delta_base; - unsigned long next_obj_offset; int j, first, last; obj->real_type = type; - delta_data = unpack_raw_entry(obj->offset, &delta_type, - &delta_size, &delta_base, - &next_obj_offset); + delta_data = get_data_from_pack(obj); + delta_size = obj->size; result = patch_delta(base_data, base_size, delta_data, delta_size, &result_size); free(delta_data); @@ -321,13 +350,13 @@ static int compare_delta_entry(const void *a, const void *b) return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ); } -static void parse_pack_objects(void) +/* Parse all objects and return the pack content SHA1 hash */ +static void parse_pack_objects(unsigned char *sha1) { int i; - unsigned long offset = sizeof(struct pack_header); struct delta_entry *delta = deltas; void *data; - unsigned long data_size; + struct stat st; /* * First pass: @@ -337,19 +366,29 @@ static void parse_pack_objects(void) */ for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - obj->offset = offset; - data = unpack_raw_entry(offset, &obj->type, &data_size, - &delta->base, &offset); + data = unpack_raw_entry(obj, &delta->base); obj->real_type = obj->type; if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { nr_deltas++; delta->obj = obj; delta++; } else - sha1_object(data, data_size, obj->type, obj->sha1); + sha1_object(data, obj->size, obj->type, obj->sha1); free(data); } - if (offset != pack_size - 20) + objects[i].offset = consumed_bytes; + + /* Check pack integrity */ + SHA1_Update(&input_ctx, input_buffer, input_offset); + SHA1_Final(sha1, &input_ctx); + if (hashcmp(fill(20), sha1)) + die("packfile '%s' SHA1 mismatch", pack_name); + use(20); + + /* If input_fd is a file, we should have reached its end now. */ + if (fstat(input_fd, &st)) + die("cannot fstat packfile '%s': %s", pack_name, strerror(errno)); + if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes) die("packfile '%s' has junk at the end", pack_name); /* Sort deltas by base SHA1/offset for fast searching */ @@ -378,18 +417,17 @@ static void parse_pack_objects(void) ofs = !find_delta_childs(&base, &ofs_first, &ofs_last); if (!ref && !ofs) continue; - data = unpack_raw_entry(obj->offset, &obj->type, &data_size, - &base, &offset); + data = get_data_from_pack(obj); if (ref) for (j = ref_first; j <= ref_last; j++) if (deltas[j].obj->type == OBJ_REF_DELTA) resolve_delta(&deltas[j], data, - data_size, obj->type); + obj->size, obj->type); if (ofs) for (j = ofs_first; j <= ofs_last; j++) if (deltas[j].obj->type == OBJ_OFS_DELTA) resolve_delta(&deltas[j], data, - data_size, obj->type); + obj->size, obj->type); free(data); } @@ -408,6 +446,10 @@ static int sha1_compare(const void *_a, const void *_b) return hashcmp(a->sha1, b->sha1); } +/* + * On entry *sha1 contains the pack content SHA1 hash, on exit it is + * the SHA1 hash of sorted object names. + */ static void write_index_file(const char *index_name, unsigned char *sha1) { struct sha1file *f; @@ -467,7 +509,7 @@ static void write_index_file(const char *index_name, unsigned char *sha1) sha1write(f, obj->sha1, 20); SHA1_Update(&ctx, obj->sha1, 20); } - sha1write(f, pack_base + pack_size - 20, 20); + sha1write(f, sha1, 20); sha1close(f, NULL, 1); free(sorted_by_sha); SHA1_Final(sha1, &ctx); @@ -513,9 +555,9 @@ int main(int argc, char **argv) open_pack_file(); parse_pack_header(); - objects = xcalloc(nr_objects, sizeof(struct object_entry)); + objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); - parse_pack_objects(); + parse_pack_objects(sha1); free(deltas); write_index_file(index_name, sha1); free(objects); -- cgit v0.10.2-6-g49f6 From 1617baa5878c9b3886926f0f2e6dc52d067ada4a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 20 Oct 2006 18:37:10 -0700 Subject: git-pickaxe: pagenate output by default. Signed-off-by: Junio C Hamano diff --git a/git.c b/git.c index 6164380..b944c37 100644 --- a/git.c +++ b/git.c @@ -245,7 +245,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "mv", cmd_mv, RUN_SETUP }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, - { "pickaxe", cmd_pickaxe, RUN_SETUP }, + { "pickaxe", cmd_pickaxe, RUN_SETUP | USE_PAGER }, { "prune", cmd_prune, RUN_SETUP }, { "prune-packed", cmd_prune_packed, RUN_SETUP }, { "push", cmd_push, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From 1ca6ca876e3553d9609823a70d168c50d7beba7e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 20 Oct 2006 18:48:18 -0700 Subject: git-pickaxe: fix nth_line() We would want to be able to refer to the end of the file as "the beginning of Nth line" for a file that is N lines long. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 74c7c9a..b595299 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -1085,6 +1085,9 @@ static int prepare_lines(struct scoreboard *sb) bol = 1; } } + sb->lineno = xrealloc(sb->lineno, + sizeof(int* ) * (num + incomplete + 1)); + sb->lineno[num + incomplete] = buf - sb->final_buf; sb->num_lines = num + incomplete; return sb->num_lines; } -- cgit v0.10.2-6-g49f6 From 5ff62c300209d761e8e7b3c78da19b910cd9b860 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 20 Oct 2006 14:51:12 -0700 Subject: git-pickaxe: improve "best match" heuristics Instead of comparing number of lines matched, look at the matched characters and count alnums, so that we do not pass blame on not-so-interesting lines, such as an empty line and a line that is indentation followed by a closing brace. Add an option --score-debug to show the score of each blame_entry while we cook this further on the "next" branch. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index b595299..52e32dc 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -34,8 +34,7 @@ static int longest_file; static int longest_author; static int max_orig_digits; static int max_digits; - -#define DEBUG 0 +static int max_score_digits; #define PICKAXE_BLAME_MOVE 01 #define PICKAXE_BLAME_COPY 02 @@ -78,6 +77,11 @@ struct blame_entry { * suspect's file; internally all line numbers are 0 based. */ int s_lno; + + /* how significant this entry is -- cached to avoid + * scanning the lines over and over + */ + unsigned score; }; struct scoreboard { @@ -215,9 +219,6 @@ static void process_u_diff(void *state_, char *line, unsigned long len) struct chunk *chunk; int off1, off2, len1, len2, num; - if (DEBUG) - fprintf(stderr, "%.*s", (int) len, line); - num = state->ret->num; if (len < 4 || line[0] != '@' || line[1] != '@') { if (state->hunk_in_pre_context && line[0] == ' ') @@ -295,10 +296,6 @@ static struct patch *get_patch(struct origin *parent, struct origin *origin) char *blob_p, *blob_o; struct patch *patch; - if (DEBUG) fprintf(stderr, "get patch %.8s %.8s\n", - sha1_to_hex(parent->commit->object.sha1), - sha1_to_hex(origin->commit->object.sha1)); - blob_p = read_sha1_file(parent->blob_sha1, type, (unsigned long *) &file_p.size); blob_o = read_sha1_file(origin->blob_sha1, type, @@ -352,6 +349,7 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src) memcpy(dst, src, sizeof(*src)); dst->prev = p; dst->next = n; + dst->score = 0; } static const char *nth_line(struct scoreboard *sb, int lno) @@ -448,7 +446,7 @@ static void split_blame(struct scoreboard *sb, add_blame_entry(sb, new_entry); } - if (DEBUG) { + if (1) { /* sanity */ struct blame_entry *ent; int lno = 0, corrupt = 0; @@ -530,12 +528,6 @@ static int pass_blame_to_parent(struct scoreboard *sb, for (i = 0; i < patch->num; i++) { struct chunk *chunk = &patch->chunks[i]; - if (DEBUG) - fprintf(stderr, - "plno = %d, tlno = %d, " - "same as parent up to %d, resync %d and %d\n", - plno, tlno, - chunk->same, chunk->p_next, chunk->t_next); blame_chunk(sb, tlno, plno, chunk->same, target, parent); plno = chunk->p_next; tlno = chunk->t_next; @@ -547,14 +539,37 @@ static int pass_blame_to_parent(struct scoreboard *sb, return 0; } -static void copy_split_if_better(struct blame_entry best_so_far[3], +static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) +{ + unsigned score; + const char *cp, *ep; + + if (e->score) + return e->score; + + score = 0; + cp = nth_line(sb, e->lno); + ep = nth_line(sb, e->lno + e->num_lines); + while (cp < ep) { + unsigned ch = *((unsigned char *)cp); + if (isalnum(ch)) + score++; + cp++; + } + e->score = score; + return score; +} + +static void copy_split_if_better(struct scoreboard *sb, + struct blame_entry best_so_far[3], struct blame_entry this[3]) { if (!this[1].suspect) return; - if (best_so_far[1].suspect && - (this[1].num_lines < best_so_far[1].num_lines)) - return; + if (best_so_far[1].suspect) { + if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1])) + return; + } memcpy(best_so_far, this, sizeof(struct blame_entry [3])); } @@ -596,7 +611,7 @@ static void find_copy_in_blob(struct scoreboard *sb, tlno + ent->s_lno, plno, chunk->same + ent->s_lno, parent); - copy_split_if_better(split, this); + copy_split_if_better(sb, split, this); } plno = chunk->p_next; tlno = chunk->t_next; @@ -699,7 +714,7 @@ static int find_copy_in_parent(struct scoreboard *sb, continue; } find_copy_in_blob(sb, ent, norigin, this, &file_p); - copy_split_if_better(split, this); + copy_split_if_better(sb, split, this); } if (split[1].suspect) split_blame(sb, split, ent); @@ -944,6 +959,7 @@ static void get_commit_info(struct commit *commit, #define OUTPUT_PORCELAIN 010 #define OUTPUT_SHOW_NAME 020 #define OUTPUT_SHOW_NUMBER 040 +#define OUTPUT_SHOW_SCORE 0100 static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) { @@ -1016,6 +1032,8 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) show_raw_time), ent->lno + 1 + cnt); else { + if (opt & OUTPUT_SHOW_SCORE) + printf(" %*d", max_score_digits, ent->score); if (opt & OUTPUT_SHOW_NAME) printf(" %-*.*s", longest_file, longest_file, suspect->path); @@ -1060,8 +1078,9 @@ static void output(struct scoreboard *sb, int option) for (ent = sb->ent; ent; ent = ent->next) { if (option & OUTPUT_PORCELAIN) emit_porcelain(sb, ent); - else + else { emit_other(sb, ent, option); + } } } @@ -1121,6 +1140,7 @@ static void find_alignment(struct scoreboard *sb, int *option) { int longest_src_lines = 0; int longest_dst_lines = 0; + unsigned largest_score = 0; struct blame_entry *e; for (e = sb->ent; e; e = e->next) { @@ -1146,9 +1166,12 @@ static void find_alignment(struct scoreboard *sb, int *option) num = e->lno + e->num_lines; if (longest_dst_lines < num) longest_dst_lines = num; + if (largest_score < ent_score(sb, e)) + largest_score = ent_score(sb, e); } max_orig_digits = lineno_width(longest_src_lines); max_digits = lineno_width(longest_dst_lines); + max_score_digits = lineno_width(largest_score); } static int has_path_in_work_tree(const char *path) @@ -1209,6 +1232,8 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) tmp = top; top = bottom; bottom = tmp; } } + else if (!strcmp("--score-debug", arg)) + output_option |= OUTPUT_SHOW_SCORE; else if (!strcmp("-f", arg) || !strcmp("--show-name", arg)) output_option |= OUTPUT_SHOW_NAME; -- cgit v0.10.2-6-g49f6 From 4a0fc95f182dbf5b0c58e6d6cfe8cd7459da7ab6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 20 Oct 2006 15:37:12 -0700 Subject: git-pickaxe: introduce heuristics to avoid "trivial" chunks This adds scoring logic to blame_entry to prevent blames on very trivial chunks (e.g. lots of empty lines, indent followed by a closing brace) from being passed down to unrelated lines in the parent. The current heuristics are quite simple and may need to be tweaked later, but we need to start somewhere. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 52e32dc..e5a50b9 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -40,6 +40,15 @@ static int max_score_digits; #define PICKAXE_BLAME_COPY 02 #define PICKAXE_BLAME_COPY_HARDER 04 +/* + * blame for a blame_entry with score lower than these thresholds + * is not passed to the parent using move/copy logic. + */ +static unsigned blame_move_score; +static unsigned blame_copy_score; +#define BLAME_DEFAULT_MOVE_SCORE 20 +#define BLAME_DEFAULT_COPY_SCORE 40 + /* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */ #define METAINFO_SHOWN (1u<<12) #define MORE_THAN_ONE_PATH (1u<<13) @@ -645,7 +654,8 @@ static int find_move_in_parent(struct scoreboard *sb, if (ent->suspect != target || ent->guilty) continue; find_copy_in_blob(sb, ent, parent, split, &file_p); - if (split[1].suspect) + if (split[1].suspect && + blame_move_score < ent_score(sb, &split[1])) split_blame(sb, split, ent); } free(blob_p); @@ -716,7 +726,8 @@ static int find_copy_in_parent(struct scoreboard *sb, find_copy_in_blob(sb, ent, norigin, this, &file_p); copy_split_if_better(sb, split, this); } - if (split[1].suspect) + if (split[1].suspect && + blame_copy_score < ent_score(sb, &split[1])) split_blame(sb, split, ent); } diff_flush(&diff_opts); @@ -1180,6 +1191,15 @@ static int has_path_in_work_tree(const char *path) return !lstat(path, &st); } +static unsigned parse_score(const char *arg) +{ + char *end; + unsigned long score = strtoul(arg, &end, 10); + if (*end) + return 0; + return score; +} + int cmd_pickaxe(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1209,12 +1229,15 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) output_option |= OUTPUT_LONG_OBJECT_NAME; else if (!strcmp("-S", arg) && ++i < argc) revs_file = argv[i]; - else if (!strcmp("-M", arg)) + else if (!strncmp("-M", arg, 2)) { opt |= PICKAXE_BLAME_MOVE; - else if (!strcmp("-C", arg)) { + blame_move_score = parse_score(arg+2); + } + else if (!strncmp("-C", arg, 2)) { if (opt & PICKAXE_BLAME_COPY) opt |= PICKAXE_BLAME_COPY_HARDER; opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; + blame_copy_score = parse_score(arg+2); } else if (!strcmp("-L", arg) && ++i < argc) { char *term; @@ -1252,6 +1275,11 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) argv[unk++] = arg; } + if (!blame_move_score) + blame_move_score = BLAME_DEFAULT_MOVE_SCORE; + if (!blame_copy_score) + blame_copy_score = BLAME_DEFAULT_COPY_SCORE; + /* We have collected options unknown to us in argv[1..unk] * which are to be passed to revision machinery if we are * going to do the "bottom" procesing. -- cgit v0.10.2-6-g49f6 From 612702e8ea3aa070586001f1aa0a2070058664e0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 20 Oct 2006 23:49:31 -0700 Subject: git-pickaxe: do not keep commit buffer. We need the commit buffer data while generating the final result, but until then we do not need them. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index e5a50b9..e628b5a 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -108,6 +108,7 @@ struct scoreboard { /* linked list of blames */ struct blame_entry *ent; + /* look-up a line in the final buffer */ int num_lines; int *lineno; }; @@ -188,7 +189,8 @@ static struct origin *find_rename(struct scoreboard *sb, for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; - if (p->status == 'R' && !strcmp(p->one->path, origin->path)) { + if ((p->status == 'R' || p->status == 'C') && + !strcmp(p->one->path, origin->path)) { porigin = find_origin(sb, parent, p->two->path); break; } @@ -457,7 +459,7 @@ static void split_blame(struct scoreboard *sb, if (1) { /* sanity */ struct blame_entry *ent; - int lno = 0, corrupt = 0; + int lno = sb->ent->lno, corrupt = 0; for (ent = sb->ent; ent; ent = ent->next) { if (lno != ent->lno) @@ -467,7 +469,7 @@ static void split_blame(struct scoreboard *sb, lno += ent->num_lines; } if (corrupt) { - lno = 0; + lno = sb->ent->lno; for (ent = sb->ent; ent; ent = ent->next) { printf("L %8d l %8d n %8d\n", lno, ent->lno, ent->num_lines); @@ -508,10 +510,9 @@ static void blame_chunk(struct scoreboard *sb, int tlno, int plno, int same, struct origin *target, struct origin *parent) { - struct blame_entry *e, *n; + struct blame_entry *e; - for (e = sb->ent; e; e = n) { - n = e->next; + for (e = sb->ent; e; e = e->next) { if (e->guilty || e->suspect != target) continue; if (same <= e->s_lno) @@ -556,7 +557,7 @@ static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) if (e->score) return e->score; - score = 0; + score = 1; cp = nth_line(sb, e->lno); ep = nth_line(sb, e->lno + e->num_lines); while (cp < ep) { @@ -933,6 +934,15 @@ static void get_commit_info(struct commit *commit, static char committer_buf[1024]; static char summary_buf[1024]; + /* We've operated without save_commit_buffer, so + * we now need to populate them for output. + */ + if (!commit->buffer) { + char type[20]; + unsigned long size; + commit->buffer = + read_sha1_file(commit->object.sha1, type, &size); + } ret->author = author_buf; get_ac_line(commit->buffer, "\nauthor ", sizeof(author_buf), author_buf, &ret->author_mail, @@ -1214,6 +1224,8 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) const char *final_commit_name = NULL; char type[10]; + save_commit_buffer = 0; + opt = 0; bottom = top = 0; seen_dashdash = 0; -- cgit v0.10.2-6-g49f6 From 46014766bdacec8ad22355ce78898b895bf172d6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 21 Oct 2006 00:41:38 -0700 Subject: git-pickaxe: do not confuse two origins that are the same. It used to be that we can compare the address of the origin structure to determine if they are the same because they are always registered with scoreboard. After introduction of the loop to try finding the best split, that is not true anymore. The current code has rather serious leaks with origin structure, but more importantly it gets confused when two origins that points at the same commit and same path. We might eventually have to refcount and gc origin, but let's fix the correctness issue first. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index e628b5a..3ab87ef 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -113,12 +113,20 @@ struct scoreboard { int *lineno; }; +static int cmp_suspect(struct origin *a, struct origin *b) +{ + int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1); + if (cmp) + return cmp; + return strcmp(a->path, b->path); +} + static void coalesce(struct scoreboard *sb) { struct blame_entry *ent, *next; for (ent = sb->ent; ent && (next = ent->next); ent = next) { - if (ent->suspect == next->suspect && + if (!cmp_suspect(ent->suspect, next->suspect) && ent->guilty == next->guilty && ent->s_lno + ent->num_lines == next->s_lno) { ent->num_lines += next->num_lines; @@ -126,6 +134,7 @@ static void coalesce(struct scoreboard *sb) if (ent->next) ent->next->prev = ent; free(next); + ent->score = 0; next = ent; /* again */ } } @@ -498,7 +507,7 @@ static int find_last_in_target(struct scoreboard *sb, struct origin *target) int last_in_target = -1; for (e = sb->ent; e; e = e->next) { - if (e->guilty || e->suspect != target) + if (e->guilty || cmp_suspect(e->suspect, target)) continue; if (last_in_target < e->s_lno + e->num_lines) last_in_target = e->s_lno + e->num_lines; @@ -513,7 +522,7 @@ static void blame_chunk(struct scoreboard *sb, struct blame_entry *e; for (e = sb->ent; e; e = e->next) { - if (e->guilty || e->suspect != target) + if (e->guilty || cmp_suspect(e->suspect, target)) continue; if (same <= e->s_lno) continue; @@ -634,7 +643,7 @@ static int find_move_in_parent(struct scoreboard *sb, struct origin *parent) { int last_in_target; - struct blame_entry *ent, split[3]; + struct blame_entry *e, split[3]; mmfile_t file_p; char type[10]; char *blob_p; @@ -651,13 +660,13 @@ static int find_move_in_parent(struct scoreboard *sb, return 0; } - for (ent = sb->ent; ent; ent = ent->next) { - if (ent->suspect != target || ent->guilty) + for (e = sb->ent; e; e = e->next) { + if (e->guilty || cmp_suspect(e->suspect, target)) continue; - find_copy_in_blob(sb, ent, parent, split, &file_p); + find_copy_in_blob(sb, e, parent, split, &file_p); if (split[1].suspect && blame_move_score < ent_score(sb, &split[1])) - split_blame(sb, split, ent); + split_blame(sb, split, e); } free(blob_p); return 0; @@ -671,7 +680,7 @@ static int find_copy_in_parent(struct scoreboard *sb, { struct diff_options diff_opts; const char *paths[1]; - struct blame_entry *ent; + struct blame_entry *e; int i; if (find_last_in_target(sb, target) < 0) @@ -696,9 +705,9 @@ static int find_copy_in_parent(struct scoreboard *sb, "", &diff_opts); diffcore_std(&diff_opts); - for (ent = sb->ent; ent; ent = ent->next) { + for (e = sb->ent; e; e = e->next) { struct blame_entry split[3]; - if (ent->suspect != target || ent->guilty) + if (e->guilty || cmp_suspect(e->suspect, target)) continue; memset(split, 0, sizeof(split)); @@ -724,12 +733,12 @@ static int find_copy_in_parent(struct scoreboard *sb, free(blob); continue; } - find_copy_in_blob(sb, ent, norigin, this, &file_p); + find_copy_in_blob(sb, e, norigin, this, &file_p); copy_split_if_better(sb, split, this); } if (split[1].suspect && blame_copy_score < ent_score(sb, &split[1])) - split_blame(sb, split, ent); + split_blame(sb, split, e); } diff_flush(&diff_opts); @@ -763,12 +772,6 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) for (e = sb->ent; e; e = e->next) if (e->suspect == origin) e->suspect = porigin; - /* now everything blamed for origin is blamed for - * porigin, we do not need to keep it anymore. - * Do not free porigin (or the ones we got from - * earlier round); they may still be used elsewhere. - */ - free_origin(origin); return; } parent_origin[i] = porigin; @@ -834,8 +837,10 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) /* Take responsibility for the remaining entries */ for (ent = sb->ent; ent; ent = ent->next) - if (ent->suspect == suspect) + if (!cmp_suspect(ent->suspect, suspect)) ent->guilty = 1; + + coalesce(sb); } } -- cgit v0.10.2-6-g49f6 From f6c0e191020ad330c06438c144e0ea787ca964fd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 21 Oct 2006 02:56:33 -0700 Subject: git-pickaxe: get rid of wasteful find_origin(). After finding out which path in the parent to scan to pass blames, using get_tree_entry() to extract the blob information again was quite wasteful, since diff-tree already gave us that information. Separate the function to create an origin out as get_origin(). You'll never know what is more efficient unless you try and/or think hard. I somehow thought that extracting one known path out of commit's tree is cheaper than running a diff-tree for the current path between the commit and its parent, but it is not the case. In real, non-toy projects, most commits do not touch the path you are interested in, and if the path is a few levels away from the toplevel, whole-subdirectory comparison logic diff-tree allows us to skip opening lower subdirectories. This commit rewrites find_origin() function to use a single-path diff-tree to see if the parent has the same blob as the current suspect, which is cheaper than extracting the blob information using get_tree_entry() and comparing it with what the current suspect has. This shaves about 6% overhead when annotating kernel/sched.c in the Linux kernel repository on my machine. The saving rises to 25% for arch/i386/kernel/Makefile. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 3ab87ef..cf474b0 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -140,48 +140,103 @@ static void coalesce(struct scoreboard *sb) } } -static void free_origin(struct origin *o) +static struct origin *get_origin(struct scoreboard *sb, + struct commit *commit, + const char *path) { - free(o); -} - -static struct origin *find_origin(struct scoreboard *sb, - struct commit *commit, - const char *path) -{ - struct blame_entry *ent; + struct blame_entry *e; struct origin *o; - unsigned mode; - char type[10]; - for (ent = sb->ent; ent; ent = ent->next) { - if (ent->suspect->commit == commit && - !strcmp(ent->suspect->path, path)) - return ent->suspect; + for (e = sb->ent; e; e = e->next) { + if (e->suspect->commit == commit && + !strcmp(e->suspect->path, path)) + return e->suspect; } - o = xcalloc(1, sizeof(*o) + strlen(path) + 1); o->commit = commit; strcpy(o->path, path); - if (get_tree_entry(commit->object.sha1, path, o->blob_sha1, &mode)) - goto err_out; - if (sha1_object_info(o->blob_sha1, type, NULL) || - strcmp(type, blob_type)) - goto err_out; return o; - err_out: - free_origin(o); - return NULL; } -static struct origin *find_rename(struct scoreboard *sb, +static int fill_blob_sha1(struct origin *origin) +{ + unsigned mode; + char type[10]; + + if (!is_null_sha1(origin->blob_sha1)) + return 0; + if (get_tree_entry(origin->commit->object.sha1, + origin->path, + origin->blob_sha1, &mode)) + goto error_out; + if (sha1_object_info(origin->blob_sha1, type, NULL) || + strcmp(type, blob_type)) + goto error_out; + return 0; + error_out: + hashclr(origin->blob_sha1); + return -1; +} + +static struct origin *find_origin(struct scoreboard *sb, struct commit *parent, struct origin *origin) { struct origin *porigin = NULL; struct diff_options diff_opts; int i; - const char *paths[1]; + const char *paths[2]; + + /* See if the origin->path is different between parent + * and origin first. Most of the time they are the + * same and diff-tree is fairly efficient about this. + */ + diff_setup(&diff_opts); + diff_opts.recursive = 1; + diff_opts.detect_rename = 0; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + paths[0] = origin->path; + paths[1] = NULL; + + diff_tree_setup_paths(paths, &diff_opts); + if (diff_setup_done(&diff_opts) < 0) + die("diff-setup"); + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); + diffcore_std(&diff_opts); + + /* It is either one entry that says "modified", or "created", + * or nothing. + */ + if (!diff_queued_diff.nr) { + /* The path is the same as parent */ + porigin = get_origin(sb, parent, origin->path); + hashcpy(porigin->blob_sha1, origin->blob_sha1); + } + else if (diff_queued_diff.nr != 1) + die("internal error in pickaxe::find_origin"); + else { + struct diff_filepair *p = diff_queued_diff.queue[0]; + switch (p->status) { + default: + die("internal error in pickaxe::find_origin (%c)", + p->status); + case 'M': + porigin = get_origin(sb, parent, origin->path); + hashcpy(porigin->blob_sha1, p->one->sha1); + break; + case 'A': + case 'T': + /* Did not exist in parent, or type changed */ + break; + } + } + diff_flush(&diff_opts); + if (porigin) + return porigin; + + /* Otherwise we would look for a rename */ diff_setup(&diff_opts); diff_opts.recursive = 1; @@ -191,16 +246,17 @@ static struct origin *find_rename(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(origin->commit->tree->object.sha1, - parent->tree->object.sha1, + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, "", &diff_opts); diffcore_std(&diff_opts); for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; if ((p->status == 'R' || p->status == 'C') && - !strcmp(p->one->path, origin->path)) { - porigin = find_origin(sb, parent, p->two->path); + !strcmp(p->two->path, origin->path)) { + porigin = get_origin(sb, parent, p->one->path); + hashcpy(porigin->blob_sha1, p->one->sha1); break; } } @@ -705,6 +761,15 @@ static int find_copy_in_parent(struct scoreboard *sb, "", &diff_opts); diffcore_std(&diff_opts); + + /* + * NEEDSWORK: This loop is wasteful in that it opens the same + * blob in the parent number of times. We should swap the + * loop inside out, which would require keeping track of + * "best blame so far" for blame entries that the current + * "target" is being suspected. + */ + for (e = sb->ent; e; e = e->next) { struct blame_entry split[3]; if (e->guilty || cmp_suspect(e->suspect, target)) @@ -724,8 +789,8 @@ static int find_copy_in_parent(struct scoreboard *sb, if (porigin && !strcmp(p->one->path, porigin->path)) /* find_move already dealt with this path */ continue; - norigin = find_origin(sb, parent, p->one->path); - + norigin = get_origin(sb, parent, p->one->path); + hashcpy(norigin->blob_sha1, p->one->sha1); blob = read_sha1_file(norigin->blob_sha1, type, (unsigned long *) &file_p.size); file_p.ptr = blob; @@ -735,6 +800,7 @@ static int find_copy_in_parent(struct scoreboard *sb, } find_copy_in_blob(sb, e, norigin, this, &file_p); copy_split_if_better(sb, split, this); + free(blob); } if (split[1].suspect && blame_copy_score < ent_score(sb, &split[1])) @@ -762,9 +828,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) if (parse_commit(p)) continue; - porigin = find_origin(sb, parent->item, origin->path); - if (!porigin) - porigin = find_rename(sb, parent->item, origin); + porigin = find_origin(sb, parent->item, origin); if (!porigin) continue; if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { @@ -1423,8 +1487,8 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) */ prepare_revision_walk(&revs); - o = find_origin(&sb, sb.final, path); - if (!o) + o = get_origin(&sb, sb.final, path); + if (fill_blob_sha1(o)) die("no such path %s in %s", path, final_commit_name); sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); -- cgit v0.10.2-6-g49f6 From aec8fa1f587d68a0e50ada2720c8dc6e09709a9c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 21 Oct 2006 03:30:53 -0700 Subject: git-pickaxe: swap comparison loop used for -C When assigning blames for code movements across file boundaries, we used to iterate over blame entries (i.e. groups of lines to be blamed) in the outer loop and compared each entry with paths in the parent commit in an inner loop. This meant that we opened the blob data from each path number of times. Reorganize the loop so that we read the same path only once, and compare it against all relevant blame entries. This should perform better, but seems to give mixed results, though. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index cf474b0..663b96d 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -737,11 +737,27 @@ static int find_copy_in_parent(struct scoreboard *sb, struct diff_options diff_opts; const char *paths[1]; struct blame_entry *e; - int i; + int i, j; + struct blame_list { + struct blame_entry *ent; + struct blame_entry split[3]; + } *blame_list; + int num_ents; - if (find_last_in_target(sb, target) < 0) + /* Count the number of entries the target is suspected for, + * and prepare a list of entry and the best split. + */ + for (e = sb->ent, num_ents = 0; e; e = e->next) + if (!e->guilty && !cmp_suspect(e->suspect, target)) + num_ents++; + if (!num_ents) return 1; /* nothing remains for this target */ + blame_list = xcalloc(num_ents, sizeof(struct blame_list)); + for (e = sb->ent, i = 0; e; e = e->next) + if (!e->guilty && !cmp_suspect(e->suspect, target)) + blame_list[i++].ent = e; + diff_setup(&diff_opts); diff_opts.recursive = 1; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; @@ -761,52 +777,45 @@ static int find_copy_in_parent(struct scoreboard *sb, "", &diff_opts); diffcore_std(&diff_opts); - - /* - * NEEDSWORK: This loop is wasteful in that it opens the same - * blob in the parent number of times. We should swap the - * loop inside out, which would require keeping track of - * "best blame so far" for blame entries that the current - * "target" is being suspected. - */ - - for (e = sb->ent; e; e = e->next) { - struct blame_entry split[3]; - if (e->guilty || cmp_suspect(e->suspect, target)) + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + struct origin *norigin; + mmfile_t file_p; + char type[10]; + char *blob; + struct blame_entry this[3]; + + if (!DIFF_FILE_VALID(p->one)) + continue; /* does not exist in parent */ + if (porigin && !strcmp(p->one->path, porigin->path)) + /* find_move already dealt with this path */ continue; - memset(split, 0, sizeof(split)); - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - struct origin *norigin; - mmfile_t file_p; - char type[10]; - char *blob; - struct blame_entry this[3]; + norigin = get_origin(sb, parent, p->one->path); + hashcpy(norigin->blob_sha1, p->one->sha1); + blob = read_sha1_file(norigin->blob_sha1, type, + (unsigned long *) &file_p.size); + file_p.ptr = blob; + if (!file_p.ptr) + continue; - if (!DIFF_FILE_VALID(p->one)) - continue; /* does not exist in parent */ - if (porigin && !strcmp(p->one->path, porigin->path)) - /* find_move already dealt with this path */ - continue; - norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->sha1); - blob = read_sha1_file(norigin->blob_sha1, type, - (unsigned long *) &file_p.size); - file_p.ptr = blob; - if (!file_p.ptr) { - free(blob); - continue; - } - find_copy_in_blob(sb, e, norigin, this, &file_p); - copy_split_if_better(sb, split, this); - free(blob); + for (j = 0; j < num_ents; j++) { + find_copy_in_blob(sb, blame_list[j].ent, norigin, + this, &file_p); + copy_split_if_better(sb, blame_list[j].split, + this); } + free(blob); + } + diff_flush(&diff_opts); + + for (j = 0; j < num_ents; j++) { + struct blame_entry *split = blame_list[j].split; if (split[1].suspect && blame_copy_score < ent_score(sb, &split[1])) - split_blame(sb, split, e); + split_blame(sb, split, blame_list[j].ent); } - diff_flush(&diff_opts); + free(blame_list); return 0; } -- cgit v0.10.2-6-g49f6 From fc61e313da0a124e9f6a213cd76944a8c4e6c918 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 21 Oct 2006 20:51:04 +0200 Subject: git-merge: show usage if run without arguments Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index 5b34b4d..789f4de 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -93,6 +93,8 @@ finish () { esac } +case "$#" in 0) usage ;; esac + rloga= while case "$#" in 0) break ;; esac do -- cgit v0.10.2-6-g49f6 From 5ea0921cbeb248a6505c32601ad841f706f942fd Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Sun, 22 Oct 2006 13:30:24 +0200 Subject: Fix usagestring for git-branch Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano diff --git a/git-branch.sh b/git-branch.sh index 4f31903..f823c78 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -1,6 +1,6 @@ #!/bin/sh -USAGE='[-l] [(-d | -D) ] | [[-f] []] | -r' +USAGE='[-l] [-f] [] | (-d | -D) | [-r]' LONG_USAGE='If no arguments, show available branches and mark current branch with a star. If one argument, create a new branch based off of current HEAD. If two arguments, create a new branch based off of .' -- cgit v0.10.2-6-g49f6 From e7fb022a42a83cbdb33e40f82ffca28eceb423c9 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 21 Oct 2006 17:52:19 +0200 Subject: gitweb: Whitespace cleanup - tabs are for indent, spaces are for align (2) Code should be aligned the same way, regardless of tab size. Use tabs for indent, but spaces for align. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0ec1eef..d01ac94 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1671,13 +1671,13 @@ sub git_print_tree_entry { if ($t->{'type'} eq "blob") { print "" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key), - -class => "list"}, esc_html($t->{'name'})) . ""; if ($have_blame) { print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blame"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); } if (defined $hash_base) { if ($have_blame) { @@ -1689,8 +1689,8 @@ sub git_print_tree_entry { } print " | " . $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base, - file_name=>"$basedir$t->{'name'}")}, - "raw"); + file_name=>"$basedir$t->{'name'}")}, + "raw"); print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + -class => "list"}, esc_html($diff{'file'})); print "$mode_chng"; @@ -1785,11 +1785,11 @@ sub git_difftree_body { print " | "; } print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'file'})}, - "blame") . " | "; + file_name=>$diff{'file'})}, + "blame") . " | "; print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'file'})}, - "history"); + file_name=>$diff{'file'})}, + "history"); print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); print "$mode_chnge"; @@ -1822,19 +1822,19 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); } else { print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, - "diff"); + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, + "diff"); } print " | "; } print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, - file_name=>$diff{'file'})}, - "blame") . " | "; + file_name=>$diff{'file'})}, + "blame") . " | "; print $cgi->a({-href => href(action=>"history", hash_base=>$hash, - file_name=>$diff{'file'})}, - "history"); + file_name=>$diff{'file'})}, + "history"); print "
\n"; my $alternate = 1; + # '..' (top directory) link if possible + if (defined $hash_base && + defined $file_name && $file_name =~ m![^/]+$!) { + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + + my $up = $file_name; + $up =~ s!/?[^/]+$!!; + undef $up unless $up; + # based on git_print_tree_entry + print '\n"; + print '\n"; + print "\n"; + + print "\n"; + } foreach my $line (@entries) { my %t = parse_ls_tree_line($line, -z => 1); -- cgit v0.10.2-6-g49f6 From 178e015c0543b581a40adbf4822f44fa592ff68b Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 23 Oct 2006 01:09:35 -0400 Subject: Use column indexes in git-cvsserver where necessary. Tonight I found a git-cvsserver instance spending a lot of time in disk IO while trying to process operations against a Git repository with >30,000 objects contained in it. Blowing away my SQLLite database and rebuilding all tables with indexes on the attributes that git-cvsserver frequently runs queries against seems to have resolved the issue quite nicely. Since the indexes shouldn't hurt performance on small repositories and always helps on larger repositories we should just always create them when creating the revision storage tables. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 08ad831..8817f8b 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -2118,9 +2118,17 @@ sub new mode TEXT NOT NULL ) "); + $self->{dbh}->do(" + CREATE INDEX revision_ix1 + ON revision (name,revision) + "); + $self->{dbh}->do(" + CREATE INDEX revision_ix2 + ON revision (name,commithash) + "); } - # Construct the revision table if required + # Construct the head table if required unless ( $self->{tables}{head} ) { $self->{dbh}->do(" @@ -2134,6 +2142,10 @@ sub new mode TEXT NOT NULL ) "); + $self->{dbh}->do(" + CREATE INDEX head_ix1 + ON head (name) + "); } # Construct the properties table if required -- cgit v0.10.2-6-g49f6 From 1d6a003a42b3c23ad7883b0bbe6a034728e51836 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 23 Oct 2006 00:46:37 -0700 Subject: git-send-email: do not pass custom Date: header We already generate a Date: header based on when the patch was emailed. git-format-patch includes the Date: header of the patch. Having two Date: headers is just confusing, so we just use the current Date: Often the mailed patches in a patch series are created over a series of several hours or days, so the Date: header from the original commit is incorrect for email, and often far off enough for spam filters to complain. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index 1c6d2cc..c42dc3b 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -514,7 +514,7 @@ foreach my $t (@files) { $2, $_) unless $quiet; push @cc, $2; } - elsif (/^[-A-Za-z]+:\s+\S/) { + elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) { push @xh, $_; } -- cgit v0.10.2-6-g49f6 From b4aee09e610567529dc619d7324dc2fe85a11db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Hasselstr=C3=B6m?= Date: Sun, 22 Oct 2006 17:02:42 -0700 Subject: ignore-errors requires cl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc-git complains that it can't find the definition of ignore-errors unless I (require 'cl). So I guess the correct place to do that is in the file itself. Signed-off-by: Karl Hasselström Signed-off-by: Junio C Hamano diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 80e7675..8b636192 100644 --- a/contrib/emacs/vc-git.el +++ b/contrib/emacs/vc-git.el @@ -33,6 +33,8 @@ ;; - working with revisions other than HEAD ;; +(eval-when-compile (require 'cl)) + (defvar git-commits-coding-system 'utf-8 "Default coding system for git commits.") -- cgit v0.10.2-6-g49f6 From 2bb10fe51eae9bf205513b6a1f9197d4599a8c03 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Mon, 23 Oct 2006 18:26:39 +0200 Subject: prune-packed: Fix uninitialized variable. The dryrun variable was made local instead of static by the previous commit, and local variables aren't initialized to zero. Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c index e12b6cf..24e3b0a 100644 --- a/builtin-prune-packed.c +++ b/builtin-prune-packed.c @@ -56,7 +56,7 @@ void prune_packed_objects(int dryrun) int cmd_prune_packed(int argc, const char **argv, const char *prefix) { int i; - int dryrun; + int dryrun = 0; for (i = 1; i < argc; i++) { const char *arg = argv[i]; -- cgit v0.10.2-6-g49f6 From 0cc6d3464a1944e930cfb12ba030eb1581323845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= Date: Mon, 23 Oct 2006 18:42:14 +0200 Subject: Documentation for the [remote] config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Santi Béjar Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 84e3891..ee51fe3 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -230,6 +230,18 @@ pull.octopus:: pull.twohead:: The default merge strategy to use when pulling a single branch. +remote..url:: + The URL of a remote repository. See gitlink:git-fetch[1] or + gitlink:git-push[1]. + +remote..fetch:: + The default set of "refspec" for gitlink:git-fetch[1]. See + gitlink:git-fetch[1]. + +remote..push:: + The default set of "refspec" for gitlink:git-push[1]. See + gitlink:git-push[1]. + show.difftree:: The default gitlink:git-diff-tree[1] arguments to be used for gitlink:git-show[1]. diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 26ecba5..670827c 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -51,6 +51,14 @@ lines are used for `git-push` and `git-fetch`/`git-pull`, respectively. Multiple `Push:` and `Pull:` lines may be specified for additional branch mappings. +Or, equivalently, in the `$GIT_DIR/config` (note the use +of `fetch` instead of `Pull:`): + +[remote ""] + url = + push = + fetch = + The name of a file in `$GIT_DIR/branches` directory can be specified as an older notation short-hand; the named file should contain a single line, a URL in one of the -- cgit v0.10.2-6-g49f6 From 810799ecab6f9164401416988d9d79270315ba18 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Mon, 23 Oct 2006 15:59:48 +0200 Subject: git-clone: honor --quiet I noticed that a cron-launched "git-clone --quiet" was generating progress output to standard error -- and thus always spamming me. The offending output was due to git-clone invoking git-read-tree with its undocumented -v option. This change turns off "-v" for --quiet. Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano diff --git a/git-clone.sh b/git-clone.sh index bf54a11..24b1195 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -414,7 +414,8 @@ Pull: refs/heads/$head_points_at:$origin_track" && case "$no_checkout" in '') - git-read-tree -m -u -v HEAD HEAD + test "z$quiet" = z && v=-v || v= + git-read-tree -m -u $v HEAD HEAD esac fi rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD" -- cgit v0.10.2-6-g49f6 From 67aef034551aed0cc417e8b758a59b9978c4f3f1 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Mon, 23 Oct 2006 22:22:25 +0200 Subject: xdiff/xemit.c (xdl_find_func): Elide trailing white space in a context header. This removes trailing blanks from git-generated diff headers the same way a similar patch did that for GNU diff: http://article.gmane.org/gmane.comp.gnu.utils.bugs/13839 That is, it removes trailing blanks on the hunk header line that shows the function name. Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 714c563..154c26f 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -90,7 +90,7 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) { *rec == '#')) { /* #define? */ if (len > sz) len = sz; - if (len && rec[len - 1] == '\n') + while (0 < len && isspace((unsigned char)rec[len - 1])) len--; memcpy(buf, rec, len); *ll = len; -- cgit v0.10.2-6-g49f6 From 83543a24c316de60b886cd98272fde2bcc99d558 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 23 Oct 2006 18:26:05 -0700 Subject: daemon: do not die on older clients. In the older times, the clients did not say which host they were trying to connect, and the code we recently added did not quite handle the older clients correctly. Noticed by Simon Arlott. Signed-off-by: Junio C Hamano diff --git a/daemon.c b/daemon.c index ad84928..e66bb80 100644 --- a/daemon.c +++ b/daemon.c @@ -450,6 +450,8 @@ void fill_in_extra_table_entries(struct interp *itable) * Replace literal host with lowercase-ized hostname. */ hp = interp_table[INTERP_SLOT_HOST].value; + if (!hp) + return; for ( ; *hp; hp++) *hp = tolower(*hp); @@ -544,8 +546,10 @@ static int execute(struct sockaddr *addr) loginfo("Extended attributes (%d bytes) exist <%.*s>", (int) pktlen - len, (int) pktlen - len, line + len + 1); - if (len && line[len-1] == '\n') + if (len && line[len-1] == '\n') { line[--len] = 0; + pktlen--; + } /* * Initialize the path interpolation table for this connection. -- cgit v0.10.2-6-g49f6 From a153adf683d2b6e22c7e892ed8a161b140156186 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Tue, 24 Oct 2006 02:39:14 +0200 Subject: gitweb: Fix setting $/ in parse_commit() If the commit couldn't have been read, $/ wasn't restored to \n properly, causing random havoc like git_get_ref_list() returning the ref names with trailing \n. Aside of potential confusion in the body of git_search(), no other $/ surprises are hopefully hidden in the code. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 23b26a2..2390603 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1009,12 +1009,11 @@ sub parse_commit { if (defined $commit_text) { @commit_lines = @$commit_text; } else { - $/ = "\0"; + local $/ = "\0"; open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; @commit_lines = split '\n', <$fd>; close $fd or return; - $/ = "\n"; pop @commit_lines; } my $header = shift @commit_lines; -- cgit v0.10.2-6-g49f6 From e827633a5d7d627eb1170b2d0c71e944d0d56faf Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Tue, 24 Oct 2006 01:01:57 +0200 Subject: Built-in cherry This replaces the shell script git-cherry with a version written in C. The behaviour of the new version differs from the original in two points: it has no long help any more, and it is handling the (optional) third parameter a bit differently. Basically, it does the equivalent of ours=`git-rev-list $ours ^$limit ^$upstream` instead of ours=`git-rev-list $ours ^$limit` Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 66c8b4b..2b53747 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,7 @@ BASIC_LDFLAGS = SCRIPT_SH = \ git-bisect.sh git-branch.sh git-checkout.sh \ - git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ + git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh \ git-ls-remote.sh \ git-merge-one-file.sh git-parse-remote.sh \ @@ -210,7 +210,7 @@ PROGRAMS = \ EXTRA_PROGRAMS = BUILT_INS = \ - git-format-patch$X git-show$X git-whatchanged$X \ + git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \ git-get-tar-commit-id$X \ $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) diff --git a/builtin-log.c b/builtin-log.c index 9d1ceae..fc5e476 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -437,3 +437,109 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) return 0; } +static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) +{ + unsigned char sha1[20]; + if (get_sha1(arg, sha1) == 0) { + struct commit *commit = lookup_commit_reference(sha1); + if (commit) { + commit->object.flags |= flags; + add_pending_object(revs, &commit->object, arg); + return 0; + } + } + return -1; +} + +static const char cherry_usage[] = +"git-cherry [-v] [] []"; +int cmd_cherry(int argc, const char **argv, const char *prefix) +{ + struct rev_info revs; + struct diff_options patch_id_opts; + struct commit *commit; + struct commit_list *list = NULL; + const char *upstream; + const char *head = "HEAD"; + const char *limit = NULL; + int verbose = 0; + + if (argc > 1 && !strcmp(argv[1], "-v")) { + verbose = 1; + argc--; + argv++; + } + + switch (argc) { + case 4: + limit = argv[3]; + /* FALLTHROUGH */ + case 3: + head = argv[2]; + /* FALLTHROUGH */ + case 2: + upstream = argv[1]; + break; + default: + usage(cherry_usage); + } + + init_revisions(&revs, prefix); + revs.diff = 1; + revs.combine_merges = 0; + revs.ignore_merges = 1; + revs.diffopt.recursive = 1; + + if (add_pending_commit(head, &revs, 0)) + die("Unknown commit %s", head); + if (add_pending_commit(upstream, &revs, UNINTERESTING)) + die("Unknown commit %s", upstream); + + /* Don't say anything if head and upstream are the same. */ + if (revs.pending.nr == 2) { + struct object_array_entry *o = revs.pending.objects; + if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0) + return 0; + } + + get_patch_ids(&revs, &patch_id_opts, prefix); + + if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) + die("Unknown commit %s", limit); + + /* reverse the list of commits */ + prepare_revision_walk(&revs); + while ((commit = get_revision(&revs)) != NULL) { + /* ignore merges */ + if (commit->parents && commit->parents->next) + continue; + + commit_list_insert(commit, &list); + } + + while (list) { + unsigned char sha1[20]; + char sign = '+'; + + commit = list->item; + if (!get_patch_id(commit, &patch_id_opts, sha1) && + lookup_object(sha1)) + sign = '-'; + + if (verbose) { + static char buf[16384]; + pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, + buf, sizeof(buf), 0, NULL, NULL, 0); + printf("%c %s %s\n", sign, + sha1_to_hex(commit->object.sha1), buf); + } + else { + printf("%c %s\n", sign, + sha1_to_hex(commit->object.sha1)); + } + + list = list->next; + } + + return 0; +} diff --git a/builtin.h b/builtin.h index f71b962..285d98f 100644 --- a/builtin.h +++ b/builtin.h @@ -19,6 +19,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); +extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_diff_files(int argc, const char **argv, const char *prefix); diff --git a/git-cherry.sh b/git-cherry.sh deleted file mode 100755 index 8832573..0000000 --- a/git-cherry.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano. -# - -USAGE='[-v] [] []' -LONG_USAGE=' __*__*__*__*__> - / - fork-point - \__+__+__+__+__+__+__+__> - -Each commit between the fork-point (or if given) and is -examined, and compared against the change each commit between the -fork-point and introduces. If the change seems to be in -the upstream, it is shown on the standard output with prefix "+". -Otherwise it is shown with prefix "-".' -. git-sh-setup - -case "$1" in -v) verbose=t; shift ;; esac - -case "$#,$1" in -1,*..*) - upstream=$(expr "z$1" : 'z\(.*\)\.\.') ours=$(expr "z$1" : '.*\.\.\(.*\)$') - set x "$upstream" "$ours" - shift ;; -esac - -case "$#" in -1) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify HEAD` || exit - limit="$upstream" - ;; -2) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify "$2"` || exit - limit="$upstream" - ;; -3) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify "$2"` && - limit=`git-rev-parse --verify "$3"` || exit - ;; -*) usage ;; -esac - -# Note that these list commits in reverse order; -# not that the order in inup matters... -inup=`git-rev-list ^$ours $upstream` && -ours=`git-rev-list $ours ^$limit` || exit - -tmp=.cherry-tmp$$ -patch=$tmp-patch -mkdir $patch -trap "rm -rf $tmp-*" 0 1 2 3 15 - -for c in $inup -do - git-diff-tree -p $c -done | git-patch-id | -while read id name -do - echo $name >>$patch/$id -done - -LF=' -' - -O= -for c in $ours -do - set x `git-diff-tree -p $c | git-patch-id` - if test "$2" != "" - then - if test -f "$patch/$2" - then - sign=- - else - sign=+ - fi - case "$verbose" in - t) - c=$(git-rev-list --pretty=oneline --max-count=1 $c) - esac - case "$O" in - '') O="$sign $c" ;; - *) O="$sign $c$LF$O" ;; - esac - fi -done -case "$O" in -'') ;; -*) echo "$O" ;; -esac diff --git a/git.c b/git.c index e089b53..f8c991f 100644 --- a/git.c +++ b/git.c @@ -224,6 +224,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, + { "cherry", cmd_cherry, RUN_SETUP }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "count-objects", cmd_count_objects, RUN_SETUP }, { "diff", cmd_diff, RUN_SETUP | USE_PAGER }, -- cgit v0.10.2-6-g49f6 From c31820c26b8f164433e67d28c403ca0df0316055 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Mon, 23 Oct 2006 23:27:45 +0200 Subject: Make git-branch a builtin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This replaces git-branch.sh with builtin-branch.c The changes is basically a patch from Kristian Høgsberg, updated to apply onto current 'next' Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index e826247..be8bf39 100644 --- a/Makefile +++ b/Makefile @@ -156,7 +156,7 @@ BASIC_CFLAGS = BASIC_LDFLAGS = SCRIPT_SH = \ - git-bisect.sh git-branch.sh git-checkout.sh \ + git-bisect.sh git-checkout.sh \ git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh \ git-ls-remote.sh \ @@ -267,6 +267,7 @@ BUILTIN_OBJS = \ builtin-add.o \ builtin-apply.o \ builtin-archive.o \ + builtin-branch.o \ builtin-cat-file.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ diff --git a/builtin-branch.c b/builtin-branch.c new file mode 100644 index 0000000..e028a53 --- /dev/null +++ b/builtin-branch.c @@ -0,0 +1,219 @@ +/* + * Builtin "git branch" + * + * Copyright (c) 2006 Kristian Høgsberg + * Based on git-branch.sh by Junio C Hamano. + */ + +#include "cache.h" +#include "refs.h" +#include "commit.h" +#include "builtin.h" + +static const char builtin_branch_usage[] = +"git-branch (-d | -D) | [-l] [-f] [] | [-r]"; + + +static const char *head; +static unsigned char head_sha1[20]; + +static int in_merge_bases(const unsigned char *sha1, + struct commit *rev1, + struct commit *rev2) +{ + struct commit_list *bases, *b; + int ret = 0; + + bases = get_merge_bases(rev1, rev2, 1); + for (b = bases; b; b = b->next) { + if (!hashcmp(sha1, b->item->object.sha1)) { + ret = 1; + break; + } + } + + free_commit_list(bases); + return ret; +} + +static void delete_branches(int argc, const char **argv, int force) +{ + struct commit *rev, *head_rev; + unsigned char sha1[20]; + char *name; + int i; + + head_rev = lookup_commit_reference(head_sha1); + for (i = 0; i < argc; i++) { + if (!strcmp(head, argv[i])) + die("Cannot delete the branch you are currently on."); + + name = xstrdup(mkpath("refs/heads/%s", argv[i])); + if (!resolve_ref(name, sha1, 1, NULL)) + die("Branch '%s' not found.", argv[i]); + + rev = lookup_commit_reference(sha1); + if (!rev || !head_rev) + die("Couldn't look up commit objects."); + + /* This checks whether the merge bases of branch and + * HEAD contains branch -- which means that the HEAD + * contains everything in both. + */ + + if (!force && + !in_merge_bases(sha1, rev, head_rev)) { + fprintf(stderr, + "The branch '%s' is not a strict subset of your current HEAD.\n" + "If you are sure you want to delete it, run 'git branch -D %s'.\n", + argv[i], argv[i]); + exit(1); + } + + if (delete_ref(name, sha1)) + printf("Error deleting branch '%s'\n", argv[i]); + else + printf("Deleted branch %s.\n", argv[i]); + + free(name); + } +} + +static int ref_index, ref_alloc; +static char **ref_list; + +static int append_ref(const char *refname, const unsigned char *sha1, int flags, + void *cb_data) +{ + if (ref_index >= ref_alloc) { + ref_alloc = alloc_nr(ref_alloc); + ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *)); + } + + ref_list[ref_index++] = xstrdup(refname); + + return 0; +} + +static int ref_cmp(const void *r1, const void *r2) +{ + return strcmp(*(char **)r1, *(char **)r2); +} + +static void print_ref_list(int remote_only) +{ + int i; + + if (remote_only) + for_each_remote_ref(append_ref, NULL); + else + for_each_branch_ref(append_ref, NULL); + + qsort(ref_list, ref_index, sizeof(char *), ref_cmp); + + for (i = 0; i < ref_index; i++) { + if (!strcmp(ref_list[i], head)) + printf("* %s\n", ref_list[i]); + else + printf(" %s\n", ref_list[i]); + } +} + +static void create_branch(const char *name, const char *start, + int force, int reflog) +{ + struct ref_lock *lock; + struct commit *commit; + unsigned char sha1[20]; + char ref[PATH_MAX], msg[PATH_MAX + 20]; + + snprintf(ref, sizeof ref, "refs/heads/%s", name); + if (check_ref_format(ref)) + die("'%s' is not a valid branch name.", name); + + if (resolve_ref(ref, sha1, 1, NULL)) { + if (!force) + die("A branch named '%s' already exists.", name); + else if (!strcmp(head, name)) + die("Cannot force update the current branch."); + } + + if (get_sha1(start, sha1) || + (commit = lookup_commit_reference(sha1)) == NULL) + die("Not a valid branch point: '%s'.", start); + hashcpy(sha1, commit->object.sha1); + + lock = lock_any_ref_for_update(ref, NULL); + if (!lock) + die("Failed to lock ref for update: %s.", strerror(errno)); + + if (reflog) { + log_all_ref_updates = 1; + snprintf(msg, sizeof msg, "branch: Created from %s", start); + } + + if (write_ref_sha1(lock, sha1, msg) < 0) + die("Failed to write ref: %s.", strerror(errno)); +} + +int cmd_branch(int argc, const char **argv, const char *prefix) +{ + int delete = 0, force_delete = 0, force_create = 0, remote_only = 0; + int reflog = 0; + int i; + + git_config(git_default_config); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-d")) { + delete = 1; + continue; + } + if (!strcmp(arg, "-D")) { + delete = 1; + force_delete = 1; + continue; + } + if (!strcmp(arg, "-f")) { + force_create = 1; + continue; + } + if (!strcmp(arg, "-r")) { + remote_only = 1; + continue; + } + if (!strcmp(arg, "-l")) { + reflog = 1; + continue; + } + usage(builtin_branch_usage); + } + + head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL)); + if (!head) + die("Failed to resolve HEAD as a valid ref."); + if (strncmp(head, "refs/heads/", 11)) + die("HEAD not found below refs/heads!"); + head += 11; + + if (delete) + delete_branches(argc - i, argv + i, force_delete); + else if (i == argc) + print_ref_list(remote_only); + else if (i == argc - 1) + create_branch(argv[i], head, force_create, reflog); + else if (i == argc - 2) + create_branch(argv[i], argv[i + 1], force_create, reflog); + else + usage(builtin_branch_usage); + + return 0; +} diff --git a/builtin.h b/builtin.h index 721b8d8..db9b369 100644 --- a/builtin.h +++ b/builtin.h @@ -15,6 +15,7 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); extern int cmd_archive(int argc, const char **argv, const char *prefix); +extern int cmd_branch(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); diff --git a/git-branch.sh b/git-branch.sh deleted file mode 100755 index 4379a07..0000000 --- a/git-branch.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/bin/sh - -USAGE='[-l] [(-d | -D) ] | [[-f] []] | -r' -LONG_USAGE='If no arguments, show available branches and mark current branch with a star. -If one argument, create a new branch based off of current HEAD. -If two arguments, create a new branch based off of .' - -SUBDIRECTORY_OK='Yes' -. git-sh-setup - -headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||') - -delete_branch () { - option="$1" - shift - for branch_name - do - case ",$headref," in - ",$branch_name,") - die "Cannot delete the branch you are on." ;; - ,,) - die "What branch are you on anyway?" ;; - esac - branch=$(git-show-ref --verify --hash -- "refs/heads/$branch_name") && - branch=$(git-rev-parse --verify "$branch^0") || - die "Seriously, what branch are you talking about?" - case "$option" in - -D) - ;; - *) - mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ') - case " $mbs " in - *' '$branch' '*) - # the merge base of branch and HEAD contains branch -- - # which means that the HEAD contains everything in both. - ;; - *) - echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD. -If you are sure you want to delete it, run 'git branch -D $branch_name'." - exit 1 - ;; - esac - ;; - esac - git update-ref -d "refs/heads/$branch_name" "$branch" - echo "Deleted branch $branch_name." - done - exit 0 -} - -ls_remote_branches () { - git-rev-parse --symbolic --all | - sed -ne 's|^refs/\(remotes/\)|\1|p' | - sort -} - -force= -create_log= -while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac -do - case "$1" in - -d | -D) - delete_branch "$@" - exit - ;; - -r) - ls_remote_branches - exit - ;; - -f) - force="$1" - ;; - -l) - create_log="yes" - ;; - --) - shift - break - ;; - -*) - usage - ;; - esac - shift -done - -case "$#" in -0) - git-rev-parse --symbolic --branches | - sort | - while read ref - do - if test "$headref" = "$ref" - then - pfx='*' - else - pfx=' ' - fi - echo "$pfx $ref" - done - exit 0 ;; -1) - head=HEAD ;; -2) - head="$2^0" ;; -esac -branchname="$1" - -rev=$(git-rev-parse --verify "$head") || exit - -git-check-ref-format "heads/$branchname" || - die "we do not like '$branchname' as a branch name." - -prev='' -if git-show-ref --verify --quiet -- "refs/heads/$branchname" -then - if test '' = "$force" - then - die "$branchname already exists." - elif test "$branchname" = "$headref" - then - die "cannot force-update the current branch." - fi - prev=`git rev-parse --verify "refs/heads/$branchname"` -fi -if test "$create_log" = 'yes' -then - mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname") - touch "$GIT_DIR/logs/refs/heads/$branchname" -fi -git update-ref -m "branch: Created from $head" "refs/heads/$branchname" "$rev" "$prev" diff --git a/git.c b/git.c index 9108fec..f197169 100644 --- a/git.c +++ b/git.c @@ -221,6 +221,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "add", cmd_add, RUN_SETUP }, { "apply", cmd_apply }, { "archive", cmd_archive }, + { "branch", cmd_branch }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, -- cgit v0.10.2-6-g49f6 From 4777b0141a4812177390da4b6ebc9d40ac3da4b5 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Tue, 24 Oct 2006 05:36:10 +0200 Subject: gitweb: Restore object-named links in item lists This restores the redundant links removed earlier. It supersedes my patch to stick slashes to tree entries. Sorry about the previous version of the patch, an unrelated snapshot link addition to tree entries slipped through (and it it didn't even compile); I've dropped the idea of snapshot links in tree entries in the meantime anyway. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 65d0a14..5aed661 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1789,16 +1789,18 @@ sub git_print_tree_entry { file_name=>"$basedir$t->{'name'}", %base_key), -class => "list"}, esc_html($t->{'name'})) . "\n"; print "\n"; print "\n" . " element sub git_print_tree_entry { my ($t, $basedir, $hash_base, $have_blame) = @_; @@ -3101,7 +3092,7 @@ sub git_log { "\n"; print "
\n"; - git_print_simplified_log($co{'comment'}); + git_print_log($co{'comment'}, -final_empty_line=> 1); print "
\n"; } git_footer_html(); @@ -3433,7 +3424,7 @@ sub git_commitdiff { git_print_authorship(\%co); print "
\n"; print "
\n"; - git_print_simplified_log($co{'comment'}, 1); # skip title + git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1); print "
\n"; # class="log" } elsif ($format eq 'plain') { -- cgit v0.10.2-6-g49f6 From 62fae51dd57b36cfbb25c9ade539ea5a6ef5ad84 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 24 Oct 2006 13:54:49 +0200 Subject: gitweb: Filter out commit ID from @difftree in git_commit and git_commitdiff Filter out commit ID output that git-diff-tree adds when called with only one (not only for --stdin) in git_commit and git_commitdiff. This also works with older git versions, which doesn't have --no-commit-id option to git-diff-tree. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5f0a134..2bc14b2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3115,6 +3115,9 @@ sub git_commit { my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed"); + # filter out commit ID output + @difftree = grep(!/^[0-9a-fA-F]{40}$/, @difftree); + # non-textual hash id's can be cached my $expires; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { @@ -3391,7 +3394,9 @@ sub git_commitdiff { while (chomp(my $line = <$fd>)) { # empty line ends raw part of diff-tree output last unless $line; - push @difftree, $line; + # filter out commit ID output + push @difftree, $line + unless $line =~ m/^[0-9a-fA-F]{40}$/; } } elsif ($format eq 'plain') { -- cgit v0.10.2-6-g49f6 From 82560983997c961d9deafe0074b787c8484c2e1d Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 24 Oct 2006 13:55:33 +0200 Subject: gitweb: Print commit message without title in commitdiff only if there is any Print the rest of commit message (title, i.e. first line of commit message, is printed separately) only if there is any. In repository which uses signoffs this shouldn't happen, because commit message should consist of at least title and signoff. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2bc14b2..c82fc62 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3428,9 +3428,11 @@ sub git_commitdiff { git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); git_print_authorship(\%co); print "
\n"; - print "
\n"; - git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1); - print "
\n"; # class="log" + if (@{$co{'comment'}} > 1) { + print "
\n"; + git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1); + print "
\n"; # class="log" + } } elsif ($format eq 'plain') { my $refs = git_get_references("tags"); -- cgit v0.10.2-6-g49f6 From a4e3bddc760c19321b00b2a660c02af9d2e97d0d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 24 Oct 2006 23:55:46 -0700 Subject: RPM package re-classification. Grabbing anything that had *arch* in its name into git-arch package was a wrong idea and we lost git-archive from git-core by mistake. Signed-off-by: Junio C Hamano diff --git a/git.spec.in b/git.spec.in index 9b1217a..83268fc 100644 --- a/git.spec.in +++ b/git.spec.in @@ -96,10 +96,10 @@ find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';' find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';' -(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files +(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files (find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files %if %{!?_without_docs:1}0 -(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files +(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files %else rm -rf $RPM_BUILD_ROOT%{_mandir} %endif @@ -126,10 +126,10 @@ rm -rf $RPM_BUILD_ROOT %files arch %defattr(-,root,root) -%doc Documentation/*arch*.txt -%{_bindir}/*arch* -%{!?_without_docs: %{_mandir}/man1/*arch*.1*} -%{!?_without_docs: %doc Documentation/*arch*.html } +%doc Documentation/git-archimport.txt +%{_bindir}/git-archimport +%{!?_without_docs: %{_mandir}/man1/git-archimport.1*} +%{!?_without_docs: %doc Documentation/git-archimport.html } %files email %defattr(-,root,root) -- cgit v0.10.2-6-g49f6 From ddaf73141c03aeaab5e8cf5fadaf8b7ebad7955b Mon Sep 17 00:00:00 2001 From: Tuncer Ayaz Date: Wed, 25 Oct 2006 12:03:06 +0200 Subject: git-fetch.sh printed protocol fix We have supported https:// protocol for some time and in 1.4.3 added ftp:// protocol. The transfer were still reported to be over http. [jc: Tuncer used substring parameter substitution ${remote%%:*} but I am deferring it to a later day. We should replace colon-expr with substring substitution after everybody's shell can grok it someday, but we are not in a hurry. ] Signed-off-by: Junio C Hamano diff --git a/git-fetch.sh b/git-fetch.sh index 79222fb..a674c8c 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -287,6 +287,7 @@ fetch_main () { # There are transports that can fetch only one head at a time... case "$remote" in http://* | https://* | ftp://*) + proto=`expr "$remote" : '\([^:]*\):'` if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi @@ -310,7 +311,7 @@ fetch_main () { done expr "z$head" : "z$_x40\$" >/dev/null || die "Failed to fetch $remote_name from $remote" - echo >&2 Fetching "$remote_name from $remote" using http + echo >&2 "Fetching $remote_name from $remote using $proto" git-http-fetch -v -a "$head" "$remote/" || exit ;; rsync://*) -- cgit v0.10.2-6-g49f6 From d47107d8104167dfe65c1743e81fa670b33f7870 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 25 Oct 2006 11:33:08 -0700 Subject: Refer to git-rev-parse:Specifying Revisions from git.txt The brief list given in "Symbolic Identifiers" section of the main documentation is good enough for overview, but help the reader to find a more comrehensive list as needed. Signed-off-by: Junio C Hamano diff --git a/Documentation/git.txt b/Documentation/git.txt index 3af6fc6..b00607e 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -562,6 +562,9 @@ HEAD:: a valid head 'name' (i.e. the contents of `$GIT_DIR/refs/heads/`). +For a more complete list of ways to spell object names, see +"SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1]. + File/Directory Structure ------------------------ -- cgit v0.10.2-6-g49f6 From 6e7d76baee08d0cd131d443c347c8a4bcd1004db Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 24 Oct 2006 23:14:30 -0700 Subject: Update cherry documentation. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt index 893baaa..e1bf8ee 100644 --- a/Documentation/git-cherry.txt +++ b/Documentation/git-cherry.txt @@ -14,8 +14,9 @@ DESCRIPTION The changeset (or "diff") of each commit between the fork-point and is compared against each commit between the fork-point and . -Every commit with a changeset that doesn't exist in the other branch -has its id (sha1) reported, prefixed by a symbol. Those existing only +Every commit that doesn't exist in the branch +has its id (sha1) reported, prefixed by a symbol. The ones that have +equivalent change already in the branch are prefixed with a minus (-) sign, and those that only exist in the branch are prefixed with a plus (+) symbol. diff --git a/git-cherry.sh b/git-cherry.sh index 8832573..cf7af55 100755 --- a/git-cherry.sh +++ b/git-cherry.sh @@ -12,8 +12,8 @@ LONG_USAGE=' __*__*__*__*__> Each commit between the fork-point (or if given) and is examined, and compared against the change each commit between the fork-point and introduces. If the change seems to be in -the upstream, it is shown on the standard output with prefix "+". -Otherwise it is shown with prefix "-".' +the upstream, it is shown on the standard output with prefix "-". +Otherwise it is shown with prefix "+".' . git-sh-setup case "$1" in -v) verbose=t; shift ;; esac -- cgit v0.10.2-6-g49f6 From 70da769a4607dbbc4efc490287ead98175cf622b Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 25 Oct 2006 02:28:55 +0200 Subject: xdiff: Match GNU diff behaviour when deciding hunk comment worthiness of lines This removes the '#' and '(' tests and adds a '$' test instead although I have no idea what it is actually good for - but hey, if that's what GNU diff does... Pasky only went and did as Junio sayeth. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 154c26f..07995ec 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -86,8 +86,7 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) { if (len > 0 && (isalpha((unsigned char)*rec) || /* identifier? */ *rec == '_' || /* also identifier? */ - *rec == '(' || /* lisp defun? */ - *rec == '#')) { /* #define? */ + *rec == '$')) { /* mysterious GNU diff's invention */ if (len > sz) len = sz; while (0 < len && isspace((unsigned char)rec[len - 1])) -- cgit v0.10.2-6-g49f6 From 84ab7b6fc7e190ac1c1d0bc5b7c48befa42eff3c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 25 Oct 2006 14:38:24 -0700 Subject: Documentation/SubmittingPatches: 3+1 != 6 Signed-off-by: Junio C Hamano diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index a5b3ebd..8a3d316 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -107,7 +107,7 @@ enhancements to them, do not forget to "cc: " the person who primarily worked on that hierarchy in contrib/. -(6) Sign your work +(4) Sign your work To improve tracking of who did what, we've borrowed the "sign-off" procedure from the Linux kernel project on patches -- cgit v0.10.2-6-g49f6 From e42797f5b6d5ed3b8c894d89493e285c40d58dc8 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 23 Oct 2006 14:50:18 -0400 Subject: enable index-pack streaming capability A new flag, --stdin, allows for a pack to be received over a stream. When this flag is provided, the pack content is written to either the named pack file or directly to the object repository under the same name as produced by git-repack. The pack index is written as well with the corresponding base name, unless the index name is overriden with -o. With this patch, git-index-pack could be used instead of git-unpack-objects when fetching remote objects but only with non "thin" packs for now. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 71ce557..db7af58 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -8,7 +8,7 @@ git-index-pack - Build pack index file for an existing packed archive SYNOPSIS -------- -'git-index-pack' [-o ] +'git-index-pack' [-o ] { | --stdin [] } DESCRIPTION @@ -29,6 +29,12 @@ OPTIONS fails if the name of packed archive does not end with .pack). +--stdin:: + When this flag is provided, the pack is read from stdin + instead and a copy is then written to . If + is not specified, the pack is written to + objects/pack/ directory of the current git repository with + a default name determined from the pack content. Author ------ diff --git a/index-pack.c b/index-pack.c index e33f605..cecdd26 100644 --- a/index-pack.c +++ b/index-pack.c @@ -8,7 +8,7 @@ #include "tree.h" static const char index_pack_usage[] = -"git-index-pack [-o index-file] pack-file"; +"git-index-pack [-o ] { | --stdin [] }"; struct object_entry { @@ -37,17 +37,18 @@ struct delta_entry union delta_base base; }; -static const char *pack_name; static struct object_entry *objects; static struct delta_entry *deltas; static int nr_objects; static int nr_deltas; +static int from_stdin; + /* We always read in 4kB chunks. */ static unsigned char input_buffer[4096]; static unsigned long input_offset, input_len, consumed_bytes; static SHA_CTX input_ctx; -static int input_fd; +static int input_fd, output_fd, mmap_fd; /* * Make sure at least "min" bytes are available in the buffer, and @@ -60,6 +61,8 @@ static void * fill(int min) if (min > sizeof(input_buffer)) die("cannot fill %d bytes", min); if (input_offset) { + if (output_fd >= 0) + write_or_die(output_fd, input_buffer, input_offset); SHA1_Update(&input_ctx, input_buffer, input_offset); memcpy(input_buffer, input_buffer + input_offset, input_len); input_offset = 0; @@ -86,13 +89,31 @@ static void use(int bytes) consumed_bytes += bytes; } -static void open_pack_file(void) +static const char * open_pack_file(const char *pack_name) { - input_fd = open(pack_name, O_RDONLY); - if (input_fd < 0) - die("cannot open packfile '%s': %s", pack_name, - strerror(errno)); + if (from_stdin) { + input_fd = 0; + if (!pack_name) { + static char tmpfile[PATH_MAX]; + snprintf(tmpfile, sizeof(tmpfile), + "%s/pack_XXXXXX", get_object_directory()); + output_fd = mkstemp(tmpfile); + pack_name = xstrdup(tmpfile); + } else + output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); + if (output_fd < 0) + die("unable to create %s: %s\n", pack_name, strerror(errno)); + mmap_fd = output_fd; + } else { + input_fd = open(pack_name, O_RDONLY); + if (input_fd < 0) + die("cannot open packfile '%s': %s", + pack_name, strerror(errno)); + output_fd = -1; + mmap_fd = input_fd; + } SHA1_Init(&input_ctx); + return pack_name; } static void parse_pack_header(void) @@ -101,10 +122,9 @@ static void parse_pack_header(void) /* Header consistency check */ if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) - die("packfile '%s' signature mismatch", pack_name); + die("pack signature mismatch"); if (!pack_version_ok(hdr->hdr_version)) - die("packfile '%s' version %d unsupported", - pack_name, ntohl(hdr->hdr_version)); + die("pack version %d unsupported", ntohl(hdr->hdr_version)); nr_objects = ntohl(hdr->hdr_entries); use(sizeof(struct pack_header)); @@ -122,8 +142,7 @@ static void bad_object(unsigned long offset, const char *format, ...) va_start(params, format); vsnprintf(buf, sizeof(buf), format, params); va_end(params); - die("packfile '%s': bad object at offset %lu: %s", - pack_name, offset, buf); + die("pack has bad object at offset %lu: %s", offset, buf); } static void *unpack_entry_data(unsigned long offset, unsigned long size) @@ -222,9 +241,9 @@ static void * get_data_from_pack(struct object_entry *obj) int st; map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE, - input_fd, from - pg_offset); + mmap_fd, from - pg_offset); if (map == MAP_FAILED) - die("cannot mmap packfile '%s': %s", pack_name, strerror(errno)); + die("cannot mmap pack file: %s", strerror(errno)); data = xmalloc(obj->size); memset(&stream, 0, sizeof(stream)); stream.next_out = data; @@ -382,14 +401,16 @@ static void parse_pack_objects(unsigned char *sha1) SHA1_Update(&input_ctx, input_buffer, input_offset); SHA1_Final(sha1, &input_ctx); if (hashcmp(fill(20), sha1)) - die("packfile '%s' SHA1 mismatch", pack_name); + die("pack is corrupted (SHA1 mismatch)"); use(20); + if (output_fd >= 0) + write_or_die(output_fd, input_buffer, input_offset); /* If input_fd is a file, we should have reached its end now. */ if (fstat(input_fd, &st)) - die("cannot fstat packfile '%s': %s", pack_name, strerror(errno)); + die("cannot fstat packfile: %s", strerror(errno)); if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes) - die("packfile '%s' has junk at the end", pack_name); + die("pack has junk at the end"); /* Sort deltas by base SHA1/offset for fast searching */ qsort(deltas, nr_deltas, sizeof(struct delta_entry), @@ -435,7 +456,7 @@ static void parse_pack_objects(unsigned char *sha1) for (i = 0; i < nr_deltas; i++) { if (deltas[i].obj->real_type == OBJ_REF_DELTA || deltas[i].obj->real_type == OBJ_OFS_DELTA) - die("packfile '%s' has unresolved deltas", pack_name); + die("pack has unresolved deltas"); } } @@ -450,12 +471,12 @@ static int sha1_compare(const void *_a, const void *_b) * On entry *sha1 contains the pack content SHA1 hash, on exit it is * the SHA1 hash of sorted object names. */ -static void write_index_file(const char *index_name, unsigned char *sha1) +static const char * write_index_file(const char *index_name, unsigned char *sha1) { struct sha1file *f; struct object_entry **sorted_by_sha, **list, **last; unsigned int array[256]; - int i; + int i, fd; SHA_CTX ctx; if (nr_objects) { @@ -472,8 +493,19 @@ static void write_index_file(const char *index_name, unsigned char *sha1) else sorted_by_sha = list = last = NULL; - unlink(index_name); - f = sha1create("%s", index_name); + if (!index_name) { + static char tmpfile[PATH_MAX]; + snprintf(tmpfile, sizeof(tmpfile), + "%s/index_XXXXXX", get_object_directory()); + fd = mkstemp(tmpfile); + index_name = xstrdup(tmpfile); + } else { + unlink(index_name); + fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600); + } + if (fd < 0) + die("unable to create %s: %s", index_name, strerror(errno)); + f = sha1fd(fd, index_name); /* * Write the first-level table (the list is sorted, @@ -513,12 +545,52 @@ static void write_index_file(const char *index_name, unsigned char *sha1) sha1close(f, NULL, 1); free(sorted_by_sha); SHA1_Final(sha1, &ctx); + return index_name; +} + +static void final(const char *final_pack_name, const char *curr_pack_name, + const char *final_index_name, const char *curr_index_name, + unsigned char *sha1) +{ + char name[PATH_MAX]; + int err; + + if (!from_stdin) { + close(input_fd); + } else { + err = close(output_fd); + if (err) + die("error while closing pack file: %s", strerror(errno)); + chmod(curr_pack_name, 0444); + } + + if (final_pack_name != curr_pack_name) { + if (!final_pack_name) { + snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", + get_object_directory(), sha1_to_hex(sha1)); + final_pack_name = name; + } + if (move_temp_to_file(curr_pack_name, final_pack_name)) + die("cannot store pack file"); + } + + chmod(curr_index_name, 0444); + if (final_index_name != curr_index_name) { + if (!final_index_name) { + snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", + get_object_directory(), sha1_to_hex(sha1)); + final_index_name = name; + } + if (move_temp_to_file(curr_index_name, final_index_name)) + die("cannot store index file"); + } } int main(int argc, char **argv) { int i; - char *index_name = NULL; + const char *curr_pack, *pack_name = NULL; + const char *curr_index, *index_name = NULL; char *index_name_buf = NULL; unsigned char sha1[20]; @@ -526,7 +598,9 @@ int main(int argc, char **argv) const char *arg = argv[i]; if (*arg == '-') { - if (!strcmp(arg, "-o")) { + if (!strcmp(arg, "--stdin")) { + from_stdin = 1; + } else if (!strcmp(arg, "-o")) { if (index_name || (i+1) >= argc) usage(index_pack_usage); index_name = argv[++i]; @@ -540,9 +614,9 @@ int main(int argc, char **argv) pack_name = arg; } - if (!pack_name) + if (!pack_name && !from_stdin) usage(index_pack_usage); - if (!index_name) { + if (!index_name && pack_name) { int len = strlen(pack_name); if (!has_extension(pack_name, ".pack")) die("packfile name '%s' does not end with '.pack'", @@ -553,13 +627,14 @@ int main(int argc, char **argv) index_name = index_name_buf; } - open_pack_file(); + curr_pack = open_pack_file(pack_name); parse_pack_header(); objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); parse_pack_objects(sha1); free(deltas); - write_index_file(index_name, sha1); + curr_index = write_index_file(index_name, sha1); + final(pack_name, curr_pack, index_name, curr_index, sha1); free(objects); free(index_name_buf); -- cgit v0.10.2-6-g49f6 From e30496dfcb98a305a57b835c248cbc3aa2376bfc Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Tue, 24 Oct 2006 05:33:17 +0200 Subject: gitweb: Support for 'forks' On repo.or.cz, I want to support project 'forks', which are meant for repositories which are spinoffs of a given project and share its objects database through the alternates mechanism. But another (and perhaps even greater) incentive for that is that those 'forked projects' do not clutter the main project index but are completely grouped inside of the project view. A forked project is just like a normal project, but given project $projectroot/$projname.git, the forked project resides in directory $projectroot/$projname/$forkname.git. This is a somewhat arbitrary naming rule, but I think that for now it's fine; if someone will need something wildly different, let them submit a patch. The 'forked' mode is by default off and can be turned on in runtime gitweb configuration just like other features. A project having forks is marked by a '+' (pointing to the list of forks) in the project list (this could become some cutesy AJAXy DHTML in the future), there is a forks section in the project summary similar to the heads and tags sections, and of course a forks view which looks the same as the root project list. Forks can be recursive. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ba7a42a..9237184 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -164,6 +164,21 @@ our %feature = ( 'pathinfo' => { 'override' => 0, 'default' => [0]}, + + # Make gitweb consider projects in project root subdirectories + # to be forks of existing projects. Given project $projname.git, + # projects matching $projname/*.git will not be shown in the main + # projects list, instead a '+' mark will be added to $projname + # there and a 'forks' view will be enabled for the project, listing + # all the forks. This feature is supported only if project list + # is taken from a directory, not file. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'forks'}{'default'} = [1]; + # Project specific override is not supported. + 'forks' => { + 'override' => 0, + 'default' => [0]}, ); sub gitweb_check_feature { @@ -409,6 +424,7 @@ my %actions = ( "commitdiff" => \&git_commitdiff, "commitdiff_plain" => \&git_commitdiff_plain, "commit" => \&git_commit, + "forks" => \&git_forks, "heads" => \&git_heads, "history" => \&git_history, "log" => \&git_log, @@ -896,13 +912,19 @@ sub git_get_project_url_list { } sub git_get_projects_list { + my ($filter) = @_; my @list; + $filter ||= ''; + $filter =~ s/\.git$//; + if (-d $projects_list) { # search in directory - my $dir = $projects_list; + my $dir = $projects_list . ($filter ? "/$filter" : ''); my $pfxlen = length("$dir"); + my $check_forks = gitweb_check_feature('forks'); + File::Find::find({ follow_fast => 1, # follow symbolic links dangling_symlinks => 0, # ignore dangling symlinks, silently @@ -914,8 +936,10 @@ sub git_get_projects_list { my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot - if (check_export_ok("$projectroot/$subdir")) { - push @list, { path => $subdir }; + if ($check_forks and $subdir =~ m#/.#) { + $File::Find::prune = 1; + } elsif (check_export_ok("$projectroot/$filter/$subdir")) { + push @list, { path => ($filter ? "$filter/" : '') . $subdir }; $File::Find::prune = 1; } }, @@ -2146,6 +2170,124 @@ sub git_patchset_body { # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +sub git_project_list_body { + my ($projlist, $order, $from, $to, $extra, $no_header) = @_; + + my $check_forks = gitweb_check_feature('forks'); + + my @projects; + foreach my $pr (@$projlist) { + my (@aa) = git_get_last_activity($pr->{'path'}); + unless (@aa) { + next; + } + ($pr->{'age'}, $pr->{'age_string'}) = @aa; + if (!defined $pr->{'descr'}) { + my $descr = git_get_project_description($pr->{'path'}) || ""; + $pr->{'descr'} = chop_str($descr, 25, 5); + } + if (!defined $pr->{'owner'}) { + $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; + } + if ($check_forks) { + my $pname = $pr->{'path'}; + $pname =~ s/\.git$//; + $pr->{'forks'} = -d "$projectroot/$pname"; + } + push @projects, $pr; + } + + $order ||= "project"; + $from = 0 unless defined $from; + $to = $#projects if (!defined $to || $#projects < $to); + + print "
' . mode_str('040000') . "'; + print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base, + file_name=>$up)}, + ".."); + print "
"; + print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blob"); if ($have_blame) { - print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, + print " | " . + $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key)}, "blame"); } if (defined $hash_base) { - if ($have_blame) { - print " | "; - } - print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, "history"); } @@ -1815,8 +1817,12 @@ sub git_print_tree_entry { esc_html($t->{'name'})); print ""; + print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "tree"); if (defined $hash_base) { - print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, file_name=>"$basedir$t->{'name'}")}, "history"); } @@ -1899,6 +1905,9 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); print " | "; } + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + hash_base=>$parent, file_name=>$diff{'file'})}, + "blob") . " | "; print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, file_name=>$diff{'file'})}, "blame") . " | "; @@ -1944,6 +1953,9 @@ sub git_difftree_body { } print " | "; } + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob") . " | "; print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, file_name=>$diff{'file'})}, "blame") . " | "; @@ -1984,6 +1996,9 @@ sub git_difftree_body { } print " | "; } + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + hash_base=>$parent, file_name=>$diff{'from_file'})}, + "blob") . " | "; print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, file_name=>$diff{'from_file'})}, "blame") . " | "; @@ -2151,6 +2166,7 @@ sub git_shortlog_body { href(action=>"commit", hash=>$commit), $ref); print "" . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); if (gitweb_have_snapshot()) { -- cgit v0.10.2-6-g49f6 From 88ad729b73264025d2d4c187ff74432d7cacafb2 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Tue, 24 Oct 2006 05:15:46 +0200 Subject: gitweb: Make search type a popup menu This makes the multiple search types actually usable by the user; if you don't read the gitweb source, you don't even have an idea that you can write things like that there. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 3f62b6d..0eda982 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -333,6 +333,8 @@ div.index_include { } div.search { + font-size: 12px; + font-weight: normal; margin: 4px 8px; position: absolute; top: 56px; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5aed661..e93a211 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -342,6 +342,13 @@ if (defined $searchtext) { $searchtext = quotemeta $searchtext; } +our $searchtype = $cgi->param('st'); +if (defined $searchtype) { + if ($searchtype =~ m/[^a-z]/) { + die_error(undef, "Invalid searchtype parameter"); + } +} + # now read PATH_INFO and use it as alternative to parameters sub evaluate_path_info { return if defined $project; @@ -406,6 +413,7 @@ my %actions = ( "log" => \&git_log, "rss" => \&git_rss, "search" => \&git_search, + "search_help" => \&git_search_help, "shortlog" => \&git_shortlog, "summary" => \&git_summary, "tag" => \&git_tag, @@ -455,6 +463,7 @@ sub href(%) { page => "pg", order => "o", searchtext => "s", + searchtype => "st", ); my %mapping = @mapping; @@ -1524,6 +1533,10 @@ EOF $cgi->hidden(-name => "p") . "\n" . $cgi->hidden(-name => "a") . "\n" . $cgi->hidden(-name => "h") . "\n" . + $cgi->popup_menu(-name => 'st', -default => 'commit', + -values => ['commit', 'author', 'committer', 'pickaxe']) . + $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) . + " search:\n", $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . "" . $cgi->end_form() . "\n"; @@ -3544,18 +3557,8 @@ sub git_search { die_error(undef, "Unknown commit object"); } - my $commit_search = 1; - my $author_search = 0; - my $committer_search = 0; - my $pickaxe_search = 0; - if ($searchtext =~ s/^author\\://i) { - $author_search = 1; - } elsif ($searchtext =~ s/^committer\\://i) { - $committer_search = 1; - } elsif ($searchtext =~ s/^pickaxe\\://i) { - $commit_search = 0; - $pickaxe_search = 1; - + $searchtype ||= 'commit'; + if ($searchtype eq 'pickaxe') { # pickaxe may take all resources of your box and run for several minutes # with every query - so decide by yourself how public you make this feature my ($have_pickaxe) = gitweb_check_feature('pickaxe'); @@ -3563,23 +3566,24 @@ sub git_search { die_error('403 Permission denied', "Permission denied"); } } + git_header_html(); git_print_page_nav('','', $hash,$co{'tree'},$hash); git_print_header_div('commit', esc_html($co{'title'}), $hash); print "\n"; my $alternate = 1; - if ($commit_search) { + if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') { $/ = "\0"; open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next; while (my $commit_text = <$fd>) { if (!grep m/$searchtext/i, $commit_text) { next; } - if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) { + if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) { next; } - if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) { + if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) { next; } my @commit_lines = split "\n", $commit_text; @@ -3621,7 +3625,7 @@ sub git_search { close $fd; } - if ($pickaxe_search) { + if ($searchtype eq 'pickaxe') { $/ = "\n"; my $git_command = git_cmd_str(); open my $fd, "-|", "$git_command rev-list $hash | " . @@ -3681,6 +3685,31 @@ sub git_search { git_footer_html(); } +sub git_search_help { + git_header_html(); + git_print_page_nav('','', $hash,$hash,$hash); + print < +
commit
+
The commit messages and authorship information will be scanned for the given string.
+
author
+
Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.
+
committer
+
Name and e-mail of the committer and date of commit will be scanned for the given string.
+EOT + my ($have_pickaxe) = gitweb_check_feature('pickaxe'); + if ($have_pickaxe) { + print <pickaxe +
All commits that caused the string to appear or disappear from any file (changes that +added, removed or "modified" the string) will be listed. This search can take a while and +takes a lot of strain on the server, so please use it wisely.
+EOT + } + print "\n"; + git_footer_html(); +} + sub git_shortlog { my $head = git_get_head_hash($project); if (!defined $hash) { -- cgit v0.10.2-6-g49f6 From 8be2890c994102d5748342f44bd6353bd482b58b Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Tue, 24 Oct 2006 05:18:39 +0200 Subject: gitweb: Do not automatically append " git" to custom site name If you customized the site name, you probably do not want the " git" appended so that the page title is not bastardized; I want repo.or.cz pages titled "Public Git Hosting", not "Public Git Hosting git" (what's hosting what?). This slightly changes the $site_name semantics but only very insignificantly. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e93a211..6047806 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -39,7 +39,8 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++"; # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks -our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; +our $site_name = "++GITWEB_SITENAME++" + || ($ENV{'SERVER_NAME'} || "Untitled") . " Git"; # filename of html text to include at top of each page our $site_header = "++GITWEB_SITE_HEADER++"; @@ -1429,7 +1430,7 @@ sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; - my $title = "$site_name git"; + my $title = "$site_name"; if (defined $project) { $title .= " - $project"; if (defined $action) { @@ -3818,7 +3819,7 @@ sub git_opml { - $site_name Git OPML Export + $site_name OPML Export -- cgit v0.10.2-6-g49f6 From 447ef09a5cf98bea28ec5123b968c966afce5772 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Tue, 24 Oct 2006 05:23:46 +0200 Subject: gitweb: Show project's README.html if available If the repository includes a README.html file, show it in the summary page. The usual "this should be in the config file" argument does not apply here since this can be larger and having such a big string in the config file would be impractical. I don't know if this is suitable upstream, but it's one of the repo.or.cz custom modifications that I've thought could be interesting for others as well. Compared to the previous patch, this adds the '.html' extension to the filename, so that it's clear it is, well, HTML. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6047806..a201043 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2530,6 +2530,14 @@ sub git_summary { } print "
\n"; + if (-s "$projectroot/$project/README.html") { + if (open my $fd, "$projectroot/$project/README.html") { + print "
readme
\n"; + print $_ while (<$fd>); + close $fd; + } + } + open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", git_get_head_hash($project) or die_error(undef, "Open git-rev-list failed"); -- cgit v0.10.2-6-g49f6 From 694500edbd51baef365c588986abe41f01acf0de Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 23 Oct 2006 21:15:34 -0700 Subject: sha1_name.c: avoid compilation warnings. Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index e517033..5cf5578 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -258,7 +258,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) return 0; /* basic@{time or number} format to query ref-log */ - reflog_len = 0; + reflog_len = at = 0; if (str[len-1] == '}') { for (at = 1; at < len - 1; at++) { if (str[at] == '@' && str[at+1] == '{') { -- cgit v0.10.2-6-g49f6 From 5d9e8ee78b1df7a9c1b2d13c2b58628201f01f72 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 23 Oct 2006 22:48:45 -0700 Subject: t3200: git-branch testsuite update The test expected "git branch --help" to exit successfully, but built-ins spawn "man" when given --help, and when the test is run, manpages may not be installed yet and "man" can legally exit non-zero in such a case. Also the new implementation logs "Created from master", instead of "Created from HEAD" in the reflog, which makes a lot more sense, so adjust the test to match that. Signed-off-by: Junio C Hamano diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 6907cbc..acb54b6 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -17,13 +17,10 @@ test_expect_success \ git-commit -m "Initial commit." && HEAD=$(git-rev-parse --verify HEAD)' -test_expect_success \ - 'git branch --help should return success now.' \ - 'git-branch --help' - test_expect_failure \ 'git branch --help should not have created a bogus branch' \ - 'test -f .git/refs/heads/--help' + 'git-branch --help /dev/null 2>/dev/null || : + test -f .git/refs/heads/--help' test_expect_success \ 'git branch abc should create a branch' \ @@ -34,7 +31,7 @@ test_expect_success \ 'git-branch a/b/c && test -f .git/refs/heads/a/b/c' cat >expect < 1117150200 +0000 branch: Created from HEAD +0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF test_expect_success \ 'git branch -l d/e/f should create a branch and a log' \ -- cgit v0.10.2-6-g49f6 From 2eb10ac7b51c8bb2257e5e2dea85929675de3fd1 Mon Sep 17 00:00:00 2001 From: Gerrit Pape Date: Tue, 24 Oct 2006 20:00:37 +0000 Subject: Set $HOME for selftests Set HOME environment variable to test trash directory and export for selftests. This fixes the git-svn selftests with nonexistent or not readable home, as found in at least one automated build system: http://buildd.debian.org/fetch.cgi?&pkg=git-core&ver=1%3A1.4.2.3-2&arch=alpha&stamp=1161537466&file=log Signed-off-by: Gerrit Pape Signed-off-by: Junio C Hamano diff --git a/t/test-lib.sh b/t/test-lib.sh index 2488e6e..07cb706 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -207,7 +207,8 @@ test_done () { # t/ subdirectory and are run in trash subdirectory. PATH=$(pwd)/..:$PATH GIT_EXEC_PATH=$(pwd)/.. -export PATH GIT_EXEC_PATH +HOME=$(pwd)/trash +export PATH GIT_EXEC_PATH HOME # Similarly use ../compat/subprocess.py if our python does not # have subprocess.py on its own. -- cgit v0.10.2-6-g49f6 From 9ffd652a385298f3565fdc3f0d0424001a376539 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 24 Oct 2006 02:50:37 -0700 Subject: git-svn: fix symlink-to-file changes when using command-line svn 1.4.0 I incorrectly thought this was hopelessly broken in svn 1.4.0, but now it's just broken in that the old method didn't work. It looks like svn propdel and svn propset must be used now and the (imho) more obvious svn rm --force && svn add no longer works. "make -C t full-svn-test" should now work. Signed-off-by: Eric Wong Acked-by: Uwe Zeisberger Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 54d2356..37ecc51 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1501,10 +1501,13 @@ sub svn_checkout_tree { apply_mod_line_blob($m); svn_check_prop_executable($m); } elsif ($m->{chg} eq 'T') { - sys(qw(svn rm --force),$m->{file_b}); - apply_mod_line_blob($m); - sys(qw(svn add), $m->{file_b}); svn_check_prop_executable($m); + apply_mod_line_blob($m); + if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) { + sys(qw(svn propdel svn:special), $m->{file_b}); + } else { + sys(qw(svn propset svn:special *),$m->{file_b}); + } } elsif ($m->{chg} eq 'A') { svn_ensure_parent_path( $m->{file_b} ); apply_mod_line_blob($m); -- cgit v0.10.2-6-g49f6 From 04d24455ceb0129195f60af6b62981542433ecf7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 24 Oct 2006 01:29:27 -0700 Subject: Documentation: note about contrib/. Signed-off-by: Junio C Hamano diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 90722c2..a5b3ebd 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -101,6 +101,11 @@ send it "To:" the mailing list, and optionally "cc:" him. If it is trivially correct or after the list reached a consensus, send it "To:" the maintainer and optionally "cc:" the list. +Also note that your maintainer does not actively involve himself in +maintaining what are in contrib/ hierarchy. When you send fixes and +enhancements to them, do not forget to "cc: " the person who primarily +worked on that hierarchy in contrib/. + (6) Sign your work -- cgit v0.10.2-6-g49f6 From f2069411c9b5b5c99756cdfb2e54a61aef5c9f72 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 24 Oct 2006 13:52:46 +0200 Subject: gitweb: Get rid of git_print_simplified_log Replace calls to git_print_simplified_log with its expansion, i.e. with calling git_print_log with appropriate options. Remove no longer used git_print_simplified_log subroutine. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a201043..5f0a134 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1776,15 +1776,6 @@ sub git_print_log ($;%) { } } -sub git_print_simplified_log { - my $log = shift; - my $remove_title = shift; - - git_print_log($log, - -final_empty_line=> 1, - -remove_title => $remove_title); -} - # print tree entry (row of git_tree), but without encompassing
\n"; + unless ($no_header) { + print "\n"; + if ($check_forks) { + print "\n"; + } + if ($order eq "project") { + @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; + print "\n"; + } else { + print "\n"; + } + if ($order eq "descr") { + @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; + print "\n"; + } else { + print "\n"; + } + if ($order eq "owner") { + @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; + print "\n"; + } else { + print "\n"; + } + if ($order eq "age") { + @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects; + print "\n"; + } else { + print "\n"; + } + print "\n" . + "\n"; + } + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + my $pr = $projects[$i]; + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + if ($check_forks) { + print "\n"; + } + print "\n" . + "\n" . + "\n"; + print "\n" . + "\n" . + "\n"; + } + if (defined $extra) { + print "\n"; + if ($check_forks) { + print "\n"; + } + print "\n" . + "\n"; + } + print "
Project" . + $cgi->a({-href => href(project=>undef, order=>'project'), + -class => "header"}, "Project") . + "Description" . + $cgi->a({-href => href(project=>undef, order=>'descr'), + -class => "header"}, "Description") . + "Owner" . + $cgi->a({-href => href(project=>undef, order=>'owner'), + -class => "header"}, "Owner") . + "Last Change" . + $cgi->a({-href => href(project=>undef, order=>'age'), + -class => "header"}, "Last Change") . + "
"; + if ($pr->{'forks'}) { + print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+"); + } + print "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), + -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'age'}) . "\">" . + $pr->{'age_string'} . "" . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . + $cgi->a({-href => '/git-browser/by-commit.html?r='.$pr->{'path'}}, "graphiclog") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . + ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') . + "
$extra
\n"; +} + sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; @@ -2364,25 +2506,9 @@ sub git_project_list { } my @list = git_get_projects_list(); - my @projects; if (!@list) { die_error(undef, "No projects found"); } - foreach my $pr (@list) { - my (@aa) = git_get_last_activity($pr->{'path'}); - unless (@aa) { - next; - } - ($pr->{'age'}, $pr->{'age_string'}) = @aa; - if (!defined $pr->{'descr'}) { - my $descr = git_get_project_description($pr->{'path'}) || ""; - $pr->{'descr'} = chop_str($descr, 25, 5); - } - if (!defined $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; - } - push @projects, $pr; - } git_header_html(); if (-f $home_text) { @@ -2392,75 +2518,30 @@ sub git_project_list { close $fd; print "
\n"; } - print "\n" . - "\n"; - $order ||= "project"; - if ($order eq "project") { - @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; - print "\n"; - } else { - print "\n"; - } - if ($order eq "descr") { - @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; - print "\n"; - } else { - print "\n"; - } - if ($order eq "owner") { - @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; - print "\n"; - } else { - print "\n"; - } - if ($order eq "age") { - @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects; - print "\n"; - } else { - print "\n"; + git_project_list_body(\@list, $order); + git_footer_html(); +} + +sub git_forks { + my $order = $cgi->param('o'); + if (defined $order && $order !~ m/project|descr|owner|age/) { + die_error(undef, "Unknown order parameter"); } - print "\n" . - "\n"; - my $alternate = 1; - foreach my $pr (@projects) { - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n"; - print "\n" . - "\n" . - "\n"; + + my @list = git_get_projects_list($project); + if (!@list) { + die_error(undef, "No forks found"); } - print "
Project" . - $cgi->a({-href => href(project=>undef, order=>'project'), - -class => "header"}, "Project") . - "Description" . - $cgi->a({-href => href(project=>undef, order=>'descr'), - -class => "header"}, "Description") . - "Owner" . - $cgi->a({-href => href(project=>undef, order=>'owner'), - -class => "header"}, "Owner") . - "Last Change" . - $cgi->a({-href => href(project=>undef, order=>'age'), - -class => "header"}, "Last Change") . - "
" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), - -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'age'}) . "\">" . - $pr->{'age_string'} . "" . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . - "
\n"; + + git_header_html(); + git_print_page_nav('',''); + git_print_header_div('summary', "$project forks"); + git_project_list_body(\@list, $order); git_footer_html(); } sub git_project_index { - my @projects = git_get_projects_list(); + my @projects = git_get_projects_list($project); print $cgi->header( -type => 'text/plain', @@ -2503,6 +2584,10 @@ sub git_summary { push @taglist, $ref; } } + my @forklist; + if (gitweb_check_feature('forks')) { + @forklist = git_get_projects_list($project); + } git_header_html(); git_print_page_nav('summary','', $head); @@ -2553,6 +2638,13 @@ sub git_summary { $cgi->a({-href => href(action=>"heads")}, "...")); } + if (@forklist) { + git_print_header_div('forks'); + git_project_list_body(\@forklist, undef, 0, 15, + $cgi->a({-href => href(action=>"forks")}, "..."), + 'noheader'); + } + git_footer_html(); } -- cgit v0.10.2-6-g49f6 From 119550af0c9f3cdd920e8d0e04b68b40b8485fdc Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Wed, 25 Oct 2006 22:43:47 -0400 Subject: Documentation: updates to "Everyday GIT" Remove the introduction: I think it should be obvious why we have this. (And if it isn't obvious then we've got other problems.) Replace reference to git whatchanged by git log. Miscellaneous style and grammar fixes. Signed-off-by: J. Bruce Fields Signed-off-by: Junio C Hamano diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index b935c18..99e24a4 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -1,22 +1,7 @@ Everyday GIT With 20 Commands Or So =================================== -GIT suite has over 100 commands, and the manual page for each of -them discusses what the command does and how it is used in -detail, but until you know what command should be used in order -to achieve what you want to do, you cannot tell which manual -page to look at, and if you know that already you do not need -the manual. - -Does that mean you need to know all of them before you can use -git? Not at all. Depending on the role you play, the set of -commands you need to know is slightly different, but in any case -what you need to learn is far smaller than the full set of -commands to carry out your day-to-day work. This document is to -serve as a cheat-sheet and a set of pointers for people playing -various roles. - -<> commands are needed by people who has a +<> commands are needed by people who have a repository --- that is everybody, because every working tree of git is a repository. @@ -25,28 +10,27 @@ essential for anybody who makes a commit, even for somebody who works alone. If you work with other people, you will need commands listed in -<> section as well. +the <> section as well. -People who play <> role need to learn some more +People who play the <> role need to learn some more commands in addition to the above. <> commands are for system -administrators who are responsible to care and feed git -repositories to support developers. +administrators who are responsible for the care and feeding +of git repositories. Basic Repository[[Basic Repository]] ------------------------------------ -Everybody uses these commands to feed and care git repositories. +Everybody uses these commands to maintain git repositories. * gitlink:git-init-db[1] or gitlink:git-clone[1] to create a new repository. - * gitlink:git-fsck-objects[1] to validate the repository. + * gitlink:git-fsck-objects[1] to check the repository for errors. - * gitlink:git-prune[1] to garbage collect cruft in the - repository. + * gitlink:git-prune[1] to remove unused objects in the repository. * gitlink:git-repack[1] to pack loose objects for efficiency. @@ -78,8 +62,8 @@ $ git repack -a -d <1> $ git prune ------------ + -<1> pack all the objects reachable from the refs into one pack -and remove unneeded other packs +<1> pack all the objects reachable from the refs into one pack, +then remove the other packs. Individual Developer (Standalone)[[Individual Developer (Standalone)]] @@ -93,9 +77,6 @@ following commands. * gitlink:git-log[1] to see what happened. - * gitlink:git-whatchanged[1] to find out where things have - come from. - * gitlink:git-checkout[1] and gitlink:git-branch[1] to switch branches. @@ -120,7 +101,7 @@ following commands. Examples ~~~~~~~~ -Extract a tarball and create a working tree and a new repository to keep track of it.:: +Use a tarball as a starting point for a new repository: + ------------ $ tar zxf frotz.tar.gz @@ -203,7 +184,7 @@ $ cd my2.6 $ edit/compile/test; git commit -a -s <1> $ git format-patch origin <2> $ git pull <3> -$ git whatchanged -p ORIG_HEAD.. arch/i386 include/asm-i386 <4> +$ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <4> $ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5> $ git reset --hard ORIG_HEAD <6> $ git prune <7> -- cgit v0.10.2-6-g49f6 From d9c04ba3ddd0945ef18657a2a373c5edfe86ef7c Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Oct 2006 06:33:07 +0200 Subject: Remove --syslog in git-daemon inetd documentation examples. It is useless because --inetd implies --syslog. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index 99e24a4..278161f 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -358,7 +358,7 @@ Run git-daemon to serve /pub/scm from inetd.:: ------------ $ grep git /etc/inetd.conf git stream tcp nowait nobody \ - /usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm + /usr/bin/git-daemon git-daemon --inetd --export-all /pub/scm ------------ + The actual configuration line should be on one line. @@ -378,7 +378,7 @@ service git wait = no user = nobody server = /usr/bin/git-daemon - server_args = --inetd --syslog --export-all --base-path=/pub/scm + server_args = --inetd --export-all --base-path=/pub/scm log_on_failure += USERID } ------------ diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index d562232..4b2ea2d 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -165,8 +165,7 @@ git-daemon as inetd server:: + ------------------------------------------------ git stream tcp nowait nobody /usr/bin/git-daemon - git-daemon --inetd --verbose - --syslog --export-all + git-daemon --inetd --verbose --export-all /pub/foo /pub/bar ------------------------------------------------ @@ -179,8 +178,7 @@ git-daemon as inetd server for virtual hosts:: + ------------------------------------------------ git stream tcp nowait nobody /usr/bin/git-daemon - git-daemon --inetd --verbose - --syslog --export-all + git-daemon --inetd --verbose --export-all --interpolated-path=/pub/%H%D /pub/www.example.org/software /pub/www.example.com/software -- cgit v0.10.2-6-g49f6 From 0074aba1c0d3b073c635fdc1d5754dc6a6b6b7b9 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 26 Oct 2006 05:44:49 +0200 Subject: diff-format.txt: Combined diff format documentation supplement Update example combined diff format to the current version $ git diff-tree -p -c fec9ebf16c948bcb4a8b88d0173ee63584bcde76 and provide complete first chunk in example. Document combined diff format headers: how "diff header" look like, which of "extended diff headers" are used with combined diff and how they look like, differences in two-line from-file/to-file header from non-combined diff format, chunk header format. It should be noted that combined diff format was designed for quick _content_ inspection and renames would work correctly to pick which blobs from each tree to compare but otherwise not reflected in the output (the pathnames are not shown). [jc: with minimum copyediting] Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 617d8f5..174d63a 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -156,31 +156,91 @@ to produce 'combined diff', which looks like this: ------------ diff --combined describe.c -@@@ +98,7 @@@ - return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; +index fabadb8,cc95eb0..4866510 +--- a/describe.c ++++ b/describe.c +@@@ -98,20 -98,12 +98,20 @@@ + return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; } - + - static void describe(char *arg) -static void describe(struct commit *cmit, int last_one) ++static void describe(char *arg, int last_one) { - + unsigned char sha1[20]; - + struct commit *cmit; + + unsigned char sha1[20]; + + struct commit *cmit; + struct commit_list *list; + static int initialized = 0; + struct commit_name *n; + + + if (get_sha1(arg, sha1) < 0) + + usage(describe_usage); + + cmit = lookup_commit_reference(sha1); + + if (!cmit) + + usage(describe_usage); + + + if (!initialized) { + initialized = 1; + for_each_ref(get_name); ------------ +1. It is preceded with a "git diff" header, that looks like + this (when '-c' option is used): + + diff --combined file ++ +or like this (when '--cc' option is used): + + diff --c file + +2. It is followed by one or more extended header lines + (this example shows a merge with two parents): + + index ,.. + mode ,.. + new file mode + deleted file mode , ++ +The `mode ,..` line appears only if at least one of +the is diferent from the rest. Extended headers with +information about detected contents movement (renames and +copying detection) are designed to work with diff of two + and are not used by combined diff format. + +3. It is followed by two-line from-file/to-file header + + --- a/file + +++ b/file ++ +Contrary to two-line header for traditional 'unified' diff +format, and similar to filenames in ordinary "diff header", +/dev/null is not used for creation or deletion. + +4. Chunk header format is modified to prevent people from + accidentally feeding it to `patch -p1`. Combined diff format + was created for review of merge commit changes, and was not + meant for apply. The change is similar to the change in the + extended 'index' header: + + @@@ @@@ ++ +There are (number of parents + 1) `@` characters in the chunk +header for combined diff format. + Unlike the traditional 'unified' diff format, which shows two files A and B with a single column that has `-` (minus -- appears in A but removed in B), `+` (plus -- missing in A but -added to B), or ` ` (space -- unchanged) prefix, this format +added to B), or `" "` (space -- unchanged) prefix, this format compares two or more files file1, file2,... with one file X, and shows how X differs from each of fileN. One column for each of fileN is prepended to the output line to note how X's line is different from it. A `-` character in the column N means that the line appears in -fileN but it does not appear in the last file. A `+` character +fileN but it does not appear in the result. A `+` character in the column N means that the line appears in the last file, -and fileN does not have that line. +and fileN does not have that line (in other words, the line was +added, from the point of view of that parent). In the above example output, the function signature was changed from both files (hence two `-` removals from both file1 and -- cgit v0.10.2-6-g49f6 From 636171cb80255682bdfc9bf5a98c9e66d4c0444a Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 25 Oct 2006 23:28:17 -0400 Subject: make index-pack able to complete thin packs. A new flag, --fix-thin, instructs git-index-pack to append any missing objects to a thin pack to make it self contained and indexable. Of course objects missing from the pack must be present elsewhere in the local repository. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index db7af58..c58287d 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -8,7 +8,8 @@ git-index-pack - Build pack index file for an existing packed archive SYNOPSIS -------- -'git-index-pack' [-o ] { | --stdin [] } +'git-index-pack' [-o ] +'git-index-pack' --stdin [--fix-thin] [-o ] [] DESCRIPTION @@ -36,6 +37,17 @@ OPTIONS objects/pack/ directory of the current git repository with a default name determined from the pack content. +--fix-thin:: + It is possible for gitlink:git-pack-objects[1] to build + "thin" pack, which records objects in deltified form based on + objects not included in the pack to reduce network traffic. + Those objects are expected to be present on the receiving end + and they must be included in the pack for that pack to be self + contained and indexable. Without this option any attempt to + index a thin pack will fail. This option only makes sense in + conjonction with --stdin. + + Author ------ Written by Sergey Vlasov diff --git a/index-pack.c b/index-pack.c index cecdd26..9086bbf 100644 --- a/index-pack.c +++ b/index-pack.c @@ -8,7 +8,7 @@ #include "tree.h" static const char index_pack_usage[] = -"git-index-pack [-o ] { | --stdin [] }"; +"git-index-pack [-o ] { | --stdin [--fix-thin] [] }"; struct object_entry { @@ -33,14 +33,15 @@ union delta_base { struct delta_entry { - struct object_entry *obj; union delta_base base; + int obj_no; }; static struct object_entry *objects; static struct delta_entry *deltas; static int nr_objects; static int nr_deltas; +static int nr_resolved_deltas; static int from_stdin; @@ -50,6 +51,18 @@ static unsigned long input_offset, input_len, consumed_bytes; static SHA_CTX input_ctx; static int input_fd, output_fd, mmap_fd; +/* Discard current buffer used content. */ +static void flush() +{ + if (input_offset) { + if (output_fd >= 0) + write_or_die(output_fd, input_buffer, input_offset); + SHA1_Update(&input_ctx, input_buffer, input_offset); + memcpy(input_buffer, input_buffer + input_offset, input_len); + input_offset = 0; + } +} + /* * Make sure at least "min" bytes are available in the buffer, and * return the pointer to the buffer. @@ -60,13 +73,7 @@ static void * fill(int min) return input_buffer + input_offset; if (min > sizeof(input_buffer)) die("cannot fill %d bytes", min); - if (input_offset) { - if (output_fd >= 0) - write_or_die(output_fd, input_buffer, input_offset); - SHA1_Update(&input_ctx, input_buffer, input_offset); - memcpy(input_buffer, input_buffer + input_offset, input_len); - input_offset = 0; - } + flush(); do { int ret = xread(input_fd, input_buffer + input_len, sizeof(input_buffer) - input_len); @@ -323,10 +330,9 @@ static void sha1_object(const void *data, unsigned long size, SHA1_Final(sha1, &ctx); } -static void resolve_delta(struct delta_entry *delta, void *base_data, +static void resolve_delta(struct object_entry *delta_obj, void *base_data, unsigned long base_size, enum object_type type) { - struct object_entry *obj = delta->obj; void *delta_data; unsigned long delta_size; void *result; @@ -334,29 +340,34 @@ static void resolve_delta(struct delta_entry *delta, void *base_data, union delta_base delta_base; int j, first, last; - obj->real_type = type; - delta_data = get_data_from_pack(obj); - delta_size = obj->size; + delta_obj->real_type = type; + delta_data = get_data_from_pack(delta_obj); + delta_size = delta_obj->size; result = patch_delta(base_data, base_size, delta_data, delta_size, &result_size); free(delta_data); if (!result) - bad_object(obj->offset, "failed to apply delta"); - sha1_object(result, result_size, type, obj->sha1); + bad_object(delta_obj->offset, "failed to apply delta"); + sha1_object(result, result_size, type, delta_obj->sha1); + nr_resolved_deltas++; - hashcpy(delta_base.sha1, obj->sha1); + hashcpy(delta_base.sha1, delta_obj->sha1); if (!find_delta_childs(&delta_base, &first, &last)) { - for (j = first; j <= last; j++) - if (deltas[j].obj->type == OBJ_REF_DELTA) - resolve_delta(&deltas[j], result, result_size, type); + for (j = first; j <= last; j++) { + struct object_entry *child = objects + deltas[j].obj_no; + if (child->real_type == OBJ_REF_DELTA) + resolve_delta(child, result, result_size, type); + } } memset(&delta_base, 0, sizeof(delta_base)); - delta_base.offset = obj->offset; + delta_base.offset = delta_obj->offset; if (!find_delta_childs(&delta_base, &first, &last)) { - for (j = first; j <= last; j++) - if (deltas[j].obj->type == OBJ_OFS_DELTA) - resolve_delta(&deltas[j], result, result_size, type); + for (j = first; j <= last; j++) { + struct object_entry *child = objects + deltas[j].obj_no; + if (child->real_type == OBJ_OFS_DELTA) + resolve_delta(child, result, result_size, type); + } } free(result); @@ -389,7 +400,7 @@ static void parse_pack_objects(unsigned char *sha1) obj->real_type = obj->type; if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { nr_deltas++; - delta->obj = obj; + delta->obj_no = i; delta++; } else sha1_object(data, obj->size, obj->type, obj->sha1); @@ -398,18 +409,15 @@ static void parse_pack_objects(unsigned char *sha1) objects[i].offset = consumed_bytes; /* Check pack integrity */ - SHA1_Update(&input_ctx, input_buffer, input_offset); + flush(); SHA1_Final(sha1, &input_ctx); if (hashcmp(fill(20), sha1)) die("pack is corrupted (SHA1 mismatch)"); - use(20); - if (output_fd >= 0) - write_or_die(output_fd, input_buffer, input_offset); /* If input_fd is a file, we should have reached its end now. */ if (fstat(input_fd, &st)) die("cannot fstat packfile: %s", strerror(errno)); - if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes) + if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes + 20) die("pack has junk at the end"); /* Sort deltas by base SHA1/offset for fast searching */ @@ -440,24 +448,161 @@ static void parse_pack_objects(unsigned char *sha1) continue; data = get_data_from_pack(obj); if (ref) - for (j = ref_first; j <= ref_last; j++) - if (deltas[j].obj->type == OBJ_REF_DELTA) - resolve_delta(&deltas[j], data, + for (j = ref_first; j <= ref_last; j++) { + struct object_entry *child = objects + deltas[j].obj_no; + if (child->real_type == OBJ_REF_DELTA) + resolve_delta(child, data, obj->size, obj->type); + } if (ofs) - for (j = ofs_first; j <= ofs_last; j++) - if (deltas[j].obj->type == OBJ_OFS_DELTA) - resolve_delta(&deltas[j], data, + for (j = ofs_first; j <= ofs_last; j++) { + struct object_entry *child = objects + deltas[j].obj_no; + if (child->real_type == OBJ_OFS_DELTA) + resolve_delta(child, data, obj->size, obj->type); + } free(data); } +} + +static int write_compressed(int fd, void *in, unsigned int size) +{ + z_stream stream; + unsigned long maxsize; + void *out; + + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, zlib_compression_level); + maxsize = deflateBound(&stream, size); + out = xmalloc(maxsize); + + /* Compress it */ + stream.next_in = in; + stream.avail_in = size; + stream.next_out = out; + stream.avail_out = maxsize; + while (deflate(&stream, Z_FINISH) == Z_OK); + deflateEnd(&stream); + + size = stream.total_out; + write_or_die(fd, out, size); + free(out); + return size; +} + +static void append_obj_to_pack(void *buf, + unsigned long size, enum object_type type) +{ + struct object_entry *obj = &objects[nr_objects++]; + unsigned char header[10]; + unsigned long s = size; + int n = 0; + unsigned char c = (type << 4) | (s & 15); + s >>= 4; + while (s) { + header[n++] = c | 0x80; + c = s & 0x7f; + s >>= 7; + } + header[n++] = c; + write_or_die(output_fd, header, n); + obj[1].offset = obj[0].offset + n; + obj[1].offset += write_compressed(output_fd, buf, size); + sha1_object(buf, size, type, obj->sha1); +} + +static int delta_pos_compare(const void *_a, const void *_b) +{ + struct delta_entry *a = *(struct delta_entry **)_a; + struct delta_entry *b = *(struct delta_entry **)_b; + return a->obj_no - b->obj_no; +} - /* Check for unresolved deltas */ +static void fix_unresolved_deltas(int nr_unresolved) +{ + struct delta_entry **sorted_by_pos; + int i, n = 0; + + /* + * Since many unresolved deltas may well be themselves base objects + * for more unresolved deltas, we really want to include the + * smallest number of base objects that would cover as much delta + * as possible by picking the + * trunc deltas first, allowing for other deltas to resolve without + * additional base objects. Since most base objects are to be found + * before deltas depending on them, a good heuristic is to start + * resolving deltas in the same order as their position in the pack. + */ + sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos)); for (i = 0; i < nr_deltas; i++) { - if (deltas[i].obj->real_type == OBJ_REF_DELTA || - deltas[i].obj->real_type == OBJ_OFS_DELTA) - die("pack has unresolved deltas"); + if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA) + continue; + sorted_by_pos[n++] = &deltas[i]; } + qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare); + + for (i = 0; i < n; i++) { + struct delta_entry *d = sorted_by_pos[i]; + void *data; + unsigned long size; + char type[10]; + enum object_type obj_type; + int j, first, last; + + if (objects[d->obj_no].real_type != OBJ_REF_DELTA) + continue; + data = read_sha1_file(d->base.sha1, type, &size); + if (!data) + continue; + if (!strcmp(type, blob_type)) obj_type = OBJ_BLOB; + else if (!strcmp(type, tree_type)) obj_type = OBJ_TREE; + else if (!strcmp(type, commit_type)) obj_type = OBJ_COMMIT; + else if (!strcmp(type, tag_type)) obj_type = OBJ_TAG; + else die("base object %s is of type '%s'", + sha1_to_hex(d->base.sha1), type); + + find_delta_childs(&d->base, &first, &last); + for (j = first; j <= last; j++) { + struct object_entry *child = objects + deltas[j].obj_no; + if (child->real_type == OBJ_REF_DELTA) + resolve_delta(child, data, size, obj_type); + } + + append_obj_to_pack(data, size, obj_type); + free(data); + } + free(sorted_by_pos); +} + +static void readjust_pack_header_and_sha1(unsigned char *sha1) +{ + struct pack_header hdr; + SHA_CTX ctx; + int size; + + /* Rewrite pack header with updated object number */ + if (lseek(output_fd, 0, SEEK_SET) != 0) + die("cannot seek back: %s", strerror(errno)); + if (xread(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) + die("cannot read pack header back: %s", strerror(errno)); + hdr.hdr_entries = htonl(nr_objects); + if (lseek(output_fd, 0, SEEK_SET) != 0) + die("cannot seek back: %s", strerror(errno)); + write_or_die(output_fd, &hdr, sizeof(hdr)); + if (lseek(output_fd, 0, SEEK_SET) != 0) + die("cannot seek back: %s", strerror(errno)); + + /* Recompute and store the new pack's SHA1 */ + SHA1_Init(&ctx); + do { + unsigned char *buf[4096]; + size = xread(output_fd, buf, sizeof(buf)); + if (size < 0) + die("cannot read pack data back: %s", strerror(errno)); + SHA1_Update(&ctx, buf, size); + } while (size > 0); + SHA1_Final(sha1, &ctx); + write_or_die(output_fd, sha1, 20); } static int sha1_compare(const void *_a, const void *_b) @@ -588,7 +733,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, int main(int argc, char **argv) { - int i; + int i, fix_thin_pack = 0; const char *curr_pack, *pack_name = NULL; const char *curr_index, *index_name = NULL; char *index_name_buf = NULL; @@ -600,6 +745,8 @@ int main(int argc, char **argv) if (*arg == '-') { if (!strcmp(arg, "--stdin")) { from_stdin = 1; + } else if (!strcmp(arg, "--fix-thin")) { + fix_thin_pack = 1; } else if (!strcmp(arg, "-o")) { if (index_name || (i+1) >= argc) usage(index_pack_usage); @@ -616,6 +763,8 @@ int main(int argc, char **argv) if (!pack_name && !from_stdin) usage(index_pack_usage); + if (fix_thin_pack && !from_stdin) + die("--fix-thin cannot be used without --stdin"); if (!index_name && pack_name) { int len = strlen(pack_name); if (!has_extension(pack_name, ".pack")) @@ -629,9 +778,28 @@ int main(int argc, char **argv) curr_pack = open_pack_file(pack_name); parse_pack_header(); - objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); - deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); + objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry)); + deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); parse_pack_objects(sha1); + if (nr_deltas != nr_resolved_deltas) { + if (fix_thin_pack) { + int nr_unresolved = nr_deltas - nr_resolved_deltas; + if (nr_unresolved <= 0) + die("confusion beyond insanity"); + objects = xrealloc(objects, + (nr_objects + nr_unresolved + 1) + * sizeof(*objects)); + fix_unresolved_deltas(nr_unresolved); + readjust_pack_header_and_sha1(sha1); + } + if (nr_deltas != nr_resolved_deltas) + die("pack has %d unresolved deltas", + nr_deltas - nr_resolved_deltas); + } else { + /* Flush remaining pack final 20-byte SHA1. */ + use(20); + flush(); + } free(deltas); curr_index = write_index_file(index_name, sha1); final(pack_name, curr_pack, index_name, curr_index, sha1); -- cgit v0.10.2-6-g49f6 From 3c9af366469524920864bbc1af4dcfd72029c314 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 25 Oct 2006 23:32:59 -0400 Subject: add progress status to index-pack This is more interesting to look at when performing a big fetch. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index c58287d..9fa4847 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -8,8 +8,8 @@ git-index-pack - Build pack index file for an existing packed archive SYNOPSIS -------- -'git-index-pack' [-o ] -'git-index-pack' --stdin [--fix-thin] [-o ] [] +'git-index-pack' [-v] [-o ] +'git-index-pack' --stdin [--fix-thin] [-v] [-o ] [] DESCRIPTION @@ -22,6 +22,9 @@ objects/pack/ directory of a git repository. OPTIONS ------- +-v:: + Be verbose about what is going on, including progress status. + -o :: Write the generated pack index into the specified file. Without this option the name of pack index diff --git a/index-pack.c b/index-pack.c index 9086bbf..2046b37 100644 --- a/index-pack.c +++ b/index-pack.c @@ -6,9 +6,11 @@ #include "commit.h" #include "tag.h" #include "tree.h" +#include +#include static const char index_pack_usage[] = -"git-index-pack [-o ] { | --stdin [--fix-thin] [] }"; +"git-index-pack [-v] [-o ] { | --stdin [--fix-thin] [] }"; struct object_entry { @@ -44,6 +46,42 @@ static int nr_deltas; static int nr_resolved_deltas; static int from_stdin; +static int verbose; + +static volatile sig_atomic_t progress_update; + +static void progress_interval(int signum) +{ + progress_update = 1; +} + +static void setup_progress_signal(void) +{ + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); + +} + +static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc) +{ + unsigned percent = n * 100 / total; + if (percent != last_pc || progress_update) { + fprintf(stderr, "%4u%% (%u/%u) done\r", percent, n, total); + progress_update = 0; + } + return percent; +} /* We always read in 4kB chunks. */ static unsigned char input_buffer[4096]; @@ -135,7 +173,6 @@ static void parse_pack_header(void) nr_objects = ntohl(hdr->hdr_entries); use(sizeof(struct pack_header)); - /*fprintf(stderr, "Indexing %d objects\n", nr_objects);*/ } static void bad_object(unsigned long offset, const char *format, @@ -383,7 +420,7 @@ static int compare_delta_entry(const void *a, const void *b) /* Parse all objects and return the pack content SHA1 hash */ static void parse_pack_objects(unsigned char *sha1) { - int i; + int i, percent = -1; struct delta_entry *delta = deltas; void *data; struct stat st; @@ -394,6 +431,8 @@ static void parse_pack_objects(unsigned char *sha1) * - calculate SHA1 of all non-delta objects; * - remember base SHA1 for all deltas. */ + if (verbose) + fprintf(stderr, "Indexing %d objects.\n", nr_objects); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; data = unpack_raw_entry(obj, &delta->base); @@ -405,8 +444,12 @@ static void parse_pack_objects(unsigned char *sha1) } else sha1_object(data, obj->size, obj->type, obj->sha1); free(data); + if (verbose) + percent = display_progress(i+1, nr_objects, percent); } objects[i].offset = consumed_bytes; + if (verbose) + fputc('\n', stderr); /* Check pack integrity */ flush(); @@ -420,6 +463,9 @@ static void parse_pack_objects(unsigned char *sha1) if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes + 20) die("pack has junk at the end"); + if (!nr_deltas) + return; + /* Sort deltas by base SHA1/offset for fast searching */ qsort(deltas, nr_deltas, sizeof(struct delta_entry), compare_delta_entry); @@ -432,6 +478,8 @@ static void parse_pack_objects(unsigned char *sha1) * recursively checking if the resulting object is used as a base * for some more deltas. */ + if (verbose) + fprintf(stderr, "Resolving %d deltas.\n", nr_deltas); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; union delta_base base; @@ -462,7 +510,12 @@ static void parse_pack_objects(unsigned char *sha1) obj->size, obj->type); } free(data); + if (verbose) + percent = display_progress(nr_resolved_deltas, + nr_deltas, percent); } + if (verbose && nr_resolved_deltas == nr_deltas) + fputc('\n', stderr); } static int write_compressed(int fd, void *in, unsigned int size) @@ -521,7 +574,7 @@ static int delta_pos_compare(const void *_a, const void *_b) static void fix_unresolved_deltas(int nr_unresolved) { struct delta_entry **sorted_by_pos; - int i, n = 0; + int i, n = 0, percent = -1; /* * Since many unresolved deltas may well be themselves base objects @@ -570,8 +623,13 @@ static void fix_unresolved_deltas(int nr_unresolved) append_obj_to_pack(data, size, obj_type); free(data); + if (verbose) + percent = display_progress(nr_resolved_deltas, + nr_deltas, percent); } free(sorted_by_pos); + if (verbose) + fputc('\n', stderr); } static void readjust_pack_header_and_sha1(unsigned char *sha1) @@ -747,6 +805,8 @@ int main(int argc, char **argv) from_stdin = 1; } else if (!strcmp(arg, "--fix-thin")) { fix_thin_pack = 1; + } else if (!strcmp(arg, "-v")) { + verbose = 1; } else if (!strcmp(arg, "-o")) { if (index_name || (i+1) >= argc) usage(index_pack_usage); @@ -780,16 +840,22 @@ int main(int argc, char **argv) parse_pack_header(); objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry)); deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); + if (verbose) + setup_progress_signal(); parse_pack_objects(sha1); if (nr_deltas != nr_resolved_deltas) { if (fix_thin_pack) { int nr_unresolved = nr_deltas - nr_resolved_deltas; + int nr_objects_initial = nr_objects; if (nr_unresolved <= 0) die("confusion beyond insanity"); objects = xrealloc(objects, (nr_objects + nr_unresolved + 1) * sizeof(*objects)); fix_unresolved_deltas(nr_unresolved); + if (verbose) + fprintf(stderr, "%d objects were added to complete this thin pack.\n", + nr_objects - nr_objects_initial); readjust_pack_header_and_sha1(sha1); } if (nr_deltas != nr_resolved_deltas) -- cgit v0.10.2-6-g49f6 From 0ac3056850394723c9b407754b44d3d37f1dcc3f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 26 Oct 2006 01:15:42 -0700 Subject: Documentation: clarify refname disambiguation rules. Nobody should create ambiguous refs (i.e. have tag "foobar" and branch "foobar" at the same time) that need to be disambiguated with these rules to keep sanity, but the rules are there so document them. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 5d42570..ed938aa 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -122,14 +122,30 @@ blobs contained in a commit. your repository whose object name starts with dae86e. * An output from `git-describe`; i.e. a closest tag, followed by a - dash, a 'g', and an abbreviated object name. + dash, a `g`, and an abbreviated object name. * A symbolic ref name. E.g. 'master' typically means the commit object referenced by $GIT_DIR/refs/heads/master. If you happen to have both heads/master and tags/master, you can explicitly say 'heads/master' to tell git which one you mean. + When ambiguous, a `` is disambiguated by taking the + first match in the following rules: -* A suffix '@' followed by a date specification enclosed in a brace + . if `$GIT_DIR/` exists, that is what you mean (this is usually + useful only for `HEAD`, `FETCH_HEAD` and `MERGE_HEAD`); + + . otherwise, `$GIT_DIR/refs/` if exists; + + . otherwise, `$GIT_DIR/refs/tags/` if exists; + + . otherwise, `$GIT_DIR/refs/heads/` if exists; + + . otherwise, `$GIT_DIR/refs/remotes/` if exists; + + . otherwise, `$GIT_DIR/refs/remotes//HEAD` if exists. + +* A ref followed by the suffix '@' with a date specification + enclosed in a brace pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1 second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value of the ref at a prior point in time. This suffix may only be @@ -146,8 +162,9 @@ blobs contained in a commit. * A suffix '{tilde}' to a revision parameter means the commit object that is the th generation grand-parent of the named commit object, following only the first parent. I.e. rev~3 is - equivalent to rev{caret}{caret}{caret} which is equivalent to\ - rev{caret}1{caret}1{caret}1. + equivalent to rev{caret}{caret}{caret} which is equivalent to + rev{caret}1{caret}1{caret}1. See below for a illustration of + the usage of this form. * A suffix '{caret}' followed by an object type name enclosed in brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object -- cgit v0.10.2-6-g49f6 From d5f6a01af0658bc0ec5f068d81ba321be94526d5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 26 Oct 2006 00:05:04 -0700 Subject: combine-diff: a few more finishing touches. "new file" and "deleted file" were already reported in the original code, but the logic was not as transparent as it could have. This uses a few variables and more comments to clarify the flow. The rule is: (1) if a path exists in the merge result when no parent had it, we report "new" (otherwise it came from the parents, as opposed to have added by the evil merge). (2) if the path does not exist in the merge result, it is "deleted". Since we can say "new" and "deleted", there is no reason not to follow the /dev/null convention. This fixes it. Appending function name after @@@ ... @@@ is trivial, so implement it. Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 174d63a..ed4ebcb 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -212,9 +212,9 @@ copying detection) are designed to work with diff of two --- a/file +++ b/file + -Contrary to two-line header for traditional 'unified' diff -format, and similar to filenames in ordinary "diff header", -/dev/null is not used for creation or deletion. +Similar to two-line header for traditional 'unified' diff +format, `/dev/null` is used to signal created or deleted +files. 4. Chunk header format is modified to prevent people from accidentally feeding it to `patch -p1`. Combined diff format diff --git a/combine-diff.c b/combine-diff.c index 46d9121..01a8437 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -489,6 +489,12 @@ static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long printf(" -%lu,%lu", l0, l1-l0); } +static int hunk_comment_line(const char *bol) +{ + int ch = *bol & 0xff; + return (isalpha(ch) || ch == '_' || ch == '$'); +} + static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, int use_color) { @@ -508,8 +514,13 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, struct sline *sl = &sline[lno]; unsigned long hunk_end; unsigned long rlines; - while (lno <= cnt && !(sline[lno].flag & mark)) + const char *hunk_comment = NULL; + + while (lno <= cnt && !(sline[lno].flag & mark)) { + if (hunk_comment_line(sline[lno].bol)) + hunk_comment = sline[lno].bol; lno++; + } if (cnt < lno) break; else { @@ -526,6 +537,22 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, show_parent_lno(sline, lno, hunk_end, i); printf(" +%lu,%lu ", lno+1, rlines); for (i = 0; i <= num_parent; i++) putchar(combine_marker); + + if (hunk_comment) { + int comment_end = 0; + for (i = 0; i < 40; i++) { + int ch = hunk_comment[i] & 0xff; + if (!ch || ch == '\n') + break; + if (!isspace(ch)) + comment_end = i; + } + if (comment_end) + putchar(' '); + for (i = 0; i < comment_end; i++) + putchar(hunk_comment[i]); + } + printf("%s\n", c_reset); while (lno < hunk_end) { struct lline *ll; @@ -707,6 +734,8 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, int use_color = opt->color_diff; const char *c_meta = diff_get_color(use_color, DIFF_METAINFO); const char *c_reset = diff_get_color(use_color, DIFF_RESET); + int added = 0; + int deleted = 0; if (rev->loginfo) show_log(rev, opt->msg_sep); @@ -722,7 +751,10 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, printf("..%s%s\n", abb, c_reset); if (mode_differs) { - int added = !!elem->mode; + deleted = !elem->mode; + + /* We say it was added if nobody had it */ + added = !deleted; for (i = 0; added && i < num_parent; i++) if (elem->parent[i].status != DIFF_STATUS_ADDED) @@ -731,7 +763,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, printf("%snew file mode %06o", c_meta, elem->mode); else { - if (!elem->mode) + if (deleted) printf("%sdeleted file ", c_meta); printf("mode "); for (i = 0; i < num_parent; i++) { @@ -743,8 +775,14 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, } printf("%s\n", c_reset); } - dump_quoted_path("--- a/", elem->path, c_meta, c_reset); - dump_quoted_path("+++ b/", elem->path, c_meta, c_reset); + if (added) + dump_quoted_path("--- /dev/", "null", c_meta, c_reset); + else + dump_quoted_path("--- a/", elem->path, c_meta, c_reset); + if (deleted) + dump_quoted_path("+++ /dev/", "null", c_meta, c_reset); + else + dump_quoted_path("+++ b/", elem->path, c_meta, c_reset); dump_sline(sline, cnt, num_parent, opt->color_diff); } free(result); -- cgit v0.10.2-6-g49f6 From 7a8ac59f2f1fdac71fdffe53eebdca382118585f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 26 Oct 2006 02:05:05 -0700 Subject: combine-diff: fix hunk_comment_line logic. We forgot that the last element of sline[] is a sentinel without the actual line. *BLUSH* Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index 01a8437..76ca651 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -491,7 +491,11 @@ static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long static int hunk_comment_line(const char *bol) { - int ch = *bol & 0xff; + int ch; + + if (!bol) + return 0; + ch = *bol & 0xff; return (isalpha(ch) || ch == '_' || ch == '$'); } -- cgit v0.10.2-6-g49f6 From 44152787bcb4fd003d1f5b8c2a7591445cee8f20 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 26 Oct 2006 02:05:59 -0700 Subject: combine-diff: honour --no-commit-id Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index 76ca651..8bf99f2 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -741,7 +741,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, int added = 0; int deleted = 0; - if (rev->loginfo) + if (rev->loginfo && !rev->no_commit_id) show_log(rev, opt->msg_sep); dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path, c_meta, c_reset); @@ -819,7 +819,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re if (!line_termination) inter_name_termination = 0; - if (rev->loginfo) + if (rev->loginfo && !rev->no_commit_id) show_log(rev, opt->msg_sep); if (opt->output_format & DIFF_FORMAT_RAW) { @@ -891,7 +891,7 @@ void diff_tree_combined(const unsigned char *sha1, diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; diffopts.recursive = 1; - show_log_first = !!rev->loginfo; + show_log_first = !!rev->loginfo && !rev->no_commit_id; needsep = 0; /* find set of paths that everybody touches */ for (i = 0; i < num_parent; i++) { -- cgit v0.10.2-6-g49f6 From 35f401a6706238b16906666b72e207e807e71f8a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 26 Oct 2006 10:50:20 +0200 Subject: gitweb: Use --no-commit-id in git_commit and git_commitdiff Use --no-commit-id option to git-diff-tree command in git_commit and git_commitdiff to filter out commit ID output that git-diff-tree adds when called with only one (not only for --stdin). Remove filtering commit IDs from git-diff-tree output. This option is in git since at least v1.0.0, so make use of it. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ba7a42a..5a81b8f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3133,14 +3133,12 @@ sub git_commit { if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $parent, $hash + open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", + @diff_opts, $parent, $hash or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed"); - # filter out commit ID output - @difftree = grep(!/^[0-9a-fA-F]{40}$/, @difftree); - # non-textual hash id's can be cached my $expires; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { @@ -3411,15 +3409,14 @@ sub git_commitdiff { my @difftree; if ($format eq 'html') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + "--no-commit-id", "--patch-with-raw", "--full-index", $hash_parent, $hash or die_error(undef, "Open git-diff-tree failed"); while (chomp(my $line = <$fd>)) { # empty line ends raw part of diff-tree output last unless $line; - # filter out commit ID output - push @difftree, $line - unless $line =~ m/^[0-9a-fA-F]{40}$/; + push @difftree, $line; } } elsif ($format eq 'plain') { -- cgit v0.10.2-6-g49f6 From 9bee24785133ba3c2361b17f8c20019ab57b6f72 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 25 Oct 2006 23:31:53 -0400 Subject: mimic unpack-objects when --stdin is used with index-pack It appears that git-unpack-objects writes the last part of the input buffer to stdout after the pack has been parsed. This looks a bit suspicious since the last fill() might have filled the buffer up to the 4096 byte limit and more data might still be pending on stdin, but since this is about being a drop-in replacement for unpack-objects let's simply duplicate the same behavior for now. [jc: with fix-up appeared in Nico's sleep] Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/index-pack.c b/index-pack.c index 2046b37..5c747a6 100644 --- a/index-pack.c +++ b/index-pack.c @@ -456,11 +456,12 @@ static void parse_pack_objects(unsigned char *sha1) SHA1_Final(sha1, &input_ctx); if (hashcmp(fill(20), sha1)) die("pack is corrupted (SHA1 mismatch)"); + use(20); /* If input_fd is a file, we should have reached its end now. */ if (fstat(input_fd, &st)) die("cannot fstat packfile: %s", strerror(errno)); - if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes + 20) + if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes) die("pack has junk at the end"); if (!nr_deltas) @@ -765,6 +766,18 @@ static void final(const char *final_pack_name, const char *curr_pack_name, if (err) die("error while closing pack file: %s", strerror(errno)); chmod(curr_pack_name, 0444); + + /* + * Let's just mimic git-unpack-objects here and write + * the last part of the buffer to stdout. + */ + while (input_len) { + err = xwrite(1, input_buffer + input_offset, input_len); + if (err <= 0) + break; + input_len -= err; + input_offset += err; + } } if (final_pack_name != curr_pack_name) { @@ -863,7 +876,6 @@ int main(int argc, char **argv) nr_deltas - nr_resolved_deltas); } else { /* Flush remaining pack final 20-byte SHA1. */ - use(20); flush(); } free(deltas); @@ -872,7 +884,8 @@ int main(int argc, char **argv) free(objects); free(index_name_buf); - printf("%s\n", sha1_to_hex(sha1)); + if (!from_stdin) + printf("%s\n", sha1_to_hex(sha1)); return 0; } -- cgit v0.10.2-6-g49f6 From 543551e53a8f8e52e72571e1f24f81d7f6598f9b Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Thu, 26 Oct 2006 15:17:24 +0100 Subject: Make filenames line up in git-status output When all the filenames line up it's much easier to copy and paste them somewhere else, or to remove the "modified:", "copied:", etc prefix. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/wt-status.c b/wt-status.c index 4b74e68..3952809 100644 --- a/wt-status.c +++ b/wt-status.c @@ -72,25 +72,25 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p) color_printf(color(WT_STATUS_HEADER), "#\t"); switch (p->status) { case DIFF_STATUS_ADDED: - color_printf(c, "new file: %s", p->one->path); break; + color_printf(c, "new file: %s", p->one->path); break; case DIFF_STATUS_COPIED: - color_printf(c, "copied: %s -> %s", + color_printf(c, "copied: %s -> %s", p->one->path, p->two->path); break; case DIFF_STATUS_DELETED: - color_printf(c, "deleted: %s", p->one->path); break; + color_printf(c, "deleted: %s", p->one->path); break; case DIFF_STATUS_MODIFIED: - color_printf(c, "modified: %s", p->one->path); break; + color_printf(c, "modified: %s", p->one->path); break; case DIFF_STATUS_RENAMED: - color_printf(c, "renamed: %s -> %s", + color_printf(c, "renamed: %s -> %s", p->one->path, p->two->path); break; case DIFF_STATUS_TYPE_CHANGED: color_printf(c, "typechange: %s", p->one->path); break; case DIFF_STATUS_UNKNOWN: - color_printf(c, "unknown: %s", p->one->path); break; + color_printf(c, "unknown: %s", p->one->path); break; case DIFF_STATUS_UNMERGED: - color_printf(c, "unmerged: %s", p->one->path); break; + color_printf(c, "unmerged: %s", p->one->path); break; default: die("bug: unhandled diff status %c", p->status); } -- cgit v0.10.2-6-g49f6 From 2b60356da5369dd60ab26eabaa91d95b6badf209 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 26 Oct 2006 18:52:39 +0200 Subject: Make git-cherry handle root trees This patch on top of 'next' makes built-in git-cherry handle root commits. It moves the static function log-tree.c::diff_root_tree() to tree-diff.c and makes it more similar to diff_tree_sha1() by shuffling around arguments and factoring out the call to log_tree_diff_flush(). Consequently the name is changed to diff_root_tree_sha1(). It is a version of diff_tree_sha1() that compares the empty tree (= root tree) against a single 'real' tree. This function is then used in get_patch_id() to compute patch IDs for initial commits instead of SEGFAULTing, as the current code does if confronted with parentless commits. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/builtin-log.c b/builtin-log.c index fc5e476..fedb013 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -171,8 +171,11 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject) static int get_patch_id(struct commit *commit, struct diff_options *options, unsigned char *sha1) { - diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1, - "", options); + if (commit->parents) + diff_tree_sha1(commit->parents->item->object.sha1, + commit->object.sha1, "", options); + else + diff_root_tree_sha1(commit->object.sha1, "", options); diffcore_std(options); return diff_flush_patch_id(options, sha1); } diff --git a/diff.h b/diff.h index ce3058e..ac7b21c 100644 --- a/diff.h +++ b/diff.h @@ -102,6 +102,8 @@ extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt); extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt); +extern int diff_root_tree_sha1(const unsigned char *new, const char *base, + struct diff_options *opt); struct combine_diff_path { struct combine_diff_path *next; diff --git a/log-tree.c b/log-tree.c index fbe1399..8787df5 100644 --- a/log-tree.c +++ b/log-tree.c @@ -252,26 +252,6 @@ int log_tree_diff_flush(struct rev_info *opt) return 1; } -static int diff_root_tree(struct rev_info *opt, - const unsigned char *new, const char *base) -{ - int retval; - void *tree; - struct tree_desc empty, real; - - tree = read_object_with_reference(new, tree_type, &real.size, NULL); - if (!tree) - die("unable to read root tree (%s)", sha1_to_hex(new)); - real.buf = tree; - - empty.buf = ""; - empty.size = 0; - retval = diff_tree(&empty, &real, base, &opt->diffopt); - free(tree); - log_tree_diff_flush(opt); - return retval; -} - static int do_diff_combined(struct rev_info *opt, struct commit *commit) { unsigned const char *sha1 = commit->object.sha1; @@ -297,8 +277,10 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log /* Root commit? */ parents = commit->parents; if (!parents) { - if (opt->show_root_diff) - diff_root_tree(opt, sha1, ""); + if (opt->show_root_diff) { + diff_root_tree_sha1(sha1, "", &opt->diffopt); + log_tree_diff_flush(opt); + } return !opt->loginfo; } diff --git a/tree-diff.c b/tree-diff.c index 7e2f4f0..37d235e 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -215,6 +215,24 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha return retval; } +int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_options *opt) +{ + int retval; + void *tree; + struct tree_desc empty, real; + + tree = read_object_with_reference(new, tree_type, &real.size, NULL); + if (!tree) + die("unable to read root tree (%s)", sha1_to_hex(new)); + real.buf = tree; + + empty.size = 0; + empty.buf = ""; + retval = diff_tree(&empty, &real, base, opt); + free(tree); + return retval; +} + static int count_paths(const char **paths) { int i = 0; -- cgit v0.10.2-6-g49f6 From fb8e23fae2ac9af831260079b14eabad316f3769 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 26 Oct 2006 22:21:02 +0200 Subject: diff-format.txt: Correct information about pathnames quoting in patch format Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 174d63a..2060ae2 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -144,8 +144,10 @@ the file that rename/copy produces, respectively. dissimilarity index index .. -3. TAB, LF, and backslash characters in pathnames are - represented as `\t`, `\n`, and `\\`, respectively. +3. TAB, LF, double quote and backslash characters in pathnames + are represented as `\t`, `\n`, `\"` and `\\`, respectively. + If there is need for such substitution then the whole + pathname is put in double quotes. combined diff format -- cgit v0.10.2-6-g49f6 From 97f7a7bd0d9d618e503623ded5b22eafada9e174 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Thu, 26 Oct 2006 21:39:05 +0200 Subject: Fix show-ref usagestring This describes the abbreviation possibilities for git-show-ref Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano diff --git a/builtin-show-ref.c b/builtin-show-ref.c index f2912e8..06ec400 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -3,7 +3,7 @@ #include "object.h" #include "tag.h" -static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash] [--tags] [--heads] [--] [pattern*]"; +static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=]] [--abbrev[=]] [--tags] [--heads] [--] [pattern*]"; static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0; -- cgit v0.10.2-6-g49f6 From a8ebdb90f9e7017aa47c7824c91dfb0dd231ac85 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Thu, 26 Oct 2006 23:32:41 +0200 Subject: git-cherry: document limit and add diagram This patch adds the diagram from the long usage string of git-cherry to its documentation, and documents the third option. I changed some of the + to - in order to save the reader from wondering where they might fit into the picture. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt index e1bf8ee..27b67b8 100644 --- a/Documentation/git-cherry.txt +++ b/Documentation/git-cherry.txt @@ -7,7 +7,7 @@ git-cherry - Find commits not merged upstream SYNOPSIS -------- -'git-cherry' [-v] [] +'git-cherry' [-v] [] [] DESCRIPTION ----------- @@ -18,7 +18,22 @@ Every commit that doesn't exist in the branch has its id (sha1) reported, prefixed by a symbol. The ones that have equivalent change already in the branch are prefixed with a minus (-) sign, and those -that only exist in the branch are prefixed with a plus (+) symbol. +that only exist in the branch are prefixed with a plus (+) symbol: + + __*__*__*__*__> + / + fork-point + \__+__+__-__+__+__-__+__> + + +If a has been given then the commits along the branch up +to and including are not reported: + + __*__*__*__*__> + / + fork-point + \__*__*____-__+__> + Because git-cherry compares the changeset rather than the commit id (sha1), you can use git-cherry to find out if a commit you made locally -- cgit v0.10.2-6-g49f6 From e2b1d1ccdd2ef4db41819bb0d242b808cfe6c422 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 27 Oct 2006 06:59:18 +0200 Subject: Documentation: add upload-archive service to git-daemon. This patch minimaly documents the upload-archive service, hoping that someone with better knowledge will improve upon. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 4b2ea2d..f10a269 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -37,6 +37,8 @@ from `git-fetch`, `git-ls-remote`, and `git-clone`. This is ideally suited for read-only updates, i.e., pulling from git repositories. +An `upload-archive` also exists to serve `git-archive`. + OPTIONS ------- --strict-paths:: @@ -155,6 +157,9 @@ upload-pack:: disable it by setting `daemon.uploadpack` configuration item to `false`. +upload-archive:: + This serves `git-archive --remote`. + EXAMPLES -------- git-daemon as inetd server:: -- cgit v0.10.2-6-g49f6 From f8a5da6d94baa0a90e7b383932911661bb551e1c Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 27 Oct 2006 07:00:57 +0200 Subject: Documentation: add git in /etc/services. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index 278161f..9677671 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -353,6 +353,13 @@ example of managing a shared central repository. Examples ~~~~~~~~ +We assume the following in /etc/services:: ++ +------------ +$ grep 9418 /etc/services +git 9418/tcp # Git Version Control System +------------ + Run git-daemon to serve /pub/scm from inetd.:: + ------------ diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index f10a269..993adc7 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -162,6 +162,13 @@ upload-archive:: EXAMPLES -------- +We assume the following in /etc/services:: ++ +------------ +$ grep 9418 /etc/services +git 9418/tcp # Git Version Control System +------------ + git-daemon as inetd server:: To set up `git-daemon` as an inetd service that handles any repository under the whitelisted set of directories, /pub/foo -- cgit v0.10.2-6-g49f6 From d6b7e0b98f8d0f84e3b2614b33b52402fefb5735 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 26 Oct 2006 12:26:44 +0200 Subject: gitweb: Check git base URLs before generating URL from it Check if each of git base URLs in @git_base_url_list is true before appending "/$project" to it to generate project URL. This fixes the error that for default configuration for gitweb in Makefile, with GITWEB_BASE_URL empty (and "++GITWEB_BASE_URL++" being "" in gitweb.cgi), we had URL of "/$project" in the summary view. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2390603..05e7b12 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -69,7 +69,7 @@ our $strict_export = "++GITWEB_STRICT_EXPORT++"; # list of git base URLs used for URL to where fetch project from, # i.e. full URL is "$git_base_url/$project" -our @git_base_url_list = ("++GITWEB_BASE_URL++"); +our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++"); # default blob_plain mimetype and default charset for text/plain blob our $default_blob_plain_mimetype = 'text/plain'; -- cgit v0.10.2-6-g49f6 From 36889a5078767be8cc0189c10d235dda327c6a30 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Oct 2006 14:29:55 -0700 Subject: tests: merge-recursive is usable without Python Many tests still protected themselves with $no_python; there is no need to do so anymore. Signed-off-by: Junio C Hamano diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh index 360a670..8b19d3c 100755 --- a/t/t3401-rebase-partial.sh +++ b/t/t3401-rebase-partial.sh @@ -52,13 +52,10 @@ test_expect_success \ 'rebase topic branch against new master and check git-am did not get halted' \ 'git-rebase master && test ! -d .dotest' -if test -z "$no_python" -then - test_expect_success \ +test_expect_success \ 'rebase --merge topic branch that was partially merged upstream' \ 'git-checkout -f my-topic-branch-merge && git-rebase --merge master-merge && test ! -d .git/.dotest-merge' -fi test_done diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index d34c6cf..0779aaa 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -7,12 +7,6 @@ test_description='git rebase --merge test' . ./test-lib.sh -if test "$no_python"; then - echo "Skipping: no python => no recursive merge" - test_done - exit 0 -fi - T="A quick brown fox jumps over the lazy dog." for i in 1 2 3 4 5 6 7 8 9 10 diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index bb25315..977c498 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -10,12 +10,6 @@ test_description='git rebase --merge --skip tests' # we assume the default git-am -3 --skip strategy is tested independently # and always works :) -if test "$no_python"; then - echo "Skipping: no python => no recursive merge" - test_done - exit 0 -fi - test_expect_success setup ' echo hello > hello && git add hello && diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh index 8f7366d..499cafb 100755 --- a/t/t6021-merge-criss-cross.sh +++ b/t/t6021-merge-criss-cross.sh @@ -10,12 +10,6 @@ test_description='Test criss-cross merge' . ./test-lib.sh -if test "$no_python"; then - echo "Skipping: no python => no recursive merge" - test_done - exit 0 -fi - test_expect_success 'prepare repository' \ 'echo "1 2 diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index 5ac2564..497ef36 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -3,12 +3,6 @@ test_description='Merge-recursive merging renames' . ./test-lib.sh -if test "$no_python"; then - echo "Skipping: no python => no recursive merge" - test_done - exit 0 -fi - test_expect_success setup \ ' cat >A <<\EOF && -- cgit v0.10.2-6-g49f6 From d9c20ba13dfca737373ba466d2a718cafdc17f92 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 27 Oct 2006 15:42:17 -0400 Subject: enhance clone and fetch -k experience Now that index-pack can be streamed with a pack, it is probably a good idea to use it directly instead of creating a temporary file and running index-pack afterwards. This way index-pack can abort early whenever a corruption is encountered even if the pack has not been fully downloaded, it can display a progress percentage as it knows how much to expects, and it is a bit faster since the pack indexing is partially done as data is received. Using fetch -k doesn't need to disable thin pack generation on the remote end either. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/fetch-clone.c b/fetch-clone.c index 76b99af..96cdab4 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -3,97 +3,6 @@ #include "pkt-line.h" #include "sideband.h" #include -#include - -static int finish_pack(const char *pack_tmp_name, const char *me) -{ - int pipe_fd[2]; - pid_t pid; - char idx[PATH_MAX]; - char final[PATH_MAX]; - char hash[41]; - unsigned char sha1[20]; - char *cp; - int err = 0; - - if (pipe(pipe_fd) < 0) - die("%s: unable to set up pipe", me); - - strcpy(idx, pack_tmp_name); /* ".git/objects/pack-XXXXXX" */ - cp = strrchr(idx, '/'); - memcpy(cp, "/pidx", 5); - - pid = fork(); - if (pid < 0) - die("%s: unable to fork off git-index-pack", me); - if (!pid) { - close(0); - dup2(pipe_fd[1], 1); - close(pipe_fd[0]); - close(pipe_fd[1]); - execl_git_cmd("index-pack", "-o", idx, pack_tmp_name, NULL); - error("cannot exec git-index-pack <%s> <%s>", - idx, pack_tmp_name); - exit(1); - } - close(pipe_fd[1]); - if (read(pipe_fd[0], hash, 40) != 40) { - error("%s: unable to read from git-index-pack", me); - err = 1; - } - close(pipe_fd[0]); - - for (;;) { - int status, code; - - if (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) - continue; - error("waitpid failed (%s)", strerror(errno)); - goto error_die; - } - if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); - error("git-index-pack died of signal %d", sig); - goto error_die; - } - if (!WIFEXITED(status)) { - error("git-index-pack died of unnatural causes %d", - status); - goto error_die; - } - code = WEXITSTATUS(status); - if (code) { - error("git-index-pack died with error code %d", code); - goto error_die; - } - if (err) - goto error_die; - break; - } - hash[40] = 0; - if (get_sha1_hex(hash, sha1)) { - error("git-index-pack reported nonsense '%s'", hash); - goto error_die; - } - /* Now we have pack in pack_tmp_name[], and - * idx in idx[]; rename them to their final names. - */ - snprintf(final, sizeof(final), - "%s/pack/pack-%s.pack", get_object_directory(), hash); - move_temp_to_file(pack_tmp_name, final); - chmod(final, 0444); - snprintf(final, sizeof(final), - "%s/pack/pack-%s.idx", get_object_directory(), hash); - move_temp_to_file(idx, final); - chmod(final, 0444); - return 0; - - error_die: - unlink(idx); - unlink(pack_tmp_name); - exit(1); -} static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) { @@ -128,7 +37,7 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) return side_pid; } -int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband) +static int get_pack(int xd[2], const char *me, int sideband, const char **argv) { int status; pid_t pid, side_pid; @@ -142,135 +51,37 @@ int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband) dup2(fd[0], 0); close(fd[0]); close(fd[1]); - execl_git_cmd("unpack-objects", quiet ? "-q" : NULL, NULL); - die("git-unpack-objects exec failed"); + execv_git_cmd(argv); + die("%s exec failed", argv[0]); } close(fd[0]); close(fd[1]); while (waitpid(pid, &status, 0) < 0) { if (errno != EINTR) - die("waiting for git-unpack-objects: %s", - strerror(errno)); + die("waiting for %s: %s", argv[0], strerror(errno)); } if (WIFEXITED(status)) { int code = WEXITSTATUS(status); if (code) - die("git-unpack-objects died with error code %d", - code); + die("%s died with error code %d", argv[0], code); return 0; } if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); - die("git-unpack-objects died of signal %d", sig); + die("%s died of signal %d", argv[0], sig); } - die("git-unpack-objects died of unnatural causes %d", status); + die("%s died of unnatural causes %d", argv[0], status); } -/* - * We average out the download speed over this many "events", where - * an event is a minimum of about half a second. That way, we get - * a reasonably stable number. - */ -#define NR_AVERAGE (4) - -/* - * A "binary msec" is a power-of-two-msec, aka 1/1024th of a second. - * Keeping the time in that format means that "bytes / msecs" means - * the same as kB/s (modulo rounding). - * - * 1000512 is a magic number (usecs in a second, rounded up by half - * of 1024, to make "rounding" come out right ;) - */ -#define usec_to_binarymsec(x) ((int)(x) / (1000512 >> 10)) +int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband) +{ + const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL }; + return get_pack(xd, me, sideband, argv); +} int receive_keep_pack(int xd[2], const char *me, int quiet, int sideband) { - char tmpfile[PATH_MAX]; - int ofd, ifd, fd[2]; - unsigned long total; - static struct timeval prev_tv; - struct average { - unsigned long bytes; - unsigned long time; - } download[NR_AVERAGE] = { {0, 0}, }; - unsigned long avg_bytes, avg_time; - int idx = 0; - - setup_sideband(sideband, me, fd, xd); - - ifd = fd[0]; - snprintf(tmpfile, sizeof(tmpfile), - "%s/pack/tmp-XXXXXX", get_object_directory()); - ofd = mkstemp(tmpfile); - if (ofd < 0) - return error("unable to create temporary file %s", tmpfile); - - gettimeofday(&prev_tv, NULL); - total = 0; - avg_bytes = 0; - avg_time = 0; - while (1) { - char buf[8192]; - ssize_t sz, wsz, pos; - sz = read(ifd, buf, sizeof(buf)); - if (sz == 0) - break; - if (sz < 0) { - if (errno != EINTR && errno != EAGAIN) { - error("error reading pack (%s)", strerror(errno)); - close(ofd); - unlink(tmpfile); - return -1; - } - sz = 0; - } - pos = 0; - while (pos < sz) { - wsz = write(ofd, buf + pos, sz - pos); - if (wsz < 0) { - error("error writing pack (%s)", - strerror(errno)); - close(ofd); - unlink(tmpfile); - return -1; - } - pos += wsz; - } - total += sz; - if (!quiet) { - static unsigned long last; - struct timeval tv; - unsigned long diff = total - last; - /* not really "msecs", but a power-of-two millisec (1/1024th of a sec) */ - unsigned long msecs; - - gettimeofday(&tv, NULL); - msecs = tv.tv_sec - prev_tv.tv_sec; - msecs <<= 10; - msecs += usec_to_binarymsec(tv.tv_usec - prev_tv.tv_usec); - - if (msecs > 500) { - prev_tv = tv; - last = total; - - /* Update averages ..*/ - avg_bytes += diff; - avg_time += msecs; - avg_bytes -= download[idx].bytes; - avg_time -= download[idx].time; - download[idx].bytes = diff; - download[idx].time = msecs; - idx++; - if (idx >= NR_AVERAGE) - idx = 0; - - fprintf(stderr, "%4lu.%03luMB (%lu kB/s) \r", - total >> 20, - 1000*((total >> 10) & 1023)>>10, - avg_bytes / avg_time ); - } - } - } - close(ofd); - return finish_pack(tmpfile, me); + const char *argv[5] = { "index-pack", "--stdin", "--fix-thin", + quiet ? NULL : "-v", NULL }; + return get_pack(xd, me, sideband, argv); } diff --git a/fetch-pack.c b/fetch-pack.c index 474d545..8720ed4 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -518,8 +518,6 @@ int main(int argc, char **argv) } if (!dest) usage(fetch_pack_usage); - if (keep_pack) - use_thin_pack = 0; pid = git_connect(fd, dest, exec); if (pid < 0) return 1; -- cgit v0.10.2-6-g49f6 From b89c4e93cc1939493c2bb9b6c3f60eabaf653eff Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 27 Oct 2006 16:14:23 -0400 Subject: index-pack: minor fixes to comment and function name Use proper english. Be more exact in one comment. [jc: I threw in a bit of style clean-up as well] Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/index-pack.c b/index-pack.c index 5c747a6..866a054 100644 --- a/index-pack.c +++ b/index-pack.c @@ -105,7 +105,7 @@ static void flush() * Make sure at least "min" bytes are available in the buffer, and * return the pointer to the buffer. */ -static void * fill(int min) +static void *fill(int min) { if (min <= input_len) return input_buffer + input_offset; @@ -134,7 +134,7 @@ static void use(int bytes) consumed_bytes += bytes; } -static const char * open_pack_file(const char *pack_name) +static const char *open_pack_file(const char *pack_name) { if (from_stdin) { input_fd = 0; @@ -275,7 +275,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ return unpack_entry_data(obj->offset, obj->size); } -static void * get_data_from_pack(struct object_entry *obj) +static void *get_data_from_pack(struct object_entry *obj) { unsigned long from = obj[0].offset + obj[0].hdr_size; unsigned long len = obj[1].offset - from; @@ -324,8 +324,8 @@ static int find_delta(const union delta_base *base) return -first-1; } -static int find_delta_childs(const union delta_base *base, - int *first_index, int *last_index) +static int find_delta_children(const union delta_base *base, + int *first_index, int *last_index) { int first = find_delta(base); int last = first; @@ -389,7 +389,7 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data, nr_resolved_deltas++; hashcpy(delta_base.sha1, delta_obj->sha1); - if (!find_delta_childs(&delta_base, &first, &last)) { + if (!find_delta_children(&delta_base, &first, &last)) { for (j = first; j <= last; j++) { struct object_entry *child = objects + deltas[j].obj_no; if (child->real_type == OBJ_REF_DELTA) @@ -399,7 +399,7 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data, memset(&delta_base, 0, sizeof(delta_base)); delta_base.offset = delta_obj->offset; - if (!find_delta_childs(&delta_base, &first, &last)) { + if (!find_delta_children(&delta_base, &first, &last)) { for (j = first; j <= last; j++) { struct object_entry *child = objects + deltas[j].obj_no; if (child->real_type == OBJ_OFS_DELTA) @@ -429,7 +429,7 @@ static void parse_pack_objects(unsigned char *sha1) * First pass: * - find locations of all objects; * - calculate SHA1 of all non-delta objects; - * - remember base SHA1 for all deltas. + * - remember base (SHA1 or offset) for all deltas. */ if (verbose) fprintf(stderr, "Indexing %d objects.\n", nr_objects); @@ -489,10 +489,10 @@ static void parse_pack_objects(unsigned char *sha1) if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) continue; hashcpy(base.sha1, obj->sha1); - ref = !find_delta_childs(&base, &ref_first, &ref_last); + ref = !find_delta_children(&base, &ref_first, &ref_last); memset(&base, 0, sizeof(base)); base.offset = obj->offset; - ofs = !find_delta_childs(&base, &ofs_first, &ofs_last); + ofs = !find_delta_children(&base, &ofs_first, &ofs_last); if (!ref && !ofs) continue; data = get_data_from_pack(obj); @@ -615,7 +615,7 @@ static void fix_unresolved_deltas(int nr_unresolved) else die("base object %s is of type '%s'", sha1_to_hex(d->base.sha1), type); - find_delta_childs(&d->base, &first, &last); + find_delta_children(&d->base, &first, &last); for (j = first; j <= last; j++) { struct object_entry *child = objects + deltas[j].obj_no; if (child->real_type == OBJ_REF_DELTA) @@ -675,7 +675,7 @@ static int sha1_compare(const void *_a, const void *_b) * On entry *sha1 contains the pack content SHA1 hash, on exit it is * the SHA1 hash of sorted object names. */ -static const char * write_index_file(const char *index_name, unsigned char *sha1) +static const char *write_index_file(const char *index_name, unsigned char *sha1) { struct sha1file *f; struct object_entry **sorted_by_sha, **list, **last; -- cgit v0.10.2-6-g49f6 From 887a612fef942dd3e7dae452e2dc582738b0fb41 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 26 Oct 2006 14:41:25 +0200 Subject: gitweb: Fix up bogus $stylesheet declarations This seems to be a pre-++ residual declaration and it wasn't good for anything at all besides flooding the webserver errorlog with "omg, our in the same scope!!" warnings. [jc: the patch was bogus by defining the variable which defeated a later test that checked it with "defined", which I fixed up.] Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index aceaeb7..7b2499a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -51,12 +51,8 @@ our $site_footer = "++GITWEB_SITE_FOOTER++"; # URI of stylesheets our @stylesheets = ("++GITWEB_CSS++"); -our $stylesheet; -# default is not to define style sheet, but it can be overwritten later -undef $stylesheet; - -# URI of default stylesheet -our $stylesheet = "++GITWEB_CSS++"; +# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG. +our $stylesheet = undef; # URI of GIT logo (72x27 size) our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type -- cgit v0.10.2-6-g49f6 From ed93b449c5e13a70beb7f0ae02fb6da95e42a805 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 8 Oct 2006 22:48:06 -0700 Subject: merge: loosen overcautious "working file will be lost" check. The three-way merge complained unconditionally when a path that does not exist in the index is involved in a merge when it existed in the working tree. If we are merging an old version that had that path tracked, but the path is not tracked anymore, and if we are merging that old version in, the result will be that the path is not tracked. In that case we should not complain. Signed-off-by: Junio C Hamano diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index fba4b0c..c49e4c6 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -23,6 +23,12 @@ case "${1:-.}${2:-.}${3:-.}" in "$1.." | "$1.$1" | "$1$1.") if [ "$2" ]; then echo "Removing $4" + else + # read-tree checked that index matches HEAD already, + # so we know we do not have this path tracked. + # there may be an unrelated working tree file here, + # which we should just leave unmolested. + exit 0 fi if test -f "$4"; then rm -f -- "$4" && @@ -34,8 +40,16 @@ case "${1:-.}${2:-.}${3:-.}" in # # Added in one. # -".$2." | "..$3" ) +".$2.") + # the other side did not add and we added so there is nothing + # to be done. + ;; +"..$3") echo "Adding $4" + test -f "$4" || { + echo "ERROR: untracked $4 is overwritten by the merge." + exit 1 + } git-update-index --add --cacheinfo "$6$7" "$2$3" "$4" && exec git-checkout-index -u -f -- "$4" ;; diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh new file mode 100755 index 0000000..018fbea --- /dev/null +++ b/t/t1004-read-tree-m-u-wf.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +test_description='read-tree -m -u checks working tree files' + +. ./test-lib.sh + +# two-tree test + +test_expect_success 'two-way setup' ' + + echo >file1 file one && + echo >file2 file two && + git update-index --add file1 file2 && + git commit -m initial && + + git branch side && + git tag -f branch-point && + + echo file2 is not tracked on the master anymore && + rm -f file2 && + git update-index --remove file2 && + git commit -a -m "master removes file2" +' + +test_expect_success 'two-way not clobbering' ' + + echo >file2 master creates untracked file2 && + if err=`git read-tree -m -u master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +# three-tree test + +test_expect_success 'three-way not complaining' ' + + rm -f file2 && + git checkout side && + echo >file3 file three && + git update-index --add file3 && + git commit -a -m "side adds file3" && + + git checkout master && + echo >file2 file two is untracked on the master side && + + git-read-tree -m -u branch-point master side +' + +test_done diff --git a/unpack-trees.c b/unpack-trees.c index 3ac0289..7cfd628 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -642,7 +642,7 @@ int threeway_merge(struct cache_entry **stages, (remote_deleted && head && head_match)) { if (index) return deleted_entry(index, index, o); - else if (path) + else if (path && !head_deleted) verify_absent(path, "removed", o); return 0; } @@ -661,8 +661,6 @@ int threeway_merge(struct cache_entry **stages, if (index) { verify_uptodate(index, o); } - else if (path) - verify_absent(path, "overwritten", o); o->nontrivial_merge = 1; -- cgit v0.10.2-6-g49f6 From 9926ba98a4a77e012ea9121abee42608fe1564b6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 23 Oct 2006 00:36:22 -0700 Subject: merge-recursive: use abbreviated commit object name. Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 2ba43ae..ccfa905 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -98,7 +98,7 @@ static void output_commit_title(struct commit *commit) if (commit->util) printf("virtual %s\n", (char *)commit->util); else { - printf("%s ", sha1_to_hex(commit->object.sha1)); + printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); if (parse_commit(commit) != 0) printf("(bad commit)\n"); else { -- cgit v0.10.2-6-g49f6 From 9fe0d87da36ce4a61359a4d5c2ea2547e29b4d04 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 23 Oct 2006 00:46:15 -0700 Subject: merge-recursive: make a few functions static. Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index ccfa905..74074c5 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -427,8 +427,9 @@ static struct path_list *get_renames(struct tree *tree, return renames; } -int update_stages(const char *path, struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, int clear) +static int update_stages(const char *path, struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + int clear) { int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; if (clear) @@ -468,7 +469,7 @@ static int remove_path(const char *name) return ret; } -int remove_file(int clean, const char *path) +static int remove_file(int clean, const char *path) { int update_cache = index_only || clean; int update_working_directory = !index_only; @@ -537,11 +538,11 @@ static void flush_buffer(int fd, const char *buf, unsigned long size) } } -void update_file_flags(const unsigned char *sha, - unsigned mode, - const char *path, - int update_cache, - int update_wd) +static void update_file_flags(const unsigned char *sha, + unsigned mode, + const char *path, + int update_cache, + int update_wd) { if (index_only) update_wd = 0; @@ -586,10 +587,10 @@ void update_file_flags(const unsigned char *sha, add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); } -void update_file(int clean, - const unsigned char *sha, - unsigned mode, - const char *path) +static void update_file(int clean, + const unsigned char *sha, + unsigned mode, + const char *path) { update_file_flags(sha, mode, path, index_only || clean, !index_only); } -- cgit v0.10.2-6-g49f6 From 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Oct 2006 14:08:14 -0700 Subject: merge-recursive: adjust to loosened "working file clobbered" check The three-way merge by git-read-tree does not complain about presense of the file in the working tree that is involved in a merge when the merge result needs to be determined by the caller. Adjust merge-recursive so that it makes sure that an untracked file is not touched when the merge decides the path should not be included in the final result. Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 74074c5..cdc2f78 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -469,10 +469,10 @@ static int remove_path(const char *name) return ret; } -static int remove_file(int clean, const char *path) +static int remove_file(int clean, const char *path, int no_wd) { int update_cache = index_only || clean; - int update_working_directory = !index_only; + int update_working_directory = !index_only && !no_wd; if (update_cache) { if (!cache_dirty) @@ -481,8 +481,7 @@ static int remove_file(int clean, const char *path) if (remove_file_from_cache(path)) return -1; } - if (update_working_directory) - { + if (update_working_directory) { unlink(path); if (errno != ENOENT || errno != EISDIR) return -1; @@ -725,13 +724,13 @@ static void conflict_rename_rename(struct rename *ren1, dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); output("%s is a directory in %s adding as %s instead", ren1_dst, branch2, dst_name1); - remove_file(0, ren1_dst); + remove_file(0, ren1_dst, 0); } if (path_list_has_path(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); output("%s is a directory in %s adding as %s instead", ren2_dst, branch1, dst_name2); - remove_file(0, ren2_dst); + remove_file(0, ren2_dst, 0); } update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); @@ -744,7 +743,7 @@ static void conflict_rename_dir(struct rename *ren1, { char *new_path = unique_path(ren1->pair->two->path, branch1); output("Renaming %s to %s instead", ren1->pair->one->path, new_path); - remove_file(0, ren1->pair->two->path); + remove_file(0, ren1->pair->two->path, 0); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); free(new_path); } @@ -759,7 +758,7 @@ static void conflict_rename_rename_2(struct rename *ren1, output("Renaming %s to %s and %s to %s instead", ren1->pair->one->path, new_path1, ren2->pair->one->path, new_path2); - remove_file(0, ren1->pair->two->path); + remove_file(0, ren1->pair->two->path, 0); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); free(new_path2); @@ -857,7 +856,7 @@ static int process_renames(struct path_list *a_renames, conflict_rename_rename(ren1, branch1, ren2, branch2); } else { struct merge_file_info mfi; - remove_file(1, ren1_src); + remove_file(1, ren1_src, 1); mfi = merge_file(ren1->pair->one, ren1->pair->two, ren2->pair->two, @@ -890,7 +889,7 @@ static int process_renames(struct path_list *a_renames, struct diff_filespec src_other, dst_other; int try_merge, stage = a_renames == renames1 ? 3: 2; - remove_file(1, ren1_src); + remove_file(1, ren1_src, 1); hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; @@ -1008,7 +1007,8 @@ static int process_entry(const char *path, struct stage_data *entry, * unchanged in the other */ if (a_sha) output("Removing %s", path); - remove_file(1, path); + /* do not touch working file if it did not exist */ + remove_file(1, path, !a_sha); } else { /* Deleted in one and changed in the other */ clean_merge = 0; @@ -1055,7 +1055,7 @@ static int process_entry(const char *path, struct stage_data *entry, output("CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", conf, path, other_branch, path, new_path); - remove_file(0, path); + remove_file(0, path, 0); update_file(0, sha, mode, new_path); } else { output("Adding %s", path); @@ -1083,7 +1083,7 @@ static int process_entry(const char *path, struct stage_data *entry, output("CONFLICT (add/add): File %s added non-identically " "in both branches. Adding as %s and %s instead.", path, new_path1, new_path2); - remove_file(0, path); + remove_file(0, path, 0); update_file(0, a_sha, a_mode, new_path1); update_file(0, b_sha, b_mode, new_path2); } @@ -1205,14 +1205,13 @@ static struct commit_list *reverse_commit_list(struct commit_list *list) * Merge the commits h1 and h2, return the resulting virtual * commit object and a flag indicating the cleaness of the merge. */ -static -int merge(struct commit *h1, - struct commit *h2, - const char *branch1, - const char *branch2, - int call_depth /* =0 */, - struct commit *ancestor /* =None */, - struct commit **result) +static int merge(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + int call_depth /* =0 */, + struct commit *ancestor /* =None */, + struct commit **result) { struct commit_list *ca = NULL, *iter; struct commit *merged_common_ancestors; -- cgit v0.10.2-6-g49f6 From 5b329a5f5e3625cdc204e3d274c89646816f384c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Oct 2006 16:07:02 -0700 Subject: t6022: ignoring untracked files by merge-recursive when they do not matter Signed-off-by: Junio C Hamano diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index 497ef36..b608e20 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -42,15 +42,20 @@ O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO EOF git add A M && -git commit -m initial && +git commit -m "initial has A and M" && git branch white && git branch red && git branch blue && +git branch yellow && sed -e "/^g /s/.*/g : master changes a line/" A+ && mv A+ A && git commit -a -m "master updates A" && +git checkout yellow && +rm -f M && +git commit -a -m "yellow removes M" && + git checkout white && sed -e "/^g /s/.*/g : white changes a line/" B && sed -e "/^G /s/.*/G : colored branch changes a line/" N && @@ -79,27 +84,27 @@ test_expect_success 'pull renaming branch into unrenaming one' \ git show-branch git pull . white && { echo "BAD: should have conflicted" - exit 1 + return 1 } git ls-files -s test "$(git ls-files -u B | wc -l)" -eq 3 || { echo "BAD: should have left stages for B" - exit 1 + return 1 } test "$(git ls-files -s N | wc -l)" -eq 1 || { echo "BAD: should have merged N" - exit 1 + return 1 } sed -ne "/^g/{ p q }" B | grep master || { echo "BAD: should have listed our change first" - exit 1 + return 1 } test "$(git diff white N | wc -l)" -eq 0 || { echo "BAD: should have taken colored branch" - exit 1 + return 1 } ' @@ -110,26 +115,26 @@ test_expect_success 'pull renaming branch into another renaming one' \ git checkout red git pull . white && { echo "BAD: should have conflicted" - exit 1 + return 1 } test "$(git ls-files -u B | wc -l)" -eq 3 || { echo "BAD: should have left stages" - exit 1 + return 1 } test "$(git ls-files -s N | wc -l)" -eq 1 || { echo "BAD: should have merged N" - exit 1 + return 1 } sed -ne "/^g/{ p q }" B | grep red || { echo "BAD: should have listed our change first" - exit 1 + return 1 } test "$(git diff white N | wc -l)" -eq 0 || { echo "BAD: should have taken colored branch" - exit 1 + return 1 } ' @@ -139,26 +144,26 @@ test_expect_success 'pull unrenaming branch into renaming one' \ git show-branch git pull . master && { echo "BAD: should have conflicted" - exit 1 + return 1 } test "$(git ls-files -u B | wc -l)" -eq 3 || { echo "BAD: should have left stages" - exit 1 + return 1 } test "$(git ls-files -s N | wc -l)" -eq 1 || { echo "BAD: should have merged N" - exit 1 + return 1 } sed -ne "/^g/{ p q }" B | grep red || { echo "BAD: should have listed our change first" - exit 1 + return 1 } test "$(git diff white N | wc -l)" -eq 0 || { echo "BAD: should have taken colored branch" - exit 1 + return 1 } ' @@ -168,35 +173,149 @@ test_expect_success 'pull conflicting renames' \ git show-branch git pull . blue && { echo "BAD: should have conflicted" - exit 1 + return 1 } test "$(git ls-files -u A | wc -l)" -eq 1 || { echo "BAD: should have left a stage" - exit 1 + return 1 } test "$(git ls-files -u B | wc -l)" -eq 1 || { echo "BAD: should have left a stage" - exit 1 + return 1 } test "$(git ls-files -u C | wc -l)" -eq 1 || { echo "BAD: should have left a stage" - exit 1 + return 1 } test "$(git ls-files -s N | wc -l)" -eq 1 || { echo "BAD: should have merged N" - exit 1 + return 1 } sed -ne "/^g/{ p q }" B | grep red || { echo "BAD: should have listed our change first" - exit 1 + return 1 } test "$(git diff white N | wc -l)" -eq 0 || { echo "BAD: should have taken colored branch" - exit 1 + return 1 + } +' + +test_expect_success 'interference with untracked working tree file' ' + + git reset --hard + git show-branch + echo >A this file should not matter + git pull . white && { + echo "BAD: should have conflicted" + return 1 + } + test -f A || { + echo "BAD: should have left A intact" + return 1 + } +' + +test_expect_success 'interference with untracked working tree file' ' + + git reset --hard + git checkout white + git show-branch + rm -f A + echo >A this file should not matter + git pull . red && { + echo "BAD: should have conflicted" + return 1 + } + test -f A || { + echo "BAD: should have left A intact" + return 1 + } +' + +test_expect_success 'interference with untracked working tree file' ' + + git reset --hard + rm -f A M + git checkout -f master + git tag -f anchor + git show-branch + git pull . yellow || { + echo "BAD: should have cleanly merged" + return 1 + } + test -f M && { + echo "BAD: should have removed M" + return 1 + } + git reset --hard anchor +' + +test_expect_success 'updated working tree file should prevent the merge' ' + + git reset --hard + rm -f A M + git checkout -f master + git tag -f anchor + git show-branch + echo >>M one line addition + cat M >M.saved + git pull . yellow && { + echo "BAD: should have complained" + return 1 + } + diff M M.saved || { + echo "BAD: should have left M intact" + return 1 + } + rm -f M.saved +' + +test_expect_success 'updated working tree file should prevent the merge' ' + + git reset --hard + rm -f A M + git checkout -f master + git tag -f anchor + git show-branch + echo >>M one line addition + cat M >M.saved + git update-index M + git pull . yellow && { + echo "BAD: should have complained" + return 1 + } + diff M M.saved || { + echo "BAD: should have left M intact" + return 1 + } + rm -f M.saved +' + +test_expect_success 'interference with untracked working tree file' ' + + git reset --hard + rm -f A M + git checkout -f yellow + git tag -f anchor + git show-branch + echo >M this file should not matter + git pull . master || { + echo "BAD: should have cleanly merged" + return 1 + } + test -f M || { + echo "BAD: should have left M intact" + return 1 + } + git ls-files -s | grep M && { + echo "BAD: M must be untracked in the result" + return 1 } + git reset --hard anchor ' test_done -- cgit v0.10.2-6-g49f6 From 367dce2a5b80da055a0d776fe830e6f19b81bd1d Mon Sep 17 00:00:00 2001 From: Dennis Stosberg Date: Sat, 28 Oct 2006 14:12:20 +0200 Subject: Bash completion support for aliases - Add aliases to the list of available git commands. - Make completion work for aliased commands. Signed-off-by: Dennis Stosberg Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d9cb17d..b074f4f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -101,6 +101,23 @@ __git_complete_file () esac } +__git_aliases () +{ + git repo-config --list | grep '^alias\.' \ + | sed -e 's/^alias\.//' -e 's/=.*$//' +} + +__git_aliased_command () +{ + local cmdline=$(git repo-config alias.$1) + for word in $cmdline; do + if [ "${word##-*}" ]; then + echo $word + return + fi + done +} + _git_branch () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -264,10 +281,18 @@ _git () { if [ $COMP_CWORD = 1 ]; then COMPREPLY=($(compgen \ - -W "--version $(git help -a|egrep '^ ')" \ + -W "--version $(git help -a|egrep '^ ') \ + $(__git_aliases)" \ -- "${COMP_WORDS[COMP_CWORD]}")) else - case "${COMP_WORDS[1]}" in + local command="${COMP_WORDS[1]}" + local expansion=$(__git_aliased_command "$command") + + if [ "$expansion" ]; then + command="$expansion" + fi + + case "$command" in branch) _git_branch ;; cat-file) _git_cat_file ;; checkout) _git_checkout ;; -- cgit v0.10.2-6-g49f6 From ba7545adab83e6bea43353937eabb467bcb7d4f7 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 28 Oct 2006 19:30:05 +0200 Subject: Documentation: Update information about in git-for-each-ref Update information about value of used when it is left unspecified. Add information about `%%` and `%xx` interpolation (URL encoding). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index d5fdcef..4af1ebf 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -38,7 +38,11 @@ OPTIONS is prefixed with an asterisk (`*`) and the ref points at a tag object, the value for the field in the object tag refers is used. When unspecified, defaults to - `%(refname)`. + `%(objectname) SPC %(objecttype) TAB %(refname)`. + It also interpolates `%%` to `%`, and `%xx` where `xx` + are hex digits interpolates to character with hex code + `xx`; for example `%00` interpolates to `\0` (NUL), + `%09` to `\t` (TAB) and `%0a` to `\n` (LF). :: If given, the name of the ref is matched against this -- cgit v0.10.2-6-g49f6 From 1dc5e55f2d2cab9ca24dc435dc2bc4f466f15ade Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 28 Oct 2006 14:25:41 -0700 Subject: Documentation: fix git-format-patch mark-up and link it from git.txt Two asterisks the SYNOPSIS section were mistaken as emphasis, and the latter backtick in "``s" were not recognized as closing backtick. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 4af1ebf..2bf6aef 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -7,14 +7,14 @@ git-for-each-ref - Output information on each ref SYNOPSIS -------- -'git-for-each-ref' [--count=]* [--shell|--perl|--python] [--sort=]* [--format=] [] +'git-for-each-ref' [--count=]\* [--shell|--perl|--python] [--sort=]\* [--format=] [] DESCRIPTION ----------- Iterate over all refs that match `` and show them according to the given ``, after sorting them according -to the given set of ``s. If `` is given, stop after +to the given set of ``. If `` is given, stop after showing that many refs. The interporated values in `` can optionally be quoted as string literals in the specified host language allowing their direct evaluation in that language. diff --git a/Documentation/git.txt b/Documentation/git.txt index b00607e..595cca1 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -160,6 +160,9 @@ gitlink:git-diff-stages[1]:: gitlink:git-diff-tree[1]:: Compares the content and mode of blobs found via two tree objects. +gitlink:git-for-each-ref[1]:: + Output information on each ref. + gitlink:git-fsck-objects[1]:: Verifies the connectivity and validity of the objects in the database. -- cgit v0.10.2-6-g49f6 From c60c56cc70dbf2acb4d15e3ea4f95d09020acc82 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 28 Oct 2006 19:43:40 +0200 Subject: gitweb: Move git_get_last_activity subroutine earlier This is purely cosmetic. Having git_get_* between two parse_* subroutines violated a good convention to group related things together. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7b2499a..6714e41 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -976,6 +976,24 @@ sub git_get_project_owner { return $owner; } +sub git_get_last_activity { + my ($path) = @_; + my $fd; + + $git_dir = "$projectroot/$path"; + open($fd, "-|", git_cmd(), 'for-each-ref', + '--format=%(refname) %(committer)', + '--sort=-committerdate', + 'refs/heads') or return; + my $most_recent = <$fd>; + close $fd or return; + if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) { + my $timestamp = $1; + my $age = time - $timestamp; + return ($age, age_string($age)); + } +} + sub git_get_references { my $type = shift || ""; my %refs; @@ -1082,24 +1100,6 @@ sub parse_tag { return %tag } -sub git_get_last_activity { - my ($path) = @_; - my $fd; - - $git_dir = "$projectroot/$path"; - open($fd, "-|", git_cmd(), 'for-each-ref', - '--format=%(refname) %(committer)', - '--sort=-committerdate', - 'refs/heads') or return; - my $most_recent = <$fd>; - close $fd or return; - if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) { - my $timestamp = $1; - my $age = time - $timestamp; - return ($age, age_string($age)); - } -} - sub parse_commit { my $commit_id = shift; my $commit_text = shift; -- cgit v0.10.2-6-g49f6 From 151602df00b8e5c5b4a8193f59a94b85f9b5aebc Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 23 Oct 2006 00:37:56 +0200 Subject: gitweb: Add "next" link to commitdiff view Add a kind of "next" view in the bottom part of navigation bar for "commitdiff" view. For commitdiff between two commits: (from: _commit_) For commitdiff for one single parent commit: (parent: _commit_) For commitdiff for one merge commit (merge: _commit_ _commit_ ...) For commitdiff for root (parentless) commit (initial) where _link_ denotes hyperlink. SHA1 is shortened to 7 characters on display, everything is perhaps unnecessary esc_html on display. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6714e41..ec46b80 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3396,6 +3396,51 @@ sub git_commitdiff { if (!%co) { die_error(undef, "Unknown commit object"); } + + # we need to prepare $formats_nav before any parameter munging + my $formats_nav; + if ($format eq 'html') { + $formats_nav = + $cgi->a({-href => href(action=>"commitdiff_plain", + hash=>$hash, hash_parent=>$hash_parent)}, + "raw"); + + if (defined $hash_parent) { + # commitdiff with two commits given + my $hash_parent_short = $hash_parent; + if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) { + $hash_parent_short = substr($hash_parent, 0, 7); + } + $formats_nav .= + ' (from: ' . + $cgi->a({-href => href(action=>"commitdiff", + hash=>$hash_parent)}, + esc_html($hash_parent_short)) . + ')'; + } elsif (!$co{'parent'}) { + # --root commitdiff + $formats_nav .= ' (initial)'; + } elsif (scalar @{$co{'parents'}} == 1) { + # single parent commit + $formats_nav .= + ' (parent: ' . + $cgi->a({-href => href(action=>"commitdiff", + hash=>$co{'parent'})}, + esc_html(substr($co{'parent'}, 0, 7))) . + ')'; + } else { + # merge commit + $formats_nav .= + ' (merge: ' . + join(' ', map { + $cgi->a({-href => href(action=>"commitdiff", + hash=>$_)}, + esc_html(substr($_, 0, 7))); + } @{$co{'parents'}} ) . + ')'; + } + } + if (!defined $hash_parent) { $hash_parent = $co{'parent'} || '--root'; } @@ -3434,10 +3479,6 @@ sub git_commitdiff { if ($format eq 'html') { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); - my $formats_nav = - $cgi->a({-href => href(action=>"commitdiff_plain", - hash=>$hash, hash_parent=>$hash_parent)}, - "raw"); git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); -- cgit v0.10.2-6-g49f6 From c7740a943ec896247ebc5514b6be02710caf3c53 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 29 Oct 2006 00:47:56 -0700 Subject: send-pack --keep: do not explode into loose objects on the receiving end. This adds "keep-pack" extension to send-pack vs receive pack protocol, and makes the receiver invoke "index-pack --stdin --fix-thin". With this, you can ask send-pack not to explode the result into loose objects on the receiving end. I've patched has_sha1_file() to re-check for added packs just like is done in read_sha1_file() for now, but I think the static "re-prepare" interface for packs was a mistake. Creation of a new pack inside a process that needs to read objects in them back ought to be a rare event, so we are better off making the callers (such as receive-pack that calls "index-pack --stdin --fix-thin") explicitly call re-prepare. That way we do not have to penalize ordinary users of read_sha1_file() and has_sha1_file(). We would need to fix this someday. Signed-off-by: Junio C Hamano diff --git a/receive-pack.c b/receive-pack.c index ea2dbd4..ef50226 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -8,10 +8,14 @@ static const char receive_pack_usage[] = "git-receive-pack "; static const char *unpacker[] = { "unpack-objects", NULL }; +static const char *keep_packer[] = { + "index-pack", "--stdin", "--fix-thin", NULL +}; static int report_status; +static int keep_pack; -static char capabilities[] = "report-status"; +static char capabilities[] = "report-status keep-pack"; static int capabilities_sent; static int show_ref(const char *path, const unsigned char *sha1) @@ -261,6 +265,8 @@ static void read_head_info(void) if (reflen + 82 < len) { if (strstr(refname + reflen + 1, "report-status")) report_status = 1; + if (strstr(refname + reflen + 1, "keep-pack")) + keep_pack = 1; } cmd = xmalloc(sizeof(struct command) + len - 80); hashcpy(cmd->old_sha1, old_sha1); @@ -275,7 +281,14 @@ static void read_head_info(void) static const char *unpack(int *error_code) { - int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD); + int code; + + if (keep_pack) + code = run_command_v_opt(ARRAY_SIZE(keep_packer) - 1, + keep_packer, RUN_GIT_CMD); + else + code = run_command_v_opt(ARRAY_SIZE(unpacker) - 1, + unpacker, RUN_GIT_CMD); *error_code = 0; switch (code) { @@ -335,7 +348,7 @@ int main(int argc, char **argv) if (!dir) usage(receive_pack_usage); - if(!enter_repo(dir, 0)) + if (!enter_repo(dir, 0)) die("'%s': unable to chdir or not a git archive", dir); write_head_info(); diff --git a/send-pack.c b/send-pack.c index 5bb123a..54d218c 100644 --- a/send-pack.c +++ b/send-pack.c @@ -6,13 +6,14 @@ #include "exec_cmd.h" static const char send_pack_usage[] = -"git-send-pack [--all] [--exec=git-receive-pack] [...]\n" +"git-send-pack [--all] [--keep] [--exec=git-receive-pack] [...]\n" " --all and explicit specification are mutually exclusive."; static const char *exec = "git-receive-pack"; static int verbose; static int send_all; static int force_update; static int use_thin_pack; +static int keep_pack; static int is_zero_sha1(const unsigned char *sha1) { @@ -270,6 +271,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) int new_refs; int ret = 0; int ask_for_status_report = 0; + int ask_to_keep_pack = 0; int expect_status_report = 0; /* No funny business with the matcher */ @@ -279,6 +281,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) /* Does the other end support the reporting? */ if (server_supports("report-status")) ask_for_status_report = 1; + if (server_supports("keep-pack") && keep_pack) + ask_to_keep_pack = 1; /* match them up */ if (!remote_tail) @@ -355,12 +359,17 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) strcpy(old_hex, sha1_to_hex(ref->old_sha1)); new_hex = sha1_to_hex(ref->new_sha1); - if (ask_for_status_report) { - packet_write(out, "%s %s %s%c%s", + if (ask_for_status_report || ask_to_keep_pack) { + packet_write(out, "%s %s %s%c%s%s", old_hex, new_hex, ref->name, 0, - "report-status"); + ask_for_status_report + ? " report-status" : "", + ask_to_keep_pack + ? " keep-pack" : ""); + if (ask_for_status_report) + expect_status_report = 1; ask_for_status_report = 0; - expect_status_report = 1; + ask_to_keep_pack = 0; } else packet_write(out, "%s %s %s", @@ -419,6 +428,10 @@ int main(int argc, char **argv) verbose = 1; continue; } + if (!strcmp(arg, "--keep")) { + keep_pack = 1; + continue; + } if (!strcmp(arg, "--thin")) { use_thin_pack = 1; continue; diff --git a/sha1_file.c b/sha1_file.c index e89d24c..278ba2f 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1292,7 +1292,7 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo return unpack_entry(&e, type, size); } -void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) +void *read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) { unsigned long mapsize; void *map, *buf; @@ -1757,7 +1757,10 @@ int has_sha1_file(const unsigned char *sha1) if (find_pack_entry(sha1, &e, NULL)) return 1; - return find_sha1_file(sha1, &st) ? 1 : 0; + if (find_sha1_file(sha1, &st)) + return 1; + reprepare_packed_git(); + return find_pack_entry(sha1, &e, NULL); } /* diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 8afb899..d831f8d 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -78,4 +78,13 @@ test_expect_success \ ! diff -u .git/refs/heads/master victim/.git/refs/heads/master ' +test_expect_success 'push with --keep' ' + t=`cd victim && git-rev-parse --verify refs/heads/master` && + git-update-ref refs/heads/master $t && + : > foo && + git add foo && + git commit -m "one more" && + git-send-pack --keep ./victim/.git/ master +' + test_done -- cgit v0.10.2-6-g49f6 From ce4231ffa80da1888a668cc7c33f5349800ab4a3 Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Sun, 29 Oct 2006 21:09:40 +0100 Subject: Mention that pull can work locally in the synopsis Signed-off-by: Robin Rosenberg Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 51577fc..2a5aea7 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -3,7 +3,7 @@ git-pull(1) NAME ---- -git-pull - Pull and merge from another repository +git-pull - Pull and merge from another repository or a local branch SYNOPSIS diff --git a/Documentation/git.txt b/Documentation/git.txt index 595cca1..fff07e1 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -324,7 +324,7 @@ gitlink:git-mv[1]:: Move or rename a file, a directory, or a symlink. gitlink:git-pull[1]:: - Fetch from and merge with a remote repository. + Fetch from and merge with a remote repository or a local branch. gitlink:git-push[1]:: Update remote refs along with associated objects. -- cgit v0.10.2-6-g49f6 From b1f33d626501c3e080b324e182f1da76f49b5bf9 Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Sun, 29 Oct 2006 21:09:48 +0100 Subject: Swap the porcelain and plumbing commands in the git man page This makes the documentation less confusing to newcomers. Signed-off-by: Robin Rosenberg Signed-off-by: Junio C Hamano diff --git a/Documentation/git.txt b/Documentation/git.txt index fff07e1..0679e3c 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -72,188 +72,6 @@ GIT COMMANDS We divide git into high level ("porcelain") commands and low level ("plumbing") commands. -Low-level commands (plumbing) ------------------------------ - -Although git includes its -own porcelain layer, its low-level commands are sufficient to support -development of alternative porcelains. Developers of such porcelains -might start by reading about gitlink:git-update-index[1] and -gitlink:git-read-tree[1]. - -We divide the low-level commands into commands that manipulate objects (in -the repository, index, and working tree), commands that interrogate and -compare objects, and commands that move objects and references between -repositories. - -Manipulation commands -~~~~~~~~~~~~~~~~~~~~~ -gitlink:git-apply[1]:: - Reads a "diff -up1" or git generated patch file and - applies it to the working tree. - -gitlink:git-checkout-index[1]:: - Copy files from the index to the working tree. - -gitlink:git-commit-tree[1]:: - Creates a new commit object. - -gitlink:git-hash-object[1]:: - Computes the object ID from a file. - -gitlink:git-index-pack[1]:: - Build pack idx file for an existing packed archive. - -gitlink:git-init-db[1]:: - Creates an empty git object database, or reinitialize an - existing one. - -gitlink:git-merge-index[1]:: - Runs a merge for files needing merging. - -gitlink:git-mktag[1]:: - Creates a tag object. - -gitlink:git-mktree[1]:: - Build a tree-object from ls-tree formatted text. - -gitlink:git-pack-objects[1]:: - Creates a packed archive of objects. - -gitlink:git-prune-packed[1]:: - Remove extra objects that are already in pack files. - -gitlink:git-read-tree[1]:: - Reads tree information into the index. - -gitlink:git-repo-config[1]:: - Get and set options in .git/config. - -gitlink:git-unpack-objects[1]:: - Unpacks objects out of a packed archive. - -gitlink:git-update-index[1]:: - Registers files in the working tree to the index. - -gitlink:git-write-tree[1]:: - Creates a tree from the index. - - -Interrogation commands -~~~~~~~~~~~~~~~~~~~~~~ - -gitlink:git-cat-file[1]:: - Provide content or type/size information for repository objects. - -gitlink:git-describe[1]:: - Show the most recent tag that is reachable from a commit. - -gitlink:git-diff-index[1]:: - Compares content and mode of blobs between the index and repository. - -gitlink:git-diff-files[1]:: - Compares files in the working tree and the index. - -gitlink:git-diff-stages[1]:: - Compares two "merge stages" in the index. - -gitlink:git-diff-tree[1]:: - Compares the content and mode of blobs found via two tree objects. - -gitlink:git-for-each-ref[1]:: - Output information on each ref. - -gitlink:git-fsck-objects[1]:: - Verifies the connectivity and validity of the objects in the database. - -gitlink:git-ls-files[1]:: - Information about files in the index and the working tree. - -gitlink:git-ls-tree[1]:: - Displays a tree object in human readable form. - -gitlink:git-merge-base[1]:: - Finds as good common ancestors as possible for a merge. - -gitlink:git-name-rev[1]:: - Find symbolic names for given revs. - -gitlink:git-pack-redundant[1]:: - Find redundant pack files. - -gitlink:git-rev-list[1]:: - Lists commit objects in reverse chronological order. - -gitlink:git-show-index[1]:: - Displays contents of a pack idx file. - -gitlink:git-tar-tree[1]:: - Creates a tar archive of the files in the named tree object. - -gitlink:git-unpack-file[1]:: - Creates a temporary file with a blob's contents. - -gitlink:git-var[1]:: - Displays a git logical variable. - -gitlink:git-verify-pack[1]:: - Validates packed git archive files. - -In general, the interrogate commands do not touch the files in -the working tree. - - -Synching repositories -~~~~~~~~~~~~~~~~~~~~~ - -gitlink:git-fetch-pack[1]:: - Updates from a remote repository (engine for ssh and - local transport). - -gitlink:git-http-fetch[1]:: - Downloads a remote git repository via HTTP by walking - commit chain. - -gitlink:git-local-fetch[1]:: - Duplicates another git repository on a local system by - walking commit chain. - -gitlink:git-peek-remote[1]:: - Lists references on a remote repository using - upload-pack protocol (engine for ssh and local - transport). - -gitlink:git-receive-pack[1]:: - Invoked by 'git-send-pack' to receive what is pushed to it. - -gitlink:git-send-pack[1]:: - Pushes to a remote repository, intelligently. - -gitlink:git-http-push[1]:: - Push missing objects using HTTP/DAV. - -gitlink:git-shell[1]:: - Restricted shell for GIT-only SSH access. - -gitlink:git-ssh-fetch[1]:: - Pulls from a remote repository over ssh connection by - walking commit chain. - -gitlink:git-ssh-upload[1]:: - Helper "server-side" program used by git-ssh-fetch. - -gitlink:git-update-server-info[1]:: - Updates auxiliary information on a dumb server to help - clients discover references and packs on it. - -gitlink:git-upload-archive[1]:: - Invoked by 'git-archive' to send a generated archive. - -gitlink:git-upload-pack[1]:: - Invoked by 'git-fetch-pack' to push - what are asked for. - - High-level commands (porcelain) ------------------------------- @@ -491,6 +309,188 @@ gitlink:git-stripspace[1]:: Filter out empty lines. +Low-level commands (plumbing) +----------------------------- + +Although git includes its +own porcelain layer, its low-level commands are sufficient to support +development of alternative porcelains. Developers of such porcelains +might start by reading about gitlink:git-update-index[1] and +gitlink:git-read-tree[1]. + +We divide the low-level commands into commands that manipulate objects (in +the repository, index, and working tree), commands that interrogate and +compare objects, and commands that move objects and references between +repositories. + +Manipulation commands +~~~~~~~~~~~~~~~~~~~~~ +gitlink:git-apply[1]:: + Reads a "diff -up1" or git generated patch file and + applies it to the working tree. + +gitlink:git-checkout-index[1]:: + Copy files from the index to the working tree. + +gitlink:git-commit-tree[1]:: + Creates a new commit object. + +gitlink:git-hash-object[1]:: + Computes the object ID from a file. + +gitlink:git-index-pack[1]:: + Build pack idx file for an existing packed archive. + +gitlink:git-init-db[1]:: + Creates an empty git object database, or reinitialize an + existing one. + +gitlink:git-merge-index[1]:: + Runs a merge for files needing merging. + +gitlink:git-mktag[1]:: + Creates a tag object. + +gitlink:git-mktree[1]:: + Build a tree-object from ls-tree formatted text. + +gitlink:git-pack-objects[1]:: + Creates a packed archive of objects. + +gitlink:git-prune-packed[1]:: + Remove extra objects that are already in pack files. + +gitlink:git-read-tree[1]:: + Reads tree information into the index. + +gitlink:git-repo-config[1]:: + Get and set options in .git/config. + +gitlink:git-unpack-objects[1]:: + Unpacks objects out of a packed archive. + +gitlink:git-update-index[1]:: + Registers files in the working tree to the index. + +gitlink:git-write-tree[1]:: + Creates a tree from the index. + + +Interrogation commands +~~~~~~~~~~~~~~~~~~~~~~ + +gitlink:git-cat-file[1]:: + Provide content or type/size information for repository objects. + +gitlink:git-describe[1]:: + Show the most recent tag that is reachable from a commit. + +gitlink:git-diff-index[1]:: + Compares content and mode of blobs between the index and repository. + +gitlink:git-diff-files[1]:: + Compares files in the working tree and the index. + +gitlink:git-diff-stages[1]:: + Compares two "merge stages" in the index. + +gitlink:git-diff-tree[1]:: + Compares the content and mode of blobs found via two tree objects. + +gitlink:git-for-each-ref[1]:: + Output information on each ref. + +gitlink:git-fsck-objects[1]:: + Verifies the connectivity and validity of the objects in the database. + +gitlink:git-ls-files[1]:: + Information about files in the index and the working tree. + +gitlink:git-ls-tree[1]:: + Displays a tree object in human readable form. + +gitlink:git-merge-base[1]:: + Finds as good common ancestors as possible for a merge. + +gitlink:git-name-rev[1]:: + Find symbolic names for given revs. + +gitlink:git-pack-redundant[1]:: + Find redundant pack files. + +gitlink:git-rev-list[1]:: + Lists commit objects in reverse chronological order. + +gitlink:git-show-index[1]:: + Displays contents of a pack idx file. + +gitlink:git-tar-tree[1]:: + Creates a tar archive of the files in the named tree object. + +gitlink:git-unpack-file[1]:: + Creates a temporary file with a blob's contents. + +gitlink:git-var[1]:: + Displays a git logical variable. + +gitlink:git-verify-pack[1]:: + Validates packed git archive files. + +In general, the interrogate commands do not touch the files in +the working tree. + + +Synching repositories +~~~~~~~~~~~~~~~~~~~~~ + +gitlink:git-fetch-pack[1]:: + Updates from a remote repository (engine for ssh and + local transport). + +gitlink:git-http-fetch[1]:: + Downloads a remote git repository via HTTP by walking + commit chain. + +gitlink:git-local-fetch[1]:: + Duplicates another git repository on a local system by + walking commit chain. + +gitlink:git-peek-remote[1]:: + Lists references on a remote repository using + upload-pack protocol (engine for ssh and local + transport). + +gitlink:git-receive-pack[1]:: + Invoked by 'git-send-pack' to receive what is pushed to it. + +gitlink:git-send-pack[1]:: + Pushes to a remote repository, intelligently. + +gitlink:git-http-push[1]:: + Push missing objects using HTTP/DAV. + +gitlink:git-shell[1]:: + Restricted shell for GIT-only SSH access. + +gitlink:git-ssh-fetch[1]:: + Pulls from a remote repository over ssh connection by + walking commit chain. + +gitlink:git-ssh-upload[1]:: + Helper "server-side" program used by git-ssh-fetch. + +gitlink:git-update-server-info[1]:: + Updates auxiliary information on a dumb server to help + clients discover references and packs on it. + +gitlink:git-upload-archive[1]:: + Invoked by 'git-archive' to send a generated archive. + +gitlink:git-upload-pack[1]:: + Invoked by 'git-fetch-pack' to push + what are asked for. + + Configuration Mechanism ----------------------- -- cgit v0.10.2-6-g49f6 From 9c572b21dd090a1e5c5bb397053bf8043ffe7fb4 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Sun, 29 Oct 2006 22:31:38 +0300 Subject: git-send-email: Document support for local sendmail instead of SMTP server Fix the --smtp-server option description to match reality. Signed-off-by: Sergey Vlasov Signed-off-by: Junio C Hamano diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 481b3f5..ec0e201 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -66,8 +66,11 @@ The options available are: all that is output. --smtp-server:: - If set, specifies the outgoing SMTP server to use. Defaults to - localhost. + If set, specifies the outgoing SMTP server to use. A full + pathname of a sendmail-like program can be specified instead; + the program must support the `-i` option. Defaults to + `/usr/sbin/sendmail` or `/usr/lib/sendmail` if such program is + available, or to `localhost` otherwise. --subject:: Specify the initial subject of the email thread. -- cgit v0.10.2-6-g49f6 From 6dcfa306f2b67b733a7eb2d7ded1bc9987809edb Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Sun, 29 Oct 2006 22:31:39 +0300 Subject: git-send-email: Read the default SMTP server from the GIT config file Make the default value for --smtp-server configurable through the 'sendemail.smtpserver' option in .git/config (or $HOME/.gitconfig). Signed-off-by: Sergey Vlasov Acked-by: Ryan Anderson Signed-off-by: Junio C Hamano diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index ec0e201..4c8d907 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -68,9 +68,11 @@ The options available are: --smtp-server:: If set, specifies the outgoing SMTP server to use. A full pathname of a sendmail-like program can be specified instead; - the program must support the `-i` option. Defaults to - `/usr/sbin/sendmail` or `/usr/lib/sendmail` if such program is - available, or to `localhost` otherwise. + the program must support the `-i` option. Default value can + be specified by the 'sendemail.smtpserver' configuration + option; the built-in default is `/usr/sbin/sendmail` or + `/usr/lib/sendmail` if such program is available, or + `localhost` otherwise. --subject:: Specify the initial subject of the email thread. diff --git a/git-send-email.perl b/git-send-email.perl index c42dc3b..4c87c20 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -231,6 +231,9 @@ if (!defined $initial_reply_to && $prompting) { } if (!$smtp_server) { + $smtp_server = $repo->config('sendemail.smtpserver'); +} +if (!$smtp_server) { foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) { if (-x $_) { $smtp_server = $_; -- cgit v0.10.2-6-g49f6 From d4ff6d92c3d7020233a9b4990a3545ee54a5c033 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sun, 29 Oct 2006 04:37:11 -0500 Subject: Allow short pack names to git-pack-objects --unpacked=. This allows us to pass just the file name of a pack rather than the complete path when we want pack-objects to consider its contents as though they were loose objects. This can be helpful if $GIT_OBJECT_DIRECTORY contains shell metacharacters which make it cumbersome to pass complete paths safely in a shell script. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index e89d24c..5e6c8b8 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1203,6 +1203,24 @@ unsigned long find_pack_entry_one(const unsigned char *sha1, return 0; } +static int matches_pack_name(struct packed_git *p, const char *ig) +{ + const char *last_c, *c; + + if (!strcmp(p->pack_name, ig)) + return 0; + + for (c = p->pack_name, last_c = c; *c;) + if (*c == '/') + last_c = ++c; + else + ++c; + if (!strcmp(last_c, ig)) + return 0; + + return 1; +} + static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) { struct packed_git *p; @@ -1214,7 +1232,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons if (ignore_packed) { const char **ig; for (ig = ignore_packed; *ig; ig++) - if (!strcmp(p->pack_name, *ig)) + if (!matches_pack_name(p, *ig)) break; if (*ig) continue; -- cgit v0.10.2-6-g49f6 From ce8590748b918687abc4c7cd2d432dd23f07ae40 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sun, 29 Oct 2006 04:37:54 -0500 Subject: Only repack active packs by skipping over kept packs. During `git repack -a -d` only repack objects which are loose or which reside in an active (a non-kept) pack. This allows the user to keep large packs as-is without continuous repacking and can be very helpful on large repositories. It should also help us resolve a race condition between `git repack -a -d` and the new pack store functionality in `git-receive-pack`. Kept packs are those which have a corresponding .keep file in $GIT_OBJECT_DIRECTORY/pack. That is pack-X.pack will be kept (not repacked and not deleted) if pack-X.keep exists in the same directory when `git repack -a -d` starts. Currently this feature is not documented and there is no user interface to keep an existing pack. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/git-repack.sh b/git-repack.sh index 17e2452..f150a55 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -45,11 +45,19 @@ case ",$all_into_one," in args='--unpacked --incremental' ;; ,t,) - args= - - # Redundancy check in all-into-one case is trivial. - existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \ - find . -type f \( -name '*.pack' -o -name '*.idx' \) -print` + if [ -d "$PACKDIR" ]; then + for e in `cd "$PACKDIR" && find . -type f -name '*.pack' \ + | sed -e 's/^\.\///' -e 's/\.pack$//'` + do + if [ -e "$PACKDIR/$e.keep" ]; then + : keep + else + args="$args --unpacked=$e.pack" + existing="$existing $e" + fi + done + fi + [ -z "$args" ] && args='--unpacked --incremental' ;; esac @@ -86,17 +94,16 @@ fi if test "$remove_redundant" = t then - # We know $existing are all redundant only when - # all-into-one is used. - if test "$all_into_one" != '' && test "$existing" != '' + # We know $existing are all redundant. + if [ -n "$existing" ] then sync ( cd "$PACKDIR" && for e in $existing do case "$e" in - ./pack-$name.pack | ./pack-$name.idx) ;; - *) rm -f $e ;; + pack-$name) ;; + *) rm -f "$e.pack" "$e.idx" "$e.keep" ;; esac done ) -- cgit v0.10.2-6-g49f6 From b8077709243138c3d8cc1c096c06a95b250a9001 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sun, 29 Oct 2006 04:41:59 -0500 Subject: Teach git-index-pack how to keep a pack file. To prevent a race condition between `index-pack --stdin` and `repack -a -d` where the repack deletes the newly created pack file before any refs are updated to reference objects contained within it we mark the pack file as one that should be kept. This removes it from the list of packs that `repack -a -d` will consider for removal. Callers such as `receive-pack` which want to invoke `index-pack` should use this new --keep option to prevent the newly created pack and index file pair from being deleted before they have finished any related ref updates. Only after all ref updates have been finished should the associated .keep file be removed. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 9fa4847..1235416 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -9,7 +9,7 @@ git-index-pack - Build pack index file for an existing packed archive SYNOPSIS -------- 'git-index-pack' [-v] [-o ] -'git-index-pack' --stdin [--fix-thin] [-v] [-o ] [] +'git-index-pack' --stdin [--fix-thin] [--keep] [-v] [-o ] [] DESCRIPTION @@ -38,7 +38,10 @@ OPTIONS instead and a copy is then written to . If is not specified, the pack is written to objects/pack/ directory of the current git repository with - a default name determined from the pack content. + a default name determined from the pack content. If + is not specified consider using --keep to + prevent a race condition between this process and + gitlink::git-repack[1] . --fix-thin:: It is possible for gitlink:git-pack-objects[1] to build @@ -48,7 +51,22 @@ OPTIONS and they must be included in the pack for that pack to be self contained and indexable. Without this option any attempt to index a thin pack will fail. This option only makes sense in - conjonction with --stdin. + conjunction with --stdin. + +--keep:: + Before moving the index into its final destination + create an empty .keep file for the associated pack file. + This option is usually necessary with --stdin to prevent a + simultaneous gitlink:git-repack[1] process from deleting + the newly constructed pack and index before refs can be + updated to use objects contained in the pack. + +--keep='why':: + Like --keep create a .keep file before moving the index into + its final destination, but rather than creating an empty file + place 'why' followed by an LF into the .keep file. The 'why' + message can later be searched for within all .keep files to + locate any which have outlived their usefulness. Author diff --git a/index-pack.c b/index-pack.c index 866a054..b37dd78 100644 --- a/index-pack.c +++ b/index-pack.c @@ -10,7 +10,7 @@ #include static const char index_pack_usage[] = -"git-index-pack [-v] [-o ] { | --stdin [--fix-thin] [] }"; +"git-index-pack [-v] [-o ] [{ ---keep | --keep= }] { | --stdin [--fix-thin] [] }"; struct object_entry { @@ -754,6 +754,7 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1) static void final(const char *final_pack_name, const char *curr_pack_name, const char *final_index_name, const char *curr_index_name, + const char *keep_name, const char *keep_msg, unsigned char *sha1) { char name[PATH_MAX]; @@ -780,6 +781,23 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } } + if (keep_msg) { + int keep_fd, keep_msg_len = strlen(keep_msg); + if (!keep_name) { + snprintf(name, sizeof(name), "%s/pack/pack-%s.keep", + get_object_directory(), sha1_to_hex(sha1)); + keep_name = name; + } + keep_fd = open(keep_name, O_RDWR | O_CREAT, 0600); + if (keep_fd < 0) + die("cannot write keep file"); + if (keep_msg_len > 0) { + write_or_die(keep_fd, keep_msg, keep_msg_len); + write_or_die(keep_fd, "\n", 1); + } + close(keep_fd); + } + if (final_pack_name != curr_pack_name) { if (!final_pack_name) { snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", @@ -807,7 +825,8 @@ int main(int argc, char **argv) int i, fix_thin_pack = 0; const char *curr_pack, *pack_name = NULL; const char *curr_index, *index_name = NULL; - char *index_name_buf = NULL; + const char *keep_name = NULL, *keep_msg = NULL; + char *index_name_buf = NULL, *keep_name_buf = NULL; unsigned char sha1[20]; for (i = 1; i < argc; i++) { @@ -818,6 +837,10 @@ int main(int argc, char **argv) from_stdin = 1; } else if (!strcmp(arg, "--fix-thin")) { fix_thin_pack = 1; + } else if (!strcmp(arg, "--keep")) { + keep_msg = ""; + } else if (!strncmp(arg, "--keep=", 7)) { + keep_msg = arg + 7; } else if (!strcmp(arg, "-v")) { verbose = 1; } else if (!strcmp(arg, "-o")) { @@ -848,6 +871,16 @@ int main(int argc, char **argv) strcpy(index_name_buf + len - 5, ".idx"); index_name = index_name_buf; } + if (keep_msg && !keep_name && pack_name) { + int len = strlen(pack_name); + if (!has_extension(pack_name, ".pack")) + die("packfile name '%s' does not end with '.pack'", + pack_name); + keep_name_buf = xmalloc(len); + memcpy(keep_name_buf, pack_name, len - 5); + strcpy(keep_name_buf + len - 5, ".keep"); + keep_name = keep_name_buf; + } curr_pack = open_pack_file(pack_name); parse_pack_header(); @@ -880,9 +913,13 @@ int main(int argc, char **argv) } free(deltas); curr_index = write_index_file(index_name, sha1); - final(pack_name, curr_pack, index_name, curr_index, sha1); + final(pack_name, curr_pack, + index_name, curr_index, + keep_name, keep_msg, + sha1); free(objects); free(index_name_buf); + free(keep_name_buf); if (!from_stdin) printf("%s\n", sha1_to_hex(sha1)); -- cgit v0.10.2-6-g49f6 From 54a4c6173e30774125016f7ff4976eb4a7485d0c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 29 Oct 2006 03:07:40 -0800 Subject: git-pickaxe: WIP to refcount origin structure. The origin structure is allocated for each commit and path while the code traverse down it is copied into different blame entries. To avoid leaks, try refcounting them. This still seems to leak, which I haven't tracked down fully yet. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 663b96d..b53c7b0 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -36,6 +36,10 @@ static int max_orig_digits; static int max_digits; static int max_score_digits; +#ifndef DEBUG +#define DEBUG 0 +#endif + #define PICKAXE_BLAME_MOVE 01 #define PICKAXE_BLAME_COPY 02 #define PICKAXE_BLAME_COPY_HARDER 04 @@ -54,14 +58,30 @@ static unsigned blame_copy_score; #define MORE_THAN_ONE_PATH (1u<<13) /* - * One blob in a commit + * One blob in a commit that is being suspected */ struct origin { + int refcnt; struct commit *commit; unsigned char blob_sha1[20]; char path[FLEX_ARRAY]; }; +static inline struct origin *origin_incref(struct origin *o) +{ + if (o) + o->refcnt++; + return o; +} + +static void origin_decref(struct origin *o) +{ + if (o && --o->refcnt <= 0) { + memset(o, 0, sizeof(*o)); + free(o); + } +} + struct blame_entry { struct blame_entry *prev; struct blame_entry *next; @@ -121,6 +141,8 @@ static int cmp_suspect(struct origin *a, struct origin *b) return strcmp(a->path, b->path); } +static void sanity_check_refcnt(struct scoreboard *); + static void coalesce(struct scoreboard *sb) { struct blame_entry *ent, *next; @@ -133,11 +155,15 @@ static void coalesce(struct scoreboard *sb) ent->next = next->next; if (ent->next) ent->next->prev = ent; + origin_decref(next->suspect); free(next); ent->score = 0; next = ent; /* again */ } } + + if (DEBUG) /* sanity */ + sanity_check_refcnt(sb); } static struct origin *get_origin(struct scoreboard *sb, @@ -150,10 +176,11 @@ static struct origin *get_origin(struct scoreboard *sb, for (e = sb->ent; e; e = e->next) { if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) - return e->suspect; + return origin_incref(e->suspect); } o = xcalloc(1, sizeof(*o) + strlen(path) + 1); o->commit = commit; + o->refcnt = 1; strcpy(o->path, path); return o; } @@ -400,6 +427,8 @@ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) { struct blame_entry *ent, *prev = NULL; + origin_incref(e->suspect); + for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next) prev = ent; @@ -420,8 +449,11 @@ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) static void dup_entry(struct blame_entry *dst, struct blame_entry *src) { struct blame_entry *p, *n; + p = dst->prev; n = dst->next; + origin_incref(src->suspect); + origin_decref(dst->suspect); memcpy(dst, src, sizeof(*src)); dst->prev = p; dst->next = n; @@ -433,7 +465,7 @@ static const char *nth_line(struct scoreboard *sb, int lno) return sb->final_buf + sb->lineno[lno]; } -static void split_overlap(struct blame_entry split[3], +static void split_overlap(struct blame_entry *split, struct blame_entry *e, int tlno, int plno, int same, struct origin *parent) @@ -457,7 +489,7 @@ static void split_overlap(struct blame_entry split[3], if (e->s_lno < tlno) { /* there is a pre-chunk part not blamed on parent */ - split[0].suspect = e->suspect; + split[0].suspect = origin_incref(e->suspect); split[0].lno = e->lno; split[0].s_lno = e->s_lno; split[0].num_lines = tlno - e->s_lno; @@ -471,7 +503,7 @@ static void split_overlap(struct blame_entry split[3], if (same < e->s_lno + e->num_lines) { /* there is a post-chunk part not blamed on parent */ - split[2].suspect = e->suspect; + split[2].suspect = origin_incref(e->suspect); split[2].lno = e->lno + (same - e->s_lno); split[2].s_lno = e->s_lno + (same - e->s_lno); split[2].num_lines = e->s_lno + e->num_lines - same; @@ -483,11 +515,11 @@ static void split_overlap(struct blame_entry split[3], if (split[1].num_lines < 1) return; - split[1].suspect = parent; + split[1].suspect = origin_incref(parent); } static void split_blame(struct scoreboard *sb, - struct blame_entry split[3], + struct blame_entry *split, struct blame_entry *e) { struct blame_entry *new_entry; @@ -522,7 +554,7 @@ static void split_blame(struct scoreboard *sb, add_blame_entry(sb, new_entry); } - if (1) { /* sanity */ + if (DEBUG) { /* sanity */ struct blame_entry *ent; int lno = sb->ent->lno, corrupt = 0; @@ -545,6 +577,14 @@ static void split_blame(struct scoreboard *sb, } } +static void decref_split(struct blame_entry *split) +{ + int i; + + for (i = 0; i < 3; i++) + origin_decref(split[i].suspect); +} + static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, int tlno, int plno, int same, struct origin *parent) @@ -552,9 +592,9 @@ static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, struct blame_entry split[3]; split_overlap(split, e, tlno, plno, same, parent); - if (!split[1].suspect) - return; - split_blame(sb, split, e); + if (split[1].suspect) + split_blame(sb, split, e); + decref_split(split); } static int find_last_in_target(struct scoreboard *sb, struct origin *target) @@ -636,22 +676,28 @@ static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) } static void copy_split_if_better(struct scoreboard *sb, - struct blame_entry best_so_far[3], - struct blame_entry this[3]) + struct blame_entry *best_so_far, + struct blame_entry *this) { + int i; + if (!this[1].suspect) return; if (best_so_far[1].suspect) { if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1])) return; } + + for (i = 0; i < 3; i++) + origin_incref(this[i].suspect); + decref_split(best_so_far); memcpy(best_so_far, this, sizeof(struct blame_entry [3])); } static void find_copy_in_blob(struct scoreboard *sb, struct blame_entry *ent, struct origin *parent, - struct blame_entry split[3], + struct blame_entry *split, mmfile_t *file_p) { const char *cp; @@ -687,6 +733,7 @@ static void find_copy_in_blob(struct scoreboard *sb, chunk->same + ent->s_lno, parent); copy_split_if_better(sb, split, this); + decref_split(this); } plno = chunk->p_next; tlno = chunk->t_next; @@ -723,6 +770,7 @@ static int find_move_in_parent(struct scoreboard *sb, if (split[1].suspect && blame_move_score < ent_score(sb, &split[1])) split_blame(sb, split, e); + decref_split(split); } free(blob_p); return 0; @@ -806,6 +854,7 @@ static int find_copy_in_parent(struct scoreboard *sb, this); } free(blob); + origin_decref(norigin); } diff_flush(&diff_opts); @@ -814,6 +863,7 @@ static int find_copy_in_parent(struct scoreboard *sb, if (split[1].suspect && blame_copy_score < ent_score(sb, &split[1])) split_blame(sb, split, blame_list[j].ent); + decref_split(split); } free(blame_list); @@ -843,9 +893,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { struct blame_entry *e; for (e = sb->ent; e; e = e->next) - if (e->suspect == origin) + if (e->suspect == origin) { + origin_incref(porigin); + origin_decref(e->suspect); e->suspect = porigin; - return; + } + origin_decref(porigin); + goto finish; } parent_origin[i] = porigin; } @@ -857,7 +911,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) if (!porigin) continue; if (pass_blame_to_parent(sb, origin, porigin)) - return; + goto finish; } /* @@ -871,7 +925,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) if (!porigin) continue; if (find_move_in_parent(sb, origin, porigin)) - return; + goto finish; } /* @@ -884,8 +938,12 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) struct origin *porigin = parent_origin[i]; if (find_copy_in_parent(sb, origin, parent->item, porigin, opt)) - return; + goto finish; } + + finish: + for (i = 0; i < MAXPARENT; i++) + origin_decref(parent_origin[i]); } static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) @@ -902,6 +960,7 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) if (!suspect) return; /* all done */ + origin_incref(suspect); commit = suspect->commit; parse_commit(commit); if (!(commit->object.flags & UNINTERESTING) && @@ -912,7 +971,7 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) for (ent = sb->ent; ent; ent = ent->next) if (!cmp_suspect(ent->suspect, suspect)) ent->guilty = 1; - + origin_decref(suspect); coalesce(sb); } } @@ -1132,7 +1191,9 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) ent->lno + 1 + cnt); else { if (opt & OUTPUT_SHOW_SCORE) - printf(" %*d", max_score_digits, ent->score); + printf(" %*d %02d", + max_score_digits, ent->score, + ent->suspect->refcnt); if (opt & OUTPUT_SHOW_NAME) printf(" %-*.*s", longest_file, longest_file, suspect->path); @@ -1273,6 +1334,45 @@ static void find_alignment(struct scoreboard *sb, int *option) max_score_digits = lineno_width(largest_score); } +static void sanity_check_refcnt(struct scoreboard *sb) +{ + int baa = 0; + struct blame_entry *ent; + + for (ent = sb->ent; ent; ent = ent->next) { + /* first mark the ones that haven't been checked */ + if (0 < ent->suspect->refcnt) + ent->suspect->refcnt = -ent->suspect->refcnt; + else if (!ent->suspect->refcnt) + baa = 1; + } + for (ent = sb->ent; ent; ent = ent->next) { + /* then pick each and see if they have the the + * correct refcnt + */ + int found; + struct blame_entry *e; + struct origin *suspect = ent->suspect; + + if (0 < suspect->refcnt) + continue; + suspect->refcnt = -suspect->refcnt; + for (found = 0, e = sb->ent; e; e = e->next) { + if (e->suspect != suspect) + continue; + found++; + } + if (suspect->refcnt != found) + baa = 1; + } + if (baa) { + int opt = 0160; + find_alignment(sb, &opt); + output(sb, opt); + die("Baa!"); + } +} + static int has_path_in_work_tree(const char *path) { struct stat st; -- cgit v0.10.2-6-g49f6 From 5ca807842fcb709757147df0044e8b4b2946ea22 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sun, 29 Oct 2006 21:33:22 -0500 Subject: missing small substitution Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/fetch-clone.c b/fetch-clone.c index 96cdab4..f629d8d 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -46,7 +46,7 @@ static int get_pack(int xd[2], const char *me, int sideband, const char **argv) side_pid = setup_sideband(sideband, me, fd, xd); pid = fork(); if (pid < 0) - die("%s: unable to fork off git-unpack-objects", me); + die("%s: unable to fork off %s", me, argv[0]); if (!pid) { dup2(fd[0], 0); close(fd[0]); -- cgit v0.10.2-6-g49f6 From 2c40f98439e07b8579d0549109a4feed54da9e40 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 29 Oct 2006 23:50:38 -0800 Subject: git-pickaxe: allow -Ln,m as well as -L n,m The command rejects -L1,10 as an invalid line range specifier and I got frustrated enough by it, so this makes it allow both forms of input. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index b53c7b0..200772c 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -1429,9 +1429,15 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; blame_copy_score = parse_score(arg+2); } - else if (!strcmp("-L", arg) && ++i < argc) { + else if (!strncmp("-L", arg, 2)) { char *term; - arg = argv[i]; + if (!arg[2]) { + if (++i >= argc) + usage(pickaxe_usage); + arg = argv[i]; + } + else + arg += 2; if (bottom || top) die("More than one '-L n,m' option given"); bottom = strtol(arg, &term, 10); -- cgit v0.10.2-6-g49f6 From f5f75c652b9c2347522159a87297820103e593e4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 29 Oct 2006 23:56:12 -0800 Subject: git-pickaxe: refcount origin correctly in find_copy_in_parent() This makes "git-pickaxe -C master -- revision.c" to finish with proper refcounts for all origins. I am reasonably happy with it. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 200772c..3e7277d 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -852,6 +852,7 @@ static int find_copy_in_parent(struct scoreboard *sb, this, &file_p); copy_split_if_better(sb, blame_list[j].split, this); + decref_split(this); } free(blob); origin_decref(norigin); -- cgit v0.10.2-6-g49f6 From ae86ad65757b857337f10f25ff31cb3348d3943d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 30 Oct 2006 14:27:52 -0800 Subject: git-pickaxe: tighten sanity checks. When compiled for debugging, make sure that refcnt sanity check code detects underflows in origin reference counting. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 3e7277d..8286832 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -973,7 +973,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) if (!cmp_suspect(ent->suspect, suspect)) ent->guilty = 1; origin_decref(suspect); - coalesce(sb); + + if (DEBUG) /* sanity */ + sanity_check_refcnt(sb); } } @@ -1341,11 +1343,14 @@ static void sanity_check_refcnt(struct scoreboard *sb) struct blame_entry *ent; for (ent = sb->ent; ent; ent = ent->next) { - /* first mark the ones that haven't been checked */ + /* Nobody should have zero or negative refcnt */ + if (ent->suspect->refcnt <= 0) + baa = 1; + } + for (ent = sb->ent; ent; ent = ent->next) { + /* Mark the ones that haven't been checked */ if (0 < ent->suspect->refcnt) ent->suspect->refcnt = -ent->suspect->refcnt; - else if (!ent->suspect->refcnt) - baa = 1; } for (ent = sb->ent; ent; ent = ent->next) { /* then pick each and see if they have the the @@ -1357,7 +1362,7 @@ static void sanity_check_refcnt(struct scoreboard *sb) if (0 < suspect->refcnt) continue; - suspect->refcnt = -suspect->refcnt; + suspect->refcnt = -suspect->refcnt; /* Unmark */ for (found = 0, e = sb->ent; e; e = e->next) { if (e->suspect != suspect) continue; -- cgit v0.10.2-6-g49f6 From 4903161fb8c74bc583b21d7ffe7abaf223df4253 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 30 Oct 2006 15:29:53 -0800 Subject: Surround "#define DEBUG 0" with "#ifndef DEBUG..#endif" Otherwise "make CFLAGS=-DDEBUG=1" is cumbersome to run. Signed-off-by: Junio C Hamano diff --git a/blame.c b/blame.c index 8cfd5d9..3e227d2 100644 --- a/blame.c +++ b/blame.c @@ -18,7 +18,9 @@ #include "revision.h" #include "xdiff-interface.h" +#ifndef DEBUG #define DEBUG 0 +#endif static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S ] [--] file [commit]\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" @@ -232,6 +234,9 @@ static void print_map(struct commit *cmit, struct commit *other) util2->num_lines ? util->num_lines : util2->num_lines; int num; + if (print_map == NULL) + ; /* to avoid "unused function" warning */ + for (i = 0; i < max; i++) { printf("i: %d ", i); num = -1; diff --git a/cache-tree.c b/cache-tree.c index d388848..a803262 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -2,7 +2,9 @@ #include "tree.h" #include "cache-tree.h" +#ifndef DEBUG #define DEBUG 0 +#endif struct cache_tree *cache_tree(void) { -- cgit v0.10.2-6-g49f6 From 4508dde16951df00529207f179fee405e8646a64 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 30 Oct 2006 16:02:07 -0800 Subject: Revert "send-pack --keep: do not explode into loose objects on the receiving end." This reverts commit c7740a943ec896247ebc5514b6be02710caf3c53. There should be a way to make this controllable from the receiver end. diff --git a/receive-pack.c b/receive-pack.c index ef50226..ea2dbd4 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -8,14 +8,10 @@ static const char receive_pack_usage[] = "git-receive-pack "; static const char *unpacker[] = { "unpack-objects", NULL }; -static const char *keep_packer[] = { - "index-pack", "--stdin", "--fix-thin", NULL -}; static int report_status; -static int keep_pack; -static char capabilities[] = "report-status keep-pack"; +static char capabilities[] = "report-status"; static int capabilities_sent; static int show_ref(const char *path, const unsigned char *sha1) @@ -265,8 +261,6 @@ static void read_head_info(void) if (reflen + 82 < len) { if (strstr(refname + reflen + 1, "report-status")) report_status = 1; - if (strstr(refname + reflen + 1, "keep-pack")) - keep_pack = 1; } cmd = xmalloc(sizeof(struct command) + len - 80); hashcpy(cmd->old_sha1, old_sha1); @@ -281,14 +275,7 @@ static void read_head_info(void) static const char *unpack(int *error_code) { - int code; - - if (keep_pack) - code = run_command_v_opt(ARRAY_SIZE(keep_packer) - 1, - keep_packer, RUN_GIT_CMD); - else - code = run_command_v_opt(ARRAY_SIZE(unpacker) - 1, - unpacker, RUN_GIT_CMD); + int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD); *error_code = 0; switch (code) { @@ -348,7 +335,7 @@ int main(int argc, char **argv) if (!dir) usage(receive_pack_usage); - if (!enter_repo(dir, 0)) + if(!enter_repo(dir, 0)) die("'%s': unable to chdir or not a git archive", dir); write_head_info(); diff --git a/send-pack.c b/send-pack.c index 54d218c..5bb123a 100644 --- a/send-pack.c +++ b/send-pack.c @@ -6,14 +6,13 @@ #include "exec_cmd.h" static const char send_pack_usage[] = -"git-send-pack [--all] [--keep] [--exec=git-receive-pack] [...]\n" +"git-send-pack [--all] [--exec=git-receive-pack] [...]\n" " --all and explicit specification are mutually exclusive."; static const char *exec = "git-receive-pack"; static int verbose; static int send_all; static int force_update; static int use_thin_pack; -static int keep_pack; static int is_zero_sha1(const unsigned char *sha1) { @@ -271,7 +270,6 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) int new_refs; int ret = 0; int ask_for_status_report = 0; - int ask_to_keep_pack = 0; int expect_status_report = 0; /* No funny business with the matcher */ @@ -281,8 +279,6 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) /* Does the other end support the reporting? */ if (server_supports("report-status")) ask_for_status_report = 1; - if (server_supports("keep-pack") && keep_pack) - ask_to_keep_pack = 1; /* match them up */ if (!remote_tail) @@ -359,17 +355,12 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) strcpy(old_hex, sha1_to_hex(ref->old_sha1)); new_hex = sha1_to_hex(ref->new_sha1); - if (ask_for_status_report || ask_to_keep_pack) { - packet_write(out, "%s %s %s%c%s%s", + if (ask_for_status_report) { + packet_write(out, "%s %s %s%c%s", old_hex, new_hex, ref->name, 0, - ask_for_status_report - ? " report-status" : "", - ask_to_keep_pack - ? " keep-pack" : ""); - if (ask_for_status_report) - expect_status_report = 1; + "report-status"); ask_for_status_report = 0; - ask_to_keep_pack = 0; + expect_status_report = 1; } else packet_write(out, "%s %s %s", @@ -428,10 +419,6 @@ int main(int argc, char **argv) verbose = 1; continue; } - if (!strcmp(arg, "--keep")) { - keep_pack = 1; - continue; - } if (!strcmp(arg, "--thin")) { use_thin_pack = 1; continue; diff --git a/sha1_file.c b/sha1_file.c index 278ba2f..e89d24c 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1292,7 +1292,7 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo return unpack_entry(&e, type, size); } -void *read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) +void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) { unsigned long mapsize; void *map, *buf; @@ -1757,10 +1757,7 @@ int has_sha1_file(const unsigned char *sha1) if (find_pack_entry(sha1, &e, NULL)) return 1; - if (find_sha1_file(sha1, &st)) - return 1; - reprepare_packed_git(); - return find_pack_entry(sha1, &e, NULL); + return find_sha1_file(sha1, &st) ? 1 : 0; } /* diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index d831f8d..8afb899 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -78,13 +78,4 @@ test_expect_success \ ! diff -u .git/refs/heads/master victim/.git/refs/heads/master ' -test_expect_success 'push with --keep' ' - t=`cd victim && git-rev-parse --verify refs/heads/master` && - git-update-ref refs/heads/master $t && - : > foo && - git add foo && - git commit -m "one more" && - git-send-pack --keep ./victim/.git/ master -' - test_done -- cgit v0.10.2-6-g49f6 From 8d63d95f297446fe110ea76d350ae15676e2ed54 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 30 Oct 2006 16:07:54 -0800 Subject: quote.c: ensure the same quoting across platforms. We read a byte from "char *" and compared it with ' ' to decide if it needs quoting to protect textual output. With a platform where char is unsigned char that would give different result. Signed-off-by: Junio C Hamano diff --git a/quote.c b/quote.c index e3a4d4a..dc5c0a7 100644 --- a/quote.c +++ b/quote.c @@ -209,7 +209,7 @@ static int quote_c_style_counted(const char *name, int namelen, if (!ch) break; if ((ch < ' ') || (ch == '"') || (ch == '\\') || - (ch == 0177)) { + (ch >= 0177)) { needquote = 1; switch (ch) { case '\a': EMITQ(); ch = 'a'; break; -- cgit v0.10.2-6-g49f6 From f69e743d97cdb3f6647ea0620a22311e7bf36051 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 30 Oct 2006 17:17:41 -0800 Subject: git-pickaxe: split find_origin() into find_rename() and find_origin(). When a merge adds a new file from the second parent, the earlier code tried to find renames in the first parent before noticing that the vertion from the second parent was added without modification. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 8286832..32dc393 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -211,7 +211,6 @@ static struct origin *find_origin(struct scoreboard *sb, { struct origin *porigin = NULL; struct diff_options diff_opts; - int i; const char *paths[2]; /* See if the origin->path is different between parent @@ -260,10 +259,17 @@ static struct origin *find_origin(struct scoreboard *sb, } } diff_flush(&diff_opts); - if (porigin) - return porigin; + return porigin; +} - /* Otherwise we would look for a rename */ +static struct origin *find_rename(struct scoreboard *sb, + struct commit *parent, + struct origin *origin) +{ + struct origin *porigin = NULL; + struct diff_options diff_opts; + int i; + const char *paths[2]; diff_setup(&diff_opts); diff_opts.recursive = 1; @@ -875,34 +881,46 @@ static int find_copy_in_parent(struct scoreboard *sb, static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) { - int i; + int i, pass; struct commit *commit = origin->commit; struct commit_list *parent; struct origin *parent_origin[MAXPARENT], *porigin; memset(parent_origin, 0, sizeof(parent_origin)); - for (i = 0, parent = commit->parents; - i < MAXPARENT && parent; - parent = parent->next, i++) { - struct commit *p = parent->item; - if (parse_commit(p)) - continue; - porigin = find_origin(sb, parent->item, origin); - if (!porigin) - continue; - if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { - struct blame_entry *e; - for (e = sb->ent; e; e = e->next) - if (e->suspect == origin) { - origin_incref(porigin); - origin_decref(e->suspect); - e->suspect = porigin; - } - origin_decref(porigin); - goto finish; + /* The first pass looks for unrenamed path to optimize for + * common cases, then we look for renames in the second pass. + */ + for (pass = 0; pass < 2; pass++) { + struct origin *(*find)(struct scoreboard *, + struct commit *, struct origin *); + find = pass ? find_rename : find_origin; + + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct commit *p = parent->item; + + if (parent_origin[i]) + continue; + if (parse_commit(p)) + continue; + porigin = find(sb, parent->item, origin); + if (!porigin) + continue; + if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { + struct blame_entry *e; + for (e = sb->ent; e; e = e->next) + if (e->suspect == origin) { + origin_incref(porigin); + origin_decref(e->suspect); + e->suspect = porigin; + } + origin_decref(porigin); + goto finish; + } + parent_origin[i] = porigin; } - parent_origin[i] = porigin; } for (i = 0, parent = commit->parents; -- cgit v0.10.2-6-g49f6 From 173a9cbe7031bc3574b3f41cb2d2375cf959ff2a Mon Sep 17 00:00:00 2001 From: Edgar Toernig Date: Mon, 30 Oct 2006 17:39:17 -0800 Subject: Use memmove instead of memcpy for overlapping areas Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 16804ab..a6a6568 100644 --- a/imap-send.c +++ b/imap-send.c @@ -272,7 +272,7 @@ buffer_gets( buffer_t * b, char **s ) n = b->bytes - start; if (n) - memcpy( b->buf, b->buf + start, n ); + memmove(b->buf, b->buf + start, n); b->offset -= start; b->bytes = n; start = 0; -- cgit v0.10.2-6-g49f6 From 79a65697be00f3b53430930078a7d34b591d5070 Mon Sep 17 00:00:00 2001 From: Edgar Toernig Date: Mon, 30 Oct 2006 17:44:27 -0800 Subject: Use memmove instead of memcpy for overlapping areas Signed-off-by: Junio C Hamano diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index e70a711..74a90c1 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -22,7 +22,7 @@ static SHA_CTX ctx; * Make sure at least "min" bytes are available in the buffer, and * return the pointer to the buffer. */ -static void * fill(int min) +static void *fill(int min) { if (min <= len) return buffer + offset; @@ -30,7 +30,7 @@ static void * fill(int min) die("cannot fill %d bytes", min); if (offset) { SHA1_Update(&ctx, buffer, offset); - memcpy(buffer, buffer + offset, len); + memmove(buffer, buffer + offset, len); offset = 0; } do { diff --git a/index-pack.c b/index-pack.c index e33f605..70640e1 100644 --- a/index-pack.c +++ b/index-pack.c @@ -53,7 +53,7 @@ static int input_fd; * Make sure at least "min" bytes are available in the buffer, and * return the pointer to the buffer. */ -static void * fill(int min) +static void *fill(int min) { if (min <= input_len) return input_buffer + input_offset; @@ -61,7 +61,7 @@ static void * fill(int min) die("cannot fill %d bytes", min); if (input_offset) { SHA1_Update(&input_ctx, input_buffer, input_offset); - memcpy(input_buffer, input_buffer + input_offset, input_len); + memmove(input_buffer, input_buffer + input_offset, input_len); input_offset = 0; } do { -- cgit v0.10.2-6-g49f6 From 744f498522d2255cf0ce967298c3d87b4727d1a4 Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Mon, 30 Oct 2006 20:37:49 -0500 Subject: Continue traversal when rev-list --unpacked finds a packed commit. When getting the list of all unpacked objects by walking the commit history, we would stop traversal whenever we hit a packed commit. However the fact that we found a packed commit does not guarantee that all previous commits are also packed. As a result the commit walkers did not show all reachable unpacked objects. Signed-off-by: Jan Harkes Signed-off-by: Junio C Hamano diff --git a/revision.c b/revision.c index 93f2513..89e6c6f 100644 --- a/revision.c +++ b/revision.c @@ -418,9 +418,6 @@ static void limit_list(struct rev_info *revs) if (revs->max_age != -1 && (commit->date < revs->max_age)) obj->flags |= UNINTERESTING; - if (revs->unpacked && - has_sha1_pack(obj->sha1, revs->ignore_packed)) - obj->flags |= UNINTERESTING; add_parents_to_list(revs, commit, &list); if (obj->flags & UNINTERESTING) { mark_parents_uninteresting(commit); @@ -1142,17 +1139,18 @@ struct commit *get_revision(struct rev_info *revs) * that we'd otherwise have done in limit_list(). */ if (!revs->limited) { - if ((revs->unpacked && - has_sha1_pack(commit->object.sha1, - revs->ignore_packed)) || - (revs->max_age != -1 && - (commit->date < revs->max_age))) + if (revs->max_age != -1 && + (commit->date < revs->max_age)) continue; add_parents_to_list(revs, commit, &revs->commits); } if (commit->object.flags & SHOWN) continue; + if (revs->unpacked && has_sha1_pack(commit->object.sha1, + revs->ignore_packed)) + continue; + /* We want to show boundary commits only when their * children are shown. When path-limiter is in effect, * rewrite_parents() drops some commits from getting shown, -- cgit v0.10.2-6-g49f6 From 9dad9d2e5b322835c87e1653268d892a95ee0f1f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 30 Oct 2006 18:58:03 -0800 Subject: revision traversal: --unpacked does not limit commit list anymore. This is needed to gain smaller latency back. Signed-off-by: Junio C Hamano diff --git a/revision.c b/revision.c index 89e6c6f..36cdfcd 100644 --- a/revision.c +++ b/revision.c @@ -1007,7 +1007,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch add_pending_object(revs, object, def); } - if (revs->topo_order || revs->unpacked) + if (revs->topo_order) revs->limited = 1; if (revs->prune_data) { -- cgit v0.10.2-6-g49f6 From 861ed12106d7f8e65b90c8a5ed4a026cd71a044d Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 30 Oct 2006 17:34:50 -0500 Subject: Remove unused variable in receive-pack. We aren't using this return code variable for anything so lets just get rid of it to keep this section of code clean. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/receive-pack.c b/receive-pack.c index ea2dbd4..675b1c1 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -273,11 +273,10 @@ static void read_head_info(void) } } -static const char *unpack(int *error_code) +static const char *unpack(void) { int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD); - *error_code = 0; switch (code) { case 0: return NULL; @@ -294,7 +293,6 @@ static const char *unpack(int *error_code) case -ERR_RUN_COMMAND_WAITPID_NOEXIT: return "unpacker died strangely"; default: - *error_code = -code; return "unpacker exited with error code"; } } @@ -345,8 +343,7 @@ int main(int argc, char **argv) read_head_info(); if (commands) { - int code; - const char *unpack_status = unpack(&code); + const char *unpack_status = unpack(); if (!unpack_status) execute_commands(); if (report_status) -- cgit v0.10.2-6-g49f6 From 6fb75bed5c4a6ce099a24e581ea327e9de3ab8b0 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 30 Oct 2006 17:35:18 -0500 Subject: Move deny_non_fast_forwards handling completely into receive-pack. The 'receive.denynonfastforwards' option has nothing to do with the repository format version. Since receive-pack already uses git_config to initialize itself before executing any updates we can use the normal configuration strategy and isolate the receive specific variables away from the core variables. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index d0a1657..f4f7be1 100644 --- a/cache.h +++ b/cache.h @@ -188,7 +188,6 @@ extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int shared_repository; -extern int deny_non_fast_forwards; extern const char *apply_default_whitespace; extern int zlib_compression_level; diff --git a/environment.c b/environment.c index 63b1d15..84d870c 100644 --- a/environment.c +++ b/environment.c @@ -20,7 +20,6 @@ int warn_ambiguous_refs = 1; int repository_format_version; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; -int deny_non_fast_forwards = 0; const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; diff --git a/receive-pack.c b/receive-pack.c index ea2dbd4..f2b1c29 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -9,11 +9,25 @@ static const char receive_pack_usage[] = "git-receive-pack "; static const char *unpacker[] = { "unpack-objects", NULL }; +static int deny_non_fast_forwards = 0; static int report_status; static char capabilities[] = "report-status"; static int capabilities_sent; +static int receive_pack_config(const char *var, const char *value) +{ + git_default_config(var, value); + + if (strcmp(var, "receive.denynonfastforwards") == 0) + { + deny_non_fast_forwards = git_config_bool(var, value); + return 0; + } + + return 0; +} + static int show_ref(const char *path, const unsigned char *sha1) { if (capabilities_sent) @@ -338,6 +352,8 @@ int main(int argc, char **argv) if(!enter_repo(dir, 0)) die("'%s': unable to chdir or not a git archive", dir); + git_config(receive_pack_config); + write_head_info(); /* EOF */ diff --git a/setup.c b/setup.c index 9a46a58..2afdba4 100644 --- a/setup.c +++ b/setup.c @@ -244,8 +244,6 @@ int check_repository_format_version(const char *var, const char *value) repository_format_version = git_config_int(var, value); else if (strcmp(var, "core.sharedrepository") == 0) shared_repository = git_config_perm(var, value); - else if (strcmp(var, "receive.denynonfastforwards") == 0) - deny_non_fast_forwards = git_config_bool(var, value); return 0; } -- cgit v0.10.2-6-g49f6 From 38c5afa87e24756f0465e24c2b7018de62e4bfc1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 30 Oct 2006 08:25:36 -0800 Subject: Allow '-' in config variable names I need this in order to allow aliases of the same form as "ls-tree", "rev-parse" etc, so that I can use [alias] my-cat=--paginate cat-file -p to add a "git my-cat" command. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/config.c b/config.c index e8f0caf..3cae390 100644 --- a/config.c +++ b/config.c @@ -103,6 +103,11 @@ static char *parse_value(void) } } +static inline int iskeychar(int c) +{ + return isalnum(c) || c == '-'; +} + static int get_value(config_fn_t fn, char *name, unsigned int len) { int c; @@ -113,7 +118,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len) c = get_next_char(); if (c == EOF) break; - if (!isalnum(c)) + if (!iskeychar(c)) break; name[len++] = tolower(c); if (len >= MAXNAME) @@ -181,7 +186,7 @@ static int get_base_var(char *name) return baselen; if (isspace(c)) return get_extended_base_var(name, baselen, c); - if (!isalnum(c) && c != '.') + if (!iskeychar(c) && c != '.') return -1; if (baselen > MAXNAME / 2) return -1; @@ -573,7 +578,7 @@ int git_config_set_multivar(const char* key, const char* value, dot = 1; /* Leave the extended basename untouched.. */ if (!dot || i > store.baselen) { - if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) { + if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) { fprintf(stderr, "invalid key: %s\n", key); free(store.key); ret = 1; -- cgit v0.10.2-6-g49f6 From bcc785f611dc6084be75999a3b6bafcc950e21d6 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 30 Oct 2006 08:28:59 -0800 Subject: git push: add verbose flag and allow overriding of default target repository This adds a command line flag "-v" to enable a more verbose mode, and "--repo=" to override the default target repository for "git push" (which otherwise always defaults to "origin"). This, together with the patch to allow dashes in config variable names, allows me to do [alias] push-all = push -v --repo=all in my user-global config file, and then I can (for any project I maintain) add to the project-local config file [remote "all"] url=one.target.repo:/directory url=another.target:/pub/somewhere/else and now "git push-all" just updates all the target repositories, and shows me what it does - regardless of which repo I am in. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/builtin-push.c b/builtin-push.c index f5150ed..3151fb7 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] [...]"; -static int all, tags, force, thin = 1; +static int all, tags, force, thin = 1, verbose; static const char *execute; #define BUF_SIZE (2084) @@ -248,6 +248,8 @@ static int do_push(const char *repo) while (dest_refspec_nr--) argv[dest_argc++] = *dest_refspec++; argv[dest_argc] = NULL; + if (verbose) + fprintf(stderr, "Pushing to %s\n", dest); err = run_command_v(argc, argv); if (!err) continue; @@ -281,6 +283,14 @@ int cmd_push(int argc, const char **argv, const char *prefix) i++; break; } + if (!strcmp(arg, "-v")) { + verbose=1; + continue; + } + if (!strncmp(arg, "--repo=", 7)) { + repo = arg+7; + continue; + } if (!strcmp(arg, "--all")) { all = 1; continue; -- cgit v0.10.2-6-g49f6 From 0d981c67d8bcf4610e165744059c1e9e14609baa Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 31 Oct 2006 01:00:01 -0800 Subject: git-pickaxe: cache one already found path per commit. Depending on how bushy the commit DAG is, this saves calls to the internal diff-tree for fork-point commits. For example, annotating Makefile in the kernel repository saves about a third of such diff-tree calls. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 32dc393..c9405e9 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -141,6 +141,8 @@ static int cmp_suspect(struct origin *a, struct origin *b) return strcmp(a->path, b->path); } +#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) ) + static void sanity_check_refcnt(struct scoreboard *); static void coalesce(struct scoreboard *sb) @@ -213,6 +215,12 @@ static struct origin *find_origin(struct scoreboard *sb, struct diff_options diff_opts; const char *paths[2]; + if (parent->util) { + struct origin *cached = parent->util; + if (!strcmp(cached->path, origin->path)) + return origin_incref(cached); + } + /* See if the origin->path is different between parent * and origin first. Most of the time they are the * same and diff-tree is fairly efficient about this. @@ -259,6 +267,12 @@ static struct origin *find_origin(struct scoreboard *sb, } } diff_flush(&diff_opts); + if (porigin) { + origin_incref(porigin); + if (parent->util) + origin_decref(parent->util); + parent->util = porigin; + } return porigin; } @@ -905,7 +919,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) continue; if (parse_commit(p)) continue; - porigin = find(sb, parent->item, origin); + porigin = find(sb, p, origin); if (!porigin) continue; if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { @@ -1371,8 +1385,10 @@ static void sanity_check_refcnt(struct scoreboard *sb) ent->suspect->refcnt = -ent->suspect->refcnt; } for (ent = sb->ent; ent; ent = ent->next) { - /* then pick each and see if they have the the - * correct refcnt + /* then pick each and see if they have the the correct + * refcnt; note that ->util caching means origin's refcnt + * may well be greater than the number of blame entries + * that use it. */ int found; struct blame_entry *e; @@ -1386,7 +1402,7 @@ static void sanity_check_refcnt(struct scoreboard *sb) continue; found++; } - if (suspect->refcnt != found) + if (suspect->refcnt < found) baa = 1; } if (baa) { -- cgit v0.10.2-6-g49f6 From 62476c8e331a22e224d87c18830913129f5f303b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 31 Oct 2006 14:22:34 -0800 Subject: Introduce a new revision set operator ^! This is a shorthand for " --not ^@", i.e. "include this commit but exclude any of its parents". When a new file $F is introduced by revision $R, this notation can be used to find a copy-and-paste from existing file in the parents of that revision without annotating the ancestry of the lines that were copied from: git pickaxe -f -C $R^! -- $F Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt index 6d22fd9..c08fdec 100644 --- a/Documentation/git-pickaxe.txt +++ b/Documentation/git-pickaxe.txt @@ -111,6 +111,44 @@ The contents of the actual line is output after the above header, prefixed by a TAB. This is to allow adding more header elements later. + +SPECIFIYING RANGES +------------------ + +Unlike `git-blame` and `git-annotate` in older git, the extent +of annotation can be limited to both line ranges and revision +ranges. When you are interested in finding the origin for +ll. 40-60 for file `foo`, you can use `-L` option like this: + + git pickaxe -L 40,60 foo + +When you are not interested in changes older than the version +v2.6.18, or changes older than 3 weeks, you can use revision +range specifiers similar to `git-rev-list`: + + git pickaxe v2.6.18.. -- foo + git pickaxe --since=3.weeks -- foo + +When revision range specifiers are used to limit the annotation, +lines that have not changed since the range boundary (either the +commit v2.6.18 or the most recent commit that is more than 3 +weeks old in the above example) are blamed for that range +boundary commit. + +A particularly useful way is to see if an added file have lines +created by copy-and-paste from existing files. Sometimes this +indicates that the developer was being sloppy and did not +refactor the code properly. You can first find the commit that +introduced the file with: + + git log --diff-filter=A --pretty=short -- foo + +and then annotate the change between the commit and its +parents, using `commit{caret}!` notation: + + git pickaxe -C -C -f $commit^! -- foo + + SEE ALSO -------- gitlink:git-blame[1] diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 5d42570..cda80b1 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -222,14 +222,21 @@ of `r1` and `r2` and is defined as It it the set of commits that are reachable from either one of `r1` or `r2` but not from both. -Here are a few examples: +Two other shorthands for naming a set that is formed by a commit +and its parent commits exists. `r1{caret}@` notation means all +parents of `r1`. `r1{caret}!` includes commit `r1` but excludes +its all parents. + +Here are a handful examples: D A B D D F A B C D F - ^A G B D + ^A G B D ^A F B C F G...I C D F G I - ^B G I C D F G I + ^B G I C D F G I + F^@ A B C + F^! H D F H Author ------ diff --git a/revision.c b/revision.c index f1e0caa..b021d33 100644 --- a/revision.c +++ b/revision.c @@ -660,6 +660,13 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; *dotdot = '^'; } + dotdot = strstr(arg, "^!"); + if (dotdot && !dotdot[2]) { + *dotdot = 0; + if (!add_parents_only(revs, arg, flags ^ UNINTERESTING)) + *dotdot = '^'; + } + local_flags = 0; if (*arg == '^') { local_flags = UNINTERESTING; -- cgit v0.10.2-6-g49f6 From 91c23e48d0666a673dd14760bb00f6d59234d9d9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 31 Oct 2006 15:56:58 -0800 Subject: link_temp_to_file: don't leave the path truncated on adjust_shared_perm failure Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 47e2a29..5fcad28 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1383,8 +1383,10 @@ static int link_temp_to_file(const char *tmpfile, const char *filename) if (dir) { *dir = 0; mkdir(filename, 0777); - if (adjust_shared_perm(filename)) + if (adjust_shared_perm(filename)) { + *dir = '/'; return -2; + } *dir = '/'; if (!link(tmpfile, filename)) return 0; -- cgit v0.10.2-6-g49f6 From ec1e46897329317d7a9996264d7e999f9a68e307 Mon Sep 17 00:00:00 2001 From: Sasha Khapyorsky Date: Thu, 26 Oct 2006 00:50:26 +0200 Subject: git-svnimport: support for partial imports This adds support for partial svn imports. Let's assume that SVN repository layout looks like: $trunk/path/to/our/project $branches/path/to/our/project $tags/path/to/our/project , and we would like to import only tree under this specific 'path/to/our/project' and not whole tree under $trunk, $branches, etc.. Now we will be be able to do it by using '-P path/to/our/project' option with git-svnimport. Signed-off-by: Sasha Khapyorsky Signed-off-by: Junio C Hamano diff --git a/git-svnimport.perl b/git-svnimport.perl index f6eff8e..cbaa8ab 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -31,7 +31,7 @@ $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, - $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F); + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,$opt_P); sub usage() { print STDERR < Date: Mon, 30 Oct 2006 12:37:54 -0800 Subject: gitweb: esc_html() author in blame Blame fails for example on block/ll_rw_blk.c at v2.6.19-rc3. Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ec46b80..bfadbe2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2659,7 +2659,7 @@ HTML print "\n"; if ($group_size) { print " 1); print ">"; print $cgi->a({-href => href(action=>"commit", -- cgit v0.10.2-6-g49f6 From 45bd0c808d2a89254ee50807a99b7cf1147aa6d7 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 30 Oct 2006 22:29:06 +0100 Subject: gitweb: Secure against commit-ish/tree-ish with the same name as path Add "--" after or argument to clearly mark it as or and not pathspec, securing against refs with the same names as files or directories in [live] repository. Some wrapping to reduce line length as well. [jc: with "oops, ls-tree does not want --" fix-up manually applied.] Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index bfadbe2..6035980 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1111,7 +1111,9 @@ sub parse_commit { @commit_lines = @$commit_text; } else { local $/ = "\0"; - open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id + open my $fd, "-|", git_cmd(), "rev-list", + "--header", "--parents", "--max-count=1", + $commit_id, "--" or return; @commit_lines = split '\n', <$fd>; close $fd or return; @@ -2529,7 +2531,7 @@ sub git_summary { } open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", - git_get_head_hash($project) + git_get_head_hash($project), "--" or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -3072,7 +3074,7 @@ sub git_log { my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash + open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--" or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -3130,7 +3132,7 @@ sub git_commit { $parent = "--root"; } open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", - @diff_opts, $parent, $hash + @diff_opts, $parent, $hash, "--" or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed"); @@ -3235,7 +3237,8 @@ sub git_blobdiff { if (defined $hash_base && defined $hash_parent_base) { if (defined $file_name) { # read raw output - open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + $hash_parent_base, $hash_base, "--", $file_name or die_error(undef, "Open git-diff-tree failed"); @difftree = map { chomp; $_ } <$fd>; @@ -3249,7 +3252,8 @@ sub git_blobdiff { # try to find filename from $hash # read filtered raw output - open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + $hash_parent_base, $hash_base, "--" or die_error(undef, "Open git-diff-tree failed"); @difftree = # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' @@ -3319,7 +3323,8 @@ sub git_blobdiff { } # open patch output - open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts, $hash_parent, $hash + open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts, + $hash_parent, $hash, "--" or die_error(undef, "Open git-diff failed"); } else { die_error('404 Not Found', "Missing one of the blob diff parameters") @@ -3450,8 +3455,8 @@ sub git_commitdiff { my @difftree; if ($format eq 'html') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, - "--no-commit-id", - "--patch-with-raw", "--full-index", $hash_parent, $hash + "--no-commit-id", "--patch-with-raw", "--full-index", + $hash_parent, $hash, "--" or die_error(undef, "Open git-diff-tree failed"); while (chomp(my $line = <$fd>)) { @@ -3462,7 +3467,7 @@ sub git_commitdiff { } elsif ($format eq 'plain') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, - '-p', $hash_parent, $hash + '-p', $hash_parent, $hash, "--" or die_error(undef, "Open git-diff-tree failed"); } else { @@ -3639,7 +3644,9 @@ sub git_search { my $alternate = 1; if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') { $/ = "\0"; - open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next; + open my $fd, "-|", git_cmd(), "rev-list", + "--header", "--parents", $hash, "--" + or next; while (my $commit_text = <$fd>) { if (!grep m/$searchtext/i, $commit_text) { next; @@ -3785,7 +3792,7 @@ sub git_shortlog { my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash + open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--" or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -3813,7 +3820,8 @@ sub git_shortlog { sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", git_get_head_hash($project) + open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", + git_get_head_hash($project), "--" or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-rev-list failed"); @@ -3837,7 +3845,7 @@ XML } my %cd = parse_date($co{'committer_epoch'}); open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, - $co{'parent'}, $co{'id'} + $co{'parent'}, $co{'id'}, "--" or next; my @difftree = map { chomp; $_ } <$fd>; close $fd -- cgit v0.10.2-6-g49f6 From 8b4b94cc7998d6312025fe3350be57c89b803f9c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 30 Oct 2006 22:25:11 +0100 Subject: gitweb: Use 's' regexp modifier to secure against filenames with LF Use 's' (treat string as single line) regexp modifier in git_get_hash_by_path (against future changes, probably unnecessary) and in parse_ls_tree_line (when called with '-z'=>1 option) to secure against filenames containing newline. [jc: the hunk on git_get_hash_by_path was unneeded, and I noticed the regexp was doing unnecessary capture, so fixed it up while I was at it.] Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6035980..bf5f829 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -860,7 +860,7 @@ sub git_get_hash_by_path { close $fd or return undef; #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/; if (defined $type && $type ne $2) { # type doesn't match return undef; @@ -1277,7 +1277,7 @@ sub parse_ls_tree_line ($;%) { my %res; #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; $res{'mode'} = $1; $res{'type'} = $2; -- cgit v0.10.2-6-g49f6 From 1da1b3a3e06fdcbbd0b154a6930fc0261a5ee866 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 1 Nov 2006 12:53:13 -0800 Subject: branch: work in subdirectories. Noticed by Andy Whitcroft Signed-off-by: Junio C Hamano diff --git a/git.c b/git.c index d6c2d2d..d2460c8 100644 --- a/git.c +++ b/git.c @@ -222,7 +222,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "annotate", cmd_annotate, }, { "apply", cmd_apply }, { "archive", cmd_archive }, - { "branch", cmd_branch }, + { "branch", cmd_branch, RUN_SETUP }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, -- cgit v0.10.2-6-g49f6 From e23ed9a8b4769fcba0944cf121d366ec7db7fe3b Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 1 Nov 2006 17:34:47 -0500 Subject: pack-objects doesn't create random pack names Documentation for pack-objects seems to be out of date in this regard. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index f52e8fa..944c9e1 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -47,9 +47,8 @@ base-name:: to determine the name of the created file. When this option is used, the two files are written in -.{pack,idx} files. is a hash - of object names (currently in random order so it does - not have any useful meaning) to make the resulting - filename reasonably unique, and written to the standard + of the sorted object names to make the resulting filename + based on the pack content, and written to the standard output of the command. --stdout:: -- cgit v0.10.2-6-g49f6 From fa438a2eb116c71eaebecdc104a1af01ea83b9fa Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 31 Oct 2006 16:58:32 -0500 Subject: make git-push a bit more verbose Currently git-push displays progress status for the local packing of objects to send, but nothing once it starts to push it over the connection. Having progress status in that later case is especially nice when pushing lots of objects over a slow network link. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 9bd1e39..5ebe34e 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty] - [--local] [--incremental] [--window=N] [--depth=N] + [--local] [--incremental] [--window=N] [--depth=N] [--all-progress] [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 41e1e74..270bcbd 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -15,7 +15,7 @@ #include #include -static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] "); - else { + do_progress >>= 1; + } + else f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "pack"); - do_progress = progress; - } if (do_progress) fprintf(stderr, "Writing %d objects.\n", nr_result); @@ -1524,6 +1524,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) progress = 1; continue; } + if (!strcmp("--all-progress", arg)) { + progress = 2; + continue; + } if (!strcmp("--incremental", arg)) { incremental = 1; continue; @@ -1641,7 +1645,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) else { if (nr_result) prepare_pack(window, depth); - if (progress && pack_to_stdout) { + if (progress == 1 && pack_to_stdout) { /* the other end usually displays progress itself */ struct itimerval v = {{0,},}; setitimer(ITIMER_REAL, &v, NULL); diff --git a/send-pack.c b/send-pack.c index fbd792c..4476666 100644 --- a/send-pack.c +++ b/send-pack.c @@ -29,6 +29,7 @@ static void exec_pack_objects(void) { static const char *args[] = { "pack-objects", + "--all-progress", "--stdout", NULL }; -- cgit v0.10.2-6-g49f6 From 20239bae943733d766e6475cf0916966c868246b Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Nov 2006 02:22:49 -0500 Subject: git-pickaxe: work properly in a subdirectory. We forgot to add prefix to the given path. [jc: interestingly enough, Jeff King had the same idea after I pushed mine out to "pu", and his patch was cleaner, so I dropped mine.] Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index c9405e9..f6e861a 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -1428,6 +1428,13 @@ static unsigned parse_score(const char *arg) return score; } +static const char *add_prefix(const char *prefix, const char *path) +{ + if (!prefix || !prefix[0]) + return path; + return prefix_path(prefix, strlen(prefix), path); +} + int cmd_pickaxe(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1548,7 +1555,7 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) /* (1) */ if (argc <= i) usage(pickaxe_usage); - path = argv[i]; + path = add_prefix(prefix, argv[i]); if (i + 1 == argc - 1) { if (unk != 1) usage(pickaxe_usage); @@ -1566,13 +1573,13 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) if (seen_dashdash) { if (seen_dashdash + 1 != argc - 1) usage(pickaxe_usage); - path = argv[seen_dashdash + 1]; + path = add_prefix(prefix, argv[seen_dashdash + 1]); for (j = i; j < seen_dashdash; j++) argv[unk++] = argv[j]; } else { /* (3) */ - path = argv[i]; + path = add_prefix(prefix, argv[i]); if (i + 1 == argc - 1) { final_commit_name = argv[i + 1]; @@ -1580,7 +1587,7 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) * old-style */ if (unk == 1 && !has_path_in_work_tree(path)) { - path = argv[i + 1]; + path = add_prefix(prefix, argv[i + 1]); final_commit_name = argv[i]; } } -- cgit v0.10.2-6-g49f6 From 5c1e235f0f2a4586ca46a5503f6283db0ac1b9ac Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Thu, 2 Nov 2006 11:10:52 +0000 Subject: Remove uneccessarily similar printf() from print_ref_list() in builtin-branch Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/builtin-branch.c b/builtin-branch.c index e028a53..368b68e 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -103,6 +103,7 @@ static int ref_cmp(const void *r1, const void *r2) static void print_ref_list(int remote_only) { int i; + char c; if (remote_only) for_each_remote_ref(append_ref, NULL); @@ -112,10 +113,11 @@ static void print_ref_list(int remote_only) qsort(ref_list, ref_index, sizeof(char *), ref_cmp); for (i = 0; i < ref_index; i++) { + c = ' '; if (!strcmp(ref_list[i], head)) - printf("* %s\n", ref_list[i]); - else - printf(" %s\n", ref_list[i]); + c = '*'; + + printf("%c %s\n", c, ref_list[i]); } } -- cgit v0.10.2-6-g49f6 From 866cae0db4af936ec6f9eb6362e50db2a1a2f792 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 2 Nov 2006 18:02:17 -0800 Subject: link_temp_to_file: call adjust_shared_perm() only when we created the directory diff --git a/sha1_file.c b/sha1_file.c index 5fcad28..27eb14b 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1382,8 +1382,7 @@ static int link_temp_to_file(const char *tmpfile, const char *filename) dir = strrchr(filename, '/'); if (dir) { *dir = 0; - mkdir(filename, 0777); - if (adjust_shared_perm(filename)) { + if (!mkdir(filename, 0777) && adjust_shared_perm(filename)) { *dir = '/'; return -2; } -- cgit v0.10.2-6-g49f6 From 44b27ec9604b0310c4d24a283319b933a86da67d Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Thu, 2 Nov 2006 12:12:44 +0100 Subject: Minor grammar fixes for git-diff-index.txt "what you are going to commit is" doesn't need the "is" and does need a comma. "can trivially see" is an unecessary split infinitive and "easily" is a more appropriate adverb. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index 9cd43f1..2df581c 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -54,7 +54,7 @@ If '--cached' is specified, it allows you to ask: For example, let's say that you have worked on your working directory, updated some files in the index and are ready to commit. You want to see exactly -*what* you are going to commit is without having to write a new tree +*what* you are going to commit, without having to write a new tree object and compare it that way, and to do that, you just do git-diff-index --cached HEAD @@ -68,7 +68,7 @@ matches my working directory. But doing a "git-diff-index" does: -100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 commit.c +100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 git-commit.c -You can trivially see that the above is a rename. +You can see easily that the above is a rename. In fact, "git-diff-index --cached" *should* always be entirely equivalent to actually doing a "git-write-tree" and comparing that. Except this one is much -- cgit v0.10.2-6-g49f6 From ba158a32b9e237912ebe7dfe7bd56e2c531cb062 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Thu, 2 Nov 2006 12:11:56 +0100 Subject: git-clone documentation didn't mention --origin as equivalent of -o Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index f973c64..8606047 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -75,6 +75,7 @@ OPTIONS this option is used, neither the `origin` branch nor the default `remotes/origin` file is created. +--origin :: -o :: Instead of using the branch name 'origin' to keep track of the upstream repository, use instead. Note -- cgit v0.10.2-6-g49f6 From ca8e2d86c44e5cea0def1d01edfb4e6aaf642dee Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Thu, 2 Nov 2006 12:13:32 +0100 Subject: pack-refs: Store the full name of the ref even when packing only tags. Using for_each_tag_ref() to enumerate tags is wrong since it removes the refs/tags/ prefix, we need to always use for_each_ref() and filter out non-tag references in the callback. Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 1087657..042d271 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -12,6 +12,7 @@ struct ref_to_prune { struct pack_refs_cb_data { int prune; + int all; struct ref_to_prune *ref_to_prune; FILE *refs_file; }; @@ -29,6 +30,8 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, { struct pack_refs_cb_data *cb = cb_data; + if (!cb->all && strncmp(path, "refs/tags/", 10)) + return 0; /* Do not pack the symbolic refs */ if (!(flags & REF_ISSYMREF)) fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); @@ -68,7 +71,6 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) { int fd, i; struct pack_refs_cb_data cbdata; - int (*iterate_ref)(each_ref_fn, void *) = for_each_tag_ref; memset(&cbdata, 0, sizeof(cbdata)); @@ -79,7 +81,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--all")) { - iterate_ref = for_each_ref; + cbdata.all = 1; continue; } /* perhaps other parameters later... */ @@ -93,7 +95,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) if (!cbdata.refs_file) die("unable to create ref-pack file structure (%s)", strerror(errno)); - iterate_ref(handle_one_ref, &cbdata); + for_each_ref(handle_one_ref, &cbdata); fflush(cbdata.refs_file); fsync(fd); fclose(cbdata.refs_file); -- cgit v0.10.2-6-g49f6 From 34eb33407d4d7eef6d7ddcd6e51525018ef9edf7 Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 2 Nov 2006 10:44:20 -0500 Subject: Add --global option to git-repo-config. Allow user to set variables in global ~/.gitconfig file using command line. Signed-off-by: Sean Estabrooks Signed-off-by: Junio C Hamano diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 8a1ab61..8199615 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -3,19 +3,19 @@ git-repo-config(1) NAME ---- -git-repo-config - Get and set options in .git/config +git-repo-config - Get and set repository or global options. SYNOPSIS -------- [verse] -'git-repo-config' [type] name [value [value_regex]] -'git-repo-config' [type] --replace-all name [value [value_regex]] -'git-repo-config' [type] --get name [value_regex] -'git-repo-config' [type] --get-all name [value_regex] -'git-repo-config' [type] --unset name [value_regex] -'git-repo-config' [type] --unset-all name [value_regex] -'git-repo-config' -l | --list +'git-repo-config' [--global] [type] name [value [value_regex]] +'git-repo-config' [--global] [type] --replace-all name [value [value_regex]] +'git-repo-config' [--global] [type] --get name [value_regex] +'git-repo-config' [--global] [type] --get-all name [value_regex] +'git-repo-config' [--global] [type] --unset name [value_regex] +'git-repo-config' [--global] [type] --unset-all name [value_regex] +'git-repo-config' [--global] -l | --list DESCRIPTION ----------- @@ -41,8 +41,9 @@ This command will fail if: . Can not write to .git/config, . no section was provided, . the section or key is invalid, -. you try to unset an option which does not exist, or -. you try to unset/set an option for which multiple lines match. +. you try to unset an option which does not exist, +. you try to unset/set an option for which multiple lines match, or +. you use --global option without $HOME being properly set. OPTIONS @@ -64,14 +65,17 @@ OPTIONS --get-regexp:: Like --get-all, but interprets the name as a regular expression. +--global:: + Use global ~/.gitconfig file rather than the repository .git/config. + --unset:: - Remove the line matching the key from .git/config. + Remove the line matching the key from config file. --unset-all:: - Remove all matching lines from .git/config. + Remove all matching lines from config file. -l, --list:: - List all variables set in .git/config. + List all variables set in config file. ENVIRONMENT @@ -79,6 +83,7 @@ ENVIRONMENT GIT_CONFIG:: Take the configuration from the given file instead of .git/config. + Using the "--global" option forces this to ~/.gitconfig. GIT_CONFIG_LOCAL:: Currently the same as $GIT_CONFIG; when Git will support global diff --git a/builtin-repo-config.c b/builtin-repo-config.c index f60cee1..7b6e572 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -3,7 +3,7 @@ #include static const char git_config_set_usage[] = -"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; +"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; static char *key; static regex_t *key_regexp; @@ -139,7 +139,16 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix) type = T_BOOL; else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) return git_config(show_all_config); - else + else if (!strcmp(argv[1], "--global")) { + char *home = getenv("HOME"); + if (home) { + char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); + setenv("GIT_CONFIG", user_config, 1); + free(user_config); + } else { + die("$HOME not set"); + } + } else break; argc--; argv++; -- cgit v0.10.2-6-g49f6 From 3175aa1ec28c8fb119058111a2f629425ef1aab0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 28 Oct 2006 13:33:46 -0700 Subject: for-each-ref: "creator" and "creatordate" fields This adds "creator" (which is parallel to "tagger" or "committer") and "creatordate" (corresponds to "taggerdate" and "committerdate"). As other "date" fields, "creatordate" sorts numerically and displays human readably. This allows for example for sorting together heavyweigth and lightweight tags. Signed-off-by: Junio C Hamano Acked-by: Jakub Narebski diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 93d3d7e..173bf38 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -59,6 +59,8 @@ static struct { { "taggername" }, { "taggeremail" }, { "taggerdate", FIELD_TIME }, + { "creator" }, + { "creatordate", FIELD_TIME }, { "subject" }, { "body" }, { "contents" }, @@ -401,6 +403,29 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru else if (!strcmp(name + wholen, "date")) grab_date(wholine, v); } + + /* For a tag or a commit object, if "creator" or "creatordate" is + * requested, do something special. + */ + if (strcmp(who, "tagger") && strcmp(who, "committer")) + return; /* "author" for commit object is not wanted */ + if (!wholine) + wholine = find_wholine(who, wholen, buf, sz); + if (!wholine) + return; + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + + if (!strcmp(name, "creatordate")) + grab_date(wholine, v); + else if (!strcmp(name, "creator")) + v->s = copy_line(wholine); + } } static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) -- cgit v0.10.2-6-g49f6 From cd1464083cd9e61a55148c21fd9b8eb3bf76d190 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 2 Nov 2006 20:23:11 +0100 Subject: gitweb: Use git-for-each-ref to generate list of heads and/or tags Add two subroutines: git_get_heads_list and git_get_refs_list, which fill out needed parts of refs info (heads and tags respectively) info using single call to git-for-each-ref, instead of using git-peek-remote to get list of references and using parse_ref for each ref to get ref info, which in turn uses at least one call of git command. Replace call to git_get_refs_list in git_summary by call to git_get_references, git_get_heads_list and git_get_tags_list (simplifying this subroutine a bit). Use git_get_heads_list in git_heads and git_get_tags_list in git_tags. Modify git_tags_body slightly to accept output from git_get_tags_list. Remove no longer used, and a bit hackish, git_get_refs_list. parse_ref is no longer used, but is left for now. Generating "summary" and "tags" views should be much faster for projects which have large number of tags. CHANGES IN OUTPUT: Before, if ref in refs/tags was tag pointing to commit we used committer epoch as epoch for ref, and used tagger epoch as epoch only for tag pointing to object of other type. If ref in refs/tags was commit, we used committer epoch as epoch for ref (see parse_ref; we sorted in gitweb by 'epoch' field). Currently we use committer epoch for refs pointing to commit objects, and tagger epoch for refs pointing to tag object, even if tag points to commit. Simple ab benchmark before and after this patch for my git.git repository (git/jnareb-git.git) with some heads and tags added as compared to git.git repository, shows around 2.4-3.0 times speedup for "summary" and "tags" views: summary 3134 +/- 24.2 ms --> 1081 +/- 30.2 ms tags 2886 +/- 18.9 ms --> 1196 +/- 15.6 ms Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index bf5f829..7710cc2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1294,47 +1294,88 @@ sub parse_ls_tree_line ($;%) { ## ...................................................................... ## parse to array of hashes functions -sub git_get_refs_list { - my $type = shift || ""; - my %refs; - my @reflist; - - my @refs; - open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" +sub git_get_heads_list { + my $limit = shift; + my @headslist; + + open my $fd, '-|', git_cmd(), 'for-each-ref', + ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate', + '--format=%(objectname) %(refname) %(subject)%00%(committer)', + 'refs/heads' or return; while (my $line = <$fd>) { - chomp $line; - if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) { - if (defined $refs{$1}) { - push @{$refs{$1}}, $2; - } else { - $refs{$1} = [ $2 ]; - } + my %ref_item; - if (! $4) { # unpeeled, direct reference - push @refs, { hash => $1, name => $3 }; # without type - } elsif ($3 eq $refs[-1]{'name'}) { - # most likely a tag is followed by its peeled - # (deref) one, and when that happens we know the - # previous one was of type 'tag'. - $refs[-1]{'type'} = "tag"; - } + chomp $line; + my ($refinfo, $committerinfo) = split(/\0/, $line); + my ($hash, $name, $title) = split(' ', $refinfo, 3); + my ($committer, $epoch, $tz) = + ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/); + $name =~ s!^refs/heads/!!; + + $ref_item{'name'} = $name; + $ref_item{'id'} = $hash; + $ref_item{'title'} = $title || '(no commit message)'; + $ref_item{'epoch'} = $epoch; + if ($epoch) { + $ref_item{'age'} = age_string(time - $ref_item{'epoch'}); + } else { + $ref_item{'age'} = "unknown"; } + + push @headslist, \%ref_item; } close $fd; - foreach my $ref (@refs) { - my $ref_file = $ref->{'name'}; - my $ref_id = $ref->{'hash'}; + return wantarray ? @headslist : \@headslist; +} + +sub git_get_tags_list { + my $limit = shift; + my @tagslist; - my $type = $ref->{'type'} || git_get_type($ref_id) || next; - my %ref_item = parse_ref($ref_file, $ref_id, $type); + open my $fd, '-|', git_cmd(), 'for-each-ref', + ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate', + '--format=%(objectname) %(objecttype) %(refname) '. + '%(*objectname) %(*objecttype) %(subject)%00%(creator)', + 'refs/tags' + or return; + while (my $line = <$fd>) { + my %ref_item; - push @reflist, \%ref_item; + chomp $line; + my ($refinfo, $creatorinfo) = split(/\0/, $line); + my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6); + my ($creator, $epoch, $tz) = + ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/); + $name =~ s!^refs/tags/!!; + + $ref_item{'type'} = $type; + $ref_item{'id'} = $id; + $ref_item{'name'} = $name; + if ($type eq "tag") { + $ref_item{'subject'} = $title; + $ref_item{'reftype'} = $reftype; + $ref_item{'refid'} = $refid; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'refid'} = $id; + } + + if ($type eq "tag" || $type eq "commit") { + $ref_item{'epoch'} = $epoch; + if ($epoch) { + $ref_item{'age'} = age_string(time - $ref_item{'epoch'}); + } else { + $ref_item{'age'} = "unknown"; + } + } + + push @tagslist, \%ref_item; } - # sort refs by age - @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return (\@reflist, \%refs); + close $fd; + + return wantarray ? @tagslist : \@tagslist; } ## ---------------------------------------------------------------------- @@ -2264,8 +2305,7 @@ sub git_tags_body { for (my $i = $from; $i <= $to; $i++) { my $entry = $taglist->[$i]; my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; + my $comment = $tag{'subject'}; my $comment_short; if (defined $comment) { $comment_short = chop_str($comment, 30, 5); @@ -2298,7 +2338,7 @@ sub git_tags_body { $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'}); if ($tag{'reftype'} eq "commit") { print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . - " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log"); + " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log"); } elsif ($tag{'reftype'} eq "blob") { print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw"); } @@ -2323,23 +2363,23 @@ sub git_heads_body { my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $headlist->[$i]; - my %tag = %$entry; - my $curr = $tag{'id'} eq $head; + my %ref = %$entry; + my $curr = $ref{'id'} eq $head; if ($alternate) { print "\n"; } else { print "\n"; } $alternate ^= 1; - print "$tag{'age'}\n" . - ($tag{'id'} eq $head ? "" : "") . - $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}), - -class => "list name"},esc_html($tag{'name'})) . + print "$ref{'age'}\n" . + ($curr ? "" : "") . + $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}), + -class => "list name"},esc_html($ref{'name'})) . "\n" . "" . - $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . - $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") . + $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " . + $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") . "\n" . ""; } @@ -2489,18 +2529,9 @@ sub git_summary { my $owner = git_get_project_owner($project); - my ($reflist, $refs) = git_get_refs_list(); - - my @taglist; - my @headlist; - foreach my $ref (@$reflist) { - if ($ref->{'name'} =~ s!^heads/!!) { - push @headlist, $ref; - } else { - $ref->{'name'} =~ s!^tags/!!; - push @taglist, $ref; - } - } + my $refs = git_get_references(); + my @taglist = git_get_tags_list(15); + my @headlist = git_get_heads_list(15); git_header_html(); git_print_page_nav('summary','', $head); @@ -2792,9 +2823,9 @@ sub git_tags { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my ($taglist) = git_get_refs_list("tags"); - if (@$taglist) { - git_tags_body($taglist); + my @tagslist = git_get_tags_list(); + if (@tagslist) { + git_tags_body(\@tagslist); } git_footer_html(); } @@ -2805,9 +2836,9 @@ sub git_heads { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my ($headlist) = git_get_refs_list("heads"); - if (@$headlist) { - git_heads_body($headlist, $head); + my @headslist = git_get_heads_list(); + if (@headslist) { + git_heads_body(\@headslist, $head); } git_footer_html(); } -- cgit v0.10.2-6-g49f6 From 241cc599b342663877a8f468513c77518c94d96f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 31 Oct 2006 17:36:27 +0100 Subject: gitweb: Output also empty patches in "commitdiff" view Remove skipping over empty patches (i.e. patches which consist solely of extended headers) in git_patchset_body, and add links to those header-only patches in git_difftree_body (but not generate blobdiff links when there were no change in file contents). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7710cc2..dfb15c1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1986,19 +1986,19 @@ sub git_difftree_body { print "\n"; print "$mode_chnge\n"; print ""; - if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - if ($action eq 'commitdiff') { - # link to patch - $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - } else { - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, - "diff"); - } - print " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print $cgi->a({-href => "#patch$patchno"}, "patch") . + " | "; + } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + # "commit" view and modified file (not onlu mode changed) + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, + "diff") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'})}, @@ -2029,19 +2029,19 @@ sub git_difftree_body { -class => "list"}, esc_html($diff{'from_file'})) . " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]\n" . ""; - if ($diff{'to_id'} ne $diff{'from_id'}) { - if ($action eq 'commitdiff') { - # link to patch - $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - } else { - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, - "diff"); - } - print " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print $cgi->a({-href => "#patch$patchno"}, "patch") . + " | "; + } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + # "commit" view and modified file (not only pure rename or copy) + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'from_file'})}, @@ -2092,13 +2092,6 @@ sub git_patchset_body { } $patch_idx++; - # for now, no extended header, hence we skip empty patches - # companion to next LINE if $in_header; - if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change - $in_header = 1; - next LINE; - } - if ($diffinfo->{'status'} eq "A") { # added print "
" . file_type($diffinfo->{'to_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, -- cgit v0.10.2-6-g49f6 From 6255ef08ae746bdad34851297a989ad6f36f6a21 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 1 Nov 2006 14:33:21 +0100 Subject: gitweb: Better support for non-CSS aware web browsers Add option to replace SPC (' ') with hard (non-breakable) space HTML entity ' ' in esc_html subroutine. Replace ' ' with ' ' for the code/diff display part in git_blob and git_patchset_body; this is to be able to view code and diffs in web browsers which doesn't understand "white-space: pre;" CSS declaration. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index dfb15c1..3dfa59f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -554,12 +554,17 @@ sub esc_url { } # replace invalid utf8 character with SUBSTITUTION sequence -sub esc_html { +sub esc_html ($;%) { my $str = shift; + my %opts = @_; + $str = to_utf8($str); $str = escapeHTML($str); $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) + if ($opts{'-nbsp'}) { + $str =~ s/ / /g; + } return $str; } @@ -784,7 +789,7 @@ sub format_diff_line { $diff_class = " incomplete"; } $line = untabify($line); - return "
" . esc_html($line) . "
\n"; + return "
" . esc_html($line, -nbsp=>1) . "
\n"; } ## ---------------------------------------------------------------------- @@ -2944,7 +2949,7 @@ sub git_blob { $nr++; $line = untabify($line); printf "
%4i %s
\n", - $nr, $nr, $nr, esc_html($line); + $nr, $nr, $nr, esc_html($line, -nbsp=>1); } close $fd or print "Reading blob failed.\n"; -- cgit v0.10.2-6-g49f6 From bed006fbddf919eed81cf62954e0332a395bf035 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 1 Nov 2006 17:06:20 -0500 Subject: Allow pack header preprocessing before unpack-objects/index-pack. Some applications which invoke unpack-objects or index-pack --stdin may want to examine the pack header to determine the number of objects contained in the pack and use that value to determine which executable to invoke to handle the rest of the pack stream. However if the caller consumes the pack header from the input stream then its no longer available for unpack-objects or index-pack --stdin, both of which need the version and object count to process the stream. This change introduces --pack_header=ver,cnt as a command line option that the caller can supply to indicate it has already consumed the pack header and what version and object count were found in that header. As this option is only meant for low level applications such as receive-pack we are not documenting it at this time. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 74a90c1..e6d7574 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -371,6 +371,21 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) recover = 1; continue; } + if (!strncmp(arg, "--pack_header=", 14)) { + struct pack_header *hdr; + char *c; + + hdr = (struct pack_header *)buffer; + hdr->hdr_signature = htonl(PACK_SIGNATURE); + hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10)); + if (*c != ',') + die("bad %s", arg); + hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10)); + if (*c) + die("bad %s", arg); + len = sizeof(*hdr); + continue; + } usage(unpack_usage); } diff --git a/index-pack.c b/index-pack.c index b37dd78..a3b55f9 100644 --- a/index-pack.c +++ b/index-pack.c @@ -841,6 +841,19 @@ int main(int argc, char **argv) keep_msg = ""; } else if (!strncmp(arg, "--keep=", 7)) { keep_msg = arg + 7; + } else if (!strncmp(arg, "--pack_header=", 14)) { + struct pack_header *hdr; + char *c; + + hdr = (struct pack_header *)input_buffer; + hdr->hdr_signature = htonl(PACK_SIGNATURE); + hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10)); + if (*c != ',') + die("bad %s", arg); + hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10)); + if (*c) + die("bad %s", arg); + input_len = sizeof(*hdr); } else if (!strcmp(arg, "-v")) { verbose = 1; } else if (!strcmp(arg, "-o")) { -- cgit v0.10.2-6-g49f6 From fc04c412d8d2412e97bb2a664a1746e333dfd9ae Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 1 Nov 2006 17:06:21 -0500 Subject: Teach receive-pack how to keep pack files based on object count. Since keeping a pushed pack or exploding it into loose objects should be a local repository decision this teaches receive-pack to decide if it should call unpack-objects or index-pack --stdin --fix-thin based on the setting of receive.unpackLimit and the number of objects contained in the received pack. If the number of objects (hdr_entries) in the received pack is below the value of receive.unpackLimit (which is 5000 by default) then we unpack-objects as we have in the past. If the hdr_entries >= receive.unpackLimit then we call index-pack and ask it to include our pid and hostname in the .keep file to make it easier to identify why a given pack has been kept in the repository. Currently this leaves every received pack as a kept pack. We really don't want that as received packs will tend to be small. Instead we want to delete the .keep file automatically after all refs have been updated. That is being left as room for future improvement. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index d9e73da..9d3c71c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -301,7 +301,16 @@ imap:: The configuration variables in the 'imap' section are described in gitlink:git-imap-send[1]. -receive.denyNonFastforwads:: +receive.unpackLimit:: + If the number of objects received in a push is below this + limit then the objects will be unpacked into loose object + files. However if the number of received objects equals or + exceeds this limit then the received pack will be stored as + a pack, after adding any missing delta bases. Storing the + pack from a push can make the push operation complete faster, + especially on slow filesystems. + +receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is not a fast forward. Use this to prevent such an update via a push, even if that push is forced. This configuration variable is diff --git a/cache.h b/cache.h index e997a85..6cb7e1d 100644 --- a/cache.h +++ b/cache.h @@ -376,6 +376,7 @@ extern struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_path); extern void prepare_packed_git(void); +extern void reprepare_packed_git(void); extern void install_packed_git(struct packed_git *pack); extern struct packed_git *find_sha1_pack(const unsigned char *sha1, diff --git a/receive-pack.c b/receive-pack.c index ed08660..b8d1270 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "pack.h" #include "refs.h" #include "pkt-line.h" #include "run-command.h" @@ -7,9 +8,8 @@ static const char receive_pack_usage[] = "git-receive-pack "; -static const char *unpacker[] = { "unpack-objects", NULL }; - static int deny_non_fast_forwards = 0; +static int unpack_limit = 5000; static int report_status; static char capabilities[] = "report-status"; @@ -25,6 +25,12 @@ static int receive_pack_config(const char *var, const char *value) return 0; } + if (strcmp(var, "receive.unpacklimit") == 0) + { + unpack_limit = git_config_int(var, value); + return 0; + } + return 0; } @@ -227,27 +233,81 @@ static void read_head_info(void) } } +static const char *parse_pack_header(struct pack_header *hdr) +{ + char *c = (char*)hdr; + ssize_t remaining = sizeof(struct pack_header); + do { + ssize_t r = xread(0, c, remaining); + if (r <= 0) + return "eof before pack header was fully read"; + remaining -= r; + c += r; + } while (remaining > 0); + if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) + return "protocol error (pack signature mismatch detected)"; + if (!pack_version_ok(hdr->hdr_version)) + return "protocol error (pack version unsupported)"; + return NULL; +} + static const char *unpack(void) { - int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD); + struct pack_header hdr; + const char *hdr_err; + char hdr_arg[38]; + int code; + + hdr_err = parse_pack_header(&hdr); + if (hdr_err) + return hdr_err; + snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u", + ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries)); + + if (ntohl(hdr.hdr_entries) < unpack_limit) { + const char *unpacker[3]; + unpacker[0] = "unpack-objects"; + unpacker[1] = hdr_arg; + unpacker[2] = NULL; + code = run_command_v_opt(1, unpacker, RUN_GIT_CMD); + } else { + const char *keeper[6]; + char my_host[255], keep_arg[128 + 255]; + + if (gethostname(my_host, sizeof(my_host))) + strcpy(my_host, "localhost"); + snprintf(keep_arg, sizeof(keep_arg), + "--keep=receive-pack %i on %s", + getpid(), my_host); + + keeper[0] = "index-pack"; + keeper[1] = "--stdin"; + keeper[2] = "--fix-thin"; + keeper[3] = hdr_arg; + keeper[4] = keep_arg; + keeper[5] = NULL; + code = run_command_v_opt(1, keeper, RUN_GIT_CMD); + if (!code) + reprepare_packed_git(); + } switch (code) { - case 0: - return NULL; - case -ERR_RUN_COMMAND_FORK: - return "unpack fork failed"; - case -ERR_RUN_COMMAND_EXEC: - return "unpack execute failed"; - case -ERR_RUN_COMMAND_WAITPID: - return "waitpid failed"; - case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: - return "waitpid is confused"; - case -ERR_RUN_COMMAND_WAITPID_SIGNAL: - return "unpacker died of signal"; - case -ERR_RUN_COMMAND_WAITPID_NOEXIT: - return "unpacker died strangely"; - default: - return "unpacker exited with error code"; + case 0: + return NULL; + case -ERR_RUN_COMMAND_FORK: + return "unpack fork failed"; + case -ERR_RUN_COMMAND_EXEC: + return "unpack execute failed"; + case -ERR_RUN_COMMAND_WAITPID: + return "waitpid failed"; + case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: + return "waitpid is confused"; + case -ERR_RUN_COMMAND_WAITPID_SIGNAL: + return "unpacker died of signal"; + case -ERR_RUN_COMMAND_WAITPID_NOEXIT: + return "unpacker died strangely"; + default: + return "unpacker exited with error code"; } } diff --git a/sha1_file.c b/sha1_file.c index 2aa944a..6ea59b5 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -663,7 +663,7 @@ void prepare_packed_git(void) prepare_packed_git_run_once = 1; } -static void reprepare_packed_git(void) +void reprepare_packed_git(void) { prepare_packed_git_run_once = 0; prepare_packed_git(); -- cgit v0.10.2-6-g49f6 From 920ccbfc3bedd384f3073c342c64fbd028a4ee3a Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 1 Nov 2006 17:06:22 -0500 Subject: git-fetch can use both --thin and --keep with fetch-pack now Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/git-fetch.sh b/git-fetch.sh index 539dff6..2b5538f 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -20,7 +20,7 @@ verbose= update_head_ok= exec= upload_pack= -keep=--thin +keep= while case "$#" in 0) break ;; esac do case "$1" in @@ -370,7 +370,7 @@ fetch_main () { ( : subshell because we muck with IFS IFS=" $LF" ( - git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote" + git-fetch-pack --thin $exec $keep "$remote" $rref || echo failed "$remote" ) | while read sha1 remote_name do -- cgit v0.10.2-6-g49f6 From da093d37506f034bfb14e6de26cce9c14b1a1216 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 1 Nov 2006 17:06:23 -0500 Subject: improve fetch-pack's handling of kept packs Since functions in fetch-clone.c were only used from fetch-pack.c, its content has been merged with fetch-pack.c. This allows for better coupling of features with much simpler implementations. One new thing is that the (abscence of) --thin also enforce it on index-pack now, such that index-pack will abort if a thin pack was _not_ asked for. The -k or --keep, when provided twice, now causes the fetched pack to be left as a kept pack just like receive-pack currently does. Eventually this will be used to close a race against concurrent repacking. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index bff9aa6..3e6cd88 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -32,7 +32,8 @@ OPTIONS -k:: Do not invoke 'git-unpack-objects' on received data, but create a single packfile out of it instead, and store it - in the object database. + in the object database. If provided twice then the pack is + locked against repacking. --exec=:: Use this to specify the path to 'git-upload-pack' on the diff --git a/Makefile b/Makefile index 1cc9f58..9bf50bc 100644 --- a/Makefile +++ b/Makefile @@ -260,7 +260,7 @@ LIB_OBJS = \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ - fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ + revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o diff --git a/cache.h b/cache.h index 6cb7e1d..f2ec5c8 100644 --- a/cache.h +++ b/cache.h @@ -416,10 +416,6 @@ extern int copy_fd(int ifd, int ofd); extern void write_or_die(int fd, const void *buf, size_t count); extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); -/* Finish off pack transfer receiving end */ -extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int); -extern int receive_keep_pack(int fd[2], const char *me, int quiet, int); - /* pager.c */ extern void setup_pager(void); extern int pager_in_use; diff --git a/fetch-clone.c b/fetch-clone.c deleted file mode 100644 index f629d8d..0000000 --- a/fetch-clone.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "cache.h" -#include "exec_cmd.h" -#include "pkt-line.h" -#include "sideband.h" -#include - -static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) -{ - pid_t side_pid; - - if (!sideband) { - fd[0] = xd[0]; - fd[1] = xd[1]; - return 0; - } - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through our fd[0]. - */ - if (pipe(fd) < 0) - die("%s: unable to set up pipe", me); - side_pid = fork(); - if (side_pid < 0) - die("%s: unable to fork off sideband demultiplexer", me); - if (!side_pid) { - /* subprocess */ - close(fd[0]); - if (xd[0] != xd[1]) - close(xd[1]); - if (recv_sideband(me, xd[0], fd[1], 2)) - exit(1); - exit(0); - } - close(xd[0]); - close(fd[1]); - fd[1] = xd[1]; - return side_pid; -} - -static int get_pack(int xd[2], const char *me, int sideband, const char **argv) -{ - int status; - pid_t pid, side_pid; - int fd[2]; - - side_pid = setup_sideband(sideband, me, fd, xd); - pid = fork(); - if (pid < 0) - die("%s: unable to fork off %s", me, argv[0]); - if (!pid) { - dup2(fd[0], 0); - close(fd[0]); - close(fd[1]); - execv_git_cmd(argv); - die("%s exec failed", argv[0]); - } - close(fd[0]); - close(fd[1]); - while (waitpid(pid, &status, 0) < 0) { - if (errno != EINTR) - die("waiting for %s: %s", argv[0], strerror(errno)); - } - if (WIFEXITED(status)) { - int code = WEXITSTATUS(status); - if (code) - die("%s died with error code %d", argv[0], code); - return 0; - } - if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); - die("%s died of signal %d", argv[0], sig); - } - die("%s died of unnatural causes %d", argv[0], status); -} - -int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband) -{ - const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL }; - return get_pack(xd, me, sideband, argv); -} - -int receive_keep_pack(int xd[2], const char *me, int quiet, int sideband) -{ - const char *argv[5] = { "index-pack", "--stdin", "--fix-thin", - quiet ? NULL : "-v", NULL }; - return get_pack(xd, me, sideband, argv); -} diff --git a/fetch-pack.c b/fetch-pack.c index 36ea092..0a169dc 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -3,6 +3,9 @@ #include "pkt-line.h" #include "commit.h" #include "tag.h" +#include "exec_cmd.h" +#include "sideband.h" +#include static int keep_pack; static int quiet; @@ -416,6 +419,103 @@ static int everything_local(struct ref **refs, int nr_match, char **match) return retval; } +static pid_t setup_sideband(int fd[2], int xd[2]) +{ + pid_t side_pid; + + if (!use_sideband) { + fd[0] = xd[0]; + fd[1] = xd[1]; + return 0; + } + /* xd[] is talking with upload-pack; subprocess reads from + * xd[0], spits out band#2 to stderr, and feeds us band#1 + * through our fd[0]. + */ + if (pipe(fd) < 0) + die("fetch-pack: unable to set up pipe"); + side_pid = fork(); + if (side_pid < 0) + die("fetch-pack: unable to fork off sideband demultiplexer"); + if (!side_pid) { + /* subprocess */ + close(fd[0]); + if (xd[0] != xd[1]) + close(xd[1]); + if (recv_sideband("fetch-pack", xd[0], fd[1], 2)) + exit(1); + exit(0); + } + close(xd[0]); + close(fd[1]); + fd[1] = xd[1]; + return side_pid; +} + +static int get_pack(int xd[2], const char **argv) +{ + int status; + pid_t pid, side_pid; + int fd[2]; + + side_pid = setup_sideband(fd, xd); + pid = fork(); + if (pid < 0) + die("fetch-pack: unable to fork off %s", argv[0]); + if (!pid) { + dup2(fd[0], 0); + close(fd[0]); + close(fd[1]); + execv_git_cmd(argv); + die("%s exec failed", argv[0]); + } + close(fd[0]); + close(fd[1]); + while (waitpid(pid, &status, 0) < 0) { + if (errno != EINTR) + die("waiting for %s: %s", argv[0], strerror(errno)); + } + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + if (code) + die("%s died with error code %d", argv[0], code); + return 0; + } + if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + die("%s died of signal %d", argv[0], sig); + } + die("%s died of unnatural causes %d", argv[0], status); +} + +static int explode_rx_pack(int xd[2]) +{ + const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL }; + return get_pack(xd, argv); +} + +static int keep_rx_pack(int xd[2]) +{ + const char *argv[6]; + char keep_arg[256]; + int n = 0; + + argv[n++] = "index-pack"; + argv[n++] = "--stdin"; + if (!quiet) + argv[n++] = "-v"; + if (use_thin_pack) + argv[n++] = "--fix-thin"; + if (keep_pack > 1) { + int s = sprintf(keep_arg, "--keep=fetch-pack %i on ", getpid()); + if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) + strcpy(keep_arg + s, "localhost"); + argv[n++] = keep_arg; + } + argv[n] = NULL; + return get_pack(xd, argv); +} + static int fetch_pack(int fd[2], int nr_match, char **match) { struct ref *ref; @@ -447,17 +547,13 @@ static int fetch_pack(int fd[2], int nr_match, char **match) goto all_done; } if (find_common(fd, sha1, ref) < 0) - if (!keep_pack) + if (keep_pack != 1) /* When cloning, it is not unusual to have * no common commit. */ fprintf(stderr, "warning: no common commits\n"); - if (keep_pack) - status = receive_keep_pack(fd, "git-fetch-pack", quiet, use_sideband); - else - status = receive_unpack_pack(fd, "git-fetch-pack", quiet, use_sideband); - + status = (keep_pack) ? keep_rx_pack(fd) : explode_rx_pack(fd); if (status) die("git-fetch-pack: fetch failed."); @@ -494,7 +590,7 @@ int main(int argc, char **argv) continue; } if (!strcmp("--keep", arg) || !strcmp("-k", arg)) { - keep_pack = 1; + keep_pack++; continue; } if (!strcmp("--thin", arg)) { -- cgit v0.10.2-6-g49f6 From 9ca4a201eaf0c58dbc7184cb2d5ab01c48cb7447 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 1 Nov 2006 17:06:24 -0500 Subject: have index-pack create .keep file more carefully If by chance we receive a pack which content (list of objects) matches another pack that we already have, and if that pack is marked with a .keep file, then we should not overwrite it. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/index-pack.c b/index-pack.c index a3b55f9..8d64a88 100644 --- a/index-pack.c +++ b/index-pack.c @@ -788,14 +788,17 @@ static void final(const char *final_pack_name, const char *curr_pack_name, get_object_directory(), sha1_to_hex(sha1)); keep_name = name; } - keep_fd = open(keep_name, O_RDWR | O_CREAT, 0600); - if (keep_fd < 0) - die("cannot write keep file"); - if (keep_msg_len > 0) { - write_or_die(keep_fd, keep_msg, keep_msg_len); - write_or_die(keep_fd, "\n", 1); + keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600); + if (keep_fd < 0) { + if (errno != EEXIST) + die("cannot write keep file"); + } else { + if (keep_msg_len > 0) { + write_or_die(keep_fd, keep_msg, keep_msg_len); + write_or_die(keep_fd, "\n", 1); + } + close(keep_fd); } - close(keep_fd); } if (final_pack_name != curr_pack_name) { -- cgit v0.10.2-6-g49f6 From 576162a45f35e157427300066b0ff566ff698a0f Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 1 Nov 2006 17:06:25 -0500 Subject: remove .keep pack lock files when done with refs update This makes both git-fetch and git-push (fetch-pack and receive-pack) safe against a possible race with aparallel git-repack -a -d that could prune the new pack while it is not yet referenced, and remove the .keep file after refs have been updated. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 1235416..2229ee8 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -69,6 +69,17 @@ OPTIONS locate any which have outlived their usefulness. +Note +---- + +Once the index has been created, the list of object names is sorted +and the SHA1 hash of that list is printed to stdout. If --stdin was +also used then this is prefixed by either "pack\t", or "keep\t" if a +new .keep file was successfully created. This is useful to remove a +.keep file used as a lock to prevent the race with gitlink:git-repack[1] +mentioned above. + + Author ------ Written by Sergey Vlasov diff --git a/git-fetch.sh b/git-fetch.sh index 2b5538f..7442dd2 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -51,7 +51,7 @@ do verbose=Yes ;; -k|--k|--ke|--kee|--keep) - keep=--keep + keep='-k -k' ;; --reflog-action=*) rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'` @@ -368,6 +368,7 @@ fetch_main () { ;; # we are already done. *) ( : subshell because we muck with IFS + pack_lockfile= IFS=" $LF" ( git-fetch-pack --thin $exec $keep "$remote" $rref || echo failed "$remote" @@ -378,6 +379,12 @@ fetch_main () { failed) echo >&2 "Fetch failure: $remote" exit 1 ;; + # special line coming from index-pack with the pack name + pack) + continue ;; + keep) + pack_lockfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep" + continue ;; esac found= single_force= @@ -408,6 +415,7 @@ fetch_main () { append_fetch_head "$sha1" "$remote" \ "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" done + if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi ) || exit ;; esac diff --git a/index-pack.c b/index-pack.c index 8d64a88..042aea8 100644 --- a/index-pack.c +++ b/index-pack.c @@ -757,6 +757,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, const char *keep_name, const char *keep_msg, unsigned char *sha1) { + char *report = "pack"; char name[PATH_MAX]; int err; @@ -767,18 +768,6 @@ static void final(const char *final_pack_name, const char *curr_pack_name, if (err) die("error while closing pack file: %s", strerror(errno)); chmod(curr_pack_name, 0444); - - /* - * Let's just mimic git-unpack-objects here and write - * the last part of the buffer to stdout. - */ - while (input_len) { - err = xwrite(1, input_buffer + input_offset, input_len); - if (err <= 0) - break; - input_len -= err; - input_offset += err; - } } if (keep_msg) { @@ -798,6 +787,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, write_or_die(keep_fd, "\n", 1); } close(keep_fd); + report = "keep"; } } @@ -821,6 +811,27 @@ static void final(const char *final_pack_name, const char *curr_pack_name, if (move_temp_to_file(curr_index_name, final_index_name)) die("cannot store index file"); } + + if (!from_stdin) { + printf("%s\n", sha1_to_hex(sha1)); + } else { + char buf[48]; + int len = snprintf(buf, sizeof(buf), "%s\t%s\n", + report, sha1_to_hex(sha1)); + xwrite(1, buf, len); + + /* + * Let's just mimic git-unpack-objects here and write + * the last part of the input buffer to stdout. + */ + while (input_len) { + err = xwrite(1, input_buffer + input_offset, input_len); + if (err <= 0) + break; + input_len -= err; + input_offset += err; + } + } } int main(int argc, char **argv) @@ -937,8 +948,5 @@ int main(int argc, char **argv) free(index_name_buf); free(keep_name_buf); - if (!from_stdin) - printf("%s\n", sha1_to_hex(sha1)); - return 0; } diff --git a/receive-pack.c b/receive-pack.c index b8d1270..d56898c 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -3,8 +3,10 @@ #include "refs.h" #include "pkt-line.h" #include "run-command.h" +#include "exec_cmd.h" #include "commit.h" #include "object.h" +#include static const char receive_pack_usage[] = "git-receive-pack "; @@ -251,12 +253,13 @@ static const char *parse_pack_header(struct pack_header *hdr) return NULL; } +static const char *pack_lockfile; + static const char *unpack(void) { struct pack_header hdr; const char *hdr_err; char hdr_arg[38]; - int code; hdr_err = parse_pack_header(&hdr); if (hdr_err) @@ -265,33 +268,13 @@ static const char *unpack(void) ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries)); if (ntohl(hdr.hdr_entries) < unpack_limit) { + int code; const char *unpacker[3]; unpacker[0] = "unpack-objects"; unpacker[1] = hdr_arg; unpacker[2] = NULL; code = run_command_v_opt(1, unpacker, RUN_GIT_CMD); - } else { - const char *keeper[6]; - char my_host[255], keep_arg[128 + 255]; - - if (gethostname(my_host, sizeof(my_host))) - strcpy(my_host, "localhost"); - snprintf(keep_arg, sizeof(keep_arg), - "--keep=receive-pack %i on %s", - getpid(), my_host); - - keeper[0] = "index-pack"; - keeper[1] = "--stdin"; - keeper[2] = "--fix-thin"; - keeper[3] = hdr_arg; - keeper[4] = keep_arg; - keeper[5] = NULL; - code = run_command_v_opt(1, keeper, RUN_GIT_CMD); - if (!code) - reprepare_packed_git(); - } - - switch (code) { + switch (code) { case 0: return NULL; case -ERR_RUN_COMMAND_FORK: @@ -308,6 +291,71 @@ static const char *unpack(void) return "unpacker died strangely"; default: return "unpacker exited with error code"; + } + } else { + const char *keeper[6]; + int fd[2], s, len, status; + pid_t pid; + char keep_arg[256]; + char packname[46]; + + s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid()); + if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) + strcpy(keep_arg + s, "localhost"); + + keeper[0] = "index-pack"; + keeper[1] = "--stdin"; + keeper[2] = "--fix-thin"; + keeper[3] = hdr_arg; + keeper[4] = keep_arg; + keeper[5] = NULL; + + if (pipe(fd) < 0) + return "index-pack pipe failed"; + pid = fork(); + if (pid < 0) + return "index-pack fork failed"; + if (!pid) { + dup2(fd[1], 1); + close(fd[1]); + close(fd[0]); + execv_git_cmd(keeper); + die("execv of index-pack failed"); + } + close(fd[1]); + + /* + * The first thing we expects from index-pack's output + * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where + * %40s is the newly created pack SHA1 name. In the "keep" + * case, we need it to remove the corresponding .keep file + * later on. If we don't get that then tough luck with it. + */ + for (len = 0; + len < 46 && (s = xread(fd[0], packname+len, 46-len)) > 0; + len += s); + close(fd[0]); + if (len == 46 && packname[45] == '\n' && + memcmp(packname, "keep\t", 5) == 0) { + char path[PATH_MAX]; + packname[45] = 0; + snprintf(path, sizeof(path), "%s/pack/pack-%s.keep", + get_object_directory(), packname + 5); + pack_lockfile = xstrdup(path); + } + + /* Then wrap our index-pack process. */ + while (waitpid(pid, &status, 0) < 0) + if (errno != EINTR) + return "waitpid failed"; + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + if (code) + return "index-pack exited with error code"; + reprepare_packed_git(); + return NULL; + } + return "index-pack abnormal exit"; } } @@ -363,6 +411,8 @@ int main(int argc, char **argv) const char *unpack_status = unpack(); if (!unpack_status) execute_commands(); + if (pack_lockfile) + unlink(pack_lockfile); if (report_status) report(unpack_status); } -- cgit v0.10.2-6-g49f6 From 8a078c3f72002af9bf79dc884fe321e3e48930fc Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Fri, 3 Nov 2006 17:41:23 +0100 Subject: git.el: Added functions for moving to the next/prev unmerged file. This is useful when doing a merge that changes many files with only a few conflicts here and there. Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 5354cd6..e283df2 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -670,6 +670,32 @@ and returns the process output as a string." (unless git-status (error "Not in git-status buffer.")) (ewoc-goto-prev git-status n)) +(defun git-next-unmerged-file (&optional n) + "Move the selection down N unmerged files." + (interactive "p") + (unless git-status (error "Not in git-status buffer.")) + (let* ((last (ewoc-locate git-status)) + (node (ewoc-next git-status last))) + (while (and node (> n 0)) + (when (eq 'unmerged (git-fileinfo->state (ewoc-data node))) + (setq n (1- n)) + (setq last node)) + (setq node (ewoc-next git-status node))) + (ewoc-goto-node git-status last))) + +(defun git-prev-unmerged-file (&optional n) + "Move the selection up N unmerged files." + (interactive "p") + (unless git-status (error "Not in git-status buffer.")) + (let* ((last (ewoc-locate git-status)) + (node (ewoc-prev git-status last))) + (while (and node (> n 0)) + (when (eq 'unmerged (git-fileinfo->state (ewoc-data node))) + (setq n (1- n)) + (setq last node)) + (setq node (ewoc-prev git-status node))) + (ewoc-goto-node git-status last))) + (defun git-add-file () "Add marked file(s) to the index cache." (interactive) @@ -967,7 +993,9 @@ and returns the process output as a string." (define-key map "m" 'git-mark-file) (define-key map "M" 'git-mark-all) (define-key map "n" 'git-next-file) + (define-key map "N" 'git-next-unmerged-file) (define-key map "p" 'git-prev-file) + (define-key map "P" 'git-prev-unmerged-file) (define-key map "q" 'git-status-quit) (define-key map "r" 'git-remove-file) (define-key map "R" 'git-resolve-file) -- cgit v0.10.2-6-g49f6 From b8ee51815ad0520b0340c2a594dee1f8de9c7c8a Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Fri, 3 Nov 2006 17:41:46 +0100 Subject: git.el: Added a function to open the current file in another window. Bound to 'o' by default, compatible with pcl-cvs and buffer-mode. Suggested by Han-Wen Nienhuys. Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index e283df2..08d6404 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -910,6 +910,15 @@ and returns the process output as a string." (when (eq 'unmerged (git-fileinfo->state info)) (smerge-mode)))) +(defun git-find-file-other-window () + "Visit the current file in its own buffer in another window." + (interactive) + (unless git-status (error "Not in git-status buffer.")) + (let ((info (ewoc-data (ewoc-locate git-status)))) + (find-file-other-window (git-fileinfo->name info)) + (when (eq 'unmerged (git-fileinfo->state info)) + (smerge-mode)))) + (defun git-find-file-imerge () "Visit the current file in interactive merge mode." (interactive) @@ -994,6 +1003,7 @@ and returns the process output as a string." (define-key map "M" 'git-mark-all) (define-key map "n" 'git-next-file) (define-key map "N" 'git-next-unmerged-file) + (define-key map "o" 'git-find-file-other-window) (define-key map "p" 'git-prev-file) (define-key map "P" 'git-prev-unmerged-file) (define-key map "q" 'git-status-quit) -- cgit v0.10.2-6-g49f6 From 2ac2b19601cd8db8660a8d55647079f1ff4885b1 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Fri, 3 Nov 2006 17:42:17 +0100 Subject: git.el: Move point after the log message header when entering log-edit mode. Suggested by Han-Wen Nienhuys. Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 08d6404..6f3b46d 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -899,7 +899,8 @@ and returns the process output as a string." (2 font-lock-function-name-face)) (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$") (1 font-lock-comment-face))))) - (log-edit #'git-do-commit nil #'git-log-edit-files buffer)))) + (log-edit #'git-do-commit nil #'git-log-edit-files buffer) + (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))) (defun git-find-file () "Visit the current file in its own buffer." -- cgit v0.10.2-6-g49f6 From 2379d61fa6355bc7a8cc8b5dce87a7d4a9505c76 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Fri, 3 Nov 2006 17:42:43 +0100 Subject: git.el: Include MERGE_MSG in the log-edit buffer even when not committing a merge. This lets us take advantage of the fact that git-cherry-pick now saves the message in MERGE_MSG too. Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 6f3b46d..972c402 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -589,6 +589,7 @@ and returns the process output as a string." (let ((commit (git-commit-tree buffer tree head))) (git-update-ref "HEAD" commit head) (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil)) + (condition-case nil (delete-file ".git/MERGE_MSG") (error nil)) (with-current-buffer buffer (erase-buffer)) (git-set-files-state files 'uptodate) (when (file-directory-p ".git/rr-cache") @@ -888,7 +889,7 @@ and returns the process output as a string." 'face 'git-header-face) (propertize git-log-msg-separator 'face 'git-separator-face) "\n") - (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG")) + (cond ((file-readable-p ".git/MERGE_MSG") (insert-file-contents ".git/MERGE_MSG")) (sign-off (insert (format "\n\nSigned-off-by: %s <%s>\n" -- cgit v0.10.2-6-g49f6 From 6768d6b8477db41a1cfdbd1d81ac8c5131c58e1d Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Fri, 3 Nov 2006 10:41:45 +0530 Subject: gitweb: Remove extra "/" in path names for git_get_project_list Without this change we get a wrong $pfxlen value and the check_export_ok() checks with with a wrong directory name. Without this patch the below $projects_list fails with gitweb $projects_list = "/tmp/a/b/"; Signed-off-by: Aneesh Kumar K.V Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3dfa59f..3759be3 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -902,6 +902,8 @@ sub git_get_projects_list { if (-d $projects_list) { # search in directory my $dir = $projects_list; + # remove the trailing "/" + $dir =~ s!/+$!!; my $pfxlen = length("$dir"); File::Find::find({ -- cgit v0.10.2-6-g49f6 From 6f9f3b263b1c86889d6fae0d50c75be0f3227003 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 4 Nov 2006 02:28:53 -0800 Subject: apply: handle "traditional" creation/deletion diff correctly. We deduced a GNU diff output that does not use /dev/null convention as creation (deletion) diff correctly by looking at the lack of context and deleted lines (added lines), but forgot to reset the new (old) name field properly. This was a regression when we added a workaround for --unified=0 insanity. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 11a5277..f70ee98 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1043,10 +1043,14 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc * then not having oldlines means the patch is creation, * and not having newlines means the patch is deletion. */ - if (patch->is_new < 0 && !oldlines) + if (patch->is_new < 0 && !oldlines) { patch->is_new = 1; - if (patch->is_delete < 0 && !newlines) + patch->old_name = NULL; + } + if (patch->is_delete < 0 && !newlines) { patch->is_delete = 1; + patch->new_name = NULL; + } } if (0 < patch->is_new && oldlines) -- cgit v0.10.2-6-g49f6 From 2f3f8b218abae6fc0574d0e22d3614fc0f5e983d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 2 Nov 2006 00:02:11 -0800 Subject: git-pickaxe: rename detection optimization The idea is that we are interested in renaming into only one path, so we do not care about renames that happen elsewhere. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index f6e861a..97b3732 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -289,6 +289,7 @@ static struct origin *find_rename(struct scoreboard *sb, diff_opts.recursive = 1; diff_opts.detect_rename = DIFF_DETECT_RENAME; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_opts.single_follow = origin->path; paths[0] = NULL; diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) diff --git a/diff.h b/diff.h index ce3058e..6fda92a 100644 --- a/diff.h +++ b/diff.h @@ -46,6 +46,7 @@ struct diff_options { const char *filter; const char *orderfile; const char *pickaxe; + const char *single_follow; unsigned recursive:1, tree_in_recursive:1, binary:1, diff --git a/diffcore-rename.c b/diffcore-rename.c index ef23901..57a74b6 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -256,11 +256,15 @@ void diffcore_rename(struct diff_options *options) for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - if (!DIFF_FILE_VALID(p->one)) + if (!DIFF_FILE_VALID(p->one)) { if (!DIFF_FILE_VALID(p->two)) continue; /* unmerged */ + else if (options->single_follow && + strcmp(options->single_follow, p->two->path)) + continue; /* not interested */ else locate_rename_dst(p->two, 1); + } else if (!DIFF_FILE_VALID(p->two)) { /* If the source is a broken "delete", and * they did not really want to get broken, -- cgit v0.10.2-6-g49f6 From 0421d9f812acc819dcf9e9c6707618aca7e80bf4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 4 Nov 2006 12:20:09 -0800 Subject: git-pickaxe: simplify Octopus merges further If more than one parents in an Octopus merge have the same origin, ignore later ones because it would not make any difference in the outcome. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 97b3732..082ff45 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -915,6 +915,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) i < MAXPARENT && parent; parent = parent->next, i++) { struct commit *p = parent->item; + int j, same; if (parent_origin[i]) continue; @@ -934,7 +935,16 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) origin_decref(porigin); goto finish; } - parent_origin[i] = porigin; + for (j = same = 0; j < i; j++) + if (!hashcmp(parent_origin[j]->blob_sha1, + porigin->blob_sha1)) { + same = 1; + break; + } + if (!same) + parent_origin[i] = porigin; + else + origin_decref(porigin); } } -- cgit v0.10.2-6-g49f6 From 650e2f6752ad29b0cba394535d3b098cf4bb102b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 4 Nov 2006 12:37:02 -0800 Subject: git-pickaxe: re-scan the blob after making progress with -M Otherwise we would miss copied lines that are contained in the parts before or after the part that we find after splitting the blame_entry (i.e. split[0] and split[2]). Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 082ff45..29839cd 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -766,7 +766,7 @@ static int find_move_in_parent(struct scoreboard *sb, struct origin *target, struct origin *parent) { - int last_in_target; + int last_in_target, made_progress; struct blame_entry *e, split[3]; mmfile_t file_p; char type[10]; @@ -784,14 +784,20 @@ static int find_move_in_parent(struct scoreboard *sb, return 0; } - for (e = sb->ent; e; e = e->next) { - if (e->guilty || cmp_suspect(e->suspect, target)) - continue; - find_copy_in_blob(sb, e, parent, split, &file_p); - if (split[1].suspect && - blame_move_score < ent_score(sb, &split[1])) - split_blame(sb, split, e); - decref_split(split); + made_progress = 1; + while (made_progress) { + made_progress = 0; + for (e = sb->ent; e; e = e->next) { + if (e->guilty || cmp_suspect(e->suspect, target)) + continue; + find_copy_in_blob(sb, e, parent, split, &file_p); + if (split[1].suspect && + blame_move_score < ent_score(sb, &split[1])) { + split_blame(sb, split, e); + made_progress = 1; + } + decref_split(split); + } } free(blob_p); return 0; -- cgit v0.10.2-6-g49f6 From 334947843cbd50895f1dc58d8bd2f217e1f1ef77 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 4 Nov 2006 16:39:03 -0800 Subject: git-pickaxe: re-scan the blob after making progress with -C The reason to do this is the same as in the previous change for line copy detection within the same file (-M). Also this fixes -C and -C -C (aka find-copies-harder) logic; in this application we are not interested in the similarity matching diffcore-rename makes, because we are only interested in scanning files that were modified, or in the case of -C -C, scanning all files in the parent and we want to do that ourselves. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 29839cd..b25e039 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -803,6 +803,36 @@ static int find_move_in_parent(struct scoreboard *sb, return 0; } + +struct blame_list { + struct blame_entry *ent; + struct blame_entry split[3]; +}; + +static struct blame_list *setup_blame_list(struct scoreboard *sb, + struct origin *target, + int *num_ents_p) +{ + struct blame_entry *e; + int num_ents, i; + struct blame_list *blame_list = NULL; + + /* Count the number of entries the target is suspected for, + * and prepare a list of entry and the best split. + */ + for (e = sb->ent, num_ents = 0; e; e = e->next) + if (!e->guilty && !cmp_suspect(e->suspect, target)) + num_ents++; + if (num_ents) { + blame_list = xcalloc(num_ents, sizeof(struct blame_list)); + for (e = sb->ent, i = 0; e; e = e->next) + if (!e->guilty && !cmp_suspect(e->suspect, target)) + blame_list[i++].ent = e; + } + *num_ents_p = num_ents; + return blame_list; +} + static int find_copy_in_parent(struct scoreboard *sb, struct origin *target, struct commit *parent, @@ -811,91 +841,101 @@ static int find_copy_in_parent(struct scoreboard *sb, { struct diff_options diff_opts; const char *paths[1]; - struct blame_entry *e; int i, j; - struct blame_list { - struct blame_entry *ent; - struct blame_entry split[3]; - } *blame_list; + int retval; + struct blame_list *blame_list; int num_ents; - /* Count the number of entries the target is suspected for, - * and prepare a list of entry and the best split. - */ - for (e = sb->ent, num_ents = 0; e; e = e->next) - if (!e->guilty && !cmp_suspect(e->suspect, target)) - num_ents++; - if (!num_ents) + blame_list = setup_blame_list(sb, target, &num_ents); + if (!blame_list) return 1; /* nothing remains for this target */ - blame_list = xcalloc(num_ents, sizeof(struct blame_list)); - for (e = sb->ent, i = 0; e; e = e->next) - if (!e->guilty && !cmp_suspect(e->suspect, target)) - blame_list[i++].ent = e; - diff_setup(&diff_opts); diff_opts.recursive = 1; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - /* Try "find copies harder" on new path */ - if ((opt & PICKAXE_BLAME_COPY_HARDER) && - (!porigin || strcmp(target->path, porigin->path))) { - diff_opts.detect_rename = DIFF_DETECT_COPY; - diff_opts.find_copies_harder = 1; - } paths[0] = NULL; diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); + + /* Try "find copies harder" on new path if requested; + * we do not want to use diffcore_rename() actually to + * match things up; find_copies_harder is set only to + * force diff_tree_sha1() to feed all filepairs to diff_queue, + * and this code needs to be after diff_setup_done(), which + * usually makes find-copies-harder imply copy detection. + */ + if ((opt & PICKAXE_BLAME_COPY_HARDER) && + (!porigin || strcmp(target->path, porigin->path))) + diff_opts.find_copies_harder = 1; + diff_tree_sha1(parent->tree->object.sha1, target->commit->tree->object.sha1, "", &diff_opts); - diffcore_std(&diff_opts); - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - struct origin *norigin; - mmfile_t file_p; - char type[10]; - char *blob; - struct blame_entry this[3]; - - if (!DIFF_FILE_VALID(p->one)) - continue; /* does not exist in parent */ - if (porigin && !strcmp(p->one->path, porigin->path)) - /* find_move already dealt with this path */ - continue; + if (!diff_opts.find_copies_harder) + diffcore_std(&diff_opts); - norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->sha1); - blob = read_sha1_file(norigin->blob_sha1, type, - (unsigned long *) &file_p.size); - file_p.ptr = blob; - if (!file_p.ptr) - continue; + retval = 0; + while (1) { + int made_progress = 0; + + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + struct origin *norigin; + mmfile_t file_p; + char type[10]; + char *blob; + struct blame_entry this[3]; + + if (!DIFF_FILE_VALID(p->one)) + continue; /* does not exist in parent */ + if (porigin && !strcmp(p->one->path, porigin->path)) + /* find_move already dealt with this path */ + continue; + + norigin = get_origin(sb, parent, p->one->path); + hashcpy(norigin->blob_sha1, p->one->sha1); + blob = read_sha1_file(norigin->blob_sha1, type, + (unsigned long *) &file_p.size); + file_p.ptr = blob; + if (!file_p.ptr) + continue; + + for (j = 0; j < num_ents; j++) { + find_copy_in_blob(sb, blame_list[j].ent, + norigin, this, &file_p); + copy_split_if_better(sb, blame_list[j].split, + this); + decref_split(this); + } + free(blob); + origin_decref(norigin); + } for (j = 0; j < num_ents; j++) { - find_copy_in_blob(sb, blame_list[j].ent, norigin, - this, &file_p); - copy_split_if_better(sb, blame_list[j].split, - this); - decref_split(this); + struct blame_entry *split = blame_list[j].split; + if (split[1].suspect && + blame_copy_score < ent_score(sb, &split[1])) { + split_blame(sb, split, blame_list[j].ent); + made_progress = 1; + } + decref_split(split); } - free(blob); - origin_decref(norigin); - } - diff_flush(&diff_opts); + free(blame_list); - for (j = 0; j < num_ents; j++) { - struct blame_entry *split = blame_list[j].split; - if (split[1].suspect && - blame_copy_score < ent_score(sb, &split[1])) - split_blame(sb, split, blame_list[j].ent); - decref_split(split); + if (!made_progress) + break; + blame_list = setup_blame_list(sb, target, &num_ents); + if (!blame_list) { + retval = 1; + break; + } } - free(blame_list); + diff_flush(&diff_opts); - return 0; + return retval; } #define MAXPARENT 16 @@ -942,7 +982,8 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) goto finish; } for (j = same = 0; j < i; j++) - if (!hashcmp(parent_origin[j]->blob_sha1, + if (parent_origin[j] && + !hashcmp(parent_origin[j]->blob_sha1, porigin->blob_sha1)) { same = 1; break; -- cgit v0.10.2-6-g49f6 From dfb960920d4953066cb0cdf3cb6dd8a5a8d9cf14 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 4 Nov 2006 13:57:18 -0500 Subject: Added completion support for git-branch.exe. On Cygwin a user might complete the new git-branch builtin as git-branch.exe, at which point bash requires a new completion registration for the command. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index b074f4f..b7b8a43 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -339,6 +339,7 @@ complete -o default -o nospace -F _git_log git-whatchanged # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # +complete -o default -F _git_branch git-branch.exe complete -o default -o nospace -F _git_cat_file git-cat-file.exe complete -o default -o nospace -F _git_diff git-diff.exe complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe -- cgit v0.10.2-6-g49f6 From 67e78c3b8aeb44681b2d4ab2cf97145b7cc9a7a2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 4 Nov 2006 13:57:44 -0500 Subject: Added bash completion support for git-reset. Completion for the --hard/--soft/--mixed modes of operation as well as a ref name for can be very useful and save some fingers. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index b7b8a43..a3fbb90 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -271,6 +271,13 @@ _git_push () esac } +_git_reset () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + local opt="--mixed --hard --soft" + COMPREPLY=($(compgen -W "$opt $(__git_refs .)" -- "$cur")) +} + _git_show () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -304,6 +311,7 @@ _git () ls-tree) _git_ls_tree ;; pull) _git_pull ;; push) _git_push ;; + reset) _git_reset ;; show) _git_show ;; show-branch) _git_log ;; whatchanged) _git_log ;; @@ -332,6 +340,7 @@ complete -o default -o nospace -F _git_ls_tree git-ls-tree complete -o default -F _git_merge_base git-merge-base complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push +complete -o default -F _git_reset git-reset complete -o default -F _git_show git-show complete -o default -o nospace -F _git_log git-whatchanged -- cgit v0.10.2-6-g49f6 From 854b97f6eb3bcbeb76b2705bfbffead1558bde77 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 4 Nov 2006 19:18:50 -0800 Subject: git-pickaxe: fix origin refcounting When we introduced the cached origin per commit, we gave up proper garbage collecting because it meant that commits hold onto their cached copy. There is no need to do so. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index b25e039..332e6a2 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -168,23 +168,28 @@ static void coalesce(struct scoreboard *sb) sanity_check_refcnt(sb); } +static struct origin *make_origin(struct commit *commit, const char *path) +{ + struct origin *o; + o = xcalloc(1, sizeof(*o) + strlen(path) + 1); + o->commit = commit; + o->refcnt = 1; + strcpy(o->path, path); + return o; +} + static struct origin *get_origin(struct scoreboard *sb, struct commit *commit, const char *path) { struct blame_entry *e; - struct origin *o; for (e = sb->ent; e; e = e->next) { if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) return origin_incref(e->suspect); } - o = xcalloc(1, sizeof(*o) + strlen(path) + 1); - o->commit = commit; - o->refcnt = 1; - strcpy(o->path, path); - return o; + return make_origin(commit, path); } static int fill_blob_sha1(struct origin *origin) @@ -216,9 +221,19 @@ static struct origin *find_origin(struct scoreboard *sb, const char *paths[2]; if (parent->util) { + /* This is a freestanding copy of origin and not + * refcounted. + */ struct origin *cached = parent->util; - if (!strcmp(cached->path, origin->path)) - return origin_incref(cached); + if (!strcmp(cached->path, origin->path)) { + porigin = get_origin(sb, parent, cached->path); + if (porigin->refcnt == 1) + hashcpy(porigin->blob_sha1, cached->blob_sha1); + return porigin; + } + /* otherwise it was not very useful; free it */ + free(parent->util); + parent->util = NULL; } /* See if the origin->path is different between parent @@ -268,10 +283,10 @@ static struct origin *find_origin(struct scoreboard *sb, } diff_flush(&diff_opts); if (porigin) { - origin_incref(porigin); - if (parent->util) - origin_decref(parent->util); - parent->util = porigin; + struct origin *cached; + cached = make_origin(porigin->commit, porigin->path); + hashcpy(cached->blob_sha1, porigin->blob_sha1); + parent->util = cached; } return porigin; } @@ -1434,8 +1449,13 @@ static void sanity_check_refcnt(struct scoreboard *sb) for (ent = sb->ent; ent; ent = ent->next) { /* Nobody should have zero or negative refcnt */ - if (ent->suspect->refcnt <= 0) + if (ent->suspect->refcnt <= 0) { + fprintf(stderr, "%s in %s has negative refcnt %d\n", + ent->suspect->path, + sha1_to_hex(ent->suspect->commit->object.sha1), + ent->suspect->refcnt); baa = 1; + } } for (ent = sb->ent; ent; ent = ent->next) { /* Mark the ones that haven't been checked */ @@ -1444,9 +1464,7 @@ static void sanity_check_refcnt(struct scoreboard *sb) } for (ent = sb->ent; ent; ent = ent->next) { /* then pick each and see if they have the the correct - * refcnt; note that ->util caching means origin's refcnt - * may well be greater than the number of blame entries - * that use it. + * refcnt. */ int found; struct blame_entry *e; @@ -1460,14 +1478,19 @@ static void sanity_check_refcnt(struct scoreboard *sb) continue; found++; } - if (suspect->refcnt < found) - baa = 1; + if (suspect->refcnt != found) { + fprintf(stderr, "%s in %s has refcnt %d, not %d\n", + ent->suspect->path, + sha1_to_hex(ent->suspect->commit->object.sha1), + ent->suspect->refcnt, found); + baa = 2; + } } if (baa) { int opt = 0160; find_alignment(sb, &opt); output(sb, opt); - die("Baa!"); + die("Baa %d!", baa); } } -- cgit v0.10.2-6-g49f6 From 98526e007e293700fa578da9a0a4459d6b5375b0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 4 Nov 2006 21:51:10 -0800 Subject: git-svn: avoid printing filenames of files we're not tracking This is purely an aesthetic change, we already skip importing of files that don't affect the subdirectory we import. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 37ecc51..cc3335a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2662,11 +2662,12 @@ sub libsvn_connect { } sub libsvn_get_file { - my ($gui, $f, $rev) = @_; + my ($gui, $f, $rev, $chg) = @_; my $p = $f; if (length $SVN_PATH > 0) { return unless ($p =~ s#^\Q$SVN_PATH\E/##); } + print "\t$chg\t$f\n" unless $_q; my ($hash, $pid, $in, $out); my $pool = SVN::Pool->new; @@ -2769,8 +2770,7 @@ sub libsvn_fetch { $pool->clear; } foreach (@amr) { - print "\t$_->[0]\t$_->[1]\n" unless $_q; - libsvn_get_file($gui, $_->[1], $rev) + libsvn_get_file($gui, $_->[1], $rev, $_->[0]); } close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); @@ -2848,8 +2848,7 @@ sub libsvn_traverse { if (defined $files) { push @$files, $file; } else { - print "\tA\t$file\n" unless $_q; - libsvn_get_file($gui, $file, $rev); + libsvn_get_file($gui, $file, $rev, 'A'); } } } -- cgit v0.10.2-6-g49f6 From b2e2ddfed8006590bc5b8f4f2a22046891958f28 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 4 Nov 2006 21:51:11 -0800 Subject: git-svn: don't die on rebuild when --upgrade is specified --copy-remote and --upgrade are rarely (never?) used together, so if --copy-remote is specified, that means the user really wanted to copy the remote ref, and we should fail if that fails. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index cc3335a..4a56f18 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3139,7 +3139,7 @@ sub copy_remote_ref { my $ref = "refs/remotes/$GIT_SVN"; if (safe_qx('git-ls-remote', $origin, $ref)) { sys(qw/git fetch/, $origin, "$ref:$ref"); - } else { + } elsif ($_cp_remote && !$_upgrade) { die "Unable to find remote reference: ", "refs/remotes/$GIT_SVN on $origin\n"; } -- cgit v0.10.2-6-g49f6 From bf8675d314ffb0289779a954fdf6ae22f3322d0d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 02:27:07 -0500 Subject: Use ULONG_MAX rather than implicit cast of -1. At least one (older) version of the Solaris C compiler won't allow 'unsigned long x = -1' without explicitly casting -1 to a type of unsigned long. So instead use ULONG_MAX, which is really the correct constant anyway. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index f70ee98..6ec22b8 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -43,7 +43,7 @@ static int apply_verbosely; static int no_add; static int show_index_info; static int line_termination = '\n'; -static unsigned long p_context = -1; +static unsigned long p_context = ULONG_MAX; static const char apply_usage[] = "git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; -- cgit v0.10.2-6-g49f6 From af8ffbed0fc016e066765706738c45c65493f392 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 02:28:25 -0500 Subject: Remove SIMPLE_PROGRAMS and make git-daemon a normal program. Some platforms (Solaris in particular) appear to require -lz as part of the link line for git-daemon, due to it linking against sha1_file.o and that module requiring inflate/deflate support. So its time to retire SIMPLE_PROGRAMS and move its last remaining member into the standard PROGRAMS list, allowing it to link against all libraries used by the rest of Git. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 66c8b4b..b52dd57 100644 --- a/Makefile +++ b/Makefile @@ -185,15 +185,12 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-cherry-pick git-status git-instaweb -# The ones that do not have to link with lcrypto, lz nor xdiff. -SIMPLE_PROGRAMS = \ - git-daemon$X - # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-merge-base$X \ + git-daemon$X \ git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ git-peek-remote$X git-receive-pack$X \ git-send-pack$X git-shell$X \ @@ -215,7 +212,7 @@ BUILT_INS = \ $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir -ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) \ +ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) \ git-merge-recur$X # Backward compatibility -- to be removed after 1.0 @@ -479,11 +476,9 @@ ifdef NEEDS_LIBICONV endif ifdef NEEDS_SOCKET EXTLIBS += -lsocket - SIMPLE_LIB += -lsocket endif ifdef NEEDS_NSL EXTLIBS += -lnsl - SIMPLE_LIB += -lnsl endif ifdef NO_D_TYPE_IN_DIRENT BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT @@ -728,11 +723,6 @@ endif git-%$X: %.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) -$(SIMPLE_PROGRAMS) : $(LIB_FILE) -$(SIMPLE_PROGRAMS) : git-%$X : %.o - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIB_FILE) $(SIMPLE_LIB) - ssh-pull.o: ssh-fetch.c ssh-push.o: ssh-upload.c git-local-fetch$X: fetch.o -- cgit v0.10.2-6-g49f6 From 6c2f207b2316149ee8dfaf026e4a869ff9ab42f7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 00:37:23 -0500 Subject: Remove unsupported C99 style struct initializers in git-archive. At least one older version of the Solaris C compiler doesn't support the newer C99 style struct initializers. To allow Git to compile on those systems use an archive description struct which is easier to initialize without the C99 struct initializer syntax. Also since the archives array is not used by anyone other than archive.c we can make it static. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/archive.h b/archive.h index 16dcdb8..6838dc7 100644 --- a/archive.h +++ b/archive.h @@ -25,8 +25,6 @@ struct archiver { parse_extra_args_fn_t parse_extra; }; -extern struct archiver archivers[]; - extern int parse_archive_args(int argc, const char **argv, struct archiver *ar); diff --git a/builtin-archive.c b/builtin-archive.c index 9177379..2df1a84 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -15,16 +15,14 @@ static const char archive_usage[] = \ "git-archive --format= [--prefix=/] [--verbose] [] [path...]"; -struct archiver archivers[] = { - { - .name = "tar", - .write_archive = write_tar_archive, - }, - { - .name = "zip", - .write_archive = write_zip_archive, - .parse_extra = parse_extra_zip_args, - }, +static struct archiver_desc +{ + const char *name; + write_archive_fn_t write_archive; + parse_extra_args_fn_t parse_extra; +} archivers[] = { + { "tar", write_tar_archive, NULL }, + { "zip", write_zip_archive, parse_extra_zip_args }, }; static int run_remote_archiver(const char *remote, int argc, @@ -88,7 +86,10 @@ static int init_archiver(const char *name, struct archiver *ar) for (i = 0; i < ARRAY_SIZE(archivers); i++) { if (!strcmp(name, archivers[i].name)) { - memcpy(ar, &archivers[i], sizeof(struct archiver)); + memset(ar, 0, sizeof(*ar)); + ar->name = archivers[i].name; + ar->write_archive = archivers[i].write_archive; + ar->parse_extra = archivers[i].parse_extra; rv = 0; break; } -- cgit v0.10.2-6-g49f6 From c74390e4a1d78e718de72e5615b7352aeec03979 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 5 Nov 2006 11:26:21 -0800 Subject: cherry is built-in, do not ship git-cherry.sh Noticed by Rene; Makefile now has another maintainer's check target to catch this kind of mistakes. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 7c3860a..2af4eb3 100644 --- a/Makefile +++ b/Makefile @@ -932,3 +932,8 @@ check-docs:: *) echo "no link: $$v";; \ esac ; \ done | sort + +### Make sure built-ins do not have dups and listed in git.c +# +check-builtins:: + ./check-builtins.sh diff --git a/check-builtins.sh b/check-builtins.sh new file mode 100755 index 0000000..d6fe6cf --- /dev/null +++ b/check-builtins.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +{ + cat <<\EOF +sayIt: + $(foreach b,$(BUILT_INS),echo XXX $b YYY;) +EOF + cat Makefile +} | +make -f - sayIt 2>/dev/null | +sed -n -e 's/.*XXX \(.*\) YYY.*/\1/p' | +sort | +{ + bad=0 + while read builtin + do + base=`expr "$builtin" : 'git-\(.*\)'` + x=`sed -ne 's/.*{ "'$base'", \(cmd_[^, ]*\).*/'$base' \1/p' git.c` + if test -z "$x" + then + echo "$base is builtin but not listed in git.c command list" + bad=1 + fi + for sfx in sh perl py + do + if test -f "$builtin.$sfx" + then + echo "$base is builtin but $builtin.$sfx still exists" + bad=1 + fi + done + done + exit $bad +} diff --git a/git-cherry.sh b/git-cherry.sh deleted file mode 100755 index cf7af55..0000000 --- a/git-cherry.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano. -# - -USAGE='[-v] [] []' -LONG_USAGE=' __*__*__*__*__> - / - fork-point - \__+__+__+__+__+__+__+__> - -Each commit between the fork-point (or if given) and is -examined, and compared against the change each commit between the -fork-point and introduces. If the change seems to be in -the upstream, it is shown on the standard output with prefix "-". -Otherwise it is shown with prefix "+".' -. git-sh-setup - -case "$1" in -v) verbose=t; shift ;; esac - -case "$#,$1" in -1,*..*) - upstream=$(expr "z$1" : 'z\(.*\)\.\.') ours=$(expr "z$1" : '.*\.\.\(.*\)$') - set x "$upstream" "$ours" - shift ;; -esac - -case "$#" in -1) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify HEAD` || exit - limit="$upstream" - ;; -2) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify "$2"` || exit - limit="$upstream" - ;; -3) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify "$2"` && - limit=`git-rev-parse --verify "$3"` || exit - ;; -*) usage ;; -esac - -# Note that these list commits in reverse order; -# not that the order in inup matters... -inup=`git-rev-list ^$ours $upstream` && -ours=`git-rev-list $ours ^$limit` || exit - -tmp=.cherry-tmp$$ -patch=$tmp-patch -mkdir $patch -trap "rm -rf $tmp-*" 0 1 2 3 15 - -for c in $inup -do - git-diff-tree -p $c -done | git-patch-id | -while read id name -do - echo $name >>$patch/$id -done - -LF=' -' - -O= -for c in $ours -do - set x `git-diff-tree -p $c | git-patch-id` - if test "$2" != "" - then - if test -f "$patch/$2" - then - sign=- - else - sign=+ - fi - case "$verbose" in - t) - c=$(git-rev-list --pretty=oneline --max-count=1 $c) - esac - case "$O" in - '') O="$sign $c" ;; - *) O="$sign $c$LF$O" ;; - esac - fi -done -case "$O" in -'') ;; -*) echo "$O" ;; -esac -- cgit v0.10.2-6-g49f6 From 2bc45477a53b9b7a5202a816b96c14428f683c38 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 5 Nov 2006 11:47:53 -0800 Subject: git-blame: add internal statistics to count read blobs. diff --git a/blame.c b/blame.c index 8cfd5d9..d0e506c 100644 --- a/blame.c +++ b/blame.c @@ -59,6 +59,7 @@ static void get_blob(struct commit *commit); static int num_get_patch; static int num_commits; static int patch_time; +static int num_read_blob; struct blame_diff_state { struct xdiff_emit_state xm; @@ -204,6 +205,7 @@ static void get_blob(struct commit *commit) return; info->buf = read_sha1_file(info->sha1, type, &info->size); + num_read_blob++; assert(!strcmp(type, blob_type)); } @@ -910,6 +912,7 @@ int main(int argc, const char **argv) } if (DEBUG) { + printf("num read blob: %d\n", num_read_blob); printf("num get patch: %d\n", num_get_patch); printf("num commits: %d\n", num_commits); printf("patch time: %f\n", patch_time / 1000000.0); -- cgit v0.10.2-6-g49f6 From c2e525d97f81bc178567cdf4dd7056ce6224eb58 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 5 Nov 2006 11:51:41 -0800 Subject: git-pickaxe: optimize by avoiding repeated read_sha1_file(). It turns out that pickaxe reads the same blob repeatedly while blame can reuse the blob already read for the parent when handling a child commit when it's parent's turn to pass its blame to the grandparent. Have a cache in the origin structure to keep the blob there, which will be garbage collected when the origin loses the last reference to it. Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 332e6a2..f12b2d4 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -40,6 +40,11 @@ static int max_score_digits; #define DEBUG 0 #endif +/* stats */ +static int num_read_blob; +static int num_get_patch; +static int num_commits; + #define PICKAXE_BLAME_MOVE 01 #define PICKAXE_BLAME_COPY 02 #define PICKAXE_BLAME_COPY_HARDER 04 @@ -63,10 +68,25 @@ static unsigned blame_copy_score; struct origin { int refcnt; struct commit *commit; + mmfile_t file; unsigned char blob_sha1[20]; char path[FLEX_ARRAY]; }; +static char *fill_origin_blob(struct origin *o, mmfile_t *file) +{ + if (!o->file.ptr) { + char type[10]; + num_read_blob++; + file->ptr = read_sha1_file(o->blob_sha1, type, + (unsigned long *)(&(file->size))); + o->file = *file; + } + else + *file = o->file; + return file->ptr; +} + static inline struct origin *origin_incref(struct origin *o) { if (o) @@ -77,6 +97,8 @@ static inline struct origin *origin_incref(struct origin *o) static void origin_decref(struct origin *o) { if (o && --o->refcnt <= 0) { + if (o->file.ptr) + free(o->file.ptr); memset(o, 0, sizeof(*o)); free(o); } @@ -431,25 +453,14 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o, static struct patch *get_patch(struct origin *parent, struct origin *origin) { mmfile_t file_p, file_o; - char type[10]; - char *blob_p, *blob_o; struct patch *patch; - blob_p = read_sha1_file(parent->blob_sha1, type, - (unsigned long *) &file_p.size); - blob_o = read_sha1_file(origin->blob_sha1, type, - (unsigned long *) &file_o.size); - file_p.ptr = blob_p; - file_o.ptr = blob_o; - if (!file_p.ptr || !file_o.ptr) { - free(blob_p); - free(blob_o); + fill_origin_blob(parent, &file_p); + fill_origin_blob(origin, &file_o); + if (!file_p.ptr || !file_o.ptr) return NULL; - } - patch = compare_buffer(&file_p, &file_o, 0); - free(blob_p); - free(blob_o); + num_get_patch++; return patch; } @@ -784,20 +795,14 @@ static int find_move_in_parent(struct scoreboard *sb, int last_in_target, made_progress; struct blame_entry *e, split[3]; mmfile_t file_p; - char type[10]; - char *blob_p; last_in_target = find_last_in_target(sb, target); if (last_in_target < 0) return 1; /* nothing remains for this target */ - blob_p = read_sha1_file(parent->blob_sha1, type, - (unsigned long *) &file_p.size); - file_p.ptr = blob_p; - if (!file_p.ptr) { - free(blob_p); + fill_origin_blob(parent, &file_p); + if (!file_p.ptr) return 0; - } made_progress = 1; while (made_progress) { @@ -814,7 +819,6 @@ static int find_move_in_parent(struct scoreboard *sb, decref_split(split); } } - free(blob_p); return 0; } @@ -900,8 +904,6 @@ static int find_copy_in_parent(struct scoreboard *sb, struct diff_filepair *p = diff_queued_diff.queue[i]; struct origin *norigin; mmfile_t file_p; - char type[10]; - char *blob; struct blame_entry this[3]; if (!DIFF_FILE_VALID(p->one)) @@ -912,9 +914,7 @@ static int find_copy_in_parent(struct scoreboard *sb, norigin = get_origin(sb, parent, p->one->path); hashcpy(norigin->blob_sha1, p->one->sha1); - blob = read_sha1_file(norigin->blob_sha1, type, - (unsigned long *) &file_p.size); - file_p.ptr = blob; + fill_origin_blob(norigin, &file_p); if (!file_p.ptr) continue; @@ -925,7 +925,6 @@ static int find_copy_in_parent(struct scoreboard *sb, this); decref_split(this); } - free(blob); origin_decref(norigin); } @@ -953,6 +952,28 @@ static int find_copy_in_parent(struct scoreboard *sb, return retval; } +/* The blobs of origin and porigin exactly match, so everything + * origin is suspected for can be blamed on the parent. + */ +static void pass_whole_blame(struct scoreboard *sb, + struct origin *origin, struct origin *porigin) +{ + struct blame_entry *e; + + if (!porigin->file.ptr && origin->file.ptr) { + /* Steal its file */ + porigin->file = origin->file; + origin->file.ptr = NULL; + } + for (e = sb->ent; e; e = e->next) { + if (cmp_suspect(e->suspect, origin)) + continue; + origin_incref(porigin); + origin_decref(e->suspect); + e->suspect = porigin; + } +} + #define MAXPARENT 16 static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) @@ -986,13 +1007,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) if (!porigin) continue; if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { - struct blame_entry *e; - for (e = sb->ent; e; e = e->next) - if (e->suspect == origin) { - origin_incref(porigin); - origin_decref(e->suspect); - e->suspect = porigin; - } + pass_whole_blame(sb, origin, porigin); origin_decref(porigin); goto finish; } @@ -1010,6 +1025,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) } } + num_commits++; for (i = 0, parent = commit->parents; i < MAXPARENT && parent; parent = parent->next, i++) { @@ -1068,7 +1084,8 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) origin_incref(suspect); commit = suspect->commit; - parse_commit(commit); + if (!commit->object.parsed) + parse_commit(commit); if (!(commit->object.flags & UNINTERESTING) && !(revs->max_age != -1 && commit->date < revs->max_age)) pass_blame(sb, suspect, opt); @@ -1735,6 +1752,7 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) die("no such path %s in %s", path, final_commit_name); sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); + num_read_blob++; lno = prepare_lines(&sb); if (bottom < 1) @@ -1772,5 +1790,11 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) free(ent); ent = e; } + + if (DEBUG) { + printf("num read blob: %d\n", num_read_blob); + printf("num get patch: %d\n", num_get_patch); + printf("num commits: %d\n", num_commits); + } return 0; } -- cgit v0.10.2-6-g49f6 From 144d33dec97405889e302c2391300ee04aad7714 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 06:20:02 -0500 Subject: Added missing completions for show-branch and merge-base. The show-branch and merge-base commands were partially supported when it came to bash completions as they were only specified in one form another. Now we specify them in both forms. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a3fbb90..fdfbf95 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -309,6 +309,7 @@ _git () log) _git_log ;; ls-remote) _git_ls_remote ;; ls-tree) _git_ls_tree ;; + merge-base) _git_merge_base ;; pull) _git_pull ;; push) _git_push ;; reset) _git_reset ;; @@ -342,12 +343,14 @@ complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push complete -o default -F _git_reset git-reset complete -o default -F _git_show git-show +complete -o default -o nospace -F _git_log git-show-branch complete -o default -o nospace -F _git_log git-whatchanged # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # +complete -o default -o nospace -F _git git.exe complete -o default -F _git_branch git-branch.exe complete -o default -o nospace -F _git_cat_file git-cat-file.exe complete -o default -o nospace -F _git_diff git-diff.exe @@ -356,4 +359,5 @@ complete -o default -o nospace -F _git_log git-log.exe complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe complete -o default -F _git_merge_base git-merge-base.exe complete -o default -o nospace -F _git_push git-push.exe +complete -o default -o nospace -F _git_log git-show-branch.exe complete -o default -o nospace -F _git_log git-whatchanged.exe -- cgit v0.10.2-6-g49f6 From 76c3eb51ede4619116ef980aa34d087c97c25cbc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 06:20:25 -0500 Subject: Only load .exe suffix'd completions on Cygwin. The only platform which actually needs to define .exe suffixes as part of its completion set is Cygwin. So don't define them on any other platform. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index fdfbf95..926638d 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -350,6 +350,7 @@ complete -o default -o nospace -F _git_log git-whatchanged # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # +if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then complete -o default -o nospace -F _git git.exe complete -o default -F _git_branch git-branch.exe complete -o default -o nospace -F _git_cat_file git-cat-file.exe @@ -361,3 +362,4 @@ complete -o default -F _git_merge_base git-merge-base.exe complete -o default -o nospace -F _git_push git-push.exe complete -o default -o nospace -F _git_log git-show-branch.exe complete -o default -o nospace -F _git_log git-whatchanged.exe +fi -- cgit v0.10.2-6-g49f6 From 56fc25f21e280c4f3816e989b34d08d7d7cc59fc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 06:21:03 -0500 Subject: Bash completion support for remotes in .git/config. Now that Git natively supports remote specifications within the config file such as: [remote "origin"] url = ... we should provide bash completion support "out of the box" for these remotes, just like we do for the .git/remotes directory. Also cleaned up the __git_aliases expansion to use the same form of querying and filtering repo-config as this saves two fork/execs in the middle of a user prompted completion. Finally also forced the variable 'word' to be local within __git_aliased_command. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 926638d..5f1be46 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -59,12 +59,21 @@ __git_refs2 () __git_remotes () { - local i REVERTGLOB=$(shopt -p nullglob) + local i ngoff IFS=$'\n' + shopt -q nullglob || ngoff=1 shopt -s nullglob for i in .git/remotes/*; do echo ${i#.git/remotes/} done - $REVERTGLOB + [ "$ngoff" ] && shopt -u nullglob + for i in $(git repo-config --list); do + case "$i" in + remote.*.url=*) + i="${i#remote.}" + echo "${i/.url=*/}" + ;; + esac + done } __git_complete_file () @@ -103,13 +112,20 @@ __git_complete_file () __git_aliases () { - git repo-config --list | grep '^alias\.' \ - | sed -e 's/^alias\.//' -e 's/=.*$//' + local i IFS=$'\n' + for i in $(git repo-config --list); do + case "$i" in + alias.*) + i="${i#alias.}" + echo "${i/=*/}" + ;; + esac + done } __git_aliased_command () { - local cmdline=$(git repo-config alias.$1) + local word cmdline=$(git repo-config --get "alias.$1") for word in $cmdline; do if [ "${word##-*}" ]; then echo $word -- cgit v0.10.2-6-g49f6 From 873537fadc9bdc35726d1c69c46926c7f5c49dd2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 06:21:57 -0500 Subject: Take --git-dir into consideration during bash completion. If the user has setup a command line of "git --git-dir=baz" then anything we complete must be performed within the scope of "baz" and not the current working directory. This is useful with commands such as "git --git-dir=git.git log m" to complete out "master" and view the log for the master branch of the git.git repository. As a nice side effect this also works for aliases within the target repository, just as git would honor them. Unfortunately because we still examine arguments by absolute position in most of the more complex commands (e.g. git push) using --git-dir with those commands will probably still cause completion to fail. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 5f1be46..f258f2f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -19,15 +19,20 @@ # source ~/.git-completion.sh # +__gitdir () +{ + echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}" +} + __git_refs () { - local cmd i is_hash=y - if [ -d "$1" ]; then + local cmd i is_hash=y dir="${1:-$(__gitdir)}" + if [ -d "$dir" ]; then cmd=git-peek-remote else cmd=git-ls-remote fi - for i in $($cmd "$1" 2>/dev/null); do + for i in $($cmd "$dir" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -40,13 +45,13 @@ __git_refs () __git_refs2 () { - local cmd i is_hash=y - if [ -d "$1" ]; then + local cmd i is_hash=y dir="${1:-$(__gitdir)}" + if [ -d "$dir" ]; then cmd=git-peek-remote else cmd=git-ls-remote fi - for i in $($cmd "$1" 2>/dev/null); do + for i in $($cmd "$dir" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -59,14 +64,14 @@ __git_refs2 () __git_remotes () { - local i ngoff IFS=$'\n' + local i ngoff IFS=$'\n' d="$(__gitdir)" shopt -q nullglob || ngoff=1 shopt -s nullglob - for i in .git/remotes/*; do - echo ${i#.git/remotes/} + for i in "$d/remotes"/*; do + echo ${i#$d/remotes/} done [ "$ngoff" ] && shopt -u nullglob - for i in $(git repo-config --list); do + for i in $(git --git-dir="$d" repo-config --list); do case "$i" in remote.*.url=*) i="${i#remote.}" @@ -95,7 +100,7 @@ __git_complete_file () ;; esac COMPREPLY=($(compgen -P "$pfx" \ - -W "$(git-ls-tree "$ls" \ + -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \ | sed '/^100... blob /s,^.* ,, /^040000 tree /{ s,^.* ,, @@ -105,7 +110,7 @@ __git_complete_file () -- "$cur")) ;; *) - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) ;; esac } @@ -113,7 +118,7 @@ __git_complete_file () __git_aliases () { local i IFS=$'\n' - for i in $(git repo-config --list); do + for i in $(git --git-dir="$(__gitdir)" repo-config --list); do case "$i" in alias.*) i="${i#alias.}" @@ -125,7 +130,8 @@ __git_aliases () __git_aliased_command () { - local word cmdline=$(git repo-config --get "alias.$1") + local word cmdline=$(git --git-dir="$(__gitdir)" \ + repo-config --get "alias.$1") for word in $cmdline; do if [ "${word##-*}" ]; then echo $word @@ -137,7 +143,7 @@ __git_aliased_command () _git_branch () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs)" -- "$cur")) } _git_cat_file () @@ -159,7 +165,7 @@ _git_cat_file () _git_checkout () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur")) } _git_diff () @@ -170,7 +176,7 @@ _git_diff () _git_diff_tree () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "-r -p -M $(__git_refs)" -- "$cur")) } _git_fetch () @@ -188,7 +194,7 @@ _git_fetch () case "$cur" in *:*) cur=$(echo "$cur" | sed 's/^.*://') - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) ;; *) local remote @@ -221,10 +227,10 @@ _git_log () *..*) local pfx=$(echo "$cur" | sed 's/\.\..*$/../') cur=$(echo "$cur" | sed 's/^.*\.\.//') - COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) ;; *) - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) ;; esac } @@ -232,7 +238,7 @@ _git_log () _git_merge_base () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git_pull () @@ -280,7 +286,7 @@ _git_push () COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) ;; *) - COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs2)" -- "$cur")) ;; esac ;; @@ -291,56 +297,67 @@ _git_reset () { local cur="${COMP_WORDS[COMP_CWORD]}" local opt="--mixed --hard --soft" - COMPREPLY=($(compgen -W "$opt $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur")) } _git_show () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git () { - if [ $COMP_CWORD = 1 ]; then + local i c=1 command __git_dir + + while [ $c -lt $COMP_CWORD ]; do + i="${COMP_WORDS[c]}" + case "$i" in + --git-dir=*) __git_dir="${i#--git-dir=}" ;; + --bare) __git_dir="." ;; + --version|--help|-p|--paginate) ;; + *) command="$i"; break ;; + esac + c=$((++c)) + done + + if [ $c -eq $COMP_CWORD -a -z "$command" ]; then COMPREPLY=($(compgen \ - -W "--version $(git help -a|egrep '^ ') \ + -W "--git-dir= --version \ + $(git help -a|egrep '^ ') \ $(__git_aliases)" \ -- "${COMP_WORDS[COMP_CWORD]}")) - else - local command="${COMP_WORDS[1]}" - local expansion=$(__git_aliased_command "$command") + return; + fi - if [ "$expansion" ]; then - command="$expansion" - fi + local expansion=$(__git_aliased_command "$command") + [ "$expansion" ] && command="$expansion" - case "$command" in - branch) _git_branch ;; - cat-file) _git_cat_file ;; - checkout) _git_checkout ;; - diff) _git_diff ;; - diff-tree) _git_diff_tree ;; - fetch) _git_fetch ;; - log) _git_log ;; - ls-remote) _git_ls_remote ;; - ls-tree) _git_ls_tree ;; - merge-base) _git_merge_base ;; - pull) _git_pull ;; - push) _git_push ;; - reset) _git_reset ;; - show) _git_show ;; - show-branch) _git_log ;; - whatchanged) _git_log ;; - *) COMPREPLY=() ;; - esac - fi + case "$command" in + branch) _git_branch ;; + cat-file) _git_cat_file ;; + checkout) _git_checkout ;; + diff) _git_diff ;; + diff-tree) _git_diff_tree ;; + fetch) _git_fetch ;; + log) _git_log ;; + ls-remote) _git_ls_remote ;; + ls-tree) _git_ls_tree ;; + merge-base) _git_merge_base ;; + pull) _git_pull ;; + push) _git_push ;; + reset) _git_reset ;; + show) _git_show ;; + show-branch) _git_log ;; + whatchanged) _git_log ;; + *) COMPREPLY=() ;; + esac } _gitk () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "--all $(__git_refs)" -- "$cur")) } complete -o default -o nospace -F _git git -- cgit v0.10.2-6-g49f6 From e5d5b21fdf0ec0aebbfaca814a15d15a718544a2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 06:24:56 -0500 Subject: Support bash completion on symmetric difference operator. Now that log, whatchanged, rev-list, etc. support the symmetric difference operator '...' we should provide bash completion for it just like we do for '..'. While we are at it we can remove two sed invocations during the interactive prompt and replace them with internal bash operations. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index f258f2f..e4a32b6 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -222,11 +222,16 @@ _git_ls_tree () _git_log () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local pfx cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in + *...*) + pfx="${cur%...*}..." + cur="${cur#*...}" + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + ;; *..*) - local pfx=$(echo "$cur" | sed 's/\.\..*$/../') - cur=$(echo "$cur" | sed 's/^.*\.\.//') + pfx="${cur%..*}.." + cur="${cur#*..}" COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) ;; *) -- cgit v0.10.2-6-g49f6 From a79c6551a315b26f2461dd9734defce88014b77b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 5 Nov 2006 06:25:25 -0500 Subject: Remove more sed invocations from within bash completion. This change removes between 1 and 4 sed invocations per completion entered by the user. In the case of cat-file the 4 invocations per completion can take a while on Cygwin; running these replacements directly within bash saves some time for the end user. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e4a32b6..a43a177 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -83,15 +83,15 @@ __git_remotes () __git_complete_file () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in ?*:*) - local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')" - cur="$(echo "$cur" | sed 's,^.*:,,')" + ref="${cur%%:*}" + cur="${cur#*:}" case "$cur" in ?*/*) - pfx="$(echo "$cur" | sed 's,/[^/]*$,,')" - cur="$(echo "$cur" | sed 's,^.*/,,')" + pfx="${cur%/*}" + cur="${cur##*/}" ls="$ref:$pfx" pfx="$pfx/" ;; @@ -193,7 +193,7 @@ _git_fetch () *) case "$cur" in *:*) - cur=$(echo "$cur" | sed 's/^.*://') + cur="${cur#*:}" COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) ;; *) @@ -287,7 +287,7 @@ _git_push () git-push) remote="${COMP_WORDS[1]}" ;; git) remote="${COMP_WORDS[2]}" ;; esac - cur=$(echo "$cur" | sed 's/^.*://') + cur="${cur#*:}" COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) ;; *) -- cgit v0.10.2-6-g49f6 From 49b8b2926fdcc4322445f0a3bda459e81cd98e9a Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 5 Nov 2006 17:22:15 -0500 Subject: Fix git-runstatus for repositories containing a file named HEAD The wt_status_print_updated() and wt_status_print_untracked() routines call setup_revisions() with 'HEAD' being the reference to the tip of the current branch. However, setup_revisions() gets confused if the branch also contains a file named 'HEAD' resulting in a fatal error. Instead, don't pass an argv to setup_revisions() at all; simply give it no arguments, and make 'HEAD' the default revision. Bug noticed by Rocco Rutte . Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/wt-status.c b/wt-status.c index 4b74e68..68ecb0b 100644 --- a/wt-status.c +++ b/wt-status.c @@ -156,10 +156,8 @@ void wt_status_print_initial(struct wt_status *s) static void wt_status_print_updated(struct wt_status *s) { struct rev_info rev; - const char *argv[] = { NULL, NULL, NULL }; - argv[1] = s->reference; init_revisions(&rev, NULL); - setup_revisions(2, argv, &rev, NULL); + setup_revisions(0, NULL, &rev, s->reference); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_print_updated_cb; rev.diffopt.format_callback_data = s; @@ -170,9 +168,8 @@ static void wt_status_print_updated(struct wt_status *s) static void wt_status_print_changed(struct wt_status *s) { struct rev_info rev; - const char *argv[] = { NULL, NULL }; init_revisions(&rev, ""); - setup_revisions(1, argv, &rev, NULL); + setup_revisions(0, NULL, &rev, NULL); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_print_changed_cb; rev.diffopt.format_callback_data = s; @@ -227,10 +224,8 @@ static void wt_status_print_untracked(const struct wt_status *s) static void wt_status_print_verbose(struct wt_status *s) { struct rev_info rev; - const char *argv[] = { NULL, NULL, NULL }; - argv[1] = s->reference; init_revisions(&rev, NULL); - setup_revisions(2, argv, &rev, NULL); + setup_revisions(0, NULL, &rev, s->reference); rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.diffopt.detect_rename = 1; run_diff_index(&rev, 1); -- cgit v0.10.2-6-g49f6 From fe732edee10f9b241c8df9b4ea081a4f33ad3968 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 4 Nov 2006 12:24:05 -0800 Subject: adjust_shared_perm: chmod() only when needed. When widening permission for files and directories in a 'shared' repository for a user with inappropriate umask() setting for shared work, make sure we call chmod() only when we actually need to. The primary idea owes credit to Johannes. Signed-off-by: Junio C Hamano diff --git a/path.c b/path.c index bb89fb0..d2c076d 100644 --- a/path.c +++ b/path.c @@ -279,7 +279,7 @@ int adjust_shared_perm(const char *path) : 0)); if (S_ISDIR(mode)) mode |= S_ISGID; - if (chmod(path, mode) < 0) + if ((mode & st.st_mode) != mode && chmod(path, mode) < 0) return -2; return 0; } -- cgit v0.10.2-6-g49f6 From 9faed78f59046cb7cef7f132726bf81c5074b08d Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Mon, 6 Nov 2006 11:38:52 +0100 Subject: merge-recursive implicitely depends on trust_executable_bit Read the configuration in to get core.filemode value for this particular repository. Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 2ba43ae..c81048d 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1308,6 +1308,7 @@ int main(int argc, char *argv[]) const char *branch1, *branch2; struct commit *result, *h1, *h2; + git_config(git_default_config); /* core.filemode */ original_index_file = getenv("GIT_INDEX_FILE"); if (!original_index_file) -- cgit v0.10.2-6-g49f6 From d28f7cb93537554e069667602a7624952e06df50 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 6 Nov 2006 00:28:52 -0800 Subject: Document git-pack-refs and link it to git(7). Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt new file mode 100644 index 0000000..5da5105 --- /dev/null +++ b/Documentation/git-pack-refs.txt @@ -0,0 +1,54 @@ +git-pack-refs(1) +================ + +NAME +---- +git-pack-refs - Pack heads and tags for efficient repository access + +SYNOPSIS +-------- +'git-pack-refs' [--all] [--prune] + +DESCRIPTION +----------- + +Traditionally, tips of branches and tags (collectively known as +'refs') were stored one file per ref under `$GIT_DIR/refs` +directory. While many branch tips tend to be updated often, +most tags and some branch tips are never updated. When a +repository has hundreds or thousands of tags, this +one-file-per-ref format both wastes storage and hurts +performance. + +This command is used to solve the storage and performance +problem by stashing the refs in a single file, +`$GIT_DIR/packed-refs`. When a ref is missing from the +traditional `$GIT_DIR/refs` hierarchy, it is looked up in this +file and used if found. + +Subsequent updates to branches always creates new file under +`$GIT_DIR/refs` hierarchy. + +OPTIONS +------- + +\--all:: + +The command by default packs all tags and leaves branch tips +alone. This is because branches are expected to be actively +developed and packing their tips does not help performance. +This option causes branch tips to be packed as well. Useful for +a repository with many branches of historical interests. + +\--prune:: + +After packing the refs, remove loose refs under `$GIT_DIR/refs` +hierarchy. This should probably become default. + +Author +------ +Written by Linus Torvalds + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index 0679e3c..4ce4f8d 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -141,6 +141,9 @@ gitlink:git-merge[1]:: gitlink:git-mv[1]:: Move or rename a file, a directory, or a symlink. +gitlink:git-pack-refs[1]:: + Pack heads and tags for efficient repository access. + gitlink:git-pull[1]:: Fetch from and merge with a remote repository or a local branch. @@ -424,6 +427,9 @@ gitlink:git-rev-list[1]:: gitlink:git-show-index[1]:: Displays contents of a pack idx file. +gitlink:git-show-ref[1]:: + List references in a local repository. + gitlink:git-tar-tree[1]:: Creates a tar archive of the files in the named tree object. -- cgit v0.10.2-6-g49f6 From e52775f43857f377aa2aa69f82ac2d2f26dc6297 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 6 Nov 2006 19:12:45 +0100 Subject: Documentation: Transplanting branch with git-rebase --onto Added example of transplantig feature branch from one development branch (for example "next") into the other development branch (for example "master"). [jc: talking Carl's advice this contains both examples sent to the list by Jakub in his original message.] Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 9d7bcaa..878eb6f 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -51,20 +51,69 @@ would be: D---E---F---G master ------------ -While, starting from the same point, the result of either of the following -commands: +The latter form is just a short-hand of `git checkout topic` +followed by `git rebase master`. - git-rebase --onto master~1 master - git-rebase --onto master~1 master topic +Here is how you would transplant a topic branch based on one +branch to another, to pretend that you forked the topic branch +from the latter branch, using `rebase --onto`. -would be: +First let's assume your 'topic' is based on branch 'next'. +For example feature developed in 'topic' depends on some +functionality which is found in 'next'. ------------ - A'--B'--C' topic - / - D---E---F---G master + o---o---o---o---o master + \ + o---o---o---o---o next + \ + o---o---o topic +------------ + +We would want to make 'topic' forked from branch 'master', +for example because the functionality 'topic' branch depend on +got merged into more stable 'master' branch, like this: + +------------ + o---o---o---o---o master + | \ + | o'--o'--o' topic + \ + o---o---o---o---o next ------------ +We can get this using the following command: + + git-rebase --onto master next topic + + +Another example of --onto option is to rebase part of a +branch. If we have the following situation: + +------------ + H---I---J topicB + / + E---F---G topicA + / + A---B---C---D master +------------ + +then the command + + git-rebase --onto master topicA topicB + +would result in: + +------------ + H'--I'--J' topicB + / + | E---F---G topicA + |/ + A---B---C---D master +------------ + +This is useful when topicB does not depend on topicA. + In case of conflict, git-rebase will stop at the first problematic commit and leave conflict markers in the tree. You can use git diff to locate the markers (<<<<<<) and make edits to resolve the conflict. For each -- cgit v0.10.2-6-g49f6 From 931233bc666b40de039d50f82f12279c94e5e5f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 6 Nov 2006 17:08:32 -0800 Subject: git-pickaxe: -L /regexp/,/regexp/ With this change, you can specify the beginning and the ending line of the range you wish to inspect with pattern matching. For example, these are equivalent with the git.git sources: git pickaxe -L 7,21 v1.4.0 -- commit.c git pickaxe -L '/^struct sort_node/,/^}/' v1.4.0 -- commit.c git pickaxe -L '7,/^}/' v1.4.0 -- commit.c git pickaxe -L '/^struct sort_node/,21' v1.4.0 -- commit.c Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index f12b2d4..673185f 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -17,6 +17,7 @@ #include #include +#include static char pickaxe_usage[] = "git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [commit] [--] file\n" @@ -1533,6 +1534,78 @@ static const char *add_prefix(const char *prefix, const char *path) return prefix_path(prefix, strlen(prefix), path); } +static const char *parse_loc(const char *spec, + struct scoreboard *sb, long lno, + long begin, long *ret) +{ + char *term; + const char *line; + long num; + int reg_error; + regex_t regexp; + regmatch_t match[1]; + + num = strtol(spec, &term, 10); + if (term != spec) { + *ret = num; + return term; + } + if (spec[0] != '/') + return spec; + + /* it could be a regexp of form /.../ */ + for (term = (char*) spec + 1; *term && *term != '/'; term++) { + if (*term == '\\') + term++; + } + if (*term != '/') + return spec; + + /* try [spec+1 .. term-1] as regexp */ + *term = 0; + begin--; /* input is in human terms */ + line = nth_line(sb, begin); + + if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && + !(reg_error = regexec(®exp, line, 1, match, 0))) { + const char *cp = line + match[0].rm_so; + const char *nline; + + while (begin++ < lno) { + nline = nth_line(sb, begin); + if (line <= cp && cp < nline) + break; + line = nline; + } + *ret = begin; + regfree(®exp); + *term++ = '/'; + return term; + } + else { + char errbuf[1024]; + regerror(reg_error, ®exp, errbuf, 1024); + die("-L parameter '%s': %s", spec + 1, errbuf); + } +} + +static void prepare_blame_range(struct scoreboard *sb, + const char *bottomtop, + long lno, + long *bottom, long *top) +{ + const char *term; + + term = parse_loc(bottomtop, sb, lno, 1, bottom); + if (*term == ',') { + term = parse_loc(term + 1, sb, lno, *bottom + 1, top); + if (*term) + usage(pickaxe_usage); + } + if (*term) + usage(pickaxe_usage); +} + int cmd_pickaxe(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1546,11 +1619,11 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) const char *revs_file = NULL; const char *final_commit_name = NULL; char type[10]; + const char *bottomtop = NULL; save_commit_buffer = 0; opt = 0; - bottom = top = 0; seen_dashdash = 0; for (unk = i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -1575,7 +1648,6 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) blame_copy_score = parse_score(arg+2); } else if (!strncmp("-L", arg, 2)) { - char *term; if (!arg[2]) { if (++i >= argc) usage(pickaxe_usage); @@ -1583,18 +1655,9 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) } else arg += 2; - if (bottom || top) + if (bottomtop) die("More than one '-L n,m' option given"); - bottom = strtol(arg, &term, 10); - if (*term == ',') { - top = strtol(term + 1, &term, 10); - if (*term) - usage(pickaxe_usage); - } - if (bottom && top && top < bottom) { - unsigned long tmp; - tmp = top; top = bottom; bottom = tmp; - } + bottomtop = arg; } else if (!strcmp("--score-debug", arg)) output_option |= OUTPUT_SHOW_SCORE; @@ -1755,6 +1818,13 @@ int cmd_pickaxe(int argc, const char **argv, const char *prefix) num_read_blob++; lno = prepare_lines(&sb); + bottom = top = 0; + if (bottomtop) + prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); + if (bottom && top && top < bottom) { + long tmp; + tmp = top; top = bottom; bottom = tmp; + } if (bottom < 1) bottom = 1; if (top < 1) -- cgit v0.10.2-6-g49f6 From 0623421b0beb5dc4bbe4eb598275e2a608226dec Mon Sep 17 00:00:00 2001 From: Tero Roponen Date: Tue, 7 Nov 2006 12:44:33 +0200 Subject: remove an unneeded test In wt-status.c there is a test which does nothing. This patch removes it. Signed-off-by: Tero Roponen Signed-off-by: Junio C Hamano diff --git a/wt-status.c b/wt-status.c index 68ecb0b..6742844 100644 --- a/wt-status.c +++ b/wt-status.c @@ -104,8 +104,6 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q, struct wt_status *s = data; int shown_header = 0; int i; - if (q->nr) { - } for (i = 0; i < q->nr; i++) { if (q->queue[i]->status == 'U') continue; -- cgit v0.10.2-6-g49f6 From 231f240b63bd6cb1313e8952448b3d5b9d2fdf26 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 7 Nov 2006 10:51:23 -0500 Subject: git-pack-objects progress flag documentation and cleanup This adds documentation for --progress and --all-progress, remove a duplicate --progress handling and make usage string more readable. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 5ebe34e..fdc6f97 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -99,6 +99,23 @@ base-name:: Only create a packed archive if it would contain at least one object. +--progress:: + Progress status is reported on the standard error stream + by default when it is attached to a terminal, unless -q + is specified. This flag forces progress status even if + the standard error stream is not directed to a terminal. + +--all-progress:: + When --stdout is specified then progress report is + displayed during the object count and deltification phases + but inhibited during the write-out phase. The reason is + that in some cases the output stream is directly linked + to another command which may wish to display progress + status of its own as it processes incoming pack data. + This flag is like --progress except that it forces progress + report for the write-out phase as well even if --stdout is + used. + -q:: This flag makes the command not to report its progress on the standard error stream. diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 270bcbd..69e5dd3 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -15,7 +15,12 @@ #include #include -static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--all-progress] [--revs [--unpacked | --all]*] [--stdout | base-name] Date: Tue, 7 Nov 2006 16:20:02 -0800 Subject: git-pickaxe: allow "-L ,+N" With this, git pickaxe -L '/--progress/,+20' v1.4.0 -- pack-objects.c gives you 20 lines starting from the first occurrence of '--progress' in pack-objects, digging from v1.4.0 version. You can also say git pickaxe -L '/--progress/,-5' v1.4.0 -- pack-objects.c Signed-off-by: Junio C Hamano diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c index 673185f..64999f3 100644 --- a/builtin-pickaxe.c +++ b/builtin-pickaxe.c @@ -1545,6 +1545,25 @@ static const char *parse_loc(const char *spec, regex_t regexp; regmatch_t match[1]; + /* Allow "-L ,+20" to mean starting at + * for 20 lines, or "-L ,-5" for 5 lines ending at + * . + */ + if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { + num = strtol(spec + 1, &term, 10); + if (term != spec + 1) { + if (spec[0] == '-') + num = 0 - num; + if (0 < num) + *ret = begin + num - 2; + else if (!num) + *ret = begin; + else + *ret = begin + num; + return term; + } + return spec; + } num = strtol(spec, &term, 10); if (term != spec) { *ret = num; -- cgit v0.10.2-6-g49f6 From baf0bfcb4b335438e9359835f2c27cccf20e54a3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 7 Nov 2006 16:36:11 -0800 Subject: GIT 1.4.3-rc1 Signed-off-by: Junio C Hamano diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 0cacac3..9796e91 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.4.3.GIT +DEF_VER=v1.4.4-rc1.GIT LF=' ' -- cgit v0.10.2-6-g49f6 From 5dd5ed09fe6b5508cf29dd1dbf40bea2f0d16ad3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 7 Nov 2006 22:00:45 -0800 Subject: gitweb: fix disabling of "forks" Apparently this code was never tested without "forks". check-feature returns a one-element list (0) when disabled, and assigning that to a scalar variable made it to be called in a scalar context, which meant my $check_forks = gitweb_check_feature("forks") were always 1! Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3c6fd7c..680832c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -926,7 +926,7 @@ sub git_get_projects_list { $dir =~ s!/+$!!; my $pfxlen = length("$dir"); - my $check_forks = gitweb_check_feature('forks'); + my ($check_forks) = gitweb_check_feature('forks'); File::Find::find({ follow_fast => 1, # follow symbolic links @@ -2212,7 +2212,7 @@ sub git_patchset_body { sub git_project_list_body { my ($projlist, $order, $from, $to, $extra, $no_header) = @_; - my $check_forks = gitweb_check_feature('forks'); + my ($check_forks) = gitweb_check_feature('forks'); my @projects; foreach my $pr (@$projlist) { @@ -2614,7 +2614,9 @@ sub git_summary { my @taglist = git_get_tags_list(15); my @headlist = git_get_heads_list(15); my @forklist; - if (gitweb_check_feature('forks')) { + my ($check_forks) = gitweb_check_feature('forks'); + + if ($check_forks) { @forklist = git_get_projects_list($project); } -- cgit v0.10.2-6-g49f6 From 83ee94c12ca16ef020f3d71eda34b6559ed6dc67 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 7 Nov 2006 22:37:17 -0800 Subject: gitweb: minimally fix "fork" support. A forked project is defined to be $projname/$forkname.git for $projname.git; the code did not check this correctly and mistook $projname/.git to be a fork of itself. This minimally fixes the breakage. Also forks were not checked when index.aux file was in use. Listing the forked ones in index.aux would show them also on the toplevel index which may go against the hierarchical nature of forks, but again this is a minimal fix to whip it in a better shape suitable to be in the 'master' branch. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 680832c..c46629f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -962,6 +962,17 @@ sub git_get_projects_list { if (!defined $path) { next; } + if ($filter ne '') { + # looking for forks; + my $pfx = substr($path, 0, length($filter)); + if ($pfx ne $filter) { + next; + } + my $sfx = substr($path, length($filter)); + if ($sfx !~ /^\/.*\.git$/) { + next; + } + } if (check_export_ok("$projectroot/$path")) { my $pr = { path => $path, @@ -2230,8 +2241,14 @@ sub git_project_list_body { } if ($check_forks) { my $pname = $pr->{'path'}; - $pname =~ s/\.git$//; - $pr->{'forks'} = -d "$projectroot/$pname"; + if (($pname =~ s/\.git$//) && + ($pname !~ /\/$/) && + (-d "$projectroot/$pname")) { + $pr->{'forks'} = "-d $projectroot/$pname"; + } + else { + $pr->{'forks'} = 0; + } } push @projects, $pr; } @@ -2297,6 +2314,7 @@ sub git_project_list_body { if ($check_forks) { print ""; if ($pr->{'forks'}) { + print "\n"; print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+"); } print "\n"; -- cgit v0.10.2-6-g49f6 From 403d0906e9876871a7913554be4caa93f21f1d15 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 8 Nov 2006 11:48:56 +0100 Subject: gitweb: Better git-unquoting and gitweb-quoting of pathnames Extend unquote subroutine, which unquotes quoted and escaped filenames which git may return, to deal not only with octal char sequence quoting, but also quoting ordinary characters including '\"' and '\\' which are respectively quoted '"' and '\', and to deal also with C escape sequences including '\t' for TAB and '\n' for LF. Add esc_path subroutine for gitweb quoting and HTML escaping filenames (currently it does equivalent of ls' --hide-control-chars, which means showing undisplayable characters (including '\n' and '\t') as '?' (question mark) character, and use 'span' element with cntrl CSS class to help rendering them differently. Convert gitweb to use esc_path correctly to print pathnames. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 0eda982..e19e6bc 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -16,6 +16,13 @@ a:hover, a:visited, a:active { color: #880000; } +span.cntrl { + border: dashed #aaaaaa; + border-width: 1px; + padding: 0px 2px 0px 2px; + margin: 0px 2px 0px 2px; +} + img.logo { float: right; border-width: 0px; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c46629f..fcf255d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -584,12 +584,46 @@ sub esc_html ($;%) { return $str; } +# quote control characters and escape filename to HTML +sub esc_path { + my $str = shift; + $str = esc_html($str); + $str =~ s|([[:cntrl:]])|?|g; + return $str; +} + # git may return quoted and escaped filenames sub unquote { my $str = shift; + + sub unq { + my $seq = shift; + my %es = ( # character escape codes, aka escape sequences + 't' => "\t", # tab (HT, TAB) + 'n' => "\n", # newline (NL) + 'r' => "\r", # return (CR) + 'f' => "\f", # form feed (FF) + 'b' => "\b", # backspace (BS) + 'a' => "\a", # alarm (bell) (BEL) + 'e' => "\e", # escape (ESC) + 'v' => "\013", # vertical tab (VT) + ); + + if ($seq =~ m/^[0-7]{1,3}$/) { + # octal char sequence + return chr(oct($seq)); + } elsif (exists $es{$seq}) { + # C escape sequence, aka character escape code + return $es{$seq} + } + # quoted ordinary character + return $seq; + } + if ($str =~ m/^"(.*)"$/) { + # needs unquoting $str = $1; - $str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg; + $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg; } return $str; } @@ -1520,7 +1554,7 @@ sub git_header_html { if (defined $action) { $title .= "/$action"; if (defined $file_name) { - $title .= " - " . esc_html($file_name); + $title .= " - " . esc_path($file_name); if ($action eq "tree" && $file_name !~ m|/$|) { $title .= "/"; } @@ -1791,20 +1825,20 @@ sub git_print_page_path { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => $fullname}, esc_html($dir)); + -title => $fullname}, esc_path($dir)); print " / "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename)); + -title => $name}, esc_path($basename)); } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename)); + -title => $name}, esc_path($basename)); print " / "; } else { - print esc_html($basename); + print esc_path($basename); } } print "
\n"; @@ -1876,7 +1910,7 @@ sub git_print_tree_entry { print "" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key), - -class => "list"}, esc_html($t->{'name'})) . "\n"; + -class => "list"}, esc_path($t->{'name'})) . "\n"; print ""; print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key)}, @@ -1903,7 +1937,7 @@ sub git_print_tree_entry { print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key)}, - esc_html($t->{'name'})); + esc_path($t->{'name'})); print "\n"; print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, @@ -1968,7 +2002,7 @@ sub git_difftree_body { print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + -class => "list"}, esc_path($diff{'file'})); print "\n"; print "$mode_chng\n"; print ""; @@ -1984,7 +2018,7 @@ sub git_difftree_body { print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + -class => "list"}, esc_path($diff{'file'})); print "\n"; print "$mode_chng\n"; print ""; @@ -2024,7 +2058,7 @@ sub git_difftree_body { print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + -class => "list"}, esc_path($diff{'file'})); print "\n"; print "$mode_chnge\n"; print ""; @@ -2064,11 +2098,11 @@ sub git_difftree_body { print "" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}), - -class => "list"}, esc_html($diff{'to_file'})) . "\n" . + -class => "list"}, esc_path($diff{'to_file'})) . "\n" . "[$nstatus from " . $cgi->a({-href => href(action=>"blob", hash_base=>$parent, hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}), - -class => "list"}, esc_html($diff{'from_file'})) . + -class => "list"}, esc_path($diff{'from_file'})) . " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]\n" . ""; if ($action eq 'commitdiff') { @@ -2191,7 +2225,7 @@ sub git_patchset_body { $file ||= $diffinfo->{'file'}; $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, hash=>$diffinfo->{'from_id'}, file_name=>$file), - -class => "list"}, esc_html($file)); + -class => "list"}, esc_path($file)); $patch_line =~ s|a/.*$|a/$file|g; print "
$patch_line
\n"; @@ -2203,7 +2237,7 @@ sub git_patchset_body { $file ||= $diffinfo->{'file'}; $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash, hash=>$diffinfo->{'to_id'}, file_name=>$file), - -class => "list"}, esc_html($file)); + -class => "list"}, esc_path($file)); $patch_line =~ s|b/.*|b/$file|g; print "
$patch_line
\n"; @@ -3521,8 +3555,8 @@ sub git_blobdiff { } else { while (my $line = <$fd>) { - $line =~ s!a/($hash|$hash_parent)!'a/'.esc_html($diffinfo{'from_file'})!eg; - $line =~ s!b/($hash|$hash_parent)!'b/'.esc_html($diffinfo{'to_file'})!eg; + $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg; + $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg; print $line; @@ -3879,7 +3913,7 @@ sub git_search { print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, hash=>$set{'id'}, file_name=>$set{'file'}), -class => "list"}, - "" . esc_html($set{'file'}) . "") . + "" . esc_path($set{'file'}) . "") . "
\n"; } print "\n" . @@ -4014,7 +4048,7 @@ XML if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { next; } - my $file = esc_html(unquote($7)); + my $file = esc_path(unquote($7)); $file = to_utf8($file); print "$file
\n"; } -- cgit v0.10.2-6-g49f6 From 1d3bc0cc0aaa056ae07a08bccc056e62d42046e8 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 8 Nov 2006 11:50:07 +0100 Subject: gitweb: Use character or octal escape codes (and add span.cntrl) in esc_path Instead of simply hiding control characters in esc_path by replacing them with '?', use Character Escape Codes (CEC) i.e. alphabetic backslash sequences like those found in C programming language and many other languages influenced by it, such as Java and Perl. If control characted doesn't have corresponding character escape code, use octal char sequence to escape it. Alternatively, controls can be replaced with Unicode Control Pictures U+2400 - U+243F (9216 - 9279), the Unicode characters reserved for representing control characters when it is necessary to print or display them. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fcf255d..b80fc60 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -584,11 +584,39 @@ sub esc_html ($;%) { return $str; } +# Make control characterss "printable". +sub quot_cec { + my $cntrl = shift; + my %es = ( # character escape codes, aka escape sequences + "\t" => '\t', # tab (HT) + "\n" => '\n', # line feed (LF) + "\r" => '\r', # carrige return (CR) + "\f" => '\f', # form feed (FF) + "\b" => '\b', # backspace (BS) + "\a" => '\a', # alarm (bell) (BEL) + "\e" => '\e', # escape (ESC) + "\013" => '\v', # vertical tab (VT) + "\000" => '\0', # nul character (NUL) + ); + my $chr = ( (exists $es{$cntrl}) + ? $es{$cntrl} + : sprintf('\%03o', ord($cntrl)) ); + return "$chr"; +} + +# Alternatively use unicode control pictures codepoints. +sub quot_upr { + my $cntrl = shift; + my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl)); + return "$chr"; +} + # quote control characters and escape filename to HTML sub esc_path { my $str = shift; + $str = esc_html($str); - $str =~ s|([[:cntrl:]])|?|g; + $str =~ s|([[:cntrl:]])|quot_cec($1)|eg; return $str; } -- cgit v0.10.2-6-g49f6 From 744d0ac33ab579845808b8b01e526adc4678a226 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 8 Nov 2006 17:59:41 +0100 Subject: gitweb: New improved patchset view Replace "gitweb diff header" with its full sha1 of blobs and replace it by "git diff" header and extended diff header. Change also somewhat highlighting of diffs. Added `file_type_long' subroutine to convert file mode in octal to file type description (only for file modes which used by git). Changes: * "gitweb diff header" which looked for example like below: file:__ -> file:__ where 'file' is file type and '' is full sha1 of blob is changed to diff --git _a/_ _b/_ In both cases links are visible and use default link style. If file is added, a/ is not hyperlinked. If file is deleted, b/ is not hyperlinked. * there is added "extended diff header", with and hyperlinked (and shortened to 7 characters), and explained: '' is extended to ' ()', where added text is slightly lighter to easy distinguish that it was added (and it is difference from git-diff output). * from-file/to-file two-line header lines have slightly darker color than removed/added lines. * chunk header has now delicate line above for easier finding chunk boundary, and top margin of 2px, both barely visible. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index e19e6bc..974b47f 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -298,40 +298,82 @@ td.mode { font-family: monospace; } -div.diff a.list { +/* styling of diffs (patchsets): commitdiff and blobdiff views */ +div.diff.header, +div.diff.extended_header { + white-space: normal; +} + +div.diff.header { + font-weight: bold; + + background-color: #edece6; + + margin-top: 4px; + padding: 4px 0px 2px 0px; + border: solid #d9d8d1; + border-width: 1px 0px 1px 0px; +} + +div.diff.header a.path { + text-decoration: underline; +} + +div.diff.extended_header, +div.diff.extended_header a.path, +div.diff.extended_header a.hash { + color: #777777; +} + +div.diff.extended_header .info { + color: #b0b0b0; +} + +div.diff.extended_header { + background-color: #f6f5ee; + padding: 2px 0px 2px 0px; +} + +div.diff a.path, +div.diff a.hash { text-decoration: none; } -div.diff a.list:hover { +div.diff a.path:hover, +div.diff a.hash:hover { text-decoration: underline; } -div.diff.to_file a.list, -div.diff.to_file, +div.diff.to_file a.path, +div.diff.to_file { + color: #007000; +} + div.diff.add { color: #008800; } -div.diff.from_file a.list, -div.diff.from_file, +div.diff.from_file a.path, +div.diff.from_file { + color: #aa0000; +} + div.diff.rem { color: #cc0000; } div.diff.chunk_header { color: #990099; + + border: dotted #ffe0ff; + border-width: 1px 0px 0px 0px; + margin-top: 2px; } div.diff.incomplete { color: #cccccc; } -div.diff_info { - font-family: monospace; - color: #000099; - background-color: #edece6; - font-style: italic; -} div.index_include { border: solid #d9d8d1; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b80fc60..f937ee1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -785,6 +785,32 @@ sub file_type { } } +# convert file mode in octal to file type description string +sub file_type_long { + my $mode = shift; + + if ($mode !~ m/^[0-7]+$/) { + return $mode; + } else { + $mode = oct $mode; + } + + if (S_ISDIR($mode & S_IFMT)) { + return "directory"; + } elsif (S_ISLNK($mode)) { + return "symlink"; + } elsif (S_ISREG($mode)) { + if ($mode & S_IXUSR) { + return "executable"; + } else { + return "file"; + }; + } else { + return "unknown"; + } +} + + ## ---------------------------------------------------------------------- ## functions returning short HTML fragments, or transforming HTML fragments ## which don't beling to other sections @@ -2171,6 +2197,7 @@ sub git_patchset_body { my $in_header = 0; my $patch_found = 0; my $diffinfo; + my (%from, %to); print "
\n"; @@ -2181,6 +2208,10 @@ sub git_patchset_body { if ($patch_line =~ m/^diff /) { # "git diff" header # beginning of patch (in patchset) if ($patch_found) { + # close extended header for previous empty patch + if ($in_header) { + print "
\n" # class="diff extended_header" + } # close previous patch print "
\n"; # class="patch" } else { @@ -2189,89 +2220,113 @@ sub git_patchset_body { } print "
\n"; + # read and prepare patch information if (ref($difftree->[$patch_idx]) eq "HASH") { + # pre-parsed (or generated by hand) $diffinfo = $difftree->[$patch_idx]; } else { $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); } + $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; + $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'}; + if ($diffinfo->{'status'} ne "A") { # not new (added) file + $from{'href'} = href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, + file_name=>$from{'file'}); + } + if ($diffinfo->{'status'} ne "D") { # not deleted file + $to{'href'} = href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, + file_name=>$to{'file'}); + } $patch_idx++; - if ($diffinfo->{'status'} eq "A") { # added - print "
" . file_type($diffinfo->{'to_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'to_id'}) . " (new)" . - "
\n"; # class="diff_info" - - } elsif ($diffinfo->{'status'} eq "D") { # deleted - print "
" . file_type($diffinfo->{'from_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'from_id'}) . " (deleted)" . - "
\n"; # class="diff_info" - - } elsif ($diffinfo->{'status'} eq "R" || # renamed - $diffinfo->{'status'} eq "C" || # copied - $diffinfo->{'status'} eq "2") { # with two filenames (from git_blobdiff) - print "
" . - file_type($diffinfo->{'from_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'from_file'})}, - $diffinfo->{'from_id'}) . - " -> " . - file_type($diffinfo->{'to_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'to_file'})}, - $diffinfo->{'to_id'}); - print "
\n"; # class="diff_info" - - } else { # modified, mode changed, ... - print "
" . - file_type($diffinfo->{'from_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'from_id'}) . - " -> " . - file_type($diffinfo->{'to_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'to_id'}); - print "
\n"; # class="diff_info" + # print "git diff" header + $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; + if ($from{'href'}) { + $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, + 'a/' . esc_path($from{'file'})); + } else { # file was added + $patch_line .= 'a/' . esc_path($from{'file'}); + } + $patch_line .= ' '; + if ($to{'href'}) { + $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, + 'b/' . esc_path($to{'file'})); + } else { # file was deleted + $patch_line .= 'b/' . esc_path($to{'file'}); } - #print "
\n"; + print "
$patch_line
\n"; + print "
\n"; $in_header = 1; next LINE; - } # start of patch in patchset + } + if ($in_header) { + if ($patch_line !~ m/^---/) { + # match + if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { + $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, + esc_path($from{'file'})); + } + if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) { + $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); + } + # match + if ($patch_line =~ m/\s(\d{6})$/) { + $patch_line .= ' (' . + file_type_long($1) . + ')'; + } + # match + if ($patch_line =~ m/^index/) { + my ($from_link, $to_link); + if ($from{'href'}) { + $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"}, + substr($diffinfo->{'from_id'},0,7)); + } else { + $from_link = '0' x 7; + } + if ($to{'href'}) { + $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"}, + substr($diffinfo->{'to_id'},0,7)); + } else { + $to_link = '0' x 7; + } + my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'}); + $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!; + } + print $patch_line . "
\n"; - if ($in_header && $patch_line =~ m/^---/) { - #print "
\n"; # class="diff extended_header" - $in_header = 0; + } else { + #$in_header && $patch_line =~ m/^---/; + print "
\n"; # class="diff extended_header" + $in_header = 0; + + if ($from{'href'}) { + $patch_line = '--- a/' . + $cgi->a({-href=>$from{'href'}, -class=>"path"}, + esc_path($from{'file'})); + } + print "
$patch_line
\n"; - my $file = $diffinfo->{'from_file'}; - $file ||= $diffinfo->{'file'}; - $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, file_name=>$file), - -class => "list"}, esc_path($file)); - $patch_line =~ s|a/.*$|a/$file|g; - print "
$patch_line
\n"; + $patch_line = <$fd>; + chomp $patch_line; - $patch_line = <$fd>; - chomp $patch_line; + #$patch_line =~ m/^+++/; + if ($to{'href'}) { + $patch_line = '+++ b/' . + $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); + } + print "
$patch_line
\n"; - #$patch_line =~ m/^+++/; - $file = $diffinfo->{'to_file'}; - $file ||= $diffinfo->{'file'}; - $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, file_name=>$file), - -class => "list"}, esc_path($file)); - $patch_line =~ s|b/.*|b/$file|g; - print "
$patch_line
\n"; + } next LINE; } - next LINE if $in_header; print format_diff_line($patch_line); } -- cgit v0.10.2-6-g49f6 From 2b2a8c78ea26791853cbedad3ba282475c620067 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 8 Nov 2006 12:22:04 -0800 Subject: gitweb: do not give blame link unconditionally in diff-tree view Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index f937ee1..634975b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2012,7 +2012,7 @@ sub git_print_tree_entry { sub git_difftree_body { my ($difftree, $hash, $parent) = @_; - + my ($have_blame) = gitweb_check_feature('blame'); print "
\n"; if ($#{$difftree} > 10) { print(($#{$difftree} + 1) . " files changed:\n"); @@ -2085,9 +2085,13 @@ sub git_difftree_body { print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'})}, "blob") . " | "; - print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'file'})}, - "blame") . " | "; + if ($have_blame) { + print $cgi->a({-href => + href(action=>"blame", + hash_base=>$parent, + file_name=>$diff{'file'})}, + "blame") . " | "; + } print $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$diff{'file'})}, "history"); @@ -2133,9 +2137,12 @@ sub git_difftree_body { print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'})}, "blob") . " | "; - print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, - file_name=>$diff{'file'})}, - "blame") . " | "; + if ($have_blame) { + print $cgi->a({-href => href(action=>"blame", + hash_base=>$hash, + file_name=>$diff{'file'})}, + "blame") . " | "; + } print $cgi->a({-href => href(action=>"history", hash_base=>$hash, file_name=>$diff{'file'})}, "history"); @@ -2176,9 +2183,12 @@ sub git_difftree_body { print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'from_file'})}, "blob") . " | "; - print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'from_file'})}, - "blame") . " | "; + if ($have_blame) { + print $cgi->a({-href => href(action=>"blame", + hash_base=>$hash, + file_name=>$diff{'to_file'})}, + "blame") . " | "; + } print $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$diff{'from_file'})}, "history"); -- cgit v0.10.2-6-g49f6 From 3a946802bb2c1106d12861849414f66fdba62303 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 8 Nov 2006 13:20:46 -0800 Subject: git-status: quote LF in its output Otherwise, commit log template would get the remainder of the filename start on a new line unquoted and the log gets messed up. I initially considered using the full quote_c_style(), but the output from the command is primarily for human consumption so chose to leave other control characters and bytes with high-bits unmolested. Signed-off-by: Junio C Hamano diff --git a/wt-status.c b/wt-status.c index 7943944..de1be5b 100644 --- a/wt-status.c +++ b/wt-status.c @@ -64,31 +64,70 @@ static void wt_status_print_trailer(void) color_printf_ln(color(WT_STATUS_HEADER), "#"); } +static const char *quote_crlf(const char *in, char *buf, size_t sz) +{ + const char *scan; + char *out; + const char *ret = in; + + for (scan = in, out = buf; *scan; scan++) { + int ch = *scan; + int quoted; + + switch (ch) { + case '\n': + quoted = 'n'; + break; + case '\r': + quoted = 'r'; + break; + default: + *out++ = ch; + continue; + } + *out++ = '\\'; + *out++ = quoted; + ret = buf; + } + *out = '\0'; + return ret; +} + static void wt_status_print_filepair(int t, struct diff_filepair *p) { const char *c = color(t); + const char *one, *two; + char onebuf[PATH_MAX], twobuf[PATH_MAX]; + + one = quote_crlf(p->one->path, onebuf, sizeof(onebuf)); + two = quote_crlf(p->two->path, twobuf, sizeof(twobuf)); + color_printf(color(WT_STATUS_HEADER), "#\t"); switch (p->status) { case DIFF_STATUS_ADDED: - color_printf(c, "new file: %s", p->one->path); break; + color_printf(c, "new file: %s", one); + break; case DIFF_STATUS_COPIED: - color_printf(c, "copied: %s -> %s", - p->one->path, p->two->path); + color_printf(c, "copied: %s -> %s", one, two); break; case DIFF_STATUS_DELETED: - color_printf(c, "deleted: %s", p->one->path); break; + color_printf(c, "deleted: %s", one); + break; case DIFF_STATUS_MODIFIED: - color_printf(c, "modified: %s", p->one->path); break; + color_printf(c, "modified: %s", one); + break; case DIFF_STATUS_RENAMED: - color_printf(c, "renamed: %s -> %s", - p->one->path, p->two->path); + color_printf(c, "renamed: %s -> %s", one, two); break; case DIFF_STATUS_TYPE_CHANGED: - color_printf(c, "typechange: %s", p->one->path); break; + color_printf(c, "typechange: %s", one); + break; case DIFF_STATUS_UNKNOWN: - color_printf(c, "unknown: %s", p->one->path); break; + color_printf(c, "unknown: %s", one); + break; case DIFF_STATUS_UNMERGED: - color_printf(c, "unmerged: %s", p->one->path); break; + color_printf(c, "unmerged: %s", one); + break; default: die("bug: unhandled diff status %c", p->status); } @@ -134,6 +173,8 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q, void wt_status_print_initial(struct wt_status *s) { int i; + char buf[PATH_MAX]; + read_cache(); if (active_nr) { s->commitable = 1; @@ -143,7 +184,8 @@ void wt_status_print_initial(struct wt_status *s) for (i = 0; i < active_nr; i++) { color_printf(color(WT_STATUS_HEADER), "#\t"); color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s", - active_cache[i]->name); + quote_crlf(active_cache[i]->name, + buf, sizeof(buf))); } if (active_nr) wt_status_print_trailer(); -- cgit v0.10.2-6-g49f6 From acca687fa9db8eaa380b65d63c3f0d4364892acf Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 8 Nov 2006 18:47:54 -0800 Subject: git-pickaxe: retire pickaxe Just make it take over blame's place. Documentation and command have all stopped mentioning "git-pickaxe". The built-in synonym is left in the command table, so you can still say "git pickaxe", but it probably is a good idea to retire it as well. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index 9891c1d..ff54d29 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -7,7 +7,9 @@ git-blame - Show what revision and author last modified each line of a file SYNOPSIS -------- -'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-S ] [--] [] +[verse] +'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] + [-M] [-C] [-C] [--since=] [] [--] DESCRIPTION ----------- @@ -15,6 +17,8 @@ DESCRIPTION Annotates each line in the given file with information from the revision which last modified the line. Optionally, start annotating from the given revision. +Also it can limit the range of lines annotated. + This report doesn't tell you anything about lines which have been deleted or replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe" interface briefly mentioned in the following paragraph. @@ -36,6 +40,12 @@ OPTIONS -c, --compatibility:: Use the same output mode as gitlink:git-annotate[1] (Default: off). +-L n,m:: + Annotate only the specified line range (lines count from + 1). The range can be specified with a regexp. For + example, `-L '/^sub esc_html /,/^}$/'` limits the + annotation only to the body of `esc_html` subroutine. + -l, --long:: Show long rev (Default: off). @@ -56,6 +66,24 @@ OPTIONS -p, --porcelain:: Show in a format designed for machine consumption. +-M:: + Detect moving lines in the file as well. When a commit + moves a block of lines in a file (e.g. the original file + has A and then B, and the commit changes it to B and + then A), traditional 'blame' algorithm typically blames + the lines that were moved up (i.e. B) to the parent and + assigns blame to the lines that were moved down (i.e. A) + to the child commit. With this option, both groups of + lines are blamed on the parent. + +-C:: + In addition to `-M`, detect lines copied from other + files that were modified in the same commit. This is + useful when you reorganize your program and move code + around across files. When this option is given twice, + the command looks for copies from all other files in the + parent for the commit that creates the file in addition. + -h, --help:: Show help message. @@ -86,13 +114,51 @@ The contents of the actual line is output after the above header, prefixed by a TAB. This is to allow adding more header elements later. + +SPECIFIYING RANGES +------------------ + +Unlike `git-blame` and `git-annotate` in older git, the extent +of annotation can be limited to both line ranges and revision +ranges. When you are interested in finding the origin for +ll. 40-60 for file `foo`, you can use `-L` option like this: + + git blame -L 40,60 foo + +When you are not interested in changes older than the version +v2.6.18, or changes older than 3 weeks, you can use revision +range specifiers similar to `git-rev-list`: + + git blame v2.6.18.. -- foo + git blame --since=3.weeks -- foo + +When revision range specifiers are used to limit the annotation, +lines that have not changed since the range boundary (either the +commit v2.6.18 or the most recent commit that is more than 3 +weeks old in the above example) are blamed for that range +boundary commit. + +A particularly useful way is to see if an added file have lines +created by copy-and-paste from existing files. Sometimes this +indicates that the developer was being sloppy and did not +refactor the code properly. You can first find the commit that +introduced the file with: + + git log --diff-filter=A --pretty=short -- foo + +and then annotate the change between the commit and its +parents, using `commit{caret}!` notation: + + git blame -C -C -f $commit^! -- foo + + SEE ALSO -------- gitlink:git-annotate[1] AUTHOR ------ -Written by Fredrik Kuivinen . +Written by Junio C Hamano GIT --- diff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt deleted file mode 100644 index c08fdec..0000000 --- a/Documentation/git-pickaxe.txt +++ /dev/null @@ -1,162 +0,0 @@ -git-pickaxe(1) -============== - -NAME ----- -git-pickaxe - Show what revision and author last modified each line of a file - -SYNOPSIS --------- -[verse] -'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] - [-M] [-C] [-C] [--since=] [] [--] - -DESCRIPTION ------------ - -Annotates each line in the given file with information from the revision which -last modified the line. Optionally, start annotating from the given revision. - -Also it can limit the range of lines annotated. - -This report doesn't tell you anything about lines which have been deleted or -replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe" -interface briefly mentioned in the following paragraph. - -Apart from supporting file annotation, git also supports searching the -development history for when a code snippet occured in a change. This makes it -possible to track when a code snippet was added to a file, moved or copied -between files, and eventually deleted or replaced. It works by searching for -a text string in the diff. A small example: - ------------------------------------------------------------------------------ -$ git log --pretty=oneline -S'blame_usage' -5040f17eba15504bad66b14a645bddd9b015ebb7 blame -S -ea4c7f9bf69e781dd0cd88d2bccb2bf5cc15c9a7 git-blame: Make the output ------------------------------------------------------------------------------ - -OPTIONS -------- --c, --compatibility:: - Use the same output mode as gitlink:git-annotate[1] (Default: off). - --L n,m:: - Annotate only the specified line range (lines count from 1). - --l, --long:: - Show long rev (Default: off). - --t, --time:: - Show raw timestamp (Default: off). - --S, --rev-file :: - Use revs from revs-file instead of calling gitlink:git-rev-list[1]. - --f, --show-name:: - Show filename in the original commit. By default - filename is shown if there is any line that came from a - file with different name, due to rename detection. - --n, --show-number:: - Show line number in the original commit (Default: off). - --p, --porcelain:: - Show in a format designed for machine consumption. - --M:: - Detect moving lines in the file as well. When a commit - moves a block of lines in a file (e.g. the original file - has A and then B, and the commit changes it to B and - then A), traditional 'blame' algorithm typically blames - the lines that were moved up (i.e. B) to the parent and - assigns blame to the lines that were moved down (i.e. A) - to the child commit. With this option, both groups of - lines are blamed on the parent. - --C:: - In addition to `-M`, detect lines copied from other - files that were modified in the same commit. This is - useful when you reorganize your program and move code - around across files. When this option is given twice, - the command looks for copies from all other files in the - parent for the commit that creates the file in addition. - --h, --help:: - Show help message. - - -THE PORCELAIN FORMAT --------------------- - -In this format, each line is output after a header; the -header at the minumum has the first line which has: - -- 40-byte SHA-1 of the commit the line is attributed to; -- the line number of the line in the original file; -- the line number of the line in the final file; -- on a line that starts a group of line from a different - commit than the previous one, the number of lines in this - group. On subsequent lines this field is absent. - -This header line is followed by the following information -at least once for each commit: - -- author name ("author"), email ("author-mail"), time - ("author-time"), and timezone ("author-tz"); similarly - for committer. -- filename in the commit the line is attributed to. -- the first line of the commit log message ("summary"). - -The contents of the actual line is output after the above -header, prefixed by a TAB. This is to allow adding more -header elements later. - - -SPECIFIYING RANGES ------------------- - -Unlike `git-blame` and `git-annotate` in older git, the extent -of annotation can be limited to both line ranges and revision -ranges. When you are interested in finding the origin for -ll. 40-60 for file `foo`, you can use `-L` option like this: - - git pickaxe -L 40,60 foo - -When you are not interested in changes older than the version -v2.6.18, or changes older than 3 weeks, you can use revision -range specifiers similar to `git-rev-list`: - - git pickaxe v2.6.18.. -- foo - git pickaxe --since=3.weeks -- foo - -When revision range specifiers are used to limit the annotation, -lines that have not changed since the range boundary (either the -commit v2.6.18 or the most recent commit that is more than 3 -weeks old in the above example) are blamed for that range -boundary commit. - -A particularly useful way is to see if an added file have lines -created by copy-and-paste from existing files. Sometimes this -indicates that the developer was being sloppy and did not -refactor the code properly. You can first find the commit that -introduced the file with: - - git log --diff-filter=A --pretty=short -- foo - -and then annotate the change between the commit and its -parents, using `commit{caret}!` notation: - - git pickaxe -C -C -f $commit^! -- foo - - -SEE ALSO --------- -gitlink:git-blame[1] - -AUTHOR ------- -Written by Junio C Hamano - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index 4facf23..52bc05a 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -252,9 +252,6 @@ gitlink:git-annotate[1]:: Annotate file lines with commit info. gitlink:git-blame[1]:: - Blame file lines on commits. - -gitlink:git-pickaxe[1]:: Find out where each line in a file came from. gitlink:git-check-ref-format[1]:: diff --git a/Makefile b/Makefile index a042df4..36ce8cd 100644 --- a/Makefile +++ b/Makefile @@ -201,7 +201,7 @@ PROGRAMS = \ git-update-server-info$X \ git-upload-pack$X git-verify-pack$X \ git-pack-redundant$X git-var$X \ - git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ + git-describe$X git-merge-tree$X git-imap-send$X \ git-merge-recursive$X \ $(EXTRA_PROGRAMS) @@ -267,6 +267,7 @@ BUILTIN_OBJS = \ builtin-annotate.o \ builtin-apply.o \ builtin-archive.o \ + builtin-blame.o \ builtin-branch.o \ builtin-cat-file.o \ builtin-checkout-index.o \ @@ -290,7 +291,6 @@ BUILTIN_OBJS = \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ - builtin-pickaxe.o \ builtin-prune.o \ builtin-prune-packed.o \ builtin-push.o \ diff --git a/blame.c b/blame.c deleted file mode 100644 index 1144c85..0000000 --- a/blame.c +++ /dev/null @@ -1,1089 +0,0 @@ -/* - * Copyright (C) 2006, Fredrik Kuivinen - */ - -#include -#include -#include -#include - -#include "cache.h" -#include "refs.h" -#include "tag.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" -#include "xdiff-interface.h" -#include "quote.h" - -#ifndef DEBUG -#define DEBUG 0 -#endif - -static const char blame_usage[] = -"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S ] [--] file [commit]\n" -" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" -" -l, --long Show long commit SHA1 (Default: off)\n" -" -t, --time Show raw timestamp (Default: off)\n" -" -f, --show-name Show original filename (Default: auto)\n" -" -n, --show-number Show original linenumber (Default: off)\n" -" -p, --porcelain Show in a format designed for machine consumption\n" -" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n" -" -h, --help This message"; - -static struct commit **blame_lines; -static int num_blame_lines; -static char *blame_contents; -static int blame_len; - -struct util_info { - int *line_map; - unsigned char sha1[20]; /* blob sha, not commit! */ - char *buf; - unsigned long size; - int num_lines; - const char *pathname; - unsigned meta_given:1; - - void *topo_data; -}; - -struct chunk { - int off1, len1; /* --- */ - int off2, len2; /* +++ */ -}; - -struct patch { - struct chunk *chunks; - int num; -}; - -static void get_blob(struct commit *commit); - -/* Only used for statistics */ -static int num_get_patch; -static int num_commits; -static int patch_time; -static int num_read_blob; - -struct blame_diff_state { - struct xdiff_emit_state xm; - struct patch *ret; -}; - -static void process_u0_diff(void *state_, char *line, unsigned long len) -{ - struct blame_diff_state *state = state_; - struct chunk *chunk; - - if (len < 4 || line[0] != '@' || line[1] != '@') - return; - - if (DEBUG) - printf("chunk line: %.*s", (int)len, line); - state->ret->num++; - state->ret->chunks = xrealloc(state->ret->chunks, - sizeof(struct chunk) * state->ret->num); - chunk = &state->ret->chunks[state->ret->num - 1]; - - assert(!strncmp(line, "@@ -", 4)); - - if (parse_hunk_header(line, len, - &chunk->off1, &chunk->len1, - &chunk->off2, &chunk->len2)) { - state->ret->num--; - return; - } - - if (chunk->len1 == 0) - chunk->off1++; - if (chunk->len2 == 0) - chunk->off2++; - - if (chunk->off1 > 0) - chunk->off1--; - if (chunk->off2 > 0) - chunk->off2--; - - assert(chunk->off1 >= 0); - assert(chunk->off2 >= 0); -} - -static struct patch *get_patch(struct commit *commit, struct commit *other) -{ - struct blame_diff_state state; - xpparam_t xpp; - xdemitconf_t xecfg; - mmfile_t file_c, file_o; - xdemitcb_t ecb; - struct util_info *info_c = (struct util_info *)commit->util; - struct util_info *info_o = (struct util_info *)other->util; - struct timeval tv_start, tv_end; - - get_blob(commit); - file_c.ptr = info_c->buf; - file_c.size = info_c->size; - - get_blob(other); - file_o.ptr = info_o->buf; - file_o.size = info_o->size; - - gettimeofday(&tv_start, NULL); - - xpp.flags = XDF_NEED_MINIMAL; - xecfg.ctxlen = 0; - xecfg.flags = 0; - ecb.outf = xdiff_outf; - ecb.priv = &state; - memset(&state, 0, sizeof(state)); - state.xm.consume = process_u0_diff; - state.ret = xmalloc(sizeof(struct patch)); - state.ret->chunks = NULL; - state.ret->num = 0; - - xdl_diff(&file_c, &file_o, &xpp, &xecfg, &ecb); - - gettimeofday(&tv_end, NULL); - patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) + - tv_end.tv_usec - tv_start.tv_usec; - - num_get_patch++; - return state.ret; -} - -static void free_patch(struct patch *p) -{ - free(p->chunks); - free(p); -} - -static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, - int baselen, const char *pathname, - unsigned mode, int stage); - -static unsigned char blob_sha1[20]; -static const char *blame_file; -static int get_blob_sha1(struct tree *t, const char *pathname, - unsigned char *sha1) -{ - const char *pathspec[2]; - blame_file = pathname; - pathspec[0] = pathname; - pathspec[1] = NULL; - hashclr(blob_sha1); - read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); - - if (is_null_sha1(blob_sha1)) - return -1; - - hashcpy(sha1, blob_sha1); - return 0; -} - -static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, - int baselen, const char *pathname, - unsigned mode, int stage) -{ - if (S_ISDIR(mode)) - return READ_TREE_RECURSIVE; - - if (strncmp(blame_file, base, baselen) || - strcmp(blame_file + baselen, pathname)) - return -1; - - hashcpy(blob_sha1, sha1); - return -1; -} - -static void get_blob(struct commit *commit) -{ - struct util_info *info = commit->util; - char type[20]; - - if (info->buf) - return; - - info->buf = read_sha1_file(info->sha1, type, &info->size); - num_read_blob++; - - assert(!strcmp(type, blob_type)); -} - -/* For debugging only */ -static void print_patch(struct patch *p) -{ - int i; - printf("Num chunks: %d\n", p->num); - for (i = 0; i < p->num; i++) { - printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, - p->chunks[i].off2, p->chunks[i].len2); - } -} - -#if DEBUG -/* For debugging only */ -static void print_map(struct commit *cmit, struct commit *other) -{ - struct util_info *util = cmit->util; - struct util_info *util2 = other->util; - - int i; - int max = - util->num_lines > - util2->num_lines ? util->num_lines : util2->num_lines; - int num; - - if (print_map == NULL) - ; /* to avoid "unused function" warning */ - - for (i = 0; i < max; i++) { - printf("i: %d ", i); - num = -1; - - if (i < util->num_lines) { - num = util->line_map[i]; - printf("%d\t", num); - } - else - printf("\t"); - - if (i < util2->num_lines) { - int num2 = util2->line_map[i]; - printf("%d\t", num2); - if (num != -1 && num2 != num) - printf("---"); - } - else - printf("\t"); - - printf("\n"); - } -} -#endif - -/* p is a patch from commit to other. */ -static void fill_line_map(struct commit *commit, struct commit *other, - struct patch *p) -{ - struct util_info *util = commit->util; - struct util_info *util2 = other->util; - int *map = util->line_map; - int *map2 = util2->line_map; - int cur_chunk = 0; - int i1, i2; - - if (DEBUG) { - if (p->num) - print_patch(p); - printf("num lines 1: %d num lines 2: %d\n", util->num_lines, - util2->num_lines); - } - - for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) { - struct chunk *chunk = NULL; - if (cur_chunk < p->num) - chunk = &p->chunks[cur_chunk]; - - if (chunk && chunk->off1 == i1) { - if (DEBUG && i2 != chunk->off2) - printf("i2: %d off2: %d\n", i2, chunk->off2); - - assert(i2 == chunk->off2); - - i1--; - i2--; - if (chunk->len1 > 0) - i1 += chunk->len1; - - if (chunk->len2 > 0) - i2 += chunk->len2; - - cur_chunk++; - } - else { - if (i2 >= util2->num_lines) - break; - - if (map[i1] != map2[i2] && map[i1] != -1) { - if (DEBUG) - printf("map: i1: %d %d %p i2: %d %d %p\n", - i1, map[i1], - (void *) (i1 != -1 ? blame_lines[map[i1]] : NULL), - i2, map2[i2], - (void *) (i2 != -1 ? blame_lines[map2[i2]] : NULL)); - if (map2[i2] != -1 && - blame_lines[map[i1]] && - !blame_lines[map2[i2]]) - map[i1] = map2[i2]; - } - - if (map[i1] == -1 && map2[i2] != -1) - map[i1] = map2[i2]; - } - - if (DEBUG > 1) - printf("l1: %d l2: %d i1: %d i2: %d\n", - map[i1], map2[i2], i1, i2); - } -} - -static int map_line(struct commit *commit, int line) -{ - struct util_info *info = commit->util; - assert(line >= 0 && line < info->num_lines); - return info->line_map[line]; -} - -static struct util_info *get_util(struct commit *commit) -{ - struct util_info *util = commit->util; - - if (util) - return util; - - util = xcalloc(1, sizeof(struct util_info)); - util->num_lines = -1; - commit->util = util; - return util; -} - -static int fill_util_info(struct commit *commit) -{ - struct util_info *util = commit->util; - - assert(util); - assert(util->pathname); - - return !!get_blob_sha1(commit->tree, util->pathname, util->sha1); -} - -static void alloc_line_map(struct commit *commit) -{ - struct util_info *util = commit->util; - int i; - - if (util->line_map) - return; - - get_blob(commit); - - util->num_lines = 0; - for (i = 0; i < util->size; i++) { - if (util->buf[i] == '\n') - util->num_lines++; - } - if (util->buf[util->size - 1] != '\n') - util->num_lines++; - - util->line_map = xmalloc(sizeof(int) * util->num_lines); - - for (i = 0; i < util->num_lines; i++) - util->line_map[i] = -1; -} - -static void init_first_commit(struct commit *commit, const char *filename) -{ - struct util_info *util = commit->util; - int i; - - util->pathname = filename; - if (fill_util_info(commit)) - die("fill_util_info failed"); - - alloc_line_map(commit); - - util = commit->util; - - for (i = 0; i < util->num_lines; i++) - util->line_map[i] = i; -} - -static void process_commits(struct rev_info *rev, const char *path, - struct commit **initial) -{ - int i; - struct util_info *util; - int lines_left; - int *blame_p; - int *new_lines; - int new_lines_len; - - struct commit *commit = get_revision(rev); - assert(commit); - init_first_commit(commit, path); - - util = commit->util; - num_blame_lines = util->num_lines; - blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines); - blame_contents = util->buf; - blame_len = util->size; - - for (i = 0; i < num_blame_lines; i++) - blame_lines[i] = NULL; - - lines_left = num_blame_lines; - blame_p = xmalloc(sizeof(int) * num_blame_lines); - new_lines = xmalloc(sizeof(int) * num_blame_lines); - do { - struct commit_list *parents; - int num_parents; - struct util_info *util; - - if (DEBUG) - printf("\nProcessing commit: %d %s\n", num_commits, - sha1_to_hex(commit->object.sha1)); - - if (lines_left == 0) - return; - - num_commits++; - memset(blame_p, 0, sizeof(int) * num_blame_lines); - new_lines_len = 0; - num_parents = 0; - for (parents = commit->parents; - parents != NULL; parents = parents->next) - num_parents++; - - if (num_parents == 0) - *initial = commit; - - if (fill_util_info(commit)) - continue; - - alloc_line_map(commit); - util = commit->util; - - for (parents = commit->parents; - parents != NULL; parents = parents->next) { - struct commit *parent = parents->item; - struct patch *patch; - - if (parse_commit(parent) < 0) - die("parse_commit error"); - - if (DEBUG) - printf("parent: %s\n", - sha1_to_hex(parent->object.sha1)); - - if (fill_util_info(parent)) { - num_parents--; - continue; - } - - patch = get_patch(parent, commit); - alloc_line_map(parent); - fill_line_map(parent, commit, patch); - - for (i = 0; i < patch->num; i++) { - int l; - for (l = 0; l < patch->chunks[i].len2; l++) { - int mapped_line = - map_line(commit, patch->chunks[i].off2 + l); - if (mapped_line != -1) { - blame_p[mapped_line]++; - if (blame_p[mapped_line] == num_parents) - new_lines[new_lines_len++] = mapped_line; - } - } - } - free_patch(patch); - } - - if (DEBUG) - printf("parents: %d\n", num_parents); - - for (i = 0; i < new_lines_len; i++) { - int mapped_line = new_lines[i]; - if (blame_lines[mapped_line] == NULL) { - blame_lines[mapped_line] = commit; - lines_left--; - if (DEBUG) - printf("blame: mapped: %d i: %d\n", - mapped_line, i); - } - } - } while ((commit = get_revision(rev)) != NULL); -} - -static int compare_tree_path(struct rev_info *revs, - struct commit *c1, struct commit *c2) -{ - int ret; - const char *paths[2]; - struct util_info *util = c2->util; - paths[0] = util->pathname; - paths[1] = NULL; - - diff_tree_setup_paths(get_pathspec(revs->prefix, paths), - &revs->pruning); - ret = rev_compare_tree(revs, c1->tree, c2->tree); - diff_tree_release_paths(&revs->pruning); - return ret; -} - -static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1, - const char *path) -{ - int ret; - const char *paths[2]; - paths[0] = path; - paths[1] = NULL; - - diff_tree_setup_paths(get_pathspec(revs->prefix, paths), - &revs->pruning); - ret = rev_same_tree_as_empty(revs, t1); - diff_tree_release_paths(&revs->pruning); - return ret; -} - -static const char *find_rename(struct commit *commit, struct commit *parent) -{ - struct util_info *cutil = commit->util; - struct diff_options diff_opts; - const char *paths[1]; - int i; - - if (DEBUG) { - printf("find_rename commit: %s ", - sha1_to_hex(commit->object.sha1)); - puts(sha1_to_hex(parent->object.sha1)); - } - - diff_setup(&diff_opts); - diff_opts.recursive = 1; - diff_opts.detect_rename = DIFF_DETECT_RENAME; - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff_setup_done failed"); - - diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1, - "", &diff_opts); - diffcore_std(&diff_opts); - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - - if (p->status == 'R' && - !strcmp(p->one->path, cutil->pathname)) { - if (DEBUG) - printf("rename %s -> %s\n", - p->one->path, p->two->path); - return p->two->path; - } - } - - return 0; -} - -static void simplify_commit(struct rev_info *revs, struct commit *commit) -{ - struct commit_list **pp, *parent; - - if (!commit->tree) - return; - - if (!commit->parents) { - struct util_info *util = commit->util; - if (!same_tree_as_empty_path(revs, commit->tree, - util->pathname)) - commit->object.flags |= TREECHANGE; - return; - } - - pp = &commit->parents; - while ((parent = *pp) != NULL) { - struct commit *p = parent->item; - - if (p->object.flags & UNINTERESTING) { - pp = &parent->next; - continue; - } - - parse_commit(p); - switch (compare_tree_path(revs, p, commit)) { - case REV_TREE_SAME: - parent->next = NULL; - commit->parents = parent; - get_util(p)->pathname = get_util(commit)->pathname; - return; - - case REV_TREE_NEW: - { - struct util_info *util = commit->util; - if (revs->remove_empty_trees && - same_tree_as_empty_path(revs, p->tree, - util->pathname)) { - const char *new_name = find_rename(commit, p); - if (new_name) { - struct util_info *putil = get_util(p); - if (!putil->pathname) - putil->pathname = xstrdup(new_name); - } - else { - *pp = parent->next; - continue; - } - } - } - - /* fallthrough */ - case REV_TREE_DIFFERENT: - pp = &parent->next; - if (!get_util(p)->pathname) - get_util(p)->pathname = - get_util(commit)->pathname; - continue; - } - die("bad tree compare for commit %s", - sha1_to_hex(commit->object.sha1)); - } - commit->object.flags |= TREECHANGE; -} - -struct commit_info -{ - char *author; - char *author_mail; - unsigned long author_time; - char *author_tz; - - /* filled only when asked for details */ - char *committer; - char *committer_mail; - unsigned long committer_time; - char *committer_tz; - - char *summary; -}; - -static void get_ac_line(const char *inbuf, const char *what, - int bufsz, char *person, char **mail, - unsigned long *time, char **tz) -{ - int len; - char *tmp, *endp; - - tmp = strstr(inbuf, what); - if (!tmp) - goto error_out; - tmp += strlen(what); - endp = strchr(tmp, '\n'); - if (!endp) - len = strlen(tmp); - else - len = endp - tmp; - if (bufsz <= len) { - error_out: - /* Ugh */ - person = *mail = *tz = "(unknown)"; - *time = 0; - return; - } - memcpy(person, tmp, len); - - tmp = person; - tmp += len; - *tmp = 0; - while (*tmp != ' ') - tmp--; - *tz = tmp+1; - - *tmp = 0; - while (*tmp != ' ') - tmp--; - *time = strtoul(tmp, NULL, 10); - - *tmp = 0; - while (*tmp != ' ') - tmp--; - *mail = tmp + 1; - *tmp = 0; -} - -static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed) -{ - int len; - char *tmp, *endp; - static char author_buf[1024]; - static char committer_buf[1024]; - static char summary_buf[1024]; - - ret->author = author_buf; - get_ac_line(commit->buffer, "\nauthor ", - sizeof(author_buf), author_buf, &ret->author_mail, - &ret->author_time, &ret->author_tz); - - if (!detailed) - return; - - ret->committer = committer_buf; - get_ac_line(commit->buffer, "\ncommitter ", - sizeof(committer_buf), committer_buf, &ret->committer_mail, - &ret->committer_time, &ret->committer_tz); - - ret->summary = summary_buf; - tmp = strstr(commit->buffer, "\n\n"); - if (!tmp) { - error_out: - sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); - return; - } - tmp += 2; - endp = strchr(tmp, '\n'); - if (!endp) - goto error_out; - len = endp - tmp; - if (len >= sizeof(summary_buf)) - goto error_out; - memcpy(summary_buf, tmp, len); - summary_buf[len] = 0; -} - -static const char *format_time(unsigned long time, const char *tz_str, - int show_raw_time) -{ - static char time_buf[128]; - time_t t = time; - int minutes, tz; - struct tm *tm; - - if (show_raw_time) { - sprintf(time_buf, "%lu %s", time, tz_str); - return time_buf; - } - - tz = atoi(tz_str); - minutes = tz < 0 ? -tz : tz; - minutes = (minutes / 100)*60 + (minutes % 100); - minutes = tz < 0 ? -minutes : minutes; - t = time + minutes * 60; - tm = gmtime(&t); - - strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); - strcat(time_buf, tz_str); - return time_buf; -} - -static void topo_setter(struct commit *c, void *data) -{ - struct util_info *util = c->util; - util->topo_data = data; -} - -static void *topo_getter(struct commit *c) -{ - struct util_info *util = c->util; - return util->topo_data; -} - -static int read_ancestry(const char *graft_file, - unsigned char **start_sha1) -{ - FILE *fp = fopen(graft_file, "r"); - char buf[1024]; - if (!fp) - return -1; - while (fgets(buf, sizeof(buf), fp)) { - /* The format is just "Commit Parent1 Parent2 ...\n" */ - int len = strlen(buf); - struct commit_graft *graft = read_graft_line(buf, len); - register_commit_graft(graft, 0); - if (!*start_sha1) - *start_sha1 = graft->sha1; - } - fclose(fp); - return 0; -} - -static int lineno_width(int lines) -{ - int i, width; - - for (width = 1, i = 10; i <= lines + 1; width++) - i *= 10; - return width; -} - -static int find_orig_linenum(struct util_info *u, int lineno) -{ - int i; - - for (i = 0; i < u->num_lines; i++) - if (lineno == u->line_map[i]) - return i + 1; - return 0; -} - -static void emit_meta(struct commit *c, int lno, - int sha1_len, int compatibility, int porcelain, - int show_name, int show_number, int show_raw_time, - int longest_file, int longest_author, - int max_digits, int max_orig_digits) -{ - struct util_info *u; - int lineno; - struct commit_info ci; - - u = c->util; - lineno = find_orig_linenum(u, lno); - - if (porcelain) { - int group_size = -1; - struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1]; - if (cc != c) { - /* This is the beginning of this group */ - int i; - for (i = lno + 1; i < num_blame_lines; i++) - if (blame_lines[i] != c) - break; - group_size = i - lno; - } - if (0 < group_size) - printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1), - lineno, lno + 1, group_size); - else - printf("%s %d %d\n", sha1_to_hex(c->object.sha1), - lineno, lno + 1); - if (!u->meta_given) { - get_commit_info(c, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); - printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); - printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - printf("filename "); - if (quote_c_style(u->pathname, NULL, NULL, 0)) - quote_c_style(u->pathname, NULL, stdout, 0); - else - fputs(u->pathname, stdout); - printf("\nsummary %s\n", ci.summary); - - u->meta_given = 1; - } - putchar('\t'); - return; - } - - get_commit_info(c, &ci, 0); - fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); - if (compatibility) { - printf("\t(%10s\t%10s\t%d)", ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - lno + 1); - } - else { - if (show_name) - printf(" %-*.*s", longest_file, longest_file, - u->pathname); - if (show_number) - printf(" %*d", max_orig_digits, - lineno); - printf(" (%-*.*s %10s %*d) ", - longest_author, longest_author, ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - max_digits, lno + 1); - } -} - -int main(int argc, const char **argv) -{ - int i; - struct commit *initial = NULL; - unsigned char sha1[20], *sha1_p = NULL; - - const char *filename = NULL, *commit = NULL; - char filename_buf[256]; - int sha1_len = 8; - int compatibility = 0; - int show_raw_time = 0; - int options = 1; - struct commit *start_commit; - - const char *args[10]; - struct rev_info rev; - - struct commit_info ci; - const char *buf; - int max_digits, max_orig_digits; - int longest_file, longest_author, longest_file_lines; - int show_name = 0; - int show_number = 0; - int porcelain = 0; - - const char *prefix = setup_git_directory(); - git_config(git_default_config); - - for (i = 1; i < argc; i++) { - if (options) { - if (!strcmp(argv[i], "-h") || - !strcmp(argv[i], "--help")) - usage(blame_usage); - if (!strcmp(argv[i], "-l") || - !strcmp(argv[i], "--long")) { - sha1_len = 40; - continue; - } - if (!strcmp(argv[i], "-c") || - !strcmp(argv[i], "--compatibility")) { - compatibility = 1; - continue; - } - if (!strcmp(argv[i], "-t") || - !strcmp(argv[i], "--time")) { - show_raw_time = 1; - continue; - } - if (!strcmp(argv[i], "-S")) { - if (i + 1 < argc && - !read_ancestry(argv[i + 1], &sha1_p)) { - compatibility = 1; - i++; - continue; - } - usage(blame_usage); - } - if (!strcmp(argv[i], "-f") || - !strcmp(argv[i], "--show-name")) { - show_name = 1; - continue; - } - if (!strcmp(argv[i], "-n") || - !strcmp(argv[i], "--show-number")) { - show_number = 1; - continue; - } - if (!strcmp(argv[i], "-p") || - !strcmp(argv[i], "--porcelain")) { - porcelain = 1; - sha1_len = 40; - show_raw_time = 1; - continue; - } - if (!strcmp(argv[i], "--")) { - options = 0; - continue; - } - if (argv[i][0] == '-') - usage(blame_usage); - options = 0; - } - - if (!options) { - if (!filename) - filename = argv[i]; - else if (!commit) - commit = argv[i]; - else - usage(blame_usage); - } - } - - if (!filename) - usage(blame_usage); - if (commit && sha1_p) - usage(blame_usage); - else if (!commit) - commit = "HEAD"; - - if (prefix) - sprintf(filename_buf, "%s%s", prefix, filename); - else - strcpy(filename_buf, filename); - filename = filename_buf; - - if (!sha1_p) { - if (get_sha1(commit, sha1)) - die("get_sha1 failed, commit '%s' not found", commit); - sha1_p = sha1; - } - start_commit = lookup_commit_reference(sha1_p); - get_util(start_commit)->pathname = filename; - if (fill_util_info(start_commit)) { - printf("%s not found in %s\n", filename, commit); - return 1; - } - - init_revisions(&rev, setup_git_directory()); - rev.remove_empty_trees = 1; - rev.topo_order = 1; - rev.prune_fn = simplify_commit; - rev.topo_setter = topo_setter; - rev.topo_getter = topo_getter; - rev.parents = 1; - rev.limited = 1; - - commit_list_insert(start_commit, &rev.commits); - - args[0] = filename; - args[1] = NULL; - diff_tree_setup_paths(args, &rev.pruning); - prepare_revision_walk(&rev); - process_commits(&rev, filename, &initial); - - for (i = 0; i < num_blame_lines; i++) - if (!blame_lines[i]) - blame_lines[i] = initial; - - buf = blame_contents; - max_digits = lineno_width(num_blame_lines); - - longest_file = 0; - longest_author = 0; - longest_file_lines = 0; - for (i = 0; i < num_blame_lines; i++) { - struct commit *c = blame_lines[i]; - struct util_info *u; - u = c->util; - - if (!show_name && strcmp(filename, u->pathname)) - show_name = 1; - if (longest_file < strlen(u->pathname)) - longest_file = strlen(u->pathname); - if (longest_file_lines < u->num_lines) - longest_file_lines = u->num_lines; - get_commit_info(c, &ci, 0); - if (longest_author < strlen(ci.author)) - longest_author = strlen(ci.author); - } - - max_orig_digits = lineno_width(longest_file_lines); - - for (i = 0; i < num_blame_lines; i++) { - emit_meta(blame_lines[i], i, - sha1_len, compatibility, porcelain, - show_name, show_number, show_raw_time, - longest_file, longest_author, - max_digits, max_orig_digits); - - if (i == num_blame_lines - 1) { - fwrite(buf, blame_len - (buf - blame_contents), - 1, stdout); - if (blame_contents[blame_len-1] != '\n') - putc('\n', stdout); - } - else { - char *next_buf = strchr(buf, '\n') + 1; - fwrite(buf, next_buf - buf, 1, stdout); - buf = next_buf; - } - } - - if (DEBUG) { - printf("num read blob: %d\n", num_read_blob); - printf("num get patch: %d\n", num_get_patch); - printf("num commits: %d\n", num_commits); - printf("patch time: %f\n", patch_time / 1000000.0); - printf("initial: %s\n", sha1_to_hex(initial->object.sha1)); - } - - return 0; -} diff --git a/builtin-blame.c b/builtin-blame.c new file mode 100644 index 0000000..1666022 --- /dev/null +++ b/builtin-blame.c @@ -0,0 +1,1889 @@ +/* + * Pickaxe + * + * Copyright (c) 2006, Junio C Hamano + */ + +#include "cache.h" +#include "builtin.h" +#include "blob.h" +#include "commit.h" +#include "tag.h" +#include "tree-walk.h" +#include "diff.h" +#include "diffcore.h" +#include "revision.h" +#include "xdiff-interface.h" + +#include +#include +#include + +static char blame_usage[] = +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [commit] [--] file\n" +" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" +" -l, --long Show long commit SHA1 (Default: off)\n" +" -t, --time Show raw timestamp (Default: off)\n" +" -f, --show-name Show original filename (Default: auto)\n" +" -n, --show-number Show original linenumber (Default: off)\n" +" -p, --porcelain Show in a format designed for machine consumption\n" +" -L n,m Process only line range n,m, counting from 1\n" +" -M, -C Find line movements within and across files\n" +" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; + +static int longest_file; +static int longest_author; +static int max_orig_digits; +static int max_digits; +static int max_score_digits; + +#ifndef DEBUG +#define DEBUG 0 +#endif + +/* stats */ +static int num_read_blob; +static int num_get_patch; +static int num_commits; + +#define PICKAXE_BLAME_MOVE 01 +#define PICKAXE_BLAME_COPY 02 +#define PICKAXE_BLAME_COPY_HARDER 04 + +/* + * blame for a blame_entry with score lower than these thresholds + * is not passed to the parent using move/copy logic. + */ +static unsigned blame_move_score; +static unsigned blame_copy_score; +#define BLAME_DEFAULT_MOVE_SCORE 20 +#define BLAME_DEFAULT_COPY_SCORE 40 + +/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */ +#define METAINFO_SHOWN (1u<<12) +#define MORE_THAN_ONE_PATH (1u<<13) + +/* + * One blob in a commit that is being suspected + */ +struct origin { + int refcnt; + struct commit *commit; + mmfile_t file; + unsigned char blob_sha1[20]; + char path[FLEX_ARRAY]; +}; + +static char *fill_origin_blob(struct origin *o, mmfile_t *file) +{ + if (!o->file.ptr) { + char type[10]; + num_read_blob++; + file->ptr = read_sha1_file(o->blob_sha1, type, + (unsigned long *)(&(file->size))); + o->file = *file; + } + else + *file = o->file; + return file->ptr; +} + +static inline struct origin *origin_incref(struct origin *o) +{ + if (o) + o->refcnt++; + return o; +} + +static void origin_decref(struct origin *o) +{ + if (o && --o->refcnt <= 0) { + if (o->file.ptr) + free(o->file.ptr); + memset(o, 0, sizeof(*o)); + free(o); + } +} + +struct blame_entry { + struct blame_entry *prev; + struct blame_entry *next; + + /* the first line of this group in the final image; + * internally all line numbers are 0 based. + */ + int lno; + + /* how many lines this group has */ + int num_lines; + + /* the commit that introduced this group into the final image */ + struct origin *suspect; + + /* true if the suspect is truly guilty; false while we have not + * checked if the group came from one of its parents. + */ + char guilty; + + /* the line number of the first line of this group in the + * suspect's file; internally all line numbers are 0 based. + */ + int s_lno; + + /* how significant this entry is -- cached to avoid + * scanning the lines over and over + */ + unsigned score; +}; + +struct scoreboard { + /* the final commit (i.e. where we started digging from) */ + struct commit *final; + + const char *path; + + /* the contents in the final; pointed into by buf pointers of + * blame_entries + */ + const char *final_buf; + unsigned long final_buf_size; + + /* linked list of blames */ + struct blame_entry *ent; + + /* look-up a line in the final buffer */ + int num_lines; + int *lineno; +}; + +static int cmp_suspect(struct origin *a, struct origin *b) +{ + int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1); + if (cmp) + return cmp; + return strcmp(a->path, b->path); +} + +#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) ) + +static void sanity_check_refcnt(struct scoreboard *); + +static void coalesce(struct scoreboard *sb) +{ + struct blame_entry *ent, *next; + + for (ent = sb->ent; ent && (next = ent->next); ent = next) { + if (!cmp_suspect(ent->suspect, next->suspect) && + ent->guilty == next->guilty && + ent->s_lno + ent->num_lines == next->s_lno) { + ent->num_lines += next->num_lines; + ent->next = next->next; + if (ent->next) + ent->next->prev = ent; + origin_decref(next->suspect); + free(next); + ent->score = 0; + next = ent; /* again */ + } + } + + if (DEBUG) /* sanity */ + sanity_check_refcnt(sb); +} + +static struct origin *make_origin(struct commit *commit, const char *path) +{ + struct origin *o; + o = xcalloc(1, sizeof(*o) + strlen(path) + 1); + o->commit = commit; + o->refcnt = 1; + strcpy(o->path, path); + return o; +} + +static struct origin *get_origin(struct scoreboard *sb, + struct commit *commit, + const char *path) +{ + struct blame_entry *e; + + for (e = sb->ent; e; e = e->next) { + if (e->suspect->commit == commit && + !strcmp(e->suspect->path, path)) + return origin_incref(e->suspect); + } + return make_origin(commit, path); +} + +static int fill_blob_sha1(struct origin *origin) +{ + unsigned mode; + char type[10]; + + if (!is_null_sha1(origin->blob_sha1)) + return 0; + if (get_tree_entry(origin->commit->object.sha1, + origin->path, + origin->blob_sha1, &mode)) + goto error_out; + if (sha1_object_info(origin->blob_sha1, type, NULL) || + strcmp(type, blob_type)) + goto error_out; + return 0; + error_out: + hashclr(origin->blob_sha1); + return -1; +} + +static struct origin *find_origin(struct scoreboard *sb, + struct commit *parent, + struct origin *origin) +{ + struct origin *porigin = NULL; + struct diff_options diff_opts; + const char *paths[2]; + + if (parent->util) { + /* This is a freestanding copy of origin and not + * refcounted. + */ + struct origin *cached = parent->util; + if (!strcmp(cached->path, origin->path)) { + porigin = get_origin(sb, parent, cached->path); + if (porigin->refcnt == 1) + hashcpy(porigin->blob_sha1, cached->blob_sha1); + return porigin; + } + /* otherwise it was not very useful; free it */ + free(parent->util); + parent->util = NULL; + } + + /* See if the origin->path is different between parent + * and origin first. Most of the time they are the + * same and diff-tree is fairly efficient about this. + */ + diff_setup(&diff_opts); + diff_opts.recursive = 1; + diff_opts.detect_rename = 0; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + paths[0] = origin->path; + paths[1] = NULL; + + diff_tree_setup_paths(paths, &diff_opts); + if (diff_setup_done(&diff_opts) < 0) + die("diff-setup"); + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); + diffcore_std(&diff_opts); + + /* It is either one entry that says "modified", or "created", + * or nothing. + */ + if (!diff_queued_diff.nr) { + /* The path is the same as parent */ + porigin = get_origin(sb, parent, origin->path); + hashcpy(porigin->blob_sha1, origin->blob_sha1); + } + else if (diff_queued_diff.nr != 1) + die("internal error in blame::find_origin"); + else { + struct diff_filepair *p = diff_queued_diff.queue[0]; + switch (p->status) { + default: + die("internal error in blame::find_origin (%c)", + p->status); + case 'M': + porigin = get_origin(sb, parent, origin->path); + hashcpy(porigin->blob_sha1, p->one->sha1); + break; + case 'A': + case 'T': + /* Did not exist in parent, or type changed */ + break; + } + } + diff_flush(&diff_opts); + if (porigin) { + struct origin *cached; + cached = make_origin(porigin->commit, porigin->path); + hashcpy(cached->blob_sha1, porigin->blob_sha1); + parent->util = cached; + } + return porigin; +} + +static struct origin *find_rename(struct scoreboard *sb, + struct commit *parent, + struct origin *origin) +{ + struct origin *porigin = NULL; + struct diff_options diff_opts; + int i; + const char *paths[2]; + + diff_setup(&diff_opts); + diff_opts.recursive = 1; + diff_opts.detect_rename = DIFF_DETECT_RENAME; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_opts.single_follow = origin->path; + paths[0] = NULL; + diff_tree_setup_paths(paths, &diff_opts); + if (diff_setup_done(&diff_opts) < 0) + die("diff-setup"); + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); + diffcore_std(&diff_opts); + + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + if ((p->status == 'R' || p->status == 'C') && + !strcmp(p->two->path, origin->path)) { + porigin = get_origin(sb, parent, p->one->path); + hashcpy(porigin->blob_sha1, p->one->sha1); + break; + } + } + diff_flush(&diff_opts); + return porigin; +} + +struct chunk { + /* line number in postimage; up to but not including this + * line is the same as preimage + */ + int same; + + /* preimage line number after this chunk */ + int p_next; + + /* postimage line number after this chunk */ + int t_next; +}; + +struct patch { + struct chunk *chunks; + int num; +}; + +struct blame_diff_state { + struct xdiff_emit_state xm; + struct patch *ret; + unsigned hunk_post_context; + unsigned hunk_in_pre_context : 1; +}; + +static void process_u_diff(void *state_, char *line, unsigned long len) +{ + struct blame_diff_state *state = state_; + struct chunk *chunk; + int off1, off2, len1, len2, num; + + num = state->ret->num; + if (len < 4 || line[0] != '@' || line[1] != '@') { + if (state->hunk_in_pre_context && line[0] == ' ') + state->ret->chunks[num - 1].same++; + else { + state->hunk_in_pre_context = 0; + if (line[0] == ' ') + state->hunk_post_context++; + else + state->hunk_post_context = 0; + } + return; + } + + if (num && state->hunk_post_context) { + chunk = &state->ret->chunks[num - 1]; + chunk->p_next -= state->hunk_post_context; + chunk->t_next -= state->hunk_post_context; + } + state->ret->num = ++num; + state->ret->chunks = xrealloc(state->ret->chunks, + sizeof(struct chunk) * num); + chunk = &state->ret->chunks[num - 1]; + if (parse_hunk_header(line, len, &off1, &len1, &off2, &len2)) { + state->ret->num--; + return; + } + + /* Line numbers in patch output are one based. */ + off1--; + off2--; + + chunk->same = len2 ? off2 : (off2 + 1); + + chunk->p_next = off1 + (len1 ? len1 : 1); + chunk->t_next = chunk->same + len2; + state->hunk_in_pre_context = 1; + state->hunk_post_context = 0; +} + +static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o, + int context) +{ + struct blame_diff_state state; + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = context; + xecfg.flags = 0; + ecb.outf = xdiff_outf; + ecb.priv = &state; + memset(&state, 0, sizeof(state)); + state.xm.consume = process_u_diff; + state.ret = xmalloc(sizeof(struct patch)); + state.ret->chunks = NULL; + state.ret->num = 0; + + xdl_diff(file_p, file_o, &xpp, &xecfg, &ecb); + + if (state.ret->num) { + struct chunk *chunk; + chunk = &state.ret->chunks[state.ret->num - 1]; + chunk->p_next -= state.hunk_post_context; + chunk->t_next -= state.hunk_post_context; + } + return state.ret; +} + +static struct patch *get_patch(struct origin *parent, struct origin *origin) +{ + mmfile_t file_p, file_o; + struct patch *patch; + + fill_origin_blob(parent, &file_p); + fill_origin_blob(origin, &file_o); + if (!file_p.ptr || !file_o.ptr) + return NULL; + patch = compare_buffer(&file_p, &file_o, 0); + num_get_patch++; + return patch; +} + +static void free_patch(struct patch *p) +{ + free(p->chunks); + free(p); +} + +static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) +{ + struct blame_entry *ent, *prev = NULL; + + origin_incref(e->suspect); + + for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next) + prev = ent; + + /* prev, if not NULL, is the last one that is below e */ + e->prev = prev; + if (prev) { + e->next = prev->next; + prev->next = e; + } + else { + e->next = sb->ent; + sb->ent = e; + } + if (e->next) + e->next->prev = e; +} + +static void dup_entry(struct blame_entry *dst, struct blame_entry *src) +{ + struct blame_entry *p, *n; + + p = dst->prev; + n = dst->next; + origin_incref(src->suspect); + origin_decref(dst->suspect); + memcpy(dst, src, sizeof(*src)); + dst->prev = p; + dst->next = n; + dst->score = 0; +} + +static const char *nth_line(struct scoreboard *sb, int lno) +{ + return sb->final_buf + sb->lineno[lno]; +} + +static void split_overlap(struct blame_entry *split, + struct blame_entry *e, + int tlno, int plno, int same, + struct origin *parent) +{ + /* it is known that lines between tlno to same came from + * parent, and e has an overlap with that range. it also is + * known that parent's line plno corresponds to e's line tlno. + * + * <---- e -----> + * <------> + * <------------> + * <------------> + * <------------------> + * + * Potentially we need to split e into three parts; before + * this chunk, the chunk to be blamed for parent, and after + * that portion. + */ + int chunk_end_lno; + memset(split, 0, sizeof(struct blame_entry [3])); + + if (e->s_lno < tlno) { + /* there is a pre-chunk part not blamed on parent */ + split[0].suspect = origin_incref(e->suspect); + split[0].lno = e->lno; + split[0].s_lno = e->s_lno; + split[0].num_lines = tlno - e->s_lno; + split[1].lno = e->lno + tlno - e->s_lno; + split[1].s_lno = plno; + } + else { + split[1].lno = e->lno; + split[1].s_lno = plno + (e->s_lno - tlno); + } + + if (same < e->s_lno + e->num_lines) { + /* there is a post-chunk part not blamed on parent */ + split[2].suspect = origin_incref(e->suspect); + split[2].lno = e->lno + (same - e->s_lno); + split[2].s_lno = e->s_lno + (same - e->s_lno); + split[2].num_lines = e->s_lno + e->num_lines - same; + chunk_end_lno = split[2].lno; + } + else + chunk_end_lno = e->lno + e->num_lines; + split[1].num_lines = chunk_end_lno - split[1].lno; + + if (split[1].num_lines < 1) + return; + split[1].suspect = origin_incref(parent); +} + +static void split_blame(struct scoreboard *sb, + struct blame_entry *split, + struct blame_entry *e) +{ + struct blame_entry *new_entry; + + if (split[0].suspect && split[2].suspect) { + /* we need to split e into two and add another for parent */ + dup_entry(e, &split[0]); + + new_entry = xmalloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); + add_blame_entry(sb, new_entry); + + new_entry = xmalloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); + add_blame_entry(sb, new_entry); + } + else if (!split[0].suspect && !split[2].suspect) + /* parent covers the entire area */ + dup_entry(e, &split[1]); + else if (split[0].suspect) { + dup_entry(e, &split[0]); + + new_entry = xmalloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); + add_blame_entry(sb, new_entry); + } + else { + dup_entry(e, &split[1]); + + new_entry = xmalloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); + add_blame_entry(sb, new_entry); + } + + if (DEBUG) { /* sanity */ + struct blame_entry *ent; + int lno = sb->ent->lno, corrupt = 0; + + for (ent = sb->ent; ent; ent = ent->next) { + if (lno != ent->lno) + corrupt = 1; + if (ent->s_lno < 0) + corrupt = 1; + lno += ent->num_lines; + } + if (corrupt) { + lno = sb->ent->lno; + for (ent = sb->ent; ent; ent = ent->next) { + printf("L %8d l %8d n %8d\n", + lno, ent->lno, ent->num_lines); + lno = ent->lno + ent->num_lines; + } + die("oops"); + } + } +} + +static void decref_split(struct blame_entry *split) +{ + int i; + + for (i = 0; i < 3; i++) + origin_decref(split[i].suspect); +} + +static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, + int tlno, int plno, int same, + struct origin *parent) +{ + struct blame_entry split[3]; + + split_overlap(split, e, tlno, plno, same, parent); + if (split[1].suspect) + split_blame(sb, split, e); + decref_split(split); +} + +static int find_last_in_target(struct scoreboard *sb, struct origin *target) +{ + struct blame_entry *e; + int last_in_target = -1; + + for (e = sb->ent; e; e = e->next) { + if (e->guilty || cmp_suspect(e->suspect, target)) + continue; + if (last_in_target < e->s_lno + e->num_lines) + last_in_target = e->s_lno + e->num_lines; + } + return last_in_target; +} + +static void blame_chunk(struct scoreboard *sb, + int tlno, int plno, int same, + struct origin *target, struct origin *parent) +{ + struct blame_entry *e; + + for (e = sb->ent; e; e = e->next) { + if (e->guilty || cmp_suspect(e->suspect, target)) + continue; + if (same <= e->s_lno) + continue; + if (tlno < e->s_lno + e->num_lines) + blame_overlap(sb, e, tlno, plno, same, parent); + } +} + +static int pass_blame_to_parent(struct scoreboard *sb, + struct origin *target, + struct origin *parent) +{ + int i, last_in_target, plno, tlno; + struct patch *patch; + + last_in_target = find_last_in_target(sb, target); + if (last_in_target < 0) + return 1; /* nothing remains for this target */ + + patch = get_patch(parent, target); + plno = tlno = 0; + for (i = 0; i < patch->num; i++) { + struct chunk *chunk = &patch->chunks[i]; + + blame_chunk(sb, tlno, plno, chunk->same, target, parent); + plno = chunk->p_next; + tlno = chunk->t_next; + } + /* rest (i.e. anything above tlno) are the same as parent */ + blame_chunk(sb, tlno, plno, last_in_target, target, parent); + + free_patch(patch); + return 0; +} + +static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) +{ + unsigned score; + const char *cp, *ep; + + if (e->score) + return e->score; + + score = 1; + cp = nth_line(sb, e->lno); + ep = nth_line(sb, e->lno + e->num_lines); + while (cp < ep) { + unsigned ch = *((unsigned char *)cp); + if (isalnum(ch)) + score++; + cp++; + } + e->score = score; + return score; +} + +static void copy_split_if_better(struct scoreboard *sb, + struct blame_entry *best_so_far, + struct blame_entry *this) +{ + int i; + + if (!this[1].suspect) + return; + if (best_so_far[1].suspect) { + if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1])) + return; + } + + for (i = 0; i < 3; i++) + origin_incref(this[i].suspect); + decref_split(best_so_far); + memcpy(best_so_far, this, sizeof(struct blame_entry [3])); +} + +static void find_copy_in_blob(struct scoreboard *sb, + struct blame_entry *ent, + struct origin *parent, + struct blame_entry *split, + mmfile_t *file_p) +{ + const char *cp; + int cnt; + mmfile_t file_o; + struct patch *patch; + int i, plno, tlno; + + cp = nth_line(sb, ent->lno); + file_o.ptr = (char*) cp; + cnt = ent->num_lines; + + while (cnt && cp < sb->final_buf + sb->final_buf_size) { + if (*cp++ == '\n') + cnt--; + } + file_o.size = cp - file_o.ptr; + + patch = compare_buffer(file_p, &file_o, 1); + + memset(split, 0, sizeof(struct blame_entry [3])); + plno = tlno = 0; + for (i = 0; i < patch->num; i++) { + struct chunk *chunk = &patch->chunks[i]; + + /* tlno to chunk->same are the same as ent */ + if (ent->num_lines <= tlno) + break; + if (tlno < chunk->same) { + struct blame_entry this[3]; + split_overlap(this, ent, + tlno + ent->s_lno, plno, + chunk->same + ent->s_lno, + parent); + copy_split_if_better(sb, split, this); + decref_split(this); + } + plno = chunk->p_next; + tlno = chunk->t_next; + } + free_patch(patch); +} + +static int find_move_in_parent(struct scoreboard *sb, + struct origin *target, + struct origin *parent) +{ + int last_in_target, made_progress; + struct blame_entry *e, split[3]; + mmfile_t file_p; + + last_in_target = find_last_in_target(sb, target); + if (last_in_target < 0) + return 1; /* nothing remains for this target */ + + fill_origin_blob(parent, &file_p); + if (!file_p.ptr) + return 0; + + made_progress = 1; + while (made_progress) { + made_progress = 0; + for (e = sb->ent; e; e = e->next) { + if (e->guilty || cmp_suspect(e->suspect, target)) + continue; + find_copy_in_blob(sb, e, parent, split, &file_p); + if (split[1].suspect && + blame_move_score < ent_score(sb, &split[1])) { + split_blame(sb, split, e); + made_progress = 1; + } + decref_split(split); + } + } + return 0; +} + + +struct blame_list { + struct blame_entry *ent; + struct blame_entry split[3]; +}; + +static struct blame_list *setup_blame_list(struct scoreboard *sb, + struct origin *target, + int *num_ents_p) +{ + struct blame_entry *e; + int num_ents, i; + struct blame_list *blame_list = NULL; + + /* Count the number of entries the target is suspected for, + * and prepare a list of entry and the best split. + */ + for (e = sb->ent, num_ents = 0; e; e = e->next) + if (!e->guilty && !cmp_suspect(e->suspect, target)) + num_ents++; + if (num_ents) { + blame_list = xcalloc(num_ents, sizeof(struct blame_list)); + for (e = sb->ent, i = 0; e; e = e->next) + if (!e->guilty && !cmp_suspect(e->suspect, target)) + blame_list[i++].ent = e; + } + *num_ents_p = num_ents; + return blame_list; +} + +static int find_copy_in_parent(struct scoreboard *sb, + struct origin *target, + struct commit *parent, + struct origin *porigin, + int opt) +{ + struct diff_options diff_opts; + const char *paths[1]; + int i, j; + int retval; + struct blame_list *blame_list; + int num_ents; + + blame_list = setup_blame_list(sb, target, &num_ents); + if (!blame_list) + return 1; /* nothing remains for this target */ + + diff_setup(&diff_opts); + diff_opts.recursive = 1; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + + paths[0] = NULL; + diff_tree_setup_paths(paths, &diff_opts); + if (diff_setup_done(&diff_opts) < 0) + die("diff-setup"); + + /* Try "find copies harder" on new path if requested; + * we do not want to use diffcore_rename() actually to + * match things up; find_copies_harder is set only to + * force diff_tree_sha1() to feed all filepairs to diff_queue, + * and this code needs to be after diff_setup_done(), which + * usually makes find-copies-harder imply copy detection. + */ + if ((opt & PICKAXE_BLAME_COPY_HARDER) && + (!porigin || strcmp(target->path, porigin->path))) + diff_opts.find_copies_harder = 1; + + diff_tree_sha1(parent->tree->object.sha1, + target->commit->tree->object.sha1, + "", &diff_opts); + + if (!diff_opts.find_copies_harder) + diffcore_std(&diff_opts); + + retval = 0; + while (1) { + int made_progress = 0; + + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + struct origin *norigin; + mmfile_t file_p; + struct blame_entry this[3]; + + if (!DIFF_FILE_VALID(p->one)) + continue; /* does not exist in parent */ + if (porigin && !strcmp(p->one->path, porigin->path)) + /* find_move already dealt with this path */ + continue; + + norigin = get_origin(sb, parent, p->one->path); + hashcpy(norigin->blob_sha1, p->one->sha1); + fill_origin_blob(norigin, &file_p); + if (!file_p.ptr) + continue; + + for (j = 0; j < num_ents; j++) { + find_copy_in_blob(sb, blame_list[j].ent, + norigin, this, &file_p); + copy_split_if_better(sb, blame_list[j].split, + this); + decref_split(this); + } + origin_decref(norigin); + } + + for (j = 0; j < num_ents; j++) { + struct blame_entry *split = blame_list[j].split; + if (split[1].suspect && + blame_copy_score < ent_score(sb, &split[1])) { + split_blame(sb, split, blame_list[j].ent); + made_progress = 1; + } + decref_split(split); + } + free(blame_list); + + if (!made_progress) + break; + blame_list = setup_blame_list(sb, target, &num_ents); + if (!blame_list) { + retval = 1; + break; + } + } + diff_flush(&diff_opts); + + return retval; +} + +/* The blobs of origin and porigin exactly match, so everything + * origin is suspected for can be blamed on the parent. + */ +static void pass_whole_blame(struct scoreboard *sb, + struct origin *origin, struct origin *porigin) +{ + struct blame_entry *e; + + if (!porigin->file.ptr && origin->file.ptr) { + /* Steal its file */ + porigin->file = origin->file; + origin->file.ptr = NULL; + } + for (e = sb->ent; e; e = e->next) { + if (cmp_suspect(e->suspect, origin)) + continue; + origin_incref(porigin); + origin_decref(e->suspect); + e->suspect = porigin; + } +} + +#define MAXPARENT 16 + +static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) +{ + int i, pass; + struct commit *commit = origin->commit; + struct commit_list *parent; + struct origin *parent_origin[MAXPARENT], *porigin; + + memset(parent_origin, 0, sizeof(parent_origin)); + + /* The first pass looks for unrenamed path to optimize for + * common cases, then we look for renames in the second pass. + */ + for (pass = 0; pass < 2; pass++) { + struct origin *(*find)(struct scoreboard *, + struct commit *, struct origin *); + find = pass ? find_rename : find_origin; + + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct commit *p = parent->item; + int j, same; + + if (parent_origin[i]) + continue; + if (parse_commit(p)) + continue; + porigin = find(sb, p, origin); + if (!porigin) + continue; + if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { + pass_whole_blame(sb, origin, porigin); + origin_decref(porigin); + goto finish; + } + for (j = same = 0; j < i; j++) + if (parent_origin[j] && + !hashcmp(parent_origin[j]->blob_sha1, + porigin->blob_sha1)) { + same = 1; + break; + } + if (!same) + parent_origin[i] = porigin; + else + origin_decref(porigin); + } + } + + num_commits++; + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct origin *porigin = parent_origin[i]; + if (!porigin) + continue; + if (pass_blame_to_parent(sb, origin, porigin)) + goto finish; + } + + /* + * Optionally run "miff" to find moves in parents' files here. + */ + if (opt & PICKAXE_BLAME_MOVE) + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct origin *porigin = parent_origin[i]; + if (!porigin) + continue; + if (find_move_in_parent(sb, origin, porigin)) + goto finish; + } + + /* + * Optionally run "ciff" to find copies from parents' files here. + */ + if (opt & PICKAXE_BLAME_COPY) + for (i = 0, parent = commit->parents; + i < MAXPARENT && parent; + parent = parent->next, i++) { + struct origin *porigin = parent_origin[i]; + if (find_copy_in_parent(sb, origin, parent->item, + porigin, opt)) + goto finish; + } + + finish: + for (i = 0; i < MAXPARENT; i++) + origin_decref(parent_origin[i]); +} + +static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) +{ + while (1) { + struct blame_entry *ent; + struct commit *commit; + struct origin *suspect = NULL; + + /* find one suspect to break down */ + for (ent = sb->ent; !suspect && ent; ent = ent->next) + if (!ent->guilty) + suspect = ent->suspect; + if (!suspect) + return; /* all done */ + + origin_incref(suspect); + commit = suspect->commit; + if (!commit->object.parsed) + parse_commit(commit); + if (!(commit->object.flags & UNINTERESTING) && + !(revs->max_age != -1 && commit->date < revs->max_age)) + pass_blame(sb, suspect, opt); + + /* Take responsibility for the remaining entries */ + for (ent = sb->ent; ent; ent = ent->next) + if (!cmp_suspect(ent->suspect, suspect)) + ent->guilty = 1; + origin_decref(suspect); + + if (DEBUG) /* sanity */ + sanity_check_refcnt(sb); + } +} + +static const char *format_time(unsigned long time, const char *tz_str, + int show_raw_time) +{ + static char time_buf[128]; + time_t t = time; + int minutes, tz; + struct tm *tm; + + if (show_raw_time) { + sprintf(time_buf, "%lu %s", time, tz_str); + return time_buf; + } + + tz = atoi(tz_str); + minutes = tz < 0 ? -tz : tz; + minutes = (minutes / 100)*60 + (minutes % 100); + minutes = tz < 0 ? -minutes : minutes; + t = time + minutes * 60; + tm = gmtime(&t); + + strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); + strcat(time_buf, tz_str); + return time_buf; +} + +struct commit_info +{ + char *author; + char *author_mail; + unsigned long author_time; + char *author_tz; + + /* filled only when asked for details */ + char *committer; + char *committer_mail; + unsigned long committer_time; + char *committer_tz; + + char *summary; +}; + +static void get_ac_line(const char *inbuf, const char *what, + int bufsz, char *person, char **mail, + unsigned long *time, char **tz) +{ + int len; + char *tmp, *endp; + + tmp = strstr(inbuf, what); + if (!tmp) + goto error_out; + tmp += strlen(what); + endp = strchr(tmp, '\n'); + if (!endp) + len = strlen(tmp); + else + len = endp - tmp; + if (bufsz <= len) { + error_out: + /* Ugh */ + person = *mail = *tz = "(unknown)"; + *time = 0; + return; + } + memcpy(person, tmp, len); + + tmp = person; + tmp += len; + *tmp = 0; + while (*tmp != ' ') + tmp--; + *tz = tmp+1; + + *tmp = 0; + while (*tmp != ' ') + tmp--; + *time = strtoul(tmp, NULL, 10); + + *tmp = 0; + while (*tmp != ' ') + tmp--; + *mail = tmp + 1; + *tmp = 0; +} + +static void get_commit_info(struct commit *commit, + struct commit_info *ret, + int detailed) +{ + int len; + char *tmp, *endp; + static char author_buf[1024]; + static char committer_buf[1024]; + static char summary_buf[1024]; + + /* We've operated without save_commit_buffer, so + * we now need to populate them for output. + */ + if (!commit->buffer) { + char type[20]; + unsigned long size; + commit->buffer = + read_sha1_file(commit->object.sha1, type, &size); + } + ret->author = author_buf; + get_ac_line(commit->buffer, "\nauthor ", + sizeof(author_buf), author_buf, &ret->author_mail, + &ret->author_time, &ret->author_tz); + + if (!detailed) + return; + + ret->committer = committer_buf; + get_ac_line(commit->buffer, "\ncommitter ", + sizeof(committer_buf), committer_buf, &ret->committer_mail, + &ret->committer_time, &ret->committer_tz); + + ret->summary = summary_buf; + tmp = strstr(commit->buffer, "\n\n"); + if (!tmp) { + error_out: + sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); + return; + } + tmp += 2; + endp = strchr(tmp, '\n'); + if (!endp) + goto error_out; + len = endp - tmp; + if (len >= sizeof(summary_buf)) + goto error_out; + memcpy(summary_buf, tmp, len); + summary_buf[len] = 0; +} + +#define OUTPUT_ANNOTATE_COMPAT 001 +#define OUTPUT_LONG_OBJECT_NAME 002 +#define OUTPUT_RAW_TIMESTAMP 004 +#define OUTPUT_PORCELAIN 010 +#define OUTPUT_SHOW_NAME 020 +#define OUTPUT_SHOW_NUMBER 040 +#define OUTPUT_SHOW_SCORE 0100 + +static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) +{ + int cnt; + const char *cp; + struct origin *suspect = ent->suspect; + char hex[41]; + + strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); + printf("%s%c%d %d %d\n", + hex, + ent->guilty ? ' ' : '*', // purely for debugging + ent->s_lno + 1, + ent->lno + 1, + ent->num_lines); + if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { + struct commit_info ci; + suspect->commit->object.flags |= METAINFO_SHOWN; + get_commit_info(suspect->commit, &ci, 1); + printf("author %s\n", ci.author); + printf("author-mail %s\n", ci.author_mail); + printf("author-time %lu\n", ci.author_time); + printf("author-tz %s\n", ci.author_tz); + printf("committer %s\n", ci.committer); + printf("committer-mail %s\n", ci.committer_mail); + printf("committer-time %lu\n", ci.committer_time); + printf("committer-tz %s\n", ci.committer_tz); + printf("filename %s\n", suspect->path); + printf("summary %s\n", ci.summary); + } + else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH) + printf("filename %s\n", suspect->path); + + cp = nth_line(sb, ent->lno); + for (cnt = 0; cnt < ent->num_lines; cnt++) { + char ch; + if (cnt) + printf("%s %d %d\n", hex, + ent->s_lno + 1 + cnt, + ent->lno + 1 + cnt); + putchar('\t'); + do { + ch = *cp++; + putchar(ch); + } while (ch != '\n' && + cp < sb->final_buf + sb->final_buf_size); + } +} + +static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) +{ + int cnt; + const char *cp; + struct origin *suspect = ent->suspect; + struct commit_info ci; + char hex[41]; + int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); + + get_commit_info(suspect->commit, &ci, 1); + strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); + + cp = nth_line(sb, ent->lno); + for (cnt = 0; cnt < ent->num_lines; cnt++) { + char ch; + + printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex); + if (opt & OUTPUT_ANNOTATE_COMPAT) + printf("\t(%10s\t%10s\t%d)", ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + ent->lno + 1 + cnt); + else { + if (opt & OUTPUT_SHOW_SCORE) + printf(" %*d %02d", + max_score_digits, ent->score, + ent->suspect->refcnt); + if (opt & OUTPUT_SHOW_NAME) + printf(" %-*.*s", longest_file, longest_file, + suspect->path); + if (opt & OUTPUT_SHOW_NUMBER) + printf(" %*d", max_orig_digits, + ent->s_lno + 1 + cnt); + printf(" (%-*.*s %10s %*d) ", + longest_author, longest_author, ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + max_digits, ent->lno + 1 + cnt); + } + do { + ch = *cp++; + putchar(ch); + } while (ch != '\n' && + cp < sb->final_buf + sb->final_buf_size); + } +} + +static void output(struct scoreboard *sb, int option) +{ + struct blame_entry *ent; + + if (option & OUTPUT_PORCELAIN) { + for (ent = sb->ent; ent; ent = ent->next) { + struct blame_entry *oth; + struct origin *suspect = ent->suspect; + struct commit *commit = suspect->commit; + if (commit->object.flags & MORE_THAN_ONE_PATH) + continue; + for (oth = ent->next; oth; oth = oth->next) { + if ((oth->suspect->commit != commit) || + !strcmp(oth->suspect->path, suspect->path)) + continue; + commit->object.flags |= MORE_THAN_ONE_PATH; + break; + } + } + } + + for (ent = sb->ent; ent; ent = ent->next) { + if (option & OUTPUT_PORCELAIN) + emit_porcelain(sb, ent); + else { + emit_other(sb, ent, option); + } + } +} + +static int prepare_lines(struct scoreboard *sb) +{ + const char *buf = sb->final_buf; + unsigned long len = sb->final_buf_size; + int num = 0, incomplete = 0, bol = 1; + + if (len && buf[len-1] != '\n') + incomplete++; /* incomplete line at the end */ + while (len--) { + if (bol) { + sb->lineno = xrealloc(sb->lineno, + sizeof(int* ) * (num + 1)); + sb->lineno[num] = buf - sb->final_buf; + bol = 0; + } + if (*buf++ == '\n') { + num++; + bol = 1; + } + } + sb->lineno = xrealloc(sb->lineno, + sizeof(int* ) * (num + incomplete + 1)); + sb->lineno[num + incomplete] = buf - sb->final_buf; + sb->num_lines = num + incomplete; + return sb->num_lines; +} + +static int read_ancestry(const char *graft_file) +{ + FILE *fp = fopen(graft_file, "r"); + char buf[1024]; + if (!fp) + return -1; + while (fgets(buf, sizeof(buf), fp)) { + /* The format is just "Commit Parent1 Parent2 ...\n" */ + int len = strlen(buf); + struct commit_graft *graft = read_graft_line(buf, len); + register_commit_graft(graft, 0); + } + fclose(fp); + return 0; +} + +static int lineno_width(int lines) +{ + int i, width; + + for (width = 1, i = 10; i <= lines + 1; width++) + i *= 10; + return width; +} + +static void find_alignment(struct scoreboard *sb, int *option) +{ + int longest_src_lines = 0; + int longest_dst_lines = 0; + unsigned largest_score = 0; + struct blame_entry *e; + + for (e = sb->ent; e; e = e->next) { + struct origin *suspect = e->suspect; + struct commit_info ci; + int num; + + if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { + suspect->commit->object.flags |= METAINFO_SHOWN; + get_commit_info(suspect->commit, &ci, 1); + if (strcmp(suspect->path, sb->path)) + *option |= OUTPUT_SHOW_NAME; + num = strlen(suspect->path); + if (longest_file < num) + longest_file = num; + num = strlen(ci.author); + if (longest_author < num) + longest_author = num; + } + num = e->s_lno + e->num_lines; + if (longest_src_lines < num) + longest_src_lines = num; + num = e->lno + e->num_lines; + if (longest_dst_lines < num) + longest_dst_lines = num; + if (largest_score < ent_score(sb, e)) + largest_score = ent_score(sb, e); + } + max_orig_digits = lineno_width(longest_src_lines); + max_digits = lineno_width(longest_dst_lines); + max_score_digits = lineno_width(largest_score); +} + +static void sanity_check_refcnt(struct scoreboard *sb) +{ + int baa = 0; + struct blame_entry *ent; + + for (ent = sb->ent; ent; ent = ent->next) { + /* Nobody should have zero or negative refcnt */ + if (ent->suspect->refcnt <= 0) { + fprintf(stderr, "%s in %s has negative refcnt %d\n", + ent->suspect->path, + sha1_to_hex(ent->suspect->commit->object.sha1), + ent->suspect->refcnt); + baa = 1; + } + } + for (ent = sb->ent; ent; ent = ent->next) { + /* Mark the ones that haven't been checked */ + if (0 < ent->suspect->refcnt) + ent->suspect->refcnt = -ent->suspect->refcnt; + } + for (ent = sb->ent; ent; ent = ent->next) { + /* then pick each and see if they have the the correct + * refcnt. + */ + int found; + struct blame_entry *e; + struct origin *suspect = ent->suspect; + + if (0 < suspect->refcnt) + continue; + suspect->refcnt = -suspect->refcnt; /* Unmark */ + for (found = 0, e = sb->ent; e; e = e->next) { + if (e->suspect != suspect) + continue; + found++; + } + if (suspect->refcnt != found) { + fprintf(stderr, "%s in %s has refcnt %d, not %d\n", + ent->suspect->path, + sha1_to_hex(ent->suspect->commit->object.sha1), + ent->suspect->refcnt, found); + baa = 2; + } + } + if (baa) { + int opt = 0160; + find_alignment(sb, &opt); + output(sb, opt); + die("Baa %d!", baa); + } +} + +static int has_path_in_work_tree(const char *path) +{ + struct stat st; + return !lstat(path, &st); +} + +static unsigned parse_score(const char *arg) +{ + char *end; + unsigned long score = strtoul(arg, &end, 10); + if (*end) + return 0; + return score; +} + +static const char *add_prefix(const char *prefix, const char *path) +{ + if (!prefix || !prefix[0]) + return path; + return prefix_path(prefix, strlen(prefix), path); +} + +static const char *parse_loc(const char *spec, + struct scoreboard *sb, long lno, + long begin, long *ret) +{ + char *term; + const char *line; + long num; + int reg_error; + regex_t regexp; + regmatch_t match[1]; + + /* Allow "-L ,+20" to mean starting at + * for 20 lines, or "-L ,-5" for 5 lines ending at + * . + */ + if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { + num = strtol(spec + 1, &term, 10); + if (term != spec + 1) { + if (spec[0] == '-') + num = 0 - num; + if (0 < num) + *ret = begin + num - 2; + else if (!num) + *ret = begin; + else + *ret = begin + num; + return term; + } + return spec; + } + num = strtol(spec, &term, 10); + if (term != spec) { + *ret = num; + return term; + } + if (spec[0] != '/') + return spec; + + /* it could be a regexp of form /.../ */ + for (term = (char*) spec + 1; *term && *term != '/'; term++) { + if (*term == '\\') + term++; + } + if (*term != '/') + return spec; + + /* try [spec+1 .. term-1] as regexp */ + *term = 0; + begin--; /* input is in human terms */ + line = nth_line(sb, begin); + + if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && + !(reg_error = regexec(®exp, line, 1, match, 0))) { + const char *cp = line + match[0].rm_so; + const char *nline; + + while (begin++ < lno) { + nline = nth_line(sb, begin); + if (line <= cp && cp < nline) + break; + line = nline; + } + *ret = begin; + regfree(®exp); + *term++ = '/'; + return term; + } + else { + char errbuf[1024]; + regerror(reg_error, ®exp, errbuf, 1024); + die("-L parameter '%s': %s", spec + 1, errbuf); + } +} + +static void prepare_blame_range(struct scoreboard *sb, + const char *bottomtop, + long lno, + long *bottom, long *top) +{ + const char *term; + + term = parse_loc(bottomtop, sb, lno, 1, bottom); + if (*term == ',') { + term = parse_loc(term + 1, sb, lno, *bottom + 1, top); + if (*term) + usage(blame_usage); + } + if (*term) + usage(blame_usage); +} + +int cmd_blame(int argc, const char **argv, const char *prefix) +{ + struct rev_info revs; + const char *path; + struct scoreboard sb; + struct origin *o; + struct blame_entry *ent; + int i, seen_dashdash, unk, opt; + long bottom, top, lno; + int output_option = 0; + const char *revs_file = NULL; + const char *final_commit_name = NULL; + char type[10]; + const char *bottomtop = NULL; + + save_commit_buffer = 0; + + opt = 0; + seen_dashdash = 0; + for (unk = i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (*arg != '-') + break; + else if (!strcmp("-c", arg)) + output_option |= OUTPUT_ANNOTATE_COMPAT; + else if (!strcmp("-t", arg)) + output_option |= OUTPUT_RAW_TIMESTAMP; + else if (!strcmp("-l", arg)) + output_option |= OUTPUT_LONG_OBJECT_NAME; + else if (!strcmp("-S", arg) && ++i < argc) + revs_file = argv[i]; + else if (!strncmp("-M", arg, 2)) { + opt |= PICKAXE_BLAME_MOVE; + blame_move_score = parse_score(arg+2); + } + else if (!strncmp("-C", arg, 2)) { + if (opt & PICKAXE_BLAME_COPY) + opt |= PICKAXE_BLAME_COPY_HARDER; + opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; + blame_copy_score = parse_score(arg+2); + } + else if (!strncmp("-L", arg, 2)) { + if (!arg[2]) { + if (++i >= argc) + usage(blame_usage); + arg = argv[i]; + } + else + arg += 2; + if (bottomtop) + die("More than one '-L n,m' option given"); + bottomtop = arg; + } + else if (!strcmp("--score-debug", arg)) + output_option |= OUTPUT_SHOW_SCORE; + else if (!strcmp("-f", arg) || + !strcmp("--show-name", arg)) + output_option |= OUTPUT_SHOW_NAME; + else if (!strcmp("-n", arg) || + !strcmp("--show-number", arg)) + output_option |= OUTPUT_SHOW_NUMBER; + else if (!strcmp("-p", arg) || + !strcmp("--porcelain", arg)) + output_option |= OUTPUT_PORCELAIN; + else if (!strcmp("--", arg)) { + seen_dashdash = 1; + i++; + break; + } + else + argv[unk++] = arg; + } + + if (!blame_move_score) + blame_move_score = BLAME_DEFAULT_MOVE_SCORE; + if (!blame_copy_score) + blame_copy_score = BLAME_DEFAULT_COPY_SCORE; + + /* We have collected options unknown to us in argv[1..unk] + * which are to be passed to revision machinery if we are + * going to do the "bottom" procesing. + * + * The remaining are: + * + * (1) if seen_dashdash, its either + * "-options -- " or + * "-options -- ". + * but the latter is allowed only if there is no + * options that we passed to revision machinery. + * + * (2) otherwise, we may have "--" somewhere later and + * might be looking at the first one of multiple 'rev' + * parameters (e.g. " master ^next ^maint -- path"). + * See if there is a dashdash first, and give the + * arguments before that to revision machinery. + * After that there must be one 'path'. + * + * (3) otherwise, its one of the three: + * "-options " + * "-options " + * "-options " + * but again the first one is allowed only if + * there is no options that we passed to revision + * machinery. + */ + + if (seen_dashdash) { + /* (1) */ + if (argc <= i) + usage(blame_usage); + path = add_prefix(prefix, argv[i]); + if (i + 1 == argc - 1) { + if (unk != 1) + usage(blame_usage); + argv[unk++] = argv[i + 1]; + } + else if (i + 1 != argc) + /* garbage at end */ + usage(blame_usage); + } + else { + int j; + for (j = i; !seen_dashdash && j < argc; j++) + if (!strcmp(argv[j], "--")) + seen_dashdash = j; + if (seen_dashdash) { + if (seen_dashdash + 1 != argc - 1) + usage(blame_usage); + path = add_prefix(prefix, argv[seen_dashdash + 1]); + for (j = i; j < seen_dashdash; j++) + argv[unk++] = argv[j]; + } + else { + /* (3) */ + path = add_prefix(prefix, argv[i]); + if (i + 1 == argc - 1) { + final_commit_name = argv[i + 1]; + + /* if (unk == 1) we could be getting + * old-style + */ + if (unk == 1 && !has_path_in_work_tree(path)) { + path = add_prefix(prefix, argv[i + 1]); + final_commit_name = argv[i]; + } + } + else if (i != argc - 1) + usage(blame_usage); /* garbage at end */ + + if (!has_path_in_work_tree(path)) + die("cannot stat path %s: %s", + path, strerror(errno)); + } + } + + if (final_commit_name) + argv[unk++] = final_commit_name; + + /* Now we got rev and path. We do not want the path pruning + * but we may want "bottom" processing. + */ + argv[unk] = NULL; + + init_revisions(&revs, NULL); + setup_revisions(unk, argv, &revs, "HEAD"); + memset(&sb, 0, sizeof(sb)); + + /* There must be one and only one positive commit in the + * revs->pending array. + */ + for (i = 0; i < revs.pending.nr; i++) { + struct object *obj = revs.pending.objects[i].item; + if (obj->flags & UNINTERESTING) + continue; + while (obj->type == OBJ_TAG) + obj = deref_tag(obj, NULL, 0); + if (obj->type != OBJ_COMMIT) + die("Non commit %s?", + revs.pending.objects[i].name); + if (sb.final) + die("More than one commit to dig from %s and %s?", + revs.pending.objects[i].name, + final_commit_name); + sb.final = (struct commit *) obj; + final_commit_name = revs.pending.objects[i].name; + } + + if (!sb.final) { + /* "--not A B -- path" without anything positive */ + unsigned char head_sha1[20]; + + final_commit_name = "HEAD"; + if (get_sha1(final_commit_name, head_sha1)) + die("No such ref: HEAD"); + sb.final = lookup_commit_reference(head_sha1); + add_pending_object(&revs, &(sb.final->object), "HEAD"); + } + + /* If we have bottom, this will mark the ancestors of the + * bottom commits we would reach while traversing as + * uninteresting. + */ + prepare_revision_walk(&revs); + + o = get_origin(&sb, sb.final, path); + if (fill_blob_sha1(o)) + die("no such path %s in %s", path, final_commit_name); + + sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); + num_read_blob++; + lno = prepare_lines(&sb); + + bottom = top = 0; + if (bottomtop) + prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); + if (bottom && top && top < bottom) { + long tmp; + tmp = top; top = bottom; bottom = tmp; + } + if (bottom < 1) + bottom = 1; + if (top < 1) + top = lno; + bottom--; + if (lno < top) + die("file %s has only %lu lines", path, lno); + + ent = xcalloc(1, sizeof(*ent)); + ent->lno = bottom; + ent->num_lines = top - bottom; + ent->suspect = o; + ent->s_lno = bottom; + + sb.ent = ent; + sb.path = path; + + if (revs_file && read_ancestry(revs_file)) + die("reading graft file %s failed: %s", + revs_file, strerror(errno)); + + assign_blame(&sb, &revs, opt); + + coalesce(&sb); + + if (!(output_option & OUTPUT_PORCELAIN)) + find_alignment(&sb, &output_option); + + output(&sb, output_option); + free((void *)sb.final_buf); + for (ent = sb.ent; ent; ) { + struct blame_entry *e = ent->next; + free(ent); + ent = e; + } + + if (DEBUG) { + printf("num read blob: %d\n", num_read_blob); + printf("num get patch: %d\n", num_get_patch); + printf("num commits: %d\n", num_commits); + } + return 0; +} diff --git a/builtin-pickaxe.c b/builtin-pickaxe.c deleted file mode 100644 index 64999f3..0000000 --- a/builtin-pickaxe.c +++ /dev/null @@ -1,1889 +0,0 @@ -/* - * Pickaxe - * - * Copyright (c) 2006, Junio C Hamano - */ - -#include "cache.h" -#include "builtin.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree-walk.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" -#include "xdiff-interface.h" - -#include -#include -#include - -static char pickaxe_usage[] = -"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [commit] [--] file\n" -" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" -" -l, --long Show long commit SHA1 (Default: off)\n" -" -t, --time Show raw timestamp (Default: off)\n" -" -f, --show-name Show original filename (Default: auto)\n" -" -n, --show-number Show original linenumber (Default: off)\n" -" -p, --porcelain Show in a format designed for machine consumption\n" -" -L n,m Process only line range n,m, counting from 1\n" -" -M, -C Find line movements within and across files\n" -" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; - -static int longest_file; -static int longest_author; -static int max_orig_digits; -static int max_digits; -static int max_score_digits; - -#ifndef DEBUG -#define DEBUG 0 -#endif - -/* stats */ -static int num_read_blob; -static int num_get_patch; -static int num_commits; - -#define PICKAXE_BLAME_MOVE 01 -#define PICKAXE_BLAME_COPY 02 -#define PICKAXE_BLAME_COPY_HARDER 04 - -/* - * blame for a blame_entry with score lower than these thresholds - * is not passed to the parent using move/copy logic. - */ -static unsigned blame_move_score; -static unsigned blame_copy_score; -#define BLAME_DEFAULT_MOVE_SCORE 20 -#define BLAME_DEFAULT_COPY_SCORE 40 - -/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */ -#define METAINFO_SHOWN (1u<<12) -#define MORE_THAN_ONE_PATH (1u<<13) - -/* - * One blob in a commit that is being suspected - */ -struct origin { - int refcnt; - struct commit *commit; - mmfile_t file; - unsigned char blob_sha1[20]; - char path[FLEX_ARRAY]; -}; - -static char *fill_origin_blob(struct origin *o, mmfile_t *file) -{ - if (!o->file.ptr) { - char type[10]; - num_read_blob++; - file->ptr = read_sha1_file(o->blob_sha1, type, - (unsigned long *)(&(file->size))); - o->file = *file; - } - else - *file = o->file; - return file->ptr; -} - -static inline struct origin *origin_incref(struct origin *o) -{ - if (o) - o->refcnt++; - return o; -} - -static void origin_decref(struct origin *o) -{ - if (o && --o->refcnt <= 0) { - if (o->file.ptr) - free(o->file.ptr); - memset(o, 0, sizeof(*o)); - free(o); - } -} - -struct blame_entry { - struct blame_entry *prev; - struct blame_entry *next; - - /* the first line of this group in the final image; - * internally all line numbers are 0 based. - */ - int lno; - - /* how many lines this group has */ - int num_lines; - - /* the commit that introduced this group into the final image */ - struct origin *suspect; - - /* true if the suspect is truly guilty; false while we have not - * checked if the group came from one of its parents. - */ - char guilty; - - /* the line number of the first line of this group in the - * suspect's file; internally all line numbers are 0 based. - */ - int s_lno; - - /* how significant this entry is -- cached to avoid - * scanning the lines over and over - */ - unsigned score; -}; - -struct scoreboard { - /* the final commit (i.e. where we started digging from) */ - struct commit *final; - - const char *path; - - /* the contents in the final; pointed into by buf pointers of - * blame_entries - */ - const char *final_buf; - unsigned long final_buf_size; - - /* linked list of blames */ - struct blame_entry *ent; - - /* look-up a line in the final buffer */ - int num_lines; - int *lineno; -}; - -static int cmp_suspect(struct origin *a, struct origin *b) -{ - int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1); - if (cmp) - return cmp; - return strcmp(a->path, b->path); -} - -#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) ) - -static void sanity_check_refcnt(struct scoreboard *); - -static void coalesce(struct scoreboard *sb) -{ - struct blame_entry *ent, *next; - - for (ent = sb->ent; ent && (next = ent->next); ent = next) { - if (!cmp_suspect(ent->suspect, next->suspect) && - ent->guilty == next->guilty && - ent->s_lno + ent->num_lines == next->s_lno) { - ent->num_lines += next->num_lines; - ent->next = next->next; - if (ent->next) - ent->next->prev = ent; - origin_decref(next->suspect); - free(next); - ent->score = 0; - next = ent; /* again */ - } - } - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); -} - -static struct origin *make_origin(struct commit *commit, const char *path) -{ - struct origin *o; - o = xcalloc(1, sizeof(*o) + strlen(path) + 1); - o->commit = commit; - o->refcnt = 1; - strcpy(o->path, path); - return o; -} - -static struct origin *get_origin(struct scoreboard *sb, - struct commit *commit, - const char *path) -{ - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - if (e->suspect->commit == commit && - !strcmp(e->suspect->path, path)) - return origin_incref(e->suspect); - } - return make_origin(commit, path); -} - -static int fill_blob_sha1(struct origin *origin) -{ - unsigned mode; - char type[10]; - - if (!is_null_sha1(origin->blob_sha1)) - return 0; - if (get_tree_entry(origin->commit->object.sha1, - origin->path, - origin->blob_sha1, &mode)) - goto error_out; - if (sha1_object_info(origin->blob_sha1, type, NULL) || - strcmp(type, blob_type)) - goto error_out; - return 0; - error_out: - hashclr(origin->blob_sha1); - return -1; -} - -static struct origin *find_origin(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin = NULL; - struct diff_options diff_opts; - const char *paths[2]; - - if (parent->util) { - /* This is a freestanding copy of origin and not - * refcounted. - */ - struct origin *cached = parent->util; - if (!strcmp(cached->path, origin->path)) { - porigin = get_origin(sb, parent, cached->path); - if (porigin->refcnt == 1) - hashcpy(porigin->blob_sha1, cached->blob_sha1); - return porigin; - } - /* otherwise it was not very useful; free it */ - free(parent->util); - parent->util = NULL; - } - - /* See if the origin->path is different between parent - * and origin first. Most of the time they are the - * same and diff-tree is fairly efficient about this. - */ - diff_setup(&diff_opts); - diff_opts.recursive = 1; - diff_opts.detect_rename = 0; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - paths[0] = origin->path; - paths[1] = NULL; - - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); - diffcore_std(&diff_opts); - - /* It is either one entry that says "modified", or "created", - * or nothing. - */ - if (!diff_queued_diff.nr) { - /* The path is the same as parent */ - porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, origin->blob_sha1); - } - else if (diff_queued_diff.nr != 1) - die("internal error in pickaxe::find_origin"); - else { - struct diff_filepair *p = diff_queued_diff.queue[0]; - switch (p->status) { - default: - die("internal error in pickaxe::find_origin (%c)", - p->status); - case 'M': - porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, p->one->sha1); - break; - case 'A': - case 'T': - /* Did not exist in parent, or type changed */ - break; - } - } - diff_flush(&diff_opts); - if (porigin) { - struct origin *cached; - cached = make_origin(porigin->commit, porigin->path); - hashcpy(cached->blob_sha1, porigin->blob_sha1); - parent->util = cached; - } - return porigin; -} - -static struct origin *find_rename(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin = NULL; - struct diff_options diff_opts; - int i; - const char *paths[2]; - - diff_setup(&diff_opts); - diff_opts.recursive = 1; - diff_opts.detect_rename = DIFF_DETECT_RENAME; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_opts.single_follow = origin->path; - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); - diffcore_std(&diff_opts); - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - if ((p->status == 'R' || p->status == 'C') && - !strcmp(p->two->path, origin->path)) { - porigin = get_origin(sb, parent, p->one->path); - hashcpy(porigin->blob_sha1, p->one->sha1); - break; - } - } - diff_flush(&diff_opts); - return porigin; -} - -struct chunk { - /* line number in postimage; up to but not including this - * line is the same as preimage - */ - int same; - - /* preimage line number after this chunk */ - int p_next; - - /* postimage line number after this chunk */ - int t_next; -}; - -struct patch { - struct chunk *chunks; - int num; -}; - -struct blame_diff_state { - struct xdiff_emit_state xm; - struct patch *ret; - unsigned hunk_post_context; - unsigned hunk_in_pre_context : 1; -}; - -static void process_u_diff(void *state_, char *line, unsigned long len) -{ - struct blame_diff_state *state = state_; - struct chunk *chunk; - int off1, off2, len1, len2, num; - - num = state->ret->num; - if (len < 4 || line[0] != '@' || line[1] != '@') { - if (state->hunk_in_pre_context && line[0] == ' ') - state->ret->chunks[num - 1].same++; - else { - state->hunk_in_pre_context = 0; - if (line[0] == ' ') - state->hunk_post_context++; - else - state->hunk_post_context = 0; - } - return; - } - - if (num && state->hunk_post_context) { - chunk = &state->ret->chunks[num - 1]; - chunk->p_next -= state->hunk_post_context; - chunk->t_next -= state->hunk_post_context; - } - state->ret->num = ++num; - state->ret->chunks = xrealloc(state->ret->chunks, - sizeof(struct chunk) * num); - chunk = &state->ret->chunks[num - 1]; - if (parse_hunk_header(line, len, &off1, &len1, &off2, &len2)) { - state->ret->num--; - return; - } - - /* Line numbers in patch output are one based. */ - off1--; - off2--; - - chunk->same = len2 ? off2 : (off2 + 1); - - chunk->p_next = off1 + (len1 ? len1 : 1); - chunk->t_next = chunk->same + len2; - state->hunk_in_pre_context = 1; - state->hunk_post_context = 0; -} - -static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o, - int context) -{ - struct blame_diff_state state; - xpparam_t xpp; - xdemitconf_t xecfg; - xdemitcb_t ecb; - - xpp.flags = XDF_NEED_MINIMAL; - xecfg.ctxlen = context; - xecfg.flags = 0; - ecb.outf = xdiff_outf; - ecb.priv = &state; - memset(&state, 0, sizeof(state)); - state.xm.consume = process_u_diff; - state.ret = xmalloc(sizeof(struct patch)); - state.ret->chunks = NULL; - state.ret->num = 0; - - xdl_diff(file_p, file_o, &xpp, &xecfg, &ecb); - - if (state.ret->num) { - struct chunk *chunk; - chunk = &state.ret->chunks[state.ret->num - 1]; - chunk->p_next -= state.hunk_post_context; - chunk->t_next -= state.hunk_post_context; - } - return state.ret; -} - -static struct patch *get_patch(struct origin *parent, struct origin *origin) -{ - mmfile_t file_p, file_o; - struct patch *patch; - - fill_origin_blob(parent, &file_p); - fill_origin_blob(origin, &file_o); - if (!file_p.ptr || !file_o.ptr) - return NULL; - patch = compare_buffer(&file_p, &file_o, 0); - num_get_patch++; - return patch; -} - -static void free_patch(struct patch *p) -{ - free(p->chunks); - free(p); -} - -static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) -{ - struct blame_entry *ent, *prev = NULL; - - origin_incref(e->suspect); - - for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next) - prev = ent; - - /* prev, if not NULL, is the last one that is below e */ - e->prev = prev; - if (prev) { - e->next = prev->next; - prev->next = e; - } - else { - e->next = sb->ent; - sb->ent = e; - } - if (e->next) - e->next->prev = e; -} - -static void dup_entry(struct blame_entry *dst, struct blame_entry *src) -{ - struct blame_entry *p, *n; - - p = dst->prev; - n = dst->next; - origin_incref(src->suspect); - origin_decref(dst->suspect); - memcpy(dst, src, sizeof(*src)); - dst->prev = p; - dst->next = n; - dst->score = 0; -} - -static const char *nth_line(struct scoreboard *sb, int lno) -{ - return sb->final_buf + sb->lineno[lno]; -} - -static void split_overlap(struct blame_entry *split, - struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - /* it is known that lines between tlno to same came from - * parent, and e has an overlap with that range. it also is - * known that parent's line plno corresponds to e's line tlno. - * - * <---- e -----> - * <------> - * <------------> - * <------------> - * <------------------> - * - * Potentially we need to split e into three parts; before - * this chunk, the chunk to be blamed for parent, and after - * that portion. - */ - int chunk_end_lno; - memset(split, 0, sizeof(struct blame_entry [3])); - - if (e->s_lno < tlno) { - /* there is a pre-chunk part not blamed on parent */ - split[0].suspect = origin_incref(e->suspect); - split[0].lno = e->lno; - split[0].s_lno = e->s_lno; - split[0].num_lines = tlno - e->s_lno; - split[1].lno = e->lno + tlno - e->s_lno; - split[1].s_lno = plno; - } - else { - split[1].lno = e->lno; - split[1].s_lno = plno + (e->s_lno - tlno); - } - - if (same < e->s_lno + e->num_lines) { - /* there is a post-chunk part not blamed on parent */ - split[2].suspect = origin_incref(e->suspect); - split[2].lno = e->lno + (same - e->s_lno); - split[2].s_lno = e->s_lno + (same - e->s_lno); - split[2].num_lines = e->s_lno + e->num_lines - same; - chunk_end_lno = split[2].lno; - } - else - chunk_end_lno = e->lno + e->num_lines; - split[1].num_lines = chunk_end_lno - split[1].lno; - - if (split[1].num_lines < 1) - return; - split[1].suspect = origin_incref(parent); -} - -static void split_blame(struct scoreboard *sb, - struct blame_entry *split, - struct blame_entry *e) -{ - struct blame_entry *new_entry; - - if (split[0].suspect && split[2].suspect) { - /* we need to split e into two and add another for parent */ - dup_entry(e, &split[0]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - else if (!split[0].suspect && !split[2].suspect) - /* parent covers the entire area */ - dup_entry(e, &split[1]); - else if (split[0].suspect) { - dup_entry(e, &split[0]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - else { - dup_entry(e, &split[1]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - - if (DEBUG) { /* sanity */ - struct blame_entry *ent; - int lno = sb->ent->lno, corrupt = 0; - - for (ent = sb->ent; ent; ent = ent->next) { - if (lno != ent->lno) - corrupt = 1; - if (ent->s_lno < 0) - corrupt = 1; - lno += ent->num_lines; - } - if (corrupt) { - lno = sb->ent->lno; - for (ent = sb->ent; ent; ent = ent->next) { - printf("L %8d l %8d n %8d\n", - lno, ent->lno, ent->num_lines); - lno = ent->lno + ent->num_lines; - } - die("oops"); - } - } -} - -static void decref_split(struct blame_entry *split) -{ - int i; - - for (i = 0; i < 3; i++) - origin_decref(split[i].suspect); -} - -static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - struct blame_entry split[3]; - - split_overlap(split, e, tlno, plno, same, parent); - if (split[1].suspect) - split_blame(sb, split, e); - decref_split(split); -} - -static int find_last_in_target(struct scoreboard *sb, struct origin *target) -{ - struct blame_entry *e; - int last_in_target = -1; - - for (e = sb->ent; e; e = e->next) { - if (e->guilty || cmp_suspect(e->suspect, target)) - continue; - if (last_in_target < e->s_lno + e->num_lines) - last_in_target = e->s_lno + e->num_lines; - } - return last_in_target; -} - -static void blame_chunk(struct scoreboard *sb, - int tlno, int plno, int same, - struct origin *target, struct origin *parent) -{ - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - if (e->guilty || cmp_suspect(e->suspect, target)) - continue; - if (same <= e->s_lno) - continue; - if (tlno < e->s_lno + e->num_lines) - blame_overlap(sb, e, tlno, plno, same, parent); - } -} - -static int pass_blame_to_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) -{ - int i, last_in_target, plno, tlno; - struct patch *patch; - - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ - - patch = get_patch(parent, target); - plno = tlno = 0; - for (i = 0; i < patch->num; i++) { - struct chunk *chunk = &patch->chunks[i]; - - blame_chunk(sb, tlno, plno, chunk->same, target, parent); - plno = chunk->p_next; - tlno = chunk->t_next; - } - /* rest (i.e. anything above tlno) are the same as parent */ - blame_chunk(sb, tlno, plno, last_in_target, target, parent); - - free_patch(patch); - return 0; -} - -static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) -{ - unsigned score; - const char *cp, *ep; - - if (e->score) - return e->score; - - score = 1; - cp = nth_line(sb, e->lno); - ep = nth_line(sb, e->lno + e->num_lines); - while (cp < ep) { - unsigned ch = *((unsigned char *)cp); - if (isalnum(ch)) - score++; - cp++; - } - e->score = score; - return score; -} - -static void copy_split_if_better(struct scoreboard *sb, - struct blame_entry *best_so_far, - struct blame_entry *this) -{ - int i; - - if (!this[1].suspect) - return; - if (best_so_far[1].suspect) { - if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1])) - return; - } - - for (i = 0; i < 3; i++) - origin_incref(this[i].suspect); - decref_split(best_so_far); - memcpy(best_so_far, this, sizeof(struct blame_entry [3])); -} - -static void find_copy_in_blob(struct scoreboard *sb, - struct blame_entry *ent, - struct origin *parent, - struct blame_entry *split, - mmfile_t *file_p) -{ - const char *cp; - int cnt; - mmfile_t file_o; - struct patch *patch; - int i, plno, tlno; - - cp = nth_line(sb, ent->lno); - file_o.ptr = (char*) cp; - cnt = ent->num_lines; - - while (cnt && cp < sb->final_buf + sb->final_buf_size) { - if (*cp++ == '\n') - cnt--; - } - file_o.size = cp - file_o.ptr; - - patch = compare_buffer(file_p, &file_o, 1); - - memset(split, 0, sizeof(struct blame_entry [3])); - plno = tlno = 0; - for (i = 0; i < patch->num; i++) { - struct chunk *chunk = &patch->chunks[i]; - - /* tlno to chunk->same are the same as ent */ - if (ent->num_lines <= tlno) - break; - if (tlno < chunk->same) { - struct blame_entry this[3]; - split_overlap(this, ent, - tlno + ent->s_lno, plno, - chunk->same + ent->s_lno, - parent); - copy_split_if_better(sb, split, this); - decref_split(this); - } - plno = chunk->p_next; - tlno = chunk->t_next; - } - free_patch(patch); -} - -static int find_move_in_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) -{ - int last_in_target, made_progress; - struct blame_entry *e, split[3]; - mmfile_t file_p; - - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ - - fill_origin_blob(parent, &file_p); - if (!file_p.ptr) - return 0; - - made_progress = 1; - while (made_progress) { - made_progress = 0; - for (e = sb->ent; e; e = e->next) { - if (e->guilty || cmp_suspect(e->suspect, target)) - continue; - find_copy_in_blob(sb, e, parent, split, &file_p); - if (split[1].suspect && - blame_move_score < ent_score(sb, &split[1])) { - split_blame(sb, split, e); - made_progress = 1; - } - decref_split(split); - } - } - return 0; -} - - -struct blame_list { - struct blame_entry *ent; - struct blame_entry split[3]; -}; - -static struct blame_list *setup_blame_list(struct scoreboard *sb, - struct origin *target, - int *num_ents_p) -{ - struct blame_entry *e; - int num_ents, i; - struct blame_list *blame_list = NULL; - - /* Count the number of entries the target is suspected for, - * and prepare a list of entry and the best split. - */ - for (e = sb->ent, num_ents = 0; e; e = e->next) - if (!e->guilty && !cmp_suspect(e->suspect, target)) - num_ents++; - if (num_ents) { - blame_list = xcalloc(num_ents, sizeof(struct blame_list)); - for (e = sb->ent, i = 0; e; e = e->next) - if (!e->guilty && !cmp_suspect(e->suspect, target)) - blame_list[i++].ent = e; - } - *num_ents_p = num_ents; - return blame_list; -} - -static int find_copy_in_parent(struct scoreboard *sb, - struct origin *target, - struct commit *parent, - struct origin *porigin, - int opt) -{ - struct diff_options diff_opts; - const char *paths[1]; - int i, j; - int retval; - struct blame_list *blame_list; - int num_ents; - - blame_list = setup_blame_list(sb, target, &num_ents); - if (!blame_list) - return 1; /* nothing remains for this target */ - - diff_setup(&diff_opts); - diff_opts.recursive = 1; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - - /* Try "find copies harder" on new path if requested; - * we do not want to use diffcore_rename() actually to - * match things up; find_copies_harder is set only to - * force diff_tree_sha1() to feed all filepairs to diff_queue, - * and this code needs to be after diff_setup_done(), which - * usually makes find-copies-harder imply copy detection. - */ - if ((opt & PICKAXE_BLAME_COPY_HARDER) && - (!porigin || strcmp(target->path, porigin->path))) - diff_opts.find_copies_harder = 1; - - diff_tree_sha1(parent->tree->object.sha1, - target->commit->tree->object.sha1, - "", &diff_opts); - - if (!diff_opts.find_copies_harder) - diffcore_std(&diff_opts); - - retval = 0; - while (1) { - int made_progress = 0; - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - struct origin *norigin; - mmfile_t file_p; - struct blame_entry this[3]; - - if (!DIFF_FILE_VALID(p->one)) - continue; /* does not exist in parent */ - if (porigin && !strcmp(p->one->path, porigin->path)) - /* find_move already dealt with this path */ - continue; - - norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->sha1); - fill_origin_blob(norigin, &file_p); - if (!file_p.ptr) - continue; - - for (j = 0; j < num_ents; j++) { - find_copy_in_blob(sb, blame_list[j].ent, - norigin, this, &file_p); - copy_split_if_better(sb, blame_list[j].split, - this); - decref_split(this); - } - origin_decref(norigin); - } - - for (j = 0; j < num_ents; j++) { - struct blame_entry *split = blame_list[j].split; - if (split[1].suspect && - blame_copy_score < ent_score(sb, &split[1])) { - split_blame(sb, split, blame_list[j].ent); - made_progress = 1; - } - decref_split(split); - } - free(blame_list); - - if (!made_progress) - break; - blame_list = setup_blame_list(sb, target, &num_ents); - if (!blame_list) { - retval = 1; - break; - } - } - diff_flush(&diff_opts); - - return retval; -} - -/* The blobs of origin and porigin exactly match, so everything - * origin is suspected for can be blamed on the parent. - */ -static void pass_whole_blame(struct scoreboard *sb, - struct origin *origin, struct origin *porigin) -{ - struct blame_entry *e; - - if (!porigin->file.ptr && origin->file.ptr) { - /* Steal its file */ - porigin->file = origin->file; - origin->file.ptr = NULL; - } - for (e = sb->ent; e; e = e->next) { - if (cmp_suspect(e->suspect, origin)) - continue; - origin_incref(porigin); - origin_decref(e->suspect); - e->suspect = porigin; - } -} - -#define MAXPARENT 16 - -static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) -{ - int i, pass; - struct commit *commit = origin->commit; - struct commit_list *parent; - struct origin *parent_origin[MAXPARENT], *porigin; - - memset(parent_origin, 0, sizeof(parent_origin)); - - /* The first pass looks for unrenamed path to optimize for - * common cases, then we look for renames in the second pass. - */ - for (pass = 0; pass < 2; pass++) { - struct origin *(*find)(struct scoreboard *, - struct commit *, struct origin *); - find = pass ? find_rename : find_origin; - - for (i = 0, parent = commit->parents; - i < MAXPARENT && parent; - parent = parent->next, i++) { - struct commit *p = parent->item; - int j, same; - - if (parent_origin[i]) - continue; - if (parse_commit(p)) - continue; - porigin = find(sb, p, origin); - if (!porigin) - continue; - if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { - pass_whole_blame(sb, origin, porigin); - origin_decref(porigin); - goto finish; - } - for (j = same = 0; j < i; j++) - if (parent_origin[j] && - !hashcmp(parent_origin[j]->blob_sha1, - porigin->blob_sha1)) { - same = 1; - break; - } - if (!same) - parent_origin[i] = porigin; - else - origin_decref(porigin); - } - } - - num_commits++; - for (i = 0, parent = commit->parents; - i < MAXPARENT && parent; - parent = parent->next, i++) { - struct origin *porigin = parent_origin[i]; - if (!porigin) - continue; - if (pass_blame_to_parent(sb, origin, porigin)) - goto finish; - } - - /* - * Optionally run "miff" to find moves in parents' files here. - */ - if (opt & PICKAXE_BLAME_MOVE) - for (i = 0, parent = commit->parents; - i < MAXPARENT && parent; - parent = parent->next, i++) { - struct origin *porigin = parent_origin[i]; - if (!porigin) - continue; - if (find_move_in_parent(sb, origin, porigin)) - goto finish; - } - - /* - * Optionally run "ciff" to find copies from parents' files here. - */ - if (opt & PICKAXE_BLAME_COPY) - for (i = 0, parent = commit->parents; - i < MAXPARENT && parent; - parent = parent->next, i++) { - struct origin *porigin = parent_origin[i]; - if (find_copy_in_parent(sb, origin, parent->item, - porigin, opt)) - goto finish; - } - - finish: - for (i = 0; i < MAXPARENT; i++) - origin_decref(parent_origin[i]); -} - -static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) -{ - while (1) { - struct blame_entry *ent; - struct commit *commit; - struct origin *suspect = NULL; - - /* find one suspect to break down */ - for (ent = sb->ent; !suspect && ent; ent = ent->next) - if (!ent->guilty) - suspect = ent->suspect; - if (!suspect) - return; /* all done */ - - origin_incref(suspect); - commit = suspect->commit; - if (!commit->object.parsed) - parse_commit(commit); - if (!(commit->object.flags & UNINTERESTING) && - !(revs->max_age != -1 && commit->date < revs->max_age)) - pass_blame(sb, suspect, opt); - - /* Take responsibility for the remaining entries */ - for (ent = sb->ent; ent; ent = ent->next) - if (!cmp_suspect(ent->suspect, suspect)) - ent->guilty = 1; - origin_decref(suspect); - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); - } -} - -static const char *format_time(unsigned long time, const char *tz_str, - int show_raw_time) -{ - static char time_buf[128]; - time_t t = time; - int minutes, tz; - struct tm *tm; - - if (show_raw_time) { - sprintf(time_buf, "%lu %s", time, tz_str); - return time_buf; - } - - tz = atoi(tz_str); - minutes = tz < 0 ? -tz : tz; - minutes = (minutes / 100)*60 + (minutes % 100); - minutes = tz < 0 ? -minutes : minutes; - t = time + minutes * 60; - tm = gmtime(&t); - - strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); - strcat(time_buf, tz_str); - return time_buf; -} - -struct commit_info -{ - char *author; - char *author_mail; - unsigned long author_time; - char *author_tz; - - /* filled only when asked for details */ - char *committer; - char *committer_mail; - unsigned long committer_time; - char *committer_tz; - - char *summary; -}; - -static void get_ac_line(const char *inbuf, const char *what, - int bufsz, char *person, char **mail, - unsigned long *time, char **tz) -{ - int len; - char *tmp, *endp; - - tmp = strstr(inbuf, what); - if (!tmp) - goto error_out; - tmp += strlen(what); - endp = strchr(tmp, '\n'); - if (!endp) - len = strlen(tmp); - else - len = endp - tmp; - if (bufsz <= len) { - error_out: - /* Ugh */ - person = *mail = *tz = "(unknown)"; - *time = 0; - return; - } - memcpy(person, tmp, len); - - tmp = person; - tmp += len; - *tmp = 0; - while (*tmp != ' ') - tmp--; - *tz = tmp+1; - - *tmp = 0; - while (*tmp != ' ') - tmp--; - *time = strtoul(tmp, NULL, 10); - - *tmp = 0; - while (*tmp != ' ') - tmp--; - *mail = tmp + 1; - *tmp = 0; -} - -static void get_commit_info(struct commit *commit, - struct commit_info *ret, - int detailed) -{ - int len; - char *tmp, *endp; - static char author_buf[1024]; - static char committer_buf[1024]; - static char summary_buf[1024]; - - /* We've operated without save_commit_buffer, so - * we now need to populate them for output. - */ - if (!commit->buffer) { - char type[20]; - unsigned long size; - commit->buffer = - read_sha1_file(commit->object.sha1, type, &size); - } - ret->author = author_buf; - get_ac_line(commit->buffer, "\nauthor ", - sizeof(author_buf), author_buf, &ret->author_mail, - &ret->author_time, &ret->author_tz); - - if (!detailed) - return; - - ret->committer = committer_buf; - get_ac_line(commit->buffer, "\ncommitter ", - sizeof(committer_buf), committer_buf, &ret->committer_mail, - &ret->committer_time, &ret->committer_tz); - - ret->summary = summary_buf; - tmp = strstr(commit->buffer, "\n\n"); - if (!tmp) { - error_out: - sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); - return; - } - tmp += 2; - endp = strchr(tmp, '\n'); - if (!endp) - goto error_out; - len = endp - tmp; - if (len >= sizeof(summary_buf)) - goto error_out; - memcpy(summary_buf, tmp, len); - summary_buf[len] = 0; -} - -#define OUTPUT_ANNOTATE_COMPAT 001 -#define OUTPUT_LONG_OBJECT_NAME 002 -#define OUTPUT_RAW_TIMESTAMP 004 -#define OUTPUT_PORCELAIN 010 -#define OUTPUT_SHOW_NAME 020 -#define OUTPUT_SHOW_NUMBER 040 -#define OUTPUT_SHOW_SCORE 0100 - -static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) -{ - int cnt; - const char *cp; - struct origin *suspect = ent->suspect; - char hex[41]; - - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); - printf("%s%c%d %d %d\n", - hex, - ent->guilty ? ' ' : '*', // purely for debugging - ent->s_lno + 1, - ent->lno + 1, - ent->num_lines); - if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - struct commit_info ci; - suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); - printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); - printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - printf("filename %s\n", suspect->path); - printf("summary %s\n", ci.summary); - } - else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH) - printf("filename %s\n", suspect->path); - - cp = nth_line(sb, ent->lno); - for (cnt = 0; cnt < ent->num_lines; cnt++) { - char ch; - if (cnt) - printf("%s %d %d\n", hex, - ent->s_lno + 1 + cnt, - ent->lno + 1 + cnt); - putchar('\t'); - do { - ch = *cp++; - putchar(ch); - } while (ch != '\n' && - cp < sb->final_buf + sb->final_buf_size); - } -} - -static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) -{ - int cnt; - const char *cp; - struct origin *suspect = ent->suspect; - struct commit_info ci; - char hex[41]; - int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); - - get_commit_info(suspect->commit, &ci, 1); - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); - - cp = nth_line(sb, ent->lno); - for (cnt = 0; cnt < ent->num_lines; cnt++) { - char ch; - - printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex); - if (opt & OUTPUT_ANNOTATE_COMPAT) - printf("\t(%10s\t%10s\t%d)", ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - ent->lno + 1 + cnt); - else { - if (opt & OUTPUT_SHOW_SCORE) - printf(" %*d %02d", - max_score_digits, ent->score, - ent->suspect->refcnt); - if (opt & OUTPUT_SHOW_NAME) - printf(" %-*.*s", longest_file, longest_file, - suspect->path); - if (opt & OUTPUT_SHOW_NUMBER) - printf(" %*d", max_orig_digits, - ent->s_lno + 1 + cnt); - printf(" (%-*.*s %10s %*d) ", - longest_author, longest_author, ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - max_digits, ent->lno + 1 + cnt); - } - do { - ch = *cp++; - putchar(ch); - } while (ch != '\n' && - cp < sb->final_buf + sb->final_buf_size); - } -} - -static void output(struct scoreboard *sb, int option) -{ - struct blame_entry *ent; - - if (option & OUTPUT_PORCELAIN) { - for (ent = sb->ent; ent; ent = ent->next) { - struct blame_entry *oth; - struct origin *suspect = ent->suspect; - struct commit *commit = suspect->commit; - if (commit->object.flags & MORE_THAN_ONE_PATH) - continue; - for (oth = ent->next; oth; oth = oth->next) { - if ((oth->suspect->commit != commit) || - !strcmp(oth->suspect->path, suspect->path)) - continue; - commit->object.flags |= MORE_THAN_ONE_PATH; - break; - } - } - } - - for (ent = sb->ent; ent; ent = ent->next) { - if (option & OUTPUT_PORCELAIN) - emit_porcelain(sb, ent); - else { - emit_other(sb, ent, option); - } - } -} - -static int prepare_lines(struct scoreboard *sb) -{ - const char *buf = sb->final_buf; - unsigned long len = sb->final_buf_size; - int num = 0, incomplete = 0, bol = 1; - - if (len && buf[len-1] != '\n') - incomplete++; /* incomplete line at the end */ - while (len--) { - if (bol) { - sb->lineno = xrealloc(sb->lineno, - sizeof(int* ) * (num + 1)); - sb->lineno[num] = buf - sb->final_buf; - bol = 0; - } - if (*buf++ == '\n') { - num++; - bol = 1; - } - } - sb->lineno = xrealloc(sb->lineno, - sizeof(int* ) * (num + incomplete + 1)); - sb->lineno[num + incomplete] = buf - sb->final_buf; - sb->num_lines = num + incomplete; - return sb->num_lines; -} - -static int read_ancestry(const char *graft_file) -{ - FILE *fp = fopen(graft_file, "r"); - char buf[1024]; - if (!fp) - return -1; - while (fgets(buf, sizeof(buf), fp)) { - /* The format is just "Commit Parent1 Parent2 ...\n" */ - int len = strlen(buf); - struct commit_graft *graft = read_graft_line(buf, len); - register_commit_graft(graft, 0); - } - fclose(fp); - return 0; -} - -static int lineno_width(int lines) -{ - int i, width; - - for (width = 1, i = 10; i <= lines + 1; width++) - i *= 10; - return width; -} - -static void find_alignment(struct scoreboard *sb, int *option) -{ - int longest_src_lines = 0; - int longest_dst_lines = 0; - unsigned largest_score = 0; - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - struct origin *suspect = e->suspect; - struct commit_info ci; - int num; - - if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); - if (strcmp(suspect->path, sb->path)) - *option |= OUTPUT_SHOW_NAME; - num = strlen(suspect->path); - if (longest_file < num) - longest_file = num; - num = strlen(ci.author); - if (longest_author < num) - longest_author = num; - } - num = e->s_lno + e->num_lines; - if (longest_src_lines < num) - longest_src_lines = num; - num = e->lno + e->num_lines; - if (longest_dst_lines < num) - longest_dst_lines = num; - if (largest_score < ent_score(sb, e)) - largest_score = ent_score(sb, e); - } - max_orig_digits = lineno_width(longest_src_lines); - max_digits = lineno_width(longest_dst_lines); - max_score_digits = lineno_width(largest_score); -} - -static void sanity_check_refcnt(struct scoreboard *sb) -{ - int baa = 0; - struct blame_entry *ent; - - for (ent = sb->ent; ent; ent = ent->next) { - /* Nobody should have zero or negative refcnt */ - if (ent->suspect->refcnt <= 0) { - fprintf(stderr, "%s in %s has negative refcnt %d\n", - ent->suspect->path, - sha1_to_hex(ent->suspect->commit->object.sha1), - ent->suspect->refcnt); - baa = 1; - } - } - for (ent = sb->ent; ent; ent = ent->next) { - /* Mark the ones that haven't been checked */ - if (0 < ent->suspect->refcnt) - ent->suspect->refcnt = -ent->suspect->refcnt; - } - for (ent = sb->ent; ent; ent = ent->next) { - /* then pick each and see if they have the the correct - * refcnt. - */ - int found; - struct blame_entry *e; - struct origin *suspect = ent->suspect; - - if (0 < suspect->refcnt) - continue; - suspect->refcnt = -suspect->refcnt; /* Unmark */ - for (found = 0, e = sb->ent; e; e = e->next) { - if (e->suspect != suspect) - continue; - found++; - } - if (suspect->refcnt != found) { - fprintf(stderr, "%s in %s has refcnt %d, not %d\n", - ent->suspect->path, - sha1_to_hex(ent->suspect->commit->object.sha1), - ent->suspect->refcnt, found); - baa = 2; - } - } - if (baa) { - int opt = 0160; - find_alignment(sb, &opt); - output(sb, opt); - die("Baa %d!", baa); - } -} - -static int has_path_in_work_tree(const char *path) -{ - struct stat st; - return !lstat(path, &st); -} - -static unsigned parse_score(const char *arg) -{ - char *end; - unsigned long score = strtoul(arg, &end, 10); - if (*end) - return 0; - return score; -} - -static const char *add_prefix(const char *prefix, const char *path) -{ - if (!prefix || !prefix[0]) - return path; - return prefix_path(prefix, strlen(prefix), path); -} - -static const char *parse_loc(const char *spec, - struct scoreboard *sb, long lno, - long begin, long *ret) -{ - char *term; - const char *line; - long num; - int reg_error; - regex_t regexp; - regmatch_t match[1]; - - /* Allow "-L ,+20" to mean starting at - * for 20 lines, or "-L ,-5" for 5 lines ending at - * . - */ - if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { - num = strtol(spec + 1, &term, 10); - if (term != spec + 1) { - if (spec[0] == '-') - num = 0 - num; - if (0 < num) - *ret = begin + num - 2; - else if (!num) - *ret = begin; - else - *ret = begin + num; - return term; - } - return spec; - } - num = strtol(spec, &term, 10); - if (term != spec) { - *ret = num; - return term; - } - if (spec[0] != '/') - return spec; - - /* it could be a regexp of form /.../ */ - for (term = (char*) spec + 1; *term && *term != '/'; term++) { - if (*term == '\\') - term++; - } - if (*term != '/') - return spec; - - /* try [spec+1 .. term-1] as regexp */ - *term = 0; - begin--; /* input is in human terms */ - line = nth_line(sb, begin); - - if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && - !(reg_error = regexec(®exp, line, 1, match, 0))) { - const char *cp = line + match[0].rm_so; - const char *nline; - - while (begin++ < lno) { - nline = nth_line(sb, begin); - if (line <= cp && cp < nline) - break; - line = nline; - } - *ret = begin; - regfree(®exp); - *term++ = '/'; - return term; - } - else { - char errbuf[1024]; - regerror(reg_error, ®exp, errbuf, 1024); - die("-L parameter '%s': %s", spec + 1, errbuf); - } -} - -static void prepare_blame_range(struct scoreboard *sb, - const char *bottomtop, - long lno, - long *bottom, long *top) -{ - const char *term; - - term = parse_loc(bottomtop, sb, lno, 1, bottom); - if (*term == ',') { - term = parse_loc(term + 1, sb, lno, *bottom + 1, top); - if (*term) - usage(pickaxe_usage); - } - if (*term) - usage(pickaxe_usage); -} - -int cmd_pickaxe(int argc, const char **argv, const char *prefix) -{ - struct rev_info revs; - const char *path; - struct scoreboard sb; - struct origin *o; - struct blame_entry *ent; - int i, seen_dashdash, unk, opt; - long bottom, top, lno; - int output_option = 0; - const char *revs_file = NULL; - const char *final_commit_name = NULL; - char type[10]; - const char *bottomtop = NULL; - - save_commit_buffer = 0; - - opt = 0; - seen_dashdash = 0; - for (unk = i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (*arg != '-') - break; - else if (!strcmp("-c", arg)) - output_option |= OUTPUT_ANNOTATE_COMPAT; - else if (!strcmp("-t", arg)) - output_option |= OUTPUT_RAW_TIMESTAMP; - else if (!strcmp("-l", arg)) - output_option |= OUTPUT_LONG_OBJECT_NAME; - else if (!strcmp("-S", arg) && ++i < argc) - revs_file = argv[i]; - else if (!strncmp("-M", arg, 2)) { - opt |= PICKAXE_BLAME_MOVE; - blame_move_score = parse_score(arg+2); - } - else if (!strncmp("-C", arg, 2)) { - if (opt & PICKAXE_BLAME_COPY) - opt |= PICKAXE_BLAME_COPY_HARDER; - opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; - blame_copy_score = parse_score(arg+2); - } - else if (!strncmp("-L", arg, 2)) { - if (!arg[2]) { - if (++i >= argc) - usage(pickaxe_usage); - arg = argv[i]; - } - else - arg += 2; - if (bottomtop) - die("More than one '-L n,m' option given"); - bottomtop = arg; - } - else if (!strcmp("--score-debug", arg)) - output_option |= OUTPUT_SHOW_SCORE; - else if (!strcmp("-f", arg) || - !strcmp("--show-name", arg)) - output_option |= OUTPUT_SHOW_NAME; - else if (!strcmp("-n", arg) || - !strcmp("--show-number", arg)) - output_option |= OUTPUT_SHOW_NUMBER; - else if (!strcmp("-p", arg) || - !strcmp("--porcelain", arg)) - output_option |= OUTPUT_PORCELAIN; - else if (!strcmp("--", arg)) { - seen_dashdash = 1; - i++; - break; - } - else - argv[unk++] = arg; - } - - if (!blame_move_score) - blame_move_score = BLAME_DEFAULT_MOVE_SCORE; - if (!blame_copy_score) - blame_copy_score = BLAME_DEFAULT_COPY_SCORE; - - /* We have collected options unknown to us in argv[1..unk] - * which are to be passed to revision machinery if we are - * going to do the "bottom" procesing. - * - * The remaining are: - * - * (1) if seen_dashdash, its either - * "-options -- " or - * "-options -- ". - * but the latter is allowed only if there is no - * options that we passed to revision machinery. - * - * (2) otherwise, we may have "--" somewhere later and - * might be looking at the first one of multiple 'rev' - * parameters (e.g. " master ^next ^maint -- path"). - * See if there is a dashdash first, and give the - * arguments before that to revision machinery. - * After that there must be one 'path'. - * - * (3) otherwise, its one of the three: - * "-options " - * "-options " - * "-options " - * but again the first one is allowed only if - * there is no options that we passed to revision - * machinery. - */ - - if (seen_dashdash) { - /* (1) */ - if (argc <= i) - usage(pickaxe_usage); - path = add_prefix(prefix, argv[i]); - if (i + 1 == argc - 1) { - if (unk != 1) - usage(pickaxe_usage); - argv[unk++] = argv[i + 1]; - } - else if (i + 1 != argc) - /* garbage at end */ - usage(pickaxe_usage); - } - else { - int j; - for (j = i; !seen_dashdash && j < argc; j++) - if (!strcmp(argv[j], "--")) - seen_dashdash = j; - if (seen_dashdash) { - if (seen_dashdash + 1 != argc - 1) - usage(pickaxe_usage); - path = add_prefix(prefix, argv[seen_dashdash + 1]); - for (j = i; j < seen_dashdash; j++) - argv[unk++] = argv[j]; - } - else { - /* (3) */ - path = add_prefix(prefix, argv[i]); - if (i + 1 == argc - 1) { - final_commit_name = argv[i + 1]; - - /* if (unk == 1) we could be getting - * old-style - */ - if (unk == 1 && !has_path_in_work_tree(path)) { - path = add_prefix(prefix, argv[i + 1]); - final_commit_name = argv[i]; - } - } - else if (i != argc - 1) - usage(pickaxe_usage); /* garbage at end */ - - if (!has_path_in_work_tree(path)) - die("cannot stat path %s: %s", - path, strerror(errno)); - } - } - - if (final_commit_name) - argv[unk++] = final_commit_name; - - /* Now we got rev and path. We do not want the path pruning - * but we may want "bottom" processing. - */ - argv[unk] = NULL; - - init_revisions(&revs, NULL); - setup_revisions(unk, argv, &revs, "HEAD"); - memset(&sb, 0, sizeof(sb)); - - /* There must be one and only one positive commit in the - * revs->pending array. - */ - for (i = 0; i < revs.pending.nr; i++) { - struct object *obj = revs.pending.objects[i].item; - if (obj->flags & UNINTERESTING) - continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", - revs.pending.objects[i].name); - if (sb.final) - die("More than one commit to dig from %s and %s?", - revs.pending.objects[i].name, - final_commit_name); - sb.final = (struct commit *) obj; - final_commit_name = revs.pending.objects[i].name; - } - - if (!sb.final) { - /* "--not A B -- path" without anything positive */ - unsigned char head_sha1[20]; - - final_commit_name = "HEAD"; - if (get_sha1(final_commit_name, head_sha1)) - die("No such ref: HEAD"); - sb.final = lookup_commit_reference(head_sha1); - add_pending_object(&revs, &(sb.final->object), "HEAD"); - } - - /* If we have bottom, this will mark the ancestors of the - * bottom commits we would reach while traversing as - * uninteresting. - */ - prepare_revision_walk(&revs); - - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) - die("no such path %s in %s", path, final_commit_name); - - sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); - num_read_blob++; - lno = prepare_lines(&sb); - - bottom = top = 0; - if (bottomtop) - prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); - if (bottom && top && top < bottom) { - long tmp; - tmp = top; top = bottom; bottom = tmp; - } - if (bottom < 1) - bottom = 1; - if (top < 1) - top = lno; - bottom--; - if (lno < top) - die("file %s has only %lu lines", path, lno); - - ent = xcalloc(1, sizeof(*ent)); - ent->lno = bottom; - ent->num_lines = top - bottom; - ent->suspect = o; - ent->s_lno = bottom; - - sb.ent = ent; - sb.path = path; - - if (revs_file && read_ancestry(revs_file)) - die("reading graft file %s failed: %s", - revs_file, strerror(errno)); - - assign_blame(&sb, &revs, opt); - - coalesce(&sb); - - if (!(output_option & OUTPUT_PORCELAIN)) - find_alignment(&sb, &output_option); - - output(&sb, output_option); - free((void *)sb.final_buf); - for (ent = sb.ent; ent; ) { - struct blame_entry *e = ent->next; - free(ent); - ent = e; - } - - if (DEBUG) { - printf("num read blob: %d\n", num_read_blob); - printf("num get patch: %d\n", num_get_patch); - printf("num commits: %d\n", num_commits); - } - return 0; -} diff --git a/builtin.h b/builtin.h index 24803df..43fed32 100644 --- a/builtin.h +++ b/builtin.h @@ -17,6 +17,7 @@ extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); extern int cmd_archive(int argc, const char **argv, const char *prefix); +extern int cmd_blame(int argc, const char **argv, const char *prefix); extern int cmd_branch(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 9a9addf..1aa07a5 100644 --- a/git.c +++ b/git.c @@ -222,6 +222,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "annotate", cmd_annotate, }, { "apply", cmd_apply }, { "archive", cmd_archive }, + { "blame", cmd_blame, RUN_SETUP | USE_PAGER }, { "branch", cmd_branch, RUN_SETUP }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, @@ -249,7 +250,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "mv", cmd_mv, RUN_SETUP }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, - { "pickaxe", cmd_pickaxe, RUN_SETUP | USE_PAGER }, + { "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER }, { "prune", cmd_prune, RUN_SETUP }, { "prune-packed", cmd_prune_packed, RUN_SETUP }, { "push", cmd_push, RUN_SETUP }, diff --git a/t/t8003-pickaxe.sh b/t/t8003-pickaxe.sh deleted file mode 100755 index d09d1c9..0000000 --- a/t/t8003-pickaxe.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -test_description='git-pickaxe' -. ./test-lib.sh - -PROG='git pickaxe -c' -. ../annotate-tests.sh - -test_done -- cgit v0.10.2-6-g49f6 From 25ffbb27a20278e9884e8f036b39806bb11ec1a8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 8 Nov 2006 15:11:10 -0800 Subject: gitweb: protect blob and diff output lines from controls. This revealed that the output from blame and tag was not chomped properly and was relying on HTML output not noticing that extra whitespace that resulted from the newline, which was also fixed. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 634975b..f4d1ef0 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -576,11 +576,10 @@ sub esc_html ($;%) { $str = to_utf8($str); $str = escapeHTML($str); - $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) - $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) if ($opts{'-nbsp'}) { $str =~ s/ / /g; } + $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg; return $str; } @@ -1879,17 +1878,17 @@ sub git_print_page_path { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => $fullname}, esc_path($dir)); + -title => esc_html($fullname)}, esc_path($dir)); print " / "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_path($basename)); + -title => esc_html($name)}, esc_path($basename)); } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_path($basename)); + -title => esc_html($name)}, esc_path($basename)); print " / "; } else { print esc_path($basename); @@ -2851,6 +2850,7 @@ sub git_tag { print "
"; my $comment = $tag{'comment'}; foreach my $line (@$comment) { + chomp($line); print esc_html($line) . "
\n"; } print "
\n"; @@ -2920,6 +2920,7 @@ HTML } } my $data = $_; + chomp($data); my $rev = substr($full_rev, 0, 8); my $author = $meta->{'author'}; my %date = parse_date($meta->{'author-time'}, -- cgit v0.10.2-6-g49f6 From 225932ed4daa84f862a739ba4ea01a0bba2dfe45 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 9 Nov 2006 00:57:13 -0800 Subject: gitweb: protect commit messages from controls. The same change as the previous. It is rather sad that commit log message parser gives list of chomped lines while tag message parser gives unchomped ones. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index f4d1ef0..1a757cc 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -814,12 +814,11 @@ sub file_type_long { ## functions returning short HTML fragments, or transforming HTML fragments ## which don't beling to other sections -# format line of commit message or tag comment +# format line of commit message. sub format_log_line_html { my $line = shift; - $line = esc_html($line); - $line =~ s/ / /g; + $line = esc_html($line, -nbsp=>1); if ($line =~ m/([0-9a-fA-F]{40})/) { my $hash_text = $1; if (git_get_type($hash_text) eq "commit") { -- cgit v0.10.2-6-g49f6 From 1c791cfbf843fee5c72b5b23c0c3ca8550e15c08 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 9 Nov 2006 02:33:35 -0800 Subject: gitweb: fix unmatched div in commitdiff When the last filepair changed only metainfo we failed to close the extended header
. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1a757cc..e54a29e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2338,6 +2338,8 @@ sub git_patchset_body { print format_diff_line($patch_line); } + print "
\n" if $in_header; # extended header + print "
\n" if $patch_found; # class="patch" print "
\n"; # class="patchset" -- cgit v0.10.2-6-g49f6 From 916d081bbaa40617643b09b6dc9c6760993cf6ed Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 9 Nov 2006 13:52:05 +0100 Subject: Nicer error messages in case saving an object to db goes wrong Currently the error e.g. when pushing to a read-only repository is quite confusing, this attempts to clean it up, unifies error reporting between various object writers and uses error() on couple more places. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 27eb14b..c9fdaa3 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1420,8 +1420,7 @@ int move_temp_to_file(const char *tmpfile, const char *filename) unlink(tmpfile); if (ret) { if (ret != EEXIST) { - fprintf(stderr, "unable to write sha1 filename %s: %s\n", filename, strerror(ret)); - return -1; + return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret)); } /* FIXME!!! Collision check here ? */ } @@ -1531,16 +1530,17 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha } if (errno != ENOENT) { - fprintf(stderr, "sha1 file %s: %s\n", filename, strerror(errno)); - return -1; + return error("sha1 file %s: %s\n", filename, strerror(errno)); } snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory()); fd = mkstemp(tmpfile); if (fd < 0) { - fprintf(stderr, "unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno)); - return -1; + if (errno == EPERM) + return error("insufficient permission for adding an object to repository database %s\n", get_object_directory()); + else + return error("unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno)); } /* Set it up */ @@ -1655,9 +1655,12 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory()); local = mkstemp(tmpfile); - if (local < 0) - return error("Couldn't open %s for %s", - tmpfile, sha1_to_hex(sha1)); + if (local < 0) { + if (errno == EPERM) + return error("insufficient permission for adding an object to repository database %s\n", get_object_directory()); + else + return error("unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno)); + } memset(&stream, 0, sizeof(stream)); -- cgit v0.10.2-6-g49f6 From 75bd7e374ef35265dfd7c3c1896f4fb91450e50d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 4 Nov 2006 21:51:10 -0800 Subject: git-svn: avoid printing filenames of files we're not tracking This is purely an aesthetic change, we already skip importing of files that don't affect the subdirectory we import. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 37ecc51..cc3335a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2662,11 +2662,12 @@ sub libsvn_connect { } sub libsvn_get_file { - my ($gui, $f, $rev) = @_; + my ($gui, $f, $rev, $chg) = @_; my $p = $f; if (length $SVN_PATH > 0) { return unless ($p =~ s#^\Q$SVN_PATH\E/##); } + print "\t$chg\t$f\n" unless $_q; my ($hash, $pid, $in, $out); my $pool = SVN::Pool->new; @@ -2769,8 +2770,7 @@ sub libsvn_fetch { $pool->clear; } foreach (@amr) { - print "\t$_->[0]\t$_->[1]\n" unless $_q; - libsvn_get_file($gui, $_->[1], $rev) + libsvn_get_file($gui, $_->[1], $rev, $_->[0]); } close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); @@ -2848,8 +2848,7 @@ sub libsvn_traverse { if (defined $files) { push @$files, $file; } else { - print "\tA\t$file\n" unless $_q; - libsvn_get_file($gui, $file, $rev); + libsvn_get_file($gui, $file, $rev, 'A'); } } } -- cgit v0.10.2-6-g49f6 From a35a045874379467395e0909958827ad89afc03d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 4 Nov 2006 21:51:11 -0800 Subject: git-svn: don't die on rebuild when --upgrade is specified --copy-remote and --upgrade are rarely (never?) used together, so if --copy-remote is specified, that means the user really wanted to copy the remote ref, and we should fail if that fails. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index cc3335a..4a56f18 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3139,7 +3139,7 @@ sub copy_remote_ref { my $ref = "refs/remotes/$GIT_SVN"; if (safe_qx('git-ls-remote', $origin, $ref)) { sys(qw/git fetch/, $origin, "$ref:$ref"); - } else { + } elsif ($_cp_remote && !$_upgrade) { die "Unable to find remote reference: ", "refs/remotes/$GIT_SVN on $origin\n"; } -- cgit v0.10.2-6-g49f6 From 45bf473a7bc2c40c8aea3d34a0eab7a41e77a8ff Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 9 Nov 2006 01:19:37 -0800 Subject: git-svn: fix dcommit losing changes when out-of-date from svn There was a bug in dcommit (and commit-diff) which caused deltas to be generated against the latest version of the changed file in a repository, and not the revision we are diffing (the tree) against locally. This bug can cause recent changes to the svn repository to be silently clobbered by git-svn if our repository is out-of-date. Thanks to Steven Grimm for noticing the bug. The (few) people using the commit-diff command are now required to use the -r/--revision argument. dcommit usage is unchanged. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 450ff1f..a764d1f 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -120,6 +120,7 @@ manually joining branches on commit. URL of the target Subversion repository. The final argument (URL) may be omitted if you are working from a git-svn-aware repository (that has been init-ed with git-svn). + The -r option is required for this. 'graft-branches':: This command attempts to detect merges/branches from already diff --git a/git-svn.perl b/git-svn.perl index 4a56f18..80b7b87 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -134,6 +134,7 @@ my %cmd = ( 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, 'file|F=s' => \$_file, + 'revision|r=s' => \$_revision, %cmt_opts } ], dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', { 'merge|m|M' => \$_merge, @@ -586,11 +587,21 @@ sub commit_lib { sub dcommit { my $gs = "refs/remotes/$GIT_SVN"; chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD")); + my $last_rev; foreach my $d (reverse @refs) { + unless (defined $last_rev) { + (undef, $last_rev, undef) = cmt_metadata("$d~1"); + unless (defined $last_rev) { + die "Unable to extract revision information ", + "from commit $d~1\n"; + } + } if ($_dry_run) { print "diff-tree $d~1 $d\n"; } else { - commit_diff("$d~1", $d); + if (my $r = commit_diff("$d~1", $d, undef, $last_rev)) { + $last_rev = $r; + } # else: no changes, same $last_rev } } return if $_dry_run; @@ -814,6 +825,8 @@ sub commit_diff { print STDERR "Needed URL or usable git-svn id command-line\n"; commit_diff_usage(); } + my $r = shift || $_revision; + die "-r|--revision is a required argument\n" unless (defined $r); if (defined $_message && defined $_file) { print STDERR "Both --message/-m and --file/-F specified ", "for the commit message.\n", @@ -830,13 +843,22 @@ sub commit_diff { ($repo, $SVN_PATH) = repo_path_split($SVN_URL); $SVN_LOG ||= libsvn_connect($repo); $SVN ||= libsvn_connect($repo); + if ($r eq 'HEAD') { + $r = $SVN->get_latest_revnum; + } elsif ($r !~ /^\d+$/) { + die "revision argument: $r not understood by git-svn\n"; + } my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); - my $ed = SVN::Git::Editor->new({ r => $SVN->get_latest_revnum, + my $rev_committed; + my $ed = SVN::Git::Editor->new({ r => $r, ra => $SVN_LOG, c => $tb, svn_path => $SVN_PATH }, $SVN->get_commit_editor($_message, - sub {print "Committed $_[0]\n"},@lock) + sub { + $rev_committed = $_[0]; + print "Committed $_[0]\n"; + }, @lock) ); my $mods = libsvn_checkout_tree($ta, $tb, $ed); if (@$mods == 0) { @@ -846,6 +868,7 @@ sub commit_diff { $ed->close_edit; } $_message = $_file = undef; + return $rev_committed; } ########################### utility functions ######################### diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh index f994b72..746c827 100755 --- a/t/t9105-git-svn-commit-diff.sh +++ b/t/t9105-git-svn-commit-diff.sh @@ -33,7 +33,7 @@ prev=`git rev-parse --verify HEAD^1` test_expect_success 'test the commit-diff command' " test -n '$prev' && test -n '$head' && - git-svn commit-diff '$prev' '$head' '$svnrepo' && + git-svn commit-diff -r1 '$prev' '$head' '$svnrepo' && svn co $svnrepo wc && cmp readme wc/readme " diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh new file mode 100755 index 0000000..58698b3 --- /dev/null +++ b/t/t9106-git-svn-commit-diff-clobber.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# +# Copyright (c) 2006 Eric Wong +test_description='git-svn commit-diff clobber' +. ./lib-git-svn.sh + +if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0 +then + echo 'Skipping: commit-diff clobber needs SVN libraries' + test_done + exit 0 +fi + +test_expect_success 'initialize repo' " + mkdir import && + cd import && + echo initial > file && + svn import -m 'initial' . $svnrepo && + cd .. && + echo initial > file && + git update-index --add file && + git commit -a -m 'initial' + " +test_expect_success 'commit change from svn side' " + svn co $svnrepo t.svn && + cd t.svn && + echo second line from svn >> file && + svn commit -m 'second line from svn' && + cd .. && + rm -rf t.svn + " + +test_expect_failure 'commit conflicting change from git' " + echo second line from git >> file && + git commit -a -m 'second line from git' && + git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo + " || true + +test_expect_success 'commit complementing change from git' " + git reset --hard HEAD~1 && + echo second line from svn >> file && + git commit -a -m 'second line from svn' && + echo third line from git >> file && + git commit -a -m 'third line from git' && + git-svn commit-diff -r2 HEAD~1 HEAD $svnrepo + " + +test_expect_failure 'dcommit fails to commit because of conflict' " + git-svn init $svnrepo && + git-svn fetch && + git reset --hard refs/remotes/git-svn && + svn co $svnrepo t.svn && + cd t.svn && + echo fourth line from svn >> file && + svn commit -m 'fourth line from svn' && + cd .. && + rm -rf t.svn && + echo 'fourth line from git' >> file && + git commit -a -m 'fourth line from git' && + git-svn dcommit + " || true + +test_expect_success 'dcommit does the svn equivalent of an index merge' " + git reset --hard refs/remotes/git-svn && + echo 'index merge' > file2 && + git update-index --add file2 && + git commit -a -m 'index merge' && + echo 'more changes' >> file2 && + git update-index file2 && + git commit -a -m 'more changes' && + git-svn dcommit + " + +test_done -- cgit v0.10.2-6-g49f6 From 18d5453ed36331b3f4123afae6c1c6bb4ef4d0a5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 9 Nov 2006 10:44:56 -0800 Subject: Documentation: move blame examples This moves the example to specify a line range with regexps to a later part of the manual page that has similar examples. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index ff54d29..bdfc666 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -41,10 +41,7 @@ OPTIONS Use the same output mode as gitlink:git-annotate[1] (Default: off). -L n,m:: - Annotate only the specified line range (lines count from - 1). The range can be specified with a regexp. For - example, `-L '/^sub esc_html /,/^}$/'` limits the - annotation only to the body of `esc_html` subroutine. + Annotate only the specified line range (lines count from 1). -l, --long:: Show long rev (Default: off). @@ -125,6 +122,12 @@ ll. 40-60 for file `foo`, you can use `-L` option like this: git blame -L 40,60 foo +Also you can use regular expression to specify the line range. + + git blame -L '/^sub hello {/,/^}$/' foo + +would limit the annotation to the body of `hello` subroutine. + When you are not interested in changes older than the version v2.6.18, or changes older than 3 weeks, you can use revision range specifiers similar to `git-rev-list`: -- cgit v0.10.2-6-g49f6 From a6ec3c1599f990b4f2f3dab2606688639f74d844 Mon Sep 17 00:00:00 2001 From: Robert Shearman Date: Tue, 3 Oct 2006 17:29:26 +0100 Subject: git-rebase: Use --ignore-if-in-upstream option when executing git-format-patch. This reduces the number of conflicts when rebasing after a series of patches to the same piece of code is committed upstream. Signed-off-by: Robert Shearman Signed-off-by: Junio C Hamano diff --git a/git-rebase.sh b/git-rebase.sh index a7373c0..413636e 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -286,7 +286,7 @@ fi if test -z "$do_merge" then - git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD | + git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD | git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \ --reflog-action=rebase exit $? -- cgit v0.10.2-6-g49f6 From a19f901d9f07b07abd2bfbad62037b9783fcaa7c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 10 Nov 2006 13:36:44 -0800 Subject: git-annotate: no need to exec blame; it is built-in now. diff --git a/builtin-annotate.c b/builtin-annotate.c index 25ad473..57c4684 100644 --- a/builtin-annotate.c +++ b/builtin-annotate.c @@ -4,7 +4,7 @@ * Copyright (C) 2006 Ryan Anderson */ #include "git-compat-util.h" -#include "exec_cmd.h" +#include "builtin.h" int cmd_annotate(int argc, const char **argv, const char *prefix) { @@ -20,6 +20,6 @@ int cmd_annotate(int argc, const char **argv, const char *prefix) } nargv[argc + 1] = NULL; - return execv_git_cmd(nargv); + return cmd_blame(argc + 1, nargv, prefix); } -- cgit v0.10.2-6-g49f6 From 8eaf79869f9eddf50ddffffb8d73a054e0514fcd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 10 Nov 2006 13:39:01 -0800 Subject: git-annotate: fix -S on graft file with comments. The graft file can contain comment lines and read_graft_line can return NULL for such an input, which should be skipped by the reader. Signed-off-by: Junio C Hamano diff --git a/builtin-blame.c b/builtin-blame.c index 1666022..066dee7 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1407,7 +1407,8 @@ static int read_ancestry(const char *graft_file) /* The format is just "Commit Parent1 Parent2 ...\n" */ int len = strlen(buf); struct commit_graft *graft = read_graft_line(buf, len); - register_commit_graft(graft, 0); + if (graft) + register_commit_graft(graft, 0); } fclose(fp); return 0; -- cgit v0.10.2-6-g49f6 From 057bc808b4aa2e7795f9bd395e68071301bc0b74 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 11 Nov 2006 14:45:35 -0800 Subject: path-list: fix path-list-insert return value When path-list-insert is called on an existing path, it returned an unrelated element in the list. Luckily most of the callers are ignoring the return value, but merge-recursive uses it at three places and this would have resulted in a bogus rename detection. Signed-off-by: Junio C Hamano diff --git a/path-list.c b/path-list.c index 0c332dc..f8800f8 100644 --- a/path-list.c +++ b/path-list.c @@ -57,7 +57,7 @@ struct path_list_item *path_list_insert(const char *path, struct path_list *list int index = add_entry(list, path); if (index < 0) - index = 1 - index; + index = -1 - index; return list->items + index; } -- cgit v0.10.2-6-g49f6 From e02cd6388f0193706279268a7d9fa57be4cbc997 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 10 Nov 2006 11:53:41 -0800 Subject: git-cvsserver: read from git with -z to get non-ASCII pathnames. Signed-off-by: Junio C Hamano diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 08ad831..053d0d9 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -2331,67 +2331,72 @@ sub update if ( defined ( $lastpicked ) ) { - my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!"); + my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-z', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!"); + local ($/) = "\0"; while ( ) { - unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)\s+(.*)$/o ) + chomp; + unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)$/o ) { die("Couldn't process git-diff-tree line : $_"); } + my ($mode, $hash, $change) = ($1, $2, $3); + my $name = ; + chomp($name); - # $log->debug("File mode=$1, hash=$2, change=$3, name=$4"); + # $log->debug("File mode=$mode, hash=$hash, change=$change, name=$name"); my $git_perms = ""; - $git_perms .= "r" if ( $1 & 4 ); - $git_perms .= "w" if ( $1 & 2 ); - $git_perms .= "x" if ( $1 & 1 ); + $git_perms .= "r" if ( $mode & 4 ); + $git_perms .= "w" if ( $mode & 2 ); + $git_perms .= "x" if ( $mode & 1 ); $git_perms = "rw" if ( $git_perms eq "" ); - if ( $3 eq "D" ) + if ( $change eq "D" ) { - #$log->debug("DELETE $4"); - $head->{$4} = { - name => $4, - revision => $head->{$4}{revision} + 1, + #$log->debug("DELETE $name"); + $head->{$name} = { + name => $name, + revision => $head->{$name}{revision} + 1, filehash => "deleted", commithash => $commit->{hash}, modified => $commit->{date}, author => $commit->{author}, mode => $git_perms, }; - $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms); + $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms); } - elsif ( $3 eq "M" ) + elsif ( $change eq "M" ) { - #$log->debug("MODIFIED $4"); - $head->{$4} = { - name => $4, - revision => $head->{$4}{revision} + 1, - filehash => $2, + #$log->debug("MODIFIED $name"); + $head->{$name} = { + name => $name, + revision => $head->{$name}{revision} + 1, + filehash => $hash, commithash => $commit->{hash}, modified => $commit->{date}, author => $commit->{author}, mode => $git_perms, }; - $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms); + $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms); } - elsif ( $3 eq "A" ) + elsif ( $change eq "A" ) { - #$log->debug("ADDED $4"); - $head->{$4} = { - name => $4, + #$log->debug("ADDED $name"); + $head->{$name} = { + name => $name, revision => 1, - filehash => $2, + filehash => $hash, commithash => $commit->{hash}, modified => $commit->{date}, author => $commit->{author}, mode => $git_perms, }; - $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms); + $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms); } else { - $log->warn("UNKNOWN FILE CHANGE mode=$1, hash=$2, change=$3, name=$4"); + $log->warn("UNKNOWN FILE CHANGE mode=$mode, hash=$hash, change=$change, name=$name"); die; } } @@ -2400,10 +2405,12 @@ sub update # this is used to detect files removed from the repo my $seen_files = {}; - my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!"); + my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-z', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!"); + local $/ = "\0"; while ( ) { - unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\s+(.*)$/o ) + chomp; + unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o ) { die("Couldn't process git-ls-tree line : $_"); } -- cgit v0.10.2-6-g49f6 From a74e60a0f59899c612053b6fe0f0f62652118151 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 11 Nov 2006 18:22:31 -0800 Subject: GIT 1.4.4-rc2 Signed-off-by: Junio C Hamano diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 9796e91..966b35d 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.4.4-rc1.GIT +DEF_VER=v1.4.4-rc2.GIT LF=' ' -- cgit v0.10.2-6-g49f6 From bae777db33951468824e6beed6de900e7db6d51f Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Sun, 12 Nov 2006 22:28:43 +0100 Subject: git-update-index(1): fix use of quoting in section title Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 41bb7e1..0e0a3af 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -216,8 +216,8 @@ $ git ls-files -s ------------ -Using "assume unchanged" bit ----------------------------- +Using ``assume unchanged'' bit +------------------------------ Many operations in git depend on your filesystem to have an efficient `lstat(2)` implementation, so that `st_mtime` -- cgit v0.10.2-6-g49f6 From 40cf043389ef4cdf3e56e7c4268d6f302e387fa0 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Mon, 13 Nov 2006 13:50:04 +0000 Subject: test-lib.sh: A command dying due to a signal is an unexpected failure. When test_expect_failure detects that a command failed, it still has to treat a program that crashed from a signal as unexpected failure. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano diff --git a/t/test-lib.sh b/t/test-lib.sh index 07cb706..3895f16 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -129,7 +129,7 @@ test_expect_failure () { error "bug in the test script: not 2 parameters to test-expect-failure" say >&3 "expecting failure: $2" test_run_ "$2" - if [ "$?" = 0 -a "$eval_ret" != 0 ] + if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ] then test_ok_ "$1" else -- cgit v0.10.2-6-g49f6 From 3d12d0cfbbda0feb6305d6c53f3cf9aae2330c4c Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Mon, 13 Nov 2006 13:50:00 +0000 Subject: Catch errors when writing an index that contains invalid objects. If git-write-index is called without --missing-ok, it reports invalid objects that it finds in the index. But without this patch it dies right away or may run into an infinite loop. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano diff --git a/cache-tree.c b/cache-tree.c index a803262..9b73c86 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -282,6 +282,8 @@ static int update_one(struct cache_tree *it, baselen + sublen + 1, missing_ok, dryrun); + if (subcnt < 0) + return subcnt; i += subcnt - 1; sub->used = 1; } diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 2c9bbb5..6aff0b8 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -209,6 +209,28 @@ test_expect_success \ 'validate object ID for a known tree.' \ 'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2' +cat >badobjects < Date: Sun, 12 Nov 2006 16:29:42 +0100 Subject: Rework cvsexportcommit to handle binary files for all cases. Also adds test cases for adding removing and deleting binary and text files plus two tests for the checks on binary files. Signed-off-by: Robin Rosenberg Signed-off-by: Junio C Hamano diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 5e23851..7bac16e 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -1,10 +1,10 @@ #!/usr/bin/perl -w # Known limitations: -# - cannot add or remove binary files # - does not propagate permissions # - tells "ready for commit" even when things could not be completed -# (eg addition of a binary file) +# (not sure this is true anymore, more testing is needed) +# - does not handle whitespace in pathnames at all. use strict; use Getopt::Std; @@ -68,9 +68,9 @@ foreach my $line (@commit) { if ($stage eq 'headers') { if ($line =~ m/^parent (\w{40})$/) { # found a parent push @parents, $1; - } elsif ($line =~ m/^author (.+) \d+ \+\d+$/) { + } elsif ($line =~ m/^author (.+) \d+ [-+]\d+$/) { $author = $1; - } elsif ($line =~ m/^committer (.+) \d+ \+\d+$/) { + } elsif ($line =~ m/^committer (.+) \d+ [-+]\d+$/) { $committer = $1; } } else { @@ -139,6 +139,17 @@ foreach my $f (@files) { push @dfiles, $fields[5]; } } +my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles); +@binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit); +map { chomp } @binfiles; +@abfiles = grep s/^Binary files \/dev\/null and b\/(.*) differ$/$1/, @binfiles; +@dbfiles = grep s/^Binary files a\/(.*) and \/dev\/null differ$/$1/, @binfiles; +@mbfiles = grep s/^Binary files a\/(.*) and b\/(.*) differ$/$1/, @binfiles; +push @bfiles, @abfiles; +push @bfiles, @dbfiles; +push @bfiles, @mbfiles; +push @mfiles, @mbfiles; + $opt_v && print "The commit affects:\n "; $opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n"; undef @files; # don't need it anymore @@ -153,6 +164,10 @@ foreach my $d (@dirs) { } foreach my $f (@afiles) { # This should return only one value + if ($f =~ m,(.*)/[^/]*$,) { + my $p = $1; + next if (grep { $_ eq $p } @dirs); + } my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; if (-d dirname $f and $status[0] !~ m/Status: Unknown$/ @@ -162,6 +177,7 @@ foreach my $f (@afiles) { warn "Status was: $status[0]\n"; } } + foreach my $f (@mfiles, @dfiles) { # TODO:we need to handle removed in cvs my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); @@ -200,24 +216,31 @@ foreach my $d (@dirs) { print "'Patching' binary files\n"; -my @bfiles = grep(m/^Binary/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit)); -@bfiles = map { chomp } @bfiles; foreach my $f (@bfiles) { # check that the file in cvs matches the "old" file # extract the file to $tmpdir and compare with cmp - my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}"); - chomp $tree; - my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; - chomp $blob; - `git-cat-file blob $blob > $tmpdir/blob`; - if (system('cmp', '-s', $f, "$tmpdir/blob")) { - warn "Binary file $f in CVS does not match parent.\n"; - $dirty = 1; - next; + if (not(grep { $_ eq $f } @afiles)) { + my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}"); + chomp $tree; + my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; + chomp $blob; + `git-cat-file blob $blob > $tmpdir/blob`; + if (system('cmp', '-s', $f, "$tmpdir/blob")) { + warn "Binary file $f in CVS does not match parent.\n"; + if (not $opt_f) { + $dirty = 1; + next; + } + } + } + if (not(grep { $_ eq $f } @dfiles)) { + my $tree = safe_pipe_capture('git-rev-parse', "$commit^{tree}"); + chomp $tree; + my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; + chomp $blob; + # replace with the new file + `git-cat-file blob $blob > $f`; } - - # replace with the new file - `git-cat-file blob $blob > $f`; # TODO: something smart with file modes @@ -231,7 +254,10 @@ if ($dirty) { my $fuzz = $opt_p ? 0 : 2; print "Patching non-binary files\n"; -print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`; + +if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) { + print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`; +} my $dirtypatch = 0; if (($? >> 8) == 2) { @@ -242,7 +268,11 @@ if (($? >> 8) == 2) { } foreach my $f (@afiles) { - system('cvs', 'add', $f); + if (grep { $_ eq $f } @bfiles) { + system('cvs', 'add','-kb',$f); + } else { + system('cvs', 'add', $f); + } if ($?) { $dirty = 1; warn "Failed to cvs add $f -- you may need to do it manually"; diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh new file mode 100755 index 0000000..6e566d4 --- /dev/null +++ b/t/t9200-git-cvsexportcommit.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# +# Copyright (c) Robin Rosenberg +# +test_description='CVS export comit. ' + +. ./test-lib.sh + +cvs >/dev/null 2>&1 +if test $? -ne 1 +then + test_expect_success 'skipping git-cvsexportcommit tests, cvs not found' : + test_done + exit +fi + +export CVSROOT=$(pwd)/cvsroot +export CVSWORK=$(pwd)/cvswork +rm -rf "$CVSROOT" "$CVSWORK" +mkdir "$CVSROOT" && +cvs init && +cvs -Q co -d "$CVSWORK" . && +export GIT_DIR=$(pwd)/.git && +echo >empty && +git add empty && +git commit -a -m "Initial" 2>/dev/null || +exit 1 + +test_expect_success \ + 'New file' \ + 'mkdir A B C D E F && + echo hello1 >A/newfile1.txt && + echo hello2 >B/newfile2.txt && + cp ../test9200a.png C/newfile3.png && + cp ../test9200a.png D/newfile4.png && + git add A/newfile1.txt && + git add B/newfile2.txt && + git add C/newfile3.png && + git add D/newfile4.png && + git commit -a -m "Test: New file" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git cvsexportcommit -c $id && + test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.1/" && + test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "newfile2.txt/1.1/" && + test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "newfile3.png/1.1/-kb" && + test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "newfile4.png/1.1/-kb" && + diff A/newfile1.txt ../A/newfile1.txt && + diff B/newfile2.txt ../B/newfile2.txt && + diff C/newfile3.png ../C/newfile3.png && + diff D/newfile4.png ../D/newfile4.png + )' + +test_expect_success \ + 'Remove two files, add two and update two' \ + 'echo Hello1 >>A/newfile1.txt && + rm -f B/newfile2.txt && + rm -f C/newfile3.png && + echo Hello5 >E/newfile5.txt && + cp ../test9200b.png D/newfile4.png && + cp ../test9200a.png F/newfile6.png && + git add E/newfile5.txt && + git add F/newfile6.png && + git commit -a -m "Test: Remove, add and update" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git cvsexportcommit -c $id && + test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.2/" && + test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "newfile4.png/1.2/-kb" && + test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" && + test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" && + diff A/newfile1.txt ../A/newfile1.txt && + diff D/newfile4.png ../D/newfile4.png && + diff E/newfile5.txt ../E/newfile5.txt && + diff F/newfile6.png ../F/newfile6.png + )' + +# Should fail (but only on the git-cvsexportcommit stage) +test_expect_success \ + 'Fail to change binary more than one generation old' \ + 'cat F/newfile6.png >>D/newfile4.png && + git commit -a -m "generatiion 1" && + cat F/newfile6.png >>D/newfile4.png && + git commit -a -m "generation 2" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + ! git cvsexportcommit -c $id + )' + +# Should fail, but only on the git-cvsexportcommit stage +test_expect_success \ + 'Fail to remove binary file more than one generation old' \ + 'git reset --hard HEAD^ && + cat F/newfile6.png >>D/newfile4.png && + git commit -a -m "generation 2 (again)" && + rm -f D/newfile4.png && + git commit -a -m "generation 3" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + ! git cvsexportcommit -c $id + )' + +# We reuse the state from two tests back here + +# This test is here because a patch for only binary files will +# fail with gnu patch, so cvsexportcommit must handle that. +test_expect_success \ + 'Remove only binary files' \ + 'git reset --hard HEAD^^^ && + rm -f D/newfile4.png && + git commit -a -m "test: remove only a binary file" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git cvsexportcommit -c $id && + test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.2/" && + test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" && + test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" && + diff A/newfile1.txt ../A/newfile1.txt && + diff E/newfile5.txt ../E/newfile5.txt && + diff F/newfile6.png ../F/newfile6.png + )' + +test_expect_success \ + 'Remove only a text file' \ + 'rm -f A/newfile1.txt && + git commit -a -m "test: remove only a binary file" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git cvsexportcommit -c $id && + test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "" && + test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" && + test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" && + diff E/newfile5.txt ../E/newfile5.txt && + diff F/newfile6.png ../F/newfile6.png + )' + +test_done diff --git a/t/test9200a.png b/t/test9200a.png new file mode 100644 index 0000000..7b181d1 Binary files /dev/null and b/t/test9200a.png differ diff --git a/t/test9200b.png b/t/test9200b.png new file mode 100644 index 0000000..ac22ccb Binary files /dev/null and b/t/test9200b.png differ -- cgit v0.10.2-6-g49f6 From e267c2f6f0784e242883b7d3fe5f36ef63d6950d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 14 Nov 2006 22:11:26 -0800 Subject: GIT 1.4.4 Signed-off-by: Junio C Hamano diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 966b35d..eca1ff2 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.4.4-rc2.GIT +DEF_VER=v1.4.4.GIT LF=' ' -- cgit v0.10.2-6-g49f6 From ef58d9587ebad999b3decfcf369ba3b4ff72b121 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 14 Nov 2006 22:23:18 -0800 Subject: apply --numstat: mark binary diffstat with - -, not 0 0 We do not even know number of lines so showing it as 0 0 is lying. This would also help Porcelains like cvsexportcommit. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index aad5526..b80ad2c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2116,7 +2116,11 @@ static void numstat_patch_list(struct patch *patch) for ( ; patch; patch = patch->next) { const char *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)) quote_c_style(name, NULL, stdout, 0); else -- cgit v0.10.2-6-g49f6 From 0a3881d4cfee5c4ee3991a1b14a10333355958f1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 15 Nov 2006 17:27:54 +0100 Subject: Seek back to current filepos when mmap()ing with NO_MMAP "git-index-pack --fix-thin" relies on mmap() not changing the current file position (otherwise the pack will be corrupted when writing the final SHA1). Meet that expectation. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/compat/mmap.c b/compat/mmap.c index 55cb120..a4d2e50 100644 --- a/compat/mmap.c +++ b/compat/mmap.c @@ -7,6 +7,7 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset) { int n = 0; + off_t current_offset = lseek(fd, 0, SEEK_CUR); if (start != NULL || !(flags & MAP_PRIVATE)) die("Invalid usage of gitfakemmap."); @@ -39,6 +40,11 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_ n += count; } + if (current_offset != lseek(fd, current_offset, SEEK_SET)) { + errno = EINVAL; + return MAP_FAILED; + } + return start; } -- cgit v0.10.2-6-g49f6 From 897643cc79cb8c05694c98a4fb0560c78952db3e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 15 Nov 2006 10:46:10 -0800 Subject: git-checkout: do not allow -f and -m at the same time. Instead of silently ignoring one over the other, complain on this incompatible combination. Signed-off-by: Junio C Hamano diff --git a/git-checkout.sh b/git-checkout.sh index 119bca1..eb28b29 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -77,6 +77,11 @@ while [ "$#" != "0" ]; do esac done +case "$force$merge" in +11) + die "git checkout: -f and -m are incompatible" +esac + # The behaviour of the command with and without explicit path # parameters is quite different. # -- cgit v0.10.2-6-g49f6 From bf7e1472df65c948581e2fecd494eccfaa40b9d9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 15 Nov 2006 10:54:10 -0800 Subject: git-checkout: allow pathspec to recover lost working tree directory It is often wanted on the #git channel that this were to work to recover removed directory: rm -fr Documentation git checkout -- Documentation git checkout HEAD -- Documentation ;# alternatively Now it does. Signed-off-by: Junio C Hamano diff --git a/git-checkout.sh b/git-checkout.sh index eb28b29..737abd0 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -112,7 +112,11 @@ Did you intend to checkout '$@' which can not be resolved as commit?" git-ls-tree --full-name -r "$new" "$@" | git-update-index --index-info || exit $? fi - git-checkout-index -f -u -- "$@" + + # Make sure the request is about existing paths. + git-ls-files --error-unmatch -- "$@" >/dev/null || exit + git-ls-files -- "$@" | + git-checkout-index -f -u --stdin exit $? else # Make sure we did not fall back on $arg^{tree} codepath -- cgit v0.10.2-6-g49f6 From faa1bbfdd2c7d2f6cf556ee3f0d54cad42b08c61 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Wed, 15 Nov 2006 21:37:50 +0100 Subject: gitweb: Put back shortlog instead of graphiclog in the project list. Looks like a repo.or.cz-specific change slipped in. Signed-off-by: Alexandre Julliard Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e54a29e..7587595 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2454,7 +2454,7 @@ sub git_project_list_body { $pr->{'age_string'} . "\n" . "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . - $cgi->a({-href => '/git-browser/by-commit.html?r='.$pr->{'path'}}, "graphiclog") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') . -- cgit v0.10.2-6-g49f6 From efe4abd14c75834d30e3e521b3597eb07ea9271b Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Wed, 15 Nov 2006 21:15:44 +0100 Subject: Run "git repack -a -d" once more at end, if there's 1MB or more of not-packed data. Although I converted upstream coreutils to git last month, I just reconverted coreutils once again, as a test, and ended up with a git repository of about 130MB (contrast with my packed git repo of size 52MB). That was because there were a lot of commits (but < 1024) after the final automatic "git-repack -a -d". Running a final git-repack -a -d && git-prune-packed cut the final repository size down to the expected size. So this looks like an easy way to improve git-cvsimport. Just run "git repack ..." at the end if there's more than some reasonable amount of not-packed data. My choice of 1MB is a little arbitrarily. I wouldn't mind missing the minimal repo size by 1MB. At the other end of the spectrum, it's probably not worthwhile to pack everything when the total repository size is less than 1MB. Here's the patch: Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 14e2c61..b54a948 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -876,6 +876,16 @@ while() { } commit() if $branch and $state != 11; +# The heuristic of repacking every 1024 commits can leave a +# lot of unpacked data. If there is more than 1MB worth of +# not-packed objects, repack once more. +my $line = `git-count-objects`; +if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) { + my ($n_objects, $kb) = ($1, $2); + 1024 < $kb + and system("git repack -a -d"); +} + foreach my $git_index (values %index) { if ($git_index ne '.git/index') { unlink($git_index); -- cgit v0.10.2-6-g49f6 From 73fbd33cce281f116852d02963935f2ae90acc76 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 15 Nov 2006 14:57:47 -0800 Subject: convert-objects: set _XOPEN_SOURCE to 600 Otherwise OpenBSD header files drop S_ISLNK() definition which is used in an inline defined in cache.h Signed-off-by: Junio C Hamano diff --git a/convert-objects.c b/convert-objects.c index 631678b..8812583 100644 --- a/convert-objects.c +++ b/convert-objects.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 /* glibc2 and AIX 5.3L need this */ +#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */ #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ #define _GNU_SOURCE #include -- cgit v0.10.2-6-g49f6 From d09e79cb1c474b3bb323356e6d1072922ab7ccb2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 16 Nov 2006 11:47:22 -0800 Subject: git-pull: allow pulling into an empty repository We used to complain that we cannot merge anything we fetched with a local branch that does not exist yet. Just treat the case as a natural extension of fast forwarding and make the local branch'es tip point at the same commit we just fetched. After all an empty repository without an initial commit is an ancestor of any commit. [jc: I added a trivial test. We've become sloppy but we should stick to the discipline of covering new behaviour with new tests. ] Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/git-pull.sh b/git-pull.sh index ed04e7d..e23beb6 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -44,10 +44,10 @@ do shift done -orig_head=$(git-rev-parse --verify HEAD) || die "Pulling into a black hole?" +orig_head=$(git-rev-parse --verify HEAD 2>/dev/null) git-fetch --update-head-ok --reflog-action=pull "$@" || exit 1 -curr_head=$(git-rev-parse --verify HEAD) +curr_head=$(git-rev-parse --verify HEAD 2>/dev/null) if test "$curr_head" != "$orig_head" then # The fetch involved updating the current branch. @@ -80,6 +80,11 @@ case "$merge_head" in exit 0 ;; ?*' '?*) + if test -z "$orig_head" + then + echo >&2 "Cannot merge multiple branches into empty head" + exit 1 + fi var=`git-repo-config --get pull.octopus` if test -n "$var" then @@ -95,6 +100,13 @@ case "$merge_head" in ;; esac +if test -z "$orig_head" +then + git-update-ref -m "initial pull" HEAD $merge_head "" && + git-read-tree --reset -u HEAD || exit 1 + exit +fi + case "$strategy_args" in '') strategy_args=$strategy_default_args diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh new file mode 100755 index 0000000..f841574 --- /dev/null +++ b/t/t5520-pull.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +test_description='pulling into void' + +. ./test-lib.sh + +D=`pwd` + +test_expect_success setup ' + + echo file >file && + git add file && + git commit -a -m original + +' + +test_expect_success 'pulling into void' ' + mkdir cloned && + cd cloned && + git init-db && + git pull .. +' + +cd "$D" + +test_expect_success 'checking the results' ' + test -f file && + test -f cloned/file && + diff file cloned/file +' + +test_done + -- cgit v0.10.2-6-g49f6 From 6b1f8c32b1a602734f14b9cf8a9f59a767752677 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 16 Nov 2006 22:57:20 -0800 Subject: "git fmt-merge-msg" SIGSEGV Ok, this is a _really_ stupid case, and I don't think it matters, but hey, we should never SIGSEGV. Steps to reproduce: mkdir duh cd duh git init-db git-fmt-merge-msg < /dev/null will cause a SIGSEGV in cmd_fmt_merge_msg(), because we're doing a strncmp() with a NULL current_branch. And yeah, it's an insane schenario, and no, it doesn't really matter. The only reason I noticed was that a broken version of my "git pull" into an empty directory would cause this. This silly patch just replaces the SIGSEGV with a controlled exit with an error message. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 3d3097d..87d3d63 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -278,6 +278,8 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) /* get current branch */ current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); + if (!current_branch) + die("No current branch"); if (!strncmp(current_branch, "refs/heads/", 11)) current_branch += 11; -- cgit v0.10.2-6-g49f6 From e9195b584f6ad49b637b56be0705a7362a95d692 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 14 Nov 2006 22:18:31 -0800 Subject: pack-objects: tweak "do not even attempt delta" heuristics The heuristics to give up deltification when both the source and the target are both in the same pack affects negatively when we are repacking the subset of objects in the existing pack. This caused any incremental updates to use suboptimal packs. Tweak the heuristics to avoid this problem. Signed-off-by: Junio C Hamano diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 69e5dd3..753bcd5 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1176,7 +1176,9 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, * on an earlier try, but only when reusing delta data. */ 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; /* -- cgit v0.10.2-6-g49f6 From f8290630cb900fc5581e91a4cc055d2fba121db0 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 18 Nov 2006 03:56:52 +0100 Subject: Fix git-for-each-refs broken for tags Unfortunately, git-for-each-refs is currently unusable for peeking into tag comments, since it uses freed pointers, so it just prints out all sort of garbage. This makes it strdup() contents and body values. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 173bf38..227aa3c 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -478,9 +478,9 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj if (!strcmp(name, "subject")) v->s = copy_line(subpos); else if (!strcmp(name, "body")) - v->s = bodypos; + v->s = xstrdup(bodypos); else if (!strcmp(name, "contents")) - v->s = subpos; + v->s = xstrdup(subpos); } } -- cgit v0.10.2-6-g49f6 From f847c07b9ac82ff3d0da0efb3bed75bc17489a17 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 18 Nov 2006 06:05:11 +0100 Subject: git-apply: Documentation typo fix inacurate -> inaccurate, sorry if it was a pun. ;-) Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index d9137c7..2cc32d1 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -150,7 +150,7 @@ discouraged. * `strip` outputs warnings for a few such errors, strips out the trailing whitespaces and applies the patch. ---inacurate-eof:: +--inaccurate-eof:: Under certain circumstances, some versions of diff do not correctly detect a missing new-line at the end of the file. As a result, patches created by such diff programs do not record incomplete lines -- cgit v0.10.2-6-g49f6 From a6e8a7677055e092424db42c044774bc6dbd74f6 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 18 Nov 2006 13:07:06 +0100 Subject: sparse fix: non-ANSI function declaration The declaration of discard_cache() in cache.h already has its "void". Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/index-pack.c b/index-pack.c index 042aea8..8331d99 100644 --- a/index-pack.c +++ b/index-pack.c @@ -90,7 +90,7 @@ static SHA_CTX input_ctx; static int input_fd, output_fd, mmap_fd; /* Discard current buffer used content. */ -static void flush() +static void flush(void) { if (input_offset) { if (output_fd >= 0) diff --git a/read-cache.c b/read-cache.c index 97c3867..0f5fb5b 100644 --- a/read-cache.c +++ b/read-cache.c @@ -844,7 +844,7 @@ unmap: die("index file corrupt"); } -int discard_cache() +int discard_cache(void) { int ret; -- cgit v0.10.2-6-g49f6 From 38f4d138ee101f3f238ffd43fac76f5b951516c1 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 18 Nov 2006 13:06:56 +0100 Subject: sparse fix: Using plain integer as NULL pointer Z_NULL is defined as 0, use a proper NULL pointer in its stead. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/archive-zip.c b/archive-zip.c index 28e7352..ae5572a 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -160,7 +160,7 @@ static int write_zip_entry(const unsigned char *sha1, void *buffer = NULL; void *deflated = NULL; - crc = crc32(0, Z_NULL, 0); + crc = crc32(0, NULL, 0); path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); if (verbose) -- cgit v0.10.2-6-g49f6 From 3dad11bfdb9363bade57ca2caadef1883767e9d3 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 18 Nov 2006 13:07:09 +0100 Subject: git-apply: slightly clean up bitfield usage This patch fixes a sparse warning about inaccurate_eof being a "dubious one-bit signed bitfield", makes three more binary variables members of this (now unsigned) bitfield and adds a short comment to indicate the nature of two ternary variables. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index aad5526..61f047f 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -140,12 +140,15 @@ struct fragment { struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; - int is_rename, is_copy, is_new, is_delete, is_binary; + int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ int rejected; unsigned long deflate_origlen; int lines_added, lines_deleted; int score; - int inaccurate_eof:1; + unsigned int inaccurate_eof:1; + unsigned int is_binary:1; + unsigned int is_copy:1; + unsigned int is_rename:1; struct fragment *fragments; char *result; unsigned long resultsize; -- cgit v0.10.2-6-g49f6 From fd931411c0635ca5e7968bd2981457056b49062b Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 18 Nov 2006 15:15:49 +0100 Subject: Document git-runstatus I copied most of the text from git-status.txt. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/Documentation/git-runstatus.txt b/Documentation/git-runstatus.txt new file mode 100644 index 0000000..89d7b92 --- /dev/null +++ b/Documentation/git-runstatus.txt @@ -0,0 +1,69 @@ +git-runstatus(1) +================ + +NAME +---- +git-runstatus - A helper for git-status and git-commit + + +SYNOPSIS +-------- +'git-runstatus' [--color|--nocolor] [--amend] [--verbose] [--untracked] + + +DESCRIPTION +----------- +Examines paths in the working tree that has changes unrecorded +to the index file, and changes between the index file and the +current HEAD commit. The former paths are what you _could_ +commit by running 'git-update-index' before running 'git +commit', and the latter paths are what you _would_ commit by +running 'git commit'. + +If there is no path that is different between the index file and +the current HEAD commit, the command exits with non-zero status. + +Note that this is _not_ the user level command you would want to +run from the command line. Use 'git-status' instead. + + +OPTIONS +------- +--color:: + Show colored status, highlighting modified file names. + +--nocolor:: + Turn off coloring. + +--amend:: + Show status based on HEAD^1, not HEAD, i.e. show what + 'git-commit --amend' would do. + +--verbose:: + Show unified diff of all file changes. + +--untracked:: + Show files in untracked directories, too. Without this + option only its name and a trailing slash are displayed + for each untracked directory. + + +OUTPUT +------ +The output from this command is designed to be used as a commit +template comments, and all the output lines are prefixed with '#'. + + +Author +------ +Originally written by Linus Torvalds as part +of git-commit, and later rewritten in C by Jeff King. + +Documentation +-------------- +Documentation by David Greaves, Junio C Hamano and the git-list . + +GIT +--- +Part of the gitlink:git[7] suite + diff --git a/Documentation/git.txt b/Documentation/git.txt index 52bc05a..619d656 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -302,6 +302,9 @@ gitlink:git-request-pull[1]:: gitlink:git-rev-parse[1]:: Pick out and massage parameters. +gitlink:git-runstatus[1]:: + A helper for git-status and git-commit. + gitlink:git-send-email[1]:: Send patch e-mails out of "format-patch --mbox" output. diff --git a/builtin-runstatus.c b/builtin-runstatus.c index 303c556..0b63037 100644 --- a/builtin-runstatus.c +++ b/builtin-runstatus.c @@ -4,7 +4,7 @@ extern int wt_status_use_color; static const char runstatus_usage[] = -"git-runstatus [--color|--nocolor] [--amend] [--verbose]"; +"git-runstatus [--color|--nocolor] [--amend] [--verbose] [--untracked]"; int cmd_runstatus(int argc, const char **argv, const char *prefix) { -- cgit v0.10.2-6-g49f6 From e3d457fb59f71dd40d24c82f48625a24492907d4 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 18 Nov 2006 20:44:08 +0100 Subject: Documentation: Define symref and update HEAD description HEAD was still described as a symlink instead of a symref. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt index 7e560b0..894883d 100644 --- a/Documentation/glossary.txt +++ b/Documentation/glossary.txt @@ -282,6 +282,13 @@ SCM:: SHA1:: Synonym for object name. +symref:: + Symbolic reference: instead of containing the SHA1 id itself, it + is of the format 'ref: refs/some/thing' and when referenced, it + recursively dereferences to this reference. 'HEAD' is a prime + example of a symref. Symbolic references are manipulated with + the gitlink:git-symbolic-ref[1] command. + topic branch:: A regular git branch that is used by a developer to identify a conceptual line of development. Since branches diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt index 275d18b..6d8c58e 100644 --- a/Documentation/repository-layout.txt +++ b/Documentation/repository-layout.txt @@ -70,12 +70,16 @@ refs/tags/`name`:: object, or a tag object that points at a commit object). HEAD:: - A symlink of the form `refs/heads/'name'` to point at - the current branch, if exists. It does not mean much if - the repository is not associated with any working tree + A symref (see glossary) to the `refs/heads/` namespace + describing the currently active branch. It does not mean + much if the repository is not associated with any working tree (i.e. a 'bare' repository), but a valid git repository - *must* have such a symlink here. It is legal if the - named branch 'name' does not (yet) exist. + *must* have the HEAD file; some porcelains may use it to + guess the designated "default" branch of the repository + (usually 'master'). It is legal if the named branch + 'name' does not (yet) exist. In some legacy setups, it is + a symbolic link instead of a symref that points at the current + branch. branches:: A slightly deprecated way to store shorthands to be used -- cgit v0.10.2-6-g49f6 From 198a4f4ff0694cb5b906278fc1c2b6e7db4d2737 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 19 Nov 2006 00:30:15 +0100 Subject: Documentation: Correct alternates documentation, document http-alternates For one, the documentation invalidly claimed that the paths have to be absolute when that's not the case and in fact there is a very valid reason not to use absolute paths (documented the reason as well). Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt index 6d8c58e..e20fb7e 100644 --- a/Documentation/repository-layout.txt +++ b/Documentation/repository-layout.txt @@ -52,9 +52,20 @@ objects/info/packs:: by default. objects/info/alternates:: - This file records absolute filesystem paths of alternate - object stores that this object store borrows objects - from, one pathname per line. + This file records paths to alternate object stores that + this object store borrows objects from, one pathname per + line. Note that not only native Git tools use it locally, + but the HTTP fetcher also tries to use it remotely; this + will usually work if you have relative paths (relative + to the object database, not to the repository!) in your + alternates file, but it will not work if you use absolute + paths unless the absolute path in filesystem and web URL + is the same. See also 'objects/info/http-alternates'. + +objects/info/http-alternates:: + This file records URLs to alternate object stores that + this object store borrows objects from, to be used when + the repository is fetched over HTTP. refs:: References are stored in subdirectories of this -- cgit v0.10.2-6-g49f6 From 6c96c0f1940b0247530d21b10042d16bf1e7b769 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 18 Nov 2006 21:39:17 -0800 Subject: git-fetch: follow lightweit tags as well. This side-ports commit fd19f620 from Cogito, in which I fixed exactly the same bug. Somehow nobody noticed this for a long time in git. Signed-off-by: Junio C Hamano diff --git a/git-fetch.sh b/git-fetch.sh index 7442dd2..eb32476 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -432,10 +432,11 @@ case "$no_tags$tags" in # using local tracking branch. taglist=$(IFS=" " && git-ls-remote $upload_pack --tags "$remote" | - sed -ne 's|^\([0-9a-f]*\)[ ]\(refs/tags/.*\)^{}$|\1 \2|p' | + sed -n -e 's|^\('"$_x40"'\) \(refs/tags/.*\)^{}$|\1 \2|p' \ + -e 's|^\('"$_x40"'\) \(refs/tags/.*\)$|\1 \2|p' | while read sha1 name do - git-show-ref --verify --quiet -- $name && continue + git-show-ref --verify --quiet -- "$name" && continue git-check-ref-format "$name" || { echo >&2 "warning: tag ${name} ignored" continue diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index df0ae48..a11ab0a 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -66,4 +66,20 @@ test_expect_success "fetch test for-merge" ' cut -f -2 .git/FETCH_HEAD >actual && diff expected actual' +test_expect_success 'fetch following tags' ' + + cd "$D" && + git tag -a -m 'annotated' anno HEAD && + git tag light HEAD && + + mkdir four && + cd four && + git init-db && + + git fetch .. :track && + git show-ref --verify refs/tags/anno && + git show-ref --verify refs/tags/light + +' + test_done -- cgit v0.10.2-6-g49f6 From ef06b91804ff5a626e265c6d9080bbc0e7924e88 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 18 Nov 2006 22:13:33 -0800 Subject: do_for_each_ref: perform the same sanity check for leftovers. An earlier commit b37a562a added a check to see if the ref points at a valid object (as a part of 'negative ref' support which we currently do not use), but did so only while iterating over both packed and loose refs, and forgot to apply the same check while iterating over the remaining ones. We might want to replace the "if null then omit it" check with "eh --- what business does a 0{40} value have here?" complaint later since we currently do not use negative refs, but that is a separate issue. Signed-off-by: Junio C Hamano diff --git a/refs.c b/refs.c index f003a0b..0e156c5 100644 --- a/refs.c +++ b/refs.c @@ -322,6 +322,20 @@ int read_ref(const char *ref, unsigned char *sha1) return -1; } +static int do_one_ref(const char *base, each_ref_fn fn, int trim, + void *cb_data, struct ref_list *entry) +{ + if (strncmp(base, entry->name, trim)) + return 0; + if (is_null_sha1(entry->sha1)) + return 0; + if (!has_sha1_file(entry->sha1)) { + error("%s does not point to a valid object!", entry->name); + return 0; + } + return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); +} + static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_data) { @@ -343,29 +357,15 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, entry = packed; packed = packed->next; } - if (strncmp(base, entry->name, trim)) - continue; - if (is_null_sha1(entry->sha1)) - continue; - if (!has_sha1_file(entry->sha1)) { - error("%s does not point to a valid object!", entry->name); - continue; - } - retval = fn(entry->name + trim, entry->sha1, - entry->flag, cb_data); + retval = do_one_ref(base, fn, trim, cb_data, entry); if (retval) return retval; } - packed = packed ? packed : loose; - while (packed) { - if (!strncmp(base, packed->name, trim)) { - retval = fn(packed->name + trim, packed->sha1, - packed->flag, cb_data); - if (retval) - return retval; - } - packed = packed->next; + for (packed = packed ? packed : loose; packed; packed = packed->next) { + retval = do_one_ref(base, fn, trim, cb_data, packed); + if (retval) + return retval; } return 0; } -- cgit v0.10.2-6-g49f6 From cf0adba7885342e1bbcf0689fece9d13e39784b4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 19 Nov 2006 13:22:44 -0800 Subject: Store peeled refs in packed-refs file. This would speed up "show-ref -d" in a repository with mostly packed tags. Signed-off-by: Junio C Hamano diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 042d271..ee5a556 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -1,5 +1,7 @@ #include "cache.h" #include "refs.h" +#include "object.h" +#include "tag.h" static const char builtin_pack_refs_usage[] = "git-pack-refs [--all] [--prune]"; @@ -29,12 +31,26 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct pack_refs_cb_data *cb = cb_data; + int is_tag_ref; - if (!cb->all && strncmp(path, "refs/tags/", 10)) - return 0; /* Do not pack the symbolic refs */ - if (!(flags & REF_ISSYMREF)) - fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if ((flags & REF_ISSYMREF)) + return 0; + is_tag_ref = !strncmp(path, "refs/tags/", 10); + if (!cb->all && !is_tag_ref) + return 0; + + fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if (is_tag_ref) { + struct object *o = parse_object(sha1); + if (o->type == OBJ_TAG) { + o = deref_tag(o, path, 0); + if (o) + fprintf(cb->refs_file, "%s %s^{}\n", + sha1_to_hex(o->sha1), path); + } + } + if (cb->prune && !do_not_prune(flags)) { int namelen = strlen(path) + 1; struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 06ec400..9ae3d08 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -13,6 +13,7 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo { struct object *obj; const char *hex; + unsigned char peeled[20]; if (tags_only || heads_only) { int match; @@ -44,12 +45,15 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo match: found_match++; - obj = parse_object(sha1); - if (!obj) { - if (quiet) - return 0; - die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1)); - } + + /* This changes the semantics slightly that even under quiet we + * detect and return error if the repository is corrupt and + * ref points at a nonexistent object. + */ + if (!has_sha1_file(sha1)) + die("git-show-ref: bad ref %s (%s)", refname, + sha1_to_hex(sha1)); + if (quiet) return 0; @@ -58,11 +62,25 @@ match: printf("%s\n", hex); else printf("%s %s\n", hex, refname); - if (deref_tags && obj->type == OBJ_TAG) { - obj = deref_tag(obj, refname, 0); - hex = find_unique_abbrev(obj->sha1, abbrev); + + if (!deref_tags) + return 0; + + if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) { + hex = find_unique_abbrev(peeled, abbrev); printf("%s %s^{}\n", hex, refname); } + else { + obj = parse_object(sha1); + if (!obj) + die("git-show-ref: bad ref %s (%s)", refname, + sha1_to_hex(sha1)); + if (obj->type == OBJ_TAG) { + obj = deref_tag(obj, refname, 0); + hex = find_unique_abbrev(obj->sha1, abbrev); + printf("%s %s^{}\n", hex, refname); + } + } return 0; } diff --git a/refs.c b/refs.c index 0e156c5..75cbc0e 100644 --- a/refs.c +++ b/refs.c @@ -1,16 +1,18 @@ #include "refs.h" #include "cache.h" +#include "object.h" +#include "tag.h" #include struct ref_list { struct ref_list *next; - unsigned char flag; /* ISSYMREF? ISPACKED? */ + unsigned char flag; /* ISSYMREF? ISPACKED? ISPEELED? */ unsigned char sha1[20]; char name[FLEX_ARRAY]; }; -static const char *parse_ref_line(char *line, unsigned char *sha1) +static const char *parse_ref_line(char *line, unsigned char *sha1, int *flag) { /* * 42: the answer to everything. @@ -21,6 +23,7 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) * +1 (newline at the end of the line) */ int len = strlen(line) - 42; + int peeled = 0; if (len <= 0) return NULL; @@ -29,11 +32,24 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) if (!isspace(line[40])) return NULL; line += 41; - if (isspace(*line)) - return NULL; + + if (isspace(*line)) { + /* "SHA-1 SP SP refs/tags/tagname^{} LF"? */ + line++; + len--; + peeled = 1; + } if (line[len] != '\n') return NULL; line[len] = 0; + + if (peeled && (len < 3 || strcmp(line + len - 3, "^{}"))) + return NULL; + + if (!peeled) + *flag &= ~REF_ISPEELED; + else + *flag |= REF_ISPEELED; return line; } @@ -108,10 +124,12 @@ static struct ref_list *get_packed_refs(void) char refline[PATH_MAX]; while (fgets(refline, sizeof(refline), f)) { unsigned char sha1[20]; - const char *name = parse_ref_line(refline, sha1); + int flag = REF_ISPACKED; + const char *name = + parse_ref_line(refline, sha1, &flag); if (!name) continue; - list = add_ref(name, sha1, REF_ISPACKED, list); + list = add_ref(name, sha1, flag, list); } fclose(f); refs = list; @@ -207,7 +225,8 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * if (lstat(path, &st) < 0) { struct ref_list *list = get_packed_refs(); while (list) { - if (!strcmp(ref, list->name)) { + if (!(list->flag & REF_ISPEELED) && + !strcmp(ref, list->name)) { hashcpy(sha1, list->sha1); if (flag) *flag |= REF_ISPACKED; @@ -329,6 +348,8 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return 0; if (is_null_sha1(entry->sha1)) return 0; + if (entry->flag & REF_ISPEELED) + return 0; if (!has_sha1_file(entry->sha1)) { error("%s does not point to a valid object!", entry->name); return 0; @@ -336,6 +357,44 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); } +int peel_ref(const char *ref, unsigned char *sha1) +{ + int flag; + unsigned char base[20]; + struct object *o; + + if (!resolve_ref(ref, base, 1, &flag)) + return -1; + + if ((flag & REF_ISPACKED)) { + struct ref_list *list = get_packed_refs(); + int len = strlen(ref); + + while (list) { + if ((list->flag & REF_ISPEELED) && + !strncmp(list->name, ref, len) && + strlen(list->name) == len + 3 && + !strcmp(list->name + len, "^{}")) { + hashcpy(sha1, list->sha1); + return 0; + } + list = list->next; + } + /* older pack-refs did not leave peeled ones in */ + } + + /* otherwise ... */ + o = parse_object(base); + if (o->type == OBJ_TAG) { + o = deref_tag(o, ref, 0); + if (o) { + hashcpy(sha1, o->sha1); + return 0; + } + } + return -1; +} + static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_data) { diff --git a/refs.h b/refs.h index a57d437..40048a6 100644 --- a/refs.h +++ b/refs.h @@ -16,6 +16,8 @@ struct ref_lock { */ #define REF_ISSYMREF 01 #define REF_ISPACKED 02 +#define REF_ISPEELED 04 /* internal use */ + typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); @@ -23,6 +25,8 @@ extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); +extern int peel_ref(const char *, unsigned char *); + /** Reads the refs file specified into sha1 **/ extern int get_ref_sha1(const char *ref, unsigned char *sha1); -- cgit v0.10.2-6-g49f6 From b8ec59234ba2c1833e29eece9ed87f7a471cbae2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 22 Oct 2006 13:23:31 +0200 Subject: Build in shortlog [jc: with minimum squelching of compiler warning under "-pedantic" compilation options.] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index d54fc3e..95fa901 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -8,6 +8,7 @@ git-shortlog - Summarize 'git log' output SYNOPSIS -------- git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] +git-shortlog [-n|--number] [-s|--summary] [...] DESCRIPTION ----------- diff --git a/Makefile b/Makefile index 36ce8cd..6fd28b0 100644 --- a/Makefile +++ b/Makefile @@ -174,7 +174,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-shortlog.perl git-rerere.perl \ + git-rerere.perl \ git-cvsserver.perl \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl @@ -300,6 +300,7 @@ BUILTIN_OBJS = \ builtin-rev-parse.o \ builtin-rm.o \ builtin-runstatus.o \ + builtin-shortlog.o \ builtin-show-branch.o \ builtin-stripspace.o \ builtin-symbolic-ref.o \ diff --git a/builtin-shortlog.c b/builtin-shortlog.c new file mode 100644 index 0000000..48a2a0b --- /dev/null +++ b/builtin-shortlog.c @@ -0,0 +1,302 @@ +#include "builtin.h" +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "path-list.h" +#include "revision.h" +#include + +static const char shortlog_usage[] = +"git-shortlog [-n] [-s] [... ]\n"; + +static int compare_by_number(const void *a1, const void *a2) +{ + const struct path_list_item *i1 = a1, *i2 = a2; + const struct path_list *l1 = i1->util, *l2 = i2->util; + + if (l1->nr < l2->nr) + return -1; + else if (l1->nr == l2->nr) + return 0; + else + return +1; +} + +static struct path_list_item mailmap_list[] = { + { "R.Marek@sh.cvut.cz", (void*)"Rudolf Marek" }, + { "Ralf.Wildenhues@gmx.de", (void*)"Ralf Wildenhues" }, + { "aherrman@de.ibm.com", (void*)"Andreas Herrmann" }, + { "akpm@osdl.org", (void*)"Andrew Morton" }, + { "andrew.vasquez@qlogic.com", (void*)"Andrew Vasquez" }, + { "aquynh@gmail.com", (void*)"Nguyen Anh Quynh" }, + { "axboe@suse.de", (void*)"Jens Axboe" }, + { "blaisorblade@yahoo.it", (void*)"Paolo 'Blaisorblade' Giarrusso" }, + { "bunk@stusta.de", (void*)"Adrian Bunk" }, + { "domen@coderock.org", (void*)"Domen Puncer" }, + { "dougg@torque.net", (void*)"Douglas Gilbert" }, + { "dwmw2@shinybook.infradead.org", (void*)"David Woodhouse" }, + { "ecashin@coraid.com", (void*)"Ed L Cashin" }, + { "felix@derklecks.de", (void*)"Felix Moeller" }, + { "fzago@systemfabricworks.com", (void*)"Frank Zago" }, + { "gregkh@suse.de", (void*)"Greg Kroah-Hartman" }, + { "hch@lst.de", (void*)"Christoph Hellwig" }, + { "htejun@gmail.com", (void*)"Tejun Heo" }, + { "jejb@mulgrave.(none)", (void*)"James Bottomley" }, + { "jejb@titanic.il.steeleye.com", (void*)"James Bottomley" }, + { "jgarzik@pretzel.yyz.us", (void*)"Jeff Garzik" }, + { "johnpol@2ka.mipt.ru", (void*)"Evgeniy Polyakov" }, + { "kay.sievers@vrfy.org", (void*)"Kay Sievers" }, + { "minyard@acm.org", (void*)"Corey Minyard" }, + { "mshah@teja.com", (void*)"Mitesh shah" }, + { "pj@ludd.ltu.se", (void*)"Peter A Jonsson" }, + { "rmps@joel.ist.utl.pt", (void*)"Rui Saraiva" }, + { "santtu.hyrkko@gmail.com", (void*)"Santtu Hyrkk,Av(B" }, + { "simon@thekelleys.org.uk", (void*)"Simon Kelley" }, + { "ssant@in.ibm.com", (void*)"Sachin P Sant" }, + { "terra@gnome.org", (void*)"Morten Welinder" }, + { "tony.luck@intel.com", (void*)"Tony Luck" }, + { "welinder@anemone.rentec.com", (void*)"Morten Welinder" }, + { "welinder@darter.rentec.com", (void*)"Morten Welinder" }, + { "welinder@troll.com", (void*)"Morten Welinder" } +}; + +static struct path_list mailmap = { + mailmap_list, + sizeof(mailmap_list) / sizeof(struct path_list_item), 0, 0 +}; + +static int map_email(char *email, char *name, int maxlen) +{ + char *p; + struct path_list_item *item; + + /* autocomplete common developers */ + p = strchr(email, '>'); + if (!p) + return 0; + + *p = '\0'; + item = path_list_lookup(email, &mailmap); + if (item != NULL) { + const char *realname = (const char *)item->util; + strncpy(name, realname, maxlen); + return 1; + } + return 0; +} + +static void insert_author_oneline(struct path_list *list, + const char *author, int authorlen, + const char *oneline, int onelinelen) +{ + const char *dot3 = "/pub/scm/linux/kernel/git/"; + char *buffer, *p; + struct path_list_item *item; + struct path_list *onelines; + + while (authorlen > 0 && isspace(author[authorlen - 1])) + authorlen--; + + buffer = xmalloc(authorlen + 1); + memcpy(buffer, author, authorlen); + buffer[authorlen] = '\0'; + + item = path_list_insert(buffer, list); + if (item->util == NULL) + item->util = xcalloc(1, sizeof(struct path_list)); + else + free(buffer); + + if (!strncmp(oneline, "[PATCH", 6)) { + char *eob = strchr(buffer, ']'); + + while (isspace(eob[1]) && eob[1] != '\n') + eob++; + if (eob - oneline < onelinelen) { + onelinelen -= eob - oneline; + oneline = eob; + } + } + + while (onelinelen > 0 && isspace(oneline[0])) { + oneline++; + onelinelen--; + } + + while (onelinelen > 0 && isspace(oneline[onelinelen - 1])) + onelinelen--; + + buffer = xmalloc(onelinelen + 1); + memcpy(buffer, oneline, onelinelen); + buffer[onelinelen] = '\0'; + + while ((p = strstr(buffer, dot3)) != NULL) { + memcpy(p, "...", 3); + strcpy(p + 2, p + sizeof(dot3) - 1); + } + + + onelines = item->util; + if (onelines->nr >= onelines->alloc) { + onelines->alloc = alloc_nr(onelines->nr); + onelines->items = xrealloc(onelines->items, + onelines->alloc + * sizeof(struct path_list_item)); + } + + onelines->items[onelines->nr].util = NULL; + onelines->items[onelines->nr++].path = buffer; +} + +static void read_from_stdin(struct path_list *list) +{ + char buffer[1024]; + + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + char *bob; + if ((buffer[0] == 'A' || buffer[0] == 'a') && + !strncmp(buffer + 1, "uthor: ", 7) && + (bob = strchr(buffer + 7, '<')) != NULL) { + char buffer2[1024], offset = 0; + + if (map_email(bob + 1, buffer, sizeof(buffer))) + bob = buffer + strlen(buffer); + else { + offset = 8; + while (isspace(bob[-1])) + bob--; + } + + while (fgets(buffer2, sizeof(buffer2), stdin) && + buffer2[0] != '\n') + ; /* chomp input */ + if (fgets(buffer2, sizeof(buffer2), stdin)) + insert_author_oneline(list, + buffer + offset, + bob - buffer - offset, + buffer2, strlen(buffer2)); + } + } +} + +static void get_from_rev(struct rev_info *rev, struct path_list *list) +{ + char scratch[1024]; + struct commit *commit; + + prepare_revision_walk(rev); + while ((commit = get_revision(rev)) != NULL) { + char *author = NULL, *oneline, *buffer; + int authorlen = authorlen, onelinelen; + + /* get author and oneline */ + for (buffer = commit->buffer; buffer && *buffer != '\0' && + *buffer != '\n'; ) { + char *eol = strchr(buffer, '\n'); + + if (eol == NULL) + eol = buffer + strlen(buffer); + else + eol++; + + if (!strncmp(buffer, "author ", 7)) { + char *bracket = strchr(buffer, '<'); + + if (bracket == NULL || bracket > eol) + die("Invalid commit buffer: %s", + sha1_to_hex(commit->object.sha1)); + + if (map_email(bracket + 1, scratch, + sizeof(scratch))) { + author = scratch; + authorlen = strlen(scratch); + } else { + while (bracket[-1] == ' ') + bracket--; + + author = buffer + 7; + authorlen = bracket - buffer - 7; + } + } + buffer = eol; + } + + if (author == NULL) + die ("Missing author: %s", + sha1_to_hex(commit->object.sha1)); + + if (buffer == NULL || *buffer == '\0') { + oneline = ""; + onelinelen = sizeof(oneline) + 1; + } else { + char *eol; + + oneline = buffer + 1; + eol = strchr(oneline, '\n'); + if (eol == NULL) + onelinelen = strlen(oneline); + else + onelinelen = eol - oneline; + } + + insert_author_oneline(list, + author, authorlen, oneline, onelinelen); + } + +} + +int cmd_shortlog(int argc, const char **argv, const char *prefix) +{ + struct rev_info rev; + struct path_list list = { NULL, 0, 0, 1 }; + int i, j, sort_by_number = 0, summary = 0; + + init_revisions(&rev, prefix); + argc = setup_revisions(argc, argv, &rev, NULL); + while (argc > 1) { + if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered")) + sort_by_number = 1; + else if (!strcmp(argv[1], "-s") || + !strcmp(argv[1], "--summary")) + summary = 1; + else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) + usage(shortlog_usage); + else + die ("unrecognized argument: %s", argv[1]); + argv++; + argc--; + } + + if (rev.pending.nr == 1) + die ("Need a range!"); + else if (rev.pending.nr == 0) + read_from_stdin(&list); + else + get_from_rev(&rev, &list); + + if (sort_by_number) + qsort(list.items, sizeof(struct path_list_item), list.nr, + compare_by_number); + + for (i = 0; i < list.nr; i++) { + struct path_list *onelines = list.items[i].util; + + printf("%s (%d):\n", list.items[i].path, onelines->nr); + if (!summary) { + for (j = onelines->nr - 1; j >= 0; j--) + printf(" %s\n", onelines->items[j].path); + printf("\n"); + } + + onelines->strdup_paths = 1; + path_list_clear(onelines, 1); + free(onelines); + list.items[i].util = NULL; + } + + list.strdup_paths = 1; + path_list_clear(&list, 1); + + return 0; +} + diff --git a/builtin.h b/builtin.h index 43fed32..b5116f3 100644 --- a/builtin.h +++ b/builtin.h @@ -55,6 +55,7 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_runstatus(int argc, const char **argv, const char *prefix); +extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); diff --git a/git-shortlog.perl b/git-shortlog.perl deleted file mode 100755 index 334fec7..0000000 --- a/git-shortlog.perl +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use Getopt::Std; -use File::Basename qw(basename dirname); - -our ($opt_h, $opt_n, $opt_s); -getopts('hns'); - -$opt_h && usage(); - -sub usage { - print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < \n"; - exit(1); -} - -my (%mailmap); -my (%email); -my (%map); -my $pstate = 1; -my $n_records = 0; -my $n_output = 0; - -sub shortlog_entry($$) { - my ($name, $desc) = @_; - my $key = $name; - - $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g; - $desc =~ s#\[PATCH\] ##g; - - # store description in array, in email->{desc list} map - if (exists $map{$key}) { - # grab ref - my $obj = $map{$key}; - - # add desc to array - push(@$obj, $desc); - } else { - # create new array, containing 1 item - my @arr = ($desc); - - # store ref to array - $map{$key} = \@arr; - } -} - -# sort comparison function -sub by_name($$) { - my ($a, $b) = @_; - - uc($a) cmp uc($b); -} -sub by_nbentries($$) { - my ($a, $b) = @_; - my $a_entries = $map{$a}; - my $b_entries = $map{$b}; - - @$b_entries - @$a_entries || by_name $a, $b; -} - -my $sort_method = $opt_n ? \&by_nbentries : \&by_name; - -sub summary_output { - my ($obj, $num, $key); - - foreach $key (sort $sort_method keys %map) { - $obj = $map{$key}; - $num = @$obj; - printf "%s: %u\n", $key, $num; - $n_output += $num; - } -} - -sub shortlog_output { - my ($obj, $num, $key, $desc); - - foreach $key (sort $sort_method keys %map) { - $obj = $map{$key}; - $num = @$obj; - - # output author - printf "%s (%u):\n", $key, $num; - - # output author's 1-line summaries - foreach $desc (reverse @$obj) { - print " $desc\n"; - $n_output++; - } - - # blank line separating author from next author - print "\n"; - } -} - -sub changelog_input { - my ($author, $desc); - - while (<>) { - # get author and email - if ($pstate == 1) { - my ($email); - - next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/; - - $n_records++; - - $author = $1; - $email = $2; - $desc = undef; - - # cset author fixups - if (exists $mailmap{$email}) { - $author = $mailmap{$email}; - } elsif (exists $mailmap{$author}) { - $author = $mailmap{$author}; - } elsif (!$author) { - $author = $email; - } - $email{$author}{$email}++; - $pstate++; - } - - # skip to blank line - elsif ($pstate == 2) { - next unless /^\s*$/; - $pstate++; - } - - # skip to non-blank line - elsif ($pstate == 3) { - next unless /^\s*?(.*)/; - - # skip lines that are obviously not - # a 1-line cset description - next if /^\s*From: /; - - chomp; - $desc = $1; - - &shortlog_entry($author, $desc); - - $pstate = 1; - } - - else { - die "invalid parse state $pstate"; - } - } -} - -sub read_mailmap { - my ($fh, $mailmap) = @_; - while (<$fh>) { - chomp; - if (/^([^#].*?)\s*<(.*)>/) { - $mailmap->{$2} = $1; - } - } -} - -sub setup_mailmap { - read_mailmap(\*DATA, \%mailmap); - if (-f '.mailmap') { - my $fh = undef; - open $fh, '<', '.mailmap'; - read_mailmap($fh, \%mailmap); - close $fh; - } -} - -sub finalize { - #print "\n$n_records records parsed.\n"; - - if ($n_records != $n_output) { - die "parse error: input records != output records\n"; - } - if (0) { - for my $author (sort keys %email) { - my $e = $email{$author}; - for my $email (sort keys %$e) { - print STDERR "$author <$email>\n"; - } - } - } -} - -&setup_mailmap; -&changelog_input; -$opt_s ? &summary_output : &shortlog_output; -&finalize; -exit(0); - - -__DATA__ -# -# Even with git, we don't always have name translations. -# So have an email->real name table to translate the -# (hopefully few) missing names -# -Adrian Bunk -Andreas Herrmann -Andrew Morton -Andrew Vasquez -Christoph Hellwig -Corey Minyard -David Woodhouse -Domen Puncer -Douglas Gilbert -Ed L Cashin -Evgeniy Polyakov -Felix Moeller -Frank Zago -Greg Kroah-Hartman -James Bottomley -James Bottomley -Jeff Garzik -Jens Axboe -Kay Sievers -Mitesh shah -Morten Welinder -Morten Welinder -Morten Welinder -Morten Welinder -Nguyen Anh Quynh -Paolo 'Blaisorblade' Giarrusso -Peter A Jonsson -Ralf Wildenhues -Rudolf Marek -Rui Saraiva -Sachin P Sant -Santtu Hyrkk,Av(B -Simon Kelley -Tejun Heo -Tony Luck diff --git a/git.c b/git.c index 1aa07a5..f97de60 100644 --- a/git.c +++ b/git.c @@ -260,6 +260,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "rev-parse", cmd_rev_parse, RUN_SETUP }, { "rm", cmd_rm, RUN_SETUP }, { "runstatus", cmd_runstatus, RUN_SETUP }, + { "shortlog", cmd_shortlog, RUN_SETUP }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, -- cgit v0.10.2-6-g49f6 From 72019cdefeb6b8fd7e8bff37b9c087302a45e29e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 19 Nov 2006 17:28:25 +0100 Subject: shortlog: do not crash on parsing "[PATCH" Annoyingly, it looked for the closing bracket in the author name instead of in the message, and then accessed the NULL pointer. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 48a2a0b..26212b0 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -108,13 +108,15 @@ static void insert_author_oneline(struct path_list *list, free(buffer); if (!strncmp(oneline, "[PATCH", 6)) { - char *eob = strchr(buffer, ']'); - - while (isspace(eob[1]) && eob[1] != '\n') - eob++; - if (eob - oneline < onelinelen) { - onelinelen -= eob - oneline; - oneline = eob; + char *eob = strchr(oneline, ']'); + + if (eob) { + while (isspace(eob[1]) && eob[1] != '\n') + eob++; + if (eob - oneline < onelinelen) { + onelinelen -= eob - oneline; + oneline = eob; + } } } -- cgit v0.10.2-6-g49f6 From d8e812502f1c72f5ef542de7eb05874e27f2b086 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 19 Nov 2006 17:28:51 +0100 Subject: shortlog: read mailmap from ./.mailmap again While at it, remove the linux specific mailmap into contrib/mailmap.linux. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 26212b0..afc9456 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -22,48 +22,40 @@ static int compare_by_number(const void *a1, const void *a2) return +1; } -static struct path_list_item mailmap_list[] = { - { "R.Marek@sh.cvut.cz", (void*)"Rudolf Marek" }, - { "Ralf.Wildenhues@gmx.de", (void*)"Ralf Wildenhues" }, - { "aherrman@de.ibm.com", (void*)"Andreas Herrmann" }, - { "akpm@osdl.org", (void*)"Andrew Morton" }, - { "andrew.vasquez@qlogic.com", (void*)"Andrew Vasquez" }, - { "aquynh@gmail.com", (void*)"Nguyen Anh Quynh" }, - { "axboe@suse.de", (void*)"Jens Axboe" }, - { "blaisorblade@yahoo.it", (void*)"Paolo 'Blaisorblade' Giarrusso" }, - { "bunk@stusta.de", (void*)"Adrian Bunk" }, - { "domen@coderock.org", (void*)"Domen Puncer" }, - { "dougg@torque.net", (void*)"Douglas Gilbert" }, - { "dwmw2@shinybook.infradead.org", (void*)"David Woodhouse" }, - { "ecashin@coraid.com", (void*)"Ed L Cashin" }, - { "felix@derklecks.de", (void*)"Felix Moeller" }, - { "fzago@systemfabricworks.com", (void*)"Frank Zago" }, - { "gregkh@suse.de", (void*)"Greg Kroah-Hartman" }, - { "hch@lst.de", (void*)"Christoph Hellwig" }, - { "htejun@gmail.com", (void*)"Tejun Heo" }, - { "jejb@mulgrave.(none)", (void*)"James Bottomley" }, - { "jejb@titanic.il.steeleye.com", (void*)"James Bottomley" }, - { "jgarzik@pretzel.yyz.us", (void*)"Jeff Garzik" }, - { "johnpol@2ka.mipt.ru", (void*)"Evgeniy Polyakov" }, - { "kay.sievers@vrfy.org", (void*)"Kay Sievers" }, - { "minyard@acm.org", (void*)"Corey Minyard" }, - { "mshah@teja.com", (void*)"Mitesh shah" }, - { "pj@ludd.ltu.se", (void*)"Peter A Jonsson" }, - { "rmps@joel.ist.utl.pt", (void*)"Rui Saraiva" }, - { "santtu.hyrkko@gmail.com", (void*)"Santtu Hyrkk,Av(B" }, - { "simon@thekelleys.org.uk", (void*)"Simon Kelley" }, - { "ssant@in.ibm.com", (void*)"Sachin P Sant" }, - { "terra@gnome.org", (void*)"Morten Welinder" }, - { "tony.luck@intel.com", (void*)"Tony Luck" }, - { "welinder@anemone.rentec.com", (void*)"Morten Welinder" }, - { "welinder@darter.rentec.com", (void*)"Morten Welinder" }, - { "welinder@troll.com", (void*)"Morten Welinder" } -}; - -static struct path_list mailmap = { - mailmap_list, - sizeof(mailmap_list) / sizeof(struct path_list_item), 0, 0 -}; +static struct path_list mailmap = {NULL, 0, 0, 0}; + +static int read_mailmap(const char *filename) +{ + char buffer[1024]; + FILE *f = fopen(filename, "r"); + + if (f == NULL) + return 1; + while (fgets(buffer, sizeof(buffer), f) != NULL) { + char *end_of_name, *left_bracket, *right_bracket; + char *name, *email; + if (buffer[0] == '#') + continue; + if ((left_bracket = strchr(buffer, '<')) == NULL) + continue; + if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL) + continue; + if (right_bracket == left_bracket + 1) + continue; + for (end_of_name = left_bracket; end_of_name != buffer + && isspace(end_of_name[-1]); end_of_name--) + /* keep on looking */ + if (end_of_name == buffer) + continue; + name = xmalloc(end_of_name - buffer + 1); + strlcpy(name, buffer, end_of_name - buffer + 1); + email = xmalloc(right_bracket - left_bracket); + strlcpy(email, left_bracket + 1, right_bracket - left_bracket); + path_list_insert(email, &mailmap)->util = name; + } + fclose(f); + return 0; +} static int map_email(char *email, char *name, int maxlen) { @@ -269,6 +261,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) argc--; } + if (!access(".mailmap", R_OK)) + read_mailmap(".mailmap"); + if (rev.pending.nr == 1) die ("Need a range!"); else if (rev.pending.nr == 0) @@ -298,6 +293,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) list.strdup_paths = 1; path_list_clear(&list, 1); + mailmap.strdup_paths = 1; + path_list_clear(&mailmap, 1); return 0; } diff --git a/contrib/mailmap.linux b/contrib/mailmap.linux new file mode 100644 index 0000000..83927c9 --- /dev/null +++ b/contrib/mailmap.linux @@ -0,0 +1,40 @@ +# +# Even with git, we don't always have name translations. +# So have an email->real name table to translate the +# (hopefully few) missing names +# +Adrian Bunk +Andreas Herrmann +Andrew Morton +Andrew Vasquez +Christoph Hellwig +Corey Minyard +David Woodhouse +Domen Puncer +Douglas Gilbert +Ed L Cashin +Evgeniy Polyakov +Felix Moeller +Frank Zago +Greg Kroah-Hartman +James Bottomley +James Bottomley +Jeff Garzik +Jens Axboe +Kay Sievers +Mitesh shah +Morten Welinder +Morten Welinder +Morten Welinder +Morten Welinder +Nguyen Anh Quynh +Paolo 'Blaisorblade' Giarrusso +Peter A Jonsson +Ralf Wildenhues +Rudolf Marek +Rui Saraiva +Sachin P Sant +Santtu Hyrkk,Av(B +Simon Kelley +Tejun Heo +Tony Luck -- cgit v0.10.2-6-g49f6 From 549652361b7fea5a5e9046571c9f0bc4a7d5d6ef Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 19 Nov 2006 17:29:14 +0100 Subject: shortlog: handle email addresses case-insensitively Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index afc9456..4775c11 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -34,6 +34,7 @@ static int read_mailmap(const char *filename) while (fgets(buffer, sizeof(buffer), f) != NULL) { char *end_of_name, *left_bracket, *right_bracket; char *name, *email; + int i; if (buffer[0] == '#') continue; if ((left_bracket = strchr(buffer, '<')) == NULL) @@ -50,7 +51,9 @@ static int read_mailmap(const char *filename) name = xmalloc(end_of_name - buffer + 1); strlcpy(name, buffer, end_of_name - buffer + 1); email = xmalloc(right_bracket - left_bracket); - strlcpy(email, left_bracket + 1, right_bracket - left_bracket); + for (i = 0; i < right_bracket - left_bracket - 1; i++) + email[i] = tolower(left_bracket[i + 1]); + email[right_bracket - left_bracket - 1] = '\0'; path_list_insert(email, &mailmap)->util = name; } fclose(f); @@ -68,6 +71,9 @@ static int map_email(char *email, char *name, int maxlen) return 0; *p = '\0'; + /* downcase the email address */ + for (p = email; *p; p++) + *p = tolower(*p); item = path_list_lookup(email, &mailmap); if (item != NULL) { const char *realname = (const char *)item->util; -- cgit v0.10.2-6-g49f6 From 69945602f95710f16ab4a5f8edfee38dc65555fa Mon Sep 17 00:00:00 2001 From: Paolo Ciarrocchi Date: Tue, 21 Nov 2006 19:55:20 +0100 Subject: Teach SubmittingPatches about git-commit -s As discussed on git mailing list let's teach the reader about the possiblity to have automatically signed off the commit running the git-commit -s command Signed-off-by: Paolo Ciarrocchi Signed-off-by: Junio C Hamano diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 8a3d316..646b6e7 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -149,6 +149,9 @@ then you just add a line saying Signed-off-by: Random J Developer +This line can be automatically added by git if you run the git-commit +command with the -s option. + Some people also put extra tags at the end. They'll just be ignored for now, but you can do this to mark internal company procedures or just point out some special detail about the sign-off. -- cgit v0.10.2-6-g49f6 From 6d6ab6104a5055b9f66cc9a80d55d2ef59d0763c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 21 Nov 2006 21:12:06 +0100 Subject: shortlog: fix "-n" Since it is now a builtin optionally taking a range, we have to parse the options before the rev machinery, to be able to shadow the short hand "-n" for "--max-count". Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 4775c11..1456e1a 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -15,11 +15,11 @@ static int compare_by_number(const void *a1, const void *a2) const struct path_list *l1 = i1->util, *l2 = i2->util; if (l1->nr < l2->nr) - return -1; + return 1; else if (l1->nr == l2->nr) return 0; else - return +1; + return -1; } static struct path_list mailmap = {NULL, 0, 0, 0}; @@ -251,8 +251,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) struct path_list list = { NULL, 0, 0, 1 }; int i, j, sort_by_number = 0, summary = 0; - init_revisions(&rev, prefix); - argc = setup_revisions(argc, argv, &rev, NULL); + /* since -n is a shadowed rev argument, parse our args first */ while (argc > 1) { if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered")) sort_by_number = 1; @@ -262,10 +261,14 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) usage(shortlog_usage); else - die ("unrecognized argument: %s", argv[1]); + break; argv++; argc--; } + init_revisions(&rev, prefix); + argc = setup_revisions(argc, argv, &rev, NULL); + if (argc > 1) + die ("unrecognized argument: %s", argv[1]); if (!access(".mailmap", R_OK)) read_mailmap(".mailmap"); @@ -278,7 +281,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) get_from_rev(&rev, &list); if (sort_by_number) - qsort(list.items, sizeof(struct path_list_item), list.nr, + qsort(list.items, list.nr, sizeof(struct path_list_item), compare_by_number); for (i = 0; i < list.nr; i++) { -- cgit v0.10.2-6-g49f6 From ac60c94d74ff3341a5175ca865fd52a0a0189146 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 21 Nov 2006 15:49:45 -0500 Subject: builtin git-shortlog is broken Another small patch to fix the output result to be conform with the perl version. Signed-off-by: Nicolas Pitre Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 1456e1a..b760b47 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -7,7 +7,7 @@ #include static const char shortlog_usage[] = -"git-shortlog [-n] [-s] [... ]\n"; +"git-shortlog [-n] [-s] [... ]"; static int compare_by_number(const void *a1, const void *a2) { @@ -287,8 +287,10 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) for (i = 0; i < list.nr; i++) { struct path_list *onelines = list.items[i].util; - printf("%s (%d):\n", list.items[i].path, onelines->nr); - if (!summary) { + if (summary) { + printf("%s: %d\n", list.items[i].path, onelines->nr); + } else { + printf("%s (%d):\n", list.items[i].path, onelines->nr); for (j = onelines->nr - 1; j >= 0; j--) printf(" %s\n", onelines->items[j].path); printf("\n"); -- cgit v0.10.2-6-g49f6 From 04408c3578300ac7cc1b1530bbaec54de299b200 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Nov 2006 23:35:38 +0100 Subject: gitweb: Protect against possible warning in git_commitdiff We may read an undef from <$fd> and unconditionally chomping it would result in a warning. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7587595..b2482fe 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3731,7 +3731,8 @@ sub git_commitdiff { $hash_parent, $hash, "--" 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 last unless $line; push @difftree, $line; -- cgit v0.10.2-6-g49f6 From 6d55f05576851aedc7c53f7438c1d505c22ee78f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Nov 2006 23:35:39 +0100 Subject: gitweb: Buffer diff header to deal with split patches + git_patchset_body refactoring There are some cases when one line from "raw" git-diff output (raw format) corresponds to more than one patch in the patchset git-diff output. To deal with this buffer git diff header and extended diff header (everything up to actual patch) to check from information from "index .." extended header line if the patch corresponds to the same or next difftree raw line. This could also be used to gather information needed for hyperlinking, and used for printing gitweb quoted filenames, from extended diff header instead of raw git-diff output. While at it, refactor git_patchset_body subroutine from the event-driven, AWK-like state-machine parsing to sequential parsing: for each patch parse (and output) git diff header, parse extended diff header, parse two-line from-file/to-file diff header, parse patch itself; patch ends with the end of input [file] or the line matching m/^diff /. For better understanding the code, there were added assertions in the comments a la Carp::Assert module. Just in case there is commented out code dealing with unexpected end of input (should not happen, hence commented out). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b2482fe..bf58c3b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2202,31 +2202,56 @@ sub git_patchset_body { my ($fd, $difftree, $hash, $hash_parent) = @_; my $patch_idx = 0; - my $in_header = 0; - my $patch_found = 0; + my $patch_line; my $diffinfo; my (%from, %to); + my ($from_id, $to_id); print "
\n"; - LINE: - while (my $patch_line = <$fd>) { + # skip to first patch + while ($patch_line = <$fd>) { chomp $patch_line; - if ($patch_line =~ m/^diff /) { # "git diff" header - # beginning of patch (in patchset) - if ($patch_found) { - # close extended header for previous empty patch - if ($in_header) { - print "
\n" # class="diff extended_header" - } - # close previous patch - print "
\n"; # class="patch" - } else { - # first patch in patchset - $patch_found = 1; + last if ($patch_line =~ m/^diff /); + } + + 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; } - print "
\n"; + + 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 "
\n"; + } else { + # advance raw git-diff output if needed + $patch_idx++ if defined $diffinfo; # read and prepare patch information if (ref($difftree->[$patch_idx]) eq "HASH") { @@ -2247,100 +2272,112 @@ sub git_patchset_body { hash=>$diffinfo->{'to_id'}, file_name=>$to{'file'}); } - $patch_idx++; - - # print "git diff" header - $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; - if ($from{'href'}) { - $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, - 'a/' . esc_path($from{'file'})); - } else { # file was added - $patch_line .= 'a/' . esc_path($from{'file'}); - } - $patch_line .= ' '; - if ($to{'href'}) { - $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, - 'b/' . esc_path($to{'file'})); - } else { # file was deleted - $patch_line .= 'b/' . esc_path($to{'file'}); - } - - print "
$patch_line
\n"; - print "
\n"; - $in_header = 1; - next LINE; + # this is first patch for raw difftree line with $patch_idx index + # we index @$difftree array from 0, but number patches from 1 + print "
\n"; } - if ($in_header) { - if ($patch_line !~ m/^---/) { - # match - if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { - $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, - esc_path($from{'file'})); - } - if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) { - $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"}, - esc_path($to{'file'})); - } - # match - if ($patch_line =~ m/\s(\d{6})$/) { - $patch_line .= ' (' . - file_type_long($1) . - ')'; + # print "git diff" header + $patch_line = shift @diff_header; + $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; + if ($from{'href'}) { + $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, + 'a/' . esc_path($from{'file'})); + } else { # file was added + $patch_line .= 'a/' . esc_path($from{'file'}); + } + $patch_line .= ' '; + if ($to{'href'}) { + $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, + 'b/' . esc_path($to{'file'})); + } else { # file was deleted + $patch_line .= 'b/' . esc_path($to{'file'}); + } + print "
$patch_line
\n"; + + # print extended diff header + print "
\n" if (@diff_header > 0); + EXTENDED_HEADER: + foreach $patch_line (@diff_header) { + # match + if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { + $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, + esc_path($from{'file'})); + } + if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) { + $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); + } + # match + if ($patch_line =~ m/\s(\d{6})$/) { + $patch_line .= ' (' . + file_type_long($1) . + ')'; + } + # match + if ($patch_line =~ m/^index/) { + my ($from_link, $to_link); + if ($from{'href'}) { + $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"}, + substr($diffinfo->{'from_id'},0,7)); + } else { + $from_link = '0' x 7; } - # match - if ($patch_line =~ m/^index/) { - my ($from_link, $to_link); - if ($from{'href'}) { - $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"}, - substr($diffinfo->{'from_id'},0,7)); - } else { - $from_link = '0' x 7; - } - if ($to{'href'}) { - $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"}, - substr($diffinfo->{'to_id'},0,7)); - } else { - $to_link = '0' x 7; - } - my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'}); - $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!; + if ($to{'href'}) { + $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"}, + substr($diffinfo->{'to_id'},0,7)); + } else { + $to_link = '0' x 7; } - print $patch_line . "
\n"; - - } else { - #$in_header && $patch_line =~ m/^---/; - print "
\n"; # class="diff extended_header" - $in_header = 0; + #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'}); + $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!; + } + print $patch_line . "
\n"; + } + print "
\n" if (@diff_header > 0); # class="diff extended_header" + + # from-file/to-file diff header + $patch_line = $last_patch_line; + #assert($patch_line =~ m/^---/) if DEBUG; + if ($from{'href'}) { + $patch_line = '--- a/' . + $cgi->a({-href=>$from{'href'}, -class=>"path"}, + esc_path($from{'file'})); + } + print "
$patch_line
\n"; - if ($from{'href'}) { - $patch_line = '--- a/' . - $cgi->a({-href=>$from{'href'}, -class=>"path"}, - esc_path($from{'file'})); - } - print "
$patch_line
\n"; + $patch_line = <$fd>; + #last PATCH unless $patch_line; + chomp $patch_line; - $patch_line = <$fd>; - chomp $patch_line; + #assert($patch_line =~ m/^+++/) if DEBUG; + if ($to{'href'}) { + $patch_line = '+++ b/' . + $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); + } + print "
$patch_line
\n"; - #$patch_line =~ m/^+++/; - if ($to{'href'}) { - $patch_line = '+++ b/' . - $cgi->a({-href=>$to{'href'}, -class=>"path"}, - esc_path($to{'file'})); - } - print "
$patch_line
\n"; + # the patch itself + LINE: + while ($patch_line = <$fd>) { + chomp $patch_line; - } + next PATCH if ($patch_line =~ m/^diff /); - next LINE; + print format_diff_line($patch_line); } - print format_diff_line($patch_line); + } continue { + print "
\n"; # class="patch" } - print "
\n" if $in_header; # extended header - - print "
\n" if $patch_found; # class="patch" print "
\n"; # class="patchset" } -- cgit v0.10.2-6-g49f6 From 9954f772eb73593d9e660e3b2c3f90341b03a087 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Nov 2006 23:35:41 +0100 Subject: gitweb: Default to $hash_base or HEAD for $hash in "commit" and "commitdiff" Set $hash parameter to $hash_base || "HEAD" if it is not set (if it is not true to be more exact). This allows [hand-edited] URLs with 'action' "commit" or "commitdiff" but without 'hash' parameter. If there is 'h' (hash) parameter provided, then gitweb tries to use this. HEAD is used _only_ if nether hash, nor hash_base are provided, i.e. for URL like below URL?p=project.git;a=commit i.e. without neither 'h' nor 'hb'. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index bf58c3b..19b3d36 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3429,6 +3429,7 @@ sub git_log { } sub git_commit { + $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); @@ -3706,6 +3707,7 @@ sub git_blobdiff_plain { sub git_commitdiff { my $format = shift || 'html'; + $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); -- cgit v0.10.2-6-g49f6 From 59e3b14e08bf309baa692bf251b2cedcde131308 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Nov 2006 23:35:40 +0100 Subject: gitweb: New improved formatting of chunk header in diff If we have provided enough info, and diff is not combined diff, and if provided diff line is chunk header, then: * split chunk header into .chunk_info and .section span elements, first containing proper chunk header, second section heading (aka. which function), for separate styling: the proper chunk header is on non-white background, section heading part uses slightly lighter color. * hyperlink from-file-range to starting line of from-file, if file was not created. * hyperlink to-file-range to starting line of to-file, if file was not deleted. Links are of invisible variety (and "list" class). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 974b47f..7177c6e 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -334,11 +334,13 @@ div.diff.extended_header { padding: 2px 0px 2px 0px; } +div.diff a.list, div.diff a.path, div.diff a.hash { text-decoration: none; } +div.diff a.list:hover, div.diff a.path:hover, div.diff a.hash:hover { text-decoration: underline; @@ -362,14 +364,25 @@ div.diff.rem { color: #cc0000; } +div.diff.chunk_header a, div.diff.chunk_header { color: #990099; +} +div.diff.chunk_header { border: dotted #ffe0ff; border-width: 1px 0px 0px 0px; margin-top: 2px; } +div.diff.chunk_header span.chunk_info { + background-color: #ffeeff; +} + +div.diff.chunk_header span.section { + color: #aa22aa; +} + div.diff.incomplete { color: #cccccc; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 19b3d36..5875ba0 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -874,8 +874,10 @@ sub format_subject_html { } } +# format patch (diff) line (rather not to be used for diff headers) sub format_diff_line { my $line = shift; + my ($from, $to) = @_; my $char = substr($line, 0, 1); my $diff_class = ""; @@ -891,6 +893,25 @@ sub format_diff_line { $diff_class = " incomplete"; } $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 = "@@ $from_text $to_text @@" . + "" . esc_html($section, -nbsp=>1) . ""; + return "
$line
\n"; + } return "
" . esc_html($line, -nbsp=>1) . "
\n"; } @@ -2372,7 +2393,7 @@ sub git_patchset_body { next PATCH if ($patch_line =~ m/^diff /); - print format_diff_line($patch_line); + print format_diff_line($patch_line, \%from, \%to); } } continue { -- cgit v0.10.2-6-g49f6 From bd5d1e42fb8edffa4e35d1257f648d586b2d054c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 19 Nov 2006 15:05:21 +0100 Subject: gitweb: Add an option to href() to return full URL href subroutine by default generates absolute URL (generated using CGI::url(-absolute=>1), and saved in $my_uri) using $my_uri as base; add an option to generate full URL using $my_url as base. New feature usage: href(..., -full=>1) Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5875ba0..8739501 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -459,7 +459,8 @@ exit; sub href(%) { 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, # too. -- cgit v0.10.2-6-g49f6 From af6feeb229110a0fa77a179c2655fca08e72f879 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 19 Nov 2006 15:05:22 +0100 Subject: gitweb: Refactor feed generation, make output prettier, add Atom feed Add support for more modern Atom web feed format. Both RSS and Atom feeds are generated by git_feed subroutine to avoid code duplication; git_rss and git_atom are thin wrappers around git_feed. Add links to Atom feed in HTML header and in page footer (but not in OPML; we should use APP, Atom Publishing Proptocol instead). Allow for feed generation for branches other than current (HEAD) branch, and for generation of feeds for file or directory history. Do not use "pre ${\sub_returning_scalar(...)} post" trick, but join strings instead: "pre " . sub_returning_scalar(...) . " post". Use href(-full=>1, ...) instead of hand-crafting gitweb urls. Make output prettier: * Use title similar to the title of web page * Use project description (if exists) for description/subtitle * Do not add anything (committer name, commit date) to feed entry title * Wrap the commit message in
* Make file names into an unordered list
* Add links (diff, conditional blame, history) to the file list.

In addition to the above points, the attached patch emits a
Last-Changed: HTTP response header field, and doesn't compute the feed
body if the HTTP request type was HEAD. This helps keep the web server
load down for well-behaved feed readers that check if the feed needs
updating.

If browser (feed reader) sent Accept: header, and it prefers 'text/xml' type
to 'application/rss+xml' (in the case of RSS feed) or 'application/atom+xml'
(in the case of Atom feed), then use 'text/xml' as content type.

Both RSS and Atom feeds validate at http://feedvalidator.org
and at http://validator.w3.org/feed/

Signed-off-by: Jakub Narebski 
Signed-off-by: Andreas Fuchs 
Signed-off-by: Junio C Hamano 

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 8739501..a32a6b7 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -425,6 +425,7 @@ my %actions = (
 	"history" => \&git_history,
 	"log" => \&git_log,
 	"rss" => \&git_rss,
+	"atom" => \&git_atom,
 	"search" => \&git_search,
 	"search_help" => \&git_search_help,
 	"shortlog" => \&git_shortlog,
@@ -1198,10 +1199,12 @@ sub parse_date {
 	$date{'mday'} = $mday;
 	$date{'day'} = $days[$wday];
 	$date{'month'} = $months[$mon];
-	$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
-	                   $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
+	$date{'rfc2822'}   = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
+	                     $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
 	$date{'mday-time'} = sprintf "%d %s %02d:%02d",
 	                     $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])$/;
 	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@@ -1209,9 +1212,9 @@ sub parse_date {
 	$date{'hour_local'} = $hour;
 	$date{'minute_local'} = $min;
 	$date{'tz_local'} = $tz;
-	$date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
-				   1900+$year, $mon+1, $mday,
-				   $hour, $min, $sec, $tz);
+	$date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
+	                          1900+$year, $mon+1, $mday,
+	                          $hour, $min, $sec, $tz);
 	return %date;
 }
 
@@ -1672,14 +1675,17 @@ EOF
 		}
 	}
 	if (defined $project) {
-		printf(''."\n",
+		printf(''."\n",
 		       esc_param($project), href(action=>"rss"));
+		printf(''."\n",
+		       esc_param($project), href(action=>"atom"));
 	} else {
 		printf(''."\n",
 		       $site_name, href(project=>undef, action=>"project_index"));
-		printf(''."\n",
 		       $site_name, href(project=>undef, action=>"opml"));
 	}
@@ -1745,7 +1751,9 @@ sub git_footer_html {
 			print "\n";
 		}
 		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 {
 		print $cgi->a({-href => href(project=>undef, action=>"opml"),
 		              -class => "rss_logo"}, "OPML") . " ";
@@ -4150,26 +4158,125 @@ sub git_shortlog {
 }
 
 ## ......................................................................
-## feeds (RSS, OPML)
+## feeds (RSS, Atom; OPML)
 
-sub git_rss {
-	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+sub git_feed {
+	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",
-		git_get_head_hash($project), "--"
+		$head, "--", (defined $file_name ? $file_name : ())
 		or die_error(undef, "Open git-rev-list failed");
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading git-rev-list failed");
-	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
-	print <
+
+	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!\n!;
+	if ($format eq 'rss') {
+		print <
 
-$project $my_uri $my_url
-${\esc_html("$my_url?p=$project;a=summary")}
-$project log
-en
 XML
+		print "$title\n" .
+		      "$alt_url\n" .
+		      "$descr\n" .
+		      "en\n";
+	} elsif ($format eq 'atom') {
+		print <
+XML
+		print "$title\n" .
+		      "$descr\n" .
+		      '' . "\n" .
+		      '' . "\n" .
+		      "" . href(-full=>1) . "\n" .
+		      # use project owner for feed author
+		      "$owner\n";
+		if (defined $favicon) {
+			print "" . esc_url($favicon) . "\n";
+		}
+		if (defined $logo_url) {
+			# not twice as wide as tall: 72 x 27 pixels
+			print "" . esc_url($logo_url) . "\n";
+		}
+		if (! %latest_date) {
+			# dummy date to keep the feed valid until commits trickle in:
+			print "1970-01-01T00:00:00Z\n";
+		} else {
+			print "$latest_date{'iso-8601'}\n";
+		}
+	}
 
+	# contents
 	for (my $i = 0; $i <= $#revlist; $i++) {
 		my $commit = $revlist[$i];
 		my %co = parse_commit($commit);
@@ -4178,42 +4285,100 @@ XML
 			last;
 		}
 		my %cd = parse_date($co{'committer_epoch'});
+
+		# get list of changed files
 		open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-			$co{'parent'}, $co{'id'}, "--"
+			$co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
 			or next;
 		my @difftree = map { chomp; $_ } <$fd>;
 		close $fd
 			or next;
-		print "\n" .
-		      "" .
-		      sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
-		      "\n" .
-		      "" . esc_html($co{'author'}) . "\n" .
-		      "$cd{'rfc2822'}\n" .
-		      "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" .
-		      "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" .
-		      "" . esc_html($co{'title'}) . "\n" .
-		      "" .
-		      "1, action=>"commit", hash=>$commit);
+		if ($format eq 'rss') {
+			print "\n" .
+			      "" . esc_html($co{'title'}) . "\n" .
+			      "" . esc_html($co{'author'}) . "\n" .
+			      "$cd{'rfc2822'}\n" .
+			      "$co_url\n" .
+			      "$co_url\n" .
+			      "" . esc_html($co{'title'}) . "\n" .
+			      "" .
+			      "\n" .
+			      "" . esc_html($co{'title'}) . "\n" .
+			      "$cd{'iso-8601'}\n" .
+			      "" . esc_html($co{'author_name'}) . "\n" .
+			      # use committer for contributor
+			      "" . esc_html($co{'committer_name'}) . "\n" .
+			      "$cd{'iso-8601'}\n" .
+			      "\n" .
+			      "$co_url\n" .
+			      "\n" .
+			      "
\n"; + } my $comment = $co{'comment'}; + print "
\n";
 		foreach my $line (@$comment) {
-			$line = to_utf8($line);
-			print "$line
\n"; + $line = esc_html($line); + print "$line\n"; } - print "
\n"; - foreach my $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(.*)$/)) { - next; + print "
    \n"; + foreach my $difftree_line (@difftree) { + my %difftree = parse_difftree_raw_line($difftree_line); + next if !$difftree{'from_id'}; + + my $file = $difftree{'file'} || $difftree{'to_file'}; + + print "
  • " . + "[" . + $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)); - $file = to_utf8($file); - print "$file
    \n"; + # if this is not a feed of a file history + if (!defined $file_name || $file_name ne $file) { + print $cgi->a({-href => href(-full=>1, action=>"history", + file_name=>$file, hash=>$commit), + -title => "history"}, 'H'); + } + $file = esc_path($file); + print "] ". + "$file
  • \n"; + } + if ($format eq 'rss') { + print "
]]>\n" . + "\n" . + "\n"; + } elsif ($format eq 'atom') { + print "\n
\n" . + "
\n" . + "\n"; } - print "]]>\n" . - "
\n" . - "
\n"; } - print "
"; + + # end of feed + if ($format eq 'rss') { + print "\n\n"; + } elsif ($format eq 'atom') { + print "\n"; + } +} + +sub git_rss { + git_feed('rss'); +} + +sub git_atom { + git_feed('atom'); } sub git_opml { -- cgit v0.10.2-6-g49f6 From 897d1d2e2a36d433c49d6246cd38133a540adddc Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 19 Nov 2006 22:51:39 +0100 Subject: gitweb: Finish restoring "blob" links in git_difftree_body This finishes work started by commit 4777b0141a4812177390da4b6ebc9d40ac3da4b5 "gitweb: Restore object-named links in item lists" by Petr Baudis. It brings back rest of "blob" links in difftree-raw like part of "commit" and "commitdiff" views, namely in git_difftree_body subroutine. Now the td.link table cell has the following links: * link to diff ("blobdiff" view) in "commit" view, if applicable (there is no link to uninteresting creation/deletion diff), or link to patch anchor in "commitdiff" view. * link to current version of file ("blob" view), with the obvious exception of file deletion, where it is link to the parent version. * link to "blame" view, if it is enabled, and file was not just created (i.e. it has any history). * link to history of the file ("history" view), again with sole exception of the case of new file. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a32a6b7..ce185d9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2092,7 +2092,11 @@ sub git_difftree_body { # link to patch $patchno++; 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 "\n"; } elsif ($diff{'status'} eq "D") { # deleted @@ -2112,13 +2116,11 @@ sub git_difftree_body { } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'})}, - "blob") . " | "; + "blob") . " | "; if ($have_blame) { - print $cgi->a({-href => - href(action=>"blame", - hash_base=>$parent, - file_name=>$diff{'file'})}, - "blame") . " | "; + print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, + file_name=>$diff{'file'})}, + "blame") . " | "; } print $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$diff{'file'})}, @@ -2163,13 +2165,12 @@ sub git_difftree_body { " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, - "blob") . " | "; + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob") . " | "; if ($have_blame) { - print $cgi->a({-href => href(action=>"blame", - hash_base=>$hash, - file_name=>$diff{'file'})}, - "blame") . " | "; + print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, + file_name=>$diff{'file'})}, + "blame") . " | "; } print $cgi->a({-href => href(action=>"history", hash_base=>$hash, file_name=>$diff{'file'})}, @@ -2208,17 +2209,16 @@ sub git_difftree_body { "diff") . " | "; } - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, - hash_base=>$parent, file_name=>$diff{'from_file'})}, - "blob") . " | "; + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$parent, file_name=>$diff{'to_file'})}, + "blob") . " | "; if ($have_blame) { - print $cgi->a({-href => href(action=>"blame", - hash_base=>$hash, - file_name=>$diff{'to_file'})}, - "blame") . " | "; + print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, + file_name=>$diff{'to_file'})}, + "blame") . " | "; } - print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'from_file'})}, + print $cgi->a({-href => href(action=>"history", hash_base=>$hash, + file_name=>$diff{'to_file'})}, "history"); print "\n"; -- cgit v0.10.2-6-g49f6 From aa9098611fb53e4e9d33757102f96145946811f4 Mon Sep 17 00:00:00 2001 From: Michal Rokos Date: Tue, 21 Nov 2006 23:19:28 +0100 Subject: archive: use setvbuf() instead of setlinebuf() This tiny patch makes GIT compile again on HP-UX 11i. [jc: The setlinebuf() is described as unportable to BSD before 4.2; it's not even in POSIX, while setvbuf() is in ISO C.] Signed-off-by: Michal Rokos Signed-off-by: Junio C Hamano diff --git a/builtin-archive.c b/builtin-archive.c index 2df1a84..a8a1f07 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -249,7 +249,7 @@ int cmd_archive(int argc, const char **argv, const char *prefix) if (remote) return run_remote_archiver(remote, argc, argv); - setlinebuf(stderr); + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); memset(&ar, 0, sizeof(ar)); tree_idx = parse_archive_args(argc, argv, &ar); -- cgit v0.10.2-6-g49f6 From 594270635732d6320d6e6d7fa127679ae90ef5b1 Mon Sep 17 00:00:00 2001 From: Paolo Ciarrocchi Date: Mon, 20 Nov 2006 21:29:41 +0100 Subject: Doc: Make comment about merging in tutorial.txt more clear Rephrased a sentence in order to make more clear the concept of pull . branch Signed-off-by: Paolo Ciarrocchi Signed-off-by: Junio C Hamano diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index 554ee0a..1e4ddfb 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -141,7 +141,7 @@ $ git commit -a ------------------------------------------------ at this point the two branches have diverged, with different changes -made in each. To merge the changes made in the two branches, run +made in each. To merge the changes made in experimental into master, run ------------------------------------------------ $ git pull . experimental -- cgit v0.10.2-6-g49f6 From 21f88ac84a024fcc73a22547e51509aa1459ab03 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Thu, 2 Nov 2006 12:12:26 +0100 Subject: Improve git-prune -n output prune_object() in show_only mode would previously just show the path to the object that would be deleted. The path the object is stored in shouldn't be shown to users, they only know about sha1 identifiers so show that instead. Further, the sha1 alone isn't that useful for examining what is going to be deleted. This patch also adds the object type to the output, which makes it easy to pick out, say, the commits and use git-show to display them. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/builtin-prune.c b/builtin-prune.c index d853902..286a94c 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -16,8 +16,14 @@ static struct rev_info revs; static int prune_object(char *path, const char *filename, const unsigned char *sha1) { + char buf[20]; + const char *type; + if (show_only) { - printf("would prune %s/%s\n", path, filename); + type = buf; + if (sha1_object_info(sha1, type, NULL)) + type = "unknown"; + printf("%s %s\n", sha1_to_hex(sha1), type ); return 0; } unlink(mkpath("%s/%s", path, filename)); -- cgit v0.10.2-6-g49f6 From 7cdbff14d4823c3a3d64c2011ab0b23f794efef8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 20 Nov 2006 00:49:31 -0800 Subject: remove merge-recursive-old This frees the Porcelain-ish that comes with the core Python-free. Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 4c8c8e4..7f2cd55 100644 --- a/.gitignore +++ b/.gitignore @@ -66,7 +66,6 @@ git-merge-one-file git-merge-ours git-merge-recur git-merge-recursive -git-merge-recursive-old git-merge-resolve git-merge-stupid git-mktag diff --git a/INSTALL b/INSTALL index fce6bc3..8f69039 100644 --- a/INSTALL +++ b/INSTALL @@ -99,9 +99,6 @@ Issues of note: - "perl" and POSIX-compliant shells are needed to use most of the barebone Porcelainish scripts. - - "python" 2.3 or more recent; if you have 2.3, you may need - to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease". - - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not have all the libraries/tools needed, or you may have diff --git a/Makefile b/Makefile index 36ce8cd..fc30dcb 100644 --- a/Makefile +++ b/Makefile @@ -69,8 +69,6 @@ all: # # Define NO_MMAP if you want to avoid mmap. # -# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. -# # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). # # Define NO_SOCKADDR_STORAGE if your platform does not have struct @@ -116,7 +114,6 @@ prefix = $(HOME) bindir = $(prefix)/bin gitexecdir = $(bindir) template_dir = $(prefix)/share/git-core/templates/ -GIT_PYTHON_DIR = $(prefix)/share/git-core/python # DESTDIR= # default configuration for gitweb @@ -135,7 +132,7 @@ GITWEB_FAVICON = git-favicon.png GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = -export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR +export prefix bindir gitexecdir template_dir CC = gcc AR = ar @@ -179,12 +176,8 @@ SCRIPT_PERL = \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl -SCRIPT_PYTHON = \ - git-merge-recursive-old.py - SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ - $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-cherry-pick git-status git-instaweb # ... and all the rest that could be moved out of bindir to gitexecdir @@ -227,12 +220,6 @@ endif ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif -ifndef PYTHON_PATH - PYTHON_PATH = /usr/bin/python -endif - -PYMODULES = \ - gitMergeCommon.py LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a @@ -423,16 +410,6 @@ endif -include config.mak.autogen -include config.mak -ifdef WITH_OWN_SUBPROCESS_PY - PYMODULES += compat/subprocess.py -else - ifeq ($(NO_PYTHON),) - ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK) - PYMODULES += compat/subprocess.py - endif - endif -endif - ifndef NO_CURL ifdef CURLDIR # This is still problematic -- gcc does not always want -R. @@ -574,8 +551,6 @@ prefix_SQ = $(subst ','\'',$(prefix)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) -PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) -GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR)) LIBS = $(GITLIBS) $(EXTLIBS) @@ -622,7 +597,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ $@.sh >$@+ chmod +x $@+ mv $@+ $@ @@ -644,15 +618,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl chmod +x $@+ mv $@+ $@ -$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py GIT-CFLAGS - rm -f $@ $@+ - sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ - -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \ - -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - $@.py >$@+ - chmod +x $@+ - mv $@+ $@ - git-cherry-pick: git-revert cp $< $@+ mv $@+ $@ @@ -689,7 +654,6 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \ -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ @@ -709,7 +673,6 @@ configure: configure.ac git$X git.spec \ $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ - $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ : GIT-VERSION-FILE %.o: %.c GIT-CFLAGS @@ -783,7 +746,7 @@ tags: find . -name '*.[hcS]' -print | xargs ctags -a ### Detect prefix changes -TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):$(GIT_PYTHON_DIR_SQ):\ +TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) GIT-CFLAGS: .FORCE-GIT-CFLAGS @@ -799,7 +762,6 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS # However, the environment gets quite big, and some programs have problems # with that. -export NO_PYTHON export NO_SVN_TESTS test: all @@ -834,8 +796,6 @@ install: all $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install $(MAKE) -C perl install - $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' - $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ then \ ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ @@ -922,7 +882,6 @@ check-docs:: case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ git-merge-resolve | git-merge-stupid | git-merge-recur | \ - git-merge-recursive-old | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/compat/subprocess.py b/compat/subprocess.py deleted file mode 100644 index 6474eab..0000000 --- a/compat/subprocess.py +++ /dev/null @@ -1,1149 +0,0 @@ -# subprocess - Subprocesses with accessible I/O streams -# -# For more information about this module, see PEP 324. -# -# This module should remain compatible with Python 2.2, see PEP 291. -# -# Copyright (c) 2003-2005 by Peter Astrand -# -# Licensed to PSF under a Contributor Agreement. -# See http://www.python.org/2.4/license for licensing details. - -r"""subprocess - Subprocesses with accessible I/O streams - -This module allows you to spawn processes, connect to their -input/output/error pipes, and obtain their return codes. This module -intends to replace several other, older modules and functions, like: - -os.system -os.spawn* -os.popen* -popen2.* -commands.* - -Information about how the subprocess module can be used to replace these -modules and functions can be found below. - - - -Using the subprocess module -=========================== -This module defines one class called Popen: - -class Popen(args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - - -Arguments are: - -args should be a string, or a sequence of program arguments. The -program to execute is normally the first item in the args sequence or -string, but can be explicitly set by using the executable argument. - -On UNIX, with shell=False (default): In this case, the Popen class -uses os.execvp() to execute the child program. args should normally -be a sequence. A string will be treated as a sequence with the string -as the only item (the program to execute). - -On UNIX, with shell=True: If args is a string, it specifies the -command string to execute through the shell. If args is a sequence, -the first item specifies the command string, and any additional items -will be treated as additional shell arguments. - -On Windows: the Popen class uses CreateProcess() to execute the child -program, which operates on strings. If args is a sequence, it will be -converted to a string using the list2cmdline method. Please note that -not all MS Windows applications interpret the command line the same -way: The list2cmdline is designed for applications using the same -rules as the MS C runtime. - -bufsize, if given, has the same meaning as the corresponding argument -to the built-in open() function: 0 means unbuffered, 1 means line -buffered, any other positive value means use a buffer of -(approximately) that size. A negative bufsize means to use the system -default, which usually means fully buffered. The default value for -bufsize is 0 (unbuffered). - -stdin, stdout and stderr specify the executed programs' standard -input, standard output and standard error file handles, respectively. -Valid values are PIPE, an existing file descriptor (a positive -integer), an existing file object, and None. PIPE indicates that a -new pipe to the child should be created. With None, no redirection -will occur; the child's file handles will be inherited from the -parent. Additionally, stderr can be STDOUT, which indicates that the -stderr data from the applications should be captured into the same -file handle as for stdout. - -If preexec_fn is set to a callable object, this object will be called -in the child process just before the child is executed. - -If close_fds is true, all file descriptors except 0, 1 and 2 will be -closed before the child process is executed. - -if shell is true, the specified command will be executed through the -shell. - -If cwd is not None, the current directory will be changed to cwd -before the child is executed. - -If env is not None, it defines the environment variables for the new -process. - -If universal_newlines is true, the file objects stdout and stderr are -opened as a text files, but lines may be terminated by any of '\n', -the Unix end-of-line convention, '\r', the Macintosh convention or -'\r\n', the Windows convention. All of these external representations -are seen as '\n' by the Python program. Note: This feature is only -available if Python is built with universal newline support (the -default). Also, the newlines attribute of the file objects stdout, -stdin and stderr are not updated by the communicate() method. - -The startupinfo and creationflags, if given, will be passed to the -underlying CreateProcess() function. They can specify things such as -appearance of the main window and priority for the new process. -(Windows only) - - -This module also defines two shortcut functions: - -call(*args, **kwargs): - Run command with arguments. Wait for command to complete, then - return the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - retcode = call(["ls", "-l"]) - - -Exceptions ----------- -Exceptions raised in the child process, before the new program has -started to execute, will be re-raised in the parent. Additionally, -the exception object will have one extra attribute called -'child_traceback', which is a string containing traceback information -from the childs point of view. - -The most common exception raised is OSError. This occurs, for -example, when trying to execute a non-existent file. Applications -should prepare for OSErrors. - -A ValueError will be raised if Popen is called with invalid arguments. - - -Security --------- -Unlike some other popen functions, this implementation will never call -/bin/sh implicitly. This means that all characters, including shell -metacharacters, can safely be passed to child processes. - - -Popen objects -============= -Instances of the Popen class have the following methods: - -poll() - Check if child process has terminated. Returns returncode - attribute. - -wait() - Wait for child process to terminate. Returns returncode attribute. - -communicate(input=None) - Interact with process: Send data to stdin. Read data from stdout - and stderr, until end-of-file is reached. Wait for process to - terminate. The optional stdin argument should be a string to be - sent to the child process, or None, if no data should be sent to - the child. - - communicate() returns a tuple (stdout, stderr). - - Note: The data read is buffered in memory, so do not use this - method if the data size is large or unlimited. - -The following attributes are also available: - -stdin - If the stdin argument is PIPE, this attribute is a file object - that provides input to the child process. Otherwise, it is None. - -stdout - If the stdout argument is PIPE, this attribute is a file object - that provides output from the child process. Otherwise, it is - None. - -stderr - If the stderr argument is PIPE, this attribute is file object that - provides error output from the child process. Otherwise, it is - None. - -pid - The process ID of the child process. - -returncode - The child return code. A None value indicates that the process - hasn't terminated yet. A negative value -N indicates that the - child was terminated by signal N (UNIX only). - - -Replacing older functions with the subprocess module -==================================================== -In this section, "a ==> b" means that b can be used as a replacement -for a. - -Note: All functions in this section fail (more or less) silently if -the executed program cannot be found; this module raises an OSError -exception. - -In the following examples, we assume that the subprocess module is -imported with "from subprocess import *". - - -Replacing /bin/sh shell backquote ---------------------------------- -output=`mycmd myarg` -==> -output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] - - -Replacing shell pipe line -------------------------- -output=`dmesg | grep hda` -==> -p1 = Popen(["dmesg"], stdout=PIPE) -p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) -output = p2.communicate()[0] - - -Replacing os.system() ---------------------- -sts = os.system("mycmd" + " myarg") -==> -p = Popen("mycmd" + " myarg", shell=True) -sts = os.waitpid(p.pid, 0) - -Note: - -* Calling the program through the shell is usually not required. - -* It's easier to look at the returncode attribute than the - exitstatus. - -A more real-world example would look like this: - -try: - retcode = call("mycmd" + " myarg", shell=True) - if retcode < 0: - print >>sys.stderr, "Child was terminated by signal", -retcode - else: - print >>sys.stderr, "Child returned", retcode -except OSError, e: - print >>sys.stderr, "Execution failed:", e - - -Replacing os.spawn* -------------------- -P_NOWAIT example: - -pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") -==> -pid = Popen(["/bin/mycmd", "myarg"]).pid - - -P_WAIT example: - -retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") -==> -retcode = call(["/bin/mycmd", "myarg"]) - - -Vector example: - -os.spawnvp(os.P_NOWAIT, path, args) -==> -Popen([path] + args[1:]) - - -Environment example: - -os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) -==> -Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) - - -Replacing os.popen* -------------------- -pipe = os.popen(cmd, mode='r', bufsize) -==> -pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout - -pipe = os.popen(cmd, mode='w', bufsize) -==> -pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin - - -(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdin, child_stdout) = (p.stdin, p.stdout) - - -(child_stdin, - child_stdout, - child_stderr) = os.popen3(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) -(child_stdin, - child_stdout, - child_stderr) = (p.stdin, p.stdout, p.stderr) - - -(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) -(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) - - -Replacing popen2.* ------------------- -Note: If the cmd argument to popen2 functions is a string, the command -is executed through /bin/sh. If it is a list, the command is directly -executed. - -(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) -==> -p = Popen(["somestring"], shell=True, bufsize=bufsize - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdout, child_stdin) = (p.stdout, p.stdin) - - -(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) -==> -p = Popen(["mycmd", "myarg"], bufsize=bufsize, - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdout, child_stdin) = (p.stdout, p.stdin) - -The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen, -except that: - -* subprocess.Popen raises an exception if the execution fails -* the capturestderr argument is replaced with the stderr argument. -* stdin=PIPE and stdout=PIPE must be specified. -* popen2 closes all filedescriptors by default, but you have to specify - close_fds=True with subprocess.Popen. - - -""" - -import sys -mswindows = (sys.platform == "win32") - -import os -import types -import traceback - -if mswindows: - import threading - import msvcrt - if 0: # <-- change this to use pywin32 instead of the _subprocess driver - import pywintypes - from win32api import GetStdHandle, STD_INPUT_HANDLE, \ - STD_OUTPUT_HANDLE, STD_ERROR_HANDLE - from win32api import GetCurrentProcess, DuplicateHandle, \ - GetModuleFileName, GetVersion - from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE - from win32pipe import CreatePipe - from win32process import CreateProcess, STARTUPINFO, \ - GetExitCodeProcess, STARTF_USESTDHANDLES, \ - STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE - from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 - else: - from _subprocess import * - class STARTUPINFO: - dwFlags = 0 - hStdInput = None - hStdOutput = None - hStdError = None - class pywintypes: - error = IOError -else: - import select - import errno - import fcntl - import pickle - -__all__ = ["Popen", "PIPE", "STDOUT", "call"] - -try: - MAXFD = os.sysconf("SC_OPEN_MAX") -except: - MAXFD = 256 - -# True/False does not exist on 2.2.0 -try: - False -except NameError: - False = 0 - True = 1 - -_active = [] - -def _cleanup(): - for inst in _active[:]: - inst.poll() - -PIPE = -1 -STDOUT = -2 - - -def call(*args, **kwargs): - """Run command with arguments. Wait for command to complete, then - return the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - retcode = call(["ls", "-l"]) - """ - return Popen(*args, **kwargs).wait() - - -def list2cmdline(seq): - """ - Translate a sequence of arguments into a command line - string, using the same rules as the MS C runtime: - - 1) Arguments are delimited by white space, which is either a - space or a tab. - - 2) A string surrounded by double quotation marks is - interpreted as a single argument, regardless of white space - contained within. A quoted string can be embedded in an - argument. - - 3) A double quotation mark preceded by a backslash is - interpreted as a literal double quotation mark. - - 4) Backslashes are interpreted literally, unless they - immediately precede a double quotation mark. - - 5) If backslashes immediately precede a double quotation mark, - every pair of backslashes is interpreted as a literal - backslash. If the number of backslashes is odd, the last - backslash escapes the next double quotation mark as - described in rule 3. - """ - - # See - # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp - result = [] - needquote = False - for arg in seq: - bs_buf = [] - - # Add a space to separate this argument from the others - if result: - result.append(' ') - - needquote = (" " in arg) or ("\t" in arg) - if needquote: - result.append('"') - - for c in arg: - if c == '\\': - # Don't know if we need to double yet. - bs_buf.append(c) - elif c == '"': - # Double backspaces. - result.append('\\' * len(bs_buf)*2) - bs_buf = [] - result.append('\\"') - else: - # Normal char - if bs_buf: - result.extend(bs_buf) - bs_buf = [] - result.append(c) - - # Add remaining backspaces, if any. - if bs_buf: - result.extend(bs_buf) - - if needquote: - result.extend(bs_buf) - result.append('"') - - return ''.join(result) - - -class Popen(object): - def __init__(self, args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - """Create new Popen instance.""" - _cleanup() - - if not isinstance(bufsize, (int, long)): - raise TypeError("bufsize must be an integer") - - if mswindows: - if preexec_fn is not None: - raise ValueError("preexec_fn is not supported on Windows " - "platforms") - if close_fds: - raise ValueError("close_fds is not supported on Windows " - "platforms") - else: - # POSIX - if startupinfo is not None: - raise ValueError("startupinfo is only supported on Windows " - "platforms") - if creationflags != 0: - raise ValueError("creationflags is only supported on Windows " - "platforms") - - self.stdin = None - self.stdout = None - self.stderr = None - self.pid = None - self.returncode = None - self.universal_newlines = universal_newlines - - # Input and output objects. The general principle is like - # this: - # - # Parent Child - # ------ ----- - # p2cwrite ---stdin---> p2cread - # c2pread <--stdout--- c2pwrite - # errread <--stderr--- errwrite - # - # On POSIX, the child objects are file descriptors. On - # Windows, these are Windows file handles. The parent objects - # are file descriptors on both platforms. The parent objects - # are None when not using PIPEs. The child objects are None - # when not redirecting. - - (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) = self._get_handles(stdin, stdout, stderr) - - self._execute_child(args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - if p2cwrite: - self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) - if c2pread: - if universal_newlines: - self.stdout = os.fdopen(c2pread, 'rU', bufsize) - else: - self.stdout = os.fdopen(c2pread, 'rb', bufsize) - if errread: - if universal_newlines: - self.stderr = os.fdopen(errread, 'rU', bufsize) - else: - self.stderr = os.fdopen(errread, 'rb', bufsize) - - _active.append(self) - - - def _translate_newlines(self, data): - data = data.replace("\r\n", "\n") - data = data.replace("\r", "\n") - return data - - - if mswindows: - # - # Windows methods - # - def _get_handles(self, stdin, stdout, stderr): - """Construct and return tuple with IO objects: - p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite - """ - if stdin == None and stdout == None and stderr == None: - return (None, None, None, None, None, None) - - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None - - if stdin == None: - p2cread = GetStdHandle(STD_INPUT_HANDLE) - elif stdin == PIPE: - p2cread, p2cwrite = CreatePipe(None, 0) - # Detach and turn into fd - p2cwrite = p2cwrite.Detach() - p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) - elif type(stdin) == types.IntType: - p2cread = msvcrt.get_osfhandle(stdin) - else: - # Assuming file-like object - p2cread = msvcrt.get_osfhandle(stdin.fileno()) - p2cread = self._make_inheritable(p2cread) - - if stdout == None: - c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) - elif stdout == PIPE: - c2pread, c2pwrite = CreatePipe(None, 0) - # Detach and turn into fd - c2pread = c2pread.Detach() - c2pread = msvcrt.open_osfhandle(c2pread, 0) - elif type(stdout) == types.IntType: - c2pwrite = msvcrt.get_osfhandle(stdout) - else: - # Assuming file-like object - c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) - c2pwrite = self._make_inheritable(c2pwrite) - - if stderr == None: - errwrite = GetStdHandle(STD_ERROR_HANDLE) - elif stderr == PIPE: - errread, errwrite = CreatePipe(None, 0) - # Detach and turn into fd - errread = errread.Detach() - errread = msvcrt.open_osfhandle(errread, 0) - elif stderr == STDOUT: - errwrite = c2pwrite - elif type(stderr) == types.IntType: - errwrite = msvcrt.get_osfhandle(stderr) - else: - # Assuming file-like object - errwrite = msvcrt.get_osfhandle(stderr.fileno()) - errwrite = self._make_inheritable(errwrite) - - return (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - - def _make_inheritable(self, handle): - """Return a duplicate of handle, which is inheritable""" - return DuplicateHandle(GetCurrentProcess(), handle, - GetCurrentProcess(), 0, 1, - DUPLICATE_SAME_ACCESS) - - - def _find_w9xpopen(self): - """Find and return absolute path to w9xpopen.exe""" - w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), - "w9xpopen.exe") - if not os.path.exists(w9xpopen): - # Eeek - file-not-found - possibly an embedding - # situation - see if we can locate it in sys.exec_prefix - w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), - "w9xpopen.exe") - if not os.path.exists(w9xpopen): - raise RuntimeError("Cannot locate w9xpopen.exe, which is " - "needed for Popen to work with your " - "shell or platform.") - return w9xpopen - - - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program (MS Windows version)""" - - if not isinstance(args, types.StringTypes): - args = list2cmdline(args) - - # Process startup details - default_startupinfo = STARTUPINFO() - if startupinfo == None: - startupinfo = default_startupinfo - if not None in (p2cread, c2pwrite, errwrite): - startupinfo.dwFlags |= STARTF_USESTDHANDLES - startupinfo.hStdInput = p2cread - startupinfo.hStdOutput = c2pwrite - startupinfo.hStdError = errwrite - - if shell: - default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW - default_startupinfo.wShowWindow = SW_HIDE - comspec = os.environ.get("COMSPEC", "cmd.exe") - args = comspec + " /c " + args - if (GetVersion() >= 0x80000000L or - os.path.basename(comspec).lower() == "command.com"): - # Win9x, or using command.com on NT. We need to - # use the w9xpopen intermediate program. For more - # information, see KB Q150956 - # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) - w9xpopen = self._find_w9xpopen() - args = '"%s" %s' % (w9xpopen, args) - # Not passing CREATE_NEW_CONSOLE has been known to - # cause random failures on win9x. Specifically a - # dialog: "Your program accessed mem currently in - # use at xxx" and a hopeful warning about the - # stability of your system. Cost is Ctrl+C wont - # kill children. - creationflags |= CREATE_NEW_CONSOLE - - # Start the process - try: - hp, ht, pid, tid = CreateProcess(executable, args, - # no special security - None, None, - # must inherit handles to pass std - # handles - 1, - creationflags, - env, - cwd, - startupinfo) - except pywintypes.error, e: - # Translate pywintypes.error to WindowsError, which is - # a subclass of OSError. FIXME: We should really - # translate errno using _sys_errlist (or simliar), but - # how can this be done from Python? - raise WindowsError(*e.args) - - # Retain the process handle, but close the thread handle - self._handle = hp - self.pid = pid - ht.Close() - - # Child is launched. Close the parent's copy of those pipe - # handles that only the child should have open. You need - # to make sure that no handles to the write end of the - # output pipe are maintained in this process or else the - # pipe will not close when the child process exits and the - # ReadFile will hang. - if p2cread != None: - p2cread.Close() - if c2pwrite != None: - c2pwrite.Close() - if errwrite != None: - errwrite.Close() - - - def poll(self): - """Check if child process has terminated. Returns returncode - attribute.""" - if self.returncode == None: - if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: - self.returncode = GetExitCodeProcess(self._handle) - _active.remove(self) - return self.returncode - - - def wait(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode == None: - obj = WaitForSingleObject(self._handle, INFINITE) - self.returncode = GetExitCodeProcess(self._handle) - _active.remove(self) - return self.returncode - - - def _readerthread(self, fh, buffer): - buffer.append(fh.read()) - - - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" - stdout = None # Return - stderr = None # Return - - if self.stdout: - stdout = [] - stdout_thread = threading.Thread(target=self._readerthread, - args=(self.stdout, stdout)) - stdout_thread.setDaemon(True) - stdout_thread.start() - if self.stderr: - stderr = [] - stderr_thread = threading.Thread(target=self._readerthread, - args=(self.stderr, stderr)) - stderr_thread.setDaemon(True) - stderr_thread.start() - - if self.stdin: - if input != None: - self.stdin.write(input) - self.stdin.close() - - if self.stdout: - stdout_thread.join() - if self.stderr: - stderr_thread.join() - - # All data exchanged. Translate lists into strings. - if stdout != None: - stdout = stdout[0] - if stderr != None: - stderr = stderr[0] - - # Translate newlines, if requested. We cannot let the file - # object do the translation: It is based on stdio, which is - # impossible to combine with select (unless forcing no - # buffering). - if self.universal_newlines and hasattr(open, 'newlines'): - if stdout: - stdout = self._translate_newlines(stdout) - if stderr: - stderr = self._translate_newlines(stderr) - - self.wait() - return (stdout, stderr) - - else: - # - # POSIX methods - # - def _get_handles(self, stdin, stdout, stderr): - """Construct and return tuple with IO objects: - p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite - """ - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None - - if stdin == None: - pass - elif stdin == PIPE: - p2cread, p2cwrite = os.pipe() - elif type(stdin) == types.IntType: - p2cread = stdin - else: - # Assuming file-like object - p2cread = stdin.fileno() - - if stdout == None: - pass - elif stdout == PIPE: - c2pread, c2pwrite = os.pipe() - elif type(stdout) == types.IntType: - c2pwrite = stdout - else: - # Assuming file-like object - c2pwrite = stdout.fileno() - - if stderr == None: - pass - elif stderr == PIPE: - errread, errwrite = os.pipe() - elif stderr == STDOUT: - errwrite = c2pwrite - elif type(stderr) == types.IntType: - errwrite = stderr - else: - # Assuming file-like object - errwrite = stderr.fileno() - - return (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - - def _set_cloexec_flag(self, fd): - try: - cloexec_flag = fcntl.FD_CLOEXEC - except AttributeError: - cloexec_flag = 1 - - old = fcntl.fcntl(fd, fcntl.F_GETFD) - fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) - - - def _close_fds(self, but): - for i in range(3, MAXFD): - if i == but: - continue - try: - os.close(i) - except: - pass - - - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program (POSIX version)""" - - if isinstance(args, types.StringTypes): - args = [args] - - if shell: - args = ["/bin/sh", "-c"] + args - - if executable == None: - executable = args[0] - - # For transferring possible exec failure from child to parent - # The first char specifies the exception type: 0 means - # OSError, 1 means some other error. - errpipe_read, errpipe_write = os.pipe() - self._set_cloexec_flag(errpipe_write) - - self.pid = os.fork() - if self.pid == 0: - # Child - try: - # Close parent's pipe ends - if p2cwrite: - os.close(p2cwrite) - if c2pread: - os.close(c2pread) - if errread: - os.close(errread) - os.close(errpipe_read) - - # Dup fds for child - if p2cread: - os.dup2(p2cread, 0) - if c2pwrite: - os.dup2(c2pwrite, 1) - if errwrite: - os.dup2(errwrite, 2) - - # Close pipe fds. Make sure we doesn't close the same - # fd more than once. - if p2cread: - os.close(p2cread) - if c2pwrite and c2pwrite not in (p2cread,): - os.close(c2pwrite) - if errwrite and errwrite not in (p2cread, c2pwrite): - os.close(errwrite) - - # Close all other fds, if asked for - if close_fds: - self._close_fds(but=errpipe_write) - - if cwd != None: - os.chdir(cwd) - - if preexec_fn: - apply(preexec_fn) - - if env == None: - os.execvp(executable, args) - else: - os.execvpe(executable, args, env) - - except: - exc_type, exc_value, tb = sys.exc_info() - # Save the traceback and attach it to the exception object - exc_lines = traceback.format_exception(exc_type, - exc_value, - tb) - exc_value.child_traceback = ''.join(exc_lines) - os.write(errpipe_write, pickle.dumps(exc_value)) - - # This exitcode won't be reported to applications, so it - # really doesn't matter what we return. - os._exit(255) - - # Parent - os.close(errpipe_write) - if p2cread and p2cwrite: - os.close(p2cread) - if c2pwrite and c2pread: - os.close(c2pwrite) - if errwrite and errread: - os.close(errwrite) - - # Wait for exec to fail or succeed; possibly raising exception - data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB - os.close(errpipe_read) - if data != "": - os.waitpid(self.pid, 0) - child_exception = pickle.loads(data) - raise child_exception - - - def _handle_exitstatus(self, sts): - if os.WIFSIGNALED(sts): - self.returncode = -os.WTERMSIG(sts) - elif os.WIFEXITED(sts): - self.returncode = os.WEXITSTATUS(sts) - else: - # Should never happen - raise RuntimeError("Unknown child exit status!") - - _active.remove(self) - - - def poll(self): - """Check if child process has terminated. Returns returncode - attribute.""" - if self.returncode == None: - try: - pid, sts = os.waitpid(self.pid, os.WNOHANG) - if pid == self.pid: - self._handle_exitstatus(sts) - except os.error: - pass - return self.returncode - - - def wait(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode == None: - pid, sts = os.waitpid(self.pid, 0) - self._handle_exitstatus(sts) - return self.returncode - - - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" - read_set = [] - write_set = [] - stdout = None # Return - stderr = None # Return - - if self.stdin: - # Flush stdio buffer. This might block, if the user has - # been writing to .stdin in an uncontrolled fashion. - self.stdin.flush() - if input: - write_set.append(self.stdin) - else: - self.stdin.close() - if self.stdout: - read_set.append(self.stdout) - stdout = [] - if self.stderr: - read_set.append(self.stderr) - stderr = [] - - while read_set or write_set: - rlist, wlist, xlist = select.select(read_set, write_set, []) - - if self.stdin in wlist: - # When select has indicated that the file is writable, - # we can write up to PIPE_BUF bytes without risk - # blocking. POSIX defines PIPE_BUF >= 512 - bytes_written = os.write(self.stdin.fileno(), input[:512]) - input = input[bytes_written:] - if not input: - self.stdin.close() - write_set.remove(self.stdin) - - if self.stdout in rlist: - data = os.read(self.stdout.fileno(), 1024) - if data == "": - self.stdout.close() - read_set.remove(self.stdout) - stdout.append(data) - - if self.stderr in rlist: - data = os.read(self.stderr.fileno(), 1024) - if data == "": - self.stderr.close() - read_set.remove(self.stderr) - stderr.append(data) - - # All data exchanged. Translate lists into strings. - if stdout != None: - stdout = ''.join(stdout) - if stderr != None: - stderr = ''.join(stderr) - - # Translate newlines, if requested. We cannot let the file - # object do the translation: It is based on stdio, which is - # impossible to combine with select (unless forcing no - # buffering). - if self.universal_newlines and hasattr(open, 'newlines'): - if stdout: - stdout = self._translate_newlines(stdout) - if stderr: - stderr = self._translate_newlines(stderr) - - self.wait() - return (stdout, stderr) - - -def _demo_posix(): - # - # Example 1: Simple redirection: Get process list - # - plist = Popen(["ps"], stdout=PIPE).communicate()[0] - print "Process list:" - print plist - - # - # Example 2: Change uid before executing child - # - if os.getuid() == 0: - p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) - p.wait() - - # - # Example 3: Connecting several subprocesses - # - print "Looking for 'hda'..." - p1 = Popen(["dmesg"], stdout=PIPE) - p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) - print repr(p2.communicate()[0]) - - # - # Example 4: Catch execution error - # - print - print "Trying a weird file..." - try: - print Popen(["/this/path/does/not/exist"]).communicate() - except OSError, e: - if e.errno == errno.ENOENT: - print "The file didn't exist. I thought so..." - print "Child traceback:" - print e.child_traceback - else: - print "Error", e.errno - else: - print >>sys.stderr, "Gosh. No error." - - -def _demo_windows(): - # - # Example 1: Connecting several subprocesses - # - print "Looking for 'PROMPT' in set output..." - p1 = Popen("set", stdout=PIPE, shell=True) - p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) - print repr(p2.communicate()[0]) - - # - # Example 2: Simple execution of program - # - print "Executing calc..." - p = Popen("calc") - p.wait() - - -if __name__ == "__main__": - if mswindows: - _demo_windows() - else: - _demo_posix() diff --git a/config.mak.in b/config.mak.in index 1cafa19..9a57840 100644 --- a/config.mak.in +++ b/config.mak.in @@ -13,7 +13,6 @@ bindir = @bindir@ #gitexecdir = @libexecdir@/git-core/ datarootdir = @datarootdir@ template_dir = @datadir@/git-core/templates/ -GIT_PYTHON_DIR = @datadir@/git-core/python mandir=@mandir@ @@ -23,7 +22,6 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH -NO_PYTHON=@NO_PYTHON@ NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ diff --git a/configure.ac b/configure.ac index cff5722..34e3478 100644 --- a/configure.ac +++ b/configure.ac @@ -75,20 +75,6 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # -# Define PYTHON_PATH to provide path to Python. -AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) -AS_HELP_STRING([--without-python], [don't use python scripts])], - [if test "$withval" = "no"; then \ - NO_PYTHON=YesPlease; \ - elif test "$withval" = "yes"; then \ - NO_PYTHON=; \ - else \ - NO_PYTHON=; \ - PYTHON_PATH=$withval; \ - fi; \ - ]) -AC_SUBST(NO_PYTHON) -AC_SUBST(PYTHON_PATH) ## Checks for programs. @@ -98,18 +84,6 @@ AC_PROG_CC([cc gcc]) #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) -# -# Define PYTHON_PATH to provide path to Python. -if test -z "$NO_PYTHON"; then - if test -z "$PYTHON_PATH"; then - AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2]) - fi - if test -n "$PYTHON_PATH"; then - GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@]) - NO_PYTHON="" - fi -fi - ## Checks for libraries. AC_MSG_NOTICE([CHECKS for libraries]) @@ -262,22 +236,9 @@ AC_SUBST(NO_SETENV) # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. # Enable it on Windows. By default, symrefs are still used. # -# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. -AC_CACHE_CHECK([for subprocess.py], - [ac_cv_python_has_subprocess_py], -[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then - ac_cv_python_has_subprocess_py=yes -else - ac_cv_python_has_subprocess_py=no -fi]) -if test $ac_cv_python_has_subprocess_py != yes; then - GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease]) -fi -# # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. - ## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE AC_MSG_NOTICE([CHECKS for site configuration]) diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py deleted file mode 100755 index 4039435..0000000 --- a/git-merge-recursive-old.py +++ /dev/null @@ -1,944 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys -sys.path.append('''@@GIT_PYTHON_PATH@@''') - -import math, random, os, re, signal, tempfile, stat, errno, traceback -from heapq import heappush, heappop -from sets import Set - -from gitMergeCommon import * - -outputIndent = 0 -def output(*args): - sys.stdout.write(' '*outputIndent) - printList(args) - -originalIndexFile = os.environ.get('GIT_INDEX_FILE', - os.environ.get('GIT_DIR', '.git') + '/index') -temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ - '/merge-recursive-tmp-index' -def setupIndex(temporary): - try: - os.unlink(temporaryIndexFile) - except OSError: - pass - if temporary: - newIndex = temporaryIndexFile - else: - newIndex = originalIndexFile - os.environ['GIT_INDEX_FILE'] = newIndex - -# This is a global variable which is used in a number of places but -# only written to in the 'merge' function. - -# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and -# don't update the working directory. -# False => Leave unmerged entries in the cache and update -# the working directory. - -cacheOnly = False - -# The entry point to the merge code -# --------------------------------- - -def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): - '''Merge the commits h1 and h2, return the resulting virtual - commit object and a flag indicating the cleanness of the merge.''' - assert(isinstance(h1, Commit) and isinstance(h2, Commit)) - - global outputIndent - - output('Merging:') - output(h1) - output(h2) - sys.stdout.flush() - - if ancestor: - ca = [ancestor] - else: - assert(isinstance(graph, Graph)) - ca = getCommonAncestors(graph, h1, h2) - output('found', len(ca), 'common ancestor(s):') - for x in ca: - output(x) - sys.stdout.flush() - - mergedCA = ca[0] - for h in ca[1:]: - outputIndent = callDepth+1 - [mergedCA, dummy] = merge(mergedCA, h, - 'Temporary merge branch 1', - 'Temporary merge branch 2', - graph, callDepth+1) - outputIndent = callDepth - assert(isinstance(mergedCA, Commit)) - - global cacheOnly - if callDepth == 0: - setupIndex(False) - cacheOnly = False - else: - setupIndex(True) - runProgram(['git-read-tree', h1.tree()]) - cacheOnly = True - - [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), - branch1Name, branch2Name) - - if graph and (clean or cacheOnly): - res = Commit(None, [h1, h2], tree=shaRes) - graph.addNode(res) - else: - res = None - - return [res, clean] - -getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) -def getFilesAndDirs(tree): - files = Set() - dirs = Set() - out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) - for l in out.split('\0'): - m = getFilesRE.match(l) - if m: - if m.group(2) == 'tree': - dirs.add(m.group(4)) - elif m.group(2) == 'blob': - files.add(m.group(4)) - - return [files, dirs] - -# Those two global variables are used in a number of places but only -# written to in 'mergeTrees' and 'uniquePath'. They keep track of -# every file and directory in the two branches that are about to be -# merged. -currentFileSet = None -currentDirectorySet = None - -def mergeTrees(head, merge, common, branch1Name, branch2Name): - '''Merge the trees 'head' and 'merge' with the common ancestor - 'common'. The name of the head branch is 'branch1Name' and the name of - the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) - where tree is the resulting tree and cleanMerge is True iff the - merge was clean.''' - - assert(isSha(head) and isSha(merge) and isSha(common)) - - if common == merge: - output('Already uptodate!') - return [head, True] - - if cacheOnly: - updateArg = '-i' - else: - updateArg = '-u' - - [out, code] = runProgram(['git-read-tree', updateArg, '-m', - common, head, merge], returnCode = True) - if code != 0: - die('git-read-tree:', out) - - [tree, code] = runProgram('git-write-tree', returnCode=True) - tree = tree.rstrip() - if code != 0: - global currentFileSet, currentDirectorySet - [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) - [filesM, dirsM] = getFilesAndDirs(merge) - currentFileSet.union_update(filesM) - currentDirectorySet.union_update(dirsM) - - entries = unmergedCacheEntries() - renamesHead = getRenames(head, common, head, merge, entries) - renamesMerge = getRenames(merge, common, head, merge, entries) - - cleanMerge = processRenames(renamesHead, renamesMerge, - branch1Name, branch2Name) - for entry in entries: - if entry.processed: - continue - if not processEntry(entry, branch1Name, branch2Name): - cleanMerge = False - - if cleanMerge or cacheOnly: - tree = runProgram('git-write-tree').rstrip() - else: - tree = None - else: - cleanMerge = True - - return [tree, cleanMerge] - -# Low level file merging, update and removal -# ------------------------------------------ - -def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, - branch1Name, branch2Name): - - merge = False - clean = True - - if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): - clean = False - if stat.S_ISREG(aMode): - mode = aMode - sha = aSha - else: - mode = bMode - sha = bSha - else: - if aSha != oSha and bSha != oSha: - merge = True - - if aMode == oMode: - mode = bMode - else: - mode = aMode - - if aSha == oSha: - sha = bSha - elif bSha == oSha: - sha = aSha - elif stat.S_ISREG(aMode): - assert(stat.S_ISREG(bMode)) - - orig = runProgram(['git-unpack-file', oSha]).rstrip() - src1 = runProgram(['git-unpack-file', aSha]).rstrip() - src2 = runProgram(['git-unpack-file', bSha]).rstrip() - try: - [out, code] = runProgram(['merge', - '-L', branch1Name + '/' + aPath, - '-L', 'orig/' + oPath, - '-L', branch2Name + '/' + bPath, - src1, orig, src2], returnCode=True) - except ProgramError, e: - print >>sys.stderr, e - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?") - - sha = runProgram(['git-hash-object', '-t', 'blob', '-w', - src1]).rstrip() - - os.unlink(orig) - os.unlink(src1) - os.unlink(src2) - - clean = (code == 0) - else: - assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) - sha = aSha - - if aSha != bSha: - clean = False - - return [sha, mode, clean, merge] - -def updateFile(clean, sha, mode, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - return updateFileExt(sha, mode, path, updateCache, updateWd) - -def updateFileExt(sha, mode, path, updateCache, updateWd): - if cacheOnly: - updateWd = False - - if updateWd: - pathComponents = path.split('/') - for x in xrange(1, len(pathComponents)): - p = '/'.join(pathComponents[0:x]) - - try: - createDir = not stat.S_ISDIR(os.lstat(p).st_mode) - except OSError: - createDir = True - - if createDir: - try: - os.mkdir(p) - except OSError, e: - die("Couldn't create directory", p, e.strerror) - - prog = ['git-cat-file', 'blob', sha] - if stat.S_ISREG(mode): - try: - os.unlink(path) - except OSError: - pass - if mode & 0100: - mode = 0777 - else: - mode = 0666 - fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) - proc = subprocess.Popen(prog, stdout=fd) - proc.wait() - os.close(fd) - elif stat.S_ISLNK(mode): - linkTarget = runProgram(prog) - os.symlink(linkTarget, path) - else: - assert(False) - - if updateWd and updateCache: - runProgram(['git-update-index', '--add', '--', path]) - elif updateCache: - runProgram(['git-update-index', '--add', '--cacheinfo', - '0%o' % mode, sha, path]) - -def setIndexStages(path, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode, - clear=True): - istring = [] - if clear: - istring.append("0 " + ("0" * 40) + "\t" + path + "\0") - if oMode: - istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) - if aMode: - istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) - if bMode: - istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) - - runProgram(['git-update-index', '-z', '--index-info'], - input="".join(istring)) - -def removeFile(clean, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - if updateCache: - runProgram(['git-update-index', '--force-remove', '--', path]) - - if updateWd: - try: - os.unlink(path) - except OSError, e: - if e.errno != errno.ENOENT and e.errno != errno.EISDIR: - raise - try: - os.removedirs(os.path.dirname(path)) - except OSError: - pass - -def uniquePath(path, branch): - def fileExists(path): - try: - os.lstat(path) - return True - except OSError, e: - if e.errno == errno.ENOENT: - return False - else: - raise - - branch = branch.replace('/', '_') - newPath = path + '~' + branch - suffix = 0 - while newPath in currentFileSet or \ - newPath in currentDirectorySet or \ - fileExists(newPath): - suffix += 1 - newPath = path + '~' + branch + '_' + str(suffix) - currentFileSet.add(newPath) - return newPath - -# Cache entry management -# ---------------------- - -class CacheEntry: - def __init__(self, path): - class Stage: - def __init__(self): - self.sha1 = None - self.mode = None - - # Used for debugging only - def __str__(self): - if self.mode != None: - m = '0%o' % self.mode - else: - m = 'None' - - if self.sha1: - sha1 = self.sha1 - else: - sha1 = 'None' - return 'sha1: ' + sha1 + ' mode: ' + m - - self.stages = [Stage(), Stage(), Stage(), Stage()] - self.path = path - self.processed = False - - def __str__(self): - return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) - -class CacheEntryContainer: - def __init__(self): - self.entries = {} - - def add(self, entry): - self.entries[entry.path] = entry - - def get(self, path): - return self.entries.get(path) - - def __iter__(self): - return self.entries.itervalues() - -unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) -def unmergedCacheEntries(): - '''Create a dictionary mapping file names to CacheEntry - objects. The dictionary contains one entry for every path with a - non-zero stage entry.''' - - lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') - lines.pop() - - res = CacheEntryContainer() - for l in lines: - m = unmergedRE.match(l) - if m: - mode = int(m.group(1), 8) - sha1 = m.group(2) - stage = int(m.group(3)) - path = m.group(4) - - e = res.get(path) - if not e: - e = CacheEntry(path) - res.add(e) - - e.stages[stage].mode = mode - e.stages[stage].sha1 = sha1 - else: - die('Error: Merge program failed: Unexpected output from', - 'git-ls-files:', l) - return res - -lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) -def getCacheEntry(path, origTree, aTree, bTree): - '''Returns a CacheEntry object which doesn't have to correspond to - a real cache entry in Git's index.''' - - def parse(out): - if out == '': - return [None, None] - else: - m = lsTreeRE.match(out) - if not m: - die('Unexpected output from git-ls-tree:', out) - elif m.group(2) == 'blob': - return [m.group(3), int(m.group(1), 8)] - else: - return [None, None] - - res = CacheEntry(path) - - [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) - [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) - [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) - - res.stages[1].sha1 = oSha - res.stages[1].mode = oMode - res.stages[2].sha1 = aSha - res.stages[2].mode = aMode - res.stages[3].sha1 = bSha - res.stages[3].mode = bMode - - return res - -# Rename detection and handling -# ----------------------------- - -class RenameEntry: - def __init__(self, - src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score): - self.srcName = src - self.srcSha = srcSha - self.srcMode = srcMode - self.srcCacheEntry = srcCacheEntry - self.dstName = dst - self.dstSha = dstSha - self.dstMode = dstMode - self.dstCacheEntry = dstCacheEntry - self.score = score - - self.processed = False - -class RenameEntryContainer: - def __init__(self): - self.entriesSrc = {} - self.entriesDst = {} - - def add(self, entry): - self.entriesSrc[entry.srcName] = entry - self.entriesDst[entry.dstName] = entry - - def getSrc(self, path): - return self.entriesSrc.get(path) - - def getDst(self, path): - return self.entriesDst.get(path) - - def __iter__(self): - return self.entriesSrc.itervalues() - -parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') -def getRenames(tree, oTree, aTree, bTree, cacheEntries): - '''Get information of all renames which occured between 'oTree' and - 'tree'. We need the three trees in the merge ('oTree', 'aTree' and - 'bTree') to be able to associate the correct cache entries with - the rename information. 'tree' is always equal to either aTree or bTree.''' - - assert(tree == aTree or tree == bTree) - inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', - '-z', oTree, tree]) - - ret = RenameEntryContainer() - try: - recs = inp.split("\0") - recs.pop() # remove last entry (which is '') - it = recs.__iter__() - while True: - rec = it.next() - m = parseDiffRenamesRE.match(rec) - - if not m: - die('Unexpected output from git-diff-tree:', rec) - - srcMode = int(m.group(1), 8) - dstMode = int(m.group(2), 8) - srcSha = m.group(3) - dstSha = m.group(4) - score = m.group(5) - src = it.next() - dst = it.next() - - srcCacheEntry = cacheEntries.get(src) - if not srcCacheEntry: - srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) - cacheEntries.add(srcCacheEntry) - - dstCacheEntry = cacheEntries.get(dst) - if not dstCacheEntry: - dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) - cacheEntries.add(dstCacheEntry) - - ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score)) - except StopIteration: - pass - return ret - -def fmtRename(src, dst): - srcPath = src.split('/') - dstPath = dst.split('/') - path = [] - endIndex = min(len(srcPath), len(dstPath)) - 1 - for x in range(0, endIndex): - if srcPath[x] == dstPath[x]: - path.append(srcPath[x]) - else: - endIndex = x - break - - if len(path) > 0: - return '/'.join(path) + \ - '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ - '/'.join(dstPath[endIndex:]) + '}' - else: - return src + ' => ' + dst - -def processRenames(renamesA, renamesB, branchNameA, branchNameB): - srcNames = Set() - for x in renamesA: - srcNames.add(x.srcName) - for x in renamesB: - srcNames.add(x.srcName) - - cleanMerge = True - for path in srcNames: - if renamesA.getSrc(path): - renames1 = renamesA - renames2 = renamesB - branchName1 = branchNameA - branchName2 = branchNameB - else: - renames1 = renamesB - renames2 = renamesA - branchName1 = branchNameB - branchName2 = branchNameA - - ren1 = renames1.getSrc(path) - ren2 = renames2.getSrc(path) - - ren1.dstCacheEntry.processed = True - ren1.srcCacheEntry.processed = True - - if ren1.processed: - continue - - ren1.processed = True - - if ren2: - # Renamed in 1 and renamed in 2 - assert(ren1.srcName == ren2.srcName) - ren2.dstCacheEntry.processed = True - ren2.processed = True - - if ren1.dstName != ren2.dstName: - output('CONFLICT (rename/rename): Rename', - fmtRename(path, ren1.dstName), 'in branch', branchName1, - 'rename', fmtRename(path, ren2.dstName), 'in', - branchName2) - cleanMerge = False - - if ren1.dstName in currentDirectorySet: - dstName1 = uniquePath(ren1.dstName, branchName1) - output(ren1.dstName, 'is a directory in', branchName2, - 'adding as', dstName1, 'instead.') - removeFile(False, ren1.dstName) - else: - dstName1 = ren1.dstName - - if ren2.dstName in currentDirectorySet: - dstName2 = uniquePath(ren2.dstName, branchName2) - output(ren2.dstName, 'is a directory in', branchName1, - 'adding as', dstName2, 'instead.') - removeFile(False, ren2.dstName) - else: - dstName2 = ren2.dstName - setIndexStages(dstName1, - None, None, - ren1.dstSha, ren1.dstMode, - None, None) - setIndexStages(dstName2, - None, None, - None, None, - ren2.dstSha, ren2.dstMode) - - else: - removeFile(True, ren1.srcName) - - [resSha, resMode, clean, merge] = \ - mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, - ren1.dstName, ren1.dstSha, ren1.dstMode, - ren2.dstName, ren2.dstSha, ren2.dstMode, - branchName1, branchName2) - - if merge or not clean: - output('Renaming', fmtRename(path, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (content): merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - ren1.srcSha, ren1.srcMode, - ren1.dstSha, ren1.dstMode, - ren2.dstSha, ren2.dstMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - else: - removeFile(True, ren1.srcName) - - # Renamed in 1, maybe changed in 2 - if renamesA == renames1: - stage = 3 - else: - stage = 2 - - srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 - srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode - - dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 - dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode - - tryMerge = False - - if ren1.dstName in currentDirectorySet: - newPath = uniquePath(ren1.dstName, branchName1) - output('CONFLICT (rename/directory): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, - 'directory', ren1.dstName, 'added in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath, 'instead') - cleanMerge = False - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath) - elif srcShaOtherBranch == None: - output('CONFLICT (rename/delete): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1, 'and deleted in', branchName2) - cleanMerge = False - updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) - elif dstShaOtherBranch: - newPath = uniquePath(ren1.dstName, branchName2) - output('CONFLICT (rename/add): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1 + '.', ren1.dstName, 'added in', branchName2) - output('Adding as', newPath, 'instead') - updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) - cleanMerge = False - tryMerge = True - elif renames2.getDst(ren1.dstName): - dst2 = renames2.getDst(ren1.dstName) - newPath1 = uniquePath(ren1.dstName, branchName1) - newPath2 = uniquePath(dst2.dstName, branchName2) - output('CONFLICT (rename/rename): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1+'. Rename', - fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath1, 'and', - dst2.srcName, 'to', newPath2, 'instead') - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) - updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) - dst2.processed = True - cleanMerge = False - else: - tryMerge = True - - if tryMerge: - - oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode - aName, bName = ren1.dstName, ren1.srcName - aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch - aMode, bMode = ren1.dstMode, srcModeOtherBranch - aBranch, bBranch = branchName1, branchName2 - - if renamesA != renames1: - aName, bName = bName, aName - aSHA1, bSHA1 = bSHA1, aSHA1 - aMode, bMode = bMode, aMode - aBranch, bBranch = bBranch, aBranch - - [resSha, resMode, clean, merge] = \ - mergeFile(oName, oSHA1, oMode, - aName, aSHA1, aMode, - bName, bSHA1, bMode, - aBranch, bBranch); - - if merge or not clean: - output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (rename/modify): Merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - - return cleanMerge - -# Per entry merge function -# ------------------------ - -def processEntry(entry, branch1Name, branch2Name): - '''Merge one cache entry.''' - - debug('processing', entry.path, 'clean cache:', cacheOnly) - - cleanMerge = True - - path = entry.path - oSha = entry.stages[1].sha1 - oMode = entry.stages[1].mode - aSha = entry.stages[2].sha1 - aMode = entry.stages[2].mode - bSha = entry.stages[3].sha1 - bMode = entry.stages[3].mode - - assert(oSha == None or isSha(oSha)) - assert(aSha == None or isSha(aSha)) - assert(bSha == None or isSha(bSha)) - - assert(oMode == None or type(oMode) is int) - assert(aMode == None or type(aMode) is int) - assert(bMode == None or type(bMode) is int) - - if (oSha and (not aSha or not bSha)): - # - # Case A: Deleted in one - # - if (not aSha and not bSha) or \ - (aSha == oSha and not bSha) or \ - (not aSha and bSha == oSha): - # Deleted in both or deleted in one and unchanged in the other - if aSha: - output('Removing', path) - removeFile(True, path) - else: - # Deleted in one and changed in the other - cleanMerge = False - if not aSha: - output('CONFLICT (delete/modify):', path, 'deleted in', - branch1Name, 'and modified in', branch2Name + '.', - 'Version', branch2Name, 'of', path, 'left in tree.') - mode = bMode - sha = bSha - else: - output('CONFLICT (modify/delete):', path, 'deleted in', - branch2Name, 'and modified in', branch1Name + '.', - 'Version', branch1Name, 'of', path, 'left in tree.') - mode = aMode - sha = aSha - - updateFile(False, sha, mode, path) - - elif (not oSha and aSha and not bSha) or \ - (not oSha and not aSha and bSha): - # - # Case B: Added in one. - # - if aSha: - addBranch = branch1Name - otherBranch = branch2Name - mode = aMode - sha = aSha - conf = 'file/directory' - else: - addBranch = branch2Name - otherBranch = branch1Name - mode = bMode - sha = bSha - conf = 'directory/file' - - if path in currentDirectorySet: - cleanMerge = False - newPath = uniquePath(path, addBranch) - output('CONFLICT (' + conf + '):', - 'There is a directory with name', path, 'in', - otherBranch + '. Adding', path, 'as', newPath) - - removeFile(False, path) - updateFile(False, sha, mode, newPath) - else: - output('Adding', path) - updateFile(True, sha, mode, path) - - elif not oSha and aSha and bSha: - # - # Case C: Added in both (check for same permissions). - # - if aSha == bSha: - if aMode != bMode: - cleanMerge = False - output('CONFLICT: File', path, - 'added identically in both branches, but permissions', - 'conflict', '0%o' % aMode, '->', '0%o' % bMode) - output('CONFLICT: adding with permission:', '0%o' % aMode) - - updateFile(False, aSha, aMode, path) - else: - # This case is handled by git-read-tree - assert(False) - else: - cleanMerge = False - newPath1 = uniquePath(path, branch1Name) - newPath2 = uniquePath(path, branch2Name) - output('CONFLICT (add/add): File', path, - 'added non-identically in both branches. Adding as', - newPath1, 'and', newPath2, 'instead.') - removeFile(False, path) - updateFile(False, aSha, aMode, newPath1) - updateFile(False, bSha, bMode, newPath2) - - elif oSha and aSha and bSha: - # - # case D: Modified in both, but differently. - # - output('Auto-merging', path) - [sha, mode, clean, dummy] = \ - mergeFile(path, oSha, oMode, - path, aSha, aMode, - path, bSha, bMode, - branch1Name, branch2Name) - if clean: - updateFile(True, sha, mode, path) - else: - cleanMerge = False - output('CONFLICT (content): Merge conflict in', path) - - if cacheOnly: - updateFile(False, sha, mode, path) - else: - updateFileExt(sha, mode, path, updateCache=False, updateWd=True) - else: - die("ERROR: Fatal merge failure, shouldn't happen.") - - return cleanMerge - -def usage(): - die('Usage:', sys.argv[0], ' ... -- ..') - -# main entry point as merge strategy module -# The first parameters up to -- are merge bases, and the rest are heads. - -if len(sys.argv) < 4: - usage() - -bases = [] -for nextArg in xrange(1, len(sys.argv)): - if sys.argv[nextArg] == '--': - if len(sys.argv) != nextArg + 3: - die('Not handling anything other than two heads merge.') - try: - h1 = firstBranch = sys.argv[nextArg + 1] - h2 = secondBranch = sys.argv[nextArg + 2] - except IndexError: - usage() - break - else: - bases.append(sys.argv[nextArg]) - -print 'Merging', h1, 'with', h2 - -try: - h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() - h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() - - if len(bases) == 1: - base = runProgram(['git-rev-parse', '--verify', - bases[0] + '^0']).rstrip() - ancestor = Commit(base, None) - [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), - firstBranch, secondBranch, None, 0, - ancestor) - else: - graph = buildGraph([h1, h2]) - [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], - firstBranch, secondBranch, graph) - - print '' -except: - if isinstance(sys.exc_info()[1], SystemExit): - raise - else: - traceback.print_exc(None, sys.stderr) - sys.exit(2) - -if clean: - sys.exit(0) -else: - sys.exit(1) diff --git a/git-merge.sh b/git-merge.sh index cb09438..84c3acf 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,16 +9,13 @@ USAGE='[-n] [--no-commit] [--squash] [-s ]... < LF=' ' -all_strategies='recur recursive recursive-old octopus resolve stupid ours' +all_strategies='recur recursive octopus resolve stupid ours' default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t -if test "@@NO_PYTHON@@"; then - all_strategies='recur recursive resolve octopus stupid ours' -fi dropsave() { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ diff --git a/git-rebase.sh b/git-rebase.sh index 546fa44..25530df 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -302,15 +302,6 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old" -then - die 'The recursive-old merge strategy is written in Python, -which this installation of git was not configured with. Please consider -a different merge strategy (e.g. recursive, resolve, or stupid) -or install Python and git with Python support.' - -fi - # start doing a rebase with git-merge # this is rename-aware if the recursive (default) strategy is used diff --git a/git.spec.in b/git.spec.in index 83268fc..f2374b7 100644 --- a/git.spec.in +++ b/git.spec.in @@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages. %package core Summary: Core git tools Group: Development/Tools -Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat +Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, expat %description core This is a stupid (but extremely fast) directory content manager. It doesn't do a whole lot, but what it _does_ do is track directory diff --git a/gitMergeCommon.py b/gitMergeCommon.py deleted file mode 100644 index fdbf9e4..0000000 --- a/gitMergeCommon.py +++ /dev/null @@ -1,275 +0,0 @@ -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys, re, os, traceback -from sets import Set - -def die(*args): - printList(args, sys.stderr) - sys.exit(2) - -def printList(list, file=sys.stdout): - for x in list: - file.write(str(x)) - file.write(' ') - file.write('\n') - -import subprocess - -# Debugging machinery -# ------------------- - -DEBUG = 0 -functionsToDebug = Set() - -def addDebug(func): - if type(func) == str: - functionsToDebug.add(func) - else: - functionsToDebug.add(func.func_name) - -def debug(*args): - if DEBUG: - funcName = traceback.extract_stack()[-2][2] - if funcName in functionsToDebug: - printList(args) - -# Program execution -# ----------------- - -class ProgramError(Exception): - def __init__(self, progStr, error): - self.progStr = progStr - self.error = error - - def __str__(self): - return self.progStr + ': ' + self.error - -addDebug('runProgram') -def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True): - debug('runProgram prog:', str(prog), 'input:', str(input)) - if type(prog) is str: - progStr = prog - else: - progStr = ' '.join(prog) - - try: - if pipeOutput: - stderr = subprocess.STDOUT - stdout = subprocess.PIPE - else: - stderr = None - stdout = None - pop = subprocess.Popen(prog, - shell = type(prog) is str, - stderr=stderr, - stdout=stdout, - stdin=subprocess.PIPE, - env=env) - except OSError, e: - debug('strerror:', e.strerror) - raise ProgramError(progStr, e.strerror) - - if input != None: - pop.stdin.write(input) - pop.stdin.close() - - if pipeOutput: - out = pop.stdout.read() - else: - out = '' - - code = pop.wait() - if returnCode: - ret = [out, code] - else: - ret = out - if code != 0 and not returnCode: - debug('error output:', out) - debug('prog:', prog) - raise ProgramError(progStr, out) -# debug('output:', out.replace('\0', '\n')) - return ret - -# Code for computing common ancestors -# ----------------------------------- - -currentId = 0 -def getUniqueId(): - global currentId - currentId += 1 - return currentId - -# The 'virtual' commit objects have SHAs which are integers -shaRE = re.compile('^[0-9a-f]{40}$') -def isSha(obj): - return (type(obj) is str and bool(shaRE.match(obj))) or \ - (type(obj) is int and obj >= 1) - -class Commit(object): - __slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha', - 'virtual'] - - def __init__(self, sha, parents, tree=None): - self.parents = parents - self.firstLineMsg = None - self.children = [] - - if tree: - tree = tree.rstrip() - assert(isSha(tree)) - self._tree = tree - - if not sha: - self.sha = getUniqueId() - self.virtual = True - self.firstLineMsg = 'virtual commit' - assert(isSha(tree)) - else: - self.virtual = False - self.sha = sha.rstrip() - assert(isSha(self.sha)) - - def tree(self): - self.getInfo() - assert(self._tree != None) - return self._tree - - def shortInfo(self): - self.getInfo() - return str(self.sha) + ' ' + self.firstLineMsg - - def __str__(self): - return self.shortInfo() - - def getInfo(self): - if self.virtual or self.firstLineMsg != None: - return - else: - info = runProgram(['git-cat-file', 'commit', self.sha]) - info = info.split('\n') - msg = False - for l in info: - if msg: - self.firstLineMsg = l - break - else: - if l.startswith('tree'): - self._tree = l[5:].rstrip() - elif l == '': - msg = True - -class Graph: - def __init__(self): - self.commits = [] - self.shaMap = {} - - def addNode(self, node): - assert(isinstance(node, Commit)) - self.shaMap[node.sha] = node - self.commits.append(node) - for p in node.parents: - p.children.append(node) - return node - - def reachableNodes(self, n1, n2): - res = {} - def traverse(n): - res[n] = True - for p in n.parents: - traverse(p) - - traverse(n1) - traverse(n2) - return res - - def fixParents(self, node): - for x in range(0, len(node.parents)): - node.parents[x] = self.shaMap[node.parents[x]] - -# addDebug('buildGraph') -def buildGraph(heads): - debug('buildGraph heads:', heads) - for h in heads: - assert(isSha(h)) - - g = Graph() - - out = runProgram(['git-rev-list', '--parents'] + heads) - for l in out.split('\n'): - if l == '': - continue - shas = l.split(' ') - - # This is a hack, we temporarily use the 'parents' attribute - # to contain a list of SHA1:s. They are later replaced by proper - # Commit objects. - c = Commit(shas[0], shas[1:]) - - g.commits.append(c) - g.shaMap[c.sha] = c - - for c in g.commits: - g.fixParents(c) - - for c in g.commits: - for p in c.parents: - p.children.append(c) - return g - -# Write the empty tree to the object database and return its SHA1 -def writeEmptyTree(): - tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index' - def delTmpIndex(): - try: - os.unlink(tmpIndex) - except OSError: - pass - delTmpIndex() - newEnv = os.environ.copy() - newEnv['GIT_INDEX_FILE'] = tmpIndex - res = runProgram(['git-write-tree'], env=newEnv).rstrip() - delTmpIndex() - return res - -def addCommonRoot(graph): - roots = [] - for c in graph.commits: - if len(c.parents) == 0: - roots.append(c) - - superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree()) - graph.addNode(superRoot) - for r in roots: - r.parents = [superRoot] - superRoot.children = roots - return superRoot - -def getCommonAncestors(graph, commit1, commit2): - '''Find the common ancestors for commit1 and commit2''' - assert(isinstance(commit1, Commit) and isinstance(commit2, Commit)) - - def traverse(start, set): - stack = [start] - while len(stack) > 0: - el = stack.pop() - set.add(el) - for p in el.parents: - if p not in set: - stack.append(p) - h1Set = Set() - h2Set = Set() - traverse(commit1, h1Set) - traverse(commit2, h2Set) - shared = h1Set.intersection(h2Set) - - if len(shared) == 0: - shared = [addCommonRoot(graph)] - - res = Set() - - for s in shared: - if len([c for c in s.children if c in shared]) == 0: - res.add(s) - return list(res) diff --git a/t/Makefile b/t/Makefile index 8983509..e1c6c92 100644 --- a/t/Makefile +++ b/t/Makefile @@ -13,10 +13,6 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) TSVN = $(wildcard t91[0-9][0-9]-*.sh) -ifdef NO_PYTHON - GIT_TEST_OPTS += --no-python -endif - all: $(T) clean $(T): diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 6aff0b8..81f3bed 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -20,10 +20,10 @@ modification *should* take notice and update the test vectors here. ################################################################ # It appears that people are getting bitten by not installing -# 'merge' (usually part of RCS package in binary distributions) -# or have too old python without subprocess. Check them and error -# out before running any tests. Also catch the bogosity of trying -# to run tests without building while we are at it. +# 'merge' (usually part of RCS package in binary distributions). +# Check this and error out before running any tests. Also catch +# the bogosity of trying to run tests without building while we +# are at it. ../git >/dev/null if test $? != 1 @@ -42,12 +42,6 @@ fi . ./test-lib.sh -test "$no_python" || "$PYTHON" -c 'import subprocess' || { - echo >&2 'Your python seem to lack "subprocess" module. -Please check INSTALL document.' - exit 1 -} - ################################################################ # init-db has been done in an empty repository. # make sure it is empty. diff --git a/t/test-lib.sh b/t/test-lib.sh index 3895f16..ac7be76 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -76,7 +76,8 @@ do -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; --no-python) - no_python=t; shift ;; + # noop now... + shift ;; *) break ;; esac @@ -210,18 +211,6 @@ GIT_EXEC_PATH=$(pwd)/.. HOME=$(pwd)/trash export PATH GIT_EXEC_PATH HOME -# Similarly use ../compat/subprocess.py if our python does not -# have subprocess.py on its own. -PYTHON=`sed -e '1{ - s/^#!// - q -}' ../git-merge-recursive-old` || { - error "You haven't built things yet, have you?" -} -"$PYTHON" -c 'import subprocess' 2>/dev/null || { - PYTHONPATH=$(pwd)/../compat - export PYTHONPATH -} GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git export GITPERLLIB test -d ../templates/blt || { -- cgit v0.10.2-6-g49f6 From 17bcdad3b7baa3b12c662663372f1e3cd560dd8e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 20 Nov 2006 01:06:09 -0800 Subject: git-merge: make it usable as the first class UI This teaches the oft-requested syntax git merge $commit to implement merging the named commit to the current branch. This hopefully would make "git merge" usable as the first class UI instead of being a mere backend for "git pull". Most notably, $commit above can be any committish, so you can say for example: git merge js/shortlog~2 to merge early part of a topic branch without merging the rest of it. A custom merge message can be given with the new --message= parameter. The message is prepended in front of the usual "Merge ..." message autogenerated with fmt-merge-message. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index bebf30a..e2954aa 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -8,12 +8,14 @@ git-merge - Grand Unified Merge Driver SYNOPSIS -------- -'git-merge' [-n] [--no-commit] [-s ]... ... - +[verse] +'git-merge' [-n] [--no-commit] [--squash] [-s ]... + [--reflog-action=] + -m= ... DESCRIPTION ----------- -This is the top-level user interface to the merge machinery +This is the top-level interface to the merge machinery which drives multiple merge strategy scripts. @@ -27,13 +29,19 @@ include::merge-options.txt[] to give a good default for automated `git-merge` invocations. :: - our branch head commit. + Our branch head commit. This has to be `HEAD`, so new + syntax does not require it :: - other branch head merged into our branch. You need at + Other branch head merged into our branch. You need at least one . Specifying more than one obviously means you are trying an Octopus. +--reflog-action=:: + This is used internally when `git-pull` calls this command + to record that the merge was created by `pull` command + in the `ref-log` entry that results from the merge. + include::merge-strategies.txt[] diff --git a/git-merge.sh b/git-merge.sh index 84c3acf..25deb1e 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -3,7 +3,8 @@ # Copyright (c) 2005 Junio C Hamano # -USAGE='[-n] [--no-commit] [--squash] [-s ]... +' +USAGE='[-n] [--no-commit] [--squash] [-s ] [--reflog-action=] [-m=] +' + . git-sh-setup LF=' @@ -92,7 +93,7 @@ finish () { case "$#" in 0) usage ;; esac -rloga= +rloga= have_message= while case "$#" in 0) break ;; esac do case "$1" in @@ -125,17 +126,63 @@ do --reflog-action=*) rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; + -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) + merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'` + have_message=t + ;; + -m|--m|--me|--mes|--mess|--messa|--messag|--message) + shift + case "$#" in + 1) usage ;; + esac + merge_msg="$1" + have_message=t + ;; -*) usage ;; *) break ;; esac shift done -merge_msg="$1" -shift -head_arg="$1" -head=$(git-rev-parse --verify "$1"^0) || usage -shift +# This could be traditional "merge HEAD ..." and the +# way we can tell it is to see if the second token is HEAD, but some +# people might have misused the interface and used a committish that +# is the same as HEAD there instead. Traditional format never would +# have "-m" so it is an additional safety measure to check for it. + +if test -z "$have_message" && + second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) && + head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) && + test "$second_token" = "$head_commit" +then + merge_msg="$1" + shift + head_arg="$1" + shift +else + # We are invoked directly as the first-class UI. + head_arg=HEAD + + # All the rest are the commits being merged; prepare + # the standard merge summary message to be appended to + # the given message. If remote is invalid we will die + # later in the common codepath so we discard the error + # in this loop. + merge_name=$(for remote + do + rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) + if git show-ref -q --verify "refs/heads/$remote" + then + what=branch + else + what=commit + fi + echo "$rh $what '$remote'" + done | git-fmt-merge-msg + ) + merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name" +fi +head=$(git-rev-parse --verify "$head_arg"^0) || usage # All the rest are remote heads test "$#" = 0 && usage ;# we need at least one remote head. -- cgit v0.10.2-6-g49f6 From 8092c7f6af044836abf83ed26d542327a4b95c08 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 21 Nov 2006 21:13:28 -0800 Subject: merge: allow merging into a yet-to-be-born branch. Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index 25deb1e..dd4e83d 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -159,6 +159,24 @@ then shift head_arg="$1" shift +elif ! git-rev-parse --verify HEAD 2>/dev/null +then + # If the merged head is a valid one there is no reason to + # forbid "git merge" into a branch yet to be born. We do + # the same for "git pull". + if test 1 -ne $# + then + echo >&2 "Can merge only exactly one commit into empty head" + exit 1 + fi + + rh=$(git rev-parse --verify "$1^0") || + die "$1 - not something we can merge" + + git-update-ref -m "initial pull" HEAD "$rh" "" && + git-read-tree --reset -u HEAD + exit + else # We are invoked directly as the first-class UI. head_arg=HEAD -- cgit v0.10.2-6-g49f6 From bfcc921430423828233dadfe274802ae68900f6e Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Tue, 21 Nov 2006 19:31:24 +0000 Subject: Add support to git-branch to show local and remote branches Instead of storing a list of refnames in append_ref, a list of structures is created. Each of these stores the refname and a symbolic constant representing its type. The creation of the list is filtered based on a command line switch; no switch means "local branches only", "-r" means "remote branches only" (as they always did); but now "-a" means "local branches or remote branches". As a side effect, the list is now not global, but allocated in print_ref_list() where it used. Also a memory leak is plugged, the memory allocated during the list creation was never freed. It lays a groundwork to also display tags, but the command being 'git branch' it is not currently used. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index d43ef1d..5376760 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,14 +8,16 @@ git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [-r] +'git-branch' [-r] [-a] 'git-branch' [-l] [-f] [] 'git-branch' (-d | -D) ... 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. +Option `-r` causes the remote-tracking branches to be listed, +and option `-a` shows both. In its second form, a new branch named will be created. It will start out with a head equal to the one given as . @@ -45,7 +47,10 @@ OPTIONS a branch that already exists with the same name. -r:: - List only the "remote" branches. + List the remote-tracking branches. + +-a:: + List both remote-tracking branches and local branches. :: The name of the branch to create or delete. diff --git a/builtin-branch.c b/builtin-branch.c index 368b68e..22e3285 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -11,7 +11,7 @@ #include "builtin.h" static const char builtin_branch_usage[] = -"git-branch (-d | -D) | [-l] [-f] [] | [-r]"; +"git-branch (-d | -D) | [-l] [-f] [] | [-r] | [-a]"; static const char *head; @@ -79,46 +79,100 @@ static void delete_branches(int argc, const char **argv, int force) } } -static int ref_index, ref_alloc; -static char **ref_list; +#define REF_UNKNOWN_TYPE 0x00 +#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, - void *cb_data) +struct ref_item { + char *name; + unsigned int kind; +}; + +struct ref_list { + int index, alloc; + 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) { - ref_alloc = alloc_nr(ref_alloc); - ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *)); + struct ref_list *ref_list = (struct ref_list*)(cb_data); + struct ref_item *newitem; + int kind = REF_UNKNOWN_TYPE; + + /* 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; + } + + /* 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)); } - ref_list[ref_index++] = xstrdup(refname); + /* Record the new item */ + newitem = &(ref_list->list[ref_list->index++]); + newitem->name = xstrdup(refname); + newitem->kind = kind; return 0; } +static void free_ref_list(struct ref_list *ref_list) +{ + int i; + + for (i = 0; i < ref_list->index; i++) + free(ref_list->list[i].name); + free(ref_list->list); +} + static int ref_cmp(const void *r1, const void *r2) { - return strcmp(*(char **)r1, *(char **)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_list(int remote_only) +static void print_ref_list(int kinds) { int i; char c; + struct ref_list ref_list; - if (remote_only) - for_each_remote_ref(append_ref, NULL); - else - for_each_branch_ref(append_ref, NULL); + memset(&ref_list, 0, sizeof(ref_list)); + ref_list.kinds = kinds; + for_each_ref(append_ref, &ref_list); - 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 = ' '; - if (!strcmp(ref_list[i], head)) + if (ref_list.list[i].kind == REF_LOCAL_BRANCH && + !strcmp(ref_list.list[i].name, head)) c = '*'; - printf("%c %s\n", c, ref_list[i]); + 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, @@ -160,8 +214,9 @@ static void create_branch(const char *name, const char *start, 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 reflog = 0; + int kinds = REF_LOCAL_BRANCH; int i; git_config(git_default_config); @@ -189,7 +244,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "-r")) { - remote_only = 1; + kinds = REF_REMOTE_BRANCH; + continue; + } + if (!strcmp(arg, "-a")) { + kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH; continue; } if (!strcmp(arg, "-l")) { @@ -209,7 +268,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) delete_branches(argc - i, argv + i, force_delete); else if (i == argc) - print_ref_list(remote_only); + print_ref_list(kinds); else if (i == argc - 1) create_branch(argv[i], head, force_create, reflog); else if (i == argc - 2) -- cgit v0.10.2-6-g49f6 From f4204ab9f6a192cdb9a68150e031d7183688bfeb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 21 Nov 2006 23:36:35 -0800 Subject: Store peeled refs in packed-refs (take 2). This fixes the previous implementation which failed to optimize repositories with tons of lightweight tags. The updated packed-refs format begins with "# packed-refs with:" line that lists the kind of extended data the file records. Currently, there is only one such extension defined, "peeled". This stores the "peeled tag" on a line that immediately follows a line for a tag object itself in the format "^". The header line itself and any extended data are ignored by older implementation, so packed-refs file generated with this version can still be used by older git. packed-refs made by older git can of course be used with this version. Signed-off-by: Junio C Hamano diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index ee5a556..8dc5b9e 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -46,8 +46,8 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, if (o->type == OBJ_TAG) { o = deref_tag(o, path, 0); if (o) - fprintf(cb->refs_file, "%s %s^{}\n", - sha1_to_hex(o->sha1), path); + fprintf(cb->refs_file, "^%s\n", + sha1_to_hex(o->sha1)); } } @@ -111,6 +111,10 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) if (!cbdata.refs_file) die("unable to create ref-pack file structure (%s)", strerror(errno)); + + /* perhaps other traits later as well */ + fprintf(cbdata.refs_file, "# pack-refs with: peeled \n"); + for_each_ref(handle_one_ref, &cbdata); fflush(cbdata.refs_file); fsync(fd); diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 9ae3d08..0739798 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -67,8 +67,10 @@ match: return 0; if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) { - hex = find_unique_abbrev(peeled, abbrev); - printf("%s %s^{}\n", hex, refname); + if (!is_null_sha1(peeled)) { + hex = find_unique_abbrev(peeled, abbrev); + printf("%s %s^{}\n", hex, refname); + } } else { obj = parse_object(sha1); diff --git a/refs.c b/refs.c index 75cbc0e..96ea8b6 100644 --- a/refs.c +++ b/refs.c @@ -5,14 +5,18 @@ #include +/* ISSYMREF=01 and ISPACKED=02 are public interfaces */ +#define REF_KNOWS_PEELED 04 + struct ref_list { struct ref_list *next; - unsigned char flag; /* ISSYMREF? ISPACKED? ISPEELED? */ + unsigned char flag; /* ISSYMREF? ISPACKED? */ unsigned char sha1[20]; + unsigned char peeled[20]; char name[FLEX_ARRAY]; }; -static const char *parse_ref_line(char *line, unsigned char *sha1, int *flag) +static const char *parse_ref_line(char *line, unsigned char *sha1) { /* * 42: the answer to everything. @@ -23,7 +27,6 @@ static const char *parse_ref_line(char *line, unsigned char *sha1, int *flag) * +1 (newline at the end of the line) */ int len = strlen(line) - 42; - int peeled = 0; if (len <= 0) return NULL; @@ -32,29 +35,18 @@ static const char *parse_ref_line(char *line, unsigned char *sha1, int *flag) if (!isspace(line[40])) return NULL; line += 41; - - if (isspace(*line)) { - /* "SHA-1 SP SP refs/tags/tagname^{} LF"? */ - line++; - len--; - peeled = 1; - } + if (isspace(*line)) + return NULL; if (line[len] != '\n') return NULL; line[len] = 0; - if (peeled && (len < 3 || strcmp(line + len - 3, "^{}"))) - return NULL; - - if (!peeled) - *flag &= ~REF_ISPEELED; - else - *flag |= REF_ISPEELED; return line; } static struct ref_list *add_ref(const char *name, const unsigned char *sha1, - int flag, struct ref_list *list) + int flag, struct ref_list *list, + struct ref_list **new_entry) { int len; struct ref_list **p = &list, *entry; @@ -66,8 +58,11 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, break; /* Same as existing entry? */ - if (!cmp) + if (!cmp) { + if (new_entry) + *new_entry = entry; return list; + } p = &entry->next; } @@ -75,10 +70,13 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, len = strlen(name) + 1; entry = xmalloc(sizeof(struct ref_list) + len); hashcpy(entry->sha1, sha1); + hashclr(entry->peeled); memcpy(entry->name, name, len); entry->flag = flag; entry->next = *p; *p = entry; + if (new_entry) + *new_entry = entry; return list; } @@ -114,27 +112,50 @@ static void invalidate_cached_refs(void) ca->did_loose = ca->did_packed = 0; } +static void read_packed_refs(FILE *f, struct cached_refs *cached_refs) +{ + struct ref_list *list = NULL; + struct ref_list *last = NULL; + char refline[PATH_MAX]; + int flag = REF_ISPACKED; + + while (fgets(refline, sizeof(refline), f)) { + unsigned char sha1[20]; + const char *name; + static const char header[] = "# pack-refs with:"; + + if (!strncmp(refline, header, sizeof(header)-1)) { + const char *traits = refline + sizeof(header) - 1; + if (strstr(traits, " peeled ")) + flag |= REF_KNOWS_PEELED; + /* perhaps other traits later as well */ + continue; + } + + name = parse_ref_line(refline, sha1); + if (name) { + list = add_ref(name, sha1, flag, list, &last); + continue; + } + if (last && + refline[0] == '^' && + strlen(refline) == 42 && + refline[41] == '\n' && + !get_sha1_hex(refline + 1, sha1)) + hashcpy(last->peeled, sha1); + } + cached_refs->packed = list; +} + static struct ref_list *get_packed_refs(void) { if (!cached_refs.did_packed) { - struct ref_list *refs = NULL; FILE *f = fopen(git_path("packed-refs"), "r"); + cached_refs.packed = NULL; if (f) { - struct ref_list *list = NULL; - char refline[PATH_MAX]; - while (fgets(refline, sizeof(refline), f)) { - unsigned char sha1[20]; - int flag = REF_ISPACKED; - const char *name = - parse_ref_line(refline, sha1, &flag); - if (!name) - continue; - list = add_ref(name, sha1, flag, list); - } + read_packed_refs(f, &cached_refs); fclose(f); - refs = list; } - cached_refs.packed = refs; cached_refs.did_packed = 1; } return cached_refs.packed; @@ -177,7 +198,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) error("%s points nowhere!", ref); continue; } - list = add_ref(ref, sha1, flag, list); + list = add_ref(ref, sha1, flag, list, NULL); } free(ref); closedir(dir); @@ -225,8 +246,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * if (lstat(path, &st) < 0) { struct ref_list *list = get_packed_refs(); while (list) { - if (!(list->flag & REF_ISPEELED) && - !strcmp(ref, list->name)) { + if (!strcmp(ref, list->name)) { hashcpy(sha1, list->sha1); if (flag) *flag |= REF_ISPACKED; @@ -348,8 +368,6 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return 0; if (is_null_sha1(entry->sha1)) return 0; - if (entry->flag & REF_ISPEELED) - return 0; if (!has_sha1_file(entry->sha1)) { error("%s does not point to a valid object!", entry->name); return 0; @@ -368,22 +386,21 @@ int peel_ref(const char *ref, unsigned char *sha1) if ((flag & REF_ISPACKED)) { struct ref_list *list = get_packed_refs(); - int len = strlen(ref); while (list) { - if ((list->flag & REF_ISPEELED) && - !strncmp(list->name, ref, len) && - strlen(list->name) == len + 3 && - !strcmp(list->name + len, "^{}")) { - hashcpy(sha1, list->sha1); - return 0; + if (!strcmp(list->name, ref)) { + if (list->flag & REF_KNOWS_PEELED) { + hashcpy(sha1, list->peeled); + return 0; + } + /* older pack-refs did not leave peeled ones */ + break; } list = list->next; } - /* older pack-refs did not leave peeled ones in */ } - /* otherwise ... */ + /* fallback - callers should not call this for unpacked refs */ o = parse_object(base); if (o->type == OBJ_TAG) { o = deref_tag(o, ref, 0); diff --git a/refs.h b/refs.h index 40048a6..cd1e1d6 100644 --- a/refs.h +++ b/refs.h @@ -10,14 +10,13 @@ struct ref_lock { int force_write; }; +#define REF_ISSYMREF 01 +#define REF_ISPACKED 02 + /* * Calls the specified function for each ref file until it returns nonzero, * and returns the value */ -#define REF_ISSYMREF 01 -#define REF_ISPACKED 02 -#define REF_ISPEELED 04 /* internal use */ - typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); -- cgit v0.10.2-6-g49f6 From 5d1faf8791fa85ba9be2282900d4f704c7790648 Mon Sep 17 00:00:00 2001 From: Chris Riddoch Date: Tue, 21 Nov 2006 16:49:15 -0700 Subject: Move --pretty options into Documentation/pretty-formats.txt Asciidoc-include it into the manuals for programs that use the --pretty command-line option, for consistency among the docs. This describes all the pretty-formats currently listed in the cmit_fmt enum in commit.h, and also briefly describes the presence and format of the 'Merge: ' line in some pretty formats. There's a hedge that limiting your view of history can affect what goes in the Merge: line, and that --abbrev/--no-abbrev do nothing to the 'raw' format. Signed-off-by: Chris Riddoch Signed-off-by: Junio C Hamano diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt index f7e8ff2..5d6e9dc 100644 --- a/Documentation/git-diff-tree.txt +++ b/Documentation/git-diff-tree.txt @@ -73,10 +73,7 @@ separated with a single space are given. This flag causes "git-diff-tree --stdin" to also show the commit message before the differences. ---pretty[=(raw|medium|short)]:: - This is used to control "pretty printing" format of the - commit message. Without "=