Merge branch 'lt/rev-list' into next
authorJunio C Hamano <junkio@cox.net>
Sun, 26 Feb 2006 23:33:49 +0000 (15:33 -0800)
committerJunio C Hamano <junkio@cox.net>
Sun, 26 Feb 2006 23:33:49 +0000 (15:33 -0800)
* lt/rev-list:
  First cut at libifying revlist generation
  Merge branch 'maint'
  sample hooks template.
  Teach the "git" command to handle some commands internally
  Use setenv(), fix warnings
  contrib/git-svn: version 0.10.0
  contrib/git-svn: optimize sequential commits to svn
  contrib/git-svn: add show-ignore command
  annotate: Use qx{} for pipes on activestate.
  annotate: Convert all -| calls to use a helper open_pipe().
  annotate: Handle dirty state and arbitrary revisions.
  git-fetch: print the new and old ref when fast-forwarding

19 files changed:
Makefile
cache.h
contrib/git-svn/git-svn.perl
contrib/git-svn/git-svn.txt
epoch.c
epoch.h
exec_cmd.c
fetch-pack.c
fsck-objects.c
git-annotate.perl
git-fetch.sh
git.c
pack-objects.c
pack-redundant.c
rev-list.c
revision.c [new file with mode: 0644]
revision.h [new file with mode: 0644]
templates/hooks--applypatch-msg
templates/hooks--pre-applypatch

index 26fd9dc..5e93f27 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -192,7 +192,7 @@ LIB_FILE=libgit.a
 LIB_H = \
        blob.h cache.h commit.h count-delta.h csum-file.h delta.h \
        diff.h epoch.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
+       run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h
 
 DIFF_OBJS = \
        diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
@@ -205,7 +205,7 @@ LIB_OBJS = \
        quote.o read-cache.o refs.o run-command.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 \
+       fetch-clone.o revision.o \
        $(DIFF_OBJS)
 
 LIBS = $(LIB_FILE)
diff --git a/cache.h b/cache.h
index 5020f07..58eec00 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -10,7 +10,7 @@
 #define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
 #endif
 
-#if defined(DT_UNKNOWN) && !NO_D_TYPE_IN_DIRENT
+#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
 #define DTYPE(de)      ((de)->d_type)
 #else
 #undef DT_UNKNOWN
index a32ce15..0b74165 100755 (executable)
@@ -8,7 +8,7 @@ use vars qw/    $AUTHOR $VERSION
                $GIT_SVN_INDEX $GIT_SVN
                $GIT_DIR $REV_DIR/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
-$VERSION = '0.9.1';
+$VERSION = '0.10.0';
 $GIT_DIR = $ENV{GIT_DIR} || "$ENV{PWD}/.git";
 $GIT_SVN = $ENV{GIT_SVN_ID} || 'git-svn';
 $GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index";
@@ -30,6 +30,7 @@ use File::Basename qw/dirname basename/;
 use File::Path qw/mkpath/;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
 use File::Spec qw//;
+use POSIX qw/strftime/;
 my $sha1 = qr/[a-f\d]{40}/;
 my $sha1_short = qr/[a-f\d]{6,40}/;
 my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
@@ -49,6 +50,7 @@ my %cmd = (
        fetch => [ \&fetch, "Download new revisions from SVN" ],
        init => [ \&init, "Initialize and fetch (import)"],
        commit => [ \&commit, "Commit git revisions to SVN" ],
+       'show-ignore' => [ \&show_ignore, "Show svn:ignore listings" ],
        rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)" ],
        help => [ \&usage, "Show help" ],
 );
@@ -258,6 +260,30 @@ sub commit {
 
 }
 
