Merge branch 'ar/batch-cat'
* ar/batch-cat: change quoting in test t1006-cat-file.sh builtin-cat-file.c: use parse_options() git-svn: Speed up fetch Git.pm: Add hash_and_insert_object and cat_blob Git.pm: Add command_bidi_pipe and command_close_bidi_pipe git-hash-object: Add --stdin-paths option Add more tests for git hash-object Move git-hash-object tests from t5303 to t1007 git-cat-file: Add --batch option git-cat-file: Add --batch-check option git-cat-file: Make option parsing a little more flexible git-cat-file: Small refactor of cmd_cat_file Add tests for git cat-file
This commit is contained in:
commit
29313449f7
@ -9,12 +9,16 @@ git-cat-file - Provide content or type/size information for repository objects
|
||||
SYNOPSIS
|
||||
--------
|
||||
'git-cat-file' [-t | -s | -e | -p | <type>] <object>
|
||||
'git-cat-file' [--batch | --batch-check] < <list-of-objects>
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Provides content or type of objects in the repository. The type
|
||||
is required unless '-t' or '-p' is used to find the object type,
|
||||
or '-s' is used to find the object size.
|
||||
In the first form, provides content or type of objects in the repository. The
|
||||
type is required unless '-t' or '-p' is used to find the object type, or '-s'
|
||||
is used to find the object size.
|
||||
|
||||
In the second form, a list of object (separated by LFs) is provided on stdin,
|
||||
and the SHA1, type, and size of each object is printed on stdout.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
@ -46,6 +50,14 @@ OPTIONS
|
||||
or to ask for a "blob" with <object> being a tag object that
|
||||
points at it.
|
||||
|
||||
--batch::
|
||||
Print the SHA1, type, size, and contents of each object provided on
|
||||
stdin. May not be combined with any other options or arguments.
|
||||
|
||||
--batch-check::
|
||||
Print the SHA1, type, and size of each object provided on stdin. May not be
|
||||
combined with any other options or arguments.
|
||||
|
||||
OUTPUT
|
||||
------
|
||||
If '-t' is specified, one of the <type>.
|
||||
@ -56,9 +68,30 @@ If '-e' is specified, no output.
|
||||
|
||||
If '-p' is specified, the contents of <object> are pretty-printed.
|
||||
|
||||
Otherwise the raw (though uncompressed) contents of the <object> will
|
||||
be returned.
|
||||
If <type> is specified, the raw (though uncompressed) contents of the <object>
|
||||
will be returned.
|
||||
|
||||
If '--batch' is specified, output of the following form is printed for each
|
||||
object specified on stdin:
|
||||
|
||||
------------
|
||||
<sha1> SP <type> SP <size> LF
|
||||
<contents> LF
|
||||
------------
|
||||
|
||||
If '--batch-check' is specified, output of the following form is printed for
|
||||
each object specified fon stdin:
|
||||
|
||||
------------
|
||||
<sha1> SP <type> SP <size> LF
|
||||
------------
|
||||
|
||||
For both '--batch' and '--batch-check', output of the following form is printed
|
||||
for each object specified on stdin that does not exist in the repository:
|
||||
|
||||
------------
|
||||
<object> SP missing LF
|
||||
------------
|
||||
|
||||
Author
|
||||
------
|
||||
|
@ -8,7 +8,7 @@ git-hash-object - Compute object ID and optionally creates a blob from a file
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
'git-hash-object' [-t <type>] [-w] [--stdin] [--] <file>...
|
||||
'git-hash-object' [-t <type>] [-w] [--stdin | --stdin-paths] [--] <file>...
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@ -32,6 +32,9 @@ OPTIONS
|
||||
--stdin::
|
||||
Read the object from standard input instead of from a file.
|
||||
|
||||
--stdin-paths::
|
||||
Read file names from stdin instead of from the command-line.
|
||||
|
||||
Author
|
||||
------
|
||||
Written by Junio C Hamano <junkio@cox.net>
|
||||
|
@ -8,6 +8,10 @@
|
||||
#include "tag.h"
|
||||
#include "tree.h"
|
||||
#include "builtin.h"
|
||||
#include "parse-options.h"
|
||||
|
||||
#define BATCH 1
|
||||
#define BATCH_CHECK 2
|
||||
|
||||
static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
|
||||
{
|
||||
@ -76,31 +80,16 @@ static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long
|
||||
write_or_die(1, cp, endp - cp);
|
||||
}
|
||||
|
||||
int cmd_cat_file(int argc, const char **argv, const char *prefix)
|
||||
static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
enum object_type type;
|
||||
void *buf;
|
||||
unsigned long size;
|
||||
int opt;
|
||||
const char *exp_type, *obj_name;
|
||||
|
||||
git_config(git_default_config);
|
||||
if (argc != 3)
|
||||
usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
|
||||
exp_type = argv[1];
|
||||
obj_name = argv[2];
|
||||
|
||||
if (get_sha1(obj_name, sha1))
|
||||
die("Not a valid object name %s", obj_name);
|
||||
|
||||
opt = 0;
|
||||
if ( exp_type[0] == '-' ) {
|
||||
opt = exp_type[1];
|
||||
if ( !opt || exp_type[2] )
|
||||
opt = -1; /* Not a single character option */
|
||||
}
|
||||
|
||||
buf = NULL;
|
||||
switch (opt) {
|
||||
case 't':
|
||||
@ -157,3 +146,108 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
|
||||
write_or_die(1, buf, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int batch_one_object(const char *obj_name, int print_contents)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
enum object_type type;
|
||||
unsigned long size;
|
||||
void *contents = contents;
|
||||
|
||||
if (!obj_name)
|
||||
return 1;
|
||||
|
||||
if (get_sha1(obj_name, sha1)) {
|
||||
printf("%s missing\n", obj_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (print_contents == BATCH)
|
||||
contents = read_sha1_file(sha1, &type, &size);
|
||||
else
|
||||
type = sha1_object_info(sha1, &size);
|
||||
|
||||
if (type <= 0)
|
||||
return 1;
|
||||
|
||||
printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
|
||||
fflush(stdout);
|
||||
|
||||
if (print_contents == BATCH) {
|
||||
write_or_die(1, contents, size);
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int batch_objects(int print_contents)
|
||||
{
|
||||
struct strbuf buf;
|
||||
|
||||
strbuf_init(&buf, 0);
|
||||
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
|
||||
int error = batch_one_object(buf.buf, print_contents);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char * const cat_file_usage[] = {
|
||||
"git-cat-file [-t|-s|-e|-p|<type>] <sha1>",
|
||||
"git-cat-file [--batch|--batch-check] < <list_of_sha1s>",
|
||||
NULL
|
||||
};
|
||||
|
||||
int cmd_cat_file(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
int opt = 0, batch = 0;
|
||||
const char *exp_type = NULL, *obj_name = NULL;
|
||||
|
||||
const struct option options[] = {
|
||||
OPT_GROUP("<type> can be one of: blob, tree, commit, tag"),
|
||||
OPT_SET_INT('t', NULL, &opt, "show object type", 't'),
|
||||
OPT_SET_INT('s', NULL, &opt, "show object size", 's'),
|
||||
OPT_SET_INT('e', NULL, &opt,
|
||||
"exit with zero when there's no error", 'e'),
|
||||
OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'),
|
||||
OPT_SET_INT(0, "batch", &batch,
|
||||
"show info and content of objects feeded on stdin", BATCH),
|
||||
OPT_SET_INT(0, "batch-check", &batch,
|
||||
"show info about objects feeded on stdin",
|
||||
BATCH_CHECK),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
git_config(git_default_config);
|
||||
|
||||
if (argc != 3 && argc != 2)
|
||||
usage_with_options(cat_file_usage, options);
|
||||
|
||||
argc = parse_options(argc, argv, options, cat_file_usage, 0);
|
||||
|
||||
if (opt) {
|
||||
if (argc == 1)
|
||||
obj_name = argv[0];
|
||||
else
|
||||
usage_with_options(cat_file_usage, options);
|
||||
}
|
||||
if (!opt && !batch) {
|
||||
if (argc == 2) {
|
||||
exp_type = argv[0];
|
||||
obj_name = argv[1];
|
||||
} else
|
||||
usage_with_options(cat_file_usage, options);
|
||||
}
|
||||
if (batch && (opt || argc)) {
|
||||
usage_with_options(cat_file_usage, options);
|
||||
}
|
||||
|
||||
if (batch)
|
||||
return batch_objects(batch);
|
||||
|
||||
return cat_one_file(opt, exp_type, obj_name);
|
||||
}
|
||||
|
42
git-svn.perl
42
git-svn.perl
@ -4,7 +4,7 @@
|
||||
use warnings;
|
||||
use strict;
|
||||
use vars qw/ $AUTHOR $VERSION
|
||||
$sha1 $sha1_short $_revision
|
||||
$sha1 $sha1_short $_revision $_repository
|
||||
$_q $_authors %users/;
|
||||
$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
|
||||
$VERSION = '@@GIT_VERSION@@';
|
||||
@ -222,6 +222,7 @@ unless ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
|
||||
}
|
||||
$ENV{GIT_DIR} = $git_dir;
|
||||
}
|
||||
$_repository = Git->repository(Repository => $ENV{GIT_DIR});
|
||||
}
|
||||
|
||||
my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
|
||||
@ -303,6 +304,7 @@ sub do_git_init_db {
|
||||
}
|
||||
}
|
||||
command_noisy(@init_db);
|
||||
$_repository = Git->repository(Repository => ".git");
|
||||
}
|
||||
my $set;
|
||||
my $pfx = "svn-remote.$Git::SVN::default_repo_id";
|
||||
@ -319,6 +321,7 @@ sub init_subdir {
|
||||
mkpath([$repo_path]) unless -d $repo_path;
|
||||
chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n";
|
||||
$ENV{GIT_DIR} = '.git';
|
||||
$_repository = Git->repository(Repository => $ENV{GIT_DIR});
|
||||
}
|
||||
|
||||
sub cmd_clone {
|
||||
@ -3030,6 +3033,7 @@ use vars qw/@ISA/;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Carp qw/croak/;
|
||||
use File::Temp qw/tempfile/;
|
||||
use IO::File qw//;
|
||||
|
||||
# file baton members: path, mode_a, mode_b, pool, fh, blob, base
|
||||
@ -3185,14 +3189,9 @@ sub apply_textdelta {
|
||||
my $base = IO::File->new_tmpfile;
|
||||
$base->autoflush(1);
|
||||
if ($fb->{blob}) {
|
||||
defined (my $pid = fork) or croak $!;
|
||||
if (!$pid) {
|
||||
open STDOUT, '>&', $base or croak $!;
|
||||
print STDOUT 'link ' if ($fb->{mode_a} == 120000);
|
||||
exec qw/git-cat-file blob/, $fb->{blob} or croak $!;
|
||||
}
|
||||
waitpid $pid, 0;
|
||||
croak $? if $?;
|
||||
print $base 'link ' if ($fb->{mode_a} == 120000);
|
||||
my $size = $::_repository->cat_blob($fb->{blob}, $base);
|
||||
die "Failed to read object $fb->{blob}" unless $size;
|
||||
|
||||
if (defined $exp) {
|
||||
seek $base, 0, 0 or croak $!;
|
||||
@ -3233,14 +3232,18 @@ sub close_file {
|
||||
sysseek($fh, 0, 0) or croak $!;
|
||||
}
|
||||
}
|
||||
defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n";
|
||||
if (!$pid) {
|
||||
open STDIN, '<&', $fh or croak $!;
|
||||
exec qw/git-hash-object -w --stdin/ or croak $!;
|
||||
|
||||
my ($tmp_fh, $tmp_filename) = File::Temp::tempfile(UNLINK => 1);
|
||||
my $result;
|
||||
while ($result = sysread($fh, my $string, 1024)) {
|
||||
syswrite($tmp_fh, $string, $result);
|
||||
}
|
||||
chomp($hash = do { local $/; <$out> });
|
||||
close $out or croak $!;
|
||||
defined $result or croak $!;
|
||||
close $tmp_fh or croak $!;
|
||||
|
||||
close $fh or croak $!;
|
||||
|
||||
$hash = $::_repository->hash_and_insert_object($tmp_filename);
|
||||
$hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
|
||||
close $fb->{base} or croak $!;
|
||||
} else {
|
||||
@ -3566,13 +3569,8 @@ sub chg_file {
|
||||
} elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
|
||||
$self->change_file_prop($fbat,'svn:special',undef);
|
||||
}
|
||||
defined(my $pid = fork) or croak $!;
|
||||
if (!$pid) {
|
||||
open STDOUT, '>&', $fh or croak $!;
|
||||
exec qw/git-cat-file blob/, $m->{sha1_b} or croak $!;
|
||||
}
|
||||
waitpid $pid, 0;
|
||||
croak $? if $?;
|
||||
my $size = $::_repository->cat_blob($m->{sha1_b}, $fh);
|
||||
croak "Failed to read object $m->{sha1_b}" unless $size;
|
||||
$fh->flush == 0 or croak $!;
|
||||
seek $fh, 0, 0 or croak $!;
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "blob.h"
|
||||
#include "quote.h"
|
||||
|
||||
static void hash_object(const char *path, enum object_type type, int write_object)
|
||||
{
|
||||
@ -20,6 +21,7 @@ static void hash_object(const char *path, enum object_type type, int write_objec
|
||||
? "Unable to add %s to database"
|
||||
: "Unable to hash %s", path);
|
||||
printf("%s\n", sha1_to_hex(sha1));
|
||||
maybe_flush_or_die(stdout, "hash to stdout");
|
||||
}
|
||||
|
||||
static void hash_stdin(const char *type, int write_object)
|
||||
@ -30,8 +32,27 @@ static void hash_stdin(const char *type, int write_object)
|
||||
printf("%s\n", sha1_to_hex(sha1));
|
||||
}
|
||||
|
||||
static void hash_stdin_paths(const char *type, int write_objects)
|
||||
{
|
||||
struct strbuf buf, nbuf;
|
||||
|
||||
strbuf_init(&buf, 0);
|
||||
strbuf_init(&nbuf, 0);
|
||||
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
|
||||
if (buf.buf[0] == '"') {
|
||||
strbuf_reset(&nbuf);
|
||||
if (unquote_c_style(&nbuf, buf.buf, NULL))
|
||||
die("line is badly quoted");
|
||||
strbuf_swap(&buf, &nbuf);
|
||||
}
|
||||
hash_object(buf.buf, type_from_string(type), write_objects);
|
||||
}
|
||||
strbuf_release(&buf);
|
||||
strbuf_release(&nbuf);
|
||||
}
|
||||
|
||||
static const char hash_object_usage[] =
|
||||
"git-hash-object [-t <type>] [-w] [--stdin] <file>...";
|
||||
"git-hash-object [ [-t <type>] [-w] [--stdin] <file>... | --stdin-paths < <list-of-paths> ]";
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
@ -42,6 +63,7 @@ int main(int argc, char **argv)
|
||||
int prefix_length = -1;
|
||||
int no_more_flags = 0;
|
||||
int hashstdin = 0;
|
||||
int stdin_paths = 0;
|
||||
|
||||
git_config(git_default_config);
|
||||
|
||||
@ -65,7 +87,19 @@ int main(int argc, char **argv)
|
||||
}
|
||||
else if (!strcmp(argv[i], "--help"))
|
||||
usage(hash_object_usage);
|
||||
else if (!strcmp(argv[i], "--stdin-paths")) {
|
||||
if (hashstdin) {
|
||||
error("Can't use --stdin-paths with --stdin");
|
||||
usage(hash_object_usage);
|
||||
}
|
||||
stdin_paths = 1;
|
||||
|
||||
}
|
||||
else if (!strcmp(argv[i], "--stdin")) {
|
||||
if (stdin_paths) {
|
||||
error("Can't use %s with --stdin-paths", argv[i]);
|
||||
usage(hash_object_usage);
|
||||
}
|
||||
if (hashstdin)
|
||||
die("Multiple --stdin arguments are not supported");
|
||||
hashstdin = 1;
|
||||
@ -76,6 +110,11 @@ int main(int argc, char **argv)
|
||||
else {
|
||||
const char *arg = argv[i];
|
||||
|
||||
if (stdin_paths) {
|
||||
error("Can't specify files (such as \"%s\") with --stdin-paths", arg);
|
||||
usage(hash_object_usage);
|
||||
}
|
||||
|
||||
if (hashstdin) {
|
||||
hash_stdin(type, write_object);
|
||||
hashstdin = 0;
|
||||
@ -87,6 +126,10 @@ int main(int argc, char **argv)
|
||||
no_more_flags = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (stdin_paths)
|
||||
hash_stdin_paths(type, write_object);
|
||||
|
||||
if (hashstdin)
|
||||
hash_stdin(type, write_object);
|
||||
return 0;
|
||||
|
208
perl/Git.pm
208
perl/Git.pm
@ -39,6 +39,10 @@ $VERSION = '0.01';
|
||||
my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ],
|
||||
STDERR => 0 );
|
||||
|
||||
my $sha1 = $repo->hash_and_insert_object('file.txt');
|
||||
my $tempfile = tempfile();
|
||||
my $size = $repo->cat_blob($sha1, $tempfile);
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
@ -51,6 +55,7 @@ require Exporter;
|
||||
# Methods which can be called as standalone functions as well:
|
||||
@EXPORT_OK = qw(command command_oneline command_noisy
|
||||
command_output_pipe command_input_pipe command_close_pipe
|
||||
command_bidi_pipe command_close_bidi_pipe
|
||||
version exec_path hash_object git_cmd_try);
|
||||
|
||||
|
||||
@ -92,6 +97,7 @@ increate nonwithstanding).
|
||||
use Carp qw(carp croak); # but croak is bad - throw instead
|
||||
use Error qw(:try);
|
||||
use Cwd qw(abs_path);
|
||||
use IPC::Open2 qw(open2);
|
||||
|
||||
}
|
||||
|
||||
@ -216,7 +222,6 @@ sub repository {
|
||||
bless $self, $class;
|
||||
}
|
||||
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
@ -375,6 +380,60 @@ sub command_close_pipe {
|
||||
_cmd_close($fh, $ctx);
|
||||
}
|
||||
|
||||
=item command_bidi_pipe ( COMMAND [, ARGUMENTS... ] )
|
||||
|
||||
Execute the given C<COMMAND> in the same way as command_output_pipe()
|
||||
does but return both an input pipe filehandle and an output pipe filehandle.
|
||||
|
||||
The function will return return C<($pid, $pipe_in, $pipe_out, $ctx)>.
|
||||
See C<command_close_bidi_pipe()> for details.
|
||||
|
||||
=cut
|
||||
|
||||
sub command_bidi_pipe {
|
||||
my ($pid, $in, $out);
|
||||
$pid = open2($in, $out, 'git', @_);
|
||||
return ($pid, $in, $out, join(' ', @_));
|
||||
}
|
||||
|
||||
=item command_close_bidi_pipe ( PID, PIPE_IN, PIPE_OUT [, CTX] )
|
||||
|
||||
Close the C<PIPE_IN> and C<PIPE_OUT> as returned from C<command_bidi_pipe()>,
|
||||
checking whether the command finished successfully. The optional C<CTX>
|
||||
argument is required if you want to see the command name in the error message,
|
||||
and it is the fourth value returned by C<command_bidi_pipe()>. The call idiom
|
||||
is:
|
||||
|
||||
my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check');
|
||||
print "000000000\n" $out;
|
||||
while (<$in>) { ... }
|
||||
$r->command_close_bidi_pipe($pid, $in, $out, $ctx);
|
||||
|
||||
Note that you should not rely on whatever actually is in C<CTX>;
|
||||
currently it is simply the command name but in future the context might
|
||||
have more complicated structure.
|
||||
|
||||
=cut
|
||||
|
||||
sub command_close_bidi_pipe {
|
||||
my ($pid, $in, $out, $ctx) = @_;
|
||||
foreach my $fh ($in, $out) {
|
||||
unless (close $fh) {
|
||||
if ($!) {
|
||||
carp "error closing pipe: $!";
|
||||
} elsif ($? >> 8) {
|
||||
throw Git::Error::Command($ctx, $? >>8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitpid $pid, 0;
|
||||
|
||||
if ($? >> 8) {
|
||||
throw Git::Error::Command($ctx, $? >>8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
=item command_noisy ( COMMAND [, ARGUMENTS... ] )
|
||||
|
||||
@ -678,6 +737,147 @@ sub hash_object {
|
||||
}
|
||||
|
||||
|
||||
=item hash_and_insert_object ( FILENAME )
|
||||
|
||||
Compute the SHA1 object id of the given C<FILENAME> and add the object to the
|
||||
object database.
|
||||
|
||||
The function returns the SHA1 hash.
|
||||
|
||||
=cut
|
||||
|
||||
# TODO: Support for passing FILEHANDLE instead of FILENAME
|
||||
sub hash_and_insert_object {
|
||||
my ($self, $filename) = @_;
|
||||
|
||||
carp "Bad filename \"$filename\"" if $filename =~ /[\r\n]/;
|
||||
|
||||
$self->_open_hash_and_insert_object_if_needed();
|
||||
my ($in, $out) = ($self->{hash_object_in}, $self->{hash_object_out});
|
||||
|
||||
unless (print $out $filename, "\n") {
|
||||
$self->_close_hash_and_insert_object();
|
||||
throw Error::Simple("out pipe went bad");
|
||||
}
|
||||
|
||||
chomp(my $hash = <$in>);
|
||||
unless (defined($hash)) {
|
||||
$self->_close_hash_and_insert_object();
|
||||
throw Error::Simple("in pipe went bad");
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
sub _open_hash_and_insert_object_if_needed {
|
||||
my ($self) = @_;
|
||||
|
||||
return if defined($self->{hash_object_pid});
|
||||
|
||||
($self->{hash_object_pid}, $self->{hash_object_in},
|
||||
$self->{hash_object_out}, $self->{hash_object_ctx}) =
|
||||
command_bidi_pipe(qw(hash-object -w --stdin-paths));
|
||||
}
|
||||
|
||||
sub _close_hash_and_insert_object {
|
||||
my ($self) = @_;
|
||||
|
||||
return unless defined($self->{hash_object_pid});
|
||||
|
||||
my @vars = map { 'hash_object_' . $_ } qw(pid in out ctx);
|
||||
|
||||
command_close_bidi_pipe($self->{@vars});
|
||||
delete $self->{@vars};
|
||||
}
|
||||
|
||||
=item cat_blob ( SHA1, FILEHANDLE )
|
||||
|
||||
Prints the contents of the blob identified by C<SHA1> to C<FILEHANDLE> and
|
||||
returns the number of bytes printed.
|
||||
|
||||
=cut
|
||||
|
||||
sub cat_blob {
|
||||
my ($self, $sha1, $fh) = @_;
|
||||
|
||||
$self->_open_cat_blob_if_needed();
|
||||
my ($in, $out) = ($self->{cat_blob_in}, $self->{cat_blob_out});
|
||||
|
||||
unless (print $out $sha1, "\n") {
|
||||
$self->_close_cat_blob();
|
||||
throw Error::Simple("out pipe went bad");
|
||||
}
|
||||
|
||||
my $description = <$in>;
|
||||
if ($description =~ / missing$/) {
|
||||
carp "$sha1 doesn't exist in the repository";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($description !~ /^[0-9a-fA-F]{40} \S+ (\d+)$/) {
|
||||
carp "Unexpected result returned from git cat-file";
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $size = $1;
|
||||
|
||||
my $blob;
|
||||
my $bytesRead = 0;
|
||||
|
||||
while (1) {
|
||||
my $bytesLeft = $size - $bytesRead;
|
||||
last unless $bytesLeft;
|
||||
|
||||
my $bytesToRead = $bytesLeft < 1024 ? $bytesLeft : 1024;
|
||||
my $read = read($in, $blob, $bytesToRead, $bytesRead);
|
||||
unless (defined($read)) {
|
||||
$self->_close_cat_blob();
|
||||
throw Error::Simple("in pipe went bad");
|
||||
}
|
||||
|
||||
$bytesRead += $read;
|
||||
}
|
||||
|
||||
# Skip past the trailing newline.
|
||||
my $newline;
|
||||
my $read = read($in, $newline, 1);
|
||||
unless (defined($read)) {
|
||||
$self->_close_cat_blob();
|
||||
throw Error::Simple("in pipe went bad");
|
||||
}
|
||||
unless ($read == 1 && $newline eq "\n") {
|
||||
$self->_close_cat_blob();
|
||||
throw Error::Simple("didn't find newline after blob");
|
||||
}
|
||||
|
||||
unless (print $fh $blob) {
|
||||
$self->_close_cat_blob();
|
||||
throw Error::Simple("couldn't write to passed in filehandle");
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
sub _open_cat_blob_if_needed {
|
||||
my ($self) = @_;
|
||||
|
||||
return if defined($self->{cat_blob_pid});
|
||||
|
||||
($self->{cat_blob_pid}, $self->{cat_blob_in},
|
||||
$self->{cat_blob_out}, $self->{cat_blob_ctx}) =
|
||||
command_bidi_pipe(qw(cat-file --batch));
|
||||
}
|
||||
|
||||
sub _close_cat_blob {
|
||||
my ($self) = @_;
|
||||
|
||||
return unless defined($self->{cat_blob_pid});
|
||||
|
||||
my @vars = map { 'cat_blob_' . $_ } qw(pid in out ctx);
|
||||
|
||||
command_close_bidi_pipe($self->{@vars});
|
||||
delete $self->{@vars};
|
||||
}
|
||||
|
||||
=back
|
||||
|
||||
@ -895,7 +1095,11 @@ sub _cmd_close {
|
||||
}
|
||||
|
||||
|
||||
sub DESTROY { }
|
||||
sub DESTROY {
|
||||
my ($self) = @_;
|
||||
$self->_close_hash_and_insert_object();
|
||||
$self->_close_cat_blob();
|
||||
}
|
||||
|
||||
|
||||
# Pipe implementation for ActiveState Perl.
|
||||
|
226
t/t1006-cat-file.sh
Executable file
226
t/t1006-cat-file.sh
Executable file
@ -0,0 +1,226 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='git cat-file'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
echo_without_newline () {
|
||||
printf '%s' "$*"
|
||||
}
|
||||
|
||||
strlen () {
|
||||
echo_without_newline "$1" | wc -c | sed -e 's/^ *//'
|
||||
}
|
||||
|
||||
maybe_remove_timestamp () {
|
||||
if test -z "$2"; then
|
||||
echo_without_newline "$1"
|
||||
else
|
||||
echo_without_newline "$(printf '%s\n' "$1" | sed -e 's/ [0-9][0-9]* [-+][0-9][0-9][0-9][0-9]$//')"
|
||||
fi
|
||||
}
|
||||
|
||||
run_tests () {
|
||||
type=$1
|
||||
sha1=$2
|
||||
size=$3
|
||||
content=$4
|
||||
pretty_content=$5
|
||||
no_ts=$6
|
||||
|
||||
batch_output="$sha1 $type $size
|
||||
$content"
|
||||
|
||||
test_expect_success "$type exists" '
|
||||
git cat-file -e $sha1
|
||||
'
|
||||
|
||||
test_expect_success "Type of $type is correct" '
|
||||
test $type = "$(git cat-file -t $sha1)"
|
||||
'
|
||||
|
||||
test_expect_success "Size of $type is correct" '
|
||||
test $size = "$(git cat-file -s $sha1)"
|
||||
'
|
||||
|
||||
test -z "$content" ||
|
||||
test_expect_success "Content of $type is correct" '
|
||||
expect="$(maybe_remove_timestamp "$content" $no_ts)"
|
||||
actual="$(maybe_remove_timestamp "$(git cat-file $type $sha1)" $no_ts)"
|
||||
|
||||
if test "z$expect" = "z$actual"
|
||||
then
|
||||
: happy
|
||||
else
|
||||
echo "Oops: expected $expect"
|
||||
echo "but got $actual"
|
||||
false
|
||||
fi
|
||||
'
|
||||
|
||||
test_expect_success "Pretty content of $type is correct" '
|
||||
expect="$(maybe_remove_timestamp "$pretty_content" $no_ts)"
|
||||
actual="$(maybe_remove_timestamp "$(git cat-file -p $sha1)" $no_ts)"
|
||||
if test "z$expect" = "z$actual"
|
||||
then
|
||||
: happy
|
||||
else
|
||||
echo "Oops: expected $expect"
|
||||
echo "but got $actual"
|
||||
false
|
||||
fi
|
||||
'
|
||||
|
||||
test -z "$content" ||
|
||||
test_expect_success "--batch output of $type is correct" '
|
||||
expect="$(maybe_remove_timestamp "$batch_output" $no_ts)"
|
||||
actual="$(maybe_remove_timestamp "$(echo $sha1 | git cat-file --batch)" no_ts)"
|
||||
if test "z$expect" = "z$actual"
|
||||
then
|
||||
: happy
|
||||
else
|
||||
echo "Oops: expected $expect"
|
||||
echo "but got $actual"
|
||||
false
|
||||
fi
|
||||
'
|
||||
|
||||
test_expect_success "--batch-check output of $type is correct" '
|
||||
expect="$sha1 $type $size"
|
||||
actual="$(echo_without_newline $sha1 | git cat-file --batch-check)"
|
||||
if test "z$expect" = "z$actual"
|
||||
then
|
||||
: happy
|
||||
else
|
||||
echo "Oops: expected $expect"
|
||||
echo "but got $actual"
|
||||
false
|
||||
fi
|
||||
'
|
||||
}
|
||||
|
||||
hello_content="Hello World"
|
||||
hello_size=$(strlen "$hello_content")
|
||||
hello_sha1=$(echo_without_newline "$hello_content" | git hash-object --stdin)
|
||||
|
||||
test_expect_success "setup" '
|
||||
echo_without_newline "$hello_content" > hello &&
|
||||
git update-index --add hello
|
||||
'
|
||||
|
||||
run_tests 'blob' $hello_sha1 $hello_size "$hello_content" "$hello_content"
|
||||
|
||||
tree_sha1=$(git write-tree)
|
||||
tree_size=33
|
||||
tree_pretty_content="100644 blob $hello_sha1 hello"
|
||||
|
||||
run_tests 'tree' $tree_sha1 $tree_size "" "$tree_pretty_content"
|
||||
|
||||
commit_message="Intial commit"
|
||||
commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1)
|
||||
commit_size=176
|
||||
commit_content="tree $tree_sha1
|
||||
author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000
|
||||
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000
|
||||
|
||||
$commit_message"
|
||||
|
||||
run_tests 'commit' $commit_sha1 $commit_size "$commit_content" "$commit_content" 1
|
||||
|
||||
tag_header_without_timestamp="object $hello_sha1
|
||||
type blob
|
||||
tag hellotag
|
||||
tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
|
||||
tag_description="This is a tag"
|
||||
tag_content="$tag_header_without_timestamp 0000000000 +0000
|
||||
|
||||
$tag_description"
|
||||
tag_pretty_content="$tag_header_without_timestamp Thu Jan 1 00:00:00 1970 +0000
|
||||
|
||||
$tag_description"
|
||||
|
||||
tag_sha1=$(echo_without_newline "$tag_content" | git mktag)
|
||||
tag_size=$(strlen "$tag_content")
|
||||
|
||||
run_tests 'tag' $tag_sha1 $tag_size "$tag_content" "$tag_pretty_content" 1
|
||||
|
||||
test_expect_success \
|
||||
"Reach a blob from a tag pointing to it" \
|
||||
"test '$hello_content' = \"\$(git cat-file blob $tag_sha1)\""
|
||||
|
||||
for batch in batch batch-check
|
||||
do
|
||||
for opt in t s e p
|
||||
do
|
||||
test_expect_success "Passing -$opt with --$batch fails" '
|
||||
test_must_fail git cat-file --$batch -$opt $hello_sha1
|
||||
'
|
||||
|
||||
test_expect_success "Passing --$batch with -$opt fails" '
|
||||
test_must_fail git cat-file -$opt --$batch $hello_sha1
|
||||
'
|
||||
done
|
||||
|
||||
test_expect_success "Passing <type> with --$batch fails" '
|
||||
test_must_fail git cat-file --$batch blob $hello_sha1
|
||||
'
|
||||
|
||||
test_expect_success "Passing --$batch with <type> fails" '
|
||||
test_must_fail git cat-file blob --$batch $hello_sha1
|
||||
'
|
||||
|
||||
test_expect_success "Passing sha1 with --$batch fails" '
|
||||
test_must_fail git cat-file --$batch $hello_sha1
|
||||
'
|
||||
done
|
||||
|
||||
test_expect_success "--batch-check for a non-existent object" '
|
||||
test "deadbeef missing" = \
|
||||
"$(echo_without_newline deadbeef | git cat-file --batch-check)"
|
||||
'
|
||||
|
||||
test_expect_success "--batch-check for an emtpy line" '
|
||||
test " missing" = "$(echo | git cat-file --batch-check)"
|
||||
'
|
||||
|
||||
batch_input="$hello_sha1
|
||||
$commit_sha1
|
||||
$tag_sha1
|
||||
deadbeef
|
||||
|
||||
"
|
||||
|
||||
batch_output="$hello_sha1 blob $hello_size
|
||||
$hello_content
|
||||
$commit_sha1 commit $commit_size
|
||||
$commit_content
|
||||
$tag_sha1 tag $tag_size
|
||||
$tag_content
|
||||
deadbeef missing
|
||||
missing"
|
||||
|
||||
test_expect_success '--batch with multiple sha1s gives correct format' '
|
||||
test "$(maybe_remove_timestamp "$batch_output" 1)" = "$(maybe_remove_timestamp "$(echo_without_newline "$batch_input" | git cat-file --batch)" 1)"
|
||||
'
|
||||
|
||||
batch_check_input="$hello_sha1
|
||||
$tree_sha1
|
||||
$commit_sha1
|
||||
$tag_sha1
|
||||
deadbeef
|
||||
|
||||
"
|
||||
|
||||
batch_check_output="$hello_sha1 blob $hello_size
|
||||
$tree_sha1 tree $tree_size
|
||||
$commit_sha1 commit $commit_size
|
||||
$tag_sha1 tag $tag_size
|
||||
deadbeef missing
|
||||
missing"
|
||||
|
||||
test_expect_success "--batch-check with multiple sha1s gives correct format" '
|
||||
test "$batch_check_output" = \
|
||||
"$(echo_without_newline "$batch_check_input" | git cat-file --batch-check)"
|
||||
'
|
||||
|
||||
test_done
|
133
t/t1007-hash-object.sh
Executable file
133
t/t1007-hash-object.sh
Executable file
@ -0,0 +1,133 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description=git-hash-object
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
echo_without_newline() {
|
||||
printf '%s' "$*"
|
||||
}
|
||||
|
||||
test_blob_does_not_exist() {
|
||||
test_expect_success 'blob does not exist in database' "
|
||||
test_must_fail git cat-file blob $1
|
||||
"
|
||||
}
|
||||
|
||||
test_blob_exists() {
|
||||
test_expect_success 'blob exists in database' "
|
||||
git cat-file blob $1
|
||||
"
|
||||
}
|
||||
|
||||
hello_content="Hello World"
|
||||
hello_sha1=5e1c309dae7f45e0f39b1bf3ac3cd9db12e7d689
|
||||
|
||||
example_content="This is an example"
|
||||
example_sha1=ddd3f836d3e3fbb7ae289aa9ae83536f76956399
|
||||
|
||||
setup_repo() {
|
||||
echo_without_newline "$hello_content" > hello
|
||||
echo_without_newline "$example_content" > example
|
||||
}
|
||||
|
||||
test_repo=test
|
||||
push_repo() {
|
||||
test_create_repo $test_repo
|
||||
cd $test_repo
|
||||
|
||||
setup_repo
|
||||
}
|
||||
|
||||
pop_repo() {
|
||||
cd ..
|
||||
rm -rf $test_repo
|
||||
}
|
||||
|
||||
setup_repo
|
||||
|
||||
# Argument checking
|
||||
|
||||
test_expect_success "multiple '--stdin's are rejected" '
|
||||
test_must_fail git hash-object --stdin --stdin < example
|
||||
'
|
||||
|
||||
test_expect_success "Can't use --stdin and --stdin-paths together" '
|
||||
test_must_fail git hash-object --stdin --stdin-paths &&
|
||||
test_must_fail git hash-object --stdin-paths --stdin
|
||||
'
|
||||
|
||||
test_expect_success "Can't pass filenames as arguments with --stdin-paths" '
|
||||
test_must_fail git hash-object --stdin-paths hello < example
|
||||
'
|
||||
|
||||
# Behavior
|
||||
|
||||
push_repo
|
||||
|
||||
test_expect_success 'hash a file' '
|
||||
test $hello_sha1 = $(git hash-object hello)
|
||||
'
|
||||
|
||||
test_blob_does_not_exist $hello_sha1
|
||||
|
||||
test_expect_success 'hash from stdin' '
|
||||
test $example_sha1 = $(git hash-object --stdin < example)
|
||||
'
|
||||
|
||||
test_blob_does_not_exist $example_sha1
|
||||
|
||||
test_expect_success 'hash a file and write to database' '
|
||||
test $hello_sha1 = $(git hash-object -w hello)
|
||||
'
|
||||
|
||||
test_blob_exists $hello_sha1
|
||||
|
||||
test_expect_success 'git hash-object --stdin file1 <file0 first operates on file0, then file1' '
|
||||
echo foo > file1 &&
|
||||
obname0=$(echo bar | git hash-object --stdin) &&
|
||||
obname1=$(git hash-object file1) &&
|
||||
obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
|
||||
obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
|
||||
test "$obname0" = "$obname0new" &&
|
||||
test "$obname1" = "$obname1new"
|
||||
'
|
||||
|
||||
pop_repo
|
||||
|
||||
for args in "-w --stdin" "--stdin -w"; do
|
||||
push_repo
|
||||
|
||||
test_expect_success "hash from stdin and write to database ($args)" '
|
||||
test $example_sha1 = $(git hash-object $args < example)
|
||||
'
|
||||
|
||||
test_blob_exists $example_sha1
|
||||
|
||||
pop_repo
|
||||
done
|
||||
|
||||
filenames="hello
|
||||
example"
|
||||
|
||||
sha1s="$hello_sha1
|
||||
$example_sha1"
|
||||
|
||||
test_expect_success "hash two files with names on stdin" '
|
||||
test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object --stdin-paths)"
|
||||
'
|
||||
|
||||
for args in "-w --stdin-paths" "--stdin-paths -w"; do
|
||||
push_repo
|
||||
|
||||
test_expect_success "hash two files with names on stdin and write to database ($args)" '
|
||||
test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object $args)"
|
||||
'
|
||||
|
||||
test_blob_exists $hello_sha1
|
||||
test_blob_exists $example_sha1
|
||||
|
||||
pop_repo
|
||||
done
|
||||
|
||||
test_done
|
@ -1,35 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description=git-hash-object
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success \
|
||||
'git hash-object -w --stdin saves the object' \
|
||||
'obname=$(echo foo | git hash-object -w --stdin) &&
|
||||
obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
|
||||
test -r .git/objects/"$obpath" &&
|
||||
rm -f .git/objects/"$obpath"'
|
||||
|
||||
test_expect_success \
|
||||
'git hash-object --stdin -w saves the object' \
|
||||
'obname=$(echo foo | git hash-object --stdin -w) &&
|
||||
obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
|
||||
test -r .git/objects/"$obpath" &&
|
||||
rm -f .git/objects/"$obpath"'
|
||||
|
||||
test_expect_success \
|
||||
'git hash-object --stdin file1 <file0 first operates on file0, then file1' \
|
||||
'echo foo > file1 &&
|
||||
obname0=$(echo bar | git hash-object --stdin) &&
|
||||
obname1=$(git hash-object file1) &&
|
||||
obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
|
||||
obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
|
||||
test "$obname0" = "$obname0new" &&
|
||||
test "$obname1" = "$obname1new"'
|
||||
|
||||
test_expect_success \
|
||||
'git hash-object refuses multiple --stdin arguments' \
|
||||
'! git hash-object --stdin --stdin < file1'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user