aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <junkio@cox.net>2006-02-26 15:33:49 -0800
committerJunio C Hamano <junkio@cox.net>2006-02-26 15:33:49 -0800
commitb5233a61955772b4430abb0eb1d03b7fbdd33e6a (patch)
treeb0a33fe62c2b957cdfcbb7349e08e86d77173f39
parentab57c8dd2abe062294eba94a1af60ba00964421c (diff)
parentae563542bf10fa8c33abd2a354e4b28aca4264d7 (diff)
downloadgit-b5233a61955772b4430abb0eb1d03b7fbdd33e6a.tar.gz
git-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--Makefile4
-rw-r--r--cache.h2
-rwxr-xr-xcontrib/git-svn/git-svn.perl57
-rw-r--r--contrib/git-svn/git-svn.txt7
-rw-r--r--epoch.c1
-rw-r--r--epoch.h1
-rw-r--r--exec_cmd.c2
-rw-r--r--fetch-pack.c2
-rw-r--r--fsck-objects.c2
-rwxr-xr-xgit-annotate.perl244
-rwxr-xr-xgit-fetch.sh1
-rw-r--r--git.c153
-rw-r--r--pack-objects.c2
-rw-r--r--pack-redundant.c4
-rw-r--r--rev-list.c397
-rw-r--r--revision.c370
-rw-r--r--revision.h48
-rw-r--r--templates/hooks--applypatch-msg1
-rw-r--r--templates/hooks--pre-applypatch1
19 files changed, 838 insertions, 461 deletions
diff --git a/Makefile b/Makefile
index 26fd9dc13..5e93f278f 100644
--- 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 5020f0714..58eec00e0 100644
--- 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
diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl
index a32ce1570..0b7416526 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 cf098d733..b4b7789de 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
-----------------
diff --git a/epoch.c b/epoch.c
index 3a767486d..0f374921d 100644
--- 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 7493d5a24..375600906 100644
--- 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)
diff --git a/exec_cmd.c b/exec_cmd.c
index 55af33bb7..b5e59a9ae 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 09738fee9..535de1066 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 6439d5512..4ddd67699 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 3800c4654..f9c2c6caf 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 de4f011e2..0346d4a45 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"
;;
*)
diff --git a/git.c b/git.c
index 4616df6e6..993cd0d49 100644
--- 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));
diff --git a/pack-objects.c b/pack-objects.c
index 0287449b4..21ee572f4 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 1869b38b7..cd81f5a66 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 67d2a483f..d1c52a6a9 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 000000000..d61410bc5
--- /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 000000000..5170ac425
--- /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 bda3c86be..02de1ef84 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 a54751600..5f56ce805 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+"$@"}
: