Browse Source

Merge branch 'sr/gfi-options'

* sr/gfi-options:
  fast-import: add (non-)relative-marks feature
  fast-import: allow for multiple --import-marks= arguments
  fast-import: test the new option command
  fast-import: add option command
  fast-import: add feature command
  fast-import: put marks reading in its own function
  fast-import: put option parsing code in separate functions
maint
Junio C Hamano 15 years ago
parent
commit
fa232d457e
  1. 79
      Documentation/git-fast-import.txt
  2. 317
      fast-import.c
  3. 152
      t/t9300-fast-import.sh

79
Documentation/git-fast-import.txt

@ -75,6 +75,20 @@ OPTIONS @@ -75,6 +75,20 @@ OPTIONS
set of marks. If a mark is defined to different values,
the last file wins.

--relative-marks::
After specifying --relative-marks= the paths specified
with --import-marks= and --export-marks= are relative
to an internal directory in the current repository.
In git-fast-import this means that the paths are relative
to the .git/info/fast-import directory. However, other
importers may use a different location.

--no-relative-marks::
Negates a previous --relative-marks. Allows for combining
relative and non-relative marks by interweaving
--(no-)-relative-marks= with the --(import|export)-marks=
options.

--export-pack-edges=<file>::
After creating a packfile, print a line of data to
<file> listing the filename of the packfile and the last
@ -303,6 +317,15 @@ and control the current import process. More detailed discussion @@ -303,6 +317,15 @@ and control the current import process. More detailed discussion
standard output. This command is optional and is not needed
to perform an import.

`feature`::
Require that fast-import supports the specified feature, or
abort if it does not.

`option`::
Specify any of the options listed under OPTIONS that do not
change stream semantic to suit the frontend's needs. This
command is optional and is not needed to perform an import.

`commit`
~~~~~~~~
Create or update a branch with a new commit, recording one logical
@ -846,6 +869,62 @@ Placing a `progress` command immediately after a `checkpoint` will @@ -846,6 +869,62 @@ Placing a `progress` command immediately after a `checkpoint` will
inform the reader when the `checkpoint` has been completed and it
can safely access the refs that fast-import updated.

`feature`
~~~~~~~~~
Require that fast-import supports the specified feature, or abort if
it does not.

....
'feature' SP <feature> LF
....

The <feature> part of the command may be any string matching
^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import.

Feature work identical as their option counterparts with the
exception of the import-marks feature, see below.

The following features are currently supported:

* date-format
* import-marks
* export-marks
* relative-marks
* no-relative-marks
* force

The import-marks behaves differently from when it is specified as
commandline option in that only one "feature import-marks" is allowed
per stream. Also, any --import-marks= specified on the commandline
will override those from the stream (if any).

`option`
~~~~~~~~
Processes the specified option so that git fast-import behaves in a
way that suits the frontend's needs.
Note that options specified by the frontend are overridden by any
options the user may specify to git fast-import itself.

....
'option' SP <option> LF
....

The `<option>` part of the command may contain any of the options
listed in the OPTIONS section that do not change import semantics,
without the leading '--' and is treated in the same way.

Option commands must be the first commands on the input (not counting
feature commands), to give an option command after any non-option
command is an error.

The following commandline options change import semantics and may therefore
not be passed as option:

* date-format
* import-marks
* export-marks
* force

Crash Reports
-------------
If fast-import is supplied invalid input it will terminate with a

317
fast-import.c

@ -295,6 +295,9 @@ static unsigned long branch_count; @@ -295,6 +295,9 @@ static unsigned long branch_count;
static unsigned long branch_load_count;
static int failure;
static FILE *pack_edges;
static unsigned int show_stats = 1;
static int global_argc;
static const char **global_argv;

/* Memory pools */
static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool);
@ -317,7 +320,10 @@ static unsigned int object_entry_alloc = 5000; @@ -317,7 +320,10 @@ static unsigned int object_entry_alloc = 5000;
static struct object_entry_pool *blocks;
static struct object_entry *object_table[1 << 16];
static struct mark_set *marks;
static const char *mark_file;
static const char *export_marks_file;
static const char *import_marks_file;
static int import_marks_file_from_stream;
static int relative_marks_paths;

