Skip to content

Commit c7d6d41

Browse files
committed
Merge branch 'ab/mktag'
"git mktag" validates its input using its own rules before writing a tag object---it has been updated to share the logic with "git fsck". * ab/mktag: (23 commits) mktag: add a --[no-]strict option mktag: mark strings for translation mktag: convert to parse-options mktag: allow omitting the header/body \n separator mktag: allow turning off fsck.extraHeaderEntry fsck: make fsck_config() re-usable mktag: use fsck instead of custom verify_tag() mktag: use puts(str) instead of printf("%s\n", str) mktag: remove redundant braces in one-line body "if" mktag: use default strbuf_read() hint mktag tests: test verify_object() with replaced objects mktag tests: improve verify_object() test coverage mktag tests: test "hash-object" compatibility mktag tests: stress test whitespace handling mktag tests: run "fsck" after creating "mytag" mktag tests: don't create "mytag" twice mktag tests: don't redirect stderr to a file needlessly mktag tests: remove needless SHA-1 hardcoding mktag tests: use "test_commit" helper mktag tests: don't needlessly use a subshell ...
2 parents 66e871b + 06ce791 commit c7d6d41

File tree

8 files changed

+374
-234
lines changed

8 files changed

+374
-234
lines changed

Documentation/git-mktag.txt

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,60 @@ git-mktag(1)
33

44
NAME
55
----
6-
git-mktag - Creates a tag object
6+
git-mktag - Creates a tag object with extra validation
77

88

99
SYNOPSIS
1010
--------
1111
[verse]
1212
'git mktag'
1313

14+
OPTIONS
15+
-------
16+
17+
--strict::
18+
By default mktag turns on the equivalent of
19+
linkgit:git-fsck[1] `--strict` mode. Use `--no-strict` to
20+
disable it.
21+
1422
DESCRIPTION
1523
-----------
16-
Reads a tag contents on standard input and creates a tag object
17-
that can also be used to sign other objects.
1824

19-
The output is the new tag's <object> identifier.
25+
Reads a tag contents on standard input and creates a tag object. The
26+
output is the new tag's <object> identifier.
27+
28+
This command is mostly equivalent to linkgit:git-hash-object[1]
29+
invoked with `-t tag -w --stdin`. I.e. both of these will create and
30+
write a tag found in `my-tag`:
31+
32+
git mktag <my-tag
33+
git hash-object -t tag -w --stdin <my-tag
34+
35+
The difference is that mktag will die before writing the tag if the
36+
tag doesn't pass a linkgit:git-fsck[1] check.
37+
38+
The "fsck" check done mktag is stricter than what linkgit:git-fsck[1]
39+
would run by default in that all `fsck.<msg-id>` messages are promoted
40+
from warnings to errors (so e.g. a missing "tagger" line is an error).
41+
42+
Extra headers in the object are also an error under mktag, but ignored
43+
by linkgit:git-fsck[1]. This extra check can be turned off by setting
44+
the appropriate `fsck.<msg-id>` varible:
45+
46+
git -c fsck.extraHeaderEntry=ignore mktag <my-tag-with-headers
2047

2148
Tag Format
2249
----------
2350
A tag signature file, to be fed to this command's standard input,
2451
has a very simple fixed format: four lines of
2552

26-
object <sha1>
53+
object <hash>
2754
type <typename>
2855
tag <tagname>
2956
tagger <tagger>
3057

3158
followed by some 'optional' free-form message (some tags created
32-
by older Git may not have `tagger` line). The message, when
59+
by older Git may not have `tagger` line). The message, when it
3360
exists, is separated by a blank line from the header. The
3461
message part may contain a signature that Git itself doesn't
3562
care about, but that can be verified with gpg.

