fast-import: introduce 'done' command
authorSverre Rabbelier <srabbelier@gmail.com>
Sat, 16 Jul 2011 13:03:32 +0000 (15:03 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 19 Jul 2011 18:17:47 +0000 (11:17 -0700)
Add a 'done' command that causes fast-import to stop reading from the
stream and exit.

If the new --done command line flag was passed on the command line
(or a "feature done" declaration included at the start of the stream),
make the 'done' command mandatory.  So "git fast-import --done"'s
input format will be prefix-free, making errors easier to detect when
they show up as early termination at some convenient time of the
upstream of a pipe writing to fast-import.

Another possible application of the 'done' command would to be allow a
fast-import stream that is only a small part of a larger encapsulating
stream to be easily parsed, leaving the file offset after the "done\n"
so the other application can pick up from there.  This patch does not
teach fast-import to do that --- fast-import still uses buffered input
(stdio).

Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Sverre Rabbelier <srabbelier@gmail.com>
Acked-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-fast-import.txt
fast-import.c
t/t9300-fast-import.sh

index 249249aac7b2b9def824794b2bf71e2c7fe497ce..0fc68a911e9659e1d06355e872d2d84359ec9e9b 100644 (file)
@@ -101,6 +101,12 @@ OPTIONS
        when the `cat-blob` command is encountered in the stream.
        The default behaviour is to write to `stdout`.
 
+--done::
+       Require a `done` command at the end of the stream.
+       This option might be useful for detecting errors that
+       cause the frontend to terminate before it has started to
+       write a stream.
+
 --export-pack-edges=<file>::
        After creating a packfile, print a line of data to
        <file> listing the filename of the packfile and the last
@@ -330,6 +336,11 @@ and control the current import process.  More detailed discussion
        standard output.  This command is optional and is not needed
        to perform an import.
 
+`done`::
+       Marks the end of the stream. This command is optional
+       unless the `done` feature was requested using the
+       `--done` command line option or `feature done` command.
+
 `cat-blob`::
        Causes fast-import to print a blob in 'cat-file --batch'
        format to the file descriptor set with `--cat-blob-fd` or
@@ -1015,6 +1026,11 @@ notes::
        Versions of fast-import not supporting notes will exit
        with a message indicating so.
 
+done::
+       Error out if the stream ends without a 'done' command.
+       Without this feature, errors causing the frontend to end
+       abruptly at a convenient point in the stream can go
+       undetected.
 
 `option`
 ~~~~~~~~
@@ -1044,6 +1060,15 @@ not be passed as option:
 * cat-blob-fd
 * force
 
+`done`
+~~~~~~
+If the `done` feature is not in use, treated as if EOF was read.
+This can be used to tell fast-import to finish early.
+
+If the `--done` command line option or `feature done` command is
+in use, the `done` command is mandatory and marks the end of the
+stream.
+
 Crash Reports
 -------------
 If fast-import is supplied invalid input it will terminate with a
index 78d978684da2e2b79340c138e1b6300100df1221..8a8a91589782203e9d7455921a80a47d7a7b3710 100644 (file)
@@ -354,6 +354,7 @@ static unsigned int cmd_save = 100;
 static uintmax_t next_mark;
 static struct strbuf new_data = STRBUF_INIT;
 static int seen_data_command;
+static int require_explicit_termination;
 
 /* Signal handling */
 static volatile sig_atomic_t checkpoint_requested;
@@ -3139,6 +3140,8 @@ static int parse_one_feature(const char *feature, int from_stream)
                relative_marks_paths = 1;
        } else if (!strcmp(feature, "no-relative-marks")) {
                relative_marks_paths = 0;
+       } else if (!strcmp(feature, "done")) {
+               require_explicit_termination = 1;
        } else if (!strcmp(feature, "force")) {
                force_update = 1;
        } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
@@ -3288,6 +3291,8 @@ int main(int argc, const char **argv)
                        parse_reset_branch();
                else if (!strcmp("checkpoint", command_buf.buf))
                        parse_checkpoint();
+               else if (!strcmp("done", command_buf.buf))
+                       break;
                else if (!prefixcmp(command_buf.buf, "progress "))
                        parse_progress();
                else if (!prefixcmp(command_buf.buf, "feature "))
@@ -3307,6 +3312,9 @@ int main(int argc, const char **argv)
        if (!seen_data_command)
                parse_argv();
 
+       if (require_explicit_termination && feof(stdin))
+               die("stream ends early");
+
        end_packfile();
 
        dump_branches();
index 6b1ba6c858562b7c085951fb1d69d4d2371e866c..526231bbeb1eba8cbcc9d1b2c32dbd7b9be4b342 100755 (executable)
@@ -2197,6 +2197,48 @@ test_expect_success 'R: quiet option results in no stats being output' '
     test_cmp empty output
 '
 
+test_expect_success 'R: feature done means terminating "done" is mandatory' '
+       echo feature done | test_must_fail git fast-import &&
+       test_must_fail git fast-import --done </dev/null
+'
+
+test_expect_success 'R: terminating "done" with trailing gibberish is ok' '
+       git fast-import <<-\EOF &&
+       feature done
+       done
+       trailing gibberish
+       EOF
+       git fast-import <<-\EOF
+       done
+       more trailing gibberish
+       EOF
+'
+
+test_expect_success 'R: terminating "done" within commit' '
+       cat >expect <<-\EOF &&
+       OBJID
+       :000000 100644 OBJID OBJID A    hello.c
+       :000000 100644 OBJID OBJID A    hello2.c
+       EOF
+       git fast-import <<-EOF &&
+       commit refs/heads/done-ends
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<EOT
+       Commit terminated by "done" command
+       EOT
+       M 100644 inline hello.c
+       data <<EOT
+       Hello, world.
+       EOT
+       C hello.c hello2.c
+       done
+       EOF
+       git rev-list done-ends |
+       git diff-tree -r --stdin --root --always |
+       sed -e "s/$_x40/OBJID/g" >actual &&
+       test_cmp expect actual
+'
+
 cat >input <<EOF
 option git non-existing-option
 EOF