+sub show_ignore {
+       require File::Find or die $!;
+       my $exclude_file = "$GIT_DIR/info/exclude";
+       open my $fh, '<', $exclude_file or croak $!;
+       chomp(my @excludes = (<$fh>));
+       close $fh or croak $!;
+
+       $SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url");
+       chdir $SVN_WC or croak $!;
+       my %ign;
+       File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){
+               s#^\./##;
+               @{$ign{$_}} = safe_qx(qw(svn propget svn:ignore),$_);
+               }}, no_chdir=>1},'.');
+
+       print "\n# /\n";
+       foreach (@{$ign{'.'}}) { print '/',$_ if /\S/ }
+       delete $ign{'.'};
+       foreach my $i (sort keys %ign) {
+               print "\n# ",$i,"\n";
+               foreach (@{$ign{$i}}) { print '/',$i,'/',$_ if /\S/ }
+       }
+}
+
 ########################### utility functions #########################
 
 sub setup_git_svn {
@@ -566,6 +592,7 @@ sub handle_rmdir {
 sub svn_commit_tree {
        my ($svn_rev, $commit) = @_;
        my $commit_msg = "$GIT_DIR/$GIT_SVN/.svn-commit.tmp.$$";
+       my %log_msg = ( msg => '' );
        open my $msg, '>', $commit_msg  or croak $!;
 
        chomp(my $type = `git-cat-file -t $commit`);
@@ -581,6 +608,7 @@ sub svn_commit_tree {
                        if (!$in_msg) {
                                $in_msg = 1 if (/^\s*$/);
                        } else {
+                               $log_msg{msg} .= $_;
                                print $msg $_ or croak $!;
                        }
                }
@@ -600,9 +628,30 @@ sub svn_commit_tree {
                        join("\n",@ci_output),"\n";
        my ($rev_committed) = ($committed =~ /^Committed revision (\d+)\./);
 
-       # resync immediately
-       my @svn_up = (qw(svn up), "-r$svn_rev");
+       my @svn_up = qw(svn up);
        push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
+       if ($rev_committed == ($svn_rev + 1)) {
+               push @svn_up, "-r$rev_committed";
+               sys(@svn_up);
+               my $info = svn_info('.');
+               my $date = $info->{'Last Changed Date'} or die "Missing date\n";
+               if ($info->{'Last Changed Rev'} != $rev_committed) {
+                       croak "$info->{'Last Changed Rev'} != $rev_committed\n"
+               }
+               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)
+                                        or croak "Failed to parse date: $date\n";
+               $log_msg{date} = "$tz $Y-$m-$d $H:$M:$S";
+               $log_msg{author} = $info->{'Last Changed Author'};
+               $log_msg{revision} = $rev_committed;
+               $log_msg{msg} .= "\n";
+               my $parent = file_to_s("$REV_DIR/$svn_rev");
+               git_commit(\%log_msg, $parent, $commit);
+               return $rev_committed;
+       }
+       # resync immediately
+       push @svn_up, "-r$svn_rev";
        sys(@svn_up);
        return fetch("$rev_committed=$commit")->{revision};
 }
@@ -699,7 +748,7 @@ sub svn_info {
        # only single-lines seem to exist in svn info output
        while (<$info_fh>) {
                chomp $_;
-               if (m#^([^:]+)\s*:\s*(\S*)$#) {
+               if (m#^([^:]+)\s*:\s*(\S.*)$#) {
                        $ret->{$1} = $2;
                        push @{$ret->{-order}}, $1;
                }
index cf098d7..b4b7789 100644 (file)
@@ -61,6 +61,11 @@ rebuild::
        the directory/repository you're tracking has moved or changed
        protocols.
 
+show-ignore::
+       Recursively finds and lists the svn:ignore property on
+       directories.  The output is suitable for appending to
+       the $GIT_DIR/info/exclude file.
+
 OPTIONS
 -------
 -r <ARG>::
@@ -152,6 +157,8 @@ Tracking and contributing to an Subversion managed-project:
        git commit git-svn-HEAD..my-branch
 # Something is committed to SVN, pull the latest into your branch::
        git-svn fetch && git pull . git-svn-HEAD
+# Append svn:ignore settings to the default git exclude file:
+       git-svn show-ignore >> .git/info/exclude
 
 DESIGN PHILOSOPHY
 -----------------
diff --git a/epoch.c b/epoch.c
index 3a76748..0f37492 100644 (file)
--- a/epoch.c
+++ b/epoch.c
@@ -15,6 +15,7 @@
 
 #include "cache.h"
 #include "commit.h"
+#include "revision.h"
 #include "epoch.h"
 
 struct fraction {
diff --git a/epoch.h b/epoch.h
index 7493d5a..3756009 100644 (file)
--- a/epoch.h
+++ b/epoch.h
@@ -11,7 +11,6 @@ typedef int (*emitter_func) (struct commit *);
 int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
 
 /* Low bits are used by rev-list */
-#define UNINTERESTING   (1u<<10)
 #define BOUNDARY        (1u<<11)
 #define VISITED         (1u<<12)
 #define DISCONTINUITY   (1u<<13)
index 55af33b..b5e59a9 100644 (file)
@@ -13,7 +13,7 @@ void git_set_exec_path(const char *exec_path)
 
 
 /* Returns the highest-priority, location to look for git programs. */
-const char *git_exec_path()
+const char *git_exec_path(void)
 {
        const char *env;
 
index 09738fe..535de10 100644 (file)
@@ -82,7 +82,7 @@ static void mark_common(struct commit *commit,
   Get the next rev to send, ignoring the common.
 */
 
-static const unsigned char* get_rev()
+static const unsigned char* get_rev(void)
 {
        struct commit *commit = NULL;
 
index 6439d55..4ddd676 100644 (file)
@@ -20,7 +20,7 @@ static int check_strict = 0;
 static int keep_cache_objects = 0; 
 static unsigned char head_sha1[20];
 
-#if NO_D_INO_IN_DIRENT
+#ifdef NO_D_INO_IN_DIRENT
 #define SORT_DIRENT 0
 #define DIRENT_SORT_HINT(de) 0
 #else
index 3800c46..f9c2c6c 100755 (executable)
@@ -8,44 +8,62 @@
 
 use warnings;
 use strict;
-use Getopt::Std;
+use Getopt::Long;
 use POSIX qw(strftime gmtime);
 
 sub usage() {
-       print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file
-
-       -l              show long rev
-       -r              follow renames
-       -S commit       use revs from revs-file instead of calling git-rev-list
+       print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
+       -l, --long
+                       Show long rev (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 ($opt_h, $opt_l, $opt_r, $opt_S);
-getopts("hlrS:") or usage();
-$opt_h && usage();
+our ($help, $longrev, $rename, $starting_rev, $rev_file) = (0, 0, 1);
+
+my $rc = GetOptions(   "long|l" => \$longrev,
+                       "help|h" => \$help,
+                       "rename|r" => \$rename,
+                       "rev-file|S" => \$rev_file);
+if (!$rc or $help) {
+       usage();
+}
 
 my $filename = shift @ARGV;
+if (@ARGV) {
+       $starting_rev = shift @ARGV;
+}
 
 my @stack = (
        {
-               'rev' => "HEAD",
+               'rev' => defined $starting_rev ? $starting_rev : "HEAD",
                'filename' => $filename,
        },
 );
 
-our (@lineoffsets, @pendinglineoffsets);
 our @filelines = ();
-open(F,"<",$filename)
-       or die "Failed to open filename: $!";
 
-while(<F>) {
-       chomp;
-       push @filelines, $_;
+if (defined $starting_rev) {
+       @filelines = git_cat_file($starting_rev, $filename);
+} else {
+       open(F,"<",$filename)
+               or die "Failed to open filename: $!";
+
+       while(<F>) {
+               chomp;
+               push @filelines, $_;
+       }
+       close(F);
+
 }
-close(F);
-our $leftover_lines = @filelines;
+
 our %revs;
 our @revqueue;
 our $head;
@@ -66,7 +84,7 @@ while (my $bound = pop @stack) {
                        next;
                }
 
-               if (!$opt_r) {
+               if (!$rename) {
                        next;
                }
 
@@ -78,8 +96,18 @@ while (my $bound = pop @stack) {
        }
 }
 push @revqueue, $head;
-init_claim($head);
-$revs{$head}{'lineoffsets'} = {};
+init_claim( defined $starting_rev ? $starting_rev : '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);
+}
 handle_rev();
 
 
@@ -88,7 +116,7 @@ foreach my $l (@filelines) {
        my ($output, $rev, $committer, $date);
        if (ref $l eq 'ARRAY') {
                ($output, $rev, $committer, $date) = @$l;
-               if (!$opt_l && length($rev) > 8) {
+               if (!$longrev && length($rev) > 8) {
                        $rev = substr($rev,0,8);
                }
        } else {
@@ -102,7 +130,6 @@ foreach my $l (@filelines) {
 
 sub init_claim {
        my ($rev) = @_;
-       my %revinfo = git_commit_info($rev);
        for (my $i = 0; $i < @filelines; $i++) {
                $filelines[$i] = [ $filelines[$i], '', '', '', 1];
                        # line,
@@ -117,7 +144,9 @@ sub init_claim {
 
 sub handle_rev {
        my $i = 0;
+       my %seen;
        while (my $rev = shift @revqueue) {
+               next if $seen{$rev}++;
 
                my %revinfo = git_commit_info($rev);
 
@@ -143,20 +172,21 @@ sub handle_rev {
 sub git_rev_list {
        my ($rev, $file) = @_;
 
-       if ($opt_S) {
-               open(P, '<' . $opt_S);
+       my $revlist;
+       if ($rev_file) {
+               open($revlist, '<' . $rev_file);
        } else {
-               open(P,"-|","git-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;
-       while(my $line = <P>) {
+       while(my $line = <$revlist>) {
                chomp $line;
                my ($rev, @parents) = split /\s+/, $line;
                push @revs, [ $rev, @parents ];
        }
-       close(P);
+       close($revlist);
 
        printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0);
        return @revs;
@@ -165,22 +195,22 @@ sub git_rev_list {
 sub find_parent_renames {
        my ($rev, $file) = @_;
 
-       open(P,"-|","git-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;
-       my $junk = <P>;
-       while (my $change = <P>) {
+       my $junk = <$patch>;
+       while (my $change = <$patch>) {
                chomp $change;
-               my $filename = <P>;
+               my $filename = <$patch>;
                chomp $filename;
 
                if ($change =~ m/^[AMD]$/ ) {
                        next;
                } elsif ($change =~ m/^R/ ) {
                        my $oldfilename = $filename;
-                       $filename = <P>;
+                       $filename = <$patch>;
                        chomp $filename;
                        if ( $file eq $filename ) {
                                my $parent = git_find_parent($rev, $oldfilename);
@@ -189,7 +219,7 @@ sub find_parent_renames {
                        }
                }
        }
-       close(P);
+       close($patch);
 
        return \%bound;
 }
@@ -198,14 +228,14 @@ sub find_parent_renames {
 sub git_find_parent {
        my ($rev, $filename) = @_;
 
-       open(REVPARENT,"-|","git-rev-list","--remove-empty", "--parents","--max-count=1","$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>;
+       my $parentline = <$revparent>;
        chomp $parentline;
        my ($revfound,$parent) = split m/\s+/, $parentline;
 
-       close(REVPARENT);
+       close($revparent);
 
        return $parent;
 }
@@ -216,24 +246,31 @@ sub git_find_parent {
 sub git_diff_parse {
        my ($parent, $rev, %revinfo) = @_;
 
-       my ($ri, $pi) = (0,0);
-       open(DIFF,"-|","git-diff-tree","-M","-p",$rev,$parent,"--",
+       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: $!";
 
+       _git_diff_parse($diff, $parent, $rev, %revinfo);
+
+       close($diff);
+}
+
+sub _git_diff_parse {
+       my ($diff, $parent, $rev, %revinfo) = @_;
+
+       my ($ri, $pi) = (0,0);
        my $slines = $revs{$rev}{'lines'};
        my @plines;
 
        my $gotheader = 0;
-       my ($remstart, $remlength, $addstart, $addlength);
-       my ($hunk_start, $hunk_index, $hunk_adds);
-       while(<DIFF>) {
+       my ($remstart);
+       my ($hunk_start, $hunk_index);
+       while(<$diff>) {
                chomp;
                if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) {
-                       ($remstart, $remlength, $addstart, $addlength) = ($1, $2, $3, $4);
+                       $remstart = $1;
                        # Adjust for 0-based arrays
                        $remstart--;
-                       $addstart--;
                        # Reinit hunk tracking.
                        $hunk_start = $remstart;
                        $hunk_index = 0;
@@ -279,7 +316,6 @@ sub git_diff_parse {
                }
                $hunk_index++;
        }
-       close(DIFF);
        for (my $i = $ri; $i < @{$slines} ; $i++) {
                push @plines, $slines->[$ri++];
        }
@@ -295,24 +331,43 @@ sub get_line {
 }
 
 sub git_cat_file {
-       my ($parent, $filename) = @_;
-       return () unless defined $parent && defined $filename;
-       my $blobline = `git-ls-tree $parent $filename`;
-       my ($mode, $type, $blob, $tfilename) = split(/\s+/, $blobline, 4);
+       my ($rev, $filename) = @_;
+       return () unless defined $rev && defined $filename;
+
+       my $blob = git_ls_tree($rev, $filename);
 
-       open(C,"-|","git-cat-file", "blob", $blob)
-               or die "Failed to git-cat-file blob $blob (rev $parent, file $filename): " . $!;
+       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(<C>) {
+       while(<$catfile>) {
                chomp;
                push @lines, $_;
        }
-       close(C);
+       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>) {
+               ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4);
+               last if ($tfilename eq $filename);
+       }
+       close($lstree);
+
+       return $blob if $filename eq $filename;
+       die "git-ls-tree failed to find blob for $filename";
+
+}
+
+
 
 sub claim_line {
        my ($floffset, $rev, $lines, %revinfo) = @_;
@@ -325,11 +380,11 @@ sub claim_line {
 
 sub git_commit_info {
        my ($rev) = @_;
-       open(COMMIT, "-|","git-cat-file", "commit", $rev)
+       my $commit = open_pipe("git-cat-file", "commit", $rev)
                or die "Failed to call git-cat-file: $!";
 
        my %info;
-       while(<COMMIT>) {
+       while(<$commit>) {
                chomp;
                last if (length $_ == 0);
 
@@ -343,7 +398,7 @@ sub git_commit_info {
                        $info{'committer_date'} = $3;
                }
        }
-       close(COMMIT);
+       close($commit);
 
        return %info;
 }
@@ -354,3 +409,80 @@ sub format_date {
        return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp));
 }
 
+# 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}});
+}
index de4f011..0346d4a 100755 (executable)
@@ -164,6 +164,7 @@ fast_forward_local () {
                ;;
            *,$local)
                echo >&2 "* $1: fast forward to $3"
+               echo >&2 "  from $local to $2"
                git-update-ref "$1" "$2" "$local"
                ;;
            *)
diff --git a/git.c b/git.c
index 4616df6..993cd0d 100644 (file)
--- a/git.c
+++ b/git.c
@@ -230,62 +230,141 @@ static void show_man_page(char *git_cmd)
        execlp("man", "man", page, NULL);
 }
 
+static int cmd_version(int argc, char **argv, char **envp)
+{
+       printf("git version %s\n", GIT_VERSION);
+       return 0;
+}
+
+static int cmd_help(int argc, char **argv, char **envp)
+{
+       char *help_cmd = argv[1];
+       if (!help_cmd)
+               cmd_usage(git_exec_path(), NULL);
+       show_man_page(help_cmd);
+       return 0;
+}
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+static void handle_internal_command(int argc, char **argv, char **envp)
+{
+       const char *cmd = argv[0];
+       static struct cmd_struct {
+               const char *cmd;
+               int (*fn)(int, char **, char **);
+       } commands[] = {
+               { "version", cmd_version },
+               { "help", cmd_help },
+       };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(commands); i++) {
+               struct cmd_struct *p = commands+i;
+               if (strcmp(p->cmd, cmd))
+                       continue;
+               exit(p->fn(argc, argv, envp));
+       }
+}
+
 int main(int argc, char **argv, char **envp)
 {
+       char *cmd = argv[0];
+       char *slash = strrchr(cmd, '/');
        char git_command[PATH_MAX + 1];
-       char wd[PATH_MAX + 1];
-       int i, show_help = 0;
-       const char *exec_path;
+       const char *exec_path = NULL;
+
+       /*
+        * Take the basename of argv[0] as the command
+        * name, and the dirname as the default exec_path
+        * if it's an absolute path and we don't have
+        * anything better.
+        */
+       if (slash) {
+               *slash++ = 0;
+               if (*cmd == '/')
+                       exec_path = cmd;
+               cmd = slash;
+       }
 
-       getcwd(wd, PATH_MAX);
+       /*
+        * "git-xxxx" is the same as "git xxxx", but we obviously:
+        *
+        *  - cannot take flags in between the "git" and the "xxxx".
+        *  - cannot execute it externally (since it would just do
+        *    the same thing over again)
+        *
+        * So we just directly call the internal command handler, and
+        * die if that one cannot handle it.
+        */
+       if (!strncmp(cmd, "git-", 4)) {
+               cmd += 4;
+               argv[0] = cmd;
+               handle_internal_command(argc, argv, envp);
+               die("cannot handle %s internally", cmd);
+       }
 
-       for (i = 1; i < argc; i++) {
-               char *arg = argv[i];
+       /* Default command: "help" */
+       cmd = "help";
 
-               if (!strcmp(arg, "help")) {
-                       show_help = 1;
-                       continue;
-               }
+       /* Look for flags.. */
+       while (argc > 1) {
+               cmd = *++argv;
+               argc--;
 
-               if (strncmp(arg, "--", 2))
+               if (strncmp(cmd, "--", 2))
                        break;
 
-               arg += 2;
+               cmd += 2;
+
+               /*
+                * For legacy reasons, the "version" and "help"
+                * commands can be written with "--" prepended
+                * to make them look like flags.
+                */
+               if (!strcmp(cmd, "help"))
+                       break;
+               if (!strcmp(cmd, "version"))
+                       break;
 
-               if (!strncmp(arg, "exec-path", 9)) {
-                       arg += 9;
-                       if (*arg == '=') {
-                               exec_path = arg + 1;
-                               git_set_exec_path(exec_path);
-                       } else {
-                               puts(git_exec_path());
-                               exit(0);
+               /*
+                * Check remaining flags (which by now must be
+                * "--exec-path", but maybe we will accept
+                * other arguments some day)
+                */
+               if (!strncmp(cmd, "exec-path", 9)) {
+                       cmd += 9;
+                       if (*cmd == '=') {
+                               git_set_exec_path(cmd + 1);
+                               continue;
                        }
-               }
-               else if (!strcmp(arg, "version")) {
-                       printf("git version %s\n", GIT_VERSION);
+                       puts(git_exec_path());
                        exit(0);
                }
-               else if (!strcmp(arg, "help"))
-                       show_help = 1;
-               else if (!show_help)
-                       cmd_usage(NULL, NULL);
-       }
-
-       if (i >= argc || show_help) {
-               if (i >= argc)
-                       cmd_usage(git_exec_path(), NULL);
-
-               show_man_page(argv[i]);
+               cmd_usage(NULL, NULL);
        }
-
+       argv[0] = cmd;
+
+       /*
+        * We search for git commands in the following order:
+        *  - git_exec_path()
+        *  - the path of the "git" command if we could find it
+        *    in $0
+        *  - the regular PATH.
+        */
+       if (exec_path)
+               prepend_to_path(exec_path, strlen(exec_path));
        exec_path = git_exec_path();
        prepend_to_path(exec_path, strlen(exec_path));
 
-       execv_git_cmd(argv + i);
+       /* See if it's an internal command */
+       handle_internal_command(argc, argv, envp);
+
+       /* .. then try the external ones */
+       execv_git_cmd(argv);
 
        if (errno == ENOENT)
-               cmd_usage(exec_path, "'%s' is not a git-command", argv[i]);
+               cmd_usage(exec_path, "'%s' is not a git-command", cmd);
 
        fprintf(stderr, "Failed to run command '%s': %s\n",
                git_command, strerror(errno));
index 0287449..21ee572 100644 (file)
@@ -768,7 +768,7 @@ static int sha1_sort(const struct object_entry *a, const struct object_entry *b)
        return memcmp(a->sha1, b->sha1, 20);
 }
 
-static struct object_entry **create_final_object_list()
+static struct object_entry **create_final_object_list(void)
 {
        struct object_entry **list;
        int i, j;
index 1869b38..cd81f5a 100644 (file)
@@ -45,7 +45,7 @@ static inline void llist_item_put(struct llist_item *item)
        free_nodes = item;
 }
 
-static inline struct llist_item *llist_item_get()
+static inline struct llist_item *llist_item_get(void)
 {
        struct llist_item *new;
        if ( free_nodes ) {
@@ -275,7 +275,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
        }
 }
 
-void pll_free(struct pll *l)
+static void pll_free(struct pll *l)
 {
        struct pll *old;
        struct pack_list *opl;
index 67d2a48..d1c52a6 100644 (file)
@@ -6,9 +6,10 @@
 #include "blob.h"
 #include "epoch.h"
 #include "diff.h"
+#include "revision.h"
+
+/* bits #0 and #1 in revision.h */
 
-#define SEEN           (1u << 0)
-#define INTERESTING    (1u << 1)
 #define COUNTED                (1u << 2)
 #define SHOWN          (1u << 3)
 #define TREECHANGE     (1u << 4)
@@ -38,60 +39,20 @@ static const char rev_list_usage[] =
 "    --bisect"
 ;
 
-static int dense = 1;
+struct rev_info revs;
+
 static int unpacked = 0;
 static int bisect_list = 0;
-static int tag_objects = 0;
-static int tree_objects = 0;
-static int blob_objects = 0;
-static int edge_hint = 0;
 static int verbose_header = 0;
 static int abbrev = DEFAULT_ABBREV;
 static int show_parents = 0;
 static int hdr_termination = 0;
 static const char *commit_prefix = "";
-static unsigned long max_age = -1;
-static unsigned long min_age = -1;
-static int max_count = -1;
 static enum cmit_fmt commit_format = CMIT_FMT_RAW;
 static int merge_order = 0;
 static int show_breaks = 0;
 static int stop_traversal = 0;
-static int topo_order = 0;
-static int lifo = 1;
 static int no_merges = 0;
-static const char **paths = NULL;
-static int remove_empty_trees = 0;
-
-struct name_path {
-       struct name_path *up;
-       int elem_len;
-       const char *elem;
-};
-
-static char *path_name(struct name_path *path, const char *name)
-{
-       struct name_path *p;
-       char *n, *m;
-       int nlen = strlen(name);
-       int len = nlen + 1;
-
-       for (p = path; p; p = p->up) {
-               if (p->elem_len)
-                       len += p->elem_len + 1;
-       }
-       n = xmalloc(len);
-       m = n + len - (nlen + 1);
-       strcpy(m, name);
-       for (p = path; p; p = p->up) {
-               if (p->elem_len) {
-                       m -= p->elem_len + 1;
-                       memcpy(m, p->elem, p->elem_len);
-                       m[p->elem_len] = '/';
-               }
-       }
-       return n;
-}
 
 static void show_commit(struct commit *commit)
 {
@@ -168,15 +129,15 @@ static int filter_commit(struct commit * commit)
                return STOP;
        if (commit->object.flags & (UNINTERESTING|SHOWN))
                return CONTINUE;
-       if (min_age != -1 && (commit->date > min_age))
+       if (revs.min_age != -1 && (commit->date > revs.min_age))
                return CONTINUE;
-       if (max_age != -1 && (commit->date < max_age)) {
+       if (revs.max_age != -1 && (commit->date < revs.max_age)) {
                stop_traversal=1;
                return CONTINUE;
        }
        if (no_merges && (commit->parents && commit->parents->next))
                return CONTINUE;
-       if (paths && dense) {
+       if (revs.paths && revs.dense) {
                if (!(commit->object.flags & TREECHANGE))
                        return CONTINUE;
                rewrite_parents(commit);
@@ -196,7 +157,7 @@ static int process_commit(struct commit * commit)
                return CONTINUE;
        }
 
-       if (max_count != -1 && !max_count--)
+       if (revs.max_count != -1 && !revs.max_count--)
                return STOP;
 
        show_commit(commit);
@@ -204,19 +165,6 @@ static int process_commit(struct commit * commit)
        return CONTINUE;
 }
 
-static struct object_list **add_object(struct object *obj,
-                                      struct object_list **p,
-                                      struct name_path *path,
-                                      const char *name)
-{
-       struct object_list *entry = xmalloc(sizeof(*entry));
-       entry->item = obj;
-       entry->next = *p;
-       entry->name = path_name(path, name);
-       *p = entry;
-       return &entry->next;
-}
-
 static struct object_list **process_blob(struct blob *blob,
                                         struct object_list **p,
                                         struct name_path *path,
@@ -224,7 +172,7 @@ static struct object_list **process_blob(struct blob *blob,
 {
        struct object *obj = &blob->object;
 
-       if (!blob_objects)
+       if (!revs.blob_objects)
                return p;
        if (obj->flags & (UNINTERESTING | SEEN))
                return p;
@@ -241,7 +189,7 @@ static struct object_list **process_tree(struct tree *tree,
        struct tree_entry_list *entry;
        struct name_path me;
 
-       if (!tree_objects)
+       if (!revs.tree_objects)
                return p;
        if (obj->flags & (UNINTERESTING | SEEN))
                return p;
@@ -314,75 +262,6 @@ static void show_commit_list(struct commit_list *list)
        }
 }
 
-static void mark_blob_uninteresting(struct blob *blob)
-{
-       if (!blob_objects)
-               return;
-       if (blob->object.flags & UNINTERESTING)
-               return;
-       blob->object.flags |= UNINTERESTING;
-}
-
-static void mark_tree_uninteresting(struct tree *tree)
-{
-       struct object *obj = &tree->object;
-       struct tree_entry_list *entry;
-
-       if (!tree_objects)
-               return;
-       if (obj->flags & UNINTERESTING)
-               return;
-       obj->flags |= UNINTERESTING;
-       if (!has_sha1_file(obj->sha1))
-               return;
-       if (parse_tree(tree) < 0)
-               die("bad tree %s", sha1_to_hex(obj->sha1));
-       entry = tree->entries;
-       tree->entries = NULL;
-       while (entry) {
-               struct tree_entry_list *next = entry->next;
-               if (entry->directory)
-                       mark_tree_uninteresting(entry->item.tree);
-               else
-                       mark_blob_uninteresting(entry->item.blob);
-               free(entry);
-               entry = next;
-       }
-}
-
-static void mark_parents_uninteresting(struct commit *commit)
-{
-       struct commit_list *parents = commit->parents;
-
-       while (parents) {
-               struct commit *commit = parents->item;
-               commit->object.flags |= UNINTERESTING;
-
-               /*
-                * Normally we haven't parsed the parent
-                * yet, so we won't have a parent of a parent
-                * here. However, it may turn out that we've
-                * reached this commit some other way (where it
-                * wasn't uninteresting), in which case we need
-                * to mark its parents recursively too..
-                */
-               if (commit->parents)
-                       mark_parents_uninteresting(commit);
-
-               /*
-                * A missing commit is ok iff its parent is marked 
-                * uninteresting.
-                *
-                * We just mark such a thing parsed, so that when
-                * it is popped next time around, we won't be trying
-                * to parse it and get an error.
-                */
-               if (!has_sha1_file(commit->object.sha1))
-                       commit->object.parsed = 1;
-               parents = parents->next;
-       }
-}
-
 static int everybody_uninteresting(struct commit_list *orig)
 {
        struct commit_list *list = orig;
@@ -413,7 +292,7 @@ static int count_distance(struct commit_list *entry)
 
                if (commit->object.flags & (UNINTERESTING | COUNTED))
                        break;
-               if (!paths || (commit->object.flags & TREECHANGE))
+               if (!revs.paths || (commit->object.flags & TREECHANGE))
                        nr++;
                commit->object.flags |= COUNTED;
                p = commit->parents;
@@ -447,7 +326,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
        nr = 0;
        p = list;
        while (p) {
-               if (!paths || (p->item->object.flags & TREECHANGE))
+               if (!revs.paths || (p->item->object.flags & TREECHANGE))
                        nr++;
                p = p->next;
        }
@@ -457,7 +336,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
        for (p = list; p; p = p->next) {
                int distance;
 
-               if (paths && !(p->item->object.flags & TREECHANGE))
+               if (revs.paths && !(p->item->object.flags & TREECHANGE))
                        continue;
 
                distance = count_distance(p);
@@ -483,7 +362,7 @@ static void mark_edge_parents_uninteresting(struct commit *commit)
                if (!(parent->object.flags & UNINTERESTING))
                        continue;
                mark_tree_uninteresting(parent->tree);
-               if (edge_hint && !(parent->object.flags & SHOWN)) {
+               if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
                        parent->object.flags |= SHOWN;
                        printf("-%s\n", sha1_to_hex(parent->object.sha1));
                }
@@ -613,7 +492,7 @@ static void try_to_simplify_commit(struct commit *commit)
                        return;
 
                case TREE_NEW:
-                       if (remove_empty_trees && same_tree_as_empty(p->tree)) {
+                       if (revs.remove_empty_trees && same_tree_as_empty(p->tree)) {
                                *pp = parent->next;
                                continue;
                        }
@@ -664,7 +543,7 @@ static void add_parents_to_list(struct commit *commit, struct commit_list **list
         * simplify the commit history and find the parent
         * that has no differences in the path set if one exists.
         */
-       if (paths)
+       if (revs.paths)
                try_to_simplify_commit(commit);
 
        parent = commit->parents;
@@ -693,7 +572,7 @@ static struct commit_list *limit_list(struct commit_list *list)
                list = list->next;
                free(entry);
 
-               if (max_age != -1 && (commit->date < max_age))
+               if (revs.max_age != -1 && (commit->date < revs.max_age))
                        obj->flags |= UNINTERESTING;
                if (unpacked && has_sha1_pack(obj->sha1))
                        obj->flags |= UNINTERESTING;
@@ -704,155 +583,40 @@ static struct commit_list *limit_list(struct commit_list *list)
                                break;
                        continue;
                }
-               if (min_age != -1 && (commit->date > min_age))
+               if (revs.min_age != -1 && (commit->date > revs.min_age))
                        continue;
                p = &commit_list_insert(commit, p)->next;
        }
-       if (tree_objects)
+       if (revs.tree_objects)
                mark_edges_uninteresting(newlist);
        if (bisect_list)
                newlist = find_bisection(newlist);
        return newlist;
 }
 
-static void add_pending_object(struct object *obj, const char *name)
-{
-       add_object(obj, &pending_objects, NULL, name);
-}
-
-static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
-{
-       struct object *object;
-
-       object = parse_object(sha1);
-       if (!object)
-               die("bad object %s", name);
-
-       /*
-        * Tag object? Look what it points to..
-        */
-       while (object->type == tag_type) {
-               struct tag *tag = (struct tag *) object;
-               object->flags |= flags;
-               if (tag_objects && !(object->flags & UNINTERESTING))
-                       add_pending_object(object, tag->tag);
-               object = parse_object(tag->tagged->sha1);
-               if (!object)
-                       die("bad object %s", sha1_to_hex(tag->tagged->sha1));
-       }
-
-       /*
-        * Commit object? Just return it, we'll do all the complex
-        * reachability crud.
-        */
-       if (object->type == commit_type) {
-               struct commit *commit = (struct commit *)object;
-               object->flags |= flags;
-               if (parse_commit(commit) < 0)
-                       die("unable to parse commit %s", name);
-               if (flags & UNINTERESTING)
-                       mark_parents_uninteresting(commit);
-               return commit;
-       }
-
-       /*
-        * Tree object? Either mark it uniniteresting, or add it
-        * to the list of objects to look at later..
-        */
-       if (object->type == tree_type) {
-               struct tree *tree = (struct tree *)object;
-               if (!tree_objects)
-                       return NULL;
-               if (flags & UNINTERESTING) {
-                       mark_tree_uninteresting(tree);
-                       return NULL;
-               }
-               add_pending_object(object, "");
-               return NULL;
-       }
-
-       /*
-        * Blob object? You know the drill by now..
-        */
-       if (object->type == blob_type) {
-               struct blob *blob = (struct blob *)object;
-               if (!blob_objects)
-                       return NULL;
-               if (flags & UNINTERESTING) {
-                       mark_blob_uninteresting(blob);
-                       return NULL;
-               }
-               add_pending_object(object, "");
-               return NULL;
-       }
-       die("%s is unknown object", name);
-}
-
-static void handle_one_commit(struct commit *com, struct commit_list **lst)
-{
-       if (!com || com->object.flags & SEEN)
-               return;
-       com->object.flags |= SEEN;
-       commit_list_insert(com, lst);
-}
-
-/* for_each_ref() callback does not allow user data -- Yuck. */
-static struct commit_list **global_lst;
-
-static int include_one_commit(const char *path, const unsigned char *sha1)
-{
-       struct commit *com = get_commit_reference(path, sha1, 0);
-       handle_one_commit(com, global_lst);
-       return 0;
-}
-
-static void handle_all(struct commit_list **lst)
-{
-       global_lst = lst;
-       for_each_ref(include_one_commit);
-       global_lst = NULL;
-}
-
 int main(int argc, const char **argv)
 {
-       const char *prefix = setup_git_directory();
-       struct commit_list *list = NULL;
+       struct commit_list *list;
        int i, limited = 0;
 
+       argc = setup_revisions(argc, argv, &revs);
+
        for (i = 1 ; i < argc; i++) {
-               int flags;
                const char *arg = argv[i];
-               char *dotdot;
-               struct commit *commit;
-               unsigned char sha1[20];
 
                /* accept -<digit>, like traditilnal "head" */
                if ((*arg == '-') && isdigit(arg[1])) {
-                       max_count = atoi(arg + 1);
+                       revs.max_count = atoi(arg + 1);
                        continue;
                }
                if (!strcmp(arg, "-n")) {
                        if (++i >= argc)
                                die("-n requires an argument");
-                       max_count = atoi(argv[i]);
+                       revs.max_count = atoi(argv[i]);
                        continue;
                }
                if (!strncmp(arg,"-n",2)) {
-                       max_count = atoi(arg + 2);
-                       continue;
-               }
-               if (!strncmp(arg, "--max-count=", 12)) {
-                       max_count = atoi(arg + 12);
-                       continue;
-               }
-               if (!strncmp(arg, "--max-age=", 10)) {
-                       max_age = atoi(arg + 10);
-                       limited = 1;
-                       continue;
-               }
-               if (!strncmp(arg, "--min-age=", 10)) {
-                       min_age = atoi(arg + 10);
-                       limited = 1;
+                       revs.max_count = atoi(arg + 2);
                        continue;
                }
                if (!strcmp(arg, "--header")) {
@@ -893,23 +657,6 @@ int main(int argc, const char **argv)
                        bisect_list = 1;
                        continue;
                }
-               if (!strcmp(arg, "--all")) {
-                       handle_all(&list);
-                       continue;
-               }
-               if (!strcmp(arg, "--objects")) {
-                       tag_objects = 1;
-                       tree_objects = 1;
-                       blob_objects = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--objects-edge")) {
-                       tag_objects = 1;
-                       tree_objects = 1;
-                       blob_objects = 1;
-                       edge_hint = 1;
-                       continue;
-               }
                if (!strcmp(arg, "--unpacked")) {
                        unpacked = 1;
                        limited = 1;
@@ -923,100 +670,42 @@ int main(int argc, const char **argv)
                        show_breaks = 1;
                        continue;
                }
-               if (!strcmp(arg, "--topo-order")) {
-                       topo_order = 1;
-                       lifo = 1;
-                       limited = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--date-order")) {
-                       topo_order = 1;
-                       lifo = 0;
-                       limited = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--dense")) {
-                       dense = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--sparse")) {
-                       dense = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "--remove-empty")) {
-                       remove_empty_trees = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
+               usage(rev_list_usage);
 
-               if (show_breaks && !merge_order)
-                       usage(rev_list_usage);
-
-               flags = 0;
-               dotdot = strstr(arg, "..");
-               if (dotdot) {
-                       unsigned char from_sha1[20];
-                       char *next = dotdot + 2;
-                       *dotdot = 0;
-                       if (!*next)
-                               next = "HEAD";
-                       if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
-                               struct commit *exclude;
-                               struct commit *include;
-                               
-                               exclude = get_commit_reference(arg, from_sha1, UNINTERESTING);
-                               include = get_commit_reference(next, sha1, 0);
-                               if (!exclude || !include)
-                                       die("Invalid revision range %s..%s", arg, next);
-                               limited = 1;
-                               handle_one_commit(exclude, &list);
-                               handle_one_commit(include, &list);
-                               continue;
-                       }
-                       *dotdot = '.';
-               }
-               if (*arg == '^') {
-                       flags = UNINTERESTING;
-                       arg++;
-                       limited = 1;
-               }
-               if (get_sha1(arg, sha1) < 0) {
-                       struct stat st;
-                       if (lstat(arg, &st) < 0)
-                               die("'%s': %s", arg, strerror(errno));
-                       break;
-               }
-               commit = get_commit_reference(arg, sha1, flags);
-               handle_one_commit(commit, &list);
        }
 
+       list = revs.commits;
+       if (list && list->next)
+               limited = 1;
+
+       if (revs.topo_order)
+               limited = 1;
+
        if (!list &&
-           (!(tag_objects||tree_objects||blob_objects) && !pending_objects))
+           (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
                usage(rev_list_usage);
 
-       paths = get_pathspec(prefix, argv + i);
-       if (paths) {
+       if (revs.paths) {
                limited = 1;
-               diff_tree_setup_paths(paths);
+               diff_tree_setup_paths(revs.paths);
        }
+       if (revs.max_age || revs.min_age)
+               limited = 1;
 
        save_commit_buffer = verbose_header;
        track_object_refs = 0;
 
        if (!merge_order) {             
                sort_by_date(&list);
-               if (list && !limited && max_count == 1 &&
-                   !tag_objects && !tree_objects && !blob_objects) {
+               if (list && !limited && revs.max_count == 1 &&
+                   !revs.tag_objects && !revs.tree_objects && !revs.blob_objects) {
                        show_commit(list->item);
                        return 0;
                }
                if (limited)
                        list = limit_list(list);
-               if (topo_order)
-                       sort_in_topological_order(&list, lifo);
+               if (revs.topo_order)
+                       sort_in_topological_order(&list, revs.lifo);
                show_commit_list(list);
        } else {
 #ifndef NO_OPENSSL
diff --git a/revision.c b/revision.c
new file mode 100644 (file)
index 0000000..d61410b
--- /dev/null
@@ -0,0 +1,370 @@
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "refs.h"
+#include "revision.h"
+
+static char *path_name(struct name_path *path, const char *name)
+{
+       struct name_path *p;
+       char *n, *m;
+       int nlen = strlen(name);
+       int len = nlen + 1;
+
+       for (p = path; p; p = p->up) {
+               if (p->elem_len)
+                       len += p->elem_len + 1;
+       }
+       n = xmalloc(len);
+       m = n + len - (nlen + 1);
+       strcpy(m, name);
+       for (p = path; p; p = p->up) {
+               if (p->elem_len) {
+                       m -= p->elem_len + 1;
+                       memcpy(m, p->elem, p->elem_len);
+                       m[p->elem_len] = '/';
+               }
+       }
+       return n;
+}
+
+struct object_list **add_object(struct object *obj,
+                                      struct object_list **p,
+                                      struct name_path *path,
+                                      const char *name)
+{
+       struct object_list *entry = xmalloc(sizeof(*entry));
+       entry->item = obj;
+       entry->next = *p;
+       entry->name = path_name(path, name);
+       *p = entry;
+       return &entry->next;
+}
+
+static void mark_blob_uninteresting(struct blob *blob)
+{
+       if (blob->object.flags & UNINTERESTING)
+               return;
+       blob->object.flags |= UNINTERESTING;
+}
+
+void mark_tree_uninteresting(struct tree *tree)
+{
+       struct object *obj = &tree->object;
+       struct tree_entry_list *entry;
+
+       if (obj->flags & UNINTERESTING)
+               return;
+       obj->flags |= UNINTERESTING;
+       if (!has_sha1_file(obj->sha1))
+               return;
+       if (parse_tree(tree) < 0)
+               die("bad tree %s", sha1_to_hex(obj->sha1));
+       entry = tree->entries;
+       tree->entries = NULL;
+       while (entry) {
+               struct tree_entry_list *next = entry->next;
+               if (entry->directory)
+                       mark_tree_uninteresting(entry->item.tree);
+               else
+                       mark_blob_uninteresting(entry->item.blob);
+               free(entry);
+               entry = next;
+       }
+}
+
+void mark_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents = commit->parents;
+
+       while (parents) {
+               struct commit *commit = parents->item;
+               commit->object.flags |= UNINTERESTING;
+
+               /*
+                * Normally we haven't parsed the parent
+                * yet, so we won't have a parent of a parent
+                * here. However, it may turn out that we've
+                * reached this commit some other way (where it
+                * wasn't uninteresting), in which case we need
+                * to mark its parents recursively too..
+                */
+               if (commit->parents)
+                       mark_parents_uninteresting(commit);
+
+               /*
+                * A missing commit is ok iff its parent is marked
+                * uninteresting.
+                *
+                * We just mark such a thing parsed, so that when
+                * it is popped next time around, we won't be trying
+                * to parse it and get an error.
+                */
+               if (!has_sha1_file(commit->object.sha1))
+                       commit->object.parsed = 1;
+               parents = parents->next;
+       }
+}
+
+static void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+{
+       add_object(obj, &revs->pending_objects, NULL, name);
+}
+
+static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+{
+       struct object *object;
+
+       object = parse_object(sha1);
+       if (!object)
+               die("bad object %s", name);
+
+       /*
+        * Tag object? Look what it points to..
+        */
+       while (object->type == tag_type) {
+               struct tag *tag = (struct tag *) object;
+               object->flags |= flags;
+               if (revs->tag_objects && !(object->flags & UNINTERESTING))
+                       add_pending_object(revs, object, tag->tag);
+               object = parse_object(tag->tagged->sha1);
+               if (!object)
+                       die("bad object %s", sha1_to_hex(tag->tagged->sha1));
+       }
+
+       /*
+        * Commit object? Just return it, we'll do all the complex
+        * reachability crud.
+        */
+       if (object->type == commit_type) {
+               struct commit *commit = (struct commit *)object;
+               object->flags |= flags;
+               if (parse_commit(commit) < 0)
+                       die("unable to parse commit %s", name);
+               if (flags & UNINTERESTING)
+                       mark_parents_uninteresting(commit);
+               return commit;
+       }
+
+       /*
+        * Tree object? Either mark it uniniteresting, or add it
+        * to the list of objects to look at later..
+        */
+       if (object->type == tree_type) {
+               struct tree *tree = (struct tree *)object;
+               if (!revs->tree_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_tree_uninteresting(tree);
+                       return NULL;
+               }
+               add_pending_object(revs, object, "");
+               return NULL;
+       }
+
+       /*
+        * Blob object? You know the drill by now..
+        */
+       if (object->type == blob_type) {
+               struct blob *blob = (struct blob *)object;
+               if (!revs->blob_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_blob_uninteresting(blob);
+                       return NULL;
+               }
+               add_pending_object(revs, object, "");
+               return NULL;
+       }
+       die("%s is unknown object", name);
+}
+
+static void add_one_commit(struct commit *commit, struct rev_info *revs)
+{
+       if (!commit || (commit->object.flags & SEEN))
+               return;
+       commit->object.flags |= SEEN;
+       commit_list_insert(commit, &revs->commits);
+}
+
+static int all_flags;
+static struct rev_info *all_revs;
+
+static int handle_one_ref(const char *path, const unsigned char *sha1)
+{
+       struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags);
+       add_one_commit(commit, all_revs);
+       return 0;
+}
+
+static void handle_all(struct rev_info *revs, unsigned flags)
+{
+       all_revs = revs;
+       all_flags = flags;
+       for_each_ref(handle_one_ref);
+}
+
+/*
+ * Parse revision information, filling in the "rev_info" structure,
+ * and removing the used arguments from the argument list.
+ *
+ * Returns the number of arguments left ("new argc").
+ */
+int setup_revisions(int argc, const char **argv, struct rev_info *revs)
+{
+       int i, flags, seen_dashdash;
+       const char *def = NULL;
+       const char **unrecognized = argv+1;
+       int left = 1;
+
+       memset(revs, 0, sizeof(*revs));
+       revs->lifo = 1;
+       revs->dense = 1;
+       revs->prefix = setup_git_directory();
+       revs->max_age = -1;
+       revs->min_age = -1;
+       revs->max_count = -1;
+
+       /* First, search for "--" */
+       seen_dashdash = 0;
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (strcmp(arg, "--"))
+                       continue;
+               argv[i] = NULL;
+               argc = i;
+               revs->paths = get_pathspec(revs->prefix, argv + i + 1);
+               seen_dashdash = 1;
+               break;
+       }
+
+       flags = 0;
+       for (i = 1; i < argc; i++) {
+               struct commit *commit;
+               const char *arg = argv[i];
+               unsigned char sha1[20];
+               char *dotdot;
+               int local_flags;
+
+               if (*arg == '-') {
+                       if (!strncmp(arg, "--max-count=", 12)) {
+                               revs->max_count = atoi(arg + 12);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--max-age=", 10)) {
+                               revs->max_age = atoi(arg + 10);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--min-age=", 10)) {
+                               revs->min_age = atoi(arg + 10);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               handle_all(revs, flags);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--not")) {
+                               flags ^= UNINTERESTING;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--default")) {
+                               if (++i >= argc)
+                                       die("bad --default argument");
+                               def = argv[i];
+                               continue;
+                       }
+                       if (!strcmp(arg, "--topo-order")) {
+                               revs->topo_order = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--date-order")) {
+                               revs->lifo = 0;
+                               revs->topo_order = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--dense")) {
+                               revs->dense = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--sparse")) {
+                               revs->dense = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--remove-empty")) {
+                               revs->remove_empty_trees = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--objects")) {
+                               revs->tag_objects = 1;
+                               revs->tree_objects = 1;
+                               revs->blob_objects = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--objects-edge")) {
+                               revs->tag_objects = 1;
+                               revs->tree_objects = 1;
+                               revs->blob_objects = 1;
+                               revs->edge_hint = 1;
+                               continue;
+                       }
+                       *unrecognized++ = arg;
+                       left++;
+                       continue;
+               }
+               dotdot = strstr(arg, "..");
+               if (dotdot) {
+                       unsigned char from_sha1[20];
+                       char *next = dotdot + 2;
+                       *dotdot = 0;
+                       if (!*next)
+                               next = "HEAD";
+                       if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
+                               struct commit *exclude;
+                               struct commit *include;
+
+                               exclude = get_commit_reference(revs, arg, from_sha1, flags ^ UNINTERESTING);
+                               include = get_commit_reference(revs, next, sha1, flags);
+                               if (!exclude || !include)
+                                       die("Invalid revision range %s..%s", arg, next);
+                               add_one_commit(exclude, revs);
+                               add_one_commit(include, revs);
+                               continue;
+                       }
+                       *dotdot = '.';
+               }
+               local_flags = 0;
+               if (*arg == '^') {
+                       local_flags = UNINTERESTING;
+                       arg++;
+               }
+               if (get_sha1(arg, sha1) < 0) {
+                       struct stat st;
+                       int j;
+
+                       if (seen_dashdash || local_flags)
+                               die("bad revision '%s'", arg);
+
+                       /* If we didn't have a "--", all filenames must exist */
+                       for (j = i; j < argc; j++) {
+                               if (lstat(argv[j], &st) < 0)
+                                       die("'%s': %s", arg, strerror(errno));
+                       }
+                       revs->paths = get_pathspec(revs->prefix, argv + i);
+                       break;
+               }
+               commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
+               add_one_commit(commit, revs);
+       }
+       if (def && !revs->commits) {
+               unsigned char sha1[20];
+               struct commit *commit;
+               if (get_sha1(def, sha1) < 0)
+                       die("bad default revision '%s'", def);
+               commit = get_commit_reference(revs, def, sha1, 0);
+               add_one_commit(commit, revs);
+       }
+       *unrecognized = NULL;
+       return left;
+}
diff --git a/revision.h b/revision.h
new file mode 100644 (file)
index 0000000..5170ac4
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef REVISION_H
+#define REVISION_H
+
+#define SEEN           (1u<<0)
+#define UNINTERESTING   (1u<<1)
+
+struct rev_info {
+       /* Starting list */
+       struct commit_list *commits;
+       struct object_list *pending_objects;
+
+       /* Basic information */
+       const char *prefix;
+       const char **paths;
+
+       /* Traversal flags */
+       unsigned int    dense:1,
+                       remove_empty_trees:1,
+                       lifo:1,
+                       topo_order:1,
+                       tag_objects:1,
+                       tree_objects:1,
+                       blob_objects:1,
+                       edge_hint:1;
+
+       /* special limits */
+       int max_count;
+       unsigned long max_age;
+       unsigned long min_age;
+};
+
+/* revision.c */
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs);
+extern void mark_parents_uninteresting(struct commit *commit);
+extern void mark_tree_uninteresting(struct tree *tree);
+
+struct name_path {
+       struct name_path *up;
+       int elem_len;
+       const char *elem;
+};
+
+extern struct object_list **add_object(struct object *obj,
+                                      struct object_list **p,
+                                      struct name_path *path,
+                                      const char *name);
+
+#endif
index bda3c86..02de1ef 100644 (file)
@@ -9,6 +9,7 @@
 #
 # To enable this hook, make this file executable.
 
+. git-sh-setup
 test -x "$GIT_DIR/hooks/commit-msg" &&
        exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
 :
index a547516..5f56ce8 100644 (file)
@@ -8,6 +8,7 @@
 #
 # To enable this hook, make this file executable.
 
+. git-sh-setup
 test -x "$GIT_DIR/hooks/pre-commit" &&
        exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
 :