/* Our last blob */
static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 };
@ -351,6 +357,9 @@ static struct recent_command *rc_free; @@ -351,6 +357,9 @@ static struct recent_command *rc_free;
static unsigned int cmd_save = 100;
static uintmax_t next_mark;
static struct strbuf new_data = STRBUF_INIT;
static int seen_data_command;

static void parse_argv(void);

static void write_branch_report(FILE *rpt, struct branch *b)
{
@ -454,8 +463,8 @@ static void write_crash_report(const char *err) @@ -454,8 +463,8 @@ static void write_crash_report(const char *err)
fputc('\n', rpt);
fputs("Marks\n", rpt);
fputs("-----\n", rpt);
if (mark_file)
fprintf(rpt, " exported to %s\n", mark_file);
if (export_marks_file)
fprintf(rpt, " exported to %s\n", export_marks_file);
else
dump_marks_helper(rpt, 0, marks);

@ -1602,13 +1611,13 @@ static void dump_marks(void) @@ -1602,13 +1611,13 @@ static void dump_marks(void)
int mark_fd;
FILE *f;

if (!mark_file)
if (!export_marks_file)
return;

mark_fd = hold_lock_file_for_update(&mark_lock, mark_file, 0);
mark_fd = hold_lock_file_for_update(&mark_lock, export_marks_file, 0);
if (mark_fd < 0) {
failure |= error("Unable to write marks file %s: %s",
mark_file, strerror(errno));
export_marks_file, strerror(errno));
return;
}

@ -1617,7 +1626,7 @@ static void dump_marks(void) @@ -1617,7 +1626,7 @@ static void dump_marks(void)
int saved_errno = errno;
rollback_lock_file(&mark_lock);
failure |= error("Unable to write marks file %s: %s",
mark_file, strerror(saved_errno));
export_marks_file, strerror(saved_errno));
return;
}

@ -1633,7 +1642,7 @@ static void dump_marks(void) @@ -1633,7 +1642,7 @@ static void dump_marks(void)
int saved_errno = errno;
rollback_lock_file(&mark_lock);
failure |= error("Unable to write marks file %s: %s",
mark_file, strerror(saved_errno));
export_marks_file, strerror(saved_errno));
return;
}

@ -1641,11 +1650,47 @@ static void dump_marks(void) @@ -1641,11 +1650,47 @@ static void dump_marks(void)
int saved_errno = errno;
rollback_lock_file(&mark_lock);
failure |= error("Unable to commit marks file %s: %s",
mark_file, strerror(saved_errno));
export_marks_file, strerror(saved_errno));
return;
}
}

static void read_marks(void)
{
char line[512];
FILE *f = fopen(import_marks_file, "r");
if (!f)
die_errno("cannot read '%s'", import_marks_file);
while (fgets(line, sizeof(line), f)) {
uintmax_t mark;
char *end;
unsigned char sha1[20];
struct object_entry *e;

end = strchr(line, '\n');
if (line[0] != ':' || !end)
die("corrupt mark line: %s", line);
*end = 0;
mark = strtoumax(line + 1, &end, 10);
if (!mark || end == line + 1
|| *end != ' ' || get_sha1(end + 1, sha1))
die("corrupt mark line: %s", line);
e = find_object(sha1);
if (!e) {
enum object_type type = sha1_object_info(sha1, NULL);
if (type < 0)
die("object not found: %s", sha1_to_hex(sha1));
e = insert_object(sha1);
e->type = type;
e->pack_id = MAX_PACK_ID;
e->offset = 1; /* just not zero! */
}
insert_mark(mark, e);
}
fclose(f);
}


