Merge branch 'bc/sha-256-part-2'

SHA-256 migration work continues.

* bc/sha-256-part-2: (44 commits)
  remote-testgit: adapt for object-format
  bundle: detect hash algorithm when reading refs
  t5300: pass --object-format to git index-pack
  t5704: send object-format capability with SHA-256
  t5703: use object-format serve option
  t5702: offer an object-format capability in the test
  t/helper: initialize the repository for test-sha1-array
  remote-curl: avoid truncating refs with ls-remote
  t1050: pass algorithm to index-pack when outside repo
  builtin/index-pack: add option to specify hash algorithm
  remote-curl: detect algorithm for dumb HTTP by size
  builtin/ls-remote: initialize repository based on fetch
  t5500: make hash independent
  serve: advertise object-format capability for protocol v2
  connect: parse v2 refs with correct hash algorithm
  connect: pass full packet reader when parsing v2 refs
  Documentation/technical: document object-format for protocol v2
  t1302: expect repo format version 1 for SHA-256
  builtin/show-index: provide options to determine hash algo
  t5302: modernize test formatting
  ...
This commit is contained in:
Junio C Hamano 2020-07-06 22:09:13 -07:00
commit 12210859da
45 changed files with 696 additions and 248 deletions

View File

@ -93,6 +93,14 @@ OPTIONS
--max-input-size=<size>::
Die, if the pack is larger than <size>.
--object-format=<hash-algorithm>::
Specify the given object format (hash algorithm) for the pack. The valid
values are 'sha1' and (if enabled) 'sha256'. The default is the algorithm for
the current repository (set by `extensions.objectFormat`), or 'sha1' if no
value is set or outside a repository.
+
This option cannot be used with --stdin.
NOTES
-----

View File

@ -9,7 +9,7 @@ git-show-index - Show packed archive index
SYNOPSIS
--------
[verse]
'git show-index'
'git show-index' [--object-format=<hash-algorithm>]
DESCRIPTION
@ -36,6 +36,15 @@ Note that you can get more information on a packfile by calling
linkgit:git-verify-pack[1]. However, as this command considers only the
index file itself, it's both faster and more flexible.
OPTIONS
-------
--object-format=<hash-algorithm>::
Specify the given object format (hash algorithm) for the index file. The
valid values are 'sha1' and (if enabled) 'sha256'. The default is the
algorithm for the current repository (set by `extensions.objectFormat`), or
'sha1' if no value is set or outside a repository..
GIT
---
Part of the linkgit:git[1] suite

View File

@ -238,6 +238,9 @@ the remote repository.
`--signed-tags=verbatim` to linkgit:git-fast-export[1]. In the
absence of this capability, Git will use `--signed-tags=warn-strip`.
'object-format'::
This indicates that the helper is able to interact with the remote
side using an explicit hash algorithm extension.
COMMANDS
@ -257,12 +260,14 @@ Support for this command is mandatory.
'list'::
Lists the refs, one per line, in the format "<value> <name>
[<attr> ...]". The value may be a hex sha1 hash, "@<dest>" for
a symref, or "?" to indicate that the helper could not get the
value of the ref. A space-separated list of attributes follows
the name; unrecognized attributes are ignored. The list ends
with a blank line.
a symref, ":<keyword> <value>" for a key-value pair, or
"?" to indicate that the helper could not get the value of the
ref. A space-separated list of attributes follows the name;
unrecognized attributes are ignored. The list ends with a
blank line.
+
See REF LIST ATTRIBUTES for a list of currently defined attributes.
See REF LIST KEYWORDS for a list of currently defined keywords.
+
Supported if the helper has the "fetch" or "import" capability.
@ -432,6 +437,18 @@ attributes are defined.
This ref is unchanged since the last import or fetch, although
the helper cannot necessarily determine what value that produced.
REF LIST KEYWORDS
-----------------
The 'list' command may produce a list of key-value pairs.
The following keys are defined.
'object-format'::
The refs are using the given hash algorithm. This keyword is only
used if the server and client both support the object-format
extension.
OPTIONS
-------
@ -516,6 +533,14 @@ set by Git if the remote helper has the 'option' capability.
transaction. If successful, all refs will be updated, or none will. If the
remote side does not support this capability, the push will fail.
'option object-format' {'true'|algorithm}::
If 'true', indicate that the caller wants hash algorithm information
to be passed back from the remote. This mode is used when fetching
refs.
+
If set to an algorithm, indicate that the caller wants to interact with
the remote side using that algorithm.
SEE ALSO
--------
linkgit:git-remote[1]

View File

@ -176,6 +176,21 @@ agent strings are purely informative for statistics and debugging
purposes, and MUST NOT be used to programmatically assume the presence
or absence of particular features.
object-format
-------------
This capability, which takes a hash algorithm as an argument, indicates
that the server supports the given hash algorithms. It may be sent
multiple times; if so, the first one given is the one used in the ref
advertisement.
When provided by the client, this indicates that it intends to use the
given hash algorithm to communicate. The algorithm provided must be one
that the server supports.
If this capability is not provided, it is assumed that the only
supported algorithm is SHA-1.
symref
------

View File

@ -483,3 +483,12 @@ included in a request. This is done by sending each option as a
a request.
The provided options must not contain a NUL or LF character.
object-format
~~~~~~~~~~~~~~~
The server can advertise the `object-format` capability with a value `X` (in the
form `object-format=X`) to notify the client that the server is able to deal
with objects using hash algorithm X. If not specified, the server is assumed to
only handle SHA-1. If the client would like to use a hash algorithm other than
SHA-1, it should specify its object-format string.

View File

