diff options
author | Junio C Hamano <junkio@cox.net> | 2006-02-26 15:33:49 -0800 |
---|---|---|
committer | Junio C Hamano <junkio@cox.net> | 2006-02-26 15:33:49 -0800 |
commit | b5233a61955772b4430abb0eb1d03b7fbdd33e6a (patch) | |
tree | b0a33fe62c2b957cdfcbb7349e08e86d77173f39 | |
parent | Merge branch 'master' into next (diff) | |
parent | First cut at libifying revlist generation (diff) | |
download | tgif-b5233a61955772b4430abb0eb1d03b7fbdd33e6a.tar.xz |
Merge branch 'lt/rev-list' into next
* 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
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | cache.h | 2 | ||||
-rwxr-xr-x | contrib/git-svn/git-svn.perl | 57 | ||||
-rw-r--r-- | contrib/git-svn/git-svn.txt | 7 | ||||
-rw-r--r-- | epoch.c | 1 | ||||
-rw-r--r-- | epoch.h | 1 | ||||
-rw-r--r-- | exec_cmd.c | 2 | ||||
-rw-r--r-- | fetch-pack.c | 2 | ||||
-rw-r--r-- | fsck-objects.c | 2 | ||||
-rwxr-xr-x | git-annotate.perl | 244 | ||||
-rwxr-xr-x | git-fetch.sh | 1 | ||||
-rw-r--r-- | git.c | 153 | ||||
-rw-r--r-- | pack-objects.c | 2 | ||||
-rw-r--r-- | pack-redundant.c | 4 | ||||
-rw-r--r-- | rev-list.c | 397 | ||||
-rw-r--r-- | revision.c | 370 | ||||
-rw-r--r-- | revision.h | 48 | ||||
-rw-r--r-- | templates/hooks--applypatch-msg | 1 | ||||
-rw-r--r-- | templates/hooks--pre-applypatch | 1 |
19 files changed, 838 insertions, 461 deletions
@@ -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) @@ -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 diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index a32ce15701..0b7416526d 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -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; } diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt index cf098d733a..b4b7789dee 100644 --- a/contrib/git-svn/git-svn.txt +++ b/contrib/git-svn/git-svn.txt @@ -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 ----------------- @@ -15,6 +15,7 @@ #include "cache.h" #include "commit.h" +#include "revision.h" #include "epoch.h" struct fraction { @@ -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) diff --git a/exec_cmd.c b/exec_cmd.c index 55af33bb7e..b5e59a9ae9 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -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; diff --git a/fetch-pack.c b/fetch-pack.c index 09738fee91..535de10660 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -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; diff --git a/fsck-objects.c b/fsck-objects.c index 6439d55126..4ddd67699c 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -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 diff --git a/git-annotate.perl b/git-annotate.perl index 3800c4654b..f9c2c6caf5 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -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}}); +} diff --git a/git-fetch.sh b/git-fetch.sh index de4f011e26..0346d4a45c 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -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" ;; *) @@ -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)); diff --git a/pack-objects.c b/pack-objects.c index 0287449b4c..21ee572f48 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -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; diff --git a/pack-redundant.c b/pack-redundant.c index 1869b38b72..cd81f5a66e 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -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; diff --git a/rev-list.c b/rev-list.c index 67d2a483fc..d1c52a6a9b 100644 --- a/rev-list.c +++ b/rev-list.c @@ -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 index 0000000000..d61410bc59 --- /dev/null +++ b/revision.c @@ -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 index 0000000000..5170ac425f --- /dev/null +++ b/revision.h @@ -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 diff --git a/templates/hooks--applypatch-msg b/templates/hooks--applypatch-msg index bda3c86be7..02de1ef84c 100644 --- a/templates/hooks--applypatch-msg +++ b/templates/hooks--applypatch-msg @@ -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+"$@"} : diff --git a/templates/hooks--pre-applypatch b/templates/hooks--pre-applypatch index a54751600e..5f56ce8053 100644 --- a/templates/hooks--pre-applypatch +++ b/templates/hooks--pre-applypatch @@ -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+"$@"} : |