builtin/fsck.c

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,7 @@ static const char *printable_type(const struct object_id *oid,
7373

7474
static int fsck_config(const char *var, const char *value, void *cb)
7575
{
76-
if (strcmp(var, "fsck.skiplist") == 0) {
77-
const char *path;
78-
struct strbuf sb = STRBUF_INIT;
79-
80-
if (git_config_pathname(&path, var, value))
81-
return 1;
82-
strbuf_addf(&sb, "skiplist=%s", path);
83-
free((char *)path);
84-
fsck_set_msg_types(&fsck_obj_options, sb.buf);
85-
strbuf_release(&sb);
86-
return 0;
87-
}
88-
89-
if (skip_prefix(var, "fsck.", &var)) {
90-
fsck_set_msg_type(&fsck_obj_options, var, value);
91-
return 0;
92-
}
93-
94-
return git_default_config(var, value, cb);
76+
return fsck_config_internal(var, value, cb, &fsck_obj_options);
9577
}
9678

9779
static int objerror(struct object *obj, const char *err)

builtin/mktag.c

Lines changed: 83 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,179 +1,110 @@
11
#include "builtin.h"
2+
#include "parse-options.h"
23
#include "tag.h"
34
#include "replace-object.h"
45
#include "object-store.h"
6+
#include "fsck.h"
7+
#include "config.h"
58

6-
/*
7-
* A signature file has a very simple fixed format: four lines
8-
* of "object <sha1>" + "type <typename>" + "tag <tagname>" +
9-
* "tagger <committer>", followed by a blank line, a free-form tag
10-
* message and a signature block that git itself doesn't care about,
11-
* but that can be verified with gpg or similar.
12-
*
13-
* The first four lines are guaranteed to be at least 83 bytes:
14-
* "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
15-
* shortest possible type-line, "tag .\n" at 6 bytes is the shortest
16-
* single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
17-
* the shortest possible tagger-line.
18-
*/
19-
20-
/*
21-
* We refuse to tag something we can't verify. Just because.
22-
*/
23-
static int verify_object(const struct object_id *oid, const char *expected_type)
9+
static char const * const builtin_mktag_usage[] = {
10+
N_("git mktag"),
11+
NULL
12+
};
13+
static int option_strict = 1;
14+
15+
static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
16+
17+
static int mktag_config(const char *var, const char *value, void *cb)
2418
{
25-
int ret = -1;
26-
enum object_type type;
27-
unsigned long size;
28-
void *buffer = read_object_file(oid, &type, &size);
29-
const struct object_id *repl = lookup_replace_object(the_repository, oid);
30-
31-
if (buffer) {
32-
if (type == type_from_string(expected_type)) {
33-
ret = check_object_signature(the_repository, repl,
34-
buffer, size,
35-
expected_type);
19+
return fsck_config_internal(var, value, cb, &fsck_options);
20+
}
21+
22+
static int mktag_fsck_error_func(struct fsck_options *o,
23+
const struct object_id *oid,
24+
enum object_type object_type,
25+
int msg_type, const char *message)
26+
{
27+
switch (msg_type) {
28+
case FSCK_WARN:
29+
if (!option_strict) {
30+
fprintf_ln(stderr, _("warning: tag input does not pass fsck: %s"), message);
31+
return 0;
32+
3633
}
37-
free(buffer);
34+
/* fallthrough */
35+
case FSCK_ERROR:
36+
/*
37+
* We treat both warnings and errors as errors, things
38+
* like missing "tagger" lines are "only" warnings
39+
* under fsck, we've always considered them an error.
40+
*/
41+
fprintf_ln(stderr, _("error: tag input does not pass fsck: %s"), message);
42+
return 1;
43+
default:
44+
BUG(_("%d (FSCK_IGNORE?) should never trigger this callback"),
45+
msg_type);
3846
}
39-
return ret;
4047
}
4148

42-
static int verify_tag(char *buffer, unsigned long size)
49+
static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type)
4350
{
44-
int typelen;
45-
char type[20];
46-
struct object_id oid;
47-
const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb, *p;
48-
size_t len;
49-
50-
if (size < 84)
51-
return error("wanna fool me ? you obviously got the size wrong !");
52-
53-
buffer[size] = 0;
54-
55-
/* Verify object line */
56-
object = buffer;
57-
if (memcmp(object, "object ", 7))
58-
return error("char%d: does not start with \"object \"", 0);
59-
60-
if (parse_oid_hex(object + 7, &oid, &p))
61-
return error("char%d: could not get SHA1 hash", 7);
62-
63-
/* Verify type line */
64-
type_line = p + 1;
65-
if (memcmp(type_line - 1, "\ntype ", 6))
66-
return error("char%d: could not find \"\\ntype \"", 47);
67-
68-
/* Verify tag-line */
69-
tag_line = strchr(type_line, '\n');
70-
if (!tag_line)
71-
return error("char%"PRIuMAX": could not find next \"\\n\"",
72-
(uintmax_t) (type_line - buffer));
73-
tag_line++;
74-
if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
75-
return error("char%"PRIuMAX": no \"tag \" found",
76-
(uintmax_t) (tag_line - buffer));
77-
78-
/* Get the actual type */
79-
typelen = tag_line - type_line - strlen("type \n");
80-
if (typelen >= sizeof(type))
81-
return error("char%"PRIuMAX": type too long",
82-
(uintmax_t) (type_line+5 - buffer));
83-
84-
memcpy(type, type_line+5, typelen);
85-
type[typelen] = 0;
86-
87-
/* Verify that the object matches */
88-
if (verify_object(&oid, type))
89-
return error("char%d: could not verify object %s", 7, oid_to_hex(&oid));
90-
91-
/* Verify the tag-name: we don't allow control characters or spaces in it */
92-
tag_line += 4;
93-
for (;;) {
94-
unsigned char c = *tag_line++;
95-
if (c == '\n')
96-
break;
97-
if (c > ' ')
98-
continue;
99-
return error("char%"PRIuMAX": could not verify tag name",
100-
(uintmax_t) (tag_line - buffer));
101-
}
51+
int ret;
52+
enum object_type type;
53+
unsigned long size;
54+
void *buffer;
55+
const struct object_id *repl;
56+
57+
buffer = read_object_file(tagged_oid, &type, &size);
58+
if (!buffer)
59+
die(_("could not read tagged object '%s'"),
60+
oid_to_hex(tagged_oid));
61+
if (type != *tagged_type)
62+
die(_("object '%s' tagged as '%s', but is a '%s' type"),
63+
oid_to_hex(tagged_oid),
64+
type_name(*tagged_type), type_name(type));
65+
66+
repl = lookup_replace_object(the_repository, tagged_oid);
67+
ret = check_object_signature(the_repository, repl,
68+
buffer, size, type_name(*tagged_type));
69+
free(buffer);
10270

103-
/* Verify the tagger line */
104-
tagger_line = tag_line;
105-
106-
if (memcmp(tagger_line, "tagger ", 7))
107-
return error("char%"PRIuMAX": could not find \"tagger \"",
108-
(uintmax_t) (tagger_line - buffer));
109-
110-
/*
111-
* Check for correct form for name and email
112-
* i.e. " <" followed by "> " on _this_ line
113-
* No angle brackets within the name or email address fields.
114-
* No spaces within the email address field.
115-
*/
116-
tagger_line += 7;
117-
if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
118-
strpbrk(tagger_line, "<>\n") != lb+1 ||
119-
strpbrk(lb+2, "><\n ") != rb)
120-
return error("char%"PRIuMAX": malformed tagger field",
121-
(uintmax_t) (tagger_line - buffer));
122-
123-
/* Check for author name, at least one character, space is acceptable */
124-
if (lb == tagger_line)
125-
return error("char%"PRIuMAX": missing tagger name",
126-
(uintmax_t) (tagger_line - buffer));
127-
128-
/* timestamp, 1 or more digits followed by space */
129-
tagger_line = rb + 2;
130-
if (!(len = strspn(tagger_line, "0123456789")))
131-
return error("char%"PRIuMAX": missing tag timestamp",
132-
(uintmax_t) (tagger_line - buffer));
133-
tagger_line += len;
134-
if (*tagger_line != ' ')
135-
return error("char%"PRIuMAX": malformed tag timestamp",
136-
(uintmax_t) (tagger_line - buffer));
137-
tagger_line++;
138-
139-
/* timezone, 5 digits [+-]hhmm, max. 1400 */
140-
if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
141-
strspn(tagger_line+1, "0123456789") == 4 &&
142-
tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
143-
return error("char%"PRIuMAX": malformed tag timezone",
144-
(uintmax_t) (tagger_line - buffer));
145-
tagger_line += 6;
146-
147-
/* Verify the blank line separating the header from the body */
148-
if (*tagger_line != '\n')
149-
return error("char%"PRIuMAX": trailing garbage in tag header",
150-
(uintmax_t) (tagger_line - buffer));
151-
152-
/* The actual stuff afterwards we don't care about.. */
153-
return 0;
71+
return ret;
15472
}
15573

15674
int cmd_mktag(int argc, const char **argv, const char *prefix)
15775
{
76+
static struct option builtin_mktag_options[] = {
77+
OPT_BOOL(0, "strict", &option_strict,
78+
N_("enable more strict checking")),
79+
OPT_END(),
80+
};
15881
struct strbuf buf = STRBUF_INIT;
82+
struct object_id tagged_oid;
83+
int tagged_type;
15984
struct object_id result;
16085

161-
if (argc != 1)
162-
usage("git mktag");
86+
argc = parse_options(argc, argv, NULL,
87+
builtin_mktag_options,
88+
builtin_mktag_usage, 0);
16389

164-
if (strbuf_read(&buf, 0, 4096) < 0) {
165-
die_errno("could not read from stdin");
166-
}
90+
if (strbuf_read(&buf, 0, 0) < 0)
91+
die_errno(_("could not read from stdin"));
92+
93+
fsck_options.error_func = mktag_fsck_error_func;
94+
fsck_set_msg_type(&fsck_options, "extraheaderentry", "warn");
95+
/* config might set fsck.extraHeaderEntry=* again */
96+
git_config(mktag_config, NULL);
97+
if (fsck_tag_standalone(NULL, buf.buf, buf.len, &fsck_options,
98+
&tagged_oid, &tagged_type))
99+
die(_("tag on stdin did not pass our strict fsck check"));
167100

168-
/* Verify it for some basic sanity: it needs to start with
169-
"object <sha1>\ntype\ntagger " */
170-
if (verify_tag(buf.buf, buf.len) < 0)
171-
die("invalid tag signature file");
101+
if (verify_object_in_tag(&tagged_oid, &tagged_type))
102+
die(_("tag on stdin did not refer to a valid object"));
172103

173104
if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0)
174-
die("unable to write tag file");
105+
die(_("unable to write tag file"));
175106

176107
strbuf_release(&buf);
177-
printf("%s\n", oid_to_hex(&result));
108+
puts(oid_to_hex(&result));
178109
return 0;
179110
}

0 commit comments

Comments
 (0)