@ -1220,6 +1220,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
refs = transport_get_remote_refs(transport, &ref_prefixes);
if (refs) {
int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
/*
* Now that we know what algorithm the remote side is using,
* let's set ours to the same thing.
*/
initialize_repository_version(hash_algo);
repo_set_hash_algo(the_repository, hash_algo);
mapped_refs = wanted_peer_refs(refs, &remote->fetch);
/*
* transport_get_remote_refs() may return refs with null sha-1

View File

@ -1555,13 +1555,9 @@ static void read_v2_anomalous_offsets(struct packed_git *p,
{
const uint32_t *idx1, *idx2;
uint32_t i;
const uint32_t hashwords = the_hash_algo->rawsz / sizeof(uint32_t);
/* The address of the 4-byte offset table */
idx1 = (((const uint32_t *)p->index_data)
+ 2 /* 8-byte header */
+ 256 /* fan out */
+ hashwords * p->num_objects /* object ID table */
idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset))
+ p->num_objects /* CRC32 table */
);
@ -1671,6 +1667,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
unsigned char pack_hash[GIT_MAX_RAWSZ];
unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */
int report_end_of_input = 0;
int hash_algo = 0;
/*
* index-pack never needs to fetch missing objects except when
@ -1764,6 +1761,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
die(_("bad %s"), arg);
} else if (skip_prefix(arg, "--max-input-size=", &arg)) {
max_input_size = strtoumax(arg, NULL, 10);
} else if (skip_prefix(arg, "--object-format=", &arg)) {
hash_algo = hash_algo_by_name(arg);
if (hash_algo == GIT_HASH_UNKNOWN)
die(_("unknown hash algorithm '%s'"), arg);
repo_set_hash_algo(the_repository, hash_algo);
} else
usage(index_pack_usage);
continue;
@ -1780,6 +1782,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
die(_("--fix-thin cannot be used without --stdin"));
if (from_stdin && !startup_info->have_repository)
die(_("--stdin requires a git repository"));
if (from_stdin && hash_algo)
die(_("--object-format cannot be used with --stdin"));
if (!index_name && pack_name)
index_name = derive_filename(pack_name, "idx", &index_name_buf);

View File

@ -118,6 +118,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
transport->server_options = &server_options;
ref = transport_get_remote_refs(transport, &ref_prefixes);
if (ref) {
int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
repo_set_hash_algo(the_repository, hash_algo);
}
if (transport_disconnect(transport)) {
UNLEAK(sorting);
return 1;

View File

@ -249,6 +249,7 @@ static void show_ref(const char *path, const struct object_id *oid)
strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
if (advertise_push_options)
strbuf_addstr(&cap, " push-options");
strbuf_addf(&cap, " object-format=%s", the_hash_algo->name);
strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
packet_write_fmt(1, "%s %s%c%s\n",
oid_to_hex(oid), path, 0, cap.buf);
@ -1624,6 +1625,8 @@ static struct command *read_head_info(struct packet_reader *reader,
linelen = strlen(reader->line);
if (linelen < reader->pktlen) {
const char *feature_list = reader->line + linelen + 1;
const char *hash = NULL;
int len = 0;
if (parse_feature_request(feature_list, "report-status"))
report_status = 1;
if (parse_feature_request(feature_list, "side-band-64k"))
@ -1636,6 +1639,13 @@ static struct command *read_head_info(struct packet_reader *reader,
if (advertise_push_options
&& parse_feature_request(feature_list, "push-options"))
use_push_options = 1;
hash = parse_feature_value(feature_list, "object-format", &len, NULL);
if (!hash) {
hash = hash_algos[GIT_HASH_SHA1].name;
len = strlen(hash);
}
if (xstrncmpz(the_hash_algo->name, hash, len))
die("error: unsupported object format '%s'", hash);
}
if (!strcmp(reader->line, "push-cert")) {

View File

@ -1,9 +1,12 @@
#include "builtin.h"
#include "cache.h"
#include "pack.h"
#include "parse-options.h"
static const char show_index_usage[] =
"git show-index";
static const char *const show_index_usage[] = {
"git show-index [--object-format=<hash-algorithm>]",
NULL
};
int cmd_show_index(int argc, const char **argv, const char *prefix)
{
@ -11,10 +14,26 @@ int cmd_show_index(int argc, const char **argv, const char *prefix)
unsigned nr;
unsigned int version;
static unsigned int top_index[256];
const unsigned hashsz = the_hash_algo->rawsz;
unsigned hashsz;
const char *hash_name = NULL;
int hash_algo;
const struct option show_index_options[] = {
OPT_STRING(0, "object-format", &hash_name, N_("hash-algorithm"),
N_("specify the hash algorithm to use")),
OPT_END()
};
argc = parse_options(argc, argv, prefix, show_index_options, show_index_usage, 0);
if (hash_name) {
hash_algo = hash_algo_by_name(hash_name);
if (hash_algo == GIT_HASH_UNKNOWN)
die(_("Unknown hash algorithm"));
repo_set_hash_algo(the_repository, hash_algo);
}
hashsz = the_hash_algo->rawsz;
if (argc != 1)
usage(show_index_usage);
if (fread(top_index, 2 * 4, 1, stdin) != 1)
die("unable to read header");
if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) {

View File

@ -23,6 +23,17 @@ static void add_to_ref_list(const struct object_id *oid, const char *name,
list->nr++;
}
static const struct git_hash_algo *detect_hash_algo(struct strbuf *buf)
{
size_t len = strcspn(buf->buf, " \n");
int algo;
algo = hash_algo_by_length(len / 2);
if (algo == GIT_HASH_UNKNOWN)
return NULL;
return &hash_algos[algo];
}
static int parse_bundle_header(int fd, struct bundle_header *header,
const char *report_path)
{
@ -52,12 +63,21 @@ static int parse_bundle_header(int fd, struct bundle_header *header,
}
strbuf_rtrim(&buf);
if (!header->hash_algo) {
header->hash_algo = detect_hash_algo(&buf);
if (!header->hash_algo) {
error(_("unknown hash algorithm length"));
status = -1;
break;
}
}
/*
* Tip lines have object name, SP, and refname.
* Prerequisites have object name that is optionally
* followed by SP and subject line.
*/
if (parse_oid_hex(buf.buf, &oid, &p) ||
if (parse_oid_hex_algop(buf.buf, &oid, &p, header->hash_algo) ||
(*p && !isspace(*p)) ||
(!is_prereq && !*p)) {
if (report_path)

View File

@ -15,6 +15,7 @@ struct ref_list {
struct bundle_header {
struct ref_list prerequisites;
struct ref_list references;
const struct git_hash_algo *hash_algo;
};
int is_bundle(const char *path, int quiet);

138
connect.c
View File

@ -18,7 +18,7 @@
static char *server_capabilities_v1;
static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;
static const char *parse_feature_value(const char *, const char *, int *);
static const char *next_server_feature_value(const char *feature, int *len, int *offset);
static int check_ref(const char *name, unsigned int flags)
{
@ -83,6 +83,21 @@ int server_supports_v2(const char *c, int die_on_error)
return 0;
}
int server_feature_v2(const char *c, const char **v)
{
int i;
for (i = 0; i < server_capabilities_v2.argc; i++) {
const char *out;
if (skip_prefix(server_capabilities_v2.argv[i], c, &out) &&
(*out == '=')) {
*v = out + 1;
return 1;
}
}
return 0;
}
int server_supports_feature(const char *c, const char *feature,
int die_on_error)
{
@ -181,17 +196,16 @@ reject:
static void annotate_refs_with_symref_info(struct ref *ref)
{
struct string_list symref = STRING_LIST_INIT_DUP;
const char *feature_list = server_capabilities_v1;
int offset = 0;
while (feature_list) {
while (1) {
int len;
const char *val;
val = parse_feature_value(feature_list, "symref", &len);
val = next_server_feature_value("symref", &len, &offset);
if (!val)
break;
parse_one_symref_info(&symref, val, len);
feature_list = val + 1;
}
string_list_sort(&symref);
@ -205,21 +219,36 @@ static void annotate_refs_with_symref_info(struct ref *ref)
string_list_clear(&symref, 0);
}
static void process_capabilities(const char *line, int *len)
static void process_capabilities(struct packet_reader *reader, int *linelen)
{
const char *feat_val;
int feat_len;
const char *line = reader->line;
int nul_location = strlen(line);
if (nul_location == *len)
if (nul_location == *linelen)
return;
server_capabilities_v1 = xstrdup(line + nul_location + 1);
*len = nul_location;
*linelen = nul_location;
feat_val = server_feature_value("object-format", &feat_len);
if (feat_val) {
char *hash_name = xstrndup(feat_val, feat_len);
int hash_algo = hash_algo_by_name(hash_name);
if (hash_algo != GIT_HASH_UNKNOWN)
reader->hash_algo = &hash_algos[hash_algo];
free(hash_name);
} else {
reader->hash_algo = &hash_algos[GIT_HASH_SHA1];
}
}
static int process_dummy_ref(const char *line)
static int process_dummy_ref(const struct packet_reader *reader)
{
const char *line = reader->line;
struct object_id oid;
const char *name;
if (parse_oid_hex(line, &oid, &name))
if (parse_oid_hex_algop(line, &oid, &name, reader->hash_algo))
return 0;
if (*name != ' ')
return 0;
@ -235,13 +264,15 @@ static void check_no_capabilities(const char *line, int len)
line + strlen(line));
}
static int process_ref(const char *line, int len, struct ref ***list,
unsigned int flags, struct oid_array *extra_have)
static int process_ref(const struct packet_reader *reader, int len,
struct ref ***list, unsigned int flags,
struct oid_array *extra_have)
{
const char *line = reader->line;
struct object_id old_oid;
const char *name;
if (parse_oid_hex(line, &old_oid, &name))
if (parse_oid_hex_algop(line, &old_oid, &name, reader->hash_algo))
return 0;
if (*name != ' ')
return 0;
@ -261,16 +292,17 @@ static int process_ref(const char *line, int len, struct ref ***list,
return 1;
}
static int process_shallow(const char *line, int len,
static int process_shallow(const struct packet_reader *reader, int len,
struct oid_array *shallow_points)
{
const char *line = reader->line;
const char *arg;
struct object_id old_oid;
if (!skip_prefix(line, "shallow ", &arg))
return 0;
if (get_oid_hex(arg, &old_oid))
if (get_oid_hex_algop(arg, &old_oid, reader->hash_algo))
die(_("protocol error: expected shallow sha-1, got '%s'"), arg);
if (!shallow_points)
die(_("repository on the other end cannot be shallow"));
@ -317,20 +349,20 @@ struct ref **get_remote_heads(struct packet_reader *reader,
switch (state) {
case EXPECTING_FIRST_REF:
process_capabilities(reader->line, &len);
if (process_dummy_ref(reader->line)) {
process_capabilities(reader, &len);
if (process_dummy_ref(reader)) {
state = EXPECTING_SHALLOW;
break;
}
state = EXPECTING_REF;
/* fallthrough */
case EXPECTING_REF:
if (process_ref(reader->line, len, &list, flags, extra_have))
if (process_ref(reader, len, &list, flags, extra_have))
break;
state = EXPECTING_SHALLOW;
/* fallthrough */
case EXPECTING_SHALLOW:
if (process_shallow(reader->line, len, shallow_points))
if (process_shallow(reader, len, shallow_points))
break;
die(_("protocol error: unexpected '%s'"), reader->line);
case EXPECTING_DONE:
@ -344,7 +376,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,
}
/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
static int process_ref_v2(const char *line, struct ref ***list)
static int process_ref_v2(struct packet_reader *reader, struct ref ***list)
{
int ret = 1;
int i = 0;
@ -352,6 +384,7 @@ static int process_ref_v2(const char *line, struct ref ***list)
struct ref *ref;
struct string_list line_sections = STRING_LIST_INIT_DUP;
const char *end;
const char *line = reader->line;
/*
* Ref lines have a number of fields which are space deliminated. The
@ -364,7 +397,7 @@ static int process_ref_v2(const char *line, struct ref ***list)
goto out;
}
if (parse_oid_hex(line_sections.items[i++].string, &old_oid, &end) ||
if (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||
*end) {
ret = 0;
goto out;
@ -372,7 +405,7 @@ static int process_ref_v2(const char *line, struct ref ***list)
ref = alloc_ref(line_sections.items[i++].string);
oidcpy(&ref->old_oid, &old_oid);
memcpy(ref->old_oid.hash, old_oid.hash, reader->hash_algo->rawsz);
**list = ref;
*list = &ref->next;
@ -385,7 +418,8 @@ static int process_ref_v2(const char *line, struct ref ***list)
struct object_id peeled_oid;
char *peeled_name;
struct ref *peeled;
if (parse_oid_hex(arg, &peeled_oid, &end) || *end) {
if (parse_oid_hex_algop(arg, &peeled_oid, &end,
reader->hash_algo) || *end) {
ret = 0;
goto out;
}
@ -393,7 +427,8 @@ static int process_ref_v2(const char *line, struct ref ***list)
peeled_name = xstrfmt("%s^{}", ref->name);
peeled = alloc_ref(peeled_name);
oidcpy(&peeled->old_oid, &peeled_oid);
memcpy(peeled->old_oid.hash, peeled_oid.hash,
reader->hash_algo->rawsz);
**list = peeled;
*list = &peeled->next;
@ -423,6 +458,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
int stateless_rpc)
{
int i;
const char *hash_name;
*list = NULL;
if (server_supports_v2("ls-refs", 1))
@ -431,6 +467,16 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
if (server_supports_v2("agent", 0))
packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());
if (server_feature_v2("object-format", &hash_name)) {
int hash_algo = hash_algo_by_name(hash_name);
if (hash_algo == GIT_HASH_UNKNOWN)
die(_("unknown object format '%s' specified by server"), hash_name);
reader->hash_algo = &hash_algos[hash_algo];
packet_write_fmt(fd_out, "object-format=%s", reader->hash_algo->name);
} else {
reader->hash_algo = &hash_algos[GIT_HASH_SHA1];
}
if (server_options && server_options->nr &&
server_supports_v2("server-option", 1))
for (i = 0; i < server_options->nr; i++)
@ -450,7 +496,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
/* Process response from server */
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
if (!process_ref_v2(reader->line, &list))
if (!process_ref_v2(reader, &list))
die(_("invalid ls-refs response: %s"), reader->line);
}
@ -463,7 +509,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
return list;
}
static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp, int *offset)
{
int len;
@ -471,6 +517,8 @@ static const char *parse_feature_value(const char *feature_list, const char *fea
return NULL;
len = strlen(feature);
if (offset)
feature_list += *offset;
while (*feature_list) {
const char *found = strstr(feature_list, feature);
if (!found)
@ -485,9 +533,14 @@ static const char *parse_feature_value(const char *feature_list, const char *fea
}
/* feature with a value (e.g., "agent=git/1.2.3") */
else if (*value == '=') {
int end;
value++;
end = strcspn(value, " \t\n");
if (lenp)
*lenp = strcspn(value, " \t\n");
*lenp = end;
if (offset)
*offset = value + end - feature_list;
return value;
}
/*
@ -500,14 +553,41 @@ static const char *parse_feature_value(const char *feature_list, const char *fea
return NULL;
}
int server_supports_hash(const char *desired, int *feature_supported)
{
int offset = 0;
int len;
const char *hash;
hash = next_server_feature_value("object-format", &len, &offset);
if (feature_supported)
*feature_supported = !!hash;
if (!hash) {
hash = hash_algos[GIT_HASH_SHA1].name;
len = strlen(hash);
}
while (hash) {
if (!xstrncmpz(desired, hash, len))
return 1;
hash = next_server_feature_value("object-format", &len, &offset);
}
return 0;
}
int parse_feature_request(const char *feature_list, const char *feature)
{
return !!parse_feature_value(feature_list, feature, NULL);
return !!parse_feature_value(feature_list, feature, NULL, NULL);
}
static const char *next_server_feature_value(const char *feature, int *len, int *offset)
{
return parse_feature_value(server_capabilities_v1, feature, len, offset);
}
const char *server_feature_value(const char *feature, int *len)
{
return parse_feature_value(server_capabilities_v1, feature, len);
return parse_feature_value(server_capabilities_v1, feature, len, NULL);
}
int server_supports(const char *feature)

View File

@ -18,7 +18,10 @@ int url_is_local_not_ssh(const char *url);
struct packet_reader;
enum protocol_version discover_version(struct packet_reader *reader);
int server_supports_hash(const char *desired, int *feature_supported);
const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp, int *offset);
int server_supports_v2(const char *c, int die_on_error);
int server_feature_v2(const char *c, const char **v);
int server_supports_feature(const char *c, const char *feature,
int die_on_error);

View File

@ -1050,6 +1050,8 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
print_verbose(args, _("Server supports %s"), "deepen-relative");
else if (args->deepen_relative)
die(_("Server does not support --deepen"));
if (!server_supports_hash(the_hash_algo->name, NULL))
die(_("Server does not support this repository's object format"));
if (!args->no_dependents) {
mark_complete_and_common_ref(negotiator, args, &ref);
@ -1188,6 +1190,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
int sideband_all, int seen_ack)
{
int ret = 0;
const char *hash_name;
struct strbuf req_buf = STRBUF_INIT;
if (server_supports_v2("fetch", 1))
@ -1202,6 +1205,17 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
args->server_options->items[i].string);
}
if (server_feature_v2("object-format", &hash_name)) {
int hash_algo = hash_algo_by_name(hash_name);
if (hash_algo_by_ptr(the_hash_algo) != hash_algo)
die(_("mismatched algorithms: client %s; server %s"),
the_hash_algo->name, hash_name);
packet_write_fmt(fd_out, "object-format=%s", the_hash_algo->name);
} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {
die(_("the server does not support algorithm '%s'"),
the_hash_algo->name);
}
packet_buf_delim(&req_buf);
if (args->use_thin_pack)
packet_buf_write(&req_buf, "thin-pack");

View File

@ -868,6 +868,12 @@ char *xgetcwd(void);
FILE *fopen_for_writing(const char *path);
FILE *fopen_or_warn(const char *path, const char *mode);
/*
* Like strncmp, but only return zero if s is NUL-terminated and exactly len
* characters long. If it is not, consider it greater than t.
*/
int xstrncmpz(const char *s, const char *t, size_t len);
/*
* FREE_AND_NULL(ptr) is like free(ptr) followed by ptr = NULL. Note
* that ptr is used twice, so don't pass e.g. ptr++.

2
git.c
View File

@ -574,7 +574,7 @@ static struct cmd_struct commands[] = {
{ "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
{ "show", cmd_show, RUN_SETUP },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show-index", cmd_show_index },
{ "show-index", cmd_show_index, RUN_SETUP_GENTLY },
{ "show-ref", cmd_show_ref, RUN_SETUP },
{ "sparse-checkout", cmd_sparse_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },

View File

@ -70,6 +70,7 @@ struct packed_git {
size_t index_size;
uint32_t num_objects;
uint32_t num_bad_objects;
uint32_t crc_offset;
unsigned char *bad_object_sha1;
int index_version;
time_t mtime;

View File

@ -178,6 +178,7 @@ int load_idx(const char *path, const unsigned int hashsz, void *idx_map,
*/
(sizeof(off_t) <= 4))
return error("pack too large for current definition of off_t in %s", path);
p->crc_offset = 8 + 4 * 256 + nr * hashsz;
}
p->index_version = version;

View File

@ -490,6 +490,7 @@ void packet_reader_init(struct packet_reader *reader, int fd,
reader->buffer_size = sizeof(packet_buffer);
reader->options = options;
reader->me = "git";
reader->hash_algo = &hash_algos[GIT_HASH_SHA1];
}
enum packet_read_status packet_reader_read(struct packet_reader *reader)

View File

@ -177,6 +177,9 @@ struct packet_reader {
unsigned use_sideband : 1;
const char *me;
/* hash algorithm in use */
const struct git_hash_algo *hash_algo;
};
/*

View File

@ -41,7 +41,9 @@ struct options {
deepen_relative : 1,
from_promisor : 1,
no_dependents : 1,
atomic : 1;
atomic : 1,
object_format : 1;
const struct git_hash_algo *hash_algo;
};
static struct options options;
static struct string_list cas_options = STRING_LIST_INIT_DUP;
@ -190,6 +192,16 @@ static int set_option(const char *name, const char *value)
} else if (!strcmp(name, "filter")) {
options.filter = xstrdup(value);
return 0;
} else if (!strcmp(name, "object-format")) {
int algo;
options.object_format = 1;
if (strcmp(value, "true")) {
algo = hash_algo_by_name(value);
if (algo == GIT_HASH_UNKNOWN)
die("unknown object format '%s'", value);
options.hash_algo = &hash_algos[algo];
}
return 0;
} else {
return 1 /* unsupported */;
}
@ -231,6 +243,7 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push)
case protocol_v0:
get_remote_heads(&reader, &list, for_push ? REF_NORMAL : 0,
NULL, &heads->shallow);
options.hash_algo = reader.hash_algo;
break;
case protocol_unknown_version:
BUG("unknown protocol version");
@ -239,6 +252,19 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push)
return list;
}
static const struct git_hash_algo *detect_hash_algo(struct discovery *heads)
{
const char *p = memchr(heads->buf, '\t', heads->len);
int algo;
if (!p)
return the_hash_algo;
algo = hash_algo_by_length((p - heads->buf) / 2);
if (algo == GIT_HASH_UNKNOWN)
return NULL;
return &hash_algos[algo];
}
static struct ref *parse_info_refs(struct discovery *heads)
{
char *data, *start, *mid;
@ -249,6 +275,12 @@ static struct ref *parse_info_refs(struct discovery *heads)
struct ref *ref = NULL;
struct ref *last_ref = NULL;
options.hash_algo = detect_hash_algo(heads);
if (!options.hash_algo)
die("%sinfo/refs not valid: could not determine hash algorithm; "
"is this a git repository?",
transport_anonymize_url(url.buf));
data = heads->buf;
start = NULL;
mid = data;
@ -259,13 +291,13 @@ static struct ref *parse_info_refs(struct discovery *heads)
if (data[i] == '\t')
mid = &data[i];
if (data[i] == '\n') {
if (mid - start != the_hash_algo->hexsz)
if (mid - start != options.hash_algo->hexsz)
die(_("%sinfo/refs not valid: is this a git repository?"),
transport_anonymize_url(url.buf));
data[i] = 0;
ref_name = mid + 1;
ref = alloc_ref(ref_name);
get_oid_hex(start, &ref->old_oid);
get_oid_hex_algop(start, &ref->old_oid, options.hash_algo);
if (!refs)
refs = ref;
if (last_ref)
@ -509,11 +541,16 @@ static struct ref *get_refs(int for_push)
static void output_refs(struct ref *refs)
{
struct ref *posn;
if (options.object_format && options.hash_algo) {
printf(":object-format %s\n", options.hash_algo->name);
}
for (posn = refs; posn; posn = posn->next) {
if (posn->symref)
printf("@%s %s\n", posn->symref, posn->name);
else
printf("%s %s\n", oid_to_hex(&posn->old_oid), posn->name);
printf("%s %s\n", hash_to_hex_algop(posn->old_oid.hash,
options.hash_algo),
posn->name);
}
printf("\n");
fflush(stdout);
@ -1499,6 +1536,7 @@ int cmd_main(int argc, const char **argv)
printf("option\n");
printf("push\n");
printf("check-connectivity\n");
printf("object-format\n");
printf("\n");
fflush(stdout);
} else if (skip_prefix(buf.buf, "stateless-connect ", &arg)) {

View File

@ -363,6 +363,7 @@ int send_pack(struct send_pack_args *args,
int atomic_supported = 0;
int use_push_options = 0;
int push_options_supported = 0;
int object_format_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
@ -389,6 +390,9 @@ int send_pack(struct send_pack_args *args,
if (server_supports("push-options"))
push_options_supported = 1;
if (!server_supports_hash(the_hash_algo->name, &object_format_supported))
die(_("the receiving end does not support this repository's hash algorithm"));
if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
int len;
push_cert_nonce = server_feature_value("push-cert", &len);
@ -429,6 +433,8 @@ int send_pack(struct send_pack_args *args,
strbuf_addstr(&cap_buf, " atomic");
if (use_push_options)
strbuf_addstr(&cap_buf, " push-options");
if (object_format_supported)
strbuf_addf(&cap_buf, " object-format=%s", the_hash_algo->name);
if (agent_supported)
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());

27
serve.c
View File

@ -22,6 +22,14 @@ static int agent_advertise(struct repository *r,
return 1;
}
static int object_format_advertise(struct repository *r,
struct strbuf *value)
{
if (value)
strbuf_addstr(value, r->hash_algo->name);
return 1;
}
struct protocol_capability {
/*
* The name of the capability. The server uses this name when
@ -57,6 +65,7 @@ static struct protocol_capability capabilities[] = {
{ "ls-refs", always_advertise, ls_refs },
{ "fetch", upload_pack_advertise, upload_pack_v2 },
{ "server-option", always_advertise, NULL },
{ "object-format", object_format_advertise, NULL },
};
static void advertise_capabilities(void)
@ -153,6 +162,22 @@ int has_capability(const struct argv_array *keys, const char *capability,
return 0;
}
static void check_algorithm(struct repository *r, struct argv_array *keys)
{
int client = GIT_HASH_SHA1, server = hash_algo_by_ptr(r->hash_algo);
const char *algo_name;
if (has_capability(keys, "object-format", &algo_name)) {
client = hash_algo_by_name(algo_name);
if (client == GIT_HASH_UNKNOWN)
die("unknown object format '%s'", algo_name);
}
if (client != server)
die("mismatched object format: server %s; client %s\n",
r->hash_algo->name, hash_algos[client].name);
}
enum request_state {
PROCESS_REQUEST_KEYS,
PROCESS_REQUEST_DONE,
@ -225,6 +250,8 @@ static int process_request(void)
if (!command)
die("no command requested");
check_algorithm(the_repository, &keys);
command->command(the_repository, &keys, &reader);
argv_array_clear(&keys);

View File

@ -1308,6 +1308,7 @@ void check_repository_format(struct repository_format *fmt)
fmt = &repo_fmt;
check_repository_format_gently(get_git_dir(), fmt, NULL);
startup_info->have_repository = 1;
repo_set_hash_algo(the_repository, fmt->hash_algo);
clear_repository_format(&repo_fmt);
}

View File

@ -12,6 +12,9 @@ int cmd__oid_array(int argc, const char **argv)
{
struct oid_array array = OID_ARRAY_INIT;
struct strbuf line = STRBUF_INIT;
int nongit_ok;
setup_git_directory_gently(&nongit_ok);
while (strbuf_getline(&line, stdin) != EOF) {
const char *arg;

View File

@ -12,6 +12,7 @@ file_size () {
}
test_expect_success setup '
test_oid_init &&
# clone does not allow us to pass core.bigfilethreshold to
# new repos, so set core.bigfilethreshold globally
git config --global core.bigfilethreshold 200k &&
@ -64,7 +65,7 @@ test_expect_success 'add a large file or two' '
test $count = 1 &&
cnt=$(git show-index <"$idx" | wc -l) &&
test $cnt = 2 &&
for l in .git/objects/??/??????????????????????????????????????
for l in .git/objects/$OIDPATH_REGEX
do
test_path_is_file "$l" || continue
bad=t
@ -177,7 +178,8 @@ test_expect_success 'git-show a large file' '
test_expect_success 'index-pack' '
git clone file://"$(pwd)"/.git foo &&
GIT_DIR=non-existent git index-pack --strict --verify foo/.git/objects/pack/*.pack
GIT_DIR=non-existent git index-pack --object-format=$(test_oid algo) \
--strict --verify foo/.git/objects/pack/*.pack
'
test_expect_success 'repack' '

View File

@ -8,6 +8,10 @@ test_description='Test repository version check'
. ./test-lib.sh
test_expect_success 'setup' '
test_oid_cache <<-\EOF &&
version sha1:0
version sha256:1
EOF
cat >test.patch <<-\EOF &&
diff --git a/test.txt b/test.txt
new file mode 100644
@ -23,7 +27,7 @@ test_expect_success 'setup' '
'
test_expect_success 'gitdir selection on normal repos' '
echo 0 >expect &&
echo $(test_oid version) >expect &&
git config core.repositoryformatversion >actual &&
git -C test config core.repositoryformatversion >actual2 &&
test_cmp expect actual &&

View File

@ -402,7 +402,7 @@ EOF
mv .git/config .git/config-saved
test_expect_success 'git branch -m q q2 without config should succeed' '
test_expect_success SHA1 'git branch -m q q2 without config should succeed' '
git branch -m q q2 &&
git branch -m q2 q
'

View File

@ -12,7 +12,8 @@ TRASH=$(pwd)
test_expect_success \
'setup' \
'rm -f .git/index* &&
'test_oid_init &&
rm -f .git/index* &&
perl -e "print \"a\" x 4096;" > a &&
perl -e "print \"b\" x 4096;" > b &&
perl -e "print \"c\" x 4096;" > c &&
@ -412,18 +413,18 @@ test_expect_success 'set up pack for non-repo tests' '
'
test_expect_success 'index-pack --stdin complains of non-repo' '
nongit test_must_fail git index-pack --stdin <foo.pack &&
nongit test_must_fail git index-pack --object-format=$(test_oid algo) --stdin <foo.pack &&
test_path_is_missing non-repo/.git
'
test_expect_success 'index-pack <pack> works in non-repo' '
nongit git index-pack ../foo.pack &&
nongit git index-pack --object-format=$(test_oid algo) ../foo.pack &&
test_path_is_file foo.idx
'
test_expect_success 'index-pack --strict <pack> works in non-repo' '
rm -f foo.idx &&
nongit git index-pack --strict ../foo.pack &&
nongit git index-pack --strict --object-format=$(test_oid algo) ../foo.pack &&
test_path_is_file foo.idx
'

View File

@ -7,65 +7,65 @@ test_description='pack index with 64-bit offsets and object CRC'
. ./test-lib.sh
test_expect_success 'setup' '
test_oid_init &&
rawsz=$(test_oid rawsz) &&
rm -rf .git &&
git init &&
git config pack.threads 1 &&
i=1 &&
while test $i -le 100
do
iii=$(printf '%03i' $i)
test-tool genrandom "bar" 200 > wide_delta_$iii &&
test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
echo $iii >file_$iii &&
test-tool genrandom "$iii" 8192 >>file_$iii &&
git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
i=$(expr $i + 1) || return 1
done &&
{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
git update-index --add file_101 &&
tree=$(git write-tree) &&
commit=$(git commit-tree $tree </dev/null) && {
echo $tree &&
git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
} >obj-list &&
git update-ref HEAD $commit
test_oid_init &&
rawsz=$(test_oid rawsz) &&
rm -rf .git &&
git init &&
git config pack.threads 1 &&
i=1 &&
while test $i -le 100
do
iii=$(printf '%03i' $i)
test-tool genrandom "bar" 200 > wide_delta_$iii &&
test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
echo $iii >file_$iii &&
test-tool genrandom "$iii" 8192 >>file_$iii &&
git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
i=$(expr $i + 1) || return 1
done &&
{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
git update-index --add file_101 &&
tree=$(git write-tree) &&
commit=$(git commit-tree $tree </dev/null) && {
echo $tree &&
git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
} >obj-list &&
git update-ref HEAD $commit
'
test_expect_success \
'pack-objects with index version 1' \
'pack1=$(git pack-objects --index-version=1 test-1 <obj-list) &&
git verify-pack -v "test-1-${pack1}.pack"'
test_expect_success 'pack-objects with index version 1' '
pack1=$(git pack-objects --index-version=1 test-1 <obj-list) &&
git verify-pack -v "test-1-${pack1}.pack"
'
test_expect_success \
'pack-objects with index version 2' \
'pack2=$(git pack-objects --index-version=2 test-2 <obj-list) &&
git verify-pack -v "test-2-${pack2}.pack"'
test_expect_success 'pack-objects with index version 2' '
pack2=$(git pack-objects --index-version=2 test-2 <obj-list) &&
git verify-pack -v "test-2-${pack2}.pack"
'
test_expect_success \
'both packs should be identical' \
'cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"'
test_expect_success 'both packs should be identical' '
cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"
'
test_expect_success \
'index v1 and index v2 should be different' \
'! cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"'
test_expect_success 'index v1 and index v2 should be different' '
! cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"
'
test_expect_success \
'index-pack with index version 1' \
'git index-pack --index-version=1 -o 1.idx "test-1-${pack1}.pack"'
test_expect_success 'index-pack with index version 1' '
git index-pack --index-version=1 -o 1.idx "test-1-${pack1}.pack"
'
test_expect_success \
'index-pack with index version 2' \
'git index-pack --index-version=2 -o 2.idx "test-1-${pack1}.pack"'
test_expect_success 'index-pack with index version 2' '
git index-pack --index-version=2 -o 2.idx "test-1-${pack1}.pack"
'
test_expect_success \
'index-pack results should match pack-objects ones' \
'cmp "test-1-${pack1}.idx" "1.idx" &&
cmp "test-2-${pack2}.idx" "2.idx"'
test_expect_success 'index-pack results should match pack-objects ones' '
cmp "test-1-${pack1}.idx" "1.idx" &&
cmp "test-2-${pack2}.idx" "2.idx"
'
test_expect_success 'index-pack --verify on index version 1' '
git index-pack --verify "test-1-${pack1}.pack"
@ -75,13 +75,13 @@ test_expect_success 'index-pack --verify on index version 2' '
git index-pack --verify "test-2-${pack2}.pack"
'
test_expect_success \
'pack-objects --index-version=2, is not accepted' \
'test_must_fail git pack-objects --index-version=2, test-3 <obj-list'
test_expect_success 'pack-objects --index-version=2, is not accepted' '
test_must_fail git pack-objects --index-version=2, test-3 <obj-list
'
test_expect_success \
'index v2: force some 64-bit offsets with pack-objects' \
'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)'
test_expect_success 'index v2: force some 64-bit offsets with pack-objects' '
pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)
'
if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) ||
! (echo "$msg" | grep "pack too large .* off_t")
@ -91,21 +91,21 @@ else
say "# skipping tests concerning 64-bit offsets"
fi
test_expect_success OFF64_T \
'index v2: verify a pack with some 64-bit offsets' \
'git verify-pack -v "test-3-${pack3}.pack"'
test_expect_success OFF64_T 'index v2: verify a pack with some 64-bit offsets' '
git verify-pack -v "test-3-${pack3}.pack"
'
test_expect_success OFF64_T \
'64-bit offsets: should be different from previous index v2 results' \
'! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"'
test_expect_success OFF64_T '64-bit offsets: should be different from previous index v2 results' '
! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"
'
test_expect_success OFF64_T \
'index v2: force some 64-bit offsets with index-pack' \
'git index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"'
test_expect_success OFF64_T 'index v2: force some 64-bit offsets with index-pack' '
git index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"
'
test_expect_success OFF64_T \
'64-bit offsets: index-pack result should match pack-objects one' \
'cmp "test-3-${pack3}.idx" "3.idx"'
test_expect_success OFF64_T '64-bit offsets: index-pack result should match pack-objects one' '
cmp "test-3-${pack3}.idx" "3.idx"
'
test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2 (cheat)' '
# This cheats by knowing which lower offset should still be encoded
@ -120,135 +120,143 @@ test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2' '
# returns the object number for given object in given pack index
index_obj_nr()
{
idx_file=$1
object_sha1=$2
nr=0
git show-index < $idx_file |
while read offs sha1 extra
do
nr=$(($nr + 1))
test "$sha1" = "$object_sha1" || continue
echo "$(($nr - 1))"
break
done
idx_file=$1
object_sha1=$2
nr=0
git show-index < $idx_file |
while read offs sha1 extra
do
nr=$(($nr + 1))
test "$sha1" = "$object_sha1" || continue
echo "$(($nr - 1))"
break
done
}
# returns the pack offset for given object as found in given pack index
index_obj_offset()
{
idx_file=$1
object_sha1=$2
git show-index < $idx_file | grep $object_sha1 |
( read offs extra && echo "$offs" )
idx_file=$1
object_sha1=$2
git show-index < $idx_file | grep $object_sha1 |
( read offs extra && echo "$offs" )
}
test_expect_success \
'[index v1] 1) stream pack to repository' \
'git index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" &&
git prune-packed &&
git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
cmp "test-1-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"'
test_expect_success '[index v1] 1) stream pack to repository' '
git index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" &&
git prune-packed &&
git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
cmp "test-1-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"
'
test_expect_success \
'[index v1] 2) create a stealth corruption in a delta base reference' \
'# This test assumes file_101 is a delta smaller than 16 bytes.
# It should be against file_100 but we substitute its base for file_099
sha1_101=$(git hash-object file_101) &&
sha1_099=$(git hash-object file_099) &&
offs_101=$(index_obj_offset 1.idx $sha1_101) &&
nr_099=$(index_obj_nr 1.idx $sha1_099) &&
chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
recordsz=$((rawsz + 4)) &&
dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
if=".git/objects/pack/pack-${pack1}.idx" \
skip=$((4 + 256 * 4 + $nr_099 * recordsz)) \
bs=1 count=$rawsz conv=notrunc &&
git cat-file blob $sha1_101 > file_101_foo1'
'[index v1] 2) create a stealth corruption in a delta base reference' '
# This test assumes file_101 is a delta smaller than 16 bytes.
# It should be against file_100 but we substitute its base for file_099
sha1_101=$(git hash-object file_101) &&
sha1_099=$(git hash-object file_099) &&
offs_101=$(index_obj_offset 1.idx $sha1_101) &&
nr_099=$(index_obj_nr 1.idx $sha1_099) &&
chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
recordsz=$((rawsz + 4)) &&
dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
if=".git/objects/pack/pack-${pack1}.idx" \
skip=$((4 + 256 * 4 + $nr_099 * recordsz)) \
bs=1 count=$rawsz conv=notrunc &&
git cat-file blob $sha1_101 > file_101_foo1
'
test_expect_success \
'[index v1] 3) corrupted delta happily returned wrong data' \
'test -f file_101_foo1 && ! cmp file_101 file_101_foo1'
'[index v1] 3) corrupted delta happily returned wrong data' '
test -f file_101_foo1 && ! cmp file_101 file_101_foo1
'
test_expect_success \
'[index v1] 4) confirm that the pack is actually corrupted' \
'test_must_fail git fsck --full $commit'
'[index v1] 4) confirm that the pack is actually corrupted' '
test_must_fail git fsck --full $commit
'
test_expect_success \
'[index v1] 5) pack-objects happily reuses corrupted data' \
'pack4=$(git pack-objects test-4 <obj-list) &&
test -f "test-4-${pack4}.pack"'
'[index v1] 5) pack-objects happily reuses corrupted data' '
pack4=$(git pack-objects test-4 <obj-list) &&
test -f "test-4-${pack4}.pack"
'
test_expect_success '[index v1] 6) newly created pack is BAD !' '
test_must_fail git verify-pack -v "test-4-${pack4}.pack"
'
test_expect_success '[index v2] 1) stream pack to repository' '
rm -f .git/objects/pack/* &&
git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
git prune-packed &&
git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
cmp "test-2-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"
'
test_expect_success \
'[index v1] 6) newly created pack is BAD !' \
'test_must_fail git verify-pack -v "test-4-${pack4}.pack"'
'[index v2] 2) create a stealth corruption in a delta base reference' '
# This test assumes file_101 is a delta smaller than 16 bytes.
# It should be against file_100 but we substitute its base for file_099
sha1_101=$(git hash-object file_101) &&
sha1_099=$(git hash-object file_099) &&
offs_101=$(index_obj_offset 1.idx $sha1_101) &&
nr_099=$(index_obj_nr 1.idx $sha1_099) &&
chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
if=".git/objects/pack/pack-${pack1}.idx" \
skip=$((8 + 256 * 4 + $nr_099 * rawsz)) \
bs=1 count=$rawsz conv=notrunc &&
git cat-file blob $sha1_101 > file_101_foo2
'
test_expect_success \
'[index v2] 1) stream pack to repository' \
'rm -f .git/objects/pack/* &&
git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
git prune-packed &&
git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
cmp "test-2-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"'
'[index v2] 3) corrupted delta happily returned wrong data' '
test -f file_101_foo2 && ! cmp file_101 file_101_foo2
'
test_expect_success \
'[index v2] 2) create a stealth corruption in a delta base reference' \
'# This test assumes file_101 is a delta smaller than 16 bytes.
# It should be against file_100 but we substitute its base for file_099
sha1_101=$(git hash-object file_101) &&
sha1_099=$(git hash-object file_099) &&
offs_101=$(index_obj_offset 1.idx $sha1_101) &&
nr_099=$(index_obj_nr 1.idx $sha1_099) &&
chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \
if=".git/objects/pack/pack-${pack1}.idx" \
skip=$((8 + 256 * 4 + $nr_099 * rawsz)) \
bs=1 count=$rawsz conv=notrunc &&
git cat-file blob $sha1_101 > file_101_foo2'
'[index v2] 4) confirm that the pack is actually corrupted' '
test_must_fail git fsck --full $commit
'
test_expect_success \
'[index v2] 3) corrupted delta happily returned wrong data' \
'test -f file_101_foo2 && ! cmp file_101 file_101_foo2'
'[index v2] 5) pack-objects refuses to reuse corrupted data' '
test_must_fail git pack-objects test-5 <obj-list &&
test_must_fail git pack-objects --no-reuse-object test-6 <obj-list
'
test_expect_success \
'[index v2] 4) confirm that the pack is actually corrupted' \
'test_must_fail git fsck --full $commit'
test_expect_success \
'[index v2] 5) pack-objects refuses to reuse corrupted data' \
'test_must_fail git pack-objects test-5 <obj-list &&
test_must_fail git pack-objects --no-reuse-object test-6 <obj-list'
test_expect_success \
'[index v2] 6) verify-pack detects CRC mismatch' \
'rm -f .git/objects/pack/* &&
git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
git verify-pack ".git/objects/pack/pack-${pack1}.pack" &&
obj=$(git hash-object file_001) &&
nr=$(index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj) &&
chmod +w ".git/objects/pack/pack-${pack1}.idx" &&
printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \
bs=1 count=4 seek=$((8 + 256 * 4 + $(wc -l <obj-list) * rawsz + $nr * 4)) &&
( while read obj
do git cat-file -p $obj >/dev/null || exit 1
done <obj-list ) &&
test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack"
'[index v2] 6) verify-pack detects CRC mismatch' '
rm -f .git/objects/pack/* &&
git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
git verify-pack ".git/objects/pack/pack-${pack1}.pack" &&
obj=$(git hash-object file_001) &&
nr=$(index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj) &&
chmod +w ".git/objects/pack/pack-${pack1}.idx" &&
printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \
bs=1 count=4 seek=$((8 + 256 * 4 + $(wc -l <obj-list) * rawsz + $nr * 4)) &&
( while read obj
do git cat-file -p $obj >/dev/null || exit 1
done <obj-list ) &&
test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack"
'
test_expect_success 'running index-pack in the object store' '
rm -f .git/objects/pack/* &&
cp test-1-${pack1}.pack .git/objects/pack/pack-${pack1}.pack &&
(
cd .git/objects/pack &&
git index-pack pack-${pack1}.pack
) &&
test -f .git/objects/pack/pack-${pack1}.idx
rm -f .git/objects/pack/* &&
cp test-1-${pack1}.pack .git/objects/pack/pack-${pack1}.pack &&
(
cd .git/objects/pack &&
git index-pack pack-${pack1}.pack
) &&
test -f .git/objects/pack/pack-${pack1}.idx
'
test_expect_success 'index-pack --strict warns upon missing tagger in tag' '
sha=$(git rev-parse HEAD) &&
cat >wrong-tag <<EOF &&
sha=$(git rev-parse HEAD) &&
cat >wrong-tag <<EOF &&
object $sha
type commit
tag guten tag
@ -256,18 +264,18 @@ tag guten tag
This is an invalid tag.
EOF
tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
pack1=$(echo $tag $sha | git pack-objects tag-test) &&
echo remove tag object &&
thirtyeight=${tag#??} &&
rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight &&
git index-pack --strict tag-test-${pack1}.pack 2>err &&
grep "^warning:.* expected .tagger. line" err
tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
pack1=$(echo $tag $sha | git pack-objects tag-test) &&
echo remove tag object &&
thirtyeight=${tag#??} &&
rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight &&
git index-pack --strict tag-test-${pack1}.pack 2>err &&
grep "^warning:.* expected .tagger. line" err
'
test_expect_success 'index-pack --fsck-objects also warns upon missing tagger in tag' '
git index-pack --fsck-objects tag-test-${pack1}.pack 2>err &&
grep "^warning:.* expected .tagger. line" err
git index-pack --fsck-objects tag-test-${pack1}.pack 2>err &&
grep "^warning:.* expected .tagger. line" err
'
test_done

View File

@ -871,9 +871,10 @@ test_expect_success 'shallow since with commit graph and already-seen commit' '
GIT_PROTOCOL=version=2 git upload-pack . <<-EOF >/dev/null
0012command=fetch
$(echo "object-format=$(test_oid algo)" | packetize)
00010013deepen-since 1
0032want $(git rev-parse other)
0032have $(git rev-parse master)
$(echo "want $(git rev-parse other)" | packetize)
$(echo "have $(git rev-parse master)" | packetize)
0000
EOF
)

View File

@ -50,6 +50,24 @@ test_expect_success 'create password-protected repository' '
"$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git"
'
test_expect_success 'create empty remote repository' '
git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" &&
mkdir -p hooks &&
write_script "hooks/post-update" <<-\EOF &&
exec git update-server-info
EOF
hooks/post-update
)
'
test_expect_success 'empty dumb HTTP repository has default hash algorithm' '
test_when_finished "rm -fr clone-empty" &&
git clone $HTTPD_URL/dumb/empty.git clone-empty &&
git -C clone-empty rev-parse --show-object-format >empty-format &&
test "$(cat empty-format)" = "$(test_oid algo)"
'
setup_askpass_helper
test_expect_success 'cloning password-protected repository can fail' '

View File

@ -46,6 +46,7 @@ ssize_b100dots() {
}
test_expect_success 'setup' '
test_oid_init &&
HTTP_CONTENT_ENCODING="identity" &&
export HTTP_CONTENT_ENCODING &&
git config http.receivepack true &&
@ -62,8 +63,8 @@ test_expect_success 'setup' '
test_copy_bytes 10 <fetch_body >fetch_body.trunc &&
hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) &&
{
printf "%s %s refs/heads/newbranch\\0report-status\\n" \
"$ZERO_OID" "$hash_next" | packetize &&
printf "%s %s refs/heads/newbranch\\0report-status object-format=%s\\n" \
"$ZERO_OID" "$hash_next" "$(test_oid algo)" | packetize &&
printf 0000 &&
echo "$hash_next" | git pack-objects --stdout
} >push_body &&

View File

@ -5,12 +5,17 @@ test_description='test protocol v2 server commands'
. ./test-lib.sh
test_expect_success 'test capability advertisement' '
test_oid_cache <<-EOF &&
wrong_algo sha1:sha256
wrong_algo sha256:sha1
EOF
cat >expect <<-EOF &&
version 2
agent=git/$(git version | cut -d" " -f3)
ls-refs
fetch=shallow
server-option
object-format=$(test_oid algo)
0000
EOF
@ -45,6 +50,7 @@ test_expect_success 'request invalid capability' '
test_expect_success 'request with no command' '
test-tool pkt-line pack >in <<-EOF &&
agent=git/test
object-format=$(test_oid algo)
0000
EOF
test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
@ -54,6 +60,7 @@ test_expect_success 'request with no command' '
test_expect_success 'request invalid command' '
test-tool pkt-line pack >in <<-EOF &&
command=foo
object-format=$(test_oid algo)
agent=git/test
0000
EOF
@ -61,6 +68,17 @@ test_expect_success 'request invalid command' '
test_i18ngrep "invalid command" err
'
test_expect_success 'wrong object-format' '
test-tool pkt-line pack >in <<-EOF &&
command=fetch
agent=git/test
object-format=$(test_oid wrong_algo)
0000
EOF
test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
test_i18ngrep "mismatched object format" err
'
# Test the basics of ls-refs
#
test_expect_success 'setup some refs and tags' '
@ -74,6 +92,7 @@ test_expect_success 'setup some refs and tags' '
test_expect_success 'basics of ls-refs' '
test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
object-format=$(test_oid algo)
0000
EOF
@ -96,6 +115,7 @@ test_expect_success 'basics of ls-refs' '
test_expect_success 'basic ref-prefixes' '
test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
object-format=$(test_oid algo)
0001
ref-prefix refs/heads/master
ref-prefix refs/tags/one
@ -116,6 +136,7 @@ test_expect_success 'basic ref-prefixes' '
test_expect_success 'refs/heads prefix' '
test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
object-format=$(test_oid algo)
0001
ref-prefix refs/heads/
0000
@ -136,6 +157,7 @@ test_expect_success 'refs/heads prefix' '
test_expect_success 'peel parameter' '
test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
object-format=$(test_oid algo)
0001
peel
ref-prefix refs/tags/
@ -157,6 +179,7 @@ test_expect_success 'peel parameter' '
test_expect_success 'symrefs parameter' '
test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
object-format=$(test_oid algo)
0001
symrefs
ref-prefix refs/heads/
@ -178,6 +201,7 @@ test_expect_success 'symrefs parameter' '
test_expect_success 'sending server-options' '
test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
object-format=$(test_oid algo)
server-option=hello
server-option=world
0001
@ -200,6 +224,7 @@ test_expect_success 'unexpected lines are not allowed in fetch request' '
test-tool pkt-line pack >in <<-EOF &&
command=fetch
object-format=$(test_oid algo)
0001
this-is-not-a-command
0000

View File

@ -13,6 +13,7 @@ start_git_daemon --export-all --enable=receive-pack
daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
test_expect_success 'create repo to be served by git-daemon' '
test_oid_init &&
git init "$daemon_parent" &&
test_commit -C "$daemon_parent" one
'
@ -393,6 +394,7 @@ test_expect_success 'even with handcrafted request, filter does not work if not
# Custom request that tries to filter even though it is not advertised.
test-tool pkt-line pack >in <<-EOF &&
command=fetch
object-format=$(test_oid algo)
0001
want $(git -C server rev-parse master)
filter blob:none

View File

@ -27,6 +27,15 @@ check_output () {
test_cmp sorted_commits actual_commits
}
write_command () {
echo "command=$1"
if test "$(test_oid algo)" != sha1
then
echo "object-format=$(test_oid algo)"
fi
}
# c(o/foo) d(o/bar)
# \ /
# b e(baz) f(master)
@ -65,7 +74,7 @@ test_expect_success 'config controls ref-in-want advertisement' '
test_expect_success 'invalid want-ref line' '
test-tool pkt-line pack >in <<-EOF &&
command=fetch
$(write_command fetch)
0001
no-progress
want-ref refs/heads/non-existent
@ -86,7 +95,7 @@ test_expect_success 'basic want-ref' '
oid=$(git rev-parse a) &&
test-tool pkt-line pack >in <<-EOF &&
command=fetch
$(write_command fetch)
0001
no-progress
want-ref refs/heads/master
@ -110,7 +119,7 @@ test_expect_success 'multiple want-ref lines' '
oid=$(git rev-parse b) &&
test-tool pkt-line pack >in <<-EOF &&
command=fetch
$(write_command fetch)
0001
no-progress
want-ref refs/heads/o/foo
@ -132,7 +141,7 @@ test_expect_success 'mix want and want-ref' '
git rev-parse e f >expected_commits &&
test-tool pkt-line pack >in <<-EOF &&
command=fetch
$(write_command fetch)
0001
no-progress
want-ref refs/heads/master
@ -155,7 +164,7 @@ test_expect_success 'want-ref with ref we already have commit for' '
oid=$(git rev-parse c) &&
test-tool pkt-line pack >in <<-EOF &&
command=fetch
$(write_command fetch)
0001
no-progress
want-ref refs/heads/o/foo

View File

@ -9,6 +9,7 @@ making sure that we do not segfault or otherwise behave badly.'
test_expect_success 'extra delim packet in v2 ls-refs args' '
{
packetize command=ls-refs &&
packetize "object-format=$(test_oid algo)" &&
printf 0001 &&
# protocol expects 0000 flush here
printf 0001
@ -21,6 +22,7 @@ test_expect_success 'extra delim packet in v2 ls-refs args' '
test_expect_success 'extra delim packet in v2 fetch args' '
{
packetize command=fetch &&
packetize "object-format=$(test_oid algo)" &&
printf 0001 &&
# protocol expects 0000 flush here
printf 0001

View File

@ -52,9 +52,11 @@ do
test -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags"
test -n "$GIT_REMOTE_TESTGIT_NO_PRIVATE_UPDATE" && echo "no-private-update"
echo 'option'
echo 'object-format'
echo
;;
list)
echo ":object-format $(git rev-parse --show-object-format=storage)"
git for-each-ref --format='? %(refname)' 'refs/heads/' 'refs/tags/'
head=$(git symbolic-ref HEAD)
echo "@$head HEAD"
@ -139,6 +141,10 @@ do
test $val = "true" && force="true" || force=
echo "ok"
;;
object-format)
test $val = "true" && object_format="true" || object_format=
echo "ok"
;;
*)
echo "unsupported"
;;

View File

@ -1414,6 +1414,7 @@ test_oid_init
ZERO_OID=$(test_oid zero)
OID_REGEX=$(echo $ZERO_OID | sed -e 's/0/[0-9a-f]/g')
OIDPATH_REGEX=$(test_oid_to_path $ZERO_OID | sed -e 's/0/[0-9a-f]/g')
EMPTY_TREE=$(test_oid empty_tree)
EMPTY_BLOB=$(test_oid empty_blob)
_z40=$ZERO_OID

View File

@ -32,7 +32,8 @@ struct helper_data {
signed_tags : 1,
check_connectivity : 1,
no_disconnect_req : 1,
no_private_update : 1;
no_private_update : 1,
object_format : 1;
/*
* As an optimization, the transport code may invoke fetch before
@ -207,6 +208,8 @@ static struct child_process *get_helper(struct transport *transport)
data->import_marks = xstrdup(arg);
} else if (starts_with(capname, "no-private-update")) {
data->no_private_update = 1;
} else if (starts_with(capname, "object-format")) {
data->object_format = 1;
} else if (mandatory) {
die(_("unknown mandatory capability %s; this remote "
"helper probably needs newer version of Git"),
@ -1104,6 +1107,12 @@ static struct ref *get_refs_list_using_list(struct transport *transport,
data->get_refs_list_called = 1;
helper = get_helper(transport);
if (data->object_format) {
write_str_in_full(helper->in, "option object-format\n");
if (recvline(data, &buf) || strcmp(buf.buf, "ok"))
exit(128);
}
if (data->push && for_push)
write_str_in_full(helper->in, "list for-push\n");
else
@ -1116,6 +1125,17 @@ static struct ref *get_refs_list_using_list(struct transport *transport,
if (!*buf.buf)
break;
else if (buf.buf[0] == ':') {
const char *value;
if (skip_prefix(buf.buf, ":object-format ", &value)) {
int algo = hash_algo_by_name(value);
if (algo == GIT_HASH_UNKNOWN)
die(_("unsupported object format '%s'"),
value);
transport->hash_algo = &hash_algos[algo];
}
continue;
}
eov = strchr(buf.buf, ' ');
if (!eov)
@ -1128,7 +1148,7 @@ static struct ref *get_refs_list_using_list(struct transport *transport,
if (buf.buf[0] == '@')
(*tail)->symref = xstrdup(buf.buf + 1);
else if (buf.buf[0] != '?')
get_oid_hex(buf.buf, &(*tail)->old_oid);
get_oid_hex_algop(buf.buf, &(*tail)->old_oid, transport->hash_algo);
if (eon) {
if (has_attribute(eon + 1, "unchanged")) {
(*tail)->status |= REF_STATUS_UPTODATE;

View File

@ -143,6 +143,9 @@ static struct ref *get_refs_from_bundle(struct transport *transport,
data->fd = read_bundle_header(transport->url, &data->header);
if (data->fd < 0)
die(_("could not read bundle '%s'"), transport->url);
transport->hash_algo = data->header.hash_algo;
for (i = 0; i < data->header.references.nr; i++) {
struct ref_list_entry *e = data->header.references.list + i;
struct ref *ref = alloc_ref(e->name);
@ -157,11 +160,14 @@ static int fetch_refs_from_bundle(struct transport *transport,
int nr_heads, struct ref **to_fetch)
{
struct bundle_transport_data *data = transport->data;
int ret;
if (!data->get_refs_from_bundle_called)
get_refs_from_bundle(transport, 0, NULL);
return unbundle(the_repository, &data->header, data->fd,
transport->progress ? BUNDLE_VERBOSE : 0);
ret = unbundle(the_repository, &data->header, data->fd,
transport->progress ? BUNDLE_VERBOSE : 0);
transport->hash_algo = data->header.hash_algo;
return ret;
}
static int close_bundle(struct transport *transport)
@ -312,6 +318,7 @@ static struct ref *handshake(struct transport *transport, int for_push,
BUG("unknown protocol version");
}
data->got_remote_heads = 1;
transport->hash_algo = reader.hash_algo;
if (reader.line_peeked)
BUG("buffer must be empty at the end of handshake()");
@ -989,9 +996,16 @@ struct transport *transport_get(struct remote *remote, const char *url)
ret->smart_options->receivepack = remote->receivepack;
}
ret->hash_algo = &hash_algos[GIT_HASH_SHA1];
return ret;
}
const struct git_hash_algo *transport_get_hash_algo(struct transport *transport)
{
return transport->hash_algo;
}
int transport_set_option(struct transport *transport,
const char *name, const char *value)
{

View File

@ -115,6 +115,8 @@ struct transport {
struct git_transport_options *smart_options;
enum transport_family family;
const struct git_hash_algo *hash_algo;
};
#define TRANSPORT_PUSH_ALL (1<<0)
@ -243,6 +245,12 @@ int transport_push(struct repository *repo,
const struct ref *transport_get_remote_refs(struct transport *transport,
const struct argv_array *ref_prefixes);
/*
* Fetch the hash algorithm used by a remote.
*
* This can only be called after fetching the remote refs.
*/
const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);
int transport_fetch_refs(struct transport *transport, struct ref *refs);
void transport_unlock_pack(struct transport *transport);
int transport_disconnect(struct transport *transport);

View File

@ -1132,7 +1132,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
struct strbuf symref_info = STRBUF_INIT;
format_symref_info(&symref_info, &data->symref);
packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s agent=%s\n",
packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s object-format=%s agent=%s\n",
oid_to_hex(oid), refname_nons,
0, capabilities,
(data->allow_uor & ALLOW_TIP_SHA1) ?
@ -1142,6 +1142,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
data->stateless_rpc ? " no-done" : "",
symref_info.buf,
data->allow_filter ? " filter" : "",
the_hash_algo->name,
git_user_agent_sanitized());
strbuf_release(&symref_info);
} else {

View File

@ -105,6 +105,14 @@ char *xstrndup(const char *str, size_t len)
return xmemdupz(str, p ? p - str : len);
}
int xstrncmpz(const char *s, const char *t, size_t len)
{
int res = strncmp(s, t, len);
if (res)
return res;
return s[len] == '\0' ? 0 : 1;
}
void *xrealloc(void *ptr, size_t size)
{
void *ret;