static int read_next_command(void)
{
static int stdin_eof = 0;
@ -1666,6 +1711,12 @@ static int read_next_command(void) @@ -1666,6 +1711,12 @@ static int read_next_command(void)
if (stdin_eof)
return EOF;

if (!seen_data_command
&& prefixcmp(command_buf.buf, "feature ")
&& prefixcmp(command_buf.buf, "option ")) {
parse_argv();
}

rc = rc_free;
if (rc)
rc_free = rc->next;
@ -2420,39 +2471,140 @@ static void parse_progress(void) @@ -2420,39 +2471,140 @@ static void parse_progress(void)
skip_optional_lf();
}

static void import_marks(const char *input_file)
static char* make_fast_import_path(const char *path)
{
char line[512];
FILE *f = fopen(input_file, "r");
if (!f)
die_errno("cannot read '%s'", input_file);
while (fgets(line, sizeof(line), f)) {
uintmax_t mark;
char *end;
unsigned char sha1[20];
struct object_entry *e;
struct strbuf abs_path = STRBUF_INIT;

end = strchr(line, '\n');
if (line[0] != ':' || !end)
die("corrupt mark line: %s", line);
*end = 0;
mark = strtoumax(line + 1, &end, 10);
if (!mark || end == line + 1
|| *end != ' ' || get_sha1(end + 1, sha1))
die("corrupt mark line: %s", line);
e = find_object(sha1);
if (!e) {
enum object_type type = sha1_object_info(sha1, NULL);
if (type < 0)
die("object not found: %s", sha1_to_hex(sha1));
e = insert_object(sha1);
e->type = type;
e->pack_id = MAX_PACK_ID;
e->offset = 1; /* just not zero! */
}
insert_mark(mark, e);
if (!relative_marks_paths || is_absolute_path(path))
return xstrdup(path);
strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
return strbuf_detach(&abs_path, NULL);
}

static void option_import_marks(const char *marks, int from_stream)
{
if (import_marks_file) {
if (from_stream)
die("Only one import-marks command allowed per stream");

/* read previous mark file */
if(!import_marks_file_from_stream)
read_marks();
}
fclose(f);

import_marks_file = make_fast_import_path(marks);
import_marks_file_from_stream = from_stream;
}

static void option_date_format(const char *fmt)
{
if (!strcmp(fmt, "raw"))
whenspec = WHENSPEC_RAW;
else if (!strcmp(fmt, "rfc2822"))
whenspec = WHENSPEC_RFC2822;
else if (!strcmp(fmt, "now"))
whenspec = WHENSPEC_NOW;
else
die("unknown --date-format argument %s", fmt);
}

static void option_max_pack_size(const char *packsize)
{
max_packsize = strtoumax(packsize, NULL, 0) * 1024 * 1024;
}

static void option_depth(const char *depth)
{
max_depth = strtoul(depth, NULL, 0);
if (max_depth > MAX_DEPTH)
die("--depth cannot exceed %u", MAX_DEPTH);
}

static void option_active_branches(const char *branches)
{
max_active_branches = strtoul(branches, NULL, 0);
}

static void option_export_marks(const char *marks)
{
export_marks_file = make_fast_import_path(marks);
}

static void option_export_pack_edges(const char *edges)
{
if (pack_edges)
fclose(pack_edges);
pack_edges = fopen(edges, "a");
if (!pack_edges)
die_errno("Cannot open '%s'", edges);
}

static int parse_one_option(const char *option)
{
if (!prefixcmp(option, "max-pack-size=")) {
option_max_pack_size(option + 14);
} else if (!prefixcmp(option, "depth=")) {
option_depth(option + 6);
} else if (!prefixcmp(option, "active-branches=")) {
option_active_branches(option + 16);
} else if (!prefixcmp(option, "export-pack-edges=")) {
option_export_pack_edges(option + 18);
} else if (!prefixcmp(option, "quiet")) {
show_stats = 0;
} else if (!prefixcmp(option, "stats")) {
show_stats = 1;
} else {
return 0;
}

return 1;
}

static int parse_one_feature(const char *feature, int from_stream)
{
if (!prefixcmp(feature, "date-format=")) {
option_date_format(feature + 12);
} else if (!prefixcmp(feature, "import-marks=")) {
option_import_marks(feature + 13, from_stream);
} else if (!prefixcmp(feature, "export-marks=")) {
option_export_marks(feature + 13);
} else if (!prefixcmp(feature, "relative-marks")) {
relative_marks_paths = 1;
} else if (!prefixcmp(feature, "no-relative-marks")) {
relative_marks_paths = 0;
} else if (!prefixcmp(feature, "force")) {
force_update = 1;
} else {
return 0;
}

return 1;
}

static void parse_feature(void)
{
char *feature = command_buf.buf + 8;

if (seen_data_command)
die("Got feature command '%s' after data command", feature);

if (parse_one_feature(feature, 1))
return;

die("This version of fast-import does not support feature %s.", feature);
}

static void parse_option(void)
{
char *option = command_buf.buf + 11;

if (seen_data_command)
die("Got option command '%s' after data command", option);

if (parse_one_option(option))
return;

die("This version of fast-import does not support option: %s", option);
}

static int git_pack_config(const char *k, const char *v, void *cb)
@ -2479,9 +2631,35 @@ static int git_pack_config(const char *k, const char *v, void *cb) @@ -2479,9 +2631,35 @@ static int git_pack_config(const char *k, const char *v, void *cb)
static const char fast_import_usage[] =
"git fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";

static void parse_argv(void)
{
unsigned int i;

for (i = 1; i < global_argc; i++) {
const char *a = global_argv[i];

if (*a != '-' || !strcmp(a, "--"))
break;

if (parse_one_option(a + 2))
continue;

if (parse_one_feature(a + 2, 0))
continue;

die("unknown option %s", a);
}
if (i != global_argc)
usage(fast_import_usage);

seen_data_command = 1;
if (import_marks_file)
read_marks();
}

int main(int argc, const char **argv)
{
unsigned int i, show_stats = 1;
unsigned int i;

git_extract_argv0_path(argv[0]);

@ -2500,52 +2678,8 @@ int main(int argc, const char **argv) @@ -2500,52 +2678,8 @@ int main(int argc, const char **argv)
avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
marks = pool_calloc(1, sizeof(struct mark_set));

for (i = 1; i < argc; i++) {
const char *a = argv[i];

if (*a != '-' || !strcmp(a, "--"))
break;
else if (!prefixcmp(a, "--date-format=")) {
const char *fmt = a + 14;
if (!strcmp(fmt, "raw"))
whenspec = WHENSPEC_RAW;
else if (!strcmp(fmt, "rfc2822"))
whenspec = WHENSPEC_RFC2822;
else if (!strcmp(fmt, "now"))
whenspec = WHENSPEC_NOW;
else
die("unknown --date-format argument %s", fmt);
}
else if (!prefixcmp(a, "--max-pack-size="))
max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024;
else if (!prefixcmp(a, "--depth=")) {
max_depth = strtoul(a + 8, NULL, 0);
if (max_depth > MAX_DEPTH)
die("--depth cannot exceed %u", MAX_DEPTH);
}
else if (!prefixcmp(a, "--active-branches="))
max_active_branches = strtoul(a + 18, NULL, 0);
else if (!prefixcmp(a, "--import-marks="))
import_marks(a + 15);
else if (!prefixcmp(a, "--export-marks="))
mark_file = a + 15;
else if (!prefixcmp(a, "--export-pack-edges=")) {
if (pack_edges)
fclose(pack_edges);
pack_edges = fopen(a + 20, "a");
if (!pack_edges)
die_errno("Cannot open '%s'", a + 20);
} else if (!strcmp(a, "--force"))
force_update = 1;
else if (!strcmp(a, "--quiet"))
show_stats = 0;
else if (!strcmp(a, "--stats"))
show_stats = 1;
else
die("unknown option %s", a);
}
if (i != argc)
usage(fast_import_usage);
global_argc = argc;
global_argv = argv;

rc_free = pool_alloc(cmd_save * sizeof(*rc_free));
for (i = 0; i < (cmd_save - 1); i++)
@ -2568,9 +2702,20 @@ int main(int argc, const char **argv) @@ -2568,9 +2702,20 @@ int main(int argc, const char **argv)
parse_checkpoint();
else if (!prefixcmp(command_buf.buf, "progress "))
parse_progress();
else if (!prefixcmp(command_buf.buf, "feature "))
parse_feature();
else if (!prefixcmp(command_buf.buf, "option git "))
parse_option();
else if (!prefixcmp(command_buf.buf, "option "))
/* ignore non-git options*/;
else
die("Unsupported command: %s", command_buf.buf);
}

/* argv hasn't been parsed yet, do so */
if (!seen_data_command)
parse_argv();

end_packfile();

dump_branches();

152
t/t9300-fast-import.sh

@ -1254,4 +1254,156 @@ test_expect_success \ @@ -1254,4 +1254,156 @@ test_expect_success \
'Q: verify note for third commit' \
'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual'

###
### series R (feature and option)
###

cat >input <<EOF
feature no-such-feature-exists
EOF

test_expect_success 'R: abort on unsupported feature' '
test_must_fail git fast-import <input
'

cat >input <<EOF
feature date-format=now
EOF

test_expect_success 'R: supported feature is accepted' '
git fast-import <input
'

cat >input << EOF
blob
data 3
hi
feature date-format=now
EOF

test_expect_success 'R: abort on receiving feature after data command' '
test_must_fail git fast-import <input
'

cat >input << EOF
feature import-marks=git.marks
feature import-marks=git2.marks
EOF

test_expect_success 'R: only one import-marks feature allowed per stream' '
test_must_fail git fast-import <input
'

cat >input << EOF
feature export-marks=git.marks
blob
mark :1
data 3
hi

EOF

test_expect_success \
'R: export-marks feature results in a marks file being created' \
'cat input | git fast-import &&
grep :1 git.marks'

test_expect_success \
'R: export-marks options can be overriden by commandline options' \
'cat input | git fast-import --export-marks=other.marks &&
grep :1 other.marks'

cat >input << EOF
feature import-marks=marks.out
feature export-marks=marks.new
EOF

test_expect_success \
'R: import to output marks works without any content' \
'cat input | git fast-import &&
test_cmp marks.out marks.new'

cat >input <<EOF
feature import-marks=nonexistant.marks
feature export-marks=marks.new
EOF

test_expect_success \
'R: import marks prefers commandline marks file over the stream' \
'cat input | git fast-import --import-marks=marks.out &&
test_cmp marks.out marks.new'


cat >input <<EOF
feature import-marks=nonexistant.marks
feature export-marks=combined.marks
EOF

test_expect_success 'R: multiple --import-marks= should be honoured' '
head -n2 marks.out > one.marks &&
tail -n +3 marks.out > two.marks &&
git fast-import --import-marks=one.marks --import-marks=two.marks <input &&
test_cmp marks.out combined.marks
'

cat >input <<EOF
feature relative-marks
feature import-marks=relative.in
feature export-marks=relative.out
EOF

test_expect_success 'R: feature relative-marks should be honoured' '
mkdir -p .git/info/fast-import/ &&
cp marks.new .git/info/fast-import/relative.in &&
git fast-import <input &&
test_cmp marks.new .git/info/fast-import/relative.out
'

cat >input <<EOF
feature relative-marks
feature import-marks=relative.in
feature no-relative-marks
feature export-marks=non-relative.out
EOF

test_expect_success 'R: feature no-relative-marks should be honoured' '
git fast-import <input &&
test_cmp marks.new non-relative.out
'

cat >input << EOF
option git quiet
blob
data 3
hi

EOF

touch empty

test_expect_success 'R: quiet option results in no stats being output' '
cat input | git fast-import 2> output &&
test_cmp empty output
'

cat >input <<EOF
option git non-existing-option
EOF

test_expect_success 'R: die on unknown option' '
test_must_fail git fast-import <input
'

test_expect_success 'R: unknown commandline options are rejected' '\
test_must_fail git fast-import --non-existing-option < /dev/null
'

cat >input <<EOF
option non-existing-vcs non-existing-option
EOF

test_expect_success 'R: ignore non-git options' '
git fast-import <input
'

test_done

Loading…
Cancel
Save